Compare commits
108 Commits
3caf253441
...
renovate/a
Author | SHA1 | Date | |
---|---|---|---|
08da6c6315 | |||
441072811e | |||
f12f007b28 | |||
02b0a19aa4 | |||
675164df57 | |||
b6b97bf11f | |||
1d991ddaf5 | |||
2b12ab95e6 | |||
5720200e32 | |||
86636b1d89 | |||
697e2b765c | |||
594583f7f1 | |||
77ed37f853 | |||
308630d306 | |||
c389a0806a | |||
cdfebbec7b | |||
7296368bf4 | |||
6b3593a2f5 | |||
71d1a31d0f | |||
a47fd370d7 | |||
0fa1670aa0 | |||
a5988c9ce3 | |||
e64d6efd98 | |||
de56c96884 | |||
bcb749682f | |||
c331b04f0e | |||
3210ac3389 | |||
bf31ef75f6 | |||
a2cb3d7166 | |||
d85910bd20 | |||
977b40d5e8 | |||
05db5b566f | |||
d5c423e94f | |||
174ebb3aa5 | |||
15774db12d | |||
390406145f | |||
08e63fc99a | |||
fa8ced3b05 | |||
e60e895fe3 | |||
2123b32615 | |||
cd14b20f2e | |||
36ffbfed1d | |||
10c836d987 | |||
e98521c1c4 | |||
ffecdba71d | |||
269135318c | |||
56d6d88ef3 | |||
91dac008dc | |||
8fba60a5e8 | |||
aa596b8b81 | |||
e3561d3fe2 | |||
c1d19fa106 | |||
f7df41cbeb | |||
d4654d8113 | |||
0dda955141 | |||
b1e06dec5d | |||
0bbfe859a3 | |||
1f491c017c | |||
6accff6ca5 | |||
7a5341a986 | |||
212f8d870d | |||
41b8407f5d | |||
e85057ebfc | |||
02d92d928b | |||
b918f88db5 | |||
858d557a3b | |||
84fef1c758 | |||
62c225b5dd | |||
9c98b848f5 | |||
e2dbdc1bbf | |||
f91daaf46a | |||
931cf083e8 | |||
d4a6d9aef8 | |||
c03cd1208c | |||
99941e701b | |||
bf80ced3e0 | |||
a8e53b6e73 | |||
e35a8b14d6 | |||
3049210660 | |||
7e6309d777 | |||
eeb4b5f089 | |||
7bab0c7123
|
|||
596c04d791
|
|||
90d60ff54c | |||
162fb413a5 | |||
34c4aa35fc
|
|||
d4892e33bd
|
|||
5a00ebf1f1
|
|||
c1893aec74
|
|||
0511c00f09
|
|||
9c0e546887
|
|||
ce4eb0b4cf
|
|||
fa71410453
|
|||
0af23af3d3
|
|||
7c67291031
|
|||
835a416013
|
|||
62240a42c1
|
|||
fdeabefd75
|
|||
74c05f97af
|
|||
cb17a9c356
|
|||
0e2c91dd12
|
|||
743bf7abde
|
|||
aad1402f81
|
|||
fb5b70ce3b
|
|||
e68fa87fc9
|
|||
a1ffc7b95d
|
|||
f2f4c40c28
|
|||
ecbecc1a58
|
9
.drone.yml
Executable file
9
.drone.yml
Executable 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
8
.idea/.gitignore
generated
vendored
@@ -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
99
.idea/workspace.xml
generated
Normal 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>
|
202
Cargo.lock
generated
202
Cargo.lock
generated
@@ -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"
|
||||||
@@ -67,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"
|
||||||
@@ -114,6 +114,7 @@ dependencies = [
|
|||||||
"byteorder",
|
"byteorder",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -122,6 +123,7 @@ version = "0.8.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4db22c32c68bd9330ab982f8ff7ffe7b10541d16ea7d7d51aac074499850402b"
|
checksum = "4db22c32c68bd9330ab982f8ff7ffe7b10541d16ea7d7d51aac074499850402b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"serde",
|
||||||
"ultraviolet",
|
"ultraviolet",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -170,6 +172,7 @@ dependencies = [
|
|||||||
"rand",
|
"rand",
|
||||||
"rand_xorshift",
|
"rand_xorshift",
|
||||||
"regex",
|
"regex",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -443,9 +446,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-queue"
|
name = "crossbeam-queue"
|
||||||
version = "0.3.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 = "b979d76c9fcb84dffc80a73f7290da0f83e4c95773494674cb44b76d13a7a110"
|
checksum = "1cd42583b04998a5363558e5f9291ee5a5ff6b49944332103f251e7479a82aa7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"crossbeam-utils 0.8.6",
|
"crossbeam-utils 0.8.6",
|
||||||
@@ -499,7 +502,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strsim",
|
"strsim",
|
||||||
"syn",
|
"syn 1.0.105",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -510,7 +513,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.105",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -531,7 +534,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.105",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -751,15 +754,6 @@ dependencies = [
|
|||||||
"gl_generator",
|
"gl_generator",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.11.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
|
||||||
dependencies = [
|
|
||||||
"ahash",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[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",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -822,6 +815,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jni-sys"
|
name = "jni-sys"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@@ -915,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"
|
||||||
@@ -1016,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"
|
||||||
@@ -1058,7 +1067,7 @@ dependencies = [
|
|||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.105",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1123,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"
|
||||||
@@ -1193,7 +1223,7 @@ dependencies = [
|
|||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.105",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1263,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"
|
||||||
@@ -1304,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",
|
||||||
]
|
]
|
||||||
@@ -1367,6 +1403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
|
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand_core",
|
"rand_core",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1451,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",
|
||||||
]
|
]
|
||||||
@@ -1463,6 +1500,8 @@ name = "roguelike"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rltk",
|
"rltk",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"specs",
|
"specs",
|
||||||
"specs-derive",
|
"specs-derive",
|
||||||
]
|
]
|
||||||
@@ -1477,6 +1516,12 @@ dependencies = [
|
|||||||
"owned_ttf_parser",
|
"owned_ttf_parser",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "safe_arch"
|
name = "safe_arch"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@@ -1515,9 +1560,35 @@ checksum = "a0eddf2e8f50ced781f288c19f18621fa72a3779e3cb58dbf23b07469b0abeb4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.135"
|
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 = "2cf9235533494ea2ddcdb794665461814781c53f19d87b76e571a1c35acbad2b"
|
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.218"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.82",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.141"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shared_library"
|
name = "shared_library"
|
||||||
@@ -1531,13 +1602,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shred"
|
name = "shred"
|
||||||
version = "0.12.0"
|
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 = "eb0210289d693217926314867c807e0b7b42f7e23c136adb31f8697f5bf242d3"
|
checksum = "dc6b2cd1ccb08cf2b25d75c936e0cc9c8cb93c39a83814956da32653236338c0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"hashbrown",
|
"atomic_refcell",
|
||||||
"mopa",
|
|
||||||
"rayon",
|
"rayon",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"tynm",
|
"tynm",
|
||||||
@@ -1591,15 +1662,17 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "specs"
|
name = "specs"
|
||||||
version = "0.17.0"
|
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 = "5dcc1e4ba7ab1f08ecb3d7e2f693defc3907e2c03bb0924f9978be45b364f83f"
|
checksum = "a60eabdfd5a80e458c3e7bcc9f1076d6ce3cc8ddb71d69691f00fc0de735a635"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
"crossbeam-queue",
|
"crossbeam-queue",
|
||||||
"hashbrown",
|
|
||||||
"hibitset",
|
"hibitset",
|
||||||
"log",
|
"log",
|
||||||
|
"nougat",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
"serde",
|
||||||
"shred",
|
"shred",
|
||||||
"shrev",
|
"shrev",
|
||||||
"tuple_utils",
|
"tuple_utils",
|
||||||
@@ -1613,7 +1686,7 @@ checksum = "3e23e09360f3d2190fec4222cd9e19d3158d5da948c0d1ea362df617dd103511"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.105",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1624,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]]
|
||||||
@@ -1650,7 +1734,7 @@ checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.105",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1692,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"
|
||||||
@@ -1715,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"
|
||||||
@@ -1764,7 +1848,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.105",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1786,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",
|
||||||
]
|
]
|
||||||
|
@@ -6,6 +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"}
|
rltk = { version = "0.8.7", features = ["serde"] }
|
||||||
specs = "0.17.0"
|
specs = { version = "0.20.0", features = ["serde"] }
|
||||||
specs-derive = "0.4.1"
|
specs-derive = { version = "0.4.1" }
|
||||||
|
serde = { version = "^1.0.202", features = ["derive"] }
|
||||||
|
serde_json = "1.0.117"
|
||||||
|
3
renovate.json
Normal file
3
renovate.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||||
|
}
|
BIN
resources/SmallDungeon_80x50.xp
Normal file
BIN
resources/SmallDungeon_80x50.xp
Normal file
Binary file not shown.
1
savegame.json
Normal file
1
savegame.json
Normal file
File diff suppressed because one or more lines are too long
@@ -1,14 +1,29 @@
|
|||||||
use rltk::{Point, RGB};
|
use rltk::{Point, RGB};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use specs::error::NoError;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
use specs::saveload::{ConvertSaveload, Marker};
|
||||||
use specs_derive::*;
|
use specs_derive::*;
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component, ConvertSaveload, Clone)]
|
||||||
pub struct Position {
|
pub struct Position {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
pub y: i32,
|
pub y: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
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)]
|
||||||
pub struct Renderable {
|
pub struct Renderable {
|
||||||
pub glyph: rltk::FontCharType,
|
pub glyph: rltk::FontCharType,
|
||||||
pub fg: RGB,
|
pub fg: RGB,
|
||||||
@@ -16,28 +31,28 @@ pub struct Renderable {
|
|||||||
pub render_order: i32,
|
pub render_order: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Serialize, Deserialize, Clone)]
|
||||||
pub struct Player {}
|
pub struct Player {}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component, ConvertSaveload, Clone)]
|
||||||
pub struct Viewshed {
|
pub struct Viewshed {
|
||||||
pub visible_tiles: Vec<rltk::Point>,
|
pub visible_tiles: Vec<rltk::Point>,
|
||||||
pub range: i32,
|
pub range: i32,
|
||||||
pub dirty: bool,
|
pub dirty: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component, Serialize, Deserialize, Clone)]
|
||||||
pub struct Monster {}
|
pub struct Monster {}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
||||||
pub struct Name {
|
pub struct Name {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct BlocksTile {}
|
pub struct BlocksTile {}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
||||||
pub struct CombatStats {
|
pub struct CombatStats {
|
||||||
pub max_hp: i32,
|
pub max_hp: i32,
|
||||||
pub hp: i32,
|
pub hp: i32,
|
||||||
@@ -45,12 +60,12 @@ pub struct CombatStats {
|
|||||||
pub power: i32,
|
pub power: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug, Clone)]
|
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
||||||
pub struct WantsToMelee {
|
pub struct WantsToMelee {
|
||||||
pub target: Entity,
|
pub target: Entity,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
||||||
pub struct SufferDamage {
|
pub struct SufferDamage {
|
||||||
pub amount: Vec<i32>,
|
pub amount: Vec<i32>,
|
||||||
}
|
}
|
||||||
@@ -68,60 +83,160 @@ impl SufferDamage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Item {}
|
pub struct Item {}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
||||||
pub struct Potion {
|
pub struct Potion {
|
||||||
pub heal_amount: i32,
|
pub heal_amount: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
||||||
pub struct InBackpack {
|
pub struct InBackpack {
|
||||||
pub owner: Entity,
|
pub owner: Entity,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug, Clone)]
|
#[derive(Component, Debug, Clone, ConvertSaveload)]
|
||||||
pub struct WantsToPickupItem {
|
pub struct WantsToPickupItem {
|
||||||
pub collected_by: Entity,
|
pub collected_by: Entity,
|
||||||
pub item: Entity,
|
pub item: Entity,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug, Clone)]
|
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
||||||
pub struct WantsToUseItem {
|
pub struct WantsToUseItem {
|
||||||
pub item: Entity,
|
pub item: Entity,
|
||||||
pub target: Option<Point>,
|
pub target: Option<Point>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug, Clone)]
|
#[derive(Component, Debug, Clone, ConvertSaveload)]
|
||||||
pub struct WantsToDropItem {
|
pub struct WantsToDropItem {
|
||||||
pub item: Entity,
|
pub item: Entity,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Consumable {}
|
pub struct Consumable {}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
||||||
pub struct ProvidesHealing {
|
pub struct ProvidesHealing {
|
||||||
pub heal_amount: i32,
|
pub heal_amount: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[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)]
|
||||||
pub struct Ranged {
|
pub struct Ranged {
|
||||||
pub range: i32,
|
pub range: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug, Clone, ConvertSaveload)]
|
||||||
pub struct InflictsDamage {
|
pub struct InflictsDamage {
|
||||||
pub damage: i32,
|
pub damage: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
||||||
pub struct AreaOfEffect {
|
pub struct AreaOfEffect {
|
||||||
pub radius: i32,
|
pub radius: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
||||||
pub struct Confusion {
|
pub struct Confusion {
|
||||||
pub turns: i32,
|
pub turns: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SerializeMe;
|
||||||
|
|
||||||
|
#[derive(Component, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct SerializationHelper {
|
||||||
|
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 {}
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
318
src/gui.rs
318
src/gui.rs
@@ -1,14 +1,15 @@
|
|||||||
use rltk::Rltk;
|
use rltk::Rltk;
|
||||||
use rltk::RGB;
|
use rltk::RGB;
|
||||||
use rltk::{BTerm, Point, VirtualKeyCode};
|
use rltk::{Point, VirtualKeyCode};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
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, InBackpack, State, Viewshed};
|
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());
|
||||||
@@ -388,3 +426,271 @@ pub fn ranged_target(
|
|||||||
|
|
||||||
(ItemMenuResult::NoResponse, None)
|
(ItemMenuResult::NoResponse, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Copy, Clone)]
|
||||||
|
pub enum MainMenuSelection {
|
||||||
|
NewGame,
|
||||||
|
LoadGame,
|
||||||
|
Quit,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Copy, Clone)]
|
||||||
|
pub enum MainMenuResult {
|
||||||
|
NoSelection { selected: MainMenuSelection },
|
||||||
|
Selected { selected: MainMenuSelection },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main_menu(gs: &mut State, ctx: &mut Rltk) -> MainMenuResult {
|
||||||
|
let save_exists = super::save_load_system::does_save_exist();
|
||||||
|
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(
|
||||||
|
20,
|
||||||
|
RGB::named(rltk::YELLOW),
|
||||||
|
RGB::named(rltk::BLACK),
|
||||||
|
"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 {
|
||||||
|
menu_selection: selection,
|
||||||
|
} = *runstate
|
||||||
|
{
|
||||||
|
if selection == MainMenuSelection::NewGame {
|
||||||
|
ctx.print_color_centered(
|
||||||
|
y,
|
||||||
|
RGB::named(rltk::MAGENTA),
|
||||||
|
RGB::named(rltk::BLACK),
|
||||||
|
"Begin New Game",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ctx.print_color_centered(
|
||||||
|
y,
|
||||||
|
RGB::named(rltk::WHITE),
|
||||||
|
RGB::named(rltk::BLACK),
|
||||||
|
"Begin New Game",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
y += 1;
|
||||||
|
|
||||||
|
if save_exists {
|
||||||
|
if selection == MainMenuSelection::LoadGame {
|
||||||
|
ctx.print_color_centered(
|
||||||
|
y,
|
||||||
|
RGB::named(rltk::MAGENTA),
|
||||||
|
RGB::named(rltk::BLACK),
|
||||||
|
"Load Game",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ctx.print_color_centered(
|
||||||
|
y,
|
||||||
|
RGB::named(rltk::WHITE),
|
||||||
|
RGB::named(rltk::BLACK),
|
||||||
|
"Load Game",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
y += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection == MainMenuSelection::Quit {
|
||||||
|
ctx.print_color_centered(
|
||||||
|
y,
|
||||||
|
RGB::named(rltk::MAGENTA),
|
||||||
|
RGB::named(rltk::BLACK),
|
||||||
|
"Quit",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ctx.print_color_centered(y, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "Quit");
|
||||||
|
}
|
||||||
|
|
||||||
|
return match ctx.key {
|
||||||
|
None => MainMenuResult::NoSelection {
|
||||||
|
selected: selection,
|
||||||
|
},
|
||||||
|
Some(key) => match key {
|
||||||
|
VirtualKeyCode::Escape => MainMenuResult::NoSelection {
|
||||||
|
selected: MainMenuSelection::Quit,
|
||||||
|
},
|
||||||
|
VirtualKeyCode::Up | VirtualKeyCode::K => {
|
||||||
|
let newselection = match selection {
|
||||||
|
MainMenuSelection::NewGame => MainMenuSelection::Quit,
|
||||||
|
MainMenuSelection::LoadGame => MainMenuSelection::NewGame,
|
||||||
|
MainMenuSelection::Quit => MainMenuSelection::LoadGame,
|
||||||
|
};
|
||||||
|
MainMenuResult::NoSelection {
|
||||||
|
selected: newselection,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VirtualKeyCode::Down | VirtualKeyCode::J => {
|
||||||
|
let newselection = match selection {
|
||||||
|
MainMenuSelection::NewGame => MainMenuSelection::LoadGame,
|
||||||
|
MainMenuSelection::LoadGame => MainMenuSelection::Quit,
|
||||||
|
MainMenuSelection::Quit => MainMenuSelection::NewGame,
|
||||||
|
};
|
||||||
|
MainMenuResult::NoSelection {
|
||||||
|
selected: newselection,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VirtualKeyCode::Return | VirtualKeyCode::I => MainMenuResult::Selected {
|
||||||
|
selected: selection,
|
||||||
|
},
|
||||||
|
_ => MainMenuResult::NoSelection {
|
||||||
|
selected: selection,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
MainMenuResult::NoSelection {
|
||||||
|
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
19
src/healing_system.rs
Normal 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
91
src/hunger_system.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,14 +1,18 @@
|
|||||||
use rltk::Rltk;
|
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
use crate::{AreaOfEffect, CombatStats, Confusion, Consumable, InBackpack, InflictsDamage, Map, Name, Position, Potion, ProvidesHealing, Ranged, State, SufferDamage, WantsToDropItem, WantsToPickupItem, WantsToUseItem};
|
|
||||||
use crate::gamelog::GameLog;
|
use crate::gamelog::GameLog;
|
||||||
use crate::gui::ItemMenuResult;
|
use crate::particle_system::ParticleBuilder;
|
||||||
use crate::spawner::{confusion_scroll, player};
|
use crate::{
|
||||||
|
AreaOfEffect, CombatStats, Confusion, Consumable, Equippable, Equipped, HungerClock,
|
||||||
|
HungerState, InBackpack, InflictsDamage, MagicMapper, Map, Name, Position, ProvidesFood,
|
||||||
|
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>,
|
||||||
@@ -48,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>,
|
||||||
@@ -59,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) {
|
||||||
@@ -76,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() {
|
||||||
@@ -88,7 +111,7 @@ impl<'a> System<'a> for ItemUseSystem {
|
|||||||
|
|
||||||
let mut targets: Vec<Entity> = Vec::new();
|
let mut targets: Vec<Entity> = Vec::new();
|
||||||
match use_item.target {
|
match use_item.target {
|
||||||
None => { targets.push(*player_entity) }
|
None => targets.push(*player_entity),
|
||||||
Some(target) => {
|
Some(target) => {
|
||||||
let area_effect = aoe.get(use_item.item);
|
let area_effect = aoe.get(use_item.item);
|
||||||
match area_effect {
|
match area_effect {
|
||||||
@@ -99,19 +122,89 @@ impl<'a> System<'a> for ItemUseSystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(area_effect) => {
|
Some(area_effect) => {
|
||||||
let mut black_tiles = rltk::field_of_view(target, area_effect.radius, &*map);
|
let mut black_tiles =
|
||||||
black_tiles.retain(|p| p.x > 0 && p.x < map.width - 1 && p.y > 0 && p.y < map.height - 1);
|
rltk::field_of_view(target, area_effect.radius, &*map);
|
||||||
|
black_tiles.retain(|p| {
|
||||||
|
p.x > 0 && p.x < map.width - 1 && p.y > 0 && p.y < map.height - 1
|
||||||
|
});
|
||||||
for tile_idx in black_tiles.iter() {
|
for tile_idx in black_tiles.iter() {
|
||||||
let idx = map.xy_idx(tile_idx.x, tile_idx.y);
|
let idx = map.xy_idx(tile_idx.x, tile_idx.y);
|
||||||
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() {
|
||||||
@@ -119,7 +212,21 @@ impl<'a> System<'a> for ItemUseSystem {
|
|||||||
if entity == *player_entity {
|
if entity == *player_entity {
|
||||||
let mob_name = names.get(*mob).unwrap();
|
let mob_name = names.get(*mob).unwrap();
|
||||||
let item_name = names.get(use_item.item).unwrap();
|
let item_name = names.get(use_item.item).unwrap();
|
||||||
game_log.entries.push(format!("You use {} on {}, inflicting {} hp.", item_name.name, mob_name.name, item_damages.damage));
|
game_log.entries.push(format!(
|
||||||
|
"You use {} on {}, inflicting {} hp.",
|
||||||
|
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;
|
||||||
@@ -140,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.,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,16 +270,31 @@ impl<'a> System<'a> for ItemUseSystem {
|
|||||||
if entity == *player_entity {
|
if entity == *player_entity {
|
||||||
let mob_name = names.get(*mob).unwrap();
|
let mob_name = names.get(*mob).unwrap();
|
||||||
let item_name = names.get(use_item.item).unwrap();
|
let item_name = names.get(use_item.item).unwrap();
|
||||||
game_log.entries.push(format!("You use {} on {}, confusing them.", item_name.name, mob_name.name));
|
game_log.entries.push(format!(
|
||||||
|
"You use {} on {}, confusing them.",
|
||||||
|
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.,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for mob in add_confusion.iter() {
|
for mob in add_confusion.iter() {
|
||||||
confused.insert(mob.0, Confusion { turns: mob.1 }).expect("Unable to insert status");
|
confused
|
||||||
|
.insert(mob.0, Confusion { turns: mob.1 })
|
||||||
|
.expect("Unable to insert status");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if used_item {
|
if used_item {
|
||||||
let consumable = consumables.get(use_item.item);
|
let consumable = consumables.get(use_item.item);
|
||||||
match consumable {
|
match consumable {
|
||||||
@@ -231,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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
226
src/main.rs
226
src/main.rs
@@ -1,7 +1,8 @@
|
|||||||
use std::thread::spawn;
|
extern crate serde;
|
||||||
|
|
||||||
use rltk::{GameState, Point, RGB};
|
use rltk::{GameState, Point};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
use specs::saveload::{SimpleMarker, SimpleMarkerAllocator};
|
||||||
|
|
||||||
use components::*;
|
use components::*;
|
||||||
use damage_system::DamageSystem;
|
use damage_system::DamageSystem;
|
||||||
@@ -12,176 +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 rex_assets;
|
||||||
|
mod save_load_system;
|
||||||
mod spawner;
|
mod spawner;
|
||||||
|
mod state;
|
||||||
|
mod trigger_system;
|
||||||
mod visibility_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 },
|
|
||||||
}
|
|
||||||
|
|
||||||
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 mapindex = MapIndexingSystem {};
|
|
||||||
mapindex.run_now(&self.ecs);
|
|
||||||
|
|
||||||
let mut meleesystem = MeleeCombatSystem {};
|
|
||||||
meleesystem.run_now(&self.ecs);
|
|
||||||
|
|
||||||
let mut damagesystem = DamageSystem {};
|
|
||||||
damagesystem.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) {
|
|
||||||
ctx.cls();
|
|
||||||
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut newrunstate;
|
|
||||||
{
|
|
||||||
let runstate = self.ecs.fetch::<RunState>();
|
|
||||||
newrunstate = *runstate;
|
|
||||||
}
|
|
||||||
|
|
||||||
match newrunstate {
|
|
||||||
RunState::PreRun => {
|
|
||||||
self.run_systems();
|
|
||||||
newrunstate = RunState::AwaitingInput;
|
|
||||||
}
|
|
||||||
RunState::AwaitingInput => newrunstate = player_input(self, ctx),
|
|
||||||
RunState::PlayerTurn => {
|
|
||||||
self.run_systems();
|
|
||||||
newrunstate = RunState::MonsterTurn;
|
|
||||||
}
|
|
||||||
RunState::MonsterTurn => {
|
|
||||||
self.run_systems();
|
|
||||||
newrunstate = RunState::AwaitingInput;
|
|
||||||
}
|
|
||||||
RunState::ShowInventory => {
|
|
||||||
let result = gui::show_inventory(self, ctx);
|
|
||||||
match result.0 {
|
|
||||||
gui::ItemMenuResult::Cancel => newrunstate = 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 {
|
|
||||||
newrunstate = 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");
|
|
||||||
newrunstate = RunState::PlayerTurn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RunState::ShowDropItem => {
|
|
||||||
let result = gui::drop_item_menu(self, ctx);
|
|
||||||
match result.0 {
|
|
||||||
gui::ItemMenuResult::Cancel => newrunstate = 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");
|
|
||||||
newrunstate = RunState::PlayerTurn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RunState::ShowTargeting { range, item } => {
|
|
||||||
let result = gui::ranged_target(self, ctx, range);
|
|
||||||
match result.0 {
|
|
||||||
gui::ItemMenuResult::Cancel => newrunstate = 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");
|
|
||||||
newrunstate = RunState::PlayerTurn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut runwriter = self.ecs.write_resource::<RunState>();
|
|
||||||
*runwriter = newrunstate;
|
|
||||||
}
|
|
||||||
damage_system::delete_the_dead(&mut self.ecs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> rltk::BError {
|
fn main() -> rltk::BError {
|
||||||
use rltk::RltkBuilder;
|
use rltk::RltkBuilder;
|
||||||
@@ -191,13 +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(RunState::PreRun);
|
|
||||||
gs.ecs.insert(rltk::RandomNumberGenerator::new());
|
|
||||||
gs.ecs.insert(gamelog::GameLog {
|
|
||||||
entries: vec!["Welcome to Rusty Roguelike".to_string()],
|
|
||||||
});
|
|
||||||
|
|
||||||
gs.ecs.register::<Position>();
|
gs.ecs.register::<Position>();
|
||||||
gs.ecs.register::<Renderable>();
|
gs.ecs.register::<Renderable>();
|
||||||
@@ -220,18 +80,38 @@ fn main() -> rltk::BError {
|
|||||||
gs.ecs.register::<InflictsDamage>();
|
gs.ecs.register::<InflictsDamage>();
|
||||||
gs.ecs.register::<AreaOfEffect>();
|
gs.ecs.register::<AreaOfEffect>();
|
||||||
gs.ecs.register::<Confusion>();
|
gs.ecs.register::<Confusion>();
|
||||||
let map = Map::new_map_rooms_and_corridors();
|
gs.ecs.register::<SimpleMarker<SerializeMe>>();
|
||||||
|
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>();
|
||||||
|
|
||||||
let (player_x, player_y) = map.rooms[0].center();
|
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());
|
||||||
gs.ecs.insert(Point::new(player_x, player_y));
|
|
||||||
let player_entity = spawner::player(&mut gs.ecs, player_x, player_y);
|
gs.ecs.insert(Map::new(1));
|
||||||
|
gs.ecs.insert(Point::new(0, 0));
|
||||||
|
gs.ecs.insert(rltk::RandomNumberGenerator::new());
|
||||||
|
let player_entity = spawner::player(&mut gs.ecs, 0, 0);
|
||||||
gs.ecs.insert(player_entity);
|
gs.ecs.insert(player_entity);
|
||||||
|
gs.ecs.insert(RunState::MapGeneration {});
|
||||||
|
gs.ecs.insert(gamelog::GameLog {
|
||||||
|
entries: vec!["Welcome to Rusty Roguelike".to_string()],
|
||||||
|
});
|
||||||
|
gs.ecs.insert(particle_system::ParticleBuilder::new());
|
||||||
|
gs.ecs.insert(rex_assets::RexAssets::new());
|
||||||
|
|
||||||
for room in map.rooms.iter().skip(1) {
|
gs.generate_world_map(1);
|
||||||
spawner::spawn_room(&mut gs.ecs, room);
|
|
||||||
}
|
|
||||||
|
|
||||||
gs.ecs.insert(map);
|
|
||||||
|
|
||||||
rltk::main_loop(context, gs)
|
rltk::main_loop(context, gs)
|
||||||
}
|
}
|
||||||
|
192
src/map.rs
192
src/map.rs
@@ -1,30 +1,34 @@
|
|||||||
use std::cmp::{max, min};
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use rltk::{Algorithm2D, BaseMap, Point, RandomNumberGenerator, Rltk, RGB};
|
use rltk::{Algorithm2D, BaseMap, FontCharType, Point, Rltk, RGB};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
use crate::rect::Rect;
|
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||||
|
|
||||||
#[derive(PartialEq, Copy, Clone)]
|
|
||||||
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)]
|
||||||
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_deserializing)]
|
||||||
pub tile_content: Vec<Vec<Entity>>,
|
pub tile_content: Vec<Vec<Entity>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,85 +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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_map_rooms_and_corridors() -> Map {
|
|
||||||
let mut map = Map {
|
|
||||||
tiles: vec![TileType::Wall; MAP_COUNT],
|
|
||||||
rooms: Vec::new(),
|
|
||||||
width: MAP_WIDTH as i32,
|
|
||||||
height: MAP_HEIGHT as i32,
|
|
||||||
revealed_tiles: vec![false; MAP_COUNT],
|
|
||||||
visible_tiles: vec![false; MAP_COUNT],
|
|
||||||
blocked: vec![false; MAP_COUNT],
|
|
||||||
tile_content: vec![Vec::new(); MAP_COUNT],
|
|
||||||
};
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
||||||
@@ -131,17 +56,25 @@ impl Map {
|
|||||||
content.clear();
|
content.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Algorithm2D for Map {
|
pub fn new(new_depth: i32) -> Map {
|
||||||
fn dimensions(&self) -> rltk::Point {
|
Map {
|
||||||
Point::new(self.width, self.height)
|
tiles: vec![TileType::Wall; MAP_COUNT],
|
||||||
|
width: MAP_WIDTH as i32,
|
||||||
|
height: MAP_HEIGHT as i32,
|
||||||
|
revealed_tiles: vec![false; MAP_COUNT],
|
||||||
|
visible_tiles: vec![false; MAP_COUNT],
|
||||||
|
blocked: vec![false; MAP_COUNT],
|
||||||
|
tile_content: vec![Vec::new(); MAP_COUNT],
|
||||||
|
depth: new_depth,
|
||||||
|
bloodstains: HashSet::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BaseMap for Map {
|
impl BaseMap for Map {
|
||||||
fn is_opaque(&self, idx: usize) -> bool {
|
fn is_opaque(&self, idx: usize) -> bool {
|
||||||
self.tiles[idx as usize] == TileType::Wall
|
self.tiles[idx] == TileType::Wall
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_pathing_distance(&self, idx1: usize, idx2: usize) -> f32 {
|
fn get_pathing_distance(&self, idx1: usize, idx2: usize) -> f32 {
|
||||||
@@ -187,36 +120,95 @@ impl BaseMap for Map {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_map(ecs: &World, ctx: &mut Rltk) {
|
impl Algorithm2D for Map {
|
||||||
let map = ecs.fetch::<Map>();
|
fn dimensions(&self) -> rltk::Point {
|
||||||
|
Point::new(self.width, self.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_map(map: &Map, ctx: &mut Rltk) {
|
||||||
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.5, 0.5, 0.5);
|
fg = RGB::from_f32(0.0, 0.5, 0.5);
|
||||||
glyph = rltk::to_cp437('.');
|
glyph = rltk::to_cp437('.');
|
||||||
}
|
}
|
||||||
|
|
||||||
TileType::Wall => {
|
TileType::Wall => {
|
||||||
fg = RGB::from_f32(0.0, 1.0, 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.visible_tiles[idx] {
|
if map.bloodstains.contains(&idx) {
|
||||||
fg = fg.to_greyscale()
|
bg = RGB::from_f32(0.71, 0., 0.);
|
||||||
}
|
}
|
||||||
ctx.set(x, y, fg, RGB::from_f32(0., 0., 0.), glyph);
|
if !map.visible_tiles[idx] {
|
||||||
|
fg = fg.to_greyscale();
|
||||||
|
bg = RGB::from_f32(0., 0., 0.);
|
||||||
|
}
|
||||||
|
ctx.set(x, y, fg, bg, glyph);
|
||||||
}
|
}
|
||||||
|
|
||||||
x += 1;
|
x += 1;
|
||||||
if x > 79 {
|
if x > MAP_WIDTH as i32 - 1 {
|
||||||
x = 0;
|
x = 0;
|
||||||
y += 1;
|
y += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
22
src/map_builders/bsp/mod.rs
Normal file
22
src/map_builders/bsp/mod.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
214
src/map_builders/bsp_dungeon.rs
Normal file
214
src/map_builders/bsp_dungeon.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
158
src/map_builders/bsp_interior.rs
Normal file
158
src/map_builders/bsp_interior.rs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
168
src/map_builders/cellular_automata.rs
Normal file
168
src/map_builders/cellular_automata.rs
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
90
src/map_builders/common.rs
Normal file
90
src/map_builders/common.rs
Normal 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
|
||||||
|
}
|
180
src/map_builders/drunkard.rs
Normal file
180
src/map_builders/drunkard.rs
Normal 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
40
src/map_builders/mod.rs
Normal 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)),
|
||||||
|
}
|
||||||
|
}
|
111
src/map_builders/simple_map.rs
Normal file
111
src/map_builders/simple_map.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,12 +1,14 @@
|
|||||||
use crate::gamelog::GameLog;
|
|
||||||
use rltk::console;
|
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
use crate::components::{CombatStats, Name, SufferDamage, WantsToMelee};
|
use crate::components::{CombatStats, Name, SufferDamage, WantsToMelee};
|
||||||
|
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>,
|
||||||
@@ -14,34 +16,93 @@ 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 {
|
||||||
let target_stats = combat_stats.get(wants_melee.target).unwrap();
|
continue;
|
||||||
if target_stats.hp > 0 {
|
}
|
||||||
let target_name = names.get(wants_melee.target).unwrap();
|
|
||||||
|
|
||||||
let damage = i32::max(0, stats.power - target_stats.defense);
|
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 damage == 0 {
|
if let Some(hc) = hunger_clock.get(entity) {
|
||||||
log.entries.push(format!(
|
if hc.state == HungerState::WellFed {
|
||||||
"{} is unable to hurt {}",
|
offensive_bonus += 1;
|
||||||
&name.name, &target_name.name
|
}
|
||||||
));
|
}
|
||||||
} else {
|
|
||||||
log.entries.push(format!(
|
let target_stats = combat_stats.get(wants_melee.target).unwrap();
|
||||||
"{} hits {}, for {} hp.",
|
if target_stats.hp > 0 {
|
||||||
&name.name, &target_name.name, damage
|
let target_name = names.get(wants_melee.target).unwrap();
|
||||||
));
|
|
||||||
SufferDamage::new_damage(&mut inflict_damage, wants_melee.target, damage)
|
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 {
|
||||||
|
log.entries.push(format!(
|
||||||
|
"{} is unable to hurt {}",
|
||||||
|
&name.name, &target_name.name
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
log.entries.push(format!(
|
||||||
|
"{} hits {}, for {} hp.",
|
||||||
|
&name.name, &target_name.name, damage
|
||||||
|
));
|
||||||
|
SufferDamage::new_damage(&mut inflict_damage, wants_melee.target, damage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,7 +1,12 @@
|
|||||||
use rltk::{console, Point};
|
use rltk::Point;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
use crate::{components::{Monster, Position, Viewshed, WantsToMelee}, Confusion, map::Map, RunState};
|
use crate::particle_system::ParticleBuilder;
|
||||||
|
use crate::{
|
||||||
|
components::{Monster, Position, Viewshed, WantsToMelee},
|
||||||
|
map::Map,
|
||||||
|
Confusion, EntityMoved, RunState,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct MonsterAI {}
|
pub struct MonsterAI {}
|
||||||
|
|
||||||
@@ -18,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) {
|
||||||
@@ -32,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 {
|
||||||
@@ -39,7 +48,7 @@ impl<'a> System<'a> for MonsterAI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (entity, mut viewshed, _monster, mut pos) in
|
for (entity, mut viewshed, _monster, mut pos) in
|
||||||
(&entities, &mut viewshed, &monster, &mut position).join()
|
(&entities, &mut viewshed, &monster, &mut position).join()
|
||||||
{
|
{
|
||||||
let mut can_act = true;
|
let mut can_act = true;
|
||||||
|
|
||||||
@@ -49,10 +58,19 @@ 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 {
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let distance =
|
let distance =
|
||||||
@@ -80,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
119
src/particle_system.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@@ -1,13 +1,15 @@
|
|||||||
use std::cmp::{max, min};
|
use std::cmp::{max, min};
|
||||||
|
|
||||||
use rltk::{console, Point, Rltk, VirtualKeyCode};
|
use rltk::{Point, Rltk, VirtualKeyCode};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
use crate::gamelog::GameLog;
|
use crate::gamelog::GameLog;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
components::{CombatStats, Player, Position, Viewshed, WantsToMelee},
|
components::{CombatStats, Player, Position, Viewshed, WantsToMelee},
|
||||||
map::{Map, TileType},
|
map::Map,
|
||||||
Item, RunState, State, WantsToPickupItem,
|
EntityMoved, HungerClock, HungerState, Item, Monster, RunState, State, TileType,
|
||||||
|
WantsToPickupItem,
|
||||||
};
|
};
|
||||||
|
|
||||||
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) {
|
||||||
@@ -18,10 +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;
|
||||||
|
}
|
||||||
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() {
|
||||||
@@ -46,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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,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),
|
||||||
@@ -99,9 +114,72 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState {
|
|||||||
VirtualKeyCode::G => get_item(&mut gs.ecs),
|
VirtualKeyCode::G => get_item(&mut gs.ecs),
|
||||||
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::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
57
src/random_table.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
@@ -1,3 +1,6 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||||
pub struct Rect {
|
pub struct Rect {
|
||||||
pub x1: i32,
|
pub x1: i32,
|
||||||
pub x2: i32,
|
pub x2: i32,
|
||||||
|
18
src/rex_assets.rs
Normal file
18
src/rex_assets.rs
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
207
src/save_load_system.rs
Normal file
207
src/save_load_system.rs
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use specs::error::NoError;
|
||||||
|
use specs::prelude::*;
|
||||||
|
use specs::saveload::{
|
||||||
|
DeserializeComponents, MarkedBuilder, SerializeComponents, SimpleMarker, SimpleMarkerAllocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::components::*;
|
||||||
|
|
||||||
|
macro_rules! serialize_individually {
|
||||||
|
($ecs:expr, $ser:expr, $data:expr, $( $type:ty),*) => {
|
||||||
|
$(
|
||||||
|
SerializeComponents::<NoError, SimpleMarker<SerializeMe>>::serialize(
|
||||||
|
&( $ecs.read_storage::<$type>(), ),
|
||||||
|
&$data.0,
|
||||||
|
&$data.1,
|
||||||
|
&mut $ser,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pub fn save_game(_ecs: &mut World) {}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub fn save_game(ecs: &mut World) {
|
||||||
|
// Create helper
|
||||||
|
let mapcopy = ecs.get_mut::<super::map::Map>().unwrap().clone();
|
||||||
|
let savehelper = ecs
|
||||||
|
.create_entity()
|
||||||
|
.with(SerializationHelper { map: mapcopy })
|
||||||
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Actually serialize
|
||||||
|
{
|
||||||
|
let data = (
|
||||||
|
ecs.entities(),
|
||||||
|
ecs.read_storage::<SimpleMarker<SerializeMe>>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let writer = File::create("./savegame.json").unwrap();
|
||||||
|
let mut serializer = serde_json::Serializer::new(writer);
|
||||||
|
serialize_individually!(
|
||||||
|
ecs,
|
||||||
|
serializer,
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
ecs.delete_entity(savehelper).expect("Crash on cleanup");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn does_save_exist() -> bool {
|
||||||
|
Path::new("./savegame.json").exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! deserialize_individually {
|
||||||
|
($ecs:expr, $de:expr, $data:expr, $( $type:ty),*) => {
|
||||||
|
$(
|
||||||
|
DeserializeComponents::<NoError, _>::deserialize(
|
||||||
|
&mut ( &mut $ecs.write_storage::<$type>(), ),
|
||||||
|
&$data.0, // entities
|
||||||
|
&mut $data.1, // marker
|
||||||
|
&mut $data.2, // allocater
|
||||||
|
&mut $de,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_game(ecs: &mut World) {
|
||||||
|
{
|
||||||
|
// Delete everything
|
||||||
|
let mut to_delete = Vec::new();
|
||||||
|
for e in ecs.entities().join() {
|
||||||
|
to_delete.push(e);
|
||||||
|
}
|
||||||
|
for del in to_delete.iter() {
|
||||||
|
ecs.delete_entity(*del).expect("Deletion failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = fs::read_to_string("./savegame.json").unwrap();
|
||||||
|
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>>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
deserialize_individually!(
|
||||||
|
ecs,
|
||||||
|
de,
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut deleteme: Option<Entity> = None;
|
||||||
|
{
|
||||||
|
let entities = ecs.entities();
|
||||||
|
let helper = ecs.read_storage::<SerializationHelper>();
|
||||||
|
let player = ecs.read_storage::<Player>();
|
||||||
|
let position = ecs.read_storage::<Position>();
|
||||||
|
for (e, h) in (&entities, &helper).join() {
|
||||||
|
let mut worldmap = ecs.write_resource::<super::map::Map>();
|
||||||
|
*worldmap = h.map.clone();
|
||||||
|
worldmap.tile_content = vec![Vec::new(); super::map::MAP_COUNT];
|
||||||
|
deleteme = Some(e);
|
||||||
|
}
|
||||||
|
for (e, _p, pos) in (&entities, &player, &position).join() {
|
||||||
|
let mut ppos = ecs.write_resource::<rltk::Point>();
|
||||||
|
*ppos = rltk::Point::new(pos.x, pos.y);
|
||||||
|
let mut player_resource = ecs.write_resource::<Entity>();
|
||||||
|
*player_resource = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ecs.delete_entity(deleteme.unwrap())
|
||||||
|
.expect("Unable to delete helper");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_save() {
|
||||||
|
if Path::new("./savegame.json").exists() {
|
||||||
|
std::fs::remove_file("./savegame.json").expect("Unable to delete file");
|
||||||
|
}
|
||||||
|
}
|
400
src/spawner.rs
400
src/spawner.rs
@@ -1,8 +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 crate::{AreaOfEffect, BlocksTile, CombatStats, Confusion, Consumable, InflictsDamage, Item, MAP_WIDTH, MAX_ITEMS, MAX_MONSTER, Monster, Name, Player, Position, Potion, ProvidesHealing, Ranged, Renderable, 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()
|
||||||
@@ -31,21 +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>>()
|
||||||
.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")
|
||||||
}
|
}
|
||||||
@@ -79,55 +81,83 @@ fn monster<S: ToString>(ecs: &mut World, x: i32, y: i32, glyph: FontCharType, na
|
|||||||
defense: 1,
|
defense: 1,
|
||||||
power: 4,
|
power: 4,
|
||||||
})
|
})
|
||||||
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
.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 mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
let map = ecs.fetch::<Map>();
|
||||||
let num_monsters = rng.roll_dice(1, MAX_MONSTER + 2) - 3;
|
for y in room.y1 + 1..room.y2 {
|
||||||
let num_items = rng.roll_dice(1, MAX_ITEMS + 2) - 3;
|
for x in room.x1 + 1..room.x2 {
|
||||||
|
let idx = map.xy_idx(x, y);
|
||||||
for _i in 0..num_monsters {
|
if map.tiles[idx] == TileType::Floor {
|
||||||
let mut added = false;
|
possible_targets.push(idx)
|
||||||
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 !monster_spawn_points.contains(&idx) {
|
|
||||||
monster_spawn_points.push(idx);
|
|
||||||
added = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _i in 0..num_items {
|
|
||||||
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() {
|
spawn_region(ecs, &possible_targets, map_depth);
|
||||||
let x = *idx % MAP_WIDTH;
|
}
|
||||||
let y = *idx / MAP_WIDTH;
|
|
||||||
random_monster(ecs, x as i32, y as i32);
|
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 num_spawns = i32::min(areas.len() as i32, rng.roll_dice(1, MAX_MONSTER + 3));
|
||||||
|
if num_spawns == 0 { return None; }
|
||||||
|
|
||||||
|
for _i in 0..num_spawns {
|
||||||
|
let array_idx = if areas.len() == 1 { 0usize } else { (rng.roll_dice(1, areas.len() as i32 - 1) as usize) };
|
||||||
|
let map_idx = areas[array_idx];
|
||||||
|
spawn_points.insert(map_idx, spawn_table.roll(&mut rng));
|
||||||
|
areas.remove(array_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx in item_spawn_points.iter() {
|
Some(spawn_points)
|
||||||
let x = *idx % MAP_WIDTH;
|
}
|
||||||
let y = *idx / MAP_WIDTH;
|
|
||||||
random_item(ecs, x as i32, y as i32);
|
fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) {
|
||||||
|
let x = (*spawn.0 % MAP_WIDTH) as i32;
|
||||||
|
let y = (*spawn.0 / MAP_WIDTH) as i32;
|
||||||
|
|
||||||
|
match spawn.1.as_ref() {
|
||||||
|
"Goblin" => goblin(ecs, x, y),
|
||||||
|
"Orc" => orc(ecs, x, y),
|
||||||
|
"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),
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +176,7 @@ pub fn health_potion(ecs: &mut World, x: i32, y: i32) {
|
|||||||
.with(Item {})
|
.with(Item {})
|
||||||
.with(Consumable {})
|
.with(Consumable {})
|
||||||
.with(ProvidesHealing { heal_amount: 8 })
|
.with(ProvidesHealing { heal_amount: 8 })
|
||||||
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +195,8 @@ pub fn magic_missile_scroll(ecs: &mut World, x: i32, y: i32) {
|
|||||||
.with(Item {})
|
.with(Item {})
|
||||||
.with(Consumable {})
|
.with(Consumable {})
|
||||||
.with(Ranged { range: 6 })
|
.with(Ranged { range: 6 })
|
||||||
.with(InflictsDamage { damage: 8 })
|
.with(InflictsDamage { damage: 20 })
|
||||||
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +205,7 @@ pub fn fireball_scroll(ecs: &mut World, x: i32, y: i32) {
|
|||||||
.with(Position { x, y })
|
.with(Position { x, y })
|
||||||
.with(Renderable {
|
.with(Renderable {
|
||||||
glyph: rltk::to_cp437(')'),
|
glyph: rltk::to_cp437(')'),
|
||||||
fg: RGB::named(rltk::CYAN),
|
fg: RGB::named(rltk::ORANGE),
|
||||||
bg: RGB::named(rltk::BLACK),
|
bg: RGB::named(rltk::BLACK),
|
||||||
render_order: 2,
|
render_order: 2,
|
||||||
})
|
})
|
||||||
@@ -185,6 +217,7 @@ pub fn fireball_scroll(ecs: &mut World, x: i32, y: i32) {
|
|||||||
.with(Ranged { range: 6 })
|
.with(Ranged { range: 6 })
|
||||||
.with(InflictsDamage { damage: 20 })
|
.with(InflictsDamage { damage: 20 })
|
||||||
.with(AreaOfEffect { radius: 3 })
|
.with(AreaOfEffect { radius: 3 })
|
||||||
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +226,7 @@ pub fn confusion_scroll(ecs: &mut World, x: i32, y: i32) {
|
|||||||
.with(Position { x, y })
|
.with(Position { x, y })
|
||||||
.with(Renderable {
|
.with(Renderable {
|
||||||
glyph: rltk::to_cp437(')'),
|
glyph: rltk::to_cp437(')'),
|
||||||
fg: RGB::named(rltk::CYAN),
|
fg: RGB::named(rltk::PINK),
|
||||||
bg: RGB::named(rltk::BLACK),
|
bg: RGB::named(rltk::BLACK),
|
||||||
render_order: 2,
|
render_order: 2,
|
||||||
})
|
})
|
||||||
@@ -204,20 +237,255 @@ pub fn confusion_scroll(ecs: &mut World, x: i32, y: i32) {
|
|||||||
.with(Consumable {})
|
.with(Consumable {})
|
||||||
.with(Ranged { range: 6 })
|
.with(Ranged { range: 6 })
|
||||||
.with(Confusion { turns: 4 })
|
.with(Confusion { turns: 4 })
|
||||||
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn room_table(map_depth: i32) -> RandomTable {
|
||||||
pub fn random_item(ecs: &mut World, x: i32, y: i32) {
|
RandomTable::new()
|
||||||
let roll: i32;
|
.add("Goblin", 10)
|
||||||
{
|
.add("Orc", 1 + map_depth)
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
.add("Health Potion", 7)
|
||||||
roll = rng.roll_dice(1, 4);
|
.add("Fireball Scroll", 2 + map_depth)
|
||||||
}
|
.add("Confusion Scroll", 2 + map_depth)
|
||||||
match roll {
|
.add("Magic Missile Scroll", 4)
|
||||||
1 => health_potion(ecs, x, y),
|
.add("Dagger", 3)
|
||||||
2 => fireball_scroll(ecs, x, y),
|
.add("Longsword", map_depth - 1)
|
||||||
3 => confusion_scroll(ecs, x, y),
|
.add("Shield", 3)
|
||||||
_ => magic_missile_scroll(ecs, x, y),
|
.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
432
src/state.rs
Normal 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
84
src/trigger_system.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@@ -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,17 +16,30 @@ 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 {
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
viewshed.dirty = false;
|
viewshed.dirty = false;
|
||||||
viewshed.visible_tiles.clear();
|
|
||||||
viewshed.visible_tiles = field_of_view(Point::new(pos.x, pos.y), viewshed.range, &*map);
|
viewshed.visible_tiles = field_of_view(Point::new(pos.x, pos.y), viewshed.range, &*map);
|
||||||
viewshed
|
viewshed
|
||||||
.visible_tiles
|
.visible_tiles
|
||||||
@@ -40,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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user