From 235f84833f5e9bd23fbbb50d8311a2e0c43b5d59 Mon Sep 17 00:00:00 2001 From: Aelita4 Date: Fri, 11 Jul 2025 19:58:07 +0200 Subject: [PATCH] Initial commit --- .gitignore | 1 + .vscode/launch.json | 45 ++++ Cargo.lock | 578 ++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 16 ++ data.json | 224 ++++++++++++++++ map.json | 330 +++++++++++++++++++++++ save.json | 1 + save.json.bak | 24 ++ src/game/mod.rs | 1 + src/game/player.rs | 81 ++++++ src/main.rs | 232 ++++++++++++++++ src/parser/mod.rs | 1 + src/parser/nlp.rs | 300 +++++++++++++++++++++ src/structs/json.rs | 50 ++++ src/structs/mod.rs | 2 + src/structs/save.rs | 18 ++ src/tui/interface.rs | 113 ++++++++ src/tui/map.rs | 63 +++++ src/tui/menu.rs | 21 ++ src/tui/mod.rs | 4 + src/tui/text.rs | 117 ++++++++ src/utils/json.rs | 13 + src/utils/maps.rs | 36 +++ src/utils/mod.rs | 3 + src/utils/os/linux.rs | 41 +++ src/utils/os/mod.rs | 34 +++ src/utils/os/windows.rs | 67 +++++ 27 files changed, 2416 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 data.json create mode 100644 map.json create mode 100644 save.json create mode 100644 save.json.bak create mode 100644 src/game/mod.rs create mode 100644 src/game/player.rs create mode 100644 src/main.rs create mode 100644 src/parser/mod.rs create mode 100644 src/parser/nlp.rs create mode 100644 src/structs/json.rs create mode 100644 src/structs/mod.rs create mode 100644 src/structs/save.rs create mode 100644 src/tui/interface.rs create mode 100644 src/tui/map.rs create mode 100644 src/tui/menu.rs create mode 100644 src/tui/mod.rs create mode 100644 src/tui/text.rs create mode 100644 src/utils/json.rs create mode 100644 src/utils/maps.rs create mode 100644 src/utils/mod.rs create mode 100644 src/utils/os/linux.rs create mode 100644 src/utils/os/mod.rs create mode 100644 src/utils/os/windows.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..ba9d696 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'metodol_projekt'", + "cargo": { + "args": [ + "build", + "--bin=metodol_projekt", + "--package=metodol_projekt" + ], + "filter": { + "name": "metodol_projekt", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'metodol_projekt'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=metodol_projekt", + "--package=metodol_projekt" + ], + "filter": { + "name": "metodol_projekt", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f40a591 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,578 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "colored" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "errno" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "metodol_projekt" +version = "0.1.0" +dependencies = [ + "chrono", + "colored", + "lazy_static", + "serde", + "serde_json", + "signal-hook", + "terminal_size", + "text_io", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "syn" +version = "2.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "text_io" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d8d3ca3b06292094e03841d8995e910712d2a10b5869c8f9725385b29761115" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..54e0bfe --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "metodol_projekt" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" +terminal_size = "0.3.0" +signal-hook = "0.3" +text_io = "0.1.13" +lazy_static = "1.5.0" +colored = "3.0.0" +chrono = "0.4.41" \ No newline at end of file diff --git a/data.json b/data.json new file mode 100644 index 0000000..2aaac49 --- /dev/null +++ b/data.json @@ -0,0 +1,224 @@ +{ + "version": 1, + "maps": [ + { + "default": true, + "map_required": true, + "starting_location": [3, 6], + "name": "Mystic Forest", + "description": "A mystical forest filled with ancient trees and hidden secrets.", + "id": "mystic_forest", + "width": 10, + "height": 10, + "events": [ + { + "evt_type": "npc", + "command_id": "oak", + "name": "Elder Oak", + "location": [2, 3], + "dialogue": [ + "You meet the Elder Oak, a towering tree with a face carved into its bark.", + "Welcome to the Mystic Forest, young traveler.", + "Many secrets lie within these woods.", + "Seek the hidden glades and you may find treasures untold." + ] + }, + { + "evt_type": "item", + "command_id": "scroll", + "name": "Ancient Scroll", + "description": "A scroll containing secrets of the forest.", + "location": [7, 8] + }, + { + "evt_type": "location", + "command_id": "mystic_cottage", + "name": "Mystic Cottage", + "description": "A small cottage made of enchanted wood, home to a wise hermit.", + "location": [2, 6] + } + ], + "tilemap": [ + ["water_border", "water_border", "water_border", "water_border", "water_border", "water_border", "water_border", "water_border", "water_border", "water_border"], + ["water_border", "grass", "grass", "grass", "grass", "grass", "grass", "grass", "grass", "water_border"], + ["water_border", "grass", "grass", "grass", "grass", "grass", "grass", "grass", "grass", "water_border"], + ["water_border", "grass", "grass", "grass", "grass", "grass", "grass", "grass", "grass", "water_border"], + ["water_border", "grass", "grass", "grass", "grass", "grass", "grass", "grass", "grass", "water_border"], + ["water_border", "grass", "grass", "grass", "grass", "grass", "grass", "grass", "grass", "water_border"], + ["water_border", "water", "house", "grass", "grass", "grass", "grass", "grass", "grass", "water_border"], + ["water_border", "grass", "grass", "grass", "grass", "grass", "grass", "grass", "grass", "water_border"], + ["water_border", "grass", "grass", "grass", "grass", "grass", "grass", "grass", "grass", "water_border"], + ["water_border", "water_border", "water_border", "water_border", "water_border", "water_border", "water_border", "water_border", "water_border", "water_border"] + ], + "tiles": { + "house": { + "name": "Mystic Cottage", + "icon": { + "char": "H", + "color": [139, 69, 19] + }, + "walkable": true, + "description": "A small cottage made of enchanted wood, home to a wise hermit.", + "bitmap": [ + ["H", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H"], + ["H", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "H"], + ["H", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "H"], + ["H", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "H"], + ["H", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "H"], + ["H", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "H"], + ["H", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "H"], + ["H", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H", "H"] + ] + }, + "water_border": { + "name": "Water Border", + "icon": { + "char": "~", + "color": [0, 0, 255] + }, + "walkable": false, + "description": "A border of water surrounding the Mystic Forest.", + "bitmap": [ + ["~", "~", "~", "~", "~", "~", "~", "~", "~", "~"], + ["~", " ", " ", " ", " ", " ", " ", " ", " ", "~"], + ["~", " ", " ", " ", " ", " ", " ", " ", " ", "~"], + ["~", " ", " ", " ", " ", " ", " ", " ", " ", "~"], + ["~", " ", " ", " ", " ", " ", " ", " ", " ", "~"], + ["~", " ", " ", " ", " ", " ", " ", " ", " ", "~"], + ["~", " ", " ", " ", " ", " ", " ", " ", " ", "~"], + ["~", "~", "~", "~", "~", "~", "~", "~", "~", "~"] + ] + }, + "water": { + "name": "Water", + "icon": { + "char": "~", + "color": [0, 128, 255] + }, + "walkable": true, + "description": "A serene body of water, perfect for fishing or swimming.", + "bitmap": [ + ["~", " ", " ", "~", " ", " ", "~", " ", " ", "~", " ", " ", "~", " ", " ", "~"], + [" ", "~", " ", " ", "~", " ", " ", "~", " ", " ", "~", " ", " ", "~", " ", " "], + [" ", " ", "~", " ", " ", "~", " ", " ", "~", " ", " ", "~", " ", " ", "~", " "], + ["~", " ", " ", "~", " ", " ", "~", " ", " ", "~", " ", " ", "~", " ", " ", "~"], + [" ", "~", " ", " ", "~", " ", " ", "~", " ", " ", "~", " ", " ", "~", " ", " "], + [" ", " ", "~", " ", " ", "~", " ", " ", "~", " ", " ", "~", " ", " ", "~", " "], + ["~", " ", " ", "~", " ", " ", "~", " ", " ", "~", " ", " ", "~", " ", " ", "~"], + [" ", "~", " ", " ", "~", " ", " ", "~", " ", " ", "~", " ", " ", "~", " ", " "] + ] + }, + "grass": { + "name": "Grass", + "icon": { + "char": "#", + "color": [0, 255, 0] + }, + "walkable": true, + "description": "Lush green grass, perfect for a picnic or a stroll.", + "bitmap": [ + ["#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " "], + [" ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#"], + ["#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " "], + [" ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#"], + ["#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " "], + [" ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#"], + ["#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " "], + [" ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#"] + ] + } + } + }, { + "default": false, + "map_required": false, + "starting_location": [2, 4], + "name": "Mystic Cottage", + "description": "A small cottage made of enchanted wood, home to a wise hermit.", + "id": "mystic_cottage", + "width": 5, + "height": 5, + "events": [ + { + "evt_type": "item", + "command_id": "map", + "name": "Mystic Forest Map", + "description": "A map of the Mystic Forest.", + "location": [7, 8] + }, + { + "evt_type": "location", + "command_id": "mystic_forest", + "name": "Door", + "description": "A small wooden door leading to the outside.", + "location": [2, 4] + } + ], + "tilemap": [ + ["wall", "wall", "wall", "wall", "wall"], + ["wall", "floor", "floor", "floor", "wall"], + ["wall", "floor", "floor", "floor", "wall"], + ["wall", "floor", "floor", "floor", "wall"], + ["wall", "wall", "door", "wall", "wall"] + ], + "tiles": { + "wall": { + "name": "Wall", + "icon": { + "char": "#", + "color": [0, 0, 255] + }, + "walkable": false, + "description": "A sturdy wall made of enchanted wood, protecting the cottage.", + "bitmap": [ + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"] + ] + }, + "floor": { + "name": "Floor", + "icon": { + "char": "+", + "color": [237, 207, 147] + }, + "walkable": true, + "description": "A cozy wooden floor, warm and inviting.", + "bitmap": [ + ["+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " "], + [" ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+"], + ["+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " "], + [" ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+"], + ["+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " "], + [" ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+"], + ["+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " "], + [" ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+"] + ] + }, + "door": { + "name": "Door", + "icon": { + "char": "@", + "color": [128, 0, 0] + }, + "walkable": true, + "description": "A small wooden door leading to the outside.", + "bitmap": [ + ["@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@"], + ["@", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "@"], + ["@", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "@"], + ["@", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "+", "-", "-", "@"], + ["@", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "|", " ", " ", "@"], + ["@", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "@"], + ["@", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "@"], + ["@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@"] + ] + } + } + } + ] +} \ No newline at end of file diff --git a/map.json b/map.json new file mode 100644 index 0000000..17f193c --- /dev/null +++ b/map.json @@ -0,0 +1,330 @@ +{ + "version": 1, + "maps": [ + { + "default": true, + "map_required": true, + "starting_location": [1, 14], + "name": "test2", + "description": "testtttt2", + "id": "test2", + "width": 16, + "height": 16, + "events": [ + { + "evt_type": "location", + "command_id": "mystic_cottage", + "name": "Mystic Cottage", + "description": "A small cottage made of enchanted wood, home to a wise hermit.", + "location": [8, 4] + }, { + "evt_type": "item", + "command_id": "gate_key", + "name": "Gate key", + "description": "A key that unlocks the gate.", + "location": [5, 15] + }, { + "evt_type": "lock", + "command_id": "gate_key", + "name": "Gate", + "description": "This gate is locked. You need a key to open it.", + "location": [7, 13] + }, { + "evt_type": "lock", + "command_id": "gate_key", + "name": "Gate", + "description": "This gate is locked. You need a key to open it.", + "location": [8, 13] + }, { + "evt_type": "item", + "command_id": "map", + "name": "Map", + "description": "A map of the Mystic Forest.", + "location": [8, 14] + }, { + "evt_type": "npc", + "command_id": "czesio", + "name": "Czesio", + "description": "A friendly NPC who can help you with your journey.", + "location": [0, 14] + } + ], + "tilemap": [ + ["grass", "grass", "grass", "grass", "fence", "fence", "fence", "fence", "fence", "fence", "fence", "fence", "grass", "grass", "grass", "grass"], + ["grass", "grass", "grass", "grass", "fence", "grass", "grass", "grass", "grass", "grass", "grass", "fence", "grass", "grass", "grass", "grass"], + ["grass", "grass", "grass", "grass", "fence", "grass", "grass", "grass", "grass", "grass", "grass", "fence", "grass", "grass", "grass", "grass"], + ["grass", "grass", "grass", "grass", "fence", "grass", "grass", "house", "house", "grass", "grass", "fence", "grass", "grass", "grass", "grass"], + ["grass", "grass", "grass", "grass", "fence", "grass", "grass", "house", "house_entry", "grass", "grass", "fence", "grass", "grass", "grass", "grass"], + ["grass", "grass", "grass", "grass", "fence", "grass", "grass", "grass", "grass", "grass", "grass", "fence", "grass", "grass", "grass", "grass"], + ["grass", "grass", "grass", "grass", "fence", "grass", "grass", "grass", "grass", "grass", "grass", "fence", "grass", "grass", "grass", "grass"], + ["grass", "grass", "grass", "grass", "fence", "grass", "grass", "grass", "grass", "wall", "wall", "fence", "grass", "grass", "grass", "grass"], + ["grass", "grass", "grass", "grass", "fence", "grass", "grass", "grass", "grass", "wall", "wall", "fence", "grass", "grass", "grass", "grass"], + ["grass", "grass", "grass", "grass", "fence", "fence", "fence", "gate", "fence", "gate", "gate", "fence", "grass", "grass", "grass", "grass"], + ["grass", "grass", "grass", "grass", "fence", "wall", "wall", "grass", "grass", "concrete", "concrete", "fence", "grass", "grass", "grass", "grass"], + ["grass", "grass", "grass", "grass", "fence", "wall", "wall", "grass", "grass", "concrete", "concrete", "fence", "grass", "grass", "grass", "grass"], + ["grass", "grass", "grass", "grass", "fence", "wall", "wall", "grass", "grass", "concrete", "concrete", "fence", "grass", "grass", "grass", "grass"], + ["fence", "fence", "fence", "fence", "fence", "wall", "wall", "gate", "gate", "wall", "wall", "fence", "fence", "fence", "fence", "fence"], + ["road", "road", "road", "road", "road", "road", "road", "road", "road", "road", "road", "road", "road", "road", "road", "road"], + ["road", "road", "road", "road", "road", "road", "road", "road", "road", "road", "road", "road", "road", "road", "road", "road"] + ], + "tiles": { + "road": { + "name": "Road", + "icon": { + "char": "=", + "color": [64, 64, 64] + }, + "walkable": true, + "description": "Part of road.", + "bitmap": [ + ["=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "="], + ["=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "="], + ["=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "="], + ["=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "="], + ["=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "="], + ["=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "="], + ["=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "="], + ["=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "=", "="] + ] + }, + "fence": { + "name": "Fence", + "icon": { + "char": "+", + "color": [75, 26, 16] + }, + "walkable": false, + "description": "A wooden fence, keeping the area secure.", + "bitmap": [ + ["+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+"], + ["|", " ", " ", "|", " ", " ", "|", " ", " ", "|", " ", " ", "|", " ", " ", "|"], + ["|", " ", " ", "|", " ", " ", "|", " ", " ", "|", " ", " ", "|", " ", " ", "|"], + ["|", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+"], + ["|", " ", " ", "|", " ", " ", "|", " ", " ", "|", " ", " ", "|", " ", " ", "|"], + ["|", " ", " ", "|", " ", " ", "|", " ", " ", "|", " ", " ", "|", " ", " ", "|"], + ["|", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+"], + ["+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+"] + ] + }, + "gate": { + "name": "Fence gate", + "icon": { + "char": "@", + "color": [75, 26, 16] + }, + "walkable": true, + "description": "A wooden gate in the fence, allowing passage.", + "bitmap": [ + ["+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+"], + ["|", " ", " ", "|", " ", " ", "@", " ", " ", "@", " ", " ", "|", " ", " ", "|"], + ["|", " ", " ", "|", " ", " ", "@", " ", " ", "@", " ", " ", "|", " ", " ", "|"], + ["|", "+", "+", "+", "+", "+", "@", " ", " ", "@", "+", "+", "+", "+", "+", "+"], + ["|", " ", " ", "|", " ", " ", "@", " ", " ", "@", " ", " ", "|", " ", " ", "|"], + ["|", " ", " ", "|", " ", " ", "@", " ", " ", "@", " ", " ", "|", " ", " ", "|"], + ["|", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+"], + ["+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+", "+"] + ] + }, + "wall": { + "name": "Wall", + "icon": { + "char": "#", + "color": [204, 204, 204] + }, + "walkable": false, + "description": "A sturdy wall made of stone, providing protection.", + "bitmap": [ + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"] + ] + }, + "concrete": { + "name": "Concrete", + "icon": { + "char": ".", + "color": [224, 224, 224] + }, + "walkable": true, + "description": "A smooth concrete surface, often used in urban areas.", + "bitmap": [ + [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."] + ] + }, + "grass": { + "name": "Grass", + "icon": { + "char": ",", + "color": [0, 255, 0] + }, + "walkable": true, + "description": "Lush green grass, perfect for a picnic or a stroll.", + "bitmap": [ + ["#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " "], + [" ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#"], + ["#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " "], + [" ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#"], + ["#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " "], + [" ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#"], + ["#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " "], + [" ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#"] + ] + }, + "house": { + "name": "House", + "icon": { + "char": "#", + "color": [153, 111, 41] + }, + "walkable": false, + "description": "A cozy little house made of enchanted wood, home to a wise hermit.", + "bitmap": [ + ["#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " "], + [" ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#"], + ["#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " "], + [" ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#"], + ["#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " "], + [" ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#"], + ["#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " "], + [" ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#"] + ] + }, + "house_entry": { + "name": "House door", + "icon": { + "char": "@", + "color": [189, 135, 48] + }, + "walkable": true, + "description": "A small wooden door leading to the inside of the house.", + "bitmap": [ + ["#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " "], + [" ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#"], + ["#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " "], + [" ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#"], + ["#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " "], + [" ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#"], + ["#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " "], + [" ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#", " ", "#"] + ] + } + }, + "npcs": { + "czesio": { + "name": "Czesio", + "description": "Dzieńdobry", + "dialogue": [ + "Hello adventurer!", + "What do you need?", + "#(00FF00)1. Nothing", + "#(00FF00)2. Gate key" + ] + } + } + }, { + "default": false, + "map_required": false, + "starting_location": [2, 4], + "name": "Mystic Cottage", + "description": "A small cottage made of enchanted wood, home to a wise hermit.", + "id": "mystic_cottage", + "width": 5, + "height": 5, + "events": [ + { + "evt_type": "item", + "command_id": "map", + "name": "Mystic Forest Map", + "description": "A map of the Mystic Forest.", + "location": [7, 8] + }, + { + "evt_type": "location", + "command_id": "test2", + "name": "Door", + "description": "A small wooden door leading to the outside.", + "location": [2, 4] + } + ], + "tilemap": [ + ["wall", "wall", "wall", "wall", "wall"], + ["wall", "floor", "floor", "floor", "wall"], + ["wall", "floor", "floor", "floor", "wall"], + ["wall", "floor", "floor", "floor", "wall"], + ["wall", "wall", "door", "wall", "wall"] + ], + "tiles": { + "wall": { + "name": "Wall", + "icon": { + "char": "#", + "color": [0, 0, 255] + }, + "walkable": false, + "description": "A sturdy wall made of enchanted wood, protecting the cottage.", + "bitmap": [ + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"], + ["#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#", "#"] + ] + }, + "floor": { + "name": "Floor", + "icon": { + "char": "+", + "color": [237, 207, 147] + }, + "walkable": true, + "description": "A cozy wooden floor, warm and inviting.", + "bitmap": [ + ["+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " "], + [" ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+"], + ["+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " "], + [" ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+"], + ["+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " "], + [" ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+"], + ["+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " "], + [" ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+", " ", "+"] + ] + }, + "door": { + "name": "Door", + "icon": { + "char": "@", + "color": [128, 0, 0] + }, + "walkable": true, + "description": "A small wooden door leading to the outside.", + "bitmap": [ + ["@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@"], + ["@", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "@"], + ["@", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "@"], + ["@", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "+", "-", "-", "@"], + ["@", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "|", " ", " ", "@"], + ["@", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "@"], + ["@", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", "@"], + ["@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@", "@"] + ] + } + }, + "npcs": {} + } + ] +} \ No newline at end of file diff --git a/save.json b/save.json new file mode 100644 index 0000000..e06084d --- /dev/null +++ b/save.json @@ -0,0 +1 @@ +{"coords":{"mystic_cottage":[2,4],"test2":[9,4]},"current_map_id":"test2","flags":{"gate_opened":true},"health":100,"inventory":["map","gate_key"],"last_save":"2025-07-11 14:11:52","lines_of_text":["> save progress","#(00FF00)Progress restored","> go d","You moved into grass tile","> go r","You moved into grass tile","> go r","You moved into grass tile","> go u","You moved into house_entry tile","> enter house","You entered a location.","> go u","You moved into floor tile","> go d","You moved into door tile","> exit house","You exited a location.","> go r","You moved into grass tile","> save progress"],"map_pack":"map","player_name":"Kapitan Bomba","stamina":50,"version":1} \ No newline at end of file diff --git a/save.json.bak b/save.json.bak new file mode 100644 index 0000000..279d7b2 --- /dev/null +++ b/save.json.bak @@ -0,0 +1,24 @@ +{ + "version": 1, + "map_pack": "map", + "player_name": "Kapitan Bomba", + "last_save": "2025-06-18T12:00:00Z", + "current_map_id": "test2", + "health": 100, + "mana": 50, + "coords": { + "test2": [5, 1] + }, + "inventory": [ + "map", + "gate_key" + ], + "flags": { + "gate_opened": true + }, + "lines_of_text": [ + "1", + "2", + "3" + ] +} \ No newline at end of file diff --git a/src/game/mod.rs b/src/game/mod.rs new file mode 100644 index 0000000..f28d7c2 --- /dev/null +++ b/src/game/mod.rs @@ -0,0 +1 @@ +pub mod player; diff --git a/src/game/player.rs b/src/game/player.rs new file mode 100644 index 0000000..0089b7e --- /dev/null +++ b/src/game/player.rs @@ -0,0 +1,81 @@ +use crate::{structs::json::Tile, tui::text::add_text, utils::maps::{get_current_map, get_map_events}}; + +pub fn move_player(x: i8, y: i8, dry_run: bool) -> bool { + let mut all_coords = crate::PLAYER_COORDS.lock().unwrap(); + let player_coords = *all_coords.get(get_current_map().unwrap().id.as_str()).unwrap(); + // let mut new_coords = (player_coords.0 + x, player_coords.1 + y); + let new_coords = ( + player_coords.0.wrapping_add_signed(x as i8), + player_coords.1.wrapping_add_signed(y as i8), + ); + + let map = get_current_map().unwrap(); + + let width = map.width as u8; + let height = map.height as u8; + + // check if new coordinates are within bounds + if new_coords.0 >= width || new_coords.1 >= height { + return false; // Out of bounds, do not move + } + + // check if new coordinates are onto walkable tile + let tile_name = map.tilemap.get(new_coords.1 as usize) + .and_then(|row| row.get(new_coords.0 as usize)) + .and_then(|tile| Some(tile.as_str())) + .unwrap_or_default(); + + let walkable = map.tiles.get(tile_name) + .and_then(|tile| Some(tile.walkable)) + .unwrap_or(false); + + // check if player is allowed to enter onto this tile + let events = get_map_events(get_current_map().unwrap().id.as_str()).unwrap(); + + let evt = events.iter().find(|e| e.evt_type == "lock" && e.location[0] == new_coords.0 && e.location[1] == new_coords.1); + if evt.is_some() { + let evt = evt.unwrap(); + + let required_item = evt.command_id.clone(); + let player_items = crate::PLAYER_INVENTORY.lock().unwrap().clone(); + if !player_items.contains(&required_item) { + if !dry_run { add_text(evt.description.as_ref().unwrap()); } + return false; // Player does not have required item to enter this tile + } + } + + if dry_run { return walkable; } + + if !walkable { + add_text(&format!("! Cannot move into {} tile", tile_name)); + return false; // Not a walkable tile, do not move + } + + all_coords.insert(map.id, new_coords); + *crate::PLAYER_STAMINA.lock().unwrap() -= 1; + + // crate::PLAYER_COORDS.lock().unwrap().0 = new_coords.0; + // crate::PLAYER_COORDS.lock().unwrap().1 = new_coords.1; + + add_text(&format!("You moved into {} tile", tile_name)); + + return true; +} + +pub fn get_current_tile() -> Option { + let map = get_current_map().unwrap(); + let player_coords = crate::PLAYER_COORDS.lock().unwrap().clone(); + let map_coords = *player_coords.get(map.id.as_str()).unwrap(); + + let tile_id = map.tilemap + .get(map_coords.1 as usize) + .and_then(|row| row.get(map_coords.0 as usize)) + .and_then(|tile| Some(tile.as_str())) + .unwrap_or_default(); + + let tile = map.tiles.get(tile_id) + .cloned() + .unwrap(); + + Some(tile) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ac9ba1c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,232 @@ +use std::collections::HashMap; +use std::io::Write; +use std::sync::Mutex; +use std::{env, io}; +use lazy_static::lazy_static; +use serde_json::{Map, Value}; +use terminal_size::{Width, Height, terminal_size}; +use signal_hook::consts::signal::SIGWINCH; +use signal_hook::iterator::Signals; +use text_io::read; + +mod utils; +use utils::os::*; + +mod tui; +use tui::interface::draw_interface; +use tui::interface::clear_input; + +mod parser; +use parser::nlp::parse_command; + +mod structs; + +use crate::parser::nlp::get_available_commands; +use crate::structs::json::MapData; +use crate::structs::save::SaveData; +use crate::tui::interface::draw_player_data; +use crate::tui::map::{draw_map, draw_tile}; +use crate::tui::menu::display_splash_screen; + +mod game; + +lazy_static! { + static ref MAP_PACK: Mutex = Mutex::new("".to_string()); + static ref PLAYER_NAME: Mutex = Mutex::new("Player".to_string()); + static ref PLAYER_HEALTH: Mutex = Mutex::new(100); + static ref PLAYER_STAMINA: Mutex = Mutex::new(50); + static ref PLAYER_COORDS: Mutex> = Mutex::new(HashMap::new()); + static ref PLAYER_INVENTORY: Mutex> = Mutex::new(Vec::new()); + static ref FLAGS: Mutex> = Mutex::new(HashMap::new()); + static ref MAPS: Mutex> = Mutex::new(Vec::new()); + static ref CURRENT_MAP_ID: Mutex = Mutex::new("default".to_string()); + static ref LINES_OF_TEXT: Mutex> = Mutex::new(Vec::new()); +} + +fn main() { + set_terminal_title("Twoja Stara: The Game"); + + let args: Vec = env::args().collect(); + if args.len() > 1 && args[1] == "-d" { + return; + } + + let mut signals = Signals::new(&[SIGWINCH]).expect("Failed to set up signal handling"); + + let min_width = 100; + let min_height = 30; + // let width: u16; + let height: u16; + + loop { + clear_console(); + let size = terminal_size(); + if let Some((Width(w), Height(h))) = size { + if w >= min_width && h >= min_height { + // width = w; + height = h; + + break; + } + + println!("Terminal size is too small. Minimum size is {}x{}, but current size is {}x{}.", min_width, min_height, w, h); + + signals.forever().find(|&signal| signal == SIGWINCH); + } + } + + // display_splash_screen(); + + enum GameState { + New, + Loaded + } + + let mut error = ""; + let game_state: GameState; + let map_name: String; + + loop { + clear_console(); + display_splash_screen(); + // move_cursor(0, 0); + println!("1. Start new game\n2. Load from save.json{}", error); + let choice: String = read!("{}\n"); + match choice.as_str() { + "1" => { + game_state = GameState::New; + + map_name = "map".to_string(); + + *PLAYER_NAME.lock().unwrap() = "Player".to_string(); + *PLAYER_HEALTH.lock().unwrap() = 100; + *PLAYER_STAMINA.lock().unwrap() = 50; + *PLAYER_COORDS.lock().unwrap() = HashMap::new(); + *PLAYER_INVENTORY.lock().unwrap() = Vec::new(); + *FLAGS.lock().unwrap() = HashMap::new(); + *CURRENT_MAP_ID.lock().unwrap() = map_name.to_string(); + + let mut lines = Vec::new(); + lines.push("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."); + *LINES_OF_TEXT.lock().unwrap() = Vec::new(); + + break; + } + "2" => { + let save = utils::json::load_json("save.json"); + match save { + Ok(data) => { + game_state = GameState::Loaded; + + let save_data: SaveData = serde_json::from_value(data).unwrap(); + let mut lines = save_data.lines_of_text; + lines.push("#(00FF00)Progress restored".to_string()); + + *PLAYER_NAME.lock().unwrap() = save_data.player_name; + *PLAYER_HEALTH.lock().unwrap() = save_data.health; + *PLAYER_STAMINA.lock().unwrap() = save_data.stamina; + *PLAYER_COORDS.lock().unwrap() = save_data.coords.iter().map(|(k, v)| (k.to_string(), (v[0], v[1]))).collect(); + *PLAYER_INVENTORY.lock().unwrap() = save_data.inventory; + *FLAGS.lock().unwrap() = save_data.flags; + *CURRENT_MAP_ID.lock().unwrap() = save_data.current_map_id.clone(); + *LINES_OF_TEXT.lock().unwrap() = lines; + + map_name = save_data.map_pack; + + break; + } + Err(e) => { + eprintln!("Error loading save file: {}", e); + return; + } + } + } + _ => { + error = "\nPlease select either 1 or 2"; + } + } + } + + println!("Loading map: {}.json", map_name); + let json_data = utils::json::load_json(format!("{}.json", map_name).as_str()); + *MAP_PACK.lock().unwrap() = map_name; + + match json_data { + Ok(data) => { + let maps: Vec = data.get("maps") + .and_then(|v| v.as_array()) + .unwrap_or(&Vec::new()) + .iter() + .filter_map(|v| v.as_object().cloned()) + .collect::>>() + .iter() + .filter_map(|map| { + print!("{:?}, ", map.get("id")); + serde_json::from_value(Value::Object(map.clone())).ok() + }) + .collect(); + *MAPS.lock().unwrap() = maps.clone(); + println!("asdf{:?}", MAPS.lock().unwrap()); + + if matches!(game_state, GameState::New) { + let current_map_id = maps.iter() + .find(|map| map.default) + .and_then(|map| Some(map.id.clone())) + .unwrap() + .to_string(); + + *CURRENT_MAP_ID.lock().unwrap() = current_map_id.clone(); + + let mut coords = PLAYER_COORDS.lock().unwrap(); + + for(_, map) in maps.iter().enumerate() { + coords.insert(map.id.clone(), ( + *map.starting_location.get(0).unwrap(), + *map.starting_location.get(1).unwrap() + )); + } + } + + + // let starting_location = data.get("data") + // .and_then(|v| v.as_object()) + // .and_then(|obj| obj.get("starting_location")) + // .and_then(|v| v.as_array()) + // .unwrap(); + + // let mut a = PLAYER_COORDS.lock().unwrap(); + // a.insert(current_map_id, ( + // starting_location.get(0).and_then(|v| v.as_u64()).unwrap_or(0).try_into().unwrap_or(0), + // starting_location.get(1).and_then(|v| v.as_u64()).unwrap_or(0).try_into().unwrap_or(0) + // )); + + // PLAYER_COORDS.lock().unwrap().0 = starting_location.get(0).and_then(|v| v.as_u64()).unwrap_or(0).try_into().unwrap_or(0); + // PLAYER_COORDS.lock().unwrap().1 = starting_location.get(1).and_then(|v| v.as_u64()).unwrap_or(0).try_into().unwrap_or(0); + // draw_map(map_data); + + }, + Err(e) => eprintln!("Error loading JSON data: {}", e), + } + + draw_interface(); + + loop { + if PLAYER_INVENTORY.lock().unwrap().clone().iter().find(|x| x == &"map").is_some() { draw_map() } + move_cursor(2, height - 2); + set_cursor_visibility(true); + + let line: String = read!("{}\n"); + parse_command(&line); + draw_tile(); + get_available_commands(); + io::stdout().flush().expect("Failed to flush stdout"); + // thread::sleep(std::time::Duration::from_millis(1000)); + clear_input(); + draw_player_data(); + } + + + + // move_cursor(width - 1, height - 1); + // loop {} +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..c5cf2cd --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1 @@ +pub mod nlp; diff --git a/src/parser/nlp.rs b/src/parser/nlp.rs new file mode 100644 index 0000000..30dfe78 --- /dev/null +++ b/src/parser/nlp.rs @@ -0,0 +1,300 @@ +use std::collections::HashMap; +use colored::Colorize; +use serde_json::to_value; + +use crate::{game::player::move_player, structs::save::SaveData, tui::{text::add_text}, utils::{json::save_json, maps::{get_current_map, get_map_events, get_npc, load_new_map}, os::move_cursor}}; + +lazy_static::lazy_static! { + static ref COMMAND_SYNONYMS: HashMap<&'static str, &'static str> = { + let mut map = HashMap::new(); + map.insert("go", "go"); + map.insert("towards", "go"); + map.insert("head", "go"); + map.insert("walk", "go"); + map.insert("move", "go"); + map.insert("mv", "go"); + map.insert("examine", "examine"); + map.insert("inspect", "examine"); + map.insert("look", "examine"); + map.insert("take", "take"); + map.insert("grab", "take"); + map.insert("pick", "take"); + map.insert("enter", "enter"); + map.insert("exit", "exit"); + map.insert("leave", "exit"); + map.insert("talk", "talk"); + map.insert("describe", "describe"); + map.insert("desc", "describe"); + map.insert("talk", "talk"); + map.insert("say", "say"); + map.insert("save", "save"); + map + }; + + static ref DIRECTIONS: HashMap<&'static str, (i8, i8)> = { + let mut map = HashMap::new(); + map.insert("north", (0, -1)); + map.insert("n", (0, -1)); + map.insert("up", (0, -1)); + map.insert("u", (0, -1)); + map.insert("south", (0, 1)); + map.insert("s", (0, 1)); + map.insert("down", (0, 1)); + map.insert("d", (0, 1)); + map.insert("east", (1, 0)); + map.insert("e", (1, 0)); + map.insert("right", (1, 0)); + map.insert("r", (1, 0)); + map.insert("west", (-1, 0)); + map.insert("w", (-1, 0)); + map.insert("left", (-1, 0)); + map.insert("l", (-1, 0)); + map + }; + + static ref LOCATIONS: HashMap<&'static str, &'static str> = { + let mut map = HashMap::new(); + map.insert("home", "home"); + map.insert("house", "home"); + map.insert("cave", "cave"); + map.insert("forest", "forest"); + map.insert("village", "village"); + map.insert("town", "town"); + map.insert("city", "city"); + map + }; +} + +pub fn parse_command(input: &str) -> Option<(&str, String)> { + let parts: Vec<&str> = input.trim().split_whitespace().collect(); + if parts.len() < 2 { + return None; + } + + let command = parts[0]; + let argument = parts[1..].join(" "); + let height = if let Some((_, terminal_size::Height(h))) = terminal_size::terminal_size() { + h + } else { + 24 // Default height if terminal size cannot be determined + }; + if let Some(synonym) = COMMAND_SYNONYMS.get(command) { + // print!("{}", input.green()); + add_text(format!("> {} {}", synonym, argument).as_str()); + move_cursor(2, height - 2); + match synonym { + &"go" => { + if let Some(direction) = DIRECTIONS.get(argument.as_str()) { + print!("{}", input.green()); + move_player(direction.0, direction.1, false); + } else { + println!("{}", format!("{} | unknown direction: {}", input, argument).red()); + } + } + &"enter" => { + let events = get_map_events(get_current_map().unwrap().id.as_str())?; + let all_coords = crate::PLAYER_COORDS.lock().unwrap().clone(); + let player_coords = all_coords.get(get_current_map().unwrap().id.as_str())?; + + let evt = events.iter().find(|e| e.evt_type == "location" && e.location[0] == player_coords.0 && e.location[1] == player_coords.1); + if evt.is_some() { + let evt = evt.unwrap(); + add_text("You entered a location."); + + load_new_map(evt.command_id.as_str()); + + move_cursor(1, 1); + print!("Player: {} | Map: {}", crate::PLAYER_NAME.lock().unwrap().bold().underline(), get_current_map().unwrap().name.bold().underline()); + + // *crate::CURRENT_MAP_ID.lock().unwrap() = evt.command_id.clone(); + } else { + add_text("! You cannot enter this location."); + } + } + &"exit" => { + let events = get_map_events(get_current_map().unwrap().id.as_str())?; + let all_coords = crate::PLAYER_COORDS.lock().unwrap().clone(); + let player_coords = all_coords.get(get_current_map().unwrap().id.as_str())?; + + // print!("{:?}, {:?}", events, player_coords); + + let evt = events.iter().find(|e| e.evt_type == "location" && e.location[0] == player_coords.0 && e.location[1] == player_coords.1); + if evt.is_some() { + let evt = evt.unwrap(); + add_text("You exited a location."); + + load_new_map(evt.command_id.as_str()); + move_cursor(1, 1); + print!("Player: {} | Map: {}", crate::PLAYER_NAME.lock().unwrap().bold().underline(), get_current_map().unwrap().name.bold().underline()); + + // *crate::CURRENT_MAP_ID.lock().unwrap() = evt.command_id.clone(); + } else { + add_text("! You cannot exit from this location."); + } + } + &"describe" => { + match argument.as_str() { + "item" => { + let events = get_map_events(get_current_map().unwrap().id.as_str())?; + let all_coords = crate::PLAYER_COORDS.lock().unwrap().clone(); + let player_coords = all_coords.get(get_current_map().unwrap().id.as_str())?; + + let evt = events.iter().find(|e| e.evt_type == "item" && e.location[0] == player_coords.0 && e.location[1] == player_coords.1); + if evt.is_some() { + let evt = evt.unwrap(); + add_text(evt.description.as_ref().unwrap_or(&"No description available.".to_string()).as_str()); + } else { + add_text("! No item to describe here."); + } + } + "tile" => { + let current_tile = crate::game::player::get_current_tile().unwrap(); + add_text(current_tile.description.as_deref().unwrap_or("No description available.")); + } + "location" => { + let map = get_current_map().unwrap(); + add_text(map.description.as_str()); + } + _ => { + print!("{}", format!("{} | unknown argument: {}", input, argument).red()); + } + } + } + &"take" => { + let events = get_map_events(get_current_map().unwrap().id.as_str())?; + let all_coords = crate::PLAYER_COORDS.lock().unwrap().clone(); + let player_coords = all_coords.get(get_current_map().unwrap().id.as_str())?; + let mut flags = crate::FLAGS.lock().unwrap(); + let evt = events.iter().find(|e| e.evt_type == "item" && e.location[0] == player_coords.0 && e.location[1] == player_coords.1); + + if !evt.is_some() { + add_text("! Nothing to pick up here."); + return None; + } + + let evt = evt.unwrap(); + + if !flags.contains_key(evt.command_id.as_str()) { + add_text(format!("You picked up {}.", evt.command_id).as_str()); + + crate::PLAYER_INVENTORY.lock().unwrap().push(evt.command_id.clone()); + flags.insert(evt.command_id.clone(), true); + } else { + add_text("! Nothing to pick up here."); + } + } + &"talk" => { + let events = get_map_events(get_current_map().unwrap().id.as_str())?; + let all_coords = crate::PLAYER_COORDS.lock().unwrap().clone(); + let player_coords = all_coords.get(get_current_map().unwrap().id.as_str())?; + + let evt = events.iter().find(|e| e.evt_type == "npc" && e.location[0] == player_coords.0 && e.location[1] == player_coords.1); + + match evt { + Some(e) => { + let npc = get_npc(&e.command_id); + match npc { + Some(n) => { + add_text(format!("You talked to {}.", n.name).as_str()); + for line in n.dialogue.iter() { + add_text(line); + } + add_text("#(FFFF00)Use `say ` to respond"); + } + None => { + add_text("! Strange NPC here, no dialogue available."); + } + } + } + None => { + add_text("! No NPC to talk to here."); + } + } + } + &"say" => { + match argument.as_str() { + "1" => { + add_text("You said: 'Nothing'"); + } + "2" => { + add_text("You said: 'Gate key'"); + add_text("You acquired a key"); + + } + _ => { + add_text("#(FF0000)! Unknown argument for 'say' command."); + } + } + } + &"save" => { + let save_data = SaveData { + version: 1, + map_pack: crate::MAP_PACK.lock().unwrap().clone(), + player_name: crate::PLAYER_NAME.lock().unwrap().clone(), + last_save: chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(), + current_map_id: crate::CURRENT_MAP_ID.lock().unwrap().clone(), + health: *crate::PLAYER_HEALTH.lock().unwrap(), + stamina: *crate::PLAYER_STAMINA.lock().unwrap(), + coords: crate::PLAYER_COORDS.lock().unwrap().iter().map(|(k, v)| (k.to_string(), vec![v.0, v.1])).collect(), + inventory: crate::PLAYER_INVENTORY.lock().unwrap().clone(), + flags: crate::FLAGS.lock().unwrap().clone(), + lines_of_text: crate::LINES_OF_TEXT.lock().unwrap().clone(), + }; + + let result = save_json("save.json", to_value(save_data).unwrap()); + match result { + Ok(_) => { + add_text("#(00FF00)Progress saved"); + } + Err(e) => { + add_text(format!("#(FF0000)Failed to save: {:?}", e).as_str()); + } + } + } + _ => { + print!("{}", input.red()); + } + } + Some((synonym, argument)) + } else { + print!("{}", input.red()); + None + } +} + +pub fn get_available_commands() { + let width = if let Some((terminal_size::Width(w), _)) = terminal_size::terminal_size() { + w + } else { + 80 // Default height if terminal size cannot be determined + }; + + let events = get_map_events(get_current_map().unwrap().id.as_str()).unwrap(); + let all_coords = crate::PLAYER_COORDS.lock().unwrap().clone(); + let player_coords = all_coords.get(get_current_map().unwrap().id.as_str()).unwrap(); + let evt_item = events.iter().find(|e| e.evt_type == "item" && e.location[0] == player_coords.0 && e.location[1] == player_coords.1); + let evt_location = events.iter().find(|e| e.evt_type == "location" && e.location[0] == player_coords.0 && e.location[1] == player_coords.1); + let evt_npc = events.iter().find(|e| e.evt_type == "npc" && e.location[0] == player_coords.0 && e.location[1] == player_coords.1); + let flags = crate::FLAGS.lock().unwrap(); + + // GO + move_cursor(width - 24, 4); + print!("GO | {} {} {} {}", + if move_player(0, -1, true) { "▲".green() } else { "▲".red() }, + if move_player(0, 1, true) { "▼".green() } else { "▼".red() }, + if move_player(-1, 0, true) { "◀".green() } else { "◀".red() }, + if move_player(1, 0, true) { "▶".green() } else { "▶".red() } + ); + + // TAKE + move_cursor(width - 24, 5); + print!("TAKE | {}", if evt_item.is_some() && !flags.contains_key(evt_item.unwrap().command_id.as_str()) { "✔".green() } else { "✘".red() }); + + // ENTER/EXIT + move_cursor(width - 24, 6); + print!("ENTER | {}", if evt_location.is_some() { "✔".green() } else { "✘".red() }); + + // TALK + move_cursor(width - 24, 7); + print!("TALK | {}", if evt_npc.is_some() { "✔".green() } else { "✘".red() }); +} diff --git a/src/structs/json.rs b/src/structs/json.rs new file mode 100644 index 0000000..b041ad4 --- /dev/null +++ b/src/structs/json.rs @@ -0,0 +1,50 @@ +use std::collections::HashMap; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct MapData { + pub default: bool, + pub map_required: bool, + pub starting_location: Vec, + pub name: String, + pub description: String, + pub id: String, + pub width: u16, + pub height: u16, + pub events: Vec, + pub tilemap: Vec>, + pub tiles: HashMap, + pub npcs: HashMap +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Events { + pub evt_type: String, + pub command_id: String, + pub name: String, + pub description: Option, + pub location: Vec, + pub dialogue: Option> +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Tile { + pub name: String, + pub icon: Option, + pub walkable: bool, + pub description: Option, + pub bitmap: Vec>, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TileIcon { + pub char: String, + pub color: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct NPC { + pub name: String, + pub description: String, + pub dialogue: Vec, +} \ No newline at end of file diff --git a/src/structs/mod.rs b/src/structs/mod.rs new file mode 100644 index 0000000..2897a7a --- /dev/null +++ b/src/structs/mod.rs @@ -0,0 +1,2 @@ +pub mod json; +pub mod save; diff --git a/src/structs/save.rs b/src/structs/save.rs new file mode 100644 index 0000000..ad3dc20 --- /dev/null +++ b/src/structs/save.rs @@ -0,0 +1,18 @@ +use std::collections::HashMap; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SaveData { + pub version: u16, + pub map_pack: String, + pub player_name: String, + pub last_save: String, + pub current_map_id: String, + pub health: u32, + pub stamina: u32, + pub coords: HashMap>, + pub inventory: Vec, + pub flags: HashMap, + pub lines_of_text: Vec +} + diff --git a/src/tui/interface.rs b/src/tui/interface.rs new file mode 100644 index 0000000..acbce20 --- /dev/null +++ b/src/tui/interface.rs @@ -0,0 +1,113 @@ +use colored::Colorize; +use terminal_size::{Width, Height, terminal_size}; + +use crate::{parser::nlp::get_available_commands, tui::{map::draw_tile, text::refresh_text}, utils::{maps::get_current_map, os::move_cursor}}; + +pub fn draw_interface() { + move_cursor(0, 0); + + let Some((Width(w), Height(h))) = terminal_size() + else { + eprintln!("Could not determine terminal size."); + return; + }; + + let horizontal_char = "─"; + let vertical_char = "│"; + // let corner_char = "+"; + let perp_down_char = "┬"; + let perp_up_char = "┴"; + let perp_left_char = "├"; + let perp_right_char = "┤"; + let corner_down_left_char = "┌"; + let corner_down_right_char = "┐"; + let corner_up_left_char = "└"; + let corner_up_right_char = "┘"; + + let horizontal_line = horizontal_char.repeat(usize::from(w - 2)); + + print!("{}", String::from(corner_down_left_char) + &horizontal_line + corner_down_right_char); + for i in 0..(h - 2) { + if i == 0 { + print!("{}", String::from(vertical_char) + &" ".repeat(usize::from(w - 2)) + vertical_char); + } else if i == 1 { + print!("{}", String::from(perp_left_char) + &horizontal_char.repeat(16) + perp_down_char + &horizontal_char.repeat(usize::from(w - 19 - 25)) + perp_down_char + &horizontal_char.repeat(24) + perp_right_char); + } else if i == h - 9 || i == 10 { + print!("{}", String::from(perp_left_char) + &horizontal_char.repeat(16) + perp_right_char + &" ".repeat(usize::from(w - 19 - 25)) + vertical_char + &" ".repeat(24) + vertical_char); + } else if i < h - 4 { + print!("{}", String::from(vertical_char) + &" ".repeat(16) + vertical_char + &" ".repeat(usize::from(w - 19 - 25)) + vertical_char + &" ".repeat(24) + vertical_char); + } else if i == h - 4 { + print!("{}", String::from(perp_left_char) + &horizontal_char.repeat(16) + perp_up_char + &horizontal_char.repeat(usize::from(w - 19 - 25)) + perp_up_char + &horizontal_char.repeat(24) + perp_right_char); + } else { + print!("{}", String::from(vertical_char) + &" ".repeat(usize::from(w - 2)) + vertical_char); + } + } + print!("{}", String::from(corner_up_left_char) + &horizontal_line + corner_up_right_char); + + draw_player_data(); + + move_cursor(w - 21, 3); + print!("{}", "POSSIBLE ACTIONS".bold().underline()); + + move_cursor(1, 1); + print!("Player: {} | Map: {}", crate::PLAYER_NAME.lock().unwrap().bold().underline(), get_current_map().unwrap().name.bold().underline()); + + draw_tile(); + get_available_commands(); + + refresh_text(); +} + +pub fn clear_input() { + let Some((Width(width), Height(height))) = terminal_size() + else { + eprintln!("Could not determine terminal size."); + return; + }; + + move_cursor(2, height - 2); + + print!("{:width$}", "", width = usize::from(width - 4)); + move_cursor(2, height - 2); +} + +pub fn draw_player_data() { + let Some((Width(_), Height(height))) = terminal_size() + else { + eprintln!("Could not determine terminal size."); + return; + }; + let y = height - 7; + + let hp = u16::try_from(*crate::PLAYER_HEALTH.lock().unwrap()).unwrap(); + let sp = u16::try_from(*crate::PLAYER_STAMINA.lock().unwrap()).unwrap(); + + let max_hp = 100; + let max_sp = 50; + + move_cursor(1, y); + print!("HP: {}/{}", hp, max_hp); + move_cursor(1, y + 1); + let hp_blocks_count = ((hp as f64) / 100.0 * 16.0).ceil() as u8; + let mut hp_blocks = String::from(""); + for _ in 0..hp_blocks_count { + hp_blocks.push('█'); + } + for _ in hp_blocks_count..16 { + hp_blocks.push('_'); + } + print!("{}", hp_blocks.red()); + + move_cursor(1, y + 2); + print!("SP: {}/{}", sp, max_sp); + move_cursor(1, y + 3); + let mp_blocks_count = ((sp as f64) / 50.0 * 16.0).ceil() as u8; + let mut mp_blocks = String::from(""); + for _ in 0..mp_blocks_count { + mp_blocks.push('█'); + } + for _ in mp_blocks_count..16 { + mp_blocks.push('_'); + } + print!("{}", mp_blocks.yellow()); +} diff --git a/src/tui/map.rs b/src/tui/map.rs new file mode 100644 index 0000000..3eeb80a --- /dev/null +++ b/src/tui/map.rs @@ -0,0 +1,63 @@ +use colored::Colorize; + +use crate::{game::player::get_current_tile, utils::os::move_cursor}; + +pub fn draw_tile() { + let tile = get_current_tile(); + + let bitmap = tile.clone().unwrap().bitmap; + + for (y, row) in bitmap.iter().enumerate() { + move_cursor(1, (y as u16) + 3); + for (_, pixel) in row.iter().enumerate() { + let colors = tile.clone().unwrap().icon.unwrap().color; + + print!("{}", pixel.on_truecolor(colors[0], colors[1], colors[2])); + } + } +} + +pub fn draw_map() { + clear_map(); + let map = crate::utils::maps::get_current_map().unwrap().clone(); + let player_coords = crate::PLAYER_COORDS.lock().unwrap().clone(); + let map_coords = *player_coords.get(map.id.as_str()).unwrap(); + + let starting_index = ( + if map.width > 16 { + if map_coords.0 > 8 { + map_coords.0 as u16 - 8 + } else { + 0 + } + } else { 0 }, + if map.height > 8 { + if map_coords.1 > 4 { + map_coords.1 as u16 - 4 + } else { + 0 + } + } else { 0 } + ); + + for (y, row) in map.tilemap.iter().skip(starting_index.1 as usize).enumerate() { + if y >= 8 { break } + move_cursor(1, (y as u16) + 12); + for (x, tile_id) in row.iter().skip(starting_index.0 as usize).enumerate() { + if x + starting_index.0 as usize == map_coords.0 as usize && y + starting_index.1 as usize == map_coords.1 as usize { + print!("{}", "X".bold().red().on_truecolor(228, 209, 178)); + continue; + } + let tile = map.tiles.get(tile_id.as_str()).unwrap(); + let icon = tile.icon.clone().unwrap(); + print!("{}", icon.char.truecolor(icon.color[0], icon.color[1], icon.color[2]).on_truecolor(228, 209, 178)); + } + } +} + +pub fn clear_map() { + for y in 0..8 { + move_cursor(1, 12 + y); + print!("{}", " ".repeat(16)); + } +} diff --git a/src/tui/menu.rs b/src/tui/menu.rs new file mode 100644 index 0000000..8c5e307 --- /dev/null +++ b/src/tui/menu.rs @@ -0,0 +1,21 @@ +use std::io::{self, Write}; + +pub fn display_splash_screen() { + let splash = r#" +▄▄▄█████▓ ██░ ██ ▓█████ ▄████ ▄▄▄ ███▄ ▄███▓▓█████ +▓ ██▒ ▓▒▓██░ ██▒▓█ ▀ ██▒ ▀█▒▒████▄ ▓██▒▀█▀ ██▒▓█ ▀ +▒ ▓██░ ▒░▒██▀▀██░▒███ ▒██░▄▄▄░▒██ ▀█▄ ▓██ ▓██░▒███ +░ ▓██▓ ░ ░▓█ ░██ ▒▓█ ▄ ░▓█ ██▓░██▄▄▄▄██ ▒██ ▒██ ▒▓█ ▄ + ▒██▒ ░ ░▓█▒░██▓░▒████▒ ░▒▓███▀▒ ▓█ ▓██▒▒██▒ ░██▒░▒████▒ + ▒ ░░ ▒ ░░▒░▒░░ ▒░ ░ ░▒ ▒ ▒▒ ▓▒█░░ ▒░ ░ ░░░ ▒░ ░ + ░ ▒ ░▒░ ░ ░ ░ ░ ░ ░ ▒ ▒▒ ░░ ░ ░ ░ ░ ░ + ░ ░ ░░ ░ ░ ░ ░ ░ ░ ▒ ░ ░ ░ + ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ + +"#; + + let stdout = io::stdout(); + let mut handle = stdout.lock(); + write!(handle, "{}", splash).unwrap(); + handle.flush().unwrap(); +} diff --git a/src/tui/mod.rs b/src/tui/mod.rs new file mode 100644 index 0000000..7ea4f25 --- /dev/null +++ b/src/tui/mod.rs @@ -0,0 +1,4 @@ +pub mod menu; +pub mod interface; +pub mod map; +pub mod text; diff --git a/src/tui/text.rs b/src/tui/text.rs new file mode 100644 index 0000000..2ffe8dd --- /dev/null +++ b/src/tui/text.rs @@ -0,0 +1,117 @@ +use colored::{ColoredString, Colorize}; +use terminal_size::{Width, Height, terminal_size}; + +use crate::utils::os::{move_cursor, set_cursor_visibility}; + +// pub fn clear_text() { +// set_cursor_visibility(false); +// // let Some((Width(width), Height(height))) = terminal_size() +// // else { +// // eprintln!("Could not determine terminal size."); +// // return; +// // }; + +// // let text_area_width: u16 = width - 4 - 16 - 24; + +// move_cursor(18, 1); +// // print!("{}", "x".repeat(usize::from(text_area_width))); +// *crate::LINES_OF_TEXT.lock().unwrap() = [].to_vec(); +// } + +fn color_text(text: &str) -> ColoredString { + if text.starts_with("#") { + let color_code_raw = &text[2..8]; + let color_code = ( + u8::from_str_radix(&color_code_raw[0..2], 16).unwrap_or(255), + u8::from_str_radix(&color_code_raw[2..4], 16).unwrap_or(255), + u8::from_str_radix(&color_code_raw[4..6], 16).unwrap_or(255) + ); + let text = &text[9..].truecolor(color_code.0, color_code.1, color_code.2); + + text.clone() + } else { + text.normal() + } +} + +pub fn add_text(text: &str) { + set_cursor_visibility(false); + let Some((Width(width), Height(height))) = terminal_size() + else { + eprintln!("Could not determine terminal size."); + return; + }; + let text_area_width: u16 = width - 4 - 16 - 24; + + let mut lines_of_text = crate::LINES_OF_TEXT.lock().unwrap(); + + // move_cursor(18, (*lines_of_text + 1).try_into().unwrap_or(1)); + + let wrapped_lines = wrap_text(text, text_area_width); + for line in wrapped_lines { + lines_of_text.push(line); + print_text(lines_of_text.clone(), text_area_width, height - 6); + // move_cursor(18, (*lines_of_text + 1).try_into().unwrap_or(1)); + } +} + +pub fn wrap_text(text: &str, width: u16) -> Vec { + let mut wrapped_lines = Vec::new(); + let mut current_line = String::new(); + let words: Vec<&str> = text.split_whitespace().collect(); + + for word in words { + if current_line.len() + word.len() + 1 > usize::from(width) { + wrapped_lines.push(current_line.trim().to_string()); + current_line.clear(); + } + if !current_line.is_empty() { + current_line.push(' '); + } + current_line.push_str(word); + } + + if !current_line.is_empty() { + wrapped_lines.push(current_line.trim().to_string()); + } + + wrapped_lines +} + +fn print_text(lines: Vec, width: u16, height: u16) { + set_cursor_visibility(false); + + let mut line_number = 3; + + if lines.len() > usize::from(height) { + let start_index = lines.len() - usize::from(height); + let end_index = lines.len(); + let visible_lines = &lines[start_index..end_index]; + for line in visible_lines.iter() { + let text = color_text(&line); + move_cursor(18, line_number); + print!("{}{}", text, " ".repeat(usize::from(width) - line.len())); + line_number += 1; + } + } else { + for line in lines.iter() { + let text = color_text(&line); + move_cursor(18, line_number); + print!("{}", text); + line_number += 1; + } + } +} + +pub fn refresh_text() { + let Some((Width(width), Height(height))) = terminal_size() + else { + eprintln!("Could not determine terminal size."); + return; + }; + let text_area_width: u16 = width - 4 - 16 - 24; + + let lines_of_text = crate::LINES_OF_TEXT.lock().unwrap(); + + print_text(lines_of_text.to_vec(), text_area_width, height); +} diff --git a/src/utils/json.rs b/src/utils/json.rs new file mode 100644 index 0000000..fcef0ed --- /dev/null +++ b/src/utils/json.rs @@ -0,0 +1,13 @@ +pub fn load_json(file_path: &str) -> Result> { + let file = std::fs::File::open(file_path)?; + let reader = std::io::BufReader::new(file); + let json_data: serde_json::Value = serde_json::from_reader(reader)?; + Ok(json_data) +} + +pub fn save_json(file_path: &str, data: serde_json::Value) -> Result<(), Box> { + let file = std::fs::File::create(file_path)?; + let writer = std::io::BufWriter::new(file); + serde_json::to_writer(writer, &data)?; + Ok(()) +} diff --git a/src/utils/maps.rs b/src/utils/maps.rs new file mode 100644 index 0000000..fd74a31 --- /dev/null +++ b/src/utils/maps.rs @@ -0,0 +1,36 @@ +use crate::structs::json::{Events, MapData, NPC}; + +pub fn get_map_data_by_id(map_id: &str) -> Option { + let maps = crate::MAPS.lock().unwrap(); + maps.iter().find(|map| map.id == map_id).cloned() +} + +pub fn get_current_map() -> Option { + let current_map_id = crate::CURRENT_MAP_ID.lock().unwrap().clone(); + let maps = crate::MAPS.lock().unwrap(); + + maps.iter().find(|map| map.id == current_map_id).cloned() +} + +pub fn get_map_events(map_id: &str) -> Option> { + let map = get_map_data_by_id(map_id)?; + Some(map.events) +} + +pub fn get_npc(npc_id: &str) -> Option { + let map = get_current_map()?; + map.npcs.get(npc_id).cloned() +} + +pub fn load_new_map(map_id: &str) { + let map = get_map_data_by_id(map_id).unwrap(); + *crate::CURRENT_MAP_ID.lock().unwrap() = map.id.clone(); + let coords = crate::PLAYER_COORDS.lock().unwrap().clone(); + if coords.get(&map.id).is_none() { + // If the player has no coordinates for this map, set starting location + let starting_location = map.starting_location.clone(); + crate::PLAYER_COORDS.lock().unwrap().insert(map.id.clone(), (starting_location[0] as u8, starting_location[1] as u8)); + } + // let new_coords = map.starting_location.clone(); + // *crate::PLAYER_COORDS.lock().unwrap() = (new_coords[0] as u8, new_coords[1] as u8); +} \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..05df7a9 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,3 @@ +pub mod json; +pub mod os; +pub mod maps; diff --git a/src/utils/os/linux.rs b/src/utils/os/linux.rs new file mode 100644 index 0000000..0e8bbf3 --- /dev/null +++ b/src/utils/os/linux.rs @@ -0,0 +1,41 @@ +use std::io::{self, Write}; + +#[cfg(target_os = "linux")] +pub fn move_cursor(x: u16, y: u16) { + { + let stdout = io::stdout(); + let mut handle = stdout.lock(); + write!(handle, "\x1b[{};{}H", y + 1, x + 1).unwrap(); + handle.flush().unwrap(); + } +} + +#[cfg(target_os = "linux")] +pub fn clear_console() { + { + let stdout = io::stdout(); + let mut handle = stdout.lock(); + write!(handle, "\x1b[2J\x1b[H").unwrap(); + handle.flush().unwrap(); + } +} + +#[cfg(target_os = "linux")] +pub fn set_terminal_title(title: &str) { + let stdout = io::stdout(); + let mut handle = stdout.lock(); + write!(handle, "\x1b]0;{}\x07", title).unwrap(); + handle.flush().unwrap(); +} + +#[cfg(target_os = "linux")] +pub fn set_cursor_visibility(enable: bool) { + let stdout = io::stdout(); + let mut handle = stdout.lock(); + if enable { + write!(handle, "\x1b[?25h").unwrap(); // Show cursor + } else { + write!(handle, "\x1b[?25l").unwrap(); // Hide cursor + } + handle.flush().unwrap(); +} diff --git a/src/utils/os/mod.rs b/src/utils/os/mod.rs new file mode 100644 index 0000000..77106cb --- /dev/null +++ b/src/utils/os/mod.rs @@ -0,0 +1,34 @@ +pub mod linux; +pub mod windows; + +pub fn move_cursor(x: u16, y: u16) { + #[cfg(target_os = "linux")] + linux::move_cursor(x, y); + + #[cfg(target_os = "windows")] + windows::move_cursor(x, y); +} + +pub fn clear_console() { + #[cfg(target_os = "linux")] + linux::clear_console(); + + #[cfg(target_os = "windows")] + windows::clear_console(); +} + +pub fn set_terminal_title(title: &str) { + #[cfg(target_os = "linux")] + linux::set_terminal_title(title); + + #[cfg(target_os = "windows")] + windows::set_terminal_title(title); +} + +pub fn set_cursor_visibility(enable: bool) { + #[cfg(target_os = "linux")] + linux::set_cursor_visibility(enable); + + #[cfg(target_os = "windows")] + windows::set_cursor_visibility(enable); +} diff --git a/src/utils/os/windows.rs b/src/utils/os/windows.rs new file mode 100644 index 0000000..c685b01 --- /dev/null +++ b/src/utils/os/windows.rs @@ -0,0 +1,67 @@ +#[cfg(target_os = "windows")] +pub fn move_cursor(x: u16, y: u16) { + { + use std::io::{self, Write}; + use std::os::windows::io::AsRawHandle; + use winapi::um::wincon::{SetConsoleCursorPosition, COORD}; + + let stdout = io::stdout(); + let handle = stdout.as_raw_handle(); + let coord = COORD { X: x as i16, Y: y as i16 }; + unsafe { + SetConsoleCursorPosition(handle, coord); + } + } +} + +#[cfg(target_os = "windows")] +pub fn clear_console() { + { + use std::io::{self, Write}; + use winapi::um::wincon::{GetConsoleScreenBufferInfo, COORD, CONSOLE_SCREEN_BUFFER_INFO, FillConsoleOutputCharacterA, FillConsoleOutputAttribute}; + use winapi::um::winbase::GetStdHandle; + use winapi::um::winnt::STD_OUTPUT_HANDLE; + + let stdout = io::stdout(); + let handle = unsafe { GetStdHandle(STD_OUTPUT_HANDLE) }; + let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = unsafe { std::mem::zeroed() }; + + unsafe { + GetConsoleScreenBufferInfo(handle, &mut csbi); + let size = csbi.dwSize; + let coord = COORD { X: 0, Y: 0 }; + FillConsoleOutputCharacterA(handle, b' ' as u8, (size.X * size.Y) as u32, coord, std::ptr::null_mut()); + FillConsoleOutputAttribute(handle, csbi.wAttributes, (size.X * size.Y) as u32, coord, std::ptr::null_mut()); + SetConsoleCursorPosition(handle, coord); + } + } +} + +#[cfg(target_os = "windows")] +pub fn set_terminal_title(title: &str) { + use std::io::{self, Write}; + use winapi::um::wincon::SetConsoleTitleA; + + let title_bytes = title.as_bytes(); + let c_title = std::ffi::CString::new(title_bytes).unwrap(); + + unsafe { + SetConsoleTitleA(c_title.as_ptr()); + } +} + +#[cfg(target_os = "windows")] +pub fn set_cursor_visibility(enable: bool) { + use std::io::{self, Write}; + use winapi::um::wincon::{SetConsoleCursorInfo, CONSOLE_CURSOR_INFO}; + + let stdout = io::stdout(); + let handle = stdout.as_raw_handle(); + let mut cursor_info: CONSOLE_CURSOR_INFO = unsafe { std::mem::zeroed() }; + + unsafe { + SetConsoleCursorInfo(handle, &mut cursor_info); + cursor_info.bVisible = if enable { 1 } else { 0 }; + SetConsoleCursorInfo(handle, &cursor_info); + } +}