Compare commits
No commits in common. "master" and "gh-pages" have entirely different histories.
154 changed files with 281 additions and 22012 deletions
22
.github/workflows/cargo-build-test.yml
vendored
22
.github/workflows/cargo-build-test.yml
vendored
|
|
@ -1,22 +0,0 @@
|
||||||
name: Rust
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "master" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "master" ]
|
|
||||||
|
|
||||||
env:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Build
|
|
||||||
run: cargo build --verbose
|
|
||||||
- name: Run tests
|
|
||||||
run: cargo test --verbose
|
|
||||||
34
.github/workflows/deploy wasm to gh-pages.yml
vendored
34
.github/workflows/deploy wasm to gh-pages.yml
vendored
|
|
@ -1,34 +0,0 @@
|
||||||
on: push
|
|
||||||
name: Build and deploy web page with WASM version
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
|
||||||
steps:
|
|
||||||
- name: Checkout project
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Setup - Rust
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: nightly
|
|
||||||
target: wasm32-unknown-unknown
|
|
||||||
override: true
|
|
||||||
- name: Setup - wasm-bindgen
|
|
||||||
uses: jetli/wasm-bindgen-action@v0.2.0
|
|
||||||
- name: Build, bind WASM
|
|
||||||
run: |
|
|
||||||
cargo build --release --target wasm32-unknown-unknown
|
|
||||||
wasm-bindgen target/wasm32-unknown-unknown/release/rust-rl.wasm --out-dir wasm --no-modules --no-typescript
|
|
||||||
- name: Publish web\ to gh-pages
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
run: |
|
|
||||||
cd wasm/
|
|
||||||
git init --initial-branch=master
|
|
||||||
git config user.name "GitHub Actions"
|
|
||||||
git config user.email "github-actions-bot@users.noreply.github.com"
|
|
||||||
git add .
|
|
||||||
|
|
||||||
git commit -m "Deploy ${GITHUB_REPOSITORY} to ${GITHUB_REPOSITORY}:gh-pages"
|
|
||||||
git push --force "https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" master:gh-pages
|
|
||||||
echo "Deploy complete"
|
|
||||||
19
.gitignore
vendored
19
.gitignore
vendored
|
|
@ -1,19 +0,0 @@
|
||||||
# Build files, documentation, benchmarks
|
|
||||||
target
|
|
||||||
wasm/index.css
|
|
||||||
wasm/index.html
|
|
||||||
docs/gifs/*
|
|
||||||
|
|
||||||
# VSCode/IDE config files
|
|
||||||
Cargo.lock
|
|
||||||
.vscode/*
|
|
||||||
.rustfmt.toml
|
|
||||||
.prettierignore
|
|
||||||
.prettierrc.json
|
|
||||||
|
|
||||||
# Save files, morgue files
|
|
||||||
savegame.json
|
|
||||||
morgue
|
|
||||||
|
|
||||||
# A default user config will be written on first run, if not present
|
|
||||||
config.toml
|
|
||||||
31
Cargo.toml
31
Cargo.toml
|
|
@ -1,31 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "rust-rl"
|
|
||||||
version = "0.1.4"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bracket-lib = { git = "https://github.com/amethyst/bracket-lib.git", rev = "851f6f08675444fb6fa088b9e67bee9fd75554c6", features = ["serde"] }
|
|
||||||
regex = "1.3.6"
|
|
||||||
specs = { version = "0.16.1", features = ["serde"] }
|
|
||||||
specs-derive = "0.4.1"
|
|
||||||
serde = { version = "1.0.93", features = ["derive"]}
|
|
||||||
serde_json = "1.0.39"
|
|
||||||
toml = "0.5"
|
|
||||||
lazy_static = "1.4.0"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
criterion = { version = "^0.5" }
|
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "systems_benchmark"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
# Enable max optimizations for dependencies, but not for our code:
|
|
||||||
[profile.dev.package."*"]
|
|
||||||
opt-level = 3
|
|
||||||
|
|
||||||
# Enable only a small amount of optimization in debug mode
|
|
||||||
# [profile.dev]
|
|
||||||
# opt-level = 1
|
|
||||||
160
README.md
160
README.md
|
|
@ -1,160 +0,0 @@
|
||||||
## a roguelike in rust, playable @ [llyw.co.uk/rl/](https://llyw.co.uk/rl/)
|
|
||||||
|
|
||||||
#### using _rltk/bracket-lib_, and _specs_
|
|
||||||
|
|
||||||
[](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`,
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>boring details about the sprint where this project started</summary>
|
|
||||||
<details>
|
|
||||||
<summary>week 1</summary>
|
|
||||||
|
|
||||||
- brogue-like colours
|
|
||||||
- i was staring at a horrible-looking game for a while as i tried to figure out how to make it look nice, before deciding to try the brogue method of colour offsets. when a map is generated, it also generates a red, green, and blue offset value for every tile on the map, and applies them during rendering. after making that change i started to miss the previous hue, so i combined the two. as it stands, every tile starts off a subtle green/blue, has rgb offsets applied on top of that, and then has the actual tile colour applied. and it ends up making something like this
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
- fov
|
|
||||||
- decided to use bracket-lib's symmetric shadowcasting for common viewsheds (i.e. sight)
|
|
||||||
- and implemented elig's [raycasting](https://www.roguebasin.com/index.php/Eligloscode) algorithm for any viewsheds that _dont_ need that level of detail. symmetric is great, but when it comes to viewsheds that often _aren't_ symmetric in the first place, it's not really necessary (i.e. it's not often you've got two people with: the same additional viewshed, both within range, etc.). doing it this way comes with the benefit of being able to easily define what blocks a viewshed, rather than having to make a whole new BaseMap to work through bracket-lib
|
|
||||||
|
|
||||||
- telepaths and having brains
|
|
||||||
- telepathy! a personal favourite rl feature, so i thought it'd be a cool test of the raycasting. right now it's simple, since the point was really just making sure the raycasting worked: there's a component for _being a telepath_, and for _having a mind_. if someone has telepathy, they'll see every entity with a mind within a given radius (defined by their telepath component), even through walls.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
- atomised spawn tables
|
|
||||||
- i tried figuring out how often things would spawn by just looking at the weighted tables, and i had no idea at a glance, so i replaced it with category tables. right now it's just rolling for an entity or a mob, and then rolling on the right table from there, but at least it means easily being able to see how often something will spawn. on average right now, there's 1 item : 3 mobs
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>week 2</summary>
|
|
||||||
|
|
||||||
- most of section 3 - generating maps
|
|
||||||
- this week was mostly just working away at the mapgen stuff. getting all the algorithms in, chaining builders, being able to do prefabs. whenever i got bored i just opened rexpaint and toyed around with making simple vaults.
|
|
||||||
|
|
||||||
- 8-bit walls
|
|
||||||
- i wasn't happy with how the walls looked, so i made the masks 8-bit instead of just 4-, which means being able to be a lot more specific with which glyphs are used. mainly it means no more grids of ╬. this comes with a side-effect of magic mapping looking a lot better.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>week 3</summary>
|
|
||||||
|
|
||||||
- (better) vault loot
|
|
||||||
- moved over to using raws and atomised spawn tables into a bunch of sub-categories in the process, like wands, equipment, potions, etc. now there's options for rolling just out of subsets of items - useful for adding a specific spawn to a vault, or ensuring there's always an amount of food on a given level, etc. can also use this in the future for categorising groups of mobs, to only spawn x mobtype on a given map too.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
- actions with directions
|
|
||||||
- made a new runstate that prompts the player to pick a direction, and takes a function as an argument. after the player picks a direction, it calls the function with that direction as the args. right now it's being used for door stuff, but now it'll be super easy to make anything else that needs the same parameters
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
- ui stuff
|
|
||||||
- there's a help screen now with controls, accessed with [?], and a death screen that actually logs some stuff
|
|
||||||
|
|
||||||

|
|
||||||
- finally, identical items in the inventory stack. i waited with this until figuring out a way that would work with extra parameters in the future like BUC. current solution is using a BTreeMap with a tuple containing the parameters that need to be the same (right now just the name) as the key, and the number of those items in the inventory as the value.
|
|
||||||
- wand uses are tracked now with a number of asterisks next to their name -- i'll change this once i add in identification
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>week 4</summary>
|
|
||||||
|
|
||||||
- d20/hacklike combat overhaul
|
|
||||||
- the framework for levels, attributes, and skills are all in, as well as a lot of the associated systems. it now uses a system that trends way closer to the -hack brand of roguelikes (it's almost identical). i thought about doing something more novel just because then i could say i made it on my own, but then i decided i'd rather lean on the 35 years of balance tweaks nethack has had than start all over from scratch. not having to worry so much about balance gives me time to do other stuff, and i think the familiarity for players will be nice too. my favourite addition is a MULTIATTACK flag for mobs - if they have it, they'll use all their natural attacks in a turn instead of picking a random one
|
|
||||||
|
|
||||||
- extremely free-form loot tables (like rats spawning... lambs?)
|
|
||||||
- i realised my loot table structure wasn't very different from the spawn tables i'd been using for mapgen. other than one field, the structures were identical, so i decided to massively generalise how loot spawning works. instead of only allowing mobs to drop items from the specifically defined loot tables, they now have the capability to drop _anything_ from _any_ table -- for example, an animal can drop stuff from the animal drop table, or it could be set to drop a random scroll, or literally any other entity... including other mobs! i decided to test this with rats that had a 25% chance to "drop" anything from the _mobs_ spawn table on death. in this case, one rat left behind a lamb, and another left behind a fawn.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
- and a huge visual overhaul!
|
|
||||||
- a whole new ui, a new font (a 14x16 curses variant), a system to spawn particles on a delay for proper - if basic - animation, and a couple new features to fill in the expanded ui space (like being able to see a list of entities in view on the sidebar).
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>week 5</summary>
|
|
||||||
|
|
||||||
- not a whole lot
|
|
||||||
- busy week for me, so the only things i managed this week were finishing up some small things from the previous week. i started on combining logs that happen on the same tick (You hit the goblin. The goblin hits you.), and fixed up the _chance to spawn a guy each turn_ system to work with the groupsize flags, so there's a chance to spawn a pack of creatures each game turn rather than packs only spawning on map generation.
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>week 6</summary>
|
|
||||||
|
|
||||||
- visuals (contd.)
|
|
||||||
- i'll start here, because it pretty much shows all the other things i did this week in the one image. the biggest change is nixing the rainbow colours i did in week 4 - having all the names on the sidebar and inventory be coloured by the colour of their glyph made for a difficult to parse ui. i swapped everything out for being coloured by their rarity (white for common and nonmagical items, grey for unidentified, and green-to-gold for uncommon-legendary rarities), and decided to add the glyph of the entity beside the name. overall, a big win i think. it provides more information more cleanly.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
- backtracking
|
|
||||||
- short and sweet, maps are persistent now. entities don't get deleted when you descend, they just get frozen, and you can return anytime. stair-dancing is a thing now, and something i haven't decided how to handle yet.
|
|
||||||
|
|
||||||
- item identification (no BUC yet!)
|
|
||||||
- unique schemes for unidentified items in the big categories (i.e. "effervescent green potions", "octagonal wands", and "GIB BERISH scrolls"), as well as the option of defining one-off obfuscated names for special cases
|
|
||||||
|
|
||||||
- encumbrance
|
|
||||||
- varying levels of being overweight, with the limit determined by strength, slowing entities down by ~25% per level over unencumbered. right now it's pretty forgiving, and i'd probably like it to stay that way. my ideal balance here would be roughly how it ends up in 5e DnD: everyone can carry a good supply of consumables, but strength characters usually don't carry a much higher quantity of items than anyone else, because the strongest armour is extremely heavy. sort of like a soft strength requirement for the heaviest gear - rather than requiring specific stats or levels to equip it, it's heavy enough that you need to be strong to lug it around. but if someone weaker wanted to, they could, they'd just have to leave some other items behind to do so. or take the speed penalty for being encumbered
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>week 7</summary>
|
|
||||||
|
|
||||||
- character creation!
|
|
||||||
- it doesn't look too pretty yet, but i managed to get it done in time. classes and ancestries are selectable, with ancestries determining some intrinsic bonuses (dwarves are hardy, catfolk are fast and have claws, etc.) and attribute maximums, and classes determining starting equipment and the actual attribute rolls. along with this, i expanded entity reactions - now a shared ancestry is taken into account first of all, and it checks faction if it doesn't manage to find anything. this means humans wont attack other humans, dwarves wont be attacked by gnomes and other dwarves, etc.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
- proper identification and beatitude
|
|
||||||
- item-ID is split from beatitudes as it should be, scrolls of identify and remove curse are in, and blessed/cursed effects are applied for a bunch of things.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
- better AI
|
|
||||||
- straight from thebracket, with a handful of tweaks of my own, i've finally atomised AI into adjacent, visible, chase, flee, and default systems. most notably,rather than hostile mobs attacking everything and passive mobs doing nothing, every mob has a faction, and most mobs have an ancestry. like mentioned above, mobs will take all this into account when determining how they want to react to any other entity. i see a lot of places to expand on this in the future, like going into alignments and other increasingly-specific reasons why any entity might want to murder another. or make friends with them. taming comes to mind here.
|
|
||||||
|
|
||||||
- an effects system
|
|
||||||
- instead of randomly doing things all around the codebase, everything is in the process of being moved over to an effects system. to put it very simply, there's a big list of every effect that needs to take place on the next tick, and each tick the queue is iterated through, the next effect is checked against a list of every entity that died this turn to make sure that it should still take place (no dead mobs still getting their attacks off), and then it makes the event happen if appropriate. if not, it just gets tossed out. it's all super modular, so effects can pretty much be applied to everything. the same damage and targeting effects work for item use and traps going off, or an entity swinging their sword, for example. i made use of this new system by adding in some aoe scrolls, like mass function and mass healing. see below for blindness improving telepathy range, and nice new particle effects.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
</details>
|
|
||||||
</details>
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
use criterion::{ black_box, criterion_group, criterion_main, Criterion };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
/// Benchmarks methods from rltk used to desaturate non-visible tiles.
|
|
||||||
// Greyscale is significantly faster, but generally looks worse - the
|
|
||||||
// third alternative is directly setting the desaturated value, if it
|
|
||||||
// is known in advance.
|
|
||||||
fn nonvisible_benchmark(c: &mut Criterion) {
|
|
||||||
let bg = black_box(RGB::from_f32(0.4, 0.0, 0.0));
|
|
||||||
|
|
||||||
c.bench_function("rgb -> greyscale", |b| b.iter(|| bg.to_greyscale()));
|
|
||||||
c.bench_function("rgb -> desaturate", |b| b.iter(|| bg.desaturate()));
|
|
||||||
}
|
|
||||||
|
|
||||||
criterion_group!(benches, nonvisible_benchmark);
|
|
||||||
criterion_main!(benches);
|
|
||||||
20
changelog.md
20
changelog.md
|
|
@ -1,20 +0,0 @@
|
||||||
## v0.1.4
|
|
||||||
### added
|
|
||||||
- **overmap**: bare, but exists. player now starts on the overworld, and can move to local maps (like the old starting town) via >. can leave local maps back to the overmap by walking out of the map boundaries.
|
|
||||||
- **intrinsics**: speed, regeneration
|
|
||||||
- **damage types**: immunities, weaknesses, and resistances
|
|
||||||
- **full keyboard support**: examining and targeting can now be done via keyboard only
|
|
||||||
- **a config file** read at runtime, unfortunately not compatible with WASM builds yet
|
|
||||||
- **morgue files**: y/n prompt to write a morgue file on death to /morgue/foo.txt, or to the console for WASM builds
|
|
||||||
- **dungeon features**: just the basics so far. a grassy, forested treant room, some barracks, etc.
|
|
||||||
- **named maps**: "Town", "Dungeon"
|
|
||||||
- **map messages/hints**: "You hear <...>."
|
|
||||||
### changed
|
|
||||||
- **colour offsets** are now per-tile (and per-theme) instead of +-% globally. i.e. varying fg/bg offset on a per-tiletype basis
|
|
||||||
- **chatlog colours** are now consistent
|
|
||||||
### fixed
|
|
||||||
- negative starting mana
|
|
||||||
- status effects only ticking if mob turn aligned with turnclock turn
|
|
||||||
- map params not being saved on map transition
|
|
||||||
- mob turns not awaiting the particle queue (mobs moving around mid-animation)
|
|
||||||
- mobs not re-pathing if their path was blocked, causing traffic jams
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
a - insects A -
|
|
||||||
b - B -
|
|
||||||
c - chickens C -
|
|
||||||
d - canines D -
|
|
||||||
e - E -
|
|
||||||
f - felines F -
|
|
||||||
g - goblins G - gnomes
|
|
||||||
h - humanoids H -
|
|
||||||
i - I -
|
|
||||||
j - J -
|
|
||||||
k - kobolds K -
|
|
||||||
l - L -
|
|
||||||
m - M -
|
|
||||||
n - N -
|
|
||||||
o - orcs O - ogres
|
|
||||||
p - P -
|
|
||||||
q - quadrupeds Q -
|
|
||||||
r - rats R -
|
|
||||||
s - spiders S -
|
|
||||||
t - T -
|
|
||||||
u - horses U -
|
|
||||||
v - V -
|
|
||||||
w - W -
|
|
||||||
x - X -
|
|
||||||
y - Y -
|
|
||||||
z - zombies Z -
|
|
||||||
@ - humans
|
|
||||||
|
|
||||||
notes
|
|
||||||
1. zombie takes priority over the creature's original glyph
|
|
||||||
2. @ is for human-sized creatures - i.e. humans, changelings
|
|
||||||
h is used for everything else - i.e. dwarfs, halflings
|
|
||||||
3. purple is reserved for captain/leader entiites - i.e. orc captain
|
|
||||||
4. similar creatures use similar colours - i.e. little dog, dog, large dog
|
|
||||||
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
DETERMINING IF AN ATTACK HITS OR NOT:
|
|
||||||
|
|
||||||
1. Whenever someone makes an attack, a flat 1d20 attack roll is made.
|
|
||||||
2. A target number is generated, depending on the mode of attack, made
|
|
||||||
up of some combination of the following:
|
|
||||||
- ATTACKER'S HIT BONUSES:
|
|
||||||
- level
|
|
||||||
- any relevant str/dex attribute bonuses
|
|
||||||
- any relevant skill modifiers
|
|
||||||
- any to-hit modifiers from equipment
|
|
||||||
- any to-hit modifiers from status effects
|
|
||||||
- DEFENDER'S ARMOUR CLASS*:
|
|
||||||
- base armour class
|
|
||||||
- any relevant skill modifiers
|
|
||||||
- any ac modifiers from equipment
|
|
||||||
- any ac modifiers from status effects
|
|
||||||
- MISCELLANEOUS MODIFIERS:
|
|
||||||
- melee attacks always have a +1 bonus
|
|
||||||
- monsters gain a +10 bonus to-hit players
|
|
||||||
3. The attack roll is compared against the target number.
|
|
||||||
4. If the attack roll is less than the target, the attack hits.**
|
|
||||||
5. If an attack hits, it deals:
|
|
||||||
- a roll of the weapon's damage die
|
|
||||||
- plus any relevant attribute bonuses
|
|
||||||
- plus any relevant skill modifiers
|
|
||||||
- MINUS a roll of the defender's AC, if the AC is negative.
|
|
||||||
|
|
||||||
notes
|
|
||||||
* when AC is less than 0, it is treated as a random number from -1 to the value. e.g. -10 AC could be anywhere from -1 to -10.
|
|
||||||
** functionally identical to rolling 1d20 + to-hit, and needing to roll higher than the defender's AC. this system just ends up
|
|
||||||
being easier to work with when involving additional modifiers, as you don't need to decide if they're a to-hit bonus or an
|
|
||||||
AC modifier -- they just always get added/subtracted from the target number that must be rolled against.
|
|
||||||
|
|
||||||
Simple example, with positive AC:
|
|
||||||
- You have an AC of 4, and the monster has a +1 to-hit, with a 1d8 damage weapon.
|
|
||||||
- The monster must roll less than 10 (monster v. player) + 1 (to-hit) + 4 (ac) = 15 to hit you.
|
|
||||||
- The monster has a 70% hit chance.
|
|
||||||
- It rolls a 12, which is lower than 15, so it hits.
|
|
||||||
- It rolls 1d8 for damage, and gets a 6.
|
|
||||||
- You take 6 points of damage.
|
|
||||||
|
|
||||||
Complex example, with negative AC:
|
|
||||||
- You have an AC of -14, and the monster has a +3 to-hit, with a 1d8 damage weapon.
|
|
||||||
- The monster must roll less than 10 (monster v. player) + 3 (to-hit) - 1d14 (ac) to hit you.
|
|
||||||
- At best (AC rolls a 1), the monster must roll less than 12 to hit you. 55% hit chance.
|
|
||||||
- At worst (AC rolls a 14), the monster must roll less than -1 to hit you. Impossible.
|
|
||||||
- It rolls a 9, and your AC rolls a 2. 9 is less than 11 (10 + 3 - 2), so it hits.
|
|
||||||
- It rolls 1d8 for damage, and gets a 6.
|
|
||||||
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.
|
|
||||||
- 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.
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "human",
|
|
||||||
"allies": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "elf",
|
|
||||||
"allies": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "dwarf",
|
|
||||||
"allies": ["gnome"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "gnome",
|
|
||||||
"allies": ["dwarf"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "catfolk",
|
|
||||||
"allies": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "player",
|
|
||||||
"responses": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "mindless",
|
|
||||||
"responses": { "default": "attack" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "neutral",
|
|
||||||
"responses": { "default": "ignore", "hostile": "flee", "mindless": "flee", "carnivore": "flee" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "hostile",
|
|
||||||
"responses": { "default": "attack", "hostile": "ignore" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "herbivore",
|
|
||||||
"responses": { "default": "flee", "herbivores": "ignore" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "carnivore",
|
|
||||||
"responses": { "default": "ignore", "herbivores": "attack", "player": "attack", "neutral": "attack" }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
448
raws/items.json
448
raws/items.json
|
|
@ -1,448 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "potion_health",
|
|
||||||
"name": { "name": "potion of health", "plural": "potions of health" },
|
|
||||||
"renderable": { "glyph": "!", "fg": "#FF00FF", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "potion",
|
|
||||||
"weight": 1,
|
|
||||||
"value": 50,
|
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
|
||||||
"effects": { "heal": "4d4+2" },
|
|
||||||
"magic": { "class": "uncommon", "naming": "potion" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "potion_health_weak",
|
|
||||||
"name": { "name": "potion of lesser health", "plural": "potions of lesser health" },
|
|
||||||
"renderable": { "glyph": "!", "fg": "#FF00FF", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "potion",
|
|
||||||
"weight": 1,
|
|
||||||
"value": 25,
|
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
|
||||||
"effects": { "heal": "2d4+2" },
|
|
||||||
"magic": { "class": "uncommon", "naming": "potion" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "scroll_identify",
|
|
||||||
"name": { "name": "scroll of identify", "plural": "scrolls of identify" },
|
|
||||||
"renderable": { "glyph": "?", "fg": "#0FFFFF", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "scroll",
|
|
||||||
"weight": 0.5,
|
|
||||||
"value": 100,
|
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE", "IDENTIFY"],
|
|
||||||
"magic": { "class": "uncommon", "naming": "scroll" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "scroll_removecurse",
|
|
||||||
"name": { "name": "scroll of remove curse", "plural": "scrolls of remove curse" },
|
|
||||||
"renderable": { "glyph": "?", "fg": "#0FFFFF", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "scroll",
|
|
||||||
"weight": 0.5,
|
|
||||||
"value": 200,
|
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE", "REMOVE_CURSE"],
|
|
||||||
"magic": { "class": "rare", "naming": "scroll" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "scroll_health",
|
|
||||||
"name": { "name": "scroll of healing word", "plural": "scrolls of healing word" },
|
|
||||||
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "scroll",
|
|
||||||
"weight": 0.5,
|
|
||||||
"value": 50,
|
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
|
||||||
"effects": { "particle_line": "*;-;#53f06d;75.0;#f9ff9f;100.0", "ranged": "12", "heal": "1d4+2" },
|
|
||||||
"magic": { "class": "uncommon", "naming": "scroll" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "scroll_mass_health",
|
|
||||||
"name": { "name": "scroll of mass healing word", "plural": "scrolls of mass healing word" },
|
|
||||||
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "scroll",
|
|
||||||
"weight": 0.5,
|
|
||||||
"value": 200,
|
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
|
||||||
"effects": { "particle": "*;#53f06d;200.0", "ranged": "12", "aoe": "3", "heal": "1d4+2" },
|
|
||||||
"magic": { "class": "rare", "naming": "scroll" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "scroll_magicmissile",
|
|
||||||
"name": { "name": "scroll of magic missile", "plural": "scrolls of magic missile" },
|
|
||||||
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "scroll",
|
|
||||||
"weight": 0.5,
|
|
||||||
"value": 50,
|
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
|
||||||
"effects": { "particle_line": "*;-;#00b7ff;75.0;#f4fc83;100.0", "ranged": "12", "damage": "3d4+3;magic" },
|
|
||||||
"magic": { "class": "uncommon", "naming": "scroll" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "scroll_embers",
|
|
||||||
"name": { "name": "scroll of embers", "plural": "scrolls of embers" },
|
|
||||||
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "scroll",
|
|
||||||
"weight": 0.5,
|
|
||||||
"value": 100,
|
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
|
||||||
"effects": { "particle": "*;#FFA500;200.0", "ranged": "10", "damage": "4d6;fire", "aoe": "2" },
|
|
||||||
"magic": { "class": "uncommon", "naming": "scroll" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "scroll_fireball",
|
|
||||||
"name": { "name": "scroll of fireball", "plural": "scrolls of fireball" },
|
|
||||||
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "scroll",
|
|
||||||
"weight": 0.5,
|
|
||||||
"value": 200,
|
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
|
||||||
"effects": {
|
|
||||||
"particle_burst": "▓;*;~;#FFA500;#000000;500.0;#ffd381;60.0",
|
|
||||||
"ranged": "10",
|
|
||||||
"damage": "8d6;fire",
|
|
||||||
"aoe": "3"
|
|
||||||
},
|
|
||||||
"magic": { "class": "rare", "naming": "scroll" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "scroll_confusion",
|
|
||||||
"name": { "name": "scroll of confusion", "plural": "scrolls of confusion" },
|
|
||||||
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "scroll",
|
|
||||||
"weight": 0.5,
|
|
||||||
"value": 100,
|
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
|
||||||
"effects": { "particle_line": "*;-;#ad56a6;75.0;#cacaca;100.0", "ranged": "10", "confusion": "4" },
|
|
||||||
"magic": { "class": "uncommon", "naming": "scroll" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "scroll_mass_confusion",
|
|
||||||
"name": { "name": "scroll of mass confusion", "plural": "scrolls of mass confusion" },
|
|
||||||
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "scroll",
|
|
||||||
"weight": 0.5,
|
|
||||||
"value": 200,
|
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
|
|
||||||
"effects": { "particle": "*;#ad56a6;200.0", "ranged": "10", "aoe": "3", "confusion": "3" },
|
|
||||||
"magic": { "class": "veryrare", "naming": "scroll" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "scroll_magicmap",
|
|
||||||
"name": { "name": "scroll of magic mapping", "plural": "scrolls of magic mapping" },
|
|
||||||
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "scroll",
|
|
||||||
"weight": 0.5,
|
|
||||||
"value": 50,
|
|
||||||
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE", "MAGICMAP"],
|
|
||||||
"effects": {},
|
|
||||||
"magic": { "class": "common", "naming": "scroll" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_dagger",
|
|
||||||
"name": { "name": "dagger", "plural": "daggers" },
|
|
||||||
"renderable": { "glyph": ")", "fg": "#808080", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "weapon",
|
|
||||||
"weight": 1,
|
|
||||||
"value": 2,
|
|
||||||
"flags": ["EQUIP_MELEE"],
|
|
||||||
"equip": { "flag": "FINESSE", "damage": "1d4", "to_hit": 0 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_shortsword",
|
|
||||||
"name": { "name": "shortsword", "plural": "shortswords" },
|
|
||||||
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "weapon",
|
|
||||||
"weight": 2,
|
|
||||||
"value": 10,
|
|
||||||
"flags": ["EQUIP_MELEE"],
|
|
||||||
"equip": { "flag": "STRENGTH", "damage": "1d6", "to_hit": 0 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_rapier",
|
|
||||||
"name": { "name": "rapier", "plural": "rapiers" },
|
|
||||||
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "weapon",
|
|
||||||
"weight": 2,
|
|
||||||
"value": 10,
|
|
||||||
"flags": ["EQUIP_MELEE"],
|
|
||||||
"equip": { "flag": "FINESSE", "damage": "1d8", "to_hit": 0 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_pitchfork",
|
|
||||||
"name": { "name": "pitchfork", "plural": "pitchforks" },
|
|
||||||
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "weapon",
|
|
||||||
"weight": 2,
|
|
||||||
"value": 5,
|
|
||||||
"flags": ["EQUIP_MELEE"],
|
|
||||||
"equip": { "flag": "FINESSE", "damage": "1d6", "to_hit": 0 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_sickle",
|
|
||||||
"name": { "name": "sickle", "plural": "sickles" },
|
|
||||||
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "weapon",
|
|
||||||
"weight": 2,
|
|
||||||
"value": 5,
|
|
||||||
"flags": ["EQUIP_MELEE"],
|
|
||||||
"equip": { "flag": "FINESSE", "damage": "1d6", "to_hit": 0 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_handaxe",
|
|
||||||
"name": { "name": "handaxe", "plural": "handaxes" },
|
|
||||||
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "weapon",
|
|
||||||
"weight": 2,
|
|
||||||
"value": 5,
|
|
||||||
"flags": ["EQUIP_MELEE"],
|
|
||||||
"equip": { "flag": "STRENGTH", "damage": "1d6", "to_hit": 0 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_longsword",
|
|
||||||
"name": { "name": "longsword", "plural": "longswords" },
|
|
||||||
"renderable": { "glyph": ")", "fg": "#FFF8DC", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "weapon",
|
|
||||||
"weight": 3,
|
|
||||||
"value": 15,
|
|
||||||
"flags": ["EQUIP_MELEE"],
|
|
||||||
"equip": { "flag": "STRENGTH", "damage": "1d8", "to_hit": 0 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_smallshield",
|
|
||||||
"name": { "name": "buckler", "plural": "bucklers" },
|
|
||||||
"renderable": { "glyph": "[", "fg": "#808080", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "armour",
|
|
||||||
"weight": 2,
|
|
||||||
"value": 5,
|
|
||||||
"flags": ["EQUIP_SHIELD"],
|
|
||||||
"effects": { "ac": "1" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_mediumshield",
|
|
||||||
"name": { "name": "medium shield", "plural": "medium shields" },
|
|
||||||
"renderable": { "glyph": "[", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "armour",
|
|
||||||
"weight": 6,
|
|
||||||
"value": 10,
|
|
||||||
"flags": ["EQUIP_SHIELD"],
|
|
||||||
"effects": { "ac": "2", "to_hit": "-1" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_largeshield",
|
|
||||||
"name": { "name": "large shield", "plural": "large shields" },
|
|
||||||
"renderable": { "glyph": "[", "fg": "#FFF8DC", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "armour",
|
|
||||||
"weight": 12,
|
|
||||||
"value": 35,
|
|
||||||
"flags": ["EQUIP_SHIELD"],
|
|
||||||
"effects": { "ac": "3", "to_hit": "-2" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_body_weakleather",
|
|
||||||
"name": { "name": "leather jacket", "plural": "leather jackets" },
|
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "armour",
|
|
||||||
"weight": 8,
|
|
||||||
"value": 5,
|
|
||||||
"flags": ["EQUIP_BODY"],
|
|
||||||
"effects": { "ac": "1" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_body_leather",
|
|
||||||
"name": { "name": "leather chestpiece", "plural": "leather chestpiece" },
|
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "armour",
|
|
||||||
"weight": 10,
|
|
||||||
"value": 10,
|
|
||||||
"flags": ["EQUIP_BODY"],
|
|
||||||
"effects": { "ac": "2" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_body_studdedleather",
|
|
||||||
"name": { "name": "studded leather chestpiece", "plural": "studded leather chestpieces" },
|
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "armour",
|
|
||||||
"weight": 13,
|
|
||||||
"value": 45,
|
|
||||||
"flags": ["EQUIP_BODY"],
|
|
||||||
"effects": { "ac": "3" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_body_ringmail_o",
|
|
||||||
"name": { "name": "orcish ring mail", "plural": "orcish ring mail" },
|
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "armour",
|
|
||||||
"weight": 45,
|
|
||||||
"value": 50,
|
|
||||||
"flags": ["EQUIP_BODY"],
|
|
||||||
"effects": { "ac": "3" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_body_ringmail",
|
|
||||||
"name": { "name": "ring mail", "plural": "ring mail" },
|
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "armour",
|
|
||||||
"weight": 45,
|
|
||||||
"value": 70,
|
|
||||||
"flags": ["EQUIP_BODY"],
|
|
||||||
"effects": { "ac": "4" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_head_leather",
|
|
||||||
"name": { "name": "leather cap", "plural": "leather caps" },
|
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "armour",
|
|
||||||
"weight": 2,
|
|
||||||
"value": 10,
|
|
||||||
"flags": ["EQUIP_HEAD"],
|
|
||||||
"effects": { "ac": "1" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_head_elvish",
|
|
||||||
"name": { "name": "elvish leather helm", "plural": "elvish leather helms" },
|
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "armour",
|
|
||||||
"weight": 2,
|
|
||||||
"value": 25,
|
|
||||||
"flags": ["EQUIP_HEAD"],
|
|
||||||
"effects": { "ac": "2" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_head_o",
|
|
||||||
"name": { "name": "orcish helm", "plural": "orcish helm" },
|
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "armour",
|
|
||||||
"weight": 6,
|
|
||||||
"value": 25,
|
|
||||||
"flags": ["EQUIP_HEAD"],
|
|
||||||
"effects": { "ac": "2" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_head_iron",
|
|
||||||
"name": { "name": "iron helm", "plural": "iron helm" },
|
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "armour",
|
|
||||||
"weight": 10,
|
|
||||||
"value": 45,
|
|
||||||
"flags": ["EQUIP_HEAD"],
|
|
||||||
"effects": { "ac": "3" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_feet_leather",
|
|
||||||
"name": { "name": "leather shoes", "plural": "leather shoes" },
|
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "armour",
|
|
||||||
"weight": 2,
|
|
||||||
"value": 10,
|
|
||||||
"flags": ["EQUIP_FEET"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_feet_elvish",
|
|
||||||
"name": { "name": "elvish leather shoes", "plural": "elvish leather shoes" },
|
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "armour",
|
|
||||||
"weight": 2,
|
|
||||||
"value": 25,
|
|
||||||
"flags": ["EQUIP_FEET"],
|
|
||||||
"effects": { "ac": "1" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_feet_o",
|
|
||||||
"name": { "name": "orcish boots", "plural": "orcish boots" },
|
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "armour",
|
|
||||||
"weight": 6,
|
|
||||||
"value": 25,
|
|
||||||
"flags": ["EQUIP_FEET"],
|
|
||||||
"effects": { "ac": "1" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_feet_iron",
|
|
||||||
"name": { "name": "iron boots", "plural": "iron boots" },
|
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "armour",
|
|
||||||
"weight": 10,
|
|
||||||
"value": 45,
|
|
||||||
"flags": ["EQUIP_FEET"],
|
|
||||||
"effects": { "ac": "2" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_neck_protection",
|
|
||||||
"name": { "name": "amulet of protection", "plural": "amulets of protection" },
|
|
||||||
"renderable": { "glyph": "\"", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "amulet",
|
|
||||||
"weight": 1,
|
|
||||||
"value": 200,
|
|
||||||
"flags": ["EQUIP_NECK"],
|
|
||||||
"effects": { "ac": "1" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "equip_back_protection",
|
|
||||||
"name": { "name": "cloak of protection", "plural": "cloaks of protection" },
|
|
||||||
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "armour",
|
|
||||||
"weight": 1,
|
|
||||||
"value": 200,
|
|
||||||
"flags": ["EQUIP_BACK"],
|
|
||||||
"effects": { "ac": "1" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "wand_magicmissile",
|
|
||||||
"name": { "name": "wand of magic missile", "plural": "wands of magic missile" },
|
|
||||||
"renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "wand",
|
|
||||||
"weight": 2,
|
|
||||||
"value": 100,
|
|
||||||
"flags": ["CHARGES"],
|
|
||||||
"effects": { "ranged": "12", "damage": "3d4+3;magic" },
|
|
||||||
"magic": { "class": "uncommon", "naming": "wand" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "wand_fireball",
|
|
||||||
"name": { "name": "wand of fireball", "plural": "wands of fireball" },
|
|
||||||
"renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "wand",
|
|
||||||
"weight": 2,
|
|
||||||
"value": 300,
|
|
||||||
"flags": ["CHARGES"],
|
|
||||||
"effects": { "ranged": "10", "damage": "8d6;fire", "aoe": "3" },
|
|
||||||
"magic": { "class": "rare", "naming": "wand" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "wand_confusion",
|
|
||||||
"name": { "name": "wand of confusion", "plural": "wands of confusion" },
|
|
||||||
"renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "wand",
|
|
||||||
"weight": 2,
|
|
||||||
"value": 200,
|
|
||||||
"flags": ["CHARGES"],
|
|
||||||
"effects": { "ranged": "10", "confusion": "4" },
|
|
||||||
"magic": { "class": "uncommon", "naming": "wand" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "wand_digging",
|
|
||||||
"name": { "name": "wand of digging", "plural": "wands of digging" },
|
|
||||||
"renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "wand",
|
|
||||||
"weight": 2,
|
|
||||||
"value": 300,
|
|
||||||
"flags": ["CHARGES", "DIGGER"],
|
|
||||||
"effects": { "ranged": "10" },
|
|
||||||
"magic": { "class": "rare", "naming": "wand" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "food_rations",
|
|
||||||
"name": { "name": "rations", "plural": "rations" },
|
|
||||||
"renderable": { "glyph": "%", "fg": "#FFA07A", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "comestible",
|
|
||||||
"weight": 1,
|
|
||||||
"value": 1,
|
|
||||||
"flags": ["FOOD", "CONSUMABLE", "STACKABLE"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "food_apple",
|
|
||||||
"name": { "name": "apple", "plural": "apples" },
|
|
||||||
"renderable": { "glyph": "%", "fg": "#00FF00", "bg": "#000000", "order": 2 },
|
|
||||||
"class": "comestible",
|
|
||||||
"weight": 0.5,
|
|
||||||
"value": 1,
|
|
||||||
"flags": ["FOOD", "CONSUMABLE", "STACKABLE"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "animal",
|
|
||||||
"table": [
|
|
||||||
{ "id": "animal_hide", "weight": 1},
|
|
||||||
{ "id": "animal_meat", "weight": 1}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
513
raws/mobs.json
513
raws/mobs.json
|
|
@ -1,513 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "npc_barkeep",
|
|
||||||
"name": "barkeep",
|
|
||||||
"renderable": { "glyph": "@", "fg": "#EE82EE", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["NEUTRAL", "IS_HUMAN"],
|
|
||||||
"vision_range": 4,
|
|
||||||
"quips": ["Drink?", "Something to eat?", "Don't go out on an empty stomach."]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "npc_townsperson",
|
|
||||||
"name": "townsperson",
|
|
||||||
"renderable": { "glyph": "@", "fg": "#9fa86c", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["NEUTRAL", "RANDOM_PATH", "IS_HUMAN"],
|
|
||||||
"vision_range": 4,
|
|
||||||
"quips": ["Hello!", "Good morning.", "<a quiet complaint about chores>"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "npc_drunk",
|
|
||||||
"name": "drunk",
|
|
||||||
"renderable": { "glyph": "@", "fg": "#a0a83c", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["NEUTRAL", "IS_HUMAN"],
|
|
||||||
"vision_range": 4,
|
|
||||||
"quips": ["Hic!", "H-Hic'.", "Get me 'nother, would you?"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "npc_fisher",
|
|
||||||
"name": "fisher",
|
|
||||||
"renderable": { "glyph": "@", "fg": "#3ca3a8", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["NEUTRAL", "IS_HUMAN"],
|
|
||||||
"vision_range": 4,
|
|
||||||
"quips": ["Hey."]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "npc_dockworker",
|
|
||||||
"name": "dock worker",
|
|
||||||
"renderable": { "glyph": "@", "fg": "#68d8de", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["NEUTRAL", "IS_HUMAN"],
|
|
||||||
"vision_range": 4,
|
|
||||||
"quips": ["No boat for a few days.", "Not much for us to do."]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "npc_priest",
|
|
||||||
"name": "priest",
|
|
||||||
"renderable": { "glyph": "@", "fg": "#FFFFFF", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["NEUTRAL", "IS_HUMAN"],
|
|
||||||
"vision_range": 4,
|
|
||||||
"quips": ["Light's givings.", "<a quiet prayer>", "Bless you."]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "npc_miner",
|
|
||||||
"name": "miner",
|
|
||||||
"renderable": { "glyph": "@", "fg": "#946123", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["NEUTRAL", "IS_HUMAN"],
|
|
||||||
"vision_range": 4,
|
|
||||||
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d8" }],
|
|
||||||
"quips": ["You're not borrowing my pick."]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "npc_guard",
|
|
||||||
"name": "smalltown guard",
|
|
||||||
"renderable": { "glyph": "@", "fg": "#034efc", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["NEUTRAL", "RANDOM_PATH", "IS_HUMAN"],
|
|
||||||
"level": 2,
|
|
||||||
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d8" }],
|
|
||||||
"equipped": ["equip_shortsword", "equip_body_leather"],
|
|
||||||
"quips": ["You wont catch me down the mine.", "Staying out of trouble?"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "rat",
|
|
||||||
"name": "rat",
|
|
||||||
"renderable": { "glyph": "r", "fg": "#aa6000", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": [],
|
|
||||||
"bac": 6,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }],
|
|
||||||
"loot": { "table": "food", "chance": 0.1 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "chicken",
|
|
||||||
"name": "chicken",
|
|
||||||
"renderable": { "glyph": "c", "fg": "#BB6000", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["HERBIVORE"],
|
|
||||||
"bac": 8,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "deer_little",
|
|
||||||
"name": "fawn",
|
|
||||||
"renderable": { "glyph": "q", "fg": "#a57037", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["HERBIVORE"],
|
|
||||||
"bac": 8,
|
|
||||||
"attacks": [{ "name": "kicks", "hit_bonus": 0, "damage": "1d2" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "sheep_little",
|
|
||||||
"name": "lamb",
|
|
||||||
"renderable": { "glyph": "q", "fg": "#e7e7e7", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["HERBIVORE", "SMALL_GROUP"],
|
|
||||||
"bac": 10,
|
|
||||||
"attacks": [{ "name": "kicks", "hit_bonus": 0, "damage": "1d2" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "chicken_little",
|
|
||||||
"name": "chick",
|
|
||||||
"renderable": { "glyph": "c", "fg": "#fae478", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["HERBIVORE"],
|
|
||||||
"bac": 10,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "horse_little",
|
|
||||||
"name": "pony",
|
|
||||||
"renderable": { "glyph": "u", "fg": "#b36c29", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["HERBIVORE", "MULTIATTACK"],
|
|
||||||
"level": 3,
|
|
||||||
"bac": 6,
|
|
||||||
"speed": 16,
|
|
||||||
"attacks": [
|
|
||||||
{ "name": "kicks", "hit_bonus": 0, "damage": "1d6" },
|
|
||||||
{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }
|
|
||||||
],
|
|
||||||
"quips": ["<a disgruntled neigh>"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "horse",
|
|
||||||
"name": "horse",
|
|
||||||
"renderable": { "glyph": "u", "fg": "#744d29", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["MULTIATTACK"],
|
|
||||||
"level": 5,
|
|
||||||
"bac": 5,
|
|
||||||
"speed": 20,
|
|
||||||
"attacks": [
|
|
||||||
{ "name": "kicks", "hit_bonus": 0, "damage": "1d8" },
|
|
||||||
{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "horse_large",
|
|
||||||
"name": "warhorse",
|
|
||||||
"renderable": { "glyph": "u", "fg": "#8a3520", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["MULTIATTACK"],
|
|
||||||
"level": 7,
|
|
||||||
"bac": 4,
|
|
||||||
"speed": 24,
|
|
||||||
"attacks": [
|
|
||||||
{ "name": "kicks", "hit_bonus": 0, "damage": "1d10" },
|
|
||||||
{ "name": "bites", "hit_bonus": 0, "damage": "1d4" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "rat_giant",
|
|
||||||
"name": "giant rat",
|
|
||||||
"renderable": { "glyph": "r", "fg": "#bb8000", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["SMALL_GROUP"],
|
|
||||||
"level": 1,
|
|
||||||
"bac": 7,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }],
|
|
||||||
"loot": { "table": "scrolls", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "dog_little",
|
|
||||||
"name": "little dog",
|
|
||||||
"renderable": { "glyph": "d", "fg": "#FFFFFF", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["NEUTRAL"],
|
|
||||||
"level": 2,
|
|
||||||
"bac": 6,
|
|
||||||
"speed": 18,
|
|
||||||
"quips": ["<woof!>", "<bark!>", "<grrr..>"],
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d6" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "dog",
|
|
||||||
"name": "dog",
|
|
||||||
"renderable": { "glyph": "d", "fg": "#EEEEEE", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": [],
|
|
||||||
"level": 4,
|
|
||||||
"bac": 5,
|
|
||||||
"speed": 16,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d6" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "dog_large",
|
|
||||||
"name": "large dog",
|
|
||||||
"renderable": { "glyph": "d", "fg": "#DDDDDD", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": [],
|
|
||||||
"level": 6,
|
|
||||||
"bac": 4,
|
|
||||||
"speed": 15,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "2d4" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "gnome",
|
|
||||||
"name": "gnome",
|
|
||||||
"renderable": { "glyph": "G", "fg": "#AA5500", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["SMALL_GROUP", "IS_GNOME"],
|
|
||||||
"level": 1,
|
|
||||||
"speed": 6,
|
|
||||||
"attacks": [{ "name": "claws", "hit_bonus": 0, "damage": "1d6" }],
|
|
||||||
"loot": { "table": "wands", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "zombie_gnome",
|
|
||||||
"name": "gnome zombie",
|
|
||||||
"renderable": { "glyph": "z", "fg": "#AA5500", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["MINDLESS"],
|
|
||||||
"level": 1,
|
|
||||||
"speed": 6,
|
|
||||||
"vision_range": 12,
|
|
||||||
"attacks": [{ "name": "claws", "hit_bonus": 0, "damage": "1d4" }],
|
|
||||||
"loot": { "table": "wands", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "goblin",
|
|
||||||
"name": "goblin",
|
|
||||||
"renderable": { "glyph": "g", "fg": "#00FF00", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": [],
|
|
||||||
"level": 1,
|
|
||||||
"speed": 9,
|
|
||||||
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d4" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "kobold",
|
|
||||||
"name": "kobold",
|
|
||||||
"renderable": { "glyph": "k", "fg": "#AA5500", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": [],
|
|
||||||
"level": 1,
|
|
||||||
"speed": 6,
|
|
||||||
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d4" }],
|
|
||||||
"loot": { "table": "food", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "zombie_kobold",
|
|
||||||
"name": "kobold zombie",
|
|
||||||
"renderable": { "glyph": "z", "fg": "#AA5500", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["MINDLESS"],
|
|
||||||
"level": 1,
|
|
||||||
"speed": 6,
|
|
||||||
"vision_range": 12,
|
|
||||||
"attacks": [{ "name": "claws", "hit_bonus": 0, "damage": "1d4" }],
|
|
||||||
"loot": { "table": "scrolls", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "kobold_large",
|
|
||||||
"name": "large kobold",
|
|
||||||
"renderable": { "glyph": "k", "fg": "#70461b", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": [],
|
|
||||||
"level": 1,
|
|
||||||
"speed": 6,
|
|
||||||
"vision_range": 12,
|
|
||||||
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d6" }],
|
|
||||||
"loot": { "table": "food", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "zombie_orc",
|
|
||||||
"name": "orc zombie",
|
|
||||||
"renderable": { "glyph": "z", "fg": "#dbd830", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["MINDLESS"],
|
|
||||||
"level": 2,
|
|
||||||
"bac": 9,
|
|
||||||
"speed": 6,
|
|
||||||
"vision_range": 12,
|
|
||||||
"attacks": [{ "name": "claws", "hit_bonus": 0, "damage": "1d6" }],
|
|
||||||
"loot": { "table": "potions", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "dwarf",
|
|
||||||
"name": "dwarf",
|
|
||||||
"renderable": { "glyph": "h", "fg": "#d61b1b", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["IS_DWARF"],
|
|
||||||
"level": 2,
|
|
||||||
"bac": 10,
|
|
||||||
"speed": 6,
|
|
||||||
"attacks": [{ "name": "hacks", "hit_bonus": 0, "damage": "1d8" }],
|
|
||||||
"equipped": ["equip_feet_iron"],
|
|
||||||
"loot": { "table": "equipment", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "zombie_dwarf",
|
|
||||||
"name": "dwarf zombie",
|
|
||||||
"renderable": { "glyph": "z", "fg": "#d61b1b", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["MINDLESS"],
|
|
||||||
"level": 2,
|
|
||||||
"bac": 9,
|
|
||||||
"speed": 6,
|
|
||||||
"vision_range": 12,
|
|
||||||
"attacks": [{ "name": "claws", "hit_bonus": 0, "damage": "1d6" }],
|
|
||||||
"loot": { "table": "equipment", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "kobold_captain",
|
|
||||||
"name": "kobold captain",
|
|
||||||
"renderable": { "glyph": "k", "fg": "#9331ac", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": [],
|
|
||||||
"level": 2,
|
|
||||||
"speed": 6,
|
|
||||||
"vision_range": 12,
|
|
||||||
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "2d4" }],
|
|
||||||
"loot": { "table": "food", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "spider_cave",
|
|
||||||
"name": "cave spider",
|
|
||||||
"renderable": { "glyph": "s", "fg": "#6b6b6b", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["SMALL_GROUP"],
|
|
||||||
"level": 1,
|
|
||||||
"bac": 3,
|
|
||||||
"speed": 12,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }],
|
|
||||||
"loot": { "table": "scrolls", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "ant_worker",
|
|
||||||
"name": "worker ant",
|
|
||||||
"renderable": { "glyph": "a", "fg": "#ca7631", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["SMALL_GROUP"],
|
|
||||||
"level": 2,
|
|
||||||
"bac": 3,
|
|
||||||
"speed": 18,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d4" }],
|
|
||||||
"loot": { "table": "food", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "ant_soldier",
|
|
||||||
"name": "soldier ant",
|
|
||||||
"renderable": { "glyph": "a", "fg": "#ca3f26", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["SMALL_GROUP", "POISON_RES"],
|
|
||||||
"level": 3,
|
|
||||||
"bac": 3,
|
|
||||||
"speed": 18,
|
|
||||||
"attacks": [
|
|
||||||
{ "name": "bites", "hit_bonus": 0, "damage": "2d4" },
|
|
||||||
{ "name": "stings", "hit_bonus": 0, "damage": "3d4;poison" }
|
|
||||||
],
|
|
||||||
"loot": { "table": "food", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "caterpillar_cave",
|
|
||||||
"name": "caterpillar",
|
|
||||||
"renderable": { "glyph": "a", "fg": "#6b6b6b", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["SMALL_GROUP"],
|
|
||||||
"level": 1,
|
|
||||||
"bac": 3,
|
|
||||||
"speed": 9,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }],
|
|
||||||
"loot": { "table": "food", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "caterpillar_giant",
|
|
||||||
"name": "giant caterpillar",
|
|
||||||
"renderable": { "glyph": "a", "fg": "#b9aeae", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["SMALL_GROUP"],
|
|
||||||
"level": 2,
|
|
||||||
"bac": 7,
|
|
||||||
"speed": 9,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d6" }],
|
|
||||||
"loot": { "table": "food", "chance": 0.10 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "jackal",
|
|
||||||
"name": "jackal",
|
|
||||||
"renderable": { "glyph": "d", "fg": "#AA5500", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["CARNIVORE", "SMALL_GROUP"],
|
|
||||||
"bac": 7,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "fox",
|
|
||||||
"name": "fox",
|
|
||||||
"renderable": { "glyph": "d", "fg": "#FF0000", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["CARNIVORE"],
|
|
||||||
"bac": 7,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "coyote",
|
|
||||||
"name": "coyote",
|
|
||||||
"renderable": { "glyph": "d", "fg": "#6E3215", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["CARNIVORE", "SMALL_GROUP"],
|
|
||||||
"level": 1,
|
|
||||||
"bac": 7,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d4" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "wolf",
|
|
||||||
"name": "wolf",
|
|
||||||
"renderable": { "glyph": "d", "fg": "#5E4225", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["CARNIVORE"],
|
|
||||||
"level": 5,
|
|
||||||
"bac": 4,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "2d4" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "goblin_chieftain",
|
|
||||||
"name": "goblin chieftain",
|
|
||||||
"renderable": { "glyph": "g", "fg": "#9331ac", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": [],
|
|
||||||
"level": 2,
|
|
||||||
"speed": 9,
|
|
||||||
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d8" }],
|
|
||||||
"loot": { "table": "wands", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "orc",
|
|
||||||
"name": "orc",
|
|
||||||
"renderable": { "glyph": "o", "fg": "#00FF00", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["SMALL_GROUP"],
|
|
||||||
"level": 1,
|
|
||||||
"speed": 9,
|
|
||||||
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d6" }],
|
|
||||||
"loot": { "table": "equipment", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "orc_hill",
|
|
||||||
"name": "hill orc",
|
|
||||||
"renderable": { "glyph": "o", "fg": "#dbd830", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["LARGE_GROUP"],
|
|
||||||
"level": 2,
|
|
||||||
"speed": 9,
|
|
||||||
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d6" }],
|
|
||||||
"loot": { "table": "equipment", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "orc_captain",
|
|
||||||
"name": "orc captain",
|
|
||||||
"renderable": { "glyph": "o", "fg": "#9331ac", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["MULTIATTACK"],
|
|
||||||
"level": 5,
|
|
||||||
"speed": 5,
|
|
||||||
"attacks": [
|
|
||||||
{ "name": "hits", "hit_bonus": 0, "damage": "2d4" },
|
|
||||||
{ "name": "hits", "hit_bonus": 0, "damage": "2d4" }
|
|
||||||
],
|
|
||||||
"loot": { "table": "equipment", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "warg",
|
|
||||||
"name": "warg",
|
|
||||||
"renderable": { "glyph": "d", "fg": "#8b7164", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["SMALL_GROUP"],
|
|
||||||
"level": 7,
|
|
||||||
"bac": 4,
|
|
||||||
"speed": 12,
|
|
||||||
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "2d6" }],
|
|
||||||
"loot": { "table": "food", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "jaguar",
|
|
||||||
"name": "jaguar",
|
|
||||||
"renderable": { "glyph": "f", "fg": "#d3b947", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["MULTIATTACK"],
|
|
||||||
"level": 4,
|
|
||||||
"bac": 6,
|
|
||||||
"speed": 15,
|
|
||||||
"attacks": [
|
|
||||||
{ "name": "claws", "hit_bonus": 0, "damage": "1d4" },
|
|
||||||
{ "name": "claws", "hit_bonus": 0, "damage": "1d4" },
|
|
||||||
{ "name": "bites", "hit_bonus": 0, "damage": "1d8" }
|
|
||||||
],
|
|
||||||
"loot": { "table": "food", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "lynx",
|
|
||||||
"name": "lynx",
|
|
||||||
"renderable": { "glyph": "f", "fg": "#b5d347", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["MULTIATTACK"],
|
|
||||||
"level": 5,
|
|
||||||
"bac": 6,
|
|
||||||
"speed": 15,
|
|
||||||
"attacks": [
|
|
||||||
{ "name": "claws", "hit_bonus": 0, "damage": "1d4" },
|
|
||||||
{ "name": "claws", "hit_bonus": 0, "damage": "1d4" },
|
|
||||||
{ "name": "bites", "hit_bonus": 0, "damage": "1d10" }
|
|
||||||
],
|
|
||||||
"loot": { "table": "food", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "panther",
|
|
||||||
"name": "panther",
|
|
||||||
"renderable": { "glyph": "f", "fg": "#58554e", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["MULTIATTACK"],
|
|
||||||
"level": 5,
|
|
||||||
"bac": 6,
|
|
||||||
"speed": 15,
|
|
||||||
"attacks": [
|
|
||||||
{ "name": "claws", "hit_bonus": 0, "damage": "1d6" },
|
|
||||||
{ "name": "claws", "hit_bonus": 0, "damage": "1d6" },
|
|
||||||
{ "name": "bites", "hit_bonus": 0, "damage": "1d10" }
|
|
||||||
],
|
|
||||||
"loot": { "table": "food", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "ogre",
|
|
||||||
"name": "ogre",
|
|
||||||
"renderable": { "glyph": "O", "fg": "#10A70d", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["SMALL_GROUP"],
|
|
||||||
"level": 5,
|
|
||||||
"bac": 5,
|
|
||||||
"speed": 10,
|
|
||||||
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "2d5" }],
|
|
||||||
"loot": { "table": "food", "chance": 0.05 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "treant_small",
|
|
||||||
"name": "treant sapling",
|
|
||||||
"renderable": { "glyph": "♠️", "fg": "#10570d", "bg": "#000000", "order": 1 },
|
|
||||||
"flags": ["LARGE_GROUP", "GREEN_BLOOD", "FIRE_WEAK"],
|
|
||||||
"level": 2,
|
|
||||||
"bac": 12,
|
|
||||||
"speed": 3,
|
|
||||||
"attacks": [{ "name": "lashes", "hit_bonus": 4, "damage": "1d8" }],
|
|
||||||
"loot": { "table": "scrolls", "chance": 0.05 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "door",
|
|
||||||
"name": "door",
|
|
||||||
"renderable": { "glyph": "+", "fg": "#00FFFF", "bg": "#000000", "order": 2 },
|
|
||||||
"flags": ["DOOR"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "prop_altar",
|
|
||||||
"name": "altar",
|
|
||||||
"renderable": { "glyph": "_", "fg": "#FFFFFF", "bg": "#000000", "order": 2 },
|
|
||||||
"flags": ["ENTRY_TRIGGER"],
|
|
||||||
"effects": { "heal": "8d8" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "prop_keg",
|
|
||||||
"name": "keg",
|
|
||||||
"renderable": { "glyph": "φ", "fg": "#AAAAAA", "bg": "#000000", "order": 2 },
|
|
||||||
"flags": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "prop_table",
|
|
||||||
"name": "table",
|
|
||||||
"renderable": { "glyph": "-", "fg": "#AAAAAA", "bg": "#000000", "order": 2 },
|
|
||||||
"flags": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "prop_hay",
|
|
||||||
"name": "hay",
|
|
||||||
"renderable": { "glyph": "%", "fg": "#c7ad39", "bg": "#000000", "order": 2 },
|
|
||||||
"flags": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "prop_statue",
|
|
||||||
"name": "statue",
|
|
||||||
"renderable": { "glyph": "@", "fg": "#ffffff", "bg": "#000000", "order": 2 },
|
|
||||||
"flags": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "prop_bed",
|
|
||||||
"name": "bed",
|
|
||||||
"renderable": { "glyph": "=", "fg": "#AAAAAA", "bg": "#000000", "order": 2 },
|
|
||||||
"flags": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "prop_chair",
|
|
||||||
"name": "chair",
|
|
||||||
"renderable": { "glyph": "└", "fg": "#AAAAAA", "bg": "#000000", "order": 2 },
|
|
||||||
"flags": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "prop_candle",
|
|
||||||
"name": "candle",
|
|
||||||
"renderable": { "glyph": "Ä", "fg": "#FFA500", "bg": "#000000", "order": 2 },
|
|
||||||
"flags": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "trap_bear",
|
|
||||||
"name": "bear trap",
|
|
||||||
"renderable": { "glyph": "^", "fg": "#e6e6e6", "bg": "#000000", "order": 2 },
|
|
||||||
"flags": ["HIDDEN", "ENTRY_TRIGGER", "SINGLE_ACTIVATION"],
|
|
||||||
"effects": { "damage": "2d4" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "trap_mini_mine",
|
|
||||||
"name": "mini-mine",
|
|
||||||
"renderable": { "glyph": "^", "fg": "#ff1e00", "bg": "#000000", "order": 2 },
|
|
||||||
"flags": ["ENTRY_TRIGGER", "SINGLE_ACTIVATION"],
|
|
||||||
"effects": { "damage": "2d4", "aoe": "3" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "trap_stonefall",
|
|
||||||
"name": "stonefall trap",
|
|
||||||
"renderable": { "glyph": "^", "fg": "#beb5a7", "bg": "#000000", "order": 2 },
|
|
||||||
"flags": ["HIDDEN", "ENTRY_TRIGGER", "SINGLE_ACTIVATION"],
|
|
||||||
"effects": { "damage": "2d10" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "trap_confusion",
|
|
||||||
"name": "magic trap",
|
|
||||||
"renderable": { "glyph": "^", "fg": "#df07df", "bg": "#000000", "order": 2 },
|
|
||||||
"flags": ["HIDDEN", "ENTRY_TRIGGER", "SINGLE_ACTIVATION"],
|
|
||||||
"effects": { "confusion": "3" }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "equipment",
|
|
||||||
"table": [
|
|
||||||
{ "id": "equip_pitchfork", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "equip_sickle", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "equip_handaxe", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "equip_dagger", "weight": 3, "difficulty": 1},
|
|
||||||
{ "id": "equip_shortsword", "weight": 3, "difficulty": 1},
|
|
||||||
{ "id": "equip_rapier", "weight": 2, "difficulty": 1},
|
|
||||||
{ "id": "equip_smallshield", "weight": 3, "difficulty": 1},
|
|
||||||
{ "id": "equip_mediumshield", "weight": 2, "difficulty": 1},
|
|
||||||
{ "id": "equip_body_weakleather", "weight": 4, "difficulty": 1},
|
|
||||||
{ "id": "equip_head_leather", "weight": 4, "difficulty": 1},
|
|
||||||
{ "id": "equip_feet_leather", "weight": 4, "difficulty": 1},
|
|
||||||
{ "id": "equip_body_leather", "weight": 4, "difficulty": 1},
|
|
||||||
{ "id": "equip_neck_protection", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "equip_back_protection", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "equip_body_studdedleather","weight": 3, "difficulty": 2},
|
|
||||||
{ "id": "equip_head_o", "weight": 4, "difficulty": 2},
|
|
||||||
{ "id": "equip_longsword", "weight": 2, "difficulty": 3},
|
|
||||||
{ "id": "equip_head_elvish", "weight": 3, "difficulty": 3},
|
|
||||||
{ "id": "equip_feet_elvish", "weight": 3, "difficulty": 3},
|
|
||||||
{ "id": "equip_body_ringmail_o", "weight": 4, "difficulty": 3},
|
|
||||||
{ "id": "equip_feet_o", "weight": 4, "difficulty": 3},
|
|
||||||
{ "id": "equip_body_ringmail", "weight": 2, "difficulty": 3},
|
|
||||||
{ "id": "equip_largeshield", "weight": 2, "difficulty": 3},
|
|
||||||
{ "id": "equip_head_iron", "weight": 3, "difficulty": 4},
|
|
||||||
{ "id": "equip_feet_iron", "weight": 2, "difficulty": 4}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "villager_equipment",
|
|
||||||
"table": [
|
|
||||||
{ "id": "equip_pitchfork", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "equip_sickle", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "equip_handaxe", "weight": 1, "difficulty": 1}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "potions",
|
|
||||||
"table": [
|
|
||||||
{ "id": "potion_health_weak", "weight": 6, "difficulty": 1},
|
|
||||||
{ "id": "potion_health", "weight": 3, "difficulty": 1}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "scrolls",
|
|
||||||
"table": [
|
|
||||||
{ "id": "scroll_identify", "weight": 3, "difficulty": 1},
|
|
||||||
{ "id": "scroll_removecurse", "weight": 2, "difficulty": 1},
|
|
||||||
{ "id": "scroll_confusion", "weight": 2, "difficulty": 1},
|
|
||||||
{ "id": "scroll_magicmap", "weight": 2, "difficulty": 1},
|
|
||||||
{ "id": "scroll_embers", "weight": 2, "difficulty": 2},
|
|
||||||
{ "id": "scroll_health", "weight": 2, "difficulty": 2},
|
|
||||||
{ "id": "scroll_fireball", "weight": 2, "difficulty": 2},
|
|
||||||
{ "id": "scroll_mass_health", "weight": 1, "difficulty": 2},
|
|
||||||
{ "id": "scroll_mass_confusion", "weight": 1, "difficulty": 3}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "wands",
|
|
||||||
"table": [
|
|
||||||
{ "id": "wand_magicmissile", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "wand_confusion", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "wand_digging", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "wand_fireball", "weight": 1, "difficulty": 2}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "food",
|
|
||||||
"table": [
|
|
||||||
{ "id": "food_rations", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "food_apple", "weight": 1, "difficulty": 1}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "mobs",
|
|
||||||
"table": [
|
|
||||||
{ "id": "sheep_little", "weight": 1, "difficulty": 0},
|
|
||||||
{ "id": "chicken", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "rat", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "goblin", "weight": 3, "difficulty": 1},
|
|
||||||
{ "id": "kobold", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "fox", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "jackal", "weight": 4, "difficulty": 1},
|
|
||||||
{ "id": "deer_little", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "treant_small", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "zombie_kobold", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "zombie_gnome", "weight": 1, "difficulty": 2},
|
|
||||||
{ "id": "kobold_large", "weight": 1, "difficulty": 2},
|
|
||||||
{ "id": "rat_giant", "weight": 2, "difficulty": 2},
|
|
||||||
{ "id": "coyote", "weight": 4, "difficulty": 2},
|
|
||||||
{ "id": "caterpillar_cave", "weight": 2, "difficulty": 2},
|
|
||||||
{ "id": "caterpillar_giant", "weight": 2, "difficulty": 3},
|
|
||||||
{ "id": "zombie_orc", "weight": 1, "difficulty": 3},
|
|
||||||
{ "id": "zombie_dwarf", "weight": 1, "difficulty": 3},
|
|
||||||
{ "id": "gnome", "weight": 1, "difficulty": 3},
|
|
||||||
{ "id": "spider_cave", "weight": 2, "difficulty": 3},
|
|
||||||
{ "id": "kobold_captain", "weight": 1, "difficulty": 3},
|
|
||||||
{ "id": "dog_little", "weight": 1, "difficulty": 3},
|
|
||||||
{ "id": "orc", "weight": 2, "difficulty": 3},
|
|
||||||
{ "id": "goblin_chieftain", "weight": 1, "difficulty": 3},
|
|
||||||
{ "id": "dwarf", "weight": 3, "difficulty": 4},
|
|
||||||
{ "id": "orc_hill", "weight": 1, "difficulty": 4},
|
|
||||||
{ "id": "horse_little", "weight": 2, "difficulty": 4},
|
|
||||||
{ "id": "ant_worker", "weight": 3, "difficulty": 4},
|
|
||||||
{ "id": "dog", "weight": 1, "difficulty": 5},
|
|
||||||
{ "id": "wolf", "weight": 2, "difficulty": 6},
|
|
||||||
{ "id": "jaguar", "weight": 2, "difficulty": 6},
|
|
||||||
{ "id": "ant_soldier", "weight": 2, "difficulty": 6},
|
|
||||||
{ "id": "orc_captain", "weight": 1, "difficulty": 7},
|
|
||||||
{ "id": "dog_large", "weight": 1, "difficulty": 7},
|
|
||||||
{ "id": "lynx", "weight": 1, "difficulty": 7},
|
|
||||||
{ "id": "panther", "weight": 1, "difficulty": 7},
|
|
||||||
{ "id": "horse", "weight": 2, "difficulty": 7},
|
|
||||||
{ "id": "ogre", "weight": 1, "difficulty": 7},
|
|
||||||
{ "id": "warg", "weight": 2, "difficulty": 8},
|
|
||||||
{ "id": "horse_large", "weight": 2, "difficulty": 9}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "squad_goblin",
|
|
||||||
"table": [
|
|
||||||
{ "id": "goblin", "weight": 3, "difficulty": 1}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "squad_kobold",
|
|
||||||
"table": [
|
|
||||||
{ "id": "kobold", "weight": 3, "difficulty": 1},
|
|
||||||
{ "id": "kobold_large", "weight": 2, "difficulty": 2}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "squad_orc",
|
|
||||||
"table": [
|
|
||||||
{ "id": "orc", "weight": 2, "difficulty": 2},
|
|
||||||
{ "id": "orc_hill", "weight": 1, "difficulty": 4}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "traps",
|
|
||||||
"table": [
|
|
||||||
{ "id": "trap_bear", "weight": 2, "difficulty": 1},
|
|
||||||
{ "id": "trap_confusion", "weight": 1, "difficulty": 1},
|
|
||||||
{ "id": "trap_mini_mine", "weight": 1, "difficulty": 3},
|
|
||||||
{ "id": "trap_stonefall", "weight": 1, "difficulty": 5}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -94,6 +94,7 @@ function passStringToWasm0(arg, malloc, realloc) {
|
||||||
const ret = encodeString(arg, view);
|
const ret = encodeString(arg, view);
|
||||||
|
|
||||||
offset += ret.written;
|
offset += ret.written;
|
||||||
|
ptr = realloc(ptr, len, offset, 1) >>> 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
WASM_VECTOR_LEN = offset;
|
WASM_VECTOR_LEN = offset;
|
||||||
|
|
@ -187,6 +188,12 @@ function getStringFromWasm0(ptr, len) {
|
||||||
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined')
|
||||||
|
? { register: () => {}, unregister: () => {} }
|
||||||
|
: new FinalizationRegistry(state => {
|
||||||
|
wasm.__wbindgen_export_2.get(state.dtor)(state.a, state.b)
|
||||||
|
});
|
||||||
|
|
||||||
function makeMutClosure(arg0, arg1, dtor, f) {
|
function makeMutClosure(arg0, arg1, dtor, f) {
|
||||||
const state = { a: arg0, b: arg1, cnt: 1, dtor };
|
const state = { a: arg0, b: arg1, cnt: 1, dtor };
|
||||||
const real = (...args) => {
|
const real = (...args) => {
|
||||||
|
|
@ -201,22 +208,22 @@ function makeMutClosure(arg0, arg1, dtor, f) {
|
||||||
} finally {
|
} finally {
|
||||||
if (--state.cnt === 0) {
|
if (--state.cnt === 0) {
|
||||||
wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);
|
wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);
|
||||||
|
CLOSURE_DTORS.unregister(state);
|
||||||
} else {
|
} else {
|
||||||
state.a = a;
|
state.a = a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
real.original = state;
|
real.original = state;
|
||||||
|
CLOSURE_DTORS.register(real, state, state);
|
||||||
return real;
|
return real;
|
||||||
}
|
}
|
||||||
function __wbg_adapter_20(arg0, arg1) {
|
function __wbg_adapter_20(arg0, arg1) {
|
||||||
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hb6c6b1cd103d974c(arg0, arg1);
|
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h8da99561c0deee8a(arg0, arg1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function __wbg_adapter_23(arg0, arg1, arg2) {
|
function __wbg_adapter_23(arg0, arg1, arg2) {
|
||||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h02b9f16709be0849(arg0, arg1, addHeapObject(arg2));
|
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h140d7fafa8b9c22e(arg0, arg1, addHeapObject(arg2));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleError(f, args) {
|
function handleError(f, args) {
|
||||||
|
|
@ -320,372 +327,382 @@ function __wbg_get_imports() {
|
||||||
const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
|
const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_instanceof_WebGl2RenderingContext_f921526c513bf717 = function(arg0) {
|
imports.wbg.__wbg_instanceof_WebGl2RenderingContext_6b8f92d566ced9e1 = function(arg0) {
|
||||||
let result;
|
let result;
|
||||||
try {
|
try {
|
||||||
result = getObject(arg0) instanceof WebGL2RenderingContext;
|
result = getObject(arg0) instanceof WebGL2RenderingContext;
|
||||||
} catch {
|
} catch (_) {
|
||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
const ret = result;
|
const ret = result;
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_bindVertexArray_8863a216d7b0a339 = function(arg0, arg1) {
|
imports.wbg.__wbg_bindVertexArray_239574d42dbbd203 = function(arg0, arg1) {
|
||||||
getObject(arg0).bindVertexArray(getObject(arg1));
|
getObject(arg0).bindVertexArray(getObject(arg1));
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_bufferData_21334671c4ba6004 = function(arg0, arg1, arg2, arg3) {
|
imports.wbg.__wbg_bufferData_c787516945ba48c2 = function(arg0, arg1, arg2, arg3) {
|
||||||
getObject(arg0).bufferData(arg1 >>> 0, getObject(arg2), arg3 >>> 0);
|
getObject(arg0).bufferData(arg1 >>> 0, getObject(arg2), arg3 >>> 0);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_createVertexArray_51d51e1e1e13e9f6 = function(arg0) {
|
imports.wbg.__wbg_createVertexArray_4f450ed4d4a69acf = function(arg0) {
|
||||||
const ret = getObject(arg0).createVertexArray();
|
const ret = getObject(arg0).createVertexArray();
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_texImage2D_07240affd06971e9 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {
|
imports.wbg.__wbg_texImage2D_2558a70047650d54 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {
|
||||||
getObject(arg0).texImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, getObject(arg9));
|
getObject(arg0).texImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, getObject(arg9));
|
||||||
}, arguments) };
|
}, arguments) };
|
||||||
imports.wbg.__wbg_attachShader_47256b6b3d42a22e = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_attachShader_2112634b3ffa9e9f = function(arg0, arg1, arg2) {
|
||||||
getObject(arg0).attachShader(getObject(arg1), getObject(arg2));
|
getObject(arg0).attachShader(getObject(arg1), getObject(arg2));
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_bindBuffer_24f6010e273fa400 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_bindBuffer_90d4fb91538001d5 = function(arg0, arg1, arg2) {
|
||||||
getObject(arg0).bindBuffer(arg1 >>> 0, getObject(arg2));
|
getObject(arg0).bindBuffer(arg1 >>> 0, getObject(arg2));
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_bindFramebuffer_a9573e340dab20fe = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_bindFramebuffer_4f950b884dc4be83 = function(arg0, arg1, arg2) {
|
||||||
getObject(arg0).bindFramebuffer(arg1 >>> 0, getObject(arg2));
|
getObject(arg0).bindFramebuffer(arg1 >>> 0, getObject(arg2));
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_bindTexture_92d6d7f8bff9531e = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_bindTexture_75a698c47a923814 = function(arg0, arg1, arg2) {
|
||||||
getObject(arg0).bindTexture(arg1 >>> 0, getObject(arg2));
|
getObject(arg0).bindTexture(arg1 >>> 0, getObject(arg2));
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_blendFunc_533de6de45b80a09 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_blendFunc_cffe61957c92e9ac = function(arg0, arg1, arg2) {
|
||||||
getObject(arg0).blendFunc(arg1 >>> 0, arg2 >>> 0);
|
getObject(arg0).blendFunc(arg1 >>> 0, arg2 >>> 0);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_clear_2db2efe323bfdf68 = function(arg0, arg1) {
|
imports.wbg.__wbg_clear_8e2508724944df18 = function(arg0, arg1) {
|
||||||
getObject(arg0).clear(arg1 >>> 0);
|
getObject(arg0).clear(arg1 >>> 0);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_clearColor_7a7d04702f7e38e5 = function(arg0, arg1, arg2, arg3, arg4) {
|
imports.wbg.__wbg_clearColor_480962bfac4e1cbd = function(arg0, arg1, arg2, arg3, arg4) {
|
||||||
getObject(arg0).clearColor(arg1, arg2, arg3, arg4);
|
getObject(arg0).clearColor(arg1, arg2, arg3, arg4);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_compileShader_6bf78b425d5c98e1 = function(arg0, arg1) {
|
imports.wbg.__wbg_compileShader_f40e0c51a7a836fd = function(arg0, arg1) {
|
||||||
getObject(arg0).compileShader(getObject(arg1));
|
getObject(arg0).compileShader(getObject(arg1));
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_createBuffer_323425af422748ac = function(arg0) {
|
imports.wbg.__wbg_createBuffer_7f57647465d111f0 = function(arg0) {
|
||||||
const ret = getObject(arg0).createBuffer();
|
const ret = getObject(arg0).createBuffer();
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_createFramebuffer_1684a99697ac9563 = function(arg0) {
|
imports.wbg.__wbg_createFramebuffer_8ebfde8c77472024 = function(arg0) {
|
||||||
const ret = getObject(arg0).createFramebuffer();
|
const ret = getObject(arg0).createFramebuffer();
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_createProgram_4eaf3b97b5747a62 = function(arg0) {
|
imports.wbg.__wbg_createProgram_7759fb2effb5d9b3 = function(arg0) {
|
||||||
const ret = getObject(arg0).createProgram();
|
const ret = getObject(arg0).createProgram();
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_createShader_429776c9dd6fb87b = function(arg0, arg1) {
|
imports.wbg.__wbg_createShader_b474ef421ec0f80b = function(arg0, arg1) {
|
||||||
const ret = getObject(arg0).createShader(arg1 >>> 0);
|
const ret = getObject(arg0).createShader(arg1 >>> 0);
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_createTexture_1bf4d6fec570124b = function(arg0) {
|
imports.wbg.__wbg_createTexture_18b4a88c14cb086e = function(arg0) {
|
||||||
const ret = getObject(arg0).createTexture();
|
const ret = getObject(arg0).createTexture();
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_disable_e02106ca6c7002d6 = function(arg0, arg1) {
|
imports.wbg.__wbg_disable_f0ef6e9a7ac6ddd7 = function(arg0, arg1) {
|
||||||
getObject(arg0).disable(arg1 >>> 0);
|
getObject(arg0).disable(arg1 >>> 0);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_drawArrays_c91ce3f736bf1f2a = function(arg0, arg1, arg2, arg3) {
|
imports.wbg.__wbg_drawArrays_5bf0d92947e472af = function(arg0, arg1, arg2, arg3) {
|
||||||
getObject(arg0).drawArrays(arg1 >>> 0, arg2, arg3);
|
getObject(arg0).drawArrays(arg1 >>> 0, arg2, arg3);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_drawElements_a9529eefaf2008bd = function(arg0, arg1, arg2, arg3, arg4) {
|
imports.wbg.__wbg_drawElements_565a93d1efa4da07 = function(arg0, arg1, arg2, arg3, arg4) {
|
||||||
getObject(arg0).drawElements(arg1 >>> 0, arg2, arg3 >>> 0, arg4);
|
getObject(arg0).drawElements(arg1 >>> 0, arg2, arg3 >>> 0, arg4);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_enable_195891416c520019 = function(arg0, arg1) {
|
imports.wbg.__wbg_enable_8b3019da8846ce76 = function(arg0, arg1) {
|
||||||
getObject(arg0).enable(arg1 >>> 0);
|
getObject(arg0).enable(arg1 >>> 0);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_enableVertexAttribArray_8804480c2ea0bb72 = function(arg0, arg1) {
|
imports.wbg.__wbg_enableVertexAttribArray_9d7b7e199f86e09b = function(arg0, arg1) {
|
||||||
getObject(arg0).enableVertexAttribArray(arg1 >>> 0);
|
getObject(arg0).enableVertexAttribArray(arg1 >>> 0);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_framebufferTexture2D_e88fcbd7f8523bb8 = function(arg0, arg1, arg2, arg3, arg4, arg5) {
|
imports.wbg.__wbg_framebufferTexture2D_a6ad7148f7983ae6 = function(arg0, arg1, arg2, arg3, arg4, arg5) {
|
||||||
getObject(arg0).framebufferTexture2D(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0, getObject(arg4), arg5);
|
getObject(arg0).framebufferTexture2D(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0, getObject(arg4), arg5);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_getError_7191ad6ea53607fe = function(arg0) {
|
imports.wbg.__wbg_getError_d02c89917f45dd5e = function(arg0) {
|
||||||
const ret = getObject(arg0).getError();
|
const ret = getObject(arg0).getError();
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_getExtension_77909f6d51d49d4d = function() { return handleError(function (arg0, arg1, arg2) {
|
imports.wbg.__wbg_getExtension_bef4112494c87f34 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||||
const ret = getObject(arg0).getExtension(getStringFromWasm0(arg1, arg2));
|
const ret = getObject(arg0).getExtension(getStringFromWasm0(arg1, arg2));
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
}, arguments) };
|
}, arguments) };
|
||||||
imports.wbg.__wbg_getParameter_55b36a787dbbfb74 = function() { return handleError(function (arg0, arg1) {
|
imports.wbg.__wbg_getParameter_aa9af66884d2b210 = function() { return handleError(function (arg0, arg1) {
|
||||||
const ret = getObject(arg0).getParameter(arg1 >>> 0);
|
const ret = getObject(arg0).getParameter(arg1 >>> 0);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
}, arguments) };
|
}, arguments) };
|
||||||
imports.wbg.__wbg_getProgramInfoLog_b81bc53188e286fa = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_getProgramInfoLog_4d189135f8d5a2de = function(arg0, arg1, arg2) {
|
||||||
const ret = getObject(arg1).getProgramInfoLog(getObject(arg2));
|
const ret = getObject(arg1).getProgramInfoLog(getObject(arg2));
|
||||||
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
var len1 = WASM_VECTOR_LEN;
|
var len1 = WASM_VECTOR_LEN;
|
||||||
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
||||||
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_getProgramParameter_35522a0bfdfaad27 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_getProgramParameter_7b04ca71a79d9047 = function(arg0, arg1, arg2) {
|
||||||
const ret = getObject(arg0).getProgramParameter(getObject(arg1), arg2 >>> 0);
|
const ret = getObject(arg0).getProgramParameter(getObject(arg1), arg2 >>> 0);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_getShaderInfoLog_968b93e75477d725 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_getShaderInfoLog_d5de3e4eab06fc46 = function(arg0, arg1, arg2) {
|
||||||
const ret = getObject(arg1).getShaderInfoLog(getObject(arg2));
|
const ret = getObject(arg1).getShaderInfoLog(getObject(arg2));
|
||||||
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
var len1 = WASM_VECTOR_LEN;
|
var len1 = WASM_VECTOR_LEN;
|
||||||
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
||||||
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_getShaderParameter_ac2727ae4fe7648e = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_getShaderParameter_4ddb51279bb1500b = function(arg0, arg1, arg2) {
|
||||||
const ret = getObject(arg0).getShaderParameter(getObject(arg1), arg2 >>> 0);
|
const ret = getObject(arg0).getShaderParameter(getObject(arg1), arg2 >>> 0);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_getSupportedExtensions_fafc31aab913037d = function(arg0) {
|
imports.wbg.__wbg_getSupportedExtensions_7a174085f9e1983a = function(arg0) {
|
||||||
const ret = getObject(arg0).getSupportedExtensions();
|
const ret = getObject(arg0).getSupportedExtensions();
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_getUniformLocation_9f6eb60c560a347b = function(arg0, arg1, arg2, arg3) {
|
imports.wbg.__wbg_getUniformLocation_51ec30e3755e574d = function(arg0, arg1, arg2, arg3) {
|
||||||
const ret = getObject(arg0).getUniformLocation(getObject(arg1), getStringFromWasm0(arg2, arg3));
|
const ret = getObject(arg0).getUniformLocation(getObject(arg1), getStringFromWasm0(arg2, arg3));
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_linkProgram_33998194075d71fb = function(arg0, arg1) {
|
imports.wbg.__wbg_linkProgram_eabc664217816e72 = function(arg0, arg1) {
|
||||||
getObject(arg0).linkProgram(getObject(arg1));
|
getObject(arg0).linkProgram(getObject(arg1));
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_shaderSource_1cb7c64dc7d1a500 = function(arg0, arg1, arg2, arg3) {
|
imports.wbg.__wbg_shaderSource_7943d06f24862a3b = function(arg0, arg1, arg2, arg3) {
|
||||||
getObject(arg0).shaderSource(getObject(arg1), getStringFromWasm0(arg2, arg3));
|
getObject(arg0).shaderSource(getObject(arg1), getStringFromWasm0(arg2, arg3));
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_texParameteri_85dad939f62a15aa = function(arg0, arg1, arg2, arg3) {
|
imports.wbg.__wbg_texParameteri_8f70dffce11d7da1 = function(arg0, arg1, arg2, arg3) {
|
||||||
getObject(arg0).texParameteri(arg1 >>> 0, arg2 >>> 0, arg3);
|
getObject(arg0).texParameteri(arg1 >>> 0, arg2 >>> 0, arg3);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_uniform1i_d2e61a6a43889648 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_uniform1i_bdcd75be097285e6 = function(arg0, arg1, arg2) {
|
||||||
getObject(arg0).uniform1i(getObject(arg1), arg2);
|
getObject(arg0).uniform1i(getObject(arg1), arg2);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_uniform3f_8364a0959b6c1570 = function(arg0, arg1, arg2, arg3, arg4) {
|
imports.wbg.__wbg_uniform3f_33d80ae15223c6db = function(arg0, arg1, arg2, arg3, arg4) {
|
||||||
getObject(arg0).uniform3f(getObject(arg1), arg2, arg3, arg4);
|
getObject(arg0).uniform3f(getObject(arg1), arg2, arg3, arg4);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_useProgram_3683cf6f60939dcd = function(arg0, arg1) {
|
imports.wbg.__wbg_useProgram_757fab437af29c20 = function(arg0, arg1) {
|
||||||
getObject(arg0).useProgram(getObject(arg1));
|
getObject(arg0).useProgram(getObject(arg1));
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_vertexAttribPointer_316ffe2f0458fde7 = function(arg0, arg1, arg2, arg3, arg4, arg5, arg6) {
|
imports.wbg.__wbg_vertexAttribPointer_4416f0325c02aa13 = function(arg0, arg1, arg2, arg3, arg4, arg5, arg6) {
|
||||||
getObject(arg0).vertexAttribPointer(arg1 >>> 0, arg2, arg3 >>> 0, arg4 !== 0, arg5, arg6);
|
getObject(arg0).vertexAttribPointer(arg1 >>> 0, arg2, arg3 >>> 0, arg4 !== 0, arg5, arg6);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_getElementById_cc0e0d931b0d9a28 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_instanceof_Window_f401953a2cf86220 = function(arg0) {
|
||||||
const ret = getObject(arg0).getElementById(getStringFromWasm0(arg1, arg2));
|
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_instanceof_HtmlCanvasElement_da5f9efa0688cf6d = function(arg0) {
|
|
||||||
let result;
|
|
||||||
try {
|
|
||||||
result = getObject(arg0) instanceof HTMLCanvasElement;
|
|
||||||
} catch {
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
const ret = result;
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_setwidth_a667a942dba6656e = function(arg0, arg1) {
|
|
||||||
getObject(arg0).width = arg1 >>> 0;
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_setheight_a747d440760fe5aa = function(arg0, arg1) {
|
|
||||||
getObject(arg0).height = arg1 >>> 0;
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_getContext_7c5944ea807bf5d3 = function() { return handleError(function (arg0, arg1, arg2) {
|
|
||||||
const ret = getObject(arg0).getContext(getStringFromWasm0(arg1, arg2));
|
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
||||||
}, arguments) };
|
|
||||||
imports.wbg.__wbg_offsetX_5a58f16f6c3a41b6 = function(arg0) {
|
|
||||||
const ret = getObject(arg0).offsetX;
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_offsetY_c45b4956f6429a95 = function(arg0) {
|
|
||||||
const ret = getObject(arg0).offsetY;
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_now_0cfdc90c97d0c24b = function(arg0) {
|
|
||||||
const ret = getObject(arg0).now();
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_instanceof_Window_9029196b662bc42a = function(arg0) {
|
|
||||||
let result;
|
let result;
|
||||||
try {
|
try {
|
||||||
result = getObject(arg0) instanceof Window;
|
result = getObject(arg0) instanceof Window;
|
||||||
} catch {
|
} catch (_) {
|
||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
const ret = result;
|
const ret = result;
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_charCode_75cea1a3a6d66388 = function(arg0) {
|
imports.wbg.__wbg_document_5100775d18896c16 = function(arg0) {
|
||||||
|
const ret = getObject(arg0).document;
|
||||||
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_setonkeydown_e2af4cfc28b2c96a = function(arg0, arg1) {
|
||||||
|
getObject(arg0).onkeydown = getObject(arg1);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_setonkeyup_d3a49733ebf1f4ab = function(arg0, arg1) {
|
||||||
|
getObject(arg0).onkeyup = getObject(arg1);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_performance_3298a9628a5c8aa4 = function(arg0) {
|
||||||
|
const ret = getObject(arg0).performance;
|
||||||
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_requestAnimationFrame_549258cfa66011f0 = function() { return handleError(function (arg0, arg1) {
|
||||||
|
const ret = getObject(arg0).requestAnimationFrame(getObject(arg1));
|
||||||
|
return ret;
|
||||||
|
}, arguments) };
|
||||||
|
imports.wbg.__wbg_getElementById_c369ff43f0db99cf = function(arg0, arg1, arg2) {
|
||||||
|
const ret = getObject(arg0).getElementById(getStringFromWasm0(arg1, arg2));
|
||||||
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_setonmousedown_9668c3209232d648 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).onmousedown = getObject(arg1);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_setonmousemove_96db8158179cb183 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).onmousemove = getObject(arg1);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_setonmouseup_d5b3f4be4e37eb4f = function(arg0, arg1) {
|
||||||
|
getObject(arg0).onmouseup = getObject(arg1);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_bufferData_5d1e6b8eaa7d23c8 = function(arg0, arg1, arg2, arg3) {
|
||||||
|
getObject(arg0).bufferData(arg1 >>> 0, getObject(arg2), arg3 >>> 0);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_texImage2D_a14a3c7863e25c89 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {
|
||||||
|
getObject(arg0).texImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, getObject(arg9));
|
||||||
|
}, arguments) };
|
||||||
|
imports.wbg.__wbg_attachShader_6397dc4fd87343d3 = function(arg0, arg1, arg2) {
|
||||||
|
getObject(arg0).attachShader(getObject(arg1), getObject(arg2));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_bindBuffer_1e5043751efddd4f = function(arg0, arg1, arg2) {
|
||||||
|
getObject(arg0).bindBuffer(arg1 >>> 0, getObject(arg2));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_bindFramebuffer_c301d73a2c2842bb = function(arg0, arg1, arg2) {
|
||||||
|
getObject(arg0).bindFramebuffer(arg1 >>> 0, getObject(arg2));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_bindTexture_772f5eb022019d87 = function(arg0, arg1, arg2) {
|
||||||
|
getObject(arg0).bindTexture(arg1 >>> 0, getObject(arg2));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_blendFunc_fc4b298f39801a9c = function(arg0, arg1, arg2) {
|
||||||
|
getObject(arg0).blendFunc(arg1 >>> 0, arg2 >>> 0);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_clear_f9731a47df2e70d8 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).clear(arg1 >>> 0);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_clearColor_42707553c40e0e0f = function(arg0, arg1, arg2, arg3, arg4) {
|
||||||
|
getObject(arg0).clearColor(arg1, arg2, arg3, arg4);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_compileShader_3af4719dfdb508e3 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).compileShader(getObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_createBuffer_34e01f5c10929b41 = function(arg0) {
|
||||||
|
const ret = getObject(arg0).createBuffer();
|
||||||
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_createFramebuffer_49ca64e9e1c6f5eb = function(arg0) {
|
||||||
|
const ret = getObject(arg0).createFramebuffer();
|
||||||
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_createProgram_9affbfa62b7b2608 = function(arg0) {
|
||||||
|
const ret = getObject(arg0).createProgram();
|
||||||
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_createShader_55ca04b44164bd41 = function(arg0, arg1) {
|
||||||
|
const ret = getObject(arg0).createShader(arg1 >>> 0);
|
||||||
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_createTexture_c13c31b2b132c17f = function(arg0) {
|
||||||
|
const ret = getObject(arg0).createTexture();
|
||||||
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_disable_5dd8c3842de93e92 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).disable(arg1 >>> 0);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_drawArrays_f619a26a53ab5ab3 = function(arg0, arg1, arg2, arg3) {
|
||||||
|
getObject(arg0).drawArrays(arg1 >>> 0, arg2, arg3);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_drawElements_0861624300587fcd = function(arg0, arg1, arg2, arg3, arg4) {
|
||||||
|
getObject(arg0).drawElements(arg1 >>> 0, arg2, arg3 >>> 0, arg4);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_enable_7abe812a71c76206 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).enable(arg1 >>> 0);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_enableVertexAttribArray_6d44444aa994f42a = function(arg0, arg1) {
|
||||||
|
getObject(arg0).enableVertexAttribArray(arg1 >>> 0);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_framebufferTexture2D_66e1968fd5b7b3e3 = function(arg0, arg1, arg2, arg3, arg4, arg5) {
|
||||||
|
getObject(arg0).framebufferTexture2D(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0, getObject(arg4), arg5);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_getError_fd1f7b2b2ba5a860 = function(arg0) {
|
||||||
|
const ret = getObject(arg0).getError();
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_getProgramInfoLog_bf1fba8fa90667c7 = function(arg0, arg1, arg2) {
|
||||||
|
const ret = getObject(arg1).getProgramInfoLog(getObject(arg2));
|
||||||
|
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
|
var len1 = WASM_VECTOR_LEN;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_getProgramParameter_10c8a43809fb8c2e = function(arg0, arg1, arg2) {
|
||||||
|
const ret = getObject(arg0).getProgramParameter(getObject(arg1), arg2 >>> 0);
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_getShaderInfoLog_0262cb299092ce92 = function(arg0, arg1, arg2) {
|
||||||
|
const ret = getObject(arg1).getShaderInfoLog(getObject(arg2));
|
||||||
|
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
|
var len1 = WASM_VECTOR_LEN;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_getShaderParameter_60b69083e8d662ce = function(arg0, arg1, arg2) {
|
||||||
|
const ret = getObject(arg0).getShaderParameter(getObject(arg1), arg2 >>> 0);
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_getUniformLocation_6eedfb513ccce732 = function(arg0, arg1, arg2, arg3) {
|
||||||
|
const ret = getObject(arg0).getUniformLocation(getObject(arg1), getStringFromWasm0(arg2, arg3));
|
||||||
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_linkProgram_af5fed9dc3f1cdf9 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).linkProgram(getObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_shaderSource_7891a1fcb69a0023 = function(arg0, arg1, arg2, arg3) {
|
||||||
|
getObject(arg0).shaderSource(getObject(arg1), getStringFromWasm0(arg2, arg3));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_texParameteri_d1035ed45d6c5655 = function(arg0, arg1, arg2, arg3) {
|
||||||
|
getObject(arg0).texParameteri(arg1 >>> 0, arg2 >>> 0, arg3);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_uniform1i_badd5ff70c0d30bf = function(arg0, arg1, arg2) {
|
||||||
|
getObject(arg0).uniform1i(getObject(arg1), arg2);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_uniform3f_866e96669df4a3d2 = function(arg0, arg1, arg2, arg3, arg4) {
|
||||||
|
getObject(arg0).uniform3f(getObject(arg1), arg2, arg3, arg4);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_useProgram_c637e43f9cd4c07a = function(arg0, arg1) {
|
||||||
|
getObject(arg0).useProgram(getObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_vertexAttribPointer_c25e4c5ed17f8a1d = function(arg0, arg1, arg2, arg3, arg4, arg5, arg6) {
|
||||||
|
getObject(arg0).vertexAttribPointer(arg1 >>> 0, arg2, arg3 >>> 0, arg4 !== 0, arg5, arg6);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_now_4e659b3d15f470d9 = function(arg0) {
|
||||||
|
const ret = getObject(arg0).now();
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_instanceof_HtmlCanvasElement_46bdbf323b0b18d1 = function(arg0) {
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = getObject(arg0) instanceof HTMLCanvasElement;
|
||||||
|
} catch (_) {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
const ret = result;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_setwidth_080107476e633963 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).width = arg1 >>> 0;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_setheight_dc240617639f1f51 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).height = arg1 >>> 0;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_getContext_df50fa48a8876636 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||||
|
const ret = getObject(arg0).getContext(getStringFromWasm0(arg1, arg2));
|
||||||
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
|
}, arguments) };
|
||||||
|
imports.wbg.__wbg_bindVertexArrayOES_abe2fd389c6a2f56 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).bindVertexArrayOES(getObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_createVertexArrayOES_886be8a08db32ce6 = function(arg0) {
|
||||||
|
const ret = getObject(arg0).createVertexArrayOES();
|
||||||
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_charCode_358ab311d74487af = function(arg0) {
|
||||||
const ret = getObject(arg0).charCode;
|
const ret = getObject(arg0).charCode;
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_keyCode_dfa86be31f5ef90c = function(arg0) {
|
imports.wbg.__wbg_keyCode_2af7775f99bf8e33 = function(arg0) {
|
||||||
const ret = getObject(arg0).keyCode;
|
const ret = getObject(arg0).keyCode;
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_code_96d6322b968b2d17 = function(arg0, arg1) {
|
imports.wbg.__wbg_code_3b0c3912a2351163 = function(arg0, arg1) {
|
||||||
const ret = getObject(arg1).code;
|
const ret = getObject(arg1).code;
|
||||||
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
const len1 = WASM_VECTOR_LEN;
|
const len1 = WASM_VECTOR_LEN;
|
||||||
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
||||||
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_getModifierState_5102ee8843516d2f = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_getModifierState_081302a3ea0063ad = function(arg0, arg1, arg2) {
|
||||||
const ret = getObject(arg0).getModifierState(getStringFromWasm0(arg1, arg2));
|
const ret = getObject(arg0).getModifierState(getStringFromWasm0(arg1, arg2));
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_document_f7ace2b956f30a4f = function(arg0) {
|
imports.wbg.__wbg_offsetX_1a40c03298c0d8b6 = function(arg0) {
|
||||||
const ret = getObject(arg0).document;
|
const ret = getObject(arg0).offsetX;
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_performance_2c295061c8b01e0b = function(arg0) {
|
|
||||||
const ret = getObject(arg0).performance;
|
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_setonkeydown_933cca3c9000a932 = function(arg0, arg1) {
|
|
||||||
getObject(arg0).onkeydown = getObject(arg1);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_setonkeyup_0dfb23e81d0afdde = function(arg0, arg1) {
|
|
||||||
getObject(arg0).onkeyup = getObject(arg1);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_requestAnimationFrame_d082200514b6674d = function() { return handleError(function (arg0, arg1) {
|
|
||||||
const ret = getObject(arg0).requestAnimationFrame(getObject(arg1));
|
|
||||||
return ret;
|
|
||||||
}, arguments) };
|
|
||||||
imports.wbg.__wbg_setonmousedown_4f38d9c057bbfcbd = function(arg0, arg1) {
|
|
||||||
getObject(arg0).onmousedown = getObject(arg1);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_setonmousemove_c0b17753786f3544 = function(arg0, arg1) {
|
|
||||||
getObject(arg0).onmousemove = getObject(arg1);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_setonmouseup_4b447fa380e33802 = function(arg0, arg1) {
|
|
||||||
getObject(arg0).onmouseup = getObject(arg1);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_bufferData_a11a9f65f31e7256 = function(arg0, arg1, arg2, arg3) {
|
|
||||||
getObject(arg0).bufferData(arg1 >>> 0, getObject(arg2), arg3 >>> 0);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_texImage2D_6175916e58c59bc7 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {
|
|
||||||
getObject(arg0).texImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, getObject(arg9));
|
|
||||||
}, arguments) };
|
|
||||||
imports.wbg.__wbg_attachShader_b65b695055670cb5 = function(arg0, arg1, arg2) {
|
|
||||||
getObject(arg0).attachShader(getObject(arg1), getObject(arg2));
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_bindBuffer_313561e5bc0e533f = function(arg0, arg1, arg2) {
|
|
||||||
getObject(arg0).bindBuffer(arg1 >>> 0, getObject(arg2));
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_bindFramebuffer_56bf6536a4ced0ec = function(arg0, arg1, arg2) {
|
|
||||||
getObject(arg0).bindFramebuffer(arg1 >>> 0, getObject(arg2));
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_bindTexture_9cb5c770d1ba2cca = function(arg0, arg1, arg2) {
|
|
||||||
getObject(arg0).bindTexture(arg1 >>> 0, getObject(arg2));
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_blendFunc_fbe9d3a688fe71c3 = function(arg0, arg1, arg2) {
|
|
||||||
getObject(arg0).blendFunc(arg1 >>> 0, arg2 >>> 0);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_clear_2ccea1f65b510c97 = function(arg0, arg1) {
|
|
||||||
getObject(arg0).clear(arg1 >>> 0);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_clearColor_de587608b28bc7ed = function(arg0, arg1, arg2, arg3, arg4) {
|
|
||||||
getObject(arg0).clearColor(arg1, arg2, arg3, arg4);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_compileShader_d88d0a8cd9b72b4d = function(arg0, arg1) {
|
|
||||||
getObject(arg0).compileShader(getObject(arg1));
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_createBuffer_59051f4461e7c5e2 = function(arg0) {
|
|
||||||
const ret = getObject(arg0).createBuffer();
|
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_createFramebuffer_223c1212ad76affc = function(arg0) {
|
|
||||||
const ret = getObject(arg0).createFramebuffer();
|
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_createProgram_88dbe21c0b682e1a = function(arg0) {
|
|
||||||
const ret = getObject(arg0).createProgram();
|
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_createShader_9d7d388633caad18 = function(arg0, arg1) {
|
|
||||||
const ret = getObject(arg0).createShader(arg1 >>> 0);
|
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_createTexture_9d0bb4d741b8ad76 = function(arg0) {
|
|
||||||
const ret = getObject(arg0).createTexture();
|
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_disable_5cf2070641fa2ed7 = function(arg0, arg1) {
|
|
||||||
getObject(arg0).disable(arg1 >>> 0);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_drawArrays_d5c7dc2b2376c85a = function(arg0, arg1, arg2, arg3) {
|
|
||||||
getObject(arg0).drawArrays(arg1 >>> 0, arg2, arg3);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_drawElements_3316ee0cd1117c2a = function(arg0, arg1, arg2, arg3, arg4) {
|
|
||||||
getObject(arg0).drawElements(arg1 >>> 0, arg2, arg3 >>> 0, arg4);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_enable_8965e69c596f0a94 = function(arg0, arg1) {
|
|
||||||
getObject(arg0).enable(arg1 >>> 0);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_enableVertexAttribArray_2b0475db43533cf2 = function(arg0, arg1) {
|
|
||||||
getObject(arg0).enableVertexAttribArray(arg1 >>> 0);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_framebufferTexture2D_953e69a8bec22fa9 = function(arg0, arg1, arg2, arg3, arg4, arg5) {
|
|
||||||
getObject(arg0).framebufferTexture2D(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0, getObject(arg4), arg5);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_getError_1e5ec1ec9e58b323 = function(arg0) {
|
|
||||||
const ret = getObject(arg0).getError();
|
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_getProgramInfoLog_0b7af4ad85fa52a4 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_offsetY_f75e8c25b9d9b679 = function(arg0) {
|
||||||
const ret = getObject(arg1).getProgramInfoLog(getObject(arg2));
|
const ret = getObject(arg0).offsetY;
|
||||||
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
return ret;
|
||||||
var len1 = WASM_VECTOR_LEN;
|
|
||||||
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
|
||||||
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_getProgramParameter_2a3735278367f8bc = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_static_accessor_MODULE_7781e47b50010688 = function() {
|
||||||
const ret = getObject(arg0).getProgramParameter(getObject(arg1), arg2 >>> 0);
|
const ret = module;
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_getShaderInfoLog_979aafa403ffb252 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_randomFillSync_bf67eeddb65b346b = function() { return handleError(function (arg0, arg1, arg2) {
|
||||||
const ret = getObject(arg1).getShaderInfoLog(getObject(arg2));
|
getObject(arg0).randomFillSync(getArrayU8FromWasm0(arg1, arg2));
|
||||||
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
}, arguments) };
|
||||||
var len1 = WASM_VECTOR_LEN;
|
imports.wbg.__wbg_getRandomValues_f6c9b08ef5448767 = function() { return handleError(function (arg0, arg1) {
|
||||||
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
getObject(arg0).getRandomValues(getObject(arg1));
|
||||||
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
}, arguments) };
|
||||||
};
|
|
||||||
imports.wbg.__wbg_getShaderParameter_e8054f1d9026fb70 = function(arg0, arg1, arg2) {
|
|
||||||
const ret = getObject(arg0).getShaderParameter(getObject(arg1), arg2 >>> 0);
|
|
||||||
return addHeapObject(ret);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_getUniformLocation_688976233799a45a = function(arg0, arg1, arg2, arg3) {
|
|
||||||
const ret = getObject(arg0).getUniformLocation(getObject(arg1), getStringFromWasm0(arg2, arg3));
|
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_linkProgram_9a2d12d120d99917 = function(arg0, arg1) {
|
|
||||||
getObject(arg0).linkProgram(getObject(arg1));
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_shaderSource_f435f9b74440bb54 = function(arg0, arg1, arg2, arg3) {
|
|
||||||
getObject(arg0).shaderSource(getObject(arg1), getStringFromWasm0(arg2, arg3));
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_texParameteri_1f17358e51eb8069 = function(arg0, arg1, arg2, arg3) {
|
|
||||||
getObject(arg0).texParameteri(arg1 >>> 0, arg2 >>> 0, arg3);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_uniform1i_9f94ef0ba6b3cc66 = function(arg0, arg1, arg2) {
|
|
||||||
getObject(arg0).uniform1i(getObject(arg1), arg2);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_uniform3f_c682f4b32f713d1a = function(arg0, arg1, arg2, arg3, arg4) {
|
|
||||||
getObject(arg0).uniform3f(getObject(arg1), arg2, arg3, arg4);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_useProgram_019eb6df066fabf5 = function(arg0, arg1) {
|
|
||||||
getObject(arg0).useProgram(getObject(arg1));
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_vertexAttribPointer_ca11984ee8843c0a = function(arg0, arg1, arg2, arg3, arg4, arg5, arg6) {
|
|
||||||
getObject(arg0).vertexAttribPointer(arg1 >>> 0, arg2, arg3 >>> 0, arg4 !== 0, arg5, arg6);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_bindVertexArrayOES_b7d9da7e073aa6b5 = function(arg0, arg1) {
|
|
||||||
getObject(arg0).bindVertexArrayOES(getObject(arg1));
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_createVertexArrayOES_6a3c3a5a68201f8f = function(arg0) {
|
|
||||||
const ret = getObject(arg0).createVertexArrayOES();
|
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_self_1c2814d86e6e51e3 = function() { return handleError(function () {
|
imports.wbg.__wbg_self_1c2814d86e6e51e3 = function() { return handleError(function () {
|
||||||
const ret = self.self;
|
const ret = self.self;
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
|
|
@ -702,104 +719,94 @@ function __wbg_get_imports() {
|
||||||
const ret = getObject(arg0) === undefined;
|
const ret = getObject(arg0) === undefined;
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_static_accessor_MODULE_7781e47b50010688 = function() {
|
|
||||||
const ret = module;
|
|
||||||
return addHeapObject(ret);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_require_9ace3ae680954e98 = function() { return handleError(function (arg0, arg1, arg2) {
|
imports.wbg.__wbg_require_9ace3ae680954e98 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||||
const ret = getObject(arg0).require(getStringFromWasm0(arg1, arg2));
|
const ret = getObject(arg0).require(getStringFromWasm0(arg1, arg2));
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
}, arguments) };
|
}, arguments) };
|
||||||
imports.wbg.__wbg_getRandomValues_f6c9b08ef5448767 = function() { return handleError(function (arg0, arg1) {
|
imports.wbg.__wbg_get_bd8e338fbd5f5cc8 = function(arg0, arg1) {
|
||||||
getObject(arg0).getRandomValues(getObject(arg1));
|
|
||||||
}, arguments) };
|
|
||||||
imports.wbg.__wbg_randomFillSync_bf67eeddb65b346b = function() { return handleError(function (arg0, arg1, arg2) {
|
|
||||||
getObject(arg0).randomFillSync(getArrayU8FromWasm0(arg1, arg2));
|
|
||||||
}, arguments) };
|
|
||||||
imports.wbg.__wbg_get_44be0491f933a435 = function(arg0, arg1) {
|
|
||||||
const ret = getObject(arg0)[arg1 >>> 0];
|
const ret = getObject(arg0)[arg1 >>> 0];
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_length_fff51ee6522a1a18 = function(arg0) {
|
imports.wbg.__wbg_length_cd7af8117672b8b8 = function(arg0) {
|
||||||
const ret = getObject(arg0).length;
|
const ret = getObject(arg0).length;
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_newnoargs_581967eacc0e2604 = function(arg0, arg1) {
|
imports.wbg.__wbg_newnoargs_e258087cd0daa0ea = function(arg0, arg1) {
|
||||||
const ret = new Function(getStringFromWasm0(arg0, arg1));
|
const ret = new Function(getStringFromWasm0(arg0, arg1));
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_call_cb65541d95d71282 = function() { return handleError(function (arg0, arg1) {
|
imports.wbg.__wbg_call_27c0f87801dedf93 = function() { return handleError(function (arg0, arg1) {
|
||||||
const ret = getObject(arg0).call(getObject(arg1));
|
const ret = getObject(arg0).call(getObject(arg1));
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
}, arguments) };
|
}, arguments) };
|
||||||
imports.wbg.__wbg_self_1ff1d729e9aae938 = function() { return handleError(function () {
|
imports.wbg.__wbg_self_ce0dbfc45cf2f5be = function() { return handleError(function () {
|
||||||
const ret = self.self;
|
const ret = self.self;
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
}, arguments) };
|
}, arguments) };
|
||||||
imports.wbg.__wbg_window_5f4faef6c12b79ec = function() { return handleError(function () {
|
imports.wbg.__wbg_window_c6fb939a7f436783 = function() { return handleError(function () {
|
||||||
const ret = window.window;
|
const ret = window.window;
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
}, arguments) };
|
}, arguments) };
|
||||||
imports.wbg.__wbg_globalThis_1d39714405582d3c = function() { return handleError(function () {
|
imports.wbg.__wbg_globalThis_d1e6af4856ba331b = function() { return handleError(function () {
|
||||||
const ret = globalThis.globalThis;
|
const ret = globalThis.globalThis;
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
}, arguments) };
|
}, arguments) };
|
||||||
imports.wbg.__wbg_global_651f05c6a0944d1c = function() { return handleError(function () {
|
imports.wbg.__wbg_global_207b558942527489 = function() { return handleError(function () {
|
||||||
const ret = global.global;
|
const ret = global.global;
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
}, arguments) };
|
}, arguments) };
|
||||||
imports.wbg.__wbg_now_9c5990bda04c7e53 = function() {
|
imports.wbg.__wbg_now_3014639a94423537 = function() {
|
||||||
const ret = Date.now();
|
const ret = Date.now();
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_buffer_085ec1f694018c4f = function(arg0) {
|
imports.wbg.__wbg_buffer_12d079cc21e14bdb = function(arg0) {
|
||||||
const ret = getObject(arg0).buffer;
|
const ret = getObject(arg0).buffer;
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_newwithbyteoffsetandlength_828b952f0e692245 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_newwithbyteoffsetandlength_41559f654c4e743c = function(arg0, arg1, arg2) {
|
||||||
const ret = new Int8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
const ret = new Int8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_newwithbyteoffsetandlength_735ed5ea2ae07fe9 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_newwithbyteoffsetandlength_4bea9f904a7e0aef = function(arg0, arg1, arg2) {
|
||||||
const ret = new Int16Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
const ret = new Int16Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_newwithbyteoffsetandlength_9f43b22ab631d1d6 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_newwithbyteoffsetandlength_425360430a1c8206 = function(arg0, arg1, arg2) {
|
||||||
const ret = new Int32Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
const ret = new Int32Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_newwithbyteoffsetandlength_6da8e527659b86aa = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_newwithbyteoffsetandlength_aa4a17c33a06e5cb = function(arg0, arg1, arg2) {
|
||||||
const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_new_8125e318e6245eed = function(arg0) {
|
imports.wbg.__wbg_new_63b92bc8671ed464 = function(arg0) {
|
||||||
const ret = new Uint8Array(getObject(arg0));
|
const ret = new Uint8Array(getObject(arg0));
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_set_5cf90238115182c3 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_set_a47bac70306a19a7 = function(arg0, arg1, arg2) {
|
||||||
getObject(arg0).set(getObject(arg1), arg2 >>> 0);
|
getObject(arg0).set(getObject(arg1), arg2 >>> 0);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_length_72e2208bbc0efc61 = function(arg0) {
|
imports.wbg.__wbg_length_c20a40f15020d68a = function(arg0) {
|
||||||
const ret = getObject(arg0).length;
|
const ret = getObject(arg0).length;
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_newwithbyteoffsetandlength_31ff1024ef0c63c7 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_newwithbyteoffsetandlength_9fd64654bc0b0817 = function(arg0, arg1, arg2) {
|
||||||
const ret = new Uint16Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
const ret = new Uint16Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_newwithbyteoffsetandlength_6df0e8c3efd2a5d3 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_newwithbyteoffsetandlength_3125852e5a7fbcff = function(arg0, arg1, arg2) {
|
||||||
const ret = new Uint32Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
const ret = new Uint32Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_newwithbyteoffsetandlength_69193e31c844b792 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_newwithbyteoffsetandlength_4a659d079a1650e0 = function(arg0, arg1, arg2) {
|
||||||
const ret = new Float32Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
const ret = new Float32Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_newwithlength_e5d69174d6984cd7 = function(arg0) {
|
imports.wbg.__wbg_newwithlength_e9b4878cebadb3d3 = function(arg0) {
|
||||||
const ret = new Uint8Array(arg0 >>> 0);
|
const ret = new Uint8Array(arg0 >>> 0);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_subarray_13db269f57aa838d = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_subarray_a1f73cd4b5b42fe1 = function(arg0, arg1, arg2) {
|
||||||
const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);
|
const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
|
|
@ -817,16 +824,16 @@ function __wbg_get_imports() {
|
||||||
const ret = wasm.memory;
|
const ret = wasm.memory;
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper258 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper650 = function(arg0, arg1, arg2) {
|
||||||
const ret = makeMutClosure(arg0, arg1, 15, __wbg_adapter_20);
|
const ret = makeMutClosure(arg0, arg1, 217, __wbg_adapter_20);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper2954 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper3243 = function(arg0, arg1, arg2) {
|
||||||
const ret = makeMutClosure(arg0, arg1, 696, __wbg_adapter_23);
|
const ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_23);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper2956 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper3245 = function(arg0, arg1, arg2) {
|
||||||
const ret = makeMutClosure(arg0, arg1, 696, __wbg_adapter_23);
|
const ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_23);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -866,7 +873,7 @@ function initSync(module) {
|
||||||
async function __wbg_init(input) {
|
async function __wbg_init(input) {
|
||||||
if (wasm !== undefined) return wasm;
|
if (wasm !== undefined) return wasm;
|
||||||
|
|
||||||
if (typeof input === 'undefined' && script_src !== 'undefined') {
|
if (typeof input === 'undefined' && typeof script_src !== 'undefined') {
|
||||||
input = script_src.replace(/\.js$/, '_bg.wasm');
|
input = script_src.replace(/\.js$/, '_bg.wasm');
|
||||||
}
|
}
|
||||||
const imports = __wbg_get_imports();
|
const imports = __wbg_get_imports();
|
||||||
BIN
rust-rl_bg.wasm
Normal file
BIN
rust-rl_bg.wasm
Normal file
Binary file not shown.
|
|
@ -1,94 +0,0 @@
|
||||||
use crate::{ raws::Reaction, Faction, HasAncestry, Map, Position, TakingTurn, WantsToMelee };
|
|
||||||
use specs::prelude::*;
|
|
||||||
|
|
||||||
pub struct AdjacentAI {}
|
|
||||||
|
|
||||||
impl<'a> System<'a> for AdjacentAI {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type SystemData = (
|
|
||||||
WriteStorage<'a, TakingTurn>,
|
|
||||||
ReadStorage<'a, Faction>,
|
|
||||||
ReadStorage<'a, HasAncestry>,
|
|
||||||
ReadStorage<'a, Position>,
|
|
||||||
ReadExpect<'a, Map>,
|
|
||||||
WriteStorage<'a, WantsToMelee>,
|
|
||||||
Entities<'a>,
|
|
||||||
ReadExpect<'a, Entity>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (mut turns, factions, ancestries, positions, map, mut want_melee, entities, player) = data;
|
|
||||||
|
|
||||||
let mut turn_done: Vec<Entity> = Vec::new();
|
|
||||||
for (entity, _turn, pos) in (&entities, &turns, &positions).join() {
|
|
||||||
if entity != *player {
|
|
||||||
let mut reactions: Vec<(Entity, Reaction)> = Vec::new();
|
|
||||||
let idx = map.xy_idx(pos.x, pos.y);
|
|
||||||
let w = map.width;
|
|
||||||
let h = map.height;
|
|
||||||
// Add possible reactions to adjacents for each direction
|
|
||||||
if pos.x > 0 {
|
|
||||||
evaluate(entity, idx - 1, &ancestries, &factions, &mut reactions);
|
|
||||||
}
|
|
||||||
if pos.x < w - 1 {
|
|
||||||
evaluate(entity, idx + 1, &ancestries, &factions, &mut reactions);
|
|
||||||
}
|
|
||||||
if pos.y > 0 {
|
|
||||||
evaluate(entity, idx - (w as usize), &ancestries, &factions, &mut reactions);
|
|
||||||
}
|
|
||||||
if pos.y < h - 1 {
|
|
||||||
evaluate(entity, idx + (w as usize), &ancestries, &factions, &mut reactions);
|
|
||||||
}
|
|
||||||
if pos.y > 0 && pos.x > 0 {
|
|
||||||
evaluate(entity, idx - (w as usize) - 1, &ancestries, &factions, &mut reactions);
|
|
||||||
}
|
|
||||||
if pos.y > 0 && pos.x < w - 1 {
|
|
||||||
evaluate(entity, idx - (w as usize) + 1, &ancestries, &factions, &mut reactions);
|
|
||||||
}
|
|
||||||
if pos.y < h - 1 && pos.x > 0 {
|
|
||||||
evaluate(entity, idx + (w as usize) - 1, &ancestries, &factions, &mut reactions);
|
|
||||||
}
|
|
||||||
if pos.y < h - 1 && pos.x < w - 1 {
|
|
||||||
evaluate(entity, idx + (w as usize) + 1, &ancestries, &factions, &mut reactions);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut done = false;
|
|
||||||
for reaction in reactions.iter() {
|
|
||||||
if let Reaction::Attack = reaction.1 {
|
|
||||||
want_melee.insert(entity, WantsToMelee { target: reaction.0 }).expect("Error inserting melee");
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if done {
|
|
||||||
turn_done.push(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove turn marker for those that are done
|
|
||||||
for done in turn_done.iter() {
|
|
||||||
turns.remove(*done);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Evaluates all possible reactions between this faction and all entities on a given tile idx.
|
|
||||||
fn evaluate(
|
|
||||||
entity: Entity,
|
|
||||||
idx: usize,
|
|
||||||
ancestries: &ReadStorage<HasAncestry>,
|
|
||||||
factions: &ReadStorage<Faction>,
|
|
||||||
reactions: &mut Vec<(Entity, Reaction)>
|
|
||||||
) {
|
|
||||||
crate::spatial::for_each_tile_content(idx, |other_entity| {
|
|
||||||
let result = crate::raws::get_reactions(
|
|
||||||
entity,
|
|
||||||
other_entity,
|
|
||||||
&factions,
|
|
||||||
&ancestries,
|
|
||||||
&crate::raws::RAWS.lock().unwrap()
|
|
||||||
);
|
|
||||||
reactions.push((other_entity, result));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
use crate::{ EntityMoved, Map, Position, TakingTurn, Telepath, Viewshed, WantsToApproach };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
|
|
||||||
pub struct ApproachAI {}
|
|
||||||
|
|
||||||
impl<'a> System<'a> for ApproachAI {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type SystemData = (
|
|
||||||
WriteStorage<'a, TakingTurn>,
|
|
||||||
WriteStorage<'a, WantsToApproach>,
|
|
||||||
WriteStorage<'a, Position>,
|
|
||||||
WriteExpect<'a, Map>,
|
|
||||||
WriteStorage<'a, Viewshed>,
|
|
||||||
WriteStorage<'a, Telepath>,
|
|
||||||
WriteStorage<'a, EntityMoved>,
|
|
||||||
Entities<'a>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (
|
|
||||||
mut turns,
|
|
||||||
mut wants_to_approach,
|
|
||||||
mut positions,
|
|
||||||
mut map,
|
|
||||||
mut viewsheds,
|
|
||||||
mut telepaths,
|
|
||||||
mut entity_moved,
|
|
||||||
entities,
|
|
||||||
) = data;
|
|
||||||
let mut turn_done: Vec<Entity> = Vec::new();
|
|
||||||
for (entity, mut pos, approach, mut viewshed, _turn) in (
|
|
||||||
&entities,
|
|
||||||
&mut positions,
|
|
||||||
&wants_to_approach,
|
|
||||||
&mut viewsheds,
|
|
||||||
&turns,
|
|
||||||
).join() {
|
|
||||||
turn_done.push(entity);
|
|
||||||
let target_idxs = if
|
|
||||||
let Some(paths) = get_adjacent_unblocked(&map, approach.idx as usize)
|
|
||||||
{
|
|
||||||
paths
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let mut path: Option<NavigationPath> = None;
|
|
||||||
let idx = map.xy_idx(pos.x, pos.y);
|
|
||||||
for tar_idx in target_idxs {
|
|
||||||
let potential_path = a_star_search(idx, tar_idx, &mut *map);
|
|
||||||
if potential_path.success && potential_path.steps.len() > 1 {
|
|
||||||
if
|
|
||||||
path.is_none() ||
|
|
||||||
potential_path.steps.len() < path.as_ref().unwrap().steps.len()
|
|
||||||
{
|
|
||||||
path = Some(potential_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let path = if path.is_some() {
|
|
||||||
path.unwrap()
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if path.success && path.steps.len() > 1 {
|
|
||||||
let idx = map.xy_idx(pos.x, pos.y);
|
|
||||||
pos.x = (path.steps[1] as i32) % map.width;
|
|
||||||
pos.y = (path.steps[1] as i32) / map.width;
|
|
||||||
entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert EntityMoved");
|
|
||||||
let new_idx = map.xy_idx(pos.x, pos.y);
|
|
||||||
crate::spatial::move_entity(entity, idx, new_idx);
|
|
||||||
viewshed.dirty = true;
|
|
||||||
if let Some(telepath) = telepaths.get_mut(entity) {
|
|
||||||
telepath.dirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wants_to_approach.clear();
|
|
||||||
for done in turn_done.iter() {
|
|
||||||
turns.remove(*done);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to get an unblocked index within one tile of a given idx, or None.
|
|
||||||
pub fn get_adjacent_unblocked(map: &WriteExpect<Map>, idx: usize) -> Option<Vec<usize>> {
|
|
||||||
let mut adjacent = Vec::new();
|
|
||||||
let x = (idx as i32) % map.width;
|
|
||||||
let y = (idx as i32) / map.width;
|
|
||||||
for i in -1..2 {
|
|
||||||
for j in -1..2 {
|
|
||||||
if i == 0 && j == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let new_idx = (x + i + (y + j) * map.width) as usize;
|
|
||||||
if !crate::spatial::is_blocked(new_idx) {
|
|
||||||
adjacent.push(new_idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if adjacent.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
return Some(adjacent);
|
|
||||||
}
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
use crate::{ Chasing, EntityMoved, Map, Position, TakingTurn, Telepath, Viewshed };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use super::approach_ai_system::get_adjacent_unblocked;
|
|
||||||
|
|
||||||
// If the target is beyond this distance, they're no longer being detected,
|
|
||||||
// so stop following them. This is essentially a combined value of the sound
|
|
||||||
// the target might be making, noise, light, etc., anything they could do to
|
|
||||||
// be detected. As those constituent systems are developed, this value should
|
|
||||||
// be changed to being a result of some calculations between them.
|
|
||||||
const MAX_CHASE_DISTANCE: usize = 15;
|
|
||||||
pub struct ChaseAI {}
|
|
||||||
|
|
||||||
impl<'a> System<'a> for ChaseAI {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type SystemData = (
|
|
||||||
WriteStorage<'a, TakingTurn>,
|
|
||||||
WriteStorage<'a, Chasing>,
|
|
||||||
WriteStorage<'a, Position>,
|
|
||||||
WriteExpect<'a, Map>,
|
|
||||||
WriteStorage<'a, Viewshed>,
|
|
||||||
WriteStorage<'a, Telepath>,
|
|
||||||
WriteStorage<'a, EntityMoved>,
|
|
||||||
Entities<'a>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (
|
|
||||||
mut turns,
|
|
||||||
mut chasing,
|
|
||||||
mut positions,
|
|
||||||
mut map,
|
|
||||||
mut viewsheds,
|
|
||||||
mut telepaths,
|
|
||||||
mut entity_moved,
|
|
||||||
entities,
|
|
||||||
) = data;
|
|
||||||
let mut targets: HashMap<Entity, (i32, i32)> = HashMap::new();
|
|
||||||
let mut end_chase: Vec<Entity> = Vec::new();
|
|
||||||
// For every chasing entity with a turn, look for a valid target position,
|
|
||||||
// and if found, store that position in a temporary HashMap. This gets around
|
|
||||||
// needing to read Position twice - that would cause borrowchecker issues.
|
|
||||||
// If there's no valid target found, remove the chasing component.
|
|
||||||
for (entity, _turn, chasing) in (&entities, &turns, &chasing).join() {
|
|
||||||
if let Some(target_pos) = positions.get(chasing.target) {
|
|
||||||
targets.insert(entity, (target_pos.x, target_pos.y));
|
|
||||||
} else {
|
|
||||||
end_chase.push(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for done in end_chase.iter() {
|
|
||||||
chasing.remove(*done);
|
|
||||||
}
|
|
||||||
end_chase.clear();
|
|
||||||
// Iterate over everyone who is *still* chasing, and path to the target
|
|
||||||
// stored in the HashMap. If successful, follow the path. If not, remove
|
|
||||||
// the chasing component.
|
|
||||||
let mut turn_done: Vec<Entity> = Vec::new();
|
|
||||||
for (entity, _turn, mut pos, _chase, mut viewshed) in (
|
|
||||||
&entities,
|
|
||||||
&turns,
|
|
||||||
&mut positions,
|
|
||||||
&chasing,
|
|
||||||
&mut viewsheds,
|
|
||||||
).join() {
|
|
||||||
turn_done.push(entity);
|
|
||||||
let target_pos = targets[&entity];
|
|
||||||
let target_idx = map.xy_idx(target_pos.0, target_pos.1);
|
|
||||||
let target_idxs = if let Some(paths) = get_adjacent_unblocked(&map, target_idx) {
|
|
||||||
paths
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let mut path: Option<NavigationPath> = None;
|
|
||||||
let idx = map.xy_idx(pos.x, pos.y);
|
|
||||||
for tar_idx in target_idxs {
|
|
||||||
let potential_path = a_star_search(idx, tar_idx, &mut *map);
|
|
||||||
if potential_path.success && potential_path.steps.len() > 1 {
|
|
||||||
if
|
|
||||||
path.is_none() ||
|
|
||||||
potential_path.steps.len() < path.as_ref().unwrap().steps.len()
|
|
||||||
{
|
|
||||||
path = Some(potential_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let path = if path.is_some() {
|
|
||||||
path.unwrap()
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if path.success && path.steps.len() > 1 && path.steps.len() < MAX_CHASE_DISTANCE {
|
|
||||||
let idx = map.xy_idx(pos.x, pos.y);
|
|
||||||
pos.x = (path.steps[1] as i32) % map.width;
|
|
||||||
pos.y = (path.steps[1] as i32) / map.width;
|
|
||||||
entity_moved.insert(entity, EntityMoved {}).expect("Failed to insert EntityMoved");
|
|
||||||
let new_idx = map.xy_idx(pos.x, pos.y);
|
|
||||||
crate::spatial::move_entity(entity, idx, new_idx);
|
|
||||||
viewshed.dirty = true;
|
|
||||||
if let Some(is_telepath) = telepaths.get_mut(entity) {
|
|
||||||
is_telepath.dirty = true;
|
|
||||||
}
|
|
||||||
turn_done.push(entity);
|
|
||||||
} else {
|
|
||||||
end_chase.push(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for done in end_chase.iter() {
|
|
||||||
chasing.remove(*done);
|
|
||||||
}
|
|
||||||
for done in turn_done.iter() {
|
|
||||||
turns.remove(*done);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
use crate::{
|
|
||||||
tile_walkable,
|
|
||||||
EntityMoved,
|
|
||||||
Map,
|
|
||||||
MoveMode,
|
|
||||||
Movement,
|
|
||||||
Position,
|
|
||||||
TakingTurn,
|
|
||||||
Telepath,
|
|
||||||
Viewshed,
|
|
||||||
};
|
|
||||||
use specs::prelude::*;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
// Rolling a 1d8+x to decide where to move, where x are the number
|
|
||||||
// of dice rolls in which they will remian stationary. i.e. If this
|
|
||||||
// const is set to 8, there is a 50% chance of not wandering.
|
|
||||||
const CHANCE_OF_REMAINING_STATIONARY: i32 = 8;
|
|
||||||
pub struct DefaultAI {}
|
|
||||||
|
|
||||||
impl<'a> System<'a> for DefaultAI {
|
|
||||||
type SystemData = (
|
|
||||||
WriteStorage<'a, TakingTurn>,
|
|
||||||
WriteStorage<'a, MoveMode>,
|
|
||||||
WriteStorage<'a, Position>,
|
|
||||||
WriteExpect<'a, Map>,
|
|
||||||
WriteStorage<'a, Viewshed>,
|
|
||||||
WriteStorage<'a, Telepath>,
|
|
||||||
WriteStorage<'a, EntityMoved>,
|
|
||||||
WriteExpect<'a, RandomNumberGenerator>,
|
|
||||||
Entities<'a>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (
|
|
||||||
mut turns,
|
|
||||||
mut move_mode,
|
|
||||||
mut positions,
|
|
||||||
mut map,
|
|
||||||
mut viewsheds,
|
|
||||||
mut telepaths,
|
|
||||||
mut entity_moved,
|
|
||||||
mut rng,
|
|
||||||
entities,
|
|
||||||
) = data;
|
|
||||||
let mut turn_done: Vec<Entity> = Vec::new();
|
|
||||||
for (entity, _turn, mut pos, mut move_mode, mut viewshed) in (
|
|
||||||
&entities,
|
|
||||||
&turns,
|
|
||||||
&mut positions,
|
|
||||||
&mut move_mode,
|
|
||||||
&mut viewsheds,
|
|
||||||
).join() {
|
|
||||||
turn_done.push(entity);
|
|
||||||
match &mut move_mode.mode {
|
|
||||||
Movement::Static => {}
|
|
||||||
Movement::Random => {
|
|
||||||
let mut x = pos.x;
|
|
||||||
let mut y = pos.y;
|
|
||||||
let move_roll = rng.roll_dice(1, 8 + CHANCE_OF_REMAINING_STATIONARY);
|
|
||||||
match move_roll {
|
|
||||||
1 => {
|
|
||||||
x -= 1;
|
|
||||||
}
|
|
||||||
2 => {
|
|
||||||
x += 1;
|
|
||||||
}
|
|
||||||
3 => {
|
|
||||||
y -= 1;
|
|
||||||
}
|
|
||||||
4 => {
|
|
||||||
y += 1;
|
|
||||||
}
|
|
||||||
5 => {
|
|
||||||
x -= 1;
|
|
||||||
y -= 1;
|
|
||||||
}
|
|
||||||
6 => {
|
|
||||||
x += 1;
|
|
||||||
y -= 1;
|
|
||||||
}
|
|
||||||
7 => {
|
|
||||||
x -= 1;
|
|
||||||
y += 1;
|
|
||||||
}
|
|
||||||
8 => {
|
|
||||||
x += 1;
|
|
||||||
y += 1;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if x > 0 && x < map.width - 1 && y > 0 && y < map.height - 1 {
|
|
||||||
let dest_idx = map.xy_idx(x, y);
|
|
||||||
if !crate::spatial::is_blocked(dest_idx) {
|
|
||||||
let idx = map.xy_idx(pos.x, pos.y);
|
|
||||||
pos.x = x;
|
|
||||||
pos.y = y;
|
|
||||||
entity_moved
|
|
||||||
.insert(entity, EntityMoved {})
|
|
||||||
.expect("Unable to insert EntityMoved");
|
|
||||||
crate::spatial::move_entity(entity, idx, dest_idx);
|
|
||||||
viewshed.dirty = true;
|
|
||||||
if let Some(is_telepath) = telepaths.get_mut(entity) {
|
|
||||||
is_telepath.dirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Movement::RandomWaypoint { path } => {
|
|
||||||
if let Some(path) = path {
|
|
||||||
// We have a path - follow it
|
|
||||||
let idx = map.xy_idx(pos.x, pos.y);
|
|
||||||
if path.len() > 1 {
|
|
||||||
if !crate::spatial::is_blocked(path[1] as usize) {
|
|
||||||
pos.x = (path[1] as i32) % map.width;
|
|
||||||
pos.y = (path[1] as i32) / map.width;
|
|
||||||
entity_moved
|
|
||||||
.insert(entity, EntityMoved {})
|
|
||||||
.expect("Unable to insert EntityMoved");
|
|
||||||
let new_idx = map.xy_idx(pos.x, pos.y);
|
|
||||||
crate::spatial::move_entity(entity, idx, new_idx);
|
|
||||||
viewshed.dirty = true;
|
|
||||||
if let Some(is_telepath) = telepaths.get_mut(entity) {
|
|
||||||
is_telepath.dirty = true;
|
|
||||||
}
|
|
||||||
path.remove(0);
|
|
||||||
} else {
|
|
||||||
// If the path is blocked, recalculate a new path to the same waypoint.
|
|
||||||
let path = a_star_search(
|
|
||||||
map.xy_idx(pos.x, pos.y) as i32,
|
|
||||||
map.xy_idx(
|
|
||||||
(path[path.len() - 1] as i32) % map.width,
|
|
||||||
(path[path.len() - 1] as i32) / map.width
|
|
||||||
) as i32,
|
|
||||||
&mut *map
|
|
||||||
);
|
|
||||||
if path.success && path.steps.len() > 1 {
|
|
||||||
move_mode.mode = Movement::RandomWaypoint {
|
|
||||||
path: Some(path.steps),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
move_mode.mode = Movement::RandomWaypoint { path: None };
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let target_x = rng.roll_dice(1, map.width - 2);
|
|
||||||
let target_y = rng.roll_dice(1, map.height - 2);
|
|
||||||
let idx = map.xy_idx(target_x, target_y);
|
|
||||||
if tile_walkable(map.tiles[idx]) {
|
|
||||||
let path = a_star_search(
|
|
||||||
map.xy_idx(pos.x, pos.y) as i32,
|
|
||||||
map.xy_idx(target_x, target_y) as i32,
|
|
||||||
&mut *map
|
|
||||||
);
|
|
||||||
if path.success && path.steps.len() > 1 {
|
|
||||||
move_mode.mode = Movement::RandomWaypoint {
|
|
||||||
path: Some(path.steps),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for done in turn_done.iter() {
|
|
||||||
turns.remove(*done);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
use crate::{ gamelog, Attributes, Burden, EquipmentChanged, Equipped, InBackpack, Item, Pools };
|
|
||||||
use specs::prelude::*;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use crate::data::entity::CARRY_CAPACITY_PER_STRENGTH;
|
|
||||||
|
|
||||||
pub struct EncumbranceSystem {}
|
|
||||||
|
|
||||||
impl<'a> System<'a> for EncumbranceSystem {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type SystemData = (
|
|
||||||
WriteStorage<'a, EquipmentChanged>,
|
|
||||||
Entities<'a>,
|
|
||||||
ReadStorage<'a, Item>,
|
|
||||||
ReadStorage<'a, InBackpack>,
|
|
||||||
ReadStorage<'a, Equipped>,
|
|
||||||
WriteStorage<'a, Pools>,
|
|
||||||
ReadStorage<'a, Attributes>,
|
|
||||||
ReadExpect<'a, Entity>,
|
|
||||||
WriteStorage<'a, Burden>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (mut equip_dirty, entities, items, backpacks, wielded, mut pools, attributes, player, mut burdened) = data;
|
|
||||||
if equip_dirty.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Build update map
|
|
||||||
let mut to_update: HashMap<Entity, f32> = HashMap::new();
|
|
||||||
for (entity, _dirty) in (&entities, &equip_dirty).join() {
|
|
||||||
to_update.insert(entity, 0.0);
|
|
||||||
}
|
|
||||||
equip_dirty.clear();
|
|
||||||
// Total up equipped items
|
|
||||||
for (item, equipped) in (&items, &wielded).join() {
|
|
||||||
if to_update.contains_key(&equipped.owner) {
|
|
||||||
let totals = to_update.get_mut(&equipped.owner).unwrap();
|
|
||||||
*totals += item.weight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Total carried items
|
|
||||||
for (item, carried) in (&items, &backpacks).join() {
|
|
||||||
if to_update.contains_key(&carried.owner) {
|
|
||||||
let totals = to_update.get_mut(&carried.owner).unwrap();
|
|
||||||
*totals += item.weight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Apply to pools
|
|
||||||
for (entity, weight) in to_update.iter() {
|
|
||||||
if let Some(pool) = pools.get_mut(*entity) {
|
|
||||||
pool.weight = *weight;
|
|
||||||
if let Some(attr) = attributes.get(*entity) {
|
|
||||||
let carry_capacity_lbs =
|
|
||||||
(attr.strength.base + attr.strength.modifiers) * CARRY_CAPACITY_PER_STRENGTH;
|
|
||||||
if (pool.weight as i32) > 3 * carry_capacity_lbs {
|
|
||||||
// Overloaded
|
|
||||||
burdened
|
|
||||||
.insert(*entity, Burden { level: crate::BurdenLevel::Overloaded })
|
|
||||||
.expect("Failed to insert Burden");
|
|
||||||
if *entity == *player {
|
|
||||||
gamelog::Logger::new().append("You're overloaded!").log();
|
|
||||||
}
|
|
||||||
} else if (pool.weight as i32) > 2 * carry_capacity_lbs {
|
|
||||||
// Strained
|
|
||||||
burdened
|
|
||||||
.insert(*entity, Burden { level: crate::BurdenLevel::Strained })
|
|
||||||
.expect("Failed to insert Burden");
|
|
||||||
if *entity == *player {
|
|
||||||
gamelog::Logger::new().append("You're strained.").log();
|
|
||||||
}
|
|
||||||
} else if (pool.weight as i32) > carry_capacity_lbs {
|
|
||||||
// Burdened
|
|
||||||
burdened
|
|
||||||
.insert(*entity, Burden { level: crate::BurdenLevel::Burdened })
|
|
||||||
.expect("Failed to insert Burden");
|
|
||||||
if *entity == *player {
|
|
||||||
gamelog::Logger::new().append("You're burdened.").log();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
burdened.remove(*entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,178 +0,0 @@
|
||||||
use crate::data::entity::*;
|
|
||||||
use crate::{
|
|
||||||
Burden,
|
|
||||||
BurdenLevel,
|
|
||||||
Clock,
|
|
||||||
Energy,
|
|
||||||
Name,
|
|
||||||
Position,
|
|
||||||
RunState,
|
|
||||||
Map,
|
|
||||||
TakingTurn,
|
|
||||||
Confusion,
|
|
||||||
Intrinsics,
|
|
||||||
};
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
use crate::config::CONFIG;
|
|
||||||
use crate::data::events::*;
|
|
||||||
|
|
||||||
pub struct EnergySystem {}
|
|
||||||
|
|
||||||
const TURN_COST: i32 = NORMAL_SPEED * TURN_COST_MULTIPLIER;
|
|
||||||
|
|
||||||
impl<'a> System<'a> for EnergySystem {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type SystemData = (
|
|
||||||
ReadExpect<'a, Map>,
|
|
||||||
ReadStorage<'a, Clock>,
|
|
||||||
WriteStorage<'a, Energy>,
|
|
||||||
ReadStorage<'a, Burden>,
|
|
||||||
ReadStorage<'a, Position>,
|
|
||||||
WriteStorage<'a, TakingTurn>,
|
|
||||||
Entities<'a>,
|
|
||||||
WriteExpect<'a, RandomNumberGenerator>,
|
|
||||||
WriteExpect<'a, RunState>,
|
|
||||||
ReadExpect<'a, Entity>,
|
|
||||||
ReadStorage<'a, Name>,
|
|
||||||
ReadExpect<'a, Point>,
|
|
||||||
ReadStorage<'a, Confusion>,
|
|
||||||
ReadStorage<'a, Intrinsics>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (
|
|
||||||
map,
|
|
||||||
clock,
|
|
||||||
mut energies,
|
|
||||||
burdens,
|
|
||||||
positions,
|
|
||||||
mut turns,
|
|
||||||
entities,
|
|
||||||
mut rng,
|
|
||||||
mut runstate,
|
|
||||||
player,
|
|
||||||
names,
|
|
||||||
player_pos,
|
|
||||||
confusion,
|
|
||||||
intrinsics,
|
|
||||||
) = data;
|
|
||||||
// If not ticking, do nothing.
|
|
||||||
if *runstate != RunState::Ticking {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Clear TakingTurn{} from every entity.
|
|
||||||
turns.clear();
|
|
||||||
// TURN COUNTER
|
|
||||||
for (entity, _clock, energy) in (&entities, &clock, &mut energies).join() {
|
|
||||||
energy.current += NORMAL_SPEED;
|
|
||||||
if energy.current >= TURN_COST {
|
|
||||||
turns
|
|
||||||
.insert(entity, TakingTurn {})
|
|
||||||
.expect("Unable to insert turn for turn counter.");
|
|
||||||
energy.current -= TURN_COST;
|
|
||||||
crate::gamelog::record_event(EVENT::Turn(1));
|
|
||||||
// Handle spawning mobs each turn
|
|
||||||
if CONFIG.logging.log_ticks {
|
|
||||||
console::log(
|
|
||||||
format!(
|
|
||||||
"===== TURN {} =====",
|
|
||||||
crate::gamelog::get_event_count(EVENT::COUNT_TURN)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// EVERYTHING ELSE
|
|
||||||
for (entity, energy, pos, _c) in (
|
|
||||||
&entities,
|
|
||||||
&mut energies,
|
|
||||||
&positions,
|
|
||||||
!&confusion,
|
|
||||||
).join() {
|
|
||||||
let burden_modifier = get_burden_modifier(&burdens, entity);
|
|
||||||
let overmap_mod = get_overmap_modifier(&map);
|
|
||||||
let intrinsic_speed = get_intrinsic_speed(&intrinsics, entity);
|
|
||||||
// Every entity has a POTENTIAL equal to their speed.
|
|
||||||
let mut energy_potential: i32 = ((energy.speed as f32) *
|
|
||||||
burden_modifier *
|
|
||||||
overmap_mod *
|
|
||||||
intrinsic_speed) as i32;
|
|
||||||
// Increment current energy by NORMAL_SPEED for every
|
|
||||||
// whole number of NORMAL_SPEEDS in their POTENTIAL.
|
|
||||||
while energy_potential >= NORMAL_SPEED {
|
|
||||||
energy_potential -= NORMAL_SPEED;
|
|
||||||
energy.current += NORMAL_SPEED;
|
|
||||||
}
|
|
||||||
// Roll a NORMAL_SPEED-sided die. If less than their
|
|
||||||
// remaining POTENTIAL, increment current energy by
|
|
||||||
// NORMAL_SPEED.
|
|
||||||
// i.e. An entity with a speed of 3/4ths NORMAL_SPEED
|
|
||||||
// will gain NORMAL_SPEED energy in 75% of ticks.
|
|
||||||
if energy_potential > 0 {
|
|
||||||
if rng.roll_dice(1, NORMAL_SPEED) <= energy_potential {
|
|
||||||
energy.current += NORMAL_SPEED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TURN_COST is equal to 4 * NORMAL_SPEED. If the current entity
|
|
||||||
// has enough energy, they take a turn and decrement their energy
|
|
||||||
// by TURN_COST. If the current entity is the player, await input.
|
|
||||||
if energy.current >= TURN_COST {
|
|
||||||
energy.current -= TURN_COST;
|
|
||||||
if entity == *player {
|
|
||||||
*runstate = RunState::AwaitingInput;
|
|
||||||
} else if cull_turn_by_distance(&player_pos, pos) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
turns.insert(entity, TakingTurn {}).expect("Unable to insert turn.");
|
|
||||||
if CONFIG.logging.log_ticks {
|
|
||||||
let name = if let Some(name) = names.get(entity) {
|
|
||||||
&name.name
|
|
||||||
} else {
|
|
||||||
"Unknown entity"
|
|
||||||
};
|
|
||||||
console::log(
|
|
||||||
format!(
|
|
||||||
"ENERGY SYSTEM: {} granted a turn. [leftover energy: {}].",
|
|
||||||
name,
|
|
||||||
energy.current
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_burden_modifier(burdens: &ReadStorage<Burden>, entity: Entity) -> f32 {
|
|
||||||
return if let Some(burden) = burdens.get(entity) {
|
|
||||||
match burden.level {
|
|
||||||
BurdenLevel::Burdened => SPEED_MOD_BURDENED,
|
|
||||||
BurdenLevel::Strained => SPEED_MOD_STRAINED,
|
|
||||||
BurdenLevel::Overloaded => SPEED_MOD_OVERLOADED,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_overmap_modifier(map: &ReadExpect<Map>) -> f32 {
|
|
||||||
return if map.overmap { SPEED_MOD_OVERMAP_TRAVEL } else { 1.0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cull_turn_by_distance(player_pos: &Point, pos: &Position) -> bool {
|
|
||||||
let distance = DistanceAlg::Pythagoras.distance2d(*player_pos, Point::new(pos.x, pos.y));
|
|
||||||
if distance > 20.0 {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_intrinsic_speed(intrinsics: &ReadStorage<Intrinsics>, entity: Entity) -> f32 {
|
|
||||||
if let Some(intrinsics) = intrinsics.get(entity) {
|
|
||||||
if intrinsics.list.contains(&crate::Intrinsic::Speed) {
|
|
||||||
return 4.0 / 3.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1.0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
use crate::{ EntityMoved, Map, Position, TakingTurn, Telepath, Viewshed, WantsToFlee };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
|
|
||||||
pub struct FleeAI {}
|
|
||||||
|
|
||||||
impl<'a> System<'a> for FleeAI {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type SystemData = (
|
|
||||||
WriteStorage<'a, TakingTurn>,
|
|
||||||
WriteStorage<'a, WantsToFlee>,
|
|
||||||
WriteStorage<'a, Position>,
|
|
||||||
WriteExpect<'a, Map>,
|
|
||||||
WriteStorage<'a, Viewshed>,
|
|
||||||
WriteStorage<'a, Telepath>,
|
|
||||||
WriteStorage<'a, EntityMoved>,
|
|
||||||
Entities<'a>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (
|
|
||||||
mut turns,
|
|
||||||
mut wants_to_flee,
|
|
||||||
mut positions,
|
|
||||||
mut map,
|
|
||||||
mut viewsheds,
|
|
||||||
mut telepaths,
|
|
||||||
mut entity_moved,
|
|
||||||
entities,
|
|
||||||
) = data;
|
|
||||||
let mut turn_done: Vec<Entity> = Vec::new();
|
|
||||||
for (entity, _turn, mut pos, fleeing, mut viewshed) in (
|
|
||||||
&entities,
|
|
||||||
&turns,
|
|
||||||
&mut positions,
|
|
||||||
&wants_to_flee,
|
|
||||||
&mut viewsheds,
|
|
||||||
).join() {
|
|
||||||
turn_done.push(entity);
|
|
||||||
let my_idx = map.xy_idx(pos.x, pos.y);
|
|
||||||
map.populate_blocked();
|
|
||||||
let flee_map = DijkstraMap::new(
|
|
||||||
map.width as usize,
|
|
||||||
map.height as usize,
|
|
||||||
&fleeing.indices,
|
|
||||||
&*map,
|
|
||||||
100.0
|
|
||||||
);
|
|
||||||
let flee_target = DijkstraMap::find_highest_exit(&flee_map, my_idx, &*map);
|
|
||||||
if let Some(flee_target) = flee_target {
|
|
||||||
if !crate::spatial::is_blocked(flee_target as usize) {
|
|
||||||
crate::spatial::move_entity(entity, my_idx, flee_target);
|
|
||||||
viewshed.dirty = true;
|
|
||||||
if let Some(is_telepath) = telepaths.get_mut(entity) {
|
|
||||||
is_telepath.dirty = true;
|
|
||||||
}
|
|
||||||
pos.x = (flee_target as i32) % map.width;
|
|
||||||
pos.y = (flee_target as i32) / map.width;
|
|
||||||
entity_moved
|
|
||||||
.insert(entity, EntityMoved {})
|
|
||||||
.expect("Unable to insert EntityMoved");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wants_to_flee.clear();
|
|
||||||
for done in turn_done.iter() {
|
|
||||||
turns.remove(*done);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
mod energy_system;
|
|
||||||
pub use energy_system::EnergySystem;
|
|
||||||
mod turn_status_system;
|
|
||||||
pub use turn_status_system::TurnStatusSystem;
|
|
||||||
mod quip_system;
|
|
||||||
pub use quip_system::QuipSystem;
|
|
||||||
mod regen_system;
|
|
||||||
pub use regen_system::RegenSystem;
|
|
||||||
mod encumbrance_system;
|
|
||||||
pub use encumbrance_system::EncumbranceSystem;
|
|
||||||
mod adjacent_ai_system;
|
|
||||||
pub use adjacent_ai_system::AdjacentAI;
|
|
||||||
mod visible_ai_system;
|
|
||||||
pub use visible_ai_system::VisibleAI;
|
|
||||||
mod approach_ai_system;
|
|
||||||
pub use approach_ai_system::ApproachAI;
|
|
||||||
mod chase_ai_system;
|
|
||||||
pub use chase_ai_system::ChaseAI;
|
|
||||||
mod flee_ai_system;
|
|
||||||
pub use flee_ai_system::FleeAI;
|
|
||||||
mod default_move_system;
|
|
||||||
pub use default_move_system::DefaultAI;
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
use crate::{ gamelog, gui::renderable_colour, Name, Quips, Renderable, TakingTurn, Viewshed };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
|
|
||||||
pub struct QuipSystem {}
|
|
||||||
|
|
||||||
impl<'a> System<'a> for QuipSystem {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type SystemData = (
|
|
||||||
Entities<'a>,
|
|
||||||
WriteStorage<'a, Quips>,
|
|
||||||
ReadStorage<'a, Name>,
|
|
||||||
ReadStorage<'a, Renderable>,
|
|
||||||
ReadStorage<'a, TakingTurn>,
|
|
||||||
ReadExpect<'a, Point>,
|
|
||||||
ReadStorage<'a, Viewshed>,
|
|
||||||
WriteExpect<'a, RandomNumberGenerator>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (entities, mut quips, names, renderables, turns, player_pos, viewsheds, mut rng) = data;
|
|
||||||
for (entity, quip, name, viewshed, _turn) in (
|
|
||||||
&entities,
|
|
||||||
&mut quips,
|
|
||||||
&names,
|
|
||||||
&viewsheds,
|
|
||||||
&turns,
|
|
||||||
).join() {
|
|
||||||
if
|
|
||||||
!quip.available.is_empty() &&
|
|
||||||
viewshed.visible_tiles.contains(&player_pos) &&
|
|
||||||
rng.roll_dice(1, 6) == 1
|
|
||||||
{
|
|
||||||
let quip_index = if quip.available.len() == 1 {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
(rng.roll_dice(1, quip.available.len() as i32) - 1) as usize
|
|
||||||
};
|
|
||||||
gamelog::Logger
|
|
||||||
::new()
|
|
||||||
.append("The")
|
|
||||||
.colour(renderable_colour(&renderables, entity))
|
|
||||||
.append(&name.name)
|
|
||||||
.colour(WHITE)
|
|
||||||
.append_n("says \"")
|
|
||||||
.append_n(&quip.available[quip_index])
|
|
||||||
.append("\"")
|
|
||||||
.log();
|
|
||||||
quip.available.remove(quip_index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
use crate::{
|
|
||||||
gamelog,
|
|
||||||
gui::Class,
|
|
||||||
Attributes,
|
|
||||||
Clock,
|
|
||||||
HasClass,
|
|
||||||
Player,
|
|
||||||
Pools,
|
|
||||||
Position,
|
|
||||||
RandomNumberGenerator,
|
|
||||||
TakingTurn,
|
|
||||||
Intrinsics,
|
|
||||||
};
|
|
||||||
use specs::prelude::*;
|
|
||||||
use crate::data::events::*;
|
|
||||||
|
|
||||||
pub struct RegenSystem {}
|
|
||||||
|
|
||||||
const MONSTER_HP_REGEN_TURN: i32 = 20;
|
|
||||||
const MONSTER_HP_REGEN_PER_TICK: i32 = 1;
|
|
||||||
|
|
||||||
const WIZARD_MP_REGEN_MOD: i32 = 3;
|
|
||||||
const NONWIZARD_MP_REGEN_MOD: i32 = 4;
|
|
||||||
const MP_REGEN_BASE: i32 = 38;
|
|
||||||
const MP_REGEN_DIVISOR: i32 = 6;
|
|
||||||
const MIN_MP_REGEN_PER_TURN: i32 = 1;
|
|
||||||
|
|
||||||
impl<'a> System<'a> for RegenSystem {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type SystemData = (
|
|
||||||
ReadStorage<'a, Clock>,
|
|
||||||
Entities<'a>,
|
|
||||||
ReadStorage<'a, Position>,
|
|
||||||
WriteStorage<'a, Pools>,
|
|
||||||
ReadStorage<'a, TakingTurn>,
|
|
||||||
ReadStorage<'a, Player>,
|
|
||||||
ReadStorage<'a, HasClass>,
|
|
||||||
ReadStorage<'a, Attributes>,
|
|
||||||
WriteExpect<'a, RandomNumberGenerator>,
|
|
||||||
ReadStorage<'a, Intrinsics>,
|
|
||||||
ReadExpect<'a, Entity>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (
|
|
||||||
clock,
|
|
||||||
entities,
|
|
||||||
positions,
|
|
||||||
mut pools,
|
|
||||||
turns,
|
|
||||||
player,
|
|
||||||
classes,
|
|
||||||
attributes,
|
|
||||||
mut rng,
|
|
||||||
intrinsics,
|
|
||||||
player_entity,
|
|
||||||
) = data;
|
|
||||||
let mut clock_turn = false;
|
|
||||||
for (_e, _c, _t) in (&entities, &clock, &turns).join() {
|
|
||||||
clock_turn = true;
|
|
||||||
}
|
|
||||||
if !clock_turn {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Monster HP regen
|
|
||||||
let current_turn = gamelog::get_event_count(EVENT::COUNT_TURN);
|
|
||||||
if current_turn % MONSTER_HP_REGEN_TURN == 0 {
|
|
||||||
for (_e, _p, pool, _player) in (&entities, &positions, &mut pools, !&player).join() {
|
|
||||||
try_hp_regen_tick(pool, MONSTER_HP_REGEN_PER_TICK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Player HP regen
|
|
||||||
let level = gamelog::get_event_count(EVENT::COUNT_LEVEL);
|
|
||||||
if
|
|
||||||
current_turn % get_player_hp_regen_turn(level) == 0 ||
|
|
||||||
intrinsics.get(*player_entity).unwrap().list.contains(&crate::Intrinsic::Regeneration)
|
|
||||||
{
|
|
||||||
for (_e, _p, pool, _player) in (&entities, &positions, &mut pools, &player).join() {
|
|
||||||
try_hp_regen_tick(pool, get_player_hp_regen_per_tick(level));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Both MP regen
|
|
||||||
for (e, _p, pool) in (&entities, &positions, &mut pools).join() {
|
|
||||||
let is_wizard = if let Some(class) = classes.get(e) {
|
|
||||||
class.name == Class::Wizard
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
let numerator = if is_wizard { WIZARD_MP_REGEN_MOD } else { NONWIZARD_MP_REGEN_MOD };
|
|
||||||
let multiplier: f32 = (numerator as f32) / (MP_REGEN_DIVISOR as f32);
|
|
||||||
let mp_regen_tick = (((MP_REGEN_BASE - pool.level) as f32) * multiplier) as i32;
|
|
||||||
if current_turn % mp_regen_tick == 0 {
|
|
||||||
try_mana_regen_tick(
|
|
||||||
pool,
|
|
||||||
rng.roll_dice(1, get_mana_regen_per_tick(e, &attributes))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_player_hp_regen_turn(level: i32) -> i32 {
|
|
||||||
if level < 10 {
|
|
||||||
return 42 / (level + 2) + 1;
|
|
||||||
} else {
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_player_hp_regen_per_tick(level: i32) -> i32 {
|
|
||||||
if level < 10 {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_hp_regen_tick(pool: &mut Pools, amount: i32) {
|
|
||||||
pool.hit_points.current = i32::min(pool.hit_points.current + amount, pool.hit_points.max);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_mana_regen_per_tick(e: Entity, attributes: &ReadStorage<Attributes>) -> i32 {
|
|
||||||
let regen = if let Some(attributes) = attributes.get(e) {
|
|
||||||
(attributes.intelligence.bonus + attributes.wisdom.bonus) / 2 + MIN_MP_REGEN_PER_TURN
|
|
||||||
} else {
|
|
||||||
MIN_MP_REGEN_PER_TURN
|
|
||||||
};
|
|
||||||
return i32::max(regen, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_mana_regen_tick(pool: &mut Pools, amount: i32) {
|
|
||||||
pool.mana.current = i32::min(pool.mana.current + amount, pool.mana.max);
|
|
||||||
}
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
use crate::{
|
|
||||||
effects::{ add_effect, EffectType, Targets },
|
|
||||||
gamelog,
|
|
||||||
gui::renderable_colour,
|
|
||||||
Clock,
|
|
||||||
Confusion,
|
|
||||||
Name,
|
|
||||||
Renderable,
|
|
||||||
TakingTurn,
|
|
||||||
Item,
|
|
||||||
Prop,
|
|
||||||
};
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
use crate::data::events::*;
|
|
||||||
|
|
||||||
pub struct TurnStatusSystem {}
|
|
||||||
|
|
||||||
impl<'a> System<'a> for TurnStatusSystem {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type SystemData = (
|
|
||||||
WriteStorage<'a, TakingTurn>,
|
|
||||||
ReadStorage<'a, Clock>,
|
|
||||||
WriteStorage<'a, Confusion>,
|
|
||||||
Entities<'a>,
|
|
||||||
ReadStorage<'a, Name>,
|
|
||||||
ReadExpect<'a, Entity>,
|
|
||||||
ReadStorage<'a, Renderable>,
|
|
||||||
ReadStorage<'a, Item>,
|
|
||||||
ReadStorage<'a, Prop>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (
|
|
||||||
mut turns,
|
|
||||||
clock,
|
|
||||||
mut confusion,
|
|
||||||
entities,
|
|
||||||
names,
|
|
||||||
player_entity,
|
|
||||||
renderables,
|
|
||||||
items,
|
|
||||||
props,
|
|
||||||
) = data;
|
|
||||||
let mut clock_tick = false;
|
|
||||||
for (_e, _c, _t) in (&entities, &clock, &turns).join() {
|
|
||||||
clock_tick = true;
|
|
||||||
}
|
|
||||||
if !clock_tick {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut logger = gamelog::Logger::new();
|
|
||||||
let mut log = false;
|
|
||||||
let mut not_my_turn: Vec<Entity> = Vec::new();
|
|
||||||
let mut not_confused: Vec<Entity> = Vec::new();
|
|
||||||
for (entity, confused, name, _i, _p) in (
|
|
||||||
&entities,
|
|
||||||
&mut confusion,
|
|
||||||
&names,
|
|
||||||
!&items,
|
|
||||||
!&props,
|
|
||||||
).join() {
|
|
||||||
confused.turns -= 1;
|
|
||||||
if confused.turns < 1 {
|
|
||||||
not_confused.push(entity);
|
|
||||||
if entity == *player_entity {
|
|
||||||
logger = logger
|
|
||||||
.append("You")
|
|
||||||
.append("snap out of it.");
|
|
||||||
log = true;
|
|
||||||
} else {
|
|
||||||
logger = logger
|
|
||||||
.append("The")
|
|
||||||
.colour(renderable_colour(&renderables, entity))
|
|
||||||
.append(&name.name)
|
|
||||||
.colour(WHITE)
|
|
||||||
.append("snaps out of it.");
|
|
||||||
log = true;
|
|
||||||
}
|
|
||||||
add_effect(
|
|
||||||
None,
|
|
||||||
EffectType::Particle {
|
|
||||||
glyph: to_cp437('!'),
|
|
||||||
fg: RGB::named(LIGHT_BLUE),
|
|
||||||
bg: RGB::named(BLACK),
|
|
||||||
lifespan: 200.0,
|
|
||||||
delay: 0.0,
|
|
||||||
},
|
|
||||||
Targets::Entity { target: entity }
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
not_my_turn.push(entity);
|
|
||||||
if entity == *player_entity {
|
|
||||||
logger = logger
|
|
||||||
.append("You")
|
|
||||||
.append("are confused!");
|
|
||||||
log = true;
|
|
||||||
gamelog::record_event(EVENT::PlayerConfused(1));
|
|
||||||
} else {
|
|
||||||
logger = logger
|
|
||||||
.append("The")
|
|
||||||
.colour(renderable_colour(&renderables, entity))
|
|
||||||
.append(&name.name)
|
|
||||||
.colour(WHITE)
|
|
||||||
.append("is confused!");
|
|
||||||
log = true;
|
|
||||||
}
|
|
||||||
add_effect(
|
|
||||||
None,
|
|
||||||
EffectType::Particle {
|
|
||||||
glyph: to_cp437('?'),
|
|
||||||
fg: RGB::named(MAGENTA),
|
|
||||||
bg: RGB::named(BLACK),
|
|
||||||
lifespan: 200.0,
|
|
||||||
delay: 0.0,
|
|
||||||
},
|
|
||||||
Targets::Entity { target: entity }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if log {
|
|
||||||
logger.log();
|
|
||||||
}
|
|
||||||
for e in not_my_turn {
|
|
||||||
turns.remove(e);
|
|
||||||
}
|
|
||||||
for e in not_confused {
|
|
||||||
confusion.remove(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
use crate::{
|
|
||||||
raws::Reaction,
|
|
||||||
Chasing,
|
|
||||||
Faction,
|
|
||||||
HasAncestry,
|
|
||||||
Map,
|
|
||||||
Mind,
|
|
||||||
Position,
|
|
||||||
TakingTurn,
|
|
||||||
Telepath,
|
|
||||||
Viewshed,
|
|
||||||
WantsToApproach,
|
|
||||||
WantsToFlee,
|
|
||||||
};
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
pub struct VisibleAI {}
|
|
||||||
|
|
||||||
impl<'a> System<'a> for VisibleAI {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type SystemData = (
|
|
||||||
ReadStorage<'a, TakingTurn>,
|
|
||||||
ReadStorage<'a, Faction>,
|
|
||||||
ReadStorage<'a, HasAncestry>,
|
|
||||||
ReadStorage<'a, Position>,
|
|
||||||
ReadExpect<'a, Map>,
|
|
||||||
WriteStorage<'a, WantsToApproach>,
|
|
||||||
WriteStorage<'a, WantsToFlee>,
|
|
||||||
Entities<'a>,
|
|
||||||
ReadExpect<'a, Entity>,
|
|
||||||
ReadStorage<'a, Viewshed>,
|
|
||||||
ReadStorage<'a, Telepath>,
|
|
||||||
ReadStorage<'a, Mind>,
|
|
||||||
WriteStorage<'a, Chasing>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (
|
|
||||||
turns,
|
|
||||||
factions,
|
|
||||||
ancestries,
|
|
||||||
positions,
|
|
||||||
map,
|
|
||||||
mut wants_to_approach,
|
|
||||||
mut wants_to_flee,
|
|
||||||
entities,
|
|
||||||
player,
|
|
||||||
viewsheds,
|
|
||||||
telepaths,
|
|
||||||
minds,
|
|
||||||
mut chasing,
|
|
||||||
) = data;
|
|
||||||
|
|
||||||
for (entity, _turn, pos, viewshed) in (&entities, &turns, &positions, &viewsheds).join() {
|
|
||||||
if entity == *player {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let this_idx = map.xy_idx(pos.x, pos.y);
|
|
||||||
let mut reactions: Vec<(usize, Reaction, Entity)> = Vec::new();
|
|
||||||
let mut flee: Vec<usize> = Vec::new();
|
|
||||||
let mut idxs: HashSet<usize> = HashSet::new();
|
|
||||||
for visible_tile in viewshed.visible_tiles.iter() {
|
|
||||||
let idx = map.xy_idx(visible_tile.x, visible_tile.y);
|
|
||||||
if this_idx != idx {
|
|
||||||
evaluate(entity, idx, &ancestries, &factions, &mut reactions, None);
|
|
||||||
idxs.insert(idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(is_telepath) = telepaths.get(entity) {
|
|
||||||
for telepath_tile in is_telepath.telepath_tiles.iter() {
|
|
||||||
let idx = map.xy_idx(telepath_tile.x, telepath_tile.y);
|
|
||||||
// If we didn't already evaluate this idx (if it's not contained in the HashSet),
|
|
||||||
// and it's not the idx we're standing on, then evaluate here w/ minds taken into
|
|
||||||
// account.
|
|
||||||
if this_idx != idx && !idxs.contains(&idx) {
|
|
||||||
evaluate(entity, idx, &ancestries, &factions, &mut reactions, Some(&minds));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reactions.sort_by(|(a, _, _), (b, _, _)| {
|
|
||||||
let (a_x, a_y) = (a % (map.width as usize), a / (map.width as usize));
|
|
||||||
let dist_a = DistanceAlg::PythagorasSquared.distance2d(
|
|
||||||
Point::new(a_x, a_y),
|
|
||||||
Point::new(pos.x, pos.y)
|
|
||||||
);
|
|
||||||
let dist_a_estimate = dist_a as i32;
|
|
||||||
let (b_x, b_y) = (b % (map.width as usize), b / (map.width as usize));
|
|
||||||
let dist_b = DistanceAlg::PythagorasSquared.distance2d(
|
|
||||||
Point::new(b_x, b_y),
|
|
||||||
Point::new(pos.x, pos.y)
|
|
||||||
);
|
|
||||||
let dist_b_estimate = dist_b as i32;
|
|
||||||
return dist_b_estimate.cmp(&dist_a_estimate);
|
|
||||||
});
|
|
||||||
let mut found_flee = false;
|
|
||||||
for reaction in reactions.iter() {
|
|
||||||
match reaction.1 {
|
|
||||||
Reaction::Attack => {
|
|
||||||
if !found_flee {
|
|
||||||
wants_to_approach
|
|
||||||
.insert(entity, WantsToApproach { idx: reaction.0 as i32 })
|
|
||||||
.expect("Error inserting WantsToApproach");
|
|
||||||
chasing
|
|
||||||
.insert(entity, Chasing { target: reaction.2 })
|
|
||||||
.expect("Unable to insert Chasing");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reaction::Flee => {
|
|
||||||
flee.push(reaction.0);
|
|
||||||
found_flee = true;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !flee.is_empty() {
|
|
||||||
wants_to_flee
|
|
||||||
.insert(entity, WantsToFlee { indices: flee })
|
|
||||||
.expect("Unable to insert");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn evaluate(
|
|
||||||
entity: Entity,
|
|
||||||
idx: usize,
|
|
||||||
ancestries: &ReadStorage<HasAncestry>,
|
|
||||||
factions: &ReadStorage<Faction>,
|
|
||||||
reactions: &mut Vec<(usize, Reaction, Entity)>,
|
|
||||||
minds: Option<&ReadStorage<Mind>>
|
|
||||||
) {
|
|
||||||
crate::spatial::for_each_tile_content(idx, |other_entity| {
|
|
||||||
let mut check = true;
|
|
||||||
if minds.is_some() {
|
|
||||||
console::log("Minds got passed! Evaluating!");
|
|
||||||
if minds.unwrap().get(other_entity).is_none() {
|
|
||||||
console::log("No brain here. Skipping!");
|
|
||||||
check = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if check {
|
|
||||||
reactions.push((
|
|
||||||
idx,
|
|
||||||
crate::raws::get_reactions(
|
|
||||||
entity,
|
|
||||||
other_entity,
|
|
||||||
&factions,
|
|
||||||
&ancestries,
|
|
||||||
&crate::raws::RAWS.lock().unwrap()
|
|
||||||
),
|
|
||||||
other_entity,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
171
src/camera.rs
171
src/camera.rs
|
|
@ -1,171 +0,0 @@
|
||||||
use super::{ Hidden, Map, Mind, Position, Prop, Renderable };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
use std::ops::Mul;
|
|
||||||
|
|
||||||
const SHOW_BOUNDARIES: bool = false;
|
|
||||||
|
|
||||||
pub fn get_screen_bounds(ecs: &World, _ctx: &mut BTerm) -> (i32, i32, i32, i32, i32, i32) {
|
|
||||||
let player_pos = ecs.fetch::<Point>();
|
|
||||||
let map = ecs.fetch::<Map>();
|
|
||||||
let (x_chars, y_chars, mut x_offset, mut y_offset) = (69, 41, 1, 10);
|
|
||||||
|
|
||||||
let centre_x = (x_chars / 2) as i32;
|
|
||||||
let centre_y = (y_chars / 2) as i32;
|
|
||||||
|
|
||||||
let min_x = if map.width < (x_chars as i32) {
|
|
||||||
x_offset += ((x_chars as i32) - map.width) / 2;
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
(player_pos.x - centre_x).clamp(0, map.width - (x_chars as i32))
|
|
||||||
};
|
|
||||||
let min_y = if map.height < (y_chars as i32) {
|
|
||||||
y_offset += ((y_chars as i32) - map.height) / 2;
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
(player_pos.y - centre_y).clamp(0, map.height - (y_chars as i32))
|
|
||||||
};
|
|
||||||
let max_x = min_x + (x_chars as i32);
|
|
||||||
let max_y = min_y + (y_chars as i32);
|
|
||||||
|
|
||||||
(min_x, max_x, min_y, max_y, x_offset, y_offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_camera(ecs: &World, ctx: &mut BTerm) {
|
|
||||||
let map = ecs.fetch::<Map>();
|
|
||||||
let (min_x, max_x, min_y, max_y, x_offset, y_offset) = get_screen_bounds(ecs, ctx);
|
|
||||||
|
|
||||||
// Render map
|
|
||||||
let mut y = 0;
|
|
||||||
for t_y in min_y..max_y {
|
|
||||||
let mut x = 0;
|
|
||||||
for t_x in min_x..max_x {
|
|
||||||
if t_x >= 0 && t_x < map.width && t_y >= 0 && t_y < map.height {
|
|
||||||
let idx = map.xy_idx(t_x, t_y);
|
|
||||||
if map.revealed_tiles[idx] {
|
|
||||||
let (glyph, fg, bg) = crate::map::themes::get_tile_renderables_for_id(
|
|
||||||
idx,
|
|
||||||
&*map,
|
|
||||||
Some(*ecs.fetch::<Point>()),
|
|
||||||
None
|
|
||||||
);
|
|
||||||
ctx.set(x + x_offset, y + y_offset, fg, bg, glyph);
|
|
||||||
}
|
|
||||||
} else if SHOW_BOUNDARIES {
|
|
||||||
ctx.set(
|
|
||||||
x + x_offset,
|
|
||||||
y + y_offset,
|
|
||||||
RGB::named(DARKSLATEGRAY),
|
|
||||||
RGB::named(BLACK),
|
|
||||||
to_cp437('#')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
x += 1;
|
|
||||||
}
|
|
||||||
y += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render entities
|
|
||||||
{
|
|
||||||
let positions = ecs.read_storage::<Position>();
|
|
||||||
let renderables = ecs.read_storage::<Renderable>();
|
|
||||||
let minds = ecs.read_storage::<Mind>();
|
|
||||||
let hidden = ecs.read_storage::<Hidden>();
|
|
||||||
let props = ecs.write_storage::<Prop>();
|
|
||||||
let map = ecs.fetch::<Map>();
|
|
||||||
let entities = ecs.entities();
|
|
||||||
|
|
||||||
let mut data = (&positions, &renderables, &entities, !&hidden).join().collect::<Vec<_>>();
|
|
||||||
data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order));
|
|
||||||
for (pos, render, ent, _hidden) in data.iter() {
|
|
||||||
let idx = map.xy_idx(pos.x, pos.y);
|
|
||||||
let entity_offset_x = pos.x - min_x;
|
|
||||||
let entity_offset_y = pos.y - min_y;
|
|
||||||
if pos.x < max_x && pos.y < max_y && pos.x >= min_x && pos.y >= min_y {
|
|
||||||
let mut draw = false;
|
|
||||||
let mut fg = render.fg;
|
|
||||||
let mut bg = crate::map::themes::get_tile_renderables_for_id(
|
|
||||||
idx,
|
|
||||||
&*map,
|
|
||||||
Some(*ecs.fetch::<Point>()),
|
|
||||||
None
|
|
||||||
).2;
|
|
||||||
// Draw entities on visible tiles
|
|
||||||
if map.visible_tiles[idx] {
|
|
||||||
draw = true;
|
|
||||||
} else {
|
|
||||||
fg = fg.mul(crate::data::visuals::NON_VISIBLE_MULTIPLIER);
|
|
||||||
// We don't darken BG, because get_tile_renderables_for_id handles this.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw entities with minds within telepath range
|
|
||||||
if !draw {
|
|
||||||
if map.telepath_tiles[idx] {
|
|
||||||
let has_mind = minds.get(*ent);
|
|
||||||
if let Some(_) = has_mind {
|
|
||||||
draw = true;
|
|
||||||
if !map.revealed_tiles[idx] {
|
|
||||||
bg = RGB::named(BLACK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Draw all props
|
|
||||||
let is_prop = props.get(*ent);
|
|
||||||
if let Some(_) = is_prop {
|
|
||||||
if map.revealed_tiles[idx] {
|
|
||||||
draw = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if draw {
|
|
||||||
ctx.set(
|
|
||||||
entity_offset_x + x_offset,
|
|
||||||
entity_offset_y + y_offset,
|
|
||||||
fg,
|
|
||||||
bg,
|
|
||||||
render.glyph
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_debug_map(map: &Map, ctx: &mut BTerm) {
|
|
||||||
let player_pos = Point::new(map.width / 2, map.height / 2);
|
|
||||||
let (x_chars, y_chars) = ctx.get_char_size();
|
|
||||||
|
|
||||||
let center_x = (x_chars / 2) as i32;
|
|
||||||
let center_y = (y_chars / 2) as i32;
|
|
||||||
|
|
||||||
let min_x = player_pos.x - center_x;
|
|
||||||
let max_x = min_x + (x_chars as i32);
|
|
||||||
let min_y = player_pos.y - center_y;
|
|
||||||
let max_y = min_y + (y_chars as i32);
|
|
||||||
|
|
||||||
let map_width = map.width;
|
|
||||||
let map_height = map.height;
|
|
||||||
|
|
||||||
let mut y = 0;
|
|
||||||
for ty in min_y..max_y {
|
|
||||||
let mut x = 0;
|
|
||||||
for tx in min_x..max_x {
|
|
||||||
if tx >= 0 && tx < map_width && ty >= 0 && ty < map_height {
|
|
||||||
let idx = map.xy_idx(tx, ty);
|
|
||||||
if map.revealed_tiles[idx] {
|
|
||||||
let (glyph, fg, bg) = crate::map::themes::get_tile_renderables_for_id(
|
|
||||||
idx,
|
|
||||||
&*map,
|
|
||||||
None,
|
|
||||||
None
|
|
||||||
);
|
|
||||||
ctx.set(x, y, fg, bg, glyph);
|
|
||||||
}
|
|
||||||
} else if SHOW_BOUNDARIES {
|
|
||||||
ctx.set(x, y, RGB::named(GRAY), RGB::named(BLACK), to_cp437('·'));
|
|
||||||
}
|
|
||||||
x += 1;
|
|
||||||
}
|
|
||||||
y += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,667 +0,0 @@
|
||||||
use crate::gui::Ancestry;
|
|
||||||
use crate::gui::Class;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use serde::{ Deserialize, Serialize };
|
|
||||||
use specs::error::NoError;
|
|
||||||
use specs::prelude::*;
|
|
||||||
use specs::saveload::{ ConvertSaveload, Marker };
|
|
||||||
use specs_derive::*;
|
|
||||||
use std::collections::{ HashMap, HashSet };
|
|
||||||
|
|
||||||
// Serialization helper code. We need to implement ConvertSaveload for each type that contains an
|
|
||||||
// Entity.
|
|
||||||
pub struct SerializeMe;
|
|
||||||
// Special component that exists to help serialize the game data
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct SerializationHelper {
|
|
||||||
pub map: super::map::Map,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct DMSerializationHelper {
|
|
||||||
pub map: super::map::MasterDungeonMap,
|
|
||||||
pub log: Vec<Vec<crate::gamelog::LogFragment>>,
|
|
||||||
pub event_counts: HashMap<String, i32>,
|
|
||||||
pub events: HashMap<u32, Vec<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, ConvertSaveload, Clone)]
|
|
||||||
pub struct Position {
|
|
||||||
pub x: i32,
|
|
||||||
pub y: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct OtherLevelPosition {
|
|
||||||
pub x: i32,
|
|
||||||
pub y: i32,
|
|
||||||
pub id: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, ConvertSaveload, Clone)]
|
|
||||||
pub struct Renderable {
|
|
||||||
pub glyph: FontCharType,
|
|
||||||
pub fg: RGB,
|
|
||||||
pub bg: RGB,
|
|
||||||
pub render_order: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Bleeds {
|
|
||||||
pub colour: RGB,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Player {}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Faction {
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum Movement {
|
|
||||||
Static,
|
|
||||||
Random,
|
|
||||||
RandomWaypoint {
|
|
||||||
path: Option<Vec<usize>>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct MoveMode {
|
|
||||||
pub mode: Movement,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Prop {}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct LootTable {
|
|
||||||
pub table: String,
|
|
||||||
pub chance: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Energy {
|
|
||||||
pub current: i32,
|
|
||||||
pub speed: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Clock {}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct TakingTurn {}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Quips {
|
|
||||||
pub available: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Mind {}
|
|
||||||
|
|
||||||
#[derive(Component, ConvertSaveload, Clone)]
|
|
||||||
pub struct Viewshed {
|
|
||||||
pub visible_tiles: Vec<Point>,
|
|
||||||
pub range: i32,
|
|
||||||
pub dirty: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, ConvertSaveload, Clone)]
|
|
||||||
pub struct Telepath {
|
|
||||||
pub telepath_tiles: Vec<Point>,
|
|
||||||
pub range: i32,
|
|
||||||
pub dirty: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
|
||||||
pub struct Name {
|
|
||||||
pub name: String,
|
|
||||||
pub plural: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct BlocksTile {}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct BlocksVisibility {}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Door {
|
|
||||||
pub open: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq)]
|
|
||||||
pub enum HungerState {
|
|
||||||
Satiated,
|
|
||||||
Normal,
|
|
||||||
Hungry,
|
|
||||||
Weak,
|
|
||||||
Fainting,
|
|
||||||
Starving,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct HungerClock {
|
|
||||||
pub state: HungerState,
|
|
||||||
pub duration: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct ProvidesNutrition {}
|
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct HasAncestry {
|
|
||||||
pub name: Ancestry,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct HasClass {
|
|
||||||
pub name: Class,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Pool {
|
|
||||||
pub max: i32,
|
|
||||||
pub current: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Pools {
|
|
||||||
pub hit_points: Pool,
|
|
||||||
pub mana: Pool,
|
|
||||||
pub xp: i32,
|
|
||||||
pub bac: i32,
|
|
||||||
pub level: i32,
|
|
||||||
pub weight: f32,
|
|
||||||
pub god: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Attribute {
|
|
||||||
pub base: i32,
|
|
||||||
pub modifiers: i32,
|
|
||||||
pub bonus: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum Skill {
|
|
||||||
Melee,
|
|
||||||
Defence,
|
|
||||||
Magic,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Skills {
|
|
||||||
pub skills: HashMap<Skill, i32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct KnownSpell {
|
|
||||||
pub display_name: String,
|
|
||||||
pub mana_cost: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct KnownSpells {
|
|
||||||
pub list: Vec<KnownSpell>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct GrantsSpell {
|
|
||||||
pub spell: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: GrantsIntrinsic, Intrinsics, etc. ? Done the same way as spells?
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Attributes {
|
|
||||||
pub strength: Attribute,
|
|
||||||
pub dexterity: Attribute,
|
|
||||||
pub constitution: Attribute,
|
|
||||||
pub intelligence: Attribute,
|
|
||||||
pub wisdom: Attribute,
|
|
||||||
pub charisma: Attribute,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
|
||||||
pub struct WantsToMelee {
|
|
||||||
pub target: Entity,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
|
||||||
pub struct GrantsXP {
|
|
||||||
pub amount: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum BUC {
|
|
||||||
Cursed,
|
|
||||||
Uncursed,
|
|
||||||
Blessed,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BUC {
|
|
||||||
pub fn noncursed(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
BUC::Cursed => false,
|
|
||||||
_ => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Clone)]
|
|
||||||
pub struct Beatitude {
|
|
||||||
pub buc: BUC,
|
|
||||||
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)]
|
|
||||||
pub struct Item {
|
|
||||||
pub weight: f32, // in lbs
|
|
||||||
pub value: f32, // base
|
|
||||||
pub category: ItemType,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum MagicItemClass {
|
|
||||||
Common,
|
|
||||||
Uncommon,
|
|
||||||
Rare,
|
|
||||||
VeryRare,
|
|
||||||
Legendary,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct MagicItem {
|
|
||||||
pub class: MagicItemClass,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct ObfuscatedName {
|
|
||||||
pub name: String,
|
|
||||||
pub plural: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct IdentifiedItem {
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct EquipmentChanged {}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
|
|
||||||
pub enum BurdenLevel {
|
|
||||||
Burdened,
|
|
||||||
Strained,
|
|
||||||
Overloaded,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Burden {
|
|
||||||
pub level: BurdenLevel,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
|
|
||||||
pub enum EquipmentSlot {
|
|
||||||
Melee,
|
|
||||||
Shield,
|
|
||||||
Head,
|
|
||||||
Body,
|
|
||||||
Hands,
|
|
||||||
Feet,
|
|
||||||
Neck,
|
|
||||||
Back,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
|
|
||||||
pub enum WeaponAttribute {
|
|
||||||
Strength,
|
|
||||||
Dexterity,
|
|
||||||
Finesse,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct MeleeWeapon {
|
|
||||||
pub damage_type: DamageType,
|
|
||||||
pub attribute: WeaponAttribute,
|
|
||||||
pub damage_n_dice: i32,
|
|
||||||
pub damage_die_type: i32,
|
|
||||||
pub damage_bonus: i32,
|
|
||||||
pub hit_bonus: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
pub struct NaturalAttack {
|
|
||||||
pub name: String,
|
|
||||||
pub damage_type: DamageType,
|
|
||||||
pub damage_n_dice: i32,
|
|
||||||
pub damage_die_type: i32,
|
|
||||||
pub damage_bonus: i32,
|
|
||||||
pub hit_bonus: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct NaturalAttacks {
|
|
||||||
pub attacks: Vec<NaturalAttack>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, ConvertSaveload, Clone)]
|
|
||||||
pub struct ArmourClassBonus {
|
|
||||||
pub amount: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, ConvertSaveload, Clone)]
|
|
||||||
pub struct ToHitBonus {
|
|
||||||
pub amount: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Equippable {
|
|
||||||
pub slot: EquipmentSlot,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, ConvertSaveload, Clone)]
|
|
||||||
pub struct Equipped {
|
|
||||||
pub owner: Entity,
|
|
||||||
pub slot: EquipmentSlot,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
|
||||||
pub struct ProvidesHealing {
|
|
||||||
pub n_dice: i32,
|
|
||||||
pub sides: i32,
|
|
||||||
pub modifier: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)]
|
|
||||||
pub enum DamageType {
|
|
||||||
Physical,
|
|
||||||
Magic, // e.g. magic missiles, silvered weapons
|
|
||||||
Fire, // e.g. fireball
|
|
||||||
Cold, // e.g. cone of cold
|
|
||||||
Poison, // e.g. poison gas
|
|
||||||
Forced, // Bypasses any immunities. e.g. Hunger ticks.
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DamageType {
|
|
||||||
pub fn is_magic(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
DamageType::Magic | DamageType::Fire | DamageType::Cold => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
|
|
||||||
pub enum DamageModifier {
|
|
||||||
None,
|
|
||||||
Weakness,
|
|
||||||
Resistance,
|
|
||||||
Immune,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DamageModifier {
|
|
||||||
const NONE_MOD: f32 = 1.0;
|
|
||||||
const WEAK_MOD: f32 = 2.0;
|
|
||||||
const RESIST_MOD: f32 = 0.5;
|
|
||||||
const IMMUNE_MOD: f32 = 0.0;
|
|
||||||
|
|
||||||
pub fn multiplier(&self) -> f32 {
|
|
||||||
match self {
|
|
||||||
DamageModifier::None => Self::NONE_MOD,
|
|
||||||
DamageModifier::Weakness => Self::WEAK_MOD,
|
|
||||||
DamageModifier::Resistance => Self::RESIST_MOD,
|
|
||||||
DamageModifier::Immune => Self::IMMUNE_MOD,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub struct HasDamageModifiers {
|
|
||||||
pub modifiers: HashMap<DamageType, DamageModifier>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasDamageModifiers {
|
|
||||||
pub fn modifier(&self, damage_type: &DamageType) -> &DamageModifier {
|
|
||||||
self.modifiers.get(damage_type).unwrap_or(&DamageModifier::None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
||||||
pub enum Intrinsic {
|
|
||||||
Regeneration, // Regenerate 1 HP on every tick
|
|
||||||
Speed, // 4/3x speed multiplier
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Intrinsic {
|
|
||||||
pub fn describe(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
Intrinsic::Regeneration => "regenerates health",
|
|
||||||
Intrinsic::Speed => "is hasted",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub struct Intrinsics {
|
|
||||||
pub list: HashSet<Intrinsic>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Intrinsics {
|
|
||||||
pub fn describe(&self) -> String {
|
|
||||||
let mut descriptions = Vec::new();
|
|
||||||
for intrinsic in &self.list {
|
|
||||||
descriptions.push(intrinsic.describe());
|
|
||||||
}
|
|
||||||
match descriptions.len() {
|
|
||||||
0 =>
|
|
||||||
unreachable!("describe() should never be called on an empty Intrinsics component."),
|
|
||||||
1 => format!("It {}.", descriptions[0]),
|
|
||||||
_ => {
|
|
||||||
let last = descriptions.pop().unwrap();
|
|
||||||
let joined = descriptions.join(", ");
|
|
||||||
format!("It {}, and {}.", joined, last)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub struct IntrinsicChanged {
|
|
||||||
pub gained: HashSet<Intrinsic>,
|
|
||||||
pub lost: HashSet<Intrinsic>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
|
||||||
pub struct InflictsDamage {
|
|
||||||
pub damage_type: DamageType,
|
|
||||||
pub n_dice: i32,
|
|
||||||
pub sides: i32,
|
|
||||||
pub modifier: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
|
||||||
pub struct Ranged {
|
|
||||||
pub range: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
|
||||||
pub struct AOE {
|
|
||||||
pub radius: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
|
||||||
pub struct Confusion {
|
|
||||||
pub turns: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Blind {}
|
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct MagicMapper {}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, ConvertSaveload)]
|
|
||||||
pub struct InBackpack {
|
|
||||||
pub owner: Entity,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, ConvertSaveload)]
|
|
||||||
pub struct WantsToPickupItem {
|
|
||||||
pub collected_by: Entity,
|
|
||||||
pub item: Entity,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, ConvertSaveload)]
|
|
||||||
pub struct WantsToDropItem {
|
|
||||||
pub item: Entity,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, ConvertSaveload)]
|
|
||||||
pub struct WantsToRemoveItem {
|
|
||||||
pub item: Entity,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, ConvertSaveload)]
|
|
||||||
pub struct WantsToUseItem {
|
|
||||||
pub item: Entity,
|
|
||||||
pub target: Option<Point>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct WantsToApproach {
|
|
||||||
pub idx: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct WantsToFlee {
|
|
||||||
pub indices: Vec<usize>, // Dijkstra
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
|
||||||
pub struct Chasing {
|
|
||||||
pub target: Entity,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Consumable {}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, ConvertSaveload)]
|
|
||||||
pub struct Charges {
|
|
||||||
pub uses: i32,
|
|
||||||
pub max_uses: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct SpawnParticleLine {
|
|
||||||
pub glyph: FontCharType,
|
|
||||||
pub tail_glyph: FontCharType,
|
|
||||||
pub colour: RGB,
|
|
||||||
pub lifetime_ms: f32,
|
|
||||||
pub trail_colour: RGB,
|
|
||||||
pub trail_lifetime_ms: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct SpawnParticleSimple {
|
|
||||||
pub glyph: FontCharType,
|
|
||||||
pub colour: RGB,
|
|
||||||
pub lifetime_ms: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct SpawnParticleBurst {
|
|
||||||
pub glyph: FontCharType,
|
|
||||||
pub head_glyph: FontCharType,
|
|
||||||
pub tail_glyph: FontCharType,
|
|
||||||
pub colour: RGB,
|
|
||||||
pub lerp: RGB,
|
|
||||||
pub lifetime_ms: f32,
|
|
||||||
pub trail_colour: RGB,
|
|
||||||
pub trail_lifetime_ms: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Destructible {}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct ProvidesRemoveCurse {}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct ProvidesIdentify {}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Digger {}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Hidden {}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct SingleActivation {}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct IdentifiedBeatitude {}
|
|
||||||
|
|
||||||
#[derive(Component, Clone, ConvertSaveload)]
|
|
||||||
pub struct ParticleLifetime {
|
|
||||||
pub lifetime_ms: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct EntryTrigger {}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct EntityMoved {}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
|
||||||
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 {}
|
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use toml::Value;
|
|
||||||
use serde::{ Serialize, Deserialize };
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref CONFIG: Config = try_load_configuration();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct Config {
|
|
||||||
pub logging: LogConfig,
|
|
||||||
pub visuals: VisualConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct LogConfig {
|
|
||||||
pub show_mapgen: bool,
|
|
||||||
pub log_combat: bool,
|
|
||||||
pub log_spawning: bool,
|
|
||||||
pub log_ticks: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct VisualConfig {
|
|
||||||
pub with_scanlines: bool,
|
|
||||||
pub with_screen_burn: bool,
|
|
||||||
pub with_darken_by_distance: bool,
|
|
||||||
pub use_bitset_walls: bool,
|
|
||||||
pub use_coloured_tile_bg: bool,
|
|
||||||
pub add_colour_variance: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Config {
|
|
||||||
fn default() -> Self {
|
|
||||||
Config {
|
|
||||||
logging: LogConfig {
|
|
||||||
show_mapgen: false,
|
|
||||||
log_combat: false,
|
|
||||||
log_spawning: false,
|
|
||||||
log_ticks: false,
|
|
||||||
},
|
|
||||||
visuals: VisualConfig {
|
|
||||||
with_scanlines: false,
|
|
||||||
with_screen_burn: false,
|
|
||||||
with_darken_by_distance: true,
|
|
||||||
use_bitset_walls: true,
|
|
||||||
use_coloured_tile_bg: true,
|
|
||||||
add_colour_variance: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn load_from_file(filename: &str) -> Config {
|
|
||||||
if let Ok(contents) = std::fs::read_to_string(filename) {
|
|
||||||
let parsed_config: Result<Value, _> = toml::from_str(&contents);
|
|
||||||
if let Ok(parsed_config) = parsed_config {
|
|
||||||
let mut config = Config::default();
|
|
||||||
let mut requires_write = false;
|
|
||||||
requires_write |= config.logging.apply_values(&parsed_config);
|
|
||||||
requires_write |= config.visuals.apply_values(&parsed_config);
|
|
||||||
if requires_write {
|
|
||||||
if let Err(write_err) = config.save_to_file(filename) {
|
|
||||||
console::log(format!("Error writing config: {:?}", write_err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let config = Config::default();
|
|
||||||
if let Err(write_err) = config.save_to_file(filename) {
|
|
||||||
console::log(format!("Error writing config: {:?}", write_err));
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
pub fn save_to_file(&self, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let toml_string = toml::to_string(self)?;
|
|
||||||
std::fs::write(filename, toml_string)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! apply_bool_value {
|
|
||||||
($config:expr, $parsed_config:expr, $changed:expr, $field:ident) => {
|
|
||||||
if let Some(value) = $parsed_config.get(stringify!($field)).and_then(|v| v.as_bool()) {
|
|
||||||
if $config.$field != value {
|
|
||||||
$config.$field = value;
|
|
||||||
$changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Section {
|
|
||||||
fn apply_values(&mut self, parsed_config: &Value) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Section for LogConfig {
|
|
||||||
fn apply_values(&mut self, parsed_config: &Value) -> bool {
|
|
||||||
if let Some(section) = parsed_config.get("logging") {
|
|
||||||
let mut missing = false;
|
|
||||||
apply_bool_value!(self, section, missing, log_spawning);
|
|
||||||
apply_bool_value!(self, section, missing, log_ticks);
|
|
||||||
missing
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Section for VisualConfig {
|
|
||||||
fn apply_values(&mut self, parsed_config: &Value) -> bool {
|
|
||||||
if let Some(section) = parsed_config.get("visuals") {
|
|
||||||
let mut missing = false;
|
|
||||||
apply_bool_value!(self, section, missing, with_scanlines);
|
|
||||||
apply_bool_value!(self, section, missing, with_screen_burn);
|
|
||||||
apply_bool_value!(self, section, missing, with_darken_by_distance);
|
|
||||||
apply_bool_value!(self, section, missing, use_bitset_walls);
|
|
||||||
apply_bool_value!(self, section, missing, use_coloured_tile_bg);
|
|
||||||
apply_bool_value!(self, section, missing, add_colour_variance);
|
|
||||||
missing
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
pub fn try_load_configuration() -> Config {
|
|
||||||
let config: Config = Config::load_from_file("config.toml");
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
pub fn try_load_configuration() -> Config {
|
|
||||||
let config = Config::default();
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
@ -1,161 +0,0 @@
|
||||||
use super::{
|
|
||||||
gamelog,
|
|
||||||
gui::renderable_colour,
|
|
||||||
Equipped,
|
|
||||||
InBackpack,
|
|
||||||
Item,
|
|
||||||
LootTable,
|
|
||||||
Name,
|
|
||||||
Player,
|
|
||||||
Pools,
|
|
||||||
Position,
|
|
||||||
Renderable,
|
|
||||||
RunState,
|
|
||||||
WantsToRemoveKey,
|
|
||||||
WantsToDelete,
|
|
||||||
};
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
use crate::data::events;
|
|
||||||
|
|
||||||
pub fn delete_the_dead(ecs: &mut World) {
|
|
||||||
let mut dead: Vec<Entity> = Vec::new();
|
|
||||||
// Using scope to make borrow checker happy
|
|
||||||
{
|
|
||||||
let combat_stats = ecs.read_storage::<Pools>();
|
|
||||||
let players = ecs.read_storage::<Player>();
|
|
||||||
let names = ecs.read_storage::<Name>();
|
|
||||||
let items = ecs.read_storage::<Item>();
|
|
||||||
let entities = ecs.entities();
|
|
||||||
let renderables = ecs.read_storage::<Renderable>();
|
|
||||||
for (entity, stats) in (&entities, &combat_stats).join() {
|
|
||||||
if stats.hit_points.current < 1 {
|
|
||||||
let player = players.get(entity);
|
|
||||||
match player {
|
|
||||||
None => {
|
|
||||||
let victim_name = names.get(entity);
|
|
||||||
if let Some(victim_name) = victim_name {
|
|
||||||
let item = items.get(entity);
|
|
||||||
if let Some(_item) = item {
|
|
||||||
gamelog::Logger
|
|
||||||
::new()
|
|
||||||
.append("The")
|
|
||||||
.colour(renderable_colour(&renderables, entity))
|
|
||||||
.append(&victim_name.name)
|
|
||||||
.colour(WHITE)
|
|
||||||
.append("is destroyed!")
|
|
||||||
.log();
|
|
||||||
} else {
|
|
||||||
gamelog::Logger
|
|
||||||
::new()
|
|
||||||
.append("The")
|
|
||||||
.colour(renderable_colour(&renderables, entity))
|
|
||||||
.append(&victim_name.name)
|
|
||||||
.colour(WHITE)
|
|
||||||
.append("dies!")
|
|
||||||
.log();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dead.push(entity);
|
|
||||||
}
|
|
||||||
// The player died, go to GameOver.
|
|
||||||
Some(_) => {
|
|
||||||
let mut runstate = ecs.write_resource::<RunState>();
|
|
||||||
*runstate = RunState::GameOver;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
crate::raws::spawn_named_entity(
|
|
||||||
&crate::raws::RAWS.lock().unwrap(),
|
|
||||||
ecs,
|
|
||||||
&loot.0,
|
|
||||||
None,
|
|
||||||
crate::raws::SpawnType::AtPosition { x: loot.1.x, y: loot.1.y },
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for item in items_to_delete {
|
|
||||||
ecs.delete_entity(item).expect("Unable to delete item.");
|
|
||||||
}
|
|
||||||
// For everything that died, increment the event log, and delete.
|
|
||||||
for victim in dead {
|
|
||||||
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.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_dead_entity_items(
|
|
||||||
ecs: &mut World,
|
|
||||||
dead: &Vec<Entity>
|
|
||||||
) -> (Vec<Entity>, Vec<(String, Position)>) {
|
|
||||||
let mut to_drop: Vec<(Entity, Position)> = Vec::new();
|
|
||||||
let mut to_spawn: Vec<(String, Position)> = Vec::new();
|
|
||||||
let entities = ecs.entities();
|
|
||||||
let mut equipped = ecs.write_storage::<Equipped>();
|
|
||||||
let mut carried = ecs.write_storage::<InBackpack>();
|
|
||||||
let mut positions = ecs.write_storage::<Position>();
|
|
||||||
let loot_tables = ecs.read_storage::<LootTable>();
|
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
|
||||||
// Make list of every item in every dead thing's inv/equip
|
|
||||||
for victim in dead.iter() {
|
|
||||||
let pos = positions.get(*victim);
|
|
||||||
for (entity, equipped) in (&entities, &equipped).join() {
|
|
||||||
if equipped.owner == *victim {
|
|
||||||
// Push equipped item entities and positions
|
|
||||||
if let Some(pos) = pos {
|
|
||||||
to_drop.push((entity, pos.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (entity, backpack) in (&entities, &carried).join() {
|
|
||||||
if backpack.owner == *victim {
|
|
||||||
// Push backpack item entities and positions
|
|
||||||
if let Some(pos) = pos {
|
|
||||||
to_drop.push((entity, pos.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(table) = loot_tables.get(*victim) {
|
|
||||||
let roll: f32 = rng.rand();
|
|
||||||
if roll < table.chance {
|
|
||||||
let potential_drop = crate::raws::roll_on_loot_table(
|
|
||||||
&crate::raws::RAWS.lock().unwrap(),
|
|
||||||
&mut rng,
|
|
||||||
&table.table
|
|
||||||
);
|
|
||||||
if let Some(id) = potential_drop {
|
|
||||||
if let Some(pos) = pos {
|
|
||||||
to_spawn.push((id, pos.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const DROP_ONE_IN_THIS_MANY_TIMES: i32 = 6;
|
|
||||||
let mut to_return: Vec<Entity> = Vec::new();
|
|
||||||
for drop in to_drop.iter() {
|
|
||||||
if rng.roll_dice(1, DROP_ONE_IN_THIS_MANY_TIMES) == 1 {
|
|
||||||
equipped.remove(drop.0);
|
|
||||||
carried.remove(drop.0);
|
|
||||||
positions.insert(drop.0, drop.1.clone()).expect("Unable to insert Position{}.");
|
|
||||||
} else {
|
|
||||||
to_return.push(drop.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (to_return, to_spawn);
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
// --- GUI ---
|
|
||||||
pub const CHAR_CREATE_HEADER: &str = "Who are you? [Aa-Zz]";
|
|
||||||
pub const ANCESTRY_INFO_HEADER: &str = "Your ancestry grants...";
|
|
||||||
pub const CLASS_INFO_HEADER: &str = "Your class grants...";
|
|
||||||
// --- ANCESTRY RENDERABLES ---
|
|
||||||
pub const ELF_GLYPH: char = '@';
|
|
||||||
pub const ELF_COLOUR: (u8, u8, u8) = (0, 255, 0);
|
|
||||||
pub const DWARF_GLYPH: char = 'h';
|
|
||||||
pub const DWARF_COLOUR: (u8, u8, u8) = (255, 0, 0);
|
|
||||||
pub const CATFOLK_GLYPH: char = '@';
|
|
||||||
pub const CATFOLK_COLOUR: (u8, u8, u8) = (200, 200, 255);
|
|
||||||
// --- ANCESTRY BONUSES ---
|
|
||||||
pub const ELF_SPEED_BONUS: i32 = 1;
|
|
||||||
pub const ELF_TELEPATH_RANGE: i32 = 6;
|
|
||||||
pub const DWARF_DEFENCE_MOD: i32 = 1;
|
|
||||||
pub const CATFOLK_SPEED_BONUS: i32 = 2;
|
|
||||||
// --- ANCESTRY ATTRIBUTE MAXIMUMS ---
|
|
||||||
pub const TOTAL_ATTRIBUTE_POINTS_MAXIMUM: i32 = 75;
|
|
||||||
pub const HUMAN_MAX_ATTR: [i32; 6] = [19, 19, 19, 19, 19, 19];
|
|
||||||
pub const ELF_MAX_ATTR: [i32; 6] = [15, 18, 15, 20, 20, 18];
|
|
||||||
pub const DWARF_MAX_ATTR: [i32; 6] = [19, 17, 20, 16, 16, 16];
|
|
||||||
pub const GNOME_MAX_ATTR: [i32; 6] = [16, 18, 16, 20, 18, 18];
|
|
||||||
pub const CATFOLK_MAX_ATTR: [i32; 6] = [16, 20, 16, 16, 18, 20];
|
|
||||||
pub const UNKNOWN_MAX_ATTR: [i32; 6] = [18, 18, 18, 18, 18, 18];
|
|
||||||
// --- CLASS MIN ATTRIBUTES ---
|
|
||||||
pub const FIGHTER_MIN_ATTR: (i32, i32, i32, i32, i32, i32) = (10, 8, 10, 6, 6, 8);
|
|
||||||
pub const ROGUE_MIN_ATTR: (i32, i32, i32, i32, i32, i32) = (8, 10, 8, 6, 8, 10);
|
|
||||||
pub const WIZARD_MIN_ATTR: (i32, i32, i32, i32, i32, i32) = (6, 8, 6, 10, 10, 8);
|
|
||||||
pub const VILLAGER_MIN_ATTR: (i32, i32, i32, i32, i32, i32) = (6, 6, 6, 6, 6, 6);
|
|
||||||
// --- CLASS ATTRIBUTE IMPROVE CHANCES ---
|
|
||||||
pub const FIGHTER_IMPR_CHANCE: [i32; 6] = [30, 20, 30, 6, 7, 7];
|
|
||||||
pub const ROGUE_IMPR_CHANCE: [i32; 6] = [18, 30, 20, 9, 8, 15];
|
|
||||||
pub const WIZARD_IMPR_CHANCE: [i32; 6] = [10, 15, 20, 30, 15, 10];
|
|
||||||
pub const VILLAGER_IMPR_CHANCE: [i32; 6] = [15, 15, 25, 15, 15, 15];
|
|
||||||
// --- CLASS STARTING ITEMS --- ## If any of these are changed, update ancestry infotext in src/gui/character_creation.rs.
|
|
||||||
pub const FIGHTER_STARTING_FOOD: &str = "1d2+1";
|
|
||||||
pub const FIGHTER_STARTING_WEAPON: &str = "equip_shortsword";
|
|
||||||
pub const FIGHTER_STARTING_ARMOUR: &str = "equip_body_ringmail";
|
|
||||||
pub const FIGHTER_STARTING_SHIELD: &str = "equip_mediumshield";
|
|
||||||
pub const ROGUE_STARTING_FOOD: &str = "1d2+2";
|
|
||||||
pub const ROGUE_STARTING_WEAPON: &str = "equip_rapier";
|
|
||||||
pub const ROGUE_STARTING_ARMOUR: &str = "equip_body_weakleather";
|
|
||||||
pub const WIZARD_STARTING_FOOD: &str = "1d2+1";
|
|
||||||
pub const WIZARD_STARTING_WEAPON: &str = "equip_dagger";
|
|
||||||
pub const WIZARD_STARTING_ARMOUR: &str = "equip_back_protection";
|
|
||||||
pub const WIZARD_MAX_SCROLL_LVL: i32 = 3;
|
|
||||||
pub const WIZARD_SCROLL_AMOUNT: &str = "1d3+1";
|
|
||||||
pub const WIZARD_POTION_AMOUNT: &str = "1d3";
|
|
||||||
pub const VILLAGER_STARTING_FOOD: &str = "1d3+2";
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
pub const DEFAULT_VIEWSHED_STANDARD: i32 = 16; // Standard viewshed radius for almost all entities.
|
|
||||||
pub const CARRY_CAPACITY_PER_STRENGTH: i32 = 5; // How much weight can be carried per point of strength.
|
|
||||||
pub const NORMAL_SPEED: i32 = 12; // Normal speed for almost all entities.
|
|
||||||
pub const SPEED_MOD_BURDENED: f32 = 0.75;
|
|
||||||
pub const SPEED_MOD_STRAINED: f32 = 0.5;
|
|
||||||
pub const SPEED_MOD_OVERLOADED: f32 = 0.25;
|
|
||||||
pub const SPEED_MOD_OVERMAP_TRAVEL: f32 = 0.33;
|
|
||||||
pub const TURN_COST_MULTIPLIER: i32 = 4; // How many ticks for NORMAL_SPEED to get a turn.
|
|
||||||
pub const ATTR_BONUS_0: i32 = 10; // At this value, the attribute bonus is 0.
|
|
||||||
pub const ATTR_NEEDED_PER_POINT: i32 = 2; // How many points +- ATTR_BONUS_0 are needed per +- 1 bonus.
|
|
||||||
pub const STANDARD_HIT_DIE: i32 = 8; // Standard hit die used for rolling HP.
|
|
||||||
pub const STANDARD_HIT_DIE_0: i32 = 4; // Standard hit die used for rolling HP for level 0.
|
|
||||||
pub const STANDARD_MANA_DIE: i32 = 4; // Standard mana die used for rolling mana.
|
|
||||||
pub const MINIMUM_MANA: i32 = 0; // The minimum mana a monster can have.
|
|
||||||
pub const MINIMUM_MANA_PLAYER: i32 = 1; // The minimum mana a player can have.
|
|
||||||
pub const STANDARD_BAC: i32 = 10; // Standard BASE AC.
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
use serde::{ Deserialize, Serialize };
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
pub enum EVENT {
|
|
||||||
Turn(i32),
|
|
||||||
Level(i32),
|
|
||||||
ChangedFloor(String),
|
|
||||||
PlayerConfused(i32),
|
|
||||||
KickedSomething(i32),
|
|
||||||
BrokeDoor(i32),
|
|
||||||
LookedForHelp(i32),
|
|
||||||
Killed(String),
|
|
||||||
PlayerDied(String),
|
|
||||||
Discovered(String),
|
|
||||||
Identified(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EVENT {
|
|
||||||
pub const COUNT_TURN: &str = "turns";
|
|
||||||
pub const COUNT_KILLED: &str = "killed";
|
|
||||||
pub const COUNT_LEVEL: &str = "level";
|
|
||||||
pub const COUNT_CHANGED_FLOOR: &str = "changed_floor";
|
|
||||||
pub const COUNT_BROKE_DOOR: &str = "BrokeDoor";
|
|
||||||
pub const COUNT_PLAYER_CONFUSED: &str = "PlayerConfused";
|
|
||||||
pub const COUNT_KICK: &str = "kick";
|
|
||||||
pub const COUNT_LOOKED_FOR_HELP: &str = "LookedForHelp";
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
use super::names::*;
|
|
||||||
use super::visuals::*;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
pub const ID_OVERMAP: i32 = 1;
|
|
||||||
|
|
||||||
pub const ID_TOWN: i32 = 10;
|
|
||||||
pub const ID_TOWN2: i32 = ID_TOWN + 1;
|
|
||||||
pub const ID_TOWN3: i32 = ID_TOWN + 2;
|
|
||||||
|
|
||||||
pub const ID_INFINITE: i32 = 1000;
|
|
||||||
|
|
||||||
pub fn get_local_desc(id: i32) -> String {
|
|
||||||
let str = match id {
|
|
||||||
ID_TOWN => NAME_STARTER_TOWN,
|
|
||||||
ID_INFINITE => NAME_DUNGEON_RANDOM,
|
|
||||||
_ => "an unnamed overmap tile",
|
|
||||||
};
|
|
||||||
return str.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_local_col(id: i32) -> RGB {
|
|
||||||
let col = match id {
|
|
||||||
ID_TOWN => TO_TOWN_COLOUR,
|
|
||||||
ID_TOWN2 => GRASS_COLOUR,
|
|
||||||
ID_OVERMAP => TO_OVERMAP_COLOUR,
|
|
||||||
_ => (255, 255, 255),
|
|
||||||
};
|
|
||||||
return RGB::from_u8(col.0, col.1, col.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rgb_to_u8(col: RGB) -> (u8, u8, u8) {
|
|
||||||
return ((col.r * 255.0) as u8, (col.g * 255.0) as u8, (col.b * 255.0) as u8);
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
pub const NOCHARGES_WREST: &str = "You wrest one last charge from the worn-out wand.";
|
|
||||||
pub const NOCHARGES_DIDNOTHING: &str = "The wand does nothing.";
|
|
||||||
|
|
||||||
pub const IDENTIFY_ALL: &str = "You feel attuned to your belongings!";
|
|
||||||
pub const IDENTIFY_ALL_BLESSED: &str = "Divine favour reveals all";
|
|
||||||
|
|
||||||
pub const REMOVECURSE: &str = "You feel a weight lifted!";
|
|
||||||
pub const REMOVECURSE_BLESSED: &str = "You feel righteous";
|
|
||||||
pub const REMOVECURSE_BLESSED_FAILED: &str = "You feel righteous! But nothing happened.";
|
|
||||||
|
|
||||||
pub const DAMAGE_PLAYER_HIT: &str = "are hit!";
|
|
||||||
pub const DAMAGE_ITEM_HIT: &str = "is ruined!";
|
|
||||||
pub const DAMAGE_OTHER_HIT: &str = "is hit!";
|
|
||||||
|
|
||||||
pub const HEAL_PLAYER_HIT: &str = "recover some vigour.";
|
|
||||||
pub const HEAL_PLAYER_HIT_BLESSED: &str = "You feel great";
|
|
||||||
pub const HEAL_OTHER_HIT: &str = "is rejuvenated!";
|
|
||||||
|
|
||||||
pub const MAGICMAP: &str = "You recall your surroundings!";
|
|
||||||
pub const MAGICMAP_CURSED: &str = "... but forget where you last were";
|
|
||||||
|
|
||||||
pub const NUTRITION: &str = "You eat the";
|
|
||||||
pub const NUTRITION_CURSED: &str = "Blech! Rotten";
|
|
||||||
pub const NUTRITION_BLESSED: &str = "Delicious";
|
|
||||||
|
|
||||||
pub const LEVELUP_PLAYER: &str = "Welcome to experience level";
|
|
||||||
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_EQUIP_ITEM: &str = "You equip the";
|
|
||||||
pub const YOU_REMOVE_ITEM: &str = "You unequip your";
|
|
||||||
pub const YOU_REMOVE_ITEM_CURSED: &str = "You can't remove the";
|
|
||||||
|
|
||||||
/// Prefixes death message.
|
|
||||||
pub const PLAYER_DIED: &str = "You died!";
|
|
||||||
/// Death message specifiers. Appended after PlayerDied.
|
|
||||||
pub const PLAYER_DIED_SUICIDE: &str = "You killed yourself";
|
|
||||||
pub const PLAYER_DIED_NAMED_ATTACKER: &str = "You were killed by";
|
|
||||||
pub const PLAYER_DIED_UNKNOWN: &str = "You were killed"; // Ultimately, this should never be used. Slowly include specific messages for any death.
|
|
||||||
/// Death message addendums. Appended at end of death message.
|
|
||||||
pub const PLAYER_DIED_ADDENDUM_FIRST: &str = " ";
|
|
||||||
pub const PLAYER_DIED_ADDENDUM_MID: &str = ", ";
|
|
||||||
pub const PLAYER_DIED_ADDENDUM_LAST: &str = ", and ";
|
|
||||||
pub const STATUS_CONFUSED_STRING: &str = "confused";
|
|
||||||
pub const STATUS_BLIND_STRING: &str = "blinded";
|
|
||||||
// Results in something like: "You died! You were killed by a kobold captain, whilst confused."
|
|
||||||
|
|
||||||
// Dungeon features
|
|
||||||
pub const FEATURE_TREANTS: &str = "You feel an unusual freshness in the air.";
|
|
||||||
pub const FEATURE_BARRACKS_GOBLIN: &str = "You hear an order being barked, and ignored.";
|
|
||||||
pub const FEATURE_BARRACKS_KOBOLD: &str = "You hear someone being reprimanded for disobedience.";
|
|
||||||
pub const FEATURE_BARRACKS_ORC: &str = "You hear someone barking orders.";
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
pub mod entity;
|
|
||||||
pub mod visuals;
|
|
||||||
pub mod messages;
|
|
||||||
pub mod char_create;
|
|
||||||
pub mod events;
|
|
||||||
pub mod ids;
|
|
||||||
pub mod names;
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
pub const NAME_OVERMAP: &str = "the travel map";
|
|
||||||
pub const SHORTNAME_OVERMAP: &str = "Travel";
|
|
||||||
pub const NAME_DUNGEON_RANDOM: &str = "the dungeon";
|
|
||||||
pub const SHORTNAME_DUNGEON_RANDOM: &str = "D";
|
|
||||||
pub const NAME_STARTER_TOWN: &str = "the port town of Saff";
|
|
||||||
pub const SHORTNAME_STARTER_TOWN: &str = "Saff";
|
|
||||||
pub const NAME_FOREST_BUILDER: &str = "the woods outside of town";
|
|
||||||
pub const SHORTNAME_FOREST_BUILDER: &str = "Woods";
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
// POST-PROCESSING
|
|
||||||
pub const WITH_DARKEN_BY_DISTANCE: bool = true; // If further away tiles should get darkened, instead of a harsh transition to non-visible.
|
|
||||||
|
|
||||||
pub const BRIGHTEN_FG_COLOUR_BY: i32 = 16;
|
|
||||||
pub const GLOBAL_OFFSET_MIN_CLAMP: f32 = -0.5;
|
|
||||||
pub const GLOBAL_OFFSET_MAX_CLAMP: f32 = 1.0;
|
|
||||||
pub const WITH_SCANLINES_BRIGHTEN_AMOUNT: f32 = 0.1; // 0.0 = no brightening, 1.0 = full brightening.
|
|
||||||
pub const NON_VISIBLE_MULTIPLIER: f32 = 0.3; // 0.0 = black, 1.0 = full colour.
|
|
||||||
pub const NON_VISIBLE_MULTIPLIER_IF_SCANLINES: f32 = 0.8; // as above, but when using scanlines. should be higher.
|
|
||||||
pub const MAX_DARKENING: f32 = 0.45; // 0.0 = black, 1.0 = full colour - only used if WITH_DARKEN_BY_DISTANCE is true.
|
|
||||||
pub const MAX_DARKENING_IF_SCANLINES: f32 = 0.9; // as above, but when using scanlines. should be higher.
|
|
||||||
pub const START_DARKEN_AT_N_TILES: f32 = 8.0; // start darkening at this distance (should always be less than entity::DEFAULT_VIEWSHED_STANDARD).
|
|
||||||
|
|
||||||
pub const SHORT_PARTICLE_LIFETIME: f32 = 100.0; // in ms
|
|
||||||
pub const DEFAULT_PARTICLE_LIFETIME: f32 = 200.0;
|
|
||||||
pub const LONG_PARTICLE_LIFETIME: f32 = 300.0;
|
|
||||||
|
|
||||||
pub const TARGETING_CURSOR_COL: (u8, u8, u8) = GOLDENROD;
|
|
||||||
pub const TARGETING_LINE_COL: (u8, u8, u8) = LIGHTGOLDENROD;
|
|
||||||
pub const TARGETING_AOE_COL: (u8, u8, u8) = (20, 20, 20);
|
|
||||||
pub const TARGETING_VALID_COL: (u8, u8, u8) = (10, 10, 10);
|
|
||||||
|
|
||||||
// THEMES
|
|
||||||
pub const BLOODSTAIN_COLOUR: (u8, u8, u8) = (153, 0, 0);
|
|
||||||
// DEFAULT THEME
|
|
||||||
pub const DEFAULT_BG_COLOUR: (u8, u8, u8) = (29, 50, 50);
|
|
||||||
pub const DEFAULT_BG_OFFSETS: (i32, i32, i32) = (10, 10, 10);
|
|
||||||
pub const WALL_COLOUR: (u8, u8, u8) = (229, 191, 94);
|
|
||||||
pub const WALL_OFFSETS: (i32, i32, i32) = (48, 48, 48);
|
|
||||||
pub const FLOOR_COLOUR: (u8, u8, u8) = (25, 204, 122);
|
|
||||||
pub const FLOOR_OFFSETS: (i32, i32, i32) = (10, 10, 10);
|
|
||||||
pub const STAIR_COLOUR: (u8, u8, u8) = (200, 200, 0);
|
|
||||||
pub const STAIR_OFFSETS: (i32, i32, i32) = (10, 10, 10);
|
|
||||||
pub const WOOD_FLOOR_COLOUR: (u8, u8, u8) = (41, 30, 20);
|
|
||||||
pub const WOOD_FLOOR_OFFSETS: (i32, i32, i32) = (10, 10, 10);
|
|
||||||
pub const FENCE_FG_COLOUR: (u8, u8, u8) = (110, 24, 0);
|
|
||||||
pub const FENCE_COLOUR: (u8, u8, u8) = (45, 30, 10);
|
|
||||||
pub const FENCE_OFFSETS: (i32, i32, i32) = (10, 10, 10);
|
|
||||||
pub const BRIDGE_COLOUR: (u8, u8, u8) = (42, 48, 37);
|
|
||||||
pub const BRIDGE_OFFSETS: (i32, i32, i32) = (10, 10, 10);
|
|
||||||
pub const GRAVEL_COLOUR: (u8, u8, u8) = (26, 26, 53);
|
|
||||||
pub const GRAVEL_OFFSETS: (i32, i32, i32) = (10, 10, 10);
|
|
||||||
pub const ROAD_COLOUR: (u8, u8, u8) = (8, 38, 40);
|
|
||||||
pub const ROAD_OFFSETS: (i32, i32, i32) = (10, 10, 10);
|
|
||||||
pub const GRASS_COLOUR: (u8, u8, u8) = (9, 65, 6);
|
|
||||||
pub const GRASS_OFFSETS: (i32, i32, i32) = (3, 20, 10);
|
|
||||||
pub const FOLIAGE_COLOUR: (u8, u8, u8) = (5, 60, 5);
|
|
||||||
pub const FOLIAGE_OFFSETS: (i32, i32, i32) = (10, 10, 10);
|
|
||||||
pub const HEAVY_FOLIAGE_COLOUR: (u8, u8, u8) = (5, 60, 5);
|
|
||||||
pub const HEAVY_FOLIAGE_OFFSETS: (i32, i32, i32) = (10, 10, 10);
|
|
||||||
pub const SAND_COLOUR: (u8, u8, u8) = (70, 70, 21);
|
|
||||||
pub const SAND_OFFSETS: (i32, i32, i32) = (10, 10, 10);
|
|
||||||
pub const SHALLOW_WATER_COLOUR: (u8, u8, u8) = (24, 47, 99);
|
|
||||||
pub const SHALLOW_WATER_OFFSETS: (i32, i32, i32) = (3, 10, 45);
|
|
||||||
pub const DEEP_WATER_COLOUR: (u8, u8, u8) = (18, 33, 63);
|
|
||||||
pub const DEEP_WATER_OFFSETS: (i32, i32, i32) = (5, 10, 32);
|
|
||||||
pub const BARS_COLOUR: (u8, u8, u8) = (100, 100, 100);
|
|
||||||
pub const IMPASSABLE_MOUNTAIN_COLOUR: (u8, u8, u8) = (20, 23, 20);
|
|
||||||
pub const IMPASSABLE_MOUNTAIN_OFFSETS: (i32, i32, i32) = (4, 4, 4);
|
|
||||||
// FOREST THEME
|
|
||||||
pub const FOREST_WALL_COLOUR: (u8, u8, u8) = (0, 153, 0);
|
|
||||||
|
|
||||||
// DEFAULT THEME
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub const WALL_GLYPH: char = '#';
|
|
||||||
pub const FLOOR_GLYPH: char = '.';
|
|
||||||
pub const DOWN_STAIR_GLYPH: char = '>';
|
|
||||||
pub const UP_STAIR_GLYPH: char = '<';
|
|
||||||
pub const WOOD_FLOOR_GLYPH: char = '.';
|
|
||||||
pub const FENCE_GLYPH: char = '=';
|
|
||||||
pub const BRIDGE_GLYPH: char = '.';
|
|
||||||
pub const GRAVEL_GLYPH: char = ';';
|
|
||||||
pub const ROAD_GLYPH: char = '.';
|
|
||||||
pub const GRASS_GLYPH: char = '"';
|
|
||||||
pub const FOLIAGE_GLYPH: char = ':';
|
|
||||||
pub const HEAVY_FOLIAGE_GLYPH: char = ';';
|
|
||||||
pub const SAND_GLYPH: char = '.';
|
|
||||||
pub const SHALLOW_WATER_GLYPH: char = '~';
|
|
||||||
pub const DEEP_WATER_GLYPH: char = '≈';
|
|
||||||
pub const BARS_GLYPH: char = '#';
|
|
||||||
pub const IMPASSABLE_MOUNTAIN_GLYPH: char = '▲';
|
|
||||||
|
|
||||||
// FOREST THEME
|
|
||||||
pub const FOREST_WALL_GLYPH: char = '♣';
|
|
||||||
|
|
||||||
// Overmap/transition stuff
|
|
||||||
pub const TO_OVERMAP_GLYPH: char = '<';
|
|
||||||
pub const TO_OVERMAP_COLOUR: (u8, u8, u8) = (205, 127, 50);
|
|
||||||
pub const TO_TOWN_GLYPH: char = 'o';
|
|
||||||
pub const TO_TOWN_COLOUR: (u8, u8, u8) = (205, 127, 50);
|
|
||||||
|
|
@ -1,348 +0,0 @@
|
||||||
use super::{ add_effect, targeting, EffectSpawner, EffectType, Entity, Targets, World };
|
|
||||||
use crate::{
|
|
||||||
gamelog,
|
|
||||||
gamesystem::{ hp_per_level, mana_per_level },
|
|
||||||
Attributes,
|
|
||||||
Confusion,
|
|
||||||
Destructible,
|
|
||||||
GrantsXP,
|
|
||||||
Map,
|
|
||||||
Player,
|
|
||||||
Pools,
|
|
||||||
Name,
|
|
||||||
Blind,
|
|
||||||
HungerClock,
|
|
||||||
HungerState,
|
|
||||||
Bleeds,
|
|
||||||
HasDamageModifiers,
|
|
||||||
};
|
|
||||||
use crate::gui::with_article;
|
|
||||||
use crate::data::visuals::{ DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME };
|
|
||||||
use crate::data::messages::LEVELUP_PLAYER;
|
|
||||||
use crate::data::events::*;
|
|
||||||
use crate::data::messages::*;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
|
|
||||||
pub fn inflict_damage(ecs: &mut World, damage: &EffectSpawner, target: Entity) {
|
|
||||||
let mut pools = ecs.write_storage::<Pools>();
|
|
||||||
if let Some(target_pool) = pools.get_mut(target) {
|
|
||||||
if !target_pool.god {
|
|
||||||
if let EffectType::Damage { amount, damage_type } = damage.effect_type {
|
|
||||||
let mult = if
|
|
||||||
let Some(modifiers) = ecs.read_storage::<HasDamageModifiers>().get(target)
|
|
||||||
{
|
|
||||||
modifiers.modifier(&damage_type).multiplier()
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
};
|
|
||||||
target_pool.hit_points.current -= ((amount as f32) * mult) as i32;
|
|
||||||
let bleeders = ecs.read_storage::<Bleeds>();
|
|
||||||
if let Some(bleeds) = bleeders.get(target) {
|
|
||||||
add_effect(
|
|
||||||
None,
|
|
||||||
EffectType::Bloodstain { colour: bleeds.colour },
|
|
||||||
Targets::Entity { target }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
add_effect(
|
|
||||||
None,
|
|
||||||
EffectType::Particle {
|
|
||||||
glyph: to_cp437('‼'),
|
|
||||||
fg: RGB::named(ORANGE),
|
|
||||||
bg: RGB::named(BLACK),
|
|
||||||
lifespan: DEFAULT_PARTICLE_LIFETIME,
|
|
||||||
delay: 0.0,
|
|
||||||
},
|
|
||||||
Targets::Entity { target }
|
|
||||||
);
|
|
||||||
if target_pool.hit_points.current < 1 {
|
|
||||||
super::DEAD_ENTITIES.lock().unwrap().push_back(target);
|
|
||||||
add_effect(damage.source, EffectType::EntityDeath, Targets::Entity { target });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if let Some(_destructible) = ecs.read_storage::<Destructible>().get(target) {
|
|
||||||
add_effect(damage.source, EffectType::EntityDeath, Targets::Entity { target });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn heal_damage(ecs: &mut World, heal: &EffectSpawner, target: Entity) {
|
|
||||||
let mut pools = ecs.write_storage::<Pools>();
|
|
||||||
if let Some(pool) = pools.get_mut(target) {
|
|
||||||
if let EffectType::Healing { amount, increment_max } = &heal.effect_type {
|
|
||||||
let before = pool.hit_points.current;
|
|
||||||
pool.hit_points.current = i32::min(
|
|
||||||
pool.hit_points.max,
|
|
||||||
pool.hit_points.current + amount
|
|
||||||
);
|
|
||||||
if pool.hit_points.current - before < *amount && *increment_max {
|
|
||||||
// If the heal was not fully effective, and healing source was noncursed, increase max HP by 1.
|
|
||||||
pool.hit_points.max += 1;
|
|
||||||
pool.hit_points.current += 1;
|
|
||||||
}
|
|
||||||
add_effect(
|
|
||||||
None,
|
|
||||||
EffectType::Particle {
|
|
||||||
glyph: to_cp437('♥'),
|
|
||||||
fg: RGB::named(BLUE),
|
|
||||||
bg: RGB::named(BLACK),
|
|
||||||
lifespan: DEFAULT_PARTICLE_LIFETIME,
|
|
||||||
delay: 0.0,
|
|
||||||
},
|
|
||||||
Targets::Entity { target }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_confusion(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
|
|
||||||
if let EffectType::Confusion { turns } = &effect.effect_type {
|
|
||||||
ecs.write_storage::<Confusion>()
|
|
||||||
.insert(target, Confusion { turns: *turns })
|
|
||||||
.expect("Unable to insert Confusion");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bloodstain(ecs: &mut World, target: usize, colour: RGB) {
|
|
||||||
let mut map = ecs.fetch_mut::<Map>();
|
|
||||||
// If the current tile isn't bloody, bloody it.
|
|
||||||
if !map.bloodstains.contains_key(&target) {
|
|
||||||
map.bloodstains.insert(target, colour);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if map.bloodstains.get(&target).unwrap() == &colour {
|
|
||||||
let mut spread: i32 = target as i32;
|
|
||||||
let mut attempts: i32 = 0;
|
|
||||||
// Otherwise, roll to move one tile in any direction.
|
|
||||||
// If this tile isn't bloody, bloody it. If not, loop.
|
|
||||||
loop {
|
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
|
||||||
attempts += 1;
|
|
||||||
spread = match rng.roll_dice(1, 8) {
|
|
||||||
1 => spread + 1,
|
|
||||||
2 => spread - 1,
|
|
||||||
3 => spread + 1 + map.width,
|
|
||||||
4 => spread - 1 + map.width,
|
|
||||||
5 => spread + 1 - map.width,
|
|
||||||
6 => spread - 1 - map.width,
|
|
||||||
7 => spread + map.width,
|
|
||||||
_ => spread - map.width,
|
|
||||||
};
|
|
||||||
// - If we're in bounds and the tile is unbloodied, bloody it and return.
|
|
||||||
// - If we ever leave bounds, return.
|
|
||||||
// - Roll a dice on each failed attempt, with an increasing change to return (soft-capping max spread)
|
|
||||||
if spread > 0 && spread < map.height * map.width {
|
|
||||||
if !map.bloodstains.contains_key(&(spread as usize)) {
|
|
||||||
map.bloodstains.insert(spread as usize, colour);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If bloodied with the same colour, return
|
|
||||||
if map.bloodstains.get(&(spread as usize)).unwrap() == &colour {
|
|
||||||
if rng.roll_dice(1, 10 - attempts) == 1 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If bloodied but a *different* colour, lerp this blood and current blood.
|
|
||||||
} else {
|
|
||||||
let new_col = map.bloodstains
|
|
||||||
.get(&(spread as usize))
|
|
||||||
.unwrap()
|
|
||||||
.lerp(colour, 0.5);
|
|
||||||
map.bloodstains.insert(spread as usize, new_col);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let curr_blood = map.bloodstains.get(&target).unwrap();
|
|
||||||
let new_colour = curr_blood.lerp(colour, 0.5);
|
|
||||||
map.bloodstains.insert(target, new_colour);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Takes a level, and returns the total XP required to reach the next level.
|
|
||||||
fn get_next_level_requirement(level: i32) -> i32 {
|
|
||||||
if level == 0 {
|
|
||||||
return 5;
|
|
||||||
} else if level < 10 {
|
|
||||||
return 20 * (2_i32).pow((level as u32) - 1);
|
|
||||||
} else if level < 20 {
|
|
||||||
return 10000 * (2_i32).pow((level as u32) - 10);
|
|
||||||
} else if level < 30 {
|
|
||||||
return 10000000 * (level - 19);
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_death_message(ecs: &World, source: Entity) -> String {
|
|
||||||
let player = ecs.fetch::<Entity>();
|
|
||||||
let mut result: String = format!("{} ", PLAYER_DIED);
|
|
||||||
// If we killed ourselves,
|
|
||||||
if source == *player {
|
|
||||||
result.push_str(format!("{}", PLAYER_DIED_SUICIDE).as_str());
|
|
||||||
} else if let Some(name) = ecs.read_storage::<Name>().get(source) {
|
|
||||||
result.push_str(
|
|
||||||
format!("{} {}", PLAYER_DIED_NAMED_ATTACKER, with_article(name.name.clone())).as_str()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
result.push_str(format!("{}", PLAYER_DIED_UNKNOWN).as_str());
|
|
||||||
}
|
|
||||||
// Status effects
|
|
||||||
{
|
|
||||||
let mut addendums: Vec<&str> = Vec::new();
|
|
||||||
if let Some(_confused) = ecs.read_storage::<Confusion>().get(*player) {
|
|
||||||
addendums.push(STATUS_CONFUSED_STRING);
|
|
||||||
}
|
|
||||||
if let Some(_blind) = ecs.read_storage::<Blind>().get(*player) {
|
|
||||||
addendums.push(STATUS_BLIND_STRING);
|
|
||||||
}
|
|
||||||
if !addendums.is_empty() {
|
|
||||||
result.push_str(" whilst");
|
|
||||||
for (i, addendum) in addendums.iter().enumerate() {
|
|
||||||
if i == 0 {
|
|
||||||
result.push_str(format!("{}{}", PLAYER_DIED_ADDENDUM_FIRST, addendum).as_str());
|
|
||||||
} else if i == addendums.len() {
|
|
||||||
result.push_str(format!("{}{}", PLAYER_DIED_ADDENDUM_LAST, addendum).as_str());
|
|
||||||
} else {
|
|
||||||
result.push_str(format!("{}{}", PLAYER_DIED_ADDENDUM_MID, addendum).as_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handles EntityDeath effects.
|
|
||||||
pub fn entity_death(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
|
|
||||||
let mut xp_gain = 0;
|
|
||||||
let mut pools = ecs.write_storage::<Pools>();
|
|
||||||
let attributes = ecs.read_storage::<Attributes>();
|
|
||||||
let names = ecs.read_storage::<Name>();
|
|
||||||
let player = ecs.fetch::<Entity>();
|
|
||||||
// If the target has a position, remove it from the SpatialMap.
|
|
||||||
if let Some(pos) = targeting::entity_position(ecs, target) {
|
|
||||||
crate::spatial::remove_entity(target, pos as usize);
|
|
||||||
}
|
|
||||||
// If the target was killed by a source, cont.
|
|
||||||
if let Some(source) = effect.source {
|
|
||||||
// If the target was the player, game over, and record source of death.
|
|
||||||
if target == *player {
|
|
||||||
gamelog::record_event(EVENT::PlayerDied(get_death_message(ecs, source)));
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
// If the player was the source, record the kill.
|
|
||||||
if let Some(tar_name) = names.get(target) {
|
|
||||||
gamelog::record_event(EVENT::Killed(tar_name.name.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Calc XP value of target.
|
|
||||||
if let Some(xp_value) = ecs.read_storage::<GrantsXP>().get(target) {
|
|
||||||
xp_gain += xp_value.amount;
|
|
||||||
}
|
|
||||||
// If there was XP, run through XP-gain and level-up.
|
|
||||||
if xp_gain != 0 {
|
|
||||||
if let None = pools.get(source) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut source_pools = pools.get_mut(source).unwrap();
|
|
||||||
let source_attributes = attributes.get(source).unwrap();
|
|
||||||
source_pools.xp += xp_gain;
|
|
||||||
let next_level_requirement = get_next_level_requirement(source_pools.level);
|
|
||||||
if next_level_requirement != -1 && source_pools.xp >= next_level_requirement {
|
|
||||||
source_pools.level += 1;
|
|
||||||
// If it was the PLAYER that levelled up:
|
|
||||||
if ecs.read_storage::<Player>().get(source).is_some() {
|
|
||||||
gamelog::record_event(EVENT::Level(1));
|
|
||||||
gamelog::Logger
|
|
||||||
::new()
|
|
||||||
.append(LEVELUP_PLAYER)
|
|
||||||
.append_n(source_pools.level)
|
|
||||||
.append("!")
|
|
||||||
.log();
|
|
||||||
let player_pos = ecs.fetch::<Point>();
|
|
||||||
let map = ecs.fetch_mut::<Map>();
|
|
||||||
for i in 0..5 {
|
|
||||||
if player_pos.y - i > 1 {
|
|
||||||
add_effect(
|
|
||||||
None,
|
|
||||||
EffectType::Particle {
|
|
||||||
glyph: to_cp437('░'),
|
|
||||||
fg: RGB::named(GOLD),
|
|
||||||
bg: RGB::named(BLACK),
|
|
||||||
lifespan: LONG_PARTICLE_LIFETIME,
|
|
||||||
delay: (i as f32) * 100.0,
|
|
||||||
},
|
|
||||||
Targets::Tile { target: map.xy_idx(player_pos.x, player_pos.y - i) }
|
|
||||||
);
|
|
||||||
if i > 2 {
|
|
||||||
add_effect(
|
|
||||||
None,
|
|
||||||
EffectType::Particle {
|
|
||||||
glyph: to_cp437('░'),
|
|
||||||
fg: RGB::named(GOLD),
|
|
||||||
bg: RGB::named(BLACK),
|
|
||||||
lifespan: LONG_PARTICLE_LIFETIME,
|
|
||||||
delay: (i as f32) * 100.0,
|
|
||||||
},
|
|
||||||
Targets::Tile {
|
|
||||||
target: map.xy_idx(
|
|
||||||
player_pos.x + (i - 2),
|
|
||||||
player_pos.y - i
|
|
||||||
),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
add_effect(
|
|
||||||
None,
|
|
||||||
EffectType::Particle {
|
|
||||||
glyph: to_cp437('░'),
|
|
||||||
fg: RGB::named(GOLD),
|
|
||||||
bg: RGB::named(BLACK),
|
|
||||||
lifespan: LONG_PARTICLE_LIFETIME,
|
|
||||||
delay: (i as f32) * 100.0,
|
|
||||||
},
|
|
||||||
Targets::Tile {
|
|
||||||
target: map.xy_idx(
|
|
||||||
player_pos.x - (i - 2),
|
|
||||||
player_pos.y - i
|
|
||||||
),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console::log("DEBUGINFO: Something other than the player levelled up.");
|
|
||||||
// TODO: Growing up, NPC-specific level-up cases.
|
|
||||||
}
|
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
|
||||||
let hp_gained = hp_per_level(
|
|
||||||
&mut rng,
|
|
||||||
source_attributes.constitution.base + source_attributes.constitution.modifiers
|
|
||||||
);
|
|
||||||
let mana_gained = mana_per_level(
|
|
||||||
&mut rng,
|
|
||||||
source_attributes.intelligence.base + source_attributes.intelligence.modifiers
|
|
||||||
);
|
|
||||||
source_pools.hit_points.max += hp_gained;
|
|
||||||
source_pools.hit_points.current += hp_gained;
|
|
||||||
// Roll for MANA gain this level
|
|
||||||
source_pools.mana.max += mana_gained;
|
|
||||||
source_pools.mana.current += mana_gained;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if target == *player {
|
|
||||||
if let Some(hc) = ecs.read_storage::<HungerClock>().get(target) {
|
|
||||||
if hc.state == HungerState::Starving {
|
|
||||||
gamelog::record_event(EVENT::PlayerDied("You starved to death!".to_string()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
gamelog::record_event(
|
|
||||||
EVENT::PlayerDied("You died from unknown causes!".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
use super::{ EffectSpawner, EffectType };
|
|
||||||
use crate::HungerClock;
|
|
||||||
use specs::prelude::*;
|
|
||||||
|
|
||||||
pub fn modify_nutrition(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
|
|
||||||
if let EffectType::ModifyNutrition { amount } = &effect.effect_type {
|
|
||||||
if let Some(hc) = ecs.write_storage::<HungerClock>().get_mut(target) {
|
|
||||||
hc.duration += amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
use super::{ EffectSpawner, EffectType };
|
|
||||||
use specs::prelude::*;
|
|
||||||
|
|
||||||
pub fn add_intrinsic(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
|
|
||||||
let intrinsic = if let EffectType::AddIntrinsic { intrinsic } = &effect.effect_type {
|
|
||||||
intrinsic
|
|
||||||
} else {
|
|
||||||
unreachable!("add_intrinsic() called with the wrong EffectType")
|
|
||||||
};
|
|
||||||
add_intr!(ecs, target, *intrinsic);
|
|
||||||
}
|
|
||||||
|
|
@ -1,190 +0,0 @@
|
||||||
use super::BUC;
|
|
||||||
use crate::spatial;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use crate::components::*;
|
|
||||||
|
|
||||||
mod damage;
|
|
||||||
mod hunger;
|
|
||||||
mod particles;
|
|
||||||
mod targeting;
|
|
||||||
mod triggers;
|
|
||||||
mod intrinsics;
|
|
||||||
|
|
||||||
pub use targeting::aoe_tiles;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref EFFECT_QUEUE: Mutex<VecDeque<EffectSpawner>> = Mutex::new(VecDeque::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref DEAD_ENTITIES: Mutex<VecDeque<Entity>> = Mutex::new(VecDeque::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum EffectType {
|
|
||||||
Damage {
|
|
||||||
amount: i32,
|
|
||||||
damage_type: DamageType,
|
|
||||||
},
|
|
||||||
Healing {
|
|
||||||
amount: i32,
|
|
||||||
increment_max: bool,
|
|
||||||
},
|
|
||||||
Confusion {
|
|
||||||
turns: i32,
|
|
||||||
},
|
|
||||||
Bloodstain {
|
|
||||||
colour: RGB,
|
|
||||||
},
|
|
||||||
Particle {
|
|
||||||
glyph: FontCharType,
|
|
||||||
fg: RGB,
|
|
||||||
bg: RGB,
|
|
||||||
lifespan: f32,
|
|
||||||
delay: f32,
|
|
||||||
},
|
|
||||||
EntityDeath,
|
|
||||||
ItemUse {
|
|
||||||
item: Entity,
|
|
||||||
},
|
|
||||||
ModifyNutrition {
|
|
||||||
amount: i32,
|
|
||||||
},
|
|
||||||
AddIntrinsic {
|
|
||||||
intrinsic: Intrinsic,
|
|
||||||
},
|
|
||||||
TriggerFire {
|
|
||||||
trigger: Entity,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum Targets {
|
|
||||||
Entity {
|
|
||||||
target: Entity,
|
|
||||||
},
|
|
||||||
EntityList {
|
|
||||||
targets: Vec<Entity>,
|
|
||||||
},
|
|
||||||
Tile {
|
|
||||||
target: usize,
|
|
||||||
},
|
|
||||||
TileList {
|
|
||||||
targets: Vec<usize>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EffectSpawner {
|
|
||||||
pub source: Option<Entity>,
|
|
||||||
pub effect_type: EffectType,
|
|
||||||
pub target: Targets,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds an effect to the effects queue
|
|
||||||
pub fn add_effect(source: Option<Entity>, effect_type: EffectType, target: Targets) {
|
|
||||||
let mut lock = EFFECT_QUEUE.lock().unwrap();
|
|
||||||
lock.push_back(EffectSpawner { source, effect_type, target });
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterates through the effects queue, applying each effect to their target.
|
|
||||||
pub fn run_effects_queue(ecs: &mut World) {
|
|
||||||
// First removes any effect in the EFFECT_QUEUE with a dead entity as its source.
|
|
||||||
loop {
|
|
||||||
let dead_entity: Option<Entity> = DEAD_ENTITIES.lock().unwrap().pop_front();
|
|
||||||
if let Some(dead_entity) = dead_entity {
|
|
||||||
EFFECT_QUEUE.lock()
|
|
||||||
.unwrap()
|
|
||||||
.retain(|x| x.source != Some(dead_entity));
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Then runs every effect that remains in the queue.
|
|
||||||
loop {
|
|
||||||
let effect: Option<EffectSpawner> = EFFECT_QUEUE.lock().unwrap().pop_front();
|
|
||||||
if let Some(effect) = effect {
|
|
||||||
target_applicator(ecs, &effect);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Applies an effect to the correct target(s).
|
|
||||||
fn target_applicator(ecs: &mut World, effect: &EffectSpawner) {
|
|
||||||
// Item use is handled differently - it creates other effects with itself
|
|
||||||
// as the source, passing all effects attached to the item into the queue.
|
|
||||||
if let EffectType::ItemUse { item } = effect.effect_type {
|
|
||||||
triggers::item_trigger(effect.source, item, &effect.target, ecs);
|
|
||||||
return;
|
|
||||||
} else if let EffectType::TriggerFire { trigger } = effect.effect_type {
|
|
||||||
triggers::trigger(effect.source, trigger, &effect.target, ecs);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Otherwise, just match the effect and enact it directly.
|
|
||||||
match &effect.target {
|
|
||||||
Targets::Tile { target } => affect_tile(ecs, effect, *target),
|
|
||||||
Targets::TileList { targets } =>
|
|
||||||
targets.iter().for_each(|target| affect_tile(ecs, effect, *target)),
|
|
||||||
Targets::Entity { target } => affect_entity(ecs, effect, *target),
|
|
||||||
Targets::EntityList { targets } =>
|
|
||||||
targets.iter().for_each(|target| affect_entity(ecs, effect, *target)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs an effect on a given tile index
|
|
||||||
fn affect_tile(ecs: &mut World, effect: &EffectSpawner, target: usize) {
|
|
||||||
if tile_effect_hits_entities(&effect.effect_type) {
|
|
||||||
spatial::for_each_tile_content(target, |entity| {
|
|
||||||
affect_entity(ecs, effect, entity);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
match &effect.effect_type {
|
|
||||||
EffectType::Particle { .. } => particles::particle_to_tile(ecs, target as i32, &effect),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
// Run the effect
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if a given effect affects entities or not.
|
|
||||||
fn tile_effect_hits_entities(effect: &EffectType) -> bool {
|
|
||||||
match effect {
|
|
||||||
EffectType::Damage { .. } => true,
|
|
||||||
EffectType::Healing { .. } => true,
|
|
||||||
EffectType::ModifyNutrition { .. } => true,
|
|
||||||
EffectType::Confusion { .. } => true,
|
|
||||||
EffectType::AddIntrinsic { .. } => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs an effect on a given entity
|
|
||||||
fn affect_entity(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
|
|
||||||
match &effect.effect_type {
|
|
||||||
EffectType::Damage { .. } => damage::inflict_damage(ecs, effect, target),
|
|
||||||
EffectType::Healing { .. } => damage::heal_damage(ecs, effect, target),
|
|
||||||
EffectType::Confusion { .. } => damage::add_confusion(ecs, effect, target),
|
|
||||||
EffectType::Bloodstain { colour } => {
|
|
||||||
if let Some(pos) = targeting::entity_position(ecs, target) {
|
|
||||||
damage::bloodstain(ecs, pos, *colour);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EffectType::Particle { .. } => {
|
|
||||||
if let Some(pos) = targeting::entity_position(ecs, target) {
|
|
||||||
particles::particle_to_tile(ecs, pos as i32, &effect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EffectType::EntityDeath => damage::entity_death(ecs, effect, target),
|
|
||||||
EffectType::ModifyNutrition { .. } => hunger::modify_nutrition(ecs, effect, target),
|
|
||||||
EffectType::AddIntrinsic { .. } => intrinsics::add_intrinsic(ecs, effect, target),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_noncursed(buc: &BUC) -> bool {
|
|
||||||
if buc == &BUC::Cursed { false } else { true }
|
|
||||||
}
|
|
||||||
|
|
@ -1,187 +0,0 @@
|
||||||
use super::{ add_effect, targeting, EffectSpawner, EffectType, Targets };
|
|
||||||
use crate::{ Map, ParticleBuilder, SpawnParticleBurst, SpawnParticleLine, SpawnParticleSimple };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
|
|
||||||
pub fn particle_to_tile(ecs: &mut World, target: i32, effect: &EffectSpawner) {
|
|
||||||
if let EffectType::Particle { glyph, fg, bg, lifespan, delay } = effect.effect_type {
|
|
||||||
let map = ecs.fetch::<Map>();
|
|
||||||
let mut particle_builder = ecs.fetch_mut::<ParticleBuilder>();
|
|
||||||
if delay <= 0.0 {
|
|
||||||
particle_builder.request(
|
|
||||||
target % map.width,
|
|
||||||
target / map.width,
|
|
||||||
fg,
|
|
||||||
bg,
|
|
||||||
glyph,
|
|
||||||
lifespan
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
particle_builder.delay(
|
|
||||||
target % map.width,
|
|
||||||
target / map.width,
|
|
||||||
fg,
|
|
||||||
bg,
|
|
||||||
glyph,
|
|
||||||
lifespan,
|
|
||||||
delay
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_simple_particles(ecs: &World, entity: Entity, target: &Targets) {
|
|
||||||
if let Some(part) = ecs.read_storage::<SpawnParticleSimple>().get(entity) {
|
|
||||||
add_effect(
|
|
||||||
None,
|
|
||||||
EffectType::Particle {
|
|
||||||
glyph: part.glyph,
|
|
||||||
fg: part.colour,
|
|
||||||
bg: RGB::named(BLACK),
|
|
||||||
lifespan: part.lifetime_ms,
|
|
||||||
delay: 0.0,
|
|
||||||
},
|
|
||||||
target.clone()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_burst_particles(ecs: &World, entity: Entity, target: &Targets) {
|
|
||||||
if let Some(part) = ecs.read_storage::<SpawnParticleBurst>().get(entity) {
|
|
||||||
if let Some(start_pos) = targeting::find_item_position(ecs, entity) {
|
|
||||||
let end_pos: i32 = get_centre(ecs, target);
|
|
||||||
spawn_line_particles(
|
|
||||||
ecs,
|
|
||||||
start_pos,
|
|
||||||
end_pos,
|
|
||||||
&(SpawnParticleLine {
|
|
||||||
glyph: part.head_glyph,
|
|
||||||
tail_glyph: part.tail_glyph,
|
|
||||||
colour: part.colour,
|
|
||||||
trail_colour: part.trail_colour,
|
|
||||||
lifetime_ms: part.trail_lifetime_ms, // 75.0 is good here.
|
|
||||||
trail_lifetime_ms: part.trail_lifetime_ms,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
let map = ecs.fetch::<Map>();
|
|
||||||
let line = line2d(
|
|
||||||
LineAlg::Bresenham,
|
|
||||||
Point::new(start_pos % map.width, start_pos / map.width),
|
|
||||||
Point::new(end_pos % map.width, end_pos / map.width)
|
|
||||||
);
|
|
||||||
let burst_delay = (line.len() as f32) * part.trail_lifetime_ms;
|
|
||||||
for i in 0..10 {
|
|
||||||
add_effect(
|
|
||||||
None,
|
|
||||||
EffectType::Particle {
|
|
||||||
glyph: part.glyph,
|
|
||||||
fg: part.colour.lerp(part.lerp, (i as f32) * 0.1),
|
|
||||||
bg: RGB::named(BLACK),
|
|
||||||
lifespan: part.lifetime_ms / 10.0, // ~50-80 is good here.
|
|
||||||
delay: burst_delay + ((i as f32) * part.lifetime_ms) / 10.0, // above + burst_delay
|
|
||||||
},
|
|
||||||
target.clone()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_centre(ecs: &World, target: &Targets) -> i32 {
|
|
||||||
match target {
|
|
||||||
Targets::Tile { target } => {
|
|
||||||
return *target as i32;
|
|
||||||
}
|
|
||||||
Targets::TileList { targets } => {
|
|
||||||
let map = ecs.fetch::<Map>();
|
|
||||||
let (mut count, mut sum_x, mut sum_y) = (0, 0, 0);
|
|
||||||
for target in targets {
|
|
||||||
sum_x += (*target as i32) % map.width;
|
|
||||||
sum_y += (*target as i32) / map.width;
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
let (mean_x, mean_y) = (sum_x / count, sum_y / count);
|
|
||||||
let centre = map.xy_idx(mean_x, mean_y);
|
|
||||||
return centre as i32;
|
|
||||||
}
|
|
||||||
Targets::Entity { target } => {
|
|
||||||
return targeting::entity_position(ecs, *target).unwrap() as i32;
|
|
||||||
}
|
|
||||||
Targets::EntityList { targets } => {
|
|
||||||
let map = ecs.fetch::<Map>();
|
|
||||||
let (mut count, mut sum_x, mut sum_y) = (0, 0, 0);
|
|
||||||
for target in targets {
|
|
||||||
if let Some(pos) = targeting::entity_position(ecs, *target) {
|
|
||||||
sum_x += (pos as i32) % map.width;
|
|
||||||
sum_y += (pos as i32) / map.width;
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let (mean_x, mean_y) = (sum_x / count, sum_y / count);
|
|
||||||
let centre = map.xy_idx(mean_x, mean_y);
|
|
||||||
return centre as i32;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_line_particles(ecs: &World, entity: Entity, target: &Targets) {
|
|
||||||
if let Some(part) = ecs.read_storage::<SpawnParticleLine>().get(entity) {
|
|
||||||
if let Some(start_pos) = targeting::find_item_position(ecs, entity) {
|
|
||||||
match target {
|
|
||||||
Targets::Tile { target } =>
|
|
||||||
spawn_line_particles(ecs, start_pos, *target as i32, part),
|
|
||||||
Targets::TileList { targets } => {
|
|
||||||
targets
|
|
||||||
.iter()
|
|
||||||
.for_each(|target|
|
|
||||||
spawn_line_particles(ecs, start_pos, *target as i32, part)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Targets::Entity { target } => {
|
|
||||||
if let Some(end_pos) = targeting::entity_position(ecs, *target) {
|
|
||||||
spawn_line_particles(ecs, start_pos, end_pos as i32, part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Targets::EntityList { targets } =>
|
|
||||||
targets.iter().for_each(|target| {
|
|
||||||
if let Some(end_pos) = targeting::entity_position(ecs, *target) {
|
|
||||||
spawn_line_particles(ecs, start_pos, end_pos as i32, part);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn_line_particles(ecs: &World, start: i32, end: i32, part: &SpawnParticleLine) {
|
|
||||||
let map = ecs.fetch::<Map>();
|
|
||||||
let start_pt = Point::new(start % map.width, start / map.width);
|
|
||||||
let end_pt = Point::new(end % map.width, end / map.width);
|
|
||||||
let line = line2d(LineAlg::Bresenham, start_pt, end_pt);
|
|
||||||
for (i, pt) in line.iter().enumerate() {
|
|
||||||
add_effect(
|
|
||||||
None,
|
|
||||||
EffectType::Particle {
|
|
||||||
glyph: part.glyph,
|
|
||||||
fg: part.colour,
|
|
||||||
bg: RGB::named(BLACK),
|
|
||||||
lifespan: part.lifetime_ms,
|
|
||||||
delay: (i as f32) * part.lifetime_ms,
|
|
||||||
},
|
|
||||||
Targets::Tile { target: map.xy_idx(pt.x, pt.y) }
|
|
||||||
);
|
|
||||||
if i > 0 {
|
|
||||||
add_effect(
|
|
||||||
None,
|
|
||||||
EffectType::Particle {
|
|
||||||
glyph: part.tail_glyph,
|
|
||||||
fg: part.trail_colour,
|
|
||||||
bg: RGB::named(BLACK),
|
|
||||||
lifespan: part.trail_lifetime_ms,
|
|
||||||
delay: (i as f32) * part.lifetime_ms,
|
|
||||||
},
|
|
||||||
Targets::Tile { target: map.xy_idx(line[i - 1].x, line[i - 1].y) }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
use crate::{ Equipped, InBackpack, Map, Position };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
|
|
||||||
pub fn entity_position(ecs: &World, target: Entity) -> Option<usize> {
|
|
||||||
if let Some(position) = ecs.read_storage::<Position>().get(target) {
|
|
||||||
let map = ecs.fetch::<Map>();
|
|
||||||
return Some(map.xy_idx(position.x, position.y));
|
|
||||||
}
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn aoe_tiles(map: &Map, target: Point, radius: i32) -> Vec<usize> {
|
|
||||||
let mut blast_tiles = field_of_view(target, radius, &*map);
|
|
||||||
blast_tiles.retain(|p| p.x > 0 && p.x < map.width - 1 && p.y > 0 && p.y < map.height - 1);
|
|
||||||
let mut result = Vec::new();
|
|
||||||
for t in blast_tiles.iter() {
|
|
||||||
result.push(map.xy_idx(t.x, t.y));
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_item_position(ecs: &World, target: Entity) -> Option<i32> {
|
|
||||||
let positions = ecs.read_storage::<Position>();
|
|
||||||
let map = ecs.fetch::<Map>();
|
|
||||||
// Does it have a position?
|
|
||||||
if let Some(pos) = positions.get(target) {
|
|
||||||
return Some(map.xy_idx(pos.x, pos.y) as i32);
|
|
||||||
}
|
|
||||||
// If not, is it carried?
|
|
||||||
if let Some(carried) = ecs.read_storage::<InBackpack>().get(target) {
|
|
||||||
if let Some(pos) = positions.get(carried.owner) {
|
|
||||||
return Some(map.xy_idx(pos.x, pos.y) as i32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Is it equipped?
|
|
||||||
if let Some(carried) = ecs.read_storage::<Equipped>().get(target) {
|
|
||||||
if let Some(pos) = positions.get(carried.owner) {
|
|
||||||
return Some(map.xy_idx(pos.x, pos.y) as i32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Out of luck: give up
|
|
||||||
console::log("DEBUGINFO: Failed to find item position");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
@ -1,461 +0,0 @@
|
||||||
use super::{ add_effect, particles, spatial, EffectType, Entity, Targets, World };
|
|
||||||
use crate::{
|
|
||||||
gamelog,
|
|
||||||
gui::item_colour_ecs,
|
|
||||||
gui::obfuscate_name_ecs,
|
|
||||||
gui::renderable_colour,
|
|
||||||
Beatitude,
|
|
||||||
Charges,
|
|
||||||
Confusion,
|
|
||||||
Consumable,
|
|
||||||
Destructible,
|
|
||||||
Equipped,
|
|
||||||
Hidden,
|
|
||||||
InBackpack,
|
|
||||||
InflictsDamage,
|
|
||||||
Item,
|
|
||||||
MagicMapper,
|
|
||||||
MasterDungeonMap,
|
|
||||||
Name,
|
|
||||||
ObfuscatedName,
|
|
||||||
Player,
|
|
||||||
Prop,
|
|
||||||
ProvidesHealing,
|
|
||||||
ProvidesIdentify,
|
|
||||||
ProvidesNutrition,
|
|
||||||
ProvidesRemoveCurse,
|
|
||||||
RandomNumberGenerator,
|
|
||||||
Renderable,
|
|
||||||
RunState,
|
|
||||||
SingleActivation,
|
|
||||||
BUC,
|
|
||||||
GrantsSpell,
|
|
||||||
KnownSpells,
|
|
||||||
Position,
|
|
||||||
Viewshed,
|
|
||||||
WantsToRemoveKey,
|
|
||||||
WantsToDelete,
|
|
||||||
};
|
|
||||||
use crate::data::messages::*;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
pub fn item_trigger(source: Option<Entity>, item: Entity, target: &Targets, ecs: &mut World) {
|
|
||||||
// Check if the item has charges, etc.
|
|
||||||
if let Some(has_charges) = ecs.write_storage::<Charges>().get_mut(item) {
|
|
||||||
if has_charges.uses <= 0 {
|
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
|
||||||
if rng.roll_dice(1, 121) != 1 {
|
|
||||||
gamelog::Logger::new().append(NOCHARGES_DIDNOTHING).log();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gamelog::Logger::new().colour(YELLOW).append(NOCHARGES_WREST);
|
|
||||||
ecs.write_storage::<Consumable>()
|
|
||||||
.insert(item, Consumable {})
|
|
||||||
.expect("Could not insert consumable");
|
|
||||||
}
|
|
||||||
has_charges.uses -= 1;
|
|
||||||
}
|
|
||||||
// Use the item via the generic system
|
|
||||||
let did_something = event_trigger(source, item, target, ecs);
|
|
||||||
// If it's a consumable, delete it
|
|
||||||
if did_something && ecs.read_storage::<Consumable>().get(item).is_some() {
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn trigger(source: Option<Entity>, trigger: Entity, target: &Targets, ecs: &mut World) {
|
|
||||||
// Remove hidden from the trigger
|
|
||||||
ecs.write_storage::<Hidden>().remove(trigger);
|
|
||||||
// Use the trigger via the generic system
|
|
||||||
let did_something = event_trigger(source, trigger, target, ecs);
|
|
||||||
// If it was single-activation, delete it
|
|
||||||
if did_something && ecs.read_storage::<SingleActivation>().get(trigger).is_some() {
|
|
||||||
ecs.entities().delete(trigger).expect("Failed to delete entity with a SingleActivation");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EventInfo {
|
|
||||||
source: Option<Entity>,
|
|
||||||
entity: Entity,
|
|
||||||
target: Targets,
|
|
||||||
buc: BUC,
|
|
||||||
log: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Currently, items can only be used by the player, and so this system is only built for that.
|
|
||||||
// It does almost no sanity-checking to make sure the logs only appear if the effect is taking
|
|
||||||
// place on the player -- once monsters can use an item, their item usage will make logs for
|
|
||||||
// the player saying they were the one who used the item. This will need refactoring then.
|
|
||||||
fn event_trigger(
|
|
||||||
source: Option<Entity>,
|
|
||||||
entity: Entity,
|
|
||||||
target: &Targets,
|
|
||||||
ecs: &mut World
|
|
||||||
) -> bool {
|
|
||||||
let buc = if let Some(beatitude) = ecs.read_storage::<Beatitude>().get(entity) {
|
|
||||||
beatitude.buc.clone()
|
|
||||||
} else {
|
|
||||||
BUC::Uncursed
|
|
||||||
};
|
|
||||||
let mut event = EventInfo { source, entity, target: target.clone(), buc, log: false };
|
|
||||||
let logger = gamelog::Logger::new();
|
|
||||||
|
|
||||||
let mut did_something = false;
|
|
||||||
particles::handle_simple_particles(ecs, entity, target);
|
|
||||||
particles::handle_burst_particles(ecs, entity, &target);
|
|
||||||
particles::handle_line_particles(ecs, entity, &target);
|
|
||||||
let (logger, restored_nutrition) = handle_restore_nutrition(ecs, &mut event, logger);
|
|
||||||
let (logger, magic_mapped) = handle_magic_mapper(ecs, &mut event, logger);
|
|
||||||
let (logger, granted_spell) = handle_grant_spell(ecs, &mut event, logger);
|
|
||||||
let (logger, removed_curse) = handle_remove_curse(ecs, &mut event, logger);
|
|
||||||
let (logger, identified) = handle_identify(ecs, &mut event, logger);
|
|
||||||
let (logger, healed) = handle_healing(ecs, &mut event, logger);
|
|
||||||
let (logger, damaged) = handle_damage(ecs, &mut event, logger);
|
|
||||||
let (logger, confused) = handle_confusion(ecs, &mut event, logger);
|
|
||||||
//let (logger, dug) = handle_dig(ecs, &mut event, logger); -- NYI i.e. Wand of Digging
|
|
||||||
did_something |=
|
|
||||||
restored_nutrition ||
|
|
||||||
magic_mapped ||
|
|
||||||
granted_spell ||
|
|
||||||
healed ||
|
|
||||||
damaged ||
|
|
||||||
confused ||
|
|
||||||
removed_curse ||
|
|
||||||
identified;
|
|
||||||
|
|
||||||
if event.log {
|
|
||||||
logger.log();
|
|
||||||
}
|
|
||||||
|
|
||||||
return did_something;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_restore_nutrition(
|
|
||||||
ecs: &mut World,
|
|
||||||
event: &mut EventInfo,
|
|
||||||
mut logger: gamelog::Logger
|
|
||||||
) -> (gamelog::Logger, bool) {
|
|
||||||
if ecs.read_storage::<ProvidesNutrition>().get(event.entity).is_some() {
|
|
||||||
let amount = match event.buc {
|
|
||||||
BUC::Blessed => 600,
|
|
||||||
BUC::Uncursed => 400,
|
|
||||||
BUC::Cursed => 200,
|
|
||||||
};
|
|
||||||
add_effect(event.source, EffectType::ModifyNutrition { amount }, event.target.clone());
|
|
||||||
logger = logger
|
|
||||||
.append(NUTRITION)
|
|
||||||
.colour(item_colour_ecs(ecs, event.entity))
|
|
||||||
.append_n(obfuscate_name_ecs(ecs, event.entity).0)
|
|
||||||
.colour(WHITE)
|
|
||||||
.period()
|
|
||||||
.buc(event.buc.clone(), Some(NUTRITION_CURSED), Some(NUTRITION_BLESSED));
|
|
||||||
event.log = true;
|
|
||||||
return (logger, true);
|
|
||||||
}
|
|
||||||
return (logger, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_magic_mapper(
|
|
||||||
ecs: &mut World,
|
|
||||||
event: &mut EventInfo,
|
|
||||||
mut logger: gamelog::Logger
|
|
||||||
) -> (gamelog::Logger, bool) {
|
|
||||||
if ecs.read_storage::<MagicMapper>().get(event.entity).is_some() {
|
|
||||||
let mut runstate = ecs.fetch_mut::<RunState>();
|
|
||||||
let cursed = if event.buc == BUC::Cursed { true } else { false };
|
|
||||||
*runstate = RunState::MagicMapReveal { row: 0, cursed: cursed };
|
|
||||||
logger = logger.append(MAGICMAP).buc(event.buc.clone(), Some(MAGICMAP_CURSED), None);
|
|
||||||
event.log = true;
|
|
||||||
return (logger, true);
|
|
||||||
}
|
|
||||||
return (logger, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_grant_spell(
|
|
||||||
ecs: &mut World,
|
|
||||||
event: &mut EventInfo,
|
|
||||||
logger: gamelog::Logger
|
|
||||||
) -> (gamelog::Logger, bool) {
|
|
||||||
if let Some(_granted_spell) = ecs.read_storage::<GrantsSpell>().get(event.entity) {
|
|
||||||
if
|
|
||||||
let Some(_known_spells) = ecs
|
|
||||||
.write_storage::<KnownSpells>()
|
|
||||||
.get_mut(event.source.unwrap())
|
|
||||||
{
|
|
||||||
// TODO: Check if the player knows *this* spell, and add it if not.
|
|
||||||
} else {
|
|
||||||
// TODO: Grant the KnownSpells component, and then add the spell.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (logger, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_healing(
|
|
||||||
ecs: &mut World,
|
|
||||||
event: &mut EventInfo,
|
|
||||||
mut logger: gamelog::Logger
|
|
||||||
) -> (gamelog::Logger, bool) {
|
|
||||||
if let Some(healing_item) = ecs.read_storage::<ProvidesHealing>().get(event.entity) {
|
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
|
||||||
let buc_mod = match event.buc {
|
|
||||||
BUC::Blessed => 2,
|
|
||||||
BUC::Cursed => -1,
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
let roll =
|
|
||||||
rng.roll_dice(healing_item.n_dice + buc_mod, healing_item.sides) +
|
|
||||||
healing_item.modifier;
|
|
||||||
add_effect(
|
|
||||||
event.source,
|
|
||||||
EffectType::Healing { amount: roll, increment_max: event.buc.noncursed() },
|
|
||||||
event.target.clone()
|
|
||||||
);
|
|
||||||
for target in get_entity_targets(&event.target) {
|
|
||||||
if
|
|
||||||
ecs.read_storage::<Prop>().get(target).is_some() ||
|
|
||||||
ecs.read_storage::<Item>().get(target).is_some()
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let renderables = ecs.read_storage::<Renderable>();
|
|
||||||
if ecs.read_storage::<Player>().get(target).is_some() {
|
|
||||||
logger = logger
|
|
||||||
.append("You")
|
|
||||||
.append(HEAL_PLAYER_HIT)
|
|
||||||
.buc(event.buc.clone(), None, Some(HEAL_PLAYER_HIT_BLESSED));
|
|
||||||
} else {
|
|
||||||
logger = logger
|
|
||||||
.append("The")
|
|
||||||
.colour(renderable_colour(&renderables, target))
|
|
||||||
.append(obfuscate_name_ecs(ecs, target).0)
|
|
||||||
.colour(WHITE)
|
|
||||||
.append(HEAL_OTHER_HIT);
|
|
||||||
}
|
|
||||||
event.log = true;
|
|
||||||
}
|
|
||||||
return (logger, true);
|
|
||||||
}
|
|
||||||
return (logger, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_damage(
|
|
||||||
ecs: &mut World,
|
|
||||||
event: &mut EventInfo,
|
|
||||||
mut logger: gamelog::Logger
|
|
||||||
) -> (gamelog::Logger, bool) {
|
|
||||||
if let Some(damage_item) = ecs.read_storage::<InflictsDamage>().get(event.entity) {
|
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
|
||||||
let roll = rng.roll_dice(damage_item.n_dice, damage_item.sides) + damage_item.modifier;
|
|
||||||
add_effect(
|
|
||||||
event.source,
|
|
||||||
EffectType::Damage { amount: roll, damage_type: damage_item.damage_type },
|
|
||||||
event.target.clone()
|
|
||||||
);
|
|
||||||
for target in get_entity_targets(&event.target) {
|
|
||||||
if ecs.read_storage::<Prop>().get(target).is_some() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let renderables = ecs.read_storage::<Renderable>();
|
|
||||||
let positions = ecs.read_storage::<Position>();
|
|
||||||
let target_pos = positions.get(target).unwrap_or(&(Position { x: 0, y: 0 }));
|
|
||||||
let viewsheds = ecs.read_storage::<Viewshed>();
|
|
||||||
let player_viewshed = viewsheds.get(*ecs.fetch::<Entity>()).unwrap();
|
|
||||||
if ecs.read_storage::<Player>().get(target).is_some() {
|
|
||||||
logger = logger
|
|
||||||
.append("You")
|
|
||||||
.append(DAMAGE_PLAYER_HIT);
|
|
||||||
event.log = true;
|
|
||||||
} else if
|
|
||||||
player_viewshed.visible_tiles.contains(&Point::new(target_pos.x, target_pos.y))
|
|
||||||
{
|
|
||||||
if ecs.read_storage::<Item>().get(target).is_some() {
|
|
||||||
if ecs.read_storage::<Destructible>().get(target).is_some() {
|
|
||||||
logger = logger
|
|
||||||
.append("The")
|
|
||||||
.colour(renderable_colour(&renderables, target))
|
|
||||||
.append(obfuscate_name_ecs(ecs, target).0)
|
|
||||||
.colour(WHITE)
|
|
||||||
.append(DAMAGE_ITEM_HIT);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger = logger
|
|
||||||
.append("The")
|
|
||||||
.colour(renderable_colour(&renderables, target))
|
|
||||||
.append(obfuscate_name_ecs(ecs, target).0)
|
|
||||||
.colour(WHITE)
|
|
||||||
.append(DAMAGE_OTHER_HIT);
|
|
||||||
}
|
|
||||||
event.log = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (logger, true);
|
|
||||||
}
|
|
||||||
return (logger, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
|
||||||
fn handle_confusion(
|
|
||||||
ecs: &mut World,
|
|
||||||
event: &mut EventInfo,
|
|
||||||
mut logger: gamelog::Logger
|
|
||||||
) -> (gamelog::Logger, bool) {
|
|
||||||
if let Some(confusion) = ecs.read_storage::<Confusion>().get(event.entity) {
|
|
||||||
add_effect(
|
|
||||||
event.source,
|
|
||||||
EffectType::Confusion { turns: confusion.turns },
|
|
||||||
event.target.clone()
|
|
||||||
);
|
|
||||||
return (logger, true);
|
|
||||||
}
|
|
||||||
return (logger, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select_single(ecs: &World, runstate: RunState) {
|
|
||||||
let mut new_runstate = ecs.fetch_mut::<RunState>();
|
|
||||||
*new_runstate = runstate;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_identify(
|
|
||||||
ecs: &mut World,
|
|
||||||
event: &mut EventInfo,
|
|
||||||
mut logger: gamelog::Logger
|
|
||||||
) -> (gamelog::Logger, bool) {
|
|
||||||
if let Some(_i) = ecs.read_storage::<ProvidesIdentify>().get(event.entity) {
|
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
|
||||||
let mut dm = ecs.fetch_mut::<MasterDungeonMap>();
|
|
||||||
let identify_all = match event.buc {
|
|
||||||
BUC::Blessed => rng.roll_dice(1, 5) == 1,
|
|
||||||
BUC::Uncursed => rng.roll_dice(1, 25) == 1,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
if !identify_all {
|
|
||||||
select_single(ecs, RunState::ShowIdentify);
|
|
||||||
return (logger, true);
|
|
||||||
}
|
|
||||||
let mut to_identify: Vec<(Entity, String)> = Vec::new();
|
|
||||||
let mut beatitudes = ecs.write_storage::<Beatitude>();
|
|
||||||
let obfuscated = ecs.read_storage::<ObfuscatedName>();
|
|
||||||
for (e, _i, _bp, name) in (
|
|
||||||
&ecs.entities(),
|
|
||||||
&ecs.read_storage::<Item>(),
|
|
||||||
&ecs.read_storage::<InBackpack>(),
|
|
||||||
&ecs.read_storage::<Name>(),
|
|
||||||
)
|
|
||||||
.join()
|
|
||||||
.filter(|(e, _i, bp, name)| {
|
|
||||||
let in_this_backpack = bp.owner == event.source.unwrap();
|
|
||||||
let has_obfuscated_name = obfuscated.get(*e).is_some();
|
|
||||||
let already_identified = dm.identified_items.contains(&name.name.clone());
|
|
||||||
let known_beatitude = beatitudes
|
|
||||||
.get(event.source.unwrap())
|
|
||||||
.map(|b| b.known)
|
|
||||||
.unwrap_or(true);
|
|
||||||
let result =
|
|
||||||
in_this_backpack &&
|
|
||||||
(has_obfuscated_name || !already_identified || !known_beatitude);
|
|
||||||
return result;
|
|
||||||
}) {
|
|
||||||
to_identify.push((e, name.name.clone()));
|
|
||||||
}
|
|
||||||
for item in to_identify {
|
|
||||||
dm.identified_items.insert(item.1);
|
|
||||||
if let Some(beatitude) = beatitudes.get_mut(item.0) {
|
|
||||||
beatitude.known = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger = logger
|
|
||||||
.append(IDENTIFY_ALL)
|
|
||||||
.buc(event.buc.clone(), None, Some(IDENTIFY_ALL_BLESSED));
|
|
||||||
event.log = true;
|
|
||||||
return (logger, true);
|
|
||||||
}
|
|
||||||
return (logger, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_remove_curse(
|
|
||||||
ecs: &mut World,
|
|
||||||
event: &mut EventInfo,
|
|
||||||
mut logger: gamelog::Logger
|
|
||||||
) -> (gamelog::Logger, bool) {
|
|
||||||
if let Some(_r) = ecs.read_storage::<ProvidesRemoveCurse>().get(event.entity) {
|
|
||||||
let mut to_decurse: Vec<Entity> = Vec::new();
|
|
||||||
match event.buc {
|
|
||||||
// If cursed, show the prompt to select one item.
|
|
||||||
BUC::Cursed => {
|
|
||||||
select_single(ecs, RunState::ShowRemoveCurse);
|
|
||||||
return (logger, true);
|
|
||||||
}
|
|
||||||
// If blessed, decurse everything in our backpack.
|
|
||||||
BUC::Blessed => {
|
|
||||||
for (entity, _i, _bp, _b) in (
|
|
||||||
&ecs.entities(),
|
|
||||||
&ecs.read_storage::<Item>(),
|
|
||||||
&ecs.read_storage::<InBackpack>(),
|
|
||||||
&ecs.read_storage::<Beatitude>(),
|
|
||||||
)
|
|
||||||
.join()
|
|
||||||
.filter(
|
|
||||||
|(_e, _i, bp, b)| bp.owner == event.source.unwrap() && b.buc == BUC::Cursed
|
|
||||||
) {
|
|
||||||
to_decurse.push(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
// If noncursed, decurse everything we have equipped.
|
|
||||||
for (entity, _i, _e, _b) in (
|
|
||||||
&ecs.entities(),
|
|
||||||
&ecs.read_storage::<Item>(),
|
|
||||||
&ecs.read_storage::<Equipped>(),
|
|
||||||
&ecs.read_storage::<Beatitude>(),
|
|
||||||
)
|
|
||||||
.join()
|
|
||||||
.filter(|(_e, _i, e, b)| e.owner == event.source.unwrap() && b.buc == BUC::Cursed) {
|
|
||||||
to_decurse.push(entity);
|
|
||||||
}
|
|
||||||
if to_decurse.len() == 0 {
|
|
||||||
match event.buc {
|
|
||||||
BUC::Uncursed => select_single(ecs, RunState::ShowRemoveCurse),
|
|
||||||
BUC::Blessed => {
|
|
||||||
logger = logger.append(REMOVECURSE_BLESSED_FAILED);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
return (logger, true);
|
|
||||||
}
|
|
||||||
let mut beatitudes = ecs.write_storage::<Beatitude>();
|
|
||||||
for e in to_decurse {
|
|
||||||
beatitudes
|
|
||||||
.insert(e, Beatitude { buc: BUC::Uncursed, known: true })
|
|
||||||
.expect("Unable to insert beatitude");
|
|
||||||
}
|
|
||||||
logger = logger.append(REMOVECURSE).buc(event.buc.clone(), None, Some(REMOVECURSE_BLESSED));
|
|
||||||
event.log = true;
|
|
||||||
return (logger, true);
|
|
||||||
}
|
|
||||||
return (logger, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_entity_targets(target: &Targets) -> Vec<Entity> {
|
|
||||||
let mut entities: Vec<Entity> = Vec::new();
|
|
||||||
match target {
|
|
||||||
Targets::Entity { target } => entities.push(*target),
|
|
||||||
Targets::EntityList { targets } => targets.iter().for_each(|target| entities.push(*target)),
|
|
||||||
Targets::Tile { target } => {
|
|
||||||
spatial::for_each_tile_content(*target, |entity| {
|
|
||||||
entities.push(entity);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Targets::TileList { targets } => {
|
|
||||||
targets.iter().for_each(|target| {
|
|
||||||
spatial::for_each_tile_content(*target, |entity| {
|
|
||||||
entities.push(entity);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entities;
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
use super::{ append_entry, LogFragment };
|
|
||||||
use crate::BUC;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
pub struct Logger {
|
|
||||||
current_colour: RGB,
|
|
||||||
fragments: Vec<LogFragment>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Logger {
|
|
||||||
/// Creates a blank builder for making message log entries.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Logger { current_colour: RGB::named(WHITE), fragments: Vec::new() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the colour of the current message logger.
|
|
||||||
pub fn colour(mut self, colour: (u8, u8, u8)) -> Self {
|
|
||||||
self.current_colour = RGB::named(colour);
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Appends text in the current colour to the current message logger.
|
|
||||||
pub fn append<T: ToString>(mut self, text: T) -> Self {
|
|
||||||
let mut text_with_space = text.to_string();
|
|
||||||
text_with_space.push_str(" ");
|
|
||||||
self.fragments.push(LogFragment { colour: self.current_colour, text: text_with_space });
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Appends text in the current colour to the current message logger, with no space.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn append_n<T: ToString>(mut self, text: T) -> Self {
|
|
||||||
self.fragments.push(LogFragment { colour: self.current_colour, text: text.to_string() });
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Appends a period to the current message logger.
|
|
||||||
pub fn period(mut self) -> Self {
|
|
||||||
self.fragments.push(LogFragment { colour: self.current_colour, text: ". ".to_string() });
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn buc<T: ToString>(mut self, buc: BUC, cursed: Option<T>, blessed: Option<T>) -> Self {
|
|
||||||
if buc == BUC::Cursed && cursed.is_some() {
|
|
||||||
self.fragments.push(LogFragment {
|
|
||||||
colour: RGB::named(SALMON),
|
|
||||||
text: cursed.unwrap().to_string(),
|
|
||||||
});
|
|
||||||
self.fragments.push(LogFragment {
|
|
||||||
colour: self.current_colour,
|
|
||||||
text: ". ".to_string(),
|
|
||||||
});
|
|
||||||
} else if buc == BUC::Blessed && blessed.is_some() {
|
|
||||||
self.fragments.push(LogFragment {
|
|
||||||
colour: RGB::named(CYAN),
|
|
||||||
text: blessed.unwrap().to_string(),
|
|
||||||
});
|
|
||||||
self.fragments.push(LogFragment {
|
|
||||||
colour: self.current_colour,
|
|
||||||
text: ". ".to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pushes the finished log entry.
|
|
||||||
pub fn log(self) {
|
|
||||||
return append_entry(self.fragments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
||||||
use std::collections::{ HashSet, HashMap };
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use crate::data::events::EVENT;
|
|
||||||
use crate::data::names::*;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
/// A count of each event that has happened over the run. i.e. "turns", "descended", "ascended"
|
|
||||||
pub static ref EVENT_COUNTER: Mutex<HashMap<String, i32>> = Mutex::new(HashMap::new());
|
|
||||||
// A record of events that happened on a given turn. i.e. "Advanced to level 2".
|
|
||||||
pub static ref EVENTS: Mutex<HashMap<u32, Vec<String>>> = Mutex::new(HashMap::new());
|
|
||||||
// A record of floors visited, and monsters killed. Used to determine if an event is significant.
|
|
||||||
static ref VISITED: Mutex<HashSet<String>> = Mutex::new({
|
|
||||||
let mut set = HashSet::new();
|
|
||||||
set.insert(NAME_OVERMAP.to_string());
|
|
||||||
set
|
|
||||||
});
|
|
||||||
static ref KILLED: Mutex<HashSet<String>> = Mutex::new(HashSet::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Makes a copy of event counts (FOR SERIALIZATION)
|
|
||||||
pub fn clone_event_counts() -> HashMap<String, i32> {
|
|
||||||
EVENT_COUNTER.lock().unwrap().clone()
|
|
||||||
}
|
|
||||||
/// Makes a copy of events (FOR SERIALIZATION)
|
|
||||||
pub fn clone_events() -> HashMap<u32, Vec<String>> {
|
|
||||||
EVENTS.lock().unwrap().clone()
|
|
||||||
}
|
|
||||||
/// Fetches event counter into mutex (FOR DESERIALIZATION)
|
|
||||||
pub fn restore_event_counter(events: HashMap<String, i32>) {
|
|
||||||
EVENT_COUNTER.lock().unwrap().clear();
|
|
||||||
events.iter().for_each(|(k, v)| {
|
|
||||||
EVENT_COUNTER.lock().unwrap().insert(k.to_string(), *v);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/// Fetches events into mutex (FOR DESERIALIZATION)
|
|
||||||
pub fn restore_events(events: HashMap<u32, Vec<String>>) {
|
|
||||||
EVENTS.lock().unwrap().clear();
|
|
||||||
events.iter().for_each(|(k, v)| {
|
|
||||||
EVENTS.lock().unwrap().insert(*k, v.to_vec());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/// Wipes all events - for starting a new game.
|
|
||||||
pub fn clear_events() {
|
|
||||||
let (mut events, mut event_counts) = (EVENTS.lock().unwrap(), EVENT_COUNTER.lock().unwrap());
|
|
||||||
events.clear();
|
|
||||||
event_counts.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
|
||||||
/// Increments the event counter by n for a given event.
|
|
||||||
fn modify_event_count<T: ToString>(event: T, n: i32) {
|
|
||||||
let event_name = event.to_string();
|
|
||||||
let mut events_lock = EVENT_COUNTER.lock();
|
|
||||||
let mut events = events_lock.as_mut().unwrap();
|
|
||||||
if let Some(e) = events.get_mut(&event_name) {
|
|
||||||
*e += n;
|
|
||||||
} else {
|
|
||||||
events.insert(event_name, n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Returns how many times an event has taken place.
|
|
||||||
pub fn get_event_count<T: ToString>(event: T) -> i32 {
|
|
||||||
let event_name = event.to_string();
|
|
||||||
let events_lock = EVENT_COUNTER.lock();
|
|
||||||
let events = events_lock.unwrap();
|
|
||||||
if let Some(e) = events.get(&event_name) {
|
|
||||||
*e
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Records an event on the current turn.
|
|
||||||
pub fn record_event(event: EVENT) {
|
|
||||||
let mut new_event: String = "unknown event".to_string();
|
|
||||||
let mut significant_event = true;
|
|
||||||
match event {
|
|
||||||
EVENT::Turn(n) => {
|
|
||||||
modify_event_count(EVENT::COUNT_TURN, n);
|
|
||||||
significant_event = false;
|
|
||||||
}
|
|
||||||
// If de-levelling is ever implemented, this needs refactoring (along with a lot of stuff).
|
|
||||||
EVENT::Level(n) => {
|
|
||||||
modify_event_count(EVENT::COUNT_LEVEL, n);
|
|
||||||
let new_lvl = get_event_count(EVENT::COUNT_LEVEL);
|
|
||||||
if new_lvl == 1 {
|
|
||||||
new_event = format!("You embarked on your first adventure!");
|
|
||||||
} else {
|
|
||||||
new_event = format!("Advanced to level {}", new_lvl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EVENT::ChangedFloor(n) => {
|
|
||||||
modify_event_count(EVENT::COUNT_CHANGED_FLOOR, 1);
|
|
||||||
if VISITED.lock().unwrap().contains(&n) {
|
|
||||||
significant_event = false;
|
|
||||||
} else {
|
|
||||||
VISITED.lock().unwrap().insert(n.clone());
|
|
||||||
new_event = format!("Visited {} for the first time", n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EVENT::KickedSomething(n) => {
|
|
||||||
modify_event_count(EVENT::COUNT_KICK, n);
|
|
||||||
significant_event = false;
|
|
||||||
}
|
|
||||||
EVENT::BrokeDoor(n) => {
|
|
||||||
modify_event_count(EVENT::COUNT_BROKE_DOOR, n);
|
|
||||||
significant_event = false;
|
|
||||||
}
|
|
||||||
EVENT::PlayerConfused(n) => {
|
|
||||||
modify_event_count(EVENT::COUNT_PLAYER_CONFUSED, n);
|
|
||||||
significant_event = false;
|
|
||||||
}
|
|
||||||
EVENT::LookedForHelp(n) => {
|
|
||||||
modify_event_count(EVENT::COUNT_LOOKED_FOR_HELP, n);
|
|
||||||
significant_event = false;
|
|
||||||
}
|
|
||||||
EVENT::Killed(name) => {
|
|
||||||
modify_event_count(EVENT::COUNT_KILLED, 1);
|
|
||||||
if KILLED.lock().unwrap().contains(&name) {
|
|
||||||
significant_event = false;
|
|
||||||
} else {
|
|
||||||
KILLED.lock().unwrap().insert(name.clone());
|
|
||||||
new_event = format!("Killed your first {}", name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EVENT::Discovered(name) => {
|
|
||||||
new_event = format!("Discovered {}", name);
|
|
||||||
}
|
|
||||||
EVENT::Identified(name) => {
|
|
||||||
new_event = format!("Identified {}", crate::gui::with_article(name));
|
|
||||||
}
|
|
||||||
EVENT::PlayerDied(str) => {
|
|
||||||
// Generating the String is handled in the death effect, to avoid passing the ecs here.
|
|
||||||
new_event = format!("{}", str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if significant_event {
|
|
||||||
EVENTS.lock()
|
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.entry(get_event_count(EVENT::COUNT_TURN) as u32)
|
|
||||||
.or_insert_with(Vec::new)
|
|
||||||
.push(new_event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
use super::{ events, LogFragment, Logger };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref LOG: Mutex<Vec<Vec<LogFragment>>> = Mutex::new(Vec::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn append_fragment(fragment: LogFragment) {
|
|
||||||
LOG.lock().unwrap().push(vec![fragment]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn append_entry(fragments: Vec<LogFragment>) {
|
|
||||||
LOG.lock().unwrap().push(fragments);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_log() {
|
|
||||||
LOG.lock().unwrap().clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_log(
|
|
||||||
console: &mut Box<dyn Console>,
|
|
||||||
pos: Point,
|
|
||||||
_descending: bool,
|
|
||||||
len: usize,
|
|
||||||
maximum_len: i32
|
|
||||||
) {
|
|
||||||
let mut y = pos.y;
|
|
||||||
let mut x = pos.x;
|
|
||||||
// Reverse the log, take the number we want to show, and iterate through them
|
|
||||||
LOG.lock()
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.take(len)
|
|
||||||
.for_each(|log| {
|
|
||||||
let mut entry_len = -2;
|
|
||||||
// Iterate through each message fragment, and get the total length
|
|
||||||
// in lines, by adding the length of every fragment and dividing it
|
|
||||||
// by the maximum length we desire. Then shuffle our start-y by that much.
|
|
||||||
log.iter().for_each(|frag| {
|
|
||||||
entry_len += frag.text.len() as i32;
|
|
||||||
});
|
|
||||||
let lines = entry_len / maximum_len;
|
|
||||||
y -= lines;
|
|
||||||
let mut i = 0;
|
|
||||||
log.iter().for_each(|frag| {
|
|
||||||
// Split every fragment up into single characters.
|
|
||||||
let parts = frag.text.split("");
|
|
||||||
for part in parts {
|
|
||||||
// This is an extremely hacky solution to a problem I don't understand yet.
|
|
||||||
// -- without this, the lines *here* and the line count *above* wont match.
|
|
||||||
if part == "" || part == "\\" {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if i > entry_len {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
if x + (part.len() as i32) > pos.x + maximum_len {
|
|
||||||
if y > pos.y - (len as i32) {
|
|
||||||
console.print(x, y, "-");
|
|
||||||
}
|
|
||||||
y += 1;
|
|
||||||
x = pos.x;
|
|
||||||
}
|
|
||||||
// Stay within bounds
|
|
||||||
if y > pos.y - (len as i32) {
|
|
||||||
console.print_color(
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
frag.colour.into(),
|
|
||||||
RGB::named(BLACK).into(),
|
|
||||||
part
|
|
||||||
);
|
|
||||||
}
|
|
||||||
x += part.len() as i32;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Take away one from the y-axis, because we want to start each entry
|
|
||||||
// on a new line, and go up an additional amount depending on how many
|
|
||||||
// lines our *previous* entry took.
|
|
||||||
y -= 1 + lines;
|
|
||||||
x = pos.x;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setup_log() {
|
|
||||||
clear_log();
|
|
||||||
events::clear_events();
|
|
||||||
|
|
||||||
Logger::new()
|
|
||||||
.append("Welcome!")
|
|
||||||
.colour(CYAN)
|
|
||||||
.append_n("Press [?] at any time to view controls")
|
|
||||||
.period()
|
|
||||||
.log();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clone_log() -> Vec<Vec<crate::gamelog::LogFragment>> {
|
|
||||||
return LOG.lock().unwrap().clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn restore_log(log: &mut Vec<Vec<crate::gamelog::LogFragment>>) {
|
|
||||||
LOG.lock().unwrap().clear();
|
|
||||||
LOG.lock().unwrap().append(log);
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
mod builder;
|
|
||||||
pub use builder::*;
|
|
||||||
mod logstore;
|
|
||||||
use logstore::*;
|
|
||||||
pub use logstore::{ clear_log, clone_log, print_log, restore_log, setup_log };
|
|
||||||
mod events;
|
|
||||||
pub use events::*;
|
|
||||||
|
|
||||||
use serde::{ Deserialize, Serialize };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
pub struct LogFragment {
|
|
||||||
pub colour: RGB,
|
|
||||||
pub text: String,
|
|
||||||
}
|
|
||||||
|
|
@ -1,172 +0,0 @@
|
||||||
use super::{ Skill, Skills };
|
|
||||||
use crate::gui::{ Ancestry, Class };
|
|
||||||
use crate::data::entity;
|
|
||||||
use crate::data::char_create::*;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use std::cmp::max;
|
|
||||||
|
|
||||||
/// Returns the attribute bonus for a given attribute score, where every 2 points above
|
|
||||||
/// or below 10 is an additional +1 or -1.
|
|
||||||
pub fn attr_bonus(value: i32) -> i32 {
|
|
||||||
return (value - entity::ATTR_BONUS_0) / entity::ATTR_NEEDED_PER_POINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of HP gained per level for a given constitution score.
|
|
||||||
pub fn hp_per_level(rng: &mut RandomNumberGenerator, constitution: i32) -> i32 {
|
|
||||||
return max(rng.roll_dice(1, entity::STANDARD_HIT_DIE) + attr_bonus(constitution), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
/// Returns a total HP roll for a player, based on a given constitution score and level.
|
|
||||||
pub fn player_hp_at_level(rng: &mut RandomNumberGenerator, constitution: i32, level: i32) -> i32 {
|
|
||||||
let mut total = entity::STANDARD_HIT_DIE + attr_bonus(constitution);
|
|
||||||
for _i in 0..level {
|
|
||||||
total += hp_per_level(rng, constitution);
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a total HP roll for an NPC, based on a given constitution score and level.
|
|
||||||
pub fn npc_hp_at_level(rng: &mut RandomNumberGenerator, constitution: i32, level: i32) -> i32 {
|
|
||||||
if level == 0 {
|
|
||||||
return rng.roll_dice(1, entity::STANDARD_HIT_DIE_0);
|
|
||||||
}
|
|
||||||
let mut total = 1;
|
|
||||||
for _i in 0..level {
|
|
||||||
total += rng.roll_dice(1, entity::STANDARD_HIT_DIE) + attr_bonus(constitution);
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of mana gained per level for a given intelligence score.
|
|
||||||
pub fn mana_per_level(rng: &mut RandomNumberGenerator, intelligence: i32) -> i32 {
|
|
||||||
return max(rng.roll_dice(1, entity::STANDARD_MANA_DIE) + attr_bonus(intelligence), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of mana gained per level for a given intelligence score.
|
|
||||||
pub fn mana_at_level(rng: &mut RandomNumberGenerator, intelligence: i32, level: i32) -> i32 {
|
|
||||||
let mut total = entity::MINIMUM_MANA;
|
|
||||||
for _i in 0..level {
|
|
||||||
total += mana_per_level(rng, intelligence);
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the skill bonus for a given skill, or -4 if the skill is not present.
|
|
||||||
pub fn skill_bonus(skill: Skill, skills: &Skills) -> i32 {
|
|
||||||
if skills.skills.contains_key(&skill) {
|
|
||||||
return skills.skills[&skill];
|
|
||||||
} else {
|
|
||||||
return -4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Roll 4d6 and drop the lowest, for rolling d20-style stats
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn roll_4d6(rng: &mut RandomNumberGenerator) -> i32 {
|
|
||||||
let mut rolls: Vec<i32> = Vec::new();
|
|
||||||
for _i in 0..4 {
|
|
||||||
rolls.push(rng.roll_dice(1, 6));
|
|
||||||
}
|
|
||||||
rolls.sort_unstable();
|
|
||||||
|
|
||||||
let mut roll = 0;
|
|
||||||
for i in 1..rolls.len() {
|
|
||||||
roll += rolls[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return roll;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handles stat distribution for a player character.
|
|
||||||
pub fn get_attribute_rolls(
|
|
||||||
rng: &mut RandomNumberGenerator,
|
|
||||||
class: Class,
|
|
||||||
ancestry: Ancestry
|
|
||||||
) -> (i32, i32, i32, i32, i32, i32) {
|
|
||||||
let (mut str, mut dex, mut con, mut int, mut wis, mut cha) = match class {
|
|
||||||
Class::Fighter => FIGHTER_MIN_ATTR,
|
|
||||||
Class::Rogue => ROGUE_MIN_ATTR,
|
|
||||||
Class::Wizard => WIZARD_MIN_ATTR,
|
|
||||||
Class::Villager => VILLAGER_MIN_ATTR,
|
|
||||||
};
|
|
||||||
let mut remaining_points = TOTAL_ATTRIBUTE_POINTS_MAXIMUM - (str + dex + con + int + wis + cha);
|
|
||||||
let improve_chance: [i32; 6] = match class {
|
|
||||||
Class::Fighter => FIGHTER_IMPR_CHANCE,
|
|
||||||
Class::Rogue => ROGUE_IMPR_CHANCE,
|
|
||||||
Class::Wizard => WIZARD_IMPR_CHANCE,
|
|
||||||
Class::Villager => VILLAGER_IMPR_CHANCE,
|
|
||||||
};
|
|
||||||
let ancestry_maximums: [i32; 6] = match ancestry {
|
|
||||||
Ancestry::Human => HUMAN_MAX_ATTR, // 114
|
|
||||||
Ancestry::Elf => ELF_MAX_ATTR, // 106
|
|
||||||
Ancestry::Dwarf => DWARF_MAX_ATTR, // 106
|
|
||||||
Ancestry::Gnome => GNOME_MAX_ATTR, // 106
|
|
||||||
Ancestry::Catfolk => CATFOLK_MAX_ATTR, // 106
|
|
||||||
_ => UNKNOWN_MAX_ATTR,
|
|
||||||
};
|
|
||||||
let improve_table = crate::random_table::RandomTable
|
|
||||||
::new()
|
|
||||||
.add("Strength", improve_chance[0])
|
|
||||||
.add("Dexterity", improve_chance[1])
|
|
||||||
.add("Constitution", improve_chance[2])
|
|
||||||
.add("Intelligence", improve_chance[3])
|
|
||||||
.add("Wisdom", improve_chance[4])
|
|
||||||
.add("Charisma", improve_chance[5]);
|
|
||||||
let mut failed_attempts = 0;
|
|
||||||
while remaining_points > 0 && failed_attempts < 100 {
|
|
||||||
let roll = improve_table.roll(rng);
|
|
||||||
match roll.as_str() {
|
|
||||||
"Strength" => {
|
|
||||||
if str < ancestry_maximums[0] {
|
|
||||||
str += 1;
|
|
||||||
remaining_points -= 1;
|
|
||||||
} else {
|
|
||||||
failed_attempts += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"Dexterity" => {
|
|
||||||
if dex < ancestry_maximums[1] {
|
|
||||||
dex += 1;
|
|
||||||
remaining_points -= 1;
|
|
||||||
} else {
|
|
||||||
failed_attempts += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"Constitution" => {
|
|
||||||
if con < ancestry_maximums[2] {
|
|
||||||
con += 1;
|
|
||||||
remaining_points -= 1;
|
|
||||||
} else {
|
|
||||||
failed_attempts += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"Intelligence" => {
|
|
||||||
if int < ancestry_maximums[3] {
|
|
||||||
int += 1;
|
|
||||||
remaining_points -= 1;
|
|
||||||
} else {
|
|
||||||
failed_attempts += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"Wisdom" => {
|
|
||||||
if wis < ancestry_maximums[4] {
|
|
||||||
wis += 1;
|
|
||||||
remaining_points -= 1;
|
|
||||||
} else {
|
|
||||||
failed_attempts += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"Charisma" => {
|
|
||||||
if cha < ancestry_maximums[5] {
|
|
||||||
cha += 1;
|
|
||||||
remaining_points -= 1;
|
|
||||||
} else {
|
|
||||||
failed_attempts += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (str, dex, con, int, wis, cha);
|
|
||||||
}
|
|
||||||
|
|
@ -1,461 +0,0 @@
|
||||||
use super::{
|
|
||||||
gamesystem::attr_bonus,
|
|
||||||
gamesystem::get_attribute_rolls,
|
|
||||||
gamesystem::mana_at_level,
|
|
||||||
Attributes,
|
|
||||||
Pools,
|
|
||||||
Renderable,
|
|
||||||
RunState,
|
|
||||||
State,
|
|
||||||
};
|
|
||||||
use crate::data::entity;
|
|
||||||
use crate::data::char_create::*;
|
|
||||||
use crate::{
|
|
||||||
raws,
|
|
||||||
Attribute,
|
|
||||||
Energy,
|
|
||||||
HasAncestry,
|
|
||||||
HasClass,
|
|
||||||
KnownSpell,
|
|
||||||
KnownSpells,
|
|
||||||
Pool,
|
|
||||||
Skill,
|
|
||||||
Skills,
|
|
||||||
Telepath,
|
|
||||||
BUC,
|
|
||||||
};
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use serde::{ Deserialize, Serialize };
|
|
||||||
use specs::prelude::*;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq)]
|
|
||||||
pub enum Ancestry {
|
|
||||||
NULL,
|
|
||||||
Human,
|
|
||||||
Dwarf,
|
|
||||||
Gnome,
|
|
||||||
Elf,
|
|
||||||
Catfolk,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq)]
|
|
||||||
pub enum Class {
|
|
||||||
Fighter,
|
|
||||||
Rogue,
|
|
||||||
Wizard,
|
|
||||||
Villager,
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref ANCESTRY_CLASS_DATA: HashMap<String, Vec<String>> = {
|
|
||||||
let mut m = HashMap::new();
|
|
||||||
// Ancestry
|
|
||||||
m.insert(
|
|
||||||
"human".to_string(),
|
|
||||||
vec![
|
|
||||||
"nothing".to_string()]);
|
|
||||||
m.insert(
|
|
||||||
"dwarf".to_string(),
|
|
||||||
vec![
|
|
||||||
"a natural bonus to defence".to_string()]);
|
|
||||||
m.insert(
|
|
||||||
"elf".to_string(),
|
|
||||||
vec![
|
|
||||||
"minor telepathy".to_string(),
|
|
||||||
"a slightly increased speed".to_string()]);
|
|
||||||
m.insert(
|
|
||||||
"catfolk".to_string(),
|
|
||||||
vec![
|
|
||||||
"increased speed".to_string(),
|
|
||||||
"increased unarmed damage".to_string()]);
|
|
||||||
// Class
|
|
||||||
m.insert(
|
|
||||||
"fighter".to_string(),
|
|
||||||
vec![
|
|
||||||
format!("a longsword, ring mail, and {} food", FIGHTER_STARTING_FOOD),
|
|
||||||
"10 str, 8 dex, 10 con, 6 int, 6 wis, 8 cha".to_string(),
|
|
||||||
"and 27 random stat points".to_string()]);
|
|
||||||
m.insert(
|
|
||||||
"rogue".to_string(),
|
|
||||||
vec![
|
|
||||||
format!("a rapier, leather armour, and {} food", ROGUE_STARTING_FOOD),
|
|
||||||
"8 str, 10 dex, 8 con, 6 int, 8 wis, 10 cha".to_string(),
|
|
||||||
"and 35 random stat points".to_string()]);
|
|
||||||
m.insert(
|
|
||||||
"wizard".to_string(),
|
|
||||||
vec![
|
|
||||||
format!("a dagger, random scrolls/potions, and {} food", WIZARD_STARTING_FOOD),
|
|
||||||
"6 str, 8 dex, 6 con, 10 int, 10 wis, 8 cha".to_string(),
|
|
||||||
"and 17 random stat points".to_string()]);
|
|
||||||
m.insert(
|
|
||||||
"villager".to_string(),
|
|
||||||
vec![
|
|
||||||
format!("the first weapon you could find, and {} food", VILLAGER_STARTING_FOOD),
|
|
||||||
"6 str, 6 dex, 6 con, 6 int, 6 wis, 6 cha".to_string(),
|
|
||||||
"and 39 random stat points".to_string()]);
|
|
||||||
return m;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Copy, Clone)]
|
|
||||||
pub enum CharCreateResult {
|
|
||||||
NoSelection {
|
|
||||||
ancestry: Ancestry,
|
|
||||||
class: Class,
|
|
||||||
},
|
|
||||||
Selected {
|
|
||||||
ancestry: Ancestry,
|
|
||||||
class: Class,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handles the player character creation screen.
|
|
||||||
pub fn character_creation(gs: &mut State, ctx: &mut BTerm) -> CharCreateResult {
|
|
||||||
let runstate = gs.ecs.fetch::<RunState>();
|
|
||||||
|
|
||||||
let mut x = 2;
|
|
||||||
let mut y = 11;
|
|
||||||
let column_width = 20;
|
|
||||||
|
|
||||||
ctx.print_color(x, y, RGB::named(WHITE), RGB::named(BLACK), CHAR_CREATE_HEADER);
|
|
||||||
y += 2;
|
|
||||||
|
|
||||||
if let RunState::CharacterCreation { ancestry, class } = *runstate {
|
|
||||||
let selected_fg = RGB::named(GREEN);
|
|
||||||
let unselected_fg = RGB::named(WHITE);
|
|
||||||
let mut fg;
|
|
||||||
let bg = RGB::named(BLACK);
|
|
||||||
|
|
||||||
// Ancestry
|
|
||||||
ctx.print_color(x, y, bg, unselected_fg, "Ancestry");
|
|
||||||
ctx.print_color(x + column_width, y, bg, unselected_fg, "Class");
|
|
||||||
y += 1;
|
|
||||||
let mut race_str = "human";
|
|
||||||
if ancestry == Ancestry::Human {
|
|
||||||
fg = selected_fg;
|
|
||||||
} else {
|
|
||||||
fg = unselected_fg;
|
|
||||||
}
|
|
||||||
ctx.print_color(x, y, fg, bg, "h. Human");
|
|
||||||
if ancestry == Ancestry::Elf {
|
|
||||||
fg = selected_fg;
|
|
||||||
race_str = "elf";
|
|
||||||
} else {
|
|
||||||
fg = unselected_fg;
|
|
||||||
}
|
|
||||||
ctx.print_color(x, y + 1, fg, bg, "e. Elf");
|
|
||||||
if ancestry == Ancestry::Dwarf {
|
|
||||||
fg = selected_fg;
|
|
||||||
race_str = "dwarf";
|
|
||||||
} else {
|
|
||||||
fg = unselected_fg;
|
|
||||||
}
|
|
||||||
ctx.print_color(x, y + 2, fg, bg, "d. Dwarf");
|
|
||||||
if ancestry == Ancestry::Catfolk {
|
|
||||||
fg = selected_fg;
|
|
||||||
race_str = "catfolk";
|
|
||||||
} else {
|
|
||||||
fg = unselected_fg;
|
|
||||||
}
|
|
||||||
ctx.print_color(x, y + 3, fg, bg, "c. Catfolk");
|
|
||||||
// Class
|
|
||||||
let mut class_str = "fighter";
|
|
||||||
x += column_width;
|
|
||||||
if class == Class::Fighter {
|
|
||||||
fg = selected_fg;
|
|
||||||
} else {
|
|
||||||
fg = unselected_fg;
|
|
||||||
}
|
|
||||||
ctx.print_color(x, y, fg, bg, "f. Fighter");
|
|
||||||
if class == Class::Rogue {
|
|
||||||
fg = selected_fg;
|
|
||||||
class_str = "rogue";
|
|
||||||
} else {
|
|
||||||
fg = unselected_fg;
|
|
||||||
}
|
|
||||||
ctx.print_color(x, y + 1, fg, bg, "r. Rogue");
|
|
||||||
if class == Class::Wizard {
|
|
||||||
fg = selected_fg;
|
|
||||||
class_str = "wizard";
|
|
||||||
} else {
|
|
||||||
fg = unselected_fg;
|
|
||||||
}
|
|
||||||
ctx.print_color(x, y + 2, fg, bg, "w. Wizard");
|
|
||||||
if class == Class::Villager {
|
|
||||||
fg = selected_fg;
|
|
||||||
class_str = "villager";
|
|
||||||
} else {
|
|
||||||
fg = unselected_fg;
|
|
||||||
}
|
|
||||||
ctx.print_color(x, y + 3, fg, bg, "v. Villager");
|
|
||||||
// Selected ancestry/class benefits
|
|
||||||
x += column_width;
|
|
||||||
ctx.print_color(x, y, selected_fg, bg, ANCESTRY_INFO_HEADER);
|
|
||||||
for line in ANCESTRY_CLASS_DATA.get(race_str).unwrap().iter() {
|
|
||||||
y += 1;
|
|
||||||
ctx.print_color(x + 1, y, unselected_fg, bg, line);
|
|
||||||
}
|
|
||||||
y += 2;
|
|
||||||
ctx.print_color(x, y, selected_fg, bg, CLASS_INFO_HEADER);
|
|
||||||
for line in ANCESTRY_CLASS_DATA.get(class_str).unwrap().iter() {
|
|
||||||
y += 1;
|
|
||||||
ctx.print_color(x + 1, y, unselected_fg, bg, line);
|
|
||||||
}
|
|
||||||
|
|
||||||
match ctx.key {
|
|
||||||
None => {
|
|
||||||
return CharCreateResult::NoSelection { ancestry, class };
|
|
||||||
}
|
|
||||||
Some(key) =>
|
|
||||||
match key {
|
|
||||||
VirtualKeyCode::Escape => {
|
|
||||||
return CharCreateResult::Selected { ancestry: Ancestry::NULL, class };
|
|
||||||
}
|
|
||||||
VirtualKeyCode::Return => {
|
|
||||||
return CharCreateResult::Selected { ancestry, class };
|
|
||||||
}
|
|
||||||
VirtualKeyCode::H => {
|
|
||||||
return CharCreateResult::NoSelection { ancestry: Ancestry::Human, class };
|
|
||||||
}
|
|
||||||
VirtualKeyCode::E => {
|
|
||||||
return CharCreateResult::NoSelection { ancestry: Ancestry::Elf, class };
|
|
||||||
}
|
|
||||||
VirtualKeyCode::D => {
|
|
||||||
return CharCreateResult::NoSelection { ancestry: Ancestry::Dwarf, class };
|
|
||||||
}
|
|
||||||
VirtualKeyCode::C => {
|
|
||||||
return CharCreateResult::NoSelection { ancestry: Ancestry::Catfolk, class };
|
|
||||||
}
|
|
||||||
VirtualKeyCode::F => {
|
|
||||||
return CharCreateResult::NoSelection { ancestry, class: Class::Fighter };
|
|
||||||
}
|
|
||||||
VirtualKeyCode::R => {
|
|
||||||
return CharCreateResult::NoSelection { ancestry, class: Class::Rogue };
|
|
||||||
}
|
|
||||||
VirtualKeyCode::W => {
|
|
||||||
return CharCreateResult::NoSelection { ancestry, class: Class::Wizard };
|
|
||||||
}
|
|
||||||
VirtualKeyCode::V => {
|
|
||||||
return CharCreateResult::NoSelection { ancestry, class: Class::Villager };
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return CharCreateResult::NoSelection { ancestry, class };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return CharCreateResult::NoSelection { ancestry: Ancestry::Human, class: Class::Fighter };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handles player ancestry setup.
|
|
||||||
pub fn setup_player_ancestry(ecs: &mut World, ancestry: Ancestry) {
|
|
||||||
let player = ecs.fetch::<Entity>();
|
|
||||||
let mut renderables = ecs.write_storage::<Renderable>();
|
|
||||||
// SKILLS
|
|
||||||
let mut skills = ecs.write_storage::<Skills>();
|
|
||||||
let player_skills = if let Some(skills) = skills.get_mut(*player) {
|
|
||||||
skills
|
|
||||||
} else {
|
|
||||||
skills
|
|
||||||
.insert(*player, Skills { skills: HashMap::new() })
|
|
||||||
.expect("Unable to insert skills component");
|
|
||||||
skills.get_mut(*player).unwrap()
|
|
||||||
};
|
|
||||||
let mut ancestries = ecs.write_storage::<HasAncestry>();
|
|
||||||
ancestries.insert(*player, HasAncestry { name: ancestry }).expect("Unable to insert ancestry");
|
|
||||||
match ancestry {
|
|
||||||
Ancestry::Human => {}
|
|
||||||
Ancestry::Dwarf => {
|
|
||||||
renderables
|
|
||||||
.insert(*player, Renderable {
|
|
||||||
glyph: to_cp437(DWARF_GLYPH),
|
|
||||||
fg: RGB::named(DWARF_COLOUR),
|
|
||||||
bg: RGB::named(BLACK),
|
|
||||||
render_order: 0,
|
|
||||||
})
|
|
||||||
.expect("Unable to insert renderable component");
|
|
||||||
*player_skills.skills.entry(Skill::Defence).or_insert(0) += DWARF_DEFENCE_MOD;
|
|
||||||
}
|
|
||||||
Ancestry::Elf => {
|
|
||||||
renderables
|
|
||||||
.insert(*player, Renderable {
|
|
||||||
glyph: to_cp437(ELF_GLYPH),
|
|
||||||
fg: RGB::named(ELF_COLOUR),
|
|
||||||
bg: RGB::named(BLACK),
|
|
||||||
render_order: 0,
|
|
||||||
})
|
|
||||||
.expect("Unable to insert renderable component");
|
|
||||||
let mut telepaths = ecs.write_storage::<Telepath>();
|
|
||||||
telepaths
|
|
||||||
.insert(*player, Telepath {
|
|
||||||
telepath_tiles: Vec::new(),
|
|
||||||
range: ELF_TELEPATH_RANGE,
|
|
||||||
dirty: true,
|
|
||||||
})
|
|
||||||
.expect("Unable to insert telepath component");
|
|
||||||
let mut speeds = ecs.write_storage::<Energy>();
|
|
||||||
speeds
|
|
||||||
.insert(*player, Energy {
|
|
||||||
current: 0,
|
|
||||||
speed: entity::NORMAL_SPEED + ELF_SPEED_BONUS,
|
|
||||||
})
|
|
||||||
.expect("Unable to insert energy component");
|
|
||||||
}
|
|
||||||
Ancestry::Catfolk => {
|
|
||||||
renderables
|
|
||||||
.insert(*player, Renderable {
|
|
||||||
glyph: to_cp437(CATFOLK_GLYPH),
|
|
||||||
fg: RGB::named(CATFOLK_COLOUR),
|
|
||||||
bg: RGB::named(BLACK),
|
|
||||||
render_order: 0,
|
|
||||||
})
|
|
||||||
.expect("Unable to insert renderable component");
|
|
||||||
let mut speeds = ecs.write_storage::<Energy>();
|
|
||||||
speeds
|
|
||||||
.insert(*player, Energy {
|
|
||||||
current: 0,
|
|
||||||
speed: entity::NORMAL_SPEED + CATFOLK_SPEED_BONUS,
|
|
||||||
})
|
|
||||||
.expect("Unable to insert energy component");
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handles player class setup
|
|
||||||
pub fn setup_player_class(ecs: &mut World, class: Class, ancestry: Ancestry) {
|
|
||||||
let player = *ecs.fetch::<Entity>();
|
|
||||||
// ATTRIBUTES
|
|
||||||
{
|
|
||||||
let mut classes = ecs.write_storage::<HasClass>();
|
|
||||||
classes.insert(player, HasClass { name: class }).expect("Unable to insert class component");
|
|
||||||
if class == Class::Wizard {
|
|
||||||
let mut spells = ecs.write_storage::<KnownSpells>();
|
|
||||||
spells
|
|
||||||
.insert(player, KnownSpells {
|
|
||||||
list: vec![KnownSpell { display_name: "zap".to_string(), mana_cost: 1 }],
|
|
||||||
})
|
|
||||||
.expect("Unable to insert known spells component");
|
|
||||||
}
|
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
|
||||||
let mut attributes = ecs.write_storage::<Attributes>();
|
|
||||||
let (str, dex, con, int, wis, cha) = get_attribute_rolls(&mut rng, class, ancestry);
|
|
||||||
attributes
|
|
||||||
.insert(player, Attributes {
|
|
||||||
strength: Attribute { base: str, modifiers: 0, bonus: attr_bonus(str) },
|
|
||||||
dexterity: Attribute { base: dex, modifiers: 0, bonus: attr_bonus(dex) },
|
|
||||||
constitution: Attribute { base: con, modifiers: 0, bonus: attr_bonus(con) },
|
|
||||||
intelligence: Attribute { base: int, modifiers: 0, bonus: attr_bonus(int) },
|
|
||||||
wisdom: Attribute { base: wis, modifiers: 0, bonus: attr_bonus(wis) },
|
|
||||||
charisma: Attribute { base: cha, modifiers: 0, bonus: attr_bonus(cha) },
|
|
||||||
})
|
|
||||||
.expect("Unable to insert attributes component");
|
|
||||||
|
|
||||||
let mut pools = ecs.write_storage::<Pools>();
|
|
||||||
let starting_mp = mana_at_level(&mut rng, int, 1);
|
|
||||||
pools
|
|
||||||
.insert(player, Pools {
|
|
||||||
hit_points: Pool {
|
|
||||||
current: 8 + attr_bonus(con),
|
|
||||||
max: entity::STANDARD_HIT_DIE + attr_bonus(con),
|
|
||||||
},
|
|
||||||
mana: Pool {
|
|
||||||
current: starting_mp,
|
|
||||||
max: starting_mp,
|
|
||||||
},
|
|
||||||
xp: 0,
|
|
||||||
level: 1,
|
|
||||||
bac: entity::STANDARD_BAC,
|
|
||||||
weight: 0.0,
|
|
||||||
god: false,
|
|
||||||
})
|
|
||||||
.expect("Unable to insert pools component");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: use seeded RNG here
|
|
||||||
let mut rng = RandomNumberGenerator::new();
|
|
||||||
let starts_with = get_starting_inventory(class, &mut rng);
|
|
||||||
for item in starts_with.0.iter() {
|
|
||||||
let buc = if rng.roll_dice(1, 3) == 1 { Some(BUC::Blessed) } else { Some(BUC::Uncursed) };
|
|
||||||
raws::spawn_named_entity(
|
|
||||||
&raws::RAWS.lock().unwrap(),
|
|
||||||
ecs,
|
|
||||||
item,
|
|
||||||
buc,
|
|
||||||
raws::SpawnType::Equipped { by: player },
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for item in starts_with.1.iter() {
|
|
||||||
raws::spawn_named_entity(
|
|
||||||
&raws::RAWS.lock().unwrap(),
|
|
||||||
ecs,
|
|
||||||
item,
|
|
||||||
None,
|
|
||||||
raws::SpawnType::Carried { by: player },
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_starting_inventory(
|
|
||||||
class: Class,
|
|
||||||
rng: &mut RandomNumberGenerator
|
|
||||||
) -> (Vec<String>, Vec<String>) {
|
|
||||||
let mut equipped: Vec<String> = Vec::new();
|
|
||||||
let mut carried: Vec<String> = Vec::new();
|
|
||||||
let starting_food: &str;
|
|
||||||
match class {
|
|
||||||
Class::Fighter => {
|
|
||||||
starting_food = FIGHTER_STARTING_FOOD;
|
|
||||||
equipped = vec![
|
|
||||||
FIGHTER_STARTING_WEAPON.to_string(),
|
|
||||||
FIGHTER_STARTING_ARMOUR.to_string(),
|
|
||||||
FIGHTER_STARTING_SHIELD.to_string()
|
|
||||||
];
|
|
||||||
}
|
|
||||||
Class::Rogue => {
|
|
||||||
starting_food = ROGUE_STARTING_FOOD;
|
|
||||||
equipped = vec![ROGUE_STARTING_WEAPON.to_string(), ROGUE_STARTING_ARMOUR.to_string()];
|
|
||||||
carried = vec!["equip_dagger".to_string(), "equip_dagger".to_string()];
|
|
||||||
}
|
|
||||||
Class::Wizard => {
|
|
||||||
starting_food = WIZARD_STARTING_FOOD;
|
|
||||||
equipped = vec![WIZARD_STARTING_WEAPON.to_string(), WIZARD_STARTING_ARMOUR.to_string()];
|
|
||||||
pick_random_table_item(
|
|
||||||
rng,
|
|
||||||
&mut carried,
|
|
||||||
"scrolls",
|
|
||||||
WIZARD_SCROLL_AMOUNT,
|
|
||||||
Some(WIZARD_MAX_SCROLL_LVL)
|
|
||||||
);
|
|
||||||
pick_random_table_item(
|
|
||||||
rng,
|
|
||||||
&mut carried,
|
|
||||||
"potions",
|
|
||||||
WIZARD_POTION_AMOUNT,
|
|
||||||
Some(WIZARD_MAX_SCROLL_LVL)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Class::Villager => {
|
|
||||||
starting_food = VILLAGER_STARTING_FOOD;
|
|
||||||
pick_random_table_item(rng, &mut equipped, "villager_equipment", "1", None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pick_random_table_item(rng, &mut carried, "food", starting_food, None);
|
|
||||||
return (equipped, carried);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pick_random_table_item(
|
|
||||||
rng: &mut RandomNumberGenerator,
|
|
||||||
push_to: &mut Vec<String>,
|
|
||||||
table: &'static str,
|
|
||||||
dice_str: &'static str,
|
|
||||||
difficulty: Option<i32>
|
|
||||||
) {
|
|
||||||
let dice = parse_dice_string(dice_str).expect("Error parsing dice");
|
|
||||||
for _i in 0..rng.roll_dice(dice.n_dice, dice.die_type) + dice.bonus {
|
|
||||||
push_to.push(raws::table_by_name(&raws::RAWS.lock().unwrap(), table, difficulty).roll(rng));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
use super::State;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Copy, Clone)]
|
|
||||||
pub enum CheatMenuResult {
|
|
||||||
NoResponse,
|
|
||||||
Cancel,
|
|
||||||
Ascend,
|
|
||||||
Descend,
|
|
||||||
Heal,
|
|
||||||
MagicMap,
|
|
||||||
GodMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show_cheat_menu(_gs: &mut State, ctx: &mut BTerm) -> CheatMenuResult {
|
|
||||||
let (x_offset, y_offset) = (1, 10);
|
|
||||||
ctx.print_color(
|
|
||||||
1 + x_offset,
|
|
||||||
1 + y_offset,
|
|
||||||
RGB::named(RED),
|
|
||||||
RGB::named(BLACK),
|
|
||||||
"DEBUG MENU! [aA-zZ][Esc.]"
|
|
||||||
);
|
|
||||||
let x = 1 + x_offset;
|
|
||||||
let mut y = 3 + y_offset;
|
|
||||||
let count = 5;
|
|
||||||
let width = 19;
|
|
||||||
|
|
||||||
ctx.draw_box(x, y, width, (count + 1) as i32, RGB::named(RED), RGB::named(BLACK));
|
|
||||||
y += 1;
|
|
||||||
// Asc
|
|
||||||
ctx.set(x_offset + 2, y, RGB::named(YELLOW), RGB::named(BLACK), to_cp437('a'));
|
|
||||||
ctx.print(x_offset + 4, y, "ASCEND A FLOOR");
|
|
||||||
y += 1;
|
|
||||||
// Desc
|
|
||||||
ctx.set(x_offset + 2, y, RGB::named(YELLOW), RGB::named(BLACK), to_cp437('d'));
|
|
||||||
ctx.print(x_offset + 4, y, "DESCEND A FLOOR");
|
|
||||||
y += 1;
|
|
||||||
// Heal
|
|
||||||
ctx.set(x_offset + 2, y, RGB::named(YELLOW), RGB::named(BLACK), to_cp437('h'));
|
|
||||||
ctx.print(x_offset + 4, y, "HEAL TO FULL");
|
|
||||||
y += 1;
|
|
||||||
// Reveal map
|
|
||||||
ctx.set(x_offset + 2, y, RGB::named(YELLOW), RGB::named(BLACK), to_cp437('m'));
|
|
||||||
ctx.print(x_offset + 4, y, "MAGIC MAP REVEAL");
|
|
||||||
y += 1;
|
|
||||||
// Godmode
|
|
||||||
ctx.set(x_offset + 2, y, RGB::named(YELLOW), RGB::named(BLACK), to_cp437('g'));
|
|
||||||
ctx.print(x_offset + 4, y, "GOD MODE");
|
|
||||||
// Match keys
|
|
||||||
match ctx.key {
|
|
||||||
None => CheatMenuResult::NoResponse,
|
|
||||||
Some(key) =>
|
|
||||||
match key {
|
|
||||||
VirtualKeyCode::A => CheatMenuResult::Ascend,
|
|
||||||
VirtualKeyCode::D => CheatMenuResult::Descend,
|
|
||||||
VirtualKeyCode::H => CheatMenuResult::Heal,
|
|
||||||
VirtualKeyCode::M => CheatMenuResult::MagicMap,
|
|
||||||
VirtualKeyCode::G => CheatMenuResult::GodMode,
|
|
||||||
VirtualKeyCode::Escape => CheatMenuResult::Cancel,
|
|
||||||
_ => CheatMenuResult::NoResponse,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
use super::{ State, RunState, tooltip::draw_tooltips, camera::get_screen_bounds };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Copy, Clone)]
|
|
||||||
pub enum FarlookResult {
|
|
||||||
NoResponse {
|
|
||||||
x: i32,
|
|
||||||
y: i32,
|
|
||||||
},
|
|
||||||
Cancel,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show_farlook(gs: &mut State, ctx: &mut BTerm) -> FarlookResult {
|
|
||||||
let runstate = gs.ecs.fetch::<RunState>();
|
|
||||||
let (_min_x, _max_x, _min_y, _max_y, x_offset, y_offset) = get_screen_bounds(&gs.ecs, ctx);
|
|
||||||
|
|
||||||
ctx.print_color(
|
|
||||||
1 + x_offset,
|
|
||||||
1 + y_offset,
|
|
||||||
RGB::named(WHITE),
|
|
||||||
RGB::named(BLACK),
|
|
||||||
"Look at what? [move keys][Esc.]"
|
|
||||||
);
|
|
||||||
|
|
||||||
if let RunState::Farlook { x, y } = *runstate {
|
|
||||||
let (screen_x, screen_y) = (69, 41);
|
|
||||||
let x = x.clamp(x_offset, x_offset - 1 + (screen_x as i32));
|
|
||||||
let y = y.clamp(y_offset, y_offset - 1 + (screen_y as i32));
|
|
||||||
|
|
||||||
ctx.set(x, y, RGB::named(WHITE), RGB::named(BLACK), to_cp437('X'));
|
|
||||||
draw_tooltips(&gs.ecs, ctx, Some((x, y)));
|
|
||||||
|
|
||||||
return match ctx.key {
|
|
||||||
None => FarlookResult::NoResponse { x, y },
|
|
||||||
Some(key) =>
|
|
||||||
match key {
|
|
||||||
VirtualKeyCode::Escape | VirtualKeyCode::X => FarlookResult::Cancel,
|
|
||||||
VirtualKeyCode::Numpad9 => FarlookResult::NoResponse { x: x + 1, y: y - 1 },
|
|
||||||
VirtualKeyCode::Numpad8 => FarlookResult::NoResponse { x, y: y - 1 },
|
|
||||||
VirtualKeyCode::Numpad7 => FarlookResult::NoResponse { x: x - 1, y: y - 1 },
|
|
||||||
VirtualKeyCode::Numpad6 => FarlookResult::NoResponse { x: x + 1, y },
|
|
||||||
VirtualKeyCode::Numpad4 => FarlookResult::NoResponse { x: x - 1, y },
|
|
||||||
VirtualKeyCode::Numpad3 => FarlookResult::NoResponse { x: x + 1, y: y + 1 },
|
|
||||||
VirtualKeyCode::Numpad2 => FarlookResult::NoResponse { x, y: y + 1 },
|
|
||||||
VirtualKeyCode::Numpad1 => FarlookResult::NoResponse { x: x - 1, y: y + 1 },
|
|
||||||
_ => FarlookResult::NoResponse { x, y },
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
let ppos = gs.ecs.fetch::<Point>();
|
|
||||||
// TODO: PPOS + offsets (should get these from screen_bounds())
|
|
||||||
return FarlookResult::NoResponse { x: ppos.x + x_offset, y: ppos.x + y_offset };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
||||||
use super::{
|
|
||||||
get_max_inventory_width,
|
|
||||||
item_colour_ecs,
|
|
||||||
obfuscate_name_ecs,
|
|
||||||
print_options,
|
|
||||||
unique_ecs,
|
|
||||||
check_key,
|
|
||||||
letter_to_option,
|
|
||||||
ItemMenuResult,
|
|
||||||
};
|
|
||||||
use crate::{
|
|
||||||
gamelog,
|
|
||||||
Beatitude,
|
|
||||||
Entity,
|
|
||||||
Equipped,
|
|
||||||
InBackpack,
|
|
||||||
Item,
|
|
||||||
MasterDungeonMap,
|
|
||||||
Name,
|
|
||||||
ObfuscatedName,
|
|
||||||
Renderable,
|
|
||||||
Key,
|
|
||||||
states::state::*,
|
|
||||||
};
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
/// Handles the Identify menu.
|
|
||||||
pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Entity>) {
|
|
||||||
let player_entity = gs.ecs.fetch::<Entity>();
|
|
||||||
let equipped = gs.ecs.read_storage::<Equipped>();
|
|
||||||
let backpack = gs.ecs.read_storage::<InBackpack>();
|
|
||||||
let entities = gs.ecs.entities();
|
|
||||||
let items = gs.ecs.read_storage::<Item>();
|
|
||||||
let obfuscated = gs.ecs.read_storage::<ObfuscatedName>();
|
|
||||||
let dm = gs.ecs.fetch::<MasterDungeonMap>();
|
|
||||||
let names = gs.ecs.read_storage::<Name>();
|
|
||||||
let renderables = gs.ecs.read_storage::<Renderable>();
|
|
||||||
let beatitudes = gs.ecs.read_storage::<Beatitude>();
|
|
||||||
let keys = gs.ecs.read_storage::<Key>();
|
|
||||||
|
|
||||||
let build_identify_iterator = || {
|
|
||||||
(&entities, &items, &renderables, &names, &keys)
|
|
||||||
.join()
|
|
||||||
.filter(|(item_entity, _i, _r, n, _k)| {
|
|
||||||
// If not owned by the player, return false.
|
|
||||||
let mut keep = false;
|
|
||||||
if let Some(bp) = backpack.get(*item_entity) {
|
|
||||||
if bp.owner == *player_entity {
|
|
||||||
keep = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If not equipped by the player, return false.
|
|
||||||
if let Some(equip) = equipped.get(*item_entity) {
|
|
||||||
if equip.owner == *player_entity {
|
|
||||||
keep = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !keep {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// If not obfuscated, or already identified, return false.
|
|
||||||
if
|
|
||||||
(!obfuscated.get(*item_entity).is_some() ||
|
|
||||||
dm.identified_items.contains(&n.name)) &&
|
|
||||||
beatitudes
|
|
||||||
.get(*item_entity)
|
|
||||||
.map(|beatitude| beatitude.known)
|
|
||||||
.unwrap_or(true)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build list of items to display
|
|
||||||
let count = build_identify_iterator().count();
|
|
||||||
// If no items, return nothing, wasting the scroll.
|
|
||||||
if count == 0 {
|
|
||||||
gamelog::Logger::new().append("You've got nothing to identify! Know-it-all.").log();
|
|
||||||
return (ItemMenuResult::Cancel, None);
|
|
||||||
}
|
|
||||||
// If only one item, return it.
|
|
||||||
if count == 1 {
|
|
||||||
let item = build_identify_iterator().nth(0).unwrap().0;
|
|
||||||
gamelog::Logger
|
|
||||||
::new()
|
|
||||||
.append("You identify the")
|
|
||||||
.colour(item_colour_ecs(&gs.ecs, item))
|
|
||||||
.append_n(obfuscate_name_ecs(&gs.ecs, item).0)
|
|
||||||
.colour(WHITE)
|
|
||||||
.append("!")
|
|
||||||
.log();
|
|
||||||
return (ItemMenuResult::Selected, Some(build_identify_iterator().nth(0).unwrap().0));
|
|
||||||
}
|
|
||||||
let mut player_inventory: super::PlayerInventory = HashMap::new();
|
|
||||||
for (entity, _i, _r, _n, key) in build_identify_iterator() {
|
|
||||||
let unique_item = unique_ecs(&gs.ecs, entity);
|
|
||||||
player_inventory
|
|
||||||
.entry(unique_item)
|
|
||||||
.and_modify(|slot| {
|
|
||||||
slot.count += 1;
|
|
||||||
})
|
|
||||||
.or_insert(super::InventorySlot { item: entity, count: 1, idx: key.idx });
|
|
||||||
}
|
|
||||||
// Get display args
|
|
||||||
let width = get_max_inventory_width(&player_inventory);
|
|
||||||
let (_, _, _, _, x_offset, y_offset) = crate::camera::get_screen_bounds(&gs.ecs, ctx);
|
|
||||||
let (x, y) = (x_offset + 1, y_offset + 3);
|
|
||||||
// Draw menu
|
|
||||||
ctx.print_color(
|
|
||||||
1 + x_offset,
|
|
||||||
1 + y_offset,
|
|
||||||
RGB::named(WHITE),
|
|
||||||
RGB::named(BLACK),
|
|
||||||
"Identify which item? [aA-zZ][Esc.]"
|
|
||||||
);
|
|
||||||
ctx.draw_box(x, y, width + 2, count + 1, RGB::named(WHITE), RGB::named(BLACK));
|
|
||||||
print_options(&gs.ecs, &player_inventory, x + 1, y + 1, ctx);
|
|
||||||
// Input
|
|
||||||
match ctx.key {
|
|
||||||
None => (ItemMenuResult::NoResponse, None),
|
|
||||||
Some(key) =>
|
|
||||||
match key {
|
|
||||||
VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None),
|
|
||||||
_ => {
|
|
||||||
let selection = letter_to_option::letter_to_option(key, ctx.shift);
|
|
||||||
if selection != -1 && check_key(selection as usize) {
|
|
||||||
// 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 backpack = gs.ecs.read_storage::<InBackpack>();
|
|
||||||
for (e, key, _b) in (&entities, &keyed_items, &backpack).join() {
|
|
||||||
if key.idx == (selection as usize) {
|
|
||||||
return (ItemMenuResult::Selected, Some(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(ItemMenuResult::NoResponse, None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
pub fn letter_to_option(key: VirtualKeyCode, shift: bool) -> i32 {
|
|
||||||
if shift {
|
|
||||||
match key {
|
|
||||||
VirtualKeyCode::A => 26,
|
|
||||||
VirtualKeyCode::B => 27,
|
|
||||||
VirtualKeyCode::C => 28,
|
|
||||||
VirtualKeyCode::D => 29,
|
|
||||||
VirtualKeyCode::E => 30,
|
|
||||||
VirtualKeyCode::F => 31,
|
|
||||||
VirtualKeyCode::G => 32,
|
|
||||||
VirtualKeyCode::H => 33,
|
|
||||||
VirtualKeyCode::I => 34,
|
|
||||||
VirtualKeyCode::J => 35,
|
|
||||||
VirtualKeyCode::K => 36,
|
|
||||||
VirtualKeyCode::L => 37,
|
|
||||||
VirtualKeyCode::M => 38,
|
|
||||||
VirtualKeyCode::N => 39,
|
|
||||||
VirtualKeyCode::O => 40,
|
|
||||||
VirtualKeyCode::P => 41,
|
|
||||||
VirtualKeyCode::Q => 42,
|
|
||||||
VirtualKeyCode::R => 43,
|
|
||||||
VirtualKeyCode::S => 44,
|
|
||||||
VirtualKeyCode::T => 45,
|
|
||||||
VirtualKeyCode::U => 46,
|
|
||||||
VirtualKeyCode::V => 47,
|
|
||||||
VirtualKeyCode::W => 48,
|
|
||||||
VirtualKeyCode::X => 49,
|
|
||||||
VirtualKeyCode::Y => 50,
|
|
||||||
VirtualKeyCode::Z => 51,
|
|
||||||
_ => -1,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match key {
|
|
||||||
VirtualKeyCode::A => 0,
|
|
||||||
VirtualKeyCode::B => 1,
|
|
||||||
VirtualKeyCode::C => 2,
|
|
||||||
VirtualKeyCode::D => 3,
|
|
||||||
VirtualKeyCode::E => 4,
|
|
||||||
VirtualKeyCode::F => 5,
|
|
||||||
VirtualKeyCode::G => 6,
|
|
||||||
VirtualKeyCode::H => 7,
|
|
||||||
VirtualKeyCode::I => 8,
|
|
||||||
VirtualKeyCode::J => 9,
|
|
||||||
VirtualKeyCode::K => 10,
|
|
||||||
VirtualKeyCode::L => 11,
|
|
||||||
VirtualKeyCode::M => 12,
|
|
||||||
VirtualKeyCode::N => 13,
|
|
||||||
VirtualKeyCode::O => 14,
|
|
||||||
VirtualKeyCode::P => 15,
|
|
||||||
VirtualKeyCode::Q => 16,
|
|
||||||
VirtualKeyCode::R => 17,
|
|
||||||
VirtualKeyCode::S => 18,
|
|
||||||
VirtualKeyCode::T => 19,
|
|
||||||
VirtualKeyCode::U => 20,
|
|
||||||
VirtualKeyCode::V => 21,
|
|
||||||
VirtualKeyCode::W => 22,
|
|
||||||
VirtualKeyCode::X => 23,
|
|
||||||
VirtualKeyCode::Y => 24,
|
|
||||||
VirtualKeyCode::Z => 25,
|
|
||||||
_ => -1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1512
src/gui/mod.rs
1512
src/gui/mod.rs
File diff suppressed because it is too large
Load diff
|
|
@ -1,144 +0,0 @@
|
||||||
use super::{
|
|
||||||
get_max_inventory_width,
|
|
||||||
item_colour_ecs,
|
|
||||||
obfuscate_name_ecs,
|
|
||||||
print_options,
|
|
||||||
unique_ecs,
|
|
||||||
check_key,
|
|
||||||
letter_to_option,
|
|
||||||
ItemMenuResult,
|
|
||||||
InventorySlot,
|
|
||||||
};
|
|
||||||
use crate::{
|
|
||||||
gamelog,
|
|
||||||
Beatitude,
|
|
||||||
Entity,
|
|
||||||
Equipped,
|
|
||||||
InBackpack,
|
|
||||||
Item,
|
|
||||||
Name,
|
|
||||||
Renderable,
|
|
||||||
states::state::*,
|
|
||||||
BUC,
|
|
||||||
Key,
|
|
||||||
};
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
/// Handles the Remove Curse menu.
|
|
||||||
pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Entity>) {
|
|
||||||
let player_entity = gs.ecs.fetch::<Entity>();
|
|
||||||
let equipped = gs.ecs.read_storage::<Equipped>();
|
|
||||||
let backpack = gs.ecs.read_storage::<InBackpack>();
|
|
||||||
let entities = gs.ecs.entities();
|
|
||||||
let items = gs.ecs.read_storage::<Item>();
|
|
||||||
let beatitudes = gs.ecs.read_storage::<Beatitude>();
|
|
||||||
let names = gs.ecs.read_storage::<Name>();
|
|
||||||
let renderables = gs.ecs.read_storage::<Renderable>();
|
|
||||||
let keys = gs.ecs.read_storage::<Key>();
|
|
||||||
|
|
||||||
let build_cursed_iterator = || {
|
|
||||||
(&entities, &items, &beatitudes, &renderables, &names, &keys)
|
|
||||||
.join()
|
|
||||||
.filter(|(item_entity, _i, b, _r, _n, _k)| {
|
|
||||||
// Set all items to FALSE initially.
|
|
||||||
let mut keep = false;
|
|
||||||
// If found in the player's backpack, set to TRUE
|
|
||||||
if let Some(bp) = backpack.get(*item_entity) {
|
|
||||||
if bp.owner == *player_entity {
|
|
||||||
keep = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If found in the player's equipslot, set to TRUE
|
|
||||||
if let Some(equip) = equipped.get(*item_entity) {
|
|
||||||
if equip.owner == *player_entity {
|
|
||||||
keep = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If it's not OUR item, RETURN FALSE.
|
|
||||||
if !keep {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// If it's identified as noncursed, RETURN FALSE.
|
|
||||||
if b.known && b.buc != BUC::Cursed {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Otherwise, return: returns any items that are unidentified,
|
|
||||||
// or identified as being cursed.
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build list of items to display
|
|
||||||
let count = build_cursed_iterator().count();
|
|
||||||
// If no items, return nothing, wasting the scroll.
|
|
||||||
if count == 0 {
|
|
||||||
gamelog::Logger::new().append("You've got nothing to decurse! What a waste.").log();
|
|
||||||
return (ItemMenuResult::Cancel, None);
|
|
||||||
}
|
|
||||||
// If only one item, return it.
|
|
||||||
if count == 1 {
|
|
||||||
let item = build_cursed_iterator().nth(0).unwrap().0;
|
|
||||||
gamelog::Logger
|
|
||||||
::new()
|
|
||||||
.append("You decurse the")
|
|
||||||
.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));
|
|
||||||
}
|
|
||||||
let mut player_inventory: super::PlayerInventory = HashMap::new();
|
|
||||||
for (entity, _i, _b, _r, _n, key) in build_cursed_iterator() {
|
|
||||||
let unique_item = unique_ecs(&gs.ecs, entity);
|
|
||||||
player_inventory
|
|
||||||
.entry(unique_item)
|
|
||||||
.and_modify(|slot| {
|
|
||||||
slot.count += 1;
|
|
||||||
})
|
|
||||||
.or_insert(InventorySlot {
|
|
||||||
item: entity,
|
|
||||||
count: 1,
|
|
||||||
idx: key.idx,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Get display args
|
|
||||||
let width = get_max_inventory_width(&player_inventory);
|
|
||||||
let (_, _, _, _, x_offset, y_offset) = crate::camera::get_screen_bounds(&gs.ecs, ctx);
|
|
||||||
let (x, y) = (x_offset + 1, y_offset + 3);
|
|
||||||
// Draw menu
|
|
||||||
ctx.print_color(
|
|
||||||
1 + x_offset,
|
|
||||||
1 + y_offset,
|
|
||||||
RGB::named(WHITE),
|
|
||||||
RGB::named(BLACK),
|
|
||||||
"Decurse which item? [aA-zZ][Esc.]"
|
|
||||||
);
|
|
||||||
ctx.draw_box(x, y, width + 2, count + 1, RGB::named(WHITE), RGB::named(BLACK));
|
|
||||||
print_options(&gs.ecs, &player_inventory, x + 1, y + 1, ctx);
|
|
||||||
// Input
|
|
||||||
match ctx.key {
|
|
||||||
None => (ItemMenuResult::NoResponse, None),
|
|
||||||
Some(key) =>
|
|
||||||
match key {
|
|
||||||
VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None),
|
|
||||||
_ => {
|
|
||||||
let selection = letter_to_option::letter_to_option(key, ctx.shift);
|
|
||||||
if selection != -1 && check_key(selection as usize) {
|
|
||||||
// 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 backpack = gs.ecs.read_storage::<InBackpack>();
|
|
||||||
for (e, key, _b) in (&entities, &keyed_items, &backpack).join() {
|
|
||||||
if key.idx == (selection as usize) {
|
|
||||||
return (ItemMenuResult::Selected, Some(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(ItemMenuResult::NoResponse, None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,205 +0,0 @@
|
||||||
use super::{
|
|
||||||
camera::get_screen_bounds,
|
|
||||||
Attributes,
|
|
||||||
Hidden,
|
|
||||||
Map,
|
|
||||||
Name,
|
|
||||||
Pools,
|
|
||||||
Position,
|
|
||||||
Renderable,
|
|
||||||
World,
|
|
||||||
RGB,
|
|
||||||
};
|
|
||||||
use crate::TileType;
|
|
||||||
use crate::data::ids::*;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
|
|
||||||
struct Tooltip {
|
|
||||||
lines: Vec<(String, RGB)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
const ATTRIBUTE_COLOUR: RGB = RGB { r: 1.0, g: 0.75, b: 0.8 };
|
|
||||||
const RED_WARNING: RGB = RGB { r: 1.0, g: 0.0, b: 0.0 };
|
|
||||||
const ORANGE_WARNING: RGB = RGB { r: 1.0, g: 0.65, b: 0.0 };
|
|
||||||
const YELLOW_WARNING: RGB = RGB { r: 1.0, g: 1.0, b: 0.0 };
|
|
||||||
const GREEN_WARNING: RGB = RGB { r: 0.0, g: 1.0, b: 0.0 };
|
|
||||||
|
|
||||||
impl Tooltip {
|
|
||||||
fn new() -> Tooltip {
|
|
||||||
return Tooltip { lines: Vec::new() };
|
|
||||||
}
|
|
||||||
fn add<S: ToString>(&mut self, line: S, fg: RGB) {
|
|
||||||
self.lines.push((line.to_string(), fg));
|
|
||||||
}
|
|
||||||
fn width(&self) -> i32 {
|
|
||||||
let mut max = 0;
|
|
||||||
for s in self.lines.iter() {
|
|
||||||
if s.0.len() > max {
|
|
||||||
max = s.0.len();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (max as i32) + 2i32;
|
|
||||||
}
|
|
||||||
fn height(&self) -> i32 {
|
|
||||||
return (self.lines.len() as i32) + 2i32;
|
|
||||||
}
|
|
||||||
fn render(&self, ctx: &mut BTerm, x: i32, y: i32) {
|
|
||||||
ctx.draw_box(
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
self.width() - 1,
|
|
||||||
self.height() - 1,
|
|
||||||
RGB::named(WHITE),
|
|
||||||
RGB::named(BLACK)
|
|
||||||
);
|
|
||||||
for (i, s) in self.lines.iter().enumerate() {
|
|
||||||
ctx.print_color(x + 1, y + (i as i32) + 1, s.1, RGB::named(BLACK), &s.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
pub fn draw_tooltips(ecs: &World, ctx: &mut BTerm, xy: Option<(i32, i32)>) {
|
|
||||||
let (min_x, _max_x, min_y, _max_y, x_offset, y_offset) = get_screen_bounds(ecs, ctx);
|
|
||||||
let map = ecs.fetch::<Map>();
|
|
||||||
let names = ecs.read_storage::<Name>();
|
|
||||||
let positions = ecs.read_storage::<Position>();
|
|
||||||
let renderables = ecs.read_storage::<Renderable>();
|
|
||||||
let hidden = ecs.read_storage::<Hidden>();
|
|
||||||
let attributes = ecs.read_storage::<Attributes>();
|
|
||||||
let pools = ecs.read_storage::<Pools>();
|
|
||||||
let entities = ecs.entities();
|
|
||||||
let player_entity = ecs.fetch::<Entity>();
|
|
||||||
|
|
||||||
let mouse_pos = if xy.is_none() { ctx.mouse_pos() } else { xy.unwrap() };
|
|
||||||
let mut mouse_pos_adjusted = mouse_pos;
|
|
||||||
mouse_pos_adjusted.0 += min_x - x_offset;
|
|
||||||
mouse_pos_adjusted.1 += min_y - y_offset;
|
|
||||||
if mouse_pos_adjusted.0 >= map.width
|
|
||||||
|| mouse_pos_adjusted.1 >= map.height
|
|
||||||
|| mouse_pos_adjusted.1 < 0 // Might need to be 1, and -1 from map height/width.
|
|
||||||
|| mouse_pos_adjusted.0 < 0
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if !(map.visible_tiles[map.xy_idx(mouse_pos_adjusted.0, mouse_pos_adjusted.1)]
|
|
||||||
|| map.telepath_tiles[map.xy_idx(mouse_pos_adjusted.0, mouse_pos_adjusted.1)])
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut tooltips: Vec<Tooltip> = Vec::new();
|
|
||||||
|
|
||||||
match map.tiles[map.xy_idx(mouse_pos_adjusted.0, mouse_pos_adjusted.1)] {
|
|
||||||
TileType::ToLocal(n) => {
|
|
||||||
let name = get_local_desc(n);
|
|
||||||
let mut tip = Tooltip::new();
|
|
||||||
tip.add(format!("You see {}.", name), get_local_col(n));
|
|
||||||
tooltips.push(tip);
|
|
||||||
}
|
|
||||||
TileType::ToOvermap(n) => {
|
|
||||||
let name = get_local_desc(n);
|
|
||||||
let mut tip = Tooltip::new();
|
|
||||||
tip.add(format!("You see an exit from {}.", name), get_local_col(n));
|
|
||||||
tooltips.push(tip);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (entity, position, renderable, _name, _hidden) in (&entities, &positions, &renderables, &names, !&hidden).join() {
|
|
||||||
if position.x == mouse_pos_adjusted.0 && position.y == mouse_pos_adjusted.1 {
|
|
||||||
let mut tip = Tooltip::new();
|
|
||||||
tip.add(crate::gui::obfuscate_name_ecs(ecs, entity).0, renderable.fg);
|
|
||||||
let intrinsics = ecs.read_storage::<crate::components::Intrinsics>();
|
|
||||||
if let Some(intrinsics) = intrinsics.get(entity) {
|
|
||||||
if !intrinsics.list.is_empty() {
|
|
||||||
tip.add(intrinsics.describe(), RGB::named(WHITE));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Attributes
|
|
||||||
let attr = attributes.get(entity);
|
|
||||||
if let Some(a) = attr {
|
|
||||||
let mut s = "".to_string();
|
|
||||||
if a.strength.bonus < -2 { s += "weak "};
|
|
||||||
if a.strength.bonus > 2 { s += "strong "};
|
|
||||||
if a.dexterity.bonus < -2 { s += "clumsy "};
|
|
||||||
if a.dexterity.bonus > 2 { s += "agile "};
|
|
||||||
if a.constitution.bonus < -2 { s += "frail "};
|
|
||||||
if a.constitution.bonus > 2 { s += "hardy "};
|
|
||||||
if a.intelligence.bonus < -2 { s += "dim "};
|
|
||||||
if a.intelligence.bonus > 2 { s += "smart "};
|
|
||||||
if a.wisdom.bonus < -2 { s += "unwise "};
|
|
||||||
if a.wisdom.bonus > 2 { s += "wisened "};
|
|
||||||
if a.charisma.bonus < -2 { s += "ugly"};
|
|
||||||
if a.charisma.bonus > 2 { s += "attractive"};
|
|
||||||
if !s.is_empty() {
|
|
||||||
if s.ends_with(" ") {
|
|
||||||
s.pop();
|
|
||||||
}
|
|
||||||
tip.add(s, ATTRIBUTE_COLOUR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Pools
|
|
||||||
let pool = pools.get(entity);
|
|
||||||
let player_pool = pools.get(*player_entity).unwrap();
|
|
||||||
if let Some(p) = pool {
|
|
||||||
let level_diff: i32 = p.level - player_pool.level;
|
|
||||||
if level_diff <= -2 {
|
|
||||||
tip.add("-weak-", YELLOW_WARNING);
|
|
||||||
} else if level_diff >= 2 {
|
|
||||||
tip.add("*threatening*", ORANGE_WARNING);
|
|
||||||
}
|
|
||||||
let health_percent: f32 = p.hit_points.current as f32 / p.hit_points.max as f32;
|
|
||||||
if health_percent == 1.0 {
|
|
||||||
tip.add("healthy", GREEN_WARNING);
|
|
||||||
} else if health_percent <= 0.25 {
|
|
||||||
tip.add("*critical*", RED_WARNING);
|
|
||||||
} else if health_percent <= 0.5 {
|
|
||||||
tip.add("-bloodied-", ORANGE_WARNING);
|
|
||||||
} else if health_percent <= 0.75 {
|
|
||||||
tip.add("injured", YELLOW_WARNING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tooltips.push(tip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tooltips.is_empty() { return ; }
|
|
||||||
|
|
||||||
let white = RGB::named(WHITE);
|
|
||||||
|
|
||||||
let arrow;
|
|
||||||
let arrow_x;
|
|
||||||
let arrow_y = mouse_pos.1;
|
|
||||||
if mouse_pos.0 > 35 {
|
|
||||||
// Render to the left
|
|
||||||
arrow = to_cp437('→');
|
|
||||||
arrow_x = mouse_pos.0 - 1;
|
|
||||||
} else {
|
|
||||||
// Render to the right
|
|
||||||
arrow = to_cp437('←');
|
|
||||||
arrow_x = mouse_pos.0 + 1;
|
|
||||||
}
|
|
||||||
ctx.set(arrow_x, arrow_y, white, RGB::named(BLACK), arrow);
|
|
||||||
|
|
||||||
let mut total_height = 0;
|
|
||||||
for t in tooltips.iter() {
|
|
||||||
total_height += t.height();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut y = mouse_pos.1 - (total_height / 2);
|
|
||||||
while y + (total_height / 2) > 50 {
|
|
||||||
y -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for t in tooltips.iter() {
|
|
||||||
let x = if mouse_pos.0 > 35 {
|
|
||||||
mouse_pos.0 - (1 + t.width())
|
|
||||||
} else {
|
|
||||||
mouse_pos.0 + (1 + 1)
|
|
||||||
};
|
|
||||||
t.render(ctx, x, y);
|
|
||||||
y += t.height();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,159 +0,0 @@
|
||||||
use super::{
|
|
||||||
effects::{ add_effect, EffectType, Targets },
|
|
||||||
gamelog,
|
|
||||||
Clock,
|
|
||||||
HungerClock,
|
|
||||||
HungerState,
|
|
||||||
TakingTurn,
|
|
||||||
DamageType,
|
|
||||||
Intrinsics,
|
|
||||||
};
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
use crate::config::CONFIG;
|
|
||||||
|
|
||||||
/// HungerSystem is in charge of ticking down the hunger clock for entities with a hunger clock,
|
|
||||||
/// every time the turn clock ticks.
|
|
||||||
pub struct HungerSystem {}
|
|
||||||
|
|
||||||
const MAX_SATIATION: i32 = 2000;
|
|
||||||
const HUNGER_BREAKPOINTS: [(i32, HungerState); 5] = [
|
|
||||||
(1000, HungerState::Satiated),
|
|
||||||
(600, HungerState::Normal),
|
|
||||||
(400, HungerState::Hungry),
|
|
||||||
(200, HungerState::Weak),
|
|
||||||
(0, HungerState::Fainting),
|
|
||||||
];
|
|
||||||
const BASE_CLOCK_DECREMENT_PER_TURN: i32 = 1;
|
|
||||||
|
|
||||||
pub fn get_hunger_state(duration: i32) -> HungerState {
|
|
||||||
for (threshold, state) in HUNGER_BREAKPOINTS.iter() {
|
|
||||||
if duration > *threshold {
|
|
||||||
return *state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return HungerState::Starving;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_hunger_colour(state: HungerState) -> (u8, u8, u8) {
|
|
||||||
match state {
|
|
||||||
HungerState::Satiated => GREEN,
|
|
||||||
HungerState::Normal => WHITE,
|
|
||||||
HungerState::Hungry => BROWN1,
|
|
||||||
HungerState::Weak => ORANGE,
|
|
||||||
HungerState::Fainting => RED3,
|
|
||||||
HungerState::Starving => RED,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> System<'a> for HungerSystem {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type SystemData = (
|
|
||||||
Entities<'a>,
|
|
||||||
WriteStorage<'a, HungerClock>,
|
|
||||||
ReadExpect<'a, Entity>,
|
|
||||||
ReadStorage<'a, Clock>,
|
|
||||||
ReadStorage<'a, TakingTurn>,
|
|
||||||
ReadStorage<'a, Intrinsics>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (entities, mut hunger_clock, player_entity, turn_clock, turns, intrinsics) = data;
|
|
||||||
|
|
||||||
// If the turn clock isn't taking a turn this tick, don't bother ticking hunger.
|
|
||||||
let mut ticked = false;
|
|
||||||
for (_e, _c, _t) in (&entities, &turn_clock, &turns).join() {
|
|
||||||
ticked = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if !ticked {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Otherwise, tick down the hunger clock for all entities with one.
|
|
||||||
for (entity, mut hunger_clock) in (&entities, &mut hunger_clock).join() {
|
|
||||||
if hunger_clock.duration >= MAX_SATIATION {
|
|
||||||
hunger_clock.duration = MAX_SATIATION;
|
|
||||||
} else {
|
|
||||||
let mut modifier = 0;
|
|
||||||
let intrinsic_regen = if let Some(i) = intrinsics.get(entity) {
|
|
||||||
i.list.contains(&crate::Intrinsic::Regeneration)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
if intrinsic_regen {
|
|
||||||
modifier += 1;
|
|
||||||
}
|
|
||||||
hunger_clock.duration -= BASE_CLOCK_DECREMENT_PER_TURN + modifier;
|
|
||||||
}
|
|
||||||
let initial_state = hunger_clock.state;
|
|
||||||
hunger_clock.state = get_hunger_state(hunger_clock.duration);
|
|
||||||
if hunger_clock.state == HungerState::Starving {
|
|
||||||
add_effect(
|
|
||||||
None,
|
|
||||||
EffectType::Damage { amount: 1, damage_type: DamageType::Forced },
|
|
||||||
Targets::Entity { target: entity }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if CONFIG.logging.log_ticks && entity == *player_entity {
|
|
||||||
console::log(
|
|
||||||
format!(
|
|
||||||
"HUNGER SYSTEM: Ticked for player entity. [clock: {}]",
|
|
||||||
hunger_clock.duration
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if hunger_clock.state == initial_state {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if entity != *player_entity {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Things which only happen to the player.
|
|
||||||
match hunger_clock.state {
|
|
||||||
HungerState::Satiated =>
|
|
||||||
gamelog::Logger
|
|
||||||
::new()
|
|
||||||
.append("You feel")
|
|
||||||
.colour(get_hunger_colour(hunger_clock.state))
|
|
||||||
.append_n("satiated")
|
|
||||||
.colour(WHITE)
|
|
||||||
.period()
|
|
||||||
.log(),
|
|
||||||
HungerState::Normal => {}
|
|
||||||
HungerState::Hungry =>
|
|
||||||
gamelog::Logger
|
|
||||||
::new()
|
|
||||||
.append("You feel")
|
|
||||||
.colour(get_hunger_colour(hunger_clock.state))
|
|
||||||
.append_n("hungry")
|
|
||||||
.colour(WHITE)
|
|
||||||
.period()
|
|
||||||
.log(),
|
|
||||||
HungerState::Weak =>
|
|
||||||
gamelog::Logger
|
|
||||||
::new()
|
|
||||||
.append("You feel")
|
|
||||||
.colour(get_hunger_colour(hunger_clock.state))
|
|
||||||
.append_n("weak with hunger")
|
|
||||||
.colour(WHITE)
|
|
||||||
.period()
|
|
||||||
.log(),
|
|
||||||
HungerState::Fainting =>
|
|
||||||
gamelog::Logger
|
|
||||||
::new()
|
|
||||||
.append("You feel")
|
|
||||||
.colour(get_hunger_colour(hunger_clock.state))
|
|
||||||
.append_n("hungry enough to faint")
|
|
||||||
.colour(WHITE)
|
|
||||||
.period()
|
|
||||||
.log(),
|
|
||||||
_ =>
|
|
||||||
gamelog::Logger
|
|
||||||
::new()
|
|
||||||
.colour(get_hunger_colour(hunger_clock.state))
|
|
||||||
.append_n("You can't go on without food!")
|
|
||||||
.log(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
use crate::{
|
|
||||||
gamelog,
|
|
||||||
gui::obfuscate_name,
|
|
||||||
gui::item_colour,
|
|
||||||
Beatitude,
|
|
||||||
Charges,
|
|
||||||
EquipmentChanged,
|
|
||||||
InBackpack,
|
|
||||||
MagicItem,
|
|
||||||
MasterDungeonMap,
|
|
||||||
Name,
|
|
||||||
ObfuscatedName,
|
|
||||||
Position,
|
|
||||||
WantsToPickupItem,
|
|
||||||
WantsToAssignKey,
|
|
||||||
};
|
|
||||||
use specs::prelude::*;
|
|
||||||
use crate::data::messages;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
pub struct ItemCollectionSystem {}
|
|
||||||
|
|
||||||
impl<'a> System<'a> for ItemCollectionSystem {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type SystemData = (
|
|
||||||
ReadExpect<'a, Entity>,
|
|
||||||
WriteStorage<'a, WantsToPickupItem>,
|
|
||||||
WriteStorage<'a, Position>,
|
|
||||||
ReadStorage<'a, Name>,
|
|
||||||
WriteStorage<'a, InBackpack>,
|
|
||||||
WriteStorage<'a, EquipmentChanged>,
|
|
||||||
ReadStorage<'a, MagicItem>,
|
|
||||||
ReadStorage<'a, ObfuscatedName>,
|
|
||||||
ReadStorage<'a, Beatitude>,
|
|
||||||
ReadExpect<'a, MasterDungeonMap>,
|
|
||||||
ReadStorage<'a, Charges>,
|
|
||||||
ReadStorage<'a, WantsToAssignKey>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (
|
|
||||||
player_entity,
|
|
||||||
mut wants_pickup,
|
|
||||||
mut positions,
|
|
||||||
names,
|
|
||||||
mut backpack,
|
|
||||||
mut equipment_changed,
|
|
||||||
magic_items,
|
|
||||||
obfuscated_names,
|
|
||||||
beatitudes,
|
|
||||||
dm,
|
|
||||||
wands,
|
|
||||||
wants_key,
|
|
||||||
) = data;
|
|
||||||
let mut to_remove: Vec<Entity> = Vec::new();
|
|
||||||
// For every item that wants to be picked up that *isn't* waiting on a key assignment.
|
|
||||||
for (pickup, _key) in (&wants_pickup, !&wants_key).join() {
|
|
||||||
if pickup.collected_by == *player_entity {
|
|
||||||
gamelog::Logger
|
|
||||||
::new()
|
|
||||||
.append(messages::YOU_PICKUP_ITEM)
|
|
||||||
.colour(item_colour(pickup.item, &beatitudes))
|
|
||||||
.append_n(
|
|
||||||
format!(
|
|
||||||
"{}",
|
|
||||||
obfuscate_name(
|
|
||||||
pickup.item,
|
|
||||||
&names,
|
|
||||||
&magic_items,
|
|
||||||
&obfuscated_names,
|
|
||||||
&beatitudes,
|
|
||||||
&dm,
|
|
||||||
Some(&wands)
|
|
||||||
).0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.colour(WHITE)
|
|
||||||
.period()
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
use crate::{
|
|
||||||
gamelog,
|
|
||||||
gui::obfuscate_name,
|
|
||||||
gui::item_colour,
|
|
||||||
Beatitude,
|
|
||||||
Charges,
|
|
||||||
EquipmentChanged,
|
|
||||||
InBackpack,
|
|
||||||
MagicItem,
|
|
||||||
MasterDungeonMap,
|
|
||||||
Name,
|
|
||||||
ObfuscatedName,
|
|
||||||
Position,
|
|
||||||
WantsToDropItem,
|
|
||||||
WantsToRemoveKey,
|
|
||||||
};
|
|
||||||
use specs::prelude::*;
|
|
||||||
use crate::data::messages;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
pub struct ItemDropSystem {}
|
|
||||||
|
|
||||||
impl<'a> System<'a> for ItemDropSystem {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type SystemData = (
|
|
||||||
ReadExpect<'a, Entity>,
|
|
||||||
Entities<'a>,
|
|
||||||
WriteStorage<'a, WantsToDropItem>,
|
|
||||||
ReadStorage<'a, Name>,
|
|
||||||
WriteStorage<'a, Position>,
|
|
||||||
WriteStorage<'a, InBackpack>,
|
|
||||||
WriteStorage<'a, EquipmentChanged>,
|
|
||||||
ReadStorage<'a, MagicItem>,
|
|
||||||
ReadStorage<'a, Beatitude>,
|
|
||||||
ReadStorage<'a, ObfuscatedName>,
|
|
||||||
ReadExpect<'a, MasterDungeonMap>,
|
|
||||||
ReadStorage<'a, Charges>,
|
|
||||||
WriteStorage<'a, WantsToRemoveKey>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (
|
|
||||||
player_entity,
|
|
||||||
entities,
|
|
||||||
mut wants_drop,
|
|
||||||
names,
|
|
||||||
mut positions,
|
|
||||||
mut backpack,
|
|
||||||
mut equipment_changed,
|
|
||||||
magic_items,
|
|
||||||
beatitudes,
|
|
||||||
obfuscated_names,
|
|
||||||
dm,
|
|
||||||
wands,
|
|
||||||
mut keys,
|
|
||||||
) = data;
|
|
||||||
|
|
||||||
for (entity, to_drop) in (&entities, &wants_drop).join() {
|
|
||||||
equipment_changed
|
|
||||||
.insert(entity, EquipmentChanged {})
|
|
||||||
.expect("Unable to insert EquipmentChanged.");
|
|
||||||
let mut dropper_pos: Position = Position { x: 0, y: 0 };
|
|
||||||
{
|
|
||||||
let dropped_pos = positions.get(entity).unwrap();
|
|
||||||
dropper_pos.x = dropped_pos.x;
|
|
||||||
dropper_pos.y = dropped_pos.y;
|
|
||||||
}
|
|
||||||
positions
|
|
||||||
.insert(to_drop.item, Position { x: dropper_pos.x, y: dropper_pos.y })
|
|
||||||
.expect("Failed to insert position.");
|
|
||||||
backpack.remove(to_drop.item);
|
|
||||||
|
|
||||||
if entity == *player_entity {
|
|
||||||
keys.insert(to_drop.item, WantsToRemoveKey {}).expect(
|
|
||||||
"Unable to insert WantsToRemoveKey"
|
|
||||||
);
|
|
||||||
gamelog::Logger
|
|
||||||
::new()
|
|
||||||
.append(messages::YOU_DROP_ITEM)
|
|
||||||
.colour(item_colour(to_drop.item, &beatitudes))
|
|
||||||
.append_n(
|
|
||||||
format!(
|
|
||||||
"{}",
|
|
||||||
obfuscate_name(
|
|
||||||
to_drop.item,
|
|
||||||
&names,
|
|
||||||
&magic_items,
|
|
||||||
&obfuscated_names,
|
|
||||||
&beatitudes,
|
|
||||||
&dm,
|
|
||||||
Some(&wands)
|
|
||||||
).0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.colour(WHITE)
|
|
||||||
.period()
|
|
||||||
.log();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wants_drop.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,172 +0,0 @@
|
||||||
use crate::{
|
|
||||||
gamelog,
|
|
||||||
gui::{ item_colour, obfuscate_name },
|
|
||||||
Beatitude,
|
|
||||||
EquipmentChanged,
|
|
||||||
Equippable,
|
|
||||||
Equipped,
|
|
||||||
IdentifiedBeatitude,
|
|
||||||
IdentifiedItem,
|
|
||||||
InBackpack,
|
|
||||||
MagicItem,
|
|
||||||
MasterDungeonMap,
|
|
||||||
Name,
|
|
||||||
ObfuscatedName,
|
|
||||||
WantsToUseItem,
|
|
||||||
BUC,
|
|
||||||
};
|
|
||||||
use specs::prelude::*;
|
|
||||||
use crate::data::messages;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
pub struct ItemEquipSystem {}
|
|
||||||
|
|
||||||
impl<'a> System<'a> for ItemEquipSystem {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type SystemData = (
|
|
||||||
ReadExpect<'a, Entity>,
|
|
||||||
Entities<'a>,
|
|
||||||
WriteStorage<'a, WantsToUseItem>,
|
|
||||||
WriteStorage<'a, IdentifiedItem>,
|
|
||||||
ReadStorage<'a, Name>,
|
|
||||||
ReadStorage<'a, Equippable>,
|
|
||||||
WriteStorage<'a, Equipped>,
|
|
||||||
WriteStorage<'a, InBackpack>,
|
|
||||||
WriteStorage<'a, EquipmentChanged>,
|
|
||||||
ReadStorage<'a, MagicItem>,
|
|
||||||
ReadStorage<'a, ObfuscatedName>,
|
|
||||||
ReadStorage<'a, Beatitude>,
|
|
||||||
WriteStorage<'a, IdentifiedBeatitude>,
|
|
||||||
ReadExpect<'a, MasterDungeonMap>,
|
|
||||||
);
|
|
||||||
|
|
||||||
#[allow(clippy::cognitive_complexity)]
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (
|
|
||||||
player_entity,
|
|
||||||
entities,
|
|
||||||
mut wants_to_use_item,
|
|
||||||
mut identified_items,
|
|
||||||
names,
|
|
||||||
equippable,
|
|
||||||
mut equipped,
|
|
||||||
mut backpack,
|
|
||||||
mut dirty,
|
|
||||||
magic_items,
|
|
||||||
obfuscated_names,
|
|
||||||
beatitudes,
|
|
||||||
mut identified_beatitude,
|
|
||||||
dm,
|
|
||||||
) = data;
|
|
||||||
let mut remove: Vec<Entity> = Vec::new();
|
|
||||||
// For every item with a target, if the item is equippable, find the correct slot.
|
|
||||||
for (target, wants_to_use_item) in (&entities, &wants_to_use_item).join() {
|
|
||||||
if let Some(can_equip) = equippable.get(wants_to_use_item.item) {
|
|
||||||
let target_slot = can_equip.slot;
|
|
||||||
let mut logger = gamelog::Logger::new();
|
|
||||||
// Remove any items target has in item's slot
|
|
||||||
let mut can_equip = true;
|
|
||||||
let mut to_unequip: Vec<Entity> = Vec::new();
|
|
||||||
for (item_entity, already_equipped, _name) in (
|
|
||||||
&entities,
|
|
||||||
&equipped,
|
|
||||||
&names,
|
|
||||||
).join() {
|
|
||||||
if already_equipped.owner == target && already_equipped.slot == target_slot {
|
|
||||||
if let Some(beatitude) = beatitudes.get(item_entity) {
|
|
||||||
if beatitude.buc == BUC::Cursed {
|
|
||||||
can_equip = false;
|
|
||||||
logger = logger
|
|
||||||
.append(messages::YOU_REMOVE_ITEM_CURSED)
|
|
||||||
.colour(item_colour(item_entity, &beatitudes))
|
|
||||||
.append_n(
|
|
||||||
obfuscate_name(
|
|
||||||
item_entity,
|
|
||||||
&names,
|
|
||||||
&magic_items,
|
|
||||||
&obfuscated_names,
|
|
||||||
&beatitudes,
|
|
||||||
&dm,
|
|
||||||
None
|
|
||||||
).0
|
|
||||||
)
|
|
||||||
.colour(WHITE)
|
|
||||||
.append("!");
|
|
||||||
identified_beatitude
|
|
||||||
.insert(item_entity, IdentifiedBeatitude {})
|
|
||||||
.expect("Unable to push");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
to_unequip.push(item_entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !can_equip {
|
|
||||||
logger.log();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for item in to_unequip.iter() {
|
|
||||||
equipped.remove(*item);
|
|
||||||
backpack
|
|
||||||
.insert(*item, InBackpack { owner: target })
|
|
||||||
.expect("Unable to insert backpack");
|
|
||||||
if target == *player_entity {
|
|
||||||
logger = logger
|
|
||||||
.append(messages::YOU_REMOVE_ITEM)
|
|
||||||
.colour(item_colour(*item, &beatitudes))
|
|
||||||
.append_n(
|
|
||||||
obfuscate_name(
|
|
||||||
*item,
|
|
||||||
&names,
|
|
||||||
&magic_items,
|
|
||||||
&obfuscated_names,
|
|
||||||
&beatitudes,
|
|
||||||
&dm,
|
|
||||||
None
|
|
||||||
).0
|
|
||||||
)
|
|
||||||
.colour(WHITE)
|
|
||||||
.period();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wield the item
|
|
||||||
equipped
|
|
||||||
.insert(wants_to_use_item.item, Equipped { owner: target, slot: target_slot })
|
|
||||||
.expect("Unable to insert equipped component");
|
|
||||||
backpack.remove(wants_to_use_item.item);
|
|
||||||
if target == *player_entity {
|
|
||||||
logger = logger
|
|
||||||
.append(messages::YOU_EQUIP_ITEM)
|
|
||||||
.colour(item_colour(wants_to_use_item.item, &beatitudes))
|
|
||||||
.append_n(
|
|
||||||
obfuscate_name(
|
|
||||||
wants_to_use_item.item,
|
|
||||||
&names,
|
|
||||||
&magic_items,
|
|
||||||
&obfuscated_names,
|
|
||||||
&beatitudes,
|
|
||||||
&dm,
|
|
||||||
None
|
|
||||||
).0
|
|
||||||
)
|
|
||||||
.colour(WHITE)
|
|
||||||
.period();
|
|
||||||
logger.log();
|
|
||||||
identified_items
|
|
||||||
.insert(target, IdentifiedItem {
|
|
||||||
name: names.get(wants_to_use_item.item).unwrap().name.clone(),
|
|
||||||
})
|
|
||||||
.expect("Unable to insert IdentifiedItem");
|
|
||||||
identified_beatitude
|
|
||||||
.insert(wants_to_use_item.item, IdentifiedBeatitude {})
|
|
||||||
.expect("Unable to push");
|
|
||||||
}
|
|
||||||
remove.push(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
remove.iter().for_each(|e| {
|
|
||||||
dirty.insert(*e, EquipmentChanged {}).expect("Unabble to insert EquipmentChanged");
|
|
||||||
wants_to_use_item.remove(*e).expect("Unable to remove *e");
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
use crate::{
|
|
||||||
Beatitude,
|
|
||||||
IdentifiedBeatitude,
|
|
||||||
IdentifiedItem,
|
|
||||||
Item,
|
|
||||||
MasterDungeonMap,
|
|
||||||
Name,
|
|
||||||
ObfuscatedName,
|
|
||||||
Player,
|
|
||||||
};
|
|
||||||
use specs::prelude::*;
|
|
||||||
use crate::data::events::*;
|
|
||||||
use crate::gamelog;
|
|
||||||
|
|
||||||
pub struct ItemIdentificationSystem {}
|
|
||||||
|
|
||||||
impl<'a> System<'a> for ItemIdentificationSystem {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type SystemData = (
|
|
||||||
ReadStorage<'a, Player>,
|
|
||||||
WriteStorage<'a, IdentifiedItem>,
|
|
||||||
WriteStorage<'a, Beatitude>,
|
|
||||||
WriteStorage<'a, IdentifiedBeatitude>,
|
|
||||||
WriteExpect<'a, MasterDungeonMap>,
|
|
||||||
ReadStorage<'a, Item>,
|
|
||||||
ReadStorage<'a, Name>,
|
|
||||||
WriteStorage<'a, ObfuscatedName>,
|
|
||||||
Entities<'a>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (
|
|
||||||
player,
|
|
||||||
mut identified,
|
|
||||||
mut beatitudes,
|
|
||||||
mut identified_beatitudes,
|
|
||||||
mut dm,
|
|
||||||
items,
|
|
||||||
names,
|
|
||||||
mut obfuscated_names,
|
|
||||||
entities,
|
|
||||||
) = data;
|
|
||||||
for (_p, id) in (&player, &identified).join() {
|
|
||||||
let tag = crate::raws::get_id_from_name(id.name.clone());
|
|
||||||
if !dm.identified_items.contains(&id.name) && crate::raws::is_tag_magic(&tag) {
|
|
||||||
if gamelog::get_event_count(EVENT::COUNT_TURN) != 1 {
|
|
||||||
gamelog::record_event(EVENT::Identified(id.name.clone()));
|
|
||||||
}
|
|
||||||
dm.identified_items.insert(id.name.clone());
|
|
||||||
for (entity, _item, name) in (&entities, &items, &names).join() {
|
|
||||||
if name.name == id.name {
|
|
||||||
obfuscated_names.remove(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (e, _id) in (&entities, &identified_beatitudes).join() {
|
|
||||||
if let Some(beatitude) = beatitudes.get_mut(e) {
|
|
||||||
beatitude.known = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Clean up
|
|
||||||
identified.clear();
|
|
||||||
identified_beatitudes.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
mod collection_system;
|
|
||||||
mod drop_system;
|
|
||||||
mod equip_system;
|
|
||||||
mod identification_system;
|
|
||||||
mod remove_system;
|
|
||||||
mod use_system;
|
|
||||||
mod keyhandling;
|
|
||||||
|
|
||||||
pub use self::{
|
|
||||||
collection_system::ItemCollectionSystem,
|
|
||||||
drop_system::ItemDropSystem,
|
|
||||||
equip_system::ItemEquipSystem,
|
|
||||||
identification_system::ItemIdentificationSystem,
|
|
||||||
remove_system::ItemRemoveSystem,
|
|
||||||
use_system::ItemUseSystem,
|
|
||||||
keyhandling::KeyHandling,
|
|
||||||
};
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
use crate::{
|
|
||||||
gamelog,
|
|
||||||
gui::{ item_colour, obfuscate_name },
|
|
||||||
Beatitude,
|
|
||||||
Equipped,
|
|
||||||
InBackpack,
|
|
||||||
MagicItem,
|
|
||||||
MasterDungeonMap,
|
|
||||||
Name,
|
|
||||||
ObfuscatedName,
|
|
||||||
WantsToRemoveItem,
|
|
||||||
BUC,
|
|
||||||
};
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
use crate::data::messages;
|
|
||||||
|
|
||||||
pub struct ItemRemoveSystem {}
|
|
||||||
|
|
||||||
impl<'a> System<'a> for ItemRemoveSystem {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type SystemData = (
|
|
||||||
Entities<'a>,
|
|
||||||
ReadExpect<'a, Entity>,
|
|
||||||
ReadStorage<'a, Name>,
|
|
||||||
WriteStorage<'a, WantsToRemoveItem>,
|
|
||||||
WriteStorage<'a, Equipped>,
|
|
||||||
WriteStorage<'a, InBackpack>,
|
|
||||||
ReadStorage<'a, MagicItem>,
|
|
||||||
ReadStorage<'a, ObfuscatedName>,
|
|
||||||
ReadStorage<'a, Beatitude>,
|
|
||||||
ReadExpect<'a, MasterDungeonMap>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (
|
|
||||||
entities,
|
|
||||||
player_entity,
|
|
||||||
names,
|
|
||||||
mut wants_remove,
|
|
||||||
mut equipped,
|
|
||||||
mut backpack,
|
|
||||||
magic_items,
|
|
||||||
obfuscated_names,
|
|
||||||
beatitudes,
|
|
||||||
dm,
|
|
||||||
) = data;
|
|
||||||
|
|
||||||
for (entity, to_remove) in (&entities, &wants_remove).join() {
|
|
||||||
let mut can_remove = true;
|
|
||||||
if let Some(beatitude) = beatitudes.get(to_remove.item) {
|
|
||||||
// If cursed, can't remove!
|
|
||||||
if beatitude.buc == BUC::Cursed {
|
|
||||||
can_remove = false;
|
|
||||||
gamelog::Logger
|
|
||||||
::new()
|
|
||||||
.append(messages::YOU_REMOVE_ITEM_CURSED)
|
|
||||||
.colour(item_colour(to_remove.item, &beatitudes))
|
|
||||||
.append_n(
|
|
||||||
obfuscate_name(
|
|
||||||
to_remove.item,
|
|
||||||
&names,
|
|
||||||
&magic_items,
|
|
||||||
&obfuscated_names,
|
|
||||||
&beatitudes,
|
|
||||||
&dm,
|
|
||||||
None
|
|
||||||
).0
|
|
||||||
)
|
|
||||||
.colour(WHITE)
|
|
||||||
.append("!")
|
|
||||||
.log();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !can_remove {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// If not cursed, remove it
|
|
||||||
equipped.remove(to_remove.item);
|
|
||||||
if let Some(_) = names.get(to_remove.item) {
|
|
||||||
if entity == *player_entity {
|
|
||||||
gamelog::Logger
|
|
||||||
::new()
|
|
||||||
.append(messages::YOU_REMOVE_ITEM)
|
|
||||||
.colour(item_colour(to_remove.item, &beatitudes))
|
|
||||||
.append_n(
|
|
||||||
obfuscate_name(
|
|
||||||
to_remove.item,
|
|
||||||
&names,
|
|
||||||
&magic_items,
|
|
||||||
&obfuscated_names,
|
|
||||||
&beatitudes,
|
|
||||||
&dm,
|
|
||||||
None
|
|
||||||
).0
|
|
||||||
)
|
|
||||||
.colour(WHITE)
|
|
||||||
.period()
|
|
||||||
.log();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
backpack
|
|
||||||
.insert(to_remove.item, InBackpack { owner: entity })
|
|
||||||
.expect("Unable to insert backpack");
|
|
||||||
}
|
|
||||||
|
|
||||||
wants_remove.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
use crate::{
|
|
||||||
effects::{ add_effect, aoe_tiles, EffectType, Targets },
|
|
||||||
EquipmentChanged,
|
|
||||||
IdentifiedItem,
|
|
||||||
IdentifiedBeatitude,
|
|
||||||
Map,
|
|
||||||
Name,
|
|
||||||
WantsToUseItem,
|
|
||||||
AOE,
|
|
||||||
};
|
|
||||||
use specs::prelude::*;
|
|
||||||
|
|
||||||
pub struct ItemUseSystem {}
|
|
||||||
|
|
||||||
impl<'a> System<'a> for ItemUseSystem {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type SystemData = (
|
|
||||||
ReadExpect<'a, Entity>,
|
|
||||||
WriteExpect<'a, Map>,
|
|
||||||
Entities<'a>,
|
|
||||||
WriteStorage<'a, WantsToUseItem>,
|
|
||||||
ReadStorage<'a, Name>,
|
|
||||||
ReadStorage<'a, AOE>,
|
|
||||||
WriteStorage<'a, EquipmentChanged>,
|
|
||||||
WriteStorage<'a, IdentifiedItem>,
|
|
||||||
WriteStorage<'a, IdentifiedBeatitude>,
|
|
||||||
);
|
|
||||||
|
|
||||||
#[allow(clippy::cognitive_complexity)]
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
|
||||||
let (
|
|
||||||
player_entity,
|
|
||||||
map,
|
|
||||||
entities,
|
|
||||||
mut wants_use,
|
|
||||||
names,
|
|
||||||
aoe,
|
|
||||||
mut dirty,
|
|
||||||
mut identified_item,
|
|
||||||
mut identified_beatitude,
|
|
||||||
) = data;
|
|
||||||
|
|
||||||
for (entity, useitem) in (&entities, &wants_use).join() {
|
|
||||||
dirty.insert(entity, EquipmentChanged {}).expect("Unable to insert");
|
|
||||||
// Identify
|
|
||||||
if entity == *player_entity {
|
|
||||||
identified_item
|
|
||||||
.insert(entity, IdentifiedItem { name: names.get(useitem.item).unwrap().name.clone() })
|
|
||||||
.expect("Unable to insert");
|
|
||||||
identified_beatitude.insert(useitem.item, IdentifiedBeatitude {}).expect("Unable to push");
|
|
||||||
}
|
|
||||||
// Call the effects system
|
|
||||||
add_effect(Some(entity), EffectType::ItemUse { item: useitem.item }, match useitem.target {
|
|
||||||
None => Targets::Entity { target: *player_entity },
|
|
||||||
Some(target) => {
|
|
||||||
if let Some(aoe) = aoe.get(useitem.item) {
|
|
||||||
Targets::TileList { targets: aoe_tiles(&*map, target, aoe.radius) }
|
|
||||||
} else {
|
|
||||||
Targets::Tile { target: map.xy_idx(target.x, target.y) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
wants_use.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
47
src/lib.rs
47
src/lib.rs
|
|
@ -1,47 +0,0 @@
|
||||||
// src/lib.rs
|
|
||||||
// 31-Aug-2023
|
|
||||||
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
extern crate serde;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
pub mod macros;
|
|
||||||
|
|
||||||
pub mod camera;
|
|
||||||
pub mod components;
|
|
||||||
pub mod raws;
|
|
||||||
pub mod map;
|
|
||||||
pub mod player;
|
|
||||||
pub mod gamelog;
|
|
||||||
pub mod gui;
|
|
||||||
pub mod map_builders;
|
|
||||||
pub mod saveload_system;
|
|
||||||
pub mod spawner;
|
|
||||||
pub mod visibility_system;
|
|
||||||
pub mod damage_system;
|
|
||||||
pub mod hunger_system;
|
|
||||||
pub mod melee_combat_system;
|
|
||||||
pub mod trigger_system;
|
|
||||||
pub mod inventory;
|
|
||||||
pub mod particle_system;
|
|
||||||
pub mod ai;
|
|
||||||
pub mod data;
|
|
||||||
pub mod config;
|
|
||||||
pub mod effects;
|
|
||||||
pub mod gamesystem;
|
|
||||||
pub mod random_table;
|
|
||||||
pub mod rex_assets;
|
|
||||||
pub mod spatial;
|
|
||||||
pub mod morgue;
|
|
||||||
pub mod states;
|
|
||||||
pub mod invkeys;
|
|
||||||
|
|
||||||
pub use components::*;
|
|
||||||
use particle_system::ParticleBuilder;
|
|
||||||
pub use map::*;
|
|
||||||
pub use states::runstate::RunState;
|
|
||||||
pub use states::state::State;
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
// macros/mod.rs
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
/// Used to check if the player has a given component.
|
|
||||||
macro_rules! player_has_component {
|
|
||||||
($ecs:expr, $component:ty) => {
|
|
||||||
{
|
|
||||||
let player = $ecs.fetch::<Entity>();
|
|
||||||
let component = $ecs.read_storage::<$component>();
|
|
||||||
if let Some(player_component) = component.get(*player) {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
/// Used to check if a given entity has a given Intrinsic.
|
|
||||||
macro_rules! has {
|
|
||||||
($ecs:expr, $entity:expr, $intrinsic:expr) => {
|
|
||||||
{
|
|
||||||
let intrinsics = $ecs.read_storage::<crate::Intrinsics>();
|
|
||||||
if let Some(has_intrinsics) = intrinsics.get($entity) {
|
|
||||||
has_intrinsics.list.contains(&$intrinsic)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
/// Used to check if the player has a given Intrinsic.
|
|
||||||
macro_rules! player_has {
|
|
||||||
($ecs:expr, $intrinsic:expr) => {
|
|
||||||
{
|
|
||||||
let player = $ecs.fetch::<Entity>();
|
|
||||||
let intrinsics = $ecs.read_storage::<crate::Intrinsics>();
|
|
||||||
if let Some(player_intrinsics) = intrinsics.get(*player) {
|
|
||||||
player_intrinsics.list.contains(&$intrinsic)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
/// Handles adding an Intrinsic to the player, and adding it to the IntrinsicChanged component.
|
|
||||||
macro_rules! add_intr {
|
|
||||||
($ecs:expr, $entity:expr, $intrinsic:expr) => {
|
|
||||||
{
|
|
||||||
let mut intrinsics = $ecs.write_storage::<crate::Intrinsics>();
|
|
||||||
if let Some(player_intrinsics) = intrinsics.get_mut($entity) {
|
|
||||||
if !player_intrinsics.list.contains(&$intrinsic) {
|
|
||||||
player_intrinsics.list.insert($intrinsic);
|
|
||||||
let mut intrinsic_changed = $ecs.write_storage::<crate::IntrinsicChanged>();
|
|
||||||
if let Some(this_intrinsic_changed) = intrinsic_changed.get_mut($entity) {
|
|
||||||
this_intrinsic_changed.gained.insert($intrinsic);
|
|
||||||
} else {
|
|
||||||
intrinsic_changed.insert($entity, crate::IntrinsicChanged {
|
|
||||||
gained: {
|
|
||||||
let mut m = std::collections::HashSet::new();
|
|
||||||
m.insert($intrinsic);
|
|
||||||
m
|
|
||||||
},
|
|
||||||
lost: std::collections::HashSet::new()
|
|
||||||
}).expect("Failed to insert IntrinsicChanged component.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
intrinsics.insert($entity, crate::Intrinsics {
|
|
||||||
list: {
|
|
||||||
let mut m = std::collections::HashSet::new();
|
|
||||||
m.insert($intrinsic);
|
|
||||||
m
|
|
||||||
}
|
|
||||||
}).expect("Failed to insert Intrinsics component.");
|
|
||||||
let mut intrinsic_changed = $ecs.write_storage::<crate::IntrinsicChanged>();
|
|
||||||
intrinsic_changed.insert($entity, crate::IntrinsicChanged {
|
|
||||||
gained: {
|
|
||||||
let mut m = std::collections::HashSet::new();
|
|
||||||
m.insert($intrinsic);
|
|
||||||
m
|
|
||||||
},
|
|
||||||
lost: std::collections::HashSet::new()
|
|
||||||
}).expect("Failed to insert IntrinsicChanged component.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
144
src/main.rs
144
src/main.rs
|
|
@ -1,144 +0,0 @@
|
||||||
use rust_rl::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
use specs::saveload::{ SimpleMarker, SimpleMarkerAllocator };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
const DISPLAYWIDTH: i32 = 105;
|
|
||||||
const DISPLAYHEIGHT: i32 = 56;
|
|
||||||
|
|
||||||
fn main() -> BError {
|
|
||||||
// Embedded resources for use in wasm build
|
|
||||||
const CURSES_14_16_BYTES: &[u8] = include_bytes!("../resources/curses14x16.png");
|
|
||||||
EMBED.lock().add_resource("resources/curses14x16.png".to_string(), CURSES_14_16_BYTES);
|
|
||||||
|
|
||||||
//link_resource!(CURSES14X16, "../resources/curses_14x16.png");
|
|
||||||
|
|
||||||
let mut context = BTermBuilder::new()
|
|
||||||
.with_title("rust-rl")
|
|
||||||
.with_dimensions(DISPLAYWIDTH, DISPLAYHEIGHT)
|
|
||||||
.with_font("curses14x16.png", 14, 16)
|
|
||||||
.with_tile_dimensions(14, 16)
|
|
||||||
.with_simple_console(DISPLAYWIDTH, DISPLAYHEIGHT, "curses14x16.png")
|
|
||||||
.build()?;
|
|
||||||
if config::CONFIG.visuals.with_scanlines {
|
|
||||||
context.with_post_scanlines(config::CONFIG.visuals.with_screen_burn);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut gs = State {
|
|
||||||
ecs: World::new(),
|
|
||||||
mapgen_next_state: Some(RunState::MainMenu {
|
|
||||||
menu_selection: gui::MainMenuSelection::NewGame,
|
|
||||||
}),
|
|
||||||
mapgen_index: 0,
|
|
||||||
mapgen_history: Vec::new(),
|
|
||||||
mapgen_timer: 0.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
gs.ecs.register::<Position>();
|
|
||||||
gs.ecs.register::<OtherLevelPosition>();
|
|
||||||
gs.ecs.register::<Renderable>();
|
|
||||||
gs.ecs.register::<Burden>();
|
|
||||||
gs.ecs.register::<Prop>();
|
|
||||||
gs.ecs.register::<Player>();
|
|
||||||
gs.ecs.register::<HasAncestry>();
|
|
||||||
gs.ecs.register::<HasClass>();
|
|
||||||
gs.ecs.register::<Chasing>();
|
|
||||||
gs.ecs.register::<Faction>();
|
|
||||||
gs.ecs.register::<Clock>();
|
|
||||||
gs.ecs.register::<Quips>();
|
|
||||||
gs.ecs.register::<Mind>();
|
|
||||||
gs.ecs.register::<Viewshed>();
|
|
||||||
gs.ecs.register::<Telepath>();
|
|
||||||
gs.ecs.register::<Name>();
|
|
||||||
gs.ecs.register::<ObfuscatedName>();
|
|
||||||
gs.ecs.register::<BlocksTile>();
|
|
||||||
gs.ecs.register::<BlocksVisibility>();
|
|
||||||
gs.ecs.register::<Door>();
|
|
||||||
gs.ecs.register::<Pools>();
|
|
||||||
gs.ecs.register::<Attributes>();
|
|
||||||
gs.ecs.register::<Skills>();
|
|
||||||
gs.ecs.register::<HungerClock>();
|
|
||||||
gs.ecs.register::<WantsToMelee>();
|
|
||||||
gs.ecs.register::<Item>();
|
|
||||||
gs.ecs.register::<Beatitude>();
|
|
||||||
gs.ecs.register::<IdentifiedItem>();
|
|
||||||
gs.ecs.register::<IdentifiedBeatitude>();
|
|
||||||
gs.ecs.register::<MagicItem>();
|
|
||||||
gs.ecs.register::<GrantsXP>();
|
|
||||||
gs.ecs.register::<LootTable>();
|
|
||||||
gs.ecs.register::<Energy>();
|
|
||||||
gs.ecs.register::<TakingTurn>();
|
|
||||||
gs.ecs.register::<Equippable>();
|
|
||||||
gs.ecs.register::<EquipmentChanged>();
|
|
||||||
gs.ecs.register::<Equipped>();
|
|
||||||
gs.ecs.register::<MeleeWeapon>();
|
|
||||||
gs.ecs.register::<NaturalAttacks>();
|
|
||||||
gs.ecs.register::<ArmourClassBonus>();
|
|
||||||
gs.ecs.register::<ToHitBonus>();
|
|
||||||
gs.ecs.register::<MoveMode>();
|
|
||||||
gs.ecs.register::<ProvidesHealing>();
|
|
||||||
gs.ecs.register::<InflictsDamage>();
|
|
||||||
gs.ecs.register::<Ranged>();
|
|
||||||
gs.ecs.register::<AOE>();
|
|
||||||
gs.ecs.register::<Digger>();
|
|
||||||
gs.ecs.register::<Confusion>();
|
|
||||||
gs.ecs.register::<Blind>();
|
|
||||||
gs.ecs.register::<MagicMapper>();
|
|
||||||
gs.ecs.register::<InBackpack>();
|
|
||||||
gs.ecs.register::<WantsToApproach>();
|
|
||||||
gs.ecs.register::<WantsToFlee>();
|
|
||||||
gs.ecs.register::<WantsToPickupItem>();
|
|
||||||
gs.ecs.register::<WantsToDropItem>();
|
|
||||||
gs.ecs.register::<WantsToRemoveItem>();
|
|
||||||
gs.ecs.register::<WantsToUseItem>();
|
|
||||||
gs.ecs.register::<Consumable>();
|
|
||||||
gs.ecs.register::<SingleActivation>();
|
|
||||||
gs.ecs.register::<Charges>();
|
|
||||||
gs.ecs.register::<ProvidesNutrition>();
|
|
||||||
gs.ecs.register::<Destructible>();
|
|
||||||
gs.ecs.register::<Hidden>();
|
|
||||||
gs.ecs.register::<EntryTrigger>();
|
|
||||||
gs.ecs.register::<EntityMoved>();
|
|
||||||
gs.ecs.register::<MultiAttack>();
|
|
||||||
gs.ecs.register::<ProvidesRemoveCurse>();
|
|
||||||
gs.ecs.register::<ProvidesIdentify>();
|
|
||||||
gs.ecs.register::<KnownSpells>();
|
|
||||||
gs.ecs.register::<GrantsSpell>();
|
|
||||||
gs.ecs.register::<Bleeds>();
|
|
||||||
gs.ecs.register::<ParticleLifetime>();
|
|
||||||
gs.ecs.register::<SpawnParticleSimple>();
|
|
||||||
gs.ecs.register::<SpawnParticleBurst>();
|
|
||||||
gs.ecs.register::<SpawnParticleLine>();
|
|
||||||
gs.ecs.register::<HasDamageModifiers>();
|
|
||||||
gs.ecs.register::<Intrinsics>();
|
|
||||||
gs.ecs.register::<IntrinsicChanged>();
|
|
||||||
gs.ecs.register::<Stackable>();
|
|
||||||
gs.ecs.register::<WantsToAssignKey>();
|
|
||||||
gs.ecs.register::<Key>();
|
|
||||||
gs.ecs.register::<WantsToRemoveKey>();
|
|
||||||
gs.ecs.register::<WantsToDelete>();
|
|
||||||
gs.ecs.register::<SimpleMarker<SerializeMe>>();
|
|
||||||
gs.ecs.register::<SerializationHelper>();
|
|
||||||
gs.ecs.register::<DMSerializationHelper>();
|
|
||||||
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());
|
|
||||||
|
|
||||||
raws::load_raws();
|
|
||||||
|
|
||||||
// Insert calls
|
|
||||||
gs.ecs.insert(RandomNumberGenerator::new());
|
|
||||||
gs.ecs.insert(map::MasterDungeonMap::new()); // Master map list
|
|
||||||
gs.ecs.insert(Map::new(true, 1, 64, 64, 0, "New Map", "N", 0)); // Map
|
|
||||||
gs.ecs.insert(Point::new(0, 0)); // Player pos
|
|
||||||
gs.ecs.insert(gui::Ancestry::Human); // ancestry
|
|
||||||
let player_entity = spawner::player(&mut gs.ecs, 0, 0);
|
|
||||||
gs.ecs.insert(player_entity); // Player entity
|
|
||||||
gs.ecs.insert(RunState::MapGeneration {}); // RunState
|
|
||||||
gs.ecs.insert(particle_system::ParticleBuilder::new());
|
|
||||||
gs.ecs.insert(rex_assets::RexAssets::new());
|
|
||||||
|
|
||||||
gamelog::setup_log();
|
|
||||||
gamelog::record_event(data::events::EVENT::Level(1));
|
|
||||||
gs.generate_world_map(1, TileType::Floor);
|
|
||||||
|
|
||||||
main_loop(context, gs)
|
|
||||||
}
|
|
||||||
|
|
@ -1,366 +0,0 @@
|
||||||
use super::{ Map, TileType };
|
|
||||||
use crate::{ gamelog, map_builders, OtherLevelPosition, Position, Telepath, Viewshed };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use serde::{ Deserialize, Serialize };
|
|
||||||
use specs::prelude::*;
|
|
||||||
use std::collections::{ HashMap, HashSet };
|
|
||||||
use crate::data::events::*;
|
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct MasterDungeonMap {
|
|
||||||
maps: HashMap<i32, Map>,
|
|
||||||
pub identified_items: HashSet<String>,
|
|
||||||
pub scroll_map: HashMap<String, String>,
|
|
||||||
pub potion_map: HashMap<String, String>,
|
|
||||||
pub wand_map: HashMap<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MasterDungeonMap {
|
|
||||||
/// Initialises a blank MasterDungeonMap
|
|
||||||
pub fn new() -> MasterDungeonMap {
|
|
||||||
let mut dm = MasterDungeonMap {
|
|
||||||
maps: HashMap::new(),
|
|
||||||
identified_items: HashSet::new(),
|
|
||||||
scroll_map: HashMap::new(),
|
|
||||||
potion_map: HashMap::new(),
|
|
||||||
wand_map: HashMap::new(),
|
|
||||||
};
|
|
||||||
// TODO: Use stored RNG
|
|
||||||
let mut rng = RandomNumberGenerator::new();
|
|
||||||
for scroll_tag in crate::raws::get_scroll_tags().iter() {
|
|
||||||
let unid_singular = make_scroll_name(&mut rng);
|
|
||||||
dm.scroll_map.insert(scroll_tag.to_string(), unid_singular);
|
|
||||||
}
|
|
||||||
let mut used_potion_names: HashSet<String> = HashSet::new();
|
|
||||||
for potion_tag in crate::raws::get_potion_tags().iter() {
|
|
||||||
let unid_singular = make_potion_name(&mut rng, &mut used_potion_names);
|
|
||||||
dm.potion_map.insert(potion_tag.to_string(), unid_singular);
|
|
||||||
}
|
|
||||||
let mut used_wand_names: HashSet<String> = HashSet::new();
|
|
||||||
for wand_tag in crate::raws::get_wand_tags().iter() {
|
|
||||||
let unid_singular = make_wand_name(&mut rng, &mut used_wand_names);
|
|
||||||
dm.wand_map.insert(wand_tag.to_string(), unid_singular);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dm;
|
|
||||||
}
|
|
||||||
/// Stores the given map in the MasterDungeonMap
|
|
||||||
pub fn store_map(&mut self, map: &Map) {
|
|
||||||
self.maps.insert(map.id, map.clone());
|
|
||||||
}
|
|
||||||
/// Gets a map by ID from the MasterDungeonMap
|
|
||||||
pub fn get_map(&self, id: i32) -> Option<Map> {
|
|
||||||
if self.maps.contains_key(&id) {
|
|
||||||
let result = self.maps[&id].clone();
|
|
||||||
return Some(result);
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_scroll_name(rng: &mut RandomNumberGenerator) -> String {
|
|
||||||
let len = 4 + rng.roll_dice(1, 6);
|
|
||||||
let space_at_i = if len > 6 && rng.roll_dice(1, 2) == 1 {
|
|
||||||
rng.roll_dice(1, len - 6) + 3
|
|
||||||
} else {
|
|
||||||
-1
|
|
||||||
};
|
|
||||||
let offset = rng.roll_dice(1, 2) - 1;
|
|
||||||
let mut name = "".to_string();
|
|
||||||
for i in 0..len {
|
|
||||||
if i == space_at_i {
|
|
||||||
name += " ";
|
|
||||||
}
|
|
||||||
if i % 2 == 0 + offset {
|
|
||||||
let char = match rng.roll_dice(1, 5) {
|
|
||||||
1 => "A",
|
|
||||||
2 => "E",
|
|
||||||
3 => "I",
|
|
||||||
4 => "O",
|
|
||||||
_ => "U",
|
|
||||||
};
|
|
||||||
name += char;
|
|
||||||
} else {
|
|
||||||
let char = match rng.roll_dice(1, 21) {
|
|
||||||
1 => "B",
|
|
||||||
2 => "C",
|
|
||||||
3 => "D",
|
|
||||||
4 => "F",
|
|
||||||
5 => "G",
|
|
||||||
6 => "H",
|
|
||||||
7 => "J",
|
|
||||||
8 => "K",
|
|
||||||
9 => "L",
|
|
||||||
10 => "M",
|
|
||||||
11 => "N",
|
|
||||||
12 => "P",
|
|
||||||
13 => "Q",
|
|
||||||
14 => "R",
|
|
||||||
15 => "S",
|
|
||||||
16 => "T",
|
|
||||||
17 => "V",
|
|
||||||
18 => "W",
|
|
||||||
19 => "X",
|
|
||||||
20 => "Y",
|
|
||||||
_ => "Z",
|
|
||||||
};
|
|
||||||
name += char;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
name += " scroll";
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
const POTION_COLOURS: &[&str] = &[
|
|
||||||
"red",
|
|
||||||
"orange",
|
|
||||||
"yellow",
|
|
||||||
"green",
|
|
||||||
"blue",
|
|
||||||
"indigo",
|
|
||||||
"violet",
|
|
||||||
"black",
|
|
||||||
"white",
|
|
||||||
"silver",
|
|
||||||
"gold",
|
|
||||||
"rainbow",
|
|
||||||
"blood",
|
|
||||||
"purple",
|
|
||||||
"cyan",
|
|
||||||
"brown",
|
|
||||||
"grey",
|
|
||||||
"octarine",
|
|
||||||
];
|
|
||||||
const POTION_ADJECTIVES: &[&str] = &[
|
|
||||||
"swirling",
|
|
||||||
"viscous",
|
|
||||||
"effervescent",
|
|
||||||
"slimy",
|
|
||||||
"oily",
|
|
||||||
"metallic",
|
|
||||||
"prismatic",
|
|
||||||
"goopy",
|
|
||||||
];
|
|
||||||
|
|
||||||
fn make_potion_name(rng: &mut RandomNumberGenerator, used_names: &mut HashSet<String>) -> String {
|
|
||||||
loop {
|
|
||||||
let mut name: String =
|
|
||||||
POTION_ADJECTIVES[
|
|
||||||
(rng.roll_dice(1, POTION_ADJECTIVES.len() as i32) as usize) - 1
|
|
||||||
].to_string();
|
|
||||||
name += " ";
|
|
||||||
name += POTION_COLOURS[(rng.roll_dice(1, POTION_COLOURS.len() as i32) as usize) - 1];
|
|
||||||
name += " potion";
|
|
||||||
|
|
||||||
if !used_names.contains(&name) {
|
|
||||||
used_names.insert(name.clone());
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const WAND_TYPES: &[&str] = &[
|
|
||||||
// Shapes
|
|
||||||
"curved",
|
|
||||||
"squircle",
|
|
||||||
"hexagonal",
|
|
||||||
"octagonal",
|
|
||||||
"pointed",
|
|
||||||
"long",
|
|
||||||
// Metals
|
|
||||||
"iron",
|
|
||||||
"steel",
|
|
||||||
"silver",
|
|
||||||
"gold",
|
|
||||||
"lead",
|
|
||||||
"tungsten",
|
|
||||||
// Other
|
|
||||||
"mahogany",
|
|
||||||
"ebony",
|
|
||||||
"crystalline",
|
|
||||||
"jeweled",
|
|
||||||
];
|
|
||||||
|
|
||||||
fn make_wand_name(rng: &mut RandomNumberGenerator, used_names: &mut HashSet<String>) -> String {
|
|
||||||
loop {
|
|
||||||
let mut name: String =
|
|
||||||
WAND_TYPES[(rng.roll_dice(1, WAND_TYPES.len() as i32) as usize) - 1].to_string();
|
|
||||||
name += " wand";
|
|
||||||
|
|
||||||
if !used_names.contains(&name) {
|
|
||||||
used_names.insert(name.clone());
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn level_transition(ecs: &mut World, new_id: i32, dest_tile: TileType) -> Option<Vec<Map>> {
|
|
||||||
// Obtain master
|
|
||||||
let dungeon_master = ecs.read_resource::<MasterDungeonMap>();
|
|
||||||
if dungeon_master.get_map(new_id).is_some() {
|
|
||||||
std::mem::drop(dungeon_master);
|
|
||||||
transition_to_existing_map(ecs, new_id, dest_tile);
|
|
||||||
return None;
|
|
||||||
} else {
|
|
||||||
std::mem::drop(dungeon_master);
|
|
||||||
return Some(transition_to_new_map(ecs, new_id, dest_tile));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transition_to_existing_map(ecs: &mut World, new_id: i32, dest_tile: TileType) {
|
|
||||||
let mut dungeon_master = ecs.write_resource::<MasterDungeonMap>();
|
|
||||||
// Unwrapping here panics if new_id isn't present. But this should
|
|
||||||
// never be called without new_id being present by level_transition.
|
|
||||||
let map = dungeon_master.get_map(new_id).unwrap();
|
|
||||||
let mut worldmap_resource = ecs.write_resource::<Map>();
|
|
||||||
let player_entity = ecs.fetch::<Entity>();
|
|
||||||
|
|
||||||
let w = map.width;
|
|
||||||
let mut possible_destinations: Vec<usize> = Vec::new();
|
|
||||||
for (idx, tt) in map.tiles.iter().enumerate() {
|
|
||||||
if *tt == dest_tile {
|
|
||||||
possible_destinations.push(idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if possible_destinations.is_empty() {
|
|
||||||
console::log("WARNING: No destination tiles found on map transition.");
|
|
||||||
match dest_tile {
|
|
||||||
TileType::DownStair => console::log("DESTINATION: DownStair"),
|
|
||||||
TileType::UpStair => console::log("DESTINATION: UpStair"),
|
|
||||||
TileType::ToOvermap(id) => console::log(format!("DESTINATION: ToOvermap({})", id)),
|
|
||||||
TileType::ToLocal(id) => console::log(format!("DESTINATION: ToLocal({})", id)),
|
|
||||||
_ => console::log("DESTINATION: Unknown"),
|
|
||||||
}
|
|
||||||
possible_destinations.push(((map.width * map.height) as usize) / 2); // Centre of map
|
|
||||||
}
|
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
|
||||||
let idx =
|
|
||||||
possible_destinations[(rng.roll_dice(1, possible_destinations.len() as i32) as usize) - 1];
|
|
||||||
let mut player_position = ecs.write_resource::<Point>();
|
|
||||||
*player_position = Point::new((idx as i32) % w, (idx as i32) / w);
|
|
||||||
let mut position_components = ecs.write_storage::<Position>();
|
|
||||||
let player_pos_component = position_components.get_mut(*player_entity);
|
|
||||||
if let Some(player_pos_component) = player_pos_component {
|
|
||||||
player_pos_component.x = (idx as i32) % w;
|
|
||||||
player_pos_component.y = (idx as i32) / w;
|
|
||||||
}
|
|
||||||
dungeon_master.store_map(&worldmap_resource);
|
|
||||||
*worldmap_resource = map;
|
|
||||||
// Dirtify viewsheds (forces refresh)
|
|
||||||
let mut viewshed_components = ecs.write_storage::<Viewshed>();
|
|
||||||
let mut telepath_components = ecs.write_storage::<Telepath>();
|
|
||||||
let vision_vs = viewshed_components.get_mut(*player_entity);
|
|
||||||
let telepath_vs = telepath_components.get_mut(*player_entity);
|
|
||||||
if let Some(vs) = vision_vs {
|
|
||||||
vs.dirty = true;
|
|
||||||
}
|
|
||||||
if let Some(vs) = telepath_vs {
|
|
||||||
vs.dirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transition_to_new_map(ecs: &mut World, new_id: i32, _dest_tile: TileType) -> Vec<Map> {
|
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
|
||||||
// Might need this to fallback to 1, but if player
|
|
||||||
// level isn't found at all, there's a bigger concern
|
|
||||||
// concern than just this function not working.
|
|
||||||
let player_level = gamelog::get_event_count(EVENT::COUNT_LEVEL);
|
|
||||||
let mut builder = map_builders::level_builder(new_id, &mut rng, 100, 50, player_level);
|
|
||||||
builder.build_map(&mut rng);
|
|
||||||
std::mem::drop(rng);
|
|
||||||
let mapgen_history = builder.build_data.history.clone();
|
|
||||||
let player_start;
|
|
||||||
let old_map: Map;
|
|
||||||
{
|
|
||||||
let mut worldmap_resource = ecs.write_resource::<Map>();
|
|
||||||
old_map = worldmap_resource.clone();
|
|
||||||
// If there is zero overmap involvement, place an upstair where we ended up.
|
|
||||||
// Otherwise, this should be hand-placed.
|
|
||||||
if !old_map.overmap && !builder.build_data.map.overmap {
|
|
||||||
if let Some(pos) = &builder.build_data.starting_position {
|
|
||||||
let up_idx = builder.build_data.map.xy_idx(pos.x, pos.y);
|
|
||||||
builder.build_data.map.tiles[up_idx] = TileType::UpStair;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*worldmap_resource = builder.build_data.map.clone();
|
|
||||||
// Unwrap so we get a CTD if there's no starting pos.
|
|
||||||
player_start = builder.build_data.starting_position.as_mut().unwrap().clone();
|
|
||||||
}
|
|
||||||
// Spawn entities
|
|
||||||
builder.spawn_entities(ecs);
|
|
||||||
// Place player and update resources
|
|
||||||
let mut player_position = ecs.write_resource::<Point>();
|
|
||||||
*player_position = Point::new(player_start.x, player_start.y);
|
|
||||||
let mut position_components = ecs.write_storage::<Position>();
|
|
||||||
let player_entity = ecs.fetch::<Entity>();
|
|
||||||
let player_pos_component = position_components.get_mut(*player_entity);
|
|
||||||
if let Some(player_pos_component) = player_pos_component {
|
|
||||||
player_pos_component.x = player_start.x;
|
|
||||||
player_pos_component.y = player_start.y;
|
|
||||||
}
|
|
||||||
// Mark viewshed as dirty (force refresh)
|
|
||||||
let mut viewshed_components = ecs.write_storage::<Viewshed>();
|
|
||||||
let mut telepath_components = ecs.write_storage::<Telepath>();
|
|
||||||
let vision_vs = viewshed_components.get_mut(*player_entity);
|
|
||||||
let telepath_vs = telepath_components.get_mut(*player_entity);
|
|
||||||
if let Some(vs) = vision_vs {
|
|
||||||
vs.dirty = true;
|
|
||||||
}
|
|
||||||
if let Some(vs) = telepath_vs {
|
|
||||||
vs.dirty = true;
|
|
||||||
}
|
|
||||||
// Store newly minted map
|
|
||||||
let mut dungeon_master = ecs.write_resource::<MasterDungeonMap>();
|
|
||||||
dungeon_master.store_map(&old_map);
|
|
||||||
dungeon_master.store_map(&builder.build_data.map);
|
|
||||||
return mapgen_history;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate through entities on the current level, save the current position and floor
|
|
||||||
/// of each entity-to-be-frozen, and then delete their current position.
|
|
||||||
pub fn freeze_entities(ecs: &mut World) {
|
|
||||||
// Obtain reqs from ECS
|
|
||||||
let entities = ecs.entities();
|
|
||||||
let mut positions = ecs.write_storage::<Position>();
|
|
||||||
let mut other_positions = ecs.write_storage::<OtherLevelPosition>();
|
|
||||||
let player_entity = ecs.fetch::<Entity>();
|
|
||||||
let map_id = ecs.fetch::<Map>().id;
|
|
||||||
// Save Positions and mark for deletion
|
|
||||||
let mut pos_to_delete: Vec<Entity> = Vec::new();
|
|
||||||
for (entity, pos) in (&entities, &positions).join() {
|
|
||||||
if entity != *player_entity {
|
|
||||||
other_positions
|
|
||||||
.insert(entity, OtherLevelPosition { x: pos.x, y: pos.y, id: map_id })
|
|
||||||
.expect("Failed to insert OtherLevelPosition");
|
|
||||||
pos_to_delete.push(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for p in pos_to_delete.iter() {
|
|
||||||
positions.remove(*p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate through entities, and insert a Position component if the
|
|
||||||
/// entity has an OtherLevelPosition for the new map id.
|
|
||||||
pub fn thaw_entities(ecs: &mut World) {
|
|
||||||
// Obtain reqs from ECS
|
|
||||||
let entities = ecs.entities();
|
|
||||||
let mut positions = ecs.write_storage::<Position>();
|
|
||||||
let mut other_positions = ecs.write_storage::<OtherLevelPosition>();
|
|
||||||
let player_entity = ecs.fetch::<Entity>();
|
|
||||||
let map_id = ecs.fetch::<Map>().id;
|
|
||||||
// Save Positions and mark for deletion
|
|
||||||
let mut pos_to_delete: Vec<Entity> = Vec::new();
|
|
||||||
for (entity, pos) in (&entities, &other_positions).join() {
|
|
||||||
if entity != *player_entity && pos.id == map_id {
|
|
||||||
positions
|
|
||||||
.insert(entity, Position { x: pos.x, y: pos.y })
|
|
||||||
.expect("Failed to insert OtherLevelPosition");
|
|
||||||
pos_to_delete.push(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for p in pos_to_delete.iter() {
|
|
||||||
other_positions.remove(*p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
use crate::{
|
|
||||||
config::CONFIG,
|
|
||||||
gamelog,
|
|
||||||
raws,
|
|
||||||
spawner,
|
|
||||||
Clock,
|
|
||||||
Map,
|
|
||||||
RandomNumberGenerator,
|
|
||||||
TakingTurn,
|
|
||||||
};
|
|
||||||
use specs::prelude::*;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use crate::data::events::*;
|
|
||||||
|
|
||||||
const TRY_SPAWN_CHANCE: i32 = 70;
|
|
||||||
const FEATURE_MESSAGE_CHANCE: i32 = 110;
|
|
||||||
|
|
||||||
pub fn maybe_map_message(ecs: &mut World) {
|
|
||||||
let mut maybe_message = false;
|
|
||||||
let map = ecs.fetch::<Map>();
|
|
||||||
if map.messages.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Scope for borrow checker (ECS)
|
|
||||||
{
|
|
||||||
let clock = ecs.read_storage::<Clock>();
|
|
||||||
let turns = ecs.read_storage::<TakingTurn>();
|
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
|
||||||
for (_c, _t) in (&clock, &turns).join() {
|
|
||||||
if rng.roll_dice(1, FEATURE_MESSAGE_CHANCE) == 1 {
|
|
||||||
maybe_message = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if maybe_message {
|
|
||||||
let mut logger = gamelog::Logger::new();
|
|
||||||
for message in map.messages.clone() {
|
|
||||||
logger = logger.append(message);
|
|
||||||
}
|
|
||||||
logger.log();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_spawn_interval(ecs: &mut World) {
|
|
||||||
let mut try_spawn = false;
|
|
||||||
// Scope for borrow checker (ECS)
|
|
||||||
{
|
|
||||||
let map = ecs.fetch::<Map>();
|
|
||||||
// Difficulty 0 maps shouldn't have respawning hostile mobs.
|
|
||||||
if map.difficulty == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let clock = ecs.read_storage::<Clock>();
|
|
||||||
let turns = ecs.read_storage::<TakingTurn>();
|
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
|
||||||
for (_c, _t) in (&clock, &turns).join() {
|
|
||||||
if rng.roll_dice(1, TRY_SPAWN_CHANCE) == 1 {
|
|
||||||
try_spawn = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if try_spawn {
|
|
||||||
if CONFIG.logging.log_spawning {
|
|
||||||
console::log("SPAWNINFO: Trying spawn.");
|
|
||||||
}
|
|
||||||
spawn_random_mob_in_free_nonvisible_tile(ecs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn_random_mob_in_free_nonvisible_tile(ecs: &mut World) {
|
|
||||||
let map = ecs.fetch::<Map>();
|
|
||||||
let mut available_tiles = populate_unblocked_nonvisible(&map);
|
|
||||||
let player_level = gamelog::get_event_count(EVENT::COUNT_LEVEL);
|
|
||||||
console::log(player_level);
|
|
||||||
let difficulty = (map.difficulty + player_level) / 2;
|
|
||||||
if available_tiles.len() == 0 {
|
|
||||||
if CONFIG.logging.log_spawning {
|
|
||||||
console::log("SPAWNINFO: No free tiles; not spawning anything..");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut spawn_locations: Vec<(i32, i32)> = Vec::new();
|
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
|
||||||
let key = spawner::mob_table(Some(difficulty)).roll(&mut rng);
|
|
||||||
let spawn_type = raws::get_mob_spawn_type(&raws::RAWS.lock().unwrap(), &key);
|
|
||||||
let roll = raws::get_mob_spawn_amount(&mut rng, &spawn_type, player_level);
|
|
||||||
for _i in 0..roll {
|
|
||||||
let idx = get_random_idx_from_tiles(&mut rng, &mut available_tiles);
|
|
||||||
spawn_locations.push(((idx as i32) % map.width, (idx as i32) / map.width));
|
|
||||||
}
|
|
||||||
// Dropping resources for borrow-checker.
|
|
||||||
std::mem::drop(map);
|
|
||||||
std::mem::drop(rng);
|
|
||||||
// For every idx in the spawn list, spawn mob.
|
|
||||||
for idx in spawn_locations {
|
|
||||||
if CONFIG.logging.log_spawning {
|
|
||||||
console::log(format!("SPAWNINFO: Spawning {} at {}, {}.", key, idx.0, idx.1));
|
|
||||||
}
|
|
||||||
raws::spawn_named_entity(
|
|
||||||
&raws::RAWS.lock().unwrap(),
|
|
||||||
ecs,
|
|
||||||
&key,
|
|
||||||
None,
|
|
||||||
raws::SpawnType::AtPosition { x: idx.0, y: idx.1 },
|
|
||||||
difficulty
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a Vec<usize> of every tile that is not blocked, and is not currently in the player's view.
|
|
||||||
fn populate_unblocked_nonvisible(map: &Map) -> Vec<usize> {
|
|
||||||
let mut tiles: Vec<usize> = Vec::new();
|
|
||||||
for (i, _tile) in map.tiles.iter().enumerate() {
|
|
||||||
if !crate::spatial::is_blocked(i) && !map.visible_tiles[i] {
|
|
||||||
tiles.push(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Picks a random index from a vector of indexes, and removes it from the vector.
|
|
||||||
fn get_random_idx_from_tiles(rng: &mut RandomNumberGenerator, area: &mut Vec<usize>) -> usize {
|
|
||||||
let idx = if area.len() == 1 {
|
|
||||||
0usize
|
|
||||||
} else {
|
|
||||||
(rng.roll_dice(1, area.len() as i32) - 1) as usize
|
|
||||||
};
|
|
||||||
area.remove(idx);
|
|
||||||
return area[idx];
|
|
||||||
}
|
|
||||||
182
src/map/mod.rs
182
src/map/mod.rs
|
|
@ -1,182 +0,0 @@
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use serde::{ Deserialize, Serialize };
|
|
||||||
use std::collections::{ HashSet, HashMap };
|
|
||||||
mod tiletype;
|
|
||||||
pub use tiletype::{ tile_cost, tile_opaque, tile_walkable, TileType, get_dest, Destination };
|
|
||||||
mod interval_spawning_system;
|
|
||||||
pub use interval_spawning_system::{ maybe_map_message, try_spawn_interval };
|
|
||||||
pub mod dungeon;
|
|
||||||
pub use dungeon::{ level_transition, MasterDungeonMap };
|
|
||||||
pub mod themes;
|
|
||||||
use super::data::visuals::{
|
|
||||||
BRIGHTEN_FG_COLOUR_BY,
|
|
||||||
GLOBAL_OFFSET_MIN_CLAMP,
|
|
||||||
GLOBAL_OFFSET_MAX_CLAMP,
|
|
||||||
};
|
|
||||||
|
|
||||||
// FIXME: If the map size gets too small, entities stop being rendered starting from the right.
|
|
||||||
// i.e. on a map size of 40*40, only entities to the left of the player are rendered.
|
|
||||||
// on a map size of 42*42, the player can see entities up to 2 tiles to their right.
|
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Map {
|
|
||||||
pub overmap: bool,
|
|
||||||
pub tiles: Vec<TileType>,
|
|
||||||
pub width: i32,
|
|
||||||
pub height: i32,
|
|
||||||
pub revealed_tiles: Vec<bool>,
|
|
||||||
pub visible_tiles: Vec<bool>,
|
|
||||||
pub lit_tiles: Vec<bool>,
|
|
||||||
pub telepath_tiles: Vec<bool>,
|
|
||||||
pub colour_offset: Vec<((f32, f32, f32), (f32, f32, f32))>,
|
|
||||||
pub additional_fg_offset: RGB,
|
|
||||||
pub id: i32,
|
|
||||||
pub name: String,
|
|
||||||
pub short_name: String,
|
|
||||||
pub depth: i32,
|
|
||||||
pub messages: HashSet<String>,
|
|
||||||
pub difficulty: i32,
|
|
||||||
pub bloodstains: HashMap<usize, RGB>,
|
|
||||||
pub view_blocked: HashSet<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Map {
|
|
||||||
pub fn xy_idx(&self, x: i32, y: i32) -> usize {
|
|
||||||
(y as usize) * (self.width as usize) + (x as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new<S: ToString>(
|
|
||||||
overmap: bool,
|
|
||||||
new_id: i32,
|
|
||||||
width: i32,
|
|
||||||
height: i32,
|
|
||||||
difficulty: i32,
|
|
||||||
name: S,
|
|
||||||
short_name: S,
|
|
||||||
depth: i32
|
|
||||||
) -> Map {
|
|
||||||
let map_tile_count = (width * height) as usize;
|
|
||||||
crate::spatial::set_size(map_tile_count);
|
|
||||||
let mut map = Map {
|
|
||||||
overmap: overmap,
|
|
||||||
tiles: vec![TileType::Wall; map_tile_count],
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
revealed_tiles: vec![false; map_tile_count],
|
|
||||||
visible_tiles: vec![false; map_tile_count],
|
|
||||||
lit_tiles: vec![true; map_tile_count], // NYI: Light sources. Once those exist, we can set this to false.
|
|
||||||
telepath_tiles: vec![false; map_tile_count],
|
|
||||||
colour_offset: vec![((0.0, 0.0, 0.0), (0.0, 0.0, 0.0)); map_tile_count],
|
|
||||||
additional_fg_offset: RGB::from_u8(
|
|
||||||
BRIGHTEN_FG_COLOUR_BY as u8,
|
|
||||||
BRIGHTEN_FG_COLOUR_BY as u8,
|
|
||||||
BRIGHTEN_FG_COLOUR_BY as u8
|
|
||||||
),
|
|
||||||
id: new_id,
|
|
||||||
name: name.to_string(),
|
|
||||||
short_name: short_name.to_string(),
|
|
||||||
messages: HashSet::new(),
|
|
||||||
depth: depth,
|
|
||||||
difficulty: difficulty,
|
|
||||||
bloodstains: HashMap::new(),
|
|
||||||
view_blocked: HashSet::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut rng = RandomNumberGenerator::new();
|
|
||||||
|
|
||||||
for idx in 0..map.colour_offset.len() {
|
|
||||||
map.colour_offset[idx].0 = (
|
|
||||||
rng.range(GLOBAL_OFFSET_MIN_CLAMP, GLOBAL_OFFSET_MAX_CLAMP),
|
|
||||||
rng.range(GLOBAL_OFFSET_MIN_CLAMP, GLOBAL_OFFSET_MAX_CLAMP),
|
|
||||||
rng.range(GLOBAL_OFFSET_MIN_CLAMP, GLOBAL_OFFSET_MAX_CLAMP),
|
|
||||||
);
|
|
||||||
map.colour_offset[idx].1 = (
|
|
||||||
rng.range(GLOBAL_OFFSET_MIN_CLAMP, GLOBAL_OFFSET_MAX_CLAMP),
|
|
||||||
rng.range(GLOBAL_OFFSET_MIN_CLAMP, GLOBAL_OFFSET_MAX_CLAMP),
|
|
||||||
rng.range(GLOBAL_OFFSET_MIN_CLAMP, GLOBAL_OFFSET_MAX_CLAMP),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Takes an index, and calculates if it can be entered.
|
|
||||||
fn is_exit_valid(&self, x: i32, y: i32) -> bool {
|
|
||||||
if x < 1 || x > self.width - 1 || y < 1 || y > self.height - 1 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let idx = self.xy_idx(x, y);
|
|
||||||
return !crate::spatial::is_blocked(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn populate_blocked(&mut self) {
|
|
||||||
crate::spatial::populate_blocked_from_map(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_content_index(&mut self) {
|
|
||||||
crate::spatial::clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Algorithm2D for Map {
|
|
||||||
fn dimensions(&self) -> Point {
|
|
||||||
Point::new(self.width, self.height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BaseMap for Map {
|
|
||||||
fn is_opaque(&self, idx: usize) -> bool {
|
|
||||||
let idx_u = idx as usize;
|
|
||||||
if idx_u > 0 && idx_u < self.tiles.len() {
|
|
||||||
return tile_opaque(self.tiles[idx_u]) || self.view_blocked.contains(&idx_u);
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_pathing_distance(&self, idx1: usize, idx2: usize) -> f32 {
|
|
||||||
let w = self.width as usize;
|
|
||||||
let p1 = Point::new(idx1 % w, idx1 / w);
|
|
||||||
let p2 = Point::new(idx2 % w, idx2 / w);
|
|
||||||
DistanceAlg::Pythagoras.distance2d(p1, p2)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Evaluate every possible exit from a given tile in a cardinal direction, and return it as a vector.
|
|
||||||
fn get_available_exits(&self, idx: usize) -> SmallVec<[(usize, f32); 10]> {
|
|
||||||
let mut exits = SmallVec::new();
|
|
||||||
let x = (idx as i32) % self.width;
|
|
||||||
let y = (idx as i32) / self.width;
|
|
||||||
let w = self.width as usize;
|
|
||||||
let tt = self.tiles[idx as usize];
|
|
||||||
|
|
||||||
// Cardinal directions
|
|
||||||
if self.is_exit_valid(x - 1, y) {
|
|
||||||
exits.push((idx - 1, tile_cost(tt)));
|
|
||||||
}
|
|
||||||
if self.is_exit_valid(x + 1, y) {
|
|
||||||
exits.push((idx + 1, tile_cost(tt)));
|
|
||||||
}
|
|
||||||
if self.is_exit_valid(x, y - 1) {
|
|
||||||
exits.push((idx - w, tile_cost(tt)));
|
|
||||||
}
|
|
||||||
if self.is_exit_valid(x, y + 1) {
|
|
||||||
exits.push((idx + w, tile_cost(tt)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Diagonals
|
|
||||||
if self.is_exit_valid(x - 1, y - 1) {
|
|
||||||
exits.push((idx - w - 1, tile_cost(tt) * 1.45));
|
|
||||||
}
|
|
||||||
if self.is_exit_valid(x + 1, y - 1) {
|
|
||||||
exits.push((idx - w + 1, tile_cost(tt) * 1.45));
|
|
||||||
}
|
|
||||||
if self.is_exit_valid(x - 1, y + 1) {
|
|
||||||
exits.push((idx + w - 1, tile_cost(tt) * 1.45));
|
|
||||||
}
|
|
||||||
if self.is_exit_valid(x + 1, y + 1) {
|
|
||||||
exits.push((idx + w + 1, tile_cost(tt) * 1.45));
|
|
||||||
}
|
|
||||||
|
|
||||||
exits
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,350 +0,0 @@
|
||||||
use super::{ Map, Point, TileType };
|
|
||||||
use crate::data::visuals::*;
|
|
||||||
use crate::config::CONFIG;
|
|
||||||
use crate::data::ids::*;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use std::ops::{ Add, Mul };
|
|
||||||
|
|
||||||
/// Gets the renderables for a tile, with darkening/offset/post-processing/etc. Passing a val for "debug" will ignore viewshed.
|
|
||||||
pub fn get_tile_renderables_for_id(
|
|
||||||
idx: usize,
|
|
||||||
map: &Map,
|
|
||||||
other_pos: Option<Point>,
|
|
||||||
debug: Option<bool>
|
|
||||||
) -> (FontCharType, RGB, RGB) {
|
|
||||||
let coloured_bg = CONFIG.visuals.use_coloured_tile_bg;
|
|
||||||
|
|
||||||
let (glyph, mut fg, mut bg, fg_offset, bg_offset) = match map.id {
|
|
||||||
ID_TOWN2 => get_forest_theme_renderables(idx, map, debug),
|
|
||||||
_ => get_default_theme_renderables(idx, map, debug),
|
|
||||||
};
|
|
||||||
|
|
||||||
// If one of the colours was left blank, make them the same.
|
|
||||||
let mut same_col: bool = false;
|
|
||||||
if fg == RGB::new() {
|
|
||||||
fg = bg;
|
|
||||||
same_col = true;
|
|
||||||
} else if bg == RGB::new() {
|
|
||||||
bg = fg;
|
|
||||||
same_col = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if same_col && coloured_bg {
|
|
||||||
fg = fg.add(map.additional_fg_offset);
|
|
||||||
}
|
|
||||||
if CONFIG.visuals.add_colour_variance {
|
|
||||||
fg = apply_colour_offset(fg, map, idx, fg_offset, true);
|
|
||||||
bg = if coloured_bg { apply_colour_offset(bg, map, idx, bg_offset, false) } else { bg };
|
|
||||||
}
|
|
||||||
|
|
||||||
if CONFIG.visuals.with_scanlines && WITH_SCANLINES_BRIGHTEN_AMOUNT > 0.0 {
|
|
||||||
fg = brighten_by(fg, WITH_SCANLINES_BRIGHTEN_AMOUNT);
|
|
||||||
bg = if coloured_bg { brighten_by(bg, WITH_SCANLINES_BRIGHTEN_AMOUNT) } else { bg };
|
|
||||||
}
|
|
||||||
let (mut multiplier, mut nonvisible, mut darken) = (1.0, false, false);
|
|
||||||
if !map.visible_tiles[idx] {
|
|
||||||
multiplier = if CONFIG.visuals.with_scanlines {
|
|
||||||
NON_VISIBLE_MULTIPLIER_IF_SCANLINES
|
|
||||||
} else {
|
|
||||||
NON_VISIBLE_MULTIPLIER
|
|
||||||
};
|
|
||||||
nonvisible = true;
|
|
||||||
}
|
|
||||||
if other_pos.is_some() && WITH_DARKEN_BY_DISTANCE && !nonvisible {
|
|
||||||
let distance = darken_by_distance(
|
|
||||||
Point::new((idx as i32) % map.width, (idx as i32) / map.width),
|
|
||||||
other_pos.unwrap()
|
|
||||||
);
|
|
||||||
multiplier = distance.clamp(
|
|
||||||
if CONFIG.visuals.with_scanlines {
|
|
||||||
NON_VISIBLE_MULTIPLIER_IF_SCANLINES
|
|
||||||
} else {
|
|
||||||
NON_VISIBLE_MULTIPLIER
|
|
||||||
},
|
|
||||||
1.0
|
|
||||||
);
|
|
||||||
darken = true;
|
|
||||||
}
|
|
||||||
if nonvisible || darken {
|
|
||||||
fg = fg.mul(multiplier);
|
|
||||||
bg = if coloured_bg { bg.mul(multiplier) } else { bg };
|
|
||||||
}
|
|
||||||
if !CONFIG.visuals.use_coloured_tile_bg {
|
|
||||||
bg = RGB::named(BLACK);
|
|
||||||
}
|
|
||||||
bg = apply_bloodstain_if_necessary(bg, map, idx);
|
|
||||||
|
|
||||||
return (glyph, fg, bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
pub fn get_default_theme_renderables(idx: usize, map: &Map, debug: Option<bool>) -> (FontCharType, RGB, RGB, (i32, i32, i32), (i32, i32, i32)) {
|
|
||||||
let glyph: FontCharType;
|
|
||||||
#[allow(unused_assignments)]
|
|
||||||
let mut fg: RGB = RGB::new();
|
|
||||||
#[allow(unused_assignments)]
|
|
||||||
let mut bg: RGB = RGB::new();
|
|
||||||
let mut offsets: (i32, i32, i32) = (0, 0, 0);
|
|
||||||
let mut bg_offsets: (i32, i32, i32) = (-1, -1, -1);
|
|
||||||
|
|
||||||
match map.tiles[idx] {
|
|
||||||
TileType::Floor => { glyph = to_cp437(FLOOR_GLYPH); fg = RGB::named(FLOOR_COLOUR); bg = RGB::named(DEFAULT_BG_COLOUR); offsets = FLOOR_OFFSETS; }
|
|
||||||
TileType::WoodFloor => { glyph = to_cp437(WOOD_FLOOR_GLYPH); bg = RGB::named(WOOD_FLOOR_COLOUR); offsets = WOOD_FLOOR_OFFSETS; }
|
|
||||||
TileType::Fence => { glyph = to_cp437(FENCE_GLYPH); fg = RGB::named(FENCE_FG_COLOUR); bg = RGB::named(FENCE_COLOUR); offsets = FENCE_OFFSETS; }
|
|
||||||
TileType::Wall => { let x = idx as i32 % map.width; let y = idx as i32 / map.width; glyph = wall_glyph(&*map, x, y, debug); fg = RGB::named(WALL_COLOUR); bg = RGB::named(DEFAULT_BG_COLOUR); offsets = WALL_OFFSETS; bg_offsets = DEFAULT_BG_OFFSETS }
|
|
||||||
TileType::DownStair => { glyph = to_cp437(DOWN_STAIR_GLYPH); fg = RGB::named(STAIR_COLOUR); bg = RGB::named(DEFAULT_BG_COLOUR); offsets = STAIR_OFFSETS;}
|
|
||||||
TileType::UpStair => { glyph = to_cp437(UP_STAIR_GLYPH); fg = RGB::named(STAIR_COLOUR); bg = RGB::named(DEFAULT_BG_COLOUR); offsets = STAIR_OFFSETS; }
|
|
||||||
TileType::Bridge => { glyph = to_cp437(BRIDGE_GLYPH); bg = RGB::named(BRIDGE_COLOUR); offsets = BRIDGE_OFFSETS; }
|
|
||||||
TileType::Gravel => { glyph = to_cp437(GRAVEL_GLYPH); bg = RGB::named(GRAVEL_COLOUR); offsets = GRAVEL_OFFSETS;}
|
|
||||||
TileType::Road => { glyph = to_cp437(ROAD_GLYPH); bg = RGB::named(ROAD_COLOUR); offsets = ROAD_OFFSETS;}
|
|
||||||
TileType::Grass => { glyph = to_cp437(GRASS_GLYPH); bg = RGB::named(GRASS_COLOUR); offsets = GRASS_OFFSETS; }
|
|
||||||
TileType::Foliage => { glyph = to_cp437(FOLIAGE_GLYPH); bg = RGB::named(FOLIAGE_COLOUR); offsets = FOLIAGE_OFFSETS; }
|
|
||||||
TileType::HeavyFoliage => { glyph = to_cp437(HEAVY_FOLIAGE_GLYPH); bg = RGB::named(HEAVY_FOLIAGE_COLOUR); offsets = HEAVY_FOLIAGE_OFFSETS; }
|
|
||||||
TileType::Sand => { glyph = to_cp437(SAND_GLYPH); bg = RGB::named(SAND_COLOUR); offsets = SAND_OFFSETS; }
|
|
||||||
TileType::ShallowWater => { glyph = to_cp437(SHALLOW_WATER_GLYPH); bg = RGB::named(SHALLOW_WATER_COLOUR); offsets = SHALLOW_WATER_OFFSETS; }
|
|
||||||
TileType::DeepWater => { glyph = to_cp437(DEEP_WATER_GLYPH); bg = RGB::named(DEEP_WATER_COLOUR); offsets = DEEP_WATER_OFFSETS; }
|
|
||||||
TileType::Bars => { glyph = to_cp437(BARS_GLYPH); fg = RGB::named(BARS_COLOUR); bg = RGB::named(FLOOR_COLOUR); }
|
|
||||||
TileType::ImpassableMountain => { glyph = to_cp437(IMPASSABLE_MOUNTAIN_GLYPH); bg = RGB::named(IMPASSABLE_MOUNTAIN_COLOUR); offsets = IMPASSABLE_MOUNTAIN_OFFSETS }
|
|
||||||
TileType::ToOvermap(_) => { glyph = to_cp437(TO_OVERMAP_GLYPH); fg = RGB::named(TO_OVERMAP_COLOUR); bg = RGB::named(GRASS_COLOUR); }
|
|
||||||
TileType::ToLocal(_) => { glyph = to_cp437(TO_TOWN_GLYPH); fg = RGB::named(TO_TOWN_COLOUR); bg = RGB::named(GRASS_COLOUR); }
|
|
||||||
}
|
|
||||||
if bg_offsets == (-1, -1, -1) {
|
|
||||||
bg_offsets = offsets;
|
|
||||||
}
|
|
||||||
return (glyph, fg, bg, offsets, bg_offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
fn get_forest_theme_renderables(idx:usize, map: &Map, debug: Option<bool>) -> (FontCharType, RGB, RGB, (i32, i32, i32), (i32, i32, i32)) {
|
|
||||||
let glyph;
|
|
||||||
#[allow(unused_assignments)]
|
|
||||||
let mut fg = RGB::new();
|
|
||||||
#[allow(unused_assignments)]
|
|
||||||
let mut bg = RGB::new();
|
|
||||||
let mut offsets: (i32, i32, i32) = (0, 0, 0);
|
|
||||||
let mut bg_offsets: (i32, i32, i32) = (-1, -1, -1);
|
|
||||||
|
|
||||||
match map.tiles[idx] {
|
|
||||||
TileType::Wall => { glyph = to_cp437(FOREST_WALL_GLYPH); fg = RGB::named(FOREST_WALL_COLOUR); bg = RGB::named(GRASS_COLOUR); offsets = GRASS_OFFSETS; }
|
|
||||||
TileType::Road => { glyph = to_cp437(ROAD_GLYPH); bg = RGB::named(ROAD_COLOUR); }
|
|
||||||
TileType::ShallowWater => { glyph = to_cp437(SHALLOW_WATER_GLYPH); bg = RGB::named(SHALLOW_WATER_COLOUR); offsets = SHALLOW_WATER_OFFSETS; }
|
|
||||||
_ => { (glyph, fg, _, offsets, _) = get_default_theme_renderables(idx, map, debug); bg = RGB::named(GRASS_COLOUR); bg_offsets = GRASS_OFFSETS; }
|
|
||||||
}
|
|
||||||
if bg_offsets == (-1, -1, -1) {
|
|
||||||
bg_offsets = offsets;
|
|
||||||
}
|
|
||||||
return (glyph, fg, bg, offsets, bg_offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_revealed_and_wall(map: &Map, x: i32, y: i32, debug: Option<bool>) -> bool {
|
|
||||||
let idx = map.xy_idx(x, y);
|
|
||||||
map.tiles[idx] == TileType::Wall &&
|
|
||||||
(if debug.is_none() { map.revealed_tiles[idx] } else { true })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wall_glyph(map: &Map, x: i32, y: i32, debug: Option<bool>) -> FontCharType {
|
|
||||||
if
|
|
||||||
x < 1 ||
|
|
||||||
x > map.width - 2 ||
|
|
||||||
y < 1 ||
|
|
||||||
y > map.height - (2 as i32) ||
|
|
||||||
!CONFIG.visuals.use_bitset_walls
|
|
||||||
{
|
|
||||||
return 35;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut mask: u8 = 0;
|
|
||||||
let diagonals_matter: Vec<u8> = vec![7, 11, 13, 14, 15];
|
|
||||||
|
|
||||||
if is_revealed_and_wall(map, x, y - 1, debug) {
|
|
||||||
// N
|
|
||||||
mask += 1;
|
|
||||||
}
|
|
||||||
if is_revealed_and_wall(map, x, y + 1, debug) {
|
|
||||||
// S
|
|
||||||
mask += 2;
|
|
||||||
}
|
|
||||||
if is_revealed_and_wall(map, x - 1, y, debug) {
|
|
||||||
// W
|
|
||||||
mask += 4;
|
|
||||||
}
|
|
||||||
if is_revealed_and_wall(map, x + 1, y, debug) {
|
|
||||||
// E
|
|
||||||
mask += 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
if diagonals_matter.contains(&mask) {
|
|
||||||
if is_revealed_and_wall(map, x + 1, y - 1, debug) {
|
|
||||||
// Top right
|
|
||||||
mask += 16;
|
|
||||||
}
|
|
||||||
if is_revealed_and_wall(map, x - 1, y - 1, debug) {
|
|
||||||
// Top left
|
|
||||||
mask += 32;
|
|
||||||
}
|
|
||||||
if is_revealed_and_wall(map, x + 1, y + 1, debug) {
|
|
||||||
// Bottom right
|
|
||||||
mask += 64;
|
|
||||||
}
|
|
||||||
if is_revealed_and_wall(map, x - 1, y + 1, debug) {
|
|
||||||
// Bottom left
|
|
||||||
mask += 128;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match mask {
|
|
||||||
0 => 254, // ■ (254) square pillar; but maybe ○ (9) looks better
|
|
||||||
1 => 186, // Wall only to the north
|
|
||||||
2 => 186, // Wall only to the south
|
|
||||||
3 => 186, // Wall to the north and south
|
|
||||||
4 => 205, // Wall only to the west
|
|
||||||
5 => 188, // Wall to the north and west
|
|
||||||
6 => 187, // Wall to the south and west
|
|
||||||
7 => 185, // Wall to the north, south and west
|
|
||||||
8 => 205, // Wall only to the east
|
|
||||||
9 => 200, // Wall to the north and east
|
|
||||||
10 => 201, // Wall to the south and east
|
|
||||||
11 => 204, // Wall to the north, south and east
|
|
||||||
12 => 205, // Wall to the east and west
|
|
||||||
13 => 202, // Wall to the east, west, and north
|
|
||||||
14 => 203, // Wall to the east, west, and south
|
|
||||||
15 => 206, // ╬ Wall on all sides
|
|
||||||
29 => 202,
|
|
||||||
31 => 206,
|
|
||||||
45 => 202,
|
|
||||||
46 => 203,
|
|
||||||
47 => 206,
|
|
||||||
55 => 185,
|
|
||||||
59 => 204,
|
|
||||||
63 => 203,
|
|
||||||
87 => 185,
|
|
||||||
126 => 203,
|
|
||||||
143 => 206,
|
|
||||||
77 => 202,
|
|
||||||
171 => 204,
|
|
||||||
187 => 204,
|
|
||||||
215 => 185,
|
|
||||||
190 => 203,
|
|
||||||
237 => 202,
|
|
||||||
30 => 203,
|
|
||||||
110 => 203,
|
|
||||||
111 => 206,
|
|
||||||
119 => 185,
|
|
||||||
142 => 203,
|
|
||||||
158 => 203,
|
|
||||||
235 => 204,
|
|
||||||
93 => 202,
|
|
||||||
109 => 202,
|
|
||||||
94 => 203,
|
|
||||||
174 => 203,
|
|
||||||
159 => 206,
|
|
||||||
221 => 202,
|
|
||||||
157 => 202,
|
|
||||||
79 => 206,
|
|
||||||
95 => 185,
|
|
||||||
23 => 185, // NSW and NSE + 1 diagonal
|
|
||||||
39 => 185,
|
|
||||||
71 => 185,
|
|
||||||
103 => 185,
|
|
||||||
135 => 185,
|
|
||||||
151 => 185,
|
|
||||||
199 => 185,
|
|
||||||
78 => 203,
|
|
||||||
27 => 204,
|
|
||||||
43 => 204,
|
|
||||||
75 => 204,
|
|
||||||
107 => 204,
|
|
||||||
139 => 204,
|
|
||||||
155 => 204,
|
|
||||||
173 => 202,
|
|
||||||
141 => 202,
|
|
||||||
205 => 202,
|
|
||||||
175 => 204,
|
|
||||||
203 => 204,
|
|
||||||
61 => 205, // NEW cases
|
|
||||||
125 => 205, // NEW cases
|
|
||||||
189 => 205, // NEW cases
|
|
||||||
206 => 205,
|
|
||||||
207 => 202,
|
|
||||||
222 => 205,
|
|
||||||
238 => 205,
|
|
||||||
253 => 205,
|
|
||||||
254 => 205,
|
|
||||||
167 => 186, // NSW, NW, SW
|
|
||||||
91 => 186, // NSE, NE, SE
|
|
||||||
183 => 186, // NSW, NW, SW, NE
|
|
||||||
123 => 186, // NSE, NE, SE, NW
|
|
||||||
231 => 186, // NSW, NW, SW, SE
|
|
||||||
219 => 186, // NSE, NE, SE, SW
|
|
||||||
247 => 186,
|
|
||||||
251 => 186,
|
|
||||||
127 => 187, // Everything except NE
|
|
||||||
191 => 201, // Everything except NW
|
|
||||||
223 => 188, // Everything except SE
|
|
||||||
239 => 200, // Everything except SW
|
|
||||||
_ => 35, // We missed one?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_colour_offset(
|
|
||||||
mut rgb: RGB,
|
|
||||||
map: &Map,
|
|
||||||
idx: usize,
|
|
||||||
offset: (i32, i32, i32),
|
|
||||||
fg: bool
|
|
||||||
) -> RGB {
|
|
||||||
let offset_mod = if fg { map.colour_offset[idx].0 } else { map.colour_offset[idx].1 };
|
|
||||||
let offset = (
|
|
||||||
(offset.0 as f32) * offset_mod.0,
|
|
||||||
(offset.1 as f32) * offset_mod.1,
|
|
||||||
(offset.2 as f32) * offset_mod.2,
|
|
||||||
);
|
|
||||||
rgb = add_i32_offsets(rgb, offset);
|
|
||||||
return rgb;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_bloodstain_if_necessary(mut bg: RGB, map: &Map, idx: usize) -> RGB {
|
|
||||||
if map.bloodstains.contains_key(&idx) {
|
|
||||||
bg = bg.add(map.bloodstains[&idx]);
|
|
||||||
}
|
|
||||||
return bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_i32_offsets(rgb: RGB, offsets: (f32, f32, f32)) -> RGB {
|
|
||||||
let r = rgb.r + (offsets.0 as f32) / 255.0;
|
|
||||||
let g = rgb.g + (offsets.1 as f32) / 255.0;
|
|
||||||
let b = rgb.b + (offsets.2 as f32) / 255.0;
|
|
||||||
|
|
||||||
return RGB::from_f32(r, g, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn multiply_by_float(rgb: RGB, offsets: (f32, f32, f32)) -> RGB {
|
|
||||||
let r = rgb.r * offsets.0;
|
|
||||||
let g = rgb.g * offsets.1;
|
|
||||||
let b = rgb.b * offsets.2;
|
|
||||||
|
|
||||||
return RGB::from_f32(r, g, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn darken_by_distance(pos: Point, other_pos: Point) -> f32 {
|
|
||||||
let distance = DistanceAlg::Pythagoras.distance2d(pos, other_pos) as f32; // Get distance in tiles.
|
|
||||||
let interp_factor =
|
|
||||||
(distance - START_DARKEN_AT_N_TILES) /
|
|
||||||
((crate::data::entity::DEFAULT_VIEWSHED_STANDARD as f32) - START_DARKEN_AT_N_TILES);
|
|
||||||
let interp_factor = interp_factor.max(0.0).min(1.0); // Clamp [0-1]
|
|
||||||
let result =
|
|
||||||
1.0 -
|
|
||||||
interp_factor *
|
|
||||||
(1.0 -
|
|
||||||
(if CONFIG.visuals.with_scanlines {
|
|
||||||
MAX_DARKENING_IF_SCANLINES
|
|
||||||
} else {
|
|
||||||
MAX_DARKENING
|
|
||||||
}));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn brighten_by(mut rgb: RGB, amount: f32) -> RGB {
|
|
||||||
rgb = rgb.add(RGB::from_f32(amount, amount, amount));
|
|
||||||
return rgb;
|
|
||||||
}
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
use serde::{ Deserialize, Serialize };
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize, Debug)]
|
|
||||||
pub enum TileType {
|
|
||||||
// Walls (opaque)
|
|
||||||
ImpassableMountain,
|
|
||||||
Wall,
|
|
||||||
// Impassable (transparent)
|
|
||||||
DeepWater,
|
|
||||||
Fence,
|
|
||||||
Bars,
|
|
||||||
// Floors (walkable)
|
|
||||||
Floor,
|
|
||||||
WoodFloor,
|
|
||||||
Gravel,
|
|
||||||
Road,
|
|
||||||
Grass,
|
|
||||||
Foliage,
|
|
||||||
HeavyFoliage,
|
|
||||||
Sand,
|
|
||||||
ShallowWater,
|
|
||||||
Bridge,
|
|
||||||
// Stairs (changes floor)
|
|
||||||
DownStair,
|
|
||||||
UpStair,
|
|
||||||
// To/From Overmap - ids are in src/data/ids.rs, are used in try_change_level() in src/player.rs
|
|
||||||
ToOvermap(i32),
|
|
||||||
ToLocal(i32),
|
|
||||||
}
|
|
||||||
pub fn tile_walkable(tt: TileType) -> bool {
|
|
||||||
match tt {
|
|
||||||
TileType::ImpassableMountain | TileType::Wall | TileType::DeepWater | TileType::Fence | TileType::Bars => false,
|
|
||||||
_ => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn tile_opaque(tt: TileType) -> bool {
|
|
||||||
match tt {
|
|
||||||
TileType::ImpassableMountain => true,
|
|
||||||
TileType::Wall => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn tile_cost(tt: TileType) -> f32 {
|
|
||||||
match tt {
|
|
||||||
TileType::Road => 0.75,
|
|
||||||
TileType::Grass => 1.2,
|
|
||||||
TileType::ShallowWater => 1.5,
|
|
||||||
_ => 1.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn get_dest(this_tile: TileType, backtracking: bool) -> Destination {
|
|
||||||
let result = if !backtracking {
|
|
||||||
match this_tile {
|
|
||||||
// If on downstair, GOTO next level, and end up on an upstair
|
|
||||||
TileType::DownStair => Destination::NextLevel,
|
|
||||||
// If on overmap ToLocal tile, GOTO local map, and end up on an overmap ToOvermap tile with corresponding ID
|
|
||||||
TileType::ToLocal(id) => Destination::ToOvermap(id),
|
|
||||||
_ => Destination::None,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match this_tile {
|
|
||||||
TileType::UpStair => Destination::PreviousLevel,
|
|
||||||
TileType::ToOvermap(id) => Destination::ToLocal(id),
|
|
||||||
_ => Destination::None,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Destination {
|
|
||||||
PreviousLevel,
|
|
||||||
NextLevel,
|
|
||||||
ToOvermap(i32),
|
|
||||||
ToLocal(i32),
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
use super::{ BuilderMap, MetaMapBuilder, Position };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum XStart {
|
|
||||||
LEFT,
|
|
||||||
CENTRE,
|
|
||||||
RIGHT,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum YStart {
|
|
||||||
TOP,
|
|
||||||
CENTRE,
|
|
||||||
BOTTOM,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AreaStartingPosition {
|
|
||||||
x: XStart,
|
|
||||||
y: YStart,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MetaMapBuilder for AreaStartingPosition {
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
self.build(rng, build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AreaStartingPosition {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn new(x: XStart, y: YStart) -> Box<AreaStartingPosition> {
|
|
||||||
Box::new(AreaStartingPosition { x, y })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
let seed_x;
|
|
||||||
let seed_y;
|
|
||||||
|
|
||||||
match self.x {
|
|
||||||
XStart::LEFT => {
|
|
||||||
seed_x = 1;
|
|
||||||
}
|
|
||||||
XStart::CENTRE => {
|
|
||||||
seed_x = build_data.map.width / 2;
|
|
||||||
}
|
|
||||||
XStart::RIGHT => {
|
|
||||||
seed_x = build_data.map.width - 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match self.y {
|
|
||||||
YStart::TOP => {
|
|
||||||
seed_y = 1;
|
|
||||||
}
|
|
||||||
YStart::CENTRE => {
|
|
||||||
seed_y = build_data.map.height / 2;
|
|
||||||
}
|
|
||||||
YStart::BOTTOM => {
|
|
||||||
seed_y = build_data.map.height - 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut available_floors: Vec<(usize, f32)> = Vec::new();
|
|
||||||
for (idx, tiletype) in build_data.map.tiles.iter().enumerate() {
|
|
||||||
if crate::tile_walkable(*tiletype) {
|
|
||||||
available_floors.push((
|
|
||||||
idx,
|
|
||||||
DistanceAlg::PythagorasSquared.distance2d(
|
|
||||||
Point::new(
|
|
||||||
(idx as i32) % build_data.map.width,
|
|
||||||
(idx as i32) / build_data.map.width
|
|
||||||
),
|
|
||||||
Point::new(seed_x, seed_y)
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if available_floors.is_empty() {
|
|
||||||
unreachable!("No valid floors to start on.");
|
|
||||||
}
|
|
||||||
|
|
||||||
available_floors.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
|
|
||||||
|
|
||||||
let start_x = (available_floors[0].0 as i32) % build_data.map.width;
|
|
||||||
let start_y = (available_floors[0].0 as i32) / build_data.map.width;
|
|
||||||
|
|
||||||
build_data.starting_position = Some(Position { x: start_x, y: start_y });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
use super::{ BuilderMap, InitialMapBuilder, Rect, TileType };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
pub struct BspDungeonBuilder {
|
|
||||||
rects: Vec<Rect>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InitialMapBuilder for BspDungeonBuilder {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
self.build(rng, build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BspDungeonBuilder {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn new() -> Box<BspDungeonBuilder> {
|
|
||||||
Box::new(BspDungeonBuilder { rects: Vec::new() })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
let mut rooms: Vec<Rect> = Vec::new();
|
|
||||||
self.rects.clear();
|
|
||||||
self.rects.push(Rect::with_size(2, 2, build_data.map.width - 5, build_data.map.height - 5)); // Start with a single map-sized rectangle
|
|
||||||
let first_room = self.rects[0];
|
|
||||||
self.add_subrects(first_room); // Divide the first room
|
|
||||||
|
|
||||||
// Up to 240 times, we get a random rectangle and divide it. If its possible to squeeze a
|
|
||||||
// room in there, we place it and add it to the rooms list.
|
|
||||||
let mut n_rooms = 0;
|
|
||||||
while n_rooms < 240 {
|
|
||||||
let rect = self.get_random_rect(rng);
|
|
||||||
let candidate = self.get_random_sub_rect(rect, rng);
|
|
||||||
|
|
||||||
if self.is_possible(candidate, &build_data, &rooms) {
|
|
||||||
rooms.push(candidate);
|
|
||||||
self.add_subrects(rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
n_rooms += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
build_data.rooms = Some(rooms);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_subrects(&mut self, rect: Rect) {
|
|
||||||
let width = i32::abs(rect.x1 - rect.x2);
|
|
||||||
let height = i32::abs(rect.y1 - rect.y2);
|
|
||||||
let half_width = i32::max(width / 2, 1);
|
|
||||||
let half_height = i32::max(height / 2, 1);
|
|
||||||
|
|
||||||
self.rects.push(Rect::with_size(rect.x1, rect.y1, half_width, half_height));
|
|
||||||
self.rects.push(Rect::with_size(rect.x1, rect.y1 + half_height, half_width, half_height));
|
|
||||||
self.rects.push(Rect::with_size(rect.x1 + half_width, rect.y1, half_width, half_height));
|
|
||||||
self.rects.push(
|
|
||||||
Rect::with_size(rect.x1 + half_width, rect.y1 + half_height, half_width, half_height)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_random_rect(&mut self, rng: &mut RandomNumberGenerator) -> Rect {
|
|
||||||
if self.rects.len() == 1 {
|
|
||||||
return self.rects[0];
|
|
||||||
}
|
|
||||||
let idx = (rng.roll_dice(1, self.rects.len() as i32) - 1) as usize;
|
|
||||||
self.rects[idx]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_random_sub_rect(&self, rect: Rect, rng: &mut RandomNumberGenerator) -> Rect {
|
|
||||||
let mut result = rect;
|
|
||||||
let rect_width = i32::abs(rect.x1 - rect.x2);
|
|
||||||
let rect_height = i32::abs(rect.y1 - rect.y2);
|
|
||||||
|
|
||||||
let w = i32::max(3, rng.roll_dice(1, i32::min(rect_width, 10)) - 1) + 1;
|
|
||||||
let h = i32::max(3, rng.roll_dice(1, i32::min(rect_height, 10)) - 1) + 1;
|
|
||||||
|
|
||||||
result.x1 += rng.roll_dice(1, 6) - 1;
|
|
||||||
result.y1 += rng.roll_dice(1, 6) - 1;
|
|
||||||
result.x2 = result.x1 + w;
|
|
||||||
result.y2 = result.y1 + h;
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_possible(&self, rect: Rect, build_data: &BuilderMap, rooms: &Vec<Rect>) -> bool {
|
|
||||||
let mut expanded = rect;
|
|
||||||
expanded.x1 -= 2;
|
|
||||||
expanded.x2 += 2;
|
|
||||||
expanded.y1 -= 2;
|
|
||||||
expanded.y2 += 2;
|
|
||||||
|
|
||||||
let mut can_build = true;
|
|
||||||
|
|
||||||
for r in rooms.iter() {
|
|
||||||
if r.intersect(&rect) {
|
|
||||||
can_build = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for y in expanded.y1..=expanded.y2 {
|
|
||||||
for x in expanded.x1..=expanded.x2 {
|
|
||||||
if x > build_data.map.width - 2 {
|
|
||||||
can_build = false;
|
|
||||||
}
|
|
||||||
if y > build_data.map.height - 2 {
|
|
||||||
can_build = false;
|
|
||||||
}
|
|
||||||
if x < 1 {
|
|
||||||
can_build = false;
|
|
||||||
}
|
|
||||||
if y < 1 {
|
|
||||||
can_build = false;
|
|
||||||
}
|
|
||||||
if can_build {
|
|
||||||
let idx = build_data.map.xy_idx(x, y);
|
|
||||||
if build_data.map.tiles[idx] != TileType::Wall {
|
|
||||||
can_build = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return can_build;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
use super::{ draw_corridor, BuilderMap, InitialMapBuilder, Rect, TileType };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
const MIN_ROOM_SIZE: i32 = 8;
|
|
||||||
|
|
||||||
pub struct BspInteriorBuilder {
|
|
||||||
rects: Vec<Rect>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InitialMapBuilder for BspInteriorBuilder {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
self.build(rng, build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BspInteriorBuilder {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn new() -> Box<BspInteriorBuilder> {
|
|
||||||
Box::new(BspInteriorBuilder { rects: Vec::new() })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
let mut rooms: Vec<Rect> = Vec::new();
|
|
||||||
self.rects.clear();
|
|
||||||
self.rects.push(Rect::with_size(1, 1, build_data.map.width - 2, build_data.map.height - 2)); // Start with a single map-sized rectangle
|
|
||||||
let first_room = self.rects[0];
|
|
||||||
self.add_subrects(first_room, rng); // Divide the first room
|
|
||||||
|
|
||||||
let rooms_copy = self.rects.clone();
|
|
||||||
for r in rooms_copy.iter() {
|
|
||||||
let room = *r;
|
|
||||||
//room.x2 -= 1;
|
|
||||||
//room.y2 -= 1;
|
|
||||||
rooms.push(room);
|
|
||||||
for y in room.y1..room.y2 {
|
|
||||||
for x in room.x1..room.x2 {
|
|
||||||
let idx = build_data.map.xy_idx(x, y);
|
|
||||||
if
|
|
||||||
idx > 0 &&
|
|
||||||
idx < ((build_data.map.width * build_data.map.height - 1) as usize)
|
|
||||||
{
|
|
||||||
build_data.map.tiles[idx] = TileType::Floor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
build_data.take_snapshot();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we want corridors
|
|
||||||
for i in 0..rooms.len() - 1 {
|
|
||||||
let room = rooms[i];
|
|
||||||
let next_room = rooms[i + 1];
|
|
||||||
let start_x = room.x1 + (rng.roll_dice(1, i32::abs(room.x1 - room.x2)) - 1);
|
|
||||||
let start_y = room.y1 + (rng.roll_dice(1, i32::abs(room.y1 - room.y2)) - 1);
|
|
||||||
let end_x =
|
|
||||||
next_room.x1 + (rng.roll_dice(1, i32::abs(next_room.x1 - next_room.x2)) - 1);
|
|
||||||
let end_y =
|
|
||||||
next_room.y1 + (rng.roll_dice(1, i32::abs(next_room.y1 - next_room.y2)) - 1);
|
|
||||||
draw_corridor(&mut build_data.map, start_x, start_y, end_x, end_y);
|
|
||||||
build_data.take_snapshot();
|
|
||||||
}
|
|
||||||
|
|
||||||
build_data.rooms = Some(rooms);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_subrects(&mut self, rect: Rect, rng: &mut RandomNumberGenerator) {
|
|
||||||
// Remove the last rect from the list
|
|
||||||
if !self.rects.is_empty() {
|
|
||||||
self.rects.remove(self.rects.len() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate boundaries
|
|
||||||
let width = rect.x2 - rect.x1;
|
|
||||||
let height = rect.y2 - rect.y1;
|
|
||||||
let half_width = width / 2;
|
|
||||||
let half_height = height / 2;
|
|
||||||
|
|
||||||
let split = rng.roll_dice(1, 4);
|
|
||||||
|
|
||||||
if split <= 2 {
|
|
||||||
// Horizontal split
|
|
||||||
let h1 = Rect::with_size(rect.x1, rect.y1, half_width - 1, height);
|
|
||||||
self.rects.push(h1);
|
|
||||||
if half_width > MIN_ROOM_SIZE {
|
|
||||||
self.add_subrects(h1, rng);
|
|
||||||
}
|
|
||||||
let h2 = Rect::with_size(rect.x1 + half_width, rect.y1, half_width, height);
|
|
||||||
self.rects.push(h2);
|
|
||||||
if half_width > MIN_ROOM_SIZE {
|
|
||||||
self.add_subrects(h2, rng);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Vertical split
|
|
||||||
let v1 = Rect::with_size(rect.x1, rect.y1, width, half_height - 1);
|
|
||||||
self.rects.push(v1);
|
|
||||||
if half_height > MIN_ROOM_SIZE {
|
|
||||||
self.add_subrects(v1, rng);
|
|
||||||
}
|
|
||||||
let v2 = Rect::with_size(rect.x1, rect.y1 + half_height, width, half_height);
|
|
||||||
self.rects.push(v2);
|
|
||||||
if half_height > MIN_ROOM_SIZE {
|
|
||||||
self.add_subrects(v2, rng);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
use super::{ BuilderMap, InitialMapBuilder, MetaMapBuilder, TileType };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
pub struct CellularAutomataBuilder {
|
|
||||||
floor_tile: TileType,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InitialMapBuilder for CellularAutomataBuilder {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
self.build(rng, build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MetaMapBuilder for CellularAutomataBuilder {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn build_map(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
self.apply_iteration(build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CellularAutomataBuilder {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn new() -> Box<CellularAutomataBuilder> {
|
|
||||||
Box::new(CellularAutomataBuilder { floor_tile: TileType::Floor })
|
|
||||||
}
|
|
||||||
pub fn floor(floor_tile: TileType) -> Box<CellularAutomataBuilder> {
|
|
||||||
Box::new(CellularAutomataBuilder { floor_tile })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::map_entry)]
|
|
||||||
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
// First we completely randomize the map, setting 55% of it to be floor.
|
|
||||||
for y in 1..build_data.map.height - 1 {
|
|
||||||
for x in 1..build_data.map.width - 1 {
|
|
||||||
let roll = rng.roll_dice(1, 100);
|
|
||||||
let idx = build_data.map.xy_idx(x, y);
|
|
||||||
if roll > 55 {
|
|
||||||
build_data.map.tiles[idx] = TileType::Floor;
|
|
||||||
} else {
|
|
||||||
build_data.map.tiles[idx] = TileType::Wall;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
build_data.take_snapshot();
|
|
||||||
|
|
||||||
// Now we iteratively apply cellular automata rules
|
|
||||||
for _i in 0..15 {
|
|
||||||
self.apply_iteration(build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_iteration(&mut self, build_data: &mut BuilderMap) {
|
|
||||||
let mut newtiles = build_data.map.tiles.clone();
|
|
||||||
|
|
||||||
for y in 1..build_data.map.height - 1 {
|
|
||||||
for x in 1..build_data.map.width - 1 {
|
|
||||||
let idx = build_data.map.xy_idx(x, y);
|
|
||||||
let mut neighbors = 0;
|
|
||||||
if build_data.map.tiles[idx - 1] == TileType::Wall {
|
|
||||||
neighbors += 1;
|
|
||||||
}
|
|
||||||
if build_data.map.tiles[idx + 1] == TileType::Wall {
|
|
||||||
neighbors += 1;
|
|
||||||
}
|
|
||||||
if build_data.map.tiles[idx - (build_data.map.width as usize)] == TileType::Wall {
|
|
||||||
neighbors += 1;
|
|
||||||
}
|
|
||||||
if build_data.map.tiles[idx + (build_data.map.width as usize)] == TileType::Wall {
|
|
||||||
neighbors += 1;
|
|
||||||
}
|
|
||||||
if
|
|
||||||
build_data.map.tiles[idx - ((build_data.map.width as usize) - 1)] ==
|
|
||||||
TileType::Wall
|
|
||||||
{
|
|
||||||
neighbors += 1;
|
|
||||||
}
|
|
||||||
if
|
|
||||||
build_data.map.tiles[idx - ((build_data.map.width as usize) + 1)] ==
|
|
||||||
TileType::Wall
|
|
||||||
{
|
|
||||||
neighbors += 1;
|
|
||||||
}
|
|
||||||
if
|
|
||||||
build_data.map.tiles[idx + ((build_data.map.width as usize) - 1)] ==
|
|
||||||
TileType::Wall
|
|
||||||
{
|
|
||||||
neighbors += 1;
|
|
||||||
}
|
|
||||||
if
|
|
||||||
build_data.map.tiles[idx + ((build_data.map.width as usize) + 1)] ==
|
|
||||||
TileType::Wall
|
|
||||||
{
|
|
||||||
neighbors += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if neighbors > 4 || neighbors == 0 {
|
|
||||||
newtiles[idx] = TileType::Wall;
|
|
||||||
} else {
|
|
||||||
newtiles[idx] = self.floor_tile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
build_data.map.tiles = newtiles.clone();
|
|
||||||
build_data.take_snapshot();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
use super::{ Map, Rect, TileType };
|
|
||||||
use std::cmp::{ max, min };
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn apply_room_to_map(map: &mut Map, room: &Rect) {
|
|
||||||
for y in room.y1 + 1..=room.y2 {
|
|
||||||
for x in room.x1 + 1..=room.x2 {
|
|
||||||
let idx = map.xy_idx(x, y);
|
|
||||||
map.tiles[idx] = TileType::Floor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_horizontal_tunnel(map: &mut Map, x1: i32, x2: i32, y: i32) -> Vec<usize> {
|
|
||||||
let mut corridor = Vec::new();
|
|
||||||
for x in min(x1, x2)..=max(x1, x2) {
|
|
||||||
let idx = map.xy_idx(x, y);
|
|
||||||
if idx > 0 && idx < (map.width as usize) * (map.height as usize) {
|
|
||||||
map.tiles[idx as usize] = TileType::Floor;
|
|
||||||
corridor.push(idx as usize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return corridor;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_vertical_tunnel(map: &mut Map, y1: i32, y2: i32, x: i32) -> Vec<usize> {
|
|
||||||
let mut corridor = Vec::new();
|
|
||||||
for y in min(y1, y2)..=max(y1, y2) {
|
|
||||||
let idx = map.xy_idx(x, y);
|
|
||||||
if idx > 0 && idx < (map.width as usize) * (map.height as usize) {
|
|
||||||
map.tiles[idx as usize] = TileType::Floor;
|
|
||||||
corridor.push(idx as usize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return corridor;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_corridor(map: &mut Map, x1: i32, y1: i32, x2: i32, y2: i32) -> Vec<usize> {
|
|
||||||
let mut corridor = Vec::new();
|
|
||||||
let mut x = x1;
|
|
||||||
let mut y = y1;
|
|
||||||
|
|
||||||
while x != x2 || y != y2 {
|
|
||||||
if x < x2 {
|
|
||||||
x += 1;
|
|
||||||
} else if x > x2 {
|
|
||||||
x -= 1;
|
|
||||||
} else if y < y2 {
|
|
||||||
y += 1;
|
|
||||||
} else if y > y2 {
|
|
||||||
y -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let idx = map.xy_idx(x, y);
|
|
||||||
if map.tiles[idx] != TileType::Floor {
|
|
||||||
map.tiles[idx] = TileType::Floor;
|
|
||||||
corridor.push(idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return corridor;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(PartialEq, Copy, Clone)]
|
|
||||||
pub enum Symmetry {
|
|
||||||
None,
|
|
||||||
Horizontal,
|
|
||||||
Vertical,
|
|
||||||
Both,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn paint(map: &mut Map, mode: Symmetry, brush_size: i32, x: i32, y: i32) {
|
|
||||||
match mode {
|
|
||||||
Symmetry::None => apply_paint(map, brush_size, x, y),
|
|
||||||
Symmetry::Horizontal => {
|
|
||||||
let centre_x = map.width / 2;
|
|
||||||
if x == centre_x {
|
|
||||||
apply_paint(map, brush_size, x, y);
|
|
||||||
} else {
|
|
||||||
let dist_x = i32::abs(centre_x - x);
|
|
||||||
apply_paint(map, brush_size, centre_x + dist_x, y);
|
|
||||||
apply_paint(map, brush_size, centre_x - dist_x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Symmetry::Vertical => {
|
|
||||||
let centre_y = map.height / 2;
|
|
||||||
if y == centre_y {
|
|
||||||
apply_paint(map, brush_size, x, y);
|
|
||||||
} else {
|
|
||||||
let dist_y = i32::abs(centre_y - y);
|
|
||||||
apply_paint(map, brush_size, x, centre_y + dist_y);
|
|
||||||
apply_paint(map, brush_size, x, centre_y - dist_y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Symmetry::Both => {
|
|
||||||
let centre_x = map.width / 2;
|
|
||||||
let centre_y = map.height / 2;
|
|
||||||
if x == centre_x && y == centre_y {
|
|
||||||
apply_paint(map, brush_size, x, y);
|
|
||||||
} else {
|
|
||||||
let dist_x = i32::abs(centre_x - x);
|
|
||||||
apply_paint(map, brush_size, centre_x + dist_x, y);
|
|
||||||
apply_paint(map, brush_size, centre_x - dist_x, y);
|
|
||||||
let dist_y = i32::abs(centre_y - y);
|
|
||||||
apply_paint(map, brush_size, x, centre_y + dist_y);
|
|
||||||
apply_paint(map, brush_size, x, centre_y - dist_y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_paint(map: &mut Map, brush_size: i32, x: i32, y: i32) {
|
|
||||||
match brush_size {
|
|
||||||
1 => {
|
|
||||||
let digger_idx = map.xy_idx(x, y);
|
|
||||||
map.tiles[digger_idx] = TileType::Floor;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let half_brush_size = brush_size / 2;
|
|
||||||
for brush_y in y - half_brush_size..y + half_brush_size {
|
|
||||||
for brush_x in x - half_brush_size..x + half_brush_size {
|
|
||||||
if brush_x > 1 && brush_x < map.width - 1 && brush_y > 1 && brush_y < map.height - 1 {
|
|
||||||
let idx = map.xy_idx(brush_x, brush_y);
|
|
||||||
map.tiles[idx] = TileType::Floor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
use super::{ BuilderMap, MetaMapBuilder, TileType };
|
|
||||||
use crate::tile_walkable;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
pub struct CullUnreachable {}
|
|
||||||
|
|
||||||
impl MetaMapBuilder for CullUnreachable {
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
self.build(rng, build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CullUnreachable {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn new() -> Box<CullUnreachable> {
|
|
||||||
Box::new(CullUnreachable {})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
let starting_pos = build_data.starting_position.as_ref().unwrap().clone();
|
|
||||||
let start_idx = build_data.map.xy_idx(starting_pos.x, starting_pos.y);
|
|
||||||
build_data.map.populate_blocked();
|
|
||||||
let map_starts: Vec<usize> = vec![start_idx];
|
|
||||||
let dijkstra_map = DijkstraMap::new(
|
|
||||||
build_data.map.width as usize,
|
|
||||||
build_data.map.height as usize,
|
|
||||||
&map_starts,
|
|
||||||
&build_data.map,
|
|
||||||
1000.0
|
|
||||||
);
|
|
||||||
for (i, tile) in build_data.map.tiles.iter_mut().enumerate() {
|
|
||||||
if tile_walkable(*tile) {
|
|
||||||
let distance_to_start = dijkstra_map.map[i];
|
|
||||||
// We can't get to this tile - so we'll make it a wall
|
|
||||||
if distance_to_start == std::f32::MAX {
|
|
||||||
*tile = TileType::Wall;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
use super::{ BuilderMap, MetaMapBuilder, TileType };
|
|
||||||
use crate::tile_walkable;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
pub struct DistantExit {}
|
|
||||||
|
|
||||||
impl MetaMapBuilder for DistantExit {
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
self.build(rng, build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DistantExit {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn new() -> Box<DistantExit> {
|
|
||||||
Box::new(DistantExit {})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
let starting_pos = build_data.starting_position.as_ref().unwrap().clone();
|
|
||||||
let start_idx = build_data.map.xy_idx(starting_pos.x, starting_pos.y);
|
|
||||||
build_data.map.populate_blocked();
|
|
||||||
let map_starts: Vec<usize> = vec![start_idx];
|
|
||||||
let dijkstra_map = DijkstraMap::new(
|
|
||||||
build_data.map.width as usize,
|
|
||||||
build_data.map.height as usize,
|
|
||||||
&map_starts,
|
|
||||||
&build_data.map,
|
|
||||||
1000.0
|
|
||||||
);
|
|
||||||
let mut exit_tile = (0, 0.0f32);
|
|
||||||
for (i, tile) in build_data.map.tiles.iter_mut().enumerate() {
|
|
||||||
if tile_walkable(*tile) {
|
|
||||||
let distance_to_start = dijkstra_map.map[i];
|
|
||||||
if distance_to_start != std::f32::MAX {
|
|
||||||
// If it is further away than our current exit candidate, move the exit
|
|
||||||
if distance_to_start > exit_tile.1 {
|
|
||||||
exit_tile.0 = i;
|
|
||||||
exit_tile.1 = distance_to_start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Place a staircase
|
|
||||||
let stairs_idx = exit_tile.0;
|
|
||||||
build_data.map.tiles[stairs_idx] = TileType::DownStair;
|
|
||||||
build_data.take_snapshot();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,221 +0,0 @@
|
||||||
use super::{ paint, BuilderMap, InitialMapBuilder, MetaMapBuilder, Position, Symmetry, TileType };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Copy, Clone)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum DLAAlgorithm {
|
|
||||||
WalkInwards,
|
|
||||||
WalkOutwards,
|
|
||||||
CentralAttractor,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DLABuilder {
|
|
||||||
algorithm: DLAAlgorithm,
|
|
||||||
brush_size: i32,
|
|
||||||
symmetry: Symmetry,
|
|
||||||
floor_percent: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InitialMapBuilder for DLABuilder {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
self.build(rng, build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MetaMapBuilder for DLABuilder {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
self.build(rng, build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DLABuilder {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn new() -> Box<DLABuilder> {
|
|
||||||
Box::new(DLABuilder {
|
|
||||||
algorithm: DLAAlgorithm::WalkInwards,
|
|
||||||
brush_size: 2,
|
|
||||||
symmetry: Symmetry::None,
|
|
||||||
floor_percent: 0.25,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn walk_inwards() -> Box<DLABuilder> {
|
|
||||||
Box::new(DLABuilder {
|
|
||||||
algorithm: DLAAlgorithm::WalkInwards,
|
|
||||||
brush_size: 1,
|
|
||||||
symmetry: Symmetry::None,
|
|
||||||
floor_percent: 0.25,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn walk_outwards() -> Box<DLABuilder> {
|
|
||||||
Box::new(DLABuilder {
|
|
||||||
algorithm: DLAAlgorithm::WalkOutwards,
|
|
||||||
brush_size: 2,
|
|
||||||
symmetry: Symmetry::None,
|
|
||||||
floor_percent: 0.25,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn central_attractor() -> Box<DLABuilder> {
|
|
||||||
Box::new(DLABuilder {
|
|
||||||
algorithm: DLAAlgorithm::CentralAttractor,
|
|
||||||
brush_size: 2,
|
|
||||||
symmetry: Symmetry::None,
|
|
||||||
floor_percent: 0.25,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn insectoid() -> Box<DLABuilder> {
|
|
||||||
Box::new(DLABuilder {
|
|
||||||
algorithm: DLAAlgorithm::CentralAttractor,
|
|
||||||
brush_size: 2,
|
|
||||||
symmetry: Symmetry::Horizontal,
|
|
||||||
floor_percent: 0.25,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn heavy_erosion() -> Box<DLABuilder> {
|
|
||||||
Box::new(DLABuilder {
|
|
||||||
algorithm: DLAAlgorithm::WalkInwards,
|
|
||||||
brush_size: 2,
|
|
||||||
symmetry: Symmetry::None,
|
|
||||||
floor_percent: 0.35,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::map_entry)]
|
|
||||||
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
// Carve a starting seed
|
|
||||||
let starting_position = Position {
|
|
||||||
x: build_data.map.width / 2,
|
|
||||||
y: build_data.map.height / 2,
|
|
||||||
};
|
|
||||||
let start_idx = build_data.map.xy_idx(starting_position.x, starting_position.y);
|
|
||||||
build_data.take_snapshot();
|
|
||||||
build_data.map.tiles[start_idx] = TileType::Floor;
|
|
||||||
build_data.map.tiles[start_idx - 1] = TileType::Floor;
|
|
||||||
build_data.map.tiles[start_idx + 1] = TileType::Floor;
|
|
||||||
build_data.map.tiles[start_idx - (build_data.map.width as usize)] = TileType::Floor;
|
|
||||||
build_data.map.tiles[start_idx + (build_data.map.width as usize)] = TileType::Floor;
|
|
||||||
|
|
||||||
// Random walker
|
|
||||||
let total_tiles = build_data.map.width * build_data.map.height;
|
|
||||||
let desired_floor_tiles = (self.floor_percent * (total_tiles as f32)) as usize;
|
|
||||||
let mut floor_tile_count = build_data.map.tiles
|
|
||||||
.iter()
|
|
||||||
.filter(|a| **a == TileType::Floor)
|
|
||||||
.count();
|
|
||||||
while floor_tile_count < desired_floor_tiles {
|
|
||||||
match self.algorithm {
|
|
||||||
DLAAlgorithm::WalkInwards => {
|
|
||||||
let mut digger_x = rng.roll_dice(1, build_data.map.width - 3) + 1;
|
|
||||||
let mut digger_y = rng.roll_dice(1, build_data.map.height - 3) + 1;
|
|
||||||
let mut prev_x = digger_x;
|
|
||||||
let mut prev_y = digger_y;
|
|
||||||
let mut digger_idx = build_data.map.xy_idx(digger_x, digger_y);
|
|
||||||
while build_data.map.tiles[digger_idx] == TileType::Wall {
|
|
||||||
prev_x = digger_x;
|
|
||||||
prev_y = digger_y;
|
|
||||||
let stagger_direction = rng.roll_dice(1, 4);
|
|
||||||
match stagger_direction {
|
|
||||||
1 => {
|
|
||||||
if digger_x > 2 {
|
|
||||||
digger_x -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2 => {
|
|
||||||
if digger_x < build_data.map.width - 2 {
|
|
||||||
digger_x += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3 => {
|
|
||||||
if digger_y > 2 {
|
|
||||||
digger_y -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
if digger_y < build_data.map.height - 2 {
|
|
||||||
digger_y += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
digger_idx = build_data.map.xy_idx(digger_x, digger_y);
|
|
||||||
}
|
|
||||||
paint(&mut build_data.map, self.symmetry, self.brush_size, prev_x, prev_y);
|
|
||||||
}
|
|
||||||
|
|
||||||
DLAAlgorithm::WalkOutwards => {
|
|
||||||
let mut digger_x = starting_position.x;
|
|
||||||
let mut digger_y = starting_position.y;
|
|
||||||
let mut digger_idx = build_data.map.xy_idx(digger_x, digger_y);
|
|
||||||
while build_data.map.tiles[digger_idx] == TileType::Floor {
|
|
||||||
let stagger_direction = rng.roll_dice(1, 4);
|
|
||||||
match stagger_direction {
|
|
||||||
1 => {
|
|
||||||
if digger_x > 2 {
|
|
||||||
digger_x -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2 => {
|
|
||||||
if digger_x < build_data.map.width - 2 {
|
|
||||||
digger_x += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3 => {
|
|
||||||
if digger_y > 2 {
|
|
||||||
digger_y -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
if digger_y < build_data.map.height - 2 {
|
|
||||||
digger_y += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
digger_idx = build_data.map.xy_idx(digger_x, digger_y);
|
|
||||||
}
|
|
||||||
paint(&mut build_data.map, self.symmetry, self.brush_size, digger_x, digger_y);
|
|
||||||
}
|
|
||||||
|
|
||||||
DLAAlgorithm::CentralAttractor => {
|
|
||||||
let mut digger_x = rng.roll_dice(1, build_data.map.width - 3) + 1;
|
|
||||||
let mut digger_y = rng.roll_dice(1, build_data.map.height - 3) + 1;
|
|
||||||
let mut prev_x = digger_x;
|
|
||||||
let mut prev_y = digger_y;
|
|
||||||
let mut digger_idx = build_data.map.xy_idx(digger_x, digger_y);
|
|
||||||
|
|
||||||
let mut path = line2d(
|
|
||||||
LineAlg::Bresenham,
|
|
||||||
Point::new(digger_x, digger_y),
|
|
||||||
Point::new(starting_position.x, starting_position.y)
|
|
||||||
);
|
|
||||||
|
|
||||||
while build_data.map.tiles[digger_idx] == TileType::Wall && !path.is_empty() {
|
|
||||||
prev_x = digger_x;
|
|
||||||
prev_y = digger_y;
|
|
||||||
digger_x = path[0].x;
|
|
||||||
digger_y = path[0].y;
|
|
||||||
path.remove(0);
|
|
||||||
digger_idx = build_data.map.xy_idx(digger_x, digger_y);
|
|
||||||
}
|
|
||||||
paint(&mut build_data.map, self.symmetry, self.brush_size, prev_x, prev_y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
build_data.take_snapshot();
|
|
||||||
|
|
||||||
floor_tile_count = build_data.map.tiles
|
|
||||||
.iter()
|
|
||||||
.filter(|a| **a == TileType::Floor)
|
|
||||||
.count();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
use super::{ BuilderMap, MetaMapBuilder, TileType };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
pub struct DoorPlacement {}
|
|
||||||
|
|
||||||
impl MetaMapBuilder for DoorPlacement {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
self.doors(rng, build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DoorPlacement {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn new() -> Box<DoorPlacement> {
|
|
||||||
Box::new(DoorPlacement {})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn doors(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
if let Some(halls_original) = &build_data.corridors {
|
|
||||||
let halls = halls_original.clone(); // Avoids nested borrow
|
|
||||||
for hall in halls.iter() {
|
|
||||||
if hall.len() > 2 {
|
|
||||||
if self.door_possible(build_data, hall[0]) {
|
|
||||||
build_data.spawn_list.push((hall[0], "door".to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// There are no corridors - scan for possible places
|
|
||||||
let tiles = build_data.map.tiles.clone();
|
|
||||||
for (i, tile) in tiles.iter().enumerate() {
|
|
||||||
if
|
|
||||||
*tile == TileType::Floor &&
|
|
||||||
self.door_possible(build_data, i) &&
|
|
||||||
rng.roll_dice(1, 6) == 1
|
|
||||||
{
|
|
||||||
build_data.spawn_list.push((i, "door".to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn door_possible(&self, build_data: &mut BuilderMap, idx: usize) -> bool {
|
|
||||||
// Iterate through spawn list. If another entity wants to spawn on this tile, return false
|
|
||||||
for spawn in build_data.spawn_list.iter() {
|
|
||||||
if spawn.0 == idx {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let x = idx % (build_data.map.width as usize);
|
|
||||||
let y = idx / (build_data.map.width as usize);
|
|
||||||
|
|
||||||
// Check for east-west door possibility
|
|
||||||
if
|
|
||||||
build_data.map.tiles[idx] == TileType::Floor &&
|
|
||||||
x > 1 &&
|
|
||||||
build_data.map.tiles[idx - 1] == TileType::Floor &&
|
|
||||||
x < (build_data.map.width as usize) - 2 &&
|
|
||||||
build_data.map.tiles[idx + 1] == TileType::Floor &&
|
|
||||||
y > 1 &&
|
|
||||||
build_data.map.tiles[idx - (build_data.map.width as usize)] == TileType::Wall &&
|
|
||||||
y < (build_data.map.height as usize) - 2 &&
|
|
||||||
build_data.map.tiles[idx + (build_data.map.width as usize)] == TileType::Wall
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for north-south door possibility
|
|
||||||
if
|
|
||||||
build_data.map.tiles[idx] == TileType::Floor &&
|
|
||||||
x > 1 &&
|
|
||||||
build_data.map.tiles[idx - 1] == TileType::Wall &&
|
|
||||||
x < (build_data.map.width as usize) - 2 &&
|
|
||||||
build_data.map.tiles[idx + 1] == TileType::Wall &&
|
|
||||||
y > 1 &&
|
|
||||||
build_data.map.tiles[idx - (build_data.map.width as usize)] == TileType::Floor &&
|
|
||||||
y < (build_data.map.height as usize) - 2 &&
|
|
||||||
build_data.map.tiles[idx + (build_data.map.width as usize)] == TileType::Floor
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,201 +0,0 @@
|
||||||
use super::{ paint, BuilderMap, InitialMapBuilder, MetaMapBuilder, Position, Symmetry, TileType };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Copy, Clone)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum DrunkSpawnMode {
|
|
||||||
StartingPoint,
|
|
||||||
Random,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DrunkardSettings {
|
|
||||||
pub spawn_mode: DrunkSpawnMode,
|
|
||||||
pub drunken_lifetime: i32,
|
|
||||||
pub floor_percent: f32,
|
|
||||||
pub brush_size: i32,
|
|
||||||
pub symmetry: Symmetry,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DrunkardsWalkBuilder {
|
|
||||||
settings: DrunkardSettings,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InitialMapBuilder for DrunkardsWalkBuilder {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
self.build(rng, build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MetaMapBuilder for DrunkardsWalkBuilder {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
self.build(rng, build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DrunkardsWalkBuilder {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn new(settings: DrunkardSettings) -> DrunkardsWalkBuilder {
|
|
||||||
DrunkardsWalkBuilder { settings }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn open_area() -> Box<DrunkardsWalkBuilder> {
|
|
||||||
Box::new(DrunkardsWalkBuilder {
|
|
||||||
settings: DrunkardSettings {
|
|
||||||
spawn_mode: DrunkSpawnMode::StartingPoint,
|
|
||||||
drunken_lifetime: 400,
|
|
||||||
floor_percent: 0.5,
|
|
||||||
brush_size: 1,
|
|
||||||
symmetry: Symmetry::None,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn open_halls() -> Box<DrunkardsWalkBuilder> {
|
|
||||||
Box::new(DrunkardsWalkBuilder {
|
|
||||||
settings: DrunkardSettings {
|
|
||||||
spawn_mode: DrunkSpawnMode::Random,
|
|
||||||
drunken_lifetime: 400,
|
|
||||||
floor_percent: 0.5,
|
|
||||||
brush_size: 1,
|
|
||||||
symmetry: Symmetry::None,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn winding_passages() -> Box<DrunkardsWalkBuilder> {
|
|
||||||
Box::new(DrunkardsWalkBuilder {
|
|
||||||
settings: DrunkardSettings {
|
|
||||||
spawn_mode: DrunkSpawnMode::Random,
|
|
||||||
drunken_lifetime: 100,
|
|
||||||
floor_percent: 0.4,
|
|
||||||
brush_size: 1,
|
|
||||||
symmetry: Symmetry::None,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn fat_passages() -> Box<DrunkardsWalkBuilder> {
|
|
||||||
Box::new(DrunkardsWalkBuilder {
|
|
||||||
settings: DrunkardSettings {
|
|
||||||
spawn_mode: DrunkSpawnMode::Random,
|
|
||||||
drunken_lifetime: 100,
|
|
||||||
floor_percent: 0.4,
|
|
||||||
brush_size: 2,
|
|
||||||
symmetry: Symmetry::None,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn fearful_symmetry() -> Box<DrunkardsWalkBuilder> {
|
|
||||||
Box::new(DrunkardsWalkBuilder {
|
|
||||||
settings: DrunkardSettings {
|
|
||||||
spawn_mode: DrunkSpawnMode::Random,
|
|
||||||
drunken_lifetime: 100,
|
|
||||||
floor_percent: 0.4,
|
|
||||||
brush_size: 1,
|
|
||||||
symmetry: Symmetry::Both,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
// Set a central starting point
|
|
||||||
let starting_position = Position {
|
|
||||||
x: build_data.map.width / 2,
|
|
||||||
y: build_data.map.height / 2,
|
|
||||||
};
|
|
||||||
let start_idx = build_data.map.xy_idx(starting_position.x, starting_position.y);
|
|
||||||
build_data.map.tiles[start_idx] = TileType::Floor;
|
|
||||||
|
|
||||||
let total_tiles = build_data.map.width * build_data.map.height;
|
|
||||||
let desired_floor_tiles = (self.settings.floor_percent * (total_tiles as f32)) as usize;
|
|
||||||
let mut floor_tile_count = build_data.map.tiles
|
|
||||||
.iter()
|
|
||||||
.filter(|a| **a == TileType::Floor)
|
|
||||||
.count();
|
|
||||||
let mut digger_count = 0;
|
|
||||||
while floor_tile_count < desired_floor_tiles {
|
|
||||||
let mut did_something = false;
|
|
||||||
let mut drunk_x;
|
|
||||||
let mut drunk_y;
|
|
||||||
match self.settings.spawn_mode {
|
|
||||||
DrunkSpawnMode::StartingPoint => {
|
|
||||||
drunk_x = starting_position.x;
|
|
||||||
drunk_y = starting_position.y;
|
|
||||||
}
|
|
||||||
DrunkSpawnMode::Random => {
|
|
||||||
if digger_count == 0 {
|
|
||||||
drunk_x = starting_position.x;
|
|
||||||
drunk_y = starting_position.y;
|
|
||||||
} else {
|
|
||||||
drunk_x = rng.roll_dice(1, build_data.map.width - 3) + 1;
|
|
||||||
drunk_y = rng.roll_dice(1, build_data.map.height - 3) + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut drunk_life = self.settings.drunken_lifetime;
|
|
||||||
|
|
||||||
while drunk_life > 0 {
|
|
||||||
let drunk_idx = build_data.map.xy_idx(drunk_x, drunk_y);
|
|
||||||
if build_data.map.tiles[drunk_idx] == TileType::Wall {
|
|
||||||
did_something = true;
|
|
||||||
}
|
|
||||||
paint(
|
|
||||||
&mut build_data.map,
|
|
||||||
self.settings.symmetry,
|
|
||||||
self.settings.brush_size,
|
|
||||||
drunk_x,
|
|
||||||
drunk_y
|
|
||||||
);
|
|
||||||
build_data.map.tiles[drunk_idx] = TileType::DownStair;
|
|
||||||
|
|
||||||
let stagger_direction = rng.roll_dice(1, 4);
|
|
||||||
match stagger_direction {
|
|
||||||
1 => {
|
|
||||||
if drunk_x > 2 {
|
|
||||||
drunk_x -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2 => {
|
|
||||||
if drunk_x < build_data.map.width - 2 {
|
|
||||||
drunk_x += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3 => {
|
|
||||||
if drunk_y > 2 {
|
|
||||||
drunk_y -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
if drunk_y < build_data.map.height - 2 {
|
|
||||||
drunk_y += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
drunk_life -= 1;
|
|
||||||
}
|
|
||||||
if did_something {
|
|
||||||
build_data.take_snapshot();
|
|
||||||
}
|
|
||||||
|
|
||||||
digger_count += 1;
|
|
||||||
for t in build_data.map.tiles.iter_mut() {
|
|
||||||
if *t == TileType::DownStair {
|
|
||||||
*t = TileType::Floor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
floor_tile_count = build_data.map.tiles
|
|
||||||
.iter()
|
|
||||||
.filter(|a| **a == TileType::Floor)
|
|
||||||
.count();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
use super::{ BuilderMap, MetaMapBuilder, TileType };
|
|
||||||
use crate::tile_walkable;
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
pub struct FillEdges {
|
|
||||||
fill_with: TileType,
|
|
||||||
only_walkable: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MetaMapBuilder for FillEdges {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
self.fill_edges(rng, build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FillEdges {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn wall() -> Box<FillEdges> {
|
|
||||||
return Box::new(FillEdges { fill_with: TileType::Wall, only_walkable: false });
|
|
||||||
}
|
|
||||||
pub fn overmap_transition(id: i32) -> Box<FillEdges> {
|
|
||||||
return Box::new(FillEdges { fill_with: TileType::ToOvermap(id), only_walkable: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fill_edges(&mut self, _rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
// Get map edges as possible points to fill
|
|
||||||
let mut possible_idxs: Vec<usize> = Vec::new();
|
|
||||||
for x in 0..build_data.map.width {
|
|
||||||
let mut idx = build_data.map.xy_idx(x, 0);
|
|
||||||
possible_idxs.push(idx);
|
|
||||||
idx = build_data.map.xy_idx(x, build_data.map.height - 1);
|
|
||||||
possible_idxs.push(idx);
|
|
||||||
}
|
|
||||||
for y in 0..build_data.map.height {
|
|
||||||
let mut idx = build_data.map.xy_idx(0, y);
|
|
||||||
possible_idxs.push(idx);
|
|
||||||
idx = build_data.map.xy_idx(build_data.map.width - 1, y);
|
|
||||||
possible_idxs.push(idx);
|
|
||||||
}
|
|
||||||
// For every possible point, first check if we only want to fill walkable tiles (and if its walkable if so)
|
|
||||||
for idx in possible_idxs {
|
|
||||||
if !self.only_walkable || tile_walkable(build_data.map.tiles[idx]) {
|
|
||||||
build_data.map.tiles[idx] = self.fill_with;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
use super::{ BuilderMap, MetaMapBuilder, TileType };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
pub struct Foliage {
|
|
||||||
start_tile: TileType,
|
|
||||||
percent: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MetaMapBuilder for Foliage {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
self.apply(rng, build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Foliage {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn percent(start_tile: TileType, percent: i32) -> Box<Foliage> {
|
|
||||||
return Box::new(Foliage { start_tile, percent });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
for tile in build_data.map.tiles.iter_mut() {
|
|
||||||
if *tile == self.start_tile {
|
|
||||||
if rng.roll_dice(1, 100) < self.percent {
|
|
||||||
match rng.roll_dice(1, 2) {
|
|
||||||
1 => {
|
|
||||||
*tile = TileType::Foliage;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
*tile = TileType::HeavyFoliage;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
use super::{
|
|
||||||
AreaStartingPosition,
|
|
||||||
BuilderChain,
|
|
||||||
BuilderMap,
|
|
||||||
CellularAutomataBuilder,
|
|
||||||
CullUnreachable,
|
|
||||||
MetaMapBuilder,
|
|
||||||
TileType,
|
|
||||||
VoronoiSpawning,
|
|
||||||
XStart,
|
|
||||||
YStart,
|
|
||||||
Foliage,
|
|
||||||
};
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use crate::data::names::*;
|
|
||||||
|
|
||||||
pub fn forest_builder(
|
|
||||||
new_id: i32,
|
|
||||||
_rng: &mut RandomNumberGenerator,
|
|
||||||
width: i32,
|
|
||||||
height: i32,
|
|
||||||
difficulty: i32,
|
|
||||||
initial_player_level: i32
|
|
||||||
) -> BuilderChain {
|
|
||||||
let mut chain = BuilderChain::new(
|
|
||||||
false,
|
|
||||||
new_id,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
difficulty,
|
|
||||||
NAME_FOREST_BUILDER,
|
|
||||||
SHORTNAME_FOREST_BUILDER,
|
|
||||||
0,
|
|
||||||
initial_player_level
|
|
||||||
);
|
|
||||||
chain.start_with(CellularAutomataBuilder::floor(TileType::Grass));
|
|
||||||
// Change ~30% of the floor to some sort of foliage.
|
|
||||||
chain.with(AreaStartingPosition::new(XStart::CENTRE, YStart::CENTRE));
|
|
||||||
chain.with(CullUnreachable::new());
|
|
||||||
chain.with(AreaStartingPosition::new(XStart::LEFT, YStart::CENTRE));
|
|
||||||
// Setup an exit and spawn mobs
|
|
||||||
chain.with(RoadExit::new());
|
|
||||||
chain.with(Foliage::percent(TileType::Grass, 30));
|
|
||||||
chain.with(VoronoiSpawning::new());
|
|
||||||
return chain;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RoadExit {}
|
|
||||||
|
|
||||||
impl MetaMapBuilder for RoadExit {
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
self.build(rng, build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RoadExit {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn new() -> Box<RoadExit> {
|
|
||||||
return Box::new(RoadExit {});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_exit(&self, build_data: &mut BuilderMap, seed_x: i32, seed_y: i32) -> (i32, i32) {
|
|
||||||
let mut available_floors: Vec<(usize, f32)> = Vec::new();
|
|
||||||
for (idx, tiletype) in build_data.map.tiles.iter().enumerate() {
|
|
||||||
if crate::map::tile_walkable(*tiletype) {
|
|
||||||
available_floors.push((
|
|
||||||
idx,
|
|
||||||
DistanceAlg::PythagorasSquared.distance2d(
|
|
||||||
Point::new(
|
|
||||||
(idx as i32) % build_data.map.width,
|
|
||||||
(idx as i32) / build_data.map.width
|
|
||||||
),
|
|
||||||
Point::new(seed_x, seed_y)
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if available_floors.is_empty() {
|
|
||||||
unreachable!("No valid floors to start on.");
|
|
||||||
}
|
|
||||||
available_floors.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
|
|
||||||
let end_x = (available_floors[0].0 as i32) % build_data.map.width;
|
|
||||||
let end_y = (available_floors[0].0 as i32) / build_data.map.width;
|
|
||||||
return (end_x, end_y);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paint_road(&self, build_data: &mut BuilderMap, x: i32, y: i32) {
|
|
||||||
if x < 1 || x > build_data.map.width - 2 || y < 1 || y > build_data.map.width - 2 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let idx = build_data.map.xy_idx(x, y);
|
|
||||||
if build_data.map.tiles[idx] != TileType::DownStair {
|
|
||||||
build_data.map.tiles[idx] = TileType::Road;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
let starting_pos = build_data.starting_position.as_ref().unwrap().clone();
|
|
||||||
let start_idx = build_data.map.xy_idx(starting_pos.x, starting_pos.y);
|
|
||||||
let (end_x, end_y) = self.find_exit(
|
|
||||||
build_data,
|
|
||||||
build_data.map.width - 2,
|
|
||||||
build_data.height / 2
|
|
||||||
);
|
|
||||||
let end_idx = build_data.map.xy_idx(end_x, end_y);
|
|
||||||
build_data.map.populate_blocked();
|
|
||||||
|
|
||||||
let path = a_star_search(start_idx, end_idx, &mut build_data.map);
|
|
||||||
for idx in path.steps.iter() {
|
|
||||||
let x = (*idx as i32) % build_data.map.width;
|
|
||||||
let y = (*idx as i32) / build_data.map.width;
|
|
||||||
self.paint_road(build_data, x, y);
|
|
||||||
self.paint_road(build_data, x - 1, y);
|
|
||||||
self.paint_road(build_data, x + 1, y);
|
|
||||||
self.paint_road(build_data, x, y - 1);
|
|
||||||
self.paint_road(build_data, x, y + 1);
|
|
||||||
}
|
|
||||||
build_data.take_snapshot();
|
|
||||||
|
|
||||||
let exit_dir = rng.roll_dice(1, 2);
|
|
||||||
let (seed_x, seed_y, stream_start_x, stream_start_y) = if exit_dir == 1 {
|
|
||||||
(build_data.map.width - 1, 1, 0, build_data.height - 1)
|
|
||||||
} else {
|
|
||||||
(build_data.map.width - 1, build_data.height - 1, 1, build_data.height - 1)
|
|
||||||
};
|
|
||||||
let (stairs_x, stairs_y) = self.find_exit(build_data, seed_x, seed_y);
|
|
||||||
let stairs_idx = build_data.map.xy_idx(stairs_x, stairs_y);
|
|
||||||
let (stream_x, stream_y) = self.find_exit(build_data, stream_start_x, stream_start_y);
|
|
||||||
let stream_idx = build_data.map.xy_idx(stream_x, stream_y) as usize;
|
|
||||||
let stream = a_star_search(stairs_idx, stream_idx, &mut build_data.map);
|
|
||||||
for tile in stream.steps.iter() {
|
|
||||||
// Maybe only turn grass to water here, and turn the road into a bridge.
|
|
||||||
// i.e. if build_data.map.tiles[*tile as usize] == TileType::Grass
|
|
||||||
build_data.map.tiles[*tile as usize] = TileType::ShallowWater;
|
|
||||||
}
|
|
||||||
build_data.map.tiles[stairs_idx] = TileType::DownStair;
|
|
||||||
build_data.take_snapshot();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,205 +0,0 @@
|
||||||
use super::{ BuilderMap, InitialMapBuilder, Map, TileType };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
pub struct MazeBuilder {}
|
|
||||||
|
|
||||||
impl InitialMapBuilder for MazeBuilder {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
self.build(rng, build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MazeBuilder {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn new() -> Box<MazeBuilder> {
|
|
||||||
Box::new(MazeBuilder {})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::map_entry)]
|
|
||||||
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
|
|
||||||
// Maze gen
|
|
||||||
let mut maze = Grid::new(build_data.map.width / 2 - 2, build_data.map.height / 2 - 2, rng);
|
|
||||||
maze.generate_maze(build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Maze code taken under MIT from https://github.com/cyucelen/mazeGenerator/ */
|
|
||||||
|
|
||||||
const TOP: usize = 0;
|
|
||||||
const RIGHT: usize = 1;
|
|
||||||
const BOTTOM: usize = 2;
|
|
||||||
const LEFT: usize = 3;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
struct Cell {
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
walls: [bool; 4],
|
|
||||||
visited: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cell {
|
|
||||||
fn new(row: i32, column: i32) -> Cell {
|
|
||||||
Cell { row, column, walls: [true, true, true, true], visited: false }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_walls(&mut self, next: &mut Cell) {
|
|
||||||
let x = self.column - next.column;
|
|
||||||
let y = self.row - next.row;
|
|
||||||
|
|
||||||
if x == 1 {
|
|
||||||
self.walls[LEFT] = false;
|
|
||||||
next.walls[RIGHT] = false;
|
|
||||||
} else if x == -1 {
|
|
||||||
self.walls[RIGHT] = false;
|
|
||||||
next.walls[LEFT] = false;
|
|
||||||
} else if y == 1 {
|
|
||||||
self.walls[TOP] = false;
|
|
||||||
next.walls[BOTTOM] = false;
|
|
||||||
} else if y == -1 {
|
|
||||||
self.walls[BOTTOM] = false;
|
|
||||||
next.walls[TOP] = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Grid<'a> {
|
|
||||||
width: i32,
|
|
||||||
height: i32,
|
|
||||||
cells: Vec<Cell>,
|
|
||||||
backtrace: Vec<usize>,
|
|
||||||
current: usize,
|
|
||||||
rng: &'a mut RandomNumberGenerator,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Grid<'a> {
|
|
||||||
fn new(width: i32, height: i32, rng: &mut RandomNumberGenerator) -> Grid {
|
|
||||||
let mut grid = Grid {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
cells: Vec::new(),
|
|
||||||
backtrace: Vec::new(),
|
|
||||||
current: 0,
|
|
||||||
rng,
|
|
||||||
};
|
|
||||||
|
|
||||||
for row in 0..height {
|
|
||||||
for column in 0..width {
|
|
||||||
grid.cells.push(Cell::new(row, column));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
grid
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_index(&self, row: i32, column: i32) -> i32 {
|
|
||||||
if row < 0 || column < 0 || column > self.width - 1 || row > self.height - 1 {
|
|
||||||
-1
|
|
||||||
} else {
|
|
||||||
column + row * self.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_available_neighbors(&self) -> Vec<usize> {
|
|
||||||
let mut neighbors: Vec<usize> = Vec::new();
|
|
||||||
|
|
||||||
let current_row = self.cells[self.current].row;
|
|
||||||
let current_column = self.cells[self.current].column;
|
|
||||||
|
|
||||||
let neighbor_indices: [i32; 4] = [
|
|
||||||
self.calculate_index(current_row - 1, current_column),
|
|
||||||
self.calculate_index(current_row, current_column + 1),
|
|
||||||
self.calculate_index(current_row + 1, current_column),
|
|
||||||
self.calculate_index(current_row, current_column - 1),
|
|
||||||
];
|
|
||||||
|
|
||||||
for i in neighbor_indices.iter() {
|
|
||||||
if *i != -1 && !self.cells[*i as usize].visited {
|
|
||||||
neighbors.push(*i as usize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
neighbors
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_next_cell(&mut self) -> Option<usize> {
|
|
||||||
let neighbors = self.get_available_neighbors();
|
|
||||||
if !neighbors.is_empty() {
|
|
||||||
if neighbors.len() == 1 {
|
|
||||||
return Some(neighbors[0]);
|
|
||||||
} else {
|
|
||||||
return Some(
|
|
||||||
neighbors[(self.rng.roll_dice(1, neighbors.len() as i32) - 1) as usize]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_maze(&mut self, build_data: &mut BuilderMap) {
|
|
||||||
let mut i = 0;
|
|
||||||
loop {
|
|
||||||
self.cells[self.current].visited = true;
|
|
||||||
let next = self.find_next_cell();
|
|
||||||
|
|
||||||
match next {
|
|
||||||
Some(next) => {
|
|
||||||
self.cells[next].visited = true;
|
|
||||||
self.backtrace.push(self.current);
|
|
||||||
// __lower_part__ __higher_part_
|
|
||||||
// / \ / \
|
|
||||||
// --------cell1------ | cell2-----------
|
|
||||||
let (lower_part, higher_part) = self.cells.split_at_mut(
|
|
||||||
std::cmp::max(self.current, next)
|
|
||||||
);
|
|
||||||
let cell1 = &mut lower_part[std::cmp::min(self.current, next)];
|
|
||||||
let cell2 = &mut higher_part[0];
|
|
||||||
cell1.remove_walls(cell2);
|
|
||||||
self.current = next;
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
if !self.backtrace.is_empty() {
|
|
||||||
self.current = self.backtrace[0];
|
|
||||||
self.backtrace.remove(0);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if i % 50 == 0 {
|
|
||||||
self.copy_to_map(&mut build_data.map);
|
|
||||||
build_data.take_snapshot();
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn copy_to_map(&self, map: &mut Map) {
|
|
||||||
// Clear the map
|
|
||||||
for i in map.tiles.iter_mut() {
|
|
||||||
*i = TileType::Wall;
|
|
||||||
}
|
|
||||||
|
|
||||||
for cell in self.cells.iter() {
|
|
||||||
let x = cell.column + 1;
|
|
||||||
let y = cell.row + 1;
|
|
||||||
let idx = map.xy_idx(x * 2, y * 2);
|
|
||||||
|
|
||||||
map.tiles[idx] = TileType::Floor;
|
|
||||||
if !cell.walls[TOP] {
|
|
||||||
map.tiles[idx - (map.width as usize)] = TileType::Floor;
|
|
||||||
}
|
|
||||||
if !cell.walls[RIGHT] {
|
|
||||||
map.tiles[idx + 1] = TileType::Floor;
|
|
||||||
}
|
|
||||||
if !cell.walls[BOTTOM] {
|
|
||||||
map.tiles[idx + (map.width as usize)] = TileType::Floor;
|
|
||||||
}
|
|
||||||
if !cell.walls[LEFT] {
|
|
||||||
map.tiles[idx - 1] = TileType::Floor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,492 +0,0 @@
|
||||||
use super::{ spawner, Map, Position, Rect, TileType };
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
|
|
||||||
mod bsp_dungeon;
|
|
||||||
use bsp_dungeon::BspDungeonBuilder;
|
|
||||||
mod bsp_interior;
|
|
||||||
use bsp_interior::BspInteriorBuilder;
|
|
||||||
mod cellular_automata;
|
|
||||||
use cellular_automata::CellularAutomataBuilder;
|
|
||||||
mod common;
|
|
||||||
mod dla;
|
|
||||||
use dla::DLABuilder;
|
|
||||||
mod drunkard;
|
|
||||||
use drunkard::DrunkardsWalkBuilder;
|
|
||||||
mod maze;
|
|
||||||
use maze::MazeBuilder;
|
|
||||||
mod simple_map;
|
|
||||||
use simple_map::SimpleMapBuilder;
|
|
||||||
mod voronoi;
|
|
||||||
use voronoi::VoronoiBuilder;
|
|
||||||
mod prefab_builder;
|
|
||||||
use prefab_builder::PrefabBuilder;
|
|
||||||
mod room_based_spawner;
|
|
||||||
mod wfc;
|
|
||||||
use room_based_spawner::*;
|
|
||||||
mod room_based_stairs;
|
|
||||||
use room_based_stairs::*;
|
|
||||||
mod room_based_starting_position;
|
|
||||||
use room_based_starting_position::*;
|
|
||||||
mod area_starting_points;
|
|
||||||
use area_starting_points::{ AreaStartingPosition, XStart, YStart };
|
|
||||||
mod cull_unreachable;
|
|
||||||
use cull_unreachable::CullUnreachable;
|
|
||||||
mod distant_exit;
|
|
||||||
use distant_exit::DistantExit;
|
|
||||||
mod voronoi_spawning;
|
|
||||||
use common::*;
|
|
||||||
use specs::prelude::*;
|
|
||||||
use voronoi_spawning::VoronoiSpawning;
|
|
||||||
use super::config::CONFIG;
|
|
||||||
use super::data::ids::*;
|
|
||||||
use super::data::names::*;
|
|
||||||
//use wfc::WaveFunctionCollapseBuilder;
|
|
||||||
mod room_exploder;
|
|
||||||
use room_exploder::RoomExploder;
|
|
||||||
mod room_corner_rounding;
|
|
||||||
use room_corner_rounding::RoomCornerRounder;
|
|
||||||
mod rooms_corridors_dogleg;
|
|
||||||
use rooms_corridors_dogleg::DoglegCorridors;
|
|
||||||
mod rooms_corridors_bsp;
|
|
||||||
use rooms_corridors_bsp::BspCorridors;
|
|
||||||
mod room_sorter;
|
|
||||||
use room_sorter::{ RoomSort, RoomSorter };
|
|
||||||
mod room_draw;
|
|
||||||
use room_draw::RoomDrawer;
|
|
||||||
mod rooms_corridors_nearest;
|
|
||||||
use rooms_corridors_nearest::NearestCorridors;
|
|
||||||
mod rooms_corridors_bresenham;
|
|
||||||
use rooms_corridors_bresenham::BresenhamCorridors;
|
|
||||||
mod rooms_corridors_spawner;
|
|
||||||
use rooms_corridors_spawner::CorridorSpawner;
|
|
||||||
mod door_placement;
|
|
||||||
use door_placement::DoorPlacement;
|
|
||||||
mod fill_edges;
|
|
||||||
use fill_edges::FillEdges;
|
|
||||||
mod town;
|
|
||||||
use town::town_builder;
|
|
||||||
mod forest;
|
|
||||||
use forest::forest_builder;
|
|
||||||
mod foliage;
|
|
||||||
use foliage::Foliage;
|
|
||||||
mod room_themer;
|
|
||||||
use room_themer::ThemeRooms;
|
|
||||||
|
|
||||||
// Shared data to be passed around build chain
|
|
||||||
pub struct BuilderMap {
|
|
||||||
pub spawn_list: Vec<(usize, String)>,
|
|
||||||
pub map: Map,
|
|
||||||
pub starting_position: Option<Position>,
|
|
||||||
pub rooms: Option<Vec<Rect>>,
|
|
||||||
pub corridors: Option<Vec<Vec<usize>>>,
|
|
||||||
pub history: Vec<Map>,
|
|
||||||
pub width: i32,
|
|
||||||
pub height: i32,
|
|
||||||
pub initial_player_level: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BuilderMap {
|
|
||||||
fn take_snapshot(&mut self) {
|
|
||||||
if CONFIG.logging.show_mapgen {
|
|
||||||
let mut snapshot = self.map.clone();
|
|
||||||
for v in snapshot.revealed_tiles.iter_mut() {
|
|
||||||
*v = true;
|
|
||||||
}
|
|
||||||
self.history.push(snapshot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct BuilderChain {
|
|
||||||
starter: Option<Box<dyn InitialMapBuilder>>,
|
|
||||||
builders: Vec<Box<dyn MetaMapBuilder>>,
|
|
||||||
pub build_data: BuilderMap,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BuilderChain {
|
|
||||||
pub fn new<S: ToString>(
|
|
||||||
overmap: bool,
|
|
||||||
new_id: i32,
|
|
||||||
width: i32,
|
|
||||||
height: i32,
|
|
||||||
difficulty: i32,
|
|
||||||
name: S,
|
|
||||||
short_name: S,
|
|
||||||
depth: i32,
|
|
||||||
initial_player_level: i32
|
|
||||||
) -> BuilderChain {
|
|
||||||
BuilderChain {
|
|
||||||
starter: None,
|
|
||||||
builders: Vec::new(),
|
|
||||||
build_data: BuilderMap {
|
|
||||||
spawn_list: Vec::new(),
|
|
||||||
map: Map::new(overmap, new_id, width, height, difficulty, name, short_name, depth),
|
|
||||||
starting_position: None,
|
|
||||||
rooms: None,
|
|
||||||
corridors: None,
|
|
||||||
history: Vec::new(),
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
initial_player_level: initial_player_level,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_with(&mut self, starter: Box<dyn InitialMapBuilder>) {
|
|
||||||
match self.starter {
|
|
||||||
None => {
|
|
||||||
self.starter = Some(starter);
|
|
||||||
}
|
|
||||||
Some(_) => unreachable!("You can only have one starting builder."),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with(&mut self, metabuilder: Box<dyn MetaMapBuilder>) {
|
|
||||||
self.builders.push(metabuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_map(&mut self, rng: &mut RandomNumberGenerator) {
|
|
||||||
match &mut self.starter {
|
|
||||||
None => unreachable!("Cannot run a map builder chain without a starting build system"),
|
|
||||||
Some(starter) => {
|
|
||||||
// Build the starting map
|
|
||||||
starter.build_map(rng, &mut self.build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build additional layers in turn
|
|
||||||
for metabuilder in self.builders.iter_mut() {
|
|
||||||
metabuilder.build_map(rng, &mut self.build_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn_entities(&mut self, ecs: &mut World) {
|
|
||||||
let mut spawned_entities = Vec::new();
|
|
||||||
for entity in self.build_data.spawn_list.iter() {
|
|
||||||
spawned_entities.push(&entity.1);
|
|
||||||
spawner::spawn_entity(ecs, &(&entity.0, &entity.1));
|
|
||||||
}
|
|
||||||
if CONFIG.logging.log_spawning {
|
|
||||||
console::log(format!("DEBUGINFO: SPAWNED ENTITIES = {:?}", spawned_entities));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait InitialMapBuilder {
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait MetaMapBuilder {
|
|
||||||
fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn random_start_position(rng: &mut RandomNumberGenerator) -> (XStart, YStart) {
|
|
||||||
let x;
|
|
||||||
let xroll = rng.roll_dice(1, 3);
|
|
||||||
match xroll {
|
|
||||||
1 => {
|
|
||||||
x = XStart::LEFT;
|
|
||||||
}
|
|
||||||
2 => {
|
|
||||||
x = XStart::CENTRE;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
x = XStart::RIGHT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let y;
|
|
||||||
let yroll = rng.roll_dice(1, 3);
|
|
||||||
match yroll {
|
|
||||||
1 => {
|
|
||||||
y = YStart::BOTTOM;
|
|
||||||
}
|
|
||||||
2 => {
|
|
||||||
y = YStart::CENTRE;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
y = YStart::TOP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn random_room_builder(rng: &mut RandomNumberGenerator, builder: &mut BuilderChain, end: bool) {
|
|
||||||
let build_roll = rng.roll_dice(1, 3);
|
|
||||||
// Start with a room builder.
|
|
||||||
match build_roll {
|
|
||||||
1 => builder.start_with(SimpleMapBuilder::new(None)),
|
|
||||||
2 => builder.start_with(BspDungeonBuilder::new()),
|
|
||||||
_ => builder.start_with(BspInteriorBuilder::new()),
|
|
||||||
}
|
|
||||||
|
|
||||||
// BspInterior makes its own doorways. If we're not using that one,
|
|
||||||
// select a sorting method, a type of corridor, and modifiers.
|
|
||||||
if build_roll != 3 {
|
|
||||||
// Sort by one of the 5 available algorithms
|
|
||||||
let sort_roll = rng.roll_dice(1, 5);
|
|
||||||
match sort_roll {
|
|
||||||
1 => builder.with(RoomSorter::new(RoomSort::LEFTMOST)),
|
|
||||||
2 => builder.with(RoomSorter::new(RoomSort::RIGHTMOST)),
|
|
||||||
3 => builder.with(RoomSorter::new(RoomSort::TOPMOST)),
|
|
||||||
4 => builder.with(RoomSorter::new(RoomSort::BOTTOMMOST)),
|
|
||||||
_ => builder.with(RoomSorter::new(RoomSort::CENTRAL)),
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.with(RoomDrawer::new());
|
|
||||||
|
|
||||||
let corridor_roll = rng.roll_dice(1, 2);
|
|
||||||
match corridor_roll {
|
|
||||||
1 => builder.with(DoglegCorridors::new()),
|
|
||||||
_ => builder.with(BspCorridors::new()),
|
|
||||||
}
|
|
||||||
|
|
||||||
let corridor_roll = rng.roll_dice(1, 4);
|
|
||||||
match corridor_roll {
|
|
||||||
1 => builder.with(DoglegCorridors::new()),
|
|
||||||
2 => builder.with(NearestCorridors::new()),
|
|
||||||
3 => builder.with(BresenhamCorridors::new()),
|
|
||||||
_ => builder.with(BspCorridors::new()),
|
|
||||||
}
|
|
||||||
|
|
||||||
let cspawn_roll = rng.roll_dice(1, 2);
|
|
||||||
if cspawn_roll == 1 {
|
|
||||||
builder.with(CorridorSpawner::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
let modifier_roll = rng.roll_dice(1, 6);
|
|
||||||
match modifier_roll {
|
|
||||||
1 => builder.with(RoomExploder::new()),
|
|
||||||
2 => builder.with(RoomCornerRounder::new()),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pick a starting position, in a room or elsewhere.
|
|
||||||
let start_roll = rng.roll_dice(1, 2);
|
|
||||||
match start_roll {
|
|
||||||
1 => builder.with(RoomBasedStartingPosition::new()),
|
|
||||||
_ => {
|
|
||||||
let (start_x, start_y) = random_start_position(rng);
|
|
||||||
builder.with(AreaStartingPosition::new(start_x, start_y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decide where to put the exit - in a room or far away, anywhere.
|
|
||||||
if !end {
|
|
||||||
let exit_roll = rng.roll_dice(1, 2);
|
|
||||||
match exit_roll {
|
|
||||||
1 => builder.with(RoomBasedStairs::new()),
|
|
||||||
_ => builder.with(DistantExit::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decide whether to spawn entities only in rooms, or with voronoi noise.
|
|
||||||
let spawn_roll = rng.roll_dice(1, 2);
|
|
||||||
match spawn_roll {
|
|
||||||
1 => builder.with(RoomBasedSpawner::new()),
|
|
||||||
_ => builder.with(VoronoiSpawning::new()),
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.with(ThemeRooms::grass(5, 5 * 5)); // 5% chance of an overgrown treant room. Must be 5*5 tiles minimum.
|
|
||||||
builder.with(ThemeRooms::barracks(5, 6 * 6)); // 5% chance of a squad barracks. Must be 6*6 tiles minimum.
|
|
||||||
}
|
|
||||||
|
|
||||||
fn random_shape_builder(
|
|
||||||
rng: &mut RandomNumberGenerator,
|
|
||||||
builder: &mut BuilderChain,
|
|
||||||
end: bool
|
|
||||||
) -> bool {
|
|
||||||
// Pick an initial builder
|
|
||||||
let builder_roll = rng.roll_dice(1, 16);
|
|
||||||
let mut want_doors = true;
|
|
||||||
match builder_roll {
|
|
||||||
1 => builder.start_with(CellularAutomataBuilder::new()),
|
|
||||||
2 => builder.start_with(DrunkardsWalkBuilder::open_area()),
|
|
||||||
3 => builder.start_with(DrunkardsWalkBuilder::open_halls()),
|
|
||||||
4 => builder.start_with(DrunkardsWalkBuilder::winding_passages()),
|
|
||||||
5 => builder.start_with(DrunkardsWalkBuilder::fat_passages()),
|
|
||||||
6 => builder.start_with(DrunkardsWalkBuilder::fearful_symmetry()),
|
|
||||||
7 => {
|
|
||||||
builder.start_with(MazeBuilder::new());
|
|
||||||
want_doors = false;
|
|
||||||
}
|
|
||||||
8 => builder.start_with(DLABuilder::walk_inwards()),
|
|
||||||
9 => builder.start_with(DLABuilder::walk_outwards()),
|
|
||||||
10 => builder.start_with(DLABuilder::central_attractor()),
|
|
||||||
11 => builder.start_with(DLABuilder::insectoid()),
|
|
||||||
12 => builder.start_with(VoronoiBuilder::pythagoras()),
|
|
||||||
13 => builder.start_with(VoronoiBuilder::manhattan()),
|
|
||||||
_ =>
|
|
||||||
builder.start_with(
|
|
||||||
PrefabBuilder::constant(prefab_builder::prefab_levels::WFC_POPULATED)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'Select' the centre by placing a starting position, and cull everywhere unreachable.
|
|
||||||
builder.with(AreaStartingPosition::new(XStart::CENTRE, YStart::CENTRE));
|
|
||||||
builder.with(CullUnreachable::new());
|
|
||||||
|
|
||||||
// Now set the start to a random spot in our remaining area.
|
|
||||||
let (start_x, start_y) = random_start_position(rng);
|
|
||||||
builder.with(AreaStartingPosition::new(start_x, start_y));
|
|
||||||
|
|
||||||
// Place the exit and spawn mobs
|
|
||||||
builder.with(VoronoiSpawning::new());
|
|
||||||
if !end {
|
|
||||||
builder.with(DistantExit::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
return want_doors;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn overmap_builder() -> BuilderChain {
|
|
||||||
let mut builder = BuilderChain::new(
|
|
||||||
true,
|
|
||||||
ID_OVERMAP,
|
|
||||||
69,
|
|
||||||
41,
|
|
||||||
0,
|
|
||||||
NAME_OVERMAP,
|
|
||||||
SHORTNAME_OVERMAP,
|
|
||||||
0,
|
|
||||||
1
|
|
||||||
);
|
|
||||||
builder.start_with(PrefabBuilder::overmap());
|
|
||||||
builder.with(Foliage::percent(TileType::Grass, 30));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum BuildType {
|
|
||||||
Room = 1,
|
|
||||||
Shape = 2,
|
|
||||||
Any = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn random_builder(
|
|
||||||
new_id: i32,
|
|
||||||
rng: &mut RandomNumberGenerator,
|
|
||||||
width: i32,
|
|
||||||
height: i32,
|
|
||||||
difficulty: i32,
|
|
||||||
depth: i32,
|
|
||||||
initial_player_level: i32,
|
|
||||||
end: bool,
|
|
||||||
build_type: BuildType
|
|
||||||
) -> BuilderChain {
|
|
||||||
console::log(format!("DEBUGINFO: Building random (ID:{}, DIFF:{})", new_id, difficulty));
|
|
||||||
let mut builder = BuilderChain::new(
|
|
||||||
false,
|
|
||||||
new_id,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
difficulty,
|
|
||||||
NAME_DUNGEON_RANDOM,
|
|
||||||
SHORTNAME_DUNGEON_RANDOM,
|
|
||||||
depth,
|
|
||||||
initial_player_level
|
|
||||||
);
|
|
||||||
let mut want_doors = true;
|
|
||||||
match build_type {
|
|
||||||
BuildType::Room => random_room_builder(rng, &mut builder, end),
|
|
||||||
BuildType::Shape => {
|
|
||||||
want_doors = random_shape_builder(rng, &mut builder, end);
|
|
||||||
}
|
|
||||||
BuildType::Any => {
|
|
||||||
let roll = rng.roll_dice(1, 2);
|
|
||||||
match roll {
|
|
||||||
1 => random_room_builder(rng, &mut builder, end),
|
|
||||||
_ => {
|
|
||||||
want_doors = random_shape_builder(rng, &mut builder, end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
WFC needs polishing up before it makes good maps. Right now it leaves too much unusable area,
|
|
||||||
by making disconnected sections and having no methods to connect them.
|
|
||||||
|
|
||||||
if rng.roll_dice(1, 1) == 1 {
|
|
||||||
builder.with(WaveFunctionCollapseBuilder::new());
|
|
||||||
|
|
||||||
// Now set the start to a random starting area
|
|
||||||
let (start_x, start_y) = random_start_position(rng);
|
|
||||||
builder.with(AreaStartingPosition::new(start_x, start_y));
|
|
||||||
|
|
||||||
// Setup an exit and spawn mobs
|
|
||||||
builder.with(VoronoiSpawning::new());
|
|
||||||
builder.with(DistantExit::new());
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
if want_doors {
|
|
||||||
builder.with(DoorPlacement::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
if rng.roll_dice(1, 20) == 1 {
|
|
||||||
builder.with(PrefabBuilder::sectional(prefab_builder::prefab_sections::UNDERGROUND_FORT));
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.with(PrefabBuilder::vaults());
|
|
||||||
// Regardless of anything else, fill the edges back in with walls. We can't walk
|
|
||||||
// there anyway, and we don't want an open line of sight into the unmapped void.
|
|
||||||
builder.with(FillEdges::wall());
|
|
||||||
|
|
||||||
builder
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn level_builder(
|
|
||||||
id: i32,
|
|
||||||
rng: &mut RandomNumberGenerator,
|
|
||||||
width: i32,
|
|
||||||
height: i32,
|
|
||||||
initial_player_level: i32
|
|
||||||
) -> BuilderChain {
|
|
||||||
match id {
|
|
||||||
ID_OVERMAP => overmap_builder(),
|
|
||||||
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_TOWN3 =>
|
|
||||||
random_builder(
|
|
||||||
id,
|
|
||||||
rng,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
2,
|
|
||||||
1,
|
|
||||||
initial_player_level,
|
|
||||||
true,
|
|
||||||
BuildType::Room
|
|
||||||
),
|
|
||||||
_ if id >= ID_INFINITE =>
|
|
||||||
random_builder(
|
|
||||||
id,
|
|
||||||
rng,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
4 + diff(ID_INFINITE, id),
|
|
||||||
1 + diff(ID_INFINITE, id),
|
|
||||||
initial_player_level,
|
|
||||||
false,
|
|
||||||
BuildType::Room
|
|
||||||
),
|
|
||||||
_ => // This should be unreachable!() eventually. Right now it's reachable with the debug/cheat menu. It should not be in normal gameplay.
|
|
||||||
random_builder(
|
|
||||||
id,
|
|
||||||
rng,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
1,
|
|
||||||
404,
|
|
||||||
initial_player_level,
|
|
||||||
false,
|
|
||||||
BuildType::Room
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn diff(branch_id: i32, lvl_id: i32) -> i32 {
|
|
||||||
return lvl_id - branch_id;
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue