Compare commits

...
Sign in to create a new pull request.

109 commits

Author SHA1 Message Date
Llywelwyn
6d4ab70c7b prompt() helper fn, help screen with placeholder text 2023-10-25 00:22:54 +01:00
Llywelwyn
e3a8c545fb .bin 2023-10-24 11:12:25 +01:00
Llywelwyn
17bebf03a4 .bin saves instead of json. it wasn't readable anyway 2023-10-24 11:11:50 +01:00
Llywelwyn
d1fe86f645 draw equipped items on player avatar - framework 2023-10-14 21:06:28 +01:00
Llywelwyn
6ba7778df0 slight volume variation on SFX to reduce uniformity 2023-10-14 21:05:54 +01:00
Llywelwyn
be7ff6b895 initial hit sounds 2023-10-14 21:04:24 +01:00
Llywelwyn
7ac2e14471 wav to ogg for ambient tracks 2023-10-12 15:33:26 +01:00
Llywelwyn
b105a415d5 door sounds, ambient tracks 2023-10-12 15:22:13 +01:00
Llywelwyn
0c74531c5b ambient track storing/replacing 2023-10-12 09:28:16 +01:00
Llywelwyn
e8d376fecf cleans up particles, and SFX/ambience w/ placeholders 2023-10-12 08:05:17 +01:00
Llywelwyn
465cd51a60 bloodsplosion 2023-10-11 17:33:51 +01:00
Llywelwyn
1de0c20b76 rm deprecated log 2023-10-11 15:35:39 +01:00
Llywelwyn
6c359a0780 as last commit - should have been included 2023-10-11 09:48:09 +01:00
Llywelwyn
13322eb2a1 adds viewport_to_px and viewport_to_idx
used for converting a viewport (x, y) value to the correct pixel location on the screen, and to the correct map index
2023-10-11 09:47:56 +01:00
Llywelwyn
f862f00f0b fixes up farlooking - sprite, etc. 2023-10-11 09:46:41 +01:00
Llywelwyn
deb9988935 removes some unneeded vision ranges 2023-10-11 08:48:44 +01:00
Llywelwyn
098617fbf1 log scales - drawn to rendertexture, and then *that* is drawn to draw 2023-10-11 08:48:27 +01:00
Llywelwyn
72a6ff6d14 up/downstair sprites 2023-10-10 19:07:38 +01:00
Llywelwyn
3c9eb2de27 draws entity hp bars above heads again 2023-10-10 19:07:30 +01:00
Llywelwyn
074e2465d7 draw now uses a projection - log text still needs tweaking
notan::text doesn't seem to use projections, so the log will need to be converted to notan::draw, or a work-around will need to be found.
2023-10-10 19:07:14 +01:00
Llywelwyn
59f7e9a8f7 clamps colour offsets to +-50%, rather than -50% to +100% 2023-10-07 11:05:37 +01:00
Llywelwyn
0ee2a80715 tile variants, and applying colour offsets again 2023-10-07 10:36:36 +01:00
Llywelwyn
396d548bf2 .col() and .offset() impls for TileTypes - and some more sprites in 2023-10-07 00:56:02 +01:00
Llywelwyn
40b048fd65 tweaks to make zoomfactor better - centred @ 2x zoom 2023-10-06 10:33:25 +01:00
Llywelwyn
af1040b970 player avatar - currently bare. need a component for avatar_sprite! 2023-10-06 10:06:04 +01:00
Llywelwyn
44b0674b5a the oryx-ening - zoom factor, and renderables refactor
currently extremely unfinished - half the sprites are gnomes, and tiles have no colours
2023-10-06 09:22:11 +01:00
Llywelwyn
8bb6a54a39 cleans up draw_targeting() with some bitflags 2023-10-05 10:27:43 +01:00
Llywelwyn
95c642d4ef rudimentary ShowTargeting - it needs a visual improvement, but it's back 2023-10-05 09:55:16 +01:00
Llywelwyn
a92f60bb15 makes keyhandling debug msgs optional 2023-10-05 04:38:34 +01:00
Llywelwyn
bc4f960d88 fixes missing borrow 2023-10-05 04:38:22 +01:00
Llywelwyn
a9dd729a2b removes mapgen debug msg 2023-10-05 04:38:09 +01:00
Llywelwyn
bb775761de Merge branch 'master' into notan
keeping things up to date. lets not have any annoying conflicts to fix later.
2023-10-05 04:30:23 +01:00
Llywelwyn
139e718fd1 more "cleanup" 2023-10-05 04:24:20 +01:00
Llywelwyn
9c1298df6b itemtypes, and filtering items by itemtype/buc/etc. 2023-10-05 03:54:31 +01:00
Llywelwyn
f3af75bf44 ensure key assignments are handled for consumed/deleted items 2023-10-05 01:05:00 +01:00
Llywelwyn
d11971126c static keys - items in inventory will save their assigned key
precursor to cleaning up/modularising inventory display, instead of needing to iterate through every item held to find unique copies, we can just check if the button pressed corresponds to any entity's Key {} index
2023-10-05 00:52:34 +01:00
Llywelwyn
a7b4f621fb better font support 2023-10-03 01:56:38 +01:00
Llywelwyn
71576f36c3 fixes reversed menu controls 2023-10-01 00:11:21 +01:00
Llywelwyn
a99f16b8db Icingdeath 2023-09-30 09:16:22 +01:00
Llywelwyn
0d834f5e23 draw showinventory 2023-09-30 08:53:16 +01:00
Llywelwyn
b524cc3b08 showinventory runstate (draw nyi) 2023-09-30 08:13:20 +01:00
Llywelwyn
56f6cb6ae8 attributes pad left so they stay aligned 2023-09-30 06:49:24 +01:00
Llywelwyn
c80cb01816 charcreation runstate 2023-09-30 06:40:35 +01:00
Llywelwyn
f4d4b414d5 draw_charcreation() 2023-09-30 06:30:48 +01:00
Llywelwyn
ec8793180d mainmenu draw 2023-09-30 03:24:30 +01:00
Llywelwyn
855304abc0 main menu runstate 2023-09-30 01:16:14 +01:00
Llywelwyn
ac0da55d14 temporarily making the gui code a mess, and fixing it later 2023-09-29 03:39:58 +01:00
Llywelwyn
d66bc0d746 de-sprite hp bar 2023-09-29 03:15:33 +01:00
Llywelwyn
719cedf526 viewport-sized dungeons 2023-09-29 02:53:14 +01:00
Llywelwyn
c757466df1 attribute exercising 2023-09-27 22:12:53 +01:00
Llywelwyn
4d614daad5 better attributes 2023-09-27 19:21:22 +01:00
Llywelwyn
1e5f565824 exercising/abusing attributes 2023-09-27 18:08:40 +01:00
Llywelwyn
046837d9a1 returning the fade2black with distance 2023-09-27 03:39:22 +01:00
Llywelwyn
cfbe4098b7 map memory 2023-09-27 02:32:51 +01:00
Llywelwyn
054468bbae hp bars 2023-09-26 22:44:44 +01:00
Llywelwyn
e723f27375 scroll sprites 2023-09-26 22:26:57 +01:00
Llywelwyn
0d230c7721 sanity check to ensure .swap() isn't being called for no reason 2023-09-26 22:11:48 +01:00
Llywelwyn
ae9f6b6ac6 proper alt sprite and render order swaps 2023-09-26 22:08:05 +01:00
Llywelwyn
06d5674199 trapdoors 2023-09-26 21:09:47 +01:00
Llywelwyn
bd450e806b improved sprites - spriteinfo and methods 2023-09-26 20:40:17 +01:00
Llywelwyn
849c400497 hp/mp bars 2023-09-26 18:27:41 +01:00
Llywelwyn
4094d3535c hunger to ui 2023-09-26 18:05:59 +01:00
Llywelwyn
d6ba6c628c sprites for entities, with text glyph fallback 2023-09-26 17:23:25 +01:00
Llywelwyn
2c4b4ca143 added gameover, added class/ancestry defaults 2023-09-25 22:04:49 +01:00
Llywelwyn
65ec5c1b15 cleanup 2023-09-25 20:50:30 +01:00
Llywelwyn
db29b60551 mapgen fix 2023-09-25 20:44:05 +01:00
Llywelwyn
e258767405 finalised gamelog 2023-09-25 20:38:52 +01:00
Llywelwyn
8de3648bae notan::text message log tests 2023-09-25 17:40:48 +01:00
Llywelwyn
093f9df86e action w/ direction, and mapgen ctd fix 2023-09-25 16:06:45 +01:00
Llywelwyn
030bda215a icon, wider log 2023-09-25 06:15:20 +01:00
Llywelwyn
c499528616 rem deprecated 2023-09-25 05:18:25 +01:00
Llywelwyn
b459966f95 alt log solution 2023-09-25 05:10:21 +01:00
Llywelwyn
aa63431d57 Notan ver 2023-09-25 04:51:38 +01:00
Llywelwyn
cdf0257598 rudimentary reimplementation of the log 2023-09-25 04:05:57 +01:00
Llywelwyn
1cd9f75ecc curses16x16 to spritesheet 2023-09-25 03:34:53 +01:00
Llywelwyn
6cc659fd23 scuffed entity chars 2023-09-25 03:11:05 +01:00
Llywelwyn
0586c2cdad map and turns 2023-09-25 02:16:06 +01:00
Llywelwyn
1038573a3a backpack 2023-09-25 01:47:40 +01:00
Llywelwyn
4e24a9b50a equip list - placeholder 2023-09-25 01:41:01 +01:00
Llywelwyn
df211c5c10 ui bulk 2023-09-25 01:30:05 +01:00
Llywelwyn
f3e58ad761 started on new ui fn 2023-09-25 00:36:11 +01:00
Llywelwyn
627d33b2d9 pixelfont 2023-09-25 00:05:26 +01:00
Llywelwyn
7f9ba34afa fixes cheatmenu, reimpl particle ticker 2023-09-24 23:56:29 +01:00
Llywelwyn
0d4c0c9558 data to consts, cheatmenu 2023-09-24 23:46:27 +01:00
Llywelwyn
a2fb893f49 mapgen runstate 2023-09-24 22:20:49 +01:00
Llywelwyn
7f02a5a30f draw_bg() 2023-09-24 21:24:11 +01:00
Llywelwyn
e482b29fc6 reimpl farlook (tooltips NYI) 2023-09-24 19:16:47 +01:00
Llywelwyn
e8aa7494a4 fixing the sea of red - entity rendering for things in view 2023-09-24 16:55:47 +01:00
Llywelwyn
643ecfcf3e drawing entities, and map memory 2023-09-24 16:15:24 +01:00
Llywelwyn
d9489e7ddc camera rendering, and breaking things temporarily 2023-09-24 14:23:48 +01:00
Llywelwyn
1299524c91 reimplementing gameloop with notan's App, instead of bracket-lib BTerm 2023-09-24 00:41:02 +01:00
Llywelwyn
683ab95531 removes ctx arg from get_screen_bounds() 2023-09-24 00:01:17 +01:00
Llywelwyn
be1c7aa1c7 draw "tiles" 2023-09-23 23:33:50 +01:00
Llywelwyn
2967cddf7b init 2023-09-23 23:15:36 +01:00
Llywelwyn
a573971d49 include dep 2023-09-23 22:29:42 +01:00
Llywelwyn
2d55f83bb3 del spritesheet test grid 2023-09-23 20:54:49 +01:00
Llywelwyn
670b365def refining the sprites 2023-09-23 19:35:51 +01:00
Llywelwyn
d58614b106 optional sprites for entities 2023-09-23 19:01:39 +01:00
Llywelwyn
cee4d02ce2 sprites init 2023-09-23 18:02:51 +01:00
Llywelwyn
8337f202cf tile clipping fixes 2023-09-23 11:58:34 +01:00
Llywelwyn
141d4b63d2 fixes infini-dungeon difficulty 2023-09-23 11:14:32 +01:00
Llywelwyn
ddcfd72318 fixing up some post-merge errors 2023-09-23 10:43:32 +01:00
Llywelwyn
441b22439f Merge branch 'master' into switching_to_draw_batches 2023-09-23 10:12:26 +01:00
Llywelwyn
a29a7f5be4 formatting 2023-09-03 22:47:59 +01:00
Llywelwyn
ae3e061ce8 back to curses -- still needs tweaking
box drawing glyphs are misaligned, etc
2023-09-03 08:42:10 +01:00
Llywelwyn
ebcce3183b fixes off-by-one pixels with a small gutter
not sure why this fixes it - but it does. needs some testing.
2023-09-03 06:56:05 +01:00
Llywelwyn
1bea2c0026 testing resizing
strange artifacts with this size of font. seems to be an OpenGL issue (on specific hardware?) - issue is present on two different devices, with different screen resolutions, but disappears when running the WASM build. might be easiest to return to something which doesn't have this issue.
2023-09-03 06:40:27 +01:00
Llywelwyn
4e0ed95a22 infinite font variations for testing - huge wip 2023-09-03 05:10:17 +01:00
Llywelwyn
2a3c59ad33 first steps - extreme wip
1. need to finish curses12x24 first of all
2. bind everything to the viewport, and make scalable
2023-09-03 01:45:18 +01:00
108 changed files with 16213 additions and 2014 deletions

3
.gitignore vendored
View file

@ -3,6 +3,7 @@ target
wasm/index.css wasm/index.css
wasm/index.html wasm/index.html
docs/gifs/* docs/gifs/*
resources/archived resources
# VSCode/IDE config files # VSCode/IDE config files
Cargo.lock Cargo.lock
@ -12,7 +13,7 @@ Cargo.lock
.prettierrc.json .prettierrc.json
# Save files, morgue files # Save files, morgue files
savegame.json savegame.bin
morgue morgue
# A default user config will be written on first run, if not present # A default user config will be written on first run, if not present

View file

@ -6,14 +6,16 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
notan = { version = "0.10.0", features = ["text", "audio"] }
bracket-lib = { git = "https://github.com/amethyst/bracket-lib.git", rev = "851f6f08675444fb6fa088b9e67bee9fd75554c6", features = ["serde"] } bracket-lib = { git = "https://github.com/amethyst/bracket-lib.git", rev = "851f6f08675444fb6fa088b9e67bee9fd75554c6", features = ["serde"] }
regex = "1.3.6" regex = "1.3.6"
specs = { version = "0.16.1", features = ["serde"] } specs = { version = "0.20.0", features = ["serde"] }
specs-derive = "0.4.1" specs-derive = "0.4.1"
serde = { version = "1.0.93", features = ["derive"]} serde = { version = "1.0.93", features = ["derive"]}
serde_json = "1.0.39" serde_json = "1.0.39"
toml = "0.5" toml = "0.5"
lazy_static = "1.4.0" lazy_static = "1.4.0"
bincode = "1.3.3"
[dev-dependencies] [dev-dependencies]
criterion = { version = "^0.5" } criterion = { version = "^0.5" }

View file

@ -2,27 +2,30 @@
{ {
"id": "potion_health", "id": "potion_health",
"name": { "name": "potion of health", "plural": "potions of health" }, "name": { "name": "potion of health", "plural": "potions of health" },
"renderable": { "glyph": "!", "fg": "#FF00FF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "!", "sprite": "potion", "fg": "#FF00FF", "order": 4 },
"class": "potion",
"weight": 1, "weight": 1,
"value": 50, "value": 50,
"flags": ["CONSUMABLE", "DESTRUCTIBLE"], "flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
"effects": { "heal": "4d4+2" }, "effects": { "heal": "4d4+2" },
"magic": { "class": "uncommon", "naming": "potion" } "magic": { "class": "uncommon", "naming": "potion" }
}, },
{ {
"id": "potion_health_weak", "id": "potion_health_weak",
"name": { "name": "potion of lesser health", "plural": "potions of lesser health" }, "name": { "name": "potion of lesser health", "plural": "potions of lesser health" },
"renderable": { "glyph": "!", "fg": "#FF00FF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "!", "sprite": "potion", "fg": "#FF00FF", "order": 4 },
"class": "potion",
"weight": 1, "weight": 1,
"value": 25, "value": 25,
"flags": ["CONSUMABLE", "DESTRUCTIBLE"], "flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"],
"effects": { "heal": "2d4+2" }, "effects": { "heal": "2d4+2" },
"magic": { "class": "uncommon", "naming": "potion" } "magic": { "class": "uncommon", "naming": "potion" }
}, },
{ {
"id": "scroll_identify", "id": "scroll_identify",
"name": { "name": "scroll of identify", "plural": "scrolls of identify" }, "name": { "name": "scroll of identify", "plural": "scrolls of identify" },
"renderable": { "glyph": "?", "fg": "#0FFFFF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "?", "sprite": "scroll_writing", "fg": "#0FFFFF", "order": 4 },
"class": "scroll",
"weight": 0.5, "weight": 0.5,
"value": 100, "value": 100,
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "IDENTIFY"], "flags": ["CONSUMABLE", "DESTRUCTIBLE", "IDENTIFY"],
@ -31,7 +34,8 @@
{ {
"id": "scroll_removecurse", "id": "scroll_removecurse",
"name": { "name": "scroll of remove curse", "plural": "scrolls of remove curse" }, "name": { "name": "scroll of remove curse", "plural": "scrolls of remove curse" },
"renderable": { "glyph": "?", "fg": "#0FFFFF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "?", "sprite": "scroll_writing", "fg": "#0FFFFF", "order": 4 },
"class": "scroll",
"weight": 0.5, "weight": 0.5,
"value": 200, "value": 200,
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "REMOVE_CURSE"], "flags": ["CONSUMABLE", "DESTRUCTIBLE", "REMOVE_CURSE"],
@ -40,7 +44,8 @@
{ {
"id": "scroll_health", "id": "scroll_health",
"name": { "name": "scroll of healing word", "plural": "scrolls of healing word" }, "name": { "name": "scroll of healing word", "plural": "scrolls of healing word" },
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "?", "sprite": "scroll_writing", "fg": "#00FFFF", "order": 4 },
"class": "scroll",
"weight": 0.5, "weight": 0.5,
"value": 50, "value": 50,
"flags": ["CONSUMABLE", "DESTRUCTIBLE"], "flags": ["CONSUMABLE", "DESTRUCTIBLE"],
@ -50,7 +55,8 @@
{ {
"id": "scroll_mass_health", "id": "scroll_mass_health",
"name": { "name": "scroll of mass healing word", "plural": "scrolls of mass healing word" }, "name": { "name": "scroll of mass healing word", "plural": "scrolls of mass healing word" },
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "?", "sprite": "scroll_writing", "fg": "#00FFFF", "order": 4 },
"class": "scroll",
"weight": 0.5, "weight": 0.5,
"value": 200, "value": 200,
"flags": ["CONSUMABLE", "DESTRUCTIBLE"], "flags": ["CONSUMABLE", "DESTRUCTIBLE"],
@ -60,7 +66,8 @@
{ {
"id": "scroll_magicmissile", "id": "scroll_magicmissile",
"name": { "name": "scroll of magic missile", "plural": "scrolls of magic missile" }, "name": { "name": "scroll of magic missile", "plural": "scrolls of magic missile" },
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "?", "sprite": "scroll_writing", "fg": "#00FFFF", "order": 4 },
"class": "scroll",
"weight": 0.5, "weight": 0.5,
"value": 50, "value": 50,
"flags": ["CONSUMABLE", "DESTRUCTIBLE"], "flags": ["CONSUMABLE", "DESTRUCTIBLE"],
@ -70,7 +77,8 @@
{ {
"id": "scroll_embers", "id": "scroll_embers",
"name": { "name": "scroll of embers", "plural": "scrolls of embers" }, "name": { "name": "scroll of embers", "plural": "scrolls of embers" },
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "?", "sprite": "scroll_writing", "fg": "#00FFFF", "order": 4 },
"class": "scroll",
"weight": 0.5, "weight": 0.5,
"value": 100, "value": 100,
"flags": ["CONSUMABLE", "DESTRUCTIBLE"], "flags": ["CONSUMABLE", "DESTRUCTIBLE"],
@ -80,7 +88,8 @@
{ {
"id": "scroll_fireball", "id": "scroll_fireball",
"name": { "name": "scroll of fireball", "plural": "scrolls of fireball" }, "name": { "name": "scroll of fireball", "plural": "scrolls of fireball" },
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "?", "sprite": "scroll_writing", "fg": "#00FFFF", "order": 4 },
"class": "scroll",
"weight": 0.5, "weight": 0.5,
"value": 200, "value": 200,
"flags": ["CONSUMABLE", "DESTRUCTIBLE"], "flags": ["CONSUMABLE", "DESTRUCTIBLE"],
@ -95,7 +104,8 @@
{ {
"id": "scroll_confusion", "id": "scroll_confusion",
"name": { "name": "scroll of confusion", "plural": "scrolls of confusion" }, "name": { "name": "scroll of confusion", "plural": "scrolls of confusion" },
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "?", "sprite": "scroll_writing", "fg": "#00FFFF", "order": 4 },
"class": "scroll",
"weight": 0.5, "weight": 0.5,
"value": 100, "value": 100,
"flags": ["CONSUMABLE", "DESTRUCTIBLE"], "flags": ["CONSUMABLE", "DESTRUCTIBLE"],
@ -105,7 +115,8 @@
{ {
"id": "scroll_mass_confusion", "id": "scroll_mass_confusion",
"name": { "name": "scroll of mass confusion", "plural": "scrolls of mass confusion" }, "name": { "name": "scroll of mass confusion", "plural": "scrolls of mass confusion" },
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "?", "sprite": "scroll_writing", "fg": "#00FFFF", "order": 4 },
"class": "scroll",
"weight": 0.5, "weight": 0.5,
"value": 200, "value": 200,
"flags": ["CONSUMABLE", "DESTRUCTIBLE"], "flags": ["CONSUMABLE", "DESTRUCTIBLE"],
@ -115,7 +126,8 @@
{ {
"id": "scroll_magicmap", "id": "scroll_magicmap",
"name": { "name": "scroll of magic mapping", "plural": "scrolls of magic mapping" }, "name": { "name": "scroll of magic mapping", "plural": "scrolls of magic mapping" },
"renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "?", "sprite": "scroll_writing", "fg": "#00FFFF", "order": 4 },
"class": "scroll",
"weight": 0.5, "weight": 0.5,
"value": 50, "value": 50,
"flags": ["CONSUMABLE", "DESTRUCTIBLE", "MAGICMAP"], "flags": ["CONSUMABLE", "DESTRUCTIBLE", "MAGICMAP"],
@ -125,7 +137,8 @@
{ {
"id": "equip_dagger", "id": "equip_dagger",
"name": { "name": "dagger", "plural": "daggers" }, "name": { "name": "dagger", "plural": "daggers" },
"renderable": { "glyph": ")", "fg": "#808080", "bg": "#000000", "order": 2 }, "renderable": { "glyph": ")", "sprite": "dagger", "fg": "#808080", "order": 4 },
"class": "weapon",
"weight": 1, "weight": 1,
"value": 2, "value": 2,
"flags": ["EQUIP_MELEE"], "flags": ["EQUIP_MELEE"],
@ -134,7 +147,9 @@
{ {
"id": "equip_shortsword", "id": "equip_shortsword",
"name": { "name": "shortsword", "plural": "shortswords" }, "name": { "name": "shortsword", "plural": "shortswords" },
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, "renderable": { "glyph": ")", "sprite": "shortsword", "fg": "#C0C0C0", "order": 4 },
"avatar": "a_shortsword",
"class": "weapon",
"weight": 2, "weight": 2,
"value": 10, "value": 10,
"flags": ["EQUIP_MELEE"], "flags": ["EQUIP_MELEE"],
@ -143,7 +158,8 @@
{ {
"id": "equip_rapier", "id": "equip_rapier",
"name": { "name": "rapier", "plural": "rapiers" }, "name": { "name": "rapier", "plural": "rapiers" },
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, "renderable": { "glyph": ")", "sprite": "gnome", "fg": "#C0C0C0", "order": 4 },
"class": "weapon",
"weight": 2, "weight": 2,
"value": 10, "value": 10,
"flags": ["EQUIP_MELEE"], "flags": ["EQUIP_MELEE"],
@ -152,7 +168,8 @@
{ {
"id": "equip_pitchfork", "id": "equip_pitchfork",
"name": { "name": "pitchfork", "plural": "pitchforks" }, "name": { "name": "pitchfork", "plural": "pitchforks" },
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, "renderable": { "glyph": ")", "sprite": "trident", "fg": "#C0C0C0", "order": 4 },
"class": "weapon",
"weight": 2, "weight": 2,
"value": 5, "value": 5,
"flags": ["EQUIP_MELEE"], "flags": ["EQUIP_MELEE"],
@ -161,7 +178,8 @@
{ {
"id": "equip_sickle", "id": "equip_sickle",
"name": { "name": "sickle", "plural": "sickles" }, "name": { "name": "sickle", "plural": "sickles" },
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, "renderable": { "glyph": ")", "sprite": "gnome", "fg": "#C0C0C0", "order": 4 },
"class": "weapon",
"weight": 2, "weight": 2,
"value": 5, "value": 5,
"flags": ["EQUIP_MELEE"], "flags": ["EQUIP_MELEE"],
@ -170,7 +188,8 @@
{ {
"id": "equip_handaxe", "id": "equip_handaxe",
"name": { "name": "handaxe", "plural": "handaxes" }, "name": { "name": "handaxe", "plural": "handaxes" },
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, "renderable": { "glyph": ")", "sprite": "handaxe", "fg": "#C0C0C0", "order": 4 },
"class": "weapon",
"weight": 2, "weight": 2,
"value": 5, "value": 5,
"flags": ["EQUIP_MELEE"], "flags": ["EQUIP_MELEE"],
@ -179,16 +198,29 @@
{ {
"id": "equip_longsword", "id": "equip_longsword",
"name": { "name": "longsword", "plural": "longswords" }, "name": { "name": "longsword", "plural": "longswords" },
"renderable": { "glyph": ")", "fg": "#FFF8DC", "bg": "#000000", "order": 2 }, "renderable": { "glyph": ")", "sprite": "longsword", "fg": "#FFF8DC", "order": 4 },
"avatar": "a_longsword",
"class": "weapon",
"weight": 3, "weight": 3,
"value": 15, "value": 15,
"flags": ["EQUIP_MELEE"], "flags": ["EQUIP_MELEE"],
"equip": { "flag": "STRENGTH", "damage": "1d8", "to_hit": 0 } "equip": { "flag": "STRENGTH", "damage": "1d8", "to_hit": 0 }
}, },
{
"id": "artifact_icingdeath",
"name": { "name": "Icingdeath", "plural": "Icingdeath" },
"renderable": { "glyph": ")", "sprite": "scimitar", "fg": "#37aecc", "order": 4 },
"class": "weapon",
"weight": 3,
"value": 300,
"flags": ["EQUIP_MELEE"],
"equip": { "flag": "FINESSE", "damage": "1d8+3;cold", "to_hit": 3 }
},
{ {
"id": "equip_smallshield", "id": "equip_smallshield",
"name": { "name": "buckler", "plural": "bucklers" }, "name": { "name": "buckler", "plural": "bucklers" },
"renderable": { "glyph": "[", "fg": "#808080", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "[", "sprite": "shield_small", "fg": "#808080", "order": 4 },
"class": "armour",
"weight": 2, "weight": 2,
"value": 5, "value": 5,
"flags": ["EQUIP_SHIELD"], "flags": ["EQUIP_SHIELD"],
@ -197,7 +229,9 @@
{ {
"id": "equip_mediumshield", "id": "equip_mediumshield",
"name": { "name": "medium shield", "plural": "medium shields" }, "name": { "name": "medium shield", "plural": "medium shields" },
"renderable": { "glyph": "[", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "[", "sprite": "shield_round", "fg": "#C0C0C0", "order": 4 },
"avatar": "a_medshield",
"class": "armour",
"weight": 6, "weight": 6,
"value": 10, "value": 10,
"flags": ["EQUIP_SHIELD"], "flags": ["EQUIP_SHIELD"],
@ -206,7 +240,8 @@
{ {
"id": "equip_largeshield", "id": "equip_largeshield",
"name": { "name": "large shield", "plural": "large shields" }, "name": { "name": "large shield", "plural": "large shields" },
"renderable": { "glyph": "[", "fg": "#FFF8DC", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "[", "sprite": "shield_large", "fg": "#FFF8DC", "order": 4 },
"class": "armour",
"weight": 12, "weight": 12,
"value": 35, "value": 35,
"flags": ["EQUIP_SHIELD"], "flags": ["EQUIP_SHIELD"],
@ -215,7 +250,8 @@
{ {
"id": "equip_body_weakleather", "id": "equip_body_weakleather",
"name": { "name": "leather jacket", "plural": "leather jackets" }, "name": { "name": "leather jacket", "plural": "leather jackets" },
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "[", "sprite": "body_leather", "fg": "#aa6000", "order": 4 },
"class": "armour",
"weight": 8, "weight": 8,
"value": 5, "value": 5,
"flags": ["EQUIP_BODY"], "flags": ["EQUIP_BODY"],
@ -224,7 +260,8 @@
{ {
"id": "equip_body_leather", "id": "equip_body_leather",
"name": { "name": "leather chestpiece", "plural": "leather chestpiece" }, "name": { "name": "leather chestpiece", "plural": "leather chestpiece" },
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "[", "sprite": "body_leather", "fg": "#aa6000", "order": 4 },
"class": "armour",
"weight": 10, "weight": 10,
"value": 10, "value": 10,
"flags": ["EQUIP_BODY"], "flags": ["EQUIP_BODY"],
@ -233,7 +270,8 @@
{ {
"id": "equip_body_studdedleather", "id": "equip_body_studdedleather",
"name": { "name": "studded leather chestpiece", "plural": "studded leather chestpieces" }, "name": { "name": "studded leather chestpiece", "plural": "studded leather chestpieces" },
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "[", "sprite": "body_leather", "fg": "#aa6000", "order": 4 },
"class": "armour",
"weight": 13, "weight": 13,
"value": 45, "value": 45,
"flags": ["EQUIP_BODY"], "flags": ["EQUIP_BODY"],
@ -242,7 +280,8 @@
{ {
"id": "equip_body_ringmail_o", "id": "equip_body_ringmail_o",
"name": { "name": "orcish ring mail", "plural": "orcish ring mail" }, "name": { "name": "orcish ring mail", "plural": "orcish ring mail" },
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "[", "sprite": "body_leather", "fg": "#aa6000", "order": 4 },
"class": "armour",
"weight": 45, "weight": 45,
"value": 50, "value": 50,
"flags": ["EQUIP_BODY"], "flags": ["EQUIP_BODY"],
@ -251,7 +290,8 @@
{ {
"id": "equip_body_ringmail", "id": "equip_body_ringmail",
"name": { "name": "ring mail", "plural": "ring mail" }, "name": { "name": "ring mail", "plural": "ring mail" },
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "[", "sprite": "body_leather", "fg": "#aa6000", "order": 4 },
"class": "armour",
"weight": 45, "weight": 45,
"value": 70, "value": 70,
"flags": ["EQUIP_BODY"], "flags": ["EQUIP_BODY"],
@ -260,7 +300,8 @@
{ {
"id": "equip_head_leather", "id": "equip_head_leather",
"name": { "name": "leather cap", "plural": "leather caps" }, "name": { "name": "leather cap", "plural": "leather caps" },
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "[", "sprite": "body_leather", "fg": "#aa6000", "order": 4 },
"class": "armour",
"weight": 2, "weight": 2,
"value": 10, "value": 10,
"flags": ["EQUIP_HEAD"], "flags": ["EQUIP_HEAD"],
@ -269,7 +310,8 @@
{ {
"id": "equip_head_elvish", "id": "equip_head_elvish",
"name": { "name": "elvish leather helm", "plural": "elvish leather helms" }, "name": { "name": "elvish leather helm", "plural": "elvish leather helms" },
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "[", "sprite": "body_leather", "fg": "#aa6000", "order": 4 },
"class": "armour",
"weight": 2, "weight": 2,
"value": 25, "value": 25,
"flags": ["EQUIP_HEAD"], "flags": ["EQUIP_HEAD"],
@ -278,7 +320,8 @@
{ {
"id": "equip_head_o", "id": "equip_head_o",
"name": { "name": "orcish helm", "plural": "orcish helm" }, "name": { "name": "orcish helm", "plural": "orcish helm" },
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "[", "sprite": "body_leather", "fg": "#aa6000", "order": 4 },
"class": "armour",
"weight": 6, "weight": 6,
"value": 25, "value": 25,
"flags": ["EQUIP_HEAD"], "flags": ["EQUIP_HEAD"],
@ -287,7 +330,8 @@
{ {
"id": "equip_head_iron", "id": "equip_head_iron",
"name": { "name": "iron helm", "plural": "iron helm" }, "name": { "name": "iron helm", "plural": "iron helm" },
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "[", "sprite": "body_leather", "fg": "#aa6000", "order": 4 },
"class": "armour",
"weight": 10, "weight": 10,
"value": 45, "value": 45,
"flags": ["EQUIP_HEAD"], "flags": ["EQUIP_HEAD"],
@ -296,7 +340,8 @@
{ {
"id": "equip_feet_leather", "id": "equip_feet_leather",
"name": { "name": "leather shoes", "plural": "leather shoes" }, "name": { "name": "leather shoes", "plural": "leather shoes" },
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "[", "sprite": "body_leather", "fg": "#aa6000", "order": 4 },
"class": "armour",
"weight": 2, "weight": 2,
"value": 10, "value": 10,
"flags": ["EQUIP_FEET"] "flags": ["EQUIP_FEET"]
@ -304,7 +349,8 @@
{ {
"id": "equip_feet_elvish", "id": "equip_feet_elvish",
"name": { "name": "elvish leather shoes", "plural": "elvish leather shoes" }, "name": { "name": "elvish leather shoes", "plural": "elvish leather shoes" },
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "[", "sprite": "body_leather", "fg": "#aa6000", "order": 4 },
"class": "armour",
"weight": 2, "weight": 2,
"value": 25, "value": 25,
"flags": ["EQUIP_FEET"], "flags": ["EQUIP_FEET"],
@ -313,7 +359,8 @@
{ {
"id": "equip_feet_o", "id": "equip_feet_o",
"name": { "name": "orcish boots", "plural": "orcish boots" }, "name": { "name": "orcish boots", "plural": "orcish boots" },
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "[", "sprite": "body_leather", "fg": "#aa6000", "order": 4 },
"class": "armour",
"weight": 6, "weight": 6,
"value": 25, "value": 25,
"flags": ["EQUIP_FEET"], "flags": ["EQUIP_FEET"],
@ -322,7 +369,8 @@
{ {
"id": "equip_feet_iron", "id": "equip_feet_iron",
"name": { "name": "iron boots", "plural": "iron boots" }, "name": { "name": "iron boots", "plural": "iron boots" },
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "[", "sprite": "body_leather", "fg": "#aa6000", "order": 4 },
"class": "armour",
"weight": 10, "weight": 10,
"value": 45, "value": 45,
"flags": ["EQUIP_FEET"], "flags": ["EQUIP_FEET"],
@ -331,7 +379,8 @@
{ {
"id": "equip_neck_protection", "id": "equip_neck_protection",
"name": { "name": "amulet of protection", "plural": "amulets of protection" }, "name": { "name": "amulet of protection", "plural": "amulets of protection" },
"renderable": { "glyph": "\"", "fg": "#aa6000", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "\"", "sprite": "body_leather", "fg": "#aa6000", "order": 4 },
"class": "amulet",
"weight": 1, "weight": 1,
"value": 200, "value": 200,
"flags": ["EQUIP_NECK"], "flags": ["EQUIP_NECK"],
@ -340,7 +389,8 @@
{ {
"id": "equip_back_protection", "id": "equip_back_protection",
"name": { "name": "cloak of protection", "plural": "cloaks of protection" }, "name": { "name": "cloak of protection", "plural": "cloaks of protection" },
"renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "[", "sprite": "body_leather", "fg": "#aa6000", "order": 4 },
"class": "armour",
"weight": 1, "weight": 1,
"value": 200, "value": 200,
"flags": ["EQUIP_BACK"], "flags": ["EQUIP_BACK"],
@ -349,7 +399,8 @@
{ {
"id": "wand_magicmissile", "id": "wand_magicmissile",
"name": { "name": "wand of magic missile", "plural": "wands of magic missile" }, "name": { "name": "wand of magic missile", "plural": "wands of magic missile" },
"renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "/", "sprite": "body_leather", "fg": "#00FFFF", "order": 4 },
"class": "wand",
"weight": 2, "weight": 2,
"value": 100, "value": 100,
"flags": ["CHARGES"], "flags": ["CHARGES"],
@ -359,7 +410,8 @@
{ {
"id": "wand_fireball", "id": "wand_fireball",
"name": { "name": "wand of fireball", "plural": "wands of fireball" }, "name": { "name": "wand of fireball", "plural": "wands of fireball" },
"renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "/", "sprite": "body_leather", "fg": "#00FFFF", "order": 4 },
"class": "wand",
"weight": 2, "weight": 2,
"value": 300, "value": 300,
"flags": ["CHARGES"], "flags": ["CHARGES"],
@ -369,7 +421,8 @@
{ {
"id": "wand_confusion", "id": "wand_confusion",
"name": { "name": "wand of confusion", "plural": "wands of confusion" }, "name": { "name": "wand of confusion", "plural": "wands of confusion" },
"renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "/", "sprite": "body_leather", "fg": "#00FFFF", "order": 4 },
"class": "wand",
"weight": 2, "weight": 2,
"value": 200, "value": 200,
"flags": ["CHARGES"], "flags": ["CHARGES"],
@ -379,7 +432,8 @@
{ {
"id": "wand_digging", "id": "wand_digging",
"name": { "name": "wand of digging", "plural": "wands of digging" }, "name": { "name": "wand of digging", "plural": "wands of digging" },
"renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "/", "sprite": "body_leather", "fg": "#00FFFF", "order": 4 },
"class": "wand",
"weight": 2, "weight": 2,
"value": 300, "value": 300,
"flags": ["CHARGES", "DIGGER"], "flags": ["CHARGES", "DIGGER"],
@ -389,17 +443,19 @@
{ {
"id": "food_rations", "id": "food_rations",
"name": { "name": "rations", "plural": "rations" }, "name": { "name": "rations", "plural": "rations" },
"renderable": { "glyph": "%", "fg": "#FFA07A", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "%", "sprite": "meat", "fg": "#FFA07A", "order": 4 },
"class": "comestible",
"weight": 1, "weight": 1,
"value": 1, "value": 1,
"flags": ["FOOD", "CONSUMABLE"] "flags": ["FOOD", "CONSUMABLE", "STACKABLE"]
}, },
{ {
"id": "food_apple", "id": "food_apple",
"name": { "name": "apple", "plural": "apples" }, "name": { "name": "apple", "plural": "apples" },
"renderable": { "glyph": "%", "fg": "#00FF00", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "%", "sprite": "body_leather", "fg": "#00FF00", "order": 4 },
"class": "comestible",
"weight": 0.5, "weight": 0.5,
"value": 1, "value": 1,
"flags": ["FOOD", "CONSUMABLE"] "flags": ["FOOD", "CONSUMABLE", "STACKABLE"]
} }
] ]

View file

@ -2,7 +2,7 @@
{ {
"id": "npc_barkeep", "id": "npc_barkeep",
"name": "barkeep", "name": "barkeep",
"renderable": { "glyph": "@", "fg": "#EE82EE", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "@", "sprite": "gnome", "fg": "#EE82EE", "order": 3 },
"flags": ["NEUTRAL", "IS_HUMAN"], "flags": ["NEUTRAL", "IS_HUMAN"],
"vision_range": 4, "vision_range": 4,
"quips": ["Drink?", "Something to eat?", "Don't go out on an empty stomach."] "quips": ["Drink?", "Something to eat?", "Don't go out on an empty stomach."]
@ -10,7 +10,7 @@
{ {
"id": "npc_townsperson", "id": "npc_townsperson",
"name": "townsperson", "name": "townsperson",
"renderable": { "glyph": "@", "fg": "#9fa86c", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "@", "sprite": "gnome", "fg": "#9fa86c", "order": 3 },
"flags": ["NEUTRAL", "RANDOM_PATH", "IS_HUMAN"], "flags": ["NEUTRAL", "RANDOM_PATH", "IS_HUMAN"],
"vision_range": 4, "vision_range": 4,
"quips": ["Hello!", "Good morning.", "<a quiet complaint about chores>"] "quips": ["Hello!", "Good morning.", "<a quiet complaint about chores>"]
@ -18,7 +18,7 @@
{ {
"id": "npc_drunk", "id": "npc_drunk",
"name": "drunk", "name": "drunk",
"renderable": { "glyph": "@", "fg": "#a0a83c", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "@", "sprite": "gnome", "fg": "#a0a83c", "order": 3 },
"flags": ["NEUTRAL", "IS_HUMAN"], "flags": ["NEUTRAL", "IS_HUMAN"],
"vision_range": 4, "vision_range": 4,
"quips": ["Hic!", "H-Hic'.", "Get me 'nother, would you?"] "quips": ["Hic!", "H-Hic'.", "Get me 'nother, would you?"]
@ -26,7 +26,7 @@
{ {
"id": "npc_fisher", "id": "npc_fisher",
"name": "fisher", "name": "fisher",
"renderable": { "glyph": "@", "fg": "#3ca3a8", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "@", "sprite": "gnome", "fg": "#3ca3a8", "order": 3 },
"flags": ["NEUTRAL", "IS_HUMAN"], "flags": ["NEUTRAL", "IS_HUMAN"],
"vision_range": 4, "vision_range": 4,
"quips": ["Hey."] "quips": ["Hey."]
@ -34,7 +34,7 @@
{ {
"id": "npc_dockworker", "id": "npc_dockworker",
"name": "dock worker", "name": "dock worker",
"renderable": { "glyph": "@", "fg": "#68d8de", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "@", "sprite": "gnome", "fg": "#68d8de", "order": 3 },
"flags": ["NEUTRAL", "IS_HUMAN"], "flags": ["NEUTRAL", "IS_HUMAN"],
"vision_range": 4, "vision_range": 4,
"quips": ["No boat for a few days.", "Not much for us to do."] "quips": ["No boat for a few days.", "Not much for us to do."]
@ -42,7 +42,7 @@
{ {
"id": "npc_priest", "id": "npc_priest",
"name": "priest", "name": "priest",
"renderable": { "glyph": "@", "fg": "#FFFFFF", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "@", "sprite": "gnome", "fg": "#FFFFFF", "order": 3 },
"flags": ["NEUTRAL", "IS_HUMAN"], "flags": ["NEUTRAL", "IS_HUMAN"],
"vision_range": 4, "vision_range": 4,
"quips": ["Light's givings.", "<a quiet prayer>", "Bless you."] "quips": ["Light's givings.", "<a quiet prayer>", "Bless you."]
@ -50,7 +50,7 @@
{ {
"id": "npc_miner", "id": "npc_miner",
"name": "miner", "name": "miner",
"renderable": { "glyph": "@", "fg": "#946123", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "@", "sprite": "gnome", "fg": "#946123", "order": 3 },
"flags": ["NEUTRAL", "IS_HUMAN"], "flags": ["NEUTRAL", "IS_HUMAN"],
"vision_range": 4, "vision_range": 4,
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d8" }], "attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d8" }],
@ -59,7 +59,7 @@
{ {
"id": "npc_guard", "id": "npc_guard",
"name": "smalltown guard", "name": "smalltown guard",
"renderable": { "glyph": "@", "fg": "#034efc", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "@", "sprite": "gnome", "fg": "#034efc", "order": 3 },
"flags": ["NEUTRAL", "RANDOM_PATH", "IS_HUMAN"], "flags": ["NEUTRAL", "RANDOM_PATH", "IS_HUMAN"],
"level": 2, "level": 2,
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d8" }], "attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d8" }],
@ -69,7 +69,7 @@
{ {
"id": "rat", "id": "rat",
"name": "rat", "name": "rat",
"renderable": { "glyph": "r", "fg": "#aa6000", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "r", "sprite": "rat", "fg": "#aa6000", "order": 3 },
"flags": [], "flags": [],
"bac": 6, "bac": 6,
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }], "attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }],
@ -78,7 +78,7 @@
{ {
"id": "chicken", "id": "chicken",
"name": "chicken", "name": "chicken",
"renderable": { "glyph": "c", "fg": "#BB6000", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "c", "sprite": "gnome", "fg": "#BB6000", "order": 3 },
"flags": ["HERBIVORE"], "flags": ["HERBIVORE"],
"bac": 8, "bac": 8,
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }] "attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }]
@ -86,7 +86,7 @@
{ {
"id": "deer_little", "id": "deer_little",
"name": "fawn", "name": "fawn",
"renderable": { "glyph": "q", "fg": "#a57037", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "q", "sprite": "gnome", "fg": "#a57037", "order": 3 },
"flags": ["HERBIVORE"], "flags": ["HERBIVORE"],
"bac": 8, "bac": 8,
"attacks": [{ "name": "kicks", "hit_bonus": 0, "damage": "1d2" }] "attacks": [{ "name": "kicks", "hit_bonus": 0, "damage": "1d2" }]
@ -94,7 +94,7 @@
{ {
"id": "sheep_little", "id": "sheep_little",
"name": "lamb", "name": "lamb",
"renderable": { "glyph": "q", "fg": "#e7e7e7", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "q", "sprite": "gnome", "fg": "#e7e7e7", "order": 3 },
"flags": ["HERBIVORE", "SMALL_GROUP"], "flags": ["HERBIVORE", "SMALL_GROUP"],
"bac": 10, "bac": 10,
"attacks": [{ "name": "kicks", "hit_bonus": 0, "damage": "1d2" }] "attacks": [{ "name": "kicks", "hit_bonus": 0, "damage": "1d2" }]
@ -102,7 +102,7 @@
{ {
"id": "chicken_little", "id": "chicken_little",
"name": "chick", "name": "chick",
"renderable": { "glyph": "c", "fg": "#fae478", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "c", "sprite": "gnome", "fg": "#fae478", "order": 3 },
"flags": ["HERBIVORE"], "flags": ["HERBIVORE"],
"bac": 10, "bac": 10,
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }] "attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }]
@ -110,7 +110,7 @@
{ {
"id": "horse_little", "id": "horse_little",
"name": "pony", "name": "pony",
"renderable": { "glyph": "u", "fg": "#b36c29", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "u", "sprite": "horse", "fg": "#b36c29", "order": 3 },
"flags": ["HERBIVORE", "MULTIATTACK"], "flags": ["HERBIVORE", "MULTIATTACK"],
"level": 3, "level": 3,
"bac": 6, "bac": 6,
@ -124,7 +124,7 @@
{ {
"id": "horse", "id": "horse",
"name": "horse", "name": "horse",
"renderable": { "glyph": "u", "fg": "#744d29", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "u", "sprite": "horse", "fg": "#744d29", "order": 3 },
"flags": ["MULTIATTACK"], "flags": ["MULTIATTACK"],
"level": 5, "level": 5,
"bac": 5, "bac": 5,
@ -137,7 +137,7 @@
{ {
"id": "horse_large", "id": "horse_large",
"name": "warhorse", "name": "warhorse",
"renderable": { "glyph": "u", "fg": "#8a3520", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "u", "sprite": "horse", "fg": "#8a3520", "order": 3 },
"flags": ["MULTIATTACK"], "flags": ["MULTIATTACK"],
"level": 7, "level": 7,
"bac": 4, "bac": 4,
@ -150,7 +150,7 @@
{ {
"id": "rat_giant", "id": "rat_giant",
"name": "giant rat", "name": "giant rat",
"renderable": { "glyph": "r", "fg": "#bb8000", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "r", "sprite": "rat_large", "fg": "#bb8000", "order": 3 },
"flags": ["SMALL_GROUP"], "flags": ["SMALL_GROUP"],
"level": 1, "level": 1,
"bac": 7, "bac": 7,
@ -160,7 +160,7 @@
{ {
"id": "dog_little", "id": "dog_little",
"name": "little dog", "name": "little dog",
"renderable": { "glyph": "d", "fg": "#FFFFFF", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "d", "sprite": "dog", "fg": "#FFFFFF", "order": 3 },
"flags": ["NEUTRAL"], "flags": ["NEUTRAL"],
"level": 2, "level": 2,
"bac": 6, "bac": 6,
@ -171,7 +171,7 @@
{ {
"id": "dog", "id": "dog",
"name": "dog", "name": "dog",
"renderable": { "glyph": "d", "fg": "#EEEEEE", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "d", "sprite": "dog", "fg": "#EEEEEE", "order": 3 },
"flags": [], "flags": [],
"level": 4, "level": 4,
"bac": 5, "bac": 5,
@ -181,7 +181,7 @@
{ {
"id": "dog_large", "id": "dog_large",
"name": "large dog", "name": "large dog",
"renderable": { "glyph": "d", "fg": "#DDDDDD", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "d", "sprite": "dog", "fg": "#DDDDDD", "order": 3 },
"flags": [], "flags": [],
"level": 6, "level": 6,
"bac": 4, "bac": 4,
@ -191,7 +191,7 @@
{ {
"id": "gnome", "id": "gnome",
"name": "gnome", "name": "gnome",
"renderable": { "glyph": "G", "fg": "#AA5500", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "G", "sprite": "gnome", "fg": "#AA5500", "order": 3 },
"flags": ["SMALL_GROUP", "IS_GNOME"], "flags": ["SMALL_GROUP", "IS_GNOME"],
"level": 1, "level": 1,
"speed": 6, "speed": 6,
@ -201,18 +201,17 @@
{ {
"id": "zombie_gnome", "id": "zombie_gnome",
"name": "gnome zombie", "name": "gnome zombie",
"renderable": { "glyph": "z", "fg": "#AA5500", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "z", "sprite": "gnome", "fg": "#AA5500", "order": 3 },
"flags": ["MINDLESS"], "flags": ["MINDLESS"],
"level": 1, "level": 1,
"speed": 6, "speed": 6,
"vision_range": 12,
"attacks": [{ "name": "claws", "hit_bonus": 0, "damage": "1d4" }], "attacks": [{ "name": "claws", "hit_bonus": 0, "damage": "1d4" }],
"loot": { "table": "wands", "chance": 0.05 } "loot": { "table": "wands", "chance": 0.05 }
}, },
{ {
"id": "goblin", "id": "goblin",
"name": "goblin", "name": "goblin",
"renderable": { "glyph": "g", "fg": "#00FF00", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "g", "sprite": "goblin", "fg": "#00FF00", "order": 3 },
"flags": [], "flags": [],
"level": 1, "level": 1,
"speed": 9, "speed": 9,
@ -221,7 +220,7 @@
{ {
"id": "kobold", "id": "kobold",
"name": "kobold", "name": "kobold",
"renderable": { "glyph": "k", "fg": "#AA5500", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "k", "sprite": "kobold", "fg": "#AA5500", "order": 3 },
"flags": [], "flags": [],
"level": 1, "level": 1,
"speed": 6, "speed": 6,
@ -231,41 +230,38 @@
{ {
"id": "zombie_kobold", "id": "zombie_kobold",
"name": "kobold zombie", "name": "kobold zombie",
"renderable": { "glyph": "z", "fg": "#AA5500", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "z", "sprite": "kobold", "fg": "#AA5500", "order": 3 },
"flags": ["MINDLESS"], "flags": ["MINDLESS"],
"level": 1, "level": 1,
"speed": 6, "speed": 6,
"vision_range": 12,
"attacks": [{ "name": "claws", "hit_bonus": 0, "damage": "1d4" }], "attacks": [{ "name": "claws", "hit_bonus": 0, "damage": "1d4" }],
"loot": { "table": "scrolls", "chance": 0.05 } "loot": { "table": "scrolls", "chance": 0.05 }
}, },
{ {
"id": "kobold_large", "id": "kobold_large",
"name": "large kobold", "name": "large kobold",
"renderable": { "glyph": "k", "fg": "#70461b", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "k", "sprite": "kobold_large", "fg": "#70461b", "order": 3 },
"flags": [], "flags": [],
"level": 1, "level": 1,
"speed": 6, "speed": 6,
"vision_range": 12,
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d6" }], "attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d6" }],
"loot": { "table": "food", "chance": 0.05 } "loot": { "table": "food", "chance": 0.05 }
}, },
{ {
"id": "zombie_orc", "id": "zombie_orc",
"name": "orc zombie", "name": "orc zombie",
"renderable": { "glyph": "z", "fg": "#dbd830", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "z", "sprite": "orc", "fg": "#dbd830", "order": 3 },
"flags": ["MINDLESS"], "flags": ["MINDLESS"],
"level": 2, "level": 2,
"bac": 9, "bac": 9,
"speed": 6, "speed": 6,
"vision_range": 12,
"attacks": [{ "name": "claws", "hit_bonus": 0, "damage": "1d6" }], "attacks": [{ "name": "claws", "hit_bonus": 0, "damage": "1d6" }],
"loot": { "table": "potions", "chance": 0.05 } "loot": { "table": "potions", "chance": 0.05 }
}, },
{ {
"id": "dwarf", "id": "dwarf",
"name": "dwarf", "name": "dwarf",
"renderable": { "glyph": "h", "fg": "#d61b1b", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "h", "sprite": "dwarf", "fg": "#d61b1b", "order": 3 },
"flags": ["IS_DWARF"], "flags": ["IS_DWARF"],
"level": 2, "level": 2,
"bac": 10, "bac": 10,
@ -277,30 +273,28 @@
{ {
"id": "zombie_dwarf", "id": "zombie_dwarf",
"name": "dwarf zombie", "name": "dwarf zombie",
"renderable": { "glyph": "z", "fg": "#d61b1b", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "z", "sprite": "dwarf", "fg": "#d61b1b", "order": 3 },
"flags": ["MINDLESS"], "flags": ["MINDLESS"],
"level": 2, "level": 2,
"bac": 9, "bac": 9,
"speed": 6, "speed": 6,
"vision_range": 12,
"attacks": [{ "name": "claws", "hit_bonus": 0, "damage": "1d6" }], "attacks": [{ "name": "claws", "hit_bonus": 0, "damage": "1d6" }],
"loot": { "table": "equipment", "chance": 0.05 } "loot": { "table": "equipment", "chance": 0.05 }
}, },
{ {
"id": "kobold_captain", "id": "kobold_captain",
"name": "kobold captain", "name": "kobold captain",
"renderable": { "glyph": "k", "fg": "#9331ac", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "k", "sprite": "kobold_c", "fg": "#9331ac", "order": 3 },
"flags": [], "flags": [],
"level": 2, "level": 2,
"speed": 6, "speed": 6,
"vision_range": 12,
"attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "2d4" }], "attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "2d4" }],
"loot": { "table": "food", "chance": 0.05 } "loot": { "table": "food", "chance": 0.05 }
}, },
{ {
"id": "spider_cave", "id": "spider_cave",
"name": "cave spider", "name": "cave spider",
"renderable": { "glyph": "s", "fg": "#6b6b6b", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "s", "sprite": "spider", "fg": "#6b6b6b", "order": 3 },
"flags": ["SMALL_GROUP"], "flags": ["SMALL_GROUP"],
"level": 1, "level": 1,
"bac": 3, "bac": 3,
@ -311,7 +305,7 @@
{ {
"id": "ant_worker", "id": "ant_worker",
"name": "worker ant", "name": "worker ant",
"renderable": { "glyph": "a", "fg": "#ca7631", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "a", "sprite": "ant", "fg": "#ca7631", "order": 3 },
"flags": ["SMALL_GROUP"], "flags": ["SMALL_GROUP"],
"level": 2, "level": 2,
"bac": 3, "bac": 3,
@ -322,7 +316,7 @@
{ {
"id": "ant_soldier", "id": "ant_soldier",
"name": "soldier ant", "name": "soldier ant",
"renderable": { "glyph": "a", "fg": "#ca3f26", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "a", "sprite": "ant", "fg": "#ca3f26", "order": 3 },
"flags": ["SMALL_GROUP", "POISON_RES"], "flags": ["SMALL_GROUP", "POISON_RES"],
"level": 3, "level": 3,
"bac": 3, "bac": 3,
@ -336,7 +330,7 @@
{ {
"id": "caterpillar_cave", "id": "caterpillar_cave",
"name": "caterpillar", "name": "caterpillar",
"renderable": { "glyph": "a", "fg": "#6b6b6b", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "a", "sprite": "caterpillar", "fg": "#6b6b6b", "order": 3 },
"flags": ["SMALL_GROUP"], "flags": ["SMALL_GROUP"],
"level": 1, "level": 1,
"bac": 3, "bac": 3,
@ -347,7 +341,7 @@
{ {
"id": "caterpillar_giant", "id": "caterpillar_giant",
"name": "giant caterpillar", "name": "giant caterpillar",
"renderable": { "glyph": "a", "fg": "#b9aeae", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "a", "sprite": "caterpillar", "fg": "#b9aeae", "order": 3 },
"flags": ["SMALL_GROUP"], "flags": ["SMALL_GROUP"],
"level": 2, "level": 2,
"bac": 7, "bac": 7,
@ -358,7 +352,7 @@
{ {
"id": "jackal", "id": "jackal",
"name": "jackal", "name": "jackal",
"renderable": { "glyph": "d", "fg": "#AA5500", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "d", "sprite": "dog", "fg": "#AA5500", "order": 3 },
"flags": ["CARNIVORE", "SMALL_GROUP"], "flags": ["CARNIVORE", "SMALL_GROUP"],
"bac": 7, "bac": 7,
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }] "attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }]
@ -366,7 +360,7 @@
{ {
"id": "fox", "id": "fox",
"name": "fox", "name": "fox",
"renderable": { "glyph": "d", "fg": "#FF0000", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "d", "sprite": "dog", "fg": "#FF0000", "order": 3 },
"flags": ["CARNIVORE"], "flags": ["CARNIVORE"],
"bac": 7, "bac": 7,
"attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }] "attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }]
@ -374,7 +368,7 @@
{ {
"id": "coyote", "id": "coyote",
"name": "coyote", "name": "coyote",
"renderable": { "glyph": "d", "fg": "#6E3215", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "d", "sprite": "dog", "fg": "#6E3215", "order": 3 },
"flags": ["CARNIVORE", "SMALL_GROUP"], "flags": ["CARNIVORE", "SMALL_GROUP"],
"level": 1, "level": 1,
"bac": 7, "bac": 7,
@ -383,7 +377,7 @@
{ {
"id": "wolf", "id": "wolf",
"name": "wolf", "name": "wolf",
"renderable": { "glyph": "d", "fg": "#5E4225", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "d", "sprite": "dog", "fg": "#5E4225", "order": 3 },
"flags": ["CARNIVORE"], "flags": ["CARNIVORE"],
"level": 5, "level": 5,
"bac": 4, "bac": 4,
@ -392,7 +386,7 @@
{ {
"id": "goblin_chieftain", "id": "goblin_chieftain",
"name": "goblin chieftain", "name": "goblin chieftain",
"renderable": { "glyph": "g", "fg": "#9331ac", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "g", "sprite": "goblin_c", "fg": "#9331ac", "order": 3 },
"flags": [], "flags": [],
"level": 2, "level": 2,
"speed": 9, "speed": 9,
@ -402,7 +396,7 @@
{ {
"id": "orc", "id": "orc",
"name": "orc", "name": "orc",
"renderable": { "glyph": "o", "fg": "#00FF00", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "o", "sprite": "orc", "fg": "#00FF00", "order": 3 },
"flags": ["SMALL_GROUP"], "flags": ["SMALL_GROUP"],
"level": 1, "level": 1,
"speed": 9, "speed": 9,
@ -412,7 +406,7 @@
{ {
"id": "orc_hill", "id": "orc_hill",
"name": "hill orc", "name": "hill orc",
"renderable": { "glyph": "o", "fg": "#dbd830", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "o", "sprite": "orc", "fg": "#dbd830", "order": 3 },
"flags": ["LARGE_GROUP"], "flags": ["LARGE_GROUP"],
"level": 2, "level": 2,
"speed": 9, "speed": 9,
@ -422,7 +416,7 @@
{ {
"id": "orc_captain", "id": "orc_captain",
"name": "orc captain", "name": "orc captain",
"renderable": { "glyph": "o", "fg": "#9331ac", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "o", "sprite": "orc", "fg": "#9331ac", "order": 3 },
"flags": ["MULTIATTACK"], "flags": ["MULTIATTACK"],
"level": 5, "level": 5,
"speed": 5, "speed": 5,
@ -435,7 +429,7 @@
{ {
"id": "warg", "id": "warg",
"name": "warg", "name": "warg",
"renderable": { "glyph": "d", "fg": "#8b7164", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "d", "sprite": "dog", "fg": "#8b7164", "order": 3 },
"flags": ["SMALL_GROUP"], "flags": ["SMALL_GROUP"],
"level": 7, "level": 7,
"bac": 4, "bac": 4,
@ -446,7 +440,7 @@
{ {
"id": "jaguar", "id": "jaguar",
"name": "jaguar", "name": "jaguar",
"renderable": { "glyph": "f", "fg": "#d3b947", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "f", "sprite": "cat_large", "fg": "#d3b947", "order": 3 },
"flags": ["MULTIATTACK"], "flags": ["MULTIATTACK"],
"level": 4, "level": 4,
"bac": 6, "bac": 6,
@ -461,7 +455,7 @@
{ {
"id": "lynx", "id": "lynx",
"name": "lynx", "name": "lynx",
"renderable": { "glyph": "f", "fg": "#b5d347", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "f", "sprite": "cat_large", "fg": "#b5d347", "order": 3 },
"flags": ["MULTIATTACK"], "flags": ["MULTIATTACK"],
"level": 5, "level": 5,
"bac": 6, "bac": 6,
@ -476,7 +470,7 @@
{ {
"id": "panther", "id": "panther",
"name": "panther", "name": "panther",
"renderable": { "glyph": "f", "fg": "#58554e", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "f", "sprite": "cat_large", "fg": "#58554e", "order": 3 },
"flags": ["MULTIATTACK"], "flags": ["MULTIATTACK"],
"level": 5, "level": 5,
"bac": 6, "bac": 6,
@ -491,7 +485,7 @@
{ {
"id": "ogre", "id": "ogre",
"name": "ogre", "name": "ogre",
"renderable": { "glyph": "O", "fg": "#10A70d", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "O", "sprite": "ogre", "fg": "#10A70d", "order": 3 },
"flags": ["SMALL_GROUP"], "flags": ["SMALL_GROUP"],
"level": 5, "level": 5,
"bac": 5, "bac": 5,
@ -502,7 +496,7 @@
{ {
"id": "treant_small", "id": "treant_small",
"name": "treant sapling", "name": "treant sapling",
"renderable": { "glyph": "♠️", "fg": "#10570d", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "♠️", "sprite": "gnome", "fg": "#10570d", "order": 3 },
"flags": ["LARGE_GROUP", "GREEN_BLOOD", "FIRE_WEAK"], "flags": ["LARGE_GROUP", "GREEN_BLOOD", "FIRE_WEAK"],
"level": 2, "level": 2,
"bac": 12, "bac": 12,

View file

@ -2,83 +2,89 @@
{ {
"id": "door", "id": "door",
"name": "door", "name": "door",
"renderable": { "glyph": "+", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "+", "sprite": "door", "alt": "door_open", "fg": "#00FFFF", "order": 5 },
"flags": ["DOOR"] "door": { "open": false, "locked": false, "blocks_vis": true, "blocks_move": true }
},
{
"id": "trapdoor",
"name": "trapdoor",
"renderable": { "glyph": "+", "sprite": "trapdoor", "alt": "trapdoor_open", "fg": "#00FFFF", "order": 5, "alt_order": 1 },
"door": { "open": false, "locked": false, "blocks_vis": false, "blocks_move": false }
}, },
{ {
"id": "prop_altar", "id": "prop_altar",
"name": "altar", "name": "altar",
"renderable": { "glyph": "_", "fg": "#FFFFFF", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "_", "sprite": "altar", "fg": "#FFFFFF", "order": 5 },
"flags": ["ENTRY_TRIGGER"], "flags": ["ENTRY_TRIGGER"],
"effects": { "heal": "8d8" } "effects": { "heal": "8d8" }
}, },
{ {
"id": "prop_keg", "id": "prop_keg",
"name": "keg", "name": "keg",
"renderable": { "glyph": "φ", "fg": "#AAAAAA", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "φ", "sprite": "gnome", "fg": "#AAAAAA", "order": 5 },
"flags": [] "flags": []
}, },
{ {
"id": "prop_table", "id": "prop_table",
"name": "table", "name": "table",
"renderable": { "glyph": "-", "fg": "#AAAAAA", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "-", "sprite": "table", "fg": "#a76d3d", "order": 5 },
"flags": [] "flags": []
}, },
{ {
"id": "prop_hay", "id": "prop_hay",
"name": "hay", "name": "hay",
"renderable": { "glyph": "%", "fg": "#c7ad39", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "%", "sprite": "plants", "fg": "#e2b82f", "order": 5 },
"flags": [] "flags": []
}, },
{ {
"id": "prop_statue", "id": "prop_statue",
"name": "statue", "name": "statue",
"renderable": { "glyph": "@", "fg": "#ffffff", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "@", "sprite": "altar", "fg": "#ffffff", "order": 5 },
"flags": [] "flags": []
}, },
{ {
"id": "prop_bed", "id": "prop_bed",
"name": "bed", "name": "bed",
"renderable": { "glyph": "=", "fg": "#AAAAAA", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "=", "sprite": "bed", "fg": "#a55d33", "order": 5 },
"flags": [] "flags": []
}, },
{ {
"id": "prop_chair", "id": "prop_chair",
"name": "chair", "name": "chair",
"renderable": { "glyph": "└", "fg": "#AAAAAA", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "└", "sprite": "chair_r", "fg": "#a76d3d", "order": 5 },
"flags": [] "flags": []
}, },
{ {
"id": "prop_candle", "id": "prop_candle",
"name": "candle", "name": "candle",
"renderable": { "glyph": "Ä", "fg": "#FFA500", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "Ä", "sprite": "candelabra", "fg": "#FFA500", "order": 4 },
"flags": [] "flags": []
}, },
{ {
"id": "trap_bear", "id": "trap_bear",
"name": "bear trap", "name": "bear trap",
"renderable": { "glyph": "^", "fg": "#e6e6e6", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "^", "sprite": "beartrap", "fg": "#e6e6e6", "order": 5 },
"flags": ["HIDDEN", "ENTRY_TRIGGER", "SINGLE_ACTIVATION"], "flags": ["HIDDEN", "ENTRY_TRIGGER", "SINGLE_ACTIVATION"],
"effects": { "damage": "2d4" } "effects": { "damage": "2d4" }
}, },
{ {
"id": "trap_mini_mine", "id": "trap_mini_mine",
"name": "mini-mine", "name": "mini-mine",
"renderable": { "glyph": "^", "fg": "#ff1e00", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "^", "sprite": "minimine", "fg": "#ff1e00", "order": 5 },
"flags": ["ENTRY_TRIGGER", "SINGLE_ACTIVATION"], "flags": ["ENTRY_TRIGGER", "SINGLE_ACTIVATION"],
"effects": { "damage": "2d4", "aoe": "3" } "effects": { "damage": "2d4", "aoe": "3" }
}, },
{ {
"id": "trap_stonefall", "id": "trap_stonefall",
"name": "stonefall trap", "name": "stonefall trap",
"renderable": { "glyph": "^", "fg": "#beb5a7", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "^", "sprite": "stones", "fg": "#beb5a7", "order": 5 },
"flags": ["HIDDEN", "ENTRY_TRIGGER", "SINGLE_ACTIVATION"], "flags": ["HIDDEN", "ENTRY_TRIGGER", "SINGLE_ACTIVATION"],
"effects": { "damage": "2d10" } "effects": { "damage": "2d10" }
}, },
{ {
"id": "trap_confusion", "id": "trap_confusion",
"name": "magic trap", "name": "magic trap",
"renderable": { "glyph": "^", "fg": "#df07df", "bg": "#000000", "order": 2 }, "renderable": { "glyph": "^", "sprite": "magic_trap", "fg": "#df07df", "order": 5 },
"flags": ["HIDDEN", "ENTRY_TRIGGER", "SINGLE_ACTIVATION"], "flags": ["HIDDEN", "ENTRY_TRIGGER", "SINGLE_ACTIVATION"],
"effects": { "confusion": "3" } "effects": { "confusion": "3" }
} }

2556
resources/atlas.json Normal file

File diff suppressed because it is too large Load diff

BIN
resources/atlas.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resources/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resources/sounds/hit.wav Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

7482
resources/td.json Normal file

File diff suppressed because it is too large Load diff

BIN
resources/td.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -45,6 +45,7 @@ impl<'a> System<'a> for ApproachAI {
continue; continue;
}; };
let mut path: Option<NavigationPath> = None; let mut path: Option<NavigationPath> = None;
let mut curr_abs_diff = 100;
let idx = map.xy_idx(pos.x, pos.y); let idx = map.xy_idx(pos.x, pos.y);
for tar_idx in target_idxs { for tar_idx in target_idxs {
let potential_path = a_star_search(idx, tar_idx, &mut *map); let potential_path = a_star_search(idx, tar_idx, &mut *map);
@ -54,6 +55,17 @@ impl<'a> System<'a> for ApproachAI {
potential_path.steps.len() < path.as_ref().unwrap().steps.len() potential_path.steps.len() < path.as_ref().unwrap().steps.len()
{ {
path = Some(potential_path); path = Some(potential_path);
let (x1, y1) = (pos.x, pos.y);
let (x2, y2) = ((tar_idx as i32) % map.width, (tar_idx as i32) / map.width);
curr_abs_diff = i32::abs(x2 - x1) + i32::abs(y2 - y1);
} else if potential_path.steps.len() == path.as_ref().unwrap().steps.len() {
let (x1, y1) = (pos.x, pos.y);
let (x2, y2) = ((tar_idx as i32) % map.width, (tar_idx as i32) / map.width);
let abs_diff = i32::abs(x2 - x1) + i32::abs(y2 - y1);
if abs_diff < curr_abs_diff {
path = Some(potential_path);
curr_abs_diff = abs_diff;
}
} }
} }
} }

View file

@ -1,7 +1,7 @@
use crate::{ gamelog, Attributes, Burden, EquipmentChanged, Equipped, InBackpack, Item, Pools }; use crate::{ gamelog, Attributes, Burden, EquipmentChanged, Equipped, InBackpack, Item, Pools };
use specs::prelude::*; use specs::prelude::*;
use std::collections::HashMap; use std::collections::HashMap;
use crate::data::entity::CARRY_CAPACITY_PER_STRENGTH; use crate::consts::entity::CARRY_CAPACITY_PER_STRENGTH;
pub struct EncumbranceSystem {} pub struct EncumbranceSystem {}
@ -20,7 +20,17 @@ impl<'a> System<'a> for EncumbranceSystem {
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
let (mut equip_dirty, entities, items, backpacks, wielded, mut pools, attributes, player, mut burdened) = data; let (
mut equip_dirty,
entities,
items,
backpacks,
wielded,
mut pools,
attributes,
player,
mut burdened,
) = data;
if equip_dirty.is_empty() { if equip_dirty.is_empty() {
return; return;
} }
@ -50,7 +60,7 @@ impl<'a> System<'a> for EncumbranceSystem {
pool.weight = *weight; pool.weight = *weight;
if let Some(attr) = attributes.get(*entity) { if let Some(attr) = attributes.get(*entity) {
let carry_capacity_lbs = let carry_capacity_lbs =
(attr.strength.base + attr.strength.modifiers) * CARRY_CAPACITY_PER_STRENGTH; (attr.strength.base + attr.strength.bonuses) * CARRY_CAPACITY_PER_STRENGTH;
if (pool.weight as i32) > 3 * carry_capacity_lbs { if (pool.weight as i32) > 3 * carry_capacity_lbs {
// Overloaded // Overloaded
burdened burdened

View file

@ -1,4 +1,4 @@
use crate::data::entity::*; use crate::consts::entity::*;
use crate::{ use crate::{
Burden, Burden,
BurdenLevel, BurdenLevel,
@ -15,7 +15,7 @@ use crate::{
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use specs::prelude::*; use specs::prelude::*;
use crate::config::CONFIG; use crate::config::CONFIG;
use crate::data::events::*; use crate::consts::events::*;
pub struct EnergySystem {} pub struct EnergySystem {}

View file

@ -12,7 +12,7 @@ use crate::{
Intrinsics, Intrinsics,
}; };
use specs::prelude::*; use specs::prelude::*;
use crate::data::events::*; use crate::consts::events::*;
pub struct RegenSystem {} pub struct RegenSystem {}
@ -121,7 +121,8 @@ fn try_hp_regen_tick(pool: &mut Pools, amount: i32) {
fn get_mana_regen_per_tick(e: Entity, attributes: &ReadStorage<Attributes>) -> i32 { fn get_mana_regen_per_tick(e: Entity, attributes: &ReadStorage<Attributes>) -> i32 {
let regen = if let Some(attributes) = attributes.get(e) { let regen = if let Some(attributes) = attributes.get(e) {
(attributes.intelligence.bonus + attributes.wisdom.bonus) / 2 + MIN_MP_REGEN_PER_TURN (attributes.intelligence.modifier() + attributes.wisdom.modifier()) / 2 +
MIN_MP_REGEN_PER_TURN
} else { } else {
MIN_MP_REGEN_PER_TURN MIN_MP_REGEN_PER_TURN
}; };

View file

@ -12,7 +12,7 @@ use crate::{
}; };
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use specs::prelude::*; use specs::prelude::*;
use crate::data::events::*; use crate::consts::events::*;
pub struct TurnStatusSystem {} pub struct TurnStatusSystem {}
@ -83,8 +83,8 @@ impl<'a> System<'a> for TurnStatusSystem {
None, None,
EffectType::Particle { EffectType::Particle {
glyph: to_cp437('!'), glyph: to_cp437('!'),
sprite: "gnome".to_string(), // FIXME: REMOVE THE GNOMES
fg: RGB::named(LIGHT_BLUE), fg: RGB::named(LIGHT_BLUE),
bg: RGB::named(BLACK),
lifespan: 200.0, lifespan: 200.0,
delay: 0.0, delay: 0.0,
}, },
@ -113,8 +113,8 @@ impl<'a> System<'a> for TurnStatusSystem {
None, None,
EffectType::Particle { EffectType::Particle {
glyph: to_cp437('?'), glyph: to_cp437('?'),
sprite: "gnome".to_string(), // FIXME: REMOVE THE GNOMES
fg: RGB::named(MAGENTA), fg: RGB::named(MAGENTA),
bg: RGB::named(BLACK),
lifespan: 200.0, lifespan: 200.0,
delay: 0.0, delay: 0.0,
}, },

View file

@ -1,14 +1,66 @@
use super::{ Hidden, Map, Mind, Position, Prop, Renderable }; use super::{ Hidden, Map, Mind, Position, Prop, Renderable, Pools };
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use specs::prelude::*; use specs::prelude::*;
use std::ops::Mul; use std::ops::Mul;
use super::consts::visuals::{ TILES_IN_VIEWPORT_H, TILES_IN_VIEWPORT_W };
use super::consts::prelude::*;
const SHOW_BOUNDARIES: bool = false; const SHOW_BOUNDARIES: bool = false;
pub fn get_screen_bounds(ecs: &World, _ctx: &mut BTerm) -> (i32, i32, i32, i32, i32, i32) { pub struct Offsets {
let player_pos = ecs.fetch::<Point>(); pub x: i32,
pub y: i32,
}
pub fn get_offset() -> Offsets {
return Offsets { x: 1, y: 8 };
}
pub struct ScreenBounds {
pub min_x: i32,
pub max_x: i32,
pub min_y: i32,
pub max_y: i32,
pub x_offset: i32,
pub y_offset: i32,
}
pub struct ScreenBoundsf32 {
pub min_x: f32,
pub max_x: f32,
pub min_y: f32,
pub max_y: f32,
pub x_offset: f32,
pub y_offset: f32,
}
impl ScreenBounds {
pub fn to_px(&self) -> ScreenBoundsf32 {
ScreenBoundsf32 {
min_x: (self.min_x as f32) * TILESIZE.sprite_x,
max_x: (self.max_x as f32) * TILESIZE.sprite_x,
min_y: (self.min_y as f32) * TILESIZE.sprite_y,
max_y: (self.max_y as f32) * TILESIZE.sprite_y,
x_offset: (self.x_offset as f32) * TILESIZE.x,
y_offset: (self.y_offset as f32) * TILESIZE.x,
}
}
}
pub fn get_screen_bounds(ecs: &World, debug: bool) -> ScreenBounds {
let map = ecs.fetch::<Map>(); let map = ecs.fetch::<Map>();
let (x_chars, y_chars, mut x_offset, mut y_offset) = (69, 41, 1, 10); let player_pos = if !debug {
*ecs.fetch::<Point>()
} else {
Point::new(map.width / 2, map.height / 2)
};
let (x_chars, y_chars, mut x_offset, mut y_offset) = (
TILES_IN_VIEWPORT_W,
TILES_IN_VIEWPORT_H,
1,
8,
);
let centre_x = (x_chars / 2) as i32; let centre_x = (x_chars / 2) as i32;
let centre_y = (y_chars / 2) as i32; let centre_y = (y_chars / 2) as i32;
@ -28,33 +80,60 @@ pub fn get_screen_bounds(ecs: &World, _ctx: &mut BTerm) -> (i32, i32, i32, i32,
let max_x = min_x + (x_chars as i32); let max_x = min_x + (x_chars as i32);
let max_y = min_y + (y_chars as i32); let max_y = min_y + (y_chars as i32);
(min_x, max_x, min_y, max_y, x_offset, y_offset) ScreenBounds { min_x, max_x, min_y, max_y, x_offset, y_offset }
}
use crate::consts::TILESIZE;
pub fn in_bounds(x: i32, y: i32, min_x: i32, min_y: i32, upper_x: i32, upper_y: i32) -> bool {
x >= min_x && x < upper_x && y >= min_y && y < upper_y
} }
pub fn render_camera(ecs: &World, ctx: &mut BTerm) { pub fn render_camera(ecs: &World, ctx: &mut BTerm) {
let map = ecs.fetch::<Map>(); let map = ecs.fetch::<Map>();
let (min_x, max_x, min_y, max_y, x_offset, y_offset) = get_screen_bounds(ecs, ctx); let bounds = get_screen_bounds(ecs, false);
// Render map // Render map
let mut y = 0; let mut y = 0;
for t_y in min_y..max_y { for t_y in bounds.min_y..bounds.max_y {
let mut x = 0; let mut x = 0;
for t_x in min_x..max_x { for t_x in bounds.min_x..bounds.max_x {
if t_x >= 0 && t_x < map.width && t_y >= 0 && t_y < map.height { if t_x >= 0 && t_x < map.width && t_y >= 0 && t_y < map.height {
let idx = map.xy_idx(t_x, t_y); let idx = map.xy_idx(t_x, t_y);
if map.revealed_tiles[idx] { if map.revealed_tiles[idx] {
if 1 == 2 {
let (glyph, fg, bg) = crate::map::themes::get_tile_renderables_for_id( let (glyph, fg, bg) = crate::map::themes::get_tile_renderables_for_id(
idx, idx,
&*map, &*map,
Some(*ecs.fetch::<Point>()), Some(*ecs.fetch::<Point>()),
None None
); );
ctx.set(x + x_offset, y + y_offset, fg, bg, glyph); ctx.set(x + bounds.x_offset, y + bounds.y_offset, fg, bg, glyph);
} else {
ctx.set_active_console(0);
let (id, tint) = crate::map::themes::get_sprite_for_id(
idx,
&*map,
Some(*ecs.fetch::<Point>())
);
ctx.add_sprite(
Rect::with_size(
x * 16 + bounds.x_offset * 16,
y * 16 + bounds.y_offset * 16,
16,
16
),
0,
RGBA::named(WHITE),
0 // Ya
);
ctx.set_active_console(TILE_LAYER);
}
} }
} else if SHOW_BOUNDARIES { } else if SHOW_BOUNDARIES {
ctx.set( ctx.set(
x + x_offset, x + bounds.x_offset,
y + y_offset, y + bounds.y_offset,
RGB::named(DARKSLATEGRAY), RGB::named(DARKSLATEGRAY),
RGB::named(BLACK), RGB::named(BLACK),
to_cp437('#') to_cp437('#')
@ -67,8 +146,11 @@ pub fn render_camera(ecs: &World, ctx: &mut BTerm) {
// Render entities // Render entities
{ {
ctx.set_active_console(ENTITY_LAYER);
let positions = ecs.read_storage::<Position>(); let positions = ecs.read_storage::<Position>();
let renderables = ecs.read_storage::<Renderable>(); let renderables = ecs.read_storage::<Renderable>();
let pools = ecs.read_storage::<Pools>();
let minds = ecs.read_storage::<Mind>(); let minds = ecs.read_storage::<Mind>();
let hidden = ecs.read_storage::<Hidden>(); let hidden = ecs.read_storage::<Hidden>();
let props = ecs.write_storage::<Prop>(); let props = ecs.write_storage::<Prop>();
@ -79,22 +161,22 @@ pub fn render_camera(ecs: &World, ctx: &mut BTerm) {
data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order)); data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order));
for (pos, render, ent, _hidden) in data.iter() { for (pos, render, ent, _hidden) in data.iter() {
let idx = map.xy_idx(pos.x, pos.y); let idx = map.xy_idx(pos.x, pos.y);
let entity_offset_x = pos.x - min_x; let entity_offset_x = pos.x - bounds.min_x;
let entity_offset_y = pos.y - min_y; let entity_offset_y = pos.y - bounds.min_y;
if pos.x < max_x && pos.y < max_y && pos.x >= min_x && pos.y >= min_y { if
pos.x < bounds.max_x &&
pos.y < bounds.max_y &&
pos.x >= bounds.min_x &&
pos.y >= bounds.min_y
{
let mut draw = false; let mut draw = false;
let mut fg = render.fg; let mut fg = render.fg;
let mut bg = crate::map::themes::get_tile_renderables_for_id( let bg = BLACK;
idx,
&*map,
Some(*ecs.fetch::<Point>()),
None
).2;
// Draw entities on visible tiles // Draw entities on visible tiles
if map.visible_tiles[idx] { if map.visible_tiles[idx] {
draw = true; draw = true;
} else { } else {
fg = fg.mul(crate::data::visuals::NON_VISIBLE_MULTIPLIER); fg = fg.mul(crate::consts::visuals::NON_VISIBLE_MULTIPLIER);
// We don't darken BG, because get_tile_renderables_for_id handles this. // We don't darken BG, because get_tile_renderables_for_id handles this.
} }
@ -104,9 +186,6 @@ pub fn render_camera(ecs: &World, ctx: &mut BTerm) {
let has_mind = minds.get(*ent); let has_mind = minds.get(*ent);
if let Some(_) = has_mind { if let Some(_) = has_mind {
draw = true; draw = true;
if !map.revealed_tiles[idx] {
bg = RGB::named(BLACK);
}
} }
} }
} }
@ -118,17 +197,52 @@ pub fn render_camera(ecs: &World, ctx: &mut BTerm) {
} }
} }
if draw { if draw {
/* if let Some(sprite) = render.sprite {
ctx.set_active_console(0);
ctx.add_sprite(
Rect::with_size(
entity_offset_x * 16 + bounds.x_offset * 16,
entity_offset_y * 16 + bounds.y_offset * 16,
16,
16
),
render.render_order,
RGBA::named(WHITE),
sprite
);
ctx.set_active_console(ENTITY_LAYER);
} else */ {
ctx.set( ctx.set(
entity_offset_x + x_offset, entity_offset_x + bounds.x_offset,
entity_offset_y + y_offset, entity_offset_y + bounds.y_offset,
fg, fg,
bg, bg,
render.glyph render.glyph
); );
} }
if let Some(pool) = pools.get(*ent) {
if pool.hit_points.current < pool.hit_points.max {
ctx.set_active_console(HP_BAR_LAYER);
crate::gui::draw_lerping_bar(
ctx,
(entity_offset_x + bounds.x_offset) * 16 + 2,
(entity_offset_y + bounds.y_offset) * 16 - 1,
14,
pool.hit_points.current,
pool.hit_points.max,
RGB::named(GREEN),
RGB::named(RED),
false,
false
);
ctx.set_active_console(ENTITY_LAYER);
} }
} }
} }
}
}
ctx.set_active_console(TILE_LAYER);
}
} }
pub fn render_debug_map(map: &Map, ctx: &mut BTerm) { pub fn render_debug_map(map: &Map, ctx: &mut BTerm) {

View file

@ -20,7 +20,7 @@ pub struct SerializationHelper {
#[derive(Component, Serialize, Deserialize, Clone)] #[derive(Component, Serialize, Deserialize, Clone)]
pub struct DMSerializationHelper { pub struct DMSerializationHelper {
pub map: super::map::MasterDungeonMap, pub map: super::map::MasterDungeonMap,
pub log: Vec<Vec<crate::gamelog::LogFragment>>, pub log: std::collections::BTreeMap<i32, Vec<crate::gamelog::LogFragment>>,
pub event_counts: HashMap<String, i32>, pub event_counts: HashMap<String, i32>,
pub events: HashMap<u32, Vec<String>>, pub events: HashMap<u32, Vec<String>>,
} }
@ -38,12 +38,91 @@ pub struct OtherLevelPosition {
pub id: i32, pub id: i32,
} }
#[derive(Component, ConvertSaveload, Clone)] #[derive(Debug, Component, ConvertSaveload, Clone)]
pub struct Renderable { pub struct Renderable {
pub glyph: FontCharType, pub glyph: FontCharType, // Legacy, and for drawing the morgue map.
pub sprite: String,
pub sprite_alt: Option<String>,
pub fg: RGB, pub fg: RGB,
pub bg: RGB, pub fg_alt: Option<RGB>,
pub render_order: i32, pub render_order: i32,
pub render_order_alt: Option<i32>,
pub offset: (f32, f32),
pub offset_alt: Option<(f32, f32)>,
// 0 = always on top: particle effects
// 1 = things that should appear infront of the player: railings, etc.
// 2 = the player
// 3 = other mobs
// 4 = interactable items
// 5 = other props: table, etc.
}
impl Renderable {
pub fn new(glyph: FontCharType, sprite: String, fg: RGB, render_order: i32) -> Self {
Self {
glyph,
sprite,
sprite_alt: None,
fg,
fg_alt: None,
render_order,
render_order_alt: None,
offset: (0.0, 0.0),
offset_alt: None,
}
}
pub fn swap(&mut self) {
let sprite = self.swap_sprite();
let fg = self.swap_fg();
let render_order = self.swap_render_order();
let offset = self.swap_offset();
let did_something = sprite || fg || render_order || offset;
if !did_something {
unreachable!(
".swap() was called on a Renderable component, but nothing happened. {:?}",
self
);
}
}
pub fn swap_sprite(&mut self) -> bool {
if let Some(sprite_alt) = &mut self.sprite_alt {
std::mem::swap(&mut self.sprite, sprite_alt);
return true;
}
false
}
pub fn swap_fg(&mut self) -> bool {
if let Some(fg_alt) = &mut self.fg_alt {
std::mem::swap(&mut self.fg, fg_alt);
return true;
}
false
}
pub fn swap_render_order(&mut self) -> bool {
if let Some(render_order_alt) = &mut self.render_order_alt {
std::mem::swap(&mut self.render_order, render_order_alt);
return true;
}
false
}
pub fn swap_offset(&mut self) -> bool {
if let Some(offset_alt) = &mut self.offset_alt {
std::mem::swap(&mut self.offset, offset_alt);
return true;
}
false
}
}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Avatar {
pub sprite: String,
}
impl Avatar {
pub fn new(sprite: String) -> Self {
Self { sprite }
}
} }
#[derive(Component, Debug, Serialize, Deserialize, Clone)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
@ -131,6 +210,9 @@ pub struct BlocksVisibility {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Door { pub struct Door {
pub open: bool, pub open: bool,
pub locked: bool,
pub blocks_vis: bool,
pub blocks_move: bool,
} }
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq)] #[derive(Serialize, Deserialize, Copy, Clone, PartialEq)]
@ -182,8 +264,36 @@ pub struct Pools {
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Attribute { pub struct Attribute {
pub base: i32, pub base: i32,
pub modifiers: i32, pub bonuses: i32,
pub bonus: i32, pub exercise: i32,
}
impl Attribute {
pub fn new(base: i32) -> Self {
Self {
base,
bonuses: 0,
exercise: 0,
}
}
// Raw attribute score. e.g. 10 base, + 4 from armour: 14 strength.
pub fn current(&self) -> i32 {
self.base + self.bonuses
}
// Attribute bonus. e.g. 14 strength = +2, 8 strength = -1
pub fn modifier(&self) -> i32 {
crate::gamesystem::attr_bonus(self.current())
}
pub fn improve(&mut self) {
if self.exercise < 50 {
self.exercise += 1;
}
}
pub fn abuse(&mut self) {
if self.exercise > -50 {
self.exercise -= 1;
}
}
} }
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
@ -215,7 +325,6 @@ pub struct GrantsSpell {
} }
// TODO: GrantsIntrinsic, Intrinsics, etc. ? Done the same way as spells? // TODO: GrantsIntrinsic, Intrinsics, etc. ? Done the same way as spells?
#[derive(Component, Debug, Serialize, Deserialize, Clone)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Attributes { pub struct Attributes {
pub strength: Attribute, pub strength: Attribute,
@ -226,6 +335,93 @@ pub struct Attributes {
pub charisma: Attribute, pub charisma: Attribute,
} }
impl Attributes {
pub const STR: i32 = 0;
pub const DEX: i32 = 1;
pub const CON: i32 = 2;
pub const INT: i32 = 3;
pub const WIS: i32 = 4;
pub const CHA: i32 = 5;
pub fn default() -> Self {
Self {
strength: Attribute::new(10),
dexterity: Attribute::new(10),
constitution: Attribute::new(10),
intelligence: Attribute::new(10),
wisdom: Attribute::new(10),
charisma: Attribute::new(10),
}
}
pub fn with_stats(str: i32, dex: i32, con: i32, int: i32, wis: i32, cha: i32) -> Self {
Self {
strength: Attribute::new(str),
dexterity: Attribute::new(dex),
constitution: Attribute::new(con),
intelligence: Attribute::new(int),
wisdom: Attribute::new(wis),
charisma: Attribute::new(cha),
}
}
pub fn attr_from_index(&self, attr: i32) -> &Attribute {
match attr {
Self::STR => &self.strength,
Self::DEX => &self.dexterity,
Self::CON => &self.constitution,
Self::INT => &self.intelligence,
Self::WIS => &self.wisdom,
Self::CHA => &self.charisma,
_ => unreachable!("Tried to get an attribute that doesn't exist."),
}
}
pub fn exercise(&mut self, attr: i32, improve: bool) {
match attr {
Self::STR => {
if improve {
self.strength.improve();
} else {
self.strength.abuse();
}
}
Self::DEX => {
if improve {
self.dexterity.improve();
} else {
self.dexterity.abuse();
}
}
Self::CON => {
if improve {
self.constitution.improve();
} else {
self.constitution.abuse();
}
}
Self::INT => {
if improve {
self.intelligence.improve();
} else {
self.intelligence.abuse();
}
}
Self::WIS => {
if improve {
self.wisdom.improve();
} else {
self.wisdom.abuse();
}
}
Self::CHA => {
if improve {
self.charisma.improve();
} else {
self.charisma.abuse();
}
}
_ => unreachable!("Tried to exercise an attribute that doesn't exist."),
}
}
}
#[derive(Component, Debug, ConvertSaveload, Clone)] #[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct WantsToMelee { pub struct WantsToMelee {
pub target: Entity, pub target: Entity,
@ -258,10 +454,40 @@ pub struct Beatitude {
pub known: bool, pub known: bool,
} }
#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)]
pub enum ItemType {
Amulet,
Weapon,
Armour,
Comestible,
Scroll,
Spellbook,
Potion,
Ring,
Wand,
}
impl ItemType {
pub fn string(&self) -> &str {
match self {
ItemType::Amulet => "Amulets",
ItemType::Weapon => "Weapons",
ItemType::Armour => "Armour",
ItemType::Comestible => "Comestibles",
ItemType::Scroll => "Scrolls",
ItemType::Spellbook => "Spellbooks",
ItemType::Potion => "Potions",
ItemType::Ring => "Rings",
ItemType::Wand => "Wands",
}
}
}
#[derive(Component, Debug, Serialize, Deserialize, Clone)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Item { pub struct Item {
pub weight: f32, // in lbs pub weight: f32, // in lbs
pub value: f32, // base pub value: f32, // base
pub category: ItemType,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
@ -289,9 +515,23 @@ pub struct IdentifiedItem {
pub name: String, pub name: String,
} }
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Stackable {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct EquipmentChanged {} pub struct EquipmentChanged {}
#[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(PartialEq, Copy, Clone, Serialize, Deserialize)] #[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
pub enum BurdenLevel { pub enum BurdenLevel {
Burdened, Burdened,
@ -509,6 +749,9 @@ pub struct InBackpack {
pub owner: Entity, pub owner: Entity,
} }
#[derive(Component, Serialize, Deserialize, Clone)]
pub struct WantsToAssignKey {}
#[derive(Component, Debug, ConvertSaveload)] #[derive(Component, Debug, ConvertSaveload)]
pub struct WantsToPickupItem { pub struct WantsToPickupItem {
pub collected_by: Entity, pub collected_by: Entity,
@ -558,7 +801,9 @@ pub struct Charges {
#[derive(Component, Serialize, Deserialize, Clone)] #[derive(Component, Serialize, Deserialize, Clone)]
pub struct SpawnParticleLine { pub struct SpawnParticleLine {
pub glyph: FontCharType, pub glyph: FontCharType,
pub sprite: String,
pub tail_glyph: FontCharType, pub tail_glyph: FontCharType,
pub tail_sprite: String,
pub colour: RGB, pub colour: RGB,
pub lifetime_ms: f32, pub lifetime_ms: f32,
pub trail_colour: RGB, pub trail_colour: RGB,
@ -568,6 +813,7 @@ pub struct SpawnParticleLine {
#[derive(Component, Serialize, Deserialize, Clone)] #[derive(Component, Serialize, Deserialize, Clone)]
pub struct SpawnParticleSimple { pub struct SpawnParticleSimple {
pub glyph: FontCharType, pub glyph: FontCharType,
pub sprite: String,
pub colour: RGB, pub colour: RGB,
pub lifetime_ms: f32, pub lifetime_ms: f32,
} }
@ -575,8 +821,11 @@ pub struct SpawnParticleSimple {
#[derive(Component, Serialize, Deserialize, Clone)] #[derive(Component, Serialize, Deserialize, Clone)]
pub struct SpawnParticleBurst { pub struct SpawnParticleBurst {
pub glyph: FontCharType, pub glyph: FontCharType,
pub sprite: String,
pub head_glyph: FontCharType, pub head_glyph: FontCharType,
pub head_sprite: String,
pub tail_glyph: FontCharType, pub tail_glyph: FontCharType,
pub tail_sprite: String,
pub colour: RGB, pub colour: RGB,
pub lerp: RGB, pub lerp: RGB,
pub lifetime_ms: f32, pub lifetime_ms: f32,

View file

@ -61,11 +61,11 @@ impl Config {
requires_write |= config.logging.apply_values(&parsed_config); requires_write |= config.logging.apply_values(&parsed_config);
requires_write |= config.visuals.apply_values(&parsed_config); requires_write |= config.visuals.apply_values(&parsed_config);
if requires_write { if requires_write {
console::log("Parsed config, but some values were changed. Saving new ver.");
if let Err(write_err) = config.save_to_file(filename) { if let Err(write_err) = config.save_to_file(filename) {
console::log(format!("Error writing config: {:?}", write_err)); console::log(format!("Error writing config: {:?}", write_err));
} }
} }
return config; return config;
} }
} }
@ -101,6 +101,8 @@ impl Section for LogConfig {
fn apply_values(&mut self, parsed_config: &Value) -> bool { fn apply_values(&mut self, parsed_config: &Value) -> bool {
if let Some(section) = parsed_config.get("logging") { if let Some(section) = parsed_config.get("logging") {
let mut missing = false; let mut missing = false;
apply_bool_value!(self, section, missing, show_mapgen);
apply_bool_value!(self, section, missing, log_combat);
apply_bool_value!(self, section, missing, log_spawning); apply_bool_value!(self, section, missing, log_spawning);
apply_bool_value!(self, section, missing, log_ticks); apply_bool_value!(self, section, missing, log_ticks);
missing missing

View file

@ -1,4 +1,4 @@
pub const DEFAULT_VIEWSHED_STANDARD: i32 = 16; // Standard viewshed radius for almost all entities. pub const DEFAULT_VIEWSHED_STANDARD: i32 = 7; // 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 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 NORMAL_SPEED: i32 = 12; // Normal speed for almost all entities.
pub const SPEED_MOD_BURDENED: f32 = 0.75; pub const SPEED_MOD_BURDENED: f32 = 0.75;

View file

@ -25,6 +25,7 @@ pub const NUTRITION_BLESSED: &str = "Delicious";
pub const LEVELUP_PLAYER: &str = "Welcome to experience level"; pub const LEVELUP_PLAYER: &str = "Welcome to experience level";
pub const YOU_PICKUP_ITEM: &str = "You pick up the"; pub const YOU_PICKUP_ITEM: &str = "You pick up the";
pub const NO_MORE_KEYS: &str = "Your backpack cannot accomodate any more items";
pub const YOU_DROP_ITEM: &str = "You drop the"; pub const YOU_DROP_ITEM: &str = "You drop the";
pub const YOU_EQUIP_ITEM: &str = "You equip the"; pub const YOU_EQUIP_ITEM: &str = "You equip the";
pub const YOU_REMOVE_ITEM: &str = "You unequip your"; pub const YOU_REMOVE_ITEM: &str = "You unequip your";

32
src/consts/mod.rs Normal file
View file

@ -0,0 +1,32 @@
pub mod entity;
pub mod visuals;
pub mod messages;
pub mod char_create;
pub mod events;
pub mod ids;
pub mod names;
pub mod sprites;
pub mod prelude {
pub use super::visuals::{ TILE_LAYER, ENTITY_LAYER, TEXT_LAYER, HP_BAR_LAYER };
pub use super::visuals::{ VIEWPORT_H, VIEWPORT_W };
}
pub struct Spritesize {
pub x: f32,
pub y: f32,
pub sprite_x: f32,
pub sprite_y: f32,
}
pub const TILESIZE: Spritesize = Spritesize {
x: 16.0,
y: 16.0,
sprite_x: 16.0 * ZOOM_FACTOR,
sprite_y: 24.0 * ZOOM_FACTOR,
};
pub const ZOOM_FACTOR: f32 = 2.0;
pub const FONTSIZE: f32 = 16.0;
pub const DISPLAYWIDTH: u32 = 120;
pub const DISPLAYHEIGHT: u32 = 67;

119
src/consts/sprites.rs Normal file
View file

@ -0,0 +1,119 @@
// Row 1
pub const UNKN: usize = 0;
pub const UNKN2: usize = 1;
pub const UNKN3: usize = 2;
pub const CANDLE: usize = 3;
pub const CANDLE2: usize = 4;
pub const CANDLE3: usize = 5;
pub const CANDLE4: usize = 6;
pub const CANDLE5: usize = 7;
pub const CANDLE6: usize = 8;
pub const CAULDRON: usize = 9;
pub const CAULDRON2: usize = 10;
pub const POTS: usize = 11;
pub const POTS2: usize = 12;
pub const POT: usize = 13;
pub const SPIKES: usize = 14;
pub const SPIKES2: usize = 15;
// Row 2
pub const WINDOW: usize = 16;
pub const DOOR: usize = 17;
pub const DOOR_OPEN: usize = 18;
pub const ROOF_BASE: usize = 19;
pub const ROOF_BASE2: usize = 20;
pub const ROOF: usize = 21;
pub const ROOF2: usize = 22;
pub const ROOF_CHIMNEY: usize = 23;
pub const SIGN: usize = 24;
pub const SIGN_BLACKSMITH: usize = 25;
pub const SIGN_POTION: usize = 26;
pub const SIGN_FURNITURE: usize = 27;
pub const WINDOW_LIT: usize = 28;
pub const STATUE_ANGEL: usize = 29;
pub const STATUE: usize = 30;
pub const STATUE_SPIDER: usize = 31;
// Row 3
pub const UNKN4: usize = 32;
pub const UNKN5: usize = 33;
pub const UNKN6: usize = 34;
pub const UNKN7: usize = 35;
pub const UNKN8: usize = 36;
pub const TREE: usize = 37;
pub const TREE2: usize = 38;
pub const PATH_GRASS: usize = 39;
pub const PATH_GRASS_QUAD: usize = 40;
pub const PATH_GRASS_QUAD2: usize = 41;
pub const PATH_GRASS_QUAD3: usize = 42;
pub const CAMPFIRE: usize = 43;
pub const CAMPFIRE_LIT: usize = 44;
pub const CAMPFIRE_LIT2: usize = 45; // ANIMATE WITH % 2 AND SOMETHING TO DO WITH FRAME TIME
pub const THRONE: usize = 46;
pub const THRONE2: usize = 47;
// Row 4
pub const BOOKSHELF: usize = 48;
pub const BOOKSHELF_EMPTY: usize = 49;
pub const BED: usize = 50;
pub const CHAIR: usize = 51;
pub const TABLE: usize = 52;
pub const TABLE_L: usize = 53;
pub const TABLE_M: usize = 54;
pub const TABLE_R: usize = 55;
pub const CHAIR_AT_TABLE_L: usize = 56;
pub const TABLE_M_PARCHMENT: usize = 57;
pub const CHAIR_AT_TABLE_R: usize = 58;
pub const TABLE_DARK: usize = 59;
pub const TABLE_DARK_SKULL: usize = 60;
pub const TABLE_DARK_L: usize = 61;
pub const TABLE_DARK_M: usize = 62;
pub const TABLE_DARK_R: usize = 63;
// Row 5
pub const GRASS: usize = 64;
pub const GRASS2: usize = 65;
pub const GRASS3: usize = 66;
pub const GRASS4: usize = 67;
pub const GRASS5: usize = 68;
pub const MUSHROOM: usize = 69;
pub const MUSHROOM_PURPLE: usize = 70;
pub const MUSHROOM_ORANGE: usize = 71;
pub const LILYPAD: usize = 72;
pub const LILYPAD2: usize = 73;
pub const LILYPAD3: usize = 74;
pub const LILYPAD4: usize = 75;
pub const LILYPAD5: usize = 76;
pub const LILYPAD6: usize = 77;
pub const LILYPAD7: usize = 78;
pub const LILYPAD8: usize = 79;
// Row 6 (80-95)
// Row 7 (96-111)
// Row 8 (112-127)
pub const FLOOR_WOOD: usize = 124;
// Row 9 (128-143)
// Row 10 (144-159)
// Row 11 (160-175)
pub const WATER_DEEP: usize = 164;
// Row 12 (176-191)
// Row 13 (192-207)
// Row 14 (208-223)
pub const FLOOR_GRASS: usize = 216;
// Row 15 (224-239)
pub const FLOOR: usize = 224;
// Row 16 (240-255)
// Row 17 (256-271)
// Row 18 (272-287)
// Row 19 (288-303)
pub const WALL_BASE: usize = 288;
pub const WALL_BASE2: usize = 289;
pub const WALL_BASE3: usize = 290;
pub const WALL_BASE4: usize = 291;
pub const WALL_CLOTH_BASE: usize = 292;
pub const WALL_CRACKED_BASE: usize = 293;
pub const WALL: usize = 294;
pub const WALL2: usize = 295;
pub const WALL3: usize = 296;
pub const WALL4: usize = 297;
pub const WALL_CRACKED: usize = 298;
pub const WALL_CLOTH_H: usize = 299;
pub const STAIR_D: usize = 300;
pub const STAIR_A: usize = 301;
pub const BASIN: usize = 302;
pub const BASIN_EMPTY: usize = 303;

View file

@ -1,17 +1,34 @@
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use super::ZOOM_FACTOR;
// POST-PROCESSING // 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 WITH_DARKEN_BY_DISTANCE: bool = true; // If further away tiles should get darkened, instead of a harsh transition to non-visible.
// Counted in 16x16 tiles, because that's how most of the screen is drawn. However,
// the viewport itself uses 16x24 sprites - so this translates to 70x28 tiles drawn.
// It also works nicely for zooming in, displaying 35x14 tiles cleanly onscreen.
pub const VIEWPORT_W: i32 = 70;
pub const VIEWPORT_H: i32 = 54;
pub const TILES_IN_VIEWPORT_W: i32 = 70 / (ZOOM_FACTOR as i32);
pub const TILES_IN_VIEWPORT_H: i32 = 36 / (ZOOM_FACTOR as i32);
pub const TILE_LAYER: usize = 1;
pub const ENTITY_LAYER: usize = 2;
pub const TEXT_LAYER: usize = 3;
pub const HP_BAR_LAYER: usize = 4;
pub const BRIGHTEN_FG_COLOUR_BY: i32 = 16; pub const BRIGHTEN_FG_COLOUR_BY: i32 = 16;
pub const GLOBAL_OFFSET_MIN_CLAMP: f32 = -0.5; pub const GLOBAL_OFFSET_MIN_CLAMP: f32 = -0.5;
pub const GLOBAL_OFFSET_MAX_CLAMP: f32 = 1.0; pub const GLOBAL_OFFSET_MAX_CLAMP: f32 = 0.5;
pub const SPRITE_OFFSET_MIN_CLAMP: f32 = 0.85;
pub const SPRITE_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 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: f32 = 0.1; // 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 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: f32 = 0.2; // 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 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 START_DARKEN_AT_N_TILES: f32 = 1.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 SHORT_PARTICLE_LIFETIME: f32 = 100.0; // in ms
pub const DEFAULT_PARTICLE_LIFETIME: f32 = 200.0; pub const DEFAULT_PARTICLE_LIFETIME: f32 = 200.0;
@ -57,6 +74,7 @@ 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_COLOUR: (u8, u8, u8) = (18, 33, 63);
pub const DEEP_WATER_OFFSETS: (i32, i32, i32) = (5, 10, 32); pub const DEEP_WATER_OFFSETS: (i32, i32, i32) = (5, 10, 32);
pub const BARS_COLOUR: (u8, u8, u8) = (100, 100, 100); pub const BARS_COLOUR: (u8, u8, u8) = (100, 100, 100);
pub const BARS_OFFSETS: (i32, i32, i32) = (10, 10, 10);
pub const IMPASSABLE_MOUNTAIN_COLOUR: (u8, u8, u8) = (20, 23, 20); pub const IMPASSABLE_MOUNTAIN_COLOUR: (u8, u8, u8) = (20, 23, 20);
pub const IMPASSABLE_MOUNTAIN_OFFSETS: (i32, i32, i32) = (4, 4, 4); pub const IMPASSABLE_MOUNTAIN_OFFSETS: (i32, i32, i32) = (4, 4, 4);
// FOREST THEME // FOREST THEME

View file

@ -11,10 +11,12 @@ use super::{
Position, Position,
Renderable, Renderable,
RunState, RunState,
WantsToRemoveKey,
WantsToDelete,
}; };
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use specs::prelude::*; use specs::prelude::*;
use crate::data::events; use crate::consts::events;
pub fn delete_the_dead(ecs: &mut World) { pub fn delete_the_dead(ecs: &mut World) {
let mut dead: Vec<Entity> = Vec::new(); let mut dead: Vec<Entity> = Vec::new();
@ -65,7 +67,17 @@ pub fn delete_the_dead(ecs: &mut World) {
} }
} }
} }
let (items_to_delete, loot_to_spawn) = handle_dead_entity_items(ecs, &dead); let (mut items_to_delete, loot_to_spawn) = handle_dead_entity_items(ecs, &dead);
{
let entities = ecs.entities();
let removekeys = ecs.read_storage::<WantsToRemoveKey>();
let delete = ecs.read_storage::<WantsToDelete>();
// Add items marked for deletion to the list, but only if they've already had their key
// assignments handled, to ensure we don't leave any dangling references behind.
for (e, _d, _r) in (&entities, &delete, !&removekeys).join() {
items_to_delete.push(e);
}
}
for loot in loot_to_spawn { for loot in loot_to_spawn {
crate::raws::spawn_named_entity( crate::raws::spawn_named_entity(
&crate::raws::RAWS.lock().unwrap(), &crate::raws::RAWS.lock().unwrap(),
@ -82,6 +94,7 @@ pub fn delete_the_dead(ecs: &mut World) {
// For everything that died, increment the event log, and delete. // For everything that died, increment the event log, and delete.
for victim in dead { for victim in dead {
gamelog::record_event(events::EVENT::Turn(1)); gamelog::record_event(events::EVENT::Turn(1));
// TODO: Delete stuff from inventory? This should be handled elsewhere.
ecs.delete_entity(victim).expect("Unable to delete."); ecs.delete_entity(victim).expect("Unable to delete.");
} }
} }

View file

@ -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;

32
src/effects/attr.rs Normal file
View file

@ -0,0 +1,32 @@
use specs::prelude::*;
use bracket_lib::prelude::*;
use super::{ EffectSpawner, EffectType };
use crate::components::Attributes;
const ATTRIBUTE_SOFTCAP: i32 = 20;
const ABUSE_CHANCE: i32 = 2; // 1 in this chance of abuse. 2 = 50%, 3 = 33%, etc.
pub(crate) fn exercise(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
// Unwrap vars from the effect
let (attr, inc) = if let EffectType::Exercise { attribute, increment } = effect.effect_type {
(attribute, increment)
} else {
return;
};
// Get target attributes
let mut attributes = ecs.write_storage::<Attributes>();
if let Some(has_attr) = attributes.get_mut(target) {
// Roll a d20. If we're trying to exercise a stat, we need to roll higher
// than the stat's current value. If we're abusing a stat, flip a coin.
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
let success = if inc {
rng.roll_dice(1, ATTRIBUTE_SOFTCAP) > has_attr.attr_from_index(attr).current()
} else {
rng.roll_dice(1, ABUSE_CHANCE) == 1
};
if success {
has_attr.exercise(attr, inc);
}
}
}

View file

@ -15,12 +15,13 @@ use crate::{
HungerState, HungerState,
Bleeds, Bleeds,
HasDamageModifiers, HasDamageModifiers,
DamageType,
}; };
use crate::gui::with_article; use crate::gui::with_article;
use crate::data::visuals::{ DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME }; use crate::consts::visuals::{ DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME };
use crate::data::messages::LEVELUP_PLAYER; use crate::consts::messages::LEVELUP_PLAYER;
use crate::data::events::*; use crate::consts::events::*;
use crate::data::messages::*; use crate::consts::messages::*;
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use specs::prelude::*; use specs::prelude::*;
@ -38,33 +39,88 @@ pub fn inflict_damage(ecs: &mut World, damage: &EffectSpawner, target: Entity) {
}; };
target_pool.hit_points.current -= ((amount as f32) * mult) as i32; target_pool.hit_points.current -= ((amount as f32) * mult) as i32;
let bleeders = ecs.read_storage::<Bleeds>(); let bleeders = ecs.read_storage::<Bleeds>();
// If the target bleeds, handle bloodstains and use the bleed colour for sfx.
if let Some(bleeds) = bleeders.get(target) { if let Some(bleeds) = bleeders.get(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,
});
for i in 0..3 {
let sprite = (
match i {
0 => "explode1",
1 => "explode2",
_ => "explode3",
}
).to_string();
add_effect(
None,
EffectType::Particle {
glyph: to_cp437('‼'),
sprite,
fg: bleeds.colour,
lifespan: 75.0,
delay: 75.0 * (i as f32),
},
Targets::Entity { target }
);
add_effect( add_effect(
None, None,
EffectType::Bloodstain { colour: bleeds.colour }, EffectType::Bloodstain { colour: bleeds.colour },
Targets::Entity { target } Targets::Entity { target }
); );
} }
} else {
// Regular damage taken effect - use damagetype to determine which one to play.
add_effect( add_effect(
None, None,
EffectType::Particle { EffectType::Particle {
glyph: to_cp437('‼'), glyph: to_cp437('‼'),
sprite: "gnome".to_string(), // FIXME: REMOVE THE GNOMES
fg: RGB::named(ORANGE), fg: RGB::named(ORANGE),
bg: RGB::named(BLACK),
lifespan: DEFAULT_PARTICLE_LIFETIME, lifespan: DEFAULT_PARTICLE_LIFETIME,
delay: 0.0, delay: 0.0,
}, },
Targets::Entity { target } Targets::Entity { target }
); );
if target_pool.hit_points.current < 1 { add_effect(
super::DEAD_ENTITIES.lock().unwrap().push_back(target); None,
add_effect(damage.source, EffectType::EntityDeath, Targets::Entity { target }); EffectType::Bloodstain { colour: bleeds.colour },
Targets::Entity { target }
);
}
} else {
// TODO: Damage taken particle effects when the target does not bleed.
// Also damage types, etc.
return;
} }
} }
} }
} else if let Some(_destructible) = ecs.read_storage::<Destructible>().get(target) { } else if let Some(_destructible) = ecs.read_storage::<Destructible>().get(target) {
add_effect(damage.source, EffectType::EntityDeath, Targets::Entity { target }); add_effect(damage.source, EffectType::EntityDeath, Targets::Entity { target });
} }
if let EffectType::Damage { amount: _, damage_type } = &damage.effect_type {
get_random_damage_sound(ecs, damage_type, target);
}
}
fn get_random_damage_sound(ecs: &World, damage_type: &DamageType, target: Entity) {
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
match damage_type {
DamageType::Physical => {
let sound = (
match rng.roll_dice(1, 4) {
1 => "whoosh1",
2 => "whoosh2",
3 => "whoosh3",
_ => "whoosh4",
}
).to_string();
add_effect(None, EffectType::Sound { sound }, Targets::Entity { target });
}
_ => {}
}
} }
pub fn heal_damage(ecs: &mut World, heal: &EffectSpawner, target: Entity) { pub fn heal_damage(ecs: &mut World, heal: &EffectSpawner, target: Entity) {
@ -83,10 +139,10 @@ pub fn heal_damage(ecs: &mut World, heal: &EffectSpawner, target: Entity) {
} }
add_effect( add_effect(
None, None,
EffectType::Particle { EffectType::Particle { // GNOMES
glyph: to_cp437('♥'), glyph: to_cp437('♥'),
sprite: "gnome".to_string(),
fg: RGB::named(BLUE), fg: RGB::named(BLUE),
bg: RGB::named(BLACK),
lifespan: DEFAULT_PARTICLE_LIFETIME, lifespan: DEFAULT_PARTICLE_LIFETIME,
delay: 0.0, delay: 0.0,
}, },
@ -184,7 +240,7 @@ fn get_death_message(ecs: &World, source: Entity) -> String {
result.push_str(format!("{}", PLAYER_DIED_SUICIDE).as_str()); result.push_str(format!("{}", PLAYER_DIED_SUICIDE).as_str());
} else if let Some(name) = ecs.read_storage::<Name>().get(source) { } else if let Some(name) = ecs.read_storage::<Name>().get(source) {
result.push_str( result.push_str(
format!("{} {}", PLAYER_DIED_NAMED_ATTACKER, with_article(name.name.clone())).as_str() format!("{} {}", PLAYER_DIED_NAMED_ATTACKER, with_article(&name.name)).as_str()
); );
} else { } else {
result.push_str(format!("{}", PLAYER_DIED_UNKNOWN).as_str()); result.push_str(format!("{}", PLAYER_DIED_UNKNOWN).as_str());
@ -266,11 +322,11 @@ pub fn entity_death(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
for i in 0..5 { for i in 0..5 {
if player_pos.y - i > 1 { if player_pos.y - i > 1 {
add_effect( add_effect(
None, None, // FIXME: REMOVE THE GNOMES
EffectType::Particle { EffectType::Particle {
glyph: to_cp437('░'), glyph: to_cp437('░'),
sprite: "gnome".to_string(),
fg: RGB::named(GOLD), fg: RGB::named(GOLD),
bg: RGB::named(BLACK),
lifespan: LONG_PARTICLE_LIFETIME, lifespan: LONG_PARTICLE_LIFETIME,
delay: (i as f32) * 100.0, delay: (i as f32) * 100.0,
}, },
@ -281,8 +337,8 @@ pub fn entity_death(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
None, None,
EffectType::Particle { EffectType::Particle {
glyph: to_cp437('░'), glyph: to_cp437('░'),
sprite: "gnome".to_string(),
fg: RGB::named(GOLD), fg: RGB::named(GOLD),
bg: RGB::named(BLACK),
lifespan: LONG_PARTICLE_LIFETIME, lifespan: LONG_PARTICLE_LIFETIME,
delay: (i as f32) * 100.0, delay: (i as f32) * 100.0,
}, },
@ -297,8 +353,8 @@ pub fn entity_death(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
None, None,
EffectType::Particle { EffectType::Particle {
glyph: to_cp437('░'), glyph: to_cp437('░'),
sprite: "gnome".to_string(),
fg: RGB::named(GOLD), fg: RGB::named(GOLD),
bg: RGB::named(BLACK),
lifespan: LONG_PARTICLE_LIFETIME, lifespan: LONG_PARTICLE_LIFETIME,
delay: (i as f32) * 100.0, delay: (i as f32) * 100.0,
}, },
@ -319,11 +375,11 @@ pub fn entity_death(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
let mut rng = ecs.write_resource::<RandomNumberGenerator>(); let mut rng = ecs.write_resource::<RandomNumberGenerator>();
let hp_gained = hp_per_level( let hp_gained = hp_per_level(
&mut rng, &mut rng,
source_attributes.constitution.base + source_attributes.constitution.modifiers source_attributes.constitution.base + source_attributes.constitution.bonuses
); );
let mana_gained = mana_per_level( let mana_gained = mana_per_level(
&mut rng, &mut rng,
source_attributes.intelligence.base + source_attributes.intelligence.modifiers source_attributes.intelligence.base + source_attributes.intelligence.bonuses
); );
source_pools.hit_points.max += hp_gained; source_pools.hit_points.max += hp_gained;
source_pools.hit_points.current += hp_gained; source_pools.hit_points.current += hp_gained;

View file

@ -2,6 +2,7 @@ use super::BUC;
use crate::spatial; use crate::spatial;
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use specs::prelude::*; use specs::prelude::*;
use notan::prelude::*;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::sync::Mutex; use std::sync::Mutex;
use crate::components::*; use crate::components::*;
@ -11,7 +12,9 @@ mod hunger;
mod particles; mod particles;
mod targeting; mod targeting;
mod triggers; mod triggers;
mod attr;
mod intrinsics; mod intrinsics;
pub mod sound;
pub use targeting::aoe_tiles; pub use targeting::aoe_tiles;
@ -32,6 +35,10 @@ pub enum EffectType {
amount: i32, amount: i32,
increment_max: bool, increment_max: bool,
}, },
Exercise {
attribute: i32,
increment: bool,
},
Confusion { Confusion {
turns: i32, turns: i32,
}, },
@ -40,8 +47,8 @@ pub enum EffectType {
}, },
Particle { Particle {
glyph: FontCharType, glyph: FontCharType,
sprite: String,
fg: RGB, fg: RGB,
bg: RGB,
lifespan: f32, lifespan: f32,
delay: f32, delay: f32,
}, },
@ -58,6 +65,9 @@ pub enum EffectType {
TriggerFire { TriggerFire {
trigger: Entity, trigger: Entity,
}, },
Sound {
sound: String,
},
} }
#[derive(Clone)] #[derive(Clone)]
@ -90,7 +100,7 @@ pub fn add_effect(source: Option<Entity>, effect_type: EffectType, target: Targe
} }
/// Iterates through the effects queue, applying each effect to their target. /// Iterates through the effects queue, applying each effect to their target.
pub fn run_effects_queue(ecs: &mut World) { pub fn run_effects_queue(app: &mut App, ecs: &mut World) {
// First removes any effect in the EFFECT_QUEUE with a dead entity as its source. // First removes any effect in the EFFECT_QUEUE with a dead entity as its source.
loop { loop {
let dead_entity: Option<Entity> = DEAD_ENTITIES.lock().unwrap().pop_front(); let dead_entity: Option<Entity> = DEAD_ENTITIES.lock().unwrap().pop_front();
@ -106,7 +116,7 @@ pub fn run_effects_queue(ecs: &mut World) {
loop { loop {
let effect: Option<EffectSpawner> = EFFECT_QUEUE.lock().unwrap().pop_front(); let effect: Option<EffectSpawner> = EFFECT_QUEUE.lock().unwrap().pop_front();
if let Some(effect) = effect { if let Some(effect) = effect {
target_applicator(ecs, &effect); target_applicator(app, ecs, &effect);
} else { } else {
break; break;
} }
@ -114,7 +124,7 @@ pub fn run_effects_queue(ecs: &mut World) {
} }
/// Applies an effect to the correct target(s). /// Applies an effect to the correct target(s).
fn target_applicator(ecs: &mut World, effect: &EffectSpawner) { fn target_applicator(app: &mut App, ecs: &mut World, effect: &EffectSpawner) {
// Item use is handled differently - it creates other effects with itself // Item use is handled differently - it creates other effects with itself
// as the source, passing all effects attached to the item into the queue. // as the source, passing all effects attached to the item into the queue.
if let EffectType::ItemUse { item } = effect.effect_type { if let EffectType::ItemUse { item } = effect.effect_type {
@ -126,25 +136,26 @@ fn target_applicator(ecs: &mut World, effect: &EffectSpawner) {
} }
// Otherwise, just match the effect and enact it directly. // Otherwise, just match the effect and enact it directly.
match &effect.target { match &effect.target {
Targets::Tile { target } => affect_tile(ecs, effect, *target), Targets::Tile { target } => affect_tile(app, ecs, effect, *target),
Targets::TileList { targets } => Targets::TileList { targets } =>
targets.iter().for_each(|target| affect_tile(ecs, effect, *target)), targets.iter().for_each(|target| affect_tile(app, ecs, effect, *target)),
Targets::Entity { target } => affect_entity(ecs, effect, *target), Targets::Entity { target } => affect_entity(app, ecs, effect, *target),
Targets::EntityList { targets } => Targets::EntityList { targets } =>
targets.iter().for_each(|target| affect_entity(ecs, effect, *target)), targets.iter().for_each(|target| affect_entity(app, ecs, effect, *target)),
} }
} }
/// Runs an effect on a given tile index /// Runs an effect on a given tile index
fn affect_tile(ecs: &mut World, effect: &EffectSpawner, target: usize) { fn affect_tile(app: &mut App, ecs: &mut World, effect: &EffectSpawner, target: usize) {
if tile_effect_hits_entities(&effect.effect_type) { if tile_effect_hits_entities(&effect.effect_type) {
spatial::for_each_tile_content(target, |entity| { spatial::for_each_tile_content(target, |entity| {
affect_entity(ecs, effect, entity); affect_entity(app, ecs, effect, entity);
}); });
} }
match &effect.effect_type { match &effect.effect_type {
EffectType::Particle { .. } => particles::particle_to_tile(ecs, target as i32, &effect), EffectType::Particle { .. } => particles::particle_to_tile(ecs, target as i32, &effect),
EffectType::Sound { .. } => sound::play_sound(app, ecs, &effect, target),
_ => {} _ => {}
} }
// Run the effect // Run the effect
@ -163,10 +174,11 @@ fn tile_effect_hits_entities(effect: &EffectType) -> bool {
} }
/// Runs an effect on a given entity /// Runs an effect on a given entity
fn affect_entity(ecs: &mut World, effect: &EffectSpawner, target: Entity) { fn affect_entity(app: &mut App, ecs: &mut World, effect: &EffectSpawner, target: Entity) {
match &effect.effect_type { match &effect.effect_type {
EffectType::Damage { .. } => damage::inflict_damage(ecs, effect, target), EffectType::Damage { .. } => damage::inflict_damage(ecs, effect, target),
EffectType::Healing { .. } => damage::heal_damage(ecs, effect, target), EffectType::Healing { .. } => damage::heal_damage(ecs, effect, target),
EffectType::Exercise { .. } => attr::exercise(ecs, effect, target),
EffectType::Confusion { .. } => damage::add_confusion(ecs, effect, target), EffectType::Confusion { .. } => damage::add_confusion(ecs, effect, target),
EffectType::Bloodstain { colour } => { EffectType::Bloodstain { colour } => {
if let Some(pos) = targeting::entity_position(ecs, target) { if let Some(pos) = targeting::entity_position(ecs, target) {
@ -181,6 +193,11 @@ fn affect_entity(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
EffectType::EntityDeath => damage::entity_death(ecs, effect, target), EffectType::EntityDeath => damage::entity_death(ecs, effect, target),
EffectType::ModifyNutrition { .. } => hunger::modify_nutrition(ecs, effect, target), EffectType::ModifyNutrition { .. } => hunger::modify_nutrition(ecs, effect, target),
EffectType::AddIntrinsic { .. } => intrinsics::add_intrinsic(ecs, effect, target), EffectType::AddIntrinsic { .. } => intrinsics::add_intrinsic(ecs, effect, target),
EffectType::Sound { .. } => {
if let Some(pos) = targeting::entity_position(ecs, target) {
sound::play_sound(app, ecs, &effect, pos);
}
}
_ => {} _ => {}
} }
} }

View file

@ -4,27 +4,27 @@ use bracket_lib::prelude::*;
use specs::prelude::*; use specs::prelude::*;
pub fn particle_to_tile(ecs: &mut World, target: i32, effect: &EffectSpawner) { pub fn particle_to_tile(ecs: &mut World, target: i32, effect: &EffectSpawner) {
if let EffectType::Particle { glyph, fg, bg, lifespan, delay } = effect.effect_type { if let EffectType::Particle { glyph, sprite, fg, lifespan, delay } = &effect.effect_type {
let map = ecs.fetch::<Map>(); let map = ecs.fetch::<Map>();
let mut particle_builder = ecs.fetch_mut::<ParticleBuilder>(); let mut particle_builder = ecs.fetch_mut::<ParticleBuilder>();
if delay <= 0.0 { if delay <= &0.0 {
particle_builder.request( particle_builder.request(
target % map.width, target % map.width,
target / map.width, target / map.width,
fg, *fg,
bg, *glyph,
glyph, sprite.clone(),
lifespan *lifespan
); );
} else { } else {
particle_builder.delay( particle_builder.delay(
target % map.width, target % map.width,
target / map.width, target / map.width,
fg, *fg,
bg, *glyph,
glyph, sprite.clone(),
lifespan, *lifespan,
delay *delay
); );
} }
} }
@ -36,8 +36,8 @@ pub fn handle_simple_particles(ecs: &World, entity: Entity, target: &Targets) {
None, None,
EffectType::Particle { EffectType::Particle {
glyph: part.glyph, glyph: part.glyph,
sprite: part.sprite.clone(),
fg: part.colour, fg: part.colour,
bg: RGB::named(BLACK),
lifespan: part.lifetime_ms, lifespan: part.lifetime_ms,
delay: 0.0, delay: 0.0,
}, },
@ -56,7 +56,9 @@ pub fn handle_burst_particles(ecs: &World, entity: Entity, target: &Targets) {
end_pos, end_pos,
&(SpawnParticleLine { &(SpawnParticleLine {
glyph: part.head_glyph, glyph: part.head_glyph,
sprite: part.head_sprite.clone(),
tail_glyph: part.tail_glyph, tail_glyph: part.tail_glyph,
tail_sprite: part.tail_sprite.clone(),
colour: part.colour, colour: part.colour,
trail_colour: part.trail_colour, trail_colour: part.trail_colour,
lifetime_ms: part.trail_lifetime_ms, // 75.0 is good here. lifetime_ms: part.trail_lifetime_ms, // 75.0 is good here.
@ -75,8 +77,8 @@ pub fn handle_burst_particles(ecs: &World, entity: Entity, target: &Targets) {
None, None,
EffectType::Particle { EffectType::Particle {
glyph: part.glyph, glyph: part.glyph,
sprite: part.sprite.clone(),
fg: part.colour.lerp(part.lerp, (i as f32) * 0.1), 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. 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 delay: burst_delay + ((i as f32) * part.lifetime_ms) / 10.0, // above + burst_delay
}, },
@ -163,8 +165,8 @@ fn spawn_line_particles(ecs: &World, start: i32, end: i32, part: &SpawnParticleL
None, None,
EffectType::Particle { EffectType::Particle {
glyph: part.glyph, glyph: part.glyph,
sprite: part.sprite.clone(),
fg: part.colour, fg: part.colour,
bg: RGB::named(BLACK),
lifespan: part.lifetime_ms, lifespan: part.lifetime_ms,
delay: (i as f32) * part.lifetime_ms, delay: (i as f32) * part.lifetime_ms,
}, },
@ -175,8 +177,8 @@ fn spawn_line_particles(ecs: &World, start: i32, end: i32, part: &SpawnParticleL
None, None,
EffectType::Particle { EffectType::Particle {
glyph: part.tail_glyph, glyph: part.tail_glyph,
sprite: part.tail_sprite.clone(),
fg: part.trail_colour, fg: part.trail_colour,
bg: RGB::named(BLACK),
lifespan: part.trail_lifetime_ms, lifespan: part.trail_lifetime_ms,
delay: (i as f32) * part.lifetime_ms, delay: (i as f32) * part.lifetime_ms,
}, },

145
src/effects/sound.rs Normal file
View file

@ -0,0 +1,145 @@
use bracket_lib::prelude::*;
use notan::prelude::*;
use specs::prelude::*;
use std::sync::Mutex;
use std::collections::HashMap;
use super::{ EffectSpawner, EffectType, Targets, add_effect };
use crate::Map;
lazy_static::lazy_static! {
pub static ref SOUNDS: Mutex<HashMap<String, (AudioSource, AudioType)>> = Mutex::new(HashMap::new());
pub static ref VOLUME: Mutex<f32> = Mutex::new(1.0);
pub static ref AMBIENCE: Mutex<Option<Sound>> = Mutex::new(None);
}
#[derive(PartialEq, Copy, Clone)]
pub enum AudioType {
Ambient,
SFX,
}
const AMBIENCE_VOL_MUL: f32 = 0.8;
const SFX_VOL_MUL: f32 = 1.0;
pub fn play_sound(app: &mut App, ecs: &mut World, effect: &EffectSpawner, target: usize) {
// Extract sound from the EffectType, or panic if we somehow called this with the wrong effect.
let sound = if let EffectType::Sound { sound } = &effect.effect_type {
sound
} else {
unreachable!("add_intrinsic() called with the wrong EffectType")
};
// Fetch all the relevant precursors.
let sounds = SOUNDS.lock().unwrap();
let volume = VOLUME.lock().unwrap();
let source = sounds.get(sound).unwrap();
let (vol, repeat) = match source.1 {
AudioType::Ambient => (*volume * AMBIENCE_VOL_MUL, true),
AudioType::SFX => {
let map = ecs.fetch::<Map>();
let ppos = ecs.fetch::<Point>();
// Get a slight variation on sound volume, just so things don't sound too uniform.
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
let vol_random = rng.range(0.9, 1.1);
// Calc distance from player to target.
let dist = DistanceAlg::PythagorasSquared.distance2d(
*ppos,
Point::new((target as i32) % map.width, (target as i32) / map.width)
);
// Play sound at volume proportional to distance.
(*volume * SFX_VOL_MUL * vol_random * (1.0 - (dist as f32) / 14.0), false)
}
};
// Play the sound.
let sound: Sound = app.audio.play_sound(&source.0, vol, repeat);
if repeat {
replace_ambience(app, &sound);
}
}
pub fn stop(app: &mut App) {
let mut ambience = AMBIENCE.lock().unwrap();
if let Some(old) = ambience.take() {
app.audio.stop(&old);
}
}
pub fn ambience(sound: &str) {
add_effect(None, EffectType::Sound { sound: sound.to_string() }, Targets::Tile { target: 0 })
}
pub fn replace_ambience(app: &mut App, sound: &Sound) {
let mut ambience = AMBIENCE.lock().unwrap();
if let Some(old) = ambience.take() {
app.audio.stop(&old);
}
*ambience = Some(sound.clone());
}
pub fn init_sounds(app: &mut App) {
let sound_data: &[(&str, &[u8], AudioType)] = &[
// (key, file_path, audiotype)
("a_relax", include_bytes!("../../resources/sounds/amb/relaxed.ogg"), AudioType::Ambient),
("whoosh1", include_bytes!("../../resources/sounds/hit/whoosh1.ogg"), AudioType::SFX),
("whoosh2", include_bytes!("../../resources/sounds/hit/whoosh2.ogg"), AudioType::SFX),
("whoosh3", include_bytes!("../../resources/sounds/hit/whoosh3.ogg"), AudioType::SFX),
("whoosh4", include_bytes!("../../resources/sounds/hit/whoosh4.ogg"), AudioType::SFX),
("d_blocked1", include_bytes!("../../resources/sounds/door/blocked1.wav"), AudioType::SFX),
("d_blocked2", include_bytes!("../../resources/sounds/door/blocked2.wav"), AudioType::SFX),
("d_blocked3", include_bytes!("../../resources/sounds/door/blocked3.wav"), AudioType::SFX),
("d_open1", include_bytes!("../../resources/sounds/door/open1.wav"), AudioType::SFX),
("d_open2", include_bytes!("../../resources/sounds/door/open2.wav"), AudioType::SFX),
("d_open3", include_bytes!("../../resources/sounds/door/open3.wav"), AudioType::SFX),
("d_close1", include_bytes!("../../resources/sounds/door/close1.wav"), AudioType::SFX),
("d_close2", include_bytes!("../../resources/sounds/door/close2.wav"), AudioType::SFX),
("d_close3", include_bytes!("../../resources/sounds/door/close3.wav"), AudioType::SFX),
];
let mut sounds = SOUNDS.lock().unwrap();
for (k, bytes, audiotype) in sound_data {
sounds.insert(k.to_string(), (app.audio.create_source(bytes).unwrap(), *audiotype));
}
}
pub fn set_volume(vol: f32) {
let mut volume = VOLUME.lock().unwrap();
*volume = vol;
}
pub fn clean(app: &mut App) {
app.audio.clean();
}
// Shorthand functions for adding generic, frequent SFX to the effect queue.
pub fn door_open(idx: usize) {
let mut rng = RandomNumberGenerator::new();
let sound = (
match rng.range(0, 3) {
0 => "d_open1",
1 => "d_open2",
_ => "d_open3",
}
).to_string();
super::add_effect(None, EffectType::Sound { sound }, Targets::Tile { target: idx });
}
pub fn door_resist(idx: usize) {
let mut rng = RandomNumberGenerator::new();
let sound = (
match rng.range(0, 3) {
0 => "d_blocked1",
1 => "d_blocked2",
_ => "d_blocked3",
}
).to_string();
add_effect(None, EffectType::Sound { sound }, Targets::Tile { target: idx });
}
pub fn door_close(idx: usize) {
let mut rng = RandomNumberGenerator::new();
let sound = (
match rng.range(0, 3) {
0 => "d_close1",
1 => "d_close2",
_ => "d_close3",
}
).to_string();
add_effect(None, EffectType::Sound { sound }, Targets::Tile { target: idx });
}

View file

@ -33,8 +33,10 @@ use crate::{
KnownSpells, KnownSpells,
Position, Position,
Viewshed, Viewshed,
WantsToRemoveKey,
WantsToDelete,
}; };
use crate::data::messages::*; use crate::consts::messages::*;
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use specs::prelude::*; use specs::prelude::*;
pub fn item_trigger(source: Option<Entity>, item: Entity, target: &Targets, ecs: &mut World) { pub fn item_trigger(source: Option<Entity>, item: Entity, target: &Targets, ecs: &mut World) {
@ -57,7 +59,10 @@ pub fn item_trigger(source: Option<Entity>, item: Entity, target: &Targets, ecs:
let did_something = event_trigger(source, item, target, ecs); let did_something = event_trigger(source, item, target, ecs);
// If it's a consumable, delete it // If it's a consumable, delete it
if did_something && ecs.read_storage::<Consumable>().get(item).is_some() { if did_something && ecs.read_storage::<Consumable>().get(item).is_some() {
ecs.entities().delete(item).expect("Failed to delete item"); let mut removekey = ecs.write_storage::<WantsToRemoveKey>();
removekey.insert(item, WantsToRemoveKey {}).expect("Unable to insert WantsToRemoveKey");
let mut delete = ecs.write_storage::<WantsToDelete>();
delete.insert(item, WantsToDelete {}).expect("Unable to insert WantsToDelete");
} }
} }

View file

@ -65,6 +65,7 @@ impl Logger {
/// Pushes the finished log entry. /// Pushes the finished log entry.
pub fn log(self) { pub fn log(self) {
return append_entry(self.fragments); let key = crate::gamelog::get_event_count(crate::consts::events::EVENT::COUNT_TURN);
return append_entry(key, self.fragments);
} }
} }

View file

@ -1,7 +1,7 @@
use std::collections::{ HashSet, HashMap }; use std::collections::{ HashSet, HashMap };
use std::sync::Mutex; use std::sync::Mutex;
use crate::data::events::EVENT; use crate::consts::events::EVENT;
use crate::data::names::*; use crate::consts::names::*;
lazy_static! { lazy_static! {
/// A count of each event that has happened over the run. i.e. "turns", "descended", "ascended" /// A count of each event that has happened over the run. i.e. "turns", "descended", "ascended"
@ -126,7 +126,7 @@ pub fn record_event(event: EVENT) {
new_event = format!("Discovered {}", name); new_event = format!("Discovered {}", name);
} }
EVENT::Identified(name) => { EVENT::Identified(name) => {
new_event = format!("Identified {}", crate::gui::with_article(name)); new_event = format!("Identified {}", crate::gui::with_article(&name));
} }
EVENT::PlayerDied(str) => { EVENT::PlayerDied(str) => {
// Generating the String is handled in the death effect, to avoid passing the ecs here. // Generating the String is handled in the death effect, to avoid passing the ecs here.

View file

@ -1,91 +1,70 @@
use super::{ events, LogFragment, Logger }; use super::{ events, LogFragment, Logger };
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use std::sync::Mutex; use std::sync::Mutex;
use std::collections::BTreeMap;
use notan::prelude::*;
use notan::text::CreateText;
use crate::consts::{ TILESIZE, FONTSIZE };
use crate::consts::visuals::VIEWPORT_W;
use crate::Fonts;
lazy_static! { lazy_static! {
static ref LOG: Mutex<Vec<Vec<LogFragment>>> = Mutex::new(Vec::new()); pub static ref LOG: Mutex<BTreeMap<i32, Vec<LogFragment>>> = Mutex::new(BTreeMap::new());
} }
#[allow(dead_code)] /// Render with specificied params.
pub fn append_fragment(fragment: LogFragment) { pub fn render_log(
LOG.lock().unwrap().push(vec![fragment]); draw: &RenderTexture,
gfx: &mut Graphics,
font: &Fonts,
pos: &(f32, f32),
width: f32,
entries: usize
) {
let mut text = gfx.create_text();
let log = LOG.lock().unwrap();
let latest: Vec<_> = log.iter().rev().take(entries).collect();
let mut initialised = false;
let mut y = pos.1;
for (_, entries) in latest {
let mut written_on_line = false;
for frag in entries.iter() {
if !written_on_line {
text.add(&frag.text)
.font(font.n())
.position(pos.0, y)
.size(FONTSIZE)
.max_width(width)
.color(Color::from_rgb(frag.colour.r, frag.colour.g, frag.colour.b))
.v_align_bottom();
written_on_line = true;
initialised = true;
} else {
text.chain(&frag.text)
.color(Color::from_rgb(frag.colour.r, frag.colour.g, frag.colour.b))
.size(FONTSIZE);
}
}
if initialised {
y = text.last_bounds().min_y();
}
}
gfx.render_to(draw, &text);
} }
pub fn append_entry(fragments: Vec<LogFragment>) { pub fn append_entry(turn: i32, fragments: Vec<LogFragment>) {
LOG.lock().unwrap().push(fragments); let mut log = LOG.lock().unwrap();
if let Some(existing) = log.get_mut(&turn) {
existing.extend(fragments);
} else {
log.insert(turn, fragments);
}
} }
pub fn clear_log() { pub fn clear_log() {
LOG.lock().unwrap().clear(); 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() { pub fn setup_log() {
clear_log(); clear_log();
events::clear_events(); events::clear_events();
@ -98,11 +77,11 @@ pub fn setup_log() {
.log(); .log();
} }
pub fn clone_log() -> Vec<Vec<crate::gamelog::LogFragment>> { pub fn clone_log() -> BTreeMap<i32, Vec<LogFragment>> {
return LOG.lock().unwrap().clone(); return LOG.lock().unwrap().clone();
} }
pub fn restore_log(log: &mut Vec<Vec<crate::gamelog::LogFragment>>) { pub fn restore_log(log: &mut BTreeMap<i32, Vec<LogFragment>>) {
LOG.lock().unwrap().clear(); LOG.lock().unwrap().clear();
LOG.lock().unwrap().append(log); LOG.lock().unwrap().append(log);
} }

View file

@ -2,7 +2,7 @@ mod builder;
pub use builder::*; pub use builder::*;
mod logstore; mod logstore;
use logstore::*; use logstore::*;
pub use logstore::{ clear_log, clone_log, print_log, restore_log, setup_log }; pub use logstore::{ LOG, clear_log, clone_log, render_log, restore_log, setup_log };
mod events; mod events;
pub use events::*; pub use events::*;

View file

@ -1,7 +1,7 @@
use super::{ Skill, Skills }; use super::{ Skill, Skills };
use crate::gui::{ Ancestry, Class }; use crate::gui::{ Ancestry, Class };
use crate::data::entity; use crate::consts::entity;
use crate::data::char_create::*; use crate::consts::char_create::*;
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use std::cmp::max; use std::cmp::max;
@ -85,6 +85,7 @@ pub fn get_attribute_rolls(
ancestry: Ancestry ancestry: Ancestry
) -> (i32, i32, i32, i32, i32, i32) { ) -> (i32, i32, i32, i32, i32, i32) {
let (mut str, mut dex, mut con, mut int, mut wis, mut cha) = match class { let (mut str, mut dex, mut con, mut int, mut wis, mut cha) = match class {
Class::Unset => VILLAGER_MIN_ATTR,
Class::Fighter => FIGHTER_MIN_ATTR, Class::Fighter => FIGHTER_MIN_ATTR,
Class::Rogue => ROGUE_MIN_ATTR, Class::Rogue => ROGUE_MIN_ATTR,
Class::Wizard => WIZARD_MIN_ATTR, Class::Wizard => WIZARD_MIN_ATTR,
@ -92,6 +93,7 @@ pub fn get_attribute_rolls(
}; };
let mut remaining_points = TOTAL_ATTRIBUTE_POINTS_MAXIMUM - (str + dex + con + int + wis + cha); let mut remaining_points = TOTAL_ATTRIBUTE_POINTS_MAXIMUM - (str + dex + con + int + wis + cha);
let improve_chance: [i32; 6] = match class { let improve_chance: [i32; 6] = match class {
Class::Unset => VILLAGER_IMPR_CHANCE,
Class::Fighter => FIGHTER_IMPR_CHANCE, Class::Fighter => FIGHTER_IMPR_CHANCE,
Class::Rogue => ROGUE_IMPR_CHANCE, Class::Rogue => ROGUE_IMPR_CHANCE,
Class::Wizard => WIZARD_IMPR_CHANCE, Class::Wizard => WIZARD_IMPR_CHANCE,

View file

@ -8,8 +8,8 @@ use super::{
RunState, RunState,
State, State,
}; };
use crate::data::entity; use crate::consts::entity;
use crate::data::char_create::*; use crate::consts::char_create::*;
use crate::{ use crate::{
raws, raws,
Attribute, Attribute,
@ -28,10 +28,11 @@ use bracket_lib::prelude::*;
use serde::{ Deserialize, Serialize }; use serde::{ Deserialize, Serialize };
use specs::prelude::*; use specs::prelude::*;
use std::collections::HashMap; use std::collections::HashMap;
use crate::consts::prelude::*;
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq)] #[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Ancestry { pub enum Ancestry {
NULL, Unset,
Human, Human,
Dwarf, Dwarf,
Gnome, Gnome,
@ -39,8 +40,9 @@ pub enum Ancestry {
Catfolk, Catfolk,
} }
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq)] #[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Class { pub enum Class {
Unset,
Fighter, Fighter,
Rogue, Rogue,
Wizard, Wizard,
@ -48,48 +50,53 @@ pub enum Class {
} }
lazy_static! { lazy_static! {
static ref ANCESTRY_CLASS_DATA: HashMap<String, Vec<String>> = { static ref ANCESTRYDATA: HashMap<Ancestry, Vec<String>> = {
let mut m = HashMap::new(); let mut m = HashMap::new();
// Ancestry
m.insert( m.insert(
"human".to_string(), Ancestry::Human,
vec![ vec![
"nothing".to_string()]); "nothing".to_string()]);
m.insert( m.insert(
"dwarf".to_string(), Ancestry::Dwarf,
vec![ vec![
"a natural bonus to defence".to_string()]); "a natural bonus to defence".to_string()]);
m.insert( m.insert(
"elf".to_string(), Ancestry::Elf,
vec![ vec![
"minor telepathy".to_string(), "minor telepathy".to_string(),
"a slightly increased speed".to_string()]); "a slightly increased speed".to_string()]);
m.insert( m.insert(
"catfolk".to_string(), Ancestry::Catfolk,
vec![ vec![
"increased speed".to_string(), "increased speed".to_string(),
"increased unarmed damage".to_string()]); "increased unarmed damage".to_string()]);
// Class return m;
};
}
lazy_static! {
static ref CLASSDATA: HashMap<Class, Vec<String>> = {
let mut m = HashMap::new();
m.insert( m.insert(
"fighter".to_string(), Class::Fighter,
vec![ vec![
format!("a longsword, ring mail, and {} food", FIGHTER_STARTING_FOOD), format!("a longsword, ring mail, and {} food", FIGHTER_STARTING_FOOD),
"10 str, 8 dex, 10 con, 6 int, 6 wis, 8 cha".to_string(), "10 str, 8 dex, 10 con, 6 int, 6 wis, 8 cha".to_string(),
"and 27 random stat points".to_string()]); "and 27 random stat points".to_string()]);
m.insert( m.insert(
"rogue".to_string(), Class::Rogue,
vec![ vec![
format!("a rapier, leather armour, and {} food", ROGUE_STARTING_FOOD), format!("a rapier, leather armour, and {} food", ROGUE_STARTING_FOOD),
"8 str, 10 dex, 8 con, 6 int, 8 wis, 10 cha".to_string(), "8 str, 10 dex, 8 con, 6 int, 8 wis, 10 cha".to_string(),
"and 35 random stat points".to_string()]); "and 35 random stat points".to_string()]);
m.insert( m.insert(
"wizard".to_string(), Class::Wizard,
vec![ vec![
format!("a dagger, random scrolls/potions, and {} food", WIZARD_STARTING_FOOD), 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(), "6 str, 8 dex, 6 con, 10 int, 10 wis, 8 cha".to_string(),
"and 17 random stat points".to_string()]); "and 17 random stat points".to_string()]);
m.insert( m.insert(
"villager".to_string(), Class::Villager,
vec![ vec![
format!("the first weapon you could find, and {} food", VILLAGER_STARTING_FOOD), 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(), "6 str, 6 dex, 6 con, 6 int, 6 wis, 6 cha".to_string(),
@ -110,142 +117,123 @@ pub enum CharCreateResult {
}, },
} }
use notan::prelude::*;
use notan::draw::{ Draw, DrawTextSection };
use super::{ FONTSIZE, TILESIZE };
use crate::consts::DISPLAYHEIGHT;
use crate::Fonts;
pub fn draw_charcreation(
ecs: &World,
draw: &mut Draw,
atlas: &HashMap<String, Texture>,
font: &Fonts
) {
let runstate = ecs.read_resource::<RunState>();
let (class, ancestry) = match *runstate {
RunState::CharacterCreation { class, ancestry } => (class, ancestry),
_ => unreachable!("draw_charcreation() called outside of CharacterCreation runstate."),
};
let (mut x, mut y) = (2.0 * TILESIZE.x, ((DISPLAYHEIGHT as f32) * TILESIZE.x) / 4.0);
const COLUMN_WIDTH: f32 = 20.0 * TILESIZE.x;
draw.text(&font.ib(), "Who are you?")
.size(FONTSIZE * 2.0)
.position(x, y)
.h_align_left();
y = draw.last_text_bounds().max_y();
let initial_y = y;
let ancestries = [
("h. Human", Ancestry::Human),
("e. Elf", Ancestry::Elf),
("d. Dwarf", Ancestry::Dwarf),
("c. Catfolk", Ancestry::Catfolk),
];
for (k, v) in &ancestries {
draw.text(font.n(), k)
.size(FONTSIZE)
.position(x, y)
.h_align_left()
.color(get_colour(ancestry, *v));
y = draw.last_text_bounds().max_y();
}
y = initial_y;
x += COLUMN_WIDTH;
let classes = [
("f. Fighter", Class::Fighter),
("r. Rogue", Class::Rogue),
("w. Wizard", Class::Wizard),
("v. Villager", Class::Villager),
];
for (k, v) in &classes {
draw.text(font.n(), k)
.size(FONTSIZE)
.position(x, y)
.h_align_left()
.color(get_colour(class, *v));
y = draw.last_text_bounds().max_y();
}
y = initial_y;
x += COLUMN_WIDTH;
for line in ANCESTRYDATA.get(&ancestry).unwrap().iter() {
draw.text(font.n(), line).size(FONTSIZE).position(x, y).h_align_left();
y = draw.last_text_bounds().max_y();
}
y += TILESIZE.x;
for line in CLASSDATA.get(&class).unwrap().iter() {
draw.text(font.n(), line).size(FONTSIZE).position(x, y).h_align_left();
y = draw.last_text_bounds().max_y();
}
}
fn get_colour<T>(selected: T, desired: T) -> Color where T: PartialEq {
if selected == desired { Color::from_rgb(0.0, 1.0, 0.0) } else { Color::WHITE }
}
/// Handles the player character creation screen. /// Handles the player character creation screen.
pub fn character_creation(gs: &mut State, ctx: &mut BTerm) -> CharCreateResult { pub fn character_creation(gs: &mut State, ctx: &mut App) -> CharCreateResult {
let runstate = gs.ecs.fetch::<RunState>(); let runstate = gs.ecs.fetch::<RunState>();
let (ancestry, class) = match *runstate {
RunState::CharacterCreation { ancestry, class } => (ancestry, class),
_ => unreachable!("character_creation() called outside of CharacterCreation runstate."),
};
let mut x = 2; let key = &ctx.keyboard;
let mut y = 11; for keycode in key.pressed.iter() {
let column_width = 20; match *keycode {
KeyCode::Escape => {
ctx.print_color(x, y, RGB::named(WHITE), RGB::named(BLACK), CHAR_CREATE_HEADER); return CharCreateResult::Selected { ancestry: Ancestry::Unset, class };
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"); KeyCode::Return => {
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 }; return CharCreateResult::Selected { ancestry, class };
} }
VirtualKeyCode::H => { KeyCode::H => {
return CharCreateResult::NoSelection { ancestry: Ancestry::Human, class }; return CharCreateResult::NoSelection { ancestry: Ancestry::Human, class };
} }
VirtualKeyCode::E => { KeyCode::E => {
return CharCreateResult::NoSelection { ancestry: Ancestry::Elf, class }; return CharCreateResult::NoSelection { ancestry: Ancestry::Elf, class };
} }
VirtualKeyCode::D => { KeyCode::D => {
return CharCreateResult::NoSelection { ancestry: Ancestry::Dwarf, class }; return CharCreateResult::NoSelection { ancestry: Ancestry::Dwarf, class };
} }
VirtualKeyCode::C => { KeyCode::C => {
return CharCreateResult::NoSelection { ancestry: Ancestry::Catfolk, class }; return CharCreateResult::NoSelection { ancestry: Ancestry::Catfolk, class };
} }
VirtualKeyCode::F => { KeyCode::F => {
return CharCreateResult::NoSelection { ancestry, class: Class::Fighter }; return CharCreateResult::NoSelection { ancestry, class: Class::Fighter };
} }
VirtualKeyCode::R => { KeyCode::R => {
return CharCreateResult::NoSelection { ancestry, class: Class::Rogue }; return CharCreateResult::NoSelection { ancestry, class: Class::Rogue };
} }
VirtualKeyCode::W => { KeyCode::W => {
return CharCreateResult::NoSelection { ancestry, class: Class::Wizard }; return CharCreateResult::NoSelection { ancestry, class: Class::Wizard };
} }
VirtualKeyCode::V => { KeyCode::V => {
return CharCreateResult::NoSelection { ancestry, class: Class::Villager }; return CharCreateResult::NoSelection { ancestry, class: Class::Villager };
} }
_ => { _ => {}
};
}
return CharCreateResult::NoSelection { ancestry, class }; return CharCreateResult::NoSelection { ancestry, class };
}
}
}
}
return CharCreateResult::NoSelection { ancestry: Ancestry::Human, class: Class::Fighter };
} }
/// Handles player ancestry setup. /// Handles player ancestry setup.
@ -267,25 +255,9 @@ pub fn setup_player_ancestry(ecs: &mut World, ancestry: Ancestry) {
match ancestry { match ancestry {
Ancestry::Human => {} Ancestry::Human => {}
Ancestry::Dwarf => { 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; *player_skills.skills.entry(Skill::Defence).or_insert(0) += DWARF_DEFENCE_MOD;
} }
Ancestry::Elf => { 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>(); let mut telepaths = ecs.write_storage::<Telepath>();
telepaths telepaths
.insert(*player, Telepath { .insert(*player, Telepath {
@ -303,14 +275,6 @@ pub fn setup_player_ancestry(ecs: &mut World, ancestry: Ancestry) {
.expect("Unable to insert energy component"); .expect("Unable to insert energy component");
} }
Ancestry::Catfolk => { 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>(); let mut speeds = ecs.write_storage::<Energy>();
speeds speeds
.insert(*player, Energy { .insert(*player, Energy {
@ -342,14 +306,7 @@ pub fn setup_player_class(ecs: &mut World, class: Class, ancestry: Ancestry) {
let mut attributes = ecs.write_storage::<Attributes>(); let mut attributes = ecs.write_storage::<Attributes>();
let (str, dex, con, int, wis, cha) = get_attribute_rolls(&mut rng, class, ancestry); let (str, dex, con, int, wis, cha) = get_attribute_rolls(&mut rng, class, ancestry);
attributes attributes
.insert(player, Attributes { .insert(player, Attributes::with_stats(str, dex, con, int, wis, cha))
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"); .expect("Unable to insert attributes component");
let mut pools = ecs.write_storage::<Pools>(); let mut pools = ecs.write_storage::<Pools>();
@ -407,6 +364,9 @@ fn get_starting_inventory(
let mut carried: Vec<String> = Vec::new(); let mut carried: Vec<String> = Vec::new();
let starting_food: &str; let starting_food: &str;
match class { match class {
Class::Unset => {
starting_food = VILLAGER_STARTING_FOOD;
}
Class::Fighter => { Class::Fighter => {
starting_food = FIGHTER_STARTING_FOOD; starting_food = FIGHTER_STARTING_FOOD;
equipped = vec![ equipped = vec![
@ -440,7 +400,7 @@ fn get_starting_inventory(
} }
Class::Villager => { Class::Villager => {
starting_food = VILLAGER_STARTING_FOOD; starting_food = VILLAGER_STARTING_FOOD;
pick_random_table_item(rng, &mut equipped, "villager_equipment", "1", None); pick_random_table_item(rng, &mut equipped, "villager_equipment", "1d1", None);
} }
} }
pick_random_table_item(rng, &mut carried, "food", starting_food, None); pick_random_table_item(rng, &mut carried, "food", starting_food, None);
@ -454,7 +414,9 @@ fn pick_random_table_item(
dice_str: &'static str, dice_str: &'static str,
difficulty: Option<i32> difficulty: Option<i32>
) { ) {
let dice = parse_dice_string(dice_str).expect("Error parsing dice"); let dice = parse_dice_string(dice_str).expect(
format!("Error parsing dice: {}", dice_str).as_str()
);
for _i in 0..rng.roll_dice(dice.n_dice, dice.die_type) + dice.bonus { 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)); push_to.push(raws::table_by_name(&raws::RAWS.lock().unwrap(), table, difficulty).roll(rng));
} }

View file

@ -1,5 +1,9 @@
use super::State; use super::{ State };
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use notan::prelude::*;
use notan::draw::DrawTextSection;
use std::collections::HashMap;
use crate::consts::{ TILESIZE, FONTSIZE };
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone)]
pub enum CheatMenuResult { pub enum CheatMenuResult {
@ -12,53 +16,50 @@ pub enum CheatMenuResult {
GodMode, GodMode,
} }
pub fn show_cheat_menu(_gs: &mut State, ctx: &mut BTerm) -> CheatMenuResult { pub fn show_cheat_menu(_gs: &mut State, ctx: &mut App) -> CheatMenuResult {
let (x_offset, y_offset) = (1, 10); let key = &ctx.keyboard;
ctx.print_color( for keycode in key.pressed.iter() {
1 + x_offset, match *keycode {
1 + y_offset, KeyCode::A => {
RGB::named(RED), return CheatMenuResult::Ascend;
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,
} }
KeyCode::D => {
return CheatMenuResult::Descend;
} }
KeyCode::H => {
return CheatMenuResult::Heal;
}
KeyCode::M => {
return CheatMenuResult::MagicMap;
}
KeyCode::G => {
return CheatMenuResult::GodMode;
}
KeyCode::Escape => {
return CheatMenuResult::Cancel;
}
_ => {}
};
}
return CheatMenuResult::NoResponse;
}
pub fn draw_cheat_menu(
draw: &mut notan::draw::Draw,
_atlas: &HashMap<String, Texture>,
font: &crate::Fonts
) {
let offsets = crate::camera::get_offset();
const DEBUG_MENU: &str =
r#"DEBUG MENU! [aA-zZ][Esc.]
a - ASCEND A FLOOR
d - DESCEND A FLOOR
h - HEAL TO FULL
m - MAGIC MAP REVEAL
g - GOD MODE"#;
draw.text(&font.n(), DEBUG_MENU)
.position((1.0 + (offsets.x as f32)) * TILESIZE.x, (1.0 + (offsets.y as f32)) * TILESIZE.x)
.color(Color::RED)
.size(FONTSIZE);
} }

View file

@ -1,5 +1,10 @@
use super::{ State, RunState, tooltip::draw_tooltips, camera::get_screen_bounds }; use super::{ State, RunState, World, tooltip::draw_tooltips, camera::get_offset };
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use notan::prelude::*;
use notan::draw::{ Draw, DrawImages };
use std::collections::HashMap;
use crate::consts::visuals::{ TILES_IN_VIEWPORT_H, TILES_IN_VIEWPORT_W };
use crate::consts::TILESIZE;
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone)]
pub enum FarlookResult { pub enum FarlookResult {
@ -10,45 +15,62 @@ pub enum FarlookResult {
Cancel, Cancel,
} }
pub fn show_farlook(gs: &mut State, ctx: &mut BTerm) -> FarlookResult { pub fn show_farlook(gs: &mut State, ctx: &mut App) -> FarlookResult {
let runstate = gs.ecs.fetch::<RunState>(); 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 { if let RunState::Farlook { x, y } = *runstate {
let (screen_x, screen_y) = (69, 41); let x = x.clamp(0, TILES_IN_VIEWPORT_W - 1);
let x = x.clamp(x_offset, x_offset - 1 + (screen_x as i32)); let y = y.clamp(0, TILES_IN_VIEWPORT_H - 1);
let y = y.clamp(y_offset, y_offset - 1 + (screen_y as i32)); let key = &ctx.keyboard;
// Movement
ctx.set(x, y, RGB::named(WHITE), RGB::named(BLACK), to_cp437('X')); for keycode in key.pressed.iter() {
draw_tooltips(&gs.ecs, ctx, Some((x, y))); match *keycode {
KeyCode::Escape | KeyCode::X => {
return match ctx.key { return FarlookResult::Cancel;
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 },
} }
}; KeyCode::Numpad1 => {
return FarlookResult::NoResponse { x: x - 1, y: y + 1 };
}
KeyCode::Numpad2 => {
return FarlookResult::NoResponse { x, y: y + 1 };
}
KeyCode::Numpad3 => {
return FarlookResult::NoResponse { x: x + 1, y: y + 1 };
}
KeyCode::Numpad4 => {
return FarlookResult::NoResponse { x: x - 1, y };
}
KeyCode::Numpad6 => {
return FarlookResult::NoResponse { x: x + 1, y };
}
KeyCode::Numpad7 => {
return FarlookResult::NoResponse { x: x - 1, y: y - 1 };
}
KeyCode::Numpad8 => {
return FarlookResult::NoResponse { x, y: y - 1 };
}
KeyCode::Numpad9 => {
return FarlookResult::NoResponse { x: x + 1, y: y - 1 };
}
_ => {}
}
}
return FarlookResult::NoResponse { x, y };
} else { } else {
let ppos = gs.ecs.fetch::<Point>(); return FarlookResult::NoResponse { x: TILES_IN_VIEWPORT_W / 2, y: TILES_IN_VIEWPORT_H / 2 };
// TODO: PPOS + offsets (should get these from screen_bounds())
return FarlookResult::NoResponse { x: ppos.x + x_offset, y: ppos.x + y_offset };
} }
} }
pub fn draw_farlook(
ecs: &World,
x: i32,
y: i32,
draw: &mut Draw,
atlas: &HashMap<String, Texture>
) {
let placement = super::viewport_to_px(x, y);
draw.image(atlas.get("select1").unwrap())
.position(placement.x, placement.y)
.size(TILESIZE.sprite_x, TILESIZE.sprite_y);
let _idx = super::viewport_to_idx(ecs, x, y);
// Get tooltip for idx, etc.
}

View file

@ -3,10 +3,12 @@ use super::{
item_colour_ecs, item_colour_ecs,
obfuscate_name_ecs, obfuscate_name_ecs,
print_options, print_options,
unique_ecs,
renderable_colour, renderable_colour,
ItemMenuResult, ItemMenuResult,
UniqueInventoryItem, UniqueInventoryItem,
BUC, BUC,
Key,
}; };
use crate::{ use crate::{
gamelog, gamelog,
@ -23,7 +25,7 @@ use crate::{
}; };
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use specs::prelude::*; use specs::prelude::*;
use std::collections::BTreeMap; use std::collections::HashMap;
/// Handles the Identify menu. /// Handles the Identify menu.
pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Entity>) { pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Entity>) {
@ -37,9 +39,12 @@ pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Enti
let names = gs.ecs.read_storage::<Name>(); let names = gs.ecs.read_storage::<Name>();
let renderables = gs.ecs.read_storage::<Renderable>(); let renderables = gs.ecs.read_storage::<Renderable>();
let beatitudes = gs.ecs.read_storage::<Beatitude>(); let beatitudes = gs.ecs.read_storage::<Beatitude>();
let keys = gs.ecs.read_storage::<Key>();
let build_identify_iterator = || { let build_identify_iterator = || {
(&entities, &items, &renderables, &names).join().filter(|(item_entity, _i, _r, n)| { (&entities, &items, &renderables, &names, &keys)
.join()
.filter(|(item_entity, _i, _r, n, _k)| {
// If not owned by the player, return false. // If not owned by the player, return false.
let mut keep = false; let mut keep = false;
if let Some(bp) = backpack.get(*item_entity) { if let Some(bp) = backpack.get(*item_entity) {
@ -91,51 +96,32 @@ pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Enti
.log(); .log();
return (ItemMenuResult::Selected, Some(build_identify_iterator().nth(0).unwrap().0)); return (ItemMenuResult::Selected, Some(build_identify_iterator().nth(0).unwrap().0));
} }
let mut player_inventory: super::PlayerInventory = BTreeMap::new(); let mut player_inventory: super::PlayerInventory = HashMap::new();
for (entity, _i, renderable, name) in build_identify_iterator() { for (entity, _i, renderable, name, key) in build_identify_iterator() {
let (singular, plural) = obfuscate_name_ecs(&gs.ecs, entity); let unique_item = unique_ecs(&gs.ecs, entity);
let beatitude_status = if
let Some(beatitude) = gs.ecs.read_storage::<Beatitude>().get(entity)
{
match beatitude.buc {
BUC::Blessed => 1,
BUC::Uncursed => 2,
BUC::Cursed => 3,
}
} else {
0
};
let unique_item = UniqueInventoryItem {
display_name: super::DisplayName { singular: singular.clone(), plural: plural.clone() },
rgb: item_colour_ecs(&gs.ecs, entity),
renderables: renderable_colour(&renderables, entity),
glyph: renderable.glyph,
beatitude_status: beatitude_status,
name: name.name.clone(),
};
player_inventory player_inventory
.entry(unique_item) .entry(unique_item)
.and_modify(|(_e, count)| { .and_modify(|slot| {
*count += 1; slot.count += 1;
}) })
.or_insert((entity, 1)); .or_insert(super::InventorySlot { item: entity, count: 1, idx: key.idx });
} }
// Get display args // Get display args
let width = get_max_inventory_width(&player_inventory); let width = get_max_inventory_width(&player_inventory);
let (_, _, _, _, x_offset, y_offset) = crate::camera::get_screen_bounds(&gs.ecs, ctx); let offsets = crate::camera::get_offset();
let (x, y) = (x_offset + 1, y_offset + 3); let (x, y) = (offsets.x + 1, offsets.y + 3);
// Draw menu // Draw menu
ctx.print_color( ctx.print_color(
1 + x_offset, 1 + offsets.x,
1 + y_offset, 1 + offsets.y,
RGB::named(WHITE), RGB::named(WHITE),
RGB::named(BLACK), RGB::named(BLACK),
"Identify which item? [aA-zZ][Esc.]" "Identify which item? [aA-zZ][Esc.]"
); );
ctx.draw_box(x, y, width + 2, count + 1, RGB::named(WHITE), RGB::named(BLACK)); ctx.draw_box(x, y, width + 2, count + 1, RGB::named(WHITE), RGB::named(BLACK));
print_options(&player_inventory, x + 1, y + 1, ctx);
// Input // Input
match ctx.key { /*match ctx.key {
None => (ItemMenuResult::NoResponse, None), None => (ItemMenuResult::NoResponse, None),
Some(key) => Some(key) =>
match key { match key {
@ -161,4 +147,6 @@ pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Enti
} }
} }
} }
*/
(ItemMenuResult::NoResponse, None)
} }

76
src/gui/inventory.rs Normal file
View file

@ -0,0 +1,76 @@
use notan::prelude::*;
use notan::draw::{ Draw, Font, DrawTextSection };
use specs::prelude::*;
use super::TILESIZE;
use crate::{ Fonts, camera::get_offset };
use super::{ items, Filter, print_options, ItemType, FONTSIZE };
pub enum Location {
All,
Backpack,
Equipped,
}
pub fn all_itemtypes() -> Vec<ItemType> {
vec![
ItemType::Weapon,
ItemType::Armour,
ItemType::Comestible,
ItemType::Potion,
ItemType::Scroll,
ItemType::Spellbook,
ItemType::Wand,
ItemType::Amulet,
ItemType::Ring
]
}
pub fn draw_items(
ecs: &World,
draw: &mut Draw,
font: &Fonts,
x: f32,
y: f32,
loc: Location,
itemtypes: Option<Vec<ItemType>>
) {
let mut y = y;
if let Some(itemtypes) = itemtypes {
for itemtype in itemtypes {
let filter = match loc {
Location::All => Filter::All(Some(itemtype)),
Location::Backpack => Filter::Backpack(Some(itemtype)),
Location::Equipped => Filter::Equipped,
};
let inv = items(ecs, filter);
if inv.is_empty() {
continue;
}
draw.text(&font.b(), itemtype.string())
.position(x, y)
.color(Color::WHITE)
.size(FONTSIZE);
y += TILESIZE.x;
y = print_options(ecs, draw, font, &inv, x, y) + TILESIZE.x;
}
} else {
let filter = match loc {
Location::All => Filter::All(None),
Location::Backpack => Filter::Backpack(None),
Location::Equipped => Filter::Equipped,
};
let inv = items(ecs, filter);
if inv.is_empty() {
return;
}
y = print_options(ecs, draw, font, &inv, x, y) + TILESIZE.x;
}
}
pub fn draw_all_items(ecs: &World, draw: &mut Draw, font: &Fonts, x: f32, y: f32) {
draw_items(ecs, draw, font, x, y, Location::All, Some(all_itemtypes()));
}
pub fn draw_backpack_items(ecs: &World, draw: &mut Draw, font: &Fonts, x: f32, y: f32) {
draw_items(ecs, draw, font, x, y, Location::Backpack, Some(all_itemtypes()));
}

View file

@ -1,65 +1,65 @@
use bracket_lib::prelude::*; use notan::prelude::*;
pub fn letter_to_option(key: VirtualKeyCode, shift: bool) -> i32 { pub fn letter_to_option(key: KeyCode, shift: bool) -> Option<usize> {
if shift { if shift {
match key { match key {
VirtualKeyCode::A => 26, KeyCode::A => Some(26),
VirtualKeyCode::B => 27, KeyCode::B => Some(27),
VirtualKeyCode::C => 28, KeyCode::C => Some(28),
VirtualKeyCode::D => 29, KeyCode::D => Some(29),
VirtualKeyCode::E => 30, KeyCode::E => Some(30),
VirtualKeyCode::F => 31, KeyCode::F => Some(31),
VirtualKeyCode::G => 32, KeyCode::G => Some(32),
VirtualKeyCode::H => 33, KeyCode::H => Some(33),
VirtualKeyCode::I => 34, KeyCode::I => Some(34),
VirtualKeyCode::J => 35, KeyCode::J => Some(35),
VirtualKeyCode::K => 36, KeyCode::K => Some(36),
VirtualKeyCode::L => 37, KeyCode::L => Some(37),
VirtualKeyCode::M => 38, KeyCode::M => Some(38),
VirtualKeyCode::N => 39, KeyCode::N => Some(39),
VirtualKeyCode::O => 40, KeyCode::O => Some(40),
VirtualKeyCode::P => 41, KeyCode::P => Some(41),
VirtualKeyCode::Q => 42, KeyCode::Q => Some(42),
VirtualKeyCode::R => 43, KeyCode::R => Some(43),
VirtualKeyCode::S => 44, KeyCode::S => Some(44),
VirtualKeyCode::T => 45, KeyCode::T => Some(45),
VirtualKeyCode::U => 46, KeyCode::U => Some(46),
VirtualKeyCode::V => 47, KeyCode::V => Some(47),
VirtualKeyCode::W => 48, KeyCode::W => Some(48),
VirtualKeyCode::X => 49, KeyCode::X => Some(49),
VirtualKeyCode::Y => 50, KeyCode::Y => Some(50),
VirtualKeyCode::Z => 51, KeyCode::Z => Some(51),
_ => -1, _ => None,
} }
} else { } else {
match key { match key {
VirtualKeyCode::A => 0, KeyCode::A => Some(0),
VirtualKeyCode::B => 1, KeyCode::B => Some(1),
VirtualKeyCode::C => 2, KeyCode::C => Some(2),
VirtualKeyCode::D => 3, KeyCode::D => Some(3),
VirtualKeyCode::E => 4, KeyCode::E => Some(4),
VirtualKeyCode::F => 5, KeyCode::F => Some(5),
VirtualKeyCode::G => 6, KeyCode::G => Some(6),
VirtualKeyCode::H => 7, KeyCode::H => Some(7),
VirtualKeyCode::I => 8, KeyCode::I => Some(8),
VirtualKeyCode::J => 9, KeyCode::J => Some(9),
VirtualKeyCode::K => 10, KeyCode::K => Some(10),
VirtualKeyCode::L => 11, KeyCode::L => Some(11),
VirtualKeyCode::M => 12, KeyCode::M => Some(12),
VirtualKeyCode::N => 13, KeyCode::N => Some(13),
VirtualKeyCode::O => 14, KeyCode::O => Some(14),
VirtualKeyCode::P => 15, KeyCode::P => Some(15),
VirtualKeyCode::Q => 16, KeyCode::Q => Some(16),
VirtualKeyCode::R => 17, KeyCode::R => Some(17),
VirtualKeyCode::S => 18, KeyCode::S => Some(18),
VirtualKeyCode::T => 19, KeyCode::T => Some(19),
VirtualKeyCode::U => 20, KeyCode::U => Some(20),
VirtualKeyCode::V => 21, KeyCode::V => Some(21),
VirtualKeyCode::W => 22, KeyCode::W => Some(22),
VirtualKeyCode::X => 23, KeyCode::X => Some(23),
VirtualKeyCode::Y => 24, KeyCode::Y => Some(24),
VirtualKeyCode::Z => 25, KeyCode::Z => Some(25),
_ => -1, _ => None,
} }
} }
} }

47
src/gui/main_menu.rs Normal file
View file

@ -0,0 +1,47 @@
use notan::prelude::*;
use notan::draw::{ Draw, CreateDraw, DrawTextSection, Font };
use specs::prelude::*;
use std::collections::HashMap;
use super::{ FONTSIZE, RunState, DISPLAYWIDTH, TILESIZE, MainMenuSelection };
use crate::consts::DISPLAYHEIGHT;
use crate::Fonts;
pub fn draw_mainmenu(ecs: &World, draw: &mut Draw, atlas: &HashMap<String, Texture>, font: &Fonts) {
let runstate = ecs.read_resource::<RunState>();
let selected = match *runstate {
RunState::MainMenu { menu_selection } => menu_selection,
_ => unreachable!("draw_mainmenu() called outside of MainMenu runstate."),
};
let save_exists = crate::saveload_system::does_save_exist();
const MID_X: f32 = ((DISPLAYWIDTH as f32) * TILESIZE.x) / 2.0;
let (x, mut y) = (MID_X, ((DISPLAYHEIGHT as f32) * TILESIZE.x) / 4.0);
draw.text(&font.ib(), "RUST-RL")
.size(FONTSIZE * 2.0)
.position(x, y)
.h_align_center();
y = draw.last_text_bounds().max_y();
draw.text(&font.n(), "New Game")
.size(FONTSIZE)
.position(x, y)
.h_align_center()
.color(get_colour(selected, MainMenuSelection::NewGame));
if save_exists {
y = draw.last_text_bounds().max_y();
draw.text(font.n(), "Load Game")
.size(FONTSIZE)
.position(x, y)
.h_align_center()
.color(get_colour(selected, MainMenuSelection::LoadGame));
}
y = draw.last_text_bounds().max_y();
draw.text(&font.n(), "Quit")
.size(FONTSIZE)
.position(x, y)
.h_align_center()
.color(get_colour(selected, MainMenuSelection::Quit));
}
fn get_colour(selected: MainMenuSelection, desired: MainMenuSelection) -> Color {
if selected == desired { Color::from_rgb(0.0, 1.0, 0.0) } else { Color::WHITE }
}

File diff suppressed because it is too large Load diff

View file

@ -3,9 +3,11 @@ use super::{
item_colour_ecs, item_colour_ecs,
obfuscate_name_ecs, obfuscate_name_ecs,
print_options, print_options,
unique_ecs,
renderable_colour, renderable_colour,
ItemMenuResult, ItemMenuResult,
UniqueInventoryItem, UniqueInventoryItem,
InventorySlot,
}; };
use crate::{ use crate::{
gamelog, gamelog,
@ -18,10 +20,11 @@ use crate::{
Renderable, Renderable,
states::state::*, states::state::*,
BUC, BUC,
Key,
}; };
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use specs::prelude::*; use specs::prelude::*;
use std::collections::BTreeMap; use std::collections::HashMap;
/// Handles the Remove Curse menu. /// Handles the Remove Curse menu.
pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Entity>) { pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Entity>) {
@ -33,11 +36,12 @@ pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<
let beatitudes = gs.ecs.read_storage::<Beatitude>(); let beatitudes = gs.ecs.read_storage::<Beatitude>();
let names = gs.ecs.read_storage::<Name>(); let names = gs.ecs.read_storage::<Name>();
let renderables = gs.ecs.read_storage::<Renderable>(); let renderables = gs.ecs.read_storage::<Renderable>();
let keys = gs.ecs.read_storage::<Key>();
let build_cursed_iterator = || { let build_cursed_iterator = || {
(&entities, &items, &beatitudes, &renderables, &names) (&entities, &items, &beatitudes, &renderables, &names, &keys)
.join() .join()
.filter(|(item_entity, _i, b, _r, _n)| { .filter(|(item_entity, _i, b, _r, _n, _k)| {
// Set all items to FALSE initially. // Set all items to FALSE initially.
let mut keep = false; let mut keep = false;
// If found in the player's backpack, set to TRUE // If found in the player's backpack, set to TRUE
@ -86,8 +90,8 @@ pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<
.log(); .log();
return (ItemMenuResult::Selected, Some(item)); return (ItemMenuResult::Selected, Some(item));
} }
let mut player_inventory: super::PlayerInventory = BTreeMap::new(); let mut player_inventory: super::PlayerInventory = HashMap::new();
for (entity, _i, _b, renderable, name) in build_cursed_iterator() { for (entity, _i, _b, renderable, name, key) in build_cursed_iterator() {
let (singular, plural) = obfuscate_name_ecs(&gs.ecs, entity); let (singular, plural) = obfuscate_name_ecs(&gs.ecs, entity);
let beatitude_status = if let beatitude_status = if
let Some(beatitude) = gs.ecs.read_storage::<Beatitude>().get(entity) let Some(beatitude) = gs.ecs.read_storage::<Beatitude>().get(entity)
@ -100,37 +104,34 @@ pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<
} else { } else {
0 0
}; };
let unique_item = UniqueInventoryItem { let unique_item = unique_ecs(&gs.ecs, entity);
display_name: super::DisplayName { singular: singular.clone(), plural: plural.clone() },
rgb: item_colour_ecs(&gs.ecs, entity),
renderables: renderable_colour(&renderables, entity),
glyph: renderable.glyph,
beatitude_status: beatitude_status,
name: name.name.clone(),
};
player_inventory player_inventory
.entry(unique_item) .entry(unique_item)
.and_modify(|(_e, count)| { .and_modify(|slot| {
*count += 1; slot.count += 1;
}) })
.or_insert((entity, 1)); .or_insert(InventorySlot {
item: entity,
count: 1,
idx: key.idx,
});
} }
// Get display args // Get display args
let width = get_max_inventory_width(&player_inventory); let width = get_max_inventory_width(&player_inventory);
let (_, _, _, _, x_offset, y_offset) = crate::camera::get_screen_bounds(&gs.ecs, ctx); let offsets = crate::camera::get_offset();
let (x, y) = (x_offset + 1, y_offset + 3); let (x, y) = (offsets.x + 1, offsets.y + 3);
// Draw menu // Draw menu
ctx.print_color( ctx.print_color(
1 + x_offset, 1 + offsets.x,
1 + y_offset, 1 + offsets.y,
RGB::named(WHITE), RGB::named(WHITE),
RGB::named(BLACK), RGB::named(BLACK),
"Decurse which item? [aA-zZ][Esc.]" "Decurse which item? [aA-zZ][Esc.]"
); );
ctx.draw_box(x, y, width + 2, count + 1, RGB::named(WHITE), RGB::named(BLACK)); ctx.draw_box(x, y, width + 2, count + 1, RGB::named(WHITE), RGB::named(BLACK));
print_options(&player_inventory, x + 1, y + 1, ctx);
// Input // Input
match ctx.key { /*match ctx.key {
None => (ItemMenuResult::NoResponse, None), None => (ItemMenuResult::NoResponse, None),
Some(key) => Some(key) =>
match key { match key {
@ -155,5 +156,6 @@ pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<
(ItemMenuResult::NoResponse, None) (ItemMenuResult::NoResponse, None)
} }
} }
} }*/
(ItemMenuResult::NoResponse, None)
} }

View file

@ -11,7 +11,8 @@ use super::{
RGB, RGB,
}; };
use crate::TileType; use crate::TileType;
use crate::data::ids::*; use crate::consts::ids::*;
use crate::consts::prelude::*;
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use specs::prelude::*; use specs::prelude::*;
@ -45,6 +46,7 @@ impl Tooltip {
return (self.lines.len() as i32) + 2i32; return (self.lines.len() as i32) + 2i32;
} }
fn render(&self, ctx: &mut BTerm, x: i32, y: i32) { fn render(&self, ctx: &mut BTerm, x: i32, y: i32) {
ctx.set_active_console(TEXT_LAYER);
ctx.draw_box( ctx.draw_box(
x, x,
y, y,
@ -56,12 +58,13 @@ impl Tooltip {
for (i, s) in self.lines.iter().enumerate() { 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); ctx.print_color(x + 1, y + (i as i32) + 1, s.1, RGB::named(BLACK), &s.0);
} }
ctx.set_active_console(TILE_LAYER);
} }
} }
#[rustfmt::skip] #[rustfmt::skip]
pub fn draw_tooltips(ecs: &World, ctx: &mut BTerm, xy: Option<(i32, i32)>) { 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 bounds = get_screen_bounds(ecs, false);
let map = ecs.fetch::<Map>(); let map = ecs.fetch::<Map>();
let names = ecs.read_storage::<Name>(); let names = ecs.read_storage::<Name>();
let positions = ecs.read_storage::<Position>(); let positions = ecs.read_storage::<Position>();
@ -74,8 +77,8 @@ pub fn draw_tooltips(ecs: &World, ctx: &mut BTerm, xy: Option<(i32, i32)>) {
let mouse_pos = if xy.is_none() { ctx.mouse_pos() } else { xy.unwrap() }; let mouse_pos = if xy.is_none() { ctx.mouse_pos() } else { xy.unwrap() };
let mut mouse_pos_adjusted = mouse_pos; let mut mouse_pos_adjusted = mouse_pos;
mouse_pos_adjusted.0 += min_x - x_offset; mouse_pos_adjusted.0 += bounds.min_x - bounds.x_offset;
mouse_pos_adjusted.1 += min_y - y_offset; mouse_pos_adjusted.1 += bounds.min_y - bounds.y_offset;
if mouse_pos_adjusted.0 >= map.width if mouse_pos_adjusted.0 >= map.width
|| mouse_pos_adjusted.1 >= map.height || 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.1 < 0 // Might need to be 1, and -1 from map height/width.
@ -121,18 +124,18 @@ pub fn draw_tooltips(ecs: &World, ctx: &mut BTerm, xy: Option<(i32, i32)>) {
let attr = attributes.get(entity); let attr = attributes.get(entity);
if let Some(a) = attr { if let Some(a) = attr {
let mut s = "".to_string(); let mut s = "".to_string();
if a.strength.bonus < -2 { s += "weak "}; if a.strength.modifier() < -2 { s += "weak "};
if a.strength.bonus > 2 { s += "strong "}; if a.strength.modifier() > 2 { s += "strong "};
if a.dexterity.bonus < -2 { s += "clumsy "}; if a.dexterity.modifier() < -2 { s += "clumsy "};
if a.dexterity.bonus > 2 { s += "agile "}; if a.dexterity.modifier() > 2 { s += "agile "};
if a.constitution.bonus < -2 { s += "frail "}; if a.constitution.modifier() < -2 { s += "frail "};
if a.constitution.bonus > 2 { s += "hardy "}; if a.constitution.modifier() > 2 { s += "hardy "};
if a.intelligence.bonus < -2 { s += "dim "}; if a.intelligence.modifier() < -2 { s += "dim "};
if a.intelligence.bonus > 2 { s += "smart "}; if a.intelligence.modifier() > 2 { s += "smart "};
if a.wisdom.bonus < -2 { s += "unwise "}; if a.wisdom.modifier() < -2 { s += "unwise "};
if a.wisdom.bonus > 2 { s += "wisened "}; if a.wisdom.modifier() > 2 { s += "wisened "};
if a.charisma.bonus < -2 { s += "ugly"}; if a.charisma.modifier() < -2 { s += "ugly"};
if a.charisma.bonus > 2 { s += "attractive"}; if a.charisma.modifier() > 2 { s += "attractive"};
if !s.is_empty() { if !s.is_empty() {
if s.ends_with(" ") { if s.ends_with(" ") {
s.pop(); s.pop();
@ -175,13 +178,15 @@ pub fn draw_tooltips(ecs: &World, ctx: &mut BTerm, xy: Option<(i32, i32)>) {
if mouse_pos.0 > 35 { if mouse_pos.0 > 35 {
// Render to the left // Render to the left
arrow = to_cp437('→'); arrow = to_cp437('→');
arrow_x = mouse_pos.0 - 1; arrow_x = mouse_pos.0 * 2 - 1;
} else { } else {
// Render to the right // Render to the right
arrow = to_cp437('←'); arrow = to_cp437('←');
arrow_x = mouse_pos.0 + 1; arrow_x = (mouse_pos.0 + 1) * 2;
} }
ctx.set_active_console(TEXT_LAYER);
ctx.set(arrow_x, arrow_y, white, RGB::named(BLACK), arrow); ctx.set(arrow_x, arrow_y, white, RGB::named(BLACK), arrow);
ctx.set_active_console(TILE_LAYER);
let mut total_height = 0; let mut total_height = 0;
for t in tooltips.iter() { for t in tooltips.iter() {
@ -195,9 +200,9 @@ pub fn draw_tooltips(ecs: &World, ctx: &mut BTerm, xy: Option<(i32, i32)>) {
for t in tooltips.iter() { for t in tooltips.iter() {
let x = if mouse_pos.0 > 35 { let x = if mouse_pos.0 > 35 {
mouse_pos.0 - (1 + t.width()) (mouse_pos.0 * 2) - (1 + t.width())
} else { } else {
mouse_pos.0 + (1 + 1) (mouse_pos.0 * 2) + 2 + 1
}; };
t.render(ctx, x, y); t.render(ctx, x, y);
y += t.height(); y += t.height();

View file

@ -12,9 +12,12 @@ use crate::{
ObfuscatedName, ObfuscatedName,
Position, Position,
WantsToPickupItem, WantsToPickupItem,
WantsToAssignKey,
Renderable,
Stackable,
}; };
use specs::prelude::*; use specs::prelude::*;
use crate::data::messages; use crate::consts::messages;
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
pub struct ItemCollectionSystem {} pub struct ItemCollectionSystem {}
@ -30,9 +33,12 @@ impl<'a> System<'a> for ItemCollectionSystem {
WriteStorage<'a, EquipmentChanged>, WriteStorage<'a, EquipmentChanged>,
ReadStorage<'a, MagicItem>, ReadStorage<'a, MagicItem>,
ReadStorage<'a, ObfuscatedName>, ReadStorage<'a, ObfuscatedName>,
ReadStorage<'a, Renderable>,
ReadStorage<'a, Beatitude>, ReadStorage<'a, Beatitude>,
ReadExpect<'a, MasterDungeonMap>, ReadExpect<'a, MasterDungeonMap>,
ReadStorage<'a, Charges>, ReadStorage<'a, Charges>,
ReadStorage<'a, WantsToAssignKey>,
ReadStorage<'a, Stackable>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
@ -45,20 +51,17 @@ impl<'a> System<'a> for ItemCollectionSystem {
mut equipment_changed, mut equipment_changed,
magic_items, magic_items,
obfuscated_names, obfuscated_names,
renderables,
beatitudes, beatitudes,
dm, dm,
wands, wands,
wants_key,
stackable,
) = data; ) = data;
for pickup in wants_pickup.join() { let mut to_remove: Vec<Entity> = Vec::new();
positions.remove(pickup.item); // For every item that wants to be picked up, that *isn't* still waiting on a key assignment.
backpack for (pickup, _key) in (&wants_pickup, !&wants_key).join() {
.insert(pickup.item, InBackpack { owner: pickup.collected_by })
.expect("Unable to pickup item.");
equipment_changed
.insert(pickup.collected_by, EquipmentChanged {})
.expect("Unable to insert EquipmentChanged.");
if pickup.collected_by == *player_entity { if pickup.collected_by == *player_entity {
gamelog::Logger gamelog::Logger
::new() ::new()
@ -82,8 +85,17 @@ impl<'a> System<'a> for ItemCollectionSystem {
.period() .period()
.log(); .log();
} }
positions.remove(pickup.item);
backpack
.insert(pickup.item, InBackpack { owner: pickup.collected_by })
.expect("Unable to pickup item.");
equipment_changed
.insert(pickup.collected_by, EquipmentChanged {})
.expect("Unable to insert EquipmentChanged.");
to_remove.push(pickup.collected_by);
}
for item in to_remove.iter() {
wants_pickup.remove(*item);
} }
wants_pickup.clear();
} }
} }

View file

@ -12,9 +12,10 @@ use crate::{
ObfuscatedName, ObfuscatedName,
Position, Position,
WantsToDropItem, WantsToDropItem,
WantsToRemoveKey,
}; };
use specs::prelude::*; use specs::prelude::*;
use crate::data::messages; use crate::consts::messages;
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
pub struct ItemDropSystem {} pub struct ItemDropSystem {}
@ -34,6 +35,7 @@ impl<'a> System<'a> for ItemDropSystem {
ReadStorage<'a, ObfuscatedName>, ReadStorage<'a, ObfuscatedName>,
ReadExpect<'a, MasterDungeonMap>, ReadExpect<'a, MasterDungeonMap>,
ReadStorage<'a, Charges>, ReadStorage<'a, Charges>,
WriteStorage<'a, WantsToRemoveKey>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
@ -50,6 +52,7 @@ impl<'a> System<'a> for ItemDropSystem {
obfuscated_names, obfuscated_names,
dm, dm,
wands, wands,
mut keys,
) = data; ) = data;
for (entity, to_drop) in (&entities, &wants_drop).join() { for (entity, to_drop) in (&entities, &wants_drop).join() {
@ -68,6 +71,9 @@ impl<'a> System<'a> for ItemDropSystem {
backpack.remove(to_drop.item); backpack.remove(to_drop.item);
if entity == *player_entity { if entity == *player_entity {
keys.insert(to_drop.item, WantsToRemoveKey {}).expect(
"Unable to insert WantsToRemoveKey"
);
gamelog::Logger gamelog::Logger
::new() ::new()
.append(messages::YOU_DROP_ITEM) .append(messages::YOU_DROP_ITEM)

View file

@ -16,7 +16,7 @@ use crate::{
BUC, BUC,
}; };
use specs::prelude::*; use specs::prelude::*;
use crate::data::messages; use crate::consts::messages;
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
pub struct ItemEquipSystem {} pub struct ItemEquipSystem {}

View file

@ -9,7 +9,7 @@ use crate::{
Player, Player,
}; };
use specs::prelude::*; use specs::prelude::*;
use crate::data::events::*; use crate::consts::events::*;
use crate::gamelog; use crate::gamelog;
pub struct ItemIdentificationSystem {} pub struct ItemIdentificationSystem {}

View file

@ -0,0 +1,175 @@
use crate::{
gamelog,
gui::unique,
Beatitude,
Charges,
MagicItem,
MasterDungeonMap,
Name,
ObfuscatedName,
Stackable,
Renderable,
WantsToAssignKey,
WantsToRemoveKey,
Key,
};
use specs::prelude::*;
use crate::consts::messages;
use bracket_lib::prelude::*;
use crate::invkeys::*;
pub struct KeyHandling {}
const DEBUG_KEYHANDLING: bool = false;
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 {
if DEBUG_KEYHANDLING {
console::log(&format!("KEYHANDLING: Item is stackable."));
}
let maybe_key = item_exists(&unique);
if maybe_key.is_some() {
if DEBUG_KEYHANDLING {
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.");
if DEBUG_KEYHANDLING {
console::log(&format!("KEYHANDLING: Assigned key idx {} to item.", key));
}
handled = true;
}
}
if !handled {
if DEBUG_KEYHANDLING {
console::log(
&format!("KEYHANDLING: Item is not stackable, or no existing stack found.")
);
}
if let Some(idx) = assign_next_available() {
if DEBUG_KEYHANDLING {
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 {
if DEBUG_KEYHANDLING {
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) {
if DEBUG_KEYHANDLING {
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.
if DEBUG_KEYHANDLING {
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 {
if DEBUG_KEYHANDLING {
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 {
if DEBUG_KEYHANDLING {
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.
if DEBUG_KEYHANDLING {
console::log(&format!("KEYHANDLING: Removing key component from item."));
}
keys.remove(e);
}
wants_removekey.clear();
wants_keys.clear();
}
}

View file

@ -4,6 +4,7 @@ mod equip_system;
mod identification_system; mod identification_system;
mod remove_system; mod remove_system;
mod use_system; mod use_system;
mod keyhandling;
pub use self::{ pub use self::{
collection_system::ItemCollectionSystem, collection_system::ItemCollectionSystem,
@ -12,4 +13,5 @@ pub use self::{
identification_system::ItemIdentificationSystem, identification_system::ItemIdentificationSystem,
remove_system::ItemRemoveSystem, remove_system::ItemRemoveSystem,
use_system::ItemUseSystem, use_system::ItemUseSystem,
keyhandling::KeyHandling,
}; };

View file

@ -13,7 +13,7 @@ use crate::{
}; };
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use specs::prelude::*; use specs::prelude::*;
use crate::data::messages; use crate::consts::messages;
pub struct ItemRemoveSystem {} pub struct ItemRemoveSystem {}

59
src/invkeys.rs Normal file
View file

@ -0,0 +1,59 @@
use std::sync::Mutex;
use std::collections::{ HashMap };
use specs::prelude::*;
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::*;
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);
}

View file

@ -29,7 +29,7 @@ pub mod trigger_system;
pub mod inventory; pub mod inventory;
pub mod particle_system; pub mod particle_system;
pub mod ai; pub mod ai;
pub mod data; pub mod consts;
pub mod config; pub mod config;
pub mod effects; pub mod effects;
pub mod gamesystem; pub mod gamesystem;
@ -38,9 +38,11 @@ pub mod rex_assets;
pub mod spatial; pub mod spatial;
pub mod morgue; pub mod morgue;
pub mod states; pub mod states;
pub mod invkeys;
pub use components::*; pub use components::*;
use particle_system::ParticleBuilder; use particle_system::ParticleBuilder;
pub use map::*; pub use map::*;
pub use states::runstate::RunState; pub use states::runstate::RunState;
pub use states::state::State; pub use states::state::State;
pub use states::state::Fonts;

View file

@ -1,31 +1,70 @@
use rust_rl::*; use rust_rl::*;
use notan::prelude::*;
use notan::math::{ vec2, vec3, Mat4, Vec2 };
use notan::draw::create_textures_from_atlas;
use notan::draw::{ CreateFont, CreateDraw, DrawImages, Draw, DrawTextSection, DrawShapes };
use specs::prelude::*; use specs::prelude::*;
use specs::saveload::{ SimpleMarker, SimpleMarkerAllocator }; use specs::saveload::{ SimpleMarker, SimpleMarkerAllocator };
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use std::collections::HashMap;
use crate::consts::{ DISPLAYHEIGHT, DISPLAYWIDTH, TILESIZE, FONTSIZE };
use crate::states::state::Fonts;
const DISPLAYWIDTH: i32 = 105; const WORK_SIZE: Vec2 = vec2(
const DISPLAYHEIGHT: i32 = 56; (DISPLAYWIDTH as f32) * TILESIZE.x,
(DISPLAYHEIGHT as f32) * TILESIZE.x
);
fn main() -> BError { #[notan_main]
// Embedded resources for use in wasm build fn main() -> Result<(), String> {
const CURSES_14_16_BYTES: &[u8] = include_bytes!("../resources/curses14x16.png"); let win_config = WindowConfig::new()
EMBED.lock().add_resource("resources/curses14x16.png".to_string(), CURSES_14_16_BYTES); .set_size(DISPLAYWIDTH * (TILESIZE.x as u32), DISPLAYHEIGHT * (TILESIZE.x as u32))
.set_title("RUST-RL")
//link_resource!(CURSES14X16, "../resources/curses_14x16.png"); .set_resizable(true)
.set_fullscreen(true)
let mut context = BTermBuilder::new() .set_taskbar_icon_data(Some(include_bytes!("../resources/icon.png")))
.with_title("rust-rl") .set_vsync(true);
.with_dimensions(DISPLAYWIDTH, DISPLAYHEIGHT) notan
.with_font("curses14x16.png", 14, 16) ::init_with(setup)
.with_tile_dimensions(14, 16) .add_config(win_config)
.with_simple_console(DISPLAYWIDTH, DISPLAYHEIGHT, "curses14x16.png") .add_config(notan::draw::DrawConfig)
.build()?; .draw(draw)
if config::CONFIG.visuals.with_scanlines { .update(update)
context.with_post_scanlines(config::CONFIG.visuals.with_screen_burn); .build()
} }
fn setup(app: &mut App, gfx: &mut Graphics) -> State {
effects::sound::init_sounds(app);
effects::sound::ambience("a_relax");
let texture = gfx
.create_texture()
.from_image(include_bytes!("../resources/atlas.png"))
.build()
.unwrap();
let data = include_bytes!("../resources/atlas.json");
let atlas = create_textures_from_atlas(data, &texture).unwrap();
let texture = gfx
.create_texture()
.from_image(include_bytes!("../resources/td.png"))
.build()
.unwrap();
let data = include_bytes!("../resources/td.json");
let interface = create_textures_from_atlas(data, &texture).unwrap();
let font = Fonts::new(
gfx.create_font(include_bytes!("../resources/fonts/Greybeard-16px.ttf")).unwrap(),
Some(
gfx.create_font(include_bytes!("../resources/fonts/Greybeard-16px-Bold.ttf")).unwrap()
),
Some(
gfx.create_font(include_bytes!("../resources/fonts/Greybeard-16px-Italic.ttf")).unwrap()
)
);
let mut gs = State { let mut gs = State {
ecs: World::new(), ecs: World::new(),
//audio: sounds,
atlas,
interface,
font,
mapgen_next_state: Some(RunState::MainMenu { mapgen_next_state: Some(RunState::MainMenu {
menu_selection: gui::MainMenuSelection::NewGame, menu_selection: gui::MainMenuSelection::NewGame,
}), }),
@ -37,6 +76,7 @@ fn main() -> BError {
gs.ecs.register::<Position>(); gs.ecs.register::<Position>();
gs.ecs.register::<OtherLevelPosition>(); gs.ecs.register::<OtherLevelPosition>();
gs.ecs.register::<Renderable>(); gs.ecs.register::<Renderable>();
gs.ecs.register::<Avatar>();
gs.ecs.register::<Burden>(); gs.ecs.register::<Burden>();
gs.ecs.register::<Prop>(); gs.ecs.register::<Prop>();
gs.ecs.register::<Player>(); gs.ecs.register::<Player>();
@ -111,6 +151,11 @@ fn main() -> BError {
gs.ecs.register::<SpawnParticleLine>(); gs.ecs.register::<SpawnParticleLine>();
gs.ecs.register::<HasDamageModifiers>(); gs.ecs.register::<HasDamageModifiers>();
gs.ecs.register::<Intrinsics>(); gs.ecs.register::<Intrinsics>();
gs.ecs.register::<Stackable>();
gs.ecs.register::<WantsToAssignKey>();
gs.ecs.register::<Key>();
gs.ecs.register::<WantsToRemoveKey>();
gs.ecs.register::<WantsToDelete>();
gs.ecs.register::<IntrinsicChanged>(); gs.ecs.register::<IntrinsicChanged>();
gs.ecs.register::<SimpleMarker<SerializeMe>>(); gs.ecs.register::<SimpleMarker<SerializeMe>>();
gs.ecs.register::<SerializationHelper>(); gs.ecs.register::<SerializationHelper>();
@ -127,13 +172,526 @@ fn main() -> BError {
gs.ecs.insert(gui::Ancestry::Human); // ancestry gs.ecs.insert(gui::Ancestry::Human); // ancestry
let player_entity = spawner::player(&mut gs.ecs, 0, 0); let player_entity = spawner::player(&mut gs.ecs, 0, 0);
gs.ecs.insert(player_entity); // Player entity gs.ecs.insert(player_entity); // Player entity
gs.ecs.insert(RunState::MapGeneration {}); // RunState gs.ecs.insert(RunState::MapGeneration {}); // TODO: Set this back to RunState::MapGen
gs.ecs.insert(particle_system::ParticleBuilder::new()); gs.ecs.insert(particle_system::ParticleBuilder::new());
gs.ecs.insert(rex_assets::RexAssets::new()); gs.ecs.insert(rex_assets::RexAssets::new());
gamelog::setup_log(); gamelog::setup_log();
gamelog::record_event(data::events::EVENT::Level(1)); gamelog::record_event(consts::events::EVENT::Level(1));
gs.generate_world_map(1, TileType::Floor); gs.generate_world_map(1, TileType::Floor);
main_loop(context, gs) gs
}
const ASCII_MODE: bool = false; // Change this to config setting
const SHOW_BOUNDARIES: bool = false; // Config setting
#[derive(PartialEq)]
enum DrawType {
None,
Player,
Visible,
Telepathy,
}
#[derive(PartialEq, Eq, Hash)]
struct DrawKey {
x: i32,
y: i32,
render_order: i32,
}
struct DrawInfo {
e: Entity,
draw_type: DrawType,
}
fn draw_entities(
map: &Map,
ecs: &World,
draw: &mut Draw,
atlas: &HashMap<String, Texture>,
_font: &Fonts
) {
{
let bounds = crate::camera::get_screen_bounds(ecs, false);
let bounds_to_px = bounds.to_px();
let offset_x = bounds_to_px.x_offset - bounds_to_px.min_x;
let offset_y = bounds_to_px.y_offset - bounds_to_px.min_y;
let positions = ecs.read_storage::<Position>();
let renderables = ecs.read_storage::<Renderable>();
let hidden = ecs.read_storage::<Hidden>();
let minds = ecs.read_storage::<Mind>();
let pools = ecs.read_storage::<Pools>();
let entities = ecs.entities();
let player = ecs.read_storage::<Player>();
let data = (&positions, &renderables, &entities, !&hidden).join().collect::<Vec<_>>();
let mut to_draw: HashMap<DrawKey, DrawInfo> = HashMap::new();
for (pos, render, e, _h) in data.iter() {
let idx = map.xy_idx(pos.x, pos.y);
if
crate::camera::in_bounds(
pos.x,
pos.y,
bounds.min_x,
bounds.min_y,
bounds.max_x,
bounds.max_y
)
{
let draw_type = if map.visible_tiles[idx] {
// If it's anything else, just draw it.
if player.get(*e).is_some() {
DrawType::Player
} else {
DrawType::Visible
}
} else if map.telepath_tiles[idx] {
let has_mind = minds.get(*e);
if has_mind.is_some() {
// Mobs we see through telepathy - generally we just
// draw these, but it uses a unique enum variant so
// it can be treated differently if needed in future.
DrawType::Telepathy
} else {
DrawType::None
}
} else {
// If we don't see it, and we don't sense it with
// telepathy, don't draw it at all.
DrawType::None
};
match draw_type {
DrawType::None => {}
_ => {
to_draw.insert(
DrawKey { x: pos.x, y: pos.y, render_order: render.render_order },
DrawInfo { e: *e, draw_type }
);
}
}
}
}
let mut entries: Vec<(&DrawKey, &DrawInfo)> = to_draw.iter().collect();
entries.sort_by_key(|&(k, _v)| std::cmp::Reverse(k.render_order));
for entry in entries.iter() {
// TODO: ABSTRACT THESE INTO FUNCTIONS ONCE FUNCTIONALITY IS SETTLED ON.
match entry.1.draw_type {
DrawType::Visible | DrawType::Telepathy => {
let renderable = renderables.get(entry.1.e).unwrap();
let id = if let Some(sprite) = atlas.get(&renderable.sprite) {
sprite
} else {
panic!("No entity sprite found for ID: {}", &renderable.sprite);
};
let x_pos = (entry.0.x as f32) * TILESIZE.sprite_x + offset_x;
let y_pos = (entry.0.y as f32) * TILESIZE.sprite_y + offset_y;
let mul = themes::darken_by_distance(
Point::new(entry.0.x, entry.0.y),
*ecs.fetch::<Point>()
);
let col = Color::from_rgb(
renderable.fg.r * mul,
renderable.fg.g * mul,
renderable.fg.b * mul
);
draw.image(id)
.position(
x_pos + renderable.offset.0 * TILESIZE.sprite_x,
y_pos + renderable.offset.1 * TILESIZE.sprite_y
)
.color(col)
.size(TILESIZE.sprite_x, TILESIZE.sprite_y);
if let Some(pool) = pools.get(entry.1.e) {
if pool.hit_points.current < pool.hit_points.max {
draw_entity_hp(x_pos, y_pos, pool, draw);
}
}
}
DrawType::Player => {
let (x_pos, y_pos) = (
(entry.0.x as f32) * TILESIZE.sprite_x + offset_x,
(entry.0.y as f32) * TILESIZE.sprite_y + offset_y,
);
let textures = get_avatar_textures(ecs, atlas);
for (tex, col) in textures.iter() {
draw.image(tex)
.position(x_pos, y_pos)
.color(*col)
.size(TILESIZE.sprite_x, TILESIZE.sprite_y);
}
}
_ => {}
}
}
}
}
fn get_avatar_textures(ecs: &World, atlas: &HashMap<String, Texture>) -> Vec<(Texture, Color)> {
let player = ecs.fetch::<Entity>();
let renderables = ecs.read_storage::<Renderable>();
let equipped = ecs.read_storage::<Equipped>();
let has_avatar = ecs.read_storage::<Avatar>();
let mut avis = Vec::new();
if let Some(renderables) = renderables.get(*player) {
if let Some(sprite) = atlas.get(&renderables.sprite) {
avis.push((
sprite.clone(),
Color::from_rgb(renderables.fg.r, renderables.fg.g, renderables.fg.b),
));
} else {
panic!("No player sprite found for ID: {}", &renderables.sprite);
}
} else {
panic!("No player renderable found!");
}
for (_e, a, r) in (&equipped, &has_avatar, &renderables)
.join()
.filter(|item| item.0.owner == *player) {
if let Some(sprite) = atlas.get(&a.sprite) {
avis.push((sprite.clone(), Color::from_rgb(r.fg.r, r.fg.g, r.fg.b)));
} else {
panic!("No avatar sprite found for ID: {}", &a.sprite);
}
}
avis
}
// Draws a HP bar LINE_WIDTH pixels thick centered above the entity.
fn draw_entity_hp(x: f32, y: f32, hp: &Pools, draw: &mut Draw) {
const LINE_WIDTH: f32 = 3.0;
let y = y + LINE_WIDTH + 1.0;
let x = x;
let fill_pct = (hp.hit_points.current as f32) / (hp.hit_points.max as f32);
draw.line((x + 1.0, y), (x + (TILESIZE.sprite_x - 1.0) * fill_pct, y))
.width(LINE_WIDTH)
.color(Color::GREEN);
}
fn render_map_in_view(
map: &Map,
ecs: &World,
draw: &mut Draw,
atlas: &HashMap<String, Texture>,
mapgen: bool
) {
let bounds = crate::camera::get_screen_bounds(ecs, mapgen);
let mut y = 0;
for tile_y in bounds.min_y..bounds.max_y {
let mut x = 0;
for tile_x in bounds.min_x..bounds.max_x {
if crate::camera::in_bounds(tile_x, tile_y, 0, 0, map.width, map.height) {
let idx = map.xy_idx(tile_x, tile_y);
if map.revealed_tiles[idx] || mapgen {
let draw_x =
(x as f32) * TILESIZE.sprite_x + (bounds.x_offset as f32) * TILESIZE.x;
let draw_y =
(y as f32) * TILESIZE.sprite_y + (bounds.y_offset as f32) * TILESIZE.x;
if ASCII_MODE {
let (glyph, fg, bg) = crate::map::themes::get_tile_renderables_for_id(
idx,
&*map,
Some(*ecs.fetch::<Point>()),
None
);
// TODO: Draw ASCII
} else {
let (id, tint) = crate::map::themes::get_sprite_for_id(
idx,
&*map,
Some(*ecs.fetch::<Point>())
);
let sprite = if let Some(sprite) = atlas.get(id) {
sprite
} else {
panic!("No sprite found for ID: {}", id);
};
draw.image(sprite)
.position(draw_x, draw_y)
.color(tint)
.size(TILESIZE.sprite_x, TILESIZE.sprite_y);
}
if !map.visible_tiles[idx] {
// Recall map memory. TODO: Improve this? Optimize? Do we need to remember more fields?
if let Some(memories) = map.memory.get(&idx) {
let mut sorted: Vec<_> = memories.iter().collect();
sorted.sort_by(|a, b| a.render_order.cmp(&b.render_order));
for memory in sorted.iter() {
let mult = consts::visuals::NON_VISIBLE_MULTIPLIER;
let col = Color::from_rgb(
memory.fg.r * mult,
memory.fg.g * mult,
memory.fg.b * mult
);
let sprite = if let Some(sprite) = atlas.get(&memory.sprite) {
sprite
} else {
panic!("No sprite found for ID: {}", memory.sprite);
};
draw.image(sprite)
.position(
draw_x + memory.offset.0 * TILESIZE.sprite_x,
draw_y + memory.offset.1 * TILESIZE.sprite_y
)
.color(col)
.size(TILESIZE.sprite_x, TILESIZE.sprite_y);
}
}
}
}
} else if SHOW_BOUNDARIES {
// TODO: Draw boundaries
}
x += 1;
}
y += 1;
}
}
struct BoxDraw {
frame: String,
fill: bool,
top_left: (i32, i32),
top_right: (i32, i32),
bottom_left: (i32, i32),
bottom_right: (i32, i32),
}
fn draw_spritebox(panel: BoxDraw, draw: &mut Draw, atlas: &HashMap<String, Texture>) {
draw.image(atlas.get(&format!("{}_1", panel.frame)).unwrap()).position(
(panel.top_left.0 as f32) * TILESIZE.x,
(panel.top_left.1 as f32) * TILESIZE.x
);
for i in panel.top_left.0 + 1..panel.top_right.0 {
draw.image(atlas.get(&format!("{}_2", panel.frame)).unwrap()).position(
(i as f32) * TILESIZE.x,
(panel.top_left.1 as f32) * TILESIZE.x
);
}
draw.image(atlas.get(&format!("{}_3", panel.frame)).unwrap()).position(
(panel.top_right.0 as f32) * TILESIZE.x,
(panel.top_right.1 as f32) * TILESIZE.x
);
for i in panel.top_left.1 + 1..panel.bottom_left.1 {
draw.image(atlas.get(&format!("{}_4", panel.frame)).unwrap()).position(
(panel.top_left.0 as f32) * TILESIZE.x,
(i as f32) * TILESIZE.x
);
}
if panel.fill {
for i in panel.top_left.0 + 1..panel.top_right.0 {
for j in panel.top_left.1 + 1..panel.bottom_left.1 {
draw.image(atlas.get(&format!("{}_5", panel.frame)).unwrap()).position(
(i as f32) * TILESIZE.x,
(j as f32) * TILESIZE.x
);
}
}
}
for i in panel.top_right.1 + 1..panel.bottom_right.1 {
draw.image(atlas.get(&format!("{}_6", panel.frame)).unwrap()).position(
(panel.top_right.0 as f32) * TILESIZE.x,
(i as f32) * TILESIZE.x
);
}
draw.image(atlas.get(&format!("{}_7", panel.frame)).unwrap()).position(
(panel.bottom_left.0 as f32) * TILESIZE.x,
(panel.bottom_left.1 as f32) * TILESIZE.x
);
for i in panel.bottom_left.0 + 1..panel.bottom_right.0 {
draw.image(atlas.get(&format!("{}_8", panel.frame)).unwrap()).position(
(i as f32) * TILESIZE.x,
(panel.bottom_left.1 as f32) * TILESIZE.x
);
}
draw.image(atlas.get(&format!("{}_9", panel.frame)).unwrap()).position(
(panel.bottom_right.0 as f32) * TILESIZE.x,
(panel.bottom_right.1 as f32) * TILESIZE.x
);
}
use crate::consts::visuals::{ VIEWPORT_H, VIEWPORT_W };
fn draw_bg(_ecs: &World, draw: &mut Draw, atlas: &HashMap<String, Texture>) {
let offset = crate::camera::get_offset();
let log = BoxDraw {
frame: "line".to_string(),
fill: false,
top_left: (0, 0),
top_right: (offset.x + VIEWPORT_W, 0),
bottom_left: (0, offset.y - 2),
bottom_right: (offset.x + VIEWPORT_W, offset.y - 2),
};
let game = BoxDraw {
frame: "line".to_string(),
fill: false,
top_left: (offset.x - 1, offset.y - 1),
top_right: (offset.x + VIEWPORT_W, offset.y - 1),
bottom_left: (offset.x - 1, offset.y + VIEWPORT_H),
bottom_right: (offset.x + VIEWPORT_W, offset.y + VIEWPORT_H),
};
let attr = BoxDraw {
frame: "line".to_string(),
fill: false,
top_left: (offset.x - 1, offset.y + VIEWPORT_H + 1),
top_right: (offset.x + VIEWPORT_W, offset.y + VIEWPORT_H + 1),
bottom_left: (offset.x - 1, (DISPLAYHEIGHT as i32) - 1),
bottom_right: (offset.x + VIEWPORT_W, (DISPLAYHEIGHT as i32) - 1),
};
let sidebox = BoxDraw {
frame: "line".to_string(),
fill: false,
top_left: (offset.x + VIEWPORT_W + 1, 0),
top_right: ((DISPLAYWIDTH as i32) - 1, 0),
bottom_left: (offset.x + VIEWPORT_W + 1, (DISPLAYHEIGHT as i32) - 1),
bottom_right: ((DISPLAYWIDTH as i32) - 1, (DISPLAYHEIGHT as i32) - 1),
};
draw_spritebox(log, draw, atlas);
draw_spritebox(game, draw, atlas);
draw_spritebox(attr, draw, atlas);
draw_spritebox(sidebox, draw, atlas);
}
fn draw(_app: &mut App, gfx: &mut Graphics, gs: &mut State) {
let mut draw = gfx.create_draw();
draw.clear(Color::BLACK);
let mut log = false;
let runstate = *gs.ecs.fetch::<RunState>();
match runstate {
RunState::MainMenu { .. } => {
gui::draw_mainmenu(&gs.ecs, &mut draw, &gs.atlas, &gs.font);
}
RunState::CharacterCreation { .. } => {
gui::draw_charcreation(&gs.ecs, &mut draw, &gs.atlas, &gs.font);
}
RunState::PreRun { .. } => {}
RunState::MapGeneration => {
draw_bg(&gs.ecs, &mut draw, &gs.interface);
if config::CONFIG.logging.show_mapgen && gs.mapgen_history.len() > 0 {
render_map_in_view(
&gs.mapgen_history[gs.mapgen_index],
&gs.ecs,
&mut draw,
&gs.atlas,
true
);
}
gui::draw_ui2(&gs.ecs, &mut draw, &gs.atlas, &gs.font);
}
_ => {
let map = gs.ecs.fetch::<Map>();
draw_bg(&gs.ecs, &mut draw, &gs.interface);
render_map_in_view(&*map, &gs.ecs, &mut draw, &gs.atlas, false);
// Special case: targeting needs to be drawn *below* entities, but above tiles.
if let RunState::ShowTargeting { range, item: _, x, y, aoe } = runstate {
gui::draw_targeting(&gs.ecs, &mut draw, &gs.atlas, x, y, range, aoe);
}
draw_entities(&*map, &gs.ecs, &mut draw, &gs.atlas, &gs.font);
gui::draw_ui2(&gs.ecs, &mut draw, &gs.atlas, &gs.font);
log = true;
}
}
match runstate {
RunState::Farlook { x, y } => {
gui::draw_farlook(&gs.ecs, x, y, &mut draw, &gs.atlas);
//draw_tooltips(&gs.ecs, ctx, Some((x, y))); TODO: Put this in draw loop
}
RunState::ShowCheatMenu => {
gui::draw_cheat_menu(&mut draw, &gs.atlas, &gs.font);
}
RunState::ActionWithDirection { .. } => {
corner_text("In what direction? [0-9]/[YUHJKLBN]", &mut draw, &gs.font);
}
RunState::GameOver => {
corner_text("Create morgue file? [Y/N]", &mut draw, &gs.font);
}
RunState::ShowInventory => {
corner_text("Use what? [aA-zZ]/[Esc.]", &mut draw, &gs.font);
let offset = crate::camera::get_offset();
let (x, y) = (
((1 + offset.x) as f32) * TILESIZE.x,
((3 + offset.y) as f32) * TILESIZE.x,
);
gui::draw_backpack_items(&gs.ecs, &mut draw, &gs.font, x, y);
}
RunState::ShowDropItem => {
corner_text("Drop what? [aA-zZ]/[Esc.]", &mut draw, &gs.font);
let offset = crate::camera::get_offset();
let (x, y) = (
((1 + offset.x) as f32) * TILESIZE.x,
((3 + offset.y) as f32) * TILESIZE.x,
);
gui::draw_backpack_items(&gs.ecs, &mut draw, &gs.font, x, y);
}
RunState::ShowRemoveItem => {
corner_text("Unequip which item? [aA-zZ]/[Esc.]", &mut draw, &gs.font);
let offset = crate::camera::get_offset();
let (x, y) = (
((1 + offset.x) as f32) * TILESIZE.x,
((3 + offset.y) as f32) * TILESIZE.x,
);
gui::draw_items(&gs.ecs, &mut draw, &gs.font, x, y, gui::Location::Equipped, None);
}
RunState::ShowTargeting { .. } => {
corner_text("Targeting which tile? [0-9]/[YUHJKLBN]", &mut draw, &gs.font);
}
RunState::HelpScreen => {
corner_text("The help screen is a placeholder! [?]", &mut draw, &gs.font);
}
_ => {}
}
// TODO: Once the rest of drawing is finalised, this should be abstracted
// into some functions that make it easier to tell what is going on. But
// for the short-term:
// 1. notan::Text is required for rich text drawing, rather than just the
// basics that are accessible with notan::Draw's .text() method.
// 2. notan::Text cannot be projected, and rendering both Draw and Text
// requires two GPU calls instead of just one.
// 3. To fix this, our log is drawn to notan::Text, then rendered to a
// render texture, and applied as any other image to notan::Draw.
// 4. notan::Draw is projected, and then rendered, and everything works.
// Further stuff: Make the render texture only as large as is required,
// so text cannot escape the bounds of the logbox.
let (width, height) = gfx.size();
let win_size = vec2(width as f32, height as f32);
let (projection, _) = calc_projection(win_size, WORK_SIZE);
if log {
let buffer = gfx
.create_render_texture(width, height)
.build()
.expect("Failed to create render texture");
gamelog::render_log(
&buffer,
gfx,
&gs.font,
&(TILESIZE.x, TILESIZE.x * 6.0 + 4.0),
(VIEWPORT_W as f32) * TILESIZE.x,
5
);
draw.image(&buffer)
.position(0.0, 0.0)
.size(width as f32, height as f32);
}
draw.set_projection(Some(projection));
gfx.render(&draw);
}
fn update(ctx: &mut App, state: &mut State) {
state.update(ctx);
}
fn corner_text(text: &str, draw: &mut Draw, font: &Fonts) {
let offset = crate::camera::get_offset();
draw.text(&font.b(), &text)
.position(((offset.x + 1) as f32) * TILESIZE.x, ((offset.y + 1) as f32) * TILESIZE.x)
.size(FONTSIZE);
}
fn calc_projection(win_size: Vec2, work_size: Vec2) -> (Mat4, f32) {
let ratio = (win_size.x / work_size.x).min(win_size.y / work_size.y);
let proj = Mat4::orthographic_rh_gl(0.0, win_size.x, win_size.y, 0.0, -1.0, 1.0);
let scale = Mat4::from_scale(vec3(ratio, ratio, 1.0));
let position = vec3(
(win_size.x - work_size.x * ratio) * 0.5,
(win_size.y - work_size.y * ratio) * 0.5,
1.0
);
let trans = Mat4::from_translation(position);
(proj * trans * scale, ratio)
} }

View file

@ -4,7 +4,7 @@ use bracket_lib::prelude::*;
use serde::{ Deserialize, Serialize }; use serde::{ Deserialize, Serialize };
use specs::prelude::*; use specs::prelude::*;
use std::collections::{ HashMap, HashSet }; use std::collections::{ HashMap, HashSet };
use crate::data::events::*; use crate::consts::events::*;
#[derive(Default, Serialize, Deserialize, Clone)] #[derive(Default, Serialize, Deserialize, Clone)]
pub struct MasterDungeonMap { pub struct MasterDungeonMap {
@ -112,26 +112,7 @@ fn make_scroll_name(rng: &mut RandomNumberGenerator) -> String {
return name; return name;
} }
const POTION_COLOURS: &[&str] = &[ const POTION_COLOURS: &[&str] = &["blue", "red", "green", "yellow", "black"];
"red",
"orange",
"yellow",
"green",
"blue",
"indigo",
"violet",
"black",
"white",
"silver",
"gold",
"rainbow",
"blood",
"purple",
"cyan",
"brown",
"grey",
"octarine",
];
const POTION_ADJECTIVES: &[&str] = &[ const POTION_ADJECTIVES: &[&str] = &[
"swirling", "swirling",
"viscous", "viscous",

View file

@ -10,7 +10,7 @@ use crate::{
}; };
use specs::prelude::*; use specs::prelude::*;
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use crate::data::events::*; use crate::consts::events::*;
const TRY_SPAWN_CHANCE: i32 = 70; const TRY_SPAWN_CHANCE: i32 = 70;
const FEATURE_MESSAGE_CHANCE: i32 = 110; const FEATURE_MESSAGE_CHANCE: i32 = 110;

View file

@ -1,23 +1,42 @@
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use specs::prelude::*;
use serde::{ Deserialize, Serialize }; use serde::{ Deserialize, Serialize };
use std::collections::{ HashSet, HashMap }; use std::collections::{ HashSet, HashMap };
mod tiletype; mod tiletype;
pub use tiletype::{ tile_cost, tile_opaque, tile_walkable, TileType, get_dest, Destination }; pub use tiletype::{
tile_cost,
tile_opaque,
tile_walkable,
tile_blocks_telepathy,
TileType,
get_dest,
Destination,
};
mod interval_spawning_system; mod interval_spawning_system;
pub use interval_spawning_system::{ maybe_map_message, try_spawn_interval }; pub use interval_spawning_system::{ maybe_map_message, try_spawn_interval };
pub mod dungeon; pub mod dungeon;
pub use dungeon::{ level_transition, MasterDungeonMap }; pub use dungeon::{ level_transition, MasterDungeonMap };
pub mod themes; pub mod themes;
use super::data::visuals::{ use super::consts::visuals::{
BRIGHTEN_FG_COLOUR_BY, BRIGHTEN_FG_COLOUR_BY,
GLOBAL_OFFSET_MIN_CLAMP, GLOBAL_OFFSET_MIN_CLAMP,
GLOBAL_OFFSET_MAX_CLAMP, GLOBAL_OFFSET_MAX_CLAMP,
SPRITE_OFFSET_MIN_CLAMP,
SPRITE_OFFSET_MAX_CLAMP,
}; };
// FIXME: If the map size gets too small, entities stop being rendered starting from the right. // 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. // 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. // on a map size of 42*42, the player can see entities up to 2 tiles to their right.
#[derive(Clone, Serialize, Deserialize)]
pub struct MapMemory {
pub sprite: String,
pub fg: RGB,
pub offset: (f32, f32),
pub render_order: i32,
}
#[derive(Default, Serialize, Deserialize, Clone)] #[derive(Default, Serialize, Deserialize, Clone)]
pub struct Map { pub struct Map {
pub overmap: bool, pub overmap: bool,
@ -25,6 +44,7 @@ pub struct Map {
pub width: i32, pub width: i32,
pub height: i32, pub height: i32,
pub revealed_tiles: Vec<bool>, pub revealed_tiles: Vec<bool>,
pub memory: HashMap<usize, Vec<MapMemory>>,
pub visible_tiles: Vec<bool>, pub visible_tiles: Vec<bool>,
pub lit_tiles: Vec<bool>, pub lit_tiles: Vec<bool>,
pub telepath_tiles: Vec<bool>, pub telepath_tiles: Vec<bool>,
@ -63,6 +83,7 @@ impl Map {
width: width, width: width,
height: height, height: height,
revealed_tiles: vec![false; map_tile_count], revealed_tiles: vec![false; map_tile_count],
memory: HashMap::new(),
visible_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. 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], telepath_tiles: vec![false; map_tile_count],
@ -91,9 +112,9 @@ impl Map {
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 = ( map.colour_offset[idx].1 = (
rng.range(GLOBAL_OFFSET_MIN_CLAMP, GLOBAL_OFFSET_MAX_CLAMP), rng.range(SPRITE_OFFSET_MIN_CLAMP, SPRITE_OFFSET_MAX_CLAMP),
rng.range(GLOBAL_OFFSET_MIN_CLAMP, GLOBAL_OFFSET_MAX_CLAMP), rng.range(SPRITE_OFFSET_MIN_CLAMP, SPRITE_OFFSET_MAX_CLAMP),
rng.range(GLOBAL_OFFSET_MIN_CLAMP, GLOBAL_OFFSET_MAX_CLAMP), rng.range(SPRITE_OFFSET_MIN_CLAMP, SPRITE_OFFSET_MAX_CLAMP),
); );
} }

View file

@ -1,9 +1,53 @@
use super::{ Map, Point, TileType }; use super::{ Map, Point, TileType };
use crate::data::visuals::*; use crate::consts::visuals::*;
use crate::config::CONFIG; use crate::config::CONFIG;
use crate::data::ids::*; use crate::consts::ids::*;
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use std::ops::{ Add, Mul }; use std::ops::{ Add, Mul };
use notan::prelude::*;
pub fn get_sprite_for_id(idx: usize, map: &Map, other_pos: Option<Point>) -> (&str, Color) {
let bloody = if map.bloodstains.contains_key(&idx) {
Some(map.bloodstains[&idx])
} else {
None
};
let f = map.colour_offset[idx].0.0; // Using offset as a source of random.
let (sprite, offset, mut colour) = match map.tiles[idx] {
TileType::Wall =>
(
map.tiles[idx].sprite(check_if_base(TileType::Wall, idx, map), f, bloody),
map.tiles[idx].offset(),
map.tiles[idx].col(bloody),
),
_ =>
(
map.tiles[idx].sprite(false, f, bloody),
map.tiles[idx].offset(),
map.tiles[idx].col(bloody),
),
};
// Get the right modifier for visibility - darkened by distance from POV, or full dark for out-of-view.
let visibility = if !map.visible_tiles[idx] {
NON_VISIBLE_MULTIPLIER
} else {
if other_pos.is_some() {
darken_by_distance(
Point::new((idx as i32) % map.width, (idx as i32) / map.width),
other_pos.unwrap()
)
} else {
1.0
}
};
// Apply our offsets to our base colour.
colour = apply_colour_offset(colour, map, idx, offset, true);
// Apply our visibility modifier
colour = colour.mul(visibility);
// Convert to a notan colour.
let tint = Color::from_rgb(colour.r, colour.g, colour.b);
return (sprite, tint);
}
/// Gets the renderables for a tile, with darkening/offset/post-processing/etc. Passing a val for "debug" will ignore viewshed. /// 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( pub fn get_tile_renderables_for_id(
@ -136,10 +180,24 @@ fn get_forest_theme_renderables(idx:usize, map: &Map, debug: Option<bool>) -> (F
return (glyph, fg, bg, offsets, bg_offsets); return (glyph, fg, bg, offsets, bg_offsets);
} }
fn is_revealed_and_wall(map: &Map, x: i32, y: i32, debug: Option<bool>) -> bool { fn is_revealed_and(tt: TileType, map: &Map, x: i32, y: i32, debug: Option<bool>) -> bool {
let idx = map.xy_idx(x, y); let idx = map.xy_idx(x, y);
map.tiles[idx] == TileType::Wall && map.tiles[idx] == tt && (if debug.is_none() { map.revealed_tiles[idx] } else { true })
(if debug.is_none() { map.revealed_tiles[idx] } else { true }) }
fn check_if_base(tt: TileType, idx: usize, map: &Map) -> bool {
let x = (idx as i32) % map.width;
let y = (idx as i32) / map.width;
// If we're on the edge, it can only be a base sprite.
if y > map.height - 2 {
return true;
}
// If the tile below is a revealed wall, we're not the base.
if is_revealed_and(tt, map, x, y + 1, None) {
return false;
}
// If the tile below isn't a revealed wall, we're the base.
return true;
} }
fn wall_glyph(map: &Map, x: i32, y: i32, debug: Option<bool>) -> FontCharType { fn wall_glyph(map: &Map, x: i32, y: i32, debug: Option<bool>) -> FontCharType {
@ -156,37 +214,37 @@ fn wall_glyph(map: &Map, x: i32, y: i32, debug: Option<bool>) -> FontCharType {
let mut mask: u8 = 0; let mut mask: u8 = 0;
let diagonals_matter: Vec<u8> = vec![7, 11, 13, 14, 15]; let diagonals_matter: Vec<u8> = vec![7, 11, 13, 14, 15];
if is_revealed_and_wall(map, x, y - 1, debug) { if is_revealed_and(TileType::Wall, map, x, y - 1, debug) {
// N // N
mask += 1; mask += 1;
} }
if is_revealed_and_wall(map, x, y + 1, debug) { if is_revealed_and(TileType::Wall, map, x, y + 1, debug) {
// S // S
mask += 2; mask += 2;
} }
if is_revealed_and_wall(map, x - 1, y, debug) { if is_revealed_and(TileType::Wall, map, x - 1, y, debug) {
// W // W
mask += 4; mask += 4;
} }
if is_revealed_and_wall(map, x + 1, y, debug) { if is_revealed_and(TileType::Wall, map, x + 1, y, debug) {
// E // E
mask += 8; mask += 8;
} }
if diagonals_matter.contains(&mask) { if diagonals_matter.contains(&mask) {
if is_revealed_and_wall(map, x + 1, y - 1, debug) { if is_revealed_and(TileType::Wall, map, x + 1, y - 1, debug) {
// Top right // Top right
mask += 16; mask += 16;
} }
if is_revealed_and_wall(map, x - 1, y - 1, debug) { if is_revealed_and(TileType::Wall, map, x - 1, y - 1, debug) {
// Top left // Top left
mask += 32; mask += 32;
} }
if is_revealed_and_wall(map, x + 1, y + 1, debug) { if is_revealed_and(TileType::Wall, map, x + 1, y + 1, debug) {
// Bottom right // Bottom right
mask += 64; mask += 64;
} }
if is_revealed_and_wall(map, x - 1, y + 1, debug) { if is_revealed_and(TileType::Wall, map, x - 1, y + 1, debug) {
// Bottom left // Bottom left
mask += 128; mask += 128;
} }
@ -326,11 +384,11 @@ pub fn multiply_by_float(rgb: RGB, offsets: (f32, f32, f32)) -> RGB {
return RGB::from_f32(r, g, b); return RGB::from_f32(r, g, b);
} }
fn darken_by_distance(pos: Point, other_pos: Point) -> f32 { pub 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 distance = DistanceAlg::Pythagoras.distance2d(pos, other_pos) as f32; // Get distance in tiles.
let interp_factor = let interp_factor =
(distance - START_DARKEN_AT_N_TILES) / (distance - START_DARKEN_AT_N_TILES) /
((crate::data::entity::DEFAULT_VIEWSHED_STANDARD as f32) - START_DARKEN_AT_N_TILES); ((crate::consts::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 interp_factor = interp_factor.max(0.0).min(1.0); // Clamp [0-1]
let result = let result =
1.0 - 1.0 -

View file

@ -1,4 +1,6 @@
use serde::{ Deserialize, Serialize }; use serde::{ Deserialize, Serialize };
use bracket_lib::prelude::*;
use crate::consts::visuals::*;
#[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize, Debug)] #[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize, Debug)]
pub enum TileType { pub enum TileType {
@ -27,9 +29,117 @@ pub enum TileType {
ToOvermap(i32), ToOvermap(i32),
ToLocal(i32), ToLocal(i32),
} }
impl TileType {
pub fn sprite(&self, base: bool, float: f32, bloody: Option<RGB>) -> &str {
if base {
return self.h(float, bloody);
}
return self.v(float, bloody);
}
fn h(&self, float: f32, _bloody: Option<RGB>) -> &str {
let options = match self {
TileType::Wall => vec!["wall_b", "wall_b_cracked"],
_ => unreachable!("Tried to get a h (base) sprite for a non-wall tile."),
};
return options[(float * (options.len() as f32)) as usize];
}
fn v(&self, float: f32, bloody: Option<RGB>) -> &str {
let mut options = match self {
TileType::ImpassableMountain => vec!["wall_b"],
TileType::Wall => vec!["wall"],
TileType::DeepWater => vec!["water", "water2"],
TileType::Fence => vec!["tiles4"],
TileType::Bars => vec!["wall_b"],
TileType::Floor => vec!["dot"],
TileType::WoodFloor => vec!["planks", "planks_missing", "planks_missing2"],
TileType::Gravel => vec!["fluff", "fluff2"],
TileType::Road =>
vec![
"tiles",
"tiles_missing",
"tiles_missing2",
"tiles_missing3",
"tiles_missing4",
"tiles_missing5",
"tiles_missing6"
],
TileType::Grass => vec!["fluff", "fluff2"],
TileType::Foliage => vec!["grass_small", "grass"],
TileType::HeavyFoliage => vec!["grass_flower"],
TileType::Sand => vec!["fluff", "fluff2"],
TileType::ShallowWater => vec!["water", "water2"],
TileType::Bridge => vec!["planks"],
TileType::DownStair => vec!["stair_down"],
TileType::UpStair => vec!["stair_up"],
TileType::ToLocal(_) => vec!["stair_down"],
TileType::ToOvermap(_) => vec!["stair_up"],
};
if bloody.is_some() && tile_walkable(*self) {
options.extend(
vec!["blood1", "blood2", "blood3", "blood4", "blood5", "blood6", "blood7"]
);
}
return options[(float * (options.len() as f32)) as usize];
}
pub fn offset(&self) -> (i32, i32, i32) {
match self {
TileType::ImpassableMountain => IMPASSABLE_MOUNTAIN_OFFSETS,
TileType::Wall => WALL_OFFSETS,
TileType::DeepWater => DEEP_WATER_OFFSETS,
TileType::Fence => FENCE_OFFSETS,
TileType::Bars => BARS_OFFSETS,
TileType::Floor => FLOOR_OFFSETS,
TileType::WoodFloor => WOOD_FLOOR_OFFSETS,
TileType::Gravel => GRAVEL_OFFSETS,
TileType::Road => ROAD_OFFSETS,
TileType::Grass => GRASS_OFFSETS,
TileType::Foliage => FOLIAGE_OFFSETS,
TileType::HeavyFoliage => HEAVY_FOLIAGE_OFFSETS,
TileType::Sand => SAND_OFFSETS,
TileType::ShallowWater => SHALLOW_WATER_OFFSETS,
TileType::Bridge => BRIDGE_OFFSETS,
TileType::DownStair => STAIR_OFFSETS,
TileType::UpStair => STAIR_OFFSETS,
TileType::ToLocal(_) => WALL_OFFSETS,
TileType::ToOvermap(_) => WALL_OFFSETS,
}
}
pub fn col(&self, bloody: Option<RGB>) -> RGB {
if let Some(bloody) = bloody {
return bloody;
}
RGB::named(match self {
TileType::ImpassableMountain => IMPASSABLE_MOUNTAIN_COLOUR,
TileType::Wall => WALL_COLOUR,
TileType::DeepWater => DEEP_WATER_COLOUR,
TileType::Fence => FENCE_COLOUR,
TileType::Bars => BARS_COLOUR,
TileType::Floor => FLOOR_COLOUR,
TileType::WoodFloor => WOOD_FLOOR_COLOUR,
TileType::Gravel => GRAVEL_COLOUR,
TileType::Road => ROAD_COLOUR,
TileType::Grass => GRASS_COLOUR,
TileType::Foliage => FOLIAGE_COLOUR,
TileType::HeavyFoliage => HEAVY_FOLIAGE_COLOUR,
TileType::Sand => SAND_COLOUR,
TileType::ShallowWater => SHALLOW_WATER_COLOUR,
TileType::Bridge => BRIDGE_COLOUR,
TileType::DownStair => STAIR_COLOUR,
TileType::UpStair => STAIR_COLOUR,
TileType::ToLocal(_) => WALL_COLOUR,
TileType::ToOvermap(_) => WALL_COLOUR,
})
}
}
pub fn tile_walkable(tt: TileType) -> bool { pub fn tile_walkable(tt: TileType) -> bool {
match tt { match tt {
TileType::ImpassableMountain | TileType::Wall | TileType::DeepWater | TileType::Fence | TileType::Bars => false, | TileType::ImpassableMountain
| TileType::Wall
| TileType::DeepWater
| TileType::Fence
| TileType::Bars => false,
_ => true, _ => true,
} }
} }
@ -40,6 +150,11 @@ pub fn tile_opaque(tt: TileType) -> bool {
_ => false, _ => false,
} }
} }
pub fn tile_blocks_telepathy(tt: TileType) -> bool {
match tt {
_ => false,
}
}
pub fn tile_cost(tt: TileType) -> f32 { pub fn tile_cost(tt: TileType) -> f32 {
match tt { match tt {
TileType::Road => 0.75, TileType::Road => 0.75,

View file

@ -12,7 +12,7 @@ use super::{
Foliage, Foliage,
}; };
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use crate::data::names::*; use crate::consts::names::*;
pub fn forest_builder( pub fn forest_builder(
new_id: i32, new_id: i32,

View file

@ -40,8 +40,8 @@ use common::*;
use specs::prelude::*; use specs::prelude::*;
use voronoi_spawning::VoronoiSpawning; use voronoi_spawning::VoronoiSpawning;
use super::config::CONFIG; use super::config::CONFIG;
use super::data::ids::*; use super::consts::ids::*;
use super::data::names::*; use super::consts::names::*;
//use wfc::WaveFunctionCollapseBuilder; //use wfc::WaveFunctionCollapseBuilder;
mod room_exploder; mod room_exploder;
use room_exploder::RoomExploder; use room_exploder::RoomExploder;
@ -215,12 +215,12 @@ fn random_start_position(rng: &mut RandomNumberGenerator) -> (XStart, YStart) {
} }
fn random_room_builder(rng: &mut RandomNumberGenerator, builder: &mut BuilderChain, end: bool) { fn random_room_builder(rng: &mut RandomNumberGenerator, builder: &mut BuilderChain, end: bool) {
let build_roll = rng.roll_dice(1, 3); let build_roll = rng.roll_dice(1, 2); // TODO: BSP Interiors, change this to 1d3 and uncomment.
// Start with a room builder. // Start with a room builder.
match build_roll { match build_roll {
1 => builder.start_with(SimpleMapBuilder::new(None)), 1 => builder.start_with(SimpleMapBuilder::new(None)),
2 => builder.start_with(BspDungeonBuilder::new()), _ => builder.start_with(BspDungeonBuilder::new()),
_ => builder.start_with(BspInteriorBuilder::new()), //_ => builder.start_with(BspInteriorBuilder::new()),
} }
// BspInterior makes its own doorways. If we're not using that one, // BspInterior makes its own doorways. If we're not using that one,
@ -301,7 +301,7 @@ fn random_shape_builder(
end: bool end: bool
) -> bool { ) -> bool {
// Pick an initial builder // Pick an initial builder
let builder_roll = rng.roll_dice(1, 16); let builder_roll = rng.roll_dice(1, 13);
let mut want_doors = true; let mut want_doors = true;
match builder_roll { match builder_roll {
1 => builder.start_with(CellularAutomataBuilder::new()), 1 => builder.start_with(CellularAutomataBuilder::new()),
@ -319,11 +319,7 @@ fn random_shape_builder(
10 => builder.start_with(DLABuilder::central_attractor()), 10 => builder.start_with(DLABuilder::central_attractor()),
11 => builder.start_with(DLABuilder::insectoid()), 11 => builder.start_with(DLABuilder::insectoid()),
12 => builder.start_with(VoronoiBuilder::pythagoras()), 12 => builder.start_with(VoronoiBuilder::pythagoras()),
13 => builder.start_with(VoronoiBuilder::manhattan()), _ => 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. // 'Select' the centre by placing a starting position, and cull everywhere unreachable.
@ -439,6 +435,8 @@ pub fn random_builder(
builder builder
} }
use crate::consts::prelude::*;
pub fn level_builder( pub fn level_builder(
id: i32, id: i32,
rng: &mut RandomNumberGenerator, rng: &mut RandomNumberGenerator,
@ -447,15 +445,15 @@ pub fn level_builder(
initial_player_level: i32 initial_player_level: i32
) -> BuilderChain { ) -> BuilderChain {
match id { match id {
ID_OVERMAP => room_accretion(), ID_OVERMAP => overmap_builder(),
ID_TOWN => town_builder(id, rng, width, height, 0, initial_player_level), ID_TOWN => town_builder(id, rng, width, height, 0, initial_player_level),
ID_TOWN2 => forest_builder(id, rng, width, height, 1, initial_player_level), ID_TOWN2 => forest_builder(id, rng, width, height, 1, initial_player_level),
ID_TOWN3 => ID_TOWN3 =>
random_builder( random_builder(
id, id,
rng, rng,
width, VIEWPORT_W,
height, VIEWPORT_H,
2, 2,
1, 1,
initial_player_level, initial_player_level,
@ -466,8 +464,8 @@ pub fn level_builder(
random_builder( random_builder(
id, id,
rng, rng,
width, VIEWPORT_W,
height, VIEWPORT_H,
4 + diff(ID_INFINITE, id), 4 + diff(ID_INFINITE, id),
1 + diff(ID_INFINITE, id), 1 + diff(ID_INFINITE, id),
initial_player_level, initial_player_level,
@ -478,8 +476,8 @@ pub fn level_builder(
random_builder( random_builder(
id, id,
rng, rng,
width, VIEWPORT_W,
height, VIEWPORT_H,
1, 1,
404, 404,
initial_player_level, initial_player_level,

View file

@ -4,7 +4,7 @@ pub mod prefab_levels;
pub mod prefab_sections; pub mod prefab_sections;
pub mod prefab_vaults; pub mod prefab_vaults;
use std::collections::HashSet; use std::collections::HashSet;
use crate::data::ids::*; use crate::consts::ids::*;
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone)]
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -5,61 +5,12 @@ pub struct PrefabLevel {
pub height: usize, pub height: usize,
} }
#[allow(dead_code)]
pub const WFC_POPULATED: PrefabLevel = PrefabLevel { template: LEVEL_MAP, width: 80, height: 43 };
pub const OVERMAP: PrefabLevel = PrefabLevel { template: OVERMAP_TEMPLATE, width: 69, height: 41 }; pub const OVERMAP: PrefabLevel = PrefabLevel { template: OVERMAP_TEMPLATE, width: 69, height: 41 };
#[allow(dead_code)]
const LEVEL_MAP: &str =
"
################################################################################
#          ########################################################    #########
#    @     ######    #########       ####     ###################        #######
#          ####   g  #                          ###############            #####
#          #### #    # #######       ####       #############                ###
##### ######### #    # #######       #########  ####    #####                ###
##### ######### ###### #######   o   #########  #### ## #####                ###
##                        ####       #########   ### ##         o            ###
##### ######### ###       ####       #######         ## #####                ###
##### ######### ###       ####       ####### #   ### ## #####                ###
##### ######### ###       ####       ####### #######    #####     o          ###
###          ## ###       ####       ####### ################                ###
###          ## ###   o   ###### ########### #   ############                ###
###          ## ###       ###### ###########     ###                         ###
###    %                  ###### ########### #   ###   !   ##                ###
###          ## ###              ######   ## #######       ##                ###
###          ## ###       ## ### #####     # ########################      #####
###          ## ###       ## ### #####     # #   ######################    #####
#### ## ####### ###### ##### ### ####          o ###########     ######    #####
#### ## ####### ###### ####   ## ####        #   #########         ###### ######
#    ## ####### ###### ####   ## ####        ############           ##### ######
# g  ## ####### ###### ####   ##        %    ###########   o      o  #### #    #
#    ## ###            ####   ## ####        #   #######   ##    ##  ####   g  #
#######                  ####### ####            ######     !    !    ### #    #
######                     ##### ####        #   ######               ### ######
#####                            #####     # ##########               ### ######
#####           !           ### ######     # ##########      o##o     ### #   ##
#####                       ### #######   ## #   ######               ###   g ##
#   ##                     #### ######## ###   o #######  ^########^ #### #   ##
# g    #                 ###### ######## #####   #######  ^        ^ #### ######
#   ##g####           ######    ######## ################           ##### ######
#   ## ########## ##########    ######## #################         ######      #
#####   ######### ########## %  ######## ###################     ######## ##   #
#### ### ######## ##########    ######## #################### ##########   #   #
### ##### ######   #########    ########          ########### #######   # g#   #
### #####           ###############      ###      ########### #######   ####   #
### ##### ####       ############## ######## g  g ########### ####         # ^ #
#### ###^####         ############# ########      #####       ####      # g#   #
#####   ######       ###            ########      ##### g     ####   !  ####^^ #
#!%^## ###  ##           ########## ########  gg                 g         # > #
#!%^   ###  ###     ############### ########      ##### g     ####      # g#   #
# %^##  ^   ###     ############### ########      #####       ##################
################################################################################";
const OVERMAP_TEMPLATE: &str = const OVERMAP_TEMPLATE: &str =
" "
^^^^^^^^^^^^^^^^^^^^^^^^^^^........ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^........... ^^^^^^^^^^^^^^^^^^^^^^^^^^........
^^^^^^^^^^^^^^^....^^^^^^............. ^^^^^^^^^^^^^^^....^^^^^^.............
^^^^^^^^^^^^^^................................. ^^^^^^^^^^^^^^.................................
^^^^^^^^^^^^^..................................... ^^^^^^^^^^^^^.....................................

View file

@ -1,6 +1,6 @@
use super::{ BuilderMap, MetaMapBuilder, Rect, TileType }; use super::{ BuilderMap, MetaMapBuilder, Rect, TileType };
use crate::tile_walkable; use crate::tile_walkable;
use crate::data::messages::{ use crate::consts::messages::{
FEATURE_TREANTS, FEATURE_TREANTS,
FEATURE_BARRACKS_GOBLIN, FEATURE_BARRACKS_GOBLIN,
FEATURE_BARRACKS_KOBOLD, FEATURE_BARRACKS_KOBOLD,

View file

@ -1,6 +1,6 @@
use super::{ BuilderChain, BuilderMap, InitialMapBuilder, Position, TileType, FillEdges }; use super::{ BuilderChain, BuilderMap, InitialMapBuilder, Position, TileType, FillEdges };
use std::collections::HashSet; use std::collections::HashSet;
use crate::data::names::*; use crate::consts::names::*;
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
pub fn town_builder( pub fn town_builder(
@ -271,6 +271,7 @@ impl TownBuilder {
building.1 + building.3 / 2 building.1 + building.3 / 2
); );
build_data.map.tiles[exit_idx] = TileType::DownStair; build_data.map.tiles[exit_idx] = TileType::DownStair;
build_data.spawn_list.push((exit_idx, "trapdoor".to_string()));
let mut to_place: Vec<&str> = vec!["npc_miner", "npc_miner", "npc_guard", "prop_chair"]; let mut to_place: Vec<&str> = vec!["npc_miner", "npc_miner", "npc_guard", "prop_chair"];
self.random_building_spawn(building, build_data, rng, &mut to_place, exit_idx) self.random_building_spawn(building, build_data, rng, &mut to_place, exit_idx)
} }

View file

@ -149,7 +149,7 @@ impl<'a> System<'a> for MeleeCombatSystem {
let attack_verb = attack.1; let attack_verb = attack.1;
// Get all offensive bonuses // Get all offensive bonuses
let d20 = rng.roll_dice(1, 20); let d20 = rng.roll_dice(1, 20);
let attribute_hit_bonus = attacker_attributes.dexterity.bonus; let attribute_hit_bonus = attacker_attributes.dexterity.modifier();
let skill_hit_bonus = gamesystem::skill_bonus(Skill::Melee, &*attacker_skills); let skill_hit_bonus = gamesystem::skill_bonus(Skill::Melee, &*attacker_skills);
let mut equipment_hit_bonus = weapon_info.hit_bonus; let mut equipment_hit_bonus = weapon_info.hit_bonus;
for (wielded, to_hit) in (&equipped, &to_hit).join() { for (wielded, to_hit) in (&equipped, &to_hit).join() {
@ -187,7 +187,7 @@ impl<'a> System<'a> for MeleeCombatSystem {
// Get armour class // Get armour class
let bac = target_pools.bac; let bac = target_pools.bac;
let attribute_ac_bonus = target_attributes.dexterity.bonus / 2; let attribute_ac_bonus = target_attributes.dexterity.modifier() / 2;
let skill_ac_bonus = gamesystem::skill_bonus(Skill::Defence, &*target_skills); let skill_ac_bonus = gamesystem::skill_bonus(Skill::Defence, &*target_skills);
let mut armour_ac_bonus = 0; let mut armour_ac_bonus = 0;
for (wielded, ac) in (&equipped, &ac).join() { for (wielded, ac) in (&equipped, &ac).join() {
@ -245,19 +245,19 @@ impl<'a> System<'a> for MeleeCombatSystem {
let mut attribute_damage_bonus = weapon_info.damage_bonus; let mut attribute_damage_bonus = weapon_info.damage_bonus;
match weapon_info.attribute { match weapon_info.attribute {
WeaponAttribute::Dexterity => { WeaponAttribute::Dexterity => {
attribute_damage_bonus += attacker_attributes.dexterity.bonus; attribute_damage_bonus += attacker_attributes.dexterity.modifier();
} }
WeaponAttribute::Strength => { WeaponAttribute::Strength => {
attribute_damage_bonus += attacker_attributes.strength.bonus; attribute_damage_bonus += attacker_attributes.strength.modifier();
} }
WeaponAttribute::Finesse => { WeaponAttribute::Finesse => {
if if
attacker_attributes.dexterity.bonus > attacker_attributes.dexterity.modifier() >
attacker_attributes.strength.bonus attacker_attributes.strength.modifier()
{ {
attribute_damage_bonus += attacker_attributes.dexterity.bonus; attribute_damage_bonus += attacker_attributes.dexterity.modifier();
} else { } else {
attribute_damage_bonus += attacker_attributes.strength.bonus; attribute_damage_bonus += attacker_attributes.strength.modifier();
} }
} }
} }
@ -295,11 +295,6 @@ impl<'a> System<'a> for MeleeCombatSystem {
); );
} }
} }
let pos = positions.get(wants_melee.target);
if let Some(pos) = pos {
particle_builder.damage_taken(pos.x, pos.y);
}
add_effect( add_effect(
Some(entity), Some(entity),
EffectType::Damage { amount: damage, damage_type: weapon_info.damage_type }, EffectType::Damage { amount: damage, damage_type: weapon_info.damage_type },

View file

@ -9,7 +9,7 @@ use specs::prelude::*;
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use to_char; use to_char;
use std::collections::HashMap; use std::collections::HashMap;
use crate::data::events::*; use crate::consts::events::*;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub fn create_morgue_file(ecs: &World) { pub fn create_morgue_file(ecs: &World) {
@ -36,6 +36,7 @@ fn create_file_name(ecs: &World, morgue_dir: &str) -> String {
let pools = ecs.read_storage::<Pools>(); let pools = ecs.read_storage::<Pools>();
let pool = pools.get(*e).unwrap(); let pool = pools.get(*e).unwrap();
let class = match ecs.read_storage::<HasClass>().get(*e).unwrap().name { let class = match ecs.read_storage::<HasClass>().get(*e).unwrap().name {
Class::Unset => "classless",
Class::Fighter => "fighter", Class::Fighter => "fighter",
Class::Wizard => "wizard", Class::Wizard => "wizard",
Class::Rogue => "rogue", Class::Rogue => "rogue",
@ -47,7 +48,7 @@ fn create_file_name(ecs: &World, morgue_dir: &str) -> String {
Ancestry::Dwarf => "dwarf", Ancestry::Dwarf => "dwarf",
Ancestry::Gnome => "gnome", Ancestry::Gnome => "gnome",
Ancestry::Catfolk => "catfolk", Ancestry::Catfolk => "catfolk",
Ancestry::NULL => "NULL", Ancestry::Unset => "NULL",
}; };
return format!( return format!(
"{}/lv{}-{}-{}-{}.txt", "{}/lv{}-{}-{}-{}.txt",
@ -64,6 +65,7 @@ fn create_morgue_string(ecs: &World) -> String {
let mut morgue_info: String = Default::default(); let mut morgue_info: String = Default::default();
let e = ecs.fetch::<Entity>(); let e = ecs.fetch::<Entity>();
let class = match ecs.read_storage::<HasClass>().get(*e).unwrap().name { let class = match ecs.read_storage::<HasClass>().get(*e).unwrap().name {
Class::Unset => "classless",
Class::Fighter => "fighter", Class::Fighter => "fighter",
Class::Wizard => "wizard", Class::Wizard => "wizard",
Class::Rogue => "rogue", Class::Rogue => "rogue",
@ -75,7 +77,7 @@ fn create_morgue_string(ecs: &World) -> String {
Ancestry::Dwarf => "dwarf", Ancestry::Dwarf => "dwarf",
Ancestry::Gnome => "gnome", Ancestry::Gnome => "gnome",
Ancestry::Catfolk => "catfolk", Ancestry::Catfolk => "catfolk",
Ancestry::NULL => "NULL", Ancestry::Unset => "NULL",
}; };
let pools = ecs.read_storage::<Pools>(); let pools = ecs.read_storage::<Pools>();
let pool = pools.get(*e).unwrap(); let pool = pools.get(*e).unwrap();
@ -127,19 +129,19 @@ fn draw_tombstone(ecs: &World, len: usize) -> String {
pool.mana.max, pool.mana.max,
"", "",
"", "",
attr.strength.base + attr.strength.modifiers, attr.strength.base + attr.strength.bonuses,
attr.strength.bonus, attr.strength.modifier(),
attr.constitution.base + attr.constitution.modifiers, attr.constitution.base + attr.constitution.bonuses,
attr.constitution.bonus, attr.constitution.modifier(),
attr.wisdom.base + attr.wisdom.modifiers, attr.wisdom.base + attr.wisdom.bonuses,
attr.wisdom.bonus, attr.wisdom.modifier(),
"", "",
attr.dexterity.base + attr.dexterity.modifiers, attr.dexterity.base + attr.dexterity.bonuses,
attr.dexterity.bonus, attr.dexterity.modifier(),
attr.intelligence.base + attr.intelligence.modifiers, attr.intelligence.base + attr.intelligence.bonuses,
attr.intelligence.bonus, attr.intelligence.modifier(),
attr.charisma.base + attr.charisma.modifiers, attr.charisma.base + attr.charisma.bonuses,
attr.charisma.bonus, attr.charisma.modifier(),
"", "",
"", "",
map.name, map.name,
@ -162,10 +164,10 @@ fn draw_map(ecs: &World) -> String {
if idx == map.xy_idx(point.x, point.y) { if idx == map.xy_idx(point.x, point.y) {
glyph_u16 = to_cp437('@'); glyph_u16 = to_cp437('@');
} else if crate::spatial::has_tile_content(idx) { } else if crate::spatial::has_tile_content(idx) {
let mut render_order = 0; let mut render_order = 4;
crate::spatial::for_each_tile_content(idx, |e| { crate::spatial::for_each_tile_content(idx, |e| {
if let Some(renderable) = ecs.read_storage::<Renderable>().get(e) { if let Some(renderable) = ecs.read_storage::<Renderable>().get(e) {
if renderable.render_order >= render_order { if renderable.render_order <= render_order {
render_order = renderable.render_order; render_order = renderable.render_order;
glyph_u16 = renderable.glyph; glyph_u16 = renderable.glyph;
} }

View file

@ -1,26 +1,27 @@
use super::{ ParticleLifetime, Position, Renderable, BTerm }; use super::{ ParticleLifetime, Position, Renderable, BTerm };
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use notan::prelude::*;
use specs::prelude::*; use specs::prelude::*;
use crate::data::visuals::{ DEFAULT_PARTICLE_LIFETIME, SHORT_PARTICLE_LIFETIME }; use crate::consts::visuals::{ DEFAULT_PARTICLE_LIFETIME, SHORT_PARTICLE_LIFETIME };
/// Runs each tick, deleting particles who are past their expiry. /// Runs each tick, deleting particles who are past their expiry.
// Should make an addition to this to also spawn delayed particles, // Should make an addition to this to also spawn delayed particles,
// running through a list and removing the frame_time_ms from the // running through a list and removing the frame_time_ms from the
// delay. When delay is <= 0, make a particle_builder.request for // delay. When delay is <= 0, make a particle_builder.request for
// the particle. // the particle.
pub fn particle_ticker(ecs: &mut World, ctx: &BTerm) { pub fn particle_ticker(ecs: &mut World, ctx: &App) {
cull_dead_particles(ecs, ctx); cull_dead_particles(ecs, ctx);
create_delayed_particles(ecs, ctx); create_delayed_particles(ecs, ctx);
} }
fn cull_dead_particles(ecs: &mut World, ctx: &BTerm) { fn cull_dead_particles(ecs: &mut World, ctx: &App) {
let mut dead_particles: Vec<Entity> = Vec::new(); let mut dead_particles: Vec<Entity> = Vec::new();
{ {
// Age out particles // Age out particles
let mut particles = ecs.write_storage::<ParticleLifetime>(); let mut particles = ecs.write_storage::<ParticleLifetime>();
let entities = ecs.entities(); let entities = ecs.entities();
for (entity, mut particle) in (&entities, &mut particles).join() { for (entity, mut particle) in (&entities, &mut particles).join() {
particle.lifetime_ms -= ctx.frame_time_ms; particle.lifetime_ms -= ctx.timer.delta_f32() * 1000.0;
if particle.lifetime_ms < 0.0 { if particle.lifetime_ms < 0.0 {
dead_particles.push(entity); dead_particles.push(entity);
} }
@ -39,18 +40,18 @@ pub fn check_queue(ecs: &World) -> bool {
return false; return false;
} }
fn create_delayed_particles(ecs: &mut World, ctx: &BTerm) { fn create_delayed_particles(ecs: &mut World, ctx: &App) {
let mut particle_builder = ecs.write_resource::<ParticleBuilder>(); let mut particle_builder = ecs.write_resource::<ParticleBuilder>();
let mut handled_particles: Vec<ParticleRequest> = Vec::new(); let mut handled_particles: Vec<ParticleRequest> = Vec::new();
for delayed_particle in particle_builder.delayed_requests.iter_mut() { for delayed_particle in particle_builder.delayed_requests.iter_mut() {
delayed_particle.delay -= ctx.frame_time_ms; delayed_particle.delay -= ctx.timer.delta_f32() * 1000.0;
if delayed_particle.delay < 0.0 { if delayed_particle.delay < 0.0 {
handled_particles.push(ParticleRequest { handled_particles.push(ParticleRequest {
x: delayed_particle.particle.x, x: delayed_particle.particle.x,
y: delayed_particle.particle.y, y: delayed_particle.particle.y,
fg: delayed_particle.particle.fg, fg: delayed_particle.particle.fg,
bg: delayed_particle.particle.bg,
glyph: delayed_particle.particle.glyph, glyph: delayed_particle.particle.glyph,
sprite: delayed_particle.particle.sprite.clone(),
lifetime: delayed_particle.particle.lifetime, lifetime: delayed_particle.particle.lifetime,
}); });
} }
@ -80,12 +81,7 @@ fn create_delayed_particles(ecs: &mut World, ctx: &BTerm) {
.insert(p, Position { x: handled.x, y: handled.y }) .insert(p, Position { x: handled.x, y: handled.y })
.expect("Could not insert position"); .expect("Could not insert position");
renderables renderables
.insert(p, Renderable { .insert(p, Renderable::new(handled.glyph, handled.sprite, handled.fg, 0))
fg: handled.fg,
bg: handled.bg,
glyph: handled.glyph,
render_order: 0,
})
.expect("Could not insert renderables"); .expect("Could not insert renderables");
particles particles
.insert(p, ParticleLifetime { lifetime_ms: handled.lifetime }) .insert(p, ParticleLifetime { lifetime_ms: handled.lifetime })
@ -98,8 +94,8 @@ pub struct ParticleRequest {
x: i32, x: i32,
y: i32, y: i32,
fg: RGB, fg: RGB,
bg: RGB,
glyph: FontCharType, glyph: FontCharType,
sprite: String,
lifetime: f32, lifetime: f32,
} }
@ -126,11 +122,11 @@ impl ParticleBuilder {
x: i32, x: i32,
y: i32, y: i32,
fg: RGB, fg: RGB,
bg: RGB,
glyph: FontCharType, glyph: FontCharType,
sprite: String,
lifetime: f32 lifetime: f32
) { ) {
self.requests.push(ParticleRequest { x, y, fg, bg, glyph, lifetime }); self.requests.push(ParticleRequest { x, y, fg, glyph, sprite, lifetime });
} }
pub fn delay( pub fn delay(
@ -138,150 +134,43 @@ impl ParticleBuilder {
x: i32, x: i32,
y: i32, y: i32,
fg: RGB, fg: RGB,
bg: RGB,
glyph: FontCharType, glyph: FontCharType,
sprite: String,
lifetime: f32, lifetime: f32,
delay: f32 delay: f32
) { ) {
self.delayed_requests.push(DelayedParticleRequest { self.delayed_requests.push(DelayedParticleRequest {
delay: delay, delay: delay,
particle: ParticleRequest { x, y, fg, bg, glyph, lifetime }, particle: ParticleRequest { x, y, fg, glyph, sprite, lifetime },
}); });
} }
// MASSIVE TODO: Animate these, or make them random. PLACEHOLDER.
pub fn damage_taken(&mut self, x: i32, y: i32) { pub fn damage_taken(&mut self, x: i32, y: i32) {
self.request( self.request(x, y, RGB::named(RED), to_cp437('‼'), "slash1".to_string(), 75.0);
x, self.delay(x, y, RGB::named(RED), to_cp437('‼'), "slash2".to_string(), 75.0, 75.0);
y, self.delay(x, y, RGB::named(RED), to_cp437('‼'), "slash3".to_string(), 75.0, 150.0);
RGB::named(ORANGE),
RGB::named(BLACK),
to_cp437('‼'),
DEFAULT_PARTICLE_LIFETIME
);
} }
pub fn attack_miss(&mut self, x: i32, y: i32) { pub fn attack_miss(&mut self, x: i32, y: i32) {
self.request( self.request(
x, x,
y, y,
RGB::named(CYAN), RGB::named(CYAN),
RGB::named(BLACK),
to_cp437('‼'), to_cp437('‼'),
"slash1".to_string(),
DEFAULT_PARTICLE_LIFETIME DEFAULT_PARTICLE_LIFETIME
); );
} }
pub fn kick(&mut self, x: i32, y: i32) { pub fn kick(&mut self, x: i32, y: i32) {
self.request( self.request(
x, x,
y, y,
RGB::named(CHOCOLATE), RGB::named(CHOCOLATE),
RGB::named(BLACK),
to_cp437('‼'), to_cp437('‼'),
"kick".to_string(),
SHORT_PARTICLE_LIFETIME SHORT_PARTICLE_LIFETIME
); );
} }
// Makes a particle request in the shape of an 'x'. Sort of.
#[allow(dead_code)]
pub fn request_star(
&mut self,
x: i32,
y: i32,
fg: RGB,
bg: RGB,
glyph: FontCharType,
lifetime: f32,
secondary_fg: RGB
) {
let eighth_l = lifetime / 8.0;
let quarter_l = eighth_l * 2.0;
self.request(x, y, fg, bg, glyph, lifetime);
self.delay(
x + 1,
y + 1,
secondary_fg.lerp(bg, 0.8),
bg,
to_cp437('/'),
quarter_l,
eighth_l
);
self.delay(
x + 1,
y - 1,
secondary_fg.lerp(bg, 0.6),
bg,
to_cp437('\\'),
quarter_l,
quarter_l
);
self.delay(
x - 1,
y - 1,
secondary_fg.lerp(bg, 0.2),
bg,
to_cp437('/'),
quarter_l,
eighth_l * 3.0
);
self.delay(
x - 1,
y + 1,
secondary_fg.lerp(bg, 0.4),
bg,
to_cp437('\\'),
quarter_l,
lifetime
);
}
// Makes a rainbow particle request in the shape of an 'x'. Sort of.
#[allow(dead_code)]
pub fn request_rainbow_star(&mut self, x: i32, y: i32, glyph: FontCharType, lifetime: f32) {
let bg = RGB::named(BLACK);
let eighth_l = lifetime / 8.0;
let quarter_l = eighth_l * 2.0;
let half_l = quarter_l * 2.0;
self.request(x, y, RGB::named(CYAN), bg, glyph, lifetime);
self.delay(x + 1, y + 1, RGB::named(RED), bg, to_cp437('\\'), half_l, eighth_l);
self.delay(x + 1, y - 1, RGB::named(ORANGE), bg, to_cp437('/'), half_l, quarter_l);
self.delay(x - 1, y - 1, RGB::named(GREEN), bg, to_cp437('\\'), half_l, eighth_l * 3.0);
self.delay(x - 1, y + 1, RGB::named(YELLOW), bg, to_cp437('/'), half_l, half_l);
}
// Makes a rainbow particle request. Sort of.
#[allow(dead_code)]
pub fn request_rainbow(&mut self, x: i32, y: i32, glyph: FontCharType, lifetime: f32) {
let bg = RGB::named(BLACK);
let eighth_l = lifetime / 8.0;
self.request(x, y, RGB::named(RED), bg, glyph, eighth_l);
self.delay(x, y, RGB::named(ORANGE), bg, glyph, eighth_l, eighth_l);
self.delay(x, y, RGB::named(YELLOW), bg, glyph, eighth_l, eighth_l * 2.0);
self.delay(x, y, RGB::named(GREEN), bg, glyph, eighth_l, eighth_l * 3.0);
self.delay(x, y, RGB::named(BLUE), bg, glyph, eighth_l, eighth_l * 4.0);
self.delay(x, y, RGB::named(INDIGO), bg, glyph, eighth_l, eighth_l * 5.0);
self.delay(x, y, RGB::named(VIOLET), bg, glyph, eighth_l, eighth_l * 6.0);
}
/// Makes a particle request in the shape of a +.
#[allow(dead_code)]
pub fn request_plus(
&mut self,
x: i32,
y: i32,
fg: RGB,
bg: RGB,
glyph: FontCharType,
lifetime: f32
) {
self.request(x, y, fg, bg, glyph, lifetime * 2.0);
self.request(x + 1, y, fg, bg, to_cp437('─'), lifetime);
self.request(x - 1, y, fg, bg, to_cp437('─'), lifetime);
self.request(x, y + 1, fg, bg, to_cp437('│'), lifetime);
self.request(x, y - 1, fg, bg, to_cp437('│'), lifetime);
}
} }
pub struct ParticleSpawnSystem {} pub struct ParticleSpawnSystem {}
@ -305,12 +194,15 @@ impl<'a> System<'a> for ParticleSpawnSystem {
.insert(p, Position { x: new_particle.x, y: new_particle.y }) .insert(p, Position { x: new_particle.x, y: new_particle.y })
.expect("Could not insert position"); .expect("Could not insert position");
renderables renderables
.insert(p, Renderable { .insert(
fg: new_particle.fg, p,
bg: new_particle.bg, Renderable::new(
glyph: new_particle.glyph, new_particle.glyph,
render_order: 0, new_particle.sprite.clone(),
}) new_particle.fg,
0
)
)
.expect("Could not insert renderables"); .expect("Could not insert renderables");
particles particles
.insert(p, ParticleLifetime { lifetime_ms: new_particle.lifetime }) .insert(p, ParticleLifetime { lifetime_ms: new_particle.lifetime })

View file

@ -30,15 +30,20 @@ use super::{
Viewshed, Viewshed,
WantsToMelee, WantsToMelee,
WantsToPickupItem, WantsToPickupItem,
WantsToAssignKey,
get_dest, get_dest,
Destination, Destination,
DamageType, DamageType,
effects::sound,
}; };
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use specs::prelude::*; use specs::prelude::*;
use std::cmp::{ max, min }; use std::cmp::{ max, min };
use crate::data::events::*; use crate::consts::events::*;
use crate::data::ids::*; use crate::consts::ids::*;
use crate::gui::with_article;
use notan::prelude::*;
use std::collections::HashMap;
pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState { pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState {
let mut positions = ecs.write_storage::<Position>(); let mut positions = ecs.write_storage::<Position>();
@ -89,6 +94,7 @@ pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState {
if door.open == true { if door.open == true {
let renderables = ecs.read_storage::<Renderable>(); let renderables = ecs.read_storage::<Renderable>();
if multiple_tile_content { if multiple_tile_content {
sound::door_resist(destination_idx);
if let Some(name) = names.get(potential_target) { if let Some(name) = names.get(potential_target) {
gamelog::Logger gamelog::Logger
::new() ::new()
@ -99,7 +105,8 @@ pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState {
.append("is blocked.") .append("is blocked.")
.log(); .log();
} }
} else if rng.roll_dice(1, 6) + attributes.strength.bonus < 2 { } else if rng.roll_dice(1, 6) + attributes.strength.modifier() < 2 {
sound::door_resist(destination_idx);
if let Some(name) = names.get(potential_target) { if let Some(name) = names.get(potential_target) {
gamelog::Logger gamelog::Logger
::new() ::new()
@ -111,13 +118,18 @@ pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState {
.log(); .log();
} }
} else { } else {
sound::door_close(destination_idx);
door.open = false; door.open = false;
if door.blocks_vis {
blocks_visibility blocks_visibility
.insert(potential_target, BlocksVisibility {}) .insert(potential_target, BlocksVisibility {})
.expect("Unable to insert BlocksVisibility."); .expect("Unable to insert BlocksVisibility.");
}
if door.blocks_move {
blocks_movement blocks_movement
.insert(potential_target, BlocksTile {}) .insert(potential_target, BlocksTile {})
.expect("Unable to insert BlocksTile."); .expect("Unable to insert BlocksTile.");
}
if let Some(name) = names.get(potential_target) { if let Some(name) = names.get(potential_target) {
gamelog::Logger gamelog::Logger
::new() ::new()
@ -132,7 +144,7 @@ pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState {
std::mem::drop(renderables); std::mem::drop(renderables);
let mut renderables = ecs.write_storage::<Renderable>(); let mut renderables = ecs.write_storage::<Renderable>();
let render_data = renderables.get_mut(potential_target).unwrap(); let render_data = renderables.get_mut(potential_target).unwrap();
render_data.glyph = to_cp437('+'); // Nethack open door, maybe just use '/' instead. render_data.swap();
door_pos = Some(Point::new(pos.x + delta_x, pos.y + delta_y)); door_pos = Some(Point::new(pos.x + delta_x, pos.y + delta_y));
} }
result = RunState::Ticking; result = RunState::Ticking;
@ -201,7 +213,8 @@ pub fn open(i: i32, j: i32, ecs: &mut World) -> RunState {
if let Some(door) = door { if let Some(door) = door {
if door.open == false { if door.open == false {
let renderables = ecs.read_storage::<Renderable>(); let renderables = ecs.read_storage::<Renderable>();
if rng.roll_dice(1, 6) + attributes.strength.bonus < 2 { if rng.roll_dice(1, 6) + attributes.strength.modifier() < 2 {
sound::door_resist(destination_idx);
if let Some(name) = names.get(potential_target) { if let Some(name) = names.get(potential_target) {
gamelog::Logger gamelog::Logger
::new() ::new()
@ -213,6 +226,7 @@ pub fn open(i: i32, j: i32, ecs: &mut World) -> RunState {
.log(); .log();
} }
} else { } else {
sound::door_open(destination_idx);
door.open = true; door.open = true;
blocks_visibility.remove(potential_target); blocks_visibility.remove(potential_target);
blocks_movement.remove(potential_target); blocks_movement.remove(potential_target);
@ -229,7 +243,7 @@ pub fn open(i: i32, j: i32, ecs: &mut World) -> RunState {
std::mem::drop(renderables); std::mem::drop(renderables);
let mut renderables = ecs.write_storage::<Renderable>(); let mut renderables = ecs.write_storage::<Renderable>();
let render_data = renderables.get_mut(potential_target).unwrap(); let render_data = renderables.get_mut(potential_target).unwrap();
render_data.glyph = to_cp437('▓'); // Nethack open door, maybe just use '/' instead. render_data.swap();
door_pos = Some(Point::new(pos.x + delta_x, pos.y + delta_y)); door_pos = Some(Point::new(pos.x + delta_x, pos.y + delta_y));
} }
result = RunState::Ticking; result = RunState::Ticking;
@ -330,15 +344,15 @@ pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState {
None, None,
EffectType::Particle { EffectType::Particle {
glyph: to_cp437('‼'), glyph: to_cp437('‼'),
sprite: "gnome".to_string(), // FIXME: REMOVE THE GNOMES
fg: RGB::named(CHOCOLATE), fg: RGB::named(CHOCOLATE),
bg: RGB::named(BLACK),
lifespan: 150.0, lifespan: 150.0,
delay: 0.0, delay: 0.0,
}, },
Targets::Entity { target: potential_target } Targets::Entity { target: potential_target }
); );
// ~33% chance of breaking it down + str // ~33% chance of breaking it down + str
if rng.roll_dice(1, 10) + attributes.strength.bonus > 6 { if rng.roll_dice(1, 10) + attributes.strength.modifier() > 6 {
gamelog::Logger gamelog::Logger
::new() ::new()
.append("As you kick the") .append("As you kick the")
@ -390,8 +404,8 @@ pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState {
None, None,
EffectType::Particle { EffectType::Particle {
glyph: to_cp437('‼'), glyph: to_cp437('‼'),
sprite: "gnome".to_string(), // FIXME: REMOVE THE GNOMES
fg: RGB::named(CHOCOLATE), fg: RGB::named(CHOCOLATE),
bg: RGB::named(BLACK),
lifespan: 150.0, lifespan: 150.0,
delay: 0.0, delay: 0.0,
}, },
@ -491,7 +505,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState
} }
let door = doors.get_mut(potential_target); let door = doors.get_mut(potential_target);
if let Some(door) = door { if let Some(door) = door {
if door.open == false { if door.open == false && door.blocks_move {
if let Some(name) = names.get(potential_target) { if let Some(name) = names.get(potential_target) {
let colour = if let colour = if
let Some(_) = ecs.read_storage::<Item>().get(potential_target) let Some(_) = ecs.read_storage::<Item>().get(potential_target)
@ -560,11 +574,11 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState
let mut logger = gamelog::Logger::new().append("You see"); let mut logger = gamelog::Logger::new().append("You see");
for i in 0..seen_items.len() { for i in 0..seen_items.len() {
if i > 0 && i < seen_items.len() { if i > 0 && i < seen_items.len() {
logger = logger.append(", a"); logger = logger.append(", ");
} }
logger = logger logger = logger
.colour(seen_items[i].1) .colour(seen_items[i].1)
.append_n(&seen_items[i].0) .append_n(with_article(&seen_items[i].0))
.colour(WHITE); .colour(WHITE);
} }
logger.period().log(); logger.period().log();
@ -633,7 +647,9 @@ fn get_item(ecs: &mut World) -> RunState {
return RunState::AwaitingInput; return RunState::AwaitingInput;
} }
Some(item) => { Some(item) => {
let mut assignkey = ecs.write_storage::<WantsToAssignKey>();
let mut pickup = ecs.write_storage::<WantsToPickupItem>(); let mut pickup = ecs.write_storage::<WantsToPickupItem>();
assignkey.insert(item, WantsToAssignKey {}).expect("Unable to insert WantsToAssignKey");
pickup pickup
.insert(*player_entity, WantsToPickupItem { collected_by: *player_entity, item }) .insert(*player_entity, WantsToPickupItem { collected_by: *player_entity, item })
.expect("Unable to insert want to pickup item."); .expect("Unable to insert want to pickup item.");
@ -642,135 +658,117 @@ fn get_item(ecs: &mut World) -> RunState {
} }
} }
pub fn player_input(gs: &mut State, ctx: &mut BTerm, on_overmap: bool) -> RunState { fn try_descend(ecs: &mut World) -> RunState {
match ctx.key { let dest = try_change_level(ecs, false);
None => { let curr_map_id = ecs.fetch::<Map>().id;
return RunState::AwaitingInput; return match dest {
} Destination::None => RunState::AwaitingInput,
Some(key) => Destination::NextLevel => RunState::GoToLevel(curr_map_id + 1, TileType::UpStair),
match key { Destination::PreviousLevel => RunState::GoToLevel(curr_map_id - 1, TileType::DownStair),
// Cardinals Destination::ToLocal(id) => RunState::GoToLevel(ID_OVERMAP, TileType::ToLocal(id)),
VirtualKeyCode::Left | VirtualKeyCode::Numpad4 | VirtualKeyCode::H => { Destination::ToOvermap(id) => RunState::GoToLevel(id, TileType::ToOvermap(id)),
return try_move_player(-1, 0, &mut gs.ecs); };
} }
VirtualKeyCode::Right | VirtualKeyCode::Numpad6 | VirtualKeyCode::L => { fn try_ascend(ecs: &mut World) -> RunState {
return try_move_player(1, 0, &mut gs.ecs); let dest = try_change_level(ecs, true);
} let curr_map_id = ecs.fetch::<Map>().id;
VirtualKeyCode::Up | VirtualKeyCode::Numpad8 | VirtualKeyCode::K => { return match dest {
return try_move_player(0, -1, &mut gs.ecs); Destination::None => RunState::AwaitingInput,
} Destination::NextLevel => RunState::GoToLevel(curr_map_id + 1, TileType::UpStair),
VirtualKeyCode::Down | VirtualKeyCode::Numpad2 | VirtualKeyCode::J => { Destination::PreviousLevel => RunState::GoToLevel(curr_map_id - 1, TileType::DownStair),
return try_move_player(0, 1, &mut gs.ecs); Destination::ToLocal(id) => RunState::GoToLevel(ID_OVERMAP, TileType::ToLocal(id)),
} Destination::ToOvermap(id) => RunState::GoToLevel(id, TileType::ToOvermap(id)),
// Diagonals };
VirtualKeyCode::Numpad9 | VirtualKeyCode::U => { }
return try_move_player(1, -1, &mut gs.ecs);
} pub fn player_input(gs: &mut State, ctx: &mut App, on_overmap: bool) -> RunState {
VirtualKeyCode::Numpad7 | VirtualKeyCode::Y => { let key = &ctx.keyboard;
return try_move_player(-1, -1, &mut gs.ecs); // Movement
} for keycode in key.pressed.iter() {
VirtualKeyCode::Numpad3 | VirtualKeyCode::N => { match *keycode {
return try_move_player(1, 1, &mut gs.ecs); KeyCode::Numpad1 | KeyCode::B => {
}
VirtualKeyCode::Numpad1 | VirtualKeyCode::B => {
return try_move_player(-1, 1, &mut gs.ecs); return try_move_player(-1, 1, &mut gs.ecs);
} }
// id KeyCode::Numpad2 | KeyCode::Down | KeyCode::J => {
VirtualKeyCode::Period => { return try_move_player(0, 1, &mut gs.ecs);
if ctx.shift { }
let dest = try_change_level(&mut gs.ecs, false); KeyCode::Numpad3 | KeyCode::N => {
let curr_map_id = gs.ecs.fetch::<Map>().id; return try_move_player(1, 1, &mut gs.ecs);
return match dest { }
// If we have no destination, do nothing. KeyCode::Numpad4 | KeyCode::Left | KeyCode::H => {
Destination::None => RunState::AwaitingInput, return try_move_player(-1, 0, &mut gs.ecs);
// If we want to go to the next level, go to the up-stair tile of id + 1. }
Destination::NextLevel => KeyCode::Numpad6 | KeyCode::Right | KeyCode::L => {
RunState::GoToLevel(curr_map_id + 1, TileType::UpStair), return try_move_player(1, 0, &mut gs.ecs);
// If we want to go to the previous level, go to the down-stair tile of id - 1. }
Destination::PreviousLevel => KeyCode::Numpad7 | KeyCode::Y => {
RunState::GoToLevel(curr_map_id - 1, TileType::DownStair), return try_move_player(-1, -1, &mut gs.ecs);
Destination::ToLocal(id) => }
RunState::GoToLevel(ID_OVERMAP, TileType::ToLocal(id)), KeyCode::Numpad8 | KeyCode::Up | KeyCode::K => {
Destination::ToOvermap(id) => return try_move_player(0, -1, &mut gs.ecs);
RunState::GoToLevel(id, TileType::ToOvermap(id)), }
}; KeyCode::Numpad9 | KeyCode::U => {
} else { return try_move_player(1, -1, &mut gs.ecs);
return skip_turn(&mut gs.ecs); // (Wait a turn) }
KeyCode::Period => {
if key.shift() {
return try_descend(&mut gs.ecs);
}
return skip_turn(&mut gs.ecs);
}
KeyCode::Comma => {
if key.shift() {
return try_ascend(&mut gs.ecs);
} }
} }
VirtualKeyCode::Comma => { KeyCode::Slash => {
if ctx.shift { if key.shift() {
let dest = try_change_level(&mut gs.ecs, true);
let curr_map_id = gs.ecs.fetch::<Map>().id;
return match dest {
Destination::None => RunState::AwaitingInput,
Destination::NextLevel =>
RunState::GoToLevel(curr_map_id + 1, TileType::UpStair),
Destination::PreviousLevel =>
RunState::GoToLevel(curr_map_id - 1, TileType::DownStair),
Destination::ToLocal(id) =>
RunState::GoToLevel(ID_OVERMAP, TileType::ToLocal(id)),
Destination::ToOvermap(id) =>
RunState::GoToLevel(id, TileType::ToOvermap(id)),
};
}
}
VirtualKeyCode::Slash => {
if ctx.shift {
return RunState::HelpScreen; return RunState::HelpScreen;
} }
} }
VirtualKeyCode::NumpadDecimal => { KeyCode::C => {
return skip_turn(&mut gs.ecs);
}
// Items
VirtualKeyCode::C => {
if !on_overmap { if !on_overmap {
return RunState::ActionWithDirection { function: try_door }; return RunState::ActionWithDirection { function: try_door };
} }
} }
VirtualKeyCode::O => { KeyCode::O => {
if !on_overmap { if !on_overmap {
return RunState::ActionWithDirection { function: open }; return RunState::ActionWithDirection { function: open };
} }
} }
VirtualKeyCode::F => { KeyCode::F => {
if !on_overmap { if !on_overmap {
return RunState::ActionWithDirection { function: kick }; return RunState::ActionWithDirection { function: kick };
} }
} }
VirtualKeyCode::G => { KeyCode::G => {
return get_item(&mut gs.ecs); return get_item(&mut gs.ecs);
} }
VirtualKeyCode::I => { KeyCode::I => {
return RunState::ShowInventory; return RunState::ShowInventory;
} }
VirtualKeyCode::D => { KeyCode::D => {
return RunState::ShowDropItem; return RunState::ShowDropItem;
} }
VirtualKeyCode::R => { KeyCode::R => {
return RunState::ShowRemoveItem; return RunState::ShowRemoveItem;
} }
// Other KeyCode::Minus => {
VirtualKeyCode::Minus => {
return RunState::ShowCheatMenu; return RunState::ShowCheatMenu;
} }
VirtualKeyCode::Escape => { KeyCode::Escape => {
return RunState::SaveGame; return RunState::SaveGame;
} }
VirtualKeyCode::X => { KeyCode::X => {
let (min_x, _max_x, min_y, _max_y, x_offset, y_offset) = get_screen_bounds( let bounds = get_screen_bounds(&gs.ecs, false);
&gs.ecs,
ctx
);
let ppos = gs.ecs.fetch::<Point>(); let ppos = gs.ecs.fetch::<Point>();
let (x, y) = (ppos.x + x_offset - min_x, ppos.y + y_offset - min_y); let (x, y) = (
ppos.x + bounds.x_offset - bounds.min_x,
ppos.y + bounds.y_offset - bounds.min_y,
);
return RunState::Farlook { x, y }; return RunState::Farlook { x, y };
} }
_ => { _ => {}
return RunState::AwaitingInput;
}
} }
} }
return RunState::AwaitingInput; return RunState::AwaitingInput;
@ -781,6 +779,18 @@ fn try_change_level(ecs: &mut World, backtracking: bool) -> Destination {
let map = ecs.fetch::<Map>(); let map = ecs.fetch::<Map>();
let player_idx = map.xy_idx(player_pos.x, player_pos.y); let player_idx = map.xy_idx(player_pos.x, player_pos.y);
let this_tile = map.tiles[player_idx]; let this_tile = map.tiles[player_idx];
let mut blocked = false;
crate::spatial::for_each_tile_content(player_idx, |potential| {
if let Some(is_door) = ecs.read_storage::<Door>().get(potential) {
if is_door.open == false {
blocked = true;
gamelog::Logger::new().append("The way is blocked.").log();
}
}
});
if blocked {
return Destination::None;
}
return get_dest(this_tile, backtracking); return get_dest(this_tile, backtracking);
} }

View file

@ -6,6 +6,8 @@ pub struct Item {
pub id: String, pub id: String,
pub name: Name, pub name: Name,
pub renderable: Option<Renderable>, pub renderable: Option<Renderable>,
pub avatar: Option<String>,
pub class: String,
pub weight: Option<f32>, pub weight: Option<f32>,
pub value: Option<f32>, pub value: Option<f32>,
pub equip: Option<Equippable>, pub equip: Option<Equippable>,
@ -30,9 +32,16 @@ pub struct Equippable {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct Renderable { pub struct Renderable {
pub glyph: String, pub glyph: String,
pub sprite: String,
pub alt: Option<String>,
pub fg: String, pub fg: String,
pub bg: String, pub fg_alt: Option<String>,
pub order: i32, pub order: i32,
pub order_alt: Option<i32>,
pub x: Option<f32>,
pub x_alt: Option<f32>,
pub y: Option<f32>,
pub y_alt: Option<f32>,
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]

View file

@ -9,4 +9,13 @@ pub struct Prop {
pub renderable: Option<Renderable>, pub renderable: Option<Renderable>,
pub flags: Option<Vec<String>>, pub flags: Option<Vec<String>>,
pub effects: Option<HashMap<String, String>>, pub effects: Option<HashMap<String, String>>,
pub door: Option<Door>,
}
#[derive(Deserialize, Debug)]
pub struct Door {
pub open: bool,
pub locked: bool,
pub blocks_vis: bool,
pub blocks_move: bool,
} }

View file

@ -4,8 +4,8 @@ use crate::gamesystem::*;
use crate::gui::Ancestry; use crate::gui::Ancestry;
use crate::random_table::RandomTable; use crate::random_table::RandomTable;
use crate::config::CONFIG; use crate::config::CONFIG;
use crate::data::visuals::BLOODSTAIN_COLOUR; use crate::consts::visuals::BLOODSTAIN_COLOUR;
use crate::data::entity::DEFAULT_VIEWSHED_STANDARD; use crate::consts::entity::DEFAULT_VIEWSHED_STANDARD;
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use specs::prelude::*; use specs::prelude::*;
use specs::saveload::{ MarkedBuilder, SimpleMarker }; use specs::saveload::{ MarkedBuilder, SimpleMarker };
@ -53,11 +53,6 @@ macro_rules! apply_flags {
"BLOCKS_VISIBILITY" => $eb = $eb.with(BlocksVisibility {}), "BLOCKS_VISIBILITY" => $eb = $eb.with(BlocksVisibility {}),
"ENTRY_TRIGGER" => $eb = $eb.with(EntryTrigger {}), "ENTRY_TRIGGER" => $eb = $eb.with(EntryTrigger {}),
"SINGLE_ACTIVATION" => $eb = $eb.with(SingleActivation {}), "SINGLE_ACTIVATION" => $eb = $eb.with(SingleActivation {}),
"DOOR" => {
$eb = $eb.with(Door { open: false });
$eb = $eb.with(BlocksVisibility {});
$eb = $eb.with(BlocksTile {});
}
// --- EFFECT FLAGS --- // --- EFFECT FLAGS ---
"FOOD" => $eb = $eb.with(ProvidesNutrition {}), "FOOD" => $eb = $eb.with(ProvidesNutrition {}),
"CONSUMABLE" => $eb = $eb.with(Consumable {}), "CONSUMABLE" => $eb = $eb.with(Consumable {}),
@ -66,6 +61,7 @@ macro_rules! apply_flags {
"IDENTIFY" => $eb = $eb.with(ProvidesIdentify {}), "IDENTIFY" => $eb = $eb.with(ProvidesIdentify {}),
"DIGGER" => $eb = $eb.with(Digger {}), "DIGGER" => $eb = $eb.with(Digger {}),
"MAGICMAP" => $eb = $eb.with(MagicMapper {}), "MAGICMAP" => $eb = $eb.with(MagicMapper {}),
"STACKABLE" => $eb = $eb.with(Stackable {}),
// CAN BE DESTROYED BY DAMAGE // CAN BE DESTROYED BY DAMAGE
"DESTRUCTIBLE" => $eb = $eb.with(Destructible {}), "DESTRUCTIBLE" => $eb = $eb.with(Destructible {}),
// --- EQUIP SLOTS --- // --- EQUIP SLOTS ---
@ -281,6 +277,7 @@ pub fn spawn_named_item(
if known_beatitude && !identified_items.contains(&item_template.name.name) { if known_beatitude && !identified_items.contains(&item_template.name.name) {
dm.identified_items.insert(item_template.name.name.clone()); dm.identified_items.insert(item_template.name.name.clone());
} }
let needs_key = is_player_owned(&player_entity, &pos);
std::mem::drop(player_entity); std::mem::drop(player_entity);
std::mem::drop(dm); std::mem::drop(dm);
// -- DROP EVERYTHING THAT INVOLVES THE ECS BEFORE THIS POINT --- // -- DROP EVERYTHING THAT INVOLVES THE ECS BEFORE THIS POINT ---
@ -293,12 +290,30 @@ pub fn spawn_named_item(
eb = eb.with(Item { eb = eb.with(Item {
weight: item_template.weight.unwrap_or(0.0), weight: item_template.weight.unwrap_or(0.0),
value: item_template.value.unwrap_or(0.0), value: item_template.value.unwrap_or(0.0),
category: match item_template.class.as_str() {
"amulet" => ItemType::Amulet,
"weapon" => ItemType::Weapon,
"armour" => ItemType::Armour,
"comestible" => ItemType::Comestible,
"scroll" => ItemType::Scroll,
"spellbook" => ItemType::Spellbook,
"potion" => ItemType::Potion,
"ring" => ItemType::Ring,
"wand" => ItemType::Wand,
_ => unreachable!("Unknown item type."),
},
}); });
eb = spawn_position(pos, eb, key, raws); eb = spawn_position(pos, eb, key, raws);
if needs_key {
eb = eb.with(WantsToAssignKey {});
}
if let Some(renderable) = &item_template.renderable { if let Some(renderable) = &item_template.renderable {
eb = eb.with(get_renderable_component(renderable)); eb = eb.with(get_renderable_component(renderable));
} }
if let Some(avatar) = &item_template.avatar {
eb = eb.with(Avatar::new(avatar.clone()));
}
// BEATITUDE // BEATITUDE
let buc = if let Some(buc_status) = buc { let buc = if let Some(buc_status) = buc {
buc_status buc_status
@ -392,6 +407,7 @@ pub fn spawn_named_mob(
if raws.mob_index.contains_key(key) { if raws.mob_index.contains_key(key) {
let mob_template = &raws.raws.mobs[raws.mob_index[key]]; let mob_template = &raws.raws.mobs[raws.mob_index[key]];
let mut player_level = 1; let mut player_level = 1;
let needs_key;
{ {
let pools = ecs.read_storage::<Pools>(); let pools = ecs.read_storage::<Pools>();
let player_entity = ecs.fetch::<Entity>(); let player_entity = ecs.fetch::<Entity>();
@ -399,12 +415,15 @@ pub fn spawn_named_mob(
if let Some(pool) = player_pool { if let Some(pool) = player_pool {
player_level = pool.level; player_level = pool.level;
} }
needs_key = is_player_owned(&player_entity, &pos);
} }
let mut eb; let mut eb;
// New entity with a position, name, combatstats, and viewshed // New entity with a position, name, combatstats, and viewshed
eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>(); eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>();
eb = spawn_position(pos, eb, key, raws); eb = spawn_position(pos, eb, key, raws);
if needs_key {
eb = eb.with(WantsToAssignKey {});
}
eb = eb.with(Name { name: mob_template.name.clone(), plural: mob_template.name.clone() }); eb = eb.with(Name { name: mob_template.name.clone(), plural: mob_template.name.clone() });
eb = eb.with(Viewshed { eb = eb.with(Viewshed {
visible_tiles: Vec::new(), visible_tiles: Vec::new(),
@ -445,36 +464,29 @@ pub fn spawn_named_mob(
} }
// Setup combat stats // Setup combat stats
let mut attr = Attributes { let mut attr = Attributes::default();
strength: Attribute { base: 10, modifiers: 0, bonus: 0 },
dexterity: Attribute { base: 10, modifiers: 0, bonus: 0 },
constitution: Attribute { base: 10, modifiers: 0, bonus: 0 },
intelligence: Attribute { base: 10, modifiers: 0, bonus: 0 },
wisdom: Attribute { base: 10, modifiers: 0, bonus: 0 },
charisma: Attribute { base: 10, modifiers: 0, bonus: 0 },
};
let mut mob_con = 10; let mut mob_con = 10;
let mut mob_int = 10; let mut mob_int = 10;
if let Some(attributes) = &mob_template.attributes { if let Some(attributes) = &mob_template.attributes {
if let Some(str) = attributes.str { if let Some(str) = attributes.str {
attr.strength = Attribute { base: str, modifiers: 0, bonus: attr_bonus(str) }; attr.strength = Attribute::new(str);
} }
if let Some(dex) = attributes.dex { if let Some(dex) = attributes.dex {
attr.strength = Attribute { base: dex, modifiers: 0, bonus: attr_bonus(dex) }; attr.strength = Attribute::new(dex);
} }
if let Some(con) = attributes.con { if let Some(con) = attributes.con {
attr.constitution = Attribute { base: con, modifiers: 0, bonus: attr_bonus(con) }; attr.constitution = Attribute::new(con);
mob_con = con; mob_con = con;
} }
if let Some(int) = attributes.int { if let Some(int) = attributes.int {
attr.intelligence = Attribute { base: int, modifiers: 0, bonus: attr_bonus(int) }; attr.intelligence = Attribute::new(int);
mob_int = int; mob_int = int;
} }
if let Some(wis) = attributes.wis { if let Some(wis) = attributes.wis {
attr.wisdom = Attribute { base: wis, modifiers: 0, bonus: attr_bonus(wis) }; attr.wisdom = Attribute::new(wis);
} }
if let Some(cha) = attributes.cha { if let Some(cha) = attributes.cha {
attr.charisma = Attribute { base: cha, modifiers: 0, bonus: attr_bonus(cha) }; attr.charisma = Attribute::new(cha);
} }
} }
eb = eb.with(attr); eb = eb.with(attr);
@ -632,10 +644,18 @@ pub fn spawn_named_prop(
pos: SpawnType pos: SpawnType
) -> Option<Entity> { ) -> Option<Entity> {
if raws.prop_index.contains_key(key) { if raws.prop_index.contains_key(key) {
let needs_key;
{
let player_entity = ecs.fetch::<Entity>();
needs_key = is_player_owned(&player_entity, &pos);
}
// ENTITY BUILDER PREP // ENTITY BUILDER PREP
let prop_template = &raws.raws.props[raws.prop_index[key]]; let prop_template = &raws.raws.props[raws.prop_index[key]];
let mut eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>(); let mut eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>();
eb = spawn_position(pos, eb, key, raws); eb = spawn_position(pos, eb, key, raws);
if needs_key {
eb = eb.with(WantsToAssignKey {});
}
// APPLY MANDATORY COMPONENTS FOR A PROP: // APPLY MANDATORY COMPONENTS FOR A PROP:
// - Name // - Name
// - Prop {} // - Prop {}
@ -656,6 +676,23 @@ pub fn spawn_named_prop(
if let Some(effects_list) = &prop_template.effects { if let Some(effects_list) = &prop_template.effects {
apply_effects!(effects_list, eb); apply_effects!(effects_list, eb);
} }
if let Some(door) = &prop_template.door {
eb = eb.with(Door {
open: door.open,
locked: door.locked,
blocks_vis: door.blocks_vis,
blocks_move: door.blocks_move,
});
if !door.open {
if door.blocks_vis {
eb = eb.with(BlocksVisibility {});
}
if door.blocks_move {
eb = eb.with(BlocksTile {});
}
}
}
// BUILD THE ENTITY // BUILD THE ENTITY
return Some(eb.build()); return Some(eb.build());
} }
@ -686,14 +723,70 @@ fn spawn_position<'a>(
eb eb
} }
fn is_player_owned(player: &Entity, pos: &SpawnType) -> bool {
match pos {
SpawnType::Carried { by } => {
if by == player {
return true;
}
}
SpawnType::Equipped { by } => {
if by == player {
return true;
}
}
_ => {}
}
false
}
fn get_renderable_component( fn get_renderable_component(
renderable: &super::item_structs::Renderable renderable: &super::item_structs::Renderable
) -> crate::components::Renderable { ) -> crate::components::Renderable {
let glyph = to_cp437(renderable.glyph.chars().next().unwrap());
let sprite = renderable.sprite.clone();
let sprite_alt = if let Some(sprite_alt) = &renderable.alt {
Some(sprite_alt.clone())
} else {
None
};
let fg = RGB::from_hex(&renderable.fg).expect("Invalid RGB");
let fg_alt = if let Some(fg_alt) = &renderable.fg_alt {
Some(RGB::from_hex(&fg_alt).expect("Invalid RGB"))
} else {
None
};
let render_order = renderable.order;
let render_order_alt = if let Some(order_alt) = renderable.order_alt {
Some(order_alt)
} else {
None
};
let offset_x = if let Some(x) = renderable.x { x } else { 0.0 };
let offset_y = if let Some(y) = renderable.y { -y } else { 0.0 };
let offset_alt: Option<(f32, f32)> = if
renderable.x_alt.is_some() ||
renderable.y_alt.is_some()
{
Some((
if let Some(x) = renderable.x_alt { x } else { 0.0 },
if let Some(y) = renderable.y_alt { -y } else { 0.0 },
))
} else {
None
};
crate::components::Renderable { crate::components::Renderable {
glyph: to_cp437(renderable.glyph.chars().next().unwrap()), glyph,
fg: RGB::from_hex(&renderable.fg).expect("Invalid RGB"), sprite,
bg: RGB::from_hex(&renderable.bg).expect("Invalid RGB"), sprite_alt,
render_order: renderable.order, fg,
fg_alt,
render_order,
render_order_alt,
offset: (offset_x, offset_y),
offset_alt,
} }
} }
@ -1017,7 +1110,7 @@ fn get_ancestry_string(ancestry: Ancestry) -> &'static str {
Ancestry::Gnome => { Ancestry::Gnome => {
return "gnome"; return "gnome";
} }
Ancestry::NULL => { Ancestry::Unset => {
return "NULL"; return "NULL";
} }
} }
@ -1026,35 +1119,41 @@ fn get_ancestry_string(ancestry: Ancestry) -> &'static str {
fn parse_particle_line(n: &str) -> SpawnParticleLine { fn parse_particle_line(n: &str) -> SpawnParticleLine {
let tokens: Vec<_> = n.split(';').collect(); let tokens: Vec<_> = n.split(';').collect();
SpawnParticleLine { SpawnParticleLine {
glyph: to_cp437(tokens[0].chars().next().unwrap()), sprite: tokens[0].to_string(),
tail_glyph: to_cp437(tokens[1].chars().next().unwrap()), tail_sprite: tokens[1].to_string(),
colour: RGB::from_hex(tokens[2]).expect("Invalid RGB"), glyph: to_cp437(tokens[2].chars().next().unwrap()),
lifetime_ms: tokens[3].parse::<f32>().unwrap(), tail_glyph: to_cp437(tokens[3].chars().next().unwrap()),
trail_colour: RGB::from_hex(tokens[4]).expect("Invalid trail RGB"), colour: RGB::from_hex(tokens[4]).expect("Invalid RGB"),
trail_lifetime_ms: tokens[5].parse::<f32>().unwrap(), lifetime_ms: tokens[5].parse::<f32>().unwrap(),
trail_colour: RGB::from_hex(tokens[6]).expect("Invalid trail RGB"),
trail_lifetime_ms: tokens[7].parse::<f32>().unwrap(),
} }
} }
fn parse_particle(n: &str) -> SpawnParticleSimple { fn parse_particle(n: &str) -> SpawnParticleSimple {
let tokens: Vec<_> = n.split(';').collect(); let tokens: Vec<_> = n.split(';').collect();
SpawnParticleSimple { SpawnParticleSimple {
glyph: to_cp437(tokens[0].chars().next().unwrap()), sprite: tokens[0].to_string(),
colour: RGB::from_hex(tokens[1]).expect("Invalid RGB"), glyph: to_cp437(tokens[1].chars().next().unwrap()),
lifetime_ms: tokens[2].parse::<f32>().unwrap(), colour: RGB::from_hex(tokens[2]).expect(&format!("Invalid RGB: {}", n)),
lifetime_ms: tokens[3].parse::<f32>().unwrap(),
} }
} }
fn parse_particle_burst(n: &str) -> SpawnParticleBurst { fn parse_particle_burst(n: &str) -> SpawnParticleBurst {
let tokens: Vec<_> = n.split(';').collect(); let tokens: Vec<_> = n.split(';').collect();
SpawnParticleBurst { SpawnParticleBurst {
glyph: to_cp437(tokens[0].chars().next().unwrap()), sprite: tokens[0].to_string(),
head_glyph: to_cp437(tokens[1].chars().next().unwrap()), head_sprite: tokens[1].to_string(),
tail_glyph: to_cp437(tokens[2].chars().next().unwrap()), tail_sprite: tokens[2].to_string(),
colour: RGB::from_hex(tokens[3]).expect("Invalid RGB"), glyph: to_cp437(tokens[3].chars().next().unwrap()),
lerp: RGB::from_hex(tokens[4]).expect("Invalid LERP RGB"), head_glyph: to_cp437(tokens[4].chars().next().unwrap()),
lifetime_ms: tokens[5].parse::<f32>().unwrap(), tail_glyph: to_cp437(tokens[5].chars().next().unwrap()),
trail_colour: RGB::from_hex(tokens[6]).expect("Invalid trail RGB"), colour: RGB::from_hex(tokens[6]).expect("Invalid RGB"),
trail_lifetime_ms: tokens[7].parse::<f32>().unwrap(), lerp: RGB::from_hex(tokens[7]).expect("Invalid LERP RGB"),
lifetime_ms: tokens[8].parse::<f32>().unwrap(),
trail_colour: RGB::from_hex(tokens[9]).expect("Invalid trail RGB"),
trail_lifetime_ms: tokens[10].parse::<f32>().unwrap(),
} }
} }

View file

@ -1,6 +1,5 @@
use super::components::*; use super::components::*;
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use specs::error::NoError;
use specs::prelude::*; use specs::prelude::*;
use specs::saveload::{ use specs::saveload::{
DeserializeComponents, DeserializeComponents,
@ -12,11 +11,12 @@ use specs::saveload::{
use std::fs; use std::fs;
use std::fs::File; use std::fs::File;
use std::path::Path; use std::path::Path;
use std::convert::Infallible;
macro_rules! serialize_individually { macro_rules! serialize_individually {
($ecs:expr, $ser:expr, $data:expr, $($type:ty),*) => { ($ecs:expr, $ser:expr, $data:expr, $($type:ty),*) => {
$( $(
SerializeComponents::<NoError, SimpleMarker<SerializeMe>>::serialize( SerializeComponents::<Infallible, SimpleMarker<SerializeMe>>::serialize(
&( $ecs.read_storage::<$type>(), ), &( $ecs.read_storage::<$type>(), ),
&$data.0, &$data.0,
&$data.1, &$data.1,
@ -28,7 +28,13 @@ macro_rules! serialize_individually {
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub fn save_game(_ecs: &mut World) {} pub fn save_game(_ecs: &mut World) {
console::log(
"Unfortunately, saving isn't supported in any easy way on the web. Sorry!
You can, at least, save your morgue file after dying - it'll be written
to the log, and just needs saving into a text file."
)
}
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub fn save_game(ecs: &mut World) { pub fn save_game(ecs: &mut World) {
@ -55,8 +61,8 @@ pub fn save_game(ecs: &mut World) {
{ {
let data = (ecs.entities(), ecs.read_storage::<SimpleMarker<SerializeMe>>()); let data = (ecs.entities(), ecs.read_storage::<SimpleMarker<SerializeMe>>());
let writer = File::create("./savegame.json").unwrap(); let writer = File::create("./savegame.bin").unwrap();
let mut serializer = serde_json::Serializer::new(writer); let mut serializer = bincode::Serializer::new(writer, bincode::options());
serialize_individually!( serialize_individually!(
ecs, ecs,
serializer, serializer,
@ -64,6 +70,7 @@ pub fn save_game(ecs: &mut World) {
AOE, AOE,
ArmourClassBonus, ArmourClassBonus,
Attributes, Attributes,
Avatar,
Beatitude, Beatitude,
Bleeds, Bleeds,
Blind, Blind,
@ -98,6 +105,7 @@ pub fn save_game(ecs: &mut World) {
IntrinsicChanged, IntrinsicChanged,
Intrinsics, Intrinsics,
Item, Item,
Key,
KnownSpells, KnownSpells,
LootTable, LootTable,
MagicItem, MagicItem,
@ -127,17 +135,21 @@ pub fn save_game(ecs: &mut World) {
SpawnParticleBurst, SpawnParticleBurst,
SpawnParticleLine, SpawnParticleLine,
SpawnParticleSimple, SpawnParticleSimple,
Stackable,
TakingTurn, TakingTurn,
Telepath, Telepath,
ToHitBonus, ToHitBonus,
Viewshed, Viewshed,
Charges, Charges,
WantsToApproach, WantsToApproach,
WantsToAssignKey,
WantsToDelete,
WantsToDropItem, WantsToDropItem,
WantsToFlee, WantsToFlee,
WantsToMelee, WantsToMelee,
WantsToPickupItem, WantsToPickupItem,
WantsToRemoveItem, WantsToRemoveItem,
WantsToRemoveKey,
WantsToUseItem, WantsToUseItem,
SerializationHelper, SerializationHelper,
DMSerializationHelper DMSerializationHelper
@ -150,13 +162,13 @@ pub fn save_game(ecs: &mut World) {
} }
pub fn does_save_exist() -> bool { pub fn does_save_exist() -> bool {
Path::new("./savegame.json").exists() Path::new("./savegame.bin").exists()
} }
macro_rules! deserialize_individually { macro_rules! deserialize_individually {
($ecs:expr, $de:expr, $data:expr, $($type:ty),*) => { ($ecs:expr, $de:expr, $data:expr, $($type:ty),*) => {
$( $(
DeserializeComponents::<NoError, _>::deserialize( DeserializeComponents::<Infallible, _>::deserialize(
&mut ( &mut $ecs.write_storage::<$type>(), ), &mut ( &mut $ecs.write_storage::<$type>(), ),
&$data.0, // entities &$data.0, // entities
&mut $data.1, // marker &mut $data.1, // marker
@ -180,8 +192,8 @@ pub fn load_game(ecs: &mut World) {
} }
} }
let data = fs::read_to_string("./savegame.json").unwrap(); let data = fs::read("./savegame.bin").unwrap();
let mut de = serde_json::Deserializer::from_str(&data); let mut de = bincode::Deserializer::with_reader(&*data, bincode::options());
{ {
let mut d = ( let mut d = (
@ -197,6 +209,7 @@ pub fn load_game(ecs: &mut World) {
AOE, AOE,
ArmourClassBonus, ArmourClassBonus,
Attributes, Attributes,
Avatar,
Beatitude, Beatitude,
Bleeds, Bleeds,
Blind, Blind,
@ -231,6 +244,7 @@ pub fn load_game(ecs: &mut World) {
IntrinsicChanged, IntrinsicChanged,
Intrinsics, Intrinsics,
Item, Item,
Key,
KnownSpells, KnownSpells,
LootTable, LootTable,
MagicItem, MagicItem,
@ -260,17 +274,21 @@ pub fn load_game(ecs: &mut World) {
SpawnParticleBurst, SpawnParticleBurst,
SpawnParticleLine, SpawnParticleLine,
SpawnParticleSimple, SpawnParticleSimple,
Stackable,
TakingTurn, TakingTurn,
Telepath, Telepath,
ToHitBonus, ToHitBonus,
Viewshed, Viewshed,
Charges, Charges,
WantsToApproach, WantsToApproach,
WantsToAssignKey,
WantsToDelete,
WantsToDropItem, WantsToDropItem,
WantsToFlee, WantsToFlee,
WantsToMelee, WantsToMelee,
WantsToPickupItem, WantsToPickupItem,
WantsToRemoveItem, WantsToRemoveItem,
WantsToRemoveKey,
WantsToUseItem, WantsToUseItem,
SerializationHelper, SerializationHelper,
DMSerializationHelper DMSerializationHelper
@ -311,7 +329,7 @@ pub fn load_game(ecs: &mut World) {
} }
pub fn delete_save() { pub fn delete_save() {
if Path::new("./savegame.json").exists() { if Path::new("./savegame.bin").exists() {
std::fs::remove_file("./savegame.json").expect("Unable to delete file"); std::fs::remove_file("./savegame.bin").expect("Unable to delete file");
} }
} }

Some files were not shown because too many files have changed in this diff Show more