Compare commits
50 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c29b93337c | ||
|
|
0584d07a1f | ||
|
|
45b9b33039 | ||
|
|
bdcd55c8a5 | ||
|
|
99c17f8521 | ||
|
|
6324449c16 | ||
|
|
d465592c0f | ||
|
|
f494efbf3f | ||
|
|
ba5d120fef | ||
|
|
c5106a63b5 | ||
|
|
9719ebbe88 | ||
|
|
2eaf431942 | ||
|
|
a7c5d2167c | ||
|
|
678636c57d | ||
|
|
30697a98bb | ||
|
|
c73f9a5458 | ||
|
|
9c8f301491 | ||
|
|
180532ee3e | ||
|
|
46bbe14bea | ||
|
|
fa4612cf1f | ||
|
|
4d21bd46d4 | ||
|
|
b5743819ec | ||
|
|
190543a361 | ||
|
|
7a27321bec | ||
|
|
97ca3a25e3 | ||
|
|
1fa7432dfe | ||
|
|
fa3b906dce | ||
|
|
2d33c90af8 | ||
|
|
23a42bab80 | ||
|
|
ae8f0d15a0 | ||
|
|
b6abfbce4a | ||
|
|
de0aa33107 | ||
|
|
921fee2ecc | ||
|
|
e8b5f6d997 | ||
|
|
654aea9a32 | ||
|
|
dc4bcbe618 | ||
|
|
8a44c94272 | ||
|
|
66013667d8 | ||
|
|
58e4742f12 | ||
|
|
727ca09e74 | ||
|
|
222c7cc914 | ||
|
|
335af8ee60 | ||
|
|
954991fd9c | ||
|
|
c4a87d9813 | ||
|
|
421c87c972 | ||
|
|
583afa7078 | ||
|
|
c4aa3de640 | ||
|
|
27c1fe9a48 | ||
|
|
6d80b80a82 | ||
|
|
65564e6f4c |
68 changed files with 1667 additions and 667 deletions
2
.github/workflows/cargo-build-test.yml
vendored
2
.github/workflows/cargo-build-test.yml
vendored
|
|
@ -12,7 +12,7 @@ env:
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "rust-rl"
|
name = "rust-rl"
|
||||||
version = "0.1.1"
|
version = "0.1.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,18 @@
|
||||||
|
|
||||||
#### using _rltk/bracket-lib_, and _specs_
|
#### using _rltk/bracket-lib_, and _specs_
|
||||||
|
|
||||||
check out the page in the header for the wasm version, pick [a release of your choice](https://github.com/Llywelwyn/rust-rl/releases), or build manually with:
|
[](https://github.com/Llywelwyn/rust-rl/actions/workflows/cargo-build-test.yml)
|
||||||
|
|
||||||
|
check out the page in the header for the wasm version, pick [a release](https://github.com/Llywelwyn/rust-rl/releases), or build manually with:
|
||||||
|
|
||||||
`git clone https://github.com/Llywelwyn/rust-rl/ && cd rust-rl && cargo build --release`,
|
`git clone https://github.com/Llywelwyn/rust-rl/ && cd rust-rl && cargo build --release`,
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
this year for roguelikedev does the complete tutorial, i followed along with thebracket's [_roguelike tutorial - in rust_](https://bfnightly.bracketproductions.com). the notes i made during the sprint are being kept below for posterity - further changes since then are noted in [changelog.txt](https://github.com/Llywelwyn/rust-rl/blob/9150ed39e45bee536060cdc769d274e639021012/changelog.txt), and in the release notes.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>boring details about the sprint where this project started</summary>
|
||||||
<details>
|
<details>
|
||||||
<summary>week 1</summary>
|
<summary>week 1</summary>
|
||||||
|
|
||||||
|
|
@ -155,3 +157,4 @@ this year for roguelikedev does the complete tutorial, i followed along with the
|
||||||

|

|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
</details>
|
||||||
|
|
|
||||||
20
changelog.md
Normal file
20
changelog.md
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
## v0.1.4
|
||||||
|
### added
|
||||||
|
- **overmap**: bare, but exists. player now starts on the overworld, and can move to local maps (like the old starting town) via >. can leave local maps back to the overmap by walking out of the map boundaries.
|
||||||
|
- **intrinsics**: speed, regeneration
|
||||||
|
- **damage types**: immunities, weaknesses, and resistances
|
||||||
|
- **full keyboard support**: examining and targeting can now be done via keyboard only
|
||||||
|
- **a config file** read at runtime, unfortunately not compatible with WASM builds yet
|
||||||
|
- **morgue files**: y/n prompt to write a morgue file on death to /morgue/foo.txt, or to the console for WASM builds
|
||||||
|
- **dungeon features**: just the basics so far. a grassy, forested treant room, some barracks, etc.
|
||||||
|
- **named maps**: "Town", "Dungeon"
|
||||||
|
- **map messages/hints**: "You hear <...>."
|
||||||
|
### changed
|
||||||
|
- **colour offsets** are now per-tile (and per-theme) instead of +-% globally. i.e. varying fg/bg offset on a per-tiletype basis
|
||||||
|
- **chatlog colours** are now consistent
|
||||||
|
### fixed
|
||||||
|
- negative starting mana
|
||||||
|
- status effects only ticking if mob turn aligned with turnclock turn
|
||||||
|
- map params not being saved on map transition
|
||||||
|
- mob turns not awaiting the particle queue (mobs moving around mid-animation)
|
||||||
|
- mobs not re-pathing if their path was blocked, causing traffic jams
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
2-September-2023
|
|
||||||
- fixes:
|
|
||||||
- negative starting mana
|
|
||||||
- confusion/status effects only being run if mob turn aligned with turnclock turn
|
|
||||||
|
|
||||||
30-August-2023
|
|
||||||
- added dungeon features: grassy forest room, and barracks variants (bunks, squads of mobtypes)
|
|
||||||
- added support for map messages: i.e. notifications for present dungeon features logged to chat every now and again
|
|
||||||
|
|
||||||
Pre-29-August-2023
|
|
||||||
- added overmap: bare, but exists. player now starts on the overworld, and can move to local maps (like the old starting town) via >. can leave local maps back to the overmap by walking out of the map boundaries.
|
|
||||||
- mouse begone: support still there if wanted, but targeting/e(x)amining can now be done via keyboard only
|
|
||||||
- added config.toml: non-wasm builds read from config.toml at runtime, or generate a new copy if not present in the exe dir. includes options for logging various details to the console, and visual choices like post-processing effects, all-black bgs vs. full-coloured, etc.
|
|
||||||
- improved morgue files: y/n prompt to write a morgue file on death (or write to console in the case of wasm), containing a map of the floor the player died on, class/race/attribute/etc. details, a fully identified backpack, and a list of significant events that took place this run w/ turn number
|
|
||||||
- refactored colour offsets: now per-tile (and per-theme), instead of global. now can include varying fg/bg offset for every type of tile.
|
|
||||||
- consistent chatlog colours: renderables for mobs, beatitude for items
|
|
||||||
- dungeon features: framework
|
|
||||||
- map identifiers (instead of displaying an incorrect depth) on ui: e.g. D1, D2, Town, Woods, etc.
|
|
||||||
- fixes:
|
|
||||||
- map params are saved on map transition, instead of only at creation. now bloodstains, vision, etc. will persist when changing between floors
|
|
||||||
- mob turns await an empty particle queue - no longer will they move mid-fireball animations
|
|
||||||
- fixed traffic jams - 1. mobs will calc the best path to any tile within range of their target, instead of trying to path directly onto the target tile, and 2. if saved path is blocked to a waypoint, mobs will recalc a new path to the same point
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
a - A -
|
a - insects A -
|
||||||
b - B -
|
b - B -
|
||||||
c - chickens C -
|
c - chickens C -
|
||||||
d - canines D -
|
d - canines D -
|
||||||
|
|
|
||||||
|
|
@ -49,3 +49,7 @@ Complex example, with negative AC:
|
||||||
bloodstains: if starts on bloodied tile, remove blood + heal, gain xp, grow (little dog -> dog), etc.
|
bloodstains: if starts on bloodied tile, remove blood + heal, gain xp, grow (little dog -> dog), etc.
|
||||||
- You have negative AC, so you roll 1d14 for damage reduction, and get an 8.
|
- You have negative AC, so you roll 1d14 for damage reduction, and get an 8.
|
||||||
- The total damage is 6 - 8 = -2, but damage can't be negative, so you take 1 point of damage.
|
- The total damage is 6 - 8 = -2, but damage can't be negative, so you take 1 point of damage.
|
||||||
|
|
||||||
|
tl;dr
|
||||||
|
1. Lower AC is better
|
||||||
|
2. Aim for 0 AC - it's an important breakpoint. Every point of AC before 0 counts for a lot.
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@
|
||||||
"id": "potion_health",
|
"id": "potion_health",
|
||||||
"name": { "name": "potion of health", "plural": "potions of health" },
|
"name": { "name": "potion of health", "plural": "potions of health" },
|
||||||
"renderable": { "glyph": "!", "fg": "#FF00FF", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "!", "fg": "#FF00FF", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "potion",
|
||||||
"weight": 1,
|
"weight": 1,
|
||||||
"value": 50,
|
"value": 50,
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE"],
|
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
||||||
"effects": { "heal": "4d4+2" },
|
"effects": { "heal": "4d4+2" },
|
||||||
"magic": { "class": "uncommon", "naming": "potion" }
|
"magic": { "class": "uncommon", "naming": "potion" }
|
||||||
},
|
},
|
||||||
|
|
@ -13,9 +14,10 @@
|
||||||
"id": "potion_health_weak",
|
"id": "potion_health_weak",
|
||||||
"name": { "name": "potion of lesser health", "plural": "potions of lesser health" },
|
"name": { "name": "potion of lesser health", "plural": "potions of lesser health" },
|
||||||
"renderable": { "glyph": "!", "fg": "#FF00FF", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "!", "fg": "#FF00FF", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "potion",
|
||||||
"weight": 1,
|
"weight": 1,
|
||||||
"value": 25,
|
"value": 25,
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE"],
|
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
||||||
"effects": { "heal": "2d4+2" },
|
"effects": { "heal": "2d4+2" },
|
||||||
"magic": { "class": "uncommon", "naming": "potion" }
|
"magic": { "class": "uncommon", "naming": "potion" }
|
||||||
},
|
},
|
||||||
|
|
@ -23,27 +25,30 @@
|
||||||
"id": "scroll_identify",
|
"id": "scroll_identify",
|
||||||
"name": { "name": "scroll of identify", "plural": "scrolls of identify" },
|
"name": { "name": "scroll of identify", "plural": "scrolls of identify" },
|
||||||
"renderable": { "glyph": "?", "fg": "#0FFFFF", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "?", "fg": "#0FFFFF", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "scroll",
|
||||||
"weight": 0.5,
|
"weight": 0.5,
|
||||||
"value": 100,
|
"value": 100,
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "IDENTIFY"],
|
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE", "IDENTIFY"],
|
||||||
"magic": { "class": "uncommon", "naming": "scroll" }
|
"magic": { "class": "uncommon", "naming": "scroll" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "scroll_removecurse",
|
"id": "scroll_removecurse",
|
||||||
"name": { "name": "scroll of remove curse", "plural": "scrolls of remove curse" },
|
"name": { "name": "scroll of remove curse", "plural": "scrolls of remove curse" },
|
||||||
"renderable": { "glyph": "?", "fg": "#0FFFFF", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "?", "fg": "#0FFFFF", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "scroll",
|
||||||
"weight": 0.5,
|
"weight": 0.5,
|
||||||
"value": 200,
|
"value": 200,
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "REMOVE_CURSE"],
|
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE", "REMOVE_CURSE"],
|
||||||
"magic": { "class": "rare", "naming": "scroll" }
|
"magic": { "class": "rare", "naming": "scroll" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "scroll_health",
|
"id": "scroll_health",
|
||||||
"name": { "name": "scroll of healing word", "plural": "scrolls of healing word" },
|
"name": { "name": "scroll of healing word", "plural": "scrolls of healing word" },
|
||||||
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "scroll",
|
||||||
"weight": 0.5,
|
"weight": 0.5,
|
||||||
"value": 50,
|
"value": 50,
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE"],
|
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
||||||
"effects": { "particle_line": "*;-;#53f06d;75.0;#f9ff9f;100.0", "ranged": "12", "heal": "1d4+2" },
|
"effects": { "particle_line": "*;-;#53f06d;75.0;#f9ff9f;100.0", "ranged": "12", "heal": "1d4+2" },
|
||||||
"magic": { "class": "uncommon", "naming": "scroll" }
|
"magic": { "class": "uncommon", "naming": "scroll" }
|
||||||
},
|
},
|
||||||
|
|
@ -51,9 +56,10 @@
|
||||||
"id": "scroll_mass_health",
|
"id": "scroll_mass_health",
|
||||||
"name": { "name": "scroll of mass healing word", "plural": "scrolls of mass healing word" },
|
"name": { "name": "scroll of mass healing word", "plural": "scrolls of mass healing word" },
|
||||||
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "scroll",
|
||||||
"weight": 0.5,
|
"weight": 0.5,
|
||||||
"value": 200,
|
"value": 200,
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE"],
|
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
||||||
"effects": { "particle": "*;#53f06d;200.0", "ranged": "12", "aoe": "3", "heal": "1d4+2" },
|
"effects": { "particle": "*;#53f06d;200.0", "ranged": "12", "aoe": "3", "heal": "1d4+2" },
|
||||||
"magic": { "class": "rare", "naming": "scroll" }
|
"magic": { "class": "rare", "naming": "scroll" }
|
||||||
},
|
},
|
||||||
|
|
@ -61,33 +67,36 @@
|
||||||
"id": "scroll_magicmissile",
|
"id": "scroll_magicmissile",
|
||||||
"name": { "name": "scroll of magic missile", "plural": "scrolls of magic missile" },
|
"name": { "name": "scroll of magic missile", "plural": "scrolls of magic missile" },
|
||||||
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "scroll",
|
||||||
"weight": 0.5,
|
"weight": 0.5,
|
||||||
"value": 50,
|
"value": 50,
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE"],
|
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
||||||
"effects": { "particle_line": "*;-;#00b7ff;75.0;#f4fc83;100.0", "ranged": "12", "damage": "3d4+3" },
|
"effects": { "particle_line": "*;-;#00b7ff;75.0;#f4fc83;100.0", "ranged": "12", "damage": "3d4+3;magic" },
|
||||||
"magic": { "class": "uncommon", "naming": "scroll" }
|
"magic": { "class": "uncommon", "naming": "scroll" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "scroll_embers",
|
"id": "scroll_embers",
|
||||||
"name": { "name": "scroll of embers", "plural": "scrolls of embers" },
|
"name": { "name": "scroll of embers", "plural": "scrolls of embers" },
|
||||||
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "scroll",
|
||||||
"weight": 0.5,
|
"weight": 0.5,
|
||||||
"value": 100,
|
"value": 100,
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE"],
|
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
||||||
"effects": { "particle": "*;#FFA500;200.0", "ranged": "10", "damage": "4d6", "aoe": "2" },
|
"effects": { "particle": "*;#FFA500;200.0", "ranged": "10", "damage": "4d6;fire", "aoe": "2" },
|
||||||
"magic": { "class": "uncommon", "naming": "scroll" }
|
"magic": { "class": "uncommon", "naming": "scroll" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "scroll_fireball",
|
"id": "scroll_fireball",
|
||||||
"name": { "name": "scroll of fireball", "plural": "scrolls of fireball" },
|
"name": { "name": "scroll of fireball", "plural": "scrolls of fireball" },
|
||||||
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "scroll",
|
||||||
"weight": 0.5,
|
"weight": 0.5,
|
||||||
"value": 200,
|
"value": 200,
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE"],
|
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
||||||
"effects": {
|
"effects": {
|
||||||
"particle_burst": "▓;*;~;#FFA500;#000000;500.0;#ffd381;60.0",
|
"particle_burst": "▓;*;~;#FFA500;#000000;500.0;#ffd381;60.0",
|
||||||
"ranged": "10",
|
"ranged": "10",
|
||||||
"damage": "8d6",
|
"damage": "8d6;fire",
|
||||||
"aoe": "3"
|
"aoe": "3"
|
||||||
},
|
},
|
||||||
"magic": { "class": "rare", "naming": "scroll" }
|
"magic": { "class": "rare", "naming": "scroll" }
|
||||||
|
|
@ -96,9 +105,10 @@
|
||||||
"id": "scroll_confusion",
|
"id": "scroll_confusion",
|
||||||
"name": { "name": "scroll of confusion", "plural": "scrolls of confusion" },
|
"name": { "name": "scroll of confusion", "plural": "scrolls of confusion" },
|
||||||
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "scroll",
|
||||||
"weight": 0.5,
|
"weight": 0.5,
|
||||||
"value": 100,
|
"value": 100,
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE"],
|
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
||||||
"effects": { "particle_line": "*;-;#ad56a6;75.0;#cacaca;100.0", "ranged": "10", "confusion": "4" },
|
"effects": { "particle_line": "*;-;#ad56a6;75.0;#cacaca;100.0", "ranged": "10", "confusion": "4" },
|
||||||
"magic": { "class": "uncommon", "naming": "scroll" }
|
"magic": { "class": "uncommon", "naming": "scroll" }
|
||||||
},
|
},
|
||||||
|
|
@ -106,9 +116,10 @@
|
||||||
"id": "scroll_mass_confusion",
|
"id": "scroll_mass_confusion",
|
||||||
"name": { "name": "scroll of mass confusion", "plural": "scrolls of mass confusion" },
|
"name": { "name": "scroll of mass confusion", "plural": "scrolls of mass confusion" },
|
||||||
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "scroll",
|
||||||
"weight": 0.5,
|
"weight": 0.5,
|
||||||
"value": 200,
|
"value": 200,
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE"],
|
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
||||||
"effects": { "particle": "*;#ad56a6;200.0", "ranged": "10", "aoe": "3", "confusion": "3" },
|
"effects": { "particle": "*;#ad56a6;200.0", "ranged": "10", "aoe": "3", "confusion": "3" },
|
||||||
"magic": { "class": "veryrare", "naming": "scroll" }
|
"magic": { "class": "veryrare", "naming": "scroll" }
|
||||||
},
|
},
|
||||||
|
|
@ -116,9 +127,10 @@
|
||||||
"id": "scroll_magicmap",
|
"id": "scroll_magicmap",
|
||||||
"name": { "name": "scroll of magic mapping", "plural": "scrolls of magic mapping" },
|
"name": { "name": "scroll of magic mapping", "plural": "scrolls of magic mapping" },
|
||||||
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "scroll",
|
||||||
"weight": 0.5,
|
"weight": 0.5,
|
||||||
"value": 50,
|
"value": 50,
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "MAGICMAP"],
|
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE", "MAGICMAP"],
|
||||||
"effects": {},
|
"effects": {},
|
||||||
"magic": { "class": "common", "naming": "scroll" }
|
"magic": { "class": "common", "naming": "scroll" }
|
||||||
},
|
},
|
||||||
|
|
@ -126,6 +138,7 @@
|
||||||
"id": "equip_dagger",
|
"id": "equip_dagger",
|
||||||
"name": { "name": "dagger", "plural": "daggers" },
|
"name": { "name": "dagger", "plural": "daggers" },
|
||||||
"renderable": { "glyph": ")", "fg": "#808080", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": ")", "fg": "#808080", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "weapon",
|
||||||
"weight": 1,
|
"weight": 1,
|
||||||
"value": 2,
|
"value": 2,
|
||||||
"flags": ["EQUIP_MELEE"],
|
"flags": ["EQUIP_MELEE"],
|
||||||
|
|
@ -135,6 +148,7 @@
|
||||||
"id": "equip_shortsword",
|
"id": "equip_shortsword",
|
||||||
"name": { "name": "shortsword", "plural": "shortswords" },
|
"name": { "name": "shortsword", "plural": "shortswords" },
|
||||||
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "weapon",
|
||||||
"weight": 2,
|
"weight": 2,
|
||||||
"value": 10,
|
"value": 10,
|
||||||
"flags": ["EQUIP_MELEE"],
|
"flags": ["EQUIP_MELEE"],
|
||||||
|
|
@ -144,6 +158,7 @@
|
||||||
"id": "equip_rapier",
|
"id": "equip_rapier",
|
||||||
"name": { "name": "rapier", "plural": "rapiers" },
|
"name": { "name": "rapier", "plural": "rapiers" },
|
||||||
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "weapon",
|
||||||
"weight": 2,
|
"weight": 2,
|
||||||
"value": 10,
|
"value": 10,
|
||||||
"flags": ["EQUIP_MELEE"],
|
"flags": ["EQUIP_MELEE"],
|
||||||
|
|
@ -153,6 +168,7 @@
|
||||||
"id": "equip_pitchfork",
|
"id": "equip_pitchfork",
|
||||||
"name": { "name": "pitchfork", "plural": "pitchforks" },
|
"name": { "name": "pitchfork", "plural": "pitchforks" },
|
||||||
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "weapon",
|
||||||
"weight": 2,
|
"weight": 2,
|
||||||
"value": 5,
|
"value": 5,
|
||||||
"flags": ["EQUIP_MELEE"],
|
"flags": ["EQUIP_MELEE"],
|
||||||
|
|
@ -162,6 +178,7 @@
|
||||||
"id": "equip_sickle",
|
"id": "equip_sickle",
|
||||||
"name": { "name": "sickle", "plural": "sickles" },
|
"name": { "name": "sickle", "plural": "sickles" },
|
||||||
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "weapon",
|
||||||
"weight": 2,
|
"weight": 2,
|
||||||
"value": 5,
|
"value": 5,
|
||||||
"flags": ["EQUIP_MELEE"],
|
"flags": ["EQUIP_MELEE"],
|
||||||
|
|
@ -171,6 +188,7 @@
|
||||||
"id": "equip_handaxe",
|
"id": "equip_handaxe",
|
||||||
"name": { "name": "handaxe", "plural": "handaxes" },
|
"name": { "name": "handaxe", "plural": "handaxes" },
|
||||||
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "weapon",
|
||||||
"weight": 2,
|
"weight": 2,
|
||||||
"value": 5,
|
"value": 5,
|
||||||
"flags": ["EQUIP_MELEE"],
|
"flags": ["EQUIP_MELEE"],
|
||||||
|
|
@ -180,6 +198,7 @@
|
||||||
"id": "equip_longsword",
|
"id": "equip_longsword",
|
||||||
"name": { "name": "longsword", "plural": "longswords" },
|
"name": { "name": "longsword", "plural": "longswords" },
|
||||||
"renderable": { "glyph": ")", "fg": "#FFF8DC", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": ")", "fg": "#FFF8DC", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "weapon",
|
||||||
"weight": 3,
|
"weight": 3,
|
||||||
"value": 15,
|
"value": 15,
|
||||||
"flags": ["EQUIP_MELEE"],
|
"flags": ["EQUIP_MELEE"],
|
||||||
|
|
@ -189,6 +208,7 @@
|
||||||
"id": "equip_smallshield",
|
"id": "equip_smallshield",
|
||||||
"name": { "name": "buckler", "plural": "bucklers" },
|
"name": { "name": "buckler", "plural": "bucklers" },
|
||||||
"renderable": { "glyph": "[", "fg": "#808080", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#808080", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "armour",
|
||||||
"weight": 2,
|
"weight": 2,
|
||||||
"value": 5,
|
"value": 5,
|
||||||
"flags": ["EQUIP_SHIELD"],
|
"flags": ["EQUIP_SHIELD"],
|
||||||
|
|
@ -198,6 +218,7 @@
|
||||||
"id": "equip_mediumshield",
|
"id": "equip_mediumshield",
|
||||||
"name": { "name": "medium shield", "plural": "medium shields" },
|
"name": { "name": "medium shield", "plural": "medium shields" },
|
||||||
"renderable": { "glyph": "[", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "armour",
|
||||||
"weight": 6,
|
"weight": 6,
|
||||||
"value": 10,
|
"value": 10,
|
||||||
"flags": ["EQUIP_SHIELD"],
|
"flags": ["EQUIP_SHIELD"],
|
||||||
|
|
@ -207,6 +228,7 @@
|
||||||
"id": "equip_largeshield",
|
"id": "equip_largeshield",
|
||||||
"name": { "name": "large shield", "plural": "large shields" },
|
"name": { "name": "large shield", "plural": "large shields" },
|
||||||
"renderable": { "glyph": "[", "fg": "#FFF8DC", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#FFF8DC", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "armour",
|
||||||
"weight": 12,
|
"weight": 12,
|
||||||
"value": 35,
|
"value": 35,
|
||||||
"flags": ["EQUIP_SHIELD"],
|
"flags": ["EQUIP_SHIELD"],
|
||||||
|
|
@ -216,6 +238,7 @@
|
||||||
"id": "equip_body_weakleather",
|
"id": "equip_body_weakleather",
|
||||||
"name": { "name": "leather jacket", "plural": "leather jackets" },
|
"name": { "name": "leather jacket", "plural": "leather jackets" },
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "armour",
|
||||||
"weight": 8,
|
"weight": 8,
|
||||||
"value": 5,
|
"value": 5,
|
||||||
"flags": ["EQUIP_BODY"],
|
"flags": ["EQUIP_BODY"],
|
||||||
|
|
@ -225,6 +248,7 @@
|
||||||
"id": "equip_body_leather",
|
"id": "equip_body_leather",
|
||||||
"name": { "name": "leather chestpiece", "plural": "leather chestpiece" },
|
"name": { "name": "leather chestpiece", "plural": "leather chestpiece" },
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "armour",
|
||||||
"weight": 10,
|
"weight": 10,
|
||||||
"value": 10,
|
"value": 10,
|
||||||
"flags": ["EQUIP_BODY"],
|
"flags": ["EQUIP_BODY"],
|
||||||
|
|
@ -234,6 +258,7 @@
|
||||||
"id": "equip_body_studdedleather",
|
"id": "equip_body_studdedleather",
|
||||||
"name": { "name": "studded leather chestpiece", "plural": "studded leather chestpieces" },
|
"name": { "name": "studded leather chestpiece", "plural": "studded leather chestpieces" },
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "armour",
|
||||||
"weight": 13,
|
"weight": 13,
|
||||||
"value": 45,
|
"value": 45,
|
||||||
"flags": ["EQUIP_BODY"],
|
"flags": ["EQUIP_BODY"],
|
||||||
|
|
@ -243,6 +268,7 @@
|
||||||
"id": "equip_body_ringmail_o",
|
"id": "equip_body_ringmail_o",
|
||||||
"name": { "name": "orcish ring mail", "plural": "orcish ring mail" },
|
"name": { "name": "orcish ring mail", "plural": "orcish ring mail" },
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "armour",
|
||||||
"weight": 45,
|
"weight": 45,
|
||||||
"value": 50,
|
"value": 50,
|
||||||
"flags": ["EQUIP_BODY"],
|
"flags": ["EQUIP_BODY"],
|
||||||
|
|
@ -252,6 +278,7 @@
|
||||||
"id": "equip_body_ringmail",
|
"id": "equip_body_ringmail",
|
||||||
"name": { "name": "ring mail", "plural": "ring mail" },
|
"name": { "name": "ring mail", "plural": "ring mail" },
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "armour",
|
||||||
"weight": 45,
|
"weight": 45,
|
||||||
"value": 70,
|
"value": 70,
|
||||||
"flags": ["EQUIP_BODY"],
|
"flags": ["EQUIP_BODY"],
|
||||||
|
|
@ -261,6 +288,7 @@
|
||||||
"id": "equip_head_leather",
|
"id": "equip_head_leather",
|
||||||
"name": { "name": "leather cap", "plural": "leather caps" },
|
"name": { "name": "leather cap", "plural": "leather caps" },
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "armour",
|
||||||
"weight": 2,
|
"weight": 2,
|
||||||
"value": 10,
|
"value": 10,
|
||||||
"flags": ["EQUIP_HEAD"],
|
"flags": ["EQUIP_HEAD"],
|
||||||
|
|
@ -270,6 +298,7 @@
|
||||||
"id": "equip_head_elvish",
|
"id": "equip_head_elvish",
|
||||||
"name": { "name": "elvish leather helm", "plural": "elvish leather helms" },
|
"name": { "name": "elvish leather helm", "plural": "elvish leather helms" },
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "armour",
|
||||||
"weight": 2,
|
"weight": 2,
|
||||||
"value": 25,
|
"value": 25,
|
||||||
"flags": ["EQUIP_HEAD"],
|
"flags": ["EQUIP_HEAD"],
|
||||||
|
|
@ -279,6 +308,7 @@
|
||||||
"id": "equip_head_o",
|
"id": "equip_head_o",
|
||||||
"name": { "name": "orcish helm", "plural": "orcish helm" },
|
"name": { "name": "orcish helm", "plural": "orcish helm" },
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "armour",
|
||||||
"weight": 6,
|
"weight": 6,
|
||||||
"value": 25,
|
"value": 25,
|
||||||
"flags": ["EQUIP_HEAD"],
|
"flags": ["EQUIP_HEAD"],
|
||||||
|
|
@ -288,6 +318,7 @@
|
||||||
"id": "equip_head_iron",
|
"id": "equip_head_iron",
|
||||||
"name": { "name": "iron helm", "plural": "iron helm" },
|
"name": { "name": "iron helm", "plural": "iron helm" },
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "armour",
|
||||||
"weight": 10,
|
"weight": 10,
|
||||||
"value": 45,
|
"value": 45,
|
||||||
"flags": ["EQUIP_HEAD"],
|
"flags": ["EQUIP_HEAD"],
|
||||||
|
|
@ -297,6 +328,7 @@
|
||||||
"id": "equip_feet_leather",
|
"id": "equip_feet_leather",
|
||||||
"name": { "name": "leather shoes", "plural": "leather shoes" },
|
"name": { "name": "leather shoes", "plural": "leather shoes" },
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "armour",
|
||||||
"weight": 2,
|
"weight": 2,
|
||||||
"value": 10,
|
"value": 10,
|
||||||
"flags": ["EQUIP_FEET"]
|
"flags": ["EQUIP_FEET"]
|
||||||
|
|
@ -305,6 +337,7 @@
|
||||||
"id": "equip_feet_elvish",
|
"id": "equip_feet_elvish",
|
||||||
"name": { "name": "elvish leather shoes", "plural": "elvish leather shoes" },
|
"name": { "name": "elvish leather shoes", "plural": "elvish leather shoes" },
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "armour",
|
||||||
"weight": 2,
|
"weight": 2,
|
||||||
"value": 25,
|
"value": 25,
|
||||||
"flags": ["EQUIP_FEET"],
|
"flags": ["EQUIP_FEET"],
|
||||||
|
|
@ -314,6 +347,7 @@
|
||||||
"id": "equip_feet_o",
|
"id": "equip_feet_o",
|
||||||
"name": { "name": "orcish boots", "plural": "orcish boots" },
|
"name": { "name": "orcish boots", "plural": "orcish boots" },
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "armour",
|
||||||
"weight": 6,
|
"weight": 6,
|
||||||
"value": 25,
|
"value": 25,
|
||||||
"flags": ["EQUIP_FEET"],
|
"flags": ["EQUIP_FEET"],
|
||||||
|
|
@ -323,6 +357,7 @@
|
||||||
"id": "equip_feet_iron",
|
"id": "equip_feet_iron",
|
||||||
"name": { "name": "iron boots", "plural": "iron boots" },
|
"name": { "name": "iron boots", "plural": "iron boots" },
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "armour",
|
||||||
"weight": 10,
|
"weight": 10,
|
||||||
"value": 45,
|
"value": 45,
|
||||||
"flags": ["EQUIP_FEET"],
|
"flags": ["EQUIP_FEET"],
|
||||||
|
|
@ -332,6 +367,7 @@
|
||||||
"id": "equip_neck_protection",
|
"id": "equip_neck_protection",
|
||||||
"name": { "name": "amulet of protection", "plural": "amulets of protection" },
|
"name": { "name": "amulet of protection", "plural": "amulets of protection" },
|
||||||
"renderable": { "glyph": "\"", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "\"", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "amulet",
|
||||||
"weight": 1,
|
"weight": 1,
|
||||||
"value": 200,
|
"value": 200,
|
||||||
"flags": ["EQUIP_NECK"],
|
"flags": ["EQUIP_NECK"],
|
||||||
|
|
@ -341,6 +377,7 @@
|
||||||
"id": "equip_back_protection",
|
"id": "equip_back_protection",
|
||||||
"name": { "name": "cloak of protection", "plural": "cloaks of protection" },
|
"name": { "name": "cloak of protection", "plural": "cloaks of protection" },
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "armour",
|
||||||
"weight": 1,
|
"weight": 1,
|
||||||
"value": 200,
|
"value": 200,
|
||||||
"flags": ["EQUIP_BACK"],
|
"flags": ["EQUIP_BACK"],
|
||||||
|
|
@ -350,26 +387,29 @@
|
||||||
"id": "wand_magicmissile",
|
"id": "wand_magicmissile",
|
||||||
"name": { "name": "wand of magic missile", "plural": "wands of magic missile" },
|
"name": { "name": "wand of magic missile", "plural": "wands of magic missile" },
|
||||||
"renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "wand",
|
||||||
"weight": 2,
|
"weight": 2,
|
||||||
"value": 100,
|
"value": 100,
|
||||||
"flags": ["CHARGES"],
|
"flags": ["CHARGES"],
|
||||||
"effects": { "ranged": "12", "damage": "3d4+3" },
|
"effects": { "ranged": "12", "damage": "3d4+3;magic" },
|
||||||
"magic": { "class": "uncommon", "naming": "wand" }
|
"magic": { "class": "uncommon", "naming": "wand" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "wand_fireball",
|
"id": "wand_fireball",
|
||||||
"name": { "name": "wand of fireball", "plural": "wands of fireball" },
|
"name": { "name": "wand of fireball", "plural": "wands of fireball" },
|
||||||
"renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "wand",
|
||||||
"weight": 2,
|
"weight": 2,
|
||||||
"value": 300,
|
"value": 300,
|
||||||
"flags": ["CHARGES"],
|
"flags": ["CHARGES"],
|
||||||
"effects": { "ranged": "10", "damage": "8d6", "aoe": "3" },
|
"effects": { "ranged": "10", "damage": "8d6;fire", "aoe": "3" },
|
||||||
"magic": { "class": "rare", "naming": "wand" }
|
"magic": { "class": "rare", "naming": "wand" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "wand_confusion",
|
"id": "wand_confusion",
|
||||||
"name": { "name": "wand of confusion", "plural": "wands of confusion" },
|
"name": { "name": "wand of confusion", "plural": "wands of confusion" },
|
||||||
"renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "wand",
|
||||||
"weight": 2,
|
"weight": 2,
|
||||||
"value": 200,
|
"value": 200,
|
||||||
"flags": ["CHARGES"],
|
"flags": ["CHARGES"],
|
||||||
|
|
@ -380,6 +420,7 @@
|
||||||
"id": "wand_digging",
|
"id": "wand_digging",
|
||||||
"name": { "name": "wand of digging", "plural": "wands of digging" },
|
"name": { "name": "wand of digging", "plural": "wands of digging" },
|
||||||
"renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "wand",
|
||||||
"weight": 2,
|
"weight": 2,
|
||||||
"value": 300,
|
"value": 300,
|
||||||
"flags": ["CHARGES", "DIGGER"],
|
"flags": ["CHARGES", "DIGGER"],
|
||||||
|
|
@ -390,16 +431,18 @@
|
||||||
"id": "food_rations",
|
"id": "food_rations",
|
||||||
"name": { "name": "rations", "plural": "rations" },
|
"name": { "name": "rations", "plural": "rations" },
|
||||||
"renderable": { "glyph": "%", "fg": "#FFA07A", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "%", "fg": "#FFA07A", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "comestible",
|
||||||
"weight": 1,
|
"weight": 1,
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"flags": ["FOOD", "CONSUMABLE"]
|
"flags": ["FOOD", "CONSUMABLE", "STACKABLE"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "food_apple",
|
"id": "food_apple",
|
||||||
"name": { "name": "apple", "plural": "apples" },
|
"name": { "name": "apple", "plural": "apples" },
|
||||||
"renderable": { "glyph": "%", "fg": "#00FF00", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "%", "fg": "#00FF00", "bg": "#000000", "order": 2 },
|
||||||
|
"class": "comestible",
|
||||||
"weight": 0.5,
|
"weight": 0.5,
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"flags": ["FOOD", "CONSUMABLE"]
|
"flags": ["FOOD", "CONSUMABLE", "STACKABLE"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
133
raws/mobs.json
133
raws/mobs.json
|
|
@ -62,7 +62,6 @@
|
||||||
"renderable": { "glyph": "@", "fg": "#034efc", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "@", "fg": "#034efc", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["NEUTRAL", "RANDOM_PATH", "IS_HUMAN"],
|
"flags": ["NEUTRAL", "RANDOM_PATH", "IS_HUMAN"],
|
||||||
"level": 2,
|
"level": 2,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d8" }],
|
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d8" }],
|
||||||
"equipped": ["equip_shortsword", "equip_body_leather"],
|
"equipped": ["equip_shortsword", "equip_body_leather"],
|
||||||
"quips": ["You wont catch me down the mine.", "Staying out of trouble?"]
|
"quips": ["You wont catch me down the mine.", "Staying out of trouble?"]
|
||||||
|
|
@ -73,7 +72,6 @@
|
||||||
"renderable": { "glyph": "r", "fg": "#aa6000", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "r", "fg": "#aa6000", "bg": "#000000", "order": 1 },
|
||||||
"flags": [],
|
"flags": [],
|
||||||
"bac": 6,
|
"bac": 6,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }],
|
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }],
|
||||||
"loot": { "table": "food", "chance": 0.1 }
|
"loot": { "table": "food", "chance": 0.1 }
|
||||||
},
|
},
|
||||||
|
|
@ -83,7 +81,6 @@
|
||||||
"renderable": { "glyph": "c", "fg": "#BB6000", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "c", "fg": "#BB6000", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["HERBIVORE"],
|
"flags": ["HERBIVORE"],
|
||||||
"bac": 8,
|
"bac": 8,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }]
|
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -92,7 +89,6 @@
|
||||||
"renderable": { "glyph": "q", "fg": "#a57037", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "q", "fg": "#a57037", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["HERBIVORE"],
|
"flags": ["HERBIVORE"],
|
||||||
"bac": 8,
|
"bac": 8,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "kicks", "hit_bonus": 0, "damage": "1d2" }]
|
"attacks": [{ "name": "kicks", "hit_bonus": 0, "damage": "1d2" }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -101,7 +97,6 @@
|
||||||
"renderable": { "glyph": "q", "fg": "#e7e7e7", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "q", "fg": "#e7e7e7", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["HERBIVORE", "SMALL_GROUP"],
|
"flags": ["HERBIVORE", "SMALL_GROUP"],
|
||||||
"bac": 10,
|
"bac": 10,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "kicks", "hit_bonus": 0, "damage": "1d2" }]
|
"attacks": [{ "name": "kicks", "hit_bonus": 0, "damage": "1d2" }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -110,7 +105,6 @@
|
||||||
"renderable": { "glyph": "c", "fg": "#fae478", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "c", "fg": "#fae478", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["HERBIVORE"],
|
"flags": ["HERBIVORE"],
|
||||||
"bac": 10,
|
"bac": 10,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }]
|
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -121,7 +115,6 @@
|
||||||
"level": 3,
|
"level": 3,
|
||||||
"bac": 6,
|
"bac": 6,
|
||||||
"speed": 16,
|
"speed": 16,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [
|
"attacks": [
|
||||||
{ "name": "kicks", "hit_bonus": 0, "damage": "1d6" },
|
{ "name": "kicks", "hit_bonus": 0, "damage": "1d6" },
|
||||||
{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }
|
{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }
|
||||||
|
|
@ -136,7 +129,6 @@
|
||||||
"level": 5,
|
"level": 5,
|
||||||
"bac": 5,
|
"bac": 5,
|
||||||
"speed": 20,
|
"speed": 20,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [
|
"attacks": [
|
||||||
{ "name": "kicks", "hit_bonus": 0, "damage": "1d8" },
|
{ "name": "kicks", "hit_bonus": 0, "damage": "1d8" },
|
||||||
{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }
|
{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }
|
||||||
|
|
@ -150,7 +142,6 @@
|
||||||
"level": 7,
|
"level": 7,
|
||||||
"bac": 4,
|
"bac": 4,
|
||||||
"speed": 24,
|
"speed": 24,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [
|
"attacks": [
|
||||||
{ "name": "kicks", "hit_bonus": 0, "damage": "1d10" },
|
{ "name": "kicks", "hit_bonus": 0, "damage": "1d10" },
|
||||||
{ "name": "bites", "hit_bonus": 0, "damage": "1d4" }
|
{ "name": "bites", "hit_bonus": 0, "damage": "1d4" }
|
||||||
|
|
@ -163,7 +154,6 @@
|
||||||
"flags": ["SMALL_GROUP"],
|
"flags": ["SMALL_GROUP"],
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"bac": 7,
|
"bac": 7,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }],
|
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }],
|
||||||
"loot": { "table": "scrolls", "chance": 0.05 }
|
"loot": { "table": "scrolls", "chance": 0.05 }
|
||||||
},
|
},
|
||||||
|
|
@ -175,7 +165,6 @@
|
||||||
"level": 2,
|
"level": 2,
|
||||||
"bac": 6,
|
"bac": 6,
|
||||||
"speed": 18,
|
"speed": 18,
|
||||||
"vision_range": 16,
|
|
||||||
"quips": ["<woof!>", "<bark!>", "<grrr..>"],
|
"quips": ["<woof!>", "<bark!>", "<grrr..>"],
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d6" }]
|
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d6" }]
|
||||||
},
|
},
|
||||||
|
|
@ -187,7 +176,6 @@
|
||||||
"level": 4,
|
"level": 4,
|
||||||
"bac": 5,
|
"bac": 5,
|
||||||
"speed": 16,
|
"speed": 16,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d6" }]
|
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d6" }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -198,7 +186,6 @@
|
||||||
"level": 6,
|
"level": 6,
|
||||||
"bac": 4,
|
"bac": 4,
|
||||||
"speed": 15,
|
"speed": 15,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "2d4" }]
|
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "2d4" }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -208,7 +195,6 @@
|
||||||
"flags": ["SMALL_GROUP", "IS_GNOME"],
|
"flags": ["SMALL_GROUP", "IS_GNOME"],
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"speed": 6,
|
"speed": 6,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "claws", "hit_bonus": 0, "damage": "1d6" }],
|
"attacks": [{ "name": "claws", "hit_bonus": 0, "damage": "1d6" }],
|
||||||
"loot": { "table": "wands", "chance": 0.05 }
|
"loot": { "table": "wands", "chance": 0.05 }
|
||||||
},
|
},
|
||||||
|
|
@ -230,7 +216,6 @@
|
||||||
"flags": [],
|
"flags": [],
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"speed": 9,
|
"speed": 9,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d4" }]
|
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d4" }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -240,7 +225,6 @@
|
||||||
"flags": [],
|
"flags": [],
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"speed": 6,
|
"speed": 6,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d4" }],
|
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d4" }],
|
||||||
"loot": { "table": "food", "chance": 0.05 }
|
"loot": { "table": "food", "chance": 0.05 }
|
||||||
},
|
},
|
||||||
|
|
@ -286,7 +270,6 @@
|
||||||
"level": 2,
|
"level": 2,
|
||||||
"bac": 10,
|
"bac": 10,
|
||||||
"speed": 6,
|
"speed": 6,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "hacks", "hit_bonus": 0, "damage": "1d8" }],
|
"attacks": [{ "name": "hacks", "hit_bonus": 0, "damage": "1d8" }],
|
||||||
"equipped": ["equip_feet_iron"],
|
"equipped": ["equip_feet_iron"],
|
||||||
"loot": { "table": "equipment", "chance": 0.05 }
|
"loot": { "table": "equipment", "chance": 0.05 }
|
||||||
|
|
@ -322,17 +305,62 @@
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"bac": 3,
|
"bac": 3,
|
||||||
"speed": 12,
|
"speed": 12,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }],
|
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }],
|
||||||
"loot": { "table": "scrolls", "chance": 0.05 }
|
"loot": { "table": "scrolls", "chance": 0.05 }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "ant_worker",
|
||||||
|
"name": "worker ant",
|
||||||
|
"renderable": { "glyph": "a", "fg": "#ca7631", "bg": "#000000", "order": 1 },
|
||||||
|
"flags": ["SMALL_GROUP"],
|
||||||
|
"level": 2,
|
||||||
|
"bac": 3,
|
||||||
|
"speed": 18,
|
||||||
|
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d4" }],
|
||||||
|
"loot": { "table": "food", "chance": 0.05 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ant_soldier",
|
||||||
|
"name": "soldier ant",
|
||||||
|
"renderable": { "glyph": "a", "fg": "#ca3f26", "bg": "#000000", "order": 1 },
|
||||||
|
"flags": ["SMALL_GROUP", "POISON_RES"],
|
||||||
|
"level": 3,
|
||||||
|
"bac": 3,
|
||||||
|
"speed": 18,
|
||||||
|
"attacks": [
|
||||||
|
{ "name": "bites", "hit_bonus": 0, "damage": "2d4" },
|
||||||
|
{ "name": "stings", "hit_bonus": 0, "damage": "3d4;poison" }
|
||||||
|
],
|
||||||
|
"loot": { "table": "food", "chance": 0.05 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "caterpillar_cave",
|
||||||
|
"name": "caterpillar",
|
||||||
|
"renderable": { "glyph": "a", "fg": "#6b6b6b", "bg": "#000000", "order": 1 },
|
||||||
|
"flags": ["SMALL_GROUP"],
|
||||||
|
"level": 1,
|
||||||
|
"bac": 3,
|
||||||
|
"speed": 9,
|
||||||
|
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }],
|
||||||
|
"loot": { "table": "food", "chance": 0.05 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "caterpillar_giant",
|
||||||
|
"name": "giant caterpillar",
|
||||||
|
"renderable": { "glyph": "a", "fg": "#b9aeae", "bg": "#000000", "order": 1 },
|
||||||
|
"flags": ["SMALL_GROUP"],
|
||||||
|
"level": 2,
|
||||||
|
"bac": 7,
|
||||||
|
"speed": 9,
|
||||||
|
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d6" }],
|
||||||
|
"loot": { "table": "food", "chance": 0.10 }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "jackal",
|
"id": "jackal",
|
||||||
"name": "jackal",
|
"name": "jackal",
|
||||||
"renderable": { "glyph": "d", "fg": "#AA5500", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "d", "fg": "#AA5500", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["CARNIVORE", "SMALL_GROUP"],
|
"flags": ["CARNIVORE", "SMALL_GROUP"],
|
||||||
"bac": 7,
|
"bac": 7,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }]
|
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -341,7 +369,6 @@
|
||||||
"renderable": { "glyph": "d", "fg": "#FF0000", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "d", "fg": "#FF0000", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["CARNIVORE"],
|
"flags": ["CARNIVORE"],
|
||||||
"bac": 7,
|
"bac": 7,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }]
|
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -351,7 +378,6 @@
|
||||||
"flags": ["CARNIVORE", "SMALL_GROUP"],
|
"flags": ["CARNIVORE", "SMALL_GROUP"],
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"bac": 7,
|
"bac": 7,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d4" }]
|
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d4" }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -361,7 +387,6 @@
|
||||||
"flags": ["CARNIVORE"],
|
"flags": ["CARNIVORE"],
|
||||||
"level": 5,
|
"level": 5,
|
||||||
"bac": 4,
|
"bac": 4,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "2d4" }]
|
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "2d4" }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -371,7 +396,6 @@
|
||||||
"flags": [],
|
"flags": [],
|
||||||
"level": 2,
|
"level": 2,
|
||||||
"speed": 9,
|
"speed": 9,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d8" }],
|
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d8" }],
|
||||||
"loot": { "table": "wands", "chance": 0.05 }
|
"loot": { "table": "wands", "chance": 0.05 }
|
||||||
},
|
},
|
||||||
|
|
@ -382,7 +406,6 @@
|
||||||
"flags": ["SMALL_GROUP"],
|
"flags": ["SMALL_GROUP"],
|
||||||
"level": 1,
|
"level": 1,
|
||||||
"speed": 9,
|
"speed": 9,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d6" }],
|
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d6" }],
|
||||||
"loot": { "table": "equipment", "chance": 0.05 }
|
"loot": { "table": "equipment", "chance": 0.05 }
|
||||||
},
|
},
|
||||||
|
|
@ -393,7 +416,6 @@
|
||||||
"flags": ["LARGE_GROUP"],
|
"flags": ["LARGE_GROUP"],
|
||||||
"level": 2,
|
"level": 2,
|
||||||
"speed": 9,
|
"speed": 9,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d6" }],
|
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d6" }],
|
||||||
"loot": { "table": "equipment", "chance": 0.05 }
|
"loot": { "table": "equipment", "chance": 0.05 }
|
||||||
},
|
},
|
||||||
|
|
@ -404,13 +426,68 @@
|
||||||
"flags": ["MULTIATTACK"],
|
"flags": ["MULTIATTACK"],
|
||||||
"level": 5,
|
"level": 5,
|
||||||
"speed": 5,
|
"speed": 5,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [
|
"attacks": [
|
||||||
{ "name": "hits", "hit_bonus": 0, "damage": "2d4" },
|
{ "name": "hits", "hit_bonus": 0, "damage": "2d4" },
|
||||||
{ "name": "hits", "hit_bonus": 0, "damage": "2d4" }
|
{ "name": "hits", "hit_bonus": 0, "damage": "2d4" }
|
||||||
],
|
],
|
||||||
"loot": { "table": "equipment", "chance": 0.05 }
|
"loot": { "table": "equipment", "chance": 0.05 }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "warg",
|
||||||
|
"name": "warg",
|
||||||
|
"renderable": { "glyph": "d", "fg": "#8b7164", "bg": "#000000", "order": 1 },
|
||||||
|
"flags": ["SMALL_GROUP"],
|
||||||
|
"level": 7,
|
||||||
|
"bac": 4,
|
||||||
|
"speed": 12,
|
||||||
|
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "2d6" }],
|
||||||
|
"loot": { "table": "food", "chance": 0.05 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "jaguar",
|
||||||
|
"name": "jaguar",
|
||||||
|
"renderable": { "glyph": "f", "fg": "#d3b947", "bg": "#000000", "order": 1 },
|
||||||
|
"flags": ["MULTIATTACK"],
|
||||||
|
"level": 4,
|
||||||
|
"bac": 6,
|
||||||
|
"speed": 15,
|
||||||
|
"attacks": [
|
||||||
|
{ "name": "claws", "hit_bonus": 0, "damage": "1d4" },
|
||||||
|
{ "name": "claws", "hit_bonus": 0, "damage": "1d4" },
|
||||||
|
{ "name": "bites", "hit_bonus": 0, "damage": "1d8" }
|
||||||
|
],
|
||||||
|
"loot": { "table": "food", "chance": 0.05 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "lynx",
|
||||||
|
"name": "lynx",
|
||||||
|
"renderable": { "glyph": "f", "fg": "#b5d347", "bg": "#000000", "order": 1 },
|
||||||
|
"flags": ["MULTIATTACK"],
|
||||||
|
"level": 5,
|
||||||
|
"bac": 6,
|
||||||
|
"speed": 15,
|
||||||
|
"attacks": [
|
||||||
|
{ "name": "claws", "hit_bonus": 0, "damage": "1d4" },
|
||||||
|
{ "name": "claws", "hit_bonus": 0, "damage": "1d4" },
|
||||||
|
{ "name": "bites", "hit_bonus": 0, "damage": "1d10" }
|
||||||
|
],
|
||||||
|
"loot": { "table": "food", "chance": 0.05 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "panther",
|
||||||
|
"name": "panther",
|
||||||
|
"renderable": { "glyph": "f", "fg": "#58554e", "bg": "#000000", "order": 1 },
|
||||||
|
"flags": ["MULTIATTACK"],
|
||||||
|
"level": 5,
|
||||||
|
"bac": 6,
|
||||||
|
"speed": 15,
|
||||||
|
"attacks": [
|
||||||
|
{ "name": "claws", "hit_bonus": 0, "damage": "1d6" },
|
||||||
|
{ "name": "claws", "hit_bonus": 0, "damage": "1d6" },
|
||||||
|
{ "name": "bites", "hit_bonus": 0, "damage": "1d10" }
|
||||||
|
],
|
||||||
|
"loot": { "table": "food", "chance": 0.05 }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "ogre",
|
"id": "ogre",
|
||||||
"name": "ogre",
|
"name": "ogre",
|
||||||
|
|
@ -419,7 +496,6 @@
|
||||||
"level": 5,
|
"level": 5,
|
||||||
"bac": 5,
|
"bac": 5,
|
||||||
"speed": 10,
|
"speed": 10,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "2d5" }],
|
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "2d5" }],
|
||||||
"loot": { "table": "food", "chance": 0.05 }
|
"loot": { "table": "food", "chance": 0.05 }
|
||||||
},
|
},
|
||||||
|
|
@ -427,11 +503,10 @@
|
||||||
"id": "treant_small",
|
"id": "treant_small",
|
||||||
"name": "treant sapling",
|
"name": "treant sapling",
|
||||||
"renderable": { "glyph": "♠️", "fg": "#10570d", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "♠️", "fg": "#10570d", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["LARGE_GROUP", "GREEN_BLOOD"],
|
"flags": ["LARGE_GROUP", "GREEN_BLOOD", "FIRE_WEAK"],
|
||||||
"level": 2,
|
"level": 2,
|
||||||
"bac": 12,
|
"bac": 12,
|
||||||
"speed": 3,
|
"speed": 3,
|
||||||
"vision_range": 16,
|
|
||||||
"attacks": [{ "name": "lashes", "hit_bonus": 4, "damage": "1d8" }],
|
"attacks": [{ "name": "lashes", "hit_bonus": 4, "damage": "1d8" }],
|
||||||
"loot": { "table": "scrolls", "chance": 0.05 }
|
"loot": { "table": "scrolls", "chance": 0.05 }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,8 @@
|
||||||
{ "id": "kobold_large", "weight": 1, "difficulty": 2},
|
{ "id": "kobold_large", "weight": 1, "difficulty": 2},
|
||||||
{ "id": "rat_giant", "weight": 2, "difficulty": 2},
|
{ "id": "rat_giant", "weight": 2, "difficulty": 2},
|
||||||
{ "id": "coyote", "weight": 4, "difficulty": 2},
|
{ "id": "coyote", "weight": 4, "difficulty": 2},
|
||||||
|
{ "id": "caterpillar_cave", "weight": 2, "difficulty": 2},
|
||||||
|
{ "id": "caterpillar_giant", "weight": 2, "difficulty": 3},
|
||||||
{ "id": "zombie_orc", "weight": 1, "difficulty": 3},
|
{ "id": "zombie_orc", "weight": 1, "difficulty": 3},
|
||||||
{ "id": "zombie_dwarf", "weight": 1, "difficulty": 3},
|
{ "id": "zombie_dwarf", "weight": 1, "difficulty": 3},
|
||||||
{ "id": "gnome", "weight": 1, "difficulty": 3},
|
{ "id": "gnome", "weight": 1, "difficulty": 3},
|
||||||
|
|
@ -102,12 +104,18 @@
|
||||||
{ "id": "dwarf", "weight": 3, "difficulty": 4},
|
{ "id": "dwarf", "weight": 3, "difficulty": 4},
|
||||||
{ "id": "orc_hill", "weight": 1, "difficulty": 4},
|
{ "id": "orc_hill", "weight": 1, "difficulty": 4},
|
||||||
{ "id": "horse_little", "weight": 2, "difficulty": 4},
|
{ "id": "horse_little", "weight": 2, "difficulty": 4},
|
||||||
|
{ "id": "ant_worker", "weight": 3, "difficulty": 4},
|
||||||
{ "id": "dog", "weight": 1, "difficulty": 5},
|
{ "id": "dog", "weight": 1, "difficulty": 5},
|
||||||
{ "id": "wolf", "weight": 2, "difficulty": 6},
|
{ "id": "wolf", "weight": 2, "difficulty": 6},
|
||||||
|
{ "id": "jaguar", "weight": 2, "difficulty": 6},
|
||||||
|
{ "id": "ant_soldier", "weight": 2, "difficulty": 6},
|
||||||
{ "id": "orc_captain", "weight": 1, "difficulty": 7},
|
{ "id": "orc_captain", "weight": 1, "difficulty": 7},
|
||||||
{ "id": "dog_large", "weight": 1, "difficulty": 7},
|
{ "id": "dog_large", "weight": 1, "difficulty": 7},
|
||||||
|
{ "id": "lynx", "weight": 1, "difficulty": 7},
|
||||||
|
{ "id": "panther", "weight": 1, "difficulty": 7},
|
||||||
{ "id": "horse", "weight": 2, "difficulty": 7},
|
{ "id": "horse", "weight": 2, "difficulty": 7},
|
||||||
{ "id": "ogre", "weight": 1, "difficulty": 7},
|
{ "id": "ogre", "weight": 1, "difficulty": 7},
|
||||||
|
{ "id": "warg", "weight": 2, "difficulty": 8},
|
||||||
{ "id": "horse_large", "weight": 2, "difficulty": 9}
|
{ "id": "horse_large", "weight": 2, "difficulty": 9}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 35 KiB |
|
|
@ -7,7 +7,6 @@ pub struct ApproachAI {}
|
||||||
impl<'a> System<'a> for ApproachAI {
|
impl<'a> System<'a> for ApproachAI {
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
type SystemData = (
|
type SystemData = (
|
||||||
WriteExpect<'a, RandomNumberGenerator>,
|
|
||||||
WriteStorage<'a, TakingTurn>,
|
WriteStorage<'a, TakingTurn>,
|
||||||
WriteStorage<'a, WantsToApproach>,
|
WriteStorage<'a, WantsToApproach>,
|
||||||
WriteStorage<'a, Position>,
|
WriteStorage<'a, Position>,
|
||||||
|
|
@ -20,7 +19,6 @@ impl<'a> System<'a> for ApproachAI {
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
let (
|
let (
|
||||||
mut rng,
|
|
||||||
mut turns,
|
mut turns,
|
||||||
mut wants_to_approach,
|
mut wants_to_approach,
|
||||||
mut positions,
|
mut positions,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use crate::{
|
||||||
Map,
|
Map,
|
||||||
TakingTurn,
|
TakingTurn,
|
||||||
Confusion,
|
Confusion,
|
||||||
|
Intrinsics,
|
||||||
};
|
};
|
||||||
use bracket_lib::prelude::*;
|
use bracket_lib::prelude::*;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
@ -36,6 +37,7 @@ impl<'a> System<'a> for EnergySystem {
|
||||||
ReadStorage<'a, Name>,
|
ReadStorage<'a, Name>,
|
||||||
ReadExpect<'a, Point>,
|
ReadExpect<'a, Point>,
|
||||||
ReadStorage<'a, Confusion>,
|
ReadStorage<'a, Confusion>,
|
||||||
|
ReadStorage<'a, Intrinsics>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
|
|
@ -53,6 +55,7 @@ impl<'a> System<'a> for EnergySystem {
|
||||||
names,
|
names,
|
||||||
player_pos,
|
player_pos,
|
||||||
confusion,
|
confusion,
|
||||||
|
intrinsics,
|
||||||
) = data;
|
) = data;
|
||||||
// If not ticking, do nothing.
|
// If not ticking, do nothing.
|
||||||
if *runstate != RunState::Ticking {
|
if *runstate != RunState::Ticking {
|
||||||
|
|
@ -68,7 +71,7 @@ impl<'a> System<'a> for EnergySystem {
|
||||||
.insert(entity, TakingTurn {})
|
.insert(entity, TakingTurn {})
|
||||||
.expect("Unable to insert turn for turn counter.");
|
.expect("Unable to insert turn for turn counter.");
|
||||||
energy.current -= TURN_COST;
|
energy.current -= TURN_COST;
|
||||||
crate::gamelog::record_event(EVENT::TURN(1));
|
crate::gamelog::record_event(EVENT::Turn(1));
|
||||||
// Handle spawning mobs each turn
|
// Handle spawning mobs each turn
|
||||||
if CONFIG.logging.log_ticks {
|
if CONFIG.logging.log_ticks {
|
||||||
console::log(
|
console::log(
|
||||||
|
|
@ -87,20 +90,14 @@ impl<'a> System<'a> for EnergySystem {
|
||||||
&positions,
|
&positions,
|
||||||
!&confusion,
|
!&confusion,
|
||||||
).join() {
|
).join() {
|
||||||
let burden_modifier = if let Some(burden) = burdens.get(entity) {
|
let burden_modifier = get_burden_modifier(&burdens, entity);
|
||||||
match burden.level {
|
let overmap_mod = get_overmap_modifier(&map);
|
||||||
BurdenLevel::Burdened => SPEED_MOD_BURDENED,
|
let intrinsic_speed = get_intrinsic_speed(&intrinsics, entity);
|
||||||
BurdenLevel::Strained => SPEED_MOD_STRAINED,
|
|
||||||
BurdenLevel::Overloaded => SPEED_MOD_OVERLOADED,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
};
|
|
||||||
let overmap_mod = if map.overmap { SPEED_MOD_OVERMAP_TRAVEL } else { 1.0 };
|
|
||||||
// Every entity has a POTENTIAL equal to their speed.
|
// Every entity has a POTENTIAL equal to their speed.
|
||||||
let mut energy_potential: i32 = ((energy.speed as f32) *
|
let mut energy_potential: i32 = ((energy.speed as f32) *
|
||||||
burden_modifier *
|
burden_modifier *
|
||||||
overmap_mod) as i32;
|
overmap_mod *
|
||||||
|
intrinsic_speed) as i32;
|
||||||
// Increment current energy by NORMAL_SPEED for every
|
// Increment current energy by NORMAL_SPEED for every
|
||||||
// whole number of NORMAL_SPEEDS in their POTENTIAL.
|
// whole number of NORMAL_SPEEDS in their POTENTIAL.
|
||||||
while energy_potential >= NORMAL_SPEED {
|
while energy_potential >= NORMAL_SPEED {
|
||||||
|
|
@ -121,37 +118,61 @@ impl<'a> System<'a> for EnergySystem {
|
||||||
// has enough energy, they take a turn and decrement their energy
|
// has enough energy, they take a turn and decrement their energy
|
||||||
// by TURN_COST. If the current entity is the player, await input.
|
// by TURN_COST. If the current entity is the player, await input.
|
||||||
if energy.current >= TURN_COST {
|
if energy.current >= TURN_COST {
|
||||||
let mut my_turn = true;
|
|
||||||
energy.current -= TURN_COST;
|
energy.current -= TURN_COST;
|
||||||
if entity == *player {
|
if entity == *player {
|
||||||
*runstate = RunState::AwaitingInput;
|
*runstate = RunState::AwaitingInput;
|
||||||
} else {
|
} else if cull_turn_by_distance(&player_pos, pos) {
|
||||||
let distance = DistanceAlg::Pythagoras.distance2d(
|
continue;
|
||||||
*player_pos,
|
|
||||||
Point::new(pos.x, pos.y)
|
|
||||||
);
|
|
||||||
if distance > 20.0 {
|
|
||||||
my_turn = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if my_turn {
|
turns.insert(entity, TakingTurn {}).expect("Unable to insert turn.");
|
||||||
turns.insert(entity, TakingTurn {}).expect("Unable to insert turn.");
|
if CONFIG.logging.log_ticks {
|
||||||
if CONFIG.logging.log_ticks {
|
let name = if let Some(name) = names.get(entity) {
|
||||||
let name = if let Some(name) = names.get(entity) {
|
&name.name
|
||||||
&name.name
|
} else {
|
||||||
} else {
|
"Unknown entity"
|
||||||
"Unknown entity"
|
};
|
||||||
};
|
console::log(
|
||||||
console::log(
|
format!(
|
||||||
format!(
|
"ENERGY SYSTEM: {} granted a turn. [leftover energy: {}].",
|
||||||
"ENERGY SYSTEM: {} granted a turn. [leftover energy: {}].",
|
name,
|
||||||
name,
|
energy.current
|
||||||
energy.current
|
)
|
||||||
)
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_burden_modifier(burdens: &ReadStorage<Burden>, entity: Entity) -> f32 {
|
||||||
|
return if let Some(burden) = burdens.get(entity) {
|
||||||
|
match burden.level {
|
||||||
|
BurdenLevel::Burdened => SPEED_MOD_BURDENED,
|
||||||
|
BurdenLevel::Strained => SPEED_MOD_STRAINED,
|
||||||
|
BurdenLevel::Overloaded => SPEED_MOD_OVERLOADED,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_overmap_modifier(map: &ReadExpect<Map>) -> f32 {
|
||||||
|
return if map.overmap { SPEED_MOD_OVERMAP_TRAVEL } else { 1.0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cull_turn_by_distance(player_pos: &Point, pos: &Position) -> bool {
|
||||||
|
let distance = DistanceAlg::Pythagoras.distance2d(*player_pos, Point::new(pos.x, pos.y));
|
||||||
|
if distance > 20.0 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_intrinsic_speed(intrinsics: &ReadStorage<Intrinsics>, entity: Entity) -> f32 {
|
||||||
|
if let Some(intrinsics) = intrinsics.get(entity) {
|
||||||
|
if intrinsics.list.contains(&crate::Intrinsic::Speed) {
|
||||||
|
return 4.0 / 3.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use crate::{
|
||||||
Position,
|
Position,
|
||||||
RandomNumberGenerator,
|
RandomNumberGenerator,
|
||||||
TakingTurn,
|
TakingTurn,
|
||||||
|
Intrinsics,
|
||||||
};
|
};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
use crate::data::events::*;
|
use crate::data::events::*;
|
||||||
|
|
@ -36,10 +37,24 @@ impl<'a> System<'a> for RegenSystem {
|
||||||
ReadStorage<'a, HasClass>,
|
ReadStorage<'a, HasClass>,
|
||||||
ReadStorage<'a, Attributes>,
|
ReadStorage<'a, Attributes>,
|
||||||
WriteExpect<'a, RandomNumberGenerator>,
|
WriteExpect<'a, RandomNumberGenerator>,
|
||||||
|
ReadStorage<'a, Intrinsics>,
|
||||||
|
ReadExpect<'a, Entity>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
let (clock, entities, positions, mut pools, turns, player, classes, attributes, mut rng) = data;
|
let (
|
||||||
|
clock,
|
||||||
|
entities,
|
||||||
|
positions,
|
||||||
|
mut pools,
|
||||||
|
turns,
|
||||||
|
player,
|
||||||
|
classes,
|
||||||
|
attributes,
|
||||||
|
mut rng,
|
||||||
|
intrinsics,
|
||||||
|
player_entity,
|
||||||
|
) = data;
|
||||||
let mut clock_turn = false;
|
let mut clock_turn = false;
|
||||||
for (_e, _c, _t) in (&entities, &clock, &turns).join() {
|
for (_e, _c, _t) in (&entities, &clock, &turns).join() {
|
||||||
clock_turn = true;
|
clock_turn = true;
|
||||||
|
|
@ -56,19 +71,29 @@ impl<'a> System<'a> for RegenSystem {
|
||||||
}
|
}
|
||||||
// Player HP regen
|
// Player HP regen
|
||||||
let level = gamelog::get_event_count(EVENT::COUNT_LEVEL);
|
let level = gamelog::get_event_count(EVENT::COUNT_LEVEL);
|
||||||
if current_turn % get_player_hp_regen_turn(level) == 0 {
|
if
|
||||||
|
current_turn % get_player_hp_regen_turn(level) == 0 ||
|
||||||
|
intrinsics.get(*player_entity).unwrap().list.contains(&crate::Intrinsic::Regeneration)
|
||||||
|
{
|
||||||
for (_e, _p, pool, _player) in (&entities, &positions, &mut pools, &player).join() {
|
for (_e, _p, pool, _player) in (&entities, &positions, &mut pools, &player).join() {
|
||||||
try_hp_regen_tick(pool, get_player_hp_regen_per_tick(level));
|
try_hp_regen_tick(pool, get_player_hp_regen_per_tick(level));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Both MP regen
|
// Both MP regen
|
||||||
for (e, _p, pool) in (&entities, &positions, &mut pools).join() {
|
for (e, _p, pool) in (&entities, &positions, &mut pools).join() {
|
||||||
let is_wizard = if let Some(class) = classes.get(e) { class.name == Class::Wizard } else { false };
|
let is_wizard = if let Some(class) = classes.get(e) {
|
||||||
|
class.name == Class::Wizard
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
let numerator = if is_wizard { WIZARD_MP_REGEN_MOD } else { NONWIZARD_MP_REGEN_MOD };
|
let numerator = if is_wizard { WIZARD_MP_REGEN_MOD } else { NONWIZARD_MP_REGEN_MOD };
|
||||||
let multiplier: f32 = (numerator as f32) / (MP_REGEN_DIVISOR as f32);
|
let multiplier: f32 = (numerator as f32) / (MP_REGEN_DIVISOR as f32);
|
||||||
let mp_regen_tick = (((MP_REGEN_BASE - pool.level) as f32) * multiplier) as i32;
|
let mp_regen_tick = (((MP_REGEN_BASE - pool.level) as f32) * multiplier) as i32;
|
||||||
if current_turn % mp_regen_tick == 0 {
|
if current_turn % mp_regen_tick == 0 {
|
||||||
try_mana_regen_tick(pool, rng.roll_dice(1, get_mana_regen_per_tick(e, &attributes)));
|
try_mana_regen_tick(
|
||||||
|
pool,
|
||||||
|
rng.roll_dice(1, get_mana_regen_per_tick(e, &attributes))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,9 +65,7 @@ impl<'a> System<'a> for TurnStatusSystem {
|
||||||
not_confused.push(entity);
|
not_confused.push(entity);
|
||||||
if entity == *player_entity {
|
if entity == *player_entity {
|
||||||
logger = logger
|
logger = logger
|
||||||
.colour(renderable_colour(&renderables, entity))
|
|
||||||
.append("You")
|
.append("You")
|
||||||
.colour(WHITE)
|
|
||||||
.append("snap out of it.");
|
.append("snap out of it.");
|
||||||
log = true;
|
log = true;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -94,12 +92,10 @@ impl<'a> System<'a> for TurnStatusSystem {
|
||||||
not_my_turn.push(entity);
|
not_my_turn.push(entity);
|
||||||
if entity == *player_entity {
|
if entity == *player_entity {
|
||||||
logger = logger
|
logger = logger
|
||||||
.colour(renderable_colour(&renderables, entity))
|
|
||||||
.append("You")
|
.append("You")
|
||||||
.colour(WHITE)
|
|
||||||
.append("are confused!");
|
.append("are confused!");
|
||||||
log = true;
|
log = true;
|
||||||
gamelog::record_event(EVENT::PLAYER_CONFUSED(1));
|
gamelog::record_event(EVENT::PlayerConfused(1));
|
||||||
} else {
|
} else {
|
||||||
logger = logger
|
logger = logger
|
||||||
.append("The")
|
.append("The")
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use specs::error::NoError;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
use specs::saveload::{ ConvertSaveload, Marker };
|
use specs::saveload::{ ConvertSaveload, Marker };
|
||||||
use specs_derive::*;
|
use specs_derive::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::{ HashMap, HashSet };
|
||||||
|
|
||||||
// Serialization helper code. We need to implement ConvertSaveload for each type that contains an
|
// Serialization helper code. We need to implement ConvertSaveload for each type that contains an
|
||||||
// Entity.
|
// Entity.
|
||||||
|
|
@ -243,16 +243,55 @@ pub enum BUC {
|
||||||
Blessed,
|
Blessed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BUC {
|
||||||
|
pub fn noncursed(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
BUC::Cursed => false,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Clone)]
|
#[derive(Component, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Clone)]
|
||||||
pub struct Beatitude {
|
pub struct Beatitude {
|
||||||
pub buc: BUC,
|
pub buc: BUC,
|
||||||
pub known: bool,
|
pub known: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ItemType {
|
||||||
|
Amulet,
|
||||||
|
Weapon,
|
||||||
|
Armour,
|
||||||
|
Comestible,
|
||||||
|
Scroll,
|
||||||
|
Spellbook,
|
||||||
|
Potion,
|
||||||
|
Ring,
|
||||||
|
Wand,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ItemType {
|
||||||
|
pub fn string(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
ItemType::Amulet => "Amulets",
|
||||||
|
ItemType::Weapon => "Weapons",
|
||||||
|
ItemType::Armour => "Armour",
|
||||||
|
ItemType::Comestible => "Comestibles",
|
||||||
|
ItemType::Scroll => "Scrolls",
|
||||||
|
ItemType::Spellbook => "Spellbooks",
|
||||||
|
ItemType::Potion => "Potions",
|
||||||
|
ItemType::Ring => "Rings",
|
||||||
|
ItemType::Wand => "Wands",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
pub weight: f32, // in lbs
|
pub weight: f32, // in lbs
|
||||||
pub value: f32, // base
|
pub value: f32, // base
|
||||||
|
pub category: ItemType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
|
||||||
|
|
@ -316,6 +355,7 @@ pub enum WeaponAttribute {
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
#[derive(Component, Serialize, Deserialize, Clone)]
|
||||||
pub struct MeleeWeapon {
|
pub struct MeleeWeapon {
|
||||||
|
pub damage_type: DamageType,
|
||||||
pub attribute: WeaponAttribute,
|
pub attribute: WeaponAttribute,
|
||||||
pub damage_n_dice: i32,
|
pub damage_n_dice: i32,
|
||||||
pub damage_die_type: i32,
|
pub damage_die_type: i32,
|
||||||
|
|
@ -326,6 +366,7 @@ pub struct MeleeWeapon {
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct NaturalAttack {
|
pub struct NaturalAttack {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub damage_type: DamageType,
|
||||||
pub damage_n_dice: i32,
|
pub damage_n_dice: i32,
|
||||||
pub damage_die_type: i32,
|
pub damage_die_type: i32,
|
||||||
pub damage_bonus: i32,
|
pub damage_bonus: i32,
|
||||||
|
|
@ -365,8 +406,108 @@ pub struct ProvidesHealing {
|
||||||
pub modifier: i32,
|
pub modifier: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum DamageType {
|
||||||
|
Physical,
|
||||||
|
Magic, // e.g. magic missiles, silvered weapons
|
||||||
|
Fire, // e.g. fireball
|
||||||
|
Cold, // e.g. cone of cold
|
||||||
|
Poison, // e.g. poison gas
|
||||||
|
Forced, // Bypasses any immunities. e.g. Hunger ticks.
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DamageType {
|
||||||
|
pub fn is_magic(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
DamageType::Magic | DamageType::Fire | DamageType::Cold => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum DamageModifier {
|
||||||
|
None,
|
||||||
|
Weakness,
|
||||||
|
Resistance,
|
||||||
|
Immune,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DamageModifier {
|
||||||
|
const NONE_MOD: f32 = 1.0;
|
||||||
|
const WEAK_MOD: f32 = 2.0;
|
||||||
|
const RESIST_MOD: f32 = 0.5;
|
||||||
|
const IMMUNE_MOD: f32 = 0.0;
|
||||||
|
|
||||||
|
pub fn multiplier(&self) -> f32 {
|
||||||
|
match self {
|
||||||
|
DamageModifier::None => Self::NONE_MOD,
|
||||||
|
DamageModifier::Weakness => Self::WEAK_MOD,
|
||||||
|
DamageModifier::Resistance => Self::RESIST_MOD,
|
||||||
|
DamageModifier::Immune => Self::IMMUNE_MOD,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct HasDamageModifiers {
|
||||||
|
pub modifiers: HashMap<DamageType, DamageModifier>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasDamageModifiers {
|
||||||
|
pub fn modifier(&self, damage_type: &DamageType) -> &DamageModifier {
|
||||||
|
self.modifiers.get(damage_type).unwrap_or(&DamageModifier::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum Intrinsic {
|
||||||
|
Regeneration, // Regenerate 1 HP on every tick
|
||||||
|
Speed, // 4/3x speed multiplier
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Intrinsic {
|
||||||
|
pub fn describe(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Intrinsic::Regeneration => "regenerates health",
|
||||||
|
Intrinsic::Speed => "is hasted",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Intrinsics {
|
||||||
|
pub list: HashSet<Intrinsic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Intrinsics {
|
||||||
|
pub fn describe(&self) -> String {
|
||||||
|
let mut descriptions = Vec::new();
|
||||||
|
for intrinsic in &self.list {
|
||||||
|
descriptions.push(intrinsic.describe());
|
||||||
|
}
|
||||||
|
match descriptions.len() {
|
||||||
|
0 =>
|
||||||
|
unreachable!("describe() should never be called on an empty Intrinsics component."),
|
||||||
|
1 => format!("It {}.", descriptions[0]),
|
||||||
|
_ => {
|
||||||
|
let last = descriptions.pop().unwrap();
|
||||||
|
let joined = descriptions.join(", ");
|
||||||
|
format!("It {}, and {}.", joined, last)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct IntrinsicChanged {
|
||||||
|
pub gained: HashSet<Intrinsic>,
|
||||||
|
pub lost: HashSet<Intrinsic>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
||||||
pub struct InflictsDamage {
|
pub struct InflictsDamage {
|
||||||
|
pub damage_type: DamageType,
|
||||||
pub n_dice: i32,
|
pub n_dice: i32,
|
||||||
pub sides: i32,
|
pub sides: i32,
|
||||||
pub modifier: i32,
|
pub modifier: i32,
|
||||||
|
|
@ -507,3 +648,20 @@ pub struct EntityMoved {}
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct MultiAttack {}
|
pub struct MultiAttack {}
|
||||||
|
|
||||||
|
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct Stackable {}
|
||||||
|
|
||||||
|
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct WantsToRemoveKey {}
|
||||||
|
|
||||||
|
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct WantsToDelete {}
|
||||||
|
|
||||||
|
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct Key {
|
||||||
|
pub idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct WantsToAssignKey {}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ use super::{
|
||||||
Position,
|
Position,
|
||||||
Renderable,
|
Renderable,
|
||||||
RunState,
|
RunState,
|
||||||
|
WantsToRemoveKey,
|
||||||
|
WantsToDelete,
|
||||||
};
|
};
|
||||||
use bracket_lib::prelude::*;
|
use bracket_lib::prelude::*;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
@ -65,7 +67,17 @@ pub fn delete_the_dead(ecs: &mut World) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let (items_to_delete, loot_to_spawn) = handle_dead_entity_items(ecs, &dead);
|
let (mut items_to_delete, loot_to_spawn) = handle_dead_entity_items(ecs, &dead);
|
||||||
|
{
|
||||||
|
let entities = ecs.entities();
|
||||||
|
let removekeys = ecs.read_storage::<WantsToRemoveKey>();
|
||||||
|
let delete = ecs.read_storage::<WantsToDelete>();
|
||||||
|
// Add items marked for deletion to the list, but only if they've already had their
|
||||||
|
// key assignments handled, to ensurew we don't leave any dangling references behind.
|
||||||
|
for (e, _d, _r) in (&entities, &delete, !&removekeys).join() {
|
||||||
|
items_to_delete.push(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
for loot in loot_to_spawn {
|
for loot in loot_to_spawn {
|
||||||
crate::raws::spawn_named_entity(
|
crate::raws::spawn_named_entity(
|
||||||
&crate::raws::RAWS.lock().unwrap(),
|
&crate::raws::RAWS.lock().unwrap(),
|
||||||
|
|
@ -81,7 +93,8 @@ pub fn delete_the_dead(ecs: &mut World) {
|
||||||
}
|
}
|
||||||
// For everything that died, increment the event log, and delete.
|
// For everything that died, increment the event log, and delete.
|
||||||
for victim in dead {
|
for victim in dead {
|
||||||
gamelog::record_event(events::EVENT::TURN(1));
|
gamelog::record_event(events::EVENT::Turn(1));
|
||||||
|
// TODO: Delete stuff from inventory? This should be handled elsewhere.
|
||||||
ecs.delete_entity(victim).expect("Unable to delete.");
|
ecs.delete_entity(victim).expect("Unable to delete.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,17 @@ use serde::{ Deserialize, Serialize };
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub enum EVENT {
|
pub enum EVENT {
|
||||||
TURN(i32),
|
Turn(i32),
|
||||||
LEVEL(i32),
|
Level(i32),
|
||||||
CHANGED_FLOOR(String),
|
ChangedFloor(String),
|
||||||
PLAYER_CONFUSED(i32),
|
PlayerConfused(i32),
|
||||||
KICKED_SOMETHING(i32),
|
KickedSomething(i32),
|
||||||
BROKE_DOOR(i32),
|
BrokeDoor(i32),
|
||||||
LOOKED_FOR_HELP(i32),
|
LookedForHelp(i32),
|
||||||
KILLED(String),
|
Killed(String),
|
||||||
PLAYER_DIED(String),
|
PlayerDied(String),
|
||||||
DISCOVERED(String),
|
Discovered(String),
|
||||||
IDENTIFIED(String),
|
Identified(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EVENT {
|
impl EVENT {
|
||||||
|
|
@ -20,8 +20,8 @@ impl EVENT {
|
||||||
pub const COUNT_KILLED: &str = "killed";
|
pub const COUNT_KILLED: &str = "killed";
|
||||||
pub const COUNT_LEVEL: &str = "level";
|
pub const COUNT_LEVEL: &str = "level";
|
||||||
pub const COUNT_CHANGED_FLOOR: &str = "changed_floor";
|
pub const COUNT_CHANGED_FLOOR: &str = "changed_floor";
|
||||||
pub const COUNT_BROKE_DOOR: &str = "broke_door";
|
pub const COUNT_BROKE_DOOR: &str = "BrokeDoor";
|
||||||
pub const COUNT_PLAYER_CONFUSED: &str = "player_confused";
|
pub const COUNT_PLAYER_CONFUSED: &str = "PlayerConfused";
|
||||||
pub const COUNT_KICK: &str = "kick";
|
pub const COUNT_KICK: &str = "kick";
|
||||||
pub const COUNT_LOOKED_FOR_HELP: &str = "looked_for_help";
|
pub const COUNT_LOOKED_FOR_HELP: &str = "LookedForHelp";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ pub const NUTRITION_BLESSED: &str = "Delicious";
|
||||||
|
|
||||||
pub const LEVELUP_PLAYER: &str = "Welcome to experience level";
|
pub const LEVELUP_PLAYER: &str = "Welcome to experience level";
|
||||||
pub const YOU_PICKUP_ITEM: &str = "You pick up the";
|
pub const YOU_PICKUP_ITEM: &str = "You pick up the";
|
||||||
|
pub const NO_MORE_KEYS: &str = "Your backpack cannot accomodate any more items";
|
||||||
pub const YOU_DROP_ITEM: &str = "You drop the";
|
pub const YOU_DROP_ITEM: &str = "You drop the";
|
||||||
pub const YOU_EQUIP_ITEM: &str = "You equip the";
|
pub const YOU_EQUIP_ITEM: &str = "You equip the";
|
||||||
pub const YOU_REMOVE_ITEM: &str = "You unequip your";
|
pub const YOU_REMOVE_ITEM: &str = "You unequip your";
|
||||||
|
|
@ -32,7 +33,7 @@ pub const YOU_REMOVE_ITEM_CURSED: &str = "You can't remove the";
|
||||||
|
|
||||||
/// Prefixes death message.
|
/// Prefixes death message.
|
||||||
pub const PLAYER_DIED: &str = "You died!";
|
pub const PLAYER_DIED: &str = "You died!";
|
||||||
/// Death message specifiers. Appended after PLAYER_DIED.
|
/// Death message specifiers. Appended after PlayerDied.
|
||||||
pub const PLAYER_DIED_SUICIDE: &str = "You killed yourself";
|
pub const PLAYER_DIED_SUICIDE: &str = "You killed yourself";
|
||||||
pub const PLAYER_DIED_NAMED_ATTACKER: &str = "You were killed by";
|
pub const PLAYER_DIED_NAMED_ATTACKER: &str = "You were killed by";
|
||||||
pub const PLAYER_DIED_UNKNOWN: &str = "You were killed"; // Ultimately, this should never be used. Slowly include specific messages for any death.
|
pub const PLAYER_DIED_UNKNOWN: &str = "You were killed"; // Ultimately, this should never be used. Slowly include specific messages for any death.
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ use crate::{
|
||||||
HungerClock,
|
HungerClock,
|
||||||
HungerState,
|
HungerState,
|
||||||
Bleeds,
|
Bleeds,
|
||||||
|
HasDamageModifiers,
|
||||||
};
|
};
|
||||||
use crate::gui::with_article;
|
use crate::gui::with_article;
|
||||||
use crate::data::visuals::{ DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME };
|
use crate::data::visuals::{ DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME };
|
||||||
|
|
@ -27,8 +28,15 @@ pub fn inflict_damage(ecs: &mut World, damage: &EffectSpawner, target: Entity) {
|
||||||
let mut pools = ecs.write_storage::<Pools>();
|
let mut pools = ecs.write_storage::<Pools>();
|
||||||
if let Some(target_pool) = pools.get_mut(target) {
|
if let Some(target_pool) = pools.get_mut(target) {
|
||||||
if !target_pool.god {
|
if !target_pool.god {
|
||||||
if let EffectType::Damage { amount } = damage.effect_type {
|
if let EffectType::Damage { amount, damage_type } = damage.effect_type {
|
||||||
target_pool.hit_points.current -= amount;
|
let mult = if
|
||||||
|
let Some(modifiers) = ecs.read_storage::<HasDamageModifiers>().get(target)
|
||||||
|
{
|
||||||
|
modifiers.modifier(&damage_type).multiplier()
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
target_pool.hit_points.current -= ((amount as f32) * mult) as i32;
|
||||||
let bleeders = ecs.read_storage::<Bleeds>();
|
let bleeders = ecs.read_storage::<Bleeds>();
|
||||||
if let Some(bleeds) = bleeders.get(target) {
|
if let Some(bleeds) = bleeders.get(target) {
|
||||||
add_effect(
|
add_effect(
|
||||||
|
|
@ -221,12 +229,12 @@ pub fn entity_death(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
|
||||||
if let Some(source) = effect.source {
|
if let Some(source) = effect.source {
|
||||||
// If the target was the player, game over, and record source of death.
|
// If the target was the player, game over, and record source of death.
|
||||||
if target == *player {
|
if target == *player {
|
||||||
gamelog::record_event(EVENT::PLAYER_DIED(get_death_message(ecs, source)));
|
gamelog::record_event(EVENT::PlayerDied(get_death_message(ecs, source)));
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// If the player was the source, record the kill.
|
// If the player was the source, record the kill.
|
||||||
if let Some(tar_name) = names.get(target) {
|
if let Some(tar_name) = names.get(target) {
|
||||||
gamelog::record_event(EVENT::KILLED(tar_name.name.clone()));
|
gamelog::record_event(EVENT::Killed(tar_name.name.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Calc XP value of target.
|
// Calc XP value of target.
|
||||||
|
|
@ -246,7 +254,7 @@ pub fn entity_death(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
|
||||||
source_pools.level += 1;
|
source_pools.level += 1;
|
||||||
// If it was the PLAYER that levelled up:
|
// If it was the PLAYER that levelled up:
|
||||||
if ecs.read_storage::<Player>().get(source).is_some() {
|
if ecs.read_storage::<Player>().get(source).is_some() {
|
||||||
gamelog::record_event(EVENT::LEVEL(1));
|
gamelog::record_event(EVENT::Level(1));
|
||||||
gamelog::Logger
|
gamelog::Logger
|
||||||
::new()
|
::new()
|
||||||
.append(LEVELUP_PLAYER)
|
.append(LEVELUP_PLAYER)
|
||||||
|
|
@ -328,11 +336,11 @@ pub fn entity_death(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
|
||||||
if target == *player {
|
if target == *player {
|
||||||
if let Some(hc) = ecs.read_storage::<HungerClock>().get(target) {
|
if let Some(hc) = ecs.read_storage::<HungerClock>().get(target) {
|
||||||
if hc.state == HungerState::Starving {
|
if hc.state == HungerState::Starving {
|
||||||
gamelog::record_event(EVENT::PLAYER_DIED("You starved to death!".to_string()));
|
gamelog::record_event(EVENT::PlayerDied("You starved to death!".to_string()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
gamelog::record_event(
|
gamelog::record_event(
|
||||||
EVENT::PLAYER_DIED("You died from unknown causes!".to_string())
|
EVENT::PlayerDied("You died from unknown causes!".to_string())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
src/effects/intrinsics.rs
Normal file
11
src/effects/intrinsics.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
use super::{ EffectSpawner, EffectType };
|
||||||
|
use specs::prelude::*;
|
||||||
|
|
||||||
|
pub fn add_intrinsic(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
|
||||||
|
let intrinsic = if let EffectType::AddIntrinsic { intrinsic } = &effect.effect_type {
|
||||||
|
intrinsic
|
||||||
|
} else {
|
||||||
|
unreachable!("add_intrinsic() called with the wrong EffectType")
|
||||||
|
};
|
||||||
|
add_intr!(ecs, target, *intrinsic);
|
||||||
|
}
|
||||||
|
|
@ -4,12 +4,14 @@ use bracket_lib::prelude::*;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
use crate::components::*;
|
||||||
|
|
||||||
mod damage;
|
mod damage;
|
||||||
mod hunger;
|
mod hunger;
|
||||||
mod particles;
|
mod particles;
|
||||||
mod targeting;
|
mod targeting;
|
||||||
mod triggers;
|
mod triggers;
|
||||||
|
mod intrinsics;
|
||||||
|
|
||||||
pub use targeting::aoe_tiles;
|
pub use targeting::aoe_tiles;
|
||||||
|
|
||||||
|
|
@ -24,6 +26,7 @@ lazy_static! {
|
||||||
pub enum EffectType {
|
pub enum EffectType {
|
||||||
Damage {
|
Damage {
|
||||||
amount: i32,
|
amount: i32,
|
||||||
|
damage_type: DamageType,
|
||||||
},
|
},
|
||||||
Healing {
|
Healing {
|
||||||
amount: i32,
|
amount: i32,
|
||||||
|
|
@ -49,6 +52,9 @@ pub enum EffectType {
|
||||||
ModifyNutrition {
|
ModifyNutrition {
|
||||||
amount: i32,
|
amount: i32,
|
||||||
},
|
},
|
||||||
|
AddIntrinsic {
|
||||||
|
intrinsic: Intrinsic,
|
||||||
|
},
|
||||||
TriggerFire {
|
TriggerFire {
|
||||||
trigger: Entity,
|
trigger: Entity,
|
||||||
},
|
},
|
||||||
|
|
@ -151,6 +157,7 @@ fn tile_effect_hits_entities(effect: &EffectType) -> bool {
|
||||||
EffectType::Healing { .. } => true,
|
EffectType::Healing { .. } => true,
|
||||||
EffectType::ModifyNutrition { .. } => true,
|
EffectType::ModifyNutrition { .. } => true,
|
||||||
EffectType::Confusion { .. } => true,
|
EffectType::Confusion { .. } => true,
|
||||||
|
EffectType::AddIntrinsic { .. } => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -173,6 +180,7 @@ fn affect_entity(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
|
||||||
}
|
}
|
||||||
EffectType::EntityDeath => damage::entity_death(ecs, effect, target),
|
EffectType::EntityDeath => damage::entity_death(ecs, effect, target),
|
||||||
EffectType::ModifyNutrition { .. } => hunger::modify_nutrition(ecs, effect, target),
|
EffectType::ModifyNutrition { .. } => hunger::modify_nutrition(ecs, effect, target),
|
||||||
|
EffectType::AddIntrinsic { .. } => intrinsics::add_intrinsic(ecs, effect, target),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use super::{ add_effect, get_noncursed, particles, spatial, EffectType, Entity, Targets, World };
|
use super::{ add_effect, particles, spatial, EffectType, Entity, Targets, World };
|
||||||
use crate::{
|
use crate::{
|
||||||
gamelog,
|
gamelog,
|
||||||
gui::item_colour_ecs,
|
gui::item_colour_ecs,
|
||||||
|
|
@ -30,10 +30,11 @@ use crate::{
|
||||||
SingleActivation,
|
SingleActivation,
|
||||||
BUC,
|
BUC,
|
||||||
GrantsSpell,
|
GrantsSpell,
|
||||||
KnownSpell,
|
|
||||||
KnownSpells,
|
KnownSpells,
|
||||||
Position,
|
Position,
|
||||||
Viewshed,
|
Viewshed,
|
||||||
|
WantsToRemoveKey,
|
||||||
|
WantsToDelete,
|
||||||
};
|
};
|
||||||
use crate::data::messages::*;
|
use crate::data::messages::*;
|
||||||
use bracket_lib::prelude::*;
|
use bracket_lib::prelude::*;
|
||||||
|
|
@ -58,7 +59,10 @@ pub fn item_trigger(source: Option<Entity>, item: Entity, target: &Targets, ecs:
|
||||||
let did_something = event_trigger(source, item, target, ecs);
|
let did_something = event_trigger(source, item, target, ecs);
|
||||||
// If it's a consumable, delete it
|
// If it's a consumable, delete it
|
||||||
if did_something && ecs.read_storage::<Consumable>().get(item).is_some() {
|
if did_something && ecs.read_storage::<Consumable>().get(item).is_some() {
|
||||||
ecs.entities().delete(item).expect("Failed to delete item");
|
let mut removekey = ecs.write_storage::<WantsToRemoveKey>();
|
||||||
|
removekey.insert(item, WantsToRemoveKey {}).expect("Unable to insert WantsToRemoveKey");
|
||||||
|
let mut delete = ecs.write_storage::<WantsToDelete>();
|
||||||
|
delete.insert(item, WantsToDelete {}).expect("Unable to insert WantsToDelete");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,11 +177,11 @@ fn handle_magic_mapper(
|
||||||
fn handle_grant_spell(
|
fn handle_grant_spell(
|
||||||
ecs: &mut World,
|
ecs: &mut World,
|
||||||
event: &mut EventInfo,
|
event: &mut EventInfo,
|
||||||
mut logger: gamelog::Logger
|
logger: gamelog::Logger
|
||||||
) -> (gamelog::Logger, bool) {
|
) -> (gamelog::Logger, bool) {
|
||||||
if let Some(granted_spell) = ecs.read_storage::<GrantsSpell>().get(event.entity) {
|
if let Some(_granted_spell) = ecs.read_storage::<GrantsSpell>().get(event.entity) {
|
||||||
if
|
if
|
||||||
let Some(known_spells) = ecs
|
let Some(_known_spells) = ecs
|
||||||
.write_storage::<KnownSpells>()
|
.write_storage::<KnownSpells>()
|
||||||
.get_mut(event.source.unwrap())
|
.get_mut(event.source.unwrap())
|
||||||
{
|
{
|
||||||
|
|
@ -206,7 +210,7 @@ fn handle_healing(
|
||||||
healing_item.modifier;
|
healing_item.modifier;
|
||||||
add_effect(
|
add_effect(
|
||||||
event.source,
|
event.source,
|
||||||
EffectType::Healing { amount: roll, increment_max: get_noncursed(&event.buc) },
|
EffectType::Healing { amount: roll, increment_max: event.buc.noncursed() },
|
||||||
event.target.clone()
|
event.target.clone()
|
||||||
);
|
);
|
||||||
for target in get_entity_targets(&event.target) {
|
for target in get_entity_targets(&event.target) {
|
||||||
|
|
@ -219,9 +223,7 @@ fn handle_healing(
|
||||||
let renderables = ecs.read_storage::<Renderable>();
|
let renderables = ecs.read_storage::<Renderable>();
|
||||||
if ecs.read_storage::<Player>().get(target).is_some() {
|
if ecs.read_storage::<Player>().get(target).is_some() {
|
||||||
logger = logger
|
logger = logger
|
||||||
.colour(renderable_colour(&renderables, target))
|
|
||||||
.append("You")
|
.append("You")
|
||||||
.colour(WHITE)
|
|
||||||
.append(HEAL_PLAYER_HIT)
|
.append(HEAL_PLAYER_HIT)
|
||||||
.buc(event.buc.clone(), None, Some(HEAL_PLAYER_HIT_BLESSED));
|
.buc(event.buc.clone(), None, Some(HEAL_PLAYER_HIT_BLESSED));
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -247,7 +249,11 @@ fn handle_damage(
|
||||||
if let Some(damage_item) = ecs.read_storage::<InflictsDamage>().get(event.entity) {
|
if let Some(damage_item) = ecs.read_storage::<InflictsDamage>().get(event.entity) {
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
||||||
let roll = rng.roll_dice(damage_item.n_dice, damage_item.sides) + damage_item.modifier;
|
let roll = rng.roll_dice(damage_item.n_dice, damage_item.sides) + damage_item.modifier;
|
||||||
add_effect(event.source, EffectType::Damage { amount: roll }, event.target.clone());
|
add_effect(
|
||||||
|
event.source,
|
||||||
|
EffectType::Damage { amount: roll, damage_type: damage_item.damage_type },
|
||||||
|
event.target.clone()
|
||||||
|
);
|
||||||
for target in get_entity_targets(&event.target) {
|
for target in get_entity_targets(&event.target) {
|
||||||
if ecs.read_storage::<Prop>().get(target).is_some() {
|
if ecs.read_storage::<Prop>().get(target).is_some() {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -259,9 +265,7 @@ fn handle_damage(
|
||||||
let player_viewshed = viewsheds.get(*ecs.fetch::<Entity>()).unwrap();
|
let player_viewshed = viewsheds.get(*ecs.fetch::<Entity>()).unwrap();
|
||||||
if ecs.read_storage::<Player>().get(target).is_some() {
|
if ecs.read_storage::<Player>().get(target).is_some() {
|
||||||
logger = logger
|
logger = logger
|
||||||
.colour(renderable_colour(&renderables, target))
|
|
||||||
.append("You")
|
.append("You")
|
||||||
.colour(WHITE)
|
|
||||||
.append(DAMAGE_PLAYER_HIT);
|
.append(DAMAGE_PLAYER_HIT);
|
||||||
event.log = true;
|
event.log = true;
|
||||||
} else if
|
} else if
|
||||||
|
|
@ -349,10 +353,10 @@ fn handle_identify(
|
||||||
.get(event.source.unwrap())
|
.get(event.source.unwrap())
|
||||||
.map(|b| b.known)
|
.map(|b| b.known)
|
||||||
.unwrap_or(true);
|
.unwrap_or(true);
|
||||||
return (
|
let result =
|
||||||
in_this_backpack &&
|
in_this_backpack &&
|
||||||
(has_obfuscated_name || !already_identified || !known_beatitude)
|
(has_obfuscated_name || !already_identified || !known_beatitude);
|
||||||
);
|
return result;
|
||||||
}) {
|
}) {
|
||||||
to_identify.push((e, name.name.clone()));
|
to_identify.push((e, name.name.clone()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::data::names::*;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// A count of each event that has happened over the run. i.e. "turns", "descended", "ascended"
|
/// A count of each event that has happened over the run. i.e. "turns", "descended", "ascended"
|
||||||
static ref EVENT_COUNTER: Mutex<HashMap<String, i32>> = Mutex::new(HashMap::new());
|
pub static ref EVENT_COUNTER: Mutex<HashMap<String, i32>> = Mutex::new(HashMap::new());
|
||||||
// A record of events that happened on a given turn. i.e. "Advanced to level 2".
|
// A record of events that happened on a given turn. i.e. "Advanced to level 2".
|
||||||
pub static ref EVENTS: Mutex<HashMap<u32, Vec<String>>> = Mutex::new(HashMap::new());
|
pub static ref EVENTS: Mutex<HashMap<u32, Vec<String>>> = Mutex::new(HashMap::new());
|
||||||
// A record of floors visited, and monsters killed. Used to determine if an event is significant.
|
// A record of floors visited, and monsters killed. Used to determine if an event is significant.
|
||||||
|
|
@ -41,8 +41,9 @@ pub fn restore_events(events: HashMap<u32, Vec<String>>) {
|
||||||
}
|
}
|
||||||
/// Wipes all events - for starting a new game.
|
/// Wipes all events - for starting a new game.
|
||||||
pub fn clear_events() {
|
pub fn clear_events() {
|
||||||
EVENT_COUNTER.lock().unwrap().clear();
|
let (mut events, mut event_counts) = (EVENTS.lock().unwrap(), EVENT_COUNTER.lock().unwrap());
|
||||||
EVENTS.lock().unwrap().clear();
|
events.clear();
|
||||||
|
event_counts.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
|
|
@ -73,12 +74,12 @@ pub fn record_event(event: EVENT) {
|
||||||
let mut new_event: String = "unknown event".to_string();
|
let mut new_event: String = "unknown event".to_string();
|
||||||
let mut significant_event = true;
|
let mut significant_event = true;
|
||||||
match event {
|
match event {
|
||||||
EVENT::TURN(n) => {
|
EVENT::Turn(n) => {
|
||||||
modify_event_count(EVENT::COUNT_TURN, n);
|
modify_event_count(EVENT::COUNT_TURN, n);
|
||||||
significant_event = false;
|
significant_event = false;
|
||||||
}
|
}
|
||||||
// If de-levelling is ever implemented, this needs refactoring (along with a lot of stuff).
|
// If de-levelling is ever implemented, this needs refactoring (along with a lot of stuff).
|
||||||
EVENT::LEVEL(n) => {
|
EVENT::Level(n) => {
|
||||||
modify_event_count(EVENT::COUNT_LEVEL, n);
|
modify_event_count(EVENT::COUNT_LEVEL, n);
|
||||||
let new_lvl = get_event_count(EVENT::COUNT_LEVEL);
|
let new_lvl = get_event_count(EVENT::COUNT_LEVEL);
|
||||||
if new_lvl == 1 {
|
if new_lvl == 1 {
|
||||||
|
|
@ -87,7 +88,7 @@ pub fn record_event(event: EVENT) {
|
||||||
new_event = format!("Advanced to level {}", new_lvl);
|
new_event = format!("Advanced to level {}", new_lvl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EVENT::CHANGED_FLOOR(n) => {
|
EVENT::ChangedFloor(n) => {
|
||||||
modify_event_count(EVENT::COUNT_CHANGED_FLOOR, 1);
|
modify_event_count(EVENT::COUNT_CHANGED_FLOOR, 1);
|
||||||
if VISITED.lock().unwrap().contains(&n) {
|
if VISITED.lock().unwrap().contains(&n) {
|
||||||
significant_event = false;
|
significant_event = false;
|
||||||
|
|
@ -96,23 +97,23 @@ pub fn record_event(event: EVENT) {
|
||||||
new_event = format!("Visited {} for the first time", n);
|
new_event = format!("Visited {} for the first time", n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EVENT::KICKED_SOMETHING(n) => {
|
EVENT::KickedSomething(n) => {
|
||||||
modify_event_count(EVENT::COUNT_KICK, n);
|
modify_event_count(EVENT::COUNT_KICK, n);
|
||||||
significant_event = false;
|
significant_event = false;
|
||||||
}
|
}
|
||||||
EVENT::BROKE_DOOR(n) => {
|
EVENT::BrokeDoor(n) => {
|
||||||
modify_event_count(EVENT::COUNT_BROKE_DOOR, n);
|
modify_event_count(EVENT::COUNT_BROKE_DOOR, n);
|
||||||
significant_event = false;
|
significant_event = false;
|
||||||
}
|
}
|
||||||
EVENT::PLAYER_CONFUSED(n) => {
|
EVENT::PlayerConfused(n) => {
|
||||||
modify_event_count(EVENT::COUNT_PLAYER_CONFUSED, n);
|
modify_event_count(EVENT::COUNT_PLAYER_CONFUSED, n);
|
||||||
significant_event = false;
|
significant_event = false;
|
||||||
}
|
}
|
||||||
EVENT::LOOKED_FOR_HELP(n) => {
|
EVENT::LookedForHelp(n) => {
|
||||||
modify_event_count(EVENT::COUNT_LOOKED_FOR_HELP, n);
|
modify_event_count(EVENT::COUNT_LOOKED_FOR_HELP, n);
|
||||||
significant_event = false;
|
significant_event = false;
|
||||||
}
|
}
|
||||||
EVENT::KILLED(name) => {
|
EVENT::Killed(name) => {
|
||||||
modify_event_count(EVENT::COUNT_KILLED, 1);
|
modify_event_count(EVENT::COUNT_KILLED, 1);
|
||||||
if KILLED.lock().unwrap().contains(&name) {
|
if KILLED.lock().unwrap().contains(&name) {
|
||||||
significant_event = false;
|
significant_event = false;
|
||||||
|
|
@ -121,13 +122,13 @@ pub fn record_event(event: EVENT) {
|
||||||
new_event = format!("Killed your first {}", name);
|
new_event = format!("Killed your first {}", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EVENT::DISCOVERED(name) => {
|
EVENT::Discovered(name) => {
|
||||||
new_event = format!("Discovered {}", name);
|
new_event = format!("Discovered {}", name);
|
||||||
}
|
}
|
||||||
EVENT::IDENTIFIED(name) => {
|
EVENT::Identified(name) => {
|
||||||
new_event = format!("Identified {}", name);
|
new_event = format!("Identified {}", crate::gui::with_article(name));
|
||||||
}
|
}
|
||||||
EVENT::PLAYER_DIED(str) => {
|
EVENT::PlayerDied(str) => {
|
||||||
// Generating the String is handled in the death effect, to avoid passing the ecs here.
|
// Generating the String is handled in the death effect, to avoid passing the ecs here.
|
||||||
new_event = format!("{}", str);
|
new_event = format!("{}", str);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ use super::{
|
||||||
item_colour_ecs,
|
item_colour_ecs,
|
||||||
obfuscate_name_ecs,
|
obfuscate_name_ecs,
|
||||||
print_options,
|
print_options,
|
||||||
renderable_colour,
|
unique_ecs,
|
||||||
|
check_key,
|
||||||
|
letter_to_option,
|
||||||
ItemMenuResult,
|
ItemMenuResult,
|
||||||
UniqueInventoryItem,
|
|
||||||
BUC,
|
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
gamelog,
|
gamelog,
|
||||||
|
|
@ -19,11 +19,12 @@ use crate::{
|
||||||
Name,
|
Name,
|
||||||
ObfuscatedName,
|
ObfuscatedName,
|
||||||
Renderable,
|
Renderable,
|
||||||
|
Key,
|
||||||
states::state::*,
|
states::state::*,
|
||||||
};
|
};
|
||||||
use bracket_lib::prelude::*;
|
use bracket_lib::prelude::*;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// Handles the Identify menu.
|
/// Handles the Identify menu.
|
||||||
pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Entity>) {
|
pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Entity>) {
|
||||||
|
|
@ -37,38 +38,41 @@ pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Enti
|
||||||
let names = gs.ecs.read_storage::<Name>();
|
let names = gs.ecs.read_storage::<Name>();
|
||||||
let renderables = gs.ecs.read_storage::<Renderable>();
|
let renderables = gs.ecs.read_storage::<Renderable>();
|
||||||
let beatitudes = gs.ecs.read_storage::<Beatitude>();
|
let beatitudes = gs.ecs.read_storage::<Beatitude>();
|
||||||
|
let keys = gs.ecs.read_storage::<Key>();
|
||||||
|
|
||||||
let build_identify_iterator = || {
|
let build_identify_iterator = || {
|
||||||
(&entities, &items, &renderables, &names).join().filter(|(item_entity, _i, _r, n)| {
|
(&entities, &items, &renderables, &names, &keys)
|
||||||
// If not owned by the player, return false.
|
.join()
|
||||||
let mut keep = false;
|
.filter(|(item_entity, _i, _r, n, _k)| {
|
||||||
if let Some(bp) = backpack.get(*item_entity) {
|
// If not owned by the player, return false.
|
||||||
if bp.owner == *player_entity {
|
let mut keep = false;
|
||||||
keep = true;
|
if let Some(bp) = backpack.get(*item_entity) {
|
||||||
|
if bp.owner == *player_entity {
|
||||||
|
keep = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
// If not equipped by the player, return false.
|
||||||
// If not equipped by the player, return false.
|
if let Some(equip) = equipped.get(*item_entity) {
|
||||||
if let Some(equip) = equipped.get(*item_entity) {
|
if equip.owner == *player_entity {
|
||||||
if equip.owner == *player_entity {
|
keep = true;
|
||||||
keep = true;
|
}
|
||||||
}
|
}
|
||||||
}
|
if !keep {
|
||||||
if !keep {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
// If not obfuscated, or already identified, return false.
|
||||||
// If not obfuscated, or already identified, return false.
|
if
|
||||||
if
|
(!obfuscated.get(*item_entity).is_some() ||
|
||||||
(!obfuscated.get(*item_entity).is_some() ||
|
dm.identified_items.contains(&n.name)) &&
|
||||||
dm.identified_items.contains(&n.name)) &&
|
beatitudes
|
||||||
beatitudes
|
.get(*item_entity)
|
||||||
.get(*item_entity)
|
.map(|beatitude| beatitude.known)
|
||||||
.map(|beatitude| beatitude.known)
|
.unwrap_or(true)
|
||||||
.unwrap_or(true)
|
{
|
||||||
{
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
return true;
|
||||||
return true;
|
})
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build list of items to display
|
// Build list of items to display
|
||||||
|
|
@ -91,34 +95,15 @@ pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Enti
|
||||||
.log();
|
.log();
|
||||||
return (ItemMenuResult::Selected, Some(build_identify_iterator().nth(0).unwrap().0));
|
return (ItemMenuResult::Selected, Some(build_identify_iterator().nth(0).unwrap().0));
|
||||||
}
|
}
|
||||||
let mut player_inventory: super::PlayerInventory = BTreeMap::new();
|
let mut player_inventory: super::PlayerInventory = HashMap::new();
|
||||||
for (entity, _i, renderable, name) in build_identify_iterator() {
|
for (entity, _i, _r, _n, key) in build_identify_iterator() {
|
||||||
let (singular, plural) = obfuscate_name_ecs(&gs.ecs, entity);
|
let unique_item = unique_ecs(&gs.ecs, entity);
|
||||||
let beatitude_status = if
|
|
||||||
let Some(beatitude) = gs.ecs.read_storage::<Beatitude>().get(entity)
|
|
||||||
{
|
|
||||||
match beatitude.buc {
|
|
||||||
BUC::Blessed => 1,
|
|
||||||
BUC::Uncursed => 2,
|
|
||||||
BUC::Cursed => 3,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
let unique_item = UniqueInventoryItem {
|
|
||||||
display_name: super::DisplayName { singular: singular.clone(), plural: plural.clone() },
|
|
||||||
rgb: item_colour_ecs(&gs.ecs, entity),
|
|
||||||
renderables: renderable_colour(&renderables, entity),
|
|
||||||
glyph: renderable.glyph,
|
|
||||||
beatitude_status: beatitude_status,
|
|
||||||
name: name.name.clone(),
|
|
||||||
};
|
|
||||||
player_inventory
|
player_inventory
|
||||||
.entry(unique_item)
|
.entry(unique_item)
|
||||||
.and_modify(|(_e, count)| {
|
.and_modify(|slot| {
|
||||||
*count += 1;
|
slot.count += 1;
|
||||||
})
|
})
|
||||||
.or_insert((entity, 1));
|
.or_insert(super::InventorySlot { item: entity, count: 1, idx: key.idx });
|
||||||
}
|
}
|
||||||
// Get display args
|
// Get display args
|
||||||
let width = get_max_inventory_width(&player_inventory);
|
let width = get_max_inventory_width(&player_inventory);
|
||||||
|
|
@ -133,7 +118,7 @@ pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Enti
|
||||||
"Identify which item? [aA-zZ][Esc.]"
|
"Identify which item? [aA-zZ][Esc.]"
|
||||||
);
|
);
|
||||||
ctx.draw_box(x, y, width + 2, count + 1, RGB::named(WHITE), RGB::named(BLACK));
|
ctx.draw_box(x, y, width + 2, count + 1, RGB::named(WHITE), RGB::named(BLACK));
|
||||||
print_options(&player_inventory, x + 1, y + 1, ctx);
|
print_options(&gs.ecs, &player_inventory, x + 1, y + 1, ctx);
|
||||||
// Input
|
// Input
|
||||||
match ctx.key {
|
match ctx.key {
|
||||||
None => (ItemMenuResult::NoResponse, None),
|
None => (ItemMenuResult::NoResponse, None),
|
||||||
|
|
@ -141,21 +126,17 @@ pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Enti
|
||||||
match key {
|
match key {
|
||||||
VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None),
|
VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None),
|
||||||
_ => {
|
_ => {
|
||||||
let selection = letter_to_option(key);
|
let selection = letter_to_option::letter_to_option(key, ctx.shift);
|
||||||
if selection > -1 && selection < (count as i32) {
|
if selection != -1 && check_key(selection as usize) {
|
||||||
let item = player_inventory
|
// Get the first entity with a Key {} component that has an idx matching "selection".
|
||||||
.iter()
|
let entities = gs.ecs.entities();
|
||||||
.nth(selection as usize)
|
let keyed_items = gs.ecs.read_storage::<Key>();
|
||||||
.unwrap().1.0;
|
let backpack = gs.ecs.read_storage::<InBackpack>();
|
||||||
gamelog::Logger
|
for (e, key, _b) in (&entities, &keyed_items, &backpack).join() {
|
||||||
::new()
|
if key.idx == (selection as usize) {
|
||||||
.append("You identify the")
|
return (ItemMenuResult::Selected, Some(e));
|
||||||
.colour(item_colour_ecs(&gs.ecs, item))
|
}
|
||||||
.append_n(obfuscate_name_ecs(&gs.ecs, item).0)
|
}
|
||||||
.colour(WHITE)
|
|
||||||
.append("!")
|
|
||||||
.log();
|
|
||||||
return (ItemMenuResult::Selected, Some(item));
|
|
||||||
}
|
}
|
||||||
(ItemMenuResult::NoResponse, None)
|
(ItemMenuResult::NoResponse, None)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
608
src/gui/mod.rs
608
src/gui/mod.rs
|
|
@ -32,6 +32,9 @@ use super::{
|
||||||
Skills,
|
Skills,
|
||||||
Viewshed,
|
Viewshed,
|
||||||
BUC,
|
BUC,
|
||||||
|
Key,
|
||||||
|
Item,
|
||||||
|
ItemType,
|
||||||
data::ids::get_local_col,
|
data::ids::get_local_col,
|
||||||
};
|
};
|
||||||
use crate::data::entity::CARRY_CAPACITY_PER_STRENGTH;
|
use crate::data::entity::CARRY_CAPACITY_PER_STRENGTH;
|
||||||
|
|
@ -43,7 +46,9 @@ use crate::data::visuals::{
|
||||||
};
|
};
|
||||||
use bracket_lib::prelude::*;
|
use bracket_lib::prelude::*;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::HashMap;
|
||||||
|
use crate::invkeys::check_key;
|
||||||
|
|
||||||
mod character_creation;
|
mod character_creation;
|
||||||
mod cheat_menu;
|
mod cheat_menu;
|
||||||
mod letter_to_option;
|
mod letter_to_option;
|
||||||
|
|
@ -101,6 +106,101 @@ pub fn draw_lerping_bar(
|
||||||
ctx.print(sx + width, sy, "]");
|
ctx.print(sx + width, sy, "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_xp(ctx: &mut BTerm, pt: Point, pool: &Pools) {
|
||||||
|
ctx.print_color(
|
||||||
|
pt.x,
|
||||||
|
pt.y,
|
||||||
|
RGB::named(WHITE),
|
||||||
|
RGB::named(BLACK),
|
||||||
|
format!("XP{}/{}", pool.level, pool.xp)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_ac(ecs: &World, skills: &Skills, stats: &Pools, attr: &Attributes) -> i32 {
|
||||||
|
let skill_ac_bonus = gamesystem::skill_bonus(Skill::Defence, skills);
|
||||||
|
let mut armour_ac_bonus = 0;
|
||||||
|
let equipped = ecs.read_storage::<Equipped>();
|
||||||
|
let ac = ecs.read_storage::<ArmourClassBonus>();
|
||||||
|
let player_entity = ecs.fetch::<Entity>();
|
||||||
|
for (wielded, ac) in (&equipped, &ac).join() {
|
||||||
|
if wielded.owner == *player_entity {
|
||||||
|
armour_ac_bonus += ac.amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stats.bac - attr.dexterity.bonus / 2 - skill_ac_bonus - armour_ac_bonus
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_ac(ctx: &mut BTerm, pt: Point, ac: i32) {
|
||||||
|
ctx.print_color(pt.x, pt.y, RGB::named(PINK), RGB::named(BLACK), "AC");
|
||||||
|
ctx.print_color(pt.x + 2, pt.y, RGB::named(WHITE), RGB::named(BLACK), ac);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_attributes(ctx: &mut BTerm, pt: Point, a: &Attributes) {
|
||||||
|
ctx.print_color(pt.x, pt.y, RGB::named(RED), RGB::named(BLACK), "STR");
|
||||||
|
ctx.print_color(pt.x + 3, pt.y, RGB::named(WHITE), RGB::named(BLACK), a.strength.base);
|
||||||
|
ctx.print_color(pt.x + 7, pt.y, RGB::named(GREEN), RGB::named(BLACK), "DEX");
|
||||||
|
ctx.print_color(pt.x + 10, pt.y, RGB::named(WHITE), RGB::named(BLACK), a.dexterity.base);
|
||||||
|
ctx.print_color(pt.x + 14, pt.y, RGB::named(ORANGE), RGB::named(BLACK), "CON");
|
||||||
|
ctx.print_color(pt.x + 17, pt.y, RGB::named(WHITE), RGB::named(BLACK), a.constitution.base);
|
||||||
|
ctx.print_color(pt.x, 54, RGB::named(CYAN), RGB::named(BLACK), "INT");
|
||||||
|
ctx.print_color(pt.x + 3, pt.y + 1, RGB::named(WHITE), RGB::named(BLACK), a.intelligence.base);
|
||||||
|
ctx.print_color(pt.x + 7, pt.y + 1, RGB::named(YELLOW), RGB::named(BLACK), "WIS");
|
||||||
|
ctx.print_color(pt.x + 10, pt.y + 1, RGB::named(WHITE), RGB::named(BLACK), a.wisdom.base);
|
||||||
|
ctx.print_color(pt.x + 14, pt.y + 1, RGB::named(PURPLE), RGB::named(BLACK), "CHA");
|
||||||
|
ctx.print_color(pt.x + 17, pt.y + 1, RGB::named(WHITE), RGB::named(BLACK), a.charisma.base);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_hunger(ctx: &mut BTerm, pt: Point, hunger: &HungerClock) {
|
||||||
|
match hunger.state {
|
||||||
|
HungerState::Satiated => {
|
||||||
|
ctx.print_color_right(
|
||||||
|
pt.x,
|
||||||
|
pt.y,
|
||||||
|
get_hunger_colour(hunger.state),
|
||||||
|
RGB::named(BLACK),
|
||||||
|
"Satiated"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
HungerState::Normal => {}
|
||||||
|
HungerState::Hungry => {
|
||||||
|
ctx.print_color_right(
|
||||||
|
pt.x,
|
||||||
|
pt.y,
|
||||||
|
get_hunger_colour(hunger.state),
|
||||||
|
RGB::named(BLACK),
|
||||||
|
"Hungry"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
HungerState::Weak => {
|
||||||
|
ctx.print_color_right(
|
||||||
|
pt.x,
|
||||||
|
pt.y,
|
||||||
|
get_hunger_colour(hunger.state),
|
||||||
|
RGB::named(BLACK),
|
||||||
|
"Weak"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
HungerState::Fainting => {
|
||||||
|
ctx.print_color_right(
|
||||||
|
pt.x,
|
||||||
|
pt.y,
|
||||||
|
get_hunger_colour(hunger.state),
|
||||||
|
RGB::named(BLACK),
|
||||||
|
"Fainting"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
HungerState::Starving => {
|
||||||
|
ctx.print_color_right(
|
||||||
|
pt.x,
|
||||||
|
pt.y,
|
||||||
|
get_hunger_colour(hunger.state),
|
||||||
|
RGB::named(BLACK),
|
||||||
|
"Starving"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn draw_ui(ecs: &World, ctx: &mut BTerm) {
|
pub fn draw_ui(ecs: &World, ctx: &mut BTerm) {
|
||||||
// Render stats
|
// Render stats
|
||||||
let pools = ecs.read_storage::<Pools>();
|
let pools = ecs.read_storage::<Pools>();
|
||||||
|
|
@ -137,111 +237,12 @@ pub fn draw_ui(ecs: &World, ctx: &mut BTerm) {
|
||||||
RGB::named(BLUE),
|
RGB::named(BLUE),
|
||||||
RGB::named(BLACK)
|
RGB::named(BLACK)
|
||||||
);
|
);
|
||||||
// Draw AC
|
draw_ac(ctx, Point::new(26, 53), calc_ac(ecs, skills, stats, attributes));
|
||||||
let skill_ac_bonus = gamesystem::skill_bonus(Skill::Defence, &*skills);
|
draw_xp(ctx, Point::new(26, 54), stats);
|
||||||
let mut armour_ac_bonus = 0;
|
draw_attributes(ctx, Point::new(38, 53), attributes);
|
||||||
let equipped = ecs.read_storage::<Equipped>();
|
draw_hunger(ctx, Point::new(70, 53), hunger);
|
||||||
let ac = ecs.read_storage::<ArmourClassBonus>();
|
|
||||||
let player_entity = ecs.fetch::<Entity>();
|
|
||||||
for (wielded, ac) in (&equipped, &ac).join() {
|
|
||||||
if wielded.owner == *player_entity {
|
|
||||||
armour_ac_bonus += ac.amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let armour_class =
|
|
||||||
stats.bac - attributes.dexterity.bonus / 2 - skill_ac_bonus - armour_ac_bonus;
|
|
||||||
ctx.print_color(26, 53, RGB::named(PINK), RGB::named(BLACK), "AC");
|
|
||||||
ctx.print_color(28, 53, RGB::named(WHITE), RGB::named(BLACK), armour_class);
|
|
||||||
// Draw level
|
|
||||||
ctx.print_color(
|
|
||||||
26,
|
|
||||||
54,
|
|
||||||
RGB::named(WHITE),
|
|
||||||
RGB::named(BLACK),
|
|
||||||
format!("XP{}/{}", stats.level, stats.xp)
|
|
||||||
);
|
|
||||||
// Draw attributes
|
|
||||||
let x = 38;
|
|
||||||
ctx.print_color(x, 53, RGB::named(RED), RGB::named(BLACK), "STR");
|
|
||||||
ctx.print_color(x + 3, 53, RGB::named(WHITE), RGB::named(BLACK), attributes.strength.base);
|
|
||||||
ctx.print_color(x + 7, 53, RGB::named(GREEN), RGB::named(BLACK), "DEX");
|
|
||||||
ctx.print_color(
|
|
||||||
x + 10,
|
|
||||||
53,
|
|
||||||
RGB::named(WHITE),
|
|
||||||
RGB::named(BLACK),
|
|
||||||
attributes.dexterity.base
|
|
||||||
);
|
|
||||||
ctx.print_color(x + 14, 53, RGB::named(ORANGE), RGB::named(BLACK), "CON");
|
|
||||||
ctx.print_color(
|
|
||||||
x + 17,
|
|
||||||
53,
|
|
||||||
RGB::named(WHITE),
|
|
||||||
RGB::named(BLACK),
|
|
||||||
attributes.constitution.base
|
|
||||||
);
|
|
||||||
ctx.print_color(x, 54, RGB::named(CYAN), RGB::named(BLACK), "INT");
|
|
||||||
ctx.print_color(
|
|
||||||
x + 3,
|
|
||||||
54,
|
|
||||||
RGB::named(WHITE),
|
|
||||||
RGB::named(BLACK),
|
|
||||||
attributes.intelligence.base
|
|
||||||
);
|
|
||||||
ctx.print_color(x + 7, 54, RGB::named(YELLOW), RGB::named(BLACK), "WIS");
|
|
||||||
ctx.print_color(x + 10, 54, RGB::named(WHITE), RGB::named(BLACK), attributes.wisdom.base);
|
|
||||||
ctx.print_color(x + 14, 54, RGB::named(PURPLE), RGB::named(BLACK), "CHA");
|
|
||||||
ctx.print_color(x + 17, 54, RGB::named(WHITE), RGB::named(BLACK), attributes.charisma.base);
|
|
||||||
// Draw hunger
|
|
||||||
match hunger.state {
|
|
||||||
HungerState::Satiated => {
|
|
||||||
ctx.print_color_right(
|
|
||||||
70,
|
|
||||||
53,
|
|
||||||
get_hunger_colour(hunger.state),
|
|
||||||
RGB::named(BLACK),
|
|
||||||
"Satiated"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
HungerState::Normal => {}
|
|
||||||
HungerState::Hungry => {
|
|
||||||
ctx.print_color_right(
|
|
||||||
70,
|
|
||||||
53,
|
|
||||||
get_hunger_colour(hunger.state),
|
|
||||||
RGB::named(BLACK),
|
|
||||||
"Hungry"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
HungerState::Weak => {
|
|
||||||
ctx.print_color_right(
|
|
||||||
70,
|
|
||||||
53,
|
|
||||||
get_hunger_colour(hunger.state),
|
|
||||||
RGB::named(BLACK),
|
|
||||||
"Weak"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
HungerState::Fainting => {
|
|
||||||
ctx.print_color_right(
|
|
||||||
70,
|
|
||||||
53,
|
|
||||||
get_hunger_colour(hunger.state),
|
|
||||||
RGB::named(BLACK),
|
|
||||||
"Fainting"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
HungerState::Starving => {
|
|
||||||
ctx.print_color_right(
|
|
||||||
70,
|
|
||||||
53,
|
|
||||||
get_hunger_colour(hunger.state),
|
|
||||||
RGB::named(BLACK),
|
|
||||||
"Starving"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Burden
|
// Burden
|
||||||
|
let player_entity = ecs.fetch::<Entity>();
|
||||||
if let Some(burden) = burden.get(*player_entity) {
|
if let Some(burden) = burden.get(*player_entity) {
|
||||||
match burden.level {
|
match burden.level {
|
||||||
crate::BurdenLevel::Burdened => {
|
crate::BurdenLevel::Burdened => {
|
||||||
|
|
@ -271,41 +272,16 @@ pub fn draw_ui(ecs: &World, ctx: &mut BTerm) {
|
||||||
ctx.print_color(20, 20, RGB::named(YELLOW), RGB::named(BLACK), "--- GODMODE: ON ---");
|
ctx.print_color(20, 20, RGB::named(YELLOW), RGB::named(BLACK), "--- GODMODE: ON ---");
|
||||||
}
|
}
|
||||||
// Draw equipment
|
// Draw equipment
|
||||||
let renderables = ecs.read_storage::<Renderable>();
|
|
||||||
let mut equipment: Vec<(String, RGB, RGB, FontCharType)> = Vec::new();
|
|
||||||
let entities = ecs.entities();
|
|
||||||
for (entity, _equipped, renderable) in (&entities, &equipped, &renderables)
|
|
||||||
.join()
|
|
||||||
.filter(|item| item.1.owner == *player_entity) {
|
|
||||||
equipment.push((
|
|
||||||
obfuscate_name_ecs(ecs, entity).0,
|
|
||||||
RGB::named(item_colour_ecs(ecs, entity)),
|
|
||||||
renderable.fg,
|
|
||||||
renderable.glyph,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let mut y = 1;
|
let mut y = 1;
|
||||||
|
let equipment = items(&ecs, Filter::Equipped);
|
||||||
if !equipment.is_empty() {
|
if !equipment.is_empty() {
|
||||||
ctx.print_color(72, y, RGB::named(BLACK), RGB::named(WHITE), "Equipment");
|
ctx.print_color(72, y, RGB::named(BLACK), RGB::named(WHITE), "Equipment");
|
||||||
let mut j = 0;
|
y += 1;
|
||||||
for item in equipment {
|
y = print_options(&ecs, &equipment, 72, y, ctx);
|
||||||
y += 1;
|
y += 1;
|
||||||
ctx.set(72, y, RGB::named(YELLOW), RGB::named(BLACK), 97 + (j as FontCharType));
|
|
||||||
j += 1;
|
|
||||||
ctx.set(74, y, item.2, RGB::named(BLACK), item.3);
|
|
||||||
ctx.print_color(76, y, item.1, RGB::named(BLACK), &item.0);
|
|
||||||
ctx.print_color(
|
|
||||||
76 + &item.0.len() + 1,
|
|
||||||
y,
|
|
||||||
RGB::named(WHITE),
|
|
||||||
RGB::named(BLACK),
|
|
||||||
"(worn)"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
y += 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw consumables
|
// Draw backpack
|
||||||
ctx.print_color(72, y, RGB::named(BLACK), RGB::named(WHITE), "Backpack");
|
ctx.print_color(72, y, RGB::named(BLACK), RGB::named(WHITE), "Backpack");
|
||||||
ctx.print_color(
|
ctx.print_color(
|
||||||
81,
|
81,
|
||||||
|
|
@ -320,8 +296,8 @@ pub fn draw_ui(ecs: &World, ctx: &mut BTerm) {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
y += 1;
|
y += 1;
|
||||||
let player_inventory = get_player_inventory(&ecs);
|
let backpack = items(&ecs, Filter::Backpack);
|
||||||
y = print_options(&player_inventory, 72, y, ctx).0;
|
y = print_options(&ecs, &backpack, 72, y, ctx);
|
||||||
|
|
||||||
// Draw spells - if we have any -- NYI!
|
// Draw spells - if we have any -- NYI!
|
||||||
if let Some(known_spells) = ecs.read_storage::<KnownSpells>().get(*player_entity) {
|
if let Some(known_spells) = ecs.read_storage::<KnownSpells>().get(*player_entity) {
|
||||||
|
|
@ -505,46 +481,46 @@ pub enum ItemMenuResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_options(
|
pub fn print_options(
|
||||||
|
_ecs: &World,
|
||||||
inventory: &PlayerInventory,
|
inventory: &PlayerInventory,
|
||||||
mut x: i32,
|
mut x: i32,
|
||||||
mut y: i32,
|
mut y: i32,
|
||||||
ctx: &mut BTerm
|
ctx: &mut BTerm
|
||||||
) -> (i32, i32) {
|
) -> i32 {
|
||||||
let mut j = 0;
|
|
||||||
let initial_x: i32 = x;
|
let initial_x: i32 = x;
|
||||||
let mut width: i32 = -1;
|
let mut sorted: Vec<_> = inventory.iter().collect();
|
||||||
for (item, (_e, item_count)) in inventory {
|
sorted.sort_by(|a, b| a.1.idx.cmp(&b.1.idx));
|
||||||
|
|
||||||
|
for (info, slot) in sorted {
|
||||||
x = initial_x;
|
x = initial_x;
|
||||||
// Print the character required to access this item. i.e. (a)
|
// Print the character required to access this item. i.e. (a)
|
||||||
if j < 26 {
|
if slot.idx < 26 {
|
||||||
ctx.set(x, y, RGB::named(YELLOW), RGB::named(BLACK), 97 + (j as FontCharType));
|
ctx.set(x, y, RGB::named(YELLOW), RGB::named(BLACK), 97 + slot.idx);
|
||||||
} else {
|
} else {
|
||||||
// If we somehow have more than 26, start using capitals
|
// If we somehow have more than 26, start using capitals
|
||||||
ctx.set(x, y, RGB::named(YELLOW), RGB::named(BLACK), 65 - 26 + (j as FontCharType));
|
ctx.set(x, y, RGB::named(YELLOW), RGB::named(BLACK), 65 - 26 + slot.idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
x += 2;
|
x += 2;
|
||||||
let fg = RGB::from_u8(item.renderables.0, item.renderables.1, item.renderables.2);
|
let fg = RGB::from_u8(info.renderables.0, info.renderables.1, info.renderables.2);
|
||||||
ctx.set(x, y, fg, RGB::named(BLACK), item.glyph);
|
ctx.set(x, y, fg, RGB::named(BLACK), info.glyph);
|
||||||
x += 2;
|
x += 2;
|
||||||
|
|
||||||
let fg = RGB::from_u8(item.rgb.0, item.rgb.1, item.rgb.2);
|
let fg = RGB::from_u8(info.rgb.0, info.rgb.1, info.rgb.2);
|
||||||
if item_count > &1 {
|
if slot.count > 1 {
|
||||||
// If more than one, print the number and pluralise
|
// If more than one, print the number and pluralise
|
||||||
// i.e. (a) 3 daggers
|
// i.e. (a) 3 daggers
|
||||||
ctx.print_color(x, y, fg, RGB::named(BLACK), item_count);
|
ctx.print_color(x, y, fg, RGB::named(BLACK), slot.count);
|
||||||
x += 2;
|
x += 2;
|
||||||
ctx.print_color(x, y, fg, RGB::named(BLACK), item.display_name.plural.to_string());
|
ctx.print_color(x, y, fg, RGB::named(BLACK), info.display_name.plural.to_string());
|
||||||
let this_width = x - initial_x + (item.display_name.plural.len() as i32);
|
|
||||||
width = if width > this_width { width } else { this_width };
|
|
||||||
} else {
|
} else {
|
||||||
if item.display_name.singular.to_lowercase().ends_with("s") {
|
if info.display_name.singular.to_lowercase().ends_with("s") {
|
||||||
ctx.print_color(x, y, fg, RGB::named(BLACK), "some");
|
ctx.print_color(x, y, fg, RGB::named(BLACK), "some");
|
||||||
x += 5;
|
x += 5;
|
||||||
} else if
|
} else if
|
||||||
['a', 'e', 'i', 'o', 'u']
|
['a', 'e', 'i', 'o', 'u']
|
||||||
.iter()
|
.iter()
|
||||||
.any(|&v| item.display_name.singular.to_lowercase().starts_with(v))
|
.any(|&v| info.display_name.singular.to_lowercase().starts_with(v))
|
||||||
{
|
{
|
||||||
// If one and starts with a vowel, print 'an'
|
// If one and starts with a vowel, print 'an'
|
||||||
// i.e. (a) an apple
|
// i.e. (a) an apple
|
||||||
|
|
@ -556,40 +532,54 @@ pub fn print_options(
|
||||||
ctx.print_color(x, y, fg, RGB::named(BLACK), "a");
|
ctx.print_color(x, y, fg, RGB::named(BLACK), "a");
|
||||||
x += 2;
|
x += 2;
|
||||||
}
|
}
|
||||||
ctx.print_color(x, y, fg, RGB::named(BLACK), item.display_name.singular.to_string());
|
/*
|
||||||
let this_width = x - initial_x + (item.display_name.singular.len() as i32);
|
let text = if let Some(worn) = ecs.read_storage::<Equipped>().get(slot.item) {
|
||||||
width = if width > this_width { width } else { this_width };
|
use crate::EquipmentSlot;
|
||||||
|
let text = match worn.slot {
|
||||||
|
EquipmentSlot::Melee | EquipmentSlot::Shield => "being held",
|
||||||
|
_ => "being worn",
|
||||||
|
};
|
||||||
|
format!("{} ({})", info.display_name.singular.to_string(), text)
|
||||||
|
} else {
|
||||||
|
info.display_name.singular.to_string()
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
let text = info.display_name.singular.to_string();
|
||||||
|
ctx.print_color(x, y, fg, RGB::named(BLACK), text);
|
||||||
}
|
}
|
||||||
|
|
||||||
y += 1;
|
y += 1;
|
||||||
j += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (y, width);
|
return y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PADDING: i32 = 4;
|
||||||
|
const SOME: i32 = 4;
|
||||||
|
const AN: i32 = 2;
|
||||||
|
const A: i32 = 1;
|
||||||
|
|
||||||
pub fn get_max_inventory_width(inventory: &PlayerInventory) -> i32 {
|
pub fn get_max_inventory_width(inventory: &PlayerInventory) -> i32 {
|
||||||
let mut width: i32 = 0;
|
let mut width: i32 = 0;
|
||||||
for (item, (_e, count)) in inventory {
|
for (item, slot) in inventory {
|
||||||
let mut this_width = item.display_name.singular.len() as i32;
|
let mut this_width = item.display_name.singular.len() as i32;
|
||||||
// Clean this up. It should use consts.
|
if slot.count <= 1 {
|
||||||
this_width += 4; // The spaces before and after the character to select this item, etc.
|
|
||||||
if count <= &1 {
|
|
||||||
if item.display_name.singular == item.display_name.plural {
|
if item.display_name.singular == item.display_name.plural {
|
||||||
this_width += 4; // "some".len
|
this_width += SOME;
|
||||||
} else if
|
} else if
|
||||||
['a', 'e', 'i', 'o', 'u'].iter().any(|&v| item.display_name.singular.starts_with(v))
|
['a', 'e', 'i', 'o', 'u'].iter().any(|&v| item.display_name.singular.starts_with(v))
|
||||||
{
|
{
|
||||||
this_width += 2; // "an".len
|
this_width += AN;
|
||||||
} else {
|
} else {
|
||||||
this_width += 1; // "a".len
|
this_width += A;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this_width += count.to_string().len() as i32; // i.e. "12".len
|
this_width =
|
||||||
|
(item.display_name.plural.len() as i32) + (slot.count.to_string().len() as i32); // i.e. "12".len
|
||||||
}
|
}
|
||||||
width = if width > this_width { width } else { this_width };
|
width = if width > this_width { width } else { this_width };
|
||||||
}
|
}
|
||||||
return width;
|
return width + PADDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inside the ECS
|
// Inside the ECS
|
||||||
|
|
@ -636,7 +626,7 @@ pub fn obfuscate_name(
|
||||||
if has_beatitude.known {
|
if has_beatitude.known {
|
||||||
let prefix = match has_beatitude.buc {
|
let prefix = match has_beatitude.buc {
|
||||||
BUC::Cursed => Some("cursed "),
|
BUC::Cursed => Some("cursed "),
|
||||||
BUC::Uncursed => None,
|
BUC::Uncursed => Some("uncursed "),
|
||||||
BUC::Blessed => Some("blessed "),
|
BUC::Blessed => Some("blessed "),
|
||||||
};
|
};
|
||||||
if prefix.is_some() {
|
if prefix.is_some() {
|
||||||
|
|
@ -831,13 +821,13 @@ pub fn show_help(ctx: &mut BTerm) -> YesNoResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
struct DisplayName {
|
struct DisplayName {
|
||||||
singular: String,
|
singular: String,
|
||||||
plural: String,
|
plural: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct UniqueInventoryItem {
|
pub struct UniqueInventoryItem {
|
||||||
display_name: DisplayName,
|
display_name: DisplayName,
|
||||||
rgb: (u8, u8, u8),
|
rgb: (u8, u8, u8),
|
||||||
|
|
@ -847,57 +837,71 @@ pub struct UniqueInventoryItem {
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type PlayerInventory = BTreeMap<UniqueInventoryItem, (Entity, i32)>;
|
pub struct InventorySlot {
|
||||||
|
pub item: Entity,
|
||||||
|
pub count: i32,
|
||||||
|
pub idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_player_inventory(ecs: &World) -> PlayerInventory {
|
pub type PlayerInventory = HashMap<UniqueInventoryItem, InventorySlot>;
|
||||||
let player_entity = ecs.fetch::<Entity>();
|
|
||||||
let names = ecs.read_storage::<Name>();
|
|
||||||
let backpack = ecs.read_storage::<InBackpack>();
|
|
||||||
let entities = ecs.entities();
|
|
||||||
let renderables = ecs.read_storage::<Renderable>();
|
|
||||||
|
|
||||||
let mut player_inventory: BTreeMap<UniqueInventoryItem, (Entity, i32)> = BTreeMap::new();
|
pub enum Filter {
|
||||||
for (entity, _pack, name, renderable) in (&entities, &backpack, &names, &renderables)
|
All,
|
||||||
.join()
|
Backpack,
|
||||||
.filter(|item| item.1.owner == *player_entity) {
|
Equipped,
|
||||||
// RGB can't be used as a key. This is converting the RGB (tuple of f32) into a tuple of u8s.
|
Category(ItemType),
|
||||||
let item_colour = item_colour_ecs(ecs, entity);
|
}
|
||||||
let renderables = (
|
|
||||||
(renderable.fg.r * 255.0) as u8,
|
macro_rules! includeitem {
|
||||||
(renderable.fg.g * 255.0) as u8,
|
($inv:expr, $ecs:expr, $e:expr, $k:expr) => {
|
||||||
(renderable.fg.b * 255.0) as u8,
|
$inv.entry(unique_ecs($ecs, $e))
|
||||||
);
|
.and_modify(|slot| {
|
||||||
let (singular, plural) = obfuscate_name_ecs(ecs, entity);
|
slot.count += 1;
|
||||||
let beatitude_status = if let Some(beatitude) = ecs.read_storage::<Beatitude>().get(entity) {
|
|
||||||
match beatitude.buc {
|
|
||||||
BUC::Blessed => 1,
|
|
||||||
BUC::Uncursed => 2,
|
|
||||||
BUC::Cursed => 3,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
let unique_item = UniqueInventoryItem {
|
|
||||||
display_name: DisplayName { singular: singular.clone(), plural: plural },
|
|
||||||
rgb: item_colour,
|
|
||||||
renderables: renderables,
|
|
||||||
glyph: renderable.glyph,
|
|
||||||
beatitude_status: beatitude_status,
|
|
||||||
name: name.name.clone(),
|
|
||||||
};
|
|
||||||
player_inventory
|
|
||||||
.entry(unique_item)
|
|
||||||
.and_modify(|(_e, count)| {
|
|
||||||
*count += 1;
|
|
||||||
})
|
})
|
||||||
.or_insert((entity, 1));
|
.or_insert(InventorySlot {
|
||||||
}
|
item: $e,
|
||||||
|
count: 1,
|
||||||
|
idx: $k.idx,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return player_inventory;
|
pub fn items(ecs: &World, filter: Filter) -> PlayerInventory {
|
||||||
|
let entities = ecs.entities();
|
||||||
|
let keys = ecs.read_storage::<Key>();
|
||||||
|
let mut inv: PlayerInventory = HashMap::new();
|
||||||
|
match filter {
|
||||||
|
Filter::All => {
|
||||||
|
for (e, k) in (&entities, &keys).join() {
|
||||||
|
includeitem!(inv, ecs, e, k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Filter::Backpack => {
|
||||||
|
let backpack = ecs.read_storage::<InBackpack>();
|
||||||
|
for (e, k, _b) in (&entities, &keys, &backpack).join() {
|
||||||
|
includeitem!(inv, ecs, e, k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Filter::Equipped => {
|
||||||
|
let equipped = ecs.read_storage::<Equipped>();
|
||||||
|
for (e, k, _e) in (&entities, &keys, &equipped).join() {
|
||||||
|
includeitem!(inv, ecs, e, k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Filter::Category(itemtype) => {
|
||||||
|
let items = ecs.read_storage::<Item>();
|
||||||
|
for (e, k, _i) in (&entities, &keys, &items)
|
||||||
|
.join()
|
||||||
|
.filter(|e| e.2.category == itemtype) {
|
||||||
|
includeitem!(inv, ecs, e, k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inv
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_inventory(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Entity>) {
|
pub fn show_inventory(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Entity>) {
|
||||||
let player_inventory = get_player_inventory(&gs.ecs);
|
let player_inventory = items(&gs.ecs, Filter::Backpack);
|
||||||
let count = player_inventory.len();
|
let count = player_inventory.len();
|
||||||
|
|
||||||
let (x_offset, y_offset) = (1, 10);
|
let (x_offset, y_offset) = (1, 10);
|
||||||
|
|
@ -915,7 +919,7 @@ pub fn show_inventory(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Optio
|
||||||
let y = 3 + y_offset;
|
let y = 3 + y_offset;
|
||||||
let width = get_max_inventory_width(&player_inventory);
|
let width = get_max_inventory_width(&player_inventory);
|
||||||
ctx.draw_box(x, y, width + 2, (count + 1) as i32, RGB::named(WHITE), RGB::named(BLACK));
|
ctx.draw_box(x, y, width + 2, (count + 1) as i32, RGB::named(WHITE), RGB::named(BLACK));
|
||||||
print_options(&player_inventory, x + 1, y + 1, ctx);
|
print_options(&gs.ecs, &player_inventory, x + 1, y + 1, ctx);
|
||||||
|
|
||||||
match ctx.key {
|
match ctx.key {
|
||||||
None => (ItemMenuResult::NoResponse, None),
|
None => (ItemMenuResult::NoResponse, None),
|
||||||
|
|
@ -924,22 +928,23 @@ pub fn show_inventory(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Optio
|
||||||
VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None),
|
VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None),
|
||||||
_ => {
|
_ => {
|
||||||
let selection = letter_to_option::letter_to_option(key, ctx.shift);
|
let selection = letter_to_option::letter_to_option(key, ctx.shift);
|
||||||
if selection > -1 && selection < (count as i32) {
|
if selection != -1 && check_key(selection as usize) {
|
||||||
if on_overmap {
|
if on_overmap {
|
||||||
gamelog::Logger
|
gamelog::Logger
|
||||||
::new()
|
::new()
|
||||||
.append("You can't use items on the overmap.")
|
.append("You can't use items on the overmap.")
|
||||||
.log();
|
.log();
|
||||||
} else {
|
} else {
|
||||||
return (
|
// Get the first entity with a Key {} component that has idx matching selection
|
||||||
ItemMenuResult::Selected,
|
let entities = gs.ecs.entities();
|
||||||
Some(
|
let keyed_items = gs.ecs.read_storage::<Key>();
|
||||||
player_inventory
|
let backpack = gs.ecs.read_storage::<InBackpack>();
|
||||||
.iter()
|
for (e, key, _b) in (&entities, &keyed_items, &backpack).join() {
|
||||||
.nth(selection as usize)
|
if key.idx == (selection as usize) {
|
||||||
.unwrap().1.0
|
return (ItemMenuResult::Selected, Some(e));
|
||||||
),
|
}
|
||||||
);
|
}
|
||||||
|
// TODO: Gamelog about not having selected item?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(ItemMenuResult::NoResponse, None)
|
(ItemMenuResult::NoResponse, None)
|
||||||
|
|
@ -949,7 +954,7 @@ pub fn show_inventory(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Optio
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drop_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Entity>) {
|
pub fn drop_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Entity>) {
|
||||||
let player_inventory = get_player_inventory(&gs.ecs);
|
let player_inventory = items(&gs.ecs, Filter::Backpack);
|
||||||
let count = player_inventory.len();
|
let count = player_inventory.len();
|
||||||
|
|
||||||
let (x_offset, y_offset) = (1, 10);
|
let (x_offset, y_offset) = (1, 10);
|
||||||
|
|
@ -967,7 +972,7 @@ pub fn drop_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Optio
|
||||||
let y = 3 + y_offset;
|
let y = 3 + y_offset;
|
||||||
let width = get_max_inventory_width(&player_inventory);
|
let width = get_max_inventory_width(&player_inventory);
|
||||||
ctx.draw_box(x, y, width + 2, (count + 1) as i32, RGB::named(WHITE), RGB::named(BLACK));
|
ctx.draw_box(x, y, width + 2, (count + 1) as i32, RGB::named(WHITE), RGB::named(BLACK));
|
||||||
print_options(&player_inventory, x + 1, y + 1, ctx);
|
print_options(&gs.ecs, &player_inventory, x + 1, y + 1, ctx);
|
||||||
|
|
||||||
match ctx.key {
|
match ctx.key {
|
||||||
None => (ItemMenuResult::NoResponse, None),
|
None => (ItemMenuResult::NoResponse, None),
|
||||||
|
|
@ -975,23 +980,23 @@ pub fn drop_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Optio
|
||||||
match key {
|
match key {
|
||||||
VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None),
|
VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None),
|
||||||
_ => {
|
_ => {
|
||||||
let selection = letter_to_option(key);
|
let selection = letter_to_option::letter_to_option(key, ctx.shift);
|
||||||
if selection > -1 && selection < (count as i32) {
|
if selection != -1 && check_key(selection as usize) {
|
||||||
if on_overmap {
|
if on_overmap {
|
||||||
gamelog::Logger
|
gamelog::Logger
|
||||||
::new()
|
::new()
|
||||||
.append("You can't drop items on the overmap.")
|
.append("You can't drop items on the overmap.")
|
||||||
.log();
|
.log();
|
||||||
} else {
|
} else {
|
||||||
return (
|
// Get the first entity with a Key {} component that has an idx matching "selection".
|
||||||
ItemMenuResult::Selected,
|
let entities = gs.ecs.entities();
|
||||||
Some(
|
let keyed_items = gs.ecs.read_storage::<Key>();
|
||||||
player_inventory
|
let backpack = gs.ecs.read_storage::<InBackpack>();
|
||||||
.iter()
|
for (e, key, _b) in (&entities, &keyed_items, &backpack).join() {
|
||||||
.nth(selection as usize)
|
if key.idx == (selection as usize) {
|
||||||
.unwrap().1.0
|
return (ItemMenuResult::Selected, Some(e));
|
||||||
),
|
}
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(ItemMenuResult::NoResponse, None)
|
(ItemMenuResult::NoResponse, None)
|
||||||
|
|
@ -1001,11 +1006,8 @@ pub fn drop_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Optio
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Entity>) {
|
pub fn remove_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Entity>) {
|
||||||
let player_entity = gs.ecs.fetch::<Entity>();
|
let player_inventory = items(&gs.ecs, Filter::Equipped);
|
||||||
let backpack = gs.ecs.read_storage::<Equipped>();
|
let count = player_inventory.len();
|
||||||
let entities = gs.ecs.entities();
|
|
||||||
let inventory = (&backpack).join().filter(|item| item.owner == *player_entity);
|
|
||||||
let count = inventory.count();
|
|
||||||
|
|
||||||
let (x_offset, y_offset) = (1, 10);
|
let (x_offset, y_offset) = (1, 10);
|
||||||
|
|
||||||
|
|
@ -1017,38 +1019,11 @@ pub fn remove_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Opt
|
||||||
"Unequip what? [aA-zZ][Esc.]"
|
"Unequip what? [aA-zZ][Esc.]"
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut equippable: Vec<(Entity, String)> = Vec::new();
|
|
||||||
let mut width = 2;
|
|
||||||
for (entity, _pack) in (&entities, &backpack)
|
|
||||||
.join()
|
|
||||||
.filter(|item| item.1.owner == *player_entity) {
|
|
||||||
let this_name = &obfuscate_name_ecs(&gs.ecs, entity).0;
|
|
||||||
let this_width = 5 + this_name.len();
|
|
||||||
width = if width > this_width { width } else { this_width };
|
|
||||||
equippable.push((entity, this_name.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let x = 1 + x_offset;
|
let x = 1 + x_offset;
|
||||||
let mut y = 3 + y_offset;
|
let y = 3 + y_offset;
|
||||||
|
let width = get_max_inventory_width(&player_inventory);
|
||||||
ctx.draw_box(x, y, width, (count + 1) as i32, RGB::named(WHITE), RGB::named(BLACK));
|
ctx.draw_box(x, y, width + 2, (count + 1) as i32, RGB::named(WHITE), RGB::named(BLACK));
|
||||||
y += 1;
|
print_options(&gs.ecs, &player_inventory, x + 1, y + 1, ctx);
|
||||||
|
|
||||||
let mut j = 0;
|
|
||||||
let renderables = gs.ecs.read_storage::<Renderable>();
|
|
||||||
for (e, name) in &equippable {
|
|
||||||
let (mut fg, glyph) = if let Some(renderable) = renderables.get(*e) {
|
|
||||||
(renderable.fg, renderable.glyph)
|
|
||||||
} else {
|
|
||||||
(RGB::named(WHITE), to_cp437('-'))
|
|
||||||
};
|
|
||||||
ctx.set(x + 1, y, RGB::named(YELLOW), RGB::named(BLACK), 97 + (j as FontCharType));
|
|
||||||
ctx.set(x + 3, y, fg, RGB::named(BLACK), glyph);
|
|
||||||
fg = RGB::named(item_colour_ecs(&gs.ecs, *e));
|
|
||||||
ctx.print_color(x + 5, y, fg, RGB::named(BLACK), name);
|
|
||||||
y += 1;
|
|
||||||
j += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
match ctx.key {
|
match ctx.key {
|
||||||
None => (ItemMenuResult::NoResponse, None),
|
None => (ItemMenuResult::NoResponse, None),
|
||||||
|
|
@ -1056,9 +1031,17 @@ pub fn remove_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Opt
|
||||||
match key {
|
match key {
|
||||||
VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None),
|
VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None),
|
||||||
_ => {
|
_ => {
|
||||||
let selection = letter_to_option(key);
|
let selection = letter_to_option::letter_to_option(key, ctx.shift);
|
||||||
if selection > -1 && selection < (count as i32) {
|
if selection != -1 && check_key(selection as usize) {
|
||||||
return (ItemMenuResult::Selected, Some(equippable[selection as usize].0));
|
// Get the first entity with a Key {} component that has an idx matching "selection".
|
||||||
|
let entities = gs.ecs.entities();
|
||||||
|
let keyed_items = gs.ecs.read_storage::<Key>();
|
||||||
|
let equipped = gs.ecs.read_storage::<Equipped>();
|
||||||
|
for (e, key, _e) in (&entities, &keyed_items, &equipped).join() {
|
||||||
|
if key.idx == (selection as usize) {
|
||||||
|
return (ItemMenuResult::Selected, Some(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(ItemMenuResult::NoResponse, None)
|
(ItemMenuResult::NoResponse, None)
|
||||||
}
|
}
|
||||||
|
|
@ -1458,3 +1441,72 @@ pub fn with_article(name: String) -> String {
|
||||||
}
|
}
|
||||||
format!("a {}", name)
|
format!("a {}", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn unique(
|
||||||
|
entity: Entity,
|
||||||
|
names: &ReadStorage<Name>,
|
||||||
|
obfuscated_names: &ReadStorage<ObfuscatedName>,
|
||||||
|
renderables: &ReadStorage<Renderable>,
|
||||||
|
beatitudes: &ReadStorage<Beatitude>,
|
||||||
|
magic_items: &ReadStorage<MagicItem>,
|
||||||
|
charges: Option<&ReadStorage<Charges>>,
|
||||||
|
dm: &MasterDungeonMap
|
||||||
|
) -> UniqueInventoryItem {
|
||||||
|
let item_colour = item_colour(entity, beatitudes);
|
||||||
|
let (singular, plural) = obfuscate_name(
|
||||||
|
entity,
|
||||||
|
names,
|
||||||
|
magic_items,
|
||||||
|
obfuscated_names,
|
||||||
|
beatitudes,
|
||||||
|
dm,
|
||||||
|
charges
|
||||||
|
);
|
||||||
|
let (renderables, glyph) = if let Some(renderable) = renderables.get(entity) {
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(renderable.fg.r * 255.0) as u8,
|
||||||
|
(renderable.fg.g * 255.0) as u8,
|
||||||
|
(renderable.fg.b * 255.0) as u8,
|
||||||
|
),
|
||||||
|
renderable.glyph,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
unreachable!("Item has no renderable component.")
|
||||||
|
};
|
||||||
|
let name = if let Some(name) = names.get(entity) {
|
||||||
|
name
|
||||||
|
} else {
|
||||||
|
unreachable!("Item has no name component.")
|
||||||
|
};
|
||||||
|
let beatitude_status = if let Some(beatitude) = beatitudes.get(entity) {
|
||||||
|
match beatitude.buc {
|
||||||
|
BUC::Blessed => 1,
|
||||||
|
BUC::Uncursed => 2,
|
||||||
|
BUC::Cursed => 3,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
UniqueInventoryItem {
|
||||||
|
display_name: DisplayName { singular: singular.clone(), plural },
|
||||||
|
rgb: item_colour,
|
||||||
|
renderables,
|
||||||
|
glyph,
|
||||||
|
beatitude_status,
|
||||||
|
name: name.name.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unique_ecs(ecs: &World, entity: Entity) -> UniqueInventoryItem {
|
||||||
|
return unique(
|
||||||
|
entity,
|
||||||
|
&ecs.read_storage::<Name>(),
|
||||||
|
&ecs.read_storage::<ObfuscatedName>(),
|
||||||
|
&ecs.read_storage::<Renderable>(),
|
||||||
|
&ecs.read_storage::<Beatitude>(),
|
||||||
|
&ecs.read_storage::<MagicItem>(),
|
||||||
|
Some(&ecs.read_storage::<Charges>()),
|
||||||
|
&ecs.fetch::<MasterDungeonMap>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@ use super::{
|
||||||
item_colour_ecs,
|
item_colour_ecs,
|
||||||
obfuscate_name_ecs,
|
obfuscate_name_ecs,
|
||||||
print_options,
|
print_options,
|
||||||
renderable_colour,
|
unique_ecs,
|
||||||
|
check_key,
|
||||||
|
letter_to_option,
|
||||||
ItemMenuResult,
|
ItemMenuResult,
|
||||||
UniqueInventoryItem,
|
InventorySlot,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
gamelog,
|
gamelog,
|
||||||
|
|
@ -18,10 +20,11 @@ use crate::{
|
||||||
Renderable,
|
Renderable,
|
||||||
states::state::*,
|
states::state::*,
|
||||||
BUC,
|
BUC,
|
||||||
|
Key,
|
||||||
};
|
};
|
||||||
use bracket_lib::prelude::*;
|
use bracket_lib::prelude::*;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// Handles the Remove Curse menu.
|
/// Handles the Remove Curse menu.
|
||||||
pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Entity>) {
|
pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Entity>) {
|
||||||
|
|
@ -33,11 +36,12 @@ pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<
|
||||||
let beatitudes = gs.ecs.read_storage::<Beatitude>();
|
let beatitudes = gs.ecs.read_storage::<Beatitude>();
|
||||||
let names = gs.ecs.read_storage::<Name>();
|
let names = gs.ecs.read_storage::<Name>();
|
||||||
let renderables = gs.ecs.read_storage::<Renderable>();
|
let renderables = gs.ecs.read_storage::<Renderable>();
|
||||||
|
let keys = gs.ecs.read_storage::<Key>();
|
||||||
|
|
||||||
let build_cursed_iterator = || {
|
let build_cursed_iterator = || {
|
||||||
(&entities, &items, &beatitudes, &renderables, &names)
|
(&entities, &items, &beatitudes, &renderables, &names, &keys)
|
||||||
.join()
|
.join()
|
||||||
.filter(|(item_entity, _i, b, _r, _n)| {
|
.filter(|(item_entity, _i, b, _r, _n, _k)| {
|
||||||
// Set all items to FALSE initially.
|
// Set all items to FALSE initially.
|
||||||
let mut keep = false;
|
let mut keep = false;
|
||||||
// If found in the player's backpack, set to TRUE
|
// If found in the player's backpack, set to TRUE
|
||||||
|
|
@ -86,34 +90,19 @@ pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<
|
||||||
.log();
|
.log();
|
||||||
return (ItemMenuResult::Selected, Some(item));
|
return (ItemMenuResult::Selected, Some(item));
|
||||||
}
|
}
|
||||||
let mut player_inventory: super::PlayerInventory = BTreeMap::new();
|
let mut player_inventory: super::PlayerInventory = HashMap::new();
|
||||||
for (entity, _i, _b, renderable, name) in build_cursed_iterator() {
|
for (entity, _i, _b, _r, _n, key) in build_cursed_iterator() {
|
||||||
let (singular, plural) = obfuscate_name_ecs(&gs.ecs, entity);
|
let unique_item = unique_ecs(&gs.ecs, entity);
|
||||||
let beatitude_status = if
|
|
||||||
let Some(beatitude) = gs.ecs.read_storage::<Beatitude>().get(entity)
|
|
||||||
{
|
|
||||||
match beatitude.buc {
|
|
||||||
BUC::Blessed => 1,
|
|
||||||
BUC::Uncursed => 2,
|
|
||||||
BUC::Cursed => 3,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
let unique_item = UniqueInventoryItem {
|
|
||||||
display_name: super::DisplayName { singular: singular.clone(), plural: plural.clone() },
|
|
||||||
rgb: item_colour_ecs(&gs.ecs, entity),
|
|
||||||
renderables: renderable_colour(&renderables, entity),
|
|
||||||
glyph: renderable.glyph,
|
|
||||||
beatitude_status: beatitude_status,
|
|
||||||
name: name.name.clone(),
|
|
||||||
};
|
|
||||||
player_inventory
|
player_inventory
|
||||||
.entry(unique_item)
|
.entry(unique_item)
|
||||||
.and_modify(|(_e, count)| {
|
.and_modify(|slot| {
|
||||||
*count += 1;
|
slot.count += 1;
|
||||||
})
|
})
|
||||||
.or_insert((entity, 1));
|
.or_insert(InventorySlot {
|
||||||
|
item: entity,
|
||||||
|
count: 1,
|
||||||
|
idx: key.idx,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// Get display args
|
// Get display args
|
||||||
let width = get_max_inventory_width(&player_inventory);
|
let width = get_max_inventory_width(&player_inventory);
|
||||||
|
|
@ -128,7 +117,7 @@ pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<
|
||||||
"Decurse which item? [aA-zZ][Esc.]"
|
"Decurse which item? [aA-zZ][Esc.]"
|
||||||
);
|
);
|
||||||
ctx.draw_box(x, y, width + 2, count + 1, RGB::named(WHITE), RGB::named(BLACK));
|
ctx.draw_box(x, y, width + 2, count + 1, RGB::named(WHITE), RGB::named(BLACK));
|
||||||
print_options(&player_inventory, x + 1, y + 1, ctx);
|
print_options(&gs.ecs, &player_inventory, x + 1, y + 1, ctx);
|
||||||
// Input
|
// Input
|
||||||
match ctx.key {
|
match ctx.key {
|
||||||
None => (ItemMenuResult::NoResponse, None),
|
None => (ItemMenuResult::NoResponse, None),
|
||||||
|
|
@ -136,21 +125,17 @@ pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<
|
||||||
match key {
|
match key {
|
||||||
VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None),
|
VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None),
|
||||||
_ => {
|
_ => {
|
||||||
let selection = letter_to_option(key);
|
let selection = letter_to_option::letter_to_option(key, ctx.shift);
|
||||||
if selection > -1 && selection < (count as i32) {
|
if selection != -1 && check_key(selection as usize) {
|
||||||
let item = player_inventory
|
// Get the first entity with a Key {} component that has an idx matching "selection".
|
||||||
.iter()
|
let entities = gs.ecs.entities();
|
||||||
.nth(selection as usize)
|
let keyed_items = gs.ecs.read_storage::<Key>();
|
||||||
.unwrap().1.0;
|
let backpack = gs.ecs.read_storage::<InBackpack>();
|
||||||
gamelog::Logger
|
for (e, key, _b) in (&entities, &keyed_items, &backpack).join() {
|
||||||
::new()
|
if key.idx == (selection as usize) {
|
||||||
.append("You decurse the")
|
return (ItemMenuResult::Selected, Some(e));
|
||||||
.colour(item_colour_ecs(&gs.ecs, item))
|
}
|
||||||
.append_n(obfuscate_name_ecs(&gs.ecs, item).0)
|
}
|
||||||
.colour(WHITE)
|
|
||||||
.append("!")
|
|
||||||
.log();
|
|
||||||
return (ItemMenuResult::Selected, Some(item));
|
|
||||||
}
|
}
|
||||||
(ItemMenuResult::NoResponse, None)
|
(ItemMenuResult::NoResponse, None)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,12 @@ pub fn draw_tooltips(ecs: &World, ctx: &mut BTerm, xy: Option<(i32, i32)>) {
|
||||||
if position.x == mouse_pos_adjusted.0 && position.y == mouse_pos_adjusted.1 {
|
if position.x == mouse_pos_adjusted.0 && position.y == mouse_pos_adjusted.1 {
|
||||||
let mut tip = Tooltip::new();
|
let mut tip = Tooltip::new();
|
||||||
tip.add(crate::gui::obfuscate_name_ecs(ecs, entity).0, renderable.fg);
|
tip.add(crate::gui::obfuscate_name_ecs(ecs, entity).0, renderable.fg);
|
||||||
|
let intrinsics = ecs.read_storage::<crate::components::Intrinsics>();
|
||||||
|
if let Some(intrinsics) = intrinsics.get(entity) {
|
||||||
|
if !intrinsics.list.is_empty() {
|
||||||
|
tip.add(intrinsics.describe(), RGB::named(WHITE));
|
||||||
|
}
|
||||||
|
}
|
||||||
// Attributes
|
// Attributes
|
||||||
let attr = attributes.get(entity);
|
let attr = attributes.get(entity);
|
||||||
if let Some(a) = attr {
|
if let Some(a) = attr {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ use super::{
|
||||||
HungerClock,
|
HungerClock,
|
||||||
HungerState,
|
HungerState,
|
||||||
TakingTurn,
|
TakingTurn,
|
||||||
|
DamageType,
|
||||||
|
Intrinsics,
|
||||||
};
|
};
|
||||||
use bracket_lib::prelude::*;
|
use bracket_lib::prelude::*;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
@ -52,10 +54,11 @@ impl<'a> System<'a> for HungerSystem {
|
||||||
ReadExpect<'a, Entity>,
|
ReadExpect<'a, Entity>,
|
||||||
ReadStorage<'a, Clock>,
|
ReadStorage<'a, Clock>,
|
||||||
ReadStorage<'a, TakingTurn>,
|
ReadStorage<'a, TakingTurn>,
|
||||||
|
ReadStorage<'a, Intrinsics>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
let (entities, mut hunger_clock, player_entity, turn_clock, turns) = data;
|
let (entities, mut hunger_clock, player_entity, turn_clock, turns, intrinsics) = data;
|
||||||
|
|
||||||
// If the turn clock isn't taking a turn this tick, don't bother ticking hunger.
|
// If the turn clock isn't taking a turn this tick, don't bother ticking hunger.
|
||||||
let mut ticked = false;
|
let mut ticked = false;
|
||||||
|
|
@ -71,14 +74,23 @@ impl<'a> System<'a> for HungerSystem {
|
||||||
if hunger_clock.duration >= MAX_SATIATION {
|
if hunger_clock.duration >= MAX_SATIATION {
|
||||||
hunger_clock.duration = MAX_SATIATION;
|
hunger_clock.duration = MAX_SATIATION;
|
||||||
} else {
|
} else {
|
||||||
hunger_clock.duration -= BASE_CLOCK_DECREMENT_PER_TURN;
|
let mut modifier = 0;
|
||||||
|
let intrinsic_regen = if let Some(i) = intrinsics.get(entity) {
|
||||||
|
i.list.contains(&crate::Intrinsic::Regeneration)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
if intrinsic_regen {
|
||||||
|
modifier += 1;
|
||||||
|
}
|
||||||
|
hunger_clock.duration -= BASE_CLOCK_DECREMENT_PER_TURN + modifier;
|
||||||
}
|
}
|
||||||
let initial_state = hunger_clock.state;
|
let initial_state = hunger_clock.state;
|
||||||
hunger_clock.state = get_hunger_state(hunger_clock.duration);
|
hunger_clock.state = get_hunger_state(hunger_clock.duration);
|
||||||
if hunger_clock.state == HungerState::Starving {
|
if hunger_clock.state == HungerState::Starving {
|
||||||
add_effect(
|
add_effect(
|
||||||
None,
|
None,
|
||||||
EffectType::Damage { amount: 1 },
|
EffectType::Damage { amount: 1, damage_type: DamageType::Forced },
|
||||||
Targets::Entity { target: entity }
|
Targets::Entity { target: entity }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use crate::{
|
||||||
ObfuscatedName,
|
ObfuscatedName,
|
||||||
Position,
|
Position,
|
||||||
WantsToPickupItem,
|
WantsToPickupItem,
|
||||||
|
WantsToAssignKey,
|
||||||
};
|
};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
use crate::data::messages;
|
use crate::data::messages;
|
||||||
|
|
@ -33,6 +34,7 @@ impl<'a> System<'a> for ItemCollectionSystem {
|
||||||
ReadStorage<'a, Beatitude>,
|
ReadStorage<'a, Beatitude>,
|
||||||
ReadExpect<'a, MasterDungeonMap>,
|
ReadExpect<'a, MasterDungeonMap>,
|
||||||
ReadStorage<'a, Charges>,
|
ReadStorage<'a, Charges>,
|
||||||
|
ReadStorage<'a, WantsToAssignKey>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
|
|
@ -48,17 +50,11 @@ impl<'a> System<'a> for ItemCollectionSystem {
|
||||||
beatitudes,
|
beatitudes,
|
||||||
dm,
|
dm,
|
||||||
wands,
|
wands,
|
||||||
|
wants_key,
|
||||||
) = data;
|
) = data;
|
||||||
|
let mut to_remove: Vec<Entity> = Vec::new();
|
||||||
for pickup in wants_pickup.join() {
|
// For every item that wants to be picked up that *isn't* waiting on a key assignment.
|
||||||
positions.remove(pickup.item);
|
for (pickup, _key) in (&wants_pickup, !&wants_key).join() {
|
||||||
backpack
|
|
||||||
.insert(pickup.item, InBackpack { owner: pickup.collected_by })
|
|
||||||
.expect("Unable to pickup item.");
|
|
||||||
equipment_changed
|
|
||||||
.insert(pickup.collected_by, EquipmentChanged {})
|
|
||||||
.expect("Unable to insert EquipmentChanged.");
|
|
||||||
|
|
||||||
if pickup.collected_by == *player_entity {
|
if pickup.collected_by == *player_entity {
|
||||||
gamelog::Logger
|
gamelog::Logger
|
||||||
::new()
|
::new()
|
||||||
|
|
@ -82,8 +78,17 @@ impl<'a> System<'a> for ItemCollectionSystem {
|
||||||
.period()
|
.period()
|
||||||
.log();
|
.log();
|
||||||
}
|
}
|
||||||
|
positions.remove(pickup.item);
|
||||||
|
backpack
|
||||||
|
.insert(pickup.item, InBackpack { owner: pickup.collected_by })
|
||||||
|
.expect("Unable to pickup item");
|
||||||
|
equipment_changed
|
||||||
|
.insert(pickup.collected_by, EquipmentChanged {})
|
||||||
|
.expect("Unable to insert EquipmentChanged");
|
||||||
|
to_remove.push(pickup.collected_by);
|
||||||
|
}
|
||||||
|
for item in to_remove.iter() {
|
||||||
|
wants_pickup.remove(*item);
|
||||||
}
|
}
|
||||||
|
|
||||||
wants_pickup.clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use crate::{
|
||||||
ObfuscatedName,
|
ObfuscatedName,
|
||||||
Position,
|
Position,
|
||||||
WantsToDropItem,
|
WantsToDropItem,
|
||||||
|
WantsToRemoveKey,
|
||||||
};
|
};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
use crate::data::messages;
|
use crate::data::messages;
|
||||||
|
|
@ -34,6 +35,7 @@ impl<'a> System<'a> for ItemDropSystem {
|
||||||
ReadStorage<'a, ObfuscatedName>,
|
ReadStorage<'a, ObfuscatedName>,
|
||||||
ReadExpect<'a, MasterDungeonMap>,
|
ReadExpect<'a, MasterDungeonMap>,
|
||||||
ReadStorage<'a, Charges>,
|
ReadStorage<'a, Charges>,
|
||||||
|
WriteStorage<'a, WantsToRemoveKey>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
|
|
@ -50,6 +52,7 @@ impl<'a> System<'a> for ItemDropSystem {
|
||||||
obfuscated_names,
|
obfuscated_names,
|
||||||
dm,
|
dm,
|
||||||
wands,
|
wands,
|
||||||
|
mut keys,
|
||||||
) = data;
|
) = data;
|
||||||
|
|
||||||
for (entity, to_drop) in (&entities, &wants_drop).join() {
|
for (entity, to_drop) in (&entities, &wants_drop).join() {
|
||||||
|
|
@ -68,6 +71,9 @@ impl<'a> System<'a> for ItemDropSystem {
|
||||||
backpack.remove(to_drop.item);
|
backpack.remove(to_drop.item);
|
||||||
|
|
||||||
if entity == *player_entity {
|
if entity == *player_entity {
|
||||||
|
keys.insert(to_drop.item, WantsToRemoveKey {}).expect(
|
||||||
|
"Unable to insert WantsToRemoveKey"
|
||||||
|
);
|
||||||
gamelog::Logger
|
gamelog::Logger
|
||||||
::new()
|
::new()
|
||||||
.append(messages::YOU_DROP_ITEM)
|
.append(messages::YOU_DROP_ITEM)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,13 @@
|
||||||
use crate::{ Beatitude, IdentifiedBeatitude, IdentifiedItem, Item, MasterDungeonMap, Name, ObfuscatedName, Player };
|
use crate::{
|
||||||
|
Beatitude,
|
||||||
|
IdentifiedBeatitude,
|
||||||
|
IdentifiedItem,
|
||||||
|
Item,
|
||||||
|
MasterDungeonMap,
|
||||||
|
Name,
|
||||||
|
ObfuscatedName,
|
||||||
|
Player,
|
||||||
|
};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
use crate::data::events::*;
|
use crate::data::events::*;
|
||||||
use crate::gamelog;
|
use crate::gamelog;
|
||||||
|
|
@ -35,7 +44,7 @@ impl<'a> System<'a> for ItemIdentificationSystem {
|
||||||
let tag = crate::raws::get_id_from_name(id.name.clone());
|
let tag = crate::raws::get_id_from_name(id.name.clone());
|
||||||
if !dm.identified_items.contains(&id.name) && crate::raws::is_tag_magic(&tag) {
|
if !dm.identified_items.contains(&id.name) && crate::raws::is_tag_magic(&tag) {
|
||||||
if gamelog::get_event_count(EVENT::COUNT_TURN) != 1 {
|
if gamelog::get_event_count(EVENT::COUNT_TURN) != 1 {
|
||||||
gamelog::record_event(EVENT::IDENTIFIED(id.name.clone()));
|
gamelog::record_event(EVENT::Identified(id.name.clone()));
|
||||||
}
|
}
|
||||||
dm.identified_items.insert(id.name.clone());
|
dm.identified_items.insert(id.name.clone());
|
||||||
for (entity, _item, name) in (&entities, &items, &names).join() {
|
for (entity, _item, name) in (&entities, &items, &names).join() {
|
||||||
|
|
|
||||||
153
src/inventory/keyhandling.rs
Normal file
153
src/inventory/keyhandling.rs
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
use crate::{
|
||||||
|
gamelog,
|
||||||
|
gui::unique,
|
||||||
|
Beatitude,
|
||||||
|
Charges,
|
||||||
|
MagicItem,
|
||||||
|
MasterDungeonMap,
|
||||||
|
Name,
|
||||||
|
ObfuscatedName,
|
||||||
|
Stackable,
|
||||||
|
Renderable,
|
||||||
|
WantsToAssignKey,
|
||||||
|
WantsToRemoveKey,
|
||||||
|
Key,
|
||||||
|
};
|
||||||
|
use specs::prelude::*;
|
||||||
|
use crate::data::messages;
|
||||||
|
use bracket_lib::prelude::*;
|
||||||
|
use crate::invkeys::*;
|
||||||
|
|
||||||
|
pub struct KeyHandling {}
|
||||||
|
|
||||||
|
const DEBUG_KEYHANDLING: bool = true;
|
||||||
|
|
||||||
|
impl<'a> System<'a> for KeyHandling {
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
type SystemData = (
|
||||||
|
Entities<'a>,
|
||||||
|
WriteStorage<'a, WantsToAssignKey>,
|
||||||
|
WriteStorage<'a, WantsToRemoveKey>,
|
||||||
|
WriteStorage<'a, Key>,
|
||||||
|
ReadStorage<'a, Stackable>,
|
||||||
|
ReadStorage<'a, Name>,
|
||||||
|
ReadStorage<'a, ObfuscatedName>,
|
||||||
|
ReadStorage<'a, Renderable>,
|
||||||
|
ReadStorage<'a, Beatitude>,
|
||||||
|
ReadStorage<'a, MagicItem>,
|
||||||
|
ReadStorage<'a, Charges>,
|
||||||
|
ReadExpect<'a, MasterDungeonMap>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
|
let (
|
||||||
|
entities,
|
||||||
|
mut wants_keys,
|
||||||
|
mut wants_removekey,
|
||||||
|
mut keys,
|
||||||
|
stackable,
|
||||||
|
names,
|
||||||
|
obfuscated_names,
|
||||||
|
renderables,
|
||||||
|
beatitudes,
|
||||||
|
magic_items,
|
||||||
|
wands,
|
||||||
|
dm,
|
||||||
|
) = data;
|
||||||
|
|
||||||
|
// For every entity that wants to be picked up, that still needs a key assigned.
|
||||||
|
for (e, _wants_key) in (&entities, &wants_keys).join() {
|
||||||
|
if DEBUG_KEYHANDLING {
|
||||||
|
console::log(&format!("KEYHANDLING: Assigning key to {:?}", e));
|
||||||
|
}
|
||||||
|
let (stacks, mut handled, unique) = (
|
||||||
|
if let Some(_) = stackable.get(e) { true } else { false },
|
||||||
|
false,
|
||||||
|
unique(
|
||||||
|
e,
|
||||||
|
&names,
|
||||||
|
&obfuscated_names,
|
||||||
|
&renderables,
|
||||||
|
&beatitudes,
|
||||||
|
&magic_items,
|
||||||
|
Some(&wands),
|
||||||
|
&dm
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if stacks {
|
||||||
|
console::log(&format!("KEYHANDLING: Item is stackable."));
|
||||||
|
let maybe_key = item_exists(&unique);
|
||||||
|
if maybe_key.is_some() {
|
||||||
|
console::log(&format!("KEYHANDLING: Existing stack found for this item."));
|
||||||
|
let key = maybe_key.unwrap();
|
||||||
|
keys.insert(e, Key { idx: key }).expect("Unable to insert Key.");
|
||||||
|
console::log(&format!("KEYHANDLING: Assigned key idx {} to item.", key));
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !handled {
|
||||||
|
console::log(
|
||||||
|
&format!("KEYHANDLING: Item is not stackable, or no existing stack found.")
|
||||||
|
);
|
||||||
|
if let Some(idx) = assign_next_available() {
|
||||||
|
console::log(
|
||||||
|
&format!("KEYHANDLING: Assigned next available index {} to item.", idx)
|
||||||
|
);
|
||||||
|
keys.insert(e, Key { idx }).expect("Unable to insert Key.");
|
||||||
|
register_stackable(stacks, unique, idx);
|
||||||
|
} else {
|
||||||
|
console::log(&format!("KEYHANDLING: No more keys available."));
|
||||||
|
gamelog::Logger
|
||||||
|
::new()
|
||||||
|
.append(messages::NO_MORE_KEYS)
|
||||||
|
.colour(WHITE)
|
||||||
|
.period()
|
||||||
|
.log();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (e, _wants_key) in (&entities, &wants_removekey).join() {
|
||||||
|
let idx = keys.get(e).unwrap().idx;
|
||||||
|
if DEBUG_KEYHANDLING {
|
||||||
|
console::log(&format!("KEYHANDLING: Removing key from {:?}", e));
|
||||||
|
}
|
||||||
|
// If the item is *not* stackable, then we can just remove the key and clear the index.
|
||||||
|
if let None = stackable.get(e) {
|
||||||
|
console::log(
|
||||||
|
&format!("KEYHANDLING: Item is not stackable, clearing index {}.", idx)
|
||||||
|
);
|
||||||
|
clear_idx(idx);
|
||||||
|
keys.remove(e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// If the item *is* stackable, then we need to check if there are any other items that
|
||||||
|
// share this key assignment, before clearing the index.
|
||||||
|
console::log(
|
||||||
|
&format!(
|
||||||
|
"KEYHANDLING: Item is stackable, checking if any other items share this key."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let mut sole_item_with_key = true;
|
||||||
|
for (entity, key) in (&entities, &keys).join() {
|
||||||
|
if entity != e && key.idx == idx {
|
||||||
|
console::log(&format!("KEYHANDLING: Another item shares index {}", idx));
|
||||||
|
sole_item_with_key = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If no other items shared this key, free up the index.
|
||||||
|
if sole_item_with_key {
|
||||||
|
console::log(
|
||||||
|
&format!("KEYHANDLING: No other items found, clearing index {}.", idx)
|
||||||
|
);
|
||||||
|
clear_idx(idx);
|
||||||
|
}
|
||||||
|
// Either way, remove the key component from this item, because we're dropping it.
|
||||||
|
console::log(&format!("KEYHANDLING: Removing key component from item."));
|
||||||
|
keys.remove(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
wants_removekey.clear();
|
||||||
|
wants_keys.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ mod equip_system;
|
||||||
mod identification_system;
|
mod identification_system;
|
||||||
mod remove_system;
|
mod remove_system;
|
||||||
mod use_system;
|
mod use_system;
|
||||||
|
mod keyhandling;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
collection_system::ItemCollectionSystem,
|
collection_system::ItemCollectionSystem,
|
||||||
|
|
@ -12,4 +13,5 @@ pub use self::{
|
||||||
identification_system::ItemIdentificationSystem,
|
identification_system::ItemIdentificationSystem,
|
||||||
remove_system::ItemRemoveSystem,
|
remove_system::ItemRemoveSystem,
|
||||||
use_system::ItemUseSystem,
|
use_system::ItemUseSystem,
|
||||||
|
keyhandling::KeyHandling,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
59
src/invkeys.rs
Normal file
59
src/invkeys.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::gui::UniqueInventoryItem;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref INVKEYS: Mutex<HashMap<UniqueInventoryItem, usize>> = Mutex::new(HashMap::new());
|
||||||
|
pub static ref ASSIGNEDKEYS: Mutex<Vec<bool>> = Mutex::new(vec![false; 52]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For (de)serialization.
|
||||||
|
pub fn clone_invkeys() -> HashMap<UniqueInventoryItem, usize> {
|
||||||
|
let invkeys = INVKEYS.lock().unwrap();
|
||||||
|
invkeys.clone()
|
||||||
|
}
|
||||||
|
pub fn restore_invkeys(invkeys: HashMap<UniqueInventoryItem, usize>) {
|
||||||
|
INVKEYS.lock().unwrap().clear();
|
||||||
|
INVKEYS.lock().unwrap().extend(invkeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_key(idx: usize) -> bool {
|
||||||
|
let lock = ASSIGNEDKEYS.lock().unwrap();
|
||||||
|
lock[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn item_exists(item: &UniqueInventoryItem) -> Option<usize> {
|
||||||
|
let invkeys = INVKEYS.lock().unwrap();
|
||||||
|
use bracket_lib::prelude::*;
|
||||||
|
console::log(&format!("{:?}", item));
|
||||||
|
if invkeys.contains_key(item) {
|
||||||
|
Some(*invkeys.get(item).unwrap())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assign_next_available() -> Option<usize> {
|
||||||
|
let mut lock = ASSIGNEDKEYS.lock().unwrap();
|
||||||
|
for (i, key) in lock.iter_mut().enumerate() {
|
||||||
|
if !*key {
|
||||||
|
*key = true;
|
||||||
|
return Some(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_stackable(stacks: bool, item: UniqueInventoryItem, idx: usize) {
|
||||||
|
if stacks {
|
||||||
|
let mut invkeys = INVKEYS.lock().unwrap();
|
||||||
|
invkeys.insert(item, idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_idx(idx: usize) {
|
||||||
|
let mut lock = ASSIGNEDKEYS.lock().unwrap();
|
||||||
|
lock[idx] = false;
|
||||||
|
let mut invkeys = INVKEYS.lock().unwrap();
|
||||||
|
invkeys.retain(|_k, v| *v != idx);
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,9 @@ extern crate serde;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
pub mod macros;
|
||||||
|
|
||||||
pub mod camera;
|
pub mod camera;
|
||||||
pub mod components;
|
pub mod components;
|
||||||
pub mod raws;
|
pub mod raws;
|
||||||
|
|
@ -35,6 +38,7 @@ pub mod rex_assets;
|
||||||
pub mod spatial;
|
pub mod spatial;
|
||||||
pub mod morgue;
|
pub mod morgue;
|
||||||
pub mod states;
|
pub mod states;
|
||||||
|
pub mod invkeys;
|
||||||
|
|
||||||
pub use components::*;
|
pub use components::*;
|
||||||
use particle_system::ParticleBuilder;
|
use particle_system::ParticleBuilder;
|
||||||
|
|
|
||||||
93
src/macros/mod.rs
Normal file
93
src/macros/mod.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
// macros/mod.rs
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
/// Used to check if the player has a given component.
|
||||||
|
macro_rules! player_has_component {
|
||||||
|
($ecs:expr, $component:ty) => {
|
||||||
|
{
|
||||||
|
let player = $ecs.fetch::<Entity>();
|
||||||
|
let component = $ecs.read_storage::<$component>();
|
||||||
|
if let Some(player_component) = component.get(*player) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
/// Used to check if a given entity has a given Intrinsic.
|
||||||
|
macro_rules! has {
|
||||||
|
($ecs:expr, $entity:expr, $intrinsic:expr) => {
|
||||||
|
{
|
||||||
|
let intrinsics = $ecs.read_storage::<crate::Intrinsics>();
|
||||||
|
if let Some(has_intrinsics) = intrinsics.get($entity) {
|
||||||
|
has_intrinsics.list.contains(&$intrinsic)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
/// Used to check if the player has a given Intrinsic.
|
||||||
|
macro_rules! player_has {
|
||||||
|
($ecs:expr, $intrinsic:expr) => {
|
||||||
|
{
|
||||||
|
let player = $ecs.fetch::<Entity>();
|
||||||
|
let intrinsics = $ecs.read_storage::<crate::Intrinsics>();
|
||||||
|
if let Some(player_intrinsics) = intrinsics.get(*player) {
|
||||||
|
player_intrinsics.list.contains(&$intrinsic)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
/// Handles adding an Intrinsic to the player, and adding it to the IntrinsicChanged component.
|
||||||
|
macro_rules! add_intr {
|
||||||
|
($ecs:expr, $entity:expr, $intrinsic:expr) => {
|
||||||
|
{
|
||||||
|
let mut intrinsics = $ecs.write_storage::<crate::Intrinsics>();
|
||||||
|
if let Some(player_intrinsics) = intrinsics.get_mut($entity) {
|
||||||
|
if !player_intrinsics.list.contains(&$intrinsic) {
|
||||||
|
player_intrinsics.list.insert($intrinsic);
|
||||||
|
let mut intrinsic_changed = $ecs.write_storage::<crate::IntrinsicChanged>();
|
||||||
|
if let Some(this_intrinsic_changed) = intrinsic_changed.get_mut($entity) {
|
||||||
|
this_intrinsic_changed.gained.insert($intrinsic);
|
||||||
|
} else {
|
||||||
|
intrinsic_changed.insert($entity, crate::IntrinsicChanged {
|
||||||
|
gained: {
|
||||||
|
let mut m = std::collections::HashSet::new();
|
||||||
|
m.insert($intrinsic);
|
||||||
|
m
|
||||||
|
},
|
||||||
|
lost: std::collections::HashSet::new()
|
||||||
|
}).expect("Failed to insert IntrinsicChanged component.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
intrinsics.insert($entity, crate::Intrinsics {
|
||||||
|
list: {
|
||||||
|
let mut m = std::collections::HashSet::new();
|
||||||
|
m.insert($intrinsic);
|
||||||
|
m
|
||||||
|
}
|
||||||
|
}).expect("Failed to insert Intrinsics component.");
|
||||||
|
let mut intrinsic_changed = $ecs.write_storage::<crate::IntrinsicChanged>();
|
||||||
|
intrinsic_changed.insert($entity, crate::IntrinsicChanged {
|
||||||
|
gained: {
|
||||||
|
let mut m = std::collections::HashSet::new();
|
||||||
|
m.insert($intrinsic);
|
||||||
|
m
|
||||||
|
},
|
||||||
|
lost: std::collections::HashSet::new()
|
||||||
|
}).expect("Failed to insert IntrinsicChanged component.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
12
src/main.rs
12
src/main.rs
|
|
@ -109,6 +109,14 @@ fn main() -> BError {
|
||||||
gs.ecs.register::<SpawnParticleSimple>();
|
gs.ecs.register::<SpawnParticleSimple>();
|
||||||
gs.ecs.register::<SpawnParticleBurst>();
|
gs.ecs.register::<SpawnParticleBurst>();
|
||||||
gs.ecs.register::<SpawnParticleLine>();
|
gs.ecs.register::<SpawnParticleLine>();
|
||||||
|
gs.ecs.register::<HasDamageModifiers>();
|
||||||
|
gs.ecs.register::<Intrinsics>();
|
||||||
|
gs.ecs.register::<IntrinsicChanged>();
|
||||||
|
gs.ecs.register::<Stackable>();
|
||||||
|
gs.ecs.register::<WantsToAssignKey>();
|
||||||
|
gs.ecs.register::<Key>();
|
||||||
|
gs.ecs.register::<WantsToRemoveKey>();
|
||||||
|
gs.ecs.register::<WantsToDelete>();
|
||||||
gs.ecs.register::<SimpleMarker<SerializeMe>>();
|
gs.ecs.register::<SimpleMarker<SerializeMe>>();
|
||||||
gs.ecs.register::<SerializationHelper>();
|
gs.ecs.register::<SerializationHelper>();
|
||||||
gs.ecs.register::<DMSerializationHelper>();
|
gs.ecs.register::<DMSerializationHelper>();
|
||||||
|
|
@ -121,7 +129,7 @@ fn main() -> BError {
|
||||||
gs.ecs.insert(map::MasterDungeonMap::new()); // Master map list
|
gs.ecs.insert(map::MasterDungeonMap::new()); // Master map list
|
||||||
gs.ecs.insert(Map::new(true, 1, 64, 64, 0, "New Map", "N", 0)); // Map
|
gs.ecs.insert(Map::new(true, 1, 64, 64, 0, "New Map", "N", 0)); // Map
|
||||||
gs.ecs.insert(Point::new(0, 0)); // Player pos
|
gs.ecs.insert(Point::new(0, 0)); // Player pos
|
||||||
gs.ecs.insert(gui::Ancestry::Dwarf); // ancestry
|
gs.ecs.insert(gui::Ancestry::Human); // ancestry
|
||||||
let player_entity = spawner::player(&mut gs.ecs, 0, 0);
|
let player_entity = spawner::player(&mut gs.ecs, 0, 0);
|
||||||
gs.ecs.insert(player_entity); // Player entity
|
gs.ecs.insert(player_entity); // Player entity
|
||||||
gs.ecs.insert(RunState::MapGeneration {}); // RunState
|
gs.ecs.insert(RunState::MapGeneration {}); // RunState
|
||||||
|
|
@ -129,7 +137,7 @@ fn main() -> BError {
|
||||||
gs.ecs.insert(rex_assets::RexAssets::new());
|
gs.ecs.insert(rex_assets::RexAssets::new());
|
||||||
|
|
||||||
gamelog::setup_log();
|
gamelog::setup_log();
|
||||||
gamelog::record_event(data::events::EVENT::LEVEL(1));
|
gamelog::record_event(data::events::EVENT::Level(1));
|
||||||
gs.generate_world_map(1, TileType::Floor);
|
gs.generate_world_map(1, TileType::Floor);
|
||||||
|
|
||||||
main_loop(context, gs)
|
main_loop(context, gs)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use super::{ BuilderMap, MetaMapBuilder, Position };
|
use super::{ BuilderMap, MetaMapBuilder, Position };
|
||||||
use bracket_lib::prelude::*;
|
use bracket_lib::prelude::*;
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub enum XStart {
|
pub enum XStart {
|
||||||
|
|
@ -76,7 +75,7 @@ impl AreaStartingPosition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if available_floors.is_empty() {
|
if available_floors.is_empty() {
|
||||||
panic!("No valid floors to start on");
|
unreachable!("No valid floors to start on.");
|
||||||
}
|
}
|
||||||
|
|
||||||
available_floors.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
|
available_floors.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ impl RoadExit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if available_floors.is_empty() {
|
if available_floors.is_empty() {
|
||||||
panic!("No valid floors to start on.");
|
unreachable!("No valid floors to start on.");
|
||||||
}
|
}
|
||||||
available_floors.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
|
available_floors.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
|
||||||
let end_x = (available_floors[0].0 as i32) % build_data.map.width;
|
let end_x = (available_floors[0].0 as i32) % build_data.map.width;
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ use forest::forest_builder;
|
||||||
mod foliage;
|
mod foliage;
|
||||||
use foliage::Foliage;
|
use foliage::Foliage;
|
||||||
mod room_themer;
|
mod room_themer;
|
||||||
use room_themer::{ Theme, ThemeRooms };
|
use room_themer::ThemeRooms;
|
||||||
|
|
||||||
// Shared data to be passed around build chain
|
// Shared data to be passed around build chain
|
||||||
pub struct BuilderMap {
|
pub struct BuilderMap {
|
||||||
|
|
@ -137,7 +137,7 @@ impl BuilderChain {
|
||||||
None => {
|
None => {
|
||||||
self.starter = Some(starter);
|
self.starter = Some(starter);
|
||||||
}
|
}
|
||||||
Some(_) => panic!("You can only have one starting builder."),
|
Some(_) => unreachable!("You can only have one starting builder."),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,7 +147,7 @@ impl BuilderChain {
|
||||||
|
|
||||||
pub fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
|
pub fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
|
||||||
match &mut self.starter {
|
match &mut self.starter {
|
||||||
None => panic!("Cannot run a map builder chain without a starting build system"),
|
None => unreachable!("Cannot run a map builder chain without a starting build system"),
|
||||||
Some(starter) => {
|
Some(starter) => {
|
||||||
// Build the starting map
|
// Build the starting map
|
||||||
starter.build_map(rng, &mut self.build_data);
|
starter.build_map(rng, &mut self.build_data);
|
||||||
|
|
@ -438,21 +438,19 @@ pub fn random_builder(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn level_builder(
|
pub fn level_builder(
|
||||||
new_id: i32,
|
id: i32,
|
||||||
rng: &mut RandomNumberGenerator,
|
rng: &mut RandomNumberGenerator,
|
||||||
width: i32,
|
width: i32,
|
||||||
height: i32,
|
height: i32,
|
||||||
initial_player_level: i32
|
initial_player_level: i32
|
||||||
) -> BuilderChain {
|
) -> BuilderChain {
|
||||||
// TODO: With difficulty and ID/depth decoupled, this can be used for branches later.
|
match id {
|
||||||
let difficulty = new_id;
|
|
||||||
match new_id {
|
|
||||||
ID_OVERMAP => overmap_builder(),
|
ID_OVERMAP => overmap_builder(),
|
||||||
ID_TOWN => town_builder(new_id, rng, width, height, 0, initial_player_level),
|
ID_TOWN => town_builder(id, rng, width, height, 0, initial_player_level),
|
||||||
ID_TOWN2 => forest_builder(new_id, rng, width, height, 1, initial_player_level),
|
ID_TOWN2 => forest_builder(id, rng, width, height, 1, initial_player_level),
|
||||||
ID_TOWN3 =>
|
ID_TOWN3 =>
|
||||||
random_builder(
|
random_builder(
|
||||||
new_id,
|
id,
|
||||||
rng,
|
rng,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
|
@ -462,25 +460,25 @@ pub fn level_builder(
|
||||||
true,
|
true,
|
||||||
BuildType::Room
|
BuildType::Room
|
||||||
),
|
),
|
||||||
_ if new_id >= ID_INFINITE =>
|
_ if id >= ID_INFINITE =>
|
||||||
random_builder(
|
random_builder(
|
||||||
new_id,
|
id,
|
||||||
rng,
|
rng,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
difficulty,
|
4 + diff(ID_INFINITE, id),
|
||||||
new_id - ID_INFINITE + 1,
|
1 + diff(ID_INFINITE, id),
|
||||||
initial_player_level,
|
initial_player_level,
|
||||||
false,
|
false,
|
||||||
BuildType::Room
|
BuildType::Room
|
||||||
),
|
),
|
||||||
_ =>
|
_ => // This should be unreachable!() eventually. Right now it's reachable with the debug/cheat menu. It should not be in normal gameplay.
|
||||||
random_builder(
|
random_builder(
|
||||||
new_id,
|
id,
|
||||||
rng,
|
rng,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
difficulty,
|
1,
|
||||||
404,
|
404,
|
||||||
initial_player_level,
|
initial_player_level,
|
||||||
false,
|
false,
|
||||||
|
|
@ -488,3 +486,7 @@ pub fn level_builder(
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn diff(branch_id: i32, lvl_id: i32) -> i32 {
|
||||||
|
return lvl_id - branch_id;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ impl RoomBasedSpawner {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("RoomBasedSpawner only works after rooms have been created");
|
unreachable!("RoomBasedSpawner tried to run without any rooms.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ impl RoomBasedStairs {
|
||||||
build_data.map.tiles[stairs_idx] = TileType::DownStair;
|
build_data.map.tiles[stairs_idx] = TileType::DownStair;
|
||||||
build_data.take_snapshot();
|
build_data.take_snapshot();
|
||||||
} else {
|
} else {
|
||||||
panic!("RoomBasedStairs only works after rooms have been created");
|
unreachable!("RoomBasedStairs tried to run without any rooms.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ impl RoomBasedStartingPosition {
|
||||||
let start_pos = rooms[0].center();
|
let start_pos = rooms[0].center();
|
||||||
build_data.starting_position = Some(Position { x: start_pos.x, y: start_pos.y });
|
build_data.starting_position = Some(Position { x: start_pos.x, y: start_pos.y });
|
||||||
} else {
|
} else {
|
||||||
panic!("RoomBasedStartingPosition only works after rooms have been created");
|
unreachable!("RoomBasedStartingPosition tried to run without any rooms.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ impl RoomCornerRounder {
|
||||||
if let Some(rooms_builder) = &build_data.rooms {
|
if let Some(rooms_builder) = &build_data.rooms {
|
||||||
rooms = rooms_builder.clone();
|
rooms = rooms_builder.clone();
|
||||||
} else {
|
} else {
|
||||||
panic!("RoomCornerRounding requires a builder with rooms.");
|
unreachable!("RoomCornerRounding tried to run without any rooms.");
|
||||||
}
|
}
|
||||||
|
|
||||||
for room in rooms.iter() {
|
for room in rooms.iter() {
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ impl RoomDrawer {
|
||||||
if let Some(rooms_builder) = &build_data.rooms {
|
if let Some(rooms_builder) = &build_data.rooms {
|
||||||
rooms = rooms_builder.clone();
|
rooms = rooms_builder.clone();
|
||||||
} else {
|
} else {
|
||||||
panic!("RoomDrawer require a builder with rooms");
|
unreachable!("RoomDrawer tried to run without any rooms.");
|
||||||
}
|
}
|
||||||
|
|
||||||
for room in rooms.iter() {
|
for room in rooms.iter() {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ impl RoomExploder {
|
||||||
if let Some(rooms_builder) = &build_data.rooms {
|
if let Some(rooms_builder) = &build_data.rooms {
|
||||||
rooms = rooms_builder.clone();
|
rooms = rooms_builder.clone();
|
||||||
} else {
|
} else {
|
||||||
panic!("RoomExploder requires a builder with rooms.");
|
unreachable!("RoomExploder tried to run without any rooms.");
|
||||||
}
|
}
|
||||||
for room in rooms.iter() {
|
for room in rooms.iter() {
|
||||||
let start = room.center();
|
let start = room.center();
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ impl ThemeRooms {
|
||||||
if let Some(rooms_builder) = &build_data.rooms {
|
if let Some(rooms_builder) = &build_data.rooms {
|
||||||
rooms = rooms_builder.clone();
|
rooms = rooms_builder.clone();
|
||||||
} else {
|
} else {
|
||||||
panic!("RoomCornerRounding requires a builder with rooms.");
|
unreachable!("RoomCornerRounding tried to run without any rooms.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let count = roll_until_fail(rng, self.percent);
|
let count = roll_until_fail(rng, self.percent);
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ impl BresenhamCorridors {
|
||||||
if let Some(rooms_builder) = &build_data.rooms {
|
if let Some(rooms_builder) = &build_data.rooms {
|
||||||
rooms = rooms_builder.clone();
|
rooms = rooms_builder.clone();
|
||||||
} else {
|
} else {
|
||||||
panic!("BresenhamCorridors require a builder with room structures");
|
unreachable!("BresenhamCorridors tried to run without any rooms.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut connected: HashSet<usize> = HashSet::new();
|
let mut connected: HashSet<usize> = HashSet::new();
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ impl BspCorridors {
|
||||||
if let Some(rooms_builder) = &build_data.rooms {
|
if let Some(rooms_builder) = &build_data.rooms {
|
||||||
rooms = rooms_builder.clone();
|
rooms = rooms_builder.clone();
|
||||||
} else {
|
} else {
|
||||||
panic!("BSP Corridors require a builder with room structures");
|
unreachable!("BSP Corridors tried to run without any rooms.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut corridors: Vec<Vec<usize>> = Vec::new();
|
let mut corridors: Vec<Vec<usize>> = Vec::new();
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ impl DoglegCorridors {
|
||||||
if let Some(rooms_builder) = &build_data.rooms {
|
if let Some(rooms_builder) = &build_data.rooms {
|
||||||
rooms = rooms_builder.clone();
|
rooms = rooms_builder.clone();
|
||||||
} else {
|
} else {
|
||||||
panic!("DoglegCorridors require a builder with rooms.");
|
unreachable!("DoglegCorridors tried to run without any rooms.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut corridors: Vec<Vec<usize>> = Vec::new();
|
let mut corridors: Vec<Vec<usize>> = Vec::new();
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ impl NearestCorridors {
|
||||||
if let Some(rooms_builder) = &build_data.rooms {
|
if let Some(rooms_builder) = &build_data.rooms {
|
||||||
rooms = rooms_builder.clone();
|
rooms = rooms_builder.clone();
|
||||||
} else {
|
} else {
|
||||||
panic!("NearestCorridors requires a builder with rooms");
|
unreachable!("NearestCorridors tried to run without any rooms.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut connected: HashSet<usize> = HashSet::new();
|
let mut connected: HashSet<usize> = HashSet::new();
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ impl CorridorSpawner {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("CorridorSpawner only works after corridors have been created");
|
unreachable!("CorridorSpawner tried to run without any corridors.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,7 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
||||||
} else {
|
} else {
|
||||||
attacks.push((
|
attacks.push((
|
||||||
MeleeWeapon {
|
MeleeWeapon {
|
||||||
|
damage_type: crate::DamageType::Physical,
|
||||||
attribute: WeaponAttribute::Strength,
|
attribute: WeaponAttribute::Strength,
|
||||||
damage_n_dice: 1,
|
damage_n_dice: 1,
|
||||||
damage_die_type: 4,
|
damage_die_type: 4,
|
||||||
|
|
@ -301,7 +302,7 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
||||||
}
|
}
|
||||||
add_effect(
|
add_effect(
|
||||||
Some(entity),
|
Some(entity),
|
||||||
EffectType::Damage { amount: damage },
|
EffectType::Damage { amount: damage, damage_type: weapon_info.damage_type },
|
||||||
Targets::Entity { target: wants_melee.target }
|
Targets::Entity { target: wants_melee.target }
|
||||||
);
|
);
|
||||||
if entity == *player_entity {
|
if entity == *player_entity {
|
||||||
|
|
@ -392,6 +393,7 @@ fn get_natural_attacks(
|
||||||
for a in nat.attacks.iter() {
|
for a in nat.attacks.iter() {
|
||||||
attacks.push((
|
attacks.push((
|
||||||
MeleeWeapon {
|
MeleeWeapon {
|
||||||
|
damage_type: a.damage_type,
|
||||||
attribute: WeaponAttribute::Strength,
|
attribute: WeaponAttribute::Strength,
|
||||||
hit_bonus: a.hit_bonus,
|
hit_bonus: a.hit_bonus,
|
||||||
damage_n_dice: a.damage_n_dice,
|
damage_n_dice: a.damage_n_dice,
|
||||||
|
|
@ -409,6 +411,7 @@ fn get_natural_attacks(
|
||||||
};
|
};
|
||||||
attacks.push((
|
attacks.push((
|
||||||
MeleeWeapon {
|
MeleeWeapon {
|
||||||
|
damage_type: nat.attacks[attack_index].damage_type,
|
||||||
attribute: WeaponAttribute::Strength,
|
attribute: WeaponAttribute::Strength,
|
||||||
hit_bonus: nat.attacks[attack_index].hit_bonus,
|
hit_bonus: nat.attacks[attack_index].hit_bonus,
|
||||||
damage_n_dice: nat.attacks[attack_index].damage_n_dice,
|
damage_n_dice: nat.attacks[attack_index].damage_n_dice,
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,10 @@ use super::{
|
||||||
Viewshed,
|
Viewshed,
|
||||||
WantsToMelee,
|
WantsToMelee,
|
||||||
WantsToPickupItem,
|
WantsToPickupItem,
|
||||||
|
WantsToAssignKey,
|
||||||
get_dest,
|
get_dest,
|
||||||
Destination,
|
Destination,
|
||||||
|
DamageType,
|
||||||
};
|
};
|
||||||
use bracket_lib::prelude::*;
|
use bracket_lib::prelude::*;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
@ -290,7 +292,7 @@ pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState {
|
||||||
if rng.roll_dice(1, 20) == 20 {
|
if rng.roll_dice(1, 20) == 20 {
|
||||||
add_effect(
|
add_effect(
|
||||||
None,
|
None,
|
||||||
EffectType::Damage { amount: 1 },
|
EffectType::Damage { amount: 1, damage_type: DamageType::Physical },
|
||||||
Targets::Entity { target: entity }
|
Targets::Entity { target: entity }
|
||||||
);
|
);
|
||||||
gamelog::Logger
|
gamelog::Logger
|
||||||
|
|
@ -350,7 +352,7 @@ pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState {
|
||||||
destroyed_pos = Some(
|
destroyed_pos = Some(
|
||||||
Point::new(pos.x + delta_x, pos.y + delta_y)
|
Point::new(pos.x + delta_x, pos.y + delta_y)
|
||||||
);
|
);
|
||||||
gamelog::record_event(EVENT::BROKE_DOOR(1));
|
gamelog::record_event(EVENT::BrokeDoor(1));
|
||||||
return false;
|
return false;
|
||||||
// 66% chance of just kicking it.
|
// 66% chance of just kicking it.
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -414,7 +416,7 @@ pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState {
|
||||||
ecs.delete_entity(destroyed_thing).expect("Unable to delete.");
|
ecs.delete_entity(destroyed_thing).expect("Unable to delete.");
|
||||||
}
|
}
|
||||||
|
|
||||||
gamelog::record_event(EVENT::KICKED_SOMETHING(1));
|
gamelog::record_event(EVENT::KickedSomething(1));
|
||||||
return RunState::Ticking;
|
return RunState::Ticking;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -632,7 +634,9 @@ fn get_item(ecs: &mut World) -> RunState {
|
||||||
return RunState::AwaitingInput;
|
return RunState::AwaitingInput;
|
||||||
}
|
}
|
||||||
Some(item) => {
|
Some(item) => {
|
||||||
|
let mut assignkey = ecs.write_storage::<WantsToAssignKey>();
|
||||||
let mut pickup = ecs.write_storage::<WantsToPickupItem>();
|
let mut pickup = ecs.write_storage::<WantsToPickupItem>();
|
||||||
|
assignkey.insert(item, WantsToAssignKey {}).expect("Unable to insert WantsToAssignKey");
|
||||||
pickup
|
pickup
|
||||||
.insert(*player_entity, WantsToPickupItem { collected_by: *player_entity, item })
|
.insert(*player_entity, WantsToPickupItem { collected_by: *player_entity, item })
|
||||||
.expect("Unable to insert want to pickup item.");
|
.expect("Unable to insert want to pickup item.");
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ pub struct Item {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: Name,
|
pub name: Name,
|
||||||
pub renderable: Option<Renderable>,
|
pub renderable: Option<Renderable>,
|
||||||
|
pub class: String,
|
||||||
pub weight: Option<f32>,
|
pub weight: Option<f32>,
|
||||||
pub value: Option<f32>,
|
pub value: Option<f32>,
|
||||||
pub equip: Option<Equippable>,
|
pub equip: Option<Equippable>,
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ pub struct Mob {
|
||||||
pub attacks: Option<Vec<NaturalAttack>>,
|
pub attacks: Option<Vec<NaturalAttack>>,
|
||||||
pub attributes: Option<MobAttributes>,
|
pub attributes: Option<MobAttributes>,
|
||||||
pub skills: Option<HashMap<String, i32>>,
|
pub skills: Option<HashMap<String, i32>>,
|
||||||
pub vision_range: i32,
|
pub vision_range: Option<i32>,
|
||||||
pub telepathy_range: Option<i32>,
|
pub telepathy_range: Option<i32>,
|
||||||
pub equipped: Option<Vec<String>>,
|
pub equipped: Option<Vec<String>>,
|
||||||
pub loot: Option<LootTableInfo>,
|
pub loot: Option<LootTableInfo>,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::gui::Ancestry;
|
||||||
use crate::random_table::RandomTable;
|
use crate::random_table::RandomTable;
|
||||||
use crate::config::CONFIG;
|
use crate::config::CONFIG;
|
||||||
use crate::data::visuals::BLOODSTAIN_COLOUR;
|
use crate::data::visuals::BLOODSTAIN_COLOUR;
|
||||||
use regex::Regex;
|
use crate::data::entity::DEFAULT_VIEWSHED_STANDARD;
|
||||||
use bracket_lib::prelude::*;
|
use bracket_lib::prelude::*;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
use specs::saveload::{ MarkedBuilder, SimpleMarker };
|
use specs::saveload::{ MarkedBuilder, SimpleMarker };
|
||||||
|
|
@ -19,13 +19,13 @@ macro_rules! apply_effects {
|
||||||
let effect_name = effect.0.as_str();
|
let effect_name = effect.0.as_str();
|
||||||
match effect_name {
|
match effect_name {
|
||||||
"heal" => {
|
"heal" => {
|
||||||
let (n_dice, sides, modifier) = parse_dice_string(effect.1.as_str());
|
let dice = parse_dice_string(effect.1.as_str()).expect("Failed to parse dice string");
|
||||||
$eb = $eb.with(ProvidesHealing { n_dice, sides, modifier })
|
$eb = $eb.with(ProvidesHealing { n_dice: dice.n_dice, sides: dice.die_type, modifier: dice.bonus })
|
||||||
}
|
}
|
||||||
"ranged" => $eb = $eb.with(Ranged { range: effect.1.parse::<i32>().unwrap() }),
|
"ranged" => $eb = $eb.with(Ranged { range: effect.1.parse::<i32>().unwrap() }),
|
||||||
"damage" => {
|
"damage" => {
|
||||||
let (n_dice, sides, modifier) = parse_dice_string(effect.1.as_str());
|
let (damage_type, dice) = parse_damage_string(effect.1.as_str());
|
||||||
$eb = $eb.with(InflictsDamage { n_dice, sides, modifier })
|
$eb = $eb.with(InflictsDamage { damage_type, n_dice: dice.n_dice, sides: dice.die_type, modifier: dice.bonus })
|
||||||
}
|
}
|
||||||
"aoe" => $eb = $eb.with(AOE { radius: effect.1.parse::<i32>().unwrap() }),
|
"aoe" => $eb = $eb.with(AOE { radius: effect.1.parse::<i32>().unwrap() }),
|
||||||
"confusion" => $eb = $eb.with(Confusion { turns: effect.1.parse::<i32>().unwrap() }),
|
"confusion" => $eb = $eb.with(Confusion { turns: effect.1.parse::<i32>().unwrap() }),
|
||||||
|
|
@ -44,6 +44,7 @@ macro_rules! apply_effects {
|
||||||
/// flags are components that have no parameters to modify.
|
/// flags are components that have no parameters to modify.
|
||||||
macro_rules! apply_flags {
|
macro_rules! apply_flags {
|
||||||
($flags:expr, $eb:expr) => {
|
($flags:expr, $eb:expr) => {
|
||||||
|
let mut damage_modifiers: HashMap<DamageType, DamageModifier> = HashMap::new();
|
||||||
for flag in $flags.iter() {
|
for flag in $flags.iter() {
|
||||||
match flag.as_str() {
|
match flag.as_str() {
|
||||||
// --- PROP FLAGS BEGIN HERE ---
|
// --- PROP FLAGS BEGIN HERE ---
|
||||||
|
|
@ -65,6 +66,7 @@ macro_rules! apply_flags {
|
||||||
"IDENTIFY" => $eb = $eb.with(ProvidesIdentify {}),
|
"IDENTIFY" => $eb = $eb.with(ProvidesIdentify {}),
|
||||||
"DIGGER" => $eb = $eb.with(Digger {}),
|
"DIGGER" => $eb = $eb.with(Digger {}),
|
||||||
"MAGICMAP" => $eb = $eb.with(MagicMapper {}),
|
"MAGICMAP" => $eb = $eb.with(MagicMapper {}),
|
||||||
|
"STACKABLE" => $eb = $eb.with(Stackable {}),
|
||||||
// CAN BE DESTROYED BY DAMAGE
|
// CAN BE DESTROYED BY DAMAGE
|
||||||
"DESTRUCTIBLE" => $eb = $eb.with(Destructible {}),
|
"DESTRUCTIBLE" => $eb = $eb.with(Destructible {}),
|
||||||
// --- EQUIP SLOTS ---
|
// --- EQUIP SLOTS ---
|
||||||
|
|
@ -88,6 +90,22 @@ macro_rules! apply_flags {
|
||||||
"NEUTRAL" => $eb = $eb.with(Faction { name: "neutral".to_string() }),
|
"NEUTRAL" => $eb = $eb.with(Faction { name: "neutral".to_string() }),
|
||||||
"HERBIVORE" => $eb = $eb.with(Faction { name: "herbivore".to_string() }),
|
"HERBIVORE" => $eb = $eb.with(Faction { name: "herbivore".to_string() }),
|
||||||
"CARNIVORE" => $eb = $eb.with(Faction { name: "carnivore".to_string() }),
|
"CARNIVORE" => $eb = $eb.with(Faction { name: "carnivore".to_string() }),
|
||||||
|
// --- DAMAGE MODIFIERS ---
|
||||||
|
"PHYS_IMMUNITY" => { damage_modifiers.insert(DamageType::Physical, DamageModifier::Immune); }
|
||||||
|
"PHYS_WEAK" => { damage_modifiers.insert(DamageType::Physical, DamageModifier::Weakness); }
|
||||||
|
"PHYS_RES" => { damage_modifiers.insert(DamageType::Physical, DamageModifier::Resistance); }
|
||||||
|
"MAGIC_IMMUNITY" => { damage_modifiers.insert(DamageType::Magic, DamageModifier::Immune); }
|
||||||
|
"MAGIC_WEAK" => { damage_modifiers.insert(DamageType::Magic, DamageModifier::Weakness); }
|
||||||
|
"MAGIC_RES" => { damage_modifiers.insert(DamageType::Magic, DamageModifier::Resistance); }
|
||||||
|
"FIRE_IMMUNITY" => { damage_modifiers.insert(DamageType::Fire, DamageModifier::Immune); }
|
||||||
|
"FIRE_WEAK" => { damage_modifiers.insert(DamageType::Fire, DamageModifier::Weakness); }
|
||||||
|
"FIRE_RES" => { damage_modifiers.insert(DamageType::Fire, DamageModifier::Resistance); }
|
||||||
|
"COLD_IMMUNITY" => { damage_modifiers.insert(DamageType::Cold, DamageModifier::Immune); }
|
||||||
|
"COLD_WEAK" => { damage_modifiers.insert(DamageType::Cold, DamageModifier::Weakness); }
|
||||||
|
"COLD_RES" => { damage_modifiers.insert(DamageType::Cold, DamageModifier::Resistance); }
|
||||||
|
"POISON_IMMUNITY" => { damage_modifiers.insert(DamageType::Poison, DamageModifier::Immune); }
|
||||||
|
"POISON_WEAK" => { damage_modifiers.insert(DamageType::Poison, DamageModifier::Weakness); }
|
||||||
|
"POISON_RES" => { damage_modifiers.insert(DamageType::Poison, DamageModifier::Resistance); }
|
||||||
// --- MOVEMENT MODES --- ( defaults to WANDER )
|
// --- MOVEMENT MODES --- ( defaults to WANDER )
|
||||||
"STATIC" => $eb = $eb.with(MoveMode { mode: Movement::Static }),
|
"STATIC" => $eb = $eb.with(MoveMode { mode: Movement::Static }),
|
||||||
"RANDOM_PATH" => $eb = $eb.with(MoveMode { mode: Movement::RandomWaypoint { path: None } }),
|
"RANDOM_PATH" => $eb = $eb.with(MoveMode { mode: Movement::RandomWaypoint { path: None } }),
|
||||||
|
|
@ -101,6 +119,9 @@ macro_rules! apply_flags {
|
||||||
_ => console::log(format!("Unrecognised flag: {}", flag.as_str())),
|
_ => console::log(format!("Unrecognised flag: {}", flag.as_str())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if damage_modifiers.len() > 0 {
|
||||||
|
$eb = $eb.with(HasDamageModifiers { modifiers: damage_modifiers });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,6 +282,7 @@ pub fn spawn_named_item(
|
||||||
if known_beatitude && !identified_items.contains(&item_template.name.name) {
|
if known_beatitude && !identified_items.contains(&item_template.name.name) {
|
||||||
dm.identified_items.insert(item_template.name.name.clone());
|
dm.identified_items.insert(item_template.name.name.clone());
|
||||||
}
|
}
|
||||||
|
let needs_key = is_player_owned(&player_entity, &pos);
|
||||||
std::mem::drop(player_entity);
|
std::mem::drop(player_entity);
|
||||||
std::mem::drop(dm);
|
std::mem::drop(dm);
|
||||||
// -- DROP EVERYTHING THAT INVOLVES THE ECS BEFORE THIS POINT ---
|
// -- DROP EVERYTHING THAT INVOLVES THE ECS BEFORE THIS POINT ---
|
||||||
|
|
@ -273,9 +295,23 @@ pub fn spawn_named_item(
|
||||||
eb = eb.with(Item {
|
eb = eb.with(Item {
|
||||||
weight: item_template.weight.unwrap_or(0.0),
|
weight: item_template.weight.unwrap_or(0.0),
|
||||||
value: item_template.value.unwrap_or(0.0),
|
value: item_template.value.unwrap_or(0.0),
|
||||||
|
category: match item_template.class.as_str() {
|
||||||
|
"amulet" => ItemType::Amulet,
|
||||||
|
"weapon" => ItemType::Weapon,
|
||||||
|
"armour" => ItemType::Armour,
|
||||||
|
"comestible" => ItemType::Comestible,
|
||||||
|
"scroll" => ItemType::Scroll,
|
||||||
|
"spellbook" => ItemType::Spellbook,
|
||||||
|
"potion" => ItemType::Potion,
|
||||||
|
"ring" => ItemType::Ring,
|
||||||
|
"wand" => ItemType::Wand,
|
||||||
|
_ => unreachable!("Unknown item type."),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
eb = spawn_position(pos, eb, key, raws);
|
eb = spawn_position(pos, eb, key, raws);
|
||||||
|
if needs_key {
|
||||||
|
eb = eb.with(WantsToAssignKey {});
|
||||||
|
}
|
||||||
if let Some(renderable) = &item_template.renderable {
|
if let Some(renderable) = &item_template.renderable {
|
||||||
eb = eb.with(get_renderable_component(renderable));
|
eb = eb.with(get_renderable_component(renderable));
|
||||||
}
|
}
|
||||||
|
|
@ -339,17 +375,18 @@ pub fn spawn_named_item(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(weapon) = &item_template.equip {
|
if let Some(weapon) = &item_template.equip {
|
||||||
let (n_dice, die_type, bonus) = parse_dice_string(weapon.damage.as_str());
|
let (damage_type, dice) = parse_damage_string(weapon.damage.as_str());
|
||||||
let weapon_attribute = match weapon.flag.as_str() {
|
let weapon_attribute = match weapon.flag.as_str() {
|
||||||
"DEXTERITY" => WeaponAttribute::Dexterity,
|
"DEXTERITY" => WeaponAttribute::Dexterity,
|
||||||
"FINESSE" => WeaponAttribute::Finesse,
|
"FINESSE" => WeaponAttribute::Finesse,
|
||||||
_ => WeaponAttribute::Strength,
|
_ => WeaponAttribute::Strength,
|
||||||
};
|
};
|
||||||
let wpn = MeleeWeapon {
|
let wpn = MeleeWeapon {
|
||||||
|
damage_type,
|
||||||
attribute: weapon_attribute,
|
attribute: weapon_attribute,
|
||||||
damage_n_dice: n_dice,
|
damage_n_dice: dice.n_dice,
|
||||||
damage_die_type: die_type,
|
damage_die_type: dice.die_type,
|
||||||
damage_bonus: bonus,
|
damage_bonus: dice.bonus,
|
||||||
hit_bonus: weapon.to_hit.unwrap_or(0),
|
hit_bonus: weapon.to_hit.unwrap_or(0),
|
||||||
};
|
};
|
||||||
eb = eb.with(wpn);
|
eb = eb.with(wpn);
|
||||||
|
|
@ -371,6 +408,7 @@ pub fn spawn_named_mob(
|
||||||
if raws.mob_index.contains_key(key) {
|
if raws.mob_index.contains_key(key) {
|
||||||
let mob_template = &raws.raws.mobs[raws.mob_index[key]];
|
let mob_template = &raws.raws.mobs[raws.mob_index[key]];
|
||||||
let mut player_level = 1;
|
let mut player_level = 1;
|
||||||
|
let needs_key;
|
||||||
{
|
{
|
||||||
let pools = ecs.read_storage::<Pools>();
|
let pools = ecs.read_storage::<Pools>();
|
||||||
let player_entity = ecs.fetch::<Entity>();
|
let player_entity = ecs.fetch::<Entity>();
|
||||||
|
|
@ -378,16 +416,23 @@ pub fn spawn_named_mob(
|
||||||
if let Some(pool) = player_pool {
|
if let Some(pool) = player_pool {
|
||||||
player_level = pool.level;
|
player_level = pool.level;
|
||||||
}
|
}
|
||||||
|
needs_key = is_player_owned(&player_entity, &pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut eb;
|
let mut eb;
|
||||||
// New entity with a position, name, combatstats, and viewshed
|
// New entity with a position, name, combatstats, and viewshed
|
||||||
eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>();
|
eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>();
|
||||||
eb = spawn_position(pos, eb, key, raws);
|
eb = spawn_position(pos, eb, key, raws);
|
||||||
|
if needs_key {
|
||||||
|
eb = eb.with(WantsToAssignKey {});
|
||||||
|
}
|
||||||
eb = eb.with(Name { name: mob_template.name.clone(), plural: mob_template.name.clone() });
|
eb = eb.with(Name { name: mob_template.name.clone(), plural: mob_template.name.clone() });
|
||||||
eb = eb.with(Viewshed {
|
eb = eb.with(Viewshed {
|
||||||
visible_tiles: Vec::new(),
|
visible_tiles: Vec::new(),
|
||||||
range: mob_template.vision_range as i32,
|
range: if let Some(range) = mob_template.vision_range {
|
||||||
|
range
|
||||||
|
} else {
|
||||||
|
DEFAULT_VIEWSHED_STANDARD
|
||||||
|
},
|
||||||
dirty: true,
|
dirty: true,
|
||||||
});
|
});
|
||||||
if let Some(telepath) = &mob_template.telepathy_range {
|
if let Some(telepath) = &mob_template.telepathy_range {
|
||||||
|
|
@ -521,13 +566,14 @@ pub fn spawn_named_mob(
|
||||||
if let Some(natural_attacks) = &mob_template.attacks {
|
if let Some(natural_attacks) = &mob_template.attacks {
|
||||||
let mut natural = NaturalAttacks { attacks: Vec::new() };
|
let mut natural = NaturalAttacks { attacks: Vec::new() };
|
||||||
for na in natural_attacks.iter() {
|
for na in natural_attacks.iter() {
|
||||||
let (n, d, b) = parse_dice_string(&na.damage);
|
let (damage_type, dice) = parse_damage_string(&na.damage);
|
||||||
let attack = NaturalAttack {
|
let attack = NaturalAttack {
|
||||||
name: na.name.clone(),
|
name: na.name.clone(),
|
||||||
|
damage_type,
|
||||||
hit_bonus: na.hit_bonus,
|
hit_bonus: na.hit_bonus,
|
||||||
damage_n_dice: n,
|
damage_n_dice: dice.n_dice,
|
||||||
damage_die_type: d,
|
damage_die_type: dice.die_type,
|
||||||
damage_bonus: b,
|
damage_bonus: dice.bonus,
|
||||||
};
|
};
|
||||||
natural.attacks.push(attack);
|
natural.attacks.push(attack);
|
||||||
}
|
}
|
||||||
|
|
@ -606,10 +652,18 @@ pub fn spawn_named_prop(
|
||||||
pos: SpawnType
|
pos: SpawnType
|
||||||
) -> Option<Entity> {
|
) -> Option<Entity> {
|
||||||
if raws.prop_index.contains_key(key) {
|
if raws.prop_index.contains_key(key) {
|
||||||
|
let needs_key;
|
||||||
|
{
|
||||||
|
let player_entity = ecs.fetch::<Entity>();
|
||||||
|
needs_key = is_player_owned(&player_entity, &pos);
|
||||||
|
}
|
||||||
// ENTITY BUILDER PREP
|
// ENTITY BUILDER PREP
|
||||||
let prop_template = &raws.raws.props[raws.prop_index[key]];
|
let prop_template = &raws.raws.props[raws.prop_index[key]];
|
||||||
let mut eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>();
|
let mut eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>();
|
||||||
eb = spawn_position(pos, eb, key, raws);
|
eb = spawn_position(pos, eb, key, raws);
|
||||||
|
if needs_key {
|
||||||
|
eb = eb.with(WantsToAssignKey {});
|
||||||
|
}
|
||||||
// APPLY MANDATORY COMPONENTS FOR A PROP:
|
// APPLY MANDATORY COMPONENTS FOR A PROP:
|
||||||
// - Name
|
// - Name
|
||||||
// - Prop {}
|
// - Prop {}
|
||||||
|
|
@ -660,6 +714,23 @@ fn spawn_position<'a>(
|
||||||
eb
|
eb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_player_owned(player: &Entity, pos: &SpawnType) -> bool {
|
||||||
|
match pos {
|
||||||
|
SpawnType::Carried { by } => {
|
||||||
|
if by == player {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SpawnType::Equipped { by } => {
|
||||||
|
if by == player {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn get_renderable_component(
|
fn get_renderable_component(
|
||||||
renderable: &super::item_structs::Renderable
|
renderable: &super::item_structs::Renderable
|
||||||
) -> crate::components::Renderable {
|
) -> crate::components::Renderable {
|
||||||
|
|
@ -701,19 +772,16 @@ pub fn table_by_name(raws: &RawMaster, key: &str, optional_difficulty: Option<i3
|
||||||
return rt;
|
return rt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console::log(
|
unreachable!(
|
||||||
format!(
|
"Something went wrong when trying to spawn {} @ map difficulty {} [upper bound: {}, lower bound: {}].",
|
||||||
"DEBUGINFO: Something went wrong when trying to spawn {} @ map difficulty {} [upper bound: {}, lower bound: {}]. Returned debug entry.",
|
key,
|
||||||
key,
|
difficulty,
|
||||||
difficulty,
|
upper_bound,
|
||||||
upper_bound,
|
lower_bound
|
||||||
lower_bound
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
return RandomTable::new().add("debug", 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_dice_string(dice: &str) -> (i32, i32, i32) {
|
/*pub fn parse_dice_string(dice: &str) -> (i32, i32, i32) {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref DICE_RE: Regex = Regex::new(r"(\d+)d(\d+)([\+\-]\d+)?").unwrap();
|
static ref DICE_RE: Regex = Regex::new(r"(\d+)d(\d+)([\+\-]\d+)?").unwrap();
|
||||||
}
|
}
|
||||||
|
|
@ -732,11 +800,11 @@ pub fn parse_dice_string(dice: &str) -> (i32, i32, i32) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(n_dice, die_type, die_bonus)
|
(n_dice, die_type, die_bonus)
|
||||||
}
|
}*/
|
||||||
|
|
||||||
fn find_slot_for_equippable_item(tag: &str, raws: &RawMaster) -> EquipmentSlot {
|
fn find_slot_for_equippable_item(tag: &str, raws: &RawMaster) -> EquipmentSlot {
|
||||||
if !raws.item_index.contains_key(tag) {
|
if !raws.item_index.contains_key(tag) {
|
||||||
panic!("Trying to equip an unknown item: {}", tag);
|
unreachable!("Tried to equip an unknown item: {}", tag);
|
||||||
}
|
}
|
||||||
let item_index = raws.item_index[tag];
|
let item_index = raws.item_index[tag];
|
||||||
let item = &raws.raws.items[item_index];
|
let item = &raws.raws.items[item_index];
|
||||||
|
|
@ -771,7 +839,7 @@ fn find_slot_for_equippable_item(tag: &str, raws: &RawMaster) -> EquipmentSlot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic!("Trying to equip {}, but it has no slot tag.", tag);
|
unreachable!("Tried to equip {}, but it has no slot tag.", tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn roll_on_loot_table(
|
pub fn roll_on_loot_table(
|
||||||
|
|
@ -1034,3 +1102,21 @@ fn parse_particle_burst(n: &str) -> SpawnParticleBurst {
|
||||||
trail_lifetime_ms: tokens[7].parse::<f32>().unwrap(),
|
trail_lifetime_ms: tokens[7].parse::<f32>().unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_damage_string(n: &str) -> (DamageType, DiceType) {
|
||||||
|
let tokens: Vec<_> = n.split(';').collect();
|
||||||
|
let damage_type = if tokens.len() > 1 {
|
||||||
|
match tokens[1] {
|
||||||
|
"physical" => DamageType::Physical,
|
||||||
|
"magic" => DamageType::Magic,
|
||||||
|
"fire" => DamageType::Fire,
|
||||||
|
"cold" => DamageType::Cold,
|
||||||
|
"poison" => DamageType::Poison,
|
||||||
|
_ => unreachable!("Unrecognised damage type in raws: {}", tokens[1]),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DamageType::Physical
|
||||||
|
};
|
||||||
|
let dice = parse_dice_string(tokens[0]).expect("Failed to parse dice string");
|
||||||
|
return (damage_type, dice);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use specs::saveload::{
|
||||||
SimpleMarker,
|
SimpleMarker,
|
||||||
SimpleMarkerAllocator,
|
SimpleMarkerAllocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
@ -88,13 +89,17 @@ pub fn save_game(ecs: &mut World) {
|
||||||
GrantsXP,
|
GrantsXP,
|
||||||
HasAncestry,
|
HasAncestry,
|
||||||
HasClass,
|
HasClass,
|
||||||
|
HasDamageModifiers,
|
||||||
Hidden,
|
Hidden,
|
||||||
HungerClock,
|
HungerClock,
|
||||||
IdentifiedBeatitude,
|
IdentifiedBeatitude,
|
||||||
IdentifiedItem,
|
IdentifiedItem,
|
||||||
InBackpack,
|
InBackpack,
|
||||||
InflictsDamage,
|
InflictsDamage,
|
||||||
|
IntrinsicChanged,
|
||||||
|
Intrinsics,
|
||||||
Item,
|
Item,
|
||||||
|
Key,
|
||||||
KnownSpells,
|
KnownSpells,
|
||||||
LootTable,
|
LootTable,
|
||||||
MagicItem,
|
MagicItem,
|
||||||
|
|
@ -124,17 +129,21 @@ pub fn save_game(ecs: &mut World) {
|
||||||
SpawnParticleBurst,
|
SpawnParticleBurst,
|
||||||
SpawnParticleLine,
|
SpawnParticleLine,
|
||||||
SpawnParticleSimple,
|
SpawnParticleSimple,
|
||||||
|
Stackable,
|
||||||
TakingTurn,
|
TakingTurn,
|
||||||
Telepath,
|
Telepath,
|
||||||
ToHitBonus,
|
ToHitBonus,
|
||||||
Viewshed,
|
Viewshed,
|
||||||
Charges,
|
Charges,
|
||||||
WantsToApproach,
|
WantsToApproach,
|
||||||
|
WantsToAssignKey,
|
||||||
|
WantsToDelete,
|
||||||
WantsToDropItem,
|
WantsToDropItem,
|
||||||
WantsToFlee,
|
WantsToFlee,
|
||||||
WantsToMelee,
|
WantsToMelee,
|
||||||
WantsToPickupItem,
|
WantsToPickupItem,
|
||||||
WantsToRemoveItem,
|
WantsToRemoveItem,
|
||||||
|
WantsToRemoveKey,
|
||||||
WantsToUseItem,
|
WantsToUseItem,
|
||||||
SerializationHelper,
|
SerializationHelper,
|
||||||
DMSerializationHelper
|
DMSerializationHelper
|
||||||
|
|
@ -218,13 +227,17 @@ pub fn load_game(ecs: &mut World) {
|
||||||
GrantsXP,
|
GrantsXP,
|
||||||
HasAncestry,
|
HasAncestry,
|
||||||
HasClass,
|
HasClass,
|
||||||
|
HasDamageModifiers,
|
||||||
Hidden,
|
Hidden,
|
||||||
HungerClock,
|
HungerClock,
|
||||||
IdentifiedBeatitude,
|
IdentifiedBeatitude,
|
||||||
IdentifiedItem,
|
IdentifiedItem,
|
||||||
InBackpack,
|
InBackpack,
|
||||||
InflictsDamage,
|
InflictsDamage,
|
||||||
|
IntrinsicChanged,
|
||||||
|
Intrinsics,
|
||||||
Item,
|
Item,
|
||||||
|
Key,
|
||||||
KnownSpells,
|
KnownSpells,
|
||||||
LootTable,
|
LootTable,
|
||||||
MagicItem,
|
MagicItem,
|
||||||
|
|
@ -254,17 +267,21 @@ pub fn load_game(ecs: &mut World) {
|
||||||
SpawnParticleBurst,
|
SpawnParticleBurst,
|
||||||
SpawnParticleLine,
|
SpawnParticleLine,
|
||||||
SpawnParticleSimple,
|
SpawnParticleSimple,
|
||||||
|
Stackable,
|
||||||
TakingTurn,
|
TakingTurn,
|
||||||
Telepath,
|
Telepath,
|
||||||
ToHitBonus,
|
ToHitBonus,
|
||||||
Viewshed,
|
Viewshed,
|
||||||
Charges,
|
Charges,
|
||||||
WantsToApproach,
|
WantsToApproach,
|
||||||
|
WantsToAssignKey,
|
||||||
|
WantsToDelete,
|
||||||
WantsToDropItem,
|
WantsToDropItem,
|
||||||
WantsToFlee,
|
WantsToFlee,
|
||||||
WantsToMelee,
|
WantsToMelee,
|
||||||
WantsToPickupItem,
|
WantsToPickupItem,
|
||||||
WantsToRemoveItem,
|
WantsToRemoveItem,
|
||||||
|
WantsToRemoveKey,
|
||||||
WantsToUseItem,
|
WantsToUseItem,
|
||||||
SerializationHelper,
|
SerializationHelper,
|
||||||
DMSerializationHelper
|
DMSerializationHelper
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ use super::{
|
||||||
Viewshed,
|
Viewshed,
|
||||||
BlocksTile,
|
BlocksTile,
|
||||||
Bleeds,
|
Bleeds,
|
||||||
|
HasDamageModifiers,
|
||||||
|
Intrinsics,
|
||||||
};
|
};
|
||||||
use crate::data::entity;
|
use crate::data::entity;
|
||||||
use crate::data::visuals::BLOODSTAIN_COLOUR;
|
use crate::data::visuals::BLOODSTAIN_COLOUR;
|
||||||
|
|
@ -32,7 +34,7 @@ use crate::gamesystem::*;
|
||||||
use bracket_lib::prelude::*;
|
use bracket_lib::prelude::*;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
use specs::saveload::{ MarkedBuilder, SimpleMarker };
|
use specs::saveload::{ MarkedBuilder, SimpleMarker };
|
||||||
use std::collections::HashMap;
|
use std::collections::{ HashMap, HashSet };
|
||||||
|
|
||||||
/// Spawns the player and returns his/her entity object.
|
/// Spawns the player and returns his/her entity object.
|
||||||
pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
|
pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
|
||||||
|
|
@ -86,7 +88,9 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
|
||||||
weight: 0.0,
|
weight: 0.0,
|
||||||
god: false,
|
god: false,
|
||||||
})
|
})
|
||||||
.with(EquipmentChanged {})
|
.with(HasDamageModifiers { modifiers: HashMap::new() })
|
||||||
|
.with(Intrinsics { list: HashSet::new() })
|
||||||
|
.with(EquipmentChanged {}) // To force re-calc of equipment bonuses.
|
||||||
.with(skills)
|
.with(skills)
|
||||||
.with(Energy { current: 0, speed: entity::NORMAL_SPEED })
|
.with(Energy { current: 0, speed: entity::NORMAL_SPEED })
|
||||||
.marked::<SimpleMarker<SerializeMe>>()
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
|
|
|
||||||
|
|
@ -64,12 +64,13 @@ impl State {
|
||||||
|
|
||||||
fn resolve_entity_decisions(&mut self) {
|
fn resolve_entity_decisions(&mut self) {
|
||||||
let mut trigger_system = trigger_system::TriggerSystem {};
|
let mut trigger_system = trigger_system::TriggerSystem {};
|
||||||
let mut inventory_system = inventory::ItemCollectionSystem {};
|
|
||||||
let mut item_equip_system = inventory::ItemEquipSystem {};
|
let mut item_equip_system = inventory::ItemEquipSystem {};
|
||||||
let mut item_use_system = inventory::ItemUseSystem {};
|
let mut item_use_system = inventory::ItemUseSystem {};
|
||||||
let mut item_drop_system = inventory::ItemDropSystem {};
|
let mut item_drop_system = inventory::ItemDropSystem {};
|
||||||
let mut item_remove_system = inventory::ItemRemoveSystem {};
|
let mut item_remove_system = inventory::ItemRemoveSystem {};
|
||||||
|
let mut inventory_system = inventory::ItemCollectionSystem {};
|
||||||
let mut item_id_system = inventory::ItemIdentificationSystem {};
|
let mut item_id_system = inventory::ItemIdentificationSystem {};
|
||||||
|
let mut key_system = inventory::KeyHandling {};
|
||||||
let mut melee_system = MeleeCombatSystem {};
|
let mut melee_system = MeleeCombatSystem {};
|
||||||
trigger_system.run_now(&self.ecs);
|
trigger_system.run_now(&self.ecs);
|
||||||
inventory_system.run_now(&self.ecs);
|
inventory_system.run_now(&self.ecs);
|
||||||
|
|
@ -78,6 +79,7 @@ impl State {
|
||||||
item_drop_system.run_now(&self.ecs);
|
item_drop_system.run_now(&self.ecs);
|
||||||
item_remove_system.run_now(&self.ecs);
|
item_remove_system.run_now(&self.ecs);
|
||||||
item_id_system.run_now(&self.ecs);
|
item_id_system.run_now(&self.ecs);
|
||||||
|
key_system.run_now(&self.ecs);
|
||||||
melee_system.run_now(&self.ecs);
|
melee_system.run_now(&self.ecs);
|
||||||
|
|
||||||
effects::run_effects_queue(&mut self.ecs);
|
effects::run_effects_queue(&mut self.ecs);
|
||||||
|
|
@ -128,7 +130,7 @@ impl State {
|
||||||
.colour(WHITE)
|
.colour(WHITE)
|
||||||
.period()
|
.period()
|
||||||
.log();
|
.log();
|
||||||
gamelog::record_event(EVENT::CHANGED_FLOOR(mapname));
|
gamelog::record_event(EVENT::ChangedFloor(mapname));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn game_over_cleanup(&mut self) {
|
fn game_over_cleanup(&mut self) {
|
||||||
|
|
@ -152,7 +154,7 @@ impl State {
|
||||||
self.generate_world_map(1, TileType::Floor);
|
self.generate_world_map(1, TileType::Floor);
|
||||||
|
|
||||||
gamelog::setup_log();
|
gamelog::setup_log();
|
||||||
gamelog::record_event(EVENT::LEVEL(1));
|
gamelog::record_event(EVENT::Level(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,7 +344,11 @@ impl GameState for State {
|
||||||
gui::ItemMenuResult::NoResponse => {}
|
gui::ItemMenuResult::NoResponse => {}
|
||||||
gui::ItemMenuResult::Selected => {
|
gui::ItemMenuResult::Selected => {
|
||||||
let item_entity = result.1.unwrap();
|
let item_entity = result.1.unwrap();
|
||||||
|
let mut removekey = self.ecs.write_storage::<WantsToRemoveKey>();
|
||||||
let mut intent = self.ecs.write_storage::<WantsToDropItem>();
|
let mut intent = self.ecs.write_storage::<WantsToDropItem>();
|
||||||
|
removekey
|
||||||
|
.insert(item_entity, WantsToRemoveKey {})
|
||||||
|
.expect("Unable to insert WantsToRemoveKey");
|
||||||
intent
|
intent
|
||||||
.insert(*self.ecs.fetch::<Entity>(), WantsToDropItem {
|
.insert(*self.ecs.fetch::<Entity>(), WantsToDropItem {
|
||||||
item: item_entity,
|
item: item_entity,
|
||||||
|
|
@ -513,7 +519,7 @@ impl GameState for State {
|
||||||
let result = gui::show_help(ctx);
|
let result = gui::show_help(ctx);
|
||||||
match result {
|
match result {
|
||||||
gui::YesNoResult::Yes => {
|
gui::YesNoResult::Yes => {
|
||||||
gamelog::record_event(EVENT::LOOKED_FOR_HELP(1));
|
gamelog::record_event(EVENT::LookedForHelp(1));
|
||||||
new_runstate = RunState::AwaitingInput;
|
new_runstate = RunState::AwaitingInput;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
|
||||||
47
tests/components_test.rs
Normal file
47
tests/components_test.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
// tests/components_test.rs
|
||||||
|
use rust_rl::components::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn damagetype_equality() {
|
||||||
|
let dt1 = DamageType::Physical;
|
||||||
|
let dt2 = DamageType::Physical;
|
||||||
|
assert_eq!(dt1, dt2);
|
||||||
|
let dt3 = DamageType::Magic;
|
||||||
|
assert_ne!(dt1, dt3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn damagetype_ismagic() {
|
||||||
|
let dt1 = DamageType::Physical;
|
||||||
|
let dt2 = DamageType::Magic;
|
||||||
|
assert!(!dt1.is_magic());
|
||||||
|
assert!(dt2.is_magic());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_damage_modifiers() {
|
||||||
|
let dm = HasDamageModifiers {
|
||||||
|
modifiers: {
|
||||||
|
let mut m = HashMap::new();
|
||||||
|
m.insert(DamageType::Physical, DamageModifier::Weakness);
|
||||||
|
m.insert(DamageType::Magic, DamageModifier::Resistance);
|
||||||
|
m
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert_eq!(dm.modifier(&DamageType::Physical), &DamageModifier::Weakness);
|
||||||
|
assert_eq!(dm.modifier(&DamageType::Magic), &DamageModifier::Resistance);
|
||||||
|
assert_ne!(dm.modifier(&DamageType::Forced), &DamageModifier::Immune);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_damage_modifier_multiplier() {
|
||||||
|
let none_mod = &DamageModifier::None.multiplier();
|
||||||
|
let weak_mod = &DamageModifier::Weakness.multiplier();
|
||||||
|
let res_mod = &DamageModifier::Resistance.multiplier();
|
||||||
|
let immune_mod = &DamageModifier::Immune.multiplier();
|
||||||
|
assert_eq!(none_mod, &1.0);
|
||||||
|
assert_eq!(weak_mod, &2.0);
|
||||||
|
assert_eq!(res_mod, &0.5);
|
||||||
|
assert_eq!(immune_mod, &0.0);
|
||||||
|
}
|
||||||
43
tests/gamelog_test.rs
Normal file
43
tests/gamelog_test.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
// tests/gamelog_test.rs
|
||||||
|
use rust_rl::gamelog::*;
|
||||||
|
use rust_rl::data::events::*;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
// To ensure this test module uses a single thread.
|
||||||
|
lazy_static! {
|
||||||
|
static ref SINGLE_THREAD: Mutex<()> = Mutex::new(());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recording_event() {
|
||||||
|
let _lock = SINGLE_THREAD.lock();
|
||||||
|
clear_events();
|
||||||
|
record_event(EVENT::Turn(1));
|
||||||
|
record_event(EVENT::Turn(0));
|
||||||
|
record_event(EVENT::Turn(-1));
|
||||||
|
record_event(EVENT::Killed("mob".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn getting_event_count() {
|
||||||
|
let _lock = SINGLE_THREAD.lock();
|
||||||
|
clear_events();
|
||||||
|
record_event(EVENT::Turn(1));
|
||||||
|
assert_eq!(get_event_count(EVENT::COUNT_TURN), 1);
|
||||||
|
record_event(EVENT::Turn(3));
|
||||||
|
assert_eq!(get_event_count(EVENT::COUNT_TURN), 4);
|
||||||
|
clear_events();
|
||||||
|
assert_eq!(get_event_count(EVENT::COUNT_TURN), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cloning_events() {
|
||||||
|
let _lock = SINGLE_THREAD.lock();
|
||||||
|
clear_events();
|
||||||
|
record_event(EVENT::Level(1));
|
||||||
|
record_event(EVENT::Turn(5));
|
||||||
|
record_event(EVENT::Identified("item".to_string()));
|
||||||
|
let cloned_events = clone_events();
|
||||||
|
assert_eq!(EVENTS.lock().unwrap().clone(), cloned_events);
|
||||||
|
}
|
||||||
|
|
@ -47,3 +47,39 @@ fn tiletype_with_var_equality() {
|
||||||
let tile3 = TileType::ToLocal(3);
|
let tile3 = TileType::ToLocal(3);
|
||||||
assert_eq!(tile2, tile3);
|
assert_eq!(tile2, tile3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn init_maps_for_tests() -> (MasterDungeonMap, Map, Map) {
|
||||||
|
let dm = MasterDungeonMap::new();
|
||||||
|
let (overmap, difficulty, name, short_name, depth) = (false, 0, "Test Map", "Test Map", 0);
|
||||||
|
let map1 = Map::new(overmap, 1, 64, 64, difficulty, name, short_name, depth);
|
||||||
|
let map2 = Map::new(overmap, 2, 128, 128, difficulty, name, short_name, depth);
|
||||||
|
(dm, map1, map2)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn map_saving() {
|
||||||
|
let (mut dm, map1, map2) = init_maps_for_tests();
|
||||||
|
dm.store_map(&map1);
|
||||||
|
dm.store_map(&map2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn map_loading() {
|
||||||
|
let (mut dm, map1, map2) = init_maps_for_tests();
|
||||||
|
dm.store_map(&map1);
|
||||||
|
let loaded_map1 = dm.get_map(map1.id).unwrap();
|
||||||
|
assert_eq!(loaded_map1.overmap, map1.overmap);
|
||||||
|
assert_eq!(loaded_map1.id, map1.id);
|
||||||
|
assert_eq!(loaded_map1.width, map1.width);
|
||||||
|
assert_eq!(loaded_map1.height, map1.height);
|
||||||
|
assert_eq!(loaded_map1.difficulty, map1.difficulty);
|
||||||
|
assert_eq!(loaded_map1.name, map1.name);
|
||||||
|
assert_eq!(loaded_map1.short_name, map1.short_name);
|
||||||
|
assert_eq!(loaded_map1.depth, map1.depth);
|
||||||
|
assert_eq!(loaded_map1.tiles.len(), map1.tiles.len());
|
||||||
|
assert_eq!(loaded_map1.messages, map1.messages);
|
||||||
|
dm.store_map(&map2);
|
||||||
|
let loaded_map2 = dm.get_map(map2.id).unwrap();
|
||||||
|
assert_eq!(loaded_map2.width, map2.width);
|
||||||
|
assert_ne!(loaded_map2.width, map1.width);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,4 @@
|
||||||
// tests/mod.rs
|
// tests/mod.rs
|
||||||
mod map_test;
|
mod map_test;
|
||||||
|
mod gamelog_test;
|
||||||
|
mod components_test;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue