├── .clippy.toml ├── .github └── workflows │ ├── ci.yml │ ├── release.yml │ └── test-build.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── config │ ├── changeable.default.toml │ ├── config.toml │ ├── control.toml │ ├── font.toml │ ├── input.toml │ ├── screen │ │ ├── 1280x800.toml │ │ └── 800x600.toml │ └── ui.toml ├── fonts │ ├── LICENSE_OFL.txt │ ├── NotoMono-Regular.ttf │ ├── NotoSans-Medium.ttf │ └── NotoSansCJKjp-Medium.otf └── text │ ├── en │ ├── log │ │ └── log.ftl │ ├── misc │ │ ├── item_info_text.ftl │ │ ├── quest.ftl │ │ └── words.ftl │ └── ui │ │ ├── choices.ftl │ │ ├── command.ftl │ │ ├── misc.ftl │ │ ├── msg.ftl │ │ └── window.ftl │ └── ja │ ├── log │ └── log.ftl │ ├── misc │ ├── item_info_text.ftl │ ├── quest.ftl │ └── words.ftl │ └── ui │ ├── choices.ftl │ ├── command.ftl │ ├── misc.ftl │ ├── msg.ftl │ └── window.ftl ├── audio ├── Cargo.toml └── src │ ├── datwalker.rs │ ├── lib.rs │ ├── musictable.rs │ ├── tool.rs │ └── wavtable.rs ├── build-pak.sh ├── common ├── Cargo.toml └── src │ ├── basic.rs │ ├── gamedata │ ├── chara.rs │ ├── defs.rs │ ├── effect.rs │ ├── faction.rs │ ├── item.rs │ ├── item_attr.rs │ ├── learned_recipes.rs │ ├── map.rs │ ├── meta.rs │ ├── mod.rs │ ├── modifier.rs │ ├── play_time.rs │ ├── player.rs │ ├── quest.rs │ ├── region.rs │ ├── settings.rs │ ├── shop.rs │ ├── site.rs │ ├── skill.rs │ ├── time.rs │ ├── town.rs │ ├── traits.rs │ └── variables.rs │ ├── gobj.rs │ ├── hashmap.rs │ ├── idx_conv.rs │ ├── impl_filebox.rs │ ├── item_selector.rs │ ├── lib.rs │ ├── maptemplate.rs │ ├── obj.rs │ ├── objholder.rs │ ├── pakutil.rs │ ├── piece_pattern.rs │ ├── regiongen.rs │ ├── saveload.rs │ ├── sitegen.rs │ └── utils.rs ├── filebox ├── Cargo.toml └── src │ ├── lib.rs │ └── ser.rs ├── images ├── rusted-ruins_128x128.png └── rusted-ruins_24x24.png ├── makepak ├── Cargo.toml └── src │ ├── buildobj │ ├── img.rs │ ├── item.rs │ └── mod.rs │ ├── compile.rs │ ├── dir.rs │ ├── error.rs │ ├── input.rs │ ├── main.rs │ ├── pyscript.rs │ └── verbose.rs ├── map-editor ├── Cargo.toml └── src │ ├── draw_map.rs │ ├── edit_map.rs │ ├── file.rs │ ├── iconview.rs │ ├── main.rs │ ├── pixbuf_holder.rs │ ├── property_controls.rs │ ├── ui.glade │ └── ui.rs ├── map-generator ├── Cargo.toml └── src │ ├── binary.rs │ ├── fractal.rs │ ├── lattice.rs │ ├── lib.rs │ └── rooms.rs ├── rng ├── Cargo.toml └── src │ └── lib.rs ├── rules ├── Cargo.toml └── src │ ├── ability.rs │ ├── biome.rs │ ├── chara.rs │ ├── chara_trait.rs │ ├── charagen.rs │ ├── class.rs │ ├── combat.rs │ ├── creation.rs │ ├── dungeon_gen.rs │ ├── effect.rs │ ├── exp.rs │ ├── faction.rs │ ├── item.rs │ ├── lib.rs │ ├── map_gen.rs │ ├── material.rs │ ├── newgame.rs │ ├── npc.rs │ ├── npc_ai.rs │ ├── params.rs │ ├── power.rs │ ├── quest.rs │ ├── race.rs │ ├── recipe.rs │ ├── town.rs │ └── world.rs ├── rusted-ruins ├── Cargo.toml └── src │ ├── config │ ├── args.rs │ ├── changeable.rs │ ├── control.rs │ ├── font.rs │ ├── input.rs │ ├── mod.rs │ └── visual.rs │ ├── context │ ├── mod.rs │ ├── sdlvalues.rs │ ├── textcachepool.rs │ ├── textrenderer.rs │ └── texture.rs │ ├── cursor.rs │ ├── damage_popup.rs │ ├── draw │ ├── border.rs │ ├── chara_info.rs │ ├── damage.rs │ ├── frame.rs │ ├── mainwin.rs │ ├── mod.rs │ ├── overlay.rs │ ├── target_mode.rs │ └── tile_getter.rs │ ├── error.rs │ ├── eventhandler.rs │ ├── game │ ├── action │ │ ├── ability.rs │ │ ├── get_item.rs │ │ ├── harvest.rs │ │ ├── mod.rs │ │ └── use_item.rs │ ├── anim_queue.rs │ ├── animation.rs │ ├── building.rs │ ├── chara │ │ ├── gen.rs │ │ ├── mod.rs │ │ ├── preturn.rs │ │ ├── status.rs │ │ ├── total_modifier.rs │ │ └── update.rs │ ├── command.rs │ ├── creation.rs │ ├── damage.rs │ ├── debug_command.rs │ ├── dungeon_gen.rs │ ├── effect │ │ ├── attack.rs │ │ ├── misc.rs │ │ ├── mod.rs │ │ ├── range.rs │ │ ├── restore.rs │ │ └── skill_learn.rs │ ├── faction.rs │ ├── frequent_tex.rs │ ├── infogetter.rs │ ├── item │ │ ├── convert_container.rs │ │ ├── filter.rs │ │ ├── gen.rs │ │ ├── info.rs │ │ ├── merged.rs │ │ ├── mod.rs │ │ ├── slot.rs │ │ ├── throw.rs │ │ └── time.rs │ ├── map │ │ ├── builder.rs │ │ ├── from_template.rs │ │ ├── mod.rs │ │ ├── search.rs │ │ ├── tile_info.rs │ │ ├── update.rs │ │ ├── wall_damage.rs │ │ └── wilderness.rs │ ├── mod.rs │ ├── newgame │ │ └── mod.rs │ ├── npc │ │ ├── map_search.rs │ │ └── mod.rs │ ├── party.rs │ ├── play_time.rs │ ├── playeract │ │ ├── mod.rs │ │ ├── moving.rs │ │ ├── restart.rs │ │ ├── shortcut.rs │ │ └── use_tool.rs │ ├── power.rs │ ├── quest.rs │ ├── region.rs │ ├── saveload.rs │ ├── script_exec.rs │ ├── script_methods.rs │ ├── shop.rs │ ├── site │ │ ├── gen.rs │ │ ├── mod.rs │ │ └── temp.rs │ ├── skill.rs │ ├── target.rs │ ├── time.rs │ ├── town.rs │ ├── turnloop.rs │ └── view.rs │ ├── log.rs │ ├── main.rs │ ├── msg_box.rs │ ├── screen.rs │ ├── sdltypeconv.rs │ ├── text │ ├── desc.rs │ ├── effect.rs │ ├── img_inline.rs │ ├── macros.rs │ ├── mod.rs │ ├── prefix.rs │ ├── readable.rs │ ├── text_id_impl.rs │ └── to_text.rs │ ├── util.rs │ └── window │ ├── ability_window.rs │ ├── build_obj_dialog.rs │ ├── choose_window.rs │ ├── closer.rs │ ├── creation_window.rs │ ├── dialogreq.rs │ ├── equip_window.rs │ ├── exit_window.rs │ ├── faction_window.rs │ ├── game_info_window.rs │ ├── group_window.rs │ ├── help_window.rs │ ├── indicator.rs │ ├── item_info_window.rs │ ├── item_menu.rs │ ├── item_window.rs │ ├── list_desc_window.rs │ ├── log_window.rs │ ├── main_window.rs │ ├── minimap.rs │ ├── misc_window.rs │ ├── mod.rs │ ├── msg_dialog.rs │ ├── newgame_window.rs │ ├── progress_bar.rs │ ├── quest_window.rs │ ├── read_window.rs │ ├── register_shortcut_dialog.rs │ ├── sidebar.rs │ ├── slot_window.rs │ ├── start_window.rs │ ├── status_window.rs │ ├── talk_window.rs │ ├── text_input_dialog.rs │ ├── text_window.rs │ ├── tile_menu.rs │ ├── toolbar.rs │ ├── widget │ ├── border.rs │ ├── button.rs │ ├── close_button.rs │ ├── gauge.rs │ ├── image.rs │ ├── label.rs │ ├── list.rs │ ├── mod.rs │ └── vscroll.rs │ └── winpos.rs └── script ├── Cargo.toml ├── build.rs ├── python └── script_yield.py └── src ├── engine.rs ├── error.rs ├── lib.rs ├── message.rs ├── parse.rs ├── random.rs └── rr.rs /.clippy.toml: -------------------------------------------------------------------------------- 1 | single-char-binding-names-threshold = 10 2 | too-many-arguments-threshold = 10 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | check-test: 7 | name: Ci 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - run: rustup component add rustfmt 17 | - run: rustup component add clippy 18 | - run: sudo apt-get update && sudo apt-get upgrade 19 | - run: sudo apt-get install -y libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev libsdl2-mixer-dev libgtk-3-dev 20 | - uses: actions-rs/cargo@v1 21 | with: 22 | command: check 23 | - uses: actions-rs/cargo@v1 24 | with: 25 | command: test 26 | - uses: actions-rs/cargo@v1 27 | with: 28 | command: fmt 29 | args: --all -- --check 30 | - uses: actions-rs/cargo@v1 31 | with: 32 | command: clippy 33 | args: -- -D warnings 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | build-with-vcpkg: 10 | runs-on: windows-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set env 14 | shell: bash 15 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 16 | - name: Install rust 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: stable 21 | target: x86_64-pc-windows-msvc 22 | override: true 23 | - name: Build pak 24 | shell: bash 25 | run: ./build-pak.sh 26 | - name: Setup cargo-vcpkg 27 | run: cargo install cargo-vcpkg 28 | - name: Build 29 | run: | 30 | pushd rusted-ruins 31 | cargo vcpkg build 32 | popd 33 | cargo rustc --release --features sdl2-static-link -p rusted-ruins -- -Clink-args="/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup" 34 | - name: Package 35 | shell: bash 36 | run: | 37 | mkdir rusted-ruins-windows 38 | mv assets rusted-ruins-windows/ 39 | cp target/release/rusted-ruins.exe rusted-ruins-windows/ 40 | 7z a rusted-ruins_${RELEASE_VERSION}_windows.zip rusted-ruins-windows/ 41 | - name: Upload 42 | uses: actions/upload-artifact@v2 43 | with: 44 | name: windows-binary 45 | path: "*.zip" 46 | - name: Release 47 | uses: softprops/action-gh-release@v1 48 | with: 49 | draft: true 50 | prerelease: true 51 | files: "*.zip" 52 | 53 | build-deb: 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@v2 57 | - name: Install rust 58 | uses: actions-rs/toolchain@v1 59 | with: 60 | profile: minimal 61 | toolchain: stable 62 | target: x86_64-unknown-linux-gnu 63 | override: true 64 | - name: Install dev packages 65 | run: sudo apt-get -y update && sudo apt-get -yq --no-install-suggests --no-install-recommends install libsdl2-dev libsdl2-ttf-dev libsdl2-image-dev libsdl2-mixer-dev libgtk-3-dev 66 | - name: Build pak 67 | shell: bash 68 | run: ./build-pak.sh 69 | - name: Setup cargo-deb 70 | run: cargo install cargo-deb 71 | - name: Build 72 | run: cargo deb -p rusted-ruins -- --features deb 73 | - name: Upload 74 | uses: actions/upload-artifact@v2 75 | with: 76 | name: deb-package 77 | path: target/debian/*.deb 78 | - name: Release 79 | uses: softprops/action-gh-release@v1 80 | with: 81 | draft: true 82 | prerelease: true 83 | files: target/debian/*.deb 84 | -------------------------------------------------------------------------------- /.github/workflows/test-build.yml: -------------------------------------------------------------------------------- 1 | name: TestBuilds 2 | 3 | on: 4 | push: 5 | branches: 6 | - build 7 | 8 | jobs: 9 | build-with-vcpkg: 10 | runs-on: windows-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set env 14 | shell: bash 15 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 16 | - name: Install rust 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: stable 21 | target: x86_64-pc-windows-msvc 22 | override: true 23 | - name: Build pak 24 | shell: bash 25 | run: ./build-pak.sh 26 | - name: Setup cargo-vcpkg 27 | run: cargo install cargo-vcpkg 28 | - name: Build 29 | run: | 30 | pushd rusted-ruins 31 | cargo vcpkg build 32 | popd 33 | cargo rustc --release --features sdl2-static-link -p rusted-ruins -- -Clink-args="/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup" 34 | - name: Package 35 | shell: bash 36 | run: | 37 | mkdir rusted-ruins-windows 38 | mv assets rusted-ruins-windows/ 39 | cp target/release/rusted-ruins.exe rusted-ruins-windows/ 40 | 7z a rusted-ruins_${RELEASE_VERSION}_windows.zip rusted-ruins-windows/ 41 | - name: Upload 42 | uses: actions/upload-artifact@v2 43 | with: 44 | name: windows-binary 45 | path: "*.zip" 46 | 47 | build-deb: 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v2 51 | - name: Install rust 52 | uses: actions-rs/toolchain@v1 53 | with: 54 | profile: minimal 55 | toolchain: stable 56 | target: x86_64-unknown-linux-gnu 57 | override: true 58 | - name: Install dev packages 59 | run: sudo apt-get -y update && sudo apt-get -yq --no-install-suggests --no-install-recommends install libsdl2-dev libsdl2-ttf-dev libsdl2-image-dev libsdl2-mixer-dev libgtk-3-dev 60 | - name: Build pak 61 | shell: bash 62 | run: ./build-pak.sh 63 | - name: Setup cargo-deb 64 | run: cargo install cargo-deb 65 | - name: Build 66 | run: cargo deb -p rusted-ruins -- --features deb 67 | - name: Upload 68 | uses: actions/upload-artifact@v2 69 | with: 70 | name: deb-package 71 | path: target/debian/*.deb 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [workspace] 3 | members = [ 4 | "common", 5 | "filebox", 6 | "makepak", 7 | "audio", 8 | "map-generator", 9 | "map-editor", 10 | "rng", 11 | "rules", 12 | "rusted-ruins", 13 | "script", 14 | ] 15 | -------------------------------------------------------------------------------- /assets/config/changeable.default.toml: -------------------------------------------------------------------------------- 1 | [game_log] 2 | combat_log = "minimum" 3 | -------------------------------------------------------------------------------- /assets/config/config.toml: -------------------------------------------------------------------------------- 1 | 2 | lang = "en" 3 | second_lang = "ja" 4 | screen_config = "screen/800x600.toml" 5 | hardware_acceleration = true 6 | enable_joystick = false 7 | sound_effect_volume = 80 8 | music_volume = 80 9 | -------------------------------------------------------------------------------- /assets/config/control.toml: -------------------------------------------------------------------------------- 1 | menu_centering = false 2 | -------------------------------------------------------------------------------- /assets/config/font.toml: -------------------------------------------------------------------------------- 1 | 2 | mono_font = "NotoMono-Regular.ttf" 3 | 4 | [font_names] 5 | # en = "NotoSans-Medium.ttf" 6 | en = "NotoSansCJKjp-Medium.otf" 7 | ja = "NotoSansCJKjp-Medium.otf" 8 | -------------------------------------------------------------------------------- /assets/config/input.toml: -------------------------------------------------------------------------------- 1 | 2 | wasd_mode = true 3 | 4 | [normal] 5 | return = "enter" 6 | e = "eat_item" 7 | i = "open_item_win" 8 | g = "pick_up_item" 9 | h = "open_help_win" 10 | q = "drink_item" 11 | r = "release_item" 12 | v = "drop_item" 13 | escape = "open_exit_win" 14 | f1 = "open_item_win" 15 | f2 = "open_status_win" 16 | f3 = "open_ability_win" 17 | f4 = "open_creation_win" 18 | f5 = "open_game_info_win" 19 | f6 = "open_exit_win" 20 | f12 = "open_debug_command_win" 21 | 22 | [dialog] 23 | return = "enter" 24 | escape = "cancel" 25 | tab = "rotate_window_right" 26 | a = "item_information" 27 | q = "cancel" 28 | -------------------------------------------------------------------------------- /assets/config/screen/1280x800.toml: -------------------------------------------------------------------------------- 1 | # ScreenConfig for 1280x800 2 | screen_w = 1280 3 | screen_h = 800 4 | 5 | [main_window] 6 | x = 36 7 | y = 0 8 | w = 1244 # 1280 - 36 9 | h = 620 # 800 - 180 10 | 11 | [log_window] 12 | x = 203 13 | y = 660 14 | w = 1080 15 | h = 177 16 | 17 | [minimap_window] 18 | x = 0 19 | y = 623 20 | w = 200 21 | h = 177 22 | 23 | [sidebar] 24 | x = 0 25 | y = 43 26 | w = 33 27 | h = 577 28 | 29 | [toolbar] 30 | x = 203 31 | y = 623 32 | w = 102 33 | h = 34 34 | 35 | [shortcut_list] 36 | x = 308 37 | y = 623 38 | w = 600 39 | h = 34 40 | 41 | [hp_indicator] 42 | x = 52 43 | y = 538 44 | w = 80 45 | h = 15 46 | 47 | [mp_indicator] 48 | x = 52 49 | y = 560 50 | w = 80 51 | h = 15 52 | 53 | [sp_indicator] 54 | x = 52 55 | y = 582 56 | w = 80 57 | h = 15 58 | 59 | [floor_info] 60 | x = 100 61 | y = 0 62 | w = 200 63 | h = 18 64 | 65 | [status_info] 66 | x = 42 67 | y = 513 68 | h = 20 69 | 70 | [date_info] 71 | x = 4 72 | y = 3 73 | 74 | [time_info] 75 | x = 0 76 | y = 0 77 | w = 60 78 | h = 40 79 | 80 | [[hborders]] 81 | x = 0 82 | y = 620 83 | len = 1280 84 | 85 | [[hborders]] 86 | x = 200 87 | y = 657 88 | len = 1080 89 | 90 | [[vborders]] 91 | x = 33 92 | y = 0 93 | len = 620 94 | 95 | [[vborders]] 96 | x = 200 97 | y = 622 98 | len = 177 99 | 100 | [[vborders]] 101 | x = 305 102 | y = 623 103 | len = 34 104 | 105 | -------------------------------------------------------------------------------- /assets/config/screen/800x600.toml: -------------------------------------------------------------------------------- 1 | # ScreenConfig for 800x600 2 | screen_w = 800 3 | screen_h = 600 4 | 5 | [main_window] 6 | x = 36 7 | y = 0 8 | w = 764 9 | h = 420 10 | 11 | [log_window] 12 | x = 203 13 | y = 460 14 | w = 600 15 | h = 140 16 | 17 | [minimap_window] 18 | x = 0 19 | y = 423 20 | w = 200 21 | h = 177 22 | 23 | [sidebar] 24 | x = 0 25 | y = 43 26 | w = 33 27 | h = 377 28 | 29 | [toolbar] 30 | x = 203 31 | y = 423 32 | w = 102 33 | h = 34 34 | 35 | [shortcut_list] 36 | x = 308 37 | y = 423 38 | w = 600 39 | h = 34 40 | 41 | [hp_indicator] 42 | x = 52 43 | y = 338 44 | w = 80 45 | h = 15 46 | 47 | [mp_indicator] 48 | x = 52 49 | y = 360 50 | w = 80 51 | h = 15 52 | 53 | [sp_indicator] 54 | x = 52 55 | y = 382 56 | w = 80 57 | h = 15 58 | 59 | [floor_info] 60 | x = 100 61 | y = 0 62 | w = 200 63 | h = 18 64 | 65 | [status_info] 66 | x = 42 67 | y = 313 68 | h = 20 69 | 70 | [date_info] 71 | x = 4 72 | y = 3 73 | 74 | [time_info] 75 | x = 0 76 | y = 0 77 | w = 60 78 | h = 40 79 | 80 | [[hborders]] 81 | x = 0 82 | y = 420 83 | len = 800 84 | 85 | [[hborders]] 86 | x = 200 87 | y = 457 88 | len = 600 89 | 90 | [[vborders]] 91 | x = 33 92 | y = 0 93 | len = 420 94 | 95 | [[vborders]] 96 | x = 200 97 | y = 422 98 | len = 177 99 | 100 | [[vborders]] 101 | x = 305 102 | y = 423 103 | len = 34 104 | 105 | -------------------------------------------------------------------------------- /assets/fonts/NotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/rusted-ruins/44c786930be83685b99297a3ce757b042ec8d333/assets/fonts/NotoMono-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/NotoSans-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/rusted-ruins/44c786930be83685b99297a3ce757b042ec8d333/assets/fonts/NotoSans-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/NotoSansCJKjp-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/rusted-ruins/44c786930be83685b99297a3ce757b042ec8d333/assets/fonts/NotoSansCJKjp-Medium.otf -------------------------------------------------------------------------------- /assets/text/en/misc/item_info_text.ftl: -------------------------------------------------------------------------------- 1 | item_info_text-empty_slot = Empty 2 | item_info_text-hit = Hit: {$hit} 3 | item_info_text-item_kind = Kind of this item: {$item_kind} 4 | item_info_text-facility = Facility ({$ty}) quality {$quality} 5 | item_info_text-material = This is made from {$material} 6 | item_info_text-medical_effect = {$medical_effect} 7 | item_info_text-nutrition = Nutrition: {$nutrition} 8 | item_info_text-melee_weapon = Melee attack: {$power} 9 | item_info_text-ranged_weapon = Ranged attack: {$power} 10 | item_info_text-remaining = (remaining: {$duration}) 11 | 12 | -------------------------------------------------------------------------------- /assets/text/en/misc/quest.ftl: -------------------------------------------------------------------------------- 1 | quest-slay_monsters = Slay monsters ({$monster}) 2 | desc-quest-slay_monsters = Slay "{$monster}" x {$n}. 3 | -------------------------------------------------------------------------------- /assets/text/en/ui/choices.ftl: -------------------------------------------------------------------------------- 1 | creation-start = Start 2 | dialog-choice-exit = Exit 3 | dialog-choice-exit_game = Exit Game 4 | dialog-choice-loadgame = Load Game 5 | dialog-choice-newgame = New Game 6 | dialog-choice-save_game = Save Game 7 | dialog-choice-restart = Restart 8 | dialog-button-cancel = Cancel 9 | dialog-button-exit = Exit 10 | dialog-button-save = Save 11 | dialog-button-title_screen = Title Screen 12 | equip_menu-information = Information 13 | equip_menu-remove = Remove 14 | item_menu-information = Information 15 | item_menu-drop_all = Drop all 16 | item_menu-register-as-shortcut = Register as shortcut 17 | item_menu-select-building = Select building 18 | tile-menu-chop = Chop a tree 19 | tile-menu-down-stairs = Go down the stairs 20 | tile-menu-enter-site = Enter this site 21 | tile-menu-enter-wilderness = Enter wilderness 22 | tile-menu-exit-to-region-map = Exit to region map 23 | tile-menu-deconstruct = Deconstruct 24 | tile-menu-harvest = Harvest 25 | tile-menu-information = Information 26 | tile-menu-target = Target 27 | tile-menu-move-to-next-map = Move to next map 28 | tile-menu-pick-up-items = Pick up items 29 | tile-menu-start-centering = Centering 30 | tile-menu-stop-centering = Stop centering 31 | tile-menu-up-stairs = Go up the stairs 32 | register_shortcut = Register shortcut ({$i}) 33 | -------------------------------------------------------------------------------- /assets/text/en/ui/command.ftl: -------------------------------------------------------------------------------- 1 | command-open_creation_win = Creation 2 | command-open_debug_command_win = Open Debug Command Window 3 | command-open_equip_win = Equipment 4 | command-open_exit_win = Open Exit Window 5 | command-open_game_info_win = Game Information 6 | command-open_help_win = Help 7 | command-open_status_win = Status 8 | command-open_item_menu = Item Menu 9 | command-pick_up_item = Pick Up Item 10 | command-drop_item = Drop Item 11 | command-drink_item = Drink Item 12 | command-eat_item = Eat Item 13 | command-release_item = Release Item 14 | command-shot = Shot 15 | -------------------------------------------------------------------------------- /assets/text/en/ui/misc.ftl: -------------------------------------------------------------------------------- 1 | item-charges = charges 2 | -------------------------------------------------------------------------------- /assets/text/en/ui/msg.ftl: -------------------------------------------------------------------------------- 1 | dialog-exit = Do you want to exit from Rusted Ruins? 2 | dialog-exit_to_regionmap = Do you want to exit to region map? 3 | dialog-gameover = You die... 4 | dialog-move_floor = Do you want to move from this floor? 5 | dialog-enter_site = Do you want to enter {$site_name}? 6 | newgame-choose_class = Choose your class 7 | newgame-choose_trait = Choose your traits 8 | newgame-input_player_name = Please input your name 9 | newgame-player_info = Do you start game as this character? 10 | newgame-remaining_point = Remaining point: {$remaining_point} 11 | -------------------------------------------------------------------------------- /assets/text/en/ui/window.ftl: -------------------------------------------------------------------------------- 1 | tab_text-chara_stats = Stats 2 | tab_text-chara_equipments = Equip 3 | tab_text-chara_skills = Skills 4 | tab_text-chara_traits = Traits 5 | tab_text-creation_art = Art 6 | tab_text-creation_construction = Build 7 | tab_text-creation_cooking = Cook 8 | tab_text-creation_craft = Craft 9 | tab_text-creation_pharmacy = Pharmacy 10 | tab_text-creation_smith = Smith 11 | tab_text-game_info = Info 12 | tab_text-game_info_faction = Factions 13 | tab_text-game_info_quest = Quests 14 | tab_text-item_list = List 15 | tab_text-item_drop = Drop 16 | tab_text-item_throw = Throw 17 | tab_text-item_drink = Drink 18 | tab_text-item_eat = Eat 19 | tab_text-item_use = Use 20 | tab_text-item_release = Release 21 | tab_text-item_read = Read 22 | tab_text-item_open = Open 23 | tab_text-item_take = Take 24 | tab_text-item_put = Put 25 | button_text-quest-report = Report 26 | button_text-quest-take = Take 27 | button_text-back = Back 28 | button_text-cancel = Cancel 29 | button_text-close = Close 30 | button_text-creation-start = Start 31 | button_text-next = Next 32 | button_text-start = Start 33 | label_text-status-carry = Carry 34 | label_text-status-faction = Faction 35 | label_text-status-travel_speed = Travel Speed 36 | label_text-play_time = Play Time 37 | label_text-creation-use-facility = Facility to use 38 | label_text-creation-required-facility = Required facility 39 | label_text-creation-no-required-facility = No required facility 40 | label_text-creation-enough-ingredients = Enough ingredients 41 | label_text-creation-not-enough-ingredients = Not enough ingredients 42 | label_text-creation-required_skill = Required Skill 43 | label_text-quest-delivery_chest = To delivery chest 44 | label_text-quest-reward = Reward 45 | list_header-faction = Faction 46 | list_header-relation = Relation 47 | list_item_text-creation-no_ingredient = No available {$group} 48 | -------------------------------------------------------------------------------- /assets/text/ja/misc/item_info_text.ftl: -------------------------------------------------------------------------------- 1 | item_info_text-empty_slot = 空 2 | item_info_text-hit = 命中: {$hit} 3 | item_info_text-item_kind = アイテムの種別: {$item_kind} 4 | item_info_text-facility = 設備 ({$ty}) 品質{$quality} 5 | item_info_text-material = 素材: {$material} 6 | item_info_text-medical_effect = {$medical_effect} 7 | item_info_text-nutrition = 栄養価: {$nutrition} 8 | item_info_text-melee_weapon = 近接攻撃力: {$power} 9 | item_info_text-ranged_weapon = 遠隔攻撃力: {$power} 10 | item_info_text-remaining = (残り: {$duration}) 11 | 12 | -------------------------------------------------------------------------------- /assets/text/ja/misc/quest.ftl: -------------------------------------------------------------------------------- 1 | quest-slay_monsters = モンスター討伐 ({$monster}) 2 | desc-quest-slay_monsters = {$monster}を{$n}体倒す。 3 | -------------------------------------------------------------------------------- /assets/text/ja/ui/choices.ftl: -------------------------------------------------------------------------------- 1 | creation-start = スタート 2 | dialog-choice-exit = 終了 3 | dialog-choice-exit_game = ゲーム終了 4 | dialog-choice-loadgame = ゲームをロード 5 | dialog-choice-newgame = ニューゲーム 6 | dialog-choice-save_game = ゲームを保存 7 | dialog-choice-restart = 再開 8 | dialog-button-cancel = キャンセル 9 | dialog-button-exit = 終了 10 | dialog-button-save = セーブ 11 | dialog-button-title_screen = タイトル画面 12 | equip_menu-information = 情報 13 | equip_menu-remove = 外す 14 | item_menu-information = 情報 15 | item_menu-drop_all = 全部置く 16 | item_menu-register-as-shortcut = ショートカット登録 17 | item_menu-select-building = 建造物選択 18 | tile-menu-chop = 木を切る 19 | tile-menu-down-stairs = 階段を降りる 20 | tile-menu-enter-site = 入る 21 | tile-menu-enter-wilderness = 野外に入る 22 | tile-menu-exit-to-region-map = 外に出る 23 | tile-menu-deconstruct = 解体する 24 | tile-menu-harvest = 収穫する 25 | tile-menu-information = 情報 26 | tile-menu-target = ターゲット 27 | tile-menu-move-to-next-map = 次のマップへ 28 | tile-menu-pick-up-items = 拾う 29 | tile-menu-start-centering = センタリング 30 | tile-menu-stop-centering = センタリングをやめる 31 | tile-menu-up-stairs = 階段を上がる 32 | register_shortcut = ショートカット ({$i}) 33 | -------------------------------------------------------------------------------- /assets/text/ja/ui/command.ftl: -------------------------------------------------------------------------------- 1 | command-open_creation_win = クリエイション 2 | command-open_debug_command_win = デバッグコマンド 3 | command-open_equip_win = 装備画面 4 | command-open_exit_win = 終了画面 5 | command-open_game_info_win = ゲーム情報 6 | command-open_help_win = ヘルプ画面 7 | command-open_status_win = ステータス画面 8 | command-open_item_menu = アイテム画面 9 | command-pick_up_item = アイテムを拾う 10 | command-drop_item = アイテムを置く 11 | command-drink_item = アイテムを飲む 12 | command-eat_item = アイテムを食べる 13 | command-release_item = 魔道具を使う 14 | command-shot = 撃つ 15 | -------------------------------------------------------------------------------- /assets/text/ja/ui/misc.ftl: -------------------------------------------------------------------------------- 1 | item-charges = チャージ回数 2 | -------------------------------------------------------------------------------- /assets/text/ja/ui/msg.ftl: -------------------------------------------------------------------------------- 1 | dialog-exit = Rusted Ruinsを終了しますか? 2 | dialog-exit_to_regionmap = リージョンマップに戻りますか? 3 | dialog-gameover = やられてしまった…。 4 | dialog-move_floor = この階から移動しますか? 5 | dialog-enter_site = {$site_name}に入りますか? 6 | newgame-choose_class = クラスを選択して下さい 7 | newgame-choose_trait = 特性を選択して下さい 8 | newgame-input_player_name = プレイヤー名を入力して下さい 9 | newgame-player_info = 以下のキャラクターでゲームを始めますか? 10 | newgame-remaining_point = 残り特性ポイント: {$remaining_point} 11 | -------------------------------------------------------------------------------- /assets/text/ja/ui/window.ftl: -------------------------------------------------------------------------------- 1 | tab_text-chara_stats = ステータス 2 | tab_text-chara_equipments = 装備 3 | tab_text-chara_skills = スキル 4 | tab_text-chara_traits = 特性 5 | tab_text-creation_art = 芸術 6 | tab_text-creation_construction = 建築 7 | tab_text-creation_cooking = 料理 8 | tab_text-creation_craft = 細工 9 | tab_text-creation_pharmacy = 薬学 10 | tab_text-creation_smith = 鍛冶 11 | tab_text-game_info = 基本情報 12 | tab_text-game_info_faction = 派閥 13 | tab_text-game_info_quest = クエスト 14 | tab_text-item_list = リスト 15 | tab_text-item_drop = 置く 16 | tab_text-item_throw = 投げる 17 | tab_text-item_drink = 飲む 18 | tab_text-item_eat = 食べる 19 | tab_text-item_use = 使う 20 | tab_text-item_release = 解放 21 | tab_text-item_read = 読む 22 | tab_text-item_open = 開く 23 | tab_text-item_take = 出す 24 | tab_text-item_put = 入れる 25 | button_text-quest-report = 報告する 26 | button_text-quest-take = 引き受ける 27 | button_text-back = 戻る 28 | button_text-cancel = キャンセル 29 | button_text-close = 閉じる 30 | button_text-creation-start = スタート 31 | button_text-next = 次へ 32 | button_text-start = スタート 33 | label_text-status-carry = 運搬力 34 | label_text-status-faction = 所属 35 | label_text-status-travel_speed = 移動速度 36 | label_text-play_time = プレイ時間 37 | label_text-creation-use-facility = 必要な設備 38 | label_text-creation-required-facility = 必要な設備 39 | label_text-creation-no-required-facility = 設備不要 40 | label_text-creation-enough-ingredients = 原材料 41 | label_text-creation-not-enough-ingredients = 原材料不足 42 | label_text-creation-required_skill = 必要スキル 43 | label_text-quest-delivery_chest = 納品箱へ 44 | label_text-quest-reward = 報酬 45 | list_header-faction = 派閥 46 | list_header-relation = 関係値 47 | list_item_text-creation-no_ingredient = 利用不能な素材 {$group} 48 | -------------------------------------------------------------------------------- /audio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusted-ruins-audio" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["T. Okubo "] 6 | 7 | [lib] 8 | name = "rusted_ruins_audio" 9 | crate-type = ["rlib"] 10 | 11 | [dependencies] 12 | anyhow = "1" 13 | log = "0.4" 14 | flate2 = "1" 15 | tar = "0.4" 16 | 17 | [dependencies.sdl2] 18 | version = "0.35" 19 | default-features = false 20 | features = ["mixer"] 21 | -------------------------------------------------------------------------------- /audio/src/datwalker.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use flate2::read::GzDecoder; 3 | use std::fs::File; 4 | use std::io::Read; 5 | use std::path::Path; 6 | use tar::Archive; 7 | 8 | pub fn datwalker( 9 | path: &Path, 10 | target_ext: &str, 11 | mut f: F, 12 | ) -> Result<(), Error> { 13 | let file = File::open(path)?; 14 | let mut a = Archive::new(GzDecoder::new(file)); 15 | 16 | let entries = a.entries()?; 17 | 18 | for file in entries { 19 | let mut file = warn_continue!(file); 20 | let path = warn_continue!(file.header().path()); 21 | 22 | if path.extension().is_some() && path.extension().unwrap() == target_ext { 23 | let filename = warn_continue!(path.file_stem().ok_or("Invalid file name")) 24 | .to_string_lossy() 25 | .into_owned(); 26 | 27 | let mut buf = Vec::new(); 28 | warn_continue!(file.read_to_end(&mut buf)); 29 | let data: &'static [u8] = Box::leak(buf.into()); 30 | f(filename, data); 31 | } 32 | } 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /audio/src/musictable.rs: -------------------------------------------------------------------------------- 1 | use sdl2::mixer::Music; 2 | use std::cell::RefCell; 3 | use std::collections::HashMap; 4 | use std::fs; 5 | use std::path::Path; 6 | 7 | pub struct MusicTable { 8 | music: HashMap>, 9 | current_music: RefCell>, 10 | } 11 | 12 | impl MusicTable { 13 | pub fn new>(data_dirs: &[P]) -> MusicTable { 14 | // Load music from datadir/music 15 | let mut music = HashMap::new(); 16 | 17 | for data_dir in data_dirs { 18 | let music_dir = data_dir.as_ref().join("music"); 19 | let music_dir = warn_continue!(fs::read_dir(music_dir)); 20 | 21 | for entry in music_dir { 22 | let entry = warn_continue!(entry); 23 | 24 | // If this file isn't ogg 25 | let path = entry.path(); 26 | if path.extension().is_none() { 27 | continue; 28 | } 29 | let extension = path.extension().unwrap(); 30 | 31 | if extension == "opus" { 32 | let m = warn_continue!(Music::from_file(&path)); 33 | let filename = warn_continue!(path.file_stem().ok_or("Invalid file name")) 34 | .to_string_lossy() 35 | .into_owned(); 36 | 37 | music.insert(filename, m); 38 | } else if extension == "dat" { 39 | warn_continue!(crate::datwalker::datwalker( 40 | &path, 41 | "opus", 42 | |filename, data| { 43 | let m = match Music::from_static_bytes(data) { 44 | Ok(m) => m, 45 | Err(e) => { 46 | warn!("{}", e); 47 | return; 48 | } 49 | }; 50 | music.insert(filename, m); 51 | } 52 | )); 53 | } 54 | } 55 | } 56 | 57 | MusicTable { 58 | music, 59 | current_music: RefCell::new(None), 60 | } 61 | } 62 | 63 | pub fn play(&self, name: &str) -> Result<(), String> { 64 | let mut current_music = self.current_music.borrow_mut(); 65 | if current_music.is_some() && current_music.as_ref().unwrap() == name { 66 | return Ok(()); 67 | } 68 | 69 | if let Some(m) = self.music.get(name) { 70 | m.play(-1)?; // Pass -1 for infinite loop 71 | *current_music = Some(name.to_owned()); 72 | Ok(()) 73 | } else { 74 | Err(format!("Unknown music \"{name}\"")) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /audio/src/tool.rs: -------------------------------------------------------------------------------- 1 | macro_rules! warn_continue { 2 | ($value:expr) => { 3 | match $value { 4 | Ok(o) => o, 5 | Err(e) => { 6 | warn!("{}", e); 7 | continue; 8 | } 9 | } 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /audio/src/wavtable.rs: -------------------------------------------------------------------------------- 1 | use sdl2::mixer::{self, Channel, Chunk}; 2 | use std::collections::HashMap; 3 | use std::fs; 4 | use std::path::Path; 5 | 6 | pub struct WavTable { 7 | channel: Channel, 8 | chunks: HashMap, 9 | } 10 | 11 | impl WavTable { 12 | pub fn new>(data_dirs: &[P], volume: i32) -> WavTable { 13 | // Load chunks from datadir/sound 14 | let mut chunks = HashMap::new(); 15 | 16 | for data_dir in data_dirs { 17 | let sound_dir = data_dir.as_ref().join("sound"); 18 | let sound_dir = warn_continue!(fs::read_dir(sound_dir)); 19 | 20 | for entry in sound_dir { 21 | let entry = warn_continue!(entry); 22 | 23 | // If this file isn't wav 24 | let path = entry.path(); 25 | if path.extension().is_none() || path.extension().unwrap() != "wav" { 26 | continue; 27 | } 28 | 29 | let chunk = warn_continue!(Chunk::from_file(&path)); 30 | let filename = warn_continue!(path.file_stem().ok_or("Invalid file name")) 31 | .to_string_lossy() 32 | .into_owned(); 33 | 34 | chunks.insert(filename, chunk); 35 | } 36 | } 37 | 38 | let channel = mixer::Channel(0); 39 | channel.set_volume(volume); 40 | 41 | WavTable { channel, chunks } 42 | } 43 | 44 | pub fn play(&self, name: &str) -> Result<(), String> { 45 | if let Some(chunk) = self.chunks.get(name) { 46 | self.channel.halt(); 47 | self.channel.play(chunk, 0)?; 48 | Ok(()) 49 | } else { 50 | Err(format!("Unknown sound effect \"{name}\"")) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /build-pak.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cargo build --release -p rusted-ruins-makepak 4 | git clone https://github.com/garkimasera/rusted-ruins-pak 5 | 6 | pushd rusted-ruins-pak || exit 7 | ./build.sh ../target/release/rusted-ruins-makepak 8 | popd || exit 9 | 10 | mkdir -p ./assets/paks 11 | mkdir -p ./assets/rules 12 | mkdir -p ./assets/sound 13 | cp -r rusted-ruins-pak/paks/* -t ./assets/paks 14 | cp -r rusted-ruins-pak/rules/* -t ./assets/rules 15 | cp -r rusted-ruins-pak/text/* -t ./assets/text 16 | cp -r rusted-ruins-pak/sound/* -t ./assets/sound 17 | -------------------------------------------------------------------------------- /common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusted-ruins-common" 3 | version = "0.12.0" 4 | edition = "2021" 5 | authors = ["T. Okubo "] 6 | 7 | [lib] 8 | name = "rusted_ruins_common" 9 | crate-type = ["rlib"] 10 | 11 | [features] 12 | global_state_obj = [] 13 | 14 | [dependencies.rusted-ruins-rng] 15 | path = "../rng" 16 | package = "rusted-ruins-rng" 17 | 18 | [dependencies.filebox] 19 | path = "../filebox" 20 | 21 | [dependencies] 22 | anyhow = "1" 23 | serde = "1" 24 | serde_derive = "1" 25 | serde_json = "1" 26 | serde_cbor = "0.11" 27 | serde_with = "2" 28 | once_cell = "1" 29 | log = "0.4" 30 | bitflags = "1" 31 | ordered-float = { version = "3", features = ["rand", "serde"] } 32 | tar = "0.4" 33 | fnv = "1" 34 | thiserror = "1" 35 | arrayvec = { version = "0.7", features = ["serde"] } 36 | enum-map = "2" 37 | regex = "1" 38 | derivative = "2" 39 | 40 | tile-geom = { git = "https://github.com/garkimasera/tile-geom.git" } 41 | 42 | -------------------------------------------------------------------------------- /common/src/basic.rs: -------------------------------------------------------------------------------- 1 | // Basic parameters for this game 2 | 3 | /// Size of one tile 4 | pub const TILE_SIZE: u32 = 48; 5 | pub const TILE_SIZE_I: i32 = TILE_SIZE as i32; 6 | /// Size of piece image 7 | /// One tile includes 4 pieces 8 | pub const PIECE_SIZE: u32 = TILE_SIZE / 2; 9 | pub const PIECE_SIZE_I: i32 = TILE_SIZE_I / 2; 10 | /// The maximum height of wall images 11 | pub const MAX_WALL_HEIGHT: u32 = 80; 12 | /// Icon size 13 | pub const ICON_SIZE: u32 = 24; 14 | /// Tab icon width 15 | pub const TAB_ICON_W: u32 = 48; 16 | /// Tab icon height 17 | pub const TAB_ICON_H: u32 = 32; 18 | /// Tab text height 19 | pub const TAB_TEXT_H: u32 = 16; 20 | /// Window border thickness 21 | pub const WINDOW_BORDER_THICKNESS: u32 = 3; 22 | 23 | /// Maximum number of items on one tile 24 | pub const MAX_ITEM_TILE: usize = 256; 25 | /// Maximum number of equipment slots 26 | pub const MAX_EQUIP_SLOT: usize = 16; 27 | 28 | pub const WAIT_TIME_NUMERATOR: u32 = 100000; 29 | 30 | /// Needed exp value to level up 31 | pub const SKILL_EXP_LVUP: u16 = 10000; 32 | 33 | /// Maximum number of registered action shortcuts 34 | pub const MAX_ACTION_SHORTCUTS: usize = 32; 35 | 36 | // Path settings 37 | pub const APP_DIR_NAME: &str = "rusted-ruins"; 38 | pub const CFG_FILES_DIR: &str = "config"; 39 | pub const ABILITY_TXT_DIR: &str = "ability"; 40 | pub const FLAVOR_TXT_DIR: &str = "flavor"; 41 | pub const OBJ_TXT_DIR: &str = "obj"; 42 | pub const QUEST_TXT_DIR: &str = "quest"; 43 | pub const LOG_TXT_DIR: &str = "log"; 44 | pub const UI_TXT_DIR: &str = "ui"; 45 | pub const TALK_TXT_DIR: &str = "talk"; 46 | pub const MISC_TXT_DIR: &str = "misc"; 47 | pub const READABLE_TXT_DIR: &str = "readable"; 48 | pub const SAVE_DIR_NAME: &str = "save"; 49 | pub const SAVE_EXTENSION: &str = "rrsve"; 50 | 51 | /// Id table 52 | pub const ID_TABLE_SECTION_TAG: &str = "§"; 53 | 54 | /// The number of auto generated dungeons per region 55 | pub const MAX_AUTO_GEN_DUNGEONS: u32 = 20; 56 | 57 | /// If the number of items on one tile is more than this, 58 | /// remaining items will be not drawed. 59 | pub const MAX_ITEM_FOR_DRAW: usize = 5; 60 | 61 | /// The number of tile image layers 62 | pub const N_TILE_IMG_LAYER: usize = 4; 63 | 64 | /// Length of ArrayString id types 65 | pub const ARRAY_STR_ID_LEN: usize = 15; 66 | 67 | /// Copyable string type for id 68 | pub type ArrayStringId = arrayvec::ArrayString; 69 | 70 | /// Bonus / penalty representation used in this game 71 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)] 72 | #[repr(i8)] 73 | pub enum BonusLevel { 74 | Awful = -4, 75 | VeryBad = -3, 76 | Bad = -2, 77 | SlightlyBad = -1, 78 | None = 0, 79 | SlightlyGood = 1, 80 | Good = 2, 81 | VeryGood = 3, 82 | Excellent = 4, 83 | Superb = 5, 84 | } 85 | 86 | impl Default for BonusLevel { 87 | fn default() -> Self { 88 | BonusLevel::None 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /common/src/gamedata/learned_recipes.rs: -------------------------------------------------------------------------------- 1 | use crate::gamedata::CreationKind; 2 | 3 | #[derive(Clone, Debug, Serialize, Deserialize)] 4 | pub struct LearnedRecipes(Vec>); 5 | 6 | impl Default for LearnedRecipes { 7 | fn default() -> Self { 8 | LearnedRecipes(vec![vec![]; CreationKind::Smith as usize + 1]) 9 | } 10 | } 11 | 12 | impl LearnedRecipes { 13 | pub fn learned(&self, kind: CreationKind, recipe_name: &str) -> bool { 14 | let recipes = &self.0[kind as usize]; 15 | recipes.iter().any(|r| r == recipe_name) 16 | } 17 | 18 | pub fn add(&mut self, kind: CreationKind, recipe_name: &str) { 19 | let recipes = &mut self.0[kind as usize]; 20 | if recipes.iter().any(|s| s == recipe_name) { 21 | return; 22 | } 23 | recipes.push(recipe_name.to_owned()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /common/src/gamedata/meta.rs: -------------------------------------------------------------------------------- 1 | /// Meta data 2 | #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] 3 | pub struct MetaData { 4 | /// Save directory name 5 | save_name: String, 6 | } 7 | 8 | impl MetaData { 9 | pub fn save_name(&self) -> &str { 10 | &self.save_name 11 | } 12 | 13 | pub fn set_save_name(&mut self, s: &str) { 14 | self.save_name = s.to_owned(); 15 | } 16 | } 17 | 18 | impl Default for MetaData { 19 | fn default() -> MetaData { 20 | MetaData { 21 | save_name: "uninit".to_owned(), 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /common/src/gamedata/modifier.rs: -------------------------------------------------------------------------------- 1 | use super::{skill::SkillKind, Element, ElementArray}; 2 | use derivative::Derivative; 3 | use fnv::FnvHashMap; 4 | use ordered_float::NotNan; 5 | 6 | /// Represents modifier for character. 7 | #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)] 8 | pub enum CharaModifier { 9 | Str(i16), 10 | Vit(i16), 11 | Dex(i16), 12 | Int(i16), 13 | Wil(i16), 14 | Cha(i16), 15 | Spd(i16), 16 | Defence { 17 | element: Element, 18 | value: NotNan, 19 | }, 20 | DefenceMultiplier { 21 | element: Element, 22 | value: NotNan, 23 | }, 24 | } 25 | 26 | /// Summed effect of modifiers a character received by properties, status, and other factors. 27 | #[derive(Clone, Debug, Serialize, Deserialize, Derivative)] 28 | #[derivative(Default)] 29 | pub struct CharaTotalModifier { 30 | pub base_hp: i32, 31 | pub base_mp: i32, 32 | pub max_hp: i32, 33 | pub max_mp: i32, 34 | pub str: i16, 35 | pub vit: i16, 36 | pub dex: i16, 37 | pub int: i16, 38 | pub wil: i16, 39 | pub cha: i16, 40 | pub spd: i16, 41 | #[derivative(Default(value = "1.0"))] 42 | pub spd_factor: f32, 43 | pub skill_level: FnvHashMap, 44 | pub defence: ElementArray<(f32, f32)>, 45 | } 46 | -------------------------------------------------------------------------------- /common/src/gamedata/play_time.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Serialize, Deserialize)] 2 | pub struct PlayTime( 3 | /// Number of seconds elapsed since the start of game 4 | u64, 5 | ); 6 | 7 | impl Default for PlayTime { 8 | fn default() -> Self { 9 | PlayTime(0) 10 | } 11 | } 12 | 13 | impl PlayTime { 14 | pub fn seconds(&self) -> u64 { 15 | self.0 16 | } 17 | 18 | pub fn advance(&mut self, secs: u64) { 19 | self.0 += secs; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /common/src/gamedata/player.rs: -------------------------------------------------------------------------------- 1 | use super::CharaId; 2 | 3 | /// Unique data for player 4 | #[derive(Clone, PartialEq, Eq, Default, Debug, Serialize, Deserialize)] 5 | pub struct Player { 6 | money: i64, 7 | pub party: fnv::FnvHashSet, 8 | pub party_dead: fnv::FnvHashSet, 9 | } 10 | 11 | impl Player { 12 | pub fn money(&self) -> i64 { 13 | self.money 14 | } 15 | 16 | pub fn set_money(&mut self, a: i64) { 17 | assert!(a >= 0); 18 | self.money = a; 19 | } 20 | 21 | pub fn add_money(&mut self, diff: i64) { 22 | self.money += diff; 23 | } 24 | 25 | pub fn sub_money(&mut self, diff: i64) { 26 | self.money -= diff; 27 | } 28 | 29 | pub fn has_money(&self, a: i64) -> bool { 30 | self.money >= a 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /common/src/gamedata/quest.rs: -------------------------------------------------------------------------------- 1 | use super::{defs::Reward, SiteId, Time}; 2 | use crate::hashmap::HashSet; 3 | use crate::objholder::ItemIdx; 4 | use std::slice::{Iter, IterMut}; 5 | 6 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)] 7 | pub enum TownQuestState { 8 | Active, 9 | Reportable, 10 | } 11 | 12 | #[derive(Debug, Default, Serialize, Deserialize)] 13 | pub struct QuestHolder { 14 | pub town_quests: Vec<(TownQuestState, TownQuest)>, 15 | pub custom_quests: Vec, 16 | pub completed_custom_quests: HashSet, 17 | } 18 | 19 | #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] 20 | pub struct TownQuest { 21 | pub sid: SiteId, 22 | pub text_id: String, 23 | pub deadline: Option, 24 | pub reward: Reward, 25 | pub kind: TownQuestKind, 26 | } 27 | 28 | #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] 29 | pub enum TownQuestKind { 30 | ItemDelivering { items: Vec<(ItemIdx, u32)> }, 31 | DestroyBase, 32 | } 33 | 34 | #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] 35 | pub struct CustomQuest { 36 | pub id: String, 37 | pub phase: String, 38 | } 39 | -------------------------------------------------------------------------------- /common/src/gamedata/settings.rs: -------------------------------------------------------------------------------- 1 | use crate::basic::MAX_ACTION_SHORTCUTS; 2 | use crate::objholder::*; 3 | 4 | /// Custom settings for each game playing 5 | #[derive(Clone, PartialEq, Eq, Default, Debug, Serialize, Deserialize)] 6 | pub struct Settings { 7 | pub action_shortcuts: Vec>, 8 | } 9 | 10 | impl Settings { 11 | pub fn new() -> Self { 12 | Self { 13 | action_shortcuts: vec![None; MAX_ACTION_SHORTCUTS], 14 | } 15 | } 16 | } 17 | 18 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)] 19 | pub enum ActionShortcut { 20 | Throw(ItemIdx), 21 | Drink(ItemIdx), 22 | Eat(ItemIdx), 23 | Use(ItemIdx), 24 | Release(ItemIdx), 25 | Read(ItemIdx), 26 | } 27 | -------------------------------------------------------------------------------- /common/src/gamedata/shop.rs: -------------------------------------------------------------------------------- 1 | use crate::{gamedata::item::ItemList, sitegen::ShopGenData}; 2 | 3 | #[derive(Clone, Debug, Serialize, Deserialize)] 4 | pub struct Shop { 5 | pub items: ItemList, 6 | /// Shop level is used to choose shop items 7 | pub level: u32, 8 | pub custom_shop_gen: Option, 9 | } 10 | -------------------------------------------------------------------------------- /common/src/gamedata/town.rs: -------------------------------------------------------------------------------- 1 | use crate::gamedata::item::ItemLocation; 2 | use crate::gamedata::quest::*; 3 | use crate::gamedata::shop::*; 4 | use crate::gamedata::time::Time; 5 | use crate::objholder::ItemIdx; 6 | use fnv::FnvHashMap; 7 | use std::collections::hash_map::{Keys, Values, ValuesMut}; 8 | use std::iter::Copied; 9 | 10 | use super::ItemListLocation; 11 | 12 | #[derive(Clone, Debug, Serialize, Deserialize)] 13 | pub struct Town { 14 | id: String, 15 | pub quests: Vec, 16 | pub quests_last_update: Time, 17 | pub delivery_chest: Option, 18 | pub delivery_chest_content: Vec<(ItemIdx, u32)>, 19 | } 20 | 21 | impl Town { 22 | pub fn new(id: &str) -> Town { 23 | Town { 24 | id: id.to_owned(), 25 | quests: Vec::new(), 26 | quests_last_update: Time::from_secs(0), 27 | delivery_chest: None, 28 | delivery_chest_content: Vec::new(), 29 | } 30 | } 31 | 32 | pub fn id(&self) -> &str { 33 | &self.id 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /common/src/gamedata/traits.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)] 2 | pub enum CharaTraitOrigin { 3 | Inherent, 4 | Race, 5 | } 6 | 7 | #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)] 8 | pub enum CharaTrait { 9 | Id(String), 10 | Player, 11 | } 12 | -------------------------------------------------------------------------------- /common/src/gamedata/variables.rs: -------------------------------------------------------------------------------- 1 | use crate::hashmap::HashMap; 2 | 3 | /// Value is used to be stored in Variable. 4 | #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)] 5 | pub enum Value { 6 | None, 7 | Bool(bool), 8 | Int(i64), 9 | String(String), 10 | } 11 | 12 | impl Default for Value { 13 | fn default() -> Self { 14 | Value::None 15 | } 16 | } 17 | 18 | impl From for Value { 19 | fn from(value: bool) -> Self { 20 | Value::Bool(value) 21 | } 22 | } 23 | 24 | impl From for Value { 25 | fn from(value: u32) -> Self { 26 | Value::Int(value.into()) 27 | } 28 | } 29 | 30 | /// Stores variables which are referenced in scripts 31 | #[derive(Debug, Serialize, Deserialize)] 32 | pub struct Variables { 33 | global: HashMap, 34 | local: HashMap<(String, String), Value>, 35 | } 36 | 37 | impl Default for Variables { 38 | fn default() -> Self { 39 | Variables { 40 | global: HashMap::default(), 41 | local: HashMap::default(), 42 | } 43 | } 44 | } 45 | 46 | impl Variables { 47 | /// Get globally named variable 48 | pub fn global_var(&self, name: &str) -> Option<&Value> { 49 | self.global.get(name) 50 | } 51 | 52 | /// Get globally named variable (mutable) 53 | pub fn global_mut(&mut self, name: &str) -> Option<&mut Value> { 54 | self.global.get_mut(name) 55 | } 56 | 57 | /// Get globally named variable 58 | pub fn set_global_var(&mut self, name: S, v: Value) { 59 | self.global.insert(name.to_string(), v); 60 | } 61 | 62 | /// Remove globally named variable 63 | pub fn remove_global_var(&mut self, name: &str) { 64 | self.global.remove(name); 65 | } 66 | 67 | /// Get local named variable 68 | pub fn local_var(&self, script_id: &str, name: &str) -> Option<&Value> { 69 | self.local.get(&(script_id.to_owned(), name.to_owned())) 70 | } 71 | 72 | /// Get local named variable (mutable) 73 | pub fn local_mut(&mut self, script_id: &str, name: &str) -> Option<&mut Value> { 74 | self.local.get_mut(&(script_id.to_owned(), name.to_owned())) 75 | } 76 | 77 | /// Get local named variable 78 | pub fn set_local_var(&mut self, script_id: S1, name: S2, v: Value) { 79 | self.local 80 | .insert((script_id.to_string(), name.to_string()), v); 81 | } 82 | 83 | /// Remove local named variable 84 | pub fn remove_local_var(&mut self, script_id: &str, name: &str) { 85 | self.local.remove(&(script_id.to_owned(), name.to_owned())); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /common/src/hashmap.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::DefaultHasher; 2 | use std::hash::BuildHasherDefault; 3 | 4 | /// This hashmap does not have random state. 5 | /// Used to fix the order of items in cbor maps. 6 | pub type HashMap = std::collections::HashMap>; 7 | pub type HashSet = std::collections::HashSet>; 8 | -------------------------------------------------------------------------------- /common/src/impl_filebox.rs: -------------------------------------------------------------------------------- 1 | use crate::gamedata::Map; 2 | use crate::utils::to_writer_with_mode; 3 | use filebox::*; 4 | use serde_cbor::{error::Error as SerdeError, from_reader}; 5 | use std::io::{Error as IoError, Read, Write}; 6 | use thiserror::Error; 7 | 8 | impl WithId for Map { 9 | type Error = MapLoadError; 10 | 11 | fn write(mut w: W, a: &Self) -> Result<(), MapLoadError> { 12 | to_writer_with_mode(&mut w, a)?; 13 | Ok(()) 14 | } 15 | 16 | fn read(r: R) -> Result { 17 | Ok(from_reader(r)?) 18 | } 19 | } 20 | 21 | #[derive(Error, Debug)] 22 | pub enum MapLoadError { 23 | #[error("io error")] 24 | Io(#[from] IoError), 25 | #[error("serde error")] 26 | Serde(#[from] SerdeError), 27 | } 28 | -------------------------------------------------------------------------------- /common/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn( 2 | rust_2018_compatibility, 3 | rust_2018_idioms, 4 | future_incompatible, 5 | nonstandard_style 6 | )] 7 | #![cfg_attr(not(global_state_obj), allow(dead_code))] 8 | #![cfg_attr(not(global_state_obj), allow(unused_variables))] 9 | #![cfg_attr(not(global_state_obj), allow(unused_imports))] 10 | #![allow(clippy::derivable_impls)] 11 | 12 | #[macro_use] 13 | extern crate serde_derive; 14 | #[macro_use] 15 | extern crate log; 16 | extern crate tile_geom as geom; 17 | 18 | mod utils; 19 | 20 | pub mod basic; 21 | pub mod hashmap; 22 | pub mod obj; 23 | #[macro_use] 24 | pub mod idx_conv; 25 | pub mod gamedata; 26 | #[cfg(feature = "global_state_obj")] 27 | pub mod gobj; 28 | pub mod impl_filebox; 29 | pub mod item_selector; 30 | pub mod maptemplate; 31 | pub mod objholder; 32 | pub mod pakutil; 33 | pub mod piece_pattern; 34 | pub mod regiongen; 35 | pub mod saveload; 36 | pub mod sitegen; 37 | -------------------------------------------------------------------------------- /common/src/pakutil.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::to_writer_with_mode; 2 | use serde_cbor::from_reader; 3 | use std::io::{Read, Write}; 4 | 5 | use super::obj::Object; 6 | 7 | /// Read object from msgpack bytes 8 | pub fn read_object(r: R) -> Result { 9 | from_reader(r) 10 | } 11 | 12 | /// Write object as msgpack 13 | pub fn write_object(w: &mut W, obj: &Object) -> Result<(), String> { 14 | to_writer_with_mode(w, obj).map_err(|e| e.to_string()) 15 | } 16 | 17 | /* 18 | Implement load_objs_dir 19 | */ 20 | use std::fs; 21 | use std::path::Path; 22 | 23 | #[derive(Debug)] 24 | pub enum PakLoadingError { 25 | Io(std::io::Error), 26 | Cbor(serde_cbor::error::Error), 27 | } 28 | 29 | /// Load objects from pak files recursively 30 | pub fn load_objs_dir(dir: &Path, cb: F) -> Vec { 31 | let mut err_stack = Vec::new(); 32 | let mut cb = cb; 33 | 34 | walk_dir(dir, &mut cb, &mut err_stack); 35 | err_stack 36 | } 37 | 38 | fn walk_dir(dir: &Path, cb: &mut dyn FnMut(Object), err_stack: &mut Vec) { 39 | let entry_iter = match fs::read_dir(dir) { 40 | Ok(o) => o, 41 | Err(e) => { 42 | err_stack.push(PakLoadingError::Io(e)); 43 | return; 44 | } 45 | }; 46 | 47 | for entry in entry_iter { 48 | let entry = match entry { 49 | Ok(o) => o, 50 | Err(e) => { 51 | err_stack.push(PakLoadingError::Io(e)); 52 | continue; 53 | } 54 | }; 55 | let path = entry.path(); 56 | if path.is_dir() { 57 | walk_dir(&path, cb, err_stack); 58 | } else if path.extension().is_some() && path.extension().unwrap() == "pak" { 59 | read_tar(&path, cb, err_stack); 60 | } 61 | } 62 | } 63 | 64 | /// Read tar file and load objects 65 | pub fn read_tar(path: &Path, cb: &mut dyn FnMut(Object), err_stack: &mut Vec) { 66 | let outputfile = match fs::File::open(path) { 67 | Ok(o) => o, 68 | Err(e) => { 69 | err_stack.push(PakLoadingError::Io(e)); 70 | return; 71 | } 72 | }; 73 | 74 | let mut ar = tar::Archive::new(outputfile); 75 | 76 | let entries = match ar.entries() { 77 | Ok(o) => o, 78 | Err(e) => { 79 | err_stack.push(PakLoadingError::Io(e)); 80 | return; 81 | } 82 | }; 83 | 84 | for file in entries { 85 | let file = match file { 86 | Ok(o) => o, 87 | Err(e) => { 88 | err_stack.push(PakLoadingError::Io(e)); 89 | continue; 90 | } 91 | }; 92 | 93 | let object = match read_object(file) { 94 | Ok(o) => o, 95 | Err(e) => { 96 | err_stack.push(PakLoadingError::Cbor(e)); 97 | continue; 98 | } 99 | }; 100 | 101 | cb(object); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /common/src/regiongen.rs: -------------------------------------------------------------------------------- 1 | use geom::Coords; 2 | 3 | /// Hold data for region generation 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct RegionGenObject { 6 | pub id: String, 7 | pub map_template_id: String, 8 | /// Id and position of SiteGenObject for towns 9 | pub towns: Vec<(String, Coords)>, 10 | /// Id and position of SiteGenObject for other sites 11 | pub others: Vec<(String, Coords)>, 12 | } 13 | -------------------------------------------------------------------------------- /common/src/sitegen.rs: -------------------------------------------------------------------------------- 1 | use crate::basic::ArrayStringId; 2 | use crate::gamedata::faction::FactionId; 3 | use crate::gamedata::map::SiteSymbolKind; 4 | use crate::gamedata::site::SiteKind; 5 | use crate::gamedata::Reward; 6 | use crate::hashmap::HashMap; 7 | use crate::item_selector::ItemSelector; 8 | use geom::Coords; 9 | 10 | /// Hold data for site generation 11 | #[derive(Clone, Serialize, Deserialize)] 12 | pub struct SiteGenObject { 13 | pub id: String, 14 | pub kind: SiteKind, 15 | pub site_symbol: SiteSymbolKind, 16 | pub default_faction_id: FactionId, 17 | pub map_template_id: Vec, 18 | pub npcs: Vec, 19 | // pub random_npcs: Vec<>, 20 | pub shops: HashMap, 21 | pub quests: Vec, 22 | /// Delivery chest potision and object id for town sites 23 | pub delivery_chest: Option<(u32, Coords, String)>, 24 | } 25 | 26 | /// Data to generate a unique citizen 27 | #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] 28 | pub struct NpcGenData { 29 | /// Unique id in this site 30 | pub id: NpcGenId, 31 | pub pos: Coords, 32 | pub floor: u32, 33 | #[serde(default)] 34 | pub name: String, 35 | pub chara_template_id: String, 36 | #[serde(default)] 37 | pub talk_script: String, 38 | } 39 | 40 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] 41 | pub enum NpcGenId { 42 | Site(u32), 43 | Unique(ArrayStringId), 44 | } 45 | 46 | /// Data to generate a shop on the site 47 | #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] 48 | pub struct ShopGenData { 49 | #[serde(default)] 50 | pub shop_kind: String, 51 | #[serde(default)] 52 | pub item_selector: ItemSelector, 53 | } 54 | 55 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 56 | pub enum QuestGenData { 57 | ItemDelivering { 58 | #[serde(default = "quest_gen_data_default_weight")] 59 | weight: f32, 60 | text_id: String, 61 | deadline: u32, 62 | reward: Reward, 63 | items: Vec<(ItemSelector, u32)>, 64 | }, 65 | } 66 | 67 | impl QuestGenData { 68 | pub fn weight(&self) -> f32 { 69 | match self { 70 | Self::ItemDelivering { weight, .. } => *weight, 71 | } 72 | } 73 | } 74 | 75 | fn quest_gen_data_default_weight() -> f32 { 76 | 1.0 77 | } 78 | -------------------------------------------------------------------------------- /common/src/utils.rs: -------------------------------------------------------------------------------- 1 | use serde_cbor::ser::{IoWrite, Serializer}; 2 | 3 | pub fn to_writer_with_mode(writer: W, value: &T) -> Result<(), serde_cbor::error::Error> 4 | where 5 | W: std::io::Write, 6 | T: serde::Serialize, 7 | { 8 | if packed_format() { 9 | value.serialize(&mut Serializer::new(&mut IoWrite::new(writer)).packed_format()) 10 | } else { 11 | value.serialize(&mut Serializer::new(&mut IoWrite::new(writer))) 12 | } 13 | } 14 | 15 | fn packed_format() -> bool { 16 | if let Ok(v) = std::env::var("RUSTED_RUINS_OBJ_FIELD_MODE") { 17 | v != "NAMED" 18 | } else { 19 | true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /filebox/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "filebox" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["T. Okubo "] 6 | 7 | [dependencies] 8 | serde = "1" 9 | flate2 = "1" 10 | log = "0.4" 11 | -------------------------------------------------------------------------------- /filebox/src/ser.rs: -------------------------------------------------------------------------------- 1 | use super::{FileBox, WithId}; 2 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 3 | 4 | impl Serialize for FileBox { 5 | fn serialize(&self, serializer: S) -> Result 6 | where 7 | S: Serializer, 8 | { 9 | self.id.serialize(serializer) 10 | } 11 | } 12 | 13 | impl<'de, T: WithId> Deserialize<'de> for FileBox { 14 | fn deserialize(deserializer: D) -> Result, D::Error> 15 | where 16 | D: Deserializer<'de>, 17 | { 18 | let id = u64::deserialize(deserializer)?; 19 | Ok(FileBox::empty(id)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /images/rusted-ruins_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/rusted-ruins/44c786930be83685b99297a3ce757b042ec8d333/images/rusted-ruins_128x128.png -------------------------------------------------------------------------------- /images/rusted-ruins_24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/rusted-ruins/44c786930be83685b99297a3ce757b042ec8d333/images/rusted-ruins_24x24.png -------------------------------------------------------------------------------- /makepak/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusted-ruins-makepak" 3 | version = "0.12.0" 4 | edition = "2021" 5 | authors = ["T. Okubo "] 6 | 7 | [dependencies] 8 | anyhow = "1" 9 | once_cell = "1" 10 | regex = "1" 11 | serde = "1" 12 | serde_derive = "1" 13 | serde_with = "2" 14 | ron = "0.8" 15 | thiserror = "1" 16 | tar = "0.4" 17 | clap = { version = "4", features = ["derive"] } 18 | image = "0.24" 19 | 20 | tile-geom = { git = "https://github.com/garkimasera/tile-geom.git" } 21 | 22 | [dependencies.rusted-ruins-common] 23 | path = "../common" 24 | 25 | 26 | -------------------------------------------------------------------------------- /makepak/src/buildobj/item.rs: -------------------------------------------------------------------------------- 1 | use super::img::build_img; 2 | use crate::error::*; 3 | use crate::input::*; 4 | use anyhow::*; 5 | use common::gamedata::*; 6 | 7 | pub fn build_item_object(input: Input) -> Result { 8 | let img = get_optional_field!(input, image); 9 | let item = get_optional_field!(input, item); 10 | let flags = ItemFlags::empty(); 11 | 12 | let kind = match item.item_kind.as_str() { 13 | "object" => ItemKind::Object, 14 | "potion" => ItemKind::Potion, 15 | "throwing" => ItemKind::Throwing, 16 | "food" => ItemKind::Food, 17 | "magic_device" => ItemKind::MagicDevice, 18 | "weapon" => ItemKind::Weapon(get_optional_field!(item, weapon_kind)), 19 | "armor" => ItemKind::Armor(get_optional_field!(item, armor_kind)), 20 | "tool" => ItemKind::Tool, 21 | "container" => ItemKind::Container, 22 | "readable" => ItemKind::Readable, 23 | "material" => ItemKind::Material, 24 | "module" => ItemKind::Module, 25 | "special" => ItemKind::Special, 26 | _ => { 27 | bail!(PakCompileError::UnexpectedValue { 28 | field_name: "item_kind".to_owned(), 29 | value: item.item_kind.clone() 30 | }); 31 | } 32 | }; 33 | 34 | Ok(ItemObject { 35 | id: input.id, 36 | img: build_img(img)?.0, 37 | default_flags: flags, 38 | kind, 39 | group: item.group, 40 | basic_price: item.basic_price, 41 | w: item.w, 42 | quality_kind: item.quality_kind, 43 | gen_weight: item.gen_weight, 44 | shop_weight: item.shop_weight.unwrap_or(item.gen_weight), 45 | gen_level: item.gen_level, 46 | attrs: item.attrs, 47 | material_group: item.material_group, 48 | material: item.material, 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /makepak/src/compile.rs: -------------------------------------------------------------------------------- 1 | use crate::dir; 2 | use crate::error::*; 3 | use crate::pyscript::read_pyscript; 4 | use crate::verbose::print_verbose; 5 | use anyhow::{bail, Result}; 6 | use common::obj::Object; 7 | use common::pakutil::write_object; 8 | use std::fs::File; 9 | use std::io::{Read, Write}; 10 | use std::path::Path; 11 | use std::path::PathBuf; 12 | 13 | use crate::buildobj::build_object; 14 | use crate::input::Input; 15 | 16 | pub fn compile(files: &[PathBuf], output_file: &Path) { 17 | let out = File::create(output_file).unwrap(); 18 | let mut builder = tar::Builder::new(out); 19 | 20 | for f in files { 21 | let f = Path::new(f); 22 | if f.is_relative() { 23 | dir::set_src_dir(f.parent()); 24 | } else { 25 | dir::set_src_dir(None); 26 | } 27 | 28 | let read_result = if Some(true) == f.extension().map(|e| e == "py") { 29 | read_pyscript(f) 30 | } else { 31 | read_input_file(f) 32 | }; 33 | 34 | let obj = match read_result { 35 | Ok(o) => o, 36 | Err(e) => { 37 | eprintln!("Cannot process \"{}\"", f.to_string_lossy()); 38 | for e in e.chain() { 39 | eprintln!("{e}"); 40 | } 41 | continue; 42 | } 43 | }; 44 | let v = write_to_vec(&obj).unwrap(); 45 | write_data_to_tar(&mut builder, &v, obj.get_id()); 46 | } 47 | builder.finish().unwrap(); 48 | } 49 | 50 | fn read_input_file>(path: P) -> Result { 51 | let path = path.as_ref(); 52 | let s = { 53 | let mut f = File::open(path)?; 54 | let mut s = String::new(); 55 | f.read_to_string(&mut s)?; 56 | s 57 | }; 58 | 59 | print_verbose(|| format!("Processing \"{path:?}\"")); 60 | 61 | let ext = if let Some(ext) = path.extension() { 62 | ext 63 | } else { 64 | bail!( 65 | "given file does not have extension: {}", 66 | path.to_string_lossy() 67 | ); 68 | }; 69 | 70 | let input: Input = if ext == "ron" { 71 | ron::de::from_str(&s)? 72 | } else { 73 | bail!("invalid input file type: {}", path.to_string_lossy()); 74 | }; 75 | 76 | print_verbose(|| format!("{input:?}")); 77 | let object = build_object(input)?; 78 | 79 | Ok(object) 80 | } 81 | 82 | fn write_to_vec(obj: &Object) -> Result> { 83 | let mut v = Vec::new(); 84 | match write_object(&mut v, obj) { 85 | Ok(_) => Ok(v), 86 | Err(e) => bail!(PakCompileError::ObjWriteError { description: e }), 87 | } 88 | } 89 | 90 | fn write_data_to_tar(builder: &mut tar::Builder, data: &[u8], path: &str) { 91 | let mut header = tar::Header::new_gnu(); 92 | header.set_path(path).unwrap(); 93 | header.set_size(data.len() as u64); 94 | header.set_mtime(0); 95 | header.set_cksum(); 96 | 97 | builder.append(&header, data).unwrap(); 98 | } 99 | -------------------------------------------------------------------------------- /makepak/src/dir.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | use std::env::current_dir; 3 | use std::path::{Path, PathBuf}; 4 | 5 | thread_local!( 6 | pub static SRC_DIR: Cell> = Cell::new(None); 7 | ); 8 | 9 | pub fn set_src_dir(path: Option<&Path>) { 10 | let path = path.to_owned(); 11 | 12 | SRC_DIR.with(|src_dir| { 13 | src_dir.replace(path.map(|p| p.to_owned())); 14 | }); 15 | } 16 | 17 | pub fn get_src_dir() -> PathBuf { 18 | SRC_DIR.with(|src_dir| { 19 | let tmp = src_dir.replace(None); 20 | let return_val: PathBuf = if let Some(ref path) = tmp { 21 | path.clone() 22 | } else { 23 | current_dir().expect("Cannot get current directory") 24 | }; 25 | src_dir.replace(tmp); 26 | return_val 27 | }) 28 | } 29 | 30 | pub fn path_from_src_dir>(p: P) -> PathBuf { 31 | let p = p.as_ref(); 32 | let mut src_dir = get_src_dir(); 33 | src_dir.push(p); 34 | src_dir 35 | } 36 | -------------------------------------------------------------------------------- /makepak/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum PakCompileError { 5 | #[error("missing field: {field_name}")] 6 | MissingField { field_name: String }, 7 | #[error("unexpected value \"{field_name}\" for field \"{value}\"")] 8 | UnexpectedValue { field_name: String, value: String }, 9 | #[error("image size error: expected size is ({input_x}, {input_y}), but png file size is ({image_x}, {image_y})")] 10 | ImageSizeError { 11 | input_x: u32, 12 | input_y: u32, 13 | image_x: u32, 14 | image_y: u32, 15 | }, 16 | #[error("object writing error\n{description}")] 17 | ObjWriteError { description: String }, 18 | } 19 | -------------------------------------------------------------------------------- /makepak/src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn( 2 | rust_2018_compatibility, 3 | rust_2018_idioms, 4 | future_incompatible, 5 | nonstandard_style 6 | )] 7 | 8 | #[macro_use] 9 | extern crate serde_derive; 10 | extern crate rusted_ruins_common as common; 11 | extern crate tile_geom as geom; 12 | 13 | mod verbose; 14 | #[macro_use] 15 | mod input; 16 | mod buildobj; 17 | mod compile; 18 | mod dir; 19 | mod error; 20 | mod pyscript; 21 | 22 | use clap::Parser; 23 | use std::path::PathBuf; 24 | 25 | #[derive(Parser, Debug)] 26 | #[clap(author, version, about)] 27 | struct Args { 28 | input_files: Vec, 29 | #[clap(short, long)] 30 | output: Option, 31 | #[clap(long)] 32 | info: bool, 33 | #[clap(long)] 34 | verbose: bool, 35 | } 36 | 37 | fn main() { 38 | let args = Args::parse(); 39 | 40 | verbose::set_verbose(args.verbose); 41 | 42 | if args.input_files.is_empty() { 43 | let _ = ::command().print_help(); 44 | return; 45 | } 46 | 47 | // Print information of pak files 48 | if args.info { 49 | print_info(&args.input_files); 50 | } 51 | 52 | let output_file = args.output.unwrap_or_else(|| { 53 | let mut path = args.input_files[0].clone(); 54 | path.set_extension("pak"); 55 | path 56 | }); 57 | 58 | compile::compile(&args.input_files, &output_file); 59 | } 60 | 61 | fn print_info(files: &[PathBuf]) { 62 | println!("Information of {files:?} will be printed"); 63 | } 64 | -------------------------------------------------------------------------------- /makepak/src/pyscript.rs: -------------------------------------------------------------------------------- 1 | use crate::verbose::print_verbose; 2 | use anyhow::*; 3 | use common::obj::{Object, ScriptObject}; 4 | use once_cell::sync::Lazy; 5 | use regex::Regex; 6 | use std::fs::File; 7 | use std::io::{BufRead, BufReader, Read, Seek}; 8 | use std::path::Path; 9 | 10 | static FIRST_LINE: Lazy = Lazy::new(|| Regex::new("# rusted-ruins-script").unwrap()); 11 | static ID_LINE: Lazy = 12 | Lazy::new(|| Regex::new("# id = \"([a-zA-Z!][a-zA-Z0-9_.-]*)\"").unwrap()); 13 | 14 | /// Read python script file 15 | pub fn read_pyscript>(path: P) -> Result { 16 | let path = path.as_ref(); 17 | let mut f = BufReader::new(File::open(path)?); 18 | print_verbose(|| format!("Processing \"{path:?}\"")); 19 | 20 | // Check the first line 21 | let mut first_line = String::new(); 22 | f.read_line(&mut first_line)?; 23 | if !FIRST_LINE.is_match(&first_line) { 24 | bail!( 25 | "the first line of {} is not vaild. Must start with \"# rusted-ruins-script\"", 26 | path.to_string_lossy() 27 | ); 28 | } 29 | 30 | // Check the second line for id 31 | let mut second_line = String::new(); 32 | f.read_line(&mut second_line)?; 33 | let id = if let Some(caps) = ID_LINE.captures(&second_line) { 34 | caps.get(1).unwrap().as_str().to_owned() 35 | } else if let Some(file_stem) = path 36 | .file_stem() 37 | .and_then(|file_stem| file_stem.to_os_string().into_string().ok()) 38 | { 39 | file_stem 40 | } else { 41 | bail!( 42 | "file name of \"{}\" cannot be use for object id", 43 | path.to_string_lossy() 44 | ); 45 | }; 46 | 47 | f.rewind()?; 48 | let mut script = String::new(); 49 | f.read_to_string(&mut script)?; 50 | 51 | Ok(Object::Script(ScriptObject { id, script })) 52 | } 53 | -------------------------------------------------------------------------------- /makepak/src/verbose.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | 3 | thread_local!(static IS_VERBOSE: Cell = Cell::new(false)); 4 | 5 | pub fn set_verbose(is_verbose: bool) { 6 | IS_VERBOSE.with(|a| { 7 | a.set(is_verbose); 8 | }); 9 | } 10 | 11 | pub fn print_verbose String>(f: F) { 12 | let is_verbose = IS_VERBOSE.with(|a| a.get()); 13 | if !is_verbose { 14 | return; 15 | } 16 | 17 | let s = f(); 18 | println!("{s}"); 19 | } 20 | -------------------------------------------------------------------------------- /map-editor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusted-ruins-map-editor" 3 | version = "0.9.0" 4 | edition = "2021" 5 | authors = ["T. Okubo "] 6 | 7 | [dependencies] 8 | rusted-ruins-common = { path = "../common", features = ["global_state_obj"] } 9 | 10 | gtk = "0.17" 11 | gdk = "0.17" 12 | gio = "0.17" 13 | gdk-pixbuf = "0.17" 14 | cairo-rs = "0.17" 15 | tar = "0.4" 16 | arrayvec = "0.7" 17 | log = "0.4" 18 | env_logger = "0.10" 19 | 20 | tile-geom = { git = "https://github.com/garkimasera/tile-geom.git" } 21 | -------------------------------------------------------------------------------- /map-editor/src/file.rs: -------------------------------------------------------------------------------- 1 | use common::obj::MapTemplateObject; 2 | use common::obj::Object; 3 | use common::pakutil; 4 | use std::error::Error; 5 | use std::fs::File; 6 | use std::io::Write; 7 | use std::path::Path; 8 | 9 | pub fn load_from_file(path: &Path) -> Result> { 10 | let mut mapobj: Option = None; 11 | let mut errors = Vec::new(); 12 | 13 | pakutil::read_tar( 14 | path, 15 | &mut |object| { 16 | if let Object::MapTemplate(o) = object { 17 | mapobj = Some(o); 18 | } 19 | }, 20 | &mut errors, 21 | ); 22 | 23 | Ok(mapobj.ok_or("Object is not found")?) 24 | } 25 | 26 | pub fn save_to_file(path: &Path, map: MapTemplateObject) -> Result<(), Box> { 27 | let file = File::create(path)?; 28 | let mut builder = tar::Builder::new(file); 29 | let obj = Object::MapTemplate(map); 30 | let mut data: Vec = Vec::new(); 31 | pakutil::write_object(&mut data, &obj).unwrap(); 32 | write_data_to_tar(&mut builder, &data, obj.get_id()); 33 | builder.finish()?; 34 | Ok(()) 35 | } 36 | 37 | fn write_data_to_tar(builder: &mut tar::Builder, data: &[u8], path: &str) { 38 | let mut header = tar::Header::new_gnu(); 39 | header.set_path(path).unwrap(); 40 | header.set_size(data.len() as u64); 41 | header.set_mtime(get_unix_time()); 42 | header.set_cksum(); 43 | 44 | builder.append(&header, data).unwrap(); 45 | } 46 | 47 | use std::time::{SystemTime, UNIX_EPOCH}; 48 | 49 | fn get_unix_time() -> u64 { 50 | let duration = match SystemTime::now().duration_since(UNIX_EPOCH) { 51 | Ok(d) => d, 52 | Err(_) => { 53 | return 0; 54 | } 55 | }; 56 | duration.as_secs() 57 | } 58 | -------------------------------------------------------------------------------- /map-editor/src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn( 2 | rust_2018_compatibility, 3 | rust_2018_idioms, 4 | future_incompatible, 5 | nonstandard_style 6 | )] 7 | 8 | extern crate rusted_ruins_common as common; 9 | extern crate tile_geom as geom; 10 | 11 | mod edit_map; 12 | #[macro_use] 13 | mod ui; 14 | mod draw_map; 15 | mod file; 16 | mod iconview; 17 | mod pixbuf_holder; 18 | mod property_controls; 19 | 20 | use gio::prelude::*; 21 | use std::env; 22 | use std::path::PathBuf; 23 | 24 | pub fn main() { 25 | env_logger::init(); 26 | 27 | let application = gtk::Application::new( 28 | Some("com.github.rusted-ruins-map-editor"), 29 | gio::ApplicationFlags::empty(), 30 | ); 31 | 32 | let mut app_dir = get_app_dir().expect("Could not found application directory"); 33 | app_dir.push("paks"); 34 | let mut pak_dirs = vec![app_dir]; 35 | for mut addon_dir in get_addon_dir().into_iter() { 36 | addon_dir.push("paks"); 37 | pak_dirs.push(addon_dir); 38 | } 39 | 40 | common::gobj::init(pak_dirs); 41 | application.connect_startup(move |app| { 42 | ui::build_ui(app); 43 | }); 44 | application.connect_activate(|_| {}); 45 | application.run(); 46 | } 47 | 48 | /// Get application directory 49 | fn get_app_dir() -> Option { 50 | if let Some(e) = env::var_os("RUSTED_RUINS_APP_DIR") { 51 | return Some(PathBuf::from(e)); 52 | } 53 | 54 | if let Ok(mut exe_file) = env::current_exe() { 55 | exe_file.pop(); 56 | exe_file.push("data"); 57 | return Some(exe_file); 58 | } 59 | 60 | if let Ok(mut cdir) = env::current_dir() { 61 | cdir.push("data"); 62 | return Some(cdir); 63 | } 64 | None 65 | } 66 | 67 | /// Get addon directories 68 | fn get_addon_dir() -> Vec { 69 | let mut v = Vec::new(); 70 | if let Some(e) = env::var_os("RUSTED_RUINS_ADDON_DIR") { 71 | v.push(PathBuf::from(e)); 72 | } 73 | v 74 | } 75 | -------------------------------------------------------------------------------- /map-editor/src/pixbuf_holder.rs: -------------------------------------------------------------------------------- 1 | use common::gobj; 2 | use common::obj::Img; 3 | use common::objholder::*; 4 | use gdk_pixbuf::{prelude::PixbufLoaderExt, Pixbuf, PixbufLoader}; 5 | 6 | pub struct PixbufSet { 7 | /// Whole image 8 | pub image: Pixbuf, 9 | /// Clipped image used to icon 10 | pub icon: Pixbuf, 11 | } 12 | 13 | macro_rules! impl_pixbuf_holder { 14 | ($({$mem:ident, $idx:ty}),*) => { 15 | // Owns Pixbuf data 16 | pub struct PixbufHolder { 17 | $(pub $mem: Vec),* 18 | } 19 | 20 | impl PixbufHolder { 21 | pub fn new() -> PixbufHolder { 22 | let objholder = gobj::get_objholder(); 23 | 24 | let mut pbh = PixbufHolder { 25 | $($mem: Vec::new()),* 26 | }; 27 | 28 | $( 29 | for ref o in &objholder.$mem { 30 | let pixbuf = load_png(&o.img); 31 | pbh.$mem.push(pixbuf); 32 | } 33 | )* 34 | 35 | pbh 36 | } 37 | } 38 | 39 | $( 40 | impl Holder<$idx> for PixbufHolder { 41 | type ReturnType = PixbufSet; 42 | fn get(&self, idx: $idx) -> &PixbufSet { 43 | &self.$mem[idx.as_usize()] 44 | } 45 | } 46 | )* 47 | } 48 | } 49 | 50 | impl_pixbuf_holder! { 51 | {deco, DecoIdx}, 52 | {chara_template, CharaTemplateIdx}, 53 | {special_tile, SpecialTileIdx}, 54 | {tile, TileIdx}, 55 | {wall, WallIdx}, 56 | {item, ItemIdx} 57 | } 58 | 59 | fn load_png(img: &Img) -> PixbufSet { 60 | const ERR_MSG: &str = "Error occured while loading image"; 61 | let loader = PixbufLoader::with_type("png").expect(ERR_MSG); 62 | loader.write(&img.data).expect(ERR_MSG); 63 | loader.close().expect(ERR_MSG); 64 | let pixbuf = loader.pixbuf().expect(ERR_MSG); 65 | 66 | let pixbuf_icon = if img.grid_nx == 1 && img.grid_ny == 1 { 67 | pixbuf.clone() 68 | } else { 69 | pixbuf.new_subpixbuf(0, 0, img.w as i32, img.h as i32) 70 | }; 71 | 72 | PixbufSet { 73 | image: pixbuf, 74 | icon: pixbuf_icon, 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /map-generator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusted-ruins-map-generator" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["T. Okubo "] 6 | 7 | [lib] 8 | name = "rusted_ruins_map_generator" 9 | crate-type = ["rlib"] 10 | 11 | [dependencies] 12 | serde = "1" 13 | serde_derive = "1" 14 | arrayvec = "0.7" 15 | rand = "0.8" 16 | 17 | tile-geom = { git = "https://github.com/garkimasera/tile-geom.git" } 18 | 19 | [dependencies.rusted-ruins-rng] 20 | path = "../rng" 21 | -------------------------------------------------------------------------------- /map-generator/src/binary.rs: -------------------------------------------------------------------------------- 1 | use geom::*; 2 | 3 | pub fn create_binary_fractal(size: Coords, weight: f32) -> Array2d { 4 | let mut map = Array2d::new(size.0 as u32, size.1 as u32, false); 5 | let height = crate::fractal::create_fractal(size, false); 6 | 7 | for p in height.iter_idx() { 8 | if height[p] < weight { 9 | map[p] = true; 10 | } 11 | } 12 | 13 | map 14 | } 15 | -------------------------------------------------------------------------------- /rng/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusted-ruins-rng" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["T. Okubo "] 6 | 7 | [lib] 8 | name = "rusted_ruins_rng" 9 | crate-type = ["rlib"] 10 | 11 | [dependencies] 12 | rand = "0.8" 13 | rand_xorshift = "0.3" 14 | -------------------------------------------------------------------------------- /rules/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusted-ruins-rules" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["T. Okubo "] 6 | 7 | [lib] 8 | name = "rusted_ruins_rules" 9 | crate-type = ["rlib"] 10 | 11 | [dependencies] 12 | anyhow = "1" 13 | serde = "1" 14 | serde_derive = "1" 15 | ron = "0.8" 16 | serde_with = "2" 17 | smallvec = "1" 18 | log = "0.4" 19 | once_cell = "1" 20 | ordered-float = { version = "3", features = ["rand", "serde"] } 21 | 22 | tile-geom = { git = "https://github.com/garkimasera/tile-geom.git" } 23 | 24 | [dependencies.rusted-ruins-common] 25 | path = "../common" 26 | features = ["global_state_obj"] 27 | 28 | [dependencies.rusted-ruins-map-generator] 29 | path = "../map-generator" 30 | -------------------------------------------------------------------------------- /rules/src/ability.rs: -------------------------------------------------------------------------------- 1 | use super::Rule; 2 | use common::gamedata::*; 3 | use std::collections::{hash_map::Iter, HashMap}; 4 | 5 | #[derive(Serialize, Deserialize, Default, Debug)] 6 | #[serde(transparent)] 7 | pub struct Abilities(HashMap); 8 | 9 | impl Abilities { 10 | pub fn iter(&self) -> Iter<'_, AbilityId, Ability> { 11 | self.0.iter() 12 | } 13 | } 14 | 15 | impl Rule for Abilities { 16 | const NAME: &'static str = "abilities"; 17 | 18 | fn append(&mut self, other: Self) { 19 | for (k, v) in other.0.into_iter() { 20 | self.0.insert(k, v); 21 | } 22 | } 23 | } 24 | 25 | impl Abilities { 26 | pub fn get(&self, id: &AbilityId) -> Option<&Ability> { 27 | self.0.get(id) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /rules/src/biome.rs: -------------------------------------------------------------------------------- 1 | use super::Rule; 2 | use common::gobj::ObjIdxAsId; 3 | use common::objholder::*; 4 | use serde_with::serde_as; 5 | use std::collections::HashMap; 6 | 7 | /// Rules for wilderness map generation 8 | #[derive(Debug, Serialize, Deserialize)] 9 | pub struct Biomes { 10 | pub biomes: HashMap, 11 | pub sub_biomes: HashMap, 12 | } 13 | 14 | impl Rule for Biomes { 15 | const NAME: &'static str = "biomes"; 16 | 17 | fn append(&mut self, other: Self) { 18 | for (k, v) in other.biomes.into_iter() { 19 | self.biomes.insert(k, v); 20 | } 21 | for (k, v) in other.sub_biomes.into_iter() { 22 | self.sub_biomes.insert(k, v); 23 | } 24 | } 25 | } 26 | 27 | #[serde_as] 28 | #[derive(Debug, Serialize, Deserialize)] 29 | pub struct BiomeDetail { 30 | #[serde_as(as = "ObjIdxAsId")] 31 | pub tile: TileIdx, 32 | #[serde_as(as = "ObjIdxAsId")] 33 | pub wall: WallIdx, 34 | #[serde_as(as = "Vec<(ObjIdxAsId, _)>")] 35 | pub plants: Vec<(ItemIdx, f32)>, 36 | #[serde_as(as = "Vec<(ObjIdxAsId, _)>")] 37 | pub items: Vec<(ItemIdx, f32)>, 38 | } 39 | 40 | #[derive(Debug, Serialize, Deserialize)] 41 | pub struct SubBiomeDetail {} 42 | -------------------------------------------------------------------------------- /rules/src/chara.rs: -------------------------------------------------------------------------------- 1 | use super::Rule; 2 | 3 | /// Rules for character parameter calculation 4 | #[derive(Debug, Serialize, Deserialize)] 5 | pub struct Chara { 6 | /// Default value of CharaParams::view_range. 7 | /// The actual value will be adjusted by character traits, and map attributes, etc. 8 | pub default_view_range: i32, 9 | /// Minimul speed 10 | pub min_spd: u16, 11 | /// Factor for calculating max hp 12 | pub max_hp_skill_factor: i32, 13 | /// Factor for calculating max mp 14 | pub max_mp_skill_factor: i32, 15 | /// Denominator for calculating max mp 16 | pub max_mp_denominator: i32, 17 | /// The probability of HP regeneration per turn, multiplied by VIT 18 | pub hp_regeneration_probability: f32, 19 | /// Multiplying factor of HP regeneration 20 | pub hp_regeneration_factor: f32, 21 | /// The probability of MP regeneration per turn, multiplied by WIL 22 | pub mp_regeneration_probability: f32, 23 | /// Multiplying factor of HP regeneration 24 | pub mp_regeneration_factor: f32, 25 | /// Default sp when a new character is created 26 | pub sp_default: f32, 27 | /// Maximum sp 28 | pub sp_max: f32, 29 | /// Character's sp is decreased by this value per turn 30 | pub sp_consumption: f32, 31 | /// Character's sp is decreased by this value per turn when hp is under the maximum 32 | pub sp_consumption_regen: f32, 33 | /// sp consumption factor in region maps 34 | pub sp_consumption_factor_in_region_map: f32, 35 | /// sp border of hungry 36 | pub sp_hungry: f32, 37 | /// sp border of weak 38 | pub sp_weak: f32, 39 | /// sp border of starving 40 | pub sp_starving: f32, 41 | /// (additional sp) = (nutriton) * (sp_nutrition_factor) 42 | pub sp_nutrition_factor: f32, 43 | /// (carrying capacity) = ((STR) / 2 + (VIT)) * (SKILL + 10) * (carrying_capacity_factor) 44 | pub carrying_capacity_factor: f32, 45 | pub carrying_capacity_threshold_burdened: f32, 46 | pub carrying_capacity_threshold_stressed: f32, 47 | pub carrying_capacity_threshold_strained: f32, 48 | pub carrying_capacity_threshold_overloaded: f32, 49 | /// speed coefficent for encumbrance status 50 | pub speed_coeff_burdened: f32, 51 | pub speed_coeff_stressed: f32, 52 | pub speed_coeff_strained: f32, 53 | pub speed_coeff_overloaded: f32, 54 | /// Damage factor for calculating encumbrance damage 55 | pub damage_factor_burdened: f32, 56 | pub damage_factor_stressed: f32, 57 | pub damage_factor_strained: f32, 58 | pub damage_factor_overloaded: f32, 59 | /// Dafame factor for calculating poison damage 60 | pub damage_factor_poisoned: f32, 61 | } 62 | 63 | impl Rule for Chara { 64 | const NAME: &'static str = "chara"; 65 | } 66 | -------------------------------------------------------------------------------- /rules/src/chara_trait.rs: -------------------------------------------------------------------------------- 1 | use super::Rule; 2 | use common::gamedata::CharaModifier; 3 | use std::collections::HashMap; 4 | 5 | #[derive(Debug, Serialize, Deserialize)] 6 | #[serde(transparent)] 7 | pub struct CharaTraits(HashMap); 8 | 9 | impl CharaTraits { 10 | pub fn get(&self, id: &str) -> &CharaTrait { 11 | &self.0[id] 12 | } 13 | } 14 | 15 | impl Rule for CharaTraits { 16 | const NAME: &'static str = "traits"; 17 | 18 | fn append(&mut self, other: Self) { 19 | for (k, v) in other.0.into_iter() { 20 | self.0.insert(k, v); 21 | } 22 | } 23 | } 24 | 25 | /// Rules for character parameter calculation 26 | #[derive(Debug, Serialize, Deserialize)] 27 | pub struct CharaTrait { 28 | #[serde(default)] 29 | pub cost: i32, 30 | #[serde(default)] 31 | pub conflicts: Vec, 32 | pub modifiers: Vec, 33 | } 34 | -------------------------------------------------------------------------------- /rules/src/charagen.rs: -------------------------------------------------------------------------------- 1 | use super::Rule; 2 | use common::gamedata::*; 3 | 4 | /// Rules for character generation 5 | #[derive(Serialize, Deserialize)] 6 | pub struct CharaGen { 7 | /// List of skills all character must have 8 | pub common_skills: Vec, 9 | /// Equipment slots all character must have 10 | pub equip_slots: Vec, 11 | } 12 | 13 | impl Rule for CharaGen { 14 | const NAME: &'static str = "chara_gen"; 15 | } 16 | -------------------------------------------------------------------------------- /rules/src/class.rs: -------------------------------------------------------------------------------- 1 | use crate::Rule; 2 | use common::basic::BonusLevel; 3 | use common::gamedata::*; 4 | use std::collections::HashMap; 5 | 6 | #[derive(Serialize, Deserialize)] 7 | #[serde(transparent)] 8 | pub struct Classes(HashMap); 9 | 10 | impl Rule for Classes { 11 | const NAME: &'static str = "classes"; 12 | 13 | fn append(&mut self, other: Self) { 14 | for (k, v) in other.0.into_iter() { 15 | self.0.insert(k, v); 16 | } 17 | } 18 | } 19 | 20 | impl Classes { 21 | pub fn get(&self, chara_class: CharaClass) -> &Class { 22 | self.0 23 | .get(&chara_class) 24 | .unwrap_or_else(|| &self.0[&CharaClass::default()]) 25 | } 26 | } 27 | 28 | /// Rules for character generation 29 | #[derive(Debug, Serialize, Deserialize)] 30 | pub struct Class { 31 | /// Attribute revisions by class 32 | pub attr: CharaAttrDiff, 33 | /// Skill bonus 34 | pub skill_bonus: HashMap, 35 | /// Item generation for equipment slot with bonus for gen level and quality 36 | #[serde(default)] 37 | pub equips: Vec, 38 | } 39 | -------------------------------------------------------------------------------- /rules/src/combat.rs: -------------------------------------------------------------------------------- 1 | use super::Rule; 2 | 3 | /// Rules for calculation related to combat. 4 | #[derive(Serialize, Deserialize)] 5 | pub struct Combat { 6 | pub throw_range_factor: u32, 7 | pub throw_range_max: u32, 8 | /// Normal state AI will become combat state when detect an enemy in this range. 9 | pub detection_range: i32, 10 | /// Factor for detection probability. 11 | pub detection_factor: f32, 12 | } 13 | 14 | impl Rule for Combat { 15 | const NAME: &'static str = "combat"; 16 | } 17 | -------------------------------------------------------------------------------- /rules/src/creation.rs: -------------------------------------------------------------------------------- 1 | use super::Rule; 2 | use common::gamedata::CreationRequiredTime; 3 | use std::collections::HashMap; 4 | 5 | #[derive(Serialize, Deserialize)] 6 | pub struct Creation { 7 | pub required_time: HashMap, 8 | pub recipe_learning_level_margin: u32, 9 | } 10 | 11 | impl Rule for Creation { 12 | const NAME: &'static str = "creation"; 13 | } 14 | -------------------------------------------------------------------------------- /rules/src/dungeon_gen.rs: -------------------------------------------------------------------------------- 1 | use crate::Rule; 2 | use common::gamedata::*; 3 | use rusted_ruins_common::item_selector::ItemSelector; 4 | use std::collections::HashMap; 5 | 6 | /// Rules for map generation 7 | pub type DungeonGen = HashMap; 8 | 9 | #[derive(Serialize, Deserialize)] 10 | pub struct DungeonGenParams { 11 | /// map symbol 12 | pub symbol: SiteSymbolKind, 13 | /// Specify map generation type that is described in map_gen and its weight. 14 | pub map_gen: Vec<(String, f32)>, 15 | /// The probability of npc generation for each race 16 | pub npc_race_probability: HashMap, 17 | /// Default npc faction 18 | pub default_faction_id: FactionId, 19 | /// Tile and wall ids 20 | pub terrain: Vec<[String; 2]>, 21 | /// Additional walls to replace default wall. 22 | pub sub_walls: Vec<(String, f32)>, 23 | /// Item generatation probability on each tile 24 | pub item_gen_probability: f64, 25 | /// Item generation weight for each ItemKind 26 | pub item_gen_weight: Vec<(ItemSelector, f32)>, 27 | /// The range of number of floor of auto generated dungeons 28 | pub floor_range: [u32; 2], 29 | /// Default map music 30 | pub music: String, 31 | /// Underground or not. 32 | /// If underground, use downstairs tiles to the deeper floor, and the exit is upstairs tile. 33 | /// If not, upstairs tile is used to go to the deeper floor like towers. 34 | pub underground: bool, 35 | } 36 | 37 | impl Rule for DungeonGen { 38 | const NAME: &'static str = "dungeon_gen"; 39 | 40 | fn append(&mut self, other: Self) { 41 | for (k, v) in other.into_iter() { 42 | self.insert(k, v); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /rules/src/effect.rs: -------------------------------------------------------------------------------- 1 | use crate::Rule; 2 | 3 | /// Rules for effect processing 4 | #[derive(Debug, Serialize, Deserialize)] 5 | pub struct Effect { 6 | pub item_drink_power_factor: f32, 7 | pub item_eat_power_factor: f32, 8 | pub mining_power_factor: f32, 9 | pub mining_power_base: f32, 10 | pub restore_hp_factor: f32, 11 | pub throw_weight_to_power_factor: f32, 12 | } 13 | 14 | impl Rule for Effect { 15 | const NAME: &'static str = "effect"; 16 | } 17 | -------------------------------------------------------------------------------- /rules/src/exp.rs: -------------------------------------------------------------------------------- 1 | use crate::Rule; 2 | 3 | /// Rules for exp calculation 4 | #[derive(Serialize, Deserialize)] 5 | pub struct Exp { 6 | /// Level difference (skill level - base level) for the first element of adjust_coeff 7 | pub begin_adjust_coeff: isize, 8 | /// Coefficient to adjust exp by difference skill level between base level 9 | pub adjust_coeff: Vec, 10 | /// All exp is multiplied by this value 11 | pub base_factor: f32, 12 | /// Int bonus to exp is (exp) * (int) / (int_bonus_divisor) 13 | pub int_bonus_divisor: u32, 14 | /// Base exp to weapon skills after attacking 15 | pub attack: u32, 16 | /// Base exp to carry skill 17 | pub carry: u32, 18 | /// Probability of adding exp to carry exp 19 | pub carry_prob: f32, 20 | /// Base exp to Conceal skill 21 | pub conceal: u32, 22 | /// Base exp to Defence skill when attacked 23 | pub defence: u32, 24 | /// Base exp to Endurance skill when attacked 25 | pub endurance: u32, 26 | /// Base exp to Endurance skill when regeneration 27 | pub endurance_regeneration: u32, 28 | /// Probability to gain Endurance skill when regeneration 29 | pub endurance_regeneration_probability: f32, 30 | /// Base exp to Evasion skill when attacked 31 | pub evasion: u32, 32 | /// Base exp to Mining skill 33 | pub mining: u32, 34 | /// Max exp to Negotiation skill 35 | pub negotiation_max: u32, 36 | /// Base exp for Creation 37 | pub creation: u32, 38 | } 39 | 40 | impl Rule for Exp { 41 | const NAME: &'static str = "exp"; 42 | } 43 | -------------------------------------------------------------------------------- /rules/src/faction.rs: -------------------------------------------------------------------------------- 1 | use common::gamedata::*; 2 | use std::collections::HashMap; 3 | 4 | use crate::Rule; 5 | 6 | #[derive(Serialize, Deserialize)] 7 | pub struct Faction { 8 | pub relation_friend: FactionRelation, 9 | pub relation_neutral: FactionRelation, 10 | /// Amount of variation of relation when attacked 11 | pub relvar_attacked: i16, 12 | pub relvar_killed: i16, 13 | pub factions: HashMap, 14 | } 15 | 16 | impl Faction { 17 | pub fn get(&self, faction: FactionId) -> &FactionInfo { 18 | self.factions 19 | .get(&faction) 20 | .unwrap_or(&self.factions[&FactionId::default()]) 21 | } 22 | 23 | pub fn relation(&self, f1: FactionId, f2: FactionId) -> FactionRelation { 24 | let f1 = self.get(f1); 25 | let f2 = self.get(f2); 26 | std::cmp::min(f1.default_relation, f2.default_relation) 27 | } 28 | } 29 | 30 | /// Rules for character generation 31 | #[derive(Serialize, Deserialize)] 32 | pub struct FactionInfo { 33 | pub default_relation: FactionRelation, 34 | #[serde(default)] 35 | pub constant: bool, 36 | } 37 | 38 | impl Rule for Faction { 39 | const NAME: &'static str = "faction"; 40 | } 41 | -------------------------------------------------------------------------------- /rules/src/item.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use common::gamedata::*; 4 | 5 | use crate::Rule; 6 | 7 | #[derive(Serialize, Deserialize)] 8 | pub struct Item { 9 | pub quality_level_factor: u32, 10 | pub rotten_item: String, 11 | pub rotten_item_gen_per_gram: u32, 12 | pub build_obj_default: BuildObj, 13 | 14 | // About slots 15 | pub ability_slot_required_quality: HashMap, 16 | pub core_slot_required_quality: HashMap, 17 | pub extend_slot_required_quality: HashMap>, 18 | pub slot_install_cost_base: HashMap, 19 | pub slot_install_cost_factor: HashMap, 20 | } 21 | 22 | impl Rule for Item { 23 | const NAME: &'static str = "item"; 24 | } 25 | -------------------------------------------------------------------------------- /rules/src/map_gen.rs: -------------------------------------------------------------------------------- 1 | use crate::Rule; 2 | use common::hashmap::HashMap; 3 | use map_generator::MapGenParam; 4 | 5 | #[derive(Serialize, Deserialize)] 6 | pub struct MapGen { 7 | pub map_gen_params: HashMap, 8 | } 9 | 10 | impl Rule for MapGen { 11 | const NAME: &'static str = "map_gen"; 12 | 13 | fn append(&mut self, other: Self) { 14 | for (k, v) in other.map_gen_params.into_iter() { 15 | self.map_gen_params.insert(k, v); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /rules/src/material.rs: -------------------------------------------------------------------------------- 1 | use crate::Rule; 2 | use common::gamedata::*; 3 | use once_cell::sync::Lazy; 4 | use std::collections::HashMap; 5 | 6 | #[derive(Serialize, Deserialize)] 7 | #[serde(transparent)] 8 | pub struct Materials(HashMap); 9 | 10 | impl Rule for Materials { 11 | const NAME: &'static str = "materials"; 12 | 13 | fn append(&mut self, other: Self) { 14 | for (k, v) in other.0.into_iter() { 15 | self.0.insert(k, v); 16 | } 17 | } 18 | } 19 | 20 | impl Materials { 21 | pub fn get(&self, material_name: &MaterialName) -> &Material { 22 | if let Some(material) = self.0.get(material_name) { 23 | material 24 | } else { 25 | static MATERIAL: Lazy = Lazy::new(Material::default); 26 | &MATERIAL 27 | } 28 | } 29 | 30 | pub fn get_by_group(&self, group: &str, level: Option) -> Vec<(MaterialName, &Material)> { 31 | self.0 32 | .iter() 33 | .filter_map(|(k, v)| { 34 | if v.group != group { 35 | return None; 36 | } 37 | if level.is_none() || level.unwrap() >= v.level { 38 | Some((*k, v)) 39 | } else { 40 | None 41 | } 42 | }) 43 | .collect() 44 | } 45 | } 46 | 47 | /// Rules for character generation 48 | #[derive(Serialize, Deserialize, Default)] 49 | pub struct Material { 50 | /// Group of this material 51 | pub group: String, 52 | /// Generation weight 53 | pub gen_weight: f32, 54 | /// Minimum level for generation 55 | pub level: u32, 56 | /// Weight factor 57 | pub w: f32, 58 | /// Dice factor 59 | pub eff: f32, 60 | /// Price factor 61 | pub price: f32, 62 | } 63 | -------------------------------------------------------------------------------- /rules/src/newgame.rs: -------------------------------------------------------------------------------- 1 | use crate::Rule; 2 | use common::gamedata::*; 3 | use common::gobj::ObjIdxAsId; 4 | use common::objholder::ItemIdx; 5 | use geom::Coords; 6 | use serde_with::serde_as; 7 | use std::collections::HashMap; 8 | 9 | /// Rules for starting new game 10 | #[serde_as] 11 | #[derive(Serialize, Deserialize)] 12 | pub struct NewGame { 13 | /// The choices of character class 14 | pub class_choices: Vec, 15 | /// The choices of character trait 16 | pub trait_choices: Vec, 17 | pub trait_initial_point: i32, 18 | pub start_region: String, 19 | pub start_pos: Coords, 20 | pub start_money: u32, 21 | pub chara_template_table: HashMap, 22 | pub common_initial_skills: Vec, 23 | pub common_initial_abilities: Vec, 24 | #[serde_as(as = "Vec<(ObjIdxAsId, _)>")] 25 | pub common_initial_items: Vec<(ItemIdx, u32)>, 26 | /// Initial game date (year) 27 | pub initial_date_year: u32, 28 | /// Initial game date (month) 29 | pub initial_date_month: u32, 30 | /// Initial game date (day) 31 | pub initial_date_day: u32, 32 | /// Initial game date (hour) 33 | pub initial_date_hour: u32, 34 | } 35 | 36 | impl Rule for NewGame { 37 | const NAME: &'static str = "newgame"; 38 | } 39 | -------------------------------------------------------------------------------- /rules/src/npc.rs: -------------------------------------------------------------------------------- 1 | use crate::Rule; 2 | 3 | /// Various parameters for game playing 4 | #[derive(Serialize, Deserialize)] 5 | pub struct Npc { 6 | /// Duration of npc recovering after map switching 7 | pub map_switch_recover_minutes: u32, 8 | /// The maximum size of player's party 9 | pub party_size_max: u32, 10 | pub party_pathfinding_step: u32, 11 | } 12 | 13 | impl Rule for Npc { 14 | const NAME: &'static str = "npc"; 15 | } 16 | -------------------------------------------------------------------------------- /rules/src/npc_ai.rs: -------------------------------------------------------------------------------- 1 | use crate::Rule; 2 | use common::gamedata::*; 3 | use std::collections::HashMap; 4 | 5 | #[derive(Debug, Serialize, Deserialize)] 6 | #[serde(transparent)] 7 | pub struct NpcAIs(HashMap); 8 | 9 | impl Rule for NpcAIs { 10 | const NAME: &'static str = "npc_ai"; 11 | 12 | fn append(&mut self, other: Self) { 13 | for (k, v) in other.0.into_iter() { 14 | self.0.insert(k, v); 15 | } 16 | } 17 | } 18 | 19 | impl NpcAIs { 20 | pub fn get(&self, kind: NpcAiKind) -> &NpcAi { 21 | self.0 22 | .get(&kind) 23 | .unwrap_or_else(|| &self.0[&NpcAiKind::default()]) 24 | } 25 | } 26 | 27 | #[derive(Debug, Serialize, Deserialize)] 28 | pub struct NpcAi { 29 | pub move_kind: MoveKind, 30 | pub pathfinding_step: u32, 31 | /// Probability of random walk when normal state. 32 | #[serde(default)] 33 | pub walk_prob: f32, 34 | /// Probabilities of npc actions in combat. 35 | #[serde(default)] 36 | pub combat_prob: HashMap, 37 | /// Probability of approaching to enemy when combat state. 38 | #[serde(default)] 39 | pub approach_enemy_prob: f32, 40 | /// Probability of using ranged weapon. 41 | #[serde(default)] 42 | pub ranged_weapon_prob: f32, 43 | /// Probability of trying to use active skill. 44 | #[serde(default)] 45 | pub ability_prob: f32, 46 | #[serde(default = "search_turn_default")] 47 | /// Required turn to change state search to normal 48 | pub search_turn: u32, 49 | } 50 | 51 | #[derive(Debug, Serialize, Deserialize)] 52 | pub enum MoveKind { 53 | NoMove, 54 | Wander, 55 | Return, 56 | } 57 | 58 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] 59 | pub enum CombatActionKind { 60 | Skip, 61 | ApproachEnemy, 62 | RangedWeapon, 63 | Ability, 64 | } 65 | 66 | impl CombatActionKind { 67 | pub const ALL: &'static [CombatActionKind] = &[ 68 | CombatActionKind::ApproachEnemy, 69 | CombatActionKind::RangedWeapon, 70 | CombatActionKind::Ability, 71 | ]; 72 | } 73 | 74 | fn search_turn_default() -> u32 { 75 | 10 76 | } 77 | -------------------------------------------------------------------------------- /rules/src/params.rs: -------------------------------------------------------------------------------- 1 | use crate::Rule; 2 | use common::basic::BonusLevel; 3 | use std::collections::HashMap; 4 | 5 | /// Various parameters for game playing 6 | #[derive(Serialize, Deserialize)] 7 | pub struct Params { 8 | /// Minutes per one turn on maps in sites 9 | pub minutes_per_turn_normal: f32, 10 | /// Tile size in region map 11 | pub regionmap_tile_size: f32, 12 | /// Skill bonus (base * value.0 + value.1). 13 | pub skill_bonus: HashMap, 14 | pub magic_device_base_power: f32, 15 | } 16 | 17 | impl Rule for Params { 18 | const NAME: &'static str = "params"; 19 | } 20 | -------------------------------------------------------------------------------- /rules/src/power.rs: -------------------------------------------------------------------------------- 1 | use super::Rule; 2 | use ordered_float::NotNan; 3 | 4 | /// Rules for calculation related to power/hit calclation. 5 | #[derive(Serialize, Deserialize)] 6 | pub struct Power { 7 | pub skill_base: f32, 8 | pub base_evasion_power: f32, 9 | pub base_defence: f32, 10 | pub bare_hand_hit: NotNan, 11 | pub bare_hand_power_base: f32, 12 | pub bare_hand_power_factor: f32, 13 | pub bare_hand_power_var: f32, 14 | pub hit_calc_factor0: f32, 15 | pub hit_calc_factor1: f32, 16 | pub hit_calc_factor2: f32, 17 | pub throw_weight_factor: f32, 18 | pub throw_hit_str_factor: f32, 19 | pub medical_power_base: f32, 20 | } 21 | 22 | impl Rule for Power { 23 | const NAME: &'static str = "power"; 24 | } 25 | -------------------------------------------------------------------------------- /rules/src/quest.rs: -------------------------------------------------------------------------------- 1 | use crate::Rule; 2 | 3 | /// Rules for quest 4 | #[derive(Serialize, Deserialize)] 5 | pub struct Quest { 6 | pub duplicate_factor: f32, 7 | pub update_duration_days: u32, 8 | } 9 | 10 | impl Rule for Quest { 11 | const NAME: &'static str = "quest"; 12 | } 13 | -------------------------------------------------------------------------------- /rules/src/race.rs: -------------------------------------------------------------------------------- 1 | use crate::Rule; 2 | use common::gamedata::*; 3 | use smallvec::{smallvec, SmallVec}; 4 | use std::collections::HashMap; 5 | 6 | #[derive(Serialize, Deserialize)] 7 | #[serde(transparent)] 8 | pub struct Races(HashMap); 9 | 10 | impl Races { 11 | pub fn get(&self, id: &str) -> &Race { 12 | self.get_id_race(id).1 13 | } 14 | 15 | pub fn get_id_race<'a>(&'a self, id: &str) -> (&'a str, &'a Race) { 16 | if let Some((id, race)) = self.0.get_key_value(id) { 17 | (id, race) 18 | } else { 19 | error!("tried to get unknown race \"{}\"", id); 20 | self.0 21 | .get_key_value("!") 22 | .map(|(id, race)| (id.as_str(), race)) 23 | .unwrap() 24 | } 25 | } 26 | 27 | /// Get races as an iterator including base races 28 | pub fn iter<'a>(&'a self, id: &str) -> impl Iterator { 29 | self.get_races(id).into_iter().map(|(_, race)| race) 30 | } 31 | 32 | /// Get race ids as an iterator including base races 33 | pub fn iter_ids<'a>(&'a self, id: &str) -> impl Iterator { 34 | self.get_races(id).into_iter().map(|(id, _)| id) 35 | } 36 | 37 | fn get_races<'a>(&'a self, id: &str) -> SmallVec<[(&'a str, &'a Race); 4]> { 38 | let (id, race) = self.get_id_race(id); 39 | if race.base_race.is_empty() { 40 | smallvec![(id, race)] 41 | } else { 42 | let mut v = self.get_races(&race.base_race); 43 | v.push((id, race)); 44 | v 45 | } 46 | } 47 | } 48 | 49 | impl Rule for Races { 50 | const NAME: &'static str = "races"; 51 | 52 | fn append(&mut self, other: Self) { 53 | for (k, v) in other.0.into_iter() { 54 | self.0.insert(k, v); 55 | } 56 | } 57 | } 58 | 59 | /// Rules for character generation 60 | #[derive(Serialize, Deserialize)] 61 | pub struct Race { 62 | #[serde(default)] 63 | pub base_race: String, 64 | /// Available Equipment slots 65 | #[serde(default)] 66 | pub equip_slots: Vec, 67 | /// Default element protection 68 | #[serde(default)] 69 | pub element_protection: ElementArray, 70 | /// Race traits. 71 | #[serde(default)] 72 | pub traits: Vec, 73 | } 74 | -------------------------------------------------------------------------------- /rules/src/recipe.rs: -------------------------------------------------------------------------------- 1 | use super::Rule; 2 | use common::gamedata::{CreationKind, Recipe}; 3 | 4 | #[derive(Serialize, Deserialize)] 5 | pub struct Recipes { 6 | #[serde(default)] 7 | art_recipes: Vec, 8 | #[serde(default)] 9 | construction_recipes: Vec, 10 | #[serde(default)] 11 | cooking_recipes: Vec, 12 | #[serde(default)] 13 | craft_recipes: Vec, 14 | #[serde(default)] 15 | pharmacy_recipes: Vec, 16 | #[serde(default)] 17 | smith_recipes: Vec, 18 | } 19 | 20 | impl Rule for Recipes { 21 | const NAME: &'static str = "recipes"; 22 | 23 | fn append(&mut self, mut other: Self) { 24 | self.art_recipes.append(&mut other.art_recipes); 25 | self.construction_recipes 26 | .append(&mut other.construction_recipes); 27 | self.cooking_recipes.append(&mut other.cooking_recipes); 28 | self.craft_recipes.append(&mut other.craft_recipes); 29 | self.pharmacy_recipes.append(&mut other.pharmacy_recipes); 30 | self.smith_recipes.append(&mut other.smith_recipes); 31 | 32 | self.art_recipes.sort(); 33 | self.construction_recipes.sort(); 34 | self.cooking_recipes.sort(); 35 | self.craft_recipes.sort(); 36 | self.pharmacy_recipes.sort(); 37 | self.smith_recipes.sort(); 38 | } 39 | } 40 | 41 | impl Recipes { 42 | pub fn get(&self, kind: CreationKind) -> &[Recipe] { 43 | match kind { 44 | CreationKind::Art => self.art_recipes.as_ref(), 45 | CreationKind::Construction => self.construction_recipes.as_ref(), 46 | CreationKind::Cooking => self.cooking_recipes.as_ref(), 47 | CreationKind::Craft => self.craft_recipes.as_ref(), 48 | CreationKind::Pharmacy => self.pharmacy_recipes.as_ref(), 49 | CreationKind::Smith => self.smith_recipes.as_ref(), 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /rules/src/town.rs: -------------------------------------------------------------------------------- 1 | use crate::Rule; 2 | use common::item_selector::ItemSelector; 3 | use std::collections::HashMap; 4 | 5 | /// Used for town simulation 6 | #[derive(Serialize, Deserialize)] 7 | pub struct Town { 8 | /// The minimum number of shop items 9 | pub min_shop_items: u32, 10 | /// The maximum number of shop items 11 | pub max_shop_items: u32, 12 | /// Shop kinds and its item selectors. 13 | pub shop_kinds: HashMap, 14 | } 15 | 16 | impl Rule for Town { 17 | const NAME: &'static str = "town"; 18 | } 19 | -------------------------------------------------------------------------------- /rules/src/world.rs: -------------------------------------------------------------------------------- 1 | use crate::Rule; 2 | use common::gamedata::Duration; 3 | 4 | /// Rules for game world 5 | #[derive(Serialize, Deserialize)] 6 | pub struct World { 7 | /// Restart map path 8 | pub restart_path: String, 9 | /// Script id to execute on restart 10 | pub restart_script: String, 11 | /// Interval of map regeneration 12 | pub map_regen_interval: Duration, 13 | } 14 | 15 | impl Rule for World { 16 | const NAME: &'static str = "world"; 17 | } 18 | -------------------------------------------------------------------------------- /rusted-ruins/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusted-ruins" 3 | version = "0.12.0" 4 | edition = "2021" 5 | authors = ["T. Okubo "] 6 | 7 | [features] 8 | default = [] 9 | deb = [] 10 | sdl2-static-link = ["sdl2/static-link", "sdl2/use-vcpkg"] 11 | 12 | [dependencies] 13 | anyhow = "1" 14 | clap = { version = "4", features = ["derive"] } 15 | dirs = "4" 16 | env_logger = "0.10" 17 | fluent = "0.14" 18 | fnv = "1" 19 | once_cell = "1" 20 | log = "0.4" 21 | regex = "1" 22 | serde = "1" 23 | serde_derive = "1" 24 | extend = "1" 25 | rand = "0.8" 26 | ordered-float = { version = "3", features = ["rand", "serde"] } 27 | tar = "0.4" 28 | toml = "0.7" 29 | unic-langid = "0.9" 30 | walkdir = "2" 31 | num-rational = "0.4" 32 | 33 | tile-geom = { git = "https://github.com/garkimasera/tile-geom.git" } 34 | 35 | [dependencies.rusted-ruins-common] 36 | path = "../common" 37 | features = ["global_state_obj"] 38 | 39 | [dependencies.rusted-ruins-audio] 40 | path = "../audio" 41 | 42 | [dependencies.rusted-ruins-rng] 43 | path = "../rng" 44 | 45 | [dependencies.rusted-ruins-rules] 46 | path = "../rules" 47 | 48 | [dependencies.rusted-ruins-map-generator] 49 | path = "../map-generator" 50 | 51 | [dependencies.rusted-ruins-script] 52 | path = "../script" 53 | 54 | [dependencies.sdl2] 55 | version = "0.35" 56 | default-features = false 57 | features = ["ttf", "image", "mixer"] 58 | 59 | # [profile.release] 60 | # debug = true 61 | 62 | # for cargo-deb 63 | # build after "./build-pak.sh", and with command "cargo deb -p rusted-ruins -- --features deb" 64 | [package.metadata.deb] 65 | copyright = "2017, T. Okubo " 66 | license-file = ["../LICENSE", "0"] 67 | extended-description = """\ 68 | Extensible open world rogue like game with pixel art.""" 69 | depends = "libsdl2-2.0-0,libsdl2-image-2.0-0,libsdl2-mixer-2.0-0,libsdl2-ttf-2.0-0" 70 | section = "games" 71 | priority = "optional" 72 | assets = [ 73 | ["../target/release/rusted-ruins", "usr/games/", "755"], 74 | ["../README.md", "usr/share/doc/rusted-ruins/README", "644"], 75 | ["../assets/**/*", "usr/share/games/rusted-ruins/assets/", "644"], 76 | ] 77 | 78 | [package.metadata.vcpkg] 79 | dependencies = ["sdl2", "sdl2-image[libjpeg-turbo,tiff,libwebp]", "sdl2-ttf", "sdl2-mixer[opusfile]"] 80 | git = "https://github.com/microsoft/vcpkg" 81 | rev = "9ff4659a075d5f4f30aaca9f7c4e7f059ecc1d6d" 82 | 83 | [package.metadata.vcpkg.target] 84 | x86_64-pc-windows-msvc = { triplet = "x64-windows-static-md" } -------------------------------------------------------------------------------- /rusted-ruins/src/config/args.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use clap::Parser; 3 | 4 | #[derive(Parser, Debug)] 5 | #[clap(author, version, about)] 6 | struct Args { 7 | /// Language 8 | #[clap(long)] 9 | lang: Option, 10 | /// Second language 11 | #[clap(long)] 12 | second_lang: Option, 13 | /// Use fixed random seed 14 | #[clap(long)] 15 | fix_rand: bool, 16 | } 17 | 18 | pub fn modify_config_by_args(mut config: Config) -> Config { 19 | let args = Args::parse(); 20 | 21 | config.fix_rand = args.fix_rand; 22 | 23 | if let Some(lang) = args.lang { 24 | config.lang = lang; 25 | } 26 | if let Some(second_lang) = args.second_lang { 27 | config.second_lang = second_lang; 28 | } 29 | 30 | config 31 | } 32 | -------------------------------------------------------------------------------- /rusted-ruins/src/config/changeable.rs: -------------------------------------------------------------------------------- 1 | use super::ASSETS_DIR; 2 | use once_cell::sync::Lazy; 3 | use std::fs::read_to_string; 4 | use std::process::exit; 5 | use std::sync::{RwLock, RwLockReadGuard}; 6 | 7 | #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] 8 | pub struct ChangeableConfig { 9 | pub game_log: GameLogConfig, 10 | } 11 | 12 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)] 13 | pub struct GameLogConfig { 14 | pub combat_log: CombatLog, 15 | } 16 | 17 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)] 18 | #[serde(rename_all = "snake_case")] 19 | pub enum CombatLog { 20 | None = 0, 21 | Minimum, 22 | Detail, 23 | } 24 | 25 | impl CombatLog { 26 | pub fn damage(&self) -> bool { 27 | *self == CombatLog::Detail 28 | } 29 | 30 | pub fn attack(&self) -> bool { 31 | *self == CombatLog::Detail 32 | } 33 | } 34 | 35 | static CHANGEABLE_CFG: Lazy> = 36 | Lazy::new(|| RwLock::new(load_changeable_cfg())); 37 | 38 | pub fn initialize() { 39 | Lazy::force(&CHANGEABLE_CFG); 40 | } 41 | 42 | pub fn read() -> RwLockReadGuard<'static, ChangeableConfig> { 43 | CHANGEABLE_CFG.read().expect("config read") 44 | } 45 | 46 | pub fn game_log_cfg() -> GameLogConfig { 47 | read().game_log 48 | } 49 | 50 | fn load_changeable_cfg() -> ChangeableConfig { 51 | let mut path = ASSETS_DIR.clone(); 52 | path.push(common::basic::CFG_FILES_DIR); 53 | path.push("changeable.default.toml"); 54 | let s = match read_to_string(&path) { 55 | Ok(s) => s, 56 | Err(e) => { 57 | error!( 58 | "Cannot load config file \"{}\"\n{}", 59 | path.to_string_lossy(), 60 | e 61 | ); 62 | exit(1); 63 | } 64 | }; 65 | 66 | match toml::de::from_str(&s) { 67 | Ok(config) => config, 68 | Err(e) => { 69 | error!( 70 | "Cannot load config file \"{}\"\n{}", 71 | path.to_string_lossy(), 72 | e 73 | ); 74 | exit(1); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /rusted-ruins/src/config/control.rs: -------------------------------------------------------------------------------- 1 | /// Misc control config 2 | #[derive(Debug, Deserialize)] 3 | pub struct ControlConfig { 4 | pub menu_centering: bool, 5 | } 6 | -------------------------------------------------------------------------------- /rusted-ruins/src/config/font.rs: -------------------------------------------------------------------------------- 1 | use super::CONFIG; 2 | use std::collections::HashMap; 3 | 4 | /// Font name for each language 5 | #[derive(Debug, Deserialize)] 6 | pub struct FontConfig { 7 | pub font_names: HashMap, 8 | pub mono_font: String, 9 | } 10 | 11 | impl FontConfig { 12 | /// Get font_name by the first language 13 | pub fn font_name(&self) -> &str { 14 | self.font_name_by_lang(&CONFIG.lang) 15 | } 16 | 17 | fn font_name_by_lang(&self, lang: &str) -> &str { 18 | if let Some(f) = self.font_names.get(lang) { 19 | f 20 | } else { 21 | warn!("Font for language \"{}\" is not set in the config", lang); 22 | if let Some(f) = self.font_names.get("en") { 23 | warn!("Use default font \"{}\"", f); 24 | f 25 | } else { 26 | error!("Cannot find defalut font"); 27 | panic!(); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /rusted-ruins/src/config/input.rs: -------------------------------------------------------------------------------- 1 | use crate::game::Command; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Deserialize, Debug)] 5 | pub struct InputConfig { 6 | pub wasd_mode: bool, 7 | pub normal: HashMap, 8 | pub dialog: HashMap, 9 | } 10 | 11 | impl InputConfig { 12 | pub fn find_key(&self, command: &Command) -> String { 13 | let mut s = String::new(); 14 | 15 | for (k, c) in &self.normal { 16 | if *c != *command { 17 | continue; 18 | } 19 | 20 | if !s.is_empty() { 21 | s.push(','); 22 | } 23 | s.push_str(k); 24 | } 25 | s 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /rusted-ruins/src/context/sdlvalues.rs: -------------------------------------------------------------------------------- 1 | use super::textcachepool::TextCache; 2 | use super::textcachepool::TextCachePool; 3 | use super::textrenderer::TextRenderer; 4 | use super::texture::TextureHolder; 5 | use crate::SdlContext; 6 | use common::gobj; 7 | use sdl2::render::{Texture, TextureCreator}; 8 | use sdl2::video::WindowContext; 9 | 10 | /// Includes data that isn't used by Game 11 | /// Used for rendering, or music/sound playing 12 | pub struct SdlValues<'sdl, 't> { 13 | pub tc: &'t TextureCreator, 14 | pub texture_holder: TextureHolder<'t>, 15 | pub text_renderer: TextRenderer<'sdl>, 16 | pub tcp: TextCachePool<'t>, 17 | } 18 | 19 | impl<'sdl, 't> SdlValues<'sdl, 't> { 20 | pub fn new(sdl_context: &'sdl SdlContext, tc: &'t TextureCreator) -> Self { 21 | SdlValues { 22 | tc, 23 | texture_holder: TextureHolder::new(gobj::get_objholder(), tc), 24 | text_renderer: TextRenderer::new(sdl_context), 25 | tcp: TextCachePool::new(), 26 | } 27 | } 28 | 29 | pub fn tex(&self) -> &TextureHolder<'_> { 30 | &self.texture_holder 31 | } 32 | 33 | pub fn tt_group(&mut self, c: &mut TextCache) -> &[Texture<'_>] { 34 | self.tcp.group(c, &self.text_renderer, self.tc) 35 | } 36 | 37 | pub fn tt_one(&mut self, c: &mut TextCache) -> &Texture<'_> { 38 | &self.tcp.group(c, &self.text_renderer, self.tc)[0] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /rusted-ruins/src/cursor.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use anyhow::{anyhow, Error}; 4 | use common::hashmap::HashMap; 5 | use common::{gobj, obj::UiImgObject}; 6 | use sdl2::image::ImageRWops; 7 | use sdl2::mouse::Cursor as SdlCursor; 8 | use sdl2::rwops::RWops; 9 | 10 | thread_local!( 11 | static CURSORS: RefCell>> = RefCell::new(None); 12 | ); 13 | 14 | pub fn load() { 15 | let mut cursors = HashMap::default(); 16 | 17 | for cursor in Cursor::ALL { 18 | match cursor.load() { 19 | Ok(loaded_cursor) => { 20 | cursors.insert(*cursor, loaded_cursor); 21 | } 22 | Err(e) => { 23 | warn!("Loading cursor \"{}\" failed\n{}", cursor.img_id(), e); 24 | } 25 | } 26 | } 27 | 28 | CURSORS.with(|c| { 29 | *c.borrow_mut() = Some(cursors); 30 | }); 31 | 32 | set(Cursor::Normal); 33 | } 34 | 35 | pub fn set(cursor: Cursor) { 36 | CURSORS.with(|c| { 37 | if let Some(c) = c.borrow().as_ref().and_then(|c| c.get(&cursor)) { 38 | c.set(); 39 | } 40 | }); 41 | } 42 | 43 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] 44 | pub enum Cursor { 45 | Normal, 46 | } 47 | 48 | impl Cursor { 49 | const ALL: &'static [Cursor] = &[Cursor::Normal]; 50 | 51 | fn img_id(&self) -> &'static str { 52 | match self { 53 | Cursor::Normal => "!cursor-normal", 54 | } 55 | } 56 | 57 | fn load(&self) -> Result { 58 | let to_err = |s: String| anyhow!("{}", s); 59 | 60 | let obj: &UiImgObject = 61 | gobj::get_by_id_checked(self.img_id()).ok_or_else(|| anyhow!("object not found"))?; 62 | 63 | let rwops = RWops::from_bytes(&obj.img.data).map_err(to_err)?; 64 | let surface = rwops.load().map_err(to_err)?; 65 | 66 | SdlCursor::from_surface(surface, obj.hot.0.into(), obj.hot.1.into()).map_err(to_err) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /rusted-ruins/src/damage_popup.rs: -------------------------------------------------------------------------------- 1 | use crate::config::UI_CFG; 2 | use common::gamedata::*; 3 | use geom::Coords; 4 | use once_cell::sync::Lazy; 5 | use std::collections::VecDeque; 6 | use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; 7 | 8 | static DAMAGE_POPUP: Lazy> = 9 | Lazy::new(|| RwLock::new(DamagePopupList::new())); 10 | 11 | pub struct DamagePopupList { 12 | pub popup_list: Vec, 13 | } 14 | 15 | pub enum PopupKind { 16 | Damage(i32), 17 | Heal(i32), 18 | Miss, 19 | } 20 | 21 | pub struct DamagePopup { 22 | pub cid: CharaId, 23 | pub pos: Coords, 24 | /// (passed_frame, popup) 25 | pub queue: VecDeque<(u32, PopupKind)>, 26 | } 27 | 28 | pub fn get() -> RwLockReadGuard<'static, DamagePopupList> { 29 | DAMAGE_POPUP.try_read().expect("failed lock COMBAT_LOG") 30 | } 31 | 32 | fn get_mut() -> RwLockWriteGuard<'static, DamagePopupList> { 33 | DAMAGE_POPUP.try_write().expect("failed lock COMBAT_LOG") 34 | } 35 | 36 | pub fn push(cid: CharaId, pos: Coords, popup: PopupKind) { 37 | get_mut().push(cid, pos, popup); 38 | } 39 | 40 | pub fn advance_frame() { 41 | get_mut().advance(); 42 | } 43 | 44 | impl DamagePopupList { 45 | pub fn new() -> DamagePopupList { 46 | DamagePopupList { 47 | popup_list: Vec::new(), 48 | } 49 | } 50 | 51 | pub fn push(&mut self, cid: CharaId, pos: Coords, popup_kind: PopupKind) { 52 | if let Some(popup) = self 53 | .popup_list 54 | .iter_mut() 55 | .find(|damaged_chara| damaged_chara.cid == cid) 56 | { 57 | popup.pos = pos; 58 | popup.queue.push_front((0, popup_kind)); 59 | } else { 60 | let mut queue = VecDeque::new(); 61 | queue.push_front((0, popup_kind)); 62 | 63 | self.popup_list.push(DamagePopup { cid, pos, queue }); 64 | } 65 | } 66 | 67 | fn advance(&mut self) { 68 | for popup in &mut self.popup_list { 69 | for (passed_frame, _) in &mut popup.queue { 70 | *passed_frame += 1; 71 | } 72 | popup 73 | .queue 74 | .retain(|(passed_frame, _)| *passed_frame < UI_CFG.damage_popup.n_frame); 75 | } 76 | 77 | self.popup_list.retain(|popup| !popup.queue.is_empty()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /rusted-ruins/src/draw/border.rs: -------------------------------------------------------------------------------- 1 | use crate::config::UI_CFG; 2 | use crate::context::Context; 3 | use sdl2::pixels::Color; 4 | use sdl2::rect::Rect; 5 | 6 | pub fn draw_window_border>(context: &mut Context<'_, '_, '_, '_>, rect: R) { 7 | let rect = rect.into(); 8 | let light_color = UI_CFG.color.border_light; 9 | let dark_color = UI_CFG.color.border_dark; 10 | 11 | let canvas = &mut context.canvas; 12 | canvas.set_viewport(None); 13 | for n in 1..4 { 14 | let r = Rect::new( 15 | rect.x - n, 16 | rect.y - n, 17 | (rect.w + 2 * n) as u32, 18 | (rect.h + 2 * n) as u32, 19 | ); 20 | let c: Color = if n == 2 { 21 | light_color.into() 22 | } else { 23 | dark_color.into() 24 | }; 25 | 26 | canvas.set_draw_color(c); 27 | try_sdl!(canvas.draw_rect(r)); 28 | } 29 | 30 | canvas.set_draw_color(UI_CFG.color.window_bg); 31 | try_sdl!(canvas.fill_rect(rect)); 32 | canvas.set_viewport(rect); 33 | } 34 | -------------------------------------------------------------------------------- /rusted-ruins/src/draw/chara_info.rs: -------------------------------------------------------------------------------- 1 | use super::mainwin::MainWinDrawer; 2 | use crate::config::UI_CFG; 3 | use crate::context::*; 4 | use crate::game::Game; 5 | use common::gobj; 6 | use common::objholder::UiImgIdx; 7 | use once_cell::sync::Lazy; 8 | use sdl2::rect::Rect; 9 | 10 | struct DrawInfo { 11 | target_icon_idx: UiImgIdx, 12 | target_icon_x: i32, 13 | target_icon_y: i32, 14 | target_icon_w: u32, 15 | target_icon_h: u32, 16 | } 17 | 18 | impl DrawInfo { 19 | fn init() -> DrawInfo { 20 | let target_icon_idx: UiImgIdx = gobj::id_to_idx("!target-icon"); 21 | let obj = gobj::get_obj(target_icon_idx); 22 | DrawInfo { 23 | target_icon_idx, 24 | target_icon_x: UI_CFG.chara_info.target_icon_x, 25 | target_icon_y: UI_CFG.chara_info.target_icon_y, 26 | target_icon_w: obj.img.w, 27 | target_icon_h: obj.img.h, 28 | } 29 | } 30 | } 31 | 32 | static DRAW_INFO: Lazy = Lazy::new(DrawInfo::init); 33 | 34 | impl MainWinDrawer { 35 | pub fn draw_chara_info(&self, context: &mut Context<'_, '_, '_, '_>, game: &Game) { 36 | let map = game.gd.get_current_map(); 37 | let target_chara = game.target_chara(); 38 | 39 | for p in self.tile_range() { 40 | if !map.is_inside(p) { 41 | continue; 42 | } 43 | let tile_info = &map.tile[p]; 44 | let cid = if let Some(cid) = tile_info.chara.as_ref() { 45 | *cid 46 | } else { 47 | continue; 48 | }; 49 | 50 | if !game.view_map.get_tile_visible(p) { 51 | continue; 52 | } 53 | 54 | let tile_rect = self.tile_rect(p, 0, 0); 55 | // Draw target icon 56 | if target_chara.is_some() && target_chara.unwrap() == cid { 57 | context.render_tex( 58 | DRAW_INFO.target_icon_idx, 59 | Rect::new( 60 | tile_rect.x + DRAW_INFO.target_icon_x, 61 | tile_rect.y + DRAW_INFO.target_icon_y, 62 | DRAW_INFO.target_icon_w, 63 | DRAW_INFO.target_icon_h, 64 | ), 65 | ); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /rusted-ruins/src/draw/frame.rs: -------------------------------------------------------------------------------- 1 | use common::obj::Img; 2 | use std::cell::Cell; 3 | 4 | thread_local!(static FRAME_COUNT: Cell = Cell::new(0)); 5 | 6 | /// Add 1 to frame counter 7 | pub fn next_frame() { 8 | FRAME_COUNT.with(|frame_count| { 9 | let a = frame_count.get(); 10 | frame_count.set(a.wrapping_add(1)); 11 | }); 12 | } 13 | 14 | /// Calculate which animation frame of image will be used 15 | pub fn calc_frame(img: &Img) -> u32 { 16 | if img.n_anim_frame == 1 { 17 | return 0; 18 | } 19 | 20 | let frame_count = FRAME_COUNT.with(|frame_count| frame_count.get()); 21 | let a = frame_count % (img.duration * img.n_anim_frame); 22 | let n = a / img.duration; 23 | debug_assert!(n < img.n_anim_frame); 24 | n 25 | } 26 | -------------------------------------------------------------------------------- /rusted-ruins/src/draw/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod border; 2 | mod chara_info; 3 | mod damage; 4 | pub mod frame; 5 | pub mod mainwin; 6 | mod overlay; 7 | mod target_mode; 8 | mod tile_getter; 9 | -------------------------------------------------------------------------------- /rusted-ruins/src/draw/overlay.rs: -------------------------------------------------------------------------------- 1 | use crate::game::frequent_tex::Overlay; 2 | use crate::game::{Game, InfoGetter}; 3 | use common::objholder::EffectImgIdx; 4 | use common::piece_pattern::*; 5 | use geom::*; 6 | 7 | pub enum FogPattern { 8 | None, 9 | Fog(EffectImgIdx), 10 | PiecePattern(EffectImgIdx, PiecePattern), 11 | } 12 | 13 | pub fn view_fog(game: &Game, p: Coords) -> FogPattern { 14 | let view_map = &game.view_map; 15 | 16 | if view_map.get_tile_visible(p) { 17 | let mut piece_pattern_flags = PiecePatternFlags::default(); 18 | for dir in &Direction::EIGHT_DIRS { 19 | piece_pattern_flags.set(*dir, view_map.get_tile_visible(p + dir.as_coords())); 20 | } 21 | 22 | let pp = piece_pattern_flags.to_piece_pattern(5); 23 | if pp == PiecePattern::SURROUNDED { 24 | FogPattern::None 25 | } else { 26 | FogPattern::PiecePattern(game.frequent_tex.overlay_idx(Overlay::Fog), pp) 27 | } 28 | } else { 29 | FogPattern::Fog(game.frequent_tex.overlay_idx(Overlay::Fog)) 30 | } 31 | } 32 | 33 | pub fn all(game: &Game) -> Option { 34 | // If current map is indoor, don't draw night overlay 35 | if !game.gd.is_open_air(game.gd.get_current_mapid()) { 36 | return None; 37 | } 38 | 39 | let date = game.gd.time.current_date(); 40 | let hour = date.hour; 41 | let minute = date.minute; 42 | let dawn_hour = 5; 43 | let dusk_hour = 18; 44 | assert!(dawn_hour < dusk_hour); 45 | 46 | if dawn_hour < hour && hour < dusk_hour { 47 | // Daytime 48 | None 49 | } else if hour == dawn_hour { 50 | Some(game.frequent_tex.overlay_idx(twilight(minute))) 51 | } else if hour == dusk_hour { 52 | Some(game.frequent_tex.overlay_idx(twilight(60 - minute))) 53 | } else { 54 | // Night 55 | Some(game.frequent_tex.overlay_idx(Overlay::Night)) 56 | } 57 | } 58 | 59 | fn twilight(minute: u16) -> Overlay { 60 | if minute < 20 { 61 | Overlay::Twilight0 62 | } else if minute < 40 { 63 | Overlay::Twilight1 64 | } else { 65 | Overlay::Twilight2 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /rusted-ruins/src/draw/target_mode.rs: -------------------------------------------------------------------------------- 1 | use super::mainwin::{MainWinDrawer, TargetModeDrawInfo}; 2 | use crate::context::*; 3 | use crate::game::Game; 4 | use common::gobj; 5 | use common::objholder::UiImgIdx; 6 | use geom::*; 7 | use once_cell::sync::Lazy; 8 | 9 | static TILE_RANGE_HIGHLIBHT: Lazy = 10 | Lazy::new(|| gobj::id_to_idx("!tile-range-highlight")); 11 | 12 | impl MainWinDrawer { 13 | pub fn draw_target_mode( 14 | &self, 15 | context: &mut Context<'_, '_, '_, '_>, 16 | game: &Game, 17 | target_mode: &TargetModeDrawInfo, 18 | ) { 19 | let map = game.gd.get_current_map(); 20 | 21 | for p in self.tile_range() { 22 | if !map.is_inside(p) { 23 | continue; 24 | } 25 | self.draw_target_mode_to_tile(context, p, game, target_mode); 26 | } 27 | } 28 | 29 | fn draw_target_mode_to_tile( 30 | &self, 31 | context: &mut Context<'_, '_, '_, '_>, 32 | pos: Coords, 33 | game: &Game, 34 | target_mode: &TargetModeDrawInfo, 35 | ) { 36 | if target_mode.range.is_inside(pos) { 37 | if !game.view_map.get_tile_visible(pos) { 38 | return; 39 | } 40 | let tile_rect = self.tile_rect(pos, 0, 0); 41 | context.render_tex(*TILE_RANGE_HIGHLIBHT, tile_rect); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /rusted-ruins/src/error.rs: -------------------------------------------------------------------------------- 1 | macro_rules! try_sdl { 2 | ($rst:expr) => { 3 | match $rst { 4 | Ok(_) => (), 5 | Err(e) => error!("SDL error : {}", e), 6 | } 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/action/ability.rs: -------------------------------------------------------------------------------- 1 | use super::Game; 2 | use crate::game::effect::do_effect; 3 | use crate::game::power::{calc_hit, calc_power}; 4 | use crate::game::{extrait::*, Target}; 5 | use crate::text::ToText; 6 | use common::gamedata::*; 7 | use rules::RULES; 8 | 9 | /// Return true if success. 10 | pub fn use_ability>( 11 | game: &mut Game, 12 | ability_id: &AbilityId, 13 | cid: CharaId, 14 | target: T, 15 | ) -> bool { 16 | let ability = if let Some(ability) = RULES.abilities.get(ability_id) { 17 | ability 18 | } else { 19 | warn!("unknown ability \"{}\"", ability_id); 20 | return false; 21 | }; 22 | 23 | if !super::ability::usable(&game.gd, cid, ability_id, false) { 24 | return false; 25 | } 26 | 27 | let chara = game.gd.chara.get(cid); 28 | if !chara.ability_available(ability) { 29 | return false; 30 | } 31 | 32 | let power = calc_power(&game.gd, cid, &ability.power_calc); 33 | let hit = calc_hit(&game.gd, cid, &ability.power_calc); 34 | 35 | let chara = game.gd.chara.get(cid); 36 | trace!( 37 | "{} uses active skill \"{}\", power = {}", 38 | chara.to_text(), 39 | ability_id, 40 | power, 41 | ); 42 | 43 | let use_ability_text_id = format!("use-ability-{}", ability.category); 44 | game_log!(&use_ability_text_id; chara=chara, ability=ability_id); 45 | 46 | do_effect(game, &ability.effect, Some(cid), target, power, hit); 47 | 48 | let chara = game.gd.chara.get_mut(cid); 49 | chara.sp -= ability.cost_sp as f32; 50 | chara.mp -= ability.cost_mp as i32; 51 | 52 | true 53 | } 54 | 55 | pub fn usable(gd: &GameData, cid: CharaId, ability_id: &AbilityId, print_log: bool) -> bool { 56 | if RULES.abilities.get(ability_id).is_none() { 57 | warn!("unknown ability \"{}\"", ability_id); 58 | return false; 59 | }; 60 | let chara = gd.chara.get(cid); 61 | let cost = cost(gd, cid, ability_id); 62 | 63 | if chara.sp < cost.sp { 64 | if print_log { 65 | game_log!("ability-not-enough-sp"; chara=chara); 66 | } 67 | return false; 68 | } 69 | 70 | if chara.mp < cost.mp { 71 | if print_log { 72 | game_log!("ability-not-enough-mp"; chara=chara); 73 | } 74 | return false; 75 | } 76 | true 77 | } 78 | 79 | #[derive(Clone, Copy, Default, Debug)] 80 | pub struct AbilityCost { 81 | pub sp: f32, 82 | pub mp: i32, 83 | } 84 | 85 | pub fn cost(_gd: &GameData, _cid: CharaId, ability_id: &AbilityId) -> AbilityCost { 86 | let ability = if let Some(ability) = RULES.abilities.get(ability_id) { 87 | ability 88 | } else { 89 | warn!("unknown ability \"{}\"", ability_id); 90 | return AbilityCost::default(); 91 | }; 92 | AbilityCost { 93 | sp: ability.cost_sp as f32, 94 | mp: ability.cost_mp as i32, 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/action/get_item.rs: -------------------------------------------------------------------------------- 1 | use crate::game::extrait::*; 2 | use common::gamedata::*; 3 | 4 | pub fn get_item>( 5 | gd: &mut GameData, 6 | item_location: ItemLocation, 7 | cid: CharaId, 8 | n: T, 9 | ) { 10 | let item = gd.get_item(item_location); 11 | let obj = item.0.obj(); 12 | let src_list = gd.get_item_list_mut(item_location.0); 13 | let n = match n.into() { 14 | ItemMoveNum::Partial(n) => n, 15 | ItemMoveNum::All => src_list.get_number(item_location.1), 16 | }; 17 | 18 | // If item is silver and dest is player, increases player money. 19 | if obj.id == "!silver" { 20 | gd.remove_item(item_location, n); 21 | if cid == CharaId::Player { 22 | gd.player.add_money(n.into()); 23 | } 24 | return; 25 | } 26 | 27 | let dest = ItemListLocation::Chara { cid }; 28 | gd.move_item(item_location, dest, n); 29 | gd.chara.get_mut(cid).update(); 30 | } 31 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/anim_queue.rs: -------------------------------------------------------------------------------- 1 | use super::animation::*; 2 | use geom::*; 3 | use std::collections::VecDeque; 4 | 5 | /// Wraps Animation queue, and provides helper functions to push Animations 6 | #[derive(Default)] 7 | pub struct AnimQueue(VecDeque); 8 | 9 | impl AnimQueue { 10 | pub fn is_empty(&self) -> bool { 11 | self.0.is_empty() 12 | } 13 | 14 | pub fn pop(&mut self) -> Option { 15 | self.0.pop_front() 16 | } 17 | 18 | pub fn push(&mut self, animation: Animation) { 19 | self.0.push_back(animation); 20 | } 21 | 22 | pub fn push_player_move(&mut self, dir: Direction) { 23 | self.push(Animation::player_move(dir)); 24 | } 25 | 26 | pub fn push_destroy(&mut self, tile: Coords) { 27 | self.push(Animation::destroy(vec![tile])); 28 | } 29 | 30 | pub fn push_work(&mut self, ratio: f32) { 31 | self.push(Animation::Work { n_frame: 2, ratio }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/effect/misc.rs: -------------------------------------------------------------------------------- 1 | use crate::game::extrait::*; 2 | use crate::game::Game; 3 | use common::gamedata::*; 4 | use common::gobj; 5 | use common::objholder::ItemIdx; 6 | use geom::*; 7 | 8 | pub fn gen_item(game: &mut Game, id: &str, pos: Coords) { 9 | let idx: ItemIdx = if let Some(idx) = gobj::id_to_idx_checked(id) { 10 | idx 11 | } else { 12 | warn!("unknown object id \"{}\"", id); 13 | return; 14 | }; 15 | 16 | let item_obj = gobj::get_obj(idx); 17 | 18 | if let Some(&ItemObjAttr::Plant { 19 | required_fertility, .. 20 | }) = find_attr!(item_obj, ItemObjAttr::Plant) 21 | { 22 | if required_fertility > game.gd.get_current_map().tile_fertility(pos) { 23 | return; 24 | } 25 | } 26 | 27 | let item = crate::game::item::gen::gen_item_from_idx(idx, 1); 28 | game.gd.add_item_on_tile(pos, item, 1); 29 | } 30 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/effect/range.rs: -------------------------------------------------------------------------------- 1 | use common::gamedata::*; 2 | use geom::*; 3 | 4 | /// Get range of the given effect 5 | pub fn effect_to_range(effect: &Effect, center: Coords) -> Shape { 6 | Shape::Circle { 7 | center, 8 | radius: effect.range, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/effect/restore.rs: -------------------------------------------------------------------------------- 1 | use crate::game::extrait::CharaExt; 2 | use crate::game::{Game, InfoGetter}; 3 | use common::gamedata::*; 4 | use rules::RULES; 5 | 6 | pub fn restore_hp(game: &mut Game, cid: CharaId, power: f32) { 7 | let value = (RULES.effect.restore_hp_factor * power) as i32; 8 | let pos = game.gd.chara_pos(cid).unwrap(); 9 | let chara = game.gd.chara.get_mut(cid); 10 | chara.heal(value); 11 | crate::damage_popup::push(cid, pos, crate::damage_popup::PopupKind::Heal(value)); 12 | } 13 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/effect/skill_learn.rs: -------------------------------------------------------------------------------- 1 | use crate::game::extrait::*; 2 | use common::gamedata::*; 3 | 4 | pub fn skill_learn(gd: &mut GameData, cid: CharaId, skills: &[SkillKind]) { 5 | for skill_kind in skills { 6 | let skill_kind = *skill_kind; 7 | let chara = gd.chara.get_mut(cid); 8 | if chara.skills.learn_new_skill(skill_kind) { 9 | game_log!("skill-learned"; chara=chara, skill=skill_kind); 10 | } else { 11 | game_log!("skill-already-learned"; chara=chara, skill=skill_kind); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/faction.rs: -------------------------------------------------------------------------------- 1 | use common::gamedata::*; 2 | use rules::RULES; 3 | 4 | #[extend::ext(pub)] 5 | impl Faction { 6 | fn change(&mut self, faction: FactionId, value: i16) { 7 | if let Some(true) = RULES 8 | .faction 9 | .factions 10 | .get(&faction) 11 | .map(|faction| faction.constant) 12 | { 13 | return; 14 | } 15 | 16 | let current_value = self.get(faction); 17 | self.set(faction, current_value + value); 18 | 19 | if value > 0 { 20 | game_log!("faction-relation-improve"; faction=faction, value=value); 21 | } else if value < 0 { 22 | let value = -value; 23 | game_log!("faction-relation-lower"; faction=faction, value=value); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/frequent_tex.rs: -------------------------------------------------------------------------------- 1 | use common::gobj; 2 | use common::objholder::*; 3 | 4 | /// Holds frequent used texture's ids 5 | pub struct FrequentTextures { 6 | effect_idx: Vec, 7 | } 8 | 9 | impl FrequentTextures { 10 | pub fn new() -> FrequentTextures { 11 | // Set Effect Object indices 12 | let effect_idx = vec![ 13 | gobj::id_to_idx("overlay-fog"), // Fog 14 | gobj::id_to_idx("overlay-fog-dark"), // Fog (dark) 15 | gobj::id_to_idx("overlay-night"), // Night 16 | gobj::id_to_idx("overlay-twilight0"), // Twilight (0 is darkest) 17 | gobj::id_to_idx("overlay-twilight1"), 18 | gobj::id_to_idx("overlay-twilight2"), 19 | ]; 20 | 21 | FrequentTextures { effect_idx } 22 | } 23 | 24 | pub fn overlay_idx(&self, o: Overlay) -> EffectImgIdx { 25 | self.effect_idx[o as usize] 26 | } 27 | } 28 | 29 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 30 | pub enum Overlay { 31 | Fog = 0, 32 | _FogDark, 33 | Night, 34 | Twilight0, // Darkest 35 | Twilight1, 36 | Twilight2, 37 | } 38 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/item/merged.rs: -------------------------------------------------------------------------------- 1 | use common::gamedata::*; 2 | 3 | #[derive(Clone)] 4 | /// ItemList with the other list. 5 | pub struct MergedItemList<'a> { 6 | pub first: (ItemListLocation, &'a ItemList), 7 | pub second: Option<(ItemListLocation, &'a ItemList)>, 8 | dummy_list: Vec<(Item, u32)>, 9 | } 10 | 11 | pub trait MergedItemListGet { 12 | fn get_merged_item_list( 13 | &self, 14 | first: ItemListLocation, 15 | second: Option, 16 | ) -> MergedItemList<'_>; 17 | } 18 | 19 | impl MergedItemListGet for GameData { 20 | fn get_merged_item_list( 21 | &self, 22 | first: ItemListLocation, 23 | second: Option, 24 | ) -> MergedItemList<'_> { 25 | assert_ne!(Some(first), second); 26 | 27 | MergedItemList { 28 | first: (first, self.get_item_list(first)), 29 | second: second.map(|il| (il, self.get_item_list(il))), 30 | dummy_list: vec![], 31 | } 32 | } 33 | } 34 | 35 | impl<'a> MergedItemList<'a> { 36 | #[allow(unused)] 37 | pub fn iter(&self) -> impl Iterator { 38 | let first = self.first.1.iter(); 39 | if let Some(second) = self.second.as_ref() { 40 | first.chain(second.1.iter()) 41 | } else { 42 | first.chain(self.dummy_list.iter()) 43 | } 44 | } 45 | 46 | pub fn item_location(&self, i: usize) -> ItemLocation { 47 | if let Some(second) = self.second.as_ref() { 48 | let first_len = self.first.1.len(); 49 | if i < first_len { 50 | (self.first.0, i as u32) 51 | } else { 52 | (second.0, (i - first_len) as u32) 53 | } 54 | } else { 55 | (self.first.0, i as u32) 56 | } 57 | } 58 | 59 | pub fn len(&self) -> usize { 60 | self.first.1.len() + self.second.map_or(0, |second| second.1.len()) 61 | } 62 | 63 | pub fn get(&self, i: usize) -> &'a (Item, u32) { 64 | if let Some(second) = self.second.as_ref() { 65 | let first_len = self.first.1.len(); 66 | if i < first_len { 67 | &self.first.1.items[i] 68 | } else { 69 | &second.1.items[i - first_len] 70 | } 71 | } else { 72 | &self.first.1.items[i] 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/item/throw.rs: -------------------------------------------------------------------------------- 1 | use crate::game::extrait::*; 2 | use common::gamedata::*; 3 | 4 | pub fn item_to_throw_effect(gd: &GameData, il: ItemLocation, cid: CharaId) -> Effect { 5 | let item = gd.get_item(il).0; 6 | let item_obj = item.obj(); 7 | let range = item.throw_range(gd.chara.get(cid).attr.str); 8 | let mut effect = if let Some(effect) = item_obj 9 | .attrs 10 | .iter() 11 | .filter_map(|attr| match attr { 12 | ItemObjAttr::Throw { effect, .. } => Some(effect), 13 | _ => None, 14 | }) 15 | .next() 16 | { 17 | effect.clone() 18 | } else { 19 | Effect { 20 | kind: vec![EffectKind::Ranged { 21 | element: Element::Physical, 22 | }], 23 | anim_kind: EffectAnimKind::Tile, 24 | anim_img: "!damage-blunt".into(), 25 | ..Effect::default() 26 | } 27 | }; 28 | effect.range = range; 29 | effect 30 | } 31 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/map/tile_info.rs: -------------------------------------------------------------------------------- 1 | use crate::game::Game; 2 | use common::gamedata::*; 3 | use geom::{Coords, Direction}; 4 | 5 | pub fn print_tile_info(_game: &Game, _pos: Coords) { 6 | game_log!("tile-information-no-info"); 7 | } 8 | 9 | #[derive(Clone, PartialEq, Eq, Debug)] 10 | pub struct TileInfoQuery { 11 | pub move_symbol: Option, 12 | pub boundary: Option<(Direction, Option)>, 13 | pub chara: Option, 14 | } 15 | 16 | pub fn tile_info_query(gd: &GameData, pos: Coords) -> TileInfoQuery { 17 | let map = gd.get_current_map(); 18 | 19 | let tinfo = &map.observed_tile[pos]; 20 | 21 | let move_symbol = match tinfo.special { 22 | SpecialTileKind::Stairs { .. } | SpecialTileKind::SiteSymbol { .. } => Some(tinfo.special), 23 | _ => None, 24 | }; 25 | 26 | let boundary = if pos.0 == 0 { 27 | Some((Direction::W, map.boundary.w)) 28 | } else if pos.0 == (map.w - 1) as i32 { 29 | Some((Direction::E, map.boundary.e)) 30 | } else if pos.1 == 0 { 31 | Some((Direction::N, map.boundary.n)) 32 | } else if pos.1 == (map.h - 1) as i32 { 33 | Some((Direction::S, map.boundary.s)) 34 | } else { 35 | None 36 | }; 37 | 38 | let chara = map.get_chara(pos); 39 | 40 | TileInfoQuery { 41 | move_symbol, 42 | boundary, 43 | chara, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/map/update.rs: -------------------------------------------------------------------------------- 1 | use crate::game::Game; 2 | use common::gamedata::*; 3 | use rules::RULES; 4 | 5 | /// Map update after map switching. 6 | pub fn update_map(game: &mut Game) { 7 | crate::game::item::time::update_item_time(&mut game.gd); 8 | 9 | let map = game.gd.get_current_map_mut(); 10 | let current_time = crate::game::time::current_time(); 11 | let duration = current_time.duration_from(map.last_visit); 12 | map.last_visit = current_time; 13 | 14 | if duration > Duration::from_minutes(RULES.npc.map_switch_recover_minutes.into()) { 15 | recover_npc(&mut game.gd); 16 | } 17 | } 18 | 19 | pub fn recover_npc(gd: &mut GameData) { 20 | for cid in gd.get_charas_on_map().into_iter() { 21 | let chara = gd.chara.get_mut(cid); 22 | 23 | chara.ai.state = AiState::Normal; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/map/wall_damage.rs: -------------------------------------------------------------------------------- 1 | use crate::game::extrait::*; 2 | use crate::game::Game; 3 | use common::gobj; 4 | use common::objholder::ItemIdx; 5 | use geom::*; 6 | 7 | pub fn wall_damage(game: &mut Game, pos: Coords, power: f32) { 8 | let map = game.gd.get_current_map_mut(); 9 | let tile = &mut map.tile[pos]; 10 | 11 | if tile.wall.is_empty() { 12 | return; 13 | } 14 | 15 | let wall_hp = tile.wall_hp; 16 | 17 | if wall_hp == std::u16::MAX { 18 | return; 19 | } 20 | 21 | let damage = power as u16; 22 | 23 | if wall_hp <= damage { 24 | let wall_obj = gobj::get_obj(tile.wall.idx().unwrap()); 25 | map.erase_wall(pos); 26 | for (mining_reward, n) in &wall_obj.mining_rewards { 27 | if let Some(item_idx) = gobj::id_to_idx_checked::(mining_reward) { 28 | let item = crate::game::item::gen::gen_item_from_idx(item_idx, 1); 29 | game.gd.add_item_on_tile(pos, item, *n); 30 | } else { 31 | warn!("unknown item id for mining reward: \"{}\"", mining_reward); 32 | } 33 | } 34 | } else { 35 | tile.wall_hp = wall_hp - damage; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/npc/map_search.rs: -------------------------------------------------------------------------------- 1 | //! Functions to search map information needed to determine NPC's behavior. 2 | 3 | /* 4 | use crate::game::InfoGetter; 5 | use common::gamedata::*; 6 | 7 | /// Search nearest other character from cid. 8 | /// If f() returns false, skip the character. 9 | /// If no character found, returns None. 10 | pub fn search_nearest_chara(gd: &GameData, cid: CharaId, mut f: F) -> Option 11 | where 12 | F: FnMut(&GameData, CharaId, CharaId) -> bool, 13 | { 14 | let map = gd.get_current_map(); 15 | let center = map.chara_pos(cid)?; 16 | 17 | let mut result_cid = None; 18 | let mut distance = i32::max_value(); 19 | 20 | for p in map.tile.iter_idx() { 21 | if let Some(tile_cid) = map.tile[p].chara { 22 | if tile_cid != cid && f(gd, cid, tile_cid) { 23 | let d = center.mdistance(p); 24 | if d < distance { 25 | distance = d; 26 | result_cid = Some(tile_cid); 27 | } 28 | } 29 | } 30 | } 31 | 32 | result_cid 33 | } 34 | 35 | /// Search the nearest hostile character 36 | pub fn search_nearest_enemy(gd: &GameData, cid: CharaId) -> Option { 37 | search_nearest_chara(gd, cid, |gd, c0, c1| { 38 | gd.chara_relation(c0, c1) == Relationship::Hostile 39 | }) 40 | } 41 | */ 42 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/party.rs: -------------------------------------------------------------------------------- 1 | use super::extrait::*; 2 | use super::play_time::UniqueIdGeneratorByTime; 3 | use common::gamedata::*; 4 | use common::gobj; 5 | use common::objholder::CharaTemplateIdx; 6 | use rules::RULES; 7 | 8 | #[extend::ext(pub, name = GameDataPartyExt)] 9 | impl GameData { 10 | /// The maximum party size 11 | fn available_party_size(&self) -> u32 { 12 | let player = self.chara.get(CharaId::Player); 13 | let skill_level = player.skill_level(SkillKind::Leadership); 14 | let cha = player.attr.cha as u32; 15 | (cha / 10 + skill_level / 10 + 1).clamp(1, RULES.npc.party_size_max) 16 | } 17 | 18 | fn has_empty_for_party(&self) -> bool { 19 | (self.player.party.len() + self.player.party_dead.len() + 1) 20 | < self.available_party_size() as usize 21 | } 22 | 23 | fn add_chara_to_party(&mut self, mut chara: Chara) -> bool { 24 | if !self.has_empty_for_party() { 25 | return false; 26 | } 27 | 28 | let cid = CharaId::Ally { 29 | id: UniqueIdGeneratorByTime.generate(), 30 | }; 31 | chara.faction = FactionId::player(); 32 | 33 | if !self.player.party.insert(cid) { 34 | return false; 35 | } 36 | 37 | game_log!("party-add-chara"; chara=chara); 38 | self.add_chara(cid, chara); 39 | 40 | let player_pos = self.player_pos(); 41 | let map = self.get_current_map_mut(); 42 | if let Some(pos) = map.empty_tile_around(player_pos) { 43 | map.locate_chara(cid, pos); 44 | } 45 | trace!("added new chara to the player's party"); 46 | 47 | true 48 | } 49 | 50 | fn add_cid_to_party(&mut self, cid: CharaId) { 51 | if !self.has_empty_for_party() { 52 | return; 53 | } 54 | 55 | match cid { 56 | CharaId::Player => unreachable!(), 57 | CharaId::Ally { .. } | CharaId::Unique { .. } => { 58 | self.player.party.insert(cid); 59 | let player_pos = self.player_pos(); 60 | let map = self.get_current_map_mut(); 61 | if let Some(pos) = map.empty_tile_around(player_pos) { 62 | map.locate_chara(cid, pos); 63 | } 64 | } 65 | _ => todo!(), 66 | } 67 | } 68 | 69 | fn gen_party_chara(&mut self, id: &str, lv: u32) -> bool { 70 | trace!("generating party chara \"{}\" lv.{}", id, lv); 71 | let idx: CharaTemplateIdx = gobj::id_to_idx(id); 72 | let chara = crate::game::chara::gen::create_chara(idx, lv, FactionId::player(), None); 73 | self.add_chara_to_party(chara) 74 | } 75 | } 76 | 77 | pub fn resurrect_party_members(gd: &mut GameData) { 78 | let cids = std::mem::take(&mut gd.player.party_dead); 79 | 80 | for cid in cids { 81 | gd.chara.get_mut(cid).resurrect(); 82 | gd.add_cid_to_party(cid); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/play_time.rs: -------------------------------------------------------------------------------- 1 | use common::gamedata::{PlayTime, UniqueId, UniqueIdGenerator}; 2 | use once_cell::sync::Lazy; 3 | use std::sync::atomic::{AtomicU64, Ordering}; 4 | use std::sync::Mutex; 5 | use std::time::{Duration, Instant}; 6 | 7 | static LOAD_TIME: Lazy> = Lazy::new(|| Mutex::new(Instant::now())); 8 | static PLAY_TIME_ON_LOAD: AtomicU64 = AtomicU64::new(0); 9 | static COUNT_ID_GEN: AtomicU64 = AtomicU64::new(0); 10 | static CURRENT_PLAY_TIME: AtomicU64 = AtomicU64::new(0); 11 | 12 | pub fn play_time_as_secs() -> u64 { 13 | CURRENT_PLAY_TIME.load(Ordering::Relaxed) 14 | } 15 | 16 | fn elapsed_since_load() -> Duration { 17 | let load_time = *LOAD_TIME.lock().unwrap(); 18 | 19 | Instant::now().duration_since(load_time) 20 | } 21 | 22 | #[derive(Debug)] 23 | pub struct UniqueIdGeneratorByTime; 24 | 25 | impl UniqueIdGenerator for UniqueIdGeneratorByTime { 26 | fn generate(&mut self) -> UniqueId { 27 | let count = COUNT_ID_GEN.fetch_add(1, Ordering::Relaxed); 28 | let current_play_time = CURRENT_PLAY_TIME.load(Ordering::Relaxed); 29 | 30 | current_play_time << 24 | count 31 | } 32 | } 33 | 34 | #[extend::ext(pub)] 35 | impl PlayTime { 36 | fn start(&mut self) { 37 | *LOAD_TIME.lock().unwrap() = Instant::now(); 38 | PLAY_TIME_ON_LOAD.store(self.seconds(), Ordering::Relaxed); 39 | COUNT_ID_GEN.store(0, Ordering::Relaxed); 40 | } 41 | 42 | fn update(&mut self) { 43 | let elapsed_secs_since_load = elapsed_since_load().as_secs(); 44 | let new_play_time = PLAY_TIME_ON_LOAD.load(Ordering::Relaxed) + elapsed_secs_since_load; 45 | let current_play_time = self.seconds(); 46 | 47 | if current_play_time < new_play_time { 48 | self.advance(new_play_time - current_play_time); 49 | COUNT_ID_GEN.store(0, Ordering::Relaxed); 50 | CURRENT_PLAY_TIME.store(self.seconds(), Ordering::Relaxed); 51 | } 52 | debug_assert_eq!(new_play_time, self.seconds()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/playeract/restart.rs: -------------------------------------------------------------------------------- 1 | use super::DoPlayerAction; 2 | use common::gamedata::*; 3 | use rules::RULES; 4 | 5 | impl<'a> DoPlayerAction<'a> { 6 | pub fn restart(&mut self) { 7 | let gd = self.gd_mut(); 8 | let player = gd.chara.get_mut(CharaId::Player); 9 | player.hp = player.attr.max_hp; 10 | 11 | let (mid, pos) = gd 12 | .region 13 | .path_to_map_id_and_pos(&RULES.world.restart_path) 14 | .unwrap(); 15 | crate::game::map::switch_map(self.0, Destination::MapIdWithPos(mid, pos)); 16 | self.0.start_script(&RULES.world.restart_script, None); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/playeract/shortcut.rs: -------------------------------------------------------------------------------- 1 | use super::DoPlayerAction; 2 | use crate::game::extrait::ItemExt; 3 | use crate::game::InfoGetter; 4 | use common::gamedata::*; 5 | 6 | impl<'a> DoPlayerAction<'a> { 7 | pub fn register_shortcut(&mut self, shortcut: ActionShortcut, n: u32) { 8 | self.0.gd.settings.action_shortcuts[n as usize] = Some(shortcut); 9 | } 10 | 11 | pub fn clear_shortcut(&mut self, n: u32) { 12 | self.0.gd.settings.action_shortcuts[n as usize] = None; 13 | } 14 | 15 | pub fn exec_shortcut(&mut self, n: usize) { 16 | let shortcut = if let Some(shortcut) = self.gd().settings.action_shortcuts[n] { 17 | shortcut 18 | } else { 19 | return; 20 | }; 21 | 22 | match shortcut { 23 | ActionShortcut::Throw(idx) => { 24 | if let Some(il) = self.gd().search_item(idx).get(0) { 25 | self.throw_item(*il); 26 | } 27 | } 28 | ActionShortcut::Drink(idx) => { 29 | if let Some(il) = self.gd().search_item(idx).get(0) { 30 | self.drink_item(*il); 31 | } 32 | } 33 | ActionShortcut::Eat(idx) => { 34 | if let Some(il) = self.gd().search_item(idx).get(0) { 35 | self.eat_item(*il); 36 | } 37 | } 38 | ActionShortcut::Use(idx) => { 39 | if let Some(il) = self.gd().search_item(idx).get(0) { 40 | self.use_item(*il); 41 | } 42 | } 43 | ActionShortcut::Release(idx) => { 44 | for il in &self.gd().search_item(idx) { 45 | let (item, _) = self.gd().get_item(*il); 46 | if let Some(n) = item.charge() { 47 | if n > 0 { 48 | self.release_item(*il); 49 | break; 50 | } 51 | } 52 | } 53 | } 54 | ActionShortcut::Read(idx) => { 55 | if let Some(il) = self.gd().search_item(idx).get(0) { 56 | self.read_item(*il); 57 | } 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/region.rs: -------------------------------------------------------------------------------- 1 | use super::map::choose_empty_tile; 2 | use super::saveload::{gen_box_id, get_map_dir}; 3 | use common::basic::MAX_AUTO_GEN_DUNGEONS; 4 | use common::gamedata::*; 5 | use common::gobj; 6 | use common::regiongen::*; 7 | use rng::*; 8 | use rules::RULES; 9 | 10 | pub fn add_region(gd: &mut GameData, id: &str) { 11 | let rg: &RegionGenObject = gobj::get_by_id(id); 12 | 13 | let map = if let Some(map) = super::map::from_template::from_template_id(&rg.id, false) { 14 | map 15 | } else { 16 | error!("Map generation failed from \"{}\"", rg.id); 17 | panic!(); 18 | }; 19 | 20 | let region = Region::new(id, map, gen_box_id(gd)); 21 | let rid = gd.region.add_region(region); 22 | add_sites_from_genobj(gd, rg, rid); 23 | } 24 | 25 | /// Generate dungeons up to the max 26 | pub fn gen_dungeon_max(gd: &mut GameData, rid: RegionId) { 27 | info!("gen dungeons"); 28 | let n_autogen_dungeons = gd.region.get(rid).get_site_n(SiteKind::AutoGenDungeon); 29 | 30 | if n_autogen_dungeons == MAX_AUTO_GEN_DUNGEONS { 31 | return; 32 | } 33 | 34 | for _ in 0..(MAX_AUTO_GEN_DUNGEONS - n_autogen_dungeons) { 35 | gen_dungeon(gd, rid); 36 | } 37 | } 38 | 39 | /// Generate one dungeon and add it to the region 40 | pub fn gen_dungeon(gd: &mut GameData, rid: RegionId) { 41 | if MAX_AUTO_GEN_DUNGEONS <= gd.region.get(rid).get_site_n(SiteKind::AutoGenDungeon) { 42 | return; 43 | } 44 | 45 | let mid = MapId::from(rid); 46 | gd.region.preload_map(mid, get_map_dir(gd)); 47 | 48 | let pos = { 49 | let region_map = gd.region.get_map(mid); 50 | match choose_empty_tile(region_map) { 51 | Some(pos) => pos, 52 | None => { 53 | warn!("Dungeon generation failed: No empty tile"); 54 | return; 55 | } 56 | } 57 | }; 58 | let dungeon_kind = RULES 59 | .dungeon_gen 60 | .keys() 61 | .collect::>() 62 | .choose(&mut get_rng()) 63 | .copied() 64 | .copied() 65 | .unwrap(); 66 | 67 | super::dungeon_gen::add_dungeon_site(gd, dungeon_kind, pos); 68 | 69 | let region_map = gd.region.get_map_mut(mid); 70 | let site_symbol_kind = RULES.dungeon_gen[&dungeon_kind].symbol; 71 | region_map.tile[pos].special = SpecialTileKind::SiteSymbol { 72 | kind: site_symbol_kind, 73 | }; 74 | } 75 | 76 | fn add_sites_from_genobj(gd: &mut GameData, rg: &RegionGenObject, rid: RegionId) { 77 | // Add towns 78 | for &(ref site_gen_id, pos) in &rg.towns { 79 | super::town::add_town(gd, rid, pos, site_gen_id); 80 | info!( 81 | "Created new a town \"{}\" at {} in {:?}", 82 | site_gen_id, pos, rid 83 | ); 84 | } 85 | 86 | // Add other sites 87 | for &(ref site_gen_id, pos) in &rg.others { 88 | super::site::gen::add_site_from_obj(gd, rid, pos, site_gen_id); 89 | info!( 90 | "Created new an other site \"{}\" at {} in {:?}", 91 | site_gen_id, pos, rid 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/saveload.rs: -------------------------------------------------------------------------------- 1 | use crate::config::USER_DIR; 2 | use crate::game::Game; 3 | use common::basic::{SAVE_DIR_NAME, SAVE_EXTENSION}; 4 | use common::gamedata::GameData; 5 | use std::fs; 6 | use std::path::PathBuf; 7 | 8 | impl Game { 9 | pub fn save_file(&self) { 10 | let save_dir = get_save_dir(); 11 | 12 | if !save_dir.exists() { 13 | match fs::create_dir_all(&save_dir) { 14 | Ok(()) => (), 15 | Err(e) => { 16 | warn!("Failed to create save directory : {}", e); 17 | return; 18 | } 19 | } 20 | } 21 | 22 | let path = self.gd.save_dir(save_dir); 23 | 24 | match self.gd.save(&path) { 25 | Ok(_) => info!("Saved to {:?}", path.to_string_lossy()), 26 | Err(e) => warn!("Faild to saving to {:?}: {}", path.to_string_lossy(), e), 27 | } 28 | } 29 | 30 | pub fn clean_save_data(&self) { 31 | let save_dir = get_save_dir(); 32 | let path = self.gd.save_dir(save_dir); 33 | if !path.exists() { 34 | return; 35 | } 36 | match self.gd.clean_map_dir(&path) { 37 | Ok(_) => info!("Clean map dir {:?}", path.to_string_lossy()), 38 | Err(e) => warn!("Faild to clean map dir {:?}: {}", path.to_string_lossy(), e), 39 | } 40 | } 41 | } 42 | 43 | pub fn save_file_list() -> Result, std::io::Error> { 44 | let mut list = Vec::new(); 45 | 46 | let save_dir = get_save_dir(); 47 | 48 | if !save_dir.exists() { 49 | fs::create_dir_all(&save_dir)?; 50 | } 51 | 52 | for entry in fs::read_dir(save_dir)? { 53 | let file = entry?; 54 | 55 | if !file.file_type()?.is_dir() { 56 | continue; 57 | } 58 | 59 | let path = file.path(); 60 | 61 | let extension = path.extension(); 62 | 63 | if extension.is_some() && extension.unwrap() == SAVE_EXTENSION { 64 | list.push(path); 65 | } 66 | } 67 | 68 | Ok(list) 69 | } 70 | 71 | /// Generate random id for FileBox 72 | pub fn gen_box_id(gd: &GameData) -> u64 { 73 | use rng::*; 74 | 75 | loop { 76 | let s = thread_rng().gen::(); 77 | 78 | // Check generated name is not used 79 | let mut path = get_each_save_dir(gd); 80 | path.push("maps"); 81 | path.push(format!("{s:016x}")); 82 | if !path.exists() { 83 | return s; 84 | } 85 | } 86 | } 87 | 88 | fn get_save_dir() -> PathBuf { 89 | USER_DIR.clone().join(SAVE_DIR_NAME) 90 | } 91 | 92 | /// Get each save directory path "save_dir/save_name" 93 | pub fn get_each_save_dir(gd: &GameData) -> PathBuf { 94 | get_save_dir().join(format!("{}.{}", gd.meta.save_name(), SAVE_EXTENSION)) 95 | } 96 | 97 | /// Get map save directory 98 | pub fn get_map_dir(gd: &GameData) -> PathBuf { 99 | get_each_save_dir(gd).join("maps") 100 | } 101 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/script_methods.rs: -------------------------------------------------------------------------------- 1 | use crate::game::extrait::*; 2 | use crate::game::InfoGetter; 3 | use common::gamedata::*; 4 | use common::gobj; 5 | use script::GameMethod; 6 | 7 | pub fn game_method_caller(gd: &mut GameData, method: GameMethod) -> Value { 8 | match method { 9 | GameMethod::CompleteCustomQuest { id } => { 10 | crate::game::quest::complete_custom_quest(gd, id); 11 | Value::None 12 | } 13 | GameMethod::CustomQuestStarted { id } => { 14 | crate::game::quest::custom_quest_started(gd, &id).into() 15 | } 16 | GameMethod::GenDungeons => { 17 | let mid = gd.get_current_mapid(); 18 | crate::game::region::gen_dungeon_max(gd, mid.rid()); 19 | Value::None 20 | } 21 | GameMethod::GenPartyChara { id, lv } => { 22 | gd.gen_party_chara(&id, lv); 23 | Value::None 24 | } 25 | GameMethod::HasEmptyForParty => gd.has_empty_for_party().into(), 26 | GameMethod::NumberOfItem { id } => gd.has_item_by_id(&id).unwrap_or(0).into(), 27 | GameMethod::ReceiveItem { id, n } => { 28 | let item = crate::game::item::gen::gen_item_from_id(&id, 1); 29 | let il = gd.get_item_list_mut(ItemListLocation::PLAYER); 30 | il.append(item.clone(), n); 31 | let player = gd.chara.get_mut(CharaId::Player); 32 | game_log!("player-receive-item"; chara=player, item=item, n=n); 33 | player.update(); 34 | Value::None 35 | } 36 | GameMethod::ReceiveMoney { amount } => { 37 | gd.player.add_money(amount); 38 | let player = gd.chara.get(CharaId::Player); 39 | game_log!("player-receive-money"; chara=player, amount=amount); 40 | Value::None 41 | } 42 | GameMethod::RemoveItem { id, n } => { 43 | let item_list = gd.get_item_list_mut(ItemListLocation::PLAYER); 44 | item_list.consume(gobj::id_to_idx(&id), n, |_, _| (), false); 45 | gd.chara.get_mut(CharaId::Player).update(); 46 | Value::None 47 | } 48 | GameMethod::ResurrectPartyMembers => { 49 | crate::game::party::resurrect_party_members(gd); 50 | Value::None 51 | } 52 | GameMethod::StartCustomQuest { id, phase } => { 53 | crate::game::quest::start_custom_quest(gd, id, phase); 54 | Value::None 55 | } 56 | GameMethod::SkillLevel { skill_kind } => { 57 | let skill_level = gd.chara.get(CharaId::Player).skill_level(skill_kind); 58 | Value::Int(skill_level.into()) 59 | } 60 | GameMethod::LearnSkill { skill_kind } => { 61 | crate::game::effect::skill_learn::skill_learn(gd, CharaId::Player, &[skill_kind]); 62 | Value::None 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/site/gen.rs: -------------------------------------------------------------------------------- 1 | use crate::game; 2 | use crate::game::extrait::CharaExt; 3 | use crate::game::saveload::gen_box_id; 4 | use common::gamedata::*; 5 | use common::gobj; 6 | use common::obj::SiteGenObject; 7 | use common::sitegen::NpcGenId; 8 | use geom::*; 9 | 10 | /// Add npcs from SiteGenObject 11 | pub fn add_npcs(gd: &mut GameData, sid: SiteId, sg: &SiteGenObject) { 12 | for npc_gen in &sg.npcs { 13 | let cid = match npc_gen.id { 14 | NpcGenId::Site(id) => CharaId::OnSite { sid, id }, 15 | NpcGenId::Unique(id) => CharaId::Unique { id }, 16 | }; 17 | 18 | if gd.chara.exist(cid) { 19 | let chara = gd.chara.get_mut(cid); 20 | chara.resurrect(); 21 | } else { 22 | let idx = gobj::id_to_idx(&npc_gen.chara_template_id); 23 | let ct: &CharaTemplateObject = gobj::get_obj(idx); 24 | let faction = if !matches!(cid, CharaId::OnSite { .. }) && ct.faction.is_unknown() { 25 | None 26 | } else { 27 | Some(sg.default_faction_id) 28 | }; 29 | 30 | let mut chara = game::chara::gen::create_chara(idx, 1, faction, None); 31 | chara.ai.initial_pos = npc_gen.pos; 32 | 33 | if !npc_gen.talk_script.is_empty() { 34 | // Talk script setting 35 | chara.talk_script = Some(npc_gen.talk_script.to_owned()); 36 | } 37 | 38 | gd.add_chara(cid, chara); 39 | } 40 | 41 | let mid = MapId::SiteMap { 42 | sid, 43 | floor: npc_gen.floor, 44 | }; 45 | gd.region.get_map_mut(mid).locate_chara(cid, npc_gen.pos); 46 | } 47 | } 48 | 49 | /// Create site from SiteGenObect and add it to region map 50 | pub fn add_site_from_obj( 51 | gd: &mut GameData, 52 | rid: RegionId, 53 | pos: Coords, 54 | site_id: &str, 55 | ) -> Option { 56 | let sg: &SiteGenObject = gobj::get_by_id(site_id); 57 | let mut site = Site::new(sg.map_template_id.len() as u32, Some(site_id.to_owned())); 58 | let site_content = SiteContent::Other; 59 | site.content = site_content; 60 | let sid = if let Some(sid) = gd.add_site(site, sg.kind, rid, Some(pos)) { 61 | sid 62 | } else { 63 | warn!( 64 | "{:?} is already occupied, and failed to add a new town", 65 | pos 66 | ); 67 | return None; 68 | }; 69 | 70 | for map_template_id in &sg.map_template_id { 71 | let map = crate::game::map::from_template::from_template_id(map_template_id, true) 72 | .unwrap_or_else(|| panic!("Map template not found: {map_template_id}")); 73 | 74 | let map_random_id = gen_box_id(gd); 75 | gd.add_map(map, sid, map_random_id); 76 | } 77 | 78 | add_npcs(gd, sid, sg); 79 | 80 | // Add symbol to region map 81 | let map = gd.region.get_map_mut(MapId::from(rid)); 82 | map.tile[pos].special = SpecialTileKind::SiteSymbol { 83 | kind: sg.site_symbol, 84 | }; 85 | 86 | crate::game::shop::update_shops(gd, sid, sg); 87 | 88 | Some(sid) 89 | } 90 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/site/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod gen; 2 | pub mod temp; 3 | 4 | use common::gamedata::*; 5 | 6 | /// Additional Site method 7 | #[extend::ext(pub)] 8 | impl Site {} 9 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/site/temp.rs: -------------------------------------------------------------------------------- 1 | use crate::game::saveload::gen_box_id; 2 | use common::gamedata::*; 3 | 4 | pub fn gen_temp_site_from_map( 5 | gd: &mut GameData, 6 | rid: RegionId, 7 | map: Map, 8 | name: &str, 9 | site_content: SiteContent, 10 | ) -> MapId { 11 | assert!(site_content.kind() == SiteKind::Temp); 12 | let mut site = Site::new(1, None); 13 | site.content = site_content; 14 | site.name = Some(name.into()); 15 | let sid = gd.add_site(site, SiteKind::Temp, rid, None).unwrap(); 16 | let map_random_id = gen_box_id(gd); 17 | gd.add_map(map, sid, map_random_id) 18 | } 19 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/target.rs: -------------------------------------------------------------------------------- 1 | use crate::game::Game; 2 | use common::gamedata::*; 3 | use geom::*; 4 | 5 | #[derive(Clone, Copy, Debug)] 6 | pub enum Target { 7 | None, 8 | Tile(Coords), 9 | Chara(CharaId), 10 | } 11 | 12 | impl From for Target { 13 | fn from(pos: Coords) -> Target { 14 | Target::Tile(pos) 15 | } 16 | } 17 | 18 | impl From for Target { 19 | fn from(cid: CharaId) -> Target { 20 | Target::Chara(cid) 21 | } 22 | } 23 | 24 | // pub fn auto_target(game: &Game, cid: CharaId, effect: &Effect) -> Option { 25 | // if effect.target_mode == TargetMode::None { 26 | // return Target::None; 27 | // } 28 | // if cid == CharaId::Player { 29 | // match auto_target_for_player(game, effect) { 30 | // Ok(target) => { return target; } 31 | // Err(_) => (), 32 | // } 33 | // } 34 | // todo!(); 35 | // } 36 | 37 | pub fn auto_target_for_player(game: &Game, effect: &Effect) -> Option { 38 | match effect.target_mode { 39 | TargetMode::None => Some(Target::None), 40 | TargetMode::Player => Some(Target::Chara(CharaId::Player)), 41 | TargetMode::Ally => None, 42 | TargetMode::Enemy => game.target_chara().map(|target| target.into()), 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/town.rs: -------------------------------------------------------------------------------- 1 | use crate::game::item::gen::gen_item_from_id; 2 | use common::gamedata::*; 3 | use common::gobj; 4 | use common::obj::SiteGenObject; 5 | use geom::Coords; 6 | 7 | /// Create town from SiteGenObect and add it to region map 8 | pub fn add_town(gd: &mut GameData, rid: RegionId, pos: Coords, town_id: &str) { 9 | let sid = crate::game::site::gen::add_site_from_obj(gd, rid, pos, town_id).unwrap(); 10 | 11 | let town = Town::new(town_id); 12 | let site_content = SiteContent::Town { 13 | town: Box::new(town), 14 | }; 15 | let mut site = gd.region.get_site_mut(sid); 16 | site.content = site_content; 17 | 18 | let sg: &SiteGenObject = gobj::get_by_id(town_id); 19 | 20 | // Locate delivery chest 21 | if let Some((floor, pos, ref id)) = sg.delivery_chest { 22 | let mut item = gen_item_from_id(id, 0); 23 | item.flags |= ItemFlags::FIXED; 24 | let ill = ItemListLocation::OnMap { 25 | mid: MapId::SiteMap { sid, floor }, 26 | pos, 27 | }; 28 | let item_list = gd.get_item_list_mut(ill); 29 | item_list.clear(); 30 | item_list.append_simple(item, 1); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /rusted-ruins/src/game/view.rs: -------------------------------------------------------------------------------- 1 | //! This module processes the view of characters 2 | 3 | use crate::game::Game; 4 | use crate::game::InfoGetter; 5 | use common::gamedata::*; 6 | use geom::*; 7 | 8 | /// The cache for determining player's view 9 | pub struct ViewMap { 10 | visible: Array2d, 11 | } 12 | 13 | impl ViewMap { 14 | pub fn new() -> ViewMap { 15 | ViewMap { 16 | visible: Array2d::new(128, 128, false), 17 | } 18 | } 19 | 20 | fn reserve_size(&mut self, w: u32, h: u32) { 21 | let size = self.visible.size(); 22 | if size.0 >= w || size.1 >= h { 23 | use std::cmp::max; 24 | self.visible = Array2d::new(max(size.0, w), max(size.1, h), false); 25 | } 26 | } 27 | 28 | fn fill(&mut self, w: u32, h: u32, value: bool) { 29 | for ny in 0..h { 30 | for nx in 0..w { 31 | self.visible[(nx, ny)] = value; 32 | } 33 | } 34 | } 35 | 36 | pub fn get_tile_visible(&self, pos: Coords) -> bool { 37 | if self.visible.in_range(pos) { 38 | self.visible[pos] 39 | } else { 40 | false 41 | } 42 | } 43 | } 44 | 45 | pub fn update_view_map(game: &mut Game) { 46 | let map = game.gd.get_current_map(); 47 | let (w, h) = map.size(); 48 | let view_map = &mut game.view_map; 49 | view_map.reserve_size(w, h); 50 | 51 | if game.gd.get_current_mapid().is_region_map() { 52 | view_map.fill(w, h, true); // Fill by true when region map 53 | return; 54 | } 55 | 56 | // Fill by false 57 | view_map.fill(w, h, false); // Fill by false 58 | 59 | let player_pos = game.gd.player_pos(); 60 | let player_view_range = game.gd.chara.get(CharaId::Player).attr.view_range; 61 | 62 | view_map.visible[player_pos] = true; 63 | 64 | for (_, pos) in MDistRangeIter::new(player_pos, player_view_range) { 65 | if !map.is_inside(pos) { 66 | continue; 67 | } 68 | 69 | for p in LineIter::new(player_pos, pos).skip(1) { 70 | view_map.visible[p] = true; 71 | if !map.tile[p].wall.is_empty() { 72 | break; 73 | } 74 | } 75 | } 76 | } 77 | 78 | pub fn calc_visual_distance(map: &Map, orig: Coords, dist: Coords) -> Option { 79 | for pos in LineIter::new(orig, dist) { 80 | if !map.tile[pos].wall.is_empty() { 81 | return None; 82 | } 83 | } 84 | 85 | Some(dist.mdistance(orig)) 86 | } 87 | -------------------------------------------------------------------------------- /rusted-ruins/src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn( 2 | rust_2018_compatibility, 3 | rust_2018_idioms, 4 | future_incompatible, 5 | nonstandard_style 6 | )] 7 | #![allow(clippy::comparison_chain, clippy::type_complexity)] 8 | 9 | extern crate rusted_ruins_audio as audio; 10 | extern crate rusted_ruins_common as common; 11 | extern crate rusted_ruins_map_generator as map_generator; 12 | extern crate rusted_ruins_rng as rng; 13 | extern crate rusted_ruins_rules as rules; 14 | extern crate rusted_ruins_script as script; 15 | extern crate tile_geom as geom; 16 | #[macro_use] 17 | extern crate serde_derive; 18 | #[macro_use] 19 | extern crate log as _; 20 | 21 | #[macro_use] 22 | mod util; 23 | #[macro_use] 24 | mod error; 25 | #[macro_use] 26 | mod log; 27 | #[macro_use] 28 | mod text; 29 | mod config; 30 | mod context; 31 | mod cursor; 32 | mod damage_popup; 33 | mod draw; 34 | mod eventhandler; 35 | mod game; 36 | mod msg_box; 37 | mod screen; 38 | mod sdltypeconv; 39 | mod window; 40 | 41 | fn main() { 42 | setup_logger(); 43 | init_lazy(); 44 | let se = script::ScriptEngine::start_init(crate::game::script_methods::game_method_caller); 45 | init_obj(); 46 | // Must be after init_obj() 47 | init_rules(); 48 | 49 | let sdl_context = SdlContext::init(); 50 | let mut screen = screen::Screen::new(&sdl_context.sdl_context); 51 | cursor::load(); 52 | 53 | se.wait_init(); 54 | screen.main_loop(&sdl_context, se); 55 | } 56 | 57 | pub struct SdlContext { 58 | pub sdl_context: sdl2::Sdl, 59 | pub ttf_context: sdl2::ttf::Sdl2TtfContext, 60 | _image: sdl2::image::Sdl2ImageContext, 61 | _audio_context: audio::AudioContext, 62 | } 63 | 64 | impl SdlContext { 65 | fn init() -> SdlContext { 66 | SdlContext { 67 | sdl_context: sdl2::init().expect("init failed : SDL Context"), 68 | ttf_context: sdl2::ttf::init().expect("init failed : SDL_ttf Context"), 69 | _image: sdl2::image::init(sdl2::image::InitFlag::PNG).expect("init failed : SDL_Image"), 70 | _audio_context: audio::init( 71 | &config::get_data_dirs(), 72 | crate::config::CONFIG.sound_effect_volume, 73 | crate::config::CONFIG.music_volume, 74 | ), 75 | } 76 | } 77 | } 78 | 79 | /// Initialize lazy values 80 | fn init_lazy() { 81 | config::init(); 82 | text::init(); 83 | log::init(); 84 | } 85 | 86 | fn init_obj() { 87 | let mut data_dirs = crate::config::get_data_dirs(); 88 | for d in data_dirs.iter_mut() { 89 | info!("loading objects from \"{}\"", d.to_string_lossy()); 90 | d.push("paks"); 91 | } 92 | common::gobj::init(data_dirs); 93 | } 94 | 95 | fn init_rules() { 96 | rules::init( 97 | &*crate::config::ASSETS_DIR, 98 | crate::config::ADDON_DIR.as_ref(), 99 | ); 100 | } 101 | 102 | /// Setup logger. It is not game logger. It is for debug and warning information. 103 | fn setup_logger() { 104 | env_logger::builder().format_timestamp(None).init(); 105 | } 106 | -------------------------------------------------------------------------------- /rusted-ruins/src/msg_box.rs: -------------------------------------------------------------------------------- 1 | use sdl2::messagebox::*; 2 | 3 | pub fn lang_selector() -> &'static str { 4 | let result = show_message_box( 5 | MessageBoxFlag::INFORMATION, 6 | &[ 7 | ButtonData { 8 | flags: MessageBoxButtonFlag::NOTHING, 9 | button_id: 0, 10 | text: "English", 11 | }, 12 | ButtonData { 13 | flags: MessageBoxButtonFlag::NOTHING, 14 | button_id: 1, 15 | text: "日本語", 16 | }, 17 | ], 18 | "Select language", 19 | "Please select language\nEnglish translation is incomplete. Some text is displayed in Japanese.", 20 | None, 21 | None, 22 | ).unwrap(); 23 | 24 | match result { 25 | ClickedButton::CustomButton(ButtonData { button_id, .. }) if *button_id == 1 => "ja", 26 | _ => "en", 27 | } 28 | } 29 | 30 | pub fn exit_with_error(title: impl std::fmt::Display, e: impl std::fmt::Display) -> ! { 31 | let title = title.to_string(); 32 | let e = e.to_string(); 33 | show_simple_message_box(MessageBoxFlag::ERROR, &title, &e, None).unwrap(); 34 | std::process::exit(1) 35 | } 36 | -------------------------------------------------------------------------------- /rusted-ruins/src/sdltypeconv.rs: -------------------------------------------------------------------------------- 1 | use crate::config::{CfgColor, CfgPos, CfgRect, SCREEN_CFG}; 2 | use common::basic::{TAB_ICON_H, TAB_TEXT_H}; 3 | use sdl2::pixels::Color; 4 | use sdl2::rect::Rect; 5 | 6 | /// Centering for the screen 7 | pub const CENTERING_POS_FOR_SCREEN: i32 = -1000; 8 | /// Centering for main window 9 | pub const CENTERING_POS: i32 = -999; 10 | 11 | impl From for Rect { 12 | fn from(value: CfgRect) -> Rect { 13 | let x = if value.x == CENTERING_POS_FOR_SCREEN { 14 | (SCREEN_CFG.screen_w - value.w) as i32 / 2 15 | } else if value.x == CENTERING_POS { 16 | SCREEN_CFG.main_window.x + (SCREEN_CFG.main_window.w - value.w) as i32 / 2 17 | } else { 18 | value.x 19 | }; 20 | let y = if value.y == CENTERING_POS_FOR_SCREEN { 21 | (SCREEN_CFG.screen_h - value.h) as i32 / 2 22 | } else if value.y == CENTERING_POS { 23 | let tab_h = TAB_ICON_H + TAB_TEXT_H; 24 | if value.h + tab_h < SCREEN_CFG.main_window.h { 25 | SCREEN_CFG.main_window.y 26 | + (SCREEN_CFG.main_window.h - value.h) as i32 / 2 27 | + tab_h as i32 / 2 28 | } else { 29 | (SCREEN_CFG.screen_h - value.h) as i32 / 2 30 | } 31 | } else { 32 | value.y 33 | }; 34 | Rect::new(x, y, value.w, value.h) 35 | } 36 | } 37 | 38 | impl From for (i32, i32) { 39 | fn from(c: CfgPos) -> Self { 40 | (c.x, c.y) 41 | } 42 | } 43 | 44 | impl From for Color { 45 | fn from(c: CfgColor) -> Self { 46 | if let Some(a) = c.a { 47 | Color::RGBA(c.r, c.g, c.b, a) 48 | } else { 49 | Color::RGB(c.r, c.g, c.b) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /rusted-ruins/src/text/desc.rs: -------------------------------------------------------------------------------- 1 | use super::to_text::CharaTraitTextId; 2 | use super::{misc_txt, ToText}; 3 | use common::gamedata::CharaTrait; 4 | use rules::RULES; 5 | use std::fmt::Write; 6 | 7 | pub fn trait_description(chara_trait: &CharaTrait) -> String { 8 | let mut desc = misc_txt(&format!("trait-{}-desc", chara_trait.text_id())); 9 | 10 | let _ = writeln!(desc, "\n"); 11 | 12 | if let CharaTrait::Id(id) = chara_trait { 13 | for modifier in &RULES.chara_traits.get(id).modifiers { 14 | let _ = writeln!(desc, "{}", &modifier.to_text()); 15 | } 16 | } 17 | 18 | desc 19 | } 20 | -------------------------------------------------------------------------------- /rusted-ruins/src/text/effect.rs: -------------------------------------------------------------------------------- 1 | use common::gamedata::{Effect, EffectKind}; 2 | 3 | use super::{misc_txt, ToText}; 4 | 5 | pub const UI_IMG_ID_ITEM_INFO: &str = "!icon-item-info"; 6 | 7 | #[derive(Debug)] 8 | pub struct EffectText { 9 | power_factor: f32, 10 | } 11 | 12 | impl Default for EffectText { 13 | fn default() -> Self { 14 | Self::new() 15 | } 16 | } 17 | 18 | impl EffectText { 19 | pub fn new() -> Self { 20 | EffectText { power_factor: 1.0 } 21 | } 22 | 23 | pub fn effect_kind( 24 | &self, 25 | effect_kind: &EffectKind, 26 | power: f32, 27 | hit: f32, 28 | ) -> (&'static str, String) { 29 | let power = format!("{:.0}", power * self.power_factor); 30 | let hit = format!("{hit:.0}"); 31 | 32 | match effect_kind { 33 | EffectKind::None => ("!", misc_txt("effect_kind-none")), 34 | EffectKind::RestoreHp => ("!", misc_txt_format!("effect_kind-restore_hp"; power=power)), 35 | EffectKind::RestoreSp => ("!", misc_txt_format!("effect_kind-restore_sp"; power=power)), 36 | EffectKind::RestoreMp => ("!", misc_txt_format!("effect_kind-restore_mp"; power=power)), 37 | EffectKind::Melee { .. } => ("!", misc_txt_format!("effect_kind-melee"; power=power)), 38 | EffectKind::Ranged { .. } => ("!", misc_txt_format!("effect_kind-ranged"; power=power)), 39 | EffectKind::Explosion { .. } => { 40 | ("!", misc_txt_format!("effect_kind-explosion"; power=power)) 41 | } 42 | EffectKind::Direct { .. } => ("!", misc_txt_format!("effect_kind-direct"; power=power)), 43 | EffectKind::Status { status } => ( 44 | "!", 45 | misc_txt_format!("effect_kind-status"; status=status, hit=hit), 46 | ), 47 | EffectKind::WallDamage => ("!", misc_txt("effect_kind-wall_damage")), 48 | EffectKind::CharaScan => ("!", misc_txt("effect_kind-chara_scan")), 49 | EffectKind::SkillLearning { .. } => ("!", misc_txt("effect_kind-skill_learning")), 50 | EffectKind::PlaceTile { .. } => ("!", misc_txt("effect_kind-place_tile")), 51 | EffectKind::GenItem { .. } => ("!", misc_txt("effect_kind-gen_item")), 52 | } 53 | } 54 | 55 | pub fn effect(&self, effect: &Effect) -> Vec<(&'static str, String)> { 56 | let power: f32 = effect.base_power.0.into(); 57 | let hit: f32 = effect.hit.into(); 58 | effect 59 | .kind 60 | .iter() 61 | .map(|effect_kind| self.effect_kind(effect_kind, power, hit)) 62 | .collect() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /rusted-ruins/src/text/img_inline.rs: -------------------------------------------------------------------------------- 1 | pub const SILVER: &str = ":ui_img/!icon-silver:"; 2 | -------------------------------------------------------------------------------- /rusted-ruins/src/text/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! misc_txt_format { 2 | ($id:expr; $($target:ident = $value:expr),*) => {{ 3 | let mut table = fluent::FluentArgs::new(); 4 | $( 5 | let value = fluent::FluentValue::String($value.to_text()); 6 | table.add(stringify!($target), value); 7 | )* 8 | 9 | crate::text::misc_txt_with_args($id, Some(&table)) 10 | }} 11 | } 12 | 13 | macro_rules! ui_txt_format { 14 | ($id:expr; $($target:ident = $value:expr),*) => {{ 15 | use crate::text::ToText; 16 | let mut table = fluent::FluentArgs::new(); 17 | $( 18 | let value = fluent::FluentValue::String($value.to_text()); 19 | table.add(stringify!($target), value); 20 | )* 21 | 22 | crate::text::ui_txt_with_args($id, Some(&table)) 23 | }} 24 | } 25 | -------------------------------------------------------------------------------- /rusted-ruins/src/text/prefix.rs: -------------------------------------------------------------------------------- 1 | use super::misc_txt; 2 | use common::gamedata::MaterialName; 3 | 4 | pub fn material(name: MaterialName) -> String { 5 | misc_txt(&format!("material-{name}")) 6 | } 7 | 8 | pub fn material_group(group: &str) -> String { 9 | misc_txt(&format!("material_group-{group}")) 10 | } 11 | -------------------------------------------------------------------------------- /rusted-ruins/src/text/readable.rs: -------------------------------------------------------------------------------- 1 | use super::READABLE_BUNDLE; 2 | 3 | const NEW_PAGE_LINE: &str = ""; 4 | 5 | pub fn readable_title_txt(id: &str) -> Option { 6 | let id = format!("{id}-title"); 7 | READABLE_BUNDLE.format(&id, None) 8 | } 9 | 10 | pub fn readable_txt(id: &str) -> Vec { 11 | let text = if let Some(text) = READABLE_BUNDLE.format(id, None) { 12 | text 13 | } else { 14 | return vec!["(empty)".to_owned()]; 15 | }; 16 | 17 | let mut v = Vec::new(); 18 | let mut s = String::new(); 19 | 20 | for line in text.lines() { 21 | if line != NEW_PAGE_LINE { 22 | s.push_str(line); 23 | s.push('\n'); 24 | } else { 25 | v.push(std::mem::take(&mut s)); 26 | } 27 | } 28 | 29 | if !s.is_empty() { 30 | v.push(s); 31 | } 32 | 33 | v 34 | } 35 | -------------------------------------------------------------------------------- /rusted-ruins/src/util.rs: -------------------------------------------------------------------------------- 1 | macro_rules! find_attr { 2 | ($e:expr, $enum_type:ident::$enum_member:ident) => { 3 | $e.attrs 4 | .iter() 5 | .find(|attr| matches!(attr, $enum_type::$enum_member { .. })) 6 | }; 7 | 8 | ($e:expr, $p:pat => $result:tt) => { 9 | $e.attrs 10 | .iter() 11 | .filter_map(|attr| match attr { 12 | $p => Some($result), 13 | _ => None, 14 | }) 15 | .next() 16 | }; 17 | 18 | ($e:expr, $enum_type:ident::$enum_member:ident($name:ident)) => { 19 | $e.attrs 20 | .iter() 21 | .filter_map(|attr| match attr { 22 | $enum_type::$enum_member($name) => Some($name), 23 | _ => None, 24 | }) 25 | .next() 26 | }; 27 | } 28 | 29 | macro_rules! find_attr_mut { 30 | ($e:expr, $enum_type:ident::$enum_member:ident) => { 31 | $e.attrs 32 | .iter_mut() 33 | .find(|attr| matches!(attr, $enum_type::$enum_member { .. })) 34 | }; 35 | 36 | ($e:expr, $p:pat => $result:tt) => { 37 | $e.attrs 38 | .iter_mut() 39 | .filter_map(|attr| match attr { 40 | $p => Some($result), 41 | _ => None, 42 | }) 43 | .next() 44 | }; 45 | 46 | ($e:expr, $enum_type:ident::$enum_member:ident($name:ident)) => { 47 | $e.attrs 48 | .iter_mut() 49 | .filter_map(|attr| match attr { 50 | $enum_type::$enum_member($name) => Some($name), 51 | _ => None, 52 | }) 53 | .next() 54 | }; 55 | } 56 | 57 | macro_rules! has_attr { 58 | ($e:expr, $p:path) => { 59 | $e.attrs.iter().any(|attr| matches!(attr, $p { .. })) 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /rusted-ruins/src/window/build_obj_dialog.rs: -------------------------------------------------------------------------------- 1 | use super::commonuse::*; 2 | use super::widget::*; 3 | use crate::config::UI_CFG; 4 | use crate::text::obj_txt; 5 | use common::gamedata::BuildObj; 6 | use common::gamedata::ItemLocation; 7 | use sdl2::rect::Rect; 8 | 9 | pub struct BuildObjDialog { 10 | rect: Rect, 11 | closer: DialogCloser, 12 | list: ListWidget, 13 | build_objs: Vec<(BuildObj, u32)>, 14 | il: ItemLocation, 15 | } 16 | 17 | impl BuildObjDialog { 18 | pub fn new(il: ItemLocation) -> BuildObjDialog { 19 | let cfg = &UI_CFG.build_obj_dialog; 20 | let rect: Rect = cfg.rect.into(); 21 | let mut list = ListWidget::with_scroll_bar( 22 | Rect::new(0, 0, rect.width(), rect.height()), 23 | cfg.column_pos.clone(), 24 | cfg.n_row, 25 | false, 26 | ); 27 | let build_objs = crate::game::building::build_obj_list(); 28 | let items: Vec = build_objs 29 | .iter() 30 | .map(|(build_obj, _)| { 31 | let item_text = match build_obj { 32 | BuildObj::Tile(id) => obj_txt(id), 33 | BuildObj::Wall(id) => obj_txt(id), 34 | }; 35 | TextCache::new(item_text, FontKind::M, UI_CFG.color.normal_font) 36 | }) 37 | .collect(); 38 | list.set_items(items); 39 | 40 | BuildObjDialog { 41 | rect, 42 | closer: DialogCloser::new(rect), 43 | list, 44 | build_objs, 45 | il, 46 | } 47 | } 48 | } 49 | 50 | impl Window for BuildObjDialog { 51 | fn draw( 52 | &mut self, 53 | context: &mut Context<'_, '_, '_, '_>, 54 | _game: &Game, 55 | _anim: Option<(&Animation, u32)>, 56 | ) { 57 | self.closer.draw(context); 58 | draw_window_border(context, self.rect); 59 | self.list.draw(context); 60 | } 61 | } 62 | 63 | impl DialogWindow for BuildObjDialog { 64 | fn process_command(&mut self, command: &Command, pa: &mut DoPlayerAction<'_>) -> DialogResult { 65 | if *command == Command::Cancel { 66 | return DialogResult::Close; 67 | } 68 | 69 | closer!(self, command); 70 | let command = command.relative_to(self.rect); 71 | 72 | if let Some(ListWidgetResponse::Select(i)) = self.list.process_command(&command) { 73 | let build_obj = self.build_objs[i as usize].0.clone(); 74 | pa.select_build_obj(self.il, build_obj); 75 | return DialogResult::Close; 76 | } 77 | 78 | DialogResult::Continue 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /rusted-ruins/src/window/dialogreq.rs: -------------------------------------------------------------------------------- 1 | use super::build_obj_dialog; 2 | use super::item_info_window; 3 | use super::item_window::*; 4 | use super::msg_dialog; 5 | use super::read_window; 6 | use super::status_window; 7 | use super::talk_window; 8 | use super::DialogWindow; 9 | use crate::game::{DialogOpenRequest, Game}; 10 | use common::gamedata::{CharaId, ModuleSlotKind}; 11 | use script::TalkText; 12 | 13 | pub fn create_dialog_from_request( 14 | req: DialogOpenRequest, 15 | game: &mut Game, 16 | ) -> Option> { 17 | Some(match req { 18 | DialogOpenRequest::YesNo { mut callback, msg } => { 19 | let msgdialog = msg_dialog::MsgDialog::with_yesno(&msg, move |pa, n| { 20 | callback(pa, n == 0); 21 | super::DialogResult::Close 22 | }); 23 | Box::new(msgdialog) 24 | } 25 | DialogOpenRequest::Talk { cid, talk_text } => create_talk_dialog(talk_text, cid, game)?, 26 | DialogOpenRequest::BuildObj { il } => Box::new(build_obj_dialog::BuildObjDialog::new(il)), 27 | DialogOpenRequest::ItemInfo { il } => { 28 | Box::new(item_info_window::ItemInfoWindow::new(il, game)) 29 | } 30 | DialogOpenRequest::CharaStatus { cid } => { 31 | Box::new(status_window::create_status_window_group(game, cid, false)) 32 | } 33 | DialogOpenRequest::Read { title } => Box::new(read_window::ReadWindow::new(&title)), 34 | DialogOpenRequest::ShopBuy { cid } => { 35 | Box::new(ItemWindow::new(ItemWindowMode::ShopBuy { cid }, game)) 36 | } 37 | DialogOpenRequest::ShopSell => Box::new(ItemWindow::new(ItemWindowMode::ShopSell, game)), 38 | DialogOpenRequest::RegisterAsShortcut { shortcut } => { 39 | Box::new(super::register_shortcut_dialog::RegisterShortcutDialog::new(shortcut)) 40 | } 41 | DialogOpenRequest::PickUpItem => Box::new(ItemWindow::new(ItemWindowMode::PickUp, game)), 42 | DialogOpenRequest::QuestOffer => { 43 | Box::new(super::quest_window::QuestWindow::new_offer(&game.gd)) 44 | } 45 | DialogOpenRequest::QuestReport => { 46 | Box::new(super::quest_window::QuestWindow::new_report(&game.gd)) 47 | } 48 | DialogOpenRequest::InstallAbilitySlot => Box::new( 49 | super::slot_window::SlotInstallWindow::new(&game.gd, ModuleSlotKind::Ability), 50 | ), 51 | DialogOpenRequest::InstallExtendSlot => Box::new( 52 | super::slot_window::SlotInstallWindow::new(&game.gd, ModuleSlotKind::Extend), 53 | ), 54 | DialogOpenRequest::InsertModule => { 55 | Box::new(super::slot_window::slot_insertable_item_window(game)) 56 | } 57 | DialogOpenRequest::GameOver => Box::new(super::exit_window::GameOverWindow::new()), 58 | }) 59 | } 60 | 61 | pub fn create_talk_dialog( 62 | talk_text: TalkText, 63 | cid: Option, 64 | game: &mut Game, 65 | ) -> Option> { 66 | let talk_window = talk_window::TalkWindow::new(&game.gd, talk_text, cid); 67 | Some(Box::new(talk_window)) 68 | } 69 | -------------------------------------------------------------------------------- /rusted-ruins/src/window/help_window.rs: -------------------------------------------------------------------------------- 1 | use super::commonuse::*; 2 | use super::widget::*; 3 | use crate::config::{INPUT_CFG, UI_CFG}; 4 | use crate::text::ToText; 5 | 6 | pub struct HelpWindow { 7 | rect: Rect, 8 | key_labels: Vec, 9 | } 10 | 11 | const COMMANDS: &[Command] = &[ 12 | Command::OpenHelpWin, 13 | Command::OpenStatusWin, 14 | Command::OpenGameInfoWin, 15 | Command::OpenItemWin, 16 | Command::OpenEquipWin, 17 | Command::EatItem, 18 | Command::DrinkItem, 19 | Command::DropItem, 20 | Command::OpenExitWin, 21 | Command::OpenCreationWin, 22 | ]; 23 | 24 | impl HelpWindow { 25 | pub fn new() -> HelpWindow { 26 | let cfg = &UI_CFG.help_window; 27 | let rect = cfg.rect.into(); 28 | 29 | let key_labels = COMMANDS 30 | .iter() 31 | .enumerate() 32 | .map(|(i, c)| { 33 | let s = format!("{} {}", c.to_text(), INPUT_CFG.find_key(c)); 34 | let mut r: Rect = cfg.key_label_start.into(); 35 | r.offset(0, cfg.key_label_h * i as i32); 36 | LabelWidget::new(r, s, FontKind::M) 37 | }) 38 | .collect::>(); 39 | 40 | HelpWindow { rect, key_labels } 41 | } 42 | } 43 | 44 | impl Window for HelpWindow { 45 | fn draw( 46 | &mut self, 47 | context: &mut Context<'_, '_, '_, '_>, 48 | _game: &Game, 49 | _anim: Option<(&Animation, u32)>, 50 | ) { 51 | draw_window_border(context, self.rect); 52 | for label in &mut self.key_labels { 53 | label.draw(context); 54 | } 55 | } 56 | } 57 | 58 | impl DialogWindow for HelpWindow { 59 | fn process_command(&mut self, command: &Command, _pa: &mut DoPlayerAction<'_>) -> DialogResult { 60 | match command { 61 | Command::Cancel => DialogResult::Close, 62 | _ => DialogResult::Continue, 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rusted-ruins/src/window/log_window.rs: -------------------------------------------------------------------------------- 1 | use crate::config::{SCREEN_CFG, UI_CFG}; 2 | use crate::context::*; 3 | use crate::game::{Animation, Game}; 4 | use crate::log; 5 | use crate::window::Window; 6 | use sdl2::pixels::Color; 7 | use sdl2::rect::Rect; 8 | use std::collections::VecDeque; 9 | 10 | pub struct LogWindow { 11 | rect: Rect, 12 | line_cache: LineCache, 13 | } 14 | 15 | impl LogWindow { 16 | pub fn new() -> LogWindow { 17 | LogWindow { 18 | rect: SCREEN_CFG.log_window.into(), 19 | line_cache: LineCache::new(), 20 | } 21 | } 22 | } 23 | 24 | impl Window for LogWindow { 25 | fn draw( 26 | &mut self, 27 | context: &mut Context<'_, '_, '_, '_>, 28 | _game: &Game, 29 | _anim: Option<(&Animation, u32)>, 30 | ) { 31 | context.set_viewport(None); 32 | context.canvas.set_draw_color(UI_CFG.color.log_window_bg); 33 | try_sdl!(context.canvas.fill_rect(self.rect)); 34 | context.set_viewport(self.rect); 35 | 36 | self.line_cache.update(); 37 | 38 | let end = self.line_cache.lines.len(); 39 | let n_display_line = UI_CFG.log_window.n_display_line; 40 | let start = if end > n_display_line { 41 | end - n_display_line 42 | } else { 43 | 0 44 | }; 45 | let dy = UI_CFG.log_window.h; 46 | 47 | for (i, line) in (start..end).enumerate() { 48 | let line_texs = context.sv.tt_group(&mut self.line_cache.lines[line]); 49 | let mut x = 0; 50 | for t in line_texs { 51 | let w = t.query().width; 52 | let h = t.query().height; 53 | let orig = Rect::new(0, 0, w, h); 54 | let dest = Rect::new(x, dy * i as i32, w, h); 55 | try_sdl!(context.canvas.copy(t, orig, dest)); 56 | x += w as i32; 57 | } 58 | } 59 | } 60 | } 61 | 62 | /// Stores TextCache for log rendering 63 | struct LineCache { 64 | lines: VecDeque, 65 | latest_line: usize, 66 | } 67 | 68 | impl LineCache { 69 | fn new() -> LineCache { 70 | LineCache { 71 | lines: VecDeque::new(), 72 | latest_line: 0, 73 | } 74 | } 75 | 76 | /// Update from log data 77 | fn update(&mut self) { 78 | log::with_lines(self.latest_line, |s| { 79 | self.append(s); 80 | }); 81 | self.latest_line = log::latest_line() 82 | } 83 | 84 | /// Append one line 85 | fn append(&mut self, s: &[String]) { 86 | let t = TextCache::group(s, FontKind::Log, Color::RGB(255, 255, 255)); 87 | self.lines.push_back(t); 88 | 89 | if self.lines.len() > 20 { 90 | self.lines.pop_front(); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /rusted-ruins/src/window/minimap.rs: -------------------------------------------------------------------------------- 1 | use crate::config::SCREEN_CFG; 2 | use crate::context::*; 3 | use crate::game::Game; 4 | use crate::game::{Animation, InfoGetter}; 5 | use crate::window::Window; 6 | use common::gobj; 7 | use geom::*; 8 | use sdl2::pixels::Color; 9 | use sdl2::rect::Rect; 10 | use sdl2::render::WindowCanvas; 11 | 12 | pub struct MiniMapWindow { 13 | rect: Rect, 14 | } 15 | 16 | impl MiniMapWindow { 17 | pub fn new() -> MiniMapWindow { 18 | MiniMapWindow { 19 | rect: SCREEN_CFG.minimap_window.into(), 20 | } 21 | } 22 | } 23 | 24 | impl Window for MiniMapWindow { 25 | fn draw( 26 | &mut self, 27 | context: &mut Context<'_, '_, '_, '_>, 28 | game: &Game, 29 | _anim: Option<(&Animation, u32)>, 30 | ) { 31 | context.canvas.set_draw_color((0, 0, 0)); 32 | context.set_viewport(None); 33 | try_sdl!(context.canvas.fill_rect(self.rect)); 34 | context.set_viewport(self.rect); 35 | draw_minimap(context.canvas, self.rect, game, context.sv); 36 | } 37 | } 38 | 39 | const RECT_SIZE: u32 = 3; 40 | const RECT_SIZE_I: i32 = RECT_SIZE as i32; 41 | 42 | fn draw_minimap(canvas: &mut WindowCanvas, rect: Rect, game: &Game, _sv: &mut SdlValues<'_, '_>) { 43 | use std::cmp::{max, min}; 44 | let map = game.gd.get_current_map(); 45 | let map_size = map.size(); 46 | let n_width = (rect.width() / RECT_SIZE) as i32; 47 | let n_height = (rect.height() / RECT_SIZE) as i32; 48 | let center_p = game.gd.player_pos(); 49 | let top_left = (center_p.0 - n_width / 2, center_p.1 - n_height / 2); 50 | let bottom_right = ( 51 | min(map_size.0 as i32 - 1, center_p.0 + n_width / 2), 52 | min(map_size.1 as i32 - 1, center_p.1 + n_height / 2), 53 | ); 54 | let (dx, dy) = (top_left.0 * RECT_SIZE_I, top_left.1 * RECT_SIZE_I); 55 | let top_left = (max(0, top_left.0), max(0, top_left.1)); 56 | 57 | for p in RectIter::new(top_left, bottom_right) { 58 | let color = if p == center_p { 59 | (255, 255, 0) 60 | } else if let Some(wall_idx) = map.observed_tile[p].wall.idx() { 61 | gobj::get_obj(wall_idx).symbol_color 62 | } else if map.observed_tile[p].tile { 63 | gobj::get_obj(map.tile[p].main_tile()).symbol_color 64 | } else { 65 | continue; 66 | }; 67 | let color = Color::RGB(color.0, color.1, color.2); 68 | 69 | let draw_rect = Rect::new( 70 | p.0 * RECT_SIZE_I - dx, 71 | p.1 * RECT_SIZE_I - dy, 72 | RECT_SIZE, 73 | RECT_SIZE, 74 | ); 75 | canvas.set_draw_color(color); 76 | try_sdl!(canvas.fill_rect(draw_rect)); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /rusted-ruins/src/window/misc_window.rs: -------------------------------------------------------------------------------- 1 | use super::commonuse::*; 2 | use super::widget::*; 3 | use common::objholder::CharaTemplateIdx; 4 | 5 | /// Displays one image 6 | pub struct ImageWindow { 7 | rect: Rect, 8 | image: ImageWidget, 9 | } 10 | 11 | impl ImageWindow { 12 | pub fn chara(rect: Rect, chara: CharaTemplateIdx) -> ImageWindow { 13 | use common::basic::TILE_SIZE; 14 | ImageWindow { 15 | rect, 16 | image: ImageWidget::chara(Rect::new(0, 0, TILE_SIZE, TILE_SIZE), chara), 17 | } 18 | } 19 | } 20 | 21 | impl Window for ImageWindow { 22 | fn draw( 23 | &mut self, 24 | context: &mut Context<'_, '_, '_, '_>, 25 | _game: &Game, 26 | _anim: Option<(&Animation, u32)>, 27 | ) { 28 | draw_window_border(context, self.rect); 29 | self.image.draw(context); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /rusted-ruins/src/window/msg_dialog.rs: -------------------------------------------------------------------------------- 1 | use super::choose_window::{ChooseWindow, DefaultBehavior}; 2 | use super::commonuse::*; 3 | use super::text_window::TextWindow; 4 | use super::winpos::{WindowHPos, WindowPos, WindowVPos}; 5 | use crate::config::UI_CFG; 6 | use crate::game::DoPlayerAction; 7 | 8 | pub struct MsgDialog { 9 | text_win: TextWindow, 10 | choose_win: ChooseWindow, 11 | action_callback: Box, u32) -> DialogResult + 'static>, 12 | } 13 | 14 | impl MsgDialog { 15 | // pub fn new(msg: &str, choices: Vec, f: F) -> MsgDialog 16 | // where 17 | // F: FnMut(&mut DoPlayerAction, u32) -> DialogResult + 'static, 18 | // { 19 | // let rect = UI_CFG.msg_dialog.rect.into(); 20 | // let text_win = TextWindow::new(rect, msg); 21 | // let winpos = WindowPos::new( 22 | // WindowHPos::RightX(rect.right()), 23 | // WindowVPos::TopMargin(rect.bottom() + UI_CFG.gap_len_between_dialogs), 24 | // ); 25 | // MsgDialog { 26 | // text_win, 27 | // choose_win: ChooseWindow::new(winpos, choices, None), 28 | // action_callback: Box::new(f), 29 | // } 30 | // } 31 | 32 | pub fn with_yesno(msg: &str, f: F) -> MsgDialog 33 | where 34 | F: FnMut(&mut DoPlayerAction<'_>, u32) -> DialogResult + 'static, 35 | { 36 | let rect = UI_CFG.msg_dialog.rect.into(); 37 | let text_win = TextWindow::new(rect, msg); 38 | let winpos = WindowPos::new( 39 | WindowHPos::RightX(rect.right()), 40 | WindowVPos::TopMargin(rect.bottom() + UI_CFG.gap_len_between_dialogs), 41 | ); 42 | MsgDialog { 43 | text_win, 44 | choose_win: ChooseWindow::with_yesno(winpos, DefaultBehavior::Close), 45 | action_callback: Box::new(f), 46 | } 47 | } 48 | } 49 | 50 | impl Window for MsgDialog { 51 | fn draw( 52 | &mut self, 53 | context: &mut Context<'_, '_, '_, '_>, 54 | game: &Game, 55 | anim: Option<(&Animation, u32)>, 56 | ) { 57 | self.text_win.draw(context, game, anim); 58 | let rect = self.text_win.get_rect(); 59 | let winpos = WindowPos::new( 60 | WindowHPos::RightX(rect.right()), 61 | WindowVPos::TopMargin(rect.bottom() + UI_CFG.gap_len_between_dialogs), 62 | ); 63 | self.choose_win.set_winpos(winpos); 64 | self.choose_win.draw(context, game, anim); 65 | } 66 | } 67 | 68 | impl DialogWindow for MsgDialog { 69 | fn process_command(&mut self, command: &Command, pa: &mut DoPlayerAction<'_>) -> DialogResult { 70 | if *command == Command::Cancel { 71 | return DialogResult::Close; 72 | } 73 | 74 | if let DialogResult::CloseWithValue(DialogCloseValue::Index(n)) = 75 | self.choose_win.process_command(command, pa) 76 | { 77 | // An choice is choosed 78 | return (self.action_callback)(pa, n); 79 | } 80 | DialogResult::Continue 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /rusted-ruins/src/window/progress_bar.rs: -------------------------------------------------------------------------------- 1 | use super::commonuse::*; 2 | use super::widget::{GaugeColorMode, GaugeWidget}; 3 | use crate::config::UI_CFG; 4 | use common::gamedata::*; 5 | 6 | pub struct ProgressBar { 7 | gauge: GaugeWidget, 8 | } 9 | 10 | impl ProgressBar { 11 | pub fn new() -> ProgressBar { 12 | let rect: Rect = UI_CFG.progress_bar.rect.into(); 13 | let gauge = GaugeWidget::new(rect, 0.0, 1.0, GaugeColorMode::Work); 14 | 15 | ProgressBar { gauge } 16 | } 17 | } 18 | 19 | impl Window for ProgressBar { 20 | fn draw( 21 | &mut self, 22 | context: &mut Context<'_, '_, '_, '_>, 23 | game: &Game, 24 | anim: Option<(&Animation, u32)>, 25 | ) { 26 | let player = game.gd.chara.get(CharaId::Player); 27 | let mut in_work = false; 28 | for status in &player.status { 29 | if let CharaStatus::Work { .. } = status { 30 | in_work = true; 31 | break; 32 | } 33 | } 34 | 35 | if !in_work { 36 | return; 37 | } 38 | 39 | if let Some((Animation::Work { ratio, .. }, _)) = anim { 40 | self.gauge.set_value(1.0 - *ratio); 41 | } 42 | 43 | context.set_viewport(None); 44 | self.gauge.draw(context); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /rusted-ruins/src/window/register_shortcut_dialog.rs: -------------------------------------------------------------------------------- 1 | use super::choose_window::{ChooseWindow, DefaultBehavior}; 2 | use super::commonuse::*; 3 | use crate::config::UI_CFG; 4 | use common::gamedata::ActionShortcut; 5 | 6 | pub struct RegisterShortcutDialog { 7 | shortcut: ActionShortcut, 8 | choose_win: ChooseWindow, 9 | } 10 | 11 | impl RegisterShortcutDialog { 12 | pub fn new(shortcut: ActionShortcut) -> Self { 13 | let n_shortcut = UI_CFG.toolbar.n_shortcut; 14 | let choices = (0..n_shortcut) 15 | .map(|i| { 16 | let i = if i != 9 { i + 1 } else { 0 }; 17 | ui_txt_format!("register_shortcut"; i=i) 18 | }) 19 | .collect(); 20 | let choose_win = ChooseWindow::new(WindowPos::CENTER, choices, DefaultBehavior::Close); 21 | Self { 22 | shortcut, 23 | choose_win, 24 | } 25 | } 26 | } 27 | 28 | impl Window for RegisterShortcutDialog { 29 | fn draw( 30 | &mut self, 31 | context: &mut Context<'_, '_, '_, '_>, 32 | game: &Game, 33 | anim: Option<(&Animation, u32)>, 34 | ) { 35 | self.choose_win.draw(context, game, anim); 36 | } 37 | } 38 | 39 | impl DialogWindow for RegisterShortcutDialog { 40 | fn process_command(&mut self, command: &Command, pa: &mut DoPlayerAction<'_>) -> DialogResult { 41 | if *command == Command::Cancel { 42 | return DialogResult::Close; 43 | } 44 | 45 | match self.choose_win.process_command(command, pa) { 46 | DialogResult::CloseWithValue(v) => { 47 | if let DialogCloseValue::Index(choosed_answer) = v { 48 | pa.register_shortcut(self.shortcut, choosed_answer); 49 | } 50 | return DialogResult::Close; 51 | } 52 | DialogResult::Close => { 53 | return DialogResult::Close; 54 | } 55 | _ => (), 56 | } 57 | DialogResult::Continue 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /rusted-ruins/src/window/widget/border.rs: -------------------------------------------------------------------------------- 1 | use super::WidgetTrait; 2 | use crate::config::UI_CFG; 3 | use crate::context::*; 4 | use sdl2::pixels::Color; 5 | use sdl2::rect::Rect; 6 | 7 | pub struct HBorder { 8 | rect: Rect, 9 | light: Color, 10 | dark: Color, 11 | } 12 | 13 | impl HBorder { 14 | pub fn new(start: (i32, i32), len: u32) -> HBorder { 15 | let rect = Rect::new(start.0, start.1, len, 3); 16 | 17 | HBorder { 18 | rect, 19 | light: UI_CFG.color.border_light.into(), 20 | dark: UI_CFG.color.border_dark.into(), 21 | } 22 | } 23 | } 24 | 25 | impl WidgetTrait for HBorder { 26 | type Response = (); 27 | 28 | fn draw(&mut self, context: &mut Context<'_, '_, '_, '_>) { 29 | let canvas = &mut context.canvas; 30 | canvas.set_viewport(None); 31 | canvas.set_draw_color(self.light); 32 | try_sdl!(canvas.draw_line( 33 | (self.rect.x, self.rect.y + 1), 34 | (self.rect.x + self.rect.w, self.rect.y + 1) 35 | )); 36 | canvas.set_draw_color(self.dark); 37 | try_sdl!(canvas.draw_line( 38 | (self.rect.x, self.rect.y), 39 | (self.rect.x + self.rect.w, self.rect.y) 40 | )); 41 | try_sdl!(canvas.draw_line( 42 | (self.rect.x, self.rect.y + 2), 43 | (self.rect.x + self.rect.w, self.rect.y + 2) 44 | )); 45 | } 46 | } 47 | 48 | pub struct VBorder { 49 | rect: Rect, 50 | light: Color, 51 | dark: Color, 52 | } 53 | 54 | impl VBorder { 55 | pub fn new(start: (i32, i32), len: u32) -> VBorder { 56 | let rect = Rect::new(start.0, start.1, 3, len); 57 | 58 | VBorder { 59 | rect, 60 | light: UI_CFG.color.border_light.into(), 61 | dark: UI_CFG.color.border_dark.into(), 62 | } 63 | } 64 | } 65 | 66 | impl WidgetTrait for VBorder { 67 | type Response = (); 68 | 69 | fn draw(&mut self, context: &mut Context<'_, '_, '_, '_>) { 70 | let canvas = &mut context.canvas; 71 | canvas.set_viewport(None); 72 | canvas.set_draw_color(self.light); 73 | try_sdl!(canvas.draw_line( 74 | (self.rect.x + 1, self.rect.y), 75 | (self.rect.x + 1, self.rect.y + self.rect.h) 76 | )); 77 | canvas.set_draw_color(self.dark); 78 | try_sdl!(canvas.draw_line( 79 | (self.rect.x, self.rect.y), 80 | (self.rect.x, self.rect.y + self.rect.h) 81 | )); 82 | try_sdl!(canvas.draw_line( 83 | (self.rect.x + 2, self.rect.y), 84 | (self.rect.x + 2, self.rect.y + self.rect.h) 85 | )); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /rusted-ruins/src/window/widget/image.rs: -------------------------------------------------------------------------------- 1 | use super::WidgetTrait; 2 | use crate::context::*; 3 | use common::gamedata::Item; 4 | use common::gamedata::*; 5 | use common::gobj; 6 | use common::objholder::*; 7 | use sdl2::rect::Rect; 8 | 9 | /// Image widget. 10 | pub struct ImageWidget { 11 | rect: Rect, 12 | idx: ImageIdx, 13 | centering: bool, 14 | } 15 | 16 | pub enum ImageIdx { 17 | UiImg(UiImgIdx), 18 | Chara(CharaTemplateIdx), 19 | Item((ItemIdx, u32)), 20 | } 21 | 22 | impl ImageIdx { 23 | pub fn item(item: &Item) -> Self { 24 | let mut variation = 0; 25 | for attr in &item.attrs { 26 | if let ItemAttr::ImageVariation(n) = attr { 27 | variation = *n; 28 | } 29 | } 30 | 31 | ImageIdx::Item((item.idx, variation)) 32 | } 33 | } 34 | 35 | impl ImageWidget { 36 | pub fn new>(rect: R, idx: ImageIdx) -> Self { 37 | let rect = rect.into(); 38 | ImageWidget { 39 | rect, 40 | idx, 41 | centering: true, 42 | } 43 | } 44 | 45 | /// Create image widget that show a UIImg 46 | pub fn ui_img>(rect: R, id: &str) -> Self { 47 | let idx: UiImgIdx = gobj::id_to_idx(id); 48 | 49 | Self::new(rect, ImageIdx::UiImg(idx)) 50 | } 51 | 52 | pub fn chara>(rect: R, chara_idx: CharaTemplateIdx) -> Self { 53 | Self::new(rect, ImageIdx::Chara(chara_idx)) 54 | } 55 | 56 | pub fn item>(rect: R, item: &Item) -> Self { 57 | Self::new(rect, ImageIdx::item(item)) 58 | } 59 | 60 | pub fn _set_rect>(&mut self, rect: R) { 61 | self.rect = rect.into(); 62 | } 63 | } 64 | 65 | impl WidgetTrait for ImageWidget { 66 | type Response = (); 67 | 68 | fn draw(&mut self, context: &mut Context<'_, '_, '_, '_>) { 69 | match self.idx { 70 | ImageIdx::UiImg(idx) => { 71 | if self.centering { 72 | context.render_tex_n_center(idx, self.rect, 0); 73 | } else { 74 | context.render_tex(idx, self.rect); 75 | } 76 | } 77 | ImageIdx::Chara(idx) => { 78 | if self.centering { 79 | context.render_tex_n_center(idx, self.rect, 0); 80 | } else { 81 | context.render_tex(idx, self.rect); 82 | } 83 | } 84 | ImageIdx::Item(idx) => { 85 | if self.centering { 86 | context.render_tex_n_center(idx.0, self.rect, 0); 87 | } else { 88 | context.render_tex(idx.0, self.rect); 89 | } 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /rusted-ruins/src/window/widget/mod.rs: -------------------------------------------------------------------------------- 1 | mod border; 2 | mod button; 3 | mod close_button; 4 | mod gauge; 5 | mod image; 6 | mod label; 7 | mod list; 8 | mod vscroll; 9 | 10 | use crate::context::*; 11 | use crate::game::Command; 12 | 13 | pub trait WidgetTrait { 14 | type Response; 15 | fn process_command(&mut self, _command: &Command) -> Option { 16 | None 17 | } 18 | fn draw(&mut self, context: &mut Context<'_, '_, '_, '_>); 19 | } 20 | 21 | pub trait MovableWidget: WidgetTrait { 22 | fn move_to(&mut self, x: i32, y: i32); 23 | } 24 | 25 | pub use self::border::*; 26 | pub use self::button::*; 27 | pub use self::close_button::*; 28 | pub use self::gauge::*; 29 | pub use self::image::*; 30 | pub use self::label::*; 31 | pub use self::list::*; 32 | pub use self::vscroll::*; 33 | -------------------------------------------------------------------------------- /rusted-ruins/src/window/winpos.rs: -------------------------------------------------------------------------------- 1 | //! Helper functions to calculate Window position 2 | #![allow(unused)] 3 | 4 | use crate::config::SCREEN_CFG; 5 | 6 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 7 | pub enum WindowHPos { 8 | Center, 9 | LeftMargin(i32), 10 | RightMargin(i32), 11 | RightX(i32), 12 | } 13 | 14 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 15 | pub enum WindowVPos { 16 | Center, 17 | TopMargin(i32), 18 | BottomMargin(i32), 19 | } 20 | 21 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 22 | pub struct WindowPos { 23 | pub h: WindowHPos, 24 | pub v: WindowVPos, 25 | } 26 | 27 | impl WindowPos { 28 | pub fn new(h: WindowHPos, v: WindowVPos) -> WindowPos { 29 | WindowPos { h, v } 30 | } 31 | 32 | pub fn from_left_top(x: i32, y: i32) -> WindowPos { 33 | WindowPos { 34 | h: WindowHPos::LeftMargin(x), 35 | v: WindowVPos::TopMargin(y), 36 | } 37 | } 38 | 39 | pub fn calc_left_top(&self, w: u32, h: u32) -> (i32, i32) { 40 | let parent_w = SCREEN_CFG.screen_w as i32; 41 | let parent_h = SCREEN_CFG.screen_h as i32; 42 | let w = w as i32; 43 | let h = h as i32; 44 | 45 | let x = match self.h { 46 | WindowHPos::Center => (parent_w - w) / 2, 47 | WindowHPos::LeftMargin(m) => m, 48 | WindowHPos::RightMargin(m) => parent_w - w - m, 49 | WindowHPos::RightX(x) => x - w, 50 | }; 51 | let y = match self.v { 52 | WindowVPos::Center => (parent_h - h) / 2, 53 | WindowVPos::TopMargin(m) => m, 54 | WindowVPos::BottomMargin(m) => parent_h - h - m, 55 | }; 56 | (x, y) 57 | } 58 | 59 | pub const CENTER: WindowPos = WindowPos { 60 | h: WindowHPos::Center, 61 | v: WindowVPos::Center, 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /script/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusted-ruins-script" 3 | version = "0.1.0" 4 | authors = ["T. Okubo "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | anyhow = "1" 9 | crossbeam-channel = "0.5" 10 | crossbeam-utils = "0.8.9" 11 | log = "0.4" 12 | nom = "7" 13 | once_cell = "1" 14 | regex = "1" 15 | thiserror = "1" 16 | 17 | rusted-ruins-common = { path = "../common", features = ["global_state_obj"] } 18 | rusted-ruins-rng = { path = "../rng" } 19 | 20 | [dependencies.rustpython-vm] 21 | git = "https://github.com/RustPython/RustPython.git" 22 | tag = "v0.2.0" 23 | default-features = false 24 | features = ["threading", "compiler", "parser"] -------------------------------------------------------------------------------- /script/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rerun-if-changed=python/"); 3 | } 4 | -------------------------------------------------------------------------------- /script/python/script_yield.py: -------------------------------------------------------------------------------- 1 | def _g(): 2 | yield 1 3 | 4 | 5 | _GeneratorType = type(_g()) 6 | 7 | 8 | class ScriptYield: 9 | def talk(text_id, choices=[], target_chara=None): 10 | return { 11 | "tag": "Talk", 12 | "talk": { 13 | "text_id": text_id, 14 | "choices": choices, 15 | "target_chara": target_chara, 16 | }, 17 | } 18 | 19 | def shop_buy(): 20 | return {"tag": "ShopBuy"} 21 | 22 | def shop_sell(): 23 | return {"tag": "ShopSell"} 24 | 25 | def quest_offer(): 26 | return {"tag": "QuestOffer"} 27 | 28 | def quest_report(): 29 | return {"tag": "QuestReport"} 30 | 31 | def _get_next_script_yield(): 32 | if not isinstance(_rrscript_gen, _GeneratorType): 33 | return None 34 | try: 35 | return _rrscript_gen.__next__() 36 | except StopIteration: 37 | return None 38 | -------------------------------------------------------------------------------- /script/src/error.rs: -------------------------------------------------------------------------------- 1 | use rustpython_vm::{builtins::PyBaseExceptionRef, compiler::CompileError, VirtualMachine}; 2 | use thiserror::Error; 3 | 4 | #[derive(Debug, Error)] 5 | pub enum Error { 6 | #[error("python exception:\n{0}")] 7 | Python(String), 8 | #[error("python compile failed:\n{0}")] 9 | Compile(#[from] CompileError), 10 | #[error("object not found")] 11 | NoObject(String), 12 | } 13 | 14 | impl Error { 15 | pub fn from_py(vm: &VirtualMachine, e: PyBaseExceptionRef) -> Self { 16 | let mut s = String::new(); 17 | let _ = vm.write_exception(&mut s, &e); 18 | Error::Python(s) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /script/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn( 2 | rust_2018_compatibility, 3 | rust_2018_idioms, 4 | future_incompatible, 5 | nonstandard_style 6 | )] 7 | #![allow(clippy::comparison_chain, clippy::type_complexity)] 8 | 9 | extern crate rusted_ruins_common as common; 10 | 11 | mod engine; 12 | mod error; 13 | mod message; 14 | mod parse; 15 | mod random; 16 | mod rr; 17 | 18 | pub use engine::ScriptEngine; 19 | pub use error::Error; 20 | pub use message::*; 21 | -------------------------------------------------------------------------------- /script/src/message.rs: -------------------------------------------------------------------------------- 1 | use common::gamedata::{GameData, SkillKind, Value}; 2 | 3 | use crate::rr::ScriptMethodErr; 4 | 5 | pub(crate) enum ScriptMessage { 6 | Finish, 7 | Fail, 8 | UiRequest(UiRequest), 9 | Exec(Box Result + Send + 'static>), 10 | Method(GameMethod), 11 | } 12 | 13 | pub enum ScriptResult { 14 | Finish, 15 | UiRequest(UiRequest), 16 | } 17 | 18 | #[derive(Clone, PartialEq, Eq, Debug)] 19 | pub enum UiRequest { 20 | Talk { talk: TalkText }, 21 | ShopBuy, 22 | ShopSell, 23 | QuestOffer, 24 | QuestReport, 25 | InstallAbilitySlot, 26 | InstallExtendSlot, 27 | } 28 | 29 | #[derive(Clone, PartialEq, Eq, Debug)] 30 | pub struct TalkText { 31 | pub text_id: String, 32 | pub choices: Vec, 33 | pub target_chara: Option, 34 | } 35 | 36 | #[derive(Clone, PartialEq, Eq, Debug)] 37 | pub enum GameMethod { 38 | CompleteCustomQuest { id: String }, 39 | CustomQuestStarted { id: String }, 40 | GenDungeons, 41 | GenPartyChara { id: String, lv: u32 }, 42 | HasEmptyForParty, 43 | NumberOfItem { id: String }, 44 | ReceiveItem { id: String, n: u32 }, 45 | ReceiveMoney { amount: i64 }, 46 | RemoveItem { id: String, n: u32 }, 47 | ResurrectPartyMembers, 48 | StartCustomQuest { id: String, phase: String }, 49 | SkillLevel { skill_kind: SkillKind }, 50 | LearnSkill { skill_kind: SkillKind }, 51 | } 52 | -------------------------------------------------------------------------------- /script/src/random.rs: -------------------------------------------------------------------------------- 1 | use rustpython_vm::pymodule; 2 | 3 | pub(crate) use _random::make_module; 4 | 5 | #[pymodule(name = "random")] 6 | mod _random { 7 | use rusted_ruins_rng as rng; 8 | 9 | #[pyfunction] 10 | fn randint(a: u64, b: u64) -> u64 { 11 | rng::gen_range(a..=b) 12 | } 13 | } 14 | --------------------------------------------------------------------------------