105 Commits

Author SHA1 Message Date
08da6c6315 Update Rust crate serde_json to v1.0.141 2025-07-19 00:54:53 +00:00
441072811e Update Rust crate serde_json to v1.0.140
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-03-04 02:22:19 +00:00
f12f007b28 Update Rust crate serde_json to v1.0.139
All checks were successful
continuous-integration/drone/push Build is passing
2025-02-20 07:35:58 +01:00
02b0a19aa4 Update Rust crate serde to v1.0.218
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-20 06:16:35 +00:00
675164df57 Update Rust crate serde_json to v1.0.138
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-01-29 02:18:11 +00:00
b6b97bf11f Update Rust crate serde_json to v1.0.137
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-01-20 02:08:46 +00:00
1d991ddaf5 Update Rust crate serde_json to v1.0.136
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-01-19 02:12:11 +00:00
2b12ab95e6 Update Rust crate serde_json to v1.0.135
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-01-07 02:03:02 +00:00
5720200e32 Update Rust crate serde to v1.0.217
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-12-28 02:03:57 +00:00
86636b1d89 Update Rust crate serde_json to v1.0.134
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-12-22 01:54:09 +00:00
697e2b765c Update Rust crate serde to v1.0.216
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-12-11 02:23:00 +00:00
594583f7f1 Update Rust crate serde_json to v1.0.133
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-17 01:56:47 +00:00
77ed37f853 Update Rust crate serde to v1.0.215
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-11-12 01:55:04 +00:00
308630d306 Update Rust crate serde to v1.0.214
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-10-29 02:14:12 +00:00
c389a0806a Update Rust crate serde to v1.0.213
Some checks are pending
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is pending
2024-10-23 01:17:49 +00:00
cdfebbec7b Update Rust crate serde_json to v1.0.132
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-10-20 01:11:47 +00:00
7296368bf4 Update Rust crate serde_json to v1.0.131
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-10-19 01:20:50 +00:00
6b3593a2f5 Update Rust crate serde to v1.0.210
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-10-18 05:00:14 +00:00
71d1a31d0f Update Rust crate serde_json to v1.0.129
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-10-18 01:10:18 +00:00
a47fd370d7 Update Rust crate serde_json to v1.0.125
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-21 23:04:15 +00:00
0fa1670aa0 Update Rust crate serde to v1.0.208
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-21 21:38:05 +00:00
a5988c9ce3 Update Rust crate serde to v1.0.203
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-25 18:19:17 +00:00
e64d6efd98 Update Rust crate serde to ^1.0.202
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-15 08:24:47 +00:00
de56c96884 Update all dependencies
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-08 01:08:00 +00:00
bcb749682f Update Rust crate serde to ^1.0.200
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-01 16:29:02 +00:00
c331b04f0e Update Rust crate serde to ^1.0.198
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-04-16 21:51:35 +00:00
3210ac3389 Update Rust crate serde_json to 1.0.116
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-04-16 05:59:08 +00:00
bf31ef75f6 Update Rust crate serde_json to 1.0.115
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-26 07:03:18 +00:00
a2cb3d7166 Update all dependencies
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-02-20 01:11:45 +00:00
d85910bd20 Update Rust crate serde_json to 1.0.113
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-01-29 04:31:54 +00:00
977b40d5e8 Update all dependencies
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-01-26 22:14:09 +00:00
05db5b566f Update Rust crate serde to ^1.0.195
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-01-06 03:25:47 +00:00
d5c423e94f Update Rust crate serde_json to 1.0.111
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-01-04 07:45:48 +00:00
174ebb3aa5 Update all dependencies
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-01-02 07:56:32 +00:00
15774db12d Update Rust crate serde_json to 1.0.109
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-01-01 01:19:26 +00:00
390406145f Update Rust crate serde to ^1.0.193
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-11-21 02:14:10 +00:00
08e63fc99a Update Rust crate serde_json to 1.0.108
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-10-30 16:16:47 +00:00
fa8ced3b05 Update Rust crate serde to ^1.0.190
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-10-26 03:41:48 +00:00
e60e895fe3 Update Rust crate serde to ^1.0.189
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-10-13 02:54:52 +00:00
2123b32615 Update Rust crate specs to 0.20.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-09-25 03:44:26 +00:00
cd14b20f2e Update Rust crate serde_json to 1.0.107
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-09-13 23:40:01 +00:00
36ffbfed1d Update Rust crate serde_json to 1.0.106
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-09-09 19:34:59 +00:00
10c836d987 Update Rust crate serde to ^1.0.188
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-08-26 02:59:20 +00:00
e98521c1c4 Update Rust crate serde to ^1.0.187
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-08-25 18:14:00 +00:00
ffecdba71d Update Rust crate serde to ^1.0.186
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-08-24 00:43:22 +00:00
269135318c Update Rust crate serde to ^1.0.185
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-08-21 05:04:51 +00:00
56d6d88ef3 Update Rust crate serde_json to 1.0.105
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-08-15 22:40:33 +00:00
91dac008dc Update Rust crate serde to ^1.0.183
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-08-07 04:30:32 +00:00
8fba60a5e8 Update Rust crate serde to ^1.0.182
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-08-06 06:03:30 +00:00
aa596b8b81 Update Rust crate serde to ^1.0.181
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-08-04 00:42:09 +00:00
e3561d3fe2 Update Rust crate serde to ^1.0.180
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-07-31 19:05:05 +00:00
c1d19fa106 Update Rust crate serde to ^1.0.179
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-07-31 00:41:35 +00:00
f7df41cbeb Update Rust crate serde to ^1.0.178
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-07-28 23:44:37 +00:00
d4654d8113 Update Rust crate serde to ^1.0.177
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-07-27 18:24:54 +00:00
0dda955141 Update Rust crate serde to ^1.0.176
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-07-26 21:04:07 +00:00
b1e06dec5d Update Rust crate serde_json to 1.0.104
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-07-26 19:52:26 +00:00
0bbfe859a3 Update all dependencies
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-07-24 03:57:15 +00:00
1f491c017c Merge pull request 'Update Rust crate serde_json to 1.0.100' (#29) from renovate/all into main
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-04 20:51:53 +00:00
6accff6ca5 Merge pull request 'Update Rust crate serde to ^1.0.166' (#28) from renovate/all into main
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-03 19:13:26 +00:00
7a5341a986 Merge pull request 'Update Rust crate serde to ^1.0.165' (#27) from renovate/all into main
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-03 12:08:59 +00:00
212f8d870d Merge pull request 'Update all dependencies' (#26) from renovate/all into main
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-24 05:23:12 +00:00
41b8407f5d Update Rust crate serde to ^1.0.162
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-05-05 08:18:48 +00:00
e85057ebfc Update Rust crate serde_json to 1.0.96
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-04-13 00:12:13 +00:00
02d92d928b Update Rust crate serde to ^1.0.160
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-04-11 06:20:51 +00:00
b918f88db5 Update Rust crate serde to ^1.0.159
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-03-28 06:34:53 +00:00
858d557a3b Update Rust crate serde_json to 1.0.95
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-03-27 17:30:52 +00:00
84fef1c758 Update Rust crate serde to ^1.0.158
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-03-20 17:44:19 +00:00
62c225b5dd Update Rust crate serde to ^1.0.157
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-03-18 10:09:19 +00:00
9c98b848f5 Update Rust crate serde to ^1.0.156
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-03-14 17:34:52 +00:00
e2dbdc1bbf Update Rust crate serde to ^1.0.154
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-03-08 20:39:19 +00:00
f91daaf46a Update Rust crate serde to ^1.0.153
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-03-07 19:26:47 +00:00
931cf083e8 Update Rust crate serde_json to 1.0.94
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-03-05 18:35:24 +00:00
d4a6d9aef8 Update Rust crate serde to ^1.0.152
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-12-26 18:03:49 +00:00
c03cd1208c Update Rust crate serde_json to 1.0.91
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-12-18 17:43:19 +00:00
99941e701b Update all dependencies
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-12-18 11:22:22 +00:00
bf80ced3e0 Update Rust crate serde to ^1.0.150
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-12-12 00:40:19 +00:00
a8e53b6e73 Update Rust crate serde to ^1.0.148
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-12-01 19:34:50 +00:00
e35a8b14d6 Update Rust crate serde_json to 1.0.89
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-11-22 14:39:29 +00:00
3049210660 Update Rust crate serde_json to 1.0.88
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-11-18 14:25:20 +00:00
7e6309d777 Update all dependencies
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-10-26 18:22:57 +00:00
eeb4b5f089 Update Rust crate rltk to 0.8.7
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-10-26 15:13:31 +00:00
7bab0c7123 Add .drone.yml
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-26 16:17:04 +02:00
596c04d791 Add .drone.yml
All checks were successful
continuous-integration/drone Build is passing
2022-10-26 11:30:38 +02:00
90d60ff54c Merge pull request 'Configure Renovate' (#1) from renovate/configure into main
Reviewed-on: https://git.front.kjuulh.io/kjuulh/roguelike/pulls/1
2022-10-25 21:00:02 +00:00
162fb413a5 Add renovate.json 2022-10-25 20:58:59 +00:00
34c4aa35fc Add drunkards walk 2022-01-30 20:10:23 +01:00
d4892e33bd Add cellular automata 2022-01-30 15:50:36 +01:00
5a00ebf1f1 Add bsp interior 2022-01-30 14:24:56 +01:00
c1893aec74 Bsp: format 2022-01-29 22:38:38 +01:00
0511c00f09 Add base bsp 2022-01-29 22:38:26 +01:00
9c0e546887 Add map test 2022-01-29 21:29:23 +01:00
ce4eb0b4cf Refactor 2022-01-29 21:27:59 +01:00
fa71410453 Refactor map 2022-01-29 20:21:13 +01:00
0af23af3d3 Add basic map 2022-01-29 16:35:10 +01:00
7c67291031 Add traps 2022-01-29 16:07:01 +01:00
835a416013 Add main menu 2022-01-29 14:31:02 +01:00
62240a42c1 Chapter 20 2022-01-29 14:14:34 +01:00
fdeabefd75 Add hunger: Reformat 2022-01-29 00:31:16 +01:00
74c05f97af Add hunger 2022-01-29 00:31:00 +01:00
cb17a9c356 Add chapter: 18 2022-01-28 21:21:29 +01:00
0e2c91dd12 Add chapter: 17 2022-01-28 20:20:04 +01:00
743bf7abde Add chapter: 16 2022-01-28 14:12:38 +01:00
aad1402f81 Finished section 1 2022-01-28 14:06:20 +01:00
fb5b70ce3b Add more difficulty 2022-01-28 00:49:08 +01:00
e68fa87fc9 formatted 2022-01-27 22:38:44 +01:00
35 changed files with 3253 additions and 522 deletions

9
.drone.yml Executable file
View File

@@ -0,0 +1,9 @@
kind: pipeline
type: docker
name: "test"
steps:
- name: test
image: harbor.front.kjuulh.io/docker-proxy/library/bash:latest
commands:
- echo 'Run tests'

8
.idea/.gitignore generated vendored
View File

@@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

99
.idea/workspace.xml generated Normal file
View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="CMakeSettings">
<configurations>
<configuration PROFILE_NAME="Debug" ENABLED="true" CONFIG_NAME="Debug" />
</configurations>
</component>
<component name="CargoProjects">
<cargoProject FILE="$PROJECT_DIR$/Cargo.toml" />
</component>
<component name="ChangeListManager">
<list default="true" id="a9f5cad0-d253-42e8-a38a-89dfec417dbc" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/src/map_builders/drunkard.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/savegame.json" beforeDir="false" afterPath="$PROJECT_DIR$/savegame.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/map_builders/common.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/map_builders/common.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/map_builders/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/map_builders/mod.rs" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ClangdSettings">
<option name="formatViaClangd" value="false" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="GitSEFilterConfiguration">
<file-type-list>
<filtered-out-file-type name="LOCAL_BRANCH" />
<filtered-out-file-type name="REMOTE_BRANCH" />
<filtered-out-file-type name="TAG" />
<filtered-out-file-type name="COMMIT_BY_MESSAGE" />
</file-type-list>
</component>
<component name="MacroExpansionManager">
<option name="directoryName" value="7zted9t8" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectId" id="24PyiDwZbQQQ0gzNvMewyQ4Zryi" />
<component name="ProjectLevelVcsManager">
<ConfirmationsSetting value="2" id="Add" />
</component>
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="RunOnceActivity.cidr.known.project.marker" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="cf.first.check.clang-format" value="false" />
<property name="cidr.known.project.marker" value="true" />
<property name="node.js.detected.package.eslint" value="true" />
<property name="node.js.detected.package.tslint" value="true" />
<property name="node.js.selected.package.eslint" value="(autodetect)" />
<property name="node.js.selected.package.tslint" value="(autodetect)" />
<property name="nodejs_package_manager_path" value="npm" />
<property name="org.rust.cargo.project.model.PROJECT_DISCOVERY" value="true" />
<property name="settings.editor.selected.configurable" value="reference.settingsdialog.project.grazie" />
</component>
<component name="RustProjectSettings">
<option name="toolchainHomeDirectory" value="$USER_HOME$/.cargo/bin" />
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="a9f5cad0-d253-42e8-a38a-89dfec417dbc" name="Changes" comment="" />
<created>1643546643557</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1643546643557</updated>
<workItem from="1643546645546" duration="12996000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
</component>
</project>

189
Cargo.lock generated
View File

@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "ab_glyph_rasterizer" name = "ab_glyph_rasterizer"
@@ -22,9 +22,14 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]] [[package]]
name = "ahash" name = "ahash"
version = "0.3.8" version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
@@ -62,15 +67,15 @@ checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0"
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.5.2" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]] [[package]]
name = "atom" name = "atomic_refcell"
version = "0.3.6" version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9ff149ed9780025acfdb36862d35b28856bb693ceb451259a7164442f22fdc3" checksum = "112ef6b3f6cb3cb6fc5b6b494ef7a848492cff1ab0ef4de10b0f7d572861c905"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
@@ -441,13 +446,12 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-queue" name = "crossbeam-queue"
version = "0.2.3" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" checksum = "1cd42583b04998a5363558e5f9291ee5a5ff6b49944332103f251e7479a82aa7"
dependencies = [ dependencies = [
"cfg-if 0.1.10", "cfg-if 1.0.0",
"crossbeam-utils 0.7.2", "crossbeam-utils 0.8.6",
"maybe-uninit",
] ]
[[package]] [[package]]
@@ -498,7 +502,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn", "syn 1.0.105",
] ]
[[package]] [[package]]
@@ -509,7 +513,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn", "syn 1.0.105",
] ]
[[package]] [[package]]
@@ -530,7 +534,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.105",
] ]
[[package]] [[package]]
@@ -750,16 +754,6 @@ dependencies = [
"gl_generator", "gl_generator",
] ]
[[package]]
name = "hashbrown"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96282e96bfcd3da0d3aa9938bedf1e50df3269b6db08b4876d2da0bb1a0841cf"
dependencies = [
"ahash",
"autocfg",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.19" version = "0.1.19"
@@ -771,11 +765,10 @@ dependencies = [
[[package]] [[package]]
name = "hibitset" name = "hibitset"
version = "0.6.3" version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93a1bb8316a44459a7d14253c4d28dd7395cbd23cc04a68c46e851b8e46d64b1" checksum = "f3ede5cfa60c958e60330d65163adbc4211e15a2653ad80eb0cce878de120121"
dependencies = [ dependencies = [
"atom",
"rayon", "rayon",
] ]
@@ -921,6 +914,22 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
] ]
[[package]]
name = "macro_rules_attribute"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf0c9b980bf4f3a37fd7b1c066941dd1b1d0152ce6ee6e8fe8c49b9f6810d862"
dependencies = [
"macro_rules_attribute-proc_macro",
"paste",
]
[[package]]
name = "macro_rules_attribute-proc_macro"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58093314a45e00c77d5c508f76e77c3396afbbc0d01506e7fae47b018bac2b1d"
[[package]] [[package]]
name = "malloc_buf" name = "malloc_buf"
version = "0.0.6" version = "0.0.6"
@@ -930,12 +939,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.4.1" version = "2.4.1"
@@ -1028,12 +1031,6 @@ dependencies = [
"ws2_32-sys", "ws2_32-sys",
] ]
[[package]]
name = "mopa"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a785740271256c230f57462d3b83e52f998433a7062fc18f96d5999474a9f915"
[[package]] [[package]]
name = "ndk" name = "ndk"
version = "0.2.1" version = "0.2.1"
@@ -1070,7 +1067,7 @@ dependencies = [
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.105",
] ]
[[package]] [[package]]
@@ -1135,6 +1132,27 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "nougat"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b57b9ced431322f054fc673f1d3c7fa52d80efd9df74ad2fc759f044742510"
dependencies = [
"macro_rules_attribute",
"nougat-proc_macros",
]
[[package]]
name = "nougat-proc_macros"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c84f77a45e99a2f9b492695d99e1c23844619caa5f3e57647cffacad773ca257"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.105",
]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.44" version = "0.1.44"
@@ -1205,7 +1223,7 @@ dependencies = [
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.105",
] ]
[[package]] [[package]]
@@ -1275,6 +1293,12 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "paste"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.1.0"
@@ -1316,18 +1340,18 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.36" version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [ dependencies = [
"unicode-xid", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.15" version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@@ -1464,9 +1488,9 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]] [[package]]
name = "rltk" name = "rltk"
version = "0.8.1" version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c83076f8174384edf79d3f4a91934b8c0e1feed6eed943608d54af790f0a7dd" checksum = "30587bc9361fc5600650d17ad6b9a91bb6a290f9d06a434dc48198d22ead7543"
dependencies = [ dependencies = [
"bracket-lib", "bracket-lib",
] ]
@@ -1536,31 +1560,32 @@ checksum = "a0eddf2e8f50ced781f288c19f18621fa72a3779e3cb58dbf23b07469b0abeb4"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.136" version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.136" version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.82",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.78" version = "1.0.141"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr",
"ryu", "ryu",
"serde", "serde",
] ]
@@ -1577,13 +1602,13 @@ dependencies = [
[[package]] [[package]]
name = "shred" name = "shred"
version = "0.10.2" version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f08237e667ac94ad20f8878b5943d91a93ccb231428446c57c21c57779016d" checksum = "dc6b2cd1ccb08cf2b25d75c936e0cc9c8cb93c39a83814956da32653236338c0"
dependencies = [ dependencies = [
"ahash",
"arrayvec", "arrayvec",
"hashbrown", "atomic_refcell",
"mopa",
"rayon", "rayon",
"smallvec", "smallvec",
"tynm", "tynm",
@@ -1637,14 +1662,15 @@ dependencies = [
[[package]] [[package]]
name = "specs" name = "specs"
version = "0.16.1" version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fff28a29366aff703d5da8a7e2c8875dc8453ac1118f842cbc0fa70c7db51240" checksum = "a60eabdfd5a80e458c3e7bcc9f1076d6ce3cc8ddb71d69691f00fc0de735a635"
dependencies = [ dependencies = [
"ahash",
"crossbeam-queue", "crossbeam-queue",
"hashbrown",
"hibitset", "hibitset",
"log", "log",
"nougat",
"rayon", "rayon",
"serde", "serde",
"shred", "shred",
@@ -1660,7 +1686,7 @@ checksum = "3e23e09360f3d2190fec4222cd9e19d3158d5da948c0d1ea362df617dd103511"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.105",
] ]
[[package]] [[package]]
@@ -1671,13 +1697,24 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.86" version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"unicode-xid", "unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
] ]
[[package]] [[package]]
@@ -1697,7 +1734,7 @@ checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.105",
] ]
[[package]] [[package]]
@@ -1739,9 +1776,9 @@ checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc"
[[package]] [[package]]
name = "tuple_utils" name = "tuple_utils"
version = "0.3.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44834418e2c5b16f47bedf35c28e148db099187dd5feee6367fb2525863af4f1" checksum = "cffaaf9392ef73cd30828797152476aaa2fa37a17856934fa63d4843f34290e9"
[[package]] [[package]]
name = "tynm" name = "tynm"
@@ -1762,10 +1799,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "unicode-xid" name = "unicode-ident"
version = "0.2.2" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
[[package]] [[package]]
name = "version_check" name = "version_check"
@@ -1811,7 +1848,7 @@ dependencies = [
"log", "log",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.105",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -1833,7 +1870,7 @@ checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.105",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]

View File

@@ -6,8 +6,8 @@ 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]
rltk = { version = "0.8.0", features = ["serde"] } rltk = { version = "0.8.7", features = ["serde"] }
specs = { version = "0.16.1", features = ["serde"] } specs = { version = "0.20.0", features = ["serde"] }
specs-derive = { version = "0.4.1" } specs-derive = { version = "0.4.1" }
serde = { version = "^1.0.44", features = ["derive"] } serde = { version = "^1.0.202", features = ["derive"] }
serde_json = "1.0.44" serde_json = "1.0.117"

3
renovate.json Normal file
View File

@@ -0,0 +1,3 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}

Binary file not shown.

1
savegame.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -11,6 +11,18 @@ pub struct Position {
pub y: i32, pub y: i32,
} }
impl Position {
pub fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
}
impl Default for Position {
fn default() -> Self {
Self { x: 0, y: 0 }
}
}
#[derive(Component, ConvertSaveload, Clone)] #[derive(Component, ConvertSaveload, Clone)]
pub struct Renderable { pub struct Renderable {
pub glyph: rltk::FontCharType, pub glyph: rltk::FontCharType,
@@ -109,6 +121,24 @@ pub struct ProvidesHealing {
pub heal_amount: i32, pub heal_amount: i32,
} }
#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct Heals {
pub amount: Vec<i32>,
}
impl Heals {
pub fn new_healing(store: &mut WriteStorage<Heals>, target: Entity, amount: i32) {
if let Some(healing) = store.get_mut(target) {
healing.amount.push(amount);
} else {
let heal = Heals {
amount: vec![amount],
};
store.insert(target, heal).expect("Unable to insert heal");
}
}
}
#[derive(Component, Debug, Clone, ConvertSaveload)] #[derive(Component, Debug, Clone, ConvertSaveload)]
pub struct Ranged { pub struct Ranged {
pub range: i32, pub range: i32,
@@ -135,3 +165,78 @@ pub struct SerializeMe;
pub struct SerializationHelper { pub struct SerializationHelper {
pub map: super::map::Map, pub map: super::map::Map,
} }
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
pub enum EquipmentSlot {
Melee,
Shield,
Head,
Shoulder,
Chest,
Legs,
Hands,
Feet,
}
#[derive(Component, Copy, Clone, Serialize, Deserialize)]
pub struct Equippable {
pub slot: EquipmentSlot,
}
#[derive(Component, ConvertSaveload, Clone)]
pub struct Equipped {
pub owner: Entity,
pub slot: EquipmentSlot,
}
#[derive(Component, ConvertSaveload, Clone)]
pub struct MeleePowerBonus {
pub power: i32,
}
#[derive(Component, ConvertSaveload, Clone)]
pub struct DefenseBonus {
pub defense: i32,
}
#[derive(Component, ConvertSaveload, Clone)]
pub struct WantsToRemoveItem {
pub item: Entity,
}
#[derive(Component, Serialize, Deserialize, Clone)]
pub struct ParticleLifetime {
pub lifetime_ms: f32,
}
#[derive(PartialEq, Serialize, Deserialize, Clone, Copy)]
pub enum HungerState {
WellFed,
Normal,
Hungry,
Starving,
}
#[derive(Component, Serialize, Deserialize, Clone)]
pub struct HungerClock {
pub state: HungerState,
pub duration: i32,
}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct ProvidesFood {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct MagicMapper {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Hidden {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct EntryTrigger {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct EntityMoved {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct SingleActivation {}

View File

@@ -1,9 +1,8 @@
use crate::gamelog::GameLog;
use crate::Name;
use rltk::console;
use specs::prelude::*; use specs::prelude::*;
use crate::components::{CombatStats, Player, SufferDamage}; use crate::components::{CombatStats, Player, SufferDamage};
use crate::gamelog::GameLog;
use crate::{Map, Name, Position, RunState};
pub struct DamageSystem {} pub struct DamageSystem {}
@@ -11,13 +10,20 @@ impl<'a> System<'a> for DamageSystem {
type SystemData = ( type SystemData = (
WriteStorage<'a, CombatStats>, WriteStorage<'a, CombatStats>,
WriteStorage<'a, SufferDamage>, WriteStorage<'a, SufferDamage>,
ReadStorage<'a, Position>,
WriteExpect<'a, Map>,
Entities<'a>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
let (mut stats, mut damage) = data; let (mut stats, mut damage, positions, mut map, entities) = data;
for (mut stats, damage) in (&mut stats, &damage).join() { for (entity, mut stats, damage) in (&entities, &mut stats, &damage).join() {
stats.hp -= damage.amount.iter().sum::<i32>(); stats.hp -= damage.amount.iter().sum::<i32>();
if let Some(pos) = positions.get(entity) {
let idx = map.xy_idx(pos.x, pos.y);
map.bloodstains.insert(idx);
}
} }
damage.clear(); damage.clear();
@@ -44,7 +50,10 @@ pub fn delete_the_dead(ecs: &mut World) {
dead.push(entity) dead.push(entity)
} }
Some(_) => console::log("You are dead"), Some(_) => {
let mut runstate = ecs.write_resource::<RunState>();
*runstate = RunState::GameOver;
}
} }
} }
} }

View File

@@ -1,14 +1,15 @@
use rltk::{Point, VirtualKeyCode};
use rltk::RGB;
use rltk::Rltk; use rltk::Rltk;
use rltk::RGB;
use rltk::{Point, VirtualKeyCode};
use specs::prelude::*; use specs::prelude::*;
use crate::{CombatStats, InBackpack, RunState, State, Viewshed};
use crate::gamelog::GameLog; use crate::gamelog::GameLog;
use crate::Map; use crate::rex_assets::RexAssets;
use crate::Name;
use crate::Player; use crate::Player;
use crate::Position; use crate::Position;
use crate::{CombatStats, Hidden, InBackpack, RunState, State, Viewshed};
use crate::{Equipped, Map};
use crate::{HungerClock, HungerState, Name};
pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
ctx.draw_box( ctx.draw_box(
@@ -22,9 +23,10 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
let combat_stats = ecs.read_storage::<CombatStats>(); let combat_stats = ecs.read_storage::<CombatStats>();
let players = ecs.read_storage::<Player>(); let players = ecs.read_storage::<Player>();
let hunger = ecs.read_storage::<HungerClock>();
let log = ecs.fetch::<GameLog>(); let log = ecs.fetch::<GameLog>();
for (_player, stats) in (&players, &combat_stats).join() { for (_player, stats, hc) in (&players, &combat_stats, &hunger).join() {
let health = format!(" HP: {} / {}", stats.hp, stats.max_hp); let health = format!(" HP: {} / {}", stats.hp, stats.max_hp);
ctx.print_color( ctx.print_color(
12, 12,
@@ -43,6 +45,31 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
RGB::named(rltk::RED), RGB::named(rltk::RED),
RGB::named(rltk::BLACK), RGB::named(rltk::BLACK),
); );
match hc.state {
HungerState::WellFed => ctx.print_color(
71,
42,
RGB::named(rltk::GREEN),
RGB::named(rltk::BLACK),
"Well Fed",
),
HungerState::Normal => {}
HungerState::Hungry => ctx.print_color(
71,
42,
RGB::named(rltk::ORANGE),
RGB::named(rltk::BLACK),
"Hungry",
),
HungerState::Starving => ctx.print_color(
71,
42,
RGB::named(rltk::RED),
RGB::named(rltk::BLACK),
"Starving",
),
}
} }
let mut y = 44; let mut y = 44;
@@ -53,6 +80,16 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
y += 1; y += 1;
} }
let map = ecs.fetch::<Map>();
let depth = format!("Depth: {}", map.depth);
ctx.print_color(
2,
43,
RGB::named(rltk::YELLOW),
RGB::named(rltk::BLACK),
&depth,
);
// Draw mouse // Draw mouse
let mouse_pos = ctx.mouse_pos(); let mouse_pos = ctx.mouse_pos();
ctx.set_bg(mouse_pos.0, mouse_pos.1, RGB::named(rltk::MAGENTA)); ctx.set_bg(mouse_pos.0, mouse_pos.1, RGB::named(rltk::MAGENTA));
@@ -63,6 +100,7 @@ fn draw_tooltips(ecs: &World, ctx: &mut Rltk) {
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>();
let hidden = ecs.read_storage::<Hidden>();
let mouse_pos = ctx.mouse_pos(); let mouse_pos = ctx.mouse_pos();
if mouse_pos.0 >= map.width || mouse_pos.1 >= map.height { if mouse_pos.0 >= map.width || mouse_pos.1 >= map.height {
@@ -70,7 +108,7 @@ fn draw_tooltips(ecs: &World, ctx: &mut Rltk) {
} }
let mut tooltip: Vec<String> = Vec::new(); let mut tooltip: Vec<String> = Vec::new();
for (name, position) in (&names, &positions).join() { for (name, position, _hidden) in (&names, &positions, !&hidden).join() {
let idx = map.xy_idx(position.x, position.y); let idx = map.xy_idx(position.x, position.y);
if position.x == mouse_pos.0 && position.y == mouse_pos.1 && map.visible_tiles[idx] { if position.x == mouse_pos.0 && position.y == mouse_pos.1 && map.visible_tiles[idx] {
tooltip.push(name.name.to_string()); tooltip.push(name.name.to_string());
@@ -406,62 +444,86 @@ pub fn main_menu(gs: &mut State, ctx: &mut Rltk) -> MainMenuResult {
let save_exists = super::save_load_system::does_save_exist(); let save_exists = super::save_load_system::does_save_exist();
let runstate = gs.ecs.fetch::<RunState>(); let runstate = gs.ecs.fetch::<RunState>();
let assets = gs.ecs.fetch::<RexAssets>();
ctx.render_xp_sprite(&assets.menu, 0, 0);
ctx.draw_box_double(
24,
18,
31,
10,
RGB::named(rltk::WHEAT),
RGB::named(rltk::BLACK),
);
ctx.print_color_centered( ctx.print_color_centered(
15, 20,
RGB::named(rltk::YELLOW), RGB::named(rltk::YELLOW),
RGB::named(rltk::BLACK), RGB::named(rltk::BLACK),
"Rust Roguelike Tutorial", "Rust Roguelike Tutorial",
); );
ctx.print_color_centered(
21,
RGB::named(rltk::CYAN),
RGB::named(rltk::BLACK),
"by Herbert Wolverson",
);
ctx.print_color_centered(
22,
RGB::named(rltk::GRAY),
RGB::named(rltk::BLACK),
"Use Up/Down Arrows and Enter",
);
let mut y = 24;
if let RunState::MainMenu { if let RunState::MainMenu {
menu_selection: selection, menu_selection: selection,
} = *runstate } = *runstate
{ {
if selection == MainMenuSelection::NewGame { if selection == MainMenuSelection::NewGame {
ctx.print_color_centered( ctx.print_color_centered(
24, y,
RGB::named(rltk::MAGENTA), RGB::named(rltk::MAGENTA),
RGB::named(rltk::BLACK), RGB::named(rltk::BLACK),
"Begin New Game", "Begin New Game",
); );
} else { } else {
ctx.print_color_centered( ctx.print_color_centered(
24, y,
RGB::named(rltk::WHITE), RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK), RGB::named(rltk::BLACK),
"Begin New Game", "Begin New Game",
); );
} }
y += 1;
if save_exists { if save_exists {
if selection == MainMenuSelection::LoadGame { if selection == MainMenuSelection::LoadGame {
ctx.print_color_centered( ctx.print_color_centered(
25, y,
RGB::named(rltk::MAGENTA), RGB::named(rltk::MAGENTA),
RGB::named(rltk::BLACK), RGB::named(rltk::BLACK),
"Load Game", "Load Game",
); );
} else { } else {
ctx.print_color_centered( ctx.print_color_centered(
25, y,
RGB::named(rltk::WHITE), RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK), RGB::named(rltk::BLACK),
"Load Game", "Load Game",
); );
} }
y += 1;
} }
if selection == MainMenuSelection::Quit { if selection == MainMenuSelection::Quit {
ctx.print_color_centered( ctx.print_color_centered(
26, y,
RGB::named(rltk::MAGENTA), RGB::named(rltk::MAGENTA),
RGB::named(rltk::BLACK), RGB::named(rltk::BLACK),
"Quit", "Quit",
); );
} else { } else {
ctx.print_color_centered(26, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "Quit"); ctx.print_color_centered(y, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "Quit");
} }
return match ctx.key { return match ctx.key {
@@ -506,3 +568,129 @@ pub fn main_menu(gs: &mut State, ctx: &mut Rltk) -> MainMenuResult {
selected: MainMenuSelection::NewGame, selected: MainMenuSelection::NewGame,
} }
} }
pub fn remove_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option<Entity>) {
let player_entity = gs.ecs.fetch::<Entity>();
let names = gs.ecs.read_storage::<Name>();
let backpack = gs.ecs.read_storage::<Equipped>();
let entities = gs.ecs.entities();
let inventory = (&backpack, &names)
.join()
.filter(|item| item.0.owner == *player_entity);
let count = inventory.count();
let mut y = (25 - (count / 2)) as i32;
ctx.draw_box(
15,
y - 2,
31,
(count + 3) as i32,
RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK),
);
ctx.print_color(
18,
y - 2,
RGB::named(rltk::YELLOW),
RGB::named(rltk::BLACK),
"Remove Which Item?",
);
ctx.print_color(
18,
y + count as i32 + 1,
RGB::named(rltk::YELLOW),
RGB::named(rltk::BLACK),
"ESCAPE to cancel",
);
let mut equippable: Vec<Entity> = Vec::new();
let mut j = 0;
for (entity, _pack, name) in (&entities, &backpack, &names)
.join()
.filter(|item| item.1.owner == *player_entity)
{
ctx.set(
17,
y,
RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK),
rltk::to_cp437('('),
);
ctx.set(
18,
y,
RGB::named(rltk::YELLOW),
RGB::named(rltk::BLACK),
97 + j as rltk::FontCharType,
);
ctx.set(
19,
y,
RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK),
rltk::to_cp437(')'),
);
ctx.print(21, y, &name.name.to_string());
equippable.push(entity);
y += 1;
j += 1;
}
match ctx.key {
None => (ItemMenuResult::NoResponse, None),
Some(key) => match key {
VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None),
_ => {
let selection = rltk::letter_to_option(key);
if selection > -1 && selection < count as i32 {
return (
ItemMenuResult::Selected,
Some(equippable[selection as usize]),
);
}
(ItemMenuResult::NoResponse, None)
}
},
}
}
#[derive(PartialEq, Copy, Clone)]
pub enum GameOverResult {
NoSelection,
QuitToMenu,
}
pub fn game_over(ctx: &mut Rltk) -> GameOverResult {
ctx.print_color_centered(
15,
RGB::named(rltk::YELLOW),
RGB::named(rltk::BLACK),
"Your journey has ended!",
);
ctx.print_color_centered(
17,
RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK),
"One day, we'll tell you all about how you did.",
);
ctx.print_color_centered(
18,
RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK),
"That day, sadly, is not in this chapter..",
);
ctx.print_color_centered(
20,
RGB::named(rltk::MAGENTA),
RGB::named(rltk::BLACK),
"Press any key to return to the menu.",
);
match ctx.key {
None => GameOverResult::NoSelection,
Some(_) => GameOverResult::QuitToMenu,
}
}

19
src/healing_system.rs Normal file
View File

@@ -0,0 +1,19 @@
use specs::prelude::*;
use crate::{CombatStats, Heals};
pub struct HealingSystem {}
impl<'a> System<'a> for HealingSystem {
type SystemData = (WriteStorage<'a, Heals>, WriteStorage<'a, CombatStats>);
fn run(&mut self, data: Self::SystemData) {
let (mut heals, mut stats) = data;
for (heal, mut stats) in (&heals, &mut stats).join() {
stats.hp = i32::min(stats.max_hp, stats.hp + heal.amount.iter().sum::<i32>());
}
heals.clear();
}
}

91
src/hunger_system.rs Normal file
View File

@@ -0,0 +1,91 @@
use specs::prelude::*;
use crate::{GameLog, Heals, HungerClock, HungerState, RunState, SufferDamage};
pub struct HungerSystem {}
impl<'a> System<'a> for HungerSystem {
type SystemData = (
Entities<'a>,
WriteStorage<'a, HungerClock>,
ReadExpect<'a, Entity>,
ReadExpect<'a, RunState>,
WriteStorage<'a, SufferDamage>,
WriteExpect<'a, GameLog>,
WriteStorage<'a, Heals>,
);
fn run(&mut self, data: Self::SystemData) {
let (
entities,
mut hunger_clock,
player_entity,
run_state,
mut suffer_damage,
mut game_log,
_heals,
) = data;
for (entity, mut clock) in (&entities, &mut hunger_clock).join() {
let mut proceed = false;
match *run_state {
RunState::PlayerTurn => {
if entity == *player_entity {
proceed = true;
}
}
RunState::MonsterTurn => {
if entity != *player_entity {
proceed = true;
}
}
_ => {
proceed = false;
}
}
if !proceed {
continue;
}
clock.duration -= 1;
if clock.duration > 0 {
continue;
}
match clock.state {
HungerState::WellFed => {
clock.state = HungerState::Normal;
clock.duration = 200;
if entity == *player_entity {
game_log
.entries
.push("You are no longer well-feed".to_string())
}
}
HungerState::Normal => {
clock.state = HungerState::Hungry;
clock.duration = 200;
if entity == *player_entity {
game_log.entries.push("You are hungry".to_string())
}
}
HungerState::Hungry => {
clock.state = HungerState::Starving;
clock.duration = 200;
if entity == *player_entity {
game_log.entries.push("You are Starving!".to_string())
}
}
HungerState::Starving => {
if entity == *player_entity {
game_log
.entries
.push("Your hunger pangs are getting painful".to_string())
}
SufferDamage::new_damage(&mut suffer_damage, entity, 1);
}
}
}
}
}

View File

@@ -1,15 +1,18 @@
use specs::prelude::*; use specs::prelude::*;
use crate::gamelog::GameLog; use crate::gamelog::GameLog;
use crate::particle_system::ParticleBuilder;
use crate::{ use crate::{
AreaOfEffect, CombatStats, Confusion, Consumable, InBackpack, InflictsDamage, Map, Name, AreaOfEffect, CombatStats, Confusion, Consumable, Equippable, Equipped, HungerClock,
Position, ProvidesHealing, Ranged, SufferDamage, WantsToDropItem, WantsToPickupItem, HungerState, InBackpack, InflictsDamage, MagicMapper, Map, Name, Position, ProvidesFood,
WantsToUseItem, ProvidesHealing, Ranged, RunState, SufferDamage, WantsToDropItem, WantsToPickupItem,
WantsToRemoveItem, WantsToUseItem,
}; };
pub struct ItemCollectionSystem {} pub struct ItemCollectionSystem {}
impl<'a> System<'a> for ItemCollectionSystem { impl<'a> System<'a> for ItemCollectionSystem {
#[allow(clippy::complexity)]
type SystemData = ( type SystemData = (
ReadExpect<'a, Entity>, ReadExpect<'a, Entity>,
WriteExpect<'a, GameLog>, WriteExpect<'a, GameLog>,
@@ -49,6 +52,7 @@ impl<'a> System<'a> for ItemCollectionSystem {
pub struct ItemUseSystem {} pub struct ItemUseSystem {}
impl<'a> System<'a> for ItemUseSystem { impl<'a> System<'a> for ItemUseSystem {
#[allow(clippy::complexity)]
type SystemData = ( type SystemData = (
ReadExpect<'a, Entity>, ReadExpect<'a, Entity>,
WriteExpect<'a, GameLog>, WriteExpect<'a, GameLog>,
@@ -60,10 +64,19 @@ impl<'a> System<'a> for ItemUseSystem {
ReadStorage<'a, ProvidesHealing>, ReadStorage<'a, ProvidesHealing>,
ReadStorage<'a, InflictsDamage>, ReadStorage<'a, InflictsDamage>,
ReadStorage<'a, Ranged>, ReadStorage<'a, Ranged>,
ReadExpect<'a, Map>, WriteExpect<'a, Map>,
WriteStorage<'a, SufferDamage>, WriteStorage<'a, SufferDamage>,
ReadStorage<'a, AreaOfEffect>, ReadStorage<'a, AreaOfEffect>,
WriteStorage<'a, Confusion>, WriteStorage<'a, Confusion>,
ReadStorage<'a, Equippable>,
WriteStorage<'a, Equipped>,
WriteStorage<'a, InBackpack>,
WriteExpect<'a, ParticleBuilder>,
ReadStorage<'a, Position>,
ReadStorage<'a, ProvidesFood>,
WriteStorage<'a, HungerClock>,
ReadStorage<'a, MagicMapper>,
WriteExpect<'a, RunState>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
@@ -77,11 +90,20 @@ impl<'a> System<'a> for ItemUseSystem {
mut combat_stats, mut combat_stats,
healing, healing,
inflicts_damage, inflicts_damage,
ranged, _ranged,
map, map,
mut suffer_damage, mut suffer_damage,
aoe, aoe,
mut confused, mut confused,
equippable,
mut equipped,
mut backpack,
mut particle_builder,
positions,
provides_food,
mut hunger_clocks,
magic_mapper,
mut run_state,
) = data; ) = data;
for (entity, use_item) in (&entities, &wants_use).join() { for (entity, use_item) in (&entities, &wants_use).join() {
@@ -110,12 +132,79 @@ impl<'a> System<'a> for ItemUseSystem {
for mob in map.tile_content[idx].iter() { for mob in map.tile_content[idx].iter() {
targets.push(*mob); targets.push(*mob);
} }
particle_builder.request(
tile_idx.x,
tile_idx.y,
rltk::RGB::named(rltk::ORANGE),
rltk::RGB::named(rltk::BLACK),
rltk::to_cp437('░'),
200.,
);
} }
} }
} }
} }
} }
if let Some(_mapper) = magic_mapper.get(use_item.item) {
used_item = true;
game_log
.entries
.push("The map is revealed to you!".to_string());
*run_state = RunState::MagicMapReveal { row: 0 };
}
if let Some(_item_edible) = provides_food.get(use_item.item) {
used_item = true;
let target = targets[0];
if let Some(hc) = hunger_clocks.get_mut(target) {
hc.state = HungerState::WellFed;
hc.duration = 20;
game_log.entries.push(format!(
"You eat the {}.",
names.get(use_item.item).unwrap().name
));
}
}
if let Some(item_equippable) = equippable.get(use_item.item) {
let target_slot = item_equippable.slot;
let target = targets[0];
let mut to_unequip: Vec<Entity> = Vec::new();
for (item_entity, already_equipped, name) in (&entities, &equipped, &names).join() {
if already_equipped.owner == target && already_equipped.slot == target_slot {
to_unequip.push(item_entity);
if target == *player_entity {
game_log.entries.push(format!("You unequip {}.", name.name));
}
}
}
for item in to_unequip.iter() {
equipped.remove(*item);
backpack
.insert(*item, InBackpack { owner: target })
.expect("Unable to insert item into backpack");
}
equipped
.insert(
use_item.item,
Equipped {
owner: target,
slot: target_slot,
},
)
.expect("Unable to equip item");
backpack.remove(use_item.item);
if target == *player_entity {
game_log.entries.push(format!(
"You equip item {}.",
names.get(use_item.item).unwrap().name
))
}
}
if let Some(item_damages) = inflicts_damage.get(use_item.item) { if let Some(item_damages) = inflicts_damage.get(use_item.item) {
used_item = false; used_item = false;
for mob in targets.iter() { for mob in targets.iter() {
@@ -127,6 +216,17 @@ impl<'a> System<'a> for ItemUseSystem {
"You use {} on {}, inflicting {} hp.", "You use {} on {}, inflicting {} hp.",
item_name.name, mob_name.name, item_damages.damage item_name.name, mob_name.name, item_damages.damage
)); ));
if let Some(pos) = positions.get(*mob) {
particle_builder.request(
pos.x,
pos.y,
rltk::RGB::named(rltk::RED),
rltk::RGB::named(rltk::BLACK),
rltk::to_cp437('‼'),
200.,
);
}
} }
used_item = true; used_item = true;
@@ -147,6 +247,16 @@ impl<'a> System<'a> for ItemUseSystem {
)); ));
} }
used_item = true; used_item = true;
if let Some(pos) = positions.get(*target) {
particle_builder.request(
pos.x,
pos.y,
rltk::RGB::named(rltk::GREEN),
rltk::RGB::named(rltk::BLACK),
rltk::to_cp437('♥'),
200.,
);
}
} }
} }
} }
@@ -164,6 +274,17 @@ impl<'a> System<'a> for ItemUseSystem {
"You use {} on {}, confusing them.", "You use {} on {}, confusing them.",
item_name.name, mob_name.name item_name.name, mob_name.name
)); ));
if let Some(pos) = positions.get(*mob) {
particle_builder.request(
pos.x,
pos.y,
rltk::RGB::named(rltk::MAGENTA),
rltk::RGB::named(rltk::BLACK),
rltk::to_cp437('?'),
200.,
);
}
} }
} }
} }
@@ -242,3 +363,26 @@ impl<'a> System<'a> for ItemDropSystem {
wants_drop.clear(); wants_drop.clear();
} }
} }
pub struct ItemRemoveSystem {}
impl<'a> System<'a> for ItemRemoveSystem {
type SystemData = (
Entities<'a>,
WriteStorage<'a, WantsToRemoveItem>,
WriteStorage<'a, Equipped>,
WriteStorage<'a, InBackpack>,
);
fn run(&mut self, data: Self::SystemData) {
let (entities, mut wants_to_remove_item, mut equipped, mut backpack) = data;
for (entity, to_remove) in (&entities, &wants_to_remove_item).join() {
equipped.remove(to_remove.item);
backpack
.insert(to_remove.item, InBackpack { owner: entity })
.expect("Unable to insert item into backpack");
}
wants_to_remove_item.clear();
}
}

View File

@@ -13,223 +13,41 @@ use monster_ai_system::*;
use player::*; use player::*;
use visibility_system::*; use visibility_system::*;
use crate::inventory_system::{ItemCollectionSystem, ItemDropSystem, ItemUseSystem}; use crate::gamelog::GameLog;
use crate::gui::MainMenuSelection;
use crate::healing_system::HealingSystem;
use crate::hunger_system::HungerSystem;
use crate::inventory_system::{
ItemCollectionSystem, ItemDropSystem, ItemRemoveSystem, ItemUseSystem,
};
use crate::particle_system::ParticleSpawnSystem;
use crate::state::{RunState, State};
use crate::trigger_system::TriggerSystem;
mod components; mod components;
mod damage_system; mod damage_system;
mod gamelog; mod gamelog;
mod gui; mod gui;
mod healing_system;
mod hunger_system;
mod inventory_system; mod inventory_system;
mod map; mod map;
mod map_builders;
mod map_indexing_system; mod map_indexing_system;
mod melee_combat_system; mod melee_combat_system;
mod monster_ai_system; mod monster_ai_system;
mod particle_system;
mod player; mod player;
mod random_table;
mod rect; mod rect;
mod spawner; mod rex_assets;
mod visibility_system;
mod save_load_system; mod save_load_system;
mod spawner;
mod state;
mod trigger_system;
mod visibility_system;
#[derive(PartialEq, Copy, Clone)] const SHOW_MAPGEN_VISUALIZER: bool = true;
pub enum RunState {
AwaitingInput,
PreRun,
PlayerTurn,
MonsterTurn,
ShowInventory,
ShowDropItem,
ShowTargeting {
range: i32,
item: Entity,
},
MainMenu {
menu_selection: gui::MainMenuSelection,
},
SaveGame,
}
pub struct State {
pub ecs: World,
}
impl State {
fn run_systems(&mut self) {
let mut vis = VisibilitySystem {};
vis.run_now(&self.ecs);
let mut mob = MonsterAI {};
mob.run_now(&self.ecs);
let mut map_index = MapIndexingSystem {};
map_index.run_now(&self.ecs);
let mut melee_system = MeleeCombatSystem {};
melee_system.run_now(&self.ecs);
let mut damage_system = DamageSystem {};
damage_system.run_now(&self.ecs);
let mut inventory = ItemCollectionSystem {};
inventory.run_now(&self.ecs);
let mut potions = ItemUseSystem {};
potions.run_now(&self.ecs);
let mut drop_items = ItemDropSystem {};
drop_items.run_now(&self.ecs);
self.ecs.maintain();
}
}
impl GameState for State {
fn tick(&mut self, ctx: &mut rltk::Rltk) {
let mut new_run_state;
{
let run_state = self.ecs.fetch::<RunState>();
new_run_state = *run_state;
}
ctx.cls();
match new_run_state {
RunState::MainMenu { .. } => {}
_ => {
draw_map(&self.ecs, ctx);
let positions = self.ecs.read_storage::<Position>();
let renderables = self.ecs.read_storage::<Renderable>();
let map = self.ecs.fetch::<Map>();
let mut data = (&positions, &renderables).join().collect::<Vec<_>>();
data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order));
for (pos, render) in data.iter() {
let idx = map.xy_idx(pos.x, pos.y);
if map.visible_tiles[idx] {
ctx.set(pos.x, pos.y, render.fg, render.bg, render.glyph)
}
}
gui::draw_ui(&self.ecs, ctx);
}
}
match new_run_state {
RunState::PreRun => {
self.run_systems();
new_run_state = RunState::AwaitingInput;
}
RunState::AwaitingInput => new_run_state = player_input(self, ctx),
RunState::PlayerTurn => {
self.run_systems();
new_run_state = RunState::MonsterTurn;
}
RunState::MonsterTurn => {
self.run_systems();
new_run_state = RunState::AwaitingInput;
}
RunState::ShowInventory => {
let result = gui::show_inventory(self, ctx);
match result.0 {
gui::ItemMenuResult::Cancel => new_run_state = RunState::AwaitingInput,
gui::ItemMenuResult::NoResponse => {}
gui::ItemMenuResult::Selected => {
let item_entity = result.1.unwrap();
let is_ranged = self.ecs.read_storage::<Ranged>();
let is_item_ranged = is_ranged.get(item_entity);
if let Some(is_item_ranged) = is_item_ranged {
new_run_state = RunState::ShowTargeting {
range: is_item_ranged.range,
item: item_entity,
}
} else {
let mut intent = self.ecs.write_storage::<WantsToUseItem>();
intent
.insert(
*self.ecs.fetch::<Entity>(),
WantsToUseItem {
item: item_entity,
target: None,
},
)
.expect("Unable to insert intent");
new_run_state = RunState::PlayerTurn;
}
}
}
}
RunState::ShowDropItem => {
let result = gui::drop_item_menu(self, ctx);
match result.0 {
gui::ItemMenuResult::Cancel => new_run_state = RunState::AwaitingInput,
gui::ItemMenuResult::NoResponse => {}
gui::ItemMenuResult::Selected => {
let item_entity = result.1.unwrap();
let mut intent = self.ecs.write_storage::<WantsToDropItem>();
intent
.insert(
*self.ecs.fetch::<Entity>(),
WantsToDropItem { item: item_entity },
)
.expect("Unable to insert intent");
new_run_state = RunState::PlayerTurn;
}
}
}
RunState::ShowTargeting { range, item } => {
let result = gui::ranged_target(self, ctx, range);
match result.0 {
gui::ItemMenuResult::Cancel => new_run_state = RunState::AwaitingInput,
gui::ItemMenuResult::NoResponse => {}
gui::ItemMenuResult::Selected => {
let mut intent = self.ecs.write_storage::<WantsToUseItem>();
intent
.insert(
*self.ecs.fetch::<Entity>(),
WantsToUseItem {
item,
target: result.1,
},
)
.expect("Unable to insert intent");
new_run_state = RunState::PlayerTurn;
}
}
}
RunState::MainMenu { .. } => {
let result = gui::main_menu(self, ctx);
match result {
gui::MainMenuResult::NoSelection { selected } => {
new_run_state = RunState::MainMenu {
menu_selection: selected,
}
}
gui::MainMenuResult::Selected { selected } => match selected {
gui::MainMenuSelection::NewGame => new_run_state = RunState::PreRun,
gui::MainMenuSelection::LoadGame => {
save_load_system::load_game(&mut self.ecs);
new_run_state = RunState::AwaitingInput;
save_load_system::delete_save();
}
gui::MainMenuSelection::Quit => ::std::process::exit(0),
},
}
}
RunState::SaveGame => {
save_load_system::save_game(&mut self.ecs);
new_run_state = RunState::MainMenu {
menu_selection: gui::MainMenuSelection::Quit,
};
}
}
{
let mut run_writer = self.ecs.write_resource::<RunState>();
*run_writer = new_run_state;
}
damage_system::delete_the_dead(&mut self.ecs);
}
}
fn main() -> rltk::BError { fn main() -> rltk::BError {
use rltk::RltkBuilder; use rltk::RltkBuilder;
@@ -239,9 +57,7 @@ fn main() -> rltk::BError {
.with_dimensions(80 * 3, 50 * 3) .with_dimensions(80 * 3, 50 * 3)
.build()?; .build()?;
context.with_post_scanlines(true); context.with_post_scanlines(true);
let mut gs = State { ecs: World::new() }; let mut gs = State::new();
gs.ecs.insert(rltk::RandomNumberGenerator::new());
gs.ecs.register::<Position>(); gs.ecs.register::<Position>();
gs.ecs.register::<Renderable>(); gs.ecs.register::<Renderable>();
@@ -266,27 +82,36 @@ fn main() -> rltk::BError {
gs.ecs.register::<Confusion>(); gs.ecs.register::<Confusion>();
gs.ecs.register::<SimpleMarker<SerializeMe>>(); gs.ecs.register::<SimpleMarker<SerializeMe>>();
gs.ecs.register::<SerializationHelper>(); gs.ecs.register::<SerializationHelper>();
gs.ecs.register::<Equippable>();
gs.ecs.register::<Equipped>();
gs.ecs.register::<MeleePowerBonus>();
gs.ecs.register::<DefenseBonus>();
gs.ecs.register::<WantsToRemoveItem>();
gs.ecs.register::<ParticleLifetime>();
gs.ecs.register::<HungerClock>();
gs.ecs.register::<Heals>();
gs.ecs.register::<ProvidesFood>();
gs.ecs.register::<MagicMapper>();
gs.ecs.register::<Hidden>();
gs.ecs.register::<EntryTrigger>();
gs.ecs.register::<EntityMoved>();
gs.ecs.register::<SingleActivation>();
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new()); gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());
let map = Map::new_map_rooms_and_corridors(); gs.ecs.insert(Map::new(1));
let (player_x, player_y) = map.rooms[0].center(); gs.ecs.insert(Point::new(0, 0));
gs.ecs.insert(rltk::RandomNumberGenerator::new());
let player_entity = spawner::player(&mut gs.ecs, player_x, player_y); let player_entity = spawner::player(&mut gs.ecs, 0, 0);
for room in map.rooms.iter().skip(1) {
spawner::spawn_room(&mut gs.ecs, room);
}
gs.ecs.insert(map);
gs.ecs.insert(Point::new(player_x, player_y));
gs.ecs.insert(player_entity); gs.ecs.insert(player_entity);
gs.ecs.insert(RunState::MainMenu { gs.ecs.insert(RunState::MapGeneration {});
menu_selection: gui::MainMenuSelection::NewGame,
});
gs.ecs.insert(gamelog::GameLog { gs.ecs.insert(gamelog::GameLog {
entries: vec!["Welcome to Rusty Roguelike".to_string()], entries: vec!["Welcome to Rusty Roguelike".to_string()],
}); });
gs.ecs.insert(particle_system::ParticleBuilder::new());
gs.ecs.insert(rex_assets::RexAssets::new());
gs.generate_world_map(1);
rltk::main_loop(context, gs) rltk::main_loop(context, gs)
} }

View File

@@ -1,32 +1,31 @@
use std::cmp::{max, min}; use std::collections::HashSet;
use rltk::{Algorithm2D, BaseMap, Point, RandomNumberGenerator, RGB, Rltk}; use rltk::{Algorithm2D, BaseMap, FontCharType, Point, Rltk, RGB};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::prelude::*; use specs::prelude::*;
use crate::rect::Rect;
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)] #[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
pub enum TileType { pub enum TileType {
Wall, Wall,
Floor, Floor,
DownStairs,
} }
pub const MAP_WIDTH: usize = 80; pub const MAP_WIDTH: usize = 80;
pub const MAP_HEIGHT: usize = 43; pub const MAP_HEIGHT: usize = 43;
pub const MAP_COUNT: usize = MAP_HEIGHT * MAP_WIDTH; pub const MAP_COUNT: usize = MAP_HEIGHT * MAP_WIDTH;
pub const MAX_MONSTER: i32 = 4; pub const MAX_MONSTER: i32 = 4;
pub const MAX_ITEMS: i32 = 2;
#[derive(Default, Serialize, Deserialize, Clone)] #[derive(Default, Serialize, Deserialize, Clone)]
pub struct Map { pub struct Map {
pub tiles: Vec<TileType>, pub tiles: Vec<TileType>,
pub rooms: Vec<Rect>,
pub width: i32, pub width: i32,
pub height: i32, pub height: i32,
pub revealed_tiles: Vec<bool>, pub revealed_tiles: Vec<bool>,
pub visible_tiles: Vec<bool>, pub visible_tiles: Vec<bool>,
pub blocked: Vec<bool>, pub blocked: Vec<bool>,
pub depth: i32,
pub bloodstains: HashSet<usize>,
#[serde(skip_serializing)] #[serde(skip_serializing)]
#[serde(skip_deserializing)] #[serde(skip_deserializing)]
@@ -38,34 +37,6 @@ impl Map {
(y as usize * self.width as usize) + x as usize (y as usize * self.width as usize) + x as usize
} }
fn apply_room_to_map(&mut self, room: &Rect) {
for y in room.y1 + 1..=room.y2 {
for x in room.x1 + 1..=room.x2 {
let idx = self.xy_idx(x, y);
self.tiles[idx] = TileType::Floor;
}
}
}
fn apply_horizontal_tunnel(&mut self, x1: i32, x2: i32, y: i32) {
for x in min(x1, x2)..=max(x1, x2) {
let idx = self.xy_idx(x, y);
if idx > 0 && idx < self.width as usize * self.height as usize {
self.tiles[idx as usize] = TileType::Floor;
}
}
}
fn apply_vertical_tunnel(&mut self, y1: i32, y2: i32, x: i32) {
for y in min(y1, y2)..=max(y1, y2) {
let idx = self.xy_idx(x, y);
if idx > 0 && idx < self.width as usize * self.height as usize {
self.tiles[idx as usize] = TileType::Floor;
}
}
}
fn is_exit_valid(&self, x: i32, y: i32) -> bool { fn is_exit_valid(&self, x: i32, y: i32) -> bool {
if x < 1 || x > self.width - 1 || y < 1 || y > self.height - 1 { if x < 1 || x > self.width - 1 || y < 1 || y > self.height - 1 {
return false; return false;
@@ -86,56 +57,18 @@ impl Map {
} }
} }
pub fn new_map_rooms_and_corridors() -> Map { pub fn new(new_depth: i32) -> Map {
let mut map = Map { Map {
tiles: vec![TileType::Wall; MAP_COUNT], tiles: vec![TileType::Wall; MAP_COUNT],
rooms: Vec::new(),
width: MAP_WIDTH as i32, width: MAP_WIDTH as i32,
height: MAP_HEIGHT as i32, height: MAP_HEIGHT as i32,
revealed_tiles: vec![false; MAP_COUNT], revealed_tiles: vec![false; MAP_COUNT],
visible_tiles: vec![false; MAP_COUNT], visible_tiles: vec![false; MAP_COUNT],
blocked: vec![false; MAP_COUNT], blocked: vec![false; MAP_COUNT],
tile_content: vec![Vec::new(); MAP_COUNT], tile_content: vec![Vec::new(); MAP_COUNT],
}; depth: new_depth,
bloodstains: HashSet::new(),
const MAX_ROOMS: i32 = 30;
const MIN_SIZE: i32 = 6;
const MAX_SIZE: i32 = 10;
let mut rng = RandomNumberGenerator::new();
for _ in 0..MAX_ROOMS {
let w = rng.range(MIN_SIZE, MAX_SIZE);
let h = rng.range(MIN_SIZE, MAX_SIZE);
let x = rng.roll_dice(1, map.width - w - 1) - 1;
let y = rng.roll_dice(1, map.height - h - 1) - 1;
let new_room = Rect::new(x, y, w, h);
let mut ok = true;
for other_room in map.rooms.iter() {
if new_room.intersect(other_room) {
ok = false
};
} }
if ok {
map.apply_room_to_map(&new_room);
if !map.rooms.is_empty() {
let (new_x, new_y) = new_room.center();
let (prev_x, prev_y) = map.rooms[map.rooms.len() - 1].center();
if rng.range(0, 2) == 1 {
map.apply_horizontal_tunnel(prev_x, new_x, prev_y);
map.apply_vertical_tunnel(prev_y, new_y, new_x);
} else {
map.apply_vertical_tunnel(prev_y, new_y, prev_x);
map.apply_horizontal_tunnel(prev_x, new_x, new_y);
}
}
map.rooms.push(new_room);
}
}
map
} }
} }
@@ -193,15 +126,14 @@ impl Algorithm2D for Map {
} }
} }
pub fn draw_map(ecs: &World, ctx: &mut Rltk) { pub fn draw_map(map: &Map, ctx: &mut Rltk) {
let map = ecs.fetch::<Map>();
let mut y = 0; let mut y = 0;
let mut x = 0; let mut x = 0;
for (idx, tile) in map.tiles.iter().enumerate() { for (idx, tile) in map.tiles.iter().enumerate() {
if map.revealed_tiles[idx] { if map.revealed_tiles[idx] {
let glyph; let glyph;
let mut fg; let mut fg;
let mut bg = RGB::from_f32(0., 0., 0.);
match tile { match tile {
TileType::Floor => { TileType::Floor => {
fg = RGB::from_f32(0.0, 0.5, 0.5); fg = RGB::from_f32(0.0, 0.5, 0.5);
@@ -210,13 +142,22 @@ pub fn draw_map(ecs: &World, ctx: &mut Rltk) {
TileType::Wall => { TileType::Wall => {
fg = RGB::from_f32(0., 1.0, 0.); fg = RGB::from_f32(0., 1.0, 0.);
glyph = rltk::to_cp437('#'); glyph = wall_glyph(&*map, x, y);
} }
TileType::DownStairs => {
glyph = rltk::to_cp437('>');
fg = RGB::from_f32(0., 1., 1.);
}
}
if map.bloodstains.contains(&idx) {
bg = RGB::from_f32(0.71, 0., 0.);
} }
if !map.visible_tiles[idx] { if !map.visible_tiles[idx] {
fg = fg.to_greyscale() fg = fg.to_greyscale();
bg = RGB::from_f32(0., 0., 0.);
} }
ctx.set(x, y, fg, RGB::from_f32(0., 0., 0.), glyph); ctx.set(x, y, fg, bg, glyph);
} }
x += 1; x += 1;
@@ -226,3 +167,48 @@ pub fn draw_map(ecs: &World, ctx: &mut Rltk) {
} }
} }
} }
fn wall_glyph(map: &Map, x: i32, y: i32) -> FontCharType {
if x < 1 || x > map.width - 2 || y < 1 || y > map.height - 2_i32 {
return 35;
}
let mut mask: u8 = 0;
if is_revealed_and_wall(map, x, y - 1) {
mask += 1;
}
if is_revealed_and_wall(map, x, y + 1) {
mask += 2;
}
if is_revealed_and_wall(map, x - 1, y) {
mask += 4;
}
if is_revealed_and_wall(map, x + 1, y) {
mask += 8;
}
match mask {
0 => 9, // Pillar because we can't see neighbors
1 => 186, // Wall only to the north
2 => 186, // Wall only to the south
3 => 186, // Wall to the north and south
4 => 205, // Wall only to the west
5 => 188, // Wall to the north and west
6 => 187, // Wall to the south and west
7 => 185, // Wall to the north, south and west
8 => 205, // Wall only to the east
9 => 200, // Wall to the north and east
10 => 201, // Wall to the south and east
11 => 204, // Wall to the north, south and east
12 => 205, // Wall to the east and west
13 => 202, // Wall to the east, west, and south
14 => 203, // Wall to the east, west, and north
15 => 206, // ╬ Wall on all sides
_ => 35, // We missed one?
}
}
fn is_revealed_and_wall(map: &Map, x: i32, y: i32) -> bool {
let idx = map.xy_idx(x, y);
map.tiles[idx] == TileType::Wall && map.revealed_tiles[idx]
}

View File

@@ -0,0 +1,22 @@
use crate::{Map, TileType};
#[inline(always)]
pub fn draw_corridor(map: &mut Map, x1: i32, y1: i32, x2: i32, y2: i32) {
let mut x = x1;
let mut y = y1;
while x != x2 || y != y2 {
if x < x2 {
x += 1;
} else if x > x2 {
x -= 1;
} else if y < y2 {
y += 1;
} else if y > y2 {
y -= 1;
}
let idx = map.xy_idx(x, y);
map.tiles[idx] = TileType::Floor;
}
}

View File

@@ -0,0 +1,214 @@
use rltk::{Point, RandomNumberGenerator};
use crate::map_builders::common::apply_room_to_map;
use crate::map_builders::{bsp, MapBuilder};
use crate::rect::Rect;
use crate::{spawner, Map, Position, TileType, World, SHOW_MAPGEN_VISUALIZER};
pub struct BspDungeonBuilder {
map: Map,
starting_position: Position,
depth: i32,
rooms: Vec<Rect>,
history: Vec<Map>,
rects: Vec<Rect>,
}
impl BspDungeonBuilder {
pub fn new(new_depth: i32) -> Self {
Self {
starting_position: Position { x: 0, y: 0 },
depth: new_depth,
rects: Vec::new(),
map: Map::new(new_depth),
history: Vec::new(),
rooms: Vec::new(),
}
}
fn build(&mut self) {
let mut rng = RandomNumberGenerator::new();
self.rects.clear();
self.place_rooms(&mut rng);
self.place_corridors(&mut rng);
self.place_stairs();
self.place_start();
}
fn place_rooms(&mut self, rng: &mut RandomNumberGenerator) {
self.rects
.push(Rect::new(2, 2, self.map.width - 5, self.map.height - 5));
let first_room = self.rects[0];
self.add_subrects(first_room);
let mut n_rooms = 0;
while n_rooms < 240 {
let rect = self.get_random_rect(rng);
let candidate = self.get_random_sub_rect(rect, rng);
if self.is_possible(candidate) {
apply_room_to_map(&mut self.map, &candidate);
self.rooms.push(candidate);
self.add_subrects(rect);
self.take_snapshot();
}
n_rooms += 1;
}
self.rooms.sort_by(|a, b| a.x1.cmp(&b.x1));
}
fn place_corridors(&mut self, rng: &mut RandomNumberGenerator) {
for i in 0..self.rooms.len() - 1 {
let room = self.rooms[i];
let next_room = self.rooms[i + 1];
let start_x = room.x1 + (rng.roll_dice(1, i32::abs(room.x1 - room.x2)) - 1);
let start_y = room.y1 + (rng.roll_dice(1, i32::abs(room.y1 - room.y2)) - 1);
let end_x =
next_room.x1 + (rng.roll_dice(1, i32::abs(next_room.x1 - next_room.x2)) - 1);
let end_y =
next_room.y1 + (rng.roll_dice(1, i32::abs(next_room.y1 - next_room.y2)) - 1);
self.draw_corridor(start_x, start_y, end_x, end_y);
self.take_snapshot();
}
}
fn place_start(&mut self) {
let start = self.rooms[0].center();
self.starting_position = Position {
x: start.0,
y: start.1,
};
}
fn place_stairs(&mut self) {
let stairs = self.rooms[self.rooms.len() - 1].center();
let stairs_idx = self.map.xy_idx(stairs.0, stairs.1);
self.map.tiles[stairs_idx] = TileType::DownStairs;
}
fn add_subrects(&mut self, rect: Rect) {
let width = i32::abs(rect.x1 - rect.x2);
let height = i32::abs(rect.y1 - rect.y2);
let half_width = i32::max(width / 2, 1);
let half_height = i32::max(height / 2, 1);
self.rects
.push(Rect::new(rect.x1, rect.y1, half_width, half_height));
self.rects.push(Rect::new(
rect.x1,
rect.y1 + half_height,
half_width,
half_height,
));
self.rects.push(Rect::new(
rect.x1 + half_width,
rect.y1,
half_width,
half_height,
));
self.rects.push(Rect::new(
rect.x1 + half_width,
rect.y1 + half_height,
half_width,
half_height,
));
}
fn get_random_rect(&mut self, rng: &mut RandomNumberGenerator) -> Rect {
if self.rects.len() == 1 {
return self.rects[0];
}
let idx = (rng.roll_dice(1, self.rects.len() as i32) - 1) as usize;
self.rects[idx]
}
fn get_random_sub_rect(&self, rect: Rect, rng: &mut RandomNumberGenerator) -> Rect {
let mut result = rect;
let rect_width = i32::abs(rect.x1 - rect.x2);
let rect_height = i32::abs(rect.y1 - rect.y2);
let w = i32::max(3, rng.roll_dice(1, i32::min(rect_width, 10)) - 1) + 1;
let h = i32::max(3, rng.roll_dice(1, i32::min(rect_height, 10)) - 1) + 1;
result.x1 += rng.roll_dice(1, 6) - 1;
result.y1 += rng.roll_dice(1, 6) - 1;
result.x2 = result.x1 + w;
result.y2 = result.y1 + h;
result
}
fn is_possible(&self, rect: Rect) -> bool {
let mut expanded = rect;
expanded.x1 -= 2;
expanded.x2 += 2;
expanded.y1 -= 2;
expanded.y2 += 2;
let mut can_build = true;
for y in expanded.y1..=expanded.y2 {
for x in expanded.x1..=expanded.x2 {
if x > self.map.width - 2 {
can_build = false;
}
if y > self.map.height - 2 {
can_build = false;
}
if x < 1 {
can_build = false;
}
if y < 1 {
can_build = false;
}
if can_build {
let idx = self.map.xy_idx(x, y);
if self.map.tiles[idx] != TileType::Wall {
can_build = false;
}
}
}
}
can_build
}
fn draw_corridor(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) {
bsp::draw_corridor(&mut self.map, x1, y1, x2, y2);
}
}
impl MapBuilder for BspDungeonBuilder {
fn build_map(&mut self) {
self.build();
}
fn spawn_entities(&mut self, ecs: &mut World) {
for room in self.rooms.iter().skip(1) {
spawner::spawn_room(ecs, room, self.depth);
}
}
fn get_map(&self) -> Map {
self.map.clone()
}
fn get_starting_position(&self) -> Position {
self.starting_position.clone()
}
fn get_snapshot_history(&self) -> Vec<Map> {
self.history.clone()
}
fn take_snapshot(&mut self) {
if SHOW_MAPGEN_VISUALIZER {
let mut snapshot = self.map.clone();
for v in snapshot.revealed_tiles.iter_mut() {
*v = true;
}
self.history.push(snapshot);
}
}
}

View File

@@ -0,0 +1,158 @@
use crate::map_builders::common::reveal_all;
use crate::map_builders::{bsp, MapBuilder};
use crate::rect::Rect;
use crate::{spawner, Map, Position, TileType, World, SHOW_MAPGEN_VISUALIZER};
use rltk::RandomNumberGenerator;
const MIN_ROOM_SIZE: i32 = 8;
pub struct BspInteriorBuilder {
map: Map,
starting_position: Position,
depth: i32,
rooms: Vec<Rect>,
history: Vec<Map>,
rects: Vec<Rect>,
}
impl BspInteriorBuilder {
pub fn new(new_depth: i32) -> Self {
Self {
map: Map::new(new_depth),
rects: Vec::new(),
history: Vec::new(),
depth: new_depth,
rooms: Vec::new(),
starting_position: Position::default(),
}
}
fn build(&mut self) {
let mut rng = RandomNumberGenerator::new();
self.place_rooms(&mut rng);
self.place_corridors(&mut rng);
self.place_stairs();
self.place_start()
}
fn place_start(&mut self) {
let start = self.rooms[0].center();
self.starting_position = Position::new(start.0, start.1)
}
fn place_stairs(&mut self) {
let stairs = self.rooms[self.rooms.len() - 1].center();
let stairs_idx = self.map.xy_idx(stairs.0, stairs.1);
self.map.tiles[stairs_idx] = TileType::DownStairs;
}
fn place_corridors(&mut self, rng: &mut RandomNumberGenerator) {
for i in 0..self.rooms.len() - 1 {
let room = self.rooms[i];
let next_room = self.rooms[i + 1];
let start_x = room.x1 + (rng.roll_dice(1, i32::abs(room.x1 - room.x2)) - 1);
let start_y = room.y1 + (rng.roll_dice(1, i32::abs(room.y1 - room.y2)) - 1);
let end_x =
next_room.x1 + (rng.roll_dice(1, i32::abs(next_room.x1 - next_room.x2)) - 1);
let end_y =
next_room.y1 + (rng.roll_dice(1, i32::abs(next_room.y1 - next_room.y2)) - 1);
self.draw_corridor(start_x, start_y, end_x, end_y);
self.take_snapshot();
}
}
fn place_rooms(&mut self, mut rng: &mut RandomNumberGenerator) {
self.rects.clear();
self.rects
.push(Rect::new(1, 1, self.map.width - 2, self.map.height - 2));
let first_room = self.rects[0];
self.add_subrects(first_room, &mut rng);
let rooms = self.rects.clone();
for r in rooms.iter() {
let room = *r;
self.rooms.push(room);
for y in room.y1..room.y2 {
for x in room.x1..room.x2 {
let idx = self.map.xy_idx(x, y);
if idx > 0 && idx < ((self.map.width * self.map.height) - 1) as usize {
self.map.tiles[idx] = TileType::Floor;
}
}
}
self.take_snapshot();
}
}
fn add_subrects(&mut self, rect: Rect, rng: &mut RandomNumberGenerator) {
if !self.rects.is_empty() {
self.rects.remove(self.rects.len() - 1);
}
let width = rect.x2 - rect.x1;
let height = rect.y2 - rect.y1;
let half_width = width / 2;
let half_height = height / 2;
let split = rng.roll_dice(1, 4);
if split <= 2 {
let h1 = Rect::new(rect.x1, rect.y1, half_width - 1, height);
self.rects.push(h1);
if half_width > MIN_ROOM_SIZE {
self.add_subrects(h1, rng);
}
let h2 = Rect::new(rect.x1 + half_width, rect.y1, half_width, height);
self.rects.push(h2);
if half_width > MIN_ROOM_SIZE {
self.add_subrects(h2, rng);
}
} else {
let v1 = Rect::new(rect.x1, rect.y1, width, half_height - 1);
self.rects.push(v1);
if half_height > MIN_ROOM_SIZE {
self.add_subrects(v1, rng);
}
let v2 = Rect::new(rect.x1, rect.y1 + half_height, width, half_height);
self.rects.push(v2);
if half_height > MIN_ROOM_SIZE {
self.add_subrects(v2, rng);
}
}
}
fn draw_corridor(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) {
bsp::draw_corridor(&mut self.map, x1, y1, x2, y2);
}
}
impl MapBuilder for BspInteriorBuilder {
fn build_map(&mut self) {
self.build();
}
fn spawn_entities(&mut self, ecs: &mut World) {
for room in self.rooms.iter().skip(1) {
spawner::spawn_room(ecs, room, self.depth);
}
}
fn get_map(&self) -> Map {
self.map.clone()
}
fn get_starting_position(&self) -> Position {
self.starting_position.clone()
}
fn get_snapshot_history(&self) -> Vec<Map> {
self.history.clone()
}
fn take_snapshot(&mut self) {
if SHOW_MAPGEN_VISUALIZER {
self.history.push(reveal_all(&self.map));
}
}
}

View File

@@ -0,0 +1,168 @@
use std::collections::HashMap;
use std::process::exit;
use rltk::RandomNumberGenerator;
use crate::map_builders::{common, MapBuilder};
use crate::{Map, Position, World, SHOW_MAPGEN_VISUALIZER, TileType, spawner};
const MIN_ROOM_SIZE: i32 = 8;
pub struct CellularAutomataBuilder {
map: Map,
starting_position: Position,
depth: i32,
history: Vec<Map>,
noise_areas : HashMap<i32, Vec<usize>>
}
impl CellularAutomataBuilder {
pub fn new(new_depth: i32) -> Self {
Self {
history: Vec::new(),
map: Map::new(new_depth),
depth: new_depth,
starting_position: Position::default(),
noise_areas: HashMap::new()
}
}
fn build(&mut self) {
let mut rng = RandomNumberGenerator::new();
self.place_random_level(&mut rng);
self.take_snapshot();
self.process_noise();
let start_idx = self.place_start();
self.place_exit(start_idx);
self.take_snapshot();
self.place_spawn_areas(rng)
}
fn place_spawn_areas(&mut self, mut rng: RandomNumberGenerator) {
let mut noise = rltk::FastNoise::seeded(rng.roll_dice(1, 65536) as u64);
noise.set_noise_type(rltk::NoiseType::Cellular);
noise.set_frequency(0.08);
noise.set_cellular_distance_function(rltk::CellularDistanceFunction::Manhattan);
for y in 1..self.map.height - 1 {
for x in 1..self.map.width - 1 {
let idx = self.map.xy_idx(x, y);
if self.map.tiles[idx] == TileType::Floor {
let cell_value_f = noise.get_noise(x as f32, y as f32) * 10240.0;
let cell_value = cell_value_f as i32;
if self.noise_areas.contains_key(&cell_value) {
self.noise_areas.get_mut(&cell_value).unwrap().push(idx);
} else {
self.noise_areas.insert(cell_value, vec![idx]);
}
}
}
}
}
fn place_exit(&mut self, start_idx: usize) {
let map_starts: Vec<usize> = vec![start_idx];
let dijkstra_map = rltk::DijkstraMap::new(self.map.width, self.map.height, &map_starts, &self.map, 200.0);
let mut exit_tile = (0, 0.0f32);
for (i, tile) in self.map.tiles.iter_mut().enumerate() {
if *tile != TileType::Floor {
continue;
}
let distance_to_start = dijkstra_map.map[i];
if distance_to_start == f32::MAX {
*tile = TileType::Wall;
} else {
if distance_to_start > exit_tile.1 {
exit_tile.0 = i;
exit_tile.1 = distance_to_start;
}
}
}
self.take_snapshot();
self.map.tiles[exit_tile.0] = TileType::DownStairs;
}
fn place_start(&mut self) -> usize {
self.starting_position = Position::new(self.map.width / 2, self.map.height / 2);
let mut start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
while self.map.tiles[start_idx] != TileType::Floor {
self.starting_position.x -= 1;
start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
}
return start_idx;
}
fn process_noise(&mut self) {
for _i in 0..15 {
let mut new_tiles = self.map.tiles.clone();
for y in 1..self.map.height - 1 {
for x in 1..self.map.width - 1 {
let idx = self.map.xy_idx(x, y);
let mut neighbors = 0;
if self.map.tiles[idx - 1] == TileType::Wall { neighbors += 1; }
if self.map.tiles[idx + 1] == TileType::Wall { neighbors += 1; }
if self.map.tiles[idx - self.map.width as usize] == TileType::Wall { neighbors += 1; }
if self.map.tiles[idx + self.map.width as usize] == TileType::Wall { neighbors += 1; }
if self.map.tiles[idx - self.map.width as usize - 1] == TileType::Wall { neighbors += 1; }
if self.map.tiles[idx - self.map.width as usize + 1] == TileType::Wall { neighbors += 1; }
if self.map.tiles[idx + self.map.width as usize - 1] == TileType::Wall { neighbors += 1; }
if self.map.tiles[idx + self.map.width as usize + 1] == TileType::Wall { neighbors += 1; }
if neighbors > 4 || neighbors == 0 {
new_tiles[idx] = TileType::Wall;
} else {
new_tiles[idx] = TileType::Floor;
}
}
}
self.map.tiles = new_tiles.clone();
self.take_snapshot();
}
}
fn place_random_level(&mut self, rng: &mut RandomNumberGenerator) {
for y in 1..self.map.height - 1 {
for x in 1..self.map.width - 1 {
let roll = rng.roll_dice(1, 100);
let idx = self.map.xy_idx(x, y);
if roll > 55 {
self.map.tiles[idx] = TileType::Floor
} else {
self.map.tiles[idx] = TileType::Wall
}
}
}
}
}
impl MapBuilder for CellularAutomataBuilder {
fn build_map(&mut self) {
self.build();
}
fn spawn_entities(&mut self, ecs: &mut World) {
for area in self.noise_areas.iter() {
spawner::spawn_region(ecs, area.1, self.depth);
}
}
fn get_map(&self) -> Map {
self.map.clone()
}
fn get_starting_position(&self) -> Position {
self.starting_position.clone()
}
fn get_snapshot_history(&self) -> Vec<Map> {
self.history.clone()
}
fn take_snapshot(&mut self) {
if SHOW_MAPGEN_VISUALIZER {
self.history.push(common::reveal_all(&self.map))
}
}
}

View File

@@ -0,0 +1,90 @@
use std::cmp::{max, min};
use std::collections::HashMap;
use crate::rect::Rect;
use crate::{Map, TileType};
pub fn apply_room_to_map(map: &mut Map, room: &Rect) {
for y in room.y1 + 1..=room.y2 {
for x in room.x1 + 1..=room.x2 {
let idx = map.xy_idx(x, y);
map.tiles[idx] = TileType::Floor;
}
}
}
pub fn apply_horizontal_tunnel(map: &mut Map, x1: i32, x2: i32, y: i32) {
for x in min(x1, x2)..=max(x1, x2) {
let idx = map.xy_idx(x, y);
if idx > 0 && idx < map.width as usize * map.height as usize {
map.tiles[idx as usize] = TileType::Floor;
}
}
}
pub fn apply_vertical_tunnel(map: &mut Map, y1: i32, y2: i32, x: i32) {
for y in min(y1, y2)..=max(y1, y2) {
let idx = map.xy_idx(x, y);
if idx > 0 && idx < map.width as usize * map.height as usize {
map.tiles[idx as usize] = TileType::Floor;
}
}
}
pub fn reveal_all(map: &Map) -> Map {
let mut snapshot = map.clone();
for v in snapshot.revealed_tiles.iter_mut() {
*v = true;
}
snapshot
}
pub fn remove_unreachable_areas_returning_most_distant(map: &mut Map, start_idx: usize) -> usize {
map.populate_blocked();
let map_starts : Vec<usize> = vec![start_idx];
let dijkstra_map = rltk::DijkstraMap::new(map.width as usize, map.height as usize, &map_starts , map, 200.0);
let mut exit_tile = (0, 0.0f32);
for (i, tile) in map.tiles.iter_mut().enumerate() {
if *tile == TileType::Floor {
let distance_to_start = dijkstra_map.map[i];
// We can't get to this tile - so we'll make it a wall
if distance_to_start == std::f32::MAX {
*tile = TileType::Wall;
} else {
// If it is further away than our current exit candidate, move the exit
if distance_to_start > exit_tile.1 {
exit_tile.0 = i;
exit_tile.1 = distance_to_start;
}
}
}
}
exit_tile.0
}
pub fn generate_voronois_spawn_regions(map: &Map, rng: &mut rltk::RandomNumberGenerator) -> HashMap<i32, Vec<usize>> {
let mut noise_areas : HashMap<i32, Vec<usize>> = HashMap::new();
let mut noise = rltk::FastNoise::seeded(rng.roll_dice(1, 65536) as u64);
noise.set_noise_type(rltk::NoiseType::Cellular);
noise.set_frequency(0.08);
noise.set_cellular_distance_function(rltk::CellularDistanceFunction::Manhattan);
for y in 1 .. map.height-1 {
for x in 1 .. map.width-1 {
let idx = map.xy_idx(x, y);
if map.tiles[idx] == TileType::Floor {
let cell_value_f = noise.get_noise(x as f32, y as f32) * 10240.0;
let cell_value = cell_value_f as i32;
if noise_areas.contains_key(&cell_value) {
noise_areas.get_mut(&cell_value).unwrap().push(idx);
} else {
noise_areas.insert(cell_value, vec![idx]);
}
}
}
}
noise_areas
}

View File

@@ -0,0 +1,180 @@
use std::collections::HashMap;
use rltk::RandomNumberGenerator;
use crate::{Map, Position, SHOW_MAPGEN_VISUALIZER, TileType, World};
use crate::map_builders::common::{generate_voronois_spawn_regions, remove_unreachable_areas_returning_most_distant, reveal_all};
use crate::map_builders::MapBuilder;
use crate::spawner::spawn_region;
pub struct DrunkardsWalkBuilder {
map: Map,
starting_position: Position,
depth: i32,
history: Vec<Map>,
noise_areas: HashMap<i32, Vec<usize>>,
settings: DrunkardSettings,
}
#[derive(PartialEq, Copy, Clone)]
pub enum DrunkSpawnMode { StartingPoint, Random }
pub struct DrunkardSettings {
pub spawn_mode: DrunkSpawnMode,
pub drunken_lifetime: i32,
pub floor_percent: f32,
}
impl DrunkardsWalkBuilder {
pub fn new(new_depth: i32, settings: DrunkardSettings) -> Self {
Self {
depth: new_depth,
map: Map::new(new_depth),
history: Vec::new(),
noise_areas: HashMap::new(),
starting_position: Position::default(),
settings,
}
}
pub fn open_area(new_depth: i32) -> Self {
DrunkardsWalkBuilder::new(
new_depth,
DrunkardSettings {
spawn_mode: DrunkSpawnMode::StartingPoint,
drunken_lifetime: 400,
floor_percent: 0.5,
},
)
}
pub fn open_halls(new_depth: i32) -> Self {
DrunkardsWalkBuilder::new(
new_depth,
DrunkardSettings {
spawn_mode: DrunkSpawnMode::Random,
drunken_lifetime: 400,
floor_percent: 0.5,
},
)
}
pub fn winding_passages(new_depth: i32) -> Self {
DrunkardsWalkBuilder::new(
new_depth,
DrunkardSettings {
spawn_mode: DrunkSpawnMode::Random,
drunken_lifetime: 100,
floor_percent: 0.4,
},
)
}
fn build(&mut self) {
let mut rng = RandomNumberGenerator::new();
self.starting_position = Position::new(self.map.width / 2, self.map.height / 2);
let start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
self.map.tiles[start_idx] = TileType::Floor;
let total_tiles = self.map.width * self.map.height;
let desired_floor_tiles = (self.settings.floor_percent * total_tiles as f32) as usize;
let mut floor_tile_count = self.map.tiles.iter().filter(|a| **a == TileType::Floor).count();
let mut digger_count = 0;
let mut active_digger_count = 0;
while floor_tile_count < desired_floor_tiles {
let mut did_something = false;
let mut drunk_x = self.starting_position.x;
let mut drunk_y = self.starting_position.y;
match self.settings.spawn_mode {
DrunkSpawnMode::StartingPoint => {
drunk_x = self.starting_position.x;
drunk_y = self.starting_position.y;
}
DrunkSpawnMode::Random => {
if digger_count == 0 {
drunk_x = self.starting_position.x;
drunk_y = self.starting_position.y;
} else {
drunk_x = rng.roll_dice(1, self.map.width - 3) + 1;
drunk_y = rng.roll_dice(1, self.map.height - 3) + 1;
}
}
}
let mut drunk_life = self.settings.drunken_lifetime;
while drunk_life > 0 {
let drunk_idx = self.map.xy_idx(drunk_x, drunk_y);
if self.map.tiles[drunk_idx] == TileType::Wall {
did_something = true;
}
self.map.tiles[drunk_idx] = TileType::DownStairs;
let stagger_direction = rng.roll_dice(1, 4);
match stagger_direction {
1 if drunk_x > 2 => { drunk_x -= 1; }
2 if drunk_x < self.map.width - 2 => { drunk_x += 1; }
3 if drunk_y > 2 => { drunk_y -= 1; }
_ if drunk_y < self.map.height - 2 => { drunk_y += 1; }
_ => {}
}
drunk_life -= 1;
}
if did_something {
self.take_snapshot();
active_digger_count += 1;
}
digger_count += 1;
for t in self.map.tiles.iter_mut() {
if *t == TileType::DownStairs {
*t = TileType::Floor;
}
}
floor_tile_count = self.map.tiles.iter().filter(|a| **a == TileType::Floor).count();
}
rltk::console::log(format!("{} dwarves gave up their sobriety, of whom {} actually found a wall.", digger_count, active_digger_count));
let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx);
self.take_snapshot();
self.map.tiles[exit_tile] = TileType::DownStairs;
self.take_snapshot();
self.noise_areas = generate_voronois_spawn_regions(&self.map, &mut rng);
}
}
impl MapBuilder for DrunkardsWalkBuilder {
fn build_map(&mut self) {
self.build();
}
fn spawn_entities(&mut self, ecs: &mut World) {
for area in self.noise_areas.iter() {
spawn_region(ecs, area.1, self.depth);
}
}
fn get_map(&self) -> Map {
self.map.clone()
}
fn get_starting_position(&self) -> Position {
self.starting_position.clone()
}
fn get_snapshot_history(&self) -> Vec<Map> {
self.history.clone()
}
fn take_snapshot(&mut self) {
if SHOW_MAPGEN_VISUALIZER {
self.history.push(reveal_all(&self.map))
}
}
}

40
src/map_builders/mod.rs Normal file
View File

@@ -0,0 +1,40 @@
use specs::World;
use crate::map_builders::bsp_dungeon::BspDungeonBuilder;
use crate::map_builders::bsp_interior::BspInteriorBuilder;
use crate::map_builders::simple_map::SimpleMapBuilder;
use crate::{Map, Position};
use crate::map_builders::cellular_automata::CellularAutomataBuilder;
use crate::map_builders::drunkard::{DrunkardSettings, DrunkardsWalkBuilder, DrunkSpawnMode};
mod bsp;
mod bsp_dungeon;
mod bsp_interior;
mod cellular_automata;
mod common;
mod simple_map;
mod drunkard;
pub trait MapBuilder {
fn build_map(&mut self);
fn spawn_entities(&mut self, ecs: &mut World);
fn get_map(&self) -> Map;
fn get_starting_position(&self) -> Position;
fn get_snapshot_history(&self) -> Vec<Map>;
fn take_snapshot(&mut self);
}
pub fn new_random_builder(new_depth: i32) -> Box<dyn MapBuilder> {
let mut rng = rltk::RandomNumberGenerator::new();
//let builder_choice = rng.roll_dice(1, 7);
let builder_choice = 4;
match builder_choice {
6 => Box::new(DrunkardsWalkBuilder::open_area(new_depth)),
5 => Box::new(DrunkardsWalkBuilder::open_halls(new_depth)),
4 => Box::new(DrunkardsWalkBuilder::winding_passages(new_depth)),
3 => Box::new(CellularAutomataBuilder::new(new_depth)),
2 => Box::new(BspInteriorBuilder::new(new_depth)),
1 => Box::new(SimpleMapBuilder::new(new_depth)),
_ => Box::new(BspDungeonBuilder::new(new_depth)),
}
}

View File

@@ -0,0 +1,111 @@
use rltk::RandomNumberGenerator;
use specs::prelude::*;
use crate::map_builders::{common, MapBuilder};
use crate::rect::Rect;
use crate::{spawner, Map, Position, TileType, SHOW_MAPGEN_VISUALIZER};
pub struct SimpleMapBuilder {
map: Map,
starting_position: Position,
depth: i32,
rooms: Vec<Rect>,
history: Vec<Map>,
}
impl SimpleMapBuilder {
pub fn new(new_depth: i32) -> Self {
Self {
starting_position: Position { x: 0, y: 0 },
map: Map::new(new_depth),
depth: new_depth,
rooms: Vec::new(),
history: Vec::new(),
}
}
fn rooms_and_corridors(&mut self) {
const MAX_ROOMS: i32 = 30;
const MIN_SIZE: i32 = 6;
const MAX_SIZE: i32 = 10;
let mut rng = RandomNumberGenerator::new();
for _ in 0..MAX_ROOMS {
let w = rng.range(MIN_SIZE, MAX_SIZE);
let h = rng.range(MIN_SIZE, MAX_SIZE);
let x = rng.roll_dice(1, self.map.width - w - 1) - 1;
let y = rng.roll_dice(1, self.map.height - h - 1) - 1;
let new_room = Rect::new(x, y, w, h);
let mut ok = true;
for other_room in self.rooms.iter() {
if new_room.intersect(other_room) {
ok = false
};
}
if ok {
common::apply_room_to_map(&mut self.map, &new_room);
self.take_snapshot();
if !self.rooms.is_empty() {
let (new_x, new_y) = new_room.center();
let (prev_x, prev_y) = self.rooms[self.rooms.len() - 1].center();
if rng.range(0, 2) == 1 {
common::apply_horizontal_tunnel(&mut self.map, prev_x, new_x, prev_y);
common::apply_vertical_tunnel(&mut self.map, prev_y, new_y, new_x);
} else {
common::apply_vertical_tunnel(&mut self.map, prev_y, new_y, prev_x);
common::apply_horizontal_tunnel(&mut self.map, prev_x, new_x, new_y);
}
}
self.rooms.push(new_room);
self.take_snapshot();
}
}
let stairs_position = self.rooms[self.rooms.len() - 1].center();
let stairs_idx = self.map.xy_idx(stairs_position.0, stairs_position.1);
self.map.tiles[stairs_idx] = TileType::DownStairs;
let start_pos = self.rooms[0].center();
self.starting_position = Position {
x: start_pos.0,
y: start_pos.1,
}
}
}
impl MapBuilder for SimpleMapBuilder {
fn build_map(&mut self) {
self.rooms_and_corridors();
}
fn spawn_entities(&mut self, ecs: &mut World) {
for room in self.rooms.iter().skip(1) {
spawner::spawn_room(ecs, room, self.depth);
}
}
fn get_map(&self) -> Map {
self.map.clone()
}
fn get_starting_position(&self) -> Position {
self.starting_position.clone()
}
fn get_snapshot_history(&self) -> Vec<Map> {
self.history.clone()
}
fn take_snapshot(&mut self) {
if SHOW_MAPGEN_VISUALIZER {
let mut snapshot = self.map.clone();
for v in snapshot.revealed_tiles.iter_mut() {
*v = true;
}
self.history.push(snapshot);
}
}
}

View File

@@ -2,10 +2,13 @@ use specs::prelude::*;
use crate::components::{CombatStats, Name, SufferDamage, WantsToMelee}; use crate::components::{CombatStats, Name, SufferDamage, WantsToMelee};
use crate::gamelog::GameLog; use crate::gamelog::GameLog;
use crate::particle_system::ParticleBuilder;
use crate::{DefenseBonus, Equipped, HungerClock, HungerState, MeleePowerBonus, Position};
pub struct MeleeCombatSystem {} pub struct MeleeCombatSystem {}
impl<'a> System<'a> for MeleeCombatSystem { impl<'a> System<'a> for MeleeCombatSystem {
#[allow(clippy::complexity)]
type SystemData = ( type SystemData = (
Entities<'a>, Entities<'a>,
WriteExpect<'a, GameLog>, WriteExpect<'a, GameLog>,
@@ -13,20 +16,80 @@ impl<'a> System<'a> for MeleeCombatSystem {
ReadStorage<'a, Name>, ReadStorage<'a, Name>,
ReadStorage<'a, CombatStats>, ReadStorage<'a, CombatStats>,
WriteStorage<'a, SufferDamage>, WriteStorage<'a, SufferDamage>,
ReadStorage<'a, MeleePowerBonus>,
ReadStorage<'a, DefenseBonus>,
ReadStorage<'a, Equipped>,
WriteExpect<'a, ParticleBuilder>,
ReadStorage<'a, Position>,
ReadStorage<'a, HungerClock>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
let (entities, mut log, mut wants_melee, names, combat_stats, mut inflict_damage) = data; let (
entities,
mut log,
mut wants_melee,
names,
combat_stats,
mut inflict_damage,
melee_bonus,
defense_bonus,
equipped,
mut particle_builder,
positions,
hunger_clock,
) = data;
for (_entity, wants_melee, name, stats) in for (entity, wants_melee, name, stats) in
(&entities, &wants_melee, &names, &combat_stats).join() (&entities, &wants_melee, &names, &combat_stats).join()
{ {
if stats.hp > 0 { if stats.hp <= 0 {
continue;
}
let mut offensive_bonus = 0;
for (_item_entity, power_bonus, equipped_by) in
(&entities, &melee_bonus, &equipped).join()
{
if equipped_by.owner == entity {
offensive_bonus += power_bonus.power;
}
}
if let Some(hc) = hunger_clock.get(entity) {
if hc.state == HungerState::WellFed {
offensive_bonus += 1;
}
}
let target_stats = combat_stats.get(wants_melee.target).unwrap(); let target_stats = combat_stats.get(wants_melee.target).unwrap();
if target_stats.hp > 0 { if target_stats.hp > 0 {
let target_name = names.get(wants_melee.target).unwrap(); let target_name = names.get(wants_melee.target).unwrap();
let damage = i32::max(0, stats.power - target_stats.defense); let mut defensive_bonus = 0;
for (_item_entity, defense_bonus, equipped_by) in
(&entities, &defense_bonus, &equipped).join()
{
if equipped_by.owner == wants_melee.target {
defensive_bonus += defense_bonus.defense;
}
}
if let Some(pos) = positions.get(wants_melee.target) {
particle_builder.request(
pos.x,
pos.y,
rltk::RGB::named(rltk::ORANGE),
rltk::RGB::named(rltk::BLACK),
rltk::to_cp437('‼'),
200.0,
);
}
let damage = i32::max(
0,
(stats.power + offensive_bonus) - (target_stats.defense + defensive_bonus),
);
if damage == 0 { if damage == 0 {
log.entries.push(format!( log.entries.push(format!(
@@ -42,7 +105,6 @@ impl<'a> System<'a> for MeleeCombatSystem {
} }
} }
} }
}
wants_melee.clear(); wants_melee.clear();
} }

View File

@@ -1,10 +1,11 @@
use rltk::Point; use rltk::Point;
use specs::prelude::*; use specs::prelude::*;
use crate::particle_system::ParticleBuilder;
use crate::{ use crate::{
components::{Monster, Position, Viewshed, WantsToMelee}, components::{Monster, Position, Viewshed, WantsToMelee},
Confusion, map::Map,
map::Map, RunState, Confusion, EntityMoved, RunState,
}; };
pub struct MonsterAI {} pub struct MonsterAI {}
@@ -22,6 +23,8 @@ impl<'a> System<'a> for MonsterAI {
WriteStorage<'a, Position>, WriteStorage<'a, Position>,
WriteStorage<'a, WantsToMelee>, WriteStorage<'a, WantsToMelee>,
WriteStorage<'a, Confusion>, WriteStorage<'a, Confusion>,
WriteExpect<'a, ParticleBuilder>,
WriteStorage<'a, EntityMoved>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
@@ -36,6 +39,8 @@ impl<'a> System<'a> for MonsterAI {
mut position, mut position,
mut wants_to_melee, mut wants_to_melee,
mut confused, mut confused,
mut particle_builder,
mut entity_moved,
) = data; ) = data;
if *runstate != RunState::MonsterTurn { if *runstate != RunState::MonsterTurn {
@@ -53,6 +58,15 @@ impl<'a> System<'a> for MonsterAI {
confused.remove(entity); confused.remove(entity);
} }
can_act = false; can_act = false;
particle_builder.request(
pos.x,
pos.y,
rltk::RGB::named(rltk::MAGENTA),
rltk::RGB::named(rltk::BLACK),
rltk::to_cp437('?'),
200.,
);
} }
if !can_act { if !can_act {
@@ -84,6 +98,10 @@ impl<'a> System<'a> for MonsterAI {
idx = map.xy_idx(pos.x, pos.y); idx = map.xy_idx(pos.x, pos.y);
map.blocked[idx] = true; map.blocked[idx] = true;
viewshed.dirty = true; viewshed.dirty = true;
entity_moved
.insert(entity, EntityMoved {})
.expect("Unable to insert entity moved");
} }
} }
} }

119
src/particle_system.rs Normal file
View File

@@ -0,0 +1,119 @@
use rltk::{Rltk, RGB};
use specs::prelude::*;
use crate::{ParticleLifetime, Position, Renderable};
pub fn cull_dead_particles(ecs: &mut World, ctx: &Rltk) {
let mut dead_particles: Vec<Entity> = Vec::new();
{
let mut particles = ecs.write_storage::<ParticleLifetime>();
let entities = ecs.entities();
for (entity, mut particle) in (&entities, &mut particles).join() {
particle.lifetime_ms -= ctx.frame_time_ms;
if particle.lifetime_ms < 0. {
dead_particles.push(entity);
}
}
}
for dead_particle in dead_particles.iter() {
ecs.delete_entity(*dead_particle)
.expect("Particle will not die");
}
}
struct ParticleRequest {
x: i32,
y: i32,
fg: RGB,
bg: RGB,
glyph: rltk::FontCharType,
lifetime: f32,
}
pub struct ParticleBuilder {
requests: Vec<ParticleRequest>,
}
impl ParticleBuilder {
pub fn new() -> ParticleBuilder {
ParticleBuilder {
requests: Vec::new(),
}
}
pub fn request(
&mut self,
x: i32,
y: i32,
fg: RGB,
bg: RGB,
glyph: rltk::FontCharType,
lifetime: f32,
) {
self.requests.push(ParticleRequest {
x,
y,
fg,
bg,
glyph,
lifetime,
});
}
}
pub struct ParticleSpawnSystem {}
impl<'a> System<'a> for ParticleSpawnSystem {
type SystemData = (
Entities<'a>,
WriteStorage<'a, Position>,
WriteStorage<'a, Renderable>,
WriteStorage<'a, ParticleLifetime>,
WriteExpect<'a, ParticleBuilder>,
);
fn run(&mut self, data: Self::SystemData) {
let (
entities,
mut positions,
mut renderables,
mut particle_lifetimes,
mut particle_builder,
) = data;
for new_particle in particle_builder.requests.iter() {
let p = entities.create();
positions
.insert(
p,
Position {
x: new_particle.x,
y: new_particle.y,
},
)
.expect("Unable to insert position");
renderables
.insert(
p,
Renderable {
fg: new_particle.fg,
bg: new_particle.bg,
glyph: new_particle.glyph,
render_order: 0,
},
)
.expect("Unable to insert renderable");
particle_lifetimes
.insert(
p,
ParticleLifetime {
lifetime_ms: new_particle.lifetime,
},
)
.expect("Unable to insert particle lifetime");
}
particle_builder.requests.clear();
}
}

View File

@@ -3,12 +3,14 @@ use std::cmp::{max, min};
use rltk::{Point, Rltk, VirtualKeyCode}; use rltk::{Point, Rltk, VirtualKeyCode};
use specs::prelude::*; use specs::prelude::*;
use crate::gamelog::GameLog;
use crate::{ use crate::{
components::{CombatStats, Player, Position, Viewshed, WantsToMelee}, components::{CombatStats, Player, Position, Viewshed, WantsToMelee},
Item, map::Map,
map::Map, RunState, State, WantsToPickupItem, EntityMoved, HungerClock, HungerState, Item, Monster, RunState, State, TileType,
WantsToPickupItem,
}; };
use crate::gamelog::GameLog;
pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) { pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) {
let mut positions = ecs.write_storage::<Position>(); let mut positions = ecs.write_storage::<Position>();
@@ -18,11 +20,18 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) {
let entities = ecs.entities(); let entities = ecs.entities();
let mut wants_to_melee = ecs.write_storage::<WantsToMelee>(); let mut wants_to_melee = ecs.write_storage::<WantsToMelee>();
let map = ecs.fetch::<Map>(); let map = ecs.fetch::<Map>();
let mut entity_moved = ecs.write_storage::<EntityMoved>();
for (entity, _player, pos, viewshed) in for (entity, _player, pos, viewshed) in
(&entities, &mut players, &mut positions, &mut viewsheds).join() (&entities, &mut players, &mut positions, &mut viewsheds).join()
{ {
if pos.x + delta_x < 1 || pos.x + delta_x > map.width - 1 || pos.y + delta_y < 1 || pos.y + delta_y > map.height - 1 { return; } if pos.x + delta_x < 1
|| pos.x + delta_x > map.width - 1
|| pos.y + delta_y < 1
|| pos.y + delta_y > map.height - 1
{
return;
}
let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y); let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y);
for potential_target in map.tile_content[destination_idx].iter() { for potential_target in map.tile_content[destination_idx].iter() {
@@ -47,6 +56,10 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) {
let mut ppos = ecs.write_resource::<Point>(); let mut ppos = ecs.write_resource::<Point>();
ppos.x = pos.x; ppos.x = pos.x;
ppos.y = pos.y; ppos.y = pos.y;
entity_moved
.insert(entity, EntityMoved {})
.expect("Unable to insert marker");
} }
} }
} }
@@ -93,6 +106,7 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState {
VirtualKeyCode::Right | VirtualKeyCode::L => try_move_player(1, 0, &mut gs.ecs), VirtualKeyCode::Right | VirtualKeyCode::L => try_move_player(1, 0, &mut gs.ecs),
VirtualKeyCode::Up | VirtualKeyCode::K => try_move_player(0, -1, &mut gs.ecs), VirtualKeyCode::Up | VirtualKeyCode::K => try_move_player(0, -1, &mut gs.ecs),
VirtualKeyCode::Down | VirtualKeyCode::J => try_move_player(0, 1, &mut gs.ecs), VirtualKeyCode::Down | VirtualKeyCode::J => try_move_player(0, 1, &mut gs.ecs),
VirtualKeyCode::Space => return skip_turn(&mut gs.ecs),
VirtualKeyCode::Y => try_move_player(1, -1, &mut gs.ecs), VirtualKeyCode::Y => try_move_player(1, -1, &mut gs.ecs),
VirtualKeyCode::U => try_move_player(-1, -1, &mut gs.ecs), VirtualKeyCode::U => try_move_player(-1, -1, &mut gs.ecs),
VirtualKeyCode::N => try_move_player(1, 1, &mut gs.ecs), VirtualKeyCode::N => try_move_player(1, 1, &mut gs.ecs),
@@ -101,9 +115,71 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState {
VirtualKeyCode::I => return RunState::ShowInventory, VirtualKeyCode::I => return RunState::ShowInventory,
VirtualKeyCode::D => return RunState::ShowDropItem, VirtualKeyCode::D => return RunState::ShowDropItem,
VirtualKeyCode::Escape => return RunState::SaveGame, VirtualKeyCode::Escape => return RunState::SaveGame,
VirtualKeyCode::R => return RunState::ShowRemoveItem,
VirtualKeyCode::Period => {
if try_next_level(&mut gs.ecs) {
return RunState::NextLevel;
}
}
_ => return RunState::AwaitingInput, _ => return RunState::AwaitingInput,
}, },
} }
RunState::PlayerTurn RunState::PlayerTurn
} }
fn skip_turn(ecs: &mut World) -> RunState {
let player_entity = ecs.fetch::<Entity>();
let viewshed_components = ecs.read_storage::<Viewshed>();
let monsters = ecs.read_storage::<Monster>();
let worldmap_resource = ecs.fetch::<Map>();
let mut can_heal = true;
let viewshed = viewshed_components.get(*player_entity).unwrap();
for tile in viewshed.visible_tiles.iter() {
let idx = worldmap_resource.xy_idx(tile.x, tile.y);
for entity_id in worldmap_resource.tile_content[idx].iter() {
if let Some(_mob) = monsters.get(*entity_id) {
can_heal = true;
}
}
}
let hunger_clock = ecs.read_storage::<HungerClock>();
let mut heal_bonus = 0;
if let Some(hc) = hunger_clock.get(*player_entity) {
match hc.state {
HungerState::Hungry | HungerState::Starving => {
can_heal = false;
}
HungerState::WellFed => {
heal_bonus = 1;
}
_ => {}
}
}
if can_heal {
let mut health_component = ecs.write_storage::<CombatStats>();
let player_hp = health_component.get_mut(*player_entity).unwrap();
player_hp.hp = i32::min(player_hp.hp + 1 + heal_bonus, player_hp.max_hp);
}
RunState::PlayerTurn
}
fn try_next_level(ecs: &mut World) -> bool {
let player_pos = ecs.fetch::<Point>();
let map = ecs.fetch::<Map>();
let player_idx = map.xy_idx(player_pos.x, player_pos.y);
if map.tiles[player_idx] == TileType::DownStairs {
true
} else {
let mut gamelog = ecs.fetch_mut::<GameLog>();
gamelog
.entries
.push("There is no way down from here".to_string());
false
}
}

57
src/random_table.rs Normal file
View File

@@ -0,0 +1,57 @@
use rltk::RandomNumberGenerator;
pub struct RandomEntry {
name: String,
weight: i32,
}
impl RandomEntry {
pub fn new<S: ToString>(name: S, weight: i32) -> RandomEntry {
RandomEntry {
name: name.to_string(),
weight,
}
}
}
#[derive(Default)]
pub struct RandomTable {
entries: Vec<RandomEntry>,
total_weight: i32,
}
impl RandomTable {
pub fn new() -> RandomTable {
RandomTable {
total_weight: 0,
entries: Vec::new(),
}
}
pub fn add<S: ToString>(mut self, name: S, weight: i32) -> RandomTable {
if weight > 0 {
self.total_weight += weight;
self.entries.push(RandomEntry::new(name, weight));
}
self
}
pub fn roll(&self, rng: &mut RandomNumberGenerator) -> String {
if self.total_weight == 0 {
return "None".to_string();
}
let mut roll = rng.roll_dice(1, self.total_weight) - 1;
let mut index: usize = 0;
while roll > 0 {
if roll < self.entries[index].weight {
return self.entries[index].name.clone();
}
roll -= self.entries[index].weight;
index += 1;
}
"None".to_string()
}
}

18
src/rex_assets.rs Normal file
View File

@@ -0,0 +1,18 @@
use rltk::rex::XpFile;
rltk::embedded_resource!(SMALL_DUNGEON, "../resources/SmallDungeon_80x50.xp");
pub struct RexAssets {
pub menu: XpFile,
}
impl RexAssets {
#[allow(clippy::new_without_default)]
pub fn new() -> RexAssets {
rltk::link_resource!(SMALL_DUNGEON, "../resources/SmallDungeon_80x50.xp");
RexAssets {
menu: XpFile::from_resource("../resources/SmallDungeon_80x50.xp").unwrap(),
}
}
}

View File

@@ -4,7 +4,9 @@ use std::path::Path;
use specs::error::NoError; use specs::error::NoError;
use specs::prelude::*; use specs::prelude::*;
use specs::saveload::{DeserializeComponents, MarkedBuilder, SerializeComponents, SimpleMarker, SimpleMarkerAllocator}; use specs::saveload::{
DeserializeComponents, MarkedBuilder, SerializeComponents, SimpleMarker, SimpleMarkerAllocator,
};
use super::components::*; use super::components::*;
@@ -37,14 +39,53 @@ pub fn save_game(ecs: &mut World) {
// Actually serialize // Actually serialize
{ {
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.json").unwrap();
let mut serializer = serde_json::Serializer::new(writer); let mut serializer = serde_json::Serializer::new(writer);
serialize_individually!(ecs, serializer, data, Position, Renderable, Player, Viewshed, Monster, serialize_individually!(
Name, BlocksTile, CombatStats, SufferDamage, WantsToMelee, Item, Consumable, Ranged, InflictsDamage, ecs,
AreaOfEffect, Confusion, ProvidesHealing, InBackpack, WantsToPickupItem, WantsToUseItem, serializer,
WantsToDropItem, SerializationHelper data,
Position,
Renderable,
Player,
Viewshed,
Monster,
Name,
BlocksTile,
CombatStats,
SufferDamage,
WantsToMelee,
Item,
Consumable,
Ranged,
InflictsDamage,
AreaOfEffect,
Confusion,
ProvidesHealing,
InBackpack,
WantsToPickupItem,
WantsToUseItem,
WantsToDropItem,
SerializationHelper,
Equippable,
Equipped,
MeleePowerBonus,
DefenseBonus,
WantsToRemoveItem,
ParticleLifetime,
HungerClock,
Heals,
ProvidesFood,
MagicMapper,
Hidden,
EntryTrigger,
EntityMoved,
SingleActivation
); );
} }
@@ -87,12 +128,52 @@ pub fn load_game(ecs: &mut World) {
let mut de = serde_json::Deserializer::from_str(&data); let mut de = serde_json::Deserializer::from_str(&data);
{ {
let mut d = (&mut ecs.entities(), &mut ecs.write_storage::<SimpleMarker<SerializeMe>>(), &mut ecs.write_resource::<SimpleMarkerAllocator<SerializeMe>>()); let mut d = (
&mut ecs.entities(),
&mut ecs.write_storage::<SimpleMarker<SerializeMe>>(),
&mut ecs.write_resource::<SimpleMarkerAllocator<SerializeMe>>(),
);
deserialize_individually!(ecs, de, d, Position, Renderable, Player, Viewshed, Monster, deserialize_individually!(
Name, BlocksTile, CombatStats, SufferDamage, WantsToMelee, Item, Consumable, Ranged, InflictsDamage, ecs,
AreaOfEffect, Confusion, ProvidesHealing, InBackpack, WantsToPickupItem, WantsToUseItem, de,
WantsToDropItem, SerializationHelper d,
Position,
Renderable,
Player,
Viewshed,
Monster,
Name,
BlocksTile,
CombatStats,
SufferDamage,
WantsToMelee,
Item,
Consumable,
Ranged,
InflictsDamage,
AreaOfEffect,
Confusion,
ProvidesHealing,
InBackpack,
WantsToPickupItem,
WantsToUseItem,
WantsToDropItem,
SerializationHelper,
Equippable,
Equipped,
MeleePowerBonus,
DefenseBonus,
WantsToRemoveItem,
ParticleLifetime,
HungerClock,
Heals,
ProvidesFood,
MagicMapper,
Hidden,
EntryTrigger,
EntityMoved,
SingleActivation
); );
} }
@@ -115,9 +196,12 @@ pub fn load_game(ecs: &mut World) {
*player_resource = e; *player_resource = e;
} }
} }
ecs.delete_entity(deleteme.unwrap()).expect("Unable to delete helper"); ecs.delete_entity(deleteme.unwrap())
.expect("Unable to delete helper");
} }
pub fn delete_save() { pub fn delete_save() {
if Path::new("./savegame.json").exists() { std::fs::remove_file("./savegame.json").expect("Unable to delete file"); } if Path::new("./savegame.json").exists() {
std::fs::remove_file("./savegame.json").expect("Unable to delete file");
}
} }

View File

@@ -1,9 +1,17 @@
use std::collections::HashMap;
use rltk::{FontCharType, RandomNumberGenerator, RGB}; use rltk::{FontCharType, RandomNumberGenerator, RGB};
use specs::prelude::*; use specs::prelude::*;
use specs::saveload::{MarkedBuilder, SimpleMarker}; use specs::saveload::{MarkedBuilder, SimpleMarker};
use crate::{AreaOfEffect, BlocksTile, CombatStats, Confusion, Consumable, InflictsDamage, Item, MAP_WIDTH, MAX_ITEMS, MAX_MONSTER, Monster, Name, Player, Position, ProvidesHealing, Ranged, Renderable, SerializeMe, Viewshed}; use crate::random_table::RandomTable;
use crate::rect::Rect; use crate::rect::Rect;
use crate::{
AreaOfEffect, BlocksTile, CombatStats, Confusion, Consumable, DefenseBonus, EntryTrigger,
EquipmentSlot, Equippable, Hidden, HungerClock, HungerState, InflictsDamage, Item, MagicMapper,
Map, MeleePowerBonus, Monster, Name, Player, Position, ProvidesFood, ProvidesHealing, Ranged,
Renderable, SerializeMe, SingleActivation, TileType, Viewshed, MAP_WIDTH, MAX_MONSTER,
};
pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
ecs.create_entity() ecs.create_entity()
@@ -32,22 +40,14 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
defense: 2, defense: 2,
power: 5, power: 5,
}) })
.with(HungerClock {
duration: 20,
state: HungerState::WellFed,
})
.marked::<SimpleMarker<SerializeMe>>() .marked::<SimpleMarker<SerializeMe>>()
.build() .build()
} }
pub fn random_monster(ecs: &mut World, x: i32, y: i32) {
let roll: i32;
{
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
roll = rng.roll_dice(1, 2);
}
match roll {
1 => orc(ecs, x, y),
_ => goblin(ecs, x, y),
}
}
fn orc(ecs: &mut World, x: i32, y: i32) { fn orc(ecs: &mut World, x: i32, y: i32) {
monster(ecs, x, y, rltk::to_cp437('o'), "Orc") monster(ecs, x, y, rltk::to_cp437('o'), "Orc")
} }
@@ -85,52 +85,79 @@ fn monster<S: ToString>(ecs: &mut World, x: i32, y: i32, glyph: FontCharType, na
.build(); .build();
} }
pub fn spawn_room(ecs: &mut World, room: &Rect) { #[allow(clippy::map_entry)]
let mut monster_spawn_points: Vec<usize> = Vec::new(); pub fn spawn_room(ecs: &mut World, room: &Rect, map_depth: i32) {
let mut item_spawn_points: Vec<usize> = Vec::new(); let mut possible_targets: Vec<usize> = Vec::new();
{ {
let map = ecs.fetch::<Map>();
for y in room.y1 + 1..room.y2 {
for x in room.x1 + 1..room.x2 {
let idx = map.xy_idx(x, y);
if map.tiles[idx] == TileType::Floor {
possible_targets.push(idx)
}
}
}
}
spawn_region(ecs, &possible_targets, map_depth);
}
pub(crate) fn spawn_region(ecs: &mut World, area: &[usize], map_depth: i32) {
if let Some(spawn_points) = calculate_spawn_points(ecs, area, map_depth) {
spawn_entities(ecs, spawn_points)
}
}
fn spawn_entities(ecs: &mut World, spawn_points: HashMap<usize, String>) {
for spawn in spawn_points.iter() {
spawn_entity(ecs, &spawn);
}
}
fn calculate_spawn_points(ecs: &mut World, area: &[usize], map_depth: i32) -> Option<HashMap<usize, String>> {
let mut spawn_points: HashMap<usize, String> = HashMap::new();
let spawn_table = room_table(map_depth);
let mut areas: Vec<usize> = Vec::from(area);
let mut rng = ecs.write_resource::<RandomNumberGenerator>(); let mut rng = ecs.write_resource::<RandomNumberGenerator>();
let num_monsters = rng.roll_dice(1, MAX_MONSTER + 2) - 3;
let num_items = rng.roll_dice(1, MAX_ITEMS + 2) - 3;
for _i in 0..num_monsters { let num_spawns = i32::min(areas.len() as i32, rng.roll_dice(1, MAX_MONSTER + 3));
let mut added = false; if num_spawns == 0 { return None; }
while !added {
let x = (room.x1 + rng.roll_dice(1, i32::abs(room.x2 - room.x1))) as usize; for _i in 0..num_spawns {
let y = (room.y1 + rng.roll_dice(1, i32::abs(room.y2 - room.y1))) as usize; let array_idx = if areas.len() == 1 { 0usize } else { (rng.roll_dice(1, areas.len() as i32 - 1) as usize) };
let idx = (y * MAP_WIDTH) + x; let map_idx = areas[array_idx];
if !monster_spawn_points.contains(&idx) { spawn_points.insert(map_idx, spawn_table.roll(&mut rng));
monster_spawn_points.push(idx); areas.remove(array_idx);
added = true;
}
}
} }
for _i in 0..num_items { Some(spawn_points)
let mut added = false; }
while !added {
let x = (room.x1 + rng.roll_dice(1, i32::abs(room.x2 - room.x1))) as usize;
let y = (room.y1 + rng.roll_dice(1, i32::abs(room.y2 - room.y1))) as usize;
let idx = (y * MAP_WIDTH) + x;
if !item_spawn_points.contains(&idx) {
item_spawn_points.push(idx);
added = true;
}
}
}
}
for idx in monster_spawn_points.iter() { fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) {
let x = *idx % MAP_WIDTH; let x = (*spawn.0 % MAP_WIDTH) as i32;
let y = *idx / MAP_WIDTH; let y = (*spawn.0 / MAP_WIDTH) as i32;
random_monster(ecs, x as i32, y as i32);
}
for idx in item_spawn_points.iter() { match spawn.1.as_ref() {
let x = *idx % MAP_WIDTH; "Goblin" => goblin(ecs, x, y),
let y = *idx / MAP_WIDTH; "Orc" => orc(ecs, x, y),
random_item(ecs, x as i32, y as i32); "Health Potion" => health_potion(ecs, x, y),
"Fireball Scroll" => fireball_scroll(ecs, x, y),
"Confusion Scroll" => confusion_scroll(ecs, x, y),
"Magic Missile Scroll" => magic_missile_scroll(ecs, x, y),
"Dagger" => dagger(ecs, x, y),
"Longsword" => longsword(ecs, x, y),
"Shield" => shield(ecs, x, y),
"Tower Shield" => tower_shield(ecs, x, y),
"Helmet" => helmet(ecs, x, y),
"Breastplate" => breastplate(ecs, x, y),
"Leggings" => leggings(ecs, x, y),
"Sabatons" => sabatons(ecs, x, y),
"Rations" => rations(ecs, x, y),
"Magic Mapping Scroll" => magic_mapper_scroll(ecs, x, y),
"Bear Trap" => bear_trap(ecs, x, y),
_ => {}
} }
} }
@@ -214,16 +241,251 @@ pub fn confusion_scroll(ecs: &mut World, x: i32, y: i32) {
.build(); .build();
} }
pub fn random_item(ecs: &mut World, x: i32, y: i32) { pub fn room_table(map_depth: i32) -> RandomTable {
let roll: i32; RandomTable::new()
{ .add("Goblin", 10)
let mut rng = ecs.write_resource::<RandomNumberGenerator>(); .add("Orc", 1 + map_depth)
roll = rng.roll_dice(1, 4); .add("Health Potion", 7)
} .add("Fireball Scroll", 2 + map_depth)
match roll { .add("Confusion Scroll", 2 + map_depth)
1 => health_potion(ecs, x, y), .add("Magic Missile Scroll", 4)
2 => fireball_scroll(ecs, x, y), .add("Dagger", 3)
3 => confusion_scroll(ecs, x, y), .add("Longsword", map_depth - 1)
_ => magic_missile_scroll(ecs, x, y), .add("Shield", 3)
} .add("Tower Shield", map_depth - 1)
.add("Helmet", map_depth - 2)
.add("Breastplate", map_depth - 3)
.add("Leggings", map_depth - 4)
.add("Sabatons", map_depth - 4)
.add("Rations", 10)
.add("Magic Mapping Scroll", 2)
.add("Bear Trap", 2)
}
fn dagger(ecs: &mut World, x: i32, y: i32) {
ecs.create_entity()
.with(Position { x, y })
.with(Renderable {
glyph: rltk::to_cp437('/'),
render_order: 2,
fg: RGB::named(rltk::CYAN),
bg: RGB::named(rltk::BLACK),
})
.with(Item {})
.with(Name {
name: "Dagger".to_string(),
})
.with(Equippable {
slot: EquipmentSlot::Melee,
})
.with(MeleePowerBonus { power: 2 })
.marked::<SimpleMarker<SerializeMe>>()
.build();
}
fn longsword(ecs: &mut World, x: i32, y: i32) {
ecs.create_entity()
.with(Position { x, y })
.with(Renderable {
glyph: rltk::to_cp437('/'),
render_order: 2,
fg: RGB::named(rltk::CYAN),
bg: RGB::named(rltk::BLACK),
})
.with(Item {})
.with(Name {
name: "Longsword".to_string(),
})
.with(Equippable {
slot: EquipmentSlot::Melee,
})
.with(MeleePowerBonus { power: 4 })
.marked::<SimpleMarker<SerializeMe>>()
.build();
}
fn shield(ecs: &mut World, x: i32, y: i32) {
ecs.create_entity()
.with(Position { x, y })
.with(Renderable {
glyph: rltk::to_cp437('('),
render_order: 2,
fg: RGB::named(rltk::CYAN),
bg: RGB::named(rltk::BLACK),
})
.with(Item {})
.with(Name {
name: "Shield".to_string(),
})
.with(Equippable {
slot: EquipmentSlot::Shield,
})
.with(DefenseBonus { defense: 1 })
.marked::<SimpleMarker<SerializeMe>>()
.build();
}
fn tower_shield(ecs: &mut World, x: i32, y: i32) {
ecs.create_entity()
.with(Position { x, y })
.with(Renderable {
glyph: rltk::to_cp437('('),
render_order: 2,
fg: RGB::named(rltk::CYAN),
bg: RGB::named(rltk::BLACK),
})
.with(Item {})
.with(Name {
name: "Tower Shield".to_string(),
})
.with(Equippable {
slot: EquipmentSlot::Shield,
})
.with(DefenseBonus { defense: 3 })
.marked::<SimpleMarker<SerializeMe>>()
.build();
}
fn helmet(ecs: &mut World, x: i32, y: i32) {
ecs.create_entity()
.with(Position { x, y })
.with(Renderable {
glyph: rltk::to_cp437('^'),
render_order: 2,
fg: RGB::named(rltk::CYAN),
bg: RGB::named(rltk::BLACK),
})
.with(Item {})
.with(Name {
name: "Helmet".to_string(),
})
.with(Equippable {
slot: EquipmentSlot::Head,
})
.with(DefenseBonus { defense: 1 })
.with(MeleePowerBonus { power: 1 })
.marked::<SimpleMarker<SerializeMe>>()
.build();
}
fn breastplate(ecs: &mut World, x: i32, y: i32) {
ecs.create_entity()
.with(Position { x, y })
.with(Renderable {
glyph: rltk::to_cp437('x'),
render_order: 2,
fg: RGB::named(rltk::CYAN),
bg: RGB::named(rltk::BLACK),
})
.with(Item {})
.with(Name {
name: "Breastplate".to_string(),
})
.with(Equippable {
slot: EquipmentSlot::Chest,
})
.with(DefenseBonus { defense: 2 })
.marked::<SimpleMarker<SerializeMe>>()
.build();
}
fn leggings(ecs: &mut World, x: i32, y: i32) {
ecs.create_entity()
.with(Position { x, y })
.with(Renderable {
glyph: rltk::to_cp437('"'),
render_order: 2,
fg: RGB::named(rltk::CYAN),
bg: RGB::named(rltk::BLACK),
})
.with(Item {})
.with(Name {
name: "Leggings".to_string(),
})
.with(Equippable {
slot: EquipmentSlot::Legs,
})
.with(DefenseBonus { defense: 1 })
.marked::<SimpleMarker<SerializeMe>>()
.build();
}
fn sabatons(ecs: &mut World, x: i32, y: i32) {
ecs.create_entity()
.with(Position { x, y })
.with(Renderable {
glyph: rltk::to_cp437(','),
render_order: 2,
fg: RGB::named(rltk::CYAN),
bg: RGB::named(rltk::BLACK),
})
.with(Item {})
.with(Name {
name: "Sabatons".to_string(),
})
.with(Equippable {
slot: EquipmentSlot::Feet,
})
.with(DefenseBonus { defense: 1 })
.with(MeleePowerBonus { power: 1 })
.marked::<SimpleMarker<SerializeMe>>()
.build();
}
fn rations(ecs: &mut World, x: i32, y: i32) {
ecs.create_entity()
.with(Position { x, y })
.with(Renderable {
glyph: rltk::to_cp437('%'),
render_order: 2,
fg: RGB::named(rltk::GREEN),
bg: RGB::named(rltk::BLACK),
})
.with(Item {})
.with(Name {
name: "Rations".to_string(),
})
.with(Consumable {})
.with(ProvidesFood {})
.marked::<SimpleMarker<SerializeMe>>()
.build();
}
fn magic_mapper_scroll(ecs: &mut World, x: i32, y: i32) {
ecs.create_entity()
.with(Position { x, y })
.with(Renderable {
glyph: rltk::to_cp437(')'),
render_order: 2,
fg: RGB::named(rltk::CYAN3),
bg: RGB::named(rltk::BLACK),
})
.with(Item {})
.with(Name {
name: "Scroll to Magic Mapping".to_string(),
})
.with(Consumable {})
.with(MagicMapper {})
.marked::<SimpleMarker<SerializeMe>>()
.build();
}
fn bear_trap(ecs: &mut World, x: i32, y: i32) {
ecs.create_entity()
.with(Position { x, y })
.with(Renderable {
glyph: rltk::to_cp437('^'),
render_order: 2,
fg: RGB::named(rltk::RED),
bg: RGB::named(rltk::BLACK),
})
.with(Name {
name: "Bear Trap".to_string(),
})
.with(Hidden {})
.with(EntryTrigger {})
.with(InflictsDamage { damage: 6 })
.with(SingleActivation {})
.marked::<SimpleMarker<SerializeMe>>()
.build();
} }

432
src/state.rs Normal file
View File

@@ -0,0 +1,432 @@
use specs::prelude::*;
use crate::*;
#[derive(PartialEq, Copy, Clone)]
pub enum RunState {
AwaitingInput,
PreRun,
PlayerTurn,
MonsterTurn,
ShowInventory,
ShowDropItem,
ShowTargeting {
range: i32,
item: Entity,
},
MainMenu {
menu_selection: gui::MainMenuSelection,
},
SaveGame,
NextLevel,
ShowRemoveItem,
GameOver,
MagicMapReveal {
row: i32,
},
MapGeneration,
}
pub struct State {
pub ecs: World,
mapgen_next_state: Option<RunState>,
mapgen_history: Vec<Map>,
mapgen_index: usize,
mapgen_timer: f32,
}
impl State {
pub fn new() -> Self {
Self {
ecs: World::new(),
mapgen_history: Vec::new(),
mapgen_index: 0,
mapgen_timer: 0.,
mapgen_next_state: Some(RunState::MainMenu {
menu_selection: MainMenuSelection::NewGame,
}),
}
}
pub fn game_over_cleanup(&mut self) {
// Delete everything
let mut to_delete = Vec::new();
for e in self.ecs.entities().join() {
to_delete.push(e);
}
for del in to_delete.iter() {
self.ecs.delete_entity(*del).expect("Deletion failed");
}
{
let player_entity = spawner::player(&mut self.ecs, 0, 0);
let mut player_entity_writer = self.ecs.write_resource::<Entity>();
*player_entity_writer = player_entity
}
self.generate_world_map(1);
}
fn entities_to_remove_on_level_change(&mut self) -> Vec<Entity> {
let entities = self.ecs.entities();
let player = self.ecs.read_storage::<Player>();
let backpack = self.ecs.read_storage::<InBackpack>();
let player_entity = self.ecs.fetch::<Entity>();
let equipped = self.ecs.read_storage::<Equipped>();
let mut to_delete: Vec<Entity> = Vec::new();
for entity in entities.join() {
let mut should_delete = true;
let p = player.get(entity);
if let Some(_p) = p {
should_delete = false;
}
let bp = backpack.get(entity);
if let Some(bp) = bp {
if bp.owner == *player_entity {
should_delete = false;
}
}
if let Some(eq) = equipped.get(entity) {
if eq.owner == *player_entity {
should_delete = false;
}
}
if should_delete {
to_delete.push(entity);
}
}
to_delete
}
fn goto_next_level(&mut self) {
let to_delete = self.entities_to_remove_on_level_change();
for target in to_delete {
self.ecs
.delete_entity(target)
.expect("Unable to delete entity")
}
let current_depth;
{
let worldmap_resource = self.ecs.write_resource::<Map>();
current_depth = worldmap_resource.depth;
}
self.generate_world_map(current_depth + 1);
let player_entity = self.ecs.fetch::<Entity>();
let mut gamelog = self.ecs.fetch_mut::<GameLog>();
gamelog
.entries
.push("You descend to the next level, and take a moment to heal".to_string());
let mut player_health_store = self.ecs.write_storage::<CombatStats>();
let player_health = player_health_store.get_mut(*player_entity);
if let Some(player_health) = player_health {
player_health.hp = i32::max(player_health.hp, player_health.max_hp / 2);
}
}
fn run_systems(&mut self) {
let mut vis = VisibilitySystem {};
vis.run_now(&self.ecs);
let mut mob = MonsterAI {};
mob.run_now(&self.ecs);
let mut trigger = TriggerSystem {};
trigger.run_now(&self.ecs);
let mut map_index = MapIndexingSystem {};
map_index.run_now(&self.ecs);
let mut melee_system = MeleeCombatSystem {};
melee_system.run_now(&self.ecs);
let mut damage_system = DamageSystem {};
damage_system.run_now(&self.ecs);
let mut healing_system = HealingSystem {};
healing_system.run_now(&self.ecs);
let mut inventory = ItemCollectionSystem {};
inventory.run_now(&self.ecs);
let mut potions = ItemUseSystem {};
potions.run_now(&self.ecs);
let mut drop_items = ItemDropSystem {};
drop_items.run_now(&self.ecs);
let mut remove_items = ItemRemoveSystem {};
remove_items.run_now(&self.ecs);
let mut hunger = HungerSystem {};
hunger.run_now(&self.ecs);
let mut particle_spawn = ParticleSpawnSystem {};
particle_spawn.run_now(&self.ecs);
self.ecs.maintain();
}
pub fn generate_world_map(&mut self, new_depth: i32) {
self.mapgen_index = 0;
self.mapgen_timer = 0.0;
self.mapgen_history.clear();
let mut map_builder = map_builders::new_random_builder(new_depth);
map_builder.build_map();
self.mapgen_history = map_builder.get_snapshot_history();
let player_start;
{
let mut world_map_resource = self.ecs.write_resource::<Map>();
*world_map_resource = map_builder.get_map();
player_start = map_builder.get_starting_position();
}
map_builder.spawn_entities(&mut self.ecs);
let (player_x, player_y) = (player_start.x, player_start.y);
let mut player_pos = self.ecs.write_resource::<Point>();
*player_pos = Point::new(player_x, player_y);
let mut pos_comp = self.ecs.write_storage::<Position>();
let player_entity = self.ecs.fetch::<Entity>();
if let Some(player_pos_comp) = pos_comp.get_mut(*player_entity) {
player_pos_comp.x = player_x;
player_pos_comp.y = player_y;
}
let mut viewshed_comp = self.ecs.write_storage::<Viewshed>();
if let Some(vs) = viewshed_comp.get_mut(*player_entity) {
vs.dirty = true;
}
}
}
impl GameState for State {
fn tick(&mut self, ctx: &mut rltk::Rltk) {
let mut new_run_state;
{
let run_state = self.ecs.fetch::<RunState>();
new_run_state = *run_state;
}
ctx.cls();
particle_system::cull_dead_particles(&mut self.ecs, ctx);
match new_run_state {
RunState::MainMenu { .. } => {}
_ => {
draw_map(&self.ecs.fetch::<Map>(), ctx);
let positions = self.ecs.read_storage::<Position>();
let renderables = self.ecs.read_storage::<Renderable>();
let hidden = self.ecs.read_storage::<Hidden>();
let map = self.ecs.fetch::<Map>();
let mut data = (&positions, &renderables, !&hidden)
.join()
.collect::<Vec<_>>();
data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order));
for (pos, render, _hidden) in data.iter() {
let idx = map.xy_idx(pos.x, pos.y);
if map.visible_tiles[idx] {
ctx.set(pos.x, pos.y, render.fg, render.bg, render.glyph)
}
}
gui::draw_ui(&self.ecs, ctx);
}
}
match new_run_state {
RunState::PreRun => {
self.run_systems();
new_run_state = RunState::AwaitingInput;
}
RunState::AwaitingInput => new_run_state = player_input(self, ctx),
RunState::PlayerTurn => {
self.run_systems();
new_run_state = RunState::MonsterTurn;
match *self.ecs.fetch::<RunState>() {
RunState::MagicMapReveal { .. } => {
new_run_state = RunState::MagicMapReveal { row: 0 }
}
_ => {
new_run_state = RunState::MonsterTurn;
}
}
}
RunState::MonsterTurn => {
self.run_systems();
new_run_state = RunState::AwaitingInput;
}
RunState::ShowInventory => {
let result = gui::show_inventory(self, ctx);
match result.0 {
gui::ItemMenuResult::Cancel => new_run_state = RunState::AwaitingInput,
gui::ItemMenuResult::NoResponse => {}
gui::ItemMenuResult::Selected => {
let item_entity = result.1.unwrap();
let is_ranged = self.ecs.read_storage::<Ranged>();
let is_item_ranged = is_ranged.get(item_entity);
if let Some(is_item_ranged) = is_item_ranged {
new_run_state = RunState::ShowTargeting {
range: is_item_ranged.range,
item: item_entity,
}
} else {
let mut intent = self.ecs.write_storage::<WantsToUseItem>();
intent
.insert(
*self.ecs.fetch::<Entity>(),
WantsToUseItem {
item: item_entity,
target: None,
},
)
.expect("Unable to insert intent");
new_run_state = RunState::PlayerTurn;
}
}
}
}
RunState::ShowDropItem => {
let result = gui::drop_item_menu(self, ctx);
match result.0 {
gui::ItemMenuResult::Cancel => new_run_state = RunState::AwaitingInput,
gui::ItemMenuResult::NoResponse => {}
gui::ItemMenuResult::Selected => {
let item_entity = result.1.unwrap();
let mut intent = self.ecs.write_storage::<WantsToDropItem>();
intent
.insert(
*self.ecs.fetch::<Entity>(),
WantsToDropItem { item: item_entity },
)
.expect("Unable to insert intent");
new_run_state = RunState::PlayerTurn;
}
}
}
RunState::ShowTargeting { range, item } => {
let result = gui::ranged_target(self, ctx, range);
match result.0 {
gui::ItemMenuResult::Cancel => new_run_state = RunState::AwaitingInput,
gui::ItemMenuResult::NoResponse => {}
gui::ItemMenuResult::Selected => {
let mut intent = self.ecs.write_storage::<WantsToUseItem>();
intent
.insert(
*self.ecs.fetch::<Entity>(),
WantsToUseItem {
item,
target: result.1,
},
)
.expect("Unable to insert intent");
new_run_state = RunState::PlayerTurn;
}
}
}
RunState::MainMenu { .. } => {
let result = gui::main_menu(self, ctx);
match result {
gui::MainMenuResult::NoSelection { selected } => {
new_run_state = RunState::MainMenu {
menu_selection: selected,
}
}
gui::MainMenuResult::Selected { selected } => match selected {
gui::MainMenuSelection::NewGame => new_run_state = RunState::PreRun,
gui::MainMenuSelection::LoadGame => {
save_load_system::load_game(&mut self.ecs);
new_run_state = RunState::AwaitingInput;
save_load_system::delete_save();
}
gui::MainMenuSelection::Quit => ::std::process::exit(0),
},
}
}
RunState::SaveGame => {
save_load_system::save_game(&mut self.ecs);
new_run_state = RunState::MainMenu {
menu_selection: gui::MainMenuSelection::Quit,
};
}
RunState::NextLevel => {
self.goto_next_level();
new_run_state = RunState::PreRun;
}
RunState::ShowRemoveItem => {
let result = gui::remove_item_menu(self, ctx);
match result.0 {
gui::ItemMenuResult::Cancel => new_run_state = RunState::AwaitingInput,
gui::ItemMenuResult::NoResponse => {}
gui::ItemMenuResult::Selected => {
let item_entity = result.1.unwrap();
let mut intent = self.ecs.write_storage::<WantsToRemoveItem>();
intent
.insert(
*self.ecs.fetch::<Entity>(),
WantsToRemoveItem { item: item_entity },
)
.expect("Unable to insert intent");
new_run_state = RunState::PlayerTurn;
}
}
}
RunState::GameOver => {
let result = gui::game_over(ctx);
match result {
gui::GameOverResult::NoSelection => {}
gui::GameOverResult::QuitToMenu => {
self.game_over_cleanup();
new_run_state = RunState::MainMenu {
menu_selection: gui::MainMenuSelection::NewGame,
};
}
}
}
RunState::MagicMapReveal { row } => {
let mut map = self.ecs.fetch_mut::<Map>();
for x in 0..MAP_WIDTH {
let idx = map.xy_idx(x as i32, row);
map.revealed_tiles[idx] = true;
}
if row as usize == MAP_HEIGHT - 1 {
new_run_state = RunState::MonsterTurn;
} else {
new_run_state = RunState::MagicMapReveal { row: row + 1 }
}
}
RunState::MapGeneration => {
if !SHOW_MAPGEN_VISUALIZER {
new_run_state = self.mapgen_next_state.unwrap();
}
ctx.cls();
draw_map(&self.mapgen_history[self.mapgen_index], ctx);
self.mapgen_timer += ctx.frame_time_ms;
if self.mapgen_timer > 300.0 {
self.mapgen_timer = 0.0;
self.mapgen_index += 1;
if self.mapgen_index >= self.mapgen_history.len() {
new_run_state = self.mapgen_next_state.unwrap();
}
}
}
}
{
let mut run_writer = self.ecs.write_resource::<RunState>();
*run_writer = new_run_state;
}
damage_system::delete_the_dead(&mut self.ecs);
}
}

84
src/trigger_system.rs Normal file
View File

@@ -0,0 +1,84 @@
use specs::prelude::*;
use crate::particle_system::ParticleBuilder;
use crate::{
EntityMoved, EntryTrigger, GameLog, Hidden, InflictsDamage, Map, Name, Position,
SingleActivation, SufferDamage,
};
pub struct TriggerSystem {}
impl<'a> System<'a> for TriggerSystem {
type SystemData = (
ReadExpect<'a, Map>,
WriteStorage<'a, EntityMoved>,
ReadStorage<'a, Position>,
ReadStorage<'a, EntryTrigger>,
WriteStorage<'a, Hidden>,
ReadStorage<'a, Name>,
Entities<'a>,
WriteExpect<'a, GameLog>,
ReadStorage<'a, InflictsDamage>,
WriteExpect<'a, ParticleBuilder>,
WriteStorage<'a, SufferDamage>,
WriteStorage<'a, SingleActivation>,
);
fn run(&mut self, data: Self::SystemData) {
let (
map,
mut entity_moved,
position,
entry_trigger,
mut hidden,
names,
entities,
mut game_log,
inflicts_damage,
mut particle_builder,
mut inflict_damage,
single_activation,
) = data;
let mut remove_entities: Vec<Entity> = Vec::new();
for (entity, mut _entity_moved, pos) in (&entities, &mut entity_moved, &position).join() {
let idx = map.xy_idx(pos.x, pos.y);
for entity_id in map.tile_content[idx].iter() {
if *entity_id == entity {
continue;
}
if let Some(_trigger) = entry_trigger.get(*entity_id) {
if let Some(name) = names.get(*entity_id) {
game_log.entries.push(format!("{} triggers", name.name))
}
hidden.remove(*entity_id);
if let Some(damage) = inflicts_damage.get(*entity_id) {
particle_builder.request(
pos.x,
pos.y,
rltk::RGB::named(rltk::ORANGE),
rltk::RGB::named(rltk::BLACK),
rltk::to_cp437('‼'),
200.0,
);
SufferDamage::new_damage(&mut inflict_damage, entity, damage.damage);
}
if let Some(_sa) = single_activation.get(*entity_id) {
remove_entities.push(*entity_id);
}
}
}
}
for trap in remove_entities.iter() {
entities.delete(*trap).expect("Could not delete trap");
}
entity_moved.clear();
}
}

View File

@@ -1,9 +1,10 @@
use rltk::{field_of_view, Point}; use rltk::{field_of_view, Point, RandomNumberGenerator};
use specs::prelude::*; use specs::prelude::*;
use crate::{ use crate::{
components::{Player, Position, Viewshed}, components::{Player, Position, Viewshed},
map::Map, map::Map,
GameLog, Hidden, Name,
}; };
pub struct VisibilitySystem {} pub struct VisibilitySystem {}
@@ -15,10 +16,24 @@ impl<'a> System<'a> for VisibilitySystem {
WriteStorage<'a, Viewshed>, WriteStorage<'a, Viewshed>,
WriteStorage<'a, Position>, WriteStorage<'a, Position>,
ReadStorage<'a, Player>, ReadStorage<'a, Player>,
WriteStorage<'a, Hidden>,
WriteExpect<'a, RandomNumberGenerator>,
ReadStorage<'a, Name>,
WriteExpect<'a, GameLog>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
let (mut map, entities, mut viewshed, pos, player) = data; let (
mut map,
entities,
mut viewshed,
pos,
player,
mut hidden,
mut rng,
names,
mut game_log,
) = data;
for (ent, viewshed, pos) in (&entities, &mut viewshed, &pos).join() { for (ent, viewshed, pos) in (&entities, &mut viewshed, &pos).join() {
if !viewshed.dirty { if !viewshed.dirty {
continue; continue;
@@ -39,6 +54,19 @@ impl<'a> System<'a> for VisibilitySystem {
let idx = map.xy_idx(vis.x, vis.y); let idx = map.xy_idx(vis.x, vis.y);
map.revealed_tiles[idx] = true; map.revealed_tiles[idx] = true;
map.visible_tiles[idx] = true; map.visible_tiles[idx] = true;
for e in map.tile_content[idx].iter() {
if let Some(_maybe_hidden) = hidden.get(*e) {
if rng.roll_dice(1, 24) == 1 {
if let Some(name) = names.get(*e) {
game_log
.entries
.push(format!("You spotted a {}.", &name.name));
}
hidden.remove(*e);
}
}
}
} }
} }
} }