Compare commits
18 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c29b93337c | ||
|
|
0584d07a1f | ||
|
|
45b9b33039 | ||
|
|
bdcd55c8a5 | ||
|
|
99c17f8521 | ||
|
|
6324449c16 | ||
|
|
d465592c0f | ||
|
|
f494efbf3f | ||
|
|
ba5d120fef | ||
|
|
c5106a63b5 | ||
|
|
9719ebbe88 | ||
|
|
2eaf431942 | ||
|
|
a7c5d2167c | ||
|
|
678636c57d | ||
|
|
30697a98bb | ||
|
|
c73f9a5458 | ||
|
|
9c8f301491 | ||
|
|
180532ee3e |
29 changed files with 866 additions and 978 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
|
||||||
|
|
|
||||||
11
README.md
11
README.md
|
|
@ -2,18 +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.
|
|
||||||
|
|
||||||
i'm also working on translating over my progress into blog entries on my site @ [llyw.co.uk](https://llyw.co.uk/), with a larger focus on some of the more interesting implementation details.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>boring details about the sprint where this project started</summary>
|
||||||
<details>
|
<details>
|
||||||
<summary>week 1</summary>
|
<summary>week 1</summary>
|
||||||
|
|
||||||
|
|
@ -157,3 +157,4 @@ i'm also working on translating over my progress into blog entries on my site @
|
||||||

|

|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
</details>
|
||||||
|
|
|
||||||
|
|
@ -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,9 +67,10 @@
|
||||||
"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;magic" },
|
"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" }
|
||||||
},
|
},
|
||||||
|
|
@ -71,9 +78,10 @@
|
||||||
"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;fire", "aoe": "2" },
|
"effects": { "particle": "*;#FFA500;200.0", "ranged": "10", "damage": "4d6;fire", "aoe": "2" },
|
||||||
"magic": { "class": "uncommon", "naming": "scroll" }
|
"magic": { "class": "uncommon", "naming": "scroll" }
|
||||||
},
|
},
|
||||||
|
|
@ -81,9 +89,10 @@
|
||||||
"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",
|
||||||
|
|
@ -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,6 +387,7 @@
|
||||||
"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"],
|
||||||
|
|
@ -360,6 +398,7 @@
|
||||||
"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"],
|
||||||
|
|
@ -370,6 +409,7 @@
|
||||||
"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"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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,9 +92,7 @@ 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::PlayerConfused(1));
|
gamelog::record_event(EVENT::PlayerConfused(1));
|
||||||
|
|
|
||||||
|
|
@ -258,10 +258,40 @@ pub struct Beatitude {
|
||||||
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)]
|
||||||
|
|
@ -618,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 {}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Config {
|
Config {
|
||||||
logging: LogConfig {
|
logging: LogConfig {
|
||||||
show_mapgen: true,
|
show_mapgen: false,
|
||||||
log_combat: false,
|
log_combat: false,
|
||||||
log_spawning: false,
|
log_spawning: false,
|
||||||
log_ticks: false,
|
log_ticks: false,
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
@ -82,6 +94,7 @@ 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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ use crate::{
|
||||||
KnownSpells,
|
KnownSpells,
|
||||||
Position,
|
Position,
|
||||||
Viewshed,
|
Viewshed,
|
||||||
|
WantsToRemoveKey,
|
||||||
|
WantsToDelete,
|
||||||
};
|
};
|
||||||
use crate::data::messages::*;
|
use crate::data::messages::*;
|
||||||
use bracket_lib::prelude::*;
|
use bracket_lib::prelude::*;
|
||||||
|
|
@ -57,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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,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 {
|
||||||
|
|
@ -262,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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
|
@ -38,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;
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,11 @@ fn main() -> BError {
|
||||||
gs.ecs.register::<HasDamageModifiers>();
|
gs.ecs.register::<HasDamageModifiers>();
|
||||||
gs.ecs.register::<Intrinsics>();
|
gs.ecs.register::<Intrinsics>();
|
||||||
gs.ecs.register::<IntrinsicChanged>();
|
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>();
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
use super::{ spawner, Map, Position, Rect, TileType };
|
use super::{ spawner, Map, Position, Rect, TileType };
|
||||||
use bracket_lib::prelude::*;
|
use bracket_lib::prelude::*;
|
||||||
|
|
||||||
mod room_accretion;
|
|
||||||
use room_accretion::RoomAccretionBuilder;
|
|
||||||
mod bsp_dungeon;
|
mod bsp_dungeon;
|
||||||
use bsp_dungeon::BspDungeonBuilder;
|
use bsp_dungeon::BspDungeonBuilder;
|
||||||
mod bsp_interior;
|
mod bsp_interior;
|
||||||
|
|
@ -447,7 +445,7 @@ pub fn level_builder(
|
||||||
initial_player_level: i32
|
initial_player_level: i32
|
||||||
) -> BuilderChain {
|
) -> BuilderChain {
|
||||||
match id {
|
match id {
|
||||||
ID_OVERMAP => room_accretion(),
|
ID_OVERMAP => overmap_builder(),
|
||||||
ID_TOWN => town_builder(id, rng, width, height, 0, initial_player_level),
|
ID_TOWN => town_builder(id, rng, width, height, 0, initial_player_level),
|
||||||
ID_TOWN2 => forest_builder(id, rng, width, height, 1, initial_player_level),
|
ID_TOWN2 => forest_builder(id, rng, width, height, 1, initial_player_level),
|
||||||
ID_TOWN3 =>
|
ID_TOWN3 =>
|
||||||
|
|
@ -492,10 +490,3 @@ pub fn level_builder(
|
||||||
fn diff(branch_id: i32, lvl_id: i32) -> i32 {
|
fn diff(branch_id: i32, lvl_id: i32) -> i32 {
|
||||||
return lvl_id - branch_id;
|
return lvl_id - branch_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn room_accretion() -> BuilderChain {
|
|
||||||
let mut builder = BuilderChain::new(false, 110, 64, 64, 0, "room_accretion", "accretion", 0, 1);
|
|
||||||
builder.start_with(RoomAccretionBuilder::new());
|
|
||||||
builder.with(AreaStartingPosition::new(XStart::CENTRE, YStart::CENTRE));
|
|
||||||
builder
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
pub const HEIGHT: usize = 64;
|
|
||||||
pub const WIDTH: usize = 64;
|
|
||||||
pub const HALLWAY_CHANCE: f32 = 0.5;
|
|
||||||
pub const VERTICAL_CORRIDOR_MIN_LENGTH: i32 = 2;
|
|
||||||
pub const VERTICAL_CORRIDOR_MAX_LENGTH: i32 = 9;
|
|
||||||
pub const HORIZONTAL_CORRIDOR_MIN_LENGTH: i32 = 5;
|
|
||||||
pub const HORIZONTAL_CORRIDOR_MAX_LENGTH: i32 = 15;
|
|
||||||
|
|
||||||
pub enum Operator {
|
|
||||||
LessThan,
|
|
||||||
GreaterThan,
|
|
||||||
LessThanEqualTo,
|
|
||||||
GreaterThanEqualTo,
|
|
||||||
EqualTo,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Operator {
|
|
||||||
pub fn eval(&self, a: i32, b: i32) -> bool {
|
|
||||||
match self {
|
|
||||||
Operator::LessThan => a < b,
|
|
||||||
Operator::GreaterThan => a > b,
|
|
||||||
Operator::LessThanEqualTo => a <= b,
|
|
||||||
Operator::GreaterThanEqualTo => a >= b,
|
|
||||||
Operator::EqualTo => a == b,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn string(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
Operator::LessThan => "<",
|
|
||||||
Operator::GreaterThan => ">",
|
|
||||||
Operator::LessThanEqualTo => "<=",
|
|
||||||
Operator::GreaterThanEqualTo => ">=",
|
|
||||||
Operator::EqualTo => "==",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CellRules {
|
|
||||||
pub adjacent_type: i32,
|
|
||||||
pub into: i32,
|
|
||||||
pub operator: Operator,
|
|
||||||
pub n: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CellRules {
|
|
||||||
const fn new(adjacent_type: i32, into: i32, operator: Operator, n: i32) -> CellRules {
|
|
||||||
CellRules {
|
|
||||||
adjacent_type,
|
|
||||||
into,
|
|
||||||
operator,
|
|
||||||
n,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref CA: Vec<Vec<CellRules>> = vec![
|
|
||||||
vec![CellRules::new(1, 1, Operator::GreaterThanEqualTo, 4)],
|
|
||||||
vec![
|
|
||||||
CellRules::new(0, 0, Operator::GreaterThanEqualTo, 5),
|
|
||||||
CellRules::new(1, 0, Operator::LessThan, 2)
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
pub enum Direction {
|
|
||||||
NoDir = -1,
|
|
||||||
North = 0,
|
|
||||||
East = 1,
|
|
||||||
South = 2,
|
|
||||||
West = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Direction {
|
|
||||||
pub fn transform(&self) -> Point {
|
|
||||||
match self {
|
|
||||||
Direction::NoDir => unreachable!("Direction::NoDir should never be transformed"),
|
|
||||||
Direction::North => Point::new(0, -1),
|
|
||||||
Direction::East => Point::new(1, 0),
|
|
||||||
Direction::South => Point::new(0, 1),
|
|
||||||
Direction::West => Point::new(-1, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn opposite_dir(&self) -> Direction {
|
|
||||||
match self {
|
|
||||||
Direction::NoDir => unreachable!("Direction::NoDir has no opposite."),
|
|
||||||
Direction::North => Direction::South,
|
|
||||||
Direction::East => Direction::West,
|
|
||||||
Direction::South => Direction::North,
|
|
||||||
Direction::West => Direction::East,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DirectionIterator {
|
|
||||||
current: Direction,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DirectionIterator {
|
|
||||||
pub fn new() -> DirectionIterator {
|
|
||||||
DirectionIterator {
|
|
||||||
current: Direction::North,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for DirectionIterator {
|
|
||||||
type Item = Direction;
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
use Direction::*;
|
|
||||||
let next_direction = match self.current {
|
|
||||||
North => East,
|
|
||||||
East => South,
|
|
||||||
South => West,
|
|
||||||
West => {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
NoDir => unreachable!("Direction::NoDir should never be iterated over."),
|
|
||||||
};
|
|
||||||
self.current = next_direction;
|
|
||||||
Some(next_direction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,397 +0,0 @@
|
||||||
use super::{ BuilderMap, Map, InitialMapBuilder, TileType, Point };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
mod consts;
|
|
||||||
use consts::*;
|
|
||||||
|
|
||||||
/// Room Accretion map builder.
|
|
||||||
pub struct RoomAccretionBuilder {}
|
|
||||||
|
|
||||||
impl InitialMapBuilder for RoomAccretionBuilder {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
self.build(rng, build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RoomAccretionBuilder {
|
|
||||||
/// Constructor for Room Accretion.
|
|
||||||
pub fn new() -> Box<RoomAccretionBuilder> {
|
|
||||||
Box::new(RoomAccretionBuilder {})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
accrete_rooms(rng, build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn grid_with_dimensions(h: usize, w: usize, value: i32) -> Vec<Vec<i32>> {
|
|
||||||
vec![vec![value; w]; h]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn in_bounds(row: i32, col: i32, build_data: &BuilderMap) -> bool {
|
|
||||||
row > 0 && row < build_data.height && col > 0 && col < build_data.width
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_continuous_shape_on_grid(
|
|
||||||
room: &Vec<Vec<i32>>,
|
|
||||||
top_offset: usize,
|
|
||||||
left_offset: usize,
|
|
||||||
grid: &mut Vec<Vec<i32>>
|
|
||||||
) {
|
|
||||||
for row in 0..room.len() {
|
|
||||||
for col in 0..room[0].len() {
|
|
||||||
if room[row][col] != 0 {
|
|
||||||
let target_row = row + top_offset;
|
|
||||||
let target_col = col + left_offset;
|
|
||||||
if target_row < grid.len() && target_col < grid[0].len() {
|
|
||||||
grid[target_row][target_col] = room[row][col];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Coordinate {
|
|
||||||
pub location: Point,
|
|
||||||
pub value: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_individual_coordinates_on_grid(coordinates: &Vec<Coordinate>, grid: &mut Vec<Vec<i32>>) {
|
|
||||||
for c in coordinates {
|
|
||||||
let x = c.location.x as usize;
|
|
||||||
let y = c.location.y as usize;
|
|
||||||
if y < grid.len() && x < grid[0].len() {
|
|
||||||
grid[y][x] = c.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_cell_neighbours(
|
|
||||||
cells: &Vec<Vec<i32>>,
|
|
||||||
row: usize,
|
|
||||||
col: usize,
|
|
||||||
h: usize,
|
|
||||||
w: usize
|
|
||||||
) -> Vec<i32> {
|
|
||||||
let mut neighbours = Vec::new();
|
|
||||||
for x in row.saturating_sub(1)..=std::cmp::min(row + 1, h - 1) {
|
|
||||||
for y in col.saturating_sub(1)..=std::cmp::min(col + 1, w - 1) {
|
|
||||||
if x != row || y != col {
|
|
||||||
neighbours.push(cells[x][y]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console::log(&format!("neighbours: {:?}", neighbours));
|
|
||||||
neighbours
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_ca_room(rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) -> Vec<Vec<i32>> {
|
|
||||||
let width = rng.range(5, 12);
|
|
||||||
let height = rng.range(5, 12);
|
|
||||||
let mut cells = grid_with_dimensions(height, width, 0);
|
|
||||||
cells = cells
|
|
||||||
.into_iter()
|
|
||||||
.map(|row| {
|
|
||||||
row.into_iter()
|
|
||||||
.map(|_| if rng.roll_dice(1, 2) == 1 { 1 } else { 0 })
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let transform_cell = |state: i32, neighbours: &Vec<i32>| -> i32 {
|
|
||||||
let rules: &[CellRules] = &CA[state as usize];
|
|
||||||
let mut new_state = state;
|
|
||||||
for rule in rules {
|
|
||||||
let n_neighbours = neighbours
|
|
||||||
.iter()
|
|
||||||
.filter(|&&neighbour| neighbour == rule.adjacent_type)
|
|
||||||
.count();
|
|
||||||
if rule.operator.eval(n_neighbours as i32, rule.n) {
|
|
||||||
new_state = rule.into;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
new_state
|
|
||||||
};
|
|
||||||
|
|
||||||
for _ in 0..5 {
|
|
||||||
let mut new_cells = vec![vec![0; width]; height];
|
|
||||||
for row in 0..height {
|
|
||||||
for col in 0..width {
|
|
||||||
let neighbours = get_cell_neighbours(&cells, row, col, height, width);
|
|
||||||
let new_state = transform_cell(cells[row][col], &neighbours);
|
|
||||||
new_cells[row][col] = new_state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cells = new_cells;
|
|
||||||
}
|
|
||||||
// TODO: Floodfill to keep largest contiguous blob
|
|
||||||
cells
|
|
||||||
}
|
|
||||||
|
|
||||||
fn room_fits_at(
|
|
||||||
hyperspace: Vec<Vec<i32>>,
|
|
||||||
top_offset: usize,
|
|
||||||
left_offset: usize,
|
|
||||||
build_data: &BuilderMap
|
|
||||||
) -> bool {
|
|
||||||
let mut x_dungeon: usize;
|
|
||||||
let mut y_dungeon: usize;
|
|
||||||
for y in 0..HEIGHT {
|
|
||||||
for x in 0..WIDTH {
|
|
||||||
if hyperspace[y][x] != 2 {
|
|
||||||
y_dungeon = y + top_offset;
|
|
||||||
x_dungeon = x + left_offset;
|
|
||||||
for i in y_dungeon.saturating_sub(1)..=std::cmp::min(y_dungeon + 1, WIDTH - 1) {
|
|
||||||
for j in x_dungeon.saturating_sub(1)..=std::cmp::min(
|
|
||||||
x_dungeon + 1,
|
|
||||||
HEIGHT - 1
|
|
||||||
) {
|
|
||||||
let pt = build_data.map.xy_idx(i as i32, j as i32);
|
|
||||||
if
|
|
||||||
!in_bounds(i as i32, j as i32, &build_data) ||
|
|
||||||
!(build_data.map.tiles[pt] == TileType::Wall) ||
|
|
||||||
build_data.spawn_list.contains(&(pt, "door".to_string()))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn direction_of_door(
|
|
||||||
grid: &Vec<Vec<i32>>,
|
|
||||||
row: usize,
|
|
||||||
col: usize,
|
|
||||||
build_data: &BuilderMap
|
|
||||||
) -> Direction {
|
|
||||||
if grid[row][col] != 0 {
|
|
||||||
return Direction::NoDir;
|
|
||||||
}
|
|
||||||
let mut solution = Direction::NoDir;
|
|
||||||
let mut dir_iter = DirectionIterator::new();
|
|
||||||
for dir in &mut dir_iter {
|
|
||||||
let new_col = (col as i32) + dir.transform().x;
|
|
||||||
let new_row = (row as i32) + dir.transform().y;
|
|
||||||
let opp_col = (col as i32) - dir.transform().x;
|
|
||||||
let opp_row = (row as i32) - dir.transform().y;
|
|
||||||
if
|
|
||||||
in_bounds(new_row, new_col, &build_data) &&
|
|
||||||
in_bounds(opp_row, opp_col, &build_data) &&
|
|
||||||
grid[opp_row as usize][opp_col as usize] != 0
|
|
||||||
{
|
|
||||||
solution = dir;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return solution;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
pub struct DoorSite {
|
|
||||||
pub x: i32,
|
|
||||||
pub y: i32,
|
|
||||||
pub dir: Direction,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn choose_random_door_site(
|
|
||||||
room: Vec<Vec<i32>>,
|
|
||||||
rng: &mut RandomNumberGenerator,
|
|
||||||
build_data: &BuilderMap
|
|
||||||
) -> Vec<Option<DoorSite>> {
|
|
||||||
let mut grid = grid_with_dimensions(HEIGHT, WIDTH, 0);
|
|
||||||
let mut door_sites: Vec<DoorSite> = Vec::new();
|
|
||||||
const LEFT_OFFSET: usize = ((WIDTH as f32) / 2.0) as usize;
|
|
||||||
const TOP_OFFSET: usize = ((HEIGHT as f32) / 2.0) as usize;
|
|
||||||
draw_continuous_shape_on_grid(&room, TOP_OFFSET, LEFT_OFFSET, &mut grid);
|
|
||||||
for row in 0..HEIGHT {
|
|
||||||
for col in 0..WIDTH {
|
|
||||||
if grid[row][col] == 0 {
|
|
||||||
let door_dir = direction_of_door(&grid, row, col, &build_data);
|
|
||||||
if door_dir == Direction::NoDir {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let mut door_failed = false;
|
|
||||||
let (mut trace_row, mut trace_col) = (
|
|
||||||
(row as i32) + door_dir.transform().y,
|
|
||||||
(col as i32) + door_dir.transform().x,
|
|
||||||
);
|
|
||||||
let mut i = 0;
|
|
||||||
while i < 10 && in_bounds(trace_row, trace_col, &build_data) && !door_failed {
|
|
||||||
if grid[trace_row as usize][trace_col as usize] != 0 {
|
|
||||||
door_failed = true;
|
|
||||||
}
|
|
||||||
trace_col += door_dir.transform().x;
|
|
||||||
trace_row += door_dir.transform().y;
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
if !door_failed {
|
|
||||||
// May need more information here.
|
|
||||||
door_sites.push(DoorSite {
|
|
||||||
x: col as i32,
|
|
||||||
y: row as i32,
|
|
||||||
dir: door_dir,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut chosen_doors: Vec<Option<DoorSite>> = vec![None; 4];
|
|
||||||
let mut dir_iter = DirectionIterator::new();
|
|
||||||
for dir in &mut dir_iter {
|
|
||||||
let doors_facing_this_dir: Vec<&DoorSite> = door_sites
|
|
||||||
.iter()
|
|
||||||
.filter(|&door| door.dir == dir)
|
|
||||||
.collect();
|
|
||||||
if !doors_facing_this_dir.is_empty() {
|
|
||||||
let index = rng.range(0, doors_facing_this_dir.len());
|
|
||||||
chosen_doors[dir as usize] = Some(*doors_facing_this_dir[index]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chosen_doors
|
|
||||||
}
|
|
||||||
|
|
||||||
fn shuffle<T>(list: &mut Vec<T>, rng: &mut RandomNumberGenerator) {
|
|
||||||
let len = list.len();
|
|
||||||
for i in (1..len).rev() {
|
|
||||||
let j = rng.range(0, i + 1);
|
|
||||||
list.swap(i, j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clamp<T: Ord>(x: T, min: T, max: T) -> T {
|
|
||||||
if x < min { min } else if x > max { max } else { x }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn attach_hallway_to(
|
|
||||||
door_sites: &mut Vec<Option<DoorSite>>,
|
|
||||||
hyperspace: &mut Vec<Vec<i32>>,
|
|
||||||
rng: &mut RandomNumberGenerator,
|
|
||||||
build_data: &BuilderMap
|
|
||||||
) {
|
|
||||||
let mut directions = vec![Direction::North, Direction::East, Direction::South, Direction::West];
|
|
||||||
shuffle(&mut directions, rng);
|
|
||||||
let mut hallway_dir: Direction = Direction::NoDir;
|
|
||||||
for i in 0..4 {
|
|
||||||
hallway_dir = directions[i];
|
|
||||||
if
|
|
||||||
door_sites[hallway_dir as usize].is_some() &&
|
|
||||||
in_bounds(
|
|
||||||
door_sites[hallway_dir as usize].unwrap().y +
|
|
||||||
hallway_dir.transform().y * VERTICAL_CORRIDOR_MAX_LENGTH,
|
|
||||||
door_sites[hallway_dir as usize].unwrap().x +
|
|
||||||
hallway_dir.transform().x * HORIZONTAL_CORRIDOR_MAX_LENGTH,
|
|
||||||
&build_data
|
|
||||||
)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let transform = hallway_dir.transform();
|
|
||||||
let hallway_len: i32 = match hallway_dir {
|
|
||||||
Direction::NoDir => {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Direction::North | Direction::South =>
|
|
||||||
rng.range(VERTICAL_CORRIDOR_MIN_LENGTH, VERTICAL_CORRIDOR_MAX_LENGTH + 1),
|
|
||||||
Direction::East | Direction::West =>
|
|
||||||
rng.range(HORIZONTAL_CORRIDOR_MIN_LENGTH, HORIZONTAL_CORRIDOR_MAX_LENGTH + 1),
|
|
||||||
};
|
|
||||||
let mut x = door_sites[hallway_dir as usize].unwrap().x;
|
|
||||||
let mut y = door_sites[hallway_dir as usize].unwrap().y;
|
|
||||||
for _i in 0..hallway_len {
|
|
||||||
if in_bounds(y, x, &build_data) {
|
|
||||||
hyperspace[y as usize][x as usize] = 1; // Dig out corridor.
|
|
||||||
}
|
|
||||||
x += transform.x;
|
|
||||||
y += transform.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
y = clamp(y - transform.y, 0, (HEIGHT as i32) - 1);
|
|
||||||
x = clamp(x - transform.x, 0, (WIDTH as i32) - 1);
|
|
||||||
|
|
||||||
let mut dir_iter = DirectionIterator::new();
|
|
||||||
for dir in &mut dir_iter {
|
|
||||||
if dir != hallway_dir.opposite_dir() {
|
|
||||||
let door_y = y + dir.transform().y;
|
|
||||||
let door_x = x + dir.transform().x;
|
|
||||||
door_sites[dir as usize] = Some(DoorSite {
|
|
||||||
x: door_x,
|
|
||||||
y: door_y,
|
|
||||||
dir,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
door_sites[dir as usize] = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console::log(&format!("door_sites: {:?}", door_sites));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn design_room_in_hyperspace(
|
|
||||||
rng: &mut RandomNumberGenerator,
|
|
||||||
build_data: &mut BuilderMap
|
|
||||||
) -> Vec<Vec<i32>> {
|
|
||||||
// Project onto hyperspace
|
|
||||||
let mut hyperspace = grid_with_dimensions(HEIGHT, WIDTH, 0);
|
|
||||||
let room_type = rng.range(0, 1);
|
|
||||||
let room = match room_type {
|
|
||||||
0 => make_ca_room(rng, build_data),
|
|
||||||
_ => unreachable!("Invalid room type."),
|
|
||||||
};
|
|
||||||
draw_continuous_shape_on_grid(&room, HEIGHT / 2, WIDTH / 2, &mut hyperspace);
|
|
||||||
let mut door_sites = choose_random_door_site(room, rng, &build_data);
|
|
||||||
let roll: f32 = rng.rand();
|
|
||||||
if roll < HALLWAY_CHANCE {
|
|
||||||
attach_hallway_to(&mut door_sites, &mut hyperspace, rng, &build_data);
|
|
||||||
}
|
|
||||||
let coords: Vec<Coordinate> = door_sites
|
|
||||||
.iter()
|
|
||||||
.filter(|&door| door.is_some())
|
|
||||||
.map(|&door| Coordinate {
|
|
||||||
location: Point::new(door.unwrap().x, door.unwrap().y),
|
|
||||||
value: 2,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
draw_individual_coordinates_on_grid(&coords, &mut hyperspace);
|
|
||||||
hyperspace
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_i32_to_tiletype(val: i32, build_data: &mut BuilderMap) -> TileType {
|
|
||||||
match val {
|
|
||||||
0 => TileType::Wall,
|
|
||||||
1 => TileType::Floor,
|
|
||||||
2 => TileType::Floor, // With door.
|
|
||||||
_ => unreachable!("Unknown TileType"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flatten_hyperspace_into_dungeon(
|
|
||||||
hyperspace: Vec<Vec<i32>>,
|
|
||||||
build_data: &mut BuilderMap
|
|
||||||
) -> Vec<TileType> {
|
|
||||||
let flattened_hyperspace: Vec<i32> = hyperspace.into_iter().flatten().collect();
|
|
||||||
flattened_hyperspace
|
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(idx, cell)| {
|
|
||||||
if cell != 0 {
|
|
||||||
match cell {
|
|
||||||
2 => build_data.spawn_list.push((idx, "door".to_string())),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
map_i32_to_tiletype(cell, build_data)
|
|
||||||
} else {
|
|
||||||
build_data.map.tiles[idx % (build_data.map.width as usize)]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn accrete_rooms(rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
let hyperspace = design_room_in_hyperspace(rng, build_data);
|
|
||||||
build_data.map.tiles = flatten_hyperspace_into_dungeon(hyperspace, build_data);
|
|
||||||
build_data.take_snapshot();
|
|
||||||
}
|
|
||||||
|
|
@ -30,6 +30,7 @@ use super::{
|
||||||
Viewshed,
|
Viewshed,
|
||||||
WantsToMelee,
|
WantsToMelee,
|
||||||
WantsToPickupItem,
|
WantsToPickupItem,
|
||||||
|
WantsToAssignKey,
|
||||||
get_dest,
|
get_dest,
|
||||||
Destination,
|
Destination,
|
||||||
DamageType,
|
DamageType,
|
||||||
|
|
@ -633,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>,
|
||||||
|
|
|
||||||
|
|
@ -66,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 ---
|
||||||
|
|
@ -281,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 ---
|
||||||
|
|
@ -293,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));
|
||||||
}
|
}
|
||||||
|
|
@ -392,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>();
|
||||||
|
|
@ -399,12 +416,15 @@ 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(),
|
||||||
|
|
@ -632,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 {}
|
||||||
|
|
@ -686,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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -98,6 +99,7 @@ pub fn save_game(ecs: &mut World) {
|
||||||
IntrinsicChanged,
|
IntrinsicChanged,
|
||||||
Intrinsics,
|
Intrinsics,
|
||||||
Item,
|
Item,
|
||||||
|
Key,
|
||||||
KnownSpells,
|
KnownSpells,
|
||||||
LootTable,
|
LootTable,
|
||||||
MagicItem,
|
MagicItem,
|
||||||
|
|
@ -127,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
|
||||||
|
|
@ -231,6 +237,7 @@ pub fn load_game(ecs: &mut World) {
|
||||||
IntrinsicChanged,
|
IntrinsicChanged,
|
||||||
Intrinsics,
|
Intrinsics,
|
||||||
Item,
|
Item,
|
||||||
|
Key,
|
||||||
KnownSpells,
|
KnownSpells,
|
||||||
LootTable,
|
LootTable,
|
||||||
MagicItem,
|
MagicItem,
|
||||||
|
|
@ -260,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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue