diff --git a/.github/workflows/3ds.yml b/.github/workflows/3ds.yml index e1aaeabde..fa5743f1e 100644 --- a/.github/workflows/3ds.yml +++ b/.github/workflows/3ds.yml @@ -58,14 +58,14 @@ jobs: - name: Upload 3dsx Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx.3dsx path: ./build/devilutionx.3dsx - name: Upload cia Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx.cia path: ./build/devilutionx.cia diff --git a/.github/workflows/Android.yml b/.github/workflows/Android.yml index 9b7cb77a3..245f887b4 100644 --- a/.github/workflows/Android.yml +++ b/.github/workflows/Android.yml @@ -41,7 +41,7 @@ jobs: $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "cmake;3.31.0" - name: Cache CMake build folder - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: android-project/app/.cxx key: ${{ github.workflow }}-v5-${{ github.sha }} @@ -54,7 +54,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-debug.apk path: android-project/app/build/outputs/apk/debug/app-debug.apk diff --git a/.github/workflows/Linux_aarch64.yml b/.github/workflows/Linux_aarch64.yml index 7866affd9..249bca31a 100644 --- a/.github/workflows/Linux_aarch64.yml +++ b/.github/workflows/Linux_aarch64.yml @@ -37,7 +37,7 @@ jobs: run: Packaging/nix/debian-cross-aarch64-prep.sh - name: Cache CMake build folder - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: build key: ${{ github.workflow }}-v8-${{ github.sha }} @@ -66,7 +66,7 @@ jobs: - name: Upload Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-aarch64-linux-gnu.tar.xz path: devilutionx-aarch64-linux-gnu.tar.xz @@ -74,7 +74,7 @@ jobs: # AppImage cross-packaging is not implemented yet. # - name: Upload AppImage # if: ${{ !env.ACT }} - # uses: actions/upload-artifact@v5 + # uses: actions/upload-artifact@v6 # with: # name: devilutionx-aarch64-linux-gnu.appimage # path: devilutionx-aarch64-linux-gnu.appimage diff --git a/.github/workflows/Linux_x86.yml b/.github/workflows/Linux_x86.yml index 85043d2c0..29d899ad0 100644 --- a/.github/workflows/Linux_x86.yml +++ b/.github/workflows/Linux_x86.yml @@ -37,7 +37,7 @@ jobs: run: Packaging/nix/debian-cross-i386-prep.sh - name: Cache CMake build folder - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: build key: ${{ github.workflow }}-v7-${{ github.sha }} @@ -67,7 +67,7 @@ jobs: - name: Upload Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-i386-linux-gnu.tar.xz path: devilutionx-i386-linux-gnu.tar.xz @@ -75,7 +75,7 @@ jobs: # AppImage cross-packaging is not implemented yet. # - name: Upload AppImage # if: ${{ !env.ACT }} - # uses: actions/upload-artifact@v5 + # uses: actions/upload-artifact@v6 # with: # name: devilutionx-i386-linux-gnu.appimage # path: devilutionx-i386-linux-gnu.appimage diff --git a/.github/workflows/Linux_x86_64.yml b/.github/workflows/Linux_x86_64.yml index a91a3888f..0e992dc8f 100644 --- a/.github/workflows/Linux_x86_64.yml +++ b/.github/workflows/Linux_x86_64.yml @@ -37,7 +37,7 @@ jobs: run: Packaging/nix/debian-host-prep.sh - name: Cache CMake build folder - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: build key: ${{ github.workflow }}-v7-${{ github.sha }} @@ -65,14 +65,14 @@ jobs: - name: Upload Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-x86_64-linux-gnu.tar.xz path: devilutionx-x86_64-linux-gnu.tar.xz - name: Upload AppImage if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-x86_64-linux-gnu.appimage path: devilutionx-x86_64-linux-gnu.appimage diff --git a/.github/workflows/Linux_x86_64_SDL1.yml b/.github/workflows/Linux_x86_64_SDL1.yml index d01541f3f..92b692f17 100644 --- a/.github/workflows/Linux_x86_64_SDL1.yml +++ b/.github/workflows/Linux_x86_64_SDL1.yml @@ -32,7 +32,7 @@ jobs: sudo apt-get install -y cmake file g++ git libfmt-dev libsdl1.2-dev libsodium-dev libpng-dev libbz2-dev rpm smpq - name: Cache CMake build folder - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: build key: ${{ github.workflow }}-v3-${{ github.sha }} @@ -54,7 +54,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx_linux_x86_64_SDL1.tar.xz path: devilutionx.tar.xz diff --git a/.github/workflows/Linux_x86_64_SDL3_test.yml b/.github/workflows/Linux_x86_64_SDL3_test.yml index 317579348..e5a4974df 100644 --- a/.github/workflows/Linux_x86_64_SDL3_test.yml +++ b/.github/workflows/Linux_x86_64_SDL3_test.yml @@ -30,10 +30,10 @@ jobs: - name: Install dependencies run: | sudo apt-get update -y - sudo apt-get install -y cmake curl g++ git libgtest-dev libgmock-dev libbenchmark-dev libfmt-dev libsodium-dev libpng-dev libbz2-dev wget + sudo apt-get install -y cmake curl g++ git libgtest-dev libgmock-dev libbenchmark-dev libfmt-dev libsodium-dev libpng-dev libbz2-dev libasound2-dev libxcursor-dev libxi-dev libxrandr-dev libxss-dev libxtst-dev libxkbcommon-dev wget - name: Cache CMake build folder - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: build key: ${{ github.workflow }}-v4-${{ github.sha }} diff --git a/.github/workflows/Linux_x86_64_test.yml b/.github/workflows/Linux_x86_64_test.yml index a3f917dc6..43a09e0b1 100644 --- a/.github/workflows/Linux_x86_64_test.yml +++ b/.github/workflows/Linux_x86_64_test.yml @@ -33,7 +33,7 @@ jobs: sudo apt-get install -y cmake curl g++ git lcov libgtest-dev libgmock-dev libbenchmark-dev libfmt-dev libsdl2-dev libsodium-dev libpng-dev libbz2-dev wget - name: Cache CMake build folder - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: build key: ${{ github.workflow }}-v3-${{ github.sha }} diff --git a/.github/workflows/PS4.yml b/.github/workflows/PS4.yml index 2ba0b6833..34296449e 100644 --- a/.github/workflows/PS4.yml +++ b/.github/workflows/PS4.yml @@ -53,7 +53,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-ps4.pkg path: build-ps4/devilutionx.pkg diff --git a/.github/workflows/PS5.yml b/.github/workflows/PS5.yml index 8431b2746..11f896eb6 100644 --- a/.github/workflows/PS5.yml +++ b/.github/workflows/PS5.yml @@ -48,7 +48,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-ps5.zip path: build-ps5/devilutionx-ps5.zip diff --git a/.github/workflows/Windows9x_MinGW.yml b/.github/workflows/Windows9x_MinGW.yml index 3b1161dbb..41b7a13fd 100644 --- a/.github/workflows/Windows9x_MinGW.yml +++ b/.github/workflows/Windows9x_MinGW.yml @@ -53,7 +53,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: path: devilutionx-win9x.zip diff --git a/.github/workflows/Windows_MSVC_x64.yml b/.github/workflows/Windows_MSVC_x64.yml index 6decc989f..eec021210 100644 --- a/.github/workflows/Windows_MSVC_x64.yml +++ b/.github/workflows/Windows_MSVC_x64.yml @@ -61,7 +61,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx.exe path: | diff --git a/.github/workflows/Windows_MinGW_x64.yml b/.github/workflows/Windows_MinGW_x64.yml index 5b5210ade..af56a0066 100644 --- a/.github/workflows/Windows_MinGW_x64.yml +++ b/.github/workflows/Windows_MinGW_x64.yml @@ -44,7 +44,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx_x64.zip path: build/devilutionx.zip diff --git a/.github/workflows/Windows_MinGW_x86.yml b/.github/workflows/Windows_MinGW_x86.yml index 4b2abeb34..2d93fd1ed 100644 --- a/.github/workflows/Windows_MinGW_x86.yml +++ b/.github/workflows/Windows_MinGW_x86.yml @@ -44,7 +44,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx_x86.zip path: build/devilutionx.zip diff --git a/.github/workflows/Windows_XP_32bit.yml b/.github/workflows/Windows_XP_32bit.yml index ba3425572..9039b2649 100644 --- a/.github/workflows/Windows_XP_32bit.yml +++ b/.github/workflows/Windows_XP_32bit.yml @@ -42,7 +42,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: path: devilutionx-windows-xp-32bit.zip diff --git a/.github/workflows/amiga-m68k.yml b/.github/workflows/amiga-m68k.yml index e870b5deb..38188ce93 100644 --- a/.github/workflows/amiga-m68k.yml +++ b/.github/workflows/amiga-m68k.yml @@ -50,7 +50,7 @@ jobs: - name: Upload Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx_m68k path: ./build/devilutionx diff --git a/.github/workflows/iOS.yml b/.github/workflows/iOS.yml index 24b57a217..8737cbe99 100644 --- a/.github/workflows/iOS.yml +++ b/.github/workflows/iOS.yml @@ -36,7 +36,7 @@ jobs: fetch-depth: 0 - name: Cache CMake build folder - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: build key: ${{ github.workflow }}-v5-${{ github.sha }} @@ -61,7 +61,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-iOS.ipa path: build/devilutionx-iOS.ipa diff --git a/.github/workflows/macOS_arm64.yml b/.github/workflows/macOS_arm64.yml index e98ee990a..309935acf 100644 --- a/.github/workflows/macOS_arm64.yml +++ b/.github/workflows/macOS_arm64.yml @@ -35,11 +35,11 @@ jobs: run: brew bundle install - name: Cache CMake build folder - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: build - key: ${{ github.workflow }}-v1-${{ github.sha }} - restore-keys: ${{ github.workflow }}-v1- + key: ${{ github.workflow }}-v2-${{ github.sha }} + restore-keys: ${{ github.workflow }}-v2- - name: Clean previous DMG working-directory: ${{github.workspace}} @@ -58,7 +58,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-arm64-macOS.dmg path: build/devilutionx-arm64-macOS.dmg diff --git a/.github/workflows/macOS_x86_64.yml b/.github/workflows/macOS_x86_64.yml index 6b209a390..088044cdf 100644 --- a/.github/workflows/macOS_x86_64.yml +++ b/.github/workflows/macOS_x86_64.yml @@ -35,11 +35,11 @@ jobs: run: brew bundle install - name: Cache CMake build folder - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: build - key: ${{ github.workflow }}-v3-${{ github.sha }} - restore-keys: ${{ github.workflow }}-v3- + key: ${{ github.workflow }}-v4-${{ github.sha }} + restore-keys: ${{ github.workflow }}-v4- - name: Clean previous DMG working-directory: ${{github.workspace}} @@ -58,7 +58,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-x86_64-macOS.dmg path: build/devilutionx-x86_64-macOS.dmg diff --git a/.github/workflows/miyoo_mini_release.yml b/.github/workflows/miyoo_mini_release.yml index 3aa9512f6..faa09e0c7 100644 --- a/.github/workflows/miyoo_mini_release.yml +++ b/.github/workflows/miyoo_mini_release.yml @@ -31,14 +31,14 @@ jobs: - name: Upload-OnionOS-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-miyoo-mini-onion-os.zip path: build-miyoo-mini/devilutionx-miyoo-mini-onion-os.zip - name: Upload-miniUI-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-miyoo-mini-miniui.zip path: build-miyoo-mini/devilutionx-miyoo-mini-miniui.zip diff --git a/.github/workflows/opendingux_release.yml b/.github/workflows/opendingux_release.yml index 32faf32e1..28c94044c 100644 --- a/.github/workflows/opendingux_release.yml +++ b/.github/workflows/opendingux_release.yml @@ -39,7 +39,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-rg350.opk.zip path: build-rg350/devilutionx-rg350.opk @@ -77,7 +77,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-lepus.opk.zip path: build-lepus/devilutionx-lepus.opk diff --git a/.github/workflows/retrofw_release.yml b/.github/workflows/retrofw_release.yml index a128ad0cc..5072dc977 100644 --- a/.github/workflows/retrofw_release.yml +++ b/.github/workflows/retrofw_release.yml @@ -39,7 +39,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-retrofw.opk.zip path: build-retrofw/devilutionx-retrofw.opk diff --git a/.github/workflows/s390x_qemu_big_endian_tests.yml b/.github/workflows/s390x_qemu_big_endian_tests.yml index 61bd68426..cc5a67d6e 100644 --- a/.github/workflows/s390x_qemu_big_endian_tests.yml +++ b/.github/workflows/s390x_qemu_big_endian_tests.yml @@ -23,7 +23,7 @@ jobs: fetch-depth: 0 - name: Cache .ccache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .ccache key: ${{ github.workflow }}-v1-${{ github.sha }} diff --git a/.github/workflows/src_dist_release.yml b/.github/workflows/src_dist_release.yml index b96077538..227560e6e 100644 --- a/.github/workflows/src_dist_release.yml +++ b/.github/workflows/src_dist_release.yml @@ -32,7 +32,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-src.tar.xz path: devilutionx-src.tar.xz @@ -63,7 +63,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-src-fully-vendored.tar.xz path: devilutionx-src-fully-vendored.tar.xz diff --git a/.github/workflows/switch.yml b/.github/workflows/switch.yml index 5fdefb139..10bea56e3 100644 --- a/.github/workflows/switch.yml +++ b/.github/workflows/switch.yml @@ -44,7 +44,7 @@ jobs: - name: Upload Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx.nro path: ./build/devilutionx.nro diff --git a/.github/workflows/vita.yml b/.github/workflows/vita.yml index 4d66baae0..9e05806e8 100644 --- a/.github/workflows/vita.yml +++ b/.github/workflows/vita.yml @@ -53,7 +53,7 @@ jobs: - name: Upload Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx.vpk path: ./build/devilutionx.vpk diff --git a/.github/workflows/xbox_nxdk.yml b/.github/workflows/xbox_nxdk.yml index 9051921ab..1721ef66f 100644 --- a/.github/workflows/xbox_nxdk.yml +++ b/.github/workflows/xbox_nxdk.yml @@ -57,7 +57,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-xbox path: build-xbox/pkg/ diff --git a/.github/workflows/xbox_one.yml b/.github/workflows/xbox_one.yml index 05147f193..180306902 100644 --- a/.github/workflows/xbox_one.yml +++ b/.github/workflows/xbox_one.yml @@ -68,7 +68,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: devilutionx-xbox-one-series if-no-files-found: error diff --git a/3rdParty/SDL3/CMakeLists.txt b/3rdParty/SDL3/CMakeLists.txt index c327d7a84..51d3fad54 100644 --- a/3rdParty/SDL3/CMakeLists.txt +++ b/3rdParty/SDL3/CMakeLists.txt @@ -16,7 +16,7 @@ include(functions/FetchContent_ExcludeFromAll_backport) include(FetchContent) FetchContent_Declare(SDL3 - URL https://github.com/libsdl-org/SDL/archive/02c4478f9364ac9ba8c0b157c01e47c2f059552c.tar.gz - URL_HASH SHA256=1c8e9369ef6c67b6ca4102cc957846966815de56924b24a8a28feeac344a506a + URL https://github.com/libsdl-org/SDL/archive/f173fd28f04cb64ae054d6a97edb5d33925f539b.tar.gz + URL_HASH SHA256=f7501d84c1a7f168567c002f4e1db4f220c4a34c51f7fa7d199962d0ed5fb42c ) FetchContent_MakeAvailable_ExcludeFromAll(SDL3) diff --git a/3rdParty/SDL3_mixer/CMakeLists.txt b/3rdParty/SDL3_mixer/CMakeLists.txt index a6e10da95..b4bd99a7f 100644 --- a/3rdParty/SDL3_mixer/CMakeLists.txt +++ b/3rdParty/SDL3_mixer/CMakeLists.txt @@ -27,7 +27,7 @@ set(SDLMIXER_VORBIS_TREMOR OFF) set(SDLMIXER_WAVPACK OFF) FetchContent_Declare_ExcludeFromAll(SDL_mixer - URL https://github.com/libsdl-org/SDL_mixer/archive/4b4b05949208ad0a49832ed34f59beeae6d6c2da.tar.gz - URL_HASH SHA256=744bbe25e127121a87b070f9211794b38e71db7e6ea497757bf45ac85525a905 + URL https://github.com/libsdl-org/SDL_mixer/archive/7d37755016f0952c32c9483c556d8608da7ee82f.tar.gz + URL_HASH SHA256=2fa63f1eb623e3acd0012a461771eb93332e2026205f9487da3a3a75bc790111 ) FetchContent_MakeAvailable_ExcludeFromAll(SDL_mixer) diff --git a/3rdParty/bzip2/CMakeLists.txt b/3rdParty/bzip2/CMakeLists.txt index c4a6ff0f8..9cd978f9a 100644 --- a/3rdParty/bzip2/CMakeLists.txt +++ b/3rdParty/bzip2/CMakeLists.txt @@ -3,7 +3,7 @@ include(functions/FetchContent_ExcludeFromAll_backport) include(FetchContent) FetchContent_Declare_ExcludeFromAll(bzip2 - GIT_REPOSITORY https://sourceware.org/git/bzip2 + GIT_REPOSITORY https://gitlab.com/bzip2/bzip2 GIT_TAG bzip2-1.0.8 ) FetchContent_MakeAvailable_ExcludeFromAll(bzip2) diff --git a/CMake/Assets.cmake b/CMake/Assets.cmake index 9f37e2721..e3ead57bc 100644 --- a/CMake/Assets.cmake +++ b/CMake/Assets.cmake @@ -5,7 +5,7 @@ if(NOT DEFINED DEVILUTIONX_ASSETS_OUTPUT_DIRECTORY) set(DEVILUTIONX_ASSETS_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/assets") endif() -set(devilutionx_langs bg cs da de el es et fi fr hr hu it ja ko pl pt_BR ro ru uk sv tr zh_CN zh_TW) +set(devilutionx_langs be bg cs da de el es et fi fr hr hu it ja ko pl pt_BR ro ru uk sv tr zh_CN zh_TW) if(USE_GETTEXT_FROM_VCPKG) # vcpkg doesn't add its own tools directory to the search path list(APPEND Gettext_ROOT ${CMAKE_CURRENT_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/tools/gettext/bin) @@ -72,6 +72,7 @@ set(devilutionx_assets fonts/12-02.clx fonts/12-03.clx fonts/12-04.clx + fonts/12-05.clx fonts/12-1f4.clx fonts/12-1f6.clx fonts/12-1f9.clx @@ -90,6 +91,7 @@ set(devilutionx_assets fonts/24-02.clx fonts/24-03.clx fonts/24-04.clx + fonts/24-05.clx fonts/24-1f4.clx fonts/24-1f6.clx fonts/24-1f9.clx @@ -101,6 +103,7 @@ set(devilutionx_assets fonts/30-02.clx fonts/30-03.clx fonts/30-04.clx + fonts/30-05.clx fonts/30-20.clx fonts/30-e0.clx fonts/42-00.clx @@ -108,12 +111,14 @@ set(devilutionx_assets fonts/42-02.clx fonts/42-03.clx fonts/42-04.clx + fonts/42-05.clx fonts/42-20.clx fonts/46-00.clx fonts/46-01.clx fonts/46-02.clx fonts/46-03.clx fonts/46-04.clx + fonts/46-05.clx fonts/46-20.clx fonts/black.trn fonts/blue.trn @@ -153,7 +158,10 @@ set(devilutionx_assets lua_internal/get_lua_function_signature.lua lua/devilutionx/events.lua lua/inspect.lua + lua/mods/adria_refills_mana/init.lua lua/mods/clock/init.lua + "lua/mods/Floating Numbers - Damage/init.lua" + "lua/mods/Floating Numbers - XP/init.lua" lua/repl_prelude.lua plrgfx/warrior/whu/whufm.trn plrgfx/warrior/whu/whulm.trn @@ -280,3 +288,4 @@ else() add_dependencies(libdevilutionx devilutionx_copied_assets) endif() endif() + diff --git a/CMake/Tests.cmake b/CMake/Tests.cmake index bb37b85b5..95328c02a 100644 --- a/CMake/Tests.cmake +++ b/CMake/Tests.cmake @@ -170,6 +170,16 @@ if(DEVILUTIONX_SCREENSHOT_FORMAT STREQUAL DEVILUTIONX_SCREENSHOT_FORMAT_PNG AND kerning_fit_spacing__align_right.png vertical_overflow.png vertical_overflow-colors.png + cursor-start.png + cursor-middle.png + cursor-end.png + multiline_cursor-end_first_line.png + multiline_cursor-start_second_line.png + multiline_cursor-middle_second_line.png + multiline_cursor-end_second_line.png + highlight-partial.png + highlight-full.png + multiline_highlight.png SRC_PREFIX test/fixtures/text_render_integration_test/ OUTPUT_DIR "${DEVILUTIONX_TEST_FIXTURES_OUTPUT_DIRECTORY}/text_render_integration_test" OUTPUT_VARIABLE _text_render_integration_test_fixtures diff --git a/README.md b/README.md index 20e1afe29..59b6c13b7 100644 --- a/README.md +++ b/README.md @@ -52,9 +52,9 @@ If you want to help test the latest development version (make sure to back up yo [![Linux x86](https://github.com/diasurgical/devilutionX/actions/workflows/Linux_x86.yml/badge.svg)](https://github.com/diasurgical/devilutionX/actions/workflows/Linux_x86.yml?query=branch%3Amaster) [![Linux x86_64 SDL1](https://github.com/diasurgical/devilutionX/actions/workflows/Linux_x86_64_SDL1.yml/badge.svg)](https://github.com/diasurgical/devilutionX/actions/workflows/Linux_x86_64_SDL1.yml?query=branch%3Amaster) [![macOS x86_64](https://github.com/diasurgical/devilutionX/actions/workflows/macOS_x86_64.yml/badge.svg)](https://github.com/diasurgical/devilutionX/actions/workflows/macOS_x86_64.yml?query=branch%3Amaster) -[![Windows MSVC x64](https://github.com/diasurgical/devilutionX/actions/workflows/Windows_MSVC_x64.yml/badge.svg)](https://github.com/diasurgical/devilutionX/actions/workflows/Windows_MSVC_x64.yml?query=branch%3Amaster) [![Windows MinGW x64](https://github.com/diasurgical/devilutionX/actions/workflows/Windows_MinGW_x64.yml/badge.svg)](https://github.com/diasurgical/devilutionX/actions/workflows/Windows_MinGW_x64.yml?query=branch%3Amaster) [![Windows MinGW x86](https://github.com/diasurgical/devilutionX/actions/workflows/Windows_MinGW_x86.yml/badge.svg)](https://github.com/diasurgical/devilutionX/actions/workflows/Windows_MinGW_x86.yml?query=branch%3Amaster) +[![Windows MSVC x64](https://github.com/diasurgical/devilutionX/actions/workflows/Windows_MSVC_x64.yml/badge.svg)](https://github.com/diasurgical/devilutionX/actions/workflows/Windows_MSVC_x64.yml?query=branch%3Amaster) [![Android](https://github.com/diasurgical/devilutionX/actions/workflows/Android.yml/badge.svg)](https://github.com/diasurgical/devilutionX/actions/workflows/Android.yml?query=branch%3Amaster) [![iOS](https://github.com/diasurgical/devilutionX/actions/workflows/iOS.yml/badge.svg)](https://github.com/diasurgical/devilutionX/actions/workflows/iOS.yml?query=branch%3Amaster) [![PS4](https://github.com/diasurgical/devilutionX/actions/workflows/PS4.yml/badge.svg)](https://github.com/diasurgical/devilutionX/actions/workflows/PS4.yml?query=branch%3Amaster) diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 164dd97cf..4501d6cc9 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -6,7 +6,6 @@ set(libdevilutionx_SRCS appfat.cpp automap.cpp capture.cpp - control.cpp cursor.cpp dead.cpp debug.cpp @@ -22,7 +21,6 @@ set(libdevilutionx_SRCS loadsave.cpp menu.cpp minitext.cpp - misdat.cpp missiles.cpp movie.cpp msg.cpp @@ -32,12 +30,17 @@ set(libdevilutionx_SRCS portal.cpp restrict.cpp sync.cpp - textdat.cpp tmsg.cpp - townerdat.cpp towners.cpp track.cpp + control/control_chat.cpp + control/control_chat_commands.cpp + control/control_flasks.cpp + control/control_gold.cpp + control/control_infobox.cpp + control/control_panel.cpp + controls/axis_direction.cpp controls/controller_motion.cpp controls/controller.cpp @@ -114,12 +117,14 @@ set(libdevilutionx_SRCS lua/modules/dev/quests.cpp lua/modules/dev/search.cpp lua/modules/dev/towners.cpp + lua/modules/floatingnumbers.cpp lua/modules/i18n.cpp lua/modules/items.cpp lua/modules/log.cpp lua/modules/monsters.cpp lua/modules/player.cpp lua/modules/render.cpp + lua/modules/system.cpp lua/modules/towners.cpp lua/repl.cpp @@ -150,6 +155,11 @@ set(libdevilutionx_SRCS storm/storm_net.cpp storm/storm_svid.cpp + + tables/misdat.cpp + tables/textdat.cpp + tables/townerdat.cpp + utils/display.cpp utils/language.cpp utils/sdl_bilinear_scale.cpp @@ -480,7 +490,7 @@ target_link_dependencies(libdevilutionx_logged_fstream PUBLIC ) add_devilutionx_object_library(libdevilutionx_items - itemdat.cpp + tables/itemdat.cpp items.cpp ) target_link_dependencies(libdevilutionx_items PUBLIC @@ -517,7 +527,7 @@ target_link_libraries(libdevilutionx_log INTERFACE target_sources(libdevilutionx_log INTERFACE $) add_devilutionx_object_library(libdevilutionx_level_objects - objdat.cpp + tables/objdat.cpp objects.cpp ) target_link_dependencies(libdevilutionx_level_objects PUBLIC @@ -534,7 +544,7 @@ target_link_dependencies(libdevilutionx_level_objects PUBLIC ) add_devilutionx_object_library(libdevilutionx_monster - monstdat.cpp + tables/monstdat.cpp monster.cpp ) target_link_dependencies(libdevilutionx_monster @@ -638,13 +648,14 @@ endif() add_devilutionx_object_library(libdevilutionx_player player.cpp - playerdat.cpp + tables/playerdat.cpp ) target_link_dependencies(libdevilutionx_player PUBLIC DevilutionX::SDL fmt::fmt magic_enum::magic_enum + sol2::sol2 tl unordered_dense::unordered_dense libdevilutionx_game_mode @@ -670,7 +681,7 @@ add_devilutionx_object_library(libdevilutionx_quick_messages ) add_devilutionx_object_library(libdevilutionx_spells - spelldat.cpp + tables/spelldat.cpp spells.cpp ) target_link_dependencies(libdevilutionx_spells PUBLIC diff --git a/Source/DiabloUI/diabloui.cpp b/Source/DiabloUI/diabloui.cpp index 732882d44..f85599af4 100644 --- a/Source/DiabloUI/diabloui.cpp +++ b/Source/DiabloUI/diabloui.cpp @@ -50,8 +50,8 @@ #include "init.hpp" #include "options.h" #include "player.h" -#include "playerdat.hpp" #include "sound_effect_enums.h" +#include "tables/playerdat.hpp" #include "utils/algorithm/container.hpp" #include "utils/display.h" #include "utils/enum_traits.h" diff --git a/Source/DiabloUI/hero/selhero.cpp b/Source/DiabloUI/hero/selhero.cpp index bb3287c41..883b4c1f4 100644 --- a/Source/DiabloUI/hero/selhero.cpp +++ b/Source/DiabloUI/hero/selhero.cpp @@ -33,7 +33,7 @@ #include "levels/gendung.h" #include "options.h" #include "pfile.h" -#include "playerdat.hpp" +#include "tables/playerdat.hpp" #include "utils/enum_traits.h" #include "utils/language.h" #include "utils/sdl_geometry.h" diff --git a/Source/DiabloUI/selstart.h b/Source/DiabloUI/selstart.h index b0ab70d43..aaa651494 100644 --- a/Source/DiabloUI/selstart.h +++ b/Source/DiabloUI/selstart.h @@ -1,7 +1,7 @@ -#pragma once - -namespace devilution { - -void UiSelStartUpGameOption(); - -} // namespace devilution +#pragma once + +namespace devilution { + +void UiSelStartUpGameOption(); + +} // namespace devilution diff --git a/Source/automap.cpp b/Source/automap.cpp index 342c019dc..6477dfa4d 100644 --- a/Source/automap.cpp +++ b/Source/automap.cpp @@ -10,7 +10,7 @@ #include -#include "control.h" +#include "control/control.hpp" #include "engine/load_file.hpp" #include "engine/palette.h" #include "engine/render/automap_render.hpp" @@ -1411,54 +1411,59 @@ void DrawAutomapText(const Surface &out) { Point linePosition { 8, 8 }; + auto advanceLine = [&](int numLines = 1) { + linePosition.y += 15 * numLines; + }; + + auto drawStringAndAdvanceLine = [&](std::string_view text, TextRenderOptions opts = {}, int numLines = 1) { + DrawString(out, text, linePosition, opts); + advanceLine(numLines); + }; + if (*GetOptions().Graphics.showFPS) { - linePosition.y += 15; + advanceLine(); } if (gbIsMultiplayer) { if (GameName != "0.0.0.0" && !IsLoopback) { std::string description = std::string(_("Game: ")); description.append(GameName); - DrawString(out, description, linePosition); - linePosition.y += 15; + drawStringAndAdvanceLine(description); } std::string description; if (IsLoopback) { description = std::string(_("Offline Game")); - } else if (!PublicGame) { + } else if (PublicGame) { + description = std::string(_("Public Game")); + } else { description = std::string(_("Password: ")); description.append(GamePassword); - } else { - description = std::string(_("Public Game")); } - DrawString(out, description, linePosition); - linePosition.y += 15; + drawStringAndAdvanceLine(description); } if (setlevel) { - DrawString(out, _(QuestLevelNames[setlvlnum]), linePosition); - return; - } - - std::string description; - switch (leveltype) { - case DTYPE_NEST: - description = fmt::format(fmt::runtime(_("Level: Nest {:d}")), currlevel - 16); - break; - case DTYPE_CRYPT: - description = fmt::format(fmt::runtime(_("Level: Crypt {:d}")), currlevel - 20); - break; - case DTYPE_TOWN: - description = std::string(_("Town")); - break; - default: - description = fmt::format(fmt::runtime(_("Level: {:d}")), currlevel); - break; + drawStringAndAdvanceLine(_(QuestLevelNames[setlvlnum])); + } else { + std::string description; + switch (leveltype) { + case DTYPE_NEST: + description = fmt::format(fmt::runtime(_("Level: Nest {:d}")), currlevel - 16); + break; + case DTYPE_CRYPT: + description = fmt::format(fmt::runtime(_("Level: Crypt {:d}")), currlevel - 20); + break; + case DTYPE_TOWN: + description = std::string(_("Town")); + break; + default: + description = fmt::format(fmt::runtime(_("Level: {:d}")), currlevel); + break; + } + drawStringAndAdvanceLine(description); } - DrawString(out, description, linePosition); - linePosition.y += 15; std::string_view difficulty; switch (sgGameInitInfo.nDifficulty) { case DIFF_NORMAL: @@ -1472,41 +1477,29 @@ void DrawAutomapText(const Surface &out) break; } - const std::string difficultyString = fmt::format(fmt::runtime(_(/* TRANSLATORS: {:s} means: Game Difficulty. */ "Difficulty: {:s}")), difficulty); - DrawString(out, difficultyString, linePosition); + const std::string description = fmt::format(fmt::runtime(_(/* TRANSLATORS: {:s} means: Game Difficulty. */ "Difficulty: {:s}")), difficulty); + drawStringAndAdvanceLine(description); #ifdef _DEBUG - const TextRenderOptions debugTextOptions { - .flags = UiFlags::ColorOrange, - }; - linePosition.y += 45; - if (DebugGodMode) { - linePosition.y += 15; - DrawString(out, "God Mode", linePosition, debugTextOptions); - } - if (DebugInvisible) { - linePosition.y += 15; - DrawString(out, "Invisible", linePosition, debugTextOptions); - } - if (DisableLighting) { - linePosition.y += 15; - DrawString(out, "Fullbright", linePosition, debugTextOptions); - } - if (DebugVision) { - linePosition.y += 15; - DrawString(out, "Draw Vision", linePosition, debugTextOptions); - } - if (DebugPath) { - linePosition.y += 15; - DrawString(out, "Draw Path", linePosition, debugTextOptions); - } - if (DebugGrid) { - linePosition.y += 15; - DrawString(out, "Draw Grid", linePosition, debugTextOptions); - } - if (DebugScrollViewEnabled) { - linePosition.y += 15; - DrawString(out, "Scroll View", linePosition, debugTextOptions); + if (DebugGodMode || DebugInvisible || DisableLighting || DebugVision || DebugPath || DebugGrid || DebugScrollViewEnabled) { + const TextRenderOptions disabled { + .flags = UiFlags::ColorBlack, + }; + const TextRenderOptions enabled { + .flags = UiFlags::ColorOrange, + }; + + advanceLine(); + drawStringAndAdvanceLine("Debug toggles:"); + drawStringAndAdvanceLine("Player:"); + drawStringAndAdvanceLine("God Mode", DebugGodMode ? enabled : disabled); + drawStringAndAdvanceLine("Invisible", DebugInvisible ? enabled : disabled); + drawStringAndAdvanceLine("Display:"); + drawStringAndAdvanceLine("Fullbright", DisableLighting ? enabled : disabled); + drawStringAndAdvanceLine("Draw Vision", DebugVision ? enabled : disabled); + drawStringAndAdvanceLine("Draw Path", DebugPath ? enabled : disabled); + drawStringAndAdvanceLine("Draw Grid", DebugGrid ? enabled : disabled); + drawStringAndAdvanceLine("Scroll View", DebugScrollViewEnabled ? enabled : disabled); } #endif } diff --git a/Source/control.cpp b/Source/control.cpp deleted file mode 100644 index f082171e1..000000000 --- a/Source/control.cpp +++ /dev/null @@ -1,2108 +0,0 @@ -/** - * @file control.cpp - * - * Implementation of the character and main control panels - */ -#include "control.h" - -#include -#include -#include -#include -#include -#include -#include - -#ifdef USE_SDL3 -#include -#include -#include -#include -#else -#include - -#ifdef USE_SDL1 -#include "utils/sdl2_to_1_2_backports.h" -#endif -#endif - -#include - -#include "DiabloUI/text_input.hpp" -#include "automap.h" -#include "controls/control_mode.hpp" -#include "controls/modifier_hints.h" -#include "controls/plrctrls.h" -#include "cursor.h" -#include "diablo_msg.hpp" -#include "engine/backbuffer_state.hpp" -#include "engine/clx_sprite.hpp" -#include "engine/load_cel.hpp" -#include "engine/render/clx_render.hpp" -#include "engine/render/primitive_render.hpp" -#include "engine/render/text_render.hpp" -#include "engine/trn.hpp" -#include "gamemenu.h" -#include "headless_mode.hpp" -#include "inv.h" -#include "inv_iterators.hpp" -#include "levels/setmaps.h" -#include "levels/trigs.h" -#include "lighting.h" -#include "minitext.h" -#include "missiles.h" -#include "options.h" -#include "panels/charpanel.hpp" -#include "panels/console.hpp" -#include "panels/mainpanel.hpp" -#include "panels/partypanel.hpp" -#include "panels/spell_book.hpp" -#include "panels/spell_icons.hpp" -#include "panels/spell_list.hpp" -#include "pfile.h" -#include "playerdat.hpp" -#include "qol/stash.h" -#include "qol/xpbar.h" -#include "quick_messages.hpp" -#include "stores.h" -#include "storm/storm_net.hpp" -#include "towners.h" -#include "utils/algorithm/container.hpp" -#include "utils/format_int.hpp" -#include "utils/language.h" -#include "utils/log.hpp" -#include "utils/parse_int.hpp" -#include "utils/screen_reader.hpp" -#include "utils/sdl_compat.h" -#include "utils/sdl_geometry.h" -#include "utils/sdl_ptrs.h" -#include "utils/status_macros.hpp" -#include "utils/str_case.hpp" -#include "utils/str_cat.hpp" -#include "utils/str_split.hpp" -#include "utils/string_or_view.hpp" -#include "utils/utf8.hpp" - -#ifdef _DEBUG -#include "debug.h" -#endif - -namespace devilution { - -bool DropGoldFlag; -TextInputCursorState GoldDropCursor; -char GoldDropText[21]; -namespace { -int8_t GoldDropInvIndex; -std::optional GoldDropInputState; -} // namespace - -bool CharPanelButton[4]; -bool LevelButtonDown; -bool CharPanelButtonActive; -UiFlags InfoColor; -int SpellbookTab; -bool ChatFlag; -bool SpellbookFlag; -bool CharFlag; -StringOrView InfoString; -StringOrView FloatingInfoString; -bool MainPanelFlag; -bool MainPanelButtonDown; -bool SpellSelectFlag; -Rectangle MainPanel; -Rectangle LeftPanel; -Rectangle RightPanel; -std::optional BottomBuffer; -OptionalOwnedClxSpriteList GoldBoxBuffer; - -const Rectangle &GetMainPanel() -{ - return MainPanel; -} -const Rectangle &GetLeftPanel() -{ - return LeftPanel; -} -const Rectangle &GetRightPanel() -{ - return RightPanel; -} -bool IsLeftPanelOpen() -{ - return CharFlag || QuestLogIsOpen || IsStashOpen; -} -bool IsRightPanelOpen() -{ - return invflag || SpellbookFlag; -} - -constexpr Size IncrementAttributeButtonSize { 41, 22 }; -/** Maps from attribute_id to the rectangle on screen used for attribute increment buttons. */ -Rectangle CharPanelButtonRect[4] = { - { { 137, 138 }, IncrementAttributeButtonSize }, - { { 137, 166 }, IncrementAttributeButtonSize }, - { { 137, 195 }, IncrementAttributeButtonSize }, - { { 137, 223 }, IncrementAttributeButtonSize } -}; - -constexpr Size WidePanelButtonSize { 71, 20 }; -constexpr Size PanelButtonSize { 33, 32 }; -/** Positions of panel buttons. */ -Rectangle MainPanelButtonRect[8] = { - // clang-format off - { { 9, 9 }, WidePanelButtonSize }, // char button - { { 9, 35 }, WidePanelButtonSize }, // quests button - { { 9, 75 }, WidePanelButtonSize }, // map button - { { 9, 101 }, WidePanelButtonSize }, // menu button - { { 560, 9 }, WidePanelButtonSize }, // inv button - { { 560, 35 }, WidePanelButtonSize }, // spells button - { { 87, 91 }, PanelButtonSize }, // chat button - { { 527, 91 }, PanelButtonSize }, // friendly fire button - // clang-format on -}; - -Rectangle LevelButtonRect = { { 40, -39 }, { 41, 22 } }; - -constexpr int BeltItems = 8; -constexpr Size BeltSize { (INV_SLOT_SIZE_PX + 1) * BeltItems, INV_SLOT_SIZE_PX }; -Rectangle BeltRect { { 205, 5 }, BeltSize }; - -Rectangle SpellButtonRect { { 565, 64 }, { 56, 56 } }; - -Rectangle FlaskTopRect { { 11, 3 }, { 62, 13 } }; -Rectangle FlaskBottomRect { { 0, 16 }, { 88, 69 } }; - -int MuteButtons = 3; -int MuteButtonPadding = 2; -Rectangle MuteButtonRect { { 172, 69 }, { 61, 16 } }; - -namespace { - -std::optional pLifeBuff; -std::optional pManaBuff; -OptionalOwnedClxSpriteList talkButtons; -OptionalOwnedClxSpriteList pDurIcons; -OptionalOwnedClxSpriteList multiButtons; -OptionalOwnedClxSpriteList pMainPanelButtons; - -enum panel_button_id : uint8_t { - PanelButtonCharinfo, - PanelButtonFirst = PanelButtonCharinfo, - PanelButtonQlog, - PanelButtonAutomap, - PanelButtonMainmenu, - PanelButtonInventory, - PanelButtonSpellbook, - PanelButtonSendmsg, - PanelButtonFriendly, - PanelButtonLast = PanelButtonFriendly, -}; - -bool MainPanelButtons[PanelButtonLast + 1]; -int TotalSpMainPanelButtons = 6; -int TotalMpMainPanelButtons = 8; -char TalkSave[8][MAX_SEND_STR_LEN]; -uint8_t TalkSaveIndex; -uint8_t NextTalkSave; -char TalkMessage[MAX_SEND_STR_LEN]; -bool TalkButtonsDown[3]; -int sgbPlrTalkTbl; -bool WhisperList[MAX_PLRS]; -int PanelPaddingHeight = 16; - -TextInputCursorState ChatCursor; -std::optional ChatInputState; - -/** Maps from panel_button_id to hotkey name. */ -const char *const PanBtnHotKey[8] = { "'c'", "'q'", N_("Tab"), N_("Esc"), "'i'", "'b'", N_("Enter"), nullptr }; -/** Maps from panel_button_id to panel button description. */ -const char *const PanBtnStr[8] = { - N_("Character Information"), - N_("Quests log"), - N_("Automap"), - N_("Main Menu"), - N_("Inventory"), - N_("Spell book"), - N_("Send Message"), - "" // Player attack -}; - -/** - * Draws the dome of the flask that protrudes above the panel top line. - * It draws a rectangle of fixed width 59 and height 'h' from the source buffer - * into the target buffer. - * @param out The target buffer. - * @param celBuf Buffer of the flask cel. - * @param targetPosition Target buffer coordinate. - */ -void DrawFlaskAbovePanel(const Surface &out, const Surface &celBuf, Point targetPosition) -{ - out.BlitFromSkipColorIndexZero(celBuf, MakeSdlRect(0, 0, celBuf.w(), celBuf.h()), targetPosition); -} - -/** - * @brief Draws the part of the life/mana flasks protruding above the bottom panel - * @see DrawFlaskLower() - * @param out The display region to draw to - * @param sourceBuffer A sprite representing the appropriate background/empty flask style - * @param offset X coordinate offset for where the flask should be drawn - * @param fillPer How full the flask is (a value from 0 to 81) - */ -void DrawFlaskUpper(const Surface &out, const Surface &sourceBuffer, int offset, int fillPer) -{ - const Rectangle &rect = FlaskTopRect; - const int emptyRows = std::clamp(81 - fillPer, 0, rect.size.height); - const int filledRows = rect.size.height - emptyRows; - - // Draw the empty part of the flask - DrawFlaskAbovePanel(out, - sourceBuffer.subregion(rect.position.x, rect.position.y, rect.size.width, rect.size.height), - GetMainPanel().position + Displacement { offset, -rect.size.height }); - - // Draw the filled part of the flask over the empty part - if (filledRows > 0) { - DrawFlaskAbovePanel(out, - BottomBuffer->subregion(offset, rect.position.y + emptyRows, rect.size.width, filledRows), - GetMainPanel().position + Displacement { offset, -rect.size.height + emptyRows }); - } -} - -/** - * Draws a section of the empty flask cel on top of the panel to create the illusion - * of the flask getting empty. This function takes a cel and draws a - * horizontal stripe of height (max-min) onto the given buffer. - * @param out Target buffer. - * @param celBuf Buffer of the flask cel. - * @param targetPosition Target buffer coordinate. - */ -void DrawFlaskOnPanel(const Surface &out, const Surface &celBuf, Point targetPosition) -{ - out.BlitFrom(celBuf, MakeSdlRect(0, 0, celBuf.w(), celBuf.h()), targetPosition); -} - -/** - * @brief Draws the part of the life/mana flasks inside the bottom panel - * @see DrawFlaskUpper() - * @param out The display region to draw to - * @param sourceBuffer A sprite representing the appropriate background/empty flask style - * @param offset X coordinate offset for where the flask should be drawn - * @param fillPer How full the flask is (a value from 0 to 80) - * @param drawFilledPortion Indicates whether to draw the filled portion of the flask - */ -void DrawFlaskLower(const Surface &out, const Surface &sourceBuffer, int offset, int fillPer, bool drawFilledPortion) -{ - const Rectangle &rect = FlaskBottomRect; - const int filledRows = std::clamp(fillPer, 0, rect.size.height); - const int emptyRows = rect.size.height - filledRows; - - // Draw the empty part of the flask - if (emptyRows > 0) { - DrawFlaskOnPanel(out, - sourceBuffer.subregion(rect.position.x, rect.position.y, rect.size.width, emptyRows), - GetMainPanel().position + Displacement { offset, 0 }); - } - - // Draw the filled part of the flask - if (drawFilledPortion && filledRows > 0) { - DrawFlaskOnPanel(out, - BottomBuffer->subregion(offset, rect.position.y + emptyRows, rect.size.width, filledRows), - GetMainPanel().position + Displacement { offset, emptyRows }); - } -} - -void SetMainPanelButtonDown(int btnId) -{ - MainPanelButtons[btnId] = true; - RedrawComponent(PanelDrawComponent::ControlButtons); - MainPanelButtonDown = true; -} - -void SetMainPanelButtonUp() -{ - RedrawComponent(PanelDrawComponent::ControlButtons); - MainPanelButtonDown = false; -} - -void SetPanelObjectPosition(UiPanels panel, Rectangle &button) -{ - button.position = GetPanelPosition(panel, button.position); -} - -void PrintInfo(const Surface &out) -{ - if (ChatFlag) - return; - - const int space[] = { 18, 12, 6, 3, 0 }; - Rectangle infoBox = InfoBoxRect; - - SetPanelObjectPosition(UiPanels::Main, infoBox); - - const auto newLineCount = static_cast(c_count(InfoString.str(), '\n')); - const int spaceIndex = std::min(4, newLineCount); - const int spacing = space[spaceIndex]; - const int lineHeight = 12 + spacing; - - // Adjusting the line height to add spacing between lines - // will also add additional space beneath the last line - // which throws off the vertical centering - infoBox.position.y += spacing / 2; - - SpeakText(InfoString); - - DrawString(out, InfoString, infoBox, - { - .flags = InfoColor | UiFlags::AlignCenter | UiFlags::VerticalCenter | UiFlags::KerningFitSpacing, - .spacing = 2, - .lineHeight = lineHeight, - }); -} - -Rectangle GetFloatingInfoRect(const int lineHeight, const int textSpacing) -{ - // Calculate the width and height of the floating info box - const std::string txt = std::string(FloatingInfoString); - - auto lines = SplitByChar(txt, '\n'); - const GameFontTables font = GameFont12; - int maxW = 0; - - for (const auto &line : lines) { - const int w = GetLineWidth(line, font, textSpacing, nullptr); - maxW = std::max(maxW, w); - } - - const auto lineCount = 1 + static_cast(c_count(FloatingInfoString.str(), '\n')); - const int totalH = lineCount * lineHeight; - - const Player &player = *InspectPlayer; - - // 1) Equipment (Rect position) - if (pcursinvitem >= INVITEM_HEAD && pcursinvitem < INVITEM_INV_FIRST) { - const int slot = pcursinvitem - INVITEM_HEAD; - static constexpr Point equipLocal[] = { - { 133, 59 }, - { 48, 205 }, - { 249, 205 }, - { 205, 60 }, - { 17, 160 }, - { 248, 160 }, - { 133, 160 }, - }; - - Point itemPosition = equipLocal[slot]; - auto &item = player.InvBody[slot]; - const Size frame = GetInvItemSize(item._iCurs + CURSOR_FIRSTITEM); - - if (slot == INVLOC_HAND_LEFT) { - itemPosition.x += frame.width == InventorySlotSizeInPixels.width - ? InventorySlotSizeInPixels.width - : 0; - itemPosition.y += frame.height == 3 * InventorySlotSizeInPixels.height - ? 0 - : -InventorySlotSizeInPixels.height; - } else if (slot == INVLOC_HAND_RIGHT) { - itemPosition.x += frame.width == InventorySlotSizeInPixels.width - ? (InventorySlotSizeInPixels.width - 1) - : 1; - itemPosition.y += frame.height == 3 * InventorySlotSizeInPixels.height - ? 0 - : -InventorySlotSizeInPixels.height; - } - - itemPosition.y++; // Align position to bottom left of the item graphic - itemPosition.x += frame.width / 2; // Align position to center of the item graphic - itemPosition.x -= maxW / 2; // Align position to the center of the floating item info box - - const Point screen = GetPanelPosition(UiPanels::Inventory, itemPosition); - - return { { screen.x, screen.y }, { maxW, totalH } }; - } - - // 2) Inventory grid (Rect position) - if (pcursinvitem >= INVITEM_INV_FIRST && pcursinvitem < INVITEM_INV_FIRST + InventoryGridCells) { - const int itemIdx = pcursinvitem - INVITEM_INV_FIRST; - - for (int j = 0; j < InventoryGridCells; ++j) { - if (player.InvGrid[j] > 0 && player.InvGrid[j] - 1 == itemIdx) { - const Item &it = player.InvList[itemIdx]; - Point itemPosition = InvRect[j + SLOTXY_INV_FIRST].position; - - itemPosition.x += GetInventorySize(it).width * InventorySlotSizeInPixels.width / 2; // Align position to center of the item graphic - itemPosition.x -= maxW / 2; // Align position to the center of the floating item info box - - const Point screen = GetPanelPosition(UiPanels::Inventory, itemPosition); - - return { { screen.x, screen.y }, { maxW, totalH } }; - } - } - } - - // 3) Belt (Rect position) - if (pcursinvitem >= INVITEM_BELT_FIRST && pcursinvitem < INVITEM_BELT_FIRST + MaxBeltItems) { - const int itemIdx = pcursinvitem - INVITEM_BELT_FIRST; - for (int i = 0; i < MaxBeltItems; ++i) { - if (player.SpdList[i].isEmpty()) - continue; - if (i != itemIdx) - continue; - - const Item &item = player.SpdList[i]; - Point itemPosition = InvRect[i + SLOTXY_BELT_FIRST].position; - - itemPosition.x += GetInventorySize(item).width * InventorySlotSizeInPixels.width / 2; // Align position to center of the item graphic - itemPosition.x -= maxW / 2; // Align position to the center of the floating item info box - - const Point screen = GetMainPanel().position + Displacement { itemPosition.x, itemPosition.y }; - - return { { screen.x, screen.y }, { maxW, totalH } }; - } - } - - // 4) Stash (Rect position) - if (pcursstashitem != StashStruct::EmptyCell) { - for (auto slot : StashGridRange) { - auto itemId = Stash.GetItemIdAtPosition(slot); - if (itemId == StashStruct::EmptyCell) - continue; - if (itemId != pcursstashitem) - continue; - - const Item &item = Stash.stashList[itemId]; - Point itemPosition = GetStashSlotCoord(slot); - const Size itemGridSize = GetInventorySize(item); - - itemPosition.y += itemGridSize.height * (InventorySlotSizeInPixels.height + 1) - 1; // Align position to bottom left of the item graphic - itemPosition.x += itemGridSize.width * InventorySlotSizeInPixels.width / 2; // Align position to center of the item graphic - itemPosition.x -= maxW / 2; // Align position to the center of the floating item info box - - return { { itemPosition.x, itemPosition.y }, { maxW, totalH } }; - } - } - - return { { 0, 0 }, { 0, 0 } }; -} - -int GetHoverSpriteHeight() -{ - if (pcursinvitem >= INVITEM_HEAD && pcursinvitem < INVITEM_INV_FIRST) { - auto &it = (*InspectPlayer).InvBody[pcursinvitem - INVITEM_HEAD]; - return GetInvItemSize(it._iCurs + CURSOR_FIRSTITEM).height + 1; - } - if (pcursinvitem >= INVITEM_INV_FIRST - && pcursinvitem < INVITEM_INV_FIRST + InventoryGridCells) { - const int idx = pcursinvitem - INVITEM_INV_FIRST; - auto &it = (*InspectPlayer).InvList[idx]; - return GetInventorySize(it).height * (InventorySlotSizeInPixels.height + 1) - - InventorySlotSizeInPixels.height; - } - if (pcursinvitem >= INVITEM_BELT_FIRST - && pcursinvitem < INVITEM_BELT_FIRST + MaxBeltItems) { - const int idx = pcursinvitem - INVITEM_BELT_FIRST; - auto &it = (*InspectPlayer).SpdList[idx]; - return GetInventorySize(it).height * (InventorySlotSizeInPixels.height + 1) - - InventorySlotSizeInPixels.height - 1; - } - if (pcursstashitem != StashStruct::EmptyCell) { - auto &it = Stash.stashList[pcursstashitem]; - return GetInventorySize(it).height * (InventorySlotSizeInPixels.height + 1); - } - return InventorySlotSizeInPixels.height; -} - -int ClampAboveOrBelow(int anchorY, int spriteH, int boxH, int pad, int linePad) -{ - const int yAbove = anchorY - spriteH - boxH - pad; - const int yBelow = anchorY + linePad / 2 + pad; - return (yAbove >= 0) ? yAbove : yBelow; -} - -void PrintFloatingInfo(const Surface &out) -{ - if (ChatFlag) - return; - if (FloatingInfoString.empty()) - return; - - const int verticalSpacing = 3; - const int lineHeight = 12 + verticalSpacing; - const int textSpacing = 2; - const int hPadding = 5; - const int vPadding = 4; - - Rectangle floatingInfoBox = GetFloatingInfoRect(lineHeight, textSpacing); - - // Prevent the floating info box from going off-screen horizontally - floatingInfoBox.position.x = std::clamp(floatingInfoBox.position.x, hPadding, GetScreenWidth() - (floatingInfoBox.size.width + hPadding)); - - const int spriteH = GetHoverSpriteHeight(); - const int anchorY = floatingInfoBox.position.y; - - // Prevent the floating info box from going off-screen vertically - floatingInfoBox.position.y = ClampAboveOrBelow(anchorY, spriteH, floatingInfoBox.size.height, vPadding, verticalSpacing); - - SpeakText(FloatingInfoString); - - for (int i = 0; i < 3; i++) - DrawHalfTransparentRectTo(out, floatingInfoBox.position.x - hPadding, floatingInfoBox.position.y - vPadding, floatingInfoBox.size.width + hPadding * 2, floatingInfoBox.size.height + vPadding * 2); - DrawHalfTransparentVerticalLine(out, { floatingInfoBox.position.x - hPadding - 1, floatingInfoBox.position.y - vPadding - 1 }, floatingInfoBox.size.height + (vPadding * 2) + 2, PAL16_GRAY + 10); - DrawHalfTransparentVerticalLine(out, { floatingInfoBox.position.x + hPadding + floatingInfoBox.size.width, floatingInfoBox.position.y - vPadding - 1 }, floatingInfoBox.size.height + (vPadding * 2) + 2, PAL16_GRAY + 10); - DrawHalfTransparentHorizontalLine(out, { floatingInfoBox.position.x - hPadding, floatingInfoBox.position.y - vPadding - 1 }, floatingInfoBox.size.width + (hPadding * 2), PAL16_GRAY + 10); - DrawHalfTransparentHorizontalLine(out, { floatingInfoBox.position.x - hPadding, floatingInfoBox.position.y + vPadding + floatingInfoBox.size.height }, floatingInfoBox.size.width + (hPadding * 2), PAL16_GRAY + 10); - - DrawString(out, FloatingInfoString, floatingInfoBox, - { - .flags = InfoColor | UiFlags::AlignCenter | UiFlags::VerticalCenter, - .spacing = textSpacing, - .lineHeight = lineHeight, - }); -} - -int CapStatPointsToAdd(int remainingStatPoints, const Player &player, CharacterAttribute attribute) -{ - const int pointsToReachCap = player.GetMaximumAttributeValue(attribute) - player.GetBaseAttributeValue(attribute); - - return std::min(remainingStatPoints, pointsToReachCap); -} - -int DrawDurIcon4Item(const Surface &out, Item &pItem, int x, int c) -{ - const int durabilityThresholdGold = 5; - const int durabilityThresholdRed = 2; - - if (pItem.isEmpty()) - return x; - if (pItem._iDurability > durabilityThresholdGold) - return x; - if (c == 0) { - switch (pItem._itype) { - case ItemType::Sword: - c = 1; - break; - case ItemType::Axe: - c = 5; - break; - case ItemType::Bow: - c = 6; - break; - case ItemType::Mace: - c = 4; - break; - case ItemType::Staff: - c = 7; - break; - case ItemType::Shield: - default: - c = 0; - break; - } - } - - // Calculate how much of the icon should be gold and red - const int height = (*pDurIcons)[c].height(); // Height of durability icon CEL - int partition = 0; - if (pItem._iDurability > durabilityThresholdRed) { - const int current = pItem._iDurability - durabilityThresholdRed; - partition = (height * current) / (durabilityThresholdGold - durabilityThresholdRed); - } - - // Draw icon - const int y = -17 + GetMainPanel().position.y; - if (partition > 0) { - const Surface stenciledBuffer = out.subregionY(y - partition, partition); - ClxDraw(stenciledBuffer, { x, partition }, (*pDurIcons)[c + 8]); // Gold icon - } - if (partition != height) { - const Surface stenciledBuffer = out.subregionY(y - height, height - partition); - ClxDraw(stenciledBuffer, { x, height }, (*pDurIcons)[c]); // Red icon - } - - return x - (*pDurIcons)[c].height() - 8; // Add in spacing for the next durability icon -} - -struct TextCmdItem { - const std::string text; - const std::string description; - const std::string requiredParameter; - std::string (*actionProc)(const std::string_view); -}; - -extern std::vector TextCmdList; - -std::string TextCmdHelp(const std::string_view parameter) -{ - if (parameter.empty()) { - std::string ret; - StrAppend(ret, _("Available Commands:")); - for (const TextCmdItem &textCmd : TextCmdList) { - StrAppend(ret, " ", _(textCmd.text)); - } - return ret; - } - auto textCmdIterator = c_find_if(TextCmdList, [&](const TextCmdItem &elem) { return elem.text == parameter; }); - if (textCmdIterator == TextCmdList.end()) - return StrCat(_("Command "), parameter, _(" is unknown.")); - auto &textCmdItem = *textCmdIterator; - if (textCmdItem.requiredParameter.empty()) - return StrCat(_("Description: "), _(textCmdItem.description), _("\nParameters: No additional parameter needed.")); - return StrCat(_("Description: "), _(textCmdItem.description), _("\nParameters: "), _(textCmdItem.requiredParameter)); -} - -void AppendArenaOverview(std::string &ret) -{ - for (int arena = SL_FIRST_ARENA; arena <= SL_LAST; arena++) { - StrAppend(ret, "\n", arena - SL_FIRST_ARENA + 1, " (", QuestLevelNames[arena], ")"); - } -} - -std::string TextCmdArena(const std::string_view parameter) -{ - std::string ret; - if (!gbIsMultiplayer) { - StrAppend(ret, _("Arenas are only supported in multiplayer.")); - return ret; - } - - if (parameter.empty()) { - StrAppend(ret, _("What arena do you want to visit?")); - AppendArenaOverview(ret); - return ret; - } - - const ParseIntResult parsedParam = ParseInt(parameter, /*min=*/0); - const _setlevels arenaLevel = parsedParam.has_value() ? static_cast<_setlevels>(parsedParam.value() - 1 + SL_FIRST_ARENA) : _setlevels::SL_NONE; - if (!IsArenaLevel(arenaLevel)) { - StrAppend(ret, _("Invalid arena-number. Valid numbers are:")); - AppendArenaOverview(ret); - return ret; - } - - if (!MyPlayer->isOnLevel(0) && !MyPlayer->isOnArenaLevel()) { - StrAppend(ret, _("To enter a arena, you need to be in town or another arena.")); - return ret; - } - - setlvltype = GetArenaLevelType(arenaLevel); - StartNewLvl(*MyPlayer, WM_DIABSETLVL, arenaLevel); - return ret; -} - -std::string TextCmdArenaPot(const std::string_view parameter) -{ - std::string ret; - if (!gbIsMultiplayer) { - StrAppend(ret, _("Arenas are only supported in multiplayer.")); - return ret; - } - const int numPots = ParseInt(parameter, /*min=*/1).value_or(1); - - Player &myPlayer = *MyPlayer; - - for (int potNumber = numPots; potNumber > 0; potNumber--) { - Item item {}; - InitializeItem(item, IDI_ARENAPOT); - GenerateNewSeed(item); - item.updateRequiredStatsCacheForPlayer(myPlayer); - - if (!AutoPlaceItemInBelt(myPlayer, item, true, true) && !AutoPlaceItemInInventory(myPlayer, item, true)) { - break; // inventory is full - } - } - - return ret; -} - -std::string TextCmdInspect(const std::string_view parameter) -{ - std::string ret; - if (!gbIsMultiplayer) { - StrAppend(ret, _("Inspecting only supported in multiplayer.")); - return ret; - } - - if (parameter.empty()) { - StrAppend(ret, _("Stopped inspecting players.")); - InspectPlayer = MyPlayer; - return ret; - } - - const std::string param = AsciiStrToLower(parameter); - auto it = c_find_if(Players, [¶m](const Player &player) { - return AsciiStrToLower(player._pName) == param; - }); - if (it == Players.end()) { - it = c_find_if(Players, [¶m](const Player &player) { - return AsciiStrToLower(player._pName).find(param) != std::string::npos; - }); - } - if (it == Players.end()) { - StrAppend(ret, _("No players found with such a name")); - return ret; - } - - Player &player = *it; - InspectPlayer = &player; - StrAppend(ret, _("Inspecting player: ")); - StrAppend(ret, player._pName); - OpenCharPanel(); - if (!SpellbookFlag) - invflag = true; - RedrawEverything(); - return ret; -} - -bool IsQuestEnabled(const Quest &quest) -{ - switch (quest._qidx) { - case Q_FARMER: - return gbIsHellfire && !sgGameInitInfo.bCowQuest; - case Q_JERSEY: - return gbIsHellfire && sgGameInitInfo.bCowQuest; - case Q_GIRL: - return gbIsHellfire && sgGameInitInfo.bTheoQuest; - case Q_CORNSTN: - return gbIsHellfire && !gbIsMultiplayer; - case Q_GRAVE: - case Q_DEFILER: - case Q_NAKRUL: - return gbIsHellfire; - case Q_TRADER: - return false; - default: - return quest._qactive != QUEST_NOTAVAIL; - } -} - -std::string TextCmdLevelSeed(const std::string_view parameter) -{ - const std::string_view levelType = setlevel ? "set level" : "dungeon level"; - - char gameId[] = { - static_cast((sgGameInitInfo.programid >> 24) & 0xFF), - static_cast((sgGameInitInfo.programid >> 16) & 0xFF), - static_cast((sgGameInitInfo.programid >> 8) & 0xFF), - static_cast(sgGameInitInfo.programid & 0xFF), - '\0' - }; - - const std::string_view mode = gbIsMultiplayer ? "MP" : "SP"; - const std::string_view questPool = UseMultiplayerQuests() ? "MP" : "Full"; - - uint32_t questFlags = 0; - for (const Quest &quest : Quests) { - questFlags <<= 1; - if (IsQuestEnabled(quest)) - questFlags |= 1; - } - - return StrCat( - "Seedinfo for ", levelType, " ", currlevel, "\n", - "seed: ", DungeonSeeds[currlevel], "\n", -#ifdef _DEBUG - "Mid1: ", glMid1Seed[currlevel], "\n", - "Mid2: ", glMid2Seed[currlevel], "\n", - "Mid3: ", glMid3Seed[currlevel], "\n", - "End: ", glEndSeed[currlevel], "\n", -#endif - "\n", - gameId, " ", mode, "\n", - questPool, " quests: ", questFlags, "\n", - "Storybook: ", DungeonSeeds[16]); -} - -std::string TextCmdPing(const std::string_view parameter) -{ - std::string ret; - const std::string param = AsciiStrToLower(parameter); - auto it = c_find_if(Players, [¶m](const Player &player) { - return AsciiStrToLower(player._pName) == param; - }); - if (it == Players.end()) { - it = c_find_if(Players, [¶m](const Player &player) { - return AsciiStrToLower(player._pName).find(param) != std::string::npos; - }); - } - if (it == Players.end()) { - StrAppend(ret, _("No players found with such a name")); - return ret; - } - - Player &player = *it; - DvlNetLatencies latencies = DvlNet_GetLatencies(player.getId()); - - StrAppend(ret, fmt::format(fmt::runtime(_(/* TRANSLATORS: {:s} means: Character Name */ "Latency statistics for {:s}:")), player.name())); - - StrAppend(ret, "\n", fmt::format(fmt::runtime(_(/* TRANSLATORS: Network connectivity statistics */ "Echo latency: {:d} ms")), latencies.echoLatency)); - - if (latencies.providerLatency) { - if (latencies.isRelayed && *latencies.isRelayed) { - StrAppend(ret, "\n", fmt::format(fmt::runtime(_(/* TRANSLATORS: Network connectivity statistics */ "Provider latency: {:d} ms (Relayed)")), *latencies.providerLatency)); - } else { - StrAppend(ret, "\n", fmt::format(fmt::runtime(_(/* TRANSLATORS: Network connectivity statistics */ "Provider latency: {:d} ms")), *latencies.providerLatency)); - } - } - - return ret; -} - -std::vector TextCmdList = { - { "/help", N_("Prints help overview or help for a specific command."), N_("[command]"), &TextCmdHelp }, - { "/arena", N_("Enter a PvP Arena."), N_(""), &TextCmdArena }, - { "/arenapot", N_("Gives Arena Potions."), N_(""), &TextCmdArenaPot }, - { "/inspect", N_("Inspects stats and equipment of another player."), N_(""), &TextCmdInspect }, - { "/seedinfo", N_("Show seed infos for current level."), "", &TextCmdLevelSeed }, - { "/ping", N_("Show latency statistics for another player."), N_(""), &TextCmdPing }, -}; - -bool CheckChatCommand(const std::string_view text) -{ - if (text.size() < 1 || text[0] != '/') - return false; - - auto textCmdIterator = c_find_if(TextCmdList, [&](const TextCmdItem &elem) { return text.find(elem.text) == 0 && (text.length() == elem.text.length() || text[elem.text.length()] == ' '); }); - if (textCmdIterator == TextCmdList.end()) { - InitDiabloMsg(StrCat(_("Command "), "\"", text, "\"", _(" is unknown."))); - return true; - } - - const TextCmdItem &textCmd = *textCmdIterator; - std::string_view parameter = ""; - if (text.length() > (textCmd.text.length() + 1)) - parameter = text.substr(textCmd.text.length() + 1); - const std::string result = textCmd.actionProc(parameter); - if (result != "") - InitDiabloMsg(result); - return true; -} - -void ResetChatMessage() -{ - if (CheckChatCommand(TalkMessage)) - return; - - uint32_t pmask = 0; - - for (size_t i = 0; i < Players.size(); i++) { - if (WhisperList[i]) - pmask |= 1 << i; - } - - NetSendCmdString(pmask, TalkMessage); -} - -void ControlPressEnter() -{ - if (TalkMessage[0] != 0) { - ResetChatMessage(); - uint8_t i = 0; - for (; i < 8; i++) { - if (strcmp(TalkSave[i], TalkMessage) == 0) - break; - } - if (i >= 8) { - strcpy(TalkSave[NextTalkSave], TalkMessage); - NextTalkSave++; - NextTalkSave &= 7; - } else { - uint8_t talkSave = NextTalkSave - 1; - talkSave &= 7; - if (i != talkSave) { - strcpy(TalkSave[i], TalkSave[talkSave]); - *BufCopy(TalkSave[talkSave], ChatInputState->value()) = '\0'; - } - } - TalkMessage[0] = '\0'; - TalkSaveIndex = NextTalkSave; - } - ResetChat(); -} - -void ControlUpDown(int v) -{ - for (int i = 0; i < 8; i++) { - TalkSaveIndex = (v + TalkSaveIndex) & 7; - if (TalkSave[TalkSaveIndex][0] != 0) { - ChatInputState->assign(TalkSave[TalkSaveIndex]); - return; - } - } -} - -void RemoveGold(Player &player, int goldIndex, int amount) -{ - const int gi = goldIndex - INVITEM_INV_FIRST; - player.InvList[gi]._ivalue -= amount; - if (player.InvList[gi]._ivalue > 0) { - SetPlrHandGoldCurs(player.InvList[gi]); - NetSyncInvItem(player, gi); - } else { - player.RemoveInvItem(gi); - } - - MakeGoldStack(player.HoldItem, amount); - NewCursor(player.HoldItem); - - player._pGold = CalculateGold(player); -} - -bool IsLevelUpButtonVisible() -{ - if (SpellSelectFlag || CharFlag || MyPlayer->_pStatPts == 0) { - return false; - } - if (ControlMode == ControlTypes::VirtualGamepad) { - return false; - } - if (IsPlayerInStore() || IsStashOpen) { - return false; - } - if (QuestLogIsOpen && GetLeftPanel().contains(GetMainPanel().position + Displacement { 0, -74 })) { - return false; - } - - return true; -} - -} // namespace - -void CalculatePanelAreas() -{ - constexpr Size MainPanelSize { 640, 128 }; - - MainPanel = { - { (gnScreenWidth - MainPanelSize.width) / 2, gnScreenHeight - MainPanelSize.height }, - MainPanelSize - }; - LeftPanel = { - { 0, 0 }, - SidePanelSize - }; - RightPanel = { - { 0, 0 }, - SidePanelSize - }; - - if (ControlMode == ControlTypes::VirtualGamepad) { - LeftPanel.position.x = gnScreenWidth / 2 - LeftPanel.size.width; - } else { - if (gnScreenWidth - LeftPanel.size.width - RightPanel.size.width > MainPanel.size.width) { - LeftPanel.position.x = (gnScreenWidth - LeftPanel.size.width - RightPanel.size.width - MainPanel.size.width) / 2; - } - } - LeftPanel.position.y = (gnScreenHeight - LeftPanel.size.height - MainPanel.size.height) / 2; - - if (ControlMode == ControlTypes::VirtualGamepad) { - RightPanel.position.x = gnScreenWidth / 2; - } else { - RightPanel.position.x = gnScreenWidth - RightPanel.size.width - LeftPanel.position.x; - } - RightPanel.position.y = LeftPanel.position.y; - - gnViewportHeight = gnScreenHeight; - if (gnScreenWidth <= MainPanel.size.width) { - // Part of the screen is fully obscured by the UI - gnViewportHeight -= MainPanel.size.height; - } -} - -bool IsChatAvailable() -{ - return gbIsMultiplayer; -} - -void FocusOnCharInfo() -{ - const Player &myPlayer = *MyPlayer; - - if (invflag || myPlayer._pStatPts <= 0) - return; - - // Find the first incrementable stat. - int stat = -1; - for (auto attribute : enum_values()) { - if (myPlayer.GetBaseAttributeValue(attribute) >= myPlayer.GetMaximumAttributeValue(attribute)) - continue; - stat = static_cast(attribute); - } - if (stat == -1) - return; - - SetCursorPos(CharPanelButtonRect[stat].Center()); -} - -void OpenCharPanel() -{ - QuestLogIsOpen = false; - CloseGoldWithdraw(); - CloseStash(); - CharFlag = true; -} - -void CloseCharPanel() -{ - CharFlag = false; - if (IsInspectingPlayer()) { - InspectPlayer = MyPlayer; - RedrawEverything(); - - if (InspectingFromPartyPanel) - InspectingFromPartyPanel = false; - else - InitDiabloMsg(_("Stopped inspecting players.")); - } -} - -void ToggleCharPanel() -{ - if (CharFlag) - CloseCharPanel(); - else - OpenCharPanel(); -} - -void AddInfoBoxString(std::string_view str, bool floatingBox /*= false*/) -{ - StringOrView &infoString = floatingBox ? FloatingInfoString : InfoString; - - if (infoString.empty()) - infoString = str; - else - infoString = StrCat(infoString, "\n", str); -} - -void AddInfoBoxString(std::string &&str, bool floatingBox /*= false*/) -{ - StringOrView &infoString = floatingBox ? FloatingInfoString : InfoString; - - if (infoString.empty()) - infoString = std::move(str); - else - infoString = StrCat(infoString, "\n", str); -} - -Point GetPanelPosition(UiPanels panel, Point offset) -{ - const Displacement displacement { offset.x, offset.y }; - - switch (panel) { - case UiPanels::Main: - return GetMainPanel().position + displacement; - case UiPanels::Quest: - case UiPanels::Character: - case UiPanels::Stash: - return GetLeftPanel().position + displacement; - case UiPanels::Spell: - case UiPanels::Inventory: - return GetRightPanel().position + displacement; - default: - return GetMainPanel().position + displacement; - } -} - -void DrawPanelBox(const Surface &out, SDL_Rect srcRect, Point targetPosition) -{ - out.BlitFrom(*BottomBuffer, srcRect, targetPosition); -} - -void DrawLifeFlaskUpper(const Surface &out) -{ - constexpr int LifeFlaskUpperOffset = 107; - DrawFlaskUpper(out, *pLifeBuff, LifeFlaskUpperOffset, MyPlayer->_pHPPer); -} - -void DrawManaFlaskUpper(const Surface &out) -{ - constexpr int ManaFlaskUpperOffset = 475; - DrawFlaskUpper(out, *pManaBuff, ManaFlaskUpperOffset, MyPlayer->_pManaPer); -} - -void DrawLifeFlaskLower(const Surface &out, bool drawFilledPortion) -{ - constexpr int LifeFlaskLowerOffset = 96; - DrawFlaskLower(out, *pLifeBuff, LifeFlaskLowerOffset, MyPlayer->_pHPPer, drawFilledPortion); -} - -void DrawManaFlaskLower(const Surface &out, bool drawFilledPortion) -{ - constexpr int ManaFlaskLowerOffset = 464; - DrawFlaskLower(out, *pManaBuff, ManaFlaskLowerOffset, MyPlayer->_pManaPer, drawFilledPortion); -} - -void DrawFlaskValues(const Surface &out, Point pos, int currValue, int maxValue) -{ - const UiFlags color = (currValue > 0 ? (currValue == maxValue ? UiFlags::ColorGold : UiFlags::ColorWhite) : UiFlags::ColorRed); - - auto drawStringWithShadow = [out, color](std::string_view text, Point pos) { - DrawString(out, text, pos + Displacement { -1, -1 }, - { .flags = UiFlags::ColorBlack | UiFlags::KerningFitSpacing, .spacing = 0 }); - DrawString(out, text, pos, - { .flags = color | UiFlags::KerningFitSpacing, .spacing = 0 }); - }; - - const std::string currText = StrCat(currValue); - drawStringWithShadow(currText, pos - Displacement { GetLineWidth(currText, GameFont12) + 1, 0 }); - drawStringWithShadow("/", pos); - drawStringWithShadow(StrCat(maxValue), pos + Displacement { GetLineWidth("/", GameFont12) + 1, 0 }); -} - -void UpdateLifeManaPercent() -{ - MyPlayer->UpdateManaPercentage(); - MyPlayer->UpdateHitPointPercentage(); -} - -tl::expected InitMainPanel() -{ - if (!HeadlessMode) { - BottomBuffer.emplace(GetMainPanel().size.width, (GetMainPanel().size.height + PanelPaddingHeight) * (IsChatAvailable() ? 2 : 1)); - pManaBuff.emplace(88, 88); - pLifeBuff.emplace(88, 88); - - RETURN_IF_ERROR(LoadPartyPanel()); - RETURN_IF_ERROR(LoadCharPanel()); - RETURN_IF_ERROR(LoadLargeSpellIcons()); - { - ASSIGN_OR_RETURN(const OwnedClxSpriteList sprite, LoadCelWithStatus("ctrlpan\\panel8", GetMainPanel().size.width)); - ClxDraw(*BottomBuffer, { 0, (GetMainPanel().size.height + PanelPaddingHeight) - 1 }, sprite[0]); - } - { - const Point bulbsPosition { 0, 87 }; - ASSIGN_OR_RETURN(const OwnedClxSpriteList statusPanel, LoadCelWithStatus("ctrlpan\\p8bulbs", 88)); - ClxDraw(*pLifeBuff, bulbsPosition, statusPanel[0]); - ClxDraw(*pManaBuff, bulbsPosition, statusPanel[1]); - } - } - ChatFlag = false; - ChatInputState = std::nullopt; - if (IsChatAvailable()) { - if (!HeadlessMode) { - { - ASSIGN_OR_RETURN(const OwnedClxSpriteList sprite, LoadCelWithStatus("ctrlpan\\talkpanl", GetMainPanel().size.width)); - ClxDraw(*BottomBuffer, { 0, (GetMainPanel().size.height + PanelPaddingHeight) * 2 - 1 }, sprite[0]); - } - multiButtons = LoadCel("ctrlpan\\p8but2", 33); - talkButtons = LoadCel("ctrlpan\\talkbutt", 61); - } - sgbPlrTalkTbl = 0; - TalkMessage[0] = '\0'; - for (bool &whisper : WhisperList) - whisper = true; - for (bool &talkButtonDown : TalkButtonsDown) - talkButtonDown = false; - } - MainPanelFlag = false; - LevelButtonDown = false; - if (!HeadlessMode) { - RETURN_IF_ERROR(LoadMainPanel()); - ASSIGN_OR_RETURN(pMainPanelButtons, LoadCelWithStatus("ctrlpan\\panel8bu", 71)); - - static const uint16_t CharButtonsFrameWidths[9] { 95, 41, 41, 41, 41, 41, 41, 41, 41 }; - ASSIGN_OR_RETURN(pChrButtons, LoadCelWithStatus("data\\charbut", CharButtonsFrameWidths)); - } - ResetMainPanelButtons(); - if (!HeadlessMode) - pDurIcons = LoadCel("items\\duricons", 32); - for (bool &buttonEnabled : CharPanelButton) - buttonEnabled = false; - CharPanelButtonActive = false; - InfoString = StringOrView {}; - FloatingInfoString = StringOrView {}; - RedrawComponent(PanelDrawComponent::Health); - RedrawComponent(PanelDrawComponent::Mana); - CloseCharPanel(); - SpellSelectFlag = false; - SpellbookTab = 0; - SpellbookFlag = false; - - if (!HeadlessMode) { - InitSpellBook(); - ASSIGN_OR_RETURN(pQLogCel, LoadCelWithStatus("data\\quest", static_cast(SidePanelSize.width))); - ASSIGN_OR_RETURN(GoldBoxBuffer, LoadCelWithStatus("ctrlpan\\golddrop", 261)); - } - CloseGoldDrop(); - CalculatePanelAreas(); - - if (!HeadlessMode) - InitModifierHints(); - - return {}; -} - -void DrawMainPanel(const Surface &out) -{ - DrawPanelBox(out, MakeSdlRect(0, sgbPlrTalkTbl + PanelPaddingHeight, GetMainPanel().size.width, GetMainPanel().size.height), GetMainPanel().position); - DrawInfoBox(out); -} - -void DrawMainPanelButtons(const Surface &out) -{ - const Point mainPanelPosition = GetMainPanel().position; - - for (int i = 0; i < TotalSpMainPanelButtons; i++) { - if (!MainPanelButtons[i]) { - DrawPanelBox(out, MakeSdlRect(MainPanelButtonRect[i].position.x, MainPanelButtonRect[i].position.y + PanelPaddingHeight, MainPanelButtonRect[i].size.width, MainPanelButtonRect[i].size.height + 1), mainPanelPosition + Displacement { MainPanelButtonRect[i].position.x, MainPanelButtonRect[i].position.y }); - } else { - const Point position = mainPanelPosition + Displacement { MainPanelButtonRect[i].position.x, MainPanelButtonRect[i].position.y }; - RenderClxSprite(out, (*pMainPanelButtons)[i], position); - RenderClxSprite(out, (*PanelButtonDown)[i], position + Displacement { 4, 0 }); - } - } - - if (IsChatAvailable()) { - RenderClxSprite(out, (*multiButtons)[MainPanelButtons[PanelButtonSendmsg] ? 1 : 0], mainPanelPosition + Displacement { MainPanelButtonRect[PanelButtonSendmsg].position.x, MainPanelButtonRect[PanelButtonSendmsg].position.y }); - - const Point friendlyButtonPosition = mainPanelPosition + Displacement { MainPanelButtonRect[PanelButtonFriendly].position.x, MainPanelButtonRect[PanelButtonFriendly].position.y }; - - if (MyPlayer->friendlyMode) - RenderClxSprite(out, (*multiButtons)[MainPanelButtons[PanelButtonFriendly] ? 3 : 2], friendlyButtonPosition); - else - RenderClxSprite(out, (*multiButtons)[MainPanelButtons[PanelButtonFriendly] ? 5 : 4], friendlyButtonPosition); - } -} - -void ResetMainPanelButtons() -{ - for (bool &panelButton : MainPanelButtons) - panelButton = false; - SetMainPanelButtonUp(); -} - -void CheckMainPanelButton() -{ - const int totalButtons = IsChatAvailable() ? TotalMpMainPanelButtons : TotalSpMainPanelButtons; - - for (int i = 0; i < totalButtons; i++) { - Rectangle button = MainPanelButtonRect[i]; - - SetPanelObjectPosition(UiPanels::Main, button); - - if (button.contains(MousePosition)) { - SetMainPanelButtonDown(i); - } - } - - Rectangle spellSelectButton = SpellButtonRect; - - SetPanelObjectPosition(UiPanels::Main, spellSelectButton); - - if (!SpellSelectFlag && spellSelectButton.contains(MousePosition)) { - if ((SDL_GetModState() & SDL_KMOD_SHIFT) != 0) { - Player &myPlayer = *MyPlayer; - myPlayer._pRSpell = SpellID::Invalid; - myPlayer._pRSplType = SpellType::Invalid; - RedrawEverything(); - return; - } - DoSpeedBook(); - gamemenu_off(); - } -} - -void CheckMainPanelButtonDead() -{ - Rectangle menuButton = MainPanelButtonRect[PanelButtonMainmenu]; - - SetPanelObjectPosition(UiPanels::Main, menuButton); - - if (menuButton.contains(MousePosition)) { - SetMainPanelButtonDown(PanelButtonMainmenu); - return; - } - - Rectangle chatButton = MainPanelButtonRect[PanelButtonSendmsg]; - - SetPanelObjectPosition(UiPanels::Main, chatButton); - - if (chatButton.contains(MousePosition)) { - SetMainPanelButtonDown(PanelButtonSendmsg); - } -} - -void DoAutoMap() -{ - if (!AutomapActive) - StartAutomap(); - else - AutomapActive = false; -} - -void CycleAutomapType() -{ - if (!AutomapActive) { - StartAutomap(); - return; - } - const AutomapType newType { static_cast>( - (static_cast(GetAutomapType()) + 1) % enum_size::value) }; - SetAutomapType(newType); - if (newType == AutomapType::FIRST) { - AutomapActive = false; - } -} - -void CheckPanelInfo() -{ - MainPanelFlag = false; - InfoString = StringOrView {}; - FloatingInfoString = StringOrView {}; - - const int totalButtons = IsChatAvailable() ? TotalMpMainPanelButtons : TotalSpMainPanelButtons; - - for (int i = 0; i < totalButtons; i++) { - Rectangle button = MainPanelButtonRect[i]; - - SetPanelObjectPosition(UiPanels::Main, button); - - if (button.contains(MousePosition)) { - if (i != 7) { - InfoString = _(PanBtnStr[i]); - } else { - if (MyPlayer->friendlyMode) - InfoString = _("Player friendly"); - else - InfoString = _("Player attack"); - } - if (PanBtnHotKey[i] != nullptr) { - AddInfoBoxString(fmt::format(fmt::runtime(_("Hotkey: {:s}")), _(PanBtnHotKey[i]))); - } - InfoColor = UiFlags::ColorWhite; - MainPanelFlag = true; - } - } - - Rectangle spellSelectButton = SpellButtonRect; - - SetPanelObjectPosition(UiPanels::Main, spellSelectButton); - - if (!SpellSelectFlag && spellSelectButton.contains(MousePosition)) { - InfoString = _("Select current spell button"); - InfoColor = UiFlags::ColorWhite; - MainPanelFlag = true; - AddInfoBoxString(_("Hotkey: 's'")); - const Player &myPlayer = *MyPlayer; - const SpellID spellId = myPlayer._pRSpell; - if (IsValidSpell(spellId)) { - switch (myPlayer._pRSplType) { - case SpellType::Skill: - AddInfoBoxString(fmt::format(fmt::runtime(_("{:s} Skill")), pgettext("spell", GetSpellData(spellId).sNameText))); - break; - case SpellType::Spell: { - AddInfoBoxString(fmt::format(fmt::runtime(_("{:s} Spell")), pgettext("spell", GetSpellData(spellId).sNameText))); - const int spellLevel = myPlayer.GetSpellLevel(spellId); - AddInfoBoxString(spellLevel == 0 ? _("Spell Level 0 - Unusable") : fmt::format(fmt::runtime(_("Spell Level {:d}")), spellLevel)); - } break; - case SpellType::Scroll: { - AddInfoBoxString(fmt::format(fmt::runtime(_("Scroll of {:s}")), pgettext("spell", GetSpellData(spellId).sNameText))); - const int scrollCount = c_count_if(InventoryAndBeltPlayerItemsRange { myPlayer }, [spellId](const Item &item) { - return item.isScrollOf(spellId); - }); - AddInfoBoxString(fmt::format(fmt::runtime(ngettext("{:d} Scroll", "{:d} Scrolls", scrollCount)), scrollCount)); - } break; - case SpellType::Charges: - AddInfoBoxString(fmt::format(fmt::runtime(_("Staff of {:s}")), pgettext("spell", GetSpellData(spellId).sNameText))); - AddInfoBoxString(fmt::format(fmt::runtime(ngettext("{:d} Charge", "{:d} Charges", myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges)), myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges)); - break; - case SpellType::Invalid: - break; - } - } - } - - Rectangle belt = BeltRect; - - SetPanelObjectPosition(UiPanels::Main, belt); - - if (belt.contains(MousePosition)) - pcursinvitem = CheckInvHLight(); - - if (CheckXPBarInfo()) - MainPanelFlag = true; -} - -void CheckMainPanelButtonUp() -{ - bool gamemenuOff = true; - - SetMainPanelButtonUp(); - - for (int i = PanelButtonFirst; i <= PanelButtonLast; i++) { - if (!MainPanelButtons[i]) - continue; - - MainPanelButtons[i] = false; - - Rectangle button = MainPanelButtonRect[i]; - - SetPanelObjectPosition(UiPanels::Main, button); - - if (!button.contains(MousePosition)) - continue; - - switch (i) { - case PanelButtonCharinfo: - ToggleCharPanel(); - break; - case PanelButtonQlog: - CloseCharPanel(); - CloseGoldWithdraw(); - CloseStash(); - if (!QuestLogIsOpen) - StartQuestlog(); - else - QuestLogIsOpen = false; - break; - case PanelButtonAutomap: - DoAutoMap(); - break; - case PanelButtonMainmenu: - if (MyPlayerIsDead) { - if (!gbIsMultiplayer) { - if (gbValidSaveFile) - gamemenu_load_game(false); - else - gamemenu_exit_game(false); - } else { - NetSendCmd(true, CMD_RETOWN); - } - break; - } else if (MyPlayer->hasNoLife()) { - break; - } - qtextflag = false; - gamemenu_handle_previous(); - gamemenuOff = false; - break; - case PanelButtonInventory: - SpellbookFlag = false; - CloseGoldWithdraw(); - CloseStash(); - invflag = !invflag; - CloseGoldDrop(); - break; - case PanelButtonSpellbook: - CloseInventory(); - CloseGoldDrop(); - SpellbookFlag = !SpellbookFlag; - break; - case PanelButtonSendmsg: - if (ChatFlag) - ResetChat(); - else - TypeChatMessage(); - break; - case PanelButtonFriendly: - // Toggle friendly Mode - NetSendCmd(true, CMD_FRIENDLYMODE); - break; - } - } - - if (gamemenuOff) - gamemenu_off(); -} - -void FreeControlPan() -{ - BottomBuffer = std::nullopt; - pManaBuff = std::nullopt; - pLifeBuff = std::nullopt; - FreeLargeSpellIcons(); - FreeSpellBook(); - pMainPanelButtons = std::nullopt; - multiButtons = std::nullopt; - talkButtons = std::nullopt; - pChrButtons = std::nullopt; - pDurIcons = std::nullopt; - pQLogCel = std::nullopt; - GoldBoxBuffer = std::nullopt; - FreeMainPanel(); - FreePartyPanel(); - FreeCharPanel(); - FreeModifierHints(); -} - -void DrawInfoBox(const Surface &out) -{ - DrawPanelBox(out, MakeSdlRect(InfoBoxRect.position.x, InfoBoxRect.position.y + PanelPaddingHeight, InfoBoxRect.size.width, InfoBoxRect.size.height), GetMainPanel().position + Displacement { InfoBoxRect.position.x, InfoBoxRect.position.y }); - if (!MainPanelFlag && !trigflag && pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell && !SpellSelectFlag && pcurs != CURSOR_HOURGLASS) { - InfoString = StringOrView {}; - InfoColor = UiFlags::ColorWhite; - } - const Player &myPlayer = *MyPlayer; - if (SpellSelectFlag || trigflag || pcurs == CURSOR_HOURGLASS) { - InfoColor = UiFlags::ColorWhite; - } else if (!myPlayer.HoldItem.isEmpty()) { - if (myPlayer.HoldItem._itype == ItemType::Gold) { - const int nGold = myPlayer.HoldItem._ivalue; - InfoString = fmt::format(fmt::runtime(ngettext("{:s} gold piece", "{:s} gold pieces", nGold)), FormatInteger(nGold)); - } else if (!myPlayer.CanUseItem(myPlayer.HoldItem)) { - InfoString = _("Requirements not met"); - } else { - InfoString = myPlayer.HoldItem.getName(); - InfoColor = myPlayer.HoldItem.getTextColor(); - } - } else { - if (pcursitem != -1) - GetItemStr(Items[pcursitem]); - else if (ObjectUnderCursor != nullptr) - GetObjectStr(*ObjectUnderCursor); - if (pcursmonst != -1) { - if (leveltype != DTYPE_TOWN) { - const Monster &monster = Monsters[pcursmonst]; - InfoColor = UiFlags::ColorWhite; - InfoString = monster.name(); - if (monster.isUnique()) { - InfoColor = UiFlags::ColorWhitegold; - PrintUniqueHistory(); - } else { - PrintMonstHistory(monster.type().type); - } - } else if (pcursitem == -1) { - InfoString = std::string_view(Towners[pcursmonst].name); - } - } - if (PlayerUnderCursor != nullptr) { - InfoColor = UiFlags::ColorWhitegold; - const auto &target = *PlayerUnderCursor; - InfoString = std::string_view(target._pName); - AddInfoBoxString(fmt::format(fmt::runtime(_("{:s}, Level: {:d}")), target.getClassName(), target.getCharacterLevel())); - AddInfoBoxString(fmt::format(fmt::runtime(_("Hit Points {:d} of {:d}")), target._pHitPoints >> 6, target._pMaxHP >> 6)); - } - if (PortraitIdUnderCursor != -1) { - InfoColor = UiFlags::ColorWhitegold; - auto &target = Players[PortraitIdUnderCursor]; - InfoString = std::string_view(target._pName); - AddInfoBoxString(_("Right click to inspect")); - } - } - if (!InfoString.empty()) - PrintInfo(out); -} - -void DrawFloatingInfoBox(const Surface &out) -{ - if (pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell) { - FloatingInfoString = StringOrView {}; - InfoColor = UiFlags::ColorWhite; - } - - if (!FloatingInfoString.empty()) - PrintFloatingInfo(out); -} - -void CheckLevelButton() -{ - if (!IsLevelUpButtonVisible()) { - return; - } - - Rectangle button = LevelButtonRect; - - SetPanelObjectPosition(UiPanels::Main, button); - - if (!LevelButtonDown && button.contains(MousePosition)) - LevelButtonDown = true; -} - -void CheckLevelButtonUp() -{ - Rectangle button = LevelButtonRect; - - SetPanelObjectPosition(UiPanels::Main, button); - - if (button.contains(MousePosition)) { - OpenCharPanel(); - } - LevelButtonDown = false; -} - -void DrawLevelButton(const Surface &out) -{ - if (IsLevelUpButtonVisible()) { - const int nCel = LevelButtonDown ? 2 : 1; - DrawString(out, _("Level Up"), { GetMainPanel().position + Displacement { 0, LevelButtonRect.position.y - 23 }, { 120, 0 } }, - { .flags = UiFlags::ColorWhite | UiFlags::AlignCenter | UiFlags::KerningFitSpacing }); - RenderClxSprite(out, (*pChrButtons)[nCel], GetMainPanel().position + Displacement { LevelButtonRect.position.x, LevelButtonRect.position.y }); - } -} - -void CheckChrBtns() -{ - const Player &myPlayer = *MyPlayer; - - if (CharPanelButtonActive || myPlayer._pStatPts == 0) - return; - - for (auto attribute : enum_values()) { - if (myPlayer.GetBaseAttributeValue(attribute) >= myPlayer.GetMaximumAttributeValue(attribute)) - continue; - auto buttonId = static_cast(attribute); - Rectangle button = CharPanelButtonRect[buttonId]; - SetPanelObjectPosition(UiPanels::Character, button); - if (button.contains(MousePosition)) { - CharPanelButton[buttonId] = true; - CharPanelButtonActive = true; - } - } -} - -void ReleaseChrBtns(bool addAllStatPoints) -{ - CharPanelButtonActive = false; - for (auto attribute : enum_values()) { - auto buttonId = static_cast(attribute); - if (!CharPanelButton[buttonId]) - continue; - - CharPanelButton[buttonId] = false; - Rectangle button = CharPanelButtonRect[buttonId]; - SetPanelObjectPosition(UiPanels::Character, button); - if (button.contains(MousePosition)) { - Player &myPlayer = *MyPlayer; - int statPointsToAdd = 1; - if (addAllStatPoints) - statPointsToAdd = CapStatPointsToAdd(myPlayer._pStatPts, myPlayer, attribute); - switch (attribute) { - case CharacterAttribute::Strength: - NetSendCmdParam1(true, CMD_ADDSTR, statPointsToAdd); - myPlayer._pStatPts -= statPointsToAdd; - break; - case CharacterAttribute::Magic: - NetSendCmdParam1(true, CMD_ADDMAG, statPointsToAdd); - myPlayer._pStatPts -= statPointsToAdd; - break; - case CharacterAttribute::Dexterity: - NetSendCmdParam1(true, CMD_ADDDEX, statPointsToAdd); - myPlayer._pStatPts -= statPointsToAdd; - break; - case CharacterAttribute::Vitality: - NetSendCmdParam1(true, CMD_ADDVIT, statPointsToAdd); - myPlayer._pStatPts -= statPointsToAdd; - break; - } - } - } -} - -void DrawDurIcon(const Surface &out) -{ - const bool hasRoomBetweenPanels = RightPanel.position.x - (LeftPanel.position.x + LeftPanel.size.width) >= 16 + (32 + 8 + 32 + 8 + 32 + 8 + 32) + 16; - const bool hasRoomUnderPanels = MainPanel.position.y - (RightPanel.position.y + RightPanel.size.height) >= 16 + 32 + 16; - - if (!hasRoomBetweenPanels && !hasRoomUnderPanels) { - if (IsLeftPanelOpen() && IsRightPanelOpen()) - return; - } - - int x = MainPanel.position.x + MainPanel.size.width - 32 - 16; - if (!hasRoomUnderPanels) { - if (IsRightPanelOpen() && MainPanel.position.x + MainPanel.size.width > RightPanel.position.x) - x -= MainPanel.position.x + MainPanel.size.width - RightPanel.position.x; - } - - Player &myPlayer = *MyPlayer; - x = DrawDurIcon4Item(out, myPlayer.InvBody[INVLOC_HEAD], x, 3); - x = DrawDurIcon4Item(out, myPlayer.InvBody[INVLOC_CHEST], x, 2); - x = DrawDurIcon4Item(out, myPlayer.InvBody[INVLOC_HAND_LEFT], x, 0); - DrawDurIcon4Item(out, myPlayer.InvBody[INVLOC_HAND_RIGHT], x, 0); -} - -void RedBack(const Surface &out) -{ - uint8_t *dst = out.begin(); - uint8_t *tbl = GetPauseTRN(); - for (int h = gnViewportHeight; h != 0; h--, dst += out.pitch() - gnScreenWidth) { - for (int w = gnScreenWidth; w != 0; w--) { - if (leveltype != DTYPE_HELL || *dst >= 32) - *dst = tbl[*dst]; - dst++; - } - } -} - -void DrawDeathText(const Surface &out) -{ - const TextRenderOptions largeTextOptions { - .flags = UiFlags::FontSize42 | UiFlags::ColorGold | UiFlags::AlignCenter | UiFlags::VerticalCenter, - .spacing = 2 - }; - const TextRenderOptions smallTextOptions { - .flags = UiFlags::FontSize30 | UiFlags::ColorGold | UiFlags::AlignCenter | UiFlags::VerticalCenter, - .spacing = 2 - }; - std::string text; - const int verticalPadding = 42; - Point linePosition { 0, gnScreenHeight / 2 - (verticalPadding * 2) }; - - text = _("You have died"); - DrawString(out, text, linePosition, largeTextOptions); - linePosition.y += verticalPadding; - - std::string buttonText; - - switch (ControlMode) { - case ControlTypes::KeyboardAndMouse: - buttonText = _("ESC"); - break; - case ControlTypes::Gamepad: - buttonText = ToString(GamepadType, ControllerButton_BUTTON_START); - break; - case ControlTypes::VirtualGamepad: - buttonText = _("Menu Button"); - break; - default: - break; - } - - if (!gbIsMultiplayer) { - if (gbValidSaveFile) - text = fmt::format(fmt::runtime(_("Press {} to load last save.")), buttonText); - else - text = fmt::format(fmt::runtime(_("Press {} to return to Main Menu.")), buttonText); - - } else { - text = fmt::format(fmt::runtime(_("Press {} to restart in town.")), buttonText); - } - DrawString(out, text, linePosition, smallTextOptions); -} - -void DrawGoldSplit(const Surface &out) -{ - const int dialogX = 30; - - ClxDraw(out, GetPanelPosition(UiPanels::Inventory, { dialogX, 178 }), (*GoldBoxBuffer)[0]); - - const std::string_view amountText = GoldDropText; - const TextInputCursorState &cursor = GoldDropCursor; - const int max = GetGoldDropMax(); - - const std::string description = fmt::format( - fmt::runtime(ngettext( - /* TRANSLATORS: {:s} is a number with separators. Dialog is shown when splitting a stash of Gold.*/ - "You have {:s} gold piece. How many do you want to remove?", - "You have {:s} gold pieces. How many do you want to remove?", - max)), - FormatInteger(max)); - - // Pre-wrap the string at spaces, otherwise DrawString would hard wrap in the middle of words - const std::string wrapped = WordWrapString(description, 200); - - // The split gold dialog is roughly 4 lines high, but we need at least one line for the player to input an amount. - // Using a clipping region 50 units high (approx 3 lines with a lineheight of 17) to ensure there is enough room left - // for the text entered by the player. - DrawString(out, wrapped, { GetPanelPosition(UiPanels::Inventory, { dialogX + 31, 75 }), { 200, 50 } }, - { .flags = UiFlags::ColorWhitegold | UiFlags::AlignCenter, .lineHeight = 17 }); - - // Even a ten digit amount of gold only takes up about half a line. There's no need to wrap or clip text here so we - // use the Point form of DrawString. - DrawString(out, amountText, GetPanelPosition(UiPanels::Inventory, { dialogX + 37, 128 }), - { - .flags = UiFlags::ColorWhite | UiFlags::PentaCursor, - .cursorPosition = static_cast(cursor.position), - .highlightRange = { static_cast(cursor.selection.begin), static_cast(cursor.selection.end) }, - }); -} - -void control_drop_gold(SDL_Keycode vkey) -{ - Player &myPlayer = *MyPlayer; - - if (myPlayer.hasNoLife()) { - CloseGoldDrop(); - return; - } - - switch (vkey) { - case SDLK_RETURN: - case SDLK_KP_ENTER: - if (const int value = GoldDropInputState->value(); value != 0) { - RemoveGold(myPlayer, GoldDropInvIndex, value); - } - CloseGoldDrop(); - break; - case SDLK_ESCAPE: - CloseGoldDrop(); - break; - default: - break; - } -} - -void DrawChatBox(const Surface &out) -{ - if (!ChatFlag) - return; - - const Point mainPanelPosition = GetMainPanel().position; - - DrawPanelBox(out, MakeSdlRect(175, sgbPlrTalkTbl + 20, 294, 5), mainPanelPosition + Displacement { 175, 4 }); - int off = 0; - for (int i = 293; i > 283; off++, i--) { - DrawPanelBox(out, MakeSdlRect((off / 2) + 175, sgbPlrTalkTbl + off + 25, i, 1), mainPanelPosition + Displacement { (off / 2) + 175, off + 9 }); - } - DrawPanelBox(out, MakeSdlRect(185, sgbPlrTalkTbl + 35, 274, 30), mainPanelPosition + Displacement { 185, 19 }); - DrawPanelBox(out, MakeSdlRect(180, sgbPlrTalkTbl + 65, 284, 5), mainPanelPosition + Displacement { 180, 49 }); - for (int i = 0; i < 10; i++) { - DrawPanelBox(out, MakeSdlRect(180, sgbPlrTalkTbl + i + 70, i + 284, 1), mainPanelPosition + Displacement { 180, i + 54 }); - } - DrawPanelBox(out, MakeSdlRect(170, sgbPlrTalkTbl + 80, 310, 55), mainPanelPosition + Displacement { 170, 64 }); - - int x = mainPanelPosition.x + 200; - const int y = mainPanelPosition.y + 10; - - const uint32_t len = DrawString(out, TalkMessage, { { x, y }, { 250, 39 } }, - { - .flags = UiFlags::ColorWhite | UiFlags::PentaCursor, - .lineHeight = 13, - .cursorPosition = static_cast(ChatCursor.position), - .highlightRange = { static_cast(ChatCursor.selection.begin), static_cast(ChatCursor.selection.end) }, - }); - ChatInputState->truncate(len); - - x += 46; - int talkBtn = 0; - for (size_t i = 0; i < Players.size(); i++) { - Player &player = Players[i]; - if (&player == MyPlayer) - continue; - - const UiFlags color = player.friendlyMode ? UiFlags::ColorWhitegold : UiFlags::ColorRed; - const Point talkPanPosition = mainPanelPosition + Displacement { 172, 84 + 18 * talkBtn }; - if (WhisperList[i]) { - // the normal (unpressed) voice button is pre-rendered on the panel, only need to draw over it when the button is held - if (TalkButtonsDown[talkBtn]) { - const unsigned spriteIndex = talkBtn == 0 ? 2 : 3; // the first button sprite includes a tip from the devils wing so is different to the rest. - ClxDraw(out, talkPanPosition, (*talkButtons)[spriteIndex]); - - // Draw the translated string over the top of the default (english) button. This graphic is inset to avoid overlapping the wingtip, letting - // the first button be treated the same as the other two further down the panel. - RenderClxSprite(out, (*TalkButton)[2], talkPanPosition + Displacement { 4, -15 }); - } - } else { - unsigned spriteIndex = talkBtn == 0 ? 0 : 1; // the first button sprite includes a tip from the devils wing so is different to the rest. - if (TalkButtonsDown[talkBtn]) - spriteIndex += 4; // held button sprites are at index 4 and 5 (with and without wingtip respectively) - ClxDraw(out, talkPanPosition, (*talkButtons)[spriteIndex]); - - // Draw the translated string over the top of the default (english) button. This graphic is inset to avoid overlapping the wingtip, letting - // the first button be treated the same as the other two further down the panel. - RenderClxSprite(out, (*TalkButton)[TalkButtonsDown[talkBtn] ? 1 : 0], talkPanPosition + Displacement { 4, -15 }); - } - if (player.plractive) { - DrawString(out, player._pName, { { x, y + 60 + talkBtn * 18 }, { 204, 0 } }, { .flags = color }); - } - - talkBtn++; - } -} - -bool CheckMuteButton() -{ - if (!ChatFlag) - return false; - - Rectangle buttons = MuteButtonRect; - - SetPanelObjectPosition(UiPanels::Main, buttons); - - buttons.size.height = (MuteButtons * buttons.size.height) + ((MuteButtons - 1) * MuteButtonPadding); - - if (!buttons.contains(MousePosition)) - return false; - - for (bool &talkButtonDown : TalkButtonsDown) { - talkButtonDown = false; - } - - const Point mainPanelPosition = GetMainPanel().position; - - TalkButtonsDown[(MousePosition.y - (69 + mainPanelPosition.y)) / 18] = true; - - return true; -} - -void CheckMuteButtonUp() -{ - if (!ChatFlag) - return; - - for (bool &talkButtonDown : TalkButtonsDown) - talkButtonDown = false; - - Rectangle buttons = MuteButtonRect; - - SetPanelObjectPosition(UiPanels::Main, buttons); - - buttons.size.height = (MuteButtons * buttons.size.height) + ((MuteButtons - 1) * MuteButtonPadding); - - if (!buttons.contains(MousePosition)) - return; - - int off = (MousePosition.y - buttons.position.y) / (MuteButtonRect.size.height + MuteButtonPadding); - - size_t playerId = 0; - for (; playerId < Players.size() && off != -1; ++playerId) { - if (playerId != MyPlayerId) - off--; - } - if (playerId > 0 && playerId <= Players.size()) - WhisperList[playerId - 1] = !WhisperList[playerId - 1]; -} - -void TypeChatMessage() -{ - if (!IsChatAvailable()) - return; - - ChatFlag = true; - TalkMessage[0] = '\0'; - ChatInputState.emplace(TextInputState::Options { - .value = TalkMessage, - .cursor = &ChatCursor, - .maxLength = sizeof(TalkMessage) - 1 }); - for (bool &talkButtonDown : TalkButtonsDown) { - talkButtonDown = false; - } - sgbPlrTalkTbl = GetMainPanel().size.height + PanelPaddingHeight; - RedrawEverything(); - TalkSaveIndex = NextTalkSave; - - SDL_Rect rect = MakeSdlRect(GetMainPanel().position.x + 200, GetMainPanel().position.y + 22, 0, 27); - SDL_SetTextInputArea(ghMainWnd, &rect, /*cursor=*/0); - SDLC_StartTextInput(ghMainWnd); -} - -void ResetChat() -{ - ChatFlag = false; - SDLC_StopTextInput(ghMainWnd); - ChatCursor = {}; - ChatInputState = std::nullopt; - sgbPlrTalkTbl = 0; - RedrawEverything(); -} - -bool IsChatActive() -{ - if (!IsChatAvailable()) - return false; - - if (!ChatFlag) - return false; - - return true; -} - -template -bool HandleInputEvent(const SDL_Event &event, std::optional &inputState) -{ - if (!inputState) { - return false; // No input state to handle - } - - if constexpr (std::is_same_v) { - return HandleTextInputEvent(event, *inputState); - } else if constexpr (std::is_same_v) { - return HandleNumberInputEvent(event, *inputState); - } - - return false; // Unknown input state type -} - -bool HandleTalkTextInputEvent(const SDL_Event &event) -{ - return HandleInputEvent(event, ChatInputState); -} - -bool CheckKeypress(SDL_Keycode vkey) -{ - if (!IsChatAvailable()) - return false; - if (!ChatFlag) - return false; - - switch (vkey) { - case SDLK_ESCAPE: - ResetChat(); - return true; - case SDLK_RETURN: - case SDLK_KP_ENTER: - ControlPressEnter(); - return true; - case SDLK_DOWN: - ControlUpDown(1); - return true; - case SDLK_UP: - ControlUpDown(-1); - return true; - default: - return vkey >= SDLK_SPACE && vkey <= SDLK_Z; - } -} - -void DiabloHotkeyMsg(uint32_t dwMsg) -{ - assert(dwMsg < QuickMessages.size()); - -#ifdef _DEBUG - constexpr std::string_view LuaPrefix = "/lua "; - for (const std::string &msg : GetOptions().Chat.szHotKeyMsgs[dwMsg]) { - if (!msg.starts_with(LuaPrefix)) continue; - InitConsole(); - RunInConsole(std::string_view(msg).substr(LuaPrefix.size())); - } -#endif - - if (!IsChatAvailable()) { - return; - } - - for (const std::string &msg : GetOptions().Chat.szHotKeyMsgs[dwMsg]) { -#ifdef _DEBUG - if (msg.starts_with(LuaPrefix)) continue; -#endif - char charMsg[MAX_SEND_STR_LEN]; - CopyUtf8(charMsg, msg, sizeof(charMsg)); - NetSendCmdString(0xFFFFFF, charMsg); - } -} - -void OpenGoldDrop(int8_t invIndex, int max) -{ - DropGoldFlag = true; - GoldDropInvIndex = invIndex; - GoldDropText[0] = '\0'; - GoldDropInputState.emplace(NumberInputState::Options { - .textOptions { - .value = GoldDropText, - .cursor = &GoldDropCursor, - .maxLength = sizeof(GoldDropText) - 1, - }, - .min = 0, - .max = max, - }); - SDLC_StartTextInput(ghMainWnd); -} - -void CloseGoldDrop() -{ - if (!DropGoldFlag) - return; - SDLC_StopTextInput(ghMainWnd); - DropGoldFlag = false; - GoldDropInputState = std::nullopt; - GoldDropInvIndex = 0; -} - -int GetGoldDropMax() -{ - return GoldDropInputState->max(); -} - -bool HandleGoldDropTextInputEvent(const SDL_Event &event) -{ - return HandleInputEvent(event, GoldDropInputState); -} - -} // namespace devilution diff --git a/Source/control.h b/Source/control/control.hpp similarity index 91% rename from Source/control.h rename to Source/control/control.hpp index e7d78e837..fe5f08489 100644 --- a/Source/control.h +++ b/Source/control/control.hpp @@ -1,8 +1,3 @@ -/** - * @file control.h - * - * Interface of the character and main control panels - */ #pragma once #include @@ -33,8 +28,8 @@ #include "engine/render/text_render.hpp" #include "engine/size.hpp" #include "panels/ui_panels.hpp" -#include "spelldat.h" #include "spells.h" +#include "tables/spelldat.h" #include "utils/attributes.h" #include "utils/string_or_view.hpp" #include "utils/ui_fwd.h" @@ -45,34 +40,37 @@ constexpr Size SidePanelSize { 320, 352 }; constexpr Rectangle InfoBoxRect = { { 177, 46 }, { 288, 64 } }; -extern bool DropGoldFlag; -extern TextInputCursorState GoldDropCursor; -extern char GoldDropText[21]; - extern bool CharPanelButton[4]; -extern bool LevelButtonDown; extern bool CharPanelButtonActive; -extern UiFlags InfoColor; + extern int SpellbookTab; -extern bool ChatFlag; -extern bool SpellbookFlag; -extern bool CharFlag; + +extern UiFlags InfoColor; + extern StringOrView InfoString; extern StringOrView FloatingInfoString; -extern bool MainPanelFlag; + +extern Rectangle MainPanelButtonRect[8]; +extern Rectangle CharPanelButtonRect[4]; + extern bool MainPanelButtonDown; -extern bool SpellSelectFlag; -const Rectangle &GetMainPanel(); -const Rectangle &GetLeftPanel(); -const Rectangle &GetRightPanel(); -bool IsLeftPanelOpen(); -bool IsRightPanelOpen(); +extern bool LevelButtonDown; + extern std::optional BottomBuffer; extern OptionalOwnedClxSpriteList GoldBoxBuffer; -extern Rectangle MainPanelButtonRect[8]; +extern bool MainPanelFlag; +extern bool ChatFlag; +extern bool SpellbookFlag; +extern bool CharFlag; +extern bool SpellSelectFlag; + +[[nodiscard]] const Rectangle &GetMainPanel(); +[[nodiscard]] const Rectangle &GetLeftPanel(); +[[nodiscard]] const Rectangle &GetRightPanel(); +bool IsLeftPanelOpen(); +bool IsRightPanelOpen(); void CalculatePanelAreas(); -bool IsChatAvailable(); /** * @brief Moves the mouse to the first attribute "+" button. @@ -85,7 +83,7 @@ void ToggleCharPanel(); /** * @brief Check if the UI can cover the game area entirely */ -inline bool CanPanelsCoverView() +[[nodiscard]] inline bool CanPanelsCoverView() { const Rectangle &mainPanel = GetMainPanel(); return GetScreenWidth() <= mainPanel.size.width && GetScreenHeight() <= SidePanelSize.height + mainPanel.size.height; @@ -96,46 +94,6 @@ void AddInfoBoxString(std::string &&str, bool floatingBox = false); void DrawPanelBox(const Surface &out, SDL_Rect srcRect, Point targetPosition); Point GetPanelPosition(UiPanels panel, Point offset = { 0, 0 }); -/** - * Draws the top dome of the life flask (that part that protrudes out of the control panel). - * The empty flask cel is drawn from the top of the flask to the fill level (there is always a 2 pixel "air gap") and - * the filled flask cel is drawn from that level to the top of the control panel if required. - */ -void DrawLifeFlaskUpper(const Surface &out); - -/** - * Controls the drawing of the area of the life flask within the control panel. - * First sets the fill amount then draws the empty flask cel portion then the filled - * flask portion. - */ -void DrawLifeFlaskLower(const Surface &out, bool drawFilledPortion); - -/** - * Draws the top dome of the mana flask (that part that protrudes out of the control panel). - * The empty flask cel is drawn from the top of the flask to the fill level (there is always a 2 pixel "air gap") and - * the filled flask cel is drawn from that level to the top of the control panel if required. - */ -void DrawManaFlaskUpper(const Surface &out); - -/** - * Controls the drawing of the area of the mana flask within the control panel. - */ -void DrawManaFlaskLower(const Surface &out, bool drawFilledPortion); - -/** - * Controls drawing of current / max values (health, mana) within the control panel. - */ -void DrawFlaskValues(const Surface &out, Point pos, int currValue, int maxValue); - -/** - * @brief calls on the active player object to update HP/Mana percentage variables - * - * This is used to ensure that DrawFlaskAbovePanel routines display an accurate representation of the players health/mana - * - * @see Player::UpdateHitPointPercentage() and Player::UpdateManaPercentage() - */ -void UpdateLifeManaPercent(); - tl::expected InitMainPanel(); void DrawMainPanel(const Surface &out); @@ -186,21 +144,66 @@ void DrawDurIcon(const Surface &out); void RedBack(const Surface &out); void DrawDeathText(const Surface &out); void DrawSpellBook(const Surface &out); -void DrawGoldSplit(const Surface &out); -void control_drop_gold(SDL_Keycode vkey); + +extern Rectangle CharPanelButtonRect[4]; + +bool CheckKeypress(SDL_Keycode vkey); +void DiabloHotkeyMsg(uint32_t dwMsg); void DrawChatBox(const Surface &out); bool CheckMuteButton(); void CheckMuteButtonUp(); void TypeChatMessage(); void ResetChat(); bool IsChatActive(); +bool IsChatAvailable(); bool HandleTalkTextInputEvent(const SDL_Event &event); -bool CheckKeypress(SDL_Keycode vkey); -void DiabloHotkeyMsg(uint32_t dwMsg); + +/** + * Draws the top dome of the life flask (that part that protrudes out of the control panel). + * The empty flask cel is drawn from the top of the flask to the fill level (there is always a 2 pixel "air gap") and + * the filled flask cel is drawn from that level to the top of the control panel if required. + */ +void DrawLifeFlaskUpper(const Surface &out); + +/** + * Controls the drawing of the area of the life flask within the control panel. + * First sets the fill amount then draws the empty flask cel portion then the filled + * flask portion. + */ +void DrawLifeFlaskLower(const Surface &out, bool drawFilledPortion); + +/** + * Draws the top dome of the mana flask (that part that protrudes out of the control panel). + * The empty flask cel is drawn from the top of the flask to the fill level (there is always a 2 pixel "air gap") and + * the filled flask cel is drawn from that level to the top of the control panel if required. + */ +void DrawManaFlaskUpper(const Surface &out); + +/** + * Controls the drawing of the area of the mana flask within the control panel. + */ +void DrawManaFlaskLower(const Surface &out, bool drawFilledPortion); + +/** + * Controls drawing of current / max values (health, mana) within the control panel. + */ +void DrawFlaskValues(const Surface &out, Point pos, int currValue, int maxValue); + +/** + * @brief calls on the active player object to update HP/Mana percentage variables + * + * This is used to ensure that DrawFlaskAbovePanel routines display an accurate representation of the players health/mana + * + * @see Player::UpdateHitPointPercentage() and Player::UpdateManaPercentage() + */ +void UpdateLifeManaPercent(); + +extern bool DropGoldFlag; + +void DrawGoldSplit(const Surface &out); +void control_drop_gold(SDL_Keycode vkey); void OpenGoldDrop(int8_t invIndex, int max); void CloseGoldDrop(); -int GetGoldDropMax(); bool HandleGoldDropTextInputEvent(const SDL_Event &event); -extern Rectangle CharPanelButtonRect[4]; } // namespace devilution diff --git a/Source/control/control_chat.cpp b/Source/control/control_chat.cpp new file mode 100644 index 000000000..c4b16b961 --- /dev/null +++ b/Source/control/control_chat.cpp @@ -0,0 +1,319 @@ +#include "control_chat.hpp" +#include "control.hpp" +#include "control_panel.hpp" + +#include "control/control_chat_commands.hpp" +#include "engine/backbuffer_state.hpp" +#include "engine/render/clx_render.hpp" +#include "options.h" +#include "panels/console.hpp" +#include "panels/mainpanel.hpp" +#include "quick_messages.hpp" +#include "utils/display.h" +#include "utils/sdl_compat.h" +#include "utils/str_cat.hpp" + +namespace devilution { + +std::optional ChatInputState; +char TalkMessage[MAX_SEND_STR_LEN]; +bool TalkButtonsDown[3]; +int sgbPlrTalkTbl; +bool WhisperList[MAX_PLRS]; +OptionalOwnedClxSpriteList talkButtons; + +namespace { + +char TalkSave[8][MAX_SEND_STR_LEN]; +uint8_t TalkSaveIndex; +uint8_t NextTalkSave; +TextInputCursorState ChatCursor; + +int MuteButtons = 3; +int MuteButtonPadding = 2; +Rectangle MuteButtonRect { { 172, 69 }, { 61, 16 } }; + +void ResetChatMessage() +{ + if (CheckChatCommand(TalkMessage)) + return; + + uint32_t pmask = 0; + + for (size_t i = 0; i < Players.size(); i++) { + if (WhisperList[i]) + pmask |= 1 << i; + } + + NetSendCmdString(pmask, TalkMessage); +} + +void ControlPressEnter() +{ + if (TalkMessage[0] != 0) { + ResetChatMessage(); + uint8_t i = 0; + for (; i < 8; i++) { + if (strcmp(TalkSave[i], TalkMessage) == 0) + break; + } + if (i >= 8) { + strcpy(TalkSave[NextTalkSave], TalkMessage); + NextTalkSave++; + NextTalkSave &= 7; + } else { + uint8_t talkSave = NextTalkSave - 1; + talkSave &= 7; + if (i != talkSave) { + strcpy(TalkSave[i], TalkSave[talkSave]); + *BufCopy(TalkSave[talkSave], ChatInputState->value()) = '\0'; + } + } + TalkMessage[0] = '\0'; + TalkSaveIndex = NextTalkSave; + } + ResetChat(); +} + +void ControlUpDown(int v) +{ + for (int i = 0; i < 8; i++) { + TalkSaveIndex = (v + TalkSaveIndex) & 7; + if (TalkSave[TalkSaveIndex][0] != 0) { + ChatInputState->assign(TalkSave[TalkSaveIndex]); + return; + } + } +} + +} // namespace + +void DrawChatBox(const Surface &out) +{ + if (!ChatFlag) + return; + + const Point mainPanelPosition = GetMainPanel().position; + + DrawPanelBox(out, MakeSdlRect(175, sgbPlrTalkTbl + 20, 294, 5), mainPanelPosition + Displacement { 175, 4 }); + int off = 0; + for (int i = 293; i > 283; off++, i--) { + DrawPanelBox(out, MakeSdlRect((off / 2) + 175, sgbPlrTalkTbl + off + 25, i, 1), mainPanelPosition + Displacement { (off / 2) + 175, off + 9 }); + } + DrawPanelBox(out, MakeSdlRect(185, sgbPlrTalkTbl + 35, 274, 30), mainPanelPosition + Displacement { 185, 19 }); + DrawPanelBox(out, MakeSdlRect(180, sgbPlrTalkTbl + 65, 284, 5), mainPanelPosition + Displacement { 180, 49 }); + for (int i = 0; i < 10; i++) { + DrawPanelBox(out, MakeSdlRect(180, sgbPlrTalkTbl + i + 70, i + 284, 1), mainPanelPosition + Displacement { 180, i + 54 }); + } + DrawPanelBox(out, MakeSdlRect(170, sgbPlrTalkTbl + 80, 310, 55), mainPanelPosition + Displacement { 170, 64 }); + + int x = mainPanelPosition.x + 200; + const int y = mainPanelPosition.y + 10; + + const uint32_t len = DrawString(out, TalkMessage, { { x, y }, { 250, 39 } }, + { + .flags = UiFlags::ColorWhite | UiFlags::PentaCursor, + .lineHeight = 13, + .cursorPosition = static_cast(ChatCursor.position), + .highlightRange = { static_cast(ChatCursor.selection.begin), static_cast(ChatCursor.selection.end) }, + }); + ChatInputState->truncate(len); + + x += 46; + int talkBtn = 0; + for (size_t i = 0; i < Players.size(); i++) { + Player &player = Players[i]; + if (&player == MyPlayer) + continue; + + const UiFlags color = player.friendlyMode ? UiFlags::ColorWhitegold : UiFlags::ColorRed; + const Point talkPanPosition = mainPanelPosition + Displacement { 172, 84 + 18 * talkBtn }; + if (WhisperList[i]) { + // the normal (unpressed) voice button is pre-rendered on the panel, only need to draw over it when the button is held + if (TalkButtonsDown[talkBtn]) { + const unsigned spriteIndex = talkBtn == 0 ? 2 : 3; // the first button sprite includes a tip from the devils wing so is different to the rest. + ClxDraw(out, talkPanPosition, (*talkButtons)[spriteIndex]); + + // Draw the translated string over the top of the default (english) button. This graphic is inset to avoid overlapping the wingtip, letting + // the first button be treated the same as the other two further down the panel. + RenderClxSprite(out, (*TalkButton)[2], talkPanPosition + Displacement { 4, -15 }); + } + } else { + unsigned spriteIndex = talkBtn == 0 ? 0 : 1; // the first button sprite includes a tip from the devils wing so is different to the rest. + if (TalkButtonsDown[talkBtn]) + spriteIndex += 4; // held button sprites are at index 4 and 5 (with and without wingtip respectively) + ClxDraw(out, talkPanPosition, (*talkButtons)[spriteIndex]); + + // Draw the translated string over the top of the default (english) button. This graphic is inset to avoid overlapping the wingtip, letting + // the first button be treated the same as the other two further down the panel. + RenderClxSprite(out, (*TalkButton)[TalkButtonsDown[talkBtn] ? 1 : 0], talkPanPosition + Displacement { 4, -15 }); + } + if (player.plractive) { + DrawString(out, player._pName, { { x, y + 60 + talkBtn * 18 }, { 204, 0 } }, { .flags = color }); + } + + talkBtn++; + } +} + +bool CheckMuteButton() +{ + if (!ChatFlag) + return false; + + Rectangle buttons = MuteButtonRect; + + SetPanelObjectPosition(UiPanels::Main, buttons); + + buttons.size.height = (MuteButtons * buttons.size.height) + ((MuteButtons - 1) * MuteButtonPadding); + + if (!buttons.contains(MousePosition)) + return false; + + for (bool &talkButtonDown : TalkButtonsDown) { + talkButtonDown = false; + } + + const Point mainPanelPosition = GetMainPanel().position; + + TalkButtonsDown[(MousePosition.y - (69 + mainPanelPosition.y)) / 18] = true; + + return true; +} + +void CheckMuteButtonUp() +{ + if (!ChatFlag) + return; + + for (bool &talkButtonDown : TalkButtonsDown) + talkButtonDown = false; + + Rectangle buttons = MuteButtonRect; + + SetPanelObjectPosition(UiPanels::Main, buttons); + + buttons.size.height = (MuteButtons * buttons.size.height) + ((MuteButtons - 1) * MuteButtonPadding); + + if (!buttons.contains(MousePosition)) + return; + + int off = (MousePosition.y - buttons.position.y) / (MuteButtonRect.size.height + MuteButtonPadding); + + size_t playerId = 0; + for (; playerId < Players.size() && off != -1; ++playerId) { + if (playerId != MyPlayerId) + off--; + } + if (playerId > 0 && playerId <= Players.size()) + WhisperList[playerId - 1] = !WhisperList[playerId - 1]; +} + +void TypeChatMessage() +{ + if (!IsChatAvailable()) + return; + + ChatFlag = true; + TalkMessage[0] = '\0'; + ChatInputState.emplace(TextInputState::Options { + .value = TalkMessage, + .cursor = &ChatCursor, + .maxLength = sizeof(TalkMessage) - 1 }); + for (bool &talkButtonDown : TalkButtonsDown) { + talkButtonDown = false; + } + sgbPlrTalkTbl = GetMainPanel().size.height + PanelPaddingHeight; + RedrawEverything(); + TalkSaveIndex = NextTalkSave; + + SDL_Rect rect = MakeSdlRect(GetMainPanel().position.x + 200, GetMainPanel().position.y + 22, 0, 27); + SDL_SetTextInputArea(ghMainWnd, &rect, /*cursor=*/0); + SDLC_StartTextInput(ghMainWnd); +} + +void ResetChat() +{ + ChatFlag = false; + SDLC_StopTextInput(ghMainWnd); + ChatCursor = {}; + ChatInputState = std::nullopt; + sgbPlrTalkTbl = 0; + RedrawEverything(); +} + +bool IsChatActive() +{ + if (!IsChatAvailable()) + return false; + + if (!ChatFlag) + return false; + + return true; +} + +bool CheckKeypress(SDL_Keycode vkey) +{ + if (!IsChatAvailable()) + return false; + if (!ChatFlag) + return false; + + switch (vkey) { + case SDLK_ESCAPE: + ResetChat(); + return true; + case SDLK_RETURN: + case SDLK_KP_ENTER: + ControlPressEnter(); + return true; + case SDLK_DOWN: + ControlUpDown(1); + return true; + case SDLK_UP: + ControlUpDown(-1); + return true; + default: + return vkey >= SDLK_SPACE && vkey <= SDLK_Z; + } +} + +void DiabloHotkeyMsg(uint32_t dwMsg) +{ + assert(dwMsg < QuickMessages.size()); + +#ifdef _DEBUG + constexpr std::string_view LuaPrefix = "/lua "; + for (const std::string &msg : GetOptions().Chat.szHotKeyMsgs[dwMsg]) { + if (!msg.starts_with(LuaPrefix)) continue; + InitConsole(); + RunInConsole(std::string_view(msg).substr(LuaPrefix.size())); + } +#endif + + if (!IsChatAvailable()) { + return; + } + + for (const std::string &msg : GetOptions().Chat.szHotKeyMsgs[dwMsg]) { +#ifdef _DEBUG + if (msg.starts_with(LuaPrefix)) continue; +#endif + char charMsg[MAX_SEND_STR_LEN]; + CopyUtf8(charMsg, msg, sizeof(charMsg)); + NetSendCmdString(0xFFFFFF, charMsg); + } +} + +bool IsChatAvailable() +{ + return gbIsMultiplayer; +} + +bool HandleTalkTextInputEvent(const SDL_Event &event) +{ + return HandleInputEvent(event, ChatInputState); +} + +} // namespace devilution diff --git a/Source/control/control_chat.hpp b/Source/control/control_chat.hpp new file mode 100644 index 000000000..b21aa0fdb --- /dev/null +++ b/Source/control/control_chat.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#include "DiabloUI/text_input.hpp" +#include "engine/clx_sprite.hpp" +#include "msg.h" +#include "multi.h" + +#ifdef USE_SDL3 +#include +#include +#include +#include +#else +#include + +#ifdef USE_SDL1 +#include "utils/sdl2_to_1_2_backports.h" +#endif +#endif + +namespace devilution { + +extern OptionalOwnedClxSpriteList talkButtons; +extern std::optional ChatInputState; +extern char TalkMessage[MAX_SEND_STR_LEN]; +extern bool TalkButtonsDown[3]; +extern int sgbPlrTalkTbl; +extern bool WhisperList[MAX_PLRS]; + +bool CheckChatCommand(std::string_view text); + +template +bool HandleInputEvent(const SDL_Event &event, std::optional &inputState) +{ + if (!inputState) { + return false; // No input state to handle + } + + if constexpr (std::is_same_v) { + return HandleTextInputEvent(event, *inputState); + } else if constexpr (std::is_same_v) { + return HandleNumberInputEvent(event, *inputState); + } + + return false; // Unknown input state type +} + +} // namespace devilution diff --git a/Source/control/control_chat_commands.cpp b/Source/control/control_chat_commands.cpp new file mode 100644 index 000000000..ec57de005 --- /dev/null +++ b/Source/control/control_chat_commands.cpp @@ -0,0 +1,280 @@ +#include "control_chat_commands.hpp" +#include "control.hpp" + +#include "diablo_msg.hpp" +#include "engine/backbuffer_state.hpp" +#include "inv.h" +#include "levels/setmaps.h" +#include "storm/storm_net.hpp" +#include "utils/algorithm/container.hpp" +#include "utils/log.hpp" +#include "utils/parse_int.hpp" +#include "utils/str_case.hpp" +#include "utils/str_cat.hpp" + +#ifdef _DEBUG +#include "debug.h" +#endif + +namespace devilution { + +namespace { + +struct TextCmdItem { + const std::string text; + const std::string description; + const std::string requiredParameter; + std::string (*actionProc)(const std::string_view); +}; + +extern std::vector TextCmdList; + +std::string TextCmdHelp(const std::string_view parameter) +{ + if (parameter.empty()) { + std::string ret; + StrAppend(ret, _("Available Commands:")); + for (const TextCmdItem &textCmd : TextCmdList) { + StrAppend(ret, " ", _(textCmd.text)); + } + return ret; + } + auto textCmdIterator = c_find_if(TextCmdList, [&](const TextCmdItem &elem) { return elem.text == parameter; }); + if (textCmdIterator == TextCmdList.end()) + return StrCat(_("Command "), parameter, _(" is unknown.")); + auto &textCmdItem = *textCmdIterator; + if (textCmdItem.requiredParameter.empty()) + return StrCat(_("Description: "), _(textCmdItem.description), _("\nParameters: No additional parameter needed.")); + return StrCat(_("Description: "), _(textCmdItem.description), _("\nParameters: "), _(textCmdItem.requiredParameter)); +} + +void AppendArenaOverview(std::string &ret) +{ + for (int arena = SL_FIRST_ARENA; arena <= SL_LAST; arena++) { + StrAppend(ret, "\n", arena - SL_FIRST_ARENA + 1, " (", QuestLevelNames[arena], ")"); + } +} + +std::string TextCmdArena(const std::string_view parameter) +{ + std::string ret; + if (!gbIsMultiplayer) { + StrAppend(ret, _("Arenas are only supported in multiplayer.")); + return ret; + } + + if (parameter.empty()) { + StrAppend(ret, _("What arena do you want to visit?")); + AppendArenaOverview(ret); + return ret; + } + + const ParseIntResult parsedParam = ParseInt(parameter, /*min=*/0); + const _setlevels arenaLevel = parsedParam.has_value() ? static_cast<_setlevels>(parsedParam.value() - 1 + SL_FIRST_ARENA) : _setlevels::SL_NONE; + if (!IsArenaLevel(arenaLevel)) { + StrAppend(ret, _("Invalid arena-number. Valid numbers are:")); + AppendArenaOverview(ret); + return ret; + } + + if (!MyPlayer->isOnLevel(0) && !MyPlayer->isOnArenaLevel()) { + StrAppend(ret, _("To enter a arena, you need to be in town or another arena.")); + return ret; + } + + setlvltype = GetArenaLevelType(arenaLevel); + StartNewLvl(*MyPlayer, WM_DIABSETLVL, arenaLevel); + return ret; +} + +std::string TextCmdArenaPot(const std::string_view parameter) +{ + std::string ret; + if (!gbIsMultiplayer) { + StrAppend(ret, _("Arenas are only supported in multiplayer.")); + return ret; + } + const int numPots = ParseInt(parameter, /*min=*/1).value_or(1); + + Player &myPlayer = *MyPlayer; + + for (int potNumber = numPots; potNumber > 0; potNumber--) { + Item item {}; + InitializeItem(item, IDI_ARENAPOT); + GenerateNewSeed(item); + item.updateRequiredStatsCacheForPlayer(myPlayer); + + if (!AutoPlaceItemInBelt(myPlayer, item, true, true) && !AutoPlaceItemInInventory(myPlayer, item, true)) { + break; // inventory is full + } + } + + return ret; +} + +std::string TextCmdInspect(const std::string_view parameter) +{ + std::string ret; + if (!gbIsMultiplayer) { + StrAppend(ret, _("Inspecting only supported in multiplayer.")); + return ret; + } + + if (parameter.empty()) { + StrAppend(ret, _("Stopped inspecting players.")); + InspectPlayer = MyPlayer; + return ret; + } + + const std::string param = AsciiStrToLower(parameter); + auto it = c_find_if(Players, [¶m](const Player &player) { + return AsciiStrToLower(player._pName) == param; + }); + if (it == Players.end()) { + it = c_find_if(Players, [¶m](const Player &player) { + return AsciiStrToLower(player._pName).find(param) != std::string::npos; + }); + } + if (it == Players.end()) { + StrAppend(ret, _("No players found with such a name")); + return ret; + } + + Player &player = *it; + InspectPlayer = &player; + StrAppend(ret, _("Inspecting player: ")); + StrAppend(ret, player._pName); + OpenCharPanel(); + if (!SpellbookFlag) + invflag = true; + RedrawEverything(); + return ret; +} + +bool IsQuestEnabled(const Quest &quest) +{ + switch (quest._qidx) { + case Q_FARMER: + return gbIsHellfire && !sgGameInitInfo.bCowQuest; + case Q_JERSEY: + return gbIsHellfire && sgGameInitInfo.bCowQuest; + case Q_GIRL: + return gbIsHellfire && sgGameInitInfo.bTheoQuest; + case Q_CORNSTN: + return gbIsHellfire && !gbIsMultiplayer; + case Q_GRAVE: + case Q_DEFILER: + case Q_NAKRUL: + return gbIsHellfire; + case Q_TRADER: + return false; + default: + return quest._qactive != QUEST_NOTAVAIL; + } +} + +std::string TextCmdLevelSeed(const std::string_view parameter) +{ + const std::string_view levelType = setlevel ? "set level" : "dungeon level"; + + char gameId[] = { + static_cast((sgGameInitInfo.programid >> 24) & 0xFF), + static_cast((sgGameInitInfo.programid >> 16) & 0xFF), + static_cast((sgGameInitInfo.programid >> 8) & 0xFF), + static_cast(sgGameInitInfo.programid & 0xFF), + '\0' + }; + + const std::string_view mode = gbIsMultiplayer ? "MP" : "SP"; + const std::string_view questPool = UseMultiplayerQuests() ? "MP" : "Full"; + + uint32_t questFlags = 0; + for (const Quest &quest : Quests) { + questFlags <<= 1; + if (IsQuestEnabled(quest)) + questFlags |= 1; + } + + return StrCat( + "Seedinfo for ", levelType, " ", currlevel, "\n", + "seed: ", DungeonSeeds[currlevel], "\n", +#ifdef _DEBUG + "Mid1: ", glMid1Seed[currlevel], "\n", + "Mid2: ", glMid2Seed[currlevel], "\n", + "Mid3: ", glMid3Seed[currlevel], "\n", + "End: ", glEndSeed[currlevel], "\n", +#endif + "\n", + gameId, " ", mode, "\n", + questPool, " quests: ", questFlags, "\n", + "Storybook: ", DungeonSeeds[16]); +} + +std::string TextCmdPing(const std::string_view parameter) +{ + std::string ret; + const std::string param = AsciiStrToLower(parameter); + auto it = c_find_if(Players, [¶m](const Player &player) { + return AsciiStrToLower(player._pName) == param; + }); + if (it == Players.end()) { + it = c_find_if(Players, [¶m](const Player &player) { + return AsciiStrToLower(player._pName).find(param) != std::string::npos; + }); + } + if (it == Players.end()) { + StrAppend(ret, _("No players found with such a name")); + return ret; + } + + Player &player = *it; + DvlNetLatencies latencies = DvlNet_GetLatencies(player.getId()); + + StrAppend(ret, fmt::format(fmt::runtime(_(/* TRANSLATORS: {:s} means: Character Name */ "Latency statistics for {:s}:")), player.name())); + + StrAppend(ret, "\n", fmt::format(fmt::runtime(_(/* TRANSLATORS: Network connectivity statistics */ "Echo latency: {:d} ms")), latencies.echoLatency)); + + if (latencies.providerLatency) { + if (latencies.isRelayed && *latencies.isRelayed) { + StrAppend(ret, "\n", fmt::format(fmt::runtime(_(/* TRANSLATORS: Network connectivity statistics */ "Provider latency: {:d} ms (Relayed)")), *latencies.providerLatency)); + } else { + StrAppend(ret, "\n", fmt::format(fmt::runtime(_(/* TRANSLATORS: Network connectivity statistics */ "Provider latency: {:d} ms")), *latencies.providerLatency)); + } + } + + return ret; +} + +std::vector TextCmdList = { + { "/help", N_("Prints help overview or help for a specific command."), N_("[command]"), &TextCmdHelp }, + { "/arena", N_("Enter a PvP Arena."), N_(""), &TextCmdArena }, + { "/arenapot", N_("Gives Arena Potions."), N_(""), &TextCmdArenaPot }, + { "/inspect", N_("Inspects stats and equipment of another player."), N_(""), &TextCmdInspect }, + { "/seedinfo", N_("Show seed infos for current level."), "", &TextCmdLevelSeed }, + { "/ping", N_("Show latency statistics for another player."), N_(""), &TextCmdPing }, +}; + +} // namespace + +bool CheckChatCommand(const std::string_view text) +{ + if (text.size() < 1 || text[0] != '/') + return false; + + auto textCmdIterator = c_find_if(TextCmdList, [&](const TextCmdItem &elem) { return text.find(elem.text) == 0 && (text.length() == elem.text.length() || text[elem.text.length()] == ' '); }); + if (textCmdIterator == TextCmdList.end()) { + InitDiabloMsg(StrCat(_("Command "), "\"", text, "\"", _(" is unknown."))); + return true; + } + + const TextCmdItem &textCmd = *textCmdIterator; + std::string_view parameter = ""; + if (text.length() > (textCmd.text.length() + 1)) + parameter = text.substr(textCmd.text.length() + 1); + const std::string result = textCmd.actionProc(parameter); + if (result != "") + InitDiabloMsg(result); + return true; +} + +} // namespace devilution diff --git a/Source/control/control_chat_commands.hpp b/Source/control/control_chat_commands.hpp new file mode 100644 index 000000000..83dac04fd --- /dev/null +++ b/Source/control/control_chat_commands.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace devilution { + +bool CheckChatCommand(std::string_view text); + +} // namespace devilution diff --git a/Source/control/control_flasks.cpp b/Source/control/control_flasks.cpp new file mode 100644 index 000000000..ad5449f70 --- /dev/null +++ b/Source/control/control_flasks.cpp @@ -0,0 +1,149 @@ +#include "control_flasks.hpp" +#include "control.hpp" + +#include "engine/surface.hpp" +#include "utils/str_cat.hpp" + +namespace devilution { + +std::optional pLifeBuff; +std::optional pManaBuff; + +namespace { + +Rectangle FlaskTopRect { { 11, 3 }, { 62, 13 } }; +Rectangle FlaskBottomRect { { 0, 16 }, { 88, 69 } }; + +/** + * Draws the dome of the flask that protrudes above the panel top line. + * It draws a rectangle of fixed width 59 and height 'h' from the source buffer + * into the target buffer. + * @param out The target buffer. + * @param celBuf Buffer of the flask cel. + * @param targetPosition Target buffer coordinate. + */ +void DrawFlaskAbovePanel(const Surface &out, const Surface &celBuf, Point targetPosition) +{ + out.BlitFromSkipColorIndexZero(celBuf, MakeSdlRect(0, 0, celBuf.w(), celBuf.h()), targetPosition); +} + +/** + * @brief Draws the part of the life/mana flasks protruding above the bottom panel + * @see DrawFlaskLower() + * @param out The display region to draw to + * @param sourceBuffer A sprite representing the appropriate background/empty flask style + * @param offset X coordinate offset for where the flask should be drawn + * @param fillPer How full the flask is (a value from 0 to 81) + */ +void DrawFlaskUpper(const Surface &out, const Surface &sourceBuffer, int offset, int fillPer) +{ + const Rectangle &rect = FlaskTopRect; + const int emptyRows = std::clamp(81 - fillPer, 0, rect.size.height); + const int filledRows = rect.size.height - emptyRows; + + // Draw the empty part of the flask + DrawFlaskAbovePanel(out, + sourceBuffer.subregion(rect.position.x, rect.position.y, rect.size.width, rect.size.height), + GetMainPanel().position + Displacement { offset, -rect.size.height }); + + // Draw the filled part of the flask over the empty part + if (filledRows > 0) { + DrawFlaskAbovePanel(out, + BottomBuffer->subregion(offset, rect.position.y + emptyRows, rect.size.width, filledRows), + GetMainPanel().position + Displacement { offset, -rect.size.height + emptyRows }); + } +} + +/** + * Draws a section of the empty flask cel on top of the panel to create the illusion + * of the flask getting empty. This function takes a cel and draws a + * horizontal stripe of height (max-min) onto the given buffer. + * @param out Target buffer. + * @param celBuf Buffer of the flask cel. + * @param targetPosition Target buffer coordinate. + */ +void DrawFlaskOnPanel(const Surface &out, const Surface &celBuf, Point targetPosition) +{ + out.BlitFrom(celBuf, MakeSdlRect(0, 0, celBuf.w(), celBuf.h()), targetPosition); +} + +/** + * @brief Draws the part of the life/mana flasks inside the bottom panel + * @see DrawFlaskUpper() + * @param out The display region to draw to + * @param sourceBuffer A sprite representing the appropriate background/empty flask style + * @param offset X coordinate offset for where the flask should be drawn + * @param fillPer How full the flask is (a value from 0 to 80) + * @param drawFilledPortion Indicates whether to draw the filled portion of the flask + */ +void DrawFlaskLower(const Surface &out, const Surface &sourceBuffer, int offset, int fillPer, bool drawFilledPortion) +{ + const Rectangle &rect = FlaskBottomRect; + const int filledRows = std::clamp(fillPer, 0, rect.size.height); + const int emptyRows = rect.size.height - filledRows; + + // Draw the empty part of the flask + if (emptyRows > 0) { + DrawFlaskOnPanel(out, + sourceBuffer.subregion(rect.position.x, rect.position.y, rect.size.width, emptyRows), + GetMainPanel().position + Displacement { offset, 0 }); + } + + // Draw the filled part of the flask + if (drawFilledPortion && filledRows > 0) { + DrawFlaskOnPanel(out, + BottomBuffer->subregion(offset, rect.position.y + emptyRows, rect.size.width, filledRows), + GetMainPanel().position + Displacement { offset, emptyRows }); + } +} + +} // namespace + +void DrawLifeFlaskUpper(const Surface &out) +{ + constexpr int LifeFlaskUpperOffset = 107; + DrawFlaskUpper(out, *pLifeBuff, LifeFlaskUpperOffset, MyPlayer->_pHPPer); +} + +void DrawManaFlaskUpper(const Surface &out) +{ + constexpr int ManaFlaskUpperOffset = 475; + DrawFlaskUpper(out, *pManaBuff, ManaFlaskUpperOffset, MyPlayer->_pManaPer); +} + +void DrawLifeFlaskLower(const Surface &out, bool drawFilledPortion) +{ + constexpr int LifeFlaskLowerOffset = 96; + DrawFlaskLower(out, *pLifeBuff, LifeFlaskLowerOffset, MyPlayer->_pHPPer, drawFilledPortion); +} + +void DrawManaFlaskLower(const Surface &out, bool drawFilledPortion) +{ + constexpr int ManaFlaskLowerOffset = 464; + DrawFlaskLower(out, *pManaBuff, ManaFlaskLowerOffset, MyPlayer->_pManaPer, drawFilledPortion); +} + +void DrawFlaskValues(const Surface &out, Point pos, int currValue, int maxValue) +{ + const UiFlags color = (currValue > 0 ? (currValue == maxValue ? UiFlags::ColorGold : UiFlags::ColorWhite) : UiFlags::ColorRed); + + auto drawStringWithShadow = [out, color](std::string_view text, Point pos) { + DrawString(out, text, pos + Displacement { -1, -1 }, + { .flags = UiFlags::ColorBlack | UiFlags::KerningFitSpacing, .spacing = 0 }); + DrawString(out, text, pos, + { .flags = color | UiFlags::KerningFitSpacing, .spacing = 0 }); + }; + + const std::string currText = StrCat(currValue); + drawStringWithShadow(currText, pos - Displacement { GetLineWidth(currText, GameFont12) + 1, 0 }); + drawStringWithShadow("/", pos); + drawStringWithShadow(StrCat(maxValue), pos + Displacement { GetLineWidth("/", GameFont12) + 1, 0 }); +} + +void UpdateLifeManaPercent() +{ + MyPlayer->UpdateManaPercentage(); + MyPlayer->UpdateHitPointPercentage(); +} + +} // namespace devilution diff --git a/Source/control/control_flasks.hpp b/Source/control/control_flasks.hpp new file mode 100644 index 000000000..3cc97f834 --- /dev/null +++ b/Source/control/control_flasks.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include + +#include "engine/surface.hpp" + +namespace devilution { + +extern std::optional pLifeBuff; +extern std::optional pManaBuff; + +} // namespace devilution diff --git a/Source/control/control_gold.cpp b/Source/control/control_gold.cpp new file mode 100644 index 000000000..8cdfb12e4 --- /dev/null +++ b/Source/control/control_gold.cpp @@ -0,0 +1,141 @@ +#include "control.hpp" +#include "control_chat.hpp" + +#include "DiabloUI/text_input.hpp" +#include "engine/render/clx_render.hpp" +#include "inv.h" +#include "utils/display.h" +#include "utils/format_int.hpp" +#include "utils/log.hpp" +#include "utils/sdl_compat.h" + +namespace devilution { + +bool DropGoldFlag; +TextInputCursorState GoldDropCursor; +char GoldDropText[21]; + +namespace { + +int8_t GoldDropInvIndex; +std::optional GoldDropInputState; + +void RemoveGold(Player &player, int goldIndex, int amount) +{ + const int gi = goldIndex - INVITEM_INV_FIRST; + player.InvList[gi]._ivalue -= amount; + if (player.InvList[gi]._ivalue > 0) { + SetPlrHandGoldCurs(player.InvList[gi]); + NetSyncInvItem(player, gi); + } else { + player.RemoveInvItem(gi); + } + + MakeGoldStack(player.HoldItem, amount); + NewCursor(player.HoldItem); + + player._pGold = CalculateGold(player); +} + +int GetGoldDropMax() +{ + return GoldDropInputState->max(); +} + +} // namespace + +void DrawGoldSplit(const Surface &out) +{ + const int dialogX = 30; + + ClxDraw(out, GetPanelPosition(UiPanels::Inventory, { dialogX, 178 }), (*GoldBoxBuffer)[0]); + + const std::string_view amountText = GoldDropText; + const TextInputCursorState &cursor = GoldDropCursor; + const int max = GetGoldDropMax(); + + const std::string description = fmt::format( + fmt::runtime(ngettext( + /* TRANSLATORS: {:s} is a number with separators. Dialog is shown when splitting a stash of Gold.*/ + "You have {:s} gold piece. How many do you want to remove?", + "You have {:s} gold pieces. How many do you want to remove?", + max)), + FormatInteger(max)); + + // Pre-wrap the string at spaces, otherwise DrawString would hard wrap in the middle of words + const std::string wrapped = WordWrapString(description, 200); + + // The split gold dialog is roughly 4 lines high, but we need at least one line for the player to input an amount. + // Using a clipping region 50 units high (approx 3 lines with a lineheight of 17) to ensure there is enough room left + // for the text entered by the player. + DrawString(out, wrapped, { GetPanelPosition(UiPanels::Inventory, { dialogX + 31, 75 }), { 200, 50 } }, + { .flags = UiFlags::ColorWhitegold | UiFlags::AlignCenter, .lineHeight = 17 }); + + // Even a ten digit amount of gold only takes up about half a line. There's no need to wrap or clip text here so we + // use the Point form of DrawString. + DrawString(out, amountText, GetPanelPosition(UiPanels::Inventory, { dialogX + 37, 128 }), + { + .flags = UiFlags::ColorWhite | UiFlags::PentaCursor, + .cursorPosition = static_cast(cursor.position), + .highlightRange = { static_cast(cursor.selection.begin), static_cast(cursor.selection.end) }, + }); +} + +void control_drop_gold(SDL_Keycode vkey) +{ + Player &myPlayer = *MyPlayer; + + if (myPlayer.hasNoLife()) { + CloseGoldDrop(); + return; + } + + switch (vkey) { + case SDLK_RETURN: + case SDLK_KP_ENTER: + if (const int value = GoldDropInputState->value(); value != 0) { + RemoveGold(myPlayer, GoldDropInvIndex, value); + } + CloseGoldDrop(); + break; + case SDLK_ESCAPE: + CloseGoldDrop(); + break; + default: + break; + } +} + +void OpenGoldDrop(int8_t invIndex, int max) +{ + DropGoldFlag = true; + GoldDropInvIndex = invIndex; + GoldDropText[0] = '\0'; + GoldDropInputState.emplace(NumberInputState::Options { + .textOptions { + .value = GoldDropText, + .cursor = &GoldDropCursor, + .maxLength = sizeof(GoldDropText) - 1, + }, + .min = 0, + .max = max, + }); + SDLC_StartTextInput(ghMainWnd); +} + +void CloseGoldDrop() +{ + if (!DropGoldFlag) + return; + SDLC_StopTextInput(ghMainWnd); + DropGoldFlag = false; + GoldDropInputState = std::nullopt; + GoldDropInvIndex = 0; +} + +bool HandleGoldDropTextInputEvent(const SDL_Event &event) +{ + return HandleInputEvent(event, GoldDropInputState); +} + +} // namespace devilution diff --git a/Source/control/control_infobox.cpp b/Source/control/control_infobox.cpp new file mode 100644 index 000000000..30469e973 --- /dev/null +++ b/Source/control/control_infobox.cpp @@ -0,0 +1,425 @@ +#include "control.hpp" +#include "control_panel.hpp" + +#include "engine/render/primitive_render.hpp" +#include "inv.h" +#include "levels/trigs.h" +#include "panels/partypanel.hpp" +#include "qol/stash.h" +#include "qol/xpbar.h" +#include "towners.h" +#include "utils/algorithm/container.hpp" +#include "utils/format_int.hpp" +#include "utils/log.hpp" +#include "utils/screen_reader.hpp" +#include "utils/str_cat.hpp" +#include "utils/str_split.hpp" + +namespace devilution { + +StringOrView InfoString; +StringOrView FloatingInfoString; + +namespace { + +void PrintInfo(const Surface &out) +{ + if (ChatFlag) + return; + + const int space[] = { 18, 12, 6, 3, 0 }; + Rectangle infoBox = InfoBoxRect; + + SetPanelObjectPosition(UiPanels::Main, infoBox); + + const auto newLineCount = static_cast(c_count(InfoString.str(), '\n')); + const int spaceIndex = std::min(4, newLineCount); + const int spacing = space[spaceIndex]; + const int lineHeight = 12 + spacing; + + // Adjusting the line height to add spacing between lines + // will also add additional space beneath the last line + // which throws off the vertical centering + infoBox.position.y += spacing / 2; + + SpeakText(InfoString); + + DrawString(out, InfoString, infoBox, + { + .flags = InfoColor | UiFlags::AlignCenter | UiFlags::VerticalCenter | UiFlags::KerningFitSpacing, + .spacing = 2, + .lineHeight = lineHeight, + }); +} + +Rectangle GetFloatingInfoRect(const int lineHeight, const int textSpacing) +{ + // Calculate the width and height of the floating info box + const std::string txt = std::string(FloatingInfoString); + + auto lines = SplitByChar(txt, '\n'); + const GameFontTables font = GameFont12; + int maxW = 0; + + for (const auto &line : lines) { + const int w = GetLineWidth(line, font, textSpacing, nullptr); + maxW = std::max(maxW, w); + } + + const auto lineCount = 1 + static_cast(c_count(FloatingInfoString.str(), '\n')); + const int totalH = lineCount * lineHeight; + + const Player &player = *InspectPlayer; + + // 1) Equipment (Rect position) + if (pcursinvitem >= INVITEM_HEAD && pcursinvitem < INVITEM_INV_FIRST) { + const int slot = pcursinvitem - INVITEM_HEAD; + static constexpr Point equipLocal[] = { + { 133, 59 }, + { 48, 205 }, + { 249, 205 }, + { 205, 60 }, + { 17, 160 }, + { 248, 160 }, + { 133, 160 }, + }; + + Point itemPosition = equipLocal[slot]; + auto &item = player.InvBody[slot]; + const Size frame = GetInvItemSize(item._iCurs + CURSOR_FIRSTITEM); + + if (slot == INVLOC_HAND_LEFT) { + itemPosition.x += frame.width == InventorySlotSizeInPixels.width + ? InventorySlotSizeInPixels.width + : 0; + itemPosition.y += frame.height == 3 * InventorySlotSizeInPixels.height + ? 0 + : -InventorySlotSizeInPixels.height; + } else if (slot == INVLOC_HAND_RIGHT) { + itemPosition.x += frame.width == InventorySlotSizeInPixels.width + ? (InventorySlotSizeInPixels.width - 1) + : 1; + itemPosition.y += frame.height == 3 * InventorySlotSizeInPixels.height + ? 0 + : -InventorySlotSizeInPixels.height; + } + + itemPosition.y++; // Align position to bottom left of the item graphic + itemPosition.x += frame.width / 2; // Align position to center of the item graphic + itemPosition.x -= maxW / 2; // Align position to the center of the floating item info box + + const Point screen = GetPanelPosition(UiPanels::Inventory, itemPosition); + + return { { screen.x, screen.y }, { maxW, totalH } }; + } + + // 2) Inventory grid (Rect position) + if (pcursinvitem >= INVITEM_INV_FIRST && pcursinvitem < INVITEM_INV_FIRST + InventoryGridCells) { + const int itemIdx = pcursinvitem - INVITEM_INV_FIRST; + + for (int j = 0; j < InventoryGridCells; ++j) { + if (player.InvGrid[j] > 0 && player.InvGrid[j] - 1 == itemIdx) { + const Item &it = player.InvList[itemIdx]; + Point itemPosition = InvRect[j + SLOTXY_INV_FIRST].position; + + itemPosition.x += GetInventorySize(it).width * InventorySlotSizeInPixels.width / 2; // Align position to center of the item graphic + itemPosition.x -= maxW / 2; // Align position to the center of the floating item info box + + const Point screen = GetPanelPosition(UiPanels::Inventory, itemPosition); + + return { { screen.x, screen.y }, { maxW, totalH } }; + } + } + } + + // 3) Belt (Rect position) + if (pcursinvitem >= INVITEM_BELT_FIRST && pcursinvitem < INVITEM_BELT_FIRST + MaxBeltItems) { + const int itemIdx = pcursinvitem - INVITEM_BELT_FIRST; + for (int i = 0; i < MaxBeltItems; ++i) { + if (player.SpdList[i].isEmpty()) + continue; + if (i != itemIdx) + continue; + + const Item &item = player.SpdList[i]; + Point itemPosition = InvRect[i + SLOTXY_BELT_FIRST].position; + + itemPosition.x += GetInventorySize(item).width * InventorySlotSizeInPixels.width / 2; // Align position to center of the item graphic + itemPosition.x -= maxW / 2; // Align position to the center of the floating item info box + + const Point screen = GetMainPanel().position + Displacement { itemPosition.x, itemPosition.y }; + + return { { screen.x, screen.y }, { maxW, totalH } }; + } + } + + // 4) Stash (Rect position) + if (pcursstashitem != StashStruct::EmptyCell) { + for (auto slot : StashGridRange) { + auto itemId = Stash.GetItemIdAtPosition(slot); + if (itemId == StashStruct::EmptyCell) + continue; + if (itemId != pcursstashitem) + continue; + + const Item &item = Stash.stashList[itemId]; + Point itemPosition = GetStashSlotCoord(slot); + const Size itemGridSize = GetInventorySize(item); + + itemPosition.y += itemGridSize.height * (InventorySlotSizeInPixels.height + 1) - 1; // Align position to bottom left of the item graphic + itemPosition.x += itemGridSize.width * InventorySlotSizeInPixels.width / 2; // Align position to center of the item graphic + itemPosition.x -= maxW / 2; // Align position to the center of the floating item info box + + return { { itemPosition.x, itemPosition.y }, { maxW, totalH } }; + } + } + + return { { 0, 0 }, { 0, 0 } }; +} + +int GetHoverSpriteHeight() +{ + if (pcursinvitem >= INVITEM_HEAD && pcursinvitem < INVITEM_INV_FIRST) { + auto &it = (*InspectPlayer).InvBody[pcursinvitem - INVITEM_HEAD]; + return GetInvItemSize(it._iCurs + CURSOR_FIRSTITEM).height + 1; + } + if (pcursinvitem >= INVITEM_INV_FIRST + && pcursinvitem < INVITEM_INV_FIRST + InventoryGridCells) { + const int idx = pcursinvitem - INVITEM_INV_FIRST; + auto &it = (*InspectPlayer).InvList[idx]; + return GetInventorySize(it).height * (InventorySlotSizeInPixels.height + 1) + - InventorySlotSizeInPixels.height; + } + if (pcursinvitem >= INVITEM_BELT_FIRST + && pcursinvitem < INVITEM_BELT_FIRST + MaxBeltItems) { + const int idx = pcursinvitem - INVITEM_BELT_FIRST; + auto &it = (*InspectPlayer).SpdList[idx]; + return GetInventorySize(it).height * (InventorySlotSizeInPixels.height + 1) + - InventorySlotSizeInPixels.height - 1; + } + if (pcursstashitem != StashStruct::EmptyCell) { + auto &it = Stash.stashList[pcursstashitem]; + return GetInventorySize(it).height * (InventorySlotSizeInPixels.height + 1); + } + return InventorySlotSizeInPixels.height; +} + +int ClampAboveOrBelow(int anchorY, int spriteH, int boxH, int pad, int linePad) +{ + const int yAbove = anchorY - spriteH - boxH - pad; + const int yBelow = anchorY + linePad / 2 + pad; + return (yAbove >= 0) ? yAbove : yBelow; +} + +void PrintFloatingInfo(const Surface &out) +{ + if (ChatFlag) + return; + if (FloatingInfoString.empty()) + return; + + const int verticalSpacing = 3; + const int lineHeight = 12 + verticalSpacing; + const int textSpacing = 2; + const int hPadding = 5; + const int vPadding = 4; + + Rectangle floatingInfoBox = GetFloatingInfoRect(lineHeight, textSpacing); + + // Prevent the floating info box from going off-screen horizontally + floatingInfoBox.position.x = std::clamp(floatingInfoBox.position.x, hPadding, GetScreenWidth() - (floatingInfoBox.size.width + hPadding)); + + const int spriteH = GetHoverSpriteHeight(); + const int anchorY = floatingInfoBox.position.y; + + // Prevent the floating info box from going off-screen vertically + floatingInfoBox.position.y = ClampAboveOrBelow(anchorY, spriteH, floatingInfoBox.size.height, vPadding, verticalSpacing); + + SpeakText(FloatingInfoString); + + for (int i = 0; i < 3; i++) + DrawHalfTransparentRectTo(out, floatingInfoBox.position.x - hPadding, floatingInfoBox.position.y - vPadding, floatingInfoBox.size.width + hPadding * 2, floatingInfoBox.size.height + vPadding * 2); + DrawHalfTransparentVerticalLine(out, { floatingInfoBox.position.x - hPadding - 1, floatingInfoBox.position.y - vPadding - 1 }, floatingInfoBox.size.height + (vPadding * 2) + 2, PAL16_GRAY + 10); + DrawHalfTransparentVerticalLine(out, { floatingInfoBox.position.x + hPadding + floatingInfoBox.size.width, floatingInfoBox.position.y - vPadding - 1 }, floatingInfoBox.size.height + (vPadding * 2) + 2, PAL16_GRAY + 10); + DrawHalfTransparentHorizontalLine(out, { floatingInfoBox.position.x - hPadding, floatingInfoBox.position.y - vPadding - 1 }, floatingInfoBox.size.width + (hPadding * 2), PAL16_GRAY + 10); + DrawHalfTransparentHorizontalLine(out, { floatingInfoBox.position.x - hPadding, floatingInfoBox.position.y + vPadding + floatingInfoBox.size.height }, floatingInfoBox.size.width + (hPadding * 2), PAL16_GRAY + 10); + + DrawString(out, FloatingInfoString, floatingInfoBox, + { + .flags = InfoColor | UiFlags::AlignCenter | UiFlags::VerticalCenter, + .spacing = textSpacing, + .lineHeight = lineHeight, + }); +} + +} // namespace + +void AddInfoBoxString(std::string_view str, bool floatingBox /*= false*/) +{ + StringOrView &infoString = floatingBox ? FloatingInfoString : InfoString; + + if (infoString.empty()) + infoString = str; + else + infoString = StrCat(infoString, "\n", str); +} + +void AddInfoBoxString(std::string &&str, bool floatingBox /*= false*/) +{ + StringOrView &infoString = floatingBox ? FloatingInfoString : InfoString; + + if (infoString.empty()) + infoString = std::move(str); + else + infoString = StrCat(infoString, "\n", str); +} + +void CheckPanelInfo() +{ + MainPanelFlag = false; + InfoString = StringOrView {}; + FloatingInfoString = StringOrView {}; + + const int totalButtons = IsChatAvailable() ? TotalMpMainPanelButtons : TotalSpMainPanelButtons; + + for (int i = 0; i < totalButtons; i++) { + Rectangle button = MainPanelButtonRect[i]; + + SetPanelObjectPosition(UiPanels::Main, button); + + if (button.contains(MousePosition)) { + if (i != 7) { + InfoString = _(PanBtnStr[i]); + } else { + if (MyPlayer->friendlyMode) + InfoString = _("Player friendly"); + else + InfoString = _("Player attack"); + } + if (PanBtnHotKey[i] != nullptr) { + AddInfoBoxString(fmt::format(fmt::runtime(_("Hotkey: {:s}")), _(PanBtnHotKey[i]))); + } + InfoColor = UiFlags::ColorWhite; + MainPanelFlag = true; + } + } + + Rectangle spellSelectButton = SpellButtonRect; + + SetPanelObjectPosition(UiPanels::Main, spellSelectButton); + + if (!SpellSelectFlag && spellSelectButton.contains(MousePosition)) { + InfoString = _("Select current spell button"); + InfoColor = UiFlags::ColorWhite; + MainPanelFlag = true; + AddInfoBoxString(_("Hotkey: 's'")); + const Player &myPlayer = *MyPlayer; + const SpellID spellId = myPlayer._pRSpell; + if (IsValidSpell(spellId)) { + switch (myPlayer._pRSplType) { + case SpellType::Skill: + AddInfoBoxString(fmt::format(fmt::runtime(_("{:s} Skill")), pgettext("spell", GetSpellData(spellId).sNameText))); + break; + case SpellType::Spell: { + AddInfoBoxString(fmt::format(fmt::runtime(_("{:s} Spell")), pgettext("spell", GetSpellData(spellId).sNameText))); + const int spellLevel = myPlayer.GetSpellLevel(spellId); + AddInfoBoxString(spellLevel == 0 ? _("Spell Level 0 - Unusable") : fmt::format(fmt::runtime(_("Spell Level {:d}")), spellLevel)); + } break; + case SpellType::Scroll: { + AddInfoBoxString(fmt::format(fmt::runtime(_("Scroll of {:s}")), pgettext("spell", GetSpellData(spellId).sNameText))); + const int scrollCount = c_count_if(InventoryAndBeltPlayerItemsRange { myPlayer }, [spellId](const Item &item) { + return item.isScrollOf(spellId); + }); + AddInfoBoxString(fmt::format(fmt::runtime(ngettext("{:d} Scroll", "{:d} Scrolls", scrollCount)), scrollCount)); + } break; + case SpellType::Charges: + AddInfoBoxString(fmt::format(fmt::runtime(_("Staff of {:s}")), pgettext("spell", GetSpellData(spellId).sNameText))); + AddInfoBoxString(fmt::format(fmt::runtime(ngettext("{:d} Charge", "{:d} Charges", myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges)), myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges)); + break; + case SpellType::Invalid: + break; + } + } + } + + Rectangle belt = BeltRect; + + SetPanelObjectPosition(UiPanels::Main, belt); + + if (belt.contains(MousePosition)) + pcursinvitem = CheckInvHLight(); + + if (CheckXPBarInfo()) + MainPanelFlag = true; +} + +void DrawInfoBox(const Surface &out) +{ + DrawPanelBox(out, MakeSdlRect(InfoBoxRect.position.x, InfoBoxRect.position.y + PanelPaddingHeight, InfoBoxRect.size.width, InfoBoxRect.size.height), GetMainPanel().position + Displacement { InfoBoxRect.position.x, InfoBoxRect.position.y }); + if (!MainPanelFlag && !trigflag && pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell && !SpellSelectFlag && pcurs != CURSOR_HOURGLASS) { + InfoString = StringOrView {}; + InfoColor = UiFlags::ColorWhite; + } + const Player &myPlayer = *MyPlayer; + if (SpellSelectFlag || trigflag || pcurs == CURSOR_HOURGLASS) { + InfoColor = UiFlags::ColorWhite; + } else if (!myPlayer.HoldItem.isEmpty()) { + if (myPlayer.HoldItem._itype == ItemType::Gold) { + const int nGold = myPlayer.HoldItem._ivalue; + InfoString = fmt::format(fmt::runtime(ngettext("{:s} gold piece", "{:s} gold pieces", nGold)), FormatInteger(nGold)); + } else if (!myPlayer.CanUseItem(myPlayer.HoldItem)) { + InfoString = _("Requirements not met"); + } else { + InfoString = myPlayer.HoldItem.getName(); + InfoColor = myPlayer.HoldItem.getTextColor(); + } + } else { + if (pcursitem != -1) + GetItemStr(Items[pcursitem]); + else if (ObjectUnderCursor != nullptr) + GetObjectStr(*ObjectUnderCursor); + if (pcursmonst != -1) { + if (leveltype != DTYPE_TOWN) { + const Monster &monster = Monsters[pcursmonst]; + InfoColor = UiFlags::ColorWhite; + InfoString = monster.name(); + if (monster.isUnique()) { + InfoColor = UiFlags::ColorWhitegold; + PrintUniqueHistory(); + } else { + PrintMonstHistory(monster.type().type); + } + } else if (pcursitem == -1) { + InfoString = std::string_view(Towners[pcursmonst].name); + } + } + if (PlayerUnderCursor != nullptr) { + InfoColor = UiFlags::ColorWhitegold; + const auto &target = *PlayerUnderCursor; + InfoString = std::string_view(target._pName); + AddInfoBoxString(fmt::format(fmt::runtime(_("{:s}, Level: {:d}")), target.getClassName(), target.getCharacterLevel())); + AddInfoBoxString(fmt::format(fmt::runtime(_("Hit Points {:d} of {:d}")), target._pHitPoints >> 6, target._pMaxHP >> 6)); + } + if (PortraitIdUnderCursor != -1) { + InfoColor = UiFlags::ColorWhitegold; + auto &target = Players[PortraitIdUnderCursor]; + InfoString = std::string_view(target._pName); + AddInfoBoxString(_("Right click to inspect")); + } + } + if (!InfoString.empty()) + PrintInfo(out); +} + +void DrawFloatingInfoBox(const Surface &out) +{ + if (pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell) { + FloatingInfoString = StringOrView {}; + InfoColor = UiFlags::ColorWhite; + } + + if (!FloatingInfoString.empty()) + PrintFloatingInfo(out); +} + +} // namespace devilution diff --git a/Source/control/control_panel.cpp b/Source/control/control_panel.cpp new file mode 100644 index 000000000..dd33e6160 --- /dev/null +++ b/Source/control/control_panel.cpp @@ -0,0 +1,832 @@ +#include "control_panel.hpp" +#include "control.hpp" +#include "control_chat.hpp" +#include "control_flasks.hpp" + +#include "automap.h" +#include "controls/control_mode.hpp" +#include "controls/modifier_hints.h" +#include "diablo_msg.hpp" +#include "engine/backbuffer_state.hpp" +#include "engine/load_cel.hpp" +#include "engine/render/clx_render.hpp" +#include "engine/trn.hpp" +#include "gamemenu.h" +#include "headless_mode.hpp" +#include "minitext.h" +#include "options.h" +#include "panels/charpanel.hpp" +#include "panels/mainpanel.hpp" +#include "panels/partypanel.hpp" +#include "panels/spell_book.hpp" +#include "panels/spell_icons.hpp" +#include "panels/spell_list.hpp" +#include "pfile.h" +#include "qol/stash.h" +#include "stores.h" +#include "utils/sdl_compat.h" + +namespace devilution { + +bool CharPanelButton[4]; +bool LevelButtonDown; +bool CharPanelButtonActive; +UiFlags InfoColor; +int SpellbookTab; +bool ChatFlag; +bool SpellbookFlag; +bool CharFlag; +bool MainPanelFlag; +bool MainPanelButtonDown; +bool SpellSelectFlag; +Rectangle MainPanel; +Rectangle LeftPanel; +Rectangle RightPanel; +std::optional BottomBuffer; +OptionalOwnedClxSpriteList GoldBoxBuffer; + +const Rectangle &GetMainPanel() +{ + return MainPanel; +} +const Rectangle &GetLeftPanel() +{ + return LeftPanel; +} +const Rectangle &GetRightPanel() +{ + return RightPanel; +} +bool IsLeftPanelOpen() +{ + return CharFlag || QuestLogIsOpen || IsStashOpen; +} +bool IsRightPanelOpen() +{ + return invflag || SpellbookFlag; +} + +constexpr Size IncrementAttributeButtonSize { 41, 22 }; +/** Maps from attribute_id to the rectangle on screen used for attribute increment buttons. */ +Rectangle CharPanelButtonRect[4] = { + { { 137, 138 }, IncrementAttributeButtonSize }, + { { 137, 166 }, IncrementAttributeButtonSize }, + { { 137, 195 }, IncrementAttributeButtonSize }, + { { 137, 223 }, IncrementAttributeButtonSize } +}; + +constexpr Size WidePanelButtonSize { 71, 20 }; +constexpr Size PanelButtonSize { 33, 32 }; +/** Positions of panel buttons. */ +Rectangle MainPanelButtonRect[8] = { + // clang-format off + { { 9, 9 }, WidePanelButtonSize }, // char button + { { 9, 35 }, WidePanelButtonSize }, // quests button + { { 9, 75 }, WidePanelButtonSize }, // map button + { { 9, 101 }, WidePanelButtonSize }, // menu button + { { 560, 9 }, WidePanelButtonSize }, // inv button + { { 560, 35 }, WidePanelButtonSize }, // spells button + { { 87, 91 }, PanelButtonSize }, // chat button + { { 527, 91 }, PanelButtonSize }, // friendly fire button + // clang-format on +}; + +Rectangle LevelButtonRect = { { 40, -39 }, { 41, 22 } }; + +constexpr int BeltItems = 8; +constexpr Size BeltSize { (INV_SLOT_SIZE_PX + 1) * BeltItems, INV_SLOT_SIZE_PX }; +Rectangle BeltRect { { 205, 5 }, BeltSize }; + +Rectangle SpellButtonRect { { 565, 64 }, { 56, 56 } }; + +int PanelPaddingHeight = 16; + +/** Maps from panel_button_id to panel button description. */ +const char *const PanBtnStr[8] = { + N_("Character Information"), + N_("Quests log"), + N_("Automap"), + N_("Main Menu"), + N_("Inventory"), + N_("Spell book"), + N_("Send Message"), + "" // Player attack +}; + +/** Maps from panel_button_id to hotkey name. */ +const char *const PanBtnHotKey[8] = { "'c'", "'q'", N_("Tab"), N_("Esc"), "'i'", "'b'", N_("Enter"), nullptr }; + +int TotalSpMainPanelButtons = 6; +int TotalMpMainPanelButtons = 8; + +namespace { + +OptionalOwnedClxSpriteList pDurIcons; +OptionalOwnedClxSpriteList multiButtons; +OptionalOwnedClxSpriteList pMainPanelButtons; + +enum panel_button_id : uint8_t { + PanelButtonCharinfo, + PanelButtonFirst = PanelButtonCharinfo, + PanelButtonQlog, + PanelButtonAutomap, + PanelButtonMainmenu, + PanelButtonInventory, + PanelButtonSpellbook, + PanelButtonSendmsg, + PanelButtonFriendly, + PanelButtonLast = PanelButtonFriendly, +}; + +bool MainPanelButtons[PanelButtonLast + 1]; + +void SetMainPanelButtonDown(int btnId) +{ + MainPanelButtons[btnId] = true; + RedrawComponent(PanelDrawComponent::ControlButtons); + MainPanelButtonDown = true; +} + +void SetMainPanelButtonUp() +{ + RedrawComponent(PanelDrawComponent::ControlButtons); + MainPanelButtonDown = false; +} + +int CapStatPointsToAdd(int remainingStatPoints, const Player &player, CharacterAttribute attribute) +{ + const int pointsToReachCap = player.GetMaximumAttributeValue(attribute) - player.GetBaseAttributeValue(attribute); + + return std::min(remainingStatPoints, pointsToReachCap); +} + +int DrawDurIcon4Item(const Surface &out, Item &pItem, int x, int c) +{ + const int durabilityThresholdGold = 5; + const int durabilityThresholdRed = 2; + + if (pItem.isEmpty()) + return x; + if (pItem._iDurability > durabilityThresholdGold) + return x; + if (c == 0) { + switch (pItem._itype) { + case ItemType::Sword: + c = 1; + break; + case ItemType::Axe: + c = 5; + break; + case ItemType::Bow: + c = 6; + break; + case ItemType::Mace: + c = 4; + break; + case ItemType::Staff: + c = 7; + break; + case ItemType::Shield: + default: + c = 0; + break; + } + } + + // Calculate how much of the icon should be gold and red + const int height = (*pDurIcons)[c].height(); // Height of durability icon CEL + int partition = 0; + if (pItem._iDurability > durabilityThresholdRed) { + const int current = pItem._iDurability - durabilityThresholdRed; + partition = (height * current) / (durabilityThresholdGold - durabilityThresholdRed); + } + + // Draw icon + const int y = -17 + GetMainPanel().position.y; + if (partition > 0) { + const Surface stenciledBuffer = out.subregionY(y - partition, partition); + ClxDraw(stenciledBuffer, { x, partition }, (*pDurIcons)[c + 8]); // Gold icon + } + if (partition != height) { + const Surface stenciledBuffer = out.subregionY(y - height, height - partition); + ClxDraw(stenciledBuffer, { x, height }, (*pDurIcons)[c]); // Red icon + } + + return x - (*pDurIcons)[c].height() - 8; // Add in spacing for the next durability icon +} + +bool IsLevelUpButtonVisible() +{ + if (SpellSelectFlag || CharFlag || MyPlayer->_pStatPts == 0) { + return false; + } + if (ControlMode == ControlTypes::VirtualGamepad) { + return false; + } + if (IsPlayerInStore() || IsStashOpen) { + return false; + } + if (QuestLogIsOpen && GetLeftPanel().contains(GetMainPanel().position + Displacement { 0, -74 })) { + return false; + } + + return true; +} + +} // namespace + +void CalculatePanelAreas() +{ + constexpr Size MainPanelSize { 640, 128 }; + + MainPanel = { + { (gnScreenWidth - MainPanelSize.width) / 2, gnScreenHeight - MainPanelSize.height }, + MainPanelSize + }; + LeftPanel = { + { 0, 0 }, + SidePanelSize + }; + RightPanel = { + { 0, 0 }, + SidePanelSize + }; + + if (ControlMode == ControlTypes::VirtualGamepad) { + LeftPanel.position.x = gnScreenWidth / 2 - LeftPanel.size.width; + } else { + if (gnScreenWidth - LeftPanel.size.width - RightPanel.size.width > MainPanel.size.width) { + LeftPanel.position.x = (gnScreenWidth - LeftPanel.size.width - RightPanel.size.width - MainPanel.size.width) / 2; + } + } + LeftPanel.position.y = (gnScreenHeight - LeftPanel.size.height - MainPanel.size.height) / 2; + + if (ControlMode == ControlTypes::VirtualGamepad) { + RightPanel.position.x = gnScreenWidth / 2; + } else { + RightPanel.position.x = gnScreenWidth - RightPanel.size.width - LeftPanel.position.x; + } + RightPanel.position.y = LeftPanel.position.y; + + gnViewportHeight = gnScreenHeight; + if (gnScreenWidth <= MainPanel.size.width) { + // Part of the screen is fully obscured by the UI + gnViewportHeight -= MainPanel.size.height; + } +} + +void FocusOnCharInfo() +{ + const Player &myPlayer = *MyPlayer; + + if (invflag || myPlayer._pStatPts <= 0) + return; + + // Find the first incrementable stat. + int stat = -1; + for (auto attribute : enum_values()) { + if (myPlayer.GetBaseAttributeValue(attribute) >= myPlayer.GetMaximumAttributeValue(attribute)) + continue; + stat = static_cast(attribute); + } + if (stat == -1) + return; + + SetCursorPos(CharPanelButtonRect[stat].Center()); +} + +void OpenCharPanel() +{ + QuestLogIsOpen = false; + CloseGoldWithdraw(); + CloseStash(); + CharFlag = true; +} + +void CloseCharPanel() +{ + CharFlag = false; + if (IsInspectingPlayer()) { + InspectPlayer = MyPlayer; + RedrawEverything(); + + if (InspectingFromPartyPanel) + InspectingFromPartyPanel = false; + else + InitDiabloMsg(_("Stopped inspecting players.")); + } +} + +void ToggleCharPanel() +{ + if (CharFlag) + CloseCharPanel(); + else + OpenCharPanel(); +} + +Point GetPanelPosition(UiPanels panel, Point offset) +{ + const Displacement displacement { offset.x, offset.y }; + + switch (panel) { + case UiPanels::Main: + return GetMainPanel().position + displacement; + case UiPanels::Quest: + case UiPanels::Character: + case UiPanels::Stash: + return GetLeftPanel().position + displacement; + case UiPanels::Spell: + case UiPanels::Inventory: + return GetRightPanel().position + displacement; + default: + return GetMainPanel().position + displacement; + } +} + +void DrawPanelBox(const Surface &out, SDL_Rect srcRect, Point targetPosition) +{ + out.BlitFrom(*BottomBuffer, srcRect, targetPosition); +} + +tl::expected InitMainPanel() +{ + if (!HeadlessMode) { + BottomBuffer.emplace(GetMainPanel().size.width, (GetMainPanel().size.height + PanelPaddingHeight) * (IsChatAvailable() ? 2 : 1)); + pManaBuff.emplace(88, 88); + pLifeBuff.emplace(88, 88); + + RETURN_IF_ERROR(LoadPartyPanel()); + RETURN_IF_ERROR(LoadCharPanel()); + RETURN_IF_ERROR(LoadLargeSpellIcons()); + { + ASSIGN_OR_RETURN(const OwnedClxSpriteList sprite, LoadCelWithStatus("ctrlpan\\panel8", GetMainPanel().size.width)); + ClxDraw(*BottomBuffer, { 0, (GetMainPanel().size.height + PanelPaddingHeight) - 1 }, sprite[0]); + } + { + const Point bulbsPosition { 0, 87 }; + ASSIGN_OR_RETURN(const OwnedClxSpriteList statusPanel, LoadCelWithStatus("ctrlpan\\p8bulbs", 88)); + ClxDraw(*pLifeBuff, bulbsPosition, statusPanel[0]); + ClxDraw(*pManaBuff, bulbsPosition, statusPanel[1]); + } + } + ChatFlag = false; + ChatInputState = std::nullopt; + if (IsChatAvailable()) { + if (!HeadlessMode) { + { + ASSIGN_OR_RETURN(const OwnedClxSpriteList sprite, LoadCelWithStatus("ctrlpan\\talkpanl", GetMainPanel().size.width)); + ClxDraw(*BottomBuffer, { 0, (GetMainPanel().size.height + PanelPaddingHeight) * 2 - 1 }, sprite[0]); + } + multiButtons = LoadCel("ctrlpan\\p8but2", 33); + talkButtons = LoadCel("ctrlpan\\talkbutt", 61); + } + sgbPlrTalkTbl = 0; + TalkMessage[0] = '\0'; + for (bool &whisper : WhisperList) + whisper = true; + for (bool &talkButtonDown : TalkButtonsDown) + talkButtonDown = false; + } + MainPanelFlag = false; + LevelButtonDown = false; + if (!HeadlessMode) { + RETURN_IF_ERROR(LoadMainPanel()); + ASSIGN_OR_RETURN(pMainPanelButtons, LoadCelWithStatus("ctrlpan\\panel8bu", 71)); + + static const uint16_t CharButtonsFrameWidths[9] { 95, 41, 41, 41, 41, 41, 41, 41, 41 }; + ASSIGN_OR_RETURN(pChrButtons, LoadCelWithStatus("data\\charbut", CharButtonsFrameWidths)); + } + ResetMainPanelButtons(); + if (!HeadlessMode) + pDurIcons = LoadCel("items\\duricons", 32); + for (bool &buttonEnabled : CharPanelButton) + buttonEnabled = false; + CharPanelButtonActive = false; + InfoString = StringOrView {}; + FloatingInfoString = StringOrView {}; + RedrawComponent(PanelDrawComponent::Health); + RedrawComponent(PanelDrawComponent::Mana); + CloseCharPanel(); + SpellSelectFlag = false; + SpellbookTab = 0; + SpellbookFlag = false; + + if (!HeadlessMode) { + InitSpellBook(); + ASSIGN_OR_RETURN(pQLogCel, LoadCelWithStatus("data\\quest", static_cast(SidePanelSize.width))); + ASSIGN_OR_RETURN(GoldBoxBuffer, LoadCelWithStatus("ctrlpan\\golddrop", 261)); + } + CloseGoldDrop(); + CalculatePanelAreas(); + + if (!HeadlessMode) + InitModifierHints(); + + return {}; +} + +void DrawMainPanel(const Surface &out) +{ + DrawPanelBox(out, MakeSdlRect(0, sgbPlrTalkTbl + PanelPaddingHeight, GetMainPanel().size.width, GetMainPanel().size.height), GetMainPanel().position); + DrawInfoBox(out); +} + +void DrawMainPanelButtons(const Surface &out) +{ + const Point mainPanelPosition = GetMainPanel().position; + + for (int i = 0; i < TotalSpMainPanelButtons; i++) { + if (!MainPanelButtons[i]) { + DrawPanelBox(out, MakeSdlRect(MainPanelButtonRect[i].position.x, MainPanelButtonRect[i].position.y + PanelPaddingHeight, MainPanelButtonRect[i].size.width, MainPanelButtonRect[i].size.height + 1), mainPanelPosition + Displacement { MainPanelButtonRect[i].position.x, MainPanelButtonRect[i].position.y }); + } else { + const Point position = mainPanelPosition + Displacement { MainPanelButtonRect[i].position.x, MainPanelButtonRect[i].position.y }; + RenderClxSprite(out, (*pMainPanelButtons)[i], position); + RenderClxSprite(out, (*PanelButtonDown)[i], position + Displacement { 4, 0 }); + } + } + + if (IsChatAvailable()) { + RenderClxSprite(out, (*multiButtons)[MainPanelButtons[PanelButtonSendmsg] ? 1 : 0], mainPanelPosition + Displacement { MainPanelButtonRect[PanelButtonSendmsg].position.x, MainPanelButtonRect[PanelButtonSendmsg].position.y }); + + const Point friendlyButtonPosition = mainPanelPosition + Displacement { MainPanelButtonRect[PanelButtonFriendly].position.x, MainPanelButtonRect[PanelButtonFriendly].position.y }; + + if (MyPlayer->friendlyMode) + RenderClxSprite(out, (*multiButtons)[MainPanelButtons[PanelButtonFriendly] ? 3 : 2], friendlyButtonPosition); + else + RenderClxSprite(out, (*multiButtons)[MainPanelButtons[PanelButtonFriendly] ? 5 : 4], friendlyButtonPosition); + } +} + +void ResetMainPanelButtons() +{ + for (bool &panelButton : MainPanelButtons) + panelButton = false; + SetMainPanelButtonUp(); +} + +void CheckMainPanelButton() +{ + const int totalButtons = IsChatAvailable() ? TotalMpMainPanelButtons : TotalSpMainPanelButtons; + + for (int i = 0; i < totalButtons; i++) { + Rectangle button = MainPanelButtonRect[i]; + + SetPanelObjectPosition(UiPanels::Main, button); + + if (button.contains(MousePosition)) { + SetMainPanelButtonDown(i); + } + } + + Rectangle spellSelectButton = SpellButtonRect; + + SetPanelObjectPosition(UiPanels::Main, spellSelectButton); + + if (!SpellSelectFlag && spellSelectButton.contains(MousePosition)) { + if ((SDL_GetModState() & SDL_KMOD_SHIFT) != 0) { + Player &myPlayer = *MyPlayer; + myPlayer._pRSpell = SpellID::Invalid; + myPlayer._pRSplType = SpellType::Invalid; + RedrawEverything(); + return; + } + DoSpeedBook(); + gamemenu_off(); + } +} + +void CheckMainPanelButtonDead() +{ + Rectangle menuButton = MainPanelButtonRect[PanelButtonMainmenu]; + + SetPanelObjectPosition(UiPanels::Main, menuButton); + + if (menuButton.contains(MousePosition)) { + SetMainPanelButtonDown(PanelButtonMainmenu); + return; + } + + Rectangle chatButton = MainPanelButtonRect[PanelButtonSendmsg]; + + SetPanelObjectPosition(UiPanels::Main, chatButton); + + if (chatButton.contains(MousePosition)) { + SetMainPanelButtonDown(PanelButtonSendmsg); + } +} + +void DoAutoMap() +{ + if (!AutomapActive) + StartAutomap(); + else + AutomapActive = false; +} + +void CycleAutomapType() +{ + if (!AutomapActive) { + StartAutomap(); + return; + } + const AutomapType newType { static_cast>( + (static_cast(GetAutomapType()) + 1) % enum_size::value) }; + SetAutomapType(newType); + if (newType == AutomapType::FIRST) { + AutomapActive = false; + } +} + +void CheckMainPanelButtonUp() +{ + bool gamemenuOff = true; + + SetMainPanelButtonUp(); + + for (int i = PanelButtonFirst; i <= PanelButtonLast; i++) { + if (!MainPanelButtons[i]) + continue; + + MainPanelButtons[i] = false; + + Rectangle button = MainPanelButtonRect[i]; + + SetPanelObjectPosition(UiPanels::Main, button); + + if (!button.contains(MousePosition)) + continue; + + switch (i) { + case PanelButtonCharinfo: + ToggleCharPanel(); + break; + case PanelButtonQlog: + CloseCharPanel(); + CloseGoldWithdraw(); + CloseStash(); + if (!QuestLogIsOpen) + StartQuestlog(); + else + QuestLogIsOpen = false; + break; + case PanelButtonAutomap: + DoAutoMap(); + break; + case PanelButtonMainmenu: + if (MyPlayerIsDead) { + if (!gbIsMultiplayer) { + if (gbValidSaveFile) + gamemenu_load_game(false); + else + gamemenu_exit_game(false); + } else { + NetSendCmd(true, CMD_RETOWN); + } + break; + } else if (MyPlayer->hasNoLife()) { + break; + } + qtextflag = false; + gamemenu_handle_previous(); + gamemenuOff = false; + break; + case PanelButtonInventory: + SpellbookFlag = false; + CloseGoldWithdraw(); + CloseStash(); + invflag = !invflag; + CloseGoldDrop(); + break; + case PanelButtonSpellbook: + CloseInventory(); + CloseGoldDrop(); + SpellbookFlag = !SpellbookFlag; + break; + case PanelButtonSendmsg: + if (ChatFlag) + ResetChat(); + else + TypeChatMessage(); + break; + case PanelButtonFriendly: + // Toggle friendly Mode + NetSendCmd(true, CMD_FRIENDLYMODE); + break; + } + } + + if (gamemenuOff) + gamemenu_off(); +} + +void FreeControlPan() +{ + BottomBuffer = std::nullopt; + pManaBuff = std::nullopt; + pLifeBuff = std::nullopt; + FreeLargeSpellIcons(); + FreeSpellBook(); + pMainPanelButtons = std::nullopt; + multiButtons = std::nullopt; + talkButtons = std::nullopt; + pChrButtons = std::nullopt; + pDurIcons = std::nullopt; + pQLogCel = std::nullopt; + GoldBoxBuffer = std::nullopt; + FreeMainPanel(); + FreePartyPanel(); + FreeCharPanel(); + FreeModifierHints(); +} + +void CheckLevelButton() +{ + if (!IsLevelUpButtonVisible()) { + return; + } + + Rectangle button = LevelButtonRect; + + SetPanelObjectPosition(UiPanels::Main, button); + + if (!LevelButtonDown && button.contains(MousePosition)) + LevelButtonDown = true; +} + +void CheckLevelButtonUp() +{ + Rectangle button = LevelButtonRect; + + SetPanelObjectPosition(UiPanels::Main, button); + + if (button.contains(MousePosition)) { + OpenCharPanel(); + } + LevelButtonDown = false; +} + +void DrawLevelButton(const Surface &out) +{ + if (IsLevelUpButtonVisible()) { + const int nCel = LevelButtonDown ? 2 : 1; + DrawString(out, _("Level Up"), { GetMainPanel().position + Displacement { 0, LevelButtonRect.position.y - 23 }, { 120, 0 } }, + { .flags = UiFlags::ColorWhite | UiFlags::AlignCenter | UiFlags::KerningFitSpacing }); + RenderClxSprite(out, (*pChrButtons)[nCel], GetMainPanel().position + Displacement { LevelButtonRect.position.x, LevelButtonRect.position.y }); + } +} + +void CheckChrBtns() +{ + const Player &myPlayer = *MyPlayer; + + if (myPlayer._pmode == PM_DEATH) + return; + + if (CharPanelButtonActive || myPlayer._pStatPts == 0) + return; + + for (auto attribute : enum_values()) { + if (myPlayer.GetBaseAttributeValue(attribute) >= myPlayer.GetMaximumAttributeValue(attribute)) + continue; + auto buttonId = static_cast(attribute); + Rectangle button = CharPanelButtonRect[buttonId]; + SetPanelObjectPosition(UiPanels::Character, button); + if (button.contains(MousePosition)) { + CharPanelButton[buttonId] = true; + CharPanelButtonActive = true; + } + } +} + +void ReleaseChrBtns(bool addAllStatPoints) +{ + const Player &myPlayer = *MyPlayer; + + if (myPlayer._pmode == PM_DEATH) + return; + + CharPanelButtonActive = false; + for (auto attribute : enum_values()) { + auto buttonId = static_cast(attribute); + if (!CharPanelButton[buttonId]) + continue; + + CharPanelButton[buttonId] = false; + Rectangle button = CharPanelButtonRect[buttonId]; + SetPanelObjectPosition(UiPanels::Character, button); + if (button.contains(MousePosition)) { + Player &myPlayer = *MyPlayer; + int statPointsToAdd = 1; + if (addAllStatPoints) + statPointsToAdd = CapStatPointsToAdd(myPlayer._pStatPts, myPlayer, attribute); + switch (attribute) { + case CharacterAttribute::Strength: + NetSendCmdParam1(true, CMD_ADDSTR, statPointsToAdd); + myPlayer._pStatPts -= statPointsToAdd; + break; + case CharacterAttribute::Magic: + NetSendCmdParam1(true, CMD_ADDMAG, statPointsToAdd); + myPlayer._pStatPts -= statPointsToAdd; + break; + case CharacterAttribute::Dexterity: + NetSendCmdParam1(true, CMD_ADDDEX, statPointsToAdd); + myPlayer._pStatPts -= statPointsToAdd; + break; + case CharacterAttribute::Vitality: + NetSendCmdParam1(true, CMD_ADDVIT, statPointsToAdd); + myPlayer._pStatPts -= statPointsToAdd; + break; + } + } + } +} + +void DrawDurIcon(const Surface &out) +{ + const bool hasRoomBetweenPanels = RightPanel.position.x - (LeftPanel.position.x + LeftPanel.size.width) >= 16 + (32 + 8 + 32 + 8 + 32 + 8 + 32) + 16; + const bool hasRoomUnderPanels = MainPanel.position.y - (RightPanel.position.y + RightPanel.size.height) >= 16 + 32 + 16; + + if (!hasRoomBetweenPanels && !hasRoomUnderPanels) { + if (IsLeftPanelOpen() && IsRightPanelOpen()) + return; + } + + int x = MainPanel.position.x + MainPanel.size.width - 32 - 16; + if (!hasRoomUnderPanels) { + if (IsRightPanelOpen() && MainPanel.position.x + MainPanel.size.width > RightPanel.position.x) + x -= MainPanel.position.x + MainPanel.size.width - RightPanel.position.x; + } + + Player &myPlayer = *MyPlayer; + x = DrawDurIcon4Item(out, myPlayer.InvBody[INVLOC_HEAD], x, 3); + x = DrawDurIcon4Item(out, myPlayer.InvBody[INVLOC_CHEST], x, 2); + x = DrawDurIcon4Item(out, myPlayer.InvBody[INVLOC_HAND_LEFT], x, 0); + DrawDurIcon4Item(out, myPlayer.InvBody[INVLOC_HAND_RIGHT], x, 0); +} + +void RedBack(const Surface &out) +{ + uint8_t *dst = out.begin(); + uint8_t *tbl = GetPauseTRN(); + for (int h = gnViewportHeight; h != 0; h--, dst += out.pitch() - gnScreenWidth) { + for (int w = gnScreenWidth; w != 0; w--) { + if (leveltype != DTYPE_HELL || *dst >= 32) + *dst = tbl[*dst]; + dst++; + } + } +} + +void DrawDeathText(const Surface &out) +{ + const TextRenderOptions largeTextOptions { + .flags = UiFlags::FontSize42 | UiFlags::ColorGold | UiFlags::AlignCenter | UiFlags::VerticalCenter, + .spacing = 2 + }; + const TextRenderOptions smallTextOptions { + .flags = UiFlags::FontSize30 | UiFlags::ColorGold | UiFlags::AlignCenter | UiFlags::VerticalCenter, + .spacing = 2 + }; + std::string text; + const int verticalPadding = 42; + Point linePosition { 0, gnScreenHeight / 2 - (verticalPadding * 2) }; + + text = _("You have died"); + DrawString(out, text, linePosition, largeTextOptions); + linePosition.y += verticalPadding; + + std::string buttonText; + + switch (ControlMode) { + case ControlTypes::KeyboardAndMouse: + buttonText = _("ESC"); + break; + case ControlTypes::Gamepad: + buttonText = ToString(GamepadType, ControllerButton_BUTTON_START); + break; + case ControlTypes::VirtualGamepad: + buttonText = _("Menu Button"); + break; + default: + break; + } + + if (!gbIsMultiplayer) { + if (gbValidSaveFile) + text = fmt::format(fmt::runtime(_("Press {} to load last save.")), buttonText); + else + text = fmt::format(fmt::runtime(_("Press {} to return to Main Menu.")), buttonText); + + } else { + text = fmt::format(fmt::runtime(_("Press {} to restart in town.")), buttonText); + } + DrawString(out, text, linePosition, smallTextOptions); +} + +void SetPanelObjectPosition(UiPanels panel, Rectangle &button) +{ + button.position = GetPanelPosition(panel, button.position); +} + +} // namespace devilution diff --git a/Source/control/control_panel.hpp b/Source/control/control_panel.hpp new file mode 100644 index 000000000..732e0dc21 --- /dev/null +++ b/Source/control/control_panel.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "engine/rectangle.hpp" +#include "panels/ui_panels.hpp" + +namespace devilution { + +extern int TotalSpMainPanelButtons; +extern int TotalMpMainPanelButtons; +extern int PanelPaddingHeight; +extern const char *const PanBtnStr[8]; +extern const char *const PanBtnHotKey[8]; +extern Rectangle SpellButtonRect; +extern Rectangle BeltRect; + +void SetPanelObjectPosition(UiPanels panel, Rectangle &button); + +} // namespace devilution diff --git a/Source/controls/controller_motion.cpp b/Source/controls/controller_motion.cpp index 5b8b4cb90..692c25057 100644 --- a/Source/controls/controller_motion.cpp +++ b/Source/controls/controller_motion.cpp @@ -9,7 +9,7 @@ #include #endif -#include "control.h" +#include "control/control.hpp" #include "controls/control_mode.hpp" #include "controls/controller.h" #ifndef USE_SDL1 diff --git a/Source/controls/keymapper.cpp b/Source/controls/keymapper.cpp index 8a7b344cf..b13b74168 100644 --- a/Source/controls/keymapper.cpp +++ b/Source/controls/keymapper.cpp @@ -12,7 +12,7 @@ #endif #endif -#include "control.h" +#include "control/control.hpp" #include "options.h" #include "utils/is_of.hpp" diff --git a/Source/controls/modifier_hints.cpp b/Source/controls/modifier_hints.cpp index 881b3adbc..729655916 100644 --- a/Source/controls/modifier_hints.cpp +++ b/Source/controls/modifier_hints.cpp @@ -4,7 +4,7 @@ #include #include "DiabloUI/ui_flags.hpp" -#include "control.h" +#include "control/control.hpp" #include "controls/controller_motion.h" #include "controls/game_controls.h" #include "controls/plrctrls.h" diff --git a/Source/controls/plrctrls.cpp b/Source/controls/plrctrls.cpp index 37d4c1c70..7a54aba31 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -18,7 +18,7 @@ #endif #include "automap.h" -#include "control.h" +#include "control/control.hpp" #include "controls/controller_motion.h" #ifndef USE_SDL1 #include "controls/devices/game_controller.h" @@ -539,7 +539,7 @@ void Interact() return; } - if (leveltype != DTYPE_TOWN && PlayerUnderCursor != nullptr && !myPlayer.friendlyMode) { + if (leveltype != DTYPE_TOWN && PlayerUnderCursor != nullptr && !PlayerUnderCursor->hasNoLife() && !myPlayer.friendlyMode) { NetSendCmdParam1(true, myPlayer.UsesRangedWeapon() ? CMD_RATTACKPID : CMD_ATTACKPID, PlayerUnderCursor->getId()); LastPlayerAction = PlayerActionType::AttackPlayerTarget; return; diff --git a/Source/controls/touch/event_handlers.cpp b/Source/controls/touch/event_handlers.cpp index 30e205e13..f6c3e435d 100644 --- a/Source/controls/touch/event_handlers.cpp +++ b/Source/controls/touch/event_handlers.cpp @@ -7,7 +7,7 @@ #include #endif -#include "control.h" +#include "control/control.hpp" #include "controls/plrctrls.h" #include "cursor.h" #include "diablo.h" diff --git a/Source/controls/touch/gamepad.cpp b/Source/controls/touch/gamepad.cpp index 257e6b4da..57facd7b2 100644 --- a/Source/controls/touch/gamepad.cpp +++ b/Source/controls/touch/gamepad.cpp @@ -6,7 +6,7 @@ #include #endif -#include "control.h" +#include "control/control.hpp" #include "controls/touch/event_handlers.h" #include "controls/touch/gamepad.h" #include "quests.h" diff --git a/Source/controls/touch/renderers.cpp b/Source/controls/touch/renderers.cpp index 56384c017..29b01cdad 100644 --- a/Source/controls/touch/renderers.cpp +++ b/Source/controls/touch/renderers.cpp @@ -6,7 +6,7 @@ #include #endif -#include "control.h" +#include "control/control.hpp" #include "cursor.h" #include "diablo.h" #include "doom.h" diff --git a/Source/cursor.cpp b/Source/cursor.cpp index fa4cef28a..0fa280b6c 100644 --- a/Source/cursor.cpp +++ b/Source/cursor.cpp @@ -21,7 +21,7 @@ #include #include "DiabloUI/diabloui.h" -#include "control.h" +#include "control/control.hpp" #include "controls/control_mode.hpp" #include "controls/plrctrls.h" #include "doom.h" diff --git a/Source/dead.cpp b/Source/dead.cpp index 99c41edd4..ee4b88d5e 100644 --- a/Source/dead.cpp +++ b/Source/dead.cpp @@ -11,8 +11,8 @@ #include "headless_mode.hpp" #include "levels/gendung.h" #include "lighting.h" -#include "misdat.h" #include "monster.h" +#include "tables/misdat.h" namespace devilution { diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 4f51b7a76..0339a9043 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -27,7 +27,7 @@ #include "appfat.h" #include "automap.h" #include "capture.h" -#include "control.h" +#include "control/control.hpp" #include "cursor.h" #include "dead.h" #ifdef _DEBUG @@ -76,7 +76,6 @@ #include "menu.h" #include "minitext.h" #include "missiles.h" -#include "monstdat.h" #include "movie.h" #include "multi.h" #include "nthread.h" @@ -88,7 +87,6 @@ #include "panels/spell_book.hpp" #include "panels/spell_list.hpp" #include "pfile.h" -#include "playerdat.hpp" #include "plrmsg.h" #include "qol/chatlog.h" #include "qol/floatingnumbers.h" @@ -101,6 +99,8 @@ #include "stores.h" #include "storm/storm_net.hpp" #include "storm/storm_svid.h" +#include "tables/monstdat.h" +#include "tables/playerdat.hpp" #include "towners.h" #include "track.h" #include "utils/console.h" @@ -281,7 +281,7 @@ void LeftMouseCmd(bool bShift) LastPlayerAction = PlayerActionType::AttackMonsterTarget; NetSendCmdParam1(true, CMD_RATTACKID, pcursmonst); } - } else if (PlayerUnderCursor != nullptr && !myPlayer.friendlyMode) { + } else if (PlayerUnderCursor != nullptr && !PlayerUnderCursor->hasNoLife() && !myPlayer.friendlyMode) { LastPlayerAction = PlayerActionType::AttackPlayerTarget; NetSendCmdParam1(true, CMD_RATTACKPID, PlayerUnderCursor->getId()); } @@ -301,7 +301,7 @@ void LeftMouseCmd(bool bShift) } else if (pcursmonst != -1) { LastPlayerAction = PlayerActionType::AttackMonsterTarget; NetSendCmdParam1(true, CMD_ATTACKID, pcursmonst); - } else if (PlayerUnderCursor != nullptr && !myPlayer.friendlyMode) { + } else if (PlayerUnderCursor != nullptr && !PlayerUnderCursor->hasNoLife() && !myPlayer.friendlyMode) { LastPlayerAction = PlayerActionType::AttackPlayerTarget; NetSendCmdParam1(true, CMD_ATTACKPID, PlayerUnderCursor->getId()); } @@ -2853,7 +2853,7 @@ bool TryIconCurs() NetSendCmdLocParam4(true, CMD_SPELLXYD, cursPosition, static_cast(spellID), static_cast(spellType), static_cast(sd), spellFrom); } else if (pcursmonst != -1 && leveltype != DTYPE_TOWN) { NetSendCmdParam4(true, CMD_SPELLID, pcursmonst, static_cast(spellID), static_cast(spellType), spellFrom); - } else if (PlayerUnderCursor != nullptr && !myPlayer.friendlyMode) { + } else if (PlayerUnderCursor != nullptr && !PlayerUnderCursor->hasNoLife() && !myPlayer.friendlyMode) { NetSendCmdParam4(true, CMD_SPELLPID, PlayerUnderCursor->getId(), static_cast(spellID), static_cast(spellType), spellFrom); } else { NetSendCmdLocParam3(true, CMD_SPELLXY, cursPosition, static_cast(spellID), static_cast(spellType), spellFrom); diff --git a/Source/diablo.h b/Source/diablo.h index c977e252b..ad28ebb82 100644 --- a/Source/diablo.h +++ b/Source/diablo.h @@ -19,7 +19,7 @@ #endif #ifdef _DEBUG -#include "monstdat.h" +#include "tables/monstdat.h" #endif #include "levels/gendung.h" #include "utils/attributes.h" diff --git a/Source/discord/discord.cpp b/Source/discord/discord.cpp index 95b022946..d21fbccf4 100644 --- a/Source/discord/discord.cpp +++ b/Source/discord/discord.cpp @@ -23,7 +23,7 @@ #include "multi.h" #include "panels/charpanel.hpp" #include "player.h" -#include "playerdat.hpp" +#include "tables/playerdat.hpp" #include "utils/language.h" #include "utils/str_cat.hpp" diff --git a/Source/doom.cpp b/Source/doom.cpp index b665c649f..2fa8f93dd 100644 --- a/Source/doom.cpp +++ b/Source/doom.cpp @@ -7,7 +7,7 @@ #include -#include "control.h" +#include "control/control.hpp" #include "engine/clx_sprite.hpp" #include "engine/load_cel.hpp" #include "engine/render/clx_render.hpp" diff --git a/Source/dvlnet/base.cpp b/Source/dvlnet/base.cpp index 2294bc940..9bc2a6e6c 100644 --- a/Source/dvlnet/base.cpp +++ b/Source/dvlnet/base.cpp @@ -155,6 +155,10 @@ tl::expected base::HandleDisconnect(packet &pkt) tl::expected base::HandleEchoRequest(packet &pkt) { + // If we have already left the game, + // there is no need to respond to echoes + if (plr_self == PLR_BROADCAST) return {}; + return pkt.Time() .and_then([&](cookie_t &&pktTime) { return pktfty->make_packet(plr_self, pkt.Source(), pktTime); @@ -168,6 +172,7 @@ tl::expected base::HandleEchoReply(packet &pkt) { const uint32_t now = SDL_GetTicks(); plr_t src = pkt.Source(); + if (src >= MAX_PLRS) return {}; return pkt.Time().transform([&](cookie_t &&pktTime) { PlayerState &playerState = playerStateTable_[src]; playerState.roundTripLatency = now - pktTime; diff --git a/Source/engine/demomode.cpp b/Source/engine/demomode.cpp index d284d6a31..1179c3421 100644 --- a/Source/engine/demomode.cpp +++ b/Source/engine/demomode.cpp @@ -127,7 +127,6 @@ struct { bool autoElixirPickup = false; bool autoOilPickup = false; bool autoPickupInTown = false; - bool adriaRefillsMana = false; bool autoEquipWeapons = false; bool autoEquipArmor = false; bool autoEquipHelms = false; @@ -166,7 +165,7 @@ void ReadSettings(FILE *in, uint8_t version) // NOLINT(readability-identifier-le DemoSettings.autoElixirPickup = ReadByte(in) != 0; DemoSettings.autoOilPickup = ReadByte(in) != 0; DemoSettings.autoPickupInTown = ReadByte(in) != 0; - DemoSettings.adriaRefillsMana = ReadByte(in) != 0; + (void)ReadByte(in); // adriaRefillsMana (removed feature, kept for backward compatibility) DemoSettings.autoEquipWeapons = ReadByte(in) != 0; DemoSettings.autoEquipArmor = ReadByte(in) != 0; DemoSettings.autoEquipHelms = ReadByte(in) != 0; @@ -195,7 +194,6 @@ void ReadSettings(FILE *in, uint8_t version) // NOLINT(readability-identifier-le { _("Auto Elixir Pickup"), DemoSettings.autoGoldPickup }, { _("Auto Oil Pickup"), DemoSettings.autoOilPickup }, { _("Auto Pickup in Town"), DemoSettings.autoPickupInTown }, - { _("Adria Refills Mana"), DemoSettings.adriaRefillsMana }, { _("Auto Equip Weapons"), DemoSettings.autoEquipWeapons }, { _("Auto Equip Armor"), DemoSettings.autoEquipArmor }, { _("Auto Equip Helms"), DemoSettings.autoEquipHelms }, @@ -231,7 +229,7 @@ void WriteSettings(FILE *out) WriteByte(out, static_cast(*options.Gameplay.autoElixirPickup)); WriteByte(out, static_cast(*options.Gameplay.autoOilPickup)); WriteByte(out, static_cast(*options.Gameplay.autoPickupInTown)); - WriteByte(out, static_cast(*options.Gameplay.adriaRefillsMana)); + WriteByte(out, 0); // adriaRefillsMana (removed feature, kept for backward compatibility) WriteByte(out, static_cast(*options.Gameplay.autoEquipWeapons)); WriteByte(out, static_cast(*options.Gameplay.autoEquipArmor)); WriteByte(out, static_cast(*options.Gameplay.autoEquipHelms)); @@ -650,7 +648,6 @@ void OverrideOptions() options.Gameplay.autoElixirPickup.SetValue(DemoSettings.autoElixirPickup); options.Gameplay.autoOilPickup.SetValue(DemoSettings.autoOilPickup); options.Gameplay.autoPickupInTown.SetValue(DemoSettings.autoPickupInTown); - options.Gameplay.adriaRefillsMana.SetValue(DemoSettings.adriaRefillsMana); options.Gameplay.autoEquipWeapons.SetValue(DemoSettings.autoEquipWeapons); options.Gameplay.autoEquipArmor.SetValue(DemoSettings.autoEquipArmor); options.Gameplay.autoEquipHelms.SetValue(DemoSettings.autoEquipHelms); diff --git a/Source/engine/random.hpp b/Source/engine/random.hpp index 17718cec7..67e99fe11 100644 --- a/Source/engine/random.hpp +++ b/Source/engine/random.hpp @@ -1,405 +1,405 @@ -/** - * @file random.hpp - * - * Contains convenience functions for random number generation - * - * This includes specific engine/distribution functions for logic that needs to be compatible with the base game. - */ -#pragma once - -#include -#include -#include -#include - -namespace devilution { - -class DiabloGenerator { -private: - /** Borland C/C++ psuedo-random number generator needed for vanilla compatibility */ - std::linear_congruential_engine lcg; - -public: - /** - * @brief Set the state of the RandomNumberEngine used by the base game to the specific seed - * @param seed New engine state - */ - DiabloGenerator(uint32_t seed) - { - lcg.seed(seed); - } - - /** - * @brief Advance the global RandomNumberEngine state by the specified number of rounds - * - * Only used to maintain vanilla compatibility until logic requiring reproducible random number generation is isolated. - * @param count How many values to discard - */ - void discardRandomValues(unsigned count) - { - lcg.discard(count); - } - - /** - * @brief Generates a random non-negative integer (most of the time) using the vanilla RNG - * - * This advances the engine state then interprets the new engine state as a signed value and calls std::abs to try - * discard the high bit of the result. This usually returns a positive number but may very rarely return -2^31. - * - * This function is only used when the base game wants to store the seed used to generate an item or level, however - * as the returned value is transformed about 50% of values do not reflect the actual engine state. It would be more - * appropriate to use GetLCGEngineState() in these cases but that may break compatibility with the base game. - * - * @return A random number in the range [0,2^31) or -2^31 - */ - int32_t advanceRndSeed() - { - const int32_t seed = static_cast(lcg()); - // since abs(INT_MIN) is undefined behavior, handle this value specially - return seed == std::numeric_limits::min() ? std::numeric_limits::min() : std::abs(seed); - } - - /** - * @brief Generates a random integer less than the given limit using the vanilla RNG - * - * If v is not a positive number this function returns 0 without calling the RNG. - * - * Limits between 32768 and 65534 should be avoided as a bug in vanilla means this function always returns a value - * less than 32768 for limits in that range. - * - * This can very rarely return a negative value in the range (-v, -1] due to the bug in AdvanceRndSeed() - * - * @see AdvanceRndSeed() - * @param v The upper limit for the return value - * @return A random number in the range [0, v) or rarely a negative value in (-v, -1] - */ - int32_t generateRnd(int32_t v) - { - if (v <= 0) - return 0; - if (v <= 0x7FFF) // use the high bits to correct for LCG bias - return (advanceRndSeed() >> 16) % v; - return advanceRndSeed() % v; - } - - /** - * @brief Generates a random boolean value using the vanilla RNG - * - * This function returns true 1 in `frequency` of the time, otherwise false. For example the default frequency of 2 - * represents a 50/50 chance. - * - * @param frequency odds of returning a true value - * @return A random boolean value - */ - bool flipCoin(unsigned frequency) - { - // Casting here because GenerateRnd takes a signed argument when it should take and yield unsigned. - return generateRnd(static_cast(frequency)) == 0; - } - - /** - * @brief Picks one of the elements in the list randomly. - * - * @param values The values to pick from - * @return A random value from the 'values' list. - */ - template - const T pickRandomlyAmong(const std::initializer_list &values) - { - const auto index { std::max(generateRnd(static_cast(values.size())), 0) }; - - return *(values.begin() + index); - } - - /** - * @brief Generates a random non-negative integer - * - * Effectively the same as GenerateRnd but will never return a negative value - * @param v upper limit for the return value - * @return a value between 0 and v-1 inclusive, i.e. the range [0, v) - */ - inline int32_t randomIntLessThan(int32_t v) - { - return std::max(generateRnd(v), 0); - } - - /** - * @brief Randomly chooses a value somewhere within the given range - * @param min lower limit, minimum possible value - * @param max upper limit, either the maximum possible value for a closed range (the default behaviour) or one greater than the maximum value for a half-open range - * @param halfOpen whether to use the limits as a half-open range or not - * @return a randomly selected integer - */ - inline int32_t randomIntBetween(int32_t min, int32_t max, bool halfOpen = false) - { - return randomIntLessThan(max - min + (halfOpen ? 0 : 1)) + min; - } -}; - -// Based on fmix32 implementation from MurmurHash3 created by Austin Appleby in 2008 -// https://github.com/aappleby/smhasher/blob/61a0530f28277f2e850bfc39600ce61d02b518de/src/MurmurHash3.cpp#L68 -// and adapted from https://prng.di.unimi.it/splitmix64.c written in 2015 by Sebastiano Vigna -// -// See also: -// Guy L. Steele, Doug Lea, and Christine H. Flood. 2014. -// Fast splittable pseudorandom number generators. SIGPLAN Not. 49, 10 (October 2014), 453–472. -// https://doi.org/10.1145/2714064.2660195 -class SplitMix32 { - uint32_t state; - -public: - SplitMix32(uint32_t state) - : state(state) - { - } - - uint32_t next() - { - uint32_t z = (state += 0x9e3779b9); - z = (z ^ (z >> 16)) * 0x85ebca6b; - z = (z ^ (z >> 13)) * 0xc2b2ae35; - return z ^ (z >> 16); - } - - void generate(uint32_t *begin, const uint32_t *end) - { - while (begin != end) { - *begin = next(); - ++begin; - } - } -}; - -// Adapted from https://prng.di.unimi.it/splitmix64.c written in 2015 by Sebastiano Vigna -// -// See also: -// Guy L. Steele, Doug Lea, and Christine H. Flood. 2014. -// Fast splittable pseudorandom number generators. SIGPLAN Not. 49, 10 (October 2014), 453–472. -// https://doi.org/10.1145/2714064.2660195 -class SplitMix64 { - uint64_t state; - -public: - SplitMix64(uint64_t state) - : state(state) - { - } - - uint64_t next() - { - uint64_t z = (state += 0x9e3779b97f4a7c15); - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; - z = (z ^ (z >> 27)) * 0x94d049bb133111eb; - return z ^ (z >> 31); - } - - void generate(uint64_t *begin, const uint64_t *end) - { - while (begin != end) { - *begin = next(); - ++begin; - } - } -}; - -/** Adapted from https://prng.di.unimi.it/xoshiro128plusplus.c written in 2019 by David Blackman and Sebastiano Vigna */ -class xoshiro128plusplus { -public: - typedef uint32_t state[4]; - - xoshiro128plusplus() { seed(); } - xoshiro128plusplus(const state &s) { copy(this->s, s); } - xoshiro128plusplus(uint64_t initialSeed) { seed(initialSeed); } - xoshiro128plusplus(uint32_t initialSeed) { seed(initialSeed); } - - uint32_t next(); - - /* This is the jump function for the generator. It is equivalent - to 2^64 calls to next(); it can be used to generate 2^64 - non-overlapping subsequences for parallel computations. */ - void jump() - { - static constexpr uint32_t JUMP[] = { 0x8764000b, 0xf542d2d3, 0x6fa035c3, 0x77f2db5b }; - - uint32_t s0 = 0; - uint32_t s1 = 0; - uint32_t s2 = 0; - uint32_t s3 = 0; - for (const uint32_t entry : JUMP) - for (int b = 0; b < 32; b++) { - if (entry & UINT32_C(1) << b) { - s0 ^= s[0]; - s1 ^= s[1]; - s2 ^= s[2]; - s3 ^= s[3]; - } - next(); - } - - s[0] = s0; - s[1] = s1; - s[2] = s2; - s[3] = s3; - } - - void save(state &s) const - { - copy(s, this->s); - } - -private: - state s; - - void seed(uint64_t value) - { - uint64_t seeds[2]; - SplitMix64 seedSequence { value }; - seedSequence.generate(seeds, seeds + 2); - - s[0] = static_cast(seeds[0] >> 32); - s[1] = static_cast(seeds[0]); - s[2] = static_cast(seeds[1] >> 32); - s[3] = static_cast(seeds[1]); - } - - void seed(uint32_t value) - { - SplitMix32 seedSequence { value }; - seedSequence.generate(s, s + 4); - } - - void seed() - { - seed(timeSeed()); - -#if !(defined(WINVER) && WINVER <= 0x0500 && (!defined(_WIN32_WINNT) || _WIN32_WINNT == 0)) - static std::random_device rd; - std::uniform_int_distribution dist; - for (uint32_t &cell : s) - cell ^= dist(rd); -#endif - } - - static uint64_t timeSeed(); - static void copy(state &dst, const state &src); -}; - -/** - * @brief Returns a copy of the global seed generator and fast-forwards the global seed generator to avoid collisions - */ -xoshiro128plusplus ReserveSeedSequence(); - -/** - * @brief Advances the global seed generator state and returns the new value - */ -uint32_t GenerateSeed(); - -/** - * @brief Set the state of the RandomNumberEngine used by the base game to the specific seed - * @param seed New engine state - */ -void SetRndSeed(uint32_t seed); - -/** - * @brief Returns the current state of the RandomNumberEngine used by the base game - * - * This is only exposed to allow for debugging vanilla code and testing. Using this engine for new code is discouraged - * due to the poor randomness and bugs in the implementation that need to be retained for compatibility. - * - * @return The current engine state - */ -uint32_t GetLCGEngineState(); - -/** - * @brief Advance the global RandomNumberEngine state by the specified number of rounds - * - * Only used to maintain vanilla compatibility until logic requiring reproducible random number generation is isolated. - * @param count How many values to discard - */ -void DiscardRandomValues(unsigned count); - -/** - * @brief Advances the global RandomNumberEngine state and returns the new value - */ -uint32_t GenerateRandomNumber(); - -/** - * @brief Generates a random non-negative integer (most of the time) using the vanilla RNG - * - * This advances the engine state then interprets the new engine state as a signed value and calls std::abs to try - * discard the high bit of the result. This usually returns a positive number but may very rarely return -2^31. - * - * This function is only used when the base game wants to store the seed used to generate an item or level, however - * as the returned value is transformed about 50% of values do not reflect the actual engine state. It would be more - * appropriate to use GetLCGEngineState() in these cases but that may break compatibility with the base game. - * - * @return A random number in the range [0,2^31) or -2^31 - */ -[[nodiscard]] int32_t AdvanceRndSeed(); - -/** - * @brief Generates a random integer less than the given limit using the vanilla RNG - * - * If v is not a positive number this function returns 0 without calling the RNG. - * - * Limits between 32768 and 65534 should be avoided as a bug in vanilla means this function always returns a value - * less than 32768 for limits in that range. - * - * This can very rarely return a negative value in the range (-v, -1] due to the bug in AdvanceRndSeed() - * - * @see AdvanceRndSeed() - * @param v The upper limit for the return value - * @return A random number in the range [0, v) or rarely a negative value in (-v, -1] - */ -int32_t GenerateRnd(int32_t v); - -/** - * @brief Generates a random boolean value using the vanilla RNG - * - * This function returns true 1 in `frequency` of the time, otherwise false. For example the default frequency of 2 - * represents a 50/50 chance. - * - * @param frequency odds of returning a true value - * @return A random boolean value - */ -bool FlipCoin(unsigned frequency = 2); - -/** - * @brief Picks one of the elements in the list randomly. - * - * @param values The values to pick from - * @return A random value from the 'values' list. - */ -template -const T PickRandomlyAmong(const std::initializer_list &values) -{ - const auto index { std::max(GenerateRnd(static_cast(values.size())), 0) }; - - return *(values.begin() + index); -} - -/** - * @brief Generates a random non-negative integer - * - * Effectively the same as GenerateRnd but will never return a negative value - * @param v upper limit for the return value - * @return a value between 0 and v-1 inclusive, i.e. the range [0, v) - */ -inline int32_t RandomIntLessThan(int32_t v) -{ - return std::max(GenerateRnd(v), 0); -} - -/** - * @brief Randomly chooses a value somewhere within the given range - * @param min lower limit, minimum possible value - * @param max upper limit, either the maximum possible value for a closed range (the default behaviour) or one greater than the maximum value for a half-open range - * @param halfOpen whether to use the limits as a half-open range or not - * @return a randomly selected integer - */ -inline int32_t RandomIntBetween(int32_t min, int32_t max, bool halfOpen = false) -{ - return RandomIntLessThan(max - min + (halfOpen ? 0 : 1)) + min; -} - -} // namespace devilution +/** + * @file random.hpp + * + * Contains convenience functions for random number generation + * + * This includes specific engine/distribution functions for logic that needs to be compatible with the base game. + */ +#pragma once + +#include +#include +#include +#include + +namespace devilution { + +class DiabloGenerator { +private: + /** Borland C/C++ psuedo-random number generator needed for vanilla compatibility */ + std::linear_congruential_engine lcg; + +public: + /** + * @brief Set the state of the RandomNumberEngine used by the base game to the specific seed + * @param seed New engine state + */ + DiabloGenerator(uint32_t seed) + { + lcg.seed(seed); + } + + /** + * @brief Advance the global RandomNumberEngine state by the specified number of rounds + * + * Only used to maintain vanilla compatibility until logic requiring reproducible random number generation is isolated. + * @param count How many values to discard + */ + void discardRandomValues(unsigned count) + { + lcg.discard(count); + } + + /** + * @brief Generates a random non-negative integer (most of the time) using the vanilla RNG + * + * This advances the engine state then interprets the new engine state as a signed value and calls std::abs to try + * discard the high bit of the result. This usually returns a positive number but may very rarely return -2^31. + * + * This function is only used when the base game wants to store the seed used to generate an item or level, however + * as the returned value is transformed about 50% of values do not reflect the actual engine state. It would be more + * appropriate to use GetLCGEngineState() in these cases but that may break compatibility with the base game. + * + * @return A random number in the range [0,2^31) or -2^31 + */ + int32_t advanceRndSeed() + { + const int32_t seed = static_cast(lcg()); + // since abs(INT_MIN) is undefined behavior, handle this value specially + return seed == std::numeric_limits::min() ? std::numeric_limits::min() : std::abs(seed); + } + + /** + * @brief Generates a random integer less than the given limit using the vanilla RNG + * + * If v is not a positive number this function returns 0 without calling the RNG. + * + * Limits between 32768 and 65534 should be avoided as a bug in vanilla means this function always returns a value + * less than 32768 for limits in that range. + * + * This can very rarely return a negative value in the range (-v, -1] due to the bug in AdvanceRndSeed() + * + * @see AdvanceRndSeed() + * @param v The upper limit for the return value + * @return A random number in the range [0, v) or rarely a negative value in (-v, -1] + */ + int32_t generateRnd(int32_t v) + { + if (v <= 0) + return 0; + if (v <= 0x7FFF) // use the high bits to correct for LCG bias + return (advanceRndSeed() >> 16) % v; + return advanceRndSeed() % v; + } + + /** + * @brief Generates a random boolean value using the vanilla RNG + * + * This function returns true 1 in `frequency` of the time, otherwise false. For example the default frequency of 2 + * represents a 50/50 chance. + * + * @param frequency odds of returning a true value + * @return A random boolean value + */ + bool flipCoin(unsigned frequency) + { + // Casting here because GenerateRnd takes a signed argument when it should take and yield unsigned. + return generateRnd(static_cast(frequency)) == 0; + } + + /** + * @brief Picks one of the elements in the list randomly. + * + * @param values The values to pick from + * @return A random value from the 'values' list. + */ + template + const T pickRandomlyAmong(const std::initializer_list &values) + { + const auto index { std::max(generateRnd(static_cast(values.size())), 0) }; + + return *(values.begin() + index); + } + + /** + * @brief Generates a random non-negative integer + * + * Effectively the same as GenerateRnd but will never return a negative value + * @param v upper limit for the return value + * @return a value between 0 and v-1 inclusive, i.e. the range [0, v) + */ + inline int32_t randomIntLessThan(int32_t v) + { + return std::max(generateRnd(v), 0); + } + + /** + * @brief Randomly chooses a value somewhere within the given range + * @param min lower limit, minimum possible value + * @param max upper limit, either the maximum possible value for a closed range (the default behaviour) or one greater than the maximum value for a half-open range + * @param halfOpen whether to use the limits as a half-open range or not + * @return a randomly selected integer + */ + inline int32_t randomIntBetween(int32_t min, int32_t max, bool halfOpen = false) + { + return randomIntLessThan(max - min + (halfOpen ? 0 : 1)) + min; + } +}; + +// Based on fmix32 implementation from MurmurHash3 created by Austin Appleby in 2008 +// https://github.com/aappleby/smhasher/blob/61a0530f28277f2e850bfc39600ce61d02b518de/src/MurmurHash3.cpp#L68 +// and adapted from https://prng.di.unimi.it/splitmix64.c written in 2015 by Sebastiano Vigna +// +// See also: +// Guy L. Steele, Doug Lea, and Christine H. Flood. 2014. +// Fast splittable pseudorandom number generators. SIGPLAN Not. 49, 10 (October 2014), 453–472. +// https://doi.org/10.1145/2714064.2660195 +class SplitMix32 { + uint32_t state; + +public: + SplitMix32(uint32_t state) + : state(state) + { + } + + uint32_t next() + { + uint32_t z = (state += 0x9e3779b9); + z = (z ^ (z >> 16)) * 0x85ebca6b; + z = (z ^ (z >> 13)) * 0xc2b2ae35; + return z ^ (z >> 16); + } + + void generate(uint32_t *begin, const uint32_t *end) + { + while (begin != end) { + *begin = next(); + ++begin; + } + } +}; + +// Adapted from https://prng.di.unimi.it/splitmix64.c written in 2015 by Sebastiano Vigna +// +// See also: +// Guy L. Steele, Doug Lea, and Christine H. Flood. 2014. +// Fast splittable pseudorandom number generators. SIGPLAN Not. 49, 10 (October 2014), 453–472. +// https://doi.org/10.1145/2714064.2660195 +class SplitMix64 { + uint64_t state; + +public: + SplitMix64(uint64_t state) + : state(state) + { + } + + uint64_t next() + { + uint64_t z = (state += 0x9e3779b97f4a7c15); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + return z ^ (z >> 31); + } + + void generate(uint64_t *begin, const uint64_t *end) + { + while (begin != end) { + *begin = next(); + ++begin; + } + } +}; + +/** Adapted from https://prng.di.unimi.it/xoshiro128plusplus.c written in 2019 by David Blackman and Sebastiano Vigna */ +class xoshiro128plusplus { +public: + typedef uint32_t state[4]; + + xoshiro128plusplus() { seed(); } + xoshiro128plusplus(const state &s) { copy(this->s, s); } + xoshiro128plusplus(uint64_t initialSeed) { seed(initialSeed); } + xoshiro128plusplus(uint32_t initialSeed) { seed(initialSeed); } + + uint32_t next(); + + /* This is the jump function for the generator. It is equivalent + to 2^64 calls to next(); it can be used to generate 2^64 + non-overlapping subsequences for parallel computations. */ + void jump() + { + static constexpr uint32_t JUMP[] = { 0x8764000b, 0xf542d2d3, 0x6fa035c3, 0x77f2db5b }; + + uint32_t s0 = 0; + uint32_t s1 = 0; + uint32_t s2 = 0; + uint32_t s3 = 0; + for (const uint32_t entry : JUMP) + for (int b = 0; b < 32; b++) { + if (entry & UINT32_C(1) << b) { + s0 ^= s[0]; + s1 ^= s[1]; + s2 ^= s[2]; + s3 ^= s[3]; + } + next(); + } + + s[0] = s0; + s[1] = s1; + s[2] = s2; + s[3] = s3; + } + + void save(state &s) const + { + copy(s, this->s); + } + +private: + state s; + + void seed(uint64_t value) + { + uint64_t seeds[2]; + SplitMix64 seedSequence { value }; + seedSequence.generate(seeds, seeds + 2); + + s[0] = static_cast(seeds[0] >> 32); + s[1] = static_cast(seeds[0]); + s[2] = static_cast(seeds[1] >> 32); + s[3] = static_cast(seeds[1]); + } + + void seed(uint32_t value) + { + SplitMix32 seedSequence { value }; + seedSequence.generate(s, s + 4); + } + + void seed() + { + seed(timeSeed()); + +#if !(defined(WINVER) && WINVER <= 0x0500 && (!defined(_WIN32_WINNT) || _WIN32_WINNT == 0)) + static std::random_device rd; + std::uniform_int_distribution dist; + for (uint32_t &cell : s) + cell ^= dist(rd); +#endif + } + + static uint64_t timeSeed(); + static void copy(state &dst, const state &src); +}; + +/** + * @brief Returns a copy of the global seed generator and fast-forwards the global seed generator to avoid collisions + */ +xoshiro128plusplus ReserveSeedSequence(); + +/** + * @brief Advances the global seed generator state and returns the new value + */ +uint32_t GenerateSeed(); + +/** + * @brief Set the state of the RandomNumberEngine used by the base game to the specific seed + * @param seed New engine state + */ +void SetRndSeed(uint32_t seed); + +/** + * @brief Returns the current state of the RandomNumberEngine used by the base game + * + * This is only exposed to allow for debugging vanilla code and testing. Using this engine for new code is discouraged + * due to the poor randomness and bugs in the implementation that need to be retained for compatibility. + * + * @return The current engine state + */ +uint32_t GetLCGEngineState(); + +/** + * @brief Advance the global RandomNumberEngine state by the specified number of rounds + * + * Only used to maintain vanilla compatibility until logic requiring reproducible random number generation is isolated. + * @param count How many values to discard + */ +void DiscardRandomValues(unsigned count); + +/** + * @brief Advances the global RandomNumberEngine state and returns the new value + */ +uint32_t GenerateRandomNumber(); + +/** + * @brief Generates a random non-negative integer (most of the time) using the vanilla RNG + * + * This advances the engine state then interprets the new engine state as a signed value and calls std::abs to try + * discard the high bit of the result. This usually returns a positive number but may very rarely return -2^31. + * + * This function is only used when the base game wants to store the seed used to generate an item or level, however + * as the returned value is transformed about 50% of values do not reflect the actual engine state. It would be more + * appropriate to use GetLCGEngineState() in these cases but that may break compatibility with the base game. + * + * @return A random number in the range [0,2^31) or -2^31 + */ +[[nodiscard]] int32_t AdvanceRndSeed(); + +/** + * @brief Generates a random integer less than the given limit using the vanilla RNG + * + * If v is not a positive number this function returns 0 without calling the RNG. + * + * Limits between 32768 and 65534 should be avoided as a bug in vanilla means this function always returns a value + * less than 32768 for limits in that range. + * + * This can very rarely return a negative value in the range (-v, -1] due to the bug in AdvanceRndSeed() + * + * @see AdvanceRndSeed() + * @param v The upper limit for the return value + * @return A random number in the range [0, v) or rarely a negative value in (-v, -1] + */ +int32_t GenerateRnd(int32_t v); + +/** + * @brief Generates a random boolean value using the vanilla RNG + * + * This function returns true 1 in `frequency` of the time, otherwise false. For example the default frequency of 2 + * represents a 50/50 chance. + * + * @param frequency odds of returning a true value + * @return A random boolean value + */ +bool FlipCoin(unsigned frequency = 2); + +/** + * @brief Picks one of the elements in the list randomly. + * + * @param values The values to pick from + * @return A random value from the 'values' list. + */ +template +const T PickRandomlyAmong(const std::initializer_list &values) +{ + const auto index { std::max(GenerateRnd(static_cast(values.size())), 0) }; + + return *(values.begin() + index); +} + +/** + * @brief Generates a random non-negative integer + * + * Effectively the same as GenerateRnd but will never return a negative value + * @param v upper limit for the return value + * @return a value between 0 and v-1 inclusive, i.e. the range [0, v) + */ +inline int32_t RandomIntLessThan(int32_t v) +{ + return std::max(GenerateRnd(v), 0); +} + +/** + * @brief Randomly chooses a value somewhere within the given range + * @param min lower limit, minimum possible value + * @param max upper limit, either the maximum possible value for a closed range (the default behaviour) or one greater than the maximum value for a half-open range + * @param halfOpen whether to use the limits as a half-open range or not + * @return a randomly selected integer + */ +inline int32_t RandomIntBetween(int32_t min, int32_t max, bool halfOpen = false) +{ + return RandomIntLessThan(max - min + (halfOpen ? 0 : 1)) + min; +} + +} // namespace devilution diff --git a/Source/engine/render/scrollrt.cpp b/Source/engine/render/scrollrt.cpp index 3b1bbeb9f..fbbcd5f1b 100644 --- a/Source/engine/render/scrollrt.cpp +++ b/Source/engine/render/scrollrt.cpp @@ -928,10 +928,8 @@ void DrawFloor(const Surface &out, const Lightmap &lightmap, Point tilePosition, { for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++, tilePosition += Direction::East, targetBufferPosition.x += TILE_WIDTH) { - if (!InDungeonBounds(tilePosition)) { - world_draw_black_tile(out, targetBufferPosition.x, targetBufferPosition.y); + if (!InDungeonBounds(tilePosition)) continue; - } if (IsFloor(tilePosition)) { DrawFloorTile(out, lightmap, tilePosition, targetBufferPosition); } @@ -1018,6 +1016,77 @@ void DrawTileContent(const Surface &out, const Lightmap &lightmap, Point tilePos } } +void DrawDirtTile(const Surface &out, const Lightmap &lightmap, Point tilePosition, Point targetBufferPosition) +{ + // This should be the *top-left* of the 2×2 dirt pattern in the actual dungeon. + // You might need to tweak these to where your dirt patch actually lives. + constexpr Point base { 0, 0 }; + + // Decide which of the 4 tiles of the 2×2 block to use, + // based on where this OOB tile is in the world grid. + const int ox = (tilePosition.x & 1); // 0 or 1 + const int oy = (tilePosition.y & 1); // 0 or 1 + + Point sample { + base.x + ox, + base.y + oy, + }; + + // Safety: clamp in case tilePosition is wildly outside and base+offset ever escapes + sample.x = std::clamp(sample.x, 0, MAXDUNX - 1); + sample.y = std::clamp(sample.y, 0, MAXDUNY - 1); + + if (!InDungeonBounds(sample) || dPiece[sample.x][sample.y] == 0) { + // Failsafe: if our sample somehow isn't valid, fall back to black + world_draw_black_tile(out, targetBufferPosition.x, targetBufferPosition.y); + return; + } + + const int lightTableIndex = dLight[sample.x][sample.y]; + + // Let the normal dungeon tile renderer compose the full tile + DrawCell(out, lightmap, sample, targetBufferPosition, lightTableIndex); +} + +/** + * @brief Render a row of tiles + * @param out Buffer to render to + * @param lightmap Per-pixel light buffer + * @param tilePosition dPiece coordinates + * @param targetBufferPosition Target buffer coordinates + * @param rows Number of rows + * @param columns Tile in a row + */ +void DrawOOB(const Surface &out, const Lightmap &lightmap, Point tilePosition, Point targetBufferPosition, int rows, int columns) +{ + for (int i = 0; i < rows + 5; i++) { // 5 extra rows needed to make sure everything gets rendered at the bottom half of the screen + for (int j = 0; j < columns; j++, tilePosition += Direction::East, targetBufferPosition.x += TILE_WIDTH) { + if (!InDungeonBounds(tilePosition)) { + if (leveltype == DTYPE_TOWN) { + world_draw_black_tile(out, targetBufferPosition.x, targetBufferPosition.y); + } else { + DrawDirtTile(out, lightmap, tilePosition, targetBufferPosition); + } + } + } + // Return to start of row + tilePosition += Displacement(Direction::West) * columns; + targetBufferPosition.x -= columns * TILE_WIDTH; + + // Jump to next row + targetBufferPosition.y += TILE_HEIGHT / 2; + if ((i & 1) != 0) { + tilePosition.x++; + columns--; + targetBufferPosition.x += TILE_WIDTH / 2; + } else { + tilePosition.y++; + columns++; + targetBufferPosition.x -= TILE_WIDTH / 2; + } + } +} + /** * @brief Scale up the top left part of the buffer 2x. */ @@ -1183,6 +1252,7 @@ void DrawGame(const Surface &fullOut, Point position, Displacement offset) DrawFloor(out, lightmap, position, Point {} + offset, rows, columns); DrawTileContent(out, lightmap, position, Point {} + offset, rows, columns); + DrawOOB(out, lightmap, position, Point {} + offset, rows, columns); if (*GetOptions().Graphics.zoom) { Zoom(fullOut.subregionY(0, gnViewportHeight)); @@ -1746,7 +1816,7 @@ void DrawAndBlit() const Rectangle &mainPanel = GetMainPanel(); - if (gnScreenWidth > mainPanel.size.width || IsRedrawEverything() || *GetOptions().Gameplay.enableFloatingNumbers != FloatingNumbers::Off) { + if (gnScreenWidth > mainPanel.size.width || IsRedrawEverything()) { drawHealth = true; drawMana = true; drawControlButtons = true; diff --git a/Source/engine/render/text_render.cpp b/Source/engine/render/text_render.cpp index c2abebd31..c41220a2d 100644 --- a/Source/engine/render/text_render.cpp +++ b/Source/engine/render/text_render.cpp @@ -437,6 +437,89 @@ int GetLineStartX(UiFlags flags, const Rectangle &rect, int lineWidth) return rect.position.x; } +void DrawLine( + const Surface &out, + std::string_view text, + Point characterPosition, + Rectangle rect, + UiFlags flags, + int curSpacing, + GameFontTables size, + text_color color, + bool outline, + const TextRenderOptions &opts, + size_t lineStartPos, + int totalWidth) +{ + CurrentFont currentFont; + + std::string_view lineCopy = text; + + size_t currentPos = 0; + + size_t cpLen; + + const auto maybeDrawCursor = [&]() { + const auto byteIndex = static_cast(lineStartPos + currentPos); + Point position = characterPosition; + if (opts.cursorPosition == byteIndex) { + if (GetAnimationFrame(2, 500) != 0 || opts.cursorStatic) { + FontStack baseFont = LoadFont(size, color, 0); + if (baseFont.has_value()) { + DrawFont(out, position, baseFont.glyph('|'), color, outline); + } + } + if (opts.renderedCursorPositionOut != nullptr) { + *opts.renderedCursorPositionOut = position; + } + } + }; + + // Start from the beginning of the line + characterPosition.x = GetLineStartX(flags, rect, totalWidth); + + while (!lineCopy.empty()) { + char32_t c = DecodeFirstUtf8CodePoint(lineCopy, &cpLen); + if (c == Utf8DecodeError) break; + if (c == ZWSP) { + currentPos += cpLen; + lineCopy.remove_prefix(cpLen); + continue; + } + + if (!currentFont.load(size, color, c)) { + c = U'?'; + if (!currentFont.load(size, color, c)) { + app_fatal("Missing fonts"); + } + } + const uint8_t frame = c & 0xFF; + + const ClxSprite glyph = currentFont.glyph(frame); + const int charWidth = glyph.width(); + + const auto byteIndex = static_cast(lineStartPos + currentPos); + + // Draw highlight + if (byteIndex >= opts.highlightRange.begin && byteIndex < opts.highlightRange.end) { + const bool lastInRange = static_cast(byteIndex + cpLen) == opts.highlightRange.end; + FillRect(out, characterPosition.x, characterPosition.y, + glyph.width() + (lastInRange ? 0 : curSpacing), glyph.height(), + opts.highlightColor); + } + + DrawFont(out, characterPosition, glyph, color, outline); + maybeDrawCursor(); + + // Move to the next position + characterPosition.x += charWidth + curSpacing; + currentPos += cpLen; + lineCopy.remove_prefix(cpLen); + } + assert(currentPos == text.size()); + maybeDrawCursor(); +} + uint32_t DoDrawString(const Surface &out, std::string_view text, Rectangle rect, Point &characterPosition, int lineWidth, int charactersInLine, int rightMargin, int bottomMargin, GameFontTables size, text_color color, bool outline, TextRenderOptions &opts) @@ -455,19 +538,26 @@ uint32_t DoDrawString(const Surface &out, std::string_view text, Rectangle rect, std::string_view remaining = text; size_t cpLen; - const auto maybeDrawCursor = [&]() { - if (opts.cursorPosition == static_cast(text.size() - remaining.size())) { - Point position = characterPosition; - MaybeWrap(position, 2, rightMargin, position.x, opts.lineHeight); - if (GetAnimationFrame(2, 500) != 0) { - FontStack baseFont = LoadFont(size, color, 0); - if (baseFont.has_value()) { - DrawFont(out, position, baseFont.glyph('|'), color, outline); - } - } - if (opts.renderedCursorPositionOut != nullptr) { - *opts.renderedCursorPositionOut = position; - } + // Track line boundaries + size_t lineStartPos = 0; + size_t lineEndPos = 0; + + const auto drawLine = [&]() { + std::string_view lineText = text.substr(lineStartPos, lineEndPos - lineStartPos); + if (!lineText.empty()) { + DrawLine( + out, + lineText, + characterPosition, + rect, + opts.flags, + curSpacing, + size, + color, + outline, + opts, + lineStartPos, + lineWidth); } }; @@ -487,8 +577,10 @@ uint32_t DoDrawString(const Surface &out, std::string_view text, Rectangle rect, const uint8_t frame = next & 0xFF; const uint16_t width = currentFont.glyph(frame).width(); if (next == U'\n' || characterPosition.x + width > rightMargin) { - if (next == '\n') - maybeDrawCursor(); + lineEndPos = text.size() - remaining.size(); + + drawLine(); + const int nextLineY = characterPosition.y + opts.lineHeight; if (nextLineY >= bottomMargin) break; @@ -506,26 +598,26 @@ uint32_t DoDrawString(const Surface &out, std::string_view text, Rectangle rect, } characterPosition.x = GetLineStartX(opts.flags, rect, lineWidth); + // Start a new line + lineStartPos = next == U'\n' ? (text.size() - remaining.size() + cpLen) : (text.size() - remaining.size()); + lineEndPos = lineStartPos; + if (next == U'\n') continue; } - const ClxSprite glyph = currentFont.glyph(frame); - const auto byteIndex = static_cast(text.size() - remaining.size()); - - // Draw highlight - if (byteIndex >= opts.highlightRange.begin && byteIndex < opts.highlightRange.end) { - const bool lastInRange = static_cast(byteIndex + cpLen) == opts.highlightRange.end; - FillRect(out, characterPosition.x, characterPosition.y, - glyph.width() + (lastInRange ? 0 : curSpacing), glyph.height(), - opts.highlightColor); - } + // Update end position as we add characters + lineEndPos = text.size() - remaining.size() + cpLen; - DrawFont(out, characterPosition, glyph, color, outline); - maybeDrawCursor(); + // Update position for the next character characterPosition.x += width + curSpacing; } - maybeDrawCursor(); + + // Draw any remaining characters in the last line + if (lineStartPos < lineEndPos) { + drawLine(); + } + return static_cast(remaining.data() - text.data()); } diff --git a/Source/engine/render/text_render.hpp b/Source/engine/render/text_render.hpp index 957d137d8..47ccf7f64 100644 --- a/Source/engine/render/text_render.hpp +++ b/Source/engine/render/text_render.hpp @@ -155,6 +155,8 @@ struct TextRenderOptions { /** @brief If a cursor is rendered, the surface coordinates are saved here. */ std::optional *renderedCursorPositionOut = nullptr; + + bool cursorStatic = false; }; /** diff --git a/Source/engine/sound.cpp b/Source/engine/sound.cpp index 6879c02e8..d5dcd20ed 100644 --- a/Source/engine/sound.cpp +++ b/Source/engine/sound.cpp @@ -348,8 +348,6 @@ void music_start(_music_id nTrack) } music.SetVolume(*GetOptions().Audio.musicVolume, VOLUME_MIN, VOLUME_MAX); - if (!diablo_is_focused()) - music_mute(); if (!music.Play(/*numIterations=*/0)) { LogError(LogCategory::Audio, "Aulib::Stream::play (from music_start): {}", SDL_GetError()); music_stop(); diff --git a/Source/gmenu.cpp b/Source/gmenu.cpp index 7c7e76afd..6e1c5230d 100644 --- a/Source/gmenu.cpp +++ b/Source/gmenu.cpp @@ -20,7 +20,7 @@ #include "DiabloUI/ui_flags.hpp" #include "appfat.h" -#include "control.h" +#include "control/control.hpp" #include "controls/axis_direction.h" #include "controls/controller_motion.h" #include "engine/clx_sprite.hpp" diff --git a/Source/interfac.cpp b/Source/interfac.cpp index 2a8efb408..0ca8a58b2 100644 --- a/Source/interfac.cpp +++ b/Source/interfac.cpp @@ -22,7 +22,7 @@ #include -#include "control.h" +#include "control/control.hpp" #include "controls/input.h" #include "engine/clx_sprite.hpp" #include "engine/dx.h" diff --git a/Source/items.cpp b/Source/items.cpp index 5a5876d07..2f6ce462c 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -28,7 +28,7 @@ #include #include "DiabloUI/ui_flags.hpp" -#include "control.h" +#include "control/control.hpp" #include "controls/control_mode.hpp" #include "controls/controller_buttons.h" #include "cursor.h" @@ -52,7 +52,6 @@ #include "headless_mode.hpp" #include "inv.h" #include "inv_iterators.hpp" -#include "itemdat.h" #include "items/validation.h" #include "levels/gendung.h" #include "levels/gendung_defs.hpp" @@ -60,25 +59,26 @@ #include "levels/town.h" #include "lighting.h" #include "minitext.h" -#include "monstdat.h" #include "monster.h" #include "msg.h" #include "multi.h" -#include "objdat.h" #include "objects.h" #include "options.h" #include "pack.h" #include "panels/info_box.hpp" #include "panels/ui_panels.hpp" #include "player.h" -#include "playerdat.hpp" #include "qol/stash.h" #include "quests.h" #include "sound_effect_enums.h" -#include "spelldat.h" #include "spells.h" #include "stores.h" -#include "textdat.h" +#include "tables/itemdat.h" +#include "tables/monstdat.h" +#include "tables/objdat.h" +#include "tables/playerdat.hpp" +#include "tables/spelldat.h" +#include "tables/textdat.h" #include "utils/enum_traits.h" #include "utils/format_int.hpp" #include "utils/is_of.hpp" diff --git a/Source/items.h b/Source/items.h index a208e75d2..9c855a405 100644 --- a/Source/items.h +++ b/Source/items.h @@ -13,9 +13,9 @@ #include "engine/animationinfo.h" #include "engine/point.hpp" #include "engine/surface.hpp" -#include "itemdat.h" #include "levels/dun_tile.hpp" #include "monster.h" +#include "tables/itemdat.h" #include "utils/is_of.hpp" #include "utils/string_or_view.hpp" diff --git a/Source/items/validation.cpp b/Source/items/validation.cpp index a6d4e6dbd..31ca442ae 100644 --- a/Source/items/validation.cpp +++ b/Source/items/validation.cpp @@ -9,10 +9,10 @@ #include #include "items.h" -#include "monstdat.h" #include "msg.h" #include "player.h" #include "spells.h" +#include "tables/monstdat.h" #include "utils/endian_swap.hpp" #include "utils/is_of.hpp" diff --git a/Source/levels/drlg_l3.cpp b/Source/levels/drlg_l3.cpp index 3dc011d99..f805c5b30 100644 --- a/Source/levels/drlg_l3.cpp +++ b/Source/levels/drlg_l3.cpp @@ -10,10 +10,10 @@ #include "levels/setmaps.h" #include "lighting.h" #include "monster.h" -#include "objdat.h" #include "objects.h" #include "player.h" #include "quests.h" +#include "tables/objdat.h" #include "utils/is_of.hpp" namespace devilution { diff --git a/Source/levels/drlg_l4.cpp b/Source/levels/drlg_l4.cpp index d351df4a7..39637e17e 100644 --- a/Source/levels/drlg_l4.cpp +++ b/Source/levels/drlg_l4.cpp @@ -12,8 +12,8 @@ #include "levels/gendung.h" #include "monster.h" #include "multi.h" -#include "objdat.h" #include "player.h" +#include "tables/objdat.h" #include "utils/is_of.hpp" namespace devilution { diff --git a/Source/levels/setmaps.cpp b/Source/levels/setmaps.cpp index 6108682e8..3858632e4 100644 --- a/Source/levels/setmaps.cpp +++ b/Source/levels/setmaps.cpp @@ -14,9 +14,9 @@ #include "levels/gendung.h" #include "levels/trigs.h" #include "msg.h" -#include "objdat.h" #include "objects.h" #include "quests.h" +#include "tables/objdat.h" #include "utils/language.h" namespace devilution { diff --git a/Source/levels/themes.h b/Source/levels/themes.h index 53bf20512..dd8fe597c 100644 --- a/Source/levels/themes.h +++ b/Source/levels/themes.h @@ -8,7 +8,7 @@ #include #include "levels/gendung.h" -#include "objdat.h" +#include "tables/objdat.h" namespace devilution { diff --git a/Source/levels/trigs.cpp b/Source/levels/trigs.cpp index 141564231..c3b88ae25 100644 --- a/Source/levels/trigs.cpp +++ b/Source/levels/trigs.cpp @@ -10,7 +10,7 @@ #include -#include "control.h" +#include "control/control.hpp" #include "controls/control_mode.hpp" #include "controls/plrctrls.h" #include "cursor.h" diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 75959092e..010658713 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -17,7 +17,7 @@ #include "automap.h" #include "codec.h" -#include "control.h" +#include "control/control.hpp" #include "cursor.h" #include "dead.h" #include "doom.h" @@ -33,10 +33,10 @@ #include "monsters/validation.hpp" #include "mpq/mpq_common.hpp" #include "pfile.h" -#include "playerdat.hpp" #include "plrmsg.h" #include "qol/stash.h" #include "stores.h" +#include "tables/playerdat.hpp" #include "utils/algorithm/container.hpp" #include "utils/endian_read.hpp" #include "utils/endian_swap.hpp" diff --git a/Source/lua/lua_event.hpp b/Source/lua/lua_event.hpp new file mode 100644 index 000000000..b7c016107 --- /dev/null +++ b/Source/lua/lua_event.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace devilution { + +/** + * @brief Triggers a Lua event by name. + * This is a minimal header for code that only needs to trigger events. + */ +void LuaEvent(std::string_view name); +void LuaEvent(std::string_view name, std::string_view arg); + +} // namespace devilution diff --git a/Source/lua/lua_global.cpp b/Source/lua/lua_global.cpp index 97043a31d..bd8358aa8 100644 --- a/Source/lua/lua_global.cpp +++ b/Source/lua/lua_global.cpp @@ -13,6 +13,7 @@ #include "effects.h" #include "engine/assets.hpp" #include "lua/modules/audio.hpp" +#include "lua/modules/floatingnumbers.hpp" #include "lua/modules/hellfire.hpp" #include "lua/modules/i18n.hpp" #include "lua/modules/items.hpp" @@ -20,8 +21,11 @@ #include "lua/modules/monsters.hpp" #include "lua/modules/player.hpp" #include "lua/modules/render.hpp" +#include "lua/modules/system.hpp" #include "lua/modules/towners.hpp" +#include "monster.h" #include "options.h" +#include "player.h" #include "plrmsg.h" #include "utils/console.h" #include "utils/log.hpp" @@ -201,6 +205,9 @@ sol::environment CreateLuaSandbox() sandbox["require"] = lua["requireGen"](sandbox, CurrentLuaState->commonPackages, LuaLoadScriptFromAssets); + // Expose commonly used enums globally for mods + sandbox["SfxID"] = lua["SfxID"]; + return sandbox; } @@ -283,6 +290,8 @@ void LuaInitialize() "devilutionx.render", LuaRenderModule(lua), "devilutionx.towners", LuaTownersModule(lua), "devilutionx.hellfire", LuaHellfireModule(lua), + "devilutionx.system", LuaSystemModule(lua), + "devilutionx.floatingnumbers", LuaFloatingNumbersModule(lua), "devilutionx.message", [](std::string_view text) { EventPlrMsg(text, UiFlags::ColorRed); }, // This package is loaded without a sandbox: "inspect", RunScript(/*env=*/std::nullopt, "inspect", /*optional=*/false)); @@ -305,7 +314,43 @@ void LuaShutdown() CurrentLuaState = std::nullopt; } +template +void CallLuaEvent(std::string_view name, Args &&...args) +{ + if (!CurrentLuaState.has_value()) { + return; + } + + const auto trigger = CurrentLuaState->events.traverse_get>(name, "trigger"); + if (!trigger.has_value() || !trigger->is()) { + LogError("events.{}.trigger is not a function", name); + return; + } + const sol::protected_function fn = trigger->as(); + SafeCallResult(fn(std::forward(args)...), /*optional=*/true); +} + void LuaEvent(std::string_view name) +{ + CallLuaEvent(name); +} + +void LuaEvent(std::string_view name, const Player *player, int arg1, int arg2) +{ + CallLuaEvent(name, player, arg1, arg2); +} + +void LuaEvent(std::string_view name, const Monster *monster, int arg1, int arg2) +{ + CallLuaEvent(name, monster, arg1, arg2); +} + +void LuaEvent(std::string_view name, const Player *player, uint32_t arg1) +{ + CallLuaEvent(name, player, arg1); +} + +void LuaEvent(std::string_view name, std::string_view arg) { if (!CurrentLuaState.has_value()) { return; @@ -317,7 +362,7 @@ void LuaEvent(std::string_view name) return; } const sol::protected_function fn = trigger->as(); - SafeCallResult(fn(), /*optional=*/true); + SafeCallResult(fn(arg), /*optional=*/true); } sol::state &GetLuaState() diff --git a/Source/lua/lua_global.hpp b/Source/lua/lua_global.hpp index 80fbb1211..24f70901e 100644 --- a/Source/lua/lua_global.hpp +++ b/Source/lua/lua_global.hpp @@ -8,10 +8,17 @@ namespace devilution { +struct Player; +struct Monster; + void LuaInitialize(); void LuaReloadActiveMods(); void LuaShutdown(); void LuaEvent(std::string_view name); +void LuaEvent(std::string_view name, std::string_view arg); +void LuaEvent(std::string_view name, const Player *player, int arg1, int arg2); +void LuaEvent(std::string_view name, const Monster *monster, int arg1, int arg2); +void LuaEvent(std::string_view name, const Player *player, uint32_t arg1); sol::state &GetLuaState(); sol::environment CreateLuaSandbox(); sol::object SafeCallResult(sol::protected_function_result result, bool optional); diff --git a/Source/lua/modules/audio.cpp b/Source/lua/modules/audio.cpp index f8ab0a7e2..73d5d5faa 100644 --- a/Source/lua/modules/audio.cpp +++ b/Source/lua/modules/audio.cpp @@ -1,9 +1,11 @@ #include "lua/modules/audio.hpp" +#include #include #include "effects.h" #include "lua/metadoc.hpp" +#include "sound_effect_enums.h" namespace devilution { @@ -14,10 +16,27 @@ bool IsValidSfx(int16_t psfx) return psfx >= 0 && psfx <= static_cast(SfxID::LAST); } +void RegisterSfxIDEnum(sol::state_view &lua) +{ + constexpr auto enumValues = magic_enum::enum_values(); + sol::table enumTable = lua.create_table(); + for (const auto enumValue : enumValues) { + const std::string_view name = magic_enum::enum_name(enumValue); + if (!name.empty() && name != "LAST" && name != "None") { + enumTable[name] = static_cast(enumValue); + } + } + // Add LAST and None explicitly + enumTable["LAST"] = static_cast(SfxID::LAST); + enumTable["None"] = static_cast(SfxID::None); + lua["SfxID"] = enumTable; +} + } // namespace sol::table LuaAudioModule(sol::state_view &lua) { + RegisterSfxIDEnum(lua); sol::table table = lua.create_table(); LuaSetDocFn(table, "playSfx", "(id: number)", @@ -25,6 +44,8 @@ sol::table LuaAudioModule(sol::state_view &lua) LuaSetDocFn(table, "playSfxLoc", "(id: number, x: number, y: number)", [](int16_t psfx, int x, int y) { if (IsValidSfx(psfx)) PlaySfxLoc(static_cast(psfx), { x, y }); }); + // Expose SfxID enum through the module table + table["SfxID"] = lua["SfxID"]; return table; } diff --git a/Source/lua/modules/dev/monsters.cpp b/Source/lua/modules/dev/monsters.cpp index c1f8822fa..b9843bcc0 100644 --- a/Source/lua/modules/dev/monsters.cpp +++ b/Source/lua/modules/dev/monsters.cpp @@ -11,9 +11,9 @@ #include "levels/tile_properties.hpp" #include "lighting.h" #include "lua/metadoc.hpp" -#include "monstdat.h" #include "monster.h" #include "player.h" +#include "tables/monstdat.h" #include "utils/str_case.hpp" #include "utils/str_cat.hpp" diff --git a/Source/lua/modules/dev/player/spells.cpp b/Source/lua/modules/dev/player/spells.cpp index ae60c40cb..d21d81d4d 100644 --- a/Source/lua/modules/dev/player/spells.cpp +++ b/Source/lua/modules/dev/player/spells.cpp @@ -8,8 +8,8 @@ #include "lua/metadoc.hpp" #include "msg.h" -#include "spelldat.h" #include "spells.h" +#include "tables/spelldat.h" #include "utils/str_cat.hpp" namespace devilution { diff --git a/Source/lua/modules/floatingnumbers.cpp b/Source/lua/modules/floatingnumbers.cpp new file mode 100644 index 000000000..922b0f9f5 --- /dev/null +++ b/Source/lua/modules/floatingnumbers.cpp @@ -0,0 +1,24 @@ +#include "lua/modules/floatingnumbers.hpp" + +#include + +#include "engine/point.hpp" +#include "lua/metadoc.hpp" +#include "qol/floatingnumbers.h" + +namespace devilution { + +sol::table LuaFloatingNumbersModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + + LuaSetDocFn(table, "add", "(text: string, pos: Point, style: UiFlags, id: integer = 0, reverseDirection: boolean = false)", + "Add a floating number", + [](const std::string &text, Point pos, UiFlags style, std::optional id, std::optional reverseDirection) { + AddFloatingNumber(pos, { 0, 0 }, text, style, id.value_or(0), reverseDirection.value_or(false)); + }); + + return table; +} + +} // namespace devilution diff --git a/Source/lua/modules/floatingnumbers.hpp b/Source/lua/modules/floatingnumbers.hpp new file mode 100644 index 000000000..008c3c3ee --- /dev/null +++ b/Source/lua/modules/floatingnumbers.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace devilution { + +sol::table LuaFloatingNumbersModule(sol::state_view &lua); + +} // namespace devilution diff --git a/Source/lua/modules/items.cpp b/Source/lua/modules/items.cpp index b84dcd80a..f83328aac 100644 --- a/Source/lua/modules/items.cpp +++ b/Source/lua/modules/items.cpp @@ -6,10 +6,10 @@ #include #include "data/file.hpp" -#include "itemdat.h" #include "items.h" #include "lua/metadoc.hpp" #include "player.h" +#include "tables/itemdat.h" #include "utils/utf8.hpp" namespace devilution { @@ -485,6 +485,17 @@ sol::table LuaItemModule(sol::state_view &lua) LuaSetDocFn(table, "addItemDataFromTsv", "(path: string, baseMappingId: number)", AddItemDataFromTsv); LuaSetDocFn(table, "addUniqueItemDataFromTsv", "(path: string, baseMappingId: number)", AddUniqueItemDataFromTsv); + // Expose enums through the module table + table["ItemIndex"] = lua["ItemIndex"]; + table["ItemType"] = lua["ItemType"]; + table["ItemClass"] = lua["ItemClass"]; + table["ItemEquipType"] = lua["ItemEquipType"]; + table["ItemMiscID"] = lua["ItemMiscID"]; + table["SpellID"] = lua["SpellID"]; + table["ItemEffectType"] = lua["ItemEffectType"]; + table["ItemSpecialEffect"] = lua["ItemSpecialEffect"]; + table["ItemSpecialEffectHf"] = lua["ItemSpecialEffectHf"]; + return table; } diff --git a/Source/lua/modules/items.hpp b/Source/lua/modules/items.hpp index 90e0a8485..e59e8af67 100644 --- a/Source/lua/modules/items.hpp +++ b/Source/lua/modules/items.hpp @@ -1,9 +1,9 @@ -#pragma once - -#include - -namespace devilution { - -sol::table LuaItemModule(sol::state_view &lua); - -} // namespace devilution +#pragma once + +#include + +namespace devilution { + +sol::table LuaItemModule(sol::state_view &lua); + +} // namespace devilution diff --git a/Source/lua/modules/monsters.cpp b/Source/lua/modules/monsters.cpp index 3fbb01b36..f648d500d 100644 --- a/Source/lua/modules/monsters.cpp +++ b/Source/lua/modules/monsters.cpp @@ -6,8 +6,10 @@ #include #include "data/file.hpp" +#include "engine/point.hpp" #include "lua/metadoc.hpp" -#include "monstdat.h" +#include "monster.h" +#include "tables/monstdat.h" #include "utils/language.h" #include "utils/str_split.hpp" @@ -27,10 +29,26 @@ void AddUniqueMonsterDataFromTsv(const std::string_view path) LoadUniqueMonstDatFromFile(dataFile, path); } +void InitMonsterUserType(sol::state_view &lua) +{ + sol::usertype monsterType = lua.new_usertype(sol::no_constructor); + LuaSetDocReadonlyProperty(monsterType, "position", "Point", + "Monster's current position (readonly)", + [](const Monster &monster) { + return Point { monster.position.tile }; + }); + LuaSetDocReadonlyProperty(monsterType, "id", "integer", + "Monster's unique ID (readonly)", + [](const Monster &monster) { + return static_cast(reinterpret_cast(&monster)); + }); +} + } // namespace sol::table LuaMonstersModule(sol::state_view &lua) { + InitMonsterUserType(lua); sol::table table = lua.create_table(); LuaSetDocFn(table, "addMonsterDataFromTsv", "(path: string)", AddMonsterDataFromTsv); LuaSetDocFn(table, "addUniqueMonsterDataFromTsv", "(path: string)", AddUniqueMonsterDataFromTsv); diff --git a/Source/lua/modules/monsters.hpp b/Source/lua/modules/monsters.hpp index 62b3655e7..fbef8696e 100644 --- a/Source/lua/modules/monsters.hpp +++ b/Source/lua/modules/monsters.hpp @@ -1,9 +1,9 @@ -#pragma once - -#include - -namespace devilution { - -sol::table LuaMonstersModule(sol::state_view &lua); - -} // namespace devilution +#pragma once + +#include + +namespace devilution { + +sol::table LuaMonstersModule(sol::state_view &lua); + +} // namespace devilution diff --git a/Source/lua/modules/player.cpp b/Source/lua/modules/player.cpp index 28a0e2cca..3cc83ce35 100644 --- a/Source/lua/modules/player.cpp +++ b/Source/lua/modules/player.cpp @@ -4,18 +4,34 @@ #include +#include "effects.h" +#include "engine/backbuffer_state.hpp" #include "engine/point.hpp" +#include "engine/random.hpp" +#include "inv.h" +#include "items.h" #include "lua/metadoc.hpp" #include "player.h" namespace devilution { namespace { + void InitPlayerUserType(sol::state_view &lua) { sol::usertype playerType = lua.new_usertype(sol::no_constructor); LuaSetDocReadonlyProperty(playerType, "name", "string", "Player's name (readonly)", &Player::name); + LuaSetDocReadonlyProperty(playerType, "id", "integer", + "Player's unique ID (readonly)", + [](const Player &player) { + return static_cast(reinterpret_cast(&player)); + }); + LuaSetDocReadonlyProperty(playerType, "position", "Point", + "Player's current position (readonly)", + [](const Player &player) -> Point { + return Point { player.position.tile }; + }); LuaSetDocFn(playerType, "addExperience", "(experience: integer, monsterLevel: integer = nil)", "Adds experience to this player based on the current game mode", [](Player &player, uint32_t experience, std::optional monsterLevel) { @@ -28,6 +44,73 @@ void InitPlayerUserType(sol::state_view &lua) LuaSetDocProperty(playerType, "characterLevel", "number", "Character level (writeable)", &Player::getCharacterLevel, &Player::setCharacterLevel); + LuaSetDocFn(playerType, "addItem", "(itemId: integer, count: integer = 1)", + "Add an item to the player's inventory", + [](Player &player, int itemId, std::optional count) -> bool { + const _item_indexes itemIndex = static_cast<_item_indexes>(itemId); + const int itemCount = count.value_or(1); + for (int i = 0; i < itemCount; i++) { + Item tempItem {}; + SetupAllItems(player, tempItem, itemIndex, AdvanceRndSeed(), 1, 1, true, false); + if (!AutoPlaceItemInInventory(player, tempItem, true)) { + return false; + } + } + CalcPlrInv(player, true); + return true; + }); + LuaSetDocFn(playerType, "hasItem", "(itemId: integer)", + "Check if the player has an item with the given ID", + [](const Player &player, int itemId) -> bool { + return HasInventoryOrBeltItemWithId(player, static_cast<_item_indexes>(itemId)); + }); + LuaSetDocFn(playerType, "removeItem", "(itemId: integer, count: integer = 1)", + "Remove an item from the player's inventory", + [](Player &player, int itemId, std::optional count) -> int { + const _item_indexes targetId = static_cast<_item_indexes>(itemId); + const int itemCount = count.value_or(1); + int removed = 0; + + // Remove from inventory + for (int i = player._pNumInv - 1; i >= 0 && removed < itemCount; i--) { + if (player.InvList[i].IDidx == targetId) { + player.RemoveInvItem(i); + removed++; + } + } + + // Remove from belt if needed + for (int i = MaxBeltItems - 1; i >= 0 && removed < itemCount; i--) { + if (!player.SpdList[i].isEmpty() && player.SpdList[i].IDidx == targetId) { + player.RemoveSpdBarItem(i); + removed++; + } + } + + if (removed > 0) { + CalcPlrInv(player, true); + } + + return removed; + }); + LuaSetDocFn(playerType, "restoreFullLife", "()", + "Restore player's HP to maximum", + [](Player &player) { + player._pHitPoints = player._pMaxHP; + player._pHPBase = player._pMaxHPBase; + }); + LuaSetDocFn(playerType, "restoreFullMana", "()", + "Restore player's mana to maximum", + [](Player &player) { + player._pMana = player._pMaxMana; + player._pManaBase = player._pMaxManaBase; + }); + LuaSetDocReadonlyProperty(playerType, "mana", "number", + "Current mana (readonly)", + [](Player &player) { return player._pMana >> 6; }); + LuaSetDocReadonlyProperty(playerType, "maxMana", "number", + "Maximum mana (readonly)", + [](Player &player) { return player._pMaxMana >> 6; }); } } // namespace @@ -45,6 +128,7 @@ sol::table LuaPlayerModule(sol::state_view &lua) [](int x, int y) { NetSendCmdLoc(MyPlayerId, true, CMD_WALKXY, Point { x, y }); }); + return table; } diff --git a/Source/lua/modules/render.cpp b/Source/lua/modules/render.cpp index 99bd550fa..4448edae6 100644 --- a/Source/lua/modules/render.cpp +++ b/Source/lua/modules/render.cpp @@ -2,6 +2,7 @@ #include +#include "DiabloUI/ui_flags.hpp" #include "engine/dx.h" #include "engine/render/text_render.hpp" #include "lua/metadoc.hpp" @@ -19,6 +20,51 @@ sol::table LuaRenderModule(sol::state_view &lua) "Returns the screen width", []() { return gnScreenWidth; }); LuaSetDocFn(table, "screen_height", "()", "Returns the screen height", []() { return gnScreenHeight; }); + + auto uiFlags = lua.create_table(); + uiFlags["None"] = UiFlags::None; + + uiFlags["FontSize12"] = UiFlags::FontSize12; + uiFlags["FontSize24"] = UiFlags::FontSize24; + uiFlags["FontSize30"] = UiFlags::FontSize30; + uiFlags["FontSize42"] = UiFlags::FontSize42; + uiFlags["FontSize46"] = UiFlags::FontSize46; + uiFlags["FontSizeDialog"] = UiFlags::FontSizeDialog; + + uiFlags["ColorUiGold"] = UiFlags::ColorUiGold; + uiFlags["ColorUiSilver"] = UiFlags::ColorUiSilver; + uiFlags["ColorUiGoldDark"] = UiFlags::ColorUiGoldDark; + uiFlags["ColorUiSilverDark"] = UiFlags::ColorUiSilverDark; + uiFlags["ColorDialogWhite"] = UiFlags::ColorDialogWhite; + uiFlags["ColorDialogYellow"] = UiFlags::ColorDialogYellow; + uiFlags["ColorDialogRed"] = UiFlags::ColorDialogRed; + uiFlags["ColorYellow"] = UiFlags::ColorYellow; + uiFlags["ColorGold"] = UiFlags::ColorGold; + uiFlags["ColorBlack"] = UiFlags::ColorBlack; + uiFlags["ColorWhite"] = UiFlags::ColorWhite; + uiFlags["ColorWhitegold"] = UiFlags::ColorWhitegold; + uiFlags["ColorRed"] = UiFlags::ColorRed; + uiFlags["ColorBlue"] = UiFlags::ColorBlue; + uiFlags["ColorOrange"] = UiFlags::ColorOrange; + uiFlags["ColorButtonface"] = UiFlags::ColorButtonface; + uiFlags["ColorButtonpushed"] = UiFlags::ColorButtonpushed; + + uiFlags["AlignCenter"] = UiFlags::AlignCenter; + uiFlags["AlignRight"] = UiFlags::AlignRight; + uiFlags["VerticalCenter"] = UiFlags::VerticalCenter; + + uiFlags["KerningFitSpacing"] = UiFlags::KerningFitSpacing; + + uiFlags["ElementDisabled"] = UiFlags::ElementDisabled; + uiFlags["ElementHidden"] = UiFlags::ElementHidden; + + uiFlags["PentaCursor"] = UiFlags::PentaCursor; + uiFlags["Outlined"] = UiFlags::Outlined; + + uiFlags["NeedsNextElement"] = UiFlags::NeedsNextElement; + + table["UiFlags"] = uiFlags; + return table; } diff --git a/Source/lua/modules/system.cpp b/Source/lua/modules/system.cpp new file mode 100644 index 000000000..0ef6a09d0 --- /dev/null +++ b/Source/lua/modules/system.cpp @@ -0,0 +1,25 @@ +#include "lua/modules/system.hpp" + +#include + +#ifdef USE_SDL3 +#include +#else +#include +#endif + +#include "lua/metadoc.hpp" + +namespace devilution { + +sol::table LuaSystemModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + + LuaSetDocFn(table, "get_ticks", "() -> integer", "Returns the number of milliseconds since the game started.", + []() { return static_cast(SDL_GetTicks()); }); + + return table; +} + +} // namespace devilution diff --git a/Source/lua/modules/system.hpp b/Source/lua/modules/system.hpp new file mode 100644 index 000000000..fa2629a7c --- /dev/null +++ b/Source/lua/modules/system.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace devilution { + +sol::table LuaSystemModule(sol::state_view &lua); + +} // namespace devilution diff --git a/Source/minitext.cpp b/Source/minitext.cpp index 1b25b0db2..075279377 100644 --- a/Source/minitext.cpp +++ b/Source/minitext.cpp @@ -10,15 +10,15 @@ #include #include "DiabloUI/ui_flags.hpp" -#include "control.h" +#include "control/control.hpp" #include "engine/clx_sprite.hpp" #include "engine/dx.h" #include "engine/load_cel.hpp" #include "engine/render/clx_render.hpp" #include "engine/render/primitive_render.hpp" #include "engine/render/text_render.hpp" -#include "playerdat.hpp" -#include "textdat.h" +#include "tables/playerdat.hpp" +#include "tables/textdat.h" #include "utils/language.h" #include "utils/timer.hpp" diff --git a/Source/minitext.h b/Source/minitext.h index 9bfdcb6e2..2dfd293e4 100644 --- a/Source/minitext.h +++ b/Source/minitext.h @@ -6,7 +6,7 @@ #pragma once #include "engine/surface.hpp" -#include "textdat.h" +#include "tables/textdat.h" namespace devilution { diff --git a/Source/missiles.cpp b/Source/missiles.cpp index f4733157f..896ce94d6 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -19,7 +19,7 @@ #include #include "appfat.h" -#include "control.h" +#include "control/control.hpp" #include "controls/control_mode.hpp" #include "controls/plrctrls.h" #include "crawl.hpp" @@ -37,19 +37,19 @@ #include "engine/world_tile.hpp" #include "function_ref.hpp" #include "interfac.h" -#include "itemdat.h" #include "items.h" #include "levels/gendung.h" #include "levels/gendung_defs.hpp" -#include "misdat.h" -#include "monstdat.h" #include "msg.h" #include "multi.h" #include "objects.h" #include "player.h" -#include "playerdat.hpp" #include "sound_effect_enums.h" -#include "spelldat.h" +#include "tables/itemdat.h" +#include "tables/misdat.h" +#include "tables/monstdat.h" +#include "tables/playerdat.hpp" +#include "tables/spelldat.h" #include "utils/enum_traits.h" #ifdef _DEBUG #include "debug.h" diff --git a/Source/missiles.h b/Source/missiles.h index 5cb6d0082..65fd2f604 100644 --- a/Source/missiles.h +++ b/Source/missiles.h @@ -12,10 +12,10 @@ #include "engine/displacement.hpp" #include "engine/point.hpp" #include "engine/world_tile.hpp" -#include "misdat.h" #include "monster.h" #include "player.h" -#include "spelldat.h" +#include "tables/misdat.h" +#include "tables/spelldat.h" #include "utils/is_of.hpp" namespace devilution { diff --git a/Source/monster.cpp b/Source/monster.cpp index 5d586cfe6..f0f9e2ad6 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -34,7 +34,7 @@ #include #include "automap.h" -#include "control.h" +#include "control/control.hpp" #include "crawl.hpp" #include "cursor.h" #include "dead.h" @@ -59,7 +59,6 @@ #include "game_mode.hpp" #include "headless_mode.hpp" #include "inv.h" -#include "itemdat.h" #include "items.h" #include "levels/crypt.h" #include "levels/drlg_l4.h" @@ -70,24 +69,25 @@ #include "levels/tile_properties.hpp" #include "levels/trigs.h" #include "lighting.h" +#include "lua/lua_global.hpp" #include "minitext.h" -#include "misdat.h" #include "missiles.h" -#include "monstdat.h" #include "movie.h" #include "msg.h" #include "multi.h" -#include "objdat.h" #include "objects.h" #include "options.h" #include "player.h" -#include "playerdat.hpp" -#include "qol/floatingnumbers.h" #include "quests.h" #include "sound_effect_enums.h" -#include "spelldat.h" #include "storm/storm_net.hpp" -#include "textdat.h" +#include "tables/itemdat.h" +#include "tables/misdat.h" +#include "tables/monstdat.h" +#include "tables/objdat.h" +#include "tables/playerdat.hpp" +#include "tables/spelldat.h" +#include "tables/textdat.h" #include "utils/algorithm/container.hpp" #include "utils/attributes.h" #include "utils/cl2_to_clx.hpp" @@ -3778,7 +3778,7 @@ void AddDoppelganger(Monster &monster) void ApplyMonsterDamage(DamageType damageType, Monster &monster, int damage) { - AddFloatingNumber(damageType, monster, damage); + LuaEvent("OnMonsterTakeDamage", &monster, damage, static_cast(damageType)); monster.hitPoints -= damage; diff --git a/Source/monster.h b/Source/monster.h index 6b3ef4303..a469ecd26 100644 --- a/Source/monster.h +++ b/Source/monster.h @@ -23,10 +23,10 @@ #include "engine/world_tile.hpp" #include "game_mode.hpp" #include "levels/dun_tile.hpp" -#include "misdat.h" -#include "monstdat.h" -#include "spelldat.h" -#include "textdat.h" +#include "tables/misdat.h" +#include "tables/monstdat.h" +#include "tables/spelldat.h" +#include "tables/textdat.h" #include "utils/language.h" namespace devilution { diff --git a/Source/msg.cpp b/Source/msg.cpp index ce578df00..b237f3cb8 100644 --- a/Source/msg.cpp +++ b/Source/msg.cpp @@ -28,7 +28,7 @@ #include "DiabloUI/diabloui.h" #include "automap.h" #include "config.h" -#include "control.h" +#include "control/control.hpp" #include "dead.h" #include "engine/backbuffer_state.hpp" #include "engine/random.hpp" @@ -1907,21 +1907,41 @@ size_t OnKnockback(const TCmdParam1 &message, Player &player) return sizeof(message); } -size_t OnResurrect(const TCmdParam1 &message, Player &player) +size_t OnResurrect(const TCmdParam1 &message, Player &caster) { const uint16_t playerIdx = Swap16LE(message.wParam1); if (gbBufferMsgs == 1) { - BufferMessage(player, &message, sizeof(message)); - } else if (playerIdx < Players.size()) { - DoResurrect(player, Players[playerIdx]); - if (&player == MyPlayer) - pfile_update(true); + BufferMessage(caster, &message, sizeof(message)); + return sizeof(message); + } + + if (playerIdx >= Players.size()) + return sizeof(message); + + Player &target = Players[playerIdx]; + + SpawnResurrectBeam(caster, target); + + if (&target == MyPlayer && target._pHitPoints <= 0) { + NetSendCmd(true, CMD_PLRALIVE); } return sizeof(message); } +size_t OnPlayerAlive(const TCmd &message, Player &target) +{ + if (gbBufferMsgs == 1) { + BufferMessage(target, &message, sizeof(message)); + return sizeof(message); + } + + ApplyResurrect(target); + + return sizeof(message); +} + size_t OnHealOther(const TCmdParam1 &message, const Player &caster) { const uint16_t playerIdx = Swap16LE(message.wParam1); @@ -3392,6 +3412,8 @@ size_t ParseCmd(uint8_t pnum, const TCmd *pCmd, size_t maxCmdSize) return HandleCmd(OnMonstDamage, player, pCmd, maxCmdSize); case CMD_PLRDEAD: return HandleCmd(OnPlayerDeath, player, pCmd, maxCmdSize); + case CMD_PLRALIVE: + return HandleCmd(OnPlayerAlive, player, pCmd, maxCmdSize); case CMD_PLRDAMAGE: return HandleCmd(OnPlayerDamage, player, pCmd, maxCmdSize); case CMD_OPENDOOR: diff --git a/Source/msg.h b/Source/msg.h index 124f14bb4..d0b51ad81 100644 --- a/Source/msg.h +++ b/Source/msg.h @@ -205,6 +205,10 @@ enum _cmd_id : uint8_t { // body (TCmdParam1): // int16_t ear_flag CMD_PLRDEAD, + // Player resurrection. + // + // body (TCmd) + CMD_PLRALIVE, // Lift item to hand request. // // body (TCmdGItem) diff --git a/Source/objects.cpp b/Source/objects.cpp index 859c7374b..b489fa3d7 100644 --- a/Source/objects.cpp +++ b/Source/objects.cpp @@ -38,10 +38,10 @@ #include "minitext.h" #include "missiles.h" #include "monster.h" -#include "objdat.h" #include "options.h" #include "qol/stash.h" #include "stores.h" +#include "tables/objdat.h" #include "towners.h" #include "track.h" #include "utils/algorithm/container.hpp" @@ -2524,24 +2524,22 @@ void OperateShrineCostOfWisdom(Player &player, SpellID spellId, diablo_message m } } - const uint32_t t = player._pMaxManaBase / 10; - const int v1 = player._pMana - player._pManaBase; - const int v2 = player._pMaxMana - player._pMaxManaBase; - player._pManaBase -= t; - player._pMana -= t; - player._pMaxMana -= t; - player._pMaxManaBase -= t; - if (player.hasNoMana()) { - player._pMana = v1; - player._pManaBase = 0; - } - if (player._pMaxMana >> 6 <= 0) { - player._pMaxMana = v2; + int maxBase = player._pMaxManaBase; + + if (maxBase < 0) { + // Fix bugged state; do not turn this into a "negative penalty" mana boost. player._pMaxManaBase = 0; + maxBase = 0; } - RedrawEverything(); + const int penalty = maxBase / 10; // 10% of max base mana (>= 0) + player._pMaxManaBase -= penalty; // will remain >= 0 + player._pManaBase -= penalty; // may go negative, allowed + player._pMaxMana -= penalty; // may go negative, allowed + player._pMana -= penalty; // may go negative, allowed + + RedrawEverything(); InitDiabloMsg(message); } diff --git a/Source/objects.h b/Source/objects.h index b7c20c5e8..c9032454d 100644 --- a/Source/objects.h +++ b/Source/objects.h @@ -16,11 +16,11 @@ #include "engine/point.hpp" #include "engine/rectangle.hpp" #include "engine/world_tile.hpp" -#include "itemdat.h" #include "levels/dun_tile.hpp" #include "monster.h" -#include "objdat.h" -#include "textdat.h" +#include "tables/itemdat.h" +#include "tables/objdat.h" +#include "tables/textdat.h" #include "utils/attributes.h" #include "utils/is_of.hpp" #include "utils/string_or_view.hpp" diff --git a/Source/options.cpp b/Source/options.cpp index 4fe9c88db..65ae9a892 100644 --- a/Source/options.cpp +++ b/Source/options.cpp @@ -72,7 +72,7 @@ namespace { void DiscoverMods() { // Add mods available by default: - std::unordered_set modNames = { "clock" }; + std::unordered_set modNames = { "clock", "adria_refills_mana", "Floating Numbers - Damage", "Floating Numbers - XP" }; if (HaveHellfire()) { modNames.insert("Hellfire"); @@ -852,7 +852,6 @@ GameplayOptions::GameplayOptions() , autoElixirPickup("Auto Elixir Pickup", OptionEntryFlags::None, N_("Auto Elixir Pickup"), N_("Elixirs are automatically collected when in close proximity to the player."), false) , autoOilPickup("Auto Oil Pickup", OptionEntryFlags::OnlyHellfire, N_("Auto Oil Pickup"), N_("Oils are automatically collected when in close proximity to the player."), false) , autoPickupInTown("Auto Pickup in Town", OptionEntryFlags::None, N_("Auto Pickup in Town"), N_("Automatically pickup items in town."), false) - , adriaRefillsMana("Adria Refills Mana", OptionEntryFlags::None, N_("Adria Refills Mana"), N_("Adria will refill your mana when you visit her shop."), false) , autoEquipWeapons("Auto Equip Weapons", OptionEntryFlags::None, N_("Auto Equip Weapons"), N_("Weapons will be automatically equipped on pickup or purchase if enabled."), true) , autoEquipArmor("Auto Equip Armor", OptionEntryFlags::None, N_("Auto Equip Armor"), N_("Armor will be automatically equipped on pickup or purchase if enabled."), false) , autoEquipHelms("Auto Equip Helms", OptionEntryFlags::None, N_("Auto Equip Helms"), N_("Helms will be automatically equipped on pickup or purchase if enabled."), false) @@ -870,12 +869,6 @@ GameplayOptions::GameplayOptions() , numFullManaPotionPickup("Full Mana Potion Pickup", OptionEntryFlags::None, N_("Full Mana Potion Pickup"), N_("Number of Full Mana potions to pick up automatically."), 0, { 0, 1, 2, 4, 8, 16 }) , numRejuPotionPickup("Rejuvenation Potion Pickup", OptionEntryFlags::None, N_("Rejuvenation Potion Pickup"), N_("Number of Rejuvenation potions to pick up automatically."), 0, { 0, 1, 2, 4, 8, 16 }) , numFullRejuPotionPickup("Full Rejuvenation Potion Pickup", OptionEntryFlags::None, N_("Full Rejuvenation Potion Pickup"), N_("Number of Full Rejuvenation potions to pick up automatically."), 0, { 0, 1, 2, 4, 8, 16 }) - , enableFloatingNumbers("Enable floating numbers", OptionEntryFlags::None, N_("Enable floating numbers"), N_("Enables floating numbers on gaining XP / dealing damage etc."), FloatingNumbers::Off, - { - { FloatingNumbers::Off, N_("Off") }, - { FloatingNumbers::Random, N_("Random Angles") }, - { FloatingNumbers::Vertical, N_("Vertical Only") }, - }) , skipLoadingScreenThresholdMs("Skip loading screen threshold, ms", OptionEntryFlags::Invisible, "", "", 0) { } @@ -902,7 +895,6 @@ std::vector GameplayOptions::GetEntries() &floatingInfoBox, &showMonsterType, &showItemLabels, - &enableFloatingNumbers, &autoRefillBelt, &autoEquipWeapons, &autoEquipArmor, @@ -920,7 +912,6 @@ std::vector GameplayOptions::GetEntries() &numFullRejuPotionPickup, &autoPickupInTown, &disableCripplingShrines, - &adriaRefillsMana, &grabInput, &pauseOnFocusLoss, &skipLoadingScreenThresholdMs, @@ -1015,34 +1006,31 @@ void OptionEntryLanguageCode::CheckLanguagesAreInitialized() const const bool haveExtraFonts = HaveExtraFonts(); // Add well-known supported languages - languages.emplace_back("bg", "Български"); - languages.emplace_back("cs", "Čeština"); languages.emplace_back("da", "Dansk"); languages.emplace_back("de", "Deutsch"); - languages.emplace_back("el", "Ελληνικά"); + languages.emplace_back("et", "Eesti"); languages.emplace_back("en", "English"); languages.emplace_back("es", "Español"); - languages.emplace_back("et", "Eesti"); languages.emplace_back("fr", "Français"); languages.emplace_back("hr", "Hrvatski"); - languages.emplace_back("hu", "Magyar"); languages.emplace_back("it", "Italiano"); - - if (haveExtraFonts) { - languages.emplace_back("ja", "日本語"); - languages.emplace_back("ko", "한국어"); - } - + languages.emplace_back("hu", "Magyar"); languages.emplace_back("pl", "Polski"); languages.emplace_back("pt_BR", "Português do Brasil"); languages.emplace_back("ro", "Română"); - languages.emplace_back("ru", "Русский"); languages.emplace_back("fi", "Suomi"); languages.emplace_back("sv", "Svenska"); languages.emplace_back("tr", "Türkçe"); + languages.emplace_back("cs", "Čeština"); + languages.emplace_back("el", "Ελληνικά"); + languages.emplace_back("be", "беларуская"); + languages.emplace_back("bg", "Български"); + languages.emplace_back("ru", "Русский"); languages.emplace_back("uk", "Українська"); if (haveExtraFonts) { + languages.emplace_back("ja", "日本語"); + languages.emplace_back("ko", "한국어"); languages.emplace_back("zh_CN", "汉语"); languages.emplace_back("zh_TW", "漢語"); } diff --git a/Source/options.h b/Source/options.h index 2b0b68a8c..7e4da902d 100644 --- a/Source/options.h +++ b/Source/options.h @@ -92,15 +92,6 @@ enum class Resampler : uint8_t { std::string_view ResamplerToString(Resampler resampler); std::optional ResamplerFromString(std::string_view resampler); -enum class FloatingNumbers : uint8_t { - /** @brief Show no floating numbers. */ - Off = 0, - /** @brief Show floating numbers at random angles. */ - Random = 1, - /** @brief Show floating numbers vertically only. */ - Vertical = 2, -}; - enum class OptionEntryType : uint8_t { Boolean, List, @@ -608,8 +599,6 @@ struct GameplayOptions : OptionCategoryBase { OptionEntryBoolean autoOilPickup; /** @brief Enable or Disable auto-pickup in town */ OptionEntryBoolean autoPickupInTown; - /** @brief Recover mana when talking to Adria. */ - OptionEntryBoolean adriaRefillsMana; /** @brief Automatically attempt to equip weapon-type items when picking them up. */ OptionEntryBoolean autoEquipWeapons; /** @brief Automatically attempt to equip armor-type items when picking them up. */ @@ -644,8 +633,6 @@ struct GameplayOptions : OptionCategoryBase { OptionEntryInt numRejuPotionPickup; /** @brief Number of Full Rejuvenating potions to pick up automatically */ OptionEntryInt numFullRejuPotionPickup; - /** @brief Enable floating numbers. */ - OptionEntryEnum enableFloatingNumbers; /** * @brief If loading takes less than this value, skips displaying the loading screen. diff --git a/Source/pack.cpp b/Source/pack.cpp index 7755c465e..fc29573b9 100644 --- a/Source/pack.cpp +++ b/Source/pack.cpp @@ -11,9 +11,9 @@ #include "game_mode.hpp" #include "items/validation.h" #include "loadsave.h" -#include "playerdat.hpp" #include "plrmsg.h" #include "stores.h" +#include "tables/playerdat.hpp" #include "utils/endian_read.hpp" #include "utils/endian_swap.hpp" #include "utils/is_of.hpp" diff --git a/Source/panels/charpanel.cpp b/Source/panels/charpanel.cpp index 30ee9c222..d7d676746 100644 --- a/Source/panels/charpanel.cpp +++ b/Source/panels/charpanel.cpp @@ -9,13 +9,13 @@ #include #include -#include "control.h" +#include "control/control.hpp" #include "engine/load_clx.hpp" #include "engine/render/clx_render.hpp" #include "engine/render/text_render.hpp" #include "panels/ui_panels.hpp" #include "player.h" -#include "playerdat.hpp" +#include "tables/playerdat.hpp" #include "utils/algorithm/container.hpp" #include "utils/display.h" #include "utils/enum_traits.h" diff --git a/Source/panels/console.cpp b/Source/panels/console.cpp index 2b6ceabe5..b14982bb8 100644 --- a/Source/panels/console.cpp +++ b/Source/panels/console.cpp @@ -22,7 +22,7 @@ #endif #include "DiabloUI/text_input.hpp" -#include "control.h" +#include "control/control.hpp" #include "engine/assets.hpp" #include "engine/displacement.hpp" #include "engine/dx.h" diff --git a/Source/panels/mainpanel.cpp b/Source/panels/mainpanel.cpp index 935fde6d1..99f8549d0 100644 --- a/Source/panels/mainpanel.cpp +++ b/Source/panels/mainpanel.cpp @@ -6,7 +6,7 @@ #include -#include "control.h" +#include "control/control.hpp" #include "engine/clx_sprite.hpp" #include "engine/load_clx.hpp" #include "engine/render/clx_render.hpp" diff --git a/Source/panels/partypanel.cpp b/Source/panels/partypanel.cpp index 2a9f49596..de59fad2c 100644 --- a/Source/panels/partypanel.cpp +++ b/Source/panels/partypanel.cpp @@ -4,7 +4,7 @@ #include #include "automap.h" -#include "control.h" +#include "control/control.hpp" #include "engine/backbuffer_state.hpp" #include "engine/clx_sprite.hpp" #include "engine/load_cel.hpp" @@ -17,10 +17,10 @@ #include "inv.h" #include "options.h" #include "pfile.h" -#include "playerdat.hpp" #include "qol/monhealthbar.h" #include "qol/stash.h" #include "stores.h" +#include "tables/playerdat.hpp" #include "utils/status_macros.hpp" #include "utils/surface_to_clx.hpp" diff --git a/Source/panels/spell_book.cpp b/Source/panels/spell_book.cpp index a9b7d18b1..532fa7a9b 100644 --- a/Source/panels/spell_book.cpp +++ b/Source/panels/spell_book.cpp @@ -7,7 +7,7 @@ #include #include -#include "control.h" +#include "control/control.hpp" #include "engine/backbuffer_state.hpp" #include "engine/clx_sprite.hpp" #include "engine/load_cel.hpp" @@ -20,7 +20,7 @@ #include "panels/spell_icons.hpp" #include "panels/ui_panels.hpp" #include "player.h" -#include "spelldat.h" +#include "tables/spelldat.h" #include "utils/language.h" #include "utils/status_macros.hpp" diff --git a/Source/panels/spell_icons.hpp b/Source/panels/spell_icons.hpp index 8d3d2e357..db66cfe8f 100644 --- a/Source/panels/spell_icons.hpp +++ b/Source/panels/spell_icons.hpp @@ -8,7 +8,7 @@ #include "engine/clx_sprite.hpp" #include "engine/point.hpp" #include "engine/surface.hpp" -#include "spelldat.h" +#include "tables/spelldat.h" #define SPLICONLENGTH 56 diff --git a/Source/panels/spell_list.cpp b/Source/panels/spell_list.cpp index 153ae048d..cb50b0341 100644 --- a/Source/panels/spell_list.cpp +++ b/Source/panels/spell_list.cpp @@ -4,7 +4,7 @@ #include -#include "control.h" +#include "control/control.hpp" #include "controls/control_mode.hpp" #include "controls/plrctrls.h" #include "engine/backbuffer_state.hpp" diff --git a/Source/panels/spell_list.hpp b/Source/panels/spell_list.hpp index 8dba57c02..0b706f96a 100644 --- a/Source/panels/spell_list.hpp +++ b/Source/panels/spell_list.hpp @@ -5,7 +5,7 @@ #include "engine/point.hpp" #include "engine/surface.hpp" -#include "spelldat.h" +#include "tables/spelldat.h" namespace devilution { diff --git a/Source/pfile.cpp b/Source/pfile.cpp index 16325e742..ec96c6608 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -27,8 +27,8 @@ #include "menu.h" #include "mpq/mpq_common.hpp" #include "pack.h" -#include "playerdat.hpp" #include "qol/stash.h" +#include "tables/playerdat.hpp" #include "utils/endian_read.hpp" #include "utils/endian_swap.hpp" #include "utils/file_util.h" diff --git a/Source/platform/ctr/asio/include/errno.h b/Source/platform/ctr/asio/include/errno.h index 209a0eae1..3ccfdf983 100644 --- a/Source/platform/ctr/asio/include/errno.h +++ b/Source/platform/ctr/asio/include/errno.h @@ -1,5 +1,5 @@ -#pragma once - -#include_next - -#define ESHUTDOWN (__ELASTERROR + 1) +#pragma once + +#include_next + +#define ESHUTDOWN (__ELASTERROR + 1) diff --git a/Source/platform/ctr/asio/include/net/if.h b/Source/platform/ctr/asio/include/net/if.h index 21ae962ff..cf074c29f 100644 --- a/Source/platform/ctr/asio/include/net/if.h +++ b/Source/platform/ctr/asio/include/net/if.h @@ -1,24 +1,24 @@ -#ifndef _NET_IF_H -#define _NET_IF_H 1 - -#define IF_NAMESIZE 16 - -struct if_nameindex { - unsigned int if_index; - char *if_name; -}; - -#ifdef __cplusplus -extern "C" { -#endif - -unsigned int if_nametoindex(const char *__ifname); -char *if_indextoname(unsigned int __ifindex, char *__ifname); -struct if_nameindex *if_nameindex(); -void if_freenameindex(struct if_nameindex *__ptr); - -#ifdef __cplusplus -} -#endif - -#endif +#ifndef _NET_IF_H +#define _NET_IF_H 1 + +#define IF_NAMESIZE 16 + +struct if_nameindex { + unsigned int if_index; + char *if_name; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +unsigned int if_nametoindex(const char *__ifname); +char *if_indextoname(unsigned int __ifindex, char *__ifname); +struct if_nameindex *if_nameindex(); +void if_freenameindex(struct if_nameindex *__ptr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Source/platform/ctr/asio/include/netdb.h b/Source/platform/ctr/asio/include/netdb.h index a902bbd10..745a3f8d5 100644 --- a/Source/platform/ctr/asio/include/netdb.h +++ b/Source/platform/ctr/asio/include/netdb.h @@ -1,8 +1,8 @@ -#pragma once - -#include_next - -#define EAI_SERVICE -401 -#define EAI_AGAIN -402 -#define EAI_BADFLAGS -403 -#define EAI_FAIL -404 +#pragma once + +#include_next + +#define EAI_SERVICE -401 +#define EAI_AGAIN -402 +#define EAI_BADFLAGS -403 +#define EAI_FAIL -404 diff --git a/Source/platform/ctr/asio/include/netinet/in.h b/Source/platform/ctr/asio/include/netinet/in.h index 4920c392d..1f3c0b605 100644 --- a/Source/platform/ctr/asio/include/netinet/in.h +++ b/Source/platform/ctr/asio/include/netinet/in.h @@ -1,30 +1,30 @@ -#pragma once - -#include -#include_next - -struct in6_addr { - uint8_t s6_addr[16]; -}; - -struct ipv6_mreq { - struct in6_addr ipv6mr_multiaddr; - unsigned ipv6mr_interface; -}; - -struct sockaddr_in6 { - sa_family_t sin6_family; - in_port_t sin6_port; - uint32_t sin6_flowinfo; - struct in6_addr sin6_addr; - uint32_t sin6_scope_id; -}; - -#define IPPROTO_IPV6 -1 -#define IP_MULTICAST_IF -1 -#define IPV6_JOIN_GROUP -1 -#define IPV6_LEAVE_GROUP -1 -#define IPV6_UNICAST_HOPS -1 -#define IPV6_MULTICAST_HOPS -1 -#define IPV6_MULTICAST_IF -1 -#define IPV6_MULTICAST_LOOP -1 +#pragma once + +#include +#include_next + +struct in6_addr { + uint8_t s6_addr[16]; +}; + +struct ipv6_mreq { + struct in6_addr ipv6mr_multiaddr; + unsigned ipv6mr_interface; +}; + +struct sockaddr_in6 { + sa_family_t sin6_family; + in_port_t sin6_port; + uint32_t sin6_flowinfo; + struct in6_addr sin6_addr; + uint32_t sin6_scope_id; +}; + +#define IPPROTO_IPV6 -1 +#define IP_MULTICAST_IF -1 +#define IPV6_JOIN_GROUP -1 +#define IPV6_LEAVE_GROUP -1 +#define IPV6_UNICAST_HOPS -1 +#define IPV6_MULTICAST_HOPS -1 +#define IPV6_MULTICAST_IF -1 +#define IPV6_MULTICAST_LOOP -1 diff --git a/Source/platform/ctr/asio/include/sys/ioctl.h b/Source/platform/ctr/asio/include/sys/ioctl.h index 6ebe078d2..d8c891e76 100644 --- a/Source/platform/ctr/asio/include/sys/ioctl.h +++ b/Source/platform/ctr/asio/include/sys/ioctl.h @@ -1,5 +1,5 @@ -#pragma once - -#include_next - -#define FIONREAD -999 +#pragma once + +#include_next + +#define FIONREAD -999 diff --git a/Source/platform/ctr/asio/include/sys/poll.h b/Source/platform/ctr/asio/include/sys/poll.h index 779ec774f..ec93b796e 100644 --- a/Source/platform/ctr/asio/include/sys/poll.h +++ b/Source/platform/ctr/asio/include/sys/poll.h @@ -1 +1 @@ -#include +#include diff --git a/Source/platform/ctr/asio/include/sys/socket.h b/Source/platform/ctr/asio/include/sys/socket.h index 08a5476eb..f74b8665e 100644 --- a/Source/platform/ctr/asio/include/sys/socket.h +++ b/Source/platform/ctr/asio/include/sys/socket.h @@ -1,31 +1,31 @@ -#pragma once - -#include_next - -#define SO_DEBUG 0 -#define SO_DONTROUTE 0 -#define SO_KEEPALIVE 0 -#define SOMAXCONN 10 -#define MSG_EOR 0 - -struct msghdr { - void *msg_name; - socklen_t msg_namelen; - struct iovec *msg_iov; - int msg_iovlen; - void *msg_control; - socklen_t msg_controllen; - int msg_flags; -}; - -#ifdef __cplusplus -extern "C" { -#endif - -ssize_t recvmsg(int socket, struct msghdr *message, int flags); -ssize_t sendmsg(int socket, const struct msghdr *message, int flags); -int socketpair(int domain, int type, int protocol, int socket_vector[2]); - -#ifdef __cplusplus -} -#endif +#pragma once + +#include_next + +#define SO_DEBUG 0 +#define SO_DONTROUTE 0 +#define SO_KEEPALIVE 0 +#define SOMAXCONN 10 +#define MSG_EOR 0 + +struct msghdr { + void *msg_name; + socklen_t msg_namelen; + struct iovec *msg_iov; + int msg_iovlen; + void *msg_control; + socklen_t msg_controllen; + int msg_flags; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +ssize_t recvmsg(int socket, struct msghdr *message, int flags); +ssize_t sendmsg(int socket, const struct msghdr *message, int flags); +int socketpair(int domain, int type, int protocol, int socket_vector[2]); + +#ifdef __cplusplus +} +#endif diff --git a/Source/platform/ctr/asio/include/sys/uio.h b/Source/platform/ctr/asio/include/sys/uio.h index 8b2c21972..5a99ec24d 100644 --- a/Source/platform/ctr/asio/include/sys/uio.h +++ b/Source/platform/ctr/asio/include/sys/uio.h @@ -1,14 +1,14 @@ -#ifndef _SYS_UIO_H -#define _SYS_UIO_H 1 - -#include - -struct iovec { - void *iov_base; - size_t iov_len; -}; - -ssize_t readv(int __fd, const struct iovec *__iovec, int __count); -ssize_t writev(int __fd, const struct iovec *__iovec, int __count); - -#endif +#ifndef _SYS_UIO_H +#define _SYS_UIO_H 1 + +#include + +struct iovec { + void *iov_base; + size_t iov_len; +}; + +ssize_t readv(int __fd, const struct iovec *__iovec, int __count); +ssize_t writev(int __fd, const struct iovec *__iovec, int __count); + +#endif diff --git a/Source/platform/ctr/asio/include/sys/un.h b/Source/platform/ctr/asio/include/sys/un.h index db574359a..29cf6ce93 100644 --- a/Source/platform/ctr/asio/include/sys/un.h +++ b/Source/platform/ctr/asio/include/sys/un.h @@ -1,11 +1,11 @@ -#ifndef _SYS_UN_H -#define _SYS_UN_H 1 - -typedef unsigned short int sa_family_t; - -struct sockaddr_un { - sa_family_t sun_family; - char sun_path[108]; -}; - -#endif +#ifndef _SYS_UN_H +#define _SYS_UN_H 1 + +typedef unsigned short int sa_family_t; + +struct sockaddr_un { + sa_family_t sun_family; + char sun_path[108]; +}; + +#endif diff --git a/Source/platform/ctr/display.cpp b/Source/platform/ctr/display.cpp index c6ba9c3c9..1a57f59b6 100644 --- a/Source/platform/ctr/display.cpp +++ b/Source/platform/ctr/display.cpp @@ -1,12 +1,12 @@ -#include "platform/ctr/display.hpp" -#include - -#include -uint32_t Get3DSScalingFlag(bool fitToScreen, int width, int height) -{ - if (fitToScreen) - return SDL_FULLSCREEN; - if (width * 3 < height * 5) - return SDL_FITHEIGHT; - return SDL_FITWIDTH; -} +#include "platform/ctr/display.hpp" +#include + +#include +uint32_t Get3DSScalingFlag(bool fitToScreen, int width, int height) +{ + if (fitToScreen) + return SDL_FULLSCREEN; + if (width * 3 < height * 5) + return SDL_FITHEIGHT; + return SDL_FITWIDTH; +} diff --git a/Source/platform/ctr/display.hpp b/Source/platform/ctr/display.hpp index f641c253b..96217e1ca 100644 --- a/Source/platform/ctr/display.hpp +++ b/Source/platform/ctr/display.hpp @@ -1,5 +1,5 @@ -#pragma once - -#include - -uint32_t Get3DSScalingFlag(bool fitToScreen, int width, int height); +#pragma once + +#include + +uint32_t Get3DSScalingFlag(bool fitToScreen, int width, int height); diff --git a/Source/platform/ctr/random.cpp b/Source/platform/ctr/random.cpp index a00969ae8..6643c1424 100644 --- a/Source/platform/ctr/random.cpp +++ b/Source/platform/ctr/random.cpp @@ -1,49 +1,49 @@ -#include <3ds.h> -#include -#include -#include - -static const char *randombytes_ctrrandom_implementation_name() -{ - return "ctrrandom"; -} - -static bool randombytes_ctrrandom_tryfill(void *const buf, const size_t size) -{ - Result res; - if (!psGetSessionHandle()) { - res = psInit(); - if (!R_SUCCEEDED(res)) - return false; - } - res = PS_GenerateRandomBytes(buf, size); - return R_SUCCEEDED(res); -} - -static uint32_t randombytes_ctrrandom() -{ - uint32_t num; - if (!randombytes_ctrrandom_tryfill(&num, sizeof(uint32_t))) - sodium_misuse(); - return num; -} - -static void randombytes_ctrrandom_buf(void *const buf, const size_t size) -{ - if (!randombytes_ctrrandom_tryfill(buf, size)) - sodium_misuse(); -} - -struct randombytes_implementation randombytes_ctrrandom_implementation = { - randombytes_ctrrandom_implementation_name, - randombytes_ctrrandom, - nullptr, - nullptr, - randombytes_ctrrandom_buf, - nullptr -}; - -void randombytes_ctrrandom_init() -{ - randombytes_set_implementation(&randombytes_ctrrandom_implementation); -} +#include <3ds.h> +#include +#include +#include + +static const char *randombytes_ctrrandom_implementation_name() +{ + return "ctrrandom"; +} + +static bool randombytes_ctrrandom_tryfill(void *const buf, const size_t size) +{ + Result res; + if (!psGetSessionHandle()) { + res = psInit(); + if (!R_SUCCEEDED(res)) + return false; + } + res = PS_GenerateRandomBytes(buf, size); + return R_SUCCEEDED(res); +} + +static uint32_t randombytes_ctrrandom() +{ + uint32_t num; + if (!randombytes_ctrrandom_tryfill(&num, sizeof(uint32_t))) + sodium_misuse(); + return num; +} + +static void randombytes_ctrrandom_buf(void *const buf, const size_t size) +{ + if (!randombytes_ctrrandom_tryfill(buf, size)) + sodium_misuse(); +} + +struct randombytes_implementation randombytes_ctrrandom_implementation = { + randombytes_ctrrandom_implementation_name, + randombytes_ctrrandom, + nullptr, + nullptr, + randombytes_ctrrandom_buf, + nullptr +}; + +void randombytes_ctrrandom_init() +{ + randombytes_set_implementation(&randombytes_ctrrandom_implementation); +} diff --git a/Source/platform/ctr/random.hpp b/Source/platform/ctr/random.hpp index 3c92ee0c1..fb559f35b 100644 --- a/Source/platform/ctr/random.hpp +++ b/Source/platform/ctr/random.hpp @@ -1,2 +1,2 @@ -#pragma once -void randombytes_ctrrandom_init(); +#pragma once +void randombytes_ctrrandom_init(); diff --git a/Source/platform/ctr/sockets.hpp b/Source/platform/ctr/sockets.hpp index fdc5cb55a..47758520b 100644 --- a/Source/platform/ctr/sockets.hpp +++ b/Source/platform/ctr/sockets.hpp @@ -1,8 +1,8 @@ -#pragma once - -namespace devilution { - -void n3ds_socInit(); -void n3ds_socExit(); - -} // namespace devilution +#pragma once + +namespace devilution { + +void n3ds_socInit(); +void n3ds_socExit(); + +} // namespace devilution diff --git a/Source/platform/ios/ios_paths.h b/Source/platform/ios/ios_paths.h index d8bcf04e1..4a983eb20 100644 --- a/Source/platform/ios/ios_paths.h +++ b/Source/platform/ios/ios_paths.h @@ -1,11 +1,11 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -extern char *IOSGetPrefPath(); - -#ifdef __cplusplus -} -#endif +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +extern char *IOSGetPrefPath(); + +#ifdef __cplusplus +} +#endif diff --git a/Source/platform/switch/asio/include/errno.h b/Source/platform/switch/asio/include/errno.h index 209a0eae1..3ccfdf983 100644 --- a/Source/platform/switch/asio/include/errno.h +++ b/Source/platform/switch/asio/include/errno.h @@ -1,5 +1,5 @@ -#pragma once - -#include_next - -#define ESHUTDOWN (__ELASTERROR + 1) +#pragma once + +#include_next + +#define ESHUTDOWN (__ELASTERROR + 1) diff --git a/Source/platform/switch/asio/include/net/if.h b/Source/platform/switch/asio/include/net/if.h index 21ae962ff..cf074c29f 100644 --- a/Source/platform/switch/asio/include/net/if.h +++ b/Source/platform/switch/asio/include/net/if.h @@ -1,24 +1,24 @@ -#ifndef _NET_IF_H -#define _NET_IF_H 1 - -#define IF_NAMESIZE 16 - -struct if_nameindex { - unsigned int if_index; - char *if_name; -}; - -#ifdef __cplusplus -extern "C" { -#endif - -unsigned int if_nametoindex(const char *__ifname); -char *if_indextoname(unsigned int __ifindex, char *__ifname); -struct if_nameindex *if_nameindex(); -void if_freenameindex(struct if_nameindex *__ptr); - -#ifdef __cplusplus -} -#endif - -#endif +#ifndef _NET_IF_H +#define _NET_IF_H 1 + +#define IF_NAMESIZE 16 + +struct if_nameindex { + unsigned int if_index; + char *if_name; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +unsigned int if_nametoindex(const char *__ifname); +char *if_indextoname(unsigned int __ifindex, char *__ifname); +struct if_nameindex *if_nameindex(); +void if_freenameindex(struct if_nameindex *__ptr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Source/platform/switch/asio/include/netinet/in.h b/Source/platform/switch/asio/include/netinet/in.h index 9081497aa..88171ded5 100644 --- a/Source/platform/switch/asio/include/netinet/in.h +++ b/Source/platform/switch/asio/include/netinet/in.h @@ -1,8 +1,8 @@ -#pragma once - -#include_next - -struct ipv6_mreq { - struct in6_addr ipv6mr_multiaddr; - unsigned ipv6mr_interface; -}; +#pragma once + +#include_next + +struct ipv6_mreq { + struct in6_addr ipv6mr_multiaddr; + unsigned ipv6mr_interface; +}; diff --git a/Source/platform/switch/asio/include/sys/uio.h b/Source/platform/switch/asio/include/sys/uio.h index f70f6dba6..303e6d158 100644 --- a/Source/platform/switch/asio/include/sys/uio.h +++ b/Source/platform/switch/asio/include/sys/uio.h @@ -1,10 +1,10 @@ -#ifndef _SYS_UIO_H -#define _SYS_UIO_H 1 - -#include -#include - -ssize_t readv(int __fd, const struct iovec *__iovec, int __count); -ssize_t writev(int __fd, const struct iovec *__iovec, int __count); - -#endif +#ifndef _SYS_UIO_H +#define _SYS_UIO_H 1 + +#include +#include + +ssize_t readv(int __fd, const struct iovec *__iovec, int __count); +ssize_t writev(int __fd, const struct iovec *__iovec, int __count); + +#endif diff --git a/Source/platform/switch/asio/include/sys/un.h b/Source/platform/switch/asio/include/sys/un.h index d3463f63e..8386010c7 100644 --- a/Source/platform/switch/asio/include/sys/un.h +++ b/Source/platform/switch/asio/include/sys/un.h @@ -1,16 +1,16 @@ -#ifndef _SYS_UN_H -#define _SYS_UN_H 1 - -#include - -#ifndef _SA_FAMILY_T_DECLARED -typedef __sa_family_t sa_family_t; -#define _SA_FAMILY_T_DECLARED -#endif - -struct sockaddr_un { - sa_family_t sun_family; - char sun_path[108]; -}; - -#endif +#ifndef _SYS_UN_H +#define _SYS_UN_H 1 + +#include + +#ifndef _SA_FAMILY_T_DECLARED +typedef __sa_family_t sa_family_t; +#define _SA_FAMILY_T_DECLARED +#endif + +struct sockaddr_un { + sa_family_t sun_family; + char sun_path[108]; +}; + +#endif diff --git a/Source/platform/switch/random.hpp b/Source/platform/switch/random.hpp index 5acd4d92b..151e175aa 100644 --- a/Source/platform/switch/random.hpp +++ b/Source/platform/switch/random.hpp @@ -1,2 +1,2 @@ -#pragma once -void randombytes_switchrandom_init(); +#pragma once +void randombytes_switchrandom_init(); diff --git a/Source/player.cpp b/Source/player.cpp index 42cc9bb8a..b0e9a7222 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -16,7 +16,7 @@ #include -#include "control.h" +#include "control/control.hpp" #include "controls/control_mode.hpp" #include "controls/plrctrls.h" #include "cursor.h" @@ -41,6 +41,7 @@ #include "levels/trigs.h" #include "lighting.h" #include "loadsave.h" +#include "lua/lua_global.hpp" #include "minitext.h" #include "missiles.h" #include "monster.h" @@ -49,7 +50,6 @@ #include "options.h" #include "player.h" #include "qol/autopickup.h" -#include "qol/floatingnumbers.h" #include "qol/stash.h" #include "spells.h" #include "stores.h" @@ -2108,7 +2108,7 @@ void LoadPlrGFX(Player &player, player_graphic graphic) return; const HeroClass cls = GetPlayerSpriteClass(player._pClass); - const PlayerWeaponGraphic animWeaponId = GetPlayerWeaponGraphic(graphic, static_cast(player._pgfxnum & 0xF)); + PlayerWeaponGraphic animWeaponId = GetPlayerWeaponGraphic(graphic, static_cast(player._pgfxnum & 0xF)); const PlayerSpriteData &spriteData = GetPlayerSpriteDataForClass(cls); const char *path = spriteData.classPath.c_str(); @@ -2145,8 +2145,8 @@ void LoadPlrGFX(Player &player, player_graphic graphic) szCel = "qm"; break; case player_graphic::Death: - if (animWeaponId != PlayerWeaponGraphic::Unarmed) - return; + // Only one Death animation exists, for unarmed characters + animWeaponId = PlayerWeaponGraphic::Unarmed; szCel = "dt"; break; case player_graphic::Block: @@ -2442,6 +2442,8 @@ void Player::_addExperience(uint32_t experience, int levelDelta) clampedExp = std::min({ clampedExp, /* level 1-5: */ getNextExperienceThreshold() / 20U, /* level 6-50: */ 200U * getCharacterLevel() }); } + LuaEvent("OnPlayerGainExperience", this, clampedExp); + const uint32_t maxExperience = GetNextExperienceThresholdForLevel(getMaxCharacterLevel()); // ensure we only add enough experience to reach the max experience cap so we don't overflow @@ -2822,7 +2824,7 @@ void ApplyPlrDamage(DamageType damageType, Player &player, int dam, int minHP /* { int totalDamage = (dam << 6) + frac; if (&player == MyPlayer && !player.hasNoLife()) { - AddFloatingNumber(damageType, player, totalDamage); + LuaEvent("OnPlayerTakeDamage", &player, totalDamage, static_cast(damageType)); } if (totalDamage > 0 && player.pManaShield && HasNoneOf(player._pIFlags, ItemSpecialEffect::NoMana)) { const uint8_t manaShieldLevel = player._pSplLvl[static_cast(SpellID::ManaShield)]; @@ -3206,7 +3208,7 @@ void CheckPlrSpell(bool isShiftHeld, SpellID spellID, SpellType spellType) } else if (pcursmonst != -1 && !isShiftHeld) { LastPlayerAction = PlayerActionType::SpellMonsterTarget; NetSendCmdParam4(true, CMD_SPELLID, pcursmonst, static_cast(spellID), static_cast(spellType), spellFrom); - } else if (PlayerUnderCursor != nullptr && !isShiftHeld && !myPlayer.friendlyMode) { + } else if (PlayerUnderCursor != nullptr && !PlayerUnderCursor->hasNoLife() && !isShiftHeld && !myPlayer.friendlyMode) { LastPlayerAction = PlayerActionType::SpellPlayerTarget; NetSendCmdParam4(true, CMD_SPELLPID, PlayerUnderCursor->getId(), static_cast(spellID), static_cast(spellType), spellFrom); } else { diff --git a/Source/player.h b/Source/player.h index 1758e2207..5cc99ca19 100644 --- a/Source/player.h +++ b/Source/player.h @@ -26,8 +26,8 @@ #include "levels/dun_tile.hpp" #include "levels/gendung.h" #include "multi.h" -#include "playerdat.hpp" -#include "spelldat.h" +#include "tables/playerdat.hpp" +#include "tables/spelldat.h" #include "utils/attributes.h" #include "utils/enum_traits.h" #include "utils/is_of.hpp" diff --git a/Source/plrmsg.cpp b/Source/plrmsg.cpp index 9d45ac84e..f879d675a 100644 --- a/Source/plrmsg.cpp +++ b/Source/plrmsg.cpp @@ -17,7 +17,7 @@ #include -#include "control.h" +#include "control/control.hpp" #include "engine/render/primitive_render.hpp" #include "engine/render/text_render.hpp" #include "inv.h" diff --git a/Source/portal.cpp b/Source/portal.cpp index 0f0769cf9..4d02adda5 100644 --- a/Source/portal.cpp +++ b/Source/portal.cpp @@ -6,10 +6,10 @@ #include "portal.h" #include "lighting.h" -#include "misdat.h" #include "missiles.h" #include "multi.h" #include "player.h" +#include "tables/misdat.h" namespace devilution { diff --git a/Source/qol/chatlog.cpp b/Source/qol/chatlog.cpp index a2d1dbb03..c73a25df5 100644 --- a/Source/qol/chatlog.cpp +++ b/Source/qol/chatlog.cpp @@ -14,7 +14,7 @@ #include "DiabloUI/ui_flags.hpp" #include "automap.h" #include "chatlog.h" -#include "control.h" +#include "control/control.hpp" #include "diablo_msg.hpp" #include "doom.h" #include "engine/render/text_render.hpp" diff --git a/Source/qol/floatingnumbers.cpp b/Source/qol/floatingnumbers.cpp index a6a93964d..0f32bebc9 100644 --- a/Source/qol/floatingnumbers.cpp +++ b/Source/qol/floatingnumbers.cpp @@ -28,9 +28,7 @@ struct FloatingNumber { uint32_t time; uint32_t lastMerge; UiFlags style; - DamageType type; - int value; - size_t index; + int id; bool reverseDirection; }; @@ -47,142 +45,45 @@ void ClearExpiredNumbers() } } -GameFontTables GetGameFontSizeByDamage(int value) +GameFontTables GetGameFontSize(UiFlags flags) { - value >>= 6; - if (value >= 300) + if (HasAnyOf(flags, UiFlags::FontSize30)) return GameFont30; - if (value >= 100) + if (HasAnyOf(flags, UiFlags::FontSize24)) return GameFont24; return GameFont12; } -UiFlags GetFontSizeByDamage(int value) -{ - value >>= 6; - if (value >= 300) - return UiFlags::FontSize30; - if (value >= 100) - return UiFlags::FontSize24; - return UiFlags::FontSize12; -} - -void UpdateFloatingData(FloatingNumber &num) -{ - if (num.value > 0 && num.value < 64) { - num.text = fmt::format("{:.2f}", num.value / 64.0); - } else { - num.text = StrCat(num.value >> 6); - } - - num.style &= ~(UiFlags::FontSize12 | UiFlags::FontSize24 | UiFlags::FontSize30); - num.style |= GetFontSizeByDamage(num.value); - - switch (num.type) { - case DamageType::Physical: - num.style |= UiFlags::ColorGold; - break; - case DamageType::Fire: - num.style |= UiFlags::ColorUiSilver; // UiSilver appears dark red ingame - break; - case DamageType::Lightning: - num.style |= UiFlags::ColorBlue; - break; - case DamageType::Magic: - num.style |= UiFlags::ColorOrange; - break; - case DamageType::Acid: - num.style |= UiFlags::ColorYellow; - break; - } -} +} // namespace -void AddFloatingNumber(Point pos, Displacement offset, DamageType type, int value, size_t index, bool damageToPlayer) +void AddFloatingNumber(Point pos, Displacement offset, std::string text, UiFlags style, int id, bool reverseDirection) { - // 45 deg angles to avoid jitter caused by px alignment - const Displacement goodAngles[] = { - { 0, -140 }, - { 100, -100 }, - { -100, -100 }, - }; - Displacement endOffset; - if (*GetOptions().Gameplay.enableFloatingNumbers == FloatingNumbers::Random) { - endOffset = goodAngles[rand() % 3]; - } else if (*GetOptions().Gameplay.enableFloatingNumbers == FloatingNumbers::Vertical) { - endOffset = goodAngles[0]; - } - - if (damageToPlayer) - endOffset = -endOffset; + if (!reverseDirection) + endOffset = { 0, -140 }; + else + endOffset = { 0, 140 }; for (auto &num : FloatingQueue) { - if (num.reverseDirection == damageToPlayer && num.type == type && num.index == index && (SDL_GetTicks() - static_cast(num.lastMerge)) <= 100) { - num.value += value; + if (id != 0 && num.id == id && (SDL_GetTicks() - static_cast(num.lastMerge)) <= 100) { + num.text = text; num.lastMerge = SDL_GetTicks(); - UpdateFloatingData(num); + num.style = style; + num.startPos = pos; return; } } FloatingNumber num { - pos, offset, endOffset, "", + pos, offset, endOffset, text, static_cast(SDL_GetTicks() + 2500), static_cast(SDL_GetTicks()), - UiFlags::Outlined, type, value, index, damageToPlayer + style | UiFlags::Outlined, id, reverseDirection }; - UpdateFloatingData(num); FloatingQueue.push_back(num); } -} // namespace - -void AddFloatingNumber(DamageType damageType, const Monster &monster, int damage) -{ - if (*GetOptions().Gameplay.enableFloatingNumbers == FloatingNumbers::Off) - return; - - Displacement offset = {}; - if (monster.isWalking()) { - offset = GetOffsetForWalking(monster.animInfo, monster.direction); - if (monster.mode == MonsterMode::MoveSideways) { - if (monster.direction == Direction::West) - offset -= Displacement { 64, 0 }; - else - offset += Displacement { 64, 0 }; - } - } - if (monster.animInfo.sprites) { - const ClxSprite sprite = monster.animInfo.currentSprite(); - offset.deltaY -= sprite.height() / 2; - } - - AddFloatingNumber(monster.position.tile, offset, damageType, damage, monster.getId(), false); -} - -void AddFloatingNumber(DamageType damageType, const Player &player, int damage) -{ - if (*GetOptions().Gameplay.enableFloatingNumbers == FloatingNumbers::Off) - return; - - Displacement offset = {}; - if (player.isWalking()) { - offset = GetOffsetForWalking(player.AnimInfo, player._pdir); - if (player._pmode == PM_WALK_SIDEWAYS) { - if (player._pdir == Direction::West) - offset -= Displacement { 64, 0 }; - else - offset += Displacement { 64, 0 }; - } - } - - AddFloatingNumber(player.position.tile, offset, damageType, damage, player.getId(), true); -} - void DrawFloatingNumbers(const Surface &out, Point viewPosition, Displacement offset) { - if (*GetOptions().Gameplay.enableFloatingNumbers == FloatingNumbers::Off) - return; - for (auto &floatingNum : FloatingQueue) { Displacement worldOffset = viewPosition - floatingNum.startPos; worldOffset = worldOffset.worldToScreen() + offset + Displacement { TILE_WIDTH / 2, -TILE_HEIGHT / 2 } + floatingNum.startOffset; @@ -193,7 +94,7 @@ void DrawFloatingNumbers(const Surface &out, Point viewPosition, Displacement of Point screenPosition { worldOffset.deltaX, worldOffset.deltaY }; - const int lineWidth = GetLineWidth(floatingNum.text, GetGameFontSizeByDamage(floatingNum.value)); + const int lineWidth = GetLineWidth(floatingNum.text, GetGameFontSize(floatingNum.style)); screenPosition.x -= lineWidth / 2; const uint32_t timeLeft = floatingNum.time - SDL_GetTicks(); const float mul = 1 - (timeLeft / 2500.0f); diff --git a/Source/qol/floatingnumbers.h b/Source/qol/floatingnumbers.h index 660ae9d64..696cacedc 100644 --- a/Source/qol/floatingnumbers.h +++ b/Source/qol/floatingnumbers.h @@ -5,15 +5,16 @@ */ #pragma once +#include + +#include "DiabloUI/ui_flags.hpp" +#include "engine/displacement.hpp" #include "engine/point.hpp" -#include "misdat.h" -#include "monster.h" -#include "player.h" +#include "engine/surface.hpp" namespace devilution { -void AddFloatingNumber(DamageType damageType, const Monster &monster, int damage); -void AddFloatingNumber(DamageType damageType, const Player &player, int damage); +void AddFloatingNumber(Point pos, Displacement offset, std::string text, UiFlags style, int id = 0, bool reverseDirection = false); void DrawFloatingNumbers(const Surface &out, Point viewPosition, Displacement offset); void ClearFloatingNumbers(); diff --git a/Source/qol/itemlabels.cpp b/Source/qol/itemlabels.cpp index 6f73c8167..838a76baf 100644 --- a/Source/qol/itemlabels.cpp +++ b/Source/qol/itemlabels.cpp @@ -9,7 +9,7 @@ #include -#include "control.h" +#include "control/control.hpp" #include "cursor.h" #include "engine/point.hpp" #include "engine/render/clx_render.hpp" diff --git a/Source/qol/monhealthbar.cpp b/Source/qol/monhealthbar.cpp index 456f3a8ca..1be073a89 100644 --- a/Source/qol/monhealthbar.cpp +++ b/Source/qol/monhealthbar.cpp @@ -9,7 +9,7 @@ #include -#include "control.h" +#include "control/control.hpp" #include "cursor.h" #include "engine/clx_sprite.hpp" #include "engine/load_clx.hpp" diff --git a/Source/qol/stash.cpp b/Source/qol/stash.cpp index b7c395508..2f7d427ff 100644 --- a/Source/qol/stash.cpp +++ b/Source/qol/stash.cpp @@ -13,7 +13,7 @@ #include #include "DiabloUI/text_input.hpp" -#include "control.h" +#include "control/control.hpp" #include "controls/plrctrls.h" #include "cursor.h" #include "engine/clx_sprite.hpp" diff --git a/Source/qol/xpbar.cpp b/Source/qol/xpbar.cpp index 271dda89d..fa8fdc989 100644 --- a/Source/qol/xpbar.cpp +++ b/Source/qol/xpbar.cpp @@ -10,7 +10,7 @@ #include -#include "control.h" +#include "control/control.hpp" #include "engine/clx_sprite.hpp" #include "engine/load_clx.hpp" #include "engine/point.hpp" @@ -18,7 +18,7 @@ #include "engine/render/primitive_render.hpp" #include "game_mode.hpp" #include "options.h" -#include "playerdat.hpp" +#include "tables/playerdat.hpp" #include "utils/format_int.hpp" #include "utils/language.h" diff --git a/Source/quests.cpp b/Source/quests.cpp index c5442d4cc..32cf9cba5 100644 --- a/Source/quests.cpp +++ b/Source/quests.cpp @@ -10,7 +10,7 @@ #include #include "DiabloUI/ui_flags.hpp" -#include "control.h" +#include "control/control.hpp" #include "cursor.h" #include "data/file.hpp" #include "data/record_reader.hpp" @@ -29,7 +29,7 @@ #include "options.h" #include "panels/ui_panels.hpp" #include "stores.h" -#include "townerdat.hpp" +#include "tables/townerdat.hpp" #include "towners.h" #include "utils/endian_swap.hpp" #include "utils/is_of.hpp" diff --git a/Source/quests.h b/Source/quests.h index 5a47bc8cc..549584e1d 100644 --- a/Source/quests.h +++ b/Source/quests.h @@ -12,9 +12,9 @@ #include "engine/surface.hpp" #include "levels/gendung.h" #include "monster.h" -#include "objdat.h" #include "panels/info_box.hpp" -#include "textdat.h" +#include "tables/objdat.h" +#include "tables/textdat.h" #include "utils/attributes.h" namespace devilution { diff --git a/Source/quests/validation.cpp b/Source/quests/validation.cpp index 5bb9e786a..fd4df6b9d 100644 --- a/Source/quests/validation.cpp +++ b/Source/quests/validation.cpp @@ -8,9 +8,9 @@ #include -#include "objdat.h" #include "quests.h" -#include "textdat.h" +#include "tables/objdat.h" +#include "tables/textdat.h" #include "utils/is_of.hpp" namespace devilution { diff --git a/Source/quests/validation.hpp b/Source/quests/validation.hpp index 205458add..2e11f121e 100644 --- a/Source/quests/validation.hpp +++ b/Source/quests/validation.hpp @@ -7,8 +7,8 @@ #include -#include "objdat.h" #include "quests.h" +#include "tables/objdat.h" namespace devilution { diff --git a/Source/spells.cpp b/Source/spells.cpp index 06e4ec23a..476e2013b 100644 --- a/Source/spells.cpp +++ b/Source/spells.cpp @@ -5,7 +5,7 @@ */ #include "spells.h" -#include "control.h" +#include "control/control.hpp" #include "cursor.h" #ifdef _DEBUG #include "debug.h" @@ -232,13 +232,21 @@ void CastSpell(Player &player, SpellID spl, WorldTilePosition src, WorldTilePosi } } -void DoResurrect(Player &player, Player &target) +void SpawnResurrectBeam(Player &caster, Player &target) { - AddMissile(target.position.tile, target.position.tile, Direction::South, MissileID::ResurrectBeam, TARGET_MONSTERS, player, 0, 0); - - if (!target.hasNoLife()) - return; + AddMissile( + target.position.tile, + target.position.tile, + Direction::South, + MissileID::ResurrectBeam, + TARGET_MONSTERS, + caster.getId(), + 0, + 0); +} +void ApplyResurrect(Player &target) +{ if (&target == MyPlayer) { MyPlayerIsDead = false; gamemenu_off(); diff --git a/Source/spells.h b/Source/spells.h index 81064f9a6..01b72e8b4 100644 --- a/Source/spells.h +++ b/Source/spells.h @@ -36,12 +36,8 @@ SpellCheckResult CheckSpell(const Player &player, SpellID sn, SpellType st, bool */ void EnsureValidReadiedSpell(Player &player); void CastSpell(Player &player, SpellID spl, WorldTilePosition src, WorldTilePosition dst, int spllvl); - -/** - * @param pnum player index - * @param rid target player index - */ -void DoResurrect(Player &player, Player &target); +void SpawnResurrectBeam(Player &caster, Player &target); +void ApplyResurrect(Player &target); void DoHealOther(const Player &caster, Player &target); int GetSpellBookLevel(SpellID s); int GetSpellStaffLevel(SpellID s); diff --git a/Source/stores.cpp b/Source/stores.cpp index 0ddabdba0..70c468f7c 100644 --- a/Source/stores.cpp +++ b/Source/stores.cpp @@ -21,12 +21,13 @@ #include "engine/render/text_render.hpp" #include "engine/trn.hpp" #include "game_mode.hpp" +#include "lua/lua_event.hpp" #include "minitext.h" #include "multi.h" #include "options.h" #include "panels/info_box.hpp" #include "qol/stash.h" -#include "townerdat.hpp" +#include "tables/townerdat.hpp" #include "towners.h" #include "utils/format_int.hpp" #include "utils/language.h" @@ -653,24 +654,8 @@ void StartSmithRepair() AddItemListBackButton(); } -void FillManaPlayer() -{ - if (!*GetOptions().Gameplay.adriaRefillsMana) - return; - - Player &myPlayer = *MyPlayer; - - if (myPlayer._pMana != myPlayer._pMaxMana) { - PlaySFX(SfxID::CastHealing); - } - myPlayer._pMana = myPlayer._pMaxMana; - myPlayer._pManaBase = myPlayer._pMaxManaBase; - RedrawComponent(PanelDrawComponent::Mana); -} - void StartWitch() { - FillManaPlayer(); IsTextFullSize = false; HasScrollbar = false; AddSText(0, 2, _("Witch's shack"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); @@ -2215,6 +2200,37 @@ void StartStore(TalkID s) CloseGoldDrop(); ClearSText(0, NumStoreLines); ReleaseStoreBtn(); + + // Fire StoreOpened Lua event for main store entries + switch (s) { + case TalkID::Smith: + LuaEvent("StoreOpened", "griswold"); + break; + case TalkID::Witch: + LuaEvent("StoreOpened", "adria"); + break; + case TalkID::Boy: + LuaEvent("StoreOpened", "wirt"); + break; + case TalkID::Healer: + LuaEvent("StoreOpened", "pepin"); + break; + case TalkID::Storyteller: + LuaEvent("StoreOpened", "cain"); + break; + case TalkID::Tavern: + LuaEvent("StoreOpened", "ogden"); + break; + case TalkID::Drunk: + LuaEvent("StoreOpened", "farnham"); + break; + case TalkID::Barmaid: + LuaEvent("StoreOpened", "gillian"); + break; + default: + break; + } + switch (s) { case TalkID::Smith: StartSmith(); diff --git a/Source/stores.h b/Source/stores.h index a6471f4b0..fc38e00db 100644 --- a/Source/stores.h +++ b/Source/stores.h @@ -9,7 +9,7 @@ #include #include "DiabloUI/ui_flags.hpp" -#include "control.h" +#include "control/control.hpp" #include "engine/clx_sprite.hpp" #include "engine/surface.hpp" #include "game_mode.hpp" diff --git a/Source/itemdat.cpp b/Source/tables/itemdat.cpp similarity index 97% rename from Source/itemdat.cpp rename to Source/tables/itemdat.cpp index 619193765..8c26c7b4d 100644 --- a/Source/itemdat.cpp +++ b/Source/tables/itemdat.cpp @@ -4,7 +4,7 @@ * Implementation of all item data. */ -#include "itemdat.h" +#include "tables/itemdat.h" #include #include @@ -15,7 +15,7 @@ #include "data/iterators.hpp" #include "data/record_reader.hpp" #include "lua/lua_global.hpp" -#include "spelldat.h" +#include "tables/spelldat.h" #include "utils/str_cat.hpp" namespace devilution { diff --git a/Source/itemdat.h b/Source/tables/itemdat.h similarity index 95% rename from Source/itemdat.h rename to Source/tables/itemdat.h index 146516fcd..77b8bcf1d 100644 --- a/Source/itemdat.h +++ b/Source/tables/itemdat.h @@ -13,8 +13,8 @@ #include #include -#include "objdat.h" -#include "spelldat.h" +#include "tables/objdat.h" +#include "tables/spelldat.h" namespace devilution { diff --git a/Source/misdat.cpp b/Source/tables/misdat.cpp similarity index 97% rename from Source/misdat.cpp rename to Source/tables/misdat.cpp index ba1fdf1a5..620fa1e4c 100644 --- a/Source/misdat.cpp +++ b/Source/tables/misdat.cpp @@ -3,7 +3,7 @@ * * Implementation of data related to missiles. */ -#include "misdat.h" +#include "tables/misdat.h" #include #include @@ -25,7 +25,7 @@ #include "missiles.h" #include "mpq/mpq_common.hpp" #include "sound_effect_enums.h" -#include "spelldat.h" +#include "tables/spelldat.h" #include "utils/file_name_generator.hpp" #include "utils/status_macros.hpp" #include "utils/str_cat.hpp" diff --git a/Source/misdat.h b/Source/tables/misdat.h similarity index 94% rename from Source/misdat.h rename to Source/tables/misdat.h index 3c2c08d3d..eb4fb7452 100644 --- a/Source/misdat.h +++ b/Source/tables/misdat.h @@ -14,7 +14,7 @@ #include "effects.h" #include "engine/clx_sprite.hpp" -#include "spelldat.h" +#include "tables/spelldat.h" #include "utils/enum_traits.h" namespace devilution { diff --git a/Source/monstdat.cpp b/Source/tables/monstdat.cpp similarity index 96% rename from Source/monstdat.cpp rename to Source/tables/monstdat.cpp index c948b39dd..33f4799e3 100644 --- a/Source/monstdat.cpp +++ b/Source/tables/monstdat.cpp @@ -3,7 +3,7 @@ * * Implementation of all monster data. */ -#include "monstdat.h" +#include "tables/monstdat.h" #include #include @@ -26,7 +26,7 @@ #include "items.h" #include "lua/lua_global.hpp" #include "monster.h" -#include "textdat.h" +#include "tables/textdat.h" #include "utils/language.h" namespace devilution { diff --git a/Source/monstdat.h b/Source/tables/monstdat.h similarity index 94% rename from Source/monstdat.h rename to Source/tables/monstdat.h index 43c6bb55c..8a7b1a7cd 100644 --- a/Source/monstdat.h +++ b/Source/tables/monstdat.h @@ -13,7 +13,7 @@ #include #include "cursor.h" -#include "textdat.h" +#include "tables/textdat.h" namespace devilution { diff --git a/Source/objdat.cpp b/Source/tables/objdat.cpp similarity index 95% rename from Source/objdat.cpp rename to Source/tables/objdat.cpp index 9b54109b5..e1c3c79d8 100644 --- a/Source/objdat.cpp +++ b/Source/tables/objdat.cpp @@ -3,7 +3,7 @@ * * Implementation of all object data. */ -#include "objdat.h" +#include "tables/objdat.h" #include #include diff --git a/Source/objdat.h b/Source/tables/objdat.h similarity index 100% rename from Source/objdat.h rename to Source/tables/objdat.h diff --git a/Source/playerdat.cpp b/Source/tables/playerdat.cpp similarity index 96% rename from Source/playerdat.cpp rename to Source/tables/playerdat.cpp index 3a7813d15..5c6b63d47 100644 --- a/Source/playerdat.cpp +++ b/Source/tables/playerdat.cpp @@ -4,7 +4,7 @@ * Implementation of all player data. */ -#include "playerdat.hpp" +#include "tables/playerdat.hpp" #include #include @@ -22,7 +22,7 @@ #include "data/value_reader.hpp" #include "items.h" #include "player.h" -#include "textdat.h" +#include "tables/textdat.h" #include "utils/language.h" #include "utils/static_vector.hpp" #include "utils/str_cat.hpp" diff --git a/Source/playerdat.hpp b/Source/tables/playerdat.hpp similarity index 95% rename from Source/playerdat.hpp rename to Source/tables/playerdat.hpp index 9af28ccd0..9f2bf68e3 100644 --- a/Source/playerdat.hpp +++ b/Source/tables/playerdat.hpp @@ -9,8 +9,8 @@ #include #include "effects.h" -#include "itemdat.h" -#include "spelldat.h" +#include "tables/itemdat.h" +#include "tables/spelldat.h" namespace devilution { diff --git a/Source/spelldat.cpp b/Source/tables/spelldat.cpp similarity index 97% rename from Source/spelldat.cpp rename to Source/tables/spelldat.cpp index 5e61a5137..501ae1561 100644 --- a/Source/spelldat.cpp +++ b/Source/tables/spelldat.cpp @@ -3,7 +3,7 @@ * * Implementation of all spell data. */ -#include "spelldat.h" +#include "tables/spelldat.h" #include #include diff --git a/Source/spelldat.h b/Source/tables/spelldat.h similarity index 100% rename from Source/spelldat.h rename to Source/tables/spelldat.h diff --git a/Source/textdat.cpp b/Source/tables/textdat.cpp similarity index 95% rename from Source/textdat.cpp rename to Source/tables/textdat.cpp index 0b02604eb..849d70dc1 100644 --- a/Source/textdat.cpp +++ b/Source/tables/textdat.cpp @@ -3,7 +3,7 @@ * * Implementation of all dialog texts. */ -#include "textdat.h" +#include "tables/textdat.h" #include #include diff --git a/Source/textdat.h b/Source/tables/textdat.h similarity index 100% rename from Source/textdat.h rename to Source/tables/textdat.h diff --git a/Source/townerdat.cpp b/Source/tables/townerdat.cpp similarity index 96% rename from Source/townerdat.cpp rename to Source/tables/townerdat.cpp index 13b449749..9f7ab2c15 100644 --- a/Source/townerdat.cpp +++ b/Source/tables/townerdat.cpp @@ -1,245 +1,245 @@ -/** - * @file townerdat.cpp - * - * Implementation of towner data loading from TSV files. - */ -#include "townerdat.hpp" - -#include -#include -#include -#include -#include - -#include -#include - -#include "data/file.hpp" -#include "data/record_reader.hpp" - -namespace devilution { - -std::vector TownersDataEntries; -std::unordered_map<_talker_id, std::array<_speech_id, MAXQUESTS>> TownerQuestDialogTable; - -namespace { - -/** - * @brief Generic enum parser using magic_enum. - * @tparam EnumT The enum type to parse - * @param value The string representation of the enum value - * @return The parsed enum value, or an error message - */ -template -tl::expected ParseEnum(std::string_view value) -{ - const auto enumValueOpt = magic_enum::enum_cast(value); - if (enumValueOpt.has_value()) { - return enumValueOpt.value(); - } - return tl::make_unexpected("Unknown enum value"); -} - -/** - * @brief Parses a comma-separated list of values. - * @tparam T The output type - * @tparam Parser A callable that converts string_view to optional - * @param value The comma-separated string - * @param out Vector to store parsed values (cleared first) - * @param parser Function to parse individual tokens - */ -template -void ParseCommaSeparatedList(std::string_view value, std::vector &out, Parser parser) -{ - out.clear(); - if (value.empty()) return; - - size_t start = 0; - while (start < value.size()) { - size_t end = value.find(',', start); - if (end == std::string_view::npos) end = value.size(); - - std::string_view token = value.substr(start, end - start); - if (auto result = parser(token)) { - out.push_back(*result); - } - - start = end + 1; - } -} - -/** - * @brief Parses a comma-separated list of speech IDs. - */ -void ParseGossipTexts(std::string_view value, std::vector<_speech_id> &out) -{ - ParseCommaSeparatedList(value, out, [](std::string_view token) -> std::optional<_speech_id> { - if (auto result = ParseSpeechId(token); result.has_value()) { - return result.value(); - } - return std::nullopt; - }); -} - -/** - * @brief Parses a comma-separated list of integers for animation frame order. - */ -void ParseAnimOrder(std::string_view value, std::vector &out) -{ - ParseCommaSeparatedList(value, out, [](std::string_view token) -> std::optional { - int val = 0; - if (auto [ptr, ec] = std::from_chars(token.data(), token.data() + token.size(), val); ec == std::errc()) { - return static_cast(val); - } - return std::nullopt; - }); -} - -void LoadTownersFromFile() -{ - const std::string_view filename = "txtdata\\towners\\towners.tsv"; - DataFile dataFile = DataFile::loadOrDie(filename); - dataFile.skipHeaderOrDie(filename); - - TownersDataEntries.clear(); - TownersDataEntries.reserve(dataFile.numRecords()); - - for (DataFileRecord record : dataFile) { - RecordReader reader { record, filename }; - TownerDataEntry &entry = TownersDataEntries.emplace_back(); - - reader.read("type", entry.type, ParseEnum<_talker_id>); - reader.readString("name", entry.name); - reader.readInt("position_x", entry.position.x); - reader.readInt("position_y", entry.position.y); - reader.read("direction", entry.direction, ParseEnum); - reader.readInt("animWidth", entry.animWidth); - reader.readString("animPath", entry.animPath); - reader.readOptionalInt("animFrames", entry.animFrames); - reader.readOptionalInt("animDelay", entry.animDelay); - - std::string gossipStr; - reader.readString("gossipTexts", gossipStr); - ParseGossipTexts(gossipStr, entry.gossipTexts); - - std::string animOrderStr; - reader.readString("animOrder", animOrderStr); - ParseAnimOrder(animOrderStr, entry.animOrder); - } - - TownersDataEntries.shrink_to_fit(); -} - -void LoadQuestDialogFromFile() -{ - const std::string_view filename = "txtdata\\towners\\quest_dialog.tsv"; - DataFile dataFile = DataFile::loadOrDie(filename); - - // Initialize table (will be populated as we read rows) - TownerQuestDialogTable.clear(); - - // Parse header to find which quest columns exist - // Store the iterator to avoid temporary lifetime issues - auto headerIt = dataFile.begin(); - DataFileRecord headerRecord = *headerIt; - std::unordered_map columnMap; - unsigned columnIndex = 0; - for (DataFileField field : headerRecord) { - columnMap[std::string(field.value())] = columnIndex++; - } - - // Reset header position and skip for data reading - dataFile.resetHeader(); - dataFile.skipHeaderOrDie(filename); - - // Find the towner_type column index - if (!columnMap.contains("towner_type")) { - return; // Invalid file format - } - unsigned townerTypeColIndex = columnMap["towner_type"]; - - // Build quest column index map - std::unordered_map questColumnMap; - for (quest_id quest : magic_enum::enum_values()) { - if (quest == Q_INVALID || quest >= MAXQUESTS) continue; - - auto questName = std::string(magic_enum::enum_name(quest)); - if (columnMap.contains(questName)) { - questColumnMap[quest] = columnMap[questName]; - } - } - - // Read data rows - for (DataFileRecord record : dataFile) { - // Read all fields into a map keyed by column index for indexed access - std::unordered_map fields; - for (DataFileField field : record) { - fields[field.column()] = field.value(); - } - - // Read towner_type - if (!fields.contains(townerTypeColIndex)) { - continue; // Invalid row - } - - auto townerTypeResult = ParseEnum<_talker_id>(fields[townerTypeColIndex]); - if (!townerTypeResult.has_value()) { - continue; // Invalid towner type - } - _talker_id townerType = townerTypeResult.value(); - - // Initialize row if it doesn't exist, then get reference - auto [it, inserted] = TownerQuestDialogTable.try_emplace(townerType); - if (inserted) { - it->second.fill(TEXT_NONE); - } - auto &dialogRow = it->second; - - // Read quest columns that exist in this file - for (const auto &[quest, colIndex] : questColumnMap) { - if (!fields.contains(colIndex)) { - continue; // Column missing in this row - } - - auto speechResult = ParseSpeechId(fields[colIndex]); - if (speechResult.has_value()) { - dialogRow[quest] = speechResult.value(); - } - } - } -} - -} // namespace - -void LoadTownerData() -{ - LoadTownersFromFile(); - LoadQuestDialogFromFile(); -} - -_speech_id GetTownerQuestDialog(_talker_id type, quest_id quest) -{ - if (quest < 0 || quest >= MAXQUESTS) { - return TEXT_NONE; - } - auto it = TownerQuestDialogTable.find(type); - if (it == TownerQuestDialogTable.end()) { - return TEXT_NONE; - } - return it->second[quest]; -} - -void SetTownerQuestDialog(_talker_id type, quest_id quest, _speech_id speech) -{ - if (quest < 0 || quest >= MAXQUESTS) { - return; - } - // Initialize row if it doesn't exist - auto [it, inserted] = TownerQuestDialogTable.try_emplace(type); - if (inserted) { - it->second.fill(TEXT_NONE); - } - it->second[quest] = speech; -} - -} // namespace devilution +/** + * @file townerdat.cpp + * + * Implementation of towner data loading from TSV files. + */ +#include "tables/townerdat.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +#include "data/file.hpp" +#include "data/record_reader.hpp" + +namespace devilution { + +std::vector TownersDataEntries; +std::unordered_map<_talker_id, std::array<_speech_id, MAXQUESTS>> TownerQuestDialogTable; + +namespace { + +/** + * @brief Generic enum parser using magic_enum. + * @tparam EnumT The enum type to parse + * @param value The string representation of the enum value + * @return The parsed enum value, or an error message + */ +template +tl::expected ParseEnum(std::string_view value) +{ + const auto enumValueOpt = magic_enum::enum_cast(value); + if (enumValueOpt.has_value()) { + return enumValueOpt.value(); + } + return tl::make_unexpected("Unknown enum value"); +} + +/** + * @brief Parses a comma-separated list of values. + * @tparam T The output type + * @tparam Parser A callable that converts string_view to optional + * @param value The comma-separated string + * @param out Vector to store parsed values (cleared first) + * @param parser Function to parse individual tokens + */ +template +void ParseCommaSeparatedList(std::string_view value, std::vector &out, Parser parser) +{ + out.clear(); + if (value.empty()) return; + + size_t start = 0; + while (start < value.size()) { + size_t end = value.find(',', start); + if (end == std::string_view::npos) end = value.size(); + + std::string_view token = value.substr(start, end - start); + if (auto result = parser(token)) { + out.push_back(*result); + } + + start = end + 1; + } +} + +/** + * @brief Parses a comma-separated list of speech IDs. + */ +void ParseGossipTexts(std::string_view value, std::vector<_speech_id> &out) +{ + ParseCommaSeparatedList(value, out, [](std::string_view token) -> std::optional<_speech_id> { + if (auto result = ParseSpeechId(token); result.has_value()) { + return result.value(); + } + return std::nullopt; + }); +} + +/** + * @brief Parses a comma-separated list of integers for animation frame order. + */ +void ParseAnimOrder(std::string_view value, std::vector &out) +{ + ParseCommaSeparatedList(value, out, [](std::string_view token) -> std::optional { + int val = 0; + if (auto [ptr, ec] = std::from_chars(token.data(), token.data() + token.size(), val); ec == std::errc()) { + return static_cast(val); + } + return std::nullopt; + }); +} + +void LoadTownersFromFile() +{ + const std::string_view filename = "txtdata\\towners\\towners.tsv"; + DataFile dataFile = DataFile::loadOrDie(filename); + dataFile.skipHeaderOrDie(filename); + + TownersDataEntries.clear(); + TownersDataEntries.reserve(dataFile.numRecords()); + + for (DataFileRecord record : dataFile) { + RecordReader reader { record, filename }; + TownerDataEntry &entry = TownersDataEntries.emplace_back(); + + reader.read("type", entry.type, ParseEnum<_talker_id>); + reader.readString("name", entry.name); + reader.readInt("position_x", entry.position.x); + reader.readInt("position_y", entry.position.y); + reader.read("direction", entry.direction, ParseEnum); + reader.readInt("animWidth", entry.animWidth); + reader.readString("animPath", entry.animPath); + reader.readOptionalInt("animFrames", entry.animFrames); + reader.readOptionalInt("animDelay", entry.animDelay); + + std::string gossipStr; + reader.readString("gossipTexts", gossipStr); + ParseGossipTexts(gossipStr, entry.gossipTexts); + + std::string animOrderStr; + reader.readString("animOrder", animOrderStr); + ParseAnimOrder(animOrderStr, entry.animOrder); + } + + TownersDataEntries.shrink_to_fit(); +} + +void LoadQuestDialogFromFile() +{ + const std::string_view filename = "txtdata\\towners\\quest_dialog.tsv"; + DataFile dataFile = DataFile::loadOrDie(filename); + + // Initialize table (will be populated as we read rows) + TownerQuestDialogTable.clear(); + + // Parse header to find which quest columns exist + // Store the iterator to avoid temporary lifetime issues + auto headerIt = dataFile.begin(); + DataFileRecord headerRecord = *headerIt; + std::unordered_map columnMap; + unsigned columnIndex = 0; + for (DataFileField field : headerRecord) { + columnMap[std::string(field.value())] = columnIndex++; + } + + // Reset header position and skip for data reading + dataFile.resetHeader(); + dataFile.skipHeaderOrDie(filename); + + // Find the towner_type column index + if (!columnMap.contains("towner_type")) { + return; // Invalid file format + } + unsigned townerTypeColIndex = columnMap["towner_type"]; + + // Build quest column index map + std::unordered_map questColumnMap; + for (quest_id quest : magic_enum::enum_values()) { + if (quest == Q_INVALID || quest >= MAXQUESTS) continue; + + auto questName = std::string(magic_enum::enum_name(quest)); + if (columnMap.contains(questName)) { + questColumnMap[quest] = columnMap[questName]; + } + } + + // Read data rows + for (DataFileRecord record : dataFile) { + // Read all fields into a map keyed by column index for indexed access + std::unordered_map fields; + for (DataFileField field : record) { + fields[field.column()] = field.value(); + } + + // Read towner_type + if (!fields.contains(townerTypeColIndex)) { + continue; // Invalid row + } + + auto townerTypeResult = ParseEnum<_talker_id>(fields[townerTypeColIndex]); + if (!townerTypeResult.has_value()) { + continue; // Invalid towner type + } + _talker_id townerType = townerTypeResult.value(); + + // Initialize row if it doesn't exist, then get reference + auto [it, inserted] = TownerQuestDialogTable.try_emplace(townerType); + if (inserted) { + it->second.fill(TEXT_NONE); + } + auto &dialogRow = it->second; + + // Read quest columns that exist in this file + for (const auto &[quest, colIndex] : questColumnMap) { + if (!fields.contains(colIndex)) { + continue; // Column missing in this row + } + + auto speechResult = ParseSpeechId(fields[colIndex]); + if (speechResult.has_value()) { + dialogRow[quest] = speechResult.value(); + } + } + } +} + +} // namespace + +void LoadTownerData() +{ + LoadTownersFromFile(); + LoadQuestDialogFromFile(); +} + +_speech_id GetTownerQuestDialog(_talker_id type, quest_id quest) +{ + if (quest < 0 || quest >= MAXQUESTS) { + return TEXT_NONE; + } + auto it = TownerQuestDialogTable.find(type); + if (it == TownerQuestDialogTable.end()) { + return TEXT_NONE; + } + return it->second[quest]; +} + +void SetTownerQuestDialog(_talker_id type, quest_id quest, _speech_id speech) +{ + if (quest < 0 || quest >= MAXQUESTS) { + return; + } + // Initialize row if it doesn't exist + auto [it, inserted] = TownerQuestDialogTable.try_emplace(type); + if (inserted) { + it->second.fill(TEXT_NONE); + } + it->second[quest] = speech; +} + +} // namespace devilution diff --git a/Source/townerdat.hpp b/Source/tables/townerdat.hpp similarity index 93% rename from Source/townerdat.hpp rename to Source/tables/townerdat.hpp index 948ac885b..08a011b66 100644 --- a/Source/townerdat.hpp +++ b/Source/tables/townerdat.hpp @@ -1,69 +1,69 @@ -/** - * @file townerdat.hpp - * - * Interface for loading towner data from TSV files. - */ -#pragma once - -#include -#include -#include -#include - -#include "engine/direction.hpp" -#include "levels/gendung.h" -#include "objdat.h" -#include "textdat.h" -#include "towners.h" -#include "utils/attributes.h" - -namespace devilution { - -/** - * @brief Data for a single towner entry loaded from TSV. - */ -struct TownerDataEntry { - _talker_id type; // Parsed from TSV using magic_enum - std::string name; - Point position; - Direction direction; - uint16_t animWidth; - std::string animPath; - uint8_t animFrames; - int16_t animDelay; - std::vector<_speech_id> gossipTexts; - std::vector animOrder; -}; - -/** Contains the data for all towners loaded from TSV. */ -extern DVL_API_FOR_TEST std::vector TownersDataEntries; - -/** Contains the quest dialog table loaded from TSV. Indexed by [towner_type][quest_id]. */ -extern std::unordered_map<_talker_id, std::array<_speech_id, MAXQUESTS>> TownerQuestDialogTable; - -/** - * @brief Loads towner data from TSV files. - * - * This function loads data from: - * - txtdata/towners/towners.tsv - Main towner definitions - * - txtdata/towners/quest_dialog.tsv - Quest dialog mappings - */ -void LoadTownerData(); - -/** - * @brief Gets the quest dialog speech ID for a towner and quest combination. - * @param type The towner type - * @param quest The quest ID - * @return The speech ID for the dialog, or TEXT_NONE if not available - */ -_speech_id GetTownerQuestDialog(_talker_id type, quest_id quest); - -/** - * @brief Sets the quest dialog speech ID for a towner and quest combination. - * @param type The towner type - * @param quest The quest ID - * @param speech The speech ID to set - */ -void SetTownerQuestDialog(_talker_id type, quest_id quest, _speech_id speech); - -} // namespace devilution +/** + * @file townerdat.hpp + * + * Interface for loading towner data from TSV files. + */ +#pragma once + +#include +#include +#include +#include + +#include "engine/direction.hpp" +#include "levels/gendung.h" +#include "tables/objdat.h" +#include "tables/textdat.h" +#include "towners.h" +#include "utils/attributes.h" + +namespace devilution { + +/** + * @brief Data for a single towner entry loaded from TSV. + */ +struct TownerDataEntry { + _talker_id type; // Parsed from TSV using magic_enum + std::string name; + Point position; + Direction direction; + uint16_t animWidth; + std::string animPath; + uint8_t animFrames; + int16_t animDelay; + std::vector<_speech_id> gossipTexts; + std::vector animOrder; +}; + +/** Contains the data for all towners loaded from TSV. */ +extern DVL_API_FOR_TEST std::vector TownersDataEntries; + +/** Contains the quest dialog table loaded from TSV. Indexed by [towner_type][quest_id]. */ +extern std::unordered_map<_talker_id, std::array<_speech_id, MAXQUESTS>> TownerQuestDialogTable; + +/** + * @brief Loads towner data from TSV files. + * + * This function loads data from: + * - txtdata/towners/towners.tsv - Main towner definitions + * - txtdata/towners/quest_dialog.tsv - Quest dialog mappings + */ +void LoadTownerData(); + +/** + * @brief Gets the quest dialog speech ID for a towner and quest combination. + * @param type The towner type + * @param quest The quest ID + * @return The speech ID for the dialog, or TEXT_NONE if not available + */ +_speech_id GetTownerQuestDialog(_talker_id type, quest_id quest); + +/** + * @brief Sets the quest dialog speech ID for a towner and quest combination. + * @param type The towner type + * @param quest The quest ID + * @param speech The speech ID to set + */ +void SetTownerQuestDialog(_talker_id type, quest_id quest, _speech_id speech); + +} // namespace devilution diff --git a/Source/towners.cpp b/Source/towners.cpp index db0f77fb5..3c8a28d3b 100644 --- a/Source/towners.cpp +++ b/Source/towners.cpp @@ -13,8 +13,8 @@ #include "inv.h" #include "minitext.h" #include "stores.h" -#include "textdat.h" -#include "townerdat.hpp" +#include "tables/textdat.h" +#include "tables/townerdat.hpp" #include "utils/is_of.hpp" #include "utils/language.h" #include "utils/str_case.hpp" diff --git a/Source/translation_dummy.cpp b/Source/translation_dummy.cpp index d426a1153..e58af8d26 100644 --- a/Source/translation_dummy.cpp +++ b/Source/translation_dummy.cpp @@ -1,1045 +1,1045 @@ -/** - * @file translation_dummy.cpp - * - * Do not edit this file manually, it is automatically generated - * and updated by the extract_translation_data.py script. - */ -#include "utils/language.h" - -namespace { - -const char *CLASS_WARRIOR_NAME = N_("Warrior"); -const char *CLASS_ROGUE_NAME = N_("Rogue"); -const char *CLASS_SORCERER_NAME = N_("Sorcerer"); -const char *CLASS_MONK_NAME = N_("Monk"); -const char *CLASS_BARD_NAME = N_("Bard"); -const char *CLASS_BARBARIAN_NAME = N_("Barbarian"); -const char *MT_NZOMBIE_NAME = P_("monster", "Zombie"); -const char *MT_BZOMBIE_NAME = P_("monster", "Ghoul"); -const char *MT_GZOMBIE_NAME = P_("monster", "Rotting Carcass"); -const char *MT_YZOMBIE_NAME = P_("monster", "Black Death"); -const char *MT_RFALLSP_NAME = P_("monster", "Fallen One"); -const char *MT_DFALLSP_NAME = P_("monster", "Carver"); -const char *MT_YFALLSP_NAME = P_("monster", "Devil Kin"); -const char *MT_BFALLSP_NAME = P_("monster", "Dark One"); -const char *MT_WSKELAX_NAME = P_("monster", "Skeleton"); -const char *MT_TSKELAX_NAME = P_("monster", "Corpse Axe"); -const char *MT_RSKELAX_NAME = P_("monster", "Burning Dead"); -const char *MT_XSKELAX_NAME = P_("monster", "Horror"); -const char *MT_NSCAV_NAME = P_("monster", "Scavenger"); -const char *MT_BSCAV_NAME = P_("monster", "Plague Eater"); -const char *MT_WSCAV_NAME = P_("monster", "Shadow Beast"); -const char *MT_YSCAV_NAME = P_("monster", "Bone Gasher"); -const char *MT_TSKELBW_NAME = P_("monster", "Corpse Bow"); -const char *MT_WSKELSD_NAME = P_("monster", "Skeleton Captain"); -const char *MT_TSKELSD_NAME = P_("monster", "Corpse Captain"); -const char *MT_RSKELSD_NAME = P_("monster", "Burning Dead Captain"); -const char *MT_XSKELSD_NAME = P_("monster", "Horror Captain"); -const char *MT_INVILORD_NAME = P_("monster", "Invisible Lord"); -const char *MT_SNEAK_NAME = P_("monster", "Hidden"); -const char *MT_STALKER_NAME = P_("monster", "Stalker"); -const char *MT_UNSEEN_NAME = P_("monster", "Unseen"); -const char *MT_ILLWEAV_NAME = P_("monster", "Illusion Weaver"); -const char *MT_LRDSAYTR_NAME = P_("monster", "Satyr Lord"); -const char *MT_NGOATMC_NAME = P_("monster", "Flesh Clan"); -const char *MT_BGOATMC_NAME = P_("monster", "Stone Clan"); -const char *MT_RGOATMC_NAME = P_("monster", "Fire Clan"); -const char *MT_GGOATMC_NAME = P_("monster", "Night Clan"); -const char *MT_FIEND_NAME = P_("monster", "Fiend"); -const char *MT_BLINK_NAME = P_("monster", "Blink"); -const char *MT_GLOOM_NAME = P_("monster", "Gloom"); -const char *MT_FAMILIAR_NAME = P_("monster", "Familiar"); -const char *MT_NACID_NAME = P_("monster", "Acid Beast"); -const char *MT_RACID_NAME = P_("monster", "Poison Spitter"); -const char *MT_BACID_NAME = P_("monster", "Pit Beast"); -const char *MT_XACID_NAME = P_("monster", "Lava Maw"); -const char *MT_SKING_NAME = P_("monster", "Skeleton King"); -const char *MT_CLEAVER_NAME = P_("monster", "The Butcher"); -const char *MT_FAT_NAME = P_("monster", "Overlord"); -const char *MT_MUDMAN_NAME = P_("monster", "Mud Man"); -const char *MT_TOAD_NAME = P_("monster", "Toad Demon"); -const char *MT_FLAYED_NAME = P_("monster", "Flayed One"); -const char *MT_WYRM_NAME = P_("monster", "Wyrm"); -const char *MT_CAVSLUG_NAME = P_("monster", "Cave Slug"); -const char *MT_DVLWYRM_NAME = P_("monster", "Devil Wyrm"); -const char *MT_DEVOUR_NAME = P_("monster", "Devourer"); -const char *MT_NMAGMA_NAME = P_("monster", "Magma Demon"); -const char *MT_YMAGMA_NAME = P_("monster", "Blood Stone"); -const char *MT_BMAGMA_NAME = P_("monster", "Hell Stone"); -const char *MT_WMAGMA_NAME = P_("monster", "Lava Lord"); -const char *MT_HORNED_NAME = P_("monster", "Horned Demon"); -const char *MT_MUDRUN_NAME = P_("monster", "Mud Runner"); -const char *MT_FROSTC_NAME = P_("monster", "Frost Charger"); -const char *MT_OBLORD_NAME = P_("monster", "Obsidian Lord"); -const char *MT_BONEDMN_NAME = P_("monster", "oldboned"); -const char *MT_REDDTH_NAME = P_("monster", "Red Death"); -const char *MT_LTCHDMN_NAME = P_("monster", "Litch Demon"); -const char *MT_UDEDBLRG_NAME = P_("monster", "Undead Balrog"); -const char *MT_INCIN_NAME = P_("monster", "Incinerator"); -const char *MT_FLAMLRD_NAME = P_("monster", "Flame Lord"); -const char *MT_DOOMFIRE_NAME = P_("monster", "Doom Fire"); -const char *MT_HELLBURN_NAME = P_("monster", "Hell Burner"); -const char *MT_STORM_NAME = P_("monster", "Red Storm"); -const char *MT_RSTORM_NAME = P_("monster", "Storm Rider"); -const char *MT_STORML_NAME = P_("monster", "Storm Lord"); -const char *MT_MAEL_NAME = P_("monster", "Maelstrom"); -const char *MT_BIGFALL_NAME = P_("monster", "Devil Kin Brute"); -const char *MT_WINGED_NAME = P_("monster", "Winged-Demon"); -const char *MT_GARGOYLE_NAME = P_("monster", "Gargoyle"); -const char *MT_BLOODCLW_NAME = P_("monster", "Blood Claw"); -const char *MT_DEATHW_NAME = P_("monster", "Death Wing"); -const char *MT_MEGA_NAME = P_("monster", "Slayer"); -const char *MT_GUARD_NAME = P_("monster", "Guardian"); -const char *MT_VTEXLRD_NAME = P_("monster", "Vortex Lord"); -const char *MT_BALROG_NAME = P_("monster", "Balrog"); -const char *MT_NSNAKE_NAME = P_("monster", "Cave Viper"); -const char *MT_RSNAKE_NAME = P_("monster", "Fire Drake"); -const char *MT_BSNAKE_NAME = P_("monster", "Gold Viper"); -const char *MT_GSNAKE_NAME = P_("monster", "Azure Drake"); -const char *MT_NBLACK_NAME = P_("monster", "Black Knight"); -const char *MT_RTBLACK_NAME = P_("monster", "Doom Guard"); -const char *MT_BTBLACK_NAME = P_("monster", "Steel Lord"); -const char *MT_RBLACK_NAME = P_("monster", "Blood Knight"); -const char *MT_UNRAV_NAME = P_("monster", "The Shredded"); -const char *MT_HOLOWONE_NAME = P_("monster", "Hollow One"); -const char *MT_PAINMSTR_NAME = P_("monster", "Pain Master"); -const char *MT_REALWEAV_NAME = P_("monster", "Reality Weaver"); -const char *MT_SUCCUBUS_NAME = P_("monster", "Succubus"); -const char *MT_SNOWWICH_NAME = P_("monster", "Snow Witch"); -const char *MT_HLSPWN_NAME = P_("monster", "Hell Spawn"); -const char *MT_SOLBRNR_NAME = P_("monster", "Soul Burner"); -const char *MT_COUNSLR_NAME = P_("monster", "Counselor"); -const char *MT_MAGISTR_NAME = P_("monster", "Magistrate"); -const char *MT_CABALIST_NAME = P_("monster", "Cabalist"); -const char *MT_ADVOCATE_NAME = P_("monster", "Advocate"); -const char *MT_GOLEM_NAME = P_("monster", "Golem"); -const char *MT_DIABLO_NAME = P_("monster", "The Dark Lord"); -const char *MT_DARKMAGE_NAME = P_("monster", "The Arch-Litch Malignus"); -const char *GHARBAD_THE_WEAK_NAME = P_("monster", "Gharbad the Weak"); -const char *ZHAR_THE_MAD_NAME = P_("monster", "Zhar the Mad"); -const char *SNOTSPILL_NAME = P_("monster", "Snotspill"); -const char *ARCH_BISHOP_LAZARUS_NAME = P_("monster", "Arch-Bishop Lazarus"); -const char *RED_VEX_NAME = P_("monster", "Red Vex"); -const char *BLACK_JADE_NAME = P_("monster", "Black Jade"); -const char *LACHDANAN_NAME = P_("monster", "Lachdanan"); -const char *WARLORD_OF_BLOOD_NAME = P_("monster", "Warlord of Blood"); -const char *HORK_DEMON_NAME = P_("monster", "Hork Demon"); -const char *THE_DEFILER_NAME = P_("monster", "The Defiler"); -const char *NA_KRUL_NAME = P_("monster", "Na-Krul"); -const char *BONEHEAD_KEENAXE_NAME = P_("monster", "Bonehead Keenaxe"); -const char *BLADESKIN_THE_SLASHER_NAME = P_("monster", "Bladeskin the Slasher"); -const char *SOULPUS_NAME = P_("monster", "Soulpus"); -const char *PUKERAT_THE_UNCLEAN_NAME = P_("monster", "Pukerat the Unclean"); -const char *BONERIPPER_NAME = P_("monster", "Boneripper"); -const char *ROTFEAST_THE_HUNGRY_NAME = P_("monster", "Rotfeast the Hungry"); -const char *GUTSHANK_THE_QUICK_NAME = P_("monster", "Gutshank the Quick"); -const char *BROKENHEAD_BANGSHIELD_NAME = P_("monster", "Brokenhead Bangshield"); -const char *BONGO_NAME = P_("monster", "Bongo"); -const char *ROTCARNAGE_NAME = P_("monster", "Rotcarnage"); -const char *SHADOWBITE_NAME = P_("monster", "Shadowbite"); -const char *DEADEYE_NAME = P_("monster", "Deadeye"); -const char *MADEYE_THE_DEAD_NAME = P_("monster", "Madeye the Dead"); -const char *EL_CHUPACABRAS_NAME = P_("monster", "El Chupacabras"); -const char *SKULLFIRE_NAME = P_("monster", "Skullfire"); -const char *WARPSKULL_NAME = P_("monster", "Warpskull"); -const char *GORETONGUE_NAME = P_("monster", "Goretongue"); -const char *PULSECRAWLER_NAME = P_("monster", "Pulsecrawler"); -const char *MOONBENDER_NAME = P_("monster", "Moonbender"); -const char *WRATHRAVEN_NAME = P_("monster", "Wrathraven"); -const char *SPINEEATER_NAME = P_("monster", "Spineeater"); -const char *BLACKASH_THE_BURNING_NAME = P_("monster", "Blackash the Burning"); -const char *SHADOWCROW_NAME = P_("monster", "Shadowcrow"); -const char *BLIGHTSTONE_THE_WEAK_NAME = P_("monster", "Blightstone the Weak"); -const char *BILEFROTH_THE_PIT_MASTER_NAME = P_("monster", "Bilefroth the Pit Master"); -const char *BLOODSKIN_DARKBOW_NAME = P_("monster", "Bloodskin Darkbow"); -const char *FOULWING_NAME = P_("monster", "Foulwing"); -const char *SHADOWDRINKER_NAME = P_("monster", "Shadowdrinker"); -const char *HAZESHIFTER_NAME = P_("monster", "Hazeshifter"); -const char *DEATHSPIT_NAME = P_("monster", "Deathspit"); -const char *BLOODGUTTER_NAME = P_("monster", "Bloodgutter"); -const char *DEATHSHADE_FLESHMAUL_NAME = P_("monster", "Deathshade Fleshmaul"); -const char *WARMAGGOT_THE_MAD_NAME = P_("monster", "Warmaggot the Mad"); -const char *GLASSKULL_THE_JAGGED_NAME = P_("monster", "Glasskull the Jagged"); -const char *BLIGHTFIRE_NAME = P_("monster", "Blightfire"); -const char *NIGHTWING_THE_COLD_NAME = P_("monster", "Nightwing the Cold"); -const char *GORESTONE_NAME = P_("monster", "Gorestone"); -const char *BRONZEFIST_FIRESTONE_NAME = P_("monster", "Bronzefist Firestone"); -const char *WRATHFIRE_THE_DOOMED_NAME = P_("monster", "Wrathfire the Doomed"); -const char *FIREWOUND_THE_GRIM_NAME = P_("monster", "Firewound the Grim"); -const char *BARON_SLUDGE_NAME = P_("monster", "Baron Sludge"); -const char *BLIGHTHORN_STEELMACE_NAME = P_("monster", "Blighthorn Steelmace"); -const char *CHAOSHOWLER_NAME = P_("monster", "Chaoshowler"); -const char *DOOMGRIN_THE_ROTTING_NAME = P_("monster", "Doomgrin the Rotting"); -const char *MADBURNER_NAME = P_("monster", "Madburner"); -const char *BONESAW_THE_LITCH_NAME = P_("monster", "Bonesaw the Litch"); -const char *BREAKSPINE_NAME = P_("monster", "Breakspine"); -const char *DEVILSKULL_SHARPBONE_NAME = P_("monster", "Devilskull Sharpbone"); -const char *BROKENSTORM_NAME = P_("monster", "Brokenstorm"); -const char *STORMBANE_NAME = P_("monster", "Stormbane"); -const char *OOZEDROOL_NAME = P_("monster", "Oozedrool"); -const char *GOLDBLIGHT_OF_THE_FLAME_NAME = P_("monster", "Goldblight of the Flame"); -const char *BLACKSTORM_NAME = P_("monster", "Blackstorm"); -const char *PLAGUEWRATH_NAME = P_("monster", "Plaguewrath"); -const char *THE_FLAYER_NAME = P_("monster", "The Flayer"); -const char *BLUEHORN_NAME = P_("monster", "Bluehorn"); -const char *WARPFIRE_HELLSPAWN_NAME = P_("monster", "Warpfire Hellspawn"); -const char *FANGSPEIR_NAME = P_("monster", "Fangspeir"); -const char *FESTERSKULL_NAME = P_("monster", "Festerskull"); -const char *LIONSKULL_THE_BENT_NAME = P_("monster", "Lionskull the Bent"); -const char *BLACKTONGUE_NAME = P_("monster", "Blacktongue"); -const char *VILETOUCH_NAME = P_("monster", "Viletouch"); -const char *VIPERFLAME_NAME = P_("monster", "Viperflame"); -const char *FANGSKIN_NAME = P_("monster", "Fangskin"); -const char *WITCHFIRE_THE_UNHOLY_NAME = P_("monster", "Witchfire the Unholy"); -const char *BLACKSKULL_NAME = P_("monster", "Blackskull"); -const char *SOULSLASH_NAME = P_("monster", "Soulslash"); -const char *WINDSPAWN_NAME = P_("monster", "Windspawn"); -const char *LORD_OF_THE_PIT_NAME = P_("monster", "Lord of the Pit"); -const char *RUSTWEAVER_NAME = P_("monster", "Rustweaver"); -const char *HOWLINGIRE_THE_SHADE_NAME = P_("monster", "Howlingire the Shade"); -const char *DOOMCLOUD_NAME = P_("monster", "Doomcloud"); -const char *BLOODMOON_SOULFIRE_NAME = P_("monster", "Bloodmoon Soulfire"); -const char *WITCHMOON_NAME = P_("monster", "Witchmoon"); -const char *GOREFEAST_NAME = P_("monster", "Gorefeast"); -const char *GRAYWAR_THE_SLAYER_NAME = P_("monster", "Graywar the Slayer"); -const char *DREADJUDGE_NAME = P_("monster", "Dreadjudge"); -const char *STAREYE_THE_WITCH_NAME = P_("monster", "Stareye the Witch"); -const char *STEELSKULL_THE_HUNTER_NAME = P_("monster", "Steelskull the Hunter"); -const char *SIR_GORASH_NAME = P_("monster", "Sir Gorash"); -const char *THE_VIZIER_NAME = P_("monster", "The Vizier"); -const char *ZAMPHIR_NAME = P_("monster", "Zamphir"); -const char *BLOODLUST_NAME = P_("monster", "Bloodlust"); -const char *WEBWIDOW_NAME = P_("monster", "Webwidow"); -const char *FLESHDANCER_NAME = P_("monster", "Fleshdancer"); -const char *GRIMSPIKE_NAME = P_("monster", "Grimspike"); -const char *DOOMLOCK_NAME = P_("monster", "Doomlock"); -const char *IDI_GOLD_NAME = N_("Gold"); -const char *IDI_WARRIOR_NAME = N_("Short Sword"); -const char *IDI_WARRSHLD_NAME = N_("Buckler"); -const char *IDI_WARRCLUB_NAME = N_("Club"); -const char *IDI_ROGUE_NAME = N_("Short Bow"); -const char *IDI_SORCERER_NAME = N_("Short Staff of Mana"); -const char *IDI_CLEAVER_NAME = N_("Cleaver"); -const char *IDI_SKCROWN_NAME = N_("The Undead Crown"); -const char *IDI_INFRARING_NAME = N_("Empyrean Band"); -const char *IDI_ROCK_NAME = N_("Magic Rock"); -const char *IDI_OPTAMULET_NAME = N_("Optic Amulet"); -const char *IDI_TRING_NAME = N_("Ring of Truth"); -const char *IDI_BANNER_NAME = N_("Tavern Sign"); -const char *IDI_HARCREST_NAME = N_("Harlequin Crest"); -const char *IDI_STEELVEIL_NAME = N_("Veil of Steel"); -const char *IDI_GLDNELIX_NAME = N_("Golden Elixir"); -const char *IDI_ANVIL_NAME = N_("Anvil of Fury"); -const char *IDI_MUSHROOM_NAME = N_("Black Mushroom"); -const char *IDI_BRAIN_NAME = N_("Brain"); -const char *IDI_FUNGALTM_NAME = N_("Fungal Tome"); -const char *IDI_SPECELIX_NAME = N_("Spectral Elixir"); -const char *IDI_BLDSTONE_NAME = N_("Blood Stone"); -const char *IDI_MAPOFDOOM_NAME = N_("Cathedral Map"); -const char *IDI_EAR_NAME = N_("Ear"); -const char *IDI_HEAL_NAME = N_("Potion of Healing"); -const char *IDI_MANA_NAME = N_("Potion of Mana"); -const char *IDI_IDENTIFY_NAME = N_("Scroll of Identify"); -const char *IDI_PORTAL_NAME = N_("Scroll of Town Portal"); -const char *IDI_ARMOFVAL_NAME = N_("Arkaine's Valor"); -const char *IDI_FULLHEAL_NAME = N_("Potion of Full Healing"); -const char *IDI_FULLMANA_NAME = N_("Potion of Full Mana"); -const char *IDI_GRISWOLD_NAME = N_("Griswold's Edge"); -const char *IDI_LGTFORGE_NAME = N_("Bovine Plate"); -const char *IDI_LAZSTAFF_NAME = N_("Staff of Lazarus"); -const char *IDI_RESURRECT_NAME = N_("Scroll of Resurrect"); -const char *IDI_OIL_NAME = N_("Blacksmith Oil"); -const char *IDI_SHORTSTAFF_NAME = N_("Short Staff"); -const char *IDI_BARDSWORD_NAME = N_("Sword"); -const char *IDI_BARDDAGGER_NAME = N_("Dagger"); -const char *IDI_RUNEBOMB_NAME = N_("Rune Bomb"); -const char *IDI_THEODORE_NAME = N_("Theodore"); -const char *IDI_AURIC_NAME = N_("Auric Amulet"); -const char *IDI_NOTE1_NAME = N_("Torn Note 1"); -const char *IDI_NOTE2_NAME = N_("Torn Note 2"); -const char *IDI_NOTE3_NAME = N_("Torn Note 3"); -const char *IDI_FULLNOTE_NAME = N_("Reconstructed Note"); -const char *IDI_BROWNSUIT_NAME = N_("Brown Suit"); -const char *IDI_GREYSUIT_NAME = N_("Grey Suit"); -const char *ITEM_48_NAME = N_("Cap"); -const char *ITEM_49_NAME = N_("Skull Cap"); -const char *ITEM_50_NAME = N_("Helm"); -const char *ITEM_51_NAME = N_("Full Helm"); -const char *ITEM_52_NAME = N_("Crown"); -const char *ITEM_53_NAME = N_("Great Helm"); -const char *ITEM_54_NAME = N_("Cape"); -const char *ITEM_55_NAME = N_("Rags"); -const char *ITEM_56_NAME = N_("Cloak"); -const char *ITEM_57_NAME = N_("Robe"); -const char *ITEM_58_NAME = N_("Quilted Armor"); -const char *ITEM_58_SHORT_NAME = N_("Armor"); -const char *ITEM_59_NAME = N_("Leather Armor"); -const char *ITEM_60_NAME = N_("Hard Leather Armor"); -const char *ITEM_61_NAME = N_("Studded Leather Armor"); -const char *ITEM_62_NAME = N_("Ring Mail"); -const char *ITEM_62_SHORT_NAME = N_("Mail"); -const char *ITEM_63_NAME = N_("Chain Mail"); -const char *ITEM_64_NAME = N_("Scale Mail"); -const char *ITEM_65_NAME = N_("Breast Plate"); -const char *ITEM_65_SHORT_NAME = N_("Plate"); -const char *ITEM_66_NAME = N_("Splint Mail"); -const char *ITEM_67_NAME = N_("Plate Mail"); -const char *ITEM_68_NAME = N_("Field Plate"); -const char *ITEM_69_NAME = N_("Gothic Plate"); -const char *ITEM_70_NAME = N_("Full Plate Mail"); -const char *ITEM_71_SHORT_NAME = N_("Shield"); -const char *ITEM_72_NAME = N_("Small Shield"); -const char *ITEM_73_NAME = N_("Large Shield"); -const char *ITEM_74_NAME = N_("Kite Shield"); -const char *ITEM_75_NAME = N_("Tower Shield"); -const char *ITEM_76_NAME = N_("Gothic Shield"); -const char *ITEM_81_NAME = N_("Potion of Rejuvenation"); -const char *ITEM_82_NAME = N_("Potion of Full Rejuvenation"); -const char *ITEM_84_NAME = N_("Oil of Accuracy"); -const char *ITEM_85_NAME = N_("Oil of Sharpness"); -const char *ITEM_86_NAME = N_("Oil"); -const char *ITEM_87_NAME = N_("Elixir of Strength"); -const char *ITEM_88_NAME = N_("Elixir of Magic"); -const char *ITEM_89_NAME = N_("Elixir of Dexterity"); -const char *ITEM_90_NAME = N_("Elixir of Vitality"); -const char *ITEM_91_NAME = N_("Scroll of Healing"); -const char *ITEM_92_NAME = N_("Scroll of Search"); -const char *ITEM_93_NAME = N_("Scroll of Lightning"); -const char *ITEM_96_NAME = N_("Scroll of Fire Wall"); -const char *ITEM_97_NAME = N_("Scroll of Inferno"); -const char *ITEM_99_NAME = N_("Scroll of Flash"); -const char *ITEM_100_NAME = N_("Scroll of Infravision"); -const char *ITEM_101_NAME = N_("Scroll of Phasing"); -const char *ITEM_102_NAME = N_("Scroll of Mana Shield"); -const char *ITEM_103_NAME = N_("Scroll of Flame Wave"); -const char *ITEM_104_NAME = N_("Scroll of Fireball"); -const char *ITEM_105_NAME = N_("Scroll of Stone Curse"); -const char *ITEM_106_NAME = N_("Scroll of Chain Lightning"); -const char *ITEM_107_NAME = N_("Scroll of Guardian"); -const char *ITEM_109_NAME = N_("Scroll of Nova"); -const char *ITEM_110_NAME = N_("Scroll of Golem"); -const char *ITEM_112_NAME = N_("Scroll of Teleport"); -const char *ITEM_113_NAME = N_("Scroll of Apocalypse"); -const char *ITEM_120_NAME = N_("Falchion"); -const char *ITEM_121_NAME = N_("Scimitar"); -const char *ITEM_122_NAME = N_("Claymore"); -const char *ITEM_123_NAME = N_("Blade"); -const char *ITEM_124_NAME = N_("Sabre"); -const char *ITEM_125_NAME = N_("Long Sword"); -const char *ITEM_126_NAME = N_("Broad Sword"); -const char *ITEM_127_NAME = N_("Bastard Sword"); -const char *ITEM_128_NAME = N_("Two-Handed Sword"); -const char *ITEM_129_NAME = N_("Great Sword"); -const char *ITEM_130_NAME = N_("Small Axe"); -const char *ITEM_130_SHORT_NAME = N_("Axe"); -const char *ITEM_132_NAME = N_("Large Axe"); -const char *ITEM_133_NAME = N_("Broad Axe"); -const char *ITEM_134_NAME = N_("Battle Axe"); -const char *ITEM_135_NAME = N_("Great Axe"); -const char *ITEM_136_NAME = N_("Mace"); -const char *ITEM_137_NAME = N_("Morning Star"); -const char *ITEM_138_NAME = N_("War Hammer"); -const char *ITEM_138_SHORT_NAME = N_("Hammer"); -const char *ITEM_139_NAME = N_("Spiked Club"); -const char *ITEM_141_NAME = N_("Flail"); -const char *ITEM_142_NAME = N_("Maul"); -const char *ITEM_143_SHORT_NAME = N_("Bow"); -const char *ITEM_144_NAME = N_("Hunter's Bow"); -const char *ITEM_145_NAME = N_("Long Bow"); -const char *ITEM_146_NAME = N_("Composite Bow"); -const char *IDI_SHORT_BATTLE_BOW_NAME = N_("Short Battle Bow"); -const char *ITEM_148_NAME = N_("Long Battle Bow"); -const char *ITEM_149_NAME = N_("Short War Bow"); -const char *ITEM_150_NAME = N_("Long War Bow"); -const char *ITEM_151_SHORT_NAME = N_("Staff"); -const char *ITEM_152_NAME = N_("Long Staff"); -const char *ITEM_153_NAME = N_("Composite Staff"); -const char *ITEM_154_NAME = N_("Quarter Staff"); -const char *ITEM_155_NAME = N_("War Staff"); -const char *ITEM_156_NAME = N_("Ring"); -const char *ITEM_159_NAME = N_("Amulet"); -const char *ITEM_161_NAME = N_("Rune of Fire"); -const char *ITEM_161_SHORT_NAME = N_("Rune"); -const char *ITEM_162_NAME = N_("Rune of Lightning"); -const char *ITEM_163_NAME = N_("Greater Rune of Fire"); -const char *ITEM_164_NAME = N_("Greater Rune of Lightning"); -const char *ITEM_165_NAME = N_("Rune of Stone"); -const char *ITEM_166_NAME = N_("Short Staff of Charged Bolt"); -const char *IDI_ARENAPOT_NAME = N_("Arena Potion"); -const char *UNIQUE_ITEM_0_NAME = N_("The Butcher's Cleaver"); -const char *UNIQUE_ITEM_9_NAME = N_("Lightforge"); -const char *UNIQUE_ITEM_10_NAME = N_("The Rift Bow"); -const char *UNIQUE_ITEM_11_NAME = N_("The Needler"); -const char *UNIQUE_ITEM_12_NAME = N_("The Celestial Bow"); -const char *UNIQUE_ITEM_13_NAME = N_("Deadly Hunter"); -const char *UNIQUE_ITEM_14_NAME = N_("Bow of the Dead"); -const char *UNIQUE_ITEM_15_NAME = N_("The Blackoak Bow"); -const char *UNIQUE_ITEM_16_NAME = N_("Flamedart"); -const char *UNIQUE_ITEM_17_NAME = N_("Fleshstinger"); -const char *UNIQUE_ITEM_18_NAME = N_("Windforce"); -const char *UNIQUE_ITEM_19_NAME = N_("Eaglehorn"); -const char *UNIQUE_ITEM_20_NAME = N_("Gonnagal's Dirk"); -const char *UNIQUE_ITEM_21_NAME = N_("The Defender"); -const char *UNIQUE_ITEM_22_NAME = N_("Gryphon's Claw"); -const char *UNIQUE_ITEM_23_NAME = N_("Black Razor"); -const char *UNIQUE_ITEM_24_NAME = N_("Gibbous Moon"); -const char *UNIQUE_ITEM_25_NAME = N_("Ice Shank"); -const char *UNIQUE_ITEM_26_NAME = N_("The Executioner's Blade"); -const char *UNIQUE_ITEM_27_NAME = N_("The Bonesaw"); -const char *UNIQUE_ITEM_28_NAME = N_("Shadowhawk"); -const char *UNIQUE_ITEM_29_NAME = N_("Wizardspike"); -const char *UNIQUE_ITEM_30_NAME = N_("Lightsabre"); -const char *UNIQUE_ITEM_31_NAME = N_("The Falcon's Talon"); -const char *UNIQUE_ITEM_32_NAME = N_("Inferno"); -const char *UNIQUE_ITEM_33_NAME = N_("Doombringer"); -const char *UNIQUE_ITEM_34_NAME = N_("The Grizzly"); -const char *UNIQUE_ITEM_35_NAME = N_("The Grandfather"); -const char *UNIQUE_ITEM_36_NAME = N_("The Mangler"); -const char *UNIQUE_ITEM_37_NAME = N_("Sharp Beak"); -const char *UNIQUE_ITEM_38_NAME = N_("BloodSlayer"); -const char *UNIQUE_ITEM_39_NAME = N_("The Celestial Axe"); -const char *UNIQUE_ITEM_40_NAME = N_("Wicked Axe"); -const char *UNIQUE_ITEM_41_NAME = N_("Stonecleaver"); -const char *UNIQUE_ITEM_42_NAME = N_("Aguinara's Hatchet"); -const char *UNIQUE_ITEM_43_NAME = N_("Hellslayer"); -const char *UNIQUE_ITEM_44_NAME = N_("Messerschmidt's Reaver"); -const char *UNIQUE_ITEM_45_NAME = N_("Crackrust"); -const char *UNIQUE_ITEM_46_NAME = N_("Hammer of Jholm"); -const char *UNIQUE_ITEM_47_NAME = N_("Civerb's Cudgel"); -const char *UNIQUE_ITEM_48_NAME = N_("The Celestial Star"); -const char *UNIQUE_ITEM_49_NAME = N_("Baranar's Star"); -const char *UNIQUE_ITEM_50_NAME = N_("Gnarled Root"); -const char *UNIQUE_ITEM_51_NAME = N_("The Cranium Basher"); -const char *UNIQUE_ITEM_52_NAME = N_("Schaefer's Hammer"); -const char *UNIQUE_ITEM_53_NAME = N_("Dreamflange"); -const char *UNIQUE_ITEM_54_NAME = N_("Staff of Shadows"); -const char *UNIQUE_ITEM_55_NAME = N_("Immolator"); -const char *UNIQUE_ITEM_56_NAME = N_("Storm Spire"); -const char *UNIQUE_ITEM_57_NAME = N_("Gleamsong"); -const char *UNIQUE_ITEM_58_NAME = N_("Thundercall"); -const char *UNIQUE_ITEM_59_NAME = N_("The Protector"); -const char *UNIQUE_ITEM_60_NAME = N_("Naj's Puzzler"); -const char *UNIQUE_ITEM_61_NAME = N_("Mindcry"); -const char *UNIQUE_ITEM_62_NAME = N_("Rod of Onan"); -const char *UNIQUE_ITEM_63_NAME = N_("Helm of Spirits"); -const char *UNIQUE_ITEM_64_NAME = N_("Thinking Cap"); -const char *UNIQUE_ITEM_65_NAME = N_("OverLord's Helm"); -const char *UNIQUE_ITEM_66_NAME = N_("Fool's Crest"); -const char *UNIQUE_ITEM_67_NAME = N_("Gotterdamerung"); -const char *UNIQUE_ITEM_68_NAME = N_("Royal Circlet"); -const char *UNIQUE_ITEM_69_NAME = N_("Torn Flesh of Souls"); -const char *UNIQUE_ITEM_70_NAME = N_("The Gladiator's Bane"); -const char *UNIQUE_ITEM_71_NAME = N_("The Rainbow Cloak"); -const char *UNIQUE_ITEM_72_NAME = N_("Leather of Aut"); -const char *UNIQUE_ITEM_73_NAME = N_("Wisdom's Wrap"); -const char *UNIQUE_ITEM_74_NAME = N_("Sparking Mail"); -const char *UNIQUE_ITEM_75_NAME = N_("Scavenger Carapace"); -const char *UNIQUE_ITEM_76_NAME = N_("Nightscape"); -const char *UNIQUE_ITEM_77_NAME = N_("Naj's Light Plate"); -const char *UNIQUE_ITEM_78_NAME = N_("Demonspike Coat"); -const char *UNIQUE_ITEM_79_NAME = N_("The Deflector"); -const char *UNIQUE_ITEM_80_NAME = N_("Split Skull Shield"); -const char *UNIQUE_ITEM_81_NAME = N_("Dragon's Breach"); -const char *UNIQUE_ITEM_82_NAME = N_("Blackoak Shield"); -const char *UNIQUE_ITEM_83_NAME = N_("Holy Defender"); -const char *UNIQUE_ITEM_84_NAME = N_("Stormshield"); -const char *UNIQUE_ITEM_85_NAME = N_("Bramble"); -const char *UNIQUE_ITEM_86_NAME = N_("Ring of Regha"); -const char *UNIQUE_ITEM_87_NAME = N_("The Bleeder"); -const char *UNIQUE_ITEM_88_NAME = N_("Constricting Ring"); -const char *UNIQUE_ITEM_89_NAME = N_("Ring of Engagement"); -const char *ITEM_PREFIX_0_NAME = N_("Tin"); -const char *ITEM_PREFIX_1_NAME = N_("Brass"); -const char *ITEM_PREFIX_2_NAME = N_("Bronze"); -const char *ITEM_PREFIX_3_NAME = N_("Iron"); -const char *ITEM_PREFIX_4_NAME = N_("Steel"); -const char *ITEM_PREFIX_5_NAME = N_("Silver"); -const char *ITEM_PREFIX_7_NAME = N_("Platinum"); -const char *ITEM_PREFIX_8_NAME = N_("Mithril"); -const char *ITEM_PREFIX_9_NAME = N_("Meteoric"); -const char *ITEM_PREFIX_10_NAME = N_("Weird"); -const char *ITEM_PREFIX_11_NAME = N_("Strange"); -const char *ITEM_PREFIX_12_NAME = N_("Useless"); -const char *ITEM_PREFIX_13_NAME = N_("Bent"); -const char *ITEM_PREFIX_14_NAME = N_("Weak"); -const char *ITEM_PREFIX_15_NAME = N_("Jagged"); -const char *ITEM_PREFIX_16_NAME = N_("Deadly"); -const char *ITEM_PREFIX_17_NAME = N_("Heavy"); -const char *ITEM_PREFIX_18_NAME = N_("Vicious"); -const char *ITEM_PREFIX_19_NAME = N_("Brutal"); -const char *ITEM_PREFIX_20_NAME = N_("Massive"); -const char *ITEM_PREFIX_21_NAME = N_("Savage"); -const char *ITEM_PREFIX_22_NAME = N_("Ruthless"); -const char *ITEM_PREFIX_23_NAME = N_("Merciless"); -const char *ITEM_PREFIX_24_NAME = N_("Clumsy"); -const char *ITEM_PREFIX_25_NAME = N_("Dull"); -const char *ITEM_PREFIX_26_NAME = N_("Sharp"); -const char *ITEM_PREFIX_27_NAME = N_("Fine"); -const char *ITEM_PREFIX_28_NAME = N_("Warrior's"); -const char *ITEM_PREFIX_29_NAME = N_("Soldier's"); -const char *ITEM_PREFIX_30_NAME = N_("Lord's"); -const char *ITEM_PREFIX_31_NAME = N_("Knight's"); -const char *ITEM_PREFIX_32_NAME = N_("Master's"); -const char *ITEM_PREFIX_33_NAME = N_("Champion's"); -const char *ITEM_PREFIX_34_NAME = N_("King's"); -const char *ITEM_PREFIX_35_NAME = N_("Vulnerable"); -const char *ITEM_PREFIX_36_NAME = N_("Rusted"); -const char *ITEM_PREFIX_38_NAME = N_("Strong"); -const char *ITEM_PREFIX_39_NAME = N_("Grand"); -const char *ITEM_PREFIX_40_NAME = N_("Valiant"); -const char *ITEM_PREFIX_41_NAME = N_("Glorious"); -const char *ITEM_PREFIX_42_NAME = N_("Blessed"); -const char *ITEM_PREFIX_43_NAME = N_("Saintly"); -const char *ITEM_PREFIX_44_NAME = N_("Awesome"); -const char *ITEM_PREFIX_45_NAME = N_("Holy"); -const char *ITEM_PREFIX_46_NAME = N_("Godly"); -const char *ITEM_PREFIX_47_NAME = N_("Red"); -const char *ITEM_PREFIX_48_NAME = N_("Crimson"); -const char *ITEM_PREFIX_50_NAME = N_("Garnet"); -const char *ITEM_PREFIX_51_NAME = N_("Ruby"); -const char *ITEM_PREFIX_52_NAME = N_("Blue"); -const char *ITEM_PREFIX_53_NAME = N_("Azure"); -const char *ITEM_PREFIX_54_NAME = N_("Lapis"); -const char *ITEM_PREFIX_55_NAME = N_("Cobalt"); -const char *ITEM_PREFIX_56_NAME = N_("Sapphire"); -const char *ITEM_PREFIX_57_NAME = N_("White"); -const char *ITEM_PREFIX_58_NAME = N_("Pearl"); -const char *ITEM_PREFIX_59_NAME = N_("Ivory"); -const char *ITEM_PREFIX_60_NAME = N_("Crystal"); -const char *ITEM_PREFIX_61_NAME = N_("Diamond"); -const char *ITEM_PREFIX_62_NAME = N_("Topaz"); -const char *ITEM_PREFIX_63_NAME = N_("Amber"); -const char *ITEM_PREFIX_64_NAME = N_("Jade"); -const char *ITEM_PREFIX_65_NAME = N_("Obsidian"); -const char *ITEM_PREFIX_66_NAME = N_("Emerald"); -const char *ITEM_PREFIX_67_NAME = N_("Hyena's"); -const char *ITEM_PREFIX_68_NAME = N_("Frog's"); -const char *ITEM_PREFIX_69_NAME = N_("Spider's"); -const char *ITEM_PREFIX_70_NAME = N_("Raven's"); -const char *ITEM_PREFIX_71_NAME = N_("Snake's"); -const char *ITEM_PREFIX_72_NAME = N_("Serpent's"); -const char *ITEM_PREFIX_73_NAME = N_("Drake's"); -const char *ITEM_PREFIX_74_NAME = N_("Dragon's"); -const char *ITEM_PREFIX_75_NAME = N_("Wyrm's"); -const char *ITEM_PREFIX_76_NAME = N_("Hydra's"); -const char *ITEM_PREFIX_77_NAME = N_("Angel's"); -const char *ITEM_PREFIX_78_NAME = N_("Arch-Angel's"); -const char *ITEM_PREFIX_79_NAME = N_("Plentiful"); -const char *ITEM_PREFIX_80_NAME = N_("Bountiful"); -const char *ITEM_PREFIX_81_NAME = N_("Flaming"); -const char *ITEM_PREFIX_82_NAME = N_("Lightning"); -const char *ITEM_SUFFIX_0_NAME = N_("quality"); -const char *ITEM_SUFFIX_1_NAME = N_("maiming"); -const char *ITEM_SUFFIX_2_NAME = N_("slaying"); -const char *ITEM_SUFFIX_3_NAME = N_("gore"); -const char *ITEM_SUFFIX_4_NAME = N_("carnage"); -const char *ITEM_SUFFIX_5_NAME = N_("slaughter"); -const char *ITEM_SUFFIX_6_NAME = N_("pain"); -const char *ITEM_SUFFIX_7_NAME = N_("tears"); -const char *ITEM_SUFFIX_8_NAME = N_("health"); -const char *ITEM_SUFFIX_9_NAME = N_("protection"); -const char *ITEM_SUFFIX_10_NAME = N_("absorption"); -const char *ITEM_SUFFIX_11_NAME = N_("deflection"); -const char *ITEM_SUFFIX_12_NAME = N_("osmosis"); -const char *ITEM_SUFFIX_13_NAME = N_("frailty"); -const char *ITEM_SUFFIX_14_NAME = N_("weakness"); -const char *ITEM_SUFFIX_15_NAME = N_("strength"); -const char *ITEM_SUFFIX_16_NAME = N_("might"); -const char *ITEM_SUFFIX_17_NAME = N_("power"); -const char *ITEM_SUFFIX_18_NAME = N_("giants"); -const char *ITEM_SUFFIX_19_NAME = N_("titans"); -const char *ITEM_SUFFIX_20_NAME = N_("paralysis"); -const char *ITEM_SUFFIX_21_NAME = N_("atrophy"); -const char *ITEM_SUFFIX_22_NAME = N_("dexterity"); -const char *ITEM_SUFFIX_23_NAME = N_("skill"); -const char *ITEM_SUFFIX_24_NAME = N_("accuracy"); -const char *ITEM_SUFFIX_25_NAME = N_("precision"); -const char *ITEM_SUFFIX_26_NAME = N_("perfection"); -const char *ITEM_SUFFIX_27_NAME = N_("the fool"); -const char *ITEM_SUFFIX_28_NAME = N_("dyslexia"); -const char *ITEM_SUFFIX_29_NAME = N_("magic"); -const char *ITEM_SUFFIX_30_NAME = N_("the mind"); -const char *ITEM_SUFFIX_31_NAME = N_("brilliance"); -const char *ITEM_SUFFIX_32_NAME = N_("sorcery"); -const char *ITEM_SUFFIX_33_NAME = N_("wizardry"); -const char *ITEM_SUFFIX_34_NAME = N_("illness"); -const char *ITEM_SUFFIX_35_NAME = N_("disease"); -const char *ITEM_SUFFIX_36_NAME = N_("vitality"); -const char *ITEM_SUFFIX_37_NAME = N_("zest"); -const char *ITEM_SUFFIX_38_NAME = N_("vim"); -const char *ITEM_SUFFIX_39_NAME = N_("vigor"); -const char *ITEM_SUFFIX_40_NAME = N_("life"); -const char *ITEM_SUFFIX_41_NAME = N_("trouble"); -const char *ITEM_SUFFIX_42_NAME = N_("the pit"); -const char *ITEM_SUFFIX_43_NAME = N_("the sky"); -const char *ITEM_SUFFIX_44_NAME = N_("the moon"); -const char *ITEM_SUFFIX_45_NAME = N_("the stars"); -const char *ITEM_SUFFIX_46_NAME = N_("the heavens"); -const char *ITEM_SUFFIX_47_NAME = N_("the zodiac"); -const char *ITEM_SUFFIX_48_NAME = N_("the vulture"); -const char *ITEM_SUFFIX_49_NAME = N_("the jackal"); -const char *ITEM_SUFFIX_50_NAME = N_("the fox"); -const char *ITEM_SUFFIX_51_NAME = N_("the jaguar"); -const char *ITEM_SUFFIX_52_NAME = N_("the eagle"); -const char *ITEM_SUFFIX_53_NAME = N_("the wolf"); -const char *ITEM_SUFFIX_54_NAME = N_("the tiger"); -const char *ITEM_SUFFIX_55_NAME = N_("the lion"); -const char *ITEM_SUFFIX_56_NAME = N_("the mammoth"); -const char *ITEM_SUFFIX_57_NAME = N_("the whale"); -const char *ITEM_SUFFIX_58_NAME = N_("fragility"); -const char *ITEM_SUFFIX_59_NAME = N_("brittleness"); -const char *ITEM_SUFFIX_60_NAME = N_("sturdiness"); -const char *ITEM_SUFFIX_61_NAME = N_("craftsmanship"); -const char *ITEM_SUFFIX_62_NAME = N_("structure"); -const char *ITEM_SUFFIX_63_NAME = N_("the ages"); -const char *ITEM_SUFFIX_64_NAME = N_("the dark"); -const char *ITEM_SUFFIX_65_NAME = N_("the night"); -const char *ITEM_SUFFIX_66_NAME = N_("light"); -const char *ITEM_SUFFIX_67_NAME = N_("radiance"); -const char *ITEM_SUFFIX_68_NAME = N_("flame"); -const char *ITEM_SUFFIX_69_NAME = N_("fire"); -const char *ITEM_SUFFIX_70_NAME = N_("burning"); -const char *ITEM_SUFFIX_71_NAME = N_("shock"); -const char *ITEM_SUFFIX_72_NAME = N_("lightning"); -const char *ITEM_SUFFIX_73_NAME = N_("thunder"); -const char *ITEM_SUFFIX_74_NAME = N_("many"); -const char *ITEM_SUFFIX_75_NAME = N_("plenty"); -const char *ITEM_SUFFIX_76_NAME = N_("thorns"); -const char *ITEM_SUFFIX_77_NAME = N_("corruption"); -const char *ITEM_SUFFIX_78_NAME = N_("thieves"); -const char *ITEM_SUFFIX_79_NAME = N_("the bear"); -const char *ITEM_SUFFIX_80_NAME = N_("the bat"); -const char *ITEM_SUFFIX_81_NAME = N_("vampires"); -const char *ITEM_SUFFIX_82_NAME = N_("the leech"); -const char *ITEM_SUFFIX_83_NAME = N_("blood"); -const char *ITEM_SUFFIX_84_NAME = N_("piercing"); -const char *ITEM_SUFFIX_85_NAME = N_("puncturing"); -const char *ITEM_SUFFIX_86_NAME = N_("bashing"); -const char *ITEM_SUFFIX_87_NAME = N_("readiness"); -const char *ITEM_SUFFIX_88_NAME = N_("swiftness"); -const char *ITEM_SUFFIX_89_NAME = N_("speed"); -const char *ITEM_SUFFIX_90_NAME = N_("haste"); -const char *ITEM_SUFFIX_91_NAME = N_("balance"); -const char *ITEM_SUFFIX_92_NAME = N_("stability"); -const char *ITEM_SUFFIX_93_NAME = N_("harmony"); -const char *ITEM_SUFFIX_94_NAME = N_("blocking"); -const char *QUEST_THE_MAGIC_ROCK_NAME = N_("The Magic Rock"); -const char *QUEST_GHARBAD_THE_WEAK_NAME = N_("Gharbad The Weak"); -const char *QUEST_ZHAR_THE_MAD_NAME = N_("Zhar the Mad"); -const char *QUEST_LACHDANAN_NAME = N_("Lachdanan"); -const char *QUEST_DIABLO_NAME = N_("Diablo"); -const char *QUEST_THE_BUTCHER_NAME = N_("The Butcher"); -const char *QUEST_OGDENS_SIGN_NAME = N_("Ogden's Sign"); -const char *QUEST_HALLS_OF_THE_BLIND_NAME = N_("Halls of the Blind"); -const char *QUEST_VALOR_NAME = N_("Valor"); -const char *QUEST_WARLORD_OF_BLOOD_NAME = N_("Warlord of Blood"); -const char *QUEST_THE_CURSE_OF_KING_LEORIC_NAME = N_("The Curse of King Leoric"); -const char *QUEST_POISONED_WATER_SUPPLY_NAME = N_("Poisoned Water Supply"); -const char *QUEST_THE_CHAMBER_OF_BONE_NAME = N_("The Chamber of Bone"); -const char *QUEST_ARCHBISHOP_LAZARUS_NAME = N_("Archbishop Lazarus"); -const char *QUEST_GRAVE_MATTERS_NAME = N_("Grave Matters"); -const char *QUEST_FARMERS_ORCHARD_NAME = N_("Farmer's Orchard"); -const char *QUEST_LITTLE_GIRL_NAME = N_("Little Girl"); -const char *QUEST_WANDERING_TRADER_NAME = N_("Wandering Trader"); -const char *QUEST_THE_DEFILER_NAME = N_("The Defiler"); -const char *QUEST_NA_KRUL_NAME = N_("Na-Krul"); -const char *QUEST_CORNERSTONE_OF_THE_WORLD_NAME = N_("Cornerstone of the World"); -const char *QUEST_THE_JERSEYS_JERSEY_NAME = N_("The Jersey's Jersey"); -const char *SPELL_FIREBOLT_NAME = P_("spell", "Firebolt"); -const char *SPELL_HEALING_NAME = P_("spell", "Healing"); -const char *SPELL_LIGHTNING_NAME = P_("spell", "Lightning"); -const char *SPELL_FLASH_NAME = P_("spell", "Flash"); -const char *SPELL_IDENTIFY_NAME = P_("spell", "Identify"); -const char *SPELL_FIRE_WALL_NAME = P_("spell", "Fire Wall"); -const char *SPELL_TOWN_PORTAL_NAME = P_("spell", "Town Portal"); -const char *SPELL_STONE_CURSE_NAME = P_("spell", "Stone Curse"); -const char *SPELL_INFRAVISION_NAME = P_("spell", "Infravision"); -const char *SPELL_PHASING_NAME = P_("spell", "Phasing"); -const char *SPELL_MANA_SHIELD_NAME = P_("spell", "Mana Shield"); -const char *SPELL_FIREBALL_NAME = P_("spell", "Fireball"); -const char *SPELL_GUARDIAN_NAME = P_("spell", "Guardian"); -const char *SPELL_CHAIN_LIGHTNING_NAME = P_("spell", "Chain Lightning"); -const char *SPELL_FLAME_WAVE_NAME = P_("spell", "Flame Wave"); -const char *SPELL_DOOM_SERPENTS_NAME = P_("spell", "Doom Serpents"); -const char *SPELL_BLOOD_RITUAL_NAME = P_("spell", "Blood Ritual"); -const char *SPELL_NOVA_NAME = P_("spell", "Nova"); -const char *SPELL_INVISIBILITY_NAME = P_("spell", "Invisibility"); -const char *SPELL_INFERNO_NAME = P_("spell", "Inferno"); -const char *SPELL_GOLEM_NAME = P_("spell", "Golem"); -const char *SPELL_RAGE_NAME = P_("spell", "Rage"); -const char *SPELL_TELEPORT_NAME = P_("spell", "Teleport"); -const char *SPELL_APOCALYPSE_NAME = P_("spell", "Apocalypse"); -const char *SPELL_ETHEREALIZE_NAME = P_("spell", "Etherealize"); -const char *SPELL_ITEM_REPAIR_NAME = P_("spell", "Item Repair"); -const char *SPELL_STAFF_RECHARGE_NAME = P_("spell", "Staff Recharge"); -const char *SPELL_TRAP_DISARM_NAME = P_("spell", "Trap Disarm"); -const char *SPELL_ELEMENTAL_NAME = P_("spell", "Elemental"); -const char *SPELL_CHARGED_BOLT_NAME = P_("spell", "Charged Bolt"); -const char *SPELL_HOLY_BOLT_NAME = P_("spell", "Holy Bolt"); -const char *SPELL_RESURRECT_NAME = P_("spell", "Resurrect"); -const char *SPELL_TELEKINESIS_NAME = P_("spell", "Telekinesis"); -const char *SPELL_HEAL_OTHER_NAME = P_("spell", "Heal Other"); -const char *SPELL_BLOOD_STAR_NAME = P_("spell", "Blood Star"); -const char *SPELL_BONE_SPIRIT_NAME = P_("spell", "Bone Spirit"); -const char *TEXT_0 = N_(" Ahh, the story of our King, is it? The tragic fall of Leoric was a harsh blow to this land. The people always loved the King, and now they live in mortal fear of him. The question that I keep asking myself is how he could have fallen so far from the Light, as Leoric had always been the holiest of men. Only the vilest powers of Hell could so utterly destroy a man from within..."); -const char *TEXT_1 = N_("The village needs your help, good master! Some months ago King Leoric's son, Prince Albrecht, was kidnapped. The King went into a rage and scoured the village for his missing child. With each passing day, Leoric seemed to slip deeper into madness. He sought to blame innocent townsfolk for the boy's disappearance and had them brutally executed. Less than half of us survived his insanity...\n \nThe King's Knights and Priests tried to placate him, but he turned against them and sadly, they were forced to kill him. With his dying breath the King called down a terrible curse upon his former followers. He vowed that they would serve him in darkness forever...\n \nThis is where things take an even darker twist than I thought possible! Our former King has risen from his eternal sleep and now commands a legion of undead minions within the Labyrinth. His body was buried in a tomb three levels beneath the Cathedral. Please, good master, put his soul at ease by destroying his now cursed form..."); -const char *TEXT_2 = N_("As I told you, good master, the King was entombed three levels below. He's down there, waiting in the putrid darkness for his chance to destroy this land..."); -const char *TEXT_3 = N_("The curse of our King has passed, but I fear that it was only part of a greater evil at work. However, we may yet be saved from the darkness that consumes our land, for your victory is a good omen. May Light guide you on your way, good master."); -const char *TEXT_4 = N_("The loss of his son was too much for King Leoric. I did what I could to ease his madness, but in the end it overcame him. A black curse has hung over this kingdom from that day forward, but perhaps if you were to free his spirit from his earthly prison, the curse would be lifted..."); -const char *TEXT_5 = N_("I don't like to think about how the King died. I like to remember him for the kind and just ruler that he was. His death was so sad and seemed very wrong, somehow."); -const char *TEXT_6 = N_("I made many of the weapons and most of the armor that King Leoric used to outfit his knights. I even crafted a huge two-handed sword of the finest mithril for him, as well as a field crown to match. I still cannot believe how he died, but it must have been some sinister force that drove him insane!"); -const char *TEXT_7 = N_("I don't care about that. Listen, no skeleton is gonna be MY king. Leoric is King. King, so you hear me? HAIL TO THE KING!"); -const char *TEXT_8 = N_("The dead who walk among the living follow the cursed King. He holds the power to raise yet more warriors for an ever growing army of the undead. If you do not stop his reign, he will surely march across this land and slay all who still live here."); -const char *TEXT_9 = N_("Look, I'm running a business here. I don't sell information, and I don't care about some King that's been dead longer than I've been alive. If you need something to use against this King of the undead, then I can help you out..."); -const char *TEXT_10 = N_("The warmth of life has entered my tomb. Prepare yourself, mortal, to serve my Master for eternity!"); -const char *TEXT_11 = N_("I see that this strange behavior puzzles you as well. I would surmise that since many demons fear the light of the sun and believe that it holds great power, it may be that the rising sun depicted on the sign you speak of has led them to believe that it too holds some arcane powers. Hmm, perhaps they are not all as smart as we had feared..."); -const char *TEXT_12 = N_("Master, I have a strange experience to relate. I know that you have a great knowledge of those monstrosities that inhabit the labyrinth, and this is something that I cannot understand for the very life of me... I was awakened during the night by a scraping sound just outside of my tavern. When I looked out from my bedroom, I saw the shapes of small demon-like creatures in the inn yard. After a short time, they ran off, but not before stealing the sign to my inn. I don't know why the demons would steal my sign but leave my family in peace... 'tis strange, no?"); -const char *TEXT_13 = N_("Oh, you didn't have to bring back my sign, but I suppose that it does save me the expense of having another one made. Well, let me see, what could I give you as a fee for finding it? Hmmm, what have we here... ah, yes! This cap was left in one of the rooms by a magician who stayed here some time ago. Perhaps it may be of some value to you."); -const char *TEXT_14 = N_("My goodness, demons running about the village at night, pillaging our homes - is nothing sacred? I hope that Ogden and Garda are all right. I suppose that they would come to see me if they were hurt..."); -const char *TEXT_15 = N_("Oh my! Is that where the sign went? My Grandmother and I must have slept right through the whole thing. Thank the Light that those monsters didn't attack the inn."); -const char *TEXT_16 = N_("Demons stole Ogden's sign, you say? That doesn't sound much like the atrocities I've heard of - or seen. \n \nDemons are concerned with ripping out your heart, not your signpost."); -const char *TEXT_17 = N_("You know what I think? Somebody took that sign, and they gonna want lots of money for it. If I was Ogden... and I'm not, but if I was... I'd just buy a new sign with some pretty drawing on it. Maybe a nice mug of ale or a piece of cheese..."); -const char *TEXT_18 = N_("No mortal can truly understand the mind of the demon. \n \nNever let their erratic actions confuse you, as that too may be their plan."); -const char *TEXT_19 = N_("What - is he saying I took that? I suppose that Griswold is on his side, too. \n \nLook, I got over simple sign stealing months ago. You can't turn a profit on a piece of wood."); -const char *TEXT_20 = N_("Hey - You that one that kill all! You get me Magic Banner or we attack! You no leave with life! You kill big uglies and give back Magic. Go past corner and door, find uglies. You give, you go!"); -const char *TEXT_21 = N_("You kill uglies, get banner. You bring to me, or else..."); -const char *TEXT_22 = N_("You give! Yes, good! Go now, we strong. We kill all with big Magic!"); -const char *TEXT_23 = N_("This does not bode well, for it confirms my darkest fears. While I did not allow myself to believe the ancient legends, I cannot deny them now. Perhaps the time has come to reveal who I am.\n \nMy true name is Deckard Cain the Elder, and I am the last descendant of an ancient Brotherhood that was dedicated to safeguarding the secrets of a timeless evil. An evil that quite obviously has now been released.\n \nThe Archbishop Lazarus, once King Leoric's most trusted advisor, led a party of simple townsfolk into the Labyrinth to find the King's missing son, Albrecht. Quite some time passed before they returned, and only a few of them escaped with their lives.\n \nCurse me for a fool! I should have suspected his veiled treachery then. It must have been Lazarus himself who kidnapped Albrecht and has since hidden him within the Labyrinth. I do not understand why the Archbishop turned to the darkness, or what his interest is in the child, unless he means to sacrifice him to his dark masters!\n \nThat must be what he has planned! The survivors of his 'rescue party' say that Lazarus was last seen running into the deepest bowels of the labyrinth. You must hurry and save the prince from the sacrificial blade of this demented fiend!"); -const char *TEXT_24 = N_("You must hurry and rescue Albrecht from the hands of Lazarus. The prince and the people of this kingdom are counting on you!"); -const char *TEXT_25 = N_("Your story is quite grim, my friend. Lazarus will surely burn in Hell for his horrific deed. The boy that you describe is not our prince, but I believe that Albrecht may yet be in danger. The symbol of power that you speak of must be a portal in the very heart of the labyrinth.\n \nKnow this, my friend - The evil that you move against is the dark Lord of Terror. He is known to mortal men as Diablo. It was he who was imprisoned within the Labyrinth many centuries ago and I fear that he seeks to once again sow chaos in the realm of mankind. You must venture through the portal and destroy Diablo before it is too late!"); -const char *TEXT_26 = N_("Lazarus was the Archbishop who led many of the townspeople into the labyrinth. I lost many good friends that day, and Lazarus never returned. I suppose he was killed along with most of the others. If you would do me a favor, good master - please do not talk to Farnham about that day."); -const char *TEXT_29 = N_("I was shocked when I heard of what the townspeople were planning to do that night. I thought that of all people, Lazarus would have had more sense than that. He was an Archbishop, and always seemed to care so much for the townsfolk of Tristram. So many were injured, I could not save them all..."); -const char *TEXT_30 = N_("I remember Lazarus as being a very kind and giving man. He spoke at my mother's funeral, and was supportive of my grandmother and myself in a very troubled time. I pray every night that somehow, he is still alive and safe."); -const char *TEXT_31 = N_("I was there when Lazarus led us into the labyrinth. He spoke of holy retribution, but when we started fighting those hellspawn, he did not so much as lift his mace against them. He just ran deeper into the dim, endless chambers that were filled with the servants of darkness!"); -const char *TEXT_32 = N_("They stab, then bite, then they're all around you. Liar! LIAR! They're all dead! Dead! Do you hear me? They just keep falling and falling... their blood spilling out all over the floor... all his fault..."); -const char *TEXT_33 = N_("I did not know this Lazarus of whom you speak, but I do sense a great conflict within his being. He poses a great danger, and will stop at nothing to serve the powers of darkness which have claimed him as theirs."); -const char *TEXT_34 = N_("Yes, the righteous Lazarus, who was sooo effective against those monsters down there. Didn't help save my leg, did it? Look, I'll give you a free piece of advice. Ask Farnham, he was there."); -const char *TEXT_35 = N_("Abandon your foolish quest. All that awaits you is the wrath of my Master! You are too late to save the child. Now you will join him in Hell!"); -const char *TEXT_37 = N_("Hmm, I don't know what I can really tell you about this that will be of any help. The water that fills our wells comes from an underground spring. I have heard of a tunnel that leads to a great lake - perhaps they are one and the same. Unfortunately, I do not know what would cause our water supply to be tainted."); -const char *TEXT_38 = N_("I have always tried to keep a large supply of foodstuffs and drink in our storage cellar, but with the entire town having no source of fresh water, even our stores will soon run dry. \n \nPlease, do what you can or I don't know what we will do."); -const char *TEXT_39 = N_("I'm glad I caught up to you in time! Our wells have become brackish and stagnant and some of the townspeople have become ill drinking from them. Our reserves of fresh water are quickly running dry. I believe that there is a passage that leads to the springs that serve our town. Please find what has caused this calamity, or we all will surely perish."); -const char *TEXT_40 = N_("Please, you must hurry. Every hour that passes brings us closer to having no water to drink. \n \nWe cannot survive for long without your help."); -const char *TEXT_41 = N_("What's that you say - the mere presence of the demons had caused the water to become tainted? Oh, truly a great evil lurks beneath our town, but your perseverance and courage gives us hope. Please take this ring - perhaps it will aid you in the destruction of such vile creatures."); -const char *TEXT_42 = N_("My grandmother is very weak, and Garda says that we cannot drink the water from the wells. Please, can you do something to help us?"); -const char *TEXT_43 = N_("Pepin has told you the truth. We will need fresh water badly, and soon. I have tried to clear one of the smaller wells, but it reeks of stagnant filth. It must be getting clogged at the source."); -const char *TEXT_44 = N_("You drink water?"); -const char *TEXT_45 = N_("The people of Tristram will die if you cannot restore fresh water to their wells. \n \nKnow this - demons are at the heart of this matter, but they remain ignorant of what they have spawned."); -const char *TEXT_46 = N_("For once, I'm with you. My business runs dry - so to speak - if I have no market to sell to. You better find out what is going on, and soon!"); -const char *TEXT_47 = N_("A book that speaks of a chamber of human bones? Well, a Chamber of Bone is mentioned in certain archaic writings that I studied in the libraries of the East. These tomes inferred that when the Lords of the underworld desired to protect great treasures, they would create domains where those who died in the attempt to steal that treasure would be forever bound to defend it. A twisted, but strangely fitting, end?"); -const char *TEXT_48 = N_("I am afraid that I don't know anything about that, good master. Cain has many books that may be of some help."); -const char *TEXT_49 = N_("This sounds like a very dangerous place. If you venture there, please take great care."); -const char *TEXT_50 = N_("I am afraid that I haven't heard anything about that. Perhaps Cain the Storyteller could be of some help."); -const char *TEXT_51 = N_("I know nothing of this place, but you may try asking Cain. He talks about many things, and it would not surprise me if he had some answers to your question."); -const char *TEXT_52 = N_("Okay, so listen. There's this chamber of wood, see. And his wife, you know - her - tells the tree... cause you gotta wait. Then I says, that might work against him, but if you think I'm gonna PAY for this... you... uh... yeah."); -const char *TEXT_53 = N_("You will become an eternal servant of the dark lords should you perish within this cursed domain. \n \nEnter the Chamber of Bone at your own peril."); -const char *TEXT_54 = N_("A vast and mysterious treasure, you say? Maybe I could be interested in picking up a few things from you... or better yet, don't you need some rare and expensive supplies to get you through this ordeal?"); -const char *TEXT_55 = N_("It seems that the Archbishop Lazarus goaded many of the townsmen into venturing into the Labyrinth to find the King's missing son. He played upon their fears and whipped them into a frenzied mob. None of them were prepared for what lay within the cold earth... Lazarus abandoned them down there - left in the clutches of unspeakable horrors - to die."); -const char *TEXT_56 = N_("Yes, Farnham has mumbled something about a hulking brute who wielded a fierce weapon. I believe he called him a butcher."); -const char *TEXT_57 = N_("By the Light, I know of this vile demon. There were many that bore the scars of his wrath upon their bodies when the few survivors of the charge led by Lazarus crawled from the Cathedral. I don't know what he used to slice open his victims, but it could not have been of this world. It left wounds festering with disease and even I found them almost impossible to treat. Beware if you plan to battle this fiend..."); -const char *TEXT_58 = N_("When Farnham said something about a butcher killing people, I immediately discounted it. But since you brought it up, maybe it is true."); -const char *TEXT_59 = N_("I saw what Farnham calls the Butcher as it swathed a path through the bodies of my friends. He swung a cleaver as large as an axe, hewing limbs and cutting down brave men where they stood. I was separated from the fray by a host of small screeching demons and somehow found the stairway leading out. I never saw that hideous beast again, but his blood-stained visage haunts me to this day."); -const char *TEXT_60 = N_("Big! Big cleaver killing all my friends. Couldn't stop him, had to run away, couldn't save them. Trapped in a room with so many bodies... so many friends... NOOOOOOOOOO!"); -const char *TEXT_61 = N_("The Butcher is a sadistic creature that delights in the torture and pain of others. You have seen his handiwork in the drunkard Farnham. His destruction will do much to ensure the safety of this village."); -const char *TEXT_62 = N_("I know more than you'd think about that grisly fiend. His little friends got a hold of me and managed to get my leg before Griswold pulled me out of that hole. \n \nI'll put it bluntly - kill him before he kills you and adds your corpse to his collection."); -const char *TEXT_63 = N_("Please, listen to me. The Archbishop Lazarus, he led us down here to find the lost prince. The bastard led us into a trap! Now everyone is dead... killed by a demon he called the Butcher. Avenge us! Find this Butcher and slay him so that our souls may finally rest..."); -const char *TEXT_65 = N_("You recite an interesting rhyme written in a style that reminds me of other works. Let me think now - what was it?\n \n...Darkness shrouds the Hidden. Eyes glowing unseen with only the sounds of razor claws briefly scraping to torment those poor souls who have been made sightless for all eternity. The prison for those so damned is named the Halls of the Blind..."); -const char *TEXT_66 = N_("I never much cared for poetry. Occasionally, I had cause to hire minstrels when the inn was doing well, but that seems like such a long time ago now. \n \nWhat? Oh, yes... uh, well, I suppose you could see what someone else knows."); -const char *TEXT_67 = N_("This does seem familiar, somehow. I seem to recall reading something very much like that poem while researching the history of demonic afflictions. It spoke of a place of great evil that... wait - you're not going there are you?"); -const char *TEXT_68 = N_("If you have questions about blindness, you should talk to Pepin. I know that he gave my grandmother a potion that helped clear her vision, so maybe he can help you, too."); -const char *TEXT_69 = N_("I am afraid that I have neither heard nor seen a place that matches your vivid description, my friend. Perhaps Cain the Storyteller could be of some help."); -const char *TEXT_70 = N_("Look here... that's pretty funny, huh? Get it? Blind - look here?"); -const char *TEXT_71 = N_("This is a place of great anguish and terror, and so serves its master well. \n \nTread carefully or you may yourself be staying much longer than you had anticipated."); -const char *TEXT_72 = N_("Lets see, am I selling you something? No. Are you giving me money to tell you about this? No. Are you now leaving and going to talk to the storyteller who lives for this kind of thing? Yes."); -const char *TEXT_73 = N_("You claim to have spoken with Lachdanan? He was a great hero during his life. Lachdanan was an honorable and just man who served his King faithfully for years. But of course, you already know that.\n \nOf those who were caught within the grasp of the King's Curse, Lachdanan would be the least likely to submit to the darkness without a fight, so I suppose that your story could be true. If I were in your place, my friend, I would find a way to release him from his torture."); -const char *TEXT_74 = N_("You speak of a brave warrior long dead! I'll have no such talk of speaking with departed souls in my inn yard, thank you very much."); -const char *TEXT_75 = N_("A golden elixir, you say. I have never concocted a potion of that color before, so I can't tell you how it would effect you if you were to try to drink it. As your healer, I strongly advise that should you find such an elixir, do as Lachdanan asks and DO NOT try to use it."); -const char *TEXT_76 = N_("I've never heard of a Lachdanan before. I'm sorry, but I don't think that I can be of much help to you."); -const char *TEXT_77 = N_("If it is actually Lachdanan that you have met, then I would advise that you aid him. I dealt with him on several occasions and found him to be honest and loyal in nature. The curse that fell upon the followers of King Leoric would fall especially hard upon him."); -const char *TEXT_78 = N_(" Lachdanan is dead. Everybody knows that, and you can't fool me into thinking any other way. You can't talk to the dead. I know!"); -const char *TEXT_79 = N_("You may meet people who are trapped within the Labyrinth, such as Lachdanan. \n \nI sense in him honor and great guilt. Aid him, and you aid all of Tristram."); -const char *TEXT_80 = N_("Wait, let me guess. Cain was swallowed up in a gigantic fissure that opened beneath him. He was incinerated in a ball of hellfire, and can't answer your questions anymore. Oh, that isn't what happened? Then I guess you'll be buying something or you'll be on your way."); -const char *TEXT_81 = N_("Please, don't kill me, just hear me out. I was once Captain of King Leoric's Knights, upholding the laws of this land with justice and honor. Then his dark Curse fell upon us for the role we played in his tragic death. As my fellow Knights succumbed to their twisted fate, I fled from the King's burial chamber, searching for some way to free myself from the Curse. I failed...\n \nI have heard of a Golden Elixir that could lift the Curse and allow my soul to rest, but I have been unable to find it. My strength now wanes, and with it the last of my humanity as well. Please aid me and find the Elixir. I will repay your efforts - I swear upon my honor."); -const char *TEXT_82 = N_("You have not found the Golden Elixir. I fear that I am doomed for eternity. Please, keep trying..."); -const char *TEXT_83 = N_("You have saved my soul from damnation, and for that I am in your debt. If there is ever a way that I can repay you from beyond the grave I will find it, but for now - take my helm. On the journey I am about to take I will have little use for it. May it protect you against the dark powers below. Go with the Light, my friend..."); -const char *TEXT_84 = N_("Griswold speaks of The Anvil of Fury - a legendary artifact long searched for, but never found. Crafted from the metallic bones of the Razor Pit demons, the Anvil of Fury was smelt around the skulls of the five most powerful magi of the underworld. Carved with runes of power and chaos, any weapon or armor forged upon this Anvil will be immersed into the realm of Chaos, imbedding it with magical properties. It is said that the unpredictable nature of Chaos makes it difficult to know what the outcome of this smithing will be..."); -const char *TEXT_85 = N_("Don't you think that Griswold would be a better person to ask about this? He's quite handy, you know."); -const char *TEXT_86 = N_("If you had been looking for information on the Pestle of Curing or the Silver Chalice of Purification, I could have assisted you, my friend. However, in this matter, you would be better served to speak to either Griswold or Cain."); -const char *TEXT_87 = N_("Griswold's father used to tell some of us when we were growing up about a giant anvil that was used to make mighty weapons. He said that when a hammer was struck upon this anvil, the ground would shake with a great fury. Whenever the earth moves, I always remember that story."); -const char *TEXT_88 = N_("Greetings! It's always a pleasure to see one of my best customers! I know that you have been venturing deeper into the Labyrinth, and there is a story I was told that you may find worth the time to listen to...\n \nOne of the men who returned from the Labyrinth told me about a mystic anvil that he came across during his escape. His description reminded me of legends I had heard in my youth about the burning Hellforge where powerful weapons of magic are crafted. The legend had it that deep within the Hellforge rested the Anvil of Fury! This Anvil contained within it the very essence of the demonic underworld...\n \nIt is said that any weapon crafted upon the burning Anvil is imbued with great power. If this anvil is indeed the Anvil of Fury, I may be able to make you a weapon capable of defeating even the darkest lord of Hell! \n \nFind the Anvil for me, and I'll get to work!"); -const char *TEXT_89 = N_("Nothing yet, eh? Well, keep searching. A weapon forged upon the Anvil could be your best hope, and I am sure that I can make you one of legendary proportions."); -const char *TEXT_90 = N_("I can hardly believe it! This is the Anvil of Fury - good work, my friend. Now we'll show those bastards that there are no weapons in Hell more deadly than those made by men! Take this and may Light protect you."); -const char *TEXT_91 = N_("Griswold can't sell his anvil. What will he do then? And I'd be angry too if someone took my anvil!"); -const char *TEXT_92 = N_("There are many artifacts within the Labyrinth that hold powers beyond the comprehension of mortals. Some of these hold fantastic power that can be used by either the Light or the Darkness. Securing the Anvil from below could shift the course of the Sin War towards the Light."); -const char *TEXT_93 = N_("If you were to find this artifact for Griswold, it could put a serious damper on my business here. Awwww, you'll never find it."); -const char *TEXT_94 = N_("The Gateway of Blood and the Halls of Fire are landmarks of mystic origin. Wherever this book you read from resides it is surely a place of great power.\n \nLegends speak of a pedestal that is carved from obsidian stone and has a pool of boiling blood atop its bone encrusted surface. There are also allusions to Stones of Blood that will open a door that guards an ancient treasure...\n \nThe nature of this treasure is shrouded in speculation, my friend, but it is said that the ancient hero Arkaine placed the holy armor Valor in a secret vault. Arkaine was the first mortal to turn the tide of the Sin War and chase the legions of darkness back to the Burning Hells.\n \nJust before Arkaine died, his armor was hidden away in a secret vault. It is said that when this holy armor is again needed, a hero will arise to don Valor once more. Perhaps you are that hero..."); -const char *TEXT_95 = N_("Every child hears the story of the warrior Arkaine and his mystic armor known as Valor. If you could find its resting place, you would be well protected against the evil in the Labyrinth."); -const char *TEXT_96 = N_("Hmm... it sounds like something I should remember, but I've been so busy learning new cures and creating better elixirs that I must have forgotten. Sorry..."); -const char *TEXT_97 = N_("The story of the magic armor called Valor is something I often heard the boys talk about. You had better ask one of the men in the village."); -const char *TEXT_98 = N_("The armor known as Valor could be what tips the scales in your favor. I will tell you that many have looked for it - including myself. Arkaine hid it well, my friend, and it will take more than a bit of luck to unlock the secrets that have kept it concealed oh, lo these many years."); -const char *TEXT_99 = N_("Zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz..."); -const char *TEXT_100 = N_("Should you find these Stones of Blood, use them carefully. \n \nThe way is fraught with danger and your only hope rests within your self trust."); -const char *TEXT_101 = N_("You intend to find the armor known as Valor? \n \nNo one has ever figured out where Arkaine stashed the stuff, and if my contacts couldn't find it, I seriously doubt you ever will either."); -const char *TEXT_102 = N_("I know of only one legend that speaks of such a warrior as you describe. His story is found within the ancient chronicles of the Sin War...\n \nStained by a thousand years of war, blood and death, the Warlord of Blood stands upon a mountain of his tattered victims. His dark blade screams a black curse to the living; a tortured invitation to any who would stand before this Executioner of Hell.\n \nIt is also written that although he was once a mortal who fought beside the Legion of Darkness during the Sin War, he lost his humanity to his insatiable hunger for blood."); -const char *TEXT_103 = N_("I am afraid that I haven't heard anything about such a vicious warrior, good master. I hope that you do not have to fight him, for he sounds extremely dangerous."); -const char *TEXT_104 = N_("Cain would be able to tell you much more about something like this than I would ever wish to know."); -const char *TEXT_105 = N_("If you are to battle such a fierce opponent, may Light be your guide and your defender. I will keep you in my thoughts."); -const char *TEXT_106 = N_("Dark and wicked legends surrounds the one Warlord of Blood. Be well prepared, my friend, for he shows no mercy or quarter."); -const char *TEXT_107 = N_("Always you gotta talk about Blood? What about flowers, and sunshine, and that pretty girl that brings the drinks. Listen here, friend - you're obsessive, you know that?"); -const char *TEXT_108 = N_("His prowess with the blade is awesome, and he has lived for thousands of years knowing only warfare. I am sorry... I can not see if you will defeat him."); -const char *TEXT_109 = N_("I haven't ever dealt with this Warlord you speak of, but he sounds like he's going through a lot of swords. Wouldn't mind supplying his armies..."); -const char *TEXT_110 = N_("My blade sings for your blood, mortal, and by my dark masters it shall not be denied."); -const char *TEXT_111 = N_("Griswold speaks of the Heaven Stone that was destined for the enclave located in the east. It was being taken there for further study. This stone glowed with an energy that somehow granted vision beyond that which a normal man could possess. I do not know what secrets it holds, my friend, but finding this stone would certainly prove most valuable."); -const char *TEXT_112 = N_("The caravan stopped here to take on some supplies for their journey to the east. I sold them quite an array of fresh fruits and some excellent sweetbreads that Garda has just finished baking. Shame what happened to them..."); -const char *TEXT_113 = N_("I don't know what it is that they thought they could see with that rock, but I will say this. If rocks are falling from the sky, you had better be careful!"); -const char *TEXT_114 = N_("Well, a caravan of some very important people did stop here, but that was quite a while ago. They had strange accents and were starting on a long journey, as I recall. \n \nI don't see how you could hope to find anything that they would have been carrying."); -const char *TEXT_115 = N_("Stay for a moment - I have a story you might find interesting. A caravan that was bound for the eastern kingdoms passed through here some time ago. It was supposedly carrying a piece of the heavens that had fallen to earth! The caravan was ambushed by cloaked riders just north of here along the roadway. I searched the wreckage for this sky rock, but it was nowhere to be found. If you should find it, I believe that I can fashion something useful from it."); -const char *TEXT_116 = N_("I am still waiting for you to bring me that stone from the heavens. I know that I can make something powerful out of it."); -const char *TEXT_117 = N_("Let me see that - aye... aye, it is as I believed. Give me a moment...\n \nAh, Here you are. I arranged pieces of the stone within a silver ring that my father left me. I hope it serves you well."); -const char *TEXT_118 = N_("I used to have a nice ring; it was a really expensive one, with blue and green and red and silver. Don't remember what happened to it, though. I really miss that ring..."); -const char *TEXT_119 = N_("The Heaven Stone is very powerful, and were it any but Griswold who bid you find it, I would prevent it. He will harness its powers and its use will be for the good of us all."); -const char *TEXT_120 = N_("If anyone can make something out of that rock, Griswold can. He knows what he is doing, and as much as I try to steal his customers, I respect the quality of his work."); -const char *TEXT_121 = N_("The witch Adria seeks a black mushroom? I know as much about Black Mushrooms as I do about Red Herrings. Perhaps Pepin the Healer could tell you more, but this is something that cannot be found in any of my stories or books."); -const char *TEXT_122 = N_("Let me just say this. Both Garda and I would never, EVER serve black mushrooms to our honored guests. If Adria wants some mushrooms in her stew, then that is her business, but I can't help you find any. Black mushrooms... disgusting!"); -const char *TEXT_123 = N_("The witch told me that you were searching for the brain of a demon to assist me in creating my elixir. It should be of great value to the many who are injured by those foul beasts, if I can just unlock the secrets I suspect that its alchemy holds. If you can remove the brain of a demon when you kill it, I would be grateful if you could bring it to me."); -const char *TEXT_124 = N_("Excellent, this is just what I had in mind. I was able to finish the elixir without this, but it can't hurt to have this to study. Would you please carry this to the witch? I believe that she is expecting it."); -const char *TEXT_125 = N_("I think Ogden might have some mushrooms in the storage cellar. Why don't you ask him?"); -const char *TEXT_126 = N_("If Adria doesn't have one of these, you can bet that's a rare thing indeed. I can offer you no more help than that, but it sounds like... a huge, gargantuan, swollen, bloated mushroom! Well, good hunting, I suppose."); -const char *TEXT_127 = N_("Ogden mixes a MEAN black mushroom, but I get sick if I drink that. Listen, listen... here's the secret - moderation is the key!"); -const char *TEXT_128 = N_("What do we have here? Interesting, it looks like a book of reagents. Keep your eyes open for a black mushroom. It should be fairly large and easy to identify. If you find it, bring it to me, won't you?"); -const char *TEXT_129 = N_("It's a big, black mushroom that I need. Now run off and get it for me so that I can use it for a special concoction that I am working on."); -const char *TEXT_130 = N_("Yes, this will be perfect for a brew that I am creating. By the way, the healer is looking for the brain of some demon or another so he can treat those who have been afflicted by their poisonous venom. I believe that he intends to make an elixir from it. If you help him find what he needs, please see if you can get a sample of the elixir for me."); -const char *TEXT_131 = N_("Why have you brought that here? I have no need for a demon's brain at this time. I do need some of the elixir that the Healer is working on. He needs that grotesque organ that you are holding, and then bring me the elixir. Simple when you think about it, isn't it?"); -const char *TEXT_132 = N_("What? Now you bring me that elixir from the healer? I was able to finish my brew without it. Why don't you just keep it..."); -const char *TEXT_133 = N_("I don't have any mushrooms of any size or color for sale. How about something a bit more useful?"); -const char *TEXT_134 = N_("So, the legend of the Map is real. Even I never truly believed any of it! I suppose it is time that I told you the truth about who I am, my friend. You see, I am not all that I seem...\n \nMy true name is Deckard Cain the Elder, and I am the last descendant of an ancient Brotherhood that was dedicated to keeping and safeguarding the secrets of a timeless evil. An evil that quite obviously has now been released...\n \nThe evil that you move against is the dark Lord of Terror - known to mortal men as Diablo. It was he who was imprisoned within the Labyrinth many centuries ago. The Map that you hold now was created ages ago to mark the time when Diablo would rise again from his imprisonment. When the two stars on that map align, Diablo will be at the height of his power. He will be all but invincible...\n \nYou are now in a race against time, my friend! Find Diablo and destroy him before the stars align, for we may never have a chance to rid the world of his evil again!"); -const char *TEXT_135 = N_("Our time is running short! I sense his dark power building and only you can stop him from attaining his full might."); -const char *TEXT_136 = N_("I am sure that you tried your best, but I fear that even your strength and will may not be enough. Diablo is now at the height of his earthly power, and you will need all your courage and strength to defeat him. May the Light protect and guide you, my friend. I will help in any way that I am able."); -const char *TEXT_137 = N_("If the witch can't help you and suggests you see Cain, what makes you think that I would know anything? It sounds like this is a very serious matter. You should hurry along and see the storyteller as Adria suggests."); -const char *TEXT_138 = N_("I can't make much of the writing on this map, but perhaps Adria or Cain could help you decipher what this refers to. \n \nI can see that it is a map of the stars in our sky, but any more than that is beyond my talents."); -const char *TEXT_139 = N_("The best person to ask about that sort of thing would be our storyteller. \n \nCain is very knowledgeable about ancient writings, and that is easily the oldest looking piece of paper that I have ever seen."); -const char *TEXT_140 = N_("I have never seen a map of this sort before. Where'd you get it? Although I have no idea how to read this, Cain or Adria may be able to provide the answers that you seek."); -const char *TEXT_141 = N_("Listen here, come close. I don't know if you know what I know, but you have really got somethin' here. That's a map."); -const char *TEXT_142 = N_("Oh, I'm afraid this does not bode well at all. This map of the stars portends great disaster, but its secrets are not mine to tell. The time has come for you to have a very serious conversation with the Storyteller..."); -const char *TEXT_143 = N_("I've been looking for a map, but that certainly isn't it. You should show that to Adria - she can probably tell you what it is. I'll say one thing; it looks old, and old usually means valuable."); -const char *TEXT_144 = N_("Pleeeease, no hurt. No Kill. Keep alive and next time good bring to you."); -const char *TEXT_145 = N_("Something for you I am making. Again, not kill Gharbad. Live and give good. \n \nYou take this as proof I keep word..."); -const char *TEXT_146 = N_("Nothing yet! Almost done. \n \nVery powerful, very strong. Live! Live! \n \nNo pain and promise I keep!"); -const char *TEXT_147 = N_("This too good for you. Very Powerful! You want - you take!"); -const char *TEXT_148 = N_("What?! Why are you here? All these interruptions are enough to make one insane. Here, take this and leave me to my work. Trouble me no more!"); -const char *TEXT_149 = N_("Arrrrgh! Your curiosity will be the death of you!!!"); -const char *TEXT_150 = N_("Hello, my friend. Stay awhile and listen..."); -const char *TEXT_151 = N_("While you are venturing deeper into the Labyrinth you may find tomes of great knowledge hidden there. \n \nRead them carefully for they can tell you things that even I cannot."); -const char *TEXT_152 = N_("I know of many myths and legends that may contain answers to questions that may arise in your journeys into the Labyrinth. If you come across challenges and questions to which you seek knowledge, seek me out and I will tell you what I can."); -const char *TEXT_153 = N_("Griswold - a man of great action and great courage. I bet he never told you about the time he went into the Labyrinth to save Wirt, did he? He knows his fair share of the dangers to be found there, but then again - so do you. He is a skilled craftsman, and if he claims to be able to help you in any way, you can count on his honesty and his skill."); -const char *TEXT_154 = N_("Ogden has owned and run the Rising Sun Inn and Tavern for almost four years now. He purchased it just a few short months before everything here went to hell. He and his wife Garda do not have the money to leave as they invested all they had in making a life for themselves here. He is a good man with a deep sense of responsibility."); -const char *TEXT_155 = N_("Poor Farnham. He is a disquieting reminder of the doomed assembly that entered into the Cathedral with Lazarus on that dark day. He escaped with his life, but his courage and much of his sanity were left in some dark pit. He finds comfort only at the bottom of his tankard nowadays, but there are occasional bits of truth buried within his constant ramblings."); -const char *TEXT_156 = N_("The witch, Adria, is an anomaly here in Tristram. She arrived shortly after the Cathedral was desecrated while most everyone else was fleeing. She had a small hut constructed at the edge of town, seemingly overnight, and has access to many strange and arcane artifacts and tomes of knowledge that even I have never seen before."); -const char *TEXT_157 = N_("The story of Wirt is a frightening and tragic one. He was taken from the arms of his mother and dragged into the labyrinth by the small, foul demons that wield wicked spears. There were many other children taken that day, including the son of King Leoric. The Knights of the palace went below, but never returned. The Blacksmith found the boy, but only after the foul beasts had begun to torture him for their sadistic pleasures."); -const char *TEXT_158 = N_("Ah, Pepin. I count him as a true friend - perhaps the closest I have here. He is a bit addled at times, but never a more caring or considerate soul has existed. His knowledge and skills are equaled by few, and his door is always open."); -const char *TEXT_159 = N_("Gillian is a fine woman. Much adored for her high spirits and her quick laugh, she holds a special place in my heart. She stays on at the tavern to support her elderly grandmother who is too sick to travel. I sometimes fear for her safety, but I know that any man in the village would rather die than see her harmed."); -const char *TEXT_160 = N_("Greetings, good master. Welcome to the Tavern of the Rising Sun!"); -const char *TEXT_161 = N_("Many adventurers have graced the tables of my tavern, and ten times as many stories have been told over as much ale. The only thing that I ever heard any of them agree on was this old axiom. Perhaps it will help you. You can cut the flesh, but you must crush the bone."); -const char *TEXT_162 = N_("Griswold the blacksmith is extremely knowledgeable about weapons and armor. If you ever need work done on your gear, he is definitely the man to see."); -const char *TEXT_163 = N_("Farnham spends far too much time here, drowning his sorrows in cheap ale. I would make him leave, but he did suffer so during his time in the Labyrinth."); -const char *TEXT_164 = N_("Adria is wise beyond her years, but I must admit - she frightens me a little. \n \nWell, no matter. If you ever have need to trade in items of sorcery, she maintains a strangely well-stocked hut just across the river."); -const char *TEXT_165 = N_("If you want to know more about the history of our village, the storyteller Cain knows quite a bit about the past."); -const char *TEXT_166 = N_("Wirt is a rapscallion and a little scoundrel. He was always getting into trouble, and it's no surprise what happened to him. \n \nHe probably went fooling about someplace that he shouldn't have been. I feel sorry for the boy, but I don't abide the company that he keeps."); -const char *TEXT_167 = N_("Pepin is a good man - and certainly the most generous in the village. He is always attending to the needs of others, but trouble of some sort or another does seem to follow him wherever he goes..."); -const char *TEXT_168 = N_("Gillian, my Barmaid? If it were not for her sense of duty to her grand-dam, she would have fled from here long ago. \n \nGoodness knows I begged her to leave, telling her that I would watch after the old woman, but she is too sweet and caring to have done so."); -const char *TEXT_169 = N_("What ails you, my friend?"); -const char *TEXT_170 = N_("I have made a very interesting discovery. Unlike us, the creatures in the Labyrinth can heal themselves without the aid of potions or magic. If you hurt one of the monsters, make sure it is dead or it very well may regenerate itself."); -const char *TEXT_171 = N_("Before it was taken over by, well, whatever lurks below, the Cathedral was a place of great learning. There are many books to be found there. If you find any, you should read them all, for some may hold secrets to the workings of the Labyrinth."); -const char *TEXT_172 = N_("Griswold knows as much about the art of war as I do about the art of healing. He is a shrewd merchant, but his work is second to none. Oh, I suppose that may be because he is the only blacksmith left here."); -const char *TEXT_173 = N_("Cain is a true friend and a wise sage. He maintains a vast library and has an innate ability to discern the true nature of many things. If you ever have any questions, he is the person to go to."); -const char *TEXT_174 = N_("Even my skills have been unable to fully heal Farnham. Oh, I have been able to mend his body, but his mind and spirit are beyond anything I can do."); -const char *TEXT_175 = N_("While I use some limited forms of magic to create the potions and elixirs I store here, Adria is a true sorceress. She never seems to sleep, and she always has access to many mystic tomes and artifacts. I believe her hut may be much more than the hovel it appears to be, but I can never seem to get inside the place."); -const char *TEXT_176 = N_("Poor Wirt. I did all that was possible for the child, but I know he despises that wooden peg that I was forced to attach to his leg. His wounds were hideous. No one - and especially such a young child - should have to suffer the way he did."); -const char *TEXT_177 = N_("I really don't understand why Ogden stays here in Tristram. He suffers from a slight nervous condition, but he is an intelligent and industrious man who would do very well wherever he went. I suppose it may be the fear of the many murders that happen in the surrounding countryside, or perhaps the wishes of his wife that keep him and his family where they are."); -const char *TEXT_178 = N_("Ogden's barmaid is a sweet girl. Her grandmother is quite ill, and suffers from delusions. \n \nShe claims that they are visions, but I have no proof of that one way or the other."); -const char *TEXT_179 = N_("Good day! How may I serve you?"); -const char *TEXT_180 = N_("My grandmother had a dream that you would come and talk to me. She has visions, you know and can see into the future."); -const char *TEXT_181 = N_("The woman at the edge of town is a witch! She seems nice enough, and her name, Adria, is very pleasing to the ear, but I am very afraid of her. \n \nIt would take someone quite brave, like you, to see what she is doing out there."); -const char *TEXT_182 = N_("Our Blacksmith is a point of pride to the people of Tristram. Not only is he a master craftsman who has won many contests within his guild, but he received praises from our King Leoric himself - may his soul rest in peace. Griswold is also a great hero; just ask Cain."); -const char *TEXT_183 = N_("Cain has been the storyteller of Tristram for as long as I can remember. He knows so much, and can tell you just about anything about almost everything."); -const char *TEXT_184 = N_("Farnham is a drunkard who fills his belly with ale and everyone else's ears with nonsense. \n \nI know that both Pepin and Ogden feel sympathy for him, but I get so frustrated watching him slip farther and farther into a befuddled stupor every night."); -const char *TEXT_185 = N_("Pepin saved my grandmother's life, and I know that I can never repay him for that. His ability to heal any sickness is more powerful than the mightiest sword and more mysterious than any spell you can name. If you ever are in need of healing, Pepin can help you."); -const char *TEXT_186 = N_("I grew up with Wirt's mother, Canace. Although she was only slightly hurt when those hideous creatures stole him, she never recovered. I think she died of a broken heart. Wirt has become a mean-spirited youngster, looking only to profit from the sweat of others. I know that he suffered and has seen horrors that I cannot even imagine, but some of that darkness hangs over him still."); -const char *TEXT_187 = N_("Ogden and his wife have taken me and my grandmother into their home and have even let me earn a few gold pieces by working at the inn. I owe so much to them, and hope one day to leave this place and help them start a grand hotel in the east."); -const char *TEXT_188 = N_("Well, what can I do for ya?"); -const char *TEXT_189 = N_("If you're looking for a good weapon, let me show this to you. Take your basic blunt weapon, such as a mace. Works like a charm against most of those undying horrors down there, and there's nothing better to shatter skinny little skeletons!"); -const char *TEXT_190 = N_("The axe? Aye, that's a good weapon, balanced against any foe. Look how it cleaves the air, and then imagine a nice fat demon head in its path. Keep in mind, however, that it is slow to swing - but talk about dealing a heavy blow!"); -const char *TEXT_191 = N_("Look at that edge, that balance. A sword in the right hands, and against the right foe, is the master of all weapons. Its keen blade finds little to hack or pierce on the undead, but against a living, breathing enemy, a sword will better slice their flesh!"); -const char *TEXT_192 = N_("Your weapons and armor will show the signs of your struggles against the Darkness. If you bring them to me, with a bit of work and a hot forge, I can restore them to top fighting form."); -const char *TEXT_193 = N_("While I have to practically smuggle in the metals and tools I need from caravans that skirt the edges of our damned town, that witch, Adria, always seems to get whatever she needs. If I knew even the smallest bit about how to harness magic as she did, I could make some truly incredible things."); -const char *TEXT_194 = N_("Gillian is a nice lass. Shame that her gammer is in such poor health or I would arrange to get both of them out of here on one of the trading caravans."); -const char *TEXT_195 = N_("Sometimes I think that Cain talks too much, but I guess that is his calling in life. If I could bend steel as well as he can bend your ear, I could make a suit of court plate good enough for an Emperor!"); -const char *TEXT_196 = N_("I was with Farnham that night that Lazarus led us into Labyrinth. I never saw the Archbishop again, and I may not have survived if Farnham was not at my side. I fear that the attack left his soul as crippled as, well, another did my leg. I cannot fight this battle for him now, but I would if I could."); -const char *TEXT_197 = N_("A good man who puts the needs of others above his own. You won't find anyone left in Tristram - or anywhere else for that matter - who has a bad thing to say about the healer."); -const char *TEXT_198 = N_("That lad is going to get himself into serious trouble... or I guess I should say, again. I've tried to interest him in working here and learning an honest trade, but he prefers the high profits of dealing in goods of dubious origin. I cannot hold that against him after what happened to him, but I do wish he would at least be careful."); -const char *TEXT_199 = N_("The Innkeeper has little business and no real way of turning a profit. He manages to make ends meet by providing food and lodging for those who occasionally drift through the village, but they are as likely to sneak off into the night as they are to pay him. If it weren't for the stores of grains and dried meats he kept in his cellar, why, most of us would have starved during that first year when the entire countryside was overrun by demons."); -const char *TEXT_200 = N_("Can't a fella drink in peace?"); -const char *TEXT_201 = N_("The gal who brings the drinks? Oh, yeah, what a pretty lady. So nice, too."); -const char *TEXT_202 = N_("Why don't that old crone do somethin' for a change. Sure, sure, she's got stuff, but you listen to me... she's unnatural. I ain't never seen her eat or drink - and you can't trust somebody who doesn't drink at least a little."); -const char *TEXT_203 = N_("Cain isn't what he says he is. Sure, sure, he talks a good story... some of 'em are real scary or funny... but I think he knows more than he knows he knows."); -const char *TEXT_204 = N_("Griswold? Good old Griswold. I love him like a brother! We fought together, you know, back when... we... Lazarus... Lazarus... Lazarus!!!"); -const char *TEXT_205 = N_("Hehehe, I like Pepin. He really tries, you know. Listen here, you should make sure you get to know him. Good fella like that with people always wantin' help. Hey, I guess that would be kinda like you, huh hero? I was a hero too..."); -const char *TEXT_206 = N_("Wirt is a kid with more problems than even me, and I know all about problems. Listen here - that kid is gotta sweet deal, but he's been there, you know? Lost a leg! Gotta walk around on a piece of wood. So sad, so sad..."); -const char *TEXT_207 = N_("Ogden is the best man in town. I don't think his wife likes me much, but as long as she keeps tappin' kegs, I'll like her just fine. Seems like I been spendin' more time with Ogden than most, but he's so good to me..."); -const char *TEXT_208 = N_("I wanna tell ya sumthin', 'cause I know all about this stuff. It's my specialty. This here is the best... theeeee best! That other ale ain't no good since those stupid dogs..."); -const char *TEXT_209 = N_("No one ever lis... listens to me. Somewhere - I ain't too sure - but somewhere under the church is a whole pile o' gold. Gleamin' and shinin' and just waitin' for someone to get it."); -const char *TEXT_210 = N_("I know you gots your own ideas, and I know you're not gonna believe this, but that weapon you got there - it just ain't no good against those big brutes! Oh, I don't care what Griswold says, they can't make anything like they used to in the old days..."); -const char *TEXT_211 = N_("If I was you... and I ain't... but if I was, I'd sell all that stuff you got and get out of here. That boy out there... He's always got somethin' good, but you gotta give him some gold or he won't even show you what he's got."); -const char *TEXT_212 = N_("I sense a soul in search of answers..."); -const char *TEXT_213 = N_("Wisdom is earned, not given. If you discover a tome of knowledge, devour its words. Should you already have knowledge of the arcane mysteries scribed within a book, remember - that level of mastery can always increase."); -const char *TEXT_214 = N_("The greatest power is often the shortest lived. You may find ancient words of power written upon scrolls of parchment. The strength of these scrolls lies in the ability of either apprentice or adept to cast them with equal ability. Their weakness is that they must first be read aloud and can never be kept at the ready in your mind. Know also that these scrolls can be read but once, so use them with care."); -const char *TEXT_215 = N_("Though the heat of the sun is beyond measure, the mere flame of a candle is of greater danger. No energies, no matter how great, can be used without the proper focus. For many spells, ensorcelled Staves may be charged with magical energies many times over. I have the ability to restore their power - but know that nothing is done without a price."); -const char *TEXT_216 = N_("The sum of our knowledge is in the sum of its people. Should you find a book or scroll that you cannot decipher, do not hesitate to bring it to me. If I can make sense of it I will share what I find."); -const char *TEXT_217 = N_("To a man who only knows Iron, there is no greater magic than Steel. The blacksmith Griswold is more of a sorcerer than he knows. His ability to meld fire and metal is unequaled in this land."); -const char *TEXT_218 = N_("Corruption has the strength of deceit, but innocence holds the power of purity. The young woman Gillian has a pure heart, placing the needs of her matriarch over her own. She fears me, but it is only because she does not understand me."); -const char *TEXT_219 = N_("A chest opened in darkness holds no greater treasure than when it is opened in the light. The storyteller Cain is an enigma, but only to those who do not look. His knowledge of what lies beneath the cathedral is far greater than even he allows himself to realize."); -const char *TEXT_220 = N_("The higher you place your faith in one man, the farther it has to fall. Farnham has lost his soul, but not to any demon. It was lost when he saw his fellow townspeople betrayed by the Archbishop Lazarus. He has knowledge to be gleaned, but you must separate fact from fantasy."); -const char *TEXT_221 = N_("The hand, the heart and the mind can perform miracles when they are in perfect harmony. The healer Pepin sees into the body in a way that even I cannot. His ability to restore the sick and injured is magnified by his understanding of the creation of elixirs and potions. He is as great an ally as you have in Tristram."); -const char *TEXT_222 = N_("There is much about the future we cannot see, but when it comes it will be the children who wield it. The boy Wirt has a blackness upon his soul, but he poses no threat to the town or its people. His secretive dealings with the urchins and unspoken guilds of nearby towns gain him access to many devices that cannot be easily found in Tristram. While his methods may be reproachful, Wirt can provide assistance for your battle against the encroaching Darkness."); -const char *TEXT_223 = N_("Earthen walls and thatched canopy do not a home create. The innkeeper Ogden serves more of a purpose in this town than many understand. He provides shelter for Gillian and her matriarch, maintains what life Farnham has left to him, and provides an anchor for all who are left in the town to what Tristram once was. His tavern, and the simple pleasures that can still be found there, provide a glimpse of a life that the people here remember. It is that memory that continues to feed their hopes for your success."); -const char *TEXT_224 = N_("Pssst... over here..."); -const char *TEXT_225 = N_("Not everyone in Tristram has a use - or a market - for everything you will find in the labyrinth. Not even me, as hard as that is to believe. \n \nSometimes, only you will be able to find a purpose for some things."); -const char *TEXT_226 = N_("Don't trust everything the drunk says. Too many ales have fogged his vision and his good sense."); -const char *TEXT_227 = N_("In case you haven't noticed, I don't buy anything from Tristram. I am an importer of quality goods. If you want to peddle junk, you'll have to see Griswold, Pepin or that witch, Adria. I'm sure that they will snap up whatever you can bring them..."); -const char *TEXT_228 = N_("I guess I owe the blacksmith my life - what there is of it. Sure, Griswold offered me an apprenticeship at the smithy, and he is a nice enough guy, but I'll never get enough money to... well, let's just say that I have definite plans that require a large amount of gold."); -const char *TEXT_229 = N_("If I were a few years older, I would shower her with whatever riches I could muster, and let me assure you I can get my hands on some very nice stuff. Gillian is a beautiful girl who should get out of Tristram as soon as it is safe. Hmmm... maybe I'll take her with me when I go..."); -const char *TEXT_230 = N_("Cain knows too much. He scares the life out of me - even more than that woman across the river. He keeps telling me about how lucky I am to be alive, and how my story is foretold in legend. I think he's off his crock."); -const char *TEXT_231 = N_("Farnham - now there is a man with serious problems, and I know all about how serious problems can be. He trusted too much in the integrity of one man, and Lazarus led him into the very jaws of death. Oh, I know what it's like down there, so don't even start telling me about your plans to destroy the evil that dwells in that Labyrinth. Just watch your legs..."); -const char *TEXT_232 = N_("As long as you don't need anything reattached, old Pepin is as good as they come. \n \nIf I'd have had some of those potions he brews, I might still have my leg..."); -const char *TEXT_233 = N_("Adria truly bothers me. Sure, Cain is creepy in what he can tell you about the past, but that witch can see into your past. She always has some way to get whatever she needs, too. Adria gets her hands on more merchandise than I've seen pass through the gates of the King's Bazaar during High Festival."); -const char *TEXT_234 = N_("Ogden is a fool for staying here. I could get him out of town for a very reasonable price, but he insists on trying to make a go of it with that stupid tavern. I guess at the least he gives Gillian a place to work, and his wife Garda does make a superb Shepherd's pie..."); -const char *TEXT_235 = N_("Beyond the Hall of Heroes lies the Chamber of Bone. Eternal death awaits any who would seek to steal the treasures secured within this room. So speaks the Lord of Terror, and so it is written."); -const char *TEXT_236 = N_("...and so, locked beyond the Gateway of Blood and past the Hall of Fire, Valor awaits for the Hero of Light to awaken..."); -const char *TEXT_237 = N_("I can see what you see not.\nVision milky then eyes rot.\nWhen you turn they will be gone,\nWhispering their hidden song.\nThen you see what cannot be,\nShadows move where light should be.\nOut of darkness, out of mind,\nCast down into the Halls of the Blind."); -const char *TEXT_238 = N_("The armories of Hell are home to the Warlord of Blood. In his wake lay the mutilated bodies of thousands. Angels and men alike have been cut down to fulfill his endless sacrifices to the Dark ones who scream for one thing - blood."); -const char *TEXT_249 = N_("Take heed and bear witness to the truths that lie herein, for they are the last legacy of the Horadrim. There is a war that rages on even now, beyond the fields that we know - between the utopian kingdoms of the High Heavens and the chaotic pits of the Burning Hells. This war is known as the Great Conflict, and it has raged and burned longer than any of the stars in the sky. Neither side ever gains sway for long as the forces of Light and Darkness constantly vie for control over all creation."); -const char *TEXT_250 = N_("Take heed and bear witness to the truths that lie herein, for they are the last legacy of the Horadrim. When the Eternal Conflict between the High Heavens and the Burning Hells falls upon mortal soil, it is called the Sin War. Angels and Demons walk amongst humanity in disguise, fighting in secret, away from the prying eyes of mortals. Some daring, powerful mortals have even allied themselves with either side, and helped to dictate the course of the Sin War."); -const char *TEXT_251 = N_("Take heed and bear witness to the truths that lie herein, for they are the last legacy of the Horadrim. Nearly three hundred years ago, it came to be known that the Three Prime Evils of the Burning Hells had mysteriously come to our world. The Three Brothers ravaged the lands of the east for decades, while humanity was left trembling in their wake. Our Order - the Horadrim - was founded by a group of secretive magi to hunt down and capture the Three Evils once and for all.\n \nThe original Horadrim captured two of the Three within powerful artifacts known as Soulstones and buried them deep beneath the desolate eastern sands. The third Evil escaped capture and fled to the west with many of the Horadrim in pursuit. The Third Evil - known as Diablo, the Lord of Terror - was eventually captured, his essence set in a Soulstone and buried within this Labyrinth.\n \nBe warned that the soulstone must be kept from discovery by those not of the faith. If Diablo were to be released, he would seek a body that is easily controlled as he would be very weak - perhaps that of an old man or a child."); -const char *TEXT_252 = N_("So it came to be that there was a great revolution within the Burning Hells known as The Dark Exile. The Lesser Evils overthrew the Three Prime Evils and banished their spirit forms to the mortal realm. The demons Belial (the Lord of Lies) and Azmodan (the Lord of Sin) fought to claim rulership of Hell during the absence of the Three Brothers. All of Hell polarized between the factions of Belial and Azmodan while the forces of the High Heavens continually battered upon the very Gates of Hell."); -const char *TEXT_253 = N_("Many demons traveled to the mortal realm in search of the Three Brothers. These demons were followed to the mortal plane by Angels who hunted them throughout the vast cities of the East. The Angels allied themselves with a secretive Order of mortal magi named the Horadrim, who quickly became adept at hunting demons. They also made many dark enemies in the underworlds."); -const char *TEXT_254 = N_("So it came to be that the Three Prime Evils were banished in spirit form to the mortal realm and after sewing chaos across the East for decades, they were hunted down by the cursed Order of the mortal Horadrim. The Horadrim used artifacts called Soulstones to contain the essence of Mephisto, the Lord of Hatred and his brother Baal, the Lord of Destruction. The youngest brother - Diablo, the Lord of Terror - escaped to the west.\n \nEventually the Horadrim captured Diablo within a Soulstone as well, and buried him under an ancient, forgotten Cathedral. There, the Lord of Terror sleeps and awaits the time of his rebirth. Know ye that he will seek a body of youth and power to possess - one that is innocent and easily controlled. He will then arise to free his Brothers and once more fan the flames of the Sin War..."); -const char *TEXT_255 = N_("All praises to Diablo - Lord of Terror and Survivor of The Dark Exile. When he awakened from his long slumber, my Lord and Master spoke to me of secrets that few mortals know. He told me the kingdoms of the High Heavens and the pits of the Burning Hells engage in an eternal war. He revealed the powers that have brought this discord to the realms of man. My lord has named the battle for this world and all who exist here the Sin War."); -const char *TEXT_256 = N_("Glory and Approbation to Diablo - Lord of Terror and Leader of the Three. My Lord spoke to me of his two Brothers, Mephisto and Baal, who were banished to this world long ago. My Lord wishes to bide his time and harness his awesome power so that he may free his captive brothers from their tombs beneath the sands of the east. Once my Lord releases his Brothers, the Sin War will once again know the fury of the Three."); -const char *TEXT_257 = N_("Hail and Sacrifice to Diablo - Lord of Terror and Destroyer of Souls. When I awoke my Master from his sleep, he attempted to possess a mortal's form. Diablo attempted to claim the body of King Leoric, but my Master was too weak from his imprisonment. My Lord required a simple and innocent anchor to this world, and so found the boy Albrecht to be perfect for the task. While the good King Leoric was left maddened by Diablo's unsuccessful possession, I kidnapped his son Albrecht and brought him before my Master. I now await Diablo's call and pray that I will be rewarded when he at last emerges as the Lord of this world."); -const char *TEXT_258 = N_("Thank goodness you've returned!\nMuch has changed since you lived here, my friend. All was peaceful until the dark riders came and destroyed our village. Many were cut down where they stood, and those who took up arms were slain or dragged away to become slaves - or worse. The church at the edge of town has been desecrated and is being used for dark rituals. The screams that echo in the night are inhuman, but some of our townsfolk may yet survive. Follow the path that lies between my tavern and the blacksmith shop to find the church and save who you can. \n \nPerhaps I can tell you more if we speak again. Good luck."); -const char *TEXT_267 = N_("Maintain your quest. Finding a treasure that is lost is not easy. Finding a treasure that is hidden less so. I will leave you with this. Do not let the sands of time confuse your search."); -const char *TEXT_268 = N_("A what?! This is foolishness. There's no treasure buried here in Tristram. Let me see that!! Ah, Look these drawings are inaccurate. They don't match our town at all. I'd keep my mind on what lies below the cathedral and not what lies below our topsoil."); -const char *TEXT_269 = N_("I really don't have time to discuss some map you are looking for. I have many sick people that require my help and yours as well."); -const char *TEXT_270 = N_("The once proud Iswall is trapped deep beneath the surface of this world. His honor stripped and his visage altered. He is trapped in immortal torment. Charged to conceal the very thing that could free him."); -const char *TEXT_271 = N_("I'll bet that Wirt saw you coming and put on an act just so he could laugh at you later when you were running around the town with your nose in the dirt. I'd ignore it."); -const char *TEXT_272 = N_("There was a time when this town was a frequent stop for travelers from far and wide. Much has changed since then. But hidden caves and buried treasure are common fantasies of any child. Wirt seldom indulges in youthful games. So it may just be his imagination."); -const char *TEXT_273 = N_("Listen here. Come close. I don't know if you know what I know, but you've have really got something here. That's a map."); -const char *TEXT_274 = N_("My grandmother often tells me stories about the strange forces that inhabit the graveyard outside of the church. And it may well interest you to hear one of them. She said that if you were to leave the proper offering in the cemetery, enter the cathedral to pray for the dead, and then return, the offering would be altered in some strange way. I don't know if this is just the talk of an old sick woman, but anything seems possible these days."); -const char *TEXT_275 = N_("Hmmm. A vast and mysterious treasure you say. Mmmm. Maybe I could be interested in picking up a few things from you. Or better yet, don't you need some rare and expensive supplies to get you through this ordeal?"); -const char *TEXT_277 = N_("So, you're the hero everyone's been talking about. Perhaps you could help a poor, simple farmer out of a terrible mess? At the edge of my orchard, just south of here, there's a horrible thing swelling out of the ground! I can't get to my crops or my bales of hay, and my poor cows will starve. The witch gave this to me and said that it would blast that thing out of my field. If you could destroy it, I would be forever grateful. I'd do it myself, but someone has to stay here with the cows..."); -const char *TEXT_278 = N_("I knew that it couldn't be as simple as that witch made it sound. It's a sad world when you can't even trust your neighbors."); -const char *TEXT_279 = N_("Is it gone? Did you send it back to the dark recesses of Hades that spawned it? You what? Oh, don't tell me you lost it! Those things don't come cheap, you know. You've got to find it, and then blast that horror out of our town."); -const char *TEXT_280 = N_("I heard the explosion from here! Many thanks to you, kind stranger. What with all these things comin' out of the ground, monsters taking over the church, and so forth, these are trying times. I am but a poor farmer, but here -- take this with my great thanks."); -const char *TEXT_281 = N_("Oh, such a trouble I have...maybe...No, I couldn't impose on you, what with all the other troubles. Maybe after you've cleansed the church of some of those creatures you could come back... and spare a little time to help a poor farmer?"); -const char *TEXT_282 = N_("Waaaah! (sniff) Waaaah! (sniff)"); -const char *TEXT_283 = N_("I lost Theo! I lost my best friend! We were playing over by the river, and Theo said he wanted to go look at the big green thing. I said we shouldn't, but we snuck over there, and then suddenly this BUG came out! We ran away but Theo fell down and the bug GRABBED him and took him away!"); -const char *TEXT_284 = N_("Didja find him? You gotta find Theodore, please! He's just little. He can't take care of himself! Please!"); -const char *TEXT_285 = N_("You found him! You found him! Thank you! Oh Theo, did those nasty bugs scare you? Hey! Ugh! There's something stuck to your fur! Ick! Come on, Theo, let's go home! Thanks again, hero person!"); -const char *TEXT_286 = N_("We have long lain dormant, and the time to awaken has come. After our long sleep, we are filled with great hunger. Soon, now, we shall feed..."); -const char *TEXT_287 = N_("Have you been enjoying yourself, little mammal? How pathetic. Your little world will be no challenge at all."); -const char *TEXT_288 = N_("These lands shall be defiled, and our brood shall overrun the fields that men call home. Our tendrils shall envelop this world, and we will feast on the flesh of its denizens. Man shall become our chattel and sustenance."); -const char *TEXT_289 = N_("Ah, I can smell you...you are close! Close! Ssss...the scent of blood and fear...how enticing..."); -const char *TEXT_296 = N_("And in the year of the Golden Light, it was so decreed that a great Cathedral be raised. The cornerstone of this holy place was to be carved from the translucent stone Antyrael, named for the Angel who shared his power with the Horadrim. \n \nIn the Year of Drawing Shadows, the ground shook and the Cathedral shattered and fell. As the building of catacombs and castles began and man stood against the ravages of the Sin War, the ruins were scavenged for their stones. And so it was that the cornerstone vanished from the eyes of man. \n \nThe stone was of this world -- and of all worlds -- as the Light is both within all things and beyond all things. Light and unity are the products of this holy foundation, a unity of purpose and a unity of possession."); -const char *TEXT_297 = N_("Moo."); -const char *TEXT_298 = N_("I said, Moo."); -const char *TEXT_299 = N_("Look I'm just a cow, OK?"); -const char *TEXT_300 = N_("All right, all right. I'm not really a cow. I don't normally go around like this; but, I was sitting at home minding my own business and all of a sudden these bugs & vines & bulbs & stuff started coming out of the floor... it was horrible! If only I had something normal to wear, it wouldn't be so bad. Hey! Could you go back to my place and get my suit for me? The brown one, not the gray one, that's for evening wear. I'd do it myself, but I don't want anyone seeing me like this. Here, take this, you might need it... to kill those things that have overgrown everything. You can't miss my house, it's just south of the fork in the river... you know... the one with the overgrown vegetable garden."); -const char *TEXT_301 = N_("What are you wasting time for? Go get my suit! And hurry! That Holstein over there keeps winking at me!"); -const char *TEXT_302 = N_("Hey, have you got my suit there? Quick, pass it over! These ears itch like you wouldn't believe!"); -const char *TEXT_303 = N_("No no no no! This is my GRAY suit! It's for evening wear! Formal occasions! I can't wear THIS. What are you, some kind of weirdo? I need the BROWN suit."); -const char *TEXT_304 = N_("Ahh, that's MUCH better. Whew! At last, some dignity! Are my antlers on straight? Good. Look, thanks a lot for helping me out. Here, take this as a gift; and, you know... a little fashion tip... you could use a little... you could use a new... yknowwhatImean? The whole adventurer motif is just so... retro. Just a word of advice, eh? Ciao."); -const char *TEXT_305 = N_("Look. I'm a cow. And you, you're monster bait. Get some experience under your belt! We'll talk..."); -const char *TEXT_307 = N_("It must truly be a fearsome task I've set before you. If there was just some way that I could... would a flagon of some nice, fresh milk help?"); -const char *TEXT_308 = N_("Oh, I could use your help, but perhaps after you've saved the catacombs from the desecration of those beasts."); -const char *TEXT_309 = N_("I need something done, but I couldn't impose on a perfect stranger. Perhaps after you've been here a while I might feel more comfortable asking a favor."); -const char *TEXT_310 = N_("I see in you the potential for greatness. Perhaps sometime while you are fulfilling your destiny, you could stop by and do a little favor for me?"); -const char *TEXT_311 = N_("I think you could probably help me, but perhaps after you've gotten a little more powerful. I wouldn't want to injure the village's only chance to destroy the menace in the church!"); -const char *TEXT_312 = N_("Me, I'm a self-made cow. Make something of yourself, and... then we'll talk."); -const char *TEXT_313 = N_("I don't have to explain myself to every tourist that walks by! Don't you have some monsters to kill? Maybe we'll talk later. If you live..."); -const char *TEXT_314 = N_("Quit bugging me. I'm looking for someone really heroic. And you're not it. I can't trust you, you're going to get eaten by monsters any day now... I need someone who's an experienced hero."); -const char *TEXT_315 = N_("All right, I'll cut the bull. I didn't mean to steer you wrong. I was sitting at home, feeling moo-dy, when things got really un-stable; a whole stampede of monsters came out of the floor! I just cowed. I just happened to be wearing this Jersey when I ran out the door, and now I look udderly ridiculous. If only I had something normal to wear, it wouldn't be so bad. Hey! Can you go back to my place and get my suit for me? The brown one, not the gray one, that's for evening wear. I'd do it myself, but I don't want anyone seeing me like this. Here, take this, you might need it... to kill those things that have overgrown everything. You can't miss my house, it's just south of the fork in the river... you know... the one with the overgrown vegetable garden."); -const char *TEXT_317 = N_("I have tried spells, threats, abjuration and bargaining with this foul creature -- to no avail. My methods of enslaving lesser demons seem to have no effect on this fearsome beast."); -const char *TEXT_318 = N_("My home is slowly becoming corrupted by the vileness of this unwanted prisoner. The crypts are full of shadows that move just beyond the corners of my vision. The faint scrabble of claws dances at the edges of my hearing. They are searching, I think, for this journal."); -const char *TEXT_319 = N_("In its ranting, the creature has let slip its name -- Na-Krul. I have attempted to research the name, but the smaller demons have somehow destroyed my library. Na-Krul... The name fills me with a cold dread. I prefer to think of it only as The Creature rather than ponder its true name."); -const char *TEXT_320 = N_("The entrapped creature's howls of fury keep me from gaining much needed sleep. It rages against the one who sent it to the Void, and it calls foul curses upon me for trapping it here. Its words fill my heart with terror, and yet I cannot block out its voice."); -const char *TEXT_321 = N_("My time is quickly running out. I must record the ways to weaken the demon, and then conceal that text, lest his minions find some way to use my knowledge to free their lord. I hope that whoever finds this journal will seek the knowledge."); -const char *TEXT_322 = N_("Whoever finds this scroll is charged with stopping the demonic creature that lies within these walls. My time is over. Even now, its hellish minions claw at the frail door behind which I hide. \n \nI have hobbled the demon with arcane magic and encased it within great walls, but I fear that will not be enough. \n \nThe spells found in my three grimoires will provide you protected entrance to his domain, but only if cast in their proper sequence. The levers at the entryway will remove the barriers and free the demon; touch them not! Use only these spells to gain entry or his power may be too great for you to defeat."); -const char *TEXT_323 = N_("In Spiritu Sanctum."); -const char *TEXT_324 = N_("Praedictum Otium."); -const char *TEXT_325 = N_("Efficio Obitus Ut Inimicus."); -const char *MT_HELLBOAR_NAME = P_("monster", "Hellboar"); -const char *MT_STINGER_NAME = P_("monster", "Stinger"); -const char *MT_PSYCHORB_NAME = P_("monster", "Psychorb"); -const char *MT_ARACHNON_NAME = P_("monster", "Arachnon"); -const char *MT_FELLTWIN_NAME = P_("monster", "Felltwin"); -const char *MT_HORKSPWN_NAME = P_("monster", "Hork Spawn"); -const char *MT_VENMTAIL_NAME = P_("monster", "Venomtail"); -const char *MT_NECRMORB_NAME = P_("monster", "Necromorb"); -const char *MT_SPIDLORD_NAME = P_("monster", "Spider Lord"); -const char *MT_LASHWORM_NAME = P_("monster", "Lashworm"); -const char *MT_TORCHANT_NAME = P_("monster", "Torchant"); -const char *MT_DEFILER_NAME = P_("monster", "Hell Bug"); -const char *MT_GRAVEDIG_NAME = P_("monster", "Gravedigger"); -const char *MT_TOMBRAT_NAME = P_("monster", "Tomb Rat"); -const char *MT_FIREBAT_NAME = P_("monster", "Firebat"); -const char *MT_SKLWING_NAME = P_("monster", "Skullwing"); -const char *MT_LICH_NAME = P_("monster", "Lich"); -const char *MT_CRYPTDMN_NAME = P_("monster", "Crypt Demon"); -const char *MT_HELLBAT_NAME = P_("monster", "Hellbat"); -const char *MT_BONEDEMN_NAME = P_("monster", "Bone Demon"); -const char *MT_ARCHLICH_NAME = P_("monster", "Arch Lich"); -const char *MT_BICLOPS_NAME = P_("monster", "Biclops"); -const char *MT_FLESTHNG_NAME = P_("monster", "Flesh Thing"); -const char *MT_REAPER_NAME = P_("monster", "Reaper"); -const char *UNIQUE_ITEM_90_NAME = N_("Giant's Knuckle"); -const char *UNIQUE_ITEM_91_NAME = N_("Mercurial Ring"); -const char *UNIQUE_ITEM_92_NAME = N_("Xorine's Ring"); -const char *UNIQUE_ITEM_93_NAME = N_("Karik's Ring"); -const char *UNIQUE_ITEM_94_NAME = N_("Ring of Magma"); -const char *UNIQUE_ITEM_95_NAME = N_("Ring of the Mystics"); -const char *UNIQUE_ITEM_96_NAME = N_("Ring of Thunder"); -const char *UNIQUE_ITEM_97_NAME = N_("Amulet of Warding"); -const char *UNIQUE_ITEM_98_NAME = N_("Gnat Sting"); -const char *UNIQUE_ITEM_99_NAME = N_("Flambeau"); -const char *UNIQUE_ITEM_100_NAME = N_("Armor of Gloom"); -const char *UNIQUE_ITEM_101_NAME = N_("Blitzen"); -const char *UNIQUE_ITEM_102_NAME = N_("Thunderclap"); -const char *UNIQUE_ITEM_103_NAME = N_("Shirotachi"); -const char *UNIQUE_ITEM_104_NAME = N_("Eater of Souls"); -const char *UNIQUE_ITEM_105_NAME = N_("Diamondedge"); -const char *UNIQUE_ITEM_106_NAME = N_("Bone Chain Armor"); -const char *UNIQUE_ITEM_107_NAME = N_("Demon Plate Armor"); -const char *UNIQUE_ITEM_108_NAME = N_("Acolyte's Amulet"); -const char *UNIQUE_ITEM_109_NAME = N_("Gladiator's Ring"); -const char *ITEM_PREFIX_83_NAME = N_("Jester's"); -const char *ITEM_PREFIX_84_NAME = N_("Crystalline"); -const char *ITEM_PREFIX_85_NAME = N_("Doppelganger's"); -const char *ITEM_SUFFIX_95_NAME = N_("devastation"); -const char *ITEM_SUFFIX_96_NAME = N_("decay"); -const char *ITEM_SUFFIX_97_NAME = N_("peril"); -const char *SPELL_MANA_NAME = P_("spell", "Mana"); -const char *SPELL_THE_MAGI_NAME = P_("spell", "the Magi"); -const char *SPELL_THE_JESTER_NAME = P_("spell", "the Jester"); -const char *SPELL_LIGHTNING_WALL_NAME = P_("spell", "Lightning Wall"); -const char *SPELL_IMMOLATION_NAME = P_("spell", "Immolation"); -const char *SPELL_WARP_NAME = P_("spell", "Warp"); -const char *SPELL_REFLECT_NAME = P_("spell", "Reflect"); -const char *SPELL_BERSERK_NAME = P_("spell", "Berserk"); -const char *SPELL_RING_OF_FIRE_NAME = P_("spell", "Ring of Fire"); -const char *SPELL_SEARCH_NAME = P_("spell", "Search"); -const char *SPELL_RUNE_OF_FIRE_NAME = P_("spell", "Rune of Fire"); -const char *SPELL_RUNE_OF_LIGHT_NAME = P_("spell", "Rune of Light"); -const char *SPELL_RUNE_OF_NOVA_NAME = P_("spell", "Rune of Nova"); -const char *SPELL_RUNE_OF_IMMOLATION_NAME = P_("spell", "Rune of Immolation"); -const char *SPELL_RUNE_OF_STONE_NAME = P_("spell", "Rune of Stone"); - -} // namespace +/** + * @file translation_dummy.cpp + * + * Do not edit this file manually, it is automatically generated + * and updated by the extract_translation_data.py script. + */ +#include "utils/language.h" + +namespace { + +const char *CLASS_WARRIOR_NAME = N_("Warrior"); +const char *CLASS_ROGUE_NAME = N_("Rogue"); +const char *CLASS_SORCERER_NAME = N_("Sorcerer"); +const char *CLASS_MONK_NAME = N_("Monk"); +const char *CLASS_BARD_NAME = N_("Bard"); +const char *CLASS_BARBARIAN_NAME = N_("Barbarian"); +const char *MT_NZOMBIE_NAME = P_("monster", "Zombie"); +const char *MT_BZOMBIE_NAME = P_("monster", "Ghoul"); +const char *MT_GZOMBIE_NAME = P_("monster", "Rotting Carcass"); +const char *MT_YZOMBIE_NAME = P_("monster", "Black Death"); +const char *MT_RFALLSP_NAME = P_("monster", "Fallen One"); +const char *MT_DFALLSP_NAME = P_("monster", "Carver"); +const char *MT_YFALLSP_NAME = P_("monster", "Devil Kin"); +const char *MT_BFALLSP_NAME = P_("monster", "Dark One"); +const char *MT_WSKELAX_NAME = P_("monster", "Skeleton"); +const char *MT_TSKELAX_NAME = P_("monster", "Corpse Axe"); +const char *MT_RSKELAX_NAME = P_("monster", "Burning Dead"); +const char *MT_XSKELAX_NAME = P_("monster", "Horror"); +const char *MT_NSCAV_NAME = P_("monster", "Scavenger"); +const char *MT_BSCAV_NAME = P_("monster", "Plague Eater"); +const char *MT_WSCAV_NAME = P_("monster", "Shadow Beast"); +const char *MT_YSCAV_NAME = P_("monster", "Bone Gasher"); +const char *MT_TSKELBW_NAME = P_("monster", "Corpse Bow"); +const char *MT_WSKELSD_NAME = P_("monster", "Skeleton Captain"); +const char *MT_TSKELSD_NAME = P_("monster", "Corpse Captain"); +const char *MT_RSKELSD_NAME = P_("monster", "Burning Dead Captain"); +const char *MT_XSKELSD_NAME = P_("monster", "Horror Captain"); +const char *MT_INVILORD_NAME = P_("monster", "Invisible Lord"); +const char *MT_SNEAK_NAME = P_("monster", "Hidden"); +const char *MT_STALKER_NAME = P_("monster", "Stalker"); +const char *MT_UNSEEN_NAME = P_("monster", "Unseen"); +const char *MT_ILLWEAV_NAME = P_("monster", "Illusion Weaver"); +const char *MT_LRDSAYTR_NAME = P_("monster", "Satyr Lord"); +const char *MT_NGOATMC_NAME = P_("monster", "Flesh Clan"); +const char *MT_BGOATMC_NAME = P_("monster", "Stone Clan"); +const char *MT_RGOATMC_NAME = P_("monster", "Fire Clan"); +const char *MT_GGOATMC_NAME = P_("monster", "Night Clan"); +const char *MT_FIEND_NAME = P_("monster", "Fiend"); +const char *MT_BLINK_NAME = P_("monster", "Blink"); +const char *MT_GLOOM_NAME = P_("monster", "Gloom"); +const char *MT_FAMILIAR_NAME = P_("monster", "Familiar"); +const char *MT_NACID_NAME = P_("monster", "Acid Beast"); +const char *MT_RACID_NAME = P_("monster", "Poison Spitter"); +const char *MT_BACID_NAME = P_("monster", "Pit Beast"); +const char *MT_XACID_NAME = P_("monster", "Lava Maw"); +const char *MT_SKING_NAME = P_("monster", "Skeleton King"); +const char *MT_CLEAVER_NAME = P_("monster", "The Butcher"); +const char *MT_FAT_NAME = P_("monster", "Overlord"); +const char *MT_MUDMAN_NAME = P_("monster", "Mud Man"); +const char *MT_TOAD_NAME = P_("monster", "Toad Demon"); +const char *MT_FLAYED_NAME = P_("monster", "Flayed One"); +const char *MT_WYRM_NAME = P_("monster", "Wyrm"); +const char *MT_CAVSLUG_NAME = P_("monster", "Cave Slug"); +const char *MT_DVLWYRM_NAME = P_("monster", "Devil Wyrm"); +const char *MT_DEVOUR_NAME = P_("monster", "Devourer"); +const char *MT_NMAGMA_NAME = P_("monster", "Magma Demon"); +const char *MT_YMAGMA_NAME = P_("monster", "Blood Stone"); +const char *MT_BMAGMA_NAME = P_("monster", "Hell Stone"); +const char *MT_WMAGMA_NAME = P_("monster", "Lava Lord"); +const char *MT_HORNED_NAME = P_("monster", "Horned Demon"); +const char *MT_MUDRUN_NAME = P_("monster", "Mud Runner"); +const char *MT_FROSTC_NAME = P_("monster", "Frost Charger"); +const char *MT_OBLORD_NAME = P_("monster", "Obsidian Lord"); +const char *MT_BONEDMN_NAME = P_("monster", "oldboned"); +const char *MT_REDDTH_NAME = P_("monster", "Red Death"); +const char *MT_LTCHDMN_NAME = P_("monster", "Litch Demon"); +const char *MT_UDEDBLRG_NAME = P_("monster", "Undead Balrog"); +const char *MT_INCIN_NAME = P_("monster", "Incinerator"); +const char *MT_FLAMLRD_NAME = P_("monster", "Flame Lord"); +const char *MT_DOOMFIRE_NAME = P_("monster", "Doom Fire"); +const char *MT_HELLBURN_NAME = P_("monster", "Hell Burner"); +const char *MT_STORM_NAME = P_("monster", "Red Storm"); +const char *MT_RSTORM_NAME = P_("monster", "Storm Rider"); +const char *MT_STORML_NAME = P_("monster", "Storm Lord"); +const char *MT_MAEL_NAME = P_("monster", "Maelstrom"); +const char *MT_BIGFALL_NAME = P_("monster", "Devil Kin Brute"); +const char *MT_WINGED_NAME = P_("monster", "Winged-Demon"); +const char *MT_GARGOYLE_NAME = P_("monster", "Gargoyle"); +const char *MT_BLOODCLW_NAME = P_("monster", "Blood Claw"); +const char *MT_DEATHW_NAME = P_("monster", "Death Wing"); +const char *MT_MEGA_NAME = P_("monster", "Slayer"); +const char *MT_GUARD_NAME = P_("monster", "Guardian"); +const char *MT_VTEXLRD_NAME = P_("monster", "Vortex Lord"); +const char *MT_BALROG_NAME = P_("monster", "Balrog"); +const char *MT_NSNAKE_NAME = P_("monster", "Cave Viper"); +const char *MT_RSNAKE_NAME = P_("monster", "Fire Drake"); +const char *MT_BSNAKE_NAME = P_("monster", "Gold Viper"); +const char *MT_GSNAKE_NAME = P_("monster", "Azure Drake"); +const char *MT_NBLACK_NAME = P_("monster", "Black Knight"); +const char *MT_RTBLACK_NAME = P_("monster", "Doom Guard"); +const char *MT_BTBLACK_NAME = P_("monster", "Steel Lord"); +const char *MT_RBLACK_NAME = P_("monster", "Blood Knight"); +const char *MT_UNRAV_NAME = P_("monster", "The Shredded"); +const char *MT_HOLOWONE_NAME = P_("monster", "Hollow One"); +const char *MT_PAINMSTR_NAME = P_("monster", "Pain Master"); +const char *MT_REALWEAV_NAME = P_("monster", "Reality Weaver"); +const char *MT_SUCCUBUS_NAME = P_("monster", "Succubus"); +const char *MT_SNOWWICH_NAME = P_("monster", "Snow Witch"); +const char *MT_HLSPWN_NAME = P_("monster", "Hell Spawn"); +const char *MT_SOLBRNR_NAME = P_("monster", "Soul Burner"); +const char *MT_COUNSLR_NAME = P_("monster", "Counselor"); +const char *MT_MAGISTR_NAME = P_("monster", "Magistrate"); +const char *MT_CABALIST_NAME = P_("monster", "Cabalist"); +const char *MT_ADVOCATE_NAME = P_("monster", "Advocate"); +const char *MT_GOLEM_NAME = P_("monster", "Golem"); +const char *MT_DIABLO_NAME = P_("monster", "The Dark Lord"); +const char *MT_DARKMAGE_NAME = P_("monster", "The Arch-Litch Malignus"); +const char *GHARBAD_THE_WEAK_NAME = P_("monster", "Gharbad the Weak"); +const char *ZHAR_THE_MAD_NAME = P_("monster", "Zhar the Mad"); +const char *SNOTSPILL_NAME = P_("monster", "Snotspill"); +const char *ARCH_BISHOP_LAZARUS_NAME = P_("monster", "Arch-Bishop Lazarus"); +const char *RED_VEX_NAME = P_("monster", "Red Vex"); +const char *BLACK_JADE_NAME = P_("monster", "Black Jade"); +const char *LACHDANAN_NAME = P_("monster", "Lachdanan"); +const char *WARLORD_OF_BLOOD_NAME = P_("monster", "Warlord of Blood"); +const char *HORK_DEMON_NAME = P_("monster", "Hork Demon"); +const char *THE_DEFILER_NAME = P_("monster", "The Defiler"); +const char *NA_KRUL_NAME = P_("monster", "Na-Krul"); +const char *BONEHEAD_KEENAXE_NAME = P_("monster", "Bonehead Keenaxe"); +const char *BLADESKIN_THE_SLASHER_NAME = P_("monster", "Bladeskin the Slasher"); +const char *SOULPUS_NAME = P_("monster", "Soulpus"); +const char *PUKERAT_THE_UNCLEAN_NAME = P_("monster", "Pukerat the Unclean"); +const char *BONERIPPER_NAME = P_("monster", "Boneripper"); +const char *ROTFEAST_THE_HUNGRY_NAME = P_("monster", "Rotfeast the Hungry"); +const char *GUTSHANK_THE_QUICK_NAME = P_("monster", "Gutshank the Quick"); +const char *BROKENHEAD_BANGSHIELD_NAME = P_("monster", "Brokenhead Bangshield"); +const char *BONGO_NAME = P_("monster", "Bongo"); +const char *ROTCARNAGE_NAME = P_("monster", "Rotcarnage"); +const char *SHADOWBITE_NAME = P_("monster", "Shadowbite"); +const char *DEADEYE_NAME = P_("monster", "Deadeye"); +const char *MADEYE_THE_DEAD_NAME = P_("monster", "Madeye the Dead"); +const char *EL_CHUPACABRAS_NAME = P_("monster", "El Chupacabras"); +const char *SKULLFIRE_NAME = P_("monster", "Skullfire"); +const char *WARPSKULL_NAME = P_("monster", "Warpskull"); +const char *GORETONGUE_NAME = P_("monster", "Goretongue"); +const char *PULSECRAWLER_NAME = P_("monster", "Pulsecrawler"); +const char *MOONBENDER_NAME = P_("monster", "Moonbender"); +const char *WRATHRAVEN_NAME = P_("monster", "Wrathraven"); +const char *SPINEEATER_NAME = P_("monster", "Spineeater"); +const char *BLACKASH_THE_BURNING_NAME = P_("monster", "Blackash the Burning"); +const char *SHADOWCROW_NAME = P_("monster", "Shadowcrow"); +const char *BLIGHTSTONE_THE_WEAK_NAME = P_("monster", "Blightstone the Weak"); +const char *BILEFROTH_THE_PIT_MASTER_NAME = P_("monster", "Bilefroth the Pit Master"); +const char *BLOODSKIN_DARKBOW_NAME = P_("monster", "Bloodskin Darkbow"); +const char *FOULWING_NAME = P_("monster", "Foulwing"); +const char *SHADOWDRINKER_NAME = P_("monster", "Shadowdrinker"); +const char *HAZESHIFTER_NAME = P_("monster", "Hazeshifter"); +const char *DEATHSPIT_NAME = P_("monster", "Deathspit"); +const char *BLOODGUTTER_NAME = P_("monster", "Bloodgutter"); +const char *DEATHSHADE_FLESHMAUL_NAME = P_("monster", "Deathshade Fleshmaul"); +const char *WARMAGGOT_THE_MAD_NAME = P_("monster", "Warmaggot the Mad"); +const char *GLASSKULL_THE_JAGGED_NAME = P_("monster", "Glasskull the Jagged"); +const char *BLIGHTFIRE_NAME = P_("monster", "Blightfire"); +const char *NIGHTWING_THE_COLD_NAME = P_("monster", "Nightwing the Cold"); +const char *GORESTONE_NAME = P_("monster", "Gorestone"); +const char *BRONZEFIST_FIRESTONE_NAME = P_("monster", "Bronzefist Firestone"); +const char *WRATHFIRE_THE_DOOMED_NAME = P_("monster", "Wrathfire the Doomed"); +const char *FIREWOUND_THE_GRIM_NAME = P_("monster", "Firewound the Grim"); +const char *BARON_SLUDGE_NAME = P_("monster", "Baron Sludge"); +const char *BLIGHTHORN_STEELMACE_NAME = P_("monster", "Blighthorn Steelmace"); +const char *CHAOSHOWLER_NAME = P_("monster", "Chaoshowler"); +const char *DOOMGRIN_THE_ROTTING_NAME = P_("monster", "Doomgrin the Rotting"); +const char *MADBURNER_NAME = P_("monster", "Madburner"); +const char *BONESAW_THE_LITCH_NAME = P_("monster", "Bonesaw the Litch"); +const char *BREAKSPINE_NAME = P_("monster", "Breakspine"); +const char *DEVILSKULL_SHARPBONE_NAME = P_("monster", "Devilskull Sharpbone"); +const char *BROKENSTORM_NAME = P_("monster", "Brokenstorm"); +const char *STORMBANE_NAME = P_("monster", "Stormbane"); +const char *OOZEDROOL_NAME = P_("monster", "Oozedrool"); +const char *GOLDBLIGHT_OF_THE_FLAME_NAME = P_("monster", "Goldblight of the Flame"); +const char *BLACKSTORM_NAME = P_("monster", "Blackstorm"); +const char *PLAGUEWRATH_NAME = P_("monster", "Plaguewrath"); +const char *THE_FLAYER_NAME = P_("monster", "The Flayer"); +const char *BLUEHORN_NAME = P_("monster", "Bluehorn"); +const char *WARPFIRE_HELLSPAWN_NAME = P_("monster", "Warpfire Hellspawn"); +const char *FANGSPEIR_NAME = P_("monster", "Fangspeir"); +const char *FESTERSKULL_NAME = P_("monster", "Festerskull"); +const char *LIONSKULL_THE_BENT_NAME = P_("monster", "Lionskull the Bent"); +const char *BLACKTONGUE_NAME = P_("monster", "Blacktongue"); +const char *VILETOUCH_NAME = P_("monster", "Viletouch"); +const char *VIPERFLAME_NAME = P_("monster", "Viperflame"); +const char *FANGSKIN_NAME = P_("monster", "Fangskin"); +const char *WITCHFIRE_THE_UNHOLY_NAME = P_("monster", "Witchfire the Unholy"); +const char *BLACKSKULL_NAME = P_("monster", "Blackskull"); +const char *SOULSLASH_NAME = P_("monster", "Soulslash"); +const char *WINDSPAWN_NAME = P_("monster", "Windspawn"); +const char *LORD_OF_THE_PIT_NAME = P_("monster", "Lord of the Pit"); +const char *RUSTWEAVER_NAME = P_("monster", "Rustweaver"); +const char *HOWLINGIRE_THE_SHADE_NAME = P_("monster", "Howlingire the Shade"); +const char *DOOMCLOUD_NAME = P_("monster", "Doomcloud"); +const char *BLOODMOON_SOULFIRE_NAME = P_("monster", "Bloodmoon Soulfire"); +const char *WITCHMOON_NAME = P_("monster", "Witchmoon"); +const char *GOREFEAST_NAME = P_("monster", "Gorefeast"); +const char *GRAYWAR_THE_SLAYER_NAME = P_("monster", "Graywar the Slayer"); +const char *DREADJUDGE_NAME = P_("monster", "Dreadjudge"); +const char *STAREYE_THE_WITCH_NAME = P_("monster", "Stareye the Witch"); +const char *STEELSKULL_THE_HUNTER_NAME = P_("monster", "Steelskull the Hunter"); +const char *SIR_GORASH_NAME = P_("monster", "Sir Gorash"); +const char *THE_VIZIER_NAME = P_("monster", "The Vizier"); +const char *ZAMPHIR_NAME = P_("monster", "Zamphir"); +const char *BLOODLUST_NAME = P_("monster", "Bloodlust"); +const char *WEBWIDOW_NAME = P_("monster", "Webwidow"); +const char *FLESHDANCER_NAME = P_("monster", "Fleshdancer"); +const char *GRIMSPIKE_NAME = P_("monster", "Grimspike"); +const char *DOOMLOCK_NAME = P_("monster", "Doomlock"); +const char *IDI_GOLD_NAME = N_("Gold"); +const char *IDI_WARRIOR_NAME = N_("Short Sword"); +const char *IDI_WARRSHLD_NAME = N_("Buckler"); +const char *IDI_WARRCLUB_NAME = N_("Club"); +const char *IDI_ROGUE_NAME = N_("Short Bow"); +const char *IDI_SORCERER_NAME = N_("Short Staff of Mana"); +const char *IDI_CLEAVER_NAME = N_("Cleaver"); +const char *IDI_SKCROWN_NAME = N_("The Undead Crown"); +const char *IDI_INFRARING_NAME = N_("Empyrean Band"); +const char *IDI_ROCK_NAME = N_("Magic Rock"); +const char *IDI_OPTAMULET_NAME = N_("Optic Amulet"); +const char *IDI_TRING_NAME = N_("Ring of Truth"); +const char *IDI_BANNER_NAME = N_("Tavern Sign"); +const char *IDI_HARCREST_NAME = N_("Harlequin Crest"); +const char *IDI_STEELVEIL_NAME = N_("Veil of Steel"); +const char *IDI_GLDNELIX_NAME = N_("Golden Elixir"); +const char *IDI_ANVIL_NAME = N_("Anvil of Fury"); +const char *IDI_MUSHROOM_NAME = N_("Black Mushroom"); +const char *IDI_BRAIN_NAME = N_("Brain"); +const char *IDI_FUNGALTM_NAME = N_("Fungal Tome"); +const char *IDI_SPECELIX_NAME = N_("Spectral Elixir"); +const char *IDI_BLDSTONE_NAME = N_("Blood Stone"); +const char *IDI_MAPOFDOOM_NAME = N_("Cathedral Map"); +const char *IDI_EAR_NAME = N_("Ear"); +const char *IDI_HEAL_NAME = N_("Potion of Healing"); +const char *IDI_MANA_NAME = N_("Potion of Mana"); +const char *IDI_IDENTIFY_NAME = N_("Scroll of Identify"); +const char *IDI_PORTAL_NAME = N_("Scroll of Town Portal"); +const char *IDI_ARMOFVAL_NAME = N_("Arkaine's Valor"); +const char *IDI_FULLHEAL_NAME = N_("Potion of Full Healing"); +const char *IDI_FULLMANA_NAME = N_("Potion of Full Mana"); +const char *IDI_GRISWOLD_NAME = N_("Griswold's Edge"); +const char *IDI_LGTFORGE_NAME = N_("Bovine Plate"); +const char *IDI_LAZSTAFF_NAME = N_("Staff of Lazarus"); +const char *IDI_RESURRECT_NAME = N_("Scroll of Resurrect"); +const char *IDI_OIL_NAME = N_("Blacksmith Oil"); +const char *IDI_SHORTSTAFF_NAME = N_("Short Staff"); +const char *IDI_BARDSWORD_NAME = N_("Sword"); +const char *IDI_BARDDAGGER_NAME = N_("Dagger"); +const char *IDI_RUNEBOMB_NAME = N_("Rune Bomb"); +const char *IDI_THEODORE_NAME = N_("Theodore"); +const char *IDI_AURIC_NAME = N_("Auric Amulet"); +const char *IDI_NOTE1_NAME = N_("Torn Note 1"); +const char *IDI_NOTE2_NAME = N_("Torn Note 2"); +const char *IDI_NOTE3_NAME = N_("Torn Note 3"); +const char *IDI_FULLNOTE_NAME = N_("Reconstructed Note"); +const char *IDI_BROWNSUIT_NAME = N_("Brown Suit"); +const char *IDI_GREYSUIT_NAME = N_("Grey Suit"); +const char *ITEM_48_NAME = N_("Cap"); +const char *ITEM_49_NAME = N_("Skull Cap"); +const char *ITEM_50_NAME = N_("Helm"); +const char *ITEM_51_NAME = N_("Full Helm"); +const char *ITEM_52_NAME = N_("Crown"); +const char *ITEM_53_NAME = N_("Great Helm"); +const char *ITEM_54_NAME = N_("Cape"); +const char *ITEM_55_NAME = N_("Rags"); +const char *ITEM_56_NAME = N_("Cloak"); +const char *ITEM_57_NAME = N_("Robe"); +const char *ITEM_58_NAME = N_("Quilted Armor"); +const char *ITEM_58_SHORT_NAME = N_("Armor"); +const char *ITEM_59_NAME = N_("Leather Armor"); +const char *ITEM_60_NAME = N_("Hard Leather Armor"); +const char *ITEM_61_NAME = N_("Studded Leather Armor"); +const char *ITEM_62_NAME = N_("Ring Mail"); +const char *ITEM_62_SHORT_NAME = N_("Mail"); +const char *ITEM_63_NAME = N_("Chain Mail"); +const char *ITEM_64_NAME = N_("Scale Mail"); +const char *ITEM_65_NAME = N_("Breast Plate"); +const char *ITEM_65_SHORT_NAME = N_("Plate"); +const char *ITEM_66_NAME = N_("Splint Mail"); +const char *ITEM_67_NAME = N_("Plate Mail"); +const char *ITEM_68_NAME = N_("Field Plate"); +const char *ITEM_69_NAME = N_("Gothic Plate"); +const char *ITEM_70_NAME = N_("Full Plate Mail"); +const char *ITEM_71_SHORT_NAME = N_("Shield"); +const char *ITEM_72_NAME = N_("Small Shield"); +const char *ITEM_73_NAME = N_("Large Shield"); +const char *ITEM_74_NAME = N_("Kite Shield"); +const char *ITEM_75_NAME = N_("Tower Shield"); +const char *ITEM_76_NAME = N_("Gothic Shield"); +const char *ITEM_81_NAME = N_("Potion of Rejuvenation"); +const char *ITEM_82_NAME = N_("Potion of Full Rejuvenation"); +const char *ITEM_84_NAME = N_("Oil of Accuracy"); +const char *ITEM_85_NAME = N_("Oil of Sharpness"); +const char *ITEM_86_NAME = N_("Oil"); +const char *ITEM_87_NAME = N_("Elixir of Strength"); +const char *ITEM_88_NAME = N_("Elixir of Magic"); +const char *ITEM_89_NAME = N_("Elixir of Dexterity"); +const char *ITEM_90_NAME = N_("Elixir of Vitality"); +const char *ITEM_91_NAME = N_("Scroll of Healing"); +const char *ITEM_92_NAME = N_("Scroll of Search"); +const char *ITEM_93_NAME = N_("Scroll of Lightning"); +const char *ITEM_96_NAME = N_("Scroll of Fire Wall"); +const char *ITEM_97_NAME = N_("Scroll of Inferno"); +const char *ITEM_99_NAME = N_("Scroll of Flash"); +const char *ITEM_100_NAME = N_("Scroll of Infravision"); +const char *ITEM_101_NAME = N_("Scroll of Phasing"); +const char *ITEM_102_NAME = N_("Scroll of Mana Shield"); +const char *ITEM_103_NAME = N_("Scroll of Flame Wave"); +const char *ITEM_104_NAME = N_("Scroll of Fireball"); +const char *ITEM_105_NAME = N_("Scroll of Stone Curse"); +const char *ITEM_106_NAME = N_("Scroll of Chain Lightning"); +const char *ITEM_107_NAME = N_("Scroll of Guardian"); +const char *ITEM_109_NAME = N_("Scroll of Nova"); +const char *ITEM_110_NAME = N_("Scroll of Golem"); +const char *ITEM_112_NAME = N_("Scroll of Teleport"); +const char *ITEM_113_NAME = N_("Scroll of Apocalypse"); +const char *ITEM_120_NAME = N_("Falchion"); +const char *ITEM_121_NAME = N_("Scimitar"); +const char *ITEM_122_NAME = N_("Claymore"); +const char *ITEM_123_NAME = N_("Blade"); +const char *ITEM_124_NAME = N_("Sabre"); +const char *ITEM_125_NAME = N_("Long Sword"); +const char *ITEM_126_NAME = N_("Broad Sword"); +const char *ITEM_127_NAME = N_("Bastard Sword"); +const char *ITEM_128_NAME = N_("Two-Handed Sword"); +const char *ITEM_129_NAME = N_("Great Sword"); +const char *ITEM_130_NAME = N_("Small Axe"); +const char *ITEM_130_SHORT_NAME = N_("Axe"); +const char *ITEM_132_NAME = N_("Large Axe"); +const char *ITEM_133_NAME = N_("Broad Axe"); +const char *ITEM_134_NAME = N_("Battle Axe"); +const char *ITEM_135_NAME = N_("Great Axe"); +const char *ITEM_136_NAME = N_("Mace"); +const char *ITEM_137_NAME = N_("Morning Star"); +const char *ITEM_138_NAME = N_("War Hammer"); +const char *ITEM_138_SHORT_NAME = N_("Hammer"); +const char *ITEM_139_NAME = N_("Spiked Club"); +const char *ITEM_141_NAME = N_("Flail"); +const char *ITEM_142_NAME = N_("Maul"); +const char *ITEM_143_SHORT_NAME = N_("Bow"); +const char *ITEM_144_NAME = N_("Hunter's Bow"); +const char *ITEM_145_NAME = N_("Long Bow"); +const char *ITEM_146_NAME = N_("Composite Bow"); +const char *IDI_SHORT_BATTLE_BOW_NAME = N_("Short Battle Bow"); +const char *ITEM_148_NAME = N_("Long Battle Bow"); +const char *ITEM_149_NAME = N_("Short War Bow"); +const char *ITEM_150_NAME = N_("Long War Bow"); +const char *ITEM_151_SHORT_NAME = N_("Staff"); +const char *ITEM_152_NAME = N_("Long Staff"); +const char *ITEM_153_NAME = N_("Composite Staff"); +const char *ITEM_154_NAME = N_("Quarter Staff"); +const char *ITEM_155_NAME = N_("War Staff"); +const char *ITEM_156_NAME = N_("Ring"); +const char *ITEM_159_NAME = N_("Amulet"); +const char *ITEM_161_NAME = N_("Rune of Fire"); +const char *ITEM_161_SHORT_NAME = N_("Rune"); +const char *ITEM_162_NAME = N_("Rune of Lightning"); +const char *ITEM_163_NAME = N_("Greater Rune of Fire"); +const char *ITEM_164_NAME = N_("Greater Rune of Lightning"); +const char *ITEM_165_NAME = N_("Rune of Stone"); +const char *ITEM_166_NAME = N_("Short Staff of Charged Bolt"); +const char *IDI_ARENAPOT_NAME = N_("Arena Potion"); +const char *UNIQUE_ITEM_0_NAME = N_("The Butcher's Cleaver"); +const char *UNIQUE_ITEM_9_NAME = N_("Lightforge"); +const char *UNIQUE_ITEM_10_NAME = N_("The Rift Bow"); +const char *UNIQUE_ITEM_11_NAME = N_("The Needler"); +const char *UNIQUE_ITEM_12_NAME = N_("The Celestial Bow"); +const char *UNIQUE_ITEM_13_NAME = N_("Deadly Hunter"); +const char *UNIQUE_ITEM_14_NAME = N_("Bow of the Dead"); +const char *UNIQUE_ITEM_15_NAME = N_("The Blackoak Bow"); +const char *UNIQUE_ITEM_16_NAME = N_("Flamedart"); +const char *UNIQUE_ITEM_17_NAME = N_("Fleshstinger"); +const char *UNIQUE_ITEM_18_NAME = N_("Windforce"); +const char *UNIQUE_ITEM_19_NAME = N_("Eaglehorn"); +const char *UNIQUE_ITEM_20_NAME = N_("Gonnagal's Dirk"); +const char *UNIQUE_ITEM_21_NAME = N_("The Defender"); +const char *UNIQUE_ITEM_22_NAME = N_("Gryphon's Claw"); +const char *UNIQUE_ITEM_23_NAME = N_("Black Razor"); +const char *UNIQUE_ITEM_24_NAME = N_("Gibbous Moon"); +const char *UNIQUE_ITEM_25_NAME = N_("Ice Shank"); +const char *UNIQUE_ITEM_26_NAME = N_("The Executioner's Blade"); +const char *UNIQUE_ITEM_27_NAME = N_("The Bonesaw"); +const char *UNIQUE_ITEM_28_NAME = N_("Shadowhawk"); +const char *UNIQUE_ITEM_29_NAME = N_("Wizardspike"); +const char *UNIQUE_ITEM_30_NAME = N_("Lightsabre"); +const char *UNIQUE_ITEM_31_NAME = N_("The Falcon's Talon"); +const char *UNIQUE_ITEM_32_NAME = N_("Inferno"); +const char *UNIQUE_ITEM_33_NAME = N_("Doombringer"); +const char *UNIQUE_ITEM_34_NAME = N_("The Grizzly"); +const char *UNIQUE_ITEM_35_NAME = N_("The Grandfather"); +const char *UNIQUE_ITEM_36_NAME = N_("The Mangler"); +const char *UNIQUE_ITEM_37_NAME = N_("Sharp Beak"); +const char *UNIQUE_ITEM_38_NAME = N_("BloodSlayer"); +const char *UNIQUE_ITEM_39_NAME = N_("The Celestial Axe"); +const char *UNIQUE_ITEM_40_NAME = N_("Wicked Axe"); +const char *UNIQUE_ITEM_41_NAME = N_("Stonecleaver"); +const char *UNIQUE_ITEM_42_NAME = N_("Aguinara's Hatchet"); +const char *UNIQUE_ITEM_43_NAME = N_("Hellslayer"); +const char *UNIQUE_ITEM_44_NAME = N_("Messerschmidt's Reaver"); +const char *UNIQUE_ITEM_45_NAME = N_("Crackrust"); +const char *UNIQUE_ITEM_46_NAME = N_("Hammer of Jholm"); +const char *UNIQUE_ITEM_47_NAME = N_("Civerb's Cudgel"); +const char *UNIQUE_ITEM_48_NAME = N_("The Celestial Star"); +const char *UNIQUE_ITEM_49_NAME = N_("Baranar's Star"); +const char *UNIQUE_ITEM_50_NAME = N_("Gnarled Root"); +const char *UNIQUE_ITEM_51_NAME = N_("The Cranium Basher"); +const char *UNIQUE_ITEM_52_NAME = N_("Schaefer's Hammer"); +const char *UNIQUE_ITEM_53_NAME = N_("Dreamflange"); +const char *UNIQUE_ITEM_54_NAME = N_("Staff of Shadows"); +const char *UNIQUE_ITEM_55_NAME = N_("Immolator"); +const char *UNIQUE_ITEM_56_NAME = N_("Storm Spire"); +const char *UNIQUE_ITEM_57_NAME = N_("Gleamsong"); +const char *UNIQUE_ITEM_58_NAME = N_("Thundercall"); +const char *UNIQUE_ITEM_59_NAME = N_("The Protector"); +const char *UNIQUE_ITEM_60_NAME = N_("Naj's Puzzler"); +const char *UNIQUE_ITEM_61_NAME = N_("Mindcry"); +const char *UNIQUE_ITEM_62_NAME = N_("Rod of Onan"); +const char *UNIQUE_ITEM_63_NAME = N_("Helm of Spirits"); +const char *UNIQUE_ITEM_64_NAME = N_("Thinking Cap"); +const char *UNIQUE_ITEM_65_NAME = N_("OverLord's Helm"); +const char *UNIQUE_ITEM_66_NAME = N_("Fool's Crest"); +const char *UNIQUE_ITEM_67_NAME = N_("Gotterdamerung"); +const char *UNIQUE_ITEM_68_NAME = N_("Royal Circlet"); +const char *UNIQUE_ITEM_69_NAME = N_("Torn Flesh of Souls"); +const char *UNIQUE_ITEM_70_NAME = N_("The Gladiator's Bane"); +const char *UNIQUE_ITEM_71_NAME = N_("The Rainbow Cloak"); +const char *UNIQUE_ITEM_72_NAME = N_("Leather of Aut"); +const char *UNIQUE_ITEM_73_NAME = N_("Wisdom's Wrap"); +const char *UNIQUE_ITEM_74_NAME = N_("Sparking Mail"); +const char *UNIQUE_ITEM_75_NAME = N_("Scavenger Carapace"); +const char *UNIQUE_ITEM_76_NAME = N_("Nightscape"); +const char *UNIQUE_ITEM_77_NAME = N_("Naj's Light Plate"); +const char *UNIQUE_ITEM_78_NAME = N_("Demonspike Coat"); +const char *UNIQUE_ITEM_79_NAME = N_("The Deflector"); +const char *UNIQUE_ITEM_80_NAME = N_("Split Skull Shield"); +const char *UNIQUE_ITEM_81_NAME = N_("Dragon's Breach"); +const char *UNIQUE_ITEM_82_NAME = N_("Blackoak Shield"); +const char *UNIQUE_ITEM_83_NAME = N_("Holy Defender"); +const char *UNIQUE_ITEM_84_NAME = N_("Stormshield"); +const char *UNIQUE_ITEM_85_NAME = N_("Bramble"); +const char *UNIQUE_ITEM_86_NAME = N_("Ring of Regha"); +const char *UNIQUE_ITEM_87_NAME = N_("The Bleeder"); +const char *UNIQUE_ITEM_88_NAME = N_("Constricting Ring"); +const char *UNIQUE_ITEM_89_NAME = N_("Ring of Engagement"); +const char *ITEM_PREFIX_0_NAME = N_("Tin"); +const char *ITEM_PREFIX_1_NAME = N_("Brass"); +const char *ITEM_PREFIX_2_NAME = N_("Bronze"); +const char *ITEM_PREFIX_3_NAME = N_("Iron"); +const char *ITEM_PREFIX_4_NAME = N_("Steel"); +const char *ITEM_PREFIX_5_NAME = N_("Silver"); +const char *ITEM_PREFIX_7_NAME = N_("Platinum"); +const char *ITEM_PREFIX_8_NAME = N_("Mithril"); +const char *ITEM_PREFIX_9_NAME = N_("Meteoric"); +const char *ITEM_PREFIX_10_NAME = N_("Weird"); +const char *ITEM_PREFIX_11_NAME = N_("Strange"); +const char *ITEM_PREFIX_12_NAME = N_("Useless"); +const char *ITEM_PREFIX_13_NAME = N_("Bent"); +const char *ITEM_PREFIX_14_NAME = N_("Weak"); +const char *ITEM_PREFIX_15_NAME = N_("Jagged"); +const char *ITEM_PREFIX_16_NAME = N_("Deadly"); +const char *ITEM_PREFIX_17_NAME = N_("Heavy"); +const char *ITEM_PREFIX_18_NAME = N_("Vicious"); +const char *ITEM_PREFIX_19_NAME = N_("Brutal"); +const char *ITEM_PREFIX_20_NAME = N_("Massive"); +const char *ITEM_PREFIX_21_NAME = N_("Savage"); +const char *ITEM_PREFIX_22_NAME = N_("Ruthless"); +const char *ITEM_PREFIX_23_NAME = N_("Merciless"); +const char *ITEM_PREFIX_24_NAME = N_("Clumsy"); +const char *ITEM_PREFIX_25_NAME = N_("Dull"); +const char *ITEM_PREFIX_26_NAME = N_("Sharp"); +const char *ITEM_PREFIX_27_NAME = N_("Fine"); +const char *ITEM_PREFIX_28_NAME = N_("Warrior's"); +const char *ITEM_PREFIX_29_NAME = N_("Soldier's"); +const char *ITEM_PREFIX_30_NAME = N_("Lord's"); +const char *ITEM_PREFIX_31_NAME = N_("Knight's"); +const char *ITEM_PREFIX_32_NAME = N_("Master's"); +const char *ITEM_PREFIX_33_NAME = N_("Champion's"); +const char *ITEM_PREFIX_34_NAME = N_("King's"); +const char *ITEM_PREFIX_35_NAME = N_("Vulnerable"); +const char *ITEM_PREFIX_36_NAME = N_("Rusted"); +const char *ITEM_PREFIX_38_NAME = N_("Strong"); +const char *ITEM_PREFIX_39_NAME = N_("Grand"); +const char *ITEM_PREFIX_40_NAME = N_("Valiant"); +const char *ITEM_PREFIX_41_NAME = N_("Glorious"); +const char *ITEM_PREFIX_42_NAME = N_("Blessed"); +const char *ITEM_PREFIX_43_NAME = N_("Saintly"); +const char *ITEM_PREFIX_44_NAME = N_("Awesome"); +const char *ITEM_PREFIX_45_NAME = N_("Holy"); +const char *ITEM_PREFIX_46_NAME = N_("Godly"); +const char *ITEM_PREFIX_47_NAME = N_("Red"); +const char *ITEM_PREFIX_48_NAME = N_("Crimson"); +const char *ITEM_PREFIX_50_NAME = N_("Garnet"); +const char *ITEM_PREFIX_51_NAME = N_("Ruby"); +const char *ITEM_PREFIX_52_NAME = N_("Blue"); +const char *ITEM_PREFIX_53_NAME = N_("Azure"); +const char *ITEM_PREFIX_54_NAME = N_("Lapis"); +const char *ITEM_PREFIX_55_NAME = N_("Cobalt"); +const char *ITEM_PREFIX_56_NAME = N_("Sapphire"); +const char *ITEM_PREFIX_57_NAME = N_("White"); +const char *ITEM_PREFIX_58_NAME = N_("Pearl"); +const char *ITEM_PREFIX_59_NAME = N_("Ivory"); +const char *ITEM_PREFIX_60_NAME = N_("Crystal"); +const char *ITEM_PREFIX_61_NAME = N_("Diamond"); +const char *ITEM_PREFIX_62_NAME = N_("Topaz"); +const char *ITEM_PREFIX_63_NAME = N_("Amber"); +const char *ITEM_PREFIX_64_NAME = N_("Jade"); +const char *ITEM_PREFIX_65_NAME = N_("Obsidian"); +const char *ITEM_PREFIX_66_NAME = N_("Emerald"); +const char *ITEM_PREFIX_67_NAME = N_("Hyena's"); +const char *ITEM_PREFIX_68_NAME = N_("Frog's"); +const char *ITEM_PREFIX_69_NAME = N_("Spider's"); +const char *ITEM_PREFIX_70_NAME = N_("Raven's"); +const char *ITEM_PREFIX_71_NAME = N_("Snake's"); +const char *ITEM_PREFIX_72_NAME = N_("Serpent's"); +const char *ITEM_PREFIX_73_NAME = N_("Drake's"); +const char *ITEM_PREFIX_74_NAME = N_("Dragon's"); +const char *ITEM_PREFIX_75_NAME = N_("Wyrm's"); +const char *ITEM_PREFIX_76_NAME = N_("Hydra's"); +const char *ITEM_PREFIX_77_NAME = N_("Angel's"); +const char *ITEM_PREFIX_78_NAME = N_("Arch-Angel's"); +const char *ITEM_PREFIX_79_NAME = N_("Plentiful"); +const char *ITEM_PREFIX_80_NAME = N_("Bountiful"); +const char *ITEM_PREFIX_81_NAME = N_("Flaming"); +const char *ITEM_PREFIX_82_NAME = N_("Lightning"); +const char *ITEM_SUFFIX_0_NAME = N_("quality"); +const char *ITEM_SUFFIX_1_NAME = N_("maiming"); +const char *ITEM_SUFFIX_2_NAME = N_("slaying"); +const char *ITEM_SUFFIX_3_NAME = N_("gore"); +const char *ITEM_SUFFIX_4_NAME = N_("carnage"); +const char *ITEM_SUFFIX_5_NAME = N_("slaughter"); +const char *ITEM_SUFFIX_6_NAME = N_("pain"); +const char *ITEM_SUFFIX_7_NAME = N_("tears"); +const char *ITEM_SUFFIX_8_NAME = N_("health"); +const char *ITEM_SUFFIX_9_NAME = N_("protection"); +const char *ITEM_SUFFIX_10_NAME = N_("absorption"); +const char *ITEM_SUFFIX_11_NAME = N_("deflection"); +const char *ITEM_SUFFIX_12_NAME = N_("osmosis"); +const char *ITEM_SUFFIX_13_NAME = N_("frailty"); +const char *ITEM_SUFFIX_14_NAME = N_("weakness"); +const char *ITEM_SUFFIX_15_NAME = N_("strength"); +const char *ITEM_SUFFIX_16_NAME = N_("might"); +const char *ITEM_SUFFIX_17_NAME = N_("power"); +const char *ITEM_SUFFIX_18_NAME = N_("giants"); +const char *ITEM_SUFFIX_19_NAME = N_("titans"); +const char *ITEM_SUFFIX_20_NAME = N_("paralysis"); +const char *ITEM_SUFFIX_21_NAME = N_("atrophy"); +const char *ITEM_SUFFIX_22_NAME = N_("dexterity"); +const char *ITEM_SUFFIX_23_NAME = N_("skill"); +const char *ITEM_SUFFIX_24_NAME = N_("accuracy"); +const char *ITEM_SUFFIX_25_NAME = N_("precision"); +const char *ITEM_SUFFIX_26_NAME = N_("perfection"); +const char *ITEM_SUFFIX_27_NAME = N_("the fool"); +const char *ITEM_SUFFIX_28_NAME = N_("dyslexia"); +const char *ITEM_SUFFIX_29_NAME = N_("magic"); +const char *ITEM_SUFFIX_30_NAME = N_("the mind"); +const char *ITEM_SUFFIX_31_NAME = N_("brilliance"); +const char *ITEM_SUFFIX_32_NAME = N_("sorcery"); +const char *ITEM_SUFFIX_33_NAME = N_("wizardry"); +const char *ITEM_SUFFIX_34_NAME = N_("illness"); +const char *ITEM_SUFFIX_35_NAME = N_("disease"); +const char *ITEM_SUFFIX_36_NAME = N_("vitality"); +const char *ITEM_SUFFIX_37_NAME = N_("zest"); +const char *ITEM_SUFFIX_38_NAME = N_("vim"); +const char *ITEM_SUFFIX_39_NAME = N_("vigor"); +const char *ITEM_SUFFIX_40_NAME = N_("life"); +const char *ITEM_SUFFIX_41_NAME = N_("trouble"); +const char *ITEM_SUFFIX_42_NAME = N_("the pit"); +const char *ITEM_SUFFIX_43_NAME = N_("the sky"); +const char *ITEM_SUFFIX_44_NAME = N_("the moon"); +const char *ITEM_SUFFIX_45_NAME = N_("the stars"); +const char *ITEM_SUFFIX_46_NAME = N_("the heavens"); +const char *ITEM_SUFFIX_47_NAME = N_("the zodiac"); +const char *ITEM_SUFFIX_48_NAME = N_("the vulture"); +const char *ITEM_SUFFIX_49_NAME = N_("the jackal"); +const char *ITEM_SUFFIX_50_NAME = N_("the fox"); +const char *ITEM_SUFFIX_51_NAME = N_("the jaguar"); +const char *ITEM_SUFFIX_52_NAME = N_("the eagle"); +const char *ITEM_SUFFIX_53_NAME = N_("the wolf"); +const char *ITEM_SUFFIX_54_NAME = N_("the tiger"); +const char *ITEM_SUFFIX_55_NAME = N_("the lion"); +const char *ITEM_SUFFIX_56_NAME = N_("the mammoth"); +const char *ITEM_SUFFIX_57_NAME = N_("the whale"); +const char *ITEM_SUFFIX_58_NAME = N_("fragility"); +const char *ITEM_SUFFIX_59_NAME = N_("brittleness"); +const char *ITEM_SUFFIX_60_NAME = N_("sturdiness"); +const char *ITEM_SUFFIX_61_NAME = N_("craftsmanship"); +const char *ITEM_SUFFIX_62_NAME = N_("structure"); +const char *ITEM_SUFFIX_63_NAME = N_("the ages"); +const char *ITEM_SUFFIX_64_NAME = N_("the dark"); +const char *ITEM_SUFFIX_65_NAME = N_("the night"); +const char *ITEM_SUFFIX_66_NAME = N_("light"); +const char *ITEM_SUFFIX_67_NAME = N_("radiance"); +const char *ITEM_SUFFIX_68_NAME = N_("flame"); +const char *ITEM_SUFFIX_69_NAME = N_("fire"); +const char *ITEM_SUFFIX_70_NAME = N_("burning"); +const char *ITEM_SUFFIX_71_NAME = N_("shock"); +const char *ITEM_SUFFIX_72_NAME = N_("lightning"); +const char *ITEM_SUFFIX_73_NAME = N_("thunder"); +const char *ITEM_SUFFIX_74_NAME = N_("many"); +const char *ITEM_SUFFIX_75_NAME = N_("plenty"); +const char *ITEM_SUFFIX_76_NAME = N_("thorns"); +const char *ITEM_SUFFIX_77_NAME = N_("corruption"); +const char *ITEM_SUFFIX_78_NAME = N_("thieves"); +const char *ITEM_SUFFIX_79_NAME = N_("the bear"); +const char *ITEM_SUFFIX_80_NAME = N_("the bat"); +const char *ITEM_SUFFIX_81_NAME = N_("vampires"); +const char *ITEM_SUFFIX_82_NAME = N_("the leech"); +const char *ITEM_SUFFIX_83_NAME = N_("blood"); +const char *ITEM_SUFFIX_84_NAME = N_("piercing"); +const char *ITEM_SUFFIX_85_NAME = N_("puncturing"); +const char *ITEM_SUFFIX_86_NAME = N_("bashing"); +const char *ITEM_SUFFIX_87_NAME = N_("readiness"); +const char *ITEM_SUFFIX_88_NAME = N_("swiftness"); +const char *ITEM_SUFFIX_89_NAME = N_("speed"); +const char *ITEM_SUFFIX_90_NAME = N_("haste"); +const char *ITEM_SUFFIX_91_NAME = N_("balance"); +const char *ITEM_SUFFIX_92_NAME = N_("stability"); +const char *ITEM_SUFFIX_93_NAME = N_("harmony"); +const char *ITEM_SUFFIX_94_NAME = N_("blocking"); +const char *QUEST_THE_MAGIC_ROCK_NAME = N_("The Magic Rock"); +const char *QUEST_GHARBAD_THE_WEAK_NAME = N_("Gharbad The Weak"); +const char *QUEST_ZHAR_THE_MAD_NAME = N_("Zhar the Mad"); +const char *QUEST_LACHDANAN_NAME = N_("Lachdanan"); +const char *QUEST_DIABLO_NAME = N_("Diablo"); +const char *QUEST_THE_BUTCHER_NAME = N_("The Butcher"); +const char *QUEST_OGDENS_SIGN_NAME = N_("Ogden's Sign"); +const char *QUEST_HALLS_OF_THE_BLIND_NAME = N_("Halls of the Blind"); +const char *QUEST_VALOR_NAME = N_("Valor"); +const char *QUEST_WARLORD_OF_BLOOD_NAME = N_("Warlord of Blood"); +const char *QUEST_THE_CURSE_OF_KING_LEORIC_NAME = N_("The Curse of King Leoric"); +const char *QUEST_POISONED_WATER_SUPPLY_NAME = N_("Poisoned Water Supply"); +const char *QUEST_THE_CHAMBER_OF_BONE_NAME = N_("The Chamber of Bone"); +const char *QUEST_ARCHBISHOP_LAZARUS_NAME = N_("Archbishop Lazarus"); +const char *QUEST_GRAVE_MATTERS_NAME = N_("Grave Matters"); +const char *QUEST_FARMERS_ORCHARD_NAME = N_("Farmer's Orchard"); +const char *QUEST_LITTLE_GIRL_NAME = N_("Little Girl"); +const char *QUEST_WANDERING_TRADER_NAME = N_("Wandering Trader"); +const char *QUEST_THE_DEFILER_NAME = N_("The Defiler"); +const char *QUEST_NA_KRUL_NAME = N_("Na-Krul"); +const char *QUEST_CORNERSTONE_OF_THE_WORLD_NAME = N_("Cornerstone of the World"); +const char *QUEST_THE_JERSEYS_JERSEY_NAME = N_("The Jersey's Jersey"); +const char *SPELL_FIREBOLT_NAME = P_("spell", "Firebolt"); +const char *SPELL_HEALING_NAME = P_("spell", "Healing"); +const char *SPELL_LIGHTNING_NAME = P_("spell", "Lightning"); +const char *SPELL_FLASH_NAME = P_("spell", "Flash"); +const char *SPELL_IDENTIFY_NAME = P_("spell", "Identify"); +const char *SPELL_FIRE_WALL_NAME = P_("spell", "Fire Wall"); +const char *SPELL_TOWN_PORTAL_NAME = P_("spell", "Town Portal"); +const char *SPELL_STONE_CURSE_NAME = P_("spell", "Stone Curse"); +const char *SPELL_INFRAVISION_NAME = P_("spell", "Infravision"); +const char *SPELL_PHASING_NAME = P_("spell", "Phasing"); +const char *SPELL_MANA_SHIELD_NAME = P_("spell", "Mana Shield"); +const char *SPELL_FIREBALL_NAME = P_("spell", "Fireball"); +const char *SPELL_GUARDIAN_NAME = P_("spell", "Guardian"); +const char *SPELL_CHAIN_LIGHTNING_NAME = P_("spell", "Chain Lightning"); +const char *SPELL_FLAME_WAVE_NAME = P_("spell", "Flame Wave"); +const char *SPELL_DOOM_SERPENTS_NAME = P_("spell", "Doom Serpents"); +const char *SPELL_BLOOD_RITUAL_NAME = P_("spell", "Blood Ritual"); +const char *SPELL_NOVA_NAME = P_("spell", "Nova"); +const char *SPELL_INVISIBILITY_NAME = P_("spell", "Invisibility"); +const char *SPELL_INFERNO_NAME = P_("spell", "Inferno"); +const char *SPELL_GOLEM_NAME = P_("spell", "Golem"); +const char *SPELL_RAGE_NAME = P_("spell", "Rage"); +const char *SPELL_TELEPORT_NAME = P_("spell", "Teleport"); +const char *SPELL_APOCALYPSE_NAME = P_("spell", "Apocalypse"); +const char *SPELL_ETHEREALIZE_NAME = P_("spell", "Etherealize"); +const char *SPELL_ITEM_REPAIR_NAME = P_("spell", "Item Repair"); +const char *SPELL_STAFF_RECHARGE_NAME = P_("spell", "Staff Recharge"); +const char *SPELL_TRAP_DISARM_NAME = P_("spell", "Trap Disarm"); +const char *SPELL_ELEMENTAL_NAME = P_("spell", "Elemental"); +const char *SPELL_CHARGED_BOLT_NAME = P_("spell", "Charged Bolt"); +const char *SPELL_HOLY_BOLT_NAME = P_("spell", "Holy Bolt"); +const char *SPELL_RESURRECT_NAME = P_("spell", "Resurrect"); +const char *SPELL_TELEKINESIS_NAME = P_("spell", "Telekinesis"); +const char *SPELL_HEAL_OTHER_NAME = P_("spell", "Heal Other"); +const char *SPELL_BLOOD_STAR_NAME = P_("spell", "Blood Star"); +const char *SPELL_BONE_SPIRIT_NAME = P_("spell", "Bone Spirit"); +const char *TEXT_0 = N_(" Ahh, the story of our King, is it? The tragic fall of Leoric was a harsh blow to this land. The people always loved the King, and now they live in mortal fear of him. The question that I keep asking myself is how he could have fallen so far from the Light, as Leoric had always been the holiest of men. Only the vilest powers of Hell could so utterly destroy a man from within..."); +const char *TEXT_1 = N_("The village needs your help, good master! Some months ago King Leoric's son, Prince Albrecht, was kidnapped. The King went into a rage and scoured the village for his missing child. With each passing day, Leoric seemed to slip deeper into madness. He sought to blame innocent townsfolk for the boy's disappearance and had them brutally executed. Less than half of us survived his insanity...\n \nThe King's Knights and Priests tried to placate him, but he turned against them and sadly, they were forced to kill him. With his dying breath the King called down a terrible curse upon his former followers. He vowed that they would serve him in darkness forever...\n \nThis is where things take an even darker twist than I thought possible! Our former King has risen from his eternal sleep and now commands a legion of undead minions within the Labyrinth. His body was buried in a tomb three levels beneath the Cathedral. Please, good master, put his soul at ease by destroying his now cursed form..."); +const char *TEXT_2 = N_("As I told you, good master, the King was entombed three levels below. He's down there, waiting in the putrid darkness for his chance to destroy this land..."); +const char *TEXT_3 = N_("The curse of our King has passed, but I fear that it was only part of a greater evil at work. However, we may yet be saved from the darkness that consumes our land, for your victory is a good omen. May Light guide you on your way, good master."); +const char *TEXT_4 = N_("The loss of his son was too much for King Leoric. I did what I could to ease his madness, but in the end it overcame him. A black curse has hung over this kingdom from that day forward, but perhaps if you were to free his spirit from his earthly prison, the curse would be lifted..."); +const char *TEXT_5 = N_("I don't like to think about how the King died. I like to remember him for the kind and just ruler that he was. His death was so sad and seemed very wrong, somehow."); +const char *TEXT_6 = N_("I made many of the weapons and most of the armor that King Leoric used to outfit his knights. I even crafted a huge two-handed sword of the finest mithril for him, as well as a field crown to match. I still cannot believe how he died, but it must have been some sinister force that drove him insane!"); +const char *TEXT_7 = N_("I don't care about that. Listen, no skeleton is gonna be MY king. Leoric is King. King, so you hear me? HAIL TO THE KING!"); +const char *TEXT_8 = N_("The dead who walk among the living follow the cursed King. He holds the power to raise yet more warriors for an ever growing army of the undead. If you do not stop his reign, he will surely march across this land and slay all who still live here."); +const char *TEXT_9 = N_("Look, I'm running a business here. I don't sell information, and I don't care about some King that's been dead longer than I've been alive. If you need something to use against this King of the undead, then I can help you out..."); +const char *TEXT_10 = N_("The warmth of life has entered my tomb. Prepare yourself, mortal, to serve my Master for eternity!"); +const char *TEXT_11 = N_("I see that this strange behavior puzzles you as well. I would surmise that since many demons fear the light of the sun and believe that it holds great power, it may be that the rising sun depicted on the sign you speak of has led them to believe that it too holds some arcane powers. Hmm, perhaps they are not all as smart as we had feared..."); +const char *TEXT_12 = N_("Master, I have a strange experience to relate. I know that you have a great knowledge of those monstrosities that inhabit the labyrinth, and this is something that I cannot understand for the very life of me... I was awakened during the night by a scraping sound just outside of my tavern. When I looked out from my bedroom, I saw the shapes of small demon-like creatures in the inn yard. After a short time, they ran off, but not before stealing the sign to my inn. I don't know why the demons would steal my sign but leave my family in peace... 'tis strange, no?"); +const char *TEXT_13 = N_("Oh, you didn't have to bring back my sign, but I suppose that it does save me the expense of having another one made. Well, let me see, what could I give you as a fee for finding it? Hmmm, what have we here... ah, yes! This cap was left in one of the rooms by a magician who stayed here some time ago. Perhaps it may be of some value to you."); +const char *TEXT_14 = N_("My goodness, demons running about the village at night, pillaging our homes - is nothing sacred? I hope that Ogden and Garda are all right. I suppose that they would come to see me if they were hurt..."); +const char *TEXT_15 = N_("Oh my! Is that where the sign went? My Grandmother and I must have slept right through the whole thing. Thank the Light that those monsters didn't attack the inn."); +const char *TEXT_16 = N_("Demons stole Ogden's sign, you say? That doesn't sound much like the atrocities I've heard of - or seen. \n \nDemons are concerned with ripping out your heart, not your signpost."); +const char *TEXT_17 = N_("You know what I think? Somebody took that sign, and they gonna want lots of money for it. If I was Ogden... and I'm not, but if I was... I'd just buy a new sign with some pretty drawing on it. Maybe a nice mug of ale or a piece of cheese..."); +const char *TEXT_18 = N_("No mortal can truly understand the mind of the demon. \n \nNever let their erratic actions confuse you, as that too may be their plan."); +const char *TEXT_19 = N_("What - is he saying I took that? I suppose that Griswold is on his side, too. \n \nLook, I got over simple sign stealing months ago. You can't turn a profit on a piece of wood."); +const char *TEXT_20 = N_("Hey - You that one that kill all! You get me Magic Banner or we attack! You no leave with life! You kill big uglies and give back Magic. Go past corner and door, find uglies. You give, you go!"); +const char *TEXT_21 = N_("You kill uglies, get banner. You bring to me, or else..."); +const char *TEXT_22 = N_("You give! Yes, good! Go now, we strong. We kill all with big Magic!"); +const char *TEXT_23 = N_("This does not bode well, for it confirms my darkest fears. While I did not allow myself to believe the ancient legends, I cannot deny them now. Perhaps the time has come to reveal who I am.\n \nMy true name is Deckard Cain the Elder, and I am the last descendant of an ancient Brotherhood that was dedicated to safeguarding the secrets of a timeless evil. An evil that quite obviously has now been released.\n \nThe Archbishop Lazarus, once King Leoric's most trusted advisor, led a party of simple townsfolk into the Labyrinth to find the King's missing son, Albrecht. Quite some time passed before they returned, and only a few of them escaped with their lives.\n \nCurse me for a fool! I should have suspected his veiled treachery then. It must have been Lazarus himself who kidnapped Albrecht and has since hidden him within the Labyrinth. I do not understand why the Archbishop turned to the darkness, or what his interest is in the child, unless he means to sacrifice him to his dark masters!\n \nThat must be what he has planned! The survivors of his 'rescue party' say that Lazarus was last seen running into the deepest bowels of the labyrinth. You must hurry and save the prince from the sacrificial blade of this demented fiend!"); +const char *TEXT_24 = N_("You must hurry and rescue Albrecht from the hands of Lazarus. The prince and the people of this kingdom are counting on you!"); +const char *TEXT_25 = N_("Your story is quite grim, my friend. Lazarus will surely burn in Hell for his horrific deed. The boy that you describe is not our prince, but I believe that Albrecht may yet be in danger. The symbol of power that you speak of must be a portal in the very heart of the labyrinth.\n \nKnow this, my friend - The evil that you move against is the dark Lord of Terror. He is known to mortal men as Diablo. It was he who was imprisoned within the Labyrinth many centuries ago and I fear that he seeks to once again sow chaos in the realm of mankind. You must venture through the portal and destroy Diablo before it is too late!"); +const char *TEXT_26 = N_("Lazarus was the Archbishop who led many of the townspeople into the labyrinth. I lost many good friends that day, and Lazarus never returned. I suppose he was killed along with most of the others. If you would do me a favor, good master - please do not talk to Farnham about that day."); +const char *TEXT_29 = N_("I was shocked when I heard of what the townspeople were planning to do that night. I thought that of all people, Lazarus would have had more sense than that. He was an Archbishop, and always seemed to care so much for the townsfolk of Tristram. So many were injured, I could not save them all..."); +const char *TEXT_30 = N_("I remember Lazarus as being a very kind and giving man. He spoke at my mother's funeral, and was supportive of my grandmother and myself in a very troubled time. I pray every night that somehow, he is still alive and safe."); +const char *TEXT_31 = N_("I was there when Lazarus led us into the labyrinth. He spoke of holy retribution, but when we started fighting those hellspawn, he did not so much as lift his mace against them. He just ran deeper into the dim, endless chambers that were filled with the servants of darkness!"); +const char *TEXT_32 = N_("They stab, then bite, then they're all around you. Liar! LIAR! They're all dead! Dead! Do you hear me? They just keep falling and falling... their blood spilling out all over the floor... all his fault..."); +const char *TEXT_33 = N_("I did not know this Lazarus of whom you speak, but I do sense a great conflict within his being. He poses a great danger, and will stop at nothing to serve the powers of darkness which have claimed him as theirs."); +const char *TEXT_34 = N_("Yes, the righteous Lazarus, who was sooo effective against those monsters down there. Didn't help save my leg, did it? Look, I'll give you a free piece of advice. Ask Farnham, he was there."); +const char *TEXT_35 = N_("Abandon your foolish quest. All that awaits you is the wrath of my Master! You are too late to save the child. Now you will join him in Hell!"); +const char *TEXT_37 = N_("Hmm, I don't know what I can really tell you about this that will be of any help. The water that fills our wells comes from an underground spring. I have heard of a tunnel that leads to a great lake - perhaps they are one and the same. Unfortunately, I do not know what would cause our water supply to be tainted."); +const char *TEXT_38 = N_("I have always tried to keep a large supply of foodstuffs and drink in our storage cellar, but with the entire town having no source of fresh water, even our stores will soon run dry. \n \nPlease, do what you can or I don't know what we will do."); +const char *TEXT_39 = N_("I'm glad I caught up to you in time! Our wells have become brackish and stagnant and some of the townspeople have become ill drinking from them. Our reserves of fresh water are quickly running dry. I believe that there is a passage that leads to the springs that serve our town. Please find what has caused this calamity, or we all will surely perish."); +const char *TEXT_40 = N_("Please, you must hurry. Every hour that passes brings us closer to having no water to drink. \n \nWe cannot survive for long without your help."); +const char *TEXT_41 = N_("What's that you say - the mere presence of the demons had caused the water to become tainted? Oh, truly a great evil lurks beneath our town, but your perseverance and courage gives us hope. Please take this ring - perhaps it will aid you in the destruction of such vile creatures."); +const char *TEXT_42 = N_("My grandmother is very weak, and Garda says that we cannot drink the water from the wells. Please, can you do something to help us?"); +const char *TEXT_43 = N_("Pepin has told you the truth. We will need fresh water badly, and soon. I have tried to clear one of the smaller wells, but it reeks of stagnant filth. It must be getting clogged at the source."); +const char *TEXT_44 = N_("You drink water?"); +const char *TEXT_45 = N_("The people of Tristram will die if you cannot restore fresh water to their wells. \n \nKnow this - demons are at the heart of this matter, but they remain ignorant of what they have spawned."); +const char *TEXT_46 = N_("For once, I'm with you. My business runs dry - so to speak - if I have no market to sell to. You better find out what is going on, and soon!"); +const char *TEXT_47 = N_("A book that speaks of a chamber of human bones? Well, a Chamber of Bone is mentioned in certain archaic writings that I studied in the libraries of the East. These tomes inferred that when the Lords of the underworld desired to protect great treasures, they would create domains where those who died in the attempt to steal that treasure would be forever bound to defend it. A twisted, but strangely fitting, end?"); +const char *TEXT_48 = N_("I am afraid that I don't know anything about that, good master. Cain has many books that may be of some help."); +const char *TEXT_49 = N_("This sounds like a very dangerous place. If you venture there, please take great care."); +const char *TEXT_50 = N_("I am afraid that I haven't heard anything about that. Perhaps Cain the Storyteller could be of some help."); +const char *TEXT_51 = N_("I know nothing of this place, but you may try asking Cain. He talks about many things, and it would not surprise me if he had some answers to your question."); +const char *TEXT_52 = N_("Okay, so listen. There's this chamber of wood, see. And his wife, you know - her - tells the tree... cause you gotta wait. Then I says, that might work against him, but if you think I'm gonna PAY for this... you... uh... yeah."); +const char *TEXT_53 = N_("You will become an eternal servant of the dark lords should you perish within this cursed domain. \n \nEnter the Chamber of Bone at your own peril."); +const char *TEXT_54 = N_("A vast and mysterious treasure, you say? Maybe I could be interested in picking up a few things from you... or better yet, don't you need some rare and expensive supplies to get you through this ordeal?"); +const char *TEXT_55 = N_("It seems that the Archbishop Lazarus goaded many of the townsmen into venturing into the Labyrinth to find the King's missing son. He played upon their fears and whipped them into a frenzied mob. None of them were prepared for what lay within the cold earth... Lazarus abandoned them down there - left in the clutches of unspeakable horrors - to die."); +const char *TEXT_56 = N_("Yes, Farnham has mumbled something about a hulking brute who wielded a fierce weapon. I believe he called him a butcher."); +const char *TEXT_57 = N_("By the Light, I know of this vile demon. There were many that bore the scars of his wrath upon their bodies when the few survivors of the charge led by Lazarus crawled from the Cathedral. I don't know what he used to slice open his victims, but it could not have been of this world. It left wounds festering with disease and even I found them almost impossible to treat. Beware if you plan to battle this fiend..."); +const char *TEXT_58 = N_("When Farnham said something about a butcher killing people, I immediately discounted it. But since you brought it up, maybe it is true."); +const char *TEXT_59 = N_("I saw what Farnham calls the Butcher as it swathed a path through the bodies of my friends. He swung a cleaver as large as an axe, hewing limbs and cutting down brave men where they stood. I was separated from the fray by a host of small screeching demons and somehow found the stairway leading out. I never saw that hideous beast again, but his blood-stained visage haunts me to this day."); +const char *TEXT_60 = N_("Big! Big cleaver killing all my friends. Couldn't stop him, had to run away, couldn't save them. Trapped in a room with so many bodies... so many friends... NOOOOOOOOOO!"); +const char *TEXT_61 = N_("The Butcher is a sadistic creature that delights in the torture and pain of others. You have seen his handiwork in the drunkard Farnham. His destruction will do much to ensure the safety of this village."); +const char *TEXT_62 = N_("I know more than you'd think about that grisly fiend. His little friends got a hold of me and managed to get my leg before Griswold pulled me out of that hole. \n \nI'll put it bluntly - kill him before he kills you and adds your corpse to his collection."); +const char *TEXT_63 = N_("Please, listen to me. The Archbishop Lazarus, he led us down here to find the lost prince. The bastard led us into a trap! Now everyone is dead... killed by a demon he called the Butcher. Avenge us! Find this Butcher and slay him so that our souls may finally rest..."); +const char *TEXT_65 = N_("You recite an interesting rhyme written in a style that reminds me of other works. Let me think now - what was it?\n \n...Darkness shrouds the Hidden. Eyes glowing unseen with only the sounds of razor claws briefly scraping to torment those poor souls who have been made sightless for all eternity. The prison for those so damned is named the Halls of the Blind..."); +const char *TEXT_66 = N_("I never much cared for poetry. Occasionally, I had cause to hire minstrels when the inn was doing well, but that seems like such a long time ago now. \n \nWhat? Oh, yes... uh, well, I suppose you could see what someone else knows."); +const char *TEXT_67 = N_("This does seem familiar, somehow. I seem to recall reading something very much like that poem while researching the history of demonic afflictions. It spoke of a place of great evil that... wait - you're not going there are you?"); +const char *TEXT_68 = N_("If you have questions about blindness, you should talk to Pepin. I know that he gave my grandmother a potion that helped clear her vision, so maybe he can help you, too."); +const char *TEXT_69 = N_("I am afraid that I have neither heard nor seen a place that matches your vivid description, my friend. Perhaps Cain the Storyteller could be of some help."); +const char *TEXT_70 = N_("Look here... that's pretty funny, huh? Get it? Blind - look here?"); +const char *TEXT_71 = N_("This is a place of great anguish and terror, and so serves its master well. \n \nTread carefully or you may yourself be staying much longer than you had anticipated."); +const char *TEXT_72 = N_("Lets see, am I selling you something? No. Are you giving me money to tell you about this? No. Are you now leaving and going to talk to the storyteller who lives for this kind of thing? Yes."); +const char *TEXT_73 = N_("You claim to have spoken with Lachdanan? He was a great hero during his life. Lachdanan was an honorable and just man who served his King faithfully for years. But of course, you already know that.\n \nOf those who were caught within the grasp of the King's Curse, Lachdanan would be the least likely to submit to the darkness without a fight, so I suppose that your story could be true. If I were in your place, my friend, I would find a way to release him from his torture."); +const char *TEXT_74 = N_("You speak of a brave warrior long dead! I'll have no such talk of speaking with departed souls in my inn yard, thank you very much."); +const char *TEXT_75 = N_("A golden elixir, you say. I have never concocted a potion of that color before, so I can't tell you how it would effect you if you were to try to drink it. As your healer, I strongly advise that should you find such an elixir, do as Lachdanan asks and DO NOT try to use it."); +const char *TEXT_76 = N_("I've never heard of a Lachdanan before. I'm sorry, but I don't think that I can be of much help to you."); +const char *TEXT_77 = N_("If it is actually Lachdanan that you have met, then I would advise that you aid him. I dealt with him on several occasions and found him to be honest and loyal in nature. The curse that fell upon the followers of King Leoric would fall especially hard upon him."); +const char *TEXT_78 = N_(" Lachdanan is dead. Everybody knows that, and you can't fool me into thinking any other way. You can't talk to the dead. I know!"); +const char *TEXT_79 = N_("You may meet people who are trapped within the Labyrinth, such as Lachdanan. \n \nI sense in him honor and great guilt. Aid him, and you aid all of Tristram."); +const char *TEXT_80 = N_("Wait, let me guess. Cain was swallowed up in a gigantic fissure that opened beneath him. He was incinerated in a ball of hellfire, and can't answer your questions anymore. Oh, that isn't what happened? Then I guess you'll be buying something or you'll be on your way."); +const char *TEXT_81 = N_("Please, don't kill me, just hear me out. I was once Captain of King Leoric's Knights, upholding the laws of this land with justice and honor. Then his dark Curse fell upon us for the role we played in his tragic death. As my fellow Knights succumbed to their twisted fate, I fled from the King's burial chamber, searching for some way to free myself from the Curse. I failed...\n \nI have heard of a Golden Elixir that could lift the Curse and allow my soul to rest, but I have been unable to find it. My strength now wanes, and with it the last of my humanity as well. Please aid me and find the Elixir. I will repay your efforts - I swear upon my honor."); +const char *TEXT_82 = N_("You have not found the Golden Elixir. I fear that I am doomed for eternity. Please, keep trying..."); +const char *TEXT_83 = N_("You have saved my soul from damnation, and for that I am in your debt. If there is ever a way that I can repay you from beyond the grave I will find it, but for now - take my helm. On the journey I am about to take I will have little use for it. May it protect you against the dark powers below. Go with the Light, my friend..."); +const char *TEXT_84 = N_("Griswold speaks of The Anvil of Fury - a legendary artifact long searched for, but never found. Crafted from the metallic bones of the Razor Pit demons, the Anvil of Fury was smelt around the skulls of the five most powerful magi of the underworld. Carved with runes of power and chaos, any weapon or armor forged upon this Anvil will be immersed into the realm of Chaos, imbedding it with magical properties. It is said that the unpredictable nature of Chaos makes it difficult to know what the outcome of this smithing will be..."); +const char *TEXT_85 = N_("Don't you think that Griswold would be a better person to ask about this? He's quite handy, you know."); +const char *TEXT_86 = N_("If you had been looking for information on the Pestle of Curing or the Silver Chalice of Purification, I could have assisted you, my friend. However, in this matter, you would be better served to speak to either Griswold or Cain."); +const char *TEXT_87 = N_("Griswold's father used to tell some of us when we were growing up about a giant anvil that was used to make mighty weapons. He said that when a hammer was struck upon this anvil, the ground would shake with a great fury. Whenever the earth moves, I always remember that story."); +const char *TEXT_88 = N_("Greetings! It's always a pleasure to see one of my best customers! I know that you have been venturing deeper into the Labyrinth, and there is a story I was told that you may find worth the time to listen to...\n \nOne of the men who returned from the Labyrinth told me about a mystic anvil that he came across during his escape. His description reminded me of legends I had heard in my youth about the burning Hellforge where powerful weapons of magic are crafted. The legend had it that deep within the Hellforge rested the Anvil of Fury! This Anvil contained within it the very essence of the demonic underworld...\n \nIt is said that any weapon crafted upon the burning Anvil is imbued with great power. If this anvil is indeed the Anvil of Fury, I may be able to make you a weapon capable of defeating even the darkest lord of Hell! \n \nFind the Anvil for me, and I'll get to work!"); +const char *TEXT_89 = N_("Nothing yet, eh? Well, keep searching. A weapon forged upon the Anvil could be your best hope, and I am sure that I can make you one of legendary proportions."); +const char *TEXT_90 = N_("I can hardly believe it! This is the Anvil of Fury - good work, my friend. Now we'll show those bastards that there are no weapons in Hell more deadly than those made by men! Take this and may Light protect you."); +const char *TEXT_91 = N_("Griswold can't sell his anvil. What will he do then? And I'd be angry too if someone took my anvil!"); +const char *TEXT_92 = N_("There are many artifacts within the Labyrinth that hold powers beyond the comprehension of mortals. Some of these hold fantastic power that can be used by either the Light or the Darkness. Securing the Anvil from below could shift the course of the Sin War towards the Light."); +const char *TEXT_93 = N_("If you were to find this artifact for Griswold, it could put a serious damper on my business here. Awwww, you'll never find it."); +const char *TEXT_94 = N_("The Gateway of Blood and the Halls of Fire are landmarks of mystic origin. Wherever this book you read from resides it is surely a place of great power.\n \nLegends speak of a pedestal that is carved from obsidian stone and has a pool of boiling blood atop its bone encrusted surface. There are also allusions to Stones of Blood that will open a door that guards an ancient treasure...\n \nThe nature of this treasure is shrouded in speculation, my friend, but it is said that the ancient hero Arkaine placed the holy armor Valor in a secret vault. Arkaine was the first mortal to turn the tide of the Sin War and chase the legions of darkness back to the Burning Hells.\n \nJust before Arkaine died, his armor was hidden away in a secret vault. It is said that when this holy armor is again needed, a hero will arise to don Valor once more. Perhaps you are that hero..."); +const char *TEXT_95 = N_("Every child hears the story of the warrior Arkaine and his mystic armor known as Valor. If you could find its resting place, you would be well protected against the evil in the Labyrinth."); +const char *TEXT_96 = N_("Hmm... it sounds like something I should remember, but I've been so busy learning new cures and creating better elixirs that I must have forgotten. Sorry..."); +const char *TEXT_97 = N_("The story of the magic armor called Valor is something I often heard the boys talk about. You had better ask one of the men in the village."); +const char *TEXT_98 = N_("The armor known as Valor could be what tips the scales in your favor. I will tell you that many have looked for it - including myself. Arkaine hid it well, my friend, and it will take more than a bit of luck to unlock the secrets that have kept it concealed oh, lo these many years."); +const char *TEXT_99 = N_("Zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz..."); +const char *TEXT_100 = N_("Should you find these Stones of Blood, use them carefully. \n \nThe way is fraught with danger and your only hope rests within your self trust."); +const char *TEXT_101 = N_("You intend to find the armor known as Valor? \n \nNo one has ever figured out where Arkaine stashed the stuff, and if my contacts couldn't find it, I seriously doubt you ever will either."); +const char *TEXT_102 = N_("I know of only one legend that speaks of such a warrior as you describe. His story is found within the ancient chronicles of the Sin War...\n \nStained by a thousand years of war, blood and death, the Warlord of Blood stands upon a mountain of his tattered victims. His dark blade screams a black curse to the living; a tortured invitation to any who would stand before this Executioner of Hell.\n \nIt is also written that although he was once a mortal who fought beside the Legion of Darkness during the Sin War, he lost his humanity to his insatiable hunger for blood."); +const char *TEXT_103 = N_("I am afraid that I haven't heard anything about such a vicious warrior, good master. I hope that you do not have to fight him, for he sounds extremely dangerous."); +const char *TEXT_104 = N_("Cain would be able to tell you much more about something like this than I would ever wish to know."); +const char *TEXT_105 = N_("If you are to battle such a fierce opponent, may Light be your guide and your defender. I will keep you in my thoughts."); +const char *TEXT_106 = N_("Dark and wicked legends surrounds the one Warlord of Blood. Be well prepared, my friend, for he shows no mercy or quarter."); +const char *TEXT_107 = N_("Always you gotta talk about Blood? What about flowers, and sunshine, and that pretty girl that brings the drinks. Listen here, friend - you're obsessive, you know that?"); +const char *TEXT_108 = N_("His prowess with the blade is awesome, and he has lived for thousands of years knowing only warfare. I am sorry... I can not see if you will defeat him."); +const char *TEXT_109 = N_("I haven't ever dealt with this Warlord you speak of, but he sounds like he's going through a lot of swords. Wouldn't mind supplying his armies..."); +const char *TEXT_110 = N_("My blade sings for your blood, mortal, and by my dark masters it shall not be denied."); +const char *TEXT_111 = N_("Griswold speaks of the Heaven Stone that was destined for the enclave located in the east. It was being taken there for further study. This stone glowed with an energy that somehow granted vision beyond that which a normal man could possess. I do not know what secrets it holds, my friend, but finding this stone would certainly prove most valuable."); +const char *TEXT_112 = N_("The caravan stopped here to take on some supplies for their journey to the east. I sold them quite an array of fresh fruits and some excellent sweetbreads that Garda has just finished baking. Shame what happened to them..."); +const char *TEXT_113 = N_("I don't know what it is that they thought they could see with that rock, but I will say this. If rocks are falling from the sky, you had better be careful!"); +const char *TEXT_114 = N_("Well, a caravan of some very important people did stop here, but that was quite a while ago. They had strange accents and were starting on a long journey, as I recall. \n \nI don't see how you could hope to find anything that they would have been carrying."); +const char *TEXT_115 = N_("Stay for a moment - I have a story you might find interesting. A caravan that was bound for the eastern kingdoms passed through here some time ago. It was supposedly carrying a piece of the heavens that had fallen to earth! The caravan was ambushed by cloaked riders just north of here along the roadway. I searched the wreckage for this sky rock, but it was nowhere to be found. If you should find it, I believe that I can fashion something useful from it."); +const char *TEXT_116 = N_("I am still waiting for you to bring me that stone from the heavens. I know that I can make something powerful out of it."); +const char *TEXT_117 = N_("Let me see that - aye... aye, it is as I believed. Give me a moment...\n \nAh, Here you are. I arranged pieces of the stone within a silver ring that my father left me. I hope it serves you well."); +const char *TEXT_118 = N_("I used to have a nice ring; it was a really expensive one, with blue and green and red and silver. Don't remember what happened to it, though. I really miss that ring..."); +const char *TEXT_119 = N_("The Heaven Stone is very powerful, and were it any but Griswold who bid you find it, I would prevent it. He will harness its powers and its use will be for the good of us all."); +const char *TEXT_120 = N_("If anyone can make something out of that rock, Griswold can. He knows what he is doing, and as much as I try to steal his customers, I respect the quality of his work."); +const char *TEXT_121 = N_("The witch Adria seeks a black mushroom? I know as much about Black Mushrooms as I do about Red Herrings. Perhaps Pepin the Healer could tell you more, but this is something that cannot be found in any of my stories or books."); +const char *TEXT_122 = N_("Let me just say this. Both Garda and I would never, EVER serve black mushrooms to our honored guests. If Adria wants some mushrooms in her stew, then that is her business, but I can't help you find any. Black mushrooms... disgusting!"); +const char *TEXT_123 = N_("The witch told me that you were searching for the brain of a demon to assist me in creating my elixir. It should be of great value to the many who are injured by those foul beasts, if I can just unlock the secrets I suspect that its alchemy holds. If you can remove the brain of a demon when you kill it, I would be grateful if you could bring it to me."); +const char *TEXT_124 = N_("Excellent, this is just what I had in mind. I was able to finish the elixir without this, but it can't hurt to have this to study. Would you please carry this to the witch? I believe that she is expecting it."); +const char *TEXT_125 = N_("I think Ogden might have some mushrooms in the storage cellar. Why don't you ask him?"); +const char *TEXT_126 = N_("If Adria doesn't have one of these, you can bet that's a rare thing indeed. I can offer you no more help than that, but it sounds like... a huge, gargantuan, swollen, bloated mushroom! Well, good hunting, I suppose."); +const char *TEXT_127 = N_("Ogden mixes a MEAN black mushroom, but I get sick if I drink that. Listen, listen... here's the secret - moderation is the key!"); +const char *TEXT_128 = N_("What do we have here? Interesting, it looks like a book of reagents. Keep your eyes open for a black mushroom. It should be fairly large and easy to identify. If you find it, bring it to me, won't you?"); +const char *TEXT_129 = N_("It's a big, black mushroom that I need. Now run off and get it for me so that I can use it for a special concoction that I am working on."); +const char *TEXT_130 = N_("Yes, this will be perfect for a brew that I am creating. By the way, the healer is looking for the brain of some demon or another so he can treat those who have been afflicted by their poisonous venom. I believe that he intends to make an elixir from it. If you help him find what he needs, please see if you can get a sample of the elixir for me."); +const char *TEXT_131 = N_("Why have you brought that here? I have no need for a demon's brain at this time. I do need some of the elixir that the Healer is working on. He needs that grotesque organ that you are holding, and then bring me the elixir. Simple when you think about it, isn't it?"); +const char *TEXT_132 = N_("What? Now you bring me that elixir from the healer? I was able to finish my brew without it. Why don't you just keep it..."); +const char *TEXT_133 = N_("I don't have any mushrooms of any size or color for sale. How about something a bit more useful?"); +const char *TEXT_134 = N_("So, the legend of the Map is real. Even I never truly believed any of it! I suppose it is time that I told you the truth about who I am, my friend. You see, I am not all that I seem...\n \nMy true name is Deckard Cain the Elder, and I am the last descendant of an ancient Brotherhood that was dedicated to keeping and safeguarding the secrets of a timeless evil. An evil that quite obviously has now been released...\n \nThe evil that you move against is the dark Lord of Terror - known to mortal men as Diablo. It was he who was imprisoned within the Labyrinth many centuries ago. The Map that you hold now was created ages ago to mark the time when Diablo would rise again from his imprisonment. When the two stars on that map align, Diablo will be at the height of his power. He will be all but invincible...\n \nYou are now in a race against time, my friend! Find Diablo and destroy him before the stars align, for we may never have a chance to rid the world of his evil again!"); +const char *TEXT_135 = N_("Our time is running short! I sense his dark power building and only you can stop him from attaining his full might."); +const char *TEXT_136 = N_("I am sure that you tried your best, but I fear that even your strength and will may not be enough. Diablo is now at the height of his earthly power, and you will need all your courage and strength to defeat him. May the Light protect and guide you, my friend. I will help in any way that I am able."); +const char *TEXT_137 = N_("If the witch can't help you and suggests you see Cain, what makes you think that I would know anything? It sounds like this is a very serious matter. You should hurry along and see the storyteller as Adria suggests."); +const char *TEXT_138 = N_("I can't make much of the writing on this map, but perhaps Adria or Cain could help you decipher what this refers to. \n \nI can see that it is a map of the stars in our sky, but any more than that is beyond my talents."); +const char *TEXT_139 = N_("The best person to ask about that sort of thing would be our storyteller. \n \nCain is very knowledgeable about ancient writings, and that is easily the oldest looking piece of paper that I have ever seen."); +const char *TEXT_140 = N_("I have never seen a map of this sort before. Where'd you get it? Although I have no idea how to read this, Cain or Adria may be able to provide the answers that you seek."); +const char *TEXT_141 = N_("Listen here, come close. I don't know if you know what I know, but you have really got somethin' here. That's a map."); +const char *TEXT_142 = N_("Oh, I'm afraid this does not bode well at all. This map of the stars portends great disaster, but its secrets are not mine to tell. The time has come for you to have a very serious conversation with the Storyteller..."); +const char *TEXT_143 = N_("I've been looking for a map, but that certainly isn't it. You should show that to Adria - she can probably tell you what it is. I'll say one thing; it looks old, and old usually means valuable."); +const char *TEXT_144 = N_("Pleeeease, no hurt. No Kill. Keep alive and next time good bring to you."); +const char *TEXT_145 = N_("Something for you I am making. Again, not kill Gharbad. Live and give good. \n \nYou take this as proof I keep word..."); +const char *TEXT_146 = N_("Nothing yet! Almost done. \n \nVery powerful, very strong. Live! Live! \n \nNo pain and promise I keep!"); +const char *TEXT_147 = N_("This too good for you. Very Powerful! You want - you take!"); +const char *TEXT_148 = N_("What?! Why are you here? All these interruptions are enough to make one insane. Here, take this and leave me to my work. Trouble me no more!"); +const char *TEXT_149 = N_("Arrrrgh! Your curiosity will be the death of you!!!"); +const char *TEXT_150 = N_("Hello, my friend. Stay awhile and listen..."); +const char *TEXT_151 = N_("While you are venturing deeper into the Labyrinth you may find tomes of great knowledge hidden there. \n \nRead them carefully for they can tell you things that even I cannot."); +const char *TEXT_152 = N_("I know of many myths and legends that may contain answers to questions that may arise in your journeys into the Labyrinth. If you come across challenges and questions to which you seek knowledge, seek me out and I will tell you what I can."); +const char *TEXT_153 = N_("Griswold - a man of great action and great courage. I bet he never told you about the time he went into the Labyrinth to save Wirt, did he? He knows his fair share of the dangers to be found there, but then again - so do you. He is a skilled craftsman, and if he claims to be able to help you in any way, you can count on his honesty and his skill."); +const char *TEXT_154 = N_("Ogden has owned and run the Rising Sun Inn and Tavern for almost four years now. He purchased it just a few short months before everything here went to hell. He and his wife Garda do not have the money to leave as they invested all they had in making a life for themselves here. He is a good man with a deep sense of responsibility."); +const char *TEXT_155 = N_("Poor Farnham. He is a disquieting reminder of the doomed assembly that entered into the Cathedral with Lazarus on that dark day. He escaped with his life, but his courage and much of his sanity were left in some dark pit. He finds comfort only at the bottom of his tankard nowadays, but there are occasional bits of truth buried within his constant ramblings."); +const char *TEXT_156 = N_("The witch, Adria, is an anomaly here in Tristram. She arrived shortly after the Cathedral was desecrated while most everyone else was fleeing. She had a small hut constructed at the edge of town, seemingly overnight, and has access to many strange and arcane artifacts and tomes of knowledge that even I have never seen before."); +const char *TEXT_157 = N_("The story of Wirt is a frightening and tragic one. He was taken from the arms of his mother and dragged into the labyrinth by the small, foul demons that wield wicked spears. There were many other children taken that day, including the son of King Leoric. The Knights of the palace went below, but never returned. The Blacksmith found the boy, but only after the foul beasts had begun to torture him for their sadistic pleasures."); +const char *TEXT_158 = N_("Ah, Pepin. I count him as a true friend - perhaps the closest I have here. He is a bit addled at times, but never a more caring or considerate soul has existed. His knowledge and skills are equaled by few, and his door is always open."); +const char *TEXT_159 = N_("Gillian is a fine woman. Much adored for her high spirits and her quick laugh, she holds a special place in my heart. She stays on at the tavern to support her elderly grandmother who is too sick to travel. I sometimes fear for her safety, but I know that any man in the village would rather die than see her harmed."); +const char *TEXT_160 = N_("Greetings, good master. Welcome to the Tavern of the Rising Sun!"); +const char *TEXT_161 = N_("Many adventurers have graced the tables of my tavern, and ten times as many stories have been told over as much ale. The only thing that I ever heard any of them agree on was this old axiom. Perhaps it will help you. You can cut the flesh, but you must crush the bone."); +const char *TEXT_162 = N_("Griswold the blacksmith is extremely knowledgeable about weapons and armor. If you ever need work done on your gear, he is definitely the man to see."); +const char *TEXT_163 = N_("Farnham spends far too much time here, drowning his sorrows in cheap ale. I would make him leave, but he did suffer so during his time in the Labyrinth."); +const char *TEXT_164 = N_("Adria is wise beyond her years, but I must admit - she frightens me a little. \n \nWell, no matter. If you ever have need to trade in items of sorcery, she maintains a strangely well-stocked hut just across the river."); +const char *TEXT_165 = N_("If you want to know more about the history of our village, the storyteller Cain knows quite a bit about the past."); +const char *TEXT_166 = N_("Wirt is a rapscallion and a little scoundrel. He was always getting into trouble, and it's no surprise what happened to him. \n \nHe probably went fooling about someplace that he shouldn't have been. I feel sorry for the boy, but I don't abide the company that he keeps."); +const char *TEXT_167 = N_("Pepin is a good man - and certainly the most generous in the village. He is always attending to the needs of others, but trouble of some sort or another does seem to follow him wherever he goes..."); +const char *TEXT_168 = N_("Gillian, my Barmaid? If it were not for her sense of duty to her grand-dam, she would have fled from here long ago. \n \nGoodness knows I begged her to leave, telling her that I would watch after the old woman, but she is too sweet and caring to have done so."); +const char *TEXT_169 = N_("What ails you, my friend?"); +const char *TEXT_170 = N_("I have made a very interesting discovery. Unlike us, the creatures in the Labyrinth can heal themselves without the aid of potions or magic. If you hurt one of the monsters, make sure it is dead or it very well may regenerate itself."); +const char *TEXT_171 = N_("Before it was taken over by, well, whatever lurks below, the Cathedral was a place of great learning. There are many books to be found there. If you find any, you should read them all, for some may hold secrets to the workings of the Labyrinth."); +const char *TEXT_172 = N_("Griswold knows as much about the art of war as I do about the art of healing. He is a shrewd merchant, but his work is second to none. Oh, I suppose that may be because he is the only blacksmith left here."); +const char *TEXT_173 = N_("Cain is a true friend and a wise sage. He maintains a vast library and has an innate ability to discern the true nature of many things. If you ever have any questions, he is the person to go to."); +const char *TEXT_174 = N_("Even my skills have been unable to fully heal Farnham. Oh, I have been able to mend his body, but his mind and spirit are beyond anything I can do."); +const char *TEXT_175 = N_("While I use some limited forms of magic to create the potions and elixirs I store here, Adria is a true sorceress. She never seems to sleep, and she always has access to many mystic tomes and artifacts. I believe her hut may be much more than the hovel it appears to be, but I can never seem to get inside the place."); +const char *TEXT_176 = N_("Poor Wirt. I did all that was possible for the child, but I know he despises that wooden peg that I was forced to attach to his leg. His wounds were hideous. No one - and especially such a young child - should have to suffer the way he did."); +const char *TEXT_177 = N_("I really don't understand why Ogden stays here in Tristram. He suffers from a slight nervous condition, but he is an intelligent and industrious man who would do very well wherever he went. I suppose it may be the fear of the many murders that happen in the surrounding countryside, or perhaps the wishes of his wife that keep him and his family where they are."); +const char *TEXT_178 = N_("Ogden's barmaid is a sweet girl. Her grandmother is quite ill, and suffers from delusions. \n \nShe claims that they are visions, but I have no proof of that one way or the other."); +const char *TEXT_179 = N_("Good day! How may I serve you?"); +const char *TEXT_180 = N_("My grandmother had a dream that you would come and talk to me. She has visions, you know and can see into the future."); +const char *TEXT_181 = N_("The woman at the edge of town is a witch! She seems nice enough, and her name, Adria, is very pleasing to the ear, but I am very afraid of her. \n \nIt would take someone quite brave, like you, to see what she is doing out there."); +const char *TEXT_182 = N_("Our Blacksmith is a point of pride to the people of Tristram. Not only is he a master craftsman who has won many contests within his guild, but he received praises from our King Leoric himself - may his soul rest in peace. Griswold is also a great hero; just ask Cain."); +const char *TEXT_183 = N_("Cain has been the storyteller of Tristram for as long as I can remember. He knows so much, and can tell you just about anything about almost everything."); +const char *TEXT_184 = N_("Farnham is a drunkard who fills his belly with ale and everyone else's ears with nonsense. \n \nI know that both Pepin and Ogden feel sympathy for him, but I get so frustrated watching him slip farther and farther into a befuddled stupor every night."); +const char *TEXT_185 = N_("Pepin saved my grandmother's life, and I know that I can never repay him for that. His ability to heal any sickness is more powerful than the mightiest sword and more mysterious than any spell you can name. If you ever are in need of healing, Pepin can help you."); +const char *TEXT_186 = N_("I grew up with Wirt's mother, Canace. Although she was only slightly hurt when those hideous creatures stole him, she never recovered. I think she died of a broken heart. Wirt has become a mean-spirited youngster, looking only to profit from the sweat of others. I know that he suffered and has seen horrors that I cannot even imagine, but some of that darkness hangs over him still."); +const char *TEXT_187 = N_("Ogden and his wife have taken me and my grandmother into their home and have even let me earn a few gold pieces by working at the inn. I owe so much to them, and hope one day to leave this place and help them start a grand hotel in the east."); +const char *TEXT_188 = N_("Well, what can I do for ya?"); +const char *TEXT_189 = N_("If you're looking for a good weapon, let me show this to you. Take your basic blunt weapon, such as a mace. Works like a charm against most of those undying horrors down there, and there's nothing better to shatter skinny little skeletons!"); +const char *TEXT_190 = N_("The axe? Aye, that's a good weapon, balanced against any foe. Look how it cleaves the air, and then imagine a nice fat demon head in its path. Keep in mind, however, that it is slow to swing - but talk about dealing a heavy blow!"); +const char *TEXT_191 = N_("Look at that edge, that balance. A sword in the right hands, and against the right foe, is the master of all weapons. Its keen blade finds little to hack or pierce on the undead, but against a living, breathing enemy, a sword will better slice their flesh!"); +const char *TEXT_192 = N_("Your weapons and armor will show the signs of your struggles against the Darkness. If you bring them to me, with a bit of work and a hot forge, I can restore them to top fighting form."); +const char *TEXT_193 = N_("While I have to practically smuggle in the metals and tools I need from caravans that skirt the edges of our damned town, that witch, Adria, always seems to get whatever she needs. If I knew even the smallest bit about how to harness magic as she did, I could make some truly incredible things."); +const char *TEXT_194 = N_("Gillian is a nice lass. Shame that her gammer is in such poor health or I would arrange to get both of them out of here on one of the trading caravans."); +const char *TEXT_195 = N_("Sometimes I think that Cain talks too much, but I guess that is his calling in life. If I could bend steel as well as he can bend your ear, I could make a suit of court plate good enough for an Emperor!"); +const char *TEXT_196 = N_("I was with Farnham that night that Lazarus led us into Labyrinth. I never saw the Archbishop again, and I may not have survived if Farnham was not at my side. I fear that the attack left his soul as crippled as, well, another did my leg. I cannot fight this battle for him now, but I would if I could."); +const char *TEXT_197 = N_("A good man who puts the needs of others above his own. You won't find anyone left in Tristram - or anywhere else for that matter - who has a bad thing to say about the healer."); +const char *TEXT_198 = N_("That lad is going to get himself into serious trouble... or I guess I should say, again. I've tried to interest him in working here and learning an honest trade, but he prefers the high profits of dealing in goods of dubious origin. I cannot hold that against him after what happened to him, but I do wish he would at least be careful."); +const char *TEXT_199 = N_("The Innkeeper has little business and no real way of turning a profit. He manages to make ends meet by providing food and lodging for those who occasionally drift through the village, but they are as likely to sneak off into the night as they are to pay him. If it weren't for the stores of grains and dried meats he kept in his cellar, why, most of us would have starved during that first year when the entire countryside was overrun by demons."); +const char *TEXT_200 = N_("Can't a fella drink in peace?"); +const char *TEXT_201 = N_("The gal who brings the drinks? Oh, yeah, what a pretty lady. So nice, too."); +const char *TEXT_202 = N_("Why don't that old crone do somethin' for a change. Sure, sure, she's got stuff, but you listen to me... she's unnatural. I ain't never seen her eat or drink - and you can't trust somebody who doesn't drink at least a little."); +const char *TEXT_203 = N_("Cain isn't what he says he is. Sure, sure, he talks a good story... some of 'em are real scary or funny... but I think he knows more than he knows he knows."); +const char *TEXT_204 = N_("Griswold? Good old Griswold. I love him like a brother! We fought together, you know, back when... we... Lazarus... Lazarus... Lazarus!!!"); +const char *TEXT_205 = N_("Hehehe, I like Pepin. He really tries, you know. Listen here, you should make sure you get to know him. Good fella like that with people always wantin' help. Hey, I guess that would be kinda like you, huh hero? I was a hero too..."); +const char *TEXT_206 = N_("Wirt is a kid with more problems than even me, and I know all about problems. Listen here - that kid is gotta sweet deal, but he's been there, you know? Lost a leg! Gotta walk around on a piece of wood. So sad, so sad..."); +const char *TEXT_207 = N_("Ogden is the best man in town. I don't think his wife likes me much, but as long as she keeps tappin' kegs, I'll like her just fine. Seems like I been spendin' more time with Ogden than most, but he's so good to me..."); +const char *TEXT_208 = N_("I wanna tell ya sumthin', 'cause I know all about this stuff. It's my specialty. This here is the best... theeeee best! That other ale ain't no good since those stupid dogs..."); +const char *TEXT_209 = N_("No one ever lis... listens to me. Somewhere - I ain't too sure - but somewhere under the church is a whole pile o' gold. Gleamin' and shinin' and just waitin' for someone to get it."); +const char *TEXT_210 = N_("I know you gots your own ideas, and I know you're not gonna believe this, but that weapon you got there - it just ain't no good against those big brutes! Oh, I don't care what Griswold says, they can't make anything like they used to in the old days..."); +const char *TEXT_211 = N_("If I was you... and I ain't... but if I was, I'd sell all that stuff you got and get out of here. That boy out there... He's always got somethin' good, but you gotta give him some gold or he won't even show you what he's got."); +const char *TEXT_212 = N_("I sense a soul in search of answers..."); +const char *TEXT_213 = N_("Wisdom is earned, not given. If you discover a tome of knowledge, devour its words. Should you already have knowledge of the arcane mysteries scribed within a book, remember - that level of mastery can always increase."); +const char *TEXT_214 = N_("The greatest power is often the shortest lived. You may find ancient words of power written upon scrolls of parchment. The strength of these scrolls lies in the ability of either apprentice or adept to cast them with equal ability. Their weakness is that they must first be read aloud and can never be kept at the ready in your mind. Know also that these scrolls can be read but once, so use them with care."); +const char *TEXT_215 = N_("Though the heat of the sun is beyond measure, the mere flame of a candle is of greater danger. No energies, no matter how great, can be used without the proper focus. For many spells, ensorcelled Staves may be charged with magical energies many times over. I have the ability to restore their power - but know that nothing is done without a price."); +const char *TEXT_216 = N_("The sum of our knowledge is in the sum of its people. Should you find a book or scroll that you cannot decipher, do not hesitate to bring it to me. If I can make sense of it I will share what I find."); +const char *TEXT_217 = N_("To a man who only knows Iron, there is no greater magic than Steel. The blacksmith Griswold is more of a sorcerer than he knows. His ability to meld fire and metal is unequaled in this land."); +const char *TEXT_218 = N_("Corruption has the strength of deceit, but innocence holds the power of purity. The young woman Gillian has a pure heart, placing the needs of her matriarch over her own. She fears me, but it is only because she does not understand me."); +const char *TEXT_219 = N_("A chest opened in darkness holds no greater treasure than when it is opened in the light. The storyteller Cain is an enigma, but only to those who do not look. His knowledge of what lies beneath the cathedral is far greater than even he allows himself to realize."); +const char *TEXT_220 = N_("The higher you place your faith in one man, the farther it has to fall. Farnham has lost his soul, but not to any demon. It was lost when he saw his fellow townspeople betrayed by the Archbishop Lazarus. He has knowledge to be gleaned, but you must separate fact from fantasy."); +const char *TEXT_221 = N_("The hand, the heart and the mind can perform miracles when they are in perfect harmony. The healer Pepin sees into the body in a way that even I cannot. His ability to restore the sick and injured is magnified by his understanding of the creation of elixirs and potions. He is as great an ally as you have in Tristram."); +const char *TEXT_222 = N_("There is much about the future we cannot see, but when it comes it will be the children who wield it. The boy Wirt has a blackness upon his soul, but he poses no threat to the town or its people. His secretive dealings with the urchins and unspoken guilds of nearby towns gain him access to many devices that cannot be easily found in Tristram. While his methods may be reproachful, Wirt can provide assistance for your battle against the encroaching Darkness."); +const char *TEXT_223 = N_("Earthen walls and thatched canopy do not a home create. The innkeeper Ogden serves more of a purpose in this town than many understand. He provides shelter for Gillian and her matriarch, maintains what life Farnham has left to him, and provides an anchor for all who are left in the town to what Tristram once was. His tavern, and the simple pleasures that can still be found there, provide a glimpse of a life that the people here remember. It is that memory that continues to feed their hopes for your success."); +const char *TEXT_224 = N_("Pssst... over here..."); +const char *TEXT_225 = N_("Not everyone in Tristram has a use - or a market - for everything you will find in the labyrinth. Not even me, as hard as that is to believe. \n \nSometimes, only you will be able to find a purpose for some things."); +const char *TEXT_226 = N_("Don't trust everything the drunk says. Too many ales have fogged his vision and his good sense."); +const char *TEXT_227 = N_("In case you haven't noticed, I don't buy anything from Tristram. I am an importer of quality goods. If you want to peddle junk, you'll have to see Griswold, Pepin or that witch, Adria. I'm sure that they will snap up whatever you can bring them..."); +const char *TEXT_228 = N_("I guess I owe the blacksmith my life - what there is of it. Sure, Griswold offered me an apprenticeship at the smithy, and he is a nice enough guy, but I'll never get enough money to... well, let's just say that I have definite plans that require a large amount of gold."); +const char *TEXT_229 = N_("If I were a few years older, I would shower her with whatever riches I could muster, and let me assure you I can get my hands on some very nice stuff. Gillian is a beautiful girl who should get out of Tristram as soon as it is safe. Hmmm... maybe I'll take her with me when I go..."); +const char *TEXT_230 = N_("Cain knows too much. He scares the life out of me - even more than that woman across the river. He keeps telling me about how lucky I am to be alive, and how my story is foretold in legend. I think he's off his crock."); +const char *TEXT_231 = N_("Farnham - now there is a man with serious problems, and I know all about how serious problems can be. He trusted too much in the integrity of one man, and Lazarus led him into the very jaws of death. Oh, I know what it's like down there, so don't even start telling me about your plans to destroy the evil that dwells in that Labyrinth. Just watch your legs..."); +const char *TEXT_232 = N_("As long as you don't need anything reattached, old Pepin is as good as they come. \n \nIf I'd have had some of those potions he brews, I might still have my leg..."); +const char *TEXT_233 = N_("Adria truly bothers me. Sure, Cain is creepy in what he can tell you about the past, but that witch can see into your past. She always has some way to get whatever she needs, too. Adria gets her hands on more merchandise than I've seen pass through the gates of the King's Bazaar during High Festival."); +const char *TEXT_234 = N_("Ogden is a fool for staying here. I could get him out of town for a very reasonable price, but he insists on trying to make a go of it with that stupid tavern. I guess at the least he gives Gillian a place to work, and his wife Garda does make a superb Shepherd's pie..."); +const char *TEXT_235 = N_("Beyond the Hall of Heroes lies the Chamber of Bone. Eternal death awaits any who would seek to steal the treasures secured within this room. So speaks the Lord of Terror, and so it is written."); +const char *TEXT_236 = N_("...and so, locked beyond the Gateway of Blood and past the Hall of Fire, Valor awaits for the Hero of Light to awaken..."); +const char *TEXT_237 = N_("I can see what you see not.\nVision milky then eyes rot.\nWhen you turn they will be gone,\nWhispering their hidden song.\nThen you see what cannot be,\nShadows move where light should be.\nOut of darkness, out of mind,\nCast down into the Halls of the Blind."); +const char *TEXT_238 = N_("The armories of Hell are home to the Warlord of Blood. In his wake lay the mutilated bodies of thousands. Angels and men alike have been cut down to fulfill his endless sacrifices to the Dark ones who scream for one thing - blood."); +const char *TEXT_249 = N_("Take heed and bear witness to the truths that lie herein, for they are the last legacy of the Horadrim. There is a war that rages on even now, beyond the fields that we know - between the utopian kingdoms of the High Heavens and the chaotic pits of the Burning Hells. This war is known as the Great Conflict, and it has raged and burned longer than any of the stars in the sky. Neither side ever gains sway for long as the forces of Light and Darkness constantly vie for control over all creation."); +const char *TEXT_250 = N_("Take heed and bear witness to the truths that lie herein, for they are the last legacy of the Horadrim. When the Eternal Conflict between the High Heavens and the Burning Hells falls upon mortal soil, it is called the Sin War. Angels and Demons walk amongst humanity in disguise, fighting in secret, away from the prying eyes of mortals. Some daring, powerful mortals have even allied themselves with either side, and helped to dictate the course of the Sin War."); +const char *TEXT_251 = N_("Take heed and bear witness to the truths that lie herein, for they are the last legacy of the Horadrim. Nearly three hundred years ago, it came to be known that the Three Prime Evils of the Burning Hells had mysteriously come to our world. The Three Brothers ravaged the lands of the east for decades, while humanity was left trembling in their wake. Our Order - the Horadrim - was founded by a group of secretive magi to hunt down and capture the Three Evils once and for all.\n \nThe original Horadrim captured two of the Three within powerful artifacts known as Soulstones and buried them deep beneath the desolate eastern sands. The third Evil escaped capture and fled to the west with many of the Horadrim in pursuit. The Third Evil - known as Diablo, the Lord of Terror - was eventually captured, his essence set in a Soulstone and buried within this Labyrinth.\n \nBe warned that the soulstone must be kept from discovery by those not of the faith. If Diablo were to be released, he would seek a body that is easily controlled as he would be very weak - perhaps that of an old man or a child."); +const char *TEXT_252 = N_("So it came to be that there was a great revolution within the Burning Hells known as The Dark Exile. The Lesser Evils overthrew the Three Prime Evils and banished their spirit forms to the mortal realm. The demons Belial (the Lord of Lies) and Azmodan (the Lord of Sin) fought to claim rulership of Hell during the absence of the Three Brothers. All of Hell polarized between the factions of Belial and Azmodan while the forces of the High Heavens continually battered upon the very Gates of Hell."); +const char *TEXT_253 = N_("Many demons traveled to the mortal realm in search of the Three Brothers. These demons were followed to the mortal plane by Angels who hunted them throughout the vast cities of the East. The Angels allied themselves with a secretive Order of mortal magi named the Horadrim, who quickly became adept at hunting demons. They also made many dark enemies in the underworlds."); +const char *TEXT_254 = N_("So it came to be that the Three Prime Evils were banished in spirit form to the mortal realm and after sewing chaos across the East for decades, they were hunted down by the cursed Order of the mortal Horadrim. The Horadrim used artifacts called Soulstones to contain the essence of Mephisto, the Lord of Hatred and his brother Baal, the Lord of Destruction. The youngest brother - Diablo, the Lord of Terror - escaped to the west.\n \nEventually the Horadrim captured Diablo within a Soulstone as well, and buried him under an ancient, forgotten Cathedral. There, the Lord of Terror sleeps and awaits the time of his rebirth. Know ye that he will seek a body of youth and power to possess - one that is innocent and easily controlled. He will then arise to free his Brothers and once more fan the flames of the Sin War..."); +const char *TEXT_255 = N_("All praises to Diablo - Lord of Terror and Survivor of The Dark Exile. When he awakened from his long slumber, my Lord and Master spoke to me of secrets that few mortals know. He told me the kingdoms of the High Heavens and the pits of the Burning Hells engage in an eternal war. He revealed the powers that have brought this discord to the realms of man. My lord has named the battle for this world and all who exist here the Sin War."); +const char *TEXT_256 = N_("Glory and Approbation to Diablo - Lord of Terror and Leader of the Three. My Lord spoke to me of his two Brothers, Mephisto and Baal, who were banished to this world long ago. My Lord wishes to bide his time and harness his awesome power so that he may free his captive brothers from their tombs beneath the sands of the east. Once my Lord releases his Brothers, the Sin War will once again know the fury of the Three."); +const char *TEXT_257 = N_("Hail and Sacrifice to Diablo - Lord of Terror and Destroyer of Souls. When I awoke my Master from his sleep, he attempted to possess a mortal's form. Diablo attempted to claim the body of King Leoric, but my Master was too weak from his imprisonment. My Lord required a simple and innocent anchor to this world, and so found the boy Albrecht to be perfect for the task. While the good King Leoric was left maddened by Diablo's unsuccessful possession, I kidnapped his son Albrecht and brought him before my Master. I now await Diablo's call and pray that I will be rewarded when he at last emerges as the Lord of this world."); +const char *TEXT_258 = N_("Thank goodness you've returned!\nMuch has changed since you lived here, my friend. All was peaceful until the dark riders came and destroyed our village. Many were cut down where they stood, and those who took up arms were slain or dragged away to become slaves - or worse. The church at the edge of town has been desecrated and is being used for dark rituals. The screams that echo in the night are inhuman, but some of our townsfolk may yet survive. Follow the path that lies between my tavern and the blacksmith shop to find the church and save who you can. \n \nPerhaps I can tell you more if we speak again. Good luck."); +const char *TEXT_267 = N_("Maintain your quest. Finding a treasure that is lost is not easy. Finding a treasure that is hidden less so. I will leave you with this. Do not let the sands of time confuse your search."); +const char *TEXT_268 = N_("A what?! This is foolishness. There's no treasure buried here in Tristram. Let me see that!! Ah, Look these drawings are inaccurate. They don't match our town at all. I'd keep my mind on what lies below the cathedral and not what lies below our topsoil."); +const char *TEXT_269 = N_("I really don't have time to discuss some map you are looking for. I have many sick people that require my help and yours as well."); +const char *TEXT_270 = N_("The once proud Iswall is trapped deep beneath the surface of this world. His honor stripped and his visage altered. He is trapped in immortal torment. Charged to conceal the very thing that could free him."); +const char *TEXT_271 = N_("I'll bet that Wirt saw you coming and put on an act just so he could laugh at you later when you were running around the town with your nose in the dirt. I'd ignore it."); +const char *TEXT_272 = N_("There was a time when this town was a frequent stop for travelers from far and wide. Much has changed since then. But hidden caves and buried treasure are common fantasies of any child. Wirt seldom indulges in youthful games. So it may just be his imagination."); +const char *TEXT_273 = N_("Listen here. Come close. I don't know if you know what I know, but you've have really got something here. That's a map."); +const char *TEXT_274 = N_("My grandmother often tells me stories about the strange forces that inhabit the graveyard outside of the church. And it may well interest you to hear one of them. She said that if you were to leave the proper offering in the cemetery, enter the cathedral to pray for the dead, and then return, the offering would be altered in some strange way. I don't know if this is just the talk of an old sick woman, but anything seems possible these days."); +const char *TEXT_275 = N_("Hmmm. A vast and mysterious treasure you say. Mmmm. Maybe I could be interested in picking up a few things from you. Or better yet, don't you need some rare and expensive supplies to get you through this ordeal?"); +const char *TEXT_277 = N_("So, you're the hero everyone's been talking about. Perhaps you could help a poor, simple farmer out of a terrible mess? At the edge of my orchard, just south of here, there's a horrible thing swelling out of the ground! I can't get to my crops or my bales of hay, and my poor cows will starve. The witch gave this to me and said that it would blast that thing out of my field. If you could destroy it, I would be forever grateful. I'd do it myself, but someone has to stay here with the cows..."); +const char *TEXT_278 = N_("I knew that it couldn't be as simple as that witch made it sound. It's a sad world when you can't even trust your neighbors."); +const char *TEXT_279 = N_("Is it gone? Did you send it back to the dark recesses of Hades that spawned it? You what? Oh, don't tell me you lost it! Those things don't come cheap, you know. You've got to find it, and then blast that horror out of our town."); +const char *TEXT_280 = N_("I heard the explosion from here! Many thanks to you, kind stranger. What with all these things comin' out of the ground, monsters taking over the church, and so forth, these are trying times. I am but a poor farmer, but here -- take this with my great thanks."); +const char *TEXT_281 = N_("Oh, such a trouble I have...maybe...No, I couldn't impose on you, what with all the other troubles. Maybe after you've cleansed the church of some of those creatures you could come back... and spare a little time to help a poor farmer?"); +const char *TEXT_282 = N_("Waaaah! (sniff) Waaaah! (sniff)"); +const char *TEXT_283 = N_("I lost Theo! I lost my best friend! We were playing over by the river, and Theo said he wanted to go look at the big green thing. I said we shouldn't, but we snuck over there, and then suddenly this BUG came out! We ran away but Theo fell down and the bug GRABBED him and took him away!"); +const char *TEXT_284 = N_("Didja find him? You gotta find Theodore, please! He's just little. He can't take care of himself! Please!"); +const char *TEXT_285 = N_("You found him! You found him! Thank you! Oh Theo, did those nasty bugs scare you? Hey! Ugh! There's something stuck to your fur! Ick! Come on, Theo, let's go home! Thanks again, hero person!"); +const char *TEXT_286 = N_("We have long lain dormant, and the time to awaken has come. After our long sleep, we are filled with great hunger. Soon, now, we shall feed..."); +const char *TEXT_287 = N_("Have you been enjoying yourself, little mammal? How pathetic. Your little world will be no challenge at all."); +const char *TEXT_288 = N_("These lands shall be defiled, and our brood shall overrun the fields that men call home. Our tendrils shall envelop this world, and we will feast on the flesh of its denizens. Man shall become our chattel and sustenance."); +const char *TEXT_289 = N_("Ah, I can smell you...you are close! Close! Ssss...the scent of blood and fear...how enticing..."); +const char *TEXT_296 = N_("And in the year of the Golden Light, it was so decreed that a great Cathedral be raised. The cornerstone of this holy place was to be carved from the translucent stone Antyrael, named for the Angel who shared his power with the Horadrim. \n \nIn the Year of Drawing Shadows, the ground shook and the Cathedral shattered and fell. As the building of catacombs and castles began and man stood against the ravages of the Sin War, the ruins were scavenged for their stones. And so it was that the cornerstone vanished from the eyes of man. \n \nThe stone was of this world -- and of all worlds -- as the Light is both within all things and beyond all things. Light and unity are the products of this holy foundation, a unity of purpose and a unity of possession."); +const char *TEXT_297 = N_("Moo."); +const char *TEXT_298 = N_("I said, Moo."); +const char *TEXT_299 = N_("Look I'm just a cow, OK?"); +const char *TEXT_300 = N_("All right, all right. I'm not really a cow. I don't normally go around like this; but, I was sitting at home minding my own business and all of a sudden these bugs & vines & bulbs & stuff started coming out of the floor... it was horrible! If only I had something normal to wear, it wouldn't be so bad. Hey! Could you go back to my place and get my suit for me? The brown one, not the gray one, that's for evening wear. I'd do it myself, but I don't want anyone seeing me like this. Here, take this, you might need it... to kill those things that have overgrown everything. You can't miss my house, it's just south of the fork in the river... you know... the one with the overgrown vegetable garden."); +const char *TEXT_301 = N_("What are you wasting time for? Go get my suit! And hurry! That Holstein over there keeps winking at me!"); +const char *TEXT_302 = N_("Hey, have you got my suit there? Quick, pass it over! These ears itch like you wouldn't believe!"); +const char *TEXT_303 = N_("No no no no! This is my GRAY suit! It's for evening wear! Formal occasions! I can't wear THIS. What are you, some kind of weirdo? I need the BROWN suit."); +const char *TEXT_304 = N_("Ahh, that's MUCH better. Whew! At last, some dignity! Are my antlers on straight? Good. Look, thanks a lot for helping me out. Here, take this as a gift; and, you know... a little fashion tip... you could use a little... you could use a new... yknowwhatImean? The whole adventurer motif is just so... retro. Just a word of advice, eh? Ciao."); +const char *TEXT_305 = N_("Look. I'm a cow. And you, you're monster bait. Get some experience under your belt! We'll talk..."); +const char *TEXT_307 = N_("It must truly be a fearsome task I've set before you. If there was just some way that I could... would a flagon of some nice, fresh milk help?"); +const char *TEXT_308 = N_("Oh, I could use your help, but perhaps after you've saved the catacombs from the desecration of those beasts."); +const char *TEXT_309 = N_("I need something done, but I couldn't impose on a perfect stranger. Perhaps after you've been here a while I might feel more comfortable asking a favor."); +const char *TEXT_310 = N_("I see in you the potential for greatness. Perhaps sometime while you are fulfilling your destiny, you could stop by and do a little favor for me?"); +const char *TEXT_311 = N_("I think you could probably help me, but perhaps after you've gotten a little more powerful. I wouldn't want to injure the village's only chance to destroy the menace in the church!"); +const char *TEXT_312 = N_("Me, I'm a self-made cow. Make something of yourself, and... then we'll talk."); +const char *TEXT_313 = N_("I don't have to explain myself to every tourist that walks by! Don't you have some monsters to kill? Maybe we'll talk later. If you live..."); +const char *TEXT_314 = N_("Quit bugging me. I'm looking for someone really heroic. And you're not it. I can't trust you, you're going to get eaten by monsters any day now... I need someone who's an experienced hero."); +const char *TEXT_315 = N_("All right, I'll cut the bull. I didn't mean to steer you wrong. I was sitting at home, feeling moo-dy, when things got really un-stable; a whole stampede of monsters came out of the floor! I just cowed. I just happened to be wearing this Jersey when I ran out the door, and now I look udderly ridiculous. If only I had something normal to wear, it wouldn't be so bad. Hey! Can you go back to my place and get my suit for me? The brown one, not the gray one, that's for evening wear. I'd do it myself, but I don't want anyone seeing me like this. Here, take this, you might need it... to kill those things that have overgrown everything. You can't miss my house, it's just south of the fork in the river... you know... the one with the overgrown vegetable garden."); +const char *TEXT_317 = N_("I have tried spells, threats, abjuration and bargaining with this foul creature -- to no avail. My methods of enslaving lesser demons seem to have no effect on this fearsome beast."); +const char *TEXT_318 = N_("My home is slowly becoming corrupted by the vileness of this unwanted prisoner. The crypts are full of shadows that move just beyond the corners of my vision. The faint scrabble of claws dances at the edges of my hearing. They are searching, I think, for this journal."); +const char *TEXT_319 = N_("In its ranting, the creature has let slip its name -- Na-Krul. I have attempted to research the name, but the smaller demons have somehow destroyed my library. Na-Krul... The name fills me with a cold dread. I prefer to think of it only as The Creature rather than ponder its true name."); +const char *TEXT_320 = N_("The entrapped creature's howls of fury keep me from gaining much needed sleep. It rages against the one who sent it to the Void, and it calls foul curses upon me for trapping it here. Its words fill my heart with terror, and yet I cannot block out its voice."); +const char *TEXT_321 = N_("My time is quickly running out. I must record the ways to weaken the demon, and then conceal that text, lest his minions find some way to use my knowledge to free their lord. I hope that whoever finds this journal will seek the knowledge."); +const char *TEXT_322 = N_("Whoever finds this scroll is charged with stopping the demonic creature that lies within these walls. My time is over. Even now, its hellish minions claw at the frail door behind which I hide. \n \nI have hobbled the demon with arcane magic and encased it within great walls, but I fear that will not be enough. \n \nThe spells found in my three grimoires will provide you protected entrance to his domain, but only if cast in their proper sequence. The levers at the entryway will remove the barriers and free the demon; touch them not! Use only these spells to gain entry or his power may be too great for you to defeat."); +const char *TEXT_323 = N_("In Spiritu Sanctum."); +const char *TEXT_324 = N_("Praedictum Otium."); +const char *TEXT_325 = N_("Efficio Obitus Ut Inimicus."); +const char *MT_HELLBOAR_NAME = P_("monster", "Hellboar"); +const char *MT_STINGER_NAME = P_("monster", "Stinger"); +const char *MT_PSYCHORB_NAME = P_("monster", "Psychorb"); +const char *MT_ARACHNON_NAME = P_("monster", "Arachnon"); +const char *MT_FELLTWIN_NAME = P_("monster", "Felltwin"); +const char *MT_HORKSPWN_NAME = P_("monster", "Hork Spawn"); +const char *MT_VENMTAIL_NAME = P_("monster", "Venomtail"); +const char *MT_NECRMORB_NAME = P_("monster", "Necromorb"); +const char *MT_SPIDLORD_NAME = P_("monster", "Spider Lord"); +const char *MT_LASHWORM_NAME = P_("monster", "Lashworm"); +const char *MT_TORCHANT_NAME = P_("monster", "Torchant"); +const char *MT_DEFILER_NAME = P_("monster", "Hell Bug"); +const char *MT_GRAVEDIG_NAME = P_("monster", "Gravedigger"); +const char *MT_TOMBRAT_NAME = P_("monster", "Tomb Rat"); +const char *MT_FIREBAT_NAME = P_("monster", "Firebat"); +const char *MT_SKLWING_NAME = P_("monster", "Skullwing"); +const char *MT_LICH_NAME = P_("monster", "Lich"); +const char *MT_CRYPTDMN_NAME = P_("monster", "Crypt Demon"); +const char *MT_HELLBAT_NAME = P_("monster", "Hellbat"); +const char *MT_BONEDEMN_NAME = P_("monster", "Bone Demon"); +const char *MT_ARCHLICH_NAME = P_("monster", "Arch Lich"); +const char *MT_BICLOPS_NAME = P_("monster", "Biclops"); +const char *MT_FLESTHNG_NAME = P_("monster", "Flesh Thing"); +const char *MT_REAPER_NAME = P_("monster", "Reaper"); +const char *UNIQUE_ITEM_90_NAME = N_("Giant's Knuckle"); +const char *UNIQUE_ITEM_91_NAME = N_("Mercurial Ring"); +const char *UNIQUE_ITEM_92_NAME = N_("Xorine's Ring"); +const char *UNIQUE_ITEM_93_NAME = N_("Karik's Ring"); +const char *UNIQUE_ITEM_94_NAME = N_("Ring of Magma"); +const char *UNIQUE_ITEM_95_NAME = N_("Ring of the Mystics"); +const char *UNIQUE_ITEM_96_NAME = N_("Ring of Thunder"); +const char *UNIQUE_ITEM_97_NAME = N_("Amulet of Warding"); +const char *UNIQUE_ITEM_98_NAME = N_("Gnat Sting"); +const char *UNIQUE_ITEM_99_NAME = N_("Flambeau"); +const char *UNIQUE_ITEM_100_NAME = N_("Armor of Gloom"); +const char *UNIQUE_ITEM_101_NAME = N_("Blitzen"); +const char *UNIQUE_ITEM_102_NAME = N_("Thunderclap"); +const char *UNIQUE_ITEM_103_NAME = N_("Shirotachi"); +const char *UNIQUE_ITEM_104_NAME = N_("Eater of Souls"); +const char *UNIQUE_ITEM_105_NAME = N_("Diamondedge"); +const char *UNIQUE_ITEM_106_NAME = N_("Bone Chain Armor"); +const char *UNIQUE_ITEM_107_NAME = N_("Demon Plate Armor"); +const char *UNIQUE_ITEM_108_NAME = N_("Acolyte's Amulet"); +const char *UNIQUE_ITEM_109_NAME = N_("Gladiator's Ring"); +const char *ITEM_PREFIX_83_NAME = N_("Jester's"); +const char *ITEM_PREFIX_84_NAME = N_("Crystalline"); +const char *ITEM_PREFIX_85_NAME = N_("Doppelganger's"); +const char *ITEM_SUFFIX_95_NAME = N_("devastation"); +const char *ITEM_SUFFIX_96_NAME = N_("decay"); +const char *ITEM_SUFFIX_97_NAME = N_("peril"); +const char *SPELL_MANA_NAME = P_("spell", "Mana"); +const char *SPELL_THE_MAGI_NAME = P_("spell", "the Magi"); +const char *SPELL_THE_JESTER_NAME = P_("spell", "the Jester"); +const char *SPELL_LIGHTNING_WALL_NAME = P_("spell", "Lightning Wall"); +const char *SPELL_IMMOLATION_NAME = P_("spell", "Immolation"); +const char *SPELL_WARP_NAME = P_("spell", "Warp"); +const char *SPELL_REFLECT_NAME = P_("spell", "Reflect"); +const char *SPELL_BERSERK_NAME = P_("spell", "Berserk"); +const char *SPELL_RING_OF_FIRE_NAME = P_("spell", "Ring of Fire"); +const char *SPELL_SEARCH_NAME = P_("spell", "Search"); +const char *SPELL_RUNE_OF_FIRE_NAME = P_("spell", "Rune of Fire"); +const char *SPELL_RUNE_OF_LIGHT_NAME = P_("spell", "Rune of Light"); +const char *SPELL_RUNE_OF_NOVA_NAME = P_("spell", "Rune of Nova"); +const char *SPELL_RUNE_OF_IMMOLATION_NAME = P_("spell", "Rune of Immolation"); +const char *SPELL_RUNE_OF_STONE_NAME = P_("spell", "Rune of Stone"); + +} // namespace diff --git a/Source/utils/display.cpp b/Source/utils/display.cpp index 8a802244f..80d0aad11 100644 --- a/Source/utils/display.cpp +++ b/Source/utils/display.cpp @@ -35,7 +35,7 @@ #include "DiabloUI/diabloui.h" #include "config.h" -#include "control.h" +#include "control/control.hpp" #include "controls/controller.h" #ifndef USE_SDL1 #include "controls/devices/game_controller.h" @@ -830,7 +830,8 @@ void SetFullscreenMode() #if USE_SDL3 if (!SDL_SetWindowFullscreen(ghMainWnd, *GetOptions().Graphics.fullscreen)) ErrSdl(); #else - if (SDL_SetWindowFullscreen(ghMainWnd, *GetOptions().Graphics.upscale ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN) != 0) ErrSdl(); + const Uint32 flags = *GetOptions().Graphics.upscale ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN; + if (SDL_SetWindowFullscreen(ghMainWnd, *GetOptions().Graphics.fullscreen ? flags : 0) != 0) ErrSdl(); #endif if (!*GetOptions().Graphics.fullscreen) { diff --git a/Source/utils/hp_mana_units.hpp b/Source/utils/hp_mana_units.hpp new file mode 100644 index 000000000..d20ed8af2 --- /dev/null +++ b/Source/utils/hp_mana_units.hpp @@ -0,0 +1,23 @@ +#pragma once + +namespace devilution { + +constexpr int HpManaFracBits = 6; +constexpr int HpManaScale = 1 << HpManaFracBits; + +constexpr int HpManaToFrac(int whole) +{ + return whole * HpManaScale; +} + +constexpr int HpManaToWhole(int frac) +{ + return frac / HpManaScale; +} + +constexpr int HpManaFromParts(int whole, int frac) +{ + return HpManaToFrac(whole) + frac; +} + +} // namespace devilution diff --git a/Source/utils/language.cpp b/Source/utils/language.cpp index f8f5d55ba..79e044182 100644 --- a/Source/utils/language.cpp +++ b/Source/utils/language.cpp @@ -157,7 +157,7 @@ void SetPluralForm(std::string_view expression) return; } - // hr, ru + // be, hr, ru if (expression == "(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2)") { GetLocalPluralId = [](int n) -> int { if (n % 10 == 1 && n % 100 != 11) diff --git a/Source/utils/sdl_thread.cpp b/Source/utils/sdl_thread.cpp index 0279f23dc..8c96f2189 100644 --- a/Source/utils/sdl_thread.cpp +++ b/Source/utils/sdl_thread.cpp @@ -1,22 +1,22 @@ -#include "utils/sdl_thread.h" - -namespace devilution { - -#ifndef __DJGPP__ -int SDLCALL SdlThread::ThreadTranslate(void *ptr) -{ - auto handler = (void (*)())ptr; - - handler(); - - return 0; -} - -void SdlThread::ThreadDeleter(SDL_Thread *thread) -{ - if (thread != nullptr) - app_fatal("Joinable thread destroyed"); -} -#endif - -} // namespace devilution +#include "utils/sdl_thread.h" + +namespace devilution { + +#ifndef __DJGPP__ +int SDLCALL SdlThread::ThreadTranslate(void *ptr) +{ + auto handler = (void (*)())ptr; + + handler(); + + return 0; +} + +void SdlThread::ThreadDeleter(SDL_Thread *thread) +{ + if (thread != nullptr) + app_fatal("Joinable thread destroyed"); +} +#endif + +} // namespace devilution diff --git a/Translations/be.po b/Translations/be.po new file mode 100644 index 000000000..bd6a7c40d --- /dev/null +++ b/Translations/be.po @@ -0,0 +1,11444 @@ + +msgid "" +msgstr "" +"Project-Id-Version: DevilutionX\n" +"POT-Creation-Date: 2022-04-11 10:56+0100\n" +"PO-Revision-Date: \n" +"Last-Translator: Данек \n" +"Language-Team: \n" +"Language: be_BY\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" +"X-Generator: Poedit 3.4.1\n" +"X-Poedit-SourceCharset: UTF-8\n" +"X-Poedit-KeywordsList: _;N_;P_:1c,2\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-SearchPath-0: Source\n" + +#: Source/DiabloUI/credits_lines.cpp:7 +msgid "Game Design" +msgstr "Дызайн гульні" + +#: Source/DiabloUI/credits_lines.cpp:10 +msgid "Senior Designers" +msgstr "Старшыя дызайнеры" + +#: Source/DiabloUI/credits_lines.cpp:13 Source/DiabloUI/credits_lines.cpp:232 +msgid "Additional Design" +msgstr "Дадатковы дызайн" + +#: Source/DiabloUI/credits_lines.cpp:16 Source/DiabloUI/credits_lines.cpp:215 +msgid "Lead Programmer" +msgstr "Вядучы праграміст" + +#: Source/DiabloUI/credits_lines.cpp:19 +msgid "Senior Programmers" +msgstr "Старшыя праграмісты" + +#: Source/DiabloUI/credits_lines.cpp:23 +msgid "Programming" +msgstr "Праграмаванне" + +#: Source/DiabloUI/credits_lines.cpp:26 +msgid "Special Guest Programmers" +msgstr "Спецыяльна запрошаныя праграмісты" + +#: Source/DiabloUI/credits_lines.cpp:29 +msgid "Battle.net Programming" +msgstr "Праграмаванне Battle.net" + +#: Source/DiabloUI/credits_lines.cpp:32 +msgid "Serial Communications Programming" +msgstr "Прагамаванне паслядоўнай передачы дадзеных" + +#: Source/DiabloUI/credits_lines.cpp:35 +msgid "Installer Programming" +msgstr "Праграмаванне ўсталёўшчыка" + +#: Source/DiabloUI/credits_lines.cpp:38 +msgid "Art Directors" +msgstr "Арт-дырэктары" + +#: Source/DiabloUI/credits_lines.cpp:41 +msgid "Artwork" +msgstr "Афармленне" + +#: Source/DiabloUI/credits_lines.cpp:48 +msgid "Technical Artwork" +msgstr "Тэхнічнае афармленне" + +#: Source/DiabloUI/credits_lines.cpp:52 +msgid "Cinematic Art Directors" +msgstr "Арт-дырэктары катсцэн" + +#: Source/DiabloUI/credits_lines.cpp:55 +msgid "3D Cinematic Artwork" +msgstr "Афармленне 3D катсцэн" + +#: Source/DiabloUI/credits_lines.cpp:61 +msgid "Cinematic Technical Artwork" +msgstr "Тэчнічнае афармленне катсцэн" + +#: Source/DiabloUI/credits_lines.cpp:64 +msgid "Executive Producer" +msgstr "Выканаўчы прадзюсар" + +#: Source/DiabloUI/credits_lines.cpp:67 +msgid "Producer" +msgstr "Прадзюсар" + +#: Source/DiabloUI/credits_lines.cpp:70 +msgid "Associate Producer" +msgstr "Памочнік прадзюсара" + +#. TRANSLATORS: Keep Strike Team as Name +#: Source/DiabloUI/credits_lines.cpp:73 +msgid "Diablo Strike Team" +msgstr "Каманда Diablo Strike" + +#: Source/DiabloUI/credits_lines.cpp:77 Source/gamemenu.cpp:67 +msgid "Music" +msgstr "Музыка" + +#: Source/DiabloUI/credits_lines.cpp:80 +msgid "Sound Design" +msgstr "Гукавы дызайн" + +#: Source/DiabloUI/credits_lines.cpp:83 +msgid "Cinematic Music & Sound" +msgstr "Музыка і гук у катсцэнах" + +#: Source/DiabloUI/credits_lines.cpp:86 +msgid "Voice Production, Direction & Casting" +msgstr "Агучка, рэжысура, пробы" + +#: Source/DiabloUI/credits_lines.cpp:89 +msgid "Script & Story" +msgstr "Сцэнарый і сюжэт" + +#: Source/DiabloUI/credits_lines.cpp:93 +msgid "Voice Editing" +msgstr "Апрацоўка голаса" + +#: Source/DiabloUI/credits_lines.cpp:96 Source/DiabloUI/credits_lines.cpp:250 +msgid "Voices" +msgstr "Галасы" + +#: Source/DiabloUI/credits_lines.cpp:101 +msgid "Recording Engineer" +msgstr "Інжынер гуказапісу" + +#: Source/DiabloUI/credits_lines.cpp:104 +msgid "Manual Design & Layout" +msgstr "Дызайн дапаможніка і макета" + +#: Source/DiabloUI/credits_lines.cpp:108 +msgid "Manual Artwork" +msgstr "Афармленне дапаможніка" + +#: Source/DiabloUI/credits_lines.cpp:112 +msgid "Provisional Director of QA (Lead Tester)" +msgstr "Часовы рэжысёр па праверцы якасці (Вядучы тэсціроўшчык)" + +#: Source/DiabloUI/credits_lines.cpp:115 +msgid "QA Assault Team (Testers)" +msgstr "Штурмавая каманда па праверцы якасці (Тэсціроўшчыкі)" + +#: Source/DiabloUI/credits_lines.cpp:120 +msgid "QA Special Ops Team (Compatibility Testers)" +msgstr "Спецпадраздзяленне па праверцы якасці (Тэсціроўшчыкі сумяшчальнасці)" + +#: Source/DiabloUI/credits_lines.cpp:123 +msgid "QA Artillery Support (Additional Testers) " +msgstr "Артылерыйская падтрымка па праверцы якасці (Дадатковыя тэсціроўшчыкі) " + +#: Source/DiabloUI/credits_lines.cpp:127 +msgid "QA Counterintelligence" +msgstr "Каманда па праверцы якасці (Контрразведка)" + +#. TRANSLATORS: A group of people +#: Source/DiabloUI/credits_lines.cpp:130 +msgid "Order of Network Information Services" +msgstr "Парадак Інфармацыйнай службы сеткі" + +#: Source/DiabloUI/credits_lines.cpp:134 +msgid "Customer Support" +msgstr "Служба падтрымкі" + +#: Source/DiabloUI/credits_lines.cpp:139 +msgid "Sales" +msgstr "Продажы" + +#: Source/DiabloUI/credits_lines.cpp:142 +msgid "Dunsel" +msgstr "Бескарысны" + +#: Source/DiabloUI/credits_lines.cpp:145 +msgid "Mr. Dabiri's Background Vocalists" +msgstr "Бэквакалісты містэра Дэбіры" + +#: Source/DiabloUI/credits_lines.cpp:149 +msgid "Public Relations" +msgstr "Сувязі з грамадствам" + +#: Source/DiabloUI/credits_lines.cpp:152 +msgid "Marketing" +msgstr "Маркетынг" + +#: Source/DiabloUI/credits_lines.cpp:155 +msgid "International Sales" +msgstr "Міжнародныя продажы" + +#: Source/DiabloUI/credits_lines.cpp:158 +msgid "U.S. Sales" +msgstr "Продажы ў ЗША" + +#: Source/DiabloUI/credits_lines.cpp:161 +msgid "Manufacturing" +msgstr "Вытворчасць" + +#: Source/DiabloUI/credits_lines.cpp:164 +msgid "Legal & Business" +msgstr "Юрыспрудэнцыя і бізнес" + +#: Source/DiabloUI/credits_lines.cpp:167 +msgid "Special Thanks To" +msgstr "Асаблівая падзяка" + +#: Source/DiabloUI/credits_lines.cpp:171 +msgid "Thanks To" +msgstr "Дзякуючы" + +#: Source/DiabloUI/credits_lines.cpp:200 +msgid "In memory of" +msgstr "У памяць" + +#: Source/DiabloUI/credits_lines.cpp:206 +msgid "Very Special Thanks to" +msgstr "Вельмі асаблівая падзяка" + +#: Source/DiabloUI/credits_lines.cpp:212 +msgid "General Manager" +msgstr "Галоўны менеджар" + +#: Source/DiabloUI/credits_lines.cpp:218 +msgid "Software Engineering" +msgstr "Распрацоўка праграмнага забеспячэння" + +#: Source/DiabloUI/credits_lines.cpp:221 +msgid "Art Director" +msgstr "Арт-дырэктар" + +#: Source/DiabloUI/credits_lines.cpp:224 +msgid "Artists" +msgstr "Мастакі" + +#: Source/DiabloUI/credits_lines.cpp:228 +msgid "Design" +msgstr "Дызайн" + +#: Source/DiabloUI/credits_lines.cpp:235 +msgid "Sound Design, SFX & Audio Engineering" +msgstr "Гукавы дызайн, SFX і гукарэжысура" + +#: Source/DiabloUI/credits_lines.cpp:238 +msgid "Quality Assurance Lead" +msgstr "Вядучы па праверцы якасці" + +#: Source/DiabloUI/credits_lines.cpp:241 +msgid "Testers" +msgstr "Тэсціроўшчыкі" + +#: Source/DiabloUI/credits_lines.cpp:246 +msgid "Manual" +msgstr "Дапаможнік" + +#: Source/DiabloUI/credits_lines.cpp:255 +msgid "\tAdditional Work" +msgstr " Дадатковая праца" + +#: Source/DiabloUI/credits_lines.cpp:257 +msgid "Quest Text Writing" +msgstr "Напісанне тэксту заданняў" + +#: Source/DiabloUI/credits_lines.cpp:260 Source/DiabloUI/credits_lines.cpp:295 +msgid "Thanks to" +msgstr "Дзякуючы" + +#: Source/DiabloUI/credits_lines.cpp:265 +msgid "\t\t\tSpecial Thanks to Blizzard Entertainment" +msgstr "\t\t\tАсаблівая падзяка Blizzard Entertainment" + +#: Source/DiabloUI/credits_lines.cpp:270 +msgid "\t\t\tSierra On-Line Inc. Northwest" +msgstr " Sierra On-Line Inc. Northwest" + +#: Source/DiabloUI/credits_lines.cpp:272 +msgid "Quality Assurance Manager" +msgstr "Кіраўнік праверкі якасці" + +#: Source/DiabloUI/credits_lines.cpp:275 +msgid "Quality Assurance Lead Tester" +msgstr "Вядучы тэсціроўшчык праверкі якасці" + +#: Source/DiabloUI/credits_lines.cpp:278 +msgid "Main Testers" +msgstr "Галоўныя тэсціроўшчыкі" + +#: Source/DiabloUI/credits_lines.cpp:281 +msgid "Additional Testers" +msgstr "Дадатковыя тэсціроўшчыкі" + +#: Source/DiabloUI/credits_lines.cpp:286 +msgid "Product Marketing Manager" +msgstr "Кіраўнік маркетынгу прадукту" + +#: Source/DiabloUI/credits_lines.cpp:289 +msgid "Public Relations Manager" +msgstr "Кіраўнік сувязі з грамадствам" + +#: Source/DiabloUI/credits_lines.cpp:292 +msgid "Associate Product Manager" +msgstr "Часовы менеджар прадукту" + +#: Source/DiabloUI/credits_lines.cpp:301 +msgid "The Ring of One Thousand" +msgstr "Пярсцёнак Тысячы" + +#: Source/DiabloUI/credits_lines.cpp:547 +msgid "\tNo souls were sold in the making of this game." +msgstr "\tПры стварэнні гульні ніводнай душы не было прададзена." + +#: Source/DiabloUI/dialogs.cpp:196 Source/DiabloUI/dialogs.cpp:208 +#: Source/DiabloUI/selconn.cpp:80 Source/DiabloUI/selgame.cpp:168 +#: Source/DiabloUI/selgame.cpp:306 Source/DiabloUI/selgame.cpp:332 +#: Source/DiabloUI/selgame.cpp:472 Source/DiabloUI/selgame.cpp:547 +#: Source/DiabloUI/selhero.cpp:154 Source/DiabloUI/selhero.cpp:179 +#: Source/DiabloUI/selhero.cpp:249 Source/DiabloUI/selhero.cpp:493 +#: Source/DiabloUI/selok.cpp:69 +msgid "OK" +msgstr "ОК" + +#: Source/DiabloUI/mainmenu.cpp:38 +msgid "Single Player" +msgstr "Адзіночная гульня" + +#: Source/DiabloUI/mainmenu.cpp:39 +msgid "Multi Player" +msgstr "Шматкарыстальніцкая гульня" + +#: Source/DiabloUI/mainmenu.cpp:40 Source/DiabloUI/settingsmenu.cpp:259 +msgid "Settings" +msgstr "Налады" + +#: Source/DiabloUI/mainmenu.cpp:41 +msgid "Support" +msgstr "Падтрымка" + +#: Source/DiabloUI/mainmenu.cpp:42 +msgid "Show Credits" +msgstr "Паказаць удзельнікаў" + +#: Source/DiabloUI/mainmenu.cpp:44 +msgid "Exit Hellfire" +msgstr "Выйсці з Hellfire" + +#: Source/DiabloUI/mainmenu.cpp:44 +msgid "Exit Diablo" +msgstr "Выйсці з Diablo" + +#: Source/DiabloUI/mainmenu.cpp:60 +msgid "Shareware" +msgstr "Дэма" + +#: Source/DiabloUI/progress.cpp:37 Source/DiabloUI/selconn.cpp:83 +#: Source/DiabloUI/selhero.cpp:157 Source/DiabloUI/selhero.cpp:182 +#: Source/DiabloUI/selhero.cpp:252 Source/DiabloUI/selhero.cpp:501 +msgid "Cancel" +msgstr "Адмяніць" + +#: Source/DiabloUI/selconn.cpp:14 +msgid "Client-Server (TCP)" +msgstr "Кліент-сервер (TCP)" + +#: Source/DiabloUI/selconn.cpp:15 +msgid "Loopback" +msgstr "Loopback" + +#: Source/DiabloUI/selconn.cpp:54 Source/DiabloUI/selgame.cpp:604 +#: Source/DiabloUI/selgame.cpp:625 +msgid "Multi Player Game" +msgstr "Шматкарыстальніцкая гульня" + +#: Source/DiabloUI/selconn.cpp:60 +msgid "Requirements:" +msgstr "Патрабаванні:" + +#: Source/DiabloUI/selconn.cpp:66 +msgid "no gateway needed" +msgstr "шлюз не патрэбен" + +#: Source/DiabloUI/selconn.cpp:72 +msgid "Select Connection" +msgstr "Выбраць злучэнне" + +#: Source/DiabloUI/selconn.cpp:75 +msgid "Change Gateway" +msgstr "Змяніць шлюз" + +#: Source/DiabloUI/selconn.cpp:108 +msgid "All computers must be connected to a TCP-compatible network." +msgstr "Усе камп'ютары павінны быць падключанымі да сеткі, сумяшчальнай з TCP." + +#: Source/DiabloUI/selconn.cpp:112 +msgid "All computers must be connected to the internet." +msgstr "Усе камп'ютары павінны быць падключанымі да Інтэрнэта." + +#: Source/DiabloUI/selconn.cpp:116 +msgid "Play by yourself with no network exposure." +msgstr "Гуляць самому без кантакта з сеткаю." + +#: Source/DiabloUI/selconn.cpp:121 +msgid "Players Supported: {:d}" +msgstr "Гульцоў падтрымліваецца: {:d}" + +#: Source/DiabloUI/selgame.cpp:83 Source/options.cpp:542 Source/options.cpp:581 +#: Source/quests.cpp:49 +msgid "Diablo" +msgstr "Diablo" + +#: Source/DiabloUI/selgame.cpp:86 +msgid "Diablo Shareware" +msgstr "Дэма-версія Diablo" + +#: Source/DiabloUI/selgame.cpp:89 Source/options.cpp:544 Source/options.cpp:595 +msgid "Hellfire" +msgstr "Hellfire" + +#: Source/DiabloUI/selgame.cpp:92 +msgid "Hellfire Shareware" +msgstr "Дэма-версія Hellfire" + +#: Source/DiabloUI/selgame.cpp:95 +msgid "The host is running a different game than you." +msgstr "Хост зараз гуляе ў другую гульню." + +#: Source/DiabloUI/selgame.cpp:97 +msgid "The host is running a different game mode ({:s}) than you." +msgstr "Хост зараз гуляе ў другую ({:s}) гульню." + +#. TRANSLATORS: Error message when somebody tries to join a game running another version. +#: Source/DiabloUI/selgame.cpp:99 +msgid "Your version {:s} does not match the host {:d}.{:d}.{:d}." +msgstr "Ваша версія {:s} не падыходзіць да хоста {:d}.{:d}.{:d}." + +#: Source/DiabloUI/selgame.cpp:134 Source/DiabloUI/selgame.cpp:533 +msgid "Description:" +msgstr "Апісанне:" + +#: Source/DiabloUI/selgame.cpp:140 +msgid "Select Action" +msgstr "Выбраць дзеянне" + +#: Source/DiabloUI/selgame.cpp:143 Source/DiabloUI/selgame.cpp:294 +#: Source/DiabloUI/selgame.cpp:453 +msgid "Create Game" +msgstr "Стварыць гульню" + +#: Source/DiabloUI/selgame.cpp:145 +msgid "Create Public Game" +msgstr "Стварыць публічную гульню" + +#: Source/DiabloUI/selgame.cpp:146 +msgid "Join Game" +msgstr "Далучыцца да гульні" + +#: Source/DiabloUI/selgame.cpp:150 +msgid "Public Games" +msgstr "Публічныя гульні" + +#: Source/DiabloUI/selgame.cpp:155 Source/error.cpp:62 +msgid "Loading..." +msgstr "Загрузка..." + +#. TRANSLATORS: type of dungeon (i.e. Cathedral, Caves) +#: Source/DiabloUI/selgame.cpp:157 Source/discord/discord.cpp:71 +#: Source/options.cpp:563 Source/panels/charpanel.cpp:137 +msgid "None" +msgstr "Ніякі" + +#: Source/DiabloUI/selgame.cpp:171 Source/DiabloUI/selgame.cpp:309 +#: Source/DiabloUI/selgame.cpp:335 Source/DiabloUI/selgame.cpp:475 +#: Source/DiabloUI/selgame.cpp:550 +msgid "CANCEL" +msgstr "Адмяніць" + +#: Source/DiabloUI/selgame.cpp:187 +msgid "Create a new game with a difficulty setting of your choice." +msgstr "Стварыць новую гульню са складанасцю на ваш выбар." + +#: Source/DiabloUI/selgame.cpp:190 +msgid "" +"Create a new public game that anyone can join with a difficulty setting of " +"your choice." +msgstr "" +"Стварыць новую публічную гульню, да якой могуць далучыцца са складанасцю на " +"ваш выбар." + +#: Source/DiabloUI/selgame.cpp:194 +msgid "Enter Game ID to join a game already in progress." +msgstr "Напішыце ID гульні, каб далучыцца да ўжо запушчанай гульні." + +#: Source/DiabloUI/selgame.cpp:196 +msgid "Enter an IP or a hostname to join a game already in progress." +msgstr "Напішыце IP ці імя хоста, каб далучыцца да ўжо запушчанай гульні." + +#: Source/DiabloUI/selgame.cpp:201 +msgid "Join the public game already in progress." +msgstr "Далучыцца да ўжо запушчанай гульні." + +#: Source/DiabloUI/selgame.cpp:207 Source/DiabloUI/selgame.cpp:299 +#: Source/DiabloUI/selgame.cpp:360 Source/DiabloUI/selgame.cpp:464 +#: Source/DiabloUI/selgame.cpp:484 Source/automap.cpp:522 +#: Source/discord/discord.cpp:100 +msgid "Normal" +msgstr "Нармальная" + +#: Source/DiabloUI/selgame.cpp:210 Source/DiabloUI/selgame.cpp:300 +#: Source/DiabloUI/selgame.cpp:364 Source/automap.cpp:525 +#: Source/discord/discord.cpp:100 +msgid "Nightmare" +msgstr "Кашмар" + +#: Source/DiabloUI/selgame.cpp:213 Source/DiabloUI/selgame.cpp:301 +#: Source/DiabloUI/selgame.cpp:368 Source/automap.cpp:528 +#: Source/discord/discord.cpp:66 Source/discord/discord.cpp:100 +msgid "Hell" +msgstr "Пекла" + +#. TRANSLATORS: {:s} means: Game Difficulty. +#: Source/DiabloUI/selgame.cpp:216 Source/automap.cpp:532 +msgid "Difficulty: {:s}" +msgstr "Складанасць: {:s}" + +#: Source/DiabloUI/selgame.cpp:220 Source/gamemenu.cpp:161 +msgid "Speed: Normal" +msgstr "Хуткасць: Нармальная" + +#: Source/DiabloUI/selgame.cpp:223 Source/gamemenu.cpp:159 +msgid "Speed: Fast" +msgstr "Хуткасць: Шпарка" + +#: Source/DiabloUI/selgame.cpp:226 Source/gamemenu.cpp:157 +msgid "Speed: Faster" +msgstr "Хуткасць: Шпарчэй" + +#: Source/DiabloUI/selgame.cpp:229 Source/gamemenu.cpp:155 +msgid "Speed: Fastest" +msgstr "Хуткасць: Найшпарчэй" + +#: Source/DiabloUI/selgame.cpp:237 +msgid "Players: " +msgstr "Гульцы: " + +#: Source/DiabloUI/selgame.cpp:297 +msgid "Select Difficulty" +msgstr "Выбраць складанасць" + +#: Source/DiabloUI/selgame.cpp:315 +msgid "Join {:s} Games" +msgstr "Далучыцца да {:s} гульняў" + +#: Source/DiabloUI/selgame.cpp:320 +msgid "Enter Game ID" +msgstr "Напішыце ID гульні" + +#: Source/DiabloUI/selgame.cpp:322 +msgid "Enter address" +msgstr "Напішыце адрас" + +#: Source/DiabloUI/selgame.cpp:361 +msgid "" +"Normal Difficulty\n" +"This is where a starting character should begin the quest to defeat Diablo." +msgstr "" +"Нармальная складанасць\n" +"Пачаткоўцу трэба пачынаць шлях да перамогі над Д'яблам тут." + +#: Source/DiabloUI/selgame.cpp:365 +msgid "" +"Nightmare Difficulty\n" +"The denizens of the Labyrinth have been bolstered and will prove to be a " +"greater challenge. This is recommended for experienced characters only." +msgstr "" +"Складанасць Кашмар\n" +"Жыхары Лабірынту ўзмацніліся, выпрабаванне стане складнейшым. Рэкамендуецца " +"толькі для дасведчаных гульцоў." + +#: Source/DiabloUI/selgame.cpp:369 +msgid "" +"Hell Difficulty\n" +"The most powerful of the underworld's creatures lurk at the gateway into " +"Hell. Only the most experienced characters should venture in this realm." +msgstr "" +"Складанасць Пекла\n" +"Наймацнейшыя стварэнні падземнага свету пільнуюць вас каля варот Пекла. " +"Толькі самыя дасведчаныя адважацца зайсці ў гэтае царства." + +#: Source/DiabloUI/selgame.cpp:384 +msgid "" +"Your character must reach level 20 before you can enter a multiplayer game " +"of Nightmare difficulty." +msgstr "" +"Ваш персанаж павінен быць 20 ўзроўню, каб зайсці ў шматкарыстальніцкую " +"гульню на складанасці Кашмар." + +#: Source/DiabloUI/selgame.cpp:386 +msgid "" +"Your character must reach level 30 before you can enter a multiplayer game " +"of Hell difficulty." +msgstr "" +"Ваш персанаж павінен быць 30 ўзроўню, каб зайсці ў шматкарыстальніцкую " +"гульню на складанасці Пекла." + +#: Source/DiabloUI/selgame.cpp:462 +msgid "Select Game Speed" +msgstr "Выбраць хуткасць гульні" + +#: Source/DiabloUI/selgame.cpp:465 Source/DiabloUI/selgame.cpp:488 +msgid "Fast" +msgstr "Шпаркая" + +#: Source/DiabloUI/selgame.cpp:466 Source/DiabloUI/selgame.cpp:492 +msgid "Faster" +msgstr "Шпарчэйшая" + +#: Source/DiabloUI/selgame.cpp:467 Source/DiabloUI/selgame.cpp:496 +msgid "Fastest" +msgstr "Найшпарчэйшая" + +#: Source/DiabloUI/selgame.cpp:485 +msgid "" +"Normal Speed\n" +"This is where a starting character should begin the quest to defeat Diablo." +msgstr "" +"Нармальная хуткасць\n" +"Пачаткоўцу трэба пачынаць шлях да перамогі над Д'яблам тут." + +#: Source/DiabloUI/selgame.cpp:489 +msgid "" +"Fast Speed\n" +"The denizens of the Labyrinth have been hastened and will prove to be a " +"greater challenge. This is recommended for experienced characters only." +msgstr "" +"Шпаркая хуткасць\n" +"Жыхароў Лабірынту прыспешылі, выпрабаванне стане складнейшым. Рэкамендуецца " +"толькі для дасведчаных гульцоў." + +#: Source/DiabloUI/selgame.cpp:493 +msgid "" +"Faster Speed\n" +"Most monsters of the dungeon will seek you out quicker than ever before. " +"Only an experienced champion should try their luck at this speed." +msgstr "" +"Шпарчэйшая хуткасць\n" +"Большасць пачвар цяпер адшукае вас шпарчэй, чым калі-небудзь раней. Толькі " +"дасведчаны асілак можа пакаштаваць шчасця на такой хуткасці." + +#: Source/DiabloUI/selgame.cpp:497 +msgid "" +"Fastest Speed\n" +"The minions of the underworld will rush to attack without hesitation. Only a " +"true speed demon should enter at this pace." +msgstr "" +"Найшпарчэйшая хутскасць\n" +"Памагатыя падзем'я рынуцца ў бой без вагання. Толькі сапраўды хуткі як чорт " +"можа увайсці такой хадою." + +#: Source/DiabloUI/selgame.cpp:539 Source/DiabloUI/selgame.cpp:544 +msgid "Enter Password" +msgstr "Увесці пароль" + +#: Source/DiabloUI/selhero.cpp:132 +msgid "Choose Class" +msgstr "Выбраць клас" + +#: Source/DiabloUI/selhero.cpp:136 Source/panels/charpanel.cpp:23 +msgid "Warrior" +msgstr "Ваяр" + +#: Source/DiabloUI/selhero.cpp:137 Source/panels/charpanel.cpp:24 +msgid "Rogue" +msgstr "Шэльма" + +#: Source/DiabloUI/selhero.cpp:138 Source/panels/charpanel.cpp:25 +msgid "Sorcerer" +msgstr "Чараўнік" + +#: Source/DiabloUI/selhero.cpp:140 Source/panels/charpanel.cpp:26 +msgid "Monk" +msgstr "Манах" + +#: Source/DiabloUI/selhero.cpp:143 Source/panels/charpanel.cpp:27 +msgid "Bard" +msgstr "Бард" + +#: Source/DiabloUI/selhero.cpp:146 Source/panels/charpanel.cpp:28 +msgid "Barbarian" +msgstr "Варвар" + +#: Source/DiabloUI/selhero.cpp:163 Source/DiabloUI/selhero.cpp:237 +msgid "New Multi Player Hero" +msgstr "Новы герой для Мультыплэера" + +#: Source/DiabloUI/selhero.cpp:163 Source/DiabloUI/selhero.cpp:237 +msgid "New Single Player Hero" +msgstr "Новы адзіночны герой" + +#: Source/DiabloUI/selhero.cpp:171 +msgid "Save File Exists" +msgstr "Захаванне існуе" + +#: Source/DiabloUI/selhero.cpp:174 Source/gamemenu.cpp:38 +msgid "Load Game" +msgstr "Загрузіць гульню" + +#: Source/DiabloUI/selhero.cpp:175 Source/gamemenu.cpp:37 +#: Source/gamemenu.cpp:48 Source/multi.cpp:737 +msgid "New Game" +msgstr "Новая гульня" + +#: Source/DiabloUI/selhero.cpp:185 Source/DiabloUI/selhero.cpp:507 +msgid "Single Player Characters" +msgstr "Персанажы для адзіночнай гульні" + +#: Source/DiabloUI/selhero.cpp:231 +msgid "" +"The Rogue and Sorcerer are only available in the full retail version of " +"Diablo. Visit https://www.gog.com/game/diablo to purchase." +msgstr "" +"Шэльма і Чараўнік даступныя толькі ў поўнай рознічнай версіі Diablo. " +"Наведайце https://www.gog.com/game/diablo, каб набыць." + +#: Source/DiabloUI/selhero.cpp:243 Source/DiabloUI/selhero.cpp:246 +msgid "Enter Name" +msgstr "Увесці імя" + +#: Source/DiabloUI/selhero.cpp:275 +msgid "" +"Invalid name. A name cannot contain spaces, reserved characters, or reserved " +"words.\n" +msgstr "" +"Імя не падыходзіць. У імені не павінна быць прабелаў, зарэзерваваных " +"сімвалаў і слоў.\n" + +#. TRANSLATORS: Error Message +#: Source/DiabloUI/selhero.cpp:282 +msgid "Unable to create character." +msgstr "Немагчыма стварыць персанажа." + +#: Source/DiabloUI/selhero.cpp:436 Source/DiabloUI/selhero.cpp:439 +msgid "Level:" +msgstr "Узровень:" + +#: Source/DiabloUI/selhero.cpp:444 +msgid "Strength:" +msgstr "Сіла:" + +#: Source/DiabloUI/selhero.cpp:449 +msgid "Magic:" +msgstr "Магія:" + +#: Source/DiabloUI/selhero.cpp:454 +msgid "Dexterity:" +msgstr "Спрыт:" + +#: Source/DiabloUI/selhero.cpp:459 +msgid "Vitality:" +msgstr "Жывучасць:" + +#: Source/DiabloUI/selhero.cpp:465 +msgid "Savegame:" +msgstr "Захаванне:" + +#: Source/DiabloUI/selhero.cpp:477 +msgid "Select Hero" +msgstr "Выбраць героя" + +#: Source/DiabloUI/selhero.cpp:485 +msgid "New Hero" +msgstr "Новы герой" + +#: Source/DiabloUI/selhero.cpp:496 +msgid "Delete" +msgstr "Выдаліць" + +#: Source/DiabloUI/selhero.cpp:505 +msgid "Multi Player Characters" +msgstr "Героі для мультыплэера" + +#: Source/DiabloUI/selhero.cpp:555 +msgid "Delete Multi Player Hero" +msgstr "Выдаліць мультыплэернага героя" + +#: Source/DiabloUI/selhero.cpp:557 +msgid "Delete Single Player Hero" +msgstr "Выдаліць адзіночнага героя" + +#: Source/DiabloUI/selhero.cpp:559 +msgid "Are you sure you want to delete the character \"{:s}\"?" +msgstr "Вы ўпэўненныя ў выдаленні персанажа \"{:s}\"?" + +#: Source/DiabloUI/selstart.cpp:43 +msgid "Enter Hellfire" +msgstr "Зайсці ў Hellfire" + +#: Source/DiabloUI/selstart.cpp:44 +msgid "Switch to Diablo" +msgstr "Вярнуцца да Diablo" + +#: Source/DiabloUI/selyesno.cpp:55 Source/stores.cpp:991 +msgid "Yes" +msgstr "Так" + +#: Source/DiabloUI/selyesno.cpp:56 Source/stores.cpp:992 +msgid "No" +msgstr "Не" + +#: Source/DiabloUI/settingsmenu.cpp:304 +msgid "Bound key:" +msgstr "Прывязаная клавіша:" + +#: Source/DiabloUI/settingsmenu.cpp:339 +msgid "Press any key to change." +msgstr "Націснеце любую клавішу каб замяніць." + +#: Source/DiabloUI/settingsmenu.cpp:341 +msgid "Unbind key" +msgstr "Адвязаць клавішу" + +#: Source/DiabloUI/settingsmenu.cpp:347 Source/gamemenu.cpp:61 +msgid "Previous Menu" +msgstr "Папярэдняе меню" + +#: Source/DiabloUI/support_lines.cpp:8 +msgid "" +"We maintain a chat server at Discord.gg/YQKCAYQ Follow the links to join our " +"community where we talk about things related to Diablo, and the Hellfire " +"expansion." +msgstr "" +"У нас ёсць чат у Discord.gg/YQKCAYQ Прайдзіе па спасылках, каб далучыцца да " +"нашай суполкі, дзе мы абмяркоўваем усё звязанае з Diablo і дапаўненнем " +"Hellfire." + +#: Source/DiabloUI/support_lines.cpp:10 +msgid "" +"DevilutionX is maintained by Diasurgical, issues and bugs can be reported at " +"this address: https://github.com/diasurgical/devilutionX To help us better " +"serve you, please be sure to include the version number, operating system, " +"and the nature of the problem." +msgstr "" +"DevilutionX падтрымліваецца Diasurgical, аб праблемах і памылках паведаміць " +"па адрасе https://github.com/diasurgical/devilutionX Каб дапамагчы нам лепей " +"вас абслугоўваць, калі ласка не забывайце дадаваць нумар версіі, аперацыйную " +"сістэму і сутнасць праблемы." + +#: Source/DiabloUI/support_lines.cpp:13 +msgid "Disclaimer:" +msgstr "Дысклеймер:" + +#: Source/DiabloUI/support_lines.cpp:14 +msgid "" +"\tDevilutionX is not supported or maintained by Blizzard Entertainment, nor " +"GOG.com. Neither Blizzard Entertainment nor GOG.com has tested or certified " +"the quality or compatibility of DevilutionX. All inquiries regarding " +"DevilutionX should be directed to Diasurgical, not to Blizzard Entertainment " +"or GOG.com." +msgstr "" +"\tDevilutionX не падтрымліваецца ні Blizzard Entertainment ні GOG.com. Ні " +"Blizzard Entertainment, ні GOG.com не тэсціравалі ці сведчылі якасць ці " +"сумяшчальнасць DevilutionX. Усе запытанні, датычныя DevilutionX, трэба " +"накіроўваць да Diasurgical, не Blizzard Entertainment ці GOG.com." + +#: Source/DiabloUI/support_lines.cpp:17 +msgid "" +"\tThis port makes use of Charis SIL, New Athena Unicode, Unifont, and Noto " +"which are licensed under the SIL Open Font License, as well as Twitmoji " +"which is licensed under CC-BY 4.0. The port also makes use of SDL which is " +"licensed under the zlib-license. See the ReadMe for further details." +msgstr "" +"\tГэты порт карыстаецца Charis SIL, New Athena Unicode, Unifont і Noto, якія " +"ліцэнзаваныя SIL Open Font, гэтак жа сама і Twitmoj ліцэнзавана CC-BY 4.0. " +"Порт таксама карыстаецца SDL, ліцэнзаванай zlib-ліцэнзіяй. Паглядзіце ReadMe " +"для дадатковых дэталяў." + +#: Source/DiabloUI/title.cpp:46 +msgid "Copyright © 1996-2001 Blizzard Entertainment" +msgstr "Copyright © 1996-2001 Blizzard Entertainment" + +#: Source/appfat.cpp:38 +msgid "Error" +msgstr "Памылка" + +#. TRANSLATORS: Error message that displays relevant information for bug report +#: Source/appfat.cpp:100 +msgid "" +"{:s}\n" +"\n" +"The error occurred at: {:s} line {:d}" +msgstr "" +"{:s}\n" +"\n" +"Адбылася памылка на {:s} радку {:d}" + +#: Source/appfat.cpp:109 +msgid "" +"Unable to open main data archive ({:s}).\n" +"\n" +"Make sure that it is in the game folder." +msgstr "" +"Немагчыма адчыніць галоўны архіў ({:s}).\n" +"\n" +"Праверце, ці яна ў папке з гульнёй." + +#: Source/appfat.cpp:114 +msgid "Data File Error" +msgstr "Памылка з файлам" + +#. TRANSLATORS: Error when Program is not allowed to write data +#: Source/appfat.cpp:120 +msgid "" +"Unable to write to location:\n" +"{:s}" +msgstr "" +"Не ўдалося запісаць месцазнаходжанне:\n" +"{:s}" + +#: Source/appfat.cpp:122 +msgid "Read-Only Directory Error" +msgstr "Памылка у Read-Only дапаможніку" + +#: Source/automap.cpp:484 +msgid "Game: " +msgstr "Гульня: " + +#: Source/automap.cpp:492 +msgid "Password: " +msgstr "Пароль: " + +#: Source/automap.cpp:495 +msgid "Public Game" +msgstr "Публічная гульня" + +#: Source/automap.cpp:509 +msgid "Level: Nest {:d}" +msgstr "Узровень: Гняздо {:d}" + +#: Source/automap.cpp:511 +msgid "Level: Crypt {:d}" +msgstr "Узровень: Крыпта {:d}" + +#: Source/automap.cpp:513 +msgid "Level: {:d}" +msgstr "Узровень: {:d}" + +#: Source/control.cpp:155 +msgid "Tab" +msgstr "Tab" + +#: Source/control.cpp:155 +msgid "Esc" +msgstr "Esc" + +#: Source/control.cpp:155 +msgid "Enter" +msgstr "Enter" + +#: Source/control.cpp:158 +msgid "Character Information" +msgstr "Аб персанажы" + +#: Source/control.cpp:159 +msgid "Quests log" +msgstr "Заданні" + +#: Source/control.cpp:160 +msgid "Automap" +msgstr "Аўтамапа" + +#: Source/control.cpp:161 +msgid "Main Menu" +msgstr "Меню" + +#: Source/control.cpp:162 Source/diablo.cpp:1537 +msgid "Inventory" +msgstr "Інвентар" + +#: Source/control.cpp:163 +msgid "Spell book" +msgstr "Кніга чараў" + +#: Source/control.cpp:164 +msgid "Send Message" +msgstr "Даслаць паведамленне" + +#: Source/control.cpp:694 +msgid "Player friendly" +msgstr "Плэер-фрэндлі" + +#: Source/control.cpp:696 +msgid "Player attack" +msgstr "Гулец атакуе" + +#: Source/control.cpp:699 +msgid "Hotkey: {:s}" +msgstr "Гарачая клавіша: {:s}" + +#: Source/control.cpp:706 +msgid "Select current spell button" +msgstr "Выбраць клавішу цяперашняй чары" + +#: Source/control.cpp:709 +msgid "Hotkey: 's'" +msgstr "Гарачая клавіша: 's'" + +#: Source/control.cpp:715 Source/panels/spell_list.cpp:163 +msgid "{:s} Skill" +msgstr "{:s} Уменне" + +#: Source/control.cpp:718 Source/panels/spell_list.cpp:170 +msgid "{:s} Spell" +msgstr "{:s} Чара" + +#: Source/control.cpp:720 Source/panels/spell_list.cpp:175 +msgid "Spell Level 0 - Unusable" +msgstr "Чара Узровень 0 – Няздатная" + +#: Source/control.cpp:720 Source/panels/spell_list.cpp:177 +msgid "Spell Level {:d}" +msgstr "Узровень Чары {:d}" + +#: Source/control.cpp:723 Source/panels/spell_list.cpp:184 +msgid "Scroll of {:s}" +msgstr "Скрутак {:s}" + +#: Source/control.cpp:728 Source/panels/spell_list.cpp:189 +msgid "{:d} Scroll" +msgid_plural "{:d} Scrolls" +msgstr[0] "{:d} Скрутак" +msgstr[1] "{:d} Скруткі" +msgstr[2] "{:d} Скруткаў" + +#: Source/control.cpp:731 Source/panels/spell_list.cpp:196 +msgid "Staff of {:s}" +msgstr "Посах {:s}" + +#: Source/control.cpp:732 Source/panels/spell_list.cpp:198 +msgid "{:d} Charge" +msgid_plural "{:d} Charges" +msgstr[0] "{:d} Зарад" +msgstr[1] "{:d} Зарады" +msgstr[2] "{:d} Зарадаў" + +#: Source/control.cpp:860 Source/inv.cpp:2004 Source/items.cpp:3486 +msgid "{:s} gold piece" +msgid_plural "{:s} gold pieces" +msgstr[0] "{:s} манета" +msgstr[1] "{:s} манеты" +msgstr[2] "{:s} манет" + +#: Source/control.cpp:862 +msgid "Requirements not met" +msgstr "Патрабаванні не выкананыя" + +#: Source/control.cpp:896 +msgid "{:s}, Level: {:d}" +msgstr "{:s}, Узровень: {:d}" + +#: Source/control.cpp:897 +msgid "Hit Points {:d} of {:d}" +msgstr "Ачкі здароўя {:d} of {:d}" + +#: Source/control.cpp:931 +msgid "Level Up" +msgstr "Новы ўзровень" + +#. TRANSLATORS: {:d} is a number. Dialog is shown when splitting a stash of Gold. +#: Source/control.cpp:1039 +msgid "You have {:s} gold piece. How many do you want to remove?" +msgid_plural "You have {:s} gold pieces. How many do you want to remove?" +msgstr[0] "У вас ёсць {:s} манета. Колькі хочаце пакінуць?" +msgstr[1] "У вас ёсць {:s} манеты. Колькі хочаце пакінуць?" +msgstr[2] "У вас ёсць {:s} манет. Колькі хочаце пакінуць?" + +#: Source/controls/modifier_hints.cpp:177 Source/qol/monhealthbar.cpp:36 +#: Source/qol/xpbar.cpp:76 +msgid "" +"Failed to load UI resources.\n" +"\n" +"Make sure devilutionx.mpq is in the game folder and that it is up to date." +msgstr "" +"Не атрымалася загрузіць рэсурсы інтэрфэйса.\n" +"\n" +"Праверце, ці devilutionx.mpq у папке з гульнёю і ці абноўлены ён." + +#: Source/cursor.cpp:234 +msgid "Town Portal" +msgstr "Партал у горад" + +#: Source/cursor.cpp:235 +msgid "from {:s}" +msgstr "з {:s}" + +#: Source/cursor.cpp:249 +msgid "Portal to" +msgstr "Партал у" + +#: Source/cursor.cpp:250 +msgid "The Unholy Altar" +msgstr "Паганы алтар" + +#: Source/cursor.cpp:250 +msgid "level 15" +msgstr "узровень 15" + +#: Source/diablo.cpp:120 +msgid "I need help! Come Here!" +msgstr "Дапамажыце! Сюды!" + +#: Source/diablo.cpp:121 +msgid "Follow me." +msgstr "За мною." + +#: Source/diablo.cpp:122 +msgid "Here's something for you." +msgstr "Вось, на." + +#: Source/diablo.cpp:123 +msgid "Now you DIE!" +msgstr "Здохні!" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:822 +msgid "Options:" +msgstr "Варыянты:" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:823 +msgid "Print this message and exit" +msgstr "Пакінуць паведамленне і выйсці" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:824 +msgid "Print the version and exit" +msgstr "Пакінуць версію і выйсці" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:825 +msgid "Specify the folder of diabdat.mpq" +msgstr "Вызначыць папку diabdat.mpq" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:826 +msgid "Specify the folder of save files" +msgstr "Вызначаць папку захаванняў" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:827 +msgid "Specify the location of diablo.ini" +msgstr "Вызначыць знаходжанне diablo.ini" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:828 +msgid "Skip startup videos" +msgstr "Прапусціць відэа" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:829 +msgid "Display frames per second" +msgstr "Паказваць кадры ў секунду" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:830 +msgid "Enable verbose logging" +msgstr "Дазволіць падрабязнае апісанне" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:831 +msgid "Record a demo file" +msgstr "Запісаць дэма-файл" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:832 +msgid "Play a demo file" +msgstr "Прайграць дэма-файл" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:833 +msgid "Disable all frame limiting during demo playback" +msgstr "Адмяніць усё абмежаванне кадраў падчас прайгравання дэма" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:834 +msgid "Game selection:" +msgstr "Выбар гульні:" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:835 +msgid "Force Shareware mode" +msgstr "Уключыць рэжым Дэма" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:836 +msgid "Force Diablo mode" +msgstr "Уключыць рэжым Diablo" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:837 +msgid "Force Hellfire mode" +msgstr "Уключыць рэжым Hellfire" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:838 +msgid "Hellfire options:" +msgstr "Налады Hellfire:" + +#: Source/diablo.cpp:844 +msgid "Report bugs at https://github.com/diasurgical/devilutionX/" +msgstr "Паведаміць аб памылках https://github.com/diasurgical/devilutionX/" + +#: Source/diablo.cpp:956 +msgid "version {:s}" +msgstr "версія {:s}" + +#: Source/diablo.cpp:1277 +msgid "-- Network timeout --" +msgstr "-- Перапынак сеткі --" + +#: Source/diablo.cpp:1278 +msgid "-- Waiting for players --" +msgstr "-- Чакаем гульцоў --" + +#: Source/diablo.cpp:1297 +msgid "No help available" +msgstr "Дапамога не даступна" + +#: Source/diablo.cpp:1298 +msgid "while in stores" +msgstr "пакуль у чаканні" + +#: Source/diablo.cpp:1439 +msgid "Belt item {}" +msgstr "Рэч на поясе {}" + +#: Source/diablo.cpp:1440 +msgid "Use Belt item." +msgstr "Скарыстаць рэч на поясе." + +#: Source/diablo.cpp:1455 +msgid "Quick spell {}" +msgstr "Быстрае закляцце {}" + +#: Source/diablo.cpp:1456 +msgid "Hotkey for skill or spell." +msgstr "Гарачая клавіша пад уменне ці чару." + +#: Source/diablo.cpp:1474 +msgid "Speedbook" +msgstr "Кніга прыткасці" + +#: Source/diablo.cpp:1475 +msgid "Open Speedbook." +msgstr "Адчыніць кнігу прыткасці." + +#: Source/diablo.cpp:1482 +msgid "Quick save" +msgstr "Хутка захавацца" + +#: Source/diablo.cpp:1483 +msgid "Saves the game." +msgstr "Захоўвае гульню." + +#: Source/diablo.cpp:1490 +msgid "Quick load" +msgstr "Хутка загрузіцца" + +#: Source/diablo.cpp:1491 +msgid "Loads the game." +msgstr "Загрузіць гульню." + +#: Source/diablo.cpp:1499 +msgid "Quit game" +msgstr "Выйсці" + +#: Source/diablo.cpp:1500 +msgid "Closes the game." +msgstr "Зачыніць гульню." + +#: Source/diablo.cpp:1506 +msgid "Stop hero" +msgstr "Спыніць героя" + +#: Source/diablo.cpp:1507 +msgid "Stops walking and cancel pending actions." +msgstr "Спыніць ход і адмяніць дзеянне." + +#: Source/diablo.cpp:1514 +msgid "Item highlighting" +msgstr "Падсвечванне рэчаў" + +#: Source/diablo.cpp:1515 +msgid "Show/hide items on ground." +msgstr "Паказваць/хаваць рэчы на зямлі." + +#: Source/diablo.cpp:1521 +msgid "Toggle item highlighting" +msgstr "Пераключыць падсвечванне рэчаў" + +#: Source/diablo.cpp:1522 +msgid "Permanent show/hide items on ground." +msgstr "Імгненна паказваць/хаваць рэчы на зямлі." + +#: Source/diablo.cpp:1528 +msgid "Toggle automap" +msgstr "Пераключыць аўтамапу" + +#: Source/diablo.cpp:1529 +msgid "Toggles if automap is displayed." +msgstr "Пераключае калі аўтамапа ўключана." + +#: Source/diablo.cpp:1538 +msgid "Open Inventory screen." +msgstr "Адчыніць інвентар." + +#: Source/diablo.cpp:1545 +msgid "Character" +msgstr "Персанаж" + +#: Source/diablo.cpp:1546 +msgid "Open Character screen." +msgstr "Адчыніць экран персанажа." + +#: Source/diablo.cpp:1553 +msgid "Quest log" +msgstr "Заданні" + +#: Source/diablo.cpp:1554 +msgid "Open Quest log." +msgstr "Паглядзець заданні." + +#: Source/diablo.cpp:1561 +msgid "Spellbook" +msgstr "Кніга чараў" + +#: Source/diablo.cpp:1562 +msgid "Open Spellbook." +msgstr "Адчыніць кнігу чараў." + +#: Source/diablo.cpp:1570 +msgid "Quick Message {}" +msgstr "Хутка паведаміць {}" + +#: Source/diablo.cpp:1571 +msgid "Use Quick Message in chat." +msgstr "Хутка паведаміць у чат." + +#: Source/diablo.cpp:1580 +msgid "Hide Info Screens" +msgstr "Схаваць экраны інфармацыі" + +#: Source/diablo.cpp:1581 +msgid "Hide all info screens." +msgstr "Схаваць усе экраны інфармацыі." + +#: Source/diablo.cpp:1601 +msgid "Zoom" +msgstr "Наблізіць" + +#: Source/diablo.cpp:1602 +msgid "Zoom Game Screen." +msgstr "Наблізіць экран гульні." + +#: Source/diablo.cpp:1612 +msgid "Pause Game" +msgstr "Спыніць гульню" + +#: Source/diablo.cpp:1613 +msgid "Pauses the game." +msgstr "Спыняе гульню." + +#: Source/diablo.cpp:1618 +msgid "Decrease Gamma" +msgstr "Паменшыць гаму" + +#: Source/diablo.cpp:1619 +msgid "Reduce screen brightness." +msgstr "Знізіць яркасць экрана." + +#: Source/diablo.cpp:1626 +msgid "Increase Gamma" +msgstr "Павысіць гаму" + +#: Source/diablo.cpp:1627 +msgid "Increase screen brightness." +msgstr "Павысіць яркасць экрана." + +#: Source/diablo.cpp:1634 +msgid "Help" +msgstr "Дапамога" + +#: Source/diablo.cpp:1635 +msgid "Open Help Screen." +msgstr "Адчыніць экран дапамогі." + +#: Source/diablo.cpp:1642 +msgid "Screenshot" +msgstr "Здымак экрана" + +#: Source/diablo.cpp:1643 +msgid "Takes a screenshot." +msgstr "Зрабіць здымак экрана." + +#: Source/diablo.cpp:1649 +msgid "Game info" +msgstr "Аб гульні" + +#: Source/diablo.cpp:1650 +msgid "Displays game infos." +msgstr "Паказвае інфармацыю аб гульні." + +#. TRANSLATORS: {:s} means: Character Name, Game Version, Game Difficulty. +#: Source/diablo.cpp:1654 +msgid "{:s} {:s}" +msgstr "{:s} {:s}" + +#: Source/diablo.cpp:1663 +msgid "Chat Log" +msgstr "Чат" + +#: Source/diablo.cpp:1664 +msgid "Displays chat log." +msgstr "Паказвае чат." + +#: Source/discord/discord.cpp:66 Source/objects.cpp:136 +msgid "Town" +msgstr "Горад" + +#: Source/discord/discord.cpp:66 +msgid "Cathedral" +msgstr "Сабор" + +#: Source/discord/discord.cpp:66 +msgid "Catacombs" +msgstr "Катакомбы" + +#: Source/discord/discord.cpp:66 +msgid "Caves" +msgstr "Пячоры" + +#: Source/discord/discord.cpp:66 +msgid "Nest" +msgstr "Гняздо" + +#: Source/discord/discord.cpp:66 +msgid "Crypt" +msgstr "Крыпта" + +#. TRANSLATORS: dungeon type and floor number i.e. "Cathedral 3" +#: Source/discord/discord.cpp:82 +msgid "{} {}" +msgstr "{} {}" + +#. TRANSLATORS: Discord character, i.e. "Lv 6 Warrior" +#: Source/discord/discord.cpp:90 +msgid "Lv {} {}" +msgstr "Уз {} {}" + +#. TRANSLATORS: Discord state i.e. "Nightmare difficulty" +#: Source/discord/discord.cpp:102 +msgid "{} difficulty" +msgstr "{} складанасць" + +#. TRANSLATORS: Discord activity, not in game +#: Source/discord/discord.cpp:182 +msgid "In Menu" +msgstr "У меню" + +#: Source/dvlnet/loopback.cpp:113 +msgid "loopback" +msgstr "loopback" + +#: Source/dvlnet/tcp_client.cpp:65 +msgid "Unable to connect" +msgstr "Няма як звязацца" + +#: Source/dvlnet/tcp_client.cpp:91 +msgid "error: read 0 bytes from server" +msgstr "памылка: счытана 0 байт з сервера" + +#: Source/error.cpp:53 +msgid "No automap available in town" +msgstr "Мапа не даступна ў горадзе" + +#: Source/error.cpp:54 +msgid "No multiplayer functions in demo" +msgstr "У дэма няма шматкарыстальніцкай гульні" + +#: Source/error.cpp:55 +msgid "Direct Sound Creation Failed" +msgstr "Стварэнне гука правалілася" + +#: Source/error.cpp:56 +msgid "Not available in shareware version" +msgstr "Недаступна ў дэма версіі" + +#: Source/error.cpp:57 +msgid "Not enough space to save" +msgstr "Няма месца каб захаваць" + +#: Source/error.cpp:58 +msgid "No Pause in town" +msgstr "Нельга ставіць паўзу ў горадзе" + +#: Source/error.cpp:59 +msgid "Copying to a hard disk is recommended" +msgstr "Рэкамендуем захваць на цвёрдым дыску" + +#: Source/error.cpp:60 +msgid "Multiplayer sync problem" +msgstr "Праблема з сінхранізацыяй у мультыплэеры" + +#: Source/error.cpp:61 +msgid "No pause in multiplayer" +msgstr "Нельга ставіць на паўзу ў мультыплэеры" + +#: Source/error.cpp:63 +msgid "Saving..." +msgstr "Захоўвае..." + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:64 +msgid "Some are weakened as one grows strong" +msgstr "Як нехта мацнее другія слабеюць" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:65 +msgid "New strength is forged through destruction" +msgstr "Моц новая куецца разбурэннем" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:66 +msgid "Those who defend seldom attack" +msgstr "Хто бароніцца рэдка нападае" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:67 +msgid "The sword of justice is swift and sharp" +msgstr "Меч справядлівасці хуткі ды востры" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:68 +msgid "While the spirit is vigilant the body thrives" +msgstr "Пакуль дух пільнуе, цела квітнее" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:69 +msgid "The powers of mana refocused renews" +msgstr "Перакіраваўшыся моц маны аднаўляецца" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:70 +msgid "Time cannot diminish the power of steel" +msgstr "Час сталі моцы не паслабіць" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:71 +msgid "Magic is not always what it seems to be" +msgstr "Магія не заўжды тое, чым здаецца" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:72 +msgid "What once was opened now is closed" +msgstr "Што адчынілі цяпер зачынілі" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:73 +msgid "Intensity comes at the cost of wisdom" +msgstr "Напружанне каштуе мудрасці" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:74 +msgid "Arcane power brings destruction" +msgstr "Таемная моц нясе разбурэнне" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:75 +msgid "That which cannot be held cannot be harmed" +msgstr "За што ні ўзяцца, няма як параніць" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:76 +msgid "Crimson and Azure become as the sun" +msgstr "Чырвань ды блакіт стануць нібы сонца" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:77 +msgid "Knowledge and wisdom at the cost of self" +msgstr "Веды ды мудрасць па кошту сябе самога" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:78 +msgid "Drink and be refreshed" +msgstr "Пі ды асвяжыся" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:79 +msgid "Wherever you go, there you are" +msgstr "Дзе б ты ні быў, вось і ты" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:80 +msgid "Energy comes at the cost of wisdom" +msgstr "Сіла жыццёвая мудрасці каштуе" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:81 +msgid "Riches abound when least expected" +msgstr "Багацця там многа, дзе менш за ўсё думаеш" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:82 +msgid "Where avarice fails, patience gains reward" +msgstr "Дзе прайграе сквапнасць, там цярпенне ўзнагародзіцца" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:83 +msgid "Blessed by a benevolent companion!" +msgstr "Благаславёны добразычлівым спадарожнікам!" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:84 +msgid "The hands of men may be guided by fate" +msgstr "Няхай рукі чалавечыя лёсам накіруюцца" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:85 +msgid "Strength is bolstered by heavenly faith" +msgstr "Сіла ўмацавана вераю нябеснай" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:86 +msgid "The essence of life flows from within" +msgstr "Жыццёвая сутнасць ліецца знутры" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:87 +msgid "The way is made clear when viewed from above" +msgstr "Зверху глядзіш – шлях лягчэй пабачыць" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:88 +msgid "Salvation comes at the cost of wisdom" +msgstr "Ратунак прыйдзе цаною мудрасці" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:89 +msgid "Mysteries are revealed in the light of reason" +msgstr "Таямніцы раскрыюцца пры святле розуму" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:90 +msgid "Those who are last may yet be first" +msgstr "Апошнія могуць быць яшчэ і першымі" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:91 +msgid "Generosity brings its own rewards" +msgstr "Шчодрасць узнагародзіць па-свойму" + +#: Source/error.cpp:92 +msgid "You must be at least level 8 to use this." +msgstr "Трэба быць хаця б 8 ўзроўню." + +#: Source/error.cpp:93 +msgid "You must be at least level 13 to use this." +msgstr "Трэба быць хаця б 13 ўзроўню." + +#: Source/error.cpp:94 +msgid "You must be at least level 17 to use this." +msgstr "Трэба быць хаця б 8 ўзроўню." + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:95 +msgid "Arcane knowledge gained!" +msgstr "Таемныя веды вывучаны!" + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:96 +msgid "That which does not kill you..." +msgstr "Тое што вас не забівае..." + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:97 +msgid "Knowledge is power." +msgstr "Веды ёсць сіла." + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:98 +msgid "Give and you shall receive." +msgstr "Давайце і вам будзе дадзена." + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:99 +msgid "Some experience is gained by touch." +msgstr "Досвед можна і крануўшыся атрымаць." + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:100 +msgid "There's no place like home." +msgstr "Усюды добра, а дома лепей." + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:101 +msgid "Spiritual energy is restored." +msgstr "Сіла духа вернута." + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:102 +msgid "You feel more agile." +msgstr "Вы нібыта пажвавелі." + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:103 +msgid "You feel stronger." +msgstr "Вы нібыта узмацнелі." + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:104 +msgid "You feel wiser." +msgstr "Вы нібыта памудрэлі." + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:105 +msgid "You feel refreshed." +msgstr "Вы нібыта пасвяжэлі." + +#. TRANSLATORS: Shrine Text. Keep atmospheric. :) +#: Source/error.cpp:106 +msgid "That which can break will." +msgstr "Тое, што можа зламіць волю." + +#: Source/gamemenu.cpp:35 +msgid "Save Game" +msgstr "Захаваць гульню" + +#: Source/gamemenu.cpp:36 Source/gamemenu.cpp:47 +msgid "Options" +msgstr "Налады" + +#: Source/gamemenu.cpp:39 Source/gamemenu.cpp:50 +msgid "Quit Game" +msgstr "Выйсці" + +#: Source/gamemenu.cpp:49 +msgid "Restart In Town" +msgstr "Пачаць зноў у Горадзе" + +#: Source/gamemenu.cpp:59 +msgid "Gamma" +msgstr "Гама" + +#: Source/gamemenu.cpp:60 Source/gamemenu.cpp:167 +msgid "Speed" +msgstr "Хуткасць" + +#: Source/gamemenu.cpp:68 +msgid "Music Disabled" +msgstr "Музыка адключана" + +#: Source/gamemenu.cpp:72 +msgid "Sound" +msgstr "Гук" + +#: Source/gamemenu.cpp:73 +msgid "Sound Disabled" +msgstr "Гук адключаны" + +#: Source/gmenu.cpp:168 +msgid "Pause" +msgstr "Паўза" + +#: Source/help.cpp:27 +msgid "$Keyboard Shortcuts:" +msgstr "$Скарачэнні клавіш:" + +#: Source/help.cpp:28 +msgid "F1: Open Help Screen" +msgstr "F1:: Адчыніць экран дапамогі" + +#: Source/help.cpp:29 +msgid "Esc: Display Main Menu" +msgstr "Esc: Паказаць Галоўнае Меню" + +#: Source/help.cpp:30 +msgid "Tab: Display Auto-map" +msgstr "Tab: Паказаць аўтамапу" + +#: Source/help.cpp:31 +msgid "Space: Hide all info screens" +msgstr "Прабел: Схаваць усе экраны інфармацыі" + +#: Source/help.cpp:32 +msgid "S: Open Speedbook" +msgstr "S: Адчыцніь кнігу прыткасці" + +#: Source/help.cpp:33 +msgid "B: Open Spellbook" +msgstr "B: Адчыцніць кнігу чараў" + +#: Source/help.cpp:34 +msgid "I: Open Inventory screen" +msgstr "I: Адчыніць інвентар" + +#: Source/help.cpp:35 +msgid "C: Open Character screen" +msgstr "C: Адчыніць экран персанажа" + +#: Source/help.cpp:36 +msgid "Q: Open Quest log" +msgstr "Q: Паглядзець заданні" + +#: Source/help.cpp:37 +msgid "F: Reduce screen brightness" +msgstr "F: Знізіць яркасць экрана" + +#: Source/help.cpp:38 +msgid "G: Increase screen brightness" +msgstr "G: Павысіць яркасць экрана" + +#: Source/help.cpp:39 +msgid "Z: Zoom Game Screen" +msgstr "Z: Наблізіць экран гульні" + +#: Source/help.cpp:40 +msgid "+ / -: Zoom Automap" +msgstr "+ / -: Маштабаванне аўтамапы" + +#: Source/help.cpp:41 +msgid "1 - 8: Use Belt item" +msgstr "1 – 8: Выкарыстаць рэч з пояса" + +#: Source/help.cpp:42 +msgid "F5, F6, F7, F8: Set hotkey for skill or spell" +msgstr "F5, F6, F7, F8: Устанавіць гарачую клавішу" + +#: Source/help.cpp:43 +msgid "Shift + Left Mouse Button: Attack without moving" +msgstr "Shift + Левая кнопка мышы: Біць не рухаючыся" + +#: Source/help.cpp:44 +msgid "Shift + Left Mouse Button (on character screen): Assign all stat points" +msgstr "" +"Shift + Левая кнопка мышы (на экране персанажа): Прызначыць ачкі статыстыкі" + +#: Source/help.cpp:45 +msgid "" +"Shift + Left Mouse Button (on inventory): Move item to belt or equip/unequip " +"item" +msgstr "" +"Shift + Левая кнопка мышы (у інвентары): Павесіць нешта на пояс ці зняць/" +"надзець рэч" + +#: Source/help.cpp:46 +msgid "Shift + Left Mouse Button (on belt): Move item to inventory" +msgstr "Shift + Левая кнопка мышы (на поясе): Убраць рэч у інвентар" + +#: Source/help.cpp:48 +msgid "$Movement:" +msgstr "$Рух:" + +#: Source/help.cpp:49 +msgid "" +"If you hold the mouse button down while moving, the character will continue " +"to move in that direction." +msgstr "" +"Калі трымаеце кнопку мышы падчас руху, персанаж будзе працягваць рухацца ў " +"гэтым кірунку." + +#: Source/help.cpp:52 +msgid "$Combat:" +msgstr "$Бой:" + +#: Source/help.cpp:53 +msgid "" +"Holding down the shift key and then left-clicking allows the character to " +"attack without moving." +msgstr "" +"Трымаючы клавішу shift і пасля націскаючы левую кнопку мышы, персанаж можа " +"біцца стоячы на месцы." + +#: Source/help.cpp:56 +msgid "$Auto-map:" +msgstr "$Аўтамапа:" + +#: Source/help.cpp:57 +msgid "" +"To access the auto-map, click the 'MAP' button on the Information Bar or " +"press 'TAB' on the keyboard. Zooming in and out of the map is done with the " +"+ and - keys. Scrolling the map uses the arrow keys." +msgstr "" +"Каб адкрыць аўтамапу, націснеце \"МАПА\" на панэлі інфармацыі ці клавішу " +"'TAB' на клавіатуры. Маштабаванне карты ажыццяўляецца клавішамі + ды -. " +"Пракручвайце карту стрэлкамі на клавіатуры." + +#: Source/help.cpp:62 +msgid "$Picking up Objects:" +msgstr "$Падыманне рэчаў:" + +#: Source/help.cpp:63 +msgid "" +"Useable items that are small in size, such as potions or scrolls, are " +"automatically placed in your 'belt' located at the top of the Interface " +"bar . When an item is placed in the belt, a small number appears in that " +"box. Items may be used by either pressing the corresponding number or right-" +"clicking on the item." +msgstr "" +"Розныя маленькія рэчы, якімі можна карыстацца, як зеллі ці скруткі, " +"аўтаматычна змяшчаюцца ў ваш \"пояс\", наверсе панэлі інтэрфейсу. Калі " +"прадмет вешаецца на пояс, невялікая лічба з'яўляецца ў яго полі. Каб " +"скарыстацца рэччу, трэба націснуць адпаведную лічбу на клавіатуры або " +"націснуць па ім правай кнопкай мышы." + +#: Source/help.cpp:69 +msgid "$Gold:" +msgstr "$Золата:" + +#: Source/help.cpp:70 +msgid "" +"You can select a specific amount of gold to drop by right-clicking on a pile " +"of gold in your inventory." +msgstr "" +"Вы можаце выбраць, колькі золата кінуць, націснуўшы правай кнопкай мышы па " +"жменьцы золота ў інвентары." + +#: Source/help.cpp:73 +msgid "$Skills & Spells:" +msgstr "$Уменні ды Чары:" + +#: Source/help.cpp:74 +msgid "" +"You can access your list of skills and spells by left-clicking on the " +"'SPELLS' button in the interface bar. Memorized spells and those available " +"through staffs are listed here. Left-clicking on the spell you wish to cast " +"will ready the spell. A readied spell may be cast by simply right-clicking " +"in the play area." +msgstr "" +"Вы можаце адкрыць спіс уменняў ды чараў, націснуўшы левай кнопкай мышы па " +"'ЧАРЫ' на панэлі інтэрфейсу. Тут пералічаны вывучаныя ды даступныя праз " +"посах чары. Каб падрыхтаваць патрэбныя чары, клікніце па іх левай кнопкай " +"мышы. Чары можна наслаць націснуўшы правай кнопкаю мышы." + +#: Source/help.cpp:80 +msgid "$Using the Speedbook for Spells:" +msgstr "$Кніга прыткасці для чараў:" + +#: Source/help.cpp:81 +msgid "" +"Left-clicking on the 'readied spell' button will open the 'Speedbook' which " +"allows you to select a skill or spell for immediate use. To use a readied " +"skill or spell, simply right-click in the main play area." +msgstr "" +"Калі націснуць на \"гатовыя чары\", то адкрыецца \"Кніга прыткасці\", якая " +"дае выбраць чары ці уменне для хуткага карыстання. Каб скарыстацца гатовым " +"уменнем ці чарамі, проста націснеце правай кнопкаю мышы." + +#: Source/help.cpp:85 +msgid "" +"Shift + Left-clicking on the 'select current spell' button will clear the " +"readied spell." +msgstr "" +"Shift + Левая кнопка мышы па \"выбраць цяперашнія чары\" прыбярэ гатовыя " +"чары." + +#: Source/help.cpp:87 +msgid "$Setting Spell Hotkeys:" +msgstr "$Устаноўка гарачых клавішаў для чараў:" + +#: Source/help.cpp:88 +msgid "" +"You can assign up to four Hotkeys for skills, spells or scrolls. Start by " +"opening the 'speedbook' as described in the section above. Press the F5, F6, " +"F7 or F8 keys after highlighting the spell you wish to assign." +msgstr "" +"Вы можаце прызначыць да чатырох гарачых клавіш для навыкаў, чараў ці " +"скруткаў. Пачніце адкрыўшы \"кнігу прыткасці\", як апісана вышэй. Націснеце " +"F5, F6, F7 ці F8 пасля вылучэння чараў, якія хочаце прызначыць." + +#: Source/help.cpp:93 +msgid "$Spell Books:" +msgstr "$Кнігі чараў:" + +#: Source/help.cpp:94 +msgid "" +"Reading more than one book increases your knowledge of that spell, allowing " +"you to cast the spell more effectively." +msgstr "" +"Чытанне больш за адну кнігу павышае вашыя веды аб гэтых чарах, дазваляючы " +"карыстацца імі эфектыўней." + +#: Source/help.cpp:177 +msgid "Shareware Hellfire Help" +msgstr "Дамапога з дэма Hellfire" + +#: Source/help.cpp:177 +msgid "Hellfire Help" +msgstr "Дапамога з Hellfire" + +#: Source/help.cpp:179 +msgid "Shareware Diablo Help" +msgstr "Дапамога з дэма Diablo" + +#: Source/help.cpp:179 +msgid "Diablo Help" +msgstr "Дапамога з Diablo" + +#: Source/help.cpp:209 Source/qol/chatlog.cpp:180 +msgid "Press ESC to end or the arrow keys to scroll." +msgstr "Націснеце ESC каб скончыць ці пракруціце стрэлачкамі." + +#: Source/init.cpp:194 +msgid "diabdat.mpq or spawn.mpq" +msgstr "diabdat.mpq ці spawn.mpq" + +#: Source/init.cpp:215 +msgid "Some Hellfire MPQs are missing" +msgstr "Нейкіх MPQ файлаў Hellfire бракуе" + +#: Source/init.cpp:215 +msgid "" +"Not all Hellfire MPQs were found.\n" +"Please copy all the hf*.mpq files." +msgstr "" +"Не ўсе MPQ файлы Hellfire знойдзеныя.\n" +"Скапіюйце ўсе hf*.mpq файлы, калі ласка." + +#: Source/init.cpp:223 +msgid "Unable to create main window" +msgstr "Немагчыма стварыць галоўнае акно" + +#: Source/itemdat.cpp:53 Source/itemdat.cpp:235 Source/panels/charpanel.cpp:164 +msgid "Gold" +msgstr "Золата" + +#: Source/itemdat.cpp:54 Source/itemdat.cpp:172 +msgid "Short Sword" +msgstr "Корд" + +#: Source/itemdat.cpp:55 Source/itemdat.cpp:124 +msgid "Buckler" +msgstr "Пуклер" + +#: Source/itemdat.cpp:56 Source/itemdat.cpp:192 Source/itemdat.cpp:193 +msgid "Club" +msgstr "Дубіна" + +#: Source/itemdat.cpp:57 Source/itemdat.cpp:196 +msgid "Short Bow" +msgstr "Кароткі лук" + +#: Source/itemdat.cpp:58 +msgid "Short Staff of Mana" +msgstr "Кароткі посах маны" + +#: Source/itemdat.cpp:59 +msgid "Cleaver" +msgstr "Сякач" + +#: Source/itemdat.cpp:60 Source/itemdat.cpp:434 +msgid "The Undead Crown" +msgstr "Нябожчыкава карона" + +#: Source/itemdat.cpp:61 Source/itemdat.cpp:435 +msgid "Empyrean Band" +msgstr "Незямная стужка" + +#: Source/itemdat.cpp:62 +msgid "Magic Rock" +msgstr "Чарадзейны камень" + +#: Source/itemdat.cpp:63 Source/itemdat.cpp:436 +msgid "Optic Amulet" +msgstr "Аптычны амулет" + +#: Source/itemdat.cpp:64 Source/itemdat.cpp:437 +msgid "Ring of Truth" +msgstr "Пярцсцёнак праўды" + +#: Source/itemdat.cpp:65 +msgid "Tavern Sign" +msgstr "Знак карчмы" + +#: Source/itemdat.cpp:66 Source/itemdat.cpp:438 +msgid "Harlequin Crest" +msgstr "Арлекінаў грабянец" + +#: Source/itemdat.cpp:67 Source/itemdat.cpp:439 +msgid "Veil of Steel" +msgstr "Стальны вэлюм" + +#: Source/itemdat.cpp:68 +msgid "Golden Elixir" +msgstr "Залаты эліксір" + +#: Source/itemdat.cpp:69 Source/quests.cpp:54 +msgid "Anvil of Fury" +msgstr "Кавадла ярасці" + +#: Source/itemdat.cpp:70 Source/quests.cpp:45 +msgid "Black Mushroom" +msgstr "Чорны грыб" + +#: Source/itemdat.cpp:71 +msgid "Brain" +msgstr "Мозг" + +#: Source/itemdat.cpp:72 +msgid "Fungal Tome" +msgstr "Грыбны фаліянт" + +#: Source/itemdat.cpp:73 +msgid "Spectral Elixir" +msgstr "Прывідны эліксір" + +#: Source/itemdat.cpp:74 +msgid "Blood Stone" +msgstr "Камень крыві" + +#: Source/itemdat.cpp:75 +msgid "Cathedral Map" +msgstr "Мапа сабору" + +#: Source/itemdat.cpp:76 +msgid "Heart" +msgstr "Сэрца" + +#: Source/itemdat.cpp:77 Source/itemdat.cpp:130 +msgid "Potion of Healing" +msgstr "Зелле лячэння" + +#: Source/itemdat.cpp:78 Source/itemdat.cpp:132 +msgid "Potion of Mana" +msgstr "Зелле маны" + +#: Source/itemdat.cpp:79 Source/itemdat.cpp:147 +msgid "Scroll of Identify" +msgstr "Скрутак выяўлення" + +#: Source/itemdat.cpp:80 Source/itemdat.cpp:151 +msgid "Scroll of Town Portal" +msgstr "Скрутак партала ў Горад" + +#: Source/itemdat.cpp:81 Source/itemdat.cpp:440 +msgid "Arkaine's Valor" +msgstr "Адвага Аркейна" + +#: Source/itemdat.cpp:82 Source/itemdat.cpp:131 +msgid "Potion of Full Healing" +msgstr "Зелле поўнага лячэння" + +#: Source/itemdat.cpp:83 Source/itemdat.cpp:133 +msgid "Potion of Full Mana" +msgstr "Зелле поўнай маны" + +#: Source/itemdat.cpp:84 Source/itemdat.cpp:441 +msgid "Griswold's Edge" +msgstr "Вастрыё Грызвальда" + +#: Source/itemdat.cpp:85 Source/itemdat.cpp:442 +msgid "Bovine Plate" +msgstr "Бычыная пласціна" + +#: Source/itemdat.cpp:86 +msgid "Staff of Lazarus" +msgstr "Посах Лазара" + +#: Source/itemdat.cpp:87 Source/itemdat.cpp:148 +msgid "Scroll of Resurrect" +msgstr "Скрутак Уваскрэсення" + +#: Source/itemdat.cpp:88 Source/itemdat.cpp:136 Source/items.cpp:172 +msgid "Blacksmith Oil" +msgstr "Кавалёва масла" + +#: Source/itemdat.cpp:89 Source/itemdat.cpp:204 +msgid "Short Staff" +msgstr "Кароткі посах" + +#: Source/itemdat.cpp:90 Source/itemdat.cpp:172 Source/itemdat.cpp:173 +#: Source/itemdat.cpp:174 Source/itemdat.cpp:175 Source/itemdat.cpp:178 +#: Source/itemdat.cpp:179 Source/itemdat.cpp:180 Source/itemdat.cpp:181 +#: Source/itemdat.cpp:182 +msgid "Sword" +msgstr "Меч" + +#: Source/itemdat.cpp:91 Source/itemdat.cpp:171 +msgid "Dagger" +msgstr "Кінжал" + +#: Source/itemdat.cpp:92 +msgid "Rune Bomb" +msgstr "Рунічная бомба" + +#: Source/itemdat.cpp:93 +msgid "Theodore" +msgstr "Тэадор" + +#: Source/itemdat.cpp:94 +msgid "Auric Amulet" +msgstr "Амулет аўры" + +#: Source/itemdat.cpp:95 +msgid "Torn Note 1" +msgstr "Абрывак 1" + +#: Source/itemdat.cpp:96 +msgid "Torn Note 2" +msgstr "Абрывак 2" + +#: Source/itemdat.cpp:97 +msgid "Torn Note 3" +msgstr "Абрывак 3" + +#: Source/itemdat.cpp:98 +msgid "Reconstructed Note" +msgstr "Узноўлены запіс" + +#: Source/itemdat.cpp:99 +msgid "Brown Suit" +msgstr "Руды ўбор" + +#: Source/itemdat.cpp:100 +msgid "Grey Suit" +msgstr "Шэры ўбор" + +#: Source/itemdat.cpp:101 Source/itemdat.cpp:102 +msgid "Cap" +msgstr "Шапка" + +#: Source/itemdat.cpp:102 +msgid "Skull Cap" +msgstr "Шышак" + +#: Source/itemdat.cpp:103 Source/itemdat.cpp:104 Source/itemdat.cpp:106 +msgid "Helm" +msgstr "Шалом" + +#: Source/itemdat.cpp:104 +msgid "Full Helm" +msgstr "Закрыты шалом" + +#: Source/itemdat.cpp:105 +msgid "Crown" +msgstr "Карона" + +#: Source/itemdat.cpp:106 +msgid "Great Helm" +msgstr "Вялікі шалом" + +#: Source/itemdat.cpp:107 +msgid "Cape" +msgstr "Накідка" + +#: Source/itemdat.cpp:108 +msgid "Rags" +msgstr "Рыззё" + +#: Source/itemdat.cpp:109 +msgid "Cloak" +msgstr "Плашч" + +#: Source/itemdat.cpp:110 +msgid "Robe" +msgstr "Мантыя" + +#: Source/itemdat.cpp:111 +msgid "Quilted Armor" +msgstr "Падшыванка" + +#: Source/itemdat.cpp:111 Source/itemdat.cpp:112 Source/itemdat.cpp:113 +#: Source/itemdat.cpp:114 Source/objects.cpp:5476 +msgid "Armor" +msgstr "Браня" + +#: Source/itemdat.cpp:112 +msgid "Leather Armor" +msgstr "Кожаны панцыр" + +#: Source/itemdat.cpp:113 +msgid "Hard Leather Armor" +msgstr "Жорсткі скураны панцыр" + +#: Source/itemdat.cpp:114 +msgid "Studded Leather Armor" +msgstr "Нітаваны скураны панцыр" + +#: Source/itemdat.cpp:115 +msgid "Ring Mail" +msgstr "Кольчаты панцыр" + +#: Source/itemdat.cpp:115 Source/itemdat.cpp:116 Source/itemdat.cpp:117 +#: Source/itemdat.cpp:119 +msgid "Mail" +msgstr "Кальчуга" + +#: Source/itemdat.cpp:116 +msgid "Chain Mail" +msgstr "Кальчуга" + +#: Source/itemdat.cpp:117 +msgid "Scale Mail" +msgstr "Лускаваты панцыр" + +#: Source/itemdat.cpp:118 +msgid "Breast Plate" +msgstr "Нагруднік" + +#: Source/itemdat.cpp:118 Source/itemdat.cpp:120 Source/itemdat.cpp:121 +#: Source/itemdat.cpp:122 Source/itemdat.cpp:123 +msgid "Plate" +msgstr "Латы" + +#: Source/itemdat.cpp:119 +msgid "Splint Mail" +msgstr "Бехцер" + +#: Source/itemdat.cpp:120 +msgid "Plate Mail" +msgstr "Латы" + +#: Source/itemdat.cpp:121 +msgid "Field Plate" +msgstr "Баявы даспех" + +#: Source/itemdat.cpp:122 +msgid "Gothic Plate" +msgstr "Гатычны даспех" + +#: Source/itemdat.cpp:123 +msgid "Full Plate Mail" +msgstr "Поўны латны даспех" + +#: Source/itemdat.cpp:124 Source/itemdat.cpp:125 Source/itemdat.cpp:126 +#: Source/itemdat.cpp:127 Source/itemdat.cpp:128 Source/itemdat.cpp:129 +msgid "Shield" +msgstr "Шчыт" + +#: Source/itemdat.cpp:125 +msgid "Small Shield" +msgstr "Малы шчыт" + +#: Source/itemdat.cpp:126 +msgid "Large Shield" +msgstr "Вялізны шчыт" + +#: Source/itemdat.cpp:127 +msgid "Kite Shield" +msgstr "Міндалепадобны шчыт" + +#: Source/itemdat.cpp:128 +msgid "Tower Shield" +msgstr "Павеза" + +#: Source/itemdat.cpp:129 +msgid "Gothic Shield" +msgstr "Гатычны шчыт" + +#: Source/itemdat.cpp:134 +msgid "Potion of Rejuvenation" +msgstr "Зелле аднаўлення" + +#: Source/itemdat.cpp:135 +msgid "Potion of Full Rejuvenation" +msgstr "Зелле поўнага аднаўлення" + +#: Source/itemdat.cpp:137 Source/items.cpp:167 +msgid "Oil of Accuracy" +msgstr "Алей дакладнасці" + +#: Source/itemdat.cpp:138 Source/items.cpp:169 +msgid "Oil of Sharpness" +msgstr "Алей вастрыні" + +#: Source/itemdat.cpp:139 +msgid "Oil" +msgstr "Алей" + +#: Source/itemdat.cpp:140 +msgid "Elixir of Strength" +msgstr "Эліксір моцы" + +#: Source/itemdat.cpp:141 +msgid "Elixir of Magic" +msgstr "Эліксір магіі" + +#: Source/itemdat.cpp:142 +msgid "Elixir of Dexterity" +msgstr "Эліксір спрыту" + +#: Source/itemdat.cpp:143 +msgid "Elixir of Vitality" +msgstr "Эліксір жывучасці" + +#: Source/itemdat.cpp:144 +msgid "Scroll of Healing" +msgstr "Скрутак лячэння" + +#: Source/itemdat.cpp:145 +msgid "Scroll of Search" +msgstr "Скрутак пошуку" + +#: Source/itemdat.cpp:146 +msgid "Scroll of Lightning" +msgstr "Скрутак маланкі" + +#: Source/itemdat.cpp:149 +msgid "Scroll of Fire Wall" +msgstr "Скрутак вогеннай сцяны" + +#: Source/itemdat.cpp:150 +msgid "Scroll of Inferno" +msgstr "Скрутак апраметнай" + +#: Source/itemdat.cpp:152 +msgid "Scroll of Flash" +msgstr "Скрутак бліску" + +#: Source/itemdat.cpp:153 +msgid "Scroll of Infravision" +msgstr "Скрутак скрозьбачання" + +#: Source/itemdat.cpp:154 +msgid "Scroll of Phasing" +msgstr "Скрутак перамяшчэння" + +#: Source/itemdat.cpp:155 +msgid "Scroll of Mana Shield" +msgstr "Скрутак шчыта маны" + +#: Source/itemdat.cpp:156 +msgid "Scroll of Flame Wave" +msgstr "Скрутак хвалі полымя" + +#: Source/itemdat.cpp:157 +msgid "Scroll of Fireball" +msgstr "Скрутак вогненнага шара" + +#: Source/itemdat.cpp:158 +msgid "Scroll of Stone Curse" +msgstr "Скрутак праклёну каменя" + +#: Source/itemdat.cpp:159 +msgid "Scroll of Chain Lightning" +msgstr "Скрутак ланцуговай маланкі" + +#: Source/itemdat.cpp:160 +msgid "Scroll of Guardian" +msgstr "Скрутак вартавога" + +#: Source/itemdat.cpp:162 +msgid "Scroll of Nova" +msgstr "Скрутак новай зоркі" + +#: Source/itemdat.cpp:163 +msgid "Scroll of Golem" +msgstr "Скрутак голема" + +#: Source/itemdat.cpp:165 +msgid "Scroll of Teleport" +msgstr "Скрутак тэлепартацыі" + +#: Source/itemdat.cpp:166 +msgid "Scroll of Apocalypse" +msgstr "Скрутак апакаліпсіса" + +#: Source/itemdat.cpp:167 Source/itemdat.cpp:168 Source/itemdat.cpp:169 +#: Source/itemdat.cpp:170 +msgid "Book of " +msgstr "Кніга " + +#: Source/itemdat.cpp:173 +msgid "Falchion" +msgstr "Фальшыён" + +#: Source/itemdat.cpp:174 +msgid "Scimitar" +msgstr "Крывая шабля" + +#: Source/itemdat.cpp:175 +msgid "Claymore" +msgstr "Клеймор" + +#: Source/itemdat.cpp:176 +msgid "Blade" +msgstr "Клінок" + +#: Source/itemdat.cpp:177 +msgid "Sabre" +msgstr "Шабля" + +#: Source/itemdat.cpp:178 +msgid "Long Sword" +msgstr "Доўгі меч" + +#: Source/itemdat.cpp:179 +msgid "Broad Sword" +msgstr "Палаш" + +#: Source/itemdat.cpp:180 +msgid "Bastard Sword" +msgstr "Паўтараручны меч" + +#: Source/itemdat.cpp:181 +msgid "Two-Handed Sword" +msgstr "Двухручны меч" + +#: Source/itemdat.cpp:182 +msgid "Great Sword" +msgstr "Аберучны меч" + +#: Source/itemdat.cpp:183 +msgid "Small Axe" +msgstr "Сякерка" + +#: Source/itemdat.cpp:183 Source/itemdat.cpp:184 Source/itemdat.cpp:185 +#: Source/itemdat.cpp:186 Source/itemdat.cpp:187 Source/itemdat.cpp:188 +msgid "Axe" +msgstr "Сякера" + +#: Source/itemdat.cpp:185 +msgid "Large Axe" +msgstr "Бярдыш" + +#: Source/itemdat.cpp:186 +msgid "Broad Axe" +msgstr "Склюд" + +#: Source/itemdat.cpp:187 +msgid "Battle Axe" +msgstr "Лабрыс" + +#: Source/itemdat.cpp:188 +msgid "Great Axe" +msgstr "Вялікая сякера" + +#: Source/itemdat.cpp:189 Source/itemdat.cpp:190 +msgid "Mace" +msgstr "Булава" + +#: Source/itemdat.cpp:190 +msgid "Morning Star" +msgstr "Маргенштэрн" + +#: Source/itemdat.cpp:191 +msgid "War Hammer" +msgstr "Баявы молат" + +#: Source/itemdat.cpp:191 +msgid "Hammer" +msgstr "Молат" + +#: Source/itemdat.cpp:192 +msgid "Spiked Club" +msgstr "Шыпастая дубіна" + +#: Source/itemdat.cpp:194 +msgid "Flail" +msgstr "Кісцень" + +#: Source/itemdat.cpp:195 +msgid "Maul" +msgstr "Кувалда" + +#: Source/itemdat.cpp:196 Source/itemdat.cpp:197 Source/itemdat.cpp:198 +#: Source/itemdat.cpp:199 Source/itemdat.cpp:200 Source/itemdat.cpp:201 +#: Source/itemdat.cpp:202 Source/itemdat.cpp:203 +msgid "Bow" +msgstr "Лук" + +#: Source/itemdat.cpp:197 +msgid "Hunter's Bow" +msgstr "Паляўнічы лук" + +#: Source/itemdat.cpp:198 +msgid "Long Bow" +msgstr "Доўгі лук" + +#: Source/itemdat.cpp:199 +msgid "Composite Bow" +msgstr "Складаны лук" + +#: Source/itemdat.cpp:200 +msgid "Short Battle Bow" +msgstr "Кароткі баявы лук" + +#: Source/itemdat.cpp:201 +msgid "Long Battle Bow" +msgstr "Доўгі баявы лук" + +#: Source/itemdat.cpp:202 +msgid "Short War Bow" +msgstr "Кароткі ваенны лук" + +#: Source/itemdat.cpp:203 +msgid "Long War Bow" +msgstr "Доўгі ваенны лук" + +#: Source/itemdat.cpp:204 Source/itemdat.cpp:205 Source/itemdat.cpp:206 +#: Source/itemdat.cpp:207 Source/itemdat.cpp:208 +#: Source/panels/spell_list.cpp:195 +msgid "Staff" +msgstr "Посах" + +#: Source/itemdat.cpp:205 +msgid "Long Staff" +msgstr "Доўгі посах" + +#: Source/itemdat.cpp:206 +msgid "Composite Staff" +msgstr "Складаны посах" + +#: Source/itemdat.cpp:207 +msgid "Quarter Staff" +msgstr "Посах міласэрнасці" + +#: Source/itemdat.cpp:208 +msgid "War Staff" +msgstr "Посах вайны" + +#: Source/itemdat.cpp:209 Source/itemdat.cpp:210 Source/itemdat.cpp:211 +msgid "Ring" +msgstr "Пярсцёнак" + +#: Source/itemdat.cpp:212 Source/itemdat.cpp:213 +msgid "Amulet" +msgstr "Амулет" + +#: Source/itemdat.cpp:214 +msgid "Rune of Fire" +msgstr "Руна агню" + +#: Source/itemdat.cpp:214 Source/itemdat.cpp:215 Source/itemdat.cpp:216 +#: Source/itemdat.cpp:217 Source/itemdat.cpp:218 +msgid "Rune" +msgstr "Руна" + +#: Source/itemdat.cpp:215 +msgid "Rune of Lightning" +msgstr "Руна маланкі" + +#: Source/itemdat.cpp:216 +msgid "Greater Rune of Fire" +msgstr "Большая руна агню" + +#: Source/itemdat.cpp:217 +msgid "Greater Rune of Lightning" +msgstr "Большая руна маланкі" + +#: Source/itemdat.cpp:218 +msgid "Rune of Stone" +msgstr "Руна каменя" + +#: Source/itemdat.cpp:219 +msgid "Short Staff of Charged Bolt" +msgstr "Кароткі посах заражанай маланкі" + +#. TRANSLATORS: Item prefix section. +#: Source/itemdat.cpp:229 +msgid "Tin" +msgstr "Алавянны" + +#: Source/itemdat.cpp:230 +msgid "Brass" +msgstr "Латуневы" + +#: Source/itemdat.cpp:231 +msgid "Bronze" +msgstr "Бронзавы" + +#: Source/itemdat.cpp:232 +msgid "Iron" +msgstr "Жалезны" + +#: Source/itemdat.cpp:233 +msgid "Steel" +msgstr "Стальны" + +#: Source/itemdat.cpp:234 +msgid "Silver" +msgstr "Срэбраны" + +#: Source/itemdat.cpp:236 +msgid "Platinum" +msgstr "Плацінавы" + +#: Source/itemdat.cpp:237 +msgid "Mithril" +msgstr "Мітрылявы" + +#: Source/itemdat.cpp:238 +msgid "Meteoric" +msgstr "Метэорны" + +#: Source/itemdat.cpp:239 Source/objects.cpp:109 +msgid "Weird" +msgstr "Дзіўны" + +#: Source/itemdat.cpp:240 +msgid "Strange" +msgstr "Невядомы" + +#: Source/itemdat.cpp:241 +msgid "Useless" +msgstr "Дарэмны" + +#: Source/itemdat.cpp:242 +msgid "Bent" +msgstr "Крывы" + +#: Source/itemdat.cpp:243 +msgid "Weak" +msgstr "Слабы" + +#: Source/itemdat.cpp:244 +msgid "Jagged" +msgstr "Вышчарблены" + +#: Source/itemdat.cpp:245 +msgid "Deadly" +msgstr "Смяротны" + +#: Source/itemdat.cpp:246 +msgid "Heavy" +msgstr "Важкі" + +#: Source/itemdat.cpp:247 +msgid "Vicious" +msgstr "Ліхі" + +#: Source/itemdat.cpp:248 +msgid "Brutal" +msgstr "Люты" + +#: Source/itemdat.cpp:249 +msgid "Massive" +msgstr "Велізарны" + +#: Source/itemdat.cpp:250 +msgid "Savage" +msgstr "Зверскі" + +#: Source/itemdat.cpp:251 +msgid "Ruthless" +msgstr "Жорсткі" + +#: Source/itemdat.cpp:252 +msgid "Merciless" +msgstr "Бязлітасны" + +#: Source/itemdat.cpp:253 +msgid "Clumsy" +msgstr "Няспраўны" + +#: Source/itemdat.cpp:254 +msgid "Dull" +msgstr "Тупы" + +#: Source/itemdat.cpp:255 +msgid "Sharp" +msgstr "Востры" + +#: Source/itemdat.cpp:256 Source/itemdat.cpp:266 +msgid "Fine" +msgstr "Файны" + +#: Source/itemdat.cpp:257 +msgid "Warrior's" +msgstr "Ваярскі" + +#: Source/itemdat.cpp:258 +msgid "Soldier's" +msgstr "Жаўнерскі" + +#: Source/itemdat.cpp:259 +msgid "Lord's" +msgstr "Валадарскі" + +#: Source/itemdat.cpp:260 +msgid "Knight's" +msgstr "Рыцарскі" + +#: Source/itemdat.cpp:261 +msgid "Master's" +msgstr "Майстроўскі" + +#: Source/itemdat.cpp:262 +msgid "Champion's" +msgstr "Чэмпіёнскі" + +#: Source/itemdat.cpp:263 +msgid "King's" +msgstr "Каралеўскі" + +#: Source/itemdat.cpp:264 +msgid "Vulnerable" +msgstr "Прыступны" + +#: Source/itemdat.cpp:265 +msgid "Rusted" +msgstr "Заіржавелы" + +#: Source/itemdat.cpp:267 +msgid "Strong" +msgstr "Моцны" + +#: Source/itemdat.cpp:268 +msgid "Grand" +msgstr "Велічэзны" + +#: Source/itemdat.cpp:269 +msgid "Valiant" +msgstr "Адважны" + +#: Source/itemdat.cpp:270 +msgid "Glorious" +msgstr "Славуты" + +#: Source/itemdat.cpp:271 +msgid "Blessed" +msgstr "Дабраславёны" + +#: Source/itemdat.cpp:272 +msgid "Saintly" +msgstr "Праведны" + +#: Source/itemdat.cpp:273 +msgid "Awesome" +msgstr "Жахлівы" + +#: Source/itemdat.cpp:274 Source/objects.cpp:121 +msgid "Holy" +msgstr "Святы" + +#: Source/itemdat.cpp:275 +msgid "Godly" +msgstr "Боскі" + +#: Source/itemdat.cpp:276 +msgid "Red" +msgstr "Чырвоны" + +#: Source/itemdat.cpp:277 Source/itemdat.cpp:278 +msgid "Crimson" +msgstr "Барвовы" + +#: Source/itemdat.cpp:279 +msgid "Garnet" +msgstr "Гранатавы" + +#: Source/itemdat.cpp:280 +msgid "Ruby" +msgstr "Рубінавы" + +#: Source/itemdat.cpp:281 +msgid "Blue" +msgstr "Сіні" + +#: Source/itemdat.cpp:282 +msgid "Azure" +msgstr "Блакітны" + +#: Source/itemdat.cpp:283 +msgid "Lapis" +msgstr "Ляпісны" + +#: Source/itemdat.cpp:284 +msgid "Cobalt" +msgstr "Кобальтавы" + +#: Source/itemdat.cpp:285 +msgid "Sapphire" +msgstr "Сапфіравы" + +#: Source/itemdat.cpp:286 +msgid "White" +msgstr "Белы" + +#: Source/itemdat.cpp:287 +msgid "Pearl" +msgstr "Жамчужны" + +#: Source/itemdat.cpp:288 +msgid "Ivory" +msgstr "Слановай косці" + +#: Source/itemdat.cpp:289 +msgid "Crystal" +msgstr "Крыштальны" + +#: Source/itemdat.cpp:290 +msgid "Diamond" +msgstr "Дыямантавы" + +#: Source/itemdat.cpp:291 +msgid "Topaz" +msgstr "Тапазавы" + +#: Source/itemdat.cpp:292 +msgid "Amber" +msgstr "Бурштынавы" + +#: Source/itemdat.cpp:293 +msgid "Jade" +msgstr "Нефрытавы" + +#: Source/itemdat.cpp:294 +msgid "Obsidian" +msgstr "Абсідыянавы" + +#: Source/itemdat.cpp:295 +msgid "Emerald" +msgstr "Смарагдавы" + +#: Source/itemdat.cpp:296 +msgid "Hyena's" +msgstr "Гіенін" + +#: Source/itemdat.cpp:297 +msgid "Frog's" +msgstr "Жабін" + +#: Source/itemdat.cpp:298 +msgid "Spider's" +msgstr "Павукоў" + +#: Source/itemdat.cpp:299 +msgid "Raven's" +msgstr "Крумкачоў" + +#: Source/itemdat.cpp:300 +msgid "Snake's" +msgstr "Змеяў" + +#: Source/itemdat.cpp:301 +msgid "Serpent's" +msgstr "Вужаў" + +#: Source/itemdat.cpp:302 +msgid "Drake's" +msgstr "Смокаў" + +#: Source/itemdat.cpp:303 +msgid "Dragon's" +msgstr "Цмокаў" + +#: Source/itemdat.cpp:304 +msgid "Wyrm's" +msgstr "Гадскі" + +#: Source/itemdat.cpp:305 +msgid "Hydra's" +msgstr "Гідрын" + +#: Source/itemdat.cpp:306 +msgid "Angel's" +msgstr "Анёльскі" + +#: Source/itemdat.cpp:307 +msgid "Arch-Angel's" +msgstr "Арханёльскі" + +#: Source/itemdat.cpp:308 +msgid "Plentiful" +msgstr "Шчодры" + +#: Source/itemdat.cpp:309 +msgid "Bountiful" +msgstr "Дастатны" + +#: Source/itemdat.cpp:310 +msgid "Flaming" +msgstr "Агністы" + +#: Source/itemdat.cpp:311 +msgid "Lightning" +msgstr "Бліскавічны" + +#: Source/itemdat.cpp:312 +msgid "Jester's" +msgstr "Блазанскі" + +#: Source/itemdat.cpp:313 +msgid "Crystalline" +msgstr "Празрасты" + +#. TRANSLATORS: Item prefix section end. +#: Source/itemdat.cpp:315 +msgid "Doppelganger's" +msgstr "Двайніковы" + +#. TRANSLATORS: Item suffix section. All items will have a word binding word. (Format: {:s} of {:s} - e.g. Rags of Valor) +#: Source/itemdat.cpp:325 +msgid "quality" +msgstr "якасці" + +#: Source/itemdat.cpp:326 +msgid "maiming" +msgstr "нявечання" + +#: Source/itemdat.cpp:327 +msgid "slaying" +msgstr "забойства" + +#: Source/itemdat.cpp:328 +msgid "gore" +msgstr "запечанай крыві" + +#: Source/itemdat.cpp:329 +msgid "carnage" +msgstr "бойні" + +#: Source/itemdat.cpp:330 +msgid "slaughter" +msgstr "разні" + +#: Source/itemdat.cpp:331 +msgid "pain" +msgstr "болю" + +#: Source/itemdat.cpp:332 +msgid "tears" +msgstr "слёзаў" + +#: Source/itemdat.cpp:333 +msgid "health" +msgstr "здароўя" + +#: Source/itemdat.cpp:334 +msgid "protection" +msgstr "аховы" + +#: Source/itemdat.cpp:335 +msgid "absorption" +msgstr "паглынання" + +#: Source/itemdat.cpp:336 +msgid "deflection" +msgstr "адхілення" + +#: Source/itemdat.cpp:337 +msgid "osmosis" +msgstr "осмасу" + +#: Source/itemdat.cpp:338 +msgid "frailty" +msgstr "кволасці" + +#: Source/itemdat.cpp:339 +msgid "weakness" +msgstr "слабасці" + +#: Source/itemdat.cpp:340 +msgid "strength" +msgstr "моцы" + +#: Source/itemdat.cpp:341 +msgid "might" +msgstr "магутнасці" + +#: Source/itemdat.cpp:342 +msgid "power" +msgstr "сілы" + +#: Source/itemdat.cpp:343 +msgid "giants" +msgstr "волатаў" + +#: Source/itemdat.cpp:344 +msgid "titans" +msgstr "тытанаў" + +#: Source/itemdat.cpp:345 +msgid "paralysis" +msgstr "паралюшу" + +#: Source/itemdat.cpp:346 +msgid "atrophy" +msgstr "адмірання" + +#: Source/itemdat.cpp:347 +msgid "dexterity" +msgstr "спрыту" + +#: Source/itemdat.cpp:348 +msgid "skill" +msgstr "спраўнасці" + +#: Source/itemdat.cpp:349 +msgid "accuracy" +msgstr "дакладнасці" + +#: Source/itemdat.cpp:350 +msgid "precision" +msgstr "зладжанасці" + +#: Source/itemdat.cpp:351 +msgid "perfection" +msgstr "беззаганнасці" + +#: Source/itemdat.cpp:352 +msgid "the fool" +msgstr "дурня" + +#: Source/itemdat.cpp:353 +msgid "dyslexia" +msgstr "дыслексіі" + +#: Source/itemdat.cpp:354 +msgid "magic" +msgstr "магіі" + +#: Source/itemdat.cpp:355 +msgid "the mind" +msgstr "розуму" + +#: Source/itemdat.cpp:356 +msgid "brilliance" +msgstr "выдатнасці" + +#: Source/itemdat.cpp:357 +msgid "sorcery" +msgstr "вядзьмарства" + +#: Source/itemdat.cpp:358 +msgid "wizardry" +msgstr "чарадзейства" + +#: Source/itemdat.cpp:359 +msgid "illness" +msgstr "немачы" + +#: Source/itemdat.cpp:360 +msgid "disease" +msgstr "хваробы" + +#: Source/itemdat.cpp:361 +msgid "vitality" +msgstr "жывучасці" + +#: Source/itemdat.cpp:362 +msgid "zest" +msgstr "запалу" + +#: Source/itemdat.cpp:363 +msgid "vim" +msgstr "энергіі" + +#: Source/itemdat.cpp:364 +msgid "vigor" +msgstr "дужасці" + +#: Source/itemdat.cpp:365 +msgid "life" +msgstr "жыцця" + +#: Source/itemdat.cpp:366 +msgid "trouble" +msgstr "бяды" + +#: Source/itemdat.cpp:367 +msgid "the pit" +msgstr "ямы" + +#: Source/itemdat.cpp:368 +msgid "the sky" +msgstr "неба" + +#: Source/itemdat.cpp:369 +msgid "the moon" +msgstr "месяца" + +#: Source/itemdat.cpp:370 +msgid "the stars" +msgstr "зорак" + +#: Source/itemdat.cpp:371 +msgid "the heavens" +msgstr "нябёс" + +#: Source/itemdat.cpp:372 +msgid "the zodiac" +msgstr "задыяку" + +#: Source/itemdat.cpp:373 +msgid "the vulture" +msgstr "драпежніка" + +#: Source/itemdat.cpp:374 +msgid "the jackal" +msgstr "шакала" + +#: Source/itemdat.cpp:375 +msgid "the fox" +msgstr "ліса" + +#: Source/itemdat.cpp:376 +msgid "the jaguar" +msgstr "ягуара" + +#: Source/itemdat.cpp:377 +msgid "the eagle" +msgstr "арла" + +#: Source/itemdat.cpp:378 +msgid "the wolf" +msgstr "ваўка" + +#: Source/itemdat.cpp:379 +msgid "the tiger" +msgstr "тыгра" + +#: Source/itemdat.cpp:380 +msgid "the lion" +msgstr "льва" + +#: Source/itemdat.cpp:381 +msgid "the mammoth" +msgstr "маманта" + +#: Source/itemdat.cpp:382 +msgid "the whale" +msgstr "кіта" + +#: Source/itemdat.cpp:383 +msgid "fragility" +msgstr "кволасці" + +#: Source/itemdat.cpp:384 +msgid "brittleness" +msgstr "ломкасці" + +#: Source/itemdat.cpp:385 +msgid "sturdiness" +msgstr "непахіснасці" + +#: Source/itemdat.cpp:386 +msgid "craftsmanship" +msgstr "майстэрства" + +#: Source/itemdat.cpp:387 +msgid "structure" +msgstr "будовы" + +#: Source/itemdat.cpp:388 +msgid "the ages" +msgstr "вякоў" + +#: Source/itemdat.cpp:389 +msgid "the dark" +msgstr "цемры" + +#: Source/itemdat.cpp:390 +msgid "the night" +msgstr "ночы" + +#: Source/itemdat.cpp:391 +msgid "light" +msgstr "святла" + +#: Source/itemdat.cpp:392 +msgid "radiance" +msgstr "ззяння" + +#: Source/itemdat.cpp:393 +msgid "flame" +msgstr "полымя" + +#: Source/itemdat.cpp:394 +msgid "fire" +msgstr "агню" + +#: Source/itemdat.cpp:395 +msgid "burning" +msgstr "спякоты" + +#: Source/itemdat.cpp:396 +msgid "shock" +msgstr "шоку" + +#: Source/itemdat.cpp:397 +msgid "lightning" +msgstr "маланкі" + +#: Source/itemdat.cpp:398 +msgid "thunder" +msgstr "грымот" + +#: Source/itemdat.cpp:399 +msgid "many" +msgstr "шматлікіх" + +#: Source/itemdat.cpp:400 +msgid "plenty" +msgstr "мноства" + +#: Source/itemdat.cpp:401 +msgid "thorns" +msgstr "церняў" + +#: Source/itemdat.cpp:402 +msgid "corruption" +msgstr "скажэння" + +#: Source/itemdat.cpp:403 +msgid "thieves" +msgstr "зладзеяў" + +#: Source/itemdat.cpp:404 +msgid "the bear" +msgstr "мядзведзя" + +#: Source/itemdat.cpp:405 +msgid "the bat" +msgstr "кажана" + +#: Source/itemdat.cpp:406 +msgid "vampires" +msgstr "вупараў" + +#: Source/itemdat.cpp:407 +msgid "the leech" +msgstr "п'яўкі" + +#: Source/itemdat.cpp:408 +msgid "blood" +msgstr "крыві" + +#: Source/itemdat.cpp:409 +msgid "piercing" +msgstr "працінання" + +#: Source/itemdat.cpp:410 +msgid "puncturing" +msgstr "праколу" + +#: Source/itemdat.cpp:411 +msgid "bashing" +msgstr "збівання" + +#: Source/itemdat.cpp:412 +msgid "readiness" +msgstr "падрыхтаванасці" + +#: Source/itemdat.cpp:413 +msgid "swiftness" +msgstr "імклівасці" + +#: Source/itemdat.cpp:414 +msgid "speed" +msgstr "хуткасці" + +#: Source/itemdat.cpp:415 +msgid "haste" +msgstr "паспеху" + +#: Source/itemdat.cpp:416 +msgid "balance" +msgstr "раўнавагі" + +#: Source/itemdat.cpp:417 +msgid "stability" +msgstr "стойкасці" + +#: Source/itemdat.cpp:418 +msgid "harmony" +msgstr "суладнасці" + +#: Source/itemdat.cpp:419 +msgid "blocking" +msgstr "блакавання" + +#: Source/itemdat.cpp:420 +msgid "devastation" +msgstr "спустошанасці" + +#: Source/itemdat.cpp:421 +msgid "decay" +msgstr "гніцця" + +#. TRANSLATORS: Item suffix section end. +#: Source/itemdat.cpp:423 +msgid "peril" +msgstr "небяспекі" + +#. TRANSLATORS: Unique Item section +#: Source/itemdat.cpp:433 +msgid "The Butcher's Cleaver" +msgstr "Сякач Мясніка" + +#: Source/itemdat.cpp:443 +msgid "The Rift Bow" +msgstr "Лук разлому" + +#: Source/itemdat.cpp:444 +msgid "The Needler" +msgstr "Ігольшчык" + +#: Source/itemdat.cpp:445 +msgid "The Celestial Bow" +msgstr "Нябескі лук" + +#: Source/itemdat.cpp:446 +msgid "Deadly Hunter" +msgstr "Смяротны паляўнічы" + +#: Source/itemdat.cpp:447 +msgid "Bow of the Dead" +msgstr "Лук памерлых" + +#: Source/itemdat.cpp:448 +msgid "The Blackoak Bow" +msgstr "Лук з чорнага дубу" + +#: Source/itemdat.cpp:449 +msgid "Flamedart" +msgstr "Палымяністы дроцік" + +#: Source/itemdat.cpp:450 +msgid "Fleshstinger" +msgstr "Джганіплоць" + +#: Source/itemdat.cpp:451 +msgid "Windforce" +msgstr "Сіла ветру" + +#: Source/itemdat.cpp:452 +msgid "Eaglehorn" +msgstr "Рог арла" + +#: Source/itemdat.cpp:453 +msgid "Gonnagal's Dirk" +msgstr "Кордзік Гонагала" + +#: Source/itemdat.cpp:454 +msgid "The Defender" +msgstr "Абаронца" + +#: Source/itemdat.cpp:455 +msgid "Gryphon's Claw" +msgstr "Кіпцюр грыфона" + +#: Source/itemdat.cpp:456 +msgid "Black Razor" +msgstr "Чорная брытва" + +#: Source/itemdat.cpp:457 +msgid "Gibbous Moon" +msgstr "Позні маладзік" + +#: Source/itemdat.cpp:458 +msgid "Ice Shank" +msgstr "Лядзяны прэнт" + +#: Source/itemdat.cpp:459 +msgid "The Executioner's Blade" +msgstr "Катаў меч" + +#: Source/itemdat.cpp:460 +msgid "The Bonesaw" +msgstr "Касцярэз" + +#: Source/itemdat.cpp:461 +msgid "Shadowhawk" +msgstr "Ястраб ценю" + +#: Source/itemdat.cpp:462 +msgid "Wizardspike" +msgstr "Вастрыё чараўніка" + +#: Source/itemdat.cpp:463 +msgid "Lightsabre" +msgstr "Шабля святла" + +#: Source/itemdat.cpp:464 +msgid "The Falcon's Talon" +msgstr "Кіпцюр сокала" + +#: Source/itemdat.cpp:465 +msgid "Inferno" +msgstr "Апраметны" + +#: Source/itemdat.cpp:466 +msgid "Doombringer" +msgstr "Веснік пагібелі" + +#: Source/itemdat.cpp:467 +msgid "The Grizzly" +msgstr "Грызлі" + +#: Source/itemdat.cpp:468 +msgid "The Grandfather" +msgstr "Дзед" + +#: Source/itemdat.cpp:469 +msgid "The Mangler" +msgstr "Рубайла" + +#: Source/itemdat.cpp:470 +msgid "Sharp Beak" +msgstr "Вострая дзюба" + +#: Source/itemdat.cpp:471 +msgid "BloodSlayer" +msgstr "Крывалівень" + +#: Source/itemdat.cpp:472 +msgid "The Celestial Axe" +msgstr "Нябеская сякера" + +#: Source/itemdat.cpp:473 +msgid "Wicked Axe" +msgstr "Ліхая сякера" + +#: Source/itemdat.cpp:474 +msgid "Stonecleaver" +msgstr "Каменясек" + +#: Source/itemdat.cpp:475 +msgid "Aguinara's Hatchet" +msgstr "Сякерка Агінары" + +#: Source/itemdat.cpp:476 +msgid "Hellslayer" +msgstr "Пагібель аекла" + +#: Source/itemdat.cpp:477 +msgid "Messerschmidt's Reaver" +msgstr "Разаральнік Мэссершміта" + +#: Source/itemdat.cpp:478 +msgid "Crackrust" +msgstr "Іржатрэск" + +#: Source/itemdat.cpp:479 +msgid "Hammer of Jholm" +msgstr "Молат Джольма" + +#: Source/itemdat.cpp:480 +msgid "Civerb's Cudgel" +msgstr "Кій Сіверба" + +#: Source/itemdat.cpp:481 +msgid "The Celestial Star" +msgstr "Нябеская зорка" + +#: Source/itemdat.cpp:482 +msgid "Baranar's Star" +msgstr "Зорка Баранара" + +#: Source/itemdat.cpp:483 +msgid "Gnarled Root" +msgstr "Скрыўлены корань" + +#: Source/itemdat.cpp:484 +msgid "The Cranium Basher" +msgstr "Разбівальнік чарапоў" + +#: Source/itemdat.cpp:485 +msgid "Schaefer's Hammer" +msgstr "Молат Шэфера" + +#: Source/itemdat.cpp:486 +msgid "Dreamflange" +msgstr "Грэбень сноў" + +#: Source/itemdat.cpp:487 +msgid "Staff of Shadows" +msgstr "Посах ценяў" + +#: Source/itemdat.cpp:488 +msgid "Immolator" +msgstr "Спаліцель" + +#: Source/itemdat.cpp:489 +msgid "Storm Spire" +msgstr "Шпіль Буры" + +#: Source/itemdat.cpp:490 +msgid "Gleamsong" +msgstr "Спеў промня" + +#: Source/itemdat.cpp:491 +msgid "Thundercall" +msgstr "Вокліч грому" + +#: Source/itemdat.cpp:492 +msgid "The Protector" +msgstr "Заступнік" + +#: Source/itemdat.cpp:493 +msgid "Naj's Puzzler" +msgstr "Загваздка Наж" + +#: Source/itemdat.cpp:494 +msgid "Mindcry" +msgstr "Выкрык розуму" + +#: Source/itemdat.cpp:495 +msgid "Rod of Onan" +msgstr "Прут Онана" + +#: Source/itemdat.cpp:496 +msgid "Helm of Spirits" +msgstr "Шалом духаў" + +#: Source/itemdat.cpp:497 +msgid "Thinking Cap" +msgstr "Талковая шапка" + +#: Source/itemdat.cpp:498 +msgid "OverLord's Helm" +msgstr "Шалом усеўладара" + +#: Source/itemdat.cpp:499 +msgid "Fool's Crest" +msgstr "Грыва блазна" + +#: Source/itemdat.cpp:500 +msgid "Gotterdamerung" +msgstr "Змрок багоў" + +#: Source/itemdat.cpp:501 +msgid "Royal Circlet" +msgstr "Каралеўскі абруч" + +#: Source/itemdat.cpp:502 +msgid "Torn Flesh of Souls" +msgstr "Драная плоць душ" + +#: Source/itemdat.cpp:503 +msgid "The Gladiator's Bane" +msgstr "Згуба гладыятара" + +#: Source/itemdat.cpp:504 +msgid "The Rainbow Cloak" +msgstr "Плашч вясёлкі" + +#: Source/itemdat.cpp:505 +msgid "Leather of Aut" +msgstr "Скура Ота" + +#: Source/itemdat.cpp:506 +msgid "Wisdom's Wrap" +msgstr "Шаль мудрасці" + +#: Source/itemdat.cpp:507 +msgid "Sparking Mail" +msgstr "Іскрыстая кальчуга" + +#: Source/itemdat.cpp:508 +msgid "Scavenger Carapace" +msgstr "Панцыр трупаеда" + +#: Source/itemdat.cpp:509 +msgid "Nightscape" +msgstr "Накідка ночы" + +#: Source/itemdat.cpp:510 +msgid "Naj's Light Plate" +msgstr "Лёгкія латы Наж" + +#: Source/itemdat.cpp:511 +msgid "Demonspike Coat" +msgstr "Курта чортавых шыпоў" + +#: Source/itemdat.cpp:512 +msgid "The Deflector" +msgstr "Адхіляльнік" + +#: Source/itemdat.cpp:513 +msgid "Split Skull Shield" +msgstr "Шчыт з расколатага чэрапа" + +#: Source/itemdat.cpp:514 +msgid "Dragon's Breach" +msgstr "Цмокава адтуліна" + +#: Source/itemdat.cpp:515 +msgid "Blackoak Shield" +msgstr "Шчыт з чорнага дубу" + +#: Source/itemdat.cpp:516 +msgid "Holy Defender" +msgstr "Святы абаронца" + +#: Source/itemdat.cpp:517 +msgid "Stormshield" +msgstr "Шчыт буры" + +#: Source/itemdat.cpp:518 +msgid "Bramble" +msgstr "Ажына" + +#: Source/itemdat.cpp:519 +msgid "Ring of Regha" +msgstr "Пярсцёнак Рэгі" + +#: Source/itemdat.cpp:520 +msgid "The Bleeder" +msgstr "Крывацёк" + +#: Source/itemdat.cpp:521 +msgid "Constricting Ring" +msgstr "Сціскальны пярсцёнак" + +#: Source/itemdat.cpp:522 +msgid "Ring of Engagement" +msgstr "Заручальны пярсцёнак" + +#: Source/itemdat.cpp:523 +msgid "Giant's Knuckle" +msgstr "Костка велікалюда" + +#: Source/itemdat.cpp:524 +msgid "Mercurial Ring" +msgstr "Пярсцёнак Меркурыя" + +#: Source/itemdat.cpp:525 +msgid "Xorine's Ring" +msgstr "Пярсцёнак Ксорына" + +#: Source/itemdat.cpp:526 +msgid "Karik's Ring" +msgstr "Пярсцёнак Карыка" + +#: Source/itemdat.cpp:527 +msgid "Ring of Magma" +msgstr "Пярсцёнак магмы" + +#: Source/itemdat.cpp:528 +msgid "Ring of the Mystics" +msgstr "Пярсцёнак містыкі" + +#: Source/itemdat.cpp:529 +msgid "Ring of Thunder" +msgstr "Пярсцёнак грому" + +#: Source/itemdat.cpp:530 +msgid "Amulet of Warding" +msgstr "Амулет аховы" + +#: Source/itemdat.cpp:531 +msgid "Gnat Sting" +msgstr "Джала мошкі" + +#: Source/itemdat.cpp:532 +msgid "Flambeau" +msgstr "Светач" + +#: Source/itemdat.cpp:533 +msgid "Armor of Gloom" +msgstr "Даспех цямнэчы" + +#: Source/itemdat.cpp:534 +msgid "Blitzen" +msgstr "Бляск" + +#: Source/itemdat.cpp:535 +msgid "Thunderclap" +msgstr "Пярун" + +#: Source/itemdat.cpp:536 +msgid "Shirotachi" +msgstr "Шыратачы" + +#: Source/itemdat.cpp:537 +msgid "Eater of Souls" +msgstr "Пажыральнік душ" + +#: Source/itemdat.cpp:538 +msgid "Diamondedge" +msgstr "Алмазнае лязо" + +#: Source/itemdat.cpp:539 +msgid "Bone Chain Armor" +msgstr "Касцяная кальчуга" + +#: Source/itemdat.cpp:540 +msgid "Demon Plate Armor" +msgstr "Латы дэмана" + +#: Source/itemdat.cpp:541 +msgid "Acolyte's Amulet" +msgstr "Амулет прыслужніка" + +#. TRANSLATORS: Unique Item section end. +#: Source/itemdat.cpp:543 +msgid "Gladiator's Ring" +msgstr "Пярсцёнак гладыятара" + +#: Source/items.cpp:168 +msgid "Oil of Mastery" +msgstr "Алей улады" + +#: Source/items.cpp:170 +msgid "Oil of Death" +msgstr "Алей смерці" + +#: Source/items.cpp:171 +msgid "Oil of Skill" +msgstr "Алей спраўнасці" + +#: Source/items.cpp:173 +msgid "Oil of Fortitude" +msgstr "Алей трываласці" + +#: Source/items.cpp:174 +msgid "Oil of Permanence" +msgstr "Алей нязменнасці" + +#: Source/items.cpp:175 +msgid "Oil of Hardening" +msgstr "Алей гарту" + +#: Source/items.cpp:176 +msgid "Oil of Imperviousness" +msgstr "Алей непрабіўнасці" + +#. TRANSLATORS: Constructs item names. Format: {Item} of {Spell}. Example: War Staff of Firewall +#: Source/items.cpp:1149 +msgctxt "spell" +msgid "{0} of {1}" +msgstr "{0} {1}" + +#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Spell}. Example: King's War Staff of Firewall +#: Source/items.cpp:1157 +msgctxt "spell" +msgid "{0} {1} of {2}" +msgstr "{0} {1} {2}" + +#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Suffix}. Example: King's Long Sword of the Whale +#: Source/items.cpp:1175 +msgid "{0} {1} of {2}" +msgstr "{0} {1} {2}" + +#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item}. Example: King's Long Sword +#: Source/items.cpp:1178 +msgid "{0} {1}" +msgstr "{0} {1}" + +#. TRANSLATORS: Constructs item names. Format: {Item} of {Suffix}. Example: Long Sword of the Whale +#: Source/items.cpp:1181 +msgid "{0} of {1}" +msgstr "{0} {1}" + +#: Source/items.cpp:1716 Source/items.cpp:1724 +msgid "increases a weapon's" +msgstr "павялічвае зброі" + +#: Source/items.cpp:1717 +msgid "chance to hit" +msgstr "шанец трапіць" + +#: Source/items.cpp:1720 +msgid "greatly increases a" +msgstr "моцна павялічвае" + +#: Source/items.cpp:1721 +msgid "weapon's chance to hit" +msgstr "шанец зброі трапіць" + +#: Source/items.cpp:1725 +msgid "damage potential" +msgstr "патэнцыял шкоды" + +#: Source/items.cpp:1728 +msgid "greatly increases a weapon's" +msgstr "моцна павялічвае зброі" + +#: Source/items.cpp:1729 +msgid "damage potential - not bows" +msgstr "патэнцыял шкоды – не для лукаў" + +#: Source/items.cpp:1732 +msgid "reduces attributes needed" +msgstr "змяншае неабходныя атрыбуты" + +#: Source/items.cpp:1733 +msgid "to use armor or weapons" +msgstr "для карыстання брані ці зброі" + +#: Source/items.cpp:1736 +#, no-c-format +msgid "restores 20% of an" +msgstr "аднаўляе 20%" + +#: Source/items.cpp:1737 +msgid "item's durability" +msgstr "мацунак рэчы" + +#: Source/items.cpp:1740 +msgid "increases an item's" +msgstr "павялічвае рэчы" + +#: Source/items.cpp:1741 +msgid "current and max durability" +msgstr "цяперашні ды максімальны мацунак" + +#: Source/items.cpp:1744 +msgid "makes an item indestructible" +msgstr "робіць рэч незнішчальнаю" + +#: Source/items.cpp:1747 +msgid "increases the armor class" +msgstr "павялічвае клас брані" + +#: Source/items.cpp:1748 +msgid "of armor and shields" +msgstr "брані і шчытоў" + +#: Source/items.cpp:1751 +msgid "greatly increases the armor" +msgstr "моцна павялічвае брані" + +#: Source/items.cpp:1752 +msgid "class of armor and shields" +msgstr "клас даспехаў ды шчытоў" + +#: Source/items.cpp:1755 Source/items.cpp:1762 +msgid "sets fire trap" +msgstr "ставіць вогненную пастку" + +#: Source/items.cpp:1759 +msgid "sets lightning trap" +msgstr "ставіць маланкавую пастку" + +#: Source/items.cpp:1765 +msgid "sets petrification trap" +msgstr "ставіць пастку акамянення" + +#: Source/items.cpp:1768 +msgid "restore all life" +msgstr "аднавіць ўсё жыццё" + +#: Source/items.cpp:1771 +msgid "restore some life" +msgstr "аднавіць трохі жыцця" + +#: Source/items.cpp:1774 +msgid "recover life" +msgstr "вярнуць жыццё" + +#: Source/items.cpp:1777 +msgid "deadly heal" +msgstr "смяротнае лячэнне" + +#: Source/items.cpp:1780 +msgid "restore some mana" +msgstr "аднавіць трохі маны" + +#: Source/items.cpp:1783 +msgid "restore all mana" +msgstr "аднавіць усю ману" + +#: Source/items.cpp:1786 +msgid "increase strength" +msgstr "павялічыць моц" + +#: Source/items.cpp:1789 +msgid "increase magic" +msgstr "павялічыць магію" + +#: Source/items.cpp:1792 +msgid "increase dexterity" +msgstr "павялічыць спрыт" + +#: Source/items.cpp:1795 +msgid "increase vitality" +msgstr "павялічыць жывучасць" + +#: Source/items.cpp:1799 +msgid "decrease strength" +msgstr "паменшыць моц" + +#: Source/items.cpp:1802 +msgid "decrease dexterity" +msgstr "паменшыць спрыт" + +#: Source/items.cpp:1805 +msgid "decrease vitality" +msgstr "паменшыць жывучасць" + +#: Source/items.cpp:1808 +msgid "restore some life and mana" +msgstr "аднавіць трохі жыцця ды маны" + +#: Source/items.cpp:1811 +msgid "restore all life and mana" +msgstr "аднавіць ўсё жыццё ды ману" + +#: Source/items.cpp:1826 Source/items.cpp:1866 +msgid "Right-click to read" +msgstr "Націснуць правай кнопкай мышы каб прачытаць" + +#: Source/items.cpp:1829 Source/items.cpp:1844 Source/items.cpp:1858 +msgid "Open inventory to use" +msgstr "Адчыніце інвентар каб выкараставаць" + +#: Source/items.cpp:1831 Source/items.cpp:1846 Source/items.cpp:1868 +msgid "Activate to read" +msgstr "Актывізуйце каб прачытаць" + +#: Source/items.cpp:1837 +msgid "Right-click to read, then" +msgstr "Націснуць правай кнопкай мышы каб прачытаць, пасля" + +#: Source/items.cpp:1838 +msgid "left-click to target" +msgstr "націснуць левай кнопкай мышы па цэлі" + +#: Source/items.cpp:1841 +msgid "Select from spell book, then" +msgstr "Выберыце з кнігі чараў, пасля" + +#: Source/items.cpp:1842 +msgid "cast spell to read" +msgstr "накладзіце чары каб прачытаць" + +#: Source/items.cpp:1855 +msgid "Right-click to use" +msgstr "Правай кнопкай мышы каб выкараставаць" + +#: Source/items.cpp:1860 +msgid "Activate to use" +msgstr "Актывізуйце каб выкараставаць" + +#: Source/items.cpp:1873 +msgid "Right-click to view" +msgstr "Правай кнопкай мышы каб пабачыць" + +#: Source/items.cpp:1875 +msgid "Activate to view" +msgstr "Актывуйце каб пабачыць" + +#: Source/items.cpp:1879 +msgctxt "player" +msgid "Level: {:d}" +msgstr "Узровень {:d}" + +#: Source/items.cpp:1882 +msgid "Doubles gold capacity" +msgstr "Падвойвае ёмістасць золата" + +#: Source/items.cpp:1893 Source/stores.cpp:282 +msgid "Required:" +msgstr "Патрэбна:" + +#: Source/items.cpp:1895 Source/stores.cpp:284 +msgid " {:d} Str" +msgstr " {:d} Моц" + +#: Source/items.cpp:1897 Source/stores.cpp:286 +msgid " {:d} Mag" +msgstr " {:d} Маг" + +#: Source/items.cpp:1899 Source/stores.cpp:288 +msgid " {:d} Dex" +msgstr " {:d} Спрт" + +#. TRANSLATORS: {:s} will be a Character Name +#: Source/items.cpp:3256 Source/player.cpp:3145 +msgid "Ear of {:s}" +msgstr "Вуха {:s}" + +#: Source/items.cpp:3552 +msgid "chance to hit: {:+d}%" +msgstr "шанец трапіць: {:+d}%" + +#: Source/items.cpp:3555 +#, no-c-format +msgid "{:+d}% damage" +msgstr "{:+d}% шкоды" + +#: Source/items.cpp:3558 Source/items.cpp:3759 +msgid "to hit: {:+d}%, {:+d}% damage" +msgstr "трапіць: {:+d}%, {:+d}% шкоды" + +#: Source/items.cpp:3561 +#, no-c-format +msgid "{:+d}% armor" +msgstr "{:+d}% брані" + +#: Source/items.cpp:3564 +msgid "armor class: {:d}" +msgstr "клас даспехаў: {:d}" + +#: Source/items.cpp:3568 Source/items.cpp:3745 +msgid "Resist Fire: {:+d}%" +msgstr "Супраціў агню: {:+d}%" + +#: Source/items.cpp:3570 +msgid "Resist Fire: {:+d}% MAX" +msgstr "Супраціў агню: {:+d}% МАКС" + +#: Source/items.cpp:3574 +msgid "Resist Lightning: {:+d}%" +msgstr "Супраціў маланцы: {:+d}%" + +#: Source/items.cpp:3576 +msgid "Resist Lightning: {:+d}% MAX" +msgstr "Супраціў маланцы: {:+d}% МАКС" + +#: Source/items.cpp:3580 +msgid "Resist Magic: {:+d}%" +msgstr "Супраціў магіі: {:+d}%" + +#: Source/items.cpp:3582 +msgid "Resist Magic: {:+d}% MAX" +msgstr "Супраціў магіі: {:+d}% МАКС" + +#: Source/items.cpp:3586 +msgid "Resist All: {:+d}%" +msgstr "Супраціў усяму: {:+d}%" + +#: Source/items.cpp:3588 +msgid "Resist All: {:+d}% MAX" +msgstr "Супраціў усяму: {:+d}% МАКС" + +#: Source/items.cpp:3591 +msgid "spells are increased {:d} level" +msgid_plural "spells are increased {:d} levels" +msgstr[0] "чары павышаныя на {:d} узровень" +msgstr[1] "чары павашанныя на {:d} ўзроўні" +msgstr[2] "чары павышаныя на {:d} узроўнюў" + +#: Source/items.cpp:3593 +msgid "spells are decreased {:d} level" +msgid_plural "spells are decreased {:d} levels" +msgstr[0] "чары паменшаныя на {:d} узровень" +msgstr[1] "чары паменьшаныя на {:d} узроўні" +msgstr[2] "чары паменьшаныя на {:d} узроўнюў" + +#: Source/items.cpp:3595 +msgid "spell levels unchanged (?)" +msgstr "узроўні чараў нязменныя (?)" + +#: Source/items.cpp:3597 +msgid "Extra charges" +msgstr "Дадатковыя зарады" + +#: Source/items.cpp:3599 +msgid "{:d} {:s} charge" +msgid_plural "{:d} {:s} charges" +msgstr[0] "{:d} {:s} зарад" +msgstr[1] "{:d} {:s} зарады" +msgstr[2] "{:d} {:s} зарадаў" + +#: Source/items.cpp:3602 +msgid "Fire hit damage: {:d}" +msgstr "Шкода агнём: {:d}" + +#: Source/items.cpp:3604 +msgid "Fire hit damage: {:d}-{:d}" +msgstr "Шкода агнём: {:d}-{:d}" + +#: Source/items.cpp:3607 +msgid "Lightning hit damage: {:d}" +msgstr "Шкода маланкаю: {:d}" + +#: Source/items.cpp:3609 +msgid "Lightning hit damage: {:d}-{:d}" +msgstr "Шкода маланкаю: {:d}-{:d}" + +#: Source/items.cpp:3612 +msgid "{:+d} to strength" +msgstr "{:+d} да моцы" + +#: Source/items.cpp:3615 +msgid "{:+d} to magic" +msgstr "{:+d} да магіі" + +#: Source/items.cpp:3618 +msgid "{:+d} to dexterity" +msgstr "{:+d} да спрыту" + +#: Source/items.cpp:3621 +msgid "{:+d} to vitality" +msgstr "{:+d} да жывучасці" + +#: Source/items.cpp:3624 +msgid "{:+d} to all attributes" +msgstr "{:+d} да ўсіх атрыбутаў" + +#: Source/items.cpp:3627 +msgid "{:+d} damage from enemies" +msgstr "{:+d} шкоды ад ворагаў" + +#: Source/items.cpp:3630 +msgid "Hit Points: {:+d}" +msgstr "Здароўе: {:+d}" + +#: Source/items.cpp:3633 +msgid "Mana: {:+d}" +msgstr "Мана: {:+d}" + +#: Source/items.cpp:3635 +msgid "high durability" +msgstr "вялікі мацунак" + +#: Source/items.cpp:3637 +msgid "decreased durability" +msgstr "паменшаны мацунак" + +#: Source/items.cpp:3639 +msgid "indestructible" +msgstr "незнішчальны" + +#: Source/items.cpp:3641 +#, no-c-format +msgid "+{:d}% light radius" +msgstr "+{:d}% радыуса святла" + +#: Source/items.cpp:3643 +#, no-c-format +msgid "-{:d}% light radius" +msgstr "-{:d}% радыуса святла" + +#: Source/items.cpp:3645 +msgid "multiple arrows per shot" +msgstr "некалькі стрэл за адзін стрэл" + +#: Source/items.cpp:3648 +msgid "fire arrows damage: {:d}" +msgstr "шкода вогненнымі стрэламі: {:d}" + +#: Source/items.cpp:3650 +msgid "fire arrows damage: {:d}-{:d}" +msgstr "шкода вогненнымі стрэламі: {:d}-{:d}" + +#: Source/items.cpp:3653 +msgid "lightning arrows damage {:d}" +msgstr "шкода маланкавымі стрэламі: {:d}" + +#: Source/items.cpp:3655 +msgid "lightning arrows damage {:d}-{:d}" +msgstr "шкода маланкавымі стрэламі: {:d}-{:d}" + +#: Source/items.cpp:3658 +msgid "fireball damage: {:d}" +msgstr "шкода вогненным шарам: {:d}" + +#: Source/items.cpp:3660 +msgid "fireball damage: {:d}-{:d}" +msgstr "шкода вогненным шарам: {:d}-{:d}" + +#: Source/items.cpp:3662 +msgid "attacker takes 1-3 damage" +msgstr "нападаючы атрымлівае 1-3 шкоды" + +#: Source/items.cpp:3664 +msgid "user loses all mana" +msgstr "карыстальнік страчвае ўсю ману" + +#: Source/items.cpp:3666 +msgid "you can't heal" +msgstr "не можаце лячыцца" + +#: Source/items.cpp:3668 +msgid "absorbs half of trap damage" +msgstr "паглынае палову шкоды пасткі" + +#: Source/items.cpp:3670 +msgid "knocks target back" +msgstr "адкідае цэль узад" + +#: Source/items.cpp:3672 +#, no-c-format +msgid "+200% damage vs. demons" +msgstr "+200% шкоды супраць дэманаў" + +#: Source/items.cpp:3674 +msgid "All Resistance equals 0" +msgstr "Увесь супраціў роўны 0" + +#: Source/items.cpp:3676 +msgid "hit monster doesn't heal" +msgstr "удар па пачвары не лечыць" + +#: Source/items.cpp:3679 +#, no-c-format +msgid "hit steals 3% mana" +msgstr "удар крадзе 3% маны" + +#: Source/items.cpp:3681 +#, no-c-format +msgid "hit steals 5% mana" +msgstr "удар крадзе 5% маны" + +#: Source/items.cpp:3685 +#, no-c-format +msgid "hit steals 3% life" +msgstr "удар крадзе 3% жыцця" + +#: Source/items.cpp:3687 +#, no-c-format +msgid "hit steals 5% life" +msgstr "удар крадзе 5% жыцця" + +#: Source/items.cpp:3691 Source/items.cpp:3693 +msgid "penetrates target's armor" +msgstr "прабівае браню цэлі" + +#: Source/items.cpp:3696 +msgid "quick attack" +msgstr "хуткі напад" + +#: Source/items.cpp:3698 +msgid "fast attack" +msgstr "шпаркі напад" + +#: Source/items.cpp:3700 +msgid "faster attack" +msgstr "шпарчэйшы напад" + +#: Source/items.cpp:3702 +msgid "fastest attack" +msgstr "найшпарчэйшы напад" + +#: Source/items.cpp:3703 Source/items.cpp:3711 Source/items.cpp:3769 +msgid "Another ability (NW)" +msgstr "Іншая здольнасць (NW)" + +#: Source/items.cpp:3706 +msgid "fast hit recovery" +msgstr "хуткае ачуньванне па ўдары" + +#: Source/items.cpp:3708 +msgid "faster hit recovery" +msgstr "хутчэйшае ачуньванне па ўдары" + +#: Source/items.cpp:3710 +msgid "fastest hit recovery" +msgstr "найхутчэйшае ачуньванне па ўдары" + +#: Source/items.cpp:3713 +msgid "fast block" +msgstr "хуткі блок" + +#: Source/items.cpp:3715 +msgid "adds {:d} point to damage" +msgid_plural "adds {:d} points to damage" +msgstr[0] "дадае {:d} ачко шкодзе" +msgstr[1] "дадае {:d} ачкі шкодзе" +msgstr[2] "дадае {:d} ачкоў шкодзе" + +#: Source/items.cpp:3717 +msgid "fires random speed arrows" +msgstr "пускае некалькі хуткіх стрэл" + +#: Source/items.cpp:3719 +msgid "unusual item damage" +msgstr "незвычайная шкода рэчай" + +#: Source/items.cpp:3721 +msgid "altered durability" +msgstr "зменены мацунак" + +#: Source/items.cpp:3723 +msgid "Faster attack swing" +msgstr "Узмах шпарчэйшай атакі" + +#: Source/items.cpp:3725 +msgid "one handed sword" +msgstr "аднаручны меч" + +#: Source/items.cpp:3727 +msgid "constantly lose hit points" +msgstr "безупынна губляць здароўе" + +#: Source/items.cpp:3729 +msgid "life stealing" +msgstr "крадзеж жыцця" + +#: Source/items.cpp:3731 +msgid "no strength requirement" +msgstr "няма патрабавання моцы" + +#: Source/items.cpp:3733 +msgid "see with infravision" +msgstr "глядзець скрозьбачаннем" + +#: Source/items.cpp:3738 +msgid "lightning damage: {:d}" +msgstr "шкода маланкаю: {:d}" + +#: Source/items.cpp:3740 +msgid "lightning damage: {:d}-{:d}" +msgstr "шкода маланкаю: {:d}-{:d}" + +#: Source/items.cpp:3742 +msgid "charged bolts on hits" +msgstr "выпускае зараджаныя стрэлы пры ўдарах" + +#: Source/items.cpp:3749 +msgid "occasional triple damage" +msgstr "выпадковая патройная шкода" + +#: Source/items.cpp:3751 +#, no-c-format +msgid "decaying {:+d}% damage" +msgstr "занепадае {:+d}% шкоды" + +#: Source/items.cpp:3753 +msgid "2x dmg to monst, 1x to you" +msgstr "2х шкд пачвр, 1х вам" + +#: Source/items.cpp:3755 +#, no-c-format +msgid "Random 0 - 600% damage" +msgstr "Выпадковая 0 – 600% шкоды" + +#: Source/items.cpp:3757 +#, no-c-format +msgid "low dur, {:+d}% damage" +msgstr "нізкі спрыт, {:+d}% шкоды" + +#: Source/items.cpp:3761 +msgid "extra AC vs demons" +msgstr "дадат. абарона ад дэманаў" + +#: Source/items.cpp:3763 +msgid "extra AC vs undead" +msgstr "дадат. абарона ад наўцоў" + +#: Source/items.cpp:3765 +msgid "50% Mana moved to Health" +msgstr "50% маны перайшло ў Здароўе" + +#: Source/items.cpp:3767 +msgid "40% Health moved to Mana" +msgstr "40% Здароўя перайшло ў Ману" + +#: Source/items.cpp:3804 Source/items.cpp:3842 +msgid "damage: {:d} Indestructible" +msgstr "шкода {:d} Незнішчальна" + +#. TRANSLATORS: Dur: is durability +#: Source/items.cpp:3806 Source/items.cpp:3844 +msgid "damage: {:d} Dur: {:d}/{:d}" +msgstr "шкода {:d} мац: {:d}/{:d}" + +#: Source/items.cpp:3809 Source/items.cpp:3847 +msgid "damage: {:d}-{:d} Indestructible" +msgstr "шкода {:d}-{:d} Незнішчальна" + +#. TRANSLATORS: Dur: is durability +#: Source/items.cpp:3811 Source/items.cpp:3849 +msgid "damage: {:d}-{:d} Dur: {:d}/{:d}" +msgstr "шкода: {:d}-{:d} мац: {:d}/{:d}" + +#: Source/items.cpp:3816 Source/items.cpp:3859 +msgid "armor: {:d} Indestructible" +msgstr "браня: {:d} Незнішчальна" + +#. TRANSLATORS: Dur: is durability +#: Source/items.cpp:3818 Source/items.cpp:3861 +msgid "armor: {:d} Dur: {:d}/{:d}" +msgstr "браня: {:d} мац: {:d}/{:d}" + +#: Source/items.cpp:3821 Source/items.cpp:3852 Source/items.cpp:3865 +#: Source/stores.cpp:256 +msgid "Charges: {:d}/{:d}" +msgstr "Зарады: {:d}/{:d}" + +#: Source/items.cpp:3830 +msgid "unique item" +msgstr "унікальная рэч" + +#: Source/items.cpp:3855 Source/items.cpp:3863 Source/items.cpp:3869 +msgid "Not Identified" +msgstr "Не выяўлена" + +#: Source/loadsave.cpp:1870 Source/loadsave.cpp:2388 +msgid "Unable to open save file archive" +msgstr "Немагчыма адкрыць архіў захаванняў" + +#: Source/loadsave.cpp:1873 +msgid "Invalid save file" +msgstr "Няправільны файл захавання" + +#: Source/loadsave.cpp:1904 +msgid "Player is on a Hellfire only level" +msgstr "Гулец толькі на ўзроўні Hellfire" + +#: Source/loadsave.cpp:2150 +msgid "Invalid game state" +msgstr "Няправільны стан гульні" + +#: Source/menu.cpp:149 +msgid "Unable to display mainmenu" +msgstr "Няма як паказаць галоўнае меню" + +#. TRANSLATORS: Monster Block start +#. MT_NZOMBIE +#: Source/monstdat.cpp:20 +msgctxt "monster" +msgid "Zombie" +msgstr "Зомбі" + +#: Source/monstdat.cpp:21 +msgctxt "monster" +msgid "Ghoul" +msgstr "Гуль" + +#: Source/monstdat.cpp:22 +msgctxt "monster" +msgid "Rotting Carcass" +msgstr "Гнілая туша" + +#: Source/monstdat.cpp:23 +msgctxt "monster" +msgid "Black Death" +msgstr "Чорная смерць" + +#: Source/monstdat.cpp:24 Source/monstdat.cpp:32 +msgctxt "monster" +msgid "Fallen One" +msgstr "Загінулы" + +#: Source/monstdat.cpp:25 Source/monstdat.cpp:33 +msgctxt "monster" +msgid "Carver" +msgstr "Рэзчык" + +#: Source/monstdat.cpp:26 Source/monstdat.cpp:34 +msgctxt "monster" +msgid "Devil Kin" +msgstr "Нячысцік" + +#: Source/monstdat.cpp:27 Source/monstdat.cpp:35 +msgctxt "monster" +msgid "Dark One" +msgstr "Цёмны" + +#: Source/monstdat.cpp:28 Source/monstdat.cpp:40 +msgctxt "monster" +msgid "Skeleton" +msgstr "Шкілет" + +#: Source/monstdat.cpp:29 +msgctxt "monster" +msgid "Corpse Axe" +msgstr "Мёртвы сякернік" + +#: Source/monstdat.cpp:30 Source/monstdat.cpp:42 +msgctxt "monster" +msgid "Burning Dead" +msgstr "Палымнеючы мрэц" + +#: Source/monstdat.cpp:31 Source/monstdat.cpp:43 +msgctxt "monster" +msgid "Horror" +msgstr "Жах" + +#: Source/monstdat.cpp:36 +msgctxt "monster" +msgid "Scavenger" +msgstr "Трупаед" + +#: Source/monstdat.cpp:37 +msgctxt "monster" +msgid "Plague Eater" +msgstr "Чумаед" + +#: Source/monstdat.cpp:38 +msgctxt "monster" +msgid "Shadow Beast" +msgstr "Звер цемры" + +#: Source/monstdat.cpp:39 +msgctxt "monster" +msgid "Bone Gasher" +msgstr "Касцяны разрэзак" + +#: Source/monstdat.cpp:41 +msgctxt "monster" +msgid "Corpse Bow" +msgstr "Мёртвы лучнік" + +#: Source/monstdat.cpp:44 +msgctxt "monster" +msgid "Skeleton Captain" +msgstr "Шкілет капітана" + +#: Source/monstdat.cpp:45 +msgctxt "monster" +msgid "Corpse Captain" +msgstr "Труп капітана" + +#: Source/monstdat.cpp:46 +msgctxt "monster" +msgid "Burning Dead Captain" +msgstr "Палымнеючы мёртвы капітан" + +#: Source/monstdat.cpp:47 +msgctxt "monster" +msgid "Horror Captain" +msgstr "Жахлівы капітан" + +#: Source/monstdat.cpp:48 +msgctxt "monster" +msgid "Invisible Lord" +msgstr "Нябачны ўладар" + +#: Source/monstdat.cpp:49 +msgctxt "monster" +msgid "Hidden" +msgstr "Схаваны" + +#: Source/monstdat.cpp:50 +msgctxt "monster" +msgid "Stalker" +msgstr "Пераследнік" + +#: Source/monstdat.cpp:51 +msgctxt "monster" +msgid "Unseen" +msgstr "Нябачны" + +#: Source/monstdat.cpp:52 +msgctxt "monster" +msgid "Illusion Weaver" +msgstr "Ткач ілюзій" + +#: Source/monstdat.cpp:53 +msgctxt "monster" +msgid "Satyr Lord" +msgstr "Валадар сатыраў" + +#: Source/monstdat.cpp:54 Source/monstdat.cpp:62 +msgctxt "monster" +msgid "Flesh Clan" +msgstr "Клан плоці" + +#: Source/monstdat.cpp:55 Source/monstdat.cpp:63 +msgctxt "monster" +msgid "Stone Clan" +msgstr "Клан каменя" + +#: Source/monstdat.cpp:56 Source/monstdat.cpp:64 +msgctxt "monster" +msgid "Fire Clan" +msgstr "Клан полымя" + +#: Source/monstdat.cpp:57 Source/monstdat.cpp:65 +msgctxt "monster" +msgid "Night Clan" +msgstr "Клан ночы" + +#: Source/monstdat.cpp:58 +msgctxt "monster" +msgid "Fiend" +msgstr "Нячысты" + +#: Source/monstdat.cpp:59 +msgctxt "monster" +msgid "Blink" +msgstr "Мірг" + +#: Source/monstdat.cpp:60 +msgctxt "monster" +msgid "Gloom" +msgstr "Морак" + +#: Source/monstdat.cpp:61 +msgctxt "monster" +msgid "Familiar" +msgstr "Фамільяр" + +#: Source/monstdat.cpp:66 +msgctxt "monster" +msgid "Acid Beast" +msgstr "Звер кіслаты" + +#: Source/monstdat.cpp:67 +msgctxt "monster" +msgid "Poison Spitter" +msgstr "Ядаплюй" + +#: Source/monstdat.cpp:68 +msgctxt "monster" +msgid "Pit Beast" +msgstr "Звер з ямы" + +#: Source/monstdat.cpp:69 +msgctxt "monster" +msgid "Lava Maw" +msgstr "Лававая ляпа" + +#: Source/monstdat.cpp:70 Source/monstdat.cpp:474 +msgctxt "monster" +msgid "Skeleton King" +msgstr "Кароль шкілетаў" + +#: Source/monstdat.cpp:71 Source/monstdat.cpp:482 +msgctxt "monster" +msgid "The Butcher" +msgstr "Мяснік" + +#: Source/monstdat.cpp:72 +msgctxt "monster" +msgid "Overlord" +msgstr "Усеўладар" + +#: Source/monstdat.cpp:73 +msgctxt "monster" +msgid "Mud Man" +msgstr "Брыда" + +#: Source/monstdat.cpp:74 +msgctxt "monster" +msgid "Toad Demon" +msgstr "Чорт-рапуха" + +#: Source/monstdat.cpp:75 +msgctxt "monster" +msgid "Flayed One" +msgstr "Залупцаваны" + +#: Source/monstdat.cpp:76 +msgctxt "monster" +msgid "Wyrm" +msgstr "Змей" + +#: Source/monstdat.cpp:77 +msgctxt "monster" +msgid "Cave Slug" +msgstr "Пячорны смоўж" + +#: Source/monstdat.cpp:78 +msgctxt "monster" +msgid "Devil Wyrm" +msgstr "Чортаў Змей" + +#: Source/monstdat.cpp:79 +msgctxt "monster" +msgid "Devourer" +msgstr "Паглынальнік" + +#: Source/monstdat.cpp:80 +msgctxt "monster" +msgid "Magma Demon" +msgstr "Дэман магмы" + +#: Source/monstdat.cpp:81 +msgctxt "monster" +msgid "Blood Stone" +msgstr "Камень крыві" + +#: Source/monstdat.cpp:82 +msgctxt "monster" +msgid "Hell Stone" +msgstr "Камень пекла" + +#: Source/monstdat.cpp:83 +msgctxt "monster" +msgid "Lava Lord" +msgstr "Валадар лавы" + +#: Source/monstdat.cpp:84 +msgctxt "monster" +msgid "Horned Demon" +msgstr "Чорт рагаты" + +#: Source/monstdat.cpp:85 +msgctxt "monster" +msgid "Mud Runner" +msgstr "Брудны бягун" + +#: Source/monstdat.cpp:86 +msgctxt "monster" +msgid "Frost Charger" +msgstr "Ледзяны нападнік" + +#: Source/monstdat.cpp:87 +msgctxt "monster" +msgid "Obsidian Lord" +msgstr "Абсідыянавы ўладар" + +#: Source/monstdat.cpp:88 +msgctxt "monster" +msgid "oldboned" +msgstr "трухлявыя косці" + +#: Source/monstdat.cpp:89 +msgctxt "monster" +msgid "Red Death" +msgstr "Чырвоная смерць" + +#: Source/monstdat.cpp:90 +msgctxt "monster" +msgid "Litch Demon" +msgstr "Кашчэй" + +#: Source/monstdat.cpp:91 +msgctxt "monster" +msgid "Undead Balrog" +msgstr "Мёртвы балраг" + +#: Source/monstdat.cpp:92 +msgctxt "monster" +msgid "Incinerator" +msgstr "Спальвальнік" + +#: Source/monstdat.cpp:93 +msgctxt "monster" +msgid "Flame Lord" +msgstr "Валадар полымя" + +#: Source/monstdat.cpp:94 +msgctxt "monster" +msgid "Doom Fire" +msgstr "Агонь пагібелі" + +#: Source/monstdat.cpp:95 +msgctxt "monster" +msgid "Hell Burner" +msgstr "Пякельны смальнік" + +#: Source/monstdat.cpp:96 +msgctxt "monster" +msgid "Red Storm" +msgstr "Чырвоная бура" + +#: Source/monstdat.cpp:97 +msgctxt "monster" +msgid "Storm Rider" +msgstr "Вершнік буры" + +#: Source/monstdat.cpp:98 +msgctxt "monster" +msgid "Storm Lord" +msgstr "Валадар буры" + +#: Source/monstdat.cpp:99 +msgctxt "monster" +msgid "Maelstrom" +msgstr "Вір" + +#: Source/monstdat.cpp:100 +msgctxt "monster" +msgid "Devil Kin Brute" +msgstr "Нячысты гіцаль" + +#: Source/monstdat.cpp:101 +msgctxt "monster" +msgid "Winged-Demon" +msgstr "Крылаты чорт" + +#: Source/monstdat.cpp:102 +msgctxt "monster" +msgid "Gargoyle" +msgstr "Гаргулля" + +#: Source/monstdat.cpp:103 +msgctxt "monster" +msgid "Blood Claw" +msgstr "Крывавы кіпцюр" + +#: Source/monstdat.cpp:104 +msgctxt "monster" +msgid "Death Wing" +msgstr "Крыло смерці" + +#: Source/monstdat.cpp:105 +msgctxt "monster" +msgid "Slayer" +msgstr "Забойца" + +#: Source/monstdat.cpp:106 +msgctxt "monster" +msgid "Guardian" +msgstr "Ахоўнік" + +#: Source/monstdat.cpp:107 +msgctxt "monster" +msgid "Vortex Lord" +msgstr "Валадар віхру" + +#: Source/monstdat.cpp:108 +msgctxt "monster" +msgid "Balrog" +msgstr "Балраг" + +#: Source/monstdat.cpp:109 +msgctxt "monster" +msgid "Cave Viper" +msgstr "Пячорная гадзіна" + +#: Source/monstdat.cpp:110 +msgctxt "monster" +msgid "Fire Drake" +msgstr "Вогненны смок" + +#: Source/monstdat.cpp:111 +msgctxt "monster" +msgid "Gold Viper" +msgstr "Залатая гадзіна" + +#: Source/monstdat.cpp:112 +msgctxt "monster" +msgid "Azure Drake" +msgstr "Блакітны смок" + +#: Source/monstdat.cpp:113 +msgctxt "monster" +msgid "Black Knight" +msgstr "Чорны рыцар" + +#: Source/monstdat.cpp:114 +msgctxt "monster" +msgid "Doom Guard" +msgstr "Вартавы лёсу" + +#: Source/monstdat.cpp:115 +msgctxt "monster" +msgid "Steel Lord" +msgstr "Валадар сталі" + +#: Source/monstdat.cpp:116 +msgctxt "monster" +msgid "Blood Knight" +msgstr "Рыцар крыві" + +#: Source/monstdat.cpp:117 +msgctxt "monster" +msgid "The Shredded" +msgstr "Шаткаваны" + +#: Source/monstdat.cpp:118 +msgctxt "monster" +msgid "Hollow One" +msgstr "Пусты" + +#: Source/monstdat.cpp:119 +msgctxt "monster" +msgid "Pain Master" +msgstr "Гаспадар болю" + +#: Source/monstdat.cpp:120 +msgctxt "monster" +msgid "Reality Weaver" +msgstr "Ткач запраўднасці" + +#: Source/monstdat.cpp:121 +msgctxt "monster" +msgid "Succubus" +msgstr "Сукуб" + +#: Source/monstdat.cpp:122 +msgctxt "monster" +msgid "Snow Witch" +msgstr "Снежная ведзьма" + +#: Source/monstdat.cpp:123 +msgctxt "monster" +msgid "Hell Spawn" +msgstr "Вырадак пекла" + +#: Source/monstdat.cpp:124 +msgctxt "monster" +msgid "Soul Burner" +msgstr "Спалідуша" + +#: Source/monstdat.cpp:125 +msgctxt "monster" +msgid "Counselor" +msgstr "Дараднік" + +#: Source/monstdat.cpp:126 +msgctxt "monster" +msgid "Magistrate" +msgstr "Суддзя" + +#: Source/monstdat.cpp:127 +msgctxt "monster" +msgid "Cabalist" +msgstr "Махляр" + +#: Source/monstdat.cpp:128 +msgctxt "monster" +msgid "Advocate" +msgstr "Заступнік" + +#: Source/monstdat.cpp:129 +msgctxt "monster" +msgid "Golem" +msgstr "Голем" + +#: Source/monstdat.cpp:130 +msgctxt "monster" +msgid "The Dark Lord" +msgstr "Цёмны валадар" + +#: Source/monstdat.cpp:131 +msgctxt "monster" +msgid "The Arch-Litch Malignus" +msgstr "Архі-Ліч Малігнус" + +#: Source/monstdat.cpp:132 +msgctxt "monster" +msgid "Hellboar" +msgstr "Пякельны дзік" + +#: Source/monstdat.cpp:133 +msgctxt "monster" +msgid "Stinger" +msgstr "Джальнік" + +#: Source/monstdat.cpp:134 +msgctxt "monster" +msgid "Psychorb" +msgstr "Псіхавока" + +#: Source/monstdat.cpp:135 +msgctxt "monster" +msgid "Arachnon" +msgstr "Павучыска" + +#: Source/monstdat.cpp:136 +msgctxt "monster" +msgid "Felltwin" +msgstr "Адцяты спарыш" + +#: Source/monstdat.cpp:137 +msgctxt "monster" +msgid "Hork Spawn" +msgstr "Вырадак рыгатні" + +#: Source/monstdat.cpp:138 +msgctxt "monster" +msgid "Venomtail" +msgstr "Атрутны хвост" + +#: Source/monstdat.cpp:139 +msgctxt "monster" +msgid "Necromorb" +msgstr "Мёртвае вока" + +#: Source/monstdat.cpp:140 +msgctxt "monster" +msgid "Spider Lord" +msgstr "Валадар павукоў" + +#: Source/monstdat.cpp:141 +msgctxt "monster" +msgid "Lashworm" +msgstr "Рабак-пуга" + +#: Source/monstdat.cpp:142 +msgctxt "monster" +msgid "Torchant" +msgstr "Успыхун" + +#: Source/monstdat.cpp:143 Source/monstdat.cpp:483 +msgctxt "monster" +msgid "Hork Demon" +msgstr "Рыготны чорт" + +#: Source/monstdat.cpp:144 +msgctxt "monster" +msgid "Hell Bug" +msgstr "Пеклаў жук" + +#: Source/monstdat.cpp:145 +msgctxt "monster" +msgid "Gravedigger" +msgstr "Далакоп" + +#: Source/monstdat.cpp:146 +msgctxt "monster" +msgid "Tomb Rat" +msgstr "Магільны пацук" + +#: Source/monstdat.cpp:147 +msgctxt "monster" +msgid "Firebat" +msgstr "Агнявы кажан" + +#: Source/monstdat.cpp:148 +msgctxt "monster" +msgid "Skullwing" +msgstr "Чарапун" + +#: Source/monstdat.cpp:149 +msgctxt "monster" +msgid "Lich" +msgstr "Ліч" + +#: Source/monstdat.cpp:150 +msgctxt "monster" +msgid "Crypt Demon" +msgstr "Склепавы чорт" + +#: Source/monstdat.cpp:151 +msgctxt "monster" +msgid "Hellbat" +msgstr "Пякельны кажан" + +#: Source/monstdat.cpp:152 +msgctxt "monster" +msgid "Bone Demon" +msgstr "Касцяны чорт" + +#: Source/monstdat.cpp:153 +msgctxt "monster" +msgid "Arch Lich" +msgstr "Архіліч" + +#: Source/monstdat.cpp:154 +msgctxt "monster" +msgid "Biclops" +msgstr "Лупаты" + +#: Source/monstdat.cpp:155 +msgctxt "monster" +msgid "Flesh Thing" +msgstr "Плоцень" + +#: Source/monstdat.cpp:156 +msgctxt "monster" +msgid "Reaper" +msgstr "Жнец" + +#. TRANSLATORS: Monster Block end +#. MT_NAKRUL +#: Source/monstdat.cpp:158 Source/monstdat.cpp:485 +msgctxt "monster" +msgid "Na-Krul" +msgstr "На-Крул" + +#. TRANSLATORS: Unique Monster Block start +#: Source/monstdat.cpp:473 +msgctxt "monster" +msgid "Gharbad the Weak" +msgstr "Кволы Гарбад" + +#: Source/monstdat.cpp:475 +msgctxt "monster" +msgid "Zhar the Mad" +msgstr "Шалёны Зар" + +#: Source/monstdat.cpp:476 +msgctxt "monster" +msgid "Snotspill" +msgstr "Смаркач" + +#: Source/monstdat.cpp:477 +msgctxt "monster" +msgid "Arch-Bishop Lazarus" +msgstr "Архібіскуп Лазар" + +#: Source/monstdat.cpp:478 +msgctxt "monster" +msgid "Red Vex" +msgstr "Чырованая бяда" + +#: Source/monstdat.cpp:479 +msgctxt "monster" +msgid "Black Jade" +msgstr "Чорная Жад" + +#: Source/monstdat.cpp:480 +msgctxt "monster" +msgid "Lachdanan" +msgstr "Лакданан" + +#: Source/monstdat.cpp:481 +msgctxt "monster" +msgid "Warlord of Blood" +msgstr "Крывавы ваявода" + +#: Source/monstdat.cpp:484 +msgctxt "monster" +msgid "The Defiler" +msgstr "Паганнік" + +#: Source/monstdat.cpp:486 +msgctxt "monster" +msgid "Bonehead Keenaxe" +msgstr "Дурыла Вострая сякера" + +#: Source/monstdat.cpp:487 +msgctxt "monster" +msgid "Bladeskin the Slasher" +msgstr "Лязаскурац Засякун" + +#: Source/monstdat.cpp:488 +msgctxt "monster" +msgid "Soulpus" +msgstr "Гнойная душа" + +#: Source/monstdat.cpp:489 +msgctxt "monster" +msgid "Pukerat the Unclean" +msgstr "Ванітапац Нячысты" + +#: Source/monstdat.cpp:490 +msgctxt "monster" +msgid "Boneripper" +msgstr "Вырвікосці" + +#: Source/monstdat.cpp:491 +msgctxt "monster" +msgid "Rotfeast the Hungry" +msgstr "Банкетагнілец Галодны" + +#: Source/monstdat.cpp:492 +msgctxt "monster" +msgid "Gutshank the Quick" +msgstr "Кішканожак Хуткі" + +#: Source/monstdat.cpp:493 +msgctxt "monster" +msgid "Brokenhead Bangshield" +msgstr "Бітылоб Шчытабой" + +#: Source/monstdat.cpp:494 +msgctxt "monster" +msgid "Bongo" +msgstr "Банго" + +#: Source/monstdat.cpp:495 +msgctxt "monster" +msgid "Rotcarnage" +msgstr "Гнілабой" + +#: Source/monstdat.cpp:496 +msgctxt "monster" +msgid "Shadowbite" +msgstr "Укус з Ценю" + +#: Source/monstdat.cpp:497 +msgctxt "monster" +msgid "Deadeye" +msgstr "Гнілы Яблычак" + +#: Source/monstdat.cpp:498 +msgctxt "monster" +msgid "Madeye the Dead" +msgstr "Мёртвы Утрапенец" + +#: Source/monstdat.cpp:499 +msgctxt "monster" +msgid "El Chupacabras" +msgstr "Чупакабра" + +#: Source/monstdat.cpp:500 +msgctxt "monster" +msgid "Skullfire" +msgstr "Вогненны Чэрап" + +#: Source/monstdat.cpp:501 +msgctxt "monster" +msgid "Warpskull" +msgstr "Крывы Чэрап" + +#: Source/monstdat.cpp:502 +msgctxt "monster" +msgid "Goretongue" +msgstr "Крывавы Язык" + +#: Source/monstdat.cpp:503 +msgctxt "monster" +msgid "Pulsecrawler" +msgstr "Пульс" + +#: Source/monstdat.cpp:504 +msgctxt "monster" +msgid "Moonbender" +msgstr "Сагнімесяц" + +#: Source/monstdat.cpp:505 +msgctxt "monster" +msgid "Wrathraven" +msgstr "Крумкач Шалу" + +#: Source/monstdat.cpp:506 +msgctxt "monster" +msgid "Spineeater" +msgstr "Хрыбтаед" + +#: Source/monstdat.cpp:507 +msgctxt "monster" +msgid "Blackash the Burning" +msgstr "Спалены Попел" + +#: Source/monstdat.cpp:508 +msgctxt "monster" +msgid "Shadowcrow" +msgstr "Цёмная Варона" + +#: Source/monstdat.cpp:509 +msgctxt "monster" +msgid "Blightstone the Weak" +msgstr "Чумны Камень Слабы" + +#: Source/monstdat.cpp:510 +msgctxt "monster" +msgid "Bilefroth the Pit Master" +msgstr "Гаспадар Ямы Пенажоўць" + +#: Source/monstdat.cpp:511 +msgctxt "monster" +msgid "Bloodskin Darkbow" +msgstr "Цёмны лук Крываскур" + +#: Source/monstdat.cpp:512 +msgctxt "monster" +msgid "Foulwing" +msgstr "Брыдкакрыл" + +#: Source/monstdat.cpp:513 +msgctxt "monster" +msgid "Shadowdrinker" +msgstr "Піток Ценю" + +#: Source/monstdat.cpp:514 +msgctxt "monster" +msgid "Hazeshifter" +msgstr "Змянісмугу" + +#: Source/monstdat.cpp:515 +msgctxt "monster" +msgid "Deathspit" +msgstr "Плявок Смерці" + +#: Source/monstdat.cpp:516 +msgctxt "monster" +msgid "Bloodgutter" +msgstr "Крывасцёк" + +#: Source/monstdat.cpp:517 +msgctxt "monster" +msgid "Deathshade Fleshmaul" +msgstr "Рвіскуры Цень смерці" + +#: Source/monstdat.cpp:518 +msgctxt "monster" +msgid "Warmaggot the Mad" +msgstr "Шалёная Гніда" + +#: Source/monstdat.cpp:519 +msgctxt "monster" +msgid "Glasskull the Jagged" +msgstr "Зубчасты Шклочэрап" + +#: Source/monstdat.cpp:520 +msgctxt "monster" +msgid "Blightfire" +msgstr "Агонь халеры" + +#: Source/monstdat.cpp:521 +msgctxt "monster" +msgid "Nightwing the Cold" +msgstr "Нячулая Начніца" + +#: Source/monstdat.cpp:522 +msgctxt "monster" +msgid "Gorestone" +msgstr "Крывакамень" + +#: Source/monstdat.cpp:523 +msgctxt "monster" +msgid "Bronzefist Firestone" +msgstr "Бронзавы Кулак агнякаменю" + +#: Source/monstdat.cpp:524 +msgctxt "monster" +msgid "Wrathfire the Doomed" +msgstr "Пракляты Гнеў Полымя" + +#: Source/monstdat.cpp:525 +msgctxt "monster" +msgid "Firewound the Grim" +msgstr "Змрочны Апёк" + +#: Source/monstdat.cpp:526 +msgctxt "monster" +msgid "Baron Sludge" +msgstr "Барон Дрыгвень" + +#: Source/monstdat.cpp:527 +msgctxt "monster" +msgid "Blighthorn Steelmace" +msgstr "Чумны Рог Стальная Булава" + +#: Source/monstdat.cpp:528 +msgctxt "monster" +msgid "Chaoshowler" +msgstr "Равун Хаосу" + +#: Source/monstdat.cpp:529 +msgctxt "monster" +msgid "Doomgrin the Rotting" +msgstr "Згнілы Вышчар" + +#: Source/monstdat.cpp:530 +msgctxt "monster" +msgid "Madburner" +msgstr "Ліхі Падпальшчык" + +#: Source/monstdat.cpp:531 +msgctxt "monster" +msgid "Bonesaw the Litch" +msgstr "Кашчэй Піла" + +#: Source/monstdat.cpp:532 +msgctxt "monster" +msgid "Breakspine" +msgstr "Зламіхрыбет" + +#: Source/monstdat.cpp:533 +msgctxt "monster" +msgid "Devilskull Sharpbone" +msgstr "Востры Чэрап" + +#: Source/monstdat.cpp:534 +msgctxt "monster" +msgid "Brokenstorm" +msgstr "Ветрабой" + +#: Source/monstdat.cpp:535 +msgctxt "monster" +msgid "Stormbane" +msgstr "Ліхая Навала" + +#: Source/monstdat.cpp:536 +msgctxt "monster" +msgid "Oozedrool" +msgstr "Цячысліна" + +#: Source/monstdat.cpp:537 +msgctxt "monster" +msgid "Goldblight of the Flame" +msgstr "Злотачума Полымя" + +#: Source/monstdat.cpp:538 +msgctxt "monster" +msgid "Blackstorm" +msgstr "Чорная Бура" + +#: Source/monstdat.cpp:539 +msgctxt "monster" +msgid "Plaguewrath" +msgstr "Гнеў Чумы" + +#: Source/monstdat.cpp:540 +msgctxt "monster" +msgid "The Flayer" +msgstr "Скуралуп" + +#: Source/monstdat.cpp:541 +msgctxt "monster" +msgid "Bluehorn" +msgstr "Блакітны Рог" + +#: Source/monstdat.cpp:542 +msgctxt "monster" +msgid "Warpfire Hellspawn" +msgstr "Пеклаў Вырадак Крывагню" + +#: Source/monstdat.cpp:543 +msgctxt "monster" +msgid "Fangspeir" +msgstr "Пазурак" + +#: Source/monstdat.cpp:544 +msgctxt "monster" +msgid "Festerskull" +msgstr "Гнілы Чэрап" + +#: Source/monstdat.cpp:545 +msgctxt "monster" +msgid "Lionskull the Bent" +msgstr "Крывы Ільвачэрап" + +#: Source/monstdat.cpp:546 +msgctxt "monster" +msgid "Blacktongue" +msgstr "Чорны Язык" + +#: Source/monstdat.cpp:547 +msgctxt "monster" +msgid "Viletouch" +msgstr "Брыдкі дотык" + +#: Source/monstdat.cpp:548 +msgctxt "monster" +msgid "Viperflame" +msgstr "Змеяполамень" + +#: Source/monstdat.cpp:549 +msgctxt "monster" +msgid "Fangskin" +msgstr "Ікласкур" + +#: Source/monstdat.cpp:550 +msgctxt "monster" +msgid "Witchfire the Unholy" +msgstr "Бязбожная Вогненная Ведзьма" + +#: Source/monstdat.cpp:551 +msgctxt "monster" +msgid "Blackskull" +msgstr "Чорны чэрап" + +#: Source/monstdat.cpp:552 +msgctxt "monster" +msgid "Soulslash" +msgstr "Душасек" + +#: Source/monstdat.cpp:553 +msgctxt "monster" +msgid "Windspawn" +msgstr "Дзіця ветру" + +#: Source/monstdat.cpp:554 +msgctxt "monster" +msgid "Lord of the Pit" +msgstr "Валадар ямы" + +#: Source/monstdat.cpp:555 +msgctxt "monster" +msgid "Rustweaver" +msgstr "Іржаткач" + +#: Source/monstdat.cpp:556 +msgctxt "monster" +msgid "Howlingire the Shade" +msgstr "Цень Равучага Гневу" + +#: Source/monstdat.cpp:557 +msgctxt "monster" +msgid "Doomcloud" +msgstr "Воблака Долі" + +#: Source/monstdat.cpp:558 +msgctxt "monster" +msgid "Bloodmoon Soulfire" +msgstr "Агонь Душы з Крывавай Луны" + +#: Source/monstdat.cpp:559 +msgctxt "monster" +msgid "Witchmoon" +msgstr "Ведзьма Месяцу" + +#: Source/monstdat.cpp:560 +msgctxt "monster" +msgid "Gorefeast" +msgstr "Крывавы Банкет" + +#: Source/monstdat.cpp:561 +msgctxt "monster" +msgid "Graywar the Slayer" +msgstr "Шэравойна Людабойца" + +#: Source/monstdat.cpp:562 +msgctxt "monster" +msgid "Dreadjudge" +msgstr "Жуда-Суддзя" + +#: Source/monstdat.cpp:563 +msgctxt "monster" +msgid "Stareye the Witch" +msgstr "Ведзьма Зоркавока" + +#: Source/monstdat.cpp:564 +msgctxt "monster" +msgid "Steelskull the Hunter" +msgstr "Стальны чэрап Паляўнічы" + +#: Source/monstdat.cpp:565 +msgctxt "monster" +msgid "Sir Gorash" +msgstr "Сэр Гораш" + +#: Source/monstdat.cpp:566 +msgctxt "monster" +msgid "The Vizier" +msgstr "Візір" + +#: Source/monstdat.cpp:567 +msgctxt "monster" +msgid "Zamphir" +msgstr "Замфір" + +#: Source/monstdat.cpp:568 +msgctxt "monster" +msgid "Bloodlust" +msgstr "Крывавая Прага" + +#: Source/monstdat.cpp:569 +msgctxt "monster" +msgid "Webwidow" +msgstr "Удава Павуціння" + +#: Source/monstdat.cpp:570 +msgctxt "monster" +msgid "Fleshdancer" +msgstr "Скураны Танцор" + +#: Source/monstdat.cpp:571 +msgctxt "monster" +msgid "Grimspike" +msgstr "Змрокашып" + +#. TRANSLATORS: Unique Monster Block end +#: Source/monstdat.cpp:573 +msgctxt "monster" +msgid "Doomlock" +msgstr "Замок Долі" + +#: Source/monster.cpp:3384 +msgid "Animal" +msgstr "Звер" + +#: Source/monster.cpp:3386 +msgid "Demon" +msgstr "Чорт" + +#: Source/monster.cpp:3388 +msgid "Undead" +msgstr "Навец" + +#: Source/monster.cpp:4643 +msgid "Type: {:s} Kills: {:d}" +msgstr "Тып: {:s} Забітых: {:d}" + +#: Source/monster.cpp:4645 +msgid "Total kills: {:d}" +msgstr "Усяго забітых: {:d}" + +#: Source/monster.cpp:4677 +msgid "Hit Points: {:d}-{:d}" +msgstr "Ачкоў здароўя: {:d}-{:d}" + +#: Source/monster.cpp:4682 +msgid "No magic resistance" +msgstr "Ані супраціву магіі" + +#: Source/monster.cpp:4685 +msgid "Resists:" +msgstr "Супраціў:" + +#: Source/monster.cpp:4687 Source/monster.cpp:4697 +msgid " Magic" +msgstr " Магія" + +#: Source/monster.cpp:4689 Source/monster.cpp:4699 +msgid " Fire" +msgstr " Агонь" + +#: Source/monster.cpp:4691 Source/monster.cpp:4701 +msgid " Lightning" +msgstr " Маланка" + +#: Source/monster.cpp:4695 +msgid "Immune:" +msgstr "Імунітэт:" + +#: Source/monster.cpp:4712 +msgid "Type: {:s}" +msgstr "Тып: {:s}" + +#: Source/monster.cpp:4717 Source/monster.cpp:4723 +msgid "No resistances" +msgstr "Ані супраціваў" + +#: Source/monster.cpp:4718 Source/monster.cpp:4727 +msgid "No Immunities" +msgstr "Ані імунітэтаў" + +#: Source/monster.cpp:4721 +msgid "Some Magic Resistances" +msgstr "Трохі супраціву магіі" + +#: Source/monster.cpp:4725 +msgid "Some Magic Immunities" +msgstr "Трохі імунітэту магіі" + +#: Source/msg.cpp:486 +msgid "Trying to drop a floor item?" +msgstr "Хочаце кінуць рэч на зямлю?" + +#: Source/msg.cpp:989 Source/msg.cpp:1024 Source/msg.cpp:1055 +#: Source/msg.cpp:1182 Source/msg.cpp:1214 Source/msg.cpp:1246 +#: Source/msg.cpp:1276 +msgid "{:s} has cast an illegal spell." +msgstr "{:s} наклаў незаконную чару." + +#: Source/msg.cpp:1684 Source/multi.cpp:738 Source/multi.cpp:787 +msgid "Player '{:s}' (level {:d}) just joined the game" +msgstr "Гулец '{:s}' (узроўню {:d}) далучыўся да гульні" + +#: Source/msg.cpp:1994 +msgid "The game ended" +msgstr "Гульня скончана" + +#: Source/msg.cpp:2000 +msgid "Unable to get level data" +msgstr "Немагчыма дазнацца аб узроўні" + +#: Source/multi.cpp:198 +msgid "Player '{:s}' just left the game" +msgstr "Гулец '{:s}' выйшаў з гульні" + +#: Source/multi.cpp:201 +msgid "Player '{:s}' killed Diablo and left the game!" +msgstr "Гулец '{:s}' забіў Д'яблу і выйшаў з гульні!" + +#: Source/multi.cpp:205 +msgid "Player '{:s}' dropped due to timeout" +msgstr "Гулец '{:s}' выляцеў з-за таймаута" + +#: Source/multi.cpp:789 +msgid "Player '{:s}' (level {:d}) is already in the game" +msgstr "Гулец '{:s}' (узроўню {:d}) ужо ў гульні" + +#. TRANSLATORS: Shrine Name Block +#: Source/objects.cpp:106 +msgid "Mysterious" +msgstr "Таямнічы" + +#: Source/objects.cpp:107 +msgid "Hidden" +msgstr "Схаваны" + +#: Source/objects.cpp:108 +msgid "Gloomy" +msgstr "Змрочны" + +#: Source/objects.cpp:110 Source/objects.cpp:117 +msgid "Magical" +msgstr "Магічны" + +#: Source/objects.cpp:111 +msgid "Stone" +msgstr "Каменны" + +#: Source/objects.cpp:112 +msgid "Religious" +msgstr "Набожны" + +#: Source/objects.cpp:113 +msgid "Enchanted" +msgstr "Зачараваны" + +#: Source/objects.cpp:114 +msgid "Thaumaturgic" +msgstr "Цудадзейны" + +#: Source/objects.cpp:115 +msgid "Fascinating" +msgstr "Чароўны" + +#: Source/objects.cpp:116 +msgid "Cryptic" +msgstr "Утоены" + +#: Source/objects.cpp:118 +msgid "Eldritch" +msgstr "Жудасны" + +#: Source/objects.cpp:119 +msgid "Eerie" +msgstr "Вусцішны" + +#: Source/objects.cpp:120 +msgid "Divine" +msgstr "Боскі" + +#: Source/objects.cpp:122 +msgid "Sacred" +msgstr "Святы" + +#: Source/objects.cpp:123 +msgid "Spiritual" +msgstr "Духоўны" + +#: Source/objects.cpp:124 +msgid "Spooky" +msgstr "Страшны" + +#: Source/objects.cpp:125 +msgid "Abandoned" +msgstr "Кінуты" + +#: Source/objects.cpp:126 +msgid "Creepy" +msgstr "Жахлівы" + +#: Source/objects.cpp:127 +msgid "Quiet" +msgstr "Ціхі" + +#: Source/objects.cpp:128 +msgid "Secluded" +msgstr "Самотны" + +#: Source/objects.cpp:129 +msgid "Ornate" +msgstr "Аздоблены" + +#: Source/objects.cpp:130 +msgid "Glimmering" +msgstr "Зіхатлівы" + +#: Source/objects.cpp:131 +msgid "Tainted" +msgstr "Запэцканы" + +#: Source/objects.cpp:132 +msgid "Oily" +msgstr "Маслены" + +#: Source/objects.cpp:133 +msgid "Glowing" +msgstr "Святлівы" + +#: Source/objects.cpp:134 +msgid "Mendicant's" +msgstr "Жабрацкі" + +#: Source/objects.cpp:135 +msgid "Sparkling" +msgstr "Бліскучы" + +#: Source/objects.cpp:137 +msgid "Shimmering" +msgstr "Мігатлівы" + +#: Source/objects.cpp:138 +msgid "Solar" +msgstr "Сонечны" + +#. TRANSLATORS: Shrine Name Block end +#: Source/objects.cpp:140 +msgid "Murphy's" +msgstr "Мёрфева" + +#. TRANSLATORS: Book Title +#: Source/objects.cpp:270 +msgid "The Great Conflict" +msgstr "Вялікае Змаганне" + +#. TRANSLATORS: Book Title +#: Source/objects.cpp:271 +msgid "The Wages of Sin are War" +msgstr "Плата за грэх — вайна" + +#. TRANSLATORS: Book Title +#: Source/objects.cpp:272 +msgid "The Tale of the Horadrim" +msgstr "Аповесць Харадрым" + +#. TRANSLATORS: Book Title +#: Source/objects.cpp:273 +msgid "The Dark Exile" +msgstr "Цёмнае выгнанне" + +#. TRANSLATORS: Book Title +#: Source/objects.cpp:274 +msgid "The Sin War" +msgstr "Вайна граху" + +#. TRANSLATORS: Book Title +#: Source/objects.cpp:275 +msgid "The Binding of the Three" +msgstr "Палон Трох" + +#. TRANSLATORS: Book Title +#: Source/objects.cpp:276 +msgid "The Realms Beyond" +msgstr "Пазамежжа" + +#. TRANSLATORS: Book Title +#: Source/objects.cpp:277 +msgid "Tale of the Three" +msgstr "Аповесць аб Трох" + +#. TRANSLATORS: Book Title +#: Source/objects.cpp:278 +msgid "The Black King" +msgstr "Чорны Кароль" + +#. TRANSLATORS: Book Title +#: Source/objects.cpp:279 +msgid "Journal: The Ensorcellment" +msgstr "Дзённік: Зачараванасць" + +#. TRANSLATORS: Book Title +#: Source/objects.cpp:280 +msgid "Journal: The Meeting" +msgstr "Дзённік: Сустрэча" + +#. TRANSLATORS: Book Title +#: Source/objects.cpp:281 +msgid "Journal: The Tirade" +msgstr "Дзённік: Тырада" + +#. TRANSLATORS: Book Title +#: Source/objects.cpp:282 +msgid "Journal: His Power Grows" +msgstr "Дзённік: Яго моц расце" + +#. TRANSLATORS: Book Title +#: Source/objects.cpp:283 +msgid "Journal: NA-KRUL" +msgstr "Дзённік: НА-КРУЛ" + +#. TRANSLATORS: Book Title +#: Source/objects.cpp:284 +msgid "Journal: The End" +msgstr "Дзённік: Канец" + +#. TRANSLATORS: Book Title +#: Source/objects.cpp:285 +msgid "A Spellbook" +msgstr "Кніга Чараў" + +#: Source/objects.cpp:5383 +msgid "Crucified Skeleton" +msgstr "Распяты шкілет" + +#: Source/objects.cpp:5387 +msgid "Lever" +msgstr "Рычаг" + +#: Source/objects.cpp:5396 +msgid "Open Door" +msgstr "Адчыненыя дзверы" + +#: Source/objects.cpp:5398 +msgid "Closed Door" +msgstr "Зачыненыя дзверы" + +#: Source/objects.cpp:5400 +msgid "Blocked Door" +msgstr "Заваленыя дзверы" + +#: Source/objects.cpp:5405 +msgid "Ancient Tome" +msgstr "Старажытны фаліянт" + +#: Source/objects.cpp:5407 +msgid "Book of Vileness" +msgstr "Кніга Подласці" + +#: Source/objects.cpp:5412 +msgid "Skull Lever" +msgstr "Чэрапаў рычаг" + +#: Source/objects.cpp:5415 +msgid "Mythical Book" +msgstr "Міфічная кніга" + +#: Source/objects.cpp:5419 +msgid "Small Chest" +msgstr "Куфэрак" + +#: Source/objects.cpp:5423 +msgid "Chest" +msgstr "Скрыня" + +#: Source/objects.cpp:5428 +msgid "Large Chest" +msgstr "Куфар" + +#: Source/objects.cpp:5431 +msgid "Sarcophagus" +msgstr "Саркафаг" + +#: Source/objects.cpp:5434 +msgid "Bookshelf" +msgstr "Кніжная паліца" + +#: Source/objects.cpp:5438 +msgid "Bookcase" +msgstr "Кніжная шафа" + +#: Source/objects.cpp:5443 +msgid "Pod" +msgstr "Кокан" + +#: Source/objects.cpp:5445 +msgid "Urn" +msgstr "Урна" + +#: Source/objects.cpp:5447 +msgid "Barrel" +msgstr "Бочка" + +#. TRANSLATORS: {:s} will be a name from the Shrine block above +#: Source/objects.cpp:5451 +msgid "{:s} Shrine" +msgstr "{:s} Алтар" + +#: Source/objects.cpp:5454 +msgid "Skeleton Tome" +msgstr "Фаліянт шкілета" + +#: Source/objects.cpp:5457 +msgid "Library Book" +msgstr "Бібліятэчная кніга" + +#: Source/objects.cpp:5460 +msgid "Blood Fountain" +msgstr "Фантан крыві" + +#: Source/objects.cpp:5463 +msgid "Decapitated Body" +msgstr "Абезгалоўлены труп" + +#: Source/objects.cpp:5466 +msgid "Book of the Blind" +msgstr "Кніга Сляпых" + +#: Source/objects.cpp:5469 +msgid "Book of Blood" +msgstr "Кніга крыві" + +#: Source/objects.cpp:5472 +msgid "Purifying Spring" +msgstr "Ачышчальная крыніца" + +#: Source/objects.cpp:5479 Source/objects.cpp:5503 +msgid "Weapon Rack" +msgstr "Паліца са зброяю" + +#: Source/objects.cpp:5482 +msgid "Goat Shrine" +msgstr "Казліны алтар" + +#: Source/objects.cpp:5485 +msgid "Cauldron" +msgstr "Кацёл" + +#: Source/objects.cpp:5488 +msgid "Murky Pool" +msgstr "Мутны басейн" + +#: Source/objects.cpp:5491 +msgid "Fountain of Tears" +msgstr "Фантан слёз" + +#: Source/objects.cpp:5494 +msgid "Steel Tome" +msgstr "Стальны фаліянт" + +#: Source/objects.cpp:5497 +msgid "Pedestal of Blood" +msgstr "Крывавы п'едэстал" + +#: Source/objects.cpp:5506 +msgid "Mushroom Patch" +msgstr "Грыбны лапік" + +#: Source/objects.cpp:5509 +msgid "Vile Stand" +msgstr "Агідная стойка" + +#: Source/objects.cpp:5512 +msgid "Slain Hero" +msgstr "Загінулы герой" + +#. TRANSLATORS: {:s} will either be a chest or a door +#: Source/objects.cpp:5519 +msgid "Trapped {:s}" +msgstr "{:s} з пасткай" + +#. TRANSLATORS: If user enabled diablo.ini setting "Disable Crippling Shrines" is set to 1; also used for Na-Kruls leaver +#: Source/objects.cpp:5524 +msgid "{:s} (disabled)" +msgstr "{:s} (немагчыма)" + +#: Source/options.cpp:420 Source/options.cpp:551 Source/options.cpp:557 +msgid "ON" +msgstr "УКЛ" + +#: Source/options.cpp:420 Source/options.cpp:549 Source/options.cpp:555 +msgid "OFF" +msgstr "ВЫКЛ" + +#: Source/options.cpp:539 +msgid "Start Up" +msgstr "Запуск" + +#: Source/options.cpp:539 +msgid "Start Up Settings" +msgstr "Запуск Наладаў" + +#: Source/options.cpp:540 +msgid "Game Mode" +msgstr "Рэжым Гульні" + +#: Source/options.cpp:540 +msgid "Play Diablo or Hellfire." +msgstr "Гуляць у Diablo ці Hellfire." + +#: Source/options.cpp:546 +msgid "Restrict to Shareware" +msgstr "Абмежаваць дэма-рэжым" + +#: Source/options.cpp:546 +msgid "" +"Makes the game compatible with the demo. Enables multiplayer with friends " +"who don't own a full copy of Diablo." +msgstr "" +"Робіць гульню спалучанаю з дэма. Дазваляе мультыплэер з сябрамі, якія не " +"маюць поўную копію Д'яблы." + +#: Source/options.cpp:547 Source/options.cpp:553 +msgid "Intro" +msgstr "Інтра" + +#: Source/options.cpp:547 Source/options.cpp:553 +msgid "Shown Intro cinematic." +msgstr "Уступны сінематык паказаны." + +#: Source/options.cpp:559 +msgid "Splash" +msgstr "Вітальны экран" + +#: Source/options.cpp:559 +msgid "Shown splash screen." +msgstr "Паказаны вітальны экран." + +#: Source/options.cpp:561 +msgid "Logo and Title Screen" +msgstr "Лагатып і тытульны экран" + +#: Source/options.cpp:562 +msgid "Title Screen" +msgstr "Тытульны экран" + +#: Source/options.cpp:581 +msgid "Diablo specific Settings" +msgstr "Спецыяльныя налады Diablo" + +#: Source/options.cpp:595 +msgid "Hellfire specific Settings" +msgstr "Спецыяльныя налады Hellfire" + +#: Source/options.cpp:609 +msgid "Audio" +msgstr "Аўдыя" + +#: Source/options.cpp:609 +msgid "Audio Settings" +msgstr "Налады аўдыя" + +#: Source/options.cpp:612 +msgid "Walking Sound" +msgstr "Гук хадзьбы" + +#: Source/options.cpp:612 +msgid "Player emits sound when walking." +msgstr "Гулец утварае гукі ходзячы." + +#: Source/options.cpp:613 +msgid "Auto Equip Sound" +msgstr "Гук аўтаэквіпа" + +#: Source/options.cpp:613 +msgid "Automatically equipping items on pickup emits the equipment sound." +msgstr "Аўтаматычны эквіп рэчаў утварае гук." + +#: Source/options.cpp:614 +msgid "Item Pickup Sound" +msgstr "Гук падбірання" + +#: Source/options.cpp:614 +msgid "Picking up items emits the items pickup sound." +msgstr "Падбіранне рэчаў утварае гук." + +#: Source/options.cpp:615 +msgid "Sample Rate" +msgstr "Частата Дыскрэтызацыі" + +#: Source/options.cpp:615 +msgid "Output sample rate (Hz)." +msgstr "Частата дыскрэтызацыі вывада гуку (Гц)." + +#: Source/options.cpp:616 +msgid "Channels" +msgstr "Каналы" + +#: Source/options.cpp:616 +msgid "Number of output channels." +msgstr "Колькасць каналаў вывада." + +#: Source/options.cpp:617 +msgid "Buffer Size" +msgstr "Памер буфера" + +#: Source/options.cpp:617 +msgid "Buffer size (number of frames per channel)." +msgstr "Памер буфера (колькасць кадраў на канал)" + +#: Source/options.cpp:618 +msgid "Resampling Quality" +msgstr "Якасць перадыскрэтызацыі" + +#: Source/options.cpp:618 +msgid "Quality of the resampler, from 0 (lowest) to 10 (highest)." +msgstr "Якасць перадыскрэтызатару, ад 0 (найменшы) да 10 (найбольшы)" + +#: Source/options.cpp:641 +msgid "Resolution" +msgstr "Раздзяляльнасць" + +#: Source/options.cpp:641 +msgid "" +"Affect the game's internal resolution and determine your view area. Note: " +"This can differ from screen resolution, when Upscaling, Integer Scaling or " +"Fit to Screen is used." +msgstr "" +"Памяняць унутраную раздзяляльнасць гульні і вызначыць ваш бачны абсяг. " +"Заўвага: Можа быць розніца з раздзяляльнасцю экрана, калі карыстуецца " +"Павелічэнне маштабу, Цэлалікавае маштабаванне ці Падагнаць пад экран." + +#: Source/options.cpp:737 +msgid "Graphics" +msgstr "Графіка" + +#: Source/options.cpp:737 +msgid "Graphics Settings" +msgstr "Налады графікі" + +#: Source/options.cpp:738 +msgid "Fullscreen" +msgstr "Поўны экран" + +#: Source/options.cpp:738 +msgid "Display the game in windowed or fullscreen mode." +msgstr "Паказаць гульню ва акне ці ў поўным экране." + +#: Source/options.cpp:740 +msgid "Fit to Screen" +msgstr "Падагнаць пад экран" + +#: Source/options.cpp:740 +msgid "" +"Automatically adjust the game window to your current desktop screen aspect " +"ratio and resolution." +msgstr "" +"Аўтаматычна наладжваць акно з гульнёю да вашых суадносінаў бакоў і " +"раздзяляльнасці." + +#: Source/options.cpp:743 +msgid "Upscale" +msgstr "Павелічэнне маштабу" + +#: Source/options.cpp:743 +msgid "" +"Enables image scaling from the game resolution to your monitor resolution. " +"Prevents changing the monitor resolution and allows window resizing." +msgstr "" +"Дазваляе маштабаванне ад раздзяляльнасці гульні да раздзяляльнасці вашага " +"манітора. Забараняе змяняць раздзяляльнасць манітора і дазваляе змяняць " +"памер вокнаў." + +#: Source/options.cpp:744 +msgid "Scaling Quality" +msgstr "Якасць маштабавання" + +#: Source/options.cpp:744 +msgid "Enables optional filters to the output image when upscaling." +msgstr "" +"Уключае дадатковыя фільтры да выходнага відарыса пры павелічэнні маштабу." + +#: Source/options.cpp:746 +msgid "Nearest Pixel" +msgstr "Найбліжэйшы Піксель" + +#: Source/options.cpp:747 +msgid "Bilinear" +msgstr "Білінейны" + +#: Source/options.cpp:748 +msgid "Anisotropic" +msgstr "Анізатропны" + +#: Source/options.cpp:750 +msgid "Integer Scaling" +msgstr "Цэлалікавае маштабаванне" + +#: Source/options.cpp:750 +msgid "Scales the image using whole number pixel ratio." +msgstr "Маштабуе відарыс, карыстуючы суадносіны пікселей цэлага ліку." + +#: Source/options.cpp:751 +msgid "Vertical Sync" +msgstr "Вертыкальная Сінхранізацыя" + +#: Source/options.cpp:751 +msgid "" +"Forces waiting for Vertical Sync. Prevents tearing effect when drawing a " +"frame. Disabling it can help with mouse lag on some systems." +msgstr "" +"Прымушае пачакаць Вертыкальную Сінхранізацыю. Забараняе эфект разрыву калі " +"кадр вымалёўваецца. Калі яго выключыць у некаторых сістэмах ладзіць лаг " +"мышкі." + +#: Source/options.cpp:754 +msgid "Color Cycling" +msgstr "Зацыкліванне колеру" + +#: Source/options.cpp:754 +msgid "Color cycling effect used for water, lava, and acid animation." +msgstr "" +"Эфект зацыклівання колеру карыстуецца для анімацыі вады, лавы, і кіслаты." + +#: Source/options.cpp:755 +msgid "Alternate nest art" +msgstr "Альтэрнатыўны малюнак гнязда" + +#: Source/options.cpp:755 +msgid "The game will use an alternative palette for Hellfire’s nest tileset." +msgstr "" +"У гульні выкарыстуецца альтэрнатыўная палітра для набору пліт гнязда для " +"Hellfire." + +#: Source/options.cpp:757 +msgid "Hardware Cursor" +msgstr "Курсор апаратнага забеспячэння" + +#: Source/options.cpp:757 +msgid "Use a hardware cursor" +msgstr "Карыстаць курсор апаратнага забеспячэння" + +#: Source/options.cpp:758 +msgid "Hardware Cursor For Items" +msgstr "Курсор апаратнага забеспячэння для рэчаў" + +#: Source/options.cpp:758 +msgid "Use a hardware cursor for items." +msgstr "Карыстаць курсор апаратнага забеспячэння для рэчаў." + +#: Source/options.cpp:759 +msgid "Hardware Cursor Maximum Size" +msgstr "Максімальны размер курсора апаратнага забеспячэння" + +#: Source/options.cpp:759 +msgid "" +"Maximum width / height for the hardware cursor. Larger cursors fall back to " +"software." +msgstr "Максімальная шырыня / даўжыня для курсора апаратнага забеспячэння." + +#: Source/options.cpp:761 +msgid "FPS Limiter" +msgstr "Абмежавальнік FPS" + +#: Source/options.cpp:761 +msgid "FPS is limited to avoid high CPU load. Limit considers refresh rate." +msgstr "" +"FPS абмежаваны, каб пазбегнуць высокай нагрузкі на працэсар. Ліміт улічвае " +"частату абнаўлення." + +#: Source/options.cpp:762 +msgid "Show FPS" +msgstr "Паказваць FPS" + +#: Source/options.cpp:762 +msgid "Displays the FPS in the upper left corner of the screen." +msgstr "Адлюстроўвае FPS у левым верхнім куце экрана." + +#: Source/options.cpp:763 +msgid "Show health values" +msgstr "Паказаць велічыню здароўя" + +#: Source/options.cpp:763 +msgid "Displays current / max health value on health globe." +msgstr "" +"Адлюстроўвае цяперашняе / максімальнае значэнне здароўя на шары здароўя." + +#: Source/options.cpp:764 +msgid "Show mana values" +msgstr "Паказаць велічыню маны" + +#: Source/options.cpp:764 +msgid "Displays current / max mana value on mana globe." +msgstr "Паказаць цяперашнюю / максімальную велічыню маны на шары маны." + +#: Source/options.cpp:813 +msgid "Gameplay" +msgstr "Гульня" + +#: Source/options.cpp:813 +msgid "Gameplay Settings" +msgstr "Налады гульні" + +#: Source/options.cpp:815 +msgid "Run in Town" +msgstr "Бегчы ў Горадзе" + +#: Source/options.cpp:815 +msgid "" +"Enable jogging/fast walking in town for Diablo and Hellfire. This option was " +"introduced in the expansion." +msgstr "" +"Дазволіць бег/хуткую хаду ў горадзе для Diablo і Hellfire. Гэтая опцыя была " +"ўведзена ў пашырэнні." + +#: Source/options.cpp:816 +msgid "Grab Input" +msgstr "Захапіць мыш" + +#: Source/options.cpp:816 +msgid "When enabled mouse is locked to the game window." +msgstr "Калі ўключана, мыш фіксуецца ў акне гульні." + +#: Source/options.cpp:817 +msgid "Theo Quest" +msgstr "Заданне Тэа" + +#: Source/options.cpp:817 +msgid "Enable Little Girl quest." +msgstr "Уключыць квэст \"Маленькая дзяўчынка\"." + +#: Source/options.cpp:818 +msgid "Cow Quest" +msgstr "Заданне Каровы" + +#: Source/options.cpp:818 +msgid "" +"Enable Jersey's quest. Lester the farmer is replaced by the Complete Nut." +msgstr "Уключыць квэст Джэрсі. Фермер Лестэр замяняецца." + +#: Source/options.cpp:819 +msgid "Friendly Fire" +msgstr "Агонь па сваіх" + +#: Source/options.cpp:819 +msgid "" +"Allow arrow/spell damage between players in multiplayer even when the " +"friendly mode is on." +msgstr "" +"Дазволіць наносіць шкоду стрэламі/чарамі паміж гульцамі ў " +"шматкарыстальніцкім рэжыме, нават калі ўключаны сяброўскі рэжым." + +#: Source/options.cpp:820 +msgid "Test Bard" +msgstr "Проба Барда" + +#: Source/options.cpp:820 +msgid "Force the Bard character type to appear in the hero selection menu." +msgstr "Прымусіць тып персанажа \"Бард\" з'явіцца ў меню выбара героя." + +#: Source/options.cpp:821 +msgid "Test Barbarian" +msgstr "Проба Варвара" + +#: Source/options.cpp:821 +msgid "" +"Force the Barbarian character type to appear in the hero selection menu." +msgstr "Прымусіць тып персанажа \"Бард\" з'явіцца ў меню выбара героя." + +#: Source/options.cpp:822 +msgid "Experience Bar" +msgstr "Палоска досведу" + +#: Source/options.cpp:822 +msgid "Experience Bar is added to the UI at the bottom of the screen." +msgstr "У карыстальніцкі інтэрфэйс унізе экрана дадаецца палоска досведу." + +#: Source/options.cpp:823 +msgid "Enemy Health Bar" +msgstr "Палоска здароўя ворагаў" + +#: Source/options.cpp:823 +msgid "Enemy Health Bar is displayed at the top of the screen." +msgstr "Наверсе экрана паказваецца палоска здароўя ворага." + +#: Source/options.cpp:824 +msgid "Auto Gold Pickup" +msgstr "Аўта-падняцце золата" + +#: Source/options.cpp:824 +msgid "Gold is automatically collected when in close proximity to the player." +msgstr "Золота аўтаматычна збіраецца, калі блізка да гульца." + +#: Source/options.cpp:825 +msgid "Auto Elixir Pickup" +msgstr "Аўта-падняцце эліксіраў" + +#: Source/options.cpp:825 +msgid "" +"Elixirs are automatically collected when in close proximity to the player." +msgstr "Эліксіры падымаюцца аўтаматычна, калі блізка да гульца." + +#: Source/options.cpp:826 +msgid "Auto Pickup in Town" +msgstr "Аўта-падняцце ў Горадзе" + +#: Source/options.cpp:826 +msgid "Automatically pickup items in town." +msgstr "Аўтаматычна падымаць рэчы ў горадзе." + +#: Source/options.cpp:827 +msgid "Adria Refills Mana" +msgstr "Эйдрыя Папаўняе Ману" + +#: Source/options.cpp:827 +msgid "Adria will refill your mana when you visit her shop." +msgstr "Эйдрыя будзе папаўняць вашу ману, пры наведванні яе крамы." + +#: Source/options.cpp:828 +msgid "Auto Equip Weapons" +msgstr "Аўта-эквіп зброі" + +#: Source/options.cpp:828 +msgid "" +"Weapons will be automatically equipped on pickup or purchase if enabled." +msgstr "" +"Зброя будзе аўтаматычна экіпіравана пры атрыманні або куплі, калі ўключана." + +#: Source/options.cpp:829 +msgid "Auto Equip Armor" +msgstr "Аўта-эквіп брані" + +#: Source/options.cpp:829 +msgid "Armor will be automatically equipped on pickup or purchase if enabled." +msgstr "" +"Браня будзе аўтаматычна экіпіравана пры атрыманні або куплі, калі ўключана." + +#: Source/options.cpp:830 +msgid "Auto Equip Helms" +msgstr "Аўта-эквіп шаломаў" + +#: Source/options.cpp:830 +msgid "Helms will be automatically equipped on pickup or purchase if enabled." +msgstr "" +"Шаломы будуць аўтаматычна экіпіраваны пры атрыманні або куплі, калі ўключана." + +#: Source/options.cpp:831 +msgid "Auto Equip Shields" +msgstr "Аўта-эквіп шчытоў" + +#: Source/options.cpp:831 +msgid "" +"Shields will be automatically equipped on pickup or purchase if enabled." +msgstr "" +"Шчыты будуць аўтаматычна экіпіраваны пры атрыманні або куплі, калі ўключана." + +#: Source/options.cpp:832 +msgid "Auto Equip Jewelry" +msgstr "Аўта-эквіп каштоўнасцяў" + +#: Source/options.cpp:832 +msgid "" +"Jewelry will be automatically equipped on pickup or purchase if enabled." +msgstr "" +"Каштоўнасці будуць аўтаматычна экіпіраваны пры атрыманні або куплі, калі " +"гэтая функцыя ўключана." + +#: Source/options.cpp:833 +msgid "Randomize Quests" +msgstr "Выпадковы парадак заданняў" + +#: Source/options.cpp:833 +msgid "Randomly selecting available quests for new games." +msgstr "Выпадковы выбар даступных заданняў для новых гульняў." + +#: Source/options.cpp:834 +msgid "Show Monster Type" +msgstr "Паказаць тып монстра" + +#: Source/options.cpp:834 +msgid "" +"Hovering over a monster will display the type of monster in the description " +"box in the UI." +msgstr "" +"Пры навядзенні курсора на монстра яго тып будзе адлюстроўвацца ў полі " +"апісання ў карыстальніцкім інтэрфейсе." + +#: Source/options.cpp:835 +msgid "Auto Refill Belt" +msgstr "Аўтазапаўненне ячэйкі пояса" + +#: Source/options.cpp:835 +msgid "Refill belt from inventory when belt item is consumed." +msgstr "Укладваць з інвентара ў пояс, калі рэч на поясе спажываецца." + +#: Source/options.cpp:836 +msgid "Disable Crippling Shrines" +msgstr "Адключыць шкодныя Алтары" + +#: Source/options.cpp:836 +msgid "" +"When enabled Cauldrons, Fascinating Shrines, Goat Shrines, Ornate Shrines " +"and Sacred Shrines are not able to be clicked on and labeled as disabled." +msgstr "" +"Калі ўключана, нельга націснуць на Катлы, Чароўныя Алтары, Казліныя Алтары, " +"Аздобленыя Алтары, Святыя Алтары, і яны паказаныя як нерабочыя." + +#: Source/options.cpp:837 +msgid "Quick Cast" +msgstr "Хуткія чары" + +#: Source/options.cpp:837 +msgid "" +"Spell hotkeys instantly cast the spell, rather than switching the readied " +"spell." +msgstr "Гарачыя клавішы Чар імгненна накладваюць чары." + +#: Source/options.cpp:838 +msgid "Heal Potion Pickup" +msgstr "Падбіранне зелляў лячэння" + +#: Source/options.cpp:838 +msgid "Number of Healing potions to pick up automatically." +msgstr "Колькасць зелляў лячэння, што аўтаматычна падбяруцца." + +#: Source/options.cpp:839 +msgid "Full Heal Potion Pickup" +msgstr "Падбіранне зелляў поўнага лячэння" + +#: Source/options.cpp:839 +msgid "Number of Full Healing potions to pick up automatically." +msgstr "Колькасць зелляў поўнага лячэння, што аўтаматычна падбяруцца." + +#: Source/options.cpp:840 +msgid "Mana Potion Pickup" +msgstr "Падбіранне зелляў маны" + +#: Source/options.cpp:840 +msgid "Number of Mana potions to pick up automatically." +msgstr "Колькасць зелляў маны, што аўтаматычна падбяруцца." + +#: Source/options.cpp:841 +msgid "Full Mana Potion Pickup" +msgstr "Падбіранне зелляў поўнай маны" + +#: Source/options.cpp:841 +msgid "Number of Full Mana potions to pick up automatically." +msgstr "Колькасць зелляў поўнай маны, што аўтаматычна падбяруцца." + +#: Source/options.cpp:842 +msgid "Rejuvenation Potion Pickup" +msgstr "Падбіранне зелляў аднаўлення" + +#: Source/options.cpp:842 +msgid "Number of Rejuvenation potions to pick up automatically." +msgstr "Колькасць зелляў аднаўлення, што аўтаматычна падбяруцца." + +#: Source/options.cpp:843 +msgid "Full Rejuvenation Potion Pickup" +msgstr "Падбіранне зелляў поўнага аднаўлення" + +#: Source/options.cpp:843 +msgid "Number of Full Rejuvenation potions to pick up automatically." +msgstr "Колькасць зелляў поўнага аднаўлення, што аўтаматычна падбяруцца." + +#: Source/options.cpp:886 +msgid "Controller" +msgstr "Кантролер" + +#: Source/options.cpp:886 +msgid "Controller Settings" +msgstr "Налады кантролера" + +#: Source/options.cpp:895 +msgid "Network" +msgstr "Сетка" + +#: Source/options.cpp:895 +msgid "Network Settings" +msgstr "Налады Сеткі" + +#: Source/options.cpp:907 +msgid "Chat" +msgstr "Чат" + +#: Source/options.cpp:907 +msgid "Chat Settings" +msgstr "Налады чата" + +#: Source/options.cpp:916 Source/options.cpp:1032 +msgid "Language" +msgstr "Мова" + +#: Source/options.cpp:916 +msgid "Define what language to use in game." +msgstr "Выбраць мову для гульні." + +#: Source/options.cpp:1032 +msgid "Language Settings" +msgstr "Налады Мовы" + +#: Source/options.cpp:1044 +msgid "Keymapping" +msgstr "Раскладка" + +#: Source/options.cpp:1044 +msgid "Keymapping Settings" +msgstr "Налады раскладкі" + +#: Source/panels/charpanel.cpp:130 +msgid "Level" +msgstr "Узровень" + +#: Source/panels/charpanel.cpp:132 +msgid "Experience" +msgstr "Досвед" + +#: Source/panels/charpanel.cpp:134 +msgid "Next level" +msgstr "Наступны ўзровень" + +#: Source/panels/charpanel.cpp:142 +msgid "Base" +msgstr "Базавы" + +#: Source/panels/charpanel.cpp:143 +msgid "Now" +msgstr "Цяперашні" + +#: Source/panels/charpanel.cpp:144 +msgid "Strength" +msgstr "Моц" + +#: Source/panels/charpanel.cpp:148 +msgid "Magic" +msgstr "Магія" + +#: Source/panels/charpanel.cpp:152 +msgid "Dexterity" +msgstr "Спрыт" + +#: Source/panels/charpanel.cpp:155 +msgid "Vitality" +msgstr "Жывучасць" + +#: Source/panels/charpanel.cpp:158 +msgid "Points to distribute" +msgstr "Ачкі на размеркаванне" + +#: Source/panels/charpanel.cpp:168 +msgid "Armor class" +msgstr "Клас даспехаў" + +#: Source/panels/charpanel.cpp:170 +msgid "To hit" +msgstr "Ударыць" + +#: Source/panels/charpanel.cpp:172 +msgid "Damage" +msgstr "Шкода" + +#: Source/panels/charpanel.cpp:179 +msgid "Life" +msgstr "Жыццё" + +#: Source/panels/charpanel.cpp:183 +msgid "Mana" +msgstr "Мана" + +#: Source/panels/charpanel.cpp:188 +msgid "Resist magic" +msgstr "Супраціў магіі" + +#: Source/panels/charpanel.cpp:190 +msgid "Resist fire" +msgstr "Супраціў агню" + +#: Source/panels/charpanel.cpp:192 +msgid "Resist lightning" +msgstr "Супраціў маланцы" + +#: Source/panels/mainpanel.cpp:62 Source/panels/mainpanel.cpp:106 +#: Source/panels/mainpanel.cpp:108 +msgid "voice" +msgstr "голас" + +#: Source/panels/mainpanel.cpp:84 +msgid "char" +msgstr "перс" + +#: Source/panels/mainpanel.cpp:85 +msgid "quests" +msgstr "квэсты" + +#: Source/panels/mainpanel.cpp:86 +msgid "map" +msgstr "мапа" + +#: Source/panels/mainpanel.cpp:87 +msgid "menu" +msgstr "меню" + +#: Source/panels/mainpanel.cpp:88 +msgid "inv" +msgstr "інв" + +#: Source/panels/mainpanel.cpp:89 +msgid "spells" +msgstr "чары" + +#: Source/panels/mainpanel.cpp:101 Source/panels/mainpanel.cpp:103 +#: Source/panels/mainpanel.cpp:105 +msgid "mute" +msgstr "мут" + +#: Source/panels/spell_book.cpp:155 Source/panels/spell_list.cpp:162 +msgid "Skill" +msgstr "Уменне" + +#: Source/panels/spell_book.cpp:159 +msgid "Staff ({:d} charge)" +msgid_plural "Staff ({:d} charges)" +msgstr[0] "Посах ({:d} зарад)" +msgstr[1] "Посах ({:d} зарады)" +msgstr[2] "Посах ({:d} зарадаў)" + +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:164 +msgctxt "spellbook" +msgid "Level {:d}" +msgstr "Узровень {:d}" + +#: Source/panels/spell_book.cpp:166 +msgid "Unusable" +msgstr "Нягодны" + +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:174 +msgid "Heals: {:d} - {:d}" +msgstr "Лечыць: {:d} – {:d}" + +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:176 +msgid "Damage: {:d} - {:d}" +msgstr "Шкода: {:d} – {:d}" + +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:180 +msgid "Dmg: 1/3 target hp" +msgstr "Шкд: 1/3 хп цэлі" + +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:182 +msgctxt "spellbook" +msgid "Mana: {:d}" +msgstr "Мана: {:d}" + +#: Source/panels/spell_list.cpp:169 +msgid "Spell" +msgstr "Чара" + +#: Source/panels/spell_list.cpp:172 +msgid "Damages undead only" +msgstr "Шкодзіць толькі наўцам" + +#: Source/panels/spell_list.cpp:183 +msgid "Scroll" +msgstr "Скрутак" + +#: Source/panels/spell_list.cpp:204 +msgid "Spell Hotkey {:s}" +msgstr "Гарачая клавіша Чары {:s}" + +#: Source/pfile.cpp:266 +msgid "Failed to open player archive for writing." +msgstr "Не ўдалося адкрыць архіў гульца для запісу." + +#: Source/pfile.cpp:308 +msgid "Failed to open stash archive for writing." +msgstr "Не ўдалося адкрыць схаваны архіў для запісу." + +#: Source/pfile.cpp:419 +msgid "Unable to open archive" +msgstr "Немагчыма адкрыць архіў" + +#: Source/pfile.cpp:421 +msgid "Unable to load character" +msgstr "Немагчыма загрузіць персанажа" + +#: Source/pfile.cpp:445 Source/pfile.cpp:465 +msgid "Unable to read to save file archive" +msgstr "Немагчыма прачытаць, каб захаваць архіў файла" + +#: Source/pfile.cpp:484 +msgid "Unable to write to save file archive" +msgstr "Немагчыма запісаць, каб захаваць архіў файла" + +#: Source/plrmsg.cpp:83 Source/qol/chatlog.cpp:126 +msgid "{:s} (lvl {:d}): " +msgstr "{:s} (узр {:d}): " + +#: Source/qol/chatlog.cpp:154 +msgid "Chat History (Messages: {:d})" +msgstr "Гісторыя чата (Паведамленняў: {:d})" + +#: Source/qol/itemlabels.cpp:72 +msgid "{:s} gold" +msgstr "{:s} золата" + +#: Source/qol/stash.cpp:619 +msgid "How many gold pieces do you want to withdraw?" +msgstr "Колькі манет хочаце пакінуць?" + +#. TRANSLATORS: Thousands separator +#: Source/qol/xpbar.cpp:61 +msgid "," +msgstr "," + +#: Source/qol/xpbar.cpp:146 +msgid "Level {:d}" +msgstr "Узровень {:d}" + +#: Source/qol/xpbar.cpp:152 Source/qol/xpbar.cpp:160 +msgid "Experience: {:s}" +msgstr "Досвед {:s}" + +#: Source/qol/xpbar.cpp:153 +msgid "Maximum Level" +msgstr "Максімальны ўзровень" + +#: Source/qol/xpbar.cpp:161 +msgid "Next Level: {:s}" +msgstr "Наступны ўзровень {:s}" + +#: Source/qol/xpbar.cpp:162 +msgid "{:s} to Level {:d}" +msgstr "{:s} да ўзроўню {:d}" + +#. TRANSLATORS: Quest Name Block +#: Source/quests.cpp:44 +msgid "The Magic Rock" +msgstr "Магічны камень" + +#: Source/quests.cpp:46 +msgid "Gharbad The Weak" +msgstr "Кволы Гарбад" + +#: Source/quests.cpp:47 +msgid "Zhar the Mad" +msgstr "Шалёны Зар" + +#: Source/quests.cpp:48 +msgid "Lachdanan" +msgstr "Лакданан" + +#: Source/quests.cpp:50 +msgid "The Butcher" +msgstr "Мяснік" + +#: Source/quests.cpp:51 +msgid "Ogden's Sign" +msgstr "Знак Огдэна" + +#: Source/quests.cpp:52 +msgid "Halls of the Blind" +msgstr "Залы Сляпых" + +#: Source/quests.cpp:53 +msgid "Valor" +msgstr "Адвага" + +#: Source/quests.cpp:55 +msgid "Warlord of Blood" +msgstr "Крывавы ваявода" + +#: Source/quests.cpp:56 +msgid "The Curse of King Leoric" +msgstr "Праклён караля Леорыка" + +#: Source/quests.cpp:57 Source/setmaps.cpp:27 +msgid "Poisoned Water Supply" +msgstr "Атручаны запас вады" + +#. TRANSLATORS: Quest Map +#: Source/quests.cpp:58 Source/quests.cpp:94 +msgid "The Chamber of Bone" +msgstr "Пакой Касцей" + +#: Source/quests.cpp:59 +msgid "Archbishop Lazarus" +msgstr "Архібіскуп Лазар" + +#: Source/quests.cpp:60 +msgid "Grave Matters" +msgstr "Магільныя справы" + +#: Source/quests.cpp:61 +msgid "Farmer's Orchard" +msgstr "Садок фермера" + +#: Source/quests.cpp:62 +msgid "Little Girl" +msgstr "Дзяўчынка" + +#: Source/quests.cpp:63 +msgid "Wandering Trader" +msgstr "Вандроўны гандляр" + +#: Source/quests.cpp:64 +msgid "The Defiler" +msgstr "Паганнік" + +#: Source/quests.cpp:65 +msgid "Na-Krul" +msgstr "На-Крул" + +#: Source/quests.cpp:66 Source/trigs.cpp:449 +msgid "Cornerstone of the World" +msgstr "Краевугольны камень свету" + +#. TRANSLATORS: Quest Name Block end +#: Source/quests.cpp:67 +msgid "The Jersey's Jersey" +msgstr "Джэрсі Джэрсі" + +#. TRANSLATORS: Quest Map +#: Source/quests.cpp:93 +msgid "King Leoric's Tomb" +msgstr "Грабніца караля Леорыка" + +#. TRANSLATORS: Quest Map +#: Source/quests.cpp:95 Source/setmaps.cpp:26 +msgid "Maze" +msgstr "Лабірынт" + +#. TRANSLATORS: Quest Map +#: Source/quests.cpp:96 +msgid "A Dark Passage" +msgstr "Цёмны Пераход" + +#. TRANSLATORS: Quest Map +#: Source/quests.cpp:97 +msgid "Unholy Altar" +msgstr "Паганы алтар" + +#. TRANSLATORS: Used for Quest Portals. {:s} is a Map Name +#: Source/quests.cpp:451 +msgid "To {:s}" +msgstr "Для {:s}" + +#: Source/setmaps.cpp:24 +msgid "Skeleton King's Lair" +msgstr "Логава Караля шкілетаў" + +#: Source/setmaps.cpp:25 +msgid "Chamber of Bone" +msgstr "Пакой Касцей" + +#: Source/setmaps.cpp:28 +msgid "Archbishop Lazarus' Lair" +msgstr "Логава архібіскупа Лазара" + +#: Source/spelldat.cpp:16 +msgctxt "spell" +msgid "Firebolt" +msgstr "Вогненная бліскавіца" + +#: Source/spelldat.cpp:17 +msgctxt "spell" +msgid "Healing" +msgstr "Лячэнне" + +#: Source/spelldat.cpp:18 +msgctxt "spell" +msgid "Lightning" +msgstr "Маланка" + +#: Source/spelldat.cpp:19 +msgctxt "spell" +msgid "Flash" +msgstr "Бліск" + +#: Source/spelldat.cpp:20 +msgctxt "spell" +msgid "Identify" +msgstr "Выявіць" + +#: Source/spelldat.cpp:21 +msgctxt "spell" +msgid "Fire Wall" +msgstr "Сцяна агню" + +#: Source/spelldat.cpp:22 +msgctxt "spell" +msgid "Town Portal" +msgstr "Партал у Горад" + +#: Source/spelldat.cpp:23 +msgctxt "spell" +msgid "Stone Curse" +msgstr "Каменны праклён" + +#: Source/spelldat.cpp:24 +msgctxt "spell" +msgid "Infravision" +msgstr "Скрозьбачанне" + +#: Source/spelldat.cpp:25 +msgctxt "spell" +msgid "Phasing" +msgstr "Перамяшчэнне" + +#: Source/spelldat.cpp:26 +msgctxt "spell" +msgid "Mana Shield" +msgstr "Шчыт маны" + +#: Source/spelldat.cpp:27 +msgctxt "spell" +msgid "Fireball" +msgstr "Вогненны шар" + +#: Source/spelldat.cpp:28 +msgctxt "spell" +msgid "Guardian" +msgstr "Стораж" + +#: Source/spelldat.cpp:29 +msgctxt "spell" +msgid "Chain Lightning" +msgstr "Маланкавы ланцуг" + +#: Source/spelldat.cpp:30 +msgctxt "spell" +msgid "Flame Wave" +msgstr "Хваля полымя" + +#: Source/spelldat.cpp:31 +msgctxt "spell" +msgid "Doom Serpents" +msgstr "Змеі пагібелі" + +#: Source/spelldat.cpp:32 +msgctxt "spell" +msgid "Blood Ritual" +msgstr "Крывавы рытуал" + +#: Source/spelldat.cpp:33 +msgctxt "spell" +msgid "Nova" +msgstr "Новая Зорка" + +#: Source/spelldat.cpp:34 +msgctxt "spell" +msgid "Invisibility" +msgstr "Нябачнасць" + +#: Source/spelldat.cpp:35 +msgctxt "spell" +msgid "Inferno" +msgstr "Пекла" + +#: Source/spelldat.cpp:36 +msgctxt "spell" +msgid "Golem" +msgstr "Голем" + +#: Source/spelldat.cpp:37 +msgctxt "spell" +msgid "Rage" +msgstr "Гнеў" + +#: Source/spelldat.cpp:38 +msgctxt "spell" +msgid "Teleport" +msgstr "Тэлепорт" + +#: Source/spelldat.cpp:39 +msgctxt "spell" +msgid "Apocalypse" +msgstr "Апакаліпсіс" + +#: Source/spelldat.cpp:40 +msgctxt "spell" +msgid "Etherealize" +msgstr "Этэрызаваць" + +#: Source/spelldat.cpp:41 +msgctxt "spell" +msgid "Item Repair" +msgstr "Наладка рэчы" + +#: Source/spelldat.cpp:42 +msgctxt "spell" +msgid "Staff Recharge" +msgstr "Перазарадка посаха" + +#: Source/spelldat.cpp:43 +msgctxt "spell" +msgid "Trap Disarm" +msgstr "Абяскоджванне пасткі" + +#: Source/spelldat.cpp:44 +msgctxt "spell" +msgid "Elemental" +msgstr "Элементаль" + +#: Source/spelldat.cpp:45 +msgctxt "spell" +msgid "Charged Bolt" +msgstr "Зараджаная бліскавіца" + +#: Source/spelldat.cpp:46 +msgctxt "spell" +msgid "Holy Bolt" +msgstr "Святая бліскавіца" + +#: Source/spelldat.cpp:47 +msgctxt "spell" +msgid "Resurrect" +msgstr "Уваскрасіць" + +#: Source/spelldat.cpp:48 +msgctxt "spell" +msgid "Telekinesis" +msgstr "Тэлекінэз" + +#: Source/spelldat.cpp:49 +msgctxt "spell" +msgid "Heal Other" +msgstr "Лячы Блізкага" + +#: Source/spelldat.cpp:50 +msgctxt "spell" +msgid "Blood Star" +msgstr "Крывавая зорка" + +#: Source/spelldat.cpp:51 +msgctxt "spell" +msgid "Bone Spirit" +msgstr "Дух косці" + +#: Source/spelldat.cpp:52 +msgctxt "spell" +msgid "Mana" +msgstr "Мана" + +#: Source/spelldat.cpp:53 +msgctxt "spell" +msgid "the Magi" +msgstr "Мудрацы" + +#: Source/spelldat.cpp:54 +msgctxt "spell" +msgid "the Jester" +msgstr "Блазан" + +#: Source/spelldat.cpp:55 +msgctxt "spell" +msgid "Lightning Wall" +msgstr "Сцяна маланкі" + +#: Source/spelldat.cpp:56 +msgctxt "spell" +msgid "Immolation" +msgstr "Спаленне" + +#: Source/spelldat.cpp:57 +msgctxt "spell" +msgid "Warp" +msgstr "Скажэнне" + +#: Source/spelldat.cpp:58 +msgctxt "spell" +msgid "Reflect" +msgstr "Адбіццё" + +#: Source/spelldat.cpp:59 +msgctxt "spell" +msgid "Berserk" +msgstr "Берсерк" + +#: Source/spelldat.cpp:60 +msgctxt "spell" +msgid "Ring of Fire" +msgstr "Пярсцёнак агню" + +#: Source/spelldat.cpp:61 +msgctxt "spell" +msgid "Search" +msgstr "Пошук" + +#: Source/spelldat.cpp:62 +msgctxt "spell" +msgid "Rune of Fire" +msgstr "Руна агню" + +#: Source/spelldat.cpp:63 +msgctxt "spell" +msgid "Rune of Light" +msgstr "Руна святла" + +#: Source/spelldat.cpp:64 +msgctxt "spell" +msgid "Rune of Nova" +msgstr "Руна новай зоркі" + +#: Source/spelldat.cpp:65 +msgctxt "spell" +msgid "Rune of Immolation" +msgstr "Руна спалення" + +#: Source/spelldat.cpp:66 +msgctxt "spell" +msgid "Rune of Stone" +msgstr "Руна каменя" + +#: Source/stores.cpp:93 +msgid "Griswold" +msgstr "Грызвальд" + +#: Source/stores.cpp:94 +msgid "Pepin" +msgstr "Піпін" + +#: Source/stores.cpp:96 +msgid "Ogden" +msgstr "Огдэн" + +#: Source/stores.cpp:97 +msgid "Cain" +msgstr "Каін" + +#: Source/stores.cpp:98 +msgid "Farnham" +msgstr "Фарнам" + +#: Source/stores.cpp:99 +msgid "Adria" +msgstr "Эйдрыя" + +#: Source/stores.cpp:100 Source/stores.cpp:1313 +msgid "Gillian" +msgstr "Джыльен" + +#: Source/stores.cpp:101 +msgid "Wirt" +msgstr "Вірт" + +#: Source/stores.cpp:220 Source/stores.cpp:227 +msgid "Back" +msgstr "Назад" + +#: Source/stores.cpp:249 Source/stores.cpp:255 +msgid ", " +msgstr ", " + +#: Source/stores.cpp:266 +msgid "Damage: {:d}-{:d} " +msgstr "Шкода {:d}-{:d} " + +#: Source/stores.cpp:268 +msgid "Armor: {:d} " +msgstr "Браня: {:d} " + +#: Source/stores.cpp:270 +msgid "Dur: {:d}/{:d}, " +msgstr "Мац: {:d}/{:d}, " + +#: Source/stores.cpp:272 +msgid "Indestructible, " +msgstr "Незнішчальны " + +#: Source/stores.cpp:280 +msgid "No required attributes" +msgstr "Няма патрэбных атрыбутаў" + +#: Source/stores.cpp:312 Source/stores.cpp:1064 Source/stores.cpp:1300 +msgid "Welcome to the" +msgstr "Вітаем у" + +#: Source/stores.cpp:313 +msgid "Blacksmith's shop" +msgstr "Кавальская майстэрня" + +#: Source/stores.cpp:314 Source/stores.cpp:668 Source/stores.cpp:1066 +#: Source/stores.cpp:1124 Source/stores.cpp:1302 Source/stores.cpp:1314 +#: Source/stores.cpp:1327 +msgid "Would you like to:" +msgstr "Ці хацелі б вы:" + +#: Source/stores.cpp:315 +msgid "Talk to Griswold" +msgstr "Пагаварыць з Грызвальдам" + +#: Source/stores.cpp:316 +msgid "Buy basic items" +msgstr "Купіць базавыя рэчы" + +#: Source/stores.cpp:317 +msgid "Buy premium items" +msgstr "Купіць асаблівыя рэчы" + +#: Source/stores.cpp:318 Source/stores.cpp:671 +msgid "Sell items" +msgstr "Прадаць" + +#: Source/stores.cpp:319 +msgid "Repair items" +msgstr "Наладзіць" + +#: Source/stores.cpp:320 +msgid "Leave the shop" +msgstr "Сысці з крамы" + +#: Source/stores.cpp:363 Source/stores.cpp:728 Source/stores.cpp:1101 +msgid "I have these items for sale:" +msgstr "Маю тут на продаж:" + +#: Source/stores.cpp:428 +msgid "I have these premium items for sale:" +msgstr "На продаж маю асаблівыя рэчы:" + +#: Source/stores.cpp:547 Source/stores.cpp:821 +msgid "You have nothing I want." +msgstr "Не маеш, што мне трэба." + +#: Source/stores.cpp:558 Source/stores.cpp:833 +msgid "Which item is for sale?" +msgstr "Што прадаеш?" + +#: Source/stores.cpp:629 +msgid "You have nothing to repair." +msgstr "Няма чаго ладзіць." + +#: Source/stores.cpp:640 +msgid "Repair which item?" +msgstr "Якую рэч наладзіць?" + +#: Source/stores.cpp:667 +msgid "Witch's shack" +msgstr "Ведзьмін будан" + +#: Source/stores.cpp:669 +msgid "Talk to Adria" +msgstr "Пагаварыць з Эйдрыяй" + +#: Source/stores.cpp:670 Source/stores.cpp:1068 +msgid "Buy items" +msgstr "Купіць" + +#: Source/stores.cpp:672 +msgid "Recharge staves" +msgstr "Перазарадзіць посахі" + +#: Source/stores.cpp:673 +msgid "Leave the shack" +msgstr "Сысці з будана" + +#: Source/stores.cpp:895 +msgid "You have nothing to recharge." +msgstr "Няма чаго перазараджаць." + +#: Source/stores.cpp:906 +msgid "Recharge which item?" +msgstr "Якую рэч перазарадзіць?" + +#: Source/stores.cpp:919 +msgid "You do not have enough gold" +msgstr "Не хапае золата" + +#: Source/stores.cpp:927 +msgid "You do not have enough room in inventory" +msgstr "Не хапае месца ў інтвентары" + +#: Source/stores.cpp:966 +msgid "Do we have a deal?" +msgstr "Ну, згода?" + +#: Source/stores.cpp:969 +msgid "Are you sure you want to identify this item?" +msgstr "Праўда хочаце выявіць гэту рэч?" + +#: Source/stores.cpp:975 +msgid "Are you sure you want to buy this item?" +msgstr "Праўда хочаце гэта купіць?" + +#: Source/stores.cpp:978 +msgid "Are you sure you want to recharge this item?" +msgstr "Праўда хочаце перазарадзіць гэту рэч?" + +#: Source/stores.cpp:982 +msgid "Are you sure you want to sell this item?" +msgstr "Праўда хочаце гэта прадаць?" + +#: Source/stores.cpp:985 +msgid "Are you sure you want to repair this item?" +msgstr "Праўда хочаце наладзіць гэту рэч?" + +#: Source/stores.cpp:999 Source/towners.cpp:150 +msgid "Wirt the Peg-legged boy" +msgstr "Аднаногі хлопчык Вірт" + +#: Source/stores.cpp:1002 Source/stores.cpp:1009 +msgid "Talk to Wirt" +msgstr "Пагаварыць з Віртам" + +#: Source/stores.cpp:1003 +msgid "I have something for sale," +msgstr "Маю тут на продаж," + +#: Source/stores.cpp:1004 +msgid "but it will cost 50 gold" +msgstr "але проста каб зірнуць" + +#: Source/stores.cpp:1005 +msgid "just to take a look. " +msgstr "50 золата. " + +#: Source/stores.cpp:1006 +msgid "What have you got?" +msgstr "Што маеш?" + +#: Source/stores.cpp:1007 Source/stores.cpp:1010 Source/stores.cpp:1127 +#: Source/stores.cpp:1317 +msgid "Say goodbye" +msgstr "Развітацца" + +#: Source/stores.cpp:1020 +msgid "I have this item for sale:" +msgstr "Прадаю вось гэту рэч:" + +#: Source/stores.cpp:1042 +msgid "Leave" +msgstr "Сысці" + +#: Source/stores.cpp:1065 +msgid "Healer's home" +msgstr "Дом лекара" + +#: Source/stores.cpp:1067 +msgid "Talk to Pepin" +msgstr "Пагаварыць з Піпінам" + +#: Source/stores.cpp:1069 +msgid "Leave Healer's home" +msgstr "Сысці з дома лекара" + +#: Source/stores.cpp:1123 +msgid "The Town Elder" +msgstr "Гарадскі старэйшына" + +#: Source/stores.cpp:1125 +msgid "Talk to Cain" +msgstr "Пагаварыць з Кейнам" + +#: Source/stores.cpp:1126 +msgid "Identify an item" +msgstr "Выявіць рэч" + +#: Source/stores.cpp:1219 +msgid "You have nothing to identify." +msgstr "Няма чаго выяўляць." + +#: Source/stores.cpp:1230 +msgid "Identify which item?" +msgstr "Якую рэч выявіць?" + +#: Source/stores.cpp:1247 +msgid "This item is:" +msgstr "Гэта рэч:" + +#: Source/stores.cpp:1250 +msgid "Done" +msgstr "Гатова" + +#: Source/stores.cpp:1259 +msgid "Talk to {:s}" +msgstr "Пагаварыць з {:s}" + +#: Source/stores.cpp:1262 +msgid "Talking to {:s}" +msgstr "Гаворыць з {:s}" + +#: Source/stores.cpp:1263 +msgid "is not available" +msgstr "недаступны" + +#: Source/stores.cpp:1264 +msgid "in the shareware" +msgstr "ва ўмоўна-бясплатнай версіі" + +#: Source/stores.cpp:1265 +msgid "version" +msgstr "версія" + +#: Source/stores.cpp:1292 +msgid "Gossip" +msgstr "Пляткарыць" + +#: Source/stores.cpp:1301 +msgid "Rising Sun" +msgstr "Узыходнае Сонца" + +#: Source/stores.cpp:1303 +msgid "Talk to Ogden" +msgstr "Пагаварыць з Огдэнам" + +#: Source/stores.cpp:1304 +msgid "Leave the tavern" +msgstr "Сысці з карчмы" + +#: Source/stores.cpp:1315 +msgid "Talk to Gillian" +msgstr "Пагаварыць з Джыліен" + +#: Source/stores.cpp:1316 +msgid "Access Storage" +msgstr "Адкрыць сховішча" + +#: Source/stores.cpp:1326 Source/towners.cpp:205 +msgid "Farnham the Drunk" +msgstr "П'яніца Фарнам" + +#: Source/stores.cpp:1328 +msgid "Talk to Farnham" +msgstr "Пагаварыць з Фарнамам" + +#: Source/stores.cpp:1329 +msgid "Say Goodbye" +msgstr "Развітацца" + +#: Source/stores.cpp:2458 +msgid "Your gold: {:s}" +msgstr "Ваша золата: {:s}" + +#. TRANSLATORS: Quest dialog spoken by Cain +#: Source/textdat.cpp:15 +msgid "" +" Ahh, the story of our King, is it? The tragic fall of Leoric was a harsh " +"blow to this land. The people always loved the King, and now they live in " +"mortal fear of him. The question that I keep asking myself is how he could " +"have fallen so far from the Light, as Leoric had always been the holiest of " +"men. Only the vilest powers of Hell could so utterly destroy a man from " +"within..." +msgstr "" +" А-а, цікавішся нашым Каралём, гм? Трагічнае падзенне Леорыка было страшным " +"ударам для гэтай зямлі. Людзі заўсёды любілі Караля, зараз жа яны да смерці " +"страшацца яго. Я ўсё пытаю сябе, як ён так аддаліўся ад Святла, бо Леорык " +"заўсёды быў з найсвяцейшых між людзьмі. Толькі самыя паганыя сілы Пекла " +"маглі так вынішчыць чалавека знутры..." + +#. TRANSLATORS: Quest dialog spoken by Ogden +#: Source/textdat.cpp:17 +msgid "" +"The village needs your help, good master! Some months ago King Leoric's son, " +"Prince Albrecht, was kidnapped. The King went into a rage and scoured the " +"village for his missing child. With each passing day, Leoric seemed to slip " +"deeper into madness. He sought to blame innocent townsfolk for the boy's " +"disappearance and had them brutally executed. Less than half of us survived " +"his insanity...\n" +" \n" +"The King's Knights and Priests tried to placate him, but he turned against " +"them and sadly, they were forced to kill him. With his dying breath the King " +"called down a terrible curse upon his former followers. He vowed that they " +"would serve him in darkness forever...\n" +" \n" +"This is where things take an even darker twist than I thought possible! Our " +"former King has risen from his eternal sleep and now commands a legion of " +"undead minions within the Labyrinth. His body was buried in a tomb three " +"levels beneath the Cathedral. Please, good master, put his soul at ease by " +"destroying his now cursed form..." +msgstr "" +"Вёсцы патрэбна твая дапамога, добры чалавек! Колькі месяцаў таму Прынца " +"Альбрэхта, Караля Леорыкава сына, скралі. Кароль раз'юшыўся і пачаў " +"абшнарваць тут усюль, шукаючы дзіця. З кожным днём Леорык усё больш шалеў ад " +"гора. Ён абвінаваціў ва ўсім нявінных гараджан і жорстка пакараў многіх " +"смерцю. Менш паловы з нас вырабілася ад яго вар'яцтва...\n" +"Рыцары Караля і Святары стараліся яго супакоіць, але ён і на іх узбурыўся, " +"і, на жаль, ім давялося забіць яго. На самым сконе ён страшна пракляў сваіх " +"былых прыхільнікаў. Ён прысягнуў, што яны будуць служыць яму ў цемры " +"вечна...\n" +"І тут пачалося такое, я ўжо думаў горш не можа быць! Былы Кароль прачнуўся " +"ад вечнага сну, і цяпер у Лабірынце ўзначаліў легіён мярцоў. Яго цела " +"пахавалі ў грабніцы на трэцім ўзроўні пад Саборам. Калі ласка, добры " +"чалавек, сунімі яго душу, зніжчы яго праклятае аблічча..." + +#. TRANSLATORS: Quest dialog spoken by Ogden +#: Source/textdat.cpp:19 +msgid "" +"As I told you, good master, the King was entombed three levels below. He's " +"down there, waiting in the putrid darkness for his chance to destroy this " +"land..." +msgstr "" +"Як я і гаварыў, добры чалавек, Караля пахавалі на тры паверхі ўніз. Ён там, " +"у агіднай цемры, толькі і чакае магчымасці знішчыць гэты край..." + +#. TRANSLATORS: Quest dialog spoken by Ogden (Quest End) +#: Source/textdat.cpp:21 +msgid "" +"The curse of our King has passed, but I fear that it was only part of a " +"greater evil at work. However, we may yet be saved from the darkness that " +"consumes our land, for your victory is a good omen. May Light guide you on " +"your way, good master." +msgstr "" +"Праклён нашага Караля прайшоў, але баюся, што вінавата тут большае зло. " +"Аднак мы яшчэ можам выратавацца ад цемры, што ахінае наш край: твая перамога " +"– добры знак. Няхай Святло вядзе цябе, добры чалавек." + +#. TRANSLATORS: Quest dialog spoken by Pepin +#: Source/textdat.cpp:23 +msgid "" +"The loss of his son was too much for King Leoric. I did what I could to ease " +"his madness, but in the end it overcame him. A black curse has hung over " +"this kingdom from that day forward, but perhaps if you were to free his " +"spirit from his earthly prison, the curse would be lifted..." +msgstr "" +"Згуба сына давяла Караля. Я зрабіў усё, што мог, каб палегчыць яго " +"шаленства, але нарэшце яно адолела яго. Чорны праклён навіс над каралеўствам " +"з таго дня, але, можа, калі б яго дух вызвалілі ад яго зямной турмы, праклён " +"бы адрабіўся..." + +#. TRANSLATORS: Quest dialog spoken by Gillian +#: Source/textdat.cpp:25 +msgid "" +"I don't like to think about how the King died. I like to remember him for " +"the kind and just ruler that he was. His death was so sad and seemed very " +"wrong, somehow." +msgstr "" +"Не хачу думаць пра смерць Караля. Мне падабаецца ўспамінаць яго добрым і " +"справядлівым, бо такім ён і быў. Яго смерць была такой сумнай, і " +"няправільнай, чамусьці." + +#. TRANSLATORS: Quest dialog spoken by Griswold +#: Source/textdat.cpp:27 +msgid "" +"I made many of the weapons and most of the armor that King Leoric used to " +"outfit his knights. I even crafted a huge two-handed sword of the finest " +"mithril for him, as well as a field crown to match. I still cannot believe " +"how he died, but it must have been some sinister force that drove him insane!" +msgstr "" +"Я выкуў шмат зброі і большую частку даспехаў, якія Кароль Леорык даваў сваім " +"рыцарам. Для яго я нават вымайстраваў вялізны двуручны меч з " +"найдасканалейшаго мітрыля, як і баявую карону ў том жа духу. Дасюль не магу " +"паверыць, што ён умёр, але гэто дакладно нейкая паганая воля звяла яго з " +"розуму!" + +#. TRANSLATORS: Quest dialog spoken by Farnham +#: Source/textdat.cpp:29 +msgid "" +"I don't care about that. Listen, no skeleton is gonna be MY king. Leoric is " +"King. King, so you hear me? HAIL TO THE KING!" +msgstr "" +"Ды начхаць мне на гэта. Слухай, ніякі шкілет не будзе МАІМ каралём. Леорык – " +"кароль. Кароль, чуеш? СЛАВА КАРАЛЮ!" + +#. TRANSLATORS: Quest dialog spoken by Adria +#: Source/textdat.cpp:31 +msgid "" +"The dead who walk among the living follow the cursed King. He holds the " +"power to raise yet more warriors for an ever growing army of the undead. If " +"you do not stop his reign, he will surely march across this land and slay " +"all who still live here." +msgstr "" +"Мёртвыя, што ходзяць меж жывых, служаць праклятаму Каралю. Ён валодае сілаю, " +"каб падняць у войска наўцоў яшчэ больш новых ваяроў. Калі не супыніш яго " +"панаванне, ён дакладна пройдзе маршам па гэтай зямлі, забіваючы ўсіх, хто " +"яшчэ тут жывы." + +#. TRANSLATORS: Quest dialog spoken by Wirt +#: Source/textdat.cpp:33 +msgid "" +"Look, I'm running a business here. I don't sell information, and I don't " +"care about some King that's been dead longer than I've been alive. If you " +"need something to use against this King of the undead, then I can help you " +"out..." +msgstr "" +"Слухай, я тут справу вяду. Інфармацыяй я не гандлюю, і мне ўсё роўна на " +"нейкага там Караля, каторы мёртвы дольш, чым я жывы. Калі ж табе трэба " +"нешта, каб біцца з ім ці з мярцамі яго, от тут я табе памочнік.." + +#. TRANSLATORS: Quest dialog spoken by The Skeleton King (Hostile) +#: Source/textdat.cpp:35 +msgid "" +"The warmth of life has entered my tomb. Prepare yourself, mortal, to serve " +"my Master for eternity!" +msgstr "" +"Цяпло жыцця ўвайшло ў мой склеп. Рыхтуйся, смертны, служыць майму Гаспадару " +"век вечны!" + +#. TRANSLATORS: Quest dialog spoken by Cain +#: Source/textdat.cpp:37 +msgid "" +"I see that this strange behavior puzzles you as well. I would surmise that " +"since many demons fear the light of the sun and believe that it holds great " +"power, it may be that the rising sun depicted on the sign you speak of has " +"led them to believe that it too holds some arcane powers. Hmm, perhaps they " +"are not all as smart as we had feared..." +msgstr "" +"Бачу, і цябе бянтэжаць гэтыя дзіўныя паводзіны. Магу толькі здагадвацца, але " +"раз многія дэманы баяцца сонечнага святла і вераць, што яно мае вялікую " +"сілу, магчыма, яны палічылі, што ўзыходнае сонца на знаку таксама стрымлівае " +"нейкую таемную сілу. Гм-м, мабыць, не такія яны і разумныя, як мы баяліся..." + +#. TRANSLATORS: Quest dialog spoken by Ogden +#: Source/textdat.cpp:39 +msgid "" +"Master, I have a strange experience to relate. I know that you have a great " +"knowledge of those monstrosities that inhabit the labyrinth, and this is " +"something that I cannot understand for the very life of me... I was awakened " +"during the night by a scraping sound just outside of my tavern. When I " +"looked out from my bedroom, I saw the shapes of small demon-like creatures " +"in the inn yard. After a short time, they ran off, but not before stealing " +"the sign to my inn. I don't know why the demons would steal my sign but " +"leave my family in peace... 'tis strange, no?" +msgstr "" +"Добры чалавек, я тут хацеў падзяліцца адным дзіўным досведам. Знаю, ты шмат " +"ведаеш пра пачвар, што насяляюць лабірынт, і я тут ніяк не магу зразумець " +"адну рэч... Я прачнуўся сярод ночы ад нейкага скрыгатання ля шынка. Я " +"выглянуў са спальні, аж убачыў на дварэ карчмы абрысы нейкіх чарцят. " +"Неўзабаве яны збеглі, але адно пасля таго, як скралі шыльду. Я не разумею, " +"нашто дэманы ўкралі знак, але маю сям'ю не чапілі... дзіўна, праўда?" + +#. TRANSLATORS: Quest dialog spoken by Ogden (Quest End) +#: Source/textdat.cpp:41 +msgid "" +"Oh, you didn't have to bring back my sign, but I suppose that it does save " +"me the expense of having another one made. Well, let me see, what could I " +"give you as a fee for finding it? Hmmm, what have we here... ah, yes! This " +"cap was left in one of the rooms by a magician who stayed here some time " +"ago. Perhaps it may be of some value to you." +msgstr "" +"Вой, табе не абавязкова было вяртаць знак, хаця так цяпер не трэба новы " +"рабіць, ці купляць. Ну, дай пагляну, чым ж я магу з табою разлічыцца? Гм-м-" +"м, што тут у нас... Ага! Вось, гэту шапку пакінуў адзін чараўнік у нашых " +"пакоях, ён нядаўна тут спыняўся. Можа табе будзе неяк карысна." + +#. TRANSLATORS: Quest dialog spoken by Pipin +#: Source/textdat.cpp:43 +msgid "" +"My goodness, demons running about the village at night, pillaging our homes " +"- is nothing sacred? I hope that Ogden and Garda are all right. I suppose " +"that they would come to see me if they were hurt..." +msgstr "" +"Бацюхны, дэманы бегаюць па вёсцы ўночы, рабуюць нашы дамы – хіба не " +"засталося нічога святога? Спадзяюся, Огдэн з Гардаю ў парадку. Яны б, " +"здаецца, прыйшлі, каб іх паранілі..." + +#. TRANSLATORS: Quest dialog spoken by Gillian +#: Source/textdat.cpp:45 +msgid "" +"Oh my! Is that where the sign went? My Grandmother and I must have slept " +"right through the whole thing. Thank the Light that those monsters didn't " +"attack the inn." +msgstr "" +"Авохці! Дык вось куды знак прапаў? Мы з бабуляю відаць усё праспалі. Дзякуй " +"Святлу гэтыя пачвары не напалі на карчму." + +#. TRANSLATORS: Quest dialog spoken by Griswold +#: Source/textdat.cpp:47 +msgid "" +"Demons stole Ogden's sign, you say? That doesn't sound much like the " +"atrocities I've heard of - or seen. \n" +" \n" +"Demons are concerned with ripping out your heart, not your signpost." +msgstr "" +"Кажаш, дэманы скралі Огдэнаў знак? Непадобно гэто да іхных лютасцяў, пра " +"якія я чуў. Ці якія бачыў сам. \n" +" \n" +"Дэманам бы сэрца табе вырваць, а не знак." + +#. TRANSLATORS: Quest dialog spoken by Farnham +#: Source/textdat.cpp:49 +msgid "" +"You know what I think? Somebody took that sign, and they gonna want lots of " +"money for it. If I was Ogden... and I'm not, but if I was... I'd just buy a " +"new sign with some pretty drawing on it. Maybe a nice mug of ale or a piece " +"of cheese..." +msgstr "" +"Ведаеш, што я мяркую? Нехта сцягнуў той знак, і цяпер запросіць за яго кучу " +"грашыскаў. Быў бы я Огдэнам... а я не Огдэн, але каб я быў... Я б проста " +"купіў новы, з якім малюнкам прыгожым. Мо з чарачкаю элю, або кавалачкам " +"сыру..." + +#. TRANSLATORS: Quest dialog spoken by Adria +#: Source/textdat.cpp:51 +msgid "" +"No mortal can truly understand the mind of the demon. \n" +" \n" +"Never let their erratic actions confuse you, as that too may be their plan." +msgstr "" +"Ніводзін смертны не можа дасканала ахапіць розум дэмана. \n" +" \n" +"Ніколі не давай іх цьмяным учынкам збіць цябе з тропу, бо гэта можа быць " +"частка іх плана." + +#. TRANSLATORS: Quest dialog spoken by Wirt +#: Source/textdat.cpp:53 +msgid "" +"What - is he saying I took that? I suppose that Griswold is on his side, " +"too. \n" +" \n" +"Look, I got over simple sign stealing months ago. You can't turn a profit on " +"a piece of wood." +msgstr "" +"Што... ён кажа, гэта я ўзяў? Відаць, і Грызвальд на яго баку.\n" +"\n" +"Слухай, я ўжо некалькі месяцаў крадзяжом шыльдаў не займаюся. На кавалку " +"дрэва не разжывешся." + +#. TRANSLATORS: Quest dialog spoken by Snotspill (Hostile) +#: Source/textdat.cpp:55 +msgid "" +"Hey - You that one that kill all! You get me Magic Banner or we attack! You " +"no leave with life! You kill big uglies and give back Magic. Go past corner " +"and door, find uglies. You give, you go!" +msgstr "" +"Гэй, гэта ты тут усіх б'еш! Нясі мне Магічны Сцяг, бо нападзём! З жыццём не " +"пойдзеш! Забі вялікіх гадасцяў і вярні Магічнага. Прайдзі ля вугла і " +"дзвярэй, знайдзі там гадасцяў. Аддасі, і хадзі!" + +#. TRANSLATORS: Quest dialog spoken by Snotspill (Hostile) +#: Source/textdat.cpp:57 +msgid "You kill uglies, get banner. You bring to me, or else..." +msgstr "Гадасцяў забі, сцяг забяры. Прынясеш мне, а не то..." + +#. TRANSLATORS: Quest dialog spoken by Snotspill (Hostile) +#: Source/textdat.cpp:59 +msgid "You give! Yes, good! Go now, we strong. We kill all with big Magic!" +msgstr "Дай! Так, добра! Ідзі, мы сільныя. Мы з вялікім Магічным усіх забіць!" + +#. TRANSLATORS: Quest dialog spoken by Cain +#: Source/textdat.cpp:61 +msgid "" +"This does not bode well, for it confirms my darkest fears. While I did not " +"allow myself to believe the ancient legends, I cannot deny them now. Perhaps " +"the time has come to reveal who I am.\n" +" \n" +"My true name is Deckard Cain the Elder, and I am the last descendant of an " +"ancient Brotherhood that was dedicated to safeguarding the secrets of a " +"timeless evil. An evil that quite obviously has now been released.\n" +" \n" +"The Archbishop Lazarus, once King Leoric's most trusted advisor, led a party " +"of simple townsfolk into the Labyrinth to find the King's missing son, " +"Albrecht. Quite some time passed before they returned, and only a few of " +"them escaped with their lives.\n" +" \n" +"Curse me for a fool! I should have suspected his veiled treachery then. It " +"must have been Lazarus himself who kidnapped Albrecht and has since hidden " +"him within the Labyrinth. I do not understand why the Archbishop turned to " +"the darkness, or what his interest is in the child, unless he means to " +"sacrifice him to his dark masters!\n" +" \n" +"That must be what he has planned! The survivors of his 'rescue party' say " +"that Lazarus was last seen running into the deepest bowels of the labyrinth. " +"You must hurry and save the prince from the sacrificial blade of this " +"demented fiend!" +msgstr "" +"Нічога добрага гэта не вяшчуе – усё пацвярджае мае найзмрочнейшыя страхі. " +"Хаця я адмаўляўся верыць старасвецкім легендам, цяпер я не магу. Прыйшла " +"пара адкрыць, хто я такі.\n" +"\n" +"Маё спраўдная імя Дэкард Кейн Старэйшы, і я апошні нашчадак старасвецкага " +"Брацтва, якое прысвяціла сябе ахове таямніц вечнага Зла. Зла, якое, цяпер " +"ужо відавочна, вызвалілі.\n" +"\n" +"Архібіскуп Лазар, калісьці дарадчык Караля Леорыка, якому той давяраў як " +"нікому, завёў натоўп простых гараджан у Лабірынт на пошукі сына Караля, " +"Альбрэхта. Мінула шмат часу, калі яны вярнуліся, і жывымі выратавалася зусім " +"мала.\n" +"\n" +"Бадай мяне, дурня! Я мусіў быў западозрыць скрыты падман. Гэта сам Лазар, " +"мусіць, і скраў Альбрэхта, і хаваў яго з тае пары ў Лабірынце. Але я не " +"разумею, чаму Архібіскуп звярнуўся да Цемры, ці якая яму карысць у дзіцяці. " +"Хіба толькі дзеля таго, каб ахвяраваць яго Цёмным Валадарам!\n" +"\n" +"Вось у чым была яго задума! Ацалелыя з таго “гурту ратавальнікаў” казалі, " +"што ў апошні раз, калі Лазара бачылі, ён збягаў у найглыбейшыя нетры " +"Лабірынта. Ты мусіш спяшацца, выратуй Прынца ад ахвярнага нажа гэтага " +"шалёнага ліхадзея!" + +#. TRANSLATORS: Quest dialog spoken by Cain +#: Source/textdat.cpp:63 +msgid "" +"You must hurry and rescue Albrecht from the hands of Lazarus. The prince and " +"the people of this kingdom are counting on you!" +msgstr "" +"Спяшайся! Альбрэхта трэба выратаваць з рук Лазара. Прынц і люд каралеўства " +"разлічваюць на цябе!" + +#. TRANSLATORS: Quest dialog spoken by Cain +#: Source/textdat.cpp:65 +msgid "" +"Your story is quite grim, my friend. Lazarus will surely burn in Hell for " +"his horrific deed. The boy that you describe is not our prince, but I " +"believe that Albrecht may yet be in danger. The symbol of power that you " +"speak of must be a portal in the very heart of the labyrinth.\n" +" \n" +"Know this, my friend - The evil that you move against is the dark Lord of " +"Terror. He is known to mortal men as Diablo. It was he who was imprisoned " +"within the Labyrinth many centuries ago and I fear that he seeks to once " +"again sow chaos in the realm of mankind. You must venture through the portal " +"and destroy Diablo before it is too late!" +msgstr "" +"Змрочная гісторыя твая, дружа мой. Лазар дакладна будзе гарэць у Пекле за " +"свой жудасны ўчынак. З твайго апісання гэта быў не наш прынц, але я думаю, " +"Альбрэхт яшчэ можа быць у бядзе. Сімвал сілы, які ты апісваеш, мусіць, " +"партал у самае сэрца лабірынта.\n" +"Ведай жа, дружа мой: зло, на якое ты ідзеш, – Цёмны Пан Жаху. Смяротным " +"людзям ён вядомы як Д’ябла. Гэта яго зняволілі у лабірынце многа вякоў таму, " +"і я страшуся, што ён зноў жадае пасеяць хаос на зямлі чалавечай. Ты мусіш " +"прайсці праз партал і знішчыць Д’ябла, покуль не позна!" + +#. TRANSLATORS: Quest dialog spoken by Ogden +#: Source/textdat.cpp:67 +msgid "" +"Lazarus was the Archbishop who led many of the townspeople into the " +"labyrinth. I lost many good friends that day, and Lazarus never returned. I " +"suppose he was killed along with most of the others. If you would do me a " +"favor, good master - please do not talk to Farnham about that day." +msgstr "" +"Лазар – гэта той Архібіскуп, каторы тады павёў многіх гараджан у лабірынт. У " +"той дзень я страціў многа сяброў, а Лазар так і не вярнуўся. Мяркую, яго " +"забілі, як і шмат каго яшчэ. Калі вам гэта не цяжар, прашу, зрабіце мне " +"ласку, не гаварыце з Фарнамам пра той дзень." + +#. TRANSLATORS: Quest dialog spoken by Pipin +#: Source/textdat.cpp:71 +msgid "" +"I was shocked when I heard of what the townspeople were planning to do that " +"night. I thought that of all people, Lazarus would have had more sense than " +"that. He was an Archbishop, and always seemed to care so much for the " +"townsfolk of Tristram. So many were injured, I could not save them all..." +msgstr "" +"Планы гараджан, калі я іх пачуў тае ночы, проста ашаламілі мяне. Я думаў, " +"што ў Лазара-то будзе больш развагі. Ён быў Архібіскупам, і заўсёды " +"падавалася, што ён вельмі любіў жыхароў Трыстрама. Столькі параненых, я не " +"змог выратаваць усіх..." + +#. TRANSLATORS: Quest dialog spoken by Gillian +#: Source/textdat.cpp:73 +msgid "" +"I remember Lazarus as being a very kind and giving man. He spoke at my " +"mother's funeral, and was supportive of my grandmother and myself in a very " +"troubled time. I pray every night that somehow, he is still alive and safe." +msgstr "" +"Я помню Лазара вельмі добрым і велікадушным чалавекам. Ён гаварыў на " +"пахаванні маёй мацеры, і заўсёды падтрымліваў маю бабулю і мяне у цяжкія " +"часы. Я малюся кожны вечар, што ён неяк і дасюль жывы і здаровы." + +#. TRANSLATORS: Quest dialog spoken by Griswold +#: Source/textdat.cpp:75 +msgid "" +"I was there when Lazarus led us into the labyrinth. He spoke of holy " +"retribution, but when we started fighting those hellspawn, he did not so " +"much as lift his mace against them. He just ran deeper into the dim, endless " +"chambers that were filled with the servants of darkness!" +msgstr "" +"Я быў там, калі Лазар завёў нас у лабірынт. Гаварыў пра святое пакаранне, а " +"як пачалася бойка з параджэннямі пекла, ён нат булавы свае не падняў на іх. " +"Просто бег уніз, у цемрадзь, у бясконцыя пакоі лабірынта, поўныя служак " +"цемры!" + +#. TRANSLATORS: Quest dialog spoken by Farnham +#: Source/textdat.cpp:77 +msgid "" +"They stab, then bite, then they're all around you. Liar! LIAR! They're all " +"dead! Dead! Do you hear me? They just keep falling and falling... their " +"blood spilling out all over the floor... all his fault..." +msgstr "" +"Поруць, кусаюцца, ажно яны ўсе вакол цябе. Хлус! ХЛУС! Яны ўсе мёртвыя! " +"Мёртвыя! Чуеш? Яны ўсё падаюць ды падаюць… кроў, іхняя кроў па ўсёй падлозе… " +"усё яго віна…" + +#. TRANSLATORS: Quest dialog spoken by Adria +#: Source/textdat.cpp:79 +msgid "" +"I did not know this Lazarus of whom you speak, but I do sense a great " +"conflict within his being. He poses a great danger, and will stop at nothing " +"to serve the powers of darkness which have claimed him as theirs." +msgstr "" +"Я не ведала таго Лазара, пра якога ты гаворыш, але я адчуваю вялікі разлад у " +"яго сутнасці. Ён уяўляе сабою страшную небяспеку, і ні перад чым не спыніцца " +"ў службе сілам цемры, што назвалі яго сваім." + +#. TRANSLATORS: Quest dialog spoken by Wirt +#: Source/textdat.cpp:81 +msgid "" +"Yes, the righteous Lazarus, who was sooo effective against those monsters " +"down there. Didn't help save my leg, did it? Look, I'll give you a free " +"piece of advice. Ask Farnham, he was there." +msgstr "" +"Ага, праведны Лазар, які ну та-а-ак ужо нам усім дапамог тады ўнізе. Назе " +"маёй адно не дапамог, от бяда. Слухай, дам за так табе параду. Спытай " +"Фарнама, ён быў там." + +#. TRANSLATORS: Quest dialog spoken by Lazarus (Hostile) +#: Source/textdat.cpp:83 +msgid "" +"Abandon your foolish quest. All that awaits you is the wrath of my Master! " +"You are too late to save the child. Now you will join him in Hell!" +msgstr "" +"Кінь сваё дурное пачынанне. Усё, што цябе чакае, толькі гнеў майго Валадара! " +"Ужо запозна ратаваць дзіця. Скора ты далучышся да яго ў Пекле!" + +#. TRANSLATORS: Quest dialog spoken by Cain +#: Source/textdat.cpp:86 +msgid "" +"Hmm, I don't know what I can really tell you about this that will be of any " +"help. The water that fills our wells comes from an underground spring. I " +"have heard of a tunnel that leads to a great lake - perhaps they are one and " +"the same. Unfortunately, I do not know what would cause our water supply to " +"be tainted." +msgstr "" +"Гм-м, не ведаю, што я мог бы расказаць табе, што магло б табе дамагчы. Вада " +"ў нашым калодзежы плыве з падземнай крыніцы. Я чуў пра тунель, які вядзе да " +"нейкага вяліка возера, магчыма, гэта адно яна і ёсць. На жаль, мне невядома, " +"з якой прычыны наша вада забрудзілася." + +#. TRANSLATORS: Quest dialog spoken by Ogden +#: Source/textdat.cpp:88 +msgid "" +"I have always tried to keep a large supply of foodstuffs and drink in our " +"storage cellar, but with the entire town having no source of fresh water, " +"even our stores will soon run dry. \n" +" \n" +"Please, do what you can or I don't know what we will do." +msgstr "" +"Я заўсёды стараўся трымаць у склепе вялізны запас ежы і пітва, але без " +"крыніцы свежай вады ва ўсім горадзе, наша сховішча скора зусім спустошыцца.\n" +"Калі ласка, зрабіце, што зможаце, інакш я не ведаю, што нам рабіць." + +#. TRANSLATORS: Quest dialog spoken by Pipin +#: Source/textdat.cpp:90 +msgid "" +"I'm glad I caught up to you in time! Our wells have become brackish and " +"stagnant and some of the townspeople have become ill drinking from them. Our " +"reserves of fresh water are quickly running dry. I believe that there is a " +"passage that leads to the springs that serve our town. Please find what has " +"caused this calamity, or we all will surely perish." +msgstr "" +"Рады я заспеў цябе ў пару! У нас у калодзезе застаялася вада, уся " +"саленаватая стала, нехта з гарадскіх нават захварэў, выпіўшы яе. Нашы запасы " +"свежай вады хутка вычэрпваюцца. Здаецца, ёсць праход, які вядзе да крыніц, " +"што жывяць наш горад. Адшукай, якая прычына гэтаму бедству, калі ласка, " +"інакш мы ўсе дакладна згінем." + +#. TRANSLATORS: Quest dialog spoken by Pipin +#: Source/textdat.cpp:92 +msgid "" +"Please, you must hurry. Every hour that passes brings us closer to having no " +"water to drink. \n" +" \n" +"We cannot survive for long without your help." +msgstr "" +"Прашу, спяшайся. З кожнай гадзінаю мы маем усё менш вады для піцця.\n" +"Без тваёй дапамогі мы доўга не пратрываем." + +#. TRANSLATORS: Quest dialog spoken by Pipin +#: Source/textdat.cpp:94 +msgid "" +"What's that you say - the mere presence of the demons had caused the water " +"to become tainted? Oh, truly a great evil lurks beneath our town, but your " +"perseverance and courage gives us hope. Please take this ring - perhaps it " +"will aid you in the destruction of such vile creatures." +msgstr "" +"Што кажаш? Сама прысутнасць дэманаў — прычына забруджвання вады? Вой, пад " +"нашым горадам сапраўды таіцца вялікае зло, але твая стойкасць і храбрасць " +"даюць нам надзею. Калі ласка, вазьмі гэты пярсцёнак — магчыма, ён дапаможа " +"табе ў знішчэнні гэтых пачвар." + +#. TRANSLATORS: Quest dialog spoken by Gillian +#: Source/textdat.cpp:96 +msgid "" +"My grandmother is very weak, and Garda says that we cannot drink the water " +"from the wells. Please, can you do something to help us?" +msgstr "" +"Маёй бабулі вельмі няможацца, а Гарда сказала, што нам няможна піць " +"калодзезя. Можаш неяк нам дапамагчы, калі ласка?" + +#. TRANSLATORS: Quest dialog spoken by Griswold +#: Source/textdat.cpp:98 +msgid "" +"Pepin has told you the truth. We will need fresh water badly, and soon. I " +"have tried to clear one of the smaller wells, but it reeks of stagnant " +"filth. It must be getting clogged at the source." +msgstr "" +"Піпін праўду сказаў. Нам сільно будзе патрэбна свежая вада, дый скоро. " +"Спрабаваў ачысціць адзін з калодзезяў, меншых, але там смуродзіць гэтай " +"затхлай гадасцю. Мусіць на крыніцы забрудзілося." + +#. TRANSLATORS: Quest dialog spoken by Farnham +#: Source/textdat.cpp:100 +msgid "You drink water?" +msgstr "Ты што, п'еш ваду?" + +#. TRANSLATORS: Quest dialog spoken by Adria +#: Source/textdat.cpp:101 +msgid "" +"The people of Tristram will die if you cannot restore fresh water to their " +"wells. \n" +" \n" +"Know this - demons are at the heart of this matter, but they remain ignorant " +"of what they have spawned." +msgstr "" +"Жыхары Трыстрама памруць, калі не аднавіць падачу свежай вады ў іх " +"калодзезі.\n" +"\n" +"Ведай жа: за гэтаю справаю стаяць дэманы, але ім не вядома, што яны " +"спарадзілі." + +#. TRANSLATORS: Quest dialog spoken by Wirt +#: Source/textdat.cpp:103 +msgid "" +"For once, I'm with you. My business runs dry - so to speak - if I have no " +"market to sell to. You better find out what is going on, and soon!" +msgstr "" +"Гэты раз я за цябе. Дзела стаіць, як той кажа, калі няма каму прадаваць. " +"Лепш дазнайся, што там робіцца, і хутчэй!" + +#. TRANSLATORS: Quest dialog spoken by Cain +#: Source/textdat.cpp:105 +msgid "" +"A book that speaks of a chamber of human bones? Well, a Chamber of Bone is " +"mentioned in certain archaic writings that I studied in the libraries of the " +"East. These tomes inferred that when the Lords of the underworld desired to " +"protect great treasures, they would create domains where those who died in " +"the attempt to steal that treasure would be forever bound to defend it. A " +"twisted, but strangely fitting, end?" +msgstr "" +"Кніга, у якой гаворыцца пра пакой з чалавечых касцей? Што ж, у некаторых " +"старадаўніх тэкстах, якія я даследаваў у Бібліятэцы Усходу, згадваецца Пакой " +"Касцей. У тых фаліянтах паведамлялася, што, калі Валадары Падзямелля " +"зажадалі ахаваць свае вялікія скарбы, яны стваралі ўладанні, дзе тыя, хто " +"паміраў, спрабуючы скрасці скарбы, назаўсёды звязваўся б з гэтым месцам і " +"бараніў яго. Жахлівы канец, але дзіўна адпаведны?" + +#. TRANSLATORS: Quest dialog spoken by Ogden +#: Source/textdat.cpp:107 +msgid "" +"I am afraid that I don't know anything about that, good master. Cain has " +"many books that may be of some help." +msgstr "" +"Баюся, што пра гэта я нічога не ведаю, добры чалавек. Кейн мае шмат кніг, " +"можа яны дапамогуць." + +#. TRANSLATORS: Quest dialog spoken by Pipin +#: Source/textdat.cpp:109 +msgid "" +"This sounds like a very dangerous place. If you venture there, please take " +"great care." +msgstr "" +"Гучыць як вельмі небяспечнае месца. Калі накіруецеся туды, прашу, будзьце " +"вельмі асцярожны." + +#. TRANSLATORS: Quest dialog spoken by Gillian +#: Source/textdat.cpp:111 +msgid "" +"I am afraid that I haven't heard anything about that. Perhaps Cain the " +"Storyteller could be of some help." +msgstr "" +"Баюся, нічога пра гэта я не чула. Можа Расказчык Кейн мог бы дапамагчы." + +#. TRANSLATORS: Quest dialog spoken by Griswold +#: Source/textdat.cpp:113 +msgid "" +"I know nothing of this place, but you may try asking Cain. He talks about " +"many things, and it would not surprise me if he had some answers to your " +"question." +msgstr "" +"Я пра гэто нічагуткі не ведаю, але мо Кейна спытай. Ён пра шмат чаго " +"гамоніць, і мяне б не здзівіло, калі ён ведае адказ на тваё пытанне." + +#. TRANSLATORS: Quest dialog spoken by Farnham +#: Source/textdat.cpp:115 +msgid "" +"Okay, so listen. There's this chamber of wood, see. And his wife, you know - " +"her - tells the tree... cause you gotta wait. Then I says, that might work " +"against him, but if you think I'm gonna PAY for this... you... uh... yeah." +msgstr "" +"Так, слухай. Ёсць такі пакой дрэва, бач. А жонка ягоная, ну ведаеш – гэтая " +"самая – кажа дрэву... не, ты пачакай. А я, кажу, яно яму бокам вылезе, але " +"калі думаеш, што я буду ПЛАЦІЦЬ... ты... гэта... так." + +#. TRANSLATORS: Quest dialog spoken by Adria +#: Source/textdat.cpp:117 +msgid "" +"You will become an eternal servant of the dark lords should you perish " +"within this cursed domain. \n" +" \n" +"Enter the Chamber of Bone at your own peril." +msgstr "" +"Ты станеш вечным слугою цёмных уладароў, калі загінеш у тым праклятым " +"месцы.\n" +"На свой страх і рызыку ўваходзь у Пакой Касцей." + +#. TRANSLATORS: Quest dialog spoken by Wirt +#: Source/textdat.cpp:119 +msgid "" +"A vast and mysterious treasure, you say? Maybe I could be interested in " +"picking up a few things from you... or better yet, don't you need some rare " +"and expensive supplies to get you through this ordeal?" +msgstr "" +"Вялізны, таямнічы скарб, кажаш? Мо мне і было б цікава заграбці з цябе рэч " +"другую... О, а шчэ лепш, табе не трэба якіх рэдкіх і дарагіх прыпасаў, каб " +"прайсці гэткае выпрабаванне?" + +#. TRANSLATORS: Quest dialog spoken by Cain +#: Source/textdat.cpp:121 +msgid "" +"It seems that the Archbishop Lazarus goaded many of the townsmen into " +"venturing into the Labyrinth to find the King's missing son. He played upon " +"their fears and whipped them into a frenzied mob. None of them were prepared " +"for what lay within the cold earth... Lazarus abandoned them down there - " +"left in the clutches of unspeakable horrors - to die." +msgstr "" +"Здаецца так, што архібіскуп Лазар прымусіў шмат мясцовых спусціцца ў " +"Лабірынт, каб адшукаць зніклага сына Караля. Ён зыграў на іх страхах, і " +"загнаў іх напрасткі ў шалёны натоўп. Ніхто не быў гатовы да таго, што ляжыць " +"пад халоднай зямлёю… Лазар пакінуў іх там, унізе – у кіпцюрах невымоўных " +"жахаў – на смерць." + +#. TRANSLATORS: Quest dialog spoken by Ogden +#: Source/textdat.cpp:123 +msgid "" +"Yes, Farnham has mumbled something about a hulking brute who wielded a " +"fierce weapon. I believe he called him a butcher." +msgstr "" +"Ну, Фарнам нешта мармытаў пра нейкае грамаднае стварэнне, якое арудавала " +"ліхой зброяй. Здаецца, ён назваў яго мясніком." + +#. TRANSLATORS: Quest dialog spoken by Pipin +#: Source/textdat.cpp:125 +msgid "" +"By the Light, I know of this vile demon. There were many that bore the scars " +"of his wrath upon their bodies when the few survivors of the charge led by " +"Lazarus crawled from the Cathedral. I don't know what he used to slice open " +"his victims, but it could not have been of this world. It left wounds " +"festering with disease and even I found them almost impossible to treat. " +"Beware if you plan to battle this fiend..." +msgstr "" +"О, Святло! Я ведаю аб гэтым агідным дэмане. Сярод тых нешматлікіх ацалелых з " +"паствы Лазара, хто выпаўз з-пад Храма, багата людзей мела сляды яго гневу на " +"сабе. Не ведаю, чым ён калечыў сваіх ахвяр, але тое дакладна не з гэтага " +"свету. Раны гнілі хваробаю, і нават мне падавалася, што загаіць іх " +"немагчыма. Шануйся, калі думаеш біцца з гэтай бэстыяй…" + +#. TRANSLATORS: Quest dialog spoken by Gillian +#: Source/textdat.cpp:127 +msgid "" +"When Farnham said something about a butcher killing people, I immediately " +"discounted it. But since you brought it up, maybe it is true." +msgstr "" +"Калі Фарнам нешта казаў пра мясніка, які забівае людзей, я адразу не " +"паверыла. Але раз і ты гэта ўзгадваеш, мабыць, гэта і праўда." + +#. TRANSLATORS: Quest dialog spoken by Griswold +#: Source/textdat.cpp:129 +msgid "" +"I saw what Farnham calls the Butcher as it swathed a path through the bodies " +"of my friends. He swung a cleaver as large as an axe, hewing limbs and " +"cutting down brave men where they stood. I was separated from the fray by a " +"host of small screeching demons and somehow found the stairway leading out. " +"I never saw that hideous beast again, but his blood-stained visage haunts me " +"to this day." +msgstr "" +"Я бачыў, што Фарнам назваў Мясніком, калі яно касіло сабе дарогу праз цяла " +"маіх сяброў. Ён размахваў секачом з сакеру, сцінаючы рукі-ногі, столькі " +"смельчакоў забіваючы на месцы. Мяне аддзяліло ад бітвы зборышчо крыклівых " +"д’ябалят, і я нек выйшаў на сходы наверх. Больш я таго брыдкаго пачварня не " +"бачыў, але ягоная крывавая морда і па гэты дзень у мяне перад вачыма." + +#. TRANSLATORS: Quest dialog spoken by Farnham (*sad face*) +#: Source/textdat.cpp:131 +msgid "" +"Big! Big cleaver killing all my friends. Couldn't stop him, had to run away, " +"couldn't save them. Trapped in a room with so many bodies... so many " +"friends... NOOOOOOOOOO!" +msgstr "" +"Вялізны! Вялізны сякач – усіх маіх сяброў пазабіваў. Не даў рады спыніць, " +"трэ было ўцякаць, не змог іх выратаваць. Прымкнула ў пакою, а там столькі " +"целаў… столькі сяброў… НЕЕЕЕЕЕЕЕЕЕ!" + +#. TRANSLATORS: Quest dialog spoken by Adria +#: Source/textdat.cpp:133 +msgid "" +"The Butcher is a sadistic creature that delights in the torture and pain of " +"others. You have seen his handiwork in the drunkard Farnham. His destruction " +"will do much to ensure the safety of this village." +msgstr "" +"Мяснік – садысцкая істота, якая цешыцца болем і катаваннем людзей. Яго " +"работу бачна ў п’яніцы Фарнаме. Знішчэнне яго дакладна забяспечыла б " +"захаванасць гэтай вёскі." + +#. TRANSLATORS: Quest dialog spoken by Wirt +#: Source/textdat.cpp:135 +msgid "" +"I know more than you'd think about that grisly fiend. His little friends got " +"a hold of me and managed to get my leg before Griswold pulled me out of that " +"hole. \n" +" \n" +"I'll put it bluntly - kill him before he kills you and adds your corpse to " +"his collection." +msgstr "" +"Я аб гэтым страшэнным чорце ведаю больш, чым ты думаеш. Ягоныя дружакі мяне " +"злавілі і нагу маю адабралі, да таго як Грызвальд выцягнуў мяне з той " +"дзіры. \n" +"\n" +"Скажу проста – забі яго, і бі першым, а то ён дадасць твой труп у сваю " +"калекцыю." + +#. TRANSLATORS: Quest dialog spoken by Wounded Townsman (Dying) +#: Source/textdat.cpp:137 +msgid "" +"Please, listen to me. The Archbishop Lazarus, he led us down here to find " +"the lost prince. The bastard led us into a trap! Now everyone is dead... " +"killed by a demon he called the Butcher. Avenge us! Find this Butcher and " +"slay him so that our souls may finally rest..." +msgstr "" +"Прашу, выслухай мяне. Архібіскуп Лазар, ён нас завёў сюды каб адшукаць " +"прынца зніклага. Сволач завёў нас у пастку! Цяпер усіх забіла… забіў іх " +"дэман, ён яго назваў Мяснік. Адпомсці за нас! Знайдзі гэтага Мясніка ды забі " +"яго, каб нашы душы знайшлі спакой…" + +#. TRANSLATORS: Quest dialog spoken by Cain +#: Source/textdat.cpp:140 +msgid "" +"You recite an interesting rhyme written in a style that reminds me of other " +"works. Let me think now - what was it?\n" +" \n" +"...Darkness shrouds the Hidden. Eyes glowing unseen with only the sounds of " +"razor claws briefly scraping to torment those poor souls who have been made " +"sightless for all eternity. The prison for those so damned is named the " +"Halls of the Blind..." +msgstr "" +"Цікавы верш ты цытуеш, стыль мне нешта нагадвае. Дай падумаць – як жа там " +"было?\n" +" \n" +"…Цьма ахінае Схаванае. Вочы нябачныя свецяцца, чуваць адно, як коратка " +"скрабуцца брытвы-кіпцюры, мучаючы бедныя душы аслепленых на век вечны. " +"Цямніца для гэтых Праклятых завецца Залы Сляпых…" + +#. TRANSLATORS: Quest dialog spoken by Ogden +#: Source/textdat.cpp:142 +msgid "" +"I never much cared for poetry. Occasionally, I had cause to hire minstrels " +"when the inn was doing well, but that seems like such a long time ago now. \n" +" \n" +"What? Oh, yes... uh, well, I suppose you could see what someone else knows." +msgstr "" +"Заўсёды быў нейкі абыякавы да паэзіі. Іншы раз даводзілася наймаць " +"менестрэляў, калі справы ў карчме ішлі добра, але гэта быццам было так " +"даўно.\n" +"\n" +"Што? А, так... гм, ну, думаю, можаш астатніх спытаць, што яны ведаюць." + +#. TRANSLATORS: Quest dialog spoken by Pipin +#: Source/textdat.cpp:144 +msgid "" +"This does seem familiar, somehow. I seem to recall reading something very " +"much like that poem while researching the history of demonic afflictions. It " +"spoke of a place of great evil that... wait - you're not going there are you?" +msgstr "" +"Гучыць праўда знаёма, чамусьці. Здаецца, прыпамінаю, што чытаў штосьці " +"вельмі падобнае, калі вывучаў гісторыю дэманічных насланняў. Там казалася " +"пра месца вялікага зла... пастой – ты ж не збіраешся ісці туды, праўда?" + +#. TRANSLATORS: Quest dialog spoken by Gillian +#: Source/textdat.cpp:146 +msgid "" +"If you have questions about blindness, you should talk to Pepin. I know that " +"he gave my grandmother a potion that helped clear her vision, so maybe he " +"can help you, too." +msgstr "" +"Калі ў цябе пытанні пра слепату, пагавары з Піпінам. Я ведаю, што ён даў " +"маёй бабулі зелле, што праясніла ёй зрок, можа ён і табе дапаможа." + +#. TRANSLATORS: Quest dialog spoken by Griswold +#: Source/textdat.cpp:148 +msgid "" +"I am afraid that I have neither heard nor seen a place that matches your " +"vivid description, my friend. Perhaps Cain the Storyteller could be of some " +"help." +msgstr "" +"Баюся я ні відам не відаў, ні слыхам не чуваў пра месцо, каб падыходзіло пад " +"гэдакае жывое апісанне, дружа. Расказчык Кейн мог бы спамагчы." + +#. TRANSLATORS: Quest dialog spoken by Farnham +#: Source/textdat.cpp:150 +msgid "Look here... that's pretty funny, huh? Get it? Blind - look here?" +msgstr "Ну глядзі... праўда смешна, га? Зразумела? Зала сляпых– глядзі?" + +#. TRANSLATORS: Quest dialog spoken by Adria +#: Source/textdat.cpp:152 +msgid "" +"This is a place of great anguish and terror, and so serves its master " +"well. \n" +" \n" +"Tread carefully or you may yourself be staying much longer than you had " +"anticipated." +msgstr "" +"Тое месца вялікіх пакут і жахаў, таму яно і служыць добра сваім гаспадарам.\n" +"\n" +"Ступай асцярожна, або і ты затрымаешся там дольш, чым спадзяваешся." + +#. TRANSLATORS: Quest dialog spoken by Wirt +#: Source/textdat.cpp:154 +msgid "" +"Lets see, am I selling you something? No. Are you giving me money to tell " +"you about this? No. Are you now leaving and going to talk to the storyteller " +"who lives for this kind of thing? Yes." +msgstr "" +"Так, паглядзім, я табе нешта прадаю? Не. Ты мне даеш грошы, каб я пра гэта " +"нешта расказаў? Не. Ты ідзеш адсюль і размаўляеш з расказчыкам, які і жыве " +"для такога? Так." + +#. TRANSLATORS: Quest dialog spoken by Cain +#: Source/textdat.cpp:156 +msgid "" +"You claim to have spoken with Lachdanan? He was a great hero during his " +"life. Lachdanan was an honorable and just man who served his King faithfully " +"for years. But of course, you already know that.\n" +" \n" +"Of those who were caught within the grasp of the King's Curse, Lachdanan " +"would be the least likely to submit to the darkness without a fight, so I " +"suppose that your story could be true. If I were in your place, my friend, I " +"would find a way to release him from his torture." +msgstr "" +"Гаворыш, Лакданан размаўляў з табою? Ён быў вялікім героем пры жыцці. " +"Шляхетны і справядлівы чалавек, які шмат год служыў Каралю вераю і праўдаю. " +"Але ты, канечне, ужо гэта ведаеш. \n" +"\n" +"З усіх, каго апанаваў праклён Караля, Лакданан як ніхто іншы не здаваўся б " +"Цемры без бою, таму, мяркую, што твае словы – праўда. Будзь я на тваім " +"месцы, дружа, я бы паспрабаваў знайсці спосаб адратаваць яго ад такой пакуты." + +#. TRANSLATORS: Quest dialog spoken by Ogden +#: Source/textdat.cpp:158 +msgid "" +"You speak of a brave warrior long dead! I'll have no such talk of speaking " +"with departed souls in my inn yard, thank you very much." +msgstr "" +"Ты пра храбрага воіна, які даўно памёр! Я не жадаю размоў пра згінулыя душы " +"на маім двары, дзякуй вам вялікі." + +#. TRANSLATORS: Quest dialog spoken by Pipin +#: Source/textdat.cpp:160 +msgid "" +"A golden elixir, you say. I have never concocted a potion of that color " +"before, so I can't tell you how it would effect you if you were to try to " +"drink it. As your healer, I strongly advise that should you find such an " +"elixir, do as Lachdanan asks and DO NOT try to use it." +msgstr "" +"Кажаш, залаты элікір. Ніколі не варыў зелляў такога колеру, таму не ведаю, " +"які яно мела б эфект, выпі ты яго. Як твой лекар, вельмі раю зрабіць, як " +"прасіў Лакданан, калі знойдзеш такі эліксір, і НЕ ПРАБУЙ яго піць." + +#. TRANSLATORS: Quest dialog spoken by Gillian +#: Source/textdat.cpp:162 +msgid "" +"I've never heard of a Lachdanan before. I'm sorry, but I don't think that I " +"can be of much help to you." +msgstr "" +"Ніколі не чула пра Лакданана. Выбачай, але я не думаю, што магу табе неяк " +"дапамагчы." + +#. TRANSLATORS: Quest dialog spoken by Ogden +#: Source/textdat.cpp:164 +msgid "" +"If it is actually Lachdanan that you have met, then I would advise that you " +"aid him. I dealt with him on several occasions and found him to be honest " +"and loyal in nature. The curse that fell upon the followers of King Leoric " +"would fall especially hard upon him." +msgstr "" +"Калі гэта сапраўды быў Лакданан, я бы параіў табе дапамагчы яму. Я меў з ім " +"справу некалькі разоў, і палічыў яго па прыродзе шчырым і верным чалавекам. " +"Праклён, што ахінуў паслядоўнікаў Караля Леорыка, паў быў на яго асабліва " +"цяжка." + +#. TRANSLATORS: Quest dialog spoken by Farnham +#: Source/textdat.cpp:166 +msgid "" +" Lachdanan is dead. Everybody knows that, and you can't fool me into " +"thinking any other way. You can't talk to the dead. I know!" +msgstr "" +" Лакданан памёр. Гэта ўсе ведаюць, і ты мяне не абдурыш, што не памёр. З " +"мёртвымі не пагаворыш. Я-то ведаю!" + +#. TRANSLATORS: Quest dialog spoken by Adria +#: Source/textdat.cpp:168 +msgid "" +"You may meet people who are trapped within the Labyrinth, such as " +"Lachdanan. \n" +" \n" +"I sense in him honor and great guilt. Aid him, and you aid all of Tristram." +msgstr "" +"Ты можаш сустрэць у Лабірынце тых, хто захрас там у пастцы, як Лакданан.\n" +"\n" +"Я адчуваю ў ім годнасць і вялікае пачуццё віны. Дапаможаш яму, і ты " +"дапаможаш усяму Трыстраму." + +#. TRANSLATORS: Quest dialog spoken by Wirt +#: Source/textdat.cpp:170 +msgid "" +"Wait, let me guess. Cain was swallowed up in a gigantic fissure that opened " +"beneath him. He was incinerated in a ball of hellfire, and can't answer your " +"questions anymore. Oh, that isn't what happened? Then I guess you'll be " +"buying something or you'll be on your way." +msgstr "" +"Пастой, дай згану. Пад Кейнам разявілася агромная рышчыліна і яго туды " +"зацягнула. Яго спапяліла ў шары пякельнага агню і ён больш не можа адказваць " +"на твае пытанні. Як, не было такога? Ну, тады ты або купляеш нешта, або " +"ідзеш сваёй дарогай." + +#. TRANSLATORS: Quest dialog spoken by Lachdanan (in despair) +#: Source/textdat.cpp:172 +msgid "" +"Please, don't kill me, just hear me out. I was once Captain of King Leoric's " +"Knights, upholding the laws of this land with justice and honor. Then his " +"dark Curse fell upon us for the role we played in his tragic death. As my " +"fellow Knights succumbed to their twisted fate, I fled from the King's " +"burial chamber, searching for some way to free myself from the Curse. I " +"failed...\n" +" \n" +"I have heard of a Golden Elixir that could lift the Curse and allow my soul " +"to rest, but I have been unable to find it. My strength now wanes, and with " +"it the last of my humanity as well. Please aid me and find the Elixir. I " +"will repay your efforts - I swear upon my honor." +msgstr "" +"Прашу, не забівай, выслухай мяне! Я быў Капітанам Рыцараў Караля Леорыка, " +"падтрымліваў законы гэтай зямлі з чэсцю і справядлівасцю. Але потым яго " +"цёмны Праклён паў на нас, за ролю, што мы сыгрылі ў яго трагічнай смерці. " +"Пакуль мае сабраты скарыліся свайму страшнаму лёсу, я збег з усыпальні " +"Караля, шукаючы, як збавіцца ад Праклёну. Аднак я не даў рады...\n" +"\n" +"Я чуў пра Залаты Эліксір, што мог бы адрабіць Праклён і дараваць маёй душы " +"спакой, але не змог знайсці яго. Мая сіла цяпер згасае, і разам з ёю астаткі " +"маёй чалавечнасці. Прашу, дапамажы мне, знайдзі Эліксір. Я адплачу табе за " +"намаганні — клянуся сваёю чэсцю." + +#. TRANSLATORS: Quest dialog spoken by Lachdanan (in despair) +#: Source/textdat.cpp:174 +msgid "" +"You have not found the Golden Elixir. I fear that I am doomed for eternity. " +"Please, keep trying..." +msgstr "" +"Залаты Эліксір не знайшоўся. Страшуся, я на векі вякоў пракляты. Прашу, " +"пашукай яшчэ..." + +#. TRANSLATORS: Quest dialog spoken by Lachdanan (Quest End) +#: Source/textdat.cpp:176 +msgid "" +"You have saved my soul from damnation, and for that I am in your debt. If " +"there is ever a way that I can repay you from beyond the grave I will find " +"it, but for now - take my helm. On the journey I am about to take I will " +"have little use for it. May it protect you against the dark powers below. Go " +"with the Light, my friend..." +msgstr "" +"Табою выратавана мая душа ад пракляцця, і за гэта я табе абавязаны. Калі " +"ёсць спосаб разлічыцца з табою з таго свету, я знайду яго, а пакуль — вазьмі " +"мой шалом. У падарожжы, у якое я адпраўляюся, мне ён не будзе надта карысны. " +"Няхай ён ахоўвае цябе ад цёмных сіл унізе. Ідзі са Святлом, друг мой..." + +#. TRANSLATORS: Quest dialog spoken by Cain +#: Source/textdat.cpp:178 +msgid "" +"Griswold speaks of The Anvil of Fury - a legendary artifact long searched " +"for, but never found. Crafted from the metallic bones of the Razor Pit " +"demons, the Anvil of Fury was smelt around the skulls of the five most " +"powerful magi of the underworld. Carved with runes of power and chaos, any " +"weapon or armor forged upon this Anvil will be immersed into the realm of " +"Chaos, imbedding it with magical properties. It is said that the " +"unpredictable nature of Chaos makes it difficult to know what the outcome of " +"this smithing will be..." +msgstr "" +"Грызвальд гаворыць пра Кавадла Ярасці, легендарны артэфакт, які доўга " +"шукалі, але так не знайшлі. Яно выраблена з касцей дэманаў з Лязовай Ямы, і " +"выплаўлялі яго побач чэрапаў пяцярых наймацнейшых магаў падземнага свету. На " +"ім высячаны руны сілы і хаосу, а любая зброя ці браня, выкутая на гэтым " +"Кавадле, паглынецца царствам Хаосу, якое ўкараніць у рэчы магічныя " +"ўласцівасці. Казалі, праз непрадказальную прыроду хаосу цяжка ведаць " +"дакладна, які будзе вынік такой коўкі…" + +#. TRANSLATORS: Quest dialog spoken by Ogden +#: Source/textdat.cpp:180 +msgid "" +"Don't you think that Griswold would be a better person to ask about this? " +"He's quite handy, you know." +msgstr "" +"Не думаеш, што было б лепш спытаць пра гэта Грызвальда? Ён да таго зручны, " +"знаеш." + +#. TRANSLATORS: Quest dialog spoken by Pipin +#: Source/textdat.cpp:182 +msgid "" +"If you had been looking for information on the Pestle of Curing or the " +"Silver Chalice of Purification, I could have assisted you, my friend. " +"However, in this matter, you would be better served to speak to either " +"Griswold or Cain." +msgstr "" +"Калі б табе былі патрэбны звесткі пра Таўкачык Гаення ці пра Срэбны Келіх " +"Ачышчэння, я мог бы дапамагчы табе, друг мой. Аднак, у гэтай справе было б " +"карысней пагаварыць з Грызвальдам ці з Кейнам." + +#. TRANSLATORS: Quest dialog spoken by Gillian +#: Source/textdat.cpp:184 +msgid "" +"Griswold's father used to tell some of us when we were growing up about a " +"giant anvil that was used to make mighty weapons. He said that when a hammer " +"was struck upon this anvil, the ground would shake with a great fury. " +"Whenever the earth moves, I always remember that story." +msgstr "" +"Грызвальдаў бацька бывала расказваў нам малым пра вялізнае кавадла, на якім " +"рабілі магутную зброю. Ён казаў, што калі молат біў па тым кавадле, зямля аж " +"трэслася шалёна. Калі трасецца зямля, я заўсёды гэта ўспамінаю." + +#. TRANSLATORS: Quest dialog spoken by Griswold +#: Source/textdat.cpp:186 +msgid "" +"Greetings! It's always a pleasure to see one of my best customers! I know " +"that you have been venturing deeper into the Labyrinth, and there is a story " +"I was told that you may find worth the time to listen to...\n" +" \n" +"One of the men who returned from the Labyrinth told me about a mystic anvil " +"that he came across during his escape. His description reminded me of " +"legends I had heard in my youth about the burning Hellforge where powerful " +"weapons of magic are crafted. The legend had it that deep within the " +"Hellforge rested the Anvil of Fury! This Anvil contained within it the very " +"essence of the demonic underworld...\n" +" \n" +"It is said that any weapon crafted upon the burning Anvil is imbued with " +"great power. If this anvil is indeed the Anvil of Fury, I may be able to " +"make you a weapon capable of defeating even the darkest lord of Hell! \n" +" \n" +"Find the Anvil for me, and I'll get to work!" +msgstr "" +"Вітаю! Заўсёды рады бачыць аднаго са сваіх найлепшых пакупнікоў! Я ведаю, ты " +"часто ходзіш глыбоко ў Лабірынт, а мне якраз нешто расказалі, што можа табе " +"паказацца вартым часу...\n" +"\n" +"Адзін чалавек, які вярнуўся з Лабірынта, гаварыў, што, калі ўцякаў, знайшоў " +"нейкае таямнічае кавадло. Яго апісанне нагадало мне легенды, якія я чуў шчэ " +"юнаком, пра Пякельную Кузню, дзе куюць чарадзейную, моцную зброю. Па " +"легендзе ў глыбіні Пякельнай Кузні стаяло Кавадло Ярасці! Гэтае Кавадло " +"змяшчало ў сабе самую існасць падземнаго свету...\n" +"\n" +"Кажуць, любая зброя выкутая на пякельным Кавадле насычана вялікай сілаю. " +"Калі гэто дапраўды Кавадло Ярасці, я змагу выкаваць такую зброю, што і " +"самаго найцямнейшаго ўладара Пекла змагла б паразіць!\n" +"\n" +"Знайдзі Кавадло, і я вазьмуся за працу!" + +#. TRANSLATORS: Quest dialog spoken by Griswold +#: Source/textdat.cpp:188 +msgid "" +"Nothing yet, eh? Well, keep searching. A weapon forged upon the Anvil could " +"be your best hope, and I am sure that I can make you one of legendary " +"proportions." +msgstr "" +"Покуль нічого, так? Ну, шукай. Зброя, выкутая на гэтым Кавадле была б " +"найлепшай надзеяю, і я пэўны я змог бы вырабіць для цябе нешто сапраўды " +"вялікае." + +#. TRANSLATORS: Quest dialog spoken by Griswold +#: Source/textdat.cpp:190 +msgid "" +"I can hardly believe it! This is the Anvil of Fury - good work, my friend. " +"Now we'll show those bastards that there are no weapons in Hell more deadly " +"than those made by men! Take this and may Light protect you." +msgstr "" +"Не магу паверыць! Кавадло Ярасці — малайчына, сябра. Цяпер-то мы ім пакажам, " +"падлюгам, што няма ў Пякле зброі смяротнейшай, ніж той, што зрабіў чалавек! " +"Трымай, і няхай Святло бароніць цябе." + +#. TRANSLATORS: Quest dialog spoken by Farnham +#: Source/textdat.cpp:192 +msgid "" +"Griswold can't sell his anvil. What will he do then? And I'd be angry too if " +"someone took my anvil!" +msgstr "" +"Грызвальд не прадасць свайго кавадла. Што ён тады будзе рабіць? І я б " +"таксама злаваў, каб нехта скраў маё кавадла!" + +#. TRANSLATORS: Quest dialog spoken by Adria +#: Source/textdat.cpp:194 +msgid "" +"There are many artifacts within the Labyrinth that hold powers beyond the " +"comprehension of mortals. Some of these hold fantastic power that can be " +"used by either the Light or the Darkness. Securing the Anvil from below " +"could shift the course of the Sin War towards the Light." +msgstr "" +"Унутры Лабірынта ёсць шмат артэфактаў, што маюць сілу па-за межамі разумення " +"смертных. Некаторыя з іх маюць сілу, якую могуць карыстаць як Святло, так і " +"Цемра. Дастаць адтуль Кавадла было б кідком на шалю Святла ў Вайне Гневу." + +#. TRANSLATORS: Quest dialog spoken by Wirt +#: Source/textdat.cpp:196 +msgid "" +"If you were to find this artifact for Griswold, it could put a serious " +"damper on my business here. Awwww, you'll never find it." +msgstr "" +"Калі знойдзеш Грызвальду гэты артэфакт, гэта моцна ўдарыць па паім дзеле. " +"Ай, ты яго ніколі не знойдзеш." + +#. TRANSLATORS: Quest dialog spoken by Cain +#: Source/textdat.cpp:198 +msgid "" +"The Gateway of Blood and the Halls of Fire are landmarks of mystic origin. " +"Wherever this book you read from resides it is surely a place of great " +"power.\n" +" \n" +"Legends speak of a pedestal that is carved from obsidian stone and has a " +"pool of boiling blood atop its bone encrusted surface. There are also " +"allusions to Stones of Blood that will open a door that guards an ancient " +"treasure...\n" +" \n" +"The nature of this treasure is shrouded in speculation, my friend, but it is " +"said that the ancient hero Arkaine placed the holy armor Valor in a secret " +"vault. Arkaine was the first mortal to turn the tide of the Sin War and " +"chase the legions of darkness back to the Burning Hells.\n" +" \n" +"Just before Arkaine died, his armor was hidden away in a secret vault. It is " +"said that when this holy armor is again needed, a hero will arise to don " +"Valor once more. Perhaps you are that hero..." +msgstr "" +"Вароты Крыві і Залі Агню — месцы таямнічага паходжання. Дзе б табе не " +"трапілася гэтая кніга, месца, дзе яна захоўваецца, дакладна вялікай сілы.\n" +"\n" +"Легенды гавораць аб п'едэстале, высечаным з абсідыянавага каменя, а наверсе " +"яго паверхні, убранай касцьмі, знаходзіцца басейн кіпучай крыві. Таксама " +"намякаецца аб Камянях Крыві, якія адкрыюць дзверы, што ахоўваюць нейкі " +"старасвецкі скарб...\n" +"\n" +"Прырода таго скарба ахутана загадкамі, дружа мой, але сказана, што " +"старадаўні герой Аркейн пакінуў святыя даспехі Адвагі ў патаемным скляпенні. " +"Аркейн быў першы смертны, што змяніў ход вайны Граху, прагнаўшы легіёны " +"цемры аж да самых Агністых Пекел.\n" +"\n" +"Акурат перад яго смерцю, даспехі Аркейна схавалі ў патаемным скляпенні. " +"Сказана, што калі святыя даспехі зноў будуць у патрэбе, паўстане іншы герой, " +"які апране Адвагу. Магчыма, ты і ёсць той герой..." + +#. TRANSLATORS: Quest dialog spoken by Ogden +#: Source/textdat.cpp:200 +msgid "" +"Every child hears the story of the warrior Arkaine and his mystic armor " +"known as Valor. If you could find its resting place, you would be well " +"protected against the evil in the Labyrinth." +msgstr "" +"Усякае дзіця ведае пра воіна Аркейна і яго таямнічыя даспехі, якія завуцца " +"Адвага. Калі знойдзеш, дзе яно захоўваецца, атрымаеш добрую абарону ад зла ў " +"Лабірынце." + +#. TRANSLATORS: Quest dialog spoken by Pipin +#: Source/textdat.cpp:202 +msgid "" +"Hmm... it sounds like something I should remember, but I've been so busy " +"learning new cures and creating better elixirs that I must have forgotten. " +"Sorry..." +msgstr "" +"Гм-м... гучыць як нешта, што я б ведаў, але я так доўга вывучаў новыя лекі " +"ды паляпшаў эліксіры, што я, мусіць, забыў. Выбачай..." + +#. TRANSLATORS: Quest dialog spoken by Gillian +#: Source/textdat.cpp:204 +msgid "" +"The story of the magic armor called Valor is something I often heard the " +"boys talk about. You had better ask one of the men in the village." +msgstr "" +"Раней я часта чула пра чароўныя даспехі, якія завуць Адвагай, пра такое " +"раней гаманілі хлопцы. Лепш мужыкоў папытай па вёсцы." + +#. TRANSLATORS: Quest dialog spoken by Griswold +#: Source/textdat.cpp:206 +msgid "" +"The armor known as Valor could be what tips the scales in your favor. I will " +"tell you that many have looked for it - including myself. Arkaine hid it " +"well, my friend, and it will take more than a bit of luck to unlock the " +"secrets that have kept it concealed oh, lo these many years." +msgstr "" +"Даспехі пад назвай Адвага могуць схіліць шалі на твой бок. Скажу, што многія " +"іх шукалі — у тым ліку і я. Аркейн добра іх схаваў, сябру, і аднае ўдачы тут " +"не хопіць, каб раскрыць сакрэты, якія хавалі іх, ух, столькі многа гадоў." + +#. TRANSLATORS: Quest dialog "spoken" by Farnham +#: Source/textdat.cpp:208 +msgid "Zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz..." +msgstr "Хр-р-р-р-р-р-р-р-р-р-р-р-р-р-р-р..." + +#. TRANSLATORS: Quest dialog spoken by Adria +#: Source/textdat.cpp:209 +msgid "" +"Should you find these Stones of Blood, use them carefully. \n" +" \n" +"The way is fraught with danger and your only hope rests within your self " +"trust." +msgstr "" +"Знойдзеш гэтыя Камяні Крыві, абыходзься з імі асцярожна.\n" +"\n" +"Шлях таіць у сябе небяспеку, і ёсць толькі адна надзея — у табе." + +#. TRANSLATORS: Quest dialog spoken by Wirt +#: Source/textdat.cpp:211 +msgid "" +"You intend to find the armor known as Valor? \n" +" \n" +"No one has ever figured out where Arkaine stashed the stuff, and if my " +"contacts couldn't find it, I seriously doubt you ever will either." +msgstr "" +"Думаеш знайсці даспехі пад назвай Адвага?\n" +"\n" +"Ніхто так і не адшукаў, дзе там Аркейн іх схаваў, і раз мае сувязі не далі " +"рады, нешта я сумняваюся, што ты калі-небудзь дасі." + +#. TRANSLATORS: Quest dialog spoken by Cain +#: Source/textdat.cpp:213 +msgid "" +"I know of only one legend that speaks of such a warrior as you describe. His " +"story is found within the ancient chronicles of the Sin War...\n" +" \n" +"Stained by a thousand years of war, blood and death, the Warlord of Blood " +"stands upon a mountain of his tattered victims. His dark blade screams a " +"black curse to the living; a tortured invitation to any who would stand " +"before this Executioner of Hell.\n" +" \n" +"It is also written that although he was once a mortal who fought beside the " +"Legion of Darkness during the Sin War, he lost his humanity to his " +"insatiable hunger for blood." +msgstr "" +"Я ведаю толькі адну легенду, якая апавядае пра такога воіна, якога ты " +"апісваеш. Яго гісторыя сустракаецца ў старадаўніх хроніках вайны Граху...\n" +"\n" +"Заплямлены тысяччу год вайны, крыві і смерці, Крывавы ваявода стаў на гары " +"сваіх падраных ахвяр.\n" +" \n" +"Таксама пісалася, што ён быў калісьці смертны, і біўся супраць Легіёна Цемры " +"ў Вайне Граху, але ён страціў сваёй чалавечнасць з-за няўтольнай прагі да " +"крыві." + +#. TRANSLATORS: Quest dialog spoken by Ogden +#: Source/textdat.cpp:215 +msgid "" +"I am afraid that I haven't heard anything about such a vicious warrior, good " +"master. I hope that you do not have to fight him, for he sounds extremely " +"dangerous." +msgstr "" +"Баюся, нічога пра такога ліхога воіна я не чуў, добры чалавек. Я " +"спадзяваюся, табе не спатрэбіцца біцца з ім, бо здаецца ён вельмі небяспечны." + +#. TRANSLATORS: Quest dialog spoken by Pipin +#: Source/textdat.cpp:217 +msgid "" +"Cain would be able to tell you much more about something like this than I " +"would ever wish to know." +msgstr "" +"Кейн мог бы расказаць табе пра такое значна больш, чым я б калі-небудзь " +"хацеў ведаць." + +#. TRANSLATORS: Quest dialog spoken by Gillian +#: Source/textdat.cpp:219 +msgid "" +"If you are to battle such a fierce opponent, may Light be your guide and " +"your defender. I will keep you in my thoughts." +msgstr "" +"Калі табе прыйдзецца біцца з такім лютым ворагам, няхай Святло будзе табе " +"спадарожнікам і ахоўцам. Я буду думаць пра цябе." + +#. TRANSLATORS: Quest dialog spoken by Griswold +#: Source/textdat.cpp:221 +msgid "" +"Dark and wicked legends surrounds the one Warlord of Blood. Be well " +"prepared, my friend, for he shows no mercy or quarter." +msgstr "" +"Цёмныя і страшныя легенды ахінаюць Крывавага Ваяводу таго. Добра " +"падрыхтуйся, сябру, бо ён не пакажа ані літасці." + +#. TRANSLATORS: Quest dialog spoken by Farnham +#: Source/textdat.cpp:223 +msgid "" +"Always you gotta talk about Blood? What about flowers, and sunshine, and " +"that pretty girl that brings the drinks. Listen here, friend - you're " +"obsessive, you know that?" +msgstr "" +"Вечна ты пра Кроў. А як жа кветкі там, сонейка, або красуня тая, што выпіўку " +"носіць. Слухай, друг, ты проста апантаны, табе не гаварылі?" + +#. TRANSLATORS: Quest dialog spoken by Adria +#: Source/textdat.cpp:225 +msgid "" +"His prowess with the blade is awesome, and he has lived for thousands of " +"years knowing only warfare. I am sorry... I can not see if you will defeat " +"him." +msgstr "" +"Яго ўмельства са зброяю жахлівае, і ён пражыў цэлыя тысячы год, ведаючы адно " +"вайну. Прабач мне... Я не бачу, ці пераможаш ты яго." + +#. TRANSLATORS: Quest dialog spoken by Wirt +#: Source/textdat.cpp:227 +msgid "" +"I haven't ever dealt with this Warlord you speak of, but he sounds like he's " +"going through a lot of swords. Wouldn't mind supplying his armies..." +msgstr "" +"Ніколі не меў спраў з гэтым Ваяводам, але відаць ён пераводзіць шмат мячоў. " +"Забяспечваць яго было б ідэяй..." + +#. TRANSLATORS: Quest dialog spoken by Warlord of Blood (Hostile) +#: Source/textdat.cpp:229 +msgid "" +"My blade sings for your blood, mortal, and by my dark masters it shall not " +"be denied." +msgstr "" +"Мой клінок пяе тваёй крыві, смертны, і клянуся сваімі гаспадарамі, яго не " +"спыняць." + +#. TRANSLATORS: Quest dialog spoken by Cain +#: Source/textdat.cpp:231 +msgid "" +"Griswold speaks of the Heaven Stone that was destined for the enclave " +"located in the east. It was being taken there for further study. This stone " +"glowed with an energy that somehow granted vision beyond that which a normal " +"man could possess. I do not know what secrets it holds, my friend, but " +"finding this stone would certainly prove most valuable." +msgstr "" +"Грызвальд гаворыць пра Нябёсны Камень. Яго меліся адвезці на Усход вучоным, " +"каб лепш даследаваць. Камень ззяў Энергіяю, якая нейкім чынам абдорвала " +"зрокам, які звычайнаму чалавеку недаступны. Не ведаю, якія сакрэты ён тоіць, " +"дружа мой, але знайсці гэты камень дорага б каштавала." + +#. TRANSLATORS: Quest dialog spoken by Ogden +#: Source/textdat.cpp:233 +msgid "" +"The caravan stopped here to take on some supplies for their journey to the " +"east. I sold them quite an array of fresh fruits and some excellent " +"sweetbreads that Garda has just finished baking. Shame what happened to " +"them..." +msgstr "" +"Той караван тут спыняўся купіць прыпасаў на дарогу на Усход. Я прадаў ім " +"багата фруктаў і прыўдалага мяса, якое Гарда толькі напякла. Шкада, што ўсё " +"так адбылося…" + +#. TRANSLATORS: Quest dialog spoken by Pipin +#: Source/textdat.cpp:235 +msgid "" +"I don't know what it is that they thought they could see with that rock, but " +"I will say this. If rocks are falling from the sky, you had better be " +"careful!" +msgstr "" +"Не ведаю, што яны там хацелі ўбачыць з гэтым каменем, але я вось што скажу. " +"Калі з неба падаюць камяні, лепш быць асцярожным!" + +#. TRANSLATORS: Quest dialog spoken by Gillian +#: Source/textdat.cpp:237 +msgid "" +"Well, a caravan of some very important people did stop here, but that was " +"quite a while ago. They had strange accents and were starting on a long " +"journey, as I recall. \n" +" \n" +"I don't see how you could hope to find anything that they would have been " +"carrying." +msgstr "" +"Ну, тут праўда спыняўся караван з нейкімі надта важнымі людзьмі, але гэта " +"ўжо даволі даўно было. Вымова ў іх была дзіўная. Калі нічога не блытаю, яны " +"пачыналі доўгае падарожжа.\n" +"\n" +"Не ўяўляю, як ты зможаш знайсці хоць штосьці з паклажы." + +#. TRANSLATORS: Quest dialog spoken by Griswold +#: Source/textdat.cpp:239 +msgid "" +"Stay for a moment - I have a story you might find interesting. A caravan " +"that was bound for the eastern kingdoms passed through here some time ago. " +"It was supposedly carrying a piece of the heavens that had fallen to earth! " +"The caravan was ambushed by cloaked riders just north of here along the " +"roadway. I searched the wreckage for this sky rock, but it was nowhere to be " +"found. If you should find it, I believe that I can fashion something useful " +"from it." +msgstr "" +"А пастаі хвілінку, маю тут адну рэч, мо цябе зацікавіць. Тут нядаўна караван " +"праязджаў, які кіраваўся на ўсходнія каралеўствы. Везлі там нібыто кавалак " +"неба, што ўпаў на зямлю! Караван трапіў у засаду нейкіх вершнікаў у плашчах, " +"на поўнач па дарозе адсюль. Я абшукаў там абломкі, але каменя і след " +"прастыў. Калі ён табе знойдзецца, думаю я б змог нешто карыснае з яго " +"зладзіць." + +#. TRANSLATORS: Quest dialog spoken by Griswold +#: Source/textdat.cpp:241 +msgid "" +"I am still waiting for you to bring me that stone from the heavens. I know " +"that I can make something powerful out of it." +msgstr "" +"Я ўсё яшчэ чакаю, ці прынясеш ты мне таго каменя з нябёсаў. Я ведаю, што " +"змагу зладзіць з яго нешто моцнае." + +#. TRANSLATORS: Quest dialog spoken by Griswold(Quest End) +#: Source/textdat.cpp:243 +msgid "" +"Let me see that - aye... aye, it is as I believed. Give me a moment...\n" +" \n" +"Ah, Here you are. I arranged pieces of the stone within a silver ring that " +"my father left me. I hope it serves you well." +msgstr "" +"Дай глянуць — але... але, так я і думаў. Дай хвілінку...\n" +"\n" +"Ну вось, трымай. Я ўладзіў кавалкі каменя ў сярэбраным пярсцёнку, які мне " +"дай мой бацько. Буду спадзявацца, яно стане табе ў прыгодзе." + +#. TRANSLATORS: Quest dialog spoken by Farnham +#: Source/textdat.cpp:245 +msgid "" +"I used to have a nice ring; it was a really expensive one, with blue and " +"green and red and silver. Don't remember what happened to it, though. I " +"really miss that ring..." +msgstr "" +"Быў у мяне некалі пярсцёнак. Даражэзны, з блакітным на ім, і зялёным, і " +"чырвоным, і серабром. Не памятаю, што з ім сталася, праўда. Моцна па ім " +"сумую..." + +#. TRANSLATORS: Quest dialog spoken by Adria +#: Source/textdat.cpp:247 +msgid "" +"The Heaven Stone is very powerful, and were it any but Griswold who bid you " +"find it, I would prevent it. He will harness its powers and its use will be " +"for the good of us all." +msgstr "" +"Нябёсны камень магутны, і калі б цябе прасіў яго знайсці хто-небудзь, апроч " +"Грызвальда, я бы не дазволіла таго. Ён пакорыць яго сілу, і камень будзе " +"ўжыты на карысць усім нам." + +#. TRANSLATORS: Quest dialog spoken by Wirt +#: Source/textdat.cpp:249 +msgid "" +"If anyone can make something out of that rock, Griswold can. He knows what " +"he is doing, and as much as I try to steal his customers, I respect the " +"quality of his work." +msgstr "" +"Калі нехта можа нешта зрабіць з таго каменя, дык гэта Грызвальд. Ён знае, " +"што робіць, хоць я і сцягваю ў яго пакупнікоў, якасць яго працы я паважаю." + +#. TRANSLATORS: Quest dialog spoken by Cain +#: Source/textdat.cpp:251 +msgid "" +"The witch Adria seeks a black mushroom? I know as much about Black Mushrooms " +"as I do about Red Herrings. Perhaps Pepin the Healer could tell you more, " +"but this is something that cannot be found in any of my stories or books." +msgstr "" +"Ведзьма Эйдрыя шукае чорны грыб? Пра такое я магу сказаць не больш, чым пра " +"селядзец. Мабыць, Піпін ведае больш, але гэта нешта такое, чаго ў маіх " +"кнігах і байках не знойдзеш." + +#. TRANSLATORS: Quest dialog spoken by Ogden +#: Source/textdat.cpp:253 +msgid "" +"Let me just say this. Both Garda and I would never, EVER serve black " +"mushrooms to our honored guests. If Adria wants some mushrooms in her stew, " +"then that is her business, but I can't help you find any. Black mushrooms... " +"disgusting!" +msgstr "" +"Вось што я табе скажу. Ні Гарда, ні я, ніколі, ніколі б не падалі сваім " +"шаноўным гасцям чорных грыбоў. Калі Эйдрыі хочацца сабе ў поліўку грыбоў, яе " +"дзела, але я табе тут не памочнік. Чорныя грыбы… тфу!" + +#. TRANSLATORS: Quest dialog spoken by Pipin +#: Source/textdat.cpp:255 +msgid "" +"The witch told me that you were searching for the brain of a demon to assist " +"me in creating my elixir. It should be of great value to the many who are " +"injured by those foul beasts, if I can just unlock the secrets I suspect " +"that its alchemy holds. If you can remove the brain of a demon when you kill " +"it, I would be grateful if you could bring it to me." +msgstr "" +"Ведзьма казала, ты шукаеш мозг дэмана, каб дамагчы мне зварыць эліксір. Ён " +"будзе вельмі каштоўны для ўсіх, каго паранілі гэтыя вычварні, мне бы толькі " +"разгадаць сакрэт, што тоіць яго састаў. Дастанеш мозг з забітага дэмана, я " +"буду вельмі ўдзячны, калі прынясеш яго мне." + +#. TRANSLATORS: Quest dialog spoken by Pepin +#: Source/textdat.cpp:257 +msgid "" +"Excellent, this is just what I had in mind. I was able to finish the elixir " +"without this, but it can't hurt to have this to study. Would you please " +"carry this to the witch? I believe that she is expecting it." +msgstr "" +"Выдатна, гэта я і ўяўляў. Я змог даварыць эліксір і без гэтага, але мець " +"такое для доследу лішнім не будзе. Калі ласка, аднясеш вось гэта ведзьме? " +"Думаю, яна якраз чакае." + +#. TRANSLATORS: Quest dialog spoken by Farnham +#: Source/textdat.cpp:259 +msgid "" +"I think Ogden might have some mushrooms in the storage cellar. Why don't you " +"ask him?" +msgstr "Думаю, Огдэн захаваў крыху грыбоў у склепенні. Можа яго спытай?" + +#. TRANSLATORS: Quest dialog spoken by Griswold +#: Source/textdat.cpp:261 +msgid "" +"If Adria doesn't have one of these, you can bet that's a rare thing indeed. " +"I can offer you no more help than that, but it sounds like... a huge, " +"gargantuan, swollen, bloated mushroom! Well, good hunting, I suppose." +msgstr "" +"Калі і Эйдрыя не мае такіх, мусіць, і праўда рэдкая рэч. Наўрад ці магу яшчэ " +"як спамагчы, але са слоў тваіх гэто… ну, здаровы такі, велізарны, уздуты, " +"азызлы грыб! Ну, добраго палявання, мусіць." + +#. TRANSLATORS: Quest dialog spoken by Farnham +#: Source/textdat.cpp:263 +msgid "" +"Ogden mixes a MEAN black mushroom, but I get sick if I drink that. Listen, " +"listen... here's the secret - moderation is the key!" +msgstr "" +"Огдэн гоніць ФАЙНЫ “чорны грыб”, хаця мяне ірве калі вып’ю такога. Слухай, " +"слухай… Вось у чым сакрэт – ТРЭБА ВЕДАЦЬ МЕРУ!" + +#. TRANSLATORS: Quest dialog spoken by Adria +#: Source/textdat.cpp:265 +msgid "" +"What do we have here? Interesting, it looks like a book of reagents. Keep " +"your eyes open for a black mushroom. It should be fairly large and easy to " +"identify. If you find it, bring it to me, won't you?" +msgstr "" +"Што гэта тут у нас? Цікава. З віду гэта Кніга Рэагентаў. Усюды пільна шукай " +"Чорны Грыб. Ён даволі вялікі, не памылішся. Калі знойдзеш, ці не прынясеш " +"мне, гм?" + +#. TRANSLATORS: Quest dialog spoken by Adria +#: Source/textdat.cpp:267 +msgid "" +"It's a big, black mushroom that I need. Now run off and get it for me so " +"that I can use it for a special concoction that I am working on." +msgstr "" +"Мне патрэбны вялікі чорны грыб. Давай, бяжы, прынясеш мне такі, і я дадам " +"яго ў асаблівае зелле, з якім зараз работаю." + +#. TRANSLATORS: Quest dialog spoken by Adria +#: Source/textdat.cpp:269 +msgid "" +"Yes, this will be perfect for a brew that I am creating. By the way, the " +"healer is looking for the brain of some demon or another so he can treat " +"those who have been afflicted by their poisonous venom. I believe that he " +"intends to make an elixir from it. If you help him find what he needs, " +"please see if you can get a sample of the elixir for me." +msgstr "" +"Так. Дасканала для майго адвару будзе. Дарэчы, лекар шукаў мозг якогасьці " +"дэмана, каб лячыць людзей, якіх заразілі атрутным ядам. Думаю, ён маецца " +"зрабіць эліксір. Калі дапаможаш яму знайсці, што яму трэба, калі ласка, " +"прынясі мне трохі гэтага эліксіру на пробу." + +#. TRANSLATORS: Quest dialog spoken by Adria +#: Source/textdat.cpp:271 +msgid "" +"Why have you brought that here? I have no need for a demon's brain at this " +"time. I do need some of the elixir that the Healer is working on. He needs " +"that grotesque organ that you are holding, and then bring me the elixir. " +"Simple when you think about it, isn't it?" +msgstr "" +"Навошта ты яго сюды нясеш? Гэты раз мне дэманаў мозг без патрэбы. Хаця мне " +"патрэбна трохі эліксіра, які лекар цяпер варыць. Лепш аднясі гэты гратэскны " +"орган яму, а мне потым прынясеш эліксіру. Праўда проста, калі трохі " +"падумаць, гм?" + +#. TRANSLATORS: Quest dialog spoken by Adria (Quest End) +#: Source/textdat.cpp:273 +msgid "" +"What? Now you bring me that elixir from the healer? I was able to finish my " +"brew without it. Why don't you just keep it..." +msgstr "" +"Чаго? Цяпер-то ты мне нясеш эліксір лекара? Я ўжо скончыла адвар і без яго. " +"Саба пакінь..." + +#. TRANSLATORS: Quest dialog spoken by Wirt +#: Source/textdat.cpp:275 +msgid "" +"I don't have any mushrooms of any size or color for sale. How about " +"something a bit more useful?" +msgstr "" +"Я не прадаю грыбы, любых колераў і памераў. Можа табе лепш чаго больш " +"карыснага?" + +#. TRANSLATORS: Quest dialog spoken by Cain (currently unused) +#: Source/textdat.cpp:277 +msgid "" +"So, the legend of the Map is real. Even I never truly believed any of it! I " +"suppose it is time that I told you the truth about who I am, my friend. You " +"see, I am not all that I seem...\n" +" \n" +"My true name is Deckard Cain the Elder, and I am the last descendant of an " +"ancient Brotherhood that was dedicated to keeping and safeguarding the " +"secrets of a timeless evil. An evil that quite obviously has now been " +"released...\n" +" \n" +"The evil that you move against is the dark Lord of Terror - known to mortal " +"men as Diablo. It was he who was imprisoned within the Labyrinth many " +"centuries ago. The Map that you hold now was created ages ago to mark the " +"time when Diablo would rise again from his imprisonment. When the two stars " +"on that map align, Diablo will be at the height of his power. He will be all " +"but invincible...\n" +" \n" +"You are now in a race against time, my friend! Find Diablo and destroy him " +"before the stars align, for we may never have a chance to rid the world of " +"his evil again!" +msgstr "" +"Значыць, легедная ад Мапе праўдзівая. Нават я ніколі да канца не верыў! " +"Здаецца, мне пара расказаць табе, хто я сапраўды такі, дружа мой. Бач, я не " +"зусім той, кім падаюся...\n" +" \n" +"Маё сапраўднае імя Дэкард Кейн Старэйшы, і я апошні нашчадак старасвецкага " +"Брацтва, якое прысвяціла сябе ахове і нагляду таямніц вечнага Зла. Зла, " +"якое, цяпер ужо відавочна, вызвалілі...\n" +"\n" +"Зло, супраць якога ты рушыш, — цёмны Пах Жаху, смертным людзям вядомы як " +"Д'ябла. Гэта яго паланілі ў Лабірынце многія стагоддзі таму. Мапа ў тваіх " +"руках была створана даўным даўно, каб адзначыць час, калі Д'ябла зноў " +"паўстане са свайго палону. Калі дзве зоркі з гэтай мапы зыдуцца, Д'ябла " +"сягне вышыні сваёй сілы. Ён стане амаль неадольны!" + +#. TRANSLATORS: Quest dialog spoken by Cain (currently unused) +#: Source/textdat.cpp:279 +msgid "" +"Our time is running short! I sense his dark power building and only you can " +"stop him from attaining his full might." +msgstr "" +"Наш час канчаецца! Я адчуваю, як яго цёмная сіла збіраецца, і толькі ты " +"можаш не даць яму дасягнуць усяе яго магутнасці." + +#. TRANSLATORS: Quest dialog spoken by Cain (currently unused) +#: Source/textdat.cpp:281 +msgid "" +"I am sure that you tried your best, but I fear that even your strength and " +"will may not be enough. Diablo is now at the height of his earthly power, " +"and you will need all your courage and strength to defeat him. May the Light " +"protect and guide you, my friend. I will help in any way that I am able." +msgstr "" +"Я ўпэўнены, большага ў цябе нельга прасіць, але я страшуся нават тваёй сілы " +"будзе не дастаткова. Д'ябла цяпер дасягнуў вышыні сваёй сілы, і табе " +"спатрэбіцца ўся твая смеласьці і моц, каб перамагчы яго. Няхай Святло будзе " +"тваім правадніком і абаронцам, дружа мой. Я дапамагу чым толькі змагу." + +#. TRANSLATORS: Quest dialog spoken by Ogden (currently unused) +#: Source/textdat.cpp:283 +msgid "" +"If the witch can't help you and suggests you see Cain, what makes you think " +"that I would know anything? It sounds like this is a very serious matter. " +"You should hurry along and see the storyteller as Adria suggests." +msgstr "" +"Раз ведзьма не дапамагла і кажа ісці да Кейна, чаго ты думаеш, што я нешта " +"ведаю? Відаць вельмі важная справа. Спяшацца бы табе да расказчыка, як " +"Эйдрыя і гаворыць." + +#. TRANSLATORS: Quest dialog spoken by Pipin (currently unused) +#: Source/textdat.cpp:285 +msgid "" +"I can't make much of the writing on this map, but perhaps Adria or Cain " +"could help you decipher what this refers to. \n" +" \n" +"I can see that it is a map of the stars in our sky, but any more than that " +"is beyond my talents." +msgstr "" +"Я не надта разумею напісанае, але можа Эйдрыя ці Кейн могуць дапамагчы " +"расшыфраваць, пра што тут.\n" +" \n" +"Я бачу, што гэта мапа зорнага неба, але больш за тое ўжо па-за мамі " +"талентамі." + +#. TRANSLATORS: Quest dialog spoken by Gillian (currently unused) +#: Source/textdat.cpp:287 +msgid "" +"The best person to ask about that sort of thing would be our storyteller. \n" +" \n" +"Cain is very knowledgeable about ancient writings, and that is easily the " +"oldest looking piece of paper that I have ever seen." +msgstr "" +"Найлепш спытаць аб гэтым нашага Расказчыка. \n" +" \n" +"Кейн вельмі добра знаецца на старажытным пісьме, а гэта лёгка можа быць " +"найстарэшай паперкай, якую я бачыла." + +#. TRANSLATORS: Quest dialog spoken by Griswold (currently unused) +#: Source/textdat.cpp:289 +msgid "" +"I have never seen a map of this sort before. Where'd you get it? Although I " +"have no idea how to read this, Cain or Adria may be able to provide the " +"answers that you seek." +msgstr "" +"Ніколі такой мапы не бачыў. Скуль яна? Хоць і не ведаю, як яе разумець, Кейн " +"або Эйдрыя можа і дадуць табе патрэбныя адказы." + +#. TRANSLATORS: Quest dialog spoken by Farnham (currently unused) +#: Source/textdat.cpp:291 +msgid "" +"Listen here, come close. I don't know if you know what I know, but you have " +"really got somethin' here. That's a map." +msgstr "" +"Слухай сюды, бліжэй стань. Не ведаю, ці ты ведаеш, што я ведаю, але тут у " +"цябе сапраўды штосьці. Гэта мапа." + +#. TRANSLATORS: Quest dialog spoken by Adria (currently unused) +#: Source/textdat.cpp:293 +msgid "" +"Oh, I'm afraid this does not bode well at all. This map of the stars " +"portends great disaster, but its secrets are not mine to tell. The time has " +"come for you to have a very serious conversation with the Storyteller..." +msgstr "" +"Авохці, нічога добрага гэта не вяшчуе. Мапа зорнага неба, яна прадракае " +"вялікую бяду, але яе сакрэты не мае. Таму прыйшла пара табе вельмі сур'ёзна " +"пагаварыць з Расказчыкам." + +#. TRANSLATORS: Quest dialog spoken by Wirt (currently unused) +#: Source/textdat.cpp:295 +msgid "" +"I've been looking for a map, but that certainly isn't it. You should show " +"that to Adria - she can probably tell you what it is. I'll say one thing; it " +"looks old, and old usually means valuable." +msgstr "" +"Мапу я шукаў, але дакладна не такую. Эйдрыі пакажы. Напэўна аб'ясніць, што " +"гэта. Скажу адно што з віду старая, а старая — значыць дарагая." + +#. TRANSLATORS: Quest dialog spoken by Gharbad the Weak +#: Source/textdat.cpp:297 +msgid "" +"Pleeeease, no hurt. No Kill. Keep alive and next time good bring to you." +msgstr "" +"Калі лааааска, не бі. Не забі. Пакіні ў жывых, наступны раз нясці табе " +"харошае." + +#. TRANSLATORS: Quest dialog spoken by Gharbad the Weak +#: Source/textdat.cpp:299 +msgid "" +"Something for you I am making. Again, not kill Gharbad. Live and give " +"good. \n" +" \n" +"You take this as proof I keep word..." +msgstr "" +"Нешта раблю для цябе. Не забівай Гарбада, калі ласка. Жыць і даць харошае.\n" +"\n" +"На, доказ, што праўду гавару..." + +#. TRANSLATORS: Quest dialog spoken by Gharbad the Weak +#: Source/textdat.cpp:301 +msgid "" +"Nothing yet! Almost done. \n" +" \n" +"Very powerful, very strong. Live! Live! \n" +" \n" +"No pain and promise I keep!" +msgstr "" +"Яшчэ нічога! Амаль гатова.\n" +"\n" +"Вельмі моцнае, вельмі сільнае. Жыць! Жыць!\n" +"\n" +"Болю не будзе і абяцананне выканаю!" + +#. TRANSLATORS: Quest dialog spoken by Gharbad the Weak (Hostile) +#: Source/textdat.cpp:303 +msgid "This too good for you. Very Powerful! You want - you take!" +msgstr "Для цябе занадта харошае. Вельмі моцнае! Ты хацець — ты забіраць!" + +#. TRANSLATORS: Quest dialog spoken by Zhar the Mad (annoyed / Hostile) +#: Source/textdat.cpp:305 +msgid "" +"What?! Why are you here? All these interruptions are enough to make one " +"insane. Here, take this and leave me to my work. Trouble me no more!" +msgstr "" +"Што?! Ты тут чаго? Так і здурнець можна калі столькі перыпыняюць. На, бяры і " +"дай мне працаваць. Не дакучай мне больш!" + +#. TRANSLATORS: Quest dialog spoken by Zhar the Mad (Hostile) +#: Source/textdat.cpp:307 +msgid "Arrrrgh! Your curiosity will be the death of you!!!" +msgstr "А-а-ай! Твая цікаўнасць стане табе смерцю!!!" + +#. TRANSLATORS: Neutral dialog spoken by Cain +#: Source/textdat.cpp:308 +msgid "Hello, my friend. Stay awhile and listen..." +msgstr "Прывет, дружа мой. Пастой крыху ды паслухай..." + +#. TRANSLATORS: Neutral dialog spoken by Cain (Gossip) +#: Source/textdat.cpp:309 +msgid "" +"While you are venturing deeper into the Labyrinth you may find tomes of " +"great knowledge hidden there. \n" +" \n" +"Read them carefully for they can tell you things that even I cannot." +msgstr "" +"На сваім шляху ўглыб Лабірынта ты можаш знайсці кнігі, якія таяць вялікія " +"веды. \n" +"\n" +"Уважліва іх чытай, бо яны табе раскажуць такое, чаго і я нават не змог бы." + +#. TRANSLATORS: Neutral dialog spoken by Cain (Gossip) +#: Source/textdat.cpp:311 +msgid "" +"I know of many myths and legends that may contain answers to questions that " +"may arise in your journeys into the Labyrinth. If you come across challenges " +"and questions to which you seek knowledge, seek me out and I will tell you " +"what I can." +msgstr "" +"Пакуль ты вандруеш па Лабірынце, ў цябе могуць паўстаць пытанні, адказы на " +"якія могуць таіць міфы і легенды якія я ведаю. Калі сутыкнешся з нейкім " +"такім пытаннем і зацікавішся ім, адшукай мяне, ды я раскажу, пра што змагу." + +#. TRANSLATORS: Neutral dialog spoken by Cain (Gossip) +#: Source/textdat.cpp:313 +msgid "" +"Griswold - a man of great action and great courage. I bet he never told you " +"about the time he went into the Labyrinth to save Wirt, did he? He knows his " +"fair share of the dangers to be found there, but then again - so do you. He " +"is a skilled craftsman, and if he claims to be able to help you in any way, " +"you can count on his honesty and his skill." +msgstr "" +"Грызвальд — чалавек вельмі руплівы і смелы. Я ўпэўнены, ён ніколі не " +"расказваў табе, як выратаваў Вірта з Лабірынта, так жа? Ён добра ведае, якая " +"вялікая небяспека тоіцца там, як і ты. Ён умелы майстар, таму калі ён " +"сцвярджае, што ва ўсім табе можа дапамагчы, можаш не сумнявацца ў яго " +"сумленнасці ды, ўмельстве." + +#. TRANSLATORS: Neutral dialog spoken by Cain (Gossip) +#: Source/textdat.cpp:315 +msgid "" +"Ogden has owned and run the Rising Sun Inn and Tavern for almost four years " +"now. He purchased it just a few short months before everything here went to " +"hell. He and his wife Garda do not have the money to leave as they invested " +"all they had in making a life for themselves here. He is a good man with a " +"deep sense of responsibility." +msgstr "" +"Огдэн ужо чатыры гады карчмарыць ва Узыходным Сонцы. Ён выкупіў яго ўсяго за " +"пару месяцаў да таго, як тут усё пакацілася к чорту. Яму і жонцы яго Гардзе " +"не хапае грошай, каб з'ехаць адсюль, бо ўсё патрацілі, каб тут асесці. Ён " +"добры чалавек, з моцным пачуццём адказнасці." + +#. TRANSLATORS: Neutral dialog spoken by Cain (Gossip) +#: Source/textdat.cpp:317 +msgid "" +"Poor Farnham. He is a disquieting reminder of the doomed assembly that " +"entered into the Cathedral with Lazarus on that dark day. He escaped with " +"his life, but his courage and much of his sanity were left in some dark pit. " +"He finds comfort only at the bottom of his tankard nowadays, but there are " +"occasional bits of truth buried within his constant ramblings." +msgstr "" +"Небарака Фарнам. Неспакойны напамін аб загубленай суполцы, што ступіла ў " +"Храм за Лазарам тым змрочным днём. Ён збег адтуль жывым, але храбрасць сваю, " +"як і частку здаровага розуму, пакінуў у якойсьці чорнай яме. Цяпер ён " +"знаходзіць суцяшэнне толькі на дне свайго куфля, але ў плыні яго бясконцай " +"балбатні схаваны крупінкі праўды." + +#. TRANSLATORS: Neutral dialog spoken by Cain (Gossip) +#: Source/textdat.cpp:319 +msgid "" +"The witch, Adria, is an anomaly here in Tristram. She arrived shortly after " +"the Cathedral was desecrated while most everyone else was fleeing. She had a " +"small hut constructed at the edge of town, seemingly overnight, and has " +"access to many strange and arcane artifacts and tomes of knowledge that even " +"I have never seen before." +msgstr "" +"Тая ведзьма, Эйдрыя, — як анамалія тут у Трыстраме. Яна прыбыла якраз калі " +"апаганілі Храм, пакуль усе вакол з’язджалі адсюль. Яна збудавала хату на " +"ўскраіне горада, відаць, ноччу, і мае доступ да многіх дзіўных і таемных " +"артэфактаў і кніг з ведамі, якія нават я ніколі не бачыў." + +#. TRANSLATORS: Neutral dialog spoken by Cain (Gossip) +#: Source/textdat.cpp:321 +msgid "" +"The story of Wirt is a frightening and tragic one. He was taken from the " +"arms of his mother and dragged into the labyrinth by the small, foul demons " +"that wield wicked spears. There were many other children taken that day, " +"including the son of King Leoric. The Knights of the palace went below, but " +"never returned. The Blacksmith found the boy, but only after the foul beasts " +"had begun to torture him for their sadistic pleasures." +msgstr "" +"Гісторыя Вірта жахлівая і трагічная. Гадкія дэманяты з коп’ямі вырвалі яго з " +"рук яго мацеры і сцягнулі ў лабірынт. Многа дзяцей забралі ў той дзень, " +"уключаючы сына Караля. Рыцары палаца спускаліся туды, але ніхто не вярнуўся. " +"Наш каваль знайшоў хлопчыка, але ўжо пасля таго, як яны пачалі яго катаваць " +"дзеля сваёй садысцкай асалоды." + +#. TRANSLATORS: Neutral dialog spoken by Cain (Gossip) +#: Source/textdat.cpp:323 +msgid "" +"Ah, Pepin. I count him as a true friend - perhaps the closest I have here. " +"He is a bit addled at times, but never a more caring or considerate soul has " +"existed. His knowledge and skills are equaled by few, and his door is always " +"open." +msgstr "" +"А, Піпін. Я лічу яго сапраўдным сябром, магчыма, бліжэйшага і не маю. Ён " +"часам трохі збянтэжаны, але не нарадзілася яшчэ душа клапатлівейшая ці " +"чуллівейшая. Мала з кім можна параўнаць яго веды і ўменне, а дзверы яго " +"заўсёды адчыненыя." + +#. TRANSLATORS: Neutral dialog spoken by Cain (Gossip) +#: Source/textdat.cpp:325 +msgid "" +"Gillian is a fine woman. Much adored for her high spirits and her quick " +"laugh, she holds a special place in my heart. She stays on at the tavern to " +"support her elderly grandmother who is too sick to travel. I sometimes fear " +"for her safety, but I know that any man in the village would rather die than " +"see her harmed." +msgstr "" +"Джыліен добрая жанчына. Яе любяць за вясёлы нораў і смяшлівасць, і яна " +"занімае асобнае месца ў маім сэрцы. Яна засталася ў карчме, каб даглядаць " +"сваю пажылую бабулю, хвароба не дае ёй ехаць. Падчас я баюся за яе, але " +"ведаю, што ўсякі ў вёсцы хутчэй бы сам памёр, чым даў бы яе ў крыўду." + +#. TRANSLATORS: Neutral dialog spoken by Ogden +#: Source/textdat.cpp:327 +msgid "Greetings, good master. Welcome to the Tavern of the Rising Sun!" +msgstr "Дабрыдзень, добры чалавек. Вітаю ў карчме Узыходнага Сонца!" + +#. TRANSLATORS: Neutral dialog spoken by Ogden (Gossip) +#: Source/textdat.cpp:329 +msgid "" +"Many adventurers have graced the tables of my tavern, and ten times as many " +"stories have been told over as much ale. The only thing that I ever heard " +"any of them agree on was this old axiom. Perhaps it will help you. You can " +"cut the flesh, but you must crush the bone." +msgstr "" +"Шмат прайдзісветаў удастоілі сталы маёй карчмы, а аповесцяў за імі расказалі " +"ў дзесяць разоў больш пад столькі ж эля. Адзіная рэч, у якой яны ўсе " +"сыходзіліся, была адна старая аксіёма. Можа яна табе дапаможа. Плоць можна " +"разрэзаць, але мусіш разбіць косць." + +#. TRANSLATORS: Neutral dialog spoken by Ogden (Gossip) +#: Source/textdat.cpp:331 +msgid "" +"Griswold the blacksmith is extremely knowledgeable about weapons and armor. " +"If you ever need work done on your gear, he is definitely the man to see." +msgstr "" +"Каваль Грызвальд надзвычай дасведчаны ў зброі і брані. Калі табе як-небудзь " +"спатрэбіцца апрацаваць свой рыштунак, табе дакладна да яго." + +#. TRANSLATORS: Neutral dialog spoken by Ogden (Gossip) +#: Source/textdat.cpp:333 +msgid "" +"Farnham spends far too much time here, drowning his sorrows in cheap ale. I " +"would make him leave, but he did suffer so during his time in the Labyrinth." +msgstr "" +"Фарнам тут зашмат часу бавіць, топіць сваё гора ў танным элю. Я б праганяў " +"яго, але ж тады ў Лабірынце ён і праўда многа выпакутаваў." + +#. TRANSLATORS: Neutral dialog spoken by Ogden (Gossip) +#: Source/textdat.cpp:335 +msgid "" +"Adria is wise beyond her years, but I must admit - she frightens me a " +"little. \n" +" \n" +"Well, no matter. If you ever have need to trade in items of sorcery, she " +"maintains a strangely well-stocked hut just across the river." +msgstr "" +"Эйдрыя мудрая не па гадах, але мушу прызнацца — яна мяне трохі пужае.\n" +"\n" +"Ну і няважна. Калі табе спатрэбяцца якія чараўнічыя рэчы, яе хата за ракою, " +"з вельмі дзіўна багатым выбарам тавараў." + +#. TRANSLATORS: Neutral dialog spoken by Ogden (Gossip) +#: Source/textdat.cpp:337 +msgid "" +"If you want to know more about the history of our village, the storyteller " +"Cain knows quite a bit about the past." +msgstr "" +"Калі хочаш больш знаць пра гісторыю нашай вёсці, расказчык Кейн ведае даволі " +"многа пра мінульшчыну." + +#. TRANSLATORS: Neutral dialog spoken by Ogden (Gossip) +#: Source/textdat.cpp:339 +msgid "" +"Wirt is a rapscallion and a little scoundrel. He was always getting into " +"trouble, and it's no surprise what happened to him. \n" +" \n" +"He probably went fooling about someplace that he shouldn't have been. I feel " +"sorry for the boy, but I don't abide the company that he keeps." +msgstr "" +"Вірт — маленькі жулік і нягоднік. Ён заўсёды трапляў у бяду, няма дзіва, што " +"з ім такое здарылася.\n" +"\n" +"Відаць залез падурэць куды нельга было. Мне шкада яго, праўда, але тых, з " +"кім ён водзіцца, я не трываю." + +#. TRANSLATORS: Neutral dialog spoken by Ogden (Gossip) +#: Source/textdat.cpp:341 +msgid "" +"Pepin is a good man - and certainly the most generous in the village. He is " +"always attending to the needs of others, but trouble of some sort or another " +"does seem to follow him wherever he goes..." +msgstr "" +"Піпін — добры чалавек, і дакладна самы велікадушны ў вёсцы. Ён заўсёды дбае " +"пра патрэбы другіх, але нейкая бяда быццам і праўда следам за ім ідзе, куды " +"б ён ні ішоў..." + +#. TRANSLATORS: Neutral dialog spoken by Ogden (Gossip) +#: Source/textdat.cpp:343 +msgid "" +"Gillian, my Barmaid? If it were not for her sense of duty to her grand-dam, " +"she would have fled from here long ago. \n" +" \n" +"Goodness knows I begged her to leave, telling her that I would watch after " +"the old woman, but she is too sweet and caring to have done so." +msgstr "" +"Джыліен, барменка мая? Каб не пачуццё абавязку перад сваёй бабуляй, яна б " +"даўно адсюль уцякла.\n" +"\n" +"Неба — сведка, маліў яе з'ехаць адсюль, казаў, маўляў, пагляджу старую, але " +"яна занадта ўжо добрая і клапатлівая, каб так зрабіць." + +#. TRANSLATORS: Neutral dialog spoken by Pipin +#: Source/textdat.cpp:345 +msgid "What ails you, my friend?" +msgstr "Што вам далягае, дружа мой?" + +#. TRANSLATORS: Neutral dialog spoken by Pipin (Gossip) +#: Source/textdat.cpp:346 +msgid "" +"I have made a very interesting discovery. Unlike us, the creatures in the " +"Labyrinth can heal themselves without the aid of potions or magic. If you " +"hurt one of the monsters, make sure it is dead or it very well may " +"regenerate itself." +msgstr "" +"Я вынайшаў адну надта цікавую рэч. У адрозненні ад нас, істоты ў Лабірынце " +"могуць гаіць свае раны без чужога ўмяшання ці магіі. Калі раніш пачвару, " +"упэўніся, што яна мёртая, іначай яна лёгка адновіцца." + +#. TRANSLATORS: Neutral dialog spoken by Pipin (Gossip) +#: Source/textdat.cpp:348 +msgid "" +"Before it was taken over by, well, whatever lurks below, the Cathedral was a " +"place of great learning. There are many books to be found there. If you find " +"any, you should read them all, for some may hold secrets to the workings of " +"the Labyrinth." +msgstr "" +"Да таго як Храм захапілі гэтыя, ну, хто там цяпер тоіцца, гэта было выдатнае " +"месца для адукацыі. Там можна знайсці багата кніг. Усё што знойдзеш, чытай, " +"бо з некаторых з іх можаш дазнацца аб тым, як устроены Лабірынт." + +#. TRANSLATORS: Neutral dialog spoken by Pipin (Gossip) +#: Source/textdat.cpp:350 +msgid "" +"Griswold knows as much about the art of war as I do about the art of " +"healing. He is a shrewd merchant, but his work is second to none. Oh, I " +"suppose that may be because he is the only blacksmith left here." +msgstr "" +"Грызвальд знаецца на ваенным дзеле так жа, як я на дзеле лячэбным. " +"Праніклівы ён гандляр, але ж работа яго нікому не ўступае. Ой, мабыць гэта " +"таму, што ён адзіны каваль, які тут застаўся." + +#. TRANSLATORS: Neutral dialog spoken by Pipin (Gossip) +#: Source/textdat.cpp:352 +msgid "" +"Cain is a true friend and a wise sage. He maintains a vast library and has " +"an innate ability to discern the true nature of many things. If you ever " +"have any questions, he is the person to go to." +msgstr "" +"Кейн – сапраўдны сябар і вялікі мудрэц. Ён трымае вялізную бібліятэку і ад " +"прыроды здольны распазнаць існую натуру многіх рэчаў. Калі будзеш мець якое-" +"небудзь пытанне, да яго дакладна звярніся." + +#. TRANSLATORS: Neutral dialog spoken by Pipin (Gossip) +#: Source/textdat.cpp:354 +msgid "" +"Even my skills have been unable to fully heal Farnham. Oh, I have been able " +"to mend his body, but his mind and spirit are beyond anything I can do." +msgstr "" +"Нават са сваімі ўменнямі я не змог да канца вылячыць Фарнама. Эх, целам-то " +"ён ачуняў, але розум яго і дух па-за ўсімі маімі здольнасцямі." + +#. TRANSLATORS: Neutral dialog spoken by Pipin (Gossip) +#: Source/textdat.cpp:356 +msgid "" +"While I use some limited forms of magic to create the potions and elixirs I " +"store here, Adria is a true sorceress. She never seems to sleep, and she " +"always has access to many mystic tomes and artifacts. I believe her hut may " +"be much more than the hovel it appears to be, but I can never seem to get " +"inside the place." +msgstr "" +"Хоць я і карыстаюся магіяю, у абмежаваных яе формах, каб у запас ствараць " +"зеллі ды эліксіры, вось Эйдрыя – сапраўдная чараўніца. Яна быццам ніколі не " +"спіць, і заўсёды мае доступ да многіх таямнічых артэфактаў і фаліянтаў. " +"Думаецца мне, хата яе — не проста халупа, якой яна падаецца, але туды ніколі " +"не атрымліваецца зайсці." + +#. TRANSLATORS: Neutral dialog spoken by Pipin (Gossip) +#: Source/textdat.cpp:358 +msgid "" +"Poor Wirt. I did all that was possible for the child, but I know he despises " +"that wooden peg that I was forced to attach to his leg. His wounds were " +"hideous. No one - and especially such a young child - should have to suffer " +"the way he did." +msgstr "" +"Бедны Вірт. Я зрабіў ўсё, што мог, для малыша, але я ведаю, ён ненавідзіць " +"тую кавялу, якую прыйшлося прымацаваць к яго назе. Раны былі жудасныя. Ды " +"ніхто – асабліва такое малое дзіця – не мусіць пакутаваць так, як давялося " +"яму." + +#. TRANSLATORS: Neutral dialog spoken by Pipin (Gossip) +#: Source/textdat.cpp:360 +msgid "" +"I really don't understand why Ogden stays here in Tristram. He suffers from " +"a slight nervous condition, but he is an intelligent and industrious man who " +"would do very well wherever he went. I suppose it may be the fear of the " +"many murders that happen in the surrounding countryside, or perhaps the " +"wishes of his wife that keep him and his family where they are." +msgstr "" +"Я праўда не разумею, чаму Огдэн дасюль застаецца ў Трыстраме. У яго трохі " +"разышліся нервы, але ён дасціпны і працавіты чалавек, дзе б ні быў, ён бы " +"прыстасаваўся. Мяркую, гэта з-за страху забойстваў, што чыняцца ў ваколіцы, " +"або гэта жаданне жонкі ягонай, што ён з сям'ёй нікуды не з'язджае." + +#. TRANSLATORS: Neutral dialog spoken by Pipin (Gossip) +#: Source/textdat.cpp:362 +msgid "" +"Ogden's barmaid is a sweet girl. Her grandmother is quite ill, and suffers " +"from delusions. \n" +" \n" +"She claims that they are visions, but I have no proof of that one way or the " +"other." +msgstr "" +"Огдэнава барменка мілая дзяўчына. Бабуля яе хварэе, мучыцца ад нейкага " +"мораку.\n" +"\n" +"Кажа, яна бачыць відзежы, але доказаў таго у мяне няма так ці інакш." + +#. TRANSLATORS: Neutral dialog spoken by Gillian +#: Source/textdat.cpp:364 +msgid "Good day! How may I serve you?" +msgstr "Добры дзень! Чым магу служыць?" + +#. TRANSLATORS: Neutral dialog spoken by Gillian (Gossip) +#: Source/textdat.cpp:365 +msgid "" +"My grandmother had a dream that you would come and talk to me. She has " +"visions, you know and can see into the future." +msgstr "" +"Мая бабуля мне сказала, што ты прыйдзеш пагаварыць са мною. А яна бачыць " +"відзежы, можа глядзець у будучыню." + +#. TRANSLATORS: Neutral dialog spoken by Gillian (Gossip) +#: Source/textdat.cpp:367 +msgid "" +"The woman at the edge of town is a witch! She seems nice enough, and her " +"name, Adria, is very pleasing to the ear, but I am very afraid of her. \n" +" \n" +"It would take someone quite brave, like you, to see what she is doing out " +"there." +msgstr "" +"Жанчына з ускраіны горада — ведзьма! Ну, яна досыць добрая, і імя ў яе, " +"Эйдрыя, прыемнае вуху, але я вельмі яе баюся.\n" +"\n" +"Толькі нехта досыць смелы, як ты, мог бы глянуць, чым яна там займаецца." + +#. TRANSLATORS: Neutral dialog spoken by Gillian (Gossip) +#: Source/textdat.cpp:369 +msgid "" +"Our Blacksmith is a point of pride to the people of Tristram. Not only is he " +"a master craftsman who has won many contests within his guild, but he " +"received praises from our King Leoric himself - may his soul rest in peace. " +"Griswold is also a great hero; just ask Cain." +msgstr "" +"Наш каваль — прычына гордасці трыстрамцаў. Не толькі ў яго рукі залатыя, і " +"ён многа спаборніцтваў у сваёй гільдыі выйграў, але яго сам кароль Леорык " +"хваліў, няхай ён спіць спакойна. Грызвальд і вялікі герой, Кейна спытай." + +#. TRANSLATORS: Neutral dialog spoken by Gillian (Gossip) +#: Source/textdat.cpp:371 +msgid "" +"Cain has been the storyteller of Tristram for as long as I can remember. He " +"knows so much, and can tell you just about anything about almost everything." +msgstr "" +"Кейн быў расказчыкам Трыстрама колькі сябе помню. Ён столькі ўсяго ведае, " +"можа расказаць пра што хочаш бадай усё што можна хацець." + +#. TRANSLATORS: Neutral dialog spoken by Gillian (Gossip) +#: Source/textdat.cpp:373 +msgid "" +"Farnham is a drunkard who fills his belly with ale and everyone else's ears " +"with nonsense. \n" +" \n" +"I know that both Pepin and Ogden feel sympathy for him, but I get so " +"frustrated watching him slip farther and farther into a befuddled stupor " +"every night." +msgstr "" +"Фарнам – п’яніца. Сабе ў глотку эль ліе, а іншым у вушы трызніць.\n" +"\n" +"Я ведаю, што Піпін і Огдэн спачуваюць яму, але мяне так засмучае бачыць, як " +"ён штоноч скочваецца ў п’яны ступар ўсё глыбей і глыбей." + +#. TRANSLATORS: Neutral dialog spoken by Gillian (Gossip) +#: Source/textdat.cpp:375 +msgid "" +"Pepin saved my grandmother's life, and I know that I can never repay him for " +"that. His ability to heal any sickness is more powerful than the mightiest " +"sword and more mysterious than any spell you can name. If you ever are in " +"need of healing, Pepin can help you." +msgstr "" +"Піпін выратаваў жыццё маёй бабулі, і я ведаю, што ніколі не адгаджу яму за " +"гэта. Яго здольнасць вылячыць любую хваробу сільнейшая за наймагутнейшы меч, " +"і больш таямнічая за любыя чары, якія ведаеш. Калі спатрэбіцца лячэнне, " +"Піпін можа дапамагчы." + +#. TRANSLATORS: Neutral dialog spoken by Gillian (Gossip) +#: Source/textdat.cpp:377 +msgid "" +"I grew up with Wirt's mother, Canace. Although she was only slightly hurt " +"when those hideous creatures stole him, she never recovered. I think she " +"died of a broken heart. Wirt has become a mean-spirited youngster, looking " +"only to profit from the sweat of others. I know that he suffered and has " +"seen horrors that I cannot even imagine, but some of that darkness hangs " +"over him still." +msgstr "" +"Я вырасла з маці Вірта, Кэнэс. Хаця яе нясільна ранілі, калі тыя вычварні " +"скралі яго, яна так і не ачулася. Думаю, яна памярла, бо ёй скрышыла сэрца. " +"Вірт стаў зламысным басяком, усё шукае як нажыцца з чужой працы. Я ведаю, як " +"ён нацярпеўся, а якіх жахаў нагледзеўся, не магу ўявіць, але тая цемра ўсё-" +"такі яшчэ вісіць над ім." + +#. TRANSLATORS: Neutral dialog spoken by Gillian (Gossip) +#: Source/textdat.cpp:379 +msgid "" +"Ogden and his wife have taken me and my grandmother into their home and have " +"even let me earn a few gold pieces by working at the inn. I owe so much to " +"them, and hope one day to leave this place and help them start a grand hotel " +"in the east." +msgstr "" +"Огдэн і жонка яго ўзялі мяне і маю бабулю к сабе, нават даюць мне зарабляць " +"сабе на хлеб у сваёй карчме. Я ім столькім абавязана, спадзяваюся аднойчы " +"з’ехаць адсюль з імі і дапамагчы ім адкрыць на ўсходзе вялікую гасцініцу." + +#. TRANSLATORS: Neutral dialog spoken by Griswold +#: Source/textdat.cpp:381 +msgid "Well, what can I do for ya?" +msgstr "Ну, чым магу спамагчы?" + +#. TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) +#: Source/textdat.cpp:382 +msgid "" +"If you're looking for a good weapon, let me show this to you. Take your " +"basic blunt weapon, such as a mace. Works like a charm against most of those " +"undying horrors down there, and there's nothing better to shatter skinny " +"little skeletons!" +msgstr "" +"Шукаеш сабе якой добрай зброі, дай вось што пакажу. Узяць хоць звычайную " +"тупую зброю, напрыклад паліцу. Супраць тых неўміручых страхаў проста цуды " +"робіць, дый няма лепшай штукі, каб такой шкілеціка раскрышыць!" + +#. TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) +#: Source/textdat.cpp:384 +msgid "" +"The axe? Aye, that's a good weapon, balanced against any foe. Look how it " +"cleaves the air, and then imagine a nice fat demon head in its path. Keep in " +"mind, however, that it is slow to swing - but talk about dealing a heavy " +"blow!" +msgstr "" +"Сякера? Але, добра зброя, на любого ворага збалансавана. Палянь як яна " +"паветро расцінае, просто ўяві харошанькаго такого гаматного дэмана на яе " +"шляху. Адно ж не забывай, што ёю размахвацца цяжко — але які пасля будзе " +"моцны ўдар!" + +#. TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) +#: Source/textdat.cpp:386 +msgid "" +"Look at that edge, that balance. A sword in the right hands, and against the " +"right foe, is the master of all weapons. Its keen blade finds little to hack " +"or pierce on the undead, but against a living, breathing enemy, a sword will " +"better slice their flesh!" +msgstr "" +"Толькі лянь на вастрыё, якая раўнавага. У правільных руках, меч, дый супраць " +"правільнаго ворага, гаспадар над усёй зброяю. Востраму лязу яго амаль няма " +"чаго сячы ці пратыкаць на мярцах, але жывому, што шчэ дыхае, меч добра плоць " +"пасячэ!" + +#. TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) +#: Source/textdat.cpp:388 +msgid "" +"Your weapons and armor will show the signs of your struggles against the " +"Darkness. If you bring them to me, with a bit of work and a hot forge, I can " +"restore them to top fighting form." +msgstr "" +"На тваёй зброі ды брані будзе відаць сляды барацьбы з Цемраю. Прынясеш мне, " +"распалім горан, трохі папрацацуймо, і вернем ім лепшы баявы стан." + +#. TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) +#: Source/textdat.cpp:390 +msgid "" +"While I have to practically smuggle in the metals and tools I need from " +"caravans that skirt the edges of our damned town, that witch, Adria, always " +"seems to get whatever she needs. If I knew even the smallest bit about how " +"to harness magic as she did, I could make some truly incredible things." +msgstr "" +"Покі я, лічы, шмуглюю, каб струманты там ды метал прыдбаць з караванаў, што " +"ходзяць часам за нашым клятым горадам, ведзьма тая, Эйдрыя, нібыто ўсё што " +"трэба дастаць можа. Знайся я хоць крыху як яна на чарадзействе, я б вырабляў " +"сапраўды файныя рэчы." + +#. TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) +#: Source/textdat.cpp:392 +msgid "" +"Gillian is a nice lass. Shame that her gammer is in such poor health or I " +"would arrange to get both of them out of here on one of the trading caravans." +msgstr "" +"Джыліен слаўная дзеўчына. Шкада, што бабулі ейнай так далягае, а то б я " +"ўладзіў, каб які гандлёвы караван вывез іх адсюль." + +#. TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) +#: Source/textdat.cpp:394 +msgid "" +"Sometimes I think that Cain talks too much, but I guess that is his calling " +"in life. If I could bend steel as well as he can bend your ear, I could make " +"a suit of court plate good enough for an Emperor!" +msgstr "" +"Іншым разам задумваюся, маўляў Кейн замнога гаворыць, але відаць такое яго " +"прызванне. Ездзі я па жалезе так, як ён табе па вушах, я б зрабіў ужо такі " +"раскошны гарнітур, Імператару б пасаваў!" + +#. TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) +#: Source/textdat.cpp:396 +msgid "" +"I was with Farnham that night that Lazarus led us into Labyrinth. I never " +"saw the Archbishop again, and I may not have survived if Farnham was not at " +"my side. I fear that the attack left his soul as crippled as, well, another " +"did my leg. I cannot fight this battle for him now, but I would if I could." +msgstr "" +"Я быў з Фарнамам тае ночы, калі Лазар завёў нас у Лабірынт. Больш я " +"Архібіскупа не бачыў, і я б сам не вырабіўся, каб не Фарнам. Баюся, тая " +"атака скалечыла яму душу, ды так, як, ну, як мне маю нагу. Зараз у ягонай " +"бітве я не магу біцца за яго, але каб прымеў, біўся б." + +#. TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) +#: Source/textdat.cpp:398 +msgid "" +"A good man who puts the needs of others above his own. You won't find anyone " +"left in Tristram - or anywhere else for that matter - who has a bad thing to " +"say about the healer." +msgstr "" +"Добры той чалавек што ставіць патрэбы іншых вышэй за свае. Не знойдзеш " +"нікого ў Трыстраме — і дзе яшчэ, раз на тое пайшло, — хто б казаў пра лекара " +"нешто дрэннае." + +#. TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) +#: Source/textdat.cpp:400 +msgid "" +"That lad is going to get himself into serious trouble... or I guess I should " +"say, again. I've tried to interest him in working here and learning an " +"honest trade, but he prefers the high profits of dealing in goods of dubious " +"origin. I cannot hold that against him after what happened to him, but I do " +"wish he would at least be careful." +msgstr "" +"І трапіць жа той малец раз у бяду... ну, мушу сказаць, зноў трапіць. Я хацеў " +"быў яго зацікавіць работай, каб вучыўся тут чэснай працы, але яму больш " +"даспадобы вялікая выгада з гандлю таварам невядома адкуль узятым. Не магу " +"яго за тое вініць, пасля ўсяго што з ім было, але я б вельмі хацеў, каб ён " +"хоць асцярожны быў." + +#. TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) +#: Source/textdat.cpp:402 +msgid "" +"The Innkeeper has little business and no real way of turning a profit. He " +"manages to make ends meet by providing food and lodging for those who " +"occasionally drift through the village, but they are as likely to sneak off " +"into the night as they are to pay him. If it weren't for the stores of " +"grains and dried meats he kept in his cellar, why, most of us would have " +"starved during that first year when the entire countryside was overrun by " +"demons." +msgstr "" +"Справы ў карчмара ідуць не надто, ён амаль не мае выгады са свайго дзела. " +"Канцы з канцамі зводзіць, забяспечваючы ежай і начлегам тых, хто падчас едзе " +"праз вёску, але яны хутчэй змыюцца ўночы, ніж яму заплацяць. Каб ён не " +"назапасіў у сваіх скляпах збожжа і сушанага мяса, авохці, з голаду б памерла " +"большасць з нас у першы ж год, калі ўсю ваколіцу запаланілі дэманы." + +#. TRANSLATORS: Neutral dialog spoken by Farnham +#: Source/textdat.cpp:404 +msgid "Can't a fella drink in peace?" +msgstr "Ужо і выпіць нельга спакойна?" + +#. TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) +#: Source/textdat.cpp:405 +msgid "" +"The gal who brings the drinks? Oh, yeah, what a pretty lady. So nice, too." +msgstr "Дзеўка, што выпіўку падае? Ой, ну так, такая прыгажуня. Яшчэ і добрая." + +#. TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) +#: Source/textdat.cpp:407 +msgid "" +"Why don't that old crone do somethin' for a change. Sure, sure, she's got " +"stuff, but you listen to me... she's unnatural. I ain't never seen her eat " +"or drink - and you can't trust somebody who doesn't drink at least a little." +msgstr "" +"Чаго тая карга хоць для разнастайнасці нешта не зробіць. Вядома, вядома ж, " +"яна там нечым займаецца, але ты мяне паслухай... яна не нармальная. Я ніколі " +"не бачыў, каб яна ела ці піла, а нельга давяраць таму, хто хоць крыху не п'е." + +#. TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) +#: Source/textdat.cpp:409 +msgid "" +"Cain isn't what he says he is. Sure, sure, he talks a good story... some of " +"'em are real scary or funny... but I think he knows more than he knows he " +"knows." +msgstr "" +"Кейн не той, за каго се выдае. Вядома, вядома ж, зубы загаворвае ён умела... " +"а часам вельмі і страшна і смешна... але думаецца мне, ён ведае больш, чым " +"сам ведае, што ведае." + +#. TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) +#: Source/textdat.cpp:411 +msgid "" +"Griswold? Good old Griswold. I love him like a brother! We fought together, " +"you know, back when... we... Lazarus... Lazarus... Lazarus!!!" +msgstr "" +"Грызвальд-то? Стары добры Грызвальд. Ды я яго як брата люблю! Мы тады разам " +"біліся, ну знаеш, калі гэта... мы... Лазар... Лазар... Лазар!!!" + +#. TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) +#: Source/textdat.cpp:413 +msgid "" +"Hehehe, I like Pepin. He really tries, you know. Listen here, you should " +"make sure you get to know him. Good fella like that with people always " +"wantin' help. Hey, I guess that would be kinda like you, huh hero? I was a " +"hero too..." +msgstr "" +"Хехехе, мне да спадобы Піпін. Ён сапраўды стараецца, знаеш так. Слухай, ты з " +"ім дакладна пазнаёмся. Добры таварыш сярод люду, якім вечна трэба помач. Ну, " +"амаль нехта як ты, а, герой? І я быў героем…" + +#. TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) +#: Source/textdat.cpp:415 +msgid "" +"Wirt is a kid with more problems than even me, and I know all about " +"problems. Listen here - that kid is gotta sweet deal, but he's been there, " +"you know? Lost a leg! Gotta walk around on a piece of wood. So sad, so sad..." +msgstr "" +"У Вірта праблем нат больш ніж у мяне, а я-то пра праблемы знаю. Паслухай – " +"дзела-то ў хлапчука спорыцца, аж ён быў там, знаеш? Нагу страціў! Клыпаць " +"цяпер на кавалку дзерава. Якое гора, якое гора…" + +#. TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) +#: Source/textdat.cpp:417 +msgid "" +"Ogden is the best man in town. I don't think his wife likes me much, but as " +"long as she keeps tappin' kegs, I'll like her just fine. Seems like I been " +"spendin' more time with Ogden than most, but he's so good to me..." +msgstr "" +"Огдэн – найлепшы мужык у горадзе. Не думаю, што жонка ягоная мяне надта " +"падабае, але пакуль яна з бочак разлівае, буду і я яе падабаць. З Огдэнам я " +"баўлю нібыта больш часу, ніж яшчэ з кім, але ён такі добры…" + +#. TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) +#: Source/textdat.cpp:419 +msgid "" +"I wanna tell ya sumthin', 'cause I know all about this stuff. It's my " +"specialty. This here is the best... theeeee best! That other ale ain't no " +"good since those stupid dogs..." +msgstr "" +"Нешта я табе скажу, бо ж гэта, я пра гэта ўсё ведаю. То мой канёк. От, ля, " +"гэты проста найлепшы… нааааайлепшы! Той другі эль зусім не харошы, усё з-за " +"тых тупых сабак…" + +#. TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) +#: Source/textdat.cpp:421 +msgid "" +"No one ever lis... listens to me. Somewhere - I ain't too sure - but " +"somewhere under the church is a whole pile o' gold. Gleamin' and shinin' and " +"just waitin' for someone to get it." +msgstr "" +"Ніхто мяне нік… ніколі ні слухае. Недзе — не пэўны дзе — недзе пад цэркваю ё " +"цэлая куча золата. Блішчыць, зіхаціць ды адно чакае, каб яго нехта прыбраў." + +#. TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) +#: Source/textdat.cpp:423 +msgid "" +"I know you gots your own ideas, and I know you're not gonna believe this, " +"but that weapon you got there - it just ain't no good against those big " +"brutes! Oh, I don't care what Griswold says, they can't make anything like " +"they used to in the old days..." +msgstr "" +"Знаю, ў цябе там сваё наўме, дый знаю, што не паверыш, але зброя твая — ну " +"зусім не дзела проці той нечысці! Ай, ды пляваць мне што там кажа Грызвальд, " +"цяперака не робяць як у даўнейшыя часы..." + +#. TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) +#: Source/textdat.cpp:425 +msgid "" +"If I was you... and I ain't... but if I was, I'd sell all that stuff you got " +"and get out of here. That boy out there... He's always got somethin good, " +"but you gotta give him some gold or he won't even show you what he's got." +msgstr "" +"Будзь я табой... хаця я не ты... але будзь я, я б прадаў усё, што маеш, і " +"даў бы драпака адсюлека. Унь хлопец гэны... У яго заўсёды нешта файнае, але " +"яму трэ золата адсыпаць, а то ён нат не пакажа, што там у яго." + +#. TRANSLATORS: Neutral dialog spoken by Adria +#: Source/textdat.cpp:427 +msgid "I sense a soul in search of answers..." +msgstr "Душу ў пошуках адказаў адчуваю..." + +#. TRANSLATORS: Neutral dialog spoken by Adria (Gossip) +#: Source/textdat.cpp:428 +msgid "" +"Wisdom is earned, not given. If you discover a tome of knowledge, devour its " +"words. Should you already have knowledge of the arcane mysteries scribed " +"within a book, remember - that level of mastery can always increase." +msgstr "" +"Мудрасць здабываецца, а не даецца. Адшукаўшы фаліянт вед, паглыні яго словы. " +"Калі высветліцца, што ты ўжо валодаеш запаветнымі тайнамі, вылажанымі ў " +"кнізе, помні – гэты ўзровень майстэрства заўжды можна перасягнуць." + +#. TRANSLATORS: Neutral dialog spoken by Adria (Gossip) +#: Source/textdat.cpp:430 +msgid "" +"The greatest power is often the shortest lived. You may find ancient words " +"of power written upon scrolls of parchment. The strength of these scrolls " +"lies in the ability of either apprentice or adept to cast them with equal " +"ability. Their weakness is that they must first be read aloud and can never " +"be kept at the ready in your mind. Know also that these scrolls can be read " +"but once, so use them with care." +msgstr "" +"Найвялікшая сіла часцей і жыве карацей ад усіх. Ты можаш знайсці словы " +"старасвецкай сілы на скрутках пергаменту. Моц такіх скруткаў заключана ва " +"уласцівасці іх карыстання: аднолькавы поспех чакае і простага вучня, і " +"адэпта. Слабасць жа іх у тым, што спачатку павінна прачытаць іх услых, і іх " +"нельга захаваць сабе ў памяці. Ведай і тое, што выкарыстаць такі скрутак " +"можна толькі аднойчы, таму выкарыстоўвай іх абачліва." + +#. TRANSLATORS: Neutral dialog spoken by Adria (Gossip) +#: Source/textdat.cpp:432 +msgid "" +"Though the heat of the sun is beyond measure, the mere flame of a candle is " +"of greater danger. No energies, no matter how great, can be used without the " +"proper focus. For many spells, ensorcelled Staves may be charged with " +"magical energies many times over. I have the ability to restore their power " +"- but know that nothing is done without a price." +msgstr "" +"Хаця гарачыню сонца нельга змераць, полымя ўсяго аднае свечкі " +"небяспечнейшае. Няма энергіі, якою можна было б карыстацца без належнага " +"фокусу. Для многіх чар гэта працуе так, што праз скрутак магчыма зарадзіць " +"посах іх магічнаю сілаю, і многа раз. Я магу аднавіць іх сілу, але ведай, " +"што нічога не робіцца дарма." + +#. TRANSLATORS: Neutral dialog spoken by Adria (Gossip) +#: Source/textdat.cpp:434 +msgid "" +"The sum of our knowledge is in the sum of its people. Should you find a book " +"or scroll that you cannot decipher, do not hesitate to bring it to me. If I " +"can make sense of it I will share what I find." +msgstr "" +"Сума нашых вед у суме людзей, што імі валодаюць. Калі знойдзеш кнігу ці " +"скрутак, які не можаш расшыфраваць, не вагайся звярнуцца да мяне. Калі я " +"ўбачу ў тым сэнс, я падзялюся, чым змагу." + +#. TRANSLATORS: Neutral dialog spoken by Adria (Gossip) +#: Source/textdat.cpp:436 +msgid "" +"To a man who only knows Iron, there is no greater magic than Steel. The " +"blacksmith Griswold is more of a sorcerer than he knows. His ability to meld " +"fire and metal is unequaled in this land." +msgstr "" +"Для чалавека, каторы ведае толькі жалеза, няма большай магіі за Сталь. " +"Каваль Грызвальд большы чараўнік, чым ён здагадваецца. У гэтым краі ў " +"здольнасці зліваць агонь і метал яму няма роўных." + +#. TRANSLATORS: Neutral dialog spoken by Adria (Gossip) +#: Source/textdat.cpp:438 +msgid "" +"Corruption has the strength of deceit, but innocence holds the power of " +"purity. The young woman Gillian has a pure heart, placing the needs of her " +"matriarch over her own. She fears me, but it is only because she does not " +"understand me." +msgstr "" +"Псота валодае сілай падману, але нявіннасць змяшчае сілу чысціні. Юная " +"кабета Джыліен мае чыстае сэрца, і ставіць беды сваёй роданачальніцы вышэй " +"за свае. Яна страшыцца мяне, але толькі таму, што не разумее мяне." + +#. TRANSLATORS: Neutral dialog spoken by Adria (Gossip) +#: Source/textdat.cpp:440 +msgid "" +"A chest opened in darkness holds no greater treasure than when it is opened " +"in the light. The storyteller Cain is an enigma, but only to those who do " +"not look. His knowledge of what lies beneath the cathedral is far greater " +"than even he allows himself to realize." +msgstr "" +"Куфар, калі адкрыты ў цемры, не змяшчацьме большы скарб, чым калі адкрыты ў " +"святле. Расказчык Кейн – таямніца, але толькі для тых, хто не ўглядаецца. " +"Яго веды аб тым, што знаходзіцца пад саборам, значна большыя, чым ён нават " +"дазваляе сабе ўявіць." + +#. TRANSLATORS: Neutral dialog spoken by Adria (Gossip) +#: Source/textdat.cpp:442 +msgid "" +"The higher you place your faith in one man, the farther it has to fall. " +"Farnham has lost his soul, but not to any demon. It was lost when he saw his " +"fellow townspeople betrayed by the Archbishop Lazarus. He has knowledge to " +"be gleaned, but you must separate fact from fantasy." +msgstr "" +"Чым вышэй ты ставіш веру ў аднаго чалавека, тым вышэй ёй потым упасці. " +"Фарнам страціў душу, але не на карысць дэманам. Яна страцілася, калі ён " +"бачыў, як яго землякам здрадзіў Архібіскуп Лазар. Ён валодае ведамі, якія " +"варта збіраць па каліве, але мусіш адрозніваць факт ад фантазіі." + +#. TRANSLATORS: Neutral dialog spoken by Adria (Gossip) +#: Source/textdat.cpp:444 +msgid "" +"The hand, the heart and the mind can perform miracles when they are in " +"perfect harmony. The healer Pepin sees into the body in a way that even I " +"cannot. His ability to restore the sick and injured is magnified by his " +"understanding of the creation of elixirs and potions. He is as great an ally " +"as you have in Tristram." +msgstr "" +"Рука, сэрца і розум твораць цуды, калі яны ў суладнасці. Лекар Піпін зазірае " +"ў цела так, як не магу нават я. Яго здольнасць аднаўляць хворых і параненых " +"памножана яго разуменнем стварання эліксіраў і зелляў. Лепшага паплечніка ў " +"Трыстраме табе не знайсці." + +#. TRANSLATORS: Neutral dialog spoken by Adria (Gossip) +#: Source/textdat.cpp:446 +msgid "" +"There is much about the future we cannot see, but when it comes it will be " +"the children who wield it. The boy Wirt has a blackness upon his soul, but " +"he poses no threat to the town or its people. His secretive dealings with " +"the urchins and unspoken guilds of nearby towns gain him access to many " +"devices that cannot be easily found in Tristram. While his methods may be " +"reproachful, Wirt can provide assistance for your battle against the " +"encroaching Darkness." +msgstr "" +"Мы многа не ведаем аб будучыні, але калі яна надыходзіць, то трапляе ў рукі " +"дзяцей. Хлопчык Вірт мае чарнату на душы сваёй, але ён не кідае ценю пагрозы " +"ні на горад, ні на яго людзей. Вынік яго патаемных спраў з абадранцамі і " +"скрытнымі гільдыямі вакольных гарадоў у яго доступе да многіх прылад, якія " +"не так проста знайсці ў Трыстраме. Яго спосабы можна дакараць, але Вірт можа " +"паспрыяць табе ў тваёй бітве з Цемраю, якая не адступае." + +#. TRANSLATORS: Neutral dialog spoken by Adria (Gossip) +#: Source/textdat.cpp:448 +msgid "" +"Earthen walls and thatched canopy do not a home create. The innkeeper Ogden " +"serves more of a purpose in this town than many understand. He provides " +"shelter for Gillian and her matriarch, maintains what life Farnham has left " +"to him, and provides an anchor for all who are left in the town to what " +"Tristram once was. His tavern, and the simple pleasures that can still be " +"found there, provide a glimpse of a life that the people here remember. It " +"is that memory that continues to feed their hopes for your success." +msgstr "" +"То не сцены ды страха дом ствараюць. У гэтым мястэчку карчмар Огдэн служыць " +"большай мэце, чым многія могуць падумаць. Ён дае прытулак Джыліен і яе " +"роданачальніцы, падтрымлівае жыццё Фарнама, якое той скінуў на яго, і " +"служыць усім, хто застаўся ў горадзе, якарам колішняга Трыстрама. Яго карчма " +"і простыя радасці, што яшчэ можна знайсці там, даюць мелькам зірнуць на " +"жыццё, якое людзі тут яшчэ помняць. Гэта ж памяць і жывіць іх надзею на твой " +"поспех." + +#. TRANSLATORS: Neutral dialog spoken by Wirt +#: Source/textdat.cpp:450 +msgid "Pssst... over here..." +msgstr "Пс... ходзь сюды..." + +#. TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) +#: Source/textdat.cpp:451 +msgid "" +"Not everyone in Tristram has a use - or a market - for everything you will " +"find in the labyrinth. Not even me, as hard as that is to believe. \n" +" \n" +"Sometimes, only you will be able to find a purpose for some things." +msgstr "" +"Не ўсім у Трыстраме ёсць карысць – ці інтарэс – да ўсяго, што ты знаходзіш у " +"лабірынце. Нават мне, як ні цяжка гэтаму паверыць.\n" +"\n" +"Іншым разам толькі табе ўдасца знайсці прымяненне некаторым рэчам." + +#. TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) +#: Source/textdat.cpp:453 +msgid "" +"Don't trust everything the drunk says. Too many ales have fogged his vision " +"and his good sense." +msgstr "" +"Не давай веры ўсяму, што кажа апівоша. Столькі многа элю патупіла яму і вочы " +"і цвярозы розум." + +#. TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) +#: Source/textdat.cpp:455 +msgid "" +"In case you haven't noticed, I don't buy anything from Tristram. I am an " +"importer of quality goods. If you want to peddle junk, you'll have to see " +"Griswold, Pepin or that witch, Adria. I'm sure that they will snap up " +"whatever you can bring them..." +msgstr "" +"Калі не добра відаць, я нічога не купляю з Трыстрама. Я імпарцёр якаснага " +"тавару. Калі хочаш смецце каму загнаць, ідзі да Грызвальда там, Піпіна, або " +"да ведзьмы той, Эйдрыі. Упэўнены, з рукамі адарвуць, што ні прынясеш…" + +#. TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) +#: Source/textdat.cpp:457 +msgid "" +"I guess I owe the blacksmith my life - what there is of it. Sure, Griswold " +"offered me an apprenticeship at the smithy, and he is a nice enough guy, but " +"I'll never get enough money to... well, let's just say that I have definite " +"plans that require a large amount of gold." +msgstr "" +"Здаецца я кавалю жыццём абавязаны, вярней тым што ад яго засталося. Вядома, " +"Грызвальд мяне ў вучні зваў на кузню, і ён даволі добры мужык, але там я " +"ніколі не накаплю на… ну, скажам так, ёсць у мяне адна задума, для якой мне " +"трэба вялікая куча золата." + +#. TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) +#: Source/textdat.cpp:459 +msgid "" +"If I were a few years older, I would shower her with whatever riches I could " +"muster, and let me assure you I can get my hands on some very nice stuff. " +"Gillian is a beautiful girl who should get out of Tristram as soon as it is " +"safe. Hmmm... maybe I'll take her with me when I go..." +msgstr "" +"Будзь я трохі старшэй, хадзіла б яна ў золаце, якое толькі б пажадала, і не " +"сумнявайся, я магу дастаць сапраўды харошыя рэчы. Джыліен – добрая дзяўчына, " +"трэба ёй цякаць з Трыстрама, пакуль мажліва. Гм-м-м… мо ўзяць яе з сабою, " +"калі пайду…" + +#. TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) +#: Source/textdat.cpp:461 +msgid "" +"Cain knows too much. He scares the life out of me - even more than that " +"woman across the river. He keeps telling me about how lucky I am to be " +"alive, and how my story is foretold in legend. I think he's off his crock." +msgstr "" +"Кейн ведае занадта многа. Як ён мяне гэтым страшыць, нават мацней за тую, " +"што за ракой жыве. Ён усё кажа, маўляў, як мне павязло што я жывы, і што мая " +"гісторыя прадказана ў легендзе. Мне здаецца ён зусім з глуздоў з'ехаў." + +#. TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) +#: Source/textdat.cpp:463 +msgid "" +"Farnham - now there is a man with serious problems, and I know all about how " +"serious problems can be. He trusted too much in the integrity of one man, " +"and Lazarus led him into the very jaws of death. Oh, I know what it's like " +"down there, so don't even start telling me about your plans to destroy the " +"evil that dwells in that Labyrinth. Just watch your legs..." +msgstr "" +"Фарнам — вось у каго сур'ёзныя праблемы, а я-то знаю, якімі сур'ёзнымі " +"могуць быць праблемы. Даверыўся шчырасці аднаго чалавека засільна, Лазар яго " +"і завёў у самую пашчу смерці. А, дый я знаю, як там, нават не пачынай пра " +"свае планы знішчыць зло, што асялілася ў Лабірынце. Ногі беражы, вось што " +"скажу..." + +#. TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) +#: Source/textdat.cpp:465 +msgid "" +"As long as you don't need anything reattached, old Pepin is as good as they " +"come. \n" +" \n" +"If I'd have had some of those potions he brews, I might still have my leg..." +msgstr "" +"Калі толькі табе не трэба нешта прымацаваць, лепш старога Піпіна тут няма. \n" +"\n" +"Былі б у мяне тады яго зеллі, можа і з нагою бы застаўся..." + +#. TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) +#: Source/textdat.cpp:467 +msgid "" +"Adria truly bothers me. Sure, Cain is creepy in what he can tell you about " +"the past, but that witch can see into your past. She always has some way to " +"get whatever she needs, too. Adria gets her hands on more merchandise than " +"I've seen pass through the gates of the King's Bazaar during High Festival." +msgstr "" +"Вось Эйдрыя мяне праўда непакоіць. Кейн канешне страшны, як ён табе пра тваё " +"мінулае можа расказаць, аж ведзьма тая можа ў тваё мінулае зазірнуць. І ёй " +"вечна ўдаецца дастаць усё, што ёй зажадаецца. Яна столькі тавару прыдабывае, " +"больш чым я бачыў цераз браму на каралеўскім базары ў Вялікі Фэст." + +#. TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) +#: Source/textdat.cpp:469 +msgid "" +"Ogden is a fool for staying here. I could get him out of town for a very " +"reasonable price, but he insists on trying to make a go of it with that " +"stupid tavern. I guess at the least he gives Gillian a place to work, and " +"his wife Garda does make a superb Shepherd's pie..." +msgstr "" +"Дурань Огдэн, што тут застаецца. Я бы яго адсюль вывез ды па вельмі разумнай " +"цане, але ён упёрся, хоча з гэтай дурной карчмы разбагацець. Ну, хоць " +"Джыліен ёсць дзе рабіць, і жонка ягоная, Гарда, проста цудоўную запяканку " +"гатуе..." + +#. TRANSLATORS: Quest text spoken aloud from a book by player +#: Source/textdat.cpp:471 Source/textdat.cpp:479 Source/textdat.cpp:487 +msgid "" +"Beyond the Hall of Heroes lies the Chamber of Bone. Eternal death awaits any " +"who would seek to steal the treasures secured within this room. So speaks " +"the Lord of Terror, and so it is written." +msgstr "" +"За заламі герояў стаіць Пакой Касцей. Вечная смерць чакае ўсякага хто " +"паквапіцца на скарбы ахаваныя ў тым месцы. Так кажа Пан Жаху, так і напісана." + +#. TRANSLATORS: Quest text spoken aloud from a book by player +#: Source/textdat.cpp:473 Source/textdat.cpp:481 Source/textdat.cpp:489 +#: Source/textdat.cpp:527 Source/textdat.cpp:535 +msgid "" +"...and so, locked beyond the Gateway of Blood and past the Hall of Fire, " +"Valor awaits for the Hero of Light to awaken..." +msgstr "" +"І вось, замкнёная за варотамі крыві, цераз залу агнёў, Адвага чакае, калі " +"герой святла абудзіцца…" + +#. TRANSLATORS: Quest text spoken aloud from a book by player +#: Source/textdat.cpp:475 Source/textdat.cpp:483 Source/textdat.cpp:491 +#: Source/textdat.cpp:529 Source/textdat.cpp:537 +msgid "" +"I can see what you see not.\n" +"Vision milky then eyes rot.\n" +"When you turn they will be gone,\n" +"Whispering their hidden song.\n" +"Then you see what cannot be,\n" +"Shadows move where light should be.\n" +"Out of darkness, out of mind,\n" +"Cast down into the Halls of the Blind." +msgstr "" +"Віджу, што табе не відна,\n" +"Зрок тупее, вочы ў гнілі.\n" +"Іх няма, як абярнешся, \n" +"Шэпчуць толькі тайну песню.\n" +"Тут убачыш, што не ўбачыць.\n" +"Дзе святло? Там цені танчуць.\n" +"З глудзу, з цьмы ты паляціш.\n" +"Проста ўніз, у Зал Сляпых." + +#. TRANSLATORS: Quest text spoken aloud from a book by player +#: Source/textdat.cpp:477 Source/textdat.cpp:485 Source/textdat.cpp:493 +msgid "" +"The armories of Hell are home to the Warlord of Blood. In his wake lay the " +"mutilated bodies of thousands. Angels and men alike have been cut down to " +"fulfill his endless sacrifices to the Dark ones who scream for one thing - " +"blood." +msgstr "" +"Збраёўні Пекла — дом Крывавага ваяводы. За ім ляжаць знявечаныя целы тысяч і " +"тысяч. Забіты і анёлы і людзі, усё ў ахвяру Цёмным, што крычаць аб адным — " +"крыві." + +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:505 +msgid "" +"Take heed and bear witness to the truths that lie herein, for they are the " +"last legacy of the Horadrim. There is a war that rages on even now, beyond " +"the fields that we know - between the utopian kingdoms of the High Heavens " +"and the chaotic pits of the Burning Hells. This war is known as the Great " +"Conflict, and it has raged and burned longer than any of the stars in the " +"sky. Neither side ever gains sway for long as the forces of Light and " +"Darkness constantly vie for control over all creation." +msgstr "" +"Зважайце ж і будзьце сведкамі ісціны, выказанае тут, бо то ёсць апошняе са " +"спадчыны Харадрым. Нават цяпер лютуе вайна, па-за межамі, вядомымі нам – меж " +"Бездакорных каралеўстваў Вярхоўных Нябёс і Бязладнай Прорвы Агністых Пекел. " +"Імя тае вайны – Вялікае Змаганне, і бушуе яны ды гарыць ужо даўжэй за ўсякую " +"зорку на небе. Ніводзін бок не бярэ верх надоўга, покуль сілы Святла і Цемры " +"бесперастанку спаборнічаюць за ўладу над усім існым." + +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:507 +msgid "" +"Take heed and bear witness to the truths that lie herein, for they are the " +"last legacy of the Horadrim. When the Eternal Conflict between the High " +"Heavens and the Burning Hells falls upon mortal soil, it is called the Sin " +"War. Angels and Demons walk amongst humanity in disguise, fighting in " +"secret, away from the prying eyes of mortals. Some daring, powerful mortals " +"have even allied themselves with either side, and helped to dictate the " +"course of the Sin War." +msgstr "" +"Зважайце ж і будзьце сведкамі ісціны, выказанае тут, бо то ёсць апошняе са " +"спадчыны Харадрым. Калі адвечнае змаганне меж Вярхоўнымі Нябёсамі і " +"Агністымі Пекламі выпадае на смертных грунт, то і ёсць Вайна Граху. Анёлы і " +"дэманы скрытыя ходзяць сярод людзей, таемна змагаючыся, чым далей ад " +"цікаўных чалавечых вачэй. Некаторыя са смертных, магутныя і смелыя, нават " +"сталі ў хаўрус з адным з двух бакоў, дапамагаючы весці ход вайны." + +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:509 +msgid "" +"Take heed and bear witness to the truths that lie herein, for they are the " +"last legacy of the Horadrim. Nearly three hundred years ago, it came to be " +"known that the Three Prime Evils of the Burning Hells had mysteriously come " +"to our world. The Three Brothers ravaged the lands of the east for decades, " +"while humanity was left trembling in their wake. Our Order - the Horadrim - " +"was founded by a group of secretive magi to hunt down and capture the Three " +"Evils once and for all.\n" +" \n" +"The original Horadrim captured two of the Three within powerful artifacts " +"known as Soulstones and buried them deep beneath the desolate eastern sands. " +"The third Evil escaped capture and fled to the west with many of the " +"Horadrim in pursuit. The Third Evil - known as Diablo, the Lord of Terror - " +"was eventually captured, his essence set in a Soulstone and buried within " +"this Labyrinth.\n" +" \n" +"Be warned that the soulstone must be kept from discovery by those not of the " +"faith. If Diablo were to be released, he would seek a body that is easily " +"controlled as he would be very weak - perhaps that of an old man or a child." +msgstr "" +"Зважайце ж і будзьце сведкамі ісціны, выказанае тут, бо то ёсць апошняе са " +"спадчыны Харадрым. Амаль трыста год таму, стала вядома аб таямнічым прыходзе " +"тройцы Першага Зла з Агністых Пекел у наш свет. Трое братоў спусташалі землі " +"Усходу дзесяцігоддзямі, покуль чалавецтва дрыжала пад імі. Нашае брацтва — " +"Харадрым — заснавалася скрытнаю суполкаю Мудрацоў, дзеля таго, каб высачыць " +"і паланіць Трое Зол раз і назаўжды.\n" +"\n" +"Першыя Харадрым двух з Трох захапілі ў моцныя артэфакты, вядомыя як Камяні " +"Душ, і пахавалі іх глыбока пад усходнімі пустынямі. Трэцяе з Зол пазбегла " +"палону і ўцякло ад вялікай пагоні Харадрым на Захад. Яго, вядомага як " +"Д'ябла, Пан Жаху, урэшце паланілі, і існасць яго змясцілі ў Камень Душы і " +"пахавалі ў Лабірынце.\n" +"\n" +"Сцеражыцеся ж, бо нельга таму стацца, каб Камень Душы знайшлі тыя, хто не ад " +"Веры. Калі яны вызваляць Д'ябла, ён шукацьме цела, лёгкае для ўладання, бо " +"ён будзе вельмі слабы. Магчыма, цела як старога альбо дзіцяці." + +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:511 +msgid "" +"So it came to be that there was a great revolution within the Burning Hells " +"known as The Dark Exile. The Lesser Evils overthrew the Three Prime Evils " +"and banished their spirit forms to the mortal realm. The demons Belial (the " +"Lord of Lies) and Azmodan (the Lord of Sin) fought to claim rulership of " +"Hell during the absence of the Three Brothers. All of Hell polarized between " +"the factions of Belial and Azmodan while the forces of the High Heavens " +"continually battered upon the very Gates of Hell." +msgstr "" +"І сталася так, што ў Агністых Пеклах быў мяцеж, празваны Цёмным Выгнаннем. " +"Меншае Зло зрынула тройцу Першага Зла, і прагнала дух іх у царства смертных. " +"Дэманы ж Бэліял, валадар ілжы, і Азмадан, валадар граху, за адсутнасцю Трох " +"Братоў распачалі барацьбу за стырно ўлады. Усё Пекла падзялілася на два " +"лагеры ў супрацьстаянні Бэліяла і Азмадана, покуль сілы Вярхоўных Нябёс " +"бесперастанна таранілі ў самую браму Пякельную." + +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:513 +msgid "" +"Many demons traveled to the mortal realm in search of the Three Brothers. " +"These demons were followed to the mortal plane by Angels who hunted them " +"throughout the vast cities of the East. The Angels allied themselves with a " +"secretive Order of mortal magi named the Horadrim, who quickly became adept " +"at hunting demons. They also made many dark enemies in the underworlds." +msgstr "" +"Многія дэманы адправіліся ў царства смертных, шукаючы Трох Братоў. А за тымі " +"дэманамі рушылі следам анёлы, і палявалі на іх па неабсяжных гарадах Усходу. " +"Анёлы аб'ядналіся з брацтвам смертных Мудрацоў, што звалася Харадрым, якія ў " +"скорым часе сталі дасведчанымі паляўнічым на дэманаў. А ў падземных светах " +"знайшлі яны сабе многа цёмных ворагаў." + +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:515 +msgid "" +"So it came to be that the Three Prime Evils were banished in spirit form to " +"the mortal realm and after sewing chaos across the East for decades, they " +"were hunted down by the cursed Order of the mortal Horadrim. The Horadrim " +"used artifacts called Soulstones to contain the essence of Mephisto, the " +"Lord of Hatred and his brother Baal, the Lord of Destruction. The youngest " +"brother - Diablo, the Lord of Terror - escaped to the west.\n" +" \n" +"Eventually the Horadrim captured Diablo within a Soulstone as well, and " +"buried him under an ancient, forgotten Cathedral. There, the Lord of Terror " +"sleeps and awaits the time of his rebirth. Know ye that he will seek a body " +"of youth and power to possess - one that is innocent and easily controlled. " +"He will then arise to free his Brothers and once more fan the flames of the " +"Sin War..." +msgstr "" +"І сталася так, што духі Тройцы Першага Зла былі выгнаны ў царства смертных, " +"а пасля таго, як яны сеялі хаос па ўсім Усходзе дзсяцігоддзямі, праклятае " +"брацтва Харадрым высачыла іх. Харадрым выкарысталі артэфакты, называныя " +"Камяні Душ, каб стрымаць у іх існасць Мефіста, Пана Нянавісці, і брата яго " +"Баала, Пана Знішчэння. Малодшы брат, Д'ябла, Пан Жаху, збег на Захад.\n" +"\n" +"Урэшце, Харадрым зняволілі ў Каменю Душы і Д'ябла, і пахавалі яго пад " +"старадаўнім, забытым Саборам. Там Пан Жаху і спіць, чакаючы гадзіну свайго " +"адраджэння. Ведайце ж, што ён шукацьме цела юнае і моцнае, каб ухапіцца ў " +"яго, такое, што бязвінным ёсць і для ўладання лёгкім. Тады паўстане ён, каб " +"збавіць братоў сваіх і зноў раздзьмуць полымя вайны Граху..." + +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:517 +msgid "" +"All praises to Diablo - Lord of Terror and Survivor of The Dark Exile. When " +"he awakened from his long slumber, my Lord and Master spoke to me of secrets " +"that few mortals know. He told me the kingdoms of the High Heavens and the " +"pits of the Burning Hells engage in an eternal war. He revealed the powers " +"that have brought this discord to the realms of man. My lord has named the " +"battle for this world and all who exist here the Sin War." +msgstr "" +"Аднаго Д’ябла ўсхваляем, пана Жаху, ацалелага Цёмнага Выгнання. Калі ён " +"прачнуўся ад свайго доўгага сну, пан мой і гаспадар загаварыў са мною аб " +"тайнах, якія немногім смертным вядомыя. Сказаў ён, што Вярхоўныя Нябёсы і " +"Прорвы Агністых Пекел вядуць вечную вайну. І адкрыў ён мне сілы, што ўнеслі " +"разлад у царствы Чалавекаў. І назваў пан і гаспадар мой бітву за свет гэты і " +"ўсіх у ім Вайною Граху." + +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:519 +msgid "" +"Glory and Approbation to Diablo - Lord of Terror and Leader of the Three. My " +"Lord spoke to me of his two Brothers, Mephisto and Baal, who were banished " +"to this world long ago. My Lord wishes to bide his time and harness his " +"awesome power so that he may free his captive brothers from their tombs " +"beneath the sands of the east. Once my Lord releases his Brothers, the Sin " +"War will once again know the fury of the Three." +msgstr "" +"Уся хвала і слава Д'яблу, пану жаху і галаве над Трыма. Пан мой гаварыў са " +"мною аб дваіх братах сваіх, Мефіста і Баале, выгнаных ў свет гэты даўным-" +"даўно. Пан мой жадае вычакаць зручны момант і сабраць сваю жахлівую сілу, " +"каб вызваліць сваіх паланёных братоў з грабніц пад усходнімі пяскамі. Калі " +"мой пан збавіць братоў сваіх, Вайна Граху ізноў спазнае лютасць Трох." + +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:521 +msgid "" +"Hail and Sacrifice to Diablo - Lord of Terror and Destroyer of Souls. When I " +"awoke my Master from his sleep, he attempted to possess a mortal's form. " +"Diablo attempted to claim the body of King Leoric, but my Master was too " +"weak from his imprisonment. My Lord required a simple and innocent anchor to " +"this world, and so found the boy Albrecht to be perfect for the task. While " +"the good King Leoric was left maddened by Diablo's unsuccessful possession, " +"I kidnapped his son Albrecht and brought him before my Master. I now await " +"Diablo's call and pray that I will be rewarded when he at last emerges as " +"the Lord of this world." +msgstr "" +"Слава і ахвяра Д'яблу, Пану Жаху і Душагубу. Калі абудзіў я Гаспадара свайго " +"ада сну яго, ён намогся ўхапіцца ў смертную абалонку. Д'ябла абраў цела " +"Караля Леорыка, але Гаспадар мой быў заслабым праз сваё зняволенне. Пан мой " +"меў патрэбу ў адным простым і бязвінным якары ў свет гэты, і так палічыў " +"хлопчыка Альбрэхта найлепшым для той справы. Пакуль слаўны Кароль Леорык " +"застаўся звар'яцелым пасля няўдалага апанавання Д'яблам, я скраў сына яго, " +"Альбрэхта, і прывёў яго к Пану свайму. Цяпер чакаю поклічу Д'яблы, і малюся, " +"што буду ўзнагароджаны, калі нарэшце ён паўстане як Пан гэтага свету." + +#. TRANSLATORS: Neutral Text spoken by Ogden +#: Source/textdat.cpp:523 +msgid "" +"Thank goodness you've returned!\n" +"Much has changed since you lived here, my friend. All was peaceful until the " +"dark riders came and destroyed our village. Many were cut down where they " +"stood, and those who took up arms were slain or dragged away to become " +"slaves - or worse. The church at the edge of town has been desecrated and is " +"being used for dark rituals. The screams that echo in the night are inhuman, " +"but some of our townsfolk may yet survive. Follow the path that lies between " +"my tavern and the blacksmith shop to find the church and save who you can. \n" +" \n" +"Perhaps I can tell you more if we speak again. Good luck." +msgstr "" +"Слава небу вы тут!\n" +"Шмат змянілася з часу, калы вы тут жылі, дружа мой. Усё было спакойна, але " +"потым прыехалі цёмныя вершнікі і знішчылі нашу вёску. Многіх забілі на месцы " +"ж, а хто адбіваўся, тых або забілі, або зацягнулі ў рабства, ці нават горш. " +"Царкву на ўскраіне апаганілі, і цяпер там чыняцца цёмныя рытуалы. То не " +"чалавечыя крыкі адтуль адгукаюцца ноччу, але магчыма, што нехта з гараджан і " +"выжыў. Ідзіце дарогай між маёй карчмой і кузняй і знайдзіце царкву, " +"выратуйце, каго зможаце.\n" +"\n" +"Можа другі раз змагу расказаць больш. Удачы." + +#. TRANSLATORS: Quest text spoken aloud from a book by player +#: Source/textdat.cpp:525 Source/textdat.cpp:533 +msgid "" +"Beyond the Hall of Heroes lies the Chamber of Bone. Eternal death awaits " +"any who would seek to steal the treasures secured within this room. So " +"speaks the Lord of Terror, and so it is written." +msgstr "" +"За заламі герояў стаіць Пакой Касцей. Вечная смерць чакае ўсякага хто " +"паквапіцца на скарбы ахаваныя ў тым месцы. Так кажа Пан Жаху, так і напісана." + +#. TRANSLATORS: Quest text spoken aloud from a book by player +#: Source/textdat.cpp:531 Source/textdat.cpp:539 +msgid "" +"The armories of Hell are home to the Warlord of Blood. In his wake lay the " +"mutilated bodies of thousands. Angels and man alike have been cut down to " +"fulfill his endless sacrifices to the Dark ones who scream for one thing - " +"blood." +msgstr "" +"Збраёўні Пекла — дом Крывавага Ваяводы. За ім ляжаць знявечаныя целы тысяч і " +"тысяч. Забіты і анёлы і людзі, усё ў ахвяру Цёмным, што крычаць аб адным — " +"крыві." + +#. TRANSLATORS: Quest text spoken by Adria +#: Source/textdat.cpp:541 +msgid "" +"Maintain your quest. Finding a treasure that is lost is not easy. Finding " +"a treasure that is hidden less so. I will leave you with this. Do not let " +"the sands of time confuse your search." +msgstr "" +"Не спыняй свае пошукі. Знайсці згублены скарб складана. Знайсці схаваны " +"скарб — ужо менш. Гэтага табе пакуль хопіць. Не дай пяскам часу збянтэжыць " +"цябе ў пошуках." + +#. TRANSLATORS: Quest text spoken by Griswold +#: Source/textdat.cpp:543 +msgid "" +"A what?! This is foolishness. There's no treasure buried here in " +"Tristram. Let me see that!! Ah, Look these drawings are inaccurate. They " +"don't match our town at all. I'd keep my mind on what lies below the " +"cathedral and not what lies below our topsoil." +msgstr "" +"Каго?! Глупство гэто. Няма пад Трыстрамам ніякіх скарбаў. Дай гляну!! А, " +"лянь, малюнкі з памылкамі. Зусім на наш горад не падобно. Я б думаў пра тое, " +"што пад саборам, а не што пад верхнім пластом глебы." + +#. TRANSLATORS: Quest text spoken by Pipin +#: Source/textdat.cpp:545 +msgid "" +"I really don't have time to discuss some map you are looking for. I have " +"many sick people that require my help and yours as well." +msgstr "" +"Па праўдзе, не маю часу, каб абмяркоўваць нейкую мапу. У мяне тут шмат " +"людзей, якім мне трэба дапамагчы, як і табе таксама." + +#. TRANSLATORS: Quest text spoken by Adria +#: Source/textdat.cpp:547 Source/textdat.cpp:559 +msgid "" +"The once proud Iswall is trapped deep beneath the surface of this world. " +"His honor stripped and his visage altered. He is trapped in immortal " +"torment. Charged to conceal the very thing that could free him." +msgstr "" +"Некалі горды Ізуал апынуўся ў пастцы пад паверхняй гэтага свету. Яго гонар " +"адняты, а твар зменены. Ён у пастцы вечных пакут. Абавязаны скрываць тую " +"адзіную рэч, што магла б яго вызваліць." + +#. TRANSLATORS: Quest text spoken by Ogden +#: Source/textdat.cpp:549 +msgid "" +"I'll bet that Wirt saw you coming and put on an act just so he could laugh " +"at you later when you were running around the town with your nose in the " +"dirt. I'd ignore it." +msgstr "" +"Стаўлю, што Вірт проста нешта выдумаў, калі цябе ўбачыў, каб потым " +"пасмяяцца, калі пачнеш бегаць па вёсцы ў гразным носам. Я б не звяртаў на " +"яго ўвагі." + +#. TRANSLATORS: Quest text spoken by Cain +#: Source/textdat.cpp:551 +msgid "" +"There was a time when this town was a frequent stop for travelers from far " +"and wide. Much has changed since then. But hidden caves and buried " +"treasure are common fantasies of any child. Wirt seldom indulges in " +"youthful games. So it may just be his imagination." +msgstr "" +"Быў час, калі ў падарожных з усіх старон гэты горад быў частым прыпынкам. " +"Шмат чаго перамянілася. Але схаваныя пячоры ды пахаваныя скарбы, усё гэта " +"звычайныя фантазіі ўсякага дзіцяці. Вірт рэдка дазваляе сабе пазабаўляцца. " +"Магчыма, гэта проста яго ўяўленне." + +#. TRANSLATORS: Quest text spoken by Farnham +#: Source/textdat.cpp:553 +msgid "" +"Listen here. Come close. I don't know if you know what I know, but you've " +"have really got something here. That's a map." +msgstr "" +"Слухай сюды. Падыдзі. Я не ведаю, ці ведаеш ты, што я ведаю, але ў цябе тут " +"нешта праўда важнае. Гэта мапа." + +#. TRANSLATORS: Quest text spoken by Gillian +#: Source/textdat.cpp:555 +msgid "" +"My grandmother often tells me stories about the strange forces that inhabit " +"the graveyard outside of the church. And it may well interest you to hear " +"one of them. She said that if you were to leave the proper offering in the " +"cemetary, enter the cathedral to pray for the dead, and then return, the " +"offering would be altered in some strange way. I don't know if this is just " +"the talk of an old sick woman, but anything seems possible these days." +msgstr "" +"Бабуля мне часта расказвае пра дзіўныя сілы, што насяляюць могілкі каля " +"царквы. Можа, адна такая гісторыя зацікавіць цябе. Яна казала, што калі " +"пакінуць на цвінтары належную ахвяру, зайсці ў сабор і памаліцца за мёртвых, " +"а потым вярнуцца, ахвяра неяк дзіўна зменіцца. Не ведаю, ці гэта проста " +"словы хворай старой, але гэтыя дні быццам усё можа быць." + +#. TRANSLATORS: Quest text spoken by Wirt +#: Source/textdat.cpp:557 +msgid "" +"Hmmm. A vast and mysterious treasure you say. Mmmm. Maybe I could be " +"interested in picking up a few things from you. Or better yet, don't you " +"need some rare and expensive supplies to get you through this ordeal?" +msgstr "" +"Гммм. Вялізны і таямнічы скарб, кажаш? Мммм. Мо я і зацікаўлюся, каб " +"прыкупіць у цябе чагось. А мо шчэ лепей, не трэба табе нейкіх рэдкіх ды " +"дарагіх прыпасаў, каб падолець у такім выпрабаванні?" + +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:561 +msgid "" +"So, you're the hero everyone's been talking about. Perhaps you could help a " +"poor, simple farmer out of a terrible mess? At the edge of my orchard, just " +"south of here, there's a horrible thing swelling out of the ground! I can't " +"get to my crops or my bales of hay, and my poor cows will starve. The witch " +"gave this to me and said that it would blast that thing out of my field. If " +"you could destroy it, I would be forever grateful. I'd do it myself, but " +"someone has to stay here with the cows..." +msgstr "" +"Дык гэта пра цябе героя тут усе балакаюць. Мо дапаможаш простаму беднаму " +"фермеру ў жудаснай бядзе? На краі майго саду, рыхтык на поўдзень адсюль, " +"нешта ўспухла проста з зямлі! Няма як падступіцца да майго жніва ды стагоў, " +"кароўкі мае бедныя будуць галадаць. Тая ведзьма дала мне вось, сказала гэта " +"ўзарве тую гадасць з майго поля. Калі знішчыш тое, век табе буду ўдзячны. Я " +"б і сам гэта зрабіў, але некаму трэба пільнаваць кароў..." + +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:563 +msgid "" +"I knew that it couldn't be as simple as that witch made it sound. It's a sad " +"world when you can't even trust your neighbors." +msgstr "" +"Так і думаў, што ўсё не так проста, як са слоў ведзьмы. Сумны гэта свет, дзе " +"не можаш давяраць нават сваім суседзям." + +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:565 +msgid "" +"Is it gone? Did you send it back to the dark recesses of Hades that spawned " +"it? You what? Oh, don't tell me you lost it! Those things don't come cheap, " +"you know. You've got to find it, and then blast that horror out of our town." +msgstr "" +"Усё? Згінула яно назад у цёмныя ямы Аду што парадзіў яго? Чаго? Ой, не " +"гавары што руна прапала! Яны ж не танныя, ну. Знайдзі яе, і ўзарві той " +"страх, каб духу яго тут не было." + +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:567 +msgid "" +"I heard the explosion from here! Many thanks to you, kind stranger. What " +"with all these things comin' out of the ground, monsters taking over the " +"church, and so forth, these are trying times. I am but a poor farmer, but " +"here -- take this with my great thanks." +msgstr "" +"Я адсюль чуў выбух! Вялікі дзякуй табе, добры незнаёмец. І што за напасць, " +"усякае з зямлі лезе, пачвары царкву захапілі, і гэтак далей, цяжкая гэта " +"часіна. Я проста бедны фермер, але вось, прымі гэта з маёй вялікай падзякай." + +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:569 +msgid "" +"Oh, such a trouble I have...maybe...No, I couldn't impose on you, what with " +"all the other troubles. Maybe after you've cleansed the church of some of " +"those creatures you could come back... and spare a little time to help a " +"poor farmer?" +msgstr "" +"Ох, якая бяда мая... можа... Не, не буду навязвацца, столькі бедаў і так. " +"Можа калі крыху ачысціш царкву ад гэтай нечысці, вернешся... і прысвеціш " +"колькі часу, каб дапамагчы беднаму фермеру?" + +#. TRANSLATORS: Quest text spoken by Little Girl +#: Source/textdat.cpp:571 +msgid "Waaaah! (sniff) Waaaah! (sniff)" +msgstr "Уэээ! (шморг) Уэээ! (шморг)" + +#. TRANSLATORS: Quest text spoken by Little Girl +#: Source/textdat.cpp:572 +msgid "" +"I lost Theo! I lost my best friend! We were playing over by the river, and " +"Theo said he wanted to go look at the big green thing. I said we shouldn't, " +"but we snuck over there, and then suddenly this BUG came out! We ran away " +"but Theo fell down and the bug GRABBED him and took him away!" +msgstr "" +"Я Тэа згубіла! Майго лепшага сябра! Мы гулялі за ракой, а Тэа сказаў што " +"хоча паглядзець на тую зялёную штукенцыю. Я сказала што нам няможна, але мы " +"залезлі туды, і тут вылез той ЖУК! Мы ходу, а Тэа ўпаў і жук той яго ХОП і " +"забраў яго!" + +#. TRANSLATORS: Quest text spoken by Little Girl +#: Source/textdat.cpp:574 +msgid "" +"Didja find him? You gotta find Theodore, please! He's just little. He " +"can't take care of himself! Please!" +msgstr "" +"Вы знайшлі яго? Знайдзіце, калі ласка! Ён такі маленькі. Ён сябе не можа " +"даглядаць! Калі ласка!" + +#. TRANSLATORS: Quest text spoken by Little Girl (Quest End) +#: Source/textdat.cpp:576 +msgid "" +"You found him! You found him! Thank you! Oh Theo, did those nasty bugs " +"scare you? Hey! Ugh! There's something stuck to your fur! Ick! Come on, " +"Theo, let's go home! Thanks again, hero person!" +msgstr "" +"Знайшлі! Знайшлі! Дзякуй вам! Ох, Тэа, цябе напужалі тыя гадкія жукі? Эй! " +"Фэ! У цябе нешта ў поўсці прыліпла! Фу! Хадзем, Тэа, дамоў! Яшчэ раз дзякуй " +"вам, герой!" + +#. TRANSLATORS: Quest text spoken by Defiler (Hostile) +#: Source/textdat.cpp:578 +msgid "" +"We have long lain dormant, and the time to awaken has come. After our long " +"sleep, we are filled with great hunger. Soon, now, we shall feed..." +msgstr "" +"Доўга ляжалі мы ў дрымоце, надышла пара нам прабудзіцца. Па нашым доўгім " +"сне, нас поўніць вялікі голад. Скора, цяпер жа, мы пакормімся..." + +#. TRANSLATORS: Quest text spoken by Defiler (Hostile) +#: Source/textdat.cpp:580 +msgid "" +"Have you been enjoying yourself, little mammal? How pathetic. Your little " +"world will be no challenge at all." +msgstr "" +"Весялішся тут, малое млекакормячае? Якая нікчэмнасць. Ваш маленькі свет не " +"стане ані перашкодаю." + +#. TRANSLATORS: Quest text spoken by Defiler (Hostile) +#: Source/textdat.cpp:582 +msgid "" +"These lands shall be defiled, and our brood shall overrun the fields that " +"men call home. Our tendrils shall envelop this world, and we will feast on " +"the flesh of its denizens. Man shall become our chattel and sustenance." +msgstr "" +"Гэтыя землі будуць апаганены, і наш вывадак ахопіць палі, якія людзі клічуць " +"домам. Нашы атожылкі ахутаюць гэты свет, і мы нажыруемся плоццю яго жыхароў. " +"Чалавек будзе нам раб і харч." + +#. TRANSLATORS: Quest text spoken by Defiler (Hostile) +#: Source/textdat.cpp:584 +msgid "" +"Ah, I can smell you...you are close! Close! Ssss...the scent of blood and " +"fear...how enticing..." +msgstr "" +"Ах, чую твой пах... ты блізка! Блізка! Сссс... пах крыві і страху... як " +"прывабна..." + +#. TRANSLATORS: Quest text spoken by Narrator +#: Source/textdat.cpp:592 +msgid "" +"And in the year of the Golden Light, it was so decreed that a great " +"Cathedral be raised. The cornerstone of this holy place was to be carved " +"from the translucent stone Antyrael, named for the Angel who shared his " +"power with the Horadrim. \n" +" \n" +"In the Year of Drawing Shadows, the ground shook and the Cathedral shattered " +"and fell. As the building of catacombs and castles began and man stood " +"against the ravages of the Sin War, the ruins were scavenged for their " +"stones. And so it was that the cornerstone vanished from the eyes of man. \n" +" \n" +"The stone was of this world -- and of all worlds -- as the Light is both " +"within all things and beyond all things. Light and unity are the products of " +"this holy foundation, a unity of purpose and a unity of possession." +msgstr "" +"І ў год жа Залатога Святла было загадана, каб паўстаў Сабор. Краевогульны " +"камень было павінна выразаць з напаўпразрыстага каменя Антыраэля, названага " +"ў гонар Анёла, што дзяліўся сілаю сваёю з Харадрым.\n" +"\n" +"У Год Набліжэння Ценяў затрэслася зямля, і абрушыўся Сабор той. Калі " +"будаўніцтва катакомб і замкаў пачалося, а чалавек паўстаў супраць " +"разбурэнняў вайны Граху, разваліны тыя абабраны былі з-за камення свайго " +"каштоўнага. І тым чынам згінуў з вачэй чалавечых краевугольны камень.\n" +"\n" +"Камень той быў ад гэтага свету — і ад усіх светаў — як Святло разам і ўнутры " +"кожнай рэчы і па-за ёю. Святло і адзінства ёсць вынік гэтае святое асновы, " +"адзінства цэлі і адзінства валодання." + +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:594 +msgid "Moo." +msgstr "Му-у." + +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:595 +msgid "I said, Moo." +msgstr "Му-у, кажу." + +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:596 +msgid "Look I'm just a cow, OK?" +msgstr "Слухай, я проста карова, добра?" + +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:597 +msgid "" +"All right, all right. I'm not really a cow. I don't normally go around " +"like this; but, I was sitting at home minding my own business and all of a " +"sudden these bugs & vines & bulbs & stuff started coming out of the floor... " +"it was horrible! If only I had something normal to wear, it wouldn't be so " +"bad. Hey! Could you go back to my place and get my suit for me? The brown " +"one, not the gray one, that's for evening wear. I'd do it myself, but I " +"don't want anyone seeing me like this. Here, take this, you might need " +"it... to kill those things that have overgrown everything. You can't miss " +"my house, it's just south of the fork in the river... you know... the one " +"with the overgrown vegetable garden." +msgstr "" +"Добра, добра. Я не зусім карова. Я так звычайна не хаджу, але ж, ну, сядзеў " +"я дома, займаўся там сваім, аж раптам павылазілі жукі, лозы, лямпы і ўсякае " +"такое прама з падлогі… Жах які! Каб я меў што нармальнае надзець, ды не было " +"б усё так кепска. Слухай! А можа сходзіш да маёй хаты і возьмеш мне мой " +"касцюм? Толькі карычневы, не шэры, яго я на вечар надзяю. Я б і сам схадзіў, " +"але не хачу, каб мяне бачылі такога. Вось, на, можа прыгадзіцца… Каб прыбіць " +"усё, чым там зарасло. Маю хату не прапусціш. Проста на поўдзень адтуль, дзе " +"рака разгаліноўваецца… Ну… Дзе там зарослы агарод." + +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:599 +msgid "" +"What are you wasting time for? Go get my suit! And hurry! That Holstein " +"over there keeps winking at me!" +msgstr "" +"Навошта марнуеш час? Ідзі і прынясі мне мой касцюм! І хутчэй! Вунь бычара " +"адзін мне ўжо падміргвае!" + +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:601 +msgid "" +"Hey, have you got my suit there? Quick, pass it over! These ears itch like " +"you wouldn't believe!" +msgstr "" +"Прывет, гэта касцюм мой у цябе? Хутчэй, давай сюды! Гэтыя вушы так свярбяць, " +"не паверыш!" + +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:603 +msgid "" +"No no no no! This is my GRAY suit! It's for evening wear! Formal " +"occasions! I can't wear THIS. What are you, some kind of weirdo? I need " +"the BROWN suit." +msgstr "" +"Не, не, не, не! Гэта мой ШЭРЫ касцюм! Ён на вечар! Для афіцыйных выпадкаў! Я " +"не магу ГЭТА надзець. Ты што, дзівак нейкі? Мне патрэбен Карычневы касцюм." + +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:605 +msgid "" +"Ahh, that's MUCH better. Whew! At last, some dignity! Are my antlers on " +"straight? Good. Look, thanks a lot for helping me out. Here, take this as " +"a gift; and, you know... a little fashion tip... you could use a little... " +"you could use a new... yknowwhatImean? The whole adventurer motif is just " +"so... retro. Just a word of advice, eh? Ciao." +msgstr "" +"Аа, вось гэта значна лепш будзе. Фух! Нарэшце, хоць якая годнасць! У мяне " +"рогі роўна сталі? Добра. Слухай, дзякуй табе вялікі за дапамогу. Вось, " +"вазьмі ў падарак, ведаеш... маленькая парада па модзе... можаш трохі... " +"навейшае.... ну разумееш пра што я? Увесь гэты матыў шукальніка прыгод " +"такі... рэтравы. Проста парада, добра? Чао." + +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:607 +msgid "" +"Look. I'm a cow. And you, you're monster bait. Get some experience under " +"your belt! We'll talk..." +msgstr "" +"Слухай. Я карова. А ты, ты прынада на пачвар. Назапасься досведу! Тады " +"пагаворым." + +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:610 +msgid "" +"It must truly be a fearsome task I've set before you. If there was just some " +"way that I could... would a flagon of some nice, fresh milk help?" +msgstr "" +"Мусіць, і праўда страшную задачу я табе паставіў. Каб быў хоць нейкі " +"спосаб... а табе не дапамог бы збанок свежага малачка?" + +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:612 +msgid "" +"Oh, I could use your help, but perhaps after you've saved the catacombs from " +"the desecration of those beasts." +msgstr "" +"О, мне б прыдалася твая дапамога, але можа пасля таго, як выратуеш катакомбы " +"ад тых пачвар-паганнікаў." + +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:614 +msgid "" +"I need something done, but I couldn't impose on a perfect stranger. Perhaps " +"after you've been here a while I might feel more comfortable asking a favor." +msgstr "" +"Мне тут трэба помач, але не магу ж я навязвацца зусім незнаёмаму чалавеку. " +"Можа, як вы пабудзеце тут крыху, вось тады мне будзе лягчэй папрасіць аб " +"паслузе." + +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:616 +msgid "" +"I see in you the potential for greatness. Perhaps sometime while you are " +"fulfilling your destiny, you could stop by and do a little favor for me?" +msgstr "" +"Я бачу твой патэнцыял вялікасці. Можа якім разам прыйдзеш, пакуль выконваеш " +"свой лёс, і зробіш мне адну паслугу?" + +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:618 +msgid "" +"I think you could probably help me, but perhaps after you've gotten a little " +"more powerful. I wouldn't want to injure the village's only chance to " +"destroy the menace in the church!" +msgstr "" +"Думаю, мажліва вы маглі б мне дапамагчы, але можа калі станеце зусім крыху " +"мацнейшымі. Я б не хацеў шкодзіць адзінай надзеі вёсцы, што можа знішчыць " +"пагрозу ў царкве!" + +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:620 +msgid "" +"Me, I'm a self-made cow. Make something of yourself, and... then we'll talk." +msgstr "" +"Я, я сваімі сіламі стаў каровай. Зрабі з сябе што-небудзь... тады і " +"пагаворым." + +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:622 +msgid "" +"I don't have to explain myself to every tourist that walks by! Don't you " +"have some monsters to kill? Maybe we'll talk later. If you live..." +msgstr "" +"І зусім не трэба мне вытлумачвацца перад кожным прахожым турыстам! Ці табе " +"не трэба было там пачвар біць? Можа потым пагаворым. Калі дажывеш..." + +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:624 +msgid "" +"Quit bugging me. I'm looking for someone really heroic. And you're not " +"it. I can't trust you, you're going to get eaten by monsters any day now... " +"I need someone who's an experienced hero." +msgstr "" +"Годзе мяне турбаваць. Я шукаю нешта сапраўды геройскае. І гэта не ты. Як мне " +"табе давяраць, цябе могуць зжэрці монстры хоць сёння... Мне патрэбен " +"дасведчаны герой." + +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:626 +msgid "" +"All right, I'll cut the bull. I didn't mean to steer you wrong. I was " +"sitting at home, feeling moo-dy, when things got really un-stable; a whole " +"stampede of monsters came out of the floor! I just cowed. I just happened " +"to be wearing this Jersey when I ran out the door, and now I look udderly " +"ridiculous. If only I had something normal to wear, it wouldn't be so bad. " +"Hey! Can you go back to my place and get my suit for me? The brown one, " +"not the gray one, that's for evening wear. I'd do it myself, but I don't " +"want anyone seeing me like this. Here, take this, you might need it... to " +"kill those things that have overgrown everything. You can't miss my house, " +"it's just south of the fork in the river... you know... the one with the " +"overgrown vegetable garden." +msgstr "" +"Добра, годзе заліваць. Не хацеў збіць цябе з тропу. Сяджу я дома, на душы " +"смууутна, аж раптам усё стала няў-стоойліва, з падлогі вылез цэлы статак " +"пачвар! Я аж замыкаў са страху. І так ужо сталася, што я быў апрануты ў гэты " +"Джэрсі, калі ўцякаў за дзверы, і цяпер выглядаю зусім недарэчна. Была б у " +"мяне якая іншая вопратка, не было б так дрэнна. Слухай! А можа сходзіш да " +"маёй хаты і возьмеш мне мой касцюм? Толькі карычневы, не шэры, яго я на " +"вечар надзяю. Я б і сам схадзіў, але не хачу, каб мяне бачылі такога. Вось, " +"на, можа прыгадзіцца… Каб прыбіць усё, чым там зарасло. Маю хату не " +"прапусціш. Проста на поўдзень адтуль, дзе рака разгаліноўваецца… Ну… Дзе там " +"зарослы агарод." + +#. TRANSLATORS: Quest text spoken by Unknown, Maybe Farmer +#: Source/textdat.cpp:628 +msgid "" +"Cloudy and cooler today. Casting the nets of necromancy across the void " +"landed two new subspecies of flying horror; a good day's work. Must " +"remember to order some more bat guano and black candles from Adria; I'm " +"running a bit low." +msgstr "" +"Сёння халадней, і хмар больш. Закінутыя ў пустату сеткі некрамансіі злавілі " +"два новыя падвіды лятучага жаху, слаўна сёння паработаў. Трэба не забыць " +"заказаць яшчэ кажанова калу і чорных свечак у Эйдрыі. Патраціліся мае запасы." + +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:630 +msgid "" +"I have tried spells, threats, abjuration and bargaining with this foul " +"creature -- to no avail. My methods of enslaving lesser demons seem to have " +"no effect on this fearsome beast." +msgstr "" +"Чары, пагрозы, адрачэнне, торг, усё спрабаваў – марна. Мае метады " +"паняволення меншых дэманаў не дзейнічаюць на гэтую жудасную пачвару, зусім." + +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:632 +msgid "" +"My home is slowly becoming corrupted by the vileness of this unwanted " +"prisoner. The crypts are full of shadows that move just beyond the corners " +"of my vision. The faint scrabble of claws dances at the edges of my " +"hearing. They are searching, I think, for this journal." +msgstr "" +"Мой дом павольна глуміцца поганню яго непажаданага вязня. Крыпты поўняцца " +"ценямі, што рушаць па-за вугламі майго зроку. На краі майго слыху ціха " +"скрабуцца ў танцы кіпцюры. Яны шукаюць, думаецца мне, гэты дзённік." + +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:634 +msgid "" +"In its ranting, the creature has let slip its name -- Na-Krul. I have " +"attempted to research the name, but the smaller demons have somehow " +"destroyed my library. Na-Krul... The name fills me with a cold dread. I " +"prefer to think of it only as The Creature rather than ponder its true name." +msgstr "" +"У рове стварэння прамільгнула яго імя — На-Крул. Я сіліўся даследаваць гэтае " +"імя, але меншыя дэманы нейкім чынам знішчылі маю бібліятэку. На-Крул... " +"Гэтае імя ўсяляе ва мне халодную жудасць. Палічу за лепшае думаць аб ім " +"толькі як аб Стварэнні, не разважаючы пра яго сапраўднае імя." + +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:636 +msgid "" +"The entrapped creature's howls of fury keep me from gaining much needed " +"sleep. It rages against the one who sent it to the Void, and it calls foul " +"curses upon me for trapping it here. Its words fill my heart with terror, " +"and yet I cannot block out its voice." +msgstr "" +"Шалёнае выццё палоннага не дае мне спаць, як моцна бы я ні хацеў. Яно лютуе " +"супраць таго, хто паслаў яго ў Пустату, і выкрыквае на мяне агідныя праклёны " +"за сваё зняволенне. Яго словы поўняць маё сэрца жахам, але ж заглушыць яго " +"голас я не магу." + +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:638 +msgid "" +"My time is quickly running out. I must record the ways to weaken the demon, " +"and then conceal that text, lest his minions find some way to use my " +"knowledge to free their lord. I hope that whoever finds this journal will " +"seek the knowledge." +msgstr "" +"Час хутка скончваецца. Я мушу запісаць спосаб, як аслабіць дэмана, і потым " +"схаваць гэты тэкст, каб яго прыслужнікі не скарысталі мае веды, каб " +"вызваліць свайго валадара. Спадзяваюся, што той, хто знойдзе гэты дзённік, " +"адшукае тыя веды." + +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:640 +msgid "" +"Whoever finds this scroll is charged with stopping the demonic creature that " +"lies within these walls. My time is over. Even now, its hellish minions " +"claw at the frail door behind which I hide. \n" +" \n" +"I have hobbled the demon with arcane magic and encased it within great " +"walls, but I fear that will not be enough. \n" +" \n" +"The spells found in my three grimoires will provide you protected entrance " +"to his domain, but only if cast in their proper sequence. The levers at the " +"entryway will remove the barriers and free the demon; touch them not! Use " +"only these spells to gain entry or his power may be too great for you to " +"defeat." +msgstr "" +"Таму, хто знойдзе гэты скрутак, даручаецца спыніць дэманічнае стварэнне, што " +"мяшкае ў гэтых сценах. Мой час мінуў. Нават цяпер, яго пякельныя прыслужнікі " +"скрабуцца ў крохкія дзверы, за якімі я схаваўся.\n" +"\n" +"Я затрымаў дэмана таемнаю магіяю і замкнуў ў вялікіх сценах, але я страшуся, " +"што гэтага не стане.\n" +"\n" +"Чары, якія знаходзяцця ў трох маіх грымуарах, дазволяць бяспечна ўвайсці ў " +"яго ўладанні, але толькі калі выкарыстаны ў правільным парадку. Рычагі пры " +"ўваходзе здымуць бар'еры і вызваляць дэмана. Не руш іх! Карыстайся толькі " +"тымі чарамі, каб трапіць унутр, альбо яго сіла будзе неадольнаю." + +#. TRANSLATORS: Quest text read aloud from book by player +#: Source/textdat.cpp:642 Source/textdat.cpp:645 Source/textdat.cpp:648 +#: Source/textdat.cpp:651 Source/textdat.cpp:654 +msgid "In Spiritu Sanctum." +msgstr "In Spiritu Sanctum." + +#. TRANSLATORS: Quest text read aloud from book by player +#: Source/textdat.cpp:643 Source/textdat.cpp:646 Source/textdat.cpp:649 +#: Source/textdat.cpp:652 Source/textdat.cpp:655 +msgid "Praedictum Otium." +msgstr "Praedictum Otium." + +#. TRANSLATORS: Quest text read aloud from book by player +#: Source/textdat.cpp:644 Source/textdat.cpp:647 Source/textdat.cpp:650 +#: Source/textdat.cpp:653 Source/textdat.cpp:656 +msgid "Efficio Obitus Ut Inimicus." +msgstr "Efficio Obitus Ut Inimicus." + +#: Source/towners.cpp:79 +msgid "Griswold the Blacksmith" +msgstr "Каваль Грызвальд" + +#: Source/towners.cpp:101 +msgid "Ogden the Tavern owner" +msgstr "Карчмар Огдэн" + +#: Source/towners.cpp:110 +msgid "Wounded Townsman" +msgstr "Паранены Гараджанін" + +#: Source/towners.cpp:132 +msgid "Adria the Witch" +msgstr "Ведзьма Эйдрыя" + +#: Source/towners.cpp:141 +msgid "Gillian the Barmaid" +msgstr "Барменка Джыліен" + +#: Source/towners.cpp:172 +msgid "Pepin the Healer" +msgstr "Лекар Піпін" + +#: Source/towners.cpp:189 +msgid "Cain the Elder" +msgstr "Старэйшына Кейн" + +#: Source/towners.cpp:218 +msgid "Cow" +msgstr "Карова" + +#: Source/towners.cpp:242 +msgid "Lester the farmer" +msgstr "Фермер Лэстэр" + +#: Source/towners.cpp:255 +msgid "Complete Nut" +msgstr "Ляснуты" + +#: Source/towners.cpp:264 +msgid "Celia" +msgstr "Сілія" + +#: Source/towners.cpp:277 +msgid "Slain Townsman" +msgstr "Забіты Гараджанін" + +#: Source/trigs.cpp:343 +msgid "Down to dungeon" +msgstr "Уніз у падзямелле" + +#: Source/trigs.cpp:354 +msgid "Down to catacombs" +msgstr "Уніз у катакомбы" + +#: Source/trigs.cpp:364 +msgid "Down to caves" +msgstr "Уніз у пячоры" + +#: Source/trigs.cpp:374 +msgid "Down to hell" +msgstr "Уніз у пекла" + +#: Source/trigs.cpp:386 +msgid "Down to Hive" +msgstr "Уніз у Вулей" + +#: Source/trigs.cpp:398 +msgid "Down to Crypt" +msgstr "Уніз у Склеп" + +#: Source/trigs.cpp:414 Source/trigs.cpp:494 Source/trigs.cpp:541 +#: Source/trigs.cpp:636 +msgid "Up to level {:d}" +msgstr "Наверх на ўзровень {:d}" + +#: Source/trigs.cpp:416 Source/trigs.cpp:471 Source/trigs.cpp:523 +#: Source/trigs.cpp:602 Source/trigs.cpp:619 Source/trigs.cpp:666 +msgid "Up to town" +msgstr "Наверх у горад" + +#: Source/trigs.cpp:427 Source/trigs.cpp:505 Source/trigs.cpp:558 +#: Source/trigs.cpp:583 Source/trigs.cpp:648 +msgid "Down to level {:d}" +msgstr "Уніз на ўзровень {:d}" + +#: Source/trigs.cpp:439 +msgid "Up to Crypt level {:d}" +msgstr "Наверх на ўзровень Склепа {:d}" + +#: Source/trigs.cpp:454 +msgid "Down to Crypt level {:d}" +msgstr "Уніз на ўзровень Склепа {:d}" + +#: Source/trigs.cpp:570 +msgid "Up to Nest level {:d}" +msgstr "Наверх на ўзровень Гнязда {:d}" + +#: Source/trigs.cpp:679 +msgid "Down to Diablo" +msgstr "Уніз да Д'ябла" + +#: Source/trigs.cpp:712 Source/trigs.cpp:726 Source/trigs.cpp:740 +msgid "Back to Level {:d}" +msgstr "Назад да ўзроўню {:d}" diff --git a/Translations/fi.po b/Translations/fi.po index 979336f39..4edb51d57 100644 --- a/Translations/fi.po +++ b/Translations/fi.po @@ -1973,7 +1973,7 @@ msgstr "Sitä, mitä ei voi pitää, ei voi vahingoittaa" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) #: Source/diablo_msg.cpp:86 msgid "Crimson and Azure become as the sun" -msgstr "Crimson ja Azure muuttuvat auringoksi" +msgstr "Karmiininpunainen ja Taivaansininen muuttuvat auringoksi" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) #: Source/diablo_msg.cpp:87 @@ -3260,7 +3260,7 @@ msgstr "rappeutuminen {:+d}% vahinko" #: Source/items.cpp:4056 msgid "2x dmg to monst, 1x to you" -msgstr "2x vahinko monsterille, 1x sinulle" +msgstr "2x vahinko hirviölle, 1x sinulle" #: Source/items.cpp:4058 #, no-c-format @@ -3370,7 +3370,7 @@ msgstr "Elämänkaaren areena" #: Source/levels/trigs.cpp:355 msgid "Down to dungeon" -msgstr "Linnakellariin" +msgstr "Tyrmään" #: Source/levels/trigs.cpp:364 msgid "Down to catacombs" @@ -3483,7 +3483,7 @@ msgid "" "Could not add a monster, since the maximum monster type number of {} has " "already been reached." msgstr "" -"Monsteria ei voitu lisätä, koska monsterityyppien enimmäismäärä {} on jo " +"Hirviötä ei voitu lisätä, koska hirviötyyppien enimmäismäärä {} on jo " "saavutettu." #: Source/monstdat.cpp:344 @@ -4497,14 +4497,14 @@ msgstr "Valitse satunnaisesti käytettävissä olevat tehtävät uusille peleill #: Source/options.cpp:811 msgid "Show Monster Type" -msgstr "Näytä monsterityyppi" +msgstr "Näytä hirviötyyppi" #: Source/options.cpp:811 msgid "" "Hovering over a monster will display the type of monster in the description " "box in the UI." msgstr "" -"Kun viet hiiren monsterin päälle, sen tyyppi näkyy käyttöliittymän " +"Kun viet hiiren hirviön päälle, sen tyyppi näkyy käyttöliittymän " "kuvauskentässä." #: Source/options.cpp:812 @@ -5864,7 +5864,7 @@ msgstr "Gharbad Heikko" #: Source/translation_dummy.cpp:119 msgctxt "monster" msgid "Zhar the Mad" -msgstr "Hirmuhullu Zhar" +msgstr "Zhar Mielipuoli" #: Source/translation_dummy.cpp:120 msgctxt "monster" @@ -6468,7 +6468,7 @@ msgstr "Täyden manan juoma" #: Source/translation_dummy.cpp:247 msgid "Griswold's Edge" -msgstr "Griswoldin Reuna" +msgstr "Griswoldin Terä" #: Source/translation_dummy.cpp:248 msgid "Bovine Plate" @@ -6588,11 +6588,11 @@ msgstr "Nastoitettu nahkahaarniska" #: Source/translation_dummy.cpp:279 msgid "Ring Mail" -msgstr "Rengasmail" +msgstr "Rengashaarniska" #: Source/translation_dummy.cpp:280 msgid "Mail" -msgstr "Posti" +msgstr "Haarniska" #: Source/translation_dummy.cpp:281 msgid "Chain Mail" @@ -6612,7 +6612,7 @@ msgstr "Panssari" #: Source/translation_dummy.cpp:285 msgid "Splint Mail" -msgstr "Lastahaarniska" +msgstr "Liuskapanssari" #: Source/translation_dummy.cpp:286 msgid "Plate Mail" @@ -6844,7 +6844,7 @@ msgstr "Varsta" #: Source/translation_dummy.cpp:345 msgid "Maul" -msgstr "Raadella" +msgstr "Moukari" #: Source/translation_dummy.cpp:346 msgid "Bow" @@ -7148,7 +7148,7 @@ msgstr "Najin pulmuri" #: Source/translation_dummy.cpp:422 msgid "Mindcry" -msgstr "Mindcry" +msgstr "Mielenitku" #: Source/translation_dummy.cpp:423 msgid "Rod of Onan" @@ -7200,11 +7200,11 @@ msgstr "Viisauden kääre" #: Source/translation_dummy.cpp:435 msgid "Sparking Mail" -msgstr "Kipinöivä posti panssari" +msgstr "Kipinöivä panssari" #: Source/translation_dummy.cpp:436 msgid "Scavenger Carapace" -msgstr "Keräilijä Karapassi" +msgstr "Keräilijän Karapassi" #: Source/translation_dummy.cpp:437 msgid "Nightscape" @@ -9167,7 +9167,7 @@ msgstr "" "Olen kuullut kultaisesta eliksiiristä, joka voisi poistaa kirouksen ja antaa " "sielulleni rauhan, mutta en ole onnistunut löytämään sitä. Voimani ovat nyt " "hiipumassa, ja niiden mukana myös viimeisetkin ihmisyyteni rippeet. Auta " -"minua ja etsi eliksiiri. Korvaan ponnistuksesi – vannon kunniasanallani." +"minua ja etsi eliksiiri. Korvaan ponnistuksesi – vannon sen kunniasanallani." #: Source/translation_dummy.cpp:762 msgid "" @@ -11367,10 +11367,10 @@ msgid "" "but we snuck over there, and then suddenly this BUG came out! We ran away " "but Theo fell down and the bug GRABBED him and took him away!" msgstr "" -"Kadotin Teron! Kadotin parhaan ystäväni! Leikimme joen rannalla, ja Theo " +"Kadotin Teron! Kadotin parhaan ystäväni! Leikimme joen rannalla, ja Tero " "sanoi haluavansa mennä katsomaan isoa vihreää juttua. Sanoin, että meidän ei " "pitäisi mennä, mutta hiivimme sinne, ja yhtäkkiä tämä HYÖNTEINEN ilmestyi! " -"Juoksimme karkuun, mutta Theo kaatui, ja hyönteinen TARTTUI häneen ja vei " +"Juoksimme karkuun, mutta Tero kaatui, ja hyönteinen TARTTUI häneen ja vei " "hänet pois!" #: Source/translation_dummy.cpp:945 @@ -11859,7 +11859,7 @@ msgstr "Jättiläisen Rystynen" #: Source/translation_dummy.cpp:1004 msgid "Mercurial Ring" -msgstr "Mercurial sormus" +msgstr "Häilyvä sormus" #: Source/translation_dummy.cpp:1005 msgid "Xorine's Ring" @@ -12005,7 +12005,7 @@ msgstr "Tulen Sormus" #: Source/translation_dummy.cpp:1038 msgctxt "spell" msgid "Search" -msgstr "Paljasta" +msgstr "Paljastus" #: Source/translation_dummy.cpp:1039 msgctxt "spell" diff --git a/Translations/ja.po b/Translations/ja.po index 311e6c2cc..28ba5273c 100644 --- a/Translations/ja.po +++ b/Translations/ja.po @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: DevilutionX\n" -"POT-Creation-Date: 2025-10-02 15:20+0200\n" +"POT-Creation-Date: 2026-01-15 20:54+0900\n" "PO-Revision-Date: \n" "Last-Translator: bubio \n" "Language-Team: \n" @@ -13,7 +13,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Poedit 3.6\n" +"X-Generator: Poedit 3.8\n" "X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-KeywordsList: _;N_;P_:1c,2\n" "X-Poedit-Basepath: ..\n" @@ -100,7 +100,7 @@ msgstr "Associate Producer" msgid "Diablo Strike Team" msgstr "Diablo Strike Team" -#: Source/DiabloUI/credits_lines.cpp:79 Source/gamemenu.cpp:79 +#: Source/DiabloUI/credits_lines.cpp:79 Source/gamemenu.cpp:83 msgid "Music" msgstr "音楽" @@ -313,51 +313,51 @@ msgstr "The Ring of One Thousand" msgid "\tNo souls were sold in the making of this game." msgstr "\tNo souls were sold in the making of this game." -#: Source/DiabloUI/dialogs.cpp:97 Source/DiabloUI/dialogs.cpp:109 -#: Source/DiabloUI/hero/selhero.cpp:199 Source/DiabloUI/hero/selhero.cpp:225 -#: Source/DiabloUI/hero/selhero.cpp:310 Source/DiabloUI/hero/selhero.cpp:550 -#: Source/DiabloUI/multi/selconn.cpp:94 Source/DiabloUI/multi/selgame.cpp:187 -#: Source/DiabloUI/multi/selgame.cpp:350 Source/DiabloUI/multi/selgame.cpp:376 -#: Source/DiabloUI/multi/selgame.cpp:518 Source/DiabloUI/multi/selgame.cpp:595 -#: Source/DiabloUI/selok.cpp:82 +#: Source/DiabloUI/dialogs.cpp:108 Source/DiabloUI/dialogs.cpp:120 +#: Source/DiabloUI/hero/selhero.cpp:204 Source/DiabloUI/hero/selhero.cpp:230 +#: Source/DiabloUI/hero/selhero.cpp:315 Source/DiabloUI/hero/selhero.cpp:555 +#: Source/DiabloUI/multi/selconn.cpp:99 Source/DiabloUI/multi/selgame.cpp:194 +#: Source/DiabloUI/multi/selgame.cpp:362 Source/DiabloUI/multi/selgame.cpp:388 +#: Source/DiabloUI/multi/selgame.cpp:530 Source/DiabloUI/multi/selgame.cpp:607 +#: Source/DiabloUI/selok.cpp:86 msgid "OK" msgstr "OK" -#: Source/DiabloUI/hero/selhero.cpp:168 +#: Source/DiabloUI/hero/selhero.cpp:173 msgid "Choose Class" msgstr "クラス​選択" -#: Source/DiabloUI/hero/selhero.cpp:202 Source/DiabloUI/hero/selhero.cpp:228 -#: Source/DiabloUI/hero/selhero.cpp:313 Source/DiabloUI/hero/selhero.cpp:558 -#: Source/DiabloUI/multi/selconn.cpp:97 Source/DiabloUI/progress.cpp:50 +#: Source/DiabloUI/hero/selhero.cpp:207 Source/DiabloUI/hero/selhero.cpp:233 +#: Source/DiabloUI/hero/selhero.cpp:318 Source/DiabloUI/hero/selhero.cpp:563 +#: Source/DiabloUI/multi/selconn.cpp:102 Source/DiabloUI/progress.cpp:57 msgid "Cancel" msgstr "キャンセル" -#: Source/DiabloUI/hero/selhero.cpp:208 Source/DiabloUI/hero/selhero.cpp:298 +#: Source/DiabloUI/hero/selhero.cpp:213 Source/DiabloUI/hero/selhero.cpp:303 msgid "New Multi Player Hero" msgstr "新しい​マルチ​プレイヤー​ヒーロー" -#: Source/DiabloUI/hero/selhero.cpp:208 Source/DiabloUI/hero/selhero.cpp:298 +#: Source/DiabloUI/hero/selhero.cpp:213 Source/DiabloUI/hero/selhero.cpp:303 msgid "New Single Player Hero" msgstr "新しい​シングルプレイヤー​ヒーロー" -#: Source/DiabloUI/hero/selhero.cpp:217 +#: Source/DiabloUI/hero/selhero.cpp:222 msgid "Save File Exists" msgstr "セーブ​ファイル​が​あり​ます" -#: Source/DiabloUI/hero/selhero.cpp:220 Source/gamemenu.cpp:50 +#: Source/DiabloUI/hero/selhero.cpp:225 Source/gamemenu.cpp:54 msgid "Load Game" msgstr "ゲーム​の​ロード" -#: Source/DiabloUI/hero/selhero.cpp:221 Source/multi.cpp:835 +#: Source/DiabloUI/hero/selhero.cpp:226 Source/multi.cpp:876 msgid "New Game" msgstr "新規​ゲーム" -#: Source/DiabloUI/hero/selhero.cpp:231 Source/DiabloUI/hero/selhero.cpp:564 +#: Source/DiabloUI/hero/selhero.cpp:236 Source/DiabloUI/hero/selhero.cpp:569 msgid "Single Player Characters" msgstr "シングルプレイヤー​キャラクター" -#: Source/DiabloUI/hero/selhero.cpp:290 +#: Source/DiabloUI/hero/selhero.cpp:295 msgid "" "The Rogue and Sorcerer are only available in the full retail version of " "Diablo. Visit https://www.gog.com/game/diablo to purchase." @@ -365,11 +365,11 @@ msgstr "" "ローグ​と​ソーサラー​は、ディアブロ​の​製品​版​で​のみ​使用​可能​です。ご​購入​は https://" "www.gog.com/game/diablo を​ご覧​ください。" -#: Source/DiabloUI/hero/selhero.cpp:304 Source/DiabloUI/hero/selhero.cpp:307 +#: Source/DiabloUI/hero/selhero.cpp:309 Source/DiabloUI/hero/selhero.cpp:312 msgid "Enter Name" msgstr "名前​を​入力" -#: Source/DiabloUI/hero/selhero.cpp:336 +#: Source/DiabloUI/hero/selhero.cpp:341 msgid "" "Invalid name. A name cannot contain spaces, reserved characters, or reserved " "words.\n" @@ -377,303 +377,313 @@ msgstr "" "名前​が​無効​です。名前​に​スペース、予約​文字、予約​語​を​含む​こと​は​でき​ませ​ん。\n" #. TRANSLATORS: Error Message -#: Source/DiabloUI/hero/selhero.cpp:343 +#: Source/DiabloUI/hero/selhero.cpp:348 msgid "Unable to create character." msgstr "キャラクター​を​作る​こと​が​でき​ませ​ん。" -#: Source/DiabloUI/hero/selhero.cpp:509 +#: Source/DiabloUI/hero/selhero.cpp:514 msgid "Level:" msgstr "レベル:" -#: Source/DiabloUI/hero/selhero.cpp:513 +#: Source/DiabloUI/hero/selhero.cpp:518 msgid "Strength:" msgstr "STR:" -#: Source/DiabloUI/hero/selhero.cpp:513 +#: Source/DiabloUI/hero/selhero.cpp:518 msgid "Magic:" msgstr "MAG:" -#: Source/DiabloUI/hero/selhero.cpp:513 +#: Source/DiabloUI/hero/selhero.cpp:518 msgid "Dexterity:" msgstr "DEX:" -#: Source/DiabloUI/hero/selhero.cpp:513 +#: Source/DiabloUI/hero/selhero.cpp:518 msgid "Vitality:" msgstr "VIT:" -#: Source/DiabloUI/hero/selhero.cpp:515 +#: Source/DiabloUI/hero/selhero.cpp:520 msgid "Savegame:" msgstr "セーブ​回数:" -#: Source/DiabloUI/hero/selhero.cpp:534 +#: Source/DiabloUI/hero/selhero.cpp:539 msgid "Select Hero" msgstr "ヒーロー​の​選択" -#: Source/DiabloUI/hero/selhero.cpp:542 +#: Source/DiabloUI/hero/selhero.cpp:547 msgid "New Hero" msgstr "新しい​ヒーロー" -#: Source/DiabloUI/hero/selhero.cpp:553 +#: Source/DiabloUI/hero/selhero.cpp:558 msgid "Delete" msgstr "削除" -#: Source/DiabloUI/hero/selhero.cpp:562 +#: Source/DiabloUI/hero/selhero.cpp:567 msgid "Multi Player Characters" msgstr "マルチ​プレイヤー​キャラクター" -#: Source/DiabloUI/hero/selhero.cpp:613 +#: Source/DiabloUI/hero/selhero.cpp:618 msgid "Delete Multi Player Hero" msgstr "マルチ​プレイヤー​ヒーロー​を​削除" -#: Source/DiabloUI/hero/selhero.cpp:615 +#: Source/DiabloUI/hero/selhero.cpp:620 msgid "Delete Single Player Hero" msgstr "シングルプレイヤー​ヒーロー​を​削除" -#: Source/DiabloUI/hero/selhero.cpp:617 +#: Source/DiabloUI/hero/selhero.cpp:622 #, c++-format msgid "Are you sure you want to delete the character \"{:s}\"?" msgstr "キャラクター​「​{:s}​」​を​削除​し​て​よろしい​です​か?" -#: Source/DiabloUI/mainmenu.cpp:48 +#: Source/DiabloUI/mainmenu.cpp:53 msgid "Single Player" msgstr "シングルプレイヤー" -#: Source/DiabloUI/mainmenu.cpp:49 +#: Source/DiabloUI/mainmenu.cpp:54 msgid "Multi Player" msgstr "マルチ​プレイヤー" -#: Source/DiabloUI/mainmenu.cpp:50 Source/DiabloUI/settingsmenu.cpp:384 +#: Source/DiabloUI/mainmenu.cpp:55 Source/DiabloUI/settingsmenu.cpp:392 msgid "Settings" msgstr "設定" -#: Source/DiabloUI/mainmenu.cpp:51 +#: Source/DiabloUI/mainmenu.cpp:56 msgid "Support" msgstr "サポート" -#: Source/DiabloUI/mainmenu.cpp:52 +#: Source/DiabloUI/mainmenu.cpp:57 msgid "Show Credits" msgstr "クレジット​を​見る" -#: Source/DiabloUI/mainmenu.cpp:54 +#: Source/DiabloUI/mainmenu.cpp:59 msgid "Exit Hellfire" msgstr "ヘルファイア​を​終了" -#: Source/DiabloUI/mainmenu.cpp:54 +#: Source/DiabloUI/mainmenu.cpp:59 msgid "Exit Diablo" msgstr "ディアブロ​を​終了" -#: Source/DiabloUI/mainmenu.cpp:71 +#: Source/DiabloUI/mainmenu.cpp:76 msgid "Shareware" msgstr "シェアウェア" -#: Source/DiabloUI/multi/selconn.cpp:26 +#: Source/DiabloUI/multi/selconn.cpp:31 msgid "Client-Server (TCP)" msgstr "クライアント - サーバー (​TCP​)" -#: Source/DiabloUI/multi/selconn.cpp:27 +#: Source/DiabloUI/multi/selconn.cpp:32 msgid "Offline" msgstr "オフライン" -#: Source/DiabloUI/multi/selconn.cpp:68 Source/DiabloUI/multi/selgame.cpp:662 -#: Source/DiabloUI/multi/selgame.cpp:688 +#: Source/DiabloUI/multi/selconn.cpp:73 Source/DiabloUI/multi/selgame.cpp:674 +#: Source/DiabloUI/multi/selgame.cpp:700 msgid "Multi Player Game" msgstr "マルチ​プレイヤー​ゲーム" -#: Source/DiabloUI/multi/selconn.cpp:74 +#: Source/DiabloUI/multi/selconn.cpp:79 msgid "Requirements:" msgstr "必要条件:" -#: Source/DiabloUI/multi/selconn.cpp:80 +#: Source/DiabloUI/multi/selconn.cpp:85 msgid "no gateway needed" msgstr "ゲートウェイ​不要" -#: Source/DiabloUI/multi/selconn.cpp:86 +#: Source/DiabloUI/multi/selconn.cpp:91 msgid "Select Connection" msgstr "コネクション​を​選択" -#: Source/DiabloUI/multi/selconn.cpp:89 +#: Source/DiabloUI/multi/selconn.cpp:94 msgid "Change Gateway" msgstr "ゲートウェイ​の​変更" -#: Source/DiabloUI/multi/selconn.cpp:122 +#: Source/DiabloUI/multi/selconn.cpp:127 msgid "All computers must be connected to a TCP-compatible network." msgstr "" "すべて​の​コンピューター​が​TCP​対応​の​ネットワーク​に​接続​さ​れ​て​いる​必要​が​あり​ます。" -#: Source/DiabloUI/multi/selconn.cpp:126 +#: Source/DiabloUI/multi/selconn.cpp:131 msgid "All computers must be connected to the internet." msgstr "すべて​の​コンピューター​が​インターネット​に​接続​さ​れ​て​いる​必要​が​あり​ます。" -#: Source/DiabloUI/multi/selconn.cpp:130 +#: Source/DiabloUI/multi/selconn.cpp:135 msgid "Play by yourself with no network exposure." msgstr "ひとり​で​プレイ​し​て​も、ネットワーク​に​触れる​こと​は​あり​ませ​ん。" -#: Source/DiabloUI/multi/selconn.cpp:135 +#: Source/DiabloUI/multi/selconn.cpp:140 #, c++-format msgid "Players Supported: {:d}" msgstr "サポート​プレイヤー​数: {:d}" -#: Source/DiabloUI/multi/selgame.cpp:100 Source/options.cpp:425 -#: Source/options.cpp:473 Source/translation_dummy.cpp:630 +#: Source/DiabloUI/multi/selgame.cpp:107 Source/options.cpp:436 +#: Source/options.cpp:484 Source/translation_dummy.cpp:630 msgid "Diablo" msgstr "ディアブロ" -#: Source/DiabloUI/multi/selgame.cpp:103 +#: Source/DiabloUI/multi/selgame.cpp:110 msgid "Diablo Shareware" msgstr "ディアブロ​・​シェアウェア" -#: Source/DiabloUI/multi/selgame.cpp:106 Source/options.cpp:427 -#: Source/options.cpp:487 +#: Source/DiabloUI/multi/selgame.cpp:113 Source/options.cpp:438 +#: Source/options.cpp:498 msgid "Hellfire" msgstr "ヘルファイア" -#: Source/DiabloUI/multi/selgame.cpp:109 +#: Source/DiabloUI/multi/selgame.cpp:116 msgid "Hellfire Shareware" msgstr "ヘルファイア​・​シェアウェア" -#: Source/DiabloUI/multi/selgame.cpp:112 +#: Source/DiabloUI/multi/selgame.cpp:119 msgid "The host is running a different game than you." msgstr "ホスト​は​あなた​と​は​別​の​ゲーム​を​し​て​い​ます。" -#: Source/DiabloUI/multi/selgame.cpp:114 +#: Source/DiabloUI/multi/selgame.cpp:121 #, c++-format msgid "The host is running a different game mode ({:s}) than you." msgstr "ホスト​は​あなた​と​違う​ゲーム​モード​(​{:s}​)​を​実行​し​て​い​ます。" #. TRANSLATORS: Error message when somebody tries to join a game running another version. -#: Source/DiabloUI/multi/selgame.cpp:116 +#: Source/DiabloUI/multi/selgame.cpp:123 #, c++-format msgid "Your version {:s} does not match the host {:d}.{:d}.{:d}." msgstr "あなた​の​バージョン​{:s}​は、ホスト​{:d}.{:d}.{:d}​と​一致​し​ませ​ん。" -#: Source/DiabloUI/multi/selgame.cpp:153 Source/DiabloUI/multi/selgame.cpp:581 +#: Source/DiabloUI/multi/selgame.cpp:160 Source/DiabloUI/multi/selgame.cpp:593 msgid "Description:" msgstr "説明:" -#: Source/DiabloUI/multi/selgame.cpp:159 +#: Source/DiabloUI/multi/selgame.cpp:166 msgid "Select Action" msgstr "アクション​を​選択" -#: Source/DiabloUI/multi/selgame.cpp:162 Source/DiabloUI/multi/selgame.cpp:338 -#: Source/DiabloUI/multi/selgame.cpp:499 +#: Source/DiabloUI/multi/selgame.cpp:169 Source/DiabloUI/multi/selgame.cpp:350 +#: Source/DiabloUI/multi/selgame.cpp:511 msgid "Create Game" msgstr "ゲーム​の​作成" -#: Source/DiabloUI/multi/selgame.cpp:164 +#: Source/DiabloUI/multi/selgame.cpp:171 msgid "Create Public Game" msgstr "パブリック​ゲーム​の​作成" -#: Source/DiabloUI/multi/selgame.cpp:165 +#: Source/DiabloUI/multi/selgame.cpp:172 msgid "Join Game" msgstr "ゲーム​に​参加" -#: Source/DiabloUI/multi/selgame.cpp:169 +#: Source/DiabloUI/multi/selgame.cpp:176 msgid "Public Games" msgstr "パブリック​・​ゲーム" -#: Source/DiabloUI/multi/selgame.cpp:174 Source/diablo_msg.cpp:72 +#: Source/DiabloUI/multi/selgame.cpp:181 Source/diablo_msg.cpp:78 msgid "Loading..." msgstr "ロード​中​…" #. TRANSLATORS: type of dungeon (i.e. Cathedral, Caves) -#: Source/DiabloUI/multi/selgame.cpp:176 Source/discord/discord.cpp:86 -#: Source/options.cpp:459 Source/options.cpp:730 +#: Source/DiabloUI/multi/selgame.cpp:183 Source/discord/discord.cpp:86 +#: Source/options.cpp:470 Source/options.cpp:781 #: Source/panels/charpanel.cpp:142 msgid "None" msgstr "なし" -#: Source/DiabloUI/multi/selgame.cpp:190 Source/DiabloUI/multi/selgame.cpp:353 -#: Source/DiabloUI/multi/selgame.cpp:379 Source/DiabloUI/multi/selgame.cpp:521 -#: Source/DiabloUI/multi/selgame.cpp:598 +#: Source/DiabloUI/multi/selgame.cpp:197 Source/DiabloUI/multi/selgame.cpp:365 +#: Source/DiabloUI/multi/selgame.cpp:391 Source/DiabloUI/multi/selgame.cpp:533 +#: Source/DiabloUI/multi/selgame.cpp:610 msgid "CANCEL" msgstr "キャンセル" -#: Source/DiabloUI/multi/selgame.cpp:229 +#: Source/DiabloUI/multi/selgame.cpp:236 msgid "Create a new game with a difficulty setting of your choice." msgstr "お​好み​の​難易度​を​設定​し​て、新しい​ゲーム​を​作成​し​ます。" -#: Source/DiabloUI/multi/selgame.cpp:232 +#: Source/DiabloUI/multi/selgame.cpp:239 msgid "" "Create a new public game that anyone can join with a difficulty setting of " "your choice." msgstr "" "誰​で​も​参加​できる​新しい​パブリック​ゲーム​を​作成​し、お​好み​の​難易度​を​設定​し​ます。" -#: Source/DiabloUI/multi/selgame.cpp:236 +#: Source/DiabloUI/multi/selgame.cpp:243 msgid "Enter Game ID to join a game already in progress." msgstr "すでに​進行中​の​ゲーム​に​参加​する​場合​は、ゲーム​ID​を​入力​し​て​ください。" -#: Source/DiabloUI/multi/selgame.cpp:238 +#: Source/DiabloUI/multi/selgame.cpp:245 msgid "Enter an IP or a hostname to join a game already in progress." msgstr "IP​また​は​ホスト​名​を​入力​する​と、すでに​進行中​の​ゲーム​に​参加​でき​ます。" -#: Source/DiabloUI/multi/selgame.cpp:243 +#: Source/DiabloUI/multi/selgame.cpp:250 msgid "Join the public game already in progress." msgstr "すでに​進行中​の​パブリック​・​ゲーム​に​参加​する。" -#: Source/DiabloUI/multi/selgame.cpp:249 Source/DiabloUI/multi/selgame.cpp:343 -#: Source/DiabloUI/multi/selgame.cpp:404 Source/DiabloUI/multi/selgame.cpp:510 -#: Source/DiabloUI/multi/selgame.cpp:530 Source/automap.cpp:1461 +#: Source/DiabloUI/multi/selgame.cpp:256 Source/DiabloUI/multi/selgame.cpp:355 +#: Source/DiabloUI/multi/selgame.cpp:416 Source/DiabloUI/multi/selgame.cpp:522 +#: Source/DiabloUI/multi/selgame.cpp:542 Source/automap.cpp:1470 #: Source/discord/discord.cpp:114 msgid "Normal" msgstr "通常" -#: Source/DiabloUI/multi/selgame.cpp:252 Source/DiabloUI/multi/selgame.cpp:344 -#: Source/DiabloUI/multi/selgame.cpp:408 Source/automap.cpp:1464 +#: Source/DiabloUI/multi/selgame.cpp:259 Source/DiabloUI/multi/selgame.cpp:356 +#: Source/DiabloUI/multi/selgame.cpp:420 Source/automap.cpp:1473 #: Source/discord/discord.cpp:114 msgid "Nightmare" msgstr "ナイトメア" -#: Source/DiabloUI/multi/selgame.cpp:255 Source/DiabloUI/multi/selgame.cpp:345 -#: Source/DiabloUI/multi/selgame.cpp:412 Source/automap.cpp:1467 +#: Source/DiabloUI/multi/selgame.cpp:262 Source/DiabloUI/multi/selgame.cpp:357 +#: Source/DiabloUI/multi/selgame.cpp:424 Source/automap.cpp:1476 #: Source/discord/discord.cpp:81 Source/discord/discord.cpp:114 msgid "Hell" msgstr "ヘル" #. TRANSLATORS: {:s} means: Game Difficulty. -#: Source/DiabloUI/multi/selgame.cpp:258 Source/automap.cpp:1471 +#: Source/DiabloUI/multi/selgame.cpp:265 Source/automap.cpp:1480 #, c++-format msgid "Difficulty: {:s}" msgstr "難易度 {:s}" -#: Source/DiabloUI/multi/selgame.cpp:262 Source/gamemenu.cpp:165 +#: Source/DiabloUI/multi/selgame.cpp:269 Source/gamemenu.cpp:169 msgid "Speed: Normal" msgstr "スピード: 通常" -#: Source/DiabloUI/multi/selgame.cpp:265 Source/gamemenu.cpp:163 +#: Source/DiabloUI/multi/selgame.cpp:272 Source/gamemenu.cpp:167 msgid "Speed: Fast" msgstr "スピード: 速い" -#: Source/DiabloUI/multi/selgame.cpp:268 Source/gamemenu.cpp:161 +#: Source/DiabloUI/multi/selgame.cpp:275 Source/gamemenu.cpp:165 msgid "Speed: Faster" msgstr "スピード: より​速い" -#: Source/DiabloUI/multi/selgame.cpp:271 Source/gamemenu.cpp:159 +#: Source/DiabloUI/multi/selgame.cpp:278 Source/gamemenu.cpp:163 msgid "Speed: Fastest" msgstr "スピード: 最速" -#: Source/DiabloUI/multi/selgame.cpp:279 +#: Source/DiabloUI/multi/selgame.cpp:286 msgid "Players: " msgstr "プレイヤー: " -#: Source/DiabloUI/multi/selgame.cpp:341 +#: Source/DiabloUI/multi/selgame.cpp:293 +#, c++-format +msgid "Ping: {:d} ms (RELAYED)" +msgstr "Ping​:​{:d} ms​(​リレー​経由​)" + +#: Source/DiabloUI/multi/selgame.cpp:295 +#, c++-format +msgid "Ping: {:d} ms" +msgstr "Ping​:​{:d} ms" + +#: Source/DiabloUI/multi/selgame.cpp:353 msgid "Select Difficulty" msgstr "難易度​の​選択" -#: Source/DiabloUI/multi/selgame.cpp:359 +#: Source/DiabloUI/multi/selgame.cpp:371 #, c++-format msgid "Join {:s} Games" msgstr "{:s}​ゲーム​に​参加" -#: Source/DiabloUI/multi/selgame.cpp:364 +#: Source/DiabloUI/multi/selgame.cpp:376 msgid "Enter Game ID" msgstr "ゲーム​ID​を​入力" -#: Source/DiabloUI/multi/selgame.cpp:366 +#: Source/DiabloUI/multi/selgame.cpp:378 msgid "Enter address" msgstr "アドレス​を​入力" -#: Source/DiabloUI/multi/selgame.cpp:405 +#: Source/DiabloUI/multi/selgame.cpp:417 msgid "" "Normal Difficulty\n" "This is where a starting character should begin the quest to defeat Diablo." @@ -681,7 +691,7 @@ msgstr "" "通常​の​難易度​\n" "​最初​の​キャラクター​が​Diablo​を​倒す​ため​の​クエスト​を​始める​の​に​適し​た​難易度​です。" -#: Source/DiabloUI/multi/selgame.cpp:409 +#: Source/DiabloUI/multi/selgame.cpp:421 msgid "" "Nightmare Difficulty\n" "The denizens of the Labyrinth have been bolstered and will prove to be a " @@ -691,7 +701,7 @@ msgstr "" "​迷宮​の​住人​たち​は​さらに​強化​さ​れ、より​大きな​挑戦​と​なる​でしょう。経験​豊富​な​キャ" "ラクター​に​のみ​お勧め​です。" -#: Source/DiabloUI/multi/selgame.cpp:413 +#: Source/DiabloUI/multi/selgame.cpp:425 msgid "" "Hell Difficulty\n" "The most powerful of the underworld's creatures lurk at the gateway into " @@ -701,7 +711,7 @@ msgstr "" "​地獄​へ​の​入り口​に​は、地下​世界​の​最強​の​生物​が​潜ん​で​い​ます。経験​豊富​な​キャラク" "ター​だけ​が、この​領域​に​足​を​踏み入れる​こと​が​でき​ます。" -#: Source/DiabloUI/multi/selgame.cpp:428 +#: Source/DiabloUI/multi/selgame.cpp:440 msgid "" "Your character must reach level 20 before you can enter a multiplayer game " "of Nightmare difficulty." @@ -709,7 +719,7 @@ msgstr "" "キャラクター​の​レベル​が​20​に​達し​て​い​ない​と、ナイトメア​難易度​の​マルチ​プレイヤー​" "ゲーム​に​参加​でき​ませ​ん。" -#: Source/DiabloUI/multi/selgame.cpp:430 +#: Source/DiabloUI/multi/selgame.cpp:442 msgid "" "Your character must reach level 30 before you can enter a multiplayer game " "of Hell difficulty." @@ -717,23 +727,23 @@ msgstr "" "キャラクター​の​レベル​が​30​に​なる​と、ヘル​難易度​の​マルチ​プレイヤー​ゲーム​に​参加​で" "きる​よう​に​なり​ます。" -#: Source/DiabloUI/multi/selgame.cpp:508 +#: Source/DiabloUI/multi/selgame.cpp:520 msgid "Select Game Speed" msgstr "ゲーム​スピード" -#: Source/DiabloUI/multi/selgame.cpp:511 Source/DiabloUI/multi/selgame.cpp:534 +#: Source/DiabloUI/multi/selgame.cpp:523 Source/DiabloUI/multi/selgame.cpp:546 msgid "Fast" msgstr "高速" -#: Source/DiabloUI/multi/selgame.cpp:512 Source/DiabloUI/multi/selgame.cpp:538 +#: Source/DiabloUI/multi/selgame.cpp:524 Source/DiabloUI/multi/selgame.cpp:550 msgid "Faster" msgstr "超​高速" -#: Source/DiabloUI/multi/selgame.cpp:513 Source/DiabloUI/multi/selgame.cpp:542 +#: Source/DiabloUI/multi/selgame.cpp:525 Source/DiabloUI/multi/selgame.cpp:554 msgid "Fastest" msgstr "神速" -#: Source/DiabloUI/multi/selgame.cpp:531 +#: Source/DiabloUI/multi/selgame.cpp:543 msgid "" "Normal Speed\n" "This is where a starting character should begin the quest to defeat Diablo." @@ -741,7 +751,7 @@ msgstr "" "通常​\n" "​ここ​で​は、主人公​が​Diablo​を​倒す​ため​の​クエスト​を​開始​し​ます。" -#: Source/DiabloUI/multi/selgame.cpp:535 +#: Source/DiabloUI/multi/selgame.cpp:547 msgid "" "Fast Speed\n" "The denizens of the Labyrinth have been hastened and will prove to be a " @@ -751,7 +761,7 @@ msgstr "" "​迷宮​の​住人​たち​は、より​速く、より​大きな​挑戦​と​なる​でしょう。経験​豊富​な​キャラク" "ター​に​のみ​お勧め​です。" -#: Source/DiabloUI/multi/selgame.cpp:539 +#: Source/DiabloUI/multi/selgame.cpp:551 msgid "" "Faster Speed\n" "Most monsters of the dungeon will seek you out quicker than ever before. " @@ -761,7 +771,7 @@ msgstr "" "​ダンジョン​内​の​ほとんど​の​モンスター​は、これ​まで​以上​に​素早く​あなた​を​探し出し​ま" "す。経験​豊富​な​チャンピオン​だけ​が、この​スピード​で​運​を​試す​こと​が​でき​ます。" -#: Source/DiabloUI/multi/selgame.cpp:543 +#: Source/DiabloUI/multi/selgame.cpp:555 msgid "" "Fastest Speed\n" "The minions of the underworld will rush to attack without hesitation. Only a " @@ -771,7 +781,7 @@ msgstr "" "​冥界​の​手下​たち​は、迷う​こと​なく​攻撃​を​仕掛け​て​くる。この​ペース​で​進入​する​の​は、" "真​の​スピード​・​デーモン​だけ​に​し​て​ください。" -#: Source/DiabloUI/multi/selgame.cpp:587 Source/DiabloUI/multi/selgame.cpp:592 +#: Source/DiabloUI/multi/selgame.cpp:599 Source/DiabloUI/multi/selgame.cpp:604 msgid "Enter Password" msgstr "パスワード​を​入力" @@ -783,39 +793,39 @@ msgstr "ヘルファイア​へ" msgid "Switch to Diablo" msgstr "ディアブロ​に​切り替え" -#: Source/DiabloUI/selyesno.cpp:68 Source/stores.cpp:967 +#: Source/DiabloUI/selyesno.cpp:72 Source/stores.cpp:940 msgid "Yes" msgstr "はい" -#: Source/DiabloUI/selyesno.cpp:69 Source/stores.cpp:968 +#: Source/DiabloUI/selyesno.cpp:73 Source/stores.cpp:941 msgid "No" msgstr "いいえ" -#: Source/DiabloUI/settingsmenu.cpp:162 +#: Source/DiabloUI/settingsmenu.cpp:170 msgid "Press gamepad buttons to change." msgstr "ゲーム​パッド​の​ボタン​を​押し​て​変更。" -#: Source/DiabloUI/settingsmenu.cpp:439 +#: Source/DiabloUI/settingsmenu.cpp:447 msgid "Bound key:" msgstr "キーバインド:" -#: Source/DiabloUI/settingsmenu.cpp:488 +#: Source/DiabloUI/settingsmenu.cpp:496 msgid "Press any key to change." msgstr "いずれ​か​の​キー​を​押す​と​変更​さ​れ​ます。" -#: Source/DiabloUI/settingsmenu.cpp:490 +#: Source/DiabloUI/settingsmenu.cpp:498 msgid "Unbind key" msgstr "キーバインド​解除" -#: Source/DiabloUI/settingsmenu.cpp:494 +#: Source/DiabloUI/settingsmenu.cpp:502 msgid "Bound button combo:" msgstr "コンボ​ボタン​の​バインド:" -#: Source/DiabloUI/settingsmenu.cpp:503 +#: Source/DiabloUI/settingsmenu.cpp:511 msgid "Unbind button combo" msgstr "コンボ​ボタン​の​バインド​解除" -#: Source/DiabloUI/settingsmenu.cpp:547 Source/gamemenu.cpp:73 +#: Source/DiabloUI/settingsmenu.cpp:555 Source/gamemenu.cpp:77 msgid "Previous Menu" msgstr "前​の​メニュー" @@ -871,16 +881,16 @@ msgstr "" "Twitmoji​を​使用​し​て​い​ます。また、この​移植​版​で​は、zlib-license​で​ライセンス​さ​れ​" "て​いる​SDL​も​使用​し​て​い​ます。詳細​は、ReadMe​を​ご覧​ください。" -#: Source/DiabloUI/title.cpp:67 +#: Source/DiabloUI/title.cpp:74 msgid "Copyright © 1996-2001 Blizzard Entertainment" msgstr "Copyright © 1996-2001 Blizzard Entertainment" -#: Source/appfat.cpp:63 +#: Source/appfat.cpp:72 msgid "Error" msgstr "Error" #. TRANSLATORS: Error message that displays relevant information for bug report -#: Source/appfat.cpp:77 +#: Source/appfat.cpp:86 #, c++-format msgid "" "{:s}\n" @@ -891,11 +901,11 @@ msgstr "" "\n" "The error occurred at: {:s} line {:d}" -#: Source/appfat.cpp:83 +#: Source/appfat.cpp:92 msgid "Data File Error" msgstr "データファイル​・​エラー" -#: Source/appfat.cpp:84 +#: Source/appfat.cpp:93 #, c++-format msgid "" "Unable to open main data archive ({:s}).\n" @@ -906,12 +916,12 @@ msgstr "" "\n" "​ゲーム​フォルダ​に​入っ​て​いる​か​確認​し​て​ください。" -#: Source/appfat.cpp:93 +#: Source/appfat.cpp:102 msgid "Read-Only Directory Error" msgstr "読み取り専用​ディレクトリ​エラー" #. TRANSLATORS: Error when Program is not allowed to write data -#: Source/appfat.cpp:94 +#: Source/appfat.cpp:103 #, c++-format msgid "" "Unable to write to location:\n" @@ -920,98 +930,61 @@ msgstr "" "書き込み​が​でき​ませ​ん:\n" "{:s}" -#: Source/automap.cpp:1416 +#: Source/automap.cpp:1429 msgid "Game: " msgstr "ゲーム: " -#: Source/automap.cpp:1424 +#: Source/automap.cpp:1436 msgid "Offline Game" msgstr "オフライン​ゲーム" -#: Source/automap.cpp:1426 -msgid "Password: " -msgstr "パスワード: " - -#: Source/automap.cpp:1429 +#: Source/automap.cpp:1438 msgid "Public Game" msgstr "パブリック​ゲーム" -#: Source/automap.cpp:1443 +#: Source/automap.cpp:1440 +msgid "Password: " +msgstr "パスワード: " + +#: Source/automap.cpp:1452 #, c++-format msgid "Level: Nest {:d}" msgstr "レベル: 巣 {:d}" -#: Source/automap.cpp:1446 +#: Source/automap.cpp:1455 #, c++-format msgid "Level: Crypt {:d}" msgstr "レベル: 地下​聖堂 {:d}" -#: Source/automap.cpp:1449 Source/discord/discord.cpp:81 Source/objects.cpp:157 +#: Source/automap.cpp:1458 Source/discord/discord.cpp:81 Source/objects.cpp:156 msgid "Town" msgstr "タウン" -#: Source/automap.cpp:1452 +#: Source/automap.cpp:1461 #, c++-format msgid "Level: {:d}" msgstr "レベル: {:d}" -#: Source/control.cpp:203 -msgid "Tab" -msgstr "Tab" - -#: Source/control.cpp:203 -msgid "Esc" -msgstr "Esc" - -#: Source/control.cpp:203 -msgid "Enter" -msgstr "入力" - -#: Source/control.cpp:206 -msgid "Character Information" -msgstr "キャラクター​情報" - -#: Source/control.cpp:207 -msgid "Quests log" -msgstr "クエスト​の​記録" - -#: Source/control.cpp:208 -msgid "Automap" -msgstr "オート​マップ" - -#: Source/control.cpp:209 -msgid "Main Menu" -msgstr "メインメニュー" - -#: Source/control.cpp:210 Source/diablo.cpp:1912 Source/diablo.cpp:2264 -msgid "Inventory" -msgstr "所持品​の​一覧" - -#: Source/control.cpp:211 -msgid "Spell book" -msgstr "スペル​ブック" - -#: Source/control.cpp:212 -msgid "Send Message" -msgstr "メッセージ​送信" - -#: Source/control.cpp:622 +#: Source/control/control_chat_commands.cpp:36 msgid "Available Commands:" msgstr "使用​できる​コマンド: " -#: Source/control.cpp:630 Source/control.cpp:814 +#: Source/control/control_chat_commands.cpp:44 +#: Source/control/control_chat_commands.cpp:266 msgid "Command " msgstr "コマンド " -#: Source/control.cpp:630 Source/control.cpp:814 +#: Source/control/control_chat_commands.cpp:44 +#: Source/control/control_chat_commands.cpp:266 msgid " is unknown." msgstr "は​不明。" -#: Source/control.cpp:633 Source/control.cpp:634 +#: Source/control/control_chat_commands.cpp:47 +#: Source/control/control_chat_commands.cpp:48 msgid "Description: " msgstr "説明: " -#: Source/control.cpp:633 +#: Source/control/control_chat_commands.cpp:47 msgid "" "\n" "Parameters: No additional parameter needed." @@ -1019,7 +992,7 @@ msgstr "" "\n" "​パラメータ: 追加​の​パラメータ​は​必要​あり​ませ​ん。" -#: Source/control.cpp:634 +#: Source/control/control_chat_commands.cpp:48 msgid "" "\n" "Parameters: " @@ -1027,219 +1000,290 @@ msgstr "" "\n" "​パラメータ: " -#: Source/control.cpp:648 Source/control.cpp:680 +#: Source/control/control_chat_commands.cpp:62 +#: Source/control/control_chat_commands.cpp:94 msgid "Arenas are only supported in multiplayer." msgstr "アリーナ​は​マルチ​プレイ​のみ​対応​し​て​い​ます。" -#: Source/control.cpp:653 +#: Source/control/control_chat_commands.cpp:67 msgid "What arena do you want to visit?" msgstr "行っ​て​み​たい​アリーナ​は​どこ​です​か?" -#: Source/control.cpp:661 +#: Source/control/control_chat_commands.cpp:75 msgid "Invalid arena-number. Valid numbers are:" msgstr "アリーナ​番号​が​無効​です。有効​な​番号​は​次​の​とおり​です: " -#: Source/control.cpp:667 +#: Source/control/control_chat_commands.cpp:81 msgid "To enter a arena, you need to be in town or another arena." msgstr "アリーナ​に​入る​に​は、街​か​他​の​アリーナ​に​いる​必要​が​あり​ます。" -#: Source/control.cpp:705 +#: Source/control/control_chat_commands.cpp:119 msgid "Inspecting only supported in multiplayer." msgstr "インスペクト​は​マルチプレーヤー​のみ​対応。" -#: Source/control.cpp:710 Source/control.cpp:1001 +#: Source/control/control_chat_commands.cpp:124 +#: Source/control/control_panel.cpp:316 msgid "Stopped inspecting players." msgstr "プレイヤー​の​インスペク​と​を​中止。" -#: Source/control.cpp:725 +#: Source/control/control_chat_commands.cpp:139 +#: Source/control/control_chat_commands.cpp:226 msgid "No players found with such a name" msgstr "ぞ​の​よう​な​名前​の​プレイヤー​は​見つかり​ませ​ん​でし​た" -#: Source/control.cpp:731 +#: Source/control/control_chat_commands.cpp:145 msgid "Inspecting player: " msgstr "プレイヤー​の​インスペクト : " -#: Source/control.cpp:800 +#. TRANSLATORS: {:s} means: Character Name +#: Source/control/control_chat_commands.cpp:233 +#, c++-format +msgid "Latency statistics for {:s}:" +msgstr "{:s} の​遅延​統計​:" + +#. TRANSLATORS: Network connectivity statistics +#: Source/control/control_chat_commands.cpp:235 Source/diablo.cpp:1578 +#, c++-format +msgid "Echo latency: {:d} ms" +msgstr "エコー​遅延​:​{:d} ms" + +#. TRANSLATORS: Network connectivity statistics +#: Source/control/control_chat_commands.cpp:239 Source/diablo.cpp:1582 +#, c++-format +msgid "Provider latency: {:d} ms (Relayed)" +msgstr "プロバイダ​遅延​:​{:d} ms​(​リレー​経由​)" + +#. TRANSLATORS: Network connectivity statistics +#: Source/control/control_chat_commands.cpp:241 Source/diablo.cpp:1584 +#, c++-format +msgid "Provider latency: {:d} ms" +msgstr "プロバイダ​遅延​:​{:d} ms" + +#: Source/control/control_chat_commands.cpp:249 msgid "Prints help overview or help for a specific command." msgstr "ヘルプ​の​概要​また​は​特定​の​コマンド​の​ヘルプ​を​表示​し​ます。" -#: Source/control.cpp:800 +#: Source/control/control_chat_commands.cpp:249 msgid "[command]" msgstr "[​コマンド​]" -#: Source/control.cpp:801 +#: Source/control/control_chat_commands.cpp:250 msgid "Enter a PvP Arena." msgstr "PvP​アリーナ​に​入る。" -#: Source/control.cpp:801 +#: Source/control/control_chat_commands.cpp:250 msgid "" msgstr "<​アリーナ​番号​>" -#: Source/control.cpp:802 +#: Source/control/control_chat_commands.cpp:251 msgid "Gives Arena Potions." msgstr "アリーナ​ポーション​を​与える。" -#: Source/control.cpp:802 +#: Source/control/control_chat_commands.cpp:251 msgid "" msgstr "<​番号​>" -#: Source/control.cpp:803 +#: Source/control/control_chat_commands.cpp:252 msgid "Inspects stats and equipment of another player." msgstr "他​の​プレーヤー​の​体力​や​装備​を​調べる。" -#: Source/control.cpp:803 +#: Source/control/control_chat_commands.cpp:252 +#: Source/control/control_chat_commands.cpp:254 msgid "" msgstr "<​プレイヤー​名​>" -#: Source/control.cpp:804 +#: Source/control/control_chat_commands.cpp:253 msgid "Show seed infos for current level." msgstr "現在​の​レベル​の​シード​情報​を​表示。" -#: Source/control.cpp:1311 +#: Source/control/control_chat_commands.cpp:254 +msgid "Show latency statistics for another player." +msgstr "他​の​プレイヤー​の​遅延​統計​を​表示​し​ます。" + +#. TRANSLATORS: {:s} is a number with separators. Dialog is shown when splitting a stash of Gold. +#: Source/control/control_gold.cpp:60 +#, c++-format +msgid "You have {:s} gold piece. How many do you want to remove?" +msgid_plural "You have {:s} gold pieces. How many do you want to remove?" +msgstr[0] "あなたは{:s}ゴールドを持っています。何枚削除しますか?" + +#: Source/control/control_infobox.cpp:295 msgid "Player friendly" msgstr "プレイヤー​は​味方" -#: Source/control.cpp:1313 +#: Source/control/control_infobox.cpp:297 msgid "Player attack" msgstr "プレイヤー​の​攻撃" -#: Source/control.cpp:1316 +#: Source/control/control_infobox.cpp:300 #, c++-format msgid "Hotkey: {:s}" msgstr "ホットキー: {:s}" -#: Source/control.cpp:1328 +#: Source/control/control_infobox.cpp:312 msgid "Select current spell button" msgstr "正しい​呪文​ボタン​を​選択​し​て​ください" -#: Source/control.cpp:1331 +#: Source/control/control_infobox.cpp:315 msgid "Hotkey: 's'" msgstr "ホットキー: ’s’" -#: Source/control.cpp:1337 Source/panels/spell_list.cpp:153 +#: Source/control/control_infobox.cpp:321 Source/panels/spell_list.cpp:153 #, c++-format msgid "{:s} Skill" msgstr "{:s} スキル" -#: Source/control.cpp:1340 Source/panels/spell_list.cpp:160 +#: Source/control/control_infobox.cpp:324 Source/panels/spell_list.cpp:160 #, c++-format msgid "{:s} Spell" msgstr "{:s} スペル" -#: Source/control.cpp:1342 Source/panels/spell_list.cpp:165 +#: Source/control/control_infobox.cpp:326 Source/panels/spell_list.cpp:165 msgid "Spell Level 0 - Unusable" msgstr "スペル​レベル 0 - 使え​ない" -#: Source/control.cpp:1342 Source/panels/spell_list.cpp:167 +#: Source/control/control_infobox.cpp:326 Source/panels/spell_list.cpp:167 #, c++-format msgid "Spell Level {:d}" msgstr "スペル​レベル {:d}" -#: Source/control.cpp:1345 Source/panels/spell_list.cpp:174 +#: Source/control/control_infobox.cpp:329 Source/panels/spell_list.cpp:174 #, c++-format msgid "Scroll of {:s}" msgstr "{:s}​の​スクロール" -#: Source/control.cpp:1349 Source/panels/spell_list.cpp:178 +#: Source/control/control_infobox.cpp:333 Source/panels/spell_list.cpp:178 #, c++-format msgid "{:d} Scroll" msgid_plural "{:d} Scrolls" msgstr[0] "{:d} スクロール" -#: Source/control.cpp:1352 Source/panels/spell_list.cpp:185 +#: Source/control/control_infobox.cpp:336 Source/panels/spell_list.cpp:185 #, c++-format msgid "Staff of {:s}" msgstr "{:s}​・​スタッフ" -#: Source/control.cpp:1353 Source/panels/spell_list.cpp:187 +#: Source/control/control_infobox.cpp:337 Source/panels/spell_list.cpp:187 #, c++-format msgid "{:d} Charge" msgid_plural "{:d} Charges" msgstr[0] "{:d}チャージ" -#: Source/control.cpp:1487 Source/inv.cpp:1979 Source/inv.cpp:1980 -#: Source/items.cpp:3808 +#: Source/control/control_infobox.cpp:369 Source/inv.cpp:1986 +#: Source/inv.cpp:1987 Source/items.cpp:3815 #, c++-format msgid "{:s} gold piece" msgid_plural "{:s} gold pieces" msgstr[0] "{:s} ゴールド" -#: Source/control.cpp:1489 +#: Source/control/control_infobox.cpp:371 msgid "Requirements not met" msgstr "能力​値​が​不足​し​て​い​ます" -#: Source/control.cpp:1518 +#: Source/control/control_infobox.cpp:400 #, c++-format msgid "{:s}, Level: {:d}" msgstr "{:s}, レベル: {:d}" -#: Source/control.cpp:1519 +#: Source/control/control_infobox.cpp:401 #, c++-format msgid "Hit Points {:d} of {:d}" msgstr "ヒット​ポイント: {:d}/{:d}" -#: Source/control.cpp:1525 -#, fuzzy -#| msgid "Right-click to use" +#: Source/control/control_infobox.cpp:407 msgid "Right click to inspect" -msgstr "右​クリック​で​使用" +msgstr "右​クリック​で​詳細​を​表示" + +#: Source/control/control_panel.cpp:106 +msgid "Character Information" +msgstr "キャラクター​情報" + +#: Source/control/control_panel.cpp:107 +msgid "Quests log" +msgstr "クエスト​の​記録" + +#: Source/control/control_panel.cpp:108 +msgid "Automap" +msgstr "オート​マップ" + +#: Source/control/control_panel.cpp:109 +msgid "Main Menu" +msgstr "メインメニュー" + +#: Source/control/control_panel.cpp:110 Source/diablo.cpp:1948 +#: Source/diablo.cpp:2300 +msgid "Inventory" +msgstr "所持品​の​一覧" + +#: Source/control/control_panel.cpp:111 +msgid "Spell book" +msgstr "スペル​ブック" + +#: Source/control/control_panel.cpp:112 +msgid "Send Message" +msgstr "メッセージ​送信" + +#: Source/control/control_panel.cpp:117 +msgid "Tab" +msgstr "Tab" + +#: Source/control/control_panel.cpp:117 +msgid "Esc" +msgstr "Esc" + +#: Source/control/control_panel.cpp:117 +msgid "Enter" +msgstr "入力" -#: Source/control.cpp:1573 +#: Source/control/control_panel.cpp:673 msgid "Level Up" msgstr "レベルアップ" -#: Source/control.cpp:1687 +#: Source/control/control_panel.cpp:795 msgid "You have died" -msgstr "" +msgstr "あなた​は​死に​まし​た" -#: Source/control.cpp:1695 +#: Source/control/control_panel.cpp:803 msgid "ESC" -msgstr "" +msgstr "ESC" -#: Source/control.cpp:1701 +#: Source/control/control_panel.cpp:809 msgid "Menu Button" -msgstr "" +msgstr "メニュー​ボタン" -#: Source/control.cpp:1709 +#: Source/control/control_panel.cpp:817 #, c++-format msgid "Press {} to load last save." -msgstr "" +msgstr "{} を​押す​と、最後​の​セーブ​データ​を​読み込み​ます。" -#: Source/control.cpp:1711 +#: Source/control/control_panel.cpp:819 #, c++-format msgid "Press {} to return to Main Menu." -msgstr "" +msgstr "{} を​押す​と、メインメニュー​に​戻り​ます。" -#: Source/control.cpp:1714 +#: Source/control/control_panel.cpp:822 #, c++-format msgid "Press {} to restart in town." -msgstr "" - -#. TRANSLATORS: {:s} is a number with separators. Dialog is shown when splitting a stash of Gold. -#: Source/control.cpp:1732 -#, c++-format -msgid "You have {:s} gold piece. How many do you want to remove?" -msgid_plural "You have {:s} gold pieces. How many do you want to remove?" -msgstr[0] "あなたは{:s}ゴールドを持っています。何枚削除しますか?" +msgstr "{} を​押す​と、町​で​再開​し​ます。" -#: Source/cursor.cpp:621 +#: Source/cursor.cpp:629 msgid "Town Portal" msgstr "タウン​ポータル" -#: Source/cursor.cpp:622 +#: Source/cursor.cpp:630 #, c++-format msgid "from {:s}" msgstr "{:s}​から" -#: Source/cursor.cpp:635 +#: Source/cursor.cpp:643 msgid "Portal to" msgstr "ポータル" -#: Source/cursor.cpp:636 +#: Source/cursor.cpp:644 msgid "The Unholy Altar" msgstr "ラザルス​の​間" -#: Source/cursor.cpp:636 +#: Source/cursor.cpp:644 msgid "level 15" msgstr "レベル​15" @@ -1284,104 +1328,104 @@ msgid "Invalid value {0} for {1} in {2} at row {3} and column {4}" msgstr "{2} の​行 {3} 列 {4} に​おけ​る {1} の​値 {0} は​無効​です。" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:989 +#: Source/diablo.cpp:1003 msgid "Print this message and exit" msgstr "Print this message and exit" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:990 +#: Source/diablo.cpp:1004 msgid "Print the version and exit" msgstr "Print the version and exit" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:991 +#: Source/diablo.cpp:1005 msgid "Specify the folder of diabdat.mpq" msgstr "Specify the folder of diabdat.mpq" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:992 +#: Source/diablo.cpp:1006 msgid "Specify the folder of save files" msgstr "Specify the folder of save files" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:993 +#: Source/diablo.cpp:1007 msgid "Specify the location of diablo.ini" msgstr "Specify the location of diablo.ini" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:994 +#: Source/diablo.cpp:1008 msgid "Specify the language code (e.g. en or pt_BR)" msgstr "Specify the language code (​e.g. en or pt_BR​)" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:995 +#: Source/diablo.cpp:1009 msgid "Skip startup videos" msgstr "Skip startup videos" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:996 +#: Source/diablo.cpp:1010 msgid "Display frames per second" msgstr "Display frames per second" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:997 +#: Source/diablo.cpp:1011 msgid "Enable verbose logging" msgstr "Enable verbose logging" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:999 +#: Source/diablo.cpp:1013 msgid "Log to a file instead of stderr" -msgstr "" +msgstr "Log to a file instead of stderr" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:1002 +#: Source/diablo.cpp:1016 msgid "Record a demo file" msgstr "Record a demo file" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:1003 +#: Source/diablo.cpp:1017 msgid "Play a demo file" msgstr "Play a demo file" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:1004 +#: Source/diablo.cpp:1018 msgid "Disable all frame limiting during demo playback" msgstr "Disable all frame limiting during demo playback" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:1007 +#: Source/diablo.cpp:1021 msgid "Game selection:" msgstr "Game selection:" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:1009 +#: Source/diablo.cpp:1023 msgid "Force Shareware mode" msgstr "Force Shareware mode" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:1010 +#: Source/diablo.cpp:1024 msgid "Force Diablo mode" msgstr "Force Diablo mode" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:1011 +#: Source/diablo.cpp:1025 msgid "Force Hellfire mode" msgstr "Force Hellfire mode" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:1012 +#: Source/diablo.cpp:1026 msgid "Hellfire options:" msgstr "Hellfire options:" -#: Source/diablo.cpp:1022 +#: Source/diablo.cpp:1036 msgid "Report bugs at https://github.com/diasurgical/devilutionX/" msgstr "Report bugs at https://github.com/diasurgical/devilutionX/" -#: Source/diablo.cpp:1202 +#: Source/diablo.cpp:1216 msgid "Please update devilutionx.mpq and fonts.mpq to the latest version" msgstr "devilutionx.mpq​と​fonts.mpq​を​最新​の​バージョン​に​更新​し​て​ください。" -#: Source/diablo.cpp:1204 +#: Source/diablo.cpp:1218 msgid "" "Failed to load UI resources.\n" "\n" @@ -1392,740 +1436,732 @@ msgstr "" "devilutionx.mpq​が​ゲーム​フォルダ​内​に​あり、最新​の​状態​で​ある​こと​を​確認​し​て​くださ" "い。" -#: Source/diablo.cpp:1208 +#: Source/diablo.cpp:1222 msgid "Please update fonts.mpq to the latest version" msgstr "fonts.mpq​を​最新​の​バージョン​に​更新​し​て​ください。" -#: Source/diablo.cpp:1551 +#: Source/diablo.cpp:1565 msgid "-- Network timeout --" msgstr "— Network timeout —" -#: Source/diablo.cpp:1552 +#: Source/diablo.cpp:1566 msgid "-- Waiting for players --" msgstr "— Waiting for players —" +#. TRANSLATORS: {:s} means: Character Name #: Source/diablo.cpp:1575 +#, c++-format +msgid "Player {:s} is timing out!" +msgstr "プレイヤー {:s} が​タイムアウト​し​て​い​ます!" + +#: Source/diablo.cpp:1611 msgid "No help available" msgstr "ヘルプ​は​使え​ませ​ん" -#: Source/diablo.cpp:1576 +#: Source/diablo.cpp:1612 msgid "while in stores" msgstr "店​で" -#: Source/diablo.cpp:1774 Source/diablo.cpp:2094 +#: Source/diablo.cpp:1810 Source/diablo.cpp:2130 #, c++-format msgid "Belt item {}" msgstr "ベルト​アイテム {}" -#: Source/diablo.cpp:1775 Source/diablo.cpp:2095 +#: Source/diablo.cpp:1811 Source/diablo.cpp:2131 msgid "Use Belt item." msgstr "ベルト​アイテム​を​使い​ます。" -#: Source/diablo.cpp:1790 Source/diablo.cpp:2110 +#: Source/diablo.cpp:1826 Source/diablo.cpp:2146 #, c++-format msgid "Quick spell {}" msgstr "クイック​スペル {}" -#: Source/diablo.cpp:1791 Source/diablo.cpp:2111 +#: Source/diablo.cpp:1827 Source/diablo.cpp:2147 msgid "Hotkey for skill or spell." msgstr "スキル​や​スペル​の​ホットキー​です。" -#: Source/diablo.cpp:1809 +#: Source/diablo.cpp:1845 msgid "Previous quick spell" msgstr "前​の​クイック​スペル" -#: Source/diablo.cpp:1810 +#: Source/diablo.cpp:1846 msgid "Selects the previous quick spell (cycles)." msgstr "前​の​クイック​スペル​を​選択​し​ます​(​サイクル​し​ます​)。" -#: Source/diablo.cpp:1817 +#: Source/diablo.cpp:1853 msgid "Next quick spell" msgstr "次​の​クイック​スペル" -#: Source/diablo.cpp:1818 +#: Source/diablo.cpp:1854 msgid "Selects the next quick spell (cycles)." msgstr "次​の​クイック​スペル​を​選択​し​ます​(​サイクル​し​ます​)。" -#: Source/diablo.cpp:1825 Source/diablo.cpp:2238 +#: Source/diablo.cpp:1861 Source/diablo.cpp:2274 msgid "Use health potion" msgstr "ヘルス​ポーション​を​使用​する" -#: Source/diablo.cpp:1826 Source/diablo.cpp:2239 +#: Source/diablo.cpp:1862 Source/diablo.cpp:2275 msgid "Use health potions from belt." msgstr "ベルト​から​ヘルス​ポーション​を​使用​する。" -#: Source/diablo.cpp:1833 Source/diablo.cpp:2246 +#: Source/diablo.cpp:1869 Source/diablo.cpp:2282 msgid "Use mana potion" msgstr "マナポーション​を​使用​する" -#: Source/diablo.cpp:1834 Source/diablo.cpp:2247 +#: Source/diablo.cpp:1870 Source/diablo.cpp:2283 msgid "Use mana potions from belt." msgstr "ベルト​から​マナポーション​を​使用​する。" -#: Source/diablo.cpp:1841 Source/diablo.cpp:2294 +#: Source/diablo.cpp:1877 Source/diablo.cpp:2330 msgid "Speedbook" msgstr "スピード​ブック" -#: Source/diablo.cpp:1842 Source/diablo.cpp:2295 +#: Source/diablo.cpp:1878 Source/diablo.cpp:2331 msgid "Open Speedbook." msgstr "スピード​ブック​を​開き​ます。" -#: Source/diablo.cpp:1849 Source/diablo.cpp:2451 +#: Source/diablo.cpp:1885 Source/diablo.cpp:2487 msgid "Quick save" msgstr "クイック​セーブ" -#: Source/diablo.cpp:1850 Source/diablo.cpp:2452 +#: Source/diablo.cpp:1886 Source/diablo.cpp:2488 msgid "Saves the game." msgstr "ゲーム​を​セーブ​し​ます。" -#: Source/diablo.cpp:1857 Source/diablo.cpp:2459 +#: Source/diablo.cpp:1893 Source/diablo.cpp:2495 msgid "Quick load" msgstr "クイック​ロード" -#: Source/diablo.cpp:1858 Source/diablo.cpp:2460 +#: Source/diablo.cpp:1894 Source/diablo.cpp:2496 msgid "Loads the game." msgstr "ゲーム​を​ロード​し​ます。" -#: Source/diablo.cpp:1866 +#: Source/diablo.cpp:1902 msgid "Quit game" msgstr "ゲーム​の​終了" -#: Source/diablo.cpp:1867 +#: Source/diablo.cpp:1903 msgid "Closes the game." msgstr "ゲーム​を​終了​し​ます。" -#: Source/diablo.cpp:1873 +#: Source/diablo.cpp:1909 msgid "Stop hero" msgstr "ヒーロー​停止" -#: Source/diablo.cpp:1874 +#: Source/diablo.cpp:1910 msgid "Stops walking and cancel pending actions." msgstr "歩行​を​停止​し、保留中​の​アクション​を​キャンセル​し​ます。" -#: Source/diablo.cpp:1881 Source/diablo.cpp:2467 +#: Source/diablo.cpp:1917 Source/diablo.cpp:2503 msgid "Item highlighting" msgstr "アイテム​の​強調​表示" -#: Source/diablo.cpp:1882 Source/diablo.cpp:2468 +#: Source/diablo.cpp:1918 Source/diablo.cpp:2504 msgid "Show/hide items on ground." msgstr "地上​に​ある​アイテム​の​表示​/​非表示​を​切り替え​ます。" -#: Source/diablo.cpp:1888 Source/diablo.cpp:2474 +#: Source/diablo.cpp:1924 Source/diablo.cpp:2510 msgid "Toggle item highlighting" msgstr "アイテム​の​ハイライト​の​切り替え" -#: Source/diablo.cpp:1889 Source/diablo.cpp:2475 +#: Source/diablo.cpp:1925 Source/diablo.cpp:2511 msgid "Permanent show/hide items on ground." msgstr "地上​に​ある​アイテム​の​常時​表示​/​非表示​を​切り替え​ます。" -#: Source/diablo.cpp:1895 Source/diablo.cpp:2304 +#: Source/diablo.cpp:1931 Source/diablo.cpp:2340 msgid "Toggle automap" msgstr "オート​マップ​の​切り替え" -#: Source/diablo.cpp:1896 Source/diablo.cpp:2305 +#: Source/diablo.cpp:1932 Source/diablo.cpp:2341 msgid "Toggles if automap is displayed." msgstr "オート​マップ​を​表示​する​か​どう​か​を​切り替え​ます。" -#: Source/diablo.cpp:1903 +#: Source/diablo.cpp:1939 msgid "Cycle map type" msgstr "マップ​タイプ​の​切替" -#: Source/diablo.cpp:1904 +#: Source/diablo.cpp:1940 msgid "Opaque -> Transparent -> Minimap -> None" msgstr "不​透過 -​> 透過 -​> ミニマップ -​> なし" -#: Source/diablo.cpp:1913 Source/diablo.cpp:2265 +#: Source/diablo.cpp:1949 Source/diablo.cpp:2301 msgid "Open Inventory screen." msgstr "インベントリ​画面​を​開き​ます。" -#: Source/diablo.cpp:1920 Source/diablo.cpp:2254 +#: Source/diablo.cpp:1956 Source/diablo.cpp:2290 msgid "Character" msgstr "キャラクター" -#: Source/diablo.cpp:1921 Source/diablo.cpp:2255 +#: Source/diablo.cpp:1957 Source/diablo.cpp:2291 msgid "Open Character screen." msgstr "キャラクター​画面​を​開き​ます。" -#: Source/diablo.cpp:1928 +#: Source/diablo.cpp:1964 msgid "Party" -msgstr "" +msgstr "パーティー" -#: Source/diablo.cpp:1929 +#: Source/diablo.cpp:1965 msgid "Open side Party panel." -msgstr "" +msgstr "パーティー​の​サイド​パネル​を​開く。" -#: Source/diablo.cpp:1936 Source/diablo.cpp:2274 +#: Source/diablo.cpp:1972 Source/diablo.cpp:2310 msgid "Quest log" msgstr "クエストログ" -#: Source/diablo.cpp:1937 Source/diablo.cpp:2275 +#: Source/diablo.cpp:1973 Source/diablo.cpp:2311 msgid "Open Quest log." msgstr "クエストログ​を​開き​ます。" -#: Source/diablo.cpp:1944 Source/diablo.cpp:2284 +#: Source/diablo.cpp:1980 Source/diablo.cpp:2320 msgid "Spellbook" msgstr "スペル​ブック" -#: Source/diablo.cpp:1945 Source/diablo.cpp:2285 +#: Source/diablo.cpp:1981 Source/diablo.cpp:2321 msgid "Open Spellbook." msgstr "スペル​ブック​を​開き​ます。" -#: Source/diablo.cpp:1953 +#: Source/diablo.cpp:1989 #, c++-format msgid "Quick Message {}" msgstr "クイック​メッセージ {}" -#: Source/diablo.cpp:1954 +#: Source/diablo.cpp:1990 msgid "Use Quick Message in chat." msgstr "チャット​で​クイック​メッセージ​を​使用​し​ます。" -#: Source/diablo.cpp:1963 Source/diablo.cpp:2481 +#: Source/diablo.cpp:1999 Source/diablo.cpp:2517 msgid "Hide Info Screens" msgstr "情報​画面​の​非表示" -#: Source/diablo.cpp:1964 Source/diablo.cpp:2482 +#: Source/diablo.cpp:2000 Source/diablo.cpp:2518 msgid "Hide all info screens." msgstr "すべて​の​情報​画面​を​非表示​に​し​ます。" -#: Source/diablo.cpp:1987 Source/diablo.cpp:2505 Source/options.cpp:737 +#: Source/diablo.cpp:2023 Source/diablo.cpp:2541 Source/options.cpp:788 msgid "Zoom" msgstr "拡大" -#: Source/diablo.cpp:1988 Source/diablo.cpp:2506 +#: Source/diablo.cpp:2024 Source/diablo.cpp:2542 msgid "Zoom Game Screen." msgstr "ゲーム​画面​を​拡大​し​ます。" -#: Source/diablo.cpp:1998 Source/diablo.cpp:2516 +#: Source/diablo.cpp:2034 Source/diablo.cpp:2552 msgid "Pause Game" msgstr "ゲーム​の​一時停止" -#: Source/diablo.cpp:1999 Source/diablo.cpp:2005 Source/diablo.cpp:2517 +#: Source/diablo.cpp:2035 Source/diablo.cpp:2041 Source/diablo.cpp:2553 msgid "Pauses the game." msgstr "ゲーム​を​一時停止​し​ます。" -#: Source/diablo.cpp:2004 +#: Source/diablo.cpp:2040 msgid "Pause Game (Alternate)" msgstr "ゲーム​の​一時停止" -#: Source/diablo.cpp:2010 Source/diablo.cpp:2522 -#, fuzzy -#| msgid "Increase screen brightness." +#: Source/diablo.cpp:2046 Source/diablo.cpp:2558 msgid "Decrease Brightness" -msgstr "画面​の​輝度​を​上げ​ます。" +msgstr "明るさ​を​下げる" -#: Source/diablo.cpp:2011 Source/diablo.cpp:2523 +#: Source/diablo.cpp:2047 Source/diablo.cpp:2559 msgid "Reduce screen brightness." msgstr "画面​の​輝度​を​下げ​ます。" -#: Source/diablo.cpp:2018 Source/diablo.cpp:2530 -#, fuzzy -#| msgid "Increase screen brightness." +#: Source/diablo.cpp:2054 Source/diablo.cpp:2566 msgid "Increase Brightness" -msgstr "画面​の​輝度​を​上げ​ます。" +msgstr "明るさ​を​上げる" -#: Source/diablo.cpp:2019 Source/diablo.cpp:2531 +#: Source/diablo.cpp:2055 Source/diablo.cpp:2567 msgid "Increase screen brightness." msgstr "画面​の​輝度​を​上げ​ます。" -#: Source/diablo.cpp:2026 Source/diablo.cpp:2538 +#: Source/diablo.cpp:2062 Source/diablo.cpp:2574 msgid "Help" msgstr "ヘルプ" -#: Source/diablo.cpp:2027 Source/diablo.cpp:2539 +#: Source/diablo.cpp:2063 Source/diablo.cpp:2575 msgid "Open Help Screen." msgstr "ヘルプ​画面​を​開き​ます。" -#: Source/diablo.cpp:2034 Source/diablo.cpp:2546 +#: Source/diablo.cpp:2070 Source/diablo.cpp:2582 msgid "Screenshot" msgstr "スクリーンショット" -#: Source/diablo.cpp:2035 Source/diablo.cpp:2547 +#: Source/diablo.cpp:2071 Source/diablo.cpp:2583 msgid "Takes a screenshot." msgstr "スクリーンショット​を​撮り​ます。" -#: Source/diablo.cpp:2041 Source/diablo.cpp:2553 +#: Source/diablo.cpp:2077 Source/diablo.cpp:2589 msgid "Game info" msgstr "ゲーム​情報" -#: Source/diablo.cpp:2042 Source/diablo.cpp:2554 +#: Source/diablo.cpp:2078 Source/diablo.cpp:2590 msgid "Displays game infos." msgstr "ゲーム​情報​を​表示​し​ます。" -#. TRANSLATORS: {:s} means: Character Name, Game Version, Game Difficulty. -#: Source/diablo.cpp:2046 Source/diablo.cpp:2558 +#. TRANSLATORS: {:s} means: Project Name, Game Version. +#: Source/diablo.cpp:2082 Source/diablo.cpp:2594 #, c++-format msgid "{:s} {:s}" msgstr "{:s} {:s}" -#: Source/diablo.cpp:2055 Source/diablo.cpp:2575 +#: Source/diablo.cpp:2091 Source/diablo.cpp:2611 msgid "Chat Log" msgstr "チャットログ" -#: Source/diablo.cpp:2056 Source/diablo.cpp:2576 +#: Source/diablo.cpp:2092 Source/diablo.cpp:2612 msgid "Displays chat log." msgstr "チャットログ​を​表示​し​ます。" -#: Source/diablo.cpp:2063 Source/diablo.cpp:2567 +#: Source/diablo.cpp:2099 Source/diablo.cpp:2603 msgid "Sort Inventory" msgstr "所持品​の​一覧" -#: Source/diablo.cpp:2064 Source/diablo.cpp:2568 +#: Source/diablo.cpp:2100 Source/diablo.cpp:2604 msgid "Sorts the inventory." msgstr "インベントリ​を​整理​し​ます。" -#: Source/diablo.cpp:2072 +#: Source/diablo.cpp:2108 msgid "Console" msgstr "コンソール" -#: Source/diablo.cpp:2073 +#: Source/diablo.cpp:2109 msgid "Opens Lua console." msgstr "Lua​コンソール​を​開き​ます。" -#: Source/diablo.cpp:2129 +#: Source/diablo.cpp:2165 msgid "Primary action" msgstr "主​アクション" -#: Source/diablo.cpp:2130 +#: Source/diablo.cpp:2166 msgid "Attack monsters, talk to towners, lift and place inventory items." msgstr "" "モンスター​を​攻撃​し​たり、街​の​人​に​話しかけ​たり、インベントリー​アイテム​を​持ち上" "げ​て​置い​たり。" -#: Source/diablo.cpp:2144 +#: Source/diablo.cpp:2180 msgid "Secondary action" msgstr "副​アクション" -#: Source/diablo.cpp:2145 +#: Source/diablo.cpp:2181 msgid "Open chests, interact with doors, pick up items." msgstr "チェスト​を​開け​たり、ドア​と​対話​し​たり、アイテム​を​拾っ​たり。" -#: Source/diablo.cpp:2159 +#: Source/diablo.cpp:2195 msgid "Spell action" msgstr "スペル​・​アクション" -#: Source/diablo.cpp:2160 +#: Source/diablo.cpp:2196 msgid "Cast the active spell." msgstr "アクティブ​な​呪文​を​唱える。" -#: Source/diablo.cpp:2174 +#: Source/diablo.cpp:2210 msgid "Cancel action" msgstr "キャンセル​・​アクション" -#: Source/diablo.cpp:2175 +#: Source/diablo.cpp:2211 msgid "Close menus." msgstr "メニュー​を​閉じる。" -#: Source/diablo.cpp:2200 +#: Source/diablo.cpp:2236 msgid "Move up" msgstr "上​へ​移動" -#: Source/diablo.cpp:2201 +#: Source/diablo.cpp:2237 msgid "Moves the player character up." msgstr "プレイヤー​キャラクター​を​上​方向​に​移動​し​ます。" -#: Source/diablo.cpp:2206 +#: Source/diablo.cpp:2242 msgid "Move down" msgstr "下​へ​移動" -#: Source/diablo.cpp:2207 +#: Source/diablo.cpp:2243 msgid "Moves the player character down." msgstr "プレイヤー​キャラクター​を​下​方向​に​移動​し​ます。" -#: Source/diablo.cpp:2212 +#: Source/diablo.cpp:2248 msgid "Move left" msgstr "左​に​移動" -#: Source/diablo.cpp:2213 +#: Source/diablo.cpp:2249 msgid "Moves the player character left." msgstr "プレイヤー​キャラクター​を​左​方向​に​移動​し​ます。" -#: Source/diablo.cpp:2218 +#: Source/diablo.cpp:2254 msgid "Move right" msgstr "右​へ​移動" -#: Source/diablo.cpp:2219 +#: Source/diablo.cpp:2255 msgid "Moves the player character right." msgstr "プレイヤー​キャラクター​を​右​方向​に​移動​し​ます。" -#: Source/diablo.cpp:2224 +#: Source/diablo.cpp:2260 msgid "Stand ground" msgstr "踏ん張る" -#: Source/diablo.cpp:2225 +#: Source/diablo.cpp:2261 msgid "Hold to prevent the player from moving." msgstr "ホールド​する​と​プレーヤー​が​動か​なく​なり​ます。" -#: Source/diablo.cpp:2230 +#: Source/diablo.cpp:2266 msgid "Toggle stand ground" msgstr "踏ん張る​の​切り替え" -#: Source/diablo.cpp:2231 +#: Source/diablo.cpp:2267 msgid "Toggle whether the player moves." msgstr "プレイヤー​移動​の​切り替え" -#: Source/diablo.cpp:2310 -#, fuzzy -#| msgid "Automap" +#: Source/diablo.cpp:2346 msgid "Automap Move Up" -msgstr "オート​マップ" +msgstr "オート​マップ​を​上​に​移動" -#: Source/diablo.cpp:2311 +#: Source/diablo.cpp:2347 msgid "Moves the automap up when active." -msgstr "" +msgstr "有効​に​する​と、オート​マップ​が​上​方向​に​移動​し​ます。" -#: Source/diablo.cpp:2316 -#, fuzzy -#| msgid "Move down" +#: Source/diablo.cpp:2352 msgid "Automap Move Down" -msgstr "下​へ​移動" +msgstr "オート​マップ​を​下​に​移動" -#: Source/diablo.cpp:2317 +#: Source/diablo.cpp:2353 msgid "Moves the automap down when active." -msgstr "" +msgstr "有効​に​する​と、オート​マップ​が​方向​に​移動​し​ます。" -#: Source/diablo.cpp:2322 -#, fuzzy -#| msgid "Move left" +#: Source/diablo.cpp:2358 msgid "Automap Move Left" -msgstr "左​に​移動" +msgstr "オート​マップ​を​左​に​移動" -#: Source/diablo.cpp:2323 -#, fuzzy -#| msgid "Moves the player character up." +#: Source/diablo.cpp:2359 msgid "Moves the automap left when active." -msgstr "プレイヤー​キャラクター​を​上​方向​に​移動​し​ます。" +msgstr "有効​に​する​と、オート​マップ​が​左​方向​に​移動​し​ます。" -#: Source/diablo.cpp:2328 -#, fuzzy -#| msgid "Move right" +#: Source/diablo.cpp:2364 msgid "Automap Move Right" -msgstr "右​へ​移動" +msgstr "オート​マップ​を​右​に​移動" -#: Source/diablo.cpp:2329 +#: Source/diablo.cpp:2365 msgid "Moves the automap right when active." -msgstr "" +msgstr "有効​に​する​と、オート​マップ​が​方向​に​移動​し​ます。" -#: Source/diablo.cpp:2334 +#: Source/diablo.cpp:2370 msgid "Move mouse up" msgstr "マウス​を​上​に​移動" -#: Source/diablo.cpp:2335 +#: Source/diablo.cpp:2371 msgid "Simulates upward mouse movement." msgstr "マウス​の​上​方向​の​動き​を​シミュレート​し​ます。" -#: Source/diablo.cpp:2340 +#: Source/diablo.cpp:2376 msgid "Move mouse down" msgstr "マウス​を​下​に​移動" -#: Source/diablo.cpp:2341 +#: Source/diablo.cpp:2377 msgid "Simulates downward mouse movement." msgstr "マウス​の​下​方向​の​動き​を​シミュレート​し​ます。" -#: Source/diablo.cpp:2346 +#: Source/diablo.cpp:2382 msgid "Move mouse left" msgstr "マウス​を​左​へ​移動" -#: Source/diablo.cpp:2347 +#: Source/diablo.cpp:2383 msgid "Simulates leftward mouse movement." msgstr "マウス​の​左​方向​の​動き​を​シミュレート​し​ます。" -#: Source/diablo.cpp:2352 +#: Source/diablo.cpp:2388 msgid "Move mouse right" msgstr "マウス​を​右​へ​移動" -#: Source/diablo.cpp:2353 +#: Source/diablo.cpp:2389 msgid "Simulates rightward mouse movement." msgstr "マウス​の​右​方向​の​動き​を​シミュレート​し​ます。" -#: Source/diablo.cpp:2371 Source/diablo.cpp:2378 +#: Source/diablo.cpp:2407 Source/diablo.cpp:2414 msgid "Left mouse click" msgstr "マウス​の​左​クリック" -#: Source/diablo.cpp:2372 Source/diablo.cpp:2379 +#: Source/diablo.cpp:2408 Source/diablo.cpp:2415 msgid "Simulates the left mouse button." msgstr "マウス​の​左​ボタン​を​シミュレート​し​ます。" -#: Source/diablo.cpp:2396 Source/diablo.cpp:2403 +#: Source/diablo.cpp:2432 Source/diablo.cpp:2439 msgid "Right mouse click" msgstr "マウス​の​右​クリック" -#: Source/diablo.cpp:2397 Source/diablo.cpp:2404 +#: Source/diablo.cpp:2433 Source/diablo.cpp:2440 msgid "Simulates the right mouse button." msgstr "マウス​の​右​ボタン​を​シミュレート​し​ます。" -#: Source/diablo.cpp:2410 +#: Source/diablo.cpp:2446 msgid "Gamepad hotspell menu" msgstr "ゲーム​パッド​の​ホット​スペル​メニュー" -#: Source/diablo.cpp:2411 +#: Source/diablo.cpp:2447 msgid "Hold to set or use spell hotkeys." msgstr "ホールド​し​て​呪文​ホットキー​を​設定​また​は​使用​し​ます。" -#: Source/diablo.cpp:2417 +#: Source/diablo.cpp:2453 msgid "Gamepad menu navigator" msgstr "ゲーム​パッド​メニュー​ナビゲーター" -#: Source/diablo.cpp:2418 +#: Source/diablo.cpp:2454 msgid "Hold to access gamepad menu navigation." msgstr "長押し​で​ゲーム​パッド​メニュー​ナビゲーション​に​アクセス​し​ます。" -#: Source/diablo.cpp:2433 Source/diablo.cpp:2442 +#: Source/diablo.cpp:2469 Source/diablo.cpp:2478 msgid "Toggle game menu" msgstr "ゲーム​メニュー​の​切り替え" -#: Source/diablo.cpp:2434 Source/diablo.cpp:2443 +#: Source/diablo.cpp:2470 Source/diablo.cpp:2479 msgid "Opens the game menu." msgstr "ゲーム​メニュー​を​開き​ます。" -#: Source/diablo_msg.cpp:63 +#: Source/diablo_msg.cpp:69 msgid "Game saved" msgstr "ゲーム​の​セーブ" -#: Source/diablo_msg.cpp:64 +#: Source/diablo_msg.cpp:70 msgid "No multiplayer functions in demo" msgstr "デモ​で​は​マルチ​プレイ​機能​は​あり​ませ​ん" -#: Source/diablo_msg.cpp:65 +#: Source/diablo_msg.cpp:71 msgid "Direct Sound Creation Failed" msgstr "Direct Sound Creation Failed" -#: Source/diablo_msg.cpp:66 +#: Source/diablo_msg.cpp:72 msgid "Not available in shareware version" msgstr "シェアウェア​版​で​は​使用​でき​ませ​ん" -#: Source/diablo_msg.cpp:67 +#: Source/diablo_msg.cpp:73 msgid "Not enough space to save" msgstr "保存​する​ため​の​十分​な​スペース​が​あり​ませ​ん" -#: Source/diablo_msg.cpp:68 +#: Source/diablo_msg.cpp:74 msgid "No Pause in town" msgstr "町中​で​は​ポーズ​でき​ませ​ん" -#: Source/diablo_msg.cpp:69 +#: Source/diablo_msg.cpp:75 msgid "Copying to a hard disk is recommended" msgstr "ハードディスク​へ​の​コピー​を​推奨" -#: Source/diablo_msg.cpp:70 +#: Source/diablo_msg.cpp:76 msgid "Multiplayer sync problem" msgstr "マルチ​プレイヤー​同期​の​問題" -#: Source/diablo_msg.cpp:71 +#: Source/diablo_msg.cpp:77 msgid "No pause in multiplayer" msgstr "マルチ​プレイ​で​は​ポーズ​が​でき​ませ​ん" -#: Source/diablo_msg.cpp:73 +#: Source/diablo_msg.cpp:79 msgid "Saving..." msgstr "セーブ​中​…" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:74 +#: Source/diablo_msg.cpp:80 msgid "Some are weakened as one grows strong" msgstr "幾多​の​犠牲​の​上、新た​なる​力​は​得​られる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:75 +#: Source/diablo_msg.cpp:81 msgid "New strength is forged through destruction" msgstr "新た​なる​力、破壊​に​より​鍛え​られる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:76 +#: Source/diablo_msg.cpp:82 msgid "Those who defend seldom attack" msgstr "厚​き​盾、矛先​は​鈍る" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:77 +#: Source/diablo_msg.cpp:83 msgid "The sword of justice is swift and sharp" msgstr "義​を​以​て​振ら​れる​剣、鋭​き​刃​は​宿る" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:78 +#: Source/diablo_msg.cpp:84 msgid "While the spirit is vigilant the body thrives" msgstr "油断​おこたら​ず​ば、その​身​は​守ら​れる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:79 +#: Source/diablo_msg.cpp:85 msgid "The powers of mana refocused renews" msgstr "広がり​し​マナ​の​力、再び​寄り集う" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:80 +#: Source/diablo_msg.cpp:86 msgid "Time cannot diminish the power of steel" msgstr "時​の​流れ、鋼​の​力​を​奪う​は​叶わ​ず" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:81 +#: Source/diablo_msg.cpp:87 msgid "Magic is not always what it seems to be" msgstr "目​に​見える​もの、常​なら​ざる​が​魔術​なり" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:82 +#: Source/diablo_msg.cpp:88 msgid "What once was opened now is closed" msgstr "開か​れ​し​物、今、再び​閉じる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:83 +#: Source/diablo_msg.cpp:89 msgid "Intensity comes at the cost of wisdom" msgstr "高揚​は​知識​の​代価​に​与え​られる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:84 +#: Source/diablo_msg.cpp:90 msgid "Arcane power brings destruction" msgstr "神秘​の​力​が​災い​を​もたらす" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:85 +#: Source/diablo_msg.cpp:91 msgid "That which cannot be held cannot be harmed" msgstr "その​何れ​か、保た​れる​こと​なく​傷つく​こと​なく" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:86 +#: Source/diablo_msg.cpp:92 msgid "Crimson and Azure become as the sun" msgstr "深紅​と​淡青、太陽​と​なる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:87 +#: Source/diablo_msg.cpp:93 msgid "Knowledge and wisdom at the cost of self" msgstr "知恵​と​知識​に​己​を​賭けよ" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:88 +#: Source/diablo_msg.cpp:94 msgid "Drink and be refreshed" msgstr "これ​を​飲み、しばし​休ま​れよ" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:89 +#: Source/diablo_msg.cpp:95 msgid "Wherever you go, there you are" msgstr "何処​へ​行く​とも、汝​は​汝" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:90 +#: Source/diablo_msg.cpp:96 msgid "Energy comes at the cost of wisdom" msgstr "その​力、知識​の​代価​に​与え​られる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:91 +#: Source/diablo_msg.cpp:97 msgid "Riches abound when least expected" msgstr "ちり​も​積もれ​ば​山​と​なる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:92 +#: Source/diablo_msg.cpp:98 msgid "Where avarice fails, patience gains reward" msgstr "強欲​は​朽ち、忍耐​は​報わ​れる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:93 +#: Source/diablo_msg.cpp:99 msgid "Blessed by a benevolent companion!" msgstr "仲間​より​の​慈愛​の​祈り​を​受ける" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:94 +#: Source/diablo_msg.cpp:100 msgid "The hands of men may be guided by fate" msgstr "人​の​手​は、時折​運命​に​よっ​て​導か​れる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:95 +#: Source/diablo_msg.cpp:101 msgid "Strength is bolstered by heavenly faith" msgstr "その​力、神​へ​の​信仰​に​よっ​て​支え​られる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:96 +#: Source/diablo_msg.cpp:102 msgid "The essence of life flows from within" msgstr "生命​の​本質、その​内​より​湧き出る" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:97 +#: Source/diablo_msg.cpp:103 msgid "The way is made clear when viewed from above" msgstr "天​より​の​眺め、道​は​明らか​と​なる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:98 +#: Source/diablo_msg.cpp:104 msgid "Salvation comes at the cost of wisdom" msgstr "救済​は​知識​の​代価​に​与え​られる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:99 +#: Source/diablo_msg.cpp:105 msgid "Mysteries are revealed in the light of reason" msgstr "神秘​は​道理​の​光​の​下、明らか​に​さ​れる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:100 +#: Source/diablo_msg.cpp:106 msgid "Those who are last may yet be first" msgstr "人​より​先んずれ​ど​も​…" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:101 +#: Source/diablo_msg.cpp:107 msgid "Generosity brings its own rewards" msgstr "寛大​なる​心、いつ​の​日​か​報わ​れん" -#: Source/diablo_msg.cpp:102 +#: Source/diablo_msg.cpp:108 msgid "You must be at least level 8 to use this." msgstr "この​レベル​は、最低​8​レベル​以上​で​なく​て​は​なり​ませ​ん。" -#: Source/diablo_msg.cpp:103 +#: Source/diablo_msg.cpp:109 msgid "You must be at least level 13 to use this." msgstr "この​レベル​は、最低​13​レベル​以上​で​なく​て​は​なり​ませ​ん。" -#: Source/diablo_msg.cpp:104 +#: Source/diablo_msg.cpp:110 msgid "You must be at least level 17 to use this." msgstr "この​レベル​は、最低​17​レベル​以上​で​なく​て​は​なり​ませ​ん。" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:105 +#: Source/diablo_msg.cpp:111 msgid "Arcane knowledge gained!" msgstr "神秘​なる​知識​が​授け​られ​た​!" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:106 +#: Source/diablo_msg.cpp:112 msgid "That which does not kill you..." msgstr "死な​ない​もの​は​…" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:107 +#: Source/diablo_msg.cpp:113 msgid "Knowledge is power." msgstr "知識​は​力​なり" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:108 +#: Source/diablo_msg.cpp:114 msgid "Give and you shall receive." msgstr "与えよ、さらば​与え​られ​ん" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:109 +#: Source/diablo_msg.cpp:115 msgid "Some experience is gained by touch." msgstr "触れる​こと​で​得​られる​経験​も​ある。" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:110 +#: Source/diablo_msg.cpp:116 msgid "There's no place like home." msgstr "家​の​よう​な​場所​は​ない。" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:111 +#: Source/diablo_msg.cpp:117 msgid "Spiritual energy is restored." msgstr "精神的​な​エネルギー​が​回復​する。" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:112 +#: Source/diablo_msg.cpp:118 msgid "You feel more agile." msgstr "より​機敏​な​感じ​が​する。" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:113 +#: Source/diablo_msg.cpp:119 msgid "You feel stronger." msgstr "強く​なっ​た​と​感じる。" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:114 +#: Source/diablo_msg.cpp:120 msgid "You feel wiser." msgstr "賢く​なっ​た​と​感じる。" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:115 +#: Source/diablo_msg.cpp:121 msgid "You feel refreshed." msgstr "爽快感​が​ある。" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/diablo_msg.cpp:116 +#: Source/diablo_msg.cpp:122 msgid "That which can break will." msgstr "壊せる​もの​は​壊す。" @@ -2176,15 +2212,23 @@ msgstr "メニュー​に​ある" msgid "loopback" msgstr "ループ​バック" -#: Source/dvlnet/tcp_client.cpp:112 +#: Source/dvlnet/tcp_client.cpp:118 msgid "Unable to connect" msgstr "接続​でき​ませ​ん" -#: Source/dvlnet/tcp_client.cpp:150 +#: Source/dvlnet/tcp_client.cpp:156 msgid "error: read 0 bytes from server" msgstr "エラー​:​サーバー​から​データ​を​読み込め​ませ​ん" -#: Source/engine/assets.cpp:244 +#: Source/dvlnet/tcp_client.cpp:216 +msgid "" +"Server failed to decrypt your packet. Check if you typed the password " +"correctly." +msgstr "" +"サーバー​が​パケット​の​復号​に​失敗​し​まし​た。パスワード​が​正しく​入力​さ​れ​て​いる​か​確" +"認​し​て​ください。" + +#: Source/engine/assets.cpp:252 #, c++-format msgid "" "Failed to open file:\n" @@ -2202,15 +2246,15 @@ msgstr "" "MPQ​ファイル​が​破損​し​て​いる​可能性​が​あり​ます。ファイル​の​整合性​を​確認​し​て​くださ" "い。" -#: Source/engine/assets.cpp:426 +#: Source/engine/assets.cpp:434 msgid "diabdat.mpq or spawn.mpq" msgstr "diabdat.mpq​また​は​spawn.mpq" -#: Source/engine/assets.cpp:464 +#: Source/engine/assets.cpp:472 msgid "Some Hellfire MPQs are missing" msgstr "いくつ​か​の​Hellfire MPQ​が​欠落​し​て​い​ます" -#: Source/engine/assets.cpp:464 +#: Source/engine/assets.cpp:472 msgid "" "Not all Hellfire MPQs were found.\n" "Please copy all the hf*.mpq files." @@ -2218,141 +2262,135 @@ msgstr "" "すべて​の​Hellfire MPQ​が​見つから​ない。\n" "​すべて​の hf​*​.mpq ファイル​を​コピー​し​て​ください。" -#: Source/engine/demomode.cpp:181 Source/options.cpp:535 +#: Source/engine/demomode.cpp:188 Source/options.cpp:546 msgid "Resolution" msgstr "解像度" -#: Source/engine/demomode.cpp:183 Source/options.cpp:784 +#: Source/engine/demomode.cpp:190 Source/options.cpp:835 msgid "Run in Town" msgstr "街​で​走る" -#: Source/engine/demomode.cpp:184 Source/options.cpp:787 +#: Source/engine/demomode.cpp:191 Source/options.cpp:838 msgid "Theo Quest" msgstr "テオクエスト" -#: Source/engine/demomode.cpp:185 Source/options.cpp:788 +#: Source/engine/demomode.cpp:192 Source/options.cpp:839 msgid "Cow Quest" msgstr "牛​クエスト" -#: Source/engine/demomode.cpp:186 Source/options.cpp:800 +#: Source/engine/demomode.cpp:193 Source/options.cpp:851 msgid "Auto Gold Pickup" msgstr "自動​ゴールド​回収" -#: Source/engine/demomode.cpp:187 Source/options.cpp:801 +#: Source/engine/demomode.cpp:194 Source/options.cpp:852 msgid "Auto Elixir Pickup" msgstr "自動​エリクサー​回収" -#: Source/engine/demomode.cpp:188 Source/options.cpp:802 +#: Source/engine/demomode.cpp:195 Source/options.cpp:853 msgid "Auto Oil Pickup" msgstr "自動​オイル​回収" -#: Source/engine/demomode.cpp:189 Source/options.cpp:803 +#: Source/engine/demomode.cpp:196 Source/options.cpp:854 msgid "Auto Pickup in Town" msgstr "街中​で​自動​回収" -#: Source/engine/demomode.cpp:190 Source/options.cpp:804 -msgid "Adria Refills Mana" -msgstr "エイドリア​の​マナ​補充" - -#: Source/engine/demomode.cpp:191 Source/options.cpp:805 +#: Source/engine/demomode.cpp:197 Source/options.cpp:855 msgid "Auto Equip Weapons" msgstr "武器​の​自動​装備" -#: Source/engine/demomode.cpp:192 Source/options.cpp:806 +#: Source/engine/demomode.cpp:198 Source/options.cpp:856 msgid "Auto Equip Armor" msgstr "アーマー​の​自動​装備" -#: Source/engine/demomode.cpp:193 Source/options.cpp:807 +#: Source/engine/demomode.cpp:199 Source/options.cpp:857 msgid "Auto Equip Helms" msgstr "ヘルム​の​自動​装備" -#: Source/engine/demomode.cpp:194 Source/options.cpp:808 +#: Source/engine/demomode.cpp:200 Source/options.cpp:858 msgid "Auto Equip Shields" msgstr "シールド​の​自動​装備" -#: Source/engine/demomode.cpp:195 Source/options.cpp:809 +#: Source/engine/demomode.cpp:201 Source/options.cpp:859 msgid "Auto Equip Jewelry" msgstr "ジュエリー​の​自動​装備" -#: Source/engine/demomode.cpp:196 Source/options.cpp:810 +#: Source/engine/demomode.cpp:202 Source/options.cpp:860 msgid "Randomize Quests" msgstr "クエスト​の​ランダム化" -#: Source/engine/demomode.cpp:197 Source/options.cpp:812 +#: Source/engine/demomode.cpp:203 Source/options.cpp:862 msgid "Show Item Labels" msgstr "アイテム​ラベル​を​表示" -#: Source/engine/demomode.cpp:198 Source/options.cpp:813 +#: Source/engine/demomode.cpp:204 Source/options.cpp:863 msgid "Auto Refill Belt" msgstr "ベルト​の​自動​補充" -#: Source/engine/demomode.cpp:199 Source/options.cpp:814 +#: Source/engine/demomode.cpp:205 Source/options.cpp:864 msgid "Disable Crippling Shrines" msgstr "被害​を​与える​祭壇​を​無効化" -#: Source/engine/demomode.cpp:203 Source/options.cpp:816 +#: Source/engine/demomode.cpp:209 Source/options.cpp:866 msgid "Heal Potion Pickup" msgstr "ヒーリング​・​ポーション​回収" -#: Source/engine/demomode.cpp:204 Source/options.cpp:817 +#: Source/engine/demomode.cpp:210 Source/options.cpp:867 msgid "Full Heal Potion Pickup" msgstr "フルヒーリング​・​ポーション​回収" -#: Source/engine/demomode.cpp:205 Source/options.cpp:818 +#: Source/engine/demomode.cpp:211 Source/options.cpp:868 msgid "Mana Potion Pickup" msgstr "マナ​・​ポーション​を​回収" -#: Source/engine/demomode.cpp:206 Source/options.cpp:819 +#: Source/engine/demomode.cpp:212 Source/options.cpp:869 msgid "Full Mana Potion Pickup" msgstr "フルマナ​・​ポーション​回収" -#: Source/engine/demomode.cpp:207 Source/options.cpp:820 +#: Source/engine/demomode.cpp:213 Source/options.cpp:870 msgid "Rejuvenation Potion Pickup" msgstr "回復​ポーション​回収" -#: Source/engine/demomode.cpp:208 Source/options.cpp:821 +#: Source/engine/demomode.cpp:214 Source/options.cpp:871 msgid "Full Rejuvenation Potion Pickup" msgstr "フル​回復​ポーション​回収" -#: Source/gamemenu.cpp:48 Source/gamemenu.cpp:60 +#: Source/gamemenu.cpp:52 Source/gamemenu.cpp:64 msgid "Options" msgstr "オプション" -#: Source/gamemenu.cpp:49 +#: Source/gamemenu.cpp:53 msgid "Save Game" msgstr "ゲーム​の​セーブ" -#: Source/gamemenu.cpp:51 Source/gamemenu.cpp:61 -#, fuzzy -#| msgid "Main Menu" +#: Source/gamemenu.cpp:55 Source/gamemenu.cpp:65 msgid "Exit to Main Menu" -msgstr "メインメニュー" +msgstr "メインメニュー​に​戻る" -#: Source/gamemenu.cpp:52 Source/gamemenu.cpp:62 +#: Source/gamemenu.cpp:56 Source/gamemenu.cpp:66 msgid "Quit Game" msgstr "ゲーム​の​終了" -#: Source/gamemenu.cpp:71 +#: Source/gamemenu.cpp:75 msgid "Gamma" msgstr "明るさ" -#: Source/gamemenu.cpp:72 Source/gamemenu.cpp:171 +#: Source/gamemenu.cpp:76 Source/gamemenu.cpp:175 msgid "Speed" msgstr "スピード" -#: Source/gamemenu.cpp:80 +#: Source/gamemenu.cpp:84 msgid "Music Disabled" msgstr "音楽​を​禁止" -#: Source/gamemenu.cpp:84 +#: Source/gamemenu.cpp:88 msgid "Sound" msgstr "効果音" -#: Source/gamemenu.cpp:85 +#: Source/gamemenu.cpp:89 msgid "Sound Disabled" msgstr "効果音​を​禁止" -#: Source/gmenu.cpp:179 +#: Source/gmenu.cpp:190 msgid "Pause" msgstr "ポーズ" @@ -2594,224 +2632,224 @@ msgstr "ディアブロ ヘルプ" msgid "Press ESC to end or the arrow keys to scroll." msgstr "ESC​を​押し​て​終了​する​か、矢印​キー​で​スクロール​し​ます。" -#: Source/init.cpp:130 +#: Source/init.cpp:136 msgid "Unable to create main window" msgstr "メイン​ウィンドウ​を​作成​でき​ませ​ん" -#: Source/inv.cpp:2228 +#: Source/inv.cpp:2235 msgid "No room for item" msgstr "アイテム​が​入る​余地​が​ない" -#: Source/items.cpp:212 Source/translation_dummy.cpp:298 +#: Source/items.cpp:217 Source/translation_dummy.cpp:298 msgid "Oil of Accuracy" msgstr "アキュラシー​・​オイル" -#: Source/items.cpp:213 +#: Source/items.cpp:218 msgid "Oil of Mastery" msgstr "マスタリー​・​オイル" -#: Source/items.cpp:214 Source/translation_dummy.cpp:299 +#: Source/items.cpp:219 Source/translation_dummy.cpp:299 msgid "Oil of Sharpness" msgstr "シャープネス​・​オイル" -#: Source/items.cpp:215 +#: Source/items.cpp:220 msgid "Oil of Death" msgstr "デス​・​オイル" -#: Source/items.cpp:216 +#: Source/items.cpp:221 msgid "Oil of Skill" msgstr "スキル​・​オイル" -#: Source/items.cpp:217 Source/translation_dummy.cpp:251 +#: Source/items.cpp:222 Source/translation_dummy.cpp:251 msgid "Blacksmith Oil" msgstr "鍛冶屋​の​オイル" -#: Source/items.cpp:218 +#: Source/items.cpp:223 msgid "Oil of Fortitude" msgstr "フォルチュード​・​オイル" -#: Source/items.cpp:219 +#: Source/items.cpp:224 msgid "Oil of Permanence" msgstr "パーマネンス​・​オイル" -#: Source/items.cpp:220 +#: Source/items.cpp:225 msgid "Oil of Hardening" msgstr "ハーデニング​・​オイル" -#: Source/items.cpp:221 +#: Source/items.cpp:226 msgid "Oil of Imperviousness" msgstr "インプレビュスネス​・​オイル" #. TRANSLATORS: Constructs item names. Format: {Item} of {Spell}. Example: War Staff of Firewall -#: Source/items.cpp:1104 +#: Source/items.cpp:1109 #, c++-format msgctxt "spell" msgid "{0} of {1}" msgstr "{1}​の​{0}" #. TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Spell}. Example: King's War Staff of Firewall -#: Source/items.cpp:1116 +#: Source/items.cpp:1121 #, c++-format msgctxt "spell" msgid "{0} {1} of {2}" msgstr "{2}​の​{0}{1}" #. TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Suffix}. Example: King's Long Sword of the Whale -#: Source/items.cpp:1154 +#: Source/items.cpp:1159 #, c++-format msgid "{0} {1} of {2}" msgstr "{2}​の​{0}{1}" #. TRANSLATORS: Constructs item names. Format: {Prefix} {Item}. Example: King's Long Sword -#: Source/items.cpp:1158 +#: Source/items.cpp:1163 #, c++-format msgid "{0} {1}" msgstr "{0} {1}" #. TRANSLATORS: Constructs item names. Format: {Item} of {Suffix}. Example: Long Sword of the Whale -#: Source/items.cpp:1162 +#: Source/items.cpp:1167 #, c++-format msgid "{0} of {1}" msgstr "{1}​の​{0}" -#: Source/items.cpp:1643 Source/items.cpp:1651 +#: Source/items.cpp:1648 Source/items.cpp:1656 msgid "increases a weapon's" msgstr "武器​の​命中​率​を" -#: Source/items.cpp:1644 +#: Source/items.cpp:1649 msgid "chance to hit" msgstr "上昇​さ​せる" -#: Source/items.cpp:1647 +#: Source/items.cpp:1652 msgid "greatly increases a" msgstr "武器​の​命中​率​を" -#: Source/items.cpp:1648 +#: Source/items.cpp:1653 msgid "weapon's chance to hit" msgstr "大幅​に​上昇​さ​せる" -#: Source/items.cpp:1652 +#: Source/items.cpp:1657 msgid "damage potential" msgstr "増大​さ​せる" -#: Source/items.cpp:1655 +#: Source/items.cpp:1660 msgid "greatly increases a weapon's" msgstr "武器​の​ダメージ​を​大幅​に" -#: Source/items.cpp:1656 +#: Source/items.cpp:1661 msgid "damage potential - not bows" msgstr "増大​さ​せる​-​ボウ​を​除く" -#: Source/items.cpp:1659 +#: Source/items.cpp:1664 msgid "reduces attributes needed" msgstr "防具​や​武器​の​必要​能力​値" -#: Source/items.cpp:1660 +#: Source/items.cpp:1665 msgid "to use armor or weapons" msgstr "の​値​を​引き下げる" -#: Source/items.cpp:1663 +#: Source/items.cpp:1668 #, no-c-format msgid "restores 20% of an" msgstr "を​20​%​回復" -#: Source/items.cpp:1664 +#: Source/items.cpp:1669 msgid "item's durability" msgstr "アイテム​の​耐久​度" -#: Source/items.cpp:1667 +#: Source/items.cpp:1672 msgid "increases an item's" msgstr "次​の​もの​を​増大​さ​せ​ます:" -#: Source/items.cpp:1668 +#: Source/items.cpp:1673 msgid "current and max durability" msgstr "20​%​増大​さ​せる" -#: Source/items.cpp:1671 +#: Source/items.cpp:1676 msgid "makes an item indestructible" msgstr "アイテム​を​壊れ​ない​よう​に​し​ます" -#: Source/items.cpp:1674 +#: Source/items.cpp:1679 msgid "increases the armor class" msgstr "防具​と​盾​の​防御​力​を" -#: Source/items.cpp:1675 +#: Source/items.cpp:1680 msgid "of armor and shields" msgstr "増大​さ​せる" -#: Source/items.cpp:1678 +#: Source/items.cpp:1683 msgid "greatly increases the armor" msgstr "防具​と​盾​の​防御​力​を" -#: Source/items.cpp:1679 +#: Source/items.cpp:1684 msgid "class of armor and shields" msgstr "大幅​に​増大​さ​せる" -#: Source/items.cpp:1682 Source/items.cpp:1689 +#: Source/items.cpp:1687 Source/items.cpp:1694 msgid "sets fire trap" msgstr "ファイヤー​トラップ​を​設置" -#: Source/items.cpp:1686 +#: Source/items.cpp:1691 msgid "sets lightning trap" msgstr "ライトニング​トラップ​を​設置" -#: Source/items.cpp:1692 +#: Source/items.cpp:1697 msgid "sets petrification trap" msgstr "石化​トラップ​を​設置" -#: Source/items.cpp:1695 +#: Source/items.cpp:1700 msgid "restore all life" msgstr "ライフ​を​全​回復" -#: Source/items.cpp:1698 +#: Source/items.cpp:1703 msgid "restore some life" msgstr "ライフ​を​回復" -#: Source/items.cpp:1701 +#: Source/items.cpp:1706 msgid "restore some mana" msgstr "マナ​を​回復" -#: Source/items.cpp:1704 +#: Source/items.cpp:1709 msgid "restore all mana" msgstr "マナ​を​全​回復" -#: Source/items.cpp:1707 +#: Source/items.cpp:1712 msgid "increase strength" msgstr "STR を​増大" -#: Source/items.cpp:1710 +#: Source/items.cpp:1715 msgid "increase magic" msgstr "MAG を​増大" -#: Source/items.cpp:1713 +#: Source/items.cpp:1718 msgid "increase dexterity" msgstr "DEX を​増大" -#: Source/items.cpp:1716 +#: Source/items.cpp:1721 msgid "increase vitality" msgstr "VIT を​増大" -#: Source/items.cpp:1719 +#: Source/items.cpp:1724 msgid "restore some life and mana" msgstr "ライフ​と​マナ​を​回復" -#: Source/items.cpp:1722 Source/items.cpp:1725 +#: Source/items.cpp:1727 Source/items.cpp:1730 msgid "restore all life and mana" msgstr "ライフ​と​マナ​を​全​回復" -#: Source/items.cpp:1726 +#: Source/items.cpp:1731 msgid "(works only in arenas)" msgstr "(​アリーナ​のみ​)" -#: Source/items.cpp:1761 +#: Source/items.cpp:1766 msgid "Right-click to view" msgstr "右​クリック​で​表示" -#: Source/items.cpp:1764 +#: Source/items.cpp:1769 msgid "Right-click to use" msgstr "右​クリック​で​使用" -#: Source/items.cpp:1766 +#: Source/items.cpp:1771 msgid "" "Right-click to read, then\n" "left-click to target" @@ -2819,23 +2857,23 @@ msgstr "" "右​クリック​で​読み、\n" "​左​クリック​で​ターゲット" -#: Source/items.cpp:1768 +#: Source/items.cpp:1773 msgid "Right-click to read" msgstr "右​クリック​で​読む" -#: Source/items.cpp:1775 +#: Source/items.cpp:1780 msgid "Activate to view" msgstr "アクティベート​し​て​表示​する" -#: Source/items.cpp:1779 Source/items.cpp:1804 +#: Source/items.cpp:1784 Source/items.cpp:1809 msgid "Open inventory to use" msgstr "使用​する​インベントリ​を​開く" -#: Source/items.cpp:1781 +#: Source/items.cpp:1786 msgid "Activate to use" msgstr "アクティベート​し​て​使う" -#: Source/items.cpp:1784 +#: Source/items.cpp:1789 msgid "" "Select from spell book, then\n" "cast spell to read" @@ -2843,21 +2881,21 @@ msgstr "" "スペル​ブック​から​選択​し​\n" "​読ん​で​キャスト" -#: Source/items.cpp:1786 +#: Source/items.cpp:1791 msgid "Activate to read" msgstr "アクティベート​し​て​読む" -#: Source/items.cpp:1800 +#: Source/items.cpp:1805 #, c++-format msgid "{} to view" msgstr "{} を​見る" -#: Source/items.cpp:1806 +#: Source/items.cpp:1811 #, c++-format msgid "{} to use" msgstr "{} を​使用​する" -#: Source/items.cpp:1809 +#: Source/items.cpp:1814 #, c++-format msgid "" "Select from spell book,\n" @@ -2866,475 +2904,475 @@ msgstr "" "スペル​ブック​から​選択、\n" "​そして​{}​を​読む" -#: Source/items.cpp:1811 +#: Source/items.cpp:1816 #, c++-format msgid "{} to read" msgstr "{} を​読む" -#: Source/items.cpp:1818 +#: Source/items.cpp:1823 #, c++-format msgctxt "player" msgid "Level: {:d}" msgstr "レベル: {:d}" -#: Source/items.cpp:1822 +#: Source/items.cpp:1827 msgid "Doubles gold capacity" msgstr "ゴールド​の​容量​を​2​倍​に​する" -#: Source/items.cpp:1855 Source/stores.cpp:327 +#: Source/items.cpp:1860 Source/stores.cpp:329 msgid "Required:" msgstr "必要​能力:" -#: Source/items.cpp:1857 Source/stores.cpp:329 +#: Source/items.cpp:1862 Source/stores.cpp:331 #, c++-format msgid " {:d} Str" msgstr " {:d} Str" -#: Source/items.cpp:1859 Source/stores.cpp:331 +#: Source/items.cpp:1864 Source/stores.cpp:333 #, c++-format msgid " {:d} Mag" msgstr " {:d} Mag" -#: Source/items.cpp:1861 Source/stores.cpp:333 +#: Source/items.cpp:1866 Source/stores.cpp:335 #, c++-format msgid " {:d} Dex" msgstr " {:d} Dex" #. TRANSLATORS: {:s} will be a spell name -#: Source/items.cpp:2217 +#: Source/items.cpp:2222 #, c++-format msgid "Book of {:s}" msgstr "呪文​の​書: {:s}" #. TRANSLATORS: {:s} will be a Character Name -#: Source/items.cpp:2220 +#: Source/items.cpp:2225 #, c++-format msgid "Ear of {:s}" msgstr "{:s}​の​耳" -#: Source/items.cpp:3874 +#: Source/items.cpp:3881 #, c++-format msgid "chance to hit: {:+d}%" msgstr "命中​率: {:+d}​%" -#: Source/items.cpp:3877 +#: Source/items.cpp:3884 #, no-c-format, c++-format msgid "{:+d}% damage" msgstr "{:+d}​%​ダメージ" -#: Source/items.cpp:3880 Source/items.cpp:4062 +#: Source/items.cpp:3887 Source/items.cpp:4069 #, c++-format msgid "to hit: {:+d}%, {:+d}% damage" msgstr "命中​率:{:+d}​%​, {:+d}​% ダメージ" -#: Source/items.cpp:3883 +#: Source/items.cpp:3890 #, no-c-format, c++-format msgid "{:+d}% armor" msgstr "{:+d}​%​防護​力" -#: Source/items.cpp:3886 +#: Source/items.cpp:3893 #, c++-format msgid "armor class: {:d}" msgstr "防御​力: {:d}" -#: Source/items.cpp:3890 +#: Source/items.cpp:3897 #, c++-format msgid "Resist Fire: {:+d}%" msgstr "耐火​炎: {:+d}​%" -#: Source/items.cpp:3892 +#: Source/items.cpp:3899 #, c++-format msgid "Resist Fire: {:+d}% MAX" msgstr "耐火​炎: {:+d}​% MAX" -#: Source/items.cpp:3896 +#: Source/items.cpp:3903 #, c++-format msgid "Resist Lightning: {:+d}%" msgstr "耐​電撃: {:+d}​%" -#: Source/items.cpp:3898 +#: Source/items.cpp:3905 #, c++-format msgid "Resist Lightning: {:+d}% MAX" msgstr "耐​電撃: {:+d}​% MAX" -#: Source/items.cpp:3902 +#: Source/items.cpp:3909 #, c++-format msgid "Resist Magic: {:+d}%" msgstr "耐​魔法: {:+d}​%" -#: Source/items.cpp:3904 +#: Source/items.cpp:3911 #, c++-format msgid "Resist Magic: {:+d}% MAX" msgstr "耐​魔法: {:+d}​% MAX" -#: Source/items.cpp:3907 +#: Source/items.cpp:3914 #, c++-format msgid "Resist All: {:+d}%" msgstr "全​耐性: {:+d}​%" -#: Source/items.cpp:3909 +#: Source/items.cpp:3916 #, c++-format msgid "Resist All: {:+d}% MAX" msgstr "全​耐性: {:+d}​% MAX" -#: Source/items.cpp:3912 +#: Source/items.cpp:3919 #, c++-format msgid "spells are increased {:d} level" msgid_plural "spells are increased {:d} levels" msgstr[0] "全呪文{:d} レベルアップ" -#: Source/items.cpp:3914 +#: Source/items.cpp:3921 #, c++-format msgid "spells are decreased {:d} level" msgid_plural "spells are decreased {:d} levels" msgstr[0] "全呪文{:d}レベルダウン" -#: Source/items.cpp:3916 +#: Source/items.cpp:3923 msgid "spell levels unchanged (?)" msgstr "呪文​の​レベル​に​変化​なし​(​?​)" -#: Source/items.cpp:3918 +#: Source/items.cpp:3925 msgid "Extra charges" msgstr "追加​チャージ" -#: Source/items.cpp:3920 +#: Source/items.cpp:3927 #, c++-format msgid "{:d} {:s} charge" msgid_plural "{:d} {:s} charges" msgstr[0] "{:d} {:s}チャージ" -#: Source/items.cpp:3923 +#: Source/items.cpp:3930 #, c++-format msgid "Fire hit damage: {:d}" msgstr "追加​火炎​ダメージ:{:d}" -#: Source/items.cpp:3925 +#: Source/items.cpp:3932 #, c++-format msgid "Fire hit damage: {:d}-{:d}" msgstr "追加​火炎​ダメージ: {:d}-{:d}" -#: Source/items.cpp:3928 +#: Source/items.cpp:3935 #, c++-format msgid "Lightning hit damage: {:d}" msgstr "追加​電撃​ダメージ: {:d}" -#: Source/items.cpp:3930 +#: Source/items.cpp:3937 #, c++-format msgid "Lightning hit damage: {:d}-{:d}" msgstr "追加​電撃​ダメージ: {:d}-{:d}" -#: Source/items.cpp:3933 +#: Source/items.cpp:3940 #, c++-format msgid "{:+d} to strength" msgstr "STR{:+d}" -#: Source/items.cpp:3936 +#: Source/items.cpp:3943 #, c++-format msgid "{:+d} to magic" msgstr "MAG{:+d}" -#: Source/items.cpp:3939 +#: Source/items.cpp:3946 #, c++-format msgid "{:+d} to dexterity" msgstr "DEX{:+d}" -#: Source/items.cpp:3942 +#: Source/items.cpp:3949 #, c++-format msgid "{:+d} to vitality" msgstr "VIT{:+d}" -#: Source/items.cpp:3945 +#: Source/items.cpp:3952 #, c++-format msgid "{:+d} to all attributes" msgstr "全​能力​値​{:+d}" -#: Source/items.cpp:3948 +#: Source/items.cpp:3955 #, c++-format msgid "{:+d} damage from enemies" msgstr "敵​から​受ける​ダメージ​{:+d}" -#: Source/items.cpp:3951 +#: Source/items.cpp:3958 #, c++-format msgid "Hit Points: {:+d}" msgstr "ヒット​ポイント: {:+d}" -#: Source/items.cpp:3954 +#: Source/items.cpp:3961 #, c++-format msgid "Mana: {:+d}" msgstr "マナ: {:+d}" -#: Source/items.cpp:3956 +#: Source/items.cpp:3963 msgid "high durability" msgstr "高い​耐久​度" -#: Source/items.cpp:3958 +#: Source/items.cpp:3965 msgid "decreased durability" msgstr "低い​耐久​度" -#: Source/items.cpp:3960 +#: Source/items.cpp:3967 msgid "indestructible" msgstr "壊れ​ない" -#: Source/items.cpp:3962 +#: Source/items.cpp:3969 #, no-c-format, c++-format msgid "+{:d}% light radius" msgstr "視界​+{:d}​%" -#: Source/items.cpp:3964 +#: Source/items.cpp:3971 #, no-c-format, c++-format msgid "-{:d}% light radius" msgstr "視界​-{:d}​%" -#: Source/items.cpp:3966 +#: Source/items.cpp:3973 msgid "multiple arrows per shot" msgstr "一回​の​射撃​で​複数​の​矢​を​放つ" -#: Source/items.cpp:3969 +#: Source/items.cpp:3976 #, c++-format msgid "fire arrows damage: {:d}" msgstr "火炎​矢​の​ダメージ: {:d}" -#: Source/items.cpp:3971 +#: Source/items.cpp:3978 #, c++-format msgid "fire arrows damage: {:d}-{:d}" msgstr "火炎​矢​の​ダメージ: {:d}-{:d}" -#: Source/items.cpp:3974 +#: Source/items.cpp:3981 #, c++-format msgid "lightning arrows damage {:d}" msgstr "電撃​矢​の​ダメージ: {:d}" -#: Source/items.cpp:3976 +#: Source/items.cpp:3983 #, c++-format msgid "lightning arrows damage {:d}-{:d}" msgstr "電撃​矢​の​ダメージ: {:d}-{:d}" -#: Source/items.cpp:3979 +#: Source/items.cpp:3986 #, c++-format msgid "fireball damage: {:d}" msgstr "ファイヤーボールダメージ: {:d}" -#: Source/items.cpp:3981 +#: Source/items.cpp:3988 #, c++-format msgid "fireball damage: {:d}-{:d}" msgstr "ファイヤーボールダメージ: {:d}-{:d}" -#: Source/items.cpp:3983 +#: Source/items.cpp:3990 msgid "attacker takes 1-3 damage" msgstr "ダメージ​時​に​反撃:1-3" -#: Source/items.cpp:3985 +#: Source/items.cpp:3992 msgid "user loses all mana" msgstr "全て​の​マナ​を​失う" -#: Source/items.cpp:3987 +#: Source/items.cpp:3994 msgid "absorbs half of trap damage" msgstr "罠​に​よる​ダメージ​半減" -#: Source/items.cpp:3989 +#: Source/items.cpp:3996 msgid "knocks target back" msgstr "ノック​バック​効果" -#: Source/items.cpp:3991 +#: Source/items.cpp:3998 #, no-c-format msgid "+200% damage vs. demons" msgstr "対​悪魔​族​ダメージ​+200​%" -#: Source/items.cpp:3993 +#: Source/items.cpp:4000 msgid "All Resistance equals 0" msgstr "全て​の​耐性​を​失う" -#: Source/items.cpp:3996 +#: Source/items.cpp:4003 #, no-c-format msgid "hit steals 3% mana" msgstr "マナ​を​3​%​吸収" -#: Source/items.cpp:3998 +#: Source/items.cpp:4005 #, no-c-format msgid "hit steals 5% mana" msgstr "マナ​を​5​%​吸収" -#: Source/items.cpp:4002 +#: Source/items.cpp:4009 #, no-c-format msgid "hit steals 3% life" msgstr "ライフ​を​3​%​吸収" -#: Source/items.cpp:4004 +#: Source/items.cpp:4011 #, no-c-format msgid "hit steals 5% life" msgstr "ライフ​を​5​%​吸収" -#: Source/items.cpp:4007 +#: Source/items.cpp:4014 msgid "penetrates target's armor" msgstr "ターゲット​の​アーマー​を​貫通" -#: Source/items.cpp:4010 +#: Source/items.cpp:4017 msgid "quick attack" msgstr "速い​攻撃" -#: Source/items.cpp:4012 +#: Source/items.cpp:4019 msgid "fast attack" msgstr "高速​攻撃" -#: Source/items.cpp:4014 +#: Source/items.cpp:4021 msgid "faster attack" msgstr "超​高速​攻撃" -#: Source/items.cpp:4016 +#: Source/items.cpp:4023 msgid "fastest attack" msgstr "神速​攻撃" -#: Source/items.cpp:4017 Source/items.cpp:4025 Source/items.cpp:4072 +#: Source/items.cpp:4024 Source/items.cpp:4032 Source/items.cpp:4079 msgid "Another ability (NW)" msgstr "別​の​能力​(​NW​)" -#: Source/items.cpp:4020 +#: Source/items.cpp:4027 msgid "fast hit recovery" msgstr "高速​体勢​回復" -#: Source/items.cpp:4022 +#: Source/items.cpp:4029 msgid "faster hit recovery" msgstr "早い​体勢​回復" -#: Source/items.cpp:4024 +#: Source/items.cpp:4031 msgid "fastest hit recovery" msgstr "神速​体勢​回復" -#: Source/items.cpp:4027 +#: Source/items.cpp:4034 msgid "fast block" msgstr "早い​防御" -#: Source/items.cpp:4029 +#: Source/items.cpp:4036 #, c++-format msgid "adds {:d} point to damage" msgid_plural "adds {:d} points to damage" msgstr[0] "追加ダメージ: {:d}" -#: Source/items.cpp:4031 +#: Source/items.cpp:4038 msgid "fires random speed arrows" msgstr "発射​間隔​が​一定​で​ない" -#: Source/items.cpp:4033 +#: Source/items.cpp:4040 msgid "unusual item damage" msgstr "魔法​の​アイテム​に​ダメージ" -#: Source/items.cpp:4035 +#: Source/items.cpp:4042 msgid "altered durability" msgstr "特殊​な​耐久​度" -#: Source/items.cpp:4037 +#: Source/items.cpp:4044 msgid "one handed sword" msgstr "片手​持ち​ソード" -#: Source/items.cpp:4039 +#: Source/items.cpp:4046 msgid "constantly lose hit points" msgstr "少し​ずつ​ライフ​を​失う" -#: Source/items.cpp:4041 +#: Source/items.cpp:4048 msgid "life stealing" msgstr "直接​攻撃​で​ライフ​吸収" -#: Source/items.cpp:4043 +#: Source/items.cpp:4050 msgid "no strength requirement" msgstr "筋力​を​必要​と​し​ない" -#: Source/items.cpp:4046 +#: Source/items.cpp:4053 #, c++-format msgid "lightning damage: {:d}" msgstr "電撃​ダメージ: {:d}" -#: Source/items.cpp:4048 +#: Source/items.cpp:4055 #, c++-format msgid "lightning damage: {:d}-{:d}" msgstr "電撃​ダメージ: {:d}-{:d}" -#: Source/items.cpp:4050 +#: Source/items.cpp:4057 msgid "charged bolts on hits" msgstr "チャージド​ボルト" -#: Source/items.cpp:4052 +#: Source/items.cpp:4059 msgid "occasional triple damage" msgstr "たまに​3​倍​の​ダメージ" -#: Source/items.cpp:4054 +#: Source/items.cpp:4061 #, no-c-format, c++-format msgid "decaying {:+d}% damage" msgstr "崩壊​{:+d}​%​ダメージ" -#: Source/items.cpp:4056 +#: Source/items.cpp:4063 msgid "2x dmg to monst, 1x to you" msgstr "モンスター​に​2​倍​の​ダメージ、自分​に​1​倍​の​ダメージ" -#: Source/items.cpp:4058 +#: Source/items.cpp:4065 #, no-c-format msgid "Random 0 - 600% damage" msgstr "ランダム​に​0​~​600​%​の​ダメージ" -#: Source/items.cpp:4060 +#: Source/items.cpp:4067 #, no-c-format, c++-format msgid "low dur, {:+d}% damage" msgstr "低い​耐久性、{:+d}​%​の​ダメージ" -#: Source/items.cpp:4064 +#: Source/items.cpp:4071 msgid "extra AC vs demons" msgstr "対​悪魔​族​の​追加​AC" -#: Source/items.cpp:4066 +#: Source/items.cpp:4073 msgid "extra AC vs undead" msgstr "対​アンデッド​用​の​追加​AC" -#: Source/items.cpp:4068 +#: Source/items.cpp:4075 msgid "50% Mana moved to Health" msgstr "50​%​の​マナ​を​ヘルス​に​移動" -#: Source/items.cpp:4070 +#: Source/items.cpp:4077 msgid "40% Health moved to Mana" msgstr "40​%​の​ヘルス​を​マナ​に​移動" -#: Source/items.cpp:4113 Source/items.cpp:4154 +#: Source/items.cpp:4120 Source/items.cpp:4161 #, c++-format msgid "damage: {:d} Indestructible" msgstr "ダメージ:{:d} 壊れ​ない" #. TRANSLATORS: Dur: is durability -#: Source/items.cpp:4115 Source/items.cpp:4156 +#: Source/items.cpp:4122 Source/items.cpp:4163 #, c++-format msgid "damage: {:d} Dur: {:d}/{:d}" msgstr "ダメージ:{:d} 耐久​度:{:d}/{:d}" -#: Source/items.cpp:4118 Source/items.cpp:4159 +#: Source/items.cpp:4125 Source/items.cpp:4166 #, c++-format msgid "damage: {:d}-{:d} Indestructible" msgstr "ダメージ: {:d}-{:d} 壊れ​ない" #. TRANSLATORS: Dur: is durability -#: Source/items.cpp:4120 Source/items.cpp:4161 +#: Source/items.cpp:4127 Source/items.cpp:4168 #, c++-format msgid "damage: {:d}-{:d} Dur: {:d}/{:d}" msgstr "ダメージ:{:d}-{:d} 耐久​度:{:d}/{:d}" -#: Source/items.cpp:4125 Source/items.cpp:4171 +#: Source/items.cpp:4132 Source/items.cpp:4178 #, c++-format msgid "armor: {:d} Indestructible" msgstr "防御​力:{:d} 壊れ​ない" #. TRANSLATORS: Dur: is durability -#: Source/items.cpp:4127 Source/items.cpp:4173 +#: Source/items.cpp:4134 Source/items.cpp:4180 #, c++-format msgid "armor: {:d} Dur: {:d}/{:d}" msgstr "防御​力: {:d} 耐久​度: {:d}/{:d}" -#: Source/items.cpp:4130 Source/items.cpp:4164 Source/items.cpp:4177 -#: Source/stores.cpp:301 +#: Source/items.cpp:4137 Source/items.cpp:4171 Source/items.cpp:4184 +#: Source/stores.cpp:303 #, c++-format msgid "Charges: {:d}/{:d}" msgstr "チャージ: {:d}/{:d}" -#: Source/items.cpp:4139 +#: Source/items.cpp:4146 msgid "unique item" msgstr "ユニーク​アイテム" -#: Source/items.cpp:4167 Source/items.cpp:4175 Source/items.cpp:4181 +#: Source/items.cpp:4174 Source/items.cpp:4182 Source/items.cpp:4188 msgid "Not Identified" msgstr "未​鑑定" @@ -3347,7 +3385,7 @@ msgid "Chamber of Bone" msgstr "納骨堂" #. TRANSLATORS: Quest Map -#: Source/levels/setmaps.cpp:29 Source/quests.cpp:78 +#: Source/levels/setmaps.cpp:29 Source/quests.cpp:80 msgid "Maze" msgstr "迷宮" @@ -3444,123 +3482,111 @@ msgstr "地下​聖堂​へ​の​入り口 レベル {:d}" msgid "Back to Level {:d}" msgstr "レベル​{:d}​へ​戻る" -#: Source/loadsave.cpp:2013 Source/loadsave.cpp:2470 +#: Source/loadsave.cpp:2016 Source/loadsave.cpp:2473 msgid "Unable to open save file archive" msgstr "保存​ファイル​の​アーカイブ​を​開け​ませ​ん" -#: Source/loadsave.cpp:2424 +#: Source/loadsave.cpp:2427 msgid "" "Stash version invalid. If you attempt to access your stash, data will be " "overwritten!!" msgstr "" +"スタッシュ​の​バージョン​が​不正​です。スタッシュ​に​アクセス​する​と、データ​が​上書き​" +"さ​れ​ます!!」" -#: Source/loadsave.cpp:2443 +#: Source/loadsave.cpp:2446 msgid "" "Stash size invalid. If you attempt to access your stash, data will be " "overwritten!!" msgstr "" +"スタッシュ​の​サイズ​が​不正​です。スタッシュ​に​アクセス​する​と、データ​が​上書き​さ​れ​" +"ます!!" -#: Source/loadsave.cpp:2474 +#: Source/loadsave.cpp:2477 msgid "Invalid save file" msgstr "無効​な​保存​ファイル" -#: Source/loadsave.cpp:2506 +#: Source/loadsave.cpp:2509 msgid "Player is on a Hellfire only level" msgstr "プレイヤー​は​Hellfire​のみ​の​レベル​に​い​ます" -#: Source/loadsave.cpp:2772 +#: Source/loadsave.cpp:2775 msgid "Invalid game state" msgstr "無効​な​ゲーム​状態" -#: Source/menu.cpp:157 +#: Source/menu.cpp:164 msgid "Unable to display mainmenu" msgstr "メインメニュー​を​表示​でき​ませ​ん" -#: Source/monstdat.cpp:331 Source/monstdat.cpp:344 -msgid "Loading Monster Data Failed" -msgstr "" - -#: Source/monstdat.cpp:331 -#, c++-format -msgid "" -"Could not add a monster, since the maximum monster type number of {} has " -"already been reached." -msgstr "" - -#: Source/monstdat.cpp:344 -#, c++-format -msgid "A monster type already exists for ID \"{}\"." -msgstr "" - -#: Source/monster.cpp:2990 +#: Source/monster.cpp:2997 msgid "Animal" msgstr "アニマル" -#: Source/monster.cpp:2992 +#: Source/monster.cpp:2999 msgid "Demon" msgstr "デーモン" -#: Source/monster.cpp:2994 +#: Source/monster.cpp:3001 msgid "Undead" msgstr "アンデッド" -#: Source/monster.cpp:4413 +#: Source/monster.cpp:4420 #, c++-format msgid "Type: {:s} Kills: {:d}" msgstr "タイプ: {:s} 殺傷​数: {:d}" -#: Source/monster.cpp:4415 +#: Source/monster.cpp:4422 #, c++-format msgid "Total kills: {:d}" msgstr "殺傷​数: {:d}" -#: Source/monster.cpp:4441 +#: Source/monster.cpp:4448 #, c++-format msgid "Hit Points: {:d}-{:d}" msgstr "ヒット​ポイント: {:d}-{:d}" -#: Source/monster.cpp:4446 +#: Source/monster.cpp:4453 msgid "No magic resistance" msgstr "魔法​耐性​無し" -#: Source/monster.cpp:4449 +#: Source/monster.cpp:4456 msgid "Resists:" msgstr "耐性:" -#: Source/monster.cpp:4451 Source/monster.cpp:4461 +#: Source/monster.cpp:4458 Source/monster.cpp:4468 msgid " Magic" msgstr " 魔法" -#: Source/monster.cpp:4453 Source/monster.cpp:4463 +#: Source/monster.cpp:4460 Source/monster.cpp:4470 msgid " Fire" msgstr " 火炎" -#: Source/monster.cpp:4455 Source/monster.cpp:4465 +#: Source/monster.cpp:4462 Source/monster.cpp:4472 msgid " Lightning" msgstr " ライトニング" -#: Source/monster.cpp:4459 +#: Source/monster.cpp:4466 msgid "Immune:" msgstr "無効:" -#: Source/monster.cpp:4476 +#: Source/monster.cpp:4483 #, c++-format msgid "Type: {:s}" msgstr "タイプ: {:s}" -#: Source/monster.cpp:4481 Source/monster.cpp:4487 +#: Source/monster.cpp:4488 Source/monster.cpp:4494 msgid "No resistances" msgstr "耐性​無し" -#: Source/monster.cpp:4482 Source/monster.cpp:4491 +#: Source/monster.cpp:4489 Source/monster.cpp:4498 msgid "No Immunities" msgstr "無効​無し" -#: Source/monster.cpp:4485 +#: Source/monster.cpp:4492 msgid "Some Magic Resistances" msgstr "魔法​耐性​あり​(​属性​不明​)" -#: Source/monster.cpp:4489 +#: Source/monster.cpp:4496 msgid "Some Magic Immunities" msgstr "魔法​無効化​(​属性​不明​)" @@ -3568,448 +3594,446 @@ msgstr "魔法​無効化​(​属性​不明​)" msgid "Failed to open archive for writing." msgstr "書き込み​の​ため​の​アーカイブ​を​開く​の​に​失敗​し​まし​た。" -#: Source/msg.cpp:1701 +#: Source/msg.cpp:1693 #, c++-format msgid "{:s} has cast an invalid spell." msgstr "{:s} が​無効​な​呪文​を​唱え​た。" -#: Source/msg.cpp:1705 +#: Source/msg.cpp:1697 #, c++-format msgid "{:s} has cast an illegal spell." msgstr "{:s} が​不正​な​呪文​を​唱え​た。" -#: Source/msg.cpp:2286 Source/multi.cpp:836 Source/multi.cpp:886 +#: Source/msg.cpp:2298 Source/multi.cpp:877 Source/multi.cpp:937 #, c++-format msgid "Player '{:s}' (level {:d}) just joined the game" msgstr "プレイヤー​「​{:s}​」​(​レベル​{:d}​)​が​ゲーム​に​参加​し​まし​た" -#: Source/msg.cpp:2718 +#: Source/msg.cpp:2730 msgid "The game ended" msgstr "ゲーム​終了" -#: Source/msg.cpp:2724 +#: Source/msg.cpp:2736 msgid "Unable to get level data" msgstr "レベル​データ​が​取得​でき​ませ​ん" -#: Source/multi.cpp:283 +#: Source/multi.cpp:294 #, c++-format msgid "Player '{:s}' just left the game" msgstr "プレイヤー​「​{:s}​」​が​ゲーム​から​離脱​し​まし​た" -#: Source/multi.cpp:286 +#: Source/multi.cpp:299 #, c++-format msgid "Player '{:s}' killed Diablo and left the game!" msgstr "プレイヤー​「​{:s}​」​が​Diablo​を​殺し​て​ゲーム​を​去っ​た​!" -#: Source/multi.cpp:290 +#: Source/multi.cpp:303 #, c++-format msgid "Player '{:s}' dropped due to timeout" msgstr "プレイヤー​「​{:s}​」​が​タイムアウト​で​脱落" -#: Source/multi.cpp:888 +#: Source/multi.cpp:941 #, c++-format msgid "Player '{:s}' (level {:d}) is already in the game" msgstr "プレイヤー​「​{:s}​」​(​レベル​{:d}​)​は​すでに​ゲーム​に​参加​し​て​い​ます" #. TRANSLATORS: Shrine Name Block -#: Source/objects.cpp:127 +#: Source/objects.cpp:126 msgid "Mysterious" msgstr "奇怪​な" -#: Source/objects.cpp:128 +#: Source/objects.cpp:127 msgid "Hidden" msgstr "ヒドゥン" -#: Source/objects.cpp:129 +#: Source/objects.cpp:128 msgid "Gloomy" msgstr "漆黒​の" -#: Source/objects.cpp:130 Source/translation_dummy.cpp:460 +#: Source/objects.cpp:129 Source/translation_dummy.cpp:460 msgid "Weird" msgstr "ウィアード" -#: Source/objects.cpp:131 Source/objects.cpp:138 +#: Source/objects.cpp:130 Source/objects.cpp:137 msgid "Magical" msgstr "魔術​の" -#: Source/objects.cpp:132 +#: Source/objects.cpp:131 msgid "Stone" msgstr "石​の" -#: Source/objects.cpp:133 +#: Source/objects.cpp:132 msgid "Religious" msgstr "信仰​の" -#: Source/objects.cpp:134 +#: Source/objects.cpp:133 msgid "Enchanted" msgstr "帯​魔​の" -#: Source/objects.cpp:135 +#: Source/objects.cpp:134 msgid "Thaumaturgic" msgstr "奇跡​の" -#: Source/objects.cpp:136 +#: Source/objects.cpp:135 msgid "Fascinating" msgstr "魅了​の" -#: Source/objects.cpp:137 +#: Source/objects.cpp:136 msgid "Cryptic" msgstr "秘密​の" -#: Source/objects.cpp:139 +#: Source/objects.cpp:138 msgid "Eldritch" msgstr "妖魔​の" -#: Source/objects.cpp:140 +#: Source/objects.cpp:139 msgid "Eerie" msgstr "不審​な" -#: Source/objects.cpp:141 +#: Source/objects.cpp:140 msgid "Divine" msgstr "神々しい" -#: Source/objects.cpp:142 Source/translation_dummy.cpp:494 +#: Source/objects.cpp:141 Source/translation_dummy.cpp:494 msgid "Holy" msgstr "ホーリー" -#: Source/objects.cpp:143 +#: Source/objects.cpp:142 msgid "Sacred" msgstr "厳粛​なる" -#: Source/objects.cpp:144 +#: Source/objects.cpp:143 msgid "Spiritual" msgstr "魂​の" -#: Source/objects.cpp:145 +#: Source/objects.cpp:144 msgid "Spooky" msgstr "憑霊​の" -#: Source/objects.cpp:146 +#: Source/objects.cpp:145 msgid "Abandoned" msgstr "荒涼​たる" -#: Source/objects.cpp:147 +#: Source/objects.cpp:146 msgid "Creepy" msgstr "陰湿​な" -#: Source/objects.cpp:148 +#: Source/objects.cpp:147 msgid "Quiet" msgstr "終了" -#: Source/objects.cpp:149 +#: Source/objects.cpp:148 msgid "Secluded" msgstr "隠遁​の" -#: Source/objects.cpp:150 +#: Source/objects.cpp:149 msgid "Ornate" msgstr "華麗​なる" -#: Source/objects.cpp:151 +#: Source/objects.cpp:150 msgid "Glimmering" msgstr "燐光​の" -#: Source/objects.cpp:152 +#: Source/objects.cpp:151 msgid "Tainted" msgstr "穢れ​た" -#: Source/objects.cpp:153 +#: Source/objects.cpp:152 msgid "Oily" msgstr "油性" -#: Source/objects.cpp:154 +#: Source/objects.cpp:153 msgid "Glowing" msgstr "光る" -#: Source/objects.cpp:155 +#: Source/objects.cpp:154 msgid "Mendicant's" msgstr "メンディキャンズ" -#: Source/objects.cpp:156 +#: Source/objects.cpp:155 msgid "Sparkling" msgstr "スパークリング" -#: Source/objects.cpp:158 +#: Source/objects.cpp:157 msgid "Shimmering" msgstr "煌めき" -#: Source/objects.cpp:159 +#: Source/objects.cpp:158 msgid "Solar" msgstr "ソーラー" #. TRANSLATORS: Shrine Name Block end -#: Source/objects.cpp:161 +#: Source/objects.cpp:160 msgid "Murphy's" msgstr "マーフィーズ" #. TRANSLATORS: Book Title -#: Source/objects.cpp:214 +#: Source/objects.cpp:213 msgid "The Great Conflict" msgstr "偉大​なる​闘争" #. TRANSLATORS: Book Title -#: Source/objects.cpp:215 +#: Source/objects.cpp:214 msgid "The Wages of Sin are War" msgstr "罪​深き​闘い" #. TRANSLATORS: Book Title -#: Source/objects.cpp:216 +#: Source/objects.cpp:215 msgid "The Tale of the Horadrim" msgstr "ホラドリム​の​物語" #. TRANSLATORS: Book Title -#: Source/objects.cpp:217 +#: Source/objects.cpp:216 msgid "The Dark Exile" msgstr "闇​の​追放" #. TRANSLATORS: Book Title -#: Source/objects.cpp:218 +#: Source/objects.cpp:217 msgid "The Sin War" msgstr "罪​深き​闘い" #. TRANSLATORS: Book Title -#: Source/objects.cpp:219 +#: Source/objects.cpp:218 msgid "The Binding of the Three" msgstr "三​兄弟​の​呪縛" #. TRANSLATORS: Book Title -#: Source/objects.cpp:220 +#: Source/objects.cpp:219 msgid "The Realms Beyond" msgstr "彼方​の​王国" #. TRANSLATORS: Book Title -#: Source/objects.cpp:221 +#: Source/objects.cpp:220 msgid "Tale of the Three" msgstr "三​大​邪悪​の​物語" #. TRANSLATORS: Book Title -#: Source/objects.cpp:222 +#: Source/objects.cpp:221 msgid "The Black King" msgstr "暗黒​の​王" #. TRANSLATORS: Book Title -#: Source/objects.cpp:223 +#: Source/objects.cpp:222 msgid "Journal: The Ensorcellment" msgstr "ジャーナル: 包囲" #. TRANSLATORS: Book Title -#: Source/objects.cpp:224 +#: Source/objects.cpp:223 msgid "Journal: The Meeting" msgstr "ジャーナル: 会議" #. TRANSLATORS: Book Title -#: Source/objects.cpp:225 +#: Source/objects.cpp:224 msgid "Journal: The Tirade" msgstr "ジャーナル: ティラード" #. TRANSLATORS: Book Title -#: Source/objects.cpp:226 +#: Source/objects.cpp:225 msgid "Journal: His Power Grows" msgstr "ジャーナル: 勢力​拡大" #. TRANSLATORS: Book Title -#: Source/objects.cpp:227 +#: Source/objects.cpp:226 msgid "Journal: NA-KRUL" msgstr "ジャーナル: ナ​・​クルル" #. TRANSLATORS: Book Title -#: Source/objects.cpp:228 +#: Source/objects.cpp:227 msgid "Journal: The End" msgstr "ジャーナル: 終焉" #. TRANSLATORS: Book Title -#: Source/objects.cpp:229 +#: Source/objects.cpp:228 msgid "A Spellbook" msgstr "スペル​ブック" -#: Source/objects.cpp:4795 +#: Source/objects.cpp:4792 msgid "Crucified Skeleton" msgstr "はりつけ​の​スケルトン" -#: Source/objects.cpp:4799 +#: Source/objects.cpp:4796 msgid "Lever" msgstr "レバー" -#: Source/objects.cpp:4809 +#: Source/objects.cpp:4806 msgid "Open Door" msgstr "開い​た​ドア" -#: Source/objects.cpp:4811 +#: Source/objects.cpp:4808 msgid "Closed Door" msgstr "閉じ​た​ドア" -#: Source/objects.cpp:4813 +#: Source/objects.cpp:4810 msgid "Blocked Door" msgstr "閉じ​られ​ない​ドア" -#: Source/objects.cpp:4818 +#: Source/objects.cpp:4815 msgid "Ancient Tome" msgstr "太古​の​書" -#: Source/objects.cpp:4820 +#: Source/objects.cpp:4817 msgid "Book of Vileness" msgstr "外道​の​書" -#: Source/objects.cpp:4825 +#: Source/objects.cpp:4822 msgid "Skull Lever" msgstr "ドクロ​の​レバー" -#: Source/objects.cpp:4827 +#: Source/objects.cpp:4824 msgid "Mythical Book" msgstr "神話​の​書" -#: Source/objects.cpp:4830 +#: Source/objects.cpp:4827 msgid "Small Chest" msgstr "小さな​チェスト" -#: Source/objects.cpp:4833 +#: Source/objects.cpp:4830 msgid "Chest" msgstr "チェスト" -#: Source/objects.cpp:4837 +#: Source/objects.cpp:4834 msgid "Large Chest" msgstr "大きな​チェスト" -#: Source/objects.cpp:4840 +#: Source/objects.cpp:4837 msgid "Sarcophagus" msgstr "棺" -#: Source/objects.cpp:4842 +#: Source/objects.cpp:4839 msgid "Bookshelf" msgstr "読​台" -#: Source/objects.cpp:4845 +#: Source/objects.cpp:4842 msgid "Bookcase" msgstr "本棚" -#: Source/objects.cpp:4848 +#: Source/objects.cpp:4845 msgid "Barrel" msgstr "樽" -#: Source/objects.cpp:4851 +#: Source/objects.cpp:4848 msgid "Pod" msgstr "ポッド" -#: Source/objects.cpp:4854 +#: Source/objects.cpp:4851 msgid "Urn" msgstr "Urn" #. TRANSLATORS: {:s} will be a name from the Shrine block above -#: Source/objects.cpp:4857 +#: Source/objects.cpp:4854 #, c++-format msgid "{:s} Shrine" msgstr "{:s} 祭壇" -#: Source/objects.cpp:4859 +#: Source/objects.cpp:4856 msgid "Skeleton Tome" msgstr "スケルトン​の​書" -#: Source/objects.cpp:4861 +#: Source/objects.cpp:4858 msgid "Library Book" msgstr "蔵書" -#: Source/objects.cpp:4863 +#: Source/objects.cpp:4860 msgid "Blood Fountain" msgstr "血色​の​泉" -#: Source/objects.cpp:4865 +#: Source/objects.cpp:4862 msgid "Decapitated Body" msgstr "首​無し​の​死体" -#: Source/objects.cpp:4867 +#: Source/objects.cpp:4864 msgid "Book of the Blind" msgstr "盲目​の​書" -#: Source/objects.cpp:4869 +#: Source/objects.cpp:4866 msgid "Book of Blood" msgstr "血潮​の​書" -#: Source/objects.cpp:4871 +#: Source/objects.cpp:4868 msgid "Purifying Spring" msgstr "清浄​なる​泉" -#: Source/objects.cpp:4874 Source/translation_dummy.cpp:275 +#: Source/objects.cpp:4871 Source/translation_dummy.cpp:275 msgid "Armor" msgstr "防具" -#: Source/objects.cpp:4876 Source/objects.cpp:4893 +#: Source/objects.cpp:4873 Source/objects.cpp:4890 msgid "Weapon Rack" msgstr "武器​架" -#: Source/objects.cpp:4878 +#: Source/objects.cpp:4875 msgid "Goat Shrine" msgstr "山羊​の​祭壇" -#: Source/objects.cpp:4880 +#: Source/objects.cpp:4877 msgid "Cauldron" msgstr "地獄​の​大釜" -#: Source/objects.cpp:4882 +#: Source/objects.cpp:4879 msgid "Murky Pool" msgstr "濁っ​た​池" -#: Source/objects.cpp:4884 +#: Source/objects.cpp:4881 msgid "Fountain of Tears" msgstr "涙​の​源泉" -#: Source/objects.cpp:4886 +#: Source/objects.cpp:4883 msgid "Steel Tome" msgstr "刀剣​の​書" -#: Source/objects.cpp:4888 +#: Source/objects.cpp:4885 msgid "Pedestal of Blood" msgstr "血​の​台座" -#: Source/objects.cpp:4895 +#: Source/objects.cpp:4892 msgid "Mushroom Patch" msgstr "キノコ​の​群生" -#: Source/objects.cpp:4897 +#: Source/objects.cpp:4894 msgid "Vile Stand" msgstr "忌まわし​き​台座" -#: Source/objects.cpp:4899 +#: Source/objects.cpp:4896 msgid "Slain Hero" msgstr "死​せ​る​勇者" #. TRANSLATORS: {:s} will either be a chest or a door -#: Source/objects.cpp:4912 +#: Source/objects.cpp:4909 #, c++-format msgid "Trapped {:s}" msgstr "トラップ​の​掛かっ​た​{:s}" #. TRANSLATORS: If user enabled diablo.ini setting "Disable Crippling Shrines" is set to 1; also used for Na-Kruls lever -#: Source/objects.cpp:4917 +#: Source/objects.cpp:4914 #, c++-format msgid "{:s} (disabled)" msgstr "{:s} (​無効​)" -#: Source/options.cpp:310 Source/options.cpp:447 Source/options.cpp:453 +#: Source/options.cpp:321 Source/options.cpp:458 Source/options.cpp:464 msgid "ON" msgstr "オン" -#: Source/options.cpp:310 Source/options.cpp:445 Source/options.cpp:451 +#: Source/options.cpp:321 Source/options.cpp:456 Source/options.cpp:462 msgid "OFF" msgstr "オフ" -#: Source/options.cpp:422 Source/options.cpp:423 +#: Source/options.cpp:433 Source/options.cpp:434 msgid "Game Mode" msgstr "ゲーム​モード" -#: Source/options.cpp:422 -#, fuzzy -#| msgid "Gameplay Settings" +#: Source/options.cpp:433 msgid "Game Mode Settings" -msgstr "ゲーム​プレイ​設定" +msgstr "ゲーム​モード​設定" -#: Source/options.cpp:423 +#: Source/options.cpp:434 msgid "Play Diablo or Hellfire." msgstr "ディアブロ、また​は​ヘルファイア​を​選択​し​ます。" -#: Source/options.cpp:429 +#: Source/options.cpp:440 msgid "Restrict to Shareware" msgstr "シェアウェア​の​制限" -#: Source/options.cpp:429 +#: Source/options.cpp:440 msgid "" "Makes the game compatible with the demo. Enables multiplayer with friends " "who don't own a full copy of Diablo." @@ -4017,113 +4041,111 @@ msgstr "" "ゲーム​を​体験版​と​互換性​の​ある​もの​に​し​ます。ディアブロ​の​製品​版​を​持っ​て​い​ない​友" "人​と​の​マルチ​プレイ​が​可能​に​なり​ます。" -#: Source/options.cpp:442 +#: Source/options.cpp:453 msgid "Start Up" msgstr "起動" -#: Source/options.cpp:442 +#: Source/options.cpp:453 msgid "Start Up Settings" msgstr "起動​時​の​設定" -#: Source/options.cpp:443 Source/options.cpp:449 +#: Source/options.cpp:454 Source/options.cpp:460 msgid "Intro" msgstr "イントロ" -#: Source/options.cpp:443 Source/options.cpp:449 +#: Source/options.cpp:454 Source/options.cpp:460 msgid "Shown Intro cinematic." msgstr "イントロ​・​シネマ​ティック​を​表示​し​ます。" -#: Source/options.cpp:455 +#: Source/options.cpp:466 msgid "Splash" msgstr "スプラッシュ" -#: Source/options.cpp:455 +#: Source/options.cpp:466 msgid "Shown splash screen." msgstr "スプラッシュ​画面​を​設定​し​ます。" -#: Source/options.cpp:457 +#: Source/options.cpp:468 msgid "Logo and Title Screen" msgstr "ロゴ​と​タイトル​画面" -#: Source/options.cpp:458 +#: Source/options.cpp:469 msgid "Title Screen" msgstr "タイトル​画面" -#: Source/options.cpp:473 +#: Source/options.cpp:484 msgid "Diablo specific Settings" msgstr "ディアブロ​固有​の​設定" -#: Source/options.cpp:487 +#: Source/options.cpp:498 msgid "Hellfire specific Settings" msgstr "ヘルファイア​固有​の​設定" -#: Source/options.cpp:501 +#: Source/options.cpp:512 msgid "Audio" msgstr "オーディオ" -#: Source/options.cpp:501 +#: Source/options.cpp:512 msgid "Audio Settings" msgstr "オーディオ​設定" -#: Source/options.cpp:504 +#: Source/options.cpp:515 msgid "Walking Sound" msgstr "歩行​音" -#: Source/options.cpp:504 +#: Source/options.cpp:515 msgid "Player emits sound when walking." msgstr "歩行​時​に​音​が​出る​よう​に​なり​ます。" -#: Source/options.cpp:505 +#: Source/options.cpp:516 msgid "Auto Equip Sound" msgstr "自動​装備​サウンド" -#: Source/options.cpp:505 +#: Source/options.cpp:516 msgid "Automatically equipping items on pickup emits the equipment sound." msgstr "ピックアップ​時​に​自動的​に​アイテム​を​装備​する​と、装備​音​が​鳴り​ます。" -#: Source/options.cpp:506 +#: Source/options.cpp:517 msgid "Item Pickup Sound" msgstr "アイテム​回収​音" -#: Source/options.cpp:506 +#: Source/options.cpp:517 msgid "Picking up items emits the items pickup sound." msgstr "アイテム​を​拾う​と、効果音​が​鳴り​ます。" -#: Source/options.cpp:507 +#: Source/options.cpp:518 msgid "Sample Rate" msgstr "サンプリングレート" -#: Source/options.cpp:507 +#: Source/options.cpp:518 msgid "Output sample rate (Hz)." msgstr "出力​サンプリングレート (​Hz​)。" -#: Source/options.cpp:508 +#: Source/options.cpp:519 msgid "Channels" msgstr "チャンネル" -#: Source/options.cpp:508 +#: Source/options.cpp:519 msgid "Number of output channels." msgstr "出力​チャンネル​数​です。" -#: Source/options.cpp:509 +#: Source/options.cpp:520 msgid "Buffer Size" msgstr "バッファ​サイズ" -#: Source/options.cpp:509 +#: Source/options.cpp:520 msgid "Buffer size (number of frames per channel)." msgstr "バッファ​サイズ​(​1​チャンネル​あたり​の​フレーム​数​)​です。" -#: Source/options.cpp:510 +#: Source/options.cpp:521 msgid "Resampling Quality" msgstr "再​サンプリング​品質" -#: Source/options.cpp:510 -#, fuzzy -#| msgid "Quality of the resampler, from 0 (lowest) to 10 (highest)." +#: Source/options.cpp:521 msgid "Quality of the resampler, from 0 (lowest) to 5 (highest)." -msgstr "再​サンプラー​の​品質​を​0​(​最低​)​から​10​(​最高​)​まで​で​設定​し​ます。" +msgstr "再​サンプラー​の​品質​を​0​(​最低​)​から​5​(​最高​)​まで​で​設定​し​ます。" -#: Source/options.cpp:535 +#: Source/options.cpp:546 msgid "" "Affect the game's internal resolution and determine your view area. Note: " "This can differ from screen resolution, when Upscaling, Integer Scaling or " @@ -4133,43 +4155,43 @@ msgstr "" "グ、整数​倍​スケーリング、画面​に​合わせる​を​使用​し​た​場合、画面​解像度​と​は​異なる​場" "合​が​あり​ます。" -#: Source/options.cpp:574 +#: Source/options.cpp:585 msgid "Resampler" msgstr "リサンプラ" -#: Source/options.cpp:574 +#: Source/options.cpp:585 msgid "Audio resampler" msgstr "オーディオ​・​リサンプラ" -#: Source/options.cpp:631 +#: Source/options.cpp:642 msgid "Device" msgstr "デバイス" -#: Source/options.cpp:631 +#: Source/options.cpp:642 msgid "Audio device" msgstr "オーディオ​・​デバイス" -#: Source/options.cpp:688 +#: Source/options.cpp:739 msgid "Graphics" msgstr "グラフィックス" -#: Source/options.cpp:688 +#: Source/options.cpp:739 msgid "Graphics Settings" msgstr "グラフィックス​設定" -#: Source/options.cpp:689 +#: Source/options.cpp:740 msgid "Fullscreen" msgstr "フルスクリーン" -#: Source/options.cpp:689 +#: Source/options.cpp:740 msgid "Display the game in windowed or fullscreen mode." msgstr "ゲーム​を​ウィンドウ​モード​また​は​フルスクリーンモード​で​表示​し​ます。" -#: Source/options.cpp:691 +#: Source/options.cpp:742 msgid "Fit to Screen" msgstr "画面​に​合わせる" -#: Source/options.cpp:691 +#: Source/options.cpp:742 msgid "" "Automatically adjust the game window to your current desktop screen aspect " "ratio and resolution." @@ -4177,11 +4199,11 @@ msgstr "" "ゲーム​ウィンドウ​を​現在​の​デスクトップ​画面​の​アスペクト​比​と​解像度​に​自動的​に​調整​" "し​ます。" -#: Source/options.cpp:700 +#: Source/options.cpp:751 msgid "Upscale" msgstr "アップ​スケーリング" -#: Source/options.cpp:700 +#: Source/options.cpp:751 msgid "" "Enables image scaling from the game resolution to your monitor resolution. " "Prevents changing the monitor resolution and allows window resizing." @@ -4189,125 +4211,125 @@ msgstr "" "ゲーム​の​解像度​から​モニター​の​解像度​へ​の​画像​の​スケーリング​を​可能​に​し​ます。モニ" "ター​解像度​の​変更​を​防ぎ、ウィンドウ​の​リサイズ​を​可能​に​し​ます。" -#: Source/options.cpp:707 +#: Source/options.cpp:758 msgid "Scaling Quality" msgstr "スケーリング​品質" -#: Source/options.cpp:707 +#: Source/options.cpp:758 msgid "Enables optional filters to the output image when upscaling." msgstr "アップス​ケール​時​の​出力​画像​に​オプション​の​フィルター​を​有効​に​し​ます。" -#: Source/options.cpp:709 +#: Source/options.cpp:760 msgid "Nearest Pixel" msgstr "近接​ピクセル" -#: Source/options.cpp:710 +#: Source/options.cpp:761 msgid "Bilinear" msgstr "バイリニア" -#: Source/options.cpp:711 +#: Source/options.cpp:762 msgid "Anisotropic" msgstr "異方​性" -#: Source/options.cpp:713 +#: Source/options.cpp:764 msgid "Integer Scaling" msgstr "整数​倍​スケーリング" -#: Source/options.cpp:713 +#: Source/options.cpp:764 msgid "Scales the image using whole number pixel ratio." msgstr "整数​の​比率​で​画像​を​スケーリング​し​ます。" -#: Source/options.cpp:721 +#: Source/options.cpp:772 msgid "Frame Rate Control" -msgstr "" +msgstr "フレーム​レート​制御" -#: Source/options.cpp:722 +#: Source/options.cpp:773 msgid "" "Manages frame rate to balance performance, reduce tearing, or save power." msgstr "" +"画面​表示​の​乱れ​を​抑え、パフォーマンス​と​消費​電力​の​バランス​を​取る​ため​に​フレーム​" +"レート​を​管理​し​ます。" -#: Source/options.cpp:732 +#: Source/options.cpp:783 msgid "Vertical Sync" msgstr "垂直​同期" -#: Source/options.cpp:734 +#: Source/options.cpp:785 msgid "Limit FPS" -msgstr "" +msgstr "フレーム​レート​制限" -#: Source/options.cpp:737 +#: Source/options.cpp:788 msgid "Zoom on when enabled." msgstr "有効​な​場合​は​ズーム。" -#: Source/options.cpp:738 -#, fuzzy -#| msgid " Lightning" +#: Source/options.cpp:789 msgid "Per-pixel Lighting" -msgstr " ライトニング" +msgstr "ピクセル​単位​の​ライティング" -#: Source/options.cpp:738 +#: Source/options.cpp:789 msgid "Subtile lighting for smoother light gradients." -msgstr "" +msgstr "光​の​グラデーション​を​より​滑らか​に​する​ため​の​繊細​な​ライティング。" -#: Source/options.cpp:739 +#: Source/options.cpp:790 msgid "Color Cycling" msgstr "カラー​サイクル" -#: Source/options.cpp:739 +#: Source/options.cpp:790 msgid "Color cycling effect used for water, lava, and acid animation." msgstr "水、溶岩、酸​の​アニメーション​に​使用​さ​れる​カラー​サイクリング​効果​です。" -#: Source/options.cpp:740 +#: Source/options.cpp:791 msgid "Alternate nest art" msgstr "異なる​巣​の​画像" -#: Source/options.cpp:740 +#: Source/options.cpp:791 msgid "The game will use an alternative palette for Hellfire’s nest tileset." msgstr "ヘルファイア​の​巣​の​タイル​セット​に​別​の​パレット​が​使用​さ​れ​ます。" -#: Source/options.cpp:742 +#: Source/options.cpp:793 msgid "Hardware Cursor" msgstr "ハードウェア​カーソル" -#: Source/options.cpp:742 +#: Source/options.cpp:793 msgid "Use a hardware cursor" msgstr "ハードウェア​カーソル​を​使用​し​ます。" -#: Source/options.cpp:743 +#: Source/options.cpp:794 msgid "Hardware Cursor For Items" msgstr "アイテム​用​ハードウェア​カーソル" -#: Source/options.cpp:743 +#: Source/options.cpp:794 msgid "Use a hardware cursor for items." msgstr "アイテム​に​ハードウェア​カーソル​を​使用​し​ます。" -#: Source/options.cpp:744 +#: Source/options.cpp:795 msgid "Hardware Cursor Maximum Size" msgstr "ハードウェア​カーソル​の​最大​サイズ" -#: Source/options.cpp:744 +#: Source/options.cpp:795 msgid "" "Maximum width / height for the hardware cursor. Larger cursors fall back to " "software." msgstr "" "ハードウェア​カーソル​の​最大​幅​/​高さ。それ​以上​の​ソフトウェア​カーソル​に​なり​ます。" -#: Source/options.cpp:746 +#: Source/options.cpp:797 msgid "Show FPS" -msgstr "FPS​を​表示" +msgstr "フレーム​レート​を​表示" -#: Source/options.cpp:746 +#: Source/options.cpp:797 msgid "Displays the FPS in the upper left corner of the screen." msgstr "画面​左上​に​FPS​を​表示​し​ます。" -#: Source/options.cpp:782 +#: Source/options.cpp:833 msgid "Gameplay" msgstr "ゲーム​プレイ" -#: Source/options.cpp:782 +#: Source/options.cpp:833 msgid "Gameplay Settings" msgstr "ゲーム​プレイ​設定" -#: Source/options.cpp:784 +#: Source/options.cpp:835 msgid "" "Enable jogging/fast walking in town for Diablo and Hellfire. This option was " "introduced in the expansion." @@ -4315,38 +4337,38 @@ msgstr "" "ディアブロ​と​ヘルファイア​で​街中​で​の​ジョギング​/​早歩き​を​有効​に​し​ます。この​オプ" "ション​は​拡張​版​で​導入​さ​れ​まし​た。" -#: Source/options.cpp:785 +#: Source/options.cpp:836 msgid "Grab Input" msgstr "入力​捕捉" -#: Source/options.cpp:785 +#: Source/options.cpp:836 msgid "When enabled mouse is locked to the game window." msgstr "有効​に​する​と、マウス​が​ゲーム​ウィンドウ​に​ロック​さ​れ​ます。" -#: Source/options.cpp:786 +#: Source/options.cpp:837 msgid "Pause Game When Window Loses Focus" msgstr "非​アクティブ​時​に​ゲーム​を​一時停止" -#: Source/options.cpp:786 +#: Source/options.cpp:837 msgid "When enabled, the game will pause when focus is lost." msgstr "有効​に​する​と、フォーカス​を​失っ​た​とき​に​ゲーム​が​一時停止​し​ます。" -#: Source/options.cpp:787 +#: Source/options.cpp:838 msgid "Enable Little Girl quest." msgstr "リトル​ガール​クエスト​を​有効​に​し​ます。" -#: Source/options.cpp:788 +#: Source/options.cpp:839 msgid "" "Enable Jersey's quest. Lester the farmer is replaced by the Complete Nut." msgstr "" "ジャージー​の​クエスト​を​有効​に​する。農家​の​レスター​の​代わり​に​コンプリート​ナット​" "が​登場​し​ます。" -#: Source/options.cpp:789 +#: Source/options.cpp:840 msgid "Friendly Fire" msgstr "フレンドリー​ファイア" -#: Source/options.cpp:789 +#: Source/options.cpp:840 msgid "" "Allow arrow/spell damage between players in multiplayer even when the " "friendly mode is on." @@ -4354,144 +4376,141 @@ msgstr "" "フレンドリー​モード​が​オン​に​なっ​て​い​て​も、マルチ​プレイヤー​の​プレイヤー​間​で​弓矢​" "や​呪文​の​ダメージ​を​許容​し​ます。" -#: Source/options.cpp:790 +#: Source/options.cpp:841 msgid "Full quests in Multiplayer" msgstr "マルチ​プレイ​で​の​フルクエスト" -#: Source/options.cpp:790 +#: Source/options.cpp:841 msgid "Enables the full/uncut singleplayer version of quests." msgstr "シングルプレイヤー​版​クエスト​の​フル​/​ノーカット​を​有効​に​する。" -#: Source/options.cpp:791 +#: Source/options.cpp:842 msgid "Test Bard" msgstr "テスト​・​バード" -#: Source/options.cpp:791 +#: Source/options.cpp:842 msgid "Force the Bard character type to appear in the hero selection menu." msgstr "ヒーロー​選択​メニュー​に​バード​の​キャラクター​タイプ​を​強制的​に​表示​し​ます。" -#: Source/options.cpp:792 +#: Source/options.cpp:843 msgid "Test Barbarian" msgstr "テスト​・​バーバリアン" -#: Source/options.cpp:792 +#: Source/options.cpp:843 msgid "" "Force the Barbarian character type to appear in the hero selection menu." msgstr "" "ヒーロー​選択​メニュー​に​バーバリアン​の​キャラクター​タイプ​を​強制的​に​表示​し​ます。" -#: Source/options.cpp:793 +#: Source/options.cpp:844 msgid "Experience Bar" msgstr "経験値​バー" -#: Source/options.cpp:793 +#: Source/options.cpp:844 msgid "Experience Bar is added to the UI at the bottom of the screen." msgstr "画面​下​の​UI​に​経験値​バー​を​追加​し​ます。" -#: Source/options.cpp:794 +#: Source/options.cpp:845 msgid "Show Item Graphics in Stores" msgstr "ストア​で​アイテム​画像​を​表示​する" -#: Source/options.cpp:794 +#: Source/options.cpp:845 msgid "Show item graphics to the left of item descriptions in store menus." msgstr "ストア​メニュー​の​アイテム​説明​の​左側​に​アイテム​画像​を​表示​し​ます。" -#: Source/options.cpp:795 +#: Source/options.cpp:846 msgid "Show health values" msgstr "ヘルス​値​を​表示" -#: Source/options.cpp:795 +#: Source/options.cpp:846 msgid "Displays current / max health value on health globe." msgstr "ヘルス​・​グローブ​上​に​ヘルス​の​現在地​/​最大値​を​表示​し​ます。" -#: Source/options.cpp:796 +#: Source/options.cpp:847 msgid "Show mana values" msgstr "マナ​値​を​表示" -#: Source/options.cpp:796 +#: Source/options.cpp:847 msgid "Displays current / max mana value on mana globe." msgstr "マナ​・​グローブ​上​に​マナ​の​現在​値​/​最大値​を​表示​し​ます。" -#: Source/options.cpp:797 -#, fuzzy -#| msgid "Character Information" +#: Source/options.cpp:848 msgid "Show Party Information" -msgstr "キャラクター​情報" +msgstr "パーティー​情報​を​表示" -#: Source/options.cpp:797 +#: Source/options.cpp:848 msgid "" "Displays the health and mana of all connected multiplayer party members." msgstr "" +"接続​し​て​いる​マルチ​プレイ​の​パーティー​メンバー​全員​の​体力​と​マナ​を​表示​し​ます。" -#: Source/options.cpp:798 +#: Source/options.cpp:849 msgid "Enemy Health Bar" msgstr "敵​の​ヘルスバー" -#: Source/options.cpp:798 +#: Source/options.cpp:849 msgid "Enemy Health Bar is displayed at the top of the screen." msgstr "画面​上部​に​敵​の​ヘルスバー​を​表示​し​ます。" -#: Source/options.cpp:799 +#: Source/options.cpp:850 msgid "Floating Item Info Box" -msgstr "" +msgstr "フローティング​アイテム​情報​ボックス" -#: Source/options.cpp:799 +#: Source/options.cpp:850 msgid "Displays item info in a floating box when hovering over an item." msgstr "" +"アイテム​に​カーソル​を​合わせる​と、フローティング​アイテム​情報​ボックス​に​アイテム​" +"情報​を​表示​し​ます。" -#: Source/options.cpp:800 +#: Source/options.cpp:851 msgid "Gold is automatically collected when in close proximity to the player." msgstr "プレイヤー​付近​の​ゴールド​を​自動的​に​回収​し​ます。" -#: Source/options.cpp:801 +#: Source/options.cpp:852 msgid "" "Elixirs are automatically collected when in close proximity to the player." msgstr "プレイヤー​付近​の​エリクサー​を​自動的​に​回収​し​ます。" -#: Source/options.cpp:802 +#: Source/options.cpp:853 msgid "Oils are automatically collected when in close proximity to the player." msgstr "プレイヤー​付近​の​オイル​を​自動的​に​回収​し​ます。" -#: Source/options.cpp:803 +#: Source/options.cpp:854 msgid "Automatically pickup items in town." msgstr "街中​で​自動的​に​アイテム​を​回収​し​ます。" -#: Source/options.cpp:804 -msgid "Adria will refill your mana when you visit her shop." -msgstr "エイドリア​の​店​を​訪れる​と、マナ​を​補充​し​て​くれ​ます。" - -#: Source/options.cpp:805 +#: Source/options.cpp:855 msgid "" "Weapons will be automatically equipped on pickup or purchase if enabled." msgstr "有効​に​する​と、武器​を​ピックアップ​や​購入​時​に​自動的​に​装備​さ​れ​ます。" -#: Source/options.cpp:806 +#: Source/options.cpp:856 msgid "Armor will be automatically equipped on pickup or purchase if enabled." msgstr "有効​に​する​と、アーマー​を​ピックアップ​や​購入​時​に​自動的​に​装備​さ​れ​ます。" -#: Source/options.cpp:807 +#: Source/options.cpp:857 msgid "Helms will be automatically equipped on pickup or purchase if enabled." msgstr "有効​に​する​と、ヘルム​を​ピックアップ​や​購入​時​に​自動的​に​装備​さ​れ​ます。" -#: Source/options.cpp:808 +#: Source/options.cpp:858 msgid "" "Shields will be automatically equipped on pickup or purchase if enabled." msgstr "有効​に​する​と、シールド​を​ピックアップ​や​購入​時​に​自動的​に​装備​さ​れ​ます。" -#: Source/options.cpp:809 +#: Source/options.cpp:859 msgid "" "Jewelry will be automatically equipped on pickup or purchase if enabled." msgstr "有効​に​する​と、ジュエリー​を​ピックアップ​や​購入​時​に​自動的​に​装備​さ​れ​ます。" -#: Source/options.cpp:810 +#: Source/options.cpp:860 msgid "Randomly selecting available quests for new games." msgstr "新規​ゲーム​で​クエスト​が​ランダム​に​選択​さ​れ​ます。" -#: Source/options.cpp:811 +#: Source/options.cpp:861 msgid "Show Monster Type" msgstr "モンスター​の​種類​を​表示" -#: Source/options.cpp:811 +#: Source/options.cpp:861 msgid "" "Hovering over a monster will display the type of monster in the description " "box in the UI." @@ -4499,15 +4518,15 @@ msgstr "" "モンスター​に​カーソル​を​合わせる​と、UI​の​説明​欄​に​モンスター​の​種類​が​表示​さ​れ​ま" "す。" -#: Source/options.cpp:812 +#: Source/options.cpp:862 msgid "Show labels for items on the ground when enabled." msgstr "有効​な​場合、アイテム​の​ラベル​を​表示​し​ます。" -#: Source/options.cpp:813 +#: Source/options.cpp:863 msgid "Refill belt from inventory when belt item is consumed." msgstr "ベルト​アイテム​が​消費​さ​れる​と、在庫​から​ベルト​を​補充​し​ます。" -#: Source/options.cpp:814 +#: Source/options.cpp:864 msgid "" "When enabled Cauldrons, Fascinating Shrines, Goat Shrines, Ornate Shrines, " "Sacred Shrines and Murphy's Shrines are not able to be clicked on and " @@ -4516,11 +4535,11 @@ msgstr "" "有効​に​する​と、地獄​の​大釜、魅了​の​祭壇、山羊​の​祭壇、華麗​なる​祭壇、神聖​なる​祭壇​" "と​マーフィー​の​祭壇​が​クリック​でき​なく​なり、無効​と​表示​さ​れ​ます。" -#: Source/options.cpp:815 +#: Source/options.cpp:865 msgid "Quick Cast" msgstr "クイック​キャスト" -#: Source/options.cpp:815 +#: Source/options.cpp:865 msgid "" "Spell hotkeys instantly cast the spell, rather than switching the readied " "spell." @@ -4528,111 +4547,89 @@ msgstr "" "呪文​の​ホットキー​は、準備​さ​れ​た​呪文​を​切り替える​の​で​は​なく、瞬時​に​呪文​を​唱え​ま" "す。" -#: Source/options.cpp:816 +#: Source/options.cpp:866 msgid "Number of Healing potions to pick up automatically." msgstr "自動的​に​拾う​ヒーリング​・​ポーション​の​数​です。" -#: Source/options.cpp:817 +#: Source/options.cpp:867 msgid "Number of Full Healing potions to pick up automatically." msgstr "自動的​に​拾う​フルヒーリング​・​ポーション​の​数​です。" -#: Source/options.cpp:818 +#: Source/options.cpp:868 msgid "Number of Mana potions to pick up automatically." msgstr "自動的​に​拾う​マナ​・​ポーション​の​数​です。" -#: Source/options.cpp:819 +#: Source/options.cpp:869 msgid "Number of Full Mana potions to pick up automatically." msgstr "自動的​に​拾う​フルマナ​・​ポーション​の​数​です。" -#: Source/options.cpp:820 +#: Source/options.cpp:870 msgid "Number of Rejuvenation potions to pick up automatically." msgstr "自動的​に​拾う​回復​ポーション​の​数​です。" -#: Source/options.cpp:821 +#: Source/options.cpp:871 msgid "Number of Full Rejuvenation potions to pick up automatically." msgstr "自動的​に​拾う​フル​回復​ポーション​の​数​です。" -#: Source/options.cpp:822 -msgid "Enable floating numbers" -msgstr "浮動​小数​点数​を​有効​に​する" - -#: Source/options.cpp:822 -msgid "Enables floating numbers on gaining XP / dealing damage etc." -msgstr "経験値​や​ダメージ​など​に​浮動​小数​点数​を​使用​する。" - -#: Source/options.cpp:824 -msgid "Off" -msgstr "オフ" - -#: Source/options.cpp:825 -msgid "Random Angles" -msgstr "ランダム​アングル" - -#: Source/options.cpp:826 -msgid "Vertical Only" -msgstr "垂直​のみ" - -#: Source/options.cpp:880 +#: Source/options.cpp:922 msgid "Controller" msgstr "コントローラー" -#: Source/options.cpp:880 +#: Source/options.cpp:922 msgid "Controller Settings" msgstr "コントローラー​設定" -#: Source/options.cpp:889 +#: Source/options.cpp:931 msgid "Network" msgstr "ネットワーク" -#: Source/options.cpp:889 +#: Source/options.cpp:931 msgid "Network Settings" msgstr "ネットワーク​設定" -#: Source/options.cpp:901 +#: Source/options.cpp:943 msgid "Chat" msgstr "チャット" -#: Source/options.cpp:901 +#: Source/options.cpp:943 msgid "Chat Settings" msgstr "チャット​設定" -#: Source/options.cpp:910 Source/options.cpp:1029 +#: Source/options.cpp:952 Source/options.cpp:1069 msgid "Language" msgstr "言語" -#: Source/options.cpp:910 +#: Source/options.cpp:952 msgid "Define what language to use in game." msgstr "ゲーム​内​で​使用​する​言語​を​定義​し​ます。" -#: Source/options.cpp:1029 +#: Source/options.cpp:1069 msgid "Language Settings" msgstr "言語​設定" -#: Source/options.cpp:1040 +#: Source/options.cpp:1080 msgid "Keymapping" msgstr "キーマッピング" -#: Source/options.cpp:1040 +#: Source/options.cpp:1080 msgid "Keymapping Settings" msgstr "キー​・​マップ​設定" -#: Source/options.cpp:1260 +#: Source/options.cpp:1300 msgid "Padmapping" msgstr "パッド​・​マッピング" -#: Source/options.cpp:1260 +#: Source/options.cpp:1300 msgid "Padmapping Settings" msgstr "パッド​・​マップ​設定" -#: Source/options.cpp:1512 +#: Source/options.cpp:1552 msgid "Mods" -msgstr "" +msgstr "MOD" -#: Source/options.cpp:1512 -#, fuzzy -#| msgid "Settings" +#: Source/options.cpp:1552 msgid "Mod Settings" -msgstr "設定" +msgstr "MOD​設定" #: Source/panels/charpanel.cpp:133 msgid "Level" @@ -4683,10 +4680,8 @@ msgid "Armor class" msgstr "防御​力" #: Source/panels/charpanel.cpp:176 -#, fuzzy -#| msgid "chance to hit" msgid "Chance To Hit" -msgstr "上昇​さ​せる" +msgstr "命中​率" #: Source/panels/charpanel.cpp:178 msgid "Damage" @@ -4812,26 +4807,15 @@ msgstr "スタッフ" msgid "Spell Hotkey {:s}" msgstr "スペルホットキー {:s}" -#: Source/pfile.cpp:762 +#: Source/pfile.cpp:767 msgid "Unable to open archive" msgstr "アーカイブ​を​開け​ませ​ん" -#: Source/pfile.cpp:764 +#: Source/pfile.cpp:769 msgid "Unable to load character" msgstr "キャラクター​を​ロード​でき​ませ​ん" -#: Source/playerdat.cpp:320 -msgid "Loading Class Data Failed" -msgstr "" - -#: Source/playerdat.cpp:320 -#, c++-format -msgid "" -"Could not add a class, since the maximum class number of {} has already been " -"reached." -msgstr "" - -#: Source/plrmsg.cpp:79 Source/qol/chatlog.cpp:130 +#: Source/plrmsg.cpp:85 Source/qol/chatlog.cpp:130 #, c++-format msgid "{:s} (lvl {:d}): " msgstr "{:s} (​レベル {:d}): " @@ -4846,7 +4830,7 @@ msgstr "チャット​履歴 (​メッセージ: {:d}​)" msgid "{:s} gold" msgstr "{:s} ゴールド" -#: Source/qol/stash.cpp:648 +#: Source/qol/stash.cpp:657 msgid "How many gold pieces do you want to withdraw?" msgstr "ゴールド​を​どれ​だけ​引き出し​ます​か?" @@ -4875,27 +4859,27 @@ msgid "{:s} to Level {:d}" msgstr "{:s}​レベル​{:d}​へ" #. TRANSLATORS: Quest Map -#: Source/quests.cpp:76 +#: Source/quests.cpp:78 msgid "King Leoric's Tomb" msgstr "レオリック​王​の​墓所" #. TRANSLATORS: Quest Map -#: Source/quests.cpp:77 Source/translation_dummy.cpp:638 +#: Source/quests.cpp:79 Source/translation_dummy.cpp:638 msgid "The Chamber of Bone" msgstr "納骨堂" #. TRANSLATORS: Quest Map -#: Source/quests.cpp:79 +#: Source/quests.cpp:81 msgid "A Dark Passage" msgstr "暗き​通廊" #. TRANSLATORS: Quest Map -#: Source/quests.cpp:80 +#: Source/quests.cpp:82 msgid "Unholy Altar" msgstr "ラザルス​の​間" #. TRANSLATORS: Used for Quest Portals. {:s} is a Map Name -#: Source/quests.cpp:355 +#: Source/quests.cpp:357 #, c++-format msgid "To {:s}" msgstr "{:s}​へ" @@ -4940,383 +4924,367 @@ msgstr "すま​ない。" msgid "I'm waiting." msgstr "待機​中。" -#: Source/stores.cpp:131 +#: Source/stores.cpp:133 msgid "Griswold" msgstr "グリズウォルド" -#: Source/stores.cpp:132 +#: Source/stores.cpp:134 msgid "Pepin" msgstr "ペピン" -#: Source/stores.cpp:134 +#: Source/stores.cpp:136 msgid "Ogden" msgstr "オグデン" -#: Source/stores.cpp:135 +#: Source/stores.cpp:137 msgid "Cain" msgstr "ケイン" -#: Source/stores.cpp:136 +#: Source/stores.cpp:138 msgid "Farnham" msgstr "ファーンハム" -#: Source/stores.cpp:137 +#: Source/stores.cpp:139 msgid "Adria" msgstr "エイドリア" -#: Source/stores.cpp:138 Source/stores.cpp:1267 +#: Source/stores.cpp:140 Source/stores.cpp:1237 msgid "Gillian" msgstr "ジリアン" -#: Source/stores.cpp:139 +#: Source/stores.cpp:141 msgid "Wirt" msgstr "ワート" -#: Source/stores.cpp:265 Source/stores.cpp:272 +#: Source/stores.cpp:267 Source/stores.cpp:274 msgid "Back" msgstr "戻る" -#: Source/stores.cpp:294 Source/stores.cpp:300 Source/stores.cpp:326 +#: Source/stores.cpp:296 Source/stores.cpp:302 Source/stores.cpp:328 msgid ", " msgstr ", " -#: Source/stores.cpp:311 +#: Source/stores.cpp:313 #, c++-format msgid "Damage: {:d}-{:d} " msgstr "ダメージ: {:d}-{:d} " -#: Source/stores.cpp:313 +#: Source/stores.cpp:315 #, c++-format msgid "Armor: {:d} " msgstr "防御​力: {:d} " -#: Source/stores.cpp:315 +#: Source/stores.cpp:317 #, c++-format msgid "Dur: {:d}/{:d}" msgstr "耐久​度: {:d}/{:d}, " -#: Source/stores.cpp:317 +#: Source/stores.cpp:319 msgid "Indestructible" msgstr "壊れ​ない" -#: Source/stores.cpp:387 Source/stores.cpp:1035 Source/stores.cpp:1254 +#: Source/stores.cpp:385 Source/stores.cpp:1008 Source/stores.cpp:1224 msgid "Welcome to the" msgstr "ようこそ​!" -#: Source/stores.cpp:388 +#: Source/stores.cpp:386 msgid "Blacksmith's shop" msgstr "グリズウォルド​の​鍛冶屋​へ" -#: Source/stores.cpp:389 Source/stores.cpp:686 Source/stores.cpp:1037 -#: Source/stores.cpp:1080 Source/stores.cpp:1256 Source/stores.cpp:1268 -#: Source/stores.cpp:1281 +#: Source/stores.cpp:387 Source/stores.cpp:662 Source/stores.cpp:1010 +#: Source/stores.cpp:1050 Source/stores.cpp:1226 Source/stores.cpp:1238 +#: Source/stores.cpp:1251 msgid "Would you like to:" msgstr "何​を​する​?" -#: Source/stores.cpp:390 +#: Source/stores.cpp:388 msgid "Talk to Griswold" msgstr "グリズウォルド​と​話す" -#: Source/stores.cpp:391 +#: Source/stores.cpp:389 msgid "Buy basic items" msgstr "ベーシック​アイテム​を​買う" -#: Source/stores.cpp:392 +#: Source/stores.cpp:390 msgid "Buy premium items" msgstr "プレミアム​アイテム​を​買う" -#: Source/stores.cpp:393 Source/stores.cpp:689 +#: Source/stores.cpp:391 Source/stores.cpp:665 msgid "Sell items" msgstr "アイテム​を​売る" -#: Source/stores.cpp:394 +#: Source/stores.cpp:392 msgid "Repair items" msgstr "アイテム​の​修理" -#: Source/stores.cpp:395 +#: Source/stores.cpp:393 msgid "Leave the shop" msgstr "店​を​出る" -#: Source/stores.cpp:423 Source/stores.cpp:725 Source/stores.cpp:1057 +#: Source/stores.cpp:421 Source/stores.cpp:701 Source/stores.cpp:1030 msgid "I have these items for sale:" msgstr "さて、何​が​欲しい​ん​だ​い" -#: Source/stores.cpp:472 +#: Source/stores.cpp:464 msgid "I have these premium items for sale:" msgstr "どれ​も​珍しい​アイテム​だろう" -#: Source/stores.cpp:568 Source/stores.cpp:818 +#: Source/stores.cpp:560 Source/stores.cpp:791 msgid "You have nothing I want." msgstr "何​も​買える​物​は​ない​よう​だ​が。" -#: Source/stores.cpp:579 Source/stores.cpp:830 +#: Source/stores.cpp:571 Source/stores.cpp:803 msgid "Which item is for sale?" msgstr "何​を​売っ​て​くれる​ん​だ​い​?" -#: Source/stores.cpp:647 +#: Source/stores.cpp:639 msgid "You have nothing to repair." msgstr "何​も​壊れ​ちゃ​い​ない​ぞ。" -#: Source/stores.cpp:658 +#: Source/stores.cpp:650 msgid "Repair which item?" msgstr "どれ​を​直す​ん​だ​?" -#: Source/stores.cpp:685 +#: Source/stores.cpp:661 msgid "Witch's shack" msgstr "魔女​の​小屋" -#: Source/stores.cpp:687 +#: Source/stores.cpp:663 msgid "Talk to Adria" msgstr "エイドリア​と​話す" -#: Source/stores.cpp:688 Source/stores.cpp:1039 +#: Source/stores.cpp:664 Source/stores.cpp:1012 msgid "Buy items" msgstr "アイテム​を​買う" -#: Source/stores.cpp:690 +#: Source/stores.cpp:666 msgid "Recharge staves" msgstr "リチャージ​する" -#: Source/stores.cpp:691 +#: Source/stores.cpp:667 msgid "Leave the shack" msgstr "小屋​を​立ち去る" -#: Source/stores.cpp:892 +#: Source/stores.cpp:865 msgid "You have nothing to recharge." msgstr "リチャージ​できる​もの​は​無い​よ。" -#: Source/stores.cpp:903 +#: Source/stores.cpp:876 msgid "Recharge which item?" msgstr "どれ​に​リチャージ​する​ん​だ​い​?" -#: Source/stores.cpp:916 +#: Source/stores.cpp:889 msgid "You do not have enough gold" msgstr "所持金​が​足り​ませ​ん" -#: Source/stores.cpp:924 +#: Source/stores.cpp:897 msgid "You do not have enough room in inventory" msgstr "置く​場所​が​あり​ませ​ん" -#: Source/stores.cpp:942 +#: Source/stores.cpp:915 msgid "Do we have a deal?" msgstr "よろしい​です​か​?" -#: Source/stores.cpp:945 +#: Source/stores.cpp:918 msgid "Are you sure you want to identify this item?" msgstr "これ​を​鑑定​する​の​だ​ね​?" -#: Source/stores.cpp:951 +#: Source/stores.cpp:924 msgid "Are you sure you want to buy this item?" msgstr "これ​を​買う​の​だ​ね​?" -#: Source/stores.cpp:954 +#: Source/stores.cpp:927 msgid "Are you sure you want to recharge this item?" msgstr "これ​に​リチャージ​する​ん​だ​ね​?" -#: Source/stores.cpp:958 +#: Source/stores.cpp:931 msgid "Are you sure you want to sell this item?" msgstr "これ​を​売っ​て​くれる​ん​だ​ね​?" -#: Source/stores.cpp:961 +#: Source/stores.cpp:934 msgid "Are you sure you want to repair this item?" msgstr "これ​を​直す​ん​だ​な​?" -#: Source/stores.cpp:975 Source/towners.cpp:785 +#: Source/stores.cpp:948 msgid "Wirt the Peg-legged boy" msgstr "義足​の​少年​ワート" -#: Source/stores.cpp:978 Source/stores.cpp:985 +#: Source/stores.cpp:951 Source/stores.cpp:958 msgid "Talk to Wirt" msgstr "ワート​と​話す" -#: Source/stores.cpp:979 +#: Source/stores.cpp:952 msgid "I have something for sale," msgstr "俺​は​いい​もの​を​持っ​て​いる。" -#: Source/stores.cpp:980 +#: Source/stores.cpp:953 msgid "but it will cost 50 gold" msgstr "で​も、50​ゴールド​払わ​なきゃ" -#: Source/stores.cpp:981 +#: Source/stores.cpp:954 msgid "just to take a look. " msgstr "見せ​て​やら​ない​ぜ。" -#: Source/stores.cpp:982 +#: Source/stores.cpp:955 msgid "What have you got?" msgstr "払う​?" -#: Source/stores.cpp:983 Source/stores.cpp:986 Source/stores.cpp:1083 -#: Source/stores.cpp:1271 +#: Source/stores.cpp:956 Source/stores.cpp:959 Source/stores.cpp:1053 +#: Source/stores.cpp:1241 msgid "Say goodbye" msgstr "立ち去る" -#: Source/stores.cpp:996 +#: Source/stores.cpp:969 msgid "I have this item for sale:" msgstr "さて、何​が​欲しい​ん​だ​い" -#: Source/stores.cpp:1013 +#: Source/stores.cpp:986 msgid "Leave" msgstr "去る" -#: Source/stores.cpp:1036 +#: Source/stores.cpp:1009 msgid "Healer's home" msgstr "治療​師​の​家" -#: Source/stores.cpp:1038 +#: Source/stores.cpp:1011 msgid "Talk to Pepin" msgstr "ペピン​と​話す" -#: Source/stores.cpp:1040 +#: Source/stores.cpp:1013 msgid "Leave Healer's home" msgstr "治療​師​の​家​を​去る" -#: Source/stores.cpp:1079 +#: Source/stores.cpp:1049 msgid "The Town Elder" msgstr "町​の​語り部" -#: Source/stores.cpp:1081 +#: Source/stores.cpp:1051 msgid "Talk to Cain" msgstr "ケイン​と​話す" -#: Source/stores.cpp:1082 +#: Source/stores.cpp:1052 msgid "Identify an item" msgstr "鑑定​し​て​もらう" -#: Source/stores.cpp:1175 +#: Source/stores.cpp:1145 msgid "You have nothing to identify." msgstr "何​も​鑑定​する​もの​は​無い​よう​だ​が。" -#: Source/stores.cpp:1186 +#: Source/stores.cpp:1156 msgid "Identify which item?" msgstr "何​を​鑑定​する​の​か​ね​?" -#: Source/stores.cpp:1201 +#: Source/stores.cpp:1171 msgid "This item is:" msgstr "何​を​する​?" -#: Source/stores.cpp:1204 +#: Source/stores.cpp:1174 msgid "Done" msgstr "終わる" -#: Source/stores.cpp:1213 +#: Source/stores.cpp:1183 #, c++-format msgid "Talk to {:s}" msgstr "{:s}​と​話す" -#: Source/stores.cpp:1216 +#: Source/stores.cpp:1186 #, c++-format msgid "Talking to {:s}" msgstr "{:s}​と​話し​て​いる" -#: Source/stores.cpp:1217 +#: Source/stores.cpp:1187 msgid "is not available" msgstr "使用​不能" -#: Source/stores.cpp:1218 +#: Source/stores.cpp:1188 msgid "in the shareware" msgstr "シェアウェア​で" -#: Source/stores.cpp:1219 +#: Source/stores.cpp:1189 msgid "version" msgstr "バージョン" -#: Source/stores.cpp:1246 +#: Source/stores.cpp:1216 msgid "Gossip" msgstr "噂話" -#: Source/stores.cpp:1255 +#: Source/stores.cpp:1225 msgid "Rising Sun" msgstr "日の出​亭" -#: Source/stores.cpp:1257 +#: Source/stores.cpp:1227 msgid "Talk to Ogden" msgstr "オグデン​と​話す" -#: Source/stores.cpp:1258 +#: Source/stores.cpp:1228 msgid "Leave the tavern" msgstr "宿屋​を​去る" -#: Source/stores.cpp:1269 +#: Source/stores.cpp:1239 msgid "Talk to Gillian" msgstr "ジリアン​と​話す" -#: Source/stores.cpp:1270 +#: Source/stores.cpp:1240 msgid "Access Storage" msgstr "スレレージ​に​アクセス" -#: Source/stores.cpp:1280 Source/towners.cpp:782 +#: Source/stores.cpp:1250 msgid "Farnham the Drunk" msgstr "よっぱらい​の​ファーンハム" -#: Source/stores.cpp:1282 +#: Source/stores.cpp:1252 msgid "Talk to Farnham" msgstr "ファーンハム​と​話す" -#: Source/stores.cpp:1283 +#: Source/stores.cpp:1253 msgid "Say Goodbye" msgstr "立ち去る" -#: Source/stores.cpp:2413 +#: Source/stores.cpp:2373 #, c++-format msgid "Your gold: {:s}" msgstr "所持金: {:s}" -#: Source/textdat.cpp:72 -msgid "Loading Text Data Failed" -msgstr "" +#: Source/tables/monstdat.cpp:331 Source/tables/monstdat.cpp:344 +msgid "Loading Monster Data Failed" +msgstr "モンスター​データ​の​読み込み​に​失敗​し​まし​た" -#: Source/textdat.cpp:72 +#: Source/tables/monstdat.cpp:331 #, c++-format -msgid "A text data entry already exists for ID \"{}\"." +msgid "" +"Could not add a monster, since the maximum monster type number of {} has " +"already been reached." msgstr "" +"最大​モンスター​種類​数 {} に​達し​て​いる​ため、モンスター​を​追加​でき​ませ​ん​でし​た。" -#: Source/towners.cpp:269 -msgid "Slain Townsman" -msgstr "住人​の​遺体" - -#: Source/towners.cpp:777 -msgid "Griswold the Blacksmith" -msgstr "鍛冶屋​の​グリズウォルド" - -#: Source/towners.cpp:778 -msgid "Pepin the Healer" -msgstr "治療​師​の​ペピン" - -#: Source/towners.cpp:779 -msgid "Wounded Townsman" -msgstr "傷つい​た​住人" - -#: Source/towners.cpp:780 -msgid "Ogden the Tavern owner" -msgstr "宿屋​の​オグデン" - -#: Source/towners.cpp:781 -msgid "Cain the Elder" -msgstr "語り部​の​ケイン" - -#: Source/towners.cpp:783 -msgid "Adria the Witch" -msgstr "魔女​の​エイドリア" +#: Source/tables/monstdat.cpp:344 +#, c++-format +msgid "A monster type already exists for ID \"{}\"." +msgstr "ID ”​{}​” の​モンスター​タイプ​は​すでに​存在​し​ます。" -#: Source/towners.cpp:784 -msgid "Gillian the Barmaid" -msgstr "ウェイトレス​の​ジリアン" +#: Source/tables/playerdat.cpp:320 +msgid "Loading Class Data Failed" +msgstr "クラス​データ​の​読み込み​に​失敗​し​まし​た" -#: Source/towners.cpp:786 -msgid "Cow" -msgstr "牛" +#: Source/tables/playerdat.cpp:320 +#, c++-format +msgid "" +"Could not add a class, since the maximum class number of {} has already been " +"reached." +msgstr "最大​クラス​数 {} に​達し​て​いる​ため、クラス​を​追加​でき​ませ​ん​でし​た。" -#: Source/towners.cpp:787 -msgid "Lester the farmer" -msgstr "農家​の​レスター" +#: Source/tables/textdat.cpp:72 +msgid "Loading Text Data Failed" +msgstr "テキストデータ​の​読み込み​に​失敗​し​まし​た" -#: Source/towners.cpp:788 -msgid "Celia" -msgstr "セリア" +#: Source/tables/textdat.cpp:72 +#, c++-format +msgid "A text data entry already exists for ID \"{}\"." +msgstr "ID “​{}​” の​テキストデータ​は​すでに​存在​し​ます。" -#: Source/towners.cpp:789 -msgid "Complete Nut" -msgstr "コンプリート​・​ナット" +#: Source/towners.cpp:200 +msgid "Slain Townsman" +msgstr "住人​の​遺体" #: Source/translation_dummy.cpp:11 msgid "Warrior" @@ -6427,7 +6395,7 @@ msgstr "大聖堂​の​マップ" #: Source/translation_dummy.cpp:239 msgid "Ear" -msgstr "" +msgstr "Ear" #: Source/translation_dummy.cpp:240 msgid "Potion of Healing" @@ -6930,10 +6898,8 @@ msgid "The Butcher's Cleaver" msgstr "ブッチャーズ​・​クリーバー" #: Source/translation_dummy.cpp:370 -#, fuzzy -#| msgid "Lightsabre" msgid "Lightforge" -msgstr "ライト​サーベル" +msgstr "ライトフォージ" #: Source/translation_dummy.cpp:371 msgid "The Rift Bow" @@ -11749,6 +11715,60 @@ msgstr "ルーン​・​オブ​・​ストーン" msgid "," msgstr "," +#~ msgid "Adria Refills Mana" +#~ msgstr "エイドリア​の​マナ​補充" + +#~ msgid "Adria will refill your mana when you visit her shop." +#~ msgstr "エイドリア​の​店​を​訪れる​と、マナ​を​補充​し​て​くれ​ます。" + +#~ msgid "Enable floating numbers" +#~ msgstr "浮動​小数​点数​を​有効​に​する" + +#~ msgid "Enables floating numbers on gaining XP / dealing damage etc." +#~ msgstr "経験値​や​ダメージ​など​に​浮動​小数​点数​を​使用​する。" + +#~ msgid "Off" +#~ msgstr "オフ" + +#~ msgid "Random Angles" +#~ msgstr "ランダム​アングル" + +#~ msgid "Vertical Only" +#~ msgstr "垂直​のみ" + +#~ msgid "Griswold the Blacksmith" +#~ msgstr "鍛冶屋​の​グリズウォルド" + +#~ msgid "Pepin the Healer" +#~ msgstr "治療​師​の​ペピン" + +#~ msgid "Wounded Townsman" +#~ msgstr "傷つい​た​住人" + +#~ msgid "Ogden the Tavern owner" +#~ msgstr "宿屋​の​オグデン" + +#~ msgid "Cain the Elder" +#~ msgstr "語り部​の​ケイン" + +#~ msgid "Adria the Witch" +#~ msgstr "魔女​の​エイドリア" + +#~ msgid "Gillian the Barmaid" +#~ msgstr "ウェイトレス​の​ジリアン" + +#~ msgid "Cow" +#~ msgstr "牛" + +#~ msgid "Lester the farmer" +#~ msgstr "農家​の​レスター" + +#~ msgid "Celia" +#~ msgstr "セリア" + +#~ msgid "Complete Nut" +#~ msgstr "コンプリート​・​ナット" + #~ msgid "Decrease Gamma" #~ msgstr "ガンマ​値​を​下げる" diff --git a/android-project/app/src/main/java/org/diasurgical/devilutionx/DevilutionXSDLActivity.java b/android-project/app/src/main/java/org/diasurgical/devilutionx/DevilutionXSDLActivity.java index 2a0ade3cb..fef3a2de7 100644 --- a/android-project/app/src/main/java/org/diasurgical/devilutionx/DevilutionXSDLActivity.java +++ b/android-project/app/src/main/java/org/diasurgical/devilutionx/DevilutionXSDLActivity.java @@ -1,12 +1,15 @@ package org.diasurgical.devilutionx; import android.content.Intent; +import android.graphics.Insets; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; +import android.util.DisplayMetrics; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.ViewTreeObserver; +import android.view.WindowInsets; import android.view.WindowManager; import org.libsdl.app.SDLActivity; @@ -65,15 +68,47 @@ public class DevilutionXSDLActivity extends SDLActivity { this.getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { + SurfaceView surface = mSurface; + SurfaceHolder holder = surface.getHolder(); + + if (!isIMEVisible()) { + holder.setSizeFromLayout(); + return; + } + // Software keyboard may encroach on the app's visible space so // force the drawing surface to fit in the visible display frame Rect visibleSpace = new Rect(); - getWindow().getDecorView().getWindowVisibleDisplayFrame(visibleSpace); - - SurfaceView surface = mSurface; - SurfaceHolder holder = surface.getHolder(); + getVisibleSpaceAroundIME(visibleSpace); holder.setFixedSize(visibleSpace.width(), visibleSpace.height()); } + + private boolean isIMEVisible() { + // Window insets are not available before Android 11 so + // we must always assume the software keyboard is visible + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) + return true; + + WindowInsets rootInsets = getWindow().getDecorView().getRootWindowInsets(); + return rootInsets.isVisible(WindowInsets.Type.ime()); + } + + private void getVisibleSpaceAroundIME(Rect visibleSpace) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + getWindow().getDecorView().getWindowVisibleDisplayFrame(visibleSpace); + return; + } + + WindowInsets rootInsets = getWindow().getDecorView().getRootWindowInsets(); + Insets imeInsets = rootInsets.getInsets(WindowInsets.Type.ime()); + DisplayMetrics realMetrics = new DisplayMetrics(); + getDisplay().getRealMetrics(realMetrics); + + visibleSpace.top = imeInsets.top; + visibleSpace.left = imeInsets.left; + visibleSpace.right = realMetrics.widthPixels - imeInsets.right; + visibleSpace.bottom = realMetrics.heightPixels - imeInsets.bottom; + } }); } diff --git a/android-project/app/src/main/res/values-be/strings.xml b/android-project/app/src/main/res/values-be/strings.xml new file mode 100644 index 000000000..27c635717 --- /dev/null +++ b/android-project/app/src/main/res/values-be/strings.xml @@ -0,0 +1,25 @@ + + DevilutionX + + + Каб гуляць у поўную версію трэба памясціць DIABDAT.MPQ з арыгінальнай гульні ў Android/data/org.diasurgical.devilutionx/files.
\n
\nКалі не маеце арыгінальнай гульні, то можаце купіць Д\'ябла на GOG.com.
+ Або можаце спампаваць spawn-версію і пагуляць у дэма. + Для больш дэталяў глядзіце Інструкцыі па ўсталяванні. + Пераправерыць + Даныя гульні + Спампаваць даныя дэма + Не хапае даных гульні + Спампоўка пачалася + Даныя дэма Diablo + Імпартаваць даныя + Для гульні трэба геймпад + Калі ласка, усталюйце праваднік каб выбраць файлы, якія хочаце імпартаваць. + Для Android TV рэкамендуем %1$s. + На наступным экране выберыце файлы якія імпартуюцца ў:\n\n%1$s + Наступныя файлы перазапішуцца. Добра?\n\n%1$s + OK + Працягнуць + Адмяніць +
+ + diff --git a/assets/fonts/12-05.clx b/assets/fonts/12-05.clx new file mode 100644 index 000000000..bd976e617 Binary files /dev/null and b/assets/fonts/12-05.clx differ diff --git a/assets/fonts/24-05.clx b/assets/fonts/24-05.clx new file mode 100644 index 000000000..45934c5d0 Binary files /dev/null and b/assets/fonts/24-05.clx differ diff --git a/assets/fonts/30-05.clx b/assets/fonts/30-05.clx new file mode 100644 index 000000000..32845480a Binary files /dev/null and b/assets/fonts/30-05.clx differ diff --git a/assets/fonts/42-05.clx b/assets/fonts/42-05.clx new file mode 100644 index 000000000..82a8323b6 Binary files /dev/null and b/assets/fonts/42-05.clx differ diff --git a/assets/fonts/46-05.clx b/assets/fonts/46-05.clx new file mode 100644 index 000000000..5dcd035e6 Binary files /dev/null and b/assets/fonts/46-05.clx differ diff --git a/assets/lua/devilutionx/events.lua b/assets/lua/devilutionx/events.lua index 5f2a15723..dc795b27e 100644 --- a/assets/lua/devilutionx/events.lua +++ b/assets/lua/devilutionx/events.lua @@ -25,9 +25,10 @@ local function CreateEvent() ---The arguments are forwarded to handlers. ---@param ... any trigger = function(...) - if arg ~= nil then + local args = {...} + if #args > 0 then for _, func in ipairs(functions) do - func(table.unpack(arg)) + func(table.unpack(args)) end else for _, func in ipairs(functions) do @@ -67,6 +68,22 @@ local events = { ---Called every frame at the end. GameDrawComplete = CreateEvent(), __doc_GameDrawComplete = "Called every frame at the end.", + + ---Called when opening a towner store. Passes the towner name as argument (e.g., "griswold", "adria", "pepin", "wirt", "cain"). + StoreOpened = CreateEvent(), + __doc_StoreOpened = "Called when opening a towner store. Passes the towner name as argument.", + + ---Called when a Monster takes damage. + OnMonsterTakeDamage = CreateEvent(), + __doc_OnMonsterTakeDamage = "Called when a Monster takes damage.", + + ---Called when Player takes damage. + OnPlayerTakeDamage = CreateEvent(), + __doc_OnPlayerTakeDamage = "Called when Player takes damage.", + + ---Called when Player gains experience. + OnPlayerGainExperience = CreateEvent(), + __doc_OnPlayerGainExperience = "Called when Player gains experience.", } ---Registers a custom event type with the given name. diff --git a/assets/lua/mods/Floating Numbers - Damage/init.lua b/assets/lua/mods/Floating Numbers - Damage/init.lua new file mode 100644 index 000000000..c3dba6354 --- /dev/null +++ b/assets/lua/mods/Floating Numbers - Damage/init.lua @@ -0,0 +1,90 @@ +local floatingnumbers = require("devilutionx.floatingnumbers") +local events = require("devilutionx.events") +local player = require("devilutionx.player") +local system = require("devilutionx.system") +local render = require("devilutionx.render") + +local DAMAGE_TYPE = { + PHYSICAL = 0, + FIRE = 1, + LIGHTNING = 2, + MAGIC = 3, + ACID = 4, +} + +local function get_damage_style(damage_val, damage_type) + local style = 0 + + local v = damage_val + if v >= 64 * 300 then + style = style | render.UiFlags.FontSize30 + elseif v >= 64 * 100 then + style = style | render.UiFlags.FontSize24 + else + style = style | render.UiFlags.FontSize12 + end + + local damage_type_styles = { + [DAMAGE_TYPE.PHYSICAL] = render.UiFlags.ColorGold, + [DAMAGE_TYPE.FIRE] = render.UiFlags.ColorUiSilver, -- shows as DarkRed in game + [DAMAGE_TYPE.LIGHTNING] = render.UiFlags.ColorBlue, + [DAMAGE_TYPE.MAGIC] = render.UiFlags.ColorOrange, + [DAMAGE_TYPE.ACID] = render.UiFlags.ColorYellow, + } + + local type_style = damage_type_styles[damage_type] + if type_style then + style = style | type_style + end + + return style +end + +local function format_damage(damage_val) + if damage_val > 0 and damage_val < 64 then + return string.format("%.2f", damage_val / 64.0) + else + return tostring(math.floor(damage_val / 64)) + end +end + +local accumulated_damage = {} +local MERGE_WINDOW_MS = 100 + +events.OnMonsterTakeDamage.add(function(monster, damage, damage_type) + local id = monster.id + local now = system.get_ticks() + + local entry = accumulated_damage[id] + if entry and (now - entry.time) < MERGE_WINDOW_MS then + entry.damage = entry.damage + damage + else + entry = { damage = damage, time = now } + accumulated_damage[id] = entry + end + entry.time = now + + local text = format_damage(entry.damage) + local style = get_damage_style(entry.damage, damage_type) + floatingnumbers.add(text, monster.position, style, id, false) +end) + +events.OnPlayerTakeDamage.add(function(_player, damage, damage_type) + if _player == player.self() then + local id = _player.id + local now = system.get_ticks() + + local entry = accumulated_damage[id] + if entry and (now - entry.time) < MERGE_WINDOW_MS then + entry.damage = entry.damage + damage + else + entry = { damage = damage, time = now } + accumulated_damage[id] = entry + end + entry.time = now + + local text = format_damage(entry.damage) + local style = get_damage_style(entry.damage, damage_type) + floatingnumbers.add(text, _player.position, style, id, true) + end +end) diff --git a/assets/lua/mods/Floating Numbers - XP/init.lua b/assets/lua/mods/Floating Numbers - XP/init.lua new file mode 100644 index 000000000..40817ac6b --- /dev/null +++ b/assets/lua/mods/Floating Numbers - XP/init.lua @@ -0,0 +1,27 @@ +local floatingnumbers = require("devilutionx.floatingnumbers") +local events = require("devilutionx.events") +local player = require("devilutionx.player") +local system = require("devilutionx.system") +local render = require("devilutionx.render") + +local accumulated_xp = {} +local MERGE_WINDOW_MS = 100 + +events.OnPlayerGainExperience.add(function(_player, experience) + if _player == player.self() then + local id = _player.id + local now = system.get_ticks() + + local entry = accumulated_xp[id] + if entry and (now - entry.time) < MERGE_WINDOW_MS then + entry.experience = entry.experience + experience + else + entry = { experience = experience, time = now } + accumulated_xp[id] = entry + end + entry.time = now + + local text = tostring(entry.experience) .. " XP" + floatingnumbers.add(text, _player.position, render.UiFlags.ColorWhite, id, true) + end +end) diff --git a/assets/lua/mods/adria_refills_mana/init.lua b/assets/lua/mods/adria_refills_mana/init.lua new file mode 100644 index 000000000..f5ca744e0 --- /dev/null +++ b/assets/lua/mods/adria_refills_mana/init.lua @@ -0,0 +1,23 @@ +-- Adria Refills Mana Mod +-- When you visit Adria's shop, your mana is restored to full. + +local events = require("devilutionx.events") +local player = require("devilutionx.player") +local audio = require("devilutionx.audio") + +events.StoreOpened.add(function(townerName) + if townerName ~= "adria" then + return + end + + local p = player.self() + if p == nil then + return + end + + -- Restore mana if player has mana capacity and it's not already full + if p.maxMana > 0 and p.mana < p.maxMana then + audio.playSfx(audio.SfxID.CastHealing) + p:restoreFullMana() + end +end) diff --git a/assets/lua/mods/clock/init.lua b/assets/lua/mods/clock/init.lua index 0c2981386..18ef67d49 100644 --- a/assets/lua/mods/clock/init.lua +++ b/assets/lua/mods/clock/init.lua @@ -2,5 +2,5 @@ local events = require("devilutionx.events") local render = require("devilutionx.render") events.GameDrawComplete.add(function() - render.string(os.date('%H:%M:%S', os.time()), render.screen_width() - 69, 6) + render.string(os.date('%H:%M:%S', os.time()), render.screen_width() - 76, 6) end) diff --git a/assets/lua/repl_prelude.lua b/assets/lua/repl_prelude.lua index b871affbc..f58aabb1a 100644 --- a/assets/lua/repl_prelude.lua +++ b/assets/lua/repl_prelude.lua @@ -1,5 +1,6 @@ events = require('devilutionx.events') i18n = require('devilutionx.i18n') +items = require('devilutionx.items') log = require('devilutionx.log') audio = require('devilutionx.audio') player = require('devilutionx.player') @@ -8,3 +9,14 @@ towners = require('devilutionx.towners') message = require('devilutionx.message') if _DEBUG then dev = require('devilutionx.dev') end inspect = require('inspect') + +-- Expose item enums from items module for easy access in console +ItemIndex = items.ItemIndex +ItemType = items.ItemType +ItemClass = items.ItemClass +ItemEquipType = items.ItemEquipType +ItemMiscID = items.ItemMiscID +SpellID = items.SpellID +ItemEffectType = items.ItemEffectType +ItemSpecialEffect = items.ItemSpecialEffect +ItemSpecialEffectHf = items.ItemSpecialEffectHf diff --git a/assets/txtdata/items/unique_itemdat.tsv b/assets/txtdata/items/unique_itemdat.tsv index 8b8ccdaa5..4d9133ae4 100644 --- a/assets/txtdata/items/unique_itemdat.tsv +++ b/assets/txtdata/items/unique_itemdat.tsv @@ -37,7 +37,7 @@ The Grizzly THE_GRIZZLY TWOHANDSWR 23 50000 STR 20 20 VIT_CURSE 5 5 DAMP 200 200 The Grandfather THE_GRANDFATHER GREATSWR 27 119800 ONEHAND ATTRIBS 5 5 TOHIT 20 20 DAMP 70 70 LIFE 20 20 The Mangler AXE LARGEAXE 2 2850 DAMP 200 200 DEX_CURSE 5 5 MAG_CURSE 5 5 MANA_CURSE 10 10 Sharp Beak GREAT_AXE LARGEAXE 2 2850 LIFE 20 20 MAG_CURSE 10 10 MANA_CURSE 10 10 -BloodSlayer AXE BROADAXE 3 2500 DAMP 100 100 3XDAMVDEM ATTRIBS_CURSE 5 5 SPLLVLADD -1 -1 +Bloodslayer AXE BROADAXE 3 2500 DAMP 100 100 3XDAMVDEM ATTRIBS_CURSE 5 5 SPLLVLADD -1 -1 The Celestial Axe BATTLEAXE 4 14100 NOMINSTR TOHIT 15 15 LIFE 15 15 STR_CURSE 15 15 Wicked Axe GREAT_AXE LARGEAXE 5 31150 TOHIT 30 30 DEX 10 10 VIT_CURSE 10 10 GETHIT 1 6 INDESTRUCTIBLE Stonecleaver STONECLEAVER BROADAXE 7 23900 LIFE 30 30 TOHIT 20 20 DAMP 50 50 LIGHTRES 40 40 diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index abaa2c6ea..7fbb38527 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -13,6 +13,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Potential item deletion when dropping a large amount of items and leaving the level +#### Graphics / Audio + +- Music doesn't unmute when focus is lost on level transition with Auto Pause On Focus Lost disabled +- Image ghosting visible on border of map in higher resolutions + +#### Stability / Performance / System + +- Crashes related to player graphics rendering in death state + ## DevilutionX 1.5.2 ### Bug Fixes diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 2ec73ab8e..3f6ca5026 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,14 +1,4 @@ -# Contribution Guide +# DevilutionX Contribution Guide +Welcome! Please review our [Contribution Guide](https://github.com/diasurgical/DevilutionX/wiki/Contributing) for more information! -This guide outlines useful resources, tools and processes for contribution to -DevilutionX. - -## C++ Standard - -Despite setting C++ standard to 20 in CMakeLists.txt, features from this version are not being used. -The oldest compiler used is GCC 6.5 - and that defines our C++ feature set (meaning most of C++17). -It's present only to take advantage of fmt::format build time errors. - -## Code style guide - -[The code style guide](https://github.com/diasurgical/devilutionX/wiki/Code-Style) is evolving with the project. +_“A project is only as strong as its contributors. Thank you for helping us keep Diablo 1 alive and better than ever!”_ diff --git a/test/fixtures/text_render_integration_test/cursor-end.png b/test/fixtures/text_render_integration_test/cursor-end.png new file mode 100644 index 000000000..7de350d04 Binary files /dev/null and b/test/fixtures/text_render_integration_test/cursor-end.png differ diff --git a/test/fixtures/text_render_integration_test/cursor-middle.png b/test/fixtures/text_render_integration_test/cursor-middle.png new file mode 100644 index 000000000..fed7cf064 Binary files /dev/null and b/test/fixtures/text_render_integration_test/cursor-middle.png differ diff --git a/test/fixtures/text_render_integration_test/cursor-start.png b/test/fixtures/text_render_integration_test/cursor-start.png new file mode 100644 index 000000000..8d60dca8d Binary files /dev/null and b/test/fixtures/text_render_integration_test/cursor-start.png differ diff --git a/test/fixtures/text_render_integration_test/highlight-full.png b/test/fixtures/text_render_integration_test/highlight-full.png new file mode 100644 index 000000000..6d1e7f95d Binary files /dev/null and b/test/fixtures/text_render_integration_test/highlight-full.png differ diff --git a/test/fixtures/text_render_integration_test/highlight-partial.png b/test/fixtures/text_render_integration_test/highlight-partial.png new file mode 100644 index 000000000..f94036a78 Binary files /dev/null and b/test/fixtures/text_render_integration_test/highlight-partial.png differ diff --git a/test/fixtures/text_render_integration_test/multiline_cursor-end_first_line.png b/test/fixtures/text_render_integration_test/multiline_cursor-end_first_line.png new file mode 100644 index 000000000..295aeaf2a Binary files /dev/null and b/test/fixtures/text_render_integration_test/multiline_cursor-end_first_line.png differ diff --git a/test/fixtures/text_render_integration_test/multiline_cursor-end_second_line.png b/test/fixtures/text_render_integration_test/multiline_cursor-end_second_line.png new file mode 100644 index 000000000..c5fa0bf97 Binary files /dev/null and b/test/fixtures/text_render_integration_test/multiline_cursor-end_second_line.png differ diff --git a/test/fixtures/text_render_integration_test/multiline_cursor-middle_second_line.png b/test/fixtures/text_render_integration_test/multiline_cursor-middle_second_line.png new file mode 100644 index 000000000..c7f681055 Binary files /dev/null and b/test/fixtures/text_render_integration_test/multiline_cursor-middle_second_line.png differ diff --git a/test/fixtures/text_render_integration_test/multiline_cursor-start_second_line.png b/test/fixtures/text_render_integration_test/multiline_cursor-start_second_line.png new file mode 100644 index 000000000..b64aaf48a Binary files /dev/null and b/test/fixtures/text_render_integration_test/multiline_cursor-start_second_line.png differ diff --git a/test/fixtures/text_render_integration_test/multiline_highlight.png b/test/fixtures/text_render_integration_test/multiline_highlight.png new file mode 100644 index 000000000..6dab07894 Binary files /dev/null and b/test/fixtures/text_render_integration_test/multiline_highlight.png differ diff --git a/test/multi_logging_test.cpp b/test/multi_logging_test.cpp index 1b3c04a4a..0911ad5e3 100644 --- a/test/multi_logging_test.cpp +++ b/test/multi_logging_test.cpp @@ -1,28 +1,28 @@ -#include - -#include "multi.h" - -namespace devilution { - -TEST(MultiplayerLogging, NormalExitReason) -{ - EXPECT_EQ("normal exit", DescribeLeaveReason(net::leaveinfo_t::LEAVE_EXIT)); -} - -TEST(MultiplayerLogging, DiabloEndingReason) -{ - EXPECT_EQ("Diablo defeated", DescribeLeaveReason(net::leaveinfo_t::LEAVE_ENDING)); -} - -TEST(MultiplayerLogging, DropReason) -{ - EXPECT_EQ("connection timeout", DescribeLeaveReason(net::leaveinfo_t::LEAVE_DROP)); -} - -TEST(MultiplayerLogging, CustomReasonCode) -{ - constexpr net::leaveinfo_t CustomCode = static_cast(0xDEADBEEF); - EXPECT_EQ("code 0xDEADBEEF", DescribeLeaveReason(CustomCode)); -} - -} // namespace devilution +#include + +#include "multi.h" + +namespace devilution { + +TEST(MultiplayerLogging, NormalExitReason) +{ + EXPECT_EQ("normal exit", DescribeLeaveReason(net::leaveinfo_t::LEAVE_EXIT)); +} + +TEST(MultiplayerLogging, DiabloEndingReason) +{ + EXPECT_EQ("Diablo defeated", DescribeLeaveReason(net::leaveinfo_t::LEAVE_ENDING)); +} + +TEST(MultiplayerLogging, DropReason) +{ + EXPECT_EQ("connection timeout", DescribeLeaveReason(net::leaveinfo_t::LEAVE_DROP)); +} + +TEST(MultiplayerLogging, CustomReasonCode) +{ + constexpr net::leaveinfo_t CustomCode = static_cast(0xDEADBEEF); + EXPECT_EQ("code 0xDEADBEEF", DescribeLeaveReason(CustomCode)); +} + +} // namespace devilution diff --git a/test/pack_test.cpp b/test/pack_test.cpp index 23771d6fd..8a3d28bb8 100644 --- a/test/pack_test.cpp +++ b/test/pack_test.cpp @@ -5,9 +5,9 @@ #include "cursor.h" #include "engine/assets.hpp" #include "game_mode.hpp" -#include "monstdat.h" #include "pack.h" -#include "playerdat.hpp" +#include "tables/monstdat.h" +#include "tables/playerdat.hpp" #include "utils/endian_swap.hpp" #include "utils/is_of.hpp" #include "utils/paths.h" diff --git a/test/player_test.cpp b/test/player_test.cpp index 7a4cbed50..4d4108e7f 100644 --- a/test/player_test.cpp +++ b/test/player_test.cpp @@ -5,7 +5,7 @@ #include "cursor.h" #include "engine/assets.hpp" #include "init.hpp" -#include "playerdat.hpp" +#include "tables/playerdat.hpp" using namespace devilution; diff --git a/test/player_test.h b/test/player_test.h index fdc797ccf..5a849eb24 100644 --- a/test/player_test.h +++ b/test/player_test.h @@ -1,31 +1,31 @@ -/** - * @file player_test.h - * - * Helpers for player related tests. - */ -#pragma once - -#include "items.h" -#include "player.h" - -using namespace devilution; - -static size_t CountItems(devilution::Item *items, int n) -{ - return std::count_if(items, items + n, [](devilution::Item x) { return !x.isEmpty(); }); -} - -static size_t Count8(int8_t *ints, int n) -{ - return std::count_if(ints, ints + n, [](int8_t x) { return x != 0; }); -} - -static size_t CountU8(uint8_t *ints, int n) -{ - return std::count_if(ints, ints + n, [](uint8_t x) { return x != 0; }); -} - -static size_t CountBool(bool *bools, int n) -{ - return std::count_if(bools, bools + n, [](bool x) { return x; }); -} +/** + * @file player_test.h + * + * Helpers for player related tests. + */ +#pragma once + +#include "items.h" +#include "player.h" + +using namespace devilution; + +static size_t CountItems(devilution::Item *items, int n) +{ + return std::count_if(items, items + n, [](devilution::Item x) { return !x.isEmpty(); }); +} + +static size_t Count8(int8_t *ints, int n) +{ + return std::count_if(ints, ints + n, [](int8_t x) { return x != 0; }); +} + +static size_t CountU8(uint8_t *ints, int n) +{ + return std::count_if(ints, ints + n, [](uint8_t x) { return x != 0; }); +} + +static size_t CountBool(bool *bools, int n) +{ + return std::count_if(bools, bools + n, [](bool x) { return x; }); +} diff --git a/test/quests_test.cpp b/test/quests_test.cpp index 42f3e3634..8194c2b70 100644 --- a/test/quests_test.cpp +++ b/test/quests_test.cpp @@ -1,88 +1,88 @@ -#include -#include - -#include "quests.h" - -#include "objdat.h" // For quest IDs - -namespace devilution { -void ResetQuests() -{ - for (auto &quest : Quests) - quest._qactive = QUEST_INIT; -} - -std::vector GetActiveFlagsForSlice(std::initializer_list ids) -{ - std::vector temp; - - for (auto id : ids) - temp.push_back(Quests[id]._qactive); - - return temp; -} - -TEST(QuestTest, SinglePlayerBadPools) -{ - ResetQuests(); - - // (INT_MIN >> 16) % 2 = 0, so the times when the RNG calls GenerateRnd(2) don't end up with a negative value. - InitialiseQuestPools(1457187811, Quests); - EXPECT_EQ(Quests[Q_SKELKING]._qactive, QUEST_NOTAVAIL) << "Skeleton King quest is deactivated with 'bad' seed"; - ResetQuests(); - - // Having this seed for level 15 requires starting a game at 1977-12-28 07:44:42 PM or 2087-02-18 10:43:02 PM - // Given Diablo was released in 1996 and it's currently 2024 this will never have been naturally hit. It's not - // possible to hit this case on a pre-NT kernel windows system but it may be possible on macos or winnt? - InitialiseQuestPools(988045466, Quests); - EXPECT_THAT(GetActiveFlagsForSlice({ Q_BUTCHER, Q_LTBANNER, Q_GARBUD }), ::testing::Each(QUEST_INIT)) << "All quests in pool 2 remain active with 'bad' seed"; - ResetQuests(); - - // This seed can only be reached by editing a save file or modifying the game. Given quest state (including - // availability) is saved as part of the game state there's no vanilla compatibility concerns here. - InitialiseQuestPools(4203210069U, Quests); - // If we wanted to retain the behaviour that vanilla Diablo would've done we should instead deactivate - // Quests[QuestGroup2[-2]]. This would hit QuestGroup1[1] (Ogden's Sign), however that's already marked - // as unavailable with this seed due to the previous quest group's roll. - EXPECT_EQ(Quests[Q_LTBANNER]._qactive, QUEST_NOTAVAIL) << "Ogden's Sign should be deactivated with 'bad' seed"; - EXPECT_EQ(Quests[Q_BLIND]._qactive, QUEST_NOTAVAIL) << "Halls of the Blind should also be deactivated with 'bad' seed"; - ResetQuests(); - - // This seed can only be reached by editing a save file or modifying the game. Given quest state (including - // availability) is saved as part of the game state there's no vanilla compatibility concerns here. - InitialiseQuestPools(2557708932U, Quests); - // If we wanted to retain the behaviour that vanilla Diablo would've done we should instead deactivate - // Quests[QuestGroup3[-2]]. This would hit QuestGroup2[1] (Magic Rock), however that's already marked - // as unavailable with this seed due to the previous quest group's roll. - EXPECT_EQ(Quests[Q_ROCK]._qactive, QUEST_NOTAVAIL) << "Magic Rock should be deactivated with 'bad' seed"; - EXPECT_EQ(Quests[Q_MUSHROOM]._qactive, QUEST_NOTAVAIL) << "Black Mushroom should also be deactivated with 'bad' seed"; - ResetQuests(); - - InitialiseQuestPools(1272442071, Quests); - EXPECT_EQ(Quests[Q_VEIL]._qactive, QUEST_NOTAVAIL) << "Lachdan quest is deactivated with 'bad' seed"; -} - -TEST(QuestTest, SinglePlayerGoodPools) -{ - ResetQuests(); - - InitialiseQuestPools(509604, Quests); - EXPECT_EQ(Quests[Q_SKELKING]._qactive, QUEST_INIT) << "Expected Skeleton King quest to be available with the given seed"; - EXPECT_EQ(Quests[Q_PWATER]._qactive, QUEST_NOTAVAIL) << "Expected Poison Water quest to be deactivated with the given seed"; - - EXPECT_EQ(Quests[Q_BUTCHER]._qactive, QUEST_INIT) << "Expected Butcher quest to be available with the given seed"; - EXPECT_EQ(Quests[Q_LTBANNER]._qactive, QUEST_INIT) << "Expected Ogden's Sign quest to be available with the given seed"; - EXPECT_EQ(Quests[Q_GARBUD]._qactive, QUEST_NOTAVAIL) << "Expected Gharbad the Weak quest to be deactivated with the given seed"; - - EXPECT_EQ(Quests[Q_BLIND]._qactive, QUEST_INIT) << "Expected Halls of the Blind quest to be available with the given seed"; - EXPECT_EQ(Quests[Q_ROCK]._qactive, QUEST_NOTAVAIL) << "Expected Magic Rock quest to be deactivated with the given seed"; - EXPECT_EQ(Quests[Q_BLOOD]._qactive, QUEST_INIT) << "Expected Valor quest to be available with the given seed"; - - EXPECT_EQ(Quests[Q_MUSHROOM]._qactive, QUEST_INIT) << "Expected Black Mushroom quest to be available with the given seed"; - EXPECT_EQ(Quests[Q_ZHAR]._qactive, QUEST_NOTAVAIL) << "Expected Zhar the Mad quest to be deactivated with the given seed"; - EXPECT_EQ(Quests[Q_ANVIL]._qactive, QUEST_INIT) << "Expected Anvil of Fury quest to be available with the given seed"; - - EXPECT_EQ(Quests[Q_VEIL]._qactive, QUEST_NOTAVAIL) << "Expected Lachdanan quest to be deactivated with the given seed"; - EXPECT_EQ(Quests[Q_WARLORD]._qactive, QUEST_INIT) << "Expected Warlord of Blood quest to be available with the given seed"; -} -} // namespace devilution +#include +#include + +#include "quests.h" + +#include "tables/objdat.h" // For quest IDs + +namespace devilution { +void ResetQuests() +{ + for (auto &quest : Quests) + quest._qactive = QUEST_INIT; +} + +std::vector GetActiveFlagsForSlice(std::initializer_list ids) +{ + std::vector temp; + + for (auto id : ids) + temp.push_back(Quests[id]._qactive); + + return temp; +} + +TEST(QuestTest, SinglePlayerBadPools) +{ + ResetQuests(); + + // (INT_MIN >> 16) % 2 = 0, so the times when the RNG calls GenerateRnd(2) don't end up with a negative value. + InitialiseQuestPools(1457187811, Quests); + EXPECT_EQ(Quests[Q_SKELKING]._qactive, QUEST_NOTAVAIL) << "Skeleton King quest is deactivated with 'bad' seed"; + ResetQuests(); + + // Having this seed for level 15 requires starting a game at 1977-12-28 07:44:42 PM or 2087-02-18 10:43:02 PM + // Given Diablo was released in 1996 and it's currently 2024 this will never have been naturally hit. It's not + // possible to hit this case on a pre-NT kernel windows system but it may be possible on macos or winnt? + InitialiseQuestPools(988045466, Quests); + EXPECT_THAT(GetActiveFlagsForSlice({ Q_BUTCHER, Q_LTBANNER, Q_GARBUD }), ::testing::Each(QUEST_INIT)) << "All quests in pool 2 remain active with 'bad' seed"; + ResetQuests(); + + // This seed can only be reached by editing a save file or modifying the game. Given quest state (including + // availability) is saved as part of the game state there's no vanilla compatibility concerns here. + InitialiseQuestPools(4203210069U, Quests); + // If we wanted to retain the behaviour that vanilla Diablo would've done we should instead deactivate + // Quests[QuestGroup2[-2]]. This would hit QuestGroup1[1] (Ogden's Sign), however that's already marked + // as unavailable with this seed due to the previous quest group's roll. + EXPECT_EQ(Quests[Q_LTBANNER]._qactive, QUEST_NOTAVAIL) << "Ogden's Sign should be deactivated with 'bad' seed"; + EXPECT_EQ(Quests[Q_BLIND]._qactive, QUEST_NOTAVAIL) << "Halls of the Blind should also be deactivated with 'bad' seed"; + ResetQuests(); + + // This seed can only be reached by editing a save file or modifying the game. Given quest state (including + // availability) is saved as part of the game state there's no vanilla compatibility concerns here. + InitialiseQuestPools(2557708932U, Quests); + // If we wanted to retain the behaviour that vanilla Diablo would've done we should instead deactivate + // Quests[QuestGroup3[-2]]. This would hit QuestGroup2[1] (Magic Rock), however that's already marked + // as unavailable with this seed due to the previous quest group's roll. + EXPECT_EQ(Quests[Q_ROCK]._qactive, QUEST_NOTAVAIL) << "Magic Rock should be deactivated with 'bad' seed"; + EXPECT_EQ(Quests[Q_MUSHROOM]._qactive, QUEST_NOTAVAIL) << "Black Mushroom should also be deactivated with 'bad' seed"; + ResetQuests(); + + InitialiseQuestPools(1272442071, Quests); + EXPECT_EQ(Quests[Q_VEIL]._qactive, QUEST_NOTAVAIL) << "Lachdan quest is deactivated with 'bad' seed"; +} + +TEST(QuestTest, SinglePlayerGoodPools) +{ + ResetQuests(); + + InitialiseQuestPools(509604, Quests); + EXPECT_EQ(Quests[Q_SKELKING]._qactive, QUEST_INIT) << "Expected Skeleton King quest to be available with the given seed"; + EXPECT_EQ(Quests[Q_PWATER]._qactive, QUEST_NOTAVAIL) << "Expected Poison Water quest to be deactivated with the given seed"; + + EXPECT_EQ(Quests[Q_BUTCHER]._qactive, QUEST_INIT) << "Expected Butcher quest to be available with the given seed"; + EXPECT_EQ(Quests[Q_LTBANNER]._qactive, QUEST_INIT) << "Expected Ogden's Sign quest to be available with the given seed"; + EXPECT_EQ(Quests[Q_GARBUD]._qactive, QUEST_NOTAVAIL) << "Expected Gharbad the Weak quest to be deactivated with the given seed"; + + EXPECT_EQ(Quests[Q_BLIND]._qactive, QUEST_INIT) << "Expected Halls of the Blind quest to be available with the given seed"; + EXPECT_EQ(Quests[Q_ROCK]._qactive, QUEST_NOTAVAIL) << "Expected Magic Rock quest to be deactivated with the given seed"; + EXPECT_EQ(Quests[Q_BLOOD]._qactive, QUEST_INIT) << "Expected Valor quest to be available with the given seed"; + + EXPECT_EQ(Quests[Q_MUSHROOM]._qactive, QUEST_INIT) << "Expected Black Mushroom quest to be available with the given seed"; + EXPECT_EQ(Quests[Q_ZHAR]._qactive, QUEST_NOTAVAIL) << "Expected Zhar the Mad quest to be deactivated with the given seed"; + EXPECT_EQ(Quests[Q_ANVIL]._qactive, QUEST_INIT) << "Expected Anvil of Fury quest to be available with the given seed"; + + EXPECT_EQ(Quests[Q_VEIL]._qactive, QUEST_NOTAVAIL) << "Expected Lachdanan quest to be deactivated with the given seed"; + EXPECT_EQ(Quests[Q_WARLORD]._qactive, QUEST_INIT) << "Expected Warlord of Blood quest to be available with the given seed"; +} +} // namespace devilution diff --git a/test/random_test.cpp b/test/random_test.cpp index 1962dff85..62fa636c2 100644 --- a/test/random_test.cpp +++ b/test/random_test.cpp @@ -1,312 +1,312 @@ -#include - -#include "engine/random.hpp" - -namespace devilution { - -// These tests use ASSERT_EQ as the PRNG is expected to depend on state from the last call, so one failing assertion -// means the rest of the results can't be trusted. -TEST(RandomTest, RandomEngineParams) -{ - // The core diablo random number generator is an LCG with Borland constants. - // This RNG must be available for network/save compatibility for things such as level generation. - - constexpr uint32_t multiplicand = 22695477; - constexpr uint32_t increment = 1; - - SetRndSeed(0); - - // Starting from a seed of 0 means the multiplicand is dropped and the state advances by increment only - ASSERT_EQ(GenerateRandomNumber(), increment) << "Increment factor is incorrect"; - - // LCGs use a formula of mult * seed + inc. Using a long form in the code to document the expected factors. - ASSERT_EQ(GenerateRandomNumber(), (multiplicand * 1) + increment) << "Multiplicand factor is incorrect"; - - // C++11 defines the default seed for a LCG engine as 1. The ten thousandth value is commonly used for sanity checking - // a sequence, so as we've had one round since state 1 we need to discard another 9998 values to get to the 10000th state. - // To make off by one errors more visible test the 9999th value as well as 10000th - DiscardRandomValues(9997); - - uint32_t expectedState = 3495122800U; - EXPECT_EQ(GenerateRandomNumber(), expectedState) << "Wrong engine state after 9999 invocations"; - expectedState = 3007658545U; - ASSERT_EQ(GenerateRandomNumber(), expectedState) << "Wrong engine state after 10000 invocations"; -} - -TEST(RandomTest, AbsDistribution) -{ - // The default distribution for RNG calls is the absolute value of the generated value interpreted as a signed int - // This relies on undefined behaviour when called on std::numeric_limits::min(). See C17 7.22.6.1 - // The current behaviour is this returns the same value (the most negative number of the type). - SetRndSeed(1457187811); // yields -2147483648 - ASSERT_EQ(AdvanceRndSeed(), std::numeric_limits::min()) << "Invalid distribution"; - SetRndSeed(3604671459U); // yields 0 - ASSERT_EQ(AdvanceRndSeed(), 0) << "Invalid distribution"; - - SetRndSeed(0); // yields +1 - ASSERT_EQ(AdvanceRndSeed(), 1) << "Invalid distribution"; - SetRndSeed(2914375622U); // yields -1 - ASSERT_EQ(AdvanceRndSeed(), 1) << "Invalid distribution"; - - SetRndSeed(3604671460U); // yields +22695477 - ASSERT_EQ(AdvanceRndSeed(), 22695477) << "Invalid distribution"; - SetRndSeed(3604671458U); // yields -22695477 - ASSERT_EQ(AdvanceRndSeed(), 22695477) << "Invalid distribution"; - - SetRndSeed(1902003768); // yields +429496729 - ASSERT_EQ(AdvanceRndSeed(), 429496729) << "Invalid distribution"; - SetRndSeed(1012371854); // yields -429496729 - ASSERT_EQ(AdvanceRndSeed(), 429496729) << "Invalid distribution"; - - SetRndSeed(189776845); // yields +1212022642 - ASSERT_EQ(AdvanceRndSeed(), 1212022642) << "Invalid distribution"; - SetRndSeed(2724598777U); // yields -1212022642 - ASSERT_EQ(AdvanceRndSeed(), 1212022642) << "Invalid distribution"; - - SetRndSeed(76596137); // yields +2147483646 - ASSERT_EQ(AdvanceRndSeed(), 2147483646) << "Invalid distribution"; - SetRndSeed(2837779485U); // yields -2147483646 - ASSERT_EQ(AdvanceRndSeed(), 2147483646) << "Invalid distribution"; - - SetRndSeed(766891974); // yields +2147483647 - ASSERT_EQ(AdvanceRndSeed(), 2147483647) << "Invalid distribution"; - SetRndSeed(2147483648U); // yields -2147483647 - ASSERT_EQ(AdvanceRndSeed(), 2147483647) << "Invalid distribution"; -} - -// The bounded distribution function used by Diablo performs a modulo operation on the result of the AbsDistribution -// tested above, with a shift operation applied before the mod when the bound is < 65535. -// -// The current implementation does not allow testing these operations independantly, hopefully this can be changed -// with confidence from these tests. -// -// The result of a mod b when a is negative was implementation defined before C++11, current behaviour is to -// preserve the sign of a. See C++17 [expr.mul] and C++03 TR1 [expr.mul] -TEST(RandomTest, ModDistributionInvalidRange) -{ - // Calling the modulo distribution with an upper bound <= 0 returns 0 without advancing the engine - auto initialSeed = 44444444; - SetRndSeed(initialSeed); - EXPECT_EQ(GenerateRnd(0), 0) << "A distribution with an upper bound of 0 must return 0"; - EXPECT_EQ(GetLCGEngineState(), initialSeed) << "Distribution with invalid range must not advance the engine state"; - EXPECT_EQ(GenerateRnd(-1), 0) << "A distribution with a negative upper bound must return 0"; - EXPECT_EQ(GetLCGEngineState(), initialSeed) << "Distribution with invalid range must not advance the engine state"; - EXPECT_EQ(GenerateRnd(std::numeric_limits::min()), 0) - << "A distribution with a negative upper bound must return 0"; - EXPECT_EQ(GetLCGEngineState(), initialSeed) << "Distribution with invalid range must not advance the engine state"; -} - -TEST(RandomTest, ShiftModDistributionSingleRange) -{ - // All results mod 1 = 0; - auto initialSeed = ::testing::UnitTest::GetInstance()->random_seed(); - SetRndSeed(initialSeed); - for (auto i = 0; i < 100; i++) - ASSERT_EQ(GenerateRnd(1), 0) << "Interval [0, 1) must return 0"; - ASSERT_NE(GetLCGEngineState(), initialSeed) << "Interval of 1 element must still advance the engine state"; - - // Just in case -0 has a distinct representation? Shouldn't be possible but cheap to test. - SetRndSeed(1457187811); - ASSERT_EQ(GenerateRnd(1), 0) << "Interval [0, 1) must return 0 when AbsDistribution yields INT_MIN"; -} - -// When called with an upper bound less than or equal to 0x7FFF this distribution function discards the low 16 bits of the output -// from the default distribution by performing a shift right of 16 bits. This relies on implementation defined -// behaviour for negative numbers, the expectation is shift right uses sign carry. See C++17 [expr.shift] -TEST(RandomTest, ShiftModDistributionSignCarry) -{ - // This distribution is used when the upper bound is a value in [1, 32768) - // Using an upper bound of 1 means the result always maps to 0, see RandomTest_ShiftModDistributionSingleRange - - // The only negative value returned from AbsDistribution is -2147483648 - // A sign-preserving shift right of 16 bits gives a result of -32768. - // This is greater in magnitude than the limit so always results in a division. - SetRndSeed(1457187811); // Test mod when a division occurs - ASSERT_EQ(GenerateRnd(32767), -1) << "Distribution must map negative numbers using sign carry shifts"; -} - -// The Diablo LCG implementation attempts to improve the quality of generated numbers that would only use the low -// bits of the LCG output but due to applying this after taking the absolute value this introduces bias. This may -// be an inconsistency with the implementation in devilutionx, see the comment for RandomTest_ShiftModDistributionHighBits -TEST(RandomTest, ShiftModDistributionLowBits) -{ - constexpr auto maxBound = 32767; - - // All the following seeds generate values less than 2^16, so after shifting they give a 0 value - SetRndSeed(3604671459U); // yields 0 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 0"; - SetRndSeed(0); // yields 1 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 1"; - SetRndSeed(2914375622U); // yields -1 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -1"; - SetRndSeed(538964771); // yields 64 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 64"; - SetRndSeed(2375410851U); // yields -64 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -64"; - SetRndSeed(1229260608); // yields 65 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 65"; - SetRndSeed(1685115014); // yields -65 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -65"; - SetRndSeed(1768225379); // yields 128 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 128"; - SetRndSeed(1146150243); // yields -128 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -128"; - SetRndSeed(1480523688); // yields 7625 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 7625"; - SetRndSeed(1433851934); // yields -7625 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -7625"; - SetRndSeed(2382565573U); // yields 32458 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 32458"; - SetRndSeed(531810049); // yields -32458 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -32458"; - SetRndSeed(1625910243); // yields 32768 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 32768"; - SetRndSeed(1288465379); // yields -32768 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -32768"; - - // -1 from the max bound for the next two to ensure it's not due to a mod with no remainder - SetRndSeed(2561524649U); // yields 65534 - ASSERT_EQ(GenerateRnd(maxBound - 1), 0) << "Invalid distribution when generator yields 65534"; - SetRndSeed(352850973U); // yields -65534 - ASSERT_EQ(GenerateRnd(maxBound - 1), 0) << "Invalid distribution when generator yields -65534"; - - SetRndSeed(3251820486U); // yields 65535 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 65535"; - SetRndSeed(3957522432U); // yields -65535 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -65535"; -} - -// The highest value GenerateRnd can return is 32767 (0x7FFF). I suspect this is the Borland rand() implementation -// Diablo appears to have reimplemented this method incorrectly as that function was implemented as the following: -// uint seed = mult * seed + inc -// return (seed >> 16) & 0x7FFF -// i.e., no cast from unsigned to signed, no modulo when building the return value. -TEST(RandomTest, ShiftModDistributionHighBits) -{ - constexpr auto maxBound = 32767; - SetRndSeed(3267226595U); // yields 65536 - ASSERT_EQ(GenerateRnd(maxBound), 1) << "Invalid distribution when generator yields 65536"; - SetRndSeed(3942116323U); // yields -65536 - ASSERT_EQ(GenerateRnd(maxBound), 1) << "Invalid distribution when generator yields -65536"; - SetRndSeed(4279561187U); // yields 131072 - ASSERT_EQ(GenerateRnd(maxBound), 2) << "Invalid distribution when generator yields 131072"; - SetRndSeed(2929781731U); // yields -131072 - ASSERT_EQ(GenerateRnd(maxBound), 2) << "Invalid distribution when generator yields -131072"; - SetRndSeed(659483619); // yields 262144 - ASSERT_EQ(GenerateRnd(maxBound), 4) << "Invalid distribution when generator yields 262144"; - SetRndSeed(2254892003U); // yields -262144 - ASSERT_EQ(GenerateRnd(maxBound), 4) << "Invalid distribution when generator yields -262144"; - SetRndSeed(3604671458U); // yields 22695477 - ASSERT_EQ(GenerateRnd(maxBound), 346) << "Invalid distribution when generator yields 22695477"; - SetRndSeed(3604671460U); // yields -22695477 - ASSERT_EQ(GenerateRnd(maxBound), 346) << "Invalid distribution when generator yields -22695477"; - SetRndSeed(1012371854); // yields 429496729 - ASSERT_EQ(GenerateRnd(maxBound), 6553) << "Invalid distribution when generator yields 429496729"; - SetRndSeed(1902003768); // yields -429496729 - ASSERT_EQ(GenerateRnd(maxBound), 6553) << "Invalid distribution when generator yields -429496729"; - SetRndSeed(189776845); // yields 1212022642 - ASSERT_EQ(GenerateRnd(maxBound), 18493) << "Invalid distribution when generator yields 1212022642"; - SetRndSeed(2724598777); // yields -1212022642 - ASSERT_EQ(GenerateRnd(maxBound), 18493) << "Invalid distribution when generator yields -1212022642"; - SetRndSeed(76596137); // yields 2147483646 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 2147483646"; - SetRndSeed(2837779485); // yields -2147483646 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -2147483646"; - SetRndSeed(766891974); // yields 2147483647 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 2147483647"; - SetRndSeed(2147483648); // yields -2147483647 - ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -2147483647"; -} - -TEST(RandomTest, ModDistributionSignPreserving) -{ - // This distribution is used when the upper bound is a value in [32768, 2147483647] - - // Sign preserving modulo when no division is performed cannot be tested in isolation with the current implementation. - SetRndSeed(1457187811); - ASSERT_EQ(GenerateRnd(32768), 0) << "Distribution must map negative numbers using sign preserving modulo"; - SetRndSeed(1457187811); - ASSERT_EQ(GenerateRnd(32769), -2) << "Distribution must map negative numbers using sign preserving modulo"; - SetRndSeed(1457187811); - ASSERT_EQ(GenerateRnd(65535), -32768) << "Distribution must map negative numbers using sign preserving modulo"; - SetRndSeed(1457187811); - ASSERT_EQ(GenerateRnd(std::numeric_limits::max()), -1) - << "Distribution must map negative numbers using sign preserving modulo"; -} - -TEST(RandomTest, NegativeReturnValues) -{ - // The bug in vanilla RNG stemming from mod instead of bitmasking means that negative values are possible for - // non-power of 2 arguments to GenerateRnd - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(3), -2) << "Unexpected return value for a limit of 3"; - - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(5), -3) << "Unexpected return value for a limit of 5"; - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(6), -2) << "Unexpected return value for a limit of 6"; - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(7), -1) << "Unexpected return value for a limit of 7"; - - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(11), -10) << "Unexpected return value for a limit of 11"; - - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(17), -9) << "Unexpected return value for a limit of 17"; - - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(19), -12) << "Unexpected return value for a limit of 19"; - - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(22), -10) << "Unexpected return value for a limit of 22"; - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(23), -16) << "Unexpected return value for a limit of 23"; - - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(25), -18) << "Unexpected return value for a limit of 25"; - - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(27), -17) << "Unexpected return value for a limit of 27"; - - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(29), -27) << "Unexpected return value for a limit of 29"; - - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(31), -1) << "Unexpected return value for a limit of 31"; - - for (const int i : { 9, 10, 12, 13, 14, 15, 18, 20, 21, 24, 26, 28, 30 }) { - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(i), -8) << "Unexpected return value for a limit of " << i; - } - - // The following values are/were used throughout the code - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(37), -23) << "Unexpected return value for a limit of 37"; - - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(38), -12) << "Unexpected return value for a limit of 38"; - - SetRndSeed(1457187811); - // commonly used for dungeon megatile coordinates - EXPECT_EQ(GenerateRnd(40), -8) << "Unexpected return value for a limit of 40"; - - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(41), -9) << "Unexpected return value for a limit of 41"; - - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(52), -8) << "Unexpected return value for a limit of 52"; - - SetRndSeed(1457187811); - // commonly used for dungeon tile coordinates - EXPECT_EQ(GenerateRnd(80), -48) << "Unexpected return value for a limit of 80"; - - SetRndSeed(1457187811); - // commonly used for percentage rolls (typically in a context that 0 and -68 lead to the same outcome) - EXPECT_EQ(GenerateRnd(100), -68) << "Unexpected return value for a limit of 100"; - - for (int i = 1; i < 32768; i *= 2) { - SetRndSeed(1457187811); - EXPECT_EQ(GenerateRnd(i), 0) << "Expect powers of 2 such as " << i << " to cleanly divide the int_min RNG value "; - } -} -} // namespace devilution +#include + +#include "engine/random.hpp" + +namespace devilution { + +// These tests use ASSERT_EQ as the PRNG is expected to depend on state from the last call, so one failing assertion +// means the rest of the results can't be trusted. +TEST(RandomTest, RandomEngineParams) +{ + // The core diablo random number generator is an LCG with Borland constants. + // This RNG must be available for network/save compatibility for things such as level generation. + + constexpr uint32_t multiplicand = 22695477; + constexpr uint32_t increment = 1; + + SetRndSeed(0); + + // Starting from a seed of 0 means the multiplicand is dropped and the state advances by increment only + ASSERT_EQ(GenerateRandomNumber(), increment) << "Increment factor is incorrect"; + + // LCGs use a formula of mult * seed + inc. Using a long form in the code to document the expected factors. + ASSERT_EQ(GenerateRandomNumber(), (multiplicand * 1) + increment) << "Multiplicand factor is incorrect"; + + // C++11 defines the default seed for a LCG engine as 1. The ten thousandth value is commonly used for sanity checking + // a sequence, so as we've had one round since state 1 we need to discard another 9998 values to get to the 10000th state. + // To make off by one errors more visible test the 9999th value as well as 10000th + DiscardRandomValues(9997); + + uint32_t expectedState = 3495122800U; + EXPECT_EQ(GenerateRandomNumber(), expectedState) << "Wrong engine state after 9999 invocations"; + expectedState = 3007658545U; + ASSERT_EQ(GenerateRandomNumber(), expectedState) << "Wrong engine state after 10000 invocations"; +} + +TEST(RandomTest, AbsDistribution) +{ + // The default distribution for RNG calls is the absolute value of the generated value interpreted as a signed int + // This relies on undefined behaviour when called on std::numeric_limits::min(). See C17 7.22.6.1 + // The current behaviour is this returns the same value (the most negative number of the type). + SetRndSeed(1457187811); // yields -2147483648 + ASSERT_EQ(AdvanceRndSeed(), std::numeric_limits::min()) << "Invalid distribution"; + SetRndSeed(3604671459U); // yields 0 + ASSERT_EQ(AdvanceRndSeed(), 0) << "Invalid distribution"; + + SetRndSeed(0); // yields +1 + ASSERT_EQ(AdvanceRndSeed(), 1) << "Invalid distribution"; + SetRndSeed(2914375622U); // yields -1 + ASSERT_EQ(AdvanceRndSeed(), 1) << "Invalid distribution"; + + SetRndSeed(3604671460U); // yields +22695477 + ASSERT_EQ(AdvanceRndSeed(), 22695477) << "Invalid distribution"; + SetRndSeed(3604671458U); // yields -22695477 + ASSERT_EQ(AdvanceRndSeed(), 22695477) << "Invalid distribution"; + + SetRndSeed(1902003768); // yields +429496729 + ASSERT_EQ(AdvanceRndSeed(), 429496729) << "Invalid distribution"; + SetRndSeed(1012371854); // yields -429496729 + ASSERT_EQ(AdvanceRndSeed(), 429496729) << "Invalid distribution"; + + SetRndSeed(189776845); // yields +1212022642 + ASSERT_EQ(AdvanceRndSeed(), 1212022642) << "Invalid distribution"; + SetRndSeed(2724598777U); // yields -1212022642 + ASSERT_EQ(AdvanceRndSeed(), 1212022642) << "Invalid distribution"; + + SetRndSeed(76596137); // yields +2147483646 + ASSERT_EQ(AdvanceRndSeed(), 2147483646) << "Invalid distribution"; + SetRndSeed(2837779485U); // yields -2147483646 + ASSERT_EQ(AdvanceRndSeed(), 2147483646) << "Invalid distribution"; + + SetRndSeed(766891974); // yields +2147483647 + ASSERT_EQ(AdvanceRndSeed(), 2147483647) << "Invalid distribution"; + SetRndSeed(2147483648U); // yields -2147483647 + ASSERT_EQ(AdvanceRndSeed(), 2147483647) << "Invalid distribution"; +} + +// The bounded distribution function used by Diablo performs a modulo operation on the result of the AbsDistribution +// tested above, with a shift operation applied before the mod when the bound is < 65535. +// +// The current implementation does not allow testing these operations independantly, hopefully this can be changed +// with confidence from these tests. +// +// The result of a mod b when a is negative was implementation defined before C++11, current behaviour is to +// preserve the sign of a. See C++17 [expr.mul] and C++03 TR1 [expr.mul] +TEST(RandomTest, ModDistributionInvalidRange) +{ + // Calling the modulo distribution with an upper bound <= 0 returns 0 without advancing the engine + auto initialSeed = 44444444; + SetRndSeed(initialSeed); + EXPECT_EQ(GenerateRnd(0), 0) << "A distribution with an upper bound of 0 must return 0"; + EXPECT_EQ(GetLCGEngineState(), initialSeed) << "Distribution with invalid range must not advance the engine state"; + EXPECT_EQ(GenerateRnd(-1), 0) << "A distribution with a negative upper bound must return 0"; + EXPECT_EQ(GetLCGEngineState(), initialSeed) << "Distribution with invalid range must not advance the engine state"; + EXPECT_EQ(GenerateRnd(std::numeric_limits::min()), 0) + << "A distribution with a negative upper bound must return 0"; + EXPECT_EQ(GetLCGEngineState(), initialSeed) << "Distribution with invalid range must not advance the engine state"; +} + +TEST(RandomTest, ShiftModDistributionSingleRange) +{ + // All results mod 1 = 0; + auto initialSeed = ::testing::UnitTest::GetInstance()->random_seed(); + SetRndSeed(initialSeed); + for (auto i = 0; i < 100; i++) + ASSERT_EQ(GenerateRnd(1), 0) << "Interval [0, 1) must return 0"; + ASSERT_NE(GetLCGEngineState(), initialSeed) << "Interval of 1 element must still advance the engine state"; + + // Just in case -0 has a distinct representation? Shouldn't be possible but cheap to test. + SetRndSeed(1457187811); + ASSERT_EQ(GenerateRnd(1), 0) << "Interval [0, 1) must return 0 when AbsDistribution yields INT_MIN"; +} + +// When called with an upper bound less than or equal to 0x7FFF this distribution function discards the low 16 bits of the output +// from the default distribution by performing a shift right of 16 bits. This relies on implementation defined +// behaviour for negative numbers, the expectation is shift right uses sign carry. See C++17 [expr.shift] +TEST(RandomTest, ShiftModDistributionSignCarry) +{ + // This distribution is used when the upper bound is a value in [1, 32768) + // Using an upper bound of 1 means the result always maps to 0, see RandomTest_ShiftModDistributionSingleRange + + // The only negative value returned from AbsDistribution is -2147483648 + // A sign-preserving shift right of 16 bits gives a result of -32768. + // This is greater in magnitude than the limit so always results in a division. + SetRndSeed(1457187811); // Test mod when a division occurs + ASSERT_EQ(GenerateRnd(32767), -1) << "Distribution must map negative numbers using sign carry shifts"; +} + +// The Diablo LCG implementation attempts to improve the quality of generated numbers that would only use the low +// bits of the LCG output but due to applying this after taking the absolute value this introduces bias. This may +// be an inconsistency with the implementation in devilutionx, see the comment for RandomTest_ShiftModDistributionHighBits +TEST(RandomTest, ShiftModDistributionLowBits) +{ + constexpr auto maxBound = 32767; + + // All the following seeds generate values less than 2^16, so after shifting they give a 0 value + SetRndSeed(3604671459U); // yields 0 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 0"; + SetRndSeed(0); // yields 1 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 1"; + SetRndSeed(2914375622U); // yields -1 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -1"; + SetRndSeed(538964771); // yields 64 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 64"; + SetRndSeed(2375410851U); // yields -64 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -64"; + SetRndSeed(1229260608); // yields 65 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 65"; + SetRndSeed(1685115014); // yields -65 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -65"; + SetRndSeed(1768225379); // yields 128 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 128"; + SetRndSeed(1146150243); // yields -128 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -128"; + SetRndSeed(1480523688); // yields 7625 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 7625"; + SetRndSeed(1433851934); // yields -7625 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -7625"; + SetRndSeed(2382565573U); // yields 32458 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 32458"; + SetRndSeed(531810049); // yields -32458 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -32458"; + SetRndSeed(1625910243); // yields 32768 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 32768"; + SetRndSeed(1288465379); // yields -32768 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -32768"; + + // -1 from the max bound for the next two to ensure it's not due to a mod with no remainder + SetRndSeed(2561524649U); // yields 65534 + ASSERT_EQ(GenerateRnd(maxBound - 1), 0) << "Invalid distribution when generator yields 65534"; + SetRndSeed(352850973U); // yields -65534 + ASSERT_EQ(GenerateRnd(maxBound - 1), 0) << "Invalid distribution when generator yields -65534"; + + SetRndSeed(3251820486U); // yields 65535 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 65535"; + SetRndSeed(3957522432U); // yields -65535 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -65535"; +} + +// The highest value GenerateRnd can return is 32767 (0x7FFF). I suspect this is the Borland rand() implementation +// Diablo appears to have reimplemented this method incorrectly as that function was implemented as the following: +// uint seed = mult * seed + inc +// return (seed >> 16) & 0x7FFF +// i.e., no cast from unsigned to signed, no modulo when building the return value. +TEST(RandomTest, ShiftModDistributionHighBits) +{ + constexpr auto maxBound = 32767; + SetRndSeed(3267226595U); // yields 65536 + ASSERT_EQ(GenerateRnd(maxBound), 1) << "Invalid distribution when generator yields 65536"; + SetRndSeed(3942116323U); // yields -65536 + ASSERT_EQ(GenerateRnd(maxBound), 1) << "Invalid distribution when generator yields -65536"; + SetRndSeed(4279561187U); // yields 131072 + ASSERT_EQ(GenerateRnd(maxBound), 2) << "Invalid distribution when generator yields 131072"; + SetRndSeed(2929781731U); // yields -131072 + ASSERT_EQ(GenerateRnd(maxBound), 2) << "Invalid distribution when generator yields -131072"; + SetRndSeed(659483619); // yields 262144 + ASSERT_EQ(GenerateRnd(maxBound), 4) << "Invalid distribution when generator yields 262144"; + SetRndSeed(2254892003U); // yields -262144 + ASSERT_EQ(GenerateRnd(maxBound), 4) << "Invalid distribution when generator yields -262144"; + SetRndSeed(3604671458U); // yields 22695477 + ASSERT_EQ(GenerateRnd(maxBound), 346) << "Invalid distribution when generator yields 22695477"; + SetRndSeed(3604671460U); // yields -22695477 + ASSERT_EQ(GenerateRnd(maxBound), 346) << "Invalid distribution when generator yields -22695477"; + SetRndSeed(1012371854); // yields 429496729 + ASSERT_EQ(GenerateRnd(maxBound), 6553) << "Invalid distribution when generator yields 429496729"; + SetRndSeed(1902003768); // yields -429496729 + ASSERT_EQ(GenerateRnd(maxBound), 6553) << "Invalid distribution when generator yields -429496729"; + SetRndSeed(189776845); // yields 1212022642 + ASSERT_EQ(GenerateRnd(maxBound), 18493) << "Invalid distribution when generator yields 1212022642"; + SetRndSeed(2724598777); // yields -1212022642 + ASSERT_EQ(GenerateRnd(maxBound), 18493) << "Invalid distribution when generator yields -1212022642"; + SetRndSeed(76596137); // yields 2147483646 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 2147483646"; + SetRndSeed(2837779485); // yields -2147483646 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -2147483646"; + SetRndSeed(766891974); // yields 2147483647 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields 2147483647"; + SetRndSeed(2147483648); // yields -2147483647 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution when generator yields -2147483647"; +} + +TEST(RandomTest, ModDistributionSignPreserving) +{ + // This distribution is used when the upper bound is a value in [32768, 2147483647] + + // Sign preserving modulo when no division is performed cannot be tested in isolation with the current implementation. + SetRndSeed(1457187811); + ASSERT_EQ(GenerateRnd(32768), 0) << "Distribution must map negative numbers using sign preserving modulo"; + SetRndSeed(1457187811); + ASSERT_EQ(GenerateRnd(32769), -2) << "Distribution must map negative numbers using sign preserving modulo"; + SetRndSeed(1457187811); + ASSERT_EQ(GenerateRnd(65535), -32768) << "Distribution must map negative numbers using sign preserving modulo"; + SetRndSeed(1457187811); + ASSERT_EQ(GenerateRnd(std::numeric_limits::max()), -1) + << "Distribution must map negative numbers using sign preserving modulo"; +} + +TEST(RandomTest, NegativeReturnValues) +{ + // The bug in vanilla RNG stemming from mod instead of bitmasking means that negative values are possible for + // non-power of 2 arguments to GenerateRnd + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(3), -2) << "Unexpected return value for a limit of 3"; + + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(5), -3) << "Unexpected return value for a limit of 5"; + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(6), -2) << "Unexpected return value for a limit of 6"; + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(7), -1) << "Unexpected return value for a limit of 7"; + + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(11), -10) << "Unexpected return value for a limit of 11"; + + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(17), -9) << "Unexpected return value for a limit of 17"; + + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(19), -12) << "Unexpected return value for a limit of 19"; + + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(22), -10) << "Unexpected return value for a limit of 22"; + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(23), -16) << "Unexpected return value for a limit of 23"; + + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(25), -18) << "Unexpected return value for a limit of 25"; + + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(27), -17) << "Unexpected return value for a limit of 27"; + + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(29), -27) << "Unexpected return value for a limit of 29"; + + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(31), -1) << "Unexpected return value for a limit of 31"; + + for (const int i : { 9, 10, 12, 13, 14, 15, 18, 20, 21, 24, 26, 28, 30 }) { + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(i), -8) << "Unexpected return value for a limit of " << i; + } + + // The following values are/were used throughout the code + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(37), -23) << "Unexpected return value for a limit of 37"; + + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(38), -12) << "Unexpected return value for a limit of 38"; + + SetRndSeed(1457187811); + // commonly used for dungeon megatile coordinates + EXPECT_EQ(GenerateRnd(40), -8) << "Unexpected return value for a limit of 40"; + + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(41), -9) << "Unexpected return value for a limit of 41"; + + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(52), -8) << "Unexpected return value for a limit of 52"; + + SetRndSeed(1457187811); + // commonly used for dungeon tile coordinates + EXPECT_EQ(GenerateRnd(80), -48) << "Unexpected return value for a limit of 80"; + + SetRndSeed(1457187811); + // commonly used for percentage rolls (typically in a context that 0 and -68 lead to the same outcome) + EXPECT_EQ(GenerateRnd(100), -68) << "Unexpected return value for a limit of 100"; + + for (int i = 1; i < 32768; i *= 2) { + SetRndSeed(1457187811); + EXPECT_EQ(GenerateRnd(i), 0) << "Expect powers of 2 such as " << i << " to cleanly divide the int_min RNG value "; + } +} +} // namespace devilution diff --git a/test/scrollrt_test.cpp b/test/scrollrt_test.cpp index f85ac40e9..941b9a4b1 100644 --- a/test/scrollrt_test.cpp +++ b/test/scrollrt_test.cpp @@ -1,6 +1,6 @@ #include -#include "control.h" +#include "control/control.hpp" #include "diablo.h" #include "engine/render/scrollrt.h" #include "options.h" diff --git a/test/text_render_integration_test.cpp b/test/text_render_integration_test.cpp index 004b0044c..6f4cacbe2 100644 --- a/test/text_render_integration_test.cpp +++ b/test/text_render_integration_test.cpp @@ -211,6 +211,116 @@ const TestFixture Fixtures[] { { "Two", UiFlags::ColorUiSilverDark }, }, }, + TestFixture { + .name = "cursor-start", + .width = 120, + .height = 15, + .fmt = "Hello World", + .opts = { + .flags = UiFlags::ColorUiGold, + .cursorPosition = 0, // Cursor at start + .cursorStatic = true, + }, + }, + TestFixture { + .name = "cursor-middle", + .width = 120, + .height = 15, + .fmt = "Hello World", + .opts = { + .flags = UiFlags::ColorUiGold, + .cursorPosition = 5, // Cursor after "Hello", + .cursorStatic = true, + }, + }, + TestFixture { + .name = "cursor-end", + .width = 120, + .height = 15, + .fmt = "Hello World", + .opts = { + .flags = UiFlags::ColorUiGold, + .cursorPosition = 11, // Cursor at end + .cursorStatic = true, + }, + }, + TestFixture { + .name = "multiline_cursor-end_first_line", + .width = 100, + .height = 50, + .fmt = "First line\nSecond line", + .opts = { + .flags = UiFlags::ColorUiGold, + .cursorPosition = 10, // Cursor at end of first line + .cursorStatic = true, + }, + }, + TestFixture { + .name = "multiline_cursor-start_second_line", + .width = 100, + .height = 50, + .fmt = "First line\nSecond line", + .opts = { + .flags = UiFlags::ColorUiGold, + .cursorPosition = 11, // Cursor at start of second line + .cursorStatic = true, + }, + }, + TestFixture { + .name = "multiline_cursor-middle_second_line", + .width = 100, + .height = 50, + .fmt = "First line\nSecond line", + .opts = { + .flags = UiFlags::ColorUiGold, + .cursorPosition = 14, // Cursor at second line, at the 'o' of "Second" + .cursorStatic = true, + }, + }, + TestFixture { + .name = "multiline_cursor-end_second_line", + .width = 100, + .height = 50, + .fmt = "First line\nSecond line", + .opts = { + .flags = UiFlags::ColorUiGold, + .cursorPosition = 22, // Cursor at start of second line + .cursorStatic = true, + }, + }, + TestFixture { + .name = "highlight-partial", + .width = 120, + .height = 15, + .fmt = "Hello World", + .opts = { + .flags = UiFlags::ColorUiGold, + .highlightRange = { 5, 10 }, // Highlight " Worl" + .highlightColor = PAL8_BLUE, + }, + }, + TestFixture { + .name = "highlight-full", + .width = 120, + .height = 15, + .fmt = "Hello World", + .opts = { + .flags = UiFlags::ColorUiGold, + .highlightRange = { 0, 11 }, // Highlight entire text + .highlightColor = PAL8_BLUE, + }, + }, + TestFixture { + .name = "multiline_highlight", + .width = 70, + .height = 50, + .fmt = "Hello\nWorld", + .opts = { + .flags = UiFlags::ColorUiGold, + .highlightRange = { 3, 8 }, // Highlight "lo\nWo" + .highlightColor = PAL8_BLUE, + }, + }, }; SDLPaletteUniquePtr LoadPalette() diff --git a/test/tile_properties_test.cpp b/test/tile_properties_test.cpp index 56d502459..539a41ad7 100644 --- a/test/tile_properties_test.cpp +++ b/test/tile_properties_test.cpp @@ -5,8 +5,8 @@ #include "levels/dun_tile.hpp" #include "levels/gendung.h" -#include "objdat.h" #include "objects.h" +#include "tables/objdat.h" namespace devilution { namespace { diff --git a/test/timedemo_test.cpp b/test/timedemo_test.cpp index b379ecc3e..824b8f062 100644 --- a/test/timedemo_test.cpp +++ b/test/timedemo_test.cpp @@ -14,10 +14,10 @@ #include "headless_mode.hpp" #include "init.hpp" #include "lua/lua_global.hpp" -#include "monstdat.h" #include "options.h" #include "pfile.h" -#include "playerdat.hpp" +#include "tables/monstdat.h" +#include "tables/playerdat.hpp" #include "utils/display.h" #include "utils/paths.h" diff --git a/test/townerdat_test.cpp b/test/townerdat_test.cpp index d9ca84f38..52343ca5f 100644 --- a/test/townerdat_test.cpp +++ b/test/townerdat_test.cpp @@ -1,278 +1,278 @@ -#include - -#ifdef USE_SDL3 -#include -#else -#include -#endif - -#include "engine/assets.hpp" -#include "townerdat.hpp" -#include "towners.h" -#include "utils/paths.h" - -namespace devilution { - -namespace { - -void SetTestAssetsPath() -{ - const std::string assetsPath = paths::BasePath() + "/assets/"; - paths::SetAssetsPath(assetsPath); -} - -void InitializeSDL() -{ -#ifdef USE_SDL3 - if (!SDL_Init(SDL_INIT_EVENTS)) { - // SDL_Init returns 0 on success in SDL3 - return; - } -#elif !defined(USE_SDL1) - if (SDL_Init(SDL_INIT_EVENTS) >= 0) { - return; - } -#else - if (SDL_Init(0) >= 0) { - return; - } -#endif - // If we get here, SDL initialization failed - // In tests, we'll continue anyway as file operations might still work -} - -/** - * @brief Helper to find a towner data entry by type. - */ -const TownerDataEntry *FindTownerDataByType(_talker_id type) -{ - for (const auto &entry : TownersDataEntries) { - if (entry.type == type) { - return &entry; - } - } - return nullptr; -} - -} // namespace - -TEST(TownerDat, LoadTownerData) -{ - InitializeSDL(); - SetTestAssetsPath(); - LoadTownerData(); - - // Verify we loaded the expected number of towners from assets - ASSERT_GE(TownersDataEntries.size(), 4u) << "Should load at least 4 towners from assets"; - - // Check Griswold (TOWN_SMITH) - const TownerDataEntry *smith = FindTownerDataByType(TOWN_SMITH); - ASSERT_NE(smith, nullptr) << "Should find TOWN_SMITH data"; - EXPECT_EQ(smith->type, TOWN_SMITH); - EXPECT_EQ(smith->name, "Griswold the Blacksmith"); - EXPECT_EQ(smith->position.x, 62); - EXPECT_EQ(smith->position.y, 63); - EXPECT_EQ(smith->direction, Direction::SouthWest); - EXPECT_EQ(smith->animWidth, 96); - EXPECT_EQ(smith->animPath, "towners\\smith\\smithn"); - EXPECT_EQ(smith->animFrames, 16); - EXPECT_EQ(smith->animDelay, 3); - EXPECT_EQ(smith->gossipTexts.size(), 11u); - EXPECT_EQ(smith->gossipTexts[0], TEXT_GRISWOLD2); - EXPECT_EQ(smith->gossipTexts[10], TEXT_GRISWOLD13); - ASSERT_GE(smith->animOrder.size(), 4u); - EXPECT_EQ(smith->animOrder[0], 4); - EXPECT_EQ(smith->animOrder[3], 7); - - // Check Pepin (TOWN_HEALER) - const TownerDataEntry *healer = FindTownerDataByType(TOWN_HEALER); - ASSERT_NE(healer, nullptr) << "Should find TOWN_HEALER data"; - EXPECT_EQ(healer->type, TOWN_HEALER); - EXPECT_EQ(healer->name, "Pepin the Healer"); - EXPECT_EQ(healer->position.x, 55); - EXPECT_EQ(healer->position.y, 79); - EXPECT_EQ(healer->direction, Direction::SouthEast); - EXPECT_EQ(healer->animFrames, 20); - EXPECT_EQ(healer->gossipTexts.size(), 9u); - ASSERT_GE(healer->animOrder.size(), 3u); - - // Check Dead Guy (TOWN_DEADGUY) - has empty gossip texts and animOrder - const TownerDataEntry *deadguy = FindTownerDataByType(TOWN_DEADGUY); - ASSERT_NE(deadguy, nullptr) << "Should find TOWN_DEADGUY data"; - EXPECT_EQ(deadguy->type, TOWN_DEADGUY); - EXPECT_EQ(deadguy->name, "Wounded Townsman"); - EXPECT_EQ(deadguy->direction, Direction::North); - EXPECT_TRUE(deadguy->gossipTexts.empty()) << "Dead guy should have no gossip texts"; - EXPECT_TRUE(deadguy->animOrder.empty()) << "Dead guy should have no custom anim order"; - - // Check Cow (TOWN_COW) - has empty animPath but animFrames and animDelay are set - const TownerDataEntry *cow = FindTownerDataByType(TOWN_COW); - ASSERT_NE(cow, nullptr) << "Should find TOWN_COW data"; - EXPECT_EQ(cow->type, TOWN_COW); - EXPECT_EQ(cow->name, "Cow"); - EXPECT_EQ(cow->position.x, 58); - EXPECT_EQ(cow->position.y, 16); - EXPECT_EQ(cow->direction, Direction::SouthWest); - EXPECT_EQ(cow->animWidth, 128); - EXPECT_TRUE(cow->animPath.empty()) << "Cow should have empty animPath"; - EXPECT_EQ(cow->animFrames, 12); - EXPECT_EQ(cow->animDelay, 3); - EXPECT_TRUE(cow->gossipTexts.empty()) << "Cow should have no gossip texts"; - EXPECT_TRUE(cow->animOrder.empty()) << "Cow should have no custom anim order"; -} - -TEST(TownerDat, LoadQuestDialogTable) -{ - InitializeSDL(); - SetTestAssetsPath(); - LoadTownerData(); - - // Check Smith quest dialogs - EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_BUTCHER), TEXT_BUTCH5); - EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_LTBANNER), TEXT_BANNER6); - EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_SKELKING), TEXT_KING7); - EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_ROCK), TEXT_INFRA6); - - // Check Healer quest dialogs - EXPECT_EQ(GetTownerQuestDialog(TOWN_HEALER, Q_BUTCHER), TEXT_BUTCH3); - EXPECT_EQ(GetTownerQuestDialog(TOWN_HEALER, Q_LTBANNER), TEXT_BANNER4); - EXPECT_EQ(GetTownerQuestDialog(TOWN_HEALER, Q_SKELKING), TEXT_KING5); - - // Check Dead guy quest dialogs - EXPECT_EQ(GetTownerQuestDialog(TOWN_DEADGUY, Q_BUTCHER), TEXT_NONE); - EXPECT_EQ(GetTownerQuestDialog(TOWN_DEADGUY, Q_LTBANNER), TEXT_NONE); -} - -TEST(TownerDat, SetTownerQuestDialog) -{ - InitializeSDL(); - SetTestAssetsPath(); - LoadTownerData(); - - // Verify initial value from assets - EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_MUSHROOM), TEXT_MUSH6); - - // Modify it - SetTownerQuestDialog(TOWN_SMITH, Q_MUSHROOM, TEXT_MUSH1); - - // Verify it changed - EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_MUSHROOM), TEXT_MUSH1); - - // Reset to original value for other tests - SetTownerQuestDialog(TOWN_SMITH, Q_MUSHROOM, TEXT_MUSH6); -} - -TEST(TownerDat, GetQuestDialogInvalidType) -{ - InitializeSDL(); - SetTestAssetsPath(); - LoadTownerData(); - - // Invalid towner type should return TEXT_NONE - // Use a value that's guaranteed to be invalid (beyond enum range) - _talker_id invalidType = static_cast<_talker_id>(255); - _speech_id result = GetTownerQuestDialog(invalidType, Q_BUTCHER); - EXPECT_EQ(result, TEXT_NONE) << "Should return TEXT_NONE for invalid towner type"; -} - -TEST(TownerDat, GetQuestDialogInvalidQuest) -{ - InitializeSDL(); - SetTestAssetsPath(); - LoadTownerData(); - - // Invalid quest ID should return TEXT_NONE - _speech_id result = GetTownerQuestDialog(TOWN_SMITH, static_cast(-1)); - EXPECT_EQ(result, TEXT_NONE) << "Should return TEXT_NONE for invalid quest ID"; - - result = GetTownerQuestDialog(TOWN_SMITH, static_cast(MAXQUESTS)); - EXPECT_EQ(result, TEXT_NONE) << "Should return TEXT_NONE for out-of-range quest ID"; -} - -TEST(TownerDat, TownerLongNamesPopulated) -{ - InitializeSDL(); - SetTestAssetsPath(); - LoadTownerData(); - - // Build TownerLongNames as InitTowners() does - TownerLongNames.clear(); - for (const auto &entry : TownersDataEntries) { - TownerLongNames.try_emplace(entry.type, entry.name); - } - - // Verify TownerLongNames is populated correctly - EXPECT_FALSE(TownerLongNames.empty()) << "TownerLongNames should not be empty after loading"; - - // Check specific entries - auto smithIt = TownerLongNames.find(TOWN_SMITH); - ASSERT_NE(smithIt, TownerLongNames.end()) << "Should find TOWN_SMITH in TownerLongNames"; - EXPECT_EQ(smithIt->second, "Griswold the Blacksmith"); - - auto healerIt = TownerLongNames.find(TOWN_HEALER); - ASSERT_NE(healerIt, TownerLongNames.end()) << "Should find TOWN_HEALER in TownerLongNames"; - EXPECT_EQ(healerIt->second, "Pepin the Healer"); -} - -TEST(TownerDat, GetNumTownerTypes) -{ - InitializeSDL(); - SetTestAssetsPath(); - LoadTownerData(); - - // Build TownerLongNames as InitTowners() does - TownerLongNames.clear(); - for (const auto &entry : TownersDataEntries) { - TownerLongNames.try_emplace(entry.type, entry.name); - } - - // GetNumTownerTypes should return the number of unique towner types - size_t numTypes = GetNumTownerTypes(); - EXPECT_GT(numTypes, 0u) << "Should have at least one towner type"; - EXPECT_EQ(numTypes, TownerLongNames.size()) << "GetNumTownerTypes should match TownerLongNames size"; -} - -TEST(TownerDat, MultipleCowsOnlyOneType) -{ - InitializeSDL(); - SetTestAssetsPath(); - LoadTownerData(); - - // Count how many TOWN_COW entries exist in the data - size_t cowCount = 0; - for (const auto &entry : TownersDataEntries) { - if (entry.type == TOWN_COW) { - cowCount++; - } - } - - // There should be multiple cows but only one type entry - EXPECT_GT(cowCount, 1u) << "TSV should have multiple cow entries"; - - // Build TownerLongNames - TownerLongNames.clear(); - for (const auto &entry : TownersDataEntries) { - TownerLongNames.try_emplace(entry.type, entry.name); - } - - // But only one entry in TownerLongNames for TOWN_COW - auto cowIt = TownerLongNames.find(TOWN_COW); - ASSERT_NE(cowIt, TownerLongNames.end()) << "Should find TOWN_COW in TownerLongNames"; - EXPECT_EQ(cowIt->second, "Cow"); -} - -TEST(TownerDat, QuestDialogOptionalColumns) -{ - InitializeSDL(); - SetTestAssetsPath(); - LoadTownerData(); - - // Verify that missing quest columns default to TEXT_NONE - // Q_FARMER, Q_GIRL, Q_DEFILER, Q_NAKRUL, Q_CORNSTN, Q_JERSEY may not be in base TSV - // but the code should handle them gracefully - _speech_id result = GetTownerQuestDialog(TOWN_SMITH, Q_FARMER); - // Should be TEXT_NONE since TOWN_SMITH doesn't have farmer quest dialog - EXPECT_EQ(result, TEXT_NONE) << "Should return TEXT_NONE for unused quest columns"; -} - -} // namespace devilution +#include + +#ifdef USE_SDL3 +#include +#else +#include +#endif + +#include "engine/assets.hpp" +#include "tables/townerdat.hpp" +#include "towners.h" +#include "utils/paths.h" + +namespace devilution { + +namespace { + +void SetTestAssetsPath() +{ + const std::string assetsPath = paths::BasePath() + "/assets/"; + paths::SetAssetsPath(assetsPath); +} + +void InitializeSDL() +{ +#ifdef USE_SDL3 + if (!SDL_Init(SDL_INIT_EVENTS)) { + // SDL_Init returns 0 on success in SDL3 + return; + } +#elif !defined(USE_SDL1) + if (SDL_Init(SDL_INIT_EVENTS) >= 0) { + return; + } +#else + if (SDL_Init(0) >= 0) { + return; + } +#endif + // If we get here, SDL initialization failed + // In tests, we'll continue anyway as file operations might still work +} + +/** + * @brief Helper to find a towner data entry by type. + */ +const TownerDataEntry *FindTownerDataByType(_talker_id type) +{ + for (const auto &entry : TownersDataEntries) { + if (entry.type == type) { + return &entry; + } + } + return nullptr; +} + +} // namespace + +TEST(TownerDat, LoadTownerData) +{ + InitializeSDL(); + SetTestAssetsPath(); + LoadTownerData(); + + // Verify we loaded the expected number of towners from assets + ASSERT_GE(TownersDataEntries.size(), 4u) << "Should load at least 4 towners from assets"; + + // Check Griswold (TOWN_SMITH) + const TownerDataEntry *smith = FindTownerDataByType(TOWN_SMITH); + ASSERT_NE(smith, nullptr) << "Should find TOWN_SMITH data"; + EXPECT_EQ(smith->type, TOWN_SMITH); + EXPECT_EQ(smith->name, "Griswold the Blacksmith"); + EXPECT_EQ(smith->position.x, 62); + EXPECT_EQ(smith->position.y, 63); + EXPECT_EQ(smith->direction, Direction::SouthWest); + EXPECT_EQ(smith->animWidth, 96); + EXPECT_EQ(smith->animPath, "towners\\smith\\smithn"); + EXPECT_EQ(smith->animFrames, 16); + EXPECT_EQ(smith->animDelay, 3); + EXPECT_EQ(smith->gossipTexts.size(), 11u); + EXPECT_EQ(smith->gossipTexts[0], TEXT_GRISWOLD2); + EXPECT_EQ(smith->gossipTexts[10], TEXT_GRISWOLD13); + ASSERT_GE(smith->animOrder.size(), 4u); + EXPECT_EQ(smith->animOrder[0], 4); + EXPECT_EQ(smith->animOrder[3], 7); + + // Check Pepin (TOWN_HEALER) + const TownerDataEntry *healer = FindTownerDataByType(TOWN_HEALER); + ASSERT_NE(healer, nullptr) << "Should find TOWN_HEALER data"; + EXPECT_EQ(healer->type, TOWN_HEALER); + EXPECT_EQ(healer->name, "Pepin the Healer"); + EXPECT_EQ(healer->position.x, 55); + EXPECT_EQ(healer->position.y, 79); + EXPECT_EQ(healer->direction, Direction::SouthEast); + EXPECT_EQ(healer->animFrames, 20); + EXPECT_EQ(healer->gossipTexts.size(), 9u); + ASSERT_GE(healer->animOrder.size(), 3u); + + // Check Dead Guy (TOWN_DEADGUY) - has empty gossip texts and animOrder + const TownerDataEntry *deadguy = FindTownerDataByType(TOWN_DEADGUY); + ASSERT_NE(deadguy, nullptr) << "Should find TOWN_DEADGUY data"; + EXPECT_EQ(deadguy->type, TOWN_DEADGUY); + EXPECT_EQ(deadguy->name, "Wounded Townsman"); + EXPECT_EQ(deadguy->direction, Direction::North); + EXPECT_TRUE(deadguy->gossipTexts.empty()) << "Dead guy should have no gossip texts"; + EXPECT_TRUE(deadguy->animOrder.empty()) << "Dead guy should have no custom anim order"; + + // Check Cow (TOWN_COW) - has empty animPath but animFrames and animDelay are set + const TownerDataEntry *cow = FindTownerDataByType(TOWN_COW); + ASSERT_NE(cow, nullptr) << "Should find TOWN_COW data"; + EXPECT_EQ(cow->type, TOWN_COW); + EXPECT_EQ(cow->name, "Cow"); + EXPECT_EQ(cow->position.x, 58); + EXPECT_EQ(cow->position.y, 16); + EXPECT_EQ(cow->direction, Direction::SouthWest); + EXPECT_EQ(cow->animWidth, 128); + EXPECT_TRUE(cow->animPath.empty()) << "Cow should have empty animPath"; + EXPECT_EQ(cow->animFrames, 12); + EXPECT_EQ(cow->animDelay, 3); + EXPECT_TRUE(cow->gossipTexts.empty()) << "Cow should have no gossip texts"; + EXPECT_TRUE(cow->animOrder.empty()) << "Cow should have no custom anim order"; +} + +TEST(TownerDat, LoadQuestDialogTable) +{ + InitializeSDL(); + SetTestAssetsPath(); + LoadTownerData(); + + // Check Smith quest dialogs + EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_BUTCHER), TEXT_BUTCH5); + EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_LTBANNER), TEXT_BANNER6); + EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_SKELKING), TEXT_KING7); + EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_ROCK), TEXT_INFRA6); + + // Check Healer quest dialogs + EXPECT_EQ(GetTownerQuestDialog(TOWN_HEALER, Q_BUTCHER), TEXT_BUTCH3); + EXPECT_EQ(GetTownerQuestDialog(TOWN_HEALER, Q_LTBANNER), TEXT_BANNER4); + EXPECT_EQ(GetTownerQuestDialog(TOWN_HEALER, Q_SKELKING), TEXT_KING5); + + // Check Dead guy quest dialogs + EXPECT_EQ(GetTownerQuestDialog(TOWN_DEADGUY, Q_BUTCHER), TEXT_NONE); + EXPECT_EQ(GetTownerQuestDialog(TOWN_DEADGUY, Q_LTBANNER), TEXT_NONE); +} + +TEST(TownerDat, SetTownerQuestDialog) +{ + InitializeSDL(); + SetTestAssetsPath(); + LoadTownerData(); + + // Verify initial value from assets + EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_MUSHROOM), TEXT_MUSH6); + + // Modify it + SetTownerQuestDialog(TOWN_SMITH, Q_MUSHROOM, TEXT_MUSH1); + + // Verify it changed + EXPECT_EQ(GetTownerQuestDialog(TOWN_SMITH, Q_MUSHROOM), TEXT_MUSH1); + + // Reset to original value for other tests + SetTownerQuestDialog(TOWN_SMITH, Q_MUSHROOM, TEXT_MUSH6); +} + +TEST(TownerDat, GetQuestDialogInvalidType) +{ + InitializeSDL(); + SetTestAssetsPath(); + LoadTownerData(); + + // Invalid towner type should return TEXT_NONE + // Use a value that's guaranteed to be invalid (beyond enum range) + _talker_id invalidType = static_cast<_talker_id>(255); + _speech_id result = GetTownerQuestDialog(invalidType, Q_BUTCHER); + EXPECT_EQ(result, TEXT_NONE) << "Should return TEXT_NONE for invalid towner type"; +} + +TEST(TownerDat, GetQuestDialogInvalidQuest) +{ + InitializeSDL(); + SetTestAssetsPath(); + LoadTownerData(); + + // Invalid quest ID should return TEXT_NONE + _speech_id result = GetTownerQuestDialog(TOWN_SMITH, static_cast(-1)); + EXPECT_EQ(result, TEXT_NONE) << "Should return TEXT_NONE for invalid quest ID"; + + result = GetTownerQuestDialog(TOWN_SMITH, static_cast(MAXQUESTS)); + EXPECT_EQ(result, TEXT_NONE) << "Should return TEXT_NONE for out-of-range quest ID"; +} + +TEST(TownerDat, TownerLongNamesPopulated) +{ + InitializeSDL(); + SetTestAssetsPath(); + LoadTownerData(); + + // Build TownerLongNames as InitTowners() does + TownerLongNames.clear(); + for (const auto &entry : TownersDataEntries) { + TownerLongNames.try_emplace(entry.type, entry.name); + } + + // Verify TownerLongNames is populated correctly + EXPECT_FALSE(TownerLongNames.empty()) << "TownerLongNames should not be empty after loading"; + + // Check specific entries + auto smithIt = TownerLongNames.find(TOWN_SMITH); + ASSERT_NE(smithIt, TownerLongNames.end()) << "Should find TOWN_SMITH in TownerLongNames"; + EXPECT_EQ(smithIt->second, "Griswold the Blacksmith"); + + auto healerIt = TownerLongNames.find(TOWN_HEALER); + ASSERT_NE(healerIt, TownerLongNames.end()) << "Should find TOWN_HEALER in TownerLongNames"; + EXPECT_EQ(healerIt->second, "Pepin the Healer"); +} + +TEST(TownerDat, GetNumTownerTypes) +{ + InitializeSDL(); + SetTestAssetsPath(); + LoadTownerData(); + + // Build TownerLongNames as InitTowners() does + TownerLongNames.clear(); + for (const auto &entry : TownersDataEntries) { + TownerLongNames.try_emplace(entry.type, entry.name); + } + + // GetNumTownerTypes should return the number of unique towner types + size_t numTypes = GetNumTownerTypes(); + EXPECT_GT(numTypes, 0u) << "Should have at least one towner type"; + EXPECT_EQ(numTypes, TownerLongNames.size()) << "GetNumTownerTypes should match TownerLongNames size"; +} + +TEST(TownerDat, MultipleCowsOnlyOneType) +{ + InitializeSDL(); + SetTestAssetsPath(); + LoadTownerData(); + + // Count how many TOWN_COW entries exist in the data + size_t cowCount = 0; + for (const auto &entry : TownersDataEntries) { + if (entry.type == TOWN_COW) { + cowCount++; + } + } + + // There should be multiple cows but only one type entry + EXPECT_GT(cowCount, 1u) << "TSV should have multiple cow entries"; + + // Build TownerLongNames + TownerLongNames.clear(); + for (const auto &entry : TownersDataEntries) { + TownerLongNames.try_emplace(entry.type, entry.name); + } + + // But only one entry in TownerLongNames for TOWN_COW + auto cowIt = TownerLongNames.find(TOWN_COW); + ASSERT_NE(cowIt, TownerLongNames.end()) << "Should find TOWN_COW in TownerLongNames"; + EXPECT_EQ(cowIt->second, "Cow"); +} + +TEST(TownerDat, QuestDialogOptionalColumns) +{ + InitializeSDL(); + SetTestAssetsPath(); + LoadTownerData(); + + // Verify that missing quest columns default to TEXT_NONE + // Q_FARMER, Q_GIRL, Q_DEFILER, Q_NAKRUL, Q_CORNSTN, Q_JERSEY may not be in base TSV + // but the code should handle them gracefully + _speech_id result = GetTownerQuestDialog(TOWN_SMITH, Q_FARMER); + // Should be TEXT_NONE since TOWN_SMITH doesn't have farmer quest dialog + EXPECT_EQ(result, TEXT_NONE) << "Should return TEXT_NONE for unused quest columns"; +} + +} // namespace devilution diff --git a/test/vendor_test.cpp b/test/vendor_test.cpp index 82a4e5ee8..d40083c20 100644 --- a/test/vendor_test.cpp +++ b/test/vendor_test.cpp @@ -3,8 +3,8 @@ #include "items.h" #include "player.h" -#include "playerdat.hpp" #include "stores.h" +#include "tables/playerdat.hpp" #include "engine/assets.hpp" #include "engine/random.hpp" diff --git a/test/writehero_test.cpp b/test/writehero_test.cpp index 2d10f9f5f..892f7e18d 100644 --- a/test/writehero_test.cpp +++ b/test/writehero_test.cpp @@ -14,7 +14,7 @@ #include "loadsave.h" #include "pack.h" #include "pfile.h" -#include "playerdat.hpp" +#include "tables/playerdat.hpp" #include "utils/endian_swap.hpp" #include "utils/file_util.h" #include "utils/paths.h"