diff --git a/.bettercodehub.yml b/.bettercodehub.yml deleted file mode 100644 index 70e508efaa2..00000000000 --- a/.bettercodehub.yml +++ /dev/null @@ -1,6 +0,0 @@ -exclude: -- /Packaging/.* -- /3rdParty/.* -languages: -- cpp -component_depth: 2 diff --git a/.circleci/config.yml b/.circleci/config.yml index ddd92ae235b..7fd42ef8981 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,7 +30,7 @@ jobs: - store_artifacts: {path: ./build/devilutionx.cia, destination: devilutionx.cia} amigaos-m68k: docker: - - image: amigadev/crosstools:m68k-amigaos + - image: amigadev/crosstools:m68k-amigaos-gcc10 working_directory: ~/repo steps: - checkout diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000000..668b596c926 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,43 @@ +ARG VARIANT=debian-12 +FROM mcr.microsoft.com/devcontainers/base:${VARIANT} +USER root + +# Install APT packages +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install tar curl zip unzip bash-completion build-essential ripgrep htop \ + ninja-build ccache g++ mold gdb clang-format clang-tidy \ + rpm pkg-config cmake git smpq gettext libsdl2-dev libsdl2-image-dev libsodium-dev \ + libpng-dev libbz2-dev libfmt-dev libgtest-dev libgmock-dev libsimpleini-dev zsh \ + qtbase5-dev qt6-base-dev ristretto \ + && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* + +# Install devilutionx-graphics-tools +RUN git clone https://github.com/diasurgical/devilutionx-graphics-tools.git /tmp/devilutionx-graphics-tools && \ + cd /tmp/devilutionx-graphics-tools && \ + cmake -S. -Bbuild-rel -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF && \ + cmake --build build-rel -j $(getconf _NPROCESSORS_ONLN) && \ + cmake --install build-rel --component Binaries && \ + rm -rf /tmp/devilutionx-graphics-tools + +# Install devilutionx-mpq-tools +RUN git clone https://github.com/diasurgical/devilutionx-mpq-tools.git /tmp/devilutionx-mpq-tools && \ + cd /tmp/devilutionx-mpq-tools && \ + cmake -S. -Bbuild-rel -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF && \ + cmake --build build-rel -j $(getconf _NPROCESSORS_ONLN) && \ + cmake --install build-rel && \ + rm -rf /tmp/devilutionx-mpq-tools + +# Install d1-graphics-tool +RUN curl -O -L https://github.com/diasurgical/d1-graphics-tool/releases/latest/download/D1GraphicsTool-Linux-x64.deb && \ + dpkg -i D1GraphicsTool-Linux-x64.deb && \ + rm D1GraphicsTool-Linux-x64.deb + +# Download spawn.mpq and fonts.mpq +RUN curl --create-dirs -O -L --output-dir /usr/local/share/diasurgical/devilutionx/ \ + https://github.com/diasurgical/devilutionx-assets/releases/latest/download/spawn.mpq && \ + curl --create-dirs -O -L --output-dir /usr/local/share/diasurgical/devilutionx/ \ + https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq && \ + chown -R vscode: /usr/local/share/diasurgical/ + +# Desktop environment configuration +COPY fluxbox /home/vscode/.fluxbox/ diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000000..6bdd635a3fe --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,30 @@ +{ + "build": { + "dockerfile": "Dockerfile" + }, + "customizations": { + "vscode": { + "extensions": [ + "github.vscode-github-actions", + "ms-vscode.cmake-tools" + ] + } + }, + "features": { + // https://github.com/devcontainers/features/tree/main/src/desktop-lite + "ghcr.io/devcontainers/features/desktop-lite:1": { + "webPort": 6080, + "password": "vscode" + }, + "ghcr.io/devcontainers-contrib/features/zsh-plugins:0": {}, + "ghcr.io/stuartleeks/dev-container-features/shell-history:0": {} + }, + "forwardPorts": [ + 6080 + ], + "portsAttributes": { + "6080": { + "label": "desktop" + } + } +} diff --git a/.devcontainer/fluxbox/apps b/.devcontainer/fluxbox/apps new file mode 100644 index 00000000000..df1a03c8613 --- /dev/null +++ b/.devcontainer/fluxbox/apps @@ -0,0 +1,7 @@ +[transient] (role=GtkFileChooserDialog) + [Dimensions] {70% 70%} + [Position] (CENTER) {0 0} +[end] +[app] (name=AppRun) (class=tiled) + [Fullscreen] {yes} +[end] diff --git a/.devcontainer/fluxbox/menu b/.devcontainer/fluxbox/menu new file mode 100644 index 00000000000..a01a06abcbb --- /dev/null +++ b/.devcontainer/fluxbox/menu @@ -0,0 +1,20 @@ +[begin] ( Application Menu ) + [exec] (File Manager) { nautilus /workspaces/devilutionX } + [exec] (D1 Graphics Tool) { D1GraphicsTool } <> + [exec] (Text Editor) { mousepad } <> + [exec] (Terminal) { tilix -w ~ -e $(readlink -f /proc/$$/exe) -il } <> + [exec] (Web Browser) { x-www-browser --disable-dev-shm-usage } <> + [submenu] (System) {} + [exec] (Set Resolution) { tilix -t "Set Resolution" -e bash /usr/local/bin/set-resolution } <> + [exec] (Edit Application Menu) { mousepad ~/.fluxbox/menu } <> + [exec] (Passwords and Keys) { seahorse } <> + [exec] (Top Processes) { tilix -t "Top" -e htop } <> + [exec] (Disk Utilization) { tilix -t "Disk Utilization" -e ncdu / } <> + [exec] (Editres) {editres} <> + [exec] (Xfontsel) {xfontsel} <> + [exec] (Xkill) {xkill} <> + [exec] (Xrefresh) {xrefresh} <> + [end] + [config] (Configuration) + [workspaces] (Workspaces) +[end] diff --git a/.editorconfig b/.editorconfig index 7ab1cba7638..3d7d00f2efa 100644 --- a/.editorconfig +++ b/.editorconfig @@ -17,6 +17,11 @@ end_of_line = lf [*.po] end_of_line = lf +[*.lua] +indent_style = space +indent_size = 2 +end_of_line = lf + [*.py] indent_style = space indent_size = 4 @@ -53,6 +58,9 @@ end_of_line = lf [*.txt] end_of_line = crlf +[*.tsv] +trim_trailing_whitespace = false + [AppRun] end_of_line = lf diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f6545858c01..6f9ca6df312 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,5 +1,7 @@ name: Bug report description: Create a report to help us improve +title: "[Issue Report]: " +labels: ["issue report"] body: - type: dropdown id: operating-system @@ -26,7 +28,8 @@ body: attributes: label: DevilutionX version options: - - 1.5.0 (latest release) + - 1.5.1 + - 1.5.0 - 1.4.1 - 1.4.0 - 1.3.0 diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000000..3ce1052d6d7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,23 @@ +name: Feature Request +description: Request a feature or improvement. +title: "[Feature Request]: " +labels: ["enhancement"] +body: + - type: dropdown + id: feature-type + attributes: + label: Feature Type + options: + - Quality of Life + - Touch Controls + - Gamepad Controls + - Other (please specify) + validations: + required: true + - type: textarea + id: description + attributes: + label: Describe + placeholder: A clear and concise description of the desired feature/change. + validations: + required: true diff --git a/.github/workflows/Android.yml b/.github/workflows/Android.yml index 7e948c4488e..97f77112882 100644 --- a/.github/workflows/Android.yml +++ b/.github/workflows/Android.yml @@ -6,10 +6,12 @@ on: - master paths-ignore: - '*.md' + - 'docs/**' pull_request: types: [ opened, synchronize ] paths-ignore: - '*.md' + - 'docs/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -23,12 +25,12 @@ jobs: run: sudo apt-get update && sudo apt-get install -y gettext - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'adopt' @@ -38,8 +40,8 @@ jobs: uses: actions/cache@v3 with: path: android-project/app/.cxx - key: ${{ github.workflow }}-v2-${{ github.sha }} - restore-keys: ${{ github.workflow }}-v2- + key: ${{ github.workflow }}-v4-${{ github.sha }} + restore-keys: ${{ github.workflow }}-v4- - name: Build working-directory: ${{github.workspace}} @@ -48,7 +50,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 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 25f7c2ba620..f889368644b 100644 --- a/.github/workflows/Linux_aarch64.yml +++ b/.github/workflows/Linux_aarch64.yml @@ -6,14 +6,17 @@ on: - master paths-ignore: - '*.md' + - 'docs/**' pull_request: types: [opened, synchronize] paths-ignore: - '*.md' + - 'docs/**' release: types: [published] paths-ignore: - '*.md' + - 'docs/**' workflow_dispatch: concurrency: @@ -25,23 +28,44 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Create Build Environment + # Work around the somewhat broken packages in the GitHub Actions Ubuntu 20.04 image. + # https://github.com/actions/runner-images/issues/4620#issuecomment-981333260 + - name: Work around broken packages + run: sudo apt-get -y install --allow-downgrades libpcre2-8-0=10.34-7 + + - name: Add clang repo + run: | + wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc + sudo tee /etc/apt/sources.list.d/clang.list < + sudo apt-get update && + sudo apt-get install -y cmake gcc-mingw-w64-i686 g++-mingw-w64-i686 mingw-w64-tools libz-mingw-w64-dev gettext dpkg-dev wget git sudo smpq && + sudo rm /usr/i686-w64-mingw32/lib/libz.dll.a && + sudo Packaging/windows/mingw9x-prep.sh + + - name: Configure CMake + shell: bash + working-directory: ${{github.workspace}} + run: cmake -S. -Bbuild-windows9x -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DCPACK=ON -DCMAKE_TOOLCHAIN_FILE=../CMake/platforms/mingw9x.toolchain.cmake -DTARGET_PLATORM=windows9x + + - name: Build + working-directory: ${{github.workspace}} + shell: bash + run: | + cmake --build build-windows9x -j $(nproc) --target package + mv build-windows9x/devilutionx.zip devilutionx-win9x.zip + + - name: Upload-Package + if: ${{ !env.ACT }} + uses: actions/upload-artifact@v4 + with: + path: devilutionx-win9x.zip + + - name: Update Release + if: ${{ github.event_name == 'release' && !env.ACT }} + uses: svenstaro/upload-release-action@v2 + with: + file: devilutionx-win9x.zip + overwrite: true + diff --git a/.github/workflows/Windows_MSVC_x64.yml b/.github/workflows/Windows_MSVC_x64.yml index 32fd06c5069..d3168dce8c8 100644 --- a/.github/workflows/Windows_MSVC_x64.yml +++ b/.github/workflows/Windows_MSVC_x64.yml @@ -6,10 +6,19 @@ on: - master paths-ignore: - '*.md' + - 'docs/**' pull_request: types: [ opened, synchronize ] paths-ignore: - '*.md' + - 'docs/**' + +permissions: + contents: write + +env: + VCPKG_FEATURE_FLAGS: dependencygraph + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -20,7 +29,7 @@ jobs: runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -31,9 +40,9 @@ jobs: uses: lukka/get-cmake@latest - name: Restore or setup vcpkg - uses: lukka/run-vcpkg@v11 + uses: lukka/run-vcpkg@v11.4 with: - vcpkgGitCommitId: '78b61582c9e093fda56a01ebb654be15a0033897' + vcpkgGitCommitId: '16ee2ecb31788c336ace8bb14c21801efb6836e4' - name: Fetch test data run: | @@ -52,7 +61,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: devilutionx.exe path: | diff --git a/.github/workflows/Windows_MinGW_x64.yml b/.github/workflows/Windows_MinGW_x64.yml index 286a1df34d2..aba82bd212c 100644 --- a/.github/workflows/Windows_MinGW_x64.yml +++ b/.github/workflows/Windows_MinGW_x64.yml @@ -6,10 +6,12 @@ on: - master paths-ignore: - '*.md' + - 'docs/**' pull_request: types: [ opened, synchronize ] paths-ignore: - '*.md' + - 'docs/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -20,7 +22,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -34,7 +36,7 @@ jobs: - name: Configure CMake shell: bash working-directory: ${{github.workspace}} - run: cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DCPACK=ON -DCMAKE_TOOLCHAIN_FILE=../CMake/platforms/mingwcc64.toolchain.cmake -DDEVILUTIONX_SYSTEM_BZIP2=OFF -DDEVILUTIONX_STATIC_LIBSODIUM=ON -DDISCORD_INTEGRATION=ON + run: cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DCPACK=ON -DCMAKE_TOOLCHAIN_FILE=../CMake/platforms/mingwcc64.toolchain.cmake -DDEVILUTIONX_SYSTEM_BZIP2=OFF -DDEVILUTIONX_STATIC_LIBSODIUM=ON -DDISCORD_INTEGRATION=ON -DSCREEN_READER_INTEGRATION=ON - name: Build working-directory: ${{github.workspace}} @@ -43,7 +45,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 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 37dfac850c5..d35f24eb957 100644 --- a/.github/workflows/Windows_MinGW_x86.yml +++ b/.github/workflows/Windows_MinGW_x86.yml @@ -6,10 +6,12 @@ on: - master paths-ignore: - '*.md' + - 'docs/**' pull_request: types: [ opened, synchronize ] paths-ignore: - '*.md' + - 'docs/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -20,7 +22,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -34,7 +36,7 @@ jobs: - name: Configure CMake shell: bash working-directory: ${{github.workspace}} - run: cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DCPACK=ON -DCMAKE_TOOLCHAIN_FILE=../CMake/platforms/mingwcc.toolchain.cmake -DDEVILUTIONX_SYSTEM_BZIP2=OFF -DDEVILUTIONX_STATIC_LIBSODIUM=ON -DDISCORD_INTEGRATION=ON + run: cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DCPACK=ON -DCMAKE_TOOLCHAIN_FILE=../CMake/platforms/mingwcc.toolchain.cmake -DDEVILUTIONX_SYSTEM_BZIP2=OFF -DDEVILUTIONX_STATIC_LIBSODIUM=ON -DDISCORD_INTEGRATION=ON -DSCREEN_READER_INTEGRATION=ON - name: Build working-directory: ${{github.workspace}} @@ -43,7 +45,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: devilutionx_x86.zip path: build/devilutionx.zip diff --git a/.github/workflows/cache-cleanup.yml b/.github/workflows/cache-cleanup.yml index b29ba4d9194..6664b4c2f0d 100644 --- a/.github/workflows/cache-cleanup.yml +++ b/.github/workflows/cache-cleanup.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries - name: Cleanup diff --git a/.github/workflows/clang-format-check.yml b/.github/workflows/clang-format-check.yml index 60836250c8e..9ade7f79944 100644 --- a/.github/workflows/clang-format-check.yml +++ b/.github/workflows/clang-format-check.yml @@ -6,10 +6,12 @@ on: - master paths-ignore: - '*.md' + - 'docs/**' pull_request: types: [ opened, synchronize ] paths-ignore: - '*.md' + - 'docs/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -20,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/iOS.yml b/.github/workflows/iOS.yml index 0eb03874949..e0197acb249 100644 --- a/.github/workflows/iOS.yml +++ b/.github/workflows/iOS.yml @@ -6,14 +6,17 @@ on: - master paths-ignore: - '*.md' + - 'docs/**' pull_request: types: [opened, synchronize] paths-ignore: - '*.md' + - 'docs/**' release: types: [published] paths-ignore: - '*.md' + - 'docs/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -28,7 +31,7 @@ jobs: runs-on: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -36,8 +39,8 @@ jobs: uses: actions/cache@v3 with: path: build - key: ${{ github.workflow }}-v2-${{ github.sha }} - restore-keys: ${{ github.workflow }}-v2- + key: ${{ github.workflow }}-v3-${{ github.sha }} + restore-keys: ${{ github.workflow }}-v3- - name: Configure CMake # Use a bash shell so we can use the same syntax for environment variable @@ -58,7 +61,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: devilutionx-iOS.ipa path: build/devilutionx-iOS.ipa diff --git a/.github/workflows/macOS_x86_64.yml b/.github/workflows/macOS_x86_64.yml index 47184756c3b..350a5babd7d 100644 --- a/.github/workflows/macOS_x86_64.yml +++ b/.github/workflows/macOS_x86_64.yml @@ -6,14 +6,17 @@ on: - master paths-ignore: - '*.md' + - 'docs/**' pull_request: types: [opened, synchronize] paths-ignore: - '*.md' + - 'docs/**' release: types: [published] paths-ignore: - '*.md' + - 'docs/**' workflow_dispatch: concurrency: @@ -24,7 +27,7 @@ jobs: build: runs-on: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -51,7 +54,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 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 2524bf61fcd..e97fd3e6178 100644 --- a/.github/workflows/miyoo_mini_release.yml +++ b/.github/workflows/miyoo_mini_release.yml @@ -5,6 +5,7 @@ on: types: [published] paths-ignore: - '*.md' + - 'docs/**' workflow_dispatch: concurrency: @@ -16,7 +17,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -30,14 +31,14 @@ jobs: - name: Upload-OnionOS-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 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@v3 + uses: actions/upload-artifact@v4 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 d9bcf573e48..04eee0d88a8 100644 --- a/.github/workflows/opendingux_release.yml +++ b/.github/workflows/opendingux_release.yml @@ -5,6 +5,7 @@ on: types: [published] paths-ignore: - '*.md' + - 'docs/**' workflow_dispatch: concurrency: @@ -16,7 +17,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -38,7 +39,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: devilutionx-rg350.opk.zip path: build-rg350/devilutionx-rg350.opk @@ -54,7 +55,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -76,7 +77,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 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 cfc105775c9..a0d261e7e58 100644 --- a/.github/workflows/retrofw_release.yml +++ b/.github/workflows/retrofw_release.yml @@ -5,6 +5,7 @@ on: types: [published] paths-ignore: - '*.md' + - 'docs/**' workflow_dispatch: concurrency: @@ -16,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -38,7 +39,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 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 d272b67e568..d1ed0def79b 100644 --- a/.github/workflows/s390x_qemu_big_endian_tests.yml +++ b/.github/workflows/s390x_qemu_big_endian_tests.yml @@ -6,6 +6,7 @@ on: types: [published] paths-ignore: - '*.md' + - 'docs/**' workflow_dispatch: concurrency: @@ -17,7 +18,7 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -33,7 +34,7 @@ jobs: - name: Run tests run: > - docker run --rm --interactive --mount type=bind,source=$(pwd),target=/host s390x/alpine sh -c + docker run run --platform linux/s390x --rm --interactive --mount type=bind,source=$(pwd),target=/host s390x/alpine sh -c " apk add --update-cache g++ ninja cmake ccache sdl2-dev sdl2_image-dev fmt-dev libpng-dev bzip2-dev gtest-dev wget && cd /host && diff --git a/.github/workflows/src_dist_release.yml b/.github/workflows/src_dist_release.yml index c87486f6580..5ecad2343ce 100644 --- a/.github/workflows/src_dist_release.yml +++ b/.github/workflows/src_dist_release.yml @@ -5,6 +5,7 @@ on: types: [published] paths-ignore: - '*.md' + - 'docs/**' workflow_dispatch: concurrency: @@ -16,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -31,7 +32,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: devilutionx-src.tar.xz path: devilutionx-src.tar.xz @@ -47,7 +48,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -62,7 +63,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: devilutionx-src-fully-vendored.tar.xz path: devilutionx-src-fully-vendored.tar.xz diff --git a/.github/workflows/translations.yml b/.github/workflows/translations.yml index b538b57410d..d5141aa6b83 100644 --- a/.github/workflows/translations.yml +++ b/.github/workflows/translations.yml @@ -6,10 +6,12 @@ on: - master paths-ignore: - '*.md' + - 'docs/**' pull_request: types: [ opened, synchronize ] paths-ignore: - '*.md' + - 'docs/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -20,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/xbox_nxdk.yml b/.github/workflows/xbox_nxdk.yml index de6adf91177..abb286ba1b7 100644 --- a/.github/workflows/xbox_nxdk.yml +++ b/.github/workflows/xbox_nxdk.yml @@ -6,14 +6,17 @@ on: - master paths-ignore: - '*.md' + - 'docs/**' pull_request: types: [opened, synchronize] paths-ignore: - '*.md' + - 'docs/**' release: types: [published] paths-ignore: - '*.md' + - 'docs/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -35,10 +38,10 @@ jobs: - name: Build nxdk shell: bash - run: PATH="${NXDK_DIR}/bin:$PATH" make -j $(nproc) -C "$NXDK_DIR" NXDK_ONLY=1 all cxbe + run: PATH="${NXDK_DIR}/bin:$PATH" make -j $(nproc) -C "$NXDK_DIR" NXDK_ONLY=1 CFLAGS=-O2 CXXFLAGS=-O2 all cxbe - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -55,7 +58,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: devilutionx-xbox path: build-xbox/pkg/ diff --git a/.github/workflows/xbox_one.yml b/.github/workflows/xbox_one.yml index fc93d1ce0e0..4624757def1 100644 --- a/.github/workflows/xbox_one.yml +++ b/.github/workflows/xbox_one.yml @@ -6,14 +6,17 @@ on: - master paths-ignore: - '*.md' + - 'docs/**' pull_request: types: [opened, synchronize] paths-ignore: - '*.md' + - 'docs/**' release: types: [published] paths-ignore: - '*.md' + - 'docs/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -24,7 +27,7 @@ jobs: runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -57,7 +60,7 @@ jobs: - name: Upload-Package if: ${{ !env.ACT }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: devilutionx-xbox-one-series if-no-files-found: error diff --git a/3rdParty/Lua/CMakeLists.txt b/3rdParty/Lua/CMakeLists.txt new file mode 100644 index 00000000000..4d998ad303f --- /dev/null +++ b/3rdParty/Lua/CMakeLists.txt @@ -0,0 +1,24 @@ +include(functions/FetchContent_MakeAvailableExcludeFromAll) + +set(LUA_ENABLE_TESTING OFF) +set(LUA_BUILD_COMPILER OFF) +if(DEVILUTIONX_STATIC_LUA) + set(LUA_ENABLE_SHARED OFF) +else() + set(LUA_ENABLE_SHARED ON) +endif() + +include(FetchContent) +FetchContent_Declare(Lua + URL https://github.com/walterschell/Lua/archive/88246d621abf7b6fba9332f49229d507f020e450.tar.gz + URL_HASH MD5=03b76927cb5341ffc53bea12c37ddcca +) +FetchContent_MakeAvailableExcludeFromAll(Lua) + +if(ANDROID AND ("${ANDROID_ABI}" STREQUAL "armeabi-v7a" OR "${ANDROID_ABI}" STREQUAL "x86")) + target_compile_definitions(lua_internal INTERFACE -DLUA_USE_C89) +elseif(NINTENDO_3DS OR VITA OR NINTENDO_SWITCH OR NXDK) + target_compile_definitions(lua_static PUBLIC -DLUA_USE_C89) +elseif(IOS) + target_compile_definitions(lua_static PUBLIC -DLUA_USE_IOS) +endif() diff --git a/3rdParty/SDL2/CMakeLists.txt b/3rdParty/SDL2/CMakeLists.txt index 97d057a9aa5..ad64990f836 100644 --- a/3rdParty/SDL2/CMakeLists.txt +++ b/3rdParty/SDL2/CMakeLists.txt @@ -15,7 +15,7 @@ set(SDL_TEST_ENABLED_BY_DEFAULT OFF) include(functions/FetchContent_MakeAvailableExcludeFromAll) include(FetchContent) FetchContent_Declare(SDL2 - URL https://github.com/libsdl-org/SDL/archive/8b39eb9b1ff885978816dd9663277608187e8676.tar.gz - URL_HASH MD5=3cf9bf5f20375aa5dcc529193c809967 + URL https://github.com/libsdl-org/SDL/releases/download/release-2.28.5/SDL2-2.28.5.tar.gz + URL_HASH SHA256=332cb37d0be20cb9541739c61f79bae5a477427d79ae85e352089afdaf6666e4 ) FetchContent_MakeAvailableExcludeFromAll(SDL2) diff --git a/3rdParty/asio/CMakeLists.txt b/3rdParty/asio/CMakeLists.txt index 3c1e4bc81f7..1c8a06ab7fe 100644 --- a/3rdParty/asio/CMakeLists.txt +++ b/3rdParty/asio/CMakeLists.txt @@ -7,8 +7,9 @@ FetchContent_Declare(asio ) FetchContent_MakeAvailableExcludeFromAll(asio) -add_library(asio INTERFACE) -target_include_directories(asio INTERFACE ${asio_SOURCE_DIR}/asio/include) +add_library(asio STATIC ${CMAKE_CURRENT_LIST_DIR}/asio_handle_exception.cpp) +target_compile_definitions(asio PUBLIC ASIO_NO_EXCEPTIONS) +target_include_directories(asio PUBLIC ${asio_SOURCE_DIR}/asio/include ${CMAKE_CURRENT_LIST_DIR}) if(NINTENDO_3DS OR NINTENDO_SWITCH) include(asio_defs REQUIRED) diff --git a/3rdParty/asio/asio_handle_exception.cpp b/3rdParty/asio/asio_handle_exception.cpp new file mode 100644 index 00000000000..20a906c707e --- /dev/null +++ b/3rdParty/asio/asio_handle_exception.cpp @@ -0,0 +1,18 @@ +#include + +#define ErrAsio(message) devilution::ErrDlg("ASIO Error", message, __FILE__, __LINE__) + +namespace devilution { + +extern void ErrDlg(const char* title, std::string_view error, std::string_view logFilePath, int logLineNr); + +} // namespace devilution + +namespace asio::detail { + +void fatal_exception(const char* message) +{ + ErrAsio(message); +} + +} // namespace asio::detail diff --git a/3rdParty/asio/asio_handle_exception.hpp b/3rdParty/asio/asio_handle_exception.hpp new file mode 100644 index 00000000000..6742717c63e --- /dev/null +++ b/3rdParty/asio/asio_handle_exception.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace asio::detail { + +void fatal_exception(const char *message); + +template +void throw_exception( + const Exception &e + ASIO_SOURCE_LOCATION_PARAM) +{ + fatal_exception(e.what()); +} + +} // namespace asio::detail diff --git a/3rdParty/discord/CMakeLists.txt b/3rdParty/discord/CMakeLists.txt index f70e117c2a9..01401881337 100644 --- a/3rdParty/discord/CMakeLists.txt +++ b/3rdParty/discord/CMakeLists.txt @@ -3,11 +3,21 @@ include(FetchContent) find_package(Patch REQUIRED) -FetchContent_Declare(discordsrc - URL https://dl-game-sdk.discordapp.net/3.2.1/discord_game_sdk.zip - URL_HASH MD5=73e5e1b3f8413a2c7184ef17476822f2 - PATCH_COMMAND "${Patch_EXECUTABLE}" -p1 -N < "${CMAKE_CURRENT_LIST_DIR}/fixes.patch" || true -) +set(Discord_SDK_URL "https://dl-game-sdk.discordapp.net/3.2.1/discord_game_sdk.zip") +set(Discord_SDK_HASH "73e5e1b3f8413a2c7184ef17476822f2") + +if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + FetchContent_Declare(discordsrc + URL ${Discord_SDK_URL} + URL_HASH MD5=${Discord_SDK_HASH} + ) +else() + FetchContent_Declare(discordsrc + URL ${Discord_SDK_URL} + URL_HASH MD5=${Discord_SDK_HASH} + PATCH_COMMAND "${Patch_EXECUTABLE}" -p1 -N < "${CMAKE_CURRENT_LIST_DIR}/fixes.patch" || true + ) +endif() FetchContent_MakeAvailableExcludeFromAll(discordsrc) file(GLOB discord_SRCS ${discordsrc_SOURCE_DIR}/cpp/*.cpp) diff --git a/3rdParty/googletest/CMakeLists.txt b/3rdParty/googletest/CMakeLists.txt index a6efb922944..be170aaf00d 100644 --- a/3rdParty/googletest/CMakeLists.txt +++ b/3rdParty/googletest/CMakeLists.txt @@ -1,10 +1,9 @@ include(functions/FetchContent_MakeAvailableExcludeFromAll) -# branch: v1.12.x (master no longer supports C++11) FetchContent_Declare( googletest - URL https://github.com/google/googletest/archive/58d77fa8070e8cec2dc1ed015d66b454c8d78850.tar.gz - URL_HASH MD5=22bdde3b84a8c506e7aba7b3c97dc760 + URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz + URL_HASH MD5=c8340a482851ef6a3fe618a082304cfc ) set(INSTALL_GTEST OFF) diff --git a/3rdParty/libfmt/CMakeLists.txt b/3rdParty/libfmt/CMakeLists.txt index df2295fb7c4..69b3b1cbb41 100644 --- a/3rdParty/libfmt/CMakeLists.txt +++ b/3rdParty/libfmt/CMakeLists.txt @@ -5,22 +5,22 @@ if(NOT WIN32 AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL FreeBSD) add_definitions(-D_POSIX_C_SOURCE=200809L) endif() +# Disable fmt/os.h functionality. +# We do not use it and it is not supported on some systems. +set(FMT_OS OFF) + if(DEVILUTIONX_STATIC_LIBFMT) set(BUILD_SHARED_LIBS OFF) else() set(BUILD_SHARED_LIBS ON) endif() include(FetchContent) -if(NXDK) - # branch: nxdk-v10.0.0 - FetchContent_Declare(libfmt - URL https://github.com/diasurgical/fmt/archive/e421e854efcb21ee641349bab604d91b34af26b8.tar.gz - URL_HASH MD5=ebaac86fca56d4ef4a162ef125433f6f - ) -else() - FetchContent_Declare(libfmt - URL https://github.com/fmtlib/fmt/archive/refs/tags/10.0.0.tar.gz - URL_HASH MD5=fa629bc1178918b7af4b2ea6b6a271dc - ) -endif() +FetchContent_Declare(libfmt + URL https://github.com/fmtlib/fmt/archive/44f3d8a77cd7e05e9da92d68635abdb4da626e9e.tar.gz + URL_HASH MD5=b6eb4573962586cfbfb6e5e2986d292b +) FetchContent_MakeAvailableExcludeFromAll(libfmt) + +if(DEVILUTIONX_WINDOWS_NO_WCHAR) + target_compile_definitions(fmt PUBLIC FMT_WINDOWS_NO_WCHAR) +endif() diff --git a/3rdParty/libmpq/CMakeLists.txt b/3rdParty/libmpq/CMakeLists.txt index 7df576b73dc..ca263ed63b3 100644 --- a/3rdParty/libmpq/CMakeLists.txt +++ b/3rdParty/libmpq/CMakeLists.txt @@ -10,8 +10,8 @@ include(functions/FetchContent_MakeAvailableExcludeFromAll) include(FetchContent) FetchContent_Declare(libmpq - URL https://github.com/diasurgical/libmpq/archive/b78d66c6fee6a501cc9b95d8556a129c68841b05.tar.gz - URL_HASH MD5=da531a1a6f719e89798a26e679ffc329 + URL https://github.com/diasurgical/libmpq/archive/7c2924d4553513eba1a70bbdb558198dd8c2726a.tar.gz + URL_HASH MD5=315c88c02b45851cdfee8460322de044 ) FetchContent_MakeAvailableExcludeFromAll(libmpq) @@ -32,3 +32,7 @@ target_link_libraries(libmpq PRIVATE ZLIB::ZLIB BZip2::BZip2) if(LIBMPQ_FILE_BUFFER_SIZE) target_compile_definitions(libmpq PRIVATE "LIBMPQ_FILE_BUFFER_SIZE=${LIBMPQ_FILE_BUFFER_SIZE}") endif() + +if(DEVILUTIONX_WINDOWS_NO_WCHAR) + target_compile_definitions(libmpq PRIVATE LIBMPQ_WINDOWS_NO_WCHAR) +endif() diff --git a/3rdParty/libzt/CMakeLists.txt b/3rdParty/libzt/CMakeLists.txt index e3feb8cd3cb..4f968fa7c2a 100644 --- a/3rdParty/libzt/CMakeLists.txt +++ b/3rdParty/libzt/CMakeLists.txt @@ -5,7 +5,7 @@ set(BUILD_HOST_SELFTEST OFF) include(FetchContent) FetchContent_Declare(libzt GIT_REPOSITORY https://github.com/diasurgical/libzt.git - GIT_TAG d6c6a069a5041a3e89594c447ced3f15d77618b8) + GIT_TAG db7b642a4ce9f0f5e0ba7f293bd7ffa7897e4831) FetchContent_MakeAvailableExcludeFromAll(libzt) if(NOT ANDROID) diff --git a/3rdParty/sol2/CMakeLists.txt b/3rdParty/sol2/CMakeLists.txt new file mode 100644 index 00000000000..ce74ecd5e03 --- /dev/null +++ b/3rdParty/sol2/CMakeLists.txt @@ -0,0 +1,13 @@ +include(functions/FetchContent_MakeAvailableExcludeFromAll) + +set(SOL2_ENABLE_INSTALL OFF) + +include(FetchContent) +FetchContent_Declare(sol2 + URL https://github.com/ThePhD/sol2/archive/9c882a28fdb6f4ad79a53a4191b43ce48a661175.tar.gz + URL_HASH MD5=2637c3fcdcce3ff34b36437c1d3b99d1 +) +FetchContent_MakeAvailableExcludeFromAll(sol2) + +target_include_directories(sol2 SYSTEM BEFORE INTERFACE ${CMAKE_CURRENT_LIST_DIR}/sol_config) +target_compile_definitions(sol2 INTERFACE SOL_NO_EXCEPTIONS=1) diff --git a/3rdParty/sol2/sol_config/sol/config.hpp b/3rdParty/sol2/sol_config/sol/config.hpp new file mode 100644 index 00000000000..07d2a2cfa10 --- /dev/null +++ b/3rdParty/sol2/sol_config/sol/config.hpp @@ -0,0 +1,8 @@ +#pragma once + +#define SOL_SAFE_USERTYPE 1 +#define SOL_SAFE_REFERENCES 1 +#define SOL_SAFE_FUNCTION_CALLS 1 +#define SOL_SAFE_FUNCTION 1 +#define SOL_SAFE_NUMERICS 1 +#define SOL_IN_DEBUG_DETECTED 0 diff --git a/3rdParty/sol2/sol_config/sol/debug.hpp b/3rdParty/sol2/sol_config/sol/debug.hpp new file mode 100644 index 00000000000..14b46a89d98 --- /dev/null +++ b/3rdParty/sol2/sol_config/sol/debug.hpp @@ -0,0 +1,36 @@ +#pragma once + +// sol2 uses std::cout for debug logging by default. +// We want to use SDL logging instead for better compatibility. + +#include +#include + +#include + +namespace devilutionx { +void Sol2DebugPrintStack(lua_State *L); +void Sol2DebugPrintSection(const std::string &message, lua_State *L); +} // namespace devilutionx + +namespace sol::detail::debug { + +inline std::string dump_types(lua_State *L) { + std::string visual; + std::size_t size = lua_gettop(L) + 1; + for (std::size_t i = 1; i < size; ++i) { + if (i != 1) { + visual += " | "; + } + visual += type_name(L, stack::get(L, static_cast(i))); + } + return visual; +} + +inline void print_stack(lua_State *L) { ::devilutionx::Sol2DebugPrintStack(L); } + +inline void print_section(const std::string &message, lua_State *L) { + ::devilutionx::Sol2DebugPrintSection(message, L); +} + +} // namespace sol::detail::debug diff --git a/3rdParty/tl/expected.hpp b/3rdParty/tl/expected.hpp new file mode 100644 index 00000000000..209a44ecf79 --- /dev/null +++ b/3rdParty/tl/expected.hpp @@ -0,0 +1,2444 @@ +/// +// expected - An implementation of std::expected with extensions +// Written in 2017 by Sy Brand (tartanllama@gmail.com, @TartanLlama) +// +// Documentation available at http://tl.tartanllama.xyz/ +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to the +// public domain worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. If not, see +// . +/// + +#ifndef TL_EXPECTED_HPP +#define TL_EXPECTED_HPP + +#define TL_EXPECTED_VERSION_MAJOR 1 +#define TL_EXPECTED_VERSION_MINOR 1 +#define TL_EXPECTED_VERSION_PATCH 0 + +#include +#include +#include +#include + +#if defined(__EXCEPTIONS) || defined(_CPPUNWIND) +#define TL_EXPECTED_EXCEPTIONS_ENABLED +#endif + +#if (defined(_MSC_VER) && _MSC_VER == 1900) +#define TL_EXPECTED_MSVC2015 +#define TL_EXPECTED_MSVC2015_CONSTEXPR +#else +#define TL_EXPECTED_MSVC2015_CONSTEXPR constexpr +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +#define TL_EXPECTED_GCC49 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \ + !defined(__clang__)) +#define TL_EXPECTED_GCC54 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \ + !defined(__clang__)) +#define TL_EXPECTED_GCC55 +#endif + +#if !defined(TL_ASSERT) +// can't have assert in constexpr in C++11 and GCC 4.9 has a compiler bug +#if (__cplusplus > 201103L) && !defined(TL_EXPECTED_GCC49) +#include +#define TL_ASSERT(x) assert(x) +#else +#define TL_ASSERT(x) +#endif +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +// GCC < 5 doesn't support overloading on const&& for member functions + +#define TL_EXPECTED_NO_CONSTRR +// GCC < 5 doesn't support some standard C++11 type traits +#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::has_trivial_copy_constructor +#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::has_trivial_copy_assign + +// This one will be different for GCC 5.7 if it's ever supported +#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible + +// GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks +// std::vector for non-copyable types +#elif (defined(__GNUC__) && __GNUC__ < 8 && !defined(__clang__)) +#ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +#define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +namespace tl { +namespace detail { +template +struct is_trivially_copy_constructible + : std::is_trivially_copy_constructible {}; +#ifdef _GLIBCXX_VECTOR +template +struct is_trivially_copy_constructible> : std::false_type {}; +#endif +} // namespace detail +} // namespace tl +#endif + +#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + tl::detail::is_trivially_copy_constructible +#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable +#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible +#else +#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::is_trivially_copy_constructible +#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable +#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible +#endif + +#if __cplusplus > 201103L +#define TL_EXPECTED_CXX14 +#endif + +#ifdef TL_EXPECTED_GCC49 +#define TL_EXPECTED_GCC49_CONSTEXPR +#else +#define TL_EXPECTED_GCC49_CONSTEXPR constexpr +#endif + +#if (__cplusplus == 201103L || defined(TL_EXPECTED_MSVC2015) || \ + defined(TL_EXPECTED_GCC49)) +#define TL_EXPECTED_11_CONSTEXPR +#else +#define TL_EXPECTED_11_CONSTEXPR constexpr +#endif + +namespace tl { +template class expected; + +#ifndef TL_MONOSTATE_INPLACE_MUTEX +#define TL_MONOSTATE_INPLACE_MUTEX +class monostate {}; + +struct in_place_t { + explicit in_place_t() = default; +}; +static constexpr in_place_t in_place{}; +#endif + +template class unexpected { +public: + static_assert(!std::is_same::value, "E must not be void"); + + unexpected() = delete; + constexpr explicit unexpected(const E &e) : m_val(e) {} + + constexpr explicit unexpected(E &&e) : m_val(std::move(e)) {} + + template ::value>::type * = nullptr> + constexpr explicit unexpected(Args &&...args) + : m_val(std::forward(args)...) {} + template < + class U, class... Args, + typename std::enable_if &, Args &&...>::value>::type * = nullptr> + constexpr explicit unexpected(std::initializer_list l, Args &&...args) + : m_val(l, std::forward(args)...) {} + + constexpr const E &value() const & { return m_val; } + TL_EXPECTED_11_CONSTEXPR E &value() & { return m_val; } + TL_EXPECTED_11_CONSTEXPR E &&value() && { return std::move(m_val); } + constexpr const E &&value() const && { return std::move(m_val); } + +private: + E m_val; +}; + +#ifdef __cpp_deduction_guides +template unexpected(E) -> unexpected; +#endif + +template +constexpr bool operator==(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() == rhs.value(); +} +template +constexpr bool operator!=(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() != rhs.value(); +} +template +constexpr bool operator<(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() < rhs.value(); +} +template +constexpr bool operator<=(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() <= rhs.value(); +} +template +constexpr bool operator>(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() > rhs.value(); +} +template +constexpr bool operator>=(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() >= rhs.value(); +} + +template +unexpected::type> make_unexpected(E &&e) { + return unexpected::type>(std::forward(e)); +} + +struct unexpect_t { + unexpect_t() = default; +}; +static constexpr unexpect_t unexpect{}; + +namespace detail { +template +[[noreturn]] TL_EXPECTED_11_CONSTEXPR void throw_exception(E &&e) { +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + throw std::forward(e); +#else + (void)e; +#ifdef _MSC_VER + __assume(0); +#else + __builtin_unreachable(); +#endif +#endif +} + +#ifndef TL_TRAITS_MUTEX +#define TL_TRAITS_MUTEX +// C++14-style aliases for brevity +template using remove_const_t = typename std::remove_const::type; +template +using remove_reference_t = typename std::remove_reference::type; +template using decay_t = typename std::decay::type; +template +using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; + +// std::conjunction from C++17 +template struct conjunction : std::true_type {}; +template struct conjunction : B {}; +template +struct conjunction + : std::conditional, B>::type {}; + +#if defined(_LIBCPP_VERSION) && __cplusplus == 201103L +#define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +#endif + +// In C++11 mode, there's an issue in libc++'s std::mem_fn +// which results in a hard-error when using it in a noexcept expression +// in some cases. This is a check to workaround the common failing case. +#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +template +struct is_pointer_to_non_const_member_func : std::false_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; + +template struct is_const_or_const_ref : std::false_type {}; +template struct is_const_or_const_ref : std::true_type {}; +template struct is_const_or_const_ref : std::true_type {}; +#endif + +// std::invoke from C++17 +// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround +template < + typename Fn, typename... Args, +#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND + typename = enable_if_t::value && + is_const_or_const_ref::value)>, +#endif + typename = enable_if_t>::value>, int = 0> +constexpr auto invoke(Fn &&f, Args &&...args) noexcept( + noexcept(std::mem_fn(f)(std::forward(args)...))) + -> decltype(std::mem_fn(f)(std::forward(args)...)) { + return std::mem_fn(f)(std::forward(args)...); +} + +template >::value>> +constexpr auto invoke(Fn &&f, Args &&...args) noexcept( + noexcept(std::forward(f)(std::forward(args)...))) + -> decltype(std::forward(f)(std::forward(args)...)) { + return std::forward(f)(std::forward(args)...); +} + +// std::invoke_result from C++17 +template struct invoke_result_impl; + +template +struct invoke_result_impl< + F, + decltype(detail::invoke(std::declval(), std::declval()...), void()), + Us...> { + using type = + decltype(detail::invoke(std::declval(), std::declval()...)); +}; + +template +using invoke_result = invoke_result_impl; + +template +using invoke_result_t = typename invoke_result::type; + +#if defined(_MSC_VER) && _MSC_VER <= 1900 +// TODO make a version which works with MSVC 2015 +template struct is_swappable : std::true_type {}; + +template struct is_nothrow_swappable : std::true_type {}; +#else +// https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept +namespace swap_adl_tests { +// if swap ADL finds this then it would call std::swap otherwise (same +// signature) +struct tag {}; + +template tag swap(T &, T &); +template tag swap(T (&a)[N], T (&b)[N]); + +// helper functions to test if an unqualified swap is possible, and if it +// becomes std::swap +template std::false_type can_swap(...) noexcept(false); +template (), std::declval()))> +std::true_type can_swap(int) noexcept(noexcept(swap(std::declval(), + std::declval()))); + +template std::false_type uses_std(...); +template +std::is_same(), std::declval())), tag> +uses_std(int); + +template +struct is_std_swap_noexcept + : std::integral_constant::value && + std::is_nothrow_move_assignable::value> {}; + +template +struct is_std_swap_noexcept : is_std_swap_noexcept {}; + +template +struct is_adl_swap_noexcept + : std::integral_constant(0))> {}; +} // namespace swap_adl_tests + +template +struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype(detail::swap_adl_tests::uses_std(0))::value || + (std::is_move_assignable::value && + std::is_move_constructible::value))> {}; + +template +struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype(detail::swap_adl_tests::uses_std( + 0))::value || + is_swappable::value)> {}; + +template +struct is_nothrow_swappable + : std::integral_constant< + bool, + is_swappable::value && + ((decltype(detail::swap_adl_tests::uses_std(0))::value && + detail::swap_adl_tests::is_std_swap_noexcept::value) || + (!decltype(detail::swap_adl_tests::uses_std(0))::value && + detail::swap_adl_tests::is_adl_swap_noexcept::value))> {}; +#endif +#endif + +// Trait for checking if a type is a tl::expected +template struct is_expected_impl : std::false_type {}; +template +struct is_expected_impl> : std::true_type {}; +template using is_expected = is_expected_impl>; + +template +using expected_enable_forward_value = detail::enable_if_t< + std::is_constructible::value && + !std::is_same, in_place_t>::value && + !std::is_same, detail::decay_t>::value && + !std::is_same, detail::decay_t>::value>; + +template +using expected_enable_from_other = detail::enable_if_t< + std::is_constructible::value && + std::is_constructible::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value>; + +template +using is_void_or = conditional_t::value, std::true_type, U>; + +template +using is_copy_constructible_or_void = + is_void_or>; + +template +using is_move_constructible_or_void = + is_void_or>; + +template +using is_copy_assignable_or_void = is_void_or>; + +template +using is_move_assignable_or_void = is_void_or>; + +} // namespace detail + +namespace detail { +struct no_init_t {}; +static constexpr no_init_t no_init{}; + +// Implements the storage of the values, and ensures that the destructor is +// trivial if it can be. +// +// This specialization is for where neither `T` or `E` is trivially +// destructible, so the destructors must be called on destruction of the +// `expected` +template ::value, + bool = std::is_trivially_destructible::value> +struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&...args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&...args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (m_has_val) { + m_val.~T(); + } else { + m_unexpect.~unexpected(); + } + } + union { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// This specialization is for when both `T` and `E` are trivially-destructible, +// so the destructor of the `expected` can be trivial. +template struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&...args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&...args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() = default; + union { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// T is trivial, E is not. +template struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base(no_init_t) + : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&...args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&...args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (!m_has_val) { + m_unexpect.~unexpected(); + } + } + + union { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// E is trivial, T is not. +template struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&...args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&...args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (m_has_val) { + m_val.~T(); + } + } + union { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// `T` is `void`, `E` is trivially-destructible +template struct expected_storage_base { +#if __GNUC__ <= 5 +// no constexpr for GCC 4/5 bug +#else + TL_EXPECTED_MSVC2015_CONSTEXPR +#endif + expected_storage_base() : m_has_val(true) {} + + constexpr expected_storage_base(no_init_t) : m_val(), m_has_val(false) {} + + constexpr expected_storage_base(in_place_t) : m_has_val(true) {} + + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() = default; + struct dummy {}; + union { + unexpected m_unexpect; + dummy m_val; + }; + bool m_has_val; +}; + +// `T` is `void`, `E` is not trivially-destructible +template struct expected_storage_base { + constexpr expected_storage_base() : m_dummy(), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_dummy(), m_has_val(false) {} + + constexpr expected_storage_base(in_place_t) : m_dummy(), m_has_val(true) {} + + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (!m_has_val) { + m_unexpect.~unexpected(); + } + } + + union { + unexpected m_unexpect; + char m_dummy; + }; + bool m_has_val; +}; + +// This base class provides some handy member functions which can be used in +// further derived classes +template +struct expected_operations_base : expected_storage_base { + using expected_storage_base::expected_storage_base; + + template void construct(Args &&...args) noexcept { + new (std::addressof(this->m_val)) T(std::forward(args)...); + this->m_has_val = true; + } + + template void construct_with(Rhs &&rhs) noexcept { + new (std::addressof(this->m_val)) T(std::forward(rhs).get()); + this->m_has_val = true; + } + + template void construct_error(Args &&...args) noexcept { + new (std::addressof(this->m_unexpect)) + unexpected(std::forward(args)...); + this->m_has_val = false; + } + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + + // These assign overloads ensure that the most efficient assignment + // implementation is used while maintaining the strong exception guarantee. + // The problematic case is where rhs has a value, but *this does not. + // + // This overload handles the case where we can just copy-construct `T` + // directly into place without throwing. + template ::value> + * = nullptr> + void assign(const expected_operations_base &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(rhs.get()); + } else { + assign_common(rhs); + } + } + + // This overload handles the case where we can attempt to create a copy of + // `T`, then no-throw move it into place if the copy was successful. + template ::value && + std::is_nothrow_move_constructible::value> + * = nullptr> + void assign(const expected_operations_base &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + T tmp = rhs.get(); + geterr().~unexpected(); + construct(std::move(tmp)); + } else { + assign_common(rhs); + } + } + + // This overload is the worst-case, where we have to move-construct the + // unexpected value into temporary storage, then try to copy the T into place. + // If the construction succeeds, then everything is fine, but if it throws, + // then we move the old unexpected value back into place before rethrowing the + // exception. + template ::value && + !std::is_nothrow_move_constructible::value> + * = nullptr> + void assign(const expected_operations_base &rhs) { + if (!this->m_has_val && rhs.m_has_val) { + auto tmp = std::move(geterr()); + geterr().~unexpected(); + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + construct(rhs.get()); + } catch (...) { + geterr() = std::move(tmp); + throw; + } +#else + construct(rhs.get()); +#endif + } else { + assign_common(rhs); + } + } + + // These overloads do the same as above, but for rvalues + template ::value> + * = nullptr> + void assign(expected_operations_base &&rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(std::move(rhs).get()); + } else { + assign_common(std::move(rhs)); + } + } + + template ::value> + * = nullptr> + void assign(expected_operations_base &&rhs) { + if (!this->m_has_val && rhs.m_has_val) { + auto tmp = std::move(geterr()); + geterr().~unexpected(); +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + construct(std::move(rhs).get()); + } catch (...) { + geterr() = std::move(tmp); + throw; + } +#else + construct(std::move(rhs).get()); +#endif + } else { + assign_common(std::move(rhs)); + } + } + +#else + + // If exceptions are disabled then we can just copy-construct + void assign(const expected_operations_base &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(rhs.get()); + } else { + assign_common(rhs); + } + } + + void assign(expected_operations_base &&rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(std::move(rhs).get()); + } else { + assign_common(std::move(rhs)); + } + } + +#endif + + // The common part of move/copy assigning + template void assign_common(Rhs &&rhs) { + if (this->m_has_val) { + if (rhs.m_has_val) { + get() = std::forward(rhs).get(); + } else { + destroy_val(); + construct_error(std::forward(rhs).geterr()); + } + } else { + if (!rhs.m_has_val) { + geterr() = std::forward(rhs).geterr(); + } + } + } + + bool has_value() const { return this->m_has_val; } + + TL_EXPECTED_11_CONSTEXPR T &get() & { return this->m_val; } + constexpr const T &get() const & { return this->m_val; } + TL_EXPECTED_11_CONSTEXPR T &&get() && { return std::move(this->m_val); } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const T &&get() const && { return std::move(this->m_val); } +#endif + + TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { + return this->m_unexpect; + } + constexpr const unexpected &geterr() const & { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR unexpected &&geterr() && { + return std::move(this->m_unexpect); + } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const unexpected &&geterr() const && { + return std::move(this->m_unexpect); + } +#endif + + TL_EXPECTED_11_CONSTEXPR void destroy_val() { get().~T(); } +}; + +// This base class provides some handy member functions which can be used in +// further derived classes +template +struct expected_operations_base : expected_storage_base { + using expected_storage_base::expected_storage_base; + + template void construct() noexcept { this->m_has_val = true; } + + // This function doesn't use its argument, but needs it so that code in + // levels above this can work independently of whether T is void + template void construct_with(Rhs &&) noexcept { + this->m_has_val = true; + } + + template void construct_error(Args &&...args) noexcept { + new (std::addressof(this->m_unexpect)) + unexpected(std::forward(args)...); + this->m_has_val = false; + } + + template void assign(Rhs &&rhs) noexcept { + if (!this->m_has_val) { + if (rhs.m_has_val) { + geterr().~unexpected(); + construct(); + } else { + geterr() = std::forward(rhs).geterr(); + } + } else { + if (!rhs.m_has_val) { + construct_error(std::forward(rhs).geterr()); + } + } + } + + bool has_value() const { return this->m_has_val; } + + TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { + return this->m_unexpect; + } + constexpr const unexpected &geterr() const & { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR unexpected &&geterr() && { + return std::move(this->m_unexpect); + } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const unexpected &&geterr() const && { + return std::move(this->m_unexpect); + } +#endif + + TL_EXPECTED_11_CONSTEXPR void destroy_val() { + // no-op + } +}; + +// This class manages conditionally having a trivial copy constructor +// This specialization is for when T and E are trivially copy constructible +template :: + value &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value> +struct expected_copy_base : expected_operations_base { + using expected_operations_base::expected_operations_base; +}; + +// This specialization is for when T or E are not trivially copy constructible +template +struct expected_copy_base : expected_operations_base { + using expected_operations_base::expected_operations_base; + + expected_copy_base() = default; + expected_copy_base(const expected_copy_base &rhs) + : expected_operations_base(no_init) { + if (rhs.has_value()) { + this->construct_with(rhs); + } else { + this->construct_error(rhs.geterr()); + } + } + + expected_copy_base(expected_copy_base &&rhs) = default; + expected_copy_base &operator=(const expected_copy_base &rhs) = default; + expected_copy_base &operator=(expected_copy_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial move constructor +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_constructible. We +// have to make do with a non-trivial move constructor even if T is trivially +// move constructible +#ifndef TL_EXPECTED_GCC49 +template >::value + &&std::is_trivially_move_constructible::value> +struct expected_move_base : expected_copy_base { + using expected_copy_base::expected_copy_base; +}; +#else +template struct expected_move_base; +#endif +template +struct expected_move_base : expected_copy_base { + using expected_copy_base::expected_copy_base; + + expected_move_base() = default; + expected_move_base(const expected_move_base &rhs) = default; + + expected_move_base(expected_move_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value) + : expected_copy_base(no_init) { + if (rhs.has_value()) { + this->construct_with(std::move(rhs)); + } else { + this->construct_error(std::move(rhs.geterr())); + } + } + expected_move_base &operator=(const expected_move_base &rhs) = default; + expected_move_base &operator=(expected_move_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial copy assignment operator +template >::value + &&TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value + &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value + &&TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value> +struct expected_copy_assign_base : expected_move_base { + using expected_move_base::expected_move_base; +}; + +template +struct expected_copy_assign_base : expected_move_base { + using expected_move_base::expected_move_base; + + expected_copy_assign_base() = default; + expected_copy_assign_base(const expected_copy_assign_base &rhs) = default; + + expected_copy_assign_base(expected_copy_assign_base &&rhs) = default; + expected_copy_assign_base &operator=(const expected_copy_assign_base &rhs) { + this->assign(rhs); + return *this; + } + expected_copy_assign_base & + operator=(expected_copy_assign_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial move assignment operator +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_assignable. We have +// to make do with a non-trivial move assignment operator even if T is trivially +// move assignable +#ifndef TL_EXPECTED_GCC49 +template , + std::is_trivially_move_constructible, + std::is_trivially_move_assignable>>:: + value &&std::is_trivially_destructible::value + &&std::is_trivially_move_constructible::value + &&std::is_trivially_move_assignable::value> +struct expected_move_assign_base : expected_copy_assign_base { + using expected_copy_assign_base::expected_copy_assign_base; +}; +#else +template struct expected_move_assign_base; +#endif + +template +struct expected_move_assign_base + : expected_copy_assign_base { + using expected_copy_assign_base::expected_copy_assign_base; + + expected_move_assign_base() = default; + expected_move_assign_base(const expected_move_assign_base &rhs) = default; + + expected_move_assign_base(expected_move_assign_base &&rhs) = default; + + expected_move_assign_base & + operator=(const expected_move_assign_base &rhs) = default; + + expected_move_assign_base & + operator=(expected_move_assign_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value + &&std::is_nothrow_move_assignable::value) { + this->assign(std::move(rhs)); + return *this; + } +}; + +// expected_delete_ctor_base will conditionally delete copy and move +// constructors depending on whether T is copy/move constructible +template ::value && + std::is_copy_constructible::value), + bool EnableMove = (is_move_constructible_or_void::value && + std::is_move_constructible::value)> +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +// expected_delete_assign_base will conditionally delete copy and move +// constructors depending on whether T and E are copy/move constructible + +// assignable +template ::value && + std::is_copy_constructible::value && + is_copy_assignable_or_void::value && + std::is_copy_assignable::value), + bool EnableMove = (is_move_constructible_or_void::value && + std::is_move_constructible::value && + is_move_assignable_or_void::value && + std::is_move_assignable::value)> +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = default; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = default; +}; + +template +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = default; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = delete; +}; + +template +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = delete; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = default; +}; + +template +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = delete; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = delete; +}; + +// This is needed to be able to construct the expected_default_ctor_base which +// follows, while still conditionally deleting the default constructor. +struct default_constructor_tag { + explicit constexpr default_constructor_tag() = default; +}; + +// expected_default_ctor_base will ensure that expected has a deleted default +// consturctor if T is not default constructible. +// This specialization is for when T is default constructible +template ::value || std::is_void::value> +struct expected_default_ctor_base { + constexpr expected_default_ctor_base() noexcept = default; + constexpr expected_default_ctor_base( + expected_default_ctor_base const &) noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = + default; + expected_default_ctor_base & + operator=(expected_default_ctor_base const &) noexcept = default; + expected_default_ctor_base & + operator=(expected_default_ctor_base &&) noexcept = default; + + constexpr explicit expected_default_ctor_base(default_constructor_tag) {} +}; + +// This specialization is for when T is not default constructible +template struct expected_default_ctor_base { + constexpr expected_default_ctor_base() noexcept = delete; + constexpr expected_default_ctor_base( + expected_default_ctor_base const &) noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = + default; + expected_default_ctor_base & + operator=(expected_default_ctor_base const &) noexcept = default; + expected_default_ctor_base & + operator=(expected_default_ctor_base &&) noexcept = default; + + constexpr explicit expected_default_ctor_base(default_constructor_tag) {} +}; +} // namespace detail + +template class bad_expected_access : public std::exception { +public: + explicit bad_expected_access(E e) : m_val(std::move(e)) {} + + virtual const char *what() const noexcept override { + return "Bad expected access"; + } + + const E &error() const & { return m_val; } + E &error() & { return m_val; } + const E &&error() const && { return std::move(m_val); } + E &&error() && { return std::move(m_val); } + +private: + E m_val; +}; + +/// An `expected` object is an object that contains the storage for +/// another object and manages the lifetime of this contained object `T`. +/// Alternatively it could contain the storage for another unexpected object +/// `E`. The contained object may not be initialized after the expected object +/// has been initialized, and may not be destroyed before the expected object +/// has been destroyed. The initialization state of the contained object is +/// tracked by the expected object. +template +class expected : private detail::expected_move_assign_base, + private detail::expected_delete_ctor_base, + private detail::expected_delete_assign_base, + private detail::expected_default_ctor_base { + static_assert(!std::is_reference::value, "T must not be a reference"); + static_assert(!std::is_same::type>::value, + "T must not be in_place_t"); + static_assert(!std::is_same::type>::value, + "T must not be unexpect_t"); + static_assert( + !std::is_same>::type>::value, + "T must not be unexpected"); + static_assert(!std::is_reference::value, "E must not be a reference"); + + T *valptr() { return std::addressof(this->m_val); } + const T *valptr() const { return std::addressof(this->m_val); } + unexpected *errptr() { return std::addressof(this->m_unexpect); } + const unexpected *errptr() const { + return std::addressof(this->m_unexpect); + } + + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &val() { + return this->m_val; + } + TL_EXPECTED_11_CONSTEXPR unexpected &err() { return this->m_unexpect; } + + template ::value> * = nullptr> + constexpr const U &val() const { + return this->m_val; + } + constexpr const unexpected &err() const { return this->m_unexpect; } + + using impl_base = detail::expected_move_assign_base; + using ctor_base = detail::expected_default_ctor_base; + +public: + typedef T value_type; + typedef E error_type; + typedef unexpected unexpected_type; + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & { + return and_then_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && { + return and_then_impl(std::move(*this), std::forward(f)); + } + template constexpr auto and_then(F &&f) const & { + return and_then_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template constexpr auto and_then(F &&f) const && { + return and_then_impl(std::move(*this), std::forward(f)); + } +#endif + +#else + template + TL_EXPECTED_11_CONSTEXPR auto + and_then(F &&f) & -> decltype(and_then_impl(std::declval(), + std::forward(f))) { + return and_then_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR auto + and_then(F &&f) && -> decltype(and_then_impl(std::declval(), + std::forward(f))) { + return and_then_impl(std::move(*this), std::forward(f)); + } + template + constexpr auto and_then(F &&f) const & -> decltype(and_then_impl( + std::declval(), std::forward(f))) { + return and_then_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr auto and_then(F &&f) const && -> decltype(and_then_impl( + std::declval(), std::forward(f))) { + return and_then_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template constexpr auto map(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + template constexpr auto map(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( + std::declval(), std::declval())) + map(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), + std::declval())) + map(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + map(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + map(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template constexpr auto transform(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + template constexpr auto transform(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( + std::declval(), std::declval())) + transform(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), + std::declval())) + transform(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + transform(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + transform(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & { + return map_error_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward(f)); + } + template constexpr auto map_error(F &&f) const & { + return map_error_impl(*this, std::forward(f)); + } + template constexpr auto map_error(F &&f) const && { + return map_error_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) & { + return map_error_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) const & { + return map_error_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) const && { + return map_error_impl(std::move(*this), std::forward(f)); + } +#endif +#endif +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) & { + return map_error_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward(f)); + } + template constexpr auto transform_error(F &&f) const & { + return map_error_impl(*this, std::forward(f)); + } + template constexpr auto transform_error(F &&f) const && { + return map_error_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + transform_error(F &&f) & { + return map_error_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + transform_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + transform_error(F &&f) const & { + return map_error_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + transform_error(F &&f) const && { + return map_error_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + template expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) & { + return or_else_impl(*this, std::forward(f)); + } + + template expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) && { + return or_else_impl(std::move(*this), std::forward(f)); + } + + template expected constexpr or_else(F &&f) const & { + return or_else_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template expected constexpr or_else(F &&f) const && { + return or_else_impl(std::move(*this), std::forward(f)); + } +#endif + constexpr expected() = default; + constexpr expected(const expected &rhs) = default; + constexpr expected(expected &&rhs) = default; + expected &operator=(const expected &rhs) = default; + expected &operator=(expected &&rhs) = default; + + template ::value> * = + nullptr> + constexpr expected(in_place_t, Args &&...args) + : impl_base(in_place, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected(in_place_t, std::initializer_list il, Args &&...args) + : impl_base(in_place, il, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template ::value> * = + nullptr, + detail::enable_if_t::value> * = + nullptr> + explicit constexpr expected(const unexpected &e) + : impl_base(unexpect, e.value()), + ctor_base(detail::default_constructor_tag{}) {} + + template < + class G = E, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr> + constexpr expected(unexpected const &e) + : impl_base(unexpect, e.value()), + ctor_base(detail::default_constructor_tag{}) {} + + template < + class G = E, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> + explicit constexpr expected(unexpected &&e) noexcept( + std::is_nothrow_constructible::value) + : impl_base(unexpect, std::move(e.value())), + ctor_base(detail::default_constructor_tag{}) {} + + template < + class G = E, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> + constexpr expected(unexpected &&e) noexcept( + std::is_nothrow_constructible::value) + : impl_base(unexpect, std::move(e.value())), + ctor_base(detail::default_constructor_tag{}) {} + + template ::value> * = + nullptr> + constexpr explicit expected(unexpect_t, Args &&...args) + : impl_base(unexpect, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected(unexpect_t, std::initializer_list il, + Args &&...args) + : impl_base(unexpect, il, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template ::value && + std::is_convertible::value)> * = + nullptr, + detail::expected_enable_from_other + * = nullptr> + explicit TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(*rhs); + } else { + this->construct_error(rhs.error()); + } + } + + template ::value && + std::is_convertible::value)> * = + nullptr, + detail::expected_enable_from_other + * = nullptr> + TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(*rhs); + } else { + this->construct_error(rhs.error()); + } + } + + template < + class U, class G, + detail::enable_if_t::value && + std::is_convertible::value)> * = nullptr, + detail::expected_enable_from_other * = nullptr> + explicit TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } else { + this->construct_error(std::move(rhs.error())); + } + } + + template < + class U, class G, + detail::enable_if_t<(std::is_convertible::value && + std::is_convertible::value)> * = nullptr, + detail::expected_enable_from_other * = nullptr> + TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } else { + this->construct_error(std::move(rhs.error())); + } + } + + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::expected_enable_forward_value * = nullptr> + explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) + : expected(in_place, std::forward(v)) {} + + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::expected_enable_forward_value * = nullptr> + TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) + : expected(in_place, std::forward(v)) {} + + template < + class U = T, class G = T, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t< + (!std::is_same, detail::decay_t>::value && + !detail::conjunction, + std::is_same>>::value && + std::is_constructible::value && + std::is_assignable::value && + std::is_nothrow_move_constructible::value)> * = nullptr> + expected &operator=(U &&v) { + if (has_value()) { + val() = std::forward(v); + } else { + err().~unexpected(); + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; + } + + return *this; + } + + template < + class U = T, class G = T, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t< + (!std::is_same, detail::decay_t>::value && + !detail::conjunction, + std::is_same>>::value && + std::is_constructible::value && + std::is_assignable::value && + std::is_nothrow_move_constructible::value)> * = nullptr> + expected &operator=(U &&v) { + if (has_value()) { + val() = std::forward(v); + } else { + auto tmp = std::move(err()); + err().~unexpected(); + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } +#else + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; +#endif + } + + return *this; + } + + template ::value && + std::is_assignable::value> * = nullptr> + expected &operator=(const unexpected &rhs) { + if (!has_value()) { + err() = rhs; + } else { + this->destroy_val(); + ::new (errptr()) unexpected(rhs); + this->m_has_val = false; + } + + return *this; + } + + template ::value && + std::is_move_assignable::value> * = nullptr> + expected &operator=(unexpected &&rhs) noexcept { + if (!has_value()) { + err() = std::move(rhs); + } else { + this->destroy_val(); + ::new (errptr()) unexpected(std::move(rhs)); + this->m_has_val = false; + } + + return *this; + } + + template ::value> * = nullptr> + void emplace(Args &&...args) { + if (has_value()) { + val().~T(); + } else { + err().~unexpected(); + this->m_has_val = true; + } + ::new (valptr()) T(std::forward(args)...); + } + + template ::value> * = nullptr> + void emplace(Args &&...args) { + if (has_value()) { + val().~T(); + ::new (valptr()) T(std::forward(args)...); + } else { + auto tmp = std::move(err()); + err().~unexpected(); + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } +#else + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; +#endif + } + } + + template &, Args &&...>::value> * = nullptr> + void emplace(std::initializer_list il, Args &&...args) { + if (has_value()) { + T t(il, std::forward(args)...); + val() = std::move(t); + } else { + err().~unexpected(); + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; + } + } + + template &, Args &&...>::value> * = nullptr> + void emplace(std::initializer_list il, Args &&...args) { + if (has_value()) { + T t(il, std::forward(args)...); + val() = std::move(t); + } else { + auto tmp = std::move(err()); + err().~unexpected(); + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } +#else + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; +#endif + } + } + +private: + using t_is_void = std::true_type; + using t_is_not_void = std::false_type; + using t_is_nothrow_move_constructible = std::true_type; + using move_constructing_t_can_throw = std::false_type; + using e_is_nothrow_move_constructible = std::true_type; + using move_constructing_e_can_throw = std::false_type; + + void swap_where_both_have_value(expected & /*rhs*/, t_is_void) noexcept { + // swapping void is a no-op + } + + void swap_where_both_have_value(expected &rhs, t_is_not_void) { + using std::swap; + swap(val(), rhs.val()); + } + + void swap_where_only_one_has_value(expected &rhs, t_is_void) noexcept( + std::is_nothrow_move_constructible::value) { + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + std::swap(this->m_has_val, rhs.m_has_val); + } + + void swap_where_only_one_has_value(expected &rhs, t_is_not_void) { + swap_where_only_one_has_value_and_t_is_not_void( + rhs, typename std::is_nothrow_move_constructible::type{}, + typename std::is_nothrow_move_constructible::type{}); + } + + void swap_where_only_one_has_value_and_t_is_not_void( + expected &rhs, t_is_nothrow_move_constructible, + e_is_nothrow_move_constructible) noexcept { + auto temp = std::move(val()); + val().~T(); + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } + + void swap_where_only_one_has_value_and_t_is_not_void( + expected &rhs, t_is_nothrow_move_constructible, + move_constructing_e_can_throw) { + auto temp = std::move(val()); + val().~T(); +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } catch (...) { + val() = std::move(temp); + throw; + } +#else + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); +#endif + } + + void swap_where_only_one_has_value_and_t_is_not_void( + expected &rhs, move_constructing_t_can_throw, + e_is_nothrow_move_constructible) { + auto temp = std::move(rhs.err()); + rhs.err().~unexpected_type(); +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (rhs.valptr()) T(std::move(val())); + val().~T(); + ::new (errptr()) unexpected_type(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } catch (...) { + rhs.err() = std::move(temp); + throw; + } +#else + ::new (rhs.valptr()) T(std::move(val())); + val().~T(); + ::new (errptr()) unexpected_type(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); +#endif + } + +public: + template + detail::enable_if_t::value && + detail::is_swappable::value && + (std::is_nothrow_move_constructible::value || + std::is_nothrow_move_constructible::value)> + swap(expected &rhs) noexcept( + std::is_nothrow_move_constructible::value + &&detail::is_nothrow_swappable::value + &&std::is_nothrow_move_constructible::value + &&detail::is_nothrow_swappable::value) { + if (has_value() && rhs.has_value()) { + swap_where_both_have_value(rhs, typename std::is_void::type{}); + } else if (!has_value() && rhs.has_value()) { + rhs.swap(*this); + } else if (has_value()) { + swap_where_only_one_has_value(rhs, typename std::is_void::type{}); + } else { + using std::swap; + swap(err(), rhs.err()); + } + } + + constexpr const T *operator->() const { + TL_ASSERT(has_value()); + return valptr(); + } + TL_EXPECTED_11_CONSTEXPR T *operator->() { + TL_ASSERT(has_value()); + return valptr(); + } + + template ::value> * = nullptr> + constexpr const U &operator*() const & { + TL_ASSERT(has_value()); + return val(); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &operator*() & { + TL_ASSERT(has_value()); + return val(); + } + template ::value> * = nullptr> + constexpr const U &&operator*() const && { + TL_ASSERT(has_value()); + return std::move(val()); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&operator*() && { + TL_ASSERT(has_value()); + return std::move(val()); + } + + constexpr bool has_value() const noexcept { return this->m_has_val; } + constexpr explicit operator bool() const noexcept { return this->m_has_val; } + + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR const U &value() const & { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return val(); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &value() & { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return val(); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR const U &&value() const && { + if (!has_value()) + detail::throw_exception(bad_expected_access(std::move(err()).value())); + return std::move(val()); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&value() && { + if (!has_value()) + detail::throw_exception(bad_expected_access(std::move(err()).value())); + return std::move(val()); + } + + constexpr const E &error() const & { + TL_ASSERT(!has_value()); + return err().value(); + } + TL_EXPECTED_11_CONSTEXPR E &error() & { + TL_ASSERT(!has_value()); + return err().value(); + } + constexpr const E &&error() const && { + TL_ASSERT(!has_value()); + return std::move(err().value()); + } + TL_EXPECTED_11_CONSTEXPR E &&error() && { + TL_ASSERT(!has_value()); + return std::move(err().value()); + } + + template constexpr T value_or(U &&v) const & { + static_assert(std::is_copy_constructible::value && + std::is_convertible::value, + "T must be copy-constructible and convertible to from U&&"); + return bool(*this) ? **this : static_cast(std::forward(v)); + } + template TL_EXPECTED_11_CONSTEXPR T value_or(U &&v) && { + static_assert(std::is_move_constructible::value && + std::is_convertible::value, + "T must be move-constructible and convertible to from U&&"); + return bool(*this) ? std::move(**this) : static_cast(std::forward(v)); + } +}; + +namespace detail { +template using exp_t = typename detail::decay_t::value_type; +template using err_t = typename detail::decay_t::error_type; +template using ret_t = expected>; + +#ifdef TL_EXPECTED_CXX14 +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval()))> +constexpr auto and_then_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() + ? detail::invoke(std::forward(f), *std::forward(exp)) + : Ret(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval()))> +constexpr auto and_then_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() ? detail::invoke(std::forward(f)) + : Ret(unexpect, std::forward(exp).error()); +} +#else +template struct TC; +template (), + *std::declval())), + detail::enable_if_t>::value> * = nullptr> +auto and_then_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() + ? detail::invoke(std::forward(f), *std::forward(exp)) + : Ret(unexpect, std::forward(exp).error()); +} + +template ())), + detail::enable_if_t>::value> * = nullptr> +constexpr auto and_then_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() ? detail::invoke(std::forward(f)) + : Ret(unexpect, std::forward(exp).error()); +} +#endif + +#ifdef TL_EXPECTED_CXX14 +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +constexpr auto expected_map_impl(Exp &&exp, F &&f) { + using result = ret_t>; + return exp.has_value() ? result(detail::invoke(std::forward(f), + *std::forward(exp))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +auto expected_map_impl(Exp &&exp, F &&f) { + using result = expected>; + if (exp.has_value()) { + detail::invoke(std::forward(f), *std::forward(exp)); + return result(); + } + + return result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> +constexpr auto expected_map_impl(Exp &&exp, F &&f) { + using result = ret_t>; + return exp.has_value() ? result(detail::invoke(std::forward(f))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> +auto expected_map_impl(Exp &&exp, F &&f) { + using result = expected>; + if (exp.has_value()) { + detail::invoke(std::forward(f)); + return result(); + } + + return result(unexpect, std::forward(exp).error()); +} +#else +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +constexpr auto expected_map_impl(Exp &&exp, F &&f) + -> ret_t> { + using result = ret_t>; + + return exp.has_value() ? result(detail::invoke(std::forward(f), + *std::forward(exp))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +auto expected_map_impl(Exp &&exp, F &&f) -> expected> { + if (exp.has_value()) { + detail::invoke(std::forward(f), *std::forward(exp)); + return {}; + } + + return unexpected>(std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> + +constexpr auto expected_map_impl(Exp &&exp, F &&f) + -> ret_t> { + using result = ret_t>; + + return exp.has_value() ? result(detail::invoke(std::forward(f))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> + +auto expected_map_impl(Exp &&exp, F &&f) -> expected> { + if (exp.has_value()) { + detail::invoke(std::forward(f)); + return {}; + } + + return unexpected>(std::forward(exp).error()); +} +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, detail::decay_t>; + return exp.has_value() + ? result(*std::forward(exp)) + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, monostate>; + if (exp.has_value()) { + return result(*std::forward(exp)); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, detail::decay_t>; + return exp.has_value() + ? result() + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, monostate>; + if (exp.has_value()) { + return result(); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +#else +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) + -> expected, detail::decay_t> { + using result = expected, detail::decay_t>; + + return exp.has_value() + ? result(*std::forward(exp)) + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { + using result = expected, monostate>; + if (exp.has_value()) { + return result(*std::forward(exp)); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) + -> expected, detail::decay_t> { + using result = expected, detail::decay_t>; + + return exp.has_value() + ? result() + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { + using result = expected, monostate>; + if (exp.has_value()) { + return result(); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +#endif + +#ifdef TL_EXPECTED_CXX14 +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto or_else_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); + return exp.has_value() ? std::forward(exp) + : detail::invoke(std::forward(f), + std::forward(exp).error()); +} + +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +detail::decay_t or_else_impl(Exp &&exp, F &&f) { + return exp.has_value() ? std::forward(exp) + : (detail::invoke(std::forward(f), + std::forward(exp).error()), + std::forward(exp)); +} +#else +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto or_else_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); + return exp.has_value() ? std::forward(exp) + : detail::invoke(std::forward(f), + std::forward(exp).error()); +} + +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +detail::decay_t or_else_impl(Exp &&exp, F &&f) { + return exp.has_value() ? std::forward(exp) + : (detail::invoke(std::forward(f), + std::forward(exp).error()), + std::forward(exp)); +} +#endif +} // namespace detail + +template +constexpr bool operator==(const expected &lhs, + const expected &rhs) { + return (lhs.has_value() != rhs.has_value()) + ? false + : (!lhs.has_value() ? lhs.error() == rhs.error() : *lhs == *rhs); +} +template +constexpr bool operator!=(const expected &lhs, + const expected &rhs) { + return (lhs.has_value() != rhs.has_value()) + ? true + : (!lhs.has_value() ? lhs.error() != rhs.error() : *lhs != *rhs); +} +template +constexpr bool operator==(const expected &lhs, + const expected &rhs) { + return (lhs.has_value() != rhs.has_value()) + ? false + : (!lhs.has_value() ? lhs.error() == rhs.error() : true); +} +template +constexpr bool operator!=(const expected &lhs, + const expected &rhs) { + return (lhs.has_value() != rhs.has_value()) + ? true + : (!lhs.has_value() ? lhs.error() == rhs.error() : false); +} + +template +constexpr bool operator==(const expected &x, const U &v) { + return x.has_value() ? *x == v : false; +} +template +constexpr bool operator==(const U &v, const expected &x) { + return x.has_value() ? *x == v : false; +} +template +constexpr bool operator!=(const expected &x, const U &v) { + return x.has_value() ? *x != v : true; +} +template +constexpr bool operator!=(const U &v, const expected &x) { + return x.has_value() ? *x != v : true; +} + +template +constexpr bool operator==(const expected &x, const unexpected &e) { + return x.has_value() ? false : x.error() == e.value(); +} +template +constexpr bool operator==(const unexpected &e, const expected &x) { + return x.has_value() ? false : x.error() == e.value(); +} +template +constexpr bool operator!=(const expected &x, const unexpected &e) { + return x.has_value() ? true : x.error() != e.value(); +} +template +constexpr bool operator!=(const unexpected &e, const expected &x) { + return x.has_value() ? true : x.error() != e.value(); +} + +template ::value || + std::is_move_constructible::value) && + detail::is_swappable::value && + std::is_move_constructible::value && + detail::is_swappable::value> * = nullptr> +void swap(expected &lhs, + expected &rhs) noexcept(noexcept(lhs.swap(rhs))) { + lhs.swap(rhs); +} +} // namespace tl + +#endif diff --git a/3rdParty/tolk/CMakeLists.txt b/3rdParty/tolk/CMakeLists.txt new file mode 100644 index 00000000000..9b4cee23ce5 --- /dev/null +++ b/3rdParty/tolk/CMakeLists.txt @@ -0,0 +1,25 @@ +include(functions/FetchContent_MakeAvailableExcludeFromAll) + +include(FetchContent) +FetchContent_Declare(Tolk + URL https://github.com/sig-a11y/tolk/archive/89de98779e3b6365dc1688538d5de4ecba3fdbab.tar.gz + URL_HASH MD5=724f6022186573dd9c5c2c92ed9e21e6 +) +FetchContent_MakeAvailableExcludeFromAll(Tolk) + +target_include_directories(Tolk PUBLIC ${libTolk_SOURCE_DIR}/src) + +if(CMAKE_SIZEOF_VOID_P EQUAL 4) + set(TOLK_LIB_DIR "${Tolk_SOURCE_DIR}/libs/x86") +else() + set(TOLK_LIB_DIR "${Tolk_SOURCE_DIR}/libs/x64") +endif() +file(GLOB TOLK_DLLS + LIST_DIRECTORIES false + "${TOLK_LIB_DIR}/*.dll" + "${TOLK_LIB_DIR}/*.ini") +foreach(_TOLK_DLL_PATH ${TOLK_DLLS}) + install(FILES "${_TOLK_DLL_PATH}" + DESTINATION "." + ) +endforeach() diff --git a/3rdParty/zlib/CMakeLists.txt b/3rdParty/zlib/CMakeLists.txt index d7a8b804114..7d5d5838fff 100644 --- a/3rdParty/zlib/CMakeLists.txt +++ b/3rdParty/zlib/CMakeLists.txt @@ -4,10 +4,10 @@ set(CMAKE_POLICY_DEFAULT_CMP0048 NEW) include(FetchContent) FetchContent_Declare(zlib - URL https://www.zlib.net/zlib-1.2.13.tar.gz - https://www.zlib.net/fossils/zlib-1.2.13.tar.gz - https://github.com/madler/zlib/releases/download/v1.2.13/zlib-1.2.13.tar.gz - URL_HASH MD5=9b8aa094c4e5765dabf4da391f00d15c + URL https://www.zlib.net/zlib-1.3.tar.gz + https://www.zlib.net/fossils/zlib-1.3.tar.gz + https://github.com/madler/zlib/releases/download/v1.3/zlib-1.3.tar.gz + URL_HASH MD5=60373b133d630f74f4a1f94c1185a53f ) FetchContent_MakeAvailableExcludeFromAll(zlib) diff --git a/CMake/Assets.cmake b/CMake/Assets.cmake index 374b154476a..da98f020e0a 100644 --- a/CMake/Assets.cmake +++ b/CMake/Assets.cmake @@ -114,6 +114,9 @@ set(devilutionx_assets fonts/blue.trn fonts/buttonface.trn fonts/buttonpushed.trn + fonts/gamedialogwhite.trn + fonts/gamedialogyellow.trn + fonts/gamedialogred.trn fonts/golduis.trn fonts/goldui.trn fonts/grayuis.trn @@ -137,10 +140,30 @@ set(devilutionx_assets levels/l2data/bonechat.dun levels/towndata/automap.dun levels/towndata/automap.amp + lua_internal/get_lua_function_signature.lua + lua/devilutionx/events.lua + lua/inspect.lua + lua/repl_prelude.lua nlevels/cutl5w.clx nlevels/cutl6w.clx nlevels/l5data/cornerstone.dun nlevels/l5data/uberroom.dun + txtdata/Experience.tsv + txtdata/classes/barbarian/attributes.tsv + txtdata/classes/bard/attributes.tsv + txtdata/classes/monk/attributes.tsv + txtdata/classes/rogue/attributes.tsv + txtdata/classes/sorcerer/attributes.tsv + txtdata/classes/warrior/attributes.tsv + txtdata/items/item_prefixes.tsv + txtdata/items/item_suffixes.tsv + txtdata/items/itemdat.tsv + txtdata/items/unique_itemdat.tsv + txtdata/missiles/missile_sprites.tsv + txtdata/monsters/monstdat.tsv + txtdata/monsters/unique_monstdat.tsv + txtdata/sound/effects.tsv + txtdata/spells/spelldat.tsv ui_art/diablo.pal ui_art/hellfire.pal ui_art/creditsw.clx @@ -161,7 +184,7 @@ endif() if(APPLE) foreach(asset_file ${devilutionx_assets}) - set(src "${CMAKE_CURRENT_SOURCE_DIR}/Packaging/resources/assets/${asset_file}") + set(src "${CMAKE_CURRENT_SOURCE_DIR}/assets/${asset_file}") get_filename_component(_asset_dir "${asset_file}" DIRECTORY) set_source_files_properties("${src}" PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/${_asset_dir}" @@ -173,7 +196,7 @@ else() # - If smpq is installed, devilutionx.mpq is built from these files. # - If smpq is not installed, the game will load the assets directly from this directoy. foreach(asset_file ${devilutionx_assets}) - set(src "${CMAKE_CURRENT_SOURCE_DIR}/Packaging/resources/assets/${asset_file}") + set(src "${CMAKE_CURRENT_SOURCE_DIR}/assets/${asset_file}") set(dst "${DEVILUTIONX_ASSETS_OUTPUT_DIRECTORY}/${asset_file}") list(APPEND DEVILUTIONX_MPQ_FILES "${asset_file}") list(APPEND DEVILUTIONX_OUTPUT_ASSETS_FILES "${dst}") diff --git a/CMake/Definitions.cmake b/CMake/Definitions.cmake index 25ad6d215b0..a444decdee3 100644 --- a/CMake/Definitions.cmake +++ b/CMake/Definitions.cmake @@ -18,8 +18,10 @@ foreach( DEVILUTIONX_RESAMPLER_SPEEX DEVILUTIONX_RESAMPLER_SDL DEVILUTIONX_PALETTE_TRANSPARENCY_BLACK_16_LUT + SCREEN_READER_INTEGRATION UNPACKED_MPQS UNPACKED_SAVES + DEVILUTIONX_WINDOWS_NO_WCHAR ) if(${def_name}) list(APPEND DEVILUTIONX_DEFINITIONS ${def_name}) @@ -90,6 +92,7 @@ foreach( REMAP_KEYBOARD_KEYS DEVILUTIONX_DEFAULT_RESAMPLER STREAM_ALL_AUDIO_MIN_FILE_SIZE + DEVILUTIONX_DISPLAY_TEXTURE_FORMAT ) if(DEFINED ${def_name} AND NOT ${def_name} STREQUAL "") list(APPEND DEVILUTIONX_DEFINITIONS ${def_name}=${${def_name}}) diff --git a/CMake/Dependencies.cmake b/CMake/Dependencies.cmake index 9bcbe09ae1a..99af8005d2c 100644 --- a/CMake/Dependencies.cmake +++ b/CMake/Dependencies.cmake @@ -24,6 +24,38 @@ if(SUPPORTS_MPQ) endif() endif() +find_package(Lua 5.4 QUIET) +if(LUA_FOUND) + message("-- Found Lua ${LUA_VERSION_STRING}") +else() + if(NOT DEFINED DEVILUTIONX_SYSTEM_LUA) + message("-- Suitable system Lua package not found, will use Lua from source") + set(DEVILUTIONX_SYSTEM_LUA OFF) + endif() +endif() +dependency_options("lua" DEVILUTIONX_SYSTEM_LUA ON DEVILUTIONX_STATIC_LUA) +if(NOT DEVILUTIONX_SYSTEM_LUA) + add_subdirectory(3rdParty/Lua) + if(DEVILUTIONX_STATIC_LUA) + set(LUA_LIBRARIES lua_static) + else() + set(LUA_LIBRARIES lua_shared) + endif() +else() + find_package(Lua 5.4 REQUIRED) + include_directories(${LUA_INCLUDE_DIR}) +endif() + +add_subdirectory(3rdParty/sol2) + +if(SCREEN_READER_INTEGRATION) + if(WIN32) + add_subdirectory(3rdParty/tolk) + else() + find_package(Speechd REQUIRED) + endif() +endif() + if(EMSCRIPTEN) # We use `USE_PTHREADS=1` here to get a version of SDL2 that supports threads. emscripten_system_library("SDL2" SDL2::SDL2 USE_SDL=2 USE_PTHREADS=1) diff --git a/CMake/Platforms.cmake b/CMake/Platforms.cmake index 56e138f1b5b..1c3d1e927b4 100644 --- a/CMake/Platforms.cmake +++ b/CMake/Platforms.cmake @@ -1,7 +1,3 @@ -if(WIN32) - include(platforms/windows) -endif() - if(HAIKU) include(platforms/haiku) endif() @@ -18,7 +14,7 @@ if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD|OpenBSD|DragonFly|NetBSD") endif() set(TARGET_PLATFORM host CACHE STRING "Target platform") -set_property(CACHE TARGET_PLATFORM PROPERTY STRINGS host retrofw rg99 rg350 gkd350h cpigamesh miyoo_mini) +set_property(CACHE TARGET_PLATFORM PROPERTY STRINGS host retrofw rg99 rg350 gkd350h cpigamesh miyoo_mini windows9x) if(TARGET_PLATFORM STREQUAL "retrofw") include(platforms/retrofw) elseif(TARGET_PLATFORM STREQUAL "rg99") @@ -33,6 +29,10 @@ elseif(TARGET_PLATFORM STREQUAL "lepus") include(platforms/lepus) elseif(TARGET_PLATFORM STREQUAL "miyoo_mini") include(platforms/miyoo_mini) +elseif(TARGET_PLATORM STREQUAL "windows9x") + include(platforms/windows9x) +elseif(WIN32) + include(platforms/windows) endif() if(NINTENDO_SWITCH) diff --git a/CMake/finders/FindSpeechd.cmake b/CMake/finders/FindSpeechd.cmake new file mode 100644 index 00000000000..e02bf82adc0 --- /dev/null +++ b/CMake/finders/FindSpeechd.cmake @@ -0,0 +1,17 @@ +# find speech-dispatcher library and header if available +# Copyright (c) 2009, Jeremy Whiting +# Copyright (c) 2011, Raphael Kubo da Costa +# This module defines +# SPEECHD_INCLUDE_DIR, where to find libspeechd.h +# SPEECHD_LIBRARIES, the libraries needed to link against speechd +# SPEECHD_FOUND, If false, speechd was not found +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +find_path(SPEECHD_INCLUDE_DIR libspeechd.h PATH_SUFFIXES speech-dispatcher) + +find_library(SPEECHD_LIBRARIES NAMES speechd) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Speechd REQUIRED_VARS SPEECHD_INCLUDE_DIR SPEECHD_LIBRARIES) diff --git a/CMake/platforms/aarch64-linux-gnu-clang-static-libc++.toolchain.cmake b/CMake/platforms/aarch64-linux-gnu-clang-static-libc++.toolchain.cmake new file mode 100644 index 00000000000..48262a14890 --- /dev/null +++ b/CMake/platforms/aarch64-linux-gnu-clang-static-libc++.toolchain.cmake @@ -0,0 +1,27 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR aarch64) + +set(triple aarch64-linux-gnu) + +set(CMAKE_C_COMPILER "/usr/bin/clang") +set(CMAKE_C_COMPILER_TARGET "${triple}") +set(CMAKE_CXX_COMPILER "/usr/bin/clang++") +set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++") +set(CMAKE_CXX_COMPILER_TARGET "${triple}") +set(CMAKE_ASM_COMPILER "/usr/bin/clang") +set(CMAKE_ASM_COMPILER_TARGET "${triple}") +set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=/usr/bin/ld.lld -static-libstdc++ -static-libgcc") + +set(CMAKE_FIND_ROOT_PATH "/usr/aarch64-linux-gnu;/usr") +set(CMAKE_LIBRARY_ARCHITECTURE "${triple}") + +set(CMAKE_STRIP "/usr/bin/aarch64-linux-gnu-strip") +set(PKG_CONFIG_EXECUTABLE "${CMAKE_CURRENT_LIST_DIR}/aarch64-linux-gnu-pkg-config" CACHE STRING "Path to pkg-config") + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + +set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE arm64) diff --git a/CMake/platforms/aarch64-linux-gnu.toolchain.cmake b/CMake/platforms/aarch64-linux-gnu.toolchain.cmake index f3d13cccd89..74e83ba05dc 100644 --- a/CMake/platforms/aarch64-linux-gnu.toolchain.cmake +++ b/CMake/platforms/aarch64-linux-gnu.toolchain.cmake @@ -7,6 +7,7 @@ set(CMAKE_CXX_COMPILER "/usr/bin/aarch64-linux-gnu-g++") set(CMAKE_FIND_ROOT_PATH "/usr/aarch64-linux-gnu;/usr") set(CMAKE_LIBRARY_ARCHITECTURE aarch64-linux-gnu) +set(CMAKE_STRIP "/usr/bin/aarch64-linux-gnu-strip") set(PKG_CONFIG_EXECUTABLE "${CMAKE_CURRENT_LIST_DIR}/aarch64-linux-gnu-pkg-config" CACHE STRING "Path to pkg-config") set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) diff --git a/CMake/platforms/debian-cross-pkg-config.sh b/CMake/platforms/debian-cross-pkg-config.sh index df78ef11ade..47381a8961f 100755 --- a/CMake/platforms/debian-cross-pkg-config.sh +++ b/CMake/platforms/debian-cross-pkg-config.sh @@ -14,11 +14,13 @@ if [ x"${PKG_CONFIG_LIBDIR+set}" = x ]; then # Normalized multiarch path if any, e.g. i386-linux-gnu for i386 multiarch="`dpkg-architecture -t"${triplet}" -qDEB_HOST_MULTIARCH 2>/dev/null`" # Native multiarch path - native_multiarch="$(cat /usr/lib/pkg-config.multiarch)" + if [ -f /usr/lib/pkg-config.multiarch ]; then + native_multiarch="$(cat /usr/lib/pkg-config.multiarch)" - # This can be used for native builds as well, in that case, just exec pkg-config "$@" directly. - if [ "$native_multiarch" = "$multiarch" ]; then - exec pkg-config "$@" + # This can be used for native builds as well, in that case, just exec pkg-config "$@" directly. + if [ "$native_multiarch" = "$multiarch" ]; then + exec pkg-config "$@" + fi fi PKG_CONFIG_LIBDIR="/usr/local/${triplet}/lib/pkgconfig" diff --git a/CMake/platforms/mingw9x.toolchain.cmake b/CMake/platforms/mingw9x.toolchain.cmake new file mode 100644 index 00000000000..1ef0c0e6817 --- /dev/null +++ b/CMake/platforms/mingw9x.toolchain.cmake @@ -0,0 +1,29 @@ +SET(MINGW_CROSS TRUE) + +SET(CROSS_PREFIX "/usr" CACHE STRING "crosstool-NG prefix") + +SET(CMAKE_SYSTEM_NAME Windows) + +# workaround +list(APPEND CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES "${CROSS_PREFIX}/i686-w64-mingw32/include") + +list(PREPEND CMAKE_C_STANDARD_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_LIST_DIR}/mingw9x/include") +list(PREPEND CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_LIST_DIR}/mingw9x/include") + +# work around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106103 +set(CMAKE_CXX_FLAGS_MINSIZEREL_INIT "${CMAKE_CXX_FLAGS_MINSIZEREL_INIT} -fno-declone-ctor-dtor") + +SET(CMAKE_C_COMPILER "i686-w64-mingw32-gcc") +SET(CMAKE_CXX_COMPILER "i686-w64-mingw32-g++") +set(CMAKE_RC_COMPILER "i686-w64-mingw32-windres") +set(CMAKE_STRIP "${CROSS_PREFIX}/i686-w64-mingw32/bin/strip") +set(PKG_CONFIG_EXECUTABLE "${CROSS_PREFIX}/bin/i686-w64-mingw32-pkg-config" CACHE STRING "Path to pkg-config") + +SET(CMAKE_FIND_ROOT_PATH "${CROSS_PREFIX}/i686-w64-mingw32" "${CROSS_PREFIX}/i686-w64-mingw32/i686-w64-mingw32") + +SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + +SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +set(WIN32_INSTALL_DLLS "${CROSS_PREFIX}/i686-w64-mingw32/bin/SDL.dll") diff --git a/CMake/platforms/mingw9x/include/windef.h b/CMake/platforms/mingw9x/include/windef.h new file mode 100644 index 00000000000..1c0aebec063 --- /dev/null +++ b/CMake/platforms/mingw9x/include/windef.h @@ -0,0 +1,22 @@ +#ifndef _WINDEF_OVERRIDE_ +#define _WINDEF_OVERRIDE_ + +#include_next + +// MinGW does not define these when _WIN32_WINNT < 0x0400 +// but it declares functions that use it unconditionally. +typedef enum _FINDEX_INFO_LEVELS { + FindExInfoStandard, + FindExInfoBasic, + FindExInfoMaxInfoLevel +} FINDEX_INFO_LEVELS; +typedef enum _FINDEX_SEARCH_OPS { + FindExSearchNameMatch, + FindExSearchLimitToDirectories, + FindExSearchLimitToDevices, + FindExSearchMaxSearchOp +} FINDEX_SEARCH_OPS; + +typedef void* SOLE_AUTHENTICATION_SERVICE; + +#endif /* _WINDEF_ */ diff --git a/CMake/platforms/mingwcc.toolchain.cmake b/CMake/platforms/mingwcc.toolchain.cmake index a6baa213f25..4d3851d64d7 100644 --- a/CMake/platforms/mingwcc.toolchain.cmake +++ b/CMake/platforms/mingwcc.toolchain.cmake @@ -10,6 +10,7 @@ list(APPEND CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES "${CROSS_PREFIX}/i686-w64-min SET(CMAKE_C_COMPILER "i686-w64-mingw32-gcc") SET(CMAKE_CXX_COMPILER "i686-w64-mingw32-g++") set(CMAKE_RC_COMPILER "i686-w64-mingw32-windres") +set(CMAKE_STRIP "${CROSS_PREFIX}/i686-w64-mingw32/bin/strip") set(PKG_CONFIG_EXECUTABLE "${CROSS_PREFIX}/bin/i686-w64-mingw32-pkg-config" CACHE STRING "Path to pkg-config") SET(CMAKE_FIND_ROOT_PATH "${CROSS_PREFIX}/i686-w64-mingw32" "${CROSS_PREFIX}/i686-w64-mingw32/i686-w64-mingw32") diff --git a/CMake/platforms/mingwcc64.toolchain.cmake b/CMake/platforms/mingwcc64.toolchain.cmake index eb3e7bd884f..37448f70946 100644 --- a/CMake/platforms/mingwcc64.toolchain.cmake +++ b/CMake/platforms/mingwcc64.toolchain.cmake @@ -10,6 +10,7 @@ list(APPEND CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES "${CROSS_PREFIX}/x86_64-w64-m SET(CMAKE_C_COMPILER "x86_64-w64-mingw32-gcc") SET(CMAKE_CXX_COMPILER "x86_64-w64-mingw32-g++") set(CMAKE_RC_COMPILER "x86_64-w64-mingw32-windres") +set(CMAKE_STRIP "${CROSS_PREFIX}/x86_64-w64-mingw32/bin/strip") set(PKG_CONFIG_EXECUTABLE "${CROSS_PREFIX}/bin/x86_64-w64-mingw32-pkg-config" CACHE STRING "Path to pkg-config") SET(CMAKE_FIND_ROOT_PATH "${CROSS_PREFIX}/x86_64-w64-mingw32" "${CROSS_PREFIX}/x86_64-w64-mingw32/x86_64-w64-mingw32") diff --git a/CMake/platforms/windows.cmake b/CMake/platforms/windows.cmake index d95563eed27..90cd4445c3e 100644 --- a/CMake/platforms/windows.cmake +++ b/CMake/platforms/windows.cmake @@ -10,6 +10,8 @@ list(APPEND DEVILUTIONX_PLATFORM_LINK_LIBRARIES wininet ) +add_definitions(-DWINVER=0x0601 -D_WIN32_WINDOWS=0x0601 -D_WIN32_WINNT=0x0601) + if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") list(APPEND DEVILUTIONX_PLATFORM_COMPILE_OPTIONS "/W3" "/Zc:__cplusplus" "/utf-8") list(APPEND DEVILUTIONX_PLATFORM_COMPILE_DEFINITIONS _CRT_SECURE_NO_WARNINGS) diff --git a/CMake/platforms/windows9x.cmake b/CMake/platforms/windows9x.cmake new file mode 100644 index 00000000000..d070cbe06d0 --- /dev/null +++ b/CMake/platforms/windows9x.cmake @@ -0,0 +1,38 @@ +set(ASAN OFF) +set(UBSAN OFF) +set(DIST ON) + +set(NONET ON) +set(DISABLE_ZERO_TIER ON) +set(USE_SDL1 ON) +set(DEVILUTIONX_SYSTEM_BZIP2 OFF) +set(DEVILUTIONX_SYSTEM_LIBFMT OFF) +set(DEVILUTIONX_STATIC_LIBSODIUM OFF) + +# Compatibility with Windows 9x 8-bit mode and improved performance +set(SDL1_VIDEO_MODE_BPP 8) +set(SDL1_FORCE_DIRECT_RENDER ON) + +set(DEVILUTIONX_WINDOWS_NO_WCHAR ON) + +# `WINVER=0x0500` without `_WIN32_WINNT` is Windows 98. +# MinGW force-defines `_WIN32_WINNT=0xa00` if it isn't defined, so define it as 0. +add_definitions(-DWINVER=0x0500 -D_WIN32_WINDOWS=0x0500 -D_WIN32_WINNT=0) + +list(APPEND DEVILUTIONX_PLATFORM_LINK_LIBRARIES + shlwapi + wsock32 + ws2_32 + wininet +) + +if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + list(APPEND DEVILUTIONX_PLATFORM_COMPILE_OPTIONS "/W3" "/Zc:__cplusplus" "/utf-8") + list(APPEND DEVILUTIONX_PLATFORM_COMPILE_DEFINITIONS _CRT_SECURE_NO_WARNINGS) +else() + list(APPEND DEVILUTIONX_PLATFORM_COMPILE_OPTIONS $<$:-gstabs>) +endif() + +if(MINGW_CROSS) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/mingw") +endif() diff --git a/CMake/platforms/xbox_nxdk.cmake b/CMake/platforms/xbox_nxdk.cmake index 8070ee25d1f..9a01375667c 100644 --- a/CMake/platforms/xbox_nxdk.cmake +++ b/CMake/platforms/xbox_nxdk.cmake @@ -9,6 +9,7 @@ set(DEVILUTIONX_SYSTEM_LIBFMT OFF) set(BUILD_ASSETS_MPQ OFF) set(DEVILUTIONX_ASSETS_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/pkg/assets") +set(DEVILUTIONX_WINDOWS_NO_WCHAR ON) set(DEVILUTIONX_RESAMPLER_SPEEX OFF) set(DEFAULT_AUDIO_BUFFER_SIZE 5120) diff --git a/CMakeLists.txt b/CMakeLists.txt index aa67083b9d9..b17b79b0009 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,6 +106,12 @@ option(MACOSX_STANDALONE_APP_BUNDLE "Generate a portable app bundle to use on ot cmake_dependent_option(DISABLE_TCP "Disable TCP multiplayer option" OFF "NOT NONET" ON) cmake_dependent_option(DISABLE_ZERO_TIER "Disable ZeroTier multiplayer option" OFF "NOT NONET" ON) +# Graphics options +if(NOT USE_SDL1) + set(DEVILUTIONX_DISPLAY_TEXTURE_FORMAT "SDL_PIXELFORMAT_RGB888" CACHE STRING "Texture format for DevilutionX textures when using the GPU renderer") + mark_as_advanced(DEVILUTIONX_DISPLAY_TEXTURE_FORMAT) +endif() + # Sound options option(NOSOUND "Disable sound support" OFF) option(DEVILUTIONX_RESAMPLER_SPEEX "Build with Speex resampler" ON) @@ -124,7 +130,7 @@ set_property(CACHE DEVILUTIONX_DEFAULT_RESAMPLER PROPERTY STRINGS ${_resamplers} option(DISABLE_LTO "Disable link-time optimization (by default enabled in release mode)" OFF) option(PIE "Generate position-independent code" OFF) cmake_dependent_option(DEVILUTIONX_DISABLE_RTTI "Disable RTTI" ON "NONET" OFF) -cmake_dependent_option(DEVILUTIONX_DISABLE_EXCEPTIONS "Disable exceptions" ON "NONET" OFF) +cmake_dependent_option(DEVILUTIONX_DISABLE_EXCEPTIONS "Disable exceptions" ON "DISABLE_ZERO_TIER" OFF) RELEASE_OPTION(DEVILUTIONX_STATIC_CXX_STDLIB "Link C++ standard library statically (if available)") option(DEVILUTIONX_PROFILE_GENERATE "Build a binary that generates the profile for PGO" OFF) option(DEVILUTIONX_PROFILE_USE "Build with PGO using the given profile file" OFF) @@ -147,6 +153,8 @@ mark_as_advanced(DEVILUTIONX_PALETTE_TRANSPARENCY_BLACK_16_LUT) # Additional features option(DISABLE_DEMOMODE "Disable demo mode support" OFF) option(DISCORD_INTEGRATION "Build with Discord SDK for rich presence support" OFF) +option(SCREEN_READER_INTEGRATION "Build with screen reader support" OFF) +mark_as_advanced(SCREEN_READER_INTEGRATION) # If both UNPACKED_MPQS and UNPACKED_SAVES are enabled, we completely remove MPQ support. if(UNPACKED_MPQS AND UNPACKED_SAVES) @@ -156,7 +164,7 @@ else() endif() # By default, devilutionx.mpq is built only if smpq is installed and MPQ support is enabled. -if(SUPPORTS_MPQ) +if(SUPPORTS_MPQ AND NOT UNPACKED_MPQS) if(NOT DEFINED BUILD_ASSETS_MPQ AND NOT SRC_DIST) find_program(SMPQ smpq) elseif(BUILD_ASSETS_MPQ) @@ -263,6 +271,11 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") add_link_options("$<$:-fprofile-dir=${DEVILUTIONX_PROFILE_DIR};-fprofile-prefix-path=${CMAKE_CURRENT_BINARY_DIR}>") endif() +if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + # u8path() function is deprecated but there is no sensible alternative and it might even get un-deprecated. + add_definitions(-D_SILENCE_CXX20_U8PATH_DEPRECATION_WARNING) +endif() + # Not a genexp because CMake doesn't support it # https://gitlab.kitware.com/cmake/cmake/-/issues/20546 if(NOT DISABLE_LTO) @@ -293,12 +306,9 @@ if(GPERF) endif() endif() -# Despite setting C++ standard to 20, 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. set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_STANDARD_REQUIRED OFF) +set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # for clang-tidy set(CMAKE_THREAD_PREFER_PTHREAD ON) set(THREADS_PREFER_PTHREAD_FLAG ON) @@ -332,8 +342,7 @@ else() add_custom_command( TARGET ${BIN_TARGET} POST_BUILD DEPENDS ${BIN_TARGET} - COMMAND $<$:${CMAKE_STRIP}> - $<$:${CMAKE_STRIP}> + COMMAND $<$,$>:${CMAKE_STRIP}> ARGS $) endif() endif() @@ -438,7 +447,7 @@ if(VITA) VERSION ${VITA_VERSION} NAME ${VITA_APP_NAME} FILE Packaging/vita/sce_sys sce_sys - FILE Packaging/resources/assets assets + FILE assets assets ${VITA_TRANSLATIONS_LIST} ) endif() @@ -530,7 +539,7 @@ if(CPACK AND (APPLE OR BUILD_ASSETS_MPQ OR SRC_DIST)) DESTINATION "." ) - foreach(_SDL2_WIN32_DLL_PATH ${SDL2_WIN32_ALL_DLLS}) + foreach(_SDL2_WIN32_DLL_PATH ${SDL2_WIN32_ALL_DLLS} ${WIN32_INSTALL_DLLS}) install(FILES "${_SDL2_WIN32_DLL_PATH}" DESTINATION "." ) @@ -548,6 +557,12 @@ if(CPACK AND (APPLE OR BUILD_ASSETS_MPQ OR SRC_DIST)) ) endif() + if(SCREEN_READER_INTEGRATION) + install(FILES "${Tolk_BINARY_DIR}/libTolk.dll" + DESTINATION "." + ) + endif() + elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") string(TOLOWER ${PROJECT_NAME} project_name) set(CPACK_PACKAGE_NAME ${project_name}) diff --git a/Packaging/OpenDingux/build.sh b/Packaging/OpenDingux/build.sh index e1d8506d2c1..6880904558a 100755 --- a/Packaging/OpenDingux/build.sh +++ b/Packaging/OpenDingux/build.sh @@ -105,6 +105,13 @@ parse_args() { >&2 echo "Error: at most one of --profile-use and --profile-generate is allowed" exit 64 fi + if [[ $TARGET = rg99 ]]; then + OPK_EXTRA_FILES+=( + Packaging/OpenDingux/devilutionx-from-disk.sh + Packaging/OpenDingux/devilutionx-umount-opk-and-run.sh + ) + OPK_DESKTOP_EXEC="devilutionx-from-disk.sh" + fi if (( PROFILE_GENERATE )); then CMAKE_CONFIGURE_OPTS+=( "-DDEVILUTIONX_PROFILE_GENERATE=ON" @@ -112,7 +119,7 @@ parse_args() { ) OPK_DESKTOP_NAME="DevilutionX PG" OPK_DESKTOP_EXEC="profile-generate.sh" - OPK_EXTRA_FILES=( + OPK_EXTRA_FILES+=( Packaging/OpenDingux/profile-generate.sh test/fixtures/timedemo/WarriorLevel1to2/demo_0.dmo ) diff --git a/Packaging/OpenDingux/devilutionx-from-disk.sh b/Packaging/OpenDingux/devilutionx-from-disk.sh new file mode 100755 index 00000000000..0fe9be9503e --- /dev/null +++ b/Packaging/OpenDingux/devilutionx-from-disk.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +# Unpacks the mounted OPK to disk before running it +# in order to avoid the memory overhead of squashfs. + +OPK_DIR="${PWD}" +STORAGE="$(grep mmcblk /proc/mounts | cut -d' ' -f2 || echo /media/data/local/home)" +UNPACK_DIR="${STORAGE}/devilutionx-opk-on-disk" + +set -e +set -x + +DO_COPY=1 +if [ -f "${UNPACK_DIR}/devilutionx" ]; then + INSTALLED_MD5="$(md5sum "${UNPACK_DIR}/devilutionx" | cut -d' ' -f1)" + OPK_MD5="$(md5sum "${PWD}/devilutionx" | cut -d' ' -f1)" + if [ "$INSTALLED_MD5" = "$OPK_MD5" ]; then + DO_COPY=0 + fi +fi + +if [ "$DO_COPY" = "1" ]; then + rm -rf "$UNPACK_DIR" + mkdir -p "$UNPACK_DIR" + cp -rf "$OPK_DIR"/* "$UNPACK_DIR" +fi + +exec "${UNPACK_DIR}/devilutionx-umount-opk-and-run.sh" "${UNPACK_DIR}/devilutionx" "$@" diff --git a/Packaging/OpenDingux/devilutionx-umount-opk-and-run.sh b/Packaging/OpenDingux/devilutionx-umount-opk-and-run.sh new file mode 100755 index 00000000000..cf992036272 --- /dev/null +++ b/Packaging/OpenDingux/devilutionx-umount-opk-and-run.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -x +echo | sudo -S umount -l "$PWD" +exec "$@" diff --git a/Packaging/OpenDingux/gkd350h-manual.txt b/Packaging/OpenDingux/gkd350h-manual.txt index d32642d8380..76f8e48f90e 100644 --- a/Packaging/OpenDingux/gkd350h-manual.txt +++ b/Packaging/OpenDingux/gkd350h-manual.txt @@ -4,13 +4,13 @@ Copy diabdat.mpq from your CD (or GoG install folder) to: For Hellfire, also copy hellfire.mpq, hfmonk.mpq, hfmusic.mpq, and hfvoice.mpq. For Chinese, Japanese, and Korean text support copy: -https://github.com/diasurgical/devilutionx-assets/releases/download/v2/fonts.mpq +https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq For the Polish voice pack copy: -https://github.com/diasurgical/devilutionx-assets/releases/download/v2/pl.mpq +https://github.com/diasurgical/devilutionx-assets/releases/latest/download/pl.mpq For the Russian voice pack copy: -https://github.com/diasurgical/devilutionx-assets/releases/download/v2/ru.mpq +https://github.com/diasurgical/devilutionx-assets/releases/latest/download/ru.mpq Game saves and diablo.ini are located at: /usr/local/home/.local/share/diasurgical/devilution/ diff --git a/Packaging/OpenDingux/lepus-manual.txt b/Packaging/OpenDingux/lepus-manual.txt index 8b964191dbc..9a75aaa43b3 100644 --- a/Packaging/OpenDingux/lepus-manual.txt +++ b/Packaging/OpenDingux/lepus-manual.txt @@ -4,13 +4,13 @@ Copy diabdat.mpq from your CD (or GoG install folder) to: For Hellfire, also copy hellfire.mpq, hfmonk.mpq, hfmusic.mpq, and hfvoice.mpq. For Chinese, Japanese, and Korean text support copy: -https://github.com/diasurgical/devilutionx-assets/releases/download/v2/fonts.mpq +https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq For the Polish voice pack copy: -https://github.com/diasurgical/devilutionx-assets/releases/download/v2/pl.mpq +https://github.com/diasurgical/devilutionx-assets/releases/latest/download/pl.mpq For the Russian voice pack copy: -https://github.com/diasurgical/devilutionx-assets/releases/download/v2/ru.mpq +https://github.com/diasurgical/devilutionx-assets/releases/latest/download/ru.mpq Game saves and diablo.ini are located at: ~/.local/share/diasurgical/devilution diff --git a/Packaging/OpenDingux/profile-generate.sh b/Packaging/OpenDingux/profile-generate.sh index ee3067f4513..c041ce4d3e9 100755 --- a/Packaging/OpenDingux/profile-generate.sh +++ b/Packaging/OpenDingux/profile-generate.sh @@ -3,8 +3,10 @@ set -x SAVE_DIR="$(mktemp -d)" -ln -s "${PWD}/demo_0_reference_spawn_0_sv" "${SAVE_DIR}/" -ln -s "${PWD}/demo_0.dmo" "${SAVE_DIR}/" +cp "${PWD}/demo_0_reference_spawn_0_sv" "${SAVE_DIR}/" +cp "${PWD}/demo_0.dmo" "${SAVE_DIR}/" cp -r "${PWD}/spawn_0_sv" "${SAVE_DIR}/" -./devilutionx --diablo --spawn --demo 0 --timedemo --save-dir "$SAVE_DIR" --data-dir ~/.local/share/diasurgical/devilution +rm -rf "${HOME}/devilutionx-profile" +mkdir -p "${HOME}/devilutionx-profile" +./devilutionx-from-disk.sh --diablo --spawn --demo 0 --timedemo --save-dir "$SAVE_DIR" --data-dir ~/.local/share/diasurgical/devilution rm -rf "$SAVE_DIR" diff --git a/Packaging/OpenDingux/retrofw-manual.txt b/Packaging/OpenDingux/retrofw-manual.txt index 729a5a35b72..bd44241dceb 100644 --- a/Packaging/OpenDingux/retrofw-manual.txt +++ b/Packaging/OpenDingux/retrofw-manual.txt @@ -4,13 +4,13 @@ Copy diabdat.mpq from your CD (or GoG install folder) to: For Hellfire, also copy hellfire.mpq, hfmonk.mpq, hfmusic.mpq, and hfvoice.mpq. For Chinese, Japanese, and Korean text support copy: -https://github.com/diasurgical/devilutionx-assets/releases/download/v2/fonts.mpq +https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq For the Polish voice pack copy: -https://github.com/diasurgical/devilutionx-assets/releases/download/v2/pl.mpq +https://github.com/diasurgical/devilutionx-assets/releases/latest/download/pl.mpq For the Russian voice pack copy: -https://github.com/diasurgical/devilutionx-assets/releases/download/v2/ru.mpq +https://github.com/diasurgical/devilutionx-assets/releases/latest/download/ru.mpq Game saves and diablo.ini are located at: ~/.local/share/diasurgical/devilution diff --git a/Packaging/OpenDingux/rg350-manual.txt b/Packaging/OpenDingux/rg350-manual.txt index c18b1ebf5c6..704f1f604e5 100644 --- a/Packaging/OpenDingux/rg350-manual.txt +++ b/Packaging/OpenDingux/rg350-manual.txt @@ -4,13 +4,13 @@ Copy diabdat.mpq from your CD (or GoG install folder) to: For Hellfire, also copy hellfire.mpq, hfmonk.mpq, hfmusic.mpq, and hfvoice.mpq. For Chinese, Japanese, and Korean text support copy: -https://github.com/diasurgical/devilutionx-assets/releases/download/v2/fonts.mpq +https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq For the Polish voice pack copy: -https://github.com/diasurgical/devilutionx-assets/releases/download/v2/pl.mpq +https://github.com/diasurgical/devilutionx-assets/releases/latest/download/pl.mpq For the Russian voice pack copy: -https://github.com/diasurgical/devilutionx-assets/releases/download/v2/ru.mpq +https://github.com/diasurgical/devilutionx-assets/releases/latest/download/ru.mpq Game saves and diablo.ini are located at: ~/.local/share/diasurgical/devilution/ diff --git a/Packaging/OpenDingux/rg99-manual.txt b/Packaging/OpenDingux/rg99-manual.txt index 542269e925f..485689438a6 100644 --- a/Packaging/OpenDingux/rg99-manual.txt +++ b/Packaging/OpenDingux/rg99-manual.txt @@ -4,10 +4,10 @@ Copy diabdat.mpq from your CD (or GoG install folder) to: For Hellfire, also copy hellfire.mpq, hfmonk.mpq, hfmusic.mpq, and hfvoice.mpq. For Chinese, Japanese, and Korean text support copy: -https://github.com/diasurgical/devilutionx-assets/releases/download/v1/fonts.mpq +https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq For the Polish voice pack copy: -https://github.com/diasurgical/devilutionx-assets/releases/download/v1/pl.mpq +https://github.com/diasurgical/devilutionx-assets/releases/latest/download/pl.mpq Game saves and diablo.ini are located at: ~/.local/share/diasurgical/devilution diff --git a/Packaging/OpenDingux/rg99-pgo.md b/Packaging/OpenDingux/rg99-pgo.md index f03e0492ef8..d092f2741ba 100644 --- a/Packaging/OpenDingux/rg99-pgo.md +++ b/Packaging/OpenDingux/rg99-pgo.md @@ -35,3 +35,58 @@ Here are the instructions for producing a PGO'd build. ``` 7. The final package is at `build-rg99/devilutionx-rg99.opk`. + +## Remote Debugging with VS Code + +If the demo crashes and you cannot reproduce this on PC, you can +use a remote debugger to diagnose the issue. + +Unpack the package and copy it to the RG99: + +```bash +cd build-rg99 +rm -rf squashfs-root +unsquashfs devilutionx-rg99.opk +ssh rg99 'rm -rf /media/data/local/home/squashfs-root' +scp -r -O squashfs-root/ rg99:/media/data/local/home/squashfs-root +``` + +Then, on RG99, prepare the demo files and run `gdbserver`: + +```bash +mkdir -p demo +cp -r squashfs-root/demo_0* demo +cp -r squashfs-root/spawn_0_sv demo +cd squashfs-root +gdbserver 10.1.1.1:8001 devilutionx --diablo --spawn --demo 0 --timedemo \ + --save-dir ~/demo --data-dir ~/.local/share/diasurgical/devilution +``` + +Then, on the PC, add the following VS Code configuration to `.vscode/launch.json`: + +```json +{ + "name": "rg99 remote debug", + "type": "cppdbg", + "request": "launch", + "program": "build-rg99/devilutionx", + "stopAtEntry": true, + "miDebuggerPath": "/opt/rs90-toolchain/bin/mipsel-linux-gdb", + "miDebuggerArgs": "-ix /opt/rs90-toolchain/mipsel-rs90-linux-musl/sysroot/usr/share/buildroot/gdbinit", + "MIMode": "gdb", + "miDebuggerServerAddress": "10.1.1.3:8001", + "targetArchitecture": "mips", + "additionalSOLibSearchPath": "/opt/rs90-toolchain/mipsel-rs90-linux-musl/sysroot", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + "externalConsole": false, + "cwd": "${workspaceFolder}" +} +``` + +Finally, run the configuration from the "Run and Debug" VS Code tab. diff --git a/Packaging/cpi-gamesh/readme.md b/Packaging/cpi-gamesh/readme.md index 13436f8034a..d7b55b599c9 100644 --- a/Packaging/cpi-gamesh/readme.md +++ b/Packaging/cpi-gamesh/readme.md @@ -10,8 +10,8 @@ Once installed, 'X' pulls the updated code and does the compiling. Note that any When the compile is finished and the diabdat.mpq is in place at '/home/cpi/.local/share/diasurgical/devilution/', you can play the game. - To run the Diablo: Hellfire expansion you will need to also copy hellfire.mpq, hfmonk.mpq, hfmusic.mpq, hfvoice.mpq. - - For Chinese, Japanese, and Korean text support download https://github.com/diasurgical/devilutionx-assets/releases/download/v1/fonts.mpq and add it to the game folder. - - For the Polish voice pack download https://github.com/diasurgical/devilutionx-assets/releases/download/v1/pl.mpq. + - For Chinese, Japanese, and Korean text support download https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq and add it to the game folder. + - For the Polish voice pack download https://github.com/diasurgical/devilutionx-assets/releases/latest/download/pl.mpq. Enjoy! For ClockworkOS v0.5, buster-backports are required to have updated libraries: https://backports.debian.org/Instructions/ diff --git a/Packaging/miyoo_mini/skeleton_MiniUI/Diablo/readme.txt b/Packaging/miyoo_mini/skeleton_MiniUI/Diablo/readme.txt index edadf48110e..a6342fda72d 100644 --- a/Packaging/miyoo_mini/skeleton_MiniUI/Diablo/readme.txt +++ b/Packaging/miyoo_mini/skeleton_MiniUI/Diablo/readme.txt @@ -11,7 +11,7 @@ For the full game: - Optional: If you want to also play the Hellfire expansion, copy hellfire.mpq, hfmonk.mpq, hfmusic.mpq, hfvoice.mpq into this fodler aswell For the free shareware version: - - Get the spawn.mpq (https://github.com/diasurgical/devilutionx-assets/releases/download/v2/spawn.mpq) + - Get the spawn.mpq (https://github.com/diasurgical/devilutionx-assets/releases/latest/download/spawn.mpq) - Copy the spawn.mpq into this folder Controls diff --git a/Packaging/miyoo_mini/skeleton_OnionOS/readme.txt b/Packaging/miyoo_mini/skeleton_OnionOS/readme.txt index 2479731fd80..ffa39b30e2b 100644 --- a/Packaging/miyoo_mini/skeleton_OnionOS/readme.txt +++ b/Packaging/miyoo_mini/skeleton_OnionOS/readme.txt @@ -12,7 +12,7 @@ For the full game: - Optional: If you want to also play the Hellfire expansion, copy hellfire.mpq, hfmonk.mpq, hfmusic.mpq, hfvoice.mpq into the same folder For the free shareware version: - - Get the spawn.mpq (https://github.com/diasurgical/devilutionx-assets/releases/download/v2/spawn.mpq) + - Get the spawn.mpq (https://github.com/diasurgical/devilutionx-assets/releases/latest/download/spawn.mpq) - Copy the spawn.mpq into Roms/PORTS/Binaries/Diablo.port/FILES_HERE Controls diff --git a/Packaging/nix/LinuxReleasePackaging.sh b/Packaging/nix/LinuxReleasePackaging.sh index 2e736504b16..4d0e6e76b61 100755 --- a/Packaging/nix/LinuxReleasePackaging.sh +++ b/Packaging/nix/LinuxReleasePackaging.sh @@ -16,7 +16,7 @@ if [[ -f "${PKG_PATH}/lib/discord_game_sdk.so" ]]; then cat <<'SH' > "${BUILD_DIR}/package/devilutionx.sh" #!/bin/sh BASEDIR="$(dirname "$(realpath "$0")")" -LD_LIBRARY_PATH="$BASEDIR" "$BASEDIR"/devilutionx +LD_LIBRARY_PATH="$BASEDIR" "$BASEDIR"/devilutionx "$@" SH chmod +x "${BUILD_DIR}/package/devilutionx.sh" fi diff --git a/Packaging/nix/README.txt b/Packaging/nix/README.txt index a4cf648c0fe..9336d9fc79d 100644 --- a/Packaging/nix/README.txt +++ b/Packaging/nix/README.txt @@ -13,9 +13,9 @@ For a full list of changes see our changelog: https://github.com/diasurgical/dev - Install libsdl2 - Copy DIABDAT.MPQ from the CD or GOG-installation (or extract it from the GoG installer) to the DevilutionX folder. - To run the Diablo: Hellfire expansion you will need to also copy hellfire.mpq, hfmonk.mpq, hfmusic.mpq, hfvoice.mpq. - - For Chinese, Japanese, and Korean text support download https://github.com/diasurgical/devilutionx-assets/releases/download/v2/fonts.mpq and add it to the game folder. - - For the Polish voice pack download https://github.com/diasurgical/devilutionx-assets/releases/download/v2/pl.mpq. - - For the Russian voice pack download https://github.com/diasurgical/devilutionx-assets/releases/download/v2/ru.mpq. + - For Chinese, Japanese, and Korean text support download https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq and add it to the game folder. + - For the Polish voice pack download https://github.com/diasurgical/devilutionx-assets/releases/latest/download/pl.mpq. + - For the Russian voice pack download https://github.com/diasurgical/devilutionx-assets/releases/latest/download/ru.mpq. - Run ./devilutionx # Multiplayer diff --git a/Packaging/nix/debian-cross-aarch64-prep.sh b/Packaging/nix/debian-cross-aarch64-prep.sh index 38f1247d61f..22704264cf1 100755 --- a/Packaging/nix/debian-cross-aarch64-prep.sh +++ b/Packaging/nix/debian-cross-aarch64-prep.sh @@ -14,11 +14,23 @@ deb [arch=arm64] http://ports.ubuntu.com/ ${FLAVOR} multiverse deb [arch=arm64] http://ports.ubuntu.com/ ${FLAVOR}-updates multiverse deb [arch=arm64] http://ports.ubuntu.com/ ${FLAVOR}-backports main restricted universe multiverse LIST - sudo sed -i 's/deb http/deb [arch=amd64,i386] http/' /etc/apt/sources.list + sudo sed -E -i 's/deb (http|file|mirror)/deb [arch=amd64,i386] \1/' /etc/apt/sources.list + cat /etc/apt/sources.list fi + +PACKAGES=( + cmake git smpq gettext dpkg-cross libc-dev-arm64-cross + libsdl2-dev:arm64 libsdl2-image-dev:arm64 libsodium-dev:arm64 + libsimpleini-dev:arm64 libpng-dev:arm64 libbz2-dev:arm64 libfmt-dev:arm64 + libspeechd-dev:arm64 +) + +if (( $# < 1 )) || [[ "$1" != --no-gcc ]]; then + PACKAGES+=(crossbuild-essential-arm64) +fi + + sudo dpkg --add-architecture arm64 sudo apt-get update -sudo apt-get install -y cmake git smpq gettext crossbuild-essential-arm64 \ - libsdl2-dev:arm64 libsdl2-image-dev:arm64 libsodium-dev:arm64 \ - libsimpleini-dev:arm64 libpng-dev:arm64 libbz2-dev:arm64 libfmt-dev:arm64 +sudo apt-get install -y "${PACKAGES[@]}" diff --git a/Packaging/nix/debian-cross-i386-prep.sh b/Packaging/nix/debian-cross-i386-prep.sh index 8f87383a65d..1b0f79218ed 100755 --- a/Packaging/nix/debian-cross-i386-prep.sh +++ b/Packaging/nix/debian-cross-i386-prep.sh @@ -2,9 +2,17 @@ set -euo pipefail set -x +PACKAGES=( + cmake git smpq gettext + libsdl2-dev:i386 libsdl2-image-dev:i386 libsodium-dev:i386 + libpng-dev:i386 libbz2-dev:i386 libfmt-dev:i386 libspeechd-dev:i386 +) + +if (( $# < 1 )) || [[ "$1" != --no-gcc ]]; then + PACKAGES+=(g++-multilib) +fi + sudo dpkg --add-architecture i386 sudo apt-get update -sudo apt-get install --ignore-hold -y \ - cmake g++-multilib git smpq gettext \ - libsdl2-dev:i386 libsdl2-image-dev:i386 libsodium-dev:i386 \ - libpng-dev:i386 libbz2-dev:i386 libfmt-dev:i386 +sudo apt-get install --ignore-hold -y "${PACKAGES[@]}" + diff --git a/Packaging/nix/debian-host-prep.sh b/Packaging/nix/debian-host-prep.sh index 419b3ec3e22..b46223fb5a6 100755 --- a/Packaging/nix/debian-host-prep.sh +++ b/Packaging/nix/debian-host-prep.sh @@ -2,7 +2,15 @@ set -euo pipefail set -x +PACKAGES=( + rpm pkg-config cmake git smpq gettext libsdl2-dev libsdl2-image-dev libsodium-dev + libpng-dev libbz2-dev libfmt-dev libspeechd-dev +) + +if (( $# < 1 )) || [[ "$1" != --no-gcc ]]; then + PACKAGES+=(g++) +fi + sudo apt-get update -sudo apt-get install -y \ - rpm pkg-config cmake g++ git smpq gettext libsdl2-dev libsdl2-image-dev libsodium-dev \ - libpng-dev libbz2-dev libfmt-dev +sudo apt-get install -y "${PACKAGES[@]}" + diff --git a/Packaging/nix/devilutionx.metainfo.xml b/Packaging/nix/devilutionx.metainfo.xml index f4ee238935d..20d820e63d3 100644 --- a/Packaging/nix/devilutionx.metainfo.xml +++ b/Packaging/nix/devilutionx.metainfo.xml @@ -64,6 +64,23 @@ + + +

This is a primarily bugfix release, which includes the following updates:

+
    +
  • Resolve various gameplay and graphical issues
  • +
  • Revamped settings menu for better organization
  • +
  • Rectification of crashes identified in version 1.5.0
  • +
  • Reduced RAM usage for improved performance
  • +
  • Updates to PVP arenas
  • +
  • Increased reliability in multiplayer functionality
  • +
  • Improved translations
  • +
  • Fixed gameplay recording playback issues
  • +
+

Please visit the full changelog for more detailed notes

+
+ https://github.com/diasurgical/devilutionX/releases/tag/1.5.1 +

This release includes a lot of features, such as:

diff --git a/Packaging/pi/README.txt b/Packaging/pi/README.txt index 25290f35e4c..71d9542fc8e 100644 --- a/Packaging/pi/README.txt +++ b/Packaging/pi/README.txt @@ -13,9 +13,9 @@ For a full list of changes see our changelog: https://github.com/diasurgical/dev - Install libsdl2 - Copy DIABDAT.MPQ from the CD or GOG-installation (or extract it from the GoG installer) to the DevilutionX folder. - To run the Diablo: Hellfire expansion you will need to also copy hellfire.mpq, hfmonk.mpq, hfmusic.mpq, hfvoice.mpq. - - For Chinese, Japanese, and Korean text support download https://github.com/diasurgical/devilutionx-assets/releases/download/v2/fonts.mpq and add it to the game folder. - - For the Polish voice pack download https://github.com/diasurgical/devilutionx-assets/releases/download/v2/pl.mpq. - - For the Russian voice pack download https://github.com/diasurgical/devilutionx-assets/releases/download/v2/ru.mpq. + - For Chinese, Japanese, and Korean text support download https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq and add it to the game folder. + - For the Polish voice pack download https://github.com/diasurgical/devilutionx-assets/releases/latest/download/pl.mpq. + - For the Russian voice pack download https://github.com/diasurgical/devilutionx-assets/releases/latest/download/ru.mpq. - Run ./devilutionx # Raspberry Pi performance diff --git a/Packaging/switch/README.txt b/Packaging/switch/README.txt index 1f5062c1e14..4cedfec46c0 100644 --- a/Packaging/switch/README.txt +++ b/Packaging/switch/README.txt @@ -1,12 +1,12 @@ # Nintendo Switch Port of DevilutionX (Diablo) # How To Install: - - Put `devilutionx.nro` and `devilutionx.mpq` in into `/switch/devilutionx` + - Put `devilutionx.nro` into `/switch/devilutionx` - Copy diabdat.mpq from your CD (or GoG install folder) to `/switch/devilutionx`. - To run the Diablo: Hellfire expansion you will need to also copy hellfire.mpq, hfmonk.mpq, hfmusic.mpq, hfvoice.mpq. - - For Chinese, Japanese, and Korean text support download https://github.com/diasurgical/devilutionx-assets/releases/download/v2/fonts.mpq and add it to the game folder. - - For the Polish voice pack download https://github.com/diasurgical/devilutionx-assets/releases/download/v2/pl.mpq. - - For the Russian voice pack download https://github.com/diasurgical/devilutionx-assets/releases/download/v2/ru.mpq. + - For Chinese, Japanese, and Korean text support download https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq and add it to the game folder. + - For the Polish voice pack download https://github.com/diasurgical/devilutionx-assets/releases/latest/download/pl.mpq. + - For the Russian voice pack download https://github.com/diasurgical/devilutionx-assets/releases/latest/download/ru.mpq. - Launch `devilutionx.nro`. (Do not use album to launch; see the note below.) - *Note:* Hold R on any installed game and launch it. Do not use album to launch. If you use album, the homebrew will only have a small amount memory available, and the touch keyboard won't work. This is true for all homebrew, not just DevilutionX. @@ -42,7 +42,7 @@ For a full list of changes see our changelog: https://github.com/diasurgical/dev # Legal DevilutionX is released to the Public Domain. The documentation and functionality provided by DevilutionX may only be utilized with assets provided by ownership of Diablo. -The source code in this repository is for non-commerical use only. If you use the source code you may not charge others for access to it or any derivative work thereof. +The source code in this repository is for non-commercial use only. If you use the source code you may not charge others for access to it or any derivative work thereof. Diablo® - Copyright © 1996 Blizzard Entertainment, Inc. All rights reserved. Diablo and Blizzard Entertainment are trademarks or registered trademarks of Blizzard Entertainment, Inc. in the U.S. and/or other countries. diff --git a/Packaging/vita/README.txt b/Packaging/vita/README.txt index 15302cea00c..a12454bd690 100644 --- a/Packaging/vita/README.txt +++ b/Packaging/vita/README.txt @@ -3,9 +3,9 @@ ## How To Play: - Install VPK - Copy diabdat.mpq from your CD or GoG installation (or [extract it from the GoG installer](https://github.com/diasurgical/devilutionX/wiki/Extracting-the-.MPQs-from-the-GoG-installer)) to the `ux0:/data/diasurgical/devilution/`. - - For Chinese, Japanese, and Korean text support download https://github.com/diasurgical/devilutionx-assets/releases/download/v2/fonts.mpq and add it to the game folder. - - For the Polish voice pack download https://github.com/diasurgical/devilutionx-assets/releases/download/v2/pl.mpq. - - For the Russian voice pack download https://github.com/diasurgical/devilutionx-assets/releases/download/v2/ru.mpq. + - For Chinese, Japanese, and Korean text support download https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq and add it to the game folder. + - For the Polish voice pack download https://github.com/diasurgical/devilutionx-assets/releases/latest/download/pl.mpq. + - For the Russian voice pack download https://github.com/diasurgical/devilutionx-assets/releases/latest/download/ru.mpq. # Building from Source diff --git a/Packaging/windows/README.txt b/Packaging/windows/README.txt index 963420e1758..194116568e2 100644 --- a/Packaging/windows/README.txt +++ b/Packaging/windows/README.txt @@ -12,9 +12,9 @@ For a full list of changes see our changelog: https://github.com/diasurgical/dev - Extract the files in the zip - Copy DIABDAT.MPQ from the CD or GOG-installation (or extract it from the GoG installer) to the DevilutionX folder. - To run the Diablo: Hellfire expansion you will need to also copy hellfire.mpq, hfmonk.mpq, hfmusic.mpq, hfvoice.mpq. - - For Chinese, Japanese, and Korean text support download https://github.com/diasurgical/devilutionx-assets/releases/download/v2/fonts.mpq and add it to the game folder. - - For the Polish voice pack download https://github.com/diasurgical/devilutionx-assets/releases/download/v2/pl.mpq. - - For the Russian voice pack download https://github.com/diasurgical/devilutionx-assets/releases/download/v2/ru.mpq. + - For Chinese, Japanese, and Korean text support download https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq and add it to the game folder. + - For the Polish voice pack download https://github.com/diasurgical/devilutionx-assets/releases/latest/download/pl.mpq. + - For the Russian voice pack download https://github.com/diasurgical/devilutionx-assets/releases/latest/download/ru.mpq. - Run devilutionx.exe # Multiplayer diff --git a/Packaging/windows/mingw-prep.sh b/Packaging/windows/mingw-prep.sh index d7dd0795a4b..3dc88ea2807 100755 --- a/Packaging/windows/mingw-prep.sh +++ b/Packaging/windows/mingw-prep.sh @@ -31,6 +31,10 @@ else SUDO="" fi +rm -rf "tmp-mingw-${MINGW_ARCH}-prep" +mkdir -p "tmp-mingw-${MINGW_ARCH}-prep" +cd "tmp-mingw-${MINGW_ARCH}-prep" + wget -q https://www.libsdl.org/release/SDL2-devel-${SDLDEV_VERS}-mingw.tar.gz -OSDL2-devel-${SDLDEV_VERS}-mingw.tar.gz tar -xzf SDL2-devel-${SDLDEV_VERS}-mingw.tar.gz $SUDO cp -r SDL2*/${MINGW_ARCH}/* ${MINGW_PREFIX} diff --git a/Packaging/windows/mingw9x-prep.sh b/Packaging/windows/mingw9x-prep.sh new file mode 100755 index 00000000000..020983cefaf --- /dev/null +++ b/Packaging/windows/mingw9x-prep.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +SDLDEV_VERS=1.2.15 +SODIUM_VERS=1.0.18 + +# exit when any command fails +set -euo pipefail + +MINGW_ARCH=i686-w64-mingw32 +SODIUM_ARCH=win32 + +# set MINGW_PREFIX +MINGW_PREFIX=/usr/${MINGW_ARCH} +if [ ! -d "${MINGW_PREFIX}" ]; then + echo "MinGW prefix not found (${MINGW_PREFIX})" + exit 1 +else + echo "Installing to ${MINGW_PREFIX}" +fi + +# only use sudo when necessary +if [ `id -u` -ne 0 ]; then + SUDO=sudo +else + SUDO="" +fi + +rm -rf tmp-mingw9x-prep +mkdir -p tmp-mingw9x-prep +cd tmp-mingw9x-prep + +curl --no-progress-meter -OL https://www.libsdl.org/release/SDL-devel-${SDLDEV_VERS}-mingw32.tar.gz +tar -xzf SDL-devel-${SDLDEV_VERS}-mingw32.tar.gz +$SUDO cp -r SDL-*/include/* ${MINGW_PREFIX}/include +$SUDO cp -r SDL-*/lib/* ${MINGW_PREFIX}/lib +$SUDO cp -r SDL-*/bin/* ${MINGW_PREFIX}/bin + +wget -q https://github.com/jedisct1/libsodium/releases/download/${SODIUM_VERS}-RELEASE/libsodium-${SODIUM_VERS}-mingw.tar.gz -Olibsodium-${SODIUM_VERS}-mingw.tar.gz +tar -xzf libsodium-${SODIUM_VERS}-mingw.tar.gz --no-same-owner +$SUDO cp -r libsodium-${SODIUM_ARCH}/* ${MINGW_PREFIX} + +# Fixup pkgconfig prefix: +find "${MINGW_PREFIX}/lib/pkgconfig/" -name '*.pc' -exec \ + $SUDO sed -i "s|^prefix=.*|prefix=${MINGW_PREFIX}|" '{}' \; + +# Fixup CMake prefix: +find "${MINGW_PREFIX}" -name '*.cmake' -exec \ + $SUDO sed -i "s|/opt/local/${MINGW_ARCH}|${MINGW_PREFIX}|" '{}' \; diff --git a/README.md b/README.md index 80e5d679c61..1a11756c9c7 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ For a full list of changes see our [changelog](docs/CHANGELOG.md). # How to Install -Note: You'll need access to the data from the original game. If you don't have an original CD then you can [buy Diablo from GoG.com](https://www.gog.com/game/diablo). Alternately you can use `spawn.mpq` from the [shareware](https://github.com/diasurgical/devilutionx-assets/releases/download/v2/spawn.mpq) [[2]](http://ftp.blizzard.com/pub/demos/diablosw.exe) version, in place of `DIABDAT.MPQ`, to play the shareware portion of the game. +Note: You'll need access to the data from the original game. If you don't have an original CD then you can [buy Diablo from GoG.com](https://www.gog.com/game/diablo). Alternately you can use `spawn.mpq` from the [shareware](https://github.com/diasurgical/devilutionx-assets/releases/latest/download/spawn.mpq) [[2]](http://ftp.blizzard.com/pub/demos/diablosw.exe) version, in place of `DIABDAT.MPQ`, to play the shareware portion of the game. Download the latest [DevilutionX release](https://github.com/diasurgical/devilutionX/releases/latest) and extract the contents to a location of your choosing or [build from source](#building-from-source). diff --git a/Source/.clang-format b/Source/.clang-format index 2403752f3a8..024bdf68edc 100644 --- a/Source/.clang-format +++ b/Source/.clang-format @@ -1,7 +1,9 @@ BasedOnStyle: webkit AlignTrailingComments: true AllowShortBlocksOnASingleLine: true -AllowShortFunctionsOnASingleLine: None +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse PointerAlignment: Right TabWidth: 4 UseTab: ForIndentation diff --git a/Source/.clang-tidy b/Source/.clang-tidy index 5f6e82aa924..13bc05437df 100644 --- a/Source/.clang-tidy +++ b/Source/.clang-tidy @@ -46,12 +46,15 @@ Checks: > performance-*, portability-*, readability-*, + -readability-identifier-length, + -bugprone-easily-swappable-parameters, -readability-magic-numbers, -misc-non-private-member-variables-in-classes, -modernize-avoid-c-arrays, -modernize-use-trailing-return-type, -modernize-concat-nested-namespaces, - -modernize-avoid-bind + -modernize-avoid-bind, + -modernize-use-constraints HeaderFilterRegex: "^(Source|test)\\.h$" diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index d9bc5b8e666..0af332af505 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -12,9 +12,9 @@ set(libdevilutionx_SRCS dead.cpp debug.cpp diablo.cpp + diablo_msg.cpp doom.cpp engine.cpp - error.cpp gamemenu.cpp gmenu.cpp help.cpp @@ -68,6 +68,10 @@ set(libdevilutionx_SRCS controls/modifier_hints.cpp controls/plrctrls.cpp + data/file.cpp + data/parser.cpp + data/record_reader.cpp + DiabloUI/button.cpp DiabloUI/credits.cpp DiabloUI/credits_lines.cpp @@ -85,6 +89,7 @@ set(libdevilutionx_SRCS DiabloUI/settingsmenu.cpp DiabloUI/support_lines.cpp DiabloUI/title.cpp + DiabloUI/text_input.cpp dvlnet/abstract_net.cpp dvlnet/base.cpp @@ -128,7 +133,29 @@ set(libdevilutionx_SRCS levels/town.cpp levels/trigs.cpp + lua/autocomplete.cpp + lua/lua.cpp + lua/modules/audio.cpp + lua/modules/dev.cpp + lua/modules/dev/display.cpp + lua/modules/dev/items.cpp + lua/modules/dev/level.cpp + lua/modules/dev/level/map.cpp + lua/modules/dev/level/warp.cpp + lua/modules/dev/monsters.cpp + lua/modules/dev/player.cpp + lua/modules/dev/player/gold.cpp + lua/modules/dev/player/spells.cpp + lua/modules/dev/player/stats.cpp + lua/modules/dev/quests.cpp + lua/modules/dev/search.cpp + lua/modules/dev/towners.cpp + lua/modules/log.cpp + lua/modules/render.cpp + lua/repl.cpp + panels/charpanel.cpp + panels/console.cpp panels/info_box.cpp panels/mainpanel.cpp panels/spell_book.cpp @@ -157,17 +184,35 @@ set(libdevilutionx_SRCS utils/language.cpp utils/logged_fstream.cpp utils/paths.cpp + utils/parse_int.cpp utils/pcx_to_clx.cpp utils/sdl_bilinear_scale.cpp utils/sdl_thread.cpp utils/str_cat.cpp utils/str_case.cpp utils/surface_to_clx.cpp + utils/timer.cpp utils/utf8.cpp) +# These files are responsible for most of the runtime in Debug mode. +# Apply some optimizations to them even in Debug mode to get reasonable performance. +set(_optimize_in_debug_srcs + engine/render/clx_render.cpp + engine/render/dun_render.cpp + engine/render/text_render.cpp + utils/cel_to_clx.cpp + utils/cl2_to_clx.cpp + utils/pcx_to_clx.cpp) +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_BUILD_TYPE STREQUAL "Debug") + set_source_files_properties(${_optimize_in_debug_srcs} PROPERTIES COMPILE_OPTIONS "-O2;--param=max-vartrack-size=900000000") +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_BUILD_TYPE STREQUAL "Debug") + set_source_files_properties(${_optimize_in_debug_srcs} PROPERTIES COMPILE_OPTIONS "-O2") +endif() + if(SUPPORTS_MPQ) list(APPEND libdevilutionx_DEPS libmpq) list(APPEND libdevilutionx_SRCS + mpq/mpq_common.cpp mpq/mpq_reader.cpp mpq/mpq_sdl_rwops.cpp mpq/mpq_writer.cpp) @@ -230,9 +275,19 @@ if(DISCORD_INTEGRATION) ) endif() +if(SCREEN_READER_INTEGRATION) + list(APPEND libdevilutionx_SRCS + utils/screen_reader.cpp + ) +endif() + add_devilutionx_library(libdevilutionx OBJECT ${libdevilutionx_SRCS}) target_include_directories(libdevilutionx PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) +if(SCREEN_READER_INTEGRATION AND NOT WIN32) + target_include_directories(libdevilutionx PUBLIC ${Speechd_INCLUDE_DIRS}) +endif() + # Use file GENERATE instead of configure_file because configure_file # does not support generator expressions. get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) @@ -256,6 +311,12 @@ if(DISCORD_INTEGRATION) target_link_libraries(libdevilutionx PRIVATE discord discord_game_sdk) endif() +target_link_libraries(libdevilutionx PUBLIC ${LUA_LIBRARIES} sol2::sol2) + +if(SCREEN_READER_INTEGRATION AND WIN32) + target_compile_definitions(libdevilutionx PRIVATE Tolk) +endif() + target_link_libraries(libdevilutionx PUBLIC Threads::Threads DevilutionX::SDL @@ -267,6 +328,14 @@ target_link_libraries(libdevilutionx PUBLIC ${libdevilutionx_DEPS} ) +if(SCREEN_READER_INTEGRATION) + if(WIN32) + target_link_libraries(libdevilutionx PUBLIC Tolk) + else() + target_link_libraries(libdevilutionx PUBLIC speechd) + endif() +endif() + if(NOT USE_SDL1) target_link_libraries(libdevilutionx PUBLIC SDL2::SDL2_image) endif() diff --git a/Source/DiabloUI/button.cpp b/Source/DiabloUI/button.cpp index 3a702dbedec..f8bc882df88 100644 --- a/Source/DiabloUI/button.cpp +++ b/Source/DiabloUI/button.cpp @@ -44,7 +44,8 @@ void RenderButton(const UiButton &button) --textRect.position.y; } - DrawString(out, button.GetText(), textRect, UiFlags::AlignCenter | UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite); + DrawString(out, button.GetText(), textRect, + { .flags = UiFlags::AlignCenter | UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite }); } bool HandleMouseEventButton(const SDL_Event &event, UiButton *button) diff --git a/Source/DiabloUI/credits.cpp b/Source/DiabloUI/credits.cpp index ebe04178cec..17c343a32e8 100644 --- a/Source/DiabloUI/credits.cpp +++ b/Source/DiabloUI/credits.cpp @@ -36,7 +36,7 @@ class CreditsRenderer { CreditsRenderer(char const *const *text, std::size_t textLines) { for (size_t i = 0; i < textLines; i++) { - string_view orgText = _(text[i]); + std::string_view orgText = _(text[i]); uint16_t offset = 0; size_t indexFirstNotTab = 0; @@ -115,7 +115,7 @@ void CreditsRenderer::Render() ScaleOutputRect(&viewport); // We use unscaled coordinates for calculation throughout. - Sint16 destY = uiPosition.y + VIEWPORT.y - (offsetY - linesBegin * LINE_H); + Sint16 destY = static_cast(uiPosition.y + VIEWPORT.y - (offsetY - linesBegin * LINE_H)); for (std::size_t i = linesBegin; i < linesEnd; ++i, destY += LINE_H) { Sint16 destX = uiPosition.x + VIEWPORT.x + 31; @@ -127,7 +127,8 @@ void CreditsRenderer::Render() dstRect.y -= viewport.y; const Surface &out = Surface(DiabloUiSurface(), viewport); - DrawString(out, lineContent.text, Point { dstRect.x, dstRect.y }, UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite, -1); + DrawString(out, lineContent.text, Point { dstRect.x, dstRect.y }, + { .flags = UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite, .spacing = -1 }); } } diff --git a/Source/DiabloUI/diabloui.cpp b/Source/DiabloUI/diabloui.cpp index 3754049aa03..753b7779156 100644 --- a/Source/DiabloUI/diabloui.cpp +++ b/Source/DiabloUI/diabloui.cpp @@ -2,11 +2,14 @@ #include #include +#include +#include #include #include "DiabloUI/button.h" #include "DiabloUI/dialogs.h" #include "DiabloUI/scrollbar.h" +#include "DiabloUI/text_input.hpp" #include "controls/controller.h" #include "controls/input.h" #include "controls/menu_controls.h" @@ -19,12 +22,15 @@ #include "engine/load_pcx.hpp" #include "engine/render/clx_render.hpp" #include "hwcursor.hpp" +#include "utils/algorithm/container.hpp" #include "utils/display.h" #include "utils/language.h" #include "utils/log.hpp" #include "utils/pcx_to_clx.hpp" +#include "utils/screen_reader.hpp" #include "utils/sdl_compat.h" #include "utils/sdl_geometry.h" +#include "utils/sdl_ptrs.h" #include "utils/sdl_wrap.h" #include "utils/str_cat.hpp" #include "utils/stubs.h" @@ -54,7 +60,6 @@ OptionalOwnedClxSpriteList ArtBackgroundWidescreen; OptionalOwnedClxSpriteList ArtBackground; OptionalOwnedClxSpriteList ArtCursor; -bool textInputActive = true; std::size_t SelectedItem = 0; namespace { @@ -67,16 +72,16 @@ std::size_t SelectedItemMax; std::size_t ListViewportSize = 1; std::size_t listOffset = 0; -void (*gfnListFocus)(int value); -void (*gfnListSelect)(int value); +void (*gfnListFocus)(size_t value); +void (*gfnListSelect)(size_t value); void (*gfnListEsc)(); void (*gfnFullscreen)(); bool (*gfnListYesNo)(); std::vector gUiItems; UiList *gUiList = nullptr; bool UiItemsWraps; -char *UiTextInput; -int UiTextInputLen; + +std::optional UiTextInputState; bool allowEmptyTextInput = false; uint32_t fadeTc; @@ -103,7 +108,12 @@ void AdjustListOffset(std::size_t itemIndex) } // namespace -void UiInitList(void (*fnFocus)(int value), void (*fnSelect)(int value), void (*fnEsc)(), const std::vector> &items, bool itemsWraps, void (*fnFullscreen)(), bool (*fnYesNo)(), size_t selectedItem /*= 0*/) +bool IsTextInputActive() +{ + return UiTextInputState.has_value(); +} + +void UiInitList(void (*fnFocus)(size_t value), void (*fnSelect)(size_t value), void (*fnEsc)(), const std::vector> &items, bool itemsWraps, void (*fnFullscreen)(), bool (*fnYesNo)(), size_t selectedItem /*= 0*/) { SelectedItem = selectedItem; SelectedItemMax = 0; @@ -124,13 +134,11 @@ void UiInitList(void (*fnFocus)(int value), void (*fnSelect)(int value), void (* #ifndef __SWITCH__ SDL_StopTextInput(); // input is enabled by default #endif - textInputActive = false; UiScrollbar *uiScrollbar = nullptr; for (const auto &item : items) { if (item->IsType(UiType::Edit)) { auto *pItemUIEdit = static_cast(item.get()); SDL_SetTextInputRect(&item->m_rect); - textInputActive = true; allowEmptyTextInput = pItemUIEdit->m_allowEmpty; #ifdef __SWITCH__ switch_start_text_input(pItemUIEdit->m_hint, pItemUIEdit->m_value, pItemUIEdit->m_max_length); @@ -141,8 +149,11 @@ void UiInitList(void (*fnFocus)(int value), void (*fnSelect)(int value), void (* #else SDL_StartTextInput(); #endif - UiTextInput = pItemUIEdit->m_value; - UiTextInputLen = pItemUIEdit->m_max_length; + UiTextInputState.emplace(TextInputState::Options { + .value = pItemUIEdit->m_value, + .cursor = &pItemUIEdit->m_cursor, + .maxLength = pItemUIEdit->m_max_length, + }); } else if (item->IsType(UiType::List)) { auto *uiList = static_cast(item.get()); SelectedItemMax = std::max(uiList->m_vecItems.size() - 1, static_cast(0)); @@ -150,6 +161,7 @@ void UiInitList(void (*fnFocus)(int value), void (*fnSelect)(int value), void (* gUiList = uiList; if (selectedItem <= SelectedItemMax && HasAnyOf(uiList->GetItem(selectedItem)->uiFlags, UiFlags::NeedsNextElement)) AdjustListOffset(selectedItem + 1); + SpeakText(uiList->GetItem(selectedItem)->m_text); } else if (item->IsType(UiType::Scrollbar)) { uiScrollbar = static_cast(item.get()); } @@ -188,12 +200,12 @@ void UiInitList_clear() void UiPlayMoveSound() { - effects_play_sound(IS_TITLEMOV); + effects_play_sound(SfxID::MenuMove); } void UiPlaySelectSound() { - effects_play_sound(IS_TITLSLCT); + effects_play_sound(SfxID::MenuSelect); } namespace { @@ -224,6 +236,7 @@ void UiFocus(std::size_t itemIndex, bool checkUp, bool ignoreItemsWraps = false) } pItem = gUiList->GetItem(itemIndex); } + SpeakText(pItem->m_text); if (HasAnyOf(pItem->uiFlags, UiFlags::NeedsNextElement)) AdjustListOffset(itemIndex + 1); @@ -287,14 +300,6 @@ void UiFocusPageDown() } } -void SelheroCatToName(const char *inBuf, char *outBuf, int cnt) -{ - size_t outLen = strlen(outBuf); - char *dest = outBuf + outLen; - size_t destCount = cnt - outLen; - CopyUtf8(dest, inBuf, destCount); -} - bool HandleMenuAction(MenuAction menuAction) { switch (menuAction) { @@ -398,53 +403,8 @@ void UiFocusNavigation(SDL_Event *event) } #endif - if (textInputActive) { - switch (event->type) { - case SDL_KEYDOWN: { - switch (event->key.keysym.sym) { -#ifndef USE_SDL1 - case SDLK_v: - if ((SDL_GetModState() & KMOD_CTRL) != 0) { - char *clipboard = SDL_GetClipboardText(); - if (clipboard == nullptr) { - Log("{}", SDL_GetError()); - } else { - SelheroCatToName(clipboard, UiTextInput, UiTextInputLen); - } - } - return; -#endif - case SDLK_BACKSPACE: - case SDLK_LEFT: { - UiTextInput[FindLastUtf8Symbols(UiTextInput)] = '\0'; - return; - } - default: - break; - } -#ifdef USE_SDL1 - if ((event->key.keysym.mod & KMOD_CTRL) == 0) { - std::string utf8; - AppendUtf8(event->key.keysym.unicode, utf8); - SelheroCatToName(utf8.c_str(), UiTextInput, UiTextInputLen); - } -#endif - break; - } -#ifndef USE_SDL1 - case SDL_TEXTINPUT: - if (textInputActive) { -#ifdef __vita__ - CopyUtf8(UiTextInput, event->text.text, UiTextInputLen); -#else - SelheroCatToName(event->text.text, UiTextInput, UiTextInputLen); -#endif - } - return; -#endif - default: - break; - } + if (UiTextInputState.has_value() && HandleTextInputEvent(*event, *UiTextInputState)) { + return; } if (event->type == SDL_MOUSEBUTTONDOWN || event->type == SDL_MOUSEBUTTONUP) { @@ -511,15 +471,14 @@ void UiHandleEvents(SDL_Event *event) void UiFocusNavigationSelect() { UiPlaySelectSound(); - if (textInputActive) { - if (!allowEmptyTextInput && strlen(UiTextInput) == 0) { + if (UiTextInputState.has_value()) { + if (!allowEmptyTextInput && UiTextInputState->empty()) { return; } #ifndef __SWITCH__ SDL_StopTextInput(); #endif - UiTextInput = nullptr; - UiTextInputLen = 0; + UiTextInputState = std::nullopt; } if (gfnListSelect != nullptr) gfnListSelect(SelectedItem); @@ -528,12 +487,11 @@ void UiFocusNavigationSelect() void UiFocusNavigationEsc() { UiPlaySelectSound(); - if (textInputActive) { + if (UiTextInputState.has_value()) { #ifndef __SWITCH__ SDL_StopTextInput(); #endif - UiTextInput = nullptr; - UiTextInputLen = 0; + UiTextInputState = std::nullopt; } if (gfnListEsc != nullptr) gfnListEsc(); @@ -636,7 +594,7 @@ void UiDestroy() UnloadUiGFX(); } -bool UiValidPlayerName(string_view name) +bool UiValidPlayerName(std::string_view name) { if (name.empty()) return false; @@ -651,10 +609,10 @@ bool UiValidPlayerName(string_view name) // Only basic latin alphabet is supported for multiplayer characters to avoid rendering issues for players who do // not have fonts.mpq installed - if (!std::all_of(name.begin(), name.end(), IsBasicLatin)) + if (!c_all_of(name, IsBasicLatin)) return false; - string_view bannedNames[] = { + std::string_view bannedNames[] = { "gvdl", "dvou", "tiju", @@ -669,8 +627,8 @@ bool UiValidPlayerName(string_view name) for (char &character : buffer) character++; - string_view tempName { buffer }; - for (string_view bannedName : bannedNames) { + std::string_view tempName { buffer }; + for (std::string_view bannedName : bannedNames) { if (tempName.find(bannedName) != tempName.npos) return false; } @@ -810,13 +768,15 @@ namespace { void Render(const UiText &uiText) { const Surface &out = Surface(DiabloUiSurface()); - DrawString(out, uiText.GetText(), MakeRectangle(uiText.m_rect), uiText.GetFlags() | UiFlags::FontSizeDialog); + DrawString(out, uiText.GetText(), MakeRectangle(uiText.m_rect), + { .flags = uiText.GetFlags() | UiFlags::FontSizeDialog }); } void Render(const UiArtText &uiArtText) { const Surface &out = Surface(DiabloUiSurface()); - DrawString(out, uiArtText.GetText(), MakeRectangle(uiArtText.m_rect), uiArtText.GetFlags(), uiArtText.GetSpacing(), uiArtText.GetLineHeight()); + DrawString(out, uiArtText.GetText(), MakeRectangle(uiArtText.m_rect), + { .flags = uiArtText.GetFlags(), .spacing = uiArtText.GetSpacing(), .lineHeight = uiArtText.GetLineHeight() }); } void Render(const UiImageClx &uiImage) @@ -842,7 +802,7 @@ void Render(const UiImageAnimatedClx &uiImage) void Render(const UiArtTextButton &uiButton) { const Surface &out = Surface(DiabloUiSurface()); - DrawString(out, uiButton.GetText(), MakeRectangle(uiButton.m_rect), uiButton.GetFlags()); + DrawString(out, uiButton.GetText(), MakeRectangle(uiButton.m_rect), { .flags = uiButton.GetFlags() }); } void Render(const UiList &uiList) @@ -850,16 +810,19 @@ void Render(const UiList &uiList) const Surface &out = Surface(DiabloUiSurface()); for (std::size_t i = listOffset; i < uiList.m_vecItems.size() && (i - listOffset) < ListViewportSize; ++i) { - SDL_Rect rect = uiList.itemRect(i - listOffset); + SDL_Rect rect = uiList.itemRect(static_cast(i - listOffset)); const UiListItem &item = *uiList.GetItem(i); if (i == SelectedItem) DrawSelector(rect); Rectangle rectangle = MakeRectangle(rect); - if (item.args.empty()) - DrawString(out, item.m_text, rectangle, uiList.GetFlags() | item.uiFlags, uiList.GetSpacing()); - else - DrawStringWithColors(out, item.m_text, item.args, rectangle, uiList.GetFlags() | item.uiFlags, uiList.GetSpacing()); + if (item.args.empty()) { + DrawString(out, item.m_text, rectangle, + { .flags = uiList.GetFlags() | item.uiFlags, .spacing = uiList.GetSpacing() }); + } else { + DrawStringWithColors(out, item.m_text, item.args, rectangle, + { .flags = uiList.GetFlags() | item.uiFlags, .spacing = uiList.GetSpacing() }); + } } } @@ -906,7 +869,13 @@ void Render(const UiEdit &uiEdit) Rectangle rect = MakeRectangle(uiEdit.m_rect).inset({ 43, 1 }); const Surface &out = Surface(DiabloUiSurface()); - DrawString(out, uiEdit.m_value, rect, uiEdit.GetFlags() | UiFlags::TextCursor); + DrawString(out, uiEdit.m_value, rect, + { + .flags = uiEdit.GetFlags(), + .cursorPosition = static_cast(uiEdit.m_cursor.position), + .highlightRange = { static_cast(uiEdit.m_cursor.selection.begin), static_cast(uiEdit.m_cursor.selection.end) }, + .highlightColor = 126, + }); } bool HandleMouseEventArtTextButton(const SDL_Event &event, const UiArtTextButton *uiButton) diff --git a/Source/DiabloUI/diabloui.h b/Source/DiabloUI/diabloui.h index 8ba6f0b10b5..a14ef3a74b0 100644 --- a/Source/DiabloUI/diabloui.h +++ b/Source/DiabloUI/diabloui.h @@ -1,10 +1,11 @@ #pragma once -#include #include #include #include +#include +#include #include #include "DiabloUI/ui_item.h" @@ -12,12 +13,12 @@ #include "engine/load_pcx.hpp" // IWYU pragma: export #include "player.h" #include "utils/display.h" -#include "utils/stdcompat/optional.hpp" namespace devilution { extern std::size_t SelectedItem; -extern bool textInputActive; + +bool IsTextInputActive(); enum _artFocus : uint8_t { FOCUS_SMALL, @@ -82,9 +83,9 @@ void UiDestroy(); void UiTitleDialog(); void UnloadUiGFX(); void UiInitialize(); -bool UiValidPlayerName(string_view name); /* check */ -void UiSelHeroMultDialog(bool (*fninfo)(bool (*fninfofunc)(_uiheroinfo *)), bool (*fncreate)(_uiheroinfo *), bool (*fnremove)(_uiheroinfo *), void (*fnstats)(unsigned int, _uidefaultstats *), _selhero_selections *dlgresult, uint32_t *saveNumber); -void UiSelHeroSingDialog(bool (*fninfo)(bool (*fninfofunc)(_uiheroinfo *)), bool (*fncreate)(_uiheroinfo *), bool (*fnremove)(_uiheroinfo *), void (*fnstats)(unsigned int, _uidefaultstats *), _selhero_selections *dlgresult, uint32_t *saveNumber, _difficulty *difficulty); +bool UiValidPlayerName(std::string_view name); /* check */ +void UiSelHeroMultDialog(bool (*fninfo)(bool (*fninfofunc)(_uiheroinfo *)), bool (*fncreate)(_uiheroinfo *), bool (*fnremove)(_uiheroinfo *), void (*fnstats)(HeroClass, _uidefaultstats *), _selhero_selections *dlgresult, uint32_t *saveNumber); +void UiSelHeroSingDialog(bool (*fninfo)(bool (*fninfofunc)(_uiheroinfo *)), bool (*fncreate)(_uiheroinfo *), bool (*fnremove)(_uiheroinfo *), void (*fnstats)(HeroClass, _uidefaultstats *), _selhero_selections *dlgresult, uint32_t *saveNumber, _difficulty *difficulty); bool UiCreditsDialog(); bool UiSupportDialog(); bool UiMainMenuDialog(const char *name, _mainmenu_selections *pdwResult, int attractTimeOut); @@ -107,7 +108,7 @@ void UiFocusNavigationSelect(); void UiFocusNavigationEsc(); void UiFocusNavigationYesNo(); -void UiInitList(void (*fnFocus)(int value), void (*fnSelect)(int value), void (*fnEsc)(), const std::vector> &items, bool wraps = false, void (*fnFullscreen)() = nullptr, bool (*fnYesNo)() = nullptr, size_t selectedItem = 0); +void UiInitList(void (*fnFocus)(size_t value), void (*fnSelect)(size_t value), void (*fnEsc)(), const std::vector> &items, bool wraps = false, void (*fnFullscreen)() = nullptr, bool (*fnYesNo)() = nullptr, size_t selectedItem = 0); void UiRenderListItems(); void UiInitList_clear(); diff --git a/Source/DiabloUI/dialogs.cpp b/Source/DiabloUI/dialogs.cpp index b351aafd2b2..ed1b3e9ecbd 100644 --- a/Source/DiabloUI/dialogs.cpp +++ b/Source/DiabloUI/dialogs.cpp @@ -1,6 +1,7 @@ #include "DiabloUI/dialogs.h" #include +#include #include #include "DiabloUI/button.h" @@ -17,7 +18,6 @@ #include "utils/display.h" #include "utils/language.h" #include "utils/log.hpp" -#include "utils/stdcompat/string_view.hpp" namespace devilution { @@ -54,7 +54,7 @@ OptionalClxSprite LoadDialogSprite(bool hasCaption, bool isError) return (*ownedDialogSprite)[0]; } -bool Init(string_view caption, string_view text, bool error, bool renderBehind) +bool Init(std::string_view caption, std::string_view text, bool error, bool renderBehind) { if (!renderBehind) { if (!UiLoadBlackBackground()) { @@ -137,7 +137,7 @@ void DialogLoop(const std::vector> &items, const std } while (!dialogEnd); } -void UiOkDialog(string_view caption, string_view text, bool error, const std::vector> &renderBehind) +void UiOkDialog(std::string_view caption, std::string_view text, bool error, const std::vector> &renderBehind) { static bool inDialog = false; @@ -183,17 +183,17 @@ void UiOkDialog(string_view caption, string_view text, bool error, const std::ve } // namespace -void UiErrorOkDialog(string_view caption, string_view text, const std::vector> &renderBehind) +void UiErrorOkDialog(std::string_view caption, std::string_view text, const std::vector> &renderBehind) { UiOkDialog(caption, text, /*error=*/true, renderBehind); } -void UiErrorOkDialog(string_view caption, string_view text, bool error) +void UiErrorOkDialog(std::string_view caption, std::string_view text, bool error) { UiOkDialog(caption, text, error, vecNULL); } -void UiErrorOkDialog(string_view text, const std::vector> &renderBehind) +void UiErrorOkDialog(std::string_view text, const std::vector> &renderBehind) { UiErrorOkDialog({}, text, renderBehind); } diff --git a/Source/DiabloUI/dialogs.h b/Source/DiabloUI/dialogs.h index 6f3d4cec177..278ab714274 100644 --- a/Source/DiabloUI/dialogs.h +++ b/Source/DiabloUI/dialogs.h @@ -1,13 +1,13 @@ #pragma once #include +#include #include "DiabloUI/ui_item.h" -#include "utils/stdcompat/string_view.hpp" namespace devilution { -void UiErrorOkDialog(string_view text, const std::vector> &renderBehind); -void UiErrorOkDialog(string_view caption, string_view text, const std::vector> &renderBehind); +void UiErrorOkDialog(std::string_view text, const std::vector> &renderBehind); +void UiErrorOkDialog(std::string_view caption, std::string_view text, const std::vector> &renderBehind); } // namespace devilution diff --git a/Source/DiabloUI/hero/selhero.cpp b/Source/DiabloUI/hero/selhero.cpp index 0c1c1069eb3..70f121795cb 100644 --- a/Source/DiabloUI/hero/selhero.cpp +++ b/Source/DiabloUI/hero/selhero.cpp @@ -29,7 +29,7 @@ bool selhero_isMultiPlayer; bool (*gfnHeroInfo)(bool (*fninfofunc)(_uiheroinfo *)); bool (*gfnHeroCreate)(_uiheroinfo *); -void (*gfnHeroStats)(unsigned int, _uidefaultstats *); +void (*gfnHeroStats)(HeroClass, _uidefaultstats *); namespace { @@ -48,15 +48,15 @@ std::vector> vecSelDlgItems; UiImageClx *SELHERO_DIALOG_HERO_IMG; -void SelheroListFocus(int value); -void SelheroListSelect(int value); +void SelheroListFocus(size_t value); +void SelheroListSelect(size_t value); void SelheroListEsc(); -void SelheroLoadFocus(int value); -void SelheroLoadSelect(int value); -void SelheroNameSelect(int value); +void SelheroLoadFocus(size_t value); +void SelheroLoadSelect(size_t value); +void SelheroNameSelect(size_t value); void SelheroNameEsc(); -void SelheroClassSelectorFocus(int value); -void SelheroClassSelectorSelect(int value); +void SelheroClassSelectorFocus(size_t value); +void SelheroClassSelectorSelect(size_t value); void SelheroClassSelectorEsc(); const char *SelheroGenerateName(HeroClass heroClass); @@ -118,12 +118,11 @@ bool SelHeroGetHeroInfo(_uiheroinfo *pInfo) return true; } -void SelheroListFocus(int value) +void SelheroListFocus(size_t value) { - const auto index = static_cast(value); UiFlags baseFlags = UiFlags::AlignCenter | UiFlags::FontSize30; - if (selhero_SaveCount != 0 && index < selhero_SaveCount) { - memcpy(&selhero_heroInfo, &selhero_heros[index], sizeof(selhero_heroInfo)); + if (selhero_SaveCount != 0 && value < selhero_SaveCount) { + memcpy(&selhero_heroInfo, &selhero_heros[value], sizeof(selhero_heroInfo)); SelheroSetStats(); SELLIST_DIALOG_DELETE_BUTTON->SetFlags(baseFlags | UiFlags::ColorUiGold); selhero_isSavegame = true; @@ -144,7 +143,7 @@ bool SelheroListDeleteYesNo() return selhero_navigateYesNo; } -void SelheroListSelect(int value) +void SelheroListSelect(size_t value) { const Point uiPosition = GetUIRectangle().position; @@ -161,16 +160,16 @@ void SelheroListSelect(int value) vecSelHeroDlgItems.push_back(std::make_unique(_("Sorcerer"), static_cast(HeroClass::Sorcerer))); if (gbIsHellfire) { vecSelHeroDlgItems.push_back(std::make_unique(_("Monk"), static_cast(HeroClass::Monk))); - } - if (gbBard || *sgOptions.Gameplay.testBard) { - vecSelHeroDlgItems.push_back(std::make_unique(_("Bard"), static_cast(HeroClass::Bard))); - } - if (gbBarbarian || *sgOptions.Gameplay.testBarbarian) { - vecSelHeroDlgItems.push_back(std::make_unique(_("Barbarian"), static_cast(HeroClass::Barbarian))); + if (gbBard || *sgOptions.Gameplay.testBard) { + vecSelHeroDlgItems.push_back(std::make_unique(_("Bard"), static_cast(HeroClass::Bard))); + } + if (gbBarbarian || *sgOptions.Gameplay.testBarbarian) { + vecSelHeroDlgItems.push_back(std::make_unique(_("Barbarian"), static_cast(HeroClass::Barbarian))); + } } if (vecSelHeroDlgItems.size() > 4) itemH = 26; - int itemY = 246 + (176 - vecSelHeroDlgItems.size() * itemH) / 2; + int itemY = static_cast(246 + (176 - vecSelHeroDlgItems.size() * itemH) / 2); vecSelDlgItems.push_back(std::make_unique(vecSelHeroDlgItems, vecSelHeroDlgItems.size(), uiPosition.x + 264, (uiPosition.y + itemY), 320, itemH, UiFlags::AlignCenter | UiFlags::FontSize24 | UiFlags::ColorUiGold)); SDL_Rect rect2 = { (Sint16)(uiPosition.x + 279), (Sint16)(uiPosition.y + 429), 140, 35 }; @@ -221,12 +220,12 @@ void SelheroListEsc() selhero_result = SELHERO_PREVIOUS; } -void SelheroClassSelectorFocus(int value) +void SelheroClassSelectorFocus(size_t value) { const auto heroClass = static_cast(vecSelHeroDlgItems[value]->m_value); _uidefaultstats defaults; - gfnHeroStats(static_cast(heroClass), &defaults); + gfnHeroStats(heroClass, &defaults); selhero_heroInfo.level = 1; selhero_heroInfo.heroclass = heroClass; @@ -260,7 +259,7 @@ void AddSelHeroBackground() std::make_unique((*ArtBackground)[0], MakeSdlRect(0, GetUIRectangle().position.y, 0, 0), UiFlags::AlignCenter)); } -void SelheroClassSelectorSelect(int value) +void SelheroClassSelectorSelect(size_t value) { auto hClass = static_cast(vecSelHeroDlgItems[value]->m_value); if (gbIsSpawn && (hClass == HeroClass::Rogue || hClass == HeroClass::Sorcerer || (hClass == HeroClass::Bard && !gbBard))) { @@ -306,7 +305,7 @@ void SelheroClassSelectorEsc() SelheroListEsc(); } -void SelheroNameSelect(int /*value*/) +void SelheroNameSelect(size_t /*value*/) { // only check names in multiplayer, we don't care about them in single if (selhero_isMultiPlayer && !UiValidPlayerName(selhero_heroInfo.name)) { @@ -330,11 +329,11 @@ void SelheroNameEsc() SelheroListSelect(selhero_SaveCount); } -void SelheroLoadFocus(int value) +void SelheroLoadFocus(size_t value) { } -void SelheroLoadSelect(int value) +void SelheroLoadSelect(size_t value) { UiInitList_clear(); selhero_endMenu = true; @@ -546,7 +545,7 @@ void selhero_List_Init() static void UiSelHeroDialog( bool (*fninfo)(bool (*fninfofunc)(_uiheroinfo *)), bool (*fncreate)(_uiheroinfo *), - void (*fnstats)(unsigned int, _uidefaultstats *), + void (*fnstats)(HeroClass, _uidefaultstats *), bool (*fnremove)(_uiheroinfo *), _selhero_selections *dlgresult, uint32_t *saveNumber) @@ -607,7 +606,7 @@ void UiSelHeroSingDialog( bool (*fninfo)(bool (*fninfofunc)(_uiheroinfo *)), bool (*fncreate)(_uiheroinfo *), bool (*fnremove)(_uiheroinfo *), - void (*fnstats)(unsigned int, _uidefaultstats *), + void (*fnstats)(HeroClass, _uidefaultstats *), _selhero_selections *dlgresult, uint32_t *saveNumber, _difficulty *difficulty) @@ -621,7 +620,7 @@ void UiSelHeroMultDialog( bool (*fninfo)(bool (*fninfofunc)(_uiheroinfo *)), bool (*fncreate)(_uiheroinfo *), bool (*fnremove)(_uiheroinfo *), - void (*fnstats)(unsigned int, _uidefaultstats *), + void (*fnstats)(HeroClass, _uidefaultstats *), _selhero_selections *dlgresult, uint32_t *saveNumber) { diff --git a/Source/DiabloUI/mainmenu.cpp b/Source/DiabloUI/mainmenu.cpp index 2bcdf0f2c95..f2592038615 100644 --- a/Source/DiabloUI/mainmenu.cpp +++ b/Source/DiabloUI/mainmenu.cpp @@ -16,7 +16,7 @@ std::vector> vecMenuItems; _mainmenu_selections MainMenuResult; -void UiMainMenuSelect(int value) +void UiMainMenuSelect(size_t value) { MainMenuResult = (_mainmenu_selections)vecMenuItems[value]->m_value; } diff --git a/Source/DiabloUI/multi/selconn.cpp b/Source/DiabloUI/multi/selconn.cpp index 9c4a6253c52..460f7fc3ac8 100644 --- a/Source/DiabloUI/multi/selconn.cpp +++ b/Source/DiabloUI/multi/selconn.cpp @@ -30,8 +30,8 @@ std::vector> vecSelConnDlg; #define DESCRIPTION_WIDTH 205 void SelconnEsc(); -void SelconnFocus(int value); -void SelconnSelect(int value); +void SelconnFocus(size_t value); +void SelconnSelect(size_t value); void SelconnLoad() { @@ -102,7 +102,7 @@ void SelconnEsc() selconn_EndMenu = true; } -void SelconnFocus(int value) +void SelconnFocus(size_t value) { int players = MAX_PLRS; switch (vecConnItems[value]->m_value) { @@ -124,7 +124,7 @@ void SelconnFocus(int value) CopyUtf8(selconn_Description, WordWrapString(selconn_Description, DESCRIPTION_WIDTH), sizeof(selconn_Description)); } -void SelconnSelect(int value) +void SelconnSelect(size_t value) { provider = vecConnItems[value]->m_value; diff --git a/Source/DiabloUI/multi/selgame.cpp b/Source/DiabloUI/multi/selgame.cpp index 120ff7b4945..eff484ae666 100644 --- a/Source/DiabloUI/multi/selgame.cpp +++ b/Source/DiabloUI/multi/selgame.cpp @@ -26,7 +26,7 @@ char selgame_Password[16] = ""; char selgame_Description[512]; std::string selgame_Title; bool selgame_enteringGame; -int selgame_selectedGame; +size_t selgame_selectedGame; bool selgame_endMenu; int *gdwPlayerId; _difficulty nDifficulty; @@ -46,7 +46,7 @@ std::vector> vecSelGameDlgItems; std::vector> vecSelGameDialog; std::vector Gamelist; uint32_t firstPublicGameInfoRequestSend = 0; -unsigned HighlightedItem; +size_t HighlightedItem; void selgame_FreeVectors() { @@ -80,7 +80,7 @@ bool IsGameCompatible(const GameData &data) static std::string GetErrorMessageIncompatibility(const GameData &data) { if (data.programid != GAME_ID) { - string_view gameMode; + std::string_view gameMode; switch (data.programid) { case GameIdDiabloFull: gameMode = _("Diablo"); @@ -103,7 +103,7 @@ static std::string GetErrorMessageIncompatibility(const GameData &data) } } -void UiInitGameSelectionList(string_view search) +void UiInitGameSelectionList(std::string_view search) { selgame_enteringGame = false; selgame_selectedGame = 0; @@ -122,6 +122,8 @@ void UiInitGameSelectionList(string_view search) selgame_FreeVectors(); + selgame_Label[0] = '\0'; + UiAddBackground(&vecSelGameDialog); UiAddLogo(&vecSelGameDialog); @@ -173,7 +175,7 @@ void UiInitGameSelectionList(string_view search) SDL_Rect rect6 = { (Sint16)(uiPosition.x + 449), (Sint16)(uiPosition.y + 427), 140, 35 }; vecSelGameDialog.push_back(std::make_unique(_("CANCEL"), &UiFocusNavigationEsc, rect6, UiFlags::AlignCenter | UiFlags::VerticalCenter | UiFlags::FontSize30 | UiFlags::ColorUiGold)); - auto selectFn = [](int index) { + auto selectFn = [](size_t index) { // UiListItem::m_value could be different from // the index if packet encryption is disabled int itemValue = vecSelGameDlgItems[index]->m_value; @@ -181,7 +183,7 @@ void UiInitGameSelectionList(string_view search) }; if (!search.empty()) { - for (unsigned i = 0; i < vecSelGameDlgItems.size(); i++) { + for (size_t i = 0; i < vecSelGameDlgItems.size(); i++) { int gameIndex = vecSelGameDlgItems[i]->m_value - 3; if (gameIndex < 0) continue; @@ -204,11 +206,10 @@ void selgame_GameSelection_Init() UiInitGameSelectionList(""); } -void selgame_GameSelection_Focus(int value) +void selgame_GameSelection_Focus(size_t value) { - const auto index = static_cast(value); - HighlightedItem = index; - const UiListItem &item = *vecSelGameDlgItems[index]; + HighlightedItem = value; + const UiListItem &item = *vecSelGameDlgItems[value]; switch (item.m_value) { case 0: CopyUtf8(selgame_Description, _("Create a new game with a difficulty setting of your choice."), sizeof(selgame_Description)); @@ -228,7 +229,7 @@ void selgame_GameSelection_Focus(int value) std::string infoString = std::string(_("Join the public game already in progress.")); infoString.append("\n\n"); if (IsGameCompatible(gameInfo.gameData)) { - string_view difficulty; + std::string_view difficulty; switch (gameInfo.gameData.nDifficulty) { case DIFF_NORMAL: difficulty = _("Normal"); @@ -244,16 +245,16 @@ void selgame_GameSelection_Focus(int value) infoString += '\n'; switch (gameInfo.gameData.nTickRate) { case 20: - AppendStrView(infoString, _("Speed: Normal")); + infoString.append(_("Speed: Normal")); break; case 30: - AppendStrView(infoString, _("Speed: Fast")); + infoString.append(_("Speed: Fast")); break; case 40: - AppendStrView(infoString, _("Speed: Faster")); + infoString.append(_("Speed: Faster")); break; case 50: - AppendStrView(infoString, _("Speed: Fastest")); + infoString.append(_("Speed: Fastest")); break; default: // This should not occure, so no translations is needed @@ -261,7 +262,7 @@ void selgame_GameSelection_Focus(int value) break; } infoString += '\n'; - AppendStrView(infoString, _("Players: ")); + infoString.append(_("Players: ")); for (auto &playerName : gameInfo.players) { infoString.append(playerName); infoString += ' '; @@ -288,7 +289,7 @@ bool UpdateHeroLevel(_uiheroinfo *pInfo) return true; } -void selgame_GameSelection_Select(int value) +void selgame_GameSelection_Select(size_t value) { selgame_enteringGame = true; selgame_selectedGame = value; @@ -382,7 +383,7 @@ void selgame_GameSelection_Esc() selgame_endMenu = true; } -void selgame_Diff_Focus(int value) +void selgame_Diff_Focus(size_t value) { switch (vecSelGameDlgItems[value]->m_value) { case DIFF_NORMAL: @@ -419,10 +420,10 @@ bool IsDifficultyAllowed(int value) return false; } -void selgame_Diff_Select(int value) +void selgame_Diff_Select(size_t value) { if (selhero_isMultiPlayer && !IsDifficultyAllowed(vecSelGameDlgItems[value]->m_value)) { - selgame_GameSelection_Select(0); + selgame_GameSelection_Select(selgame_selectedGame); return; } @@ -508,7 +509,7 @@ void selgame_GameSpeedSelection() UiInitList(selgame_Speed_Focus, selgame_Speed_Select, selgame_Speed_Esc, vecSelGameDialog, true); } -void selgame_Speed_Focus(int value) +void selgame_Speed_Focus(size_t value) { switch (vecSelGameDlgItems[value]->m_value) { case 20: @@ -533,10 +534,10 @@ void selgame_Speed_Focus(int value) void selgame_Speed_Esc() { - selgame_GameSelection_Select(0); + selgame_GameSelection_Select(selgame_selectedGame); } -void selgame_Speed_Select(int value) +void selgame_Speed_Select(size_t value) { nTickRate = vecSelGameDlgItems[value]->m_value; @@ -548,7 +549,7 @@ void selgame_Speed_Select(int value) selgame_Password_Init(0); } -void selgame_Password_Init(int /*value*/) +void selgame_Password_Init(size_t /*value*/) { memset(&selgame_Password, 0, sizeof(selgame_Password)); @@ -600,7 +601,7 @@ static bool IsGameCompatibleWithErrorMessage(const GameData &data) return false; } -void selgame_Password_Select(int /*value*/) +void selgame_Password_Select(size_t /*value*/) { char *gamePassword = nullptr; if (selgame_selectedGame == 0) diff --git a/Source/DiabloUI/multi/selgame.h b/Source/DiabloUI/multi/selgame.h index 17d2ecff060..3dece866151 100644 --- a/Source/DiabloUI/multi/selgame.h +++ b/Source/DiabloUI/multi/selgame.h @@ -7,18 +7,18 @@ namespace devilution { extern _difficulty nDifficulty; void selgame_GameSelection_Init(); -void selgame_GameSelection_Focus(int value); -void selgame_GameSelection_Select(int value); +void selgame_GameSelection_Focus(size_t value); +void selgame_GameSelection_Select(size_t value); void selgame_GameSelection_Esc(); -void selgame_Diff_Focus(int value); -void selgame_Diff_Select(int value); +void selgame_Diff_Focus(size_t value); +void selgame_Diff_Select(size_t value); void selgame_Diff_Esc(); void selgame_GameSpeedSelection(); -void selgame_Speed_Focus(int value); -void selgame_Speed_Select(int value); +void selgame_Speed_Focus(size_t value); +void selgame_Speed_Select(size_t value); void selgame_Speed_Esc(); -void selgame_Password_Init(int value); -void selgame_Password_Select(int value); +void selgame_Password_Init(size_t value); +void selgame_Password_Select(size_t value); void selgame_Password_Esc(); } // namespace devilution diff --git a/Source/DiabloUI/selok.cpp b/Source/DiabloUI/selok.cpp index 131722b8d06..81f63989acc 100644 --- a/Source/DiabloUI/selok.cpp +++ b/Source/DiabloUI/selok.cpp @@ -30,7 +30,7 @@ void selok_Free() vecSelOkDialog.clear(); } -void selok_Select(int /*value*/) +void selok_Select(size_t /*value*/) { selok_endMenu = true; } diff --git a/Source/DiabloUI/selstart.cpp b/Source/DiabloUI/selstart.cpp index 950097e1345..89e5e2220e8 100644 --- a/Source/DiabloUI/selstart.cpp +++ b/Source/DiabloUI/selstart.cpp @@ -15,7 +15,7 @@ bool endMenu; std::vector> vecDialogItems; std::vector> vecDialog; -void ItemSelected(int value) +void ItemSelected(size_t value) { auto option = static_cast(vecDialogItems[value]->m_value); sgOptions.StartUp.gameMode.SetValue(option); diff --git a/Source/DiabloUI/selyesno.cpp b/Source/DiabloUI/selyesno.cpp index 7ff62982b79..53fc9ebb873 100644 --- a/Source/DiabloUI/selyesno.cpp +++ b/Source/DiabloUI/selyesno.cpp @@ -26,7 +26,7 @@ void SelyesnoFree() vecSelYesNoDialog.clear(); } -void SelyesnoSelect(int value) +void SelyesnoSelect(size_t value) { selyesno_value = vecSelYesNoDialogItems[value]->m_value == 0; selyesno_endMenu = true; diff --git a/Source/DiabloUI/settingsmenu.cpp b/Source/DiabloUI/settingsmenu.cpp index 8fe7bd1290a..3e6be08b1a5 100644 --- a/Source/DiabloUI/settingsmenu.cpp +++ b/Source/DiabloUI/settingsmenu.cpp @@ -1,6 +1,7 @@ #include "selstart.h" #include +#include #include @@ -14,7 +15,6 @@ #include "hwcursor.hpp" #include "options.h" #include "utils/language.h" -#include "utils/stdcompat/optional.hpp" #include "utils/utf8.hpp" namespace devilution { @@ -159,7 +159,7 @@ void UpdateDescription(const OptionCategoryBase &category) CopyUtf8(optionDescription, paragraphs, sizeof(optionDescription)); } -void ItemFocused(int value) +void ItemFocused(size_t value) { switch (shownMenu) { case ShownMenuType::Categories: { @@ -219,10 +219,9 @@ bool ChangeOptionValue(OptionEntryBase *pOption, size_t listIndex) return true; } -void ItemSelected(int value) +void ItemSelected(size_t value) { - const auto index = static_cast(value); - auto &vecItem = vecDialogItems[index]; + auto &vecItem = vecDialogItems[value]; int vecItemValue = vecItem->m_value; if (vecItemValue < 0) { auto specialMenuEntry = static_cast(vecItemValue); @@ -285,7 +284,7 @@ void ItemSelected(int value) } if (updateValueDescription) { auto args = CreateDrawStringFormatArgForEntry(pOption); - bool optionUsesTwoLines = ((index + 1) < vecDialogItems.size() && vecDialogItems[index]->m_value == vecDialogItems[index + 1]->m_value); + bool optionUsesTwoLines = ((value + 1) < vecDialogItems.size() && vecDialogItems[value]->m_value == vecDialogItems[value + 1]->m_value); if (NeedsTwoLinesToDisplayOption(args) != optionUsesTwoLines) { selectedOption = pOption; endMenu = true; @@ -294,7 +293,7 @@ void ItemSelected(int value) for (auto &arg : args) vecItem->args.push_back(arg); if (optionUsesTwoLines) { - vecDialogItems[index + 1]->m_text = pOption->GetValueDescription().data(); + vecDialogItems[value + 1]->m_text = pOption->GetValueDescription().data(); } } } @@ -360,7 +359,7 @@ void UiSettingsMenu() optionDescription[0] = '\0'; - string_view titleText; + std::string_view titleText; switch (shownMenu) { case ShownMenuType::Categories: titleText = _("Settings"); @@ -381,15 +380,13 @@ void UiSettingsMenu() switch (shownMenu) { case ShownMenuType::Categories: { - size_t catCount = 0; size_t catIndex = 0; - for (auto *pCategory : sgOptions.GetCategories()) { - for (auto *pEntry : pCategory->GetEntries()) { + for (OptionCategoryBase *pCategory : sgOptions.GetCategories()) { + for (OptionEntryBase *pEntry : pCategory->GetEntries()) { if (!IsValidEntry(pEntry)) continue; if (selectedCategory == pCategory) itemToSelect = vecDialogItems.size(); - catCount += 1; vecDialogItems.push_back(std::make_unique(pCategory->GetName(), static_cast(catIndex), UiFlags::ColorUiGold)); break; } @@ -397,17 +394,18 @@ void UiSettingsMenu() } } break; case ShownMenuType::Settings: { - for (auto *pEntry : selectedCategory->GetEntries()) { + for (OptionEntryBase *pEntry : selectedCategory->GetEntries()) { if (!IsValidEntry(pEntry)) continue; if (selectedOption == pEntry) itemToSelect = vecDialogItems.size(); auto formatArgs = CreateDrawStringFormatArgForEntry(pEntry); + int optionId = static_cast(vecOptions.size()); if (NeedsTwoLinesToDisplayOption(formatArgs)) { - vecDialogItems.push_back(std::make_unique("{}:", formatArgs, vecOptions.size(), UiFlags::ColorUiGold | UiFlags::NeedsNextElement)); - vecDialogItems.push_back(std::make_unique(pEntry->GetValueDescription(), vecOptions.size(), UiFlags::ColorUiSilver | UiFlags::ElementDisabled)); + vecDialogItems.push_back(std::make_unique("{}:", formatArgs, optionId, UiFlags::ColorUiGold | UiFlags::NeedsNextElement)); + vecDialogItems.push_back(std::make_unique(pEntry->GetValueDescription(), optionId, UiFlags::ColorUiSilver | UiFlags::ElementDisabled)); } else { - vecDialogItems.push_back(std::make_unique("{}: {}", formatArgs, vecOptions.size(), UiFlags::ColorUiGold)); + vecDialogItems.push_back(std::make_unique("{}: {}", formatArgs, optionId, UiFlags::ColorUiGold)); } vecOptions.push_back(pEntry); } @@ -415,7 +413,7 @@ void UiSettingsMenu() case ShownMenuType::ListOption: { auto *pOptionList = static_cast(selectedOption); for (size_t i = 0; i < pOptionList->GetListSize(); i++) { - vecDialogItems.push_back(std::make_unique(pOptionList->GetListDescription(i), i, UiFlags::ColorUiGold)); + vecDialogItems.push_back(std::make_unique(pOptionList->GetListDescription(i), static_cast(i), UiFlags::ColorUiGold)); } itemToSelect = pOptionList->GetActiveListIndex(); UpdateDescription(*pOptionList); @@ -447,6 +445,19 @@ void UiSettingsMenu() break; } break; +#if SDL_VERSION_ATLEAST(2, 0, 0) + case SDL_MOUSEWHEEL: + if (event.wheel.y > 0) { + key = MouseScrollUpButton; + } else if (event.wheel.y < 0) { + key = MouseScrollDownButton; + } else if (event.wheel.x > 0) { + key = MouseScrollLeftButton; + } else if (event.wheel.x < 0) { + key = MouseScrollRightButton; + } + break; +#endif } // Ignore unknown keys if (key == SDLK_UNKNOWN) diff --git a/Source/DiabloUI/text_input.cpp b/Source/DiabloUI/text_input.cpp new file mode 100644 index 00000000000..02fb94a3b32 --- /dev/null +++ b/Source/DiabloUI/text_input.cpp @@ -0,0 +1,205 @@ +#include "DiabloUI/text_input.hpp" + +#include +#include +#include + +#include +#include + +#ifdef USE_SDL1 +#include "utils/sdl2_to_1_2_backports.h" +#endif + +#include "utils/log.hpp" +#include "utils/parse_int.hpp" +#include "utils/sdl_ptrs.h" +#include "utils/str_cat.hpp" +#include "utils/utf8.hpp" + +namespace devilution { + +namespace { + +bool HandleInputEvent(const SDL_Event &event, TextInputState &state, + tl::function_ref typeFn, + [[maybe_unused]] tl::function_ref assignFn) +{ + const auto modState = SDL_GetModState(); + const bool isCtrl = (modState & KMOD_CTRL) != 0; + const bool isAlt = (modState & KMOD_ALT) != 0; + const bool isShift = (modState & KMOD_SHIFT) != 0; + switch (event.type) { + case SDL_KEYDOWN: { + switch (event.key.keysym.sym) { +#ifndef USE_SDL1 + case SDLK_a: + if (isCtrl) { + state.setCursorToStart(); + state.setSelectCursorToEnd(); + } + return true; + case SDLK_c: + if (isCtrl) { + const std::string selectedText { state.selectedText() }; + if (SDL_SetClipboardText(selectedText.c_str()) < 0) { + Log("{}", SDL_GetError()); + } + } + return true; + case SDLK_x: + if (isCtrl) { + const std::string selectedText { state.selectedText() }; + if (SDL_SetClipboardText(selectedText.c_str()) < 0) { + Log("{}", SDL_GetError()); + } else { + state.eraseSelection(); + } + } + return true; + case SDLK_v: + if (isCtrl) { + if (SDL_HasClipboardText() == SDL_TRUE) { + std::unique_ptr> clipboard { SDL_GetClipboardText() }; + if (clipboard == nullptr || *clipboard == '\0') { + Log("{}", SDL_GetError()); + } else { + typeFn(clipboard.get()); + } + } + } + return true; +#endif + case SDLK_BACKSPACE: + state.backspace(/*word=*/isCtrl || isAlt); + return true; + case SDLK_DELETE: + state.del(/*word=*/isCtrl || isAlt); + return true; + case SDLK_LEFT: + isShift ? state.moveSelectCursorLeft(/*word=*/isCtrl || isAlt) : state.moveCursorLeft(/*word=*/isCtrl || isAlt); + return true; + case SDLK_RIGHT: + isShift ? state.moveSelectCursorRight(/*word=*/isCtrl || isAlt) : state.moveCursorRight(/*word=*/isCtrl || isAlt); + return true; + case SDLK_HOME: + isShift ? state.setSelectCursorToStart() : state.setCursorToStart(); + return true; + case SDLK_END: + isShift ? state.setSelectCursorToEnd() : state.setCursorToEnd(); + return true; + default: + break; + } +#ifdef USE_SDL1 + if ((event.key.keysym.mod & KMOD_CTRL) == 0 && event.key.keysym.unicode >= ' ') { + std::string utf8; + AppendUtf8(event.key.keysym.unicode, utf8); + typeFn(utf8); + return true; + } +#else + // Mark events that will also trigger SDL_TEXTINPUT as handled. + return !isCtrl && !isAlt + && event.key.keysym.sym >= SDLK_SPACE && event.key.keysym.sym <= SDLK_z; +#endif + } break; +#ifndef USE_SDL1 + case SDL_TEXTINPUT: +#ifdef __vita__ + assignFn(event.text.text); +#else + typeFn(event.text.text); +#endif + return true; + case SDL_TEXTEDITING: + return true; +#endif + default: + return false; + } + return false; +} + +} // namespace + +bool HandleTextInputEvent(const SDL_Event &event, TextInputState &state) +{ + return HandleInputEvent( + event, state, [&](std::string_view str) { + state.type(str); + return true; }, + [&](std::string_view str) { + state.assign(str); + return true; + }); +} + +[[nodiscard]] int NumberInputState::value(int defaultValue) const +{ + return ParseInt(textInput_.value()).value_or(defaultValue); +} + +std::string NumberInputState::filterStr(std::string_view str, bool allowMinus) +{ + std::string result; + if (allowMinus && !str.empty() && str[0] == '-') { + str.remove_prefix(1); + result += '-'; + } + for (const char c : str) { + if (c >= '0' && c <= '9') { + result += c; + } + } + return result; +} + +void NumberInputState::type(std::string_view str) +{ + const std::string filtered = filterStr( + str, /*allowMinus=*/min_ < 0 && textInput_.cursorPosition() == 0); + if (filtered.empty()) + return; + textInput_.type(filtered); + enforceRange(); +} + +void NumberInputState::assign(std::string_view str) +{ + const std::string filtered = filterStr(str, /*allowMinus=*/min_ < 0); + if (filtered.empty()) { + textInput_.clear(); + return; + } + textInput_.assign(filtered); + enforceRange(); +} + +void NumberInputState::enforceRange() +{ + if (textInput_.empty()) + return; + ParseIntResult parsed = ParseInt(textInput_.value()); + if (parsed.has_value()) { + if (*parsed > max_) { + textInput_.assign(StrCat(max_)); + } else if (*parsed < min_) { + textInput_.assign(StrCat(min_)); + } + } +} + +bool HandleNumberInputEvent(const SDL_Event &event, NumberInputState &state) +{ + return HandleInputEvent( + event, state.textInput(), [&](std::string_view str) { + state.type(str); + return true; }, + [&](std::string_view str) { + state.assign(str); + return true; + }); +} + +} // namespace devilution diff --git a/Source/DiabloUI/text_input.hpp b/Source/DiabloUI/text_input.hpp new file mode 100644 index 00000000000..3b5a7438457 --- /dev/null +++ b/Source/DiabloUI/text_input.hpp @@ -0,0 +1,426 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "utils/utf8.hpp" + +namespace devilution { + +/** @brief A range of bytes in text. */ +struct TextRange { + size_t begin = 0; + size_t end = 0; + + [[nodiscard]] size_t size() const + { + return end - begin; + } + + [[nodiscard]] bool empty() const + { + return begin == end; + } + + void clear() + { + begin = end = 0; + } +}; + +/** + * @brief Current state of the cursor and the selection range. + */ +struct TextInputCursorState { + size_t position = 0; + TextRange selection; +}; + +/** + * @brief Manages state for a single-line text input with a cursor. + * + * The text value and the cursor position are stored externally. + */ +class TextInputState { + /** + * @brief Manages an unowned fixed size char array. + */ + struct Buffer { + Buffer(char *begin, size_t maxLength) + : buf_(begin) + , maxLength_(maxLength) + { + std::string_view str(begin); + str = TruncateUtf8(str, maxLength); + len_ = str.size(); + buf_[len_] = '\0'; + } + + [[nodiscard]] size_t size() const + { + return len_; + } + + [[nodiscard]] bool empty() const + { + return len_ == 0; + } + + Buffer &operator=(std::string_view value) + { + value = TruncateUtf8(value, maxLength_); + CopyUtf8(buf_, value, maxLength_); + len_ = value.size(); + return *this; + } + + void insert(size_t pos, std::string_view value) + { + value = truncateForInsertion(value); + std::memmove(&buf_[pos + value.size()], &buf_[pos], len_ - pos); + std::memcpy(&buf_[pos], value.data(), value.size()); + len_ += value.size(); + buf_[len_] = '\0'; + } + + void erase(size_t pos, size_t len) + { + std::memmove(&buf_[pos], &buf_[pos + len], len_ - (pos + len)); + len_ -= len; + buf_[len_] = '\0'; + } + + void clear() + { + len_ = 0; + buf_[0] = '\0'; + } + + explicit operator std::string_view() const + { + return { buf_, len_ }; + } + + private: + /** + * @brief Truncates `text` so that it would fit when inserted, + * respecting UTF-8 code point boundaries. + */ + [[nodiscard]] std::string_view truncateForInsertion(std::string_view text) const + { + return TruncateUtf8(text, maxLength_ - len_); + } + + char *buf_; // unowned + size_t maxLength_; + size_t len_; + }; + +public: + struct Options { + char *value; // unowned + TextInputCursorState *cursor; // unowned + size_t maxLength = 0; + }; + TextInputState(const Options &options) + : value_(options.value, options.maxLength) + , cursor_(options.cursor) + { + cursor_->position = value_.size(); + } + + [[nodiscard]] std::string_view value() const + { + return std::string_view(value_); + } + + [[nodiscard]] std::string_view selectedText() const + { + return value().substr(cursor_->selection.begin, cursor_->selection.size()); + } + + [[nodiscard]] bool empty() const + { + return value_.empty(); + } + + [[nodiscard]] size_t cursorPosition() const + { + return cursor_->position; + } + + /** + * @brief Overwrites the value with the given text and moves cursor to the end. + */ + void assign(std::string_view text) + { + value_ = text; + cursor_->position = value_.size(); + } + + void clear() + { + value_.clear(); + cursor_->position = 0; + } + + /** + * @brief Truncate to precisely `length` bytes. + */ + void truncate(size_t length) + { + if (length >= value().size()) + return; + value_ = value().substr(0, length); + cursor_->position = std::min(cursor_->position, value_.size()); + } + + /** + * @brief Erases the currently selected text and sets the cursor to selection start. + */ + void eraseSelection() + { + value_.erase(cursor_->selection.begin, cursor_->selection.size()); + cursor_->position = cursor_->selection.begin; + cursor_->selection.clear(); + } + + /** + * @brief Inserts the text at the current cursor position. + */ + void type(std::string_view text) + { + if (!cursor_->selection.empty()) + eraseSelection(); + const size_t prevSize = value_.size(); + value_.insert(cursor_->position, text); + cursor_->position += value_.size() - prevSize; + } + + void backspace(bool word) + { + if (cursor_->selection.empty()) { + if (cursor_->position == 0) + return; + cursor_->selection.begin = prevPosition(word); + cursor_->selection.end = cursor_->position; + } + eraseSelection(); + } + + void del(bool word) + { + if (cursor_->selection.empty()) { + if (cursor_->position == value_.size()) + return; + cursor_->selection.begin = cursor_->position; + cursor_->selection.end = nextPosition(word); + } + eraseSelection(); + } + + void setCursorToStart() + { + cursor_->position = 0; + cursor_->selection.clear(); + } + + void setSelectCursorToStart() + { + if (cursor_->selection.empty()) { + cursor_->selection.end = cursor_->position; + } else if (cursor_->selection.end == cursor_->position) { + cursor_->selection.end = cursor_->selection.begin; + } + cursor_->selection.begin = cursor_->position = 0; + } + + void setCursorToEnd() + { + cursor_->position = value_.size(); + cursor_->selection.clear(); + } + + void setSelectCursorToEnd() + { + if (cursor_->selection.empty()) { + cursor_->selection.begin = cursor_->position; + } else if (cursor_->selection.begin == cursor_->position) { + cursor_->selection.begin = cursor_->selection.end; + } + cursor_->selection.end = cursor_->position = value_.size(); + } + + void moveCursorLeft(bool word) + { + cursor_->selection.clear(); + if (cursor_->position == 0) + return; + const size_t newPosition = prevPosition(word); + cursor_->position = newPosition; + } + + void moveSelectCursorLeft(bool word) + { + if (cursor_->position == 0) + return; + const size_t newPosition = prevPosition(word); + if (cursor_->selection.empty()) { + cursor_->selection.begin = newPosition; + cursor_->selection.end = cursor_->position; + } else if (cursor_->selection.end == cursor_->position) { + cursor_->selection.end = newPosition; + } else { + cursor_->selection.begin = newPosition; + } + cursor_->position = newPosition; + } + + void moveCursorRight(bool word) + { + cursor_->selection.clear(); + if (cursor_->position == value_.size()) + return; + const size_t newPosition = nextPosition(word); + cursor_->position = newPosition; + } + + void moveSelectCursorRight(bool word) + { + if (cursor_->position == value_.size()) + return; + const size_t newPosition = nextPosition(word); + if (cursor_->selection.empty()) { + cursor_->selection.begin = cursor_->position; + cursor_->selection.end = newPosition; + } else if (cursor_->selection.begin == cursor_->position) { + cursor_->selection.begin = newPosition; + } else { + cursor_->selection.end = newPosition; + } + cursor_->position = newPosition; + } + +private: + [[nodiscard]] static bool isWordSeparator(unsigned char c) + { + const bool isAsciiWordChar = (c >= '0' && c <= '9') + || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'; + return c <= '\x7E' && !isAsciiWordChar; + } + + [[nodiscard]] size_t prevPosition(bool word) const + { + const std::string_view str = beforeCursor(); + size_t pos = FindLastUtf8Symbols(str); + if (!word) + return pos; + while (pos > 0 && isWordSeparator(str[pos])) { + pos = FindLastUtf8Symbols({ str.data(), pos }); + } + while (pos > 0) { + const size_t prevPos = FindLastUtf8Symbols({ str.data(), pos }); + if (isWordSeparator(str[prevPos])) + break; + pos = prevPos; + } + return pos; + } + + [[nodiscard]] size_t nextPosition(bool word) const + { + const std::string_view str = afterCursor(); + size_t pos = Utf8CodePointLen(str.data()); + if (!word) + return cursor_->position + pos; + while (pos < str.size() && isWordSeparator(str[pos])) { + pos += Utf8CodePointLen(str.data() + pos); + } + while (pos < str.size()) { + pos += Utf8CodePointLen(str.data() + pos); + if (isWordSeparator(str[pos])) + break; + } + return cursor_->position + pos; + } + + [[nodiscard]] std::string_view beforeCursor() const + { + return value().substr(0, cursor_->position); + } + + [[nodiscard]] std::string_view afterCursor() const + { + return value().substr(cursor_->position); + } + + Buffer value_; + TextInputCursorState *cursor_; // unowned +}; + +/** + * @brief Manages state for a number input with a cursor. + */ +class NumberInputState { +public: + struct Options { + TextInputState::Options textOptions; + int min; + int max; + }; + NumberInputState(const Options &options) + : textInput_(options.textOptions) + , min_(options.min) + , max_(options.max) + { + } + + [[nodiscard]] bool empty() const + { + return textInput_.empty(); + } + + [[nodiscard]] int value(int defaultValue = 0) const; + + [[nodiscard]] int max() const + { + return max_; + } + + /** + * @brief Inserts the text at the current cursor position. + * + * Ignores non-numeric characters. + */ + void type(std::string_view str); + + /** + * @brief Sets the text of the input. + * + * Ignores non-numeric characters. + */ + void assign(std::string_view str); + + TextInputState &textInput() + { + return textInput_; + } + +private: + void enforceRange(); + std::string filterStr(std::string_view str, bool allowMinus); + + TextInputState textInput_; + int min_; + int max_; +}; + +bool HandleTextInputEvent(const SDL_Event &event, TextInputState &state); +bool HandleNumberInputEvent(const SDL_Event &event, NumberInputState &state); + +} // namespace devilution diff --git a/Source/DiabloUI/title.cpp b/Source/DiabloUI/title.cpp index d8c43e2c41b..c9d8bfb5b14 100644 --- a/Source/DiabloUI/title.cpp +++ b/Source/DiabloUI/title.cpp @@ -1,3 +1,5 @@ +#include + #include "DiabloUI/diabloui.h" #include "control.h" #include "controls/input.h" @@ -5,9 +7,9 @@ #include "discord/discord.h" #include "engine/load_clx.hpp" #include "engine/load_pcx.hpp" +#include "utils/algorithm/container.hpp" #include "utils/language.h" #include "utils/sdl_geometry.h" -#include "utils/stdcompat/optional.hpp" namespace devilution { namespace { @@ -68,8 +70,7 @@ void UiTitleDialog() discord_manager::UpdateMenu(); while (PollEvent(&event) != 0) { - std::vector menuActions = GetMenuActions(event); - if (std::any_of(menuActions.begin(), menuActions.end(), [](auto menuAction) { return menuAction != MenuAction_NONE; })) { + if (c_any_of(GetMenuActions(event), [](MenuAction menuAction) { return menuAction != MenuAction_NONE; })) { endMenu = true; break; } diff --git a/Source/DiabloUI/ui_flags.hpp b/Source/DiabloUI/ui_flags.hpp index 9c3c9d33ac6..9a84d8939c6 100644 --- a/Source/DiabloUI/ui_flags.hpp +++ b/Source/DiabloUI/ui_flags.hpp @@ -22,32 +22,33 @@ enum class UiFlags : uint32_t { ColorUiGoldDark = 1 << 8, ColorUiSilverDark = 1 << 9, ColorDialogWhite = 1 << 10, - ColorYellow = 1 << 11, - ColorGold = 1 << 12, - ColorBlack = 1 << 13, - ColorWhite = 1 << 14, - ColorWhitegold = 1 << 15, - ColorRed = 1 << 16, - ColorBlue = 1 << 17, - ColorOrange = 1 << 18, - ColorButtonface = 1 << 19, - ColorButtonpushed = 1 << 20, - - AlignCenter = 1 << 21, - AlignRight = 1 << 22, - VerticalCenter = 1 << 23, - - KerningFitSpacing = 1 << 24, - - ElementDisabled = 1 << 25, - ElementHidden = 1 << 26, - - PentaCursor = 1 << 27, - TextCursor = 1 << 28, - Outlined = 1 << 29, + ColorDialogYellow = 1 << 11, + ColorDialogRed = 1 << 12, + ColorYellow = 1 << 13, + ColorGold = 1 << 14, + ColorBlack = 1 << 15, + ColorWhite = 1 << 16, + ColorWhitegold = 1 << 17, + ColorRed = 1 << 18, + ColorBlue = 1 << 19, + ColorOrange = 1 << 20, + ColorButtonface = 1 << 21, + ColorButtonpushed = 1 << 22, + + AlignCenter = 1 << 23, + AlignRight = 1 << 24, + VerticalCenter = 1 << 25, + + KerningFitSpacing = 1 << 26, + + ElementDisabled = 1 << 27, + ElementHidden = 1 << 28, + + PentaCursor = 1 << 29, + Outlined = 1 << 30, /** @brief Ensures that the if current element is active that the next element is also visible. */ - NeedsNextElement = 1 << 30, + NeedsNextElement = 1U << 31U, // clang-format on }; use_enum_as_flags(UiFlags); diff --git a/Source/DiabloUI/ui_item.h b/Source/DiabloUI/ui_item.h index e762a334b37..9b6a23de0f3 100644 --- a/Source/DiabloUI/ui_item.h +++ b/Source/DiabloUI/ui_item.h @@ -5,6 +5,7 @@ #include #include +#include "DiabloUI/text_input.hpp" #include "DiabloUI/ui_flags.hpp" #include "engine/clx_sprite.hpp" #include "engine/render/text_render.hpp" @@ -179,7 +180,7 @@ class UiArtText : public UiItemBase { { } - [[nodiscard]] string_view GetText() const + [[nodiscard]] std::string_view GetText() const { if (text_ != nullptr) return text_; @@ -227,7 +228,7 @@ class UiArtTextButton : public UiItemBase { public: using Callback = void (*)(); - UiArtTextButton(string_view text, Callback action, SDL_Rect rect, UiFlags flags = UiFlags::None) + UiArtTextButton(std::string_view text, Callback action, SDL_Rect rect, UiFlags flags = UiFlags::None) : UiItemBase(UiType::ArtTextButton, rect, flags) , text_(text) , action_(action) @@ -239,7 +240,7 @@ class UiArtTextButton : public UiItemBase { UiItemBase::SetFlags(flags); } - [[nodiscard]] string_view GetText() const + [[nodiscard]] std::string_view GetText() const { return text_; } @@ -250,7 +251,7 @@ class UiArtTextButton : public UiItemBase { } private: - string_view text_; + std::string_view text_; Callback action_; }; @@ -258,7 +259,7 @@ class UiArtTextButton : public UiItemBase { class UiEdit : public UiItemBase { public: - UiEdit(string_view hint, char *value, std::size_t maxLength, bool allowEmpty, SDL_Rect rect, UiFlags flags = UiFlags::None) + UiEdit(std::string_view hint, char *value, std::size_t maxLength, bool allowEmpty, SDL_Rect rect, UiFlags flags = UiFlags::None) : UiItemBase(UiType::Edit, rect, flags) , m_hint(hint) , m_value(value) @@ -268,9 +269,10 @@ class UiEdit : public UiItemBase { } // private: - string_view m_hint; + std::string_view m_hint; char *m_value; std::size_t m_max_length; + TextInputCursorState m_cursor; bool m_allowEmpty; }; @@ -280,19 +282,19 @@ class UiEdit : public UiItemBase { class UiText : public UiItemBase { public: - UiText(string_view text, SDL_Rect rect, UiFlags flags = UiFlags::ColorDialogWhite) + UiText(std::string_view text, SDL_Rect rect, UiFlags flags = UiFlags::ColorDialogWhite) : UiItemBase(UiType::Text, rect, flags) , text_(text) { } - [[nodiscard]] string_view GetText() const + [[nodiscard]] std::string_view GetText() const { return text_; } private: - string_view text_; + std::string_view text_; }; //============================================================================= @@ -303,7 +305,7 @@ class UiButton : public UiItemBase { public: using Callback = void (*)(); - UiButton(string_view text, Callback action, SDL_Rect rect, UiFlags flags = UiFlags::None) + UiButton(std::string_view text, Callback action, SDL_Rect rect, UiFlags flags = UiFlags::None) : UiItemBase(UiType::Button, rect, flags) , text_(text) , action_(action) @@ -311,7 +313,7 @@ class UiButton : public UiItemBase { { } - [[nodiscard]] string_view GetText() const + [[nodiscard]] std::string_view GetText() const { return text_; } @@ -337,7 +339,7 @@ class UiButton : public UiItemBase { } private: - string_view text_; + std::string_view text_; Callback action_; // State @@ -348,14 +350,14 @@ class UiButton : public UiItemBase { class UiListItem { public: - UiListItem(string_view text = "", int value = 0, UiFlags uiFlags = UiFlags::None) + UiListItem(std::string_view text = "", int value = 0, UiFlags uiFlags = UiFlags::None) : m_text(text) , m_value(value) , uiFlags(uiFlags) { } - UiListItem(string_view text, std::vector &args, int value = 0, UiFlags uiFlags = UiFlags::None) + UiListItem(std::string_view text, std::vector &args, int value = 0, UiFlags uiFlags = UiFlags::None) : m_text(text) , args(args) , m_value(value) @@ -364,7 +366,7 @@ class UiListItem { } // private: - string_view m_text; + std::string_view m_text; std::vector args; int m_value; UiFlags uiFlags; diff --git a/Source/appfat.cpp b/Source/appfat.cpp index c29cea7c1dc..8eed34ed930 100644 --- a/Source/appfat.cpp +++ b/Source/appfat.cpp @@ -46,7 +46,7 @@ void FreeDlg() } // namespace -void app_fatal(string_view str) +void app_fatal(std::string_view str) { FreeDlg(); UiErrorOkDialog(_("Error"), str); @@ -60,7 +60,7 @@ void assert_fail(int nLineNo, const char *pszFile, const char *pszFail) } #endif -void ErrDlg(const char *title, string_view error, string_view logFilePath, int logLineNr) +void ErrDlg(const char *title, std::string_view error, std::string_view logFilePath, int logLineNr) { FreeDlg(); @@ -70,7 +70,7 @@ void ErrDlg(const char *title, string_view error, string_view logFilePath, int l diablo_quit(1); } -void InsertCDDlg(string_view archiveName) +void InsertCDDlg(std::string_view archiveName) { std::string text = fmt::format( fmt::runtime(_("Unable to open main data archive ({:s}).\n" @@ -82,7 +82,7 @@ void InsertCDDlg(string_view archiveName) diablo_quit(1); } -void DirErrorDlg(string_view error) +void DirErrorDlg(std::string_view error) { std::string text = fmt::format(fmt::runtime(_(/* TRANSLATORS: Error when Program is not allowed to write data */ "Unable to write to location:\n{:s}")), error); diff --git a/Source/appfat.h b/Source/appfat.h index 4b12c262d33..9c98572f7fe 100644 --- a/Source/appfat.h +++ b/Source/appfat.h @@ -5,10 +5,11 @@ */ #pragma once +#include + #include #include "utils/attributes.h" -#include "utils/stdcompat/string_view.hpp" namespace devilution { @@ -26,7 +27,7 @@ namespace devilution { * @brief Terminates the game and displays an error message box. * @param str Error message. */ -[[noreturn]] void app_fatal(string_view str); +[[noreturn]] void app_fatal(std::string_view str); #ifdef _DEBUG /** @@ -40,16 +41,16 @@ namespace devilution { /** * @brief Terminates the game and displays an error dialog box based on the given dialog_id. */ -[[noreturn]] void ErrDlg(const char *title, string_view error, string_view logFilePath, int logLineNr); +[[noreturn]] void ErrDlg(const char *title, std::string_view error, std::string_view logFilePath, int logLineNr); /** * @brief Terminates the game with an insert CD error dialog. */ -[[noreturn]] void InsertCDDlg(string_view archiveName); +[[noreturn]] void InsertCDDlg(std::string_view archiveName); /** * @brief Terminates the game with a read-only directory error dialog. */ -[[noreturn]] void DirErrorDlg(string_view error); +[[noreturn]] void DirErrorDlg(std::string_view error); } // namespace devilution diff --git a/Source/automap.cpp b/Source/automap.cpp index 94da6860b10..a0029969c0e 100644 --- a/Source/automap.cpp +++ b/Source/automap.cpp @@ -5,6 +5,7 @@ */ #include "automap.h" +#include #include #include @@ -16,13 +17,15 @@ #include "levels/gendung.h" #include "levels/setmaps.h" #include "player.h" +#include "utils/attributes.h" +#include "utils/enum_traits.h" #include "utils/language.h" -#include "utils/stdcompat/algorithm.hpp" #include "utils/ui_fwd.h" #include "utils/utf8.hpp" #ifdef _DEBUG #include "debug.h" +#include "lighting.h" #endif namespace devilution { @@ -39,6 +42,14 @@ enum MapColors : uint8_t { MapColorsDim = (PAL16_YELLOW + 8), /** color for items on automap */ MapColorsItem = (PAL8_BLUE + 1), + /** color for activated pentragram on automap */ + MapColorsPentagramOpen = (PAL8_RED + 2), + /** color for cave lava on automap */ + MapColorsLava = (PAL8_ORANGE + 2), + /** color for cave water on automap */ + MapColorsWater = (PAL8_BLUE + 2), + /** color for hive acid on automap */ + MapColorsAcid = (PAL8_YELLOW + 4), }; struct AutomapTile { @@ -69,6 +80,41 @@ struct AutomapTile { RiverLeftOut, RiverRightIn, RiverRightOut, + CaveHorizontalWoodCross, + CaveVerticalWoodCross, + CaveLeftCorner, + CaveRightCorner, + CaveBottomCorner, + CaveHorizontalWood, + CaveVerticalWood, + CaveWoodCross, + CaveRightWoodCross, + CaveLeftWoodCross, + HorizontalLavaThin, + VerticalLavaThin, + BendSouthLavaThin, + BendWestLavaThin, + BendEastLavaThin, + BendNorthLavaThin, + VerticalWallLava, + HorizontalWallLava, + SELava, + SWLava, + NELava, + NWLava, + SLava, + WLava, + ELava, + NLava, + Lava, + CaveHorizontalWallLava, + CaveVerticalWallLava, + HorizontalBridgeLava, + VerticalBridgeLava, + VerticalDiamond, + HorizontalDiamond, + PentagramClosed, + PentagramOpen, }; Types type; @@ -89,12 +135,20 @@ struct AutomapTile { // clang-format on }; - Flags flags; + Flags flags = {}; - constexpr bool HasFlag(Flags test) const + [[nodiscard]] DVL_ALWAYS_INLINE constexpr bool hasFlag(Flags test) const { return (static_cast(flags) & static_cast(test)) != 0; } + + template + [[nodiscard]] DVL_ALWAYS_INLINE constexpr bool hasAnyFlag(Flags flag, Args... flags) + { + return (static_cast(this->flags) + & (static_cast(flag) | ... | static_cast(flags))) + != 0; + } }; /** @@ -102,311 +156,754 @@ struct AutomapTile { */ std::array AutomapTypeTiles; +/** + * @brief Draw a diamond on top tile. + */ void DrawDiamond(const Surface &out, Point center, uint8_t color) { - const Point left { center.x - AmLine(16), center.y }; - const Point top { center.x, center.y - AmLine(8) }; - const Point bottom { center.x, center.y + AmLine(8) }; - - DrawMapLineNE(out, left, AmLine(8), color); - DrawMapLineSE(out, left, AmLine(8), color); - DrawMapLineSE(out, top, AmLine(8), color); - DrawMapLineNE(out, bottom, AmLine(8), color); + DrawMapLineNE(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::FullTile), color); + DrawMapLineSE(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::FullTile), color); + DrawMapLineSE(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileUp), AmLine(AmLineLength::FullTile), color); + DrawMapLineNE(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::None), AmLine(AmLineLength::FullTile), color); } -void DrawMapVerticalDoor(const Surface &out, Point center, uint8_t colorBright, uint8_t colorDim) +/** + * @brief Draws a bright diamond and a line, orientation depending on the tileset. + */ +void DrawMapVerticalDoor(const Surface &out, Point center, AutomapTile neTile, uint8_t colorBright, uint8_t colorDim) { - DrawMapLineNE(out, { center.x + AmLine(8), center.y - AmLine(4) }, AmLine(4), colorDim); - DrawMapLineNE(out, { center.x - AmLine(16), center.y + AmLine(8) }, AmLine(4), colorDim); - DrawDiamond(out, center, colorBright); -} + AmWidthOffset lWidthOffset; + AmHeightOffset lHeightOffset; + AmWidthOffset dWidthOffset; + AmHeightOffset dHeightOffset; + AmLineLength length; -void DrawMapHorizontalDoor(const Surface &out, Point center, uint8_t colorBright, uint8_t colorDim) -{ - DrawMapLineSE(out, { center.x - AmLine(16), center.y - AmLine(8) }, AmLine(4), colorDim); - DrawMapLineSE(out, { center.x + AmLine(8), center.y + AmLine(4) }, AmLine(4), colorDim); - DrawDiamond(out, center, colorBright); + switch (leveltype) { + case DTYPE_CATHEDRAL: + case DTYPE_CRYPT: + lWidthOffset = AmWidthOffset::QuarterTileLeft; + lHeightOffset = AmHeightOffset::QuarterTileUp; + + dWidthOffset = AmWidthOffset::HalfTileLeft; + dHeightOffset = AmHeightOffset::HalfTileDown; + + length = AmLineLength::HalfTile; + break; + case DTYPE_CATACOMBS: + lWidthOffset = AmWidthOffset::ThreeQuartersTileLeft; + lHeightOffset = AmHeightOffset::QuarterTileDown; + + dWidthOffset = AmWidthOffset::None; + dHeightOffset = AmHeightOffset::None; + + length = AmLineLength::FullTile; + break; + case DTYPE_CAVES: + lWidthOffset = AmWidthOffset::QuarterTileLeft; + lHeightOffset = AmHeightOffset::ThreeQuartersTileDown; + + dWidthOffset = AmWidthOffset::HalfTileRight; + dHeightOffset = AmHeightOffset::HalfTileDown; + + length = AmLineLength::FullTile; + break; + default: + app_fatal("Invalid leveltype"); + } + if (!(neTile.hasFlag(AutomapTile::Flags::VerticalPassage) && leveltype == DTYPE_CATHEDRAL)) + DrawMapLineNE(out, center + AmOffset(lWidthOffset, lHeightOffset), AmLine(length), colorDim); + DrawDiamond(out, center + AmOffset(dWidthOffset, dHeightOffset), colorBright); } -void DrawDirt(const Surface &out, Point center, uint8_t color) +/** + * @brief Draws a bright diamond and a line, orientation depending on the tileset. + */ +void DrawMapHorizontalDoor(const Surface &out, Point center, AutomapTile nwTile, uint8_t colorBright, uint8_t colorDim) { - out.SetPixel({ center.x + AmLine(8) - AmLine(32), center.y + AmLine(4) }, color); + AmWidthOffset lWidthOffset; + AmHeightOffset lHeightOffset; + AmWidthOffset dWidthOffset; + AmHeightOffset dHeightOffset; + AmLineLength length; - out.SetPixel({ center.x - AmLine(16), center.y }, color); - out.SetPixel({ center.x - AmLine(16), center.y + AmLine(8) }, color); + switch (leveltype) { + case DTYPE_CATHEDRAL: + case DTYPE_CRYPT: + lWidthOffset = AmWidthOffset::None; + lHeightOffset = AmHeightOffset::HalfTileUp; - out.SetPixel({ center.x - AmLine(8), center.y - AmLine(4) }, color); - out.SetPixel({ center.x - AmLine(8), center.y + AmLine(4) }, color); - out.SetPixel({ center.x - AmLine(8), center.y + AmLine(16) - AmLine(4) }, color); + dWidthOffset = AmWidthOffset::HalfTileRight; + dHeightOffset = AmHeightOffset::HalfTileDown; - out.SetPixel({ center.x, center.y - AmLine(8) }, color); - out.SetPixel(center, color); - out.SetPixel({ center.x, center.y + AmLine(8) }, color); - out.SetPixel({ center.x, center.y + AmLine(16) }, color); + length = AmLineLength::HalfTile; + break; + case DTYPE_CATACOMBS: + lWidthOffset = AmWidthOffset::QuarterTileRight; + lHeightOffset = AmHeightOffset::QuarterTileUp; - out.SetPixel({ center.x + AmLine(8), center.y - AmLine(4) }, color); - out.SetPixel({ center.x + AmLine(8), center.y + AmLine(4) }, color); - out.SetPixel({ center.x + AmLine(8), center.y + AmLine(16) - AmLine(4) }, color); + dWidthOffset = AmWidthOffset::None; + dHeightOffset = AmHeightOffset::None; - out.SetPixel({ center.x + AmLine(16), center.y }, color); - out.SetPixel({ center.x + AmLine(16), center.y + AmLine(8) }, color); + length = AmLineLength::FullTile; + break; + case DTYPE_CAVES: + lWidthOffset = AmWidthOffset::QuarterTileLeft; + lHeightOffset = AmHeightOffset::QuarterTileDown; + + dWidthOffset = AmWidthOffset::HalfTileLeft; + dHeightOffset = AmHeightOffset::HalfTileDown; + + length = AmLineLength::FullTile; + break; + break; + default: + app_fatal("Invalid leveltype"); + } + if (!(nwTile.hasFlag(AutomapTile::Flags::HorizontalPassage) && leveltype == DTYPE_CATHEDRAL)) + DrawMapLineSE(out, center + AmOffset(lWidthOffset, lHeightOffset), AmLine(length), colorDim); + DrawDiamond(out, center + AmOffset(dWidthOffset, dHeightOffset), colorBright); +} - out.SetPixel({ center.x - AmLine(8) + AmLine(32), center.y + AmLine(4) }, color); +/** + * @brief Draw 16 individual pixels equally spaced apart, used to communicate OOB area to the player. + */ +void DrawDirt(const Surface &out, Point center, AutomapTile nwTile, AutomapTile neTile, uint8_t color) +{ + SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileLeft, AmHeightOffset::QuarterTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileUp), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); + // Prevent the top dirt pixel from appearing inside arch diamonds + if (!nwTile.hasAnyFlag(AutomapTile::Flags::HorizontalArch, AutomapTile::Flags::HorizontalGrate) + && !neTile.hasAnyFlag(AutomapTile::Flags::VerticalArch, AutomapTile::Flags::VerticalGrate)) + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileUp), color); + SetMapPixel(out, center, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); } void DrawBridge(const Surface &out, Point center, uint8_t color) { - out.SetPixel(center, color); + SetMapPixel(out, center, color); - out.SetPixel({ center.x + AmLine(8), center.y - AmLine(4) }, color); - out.SetPixel({ center.x + AmLine(8), center.y + AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); - out.SetPixel({ center.x + AmLine(16), center.y }, color); - out.SetPixel({ center.x + AmLine(16), center.y + AmLine(8) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); - out.SetPixel({ center.x - AmLine(8) + AmLine(32), center.y + AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); } void DrawRiverRightIn(const Surface &out, Point center, uint8_t color) { - out.SetPixel({ center.x - AmLine(16), center.y + AmLine(8) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); - out.SetPixel({ center.x - AmLine(8), center.y + AmLine(4) }, color); - out.SetPixel({ center.x - AmLine(8), center.y + AmLine(16) - AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); - out.SetPixel(center, color); - out.SetPixel({ center.x, center.y + AmLine(8) }, color); - out.SetPixel({ center.x, center.y + AmLine(16) }, color); + SetMapPixel(out, center, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); - out.SetPixel({ center.x + AmLine(8), center.y + AmLine(4) }, color); - out.SetPixel({ center.x + AmLine(8), center.y + AmLine(16) - AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); - out.SetPixel({ center.x + AmLine(16), center.y }, color); - out.SetPixel({ center.x + AmLine(16), center.y + AmLine(8) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); - out.SetPixel({ center.x - AmLine(8) + AmLine(32), center.y + AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); } void DrawRiverCornerSouth(const Surface &out, Point center, uint8_t color) { - out.SetPixel({ center.x, center.y + AmLine(16) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); } void DrawRiverCornerNorth(const Surface &out, Point center, uint8_t color) { - out.SetPixel({ center.x - AmLine(8), center.y - AmLine(4) }, color); - out.SetPixel({ center.x, center.y - AmLine(8) }, color); - out.SetPixel({ center.x + AmLine(8), center.y - AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileUp), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileUp), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); } void DrawRiverLeftOut(const Surface &out, Point center, uint8_t color) { - out.SetPixel({ center.x + AmLine(8) - AmLine(32), center.y + AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileLeft, AmHeightOffset::QuarterTileDown), color); - out.SetPixel({ center.x - AmLine(16), center.y }, color); - out.SetPixel({ center.x - AmLine(16), center.y + AmLine(8) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); - out.SetPixel({ center.x - AmLine(8), center.y + AmLine(4) }, color); - out.SetPixel({ center.x - AmLine(8), center.y + AmLine(16) - AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); - out.SetPixel(center, color); - out.SetPixel({ center.x, center.y + AmLine(8) }, color); + SetMapPixel(out, center, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); - out.SetPixel({ center.x + AmLine(8), center.y - AmLine(4) }, color); - out.SetPixel({ center.x + AmLine(8), center.y + AmLine(4) }, color); - out.SetPixel({ center.x + AmLine(8), center.y + AmLine(16) - AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); - out.SetPixel({ center.x + AmLine(16), center.y }, color); - out.SetPixel({ center.x + AmLine(16), center.y + AmLine(8) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); - out.SetPixel({ center.x - AmLine(8) + AmLine(32), center.y + AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); } void DrawRiverLeftIn(const Surface &out, Point center, uint8_t color) { - out.SetPixel({ center.x - AmLine(16), center.y }, color); - out.SetPixel({ center.x - AmLine(16), center.y + AmLine(8) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); - out.SetPixel({ center.x - AmLine(8), center.y - AmLine(4) }, color); - out.SetPixel({ center.x - AmLine(8), center.y + AmLine(4) }, color); - out.SetPixel({ center.x - AmLine(8), center.y + AmLine(16) - AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileUp), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); - out.SetPixel({ center.x, center.y - AmLine(8) }, color); - out.SetPixel(center, color); - out.SetPixel({ center.x, center.y + AmLine(8) }, color); - out.SetPixel({ center.x, center.y + AmLine(16) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileUp), color); + SetMapPixel(out, center, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); - out.SetPixel({ center.x + AmLine(8), center.y - AmLine(4) }, color); - out.SetPixel({ center.x + AmLine(8), center.y + AmLine(4) }, color); - out.SetPixel({ center.x + AmLine(8), center.y + AmLine(16) - AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); } void DrawRiverCornerWest(const Surface &out, Point center, uint8_t color) { - out.SetPixel({ center.x + AmLine(8) - AmLine(32), center.y + AmLine(4) }, color); - out.SetPixel({ center.x - AmLine(16), center.y }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileLeft, AmHeightOffset::QuarterTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), color); } void DrawRiverCornerEast(const Surface &out, Point center, uint8_t color) { - out.SetPixel({ center.x + AmLine(16), center.y }, color); - out.SetPixel({ center.x + AmLine(16), center.y + AmLine(8) }, color); - out.SetPixel({ center.x - AmLine(8) + AmLine(32), center.y + AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); } void DrawRiverRightOut(const Surface &out, Point center, uint8_t color) { - out.SetPixel({ center.x - AmLine(8), center.y + AmLine(4) }, color); - out.SetPixel({ center.x - AmLine(8), center.y + AmLine(16) - AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); - out.SetPixel(center, color); - out.SetPixel({ center.x, center.y + AmLine(8) }, color); - out.SetPixel({ center.x, center.y + AmLine(16) }, color); + SetMapPixel(out, center, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); - out.SetPixel({ center.x + AmLine(8), center.y - AmLine(4) }, color); - out.SetPixel({ center.x + AmLine(8), center.y + AmLine(4) }, color); - out.SetPixel({ center.x + AmLine(8), center.y + AmLine(16) - AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); - out.SetPixel({ center.x + AmLine(16), center.y }, color); - out.SetPixel({ center.x + AmLine(16), center.y + AmLine(8) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); - out.SetPixel({ center.x - AmLine(8) + AmLine(32), center.y + AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); } void DrawRiver(const Surface &out, Point center, uint8_t color) { - out.SetPixel({ center.x - AmLine(16), center.y + AmLine(8) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); - out.SetPixel({ center.x - AmLine(8), center.y + AmLine(4) }, color); - out.SetPixel({ center.x - AmLine(8), center.y + AmLine(16) - AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); - out.SetPixel(center, color); - out.SetPixel({ center.x, center.y + AmLine(8) }, color); - out.SetPixel({ center.x, center.y + AmLine(16) }, color); + SetMapPixel(out, center, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); - out.SetPixel({ center.x + AmLine(8), center.y - AmLine(4) }, color); - out.SetPixel({ center.x + AmLine(8), center.y + AmLine(4) }, color); - out.SetPixel({ center.x + AmLine(8), center.y + AmLine(16) - AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); - out.SetPixel({ center.x + AmLine(16), center.y }, color); - out.SetPixel({ center.x + AmLine(16), center.y + AmLine(8) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); - out.SetPixel({ center.x - AmLine(8) + AmLine(32), center.y + AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); } void DrawRiverForkIn(const Surface &out, Point center, uint8_t color) { - out.SetPixel({ center.x - AmLine(16), center.y }, color); - out.SetPixel({ center.x - AmLine(16), center.y + AmLine(8) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); - out.SetPixel({ center.x - AmLine(8), center.y - AmLine(4) }, color); - out.SetPixel({ center.x - AmLine(8), center.y + AmLine(4) }, color); - out.SetPixel({ center.x - AmLine(8), center.y + AmLine(16) - AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileUp), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::FullTileDown), color); - out.SetPixel({ center.x, center.y - AmLine(8) }, color); - out.SetPixel(center, color); - out.SetPixel({ center.x, center.y + AmLine(8) }, color); - out.SetPixel({ center.x, center.y + AmLine(16) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileUp), color); + SetMapPixel(out, center, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); - out.SetPixel({ center.x + AmLine(8), center.y - AmLine(4) }, color); - out.SetPixel({ center.x + AmLine(8), center.y + AmLine(4) }, color); - out.SetPixel({ center.x + AmLine(8), center.y + AmLine(16) - AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); - out.SetPixel({ center.x + AmLine(16), center.y }, color); - out.SetPixel({ center.x + AmLine(16), center.y + AmLine(8) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); - out.SetPixel({ center.x - AmLine(8) + AmLine(32), center.y + AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); } void DrawRiverForkOut(const Surface &out, Point center, uint8_t color) { - out.SetPixel({ center.x + AmLine(8) - AmLine(32), center.y + AmLine(4) }, color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileLeft, AmHeightOffset::QuarterTileDown), color); + + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); + + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); + + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); + + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); +} - out.SetPixel({ center.x - AmLine(16), center.y }, color); - out.SetPixel({ center.x - AmLine(16), center.y + AmLine(8) }, color); +template +void DrawLavaRiver(const Surface &out, Point center, uint8_t color, bool hasBridge) +{ + // First row (y = 0) + if constexpr (IsAnyOf(Direction::NorthWest, TDir1, TDir2)) { + if (!(hasBridge && IsAnyOf(TDir1, Direction::NorthWest))) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileUp), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), color); + } + } - out.SetPixel({ center.x - AmLine(8), center.y + AmLine(16) - AmLine(4) }, color); + // Second row (y = 1) + if constexpr (IsAnyOf(Direction::NorthEast, TDir1, TDir2)) { + if (!(hasBridge && IsAnyOf(Direction::NorthEast, TDir1, TDir2))) + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); + } + if constexpr (IsAnyOf(Direction::NorthWest, TDir1, TDir2) || IsAnyOf(Direction::NorthEast, TDir1, TDir2)) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::None), color); + } + if constexpr (IsAnyOf(Direction::SouthWest, TDir1, TDir2) || IsAnyOf(Direction::NorthWest, TDir1, TDir2)) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); + } + if constexpr (IsAnyOf(Direction::SouthWest, TDir1, TDir2)) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); + } - out.SetPixel({ center.x, center.y + AmLine(16) }, color); + // Third row (y = 2) + if constexpr (IsAnyOf(Direction::NorthEast, TDir1, TDir2)) { + if (!(hasBridge && IsAnyOf(Direction::NorthEast, TDir1, TDir2))) + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); + } + if constexpr (IsAnyOf(Direction::NorthEast, TDir1, TDir2) || IsAnyOf(Direction::SouthEast, TDir1, TDir2)) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); + } + if constexpr (IsAnyOf(Direction::SouthWest, TDir1, TDir2) || IsAnyOf(Direction::SouthEast, TDir1, TDir2)) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); + } + if constexpr (IsAnyOf(Direction::SouthWest, TDir1, TDir2)) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); + } - out.SetPixel({ center.x + AmLine(8), center.y + AmLine(16) - AmLine(4) }, color); + // Fourth row (y = 3) + if constexpr (IsAnyOf(Direction::SouthEast, TDir1, TDir2)) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); + } } +template +void DrawLava(const Surface &out, Point center, uint8_t color) +{ + if constexpr (IsAnyOf(TDir, Direction::NorthWest, Direction::North, Direction::NorthEast, Direction::NoDirection)) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileUp), color); // north corner + } + if constexpr (IsNoneOf(TDir, Direction::South, Direction::SouthEast, Direction::East)) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileUp), color); // northwest edge + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), color); // northwest edge + } + if constexpr (IsAnyOf(TDir, Direction::SouthWest, Direction::West, Direction::NorthWest, Direction::NoDirection)) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileLeft, AmHeightOffset::QuarterTileDown), color); // west corner + } + if constexpr (IsAnyOf(TDir, Direction::South, Direction::SouthWest, Direction::West, Direction::NorthWest, Direction::SouthEast, Direction::NoDirection)) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); // southwest edge + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); // southwest edge + } + if constexpr (IsAnyOf(TDir, Direction::South, Direction::SouthWest, Direction::SouthEast, Direction::NoDirection)) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); // south corner + } + if constexpr (IsAnyOf(TDir, Direction::South, Direction::SouthWest, Direction::NorthEast, Direction::East, Direction::SouthEast, Direction::NoDirection)) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); // southeast edge + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); // southeast edge + } + if constexpr (IsAnyOf(TDir, Direction::NorthEast, Direction::East, Direction::SouthEast, Direction::NoDirection)) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); // east corner + } + if constexpr (IsNoneOf(TDir, Direction::South, Direction::SouthWest, Direction::West)) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); // northeast edge + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); // northeast edge + } + if constexpr (TDir != Direction::South) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::None), color); // north center + } + if constexpr (TDir != Direction::East) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); // west center + } + if constexpr (TDir != Direction::West) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); // east center + } + if constexpr (TDir != Direction::North) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); // south center + } +} + +/** + * @brief Draw 4 south-east facing lines, used to communicate trigger locations to the player. + */ void DrawStairs(const Surface &out, Point center, uint8_t color) { constexpr int NumStairSteps = 4; - const Displacement offset = { -AmLine(8), AmLine(4) }; - Point p = { center.x - AmLine(8), center.y - AmLine(8) - AmLine(4) }; + const Displacement offset = AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown); + AmWidthOffset w = AmWidthOffset::QuarterTileLeft; + AmHeightOffset h = AmHeightOffset::QuarterTileUp; + + if (IsAnyOf(leveltype, DTYPE_CATACOMBS, DTYPE_HELL)) { + w = AmWidthOffset::QuarterTileLeft; + h = AmHeightOffset::ThreeQuartersTileUp; + } + + // Initial point based on the 'center' position. + Point p = center + AmOffset(w, h); + for (int i = 0; i < NumStairSteps; ++i) { - DrawMapLineSE(out, p, AmLine(16), color); + DrawMapLineSE(out, p, AmLine(AmLineLength::DoubleTile), color); p += offset; } } +/** + * @brief Redraws the bright line of the door diamond that gets overwritten by later drawn lines. + */ +void FixHorizontalDoor(const Surface &out, Point center, AutomapTile nwTile, uint8_t colorBright) +{ + if (leveltype != DTYPE_CATACOMBS && nwTile.hasFlag(AutomapTile::Flags::HorizontalDoor)) { + DrawMapLineNE(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::FullTile), colorBright); + } +} + +/** + * @brief Redraws the bright line of the door diamond that gets overwritten by later drawn lines. + */ +void FixVerticalDoor(const Surface &out, Point center, AutomapTile neTile, uint8_t colorBright) +{ + if (leveltype != DTYPE_CATACOMBS && neTile.hasFlag(AutomapTile::Flags::VerticalDoor)) { + DrawMapLineSE(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileUp), AmLine(AmLineLength::FullTile), colorBright); + } +} + +/** + * @brief Draw half-tile length lines to connect walls to any walls to the north-west and/or north-east + */ +void DrawWallConnections(const Surface &out, Point center, AutomapTile tile, AutomapTile nwTile, AutomapTile neTile, uint8_t colorBright, uint8_t colorDim) +{ + if (tile.hasFlag(AutomapTile::Flags::HorizontalDoor) && nwTile.hasFlag(AutomapTile::Flags::HorizontalDoor)) { + // fix missing lower half of the line connecting door pairs in Lazarus' level + DrawMapLineSE(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::HalfTile), colorDim); + } + if (IsAnyOf(nwTile.type, AutomapTile::Types::HorizontalWallLava, AutomapTile::Types::Horizontal, AutomapTile::Types::HorizontalDiamond, AutomapTile::Types::FenceHorizontal, AutomapTile::Types::Cross, AutomapTile::Types::CaveVerticalWoodCross, AutomapTile::Types::CaveRightCorner)) { + DrawMapLineSE(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileUp), AmLine(AmLineLength::HalfTile), colorDim); + FixHorizontalDoor(out, center, nwTile, colorBright); + } + if (IsAnyOf(neTile.type, AutomapTile::Types::VerticalWallLava, AutomapTile::Types::Vertical, AutomapTile::Types::VerticalDiamond, AutomapTile::Types::FenceVertical, AutomapTile::Types::Cross, AutomapTile::Types::CaveHorizontalWoodCross, AutomapTile::Types::CaveLeftCorner)) { + DrawMapLineNE(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::HalfTile), colorDim); + FixVerticalDoor(out, center, neTile, colorBright); + } +} + +/** + * @brief Draws a dotted line to represent a wall grate. + */ +void DrawMapVerticalGrate(const Surface &out, Point center, uint8_t colorDim) +{ + Point pos1 = center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None) + AmOffset(AmWidthOffset::EighthTileRight, AmHeightOffset::EighthTileUp); + Point pos2 = center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None); + Point pos3 = center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None) + AmOffset(AmWidthOffset::EighthTileLeft, AmHeightOffset::EighthTileDown); + + SetMapPixel(out, pos1 + Displacement { 0, 1 }, 0); + SetMapPixel(out, pos2 + Displacement { 0, 1 }, 0); + SetMapPixel(out, pos3 + Displacement { 0, 1 }, 0); + SetMapPixel(out, pos1, colorDim); + SetMapPixel(out, pos2, colorDim); + SetMapPixel(out, pos3, colorDim); +} + +/** + * @brief Draws a dotted line to represent a wall grate. + */ +void DrawMapHorizontalGrate(const Surface &out, Point center, uint8_t colorDim) +{ + Point pos1 = center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None) + AmOffset(AmWidthOffset::EighthTileLeft, AmHeightOffset::EighthTileUp); + Point pos2 = center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None); + Point pos3 = center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None) + AmOffset(AmWidthOffset::EighthTileRight, AmHeightOffset::EighthTileDown); + + SetMapPixel(out, pos1 + Displacement { 0, 1 }, 0); + SetMapPixel(out, pos2 + Displacement { 0, 1 }, 0); + SetMapPixel(out, pos3 + Displacement { 0, 1 }, 0); + SetMapPixel(out, pos1, colorDim); + SetMapPixel(out, pos2, colorDim); + SetMapPixel(out, pos3, colorDim); +} /** * Left-facing obstacle */ -void DrawHorizontal(const Surface &out, Point center, AutomapTile tile, uint8_t colorBright, uint8_t colorDim) +void DrawHorizontal(const Surface &out, Point center, AutomapTile tile, AutomapTile nwTile, AutomapTile neTile, AutomapTile seTile, uint8_t colorBright, uint8_t colorDim) { - if (!tile.HasFlag(AutomapTile::Flags::HorizontalPassage)) { - DrawMapLineSE(out, { center.x, center.y - AmLine(16) }, AmLine(16), colorDim); - return; + AmWidthOffset w = AmWidthOffset::None; + AmHeightOffset h = AmHeightOffset::HalfTileUp; + AmLineLength l = AmLineLength::FullAndHalfTile; + + // Draw a diamond in the top tile + if (neTile.hasAnyFlag(AutomapTile::Flags::VerticalArch, AutomapTile::Flags::VerticalGrate) // NE tile has an arch, so add a diamond for visual consistency + || nwTile.hasAnyFlag(AutomapTile::Flags::HorizontalArch, AutomapTile::Flags::HorizontalGrate) // NW tile has an arch, so add a diamond for visual consistency + || tile.hasAnyFlag(AutomapTile::Flags::VerticalArch, AutomapTile::Flags::HorizontalArch, AutomapTile::Flags::VerticalGrate, AutomapTile::Flags::HorizontalGrate) // Current tile has an arch, add a diamond + || tile.type == AutomapTile::Types::HorizontalDiamond) { // wall ending in hell that should end with a diamond + w = AmWidthOffset::QuarterTileRight; + h = AmHeightOffset::QuarterTileUp; + l = AmLineLength::FullTile; // shorten line to avoid overdraw + DrawDiamond(out, center, colorDim); + FixHorizontalDoor(out, center, nwTile, colorBright); + FixVerticalDoor(out, center, neTile, colorBright); + } + // Shorten line to avoid overdraw + if (IsAnyOf(leveltype, DTYPE_CAVES, DTYPE_NEST) + && IsAnyOf(tile.type, AutomapTile::Types::CaveVerticalCross, AutomapTile::Types::CaveVerticalWoodCross) + && !(IsAnyOf(seTile.type, AutomapTile::Types::Horizontal, AutomapTile::Types::CaveVerticalCross, AutomapTile::Types::CaveVerticalWoodCross, AutomapTile::Types::Corner))) { + l = AmLineLength::FullTile; } - if (tile.HasFlag(AutomapTile::Flags::HorizontalDoor)) { - DrawMapHorizontalDoor(out, { center.x + AmLine(16), center.y - AmLine(8) }, colorBright, colorDim); + // Draw the wall line if the wall is solid + if (!tile.hasFlag(AutomapTile::Flags::HorizontalPassage)) { + DrawMapLineSE(out, center + AmOffset(w, h), AmLine(l), colorDim); + return; } - if (tile.HasFlag(AutomapTile::Flags::HorizontalGrate)) { - DrawMapLineSE(out, { center.x + AmLine(16), center.y - AmLine(8) }, AmLine(8), colorDim); - DrawDiamond(out, { center.x, center.y - AmLine(8) }, colorDim); - } else if (tile.HasFlag(AutomapTile::Flags::HorizontalArch)) { - DrawDiamond(out, { center.x, center.y - AmLine(8) }, colorDim); + // Draw door or grate + if (tile.hasFlag(AutomapTile::Flags::HorizontalDoor)) { + DrawMapHorizontalDoor(out, center, nwTile, colorBright, colorDim); + } else if (tile.hasFlag(AutomapTile::Flags::HorizontalGrate)) { + DrawMapHorizontalGrate(out, center, colorDim); } } /** * Right-facing obstacle */ -void DrawVertical(const Surface &out, Point center, AutomapTile tile, uint8_t colorBright, uint8_t colorDim) +void DrawVertical(const Surface &out, Point center, AutomapTile tile, AutomapTile nwTile, AutomapTile neTile, AutomapTile swTile, uint8_t colorBright, uint8_t colorDim) { - if (!tile.HasFlag(AutomapTile::Flags::VerticalPassage)) { - DrawMapLineNE(out, { center.x - AmLine(32), center.y }, AmLine(16), colorDim); + AmWidthOffset w = AmWidthOffset::ThreeQuartersTileLeft; + AmHeightOffset h = AmHeightOffset::QuarterTileDown; + AmLineLength l = AmLineLength::FullAndHalfTile; + + // Draw a diamond in the top tile + if (neTile.hasAnyFlag(AutomapTile::Flags::VerticalArch, AutomapTile::Flags::VerticalGrate) // NE tile has an arch, so add a diamond for visual consistency + || nwTile.hasAnyFlag(AutomapTile::Flags::HorizontalArch, AutomapTile::Flags::HorizontalGrate) // NW tile has an arch, so add a diamond for visual consistency + || tile.hasAnyFlag(AutomapTile::Flags::VerticalArch, AutomapTile::Flags::HorizontalArch, AutomapTile::Flags::VerticalGrate, AutomapTile::Flags::HorizontalGrate) // Current tile has an arch, add a diamond + || tile.type == AutomapTile::Types::VerticalDiamond) { // wall ending in hell that should end with a diamond + l = AmLineLength::FullTile; // shorten line to avoid overdraw + DrawDiamond(out, center, colorDim); + FixVerticalDoor(out, center, nwTile, colorBright); + FixVerticalDoor(out, center, neTile, colorBright); + } + // Shorten line to avoid overdraw and adjust offset to match + if (IsAnyOf(leveltype, DTYPE_CAVES, DTYPE_NEST) + && IsAnyOf(tile.type, AutomapTile::Types::CaveHorizontalCross, AutomapTile::Types::CaveHorizontalWoodCross) + && !(IsAnyOf(swTile.type, AutomapTile::Types::Vertical, AutomapTile::Types::CaveHorizontalCross, AutomapTile::Types::CaveHorizontalWoodCross, AutomapTile::Types::Corner))) { + w = AmWidthOffset::HalfTileLeft; + h = AmHeightOffset::None; + l = AmLineLength::FullTile; + } + // Draw the wall line if the wall is solid + if (!tile.hasFlag(AutomapTile::Flags::VerticalPassage)) { + DrawMapLineNE(out, center + AmOffset(w, h), AmLine(l), colorDim); return; } - if (tile.HasFlag(AutomapTile::Flags::VerticalDoor)) { // two wall segments with a door in the middle - DrawMapVerticalDoor(out, { center.x - AmLine(16), center.y - AmLine(8) }, colorBright, colorDim); + // Draw door or grate + if (tile.hasFlag(AutomapTile::Flags::VerticalDoor)) { + DrawMapVerticalDoor(out, center, neTile, colorBright, colorDim); + } else if (tile.hasFlag(AutomapTile::Flags::VerticalGrate)) { + DrawMapVerticalGrate(out, center, colorDim); + } +} + +/** + * @brief Draw half-tile length lines to connect walls to any walls to the south-west and/or south-east + * (For caves the horizontal/vertical flags are swapped) + */ +void DrawCaveWallConnections(const Surface &out, Point center, AutomapTile sTile, AutomapTile swTile, AutomapTile seTile, uint8_t colorDim) +{ + if (IsAnyOf(swTile.type, AutomapTile::Types::CaveVerticalWallLava, AutomapTile::Types::CaveVertical, AutomapTile::Types::CaveVerticalWood, AutomapTile::Types::CaveCross, AutomapTile::Types::CaveWoodCross, AutomapTile::Types::CaveRightWoodCross, AutomapTile::Types::CaveLeftWoodCross, AutomapTile::Types::CaveRightCorner)) { + DrawMapLineNE(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), AmLine(AmLineLength::HalfTile), colorDim); + } + if (IsAnyOf(seTile.type, AutomapTile::Types::CaveHorizontalWallLava, AutomapTile::Types::CaveHorizontal, AutomapTile::Types::CaveHorizontalWood, AutomapTile::Types::CaveCross, AutomapTile::Types::CaveWoodCross, AutomapTile::Types::CaveRightWoodCross, AutomapTile::Types::CaveLeftWoodCross, AutomapTile::Types::CaveLeftCorner)) { + DrawMapLineSE(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), AmLine(AmLineLength::HalfTile), colorDim); } - if (tile.HasFlag(AutomapTile::Flags::VerticalGrate)) { // right-facing half-wall - DrawMapLineNE(out, { center.x - AmLine(32), center.y }, AmLine(8), colorDim); - DrawDiamond(out, { center.x, center.y - AmLine(8) }, colorDim); - } else if (tile.HasFlag(AutomapTile::Flags::VerticalArch)) { // window or passable column - DrawDiamond(out, { center.x, center.y - AmLine(8) }, colorDim); +} +void DrawCaveHorizontalDirt(const Surface &out, Point center, AutomapTile tile, AutomapTile swTile, uint8_t colorDim) +{ + if (swTile.hasFlag(AutomapTile::Flags::Dirt) || (leveltype != DTYPE_TOWN && IsNoneOf(tile.type, AutomapTile::Types::CaveHorizontalWood, AutomapTile::Types::CaveHorizontalWoodCross, AutomapTile::Types::CaveWoodCross, AutomapTile::Types::CaveLeftWoodCross))) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileLeft, AmHeightOffset::QuarterTileDown), colorDim); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), colorDim); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), colorDim); + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), colorDim); } } /** * For caves the horizontal/vertical flags are swapped */ -void DrawCaveHorizontal(const Surface &out, Point center, AutomapTile tile, uint8_t colorBright, uint8_t colorDim) +void DrawCaveHorizontal(const Surface &out, Point center, AutomapTile tile, AutomapTile nwTile, AutomapTile swTile, uint8_t colorBright, uint8_t colorDim) { - if (tile.HasFlag(AutomapTile::Flags::VerticalDoor)) { - DrawMapHorizontalDoor(out, { center.x - AmLine(16), center.y + AmLine(8) }, colorBright, colorDim); + if (tile.hasFlag(AutomapTile::Flags::VerticalDoor)) { + DrawMapHorizontalDoor(out, center, nwTile, colorBright, colorDim); } else { - DrawMapLineSE(out, { center.x - AmLine(32), center.y }, AmLine(16), colorDim); + AmWidthOffset w; + AmHeightOffset h; + AmLineLength l; + + if (IsAnyOf(tile.type, AutomapTile::Types::CaveHorizontalCross, AutomapTile::Types::CaveHorizontalWoodCross)) { + w = AmWidthOffset::HalfTileLeft; + h = AmHeightOffset::None; + l = AmLineLength::FullTile; + } else { + w = AmWidthOffset::ThreeQuartersTileLeft; + h = AmHeightOffset::QuarterTileUp; + l = AmLineLength::FullAndHalfTile; + } + DrawCaveHorizontalDirt(out, center, tile, swTile, colorDim); + DrawMapLineSE(out, center + AmOffset(w, h), AmLine(l), colorDim); + } +} + +void DrawCaveVerticalDirt(const Surface &out, Point center, AutomapTile tile, AutomapTile seTile, uint8_t colorDim) +{ + if (seTile.hasFlag(AutomapTile::Flags::Dirt) || (leveltype != DTYPE_TOWN && IsNoneOf(tile.type, AutomapTile::Types::CaveVerticalWood, AutomapTile::Types::CaveVerticalWoodCross, AutomapTile::Types::CaveWoodCross, AutomapTile::Types::CaveRightWoodCross))) { + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), colorDim); + SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), colorDim); + SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), colorDim); + SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), colorDim); } } /** * For caves the horizontal/vertical flags are swapped */ -void DrawCaveVertical(const Surface &out, Point center, AutomapTile tile, uint8_t colorBright, uint8_t colorDim) +void DrawCaveVertical(const Surface &out, Point center, AutomapTile tile, AutomapTile neTile, AutomapTile seTile, uint8_t colorBright, uint8_t colorDim) { - if (tile.HasFlag(AutomapTile::Flags::HorizontalDoor)) { - DrawMapVerticalDoor(out, { center.x + AmLine(16), center.y + AmLine(8) }, colorBright, colorDim); + if (tile.hasFlag(AutomapTile::Flags::HorizontalDoor)) { + DrawMapVerticalDoor(out, center, neTile, colorBright, colorDim); } else { - DrawMapLineNE(out, { center.x, center.y + AmLine(16) }, AmLine(16), colorDim); + AmLineLength l; + + if (IsAnyOf(tile.type, AutomapTile::Types::CaveVerticalCross, AutomapTile::Types::CaveVerticalWoodCross)) { + l = AmLineLength::FullTile; + } else { + l = AmLineLength::FullAndHalfTile; + } + DrawCaveVerticalDirt(out, center, tile, seTile, colorDim); + DrawMapLineNE(out, { center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown) }, AmLine(l), colorDim); + } +} + +void DrawCaveLeftCorner(const Surface &out, Point center, uint8_t colorDim) +{ + DrawMapLineSE(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileLeft, AmHeightOffset::QuarterTileUp), AmLine(AmLineLength::HalfTile), colorDim); + DrawMapLineNE(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileLeft, AmHeightOffset::QuarterTileDown), AmLine(AmLineLength::HalfTile), colorDim); +} + +void DrawCaveRightCorner(const Surface &out, Point center, uint8_t colorDim) +{ + DrawMapLineSE(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), AmLine(AmLineLength::HalfTile), colorDim); + DrawMapLineNE(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), AmLine(AmLineLength::HalfTile), colorDim); +} + +void DrawMapEllipse(const Surface &out, Point from, int radius, uint8_t colorIndex) +{ + const int a = radius; + const int b = radius / 2; + + int x = 0; + int y = b; + + // Offset ellipse so the center of the ellipse is the center of our megatile on the x plane + from.x -= radius; + + // Initial point + out.SetPixel({ from.x, from.y + b }, colorIndex); + out.SetPixel({ from.x, from.y - b }, colorIndex); + + // Initialize the parameters + int p1 = (b * b) - (a * a * b) + (a * a) / 4; + + // Region 1 + while ((b * b * x) < (a * a * y)) { + x++; + if (p1 < 0) { + p1 += (2 * b * b * x) + (b * b); + } else { + y--; + p1 += (2 * b * b * x) - (2 * a * a * y) + (b * b); + } + + out.SetPixel({ from.x + x, from.y + y }, colorIndex); + out.SetPixel({ from.x - x, from.y + y }, colorIndex); + out.SetPixel({ from.x + x, from.y - y }, colorIndex); + out.SetPixel({ from.x - x, from.y - y }, colorIndex); + } + + // Initialize the second parameter for Region 2 + int p2 = (b * b * ((x + 1) * (x + 1))) + (a * a * ((y - 1) * (y - 1))) - (a * a * b * b); + + // Region 2 + while (y > 0) { + y--; + if (p2 > 0) { + p2 += (-2 * a * a * y) + (a * a); + } else { + x++; + p2 += (2 * b * b * x) - (2 * a * a * y) + (a * a); + } + + out.SetPixel({ from.x + x, from.y + y }, colorIndex); + out.SetPixel({ from.x - x, from.y + y }, colorIndex); + out.SetPixel({ from.x + x, from.y - y }, colorIndex); + out.SetPixel({ from.x - x, from.y - y }, colorIndex); } } +void DrawMapStar(const Surface &out, Point from, int radius, uint8_t color) +{ + const int scaleFactor = 128; + Point anchors[5]; + + // Offset star so the center of the star is the center of our megatile on the x plane + from.x -= radius; + + anchors[0] = { from.x - (121 * radius / scaleFactor), from.y + (19 * radius / scaleFactor) }; // Left Point + anchors[1] = { from.x + (121 * radius / scaleFactor), from.y + (19 * radius / scaleFactor) }; // Right Point + anchors[2] = { from.x, from.y + (64 * radius / scaleFactor) }; // Bottom Point + anchors[3] = { from.x - (75 * radius / scaleFactor), from.y - (51 * radius / scaleFactor) }; // Top Left Point + anchors[4] = { from.x + (75 * radius / scaleFactor), from.y - (51 * radius / scaleFactor) }; // Top Right Point + + // Draw lines between the anchors to form a star + DrawMapFreeLine(out, anchors[3], anchors[1], color); // Connect Top Left -> Right + DrawMapFreeLine(out, anchors[1], anchors[0], color); // Connect Right -> Left + DrawMapFreeLine(out, anchors[0], anchors[4], color); // Connect Left -> Top Right + DrawMapFreeLine(out, anchors[4], anchors[2], color); // Connect Top Right -> Bottom + DrawMapFreeLine(out, anchors[2], anchors[3], color); // Connect Bottom -> Top Left +} + /** * @brief Check if a given tile has the provided AutomapTile flag */ @@ -416,13 +913,13 @@ bool HasAutomapFlag(Point position, AutomapTile::Flags type) return false; } - return AutomapTypeTiles[dungeon[position.x][position.y]].HasFlag(type); + return AutomapTypeTiles[dungeon[position.x][position.y]].hasFlag(type); } /** * @brief Returns the automap shape at the given coordinate. */ -AutomapTile GetAutomapType(Point position) +AutomapTile GetAutomapTileType(Point position) { if (position.x < 0 || position.x >= DMAXX || position.y < 0 || position.y >= DMAXX) { return {}; @@ -469,7 +966,7 @@ AutomapTile GetAutomapTypeView(Point map) return {}; } - return GetAutomapType(map); + return GetAutomapTileType(map); } /** @@ -477,10 +974,9 @@ AutomapTile GetAutomapTypeView(Point map) */ void DrawAutomapTile(const Surface &out, Point center, Point map) { - AutomapTile tile = GetAutomapTypeView(map); uint8_t colorBright = MapColorsBright; uint8_t colorDim = MapColorsDim; - MapExplorationType explorationType = static_cast(AutomapView[clamp(map.x, 0, DMAXX - 1)][clamp(map.y, 0, DMAXY - 1)]); + MapExplorationType explorationType = static_cast(AutomapView[std::clamp(map.x, 0, DMAXX - 1)][std::clamp(map.y, 0, DMAXY - 1)]); switch (explorationType) { case MAP_EXP_SHRINE: @@ -497,49 +993,138 @@ void DrawAutomapTile(const Surface &out, Point center, Point map) break; } - if (tile.HasFlag(AutomapTile::Flags::Dirt)) { - DrawDirt(out, center, colorDim); + bool noConnect = false; + AutomapTile tile = GetAutomapTypeView(map + Direction::NoDirection); + AutomapTile nwTile = GetAutomapTypeView(map + Direction::NorthWest); + AutomapTile neTile = GetAutomapTypeView(map + Direction::NorthEast); + +#ifdef _DEBUG + if (DebugVision) { + if (IsTileLit(map.megaToWorld())) + DrawDiamond(out, center, PAL8_ORANGE + 1); + if (IsTileLit(map.megaToWorld() + Direction::South)) + DrawDiamond(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), PAL8_ORANGE + 1); + if (IsTileLit(map.megaToWorld() + Direction::SouthWest)) + DrawDiamond(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), PAL8_ORANGE + 1); + if (IsTileLit(map.megaToWorld() + Direction::SouthEast)) + DrawDiamond(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), PAL8_ORANGE + 1); + } +#endif + + // If the tile is an arch, grate, or diamond, we draw a diamond and therefore don't want connection lines + if (tile.hasAnyFlag(AutomapTile::Flags::HorizontalArch, AutomapTile::Flags::VerticalArch, AutomapTile::Flags::HorizontalGrate, AutomapTile::Flags::VerticalGrate) + || nwTile.hasAnyFlag(AutomapTile::Flags::HorizontalArch, AutomapTile::Flags::HorizontalGrate) + || neTile.hasAnyFlag(AutomapTile::Flags::VerticalArch, AutomapTile::Flags::VerticalGrate) + || tile.type == AutomapTile::Types::Diamond) { + noConnect = true; + } + + // These tilesets have doors where the connection lines would be drawn + if (IsAnyOf(leveltype, DTYPE_CATACOMBS, DTYPE_CAVES) && (tile.hasFlag(AutomapTile::Flags::HorizontalDoor) || tile.hasFlag(AutomapTile::Flags::VerticalDoor))) + noConnect = true; + + const AutomapTile swTile = GetAutomapTypeView(map + Direction::SouthWest); + const AutomapTile sTile = GetAutomapTypeView(map + Direction::South); + const AutomapTile seTile = GetAutomapTypeView(map + Direction::SouthEast); + const AutomapTile nTile = GetAutomapTypeView(map + Direction::North); + const AutomapTile wTile = GetAutomapTypeView(map + Direction::West); + const AutomapTile eTile = GetAutomapTypeView(map + Direction::East); + + if ((leveltype == DTYPE_TOWN && tile.hasFlag(AutomapTile::Flags::Dirt)) + || (tile.hasFlag(AutomapTile::Flags::Dirt) + && (tile.type != AutomapTile::Types::None + || swTile.type != AutomapTile::Types::None + || sTile.type != AutomapTile::Types::None + || seTile.type != AutomapTile::Types::None + || IsAnyOf(nwTile.type, AutomapTile::Types::CaveCross, AutomapTile::Types::CaveVertical, AutomapTile::Types::CaveVerticalCross, AutomapTile::Types::CaveVerticalWallLava, AutomapTile::Types::CaveLeftWoodCross) + || IsAnyOf(nTile.type, AutomapTile::Types::CaveCross) + || IsAnyOf(neTile.type, AutomapTile::Types::CaveCross, AutomapTile::Types::CaveHorizontal, AutomapTile::Types::CaveHorizontalCross, AutomapTile::Types::CaveHorizontalWallLava, AutomapTile::Types::CaveRightWoodCross) + || IsAnyOf(wTile.type, AutomapTile::Types::CaveVerticalCross) + || IsAnyOf(eTile.type, AutomapTile::Types::CaveHorizontalCross)))) { + DrawDirt(out, center, nwTile, neTile, colorDim); } - if (tile.HasFlag(AutomapTile::Flags::Stairs)) { + if (tile.hasFlag(AutomapTile::Flags::Stairs)) { DrawStairs(out, center, colorBright); } + if (!noConnect) { + if (IsAnyOf(leveltype, DTYPE_TOWN, DTYPE_CAVES, DTYPE_NEST)) { + DrawCaveWallConnections(out, center, sTile, swTile, seTile, colorDim); + } + DrawWallConnections(out, center, tile, nwTile, neTile, colorBright, colorDim); + } + + uint8_t lavaColor = MapColorsLava; + if (leveltype == DTYPE_NEST) { + lavaColor = MapColorsAcid; + } else if (setlevel && setlvlnum == Quests[Q_PWATER]._qslvl) { + if (Quests[Q_PWATER]._qactive != QUEST_DONE) { + lavaColor = MapColorsAcid; + } else { + lavaColor = MapColorsWater; + } + } + switch (tile.type) { case AutomapTile::Types::Diamond: // stand-alone column or other unpassable object - DrawDiamond(out, { center.x, center.y - AmLine(8) }, colorDim); + DrawDiamond(out, center, colorDim); break; case AutomapTile::Types::Vertical: case AutomapTile::Types::FenceVertical: - DrawVertical(out, center, tile, colorBright, colorDim); + case AutomapTile::Types::VerticalDiamond: + DrawVertical(out, center, tile, nwTile, neTile, swTile, colorBright, colorDim); break; case AutomapTile::Types::Horizontal: case AutomapTile::Types::FenceHorizontal: - DrawHorizontal(out, center, tile, colorBright, colorDim); + case AutomapTile::Types::HorizontalDiamond: + DrawHorizontal(out, center, tile, nwTile, neTile, seTile, colorBright, colorDim); break; case AutomapTile::Types::Cross: - DrawVertical(out, center, tile, colorBright, colorDim); - DrawHorizontal(out, center, tile, colorBright, colorDim); + DrawVertical(out, center, tile, nwTile, neTile, swTile, colorBright, colorDim); + DrawHorizontal(out, center, tile, nwTile, neTile, seTile, colorBright, colorDim); break; case AutomapTile::Types::CaveHorizontalCross: - DrawVertical(out, center, tile, colorBright, colorDim); - DrawCaveHorizontal(out, center, tile, colorBright, colorDim); + case AutomapTile::Types::CaveHorizontalWoodCross: + DrawVertical(out, center, tile, nwTile, neTile, swTile, colorBright, colorDim); + DrawCaveHorizontal(out, center, tile, nwTile, swTile, colorBright, colorDim); break; case AutomapTile::Types::CaveVerticalCross: - DrawHorizontal(out, center, tile, colorBright, colorDim); - DrawCaveVertical(out, center, tile, colorBright, colorDim); + case AutomapTile::Types::CaveVerticalWoodCross: + DrawHorizontal(out, center, tile, nwTile, neTile, seTile, colorBright, colorDim); + DrawCaveVertical(out, center, tile, neTile, seTile, colorBright, colorDim); break; case AutomapTile::Types::CaveHorizontal: - DrawCaveHorizontal(out, center, tile, colorBright, colorDim); + case AutomapTile::Types::CaveHorizontalWood: + DrawCaveHorizontal(out, center, tile, nwTile, swTile, colorBright, colorDim); break; case AutomapTile::Types::CaveVertical: - DrawCaveVertical(out, center, tile, colorBright, colorDim); + case AutomapTile::Types::CaveVerticalWood: + DrawCaveVertical(out, center, tile, neTile, seTile, colorBright, colorDim); break; case AutomapTile::Types::CaveCross: - DrawCaveHorizontal(out, center, tile, colorBright, colorDim); - DrawCaveVertical(out, center, tile, colorBright, colorDim); + // Add the missing dirt pixel + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), colorDim); + [[fallthrough]]; + case AutomapTile::Types::CaveWoodCross: + case AutomapTile::Types::CaveRightWoodCross: + case AutomapTile::Types::CaveLeftWoodCross: + DrawCaveHorizontal(out, center, tile, nwTile, swTile, colorBright, colorDim); + DrawCaveVertical(out, center, tile, neTile, seTile, colorBright, colorDim); + break; + case AutomapTile::Types::CaveLeftCorner: + DrawCaveLeftCorner(out, center, colorDim); + break; + case AutomapTile::Types::CaveRightCorner: + DrawCaveRightCorner(out, center, colorDim); break; case AutomapTile::Types::Corner: + break; + case AutomapTile::Types::CaveBottomCorner: + // Add the missing dirt pixel + // BUGFIX: A tile in poisoned water supply isn't drawing this pixel + SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), colorDim); + break; case AutomapTile::Types::None: break; case AutomapTile::Types::Bridge: @@ -578,7 +1163,108 @@ void DrawAutomapTile(const Surface &out, Point center, Point map) case AutomapTile::Types::RiverRightOut: DrawRiverRightOut(out, center, MapColorsItem); break; + case AutomapTile::Types::HorizontalLavaThin: + DrawLavaRiver(out, center, lavaColor, false); + break; + case AutomapTile::Types::VerticalLavaThin: + DrawLavaRiver(out, center, lavaColor, false); + break; + case AutomapTile::Types::BendSouthLavaThin: + DrawLavaRiver(out, center, lavaColor, false); + break; + case AutomapTile::Types::BendWestLavaThin: + DrawLavaRiver(out, center, lavaColor, false); + break; + case AutomapTile::Types::BendEastLavaThin: + DrawLavaRiver(out, center, lavaColor, false); + break; + case AutomapTile::Types::BendNorthLavaThin: + DrawLavaRiver(out, center, lavaColor, false); + break; + case AutomapTile::Types::VerticalWallLava: + DrawVertical(out, center, tile, nwTile, neTile, swTile, colorBright, colorDim); + DrawLavaRiver(out, center, lavaColor, false); + break; + case AutomapTile::Types::HorizontalWallLava: + DrawHorizontal(out, center, tile, nwTile, neTile, swTile, colorBright, colorDim); + DrawLavaRiver(out, center, lavaColor, false); + break; + case AutomapTile::Types::SELava: + DrawLava(out, center, lavaColor); + break; + case AutomapTile::Types::SWLava: + DrawLava(out, center, lavaColor); + break; + case AutomapTile::Types::NELava: + DrawLava(out, center, lavaColor); + break; + case AutomapTile::Types::NWLava: + DrawLava(out, center, lavaColor); + break; + case AutomapTile::Types::SLava: + DrawLava(out, center, lavaColor); + break; + case AutomapTile::Types::WLava: + DrawLava(out, center, lavaColor); + break; + case AutomapTile::Types::ELava: + DrawLava(out, center, lavaColor); + break; + case AutomapTile::Types::NLava: + DrawLava(out, center, lavaColor); + break; + case AutomapTile::Types::Lava: + DrawLava(out, center, lavaColor); + break; + case AutomapTile::Types::CaveHorizontalWallLava: + DrawCaveHorizontal(out, center, tile, nwTile, swTile, colorBright, colorDim); + DrawLavaRiver(out, center, lavaColor, false); + break; + case AutomapTile::Types::CaveVerticalWallLava: + DrawCaveVertical(out, center, tile, neTile, seTile, colorBright, colorDim); + DrawLavaRiver(out, center, lavaColor, false); + break; + case AutomapTile::Types::HorizontalBridgeLava: + DrawLavaRiver(out, center, lavaColor, true); + break; + case AutomapTile::Types::VerticalBridgeLava: + DrawLavaRiver(out, center, lavaColor, true); + break; + case AutomapTile::Types::PentagramClosed: + // Functions are called twice to integrate shadow. Shadows are not drawn inside these functions to avoid shadows being drawn on top of normal pixels. + DrawMapEllipse(out, center + Displacement { 0, 1 }, AmLine(AmLineLength::OctupleTile), 0); // shadow + DrawMapStar(out, center + Displacement { 0, 1 }, AmLine(AmLineLength::OctupleTile), 0); // shadow + DrawMapEllipse(out, center, AmLine(AmLineLength::OctupleTile), colorDim); + DrawMapStar(out, center, AmLine(AmLineLength::OctupleTile), colorDim); + break; + case AutomapTile::Types::PentagramOpen: + // Functions are called twice to integrate shadow. Shadows are not drawn inside these functions to avoid shadows being drawn on top of normal pixels. + DrawMapEllipse(out, center + Displacement { 0, 1 }, AmLine(AmLineLength::OctupleTile), 0); // shadow + DrawMapStar(out, center + Displacement { 0, 1 }, AmLine(AmLineLength::OctupleTile), 0); // shadow + DrawMapEllipse(out, center, AmLine(AmLineLength::OctupleTile), MapColorsPentagramOpen); + DrawMapStar(out, center, AmLine(AmLineLength::OctupleTile), MapColorsPentagramOpen); + break; + } +} + +Displacement GetAutomapScreen() +{ + Displacement screen = {}; + + if (GetAutomapType() == AutomapType::Minimap) { + screen = { + MinimapRect.position.x + MinimapRect.size.width / 2, + MinimapRect.position.y + MinimapRect.size.height / 2 + }; + } else { + screen = { + gnScreenWidth / 2, + (gnScreenHeight - GetMainPanel().size.height) / 2 + }; } + screen += AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown); + + return screen; } void SearchAutomapItem(const Surface &out, const Displacement &myPlayerOffset, int searchRadius, tl::function_ref highlightTile) @@ -593,11 +1279,13 @@ void SearchAutomapItem(const Surface &out, const Displacement &myPlayerOffset, i tile.y++; } - const int startX = clamp(tile.x - searchRadius, 0, MAXDUNX); - const int startY = clamp(tile.y - searchRadius, 0, MAXDUNY); + const int startX = std::clamp(tile.x - searchRadius, 0, MAXDUNX); + const int startY = std::clamp(tile.y - searchRadius, 0, MAXDUNY); - const int endX = clamp(tile.x + searchRadius, 0, MAXDUNX); - const int endY = clamp(tile.y + searchRadius, 0, MAXDUNY); + const int endX = std::clamp(tile.x + searchRadius, 0, MAXDUNX); + const int endY = std::clamp(tile.y + searchRadius, 0, MAXDUNY); + + int scale = (GetAutomapType() == AutomapType::Minimap) ? MinimapScale : AutoMapScale; for (int i = startX; i < endX; i++) { for (int j = startY; j < endY; j++) { @@ -608,8 +1296,8 @@ void SearchAutomapItem(const Surface &out, const Displacement &myPlayerOffset, i int py = j - 2 * AutomapOffset.deltaY - ViewPosition.y; Point screen = { - (myPlayerOffset.deltaX * AutoMapScale / 100 / 2) + (px - py) * AmLine(16) + gnScreenWidth / 2, - (myPlayerOffset.deltaY * AutoMapScale / 100 / 2) + (px + py) * AmLine(8) + (gnScreenHeight - GetMainPanel().size.height) / 2 + (myPlayerOffset.deltaX * scale / 100 / 2) + (px - py) * AmLine(AmLineLength::DoubleTile) + gnScreenWidth / 2, + (myPlayerOffset.deltaY * scale / 100 / 2) + (px + py) * AmLine(AmLineLength::FullTile) + (gnScreenHeight - GetMainPanel().size.height) / 2 }; if (CanPanelsCoverView()) { @@ -618,7 +1306,7 @@ void SearchAutomapItem(const Surface &out, const Displacement &myPlayerOffset, i if (IsLeftPanelOpen()) screen.x += 160; } - screen.y -= AmLine(8); + screen.y -= AmLine(AmLineLength::FullTile); DrawDiamond(out, screen, MapColorsItem); } } @@ -627,11 +1315,10 @@ void SearchAutomapItem(const Surface &out, const Displacement &myPlayerOffset, i /** * @brief Renders an arrow on the automap, centered on and facing the direction of the player. */ -void DrawAutomapPlr(const Surface &out, const Displacement &myPlayerOffset, int playerId) +void DrawAutomapPlr(const Surface &out, const Displacement &myPlayerOffset, const Player &player) { - int playerColor = MapColorsPlayer + (8 * playerId) % 128; + const uint8_t playerColor = MapColorsPlayer + (8 * player.getId()) % 128; - Player &player = Players[playerId]; Point tile = player.position.tile; if (player._pmode == PM_WALK_SIDEWAYS) { tile = player.position.future; @@ -644,67 +1331,71 @@ void DrawAutomapPlr(const Surface &out, const Displacement &myPlayerOffset, int if (player.isWalking()) playerOffset = GetOffsetForWalking(player.AnimInfo, player._pdir); + int scale = (GetAutomapType() == AutomapType::Minimap) ? MinimapScale : AutoMapScale; + Point base = { - ((playerOffset.deltaX + myPlayerOffset.deltaX) * AutoMapScale / 100 / 2) + (px - py) * AmLine(16) + gnScreenWidth / 2, - ((playerOffset.deltaY + myPlayerOffset.deltaY) * AutoMapScale / 100 / 2) + (px + py) * AmLine(8) + (gnScreenHeight - GetMainPanel().size.height) / 2 + ((playerOffset.deltaX + myPlayerOffset.deltaX) * scale / 100 / 2) + (px - py) * AmLine(AmLineLength::DoubleTile), + ((playerOffset.deltaY + myPlayerOffset.deltaY) * scale / 100 / 2) + (px + py) * AmLine(AmLineLength::FullTile) + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown).deltaY }; + base += GetAutomapScreen(); + if (CanPanelsCoverView()) { if (IsRightPanelOpen()) base.x -= gnScreenWidth / 4; if (IsLeftPanelOpen()) base.x += gnScreenWidth / 4; } - base.y -= AmLine(16); + base.y -= AmLine(AmLineLength::DoubleTile); switch (player._pdir) { case Direction::North: { - const Point point { base.x, base.y - AmLine(16) }; - DrawVerticalLine(out, point, AmLine(16), playerColor); - DrawMapLineSteepNE(out, { point.x - AmLine(4), point.y + 2 * AmLine(4) }, AmLine(4), playerColor); - DrawMapLineSteepNW(out, { point.x + AmLine(4), point.y + 2 * AmLine(4) }, AmLine(4), playerColor); + const Point point = base + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileUp); + DrawMapLineNS(out, point, AmLine(AmLineLength::DoubleTile), playerColor); + DrawMapLineSteepNE(out, point + AmOffset(AmWidthOffset::EighthTileLeft, AmHeightOffset::HalfTileDown), AmLine(AmLineLength::HalfTile), playerColor); + DrawMapLineSteepNW(out, point + AmOffset(AmWidthOffset::EighthTileRight, AmHeightOffset::HalfTileDown), AmLine(AmLineLength::HalfTile), playerColor); } break; case Direction::NorthEast: { - const Point point { base.x + AmLine(16), base.y - AmLine(8) }; - DrawHorizontalLine(out, { point.x - AmLine(8), point.y }, AmLine(8), playerColor); - DrawMapLineNE(out, { point.x - 2 * AmLine(8), point.y + AmLine(8) }, AmLine(8), playerColor); - DrawMapLineSteepSW(out, point, AmLine(4), playerColor); + const Point point = base + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileUp); + DrawMapLineWE(out, point + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::None), AmLine(AmLineLength::FullTile), playerColor); + DrawMapLineNE(out, point + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), AmLine(AmLineLength::FullTile), playerColor); + DrawMapLineSteepSW(out, point, AmLine(AmLineLength::HalfTile), playerColor); } break; case Direction::East: { - const Point point { base.x + AmLine(16), base.y }; - DrawMapLineNW(out, point, AmLine(4), playerColor); - DrawHorizontalLine(out, { point.x - AmLine(16), point.y }, AmLine(16), playerColor); - DrawMapLineSW(out, point, AmLine(4), playerColor); + const Point point = base + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None); + DrawMapLineNW(out, point, AmLine(AmLineLength::HalfTile), playerColor); + DrawMapLineWE(out, point + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), AmLine(AmLineLength::DoubleTile), playerColor); + DrawMapLineSW(out, point, AmLine(AmLineLength::HalfTile), playerColor); } break; case Direction::SouthEast: { - const Point point { base.x + AmLine(16), base.y + AmLine(8) }; - DrawMapLineSteepNW(out, point, AmLine(4), playerColor); - DrawMapLineSE(out, { point.x - 2 * AmLine(8), point.y - AmLine(8) }, AmLine(8), playerColor); - DrawHorizontalLine(out, { point.x - (AmLine(8) + 1), point.y }, AmLine(8) + 1, playerColor); + const Point point = base + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown); + DrawMapLineSteepNW(out, point, AmLine(AmLineLength::HalfTile), playerColor); + DrawMapLineSE(out, point + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::FullTile), playerColor); + DrawMapLineWE(out, point + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::None) + Displacement { -1, 0 }, AmLine(AmLineLength::FullTile) + 1, playerColor); } break; case Direction::South: { - const Point point { base.x, base.y + AmLine(16) }; - DrawVerticalLine(out, { point.x, point.y - AmLine(16) }, AmLine(16), playerColor); - DrawMapLineSteepSW(out, { point.x + AmLine(4), point.y - 2 * AmLine(4) }, AmLine(4), playerColor); - DrawMapLineSteepSE(out, { point.x - AmLine(4), point.y - 2 * AmLine(4) }, AmLine(4), playerColor); + const Point point = base + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown); + DrawMapLineNS(out, point + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileUp), AmLine(AmLineLength::DoubleTile), playerColor); + DrawMapLineSteepSW(out, point + AmOffset(AmWidthOffset::EighthTileRight, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::HalfTile), playerColor); + DrawMapLineSteepSE(out, point + AmOffset(AmWidthOffset::EighthTileLeft, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::HalfTile), playerColor); } break; case Direction::SouthWest: { - const Point point { base.x - AmLine(16), base.y + AmLine(8) }; - DrawMapLineSteepNE(out, point, AmLine(4), playerColor); - DrawMapLineSW(out, { point.x + 2 * AmLine(8), point.y - AmLine(8) }, AmLine(8), playerColor); - DrawHorizontalLine(out, point, AmLine(8) + 1, playerColor); + const Point point = base + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown); + DrawMapLineSteepNE(out, point, AmLine(AmLineLength::HalfTile), playerColor); + DrawMapLineSW(out, point + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::FullTile), playerColor); + DrawMapLineWE(out, point, AmLine(AmLineLength::FullTile) + 1, playerColor); } break; case Direction::West: { - const Point point { base.x - AmLine(16), base.y }; - DrawMapLineNE(out, point, AmLine(4), playerColor); - DrawHorizontalLine(out, point, AmLine(16) + 1, playerColor); - DrawMapLineSE(out, point, AmLine(4), playerColor); + const Point point = base + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None); + DrawMapLineNE(out, point, AmLine(AmLineLength::HalfTile), playerColor); + DrawMapLineWE(out, point, AmLine(AmLineLength::DoubleTile) + 1, playerColor); + DrawMapLineSE(out, point, AmLine(AmLineLength::HalfTile), playerColor); } break; case Direction::NorthWest: { - const Point point { base.x - AmLine(16), base.y - AmLine(8) }; - DrawMapLineNW(out, { point.x + 2 * AmLine(8), point.y + AmLine(8) }, AmLine(8), playerColor); - DrawHorizontalLine(out, point, AmLine(8) + 1, playerColor); - DrawMapLineSteepSE(out, point, AmLine(4), playerColor); + const Point point = base + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileUp); + DrawMapLineNW(out, point + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), AmLine(AmLineLength::FullTile), playerColor); + DrawMapLineWE(out, point, AmLine(AmLineLength::FullTile) + 1, playerColor); + DrawMapLineSteepSE(out, point, AmLine(AmLineLength::HalfTile), playerColor); } break; case Direction::NoDirection: break; @@ -762,7 +1453,7 @@ void DrawAutomapText(const Surface &out) DrawString(out, description, linePosition); linePosition.y += 15; - string_view difficulty; + std::string_view difficulty; switch (sgGameInitInfo.nDifficulty) { case DIFF_NORMAL: difficulty = _("Normal"); @@ -777,6 +1468,37 @@ void DrawAutomapText(const Surface &out) std::string difficultyString = fmt::format(fmt::runtime(_(/* TRANSLATORS: {:s} means: Game Difficulty. */ "Difficulty: {:s}")), difficulty); DrawString(out, difficultyString, linePosition); + +#ifdef _DEBUG + const TextRenderOptions debugTextOptions { + .flags = UiFlags::ColorOrange, + }; + linePosition.y += 45; + if (DebugGodMode) { + linePosition.y += 15; + DrawString(out, "God Mode", 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); + } +#endif } std::unique_ptr LoadAutomapData(size_t &tileCount) @@ -804,20 +1526,148 @@ std::unique_ptr LoadAutomapData(size_t &tileCount) } // namespace bool AutomapActive; +AutomapType CurrentAutomapType = AutomapType::Opaque; uint8_t AutomapView[DMAXX][DMAXY]; int AutoMapScale; +int MinimapScale; Displacement AutomapOffset; +Rectangle MinimapRect {}; void InitAutomapOnce() { AutomapActive = false; AutoMapScale = 50; + + // Set the dimensions and screen position of the minimap relative to the screen dimensions + int minimapWidth = gnScreenWidth / 4; + Size minimapSize { minimapWidth, minimapWidth / 2 }; + int minimapPadding = gnScreenWidth / 128; + MinimapRect = Rectangle { { gnScreenWidth - minimapPadding - minimapSize.width, minimapPadding }, minimapSize }; + + // Set minimap scale + int height = 480; + int scale = 25; + int factor = gnScreenHeight / height; + + if (factor >= 8) { + MinimapScale = scale * 8; + } else { + MinimapScale = scale * factor; + } } void InitAutomap() { size_t tileCount = 0; std::unique_ptr tileTypes = LoadAutomapData(tileCount); + + switch (leveltype) { + case DTYPE_CATACOMBS: + tileTypes[41] = { AutomapTile::Types::FenceHorizontal }; + break; + case DTYPE_TOWN: // Town automap uses a dun file that contains caves tileset + case DTYPE_CAVES: + case DTYPE_NEST: + tileTypes[4] = { AutomapTile::Types::CaveBottomCorner }; + tileTypes[12] = { AutomapTile::Types::CaveRightCorner }; + tileTypes[13] = { AutomapTile::Types::CaveLeftCorner }; + if (IsAnyOf(leveltype, DTYPE_CAVES)) { + tileTypes[129] = { AutomapTile::Types::CaveHorizontalWoodCross }; + tileTypes[131] = { AutomapTile::Types::CaveHorizontalWoodCross }; + tileTypes[133] = { AutomapTile::Types::CaveHorizontalWood }; + tileTypes[135] = { AutomapTile::Types::CaveHorizontalWood }; + tileTypes[150] = { AutomapTile::Types::CaveHorizontalWood }; + tileTypes[145] = { AutomapTile::Types::CaveHorizontalWood, AutomapTile::Flags::VerticalDoor }; + tileTypes[147] = { AutomapTile::Types::CaveHorizontalWood, AutomapTile::Flags::VerticalDoor }; + tileTypes[130] = { AutomapTile::Types::CaveVerticalWoodCross }; + tileTypes[132] = { AutomapTile::Types::CaveVerticalWoodCross }; + tileTypes[134] = { AutomapTile::Types::CaveVerticalWood }; + tileTypes[136] = { AutomapTile::Types::CaveVerticalWood }; + tileTypes[151] = { AutomapTile::Types::CaveVerticalWood }; + tileTypes[146] = { AutomapTile::Types::CaveVerticalWood, AutomapTile::Flags::HorizontalDoor }; + tileTypes[148] = { AutomapTile::Types::CaveVerticalWood, AutomapTile::Flags::HorizontalDoor }; + tileTypes[137] = { AutomapTile::Types::CaveWoodCross }; + tileTypes[140] = { AutomapTile::Types::CaveWoodCross }; + tileTypes[141] = { AutomapTile::Types::CaveWoodCross }; + tileTypes[142] = { AutomapTile::Types::CaveWoodCross }; + tileTypes[138] = { AutomapTile::Types::CaveRightWoodCross }; + tileTypes[139] = { AutomapTile::Types::CaveLeftWoodCross }; + tileTypes[14] = { AutomapTile::Types::HorizontalLavaThin }; + tileTypes[15] = { AutomapTile::Types::HorizontalLavaThin }; + tileTypes[16] = { AutomapTile::Types::VerticalLavaThin }; + tileTypes[17] = { AutomapTile::Types::VerticalLavaThin }; + tileTypes[18] = { AutomapTile::Types::BendSouthLavaThin }; + tileTypes[19] = { AutomapTile::Types::BendWestLavaThin }; + tileTypes[20] = { AutomapTile::Types::BendEastLavaThin }; + tileTypes[21] = { AutomapTile::Types::BendNorthLavaThin }; + tileTypes[22] = { AutomapTile::Types::VerticalWallLava }; + tileTypes[23] = { AutomapTile::Types::HorizontalWallLava }; + tileTypes[24] = { AutomapTile::Types::SELava }; + tileTypes[25] = { AutomapTile::Types::SWLava }; + tileTypes[26] = { AutomapTile::Types::NELava }; + tileTypes[27] = { AutomapTile::Types::NWLava }; + tileTypes[28] = { AutomapTile::Types::SLava }; + tileTypes[29] = { AutomapTile::Types::WLava }; + tileTypes[30] = { AutomapTile::Types::ELava }; + tileTypes[31] = { AutomapTile::Types::NLava }; + tileTypes[32] = { AutomapTile::Types::Lava }; + tileTypes[33] = { AutomapTile::Types::Lava }; + tileTypes[34] = { AutomapTile::Types::Lava }; + tileTypes[35] = { AutomapTile::Types::Lava }; + tileTypes[36] = { AutomapTile::Types::Lava }; + tileTypes[37] = { AutomapTile::Types::Lava }; + tileTypes[38] = { AutomapTile::Types::Lava }; + tileTypes[39] = { AutomapTile::Types::Lava }; + tileTypes[40] = { AutomapTile::Types::Lava }; + tileTypes[41] = { AutomapTile::Types::CaveHorizontalWallLava }; + tileTypes[42] = { AutomapTile::Types::CaveVerticalWallLava }; + tileTypes[43] = { AutomapTile::Types::HorizontalBridgeLava }; + tileTypes[44] = { AutomapTile::Types::VerticalBridgeLava }; + } else if (IsAnyOf(leveltype, DTYPE_NEST)) { + tileTypes[102] = { AutomapTile::Types::HorizontalLavaThin }; + tileTypes[103] = { AutomapTile::Types::HorizontalLavaThin }; + tileTypes[108] = { AutomapTile::Types::HorizontalLavaThin }; + tileTypes[104] = { AutomapTile::Types::VerticalLavaThin }; + tileTypes[105] = { AutomapTile::Types::VerticalLavaThin }; + tileTypes[107] = { AutomapTile::Types::VerticalLavaThin }; + tileTypes[112] = { AutomapTile::Types::BendSouthLavaThin }; + tileTypes[113] = { AutomapTile::Types::BendWestLavaThin }; + tileTypes[110] = { AutomapTile::Types::BendEastLavaThin }; + tileTypes[111] = { AutomapTile::Types::BendNorthLavaThin }; + tileTypes[134] = { AutomapTile::Types::VerticalWallLava }; + tileTypes[135] = { AutomapTile::Types::HorizontalWallLava }; + tileTypes[118] = { AutomapTile::Types::SELava }; + tileTypes[119] = { AutomapTile::Types::SWLava }; + tileTypes[120] = { AutomapTile::Types::NELava }; + tileTypes[121] = { AutomapTile::Types::NWLava }; + tileTypes[106] = { AutomapTile::Types::SLava }; + tileTypes[114] = { AutomapTile::Types::WLava }; + tileTypes[130] = { AutomapTile::Types::ELava }; + tileTypes[122] = { AutomapTile::Types::NLava }; + tileTypes[117] = { AutomapTile::Types::Lava }; + tileTypes[124] = { AutomapTile::Types::Lava }; + tileTypes[126] = { AutomapTile::Types::Lava }; + tileTypes[127] = { AutomapTile::Types::Lava }; + tileTypes[128] = { AutomapTile::Types::Lava }; + tileTypes[129] = { AutomapTile::Types::Lava }; + tileTypes[131] = { AutomapTile::Types::Lava }; + tileTypes[132] = { AutomapTile::Types::Lava }; + tileTypes[133] = { AutomapTile::Types::Lava }; + tileTypes[136] = { AutomapTile::Types::CaveHorizontalWallLava }; + tileTypes[137] = { AutomapTile::Types::CaveVerticalWallLava }; + tileTypes[115] = { AutomapTile::Types::HorizontalBridgeLava }; + tileTypes[116] = { AutomapTile::Types::VerticalBridgeLava }; + } + break; + case DTYPE_HELL: + tileTypes[51] = { AutomapTile::Types::VerticalDiamond }; + tileTypes[55] = { AutomapTile::Types::HorizontalDiamond }; + tileTypes[102] = { AutomapTile::Types::PentagramClosed }; + tileTypes[111] = { AutomapTile::Types::PentagramOpen }; + break; + default: + break; + } for (unsigned i = 0; i < tileCount; i++) { AutomapTypeTiles[i + 1] = tileTypes[i]; } @@ -861,18 +1711,22 @@ void AutomapRight() void AutomapZoomIn() { - if (AutoMapScale >= 200) + int &scale = (GetAutomapType() == AutomapType::Minimap) ? MinimapScale : AutoMapScale; + + if (scale >= 200) return; - AutoMapScale += 5; + scale += 25; } void AutomapZoomOut() { - if (AutoMapScale <= 50) + int &scale = (GetAutomapType() == AutomapType::Minimap) ? MinimapScale : AutoMapScale; + + if (scale <= 25) return; - AutoMapScale -= 5; + scale -= 25; } void DrawAutomap(const Surface &out) @@ -898,37 +1752,58 @@ void DrawAutomap(const Surface &out) if (myPlayer.isWalking()) myPlayerOffset = GetOffsetForWalking(myPlayer.AnimInfo, myPlayer._pdir, true); - int d = (AutoMapScale * 64) / 100; + int scale = (GetAutomapType() == AutomapType::Minimap) ? MinimapScale : AutoMapScale; + int d = (scale * 64) / 100; int cells = 2 * (gnScreenWidth / 2 / d) + 1; if (((gnScreenWidth / 2) % d) != 0) cells++; - if (((gnScreenWidth / 2) % d) >= (AutoMapScale * 32) / 100) + if (((gnScreenWidth / 2) % d) >= (scale * 32) / 100) cells++; if ((myPlayerOffset.deltaX + myPlayerOffset.deltaY) != 0) cells++; - Point screen { - gnScreenWidth / 2, - (gnScreenHeight - GetMainPanel().size.height) / 2 - }; + if (GetAutomapType() == AutomapType::Minimap) { + // Background fill + DrawHalfTransparentRectTo(out, MinimapRect.position.x, MinimapRect.position.y, MinimapRect.size.width, MinimapRect.size.height); + + uint8_t frameShadowColor = PAL16_YELLOW + 12; + + // Shadow + DrawHorizontalLine(out, MinimapRect.position + Displacement { -1, -1 }, MinimapRect.size.width + 1, frameShadowColor); + DrawHorizontalLine(out, MinimapRect.position + Displacement { -2, MinimapRect.size.height + 1 }, MinimapRect.size.width + 4, frameShadowColor); + DrawVerticalLine(out, MinimapRect.position + Displacement { -1, 0 }, MinimapRect.size.height, frameShadowColor); + DrawVerticalLine(out, MinimapRect.position + Displacement { MinimapRect.size.width + 1, -2 }, MinimapRect.size.height + 3, frameShadowColor); + + // Frame + DrawHorizontalLine(out, MinimapRect.position + Displacement { -2, -2 }, MinimapRect.size.width + 3, MapColorsDim); + DrawHorizontalLine(out, MinimapRect.position + Displacement { -2, MinimapRect.size.height }, MinimapRect.size.width + 3, MapColorsDim); + DrawVerticalLine(out, MinimapRect.position + Displacement { -2, -1 }, MinimapRect.size.height + 1, MapColorsDim); + DrawVerticalLine(out, MinimapRect.position + Displacement { MinimapRect.size.width, -1 }, MinimapRect.size.height + 1, MapColorsDim); + } + + Point screen = {}; + + screen += GetAutomapScreen(); + if ((cells & 1) != 0) { - screen.x -= AmLine(64) * ((cells - 1) / 2); - screen.y -= AmLine(32) * ((cells + 1) / 2); + screen.x -= AmOffset(AmWidthOffset::DoubleTileRight, AmHeightOffset::None).deltaX * ((cells - 1) / 2); + screen.y -= AmOffset(AmWidthOffset::None, AmHeightOffset::DoubleTileDown).deltaY * ((cells + 1) / 2); + } else { - screen.x -= AmLine(64) * (cells / 2) - AmLine(32); - screen.y -= AmLine(32) * (cells / 2) + AmLine(16); + screen.x -= AmOffset(AmWidthOffset::DoubleTileRight, AmHeightOffset::None).deltaX * (cells / 2) + AmOffset(AmWidthOffset::FullTileLeft, AmHeightOffset::None).deltaX; + screen.y -= AmOffset(AmWidthOffset::None, AmHeightOffset::DoubleTileDown).deltaY * (cells / 2) + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown).deltaY; } if ((ViewPosition.x & 1) != 0) { - screen.x -= AmLine(16); - screen.y -= AmLine(8); + screen.x -= AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None).deltaX; + screen.y -= AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown).deltaY; } if ((ViewPosition.y & 1) != 0) { - screen.x += AmLine(16); - screen.y -= AmLine(8); + screen.x += AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None).deltaX; + screen.y -= AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown).deltaY; } - screen.x += AutoMapScale * myPlayerOffset.deltaX / 100 / 2; - screen.y += AutoMapScale * myPlayerOffset.deltaY / 100 / 2; + screen.x += scale * myPlayerOffset.deltaX / 100 / 2; + screen.y += scale * myPlayerOffset.deltaY / 100 / 2; if (CanPanelsCoverView()) { if (IsRightPanelOpen()) { @@ -945,23 +1820,22 @@ void DrawAutomap(const Surface &out) Point tile1 = screen; for (int j = 0; j < cells; j++) { DrawAutomapTile(out, tile1, { map.x + j, map.y - j }); - tile1.x += AmLine(64); + tile1.x += AmOffset(AmWidthOffset::DoubleTileRight, AmHeightOffset::None).deltaX; } map.y++; - Point tile2 { screen.x - AmLine(32), screen.y + AmLine(16) }; + Point tile2 = screen + AmOffset(AmWidthOffset::FullTileLeft, AmHeightOffset::FullTileDown); for (int j = 0; j <= cells; j++) { DrawAutomapTile(out, tile2, { map.x + j, map.y - j }); - tile2.x += AmLine(64); + tile2.x += AmOffset(AmWidthOffset::DoubleTileRight, AmHeightOffset::None).deltaX; } map.x++; - screen.y += AmLine(32); + screen.y += AmOffset(AmWidthOffset::None, AmHeightOffset::DoubleTileDown).deltaY; } - for (size_t playerId = 0; playerId < Players.size(); playerId++) { - Player &player = Players[playerId]; + for (const Player &player : Players) { if (player.isOnActiveLevel() && player.plractive && !player._pLvlChanging && (&player == MyPlayer || player.friendlyMode)) { - DrawAutomapPlr(out, myPlayerOffset, playerId); + DrawAutomapPlr(out, myPlayerOffset, player); } } @@ -991,14 +1865,14 @@ void SetAutomapView(Point position, MapExplorationType explorer) UpdateAutomapExplorer(map, explorer); - AutomapTile tile = GetAutomapType(map); - bool solid = tile.HasFlag(AutomapTile::Flags::Dirt); + AutomapTile tile = GetAutomapTileType(map); + bool solid = tile.hasFlag(AutomapTile::Flags::Dirt); switch (tile.type) { case AutomapTile::Types::Vertical: if (solid) { - auto tileSW = GetAutomapType({ map.x, map.y + 1 }); - if (tileSW.type == AutomapTile::Types::Corner && tileSW.HasFlag(AutomapTile::Flags::Dirt)) + auto tileSW = GetAutomapTileType({ map.x, map.y + 1 }); + if (tileSW.type == AutomapTile::Types::Corner && tileSW.hasFlag(AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x, map.y + 1 }, explorer); } else if (HasAutomapFlag({ map.x - 1, map.y }, AutomapTile::Flags::Dirt)) { UpdateAutomapExplorer({ map.x - 1, map.y }, explorer); @@ -1006,8 +1880,8 @@ void SetAutomapView(Point position, MapExplorationType explorer) break; case AutomapTile::Types::Horizontal: if (solid) { - auto tileSE = GetAutomapType({ map.x + 1, map.y }); - if (tileSE.type == AutomapTile::Types::Corner && tileSE.HasFlag(AutomapTile::Flags::Dirt)) + auto tileSE = GetAutomapTileType({ map.x + 1, map.y }); + if (tileSE.type == AutomapTile::Types::Corner && tileSE.hasFlag(AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x + 1, map.y }, explorer); } else if (HasAutomapFlag({ map.x, map.y - 1 }, AutomapTile::Flags::Dirt)) { UpdateAutomapExplorer({ map.x, map.y - 1 }, explorer); @@ -1015,11 +1889,11 @@ void SetAutomapView(Point position, MapExplorationType explorer) break; case AutomapTile::Types::Cross: if (solid) { - auto tileSW = GetAutomapType({ map.x, map.y + 1 }); - if (tileSW.type == AutomapTile::Types::Corner && tileSW.HasFlag(AutomapTile::Flags::Dirt)) + auto tileSW = GetAutomapTileType({ map.x, map.y + 1 }); + if (tileSW.type == AutomapTile::Types::Corner && tileSW.hasFlag(AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x, map.y + 1 }, explorer); - auto tileSE = GetAutomapType({ map.x + 1, map.y }); - if (tileSE.type == AutomapTile::Types::Corner && tileSE.HasFlag(AutomapTile::Flags::Dirt)) + auto tileSE = GetAutomapTileType({ map.x + 1, map.y }); + if (tileSE.type == AutomapTile::Types::Corner && tileSE.hasFlag(AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x + 1, map.y }, explorer); } else { if (HasAutomapFlag({ map.x - 1, map.y }, AutomapTile::Flags::Dirt)) @@ -1034,8 +1908,8 @@ void SetAutomapView(Point position, MapExplorationType explorer) if (solid) { if (HasAutomapFlag({ map.x, map.y - 1 }, AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x, map.y - 1 }, explorer); - auto tileSW = GetAutomapType({ map.x, map.y + 1 }); - if (tileSW.type == AutomapTile::Types::Corner && tileSW.HasFlag(AutomapTile::Flags::Dirt)) + auto tileSW = GetAutomapTileType({ map.x, map.y + 1 }); + if (tileSW.type == AutomapTile::Types::Corner && tileSW.hasFlag(AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x, map.y + 1 }, explorer); } else if (HasAutomapFlag({ map.x - 1, map.y }, AutomapTile::Flags::Dirt)) { UpdateAutomapExplorer({ map.x - 1, map.y }, explorer); @@ -1045,8 +1919,8 @@ void SetAutomapView(Point position, MapExplorationType explorer) if (solid) { if (HasAutomapFlag({ map.x - 1, map.y }, AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x - 1, map.y }, explorer); - auto tileSE = GetAutomapType({ map.x + 1, map.y }); - if (tileSE.type == AutomapTile::Types::Corner && tileSE.HasFlag(AutomapTile::Flags::Dirt)) + auto tileSE = GetAutomapTileType({ map.x + 1, map.y }); + if (tileSE.type == AutomapTile::Types::Corner && tileSE.hasFlag(AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x + 1, map.y }, explorer); } else if (HasAutomapFlag({ map.x, map.y - 1 }, AutomapTile::Flags::Dirt)) { UpdateAutomapExplorer({ map.x, map.y - 1 }, explorer); diff --git a/Source/automap.h b/Source/automap.h index b2b1cb23d37..dee4cd2736b 100644 --- a/Source/automap.h +++ b/Source/automap.h @@ -34,15 +34,99 @@ extern DVL_API_FOR_TEST bool AutomapActive; extern uint8_t AutomapView[DMAXX][DMAXY]; /** Specifies the scale of the automap. */ extern DVL_API_FOR_TEST int AutoMapScale; +extern DVL_API_FOR_TEST int MinimapScale; extern DVL_API_FOR_TEST Displacement AutomapOffset; +extern Rectangle MinimapRect; + +/** Defines the offsets used for Automap lines */ +enum class AmWidthOffset : int8_t { + None, + EighthTileRight = TILE_WIDTH >> 4, + QuarterTileRight = TILE_WIDTH >> 3, + HalfTileRight = TILE_WIDTH >> 2, + ThreeQuartersTileRight = (TILE_WIDTH >> 1) - (TILE_WIDTH >> 3), + FullTileRight = TILE_WIDTH >> 1, + DoubleTileRight = TILE_WIDTH, + EighthTileLeft = -EighthTileRight, + QuarterTileLeft = -QuarterTileRight, + HalfTileLeft = -HalfTileRight, + ThreeQuartersTileLeft = -ThreeQuartersTileRight, + FullTileLeft = -FullTileRight, + DoubleTileLeft = -DoubleTileRight, +}; + +enum class AmHeightOffset : int8_t { + None, + EighthTileDown = TILE_HEIGHT >> 4, + QuarterTileDown = TILE_HEIGHT >> 3, + HalfTileDown = TILE_HEIGHT >> 2, + ThreeQuartersTileDown = (TILE_HEIGHT >> 1) - (TILE_HEIGHT >> 3), + FullTileDown = TILE_HEIGHT >> 1, + DoubleTileDown = TILE_HEIGHT, + EighthTileUp = -EighthTileDown, + QuarterTileUp = -QuarterTileDown, + HalfTileUp = -HalfTileDown, + ThreeQuartersTileUp = -ThreeQuartersTileDown, + FullTileUp = -FullTileDown, + DoubleTileUp = -DoubleTileDown, +}; + +enum class AmLineLength : uint8_t { + QuarterTile = 2, + HalfTile = 4, + FullTile = 8, + FullAndHalfTile = 12, + DoubleTile = 16, + OctupleTile = 64, +}; + +enum class AutomapType : uint8_t { + Opaque, + FIRST = Opaque, + Transparent, + Minimap, + LAST = Minimap +}; + +extern DVL_API_FOR_TEST AutomapType CurrentAutomapType; + +/** + * @brief Sets the map type. Does not change `AutomapActive`. + */ +inline void SetAutomapType(AutomapType type) +{ + CurrentAutomapType = type; +} + +/** + * @brief Sets the map type. Does not change `AutomapActive`. + */ +inline AutomapType GetAutomapType() +{ + return CurrentAutomapType; +} + +inline Displacement AmOffset(AmWidthOffset x, AmHeightOffset y) +{ + int scale = (GetAutomapType() == AutomapType::Minimap) ? MinimapScale : AutoMapScale; + + return { scale * static_cast(x) / 100, scale * static_cast(y) / 100 }; +} -inline int AmLine(int x) +inline int AmLine(AmLineLength l) { - assert(x >= 4 && x <= 64); - assert((x & (x - 1)) == 0); - return AutoMapScale * x / 100; + int scale = (GetAutomapType() == AutomapType::Minimap) ? MinimapScale : AutoMapScale; + + return scale * static_cast(l) / 100; } +/** + * @brief Sets the map type. Does not change `AutomapActive`. + */ +void SetAutomapType(AutomapType type); + +AutomapType GetAutomapType(); + /** * @brief Initializes the automap. */ @@ -58,6 +142,11 @@ void InitAutomap(); */ void StartAutomap(); +/** + * @brief Displays the minimap. + */ +void StartMinimap(); + /** * @brief Scrolls the automap upwards. */ diff --git a/Source/codec.cpp b/Source/codec.cpp index 55e95598bc6..a58de0c6ce9 100644 --- a/Source/codec.cpp +++ b/Source/codec.cpp @@ -7,7 +7,6 @@ #include "sha.h" #include "utils/endian.hpp" #include "utils/log.hpp" -#include "utils/stdcompat/cstddef.hpp" namespace devilution { namespace { @@ -53,7 +52,7 @@ SHA1Context CodecInitKey(const char *pszPassword) return context; } -CodecSignature GetCodecSignature(byte *src) +CodecSignature GetCodecSignature(std::byte *src) { CodecSignature result; result.checksum = LoadLE32(src); @@ -63,16 +62,16 @@ CodecSignature GetCodecSignature(byte *src) return result; } -void SetCodecSignature(byte *dst, CodecSignature sig) +void SetCodecSignature(std::byte *dst, CodecSignature sig) { - *dst++ = static_cast(sig.checksum); - *dst++ = static_cast(sig.checksum >> 8); - *dst++ = static_cast(sig.checksum >> 16); - *dst++ = static_cast(sig.checksum >> 24); - *dst++ = static_cast(sig.error); - *dst++ = static_cast(sig.lastChunkSize); - *dst++ = static_cast(0); - *dst++ = static_cast(0); + *dst++ = static_cast(sig.checksum); + *dst++ = static_cast(sig.checksum >> 8); + *dst++ = static_cast(sig.checksum >> 16); + *dst++ = static_cast(sig.checksum >> 24); + *dst++ = static_cast(sig.error); + *dst++ = static_cast(sig.lastChunkSize); + *dst++ = static_cast(0); + *dst++ = static_cast(0); } void ByteSwapBlock(uint32_t *data) @@ -89,7 +88,7 @@ void XorBlock(const uint32_t *shaResult, uint32_t *out) } // namespace -std::size_t codec_decode(byte *pbSrcDst, std::size_t size, const char *pszPassword) +std::size_t codec_decode(std::byte *pbSrcDst, std::size_t size, const char *pszPassword) { uint32_t buf[BlockSize]; uint32_t dst[SHA1HashSize]; @@ -134,7 +133,7 @@ std::size_t codec_get_encoded_len(std::size_t dwSrcBytes) return dwSrcBytes + SignatureSize; } -void codec_encode(byte *pbSrcDst, std::size_t size, std::size_t size64, const char *pszPassword) +void codec_encode(std::byte *pbSrcDst, std::size_t size, std::size_t size64, const char *pszPassword) { uint32_t buf[BlockSize]; uint32_t tmp[SHA1HashSize]; diff --git a/Source/codec.h b/Source/codec.h index 5acc9d28dd1..f6d716d0221 100644 --- a/Source/codec.h +++ b/Source/codec.h @@ -5,12 +5,12 @@ */ #pragma once -#include "utils/stdcompat/cstddef.hpp" +#include namespace devilution { -std::size_t codec_decode(byte *pbSrcDst, std::size_t size, const char *pszPassword); +std::size_t codec_decode(std::byte *pbSrcDst, std::size_t size, const char *pszPassword); std::size_t codec_get_encoded_len(std::size_t dwSrcBytes); -void codec_encode(byte *pbSrcDst, std::size_t size, std::size_t size_64, const char *pszPassword); +void codec_encode(std::byte *pbSrcDst, std::size_t size, std::size_t size_64, const char *pszPassword); } // namespace devilution diff --git a/Source/control.cpp b/Source/control.cpp index 9a40e7f3a61..7919cff8d78 100644 --- a/Source/control.cpp +++ b/Source/control.cpp @@ -9,21 +9,24 @@ #include #include #include +#include +#include #include #include +#include "DiabloUI/text_input.hpp" #include "automap.h" #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/text_render.hpp" #include "engine/trn.hpp" -#include "error.h" #include "gamemenu.h" #include "init.h" #include "inv.h" @@ -35,6 +38,7 @@ #include "missiles.h" #include "options.h" #include "panels/charpanel.hpp" +#include "panels/console.hpp" #include "panels/mainpanel.hpp" #include "panels/spell_book.hpp" #include "panels/spell_icons.hpp" @@ -44,11 +48,14 @@ #include "qol/xpbar.h" #include "stores.h" #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_geometry.h" -#include "utils/stdcompat/optional.hpp" +#include "utils/sdl_ptrs.h" #include "utils/str_case.hpp" #include "utils/str_cat.hpp" #include "utils/string_or_view.hpp" @@ -61,19 +68,23 @@ namespace devilution { bool dropGoldFlag; +TextInputCursorState GoldDropCursor; +char GoldDropText[21]; +namespace { +int8_t GoldDropInvIndex; +std::optional GoldDropInputState; +} // namespace + bool chrbtn[4]; bool lvlbtndown; -int dropGoldValue; bool chrbtnactive; UiFlags InfoColor; int sbooktab; -int8_t initialDropGoldIndex; bool talkflag; bool sbookflag; bool chrflag; StringOrView InfoString; bool panelflag; -int initialDropGoldValue; bool panbtndown; bool spselflag; Rectangle MainPanel; @@ -145,6 +156,9 @@ bool TalkButtonsDown[3]; int sgbPlrTalkTbl; bool WhisperList[MAX_PLRS]; +TextInputCursorState ChatCursor; +std::optional ChatInputState; + enum panel_button_id : uint8_t { PanelButtonCharinfo, PanelButtonQlog, @@ -212,7 +226,7 @@ void DrawFlask(const Surface &out, const Surface &celBuf, Point sourcePosition, void DrawFlaskUpper(const Surface &out, const Surface &sourceBuffer, int offset, int fillPer) { // clamping because this function only draws the top 12% of the flask display - int emptyPortion = clamp(80 - fillPer, 0, 11) + 2; // +2 to account for the frame being included in the sprite + int emptyPortion = std::clamp(80 - fillPer, 0, 11) + 2; // +2 to account for the frame being included in the sprite // Draw the empty part of the flask DrawFlask(out, sourceBuffer, { 13, 3 }, GetMainPanel().position + Displacement { offset, -13 }, emptyPortion); @@ -231,7 +245,7 @@ void DrawFlaskUpper(const Surface &out, const Surface &sourceBuffer, int offset, */ void DrawFlaskLower(const Surface &out, const Surface &sourceBuffer, int offset, int fillPer) { - int filled = clamp(fillPer, 0, 69); + int filled = std::clamp(fillPer, 0, 69); if (filled < 69) DrawFlaskTop(out, GetMainPanel().position + Displacement { offset, 0 }, sourceBuffer, 16, 85 - filled); @@ -255,9 +269,9 @@ void PrintInfo(const Surface &out) return; const int space[] = { 18, 12, 6, 3, 0 }; - Rectangle infoArea { GetMainPanel().position + Displacement { 177, 46 }, { 288, 60 } }; + Rectangle infoArea { GetMainPanel().position + InfoBoxTopLeft, InfoBoxSize }; - const int newLineCount = std::count(InfoString.str().begin(), InfoString.str().end(), '\n'); + 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; @@ -267,7 +281,14 @@ void PrintInfo(const Surface &out) // which throws off the vertical centering infoArea.position.y += spacing / 2; - DrawString(out, InfoString, infoArea, InfoColor | UiFlags::AlignCenter | UiFlags::VerticalCenter | UiFlags::KerningFitSpacing, 2, lineHeight); + SpeakText(InfoString); + + DrawString(out, InfoString, infoArea, + { + .flags = InfoColor | UiFlags::AlignCenter | UiFlags::VerticalCenter | UiFlags::KerningFitSpacing, + .spacing = 2, + .lineHeight = lineHeight, + }); } int CapStatPointsToAdd(int remainingStatPoints, const Player &player, CharacterAttribute attribute) @@ -336,12 +357,12 @@ struct TextCmdItem { const std::string text; const std::string description; const std::string requiredParameter; - std::string (*actionProc)(const string_view); + std::string (*actionProc)(const std::string_view); }; extern std::vector TextCmdList; -std::string TextCmdHelp(const string_view parameter) +std::string TextCmdHelp(const std::string_view parameter) { if (parameter.empty()) { std::string ret; @@ -351,9 +372,9 @@ std::string TextCmdHelp(const string_view parameter) } return ret; } - auto textCmdIterator = std::find_if(TextCmdList.begin(), TextCmdList.end(), [&](const TextCmdItem &elem) { return elem.text == parameter; }); + auto textCmdIterator = c_find_if(TextCmdList, [&](const TextCmdItem &elem) { return elem.text == parameter; }); if (textCmdIterator == TextCmdList.end()) - return StrCat(_("Command "), parameter, _(" is unkown.")); + return StrCat(_("Command "), parameter, _(" is unknown.")); auto &textCmdItem = *textCmdIterator; if (textCmdItem.requiredParameter.empty()) return StrCat(_("Description: "), _(textCmdItem.description), _("\nParameters: No additional parameter needed.")); @@ -373,7 +394,7 @@ const dungeon_type DungeonTypeForArena[] = { dungeon_type::DTYPE_HELL, // SL_ARENA_CIRCLE_OF_LIFE }; -std::string TextCmdArena(const string_view parameter) +std::string TextCmdArena(const std::string_view parameter) { std::string ret; if (!gbIsMultiplayer) { @@ -387,9 +408,9 @@ std::string TextCmdArena(const string_view parameter) return ret; } - int arenaNumber = atoi(parameter.data()); - _setlevels arenaLevel = static_cast<_setlevels>(arenaNumber - 1 + SL_FIRST_ARENA); - if (arenaNumber < 0 || !IsArenaLevel(arenaLevel)) { + 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; @@ -405,23 +426,24 @@ std::string TextCmdArena(const string_view parameter) return ret; } -std::string TextCmdArenaPot(const string_view parameter) +std::string TextCmdArenaPot(const std::string_view parameter) { std::string ret; if (!gbIsMultiplayer) { StrAppend(ret, _("Arenas are only supported in multiplayer.")); return ret; } + int numPots = ParseInt(parameter, /*min=*/1).value_or(1); Player &myPlayer = *MyPlayer; - for (int potNumber = std::max(1, atoi(parameter.data())); potNumber > 0; potNumber--) { + for (int potNumber = numPots; potNumber > 0; potNumber--) { Item item {}; InitializeItem(item, IDI_ARENAPOT); GenerateNewSeed(item); item.updateRequiredStatsCacheForPlayer(myPlayer); - if (!AutoPlaceItemInBelt(myPlayer, item, true) && !AutoPlaceItemInInventory(myPlayer, item, true)) { + if (!AutoPlaceItemInBelt(myPlayer, item, true, true) && !AutoPlaceItemInInventory(myPlayer, item, true, true)) { break; // inventory is full } } @@ -429,7 +451,7 @@ std::string TextCmdArenaPot(const string_view parameter) return ret; } -std::string TextCmdInspect(const string_view parameter) +std::string TextCmdInspect(const std::string_view parameter) { std::string ret; if (!gbIsMultiplayer) { @@ -444,20 +466,27 @@ std::string TextCmdInspect(const string_view parameter) } const std::string param = AsciiStrToLower(parameter); - for (auto &player : Players) { - const std::string playerName = AsciiStrToLower(player._pName); - if (playerName.find(param) != std::string::npos) { - InspectPlayer = &player; - StrAppend(ret, _("Inspecting player: ")); - StrAppend(ret, player._pName); - OpenCharPanel(); - if (!sbookflag) - invflag = true; - RedrawEverything(); - return ret; - } + 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; } - StrAppend(ret, _("No players found with such a name")); + + Player &player = *it; + InspectPlayer = &player; + StrAppend(ret, _("Inspecting player: ")); + StrAppend(ret, player._pName); + OpenCharPanel(); + if (!sbookflag) + invflag = true; + RedrawEverything(); return ret; } @@ -483,9 +512,9 @@ bool IsQuestEnabled(const Quest &quest) } } -std::string TextCmdLevelSeed(const string_view parameter) +std::string TextCmdLevelSeed(const std::string_view parameter) { - string_view levelType = setlevel ? "set level" : "dungeon level"; + std::string_view levelType = setlevel ? "set level" : "dungeon level"; char gameId[] = { static_cast((sgGameInitInfo.programid >> 24) & 0xFF), @@ -495,8 +524,8 @@ std::string TextCmdLevelSeed(const string_view parameter) '\0' }; - string_view mode = gbIsMultiplayer ? "MP" : "SP"; - string_view questPool = UseMultiplayerQuests() ? "MP" : "Full"; + std::string_view mode = gbIsMultiplayer ? "MP" : "SP"; + std::string_view questPool = UseMultiplayerQuests() ? "MP" : "Full"; uint32_t questFlags = 0; for (const Quest &quest : Quests) { @@ -521,26 +550,26 @@ std::string TextCmdLevelSeed(const string_view parameter) } std::vector TextCmdList = { - { N_("/help"), N_("Prints help overview or help for a specific command."), N_("[command]"), &TextCmdHelp }, - { N_("/arena"), N_("Enter a PvP Arena."), N_(""), &TextCmdArena }, - { N_("/arenapot"), N_("Gives Arena Potions."), N_(""), &TextCmdArenaPot }, - { N_("/inspect"), N_("Inspects stats and equipment of another player."), N_(""), &TextCmdInspect }, - { N_("/seedinfo"), N_("Show seed infos for current level."), "", &TextCmdLevelSeed }, + { "/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 }, }; -bool CheckTextCommand(const string_view text) +bool CheckTextCommand(const std::string_view text) { if (text.size() < 1 || text[0] != '/') return false; - auto textCmdIterator = std::find_if(TextCmdList.begin(), TextCmdList.end(), [&](const TextCmdItem &elem) { return text.find(elem.text) == 0 && (text.length() == elem.text.length() || text[elem.text.length()] == ' '); }); + 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.")); + InitDiabloMsg(StrCat(_("Command "), "\"", text, "\"", _(" is unknown."))); return true; } TextCmdItem &textCmd = *textCmdIterator; - string_view parameter = ""; + 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); @@ -551,10 +580,6 @@ bool CheckTextCommand(const string_view text) void ResetTalkMsg() { -#ifdef _DEBUG - if (CheckDebugTextCommand(TalkMessage)) - return; -#endif if (CheckTextCommand(TalkMessage)) return; @@ -586,7 +611,7 @@ void ControlPressEnter() talkSave &= 7; if (i != talkSave) { strcpy(TalkSave[i], TalkSave[talkSave]); - strcpy(TalkSave[talkSave], TalkMessage); + *BufCopy(TalkSave[talkSave], ChatInputState->value()) = '\0'; } } TalkMessage[0] = '\0'; @@ -600,16 +625,16 @@ void ControlUpDown(int v) for (int i = 0; i < 8; i++) { TalkSaveIndex = (v + TalkSaveIndex) & 7; if (TalkSave[TalkSaveIndex][0] != 0) { - strcpy(TalkMessage, TalkSave[TalkSaveIndex]); + ChatInputState->assign(TalkSave[TalkSaveIndex]); return; } } } -void RemoveGold(Player &player, int goldIndex) +void RemoveGold(Player &player, int goldIndex, int amount) { - int gi = goldIndex - INVITEM_INV_FIRST; - player.InvList[gi]._ivalue -= dropGoldValue; + 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); @@ -617,11 +642,10 @@ void RemoveGold(Player &player, int goldIndex) player.RemoveInvItem(gi); } - MakeGoldStack(player.HoldItem, dropGoldValue); + MakeGoldStack(player.HoldItem, amount); NewCursor(player.HoldItem); player._pGold = CalculateGold(player); - dropGoldValue = 0; } bool IsLevelUpButtonVisible() @@ -686,11 +710,7 @@ void CalculatePanelAreas() bool IsChatAvailable() { -#ifdef _DEBUG - return true; -#else return gbIsMultiplayer; -#endif } void FocusOnCharInfo() @@ -739,7 +759,7 @@ void ToggleCharPanel() OpenCharPanel(); } -void AddPanelString(string_view str) +void AddPanelString(std::string_view str) { if (InfoString.empty()) InfoString = str; @@ -807,9 +827,11 @@ void DrawFlaskValues(const Surface &out, Point pos, int currValue, int maxValue) { UiFlags color = (currValue > 0 ? (currValue == maxValue ? UiFlags::ColorGold : UiFlags::ColorWhite) : UiFlags::ColorRed); - auto drawStringWithShadow = [out, color](string_view text, Point pos) { - DrawString(out, text, pos + Displacement { -1, -1 }, UiFlags::ColorBlack | UiFlags::KerningFitSpacing, 0); - DrawString(out, text, pos, color | UiFlags::KerningFitSpacing, 0); + 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 }); }; std::string currText = StrCat(currValue); @@ -845,6 +867,7 @@ void InitControlPan() } } talkflag = false; + ChatInputState = std::nullopt; if (IsChatAvailable()) { if (!HeadlessMode) { { @@ -880,7 +903,7 @@ void InitControlPan() for (bool &buttonEnabled : chrbtn) buttonEnabled = false; chrbtnactive = false; - InfoString = {}; + InfoString = StringOrView {}; RedrawComponent(PanelDrawComponent::Health); RedrawComponent(PanelDrawComponent::Mana); CloseCharPanel(); @@ -894,10 +917,6 @@ void InitControlPan() pGBoxBuff = LoadCel("ctrlpan\\golddrop", 261); } CloseGoldDrop(); - dropGoldValue = 0; - initialDropGoldValue = 0; - initialDropGoldIndex = 0; - CalculatePanelAreas(); if (!HeadlessMode) @@ -997,6 +1016,20 @@ void DoAutoMap() 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() { panelflag = false; @@ -1025,7 +1058,7 @@ void CheckPanelInfo() InfoColor = UiFlags::ColorWhite; panelflag = true; AddPanelString(_("Hotkey: 's'")); - Player &myPlayer = *MyPlayer; + const Player &myPlayer = *MyPlayer; const SpellID spellId = myPlayer._pRSpell; if (IsValidSpell(spellId)) { switch (myPlayer._pRSplType) { @@ -1039,8 +1072,7 @@ void CheckPanelInfo() } break; case SpellType::Scroll: { AddPanelString(fmt::format(fmt::runtime(_("Scroll of {:s}")), pgettext("spell", GetSpellData(spellId).sNameText))); - const InventoryAndBeltPlayerItemsRange items { myPlayer }; - const int scrollCount = std::count_if(items.begin(), items.end(), [spellId](const Item &item) { + const int scrollCount = c_count_if(InventoryAndBeltPlayerItemsRange { myPlayer }, [spellId](const Item &item) { return item.isScrollOf(spellId); }); AddPanelString(fmt::format(fmt::runtime(ngettext("{:d} Scroll", "{:d} Scrolls", scrollCount)), scrollCount)); @@ -1110,17 +1142,11 @@ void CheckBtnUp() CloseGoldWithdraw(); CloseStash(); invflag = !invflag; - if (dropGoldFlag) { - CloseGoldDrop(); - dropGoldValue = 0; - } + CloseGoldDrop(); break; case PanelButtonSpellbook: CloseInventory(); - if (dropGoldFlag) { - CloseGoldDrop(); - dropGoldValue = 0; - } + CloseGoldDrop(); sbookflag = !sbookflag; break; case PanelButtonSendmsg: @@ -1161,9 +1187,9 @@ void FreeControlPan() void DrawInfoBox(const Surface &out) { - DrawPanelBox(out, { 177, 62, 288, 63 }, GetMainPanel().position + Displacement { 177, 46 }); + DrawPanelBox(out, { 177, 62, InfoBoxSize.width, InfoBoxSize.height }, GetMainPanel().position + InfoBoxTopLeft); if (!panelflag && !trigflag && pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell && !spselflag) { - InfoString = {}; + InfoString = StringOrView {}; InfoColor = UiFlags::ColorWhite; } Player &myPlayer = *MyPlayer; @@ -1196,14 +1222,14 @@ void DrawInfoBox(const Surface &out) PrintMonstHistory(monster.type().type); } } else if (pcursitem == -1) { - InfoString = string_view(Towners[pcursmonst].name); + InfoString = std::string_view(Towners[pcursmonst].name); } } - if (pcursplr != -1) { + if (PlayerUnderCursor != nullptr) { InfoColor = UiFlags::ColorWhitegold; - auto &target = Players[pcursplr]; - InfoString = string_view(target._pName); - AddPanelString(fmt::format(fmt::runtime(_("{:s}, Level: {:d}")), _(PlayersData[static_cast(target._pClass)].className), target._pLevel)); + auto &target = *PlayerUnderCursor; + InfoString = std::string_view(target._pName); + AddPanelString(fmt::format(fmt::runtime(_("{:s}, Level: {:d}")), target.getClassName(), target.getCharacterLevel())); AddPanelString(fmt::format(fmt::runtime(_("Hit Points {:d} of {:d}")), target._pHitPoints >> 6, target._pMaxHP >> 6)); } } @@ -1235,7 +1261,8 @@ void DrawLevelUpIcon(const Surface &out) { if (IsLevelUpButtonVisible()) { int nCel = lvlbtndown ? 2 : 1; - DrawString(out, _("Level Up"), { GetMainPanel().position + Displacement { 0, -62 }, { 120, 0 } }, UiFlags::ColorWhite | UiFlags::AlignCenter); + DrawString(out, _("Level Up"), { GetMainPanel().position + Displacement { 0, -62 }, { 120, 0 } }, + { .flags = UiFlags::ColorWhite | UiFlags::AlignCenter | UiFlags::KerningFitSpacing }); ClxDraw(out, GetMainPanel().position + Displacement { 40, -17 }, (*pChrButtons)[nCel]); } } @@ -1334,19 +1361,23 @@ void RedBack(const Surface &out) } } -void DrawGoldSplit(const Surface &out, int amount) +void DrawGoldSplit(const Surface &out) { const int dialogX = 30; ClxDraw(out, GetPanelPosition(UiPanels::Inventory, { dialogX, 178 }), (*pGBoxBuff)[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?", - initialDropGoldValue)), - FormatInteger(initialDropGoldValue)); + 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); @@ -1354,15 +1385,17 @@ void DrawGoldSplit(const Surface &out, int amount) // 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 } }, UiFlags::ColorWhitegold | UiFlags::AlignCenter, 1, 17); + DrawString(out, wrapped, { GetPanelPosition(UiPanels::Inventory, { dialogX + 31, 75 }), { 200, 50 } }, + { .flags = UiFlags::ColorWhitegold | UiFlags::AlignCenter, .lineHeight = 17 }); - std::string value; - if (amount > 0) { - value = StrCat(amount); - } // 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, value, GetPanelPosition(UiPanels::Inventory, { dialogX + 37, 128 }), UiFlags::ColorWhite | UiFlags::PentaCursor); + 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) @@ -1371,19 +1404,22 @@ void control_drop_gold(SDL_Keycode vkey) if (myPlayer._pHitPoints >> 6 <= 0) { CloseGoldDrop(); - dropGoldValue = 0; return; } - if (vkey == SDLK_RETURN || vkey == SDLK_KP_ENTER) { - if (dropGoldValue > 0) - RemoveGold(myPlayer, initialDropGoldIndex); + switch (vkey) { + case SDLK_RETURN: + case SDLK_KP_ENTER: + if (const int value = GoldDropInputState->value(); value != 0) { + RemoveGold(myPlayer, GoldDropInvIndex, value); + } CloseGoldDrop(); - } else if (vkey == SDLK_ESCAPE) { + break; + case SDLK_ESCAPE: CloseGoldDrop(); - dropGoldValue = 0; - } else if (vkey == SDLK_BACKSPACE) { - dropGoldValue = dropGoldValue / 10; + break; + default: + break; } } @@ -1409,8 +1445,14 @@ void DrawTalkPan(const Surface &out) int x = mainPanelPosition.x + 200; int y = mainPanelPosition.y + 10; - const uint32_t len = DrawString(out, TalkMessage, { { x, y }, { 250, 39 } }, UiFlags::ColorWhite | UiFlags::PentaCursor, 1, 13); - TalkMessage[std::min(len, sizeof(TalkMessage) - 1)] = '\0'; + 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; @@ -1442,7 +1484,7 @@ void DrawTalkPan(const Surface &out) 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 } }, color); + DrawString(out, player._pName, { { x, y + 60 + talkBtn * 18 }, { 204, 0 } }, { .flags = color }); } talkBtn++; @@ -1504,9 +1546,13 @@ void control_type_message() return; talkflag = true; + TalkMessage[0] = '\0'; + ChatInputState.emplace(TextInputState::Options { + .value = TalkMessage, + .cursor = &ChatCursor, + .maxLength = sizeof(TalkMessage) - 1 }); SDL_Rect rect = MakeSdlRect(GetMainPanel().position.x + 200, GetMainPanel().position.y + 22, 0, 27); SDL_SetTextInputRect(&rect); - TalkMessage[0] = '\0'; for (bool &talkButtonDown : TalkButtonsDown) { talkButtonDown = false; } @@ -1520,6 +1566,7 @@ void control_reset_talk() { talkflag = false; SDL_StopTextInput(); + ChatInputState = std::nullopt; sgbPlrTalkTbl = 0; RedrawEverything(); } @@ -1535,9 +1582,9 @@ bool IsTalkActive() return true; } -void control_new_text(string_view text) +bool HandleTalkTextInputEvent(const SDL_Event &event) { - strncat(TalkMessage, text.data(), sizeof(TalkMessage) - strlen(TalkMessage) - 1); + return HandleTextInputEvent(event, *ChatInputState); } bool control_presskeys(SDL_Keycode vkey) @@ -1555,9 +1602,6 @@ bool control_presskeys(SDL_Keycode vkey) case SDLK_KP_ENTER: ControlPressEnter(); return true; - case SDLK_BACKSPACE: - TalkMessage[FindLastUtf8Symbols(TalkMessage)] = '\0'; - return true; case SDLK_DOWN: ControlUpDown(1); return true; @@ -1577,40 +1621,56 @@ void DiabloHotkeyMsg(uint32_t dwMsg) assert(dwMsg < QUICK_MESSAGE_OPTIONS); - for (auto &msg : sgOptions.Chat.szHotKeyMsgs[dwMsg]) { - + for (const std::string &msg : sgOptions.Chat.szHotKeyMsgs[dwMsg]) { #ifdef _DEBUG - if (CheckDebugTextCommand(msg)) + constexpr std::string_view LuaPrefix = "/lua "; + if (msg.starts_with(LuaPrefix)) { + InitConsole(); + RunInConsole(std::string_view(msg).substr(LuaPrefix.size())); continue; + } #endif - if (CheckTextCommand(msg)) - continue; 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, + }); + SDL_StartTextInput(); +} + void CloseGoldDrop() { if (!dropGoldFlag) return; - dropGoldFlag = false; SDL_StopTextInput(); + dropGoldFlag = false; + GoldDropInputState = std::nullopt; + GoldDropInvIndex = 0; } -void GoldDropNewText(string_view text) +int GetGoldDropMax() { - for (char vkey : text) { - int digit = vkey - '0'; - if (digit >= 0 && digit <= 9) { - int newGoldValue = dropGoldValue * 10; - newGoldValue += digit; - if (newGoldValue <= initialDropGoldValue) { - dropGoldValue = newGoldValue; - } - } - } + return GoldDropInputState->max(); +} + +bool HandleGoldDropTextInputEvent(const SDL_Event &event) +{ + return HandleNumberInputEvent(event, *GoldDropInputState); } } // namespace devilution diff --git a/Source/control.h b/Source/control.h index eae004760f7..1cb61a28f0f 100644 --- a/Source/control.h +++ b/Source/control.h @@ -7,6 +7,8 @@ #include #include +#include +#include #include @@ -14,17 +16,18 @@ #include "utils/sdl2_to_1_2_backports.h" #endif +#include "DiabloUI/text_input.hpp" #include "DiabloUI/ui_flags.hpp" #include "engine.h" +#include "engine/displacement.hpp" #include "engine/point.hpp" #include "engine/rectangle.hpp" #include "engine/render/text_render.hpp" +#include "engine/size.hpp" #include "panels/ui_panels.hpp" #include "spelldat.h" #include "spells.h" #include "utils/attributes.h" -#include "utils/stdcompat/optional.hpp" -#include "utils/stdcompat/string_view.hpp" #include "utils/string_or_view.hpp" #include "utils/ui_fwd.h" @@ -32,20 +35,24 @@ namespace devilution { constexpr Size SidePanelSize { 320, 352 }; +// Info box displacement of the top-left corner relative to GetMainPanel().position. +constexpr Displacement InfoBoxTopLeft { 177, 46 }; +constexpr Size InfoBoxSize { 288, 64 }; + extern bool dropGoldFlag; +extern TextInputCursorState GoldDropCursor; +extern char GoldDropText[21]; + extern bool chrbtn[4]; extern bool lvlbtndown; -extern int dropGoldValue; extern bool chrbtnactive; extern UiFlags InfoColor; extern int sbooktab; -extern int8_t initialDropGoldIndex; extern bool talkflag; extern bool sbookflag; extern bool chrflag; extern StringOrView InfoString; extern bool panelflag; -extern int initialDropGoldValue; extern bool panbtndown; extern bool spselflag; const Rectangle &GetMainPanel(); @@ -76,12 +83,8 @@ inline bool CanPanelsCoverView() const Rectangle &mainPanel = GetMainPanel(); return GetScreenWidth() <= mainPanel.size.width && GetScreenHeight() <= SidePanelSize.height + mainPanel.size.height; } -void DrawSpellList(const Surface &out); -void SetSpell(); -void SetSpeedSpell(size_t slot); -void ToggleSpell(size_t slot); -void AddPanelString(string_view str); +void AddPanelString(std::string_view str); void AddPanelString(std::string &&str); void DrawPanelBox(const Surface &out, SDL_Rect srcRect, Point targetPosition); Point GetPanelPosition(UiPanels panel, Point offset = { 0, 0 }); @@ -126,12 +129,6 @@ void DrawFlaskValues(const Surface &out, Point pos, int currValue, int maxValue) */ void control_update_life_mana(); -/** - * @brief draws the current right mouse button spell. - * @param out screen buffer representing the main UI panel - */ -void DrawSpell(const Surface &out); - void InitControlPan(); void DrawCtrlPan(const Surface &out); @@ -153,6 +150,7 @@ void DoPanBtn(); void control_check_btn_press(); void DoAutoMap(); +void CycleAutomapType(); /** * Checks the mouse cursor position within the control panel and sets information @@ -179,7 +177,7 @@ void ReleaseChrBtns(bool addAllStatPoints); void DrawDurIcon(const Surface &out); void RedBack(const Surface &out); void DrawSpellBook(const Surface &out); -void DrawGoldSplit(const Surface &out, int amount); +void DrawGoldSplit(const Surface &out); void control_drop_gold(SDL_Keycode vkey); void DrawTalkPan(const Surface &out); bool control_check_talk_btn(); @@ -187,11 +185,13 @@ void control_release_talk_btn(); void control_type_message(); void control_reset_talk(); bool IsTalkActive(); -void control_new_text(string_view text); +bool HandleTalkTextInputEvent(const SDL_Event &event); bool control_presskeys(SDL_Keycode vkey); void DiabloHotkeyMsg(uint32_t dwMsg); +void OpenGoldDrop(int8_t invIndex, int max); void CloseGoldDrop(); -void GoldDropNewText(string_view text); +int GetGoldDropMax(); +bool HandleGoldDropTextInputEvent(const SDL_Event &event); extern Rectangle ChrBtnsRect[4]; } // namespace devilution diff --git a/Source/controls/controller_buttons.cpp b/Source/controls/controller_buttons.cpp index 4da80c5be94..3c407225c72 100644 --- a/Source/controls/controller_buttons.cpp +++ b/Source/controls/controller_buttons.cpp @@ -4,116 +4,116 @@ namespace devilution { namespace controller_button_icon { -const string_view Playstation_Triangle = "\uE000"; -const string_view Playstation_Square = "\uE001"; -const string_view Playstation_X = "\uE002"; -const string_view Playstation_Circle = "\uE003"; -const string_view Playstation_Options = "\uE004"; -const string_view Playstation_Share = "\uE005"; -const string_view Playstation_L2 = "\uE006"; -const string_view Playstation_R2 = "\uE007"; -const string_view Playstation_L1 = "\uE008"; -const string_view Playstation_R1 = "\uE009"; -const string_view Playstation_DPad_Up = "\uE00A"; -const string_view Playstation_DPad_Right = "\uE00B"; -const string_view Playstation_DPad_Down = "\uE00C"; -const string_view Playstation_DPad_Left = "\uE00D"; -const string_view Playstation_LStick_NW = "\uE00E"; -const string_view Playstation_LStick_W = "\uE00F"; -const string_view Playstation_LStick_SW = "\uE010"; -const string_view Playstation_LStick_N = "\uE011"; -const string_view Playstation_LStick = "\uE012"; -const string_view Playstation_LStick_S = "\uE013"; -const string_view Playstation_LStick_NE = "\uE014"; -const string_view Playstation_LStick_E = "\uE015"; -const string_view Playstation_LStick_SE = "\uE016"; -const string_view Playstation_L3 = "\uE017"; -const string_view Playstation_RStick_NW = "\uE018"; -const string_view Playstation_RStick_W = "\uE019"; -const string_view Playstation_RStick_SW = "\uE01A"; -const string_view Playstation_RStick_N = "\uE01B"; -const string_view Playstation_RStick = "\uE01C"; -const string_view Playstation_RStick_S = "\uE01D"; -const string_view Playstation_RStick_NE = "\uE01E"; -const string_view Playstation_RStick_E = "\uE01F"; -const string_view Playstation_RStick_SE = "\uE020"; -const string_view Playstation_R3 = "\uE021"; -const string_view Playstation_Touchpad = "\uE022"; -const string_view Nintendo_X = "\uE023"; -const string_view Nintendo_Y = "\uE024"; -const string_view Nintendo_B = "\uE025"; -const string_view Nintendo_A = "\uE026"; -const string_view Nintendo_Plus = "\uE027"; -const string_view Nintendo_Minus = "\uE028"; -const string_view Nintendo_ZL = "\uE029"; -const string_view Nintendo_ZR = "\uE02A"; -const string_view Nintendo_L = "\uE02B"; -const string_view Nintendo_R = "\uE02C"; -const string_view Nintendo_DPad_Up = "\uE02D"; -const string_view Nintendo_DPad_Right = "\uE02E"; -const string_view Nintendo_DPad_Down = "\uE02F"; -const string_view Nintendo_DPad_Left = "\uE030"; -const string_view Nintendo_LStick_NW = "\uE031"; -const string_view Nintendo_LStick_W = "\uE032"; -const string_view Nintendo_LStick_SW = "\uE033"; -const string_view Nintendo_LStick_N = "\uE034"; -const string_view Nintendo_LStick = "\uE035"; -const string_view Nintendo_LStick_S = "\uE036"; -const string_view Nintendo_LStick_NE = "\uE037"; -const string_view Nintendo_LStick_E = "\uE038"; -const string_view Nintendo_LStick_SE = "\uE039"; -const string_view Nintendo_LStick_Click = "\uE03A"; -const string_view Nintendo_RStick_NW = "\uE03B"; -const string_view Nintendo_RStick_W = "\uE03C"; -const string_view Nintendo_RStick_SW = "\uE03D"; -const string_view Nintendo_RStick_N = "\uE03E"; -const string_view Nintendo_RStick = "\uE03F"; -const string_view Nintendo_RStick_S = "\uE040"; -const string_view Nintendo_RStick_NE = "\uE041"; -const string_view Nintendo_RStick_E = "\uE042"; -const string_view Nintendo_RStick_SE = "\uE043"; -const string_view Nintendo_RStick_Click = "\uE044"; -const string_view Nintendo_Home = "\uE045"; -const string_view Nintendo_Screenshot = "\uE046"; -const string_view Nintendo_SL = "\uE047"; -const string_view Nintendo_SR = "\uE048"; -const string_view Xbox_Y = "\uE049"; -const string_view Xbox_X = "\uE04A"; -const string_view Xbox_A = "\uE04B"; -const string_view Xbox_B = "\uE04C"; -const string_view Xbox_Menu = "\uE04D"; -const string_view Xbox_View = "\uE04E"; -const string_view Xbox_LT = "\uE04F"; -const string_view Xbox_RT = "\uE050"; -const string_view Xbox_LB = "\uE051"; -const string_view Xbox_RB = "\uE052"; -const string_view Xbox_DPad_Up = "\uE053"; -const string_view Xbox_DPad_Right = "\uE054"; -const string_view Xbox_DPad_Down = "\uE055"; -const string_view Xbox_DPad_Left = "\uE056"; -const string_view Xbox_LStick_NW = "\uE057"; -const string_view Xbox_LStick_W = "\uE058"; -const string_view Xbox_LStick_SW = "\uE059"; -const string_view Xbox_LStick_N = "\uE05A"; -const string_view Xbox_LStick = "\uE05B"; -const string_view Xbox_LStick_NE = "\uE05C"; -const string_view Xbox_LStick_E = "\uE05D"; -const string_view Xbox_LStick_SE = "\uE05E"; -const string_view Xbox_LStick_Click = "\uE05F"; -const string_view Xbox_RStick_NW = "\uE060"; -const string_view Xbox_RStick_W = "\uE061"; -const string_view Xbox_RStick_SW = "\uE062"; -const string_view Xbox_RStick_N = "\uE063"; -const string_view Xbox_RStick = "\uE064"; -const string_view Xbox_RStick_S = "\uE065"; -const string_view Xbox_RStick_NE = "\uE066"; -const string_view Xbox_RStick_E = "\uE067"; -const string_view Xbox_RStick_SE = "\uE068"; -const string_view Xbox_RStick_Click = "\uE069"; -const string_view Xbox_Xbox = "\uE06A"; +const std::string_view Playstation_Triangle = "\uE000"; +const std::string_view Playstation_Square = "\uE001"; +const std::string_view Playstation_X = "\uE002"; +const std::string_view Playstation_Circle = "\uE003"; +const std::string_view Playstation_Options = "\uE004"; +const std::string_view Playstation_Share = "\uE005"; +const std::string_view Playstation_L2 = "\uE006"; +const std::string_view Playstation_R2 = "\uE007"; +const std::string_view Playstation_L1 = "\uE008"; +const std::string_view Playstation_R1 = "\uE009"; +const std::string_view Playstation_DPad_Up = "\uE00A"; +const std::string_view Playstation_DPad_Right = "\uE00B"; +const std::string_view Playstation_DPad_Down = "\uE00C"; +const std::string_view Playstation_DPad_Left = "\uE00D"; +const std::string_view Playstation_LStick_NW = "\uE00E"; +const std::string_view Playstation_LStick_W = "\uE00F"; +const std::string_view Playstation_LStick_SW = "\uE010"; +const std::string_view Playstation_LStick_N = "\uE011"; +const std::string_view Playstation_LStick = "\uE012"; +const std::string_view Playstation_LStick_S = "\uE013"; +const std::string_view Playstation_LStick_NE = "\uE014"; +const std::string_view Playstation_LStick_E = "\uE015"; +const std::string_view Playstation_LStick_SE = "\uE016"; +const std::string_view Playstation_L3 = "\uE017"; +const std::string_view Playstation_RStick_NW = "\uE018"; +const std::string_view Playstation_RStick_W = "\uE019"; +const std::string_view Playstation_RStick_SW = "\uE01A"; +const std::string_view Playstation_RStick_N = "\uE01B"; +const std::string_view Playstation_RStick = "\uE01C"; +const std::string_view Playstation_RStick_S = "\uE01D"; +const std::string_view Playstation_RStick_NE = "\uE01E"; +const std::string_view Playstation_RStick_E = "\uE01F"; +const std::string_view Playstation_RStick_SE = "\uE020"; +const std::string_view Playstation_R3 = "\uE021"; +const std::string_view Playstation_Touchpad = "\uE022"; +const std::string_view Nintendo_X = "\uE023"; +const std::string_view Nintendo_Y = "\uE024"; +const std::string_view Nintendo_B = "\uE025"; +const std::string_view Nintendo_A = "\uE026"; +const std::string_view Nintendo_Plus = "\uE027"; +const std::string_view Nintendo_Minus = "\uE028"; +const std::string_view Nintendo_ZL = "\uE029"; +const std::string_view Nintendo_ZR = "\uE02A"; +const std::string_view Nintendo_L = "\uE02B"; +const std::string_view Nintendo_R = "\uE02C"; +const std::string_view Nintendo_DPad_Up = "\uE02D"; +const std::string_view Nintendo_DPad_Right = "\uE02E"; +const std::string_view Nintendo_DPad_Down = "\uE02F"; +const std::string_view Nintendo_DPad_Left = "\uE030"; +const std::string_view Nintendo_LStick_NW = "\uE031"; +const std::string_view Nintendo_LStick_W = "\uE032"; +const std::string_view Nintendo_LStick_SW = "\uE033"; +const std::string_view Nintendo_LStick_N = "\uE034"; +const std::string_view Nintendo_LStick = "\uE035"; +const std::string_view Nintendo_LStick_S = "\uE036"; +const std::string_view Nintendo_LStick_NE = "\uE037"; +const std::string_view Nintendo_LStick_E = "\uE038"; +const std::string_view Nintendo_LStick_SE = "\uE039"; +const std::string_view Nintendo_LStick_Click = "\uE03A"; +const std::string_view Nintendo_RStick_NW = "\uE03B"; +const std::string_view Nintendo_RStick_W = "\uE03C"; +const std::string_view Nintendo_RStick_SW = "\uE03D"; +const std::string_view Nintendo_RStick_N = "\uE03E"; +const std::string_view Nintendo_RStick = "\uE03F"; +const std::string_view Nintendo_RStick_S = "\uE040"; +const std::string_view Nintendo_RStick_NE = "\uE041"; +const std::string_view Nintendo_RStick_E = "\uE042"; +const std::string_view Nintendo_RStick_SE = "\uE043"; +const std::string_view Nintendo_RStick_Click = "\uE044"; +const std::string_view Nintendo_Home = "\uE045"; +const std::string_view Nintendo_Screenshot = "\uE046"; +const std::string_view Nintendo_SL = "\uE047"; +const std::string_view Nintendo_SR = "\uE048"; +const std::string_view Xbox_Y = "\uE049"; +const std::string_view Xbox_X = "\uE04A"; +const std::string_view Xbox_A = "\uE04B"; +const std::string_view Xbox_B = "\uE04C"; +const std::string_view Xbox_Menu = "\uE04D"; +const std::string_view Xbox_View = "\uE04E"; +const std::string_view Xbox_LT = "\uE04F"; +const std::string_view Xbox_RT = "\uE050"; +const std::string_view Xbox_LB = "\uE051"; +const std::string_view Xbox_RB = "\uE052"; +const std::string_view Xbox_DPad_Up = "\uE053"; +const std::string_view Xbox_DPad_Right = "\uE054"; +const std::string_view Xbox_DPad_Down = "\uE055"; +const std::string_view Xbox_DPad_Left = "\uE056"; +const std::string_view Xbox_LStick_NW = "\uE057"; +const std::string_view Xbox_LStick_W = "\uE058"; +const std::string_view Xbox_LStick_SW = "\uE059"; +const std::string_view Xbox_LStick_N = "\uE05A"; +const std::string_view Xbox_LStick = "\uE05B"; +const std::string_view Xbox_LStick_NE = "\uE05C"; +const std::string_view Xbox_LStick_E = "\uE05D"; +const std::string_view Xbox_LStick_SE = "\uE05E"; +const std::string_view Xbox_LStick_Click = "\uE05F"; +const std::string_view Xbox_RStick_NW = "\uE060"; +const std::string_view Xbox_RStick_W = "\uE061"; +const std::string_view Xbox_RStick_SW = "\uE062"; +const std::string_view Xbox_RStick_N = "\uE063"; +const std::string_view Xbox_RStick = "\uE064"; +const std::string_view Xbox_RStick_S = "\uE065"; +const std::string_view Xbox_RStick_NE = "\uE066"; +const std::string_view Xbox_RStick_E = "\uE067"; +const std::string_view Xbox_RStick_SE = "\uE068"; +const std::string_view Xbox_RStick_Click = "\uE069"; +const std::string_view Xbox_Xbox = "\uE06A"; } // namespace controller_button_icon -string_view ToPlayStationIcon(ControllerButton button) +std::string_view ToPlayStationIcon(ControllerButton button) { switch (button) { case devilution::ControllerButton_BUTTON_A: @@ -153,7 +153,7 @@ string_view ToPlayStationIcon(ControllerButton button) } } -string_view ToNintendoIcon(ControllerButton button) +std::string_view ToNintendoIcon(ControllerButton button) { switch (button) { case devilution::ControllerButton_BUTTON_A: @@ -193,7 +193,7 @@ string_view ToNintendoIcon(ControllerButton button) } } -string_view ToXboxIcon(ControllerButton button) +std::string_view ToXboxIcon(ControllerButton button) { switch (button) { case devilution::ControllerButton_BUTTON_A: @@ -233,7 +233,7 @@ string_view ToXboxIcon(ControllerButton button) } } -string_view ToGenericButtonText(ControllerButton button) +std::string_view ToGenericButtonText(ControllerButton button) { switch (button) { case devilution::ControllerButton_BUTTON_A: @@ -277,7 +277,7 @@ string_view ToGenericButtonText(ControllerButton button) } } -string_view ToString(ControllerButton button) +std::string_view ToString(ControllerButton button) { switch (GamepadType) { case devilution::GamepadLayout::PlayStation: diff --git a/Source/controls/controller_buttons.h b/Source/controls/controller_buttons.h index a65bee5dd37..1eb100068f4 100644 --- a/Source/controls/controller_buttons.h +++ b/Source/controls/controller_buttons.h @@ -3,8 +3,7 @@ #include #include - -#include "utils/stdcompat/string_view.hpp" +#include namespace devilution { @@ -65,119 +64,119 @@ inline bool IsDPadButton(ControllerButton button) } namespace controller_button_icon { -extern const string_view Playstation_Triangle; -extern const string_view Playstation_Square; -extern const string_view Playstation_X; -extern const string_view Playstation_Circle; -extern const string_view Playstation_Options; -extern const string_view Playstation_Share; -extern const string_view Playstation_L2; -extern const string_view Playstation_R2; -extern const string_view Playstation_L1; -extern const string_view Playstation_R1; -extern const string_view Playstation_DPad_Up; -extern const string_view Playstation_DPad_Right; -extern const string_view Playstation_DPad_Down; -extern const string_view Playstation_DPad_Left; -extern const string_view Playstation_LStick_NW; -extern const string_view Playstation_LStick_W; -extern const string_view Playstation_LStick_SW; -extern const string_view Playstation_LStick_N; -extern const string_view Playstation_LStick; -extern const string_view Playstation_LStick_S; -extern const string_view Playstation_LStick_NE; -extern const string_view Playstation_LStick_E; -extern const string_view Playstation_LStick_SE; -extern const string_view Playstation_L3; -extern const string_view Playstation_RStick_NW; -extern const string_view Playstation_RStick_W; -extern const string_view Playstation_RStick_SW; -extern const string_view Playstation_RStick_N; -extern const string_view Playstation_RStick; -extern const string_view Playstation_RStick_S; -extern const string_view Playstation_RStick_NE; -extern const string_view Playstation_RStick_E; -extern const string_view Playstation_RStick_SE; -extern const string_view Playstation_R3; -extern const string_view Playstation_Touchpad; -extern const string_view Nintendo_X; -extern const string_view Nintendo_Y; -extern const string_view Nintendo_B; -extern const string_view Nintendo_A; -extern const string_view Nintendo_Plus; -extern const string_view Nintendo_Minus; -extern const string_view Nintendo_ZL; -extern const string_view Nintendo_ZR; -extern const string_view Nintendo_L; -extern const string_view Nintendo_R; -extern const string_view Nintendo_DPad_Up; -extern const string_view Nintendo_DPad_Right; -extern const string_view Nintendo_DPad_Down; -extern const string_view Nintendo_DPad_Left; -extern const string_view Nintendo_LStick_NW; -extern const string_view Nintendo_LStick_W; -extern const string_view Nintendo_LStick_SW; -extern const string_view Nintendo_LStick_N; -extern const string_view Nintendo_LStick; -extern const string_view Nintendo_LStick_S; -extern const string_view Nintendo_LStick_NE; -extern const string_view Nintendo_LStick_E; -extern const string_view Nintendo_LStick_SE; -extern const string_view Nintendo_LStick_Click; -extern const string_view Nintendo_RStick_NW; -extern const string_view Nintendo_RStick_W; -extern const string_view Nintendo_RStick_SW; -extern const string_view Nintendo_RStick_N; -extern const string_view Nintendo_RStick; -extern const string_view Nintendo_RStick_S; -extern const string_view Nintendo_RStick_NE; -extern const string_view Nintendo_RStick_E; -extern const string_view Nintendo_RStick_SE; -extern const string_view Nintendo_RStick_Click; -extern const string_view Nintendo_Home; -extern const string_view Nintendo_Screenshot; -extern const string_view Nintendo_SL; -extern const string_view Nintendo_SR; -extern const string_view Xbox_Y; -extern const string_view Xbox_X; -extern const string_view Xbox_A; -extern const string_view Xbox_B; -extern const string_view Xbox_Menu; -extern const string_view Xbox_View; -extern const string_view Xbox_LT; -extern const string_view Xbox_RT; -extern const string_view Xbox_LB; -extern const string_view Xbox_RB; -extern const string_view Xbox_DPad_Up; -extern const string_view Xbox_DPad_Right; -extern const string_view Xbox_DPad_Down; -extern const string_view Xbox_DPad_Left; -extern const string_view Xbox_LStick_NW; -extern const string_view Xbox_LStick_W; -extern const string_view Xbox_LStick_SW; -extern const string_view Xbox_LStick_N; -extern const string_view Xbox_LStick; -extern const string_view Xbox_LStick_NE; -extern const string_view Xbox_LStick_E; -extern const string_view Xbox_LStick_SE; -extern const string_view Xbox_LStick_Click; -extern const string_view Xbox_RStick_NW; -extern const string_view Xbox_RStick_W; -extern const string_view Xbox_RStick_SW; -extern const string_view Xbox_RStick_N; -extern const string_view Xbox_RStick; -extern const string_view Xbox_RStick_S; -extern const string_view Xbox_RStick_NE; -extern const string_view Xbox_RStick_E; -extern const string_view Xbox_RStick_SE; -extern const string_view Xbox_RStick_Click; -extern const string_view Xbox_Xbox; +extern const std::string_view Playstation_Triangle; +extern const std::string_view Playstation_Square; +extern const std::string_view Playstation_X; +extern const std::string_view Playstation_Circle; +extern const std::string_view Playstation_Options; +extern const std::string_view Playstation_Share; +extern const std::string_view Playstation_L2; +extern const std::string_view Playstation_R2; +extern const std::string_view Playstation_L1; +extern const std::string_view Playstation_R1; +extern const std::string_view Playstation_DPad_Up; +extern const std::string_view Playstation_DPad_Right; +extern const std::string_view Playstation_DPad_Down; +extern const std::string_view Playstation_DPad_Left; +extern const std::string_view Playstation_LStick_NW; +extern const std::string_view Playstation_LStick_W; +extern const std::string_view Playstation_LStick_SW; +extern const std::string_view Playstation_LStick_N; +extern const std::string_view Playstation_LStick; +extern const std::string_view Playstation_LStick_S; +extern const std::string_view Playstation_LStick_NE; +extern const std::string_view Playstation_LStick_E; +extern const std::string_view Playstation_LStick_SE; +extern const std::string_view Playstation_L3; +extern const std::string_view Playstation_RStick_NW; +extern const std::string_view Playstation_RStick_W; +extern const std::string_view Playstation_RStick_SW; +extern const std::string_view Playstation_RStick_N; +extern const std::string_view Playstation_RStick; +extern const std::string_view Playstation_RStick_S; +extern const std::string_view Playstation_RStick_NE; +extern const std::string_view Playstation_RStick_E; +extern const std::string_view Playstation_RStick_SE; +extern const std::string_view Playstation_R3; +extern const std::string_view Playstation_Touchpad; +extern const std::string_view Nintendo_X; +extern const std::string_view Nintendo_Y; +extern const std::string_view Nintendo_B; +extern const std::string_view Nintendo_A; +extern const std::string_view Nintendo_Plus; +extern const std::string_view Nintendo_Minus; +extern const std::string_view Nintendo_ZL; +extern const std::string_view Nintendo_ZR; +extern const std::string_view Nintendo_L; +extern const std::string_view Nintendo_R; +extern const std::string_view Nintendo_DPad_Up; +extern const std::string_view Nintendo_DPad_Right; +extern const std::string_view Nintendo_DPad_Down; +extern const std::string_view Nintendo_DPad_Left; +extern const std::string_view Nintendo_LStick_NW; +extern const std::string_view Nintendo_LStick_W; +extern const std::string_view Nintendo_LStick_SW; +extern const std::string_view Nintendo_LStick_N; +extern const std::string_view Nintendo_LStick; +extern const std::string_view Nintendo_LStick_S; +extern const std::string_view Nintendo_LStick_NE; +extern const std::string_view Nintendo_LStick_E; +extern const std::string_view Nintendo_LStick_SE; +extern const std::string_view Nintendo_LStick_Click; +extern const std::string_view Nintendo_RStick_NW; +extern const std::string_view Nintendo_RStick_W; +extern const std::string_view Nintendo_RStick_SW; +extern const std::string_view Nintendo_RStick_N; +extern const std::string_view Nintendo_RStick; +extern const std::string_view Nintendo_RStick_S; +extern const std::string_view Nintendo_RStick_NE; +extern const std::string_view Nintendo_RStick_E; +extern const std::string_view Nintendo_RStick_SE; +extern const std::string_view Nintendo_RStick_Click; +extern const std::string_view Nintendo_Home; +extern const std::string_view Nintendo_Screenshot; +extern const std::string_view Nintendo_SL; +extern const std::string_view Nintendo_SR; +extern const std::string_view Xbox_Y; +extern const std::string_view Xbox_X; +extern const std::string_view Xbox_A; +extern const std::string_view Xbox_B; +extern const std::string_view Xbox_Menu; +extern const std::string_view Xbox_View; +extern const std::string_view Xbox_LT; +extern const std::string_view Xbox_RT; +extern const std::string_view Xbox_LB; +extern const std::string_view Xbox_RB; +extern const std::string_view Xbox_DPad_Up; +extern const std::string_view Xbox_DPad_Right; +extern const std::string_view Xbox_DPad_Down; +extern const std::string_view Xbox_DPad_Left; +extern const std::string_view Xbox_LStick_NW; +extern const std::string_view Xbox_LStick_W; +extern const std::string_view Xbox_LStick_SW; +extern const std::string_view Xbox_LStick_N; +extern const std::string_view Xbox_LStick; +extern const std::string_view Xbox_LStick_NE; +extern const std::string_view Xbox_LStick_E; +extern const std::string_view Xbox_LStick_SE; +extern const std::string_view Xbox_LStick_Click; +extern const std::string_view Xbox_RStick_NW; +extern const std::string_view Xbox_RStick_W; +extern const std::string_view Xbox_RStick_SW; +extern const std::string_view Xbox_RStick_N; +extern const std::string_view Xbox_RStick; +extern const std::string_view Xbox_RStick_S; +extern const std::string_view Xbox_RStick_NE; +extern const std::string_view Xbox_RStick_E; +extern const std::string_view Xbox_RStick_SE; +extern const std::string_view Xbox_RStick_Click; +extern const std::string_view Xbox_Xbox; } // namespace controller_button_icon -string_view ToPlayStationIcon(ControllerButton button); -string_view ToNintendoIcon(ControllerButton button); -string_view ToXboxIcon(ControllerButton button); -string_view ToGenericButtonText(ControllerButton button); -string_view ToString(ControllerButton button); +std::string_view ToPlayStationIcon(ControllerButton button); +std::string_view ToNintendoIcon(ControllerButton button); +std::string_view ToXboxIcon(ControllerButton button); +std::string_view ToGenericButtonText(ControllerButton button); +std::string_view ToString(ControllerButton button); } // namespace devilution diff --git a/Source/controls/controller_motion.cpp b/Source/controls/controller_motion.cpp index c959b35f13b..b59344787b2 100644 --- a/Source/controls/controller_motion.cpp +++ b/Source/controls/controller_motion.cpp @@ -48,7 +48,7 @@ void ScaleJoystickAxes(float *x, float *y, float deadzone) analogX = (analogX * scalingFactor); analogY = (analogY * scalingFactor); - // clamp to ensure results will never exceed the max_axis value + // std::clamp to ensure results will never exceed the max_axis value float clampingFactor = 1.F; float absAnalogX = std::fabs(analogX); float absAnalogY = std::fabs(analogY); @@ -70,7 +70,7 @@ void ScaleJoystickAxes(float *x, float *y, float deadzone) bool IsMovementOverriddenByPadmapper(ControllerButton button) { ControllerButtonEvent releaseEvent { button, true }; - string_view actionName = sgOptions.Padmapper.ActionNameTriggeredByButtonEvent(releaseEvent); + std::string_view actionName = sgOptions.Padmapper.ActionNameTriggeredByButtonEvent(releaseEvent); ControllerButtonCombo buttonCombo = sgOptions.Padmapper.ButtonComboForAction(actionName); return buttonCombo.modifier != ControllerButton_NONE; } @@ -78,12 +78,12 @@ bool IsMovementOverriddenByPadmapper(ControllerButton button) bool TriggersQuickSpellAction(ControllerButton button) { ControllerButtonEvent releaseEvent { button, true }; - string_view actionName = sgOptions.Padmapper.ActionNameTriggeredByButtonEvent(releaseEvent); + std::string_view actionName = sgOptions.Padmapper.ActionNameTriggeredByButtonEvent(releaseEvent); - string_view prefix { "QuickSpell" }; + std::string_view prefix { "QuickSpell" }; if (actionName.size() < prefix.size()) return false; - string_view truncatedActionName { actionName.data(), prefix.size() }; + std::string_view truncatedActionName { actionName.data(), prefix.size() }; return truncatedActionName == prefix; } @@ -149,21 +149,22 @@ bool IsControllerMotion(const SDL_Event &event) } #endif +#if defined(JOY_AXIS_LEFTX) || defined(JOY_AXIS_LEFTY) || defined(JOY_AXIS_RIGHTX) || defined(JOY_AXIS_RIGHTY) if (event.type == SDL_JOYAXISMOTION) { switch (event.jaxis.axis) { #ifdef JOY_AXIS_LEFTX case JOY_AXIS_LEFTX: return true; #endif -#ifdef JOY_AXIS_LEFTX +#ifdef JOY_AXIS_LEFTY case JOY_AXIS_LEFTY: return true; #endif -#ifdef JOY_AXIS_LEFTX +#ifdef JOY_AXIS_RIGHTX case JOY_AXIS_RIGHTX: return true; #endif -#ifdef JOY_AXIS_LEFTX +#ifdef JOY_AXIS_RIGHTY case JOY_AXIS_RIGHTY: return true; #endif @@ -171,6 +172,7 @@ bool IsControllerMotion(const SDL_Event &event) return false; } } +#endif return false; } @@ -248,7 +250,7 @@ void SimulateRightStickWithPadmapper(ControllerButtonEvent ctrlEvent) if (!ctrlEvent.up && ctrlEvent.button == SuppressedButton) return; - string_view actionName = sgOptions.Padmapper.ActionNameTriggeredByButtonEvent(ctrlEvent); + std::string_view actionName = sgOptions.Padmapper.ActionNameTriggeredByButtonEvent(ctrlEvent); bool upTriggered = actionName == "MouseUp"; bool downTriggered = actionName == "MouseDown"; bool leftTriggered = actionName == "MouseLeft"; diff --git a/Source/controls/devices/joystick.cpp b/Source/controls/devices/joystick.cpp index 62f6931d4f0..3770e595cfd 100644 --- a/Source/controls/devices/joystick.cpp +++ b/Source/controls/devices/joystick.cpp @@ -16,6 +16,10 @@ StaticVector Joystick::ToControllerButtonEvents(const case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: { bool up = (event.jbutton.state == SDL_RELEASED); +#if defined(JOY_BUTTON_A) || defined(JOY_BUTTON_B) || defined(JOY_BUTTON_X) || defined(JOY_BUTTON_Y) \ + || defined(JOY_BUTTON_LEFTSTICK) || defined(JOY_BUTTON_RIGHTSTICK) || defined(JOY_BUTTON_LEFTSHOULDER) || defined(JOY_BUTTON_RIGHTSHOULDER) \ + || defined(JOY_BUTTON_TRIGGERLEFT) || defined(JOY_BUTTON_TRIGGERRIGHT) || defined(JOY_BUTTON_START) || defined(JOY_BUTTON_BACK) \ + || defined(JOY_BUTTON_DPAD_LEFT) || defined(JOY_BUTTON_DPAD_UP) || defined(JOY_BUTTON_DPAD_RIGHT) || defined(JOY_BUTTON_DPAD_DOWN) switch (event.jbutton.button) { #ifdef JOY_BUTTON_A case JOY_BUTTON_A: @@ -84,7 +88,9 @@ StaticVector Joystick::ToControllerButtonEvents(const default: return { ControllerButtonEvent { ControllerButton_IGNORE, up } }; } - break; +#else + return { ControllerButtonEvent { ControllerButton_IGNORE, up } }; +#endif } case SDL_JOYHATMOTION: { Joystick *joystick = Get(event); @@ -167,6 +173,10 @@ void Joystick::UnlockHatState() int Joystick::ToSdlJoyButton(ControllerButton button) { +#if defined(JOY_BUTTON_A) || defined(JOY_BUTTON_B) || defined(JOY_BUTTON_X) || defined(JOY_BUTTON_Y) \ + || defined(JOY_BUTTON_BACK) || defined(JOY_BUTTON_START) || defined(JOY_BUTTON_LEFTSTICK) || defined(JOY_BUTTON_RIGHTSTICK) \ + || defined(JOY_BUTTON_LEFTSHOULDER) || defined(JOY_BUTTON_RIGHTSHOULDER) || defined(JOY_BUTTON_TRIGGERLEFT) || defined(JOY_BUTTON_TRIGGERRIGHT) \ + || defined(JOY_BUTTON_DPAD_LEFT) || defined(JOY_BUTTON_DPAD_UP) || defined(JOY_BUTTON_DPAD_RIGHT) || defined(JOY_BUTTON_DPAD_DOWN) switch (button) { #ifdef JOY_BUTTON_A case ControllerButton_BUTTON_A: @@ -235,11 +245,15 @@ int Joystick::ToSdlJoyButton(ControllerButton button) default: return -1; } +#else + return -1; +#endif } // NOLINTNEXTLINE(readability-convert-member-functions-to-static): Not static if joystick mappings are defined. bool Joystick::IsHatButtonPressed(ControllerButton button) const { +#if (defined(JOY_HAT_DPAD_UP_HAT) && defined(JOY_HAT_DPAD_UP)) || (defined(JOY_HAT_DPAD_DOWN_HAT) && defined(JOY_HAT_DPAD_DOWN)) || (defined(JOY_HAT_DPAD_LEFT_HAT) && defined(JOY_HAT_DPAD_LEFT)) || (defined(JOY_HAT_DPAD_RIGHT_HAT) && defined(JOY_HAT_DPAD_RIGHT)) switch (button) { #if defined(JOY_HAT_DPAD_UP_HAT) && defined(JOY_HAT_DPAD_UP) case ControllerButton_BUTTON_DPAD_UP: @@ -260,6 +274,9 @@ bool Joystick::IsHatButtonPressed(ControllerButton button) const default: return false; } +#else + return false; +#endif } bool Joystick::IsPressed(ControllerButton button) const @@ -279,6 +296,8 @@ bool Joystick::ProcessAxisMotion(const SDL_Event &event) { if (event.type != SDL_JOYAXISMOTION) return false; + +#if defined(JOY_AXIS_LEFTX) || defined(JOY_AXIS_LEFTY) || defined(JOY_AXIS_RIGHTX) || defined(JOY_AXIS_RIGHTY) switch (event.jaxis.axis) { #ifdef JOY_AXIS_LEFTX case JOY_AXIS_LEFTX: @@ -307,6 +326,9 @@ bool Joystick::ProcessAxisMotion(const SDL_Event &event) default: return false; } +#else + return false; +#endif } void Joystick::Add(int deviceIndex) diff --git a/Source/controls/game_controls.cpp b/Source/controls/game_controls.cpp index d4fa00513d4..9538e1032ce 100644 --- a/Source/controls/game_controls.cpp +++ b/Source/controls/game_controls.cpp @@ -13,6 +13,7 @@ #include "gamemenu.h" #include "gmenu.h" #include "options.h" +#include "panels/spell_list.hpp" #include "qol/stash.h" #include "stores.h" @@ -269,7 +270,7 @@ void PressControllerButton(ControllerButton button) gamemenu_on(); return; case devilution::ControllerButton_BUTTON_DPAD_DOWN: - DoAutoMap(); + CycleAutomapType(); return; case devilution::ControllerButton_BUTTON_DPAD_LEFT: ProcessGameAction(GameAction { GameActionType_TOGGLE_CHARACTER_INFO }); @@ -335,7 +336,7 @@ bool IsSimulatedMouseClickBinding(ControllerButtonEvent ctrlEvent) return false; if (!ctrlEvent.up && ctrlEvent.button == SuppressedButton) return false; - string_view actionName = sgOptions.Padmapper.ActionNameTriggeredByButtonEvent(ctrlEvent); + std::string_view actionName = sgOptions.Padmapper.ActionNameTriggeredByButtonEvent(ctrlEvent); return IsAnyOf(actionName, "LeftMouseClick1", "LeftMouseClick2", "RightMouseClick1", "RightMouseClick2"); } diff --git a/Source/controls/menu_controls.cpp b/Source/controls/menu_controls.cpp index 619886cca9f..732525e84f8 100644 --- a/Source/controls/menu_controls.cpp +++ b/Source/controls/menu_controls.cpp @@ -113,16 +113,25 @@ std::vector GetMenuActions(const SDL_Event &event) case SDLK_KP_ENTER: return { MenuAction_SELECT }; case SDLK_SPACE: - if (!textInputActive) { + if (!IsTextInputActive()) { return { MenuAction_SELECT }; } break; case SDLK_DELETE: - return { MenuAction_DELETE }; + if (!IsTextInputActive()) { + return { MenuAction_DELETE }; + } + break; case SDLK_LEFT: - return { MenuAction_LEFT }; + if (!IsTextInputActive()) { + return { MenuAction_LEFT }; + } + break; case SDLK_RIGHT: - return { MenuAction_RIGHT }; + if (!IsTextInputActive()) { + return { MenuAction_RIGHT }; + } + break; case SDLK_ESCAPE: return { MenuAction_BACK }; default: diff --git a/Source/controls/modifier_hints.cpp b/Source/controls/modifier_hints.cpp index 9c702dc0726..455a00eb422 100644 --- a/Source/controls/modifier_hints.cpp +++ b/Source/controls/modifier_hints.cpp @@ -33,12 +33,6 @@ constexpr int CircleTop = 101; /** Spell icon side size. */ constexpr int IconSize = 37; -/** Spell icon text right margin. */ -constexpr int IconSizeTextMarginRight = 3; - -/** Spell icon text top margin. */ -constexpr int IconSizeTextMarginTop = 2; - constexpr int HintBoxSize = 39; constexpr int HintBoxMargin = 5; diff --git a/Source/controls/plrctrls.cpp b/Source/controls/plrctrls.cpp index 54cc8d0f21d..1ab2088173d 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -1,6 +1,7 @@ #include "controls/plrctrls.h" #include +#include #include #include @@ -103,7 +104,7 @@ int GetRotaryDistance(Point destination) int d1 = static_cast(myPlayer._pdir); int d2 = static_cast(GetDirection(myPlayer.position.future, destination)); - int d = abs(d1 - d2); + int d = std::abs(d1 - d2); if (d > 4) return 4 - (d % 4); @@ -328,7 +329,7 @@ void FindMeleeTarget() visited[dx][dy] = true; if (dMonster[dx][dy] != 0) { - const int mi = abs(dMonster[dx][dy]) - 1; + const int mi = std::abs(dMonster[dx][dy]) - 1; const auto &monster = Monsters[mi]; if (CanTargetMonster(monster)) { const bool newCanTalk = CanTalkToMonst(monster); @@ -381,8 +382,7 @@ void CheckPlayerNearby() if (myPlayer.friendlyMode && spl != SpellID::Resurrect && spl != SpellID::HealOther) return; - for (size_t i = 0; i < Players.size(); i++) { - const Player &player = Players[i]; + for (const Player &player : Players) { if (&player == MyPlayer) continue; const int mx = player.position.future.x; @@ -400,15 +400,15 @@ void CheckPlayerNearby() continue; } - if (pcursplr != -1 && distance < newDdistance) + if (PlayerUnderCursor != nullptr && distance < newDdistance) continue; const int newRotations = GetRotaryDistance(player.position.future); - if (pcursplr != -1 && distance == newDdistance && rotations < newRotations) + if (PlayerUnderCursor != nullptr && distance == newDdistance && rotations < newRotations) continue; distance = newDdistance; rotations = newRotations; - pcursplr = i; + PlayerUnderCursor = &player; } } @@ -474,7 +474,7 @@ void FindTrigger() } } - if (pcursmonst != -1 || pcursplr != -1 || cursPosition.x == -1 || cursPosition.y == -1) + if (pcursmonst != -1 || PlayerUnderCursor != nullptr || cursPosition.x == -1 || cursPosition.y == -1) return; // Prefer monster/player info text CheckTrigForce(); @@ -533,8 +533,8 @@ void Interact() return; } - if (leveltype != DTYPE_TOWN && pcursplr != -1 && !myPlayer.friendlyMode) { - NetSendCmdParam1(true, myPlayer.UsesRangedWeapon() ? CMD_RATTACKPID : CMD_ATTACKPID, pcursplr); + if (leveltype != DTYPE_TOWN && PlayerUnderCursor != nullptr && !myPlayer.friendlyMode) { + NetSendCmdParam1(true, myPlayer.UsesRangedWeapon() ? CMD_RATTACKPID : CMD_ATTACKPID, PlayerUnderCursor->getId()); LastMouseButtonAction = MouseActionType::AttackPlayerTarget; return; } @@ -654,10 +654,10 @@ Point InvGetEquipSlotCoordFromInvSlot(const inv_xy_slot slot) Point GetSlotCoord(int slot) { if (slot >= SLOTXY_BELT_FIRST && slot <= SLOTXY_BELT_LAST) { - return GetPanelPosition(UiPanels::Main, InvRect[slot].position); + return GetPanelPosition(UiPanels::Main, InvRect[slot].Center()); } - return GetPanelPosition(UiPanels::Inventory, InvRect[slot].position); + return GetPanelPosition(UiPanels::Inventory, InvRect[slot].Center()); } /** @@ -666,7 +666,7 @@ Point GetSlotCoord(int slot) int GetItemIdOnSlot(int slot) { if (slot >= SLOTXY_INV_FIRST && slot <= SLOTXY_INV_LAST) { - return abs(MyPlayer->InvGrid[slot - SLOTXY_INV_FIRST]); + return std::abs(MyPlayer->InvGrid[slot - SLOTXY_INV_FIRST]); } return 0; @@ -733,25 +733,12 @@ void ResetInvCursorPosition() } else { mousePos = GetSlotCoord(Slot); } - - if (!MyPlayer->HoldItem.isEmpty()) { - mousePos += Displacement { -INV_SLOT_HALF_SIZE_PX, -INV_SLOT_HALF_SIZE_PX }; - } } else if (Slot >= SLOTXY_BELT_FIRST && Slot <= SLOTXY_BELT_LAST) { mousePos = GetSlotCoord(Slot); - if (!MyPlayer->HoldItem.isEmpty()) - mousePos += Displacement { -INV_SLOT_HALF_SIZE_PX, -INV_SLOT_HALF_SIZE_PX }; } else { mousePos = InvGetEquipSlotCoordFromInvSlot((inv_xy_slot)Slot); - if (!MyPlayer->HoldItem.isEmpty()) { - Size itemSize = GetInventorySize(MyPlayer->HoldItem); - mousePos += Displacement { -INV_SLOT_HALF_SIZE_PX, -INV_SLOT_HALF_SIZE_PX * itemSize.height }; - } } - mousePos.x += (InventorySlotSizeInPixels.width / 2); - mousePos.y -= (InventorySlotSizeInPixels.height / 2); - SetCursorPos(mousePos); } @@ -759,7 +746,6 @@ int FindClosestInventorySlot(Point mousePos) { int shortestDistance = std::numeric_limits::max(); int bestSlot = 0; - mousePos += Displacement { -INV_SLOT_HALF_SIZE_PX, INV_SLOT_HALF_SIZE_PX }; for (int i = 0; i < NUM_XY_SLOTS; i++) { int distance = mousePos.ManhattanDistance(GetSlotCoord(i)); @@ -776,7 +762,6 @@ Point FindClosestStashSlot(Point mousePos) { int shortestDistance = std::numeric_limits::max(); Point bestSlot = {}; - mousePos += Displacement { -INV_SLOT_HALF_SIZE_PX, -INV_SLOT_HALF_SIZE_PX }; for (Point point : PointsInRectangle(Rectangle { { 0, 0 }, Size { 10, 10 } })) { int distance = mousePos.ManhattanDistance(GetStashSlotCoord(point)); @@ -819,7 +804,7 @@ void InventoryMove(AxisDirection dir) const Item &heldItem = MyPlayer->HoldItem; const bool isHoldingItem = !heldItem.isEmpty(); - Size itemSize = GetInventorySize(heldItem); + Size itemSize = isHoldingItem ? GetInventorySize(heldItem) : Size { 1 }; // when item is on cursor (pcurs > 1), this is the real cursor XY if (dir.x == AxisDirectionX_LEFT) { @@ -948,7 +933,7 @@ void InventoryMove(AxisDirection dir) if (Slot == SLOTXY_HEAD || Slot == SLOTXY_CHEST) { Slot = SLOTXY_INV_ROW1_FIRST + 4; } else if (Slot == SLOTXY_RING_LEFT || Slot == SLOTXY_HAND_LEFT) { - Slot = SLOTXY_INV_ROW1_FIRST + 1; + Slot = SLOTXY_INV_ROW1_FIRST + (itemSize.width > 1 ? 0 : 1); } else if (Slot == SLOTXY_RING_RIGHT || Slot == SLOTXY_HAND_RIGHT || Slot == SLOTXY_AMULET) { Slot = SLOTXY_INV_ROW1_LAST - 1; } else if (Slot <= (SLOTXY_INV_ROW4_LAST - (itemSize.height * INV_ROW_SLOT_SIZE))) { @@ -1006,28 +991,27 @@ void InventoryMove(AxisDirection dir) } else { mousePos = GetSlotCoord(Slot); } - // move cursor to the center of the slot if not holding anything or top left is holding an object - if (isHoldingItem) { - if (Slot < SLOTXY_INV_FIRST) { - // The coordinates we get for body slots are based on the centre of the region relative to the hand cursor - // Need to adjust the position for items larger than 1x1 so they're aligned as expected - mousePos.x -= itemSize.width * INV_SLOT_HALF_SIZE_PX; - mousePos.y -= itemSize.height * INV_SLOT_HALF_SIZE_PX; - } - } else { - // get item under new slot if navigating on the inventory - if (Slot >= SLOTXY_INV_FIRST && Slot <= SLOTXY_BELT_LAST) { + // If we're in the inventory we may need to move the cursor to an area that doesn't line up with the center of a cell + if (Slot >= SLOTXY_INV_FIRST && Slot <= SLOTXY_INV_LAST) { + if (!isHoldingItem) { + // If we're not holding an item int8_t itemInvId = GetItemIdOnSlot(Slot); - int itemSlot = FindFirstSlotOnItem(itemInvId); - if (itemSlot < 0) - itemSlot = Slot; - - // offset the cursor so it shows over the center of the item - mousePos = GetSlotCoord(itemSlot); - itemSize = GetItemSizeOnSlot(itemSlot); - mousePos.x += (itemSize.width * InventorySlotSizeInPixels.width) / 2; - mousePos.y += (itemSize.height * InventorySlotSizeInPixels.height) / 2; + if (itemInvId != 0) { + // but the cursor moved over an item + int itemSlot = FindFirstSlotOnItem(itemInvId); + if (itemSlot < 0) + itemSlot = Slot; + + // then we need to offset the cursor so it shows over the center of the item + mousePos = GetSlotCoord(itemSlot); + itemSize = GetItemSizeOnSlot(itemSlot); + } } + // At this point itemSize is either the size of the cell/item the hand cursor is over, or the size of the item we're currently holding. + // mousePos is the center of the top left cell of the item under the hand cursor, or the top left cell of the region that could fit the item we're holding. + // either way we need to offset the mouse position to account for items (we're holding or hovering over) with a dimension larger than a single cell. + mousePos.x += ((itemSize.width - 1) * InventorySlotSizeInPixels.width) / 2; + mousePos.y += ((itemSize.height - 1) * InventorySlotSizeInPixels.height) / 2; } if (mousePos == MousePosition) { @@ -1168,7 +1152,7 @@ void StashMove(AxisDirection dir) } else if (dir.y == AxisDirectionY_DOWN) { if (ActiveStashSlot.y < 10 - itemSize.height) { ActiveStashSlot.y++; - } else if ((holdItem.isEmpty() || CanBePlacedOnBelt(holdItem)) && ActiveStashSlot.x > 1) { + } else if ((holdItem.isEmpty() || CanBePlacedOnBelt(*MyPlayer, holdItem)) && ActiveStashSlot.x > 1) { int beltSlot = ActiveStashSlot.x - 2; Slot = SLOTXY_BELT_FIRST + beltSlot; ActiveStashSlot = InvalidStashPoint; @@ -1183,9 +1167,9 @@ void StashMove(AxisDirection dir) if (ActiveStashSlot != InvalidStashPoint) { Point mousePos = GetStashSlotCoord(ActiveStashSlot); - if (pcurs == CURSOR_HAND) { - mousePos += Displacement { INV_SLOT_HALF_SIZE_PX, INV_SLOT_HALF_SIZE_PX }; - } + // Stash coordinates are all the top left of the cell, so we need to shift the mouse to the center of the held item + // or the center of the cell if we have a hand cursor (itemSize will be 1x1 here so we can use the same calculation) + mousePos += Displacement { itemSize.width * INV_SLOT_HALF_SIZE_PX, itemSize.height * INV_SLOT_HALF_SIZE_PX }; SetCursorPos(mousePos); return; } @@ -1290,13 +1274,11 @@ bool IsPathBlocked(Point position, Direction dir) return !PosOkPlayer(myPlayer, leftStep) && !PosOkPlayer(myPlayer, rightStep); } -void WalkInDir(size_t playerId, AxisDirection dir) +void WalkInDir(Player &player, AxisDirection dir) { - Player &player = Players[playerId]; - if (dir.x == AxisDirectionX_NONE && dir.y == AxisDirectionY_NONE) { if (ControlMode != ControlTypes::KeyboardAndMouse && player.walkpath[0] != WALK_NONE && player.destAction == ACTION_NONE) - NetSendCmdLoc(playerId, true, CMD_WALKXY, player.position.future); // Stop walking + NetSendCmdLoc(player.getId(), true, CMD_WALKXY, player.position.future); // Stop walking return; } @@ -1318,7 +1300,7 @@ void WalkInDir(size_t playerId, AxisDirection dir) return; // Don't start backtrack around obstacles } - NetSendCmdLoc(playerId, true, CMD_WALKXY, delta); + NetSendCmdLoc(player.getId(), true, CMD_WALKXY, delta); } void QuestLogMove(AxisDirection moveDir) @@ -1376,13 +1358,13 @@ void ProcessLeftStickOrDPadGameUI() handler(GetLeftStickOrDpadDirection(false)); } -void Movement(size_t playerId) +void Movement(Player &player) { if (PadMenuNavigatorActive || PadHotspellMenuActive || InGameMenu()) return; if (GetLeftStickOrDPadGameUIHandler() == nullptr) { - WalkInDir(playerId, GetMoveDirection()); + WalkInDir(player, GetMoveDirection()); } } @@ -1483,7 +1465,7 @@ bool ContinueSimulatedMouseEvent(const SDL_Event &event, const ControllerButtonE return SimulatingMouseWithPadmapper || IsSimulatedMouseClickBinding(gamepadEvent); } -string_view ControlTypeToString(ControlTypes controlType) +std::string_view ControlTypeToString(ControlTypes controlType) { switch (controlType) { case ControlTypes::None: @@ -1513,7 +1495,7 @@ void LogControlDeviceAndModeChange(ControlTypes newControlDevice, ControlTypes n } #ifndef USE_SDL1 -string_view GamepadTypeToString(GamepadLayout gamepadLayout) +std::string_view GamepadTypeToString(GamepadLayout gamepadLayout) { switch (gamepadLayout) { case GamepadLayout::Nintendo: @@ -1757,14 +1739,14 @@ void plrctrls_after_check_curs_move() if (ControllerActionHeld != GameActionType_NONE && IsNoneOf(LastMouseButtonAction, MouseActionType::None, MouseActionType::Attack, MouseActionType::Spell)) { InvalidateTargets(); - if (pcursmonst == -1 && ObjectUnderCursor == nullptr && pcursitem == -1 && pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell && pcursplr == -1) { + if (pcursmonst == -1 && ObjectUnderCursor == nullptr && pcursitem == -1 && pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell && PlayerUnderCursor == nullptr) { FindTrigger(); } return; } // Clear focuse set by cursor - pcursplr = -1; + PlayerUnderCursor = nullptr; pcursmonst = -1; pcursitem = -1; ObjectUnderCursor = nullptr; @@ -1780,7 +1762,7 @@ void plrctrls_after_check_curs_move() return; } if (!invflag) { - InfoString = {}; + InfoString = StringOrView {}; FindActor(); FindItemOrObject(); FindTrigger(); @@ -1795,7 +1777,7 @@ void plrctrls_every_frame() void plrctrls_after_game_logic() { - Movement(MyPlayerId); + Movement(*MyPlayer); } void UseBeltItem(int type) @@ -1826,46 +1808,49 @@ void PerformPrimaryAction() } else if (GetRightPanel().contains(MousePosition) || GetMainPanel().contains(MousePosition)) { int inventorySlot = (Slot >= 0) ? Slot : FindClosestInventorySlot(MousePosition); - const Size cursorSizeInCells = MyPlayer->HoldItem.isEmpty() ? Size { 1, 1 } : GetInventorySize(MyPlayer->HoldItem); - - // Find any item occupying a slot that is currently under the cursor - int8_t itemUnderCursor = [](int inventorySlot, Size cursorSizeInCells) { - if (inventorySlot < SLOTXY_INV_FIRST || inventorySlot > SLOTXY_INV_LAST) - return 0; - for (int x = 0; x < cursorSizeInCells.width; x++) { - for (int y = 0; y < cursorSizeInCells.height; y++) { - int slotUnderCursor = inventorySlot + x + y * INV_ROW_SLOT_SIZE; - if (slotUnderCursor > SLOTXY_INV_LAST) - continue; - int itemId = GetItemIdOnSlot(slotUnderCursor); - if (itemId != 0) - return itemId; + int jumpSlot = inventorySlot; // If the cursor is over an inventory slot we may need to adjust it due to pasting items of different sizes over each other + if (inventorySlot >= SLOTXY_INV_FIRST && inventorySlot <= SLOTXY_INV_LAST) { + const Size cursorSizeInCells = MyPlayer->HoldItem.isEmpty() ? Size { 1, 1 } : GetInventorySize(MyPlayer->HoldItem); + + // Find any item occupying a slot that is currently under the cursor + int8_t itemUnderCursor = [](int inventorySlot, Size cursorSizeInCells) { + if (inventorySlot < SLOTXY_INV_FIRST || inventorySlot > SLOTXY_INV_LAST) + return 0; + for (int x = 0; x < cursorSizeInCells.width; x++) { + for (int y = 0; y < cursorSizeInCells.height; y++) { + int slotUnderCursor = inventorySlot + x + y * INV_ROW_SLOT_SIZE; + if (slotUnderCursor > SLOTXY_INV_LAST) + continue; + int itemId = GetItemIdOnSlot(slotUnderCursor); + if (itemId != 0) + return itemId; + } } - } - return 0; - }(inventorySlot, cursorSizeInCells); + return 0; + }(inventorySlot, cursorSizeInCells); - // The cursor will need to be shifted to - // this slot if the item is swapped or lifted - int jumpSlot = FindFirstSlotOnItem(itemUnderCursor); + // Capture the first slot of the first item (if any) under the cursor + if (itemUnderCursor > 0) + jumpSlot = FindFirstSlotOnItem(itemUnderCursor); + } CheckInvItem(); - // If we don't find the item in the same position as before, - // it suggests that the item was swapped or lifted - int newSlot = FindFirstSlotOnItem(itemUnderCursor); - if (jumpSlot >= 0 && jumpSlot != newSlot) { + if (inventorySlot >= SLOTXY_INV_FIRST && inventorySlot <= SLOTXY_INV_LAST) { Point mousePos = GetSlotCoord(jumpSlot); Slot = jumpSlot; + const Size newCursorSizeInCells = MyPlayer->HoldItem.isEmpty() ? GetItemSizeOnSlot(jumpSlot) : GetInventorySize(MyPlayer->HoldItem); + mousePos.x += ((newCursorSizeInCells.width - 1) * InventorySlotSizeInPixels.width) / 2; + mousePos.y += ((newCursorSizeInCells.height - 1) * InventorySlotSizeInPixels.height) / 2; SetCursorPos(mousePos); } } else if (IsStashOpen && GetLeftPanel().contains(MousePosition)) { Point stashSlot = (ActiveStashSlot != InvalidStashPoint) ? ActiveStashSlot : FindClosestStashSlot(MousePosition); - const Size cursorSizeInCells = MyPlayer->HoldItem.isEmpty() ? Size { 1, 1 } : GetInventorySize(MyPlayer->HoldItem); + Size cursorSizeInCells = MyPlayer->HoldItem.isEmpty() ? Size { 1, 1 } : GetInventorySize(MyPlayer->HoldItem); // Find any item occupying a slot that is currently under the cursor StashStruct::StashCell itemUnderCursor = [](Point stashSlot, Size cursorSizeInCells) -> StashStruct::StashCell { - if (stashSlot != InvalidStashPoint) + if (stashSlot == InvalidStashPoint) return StashStruct::EmptyCell; for (Point slotUnderCursor : PointsInRectangle(Rectangle { stashSlot, cursorSizeInCells })) { if (slotUnderCursor.x >= 10 || slotUnderCursor.y >= 10) @@ -1877,20 +1862,26 @@ void PerformPrimaryAction() return StashStruct::EmptyCell; }(stashSlot, cursorSizeInCells); - // The cursor will need to be shifted to - // this slot if the item is swapped or lifted - Point jumpSlot = FindFirstStashSlotOnItem(itemUnderCursor); + Point jumpSlot = itemUnderCursor == StashStruct::EmptyCell ? stashSlot : FindFirstStashSlotOnItem(itemUnderCursor); CheckStashItem(MousePosition); - // If we don't find the item in the same position as before, - // it suggests that the item was swapped or lifted - Point newSlot = FindFirstStashSlotOnItem(itemUnderCursor); - if (jumpSlot != InvalidStashPoint && jumpSlot != newSlot) { - Point mousePos = GetStashSlotCoord(jumpSlot); - mousePos.y -= InventorySlotSizeInPixels.height; - ActiveStashSlot = jumpSlot; - SetCursorPos(mousePos); + Point mousePos = GetStashSlotCoord(jumpSlot); + ActiveStashSlot = jumpSlot; + if (MyPlayer->HoldItem.isEmpty()) { + // For inventory cut/paste we can combine the cases where we swap or simply paste items. Because stash movement is always cell based (there's no fast + // movement over large items) it looks better if we offset the hand cursor to the bottom right cell of the item we just placed. + ActiveStashSlot += Displacement { cursorSizeInCells - 1 }; // shift the active stash slot coordinates to account for items larger than 1x1 + // Then we displace the mouse position to the bottom right corner of the item, then shift it back half a cell to center it. + // Could also be written as (cursorSize - 1) * InventorySlotSize + HalfInventorySlotSize, same thing in the end. + mousePos += Displacement { cursorSizeInCells } * Displacement { InventorySlotSizeInPixels } - Displacement { InventorySlotSizeInPixels } / 2; + } else { + // If we've picked up an item then use the same logic as the inventory so that the cursor is offset to the center of where the old item location was + // (in this case jumpSlot was the top left cell of where it used to be in the grid, and we need to update the cursor size since we're now holding the item) + cursorSizeInCells = GetInventorySize(MyPlayer->HoldItem); + mousePos.x += ((cursorSizeInCells.width) * InventorySlotSizeInPixels.width) / 2; + mousePos.y += ((cursorSizeInCells.height) * InventorySlotSizeInPixels.height) / 2; } + SetCursorPos(mousePos); } return; } @@ -1920,7 +1911,7 @@ bool SpellHasActorTarget() cursPosition = Monsters[pcursmonst].position.tile; } - return pcursplr != -1 || pcursmonst != -1; + return PlayerUnderCursor != nullptr || pcursmonst != -1; } void UpdateSpellTarget(SpellID spell) @@ -1928,7 +1919,7 @@ void UpdateSpellTarget(SpellID spell) if (SpellHasActorTarget()) return; - pcursplr = -1; + PlayerUnderCursor = nullptr; pcursmonst = -1; Player &myPlayer = *MyPlayer; @@ -2008,7 +1999,7 @@ void PerformSpellAction() const Player &myPlayer = *MyPlayer; SpellID spl = myPlayer._pRSpell; - if ((pcursplr == -1 && (spl == SpellID::Resurrect || spl == SpellID::HealOther)) + if ((PlayerUnderCursor == nullptr && (spl == SpellID::Resurrect || spl == SpellID::HealOther)) || (ObjectUnderCursor == nullptr && spl == SpellID::TrapDisarm)) { myPlayer.Say(HeroSpeech::ICantCastThatHere); return; @@ -2016,7 +2007,7 @@ void PerformSpellAction() UpdateSpellTarget(myPlayer._pRSpell); CheckPlrSpell(false); - if (pcursplr != -1) + if (PlayerUnderCursor != nullptr) LastMouseButtonAction = MouseActionType::SpellPlayerTarget; else if (pcursmonst != -1) LastMouseButtonAction = MouseActionType::SpellMonsterTarget; diff --git a/Source/controls/touch/event_handlers.cpp b/Source/controls/touch/event_handlers.cpp index 748e6056b1a..9b688c70964 100644 --- a/Source/controls/touch/event_handlers.cpp +++ b/Source/controls/touch/event_handlers.cpp @@ -9,6 +9,7 @@ #include "gmenu.h" #include "inv.h" #include "panels/spell_book.hpp" +#include "panels/spell_list.hpp" #include "qol/stash.h" #include "stores.h" #include "utils/ui_fwd.h" diff --git a/Source/controls/touch/gamepad.cpp b/Source/controls/touch/gamepad.cpp index 8110dc73ffd..06f88d7ef1e 100644 --- a/Source/controls/touch/gamepad.cpp +++ b/Source/controls/touch/gamepad.cpp @@ -14,6 +14,11 @@ namespace { constexpr double Pi = 3.141592653589793; +int roundToInt(double value) +{ + return static_cast(round(value)); +} + constexpr bool PointsUp(double angle) { constexpr double UpAngle = Pi / 2; @@ -52,7 +57,7 @@ void InitializeVirtualGamepad() int inputMargin = screenPixels / 10; int menuButtonWidth = screenPixels / 10; int directionPadSize = screenPixels / 4; - int padButtonSize = round(1.1 * screenPixels / 10); + int padButtonSize = roundToInt(1.1 * screenPixels / 10); int padButtonSpacing = inputMargin / 3; float hdpi; @@ -70,11 +75,11 @@ void InitializeVirtualGamepad() vdpi *= static_cast(gnScreenHeight) / clientHeight; float dpi = std::min(hdpi, vdpi); - inputMargin = round(0.25 * dpi); - menuButtonWidth = round(0.2 * dpi); - directionPadSize = round(dpi); - padButtonSize = round(0.3 * dpi); - padButtonSpacing = round(0.1 * dpi); + inputMargin = roundToInt(0.25 * dpi); + menuButtonWidth = roundToInt(0.2 * dpi); + directionPadSize = roundToInt(dpi); + padButtonSize = roundToInt(0.3 * dpi); + padButtonSpacing = roundToInt(0.1 * dpi); } int menuPanelTopMargin = 30; @@ -85,7 +90,7 @@ void InitializeVirtualGamepad() int rightMarginMenuButton2 = rightMarginMenuButton3 + menuPanelButtonSpacing + menuPanelButtonSize.width; int rightMarginMenuButton1 = rightMarginMenuButton2 + menuPanelButtonSpacing + menuPanelButtonSize.width; - int padButtonAreaWidth = round(std::sqrt(2) * (padButtonSize + padButtonSpacing)); + int padButtonAreaWidth = roundToInt(std::sqrt(2) * (padButtonSize + padButtonSpacing)); int padButtonRight = gnScreenWidth - inputMargin - padButtonSize / 2; int padButtonLeft = padButtonRight - padButtonAreaWidth; @@ -130,7 +135,7 @@ void InitializeVirtualGamepad() directionPad.position = directionPadArea.position; int standButtonDiagonalOffset = directionPadArea.radius + padButtonSpacing / 2 + padButtonSize / 2; - int standButtonOffset = round(standButtonDiagonalOffset / std::sqrt(2)); + int standButtonOffset = roundToInt(standButtonDiagonalOffset / std::sqrt(2)); Circle &standButtonArea = VirtualGamepadState.standButton.area; standButtonArea.position.x = directionPadArea.position.x - standButtonOffset; standButtonArea.position.y = directionPadArea.position.y + standButtonOffset; @@ -223,8 +228,8 @@ void VirtualDirectionPad::UpdatePosition(Point touchCoordinates) int x = diff.deltaX; int y = diff.deltaY; double dist = sqrt(x * x + y * y); - x = round(x * area.radius / dist); - y = round(y * area.radius / dist); + x = roundToInt(x * area.radius / dist); + y = roundToInt(y * area.radius / dist); position.x = area.position.x + x; position.y = area.position.y + y; } diff --git a/Source/controls/touch/renderers.h b/Source/controls/touch/renderers.h index a449f951977..8b8ccd4adb1 100644 --- a/Source/controls/touch/renderers.h +++ b/Source/controls/touch/renderers.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include @@ -9,7 +10,6 @@ #include "engine/surface.hpp" #include "utils/png.h" #include "utils/sdl_ptrs.h" -#include "utils/stdcompat/optional.hpp" namespace devilution { diff --git a/Source/cursor.cpp b/Source/cursor.cpp index c5af4f927c0..31e39cf30dd 100644 --- a/Source/cursor.cpp +++ b/Source/cursor.cpp @@ -5,6 +5,7 @@ */ #include "cursor.h" +#include #include #include @@ -15,8 +16,10 @@ #include "doom.h" #include "engine.h" #include "engine/backbuffer_state.hpp" +#include "engine/demomode.h" #include "engine/load_cel.hpp" #include "engine/point.hpp" +#include "engine/points_in_rectangle_range.hpp" #include "engine/render/clx_render.hpp" #include "engine/trn.hpp" #include "hwcursor.hpp" @@ -115,6 +118,318 @@ const uint16_t InvItemHeight2[InvItems2Size] = { OptionalOwnedClxSpriteList *HalfSizeItemSprites; OptionalOwnedClxSpriteList *HalfSizeItemSpritesRed; +bool IsValidMonsterForSelection(const Monster &monster) +{ + if (monster.hitPoints >> 6 <= 0) + return false; + if ((monster.flags & MFLAG_HIDDEN) != 0) + return false; + if (monster.isPlayerMinion()) + return false; + return true; +} + +bool TrySelectMonster(bool flipflag, Point tile, tl::function_ref isValidMonster) +{ + auto checkPosition = [&](int8_t selectionType, Displacement displacement) { + Point posToCheck = tile + displacement; + if (!InDungeonBounds(posToCheck) || dMonster[posToCheck.x][posToCheck.y] == 0) + return; + const uint16_t monsterId = std::abs(dMonster[posToCheck.x][posToCheck.y]) - 1; + const Monster &monster = Monsters[monsterId]; + if (IsTileLit(posToCheck) && (monster.data().selectionType & selectionType) != 0 && isValidMonster(monster)) { + cursPosition = posToCheck; + pcursmonst = monsterId; + } + }; + + if (!flipflag) + checkPosition(4, { 2, 1 }); + if (flipflag) + checkPosition(4, { 1, 2 }); + checkPosition(4, { 2, 2 }); + if (!flipflag) + checkPosition(2, { 1, 0 }); + if (flipflag) + checkPosition(2, { 0, 1 }); + checkPosition(1, { 0, 0 }); + checkPosition(2, { 1, 1 }); + return pcursmonst != -1; +} + +bool TrySelectTowner(bool flipflag, Point tile) +{ + auto checkPosition = [&](Displacement displacement) { + Point posToCheck = tile + displacement; + if (!InDungeonBounds(posToCheck) || dMonster[posToCheck.x][posToCheck.y] == 0) + return; + const uint16_t monsterId = std::abs(dMonster[posToCheck.x][posToCheck.y]) - 1; + cursPosition = posToCheck; + pcursmonst = monsterId; + }; + if (!flipflag) + checkPosition({ 1, 0 }); + if (flipflag) + checkPosition({ 0, 1 }); + checkPosition({ 0, 0 }); + checkPosition({ 1, 1 }); + return pcursmonst != -1; +} + +bool TrySelectPlayer(bool flipflag, int mx, int my) +{ + if (!flipflag && mx + 1 < MAXDUNX && dPlayer[mx + 1][my] != 0) { + const uint8_t playerId = std::abs(dPlayer[mx + 1][my]) - 1; + Player &player = Players[playerId]; + if (&player != MyPlayer && player._pHitPoints != 0) { + cursPosition = Point { mx, my } + Displacement { 1, 0 }; + PlayerUnderCursor = &player; + } + } + if (flipflag && my + 1 < MAXDUNY && dPlayer[mx][my + 1] != 0) { + const uint8_t playerId = std::abs(dPlayer[mx][my + 1]) - 1; + Player &player = Players[playerId]; + if (&player != MyPlayer && player._pHitPoints != 0) { + cursPosition = Point { mx, my } + Displacement { 0, 1 }; + PlayerUnderCursor = &player; + } + } + if (dPlayer[mx][my] != 0) { + const uint8_t playerId = std::abs(dPlayer[mx][my]) - 1; + Player &player = Players[playerId]; + if (&player != MyPlayer) { + cursPosition = { mx, my }; + PlayerUnderCursor = &player; + } + } + if (TileContainsDeadPlayer({ mx, my })) { + for (const Player &player : Players) { + if (player.position.tile == Point { mx, my } && &player != MyPlayer) { + cursPosition = { mx, my }; + PlayerUnderCursor = &player; + } + } + } + if (pcurs == CURSOR_RESURRECT) { + for (int xx = -1; xx < 2; xx++) { + for (int yy = -1; yy < 2; yy++) { + if (TileContainsDeadPlayer({ mx + xx, my + yy })) { + for (const Player &player : Players) { + if (player.position.tile.x == mx + xx && player.position.tile.y == my + yy && &player != MyPlayer) { + cursPosition = Point { mx, my } + Displacement { xx, yy }; + PlayerUnderCursor = &player; + } + } + } + } + } + } + if (mx + 1 < MAXDUNX && my + 1 < MAXDUNY && dPlayer[mx + 1][my + 1] != 0) { + const uint8_t playerId = std::abs(dPlayer[mx + 1][my + 1]) - 1; + const Player &player = Players[playerId]; + if (&player != MyPlayer && player._pHitPoints != 0) { + cursPosition = Point { mx, my } + Displacement { 1, 1 }; + PlayerUnderCursor = &player; + } + } + + return PlayerUnderCursor != nullptr; +} + +bool TrySelectObject(bool flipflag, Point tile) +{ + // No monsters or players under the cursor, try find an object starting with the tile below the current tile (tall + // objects like doors) + Point testPosition = tile + Direction::South; + Object *object = FindObjectAtPosition(testPosition); + + if (object == nullptr || object->_oSelFlag < 2) { + // Either no object or can't interact from the test position, try the current tile + testPosition = tile; + object = FindObjectAtPosition(testPosition); + + if (object == nullptr || IsNoneOf(object->_oSelFlag, 1, 3)) { + // Still no object (that could be activated from this position), try the tile to the bottom left or right + // (whichever is closest to the cursor as determined when we set flipflag earlier) + testPosition = tile + (flipflag ? Direction::SouthWest : Direction::SouthEast); + object = FindObjectAtPosition(testPosition); + + if (object != nullptr && object->_oSelFlag < 2) { + // Found an object but it's not in range, clear the pointer + object = nullptr; + } + } + } + if (object == nullptr) + return false; + + // found object that can be activated with the given cursor position + cursPosition = testPosition; + ObjectUnderCursor = object; + return true; +} + +bool TrySelectItem(bool flipflag, int mx, int my) +{ + if (!flipflag && mx + 1 < MAXDUNX && dItem[mx + 1][my] > 0) { + const uint8_t itemId = dItem[mx + 1][my] - 1; + if (Items[itemId]._iSelFlag >= 2) { + cursPosition = Point { mx, my } + Displacement { 1, 0 }; + pcursitem = static_cast(itemId); + } + } + if (flipflag && my + 1 < MAXDUNY && dItem[mx][my + 1] > 0) { + const uint8_t itemId = dItem[mx][my + 1] - 1; + if (Items[itemId]._iSelFlag >= 2) { + cursPosition = Point { mx, my } + Displacement { 0, 1 }; + pcursitem = static_cast(itemId); + } + } + if (dItem[mx][my] > 0) { + const uint8_t itemId = dItem[mx][my] - 1; + if (Items[itemId]._iSelFlag == 1 || Items[itemId]._iSelFlag == 3) { + cursPosition = { mx, my }; + pcursitem = static_cast(itemId); + } + } + if (mx + 1 < MAXDUNX && my + 1 < MAXDUNY && dItem[mx + 1][my + 1] > 0) { + const uint8_t itemId = dItem[mx + 1][my + 1] - 1; + if (Items[itemId]._iSelFlag >= 2) { + cursPosition = Point { mx, my } + Displacement { 1, 1 }; + pcursitem = static_cast(itemId); + } + } + return pcursitem != -1; +} + +bool TrySelectPixelBased(Point tile) +{ + if (demo::IsRunning() || demo::IsRecording() || HeadlessMode) { + // Recorded demos can run headless, but headless mode doesn't support loading sprites that are needed for pixel perfect selection + // => Ensure demos are always compatible + // => Never use sprites for selection when handling demos + return false; + } + + auto checkSprite = [](Point renderingTile, const ClxSprite sprite, Displacement renderingOffset) { + const Point renderPosition = GetScreenPosition(renderingTile) + renderingOffset; + Point spriteTopLeft = renderPosition - Displacement { 0, sprite.height() }; + Size spriteSize = { sprite.width(), sprite.height() }; + if (*sgOptions.Graphics.zoom) { + spriteSize *= 2; + spriteTopLeft *= 2; + } + const Rectangle spriteCoords = Rectangle(spriteTopLeft, spriteSize); + if (!spriteCoords.contains(MousePosition)) + return false; + Point pointInSprite = Point { 0, 0 } + (MousePosition - spriteCoords.position); + if (*sgOptions.Graphics.zoom) + pointInSprite /= 2; + return IsPointWithinClx(pointInSprite, sprite); + }; + + auto convertFromRenderingToWorldTile = [](Point renderingPoint) { + // Columns + Displacement ret = Displacement(Direction::East) * renderingPoint.x; + // Rows + ret += Displacement(Direction::South) * renderingPoint.y / 2; + if (renderingPoint.y & 1) + ret.deltaY += 1; + return ret; + }; + + // Try to find the selected entity from rendered pixels. + // We search the rendered rows/columns backwards, because the last rendered tile overrides previous rendered pixels. + auto searchArea = PointsInRectangle(Rectangle { { -1, -1 }, { 3, 8 } }); + for (auto it = searchArea.rbegin(); it != searchArea.rend(); ++it) { + Point renderingColumnRaw = *it; + Point adjacentTile = tile + convertFromRenderingToWorldTile(renderingColumnRaw); + if (!InDungeonBounds(adjacentTile)) + continue; + + int monsterId = dMonster[adjacentTile.x][adjacentTile.y]; + // Never select a monster if a target-player-only spell is selected + if (monsterId != 0 && IsNoneOf(pcurs, CURSOR_HEALOTHER, CURSOR_RESURRECT)) { + monsterId = std::abs(monsterId) - 1; + if (leveltype == DTYPE_TOWN) { + const Towner &towner = Towners[monsterId]; + const ClxSprite sprite = towner.currentSprite(); + Displacement renderingOffset = towner.getRenderingOffset(); + if (checkSprite(adjacentTile, sprite, renderingOffset)) { + cursPosition = adjacentTile; + pcursmonst = monsterId; + return true; + } + } else { + const Monster &monster = Monsters[monsterId]; + if (IsTileLit(adjacentTile) && IsValidMonsterForSelection(monster)) { + const ClxSprite sprite = monster.animInfo.currentSprite(); + Displacement renderingOffset = monster.getRenderingOffset(sprite); + if (checkSprite(adjacentTile, sprite, renderingOffset)) { + cursPosition = adjacentTile; + pcursmonst = monsterId; + return true; + } + } + } + } + + const int8_t dPlayerValue = dPlayer[adjacentTile.x][adjacentTile.y]; + if (dPlayerValue != 0) { + const uint8_t playerId = std::abs(dPlayerValue) - 1; + if (playerId != MyPlayerId) { + const Player &player = Players[playerId]; + const ClxSprite sprite = player.currentSprite(); + Displacement renderingOffset = player.getRenderingOffset(sprite); + if (checkSprite(adjacentTile, sprite, renderingOffset)) { + cursPosition = adjacentTile; + PlayerUnderCursor = &player; + return true; + } + } + } + if (TileContainsDeadPlayer(adjacentTile)) { + for (const Player &player : Players) { + if (player.position.tile == adjacentTile && &player != MyPlayer) { + const ClxSprite sprite = player.currentSprite(); + Displacement renderingOffset = player.getRenderingOffset(sprite); + if (checkSprite(adjacentTile, sprite, renderingOffset)) { + cursPosition = adjacentTile; + PlayerUnderCursor = &player; + return true; + } + } + } + } + + Object *object = FindObjectAtPosition(adjacentTile); + if (object != nullptr && object->_oSelFlag != 0) { + const ClxSprite sprite = object->currentSprite(); + Displacement renderingOffset = object->getRenderingOffset(sprite, adjacentTile); + if (checkSprite(adjacentTile, sprite, renderingOffset)) { + cursPosition = adjacentTile; + ObjectUnderCursor = object; + return true; + } + } + + uint8_t itemId = dItem[adjacentTile.x][adjacentTile.y]; + if (itemId != 0) { + itemId = itemId - 1; + const Item &item = Items[itemId]; + const ClxSprite sprite = item.AnimInfo.currentSprite(); + Displacement renderingOffset = item.getRenderingOffset(sprite); + if (checkSprite(adjacentTile, sprite, renderingOffset)) { + cursPosition = adjacentTile; + pcursitem = static_cast(itemId); + return true; + } + } + } + + return false; +} + } // namespace /** Current highlighted monster */ @@ -129,7 +444,7 @@ int8_t pcursitem; /** Current highlighted object */ Object *ObjectUnderCursor; /** Current highlighted player */ -int8_t pcursplr; +const Player *PlayerUnderCursor; /** Current highlighted tile position */ Point cursPosition; /** Previously highlighted monster */ @@ -313,7 +628,7 @@ void InitLevelCursor() ObjectUnderCursor = nullptr; pcursitem = -1; pcursstashitem = StashStruct::EmptyCell; - pcursplr = -1; + PlayerUnderCursor = nullptr; ClearCursor(); } @@ -439,8 +754,8 @@ void CheckCursMove() mx++; } - mx = clamp(mx, 0, MAXDUNX - 1); - my = clamp(my, 0, MAXDUNY - 1); + mx = std::clamp(mx, 0, MAXDUNX - 1); + my = std::clamp(my, 0, MAXDUNY - 1); const Point currentTile { mx, my }; @@ -448,7 +763,7 @@ void CheckCursMove() if ((sgbMouseDown != CLICK_NONE || ControllerActionHeld != GameActionType_NONE) && IsNoneOf(LastMouseButtonAction, MouseActionType::None, MouseActionType::Attack, MouseActionType::Spell)) { InvalidateTargets(); - if (pcursmonst == -1 && ObjectUnderCursor == nullptr && pcursitem == -1 && pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell && pcursplr == -1) { + if (pcursmonst == -1 && ObjectUnderCursor == nullptr && pcursitem == -1 && pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell && PlayerUnderCursor == nullptr) { cursPosition = { mx, my }; CheckTrigForce(); CheckTown(); @@ -468,7 +783,7 @@ void CheckCursMove() } pcursinvitem = -1; pcursstashitem = StashStruct::EmptyCell; - pcursplr = -1; + PlayerUnderCursor = nullptr; ShowUniqueItemInfoBox = false; panelflag = false; trigflag = false; @@ -501,273 +816,61 @@ void CheckCursMove() return; } + if (pcurs == CURSOR_IDENTIFY) { + ObjectUnderCursor = nullptr; + pcursmonst = -1; + pcursitem = -1; + cursPosition = { mx, my }; + return; + } + + if (TrySelectPixelBased(currentTile)) + return; + if (leveltype != DTYPE_TOWN) { - if (pcurstemp != -1) { - if (!flipflag && mx + 2 < MAXDUNX && my + 1 < MAXDUNY && dMonster[mx + 2][my + 1] != 0 && IsTileLit({ mx + 2, my + 1 })) { - const uint16_t monsterId = abs(dMonster[mx + 2][my + 1]) - 1; - if (monsterId == pcurstemp && Monsters[monsterId].hitPoints >> 6 > 0 && (Monsters[monsterId].data().selectionType & 4) != 0) { - cursPosition = Point { mx, my } + Displacement { 2, 1 }; - pcursmonst = monsterId; - } - } - if (flipflag && mx + 1 < MAXDUNX && my + 2 < MAXDUNY && dMonster[mx + 1][my + 2] != 0 && IsTileLit({ mx + 1, my + 2 })) { - const uint16_t monsterId = abs(dMonster[mx + 1][my + 2]) - 1; - if (monsterId == pcurstemp && Monsters[monsterId].hitPoints >> 6 > 0 && (Monsters[monsterId].data().selectionType & 4) != 0) { - cursPosition = Point { mx, my } + Displacement { 1, 2 }; - pcursmonst = monsterId; - } - } - if (mx + 2 < MAXDUNX && my + 2 < MAXDUNY && dMonster[mx + 2][my + 2] != 0 && IsTileLit({ mx + 2, my + 2 })) { - const uint16_t monsterId = abs(dMonster[mx + 2][my + 2]) - 1; - if (monsterId == pcurstemp && Monsters[monsterId].hitPoints >> 6 > 0 && (Monsters[monsterId].data().selectionType & 4) != 0) { - cursPosition = Point { mx, my } + Displacement { 2, 2 }; - pcursmonst = monsterId; - } - } - if (mx + 1 < MAXDUNX && !flipflag && dMonster[mx + 1][my] != 0 && IsTileLit({ mx + 1, my })) { - const uint16_t monsterId = abs(dMonster[mx + 1][my]) - 1; - if (monsterId == pcurstemp && Monsters[monsterId].hitPoints >> 6 > 0 && (Monsters[monsterId].data().selectionType & 2) != 0) { - cursPosition = Point { mx, my } + Displacement { 1, 0 }; - pcursmonst = monsterId; - } - } - if (my + 1 < MAXDUNY && flipflag && dMonster[mx][my + 1] != 0 && IsTileLit({ mx, my + 1 })) { - const uint16_t monsterId = abs(dMonster[mx][my + 1]) - 1; - if (monsterId == pcurstemp && Monsters[monsterId].hitPoints >> 6 > 0 && (Monsters[monsterId].data().selectionType & 2) != 0) { - cursPosition = Point { mx, my } + Displacement { 0, 1 }; - pcursmonst = monsterId; - } - } - if (dMonster[mx][my] != 0 && IsTileLit({ mx, my })) { - const uint16_t monsterId = abs(dMonster[mx][my]) - 1; - if (monsterId == pcurstemp && Monsters[monsterId].hitPoints >> 6 > 0 && (Monsters[monsterId].data().selectionType & 1) != 0) { - cursPosition = { mx, my }; - pcursmonst = monsterId; - } - } - if (mx + 1 < MAXDUNX && my + 1 < MAXDUNY && dMonster[mx + 1][my + 1] != 0 && IsTileLit({ mx + 1, my + 1 })) { - const uint16_t monsterId = abs(dMonster[mx + 1][my + 1]) - 1; - if (monsterId == pcurstemp && Monsters[monsterId].hitPoints >> 6 > 0 && (Monsters[monsterId].data().selectionType & 2) != 0) { - cursPosition = Point { mx, my } + Displacement { 1, 1 }; - pcursmonst = monsterId; - } - } - if (pcursmonst != -1 && (Monsters[pcursmonst].flags & MFLAG_HIDDEN) != 0) { - pcursmonst = -1; - cursPosition = { mx, my }; - } - if (pcursmonst != -1 && Monsters[pcursmonst].isPlayerMinion()) { - pcursmonst = -1; - } - if (pcursmonst != -1) { + // Never select a monster if a target-player-only spell is selected + if (IsNoneOf(pcurs, CURSOR_HEALOTHER, CURSOR_RESURRECT)) { + if (pcurstemp != -1 && TrySelectMonster(flipflag, currentTile, [](const Monster &monster) { + if (!IsValidMonsterForSelection(monster)) + return false; + if (monster.getId() != static_cast(pcurstemp)) + return false; + return true; + })) { + // found a valid previous selected monster return; } - } - if (!flipflag && mx + 2 < MAXDUNX && my + 1 < MAXDUNY && dMonster[mx + 2][my + 1] != 0 && IsTileLit({ mx + 2, my + 1 })) { - int monsterId = abs(dMonster[mx + 2][my + 1]) - 1; - if (Monsters[monsterId].hitPoints >> 6 > 0 && (Monsters[monsterId].data().selectionType & 4) != 0) { - cursPosition = Point { mx, my } + Displacement { 2, 1 }; - pcursmonst = monsterId; - } - } - if (flipflag && mx + 1 < MAXDUNX && my + 2 < MAXDUNY && dMonster[mx + 1][my + 2] != 0 && IsTileLit({ mx + 1, my + 2 })) { - const uint16_t monsterId = abs(dMonster[mx + 1][my + 2]) - 1; - if (Monsters[monsterId].hitPoints >> 6 > 0 && (Monsters[monsterId].data().selectionType & 4) != 0) { - cursPosition = Point { mx, my } + Displacement { 1, 2 }; - pcursmonst = monsterId; - } - } - if (mx + 2 < MAXDUNX && my + 2 < MAXDUNY && dMonster[mx + 2][my + 2] != 0 && IsTileLit({ mx + 2, my + 2 })) { - const uint16_t monsterId = abs(dMonster[mx + 2][my + 2]) - 1; - if (Monsters[monsterId].hitPoints >> 6 > 0 && (Monsters[monsterId].data().selectionType & 4) != 0) { - cursPosition = Point { mx, my } + Displacement { 2, 2 }; - pcursmonst = monsterId; - } - } - if (!flipflag && mx + 1 < MAXDUNX && dMonster[mx + 1][my] != 0 && IsTileLit({ mx + 1, my })) { - const uint16_t monsterId = abs(dMonster[mx + 1][my]) - 1; - if (Monsters[monsterId].hitPoints >> 6 > 0 && (Monsters[monsterId].data().selectionType & 2) != 0) { - cursPosition = Point { mx, my } + Displacement { 1, 0 }; - pcursmonst = monsterId; - } - } - if (flipflag && my + 1 < MAXDUNY && dMonster[mx][my + 1] != 0 && IsTileLit({ mx, my + 1 })) { - const uint16_t monsterId = abs(dMonster[mx][my + 1]) - 1; - if (Monsters[monsterId].hitPoints >> 6 > 0 && (Monsters[monsterId].data().selectionType & 2) != 0) { - cursPosition = Point { mx, my } + Displacement { 0, 1 }; - pcursmonst = monsterId; - } - } - if (dMonster[mx][my] != 0 && IsTileLit({ mx, my })) { - const uint16_t monsterId = abs(dMonster[mx][my]) - 1; - if (Monsters[monsterId].hitPoints >> 6 > 0 && (Monsters[monsterId].data().selectionType & 1) != 0) { - cursPosition = { mx, my }; - pcursmonst = monsterId; - } - } - if (mx + 1 < MAXDUNX && my + 1 < MAXDUNY && dMonster[mx + 1][my + 1] != 0 && IsTileLit({ mx + 1, my + 1 })) { - const uint16_t monsterId = abs(dMonster[mx + 1][my + 1]) - 1; - if (Monsters[monsterId].hitPoints >> 6 > 0 && (Monsters[monsterId].data().selectionType & 2) != 0) { - cursPosition = Point { mx, my } + Displacement { 1, 1 }; - pcursmonst = monsterId; + if (TrySelectMonster(flipflag, currentTile, IsValidMonsterForSelection)) { + // found a valid monster + return; } } - if (pcursmonst != -1 && (Monsters[pcursmonst].flags & MFLAG_HIDDEN) != 0) { - pcursmonst = -1; - cursPosition = { mx, my }; - } - if (pcursmonst != -1 && (Monsters[pcursmonst].isPlayerMinion() || IsAnyOf(pcurs, CURSOR_HEALOTHER, CURSOR_RESURRECT))) { - pcursmonst = -1; - } } else { - if (!flipflag && mx + 1 < MAXDUNX && dMonster[mx + 1][my] > 0) { - pcursmonst = dMonster[mx + 1][my] - 1; - cursPosition = Point { mx, my } + Displacement { 1, 0 }; - } - if (flipflag && my + 1 < MAXDUNY && dMonster[mx][my + 1] > 0) { - pcursmonst = dMonster[mx][my + 1] - 1; - cursPosition = Point { mx, my } + Displacement { 0, 1 }; - } - if (dMonster[mx][my] > 0) { - pcursmonst = dMonster[mx][my] - 1; - cursPosition = { mx, my }; - } - if (mx + 1 < MAXDUNX && my + 1 < MAXDUNY && dMonster[mx + 1][my + 1] > 0) { - pcursmonst = dMonster[mx + 1][my + 1] - 1; - cursPosition = Point { mx, my } + Displacement { 1, 1 }; + if (TrySelectTowner(flipflag, currentTile)) { + // found a towner + return; } } - if (pcursmonst == -1) { - if (!flipflag && mx + 1 < MAXDUNX && dPlayer[mx + 1][my] != 0) { - const uint8_t playerId = abs(dPlayer[mx + 1][my]) - 1; - Player &player = Players[playerId]; - if (&player != MyPlayer && player._pHitPoints != 0) { - cursPosition = Point { mx, my } + Displacement { 1, 0 }; - pcursplr = static_cast(playerId); - } - } - if (flipflag && my + 1 < MAXDUNY && dPlayer[mx][my + 1] != 0) { - const uint8_t playerId = abs(dPlayer[mx][my + 1]) - 1; - Player &player = Players[playerId]; - if (&player != MyPlayer && player._pHitPoints != 0) { - cursPosition = Point { mx, my } + Displacement { 0, 1 }; - pcursplr = static_cast(playerId); - } - } - if (dPlayer[mx][my] != 0) { - const uint8_t playerId = abs(dPlayer[mx][my]) - 1; - if (playerId != MyPlayerId) { - cursPosition = { mx, my }; - pcursplr = static_cast(playerId); - } - } - if (TileContainsDeadPlayer({ mx, my })) { - for (const Player &player : Players) { - if (player.position.tile == Point { mx, my } && &player != MyPlayer) { - cursPosition = { mx, my }; - pcursplr = static_cast(player.getId()); - } - } - } - if (pcurs == CURSOR_RESURRECT) { - for (int xx = -1; xx < 2; xx++) { - for (int yy = -1; yy < 2; yy++) { - if (TileContainsDeadPlayer({ mx + xx, my + yy })) { - for (const Player &player : Players) { - if (player.position.tile.x == mx + xx && player.position.tile.y == my + yy && &player != MyPlayer) { - cursPosition = Point { mx, my } + Displacement { xx, yy }; - pcursplr = static_cast(player.getId()); - } - } - } - } - } - } - if (mx + 1 < MAXDUNX && my + 1 < MAXDUNY && dPlayer[mx + 1][my + 1] != 0) { - const uint8_t playerId = abs(dPlayer[mx + 1][my + 1]) - 1; - const Player &player = Players[playerId]; - if (&player != MyPlayer && player._pHitPoints != 0) { - cursPosition = Point { mx, my } + Displacement { 1, 1 }; - pcursplr = static_cast(playerId); - } - } + if (TrySelectPlayer(flipflag, mx, my)) { + // found a player + return; } - if (pcursmonst == -1 && pcursplr == -1) { - // No monsters or players under the cursor, try find an object starting with the tile below the current tile (tall - // objects like doors) - Point testPosition = currentTile + Direction::South; - Object *object = FindObjectAtPosition(testPosition); - if (object == nullptr || object->_oSelFlag < 2) { - // Either no object or can't interact from the test position, try the current tile - testPosition = currentTile; - object = FindObjectAtPosition(testPosition); - - if (object == nullptr || IsNoneOf(object->_oSelFlag, 1, 3)) { - // Still no object (that could be activated from this position), try the tile to the bottom left or right - // (whichever is closest to the cursor as determined when we set flipflag earlier) - testPosition = currentTile + (flipflag ? Direction::SouthWest : Direction::SouthEast); - object = FindObjectAtPosition(testPosition); - - if (object != nullptr && object->_oSelFlag < 2) { - // Found an object but it's not in range, clear the pointer - object = nullptr; - } - } - } - if (object != nullptr) { - // found object that can be activated with the given cursor position - cursPosition = testPosition; - ObjectUnderCursor = object; - } - } - if (pcursplr == -1 && ObjectUnderCursor == nullptr && pcursmonst == -1) { - if (!flipflag && mx + 1 < MAXDUNX && dItem[mx + 1][my] > 0) { - const uint8_t itemId = dItem[mx + 1][my] - 1; - if (Items[itemId]._iSelFlag >= 2) { - cursPosition = Point { mx, my } + Displacement { 1, 0 }; - pcursitem = static_cast(itemId); - } - } - if (flipflag && my + 1 < MAXDUNY && dItem[mx][my + 1] > 0) { - const uint8_t itemId = dItem[mx][my + 1] - 1; - if (Items[itemId]._iSelFlag >= 2) { - cursPosition = Point { mx, my } + Displacement { 0, 1 }; - pcursitem = static_cast(itemId); - } - } - if (dItem[mx][my] > 0) { - const uint8_t itemId = dItem[mx][my] - 1; - if (Items[itemId]._iSelFlag == 1 || Items[itemId]._iSelFlag == 3) { - cursPosition = { mx, my }; - pcursitem = static_cast(itemId); - } - } - if (mx + 1 < MAXDUNX && my + 1 < MAXDUNY && dItem[mx + 1][my + 1] > 0) { - const uint8_t itemId = dItem[mx + 1][my + 1] - 1; - if (Items[itemId]._iSelFlag >= 2) { - cursPosition = Point { mx, my } + Displacement { 1, 1 }; - pcursitem = static_cast(itemId); - } - } - if (pcursitem == -1) { - cursPosition = { mx, my }; - CheckTrigForce(); - CheckTown(); - CheckRportal(); - } + if (TrySelectObject(flipflag, currentTile)) { + // found an object + return; } - if (pcurs == CURSOR_IDENTIFY) { - ObjectUnderCursor = nullptr; - pcursmonst = -1; - pcursitem = -1; - cursPosition = { mx, my }; - } - if (pcursmonst != -1 && leveltype != DTYPE_TOWN && Monsters[pcursmonst].isPlayerMinion()) { - pcursmonst = -1; + if (TrySelectItem(flipflag, mx, my)) { + // found an item + return; } + + cursPosition = currentTile; + CheckTrigForce(); + CheckTown(); + CheckRportal(); } } // namespace devilution diff --git a/Source/cursor.h b/Source/cursor.h index 2803230e8ad..57e0c9f9afe 100644 --- a/Source/cursor.h +++ b/Source/cursor.h @@ -6,12 +6,12 @@ #pragma once #include +#include #include #include "engine.h" #include "engine/clx_sprite.hpp" #include "utils/attributes.h" -#include "utils/stdcompat/optional.hpp" namespace devilution { @@ -39,7 +39,8 @@ extern int8_t pcursitem; struct Object; // Defined in objects.h extern Object *ObjectUnderCursor; -extern int8_t pcursplr; +struct Player; // Defined in player.h +extern const Player *PlayerUnderCursor; extern Point cursPosition; extern DVL_API_FOR_TEST int pcurs; diff --git a/Source/data/file.cpp b/Source/data/file.cpp new file mode 100644 index 00000000000..b2760d05735 --- /dev/null +++ b/Source/data/file.cpp @@ -0,0 +1,162 @@ +#include "file.hpp" + +#include + +#include "engine/assets.hpp" +#include "utils/algorithm/container.hpp" +#include "utils/language.h" + +namespace devilution { +tl::expected DataFile::load(std::string_view path) +{ + AssetRef ref = FindAsset(path); + if (!ref.ok()) + return tl::unexpected { Error::NotFound }; + const size_t size = ref.size(); + // TODO: It should be possible to stream the data file contents instead of copying the whole thing into memory + std::unique_ptr data { new char[size] }; + { + AssetHandle handle = OpenAsset(std::move(ref)); + if (!handle.ok()) + return tl::unexpected { Error::OpenFailed }; + if (size > 0 && !handle.read(data.get(), size)) + return tl::unexpected { Error::BadRead }; + } + return DataFile { std::move(data), size }; +} + +DataFile DataFile::loadOrDie(std::string_view path) +{ + tl::expected dataFileResult = DataFile::load(path); + if (!dataFileResult.has_value()) { + DataFile::reportFatalError(dataFileResult.error(), path); + } + return *std::move(dataFileResult); +} + +void DataFile::reportFatalError(Error code, std::string_view fileName) +{ + switch (code) { + case Error::NotFound: + case Error::OpenFailed: + case Error::BadRead: + app_fatal(fmt::format(fmt::runtime(_( + /* TRANSLATORS: Error message when a data file is missing or corrupt. Arguments are {file name} */ + "Unable to load data from file {0}")), + fileName)); + case Error::NoContent: + app_fatal(fmt::format(fmt::runtime(_( + /* TRANSLATORS: Error message when a data file is empty or only contains the header row. Arguments are {file name} */ + "{0} is incomplete, please check the file contents.")), + fileName)); + case Error::NotEnoughColumns: + app_fatal(fmt::format(fmt::runtime(_( + /* TRANSLATORS: Error message when a data file doesn't contain the expected columns. Arguments are {file name} */ + "Your {0} file doesn't have the expected columns, please make sure it matches the documented format.")), + fileName)); + } +} + +void DataFile::reportFatalFieldError(DataFileField::Error code, std::string_view fileName, std::string_view fieldName, const DataFileField &field, std::string_view details) +{ + std::string detailsStr; + if (!details.empty()) { + detailsStr = StrCat("\n", details); + } + switch (code) { + case DataFileField::Error::NotANumber: + app_fatal(fmt::format(fmt::runtime(_( + /* TRANSLATORS: Error message when parsing a data file and a text value is encountered when a number is expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} */ + "Non-numeric value {0} for {1} in {2} at row {3} and column {4}")), + field.currentValue(), fieldName, fileName, field.row(), field.column()) + .append(detailsStr)); + case DataFileField::Error::OutOfRange: + app_fatal(fmt::format(fmt::runtime(_( + /* TRANSLATORS: Error message when parsing a data file and we find a number larger than expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} */ + "Out of range value {0} for {1} in {2} at row {3} and column {4}")), + field.currentValue(), fieldName, fileName, field.row(), field.column()) + .append(detailsStr)); + case DataFileField::Error::InvalidValue: + app_fatal(fmt::format(fmt::runtime(_( + /* TRANSLATORS: Error message when we find an unrecognised value in a key column. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} */ + "Invalid value {0} for {1} in {2} at row {3} and column {4}")), + field.currentValue(), fieldName, fileName, field.row(), field.column()) + .append(detailsStr)); + } +} + +tl::expected DataFile::parseHeader(ColumnDefinition *begin, ColumnDefinition *end, tl::function_ref(std::string_view)> mapper) +{ + std::bitset::max()> seenColumns; + unsigned lastColumn = 0; + + RecordIterator firstRecord { data(), data() + size(), false }; + for (DataFileField field : *firstRecord) { + if (begin == end) { + // All key columns have been identified + break; + } + + auto mapResult = mapper(*field); + if (!mapResult.has_value()) { + // not a key column + continue; + } + + uint8_t columnType = mapResult.value(); + if (seenColumns.test(columnType)) { + // Repeated column? unusual, maybe this should be an error + continue; + } + seenColumns.set(columnType); + + unsigned skipColumns = 0; + if (field.column() > lastColumn) + skipColumns = field.column() - lastColumn - 1; + lastColumn = field.column(); + + *begin = { columnType, skipColumns }; + ++begin; + } + + // Incrementing the iterator causes it to read to the end of the record in case we broke early (maybe there were extra columns) + ++firstRecord; + if (firstRecord == this->end()) { + return tl::unexpected { Error::NoContent }; + } + + body_ = firstRecord.data(); + + if (begin != end) { + return tl::unexpected { Error::NotEnoughColumns }; + } + return {}; +} + +tl::expected DataFile::skipHeader() +{ + RecordIterator it { data(), data() + size(), false }; + ++it; + if (it == this->end()) { + return tl::unexpected { Error::NoContent }; + } + body_ = it.data(); + return {}; +} + +void DataFile::skipHeaderOrDie(std::string_view path) +{ + if (tl::expected result = skipHeader(); !result.has_value()) { + DataFile::reportFatalError(result.error(), path); + } +} + +[[nodiscard]] size_t DataFile::numRecords() const +{ + if (content_.empty()) return 0; + const auto numNewlines = static_cast(c_count(content_, '\n') + (content_.back() == '\n' ? 0 : 1)); + if (numNewlines < 2) return 0; + return static_cast(numNewlines - 1); +} + +} // namespace devilution diff --git a/Source/data/file.hpp b/Source/data/file.hpp new file mode 100644 index 00000000000..39a759cf0f5 --- /dev/null +++ b/Source/data/file.hpp @@ -0,0 +1,140 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "iterators.hpp" + +namespace devilution { + +struct ColumnDefinition { + enum class Error { + UnknownColumn + }; + + uint8_t type = std::numeric_limits::max(); + + // The number of fields between this column and the last one identified as important (or from start of the record if this is the first column we care about) + unsigned skipLength = 0; + + bool operator==(const ColumnDefinition &other) const = default; + + template + explicit operator T() const + { + return static_cast(type); + } +}; + +/** + * @brief Container for a tab-delimited file following the TSV-like format described in txtdata/Readme.md + */ +class DataFile { + std::unique_ptr data_; + std::string_view content_; + + const char *body_; + + DataFile() = delete; + + /** + * @brief Creates a view over a sequence of utf8 code units, skipping over the BOM if present + * @param data pointer to the raw data backing the view (this container will take ownership to ensure the lifetime of the view) + * @param size total number of bytes/code units including the BOM if present + */ + DataFile(std::unique_ptr &&data, size_t size) + : data_(std::move(data)) + , content_(data_.get(), size) + { + constexpr std::string_view utf8BOM = "\xef\xbb\xbf"; + if (this->content_.starts_with(utf8BOM)) + this->content_.remove_prefix(utf8BOM.size()); + + body_ = this->content_.data(); + } + +public: + enum class Error { + NotFound, + OpenFailed, + BadRead, + NoContent, + NotEnoughColumns + }; + + /** + * @brief Attempts to load a data file (using the same mechanism as other runtime assets) + * + * @param path file to load including the /txtdata/ prefix + * @return an object containing an owned pointer to an in-memory copy of the file + * or an error code describing the reason for failure. + */ + static tl::expected load(std::string_view path); + + static DataFile loadOrDie(std::string_view path); + + static void reportFatalError(Error code, std::string_view fileName); + static void reportFatalFieldError(DataFileField::Error code, std::string_view fileName, std::string_view fieldName, const DataFileField &field, std::string_view details = {}); + + void resetHeader() + { + body_ = content_.data(); + } + + /** + * @brief Attempts to parse the first row/record in the file, populating the range defined by [begin, end) using the provided mapping function + * + * This method will also set an internal marker so that future uses of the begin iterator skip the header line. + * @param begin Start of the destination range + * @param end End of the destination range + * @param mapper Function that maps from a string_view to a unique numeric identifier for the column + * @return If the file ends after the header or not enough columns were defined this function returns an error code describing the failure. + */ + [[nodiscard]] tl::expected parseHeader(ColumnDefinition *begin, ColumnDefinition *end, tl::function_ref(std::string_view)> mapper); + + /** + * @brief Templated version of parseHeader(uint8_t) to allow using directly with enum definitions of columns + * @tparam T An enum or any type that defines operator uint8_t() + * @param begin Start of the destination range + * @param end End of the destination range + * @param typedMapper Function that maps from a string_view to a unique T value + * @return A void success result or an error code as described above + */ + template + [[nodiscard]] tl::expected parseHeader(ColumnDefinition *begin, ColumnDefinition *end, std::function(std::string_view)> typedMapper) + { + return parseHeader(begin, end, [typedMapper](std::string_view label) { return typedMapper(label).transform([](T value) { return static_cast(value); }); }); + } + + [[nodiscard]] tl::expected skipHeader(); + + void skipHeaderOrDie(std::string_view path); + + [[nodiscard]] RecordIterator begin() const + { + return { body_, data() + size(), body_ != data() }; + } + + [[nodiscard]] RecordIterator end() const + { + return {}; + } + + // Assumes a header + [[nodiscard]] size_t numRecords() const; + + [[nodiscard]] const char *data() const + { + return content_.data(); + } + + [[nodiscard]] size_t size() const + { + return content_.size(); + } +}; +} // namespace devilution diff --git a/Source/data/iterators.hpp b/Source/data/iterators.hpp new file mode 100644 index 00000000000..ccaab1c5da4 --- /dev/null +++ b/Source/data/iterators.hpp @@ -0,0 +1,538 @@ +#pragma once + +#include +#include +#include + +#include + +#include "parser.hpp" +#include "utils/parse_int.hpp" +#include "utils/str_cat.hpp" +#include "utils/str_split.hpp" + +namespace devilution { + +class DataFileField { + GetFieldResult *state_; + const char *end_; + unsigned row_; + unsigned column_; + +public: + enum class Error { + NotANumber, + OutOfRange, + InvalidValue + }; + + static tl::expected mapError(std::errc ec) + { + if (ec == std::errc()) + return {}; + switch (ec) { + case std::errc::result_out_of_range: + return tl::unexpected { Error::OutOfRange }; + case std::errc::invalid_argument: + return tl::unexpected { Error::NotANumber }; + default: + return tl::unexpected { Error::InvalidValue }; + } + } + + static tl::expected mapError(ParseIntError ec) + { + switch (ec) { + case ParseIntError::OutOfRange: + return tl::unexpected { Error::OutOfRange }; + case ParseIntError::ParseError: + return tl::unexpected { Error::NotANumber }; + default: + return tl::unexpected { Error::InvalidValue }; + } + } + + DataFileField(GetFieldResult *state, const char *end, unsigned row, unsigned column) + : state_(state) + , end_(end) + , row_(row) + , column_(column) + { + } + + /** + * @brief Returns a view of the current field + * + * This method scans the current field if this is the first value access since the last + * advance. If you expect the field to contain a numeric value then calling parseInt first + * is more efficient, but calling the methods in either order is supported. + * @return The current field value (may be an empty string) or a zero length string_view + */ + [[nodiscard]] std::string_view value() + { + if (state_->status == GetFieldResult::Status::ReadyToRead) { + *state_ = GetNextField(state_->next, end_); + } + return state_->value; + } + + /** + * Convenience function to let DataFileField instances be used like other single-value STL containers + */ + [[nodiscard]] std::string_view operator*() + { + return this->value(); + } + + /** + * @brief Attempts to parse the current field as a numeric value using std::from_chars + * + * You can freely interleave this method with calls to operator*. If this is the first value + * access since the last advance this will scan the current field and store it for later + * use with operator* or repeated calls to parseInt (even with different types). + * @tparam T an Integral type supported by std::from_chars + * @param destination value to store the result of successful parsing + * @return an error code corresponding to the from_chars result if parsing failed + */ + template + [[nodiscard]] tl::expected parseInt(T &destination) + { + std::from_chars_result result {}; + if (state_->status == GetFieldResult::Status::ReadyToRead) { + const char *begin = state_->next; + result = std::from_chars(begin, end_, destination); + if (result.ec != std::errc::invalid_argument) { + // from_chars was able to consume at least one character, consume the rest of the field + *state_ = GetNextField(result.ptr, end_); + // and prepend what was already parsed + state_->value = { begin, (state_->value.data() - begin) + state_->value.size() }; + } + } else { + result = std::from_chars(state_->value.data(), end_, destination); + } + + return mapError(result.ec); + } + + [[nodiscard]] tl::expected parseBool(bool &destination) + { + const std::string_view str = value(); + if (str == "true") { + destination = true; + return {}; + } + if (str == "false") { + destination = false; + return {}; + } + return tl::make_unexpected(DataFileField::Error::InvalidValue); + } + + template + [[nodiscard]] tl::expected parseIntArray(T *destination, size_t n) + { + size_t i = 0; + for (const std::string_view part : SplitByChar(value(), ',')) { + if (i == n) + return tl::make_unexpected(Error::InvalidValue); + const std::from_chars_result result + = std::from_chars(part.data(), part.data() + part.size(), destination[i]); + if (result.ec != std::errc()) + return mapError(result.ec); + ++i; + } + if (i != n) + return tl::make_unexpected(Error::InvalidValue); + return {}; + } + + template + [[nodiscard]] tl::expected parseIntArray(T (&destination)[N]) + { + return parseIntArray(destination, N); + } + + template + [[nodiscard]] tl::expected parseIntArray(std::array &destination) + { + return parseIntArray(destination.data(), N); + } + + template + [[nodiscard]] tl::expected parseEnumArray(T *destination, size_t n, std::optional fillMissing, ParseFn &&parseFn) + { + size_t i = 0; + const std::string_view str = value(); + if (!str.empty()) { + for (const std::string_view part : SplitByChar(str, ',')) { + if (i == n) + return tl::make_unexpected(StrCat("Too many values, max: ", n)); + auto result = parseFn(part); + if (!result.has_value()) { + return tl::make_unexpected(std::move(result).error()); + } + destination[i++] = *result; + } + } + if (i != n) { + if (!fillMissing.has_value()) { + return tl::make_unexpected(StrCat("Too few values, expected ", n, " got ", i)); + } + while (i < n) { + destination[i++] = *fillMissing; + } + } + return {}; + } + + template + [[nodiscard]] tl::expected parseEnumArray(T (&destination)[N], std::optional fillMissing, ParseFn &&parseFn) + { + return parseEnumArray(destination, N, std::move(fillMissing), std::forward(parseFn)); + } + + template + [[nodiscard]] tl::expected parseIntArray(std::array &destination, std::optional fillMissing, ParseFn &&parseFn) + { + return parseEnumArray(destination.data(), N, std::move(fillMissing), std::forward(parseFn)); + } + + template + [[nodiscard]] tl::expected parseEnumList(T &destination, ParseFn &&parseFn) + { + destination = {}; + const std::string_view str = value(); + if (str.empty()) + return {}; + for (const std::string_view part : SplitByChar(str, ',')) { + auto result = parseFn(part); + if (!result.has_value()) + return tl::make_unexpected(std::move(result).error()); + destination |= result.value(); + } + return {}; + } + + template + [[nodiscard]] tl::expected asInt() + { + T value = 0; + return parseInt(value).map([value]() { return value; }); + } + + /** + * @brief Attempts to parse the current field as a fixed point value with 6 bits for the fraction + * + * You can freely interleave this method with calls to operator*. If this is the first value + * access since the last advance this will scan the current field and store it for later + * use with operator* or repeated calls to parseInt/Fixed6 (even with different types). + * @tparam T an Integral type supported by std::from_chars + * @param destination value to store the result of successful parsing + * @return an error code equivalent to what you'd get from from_chars if parsing failed + */ + template + [[nodiscard]] tl::expected parseFixed6(T &destination) + { + ParseIntResult parseResult; + if (state_->status == GetFieldResult::Status::ReadyToRead) { + const char *begin = state_->next; + // first read, consume digits + parseResult = ParseFixed6({ begin, static_cast(end_ - begin) }, &state_->next); + // then read the remainder of the field + *state_ = GetNextField(state_->next, end_); + // and prepend what was already parsed + state_->value = { begin, (state_->value.data() - begin) + state_->value.size() }; + } else { + parseResult = ParseFixed6(state_->value); + } + + if (parseResult.has_value()) { + destination = parseResult.value(); + return {}; + } else { + return mapError(parseResult.error()); + } + } + + template + [[nodiscard]] tl::expected asFixed6() + { + T value = 0; + return parseFixed6(value).map([value]() { return value; }); + } + + /** + * Returns the current row number + */ + [[nodiscard]] unsigned row() const + { + return row_; + } + + /** + * Returns the current column/field number (from the start of the row/record) + */ + [[nodiscard]] unsigned column() const + { + return column_; + } + + /** + * Allows accessing the value of this field in a const context + * + * This requires an actual non-const value access to happen first before it returns + * any useful results, intended for use in error reporting (or test output). + */ + [[nodiscard]] std::string_view currentValue() const + { + return state_->value; + } +}; + +/** + * @brief Show the field value along with the row/column number (mainly used in test failure messages) + * @param stream output stream, expected to have overloads for unsigned, std::string_view, and char* + * @param field Object to display + * @return the stream, to allow chaining + */ +inline std::ostream &operator<<(std::ostream &stream, const DataFileField &field) +{ + return stream << "\"" << field.currentValue() << "\" (at row " << field.row() << ", column " << field.column() << ")"; +} + +class FieldIterator { + GetFieldResult *state_; + const char *const end_; + const unsigned row_; + unsigned column_ = 0; + +public: + using iterator_category = std::input_iterator_tag; + using value_type = DataFileField; + + FieldIterator() + : state_(nullptr) + , end_(nullptr) + , row_(0) + { + } + + FieldIterator(GetFieldResult *state, const char *end, unsigned row) + : state_(state) + , end_(end) + , row_(row) + { + state_->status = GetFieldResult::Status::ReadyToRead; + } + + [[nodiscard]] bool operator==(const FieldIterator &rhs) const + { + if (state_ == nullptr && rhs.state_ == nullptr) + return true; + + return state_ != nullptr && rhs.state_ != nullptr && state_->next == rhs.state_->next; + } + + [[nodiscard]] bool operator!=(const FieldIterator &rhs) const + { + return !(*this == rhs); + } + + /** + * Advances to the next field in the current record + */ + FieldIterator &operator++() + { + return *this += 1; + } + + /** + * @brief Advances by the specified number of fields + * + * if a non-zero increment is provided and advancing the iterator causes it to reach the end + * of the record the iterator is invalidated. It will compare equal to an end iterator and + * cannot be used for value access or any further parsing + * @param increment how many fields to advance (can be 0) + * @return self-reference + */ + FieldIterator &operator+=(unsigned increment) + { + if (increment == 0) + return *this; + + if (state_->status == GetFieldResult::Status::ReadyToRead) { + // We never read the value and no longer need it, discard it so that we end up + // advancing past the field delimiter (as if a value access had happened) + *state_ = DiscardField(state_->next, end_); + } + + if (state_->endOfRecord()) { + state_ = nullptr; + } else { + unsigned fieldsSkipped = 0; + // By this point we've already advanced past the end of this field (either because the + // last value access found the end of the field by necessity or we discarded it a few + // lines up), so we only need to advance further if an increment greater than 1 was + // provided. + *state_ = DiscardMultipleFields(state_->next, end_, increment - 1, &fieldsSkipped); + // As we've consumed the current field by this point we need to increment the internal + // column counter one extra time so we have an accurate value. + column_ += fieldsSkipped + 1; + // We use Status::ReadyToRead as a marker so we only read the next value on the next + // value access, this allows consumers to choose the most efficient method (e.g. if + // they want the value as an int) or even repeated advances without using a value. + state_->status = GetFieldResult::Status::ReadyToRead; + } + return *this; + } + + /** + * @brief Returns a view of the current field + * + * The returned value is a thin wrapper over the current state of this iterator (or last + * successful read if incrementing this iterator would result in it reaching the end state). + */ + [[nodiscard]] value_type operator*() + { + return { state_, end_, row_, column_ }; + } + + /** + * @brief Returns the current row number + */ + [[nodiscard]] unsigned row() const + { + return row_; + } + /** + * @brief Returns the current column/field number (from the start of the row/record) + */ + [[nodiscard]] unsigned column() const + { + return column_; + } +}; + +class DataFileRecord { + GetFieldResult *state_; + const char *const end_; + const unsigned row_; + +public: + DataFileRecord(GetFieldResult *state, const char *end, unsigned row) + : state_(state) + , end_(end) + , row_(row) + { + } + + [[nodiscard]] FieldIterator begin() + { + return { state_, end_, row_ }; + } + + [[nodiscard]] FieldIterator end() const + { + return {}; + } + + [[nodiscard]] unsigned row() const + { + return row_; + } +}; + +class RecordIterator { + GetFieldResult state_; + const char *const end_; + unsigned row_ = 0; + +public: + using iterator_category = std::forward_iterator_tag; + using value_type = DataFileRecord; + + RecordIterator() + : state_(nullptr, GetFieldResult::Status::EndOfFile) + , end_(nullptr) + { + } + + RecordIterator(const char *begin, const char *end, bool skippedHeader) + : state_(begin) + , end_(end) + , row_(skippedHeader ? 1 : 0) + { + } + + [[nodiscard]] bool operator==(const RecordIterator &rhs) const + { + return state_.next == rhs.state_.next; + } + + [[nodiscard]] bool operator!=(const RecordIterator &rhs) const + { + return !(*this == rhs); + } + + RecordIterator &operator++() + { + return *this += 1; + } + + RecordIterator &operator+=(unsigned increment) + { + if (increment == 0) + return *this; + + if (!state_.endOfRecord()) { + // The field iterator either hasn't been used or hasn't consumed the entire record + state_ = DiscardRemainingFields(state_.next, end_); + } + + if (state_.endOfFile()) { + state_.next = nullptr; + } else { + unsigned recordsSkipped = 0; + // By this point we've already advanced past the end of this record (either because the + // last value access found the end of the record by necessity or we discarded any + // leftovers a few lines up), so we only need to advance further if an increment + // greater than 1 was provided. + state_ = DiscardMultipleRecords(state_.next, end_, increment - 1, &recordsSkipped); + // As we've consumed the current record by this point we need to increment the internal + // row counter one extra time so we have an accurate value. + row_ += recordsSkipped + 1; + // We use Status::ReadyToRead as a marker in case the DataFileField iterator is never + // used, so the next call to operator+= will advance past the current record + state_.status = GetFieldResult::Status::ReadyToRead; + } + return *this; + } + + [[nodiscard]] DataFileRecord operator*() + { + return { &state_, end_, row_ }; + } + + /** + * @brief Exposes the current location of this input iterator. + * + * This is only expected to be used internally so the DataFile instance knows where the header + * ends and the body begins. You probably don't want to use this directly. + */ + [[nodiscard]] const char *data() const + { + return state_.next; + } + + /** + * @brief Returns the current row/record number (from the start of the file) + * + * The header row is always considered row 0, however if you've called DataFile.parseHeader() + * before calling DataFile.begin() then you'll get row 1 as the first record of the range. + */ + [[nodiscard]] unsigned row() const + { + return row_; + } +}; +} // namespace devilution diff --git a/Source/data/parser.cpp b/Source/data/parser.cpp new file mode 100644 index 00000000000..1ad3ad69eaa --- /dev/null +++ b/Source/data/parser.cpp @@ -0,0 +1,64 @@ +#include "parser.hpp" + +namespace devilution { +GetFieldResult HandleRecordTerminator(const char *begin, const char *end) +{ + if (begin == end) { + return { end, GetFieldResult::Status::NoFinalTerminator }; + } + + if (*begin == '\r') { + ++begin; + if (begin == end) { + return { end, GetFieldResult::Status::FileTruncated }; + } + // carriage returns should be followed by a newline, so let's let the following checks handle it + } + if (*begin == '\n') { + ++begin; + if (begin == end) { + return { end, GetFieldResult::Status::EndOfFile }; + } + + return { begin, GetFieldResult::Status::EndOfRecord }; + } + + return { begin, GetFieldResult::Status::BadRecordTerminator }; +} + +GetFieldResult DiscardMultipleFields(const char *begin, const char *end, unsigned skipLength, unsigned *fieldsSkipped) +{ + GetFieldResult result { begin }; + unsigned skipCount = 0; + while (skipCount < skipLength) { + ++skipCount; + result = DiscardField(result.next, end); + if (result.endOfRecord()) { + // Found the end of record early + break; + } + } + if (fieldsSkipped != nullptr) { + *fieldsSkipped = skipCount; + } + return result; +} + +GetFieldResult DiscardMultipleRecords(const char *begin, const char *end, unsigned skipLength, unsigned *recordsSkipped) +{ + GetFieldResult result { begin }; + unsigned skipCount = 0; + while (skipCount < skipLength) { + ++skipCount; + result = DiscardRemainingFields(result.next, end); + if (result.endOfFile()) { + // Found the end of file early + break; + } + } + if (recordsSkipped != nullptr) { + *recordsSkipped = skipCount; + } + return result; +} +} // namespace devilution diff --git a/Source/data/parser.hpp b/Source/data/parser.hpp new file mode 100644 index 00000000000..90ce4d9ef6e --- /dev/null +++ b/Source/data/parser.hpp @@ -0,0 +1,206 @@ +#pragma once + +#include +#include + +#include "engine.h" // For IsAnyOf + +namespace devilution { + +struct GetFieldResult { + std::string_view value; + + const char *next = nullptr; + + enum class Status { + ReadyToRead, + EndOfField, + EndOfRecord, + BadRecordTerminator, + EndOfFile, + FileTruncated, + NoFinalTerminator + } status + = Status::ReadyToRead; + + GetFieldResult() = default; + + GetFieldResult(const char *next) + : value() + , next(next) + , status(Status::ReadyToRead) + { + } + + GetFieldResult(const char *next, const Status &status) + : value() + , next(next) + , status(status) + { + } + + /** + * @brief Recreates a GetFieldResult with a new value + */ + GetFieldResult(std::string_view value, const GetFieldResult &result) + : value(value) + , next(result.next) + , status(result.status) + { + } + + /** + * @brief Returns true if the last read reached the end of the current record + */ + [[nodiscard]] bool endOfRecord() const + { + return IsAnyOf(status, Status::EndOfRecord, Status::BadRecordTerminator) || endOfFile(); + } + + /** + * @brief Returns true if the last read reached the end of the file/stream + */ + [[nodiscard]] bool endOfFile() const + { + return IsAnyOf(status, Status::EndOfFile, Status::FileTruncated, Status::NoFinalTerminator); + } +}; + +/** + * @brief Checks if this character is potentially part of a record terminator sequence + * @param c character to check + * @return true if it's a record terminator (lf) or carriage return (cr, accepted as the start of a crlf pair) + */ +constexpr bool IsRecordTerminator(char c) +{ + return c == '\r' || c == '\n'; +} + +/** + * @brief Checks if this character is a field separator + * + * Note that record terminator sequences also act to separate fields + * @param c character to check + * @return true if it's a field separator (tab) or part of a record terminator sequence + */ +constexpr bool IsFieldSeparator(char c) +{ + return c == '\t' || IsRecordTerminator(c); +} + +/** + * @brief Consumes the current record terminator sequence and returns a result describing whether at least one more record is available. + * + * Assumes that begin points to a record terminator (lf or crlf) or the last read reached the end + * of the final record (in which case the terminator is optional). If we reached the end of the + * stream (`begin == end`) then `status` will compare equal to Status::NoFinalTerminator. If we + * found a carriage return (cr) character just before the end of the stream then it's likely the + * file was truncated, `status` will contain Status::FileTruncated. If we found a carriage return + * that is followed by any character other than a newline (lf), or `begin` didn't point to a record + * terminator, then `status` will be Status::BadRecordTerminator and the file has probably been + * mangled. Otherwise `status` will be Status::EndOfRecord at least one more record is available or + * Status::EndOfFile if this was the end of the last record. + * @param begin start of a stream (expected to be pointing to a record terminator) + * @param end one past the last character of the stream + * @return a struct containing a pointer to the start of the next record (if more characters are + * available) or a copy of end, and a status code describing the terminator. + */ +GetFieldResult HandleRecordTerminator(const char *begin, const char *end); + +/** + * @brief Consumes the current field (or record) separator and returns a result describing whether at least one more field is available. + * + * Assumes that begin points to a field or record separator (tab, cr, lf, or EOF). If there are + * more fields in the current record then the return value will have a pointer to the start of the + * next field and `status` will be GetFieldResult::Status::EndOfField. Otherwise refer to + * HandleRecordTerminator for a description of the different codes. + * @param begin start of a stream (expected to be pointing to a record separator) + * @param end one past the last character of the stream + * @return a struct containing a pointer to the start of the next field (if more characters are + * available) or a copy of end, and optionally an error code describing what type of + * separator was found. + */ +inline GetFieldResult HandleFieldSeparator(const char *begin, const char *end) +{ + if (begin != end && *begin == '\t') { + return { begin + 1, GetFieldResult::Status::EndOfField }; + } + + return HandleRecordTerminator(begin, end); +} + +/** + * @brief Advances to the next field separator without saving any characters + * @param begin first character of the stream + * @param end one past the last character in the stream + * @return a GetFieldResult struct containing an empty value, a pointer to the start of the next + * field/record, and a status code describing what type of separator was found + */ +inline GetFieldResult DiscardField(const char *begin, const char *end) +{ + const char *nextSeparator = std::find_if(begin, end, IsFieldSeparator); + + return HandleFieldSeparator(nextSeparator, end); +} + +/** + * @brief Advances by the specified number of fields or until the end of the record, whichever occurs first + * @param begin first character of the stream + * @param end one past the last character in the stream + * @param skipLength how many fields to skip (specifying 0 will cause the method to return without advancing) + * @param fieldsSkipped optional output parameter, will be filled with a count of how many fields + * were skipped in case the end of record was reached early + * @return a GetFieldResult struct containing an empty value, a pointer to the start of the next + * field/record, and a status code describing what type of separator was found + */ +GetFieldResult DiscardMultipleFields(const char *begin, const char *end, unsigned skipLength, unsigned *fieldsSkipped = nullptr); + +/** + * @brief Advances by the specified number of records or until the end of the file, whichever occurs first + * @param begin first character of the stream + * @param end one past the last character in the stream + * @param skipLength how many records to skip (specifying 0 will cause the method to return without advancing) + * @param recordsSkipped optional output parameter, will be filled with a count of how many records + * were skipped in case the end of file was reached early + * @return a GetFieldResult struct containing an empty value, a pointer to the start of the next + * record, and a status code describing what type of separator was found + */ +GetFieldResult DiscardMultipleRecords(const char *begin, const char *end, unsigned skipLength, unsigned *recordsSkipped = nullptr); + +/** + * @brief Discard any remaining fields in the current record + * @param begin pointer to the current character in the stream + * @param end one past the last character in the stream + * @return a GetFieldResult struct containing an empty value, the start of the next record (or + * `end`), and a status describing whether more records are available + */ +inline GetFieldResult DiscardRemainingFields(const char *begin, const char *end) +{ + const char *nextSeparator = std::find_if(begin, end, IsRecordTerminator); + + return HandleRecordTerminator(nextSeparator, end); +} + +/** + * @brief Returns a view of the next field from a tab-delimited stream. + * + * Note that the result *always* contains a value after calling this function as a zero-length + * field is a valid value. This function consumes the field separator whenever possible, the + * `next` member of the returned type will be either the start of the next field/record or `end`. + * The `status` member contains additional information to distinguish between the end of a field + * and the end of a record. If there are additional fields in this record then `status` will be + * GetFieldResult::Status::EndOfField, otherwise refer to HandleRecordTerminator for the meanings + * associated with the remaining codes. + * @param begin first character of the stream + * @param end one past the last character in the stream + * @return a GetFieldResult struct containing a string_view of the field, the start of the next + * field/record, and a status code describing what type of separator was found + */ +inline GetFieldResult GetNextField(const char *begin, const char *end) +{ + const char *nextSeparator = std::find_if(begin, end, IsFieldSeparator); + + // Can't use the string_view(It, It) constructor since that was only added in C++20... + return { { begin, static_cast(nextSeparator - begin) }, HandleFieldSeparator(nextSeparator, end) }; +} +} // namespace devilution diff --git a/Source/data/record_reader.cpp b/Source/data/record_reader.cpp new file mode 100644 index 00000000000..6b6ef696058 --- /dev/null +++ b/Source/data/record_reader.cpp @@ -0,0 +1,17 @@ +#include "data/record_reader.hpp" + +namespace devilution { + +void RecordReader::advance() +{ + if (needsIncrement_) { + ++it_; + } else { + needsIncrement_ = true; + } + if (it_ == end_) { + DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename_); + } +} + +} // namespace devilution diff --git a/Source/data/record_reader.hpp b/Source/data/record_reader.hpp new file mode 100644 index 00000000000..df3795401cf --- /dev/null +++ b/Source/data/record_reader.hpp @@ -0,0 +1,140 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "data/file.hpp" +#include "data/iterators.hpp" + +namespace devilution { + +/** + * @brief A record reader that treats every error as fatal. + */ +class RecordReader { +public: + RecordReader(DataFileRecord &record, std::string_view filename) + : it_(record.begin()) + , end_(record.end()) + , filename_(filename) + { + } + + template + typename std::enable_if_t, void> + readInt(std::string_view name, T &out) + { + DataFileField field = nextField(); + failOnError(field.parseInt(out), name, field); + } + + template + typename std::enable_if_t, void> + readOptionalInt(std::string_view name, T &out) + { + DataFileField field = nextField(); + if (field.value().empty()) return; + failOnError(field.parseInt(out), name, field); + } + + template + void readIntArray(std::string_view name, T (&out)[N]) + { + DataFileField field = nextField(); + failOnError(field.parseIntArray(out), name, field); + } + + template + void readEnumArray(std::string_view name, std::optional fillMissing, T (&out)[N], F &&parseFn) + { + DataFileField field = nextField(); + failOnError(field.parseEnumArray(out, fillMissing, parseFn), name, field, DataFileField::Error::InvalidValue); + } + + template + void readIntArray(std::string_view name, std::array &out) + { + DataFileField field = nextField(); + failOnError(field.parseIntArray(out), name, field); + } + + template + typename std::enable_if_t, void> + readFixed6(std::string_view name, T &out) + { + DataFileField field = nextField(); + failOnError(field.parseFixed6(out), name, field); + } + + void readBool(std::string_view name, bool &out) + { + DataFileField field = nextField(); + failOnError(field.parseBool(out), name, field); + } + + void readString(std::string_view name, std::string &out) + { + advance(); + out = (*it_).value(); + } + + template + void read(std::string_view name, T &out, F &&parseFn) + { + DataFileField field = nextField(); + tl::expected result = parseFn(field.value()); + failOnError(result, name, field, DataFileField::Error::InvalidValue); + out = *std::move(result); + } + + template + void readEnumList(std::string_view name, T &out, F &&parseFn) + { + DataFileField field = nextField(); + failOnError(field.parseEnumList(out, std::forward(parseFn)), + name, field, DataFileField::Error::InvalidValue); + } + + std::string_view value() + { + advance(); + needsIncrement_ = false; + return (*it_).value(); + } + + void advance(); + + DataFileField nextField() + { + advance(); + return *it_; + } + +private: + template + void failOnError(const tl::expected &result, std::string_view name, const DataFileField &field) + { + if (!result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename_, name, field); + } + } + + template + void failOnError(const tl::expected &result, std::string_view name, const DataFileField &field, DataFileField::Error error) + { + if (!result.has_value()) { + DataFile::reportFatalFieldError(error, filename_, name, field, result.error()); + } + } + + FieldIterator it_; + const FieldIterator end_; + std::string_view filename_; + bool needsIncrement_ = false; +}; + +} // namespace devilution diff --git a/Source/debug.cpp b/Source/debug.cpp index 189d82f32f4..1252f8b8059 100644 --- a/Source/debug.cpp +++ b/Source/debug.cpp @@ -6,35 +6,21 @@ #ifdef _DEBUG +#include #include #include #include "debug.h" #include "automap.h" -#include "control.h" #include "cursor.h" -#include "engine/backbuffer_state.hpp" -#include "engine/events.hpp" #include "engine/load_cel.hpp" #include "engine/point.hpp" -#include "error.h" -#include "inv.h" -#include "levels/setmaps.h" #include "lighting.h" -#include "monstdat.h" #include "monster.h" #include "plrmsg.h" -#include "quests.h" -#include "spells.h" -#include "towners.h" -#include "utils/endian_stream.hpp" -#include "utils/file_util.h" -#include "utils/language.h" -#include "utils/log.hpp" #include "utils/str_case.hpp" #include "utils/str_cat.hpp" -#include "utils/str_split.hpp" namespace devilution { @@ -57,36 +43,6 @@ uint32_t glEndSeed[NUMLEVELS]; namespace { -enum class DebugGridTextItem : uint16_t { - None, - dPiece, - dTransVal, - dLight, - dPreLight, - dFlags, - dPlayer, - dMonster, - dCorpse, - dObject, - dItem, - dSpecial, - - coords, - cursorcoords, - objectindex, - - // take dPiece as index - Solid, - Transparent, - Trap, - - // megatiles - AutomapView, - dungeon, - pdungeon, - Protected, -}; - DebugGridTextItem SelectedDebugGridTextItem; int DebugMonsterId; @@ -116,969 +72,6 @@ void PrintDebugMonster(const Monster &monster) EventPlrMsg(StrCat("Active List = ", bActive ? 1 : 0, ", Squelch = ", monster.activeForTicks), UiFlags::ColorWhite); } -struct DebugCmdItem { - const string_view text; - const string_view description; - const string_view requiredParameter; - std::string (*actionProc)(const string_view); -}; - -extern std::vector DebugCmdList; - -std::string DebugCmdHelp(const string_view parameter) -{ - if (parameter.empty()) { - std::string ret = "Available Debug Commands: "; - bool first = true; - for (const auto &dbgCmd : DebugCmdList) { - if (first) - first = false; - else - ret.append(" - "); - ret.append(std::string(dbgCmd.text)); - } - return ret; - } - auto debugCmdIterator = std::find_if(DebugCmdList.begin(), DebugCmdList.end(), [&](const DebugCmdItem &elem) { return elem.text == parameter; }); - if (debugCmdIterator == DebugCmdList.end()) - return StrCat("Debug command ", parameter, " wasn't found"); - auto &dbgCmdItem = *debugCmdIterator; - if (dbgCmdItem.requiredParameter.empty()) - return StrCat("Description: ", dbgCmdItem.description, "\nParameters: No additional parameter needed."); - return StrCat("Description: ", dbgCmdItem.description, "\nParameters: ", dbgCmdItem.requiredParameter); -} - -std::string DebugCmdGiveGoldCheat(const string_view parameter) -{ - Player &myPlayer = *MyPlayer; - - for (int8_t &itemIndex : myPlayer.InvGrid) { - if (itemIndex != 0) - continue; - - Item &goldItem = myPlayer.InvList[myPlayer._pNumInv]; - MakeGoldStack(goldItem, GOLD_MAX_LIMIT); - myPlayer._pNumInv++; - itemIndex = myPlayer._pNumInv; - - myPlayer._pGold += goldItem._ivalue; - } - CalcPlrInv(myPlayer, true); - - return "You are now rich! If only this was as easy in real life..."; -} - -std::string DebugCmdTakeGoldCheat(const string_view parameter) -{ - Player &myPlayer = *MyPlayer; - - for (auto itemIndex : myPlayer.InvGrid) { - itemIndex -= 1; - - if (itemIndex < 0) - continue; - if (myPlayer.InvList[itemIndex]._itype != ItemType::Gold) - continue; - - myPlayer.RemoveInvItem(itemIndex); - } - - myPlayer._pGold = 0; - - return "You are poor..."; -} - -std::string DebugCmdWarpToLevel(const string_view parameter) -{ - Player &myPlayer = *MyPlayer; - auto level = atoi(parameter.data()); - if (level < 0 || level > (gbIsHellfire ? 24 : 16)) - return StrCat("Level ", level, " is not known. Do you want to write a mod?"); - if (!setlevel && myPlayer.isOnLevel(level)) - return StrCat("I did nothing but fulfilled your wish. You are already at level ", level, "."); - - StartNewLvl(myPlayer, (level != 21) ? interface_mode::WM_DIABNEXTLVL : interface_mode::WM_DIABTOWNWARP, level); - return StrCat("Welcome to level ", level, "."); -} - -std::string DebugCmdLoadQuestMap(const string_view parameter) -{ - if (parameter.empty()) { - std::string ret = "What mapid do you want to visit?"; - for (auto &quest : Quests) { - if (quest._qslvl <= 0) - continue; - StrAppend(ret, " ", quest._qslvl, " (", QuestLevelNames[quest._qslvl], ")"); - } - return ret; - } - - auto level = atoi(parameter.data()); - if (level < 1) - return "Map id must be 1 or higher"; - if (setlevel && setlvlnum == level) - return StrCat("I did nothing but fulfilled your wish. You are already at mapid .", level); - - for (auto &quest : Quests) { - if (level != quest._qslvl) - continue; - - setlvltype = quest._qlvltype; - StartNewLvl(*MyPlayer, WM_DIABSETLVL, level); - - return StrCat("Welcome to ", QuestLevelNames[level], "."); - } - - return StrCat("Mapid ", level, " is not known. Do you want to write a mod?"); -} - -std::string DebugCmdLoadMap(const string_view parameter) -{ - TestMapPath.clear(); - int mapType = 0; - Point spawn = {}; - - int count = 0; - for (string_view arg : SplitByChar(parameter, ' ')) { - switch (count) { - case 0: - TestMapPath = StrCat(arg, ".dun"); - break; - case 1: - mapType = atoi(std::string(arg).c_str()); - break; - case 2: - spawn.x = atoi(std::string(arg).c_str()); - break; - case 3: - spawn.y = atoi(std::string(arg).c_str()); - break; - } - count++; - } - - if (TestMapPath.empty() || mapType < DTYPE_CATHEDRAL || mapType > DTYPE_LAST || !InDungeonBounds(spawn)) - return "Directions not understood"; - - setlvltype = static_cast(mapType); - ViewPosition = spawn; - - StartNewLvl(*MyPlayer, WM_DIABSETLVL, SL_NONE); - - return "Welcome to this unique place."; -} - -std::string ExportDun(const string_view parameter) -{ - std::string levelName = StrCat(currlevel, "-", glSeedTbl[currlevel], ".dun"); - - FILE *dunFile = OpenFile(levelName.c_str(), "ab"); - - WriteLE16(dunFile, DMAXX); - WriteLE16(dunFile, DMAXY); - - /** Tiles. */ - for (int y = 0; y < DMAXY; y++) { - for (int x = 0; x < DMAXX; x++) { - WriteLE16(dunFile, dungeon[x][y]); - } - } - - /** Padding */ - for (int y = 16; y < MAXDUNY - 16; y++) { - for (int x = 16; x < MAXDUNX - 16; x++) { - WriteLE16(dunFile, 0); - } - } - - /** Monsters */ - for (int y = 16; y < MAXDUNY - 16; y++) { - for (int x = 16; x < MAXDUNX - 16; x++) { - uint16_t monsterId = 0; - if (dMonster[x][y] > 0) { - for (int i = 0; i < 157; i++) { - if (MonstConvTbl[i] == Monsters[abs(dMonster[x][y]) - 1].type().type) { - monsterId = i + 1; - break; - } - } - } - WriteLE16(dunFile, monsterId); - } - } - - /** Objects */ - for (int y = 16; y < MAXDUNY - 16; y++) { - for (int x = 16; x < MAXDUNX - 16; x++) { - uint16_t objectId = 0; - Object *object = FindObjectAtPosition({ x, y }, false); - if (object != nullptr) { - for (int i = 0; i < 147; i++) { - if (ObjTypeConv[i] == object->_otype) { - objectId = i; - break; - } - } - } - WriteLE16(dunFile, objectId); - } - } - - /** Transparency */ - for (int y = 16; y < MAXDUNY - 16; y++) { - for (int x = 16; x < MAXDUNX - 16; x++) { - WriteLE16(dunFile, dTransVal[x][y]); - } - } - std::fclose(dunFile); - - return StrCat(levelName, " saved. Happy mapping!"); -} - -std::unordered_map TownerShortNameToTownerId = { - { "griswold", _talker_id::TOWN_SMITH }, - { "pepin", _talker_id::TOWN_HEALER }, - { "ogden", _talker_id::TOWN_TAVERN }, - { "cain", _talker_id::TOWN_STORY }, - { "farnham", _talker_id::TOWN_DRUNK }, - { "adria", _talker_id::TOWN_WITCH }, - { "gillian", _talker_id::TOWN_BMAID }, - { "wirt", _talker_id ::TOWN_PEGBOY }, - { "lester", _talker_id ::TOWN_FARMER }, - { "girl", _talker_id ::TOWN_GIRL }, - { "nut", _talker_id::TOWN_COWFARM }, -}; - -std::string DebugCmdVisitTowner(const string_view parameter) -{ - Player &myPlayer = *MyPlayer; - - if (setlevel || !myPlayer.isOnLevel(0)) - return "What kind of friends do you have in dungeons?"; - - if (parameter.empty()) { - std::string ret; - ret = "Who? "; - for (auto &entry : TownerShortNameToTownerId) { - ret.append(" "); - ret.append(std::string(entry.first)); - } - return ret; - } - - auto it = TownerShortNameToTownerId.find(parameter); - if (it == TownerShortNameToTownerId.end()) - return StrCat(parameter, " is unknown. Perhaps he is a ninja?"); - - for (auto &towner : Towners) { - if (towner._ttype != it->second) - continue; - - CastSpell( - MyPlayerId, - SpellID::Teleport, - myPlayer.position.tile.x, - myPlayer.position.tile.y, - towner.position.x, - towner.position.y, - 1); - - return StrCat("Say hello to ", parameter, " from me."); - } - - return StrCat("Couldn't find ", parameter, "."); -} - -std::string DebugCmdResetLevel(const string_view parameter) -{ - Player &myPlayer = *MyPlayer; - - auto args = SplitByChar(parameter, ' '); - auto it = args.begin(); - if (it == args.end()) - return "What level do you want to visit?"; - auto level = atoi(std::string(*it).c_str()); - if (level < 0 || level > (gbIsHellfire ? 24 : 16)) - return StrCat("Level ", level, " is not known. Do you want to write an extension mod?"); - myPlayer._pLvlVisited[level] = false; - DeltaClearLevel(level); - - if (++it != args.end()) { - const auto seed = static_cast(std::stoul(std::string(*it))); - glSeedTbl[level] = seed; - } - - if (myPlayer.isOnLevel(level)) - return StrCat("Level ", level, " can't be cleaned, cause you still occupy it!"); - return StrCat("Level ", level, " was restored and looks fabulous."); -} - -std::string DebugCmdGodMode(const string_view parameter) -{ - DebugGodMode = !DebugGodMode; - if (DebugGodMode) - return "A god descended."; - return "You are mortal, beware of the darkness."; -} - -std::string DebugCmdLighting(const string_view parameter) -{ - ToggleLighting(); - - return "All raindrops are the same."; -} - -std::string DebugCmdMapReveal(const string_view parameter) -{ - for (int x = 0; x < DMAXX; x++) - for (int y = 0; y < DMAXY; y++) - UpdateAutomapExplorer({ x, y }, MAP_EXP_SHRINE); - - return "The way is made clear when viewed from above"; -} - -std::string DebugCmdMapHide(const string_view parameter) -{ - for (int x = 0; x < DMAXX; x++) - for (int y = 0; y < DMAXY; y++) - AutomapView[x][y] = MAP_EXP_NONE; - - return "The way is made unclear when viewed from below"; -} - -std::string DebugCmdVision(const string_view parameter) -{ - DebugVision = !DebugVision; - if (DebugVision) - return "You see as I do."; - - return "My path is set."; -} - -std::string DebugCmdPath(const string_view parameter) -{ - DebugPath = !DebugPath; - if (DebugPath) - return "The mushroom trail."; - - return "The path is hidden."; -} - -std::string DebugCmdQuest(const string_view parameter) -{ - if (parameter.empty()) { - std::string ret = "You must provide an id. This could be: all"; - for (auto &quest : Quests) { - if (IsNoneOf(quest._qactive, QUEST_NOTAVAIL, QUEST_INIT)) - continue; - StrAppend(ret, ", ", quest._qidx, " (", QuestsData[quest._qidx]._qlstr, ")"); - } - return ret; - } - - if (parameter.compare("all") == 0) { - for (auto &quest : Quests) { - if (IsNoneOf(quest._qactive, QUEST_NOTAVAIL, QUEST_INIT)) - continue; - - quest._qactive = QUEST_ACTIVE; - quest._qlog = true; - } - - return "Happy questing"; - } - - int questId = atoi(parameter.data()); - - if (questId >= MAXQUESTS) - return StrCat("Quest ", questId, " is not known. Do you want to write a mod?"); - auto &quest = Quests[questId]; - - if (IsNoneOf(quest._qactive, QUEST_NOTAVAIL, QUEST_INIT)) - return StrCat(QuestsData[questId]._qlstr, " was already given."); - - quest._qactive = QUEST_ACTIVE; - quest._qlog = true; - - return StrCat(QuestsData[questId]._qlstr, " enabled."); -} - -std::string DebugCmdLevelUp(const string_view parameter) -{ - int levels = std::max(1, atoi(parameter.data())); - for (int i = 0; i < levels; i++) - NetSendCmd(true, CMD_CHEAT_EXPERIENCE); - return "New experience leads to new insights."; -} - -std::string DebugCmdMaxStats(const string_view parameter) -{ - Player &myPlayer = *MyPlayer; - ModifyPlrStr(myPlayer, myPlayer.GetMaximumAttributeValue(CharacterAttribute::Strength) - myPlayer._pBaseStr); - ModifyPlrMag(myPlayer, myPlayer.GetMaximumAttributeValue(CharacterAttribute::Magic) - myPlayer._pBaseMag); - ModifyPlrDex(myPlayer, myPlayer.GetMaximumAttributeValue(CharacterAttribute::Dexterity) - myPlayer._pBaseDex); - ModifyPlrVit(myPlayer, myPlayer.GetMaximumAttributeValue(CharacterAttribute::Vitality) - myPlayer._pBaseVit); - return "Who needs elixirs anyway?"; -} - -std::string DebugCmdMinStats(const string_view parameter) -{ - Player &myPlayer = *MyPlayer; - ModifyPlrStr(myPlayer, -myPlayer._pBaseStr); - ModifyPlrMag(myPlayer, -myPlayer._pBaseMag); - ModifyPlrDex(myPlayer, -myPlayer._pBaseDex); - ModifyPlrVit(myPlayer, -myPlayer._pBaseVit); - return "From hero to zero."; -} - -std::string DebugCmdSetSpellsLevel(const string_view parameter) -{ - uint8_t level = static_cast(std::max(0, atoi(parameter.data()))); - for (uint8_t i = static_cast(SpellID::Firebolt); i < MAX_SPELLS; i++) { - if (GetSpellBookLevel(static_cast(i)) != -1) { - NetSendCmdParam2(true, CMD_CHANGE_SPELL_LEVEL, i, level); - } - } - if (level == 0) - MyPlayer->_pMemSpells = 0; - - return "Knowledge is power."; -} - -std::string DebugCmdRefillHealthMana(const string_view parameter) -{ - Player &myPlayer = *MyPlayer; - myPlayer.RestoreFullLife(); - myPlayer.RestoreFullMana(); - RedrawComponent(PanelDrawComponent::Health); - RedrawComponent(PanelDrawComponent::Mana); - - return "Ready for more."; -} - -std::string DebugCmdChangeHealth(const string_view parameter) -{ - Player &myPlayer = *MyPlayer; - int change = -1; - - if (!parameter.empty()) - change = atoi(parameter.data()); - - if (change == 0) - return "Health hasn't changed."; - - int newHealth = myPlayer._pHitPoints + (change * 64); - SetPlayerHitPoints(myPlayer, newHealth); - if (newHealth <= 0) - SyncPlrKill(myPlayer, DeathReason::MonsterOrTrap); - - return "Health has changed."; -} - -std::string DebugCmdChangeMana(const string_view parameter) -{ - Player &myPlayer = *MyPlayer; - int change = -1; - - if (!parameter.empty()) - change = atoi(parameter.data()); - - if (change == 0) - return "Mana hasn't changed."; - - int newMana = myPlayer._pMana + (change * 64); - myPlayer._pMana = newMana; - myPlayer._pManaBase = myPlayer._pMana + myPlayer._pMaxManaBase - myPlayer._pMaxMana; - RedrawComponent(PanelDrawComponent::Mana); - - return "Mana has changed."; -} - -std::string DebugCmdGenerateUniqueItem(const string_view parameter) -{ - return DebugSpawnUniqueItem(parameter.data()); -} - -std::string DebugCmdGenerateItem(const string_view parameter) -{ - return DebugSpawnItem(parameter.data()); -} - -std::string DebugCmdExit(const string_view parameter) -{ - gbRunGame = false; - gbRunGameResult = false; - return "See you again my Lord."; -} - -std::string DebugCmdArrow(const string_view parameter) -{ - Player &myPlayer = *MyPlayer; - - myPlayer._pIFlags &= ~ItemSpecialEffect::FireArrows; - myPlayer._pIFlags &= ~ItemSpecialEffect::LightningArrows; - - if (parameter == "normal") { - // we removed the parameter at the top - } else if (parameter == "fire") { - myPlayer._pIFlags |= ItemSpecialEffect::FireArrows; - } else if (parameter == "lightning") { - myPlayer._pIFlags |= ItemSpecialEffect::LightningArrows; - } else if (parameter == "explosion") { - myPlayer._pIFlags |= (ItemSpecialEffect::FireArrows | ItemSpecialEffect::LightningArrows); - } else { - return "Unknown is sometimes similar to nothing (unkown effect)."; - } - - return "I can shoot any arrow."; -} - -std::string DebugCmdTalkToTowner(const string_view parameter) -{ - if (DebugTalkToTowner(parameter.data())) { - return "Hello from the other side."; - } - return "NPC not found."; -} - -std::string DebugCmdShowGrid(const string_view parameter) -{ - DebugGrid = !DebugGrid; - if (DebugGrid) - return "A basket full of rectangles and mushrooms."; - - return "Back to boring."; -} - -std::string DebugCmdSpawnUniqueMonster(const string_view parameter) -{ - if (leveltype == DTYPE_TOWN) - return "Do you want to kill the towners?!?"; - - std::string name; - int count = 1; - for (string_view arg : SplitByChar(parameter, ' ')) { - const int num = atoi(std::string(arg).c_str()); - if (num > 0) { - count = num; - break; - } - AppendStrView(name, arg); - name += ' '; - } - if (name.empty()) - return "Monster name cannot be empty. Duh."; - - name.pop_back(); // remove last space - AsciiStrToLower(name); - - int mtype = -1; - UniqueMonsterType uniqueIndex = UniqueMonsterType::None; - for (size_t i = 0; UniqueMonstersData[i].mtype != MT_INVALID; i++) { - auto mondata = UniqueMonstersData[i]; - const std::string monsterName = AsciiStrToLower(mondata.mName); - if (monsterName.find(name) == std::string::npos) - continue; - mtype = mondata.mtype; - uniqueIndex = static_cast(i); - if (monsterName == name) // to support partial name matching but always choose the correct monster if full name is given - break; - } - - if (mtype == -1) - return "Monster not found!"; - - size_t id = MaxLvlMTypes - 1; - bool found = false; - - for (size_t i = 0; i < LevelMonsterTypeCount; i++) { - if (LevelMonsterTypes[i].type == mtype) { - id = i; - found = true; - break; - } - } - - if (!found) { - CMonster &monsterType = LevelMonsterTypes[id]; - monsterType.type = static_cast<_monster_id>(mtype); - InitMonsterGFX(monsterType); - InitMonsterSND(monsterType); - monsterType.placeFlags |= PLACE_SCATTER; - monsterType.corpseId = 1; - } - - Player &myPlayer = *MyPlayer; - - int spawnedMonster = 0; - - auto ret = Crawl(0, MaxCrawlRadius, [&](Displacement displacement) -> std::optional { - Point pos = myPlayer.position.tile + displacement; - if (dPlayer[pos.x][pos.y] != 0 || dMonster[pos.x][pos.y] != 0) - return {}; - if (!IsTileWalkable(pos)) - return {}; - - Monster *monster = AddMonster(pos, myPlayer._pdir, id, true); - if (monster == nullptr) - return StrCat("I could only summon ", spawnedMonster, " Monsters. The rest strike for shorter working hours."); - PrepareUniqueMonst(*monster, uniqueIndex, 0, 0, UniqueMonstersData[static_cast(uniqueIndex)]); - monster->corpseId = 1; - spawnedMonster += 1; - - if (spawnedMonster >= count) - return "Let the fighting begin!"; - - return {}; - }); - - if (!ret) - ret = StrCat("I could only summon ", spawnedMonster, " Monsters. The rest strike for shorter working hours."); - return *ret; -} - -std::string DebugCmdSpawnMonster(const string_view parameter) -{ - if (leveltype == DTYPE_TOWN) - return "Do you want to kill the towners?!?"; - - std::string name; - int count = 1; - for (string_view arg : SplitByChar(parameter, ' ')) { - const int num = atoi(std::string(arg).c_str()); - if (num > 0) { - count = num; - break; - } - AppendStrView(name, arg); - name += ' '; - } - if (name.empty()) - return "Monster name cannot be empty. Duh."; - - name.pop_back(); // remove last space - AsciiStrToLower(name); - - int mtype = -1; - - for (int i = 0; i < NUM_MTYPES; i++) { - auto mondata = MonstersData[i]; - const std::string monsterName = AsciiStrToLower(mondata.name); - if (monsterName.find(name) == std::string::npos) - continue; - mtype = i; - if (monsterName == name) // to support partial name matching but always choose the correct monster if full name is given - break; - } - - if (mtype == -1) - return "Monster not found!"; - - size_t id = MaxLvlMTypes - 1; - bool found = false; - - for (size_t i = 0; i < LevelMonsterTypeCount; i++) { - if (LevelMonsterTypes[i].type == mtype) { - id = i; - found = true; - break; - } - } - - if (!found) { - CMonster &monsterType = LevelMonsterTypes[id]; - monsterType.type = static_cast<_monster_id>(mtype); - InitMonsterGFX(monsterType); - InitMonsterSND(monsterType); - monsterType.placeFlags |= PLACE_SCATTER; - monsterType.corpseId = 1; - } - - Player &myPlayer = *MyPlayer; - - int spawnedMonster = 0; - - auto ret = Crawl(0, MaxCrawlRadius, [&](Displacement displacement) -> std::optional { - Point pos = myPlayer.position.tile + displacement; - if (dPlayer[pos.x][pos.y] != 0 || dMonster[pos.x][pos.y] != 0) - return {}; - if (!IsTileWalkable(pos)) - return {}; - - if (AddMonster(pos, myPlayer._pdir, id, true) == nullptr) - return StrCat("I could only summon ", spawnedMonster, " Monsters. The rest strike for shorter working hours."); - spawnedMonster += 1; - - if (spawnedMonster >= count) - return "Let the fighting begin!"; - - return {}; - }); - - if (!ret) - ret = StrCat("I could only summon ", spawnedMonster, " Monsters. The rest strike for shorter working hours."); - return *ret; -} - -std::string DebugCmdShowTileData(const string_view parameter) -{ - std::string paramList[] = { - "dPiece", - "dTransVal", - "dLight", - "dPreLight", - "dFlags", - "dPlayer", - "dMonster", - "dCorpse", - "dObject", - "dItem", - "dSpecial", - "coords", - "cursorcoords", - "objectindex", - "solid", - "transparent", - "trap", - "AutomapView", - "dungeon", - "pdungeon", - "Protected", - }; - - if (parameter == "clear") { - SelectedDebugGridTextItem = DebugGridTextItem::None; - return "Tile data cleared!"; - } - if (parameter == "") { - std::string list = "clear"; - for (const auto ¶m : paramList) { - list += " / " + param; - } - return list; - } - bool found = false; - int index = 0; - for (const auto ¶m : paramList) { - index++; - if (parameter != param) - continue; - found = true; - auto newGridText = static_cast(index); - if (newGridText == SelectedDebugGridTextItem) { - SelectedDebugGridTextItem = DebugGridTextItem::None; - return "Tile data toggled... now you see nothing."; - } - SelectedDebugGridTextItem = newGridText; - break; - } - if (!found) - return "Invalid name. Check names using tiledata command."; - - return "Special powers activated."; -} - -std::string DebugCmdScrollView(const string_view parameter) -{ - DebugScrollViewEnabled = !DebugScrollViewEnabled; - if (DebugScrollViewEnabled) - return "You can see as far as an eagle."; - InitMultiView(); - return "If you want to see the world, you need to explore it yourself."; -} - -std::string DebugCmdItemInfo(const string_view parameter) -{ - Player &myPlayer = *MyPlayer; - Item *pItem = nullptr; - if (!myPlayer.HoldItem.isEmpty()) { - pItem = &myPlayer.HoldItem; - } else if (pcursinvitem != -1) { - if (pcursinvitem <= INVITEM_INV_LAST) - pItem = &myPlayer.InvList[pcursinvitem - INVITEM_INV_FIRST]; - else - pItem = &myPlayer.SpdList[pcursinvitem - INVITEM_BELT_FIRST]; - } else if (pcursitem != -1) { - pItem = &Items[pcursitem]; - } - if (pItem != nullptr) { - return StrCat("Name: ", pItem->_iIName, "\nIDidx: ", pItem->IDidx, "\nSeed: ", pItem->_iSeed, "\nCreateInfo: ", pItem->_iCreateInfo); - } - return StrCat("Numitems: ", ActiveItemCount); -} - -std::string DebugCmdQuestInfo(const string_view parameter) -{ - if (parameter.empty()) { - std::string ret = "You must provide an id. This could be:"; - for (auto &quest : Quests) { - if (IsNoneOf(quest._qactive, QUEST_NOTAVAIL, QUEST_INIT)) - continue; - StrAppend(ret, ", ", quest._qidx, " (", QuestsData[quest._qidx]._qlstr, ")"); - } - return ret; - } - - int questId = atoi(parameter.data()); - - if (questId >= MAXQUESTS) - return StrCat("Quest ", questId, " is not known. Do you want to write a mod?"); - auto &quest = Quests[questId]; - return StrCat("\nQuest: ", QuestsData[quest._qidx]._qlstr, "\nActive: ", quest._qactive, " Var1: ", quest._qvar1, " Var2: ", quest._qvar2); -} - -std::string DebugCmdPlayerInfo(const string_view parameter) -{ - int playerId = atoi(parameter.data()); - if (static_cast(playerId) >= Players.size()) - return "My friend, we need a valid playerId."; - Player &player = Players[playerId]; - if (!player.plractive) - return "Player is not active"; - - const Point target = player.GetTargetPosition(); - return StrCat("Plr ", playerId, " is ", player._pName, - "\nLvl: ", player.plrlevel, " Changing: ", player._pLvlChanging, - "\nTile.x: ", player.position.tile.x, " Tile.y: ", player.position.tile.y, " Target.x: ", target.x, " Target.y: ", target.y, - "\nMode: ", player._pmode, " destAction: ", player.destAction, " walkpath[0]: ", player.walkpath[0], - "\nInvincible:", player._pInvincible ? 1 : 0, " HitPoints:", player._pHitPoints); -} - -std::string DebugCmdToggleFPS(const string_view parameter) -{ - frameflag = !frameflag; - return ""; -} - -std::string DebugCmdChangeTRN(const string_view parameter) -{ - std::string out; - const auto parts = SplitByChar(parameter, ' '); - auto it = parts.begin(); - if (it != parts.end()) { - const string_view first = *it; - if (++it != parts.end()) { - const string_view second = *it; - string_view prefix; - if (first == "mon") { - prefix = "monsters\\monsters\\"; - } else if (first == "plr") { - prefix = "plrgfx\\"; - } - debugTRN = StrCat(prefix, second, ".trn"); - } else { - debugTRN = StrCat(first, ".trn"); - } - out = fmt::format("I am a pretty butterfly. \n(Loading TRN: {:s})", debugTRN); - } else { - debugTRN = ""; - out = "I am a big brown potato."; - } - auto &player = *MyPlayer; - InitPlayerGFX(player); - StartStand(player, player._pdir); - return out; -} - -std::string DebugCmdSearchMonster(const string_view parameter) -{ - if (parameter.empty()) { - std::string ret = "What should I search? I'm too lazy to search for everything... you must provide a monster name!"; - return ret; - } - - std::string name; - AppendStrView(name, parameter); - AsciiStrToLower(name); - SearchMonsters.push_back(name); - - return "We will find this bastard!"; -} - -std::string DebugCmdSearchItem(const string_view parameter) -{ - if (parameter.empty()) { - std::string ret = "What should I search? I'm too lazy to search for everything... you must provide a item name!"; - return ret; - } - - std::string name; - AppendStrView(name, parameter); - AsciiStrToLower(name); - SearchItems.push_back(name); - - return "Are you greedy? Anyway I will help you."; -} - -std::string DebugCmdSearchObject(const string_view parameter) -{ - if (parameter.empty()) { - std::string ret = "What should I search? I'm too lazy to search for everything... you must provide a object name!"; - return ret; - } - - std::string name; - AppendStrView(name, parameter); - AsciiStrToLower(name); - SearchObjects.push_back(name); - - return "I will look for the pyramids. Oh sorry, I'm looking for what you want, of course."; -} - -std::string DebugCmdClearSearch(const string_view parameter) -{ - SearchMonsters.clear(); - SearchItems.clear(); - SearchObjects.clear(); - - return "Now you have to find it yourself."; -} - -std::vector DebugCmdList = { - { "help", "Prints help overview or help for a specific command.", "({command})", &DebugCmdHelp }, - { "givegold", "Fills the inventory with gold.", "", &DebugCmdGiveGoldCheat }, - { "givexp", "Levels the player up (min 1 level or {levels}).", "({levels})", &DebugCmdLevelUp }, - { "maxstats", "Sets all stat values to maximum.", "", &DebugCmdMaxStats }, - { "minstats", "Sets all stat values to minimum.", "", &DebugCmdMinStats }, - { "setspells", "Set spell level to {level} for all spells.", "{level}", &DebugCmdSetSpellsLevel }, - { "takegold", "Removes all gold from inventory.", "", &DebugCmdTakeGoldCheat }, - { "givequest", "Enable a given quest.", "({id})", &DebugCmdQuest }, - { "givemap", "Reveal the map.", "", &DebugCmdMapReveal }, - { "takemap", "Hide the map.", "", &DebugCmdMapHide }, - { "goto", "Moves to specifided {level} (use 0 for town).", "{level}", &DebugCmdWarpToLevel }, - { "questmap", "Load a quest level {level}.", "{level}", &DebugCmdLoadQuestMap }, - { "map", "Load custom level from a given {path}.dun.", "{path} {type} {x} {y}", &DebugCmdLoadMap }, - { "exportdun", "Save the current level as a dun-file.", "", &ExportDun }, - { "visit", "Visit a towner.", "{towner}", &DebugCmdVisitTowner }, - { "restart", "Resets specified {level}.", "{level} ({seed})", &DebugCmdResetLevel }, - { "god", "Toggles godmode.", "", &DebugCmdGodMode }, - { "drawvision", "Toggles vision debug rendering.", "", &DebugCmdVision }, - { "drawpath", "Toggles path debug rendering.", "", &DebugCmdPath }, - { "fullbright", "Toggles whether light shading is in effect.", "", &DebugCmdLighting }, - { "fill", "Refills health and mana.", "", &DebugCmdRefillHealthMana }, - { "changehp", "Changes health by {value} (Use a negative value to remove health).", "{value}", &DebugCmdChangeHealth }, - { "changemp", "Changes mana by {value} (Use a negative value to remove mana).", "{value}", &DebugCmdChangeMana }, - { "dropu", "Attempts to generate unique item {name}.", "{name}", &DebugCmdGenerateUniqueItem }, - { "drop", "Attempts to generate item {name}.", "{name}", &DebugCmdGenerateItem }, - { "talkto", "Interacts with a NPC whose name contains {name}.", "{name}", &DebugCmdTalkToTowner }, - { "exit", "Exits the game.", "", &DebugCmdExit }, - { "arrow", "Changes arrow effect (normal, fire, lightning, explosion).", "{effect}", &DebugCmdArrow }, - { "grid", "Toggles showing grid.", "", &DebugCmdShowGrid }, - { "spawnu", "Spawns unique monster {name}.", "{name} ({count})", &DebugCmdSpawnUniqueMonster }, - { "spawn", "Spawns monster {name}.", "{name} ({count})", &DebugCmdSpawnMonster }, - { "tiledata", "Toggles showing tile data {name} (leave name empty to see a list).", "{name}", &DebugCmdShowTileData }, - { "scrollview", "Toggles scroll view feature (with shift+mouse).", "", &DebugCmdScrollView }, - { "iteminfo", "Shows info of currently selected item.", "", &DebugCmdItemInfo }, - { "questinfo", "Shows info of quests.", "{id}", &DebugCmdQuestInfo }, - { "playerinfo", "Shows info of player.", "{playerid}", &DebugCmdPlayerInfo }, - { "fps", "Toggles displaying FPS", "", &DebugCmdToggleFPS }, - { "trn", "Makes player use TRN {trn} - Write 'plr' before it to look in plrgfx\\ or 'mon' to look in monsters\\monsters\\ - example: trn plr infra is equal to 'plrgfx\\infra.trn'", "{trn}", &DebugCmdChangeTRN }, - { "searchmonster", "Searches the automap for {monster}", "{monster}", &DebugCmdSearchMonster }, - { "searchitem", "Searches the automap for {item}", "{item}", &DebugCmdSearchItem }, - { "searchobject", "Searches the automap for {object}", "{object}", &DebugCmdSearchObject }, - { "clearsearch", "Search in the auto map is cleared", "", &DebugCmdClearSearch }, -}; - } // namespace void LoadDebugGFX() @@ -1095,7 +88,7 @@ void GetDebugMonster() { int monsterIndex = pcursmonst; if (monsterIndex == -1) - monsterIndex = abs(dMonster[cursPosition.x][cursPosition.y]) - 1; + monsterIndex = std::abs(dMonster[cursPosition.x][cursPosition.y]) - 1; if (monsterIndex == -1) monsterIndex = DebugMonsterId; @@ -1120,23 +113,6 @@ void SetDebugLevelSeedInfos(uint32_t mid1Seed, uint32_t mid2Seed, uint32_t mid3S glEndSeed[currlevel] = endSeed; } -bool CheckDebugTextCommand(const string_view text) -{ - auto debugCmdIterator = std::find_if(DebugCmdList.begin(), DebugCmdList.end(), [&](const DebugCmdItem &elem) { return text.find(elem.text) == 0 && (text.length() == elem.text.length() || text[elem.text.length()] == ' '); }); - if (debugCmdIterator == DebugCmdList.end()) - return false; - - auto &dbgCmd = *debugCmdIterator; - string_view parameter = ""; - if (text.length() > (dbgCmd.text.length() + 1)) - parameter = text.substr(dbgCmd.text.length() + 1); - const auto result = dbgCmd.actionProc(parameter); - Log("DebugCmd: {} Result: {}", text, result); - if (result != "") - InitDiabloMsg(result); - return true; -} - bool IsDebugGridTextNeeded() { return SelectedDebugGridTextItem != DebugGridTextItem::None; @@ -1155,6 +131,16 @@ bool IsDebugGridInMegatiles() } } +DebugGridTextItem GetDebugGridTextType() +{ + return SelectedDebugGridTextItem; +} + +void SetDebugGridTextType(DebugGridTextItem value) +{ + SelectedDebugGridTextItem = value; +} + bool GetDebugGridText(Point dungeonCoords, char *debugGridTextBuffer) { int info = 0; @@ -1249,7 +235,7 @@ bool IsDebugAutomapHighlightNeeded() bool ShouldHighlightDebugAutomapTile(Point position) { - auto matchesSearched = [](const string_view name, const std::vector &searchedNames) { + auto matchesSearched = [](const std::string_view name, const std::vector &searchedNames) { const std::string lowercaseName = AsciiStrToLower(name); for (const auto &searchedName : searchedNames) { if (lowercaseName.find(searchedName) != std::string::npos) { @@ -1260,14 +246,14 @@ bool ShouldHighlightDebugAutomapTile(Point position) }; if (SearchMonsters.size() > 0 && dMonster[position.x][position.y] != 0) { - const int mi = abs(dMonster[position.x][position.y]) - 1; + const int mi = std::abs(dMonster[position.x][position.y]) - 1; const Monster &monster = Monsters[mi]; if (matchesSearched(monster.name(), SearchMonsters)) return true; } if (SearchItems.size() > 0 && dItem[position.x][position.y] != 0) { - const int itemId = abs(dItem[position.x][position.y]) - 1; + const int itemId = std::abs(dItem[position.x][position.y]) - 1; const Item &item = Items[itemId]; if (matchesSearched(item.getName(), SearchItems)) return true; @@ -1282,6 +268,28 @@ bool ShouldHighlightDebugAutomapTile(Point position) return false; } +void AddDebugAutomapMonsterHighlight(std::string_view name) +{ + SearchMonsters.emplace_back(name); +} + +void AddDebugAutomapItemHighlight(std::string_view name) +{ + SearchItems.emplace_back(name); +} + +void AddDebugAutomapObjectHighlight(std::string_view name) +{ + SearchObjects.emplace_back(name); +} + +void ClearDebugAutomapHighlights() +{ + SearchMonsters.clear(); + SearchItems.clear(); + SearchObjects.clear(); +} + } // namespace devilution #endif diff --git a/Source/debug.h b/Source/debug.h index 050d05c6739..29d994fc919 100644 --- a/Source/debug.h +++ b/Source/debug.h @@ -6,12 +6,12 @@ #pragma once #include +#include #include #include "diablo.h" #include "engine.h" #include "engine/clx_sprite.hpp" -#include "utils/stdcompat/string_view.hpp" namespace devilution { @@ -30,16 +30,51 @@ extern uint32_t glMid2Seed[NUMLEVELS]; extern uint32_t glMid3Seed[NUMLEVELS]; extern uint32_t glEndSeed[NUMLEVELS]; +enum class DebugGridTextItem : uint16_t { + None, + dPiece, + dTransVal, + dLight, + dPreLight, + dFlags, + dPlayer, + dMonster, + dCorpse, + dObject, + dItem, + dSpecial, + + coords, + cursorcoords, + objectindex, + + // take dPiece as index + Solid, + Transparent, + Trap, + + // megatiles + AutomapView, + dungeon, + pdungeon, + Protected, +}; + void FreeDebugGFX(); void LoadDebugGFX(); void GetDebugMonster(); void NextDebugMonster(); void SetDebugLevelSeedInfos(uint32_t mid1Seed, uint32_t mid2Seed, uint32_t mid3Seed, uint32_t endSeed); -bool CheckDebugTextCommand(const string_view text); bool IsDebugGridTextNeeded(); bool IsDebugGridInMegatiles(); +DebugGridTextItem GetDebugGridTextType(); +void SetDebugGridTextType(DebugGridTextItem value); bool GetDebugGridText(Point dungeonCoords, char *debugGridTextBuffer); bool IsDebugAutomapHighlightNeeded(); bool ShouldHighlightDebugAutomapTile(Point position); +void AddDebugAutomapMonsterHighlight(std::string_view name); +void AddDebugAutomapItemHighlight(std::string_view name); +void AddDebugAutomapObjectHighlight(std::string_view name); +void ClearDebugAutomapHighlights(); } // namespace devilution diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 48e33ce8ac1..fe3a610e6ea 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -5,6 +5,7 @@ */ #include #include +#include #include @@ -13,6 +14,7 @@ #include "DiabloUI/selstart.h" #include "automap.h" #include "capture.h" +#include "control.h" #include "cursor.h" #include "dead.h" #ifdef _DEBUG @@ -22,6 +24,7 @@ #include "controls/plrctrls.h" #include "controls/remap_keyboard.h" #include "diablo.h" +#include "diablo_msg.hpp" #include "discord/discord.h" #include "doom.h" #include "encrypt.h" @@ -34,7 +37,6 @@ #include "engine/load_file.hpp" #include "engine/random.hpp" #include "engine/sound.h" -#include "error.h" #include "gamemenu.h" #include "gmenu.h" #include "help.h" @@ -51,18 +53,22 @@ #include "levels/trigs.h" #include "lighting.h" #include "loadsave.h" +#include "lua/lua.hpp" #include "menu.h" #include "minitext.h" #include "missiles.h" +#include "monstdat.h" #include "movie.h" #include "multi.h" #include "nthread.h" #include "objects.h" #include "options.h" +#include "panels/console.hpp" #include "panels/info_box.hpp" #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" @@ -79,8 +85,9 @@ #include "utils/console.h" #include "utils/display.h" #include "utils/language.h" +#include "utils/parse_int.hpp" #include "utils/paths.h" -#include "utils/stdcompat/string_view.hpp" +#include "utils/screen_reader.hpp" #include "utils/str_cat.hpp" #include "utils/utf8.hpp" @@ -122,10 +129,16 @@ std::vector DebugCmdsFromCommandLine; #endif GameLogicStep gGameLogicStep = GameLogicStep::None; QuickMessage QuickMessages[QUICK_MESSAGE_OPTIONS] = { - { "QuickMessage1", N_("I need help! Come Here!") }, + { "QuickMessage1", N_("I need help! Come here!") }, { "QuickMessage2", N_("Follow me.") }, { "QuickMessage3", N_("Here's something for you.") }, - { "QuickMessage4", N_("Now you DIE!") } + { "QuickMessage4", N_("Now you DIE!") }, + { "QuickMessage5", N_("Heal yourself!") }, + { "QuickMessage6", N_("Watch out!") }, + { "QuickMessage7", N_("Thanks.") }, + { "QuickMessage8", N_("Retreat!") }, + { "QuickMessage9", N_("Sorry.") }, + { "QuickMessage10", N_("I'm waiting.") }, }; /** This and the following mouse variables are for handling in-game click-and-hold actions */ @@ -233,7 +246,7 @@ void LeftMouseCmd(bool bShift) NetSendCmdLocParam1(true, invflag ? CMD_GOTOGETITEM : CMD_GOTOAGETITEM, cursPosition, pcursitem); if (pcursmonst != -1) NetSendCmdLocParam1(true, CMD_TALKXY, cursPosition, pcursmonst); - if (pcursitem == -1 && pcursmonst == -1 && pcursplr == -1) { + if (pcursitem == -1 && pcursmonst == -1 && PlayerUnderCursor == nullptr) { LastMouseButtonAction = MouseActionType::Walk; NetSendCmdLoc(MyPlayerId, true, CMD_WALKXY, cursPosition); } @@ -258,9 +271,9 @@ void LeftMouseCmd(bool bShift) LastMouseButtonAction = MouseActionType::AttackMonsterTarget; NetSendCmdParam1(true, CMD_RATTACKID, pcursmonst); } - } else if (pcursplr != -1 && !myPlayer.friendlyMode) { + } else if (PlayerUnderCursor != nullptr && !myPlayer.friendlyMode) { LastMouseButtonAction = MouseActionType::AttackPlayerTarget; - NetSendCmdParam1(true, CMD_RATTACKPID, pcursplr); + NetSendCmdParam1(true, CMD_RATTACKPID, PlayerUnderCursor->getId()); } } else { if (bShift) { @@ -278,12 +291,12 @@ void LeftMouseCmd(bool bShift) } else if (pcursmonst != -1) { LastMouseButtonAction = MouseActionType::AttackMonsterTarget; NetSendCmdParam1(true, CMD_ATTACKID, pcursmonst); - } else if (pcursplr != -1 && !myPlayer.friendlyMode) { + } else if (PlayerUnderCursor != nullptr && !myPlayer.friendlyMode) { LastMouseButtonAction = MouseActionType::AttackPlayerTarget; - NetSendCmdParam1(true, CMD_ATTACKPID, pcursplr); + NetSendCmdParam1(true, CMD_ATTACKPID, PlayerUnderCursor->getId()); } } - if (!bShift && pcursitem == -1 && ObjectUnderCursor == nullptr && pcursmonst == -1 && pcursplr == -1) { + if (!bShift && pcursitem == -1 && ObjectUnderCursor == nullptr && pcursmonst == -1 && PlayerUnderCursor == nullptr) { LastMouseButtonAction = MouseActionType::Walk; NetSendCmdLoc(MyPlayerId, true, CMD_WALKXY, cursPosition); } @@ -413,6 +426,12 @@ void RightMouseDown(bool isShiftHeld) return; } + if (qtextflag) { + qtextflag = false; + stream_stop(); + return; + } + if (DoomFlag) { doom_close(); return; @@ -468,10 +487,6 @@ void PressKey(SDL_Keycode vkey, uint16_t modState) if (vkey == SDLK_UNKNOWN) return; - if (vkey == SDLK_PAUSE) { - diablo_pause_game(); - return; - } if (gmenu_presskeys(vkey) || control_presskeys(vkey)) { return; } @@ -546,7 +561,7 @@ void PressKey(SDL_Keycode vkey, uint16_t modState) } return; #ifdef _DEBUG - case SDLK_m: + case SDLK_v: if ((modState & KMOD_SHIFT) != 0) NextDebugMonster(); else @@ -665,23 +680,6 @@ void HandleMouseButtonUp(Uint8 button, uint16_t modState) } } -bool HandleTextInput(string_view text) -{ - if (IsTalkActive()) { - control_new_text(text); - return true; - } - if (dropGoldFlag) { - GoldDropNewText(text); - return true; - } - if (IsWithdrawGoldOpen) { - GoldWithdrawNewText(text); - return true; - } - return false; -} - [[maybe_unused]] void LogUnhandledEvent(const char *name, int value) { LogVerbose("Unhandled SDL event: {} {}", name, value); @@ -711,34 +709,29 @@ void GameEventHandler(const SDL_Event &event, uint16_t modState) return; } - switch (event.type) { - case SDL_KEYDOWN: { -#ifdef USE_SDL1 - // SDL1 does not support TEXTINPUT events, so we emulate them here. - const Uint16 bmpCodePoint = event.key.keysym.unicode; - if (bmpCodePoint >= ' ') { - std::string utf8; - AppendUtf8(bmpCodePoint, utf8); - if (HandleTextInput(utf8)) { - return; - } - } +#ifdef _DEBUG + if (ConsoleHandleEvent(event)) { + return; + } #endif - PressKey(event.key.keysym.sym, modState); + + if (IsTalkActive() && HandleTalkTextInputEvent(event)) { return; } - case SDL_KEYUP: - ReleaseKey(event.key.keysym.sym); + if (dropGoldFlag && HandleGoldDropTextInputEvent(event)) { return; -#if SDL_VERSION_ATLEAST(2, 0, 0) - case SDL_TEXTEDITING: + } + if (IsWithdrawGoldOpen && HandleGoldWithdrawTextInputEvent(event)) { return; - case SDL_TEXTINPUT: - if (!HandleTextInput(event.text.text)) { - LogUnhandledEvent("SDL_TEXTINPUT", event.text.windowID); - } + } + + switch (event.type) { + case SDL_KEYDOWN: + PressKey(event.key.keysym.sym, modState); + return; + case SDL_KEYUP: + ReleaseKey(event.key.keysym.sym); return; -#endif case SDL_MOUSEMOTION: if (ControlMode == ControlTypes::KeyboardAndMouse && invflag) InvalidateInventorySlot(); @@ -753,6 +746,43 @@ void GameEventHandler(const SDL_Event &event, uint16_t modState) MousePosition = { event.button.x, event.button.y }; HandleMouseButtonUp(event.button.button, modState); return; +#if SDL_VERSION_ATLEAST(2, 0, 0) + case SDL_MOUSEWHEEL: + if (event.wheel.y > 0) { // Up + if (stextflag != TalkID::None) { + StoreUp(); + } else if (QuestLogIsOpen) { + QuestlogUp(); + } else if (HelpFlag) { + HelpScrollUp(); + } else if (ChatLogFlag) { + ChatLogScrollUp(); + } else if (IsStashOpen) { + Stash.PreviousPage(); + } else { + sgOptions.Keymapper.KeyPressed(MouseScrollUpButton); + } + } else if (event.wheel.y < 0) { // down + if (stextflag != TalkID::None) { + StoreDown(); + } else if (QuestLogIsOpen) { + QuestlogDown(); + } else if (HelpFlag) { + HelpScrollDown(); + } else if (ChatLogFlag) { + ChatLogScrollDown(); + } else if (IsStashOpen) { + Stash.NextPage(); + } else { + sgOptions.Keymapper.KeyPressed(MouseScrollDownButton); + } + } else if (event.wheel.x > 0) { // left + sgOptions.Keymapper.KeyPressed(MouseScrollLeftButton); + } else if (event.wheel.x < 0) { // right + sgOptions.Keymapper.KeyPressed(MouseScrollRightButton); + } + break; +#endif default: if (IsCustomEvent(event.type)) { if (gbIsMultiplayer) @@ -813,6 +843,7 @@ void RunGameLoop(interface_mode uMsg) nthread_ignore_mutex(false); discord_manager::StartGame(); + LuaEvent("GameStart"); #ifdef GPERF_HEAP_FIRST_GAME_ITERATION unsigned run_game_iteration = 0; #endif @@ -821,8 +852,9 @@ void RunGameLoop(interface_mode uMsg) #ifdef _DEBUG if (!gbGameLoopStartup && !DebugCmdsFromCommandLine.empty()) { - for (auto &cmd : DebugCmdsFromCommandLine) { - CheckDebugTextCommand(cmd); + InitConsole(); + for (const std::string &cmd : DebugCmdsFromCommandLine) { + RunInConsole(cmd); } DebugCmdsFromCommandLine.clear(); } @@ -893,7 +925,7 @@ void RunGameLoop(interface_mode uMsg) } } -void PrintWithRightPadding(string_view str, size_t width) +void PrintWithRightPadding(std::string_view str, size_t width) { printInConsole(str); if (str.size() >= width) @@ -901,7 +933,7 @@ void PrintWithRightPadding(string_view str, size_t width) printInConsole(std::string(width - str.size(), ' ')); } -void PrintHelpOption(string_view flags, string_view description) +void PrintHelpOption(std::string_view flags, std::string_view description) { printInConsole(" "); PrintWithRightPadding(flags, 20); @@ -949,13 +981,18 @@ void PrintHelpOption(string_view flags, string_view description) diablo_quit(0); } -void PrintFlagsRequiresArgument(string_view flag) +void PrintFlagMessage(std::string_view flag, std::string_view message) { printInConsole(flag); - printInConsole(" requires an argument"); + printInConsole(message); printNewlineInConsole(); } +void PrintFlagRequiresArgument(std::string_view flag) +{ + PrintFlagMessage(flag, " requires an argument"); +} + void DiabloParseFlags(int argc, char **argv) { #ifdef _DEBUG @@ -969,7 +1006,7 @@ void DiabloParseFlags(int argc, char **argv) bool createDemoReference = false; #endif for (int i = 1; i < argc; i++) { - const string_view arg = argv[i]; + const std::string_view arg = argv[i]; if (arg == "-h" || arg == "--help") { PrintHelpAndExit(); } else if (arg == "--version") { @@ -980,44 +1017,54 @@ void DiabloParseFlags(int argc, char **argv) diablo_quit(0); } else if (arg == "--data-dir") { if (i + 1 == argc) { - PrintFlagsRequiresArgument("--data-dir"); + PrintFlagRequiresArgument("--data-dir"); diablo_quit(64); } paths::SetBasePath(argv[++i]); } else if (arg == "--save-dir") { if (i + 1 == argc) { - PrintFlagsRequiresArgument("--save-dir"); + PrintFlagRequiresArgument("--save-dir"); diablo_quit(64); } paths::SetPrefPath(argv[++i]); } else if (arg == "--config-dir") { if (i + 1 == argc) { - PrintFlagsRequiresArgument("--config-dir"); + PrintFlagRequiresArgument("--config-dir"); diablo_quit(64); } paths::SetConfigPath(argv[++i]); } else if (arg == "--lang") { if (i + 1 == argc) { - PrintFlagsRequiresArgument("--lang"); + PrintFlagRequiresArgument("--lang"); diablo_quit(64); } forceLocale = argv[++i]; #ifndef DISABLE_DEMOMODE } else if (arg == "--demo") { if (i + 1 == argc) { - PrintFlagsRequiresArgument("--demo"); + PrintFlagRequiresArgument("--demo"); + diablo_quit(64); + } + ParseIntResult parsedParam = ParseInt(argv[++i]); + if (!parsedParam.has_value()) { + PrintFlagMessage("--demo", " must be a number"); diablo_quit(64); } - demoNumber = SDL_atoi(argv[++i]); + demoNumber = parsedParam.value(); gbShowIntro = false; } else if (arg == "--timedemo") { timedemo = true; } else if (arg == "--record") { if (i + 1 == argc) { - PrintFlagsRequiresArgument("--record"); + PrintFlagRequiresArgument("--record"); + diablo_quit(64); + } + ParseIntResult parsedParam = ParseInt(argv[++i]); + if (!parsedParam.has_value()) { + PrintFlagMessage("--record", " must be a number"); diablo_quit(64); } - recordNumber = SDL_atoi(argv[++i]); + recordNumber = parsedParam.value(); } else if (arg == "--create-reference") { createDemoReference = true; #else @@ -1118,6 +1165,7 @@ void ApplicationInit() init_create_window(); was_window_init = true; + InitializeScreenReader(); LanguageInitialize(); SetApplicationVersions(); @@ -1205,6 +1253,9 @@ void DiabloDeinit() { FreeItemGFX(); + LuaShutdown(); + ShutDownScreenReader(); + if (gbSndInited) effects_cleanup_sfx(); snd_deinit(); @@ -1419,7 +1470,7 @@ void TimeoutCursor(bool bTimeout) if (sgnTimeoutCurs == CURSOR_NONE && sgbMouseDown == CLICK_NONE) { sgnTimeoutCurs = pcurs; multi_net_ping(); - InfoString = {}; + InfoString = StringOrView {}; AddPanelString(_("-- Network timeout --")); AddPanelString(_("-- Waiting for players --")); NewCursor(CURSOR_HOURGLASS); @@ -1433,7 +1484,7 @@ void TimeoutCursor(bool bTimeout) if (pcurs == CURSOR_HOURGLASS) NewCursor(sgnTimeoutCurs); sgnTimeoutCurs = CURSOR_NONE; - InfoString = {}; + InfoString = StringOrView {}; RedrawEverything(); } } @@ -1443,7 +1494,7 @@ void HelpKeyPressed() if (HelpFlag) { HelpFlag = false; } else if (stextflag != TalkID::None) { - InfoString = {}; + InfoString = StringOrView {}; AddPanelString(_("No help available")); /// BUGFIX: message isn't displayed AddPanelString(_("while in stores")); LastMouseButtonAction = MouseActionType::None; @@ -1563,6 +1614,33 @@ void SpellBookKeyPressed() CloseInventory(); } +void CycleSpellHotkeys(bool next) +{ + StaticVector validHotKeyIndexes; + std::optional currentIndex; + for (size_t slot = 0; slot < NumHotkeys; slot++) { + if (!IsValidSpeedSpell(slot)) + continue; + if (MyPlayer->_pRSpell == MyPlayer->_pSplHotKey[slot] && MyPlayer->_pRSplType == MyPlayer->_pSplTHotKey[slot]) { + // found current + currentIndex = validHotKeyIndexes.size(); + } + validHotKeyIndexes.emplace_back(slot); + } + if (validHotKeyIndexes.size() == 0) + return; + + size_t newIndex; + if (!currentIndex) { + newIndex = next ? 0 : (validHotKeyIndexes.size() - 1); + } else if (next) { + newIndex = (*currentIndex == validHotKeyIndexes.size() - 1) ? 0 : (*currentIndex + 1); + } else { + newIndex = *currentIndex == 0 ? (validHotKeyIndexes.size() - 1) : (*currentIndex - 1); + } + ToggleSpell(validHotKeyIndexes[newIndex]); +} + bool IsPlayerDead() { return MyPlayer->_pmode == PM_DEATH || MyPlayerIsDead; @@ -1577,11 +1655,23 @@ bool CanPlayerTakeAction() { return !IsPlayerDead() && IsGameRunning(); } + +bool CanAutomapBeToggledOff() +{ + // check if every window is closed - if yes, automap can be toggled off + if (!QuestLogIsOpen && !IsWithdrawGoldOpen && !IsStashOpen && !chrflag + && !sbookflag && !invflag && !isGameMenuOpen && !qtextflag && !spselflag + && !ChatLogFlag && !HelpFlag) + return true; + + return false; +} + } // namespace void InitKeymapActions() { - for (int i = 0; i < 8; ++i) { + for (uint32_t i = 0; i < 8; ++i) { sgOptions.Keymapper.AddAction( "BeltItem{}", N_("Belt item {}"), @@ -1597,7 +1687,7 @@ void InitKeymapActions() CanPlayerTakeAction, i + 1); } - for (size_t i = 0; i < NumHotkeys; ++i) { + for (uint32_t i = 0; i < NumHotkeys; ++i) { sgOptions.Keymapper.AddAction( "QuickSpell{}", N_("Quick spell {}"), @@ -1617,6 +1707,22 @@ void InitKeymapActions() CanPlayerTakeAction, i + 1); } + sgOptions.Keymapper.AddAction( + "QuickSpellPrevious", + N_("Previous quick spell"), + N_("Selects the previous quick spell (cycles)."), + MouseScrollUpButton, + [] { CycleSpellHotkeys(false); }, + nullptr, + CanPlayerTakeAction); + sgOptions.Keymapper.AddAction( + "QuickSpellNext", + N_("Next quick spell"), + N_("Selects the next quick spell (cycles)."), + MouseScrollDownButton, + [] { CycleSpellHotkeys(true); }, + nullptr, + CanPlayerTakeAction); sgOptions.Keymapper.AddAction( "UseHealthPotion", N_("Use health potion"), @@ -1695,6 +1801,14 @@ void InitKeymapActions() DoAutoMap, nullptr, IsGameRunning); + sgOptions.Keymapper.AddAction( + "CycleAutomapType", + N_("Cycle map type"), + N_("Opaque -> Transparent -> Minimap -> None"), + SDLK_m, + CycleAutomapType, + nullptr, + IsGameRunning); sgOptions.Keymapper.AddAction( "Inventory", @@ -1728,12 +1842,12 @@ void InitKeymapActions() SpellBookKeyPressed, nullptr, CanPlayerTakeAction); - for (int i = 0; i < 4; ++i) { + for (uint32_t i = 0; i < QUICK_MESSAGE_OPTIONS; ++i) { sgOptions.Keymapper.AddAction( "QuickMessage{}", N_("Quick Message {}"), N_("Use Quick Message in chat."), - SDLK_F9 + i, + (i < 4) ? static_cast(SDLK_F9) + i : static_cast(SDLK_UNKNOWN), [i]() { DiabloHotkeyMsg(i); }, nullptr, nullptr, @@ -1745,6 +1859,9 @@ void InitKeymapActions() N_("Hide all info screens."), SDLK_SPACE, [] { + if (CanAutomapBeToggledOff()) + AutomapActive = false; + ClosePanels(); HelpFlag = false; ChatLogFlag = false; @@ -1753,7 +1870,7 @@ void InitKeymapActions() qtextflag = false; stream_stop(); } - AutomapActive = false; + CancelCurrentDiabloMsg(); gamemenu_off(); doom_close(); @@ -1777,6 +1894,12 @@ void InitKeymapActions() N_("Pauses the game."), 'P', diablo_pause_game); + sgOptions.Keymapper.AddAction( + "Pause Game (Alternate)", + N_("Pause Game (Alternate)"), + N_("Pauses the game."), + SDLK_PAUSE, + diablo_pause_game); sgOptions.Keymapper.AddAction( "DecreaseGamma", N_("Decrease Gamma"), @@ -1831,6 +1954,12 @@ void InitKeymapActions() ToggleChatLog(); }); #ifdef _DEBUG + sgOptions.Keymapper.AddAction( + "OpenConsole", + N_("Console"), + N_("Opens Lua console."), + SDLK_BACKQUOTE, + OpenConsole); sgOptions.Keymapper.AddAction( "DebugToggle", "Debug toggle", @@ -1861,7 +1990,7 @@ void InitPadmapActions() CanPlayerTakeAction, i + 1); } - for (size_t i = 0; i < NumHotkeys; ++i) { + for (uint32_t i = 0; i < NumHotkeys; ++i) { sgOptions.Padmapper.AddAction( "QuickSpell{}", N_("Quick spell {}"), @@ -2213,6 +2342,9 @@ void InitPadmapActions() N_("Hide all info screens."), ControllerButton_NONE, [] { + if (CanAutomapBeToggledOff()) + AutomapActive = false; + ClosePanels(); HelpFlag = false; ChatLogFlag = false; @@ -2221,7 +2353,7 @@ void InitPadmapActions() qtextflag = false; stream_stop(); } - AutomapActive = false; + CancelCurrentDiabloMsg(); gamemenu_off(); doom_close(); @@ -2414,11 +2546,21 @@ int DiabloMain(int argc, char **argv) LoadLanguageArchive(); ApplicationInit(); + LuaInitialize(); SaveOptions(); // Finally load game data LoadGameArchives(); + // Load dynamic data before we go into the menu as we need to initialise player characters in memory pretty early. + LoadPlayerDataFiles(); + + // TODO: We can probably load this much later (when the game is starting). + LoadSpellData(); + LoadMissileData(); + LoadMonsterData(); + LoadItemData(); + DiabloInit(); #ifdef __UWP__ onInitialized(); @@ -2435,8 +2577,8 @@ int DiabloMain(int argc, char **argv) bool TryIconCurs() { if (pcurs == CURSOR_RESURRECT) { - if (pcursplr != -1) { - NetSendCmdParam1(true, CMD_RESURRECT, pcursplr); + if (PlayerUnderCursor != nullptr) { + NetSendCmdParam1(true, CMD_RESURRECT, PlayerUnderCursor->getId()); NewCursor(CURSOR_HAND); return true; } @@ -2445,8 +2587,8 @@ bool TryIconCurs() } if (pcurs == CURSOR_HEALOTHER) { - if (pcursplr != -1) { - NetSendCmdParam1(true, CMD_HEALOTHER, pcursplr); + if (PlayerUnderCursor != nullptr) { + NetSendCmdParam1(true, CMD_HEALOTHER, PlayerUnderCursor->getId()); NewCursor(CURSOR_HAND); return true; } @@ -2477,7 +2619,7 @@ bool TryIconCurs() DoRepair(myPlayer, pcursinvitem); else if (pcursstashitem != StashStruct::EmptyCell) { Item &item = Stash.stashList[pcursstashitem]; - RepairItem(item, myPlayer._pLevel); + RepairItem(item, myPlayer.getCharacterLevel()); } NewCursor(CURSOR_HAND); return true; @@ -2517,8 +2659,8 @@ bool TryIconCurs() NetSendCmdLocParam5(true, CMD_SPELLXYD, cursPosition, static_cast(spellID), static_cast(spellType), static_cast(sd), spellLevel, spellFrom); } else if (pcursmonst != -1) { NetSendCmdParam5(true, CMD_SPELLID, pcursmonst, static_cast(spellID), static_cast(spellType), spellLevel, spellFrom); - } else if (pcursplr != -1 && !myPlayer.friendlyMode) { - NetSendCmdParam5(true, CMD_SPELLPID, pcursplr, static_cast(spellID), static_cast(spellType), spellLevel, spellFrom); + } else if (PlayerUnderCursor != nullptr && !myPlayer.friendlyMode) { + NetSendCmdParam5(true, CMD_SPELLPID, PlayerUnderCursor->getId(), static_cast(spellID), static_cast(spellType), spellLevel, spellFrom); } else { NetSendCmdLocParam4(true, CMD_SPELLXY, cursPosition, static_cast(spellID), static_cast(spellType), spellLevel, spellFrom); } @@ -2730,7 +2872,10 @@ void LoadGameLevel(bool firstflag, lvl_entry lvldir) } if (firstflag || lvldir == ENTRY_LOAD) { + bool isHellfireSaveGame = gbIsHellfireSaveGame; + gbIsHellfireSaveGame = gbIsHellfire; LoadStash(); + gbIsHellfireSaveGame = isHellfireSaveGame; } IncProgress(); @@ -3016,7 +3161,7 @@ bool IsDiabloAlive(bool playSFX) { if (Quests[Q_DIABLO]._qactive == QUEST_DONE && !gbIsMultiplayer) { if (playSFX) - PlaySFX(USFX_DIABLOD); + PlaySFX(SfxID::DiabloDeath); return false; } diff --git a/Source/diablo.h b/Source/diablo.h index 0d34a62be33..a0116a14c62 100644 --- a/Source/diablo.h +++ b/Source/diablo.h @@ -117,7 +117,7 @@ struct QuickMessage { const char *const message; }; -constexpr size_t QUICK_MESSAGE_OPTIONS = 4; +constexpr size_t QUICK_MESSAGE_OPTIONS = 10; extern QuickMessage QuickMessages[QUICK_MESSAGE_OPTIONS]; /** * @brief Specifices what game logic step is currently executed diff --git a/Source/error.cpp b/Source/diablo_msg.cpp similarity index 51% rename from Source/error.cpp rename to Source/diablo_msg.cpp index 451fb953e77..7b2ebe4d607 100644 --- a/Source/error.cpp +++ b/Source/diablo_msg.cpp @@ -1,49 +1,57 @@ /** - * @file error.cpp + * @file diablo_msg.cpp * * Implementation of in-game message functions. */ +#include #include #include -#include "error.h" +#include "diablo_msg.hpp" #include "DiabloUI/ui_flags.hpp" +#include "engine/clx_sprite.hpp" #include "engine/render/clx_render.hpp" #include "engine/render/text_render.hpp" #include "panels/info_box.hpp" -#include "stores.h" +#include "utils/algorithm/container.hpp" #include "utils/language.h" +#include "utils/str_split.hpp" namespace devilution { namespace { -std::deque DiabloMessages; +struct MessageEntry { + std::string text; + uint32_t duration; // Duration in milliseconds +}; + +std::deque DiabloMessages; +uint32_t msgStartTime; std::vector TextLines; -uint32_t msgdelay; -int ErrorWindowHeight = 54; -const int LineHeight = 12; -const int LineWidth = 418; +int OuterHeight; +int LineWidth; +int LineHeight; -void InitNextLines() +void InitDiabloMsg() { - msgdelay = SDL_GetTicks(); TextLines.clear(); + if (DiabloMessages.empty()) + return; - const std::string paragraphs = WordWrapString(DiabloMessages.front(), LineWidth, GameFont12, 1); - - size_t previous = 0; - while (true) { - size_t next = paragraphs.find('\n', previous); - TextLines.emplace_back(paragraphs.substr(previous, next - previous)); - if (next == std::string::npos) - break; - previous = next + 1; + LineWidth = 418; + const std::string_view text = DiabloMessages.front().text; + const std::string wrapped = WordWrapString(text, LineWidth, GameFont12); + for (const std::string_view line : SplitByChar(wrapped, '\n')) { + LineWidth = std::max(LineWidth, GetLineWidth(text, GameFont12)); + TextLines.emplace_back(line); } - ErrorWindowHeight = std::max(54, static_cast((TextLines.size() * LineHeight) + 42)); + msgStartTime = SDL_GetTicks(); + LineHeight = GetLineHeight(text, GameFont12); + OuterHeight = static_cast((TextLines.size()) * LineHeight) + 42; } } // namespace @@ -51,7 +59,7 @@ void InitNextLines() /** Maps from error_id to error message. */ const char *const MsgStrings[] = { "", - N_("No automap available in town"), + N_("Game saved"), N_("No multiplayer functions in demo"), N_("Direct Sound Creation Failed"), N_("Not available in shareware version"), @@ -107,22 +115,21 @@ const char *const MsgStrings[] = { N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "That which can break will."), }; -void InitDiabloMsg(diablo_message e) +void InitDiabloMsg(diablo_message e, uint32_t duration /*= 3500*/) { - InitDiabloMsg(LanguageTranslate(MsgStrings[e])); + InitDiabloMsg(LanguageTranslate(MsgStrings[e]), duration); } -void InitDiabloMsg(string_view msg) +void InitDiabloMsg(std::string_view msg, uint32_t duration /*= 3500*/) { - if (DiabloMessages.size() >= MAX_SEND_STR_LEN) + if (c_find_if(DiabloMessages, [&msg](const MessageEntry &entry) { return entry.text == msg; }) + != DiabloMessages.end()) return; - if (std::find(DiabloMessages.begin(), DiabloMessages.end(), msg) != DiabloMessages.end()) - return; - - DiabloMessages.push_back(std::string(msg)); - if (DiabloMessages.size() == 1) - InitNextLines(); + DiabloMessages.push_back({ std::string(msg), duration }); + if (DiabloMessages.size() == 1) { + InitDiabloMsg(); + } } bool IsDiabloMsgAvailable() @@ -132,7 +139,10 @@ bool IsDiabloMsgAvailable() void CancelCurrentDiabloMsg() { - msgdelay = 0; + if (!DiabloMessages.empty()) { + DiabloMessages.pop_front(); + InitDiabloMsg(); + } } void ClrDiabloMsg() @@ -142,42 +152,70 @@ void ClrDiabloMsg() void DrawDiabloMsg(const Surface &out) { - auto &uiRectanglePosition = GetUIRectangle().position; - int dialogStartY = ((gnScreenHeight - GetMainPanel().size.height) / 2) - (ErrorWindowHeight / 2) + 9; - - ClxDraw(out, { uiRectanglePosition.x + 101, dialogStartY }, (*pSTextSlidCels)[0]); - ClxDraw(out, { uiRectanglePosition.x + 101, dialogStartY + ErrorWindowHeight - 6 }, (*pSTextSlidCels)[1]); - ClxDraw(out, { uiRectanglePosition.x + 527, dialogStartY + ErrorWindowHeight - 6 }, (*pSTextSlidCels)[2]); - ClxDraw(out, { uiRectanglePosition.x + 527, dialogStartY }, (*pSTextSlidCels)[3]); - - int sx = uiRectanglePosition.x + 109; - for (int i = 0; i < 35; i++) { - ClxDraw(out, { sx, dialogStartY }, (*pSTextSlidCels)[4]); - ClxDraw(out, { sx, dialogStartY + ErrorWindowHeight - 6 }, (*pSTextSlidCels)[6]); - sx += 12; + const ClxSpriteList sprites = *pSTextSlidCels; + const ClxSprite borderCornerTopLeftSprite = sprites[0]; + const ClxSprite borderCornerBottomLeftSprite = sprites[1]; + const ClxSprite borderCornerBottomRightSprite = sprites[2]; + const ClxSprite borderCornerTopRightSprite = sprites[3]; + const ClxSprite borderTopSprite = sprites[4]; + const ClxSprite borderLeftSprite = sprites[5]; + const ClxSprite borderBottomSprite = sprites[6]; + const ClxSprite borderRightSprite = sprites[7]; + + const int borderPartWidth = 12; + const int borderPartHeight = 12; + + const int textPaddingX = 5; + const int borderThickness = 3; + + const int outerHeight = std::min(out.h(), OuterHeight); + const int outerWidth = std::min(out.w(), LineWidth + 2 * borderThickness + textPaddingX); + const int innerWidth = outerWidth - 2 * borderThickness; + const int lineWidth = innerWidth - textPaddingX; + const int innerHeight = outerHeight - 2 * borderThickness; + + const Point topLeft { (out.w() - outerWidth) / 2, (out.h() - outerHeight) / 2 }; + + const int innerXBegin = topLeft.x + borderThickness; + const int innerXEnd = innerXBegin + innerWidth; + const int innerYBegin = topLeft.y + borderThickness; + const int innerYEnd = innerYBegin + innerHeight; + const int borderRightX = innerXEnd - (borderPartWidth - borderThickness); + const int borderBottomY = innerYEnd - (borderPartHeight - borderThickness); + + RenderClxSprite(out, borderCornerTopLeftSprite, topLeft); + RenderClxSprite(out, borderCornerBottomLeftSprite, { topLeft.x, borderBottomY }); + RenderClxSprite(out, borderCornerBottomRightSprite, { borderRightX, borderBottomY }); + RenderClxSprite(out, borderCornerTopRightSprite, { borderRightX, topLeft.y }); + + const Surface horizontalBorderOut = out.subregionX(topLeft.x, outerWidth - borderPartWidth); + for (int x = borderPartWidth; x < horizontalBorderOut.w(); x += borderPartWidth) { + RenderClxSprite(horizontalBorderOut, borderTopSprite, { x, topLeft.y }); + RenderClxSprite(horizontalBorderOut, borderBottomSprite, { x, borderBottomY }); } - int drawnYborder = 12; - while ((drawnYborder + 12) < ErrorWindowHeight) { - ClxDraw(out, { uiRectanglePosition.x + 101, dialogStartY + drawnYborder }, (*pSTextSlidCels)[5]); - ClxDraw(out, { uiRectanglePosition.x + 527, dialogStartY + drawnYborder }, (*pSTextSlidCels)[7]); - drawnYborder += 12; - } - - DrawHalfTransparentRectTo(out, uiRectanglePosition.x + 104, dialogStartY - 8, 432, ErrorWindowHeight); - int lineNumber = 0; - for (auto &line : TextLines) { - DrawString(out, line, { { uiRectanglePosition.x + 109, dialogStartY + 12 + lineNumber * LineHeight }, { LineWidth, LineHeight } }, UiFlags::AlignCenter, 1, LineHeight); - lineNumber += 1; + const Surface verticalBorderOut = out.subregionY(topLeft.y, outerHeight - borderPartHeight); + for (int y = borderPartHeight; y < verticalBorderOut.h(); y += borderPartHeight) { + RenderClxSprite(verticalBorderOut, borderLeftSprite, { topLeft.x, y }); + RenderClxSprite(verticalBorderOut, borderRightSprite, { borderRightX, y }); } - - if (msgdelay > 0 && msgdelay <= SDL_GetTicks() - 3500) { - msgdelay = 0; + DrawHalfTransparentRectTo(out, innerXBegin, innerYBegin, innerWidth, innerHeight); + + const int textX = innerXBegin + textPaddingX; + int textY = innerYBegin + (innerHeight - LineHeight * static_cast(TextLines.size())) / 2; + for (const std::string &line : TextLines) { + DrawString(out, line, { { textX, textY }, { lineWidth, LineHeight } }, + { .flags = UiFlags::AlignCenter, .lineHeight = LineHeight }); + textY += LineHeight; } - if (msgdelay == 0) { + + // Calculate the time the current message has been displayed + const uint32_t messageElapsedTime = SDL_GetTicks() - msgStartTime; + + // Check if the current message's duration has passed + if (!DiabloMessages.empty() && messageElapsedTime >= DiabloMessages.front().duration) { DiabloMessages.pop_front(); - if (!DiabloMessages.empty()) - InitNextLines(); + InitDiabloMsg(); } } diff --git a/Source/error.h b/Source/diablo_msg.hpp similarity index 84% rename from Source/error.h rename to Source/diablo_msg.hpp index 57b66b936cc..34abaec2fbc 100644 --- a/Source/error.h +++ b/Source/diablo_msg.hpp @@ -1,5 +1,5 @@ /** - * @file error.h + * @file diablo_msg.hpp * * Interface of in-game message functions. */ @@ -7,15 +7,15 @@ #include #include +#include #include "engine.h" -#include "utils/stdcompat/string_view.hpp" namespace devilution { enum diablo_message : uint8_t { EMSG_NONE, - EMSG_NO_AUTOMAP_IN_TOWN, + EMSG_GAME_SAVED, EMSG_NO_MULTIPLAYER_IN_DEMO, EMSG_DIRECT_SOUND_FAILED, EMSG_NOT_IN_SHAREWARE, @@ -71,8 +71,8 @@ enum diablo_message : uint8_t { EMSG_SHRINE_MURPHYS, }; -void InitDiabloMsg(diablo_message e); -void InitDiabloMsg(string_view msg); +void InitDiabloMsg(diablo_message e, uint32_t duration = 3500); +void InitDiabloMsg(std::string_view msg, uint32_t duration = 3500); bool IsDiabloMsgAvailable(); void CancelCurrentDiabloMsg(); void ClrDiabloMsg(); diff --git a/Source/discord/discord.cpp b/Source/discord/discord.cpp index 710a9210f69..6353facb69f 100644 --- a/Source/discord/discord.cpp +++ b/Source/discord/discord.cpp @@ -1,5 +1,10 @@ #include "discord.h" +#ifdef _WIN32 +// On Windows, discordsrc-src/cpp/discord.h includes windows.h +#define NOMINMAX 1 +#define WIN32_LEAN_AND_MEAN +#endif #include #include @@ -43,7 +48,7 @@ struct PlayerData { dungeon_type dungeonArea; _setlevels questMap; Uint8 dungeonLevel; - Sint8 playerLevel; + Uint8 playerLevel; int playerGfx; // Why??? This is POD @@ -88,8 +93,7 @@ std::string GetLocationString() std::string GetCharacterString() { - const string_view charClassStr = _(PlayersData[static_cast(MyPlayer->_pClass)].className); - return fmt::format(fmt::runtime(_(/* TRANSLATORS: Discord character, i.e. "Lv 6 Warrior" */ "Lv {} {}")), tracked_data.playerLevel, charClassStr); + return fmt::format(fmt::runtime(_(/* TRANSLATORS: Discord character, i.e. "Lv 6 Warrior" */ "Lv {} {}")), tracked_data.playerLevel, MyPlayer->getClassName()); } std::string GetDetailString() @@ -100,7 +104,7 @@ std::string GetDetailString() std::string GetStateString() { constexpr std::array DifficultyStrs = { N_("Normal"), N_("Nightmare"), N_("Hell") }; - const string_view difficultyStr = _(DifficultyStrs[sgGameInitInfo.nDifficulty]); + const std::string_view difficultyStr = _(DifficultyStrs[sgGameInitInfo.nDifficulty]); return fmt::format(fmt::runtime(_(/* TRANSLATORS: Discord state i.e. "Nightmare difficulty" */ "{} difficulty")), difficultyStr); } @@ -137,7 +141,7 @@ void UpdateGame() return; auto newData = PlayerData { - leveltype, setlvlnum, currlevel, MyPlayer->_pLevel, MyPlayer->_pgfxnum + leveltype, setlvlnum, currlevel, MyPlayer->getCharacterLevel(), MyPlayer->_pgfxnum }; if (newData != tracked_data) { tracked_data = newData; diff --git a/Source/doom.cpp b/Source/doom.cpp index 6002f1dd2bf..91ba9e27696 100644 --- a/Source/doom.cpp +++ b/Source/doom.cpp @@ -5,12 +5,13 @@ */ #include "doom.h" +#include + #include "control.h" #include "engine.h" #include "engine/clx_sprite.hpp" #include "engine/load_cel.hpp" #include "engine/render/clx_render.hpp" -#include "utils/stdcompat/optional.hpp" namespace devilution { namespace { diff --git a/Source/dvlnet/abstract_net.cpp b/Source/dvlnet/abstract_net.cpp index d12dbc28118..a4260ee89f7 100644 --- a/Source/dvlnet/abstract_net.cpp +++ b/Source/dvlnet/abstract_net.cpp @@ -1,20 +1,22 @@ #include "dvlnet/abstract_net.h" +#include "dvlnet/loopback.h" #include "utils/stubs.h" + #ifndef NONET -#include "dvlnet/base_protocol.h" #include "dvlnet/cdwrap.h" + #ifndef DISABLE_ZERO_TIER +#include "dvlnet/base_protocol.h" #include "dvlnet/protocol_zt.h" #endif + #ifndef DISABLE_TCP #include "dvlnet/tcp_client.h" #endif #endif -#include "dvlnet/loopback.h" -namespace devilution { -namespace net { +namespace devilution::net { std::unique_ptr abstract_net::MakeNet(provider_t provider) { @@ -24,11 +26,15 @@ std::unique_ptr abstract_net::MakeNet(provider_t provider) switch (provider) { #ifndef DISABLE_TCP case SELCONN_TCP: - return std::make_unique>(); + return std::make_unique([]() { + return std::make_unique(); + }); #endif #ifndef DISABLE_ZERO_TIER case SELCONN_ZT: - return std::make_unique>>(); + return std::make_unique([]() { + return std::make_unique>(); + }); #endif case SELCONN_LOOPBACK: return std::make_unique(); @@ -38,5 +44,4 @@ std::unique_ptr abstract_net::MakeNet(provider_t provider) #endif } -} // namespace net -} // namespace devilution +} // namespace devilution::net diff --git a/Source/dvlnet/abstract_net.h b/Source/dvlnet/abstract_net.h index 8ae09de88a0..832c50cbace 100644 --- a/Source/dvlnet/abstract_net.h +++ b/Source/dvlnet/abstract_net.h @@ -9,27 +9,19 @@ #include "multi.h" #include "storm/storm_net.hpp" -namespace devilution { -namespace net { +namespace devilution::net { -typedef std::vector buffer_t; -typedef unsigned long provider_t; -class dvlnet_exception : public std::exception { -public: - const char *what() const throw() override - { - return "Network error"; - } -}; +using buffer_t = std::vector; +using provider_t = unsigned long; class abstract_net { public: virtual int create(std::string addrstr) = 0; virtual int join(std::string addrstr) = 0; - virtual bool SNetReceiveMessage(uint8_t *sender, void **data, uint32_t *size) = 0; - virtual bool SNetSendMessage(int dest, void *data, unsigned int size) = 0; + virtual bool SNetReceiveMessage(uint8_t *sender, void **data, size_t *size) = 0; + virtual bool SNetSendMessage(uint8_t dest, void *data, size_t size) = 0; virtual bool SNetReceiveTurns(char **data, size_t *size, uint32_t *status) = 0; - virtual bool SNetSendTurn(char *data, unsigned int size) = 0; + virtual bool SNetSendTurn(char *data, size_t size) = 0; virtual void SNetGetProviderCaps(struct _SNETCAPS *caps) = 0; virtual bool SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func) = 0; virtual bool SNetUnregisterEventHandler(event_type evtype) = 0; @@ -67,5 +59,4 @@ class abstract_net { static std::unique_ptr MakeNet(provider_t provider); }; -} // namespace net -} // namespace devilution +} // namespace devilution::net diff --git a/Source/dvlnet/base.cpp b/Source/dvlnet/base.cpp index 4e88c2dc26b..bdbb953d669 100644 --- a/Source/dvlnet/base.cpp +++ b/Source/dvlnet/base.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include "player.h" namespace devilution { @@ -37,93 +39,117 @@ void base::DisconnectNet(plr_t plr) { } -void base::SendEchoRequest(plr_t player) +tl::expected base::SendEchoRequest(plr_t player) { if (plr_self == PLR_BROADCAST) - return; + return {}; if (player == plr_self) - return; + return {}; timestamp_t now = SDL_GetTicks(); - auto echo = pktfty->make_packet(plr_self, player, now); - send(*echo); + tl::expected, PacketError> pkt + = pktfty->make_packet(plr_self, player, now); + if (!pkt.has_value()) { + return tl::make_unexpected(pkt.error()); + } + return send(**pkt); } -void base::HandleAccept(packet &pkt) +tl::expected base::HandleAccept(packet &pkt) { if (plr_self != PLR_BROADCAST) { - return; // already have player id + return {}; // already have player id } if (pkt.Cookie() == cookie_self) { - plr_self = pkt.NewPlayer(); + tl::expected newPlayerPkt = pkt.NewPlayer(); + if (!newPlayerPkt.has_value()) + return tl::make_unexpected(newPlayerPkt.error()); + plr_self = *std::move(newPlayerPkt); Connect(plr_self); } - if (game_init_info != pkt.Info()) { - if (pkt.Info().size() != sizeof(GameData)) { + tl::expected infoPkt = pkt.Info(); + if (!infoPkt.has_value()) + return tl::make_unexpected(infoPkt.error()); + const buffer_t &info = **infoPkt; + if (game_init_info != info) { + if (info.size() != sizeof(GameData)) { ABORT(); } // we joined and did not create - game_init_info = pkt.Info(); + game_init_info = info; _SNETEVENT ev; ev.eventid = EVENT_TYPE_PLAYER_CREATE_GAME; ev.playerid = plr_self; - ev.data = const_cast(pkt.Info().data()); - ev.databytes = pkt.Info().size(); + ev.data = const_cast(info.data()); + ev.databytes = info.size(); RunEventHandler(ev); } + return {}; } -void base::HandleConnect(packet &pkt) +tl::expected base::HandleConnect(packet &pkt) { - plr_t newPlayer = pkt.NewPlayer(); - Connect(newPlayer); + return pkt.NewPlayer().transform([this](plr_t &&newPlayer) { + Connect(newPlayer); + }); } -void base::HandleTurn(packet &pkt) +tl::expected base::HandleTurn(packet &pkt) { plr_t src = pkt.Source(); PlayerState &playerState = playerStateTable_[src]; std::deque &turnQueue = playerState.turnQueue; - const turn_t &turn = pkt.Turn(); - turnQueue.push_back(turn); - MakeReady(turn.SequenceNumber); -} - -void base::HandleDisconnect(packet &pkt) -{ - plr_t newPlayer = pkt.NewPlayer(); - if (newPlayer != plr_self) { - if (IsConnected(newPlayer)) { - auto leaveinfo = pkt.LeaveInfo(); - _SNETEVENT ev; - ev.eventid = EVENT_TYPE_PLAYER_LEAVE_GAME; - ev.playerid = pkt.NewPlayer(); - ev.data = reinterpret_cast(&leaveinfo); - ev.databytes = sizeof(leaveinfo_t); - RunEventHandler(ev); - DisconnectNet(newPlayer); - ClearMsg(newPlayer); - PlayerState &playerState = playerStateTable_[newPlayer]; - playerState.isConnected = false; - playerState.turnQueue.clear(); - } - } else { - ABORT(); // we were dropped by the owner?!? + return pkt.Turn().transform([&](turn_t &&turn) { + turnQueue.push_back(turn); + MakeReady(turn.SequenceNumber); + }); +} + +tl::expected base::HandleDisconnect(packet &pkt) +{ + tl::expected newPlayer = pkt.NewPlayer(); + if (!newPlayer.has_value()) + return tl::make_unexpected(newPlayer.error()); + if (*newPlayer == plr_self) + return tl::make_unexpected("We were dropped by the owner?"); + if (IsConnected(*newPlayer)) { + tl::expected leaveinfo = pkt.LeaveInfo(); + if (!leaveinfo.has_value()) + return tl::make_unexpected(leaveinfo.error()); + _SNETEVENT ev; + ev.eventid = EVENT_TYPE_PLAYER_LEAVE_GAME; + ev.playerid = *newPlayer; + ev.data = reinterpret_cast(&*leaveinfo); + ev.databytes = sizeof(leaveinfo_t); + RunEventHandler(ev); + DisconnectNet(*newPlayer); + ClearMsg(*newPlayer); + PlayerState &playerState = playerStateTable_[*newPlayer]; + playerState.isConnected = false; + playerState.turnQueue.clear(); } + return {}; } -void base::HandleEchoRequest(packet &pkt) +tl::expected base::HandleEchoRequest(packet &pkt) { - auto reply = pktfty->make_packet(plr_self, pkt.Source(), pkt.Time()); - send(*reply); + return pkt.Time() + .and_then([&](cookie_t &&pktTime) { + return pktfty->make_packet(plr_self, pkt.Source(), pktTime); + }) + .and_then([&](std::unique_ptr &&pkt) { + return send(*pkt); + }); } -void base::HandleEchoReply(packet &pkt) +tl::expected base::HandleEchoReply(packet &pkt) { - uint32_t now = SDL_GetTicks(); + const uint32_t now = SDL_GetTicks(); plr_t src = pkt.Source(); - PlayerState &playerState = playerStateTable_[src]; - playerState.roundTripLatency = now - pkt.Time(); + return pkt.Time().transform([&](cookie_t &&pktTime) { + PlayerState &playerState = playerStateTable_[src]; + playerState.roundTripLatency = now - pktTime; + }); } void base::ClearMsg(plr_t plr) @@ -136,14 +162,15 @@ void base::ClearMsg(plr_t plr) message_queue.end()); } -void base::Connect(plr_t player) +tl::expected base::Connect(plr_t player) { PlayerState &playerState = playerStateTable_[player]; bool wasConnected = playerState.isConnected; playerState.isConnected = true; if (!wasConnected) - SendFirstTurnIfReady(player); + return SendFirstTurnIfReady(player); + return {}; } bool base::IsConnected(plr_t player) const @@ -152,40 +179,38 @@ bool base::IsConnected(plr_t player) const return playerState.isConnected; } -void base::RecvLocal(packet &pkt) +tl::expected base::RecvLocal(packet &pkt) { if (pkt.Source() < MAX_PLRS) { - Connect(pkt.Source()); + if (tl::expected result = Connect(pkt.Source()); + !result.has_value()) { + return result; + } } switch (pkt.Type()) { case PT_MESSAGE: - message_queue.emplace_back(pkt.Source(), pkt.Message()); - break; + return pkt.Message().transform([&](const buffer_t *message) { + message_queue.emplace_back(pkt.Source(), *message); + }); case PT_TURN: - HandleTurn(pkt); - break; + return HandleTurn(pkt); case PT_JOIN_ACCEPT: - HandleAccept(pkt); - break; + return HandleAccept(pkt); case PT_CONNECT: - HandleConnect(pkt); - break; + return HandleConnect(pkt); case PT_DISCONNECT: - HandleDisconnect(pkt); - break; + return HandleDisconnect(pkt); case PT_ECHO_REQUEST: - HandleEchoRequest(pkt); - break; + return HandleEchoRequest(pkt); case PT_ECHO_REPLY: - HandleEchoReply(pkt); - break; + return HandleEchoReply(pkt); default: - break; + return {}; // otherwise drop } } -bool base::SNetReceiveMessage(uint8_t *sender, void **data, uint32_t *size) +bool base::SNetReceiveMessage(uint8_t *sender, void **data, size_t *size) { poll(); if (message_queue.empty()) @@ -198,23 +223,31 @@ bool base::SNetReceiveMessage(uint8_t *sender, void **data, uint32_t *size) return true; } -bool base::SNetSendMessage(int playerId, void *data, unsigned int size) +bool base::SNetSendMessage(uint8_t playerId, void *data, size_t size) { - if (playerId != SNPLAYER_ALL && playerId != SNPLAYER_OTHERS - && (playerId < 0 || playerId >= MAX_PLRS)) + if (playerId != SNPLAYER_OTHERS && playerId >= MAX_PLRS) abort(); auto *rawMessage = reinterpret_cast(data); buffer_t message(rawMessage, rawMessage + size); - if (playerId == plr_self || playerId == SNPLAYER_ALL) + if (playerId == plr_self) message_queue.emplace_back(plr_self, message); plr_t dest; - if (playerId == SNPLAYER_ALL || playerId == SNPLAYER_OTHERS) + if (playerId == SNPLAYER_OTHERS) dest = PLR_BROADCAST; else dest = playerId; if (dest != plr_self) { - auto pkt = pktfty->make_packet(plr_self, dest, message); - send(*pkt); + tl::expected, PacketError> pkt + = pktfty->make_packet(plr_self, dest, message); + if (!pkt.has_value()) { + LogError("make_packet: {}", pkt.error().what()); + return false; + } + tl::expected result = send(**pkt); + if (!result.has_value()) { + LogError("send: {}", result.error().what()); + return false; + } } return true; } @@ -302,7 +335,7 @@ bool base::SNetReceiveTurns(char **data, size_t *size, uint32_t *status) return false; } -bool base::SNetSendTurn(char *data, unsigned int size) +bool base::SNetSendTurn(char *data, size_t size) { if (size != sizeof(int32_t)) ABORT(); @@ -319,37 +352,50 @@ bool base::SNetSendTurn(char *data, unsigned int size) return true; } -void base::SendTurnIfReady(turn_t turn) +tl::expected base::SendTurnIfReady(turn_t turn) { if (awaitingSequenceNumber_) awaitingSequenceNumber_ = !IsGameHost(); if (!awaitingSequenceNumber_) { - auto pkt = pktfty->make_packet(plr_self, PLR_BROADCAST, turn); - send(*pkt); + tl::expected, PacketError> pkt + = pktfty->make_packet(plr_self, PLR_BROADCAST, turn); + if (!pkt.has_value()) { + return tl::make_unexpected(pkt.error()); + } + return send(**pkt); } + return {}; } -void base::SendFirstTurnIfReady(plr_t player) +tl::expected base::SendFirstTurnIfReady(plr_t player) { if (awaitingSequenceNumber_) - return; + return {}; PlayerState &playerState = playerStateTable_[plr_self]; std::deque &turnQueue = playerState.turnQueue; if (turnQueue.empty()) - return; + return {}; for (turn_t turn : turnQueue) { - auto pkt = pktfty->make_packet(plr_self, player, turn); - send(*pkt); + tl::expected, PacketError> pkt + = pktfty->make_packet(plr_self, player, turn); + if (!pkt.has_value()) { + return tl::make_unexpected(pkt.error()); + } + tl::expected result = send(**pkt); + if (!result.has_value()) { + return result; + } } + return {}; } -void base::MakeReady(seq_t sequenceNumber) +tl::expected base::MakeReady(seq_t sequenceNumber) { if (!awaitingSequenceNumber_) - return; + return {}; current_turn = sequenceNumber; next_turn = sequenceNumber; @@ -360,8 +406,12 @@ void base::MakeReady(seq_t sequenceNumber) for (turn_t &turn : turnQueue) { turn.SequenceNumber = next_turn; next_turn++; - SendTurnIfReady(turn); + if (tl::expected result = SendTurnIfReady(turn); + !result.has_value()) { + return result; + } } + return {}; } void base::SNetGetProviderCaps(struct _SNETCAPS *caps) @@ -400,27 +450,54 @@ bool base::SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func) bool base::SNetLeaveGame(int type) { - auto pkt = pktfty->make_packet(plr_self, PLR_BROADCAST, - plr_self, type); - send(*pkt); + tl::expected, PacketError> pkt + = pktfty->make_packet( + plr_self, PLR_BROADCAST, plr_self, type); + if (!pkt.has_value()) { + LogError("make_packet: {}", pkt.error().what()); + return false; + } + tl::expected result = send(**pkt); + if (!result.has_value()) { + LogError("send: {}", result.error().what()); + return false; + } plr_self = PLR_BROADCAST; return true; } bool base::SNetDropPlayer(int playerid, uint32_t flags) { - auto pkt = pktfty->make_packet(plr_self, - PLR_BROADCAST, - (plr_t)playerid, - (leaveinfo_t)flags); - send(*pkt); - RecvLocal(*pkt); + plr_t plr = static_cast(playerid); + tl::expected, PacketError> pkt + = pktfty->make_packet( + plr_self, + PLR_BROADCAST, + plr, + static_cast(flags)); + if (!pkt.has_value()) { + LogError("make_packet: {}", pkt.error().what()); + return false; + } + // Disconnect at the network layer first so we + // don't send players their own disconnect packet + DisconnectNet(plr); + tl::expected sendResult = send(**pkt); + if (!sendResult.has_value()) { + LogError("send: {}", sendResult.error().what()); + return false; + } + tl::expected receiveResult = RecvLocal(**pkt); + if (!receiveResult.has_value()) { + LogError("SNetDropPlayer: {}", receiveResult.error().what()); + return false; + } return true; } plr_t base::GetOwner() { - for (size_t i = 0; i < Players.size(); ++i) { + for (plr_t i = 0; i < Players.size(); ++i) { if (IsConnected(i)) { return i; } @@ -435,7 +512,7 @@ bool base::SNetGetOwnerTurnsWaiting(uint32_t *turns) plr_t owner = GetOwner(); PlayerState &playerState = playerStateTable_[owner]; std::deque &turnQueue = playerState.turnQueue; - *turns = turnQueue.size(); + *turns = static_cast(turnQueue.size()); return true; } @@ -444,7 +521,7 @@ bool base::SNetGetTurnsInTransit(uint32_t *turns) { PlayerState &playerState = playerStateTable_[plr_self]; std::deque &turnQueue = playerState.turnQueue; - *turns = turnQueue.size(); + *turns = static_cast(turnQueue.size()); return true; } diff --git a/Source/dvlnet/base.h b/Source/dvlnet/base.h index 545e818d0aa..10979d0ef23 100644 --- a/Source/dvlnet/base.h +++ b/Source/dvlnet/base.h @@ -17,32 +17,28 @@ namespace net { class base : public abstract_net { public: - virtual int create(std::string addrstr) = 0; - virtual int join(std::string addrstr) = 0; - - virtual bool SNetReceiveMessage(uint8_t *sender, void **data, uint32_t *size); - virtual bool SNetSendMessage(int playerId, void *data, unsigned int size); - virtual bool SNetReceiveTurns(char **data, size_t *size, uint32_t *status); - virtual bool SNetSendTurn(char *data, unsigned int size); - virtual void SNetGetProviderCaps(struct _SNETCAPS *caps); - virtual bool SNetRegisterEventHandler(event_type evtype, - SEVTHANDLER func); - virtual bool SNetUnregisterEventHandler(event_type evtype); - virtual bool SNetLeaveGame(int type); - virtual bool SNetDropPlayer(int playerid, uint32_t flags); - virtual bool SNetGetOwnerTurnsWaiting(uint32_t *turns); - virtual bool SNetGetTurnsInTransit(uint32_t *turns); - - virtual void poll() = 0; - virtual void send(packet &pkt) = 0; + bool SNetReceiveMessage(uint8_t *sender, void **data, size_t *size) override; + bool SNetSendMessage(uint8_t playerId, void *data, size_t size) override; + bool SNetReceiveTurns(char **data, size_t *size, uint32_t *status) override; + bool SNetSendTurn(char *data, size_t size) override; + void SNetGetProviderCaps(struct _SNETCAPS *caps) override; + bool SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func) override; + bool SNetUnregisterEventHandler(event_type evtype) override; + bool SNetLeaveGame(int type) override; + bool SNetDropPlayer(int playerid, uint32_t flags) override; + bool SNetGetOwnerTurnsWaiting(uint32_t *turns) override; + bool SNetGetTurnsInTransit(uint32_t *turns) override; + + virtual tl::expected poll() = 0; + virtual tl::expected send(packet &pkt) = 0; virtual void DisconnectNet(plr_t plr); void setup_gameinfo(buffer_t info); - virtual void setup_password(std::string pw); - virtual void clear_password(); + void setup_password(std::string pw) override; + void clear_password() override; - virtual ~base() = default; + ~base() override = default; protected: std::map registered_handlers; @@ -80,10 +76,10 @@ class base : public abstract_net { std::unique_ptr pktfty; - void Connect(plr_t player); - void RecvLocal(packet &pkt); + tl::expected Connect(plr_t player); + tl::expected RecvLocal(packet &pkt); void RunEventHandler(_SNETEVENT &ev); - void SendEchoRequest(plr_t player); + tl::expected SendEchoRequest(plr_t player); [[nodiscard]] bool IsConnected(plr_t player) const; virtual bool IsGameHost() = 0; @@ -94,17 +90,17 @@ class base : public abstract_net { plr_t GetOwner(); bool AllTurnsArrived(); - void MakeReady(seq_t sequenceNumber); - void SendTurnIfReady(turn_t turn); - void SendFirstTurnIfReady(plr_t player); + tl::expected MakeReady(seq_t sequenceNumber); + tl::expected SendTurnIfReady(turn_t turn); + tl::expected SendFirstTurnIfReady(plr_t player); void ClearMsg(plr_t plr); - void HandleAccept(packet &pkt); - void HandleConnect(packet &pkt); - void HandleTurn(packet &pkt); - void HandleDisconnect(packet &pkt); - void HandleEchoRequest(packet &pkt); - void HandleEchoReply(packet &pkt); + tl::expected HandleAccept(packet &pkt); + tl::expected HandleConnect(packet &pkt); + tl::expected HandleTurn(packet &pkt); + tl::expected HandleDisconnect(packet &pkt); + tl::expected HandleEchoRequest(packet &pkt); + tl::expected HandleEchoReply(packet &pkt); }; } // namespace net diff --git a/Source/dvlnet/base_protocol.h b/Source/dvlnet/base_protocol.h index 76bf524a2f0..cf014506679 100644 --- a/Source/dvlnet/base_protocol.h +++ b/Source/dvlnet/base_protocol.h @@ -10,16 +10,15 @@ #include "player.h" #include "utils/log.hpp" -namespace devilution { -namespace net { +namespace devilution::net { template class base_protocol : public base { public: int create(std::string addrstr) override; int join(std::string addrstr) override; - void poll() override; - void send(packet &pkt) override; + tl::expected poll() override; + tl::expected send(packet &pkt) override; void DisconnectNet(plr_t plr) override; bool SNetLeaveGame(int type) override; @@ -45,23 +44,29 @@ class base_protocol : public base { endpoint_t firstpeer; std::string gamename; - std::map, endpoint_t>> game_list; + + struct GameListValue { + GameData data; + std::vector playerNames; + endpoint_t peer; + }; + std::map game_list; std::array peers; bool isGameHost_; plr_t get_master(); - void InitiateHandshake(plr_t player); - void SendTo(plr_t player, packet &pkt); + tl::expected InitiateHandshake(plr_t player); + tl::expected SendTo(plr_t player, packet &pkt); void DrainSendQueue(plr_t player); void recv(); - void handle_join_request(packet &pkt, endpoint_t sender); - void recv_decrypted(packet &pkt, endpoint_t sender); - void recv_ingame(packet &pkt, endpoint_t sender); + tl::expected handle_join_request(packet &pkt, endpoint_t sender); + tl::expected recv_decrypted(packet &pkt, endpoint_t sender); + tl::expected recv_ingame(packet &pkt, endpoint_t sender); bool is_recognized(endpoint_t sender); - bool wait_network(); + tl::expected wait_network(); bool wait_firstpeer(); - void wait_join(); + tl::expected wait_join(); }; template @@ -75,15 +80,18 @@ plr_t base_protocol

::get_master() } template -bool base_protocol

::wait_network() +tl::expected base_protocol

::wait_network() { // wait for ZeroTier for 5 seconds for (auto i = 0; i < 500; ++i) { - if (proto.network_online()) - break; + tl::expected status = proto.network_online(); + if (!status.has_value()) + return status; + if (*status) + return true; SDL_Delay(10); } - return proto.network_online(); + return false; } template @@ -99,8 +107,8 @@ bool base_protocol

::wait_firstpeer() { // wait for peer for 5 seconds for (auto i = 0; i < 500; ++i) { - if (game_list.count(gamename)) { - firstpeer = std::get<2>(game_list[gamename]); + if (game_list.find(gamename) != game_list.end()) { + firstpeer = game_list[gamename].peer; break; } send_info_request(); @@ -113,26 +121,43 @@ bool base_protocol

::wait_firstpeer() template bool base_protocol

::send_info_request() { - if (!proto.network_online()) + tl::expected status = proto.network_online(); + if (!status.has_value()) { + LogError("network_online: {}", status.error().what()); return false; - auto pkt = pktfty->make_packet(PLR_BROADCAST, PLR_MASTER); - proto.send_oob_mc(pkt->Data()); + } + if (!*status) + return false; + tl::expected, PacketError> pkt + = pktfty->make_packet(PLR_BROADCAST, PLR_MASTER); + if (!pkt.has_value()) { + LogError("make_packet: {}", pkt.error().what()); + return false; + } + proto.send_oob_mc((*pkt)->Data()); return true; } template -void base_protocol

::wait_join() +tl::expected base_protocol

::wait_join() { cookie_self = packet_out::GenerateCookie(); - auto pkt = pktfty->make_packet(PLR_BROADCAST, - PLR_MASTER, cookie_self, game_init_info); - proto.send(firstpeer, pkt->Data()); + tl::expected, PacketError> pkt + = pktfty->make_packet(PLR_BROADCAST, PLR_MASTER, cookie_self, game_init_info); + if (!pkt.has_value()) { + return tl::make_unexpected(pkt.error()); + } + tl::expected result = proto.send(firstpeer, (*pkt)->Data()); + if (!result.has_value()) { + return result; + } for (auto i = 0; i < 500; ++i) { recv(); if (plr_self != PLR_BROADCAST) - break; // join successful + return {}; // join successful SDL_Delay(10); } + return tl::make_unexpected("Timeout waiting to join game"); } template @@ -141,9 +166,18 @@ int base_protocol

::create(std::string addrstr) gamename = addrstr; isGameHost_ = true; - if (wait_network()) { + tl::expected isReady = wait_network(); + if (!isReady.has_value()) { + LogError("wait_network: {}", isReady.error().what()); + return -1; + } + if (*isReady) { plr_self = 0; - Connect(plr_self); + if (tl::expected result = Connect(plr_self); + !result.has_value()) { + LogError("Connect: {}", result.error().what()); + return -1; + } } return (plr_self == PLR_BROADCAST ? -1 : plr_self); } @@ -154,9 +188,21 @@ int base_protocol

::join(std::string addrstr) gamename = addrstr; isGameHost_ = false; - if (wait_network()) { - if (wait_firstpeer()) - wait_join(); + tl::expected isReady = wait_network(); + if (!isReady.has_value()) { + const std::string_view message = isReady.error().what(); + SDL_SetError("wait_join: %.*s", static_cast(message.size()), message.data()); + return -1; + } + if (*isReady) { + if (wait_firstpeer()) { + tl::expected result = wait_join(); + if (!result.has_value()) { + const std::string_view message = result.error().what(); + SDL_SetError("wait_join: %.*s", static_cast(message.size()), message.data()); + return -1; + } + } } return (plr_self == PLR_BROADCAST ? -1 : plr_self); } @@ -168,13 +214,14 @@ bool base_protocol

::IsGameHost() } template -void base_protocol

::poll() +tl::expected base_protocol

::poll() { recv(); + return {}; } template -void base_protocol

::InitiateHandshake(plr_t player) +tl::expected base_protocol

::InitiateHandshake(plr_t player) { Peer &peer = peers[player]; @@ -183,74 +230,76 @@ void base_protocol

::InitiateHandshake(plr_t player) // If the connection is already open, it should be safe to initiate from either end. // If not, only the player with the smaller player number should initiate the handshake. if (plr_self < player || proto.is_peer_connected(peer.endpoint)) - SendEchoRequest(player); + return SendEchoRequest(player); + + return {}; } template -void base_protocol

::send(packet &pkt) +tl::expected base_protocol

::send(packet &pkt) { plr_t destination = pkt.Destination(); - if (destination < MAX_PLRS) { - if (destination == MyPlayerId) - return; - SendTo(destination, pkt); - } else if (destination == PLR_BROADCAST) { - for (plr_t player = 0; player < Players.size(); player++) - SendTo(player, pkt); - } else if (destination == PLR_MASTER) { - throw dvlnet_exception(); - } else { - throw dvlnet_exception(); + if (destination == PLR_BROADCAST) { + for (plr_t player = 0; player < Players.size(); player++) { + tl::expected result = SendTo(player, pkt); + if (!result.has_value()) + LogError("Failed to send packet {} to player {}: {}", static_cast(pkt.Type()), player, result.error().what()); + } + return {}; } + if (destination >= MAX_PLRS) + return tl::make_unexpected("Invalid player ID"); + if (destination == MyPlayerId) + return {}; + return SendTo(destination, pkt); } template -void base_protocol

::SendTo(plr_t player, packet &pkt) +tl::expected base_protocol

::SendTo(plr_t player, packet &pkt) { Peer &peer = peers[player]; if (!peer.endpoint) - return; + return {}; // The handshake uses echo packets so clients know // when they can safely drain their send queues - if (peer.sendQueue && !IsAnyOf(pkt.Type(), PT_ECHO_REQUEST, PT_ECHO_REPLY)) + if (peer.sendQueue && !IsAnyOf(pkt.Type(), PT_ECHO_REQUEST, PT_ECHO_REPLY)) { peer.sendQueue->push_back(pkt); - else - proto.send(peer.endpoint, pkt.Data()); + return {}; + } + + return proto.send(peer.endpoint, pkt.Data()); } template void base_protocol

::recv() { - try { - buffer_t pkt_buf; - endpoint_t sender; - while (proto.recv(sender, pkt_buf)) { // read until kernel buffer is empty? - try { - auto pkt = pktfty->make_packet(pkt_buf); - recv_decrypted(*pkt, sender); - } catch (packet_exception &e) { - // drop packet - proto.disconnect(sender); - Log("{}", e.what()); - } + buffer_t pkt_buf; + endpoint_t sender; + while (proto.recv(sender, pkt_buf)) { // read until kernel buffer is empty? + tl::expected result + = pktfty->make_packet(pkt_buf) + .and_then([&](std::unique_ptr &&pkt) { + return recv_decrypted(*pkt, sender); + }); + if (!result.has_value()) { + // drop packet + proto.disconnect(sender); + Log("{}", result.error().what()); } - while (proto.get_disconnected(sender)) { - for (plr_t i = 0; i < Players.size(); ++i) { - if (peers[i].endpoint == sender) { - DisconnectNet(i); - break; - } + } + while (proto.get_disconnected(sender)) { + for (plr_t i = 0; i < Players.size(); ++i) { + if (peers[i].endpoint == sender) { + DisconnectNet(i); + break; } } - } catch (std::exception &e) { - Log("{}", e.what()); - return; } } template -void base_protocol

::handle_join_request(packet &pkt, endpoint_t sender) +tl::expected base_protocol

::handle_join_request(packet &inPkt, endpoint_t sender) { plr_t i; for (i = 0; i < Players.size(); ++i) { @@ -258,70 +307,89 @@ void base_protocol

::handle_join_request(packet &pkt, endpoint_t sender) if (i != plr_self && !peer.endpoint) { peer.endpoint = sender; peer.sendQueue = std::make_unique>(); - Connect(i); + if (tl::expected result = Connect(i); + !result.has_value()) { + return result; + } break; } } if (i >= MAX_PLRS) { // already full - return; + return {}; } auto senderinfo = sender.serialize(); for (plr_t j = 0; j < Players.size(); ++j) { endpoint_t peer = peers[j].endpoint; if ((j != plr_self) && (j != i) && peer) { - auto peerpkt = pktfty->make_packet(PLR_MASTER, PLR_BROADCAST, i, senderinfo); - proto.send(peer, peerpkt->Data()); - - auto infopkt = pktfty->make_packet(PLR_MASTER, PLR_BROADCAST, j, peer.serialize()); - proto.send(sender, infopkt->Data()); + tl::expected result + = pktfty->make_packet(PLR_MASTER, PLR_BROADCAST, i, senderinfo) + .and_then([&](std::unique_ptr &&pkt) { return proto.send(peer, pkt->Data()); }) + .and_then([&]() { return pktfty->make_packet(PLR_MASTER, PLR_BROADCAST, j, peer.serialize()); }) + .and_then([&](std::unique_ptr &&pkt) { return proto.send(sender, pkt->Data()); }); + if (!result.has_value()) + return result; } } // PT_JOIN_ACCEPT must be sent after all PT_CONNECT packets so the new player does // not resume game logic until after having been notified of all existing players - auto reply = pktfty->make_packet(plr_self, PLR_BROADCAST, - pkt.Cookie(), i, - game_init_info); - proto.send(sender, reply->Data()); + tl::expected cookie = inPkt.Cookie(); + if (!cookie.has_value()) + return tl::make_unexpected(cookie.error()); + tl::expected, PacketError> pkt + = pktfty->make_packet(plr_self, PLR_BROADCAST, *cookie, i, game_init_info); + if (!pkt.has_value()) + return tl::make_unexpected(pkt.error()); + tl::expected result = proto.send(sender, (*pkt)->Data()); + if (!result.has_value()) + return result; DrainSendQueue(i); + return {}; } template -void base_protocol

::recv_decrypted(packet &pkt, endpoint_t sender) +tl::expected base_protocol

::recv_decrypted(packet &pkt, endpoint_t sender) { if (pkt.Source() == PLR_BROADCAST && pkt.Destination() == PLR_MASTER && pkt.Type() == PT_INFO_REPLY) { size_t neededSize = sizeof(GameData) + (PlayerNameLength * MAX_PLRS); - if (pkt.Info().size() < neededSize) - return; - const GameData *gameData = (const GameData *)pkt.Info().data(); + const tl::expected pktInfo = pkt.Info(); + if (!pktInfo.has_value()) + return tl::make_unexpected(pktInfo.error()); + const buffer_t &infoBuffer = **pktInfo; + if (infoBuffer.size() < neededSize) + return {}; + const GameData *gameData = reinterpret_cast(infoBuffer.data()); if (gameData->size != sizeof(GameData)) - return; + return {}; std::vector playerNames; for (size_t i = 0; i < Players.size(); i++) { std::string playerName; - const char *playerNamePointer = (const char *)(pkt.Info().data() + sizeof(GameData) + (i * PlayerNameLength)); + const char *playerNamePointer = reinterpret_cast(infoBuffer.data() + sizeof(GameData) + (i * PlayerNameLength)); playerName.append(playerNamePointer, strnlen(playerNamePointer, PlayerNameLength)); if (!playerName.empty()) playerNames.push_back(playerName); } std::string gameName; - size_t gameNameSize = pkt.Info().size() - neededSize; + size_t gameNameSize = infoBuffer.size() - neededSize; gameName.resize(gameNameSize); - std::memcpy(&gameName[0], pkt.Info().data() + neededSize, gameNameSize); - game_list[gameName] = std::make_tuple(*gameData, playerNames, sender); - return; + std::memcpy(&gameName[0], infoBuffer.data() + neededSize, gameNameSize); + game_list[gameName] = GameListValue { *gameData, std::move(playerNames), sender }; + return {}; } - recv_ingame(pkt, sender); + return recv_ingame(pkt, sender); } template -void base_protocol

::recv_ingame(packet &pkt, endpoint_t sender) +tl::expected base_protocol

::recv_ingame(packet &pkt, endpoint_t sender) { if (pkt.Source() == PLR_BROADCAST && pkt.Destination() == PLR_MASTER) { if (pkt.Type() == PT_JOIN_REQUEST) { - handle_join_request(pkt, sender); + if (tl::expected result = handle_join_request(pkt, sender); + !result.has_value()) { + return result; + } } else if (pkt.Type() == PT_INFO_REQUEST) { if ((plr_self != PLR_BROADCAST) && (get_master() == plr_self)) { buffer_t buf; @@ -335,58 +403,84 @@ void base_protocol

::recv_ingame(packet &pkt, endpoint_t sender) } } std::memcpy(buf.data() + game_init_info.size() + (PlayerNameLength * MAX_PLRS), &gamename[0], gamename.size()); - auto reply = pktfty->make_packet(PLR_BROADCAST, - PLR_MASTER, - buf); - proto.send_oob(sender, reply->Data()); + tl::expected, PacketError> reply + = pktfty->make_packet(PLR_BROADCAST, PLR_MASTER, buf); + if (!reply.has_value()) { + return tl::make_unexpected(reply.error()); + } + proto.send_oob(sender, (*reply)->Data()); } } - return; - } else if (pkt.Source() == PLR_MASTER && pkt.Type() == PT_CONNECT) { + return {}; + } + if (pkt.Source() == PLR_MASTER && pkt.Type() == PT_CONNECT) { if (!is_recognized(sender)) { LogDebug("Invalid packet: PT_CONNECT received from unrecognized endpoint"); - return; + return {}; } // addrinfo packets - plr_t newPlayer = pkt.NewPlayer(); - Peer &peer = peers[newPlayer]; - peer.endpoint.unserialize(pkt.Info()); + tl::expected newPlayer = pkt.NewPlayer(); + if (!newPlayer.has_value()) + return tl::make_unexpected(newPlayer.error()); + Peer &peer = peers[*newPlayer]; + tl::expected pktInfo = pkt.Info(); + if (!pktInfo.has_value()) + return tl::make_unexpected(pktInfo.error()); + if (tl::expected result = peer.endpoint.unserialize(**pktInfo); + !result.has_value()) { + return result; + } peer.sendQueue = std::make_unique>(); - Connect(newPlayer); + if (tl::expected result = Connect(*newPlayer); + !result.has_value()) { + return result; + } if (plr_self != PLR_BROADCAST) - InitiateHandshake(newPlayer); - - return; - } else if (pkt.Source() >= MAX_PLRS) { + return InitiateHandshake(*newPlayer); + return {}; + } + if (pkt.Source() >= MAX_PLRS) { // normal packets LogDebug("Invalid packet: packet source ({}) >= MAX_PLRS", pkt.Source()); - return; - } else if (sender == firstpeer && pkt.Type() == PT_JOIN_ACCEPT) { + return {}; + } + if (sender == firstpeer && pkt.Type() == PT_JOIN_ACCEPT) { plr_t src = pkt.Source(); peers[src].endpoint = sender; - Connect(src); + if (tl::expected result = Connect(src); + !result.has_value()) { + return result; + } firstpeer = {}; } else if (sender != peers[pkt.Source()].endpoint) { LogDebug("Invalid packet: packet source ({}) received from unrecognized endpoint", pkt.Source()); - return; + return {}; } if (pkt.Destination() != plr_self && pkt.Destination() != PLR_BROADCAST) - return; // packet not for us, drop + return {}; // packet not for us, drop bool wasBroadcast = plr_self == PLR_BROADCAST; - RecvLocal(pkt); + if (tl::expected result = RecvLocal(pkt); + !result.has_value()) { + return result; + } if (plr_self != PLR_BROADCAST) { if (wasBroadcast) { // Send a handshake to everyone just after PT_JOIN_ACCEPT - for (plr_t player = 0; player < Players.size(); player++) - InitiateHandshake(player); + for (plr_t player = 0; player < Players.size(); player++) { + if (tl::expected result = InitiateHandshake(player); + !result.has_value()) { + return result; + } + } } DrainSendQueue(pkt.Source()); } + return {}; } template @@ -399,7 +493,9 @@ void base_protocol

::DrainSendQueue(plr_t player) std::deque &sendQueue = *srcPeer.sendQueue; while (!sendQueue.empty()) { packet &pkt = sendQueue.front(); - proto.send(srcPeer.endpoint, pkt.Data()); + tl::expected result = proto.send(srcPeer.endpoint, pkt.Data()); + if (!result.has_value()) + LogError("DrainSendQueue failed to send packet: {}", result.error().what()); sendQueue.pop_front(); } @@ -434,8 +530,10 @@ std::vector base_protocol

::get_gamelist() { recv(); std::vector ret; - for (auto &s : game_list) { - ret.push_back({ s.first, std::get<0>(s.second), std::get<1>(s.second) }); + ret.reserve(game_list.size()); + for (const auto &[name, gameInfo] : game_list) { + const auto &[gameData, players, _] = gameInfo; + ret.push_back(GameInfo { name, gameData, players }); } return ret; } @@ -454,5 +552,4 @@ std::string base_protocol

::make_default_gamename() return proto.make_default_gamename(); } -} // namespace net -} // namespace devilution +} // namespace devilution::net diff --git a/Source/dvlnet/cdwrap.cpp b/Source/dvlnet/cdwrap.cpp index dcc6d04b324..371d472aeb5 100644 --- a/Source/dvlnet/cdwrap.cpp +++ b/Source/dvlnet/cdwrap.cpp @@ -1 +1,133 @@ #include "dvlnet/cdwrap.h" + +namespace devilution::net { + +void cdwrap::reset() +{ + dvlnet_wrap = make_net_fn_(); + dvlnet_wrap->setup_gameinfo(game_init_info); + + if (game_pw != std::nullopt) { + dvlnet_wrap->setup_password(*game_pw); + } else { + dvlnet_wrap->clear_password(); + } + + for (const auto &[eventType, eventHandler] : registered_handlers) + dvlnet_wrap->SNetRegisterEventHandler(eventType, eventHandler); +} + +int cdwrap::create(std::string addrstr) +{ + reset(); + return dvlnet_wrap->create(addrstr); +} + +int cdwrap::join(std::string addrstr) +{ + game_init_info = buffer_t(); + reset(); + return dvlnet_wrap->join(addrstr); +} + +void cdwrap::setup_gameinfo(buffer_t info) +{ + game_init_info = std::move(info); + if (dvlnet_wrap) + dvlnet_wrap->setup_gameinfo(game_init_info); +} + +bool cdwrap::SNetReceiveMessage(uint8_t *sender, void **data, size_t *size) +{ + return dvlnet_wrap->SNetReceiveMessage(sender, data, size); +} + +bool cdwrap::SNetSendMessage(uint8_t playerID, void *data, size_t size) +{ + return dvlnet_wrap->SNetSendMessage(playerID, data, size); +} + +bool cdwrap::SNetReceiveTurns(char **data, size_t *size, uint32_t *status) +{ + return dvlnet_wrap->SNetReceiveTurns(data, size, status); +} + +bool cdwrap::SNetSendTurn(char *data, size_t size) +{ + return dvlnet_wrap->SNetSendTurn(data, size); +} + +void cdwrap::SNetGetProviderCaps(struct _SNETCAPS *caps) +{ + dvlnet_wrap->SNetGetProviderCaps(caps); +} + +bool cdwrap::SNetUnregisterEventHandler(event_type evtype) +{ + registered_handlers.erase(evtype); + if (dvlnet_wrap) + return dvlnet_wrap->SNetUnregisterEventHandler(evtype); + return true; +} + +bool cdwrap::SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func) +{ + registered_handlers[evtype] = func; + if (dvlnet_wrap) + return dvlnet_wrap->SNetRegisterEventHandler(evtype, func); + return true; +} + +bool cdwrap::SNetLeaveGame(int type) +{ + return dvlnet_wrap->SNetLeaveGame(type); +} + +bool cdwrap::SNetDropPlayer(int playerid, uint32_t flags) +{ + return dvlnet_wrap->SNetDropPlayer(playerid, flags); +} + +bool cdwrap::SNetGetOwnerTurnsWaiting(uint32_t *turns) +{ + return dvlnet_wrap->SNetGetOwnerTurnsWaiting(turns); +} + +bool cdwrap::SNetGetTurnsInTransit(uint32_t *turns) +{ + return dvlnet_wrap->SNetGetTurnsInTransit(turns); +} + +std::string cdwrap::make_default_gamename() +{ + return dvlnet_wrap->make_default_gamename(); +} + +bool cdwrap::send_info_request() +{ + return dvlnet_wrap->send_info_request(); +} + +void cdwrap::clear_gamelist() +{ + dvlnet_wrap->clear_gamelist(); +} + +std::vector cdwrap::get_gamelist() +{ + return dvlnet_wrap->get_gamelist(); +} + +void cdwrap::setup_password(std::string pw) +{ + game_pw = pw; + return dvlnet_wrap->setup_password(pw); +} + +void cdwrap::clear_password() +{ + game_pw = std::nullopt; + return dvlnet_wrap->clear_password(); +} + +} // namespace devilution::net diff --git a/Source/dvlnet/cdwrap.h b/Source/dvlnet/cdwrap.h index cf3f612e2c5..339477d29a9 100644 --- a/Source/dvlnet/cdwrap.h +++ b/Source/dvlnet/cdwrap.h @@ -4,208 +4,56 @@ #include #include #include +#include #include #include +#include + #include "dvlnet/abstract_net.h" #include "storm/storm_net.hpp" -#include "utils/stdcompat/optional.hpp" -namespace devilution { -namespace net { +namespace devilution::net { -template class cdwrap : public abstract_net { private: std::unique_ptr dvlnet_wrap; std::map registered_handlers; buffer_t game_init_info; std::optional game_pw; + tl::function_ref()> make_net_fn_; void reset(); public: - virtual int create(std::string addrstr); - virtual int join(std::string addrstr); - virtual bool SNetReceiveMessage(uint8_t *sender, void **data, uint32_t *size); - virtual bool SNetSendMessage(int dest, void *data, unsigned int size); - virtual bool SNetReceiveTurns(char **data, size_t *size, uint32_t *status); - virtual bool SNetSendTurn(char *data, unsigned int size); - virtual void SNetGetProviderCaps(struct _SNETCAPS *caps); - virtual bool SNetRegisterEventHandler(event_type evtype, - SEVTHANDLER func); - virtual bool SNetUnregisterEventHandler(event_type evtype); - virtual bool SNetLeaveGame(int type); - virtual bool SNetDropPlayer(int playerid, uint32_t flags); - virtual bool SNetGetOwnerTurnsWaiting(uint32_t *turns); - virtual bool SNetGetTurnsInTransit(uint32_t *turns); - virtual void setup_gameinfo(buffer_t info); - virtual std::string make_default_gamename(); - virtual bool send_info_request(); - virtual void clear_gamelist(); - virtual std::vector get_gamelist(); - virtual void setup_password(std::string pw); - virtual void clear_password(); + explicit cdwrap(tl::function_ref()> makeNetFn) + : make_net_fn_(makeNetFn) + { + reset(); + } + + int create(std::string addrstr) override; + int join(std::string addrstr) override; + bool SNetReceiveMessage(uint8_t *sender, void **data, size_t *size) override; + bool SNetSendMessage(uint8_t dest, void *data, size_t size) override; + bool SNetReceiveTurns(char **data, size_t *size, uint32_t *status) override; + bool SNetSendTurn(char *data, size_t size) override; + void SNetGetProviderCaps(struct _SNETCAPS *caps) override; + bool SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func) override; + bool SNetUnregisterEventHandler(event_type evtype) override; + bool SNetLeaveGame(int type) override; + bool SNetDropPlayer(int playerid, uint32_t flags) override; + bool SNetGetOwnerTurnsWaiting(uint32_t *turns) override; + bool SNetGetTurnsInTransit(uint32_t *turns) override; + void setup_gameinfo(buffer_t info) override; + std::string make_default_gamename() override; + bool send_info_request() override; + void clear_gamelist() override; + std::vector get_gamelist() override; + void setup_password(std::string pw) override; + void clear_password() override; - cdwrap(); virtual ~cdwrap() = default; }; -template -cdwrap::cdwrap() -{ - reset(); -} - -template -void cdwrap::reset() -{ - dvlnet_wrap.reset(new T); - dvlnet_wrap->setup_gameinfo(game_init_info); - - if (game_pw != std::nullopt) - dvlnet_wrap->setup_password(*game_pw); - else - dvlnet_wrap->clear_password(); - - for (const auto &pair : registered_handlers) - dvlnet_wrap->SNetRegisterEventHandler(pair.first, pair.second); -} - -template -int cdwrap::create(std::string addrstr) -{ - reset(); - return dvlnet_wrap->create(addrstr); -} - -template -int cdwrap::join(std::string addrstr) -{ - game_init_info = buffer_t(); - reset(); - return dvlnet_wrap->join(addrstr); -} - -template -void cdwrap::setup_gameinfo(buffer_t info) -{ - game_init_info = std::move(info); - if (dvlnet_wrap) - dvlnet_wrap->setup_gameinfo(game_init_info); -} - -template -bool cdwrap::SNetReceiveMessage(uint8_t *sender, void **data, uint32_t *size) -{ - return dvlnet_wrap->SNetReceiveMessage(sender, data, size); -} - -template -bool cdwrap::SNetSendMessage(int playerID, void *data, unsigned int size) -{ - return dvlnet_wrap->SNetSendMessage(playerID, data, size); -} - -template -bool cdwrap::SNetReceiveTurns(char **data, size_t *size, uint32_t *status) -{ - return dvlnet_wrap->SNetReceiveTurns(data, size, status); -} - -template -bool cdwrap::SNetSendTurn(char *data, unsigned int size) -{ - return dvlnet_wrap->SNetSendTurn(data, size); -} - -template -void cdwrap::SNetGetProviderCaps(struct _SNETCAPS *caps) -{ - dvlnet_wrap->SNetGetProviderCaps(caps); -} - -template -bool cdwrap::SNetUnregisterEventHandler(event_type evtype) -{ - registered_handlers.erase(evtype); - if (dvlnet_wrap) - return dvlnet_wrap->SNetUnregisterEventHandler(evtype); - else - return true; -} - -template -bool cdwrap::SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func) -{ - registered_handlers[evtype] = func; - if (dvlnet_wrap) - return dvlnet_wrap->SNetRegisterEventHandler(evtype, func); - else - return true; -} - -template -bool cdwrap::SNetLeaveGame(int type) -{ - return dvlnet_wrap->SNetLeaveGame(type); -} - -template -bool cdwrap::SNetDropPlayer(int playerid, uint32_t flags) -{ - return dvlnet_wrap->SNetDropPlayer(playerid, flags); -} - -template -bool cdwrap::SNetGetOwnerTurnsWaiting(uint32_t *turns) -{ - return dvlnet_wrap->SNetGetOwnerTurnsWaiting(turns); -} - -template -bool cdwrap::SNetGetTurnsInTransit(uint32_t *turns) -{ - return dvlnet_wrap->SNetGetTurnsInTransit(turns); -} - -template -std::string cdwrap::make_default_gamename() -{ - return dvlnet_wrap->make_default_gamename(); -} - -template -bool cdwrap::send_info_request() -{ - return dvlnet_wrap->send_info_request(); -} - -template -void cdwrap::clear_gamelist() -{ - dvlnet_wrap->clear_gamelist(); -} - -template -std::vector cdwrap::get_gamelist() -{ - return dvlnet_wrap->get_gamelist(); -} - -template -void cdwrap::setup_password(std::string pw) -{ - game_pw = pw; - return dvlnet_wrap->setup_password(pw); -} - -template -void cdwrap::clear_password() -{ - game_pw = std::nullopt; - return dvlnet_wrap->clear_password(); -} - -} // namespace net -} // namespace devilution +} // namespace devilution::net diff --git a/Source/dvlnet/frame_queue.cpp b/Source/dvlnet/frame_queue.cpp index 73e321c01e8..97b8359ddb0 100644 --- a/Source/dvlnet/frame_queue.cpp +++ b/Source/dvlnet/frame_queue.cpp @@ -9,25 +9,29 @@ namespace devilution { namespace net { -#if DVL_EXCEPTIONS -#define FRAME_QUEUE_ERROR throw frame_queue_exception() -#else -#define FRAME_QUEUE_ERROR app_fatal("frame queue error") -#endif +namespace { + +PacketError FrameQueueError() +{ + return PacketError("Incorrect frame size"); +} + +} // namespace framesize_t frame_queue::Size() const { return current_size; } -buffer_t frame_queue::Read(framesize_t s) +tl::expected frame_queue::Read(framesize_t s) { if (current_size < s) - FRAME_QUEUE_ERROR; + return tl::make_unexpected(FrameQueueError()); buffer_t ret; while (s > 0 && s >= buffer_deque.front().size()) { - s -= buffer_deque.front().size(); - current_size -= buffer_deque.front().size(); + framesize_t bufferSize = static_cast(buffer_deque.front().size()); + s -= bufferSize; + current_size -= bufferSize; ret.insert(ret.end(), buffer_deque.front().begin(), buffer_deque.front().end()); @@ -46,38 +50,40 @@ buffer_t frame_queue::Read(framesize_t s) void frame_queue::Write(buffer_t buf) { - current_size += buf.size(); + current_size += static_cast(buf.size()); buffer_deque.push_back(std::move(buf)); } -bool frame_queue::PacketReady() +tl::expected frame_queue::PacketReady() { if (nextsize == 0) { if (Size() < sizeof(framesize_t)) return false; - auto szbuf = Read(sizeof(framesize_t)); - std::memcpy(&nextsize, &szbuf[0], sizeof(framesize_t)); + tl::expected szbuf = Read(sizeof(framesize_t)); + if (!szbuf.has_value()) + return tl::make_unexpected(szbuf.error()); + std::memcpy(&nextsize, &(*szbuf)[0], sizeof(framesize_t)); if (nextsize == 0) - FRAME_QUEUE_ERROR; + return tl::make_unexpected(FrameQueueError()); } return Size() >= nextsize; } -buffer_t frame_queue::ReadPacket() +tl::expected frame_queue::ReadPacket() { if (nextsize == 0 || Size() < nextsize) - FRAME_QUEUE_ERROR; - auto ret = Read(nextsize); + return tl::make_unexpected(FrameQueueError()); + tl::expected ret = Read(nextsize); nextsize = 0; return ret; } -buffer_t frame_queue::MakeFrame(buffer_t packetbuf) +tl::expected frame_queue::MakeFrame(buffer_t packetbuf) { buffer_t ret; - if (packetbuf.size() > max_frame_size) - ABORT(); - framesize_t size = packetbuf.size(); + framesize_t size = static_cast(packetbuf.size()); + if (size > max_frame_size) + return tl::make_unexpected("Buffer exceeds maximum frame size"); ret.insert(ret.end(), packet_out::begin(size), packet_out::end(size)); ret.insert(ret.end(), packetbuf.begin(), packetbuf.end()); return ret; diff --git a/Source/dvlnet/frame_queue.h b/Source/dvlnet/frame_queue.h index e2673fa4143..eadf82b2d59 100644 --- a/Source/dvlnet/frame_queue.h +++ b/Source/dvlnet/frame_queue.h @@ -5,19 +5,14 @@ #include #include +#include + +#include "dvlnet/packet.h" + namespace devilution { namespace net { typedef std::vector buffer_t; - -class frame_queue_exception : public std::exception { -public: - const char *what() const throw() override - { - return "Incorrect frame size"; - } -}; - typedef uint32_t framesize_t; class frame_queue { @@ -30,14 +25,14 @@ class frame_queue { framesize_t nextsize = 0; framesize_t Size() const; - buffer_t Read(framesize_t s); + tl::expected Read(framesize_t s); public: - bool PacketReady(); - buffer_t ReadPacket(); + tl::expected PacketReady(); + tl::expected ReadPacket(); void Write(buffer_t buf); - static buffer_t MakeFrame(buffer_t packetbuf); + static tl::expected MakeFrame(buffer_t packetbuf); }; } // namespace net diff --git a/Source/dvlnet/loopback.cpp b/Source/dvlnet/loopback.cpp index eac4d08b44b..21ca4e12c32 100644 --- a/Source/dvlnet/loopback.cpp +++ b/Source/dvlnet/loopback.cpp @@ -7,8 +7,7 @@ #include "utils/language.h" #include "utils/stubs.h" -namespace devilution { -namespace net { +namespace devilution::net { int loopback::create(std::string /*addrstr*/) { @@ -21,7 +20,7 @@ int loopback::join(std::string /*addrstr*/) ABORT(); } -bool loopback::SNetReceiveMessage(uint8_t *sender, void **data, uint32_t *size) +bool loopback::SNetReceiveMessage(uint8_t *sender, void **data, size_t *size) { if (message_queue.empty()) return false; @@ -33,9 +32,9 @@ bool loopback::SNetReceiveMessage(uint8_t *sender, void **data, uint32_t *size) return true; } -bool loopback::SNetSendMessage(int dest, void *data, unsigned int size) +bool loopback::SNetSendMessage(uint8_t dest, void *data, size_t size) { - if (dest == plr_single || dest == SNPLAYER_ALL) { + if (dest == plr_single) { auto *rawMessage = reinterpret_cast(data); buffer_t message(rawMessage, rawMessage + size); message_queue.push(message); @@ -52,7 +51,7 @@ bool loopback::SNetReceiveTurns(char **data, size_t *size, uint32_t * /*status*/ return true; } -bool loopback::SNetSendTurn(char * /*data*/, unsigned int /*size*/) +bool loopback::SNetSendTurn(char * /*data*/, size_t /*size*/) { return true; } @@ -118,5 +117,4 @@ std::string loopback::make_default_gamename() return std::string(_("loopback")); } -} // namespace net -} // namespace devilution +} // namespace devilution::net diff --git a/Source/dvlnet/loopback.h b/Source/dvlnet/loopback.h index 72737cd148c..cbb295b5ee6 100644 --- a/Source/dvlnet/loopback.h +++ b/Source/dvlnet/loopback.h @@ -6,37 +6,32 @@ #include "dvlnet/abstract_net.h" -namespace devilution { -namespace net { +namespace devilution::net { class loopback : public abstract_net { private: std::queue message_queue; buffer_t message_last; - uint8_t plr_single; + uint8_t plr_single = 0; public: - loopback() - { - plr_single = 0; - }; + loopback() = default; - virtual int create(std::string addrstr); - virtual int join(std::string addrstr); - virtual bool SNetReceiveMessage(uint8_t *sender, void **data, uint32_t *size); - virtual bool SNetSendMessage(int dest, void *data, unsigned int size); - virtual bool SNetReceiveTurns(char **data, size_t *size, uint32_t *status); - virtual bool SNetSendTurn(char *data, unsigned int size); - virtual void SNetGetProviderCaps(struct _SNETCAPS *caps); - virtual bool SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func); - virtual bool SNetUnregisterEventHandler(event_type evtype); - virtual bool SNetLeaveGame(int type); - virtual bool SNetDropPlayer(int playerid, uint32_t flags); - virtual bool SNetGetOwnerTurnsWaiting(uint32_t *turns); - virtual bool SNetGetTurnsInTransit(uint32_t *turns); - virtual void setup_gameinfo(buffer_t info); - virtual std::string make_default_gamename(); + int create(std::string addrstr) override; + int join(std::string addrstr) override; + bool SNetReceiveMessage(uint8_t *sender, void **data, size_t *size) override; + bool SNetSendMessage(uint8_t dest, void *data, size_t size) override; + bool SNetReceiveTurns(char **data, size_t *size, uint32_t *status) override; + bool SNetSendTurn(char *data, size_t size) override; + void SNetGetProviderCaps(struct _SNETCAPS *caps) override; + bool SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func) override; + bool SNetUnregisterEventHandler(event_type evtype) override; + bool SNetLeaveGame(int type) override; + bool SNetDropPlayer(int playerid, uint32_t flags) override; + bool SNetGetOwnerTurnsWaiting(uint32_t *turns) override; + bool SNetGetTurnsInTransit(uint32_t *turns) override; + void setup_gameinfo(buffer_t info) override; + std::string make_default_gamename() override; }; -} // namespace net -} // namespace devilution +} // namespace devilution::net diff --git a/Source/dvlnet/packet.cpp b/Source/dvlnet/packet.cpp index 08771561a40..9507f24b4fb 100644 --- a/Source/dvlnet/packet.cpp +++ b/Source/dvlnet/packet.cpp @@ -10,8 +10,12 @@ #include #endif -namespace devilution { -namespace net { +#include + +#include "utils/algorithm/container.hpp" +#include "utils/str_cat.hpp" + +namespace devilution::net { #ifdef PACKET_ENCRYPTION @@ -80,39 +84,42 @@ const char *packet_type_to_string(uint8_t packetType) } } -wrong_packet_type_exception::wrong_packet_type_exception(std::initializer_list expectedTypes, std::uint8_t actual) +PacketError PacketTypeError(std::uint8_t unknownPacketType) +{ + return PacketError(StrCat("Unknown packet type ", unknownPacketType)); +} + +PacketError PacketTypeError(std::initializer_list expectedTypes, std::uint8_t actual) { - message_ = "Expected packet of type "; - const auto appendPacketType = [this](std::uint8_t t) { + std::string message = "Expected packet of type "; + const auto appendPacketType = [&](std::uint8_t t) { const char *typeStr = packet_type_to_string(t); if (typeStr != nullptr) - message_.append(typeStr); + message.append(typeStr); else - message_.append(std::to_string(t)); + StrAppend(message, t); }; constexpr char KJoinTypes[] = " or "; for (const packet_type t : expectedTypes) { appendPacketType(t); - message_.append(KJoinTypes); + message.append(KJoinTypes); } - message_.resize(message_.size() - (sizeof(KJoinTypes) - 1)); - message_.append(", got"); + message.resize(message.size() - (sizeof(KJoinTypes) - 1)); + message.append(", got"); appendPacketType(actual); + return PacketError(std::move(message)); } namespace { -void CheckPacketTypeOneOf(std::initializer_list expectedTypes, std::uint8_t actualType) +tl::expected CheckPacketTypeOneOf(std::initializer_list expectedTypes, std::uint8_t actualType) { - for (std::uint8_t packetType : expectedTypes) - if (actualType == packetType) - return; -#if DVL_EXCEPTIONS - throw wrong_packet_type_exception(expectedTypes, actualType); -#else - app_fatal("wrong packet type"); -#endif + if (c_none_of(expectedTypes, + [actualType](uint8_t type) { return type == actualType; })) { + return tl::make_unexpected(PacketTypeError(expectedTypes, actualType)); + } + return {}; } } // namespace @@ -143,64 +150,60 @@ plr_t packet::Destination() const return m_dest; } -const buffer_t &packet::Message() +tl::expected packet::Message() { assert(have_decrypted); - CheckPacketTypeOneOf({ PT_MESSAGE }, m_type); - return m_message; + return CheckPacketTypeOneOf({ PT_MESSAGE }, m_type) + .transform([this]() { return &m_message; }); } -turn_t packet::Turn() +tl::expected packet::Turn() { assert(have_decrypted); - CheckPacketTypeOneOf({ PT_TURN }, m_type); - return m_turn; + return CheckPacketTypeOneOf({ PT_TURN }, m_type) + .transform([this]() { return m_turn; }); } -cookie_t packet::Cookie() +tl::expected packet::Cookie() { assert(have_decrypted); - CheckPacketTypeOneOf({ PT_JOIN_REQUEST, PT_JOIN_ACCEPT }, m_type); - return m_cookie; + return CheckPacketTypeOneOf({ PT_JOIN_REQUEST, PT_JOIN_ACCEPT }, m_type) + .transform([this]() { return m_cookie; }); } -plr_t packet::NewPlayer() +tl::expected packet::NewPlayer() { assert(have_decrypted); - CheckPacketTypeOneOf({ PT_JOIN_ACCEPT, PT_CONNECT, PT_DISCONNECT }, m_type); - return m_newplr; + return CheckPacketTypeOneOf({ PT_JOIN_ACCEPT, PT_CONNECT, PT_DISCONNECT }, m_type) + .transform([this]() { return m_newplr; }); } -timestamp_t packet::Time() +tl::expected packet::Time() { assert(have_decrypted); - CheckPacketTypeOneOf({ PT_ECHO_REQUEST, PT_ECHO_REPLY }, m_type); - return m_time; + return CheckPacketTypeOneOf({ PT_ECHO_REQUEST, PT_ECHO_REPLY }, m_type) + .transform([this]() { return m_time; }); } -const buffer_t &packet::Info() +tl::expected packet::Info() { assert(have_decrypted); - CheckPacketTypeOneOf({ PT_JOIN_REQUEST, PT_JOIN_ACCEPT, PT_CONNECT, PT_INFO_REPLY }, m_type); - return m_info; + return CheckPacketTypeOneOf({ PT_JOIN_REQUEST, PT_JOIN_ACCEPT, PT_CONNECT, PT_INFO_REPLY }, m_type) + .transform([this]() { return &m_info; }); } -leaveinfo_t packet::LeaveInfo() +tl::expected packet::LeaveInfo() { assert(have_decrypted); - CheckPacketTypeOneOf({ PT_DISCONNECT }, m_type); - return m_leaveinfo; + return CheckPacketTypeOneOf({ PT_DISCONNECT }, m_type) + .transform([this]() { return m_leaveinfo; }); } -void packet_in::Create(buffer_t buf) +tl::expected packet_in::Create(buffer_t buf) { assert(!have_encrypted && !have_decrypted); if (buf.size() < sizeof(packet_type) + 2 * sizeof(plr_t)) -#if DVL_EXCEPTIONS - throw packet_exception(); -#else - app_fatal("invalid packet"); -#endif + return tl::make_unexpected(PacketError()); decrypted_buffer = std::move(buf); have_decrypted = true; @@ -210,10 +213,11 @@ void packet_in::Create(buffer_t buf) // we save a copy in encrypted_buffer anyway encrypted_buffer = decrypted_buffer; have_encrypted = true; + return {}; } #ifdef PACKET_ENCRYPTION -void packet_in::Decrypt(buffer_t buf) +tl::expected packet_in::Decrypt(buffer_t buf) { assert(!have_encrypted && !have_decrypted); encrypted_buffer = std::move(buf); @@ -222,7 +226,7 @@ void packet_in::Decrypt(buffer_t buf) if (encrypted_buffer.size() < crypto_secretbox_NONCEBYTES + crypto_secretbox_MACBYTES + sizeof(packet_type) + 2 * sizeof(plr_t)) - throw packet_exception(); + return tl::make_unexpected(PacketError()); auto pktlen = (encrypted_buffer.size() - crypto_secretbox_NONCEBYTES - crypto_secretbox_MACBYTES); @@ -234,9 +238,10 @@ void packet_in::Decrypt(buffer_t buf) encrypted_buffer.data(), key.data()); if (status != 0) - throw packet_exception(); + return tl::make_unexpected(PacketError()); have_decrypted = true; + return {}; } #endif @@ -298,5 +303,4 @@ packet_factory::packet_factory(std::string pw) #endif } -} // namespace net -} // namespace devilution +} // namespace devilution::net diff --git a/Source/dvlnet/packet.h b/Source/dvlnet/packet.h index a306a422fe5..f3dd7684233 100644 --- a/Source/dvlnet/packet.h +++ b/Source/dvlnet/packet.h @@ -6,6 +6,8 @@ #include #include +#include + #ifdef PACKET_ENCRYPTION #include #endif @@ -13,6 +15,7 @@ #include "appfat.h" #include "dvlnet/abstract_net.h" #include "utils/attributes.h" +#include "utils/str_cat.hpp" #include "utils/stubs.h" namespace devilution { @@ -56,27 +59,55 @@ struct turn_t { static constexpr plr_t PLR_MASTER = 0xFE; static constexpr plr_t PLR_BROADCAST = 0xFF; -class packet_exception : public dvlnet_exception { +class PacketError { public: - const char *what() const throw() override + PacketError() + : message_(std::string_view("Incorrect package size")) { - return "Incorrect package size"; } -}; -class wrong_packet_type_exception : public packet_exception { -public: - wrong_packet_type_exception(std::initializer_list expectedTypes, std::uint8_t actual); + PacketError(const char message[]) + : message_(std::string_view(message)) + { + } + + PacketError(std::string &&message) + : message_(std::move(message)) + { + } + + PacketError(std::string_view message) + : message_(message) + { + } + + PacketError(const PacketError &error) + : message_(std::string(error.message_)) + { + } - const char *what() const throw() override + PacketError(PacketError &&error) + : message_(std::move(error.message_)) { - return message_.c_str(); + } + + std::string_view what() const + { + return message_; } private: - std::string message_; + StringOrView message_; }; +inline PacketError IoHandlerError(std::string message) +{ + return PacketError(std::move(message)); +} + +PacketError PacketTypeError(std::uint8_t unknownPacketType); +PacketError PacketTypeError(std::initializer_list expectedTypes, std::uint8_t actual); + class packet { protected: packet_type m_type; @@ -105,30 +136,30 @@ class packet { packet_type Type(); plr_t Source() const; plr_t Destination() const; - const buffer_t &Message(); - turn_t Turn(); - cookie_t Cookie(); - plr_t NewPlayer(); - timestamp_t Time(); - const buffer_t &Info(); - leaveinfo_t LeaveInfo(); + tl::expected Message(); + tl::expected Turn(); + tl::expected Cookie(); + tl::expected NewPlayer(); + tl::expected Time(); + tl::expected Info(); + tl::expected LeaveInfo(); }; template class packet_proc : public packet { public: using packet::packet; - void process_data(); + tl::expected process_data(); }; class packet_in : public packet_proc { public: using packet_proc::packet_proc; - void Create(buffer_t buf); - void process_element(buffer_t &x); + tl::expected Create(buffer_t buf); + tl::expected process_element(buffer_t &x); template - void process_element(T &x); - void Decrypt(buffer_t buf); + tl::expected process_element(T &x); + tl::expected Decrypt(buffer_t buf); }; class packet_out : public packet_proc { @@ -138,9 +169,9 @@ class packet_out : public packet_proc { template void create(Args... args); - void process_element(buffer_t &x); + tl::expected process_element(buffer_t &x); template - void process_element(T &x); + tl::expected process_element(T &x); template static const unsigned char *begin(const T &x); template @@ -150,67 +181,68 @@ class packet_out : public packet_proc { }; template -void packet_proc

::process_data() +tl::expected packet_proc

::process_data() { P &self = static_cast

(*this); - self.process_element(m_type); - self.process_element(m_src); - self.process_element(m_dest); + { + tl::expected result + = self.process_element(m_type) + .and_then([&]() { + return self.process_element(m_src); + }) + .and_then([&]() { + return self.process_element(m_dest); + }); + if (!result.has_value()) + return result; + } switch (m_type) { case PT_MESSAGE: - self.process_element(m_message); - break; + return self.process_element(m_message); case PT_TURN: - self.process_element(m_turn.SequenceNumber); - self.process_element(m_turn.Value); - break; + return self.process_element(m_turn.SequenceNumber) + .and_then([&]() { return self.process_element(m_turn.Value); }); case PT_JOIN_REQUEST: - self.process_element(m_cookie); - self.process_element(m_info); - break; + return self.process_element(m_cookie) + .and_then([&]() { return self.process_element(m_info); }); case PT_JOIN_ACCEPT: - self.process_element(m_cookie); - self.process_element(m_newplr); - self.process_element(m_info); - break; + return self.process_element(m_cookie) + .and_then([&]() { return self.process_element(m_newplr); }) + .and_then([&]() { return self.process_element(m_info); }); case PT_CONNECT: - self.process_element(m_newplr); - self.process_element(m_info); - break; + return self.process_element(m_newplr) + .and_then([&]() { return self.process_element(m_info); }); case PT_DISCONNECT: - self.process_element(m_newplr); - self.process_element(m_leaveinfo); - break; + return self.process_element(m_newplr) + .and_then([&]() { return self.process_element(m_leaveinfo); }); case PT_INFO_REPLY: - self.process_element(m_info); - break; + return self.process_element(m_info); case PT_INFO_REQUEST: - break; + return {}; case PT_ECHO_REQUEST: case PT_ECHO_REPLY: - self.process_element(m_time); - break; + return self.process_element(m_time); } + return tl::make_unexpected(PacketTypeError(m_type)); } -inline void packet_in::process_element(buffer_t &x) +inline tl::expected packet_in::process_element(buffer_t &x) { x.insert(x.begin(), decrypted_buffer.begin(), decrypted_buffer.end()); decrypted_buffer.resize(0); + return {}; } template -void packet_in::process_element(T &x) +tl::expected packet_in::process_element(T &x) { - if (decrypted_buffer.size() < sizeof(T)) -#if DVL_EXCEPTIONS - throw packet_exception(); -#else - app_fatal("invalid packet"); -#endif + if (decrypted_buffer.size() < sizeof(T)) { + return tl::make_unexpected(PacketError()); + } std::memcpy(&x, decrypted_buffer.data(), sizeof(T)); decrypted_buffer.erase(decrypted_buffer.begin(), decrypted_buffer.begin() + sizeof(T)); + return {}; } template <> @@ -352,15 +384,17 @@ inline void packet_out::create(plr_t s, plr_t d, timestamp_t t) m_time = t; } -inline void packet_out::process_element(buffer_t &x) +inline tl::expected packet_out::process_element(buffer_t &x) { decrypted_buffer.insert(decrypted_buffer.end(), x.begin(), x.end()); + return {}; } template -void packet_out::process_element(T &x) +tl::expected packet_out::process_element(T &x) { decrypted_buffer.insert(decrypted_buffer.end(), begin(x), end(x)); + return {}; } template @@ -384,12 +418,12 @@ class packet_factory { packet_factory(); packet_factory(std::string pw); - std::unique_ptr make_packet(buffer_t buf); + tl::expected, PacketError> make_packet(buffer_t buf); template - std::unique_ptr make_packet(Args... args); + tl::expected, PacketError> make_packet(Args... args); }; -inline std::unique_ptr packet_factory::make_packet(buffer_t buf) +inline tl::expected, PacketError> packet_factory::make_packet(buffer_t buf) { auto ret = std::make_unique(key); #ifndef PACKET_ENCRYPTION @@ -400,16 +434,20 @@ inline std::unique_ptr packet_factory::make_packet(buffer_t buf) else ret->Decrypt(std::move(buf)); #endif - ret->process_data(); + if (const tl::expected result = ret->process_data(); !result.has_value()) { + return tl::make_unexpected(result.error()); + } return ret; } template -std::unique_ptr packet_factory::make_packet(Args... args) +tl::expected, PacketError> packet_factory::make_packet(Args... args) { auto ret = std::make_unique(key); ret->create(args...); - ret->process_data(); + if (const tl::expected result = ret->process_data(); !result.has_value()) { + return tl::make_unexpected(result.error()); + } #ifdef PACKET_ENCRYPTION if (secure) ret->Encrypt(); diff --git a/Source/dvlnet/protocol_zt.cpp b/Source/dvlnet/protocol_zt.cpp index ff50a33e86c..134614bccaa 100644 --- a/Source/dvlnet/protocol_zt.cpp +++ b/Source/dvlnet/protocol_zt.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -46,7 +47,7 @@ void protocol_zt::set_reuseaddr(int fd) lwip_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes)); } -bool protocol_zt::network_online() +tl::expected protocol_zt::network_online() { if (!zerotier_network_ready()) return false; @@ -64,7 +65,7 @@ bool protocol_zt::network_online() if (ret < 0) { Log("lwip, (udp) bind: {}", strerror(errno)); SDL_SetError("lwip, (udp) bind: %s", strerror(errno)); - throw protocol_exception(); + return tl::make_unexpected(ProtocolError()); } set_nonblock(fd_udp); } @@ -75,13 +76,13 @@ bool protocol_zt::network_online() if (r1 < 0) { Log("lwip, (tcp) bind: {}", strerror(errno)); SDL_SetError("lwip, (udp) bind: %s", strerror(errno)); - throw protocol_exception(); + return tl::make_unexpected(ProtocolError()); } auto r2 = lwip_listen(fd_tcp, 10); if (r2 < 0) { Log("lwip, listen: {}", strerror(errno)); SDL_SetError("lwip, listen: %s", strerror(errno)); - throw protocol_exception(); + return tl::make_unexpected(ProtocolError()); } set_nonblock(fd_tcp); set_nodelay(fd_tcp); @@ -89,10 +90,13 @@ bool protocol_zt::network_online() return true; } -bool protocol_zt::send(const endpoint &peer, const buffer_t &data) +tl::expected protocol_zt::send(const endpoint &peer, const buffer_t &data) { - peer_list[peer].send_queue.push_back(frame_queue::MakeFrame(data)); - return true; + tl::expected frame = frame_queue::MakeFrame(data); + if (!frame.has_value()) + return tl::make_unexpected(frame.error()); + peer_list[peer].send_queue.push_back(*frame); + return {}; } bool protocol_zt::send_oob(const endpoint &peer, const buffer_t &data) const @@ -113,7 +117,7 @@ bool protocol_zt::send_oob_mc(const buffer_t &data) const return send_oob(mc, data); } -bool protocol_zt::send_queued_peer(const endpoint &peer) +tl::expected protocol_zt::send_queued_peer(const endpoint &peer) { if (peer_list[peer].fd == -1) { peer_list[peer].fd = lwip_socket(AF_INET6, SOCK_STREAM, 0); @@ -142,7 +146,7 @@ bool protocol_zt::send_queued_peer(const endpoint &peer) if (decltype(len)(r) == len) { peer_list[peer].send_queue.pop_front(); } else { - throw protocol_exception(); + return tl::make_unexpected(ProtocolError()); } } return true; @@ -163,8 +167,13 @@ bool protocol_zt::recv_peer(const endpoint &peer) bool protocol_zt::send_queued_all() { - for (auto &peer : peer_list) { - if (!send_queued_peer(peer.first)) { + for (const auto &[endpoint, _] : peer_list) { + tl::expected result = send_queued_peer(endpoint); + if (!result.has_value()) { + LogError("send_queued_peer: {}", result.error().what()); + continue; + } + if (!*result) { // handle error? } } @@ -173,10 +182,10 @@ bool protocol_zt::send_queued_all() bool protocol_zt::recv_from_peers() { - for (auto &peer : peer_list) { - if (peer.second.fd != -1) { - if (!recv_peer(peer.first)) { - disconnect_queue.push_back(peer.first); + for (const auto &[endpoint, state] : peer_list) { + if (state.fd != -1) { + if (!recv_peer(endpoint)) { + disconnect_queue.push_back(endpoint); } } } @@ -237,11 +246,21 @@ bool protocol_zt::recv(endpoint &peer, buffer_t &data) } for (auto &p : peer_list) { - if (p.second.recv_queue.PacketReady()) { - peer = p.first; - data = p.second.recv_queue.ReadPacket(); - return true; + tl::expected ready = p.second.recv_queue.PacketReady(); + if (!ready.has_value()) { + LogError("PacketReady: {}", ready.error().what()); + continue; } + if (!*ready) + continue; + tl::expected packet = p.second.recv_queue.ReadPacket(); + if (!packet.has_value()) { + LogError("Failed reading packet data from peer: {}", packet.error().what()); + continue; + } + peer = p.first; + data = *packet; + return true; } return false; } @@ -279,9 +298,9 @@ void protocol_zt::close_all() lwip_close(fd_udp); fd_udp = -1; } - for (auto &peer : peer_list) { - if (peer.second.fd != -1) - lwip_close(peer.second.fd); + for (auto &[_, state] : peer_list) { + if (state.fd != -1) + lwip_close(state.fd); } peer_list.clear(); } @@ -312,13 +331,34 @@ bool protocol_zt::is_peer_connected(endpoint &peer) return peer_list.count(peer) != 0 && peer_list[peer].fd != -1; } +bool protocol_zt::is_peer_relayed(const endpoint &peer) const +{ + ip6_addr_t address = {}; + IP6_ADDR_PART(&address, 0, peer.addr[0], peer.addr[1], peer.addr[2], peer.addr[3]); + IP6_ADDR_PART(&address, 1, peer.addr[4], peer.addr[5], peer.addr[6], peer.addr[7]); + IP6_ADDR_PART(&address, 2, peer.addr[8], peer.addr[9], peer.addr[10], peer.addr[11]); + IP6_ADDR_PART(&address, 3, peer.addr[12], peer.addr[13], peer.addr[14], peer.addr[15]); + + const u8_t *hwaddr; + if (nd6_get_next_hop_addr_or_queue(netif_default, nullptr, &address, &hwaddr) != ERR_OK) + return true; + + uint64_t mac = hwaddr[0]; + mac = (mac << 8) | hwaddr[1]; + mac = (mac << 8) | hwaddr[2]; + mac = (mac << 8) | hwaddr[3]; + mac = (mac << 8) | hwaddr[4]; + mac = (mac << 8) | hwaddr[5]; + return zerotier_is_relayed(mac); +} + std::string protocol_zt::make_default_gamename() { std::string ret; std::string allowedChars = "abcdefghkopqrstuvwxyz"; std::random_device rd; - std::uniform_int_distribution dist(0, allowedChars.size() - 1); - for (int i = 0; i < 5; ++i) { + std::uniform_int_distribution dist(0, allowedChars.size() - 1); + for (size_t i = 0; i < 5; ++i) { ret += allowedChars.at(dist(rd)); } return ret; diff --git a/Source/dvlnet/protocol_zt.h b/Source/dvlnet/protocol_zt.h index 95513bec1dd..f625acfff16 100644 --- a/Source/dvlnet/protocol_zt.h +++ b/Source/dvlnet/protocol_zt.h @@ -11,17 +11,15 @@ #include #include "dvlnet/frame_queue.h" +#include "dvlnet/packet.h" namespace devilution { namespace net { -class protocol_exception : public std::exception { -public: - const char *what() const throw() override - { - return "Protocol error"; - } -}; +inline PacketError ProtocolError() +{ + return PacketError("Protocol error"); +} class protocol_zt { public: @@ -55,11 +53,12 @@ class protocol_zt { return buffer_t(addr.begin(), addr.end()); } - void unserialize(const buffer_t &buf) + tl::expected unserialize(const buffer_t &buf) { if (buf.size() != 16) - throw protocol_exception(); + return tl::make_unexpected(ProtocolError()); std::copy(buf.begin(), buf.end(), addr.begin()); + return {}; } void from_string(const std::string &str); @@ -68,13 +67,14 @@ class protocol_zt { protocol_zt(); ~protocol_zt(); void disconnect(const endpoint &peer); - bool send(const endpoint &peer, const buffer_t &data); + tl::expected send(const endpoint &peer, const buffer_t &data); bool send_oob(const endpoint &peer, const buffer_t &data) const; bool send_oob_mc(const buffer_t &data) const; bool recv(endpoint &peer, buffer_t &data); bool get_disconnected(endpoint &peer); - bool network_online(); + tl::expected network_online(); bool is_peer_connected(endpoint &peer); + bool is_peer_relayed(const endpoint &peer) const; static std::string make_default_gamename(); private: @@ -101,7 +101,7 @@ class protocol_zt { static void set_nodelay(int fd); static void set_reuseaddr(int fd); - bool send_queued_peer(const endpoint &peer); + tl::expected send_queued_peer(const endpoint &peer); bool recv_peer(const endpoint &peer); bool send_queued_all(); bool recv_from_peers(); diff --git a/Source/dvlnet/tcp_client.cpp b/Source/dvlnet/tcp_client.cpp index 7ad906d9339..25e15ae50a9 100644 --- a/Source/dvlnet/tcp_client.cpp +++ b/Source/dvlnet/tcp_client.cpp @@ -1,29 +1,26 @@ #include "dvlnet/tcp_client.h" -#include "options.h" -#include "utils/language.h" -#include #include #include #include #include #include +#include #include +#include -namespace devilution { -namespace net { +#include "options.h" +#include "utils/language.h" +#include "utils/str_cat.hpp" + +namespace devilution::net { int tcp_client::create(std::string addrstr) { - try { - auto port = *sgOptions.Network.port; - local_server = std::make_unique(ioc, addrstr, port, *pktfty); - return join(local_server->LocalhostSelf()); - } catch (std::system_error &e) { - SDL_SetError("%s", e.what()); - return -1; - } + auto port = *sgOptions.Network.port; + local_server = std::make_unique(ioc, addrstr, port, *pktfty); + return join(local_server->LocalhostSelf()); } int tcp_client::join(std::string addrstr) @@ -31,31 +28,48 @@ int tcp_client::join(std::string addrstr) constexpr int MsSleep = 10; constexpr int NoSleep = 250; - try { - std::stringstream port; - port << *sgOptions.Network.port; - asio::connect(sock, resolver.resolve(addrstr, port.str())); - asio::ip::tcp::no_delay option(true); - sock.set_option(option); - } catch (std::exception &e) { - SDL_SetError("%s", e.what()); + std::string port = StrCat(*sgOptions.Network.port); + + asio::error_code errorCode; + asio::ip::basic_resolver_results range = resolver.resolve(addrstr, port, errorCode); + if (errorCode) { + SDL_SetError("%s", errorCode.message().c_str()); return -1; } + + asio::connect(sock, range, errorCode); + if (errorCode) { + SDL_SetError("%s", errorCode.message().c_str()); + return -1; + } + + asio::ip::tcp::no_delay option(true); + sock.set_option(option, errorCode); + if (errorCode) + LogError("Client error setting socket option: {}", errorCode.message()); + StartReceive(); { cookie_self = packet_out::GenerateCookie(); - auto pkt = pktfty->make_packet(PLR_BROADCAST, - PLR_MASTER, cookie_self, - game_init_info); - send(*pkt); + tl::expected, PacketError> pkt + = pktfty->make_packet( + PLR_BROADCAST, PLR_MASTER, cookie_self, game_init_info); + if (!pkt.has_value()) { + const std::string_view message = pkt.error().what(); + SDL_SetError("make_packet: %.*s", static_cast(message.size()), message.data()); + return -1; + } + tl::expected sendResult = send(**pkt); + if (!sendResult.has_value()) { + const std::string_view message = sendResult.error().what(); + SDL_SetError("send: %.*s", static_cast(message.size()), message.data()); + return -1; + } for (auto i = 0; i < NoSleep; ++i) { - try { - poll(); - } catch (const dvlnet_exception &e) { - SDL_SetError("Network error: %s", e.what()); - return -1; - } catch (const std::runtime_error &e) { - SDL_SetError("%s", e.what()); + tl::expected pollResult = poll(); + if (!pollResult.has_value()) { + const std::string_view message = pollResult.error().what(); + SDL_SetError("%.*s", static_cast(message.size()), message.data()); return -1; } if (plr_self != PLR_BROADCAST) @@ -64,7 +78,8 @@ int tcp_client::join(std::string addrstr) } } if (plr_self == PLR_BROADCAST) { - SDL_SetError("%s", _("Unable to connect").data()); + const std::string_view message = _("Unable to connect"); + SDL_SetError("%.*s", static_cast(message.size()), message.data()); return -1; } @@ -76,28 +91,54 @@ bool tcp_client::IsGameHost() return local_server != nullptr; } -void tcp_client::poll() +tl::expected tcp_client::poll() { - ioc.poll(); + while (ioc.poll_one() > 0) { + if (IsGameHost()) { + tl::expected serverResult = local_server->CheckIoHandlerError(); + if (!serverResult.has_value()) + return serverResult; + } + if (ioHandlerResult == std::nullopt) + continue; + tl::expected packetError = tl::make_unexpected(*ioHandlerResult); + ioHandlerResult = std::nullopt; + return packetError; + } + return {}; } void tcp_client::HandleReceive(const asio::error_code &error, size_t bytesRead) { if (error) { - // error in recv from server - // returning and doing nothing should be the same - // as if all connections to other clients were lost + PacketError packetError = IoHandlerError(error.message()); + RaiseIoHandlerError(packetError); return; } if (bytesRead == 0) { - throw std::runtime_error(_("error: read 0 bytes from server").data()); + PacketError packetError(_("error: read 0 bytes from server")); + RaiseIoHandlerError(packetError); + return; } recv_buffer.resize(bytesRead); recv_queue.Write(std::move(recv_buffer)); recv_buffer.resize(frame_queue::max_frame_size); - while (recv_queue.PacketReady()) { - auto pkt = pktfty->make_packet(recv_queue.ReadPacket()); - RecvLocal(*pkt); + while (true) { + tl::expected ready = recv_queue.PacketReady(); + if (!ready.has_value()) { + RaiseIoHandlerError(ready.error()); + return; + } + if (!*ready) + break; + tl::expected result + = recv_queue.ReadPacket() + .and_then([this](buffer_t &&pktData) { return pktfty->make_packet(pktData); }) + .and_then([this](std::unique_ptr &&pkt) { return RecvLocal(*pkt); }); + if (!result.has_value()) { + RaiseIoHandlerError(result.error()); + return; + } } StartReceive(); } @@ -111,16 +152,27 @@ void tcp_client::StartReceive() void tcp_client::HandleSend(const asio::error_code &error, size_t bytesSent) { - // empty for now + if (error) + RaiseIoHandlerError(error.message()); } -void tcp_client::send(packet &pkt) +tl::expected tcp_client::send(packet &pkt) { - auto frame = std::make_unique(frame_queue::MakeFrame(pkt.Data())); - auto buf = asio::buffer(*frame); - asio::async_write(sock, buf, [this, frame = std::move(frame)](const asio::error_code &error, size_t bytesSent) { + tl::expected frame = frame_queue::MakeFrame(pkt.Data()); + if (!frame.has_value()) + return tl::make_unexpected(frame.error()); + std::unique_ptr framePtr = std::make_unique(*frame); + asio::mutable_buffer buf = asio::buffer(*framePtr); + asio::async_write(sock, buf, [this, frame = std::move(framePtr)](const asio::error_code &error, size_t bytesSent) { HandleSend(error, bytesSent); }); + return {}; +} + +void tcp_client::DisconnectNet(plr_t plr) +{ + if (local_server != nullptr) + local_server->DisconnectNet(plr); } bool tcp_client::SNetLeaveGame(int type) @@ -138,8 +190,12 @@ std::string tcp_client::make_default_gamename() return std::string(sgOptions.Network.szBindAddress); } +void tcp_client::RaiseIoHandlerError(const PacketError &error) +{ + ioHandlerResult.emplace(error); +} + tcp_client::~tcp_client() = default; -} // namespace net -} // namespace devilution +} // namespace devilution::net diff --git a/Source/dvlnet/tcp_client.h b/Source/dvlnet/tcp_client.h index 8a06213e4e6..7a68072b72a 100644 --- a/Source/dvlnet/tcp_client.h +++ b/Source/dvlnet/tcp_client.h @@ -3,26 +3,36 @@ #include #include +// This header must be included before any 3DS code +// because 3DS SDK defines a macro with the same name +// as an fmt template parameter in some versions of fmt. +// See https://github.com/fmtlib/fmt/issues/3632 +// +// 3DS uses some custom ASIO code that transitively includes +// the 3DS SDK. +#include + #include #include #include #include +#include #include "dvlnet/base.h" #include "dvlnet/frame_queue.h" #include "dvlnet/packet.h" #include "dvlnet/tcp_server.h" -namespace devilution { -namespace net { +namespace devilution::net { class tcp_client : public base { public: int create(std::string addrstr) override; int join(std::string addrstr) override; - void poll() override; - void send(packet &pkt) override; + tl::expected poll() override; + tl::expected send(packet &pkt) override; + void DisconnectNet(plr_t plr) override; bool SNetLeaveGame(int type) override; @@ -42,10 +52,13 @@ class tcp_client : public base { asio::ip::tcp::socket sock = asio::ip::tcp::socket(ioc); std::unique_ptr local_server; // must be declared *after* ioc + std::optional ioHandlerResult; + void HandleReceive(const asio::error_code &error, size_t bytesRead); void StartReceive(); void HandleSend(const asio::error_code &error, size_t bytesSent); + + void RaiseIoHandlerError(const PacketError &error); }; -} // namespace net -} // namespace devilution +} // namespace devilution::net diff --git a/Source/dvlnet/tcp_server.cpp b/Source/dvlnet/tcp_server.cpp index 6cdb8b1cef8..66fef97287a 100644 --- a/Source/dvlnet/tcp_server.cpp +++ b/Source/dvlnet/tcp_server.cpp @@ -5,12 +5,13 @@ #include #include +#include + #include "dvlnet/base.h" #include "player.h" #include "utils/log.hpp" -namespace devilution { -namespace net { +namespace devilution::net { tcp_server::tcp_server(asio::io_context &ioc, const std::string &bindaddr, unsigned short port, packet_factory &pktfty) @@ -76,91 +77,129 @@ void tcp_server::HandleReceive(const scc &con, const asio::error_code &ec, con->recv_buffer.resize(bytesRead); con->recv_queue.Write(std::move(con->recv_buffer)); con->recv_buffer.resize(frame_queue::max_frame_size); - try { - while (con->recv_queue.PacketReady()) { - try { - auto pkt = pktfty.make_packet(con->recv_queue.ReadPacket()); - if (con->plr == PLR_BROADCAST) { - HandleReceiveNewPlayer(con, *pkt); - } else { - con->timeout = timeout_active; - HandleReceivePacket(*pkt); - } - } catch (dvlnet_exception &e) { - Log("Network error: {}", e.what()); + while (true) { + tl::expected ready = con->recv_queue.PacketReady(); + if (!ready.has_value()) { + Log("PacketReady: {}", ready.error().what()); + DropConnection(con); + return; + } + if (!*ready) + break; + tl::expected pktData = con->recv_queue.ReadPacket(); + if (!pktData.has_value()) { + Log("ReadPacket: {}", pktData.error().what()); + DropConnection(con); + return; + } + tl::expected, PacketError> pkt = pktfty.make_packet(*pktData); + if (!pkt.has_value()) { + Log("make_packet: {}", pkt.error().what()); + DropConnection(con); + return; + } + if (con->plr == PLR_BROADCAST) { + tl::expected result = HandleReceiveNewPlayer(con, **pkt); + if (!result.has_value()) { + Log("HandleReceiveNewPlayer: {}", result.error().what()); + DropConnection(con); + return; + } + } else { + con->timeout = timeout_active; + tl::expected result = HandleReceivePacket(**pkt); + if (!result.has_value()) { + Log("Network error: {}", result.error().what()); DropConnection(con); return; } } - } catch (frame_queue_exception &e) { - Log("Invalid packet: {}", e.what()); - DropConnection(con); - return; } StartReceive(con); } -void tcp_server::HandleReceiveNewPlayer(const scc &con, packet &pkt) +tl::expected tcp_server::HandleReceiveNewPlayer(const scc &con, packet &inPkt) { auto newplr = NextFree(); if (newplr == PLR_BROADCAST) - throw server_exception(); + return tl::make_unexpected(ServerError()); - if (Empty()) - game_init_info = pkt.Info(); + if (Empty()) { + tl::expected pktInfo = inPkt.Info(); + if (!pktInfo.has_value()) + return tl::make_unexpected(pktInfo.error()); + game_init_info = **pktInfo; + } for (plr_t player = 0; player < Players.size(); player++) { if (connections[player]) { - auto playerPacket = pktfty.make_packet(PLR_MASTER, PLR_BROADCAST, newplr); - StartSend(connections[player], *playerPacket); - - auto newplrPacket = pktfty.make_packet(PLR_MASTER, PLR_BROADCAST, player); - StartSend(con, *newplrPacket); + tl::expected result + = pktfty.make_packet(PLR_MASTER, PLR_BROADCAST, newplr) + .and_then([&](std::unique_ptr &&pkt) { return StartSend(connections[player], *pkt); }) + .and_then([&]() { return pktfty.make_packet(PLR_MASTER, PLR_BROADCAST, player); }) + .and_then([&](std::unique_ptr &&pkt) { return StartSend(con, *pkt); }); + if (!result.has_value()) + return result; } } - auto reply = pktfty.make_packet(PLR_MASTER, PLR_BROADCAST, - pkt.Cookie(), newplr, - game_init_info); - StartSend(con, *reply); + tl::expected result + = inPkt.Cookie() + .and_then([&](cookie_t &&cookie) { return pktfty.make_packet(PLR_MASTER, PLR_BROADCAST, cookie, newplr, game_init_info); }) + .and_then([&](std::unique_ptr &&pkt) { return StartSend(con, *pkt); }); + if (!result.has_value()) + return result; con->plr = newplr; connections[newplr] = con; con->timeout = timeout_active; + return {}; } -void tcp_server::HandleReceivePacket(packet &pkt) +tl::expected tcp_server::HandleReceivePacket(packet &pkt) { - SendPacket(pkt); + return SendPacket(pkt); } -void tcp_server::SendPacket(packet &pkt) +tl::expected tcp_server::SendPacket(packet &pkt) { if (pkt.Destination() == PLR_BROADCAST) { - for (size_t i = 0; i < Players.size(); ++i) - if (i != pkt.Source() && connections[i]) - StartSend(connections[i], pkt); - } else { - if (pkt.Destination() >= MAX_PLRS) - throw server_exception(); - if ((pkt.Destination() != pkt.Source()) && connections[pkt.Destination()]) - StartSend(connections[pkt.Destination()], pkt); + for (size_t i = 0; i < Players.size(); ++i) { + if (i == pkt.Source() || !connections[i]) + continue; + tl::expected result = StartSend(connections[i], pkt); + if (!result.has_value()) + LogError("Failed to send packet {} to player {}: {}", static_cast(pkt.Type()), i, result.error().what()); + } + return {}; } + if (pkt.Destination() >= MAX_PLRS) + return tl::make_unexpected(ServerError()); + if (pkt.Destination() == pkt.Source() || !connections[pkt.Destination()]) + return {}; + return StartSend(connections[pkt.Destination()], pkt); } -void tcp_server::StartSend(const scc &con, packet &pkt) +tl::expected tcp_server::StartSend(const scc &con, packet &pkt) { - auto frame = std::make_unique(frame_queue::MakeFrame(pkt.Data())); - auto buf = asio::buffer(*frame); + tl::expected frame = frame_queue::MakeFrame(pkt.Data()); + if (!frame.has_value()) + return tl::make_unexpected(frame.error()); + std::unique_ptr framePtr = std::make_unique(*frame); + asio::mutable_buffer buf = asio::buffer(*framePtr); asio::async_write(con->socket, buf, - [this, con, frame = std::move(frame)](const asio::error_code &ec, size_t bytesSent) { + [this, con, frame = std::move(framePtr)](const asio::error_code &ec, size_t bytesSent) { HandleSend(con, ec, bytesSent); }); + return {}; } void tcp_server::HandleSend(const scc &con, const asio::error_code &ec, size_t bytesSent) { - // empty for now + if (ec) { + Log("Network error: {}", ec.message()); + DropConnection(con); + } } void tcp_server::StartAccept() @@ -173,13 +212,19 @@ void tcp_server::StartAccept() void tcp_server::HandleAccept(const scc &con, const asio::error_code &ec) { - if (ec) + if (ec) { + PacketError packetError = IoHandlerError(ec.message()); + RaiseIoHandlerError(packetError); return; + } if (NextFree() == PLR_BROADCAST) { DropConnection(con); } else { + asio::error_code errorCode; asio::ip::tcp::no_delay option(true); - con->socket.set_option(option); + con->socket.set_option(option, errorCode); + if (errorCode) + LogError("Server error setting socket option: {}", errorCode.message()); con->timeout = timeout_connect; StartReceive(con); StartTimeout(con); @@ -212,16 +257,46 @@ void tcp_server::HandleTimeout(const scc &con, const asio::error_code &ec) void tcp_server::DropConnection(const scc &con) { - if (con->plr != PLR_BROADCAST) { - auto pkt = pktfty.make_packet(PLR_MASTER, PLR_BROADCAST, - con->plr, LEAVE_DROP); - connections[con->plr] = nullptr; - SendPacket(*pkt); - // TODO: investigate if it is really ok for the server to - // drop a client directly. + plr_t plr = con->plr; + con->timer.cancel(); + con->socket.close(); + if (plr == PLR_BROADCAST) { + return; } + connections[plr] = nullptr; + + tl::expected, PacketError> pkt + = pktfty.make_packet(PLR_MASTER, PLR_BROADCAST, + plr, LEAVE_DROP); + if (pkt.has_value()) { + SendPacket(**pkt); + } else { + LogError("make_packet: {}", pkt.error().what()); + } +} + +void tcp_server::RaiseIoHandlerError(const PacketError &error) +{ + ioHandlerResult.emplace(error); +} + +tl::expected tcp_server::CheckIoHandlerError() +{ + if (ioHandlerResult == std::nullopt) + return {}; + tl::expected packetError = tl::make_unexpected(*ioHandlerResult); + ioHandlerResult = std::nullopt; + return packetError; +} + +void tcp_server::DisconnectNet(plr_t plr) +{ + scc &con = connections[plr]; + if (con == nullptr) + return; con->timer.cancel(); con->socket.close(); + con = nullptr; } void tcp_server::Close() @@ -232,5 +307,4 @@ void tcp_server::Close() tcp_server::~tcp_server() = default; -} // namespace net -} // namespace devilution +} // namespace devilution::net diff --git a/Source/dvlnet/tcp_server.h b/Source/dvlnet/tcp_server.h index d56fdce8c72..4827cbadbfc 100644 --- a/Source/dvlnet/tcp_server.h +++ b/Source/dvlnet/tcp_server.h @@ -4,32 +4,42 @@ #include #include +// This header must be included before any 3DS code +// because 3DS SDK defines a macro with the same name +// as an fmt template parameter in some versions of fmt. +// See https://github.com/fmtlib/fmt/issues/3632 +// +// 3DS uses some custom ASIO code that transitively includes +// the 3DS SDK. +#include + +#include + #include #include #include #include +#include #include "dvlnet/abstract_net.h" #include "dvlnet/frame_queue.h" #include "dvlnet/packet.h" #include "multi.h" -namespace devilution { -namespace net { +namespace devilution::net { -class server_exception : public dvlnet_exception { -public: - const char *what() const throw() override - { - return "Invalid player ID"; - } -}; +inline PacketError ServerError() +{ + return PacketError("Invalid player ID"); +} class tcp_server { public: tcp_server(asio::io_context &ioc, const std::string &bindaddr, unsigned short port, packet_factory &pktfty); std::string LocalhostSelf(); + tl::expected CheckIoHandlerError(); + void DisconnectNet(plr_t plr); void Close(); virtual ~tcp_server(); @@ -59,6 +69,8 @@ class tcp_server { std::array connections; buffer_t game_init_info; + std::optional ioHandlerResult; + scc MakeConnection(); plr_t NextFree(); bool Empty(); @@ -66,15 +78,15 @@ class tcp_server { void HandleAccept(const scc &con, const asio::error_code &ec); void StartReceive(const scc &con); void HandleReceive(const scc &con, const asio::error_code &ec, size_t bytesRead); - void HandleReceiveNewPlayer(const scc &con, packet &pkt); - void HandleReceivePacket(packet &pkt); - void SendPacket(packet &pkt); - void StartSend(const scc &con, packet &pkt); + tl::expected HandleReceiveNewPlayer(const scc &con, packet &pkt); + tl::expected HandleReceivePacket(packet &pkt); + tl::expected SendPacket(packet &pkt); + tl::expected StartSend(const scc &con, packet &pkt); void HandleSend(const scc &con, const asio::error_code &ec, size_t bytesSent); void StartTimeout(const scc &con); void HandleTimeout(const scc &con, const asio::error_code &ec); void DropConnection(const scc &con); + void RaiseIoHandlerError(const PacketError &error); }; -} // namespace net -} // namespace devilution +} // namespace devilution::net diff --git a/Source/dvlnet/zerotier_native.cpp b/Source/dvlnet/zerotier_native.cpp index 791250151ea..921b13a78c9 100644 --- a/Source/dvlnet/zerotier_native.cpp +++ b/Source/dvlnet/zerotier_native.cpp @@ -2,6 +2,7 @@ #include #include +#include #ifdef USE_SDL1 #include "utils/sdl2_to_1_2_backports.h" @@ -9,7 +10,7 @@ #include "utils/sdl2_backports.h" #endif -#if (defined(_WIN64) || defined(_WIN32)) && !defined(NXDK) +#if defined(_WIN32) && !defined(DEVILUTIONX_WINDOWS_NO_WCHAR) #include "utils/stdcompat/filesystem.hpp" #ifdef DVL_HAS_FILESYSTEM #define DVL_ZT_SYMLINK @@ -27,6 +28,7 @@ #include #include +#include "utils/algorithm/container.hpp" #include "utils/log.hpp" #include "utils/paths.h" @@ -37,13 +39,22 @@ namespace net { namespace { +// static constexpr uint64_t zt_earth = 0x8056c2e21c000001; +constexpr uint64_t ZtNetwork = 0xa84ac5c10a7ebb5f; + +std::atomic_bool zt_network_ready(false); +std::atomic_bool zt_node_online(false); +std::atomic_bool zt_joined(false); + +std::unordered_map ztPeerEvents; + #ifdef DVL_ZT_SYMLINK -bool HasMultiByteChars(string_view path) +bool HasMultiByteChars(std::string_view path) { - return std::any_of(path.begin(), path.end(), IsTrailUtf8CodeUnit); + return c_any_of(path, IsTrailUtf8CodeUnit); } -std::string ComputeAlternateFolderName(string_view path) +std::string ComputeAlternateFolderName(std::string_view path) { const size_t hashSize = crypto_generichash_BYTES; unsigned char hash[hashSize]; @@ -58,7 +69,7 @@ std::string ComputeAlternateFolderName(string_view path) return fmt::format("{:02x}", fmt::join(hash, "")); } -std::string ToZTCompliantPath(string_view configPath) +std::string ToZTCompliantPath(std::string_view configPath) { if (!HasMultiByteChars(configPath)) return std::string(configPath); @@ -106,38 +117,48 @@ std::string ToZTCompliantPath(string_view configPath) } #endif -} // namespace - -// static constexpr uint64_t zt_earth = 0x8056c2e21c000001; -static constexpr uint64_t ZtNetwork = 0xa84ac5c10a7ebb5f; - -static std::atomic_bool zt_network_ready(false); -static std::atomic_bool zt_node_online(false); -static std::atomic_bool zt_joined(false); - -static void Callback(void *ptr) +void Callback(void *ptr) { zts_event_msg_t *msg = reinterpret_cast(ptr); - // printf("callback %i\n", msg->eventCode); - if (msg->event_code == ZTS_EVENT_NODE_ONLINE) { + + switch (msg->event_code) { + case ZTS_EVENT_NODE_ONLINE: Log("ZeroTier: ZTS_EVENT_NODE_ONLINE, nodeId={:x}", (unsigned long long)msg->node->node_id); zt_node_online = true; if (!zt_joined) { zts_net_join(ZtNetwork); zt_joined = true; } - } else if (msg->event_code == ZTS_EVENT_NODE_OFFLINE) { + break; + + case ZTS_EVENT_NODE_OFFLINE: Log("ZeroTier: ZTS_EVENT_NODE_OFFLINE"); zt_node_online = false; - } else if (msg->event_code == ZTS_EVENT_NETWORK_READY_IP6) { + break; + + case ZTS_EVENT_NETWORK_READY_IP6: Log("ZeroTier: ZTS_EVENT_NETWORK_READY_IP6, networkId={:x}", (unsigned long long)msg->network->net_id); zt_ip6setup(); zt_network_ready = true; - } else if (msg->event_code == ZTS_EVENT_ADDR_ADDED_IP6) { + break; + + case ZTS_EVENT_ADDR_ADDED_IP6: print_ip6_addr(&(msg->addr->addr)); + break; + + case ZTS_EVENT_PEER_DIRECT: + case ZTS_EVENT_PEER_RELAY: + ztPeerEvents[msg->peer->peer_id] = static_cast(msg->event_code); + break; + + case ZTS_EVENT_PEER_PATH_DEAD: + ztPeerEvents.erase(msg->peer->peer_id); + break; } } +} // namespace + bool zerotier_network_ready() { return zt_network_ready && zt_node_online; @@ -155,5 +176,20 @@ void zerotier_network_start() zts_node_start(); } +bool zerotier_is_relayed(uint64_t mac) +{ + bool isRelayed = true; + if (zts_core_lock_obtain() != ZTS_ERR_OK) + return isRelayed; + zts_peer_info_t peerInfo; + if (zts_core_query_peer_info(ZtNetwork, mac, &peerInfo) == ZTS_ERR_OK) { + auto peerEvent = ztPeerEvents.find(peerInfo.peer_id); + if (peerEvent != ztPeerEvents.end()) + isRelayed = (peerEvent->second == ZTS_EVENT_PEER_RELAY); + } + zts_core_lock_release(); + return isRelayed; +} + } // namespace net } // namespace devilution diff --git a/Source/dvlnet/zerotier_native.h b/Source/dvlnet/zerotier_native.h index 3febbe6ed0c..48149e16db0 100644 --- a/Source/dvlnet/zerotier_native.h +++ b/Source/dvlnet/zerotier_native.h @@ -1,11 +1,13 @@ #pragma once +#include + namespace devilution { namespace net { bool zerotier_network_ready(); void zerotier_network_start(); -void zerotier_network_stop(); +bool zerotier_is_relayed(uint64_t mac); // NOTE: We have patched our libzt to have the corresponding multicast // MAC hardcoded, since libzt is still missing the proper handling. diff --git a/Source/effects.cpp b/Source/effects.cpp index 5bca71ed403..1683947b4be 100644 --- a/Source/effects.cpp +++ b/Source/effects.cpp @@ -6,20 +6,24 @@ #include "effects.h" #include +#include +#include + +#include "data/file.hpp" +#include "data/iterators.hpp" +#include "data/record_reader.hpp" #include "engine/random.hpp" #include "engine/sound.h" #include "engine/sound_defs.hpp" #include "engine/sound_position.hpp" #include "init.h" #include "player.h" -#include "utils/stdcompat/algorithm.hpp" -#include "utils/str_cat.hpp" namespace devilution { int sfxdelay; -_sfx_id sfxdnum = SFX_NONE; +SfxID sfxdnum = SfxID::None; namespace { @@ -32,942 +36,8 @@ constexpr bool AllowStreaming = false; /** Specifies the sound file and the playback state of the current sound effect. */ TSFX *sgpStreamSFX = nullptr; -/* data */ /** List of all sounds, except monsters and music */ -TSFX sgSFX[] = { - // clang-format off -// _sfx_id bFlags pszName pSnd -/*PS_WALK1*/ { sfx_MISC, "sfx\\misc\\walk1.wav", nullptr }, -/*PS_BFIRE*/ { sfx_MISC, "sfx\\misc\\bfire.wav", nullptr }, -/*PS_TMAG*/ { sfx_MISC, "sfx\\misc\\tmag.wav", nullptr }, -/*PS_SWING*/ { sfx_MISC, "sfx\\misc\\swing.wav", nullptr }, -/*PS_SWING2*/ { sfx_MISC, "sfx\\misc\\swing2.wav", nullptr }, -/*PS_DEAD*/ { sfx_MISC, "sfx\\misc\\dead.wav", nullptr }, -/*IS_STING1*/ { sfx_MISC | sfx_HELLFIRE, "sfx\\misc\\sting1.wav", nullptr }, -/*IS_FBALLBOW*/ { sfx_MISC | sfx_HELLFIRE, "sfx\\misc\\fballbow.wav", nullptr }, -/*IS_QUESTDN*/ { sfx_STREAM, "sfx\\misc\\questdon.wav", nullptr }, -/*IS_BARLFIRE*/ { sfx_MISC, "sfx\\items\\barlfire.wav", nullptr }, -/*IS_BARREL*/ { sfx_MISC, "sfx\\items\\barrel.wav", nullptr }, -/*IS_POPPOP8*/ { sfx_MISC | sfx_HELLFIRE, "sfx\\items\\podpop8.wav", nullptr }, -/*IS_POPPOP5*/ { sfx_MISC | sfx_HELLFIRE, "sfx\\items\\podpop5.wav", nullptr }, -/*IS_POPPOP3*/ { sfx_MISC | sfx_HELLFIRE, "sfx\\items\\urnpop3.wav", nullptr }, -/*IS_POPPOP2*/ { sfx_MISC | sfx_HELLFIRE, "sfx\\items\\urnpop2.wav", nullptr }, -/*IS_BHIT*/ { sfx_MISC, "sfx\\items\\bhit.wav", nullptr }, -/*IS_BHIT1*/ { sfx_MISC, "sfx\\items\\bhit1.wav", nullptr }, -/*IS_CHEST*/ { sfx_MISC, "sfx\\items\\chest.wav", nullptr }, -/*IS_DOORCLOS*/ { sfx_MISC, "sfx\\items\\doorclos.wav", nullptr }, -/*IS_DOOROPEN*/ { sfx_MISC, "sfx\\items\\dooropen.wav", nullptr }, -/*IS_FANVL*/ { sfx_MISC, "sfx\\items\\flipanvl.wav", nullptr }, -/*IS_FAXE*/ { sfx_MISC, "sfx\\items\\flipaxe.wav", nullptr }, -/*IS_FBLST*/ { sfx_MISC, "sfx\\items\\flipblst.wav", nullptr }, -/*IS_FBODY*/ { sfx_MISC, "sfx\\items\\flipbody.wav", nullptr }, -/*IS_FBOOK*/ { sfx_MISC, "sfx\\items\\flipbook.wav", nullptr }, -/*IS_FBOW*/ { sfx_MISC, "sfx\\items\\flipbow.wav", nullptr }, -/*IS_FCAP*/ { sfx_MISC, "sfx\\items\\flipcap.wav", nullptr }, -/*IS_FHARM*/ { sfx_MISC, "sfx\\items\\flipharm.wav", nullptr }, -/*IS_FLARM*/ { sfx_MISC, "sfx\\items\\fliplarm.wav", nullptr }, -/*IS_FMUSH*/ { sfx_MISC, "sfx\\items\\flipmush.wav", nullptr }, -/*IS_FPOT*/ { sfx_MISC, "sfx\\items\\flippot.wav", nullptr }, -/*IS_FRING*/ { sfx_MISC, "sfx\\items\\flipring.wav", nullptr }, -/*IS_FROCK*/ { sfx_MISC, "sfx\\items\\fliprock.wav", nullptr }, -/*IS_FSCRL*/ { sfx_MISC, "sfx\\items\\flipscrl.wav", nullptr }, -/*IS_FSHLD*/ { sfx_MISC, "sfx\\items\\flipshld.wav", nullptr }, -/*IS_FSIGN*/ { sfx_MISC, "sfx\\items\\flipsign.wav", nullptr }, -/*IS_FSTAF*/ { sfx_MISC, "sfx\\items\\flipstaf.wav", nullptr }, -/*IS_FSWOR*/ { sfx_MISC, "sfx\\items\\flipswor.wav", nullptr }, -/*IS_GOLD*/ { sfx_MISC, "sfx\\items\\gold.wav", nullptr }, -/*IS_IANVL*/ { sfx_MISC, "sfx\\items\\invanvl.wav", nullptr }, -/*IS_IAXE*/ { sfx_MISC, "sfx\\items\\invaxe.wav", nullptr }, -/*IS_IBLST*/ { sfx_MISC, "sfx\\items\\invblst.wav", nullptr }, -/*IS_IBODY*/ { sfx_MISC, "sfx\\items\\invbody.wav", nullptr }, -/*IS_IBOOK*/ { sfx_MISC, "sfx\\items\\invbook.wav", nullptr }, -/*IS_IBOW*/ { sfx_MISC, "sfx\\items\\invbow.wav", nullptr }, -/*IS_ICAP*/ { sfx_MISC, "sfx\\items\\invcap.wav", nullptr }, -/*IS_IGRAB*/ { sfx_MISC, "sfx\\items\\invgrab.wav", nullptr }, -/*IS_IHARM*/ { sfx_MISC, "sfx\\items\\invharm.wav", nullptr }, -/*IS_ILARM*/ { sfx_MISC, "sfx\\items\\invlarm.wav", nullptr }, -/*IS_IMUSH*/ { sfx_MISC, "sfx\\items\\invmush.wav", nullptr }, -/*IS_IPOT*/ { sfx_MISC, "sfx\\items\\invpot.wav", nullptr }, -/*IS_IRING*/ { sfx_MISC, "sfx\\items\\invring.wav", nullptr }, -/*IS_IROCK*/ { sfx_MISC, "sfx\\items\\invrock.wav", nullptr }, -/*IS_ISCROL*/ { sfx_MISC, "sfx\\items\\invscrol.wav", nullptr }, -/*IS_ISHIEL*/ { sfx_MISC, "sfx\\items\\invshiel.wav", nullptr }, -/*IS_ISIGN*/ { sfx_MISC, "sfx\\items\\invsign.wav", nullptr }, -/*IS_ISTAF*/ { sfx_MISC, "sfx\\items\\invstaf.wav", nullptr }, -/*IS_ISWORD*/ { sfx_MISC, "sfx\\items\\invsword.wav", nullptr }, -/*IS_LEVER*/ { sfx_MISC, "sfx\\items\\lever.wav", nullptr }, -/*IS_MAGIC*/ { sfx_MISC, "sfx\\items\\magic.wav", nullptr }, -/*IS_MAGIC1*/ { sfx_MISC, "sfx\\items\\magic1.wav", nullptr }, -/*IS_RBOOK*/ { sfx_MISC, "sfx\\items\\readbook.wav", nullptr }, -/*IS_SARC*/ { sfx_MISC, "sfx\\items\\sarc.wav", nullptr }, -/*IS_TITLEMOV*/ { sfx_UI, "sfx\\items\\titlemov.wav", nullptr }, -/*IS_TITLSLCT*/ { sfx_UI, "sfx\\items\\titlslct.wav", nullptr }, -/*IS_TRAP*/ { sfx_MISC, "sfx\\items\\trap.wav", nullptr }, -/*IS_CAST2*/ { sfx_MISC, "sfx\\misc\\cast2.wav", nullptr }, -/*IS_CAST4*/ { sfx_MISC, "sfx\\misc\\cast4.wav", nullptr }, -/*IS_CAST6*/ { sfx_MISC, "sfx\\misc\\cast6.wav", nullptr }, -/*IS_CAST7*/ { sfx_MISC, "sfx\\misc\\cast7.wav", nullptr }, -/*IS_CAST8*/ { sfx_MISC, "sfx\\misc\\cast8.wav", nullptr }, -/*IS_REPAIR*/ { sfx_MISC, "sfx\\misc\\repair.wav", nullptr }, -/*LS_ACID*/ { sfx_MISC, "sfx\\misc\\acids1.wav", nullptr }, -/*LS_ACIDS*/ { sfx_MISC, "sfx\\misc\\acids2.wav", nullptr }, -/*LS_APOC*/ { sfx_MISC, "sfx\\misc\\apoc.wav", nullptr }, -/*LS_BLODSTAR*/ { sfx_MISC, "sfx\\misc\\blodstar.wav", nullptr }, -/*LS_BLSIMPT*/ { sfx_MISC, "sfx\\misc\\blsimpt.wav", nullptr }, -/*LS_BONESP*/ { sfx_MISC, "sfx\\misc\\bonesp.wav", nullptr }, -/*LS_BSIMPCT*/ { sfx_MISC, "sfx\\misc\\bsimpct.wav", nullptr }, -/*LS_CALDRON*/ { sfx_MISC, "sfx\\misc\\caldron.wav", nullptr }, -/*LS_CBOLT*/ { sfx_MISC, "sfx\\misc\\cbolt.wav", nullptr }, -/*LS_DSERP*/ { sfx_MISC, "sfx\\misc\\dserp.wav", nullptr }, -/*LS_ELECIMP1*/ { sfx_MISC, "sfx\\misc\\elecimp1.wav", nullptr }, -/*LS_ELEMENTL*/ { sfx_MISC, "sfx\\misc\\elementl.wav", nullptr }, -/*LS_ETHEREAL*/ { sfx_MISC, "sfx\\misc\\ethereal.wav", nullptr }, -/*LS_FBOLT1*/ { sfx_MISC, "sfx\\misc\\fbolt1.wav", nullptr }, -/*LS_FIRIMP2*/ { sfx_MISC, "sfx\\misc\\firimp2.wav", nullptr }, -/*LS_FLAMWAVE*/ { sfx_MISC, "sfx\\misc\\flamwave.wav", nullptr }, -/*LS_FOUNTAIN*/ { sfx_MISC, "sfx\\misc\\fountain.wav", nullptr }, -/*LS_GOLUM*/ { sfx_MISC, "sfx\\misc\\golum.wav", nullptr }, -/*LS_GSHRINE*/ { sfx_MISC, "sfx\\misc\\gshrine.wav", nullptr }, -/*LS_GUARD*/ { sfx_MISC, "sfx\\misc\\guard.wav", nullptr }, -/*LS_GUARDLAN*/ { sfx_MISC, "sfx\\misc\\grdlanch.wav", nullptr }, -/*LS_HOLYBOLT*/ { sfx_MISC, "sfx\\misc\\holybolt.wav", nullptr }, -/*LS_INFRAVIS*/ { sfx_MISC, "sfx\\misc\\infravis.wav", nullptr }, -/*LS_INVISIBL*/ { sfx_MISC, "sfx\\misc\\invisibl.wav", nullptr }, -/*LS_LNING1*/ { sfx_MISC, "sfx\\misc\\lning1.wav", nullptr }, -/*LS_MSHIELD*/ { sfx_MISC, "sfx\\misc\\mshield.wav", nullptr }, -/*LS_NESTXPLD*/ { sfx_MISC | sfx_HELLFIRE, "sfx\\misc\\nestxpld.wav", nullptr }, -/*LS_NOVA*/ { sfx_MISC, "sfx\\misc\\nova.wav", nullptr }, -/*LS_PUDDLE*/ { sfx_MISC, "sfx\\misc\\puddle.wav", nullptr }, -/*LS_RESUR*/ { sfx_MISC, "sfx\\misc\\resur.wav", nullptr }, -/*LS_SCURIMP*/ { sfx_MISC, "sfx\\misc\\scurimp.wav", nullptr }, -/*LS_SENTINEL*/ { sfx_MISC, "sfx\\misc\\sentinel.wav", nullptr }, -/*LS_SPOUTSTR*/ { sfx_MISC, "sfx\\misc\\spoutstr.wav", nullptr }, -/*LS_TRAPDIS*/ { sfx_MISC, "sfx\\misc\\trapdis.wav", nullptr }, -/*LS_TELEPORT*/ { sfx_MISC, "sfx\\misc\\teleport.wav", nullptr }, -/*LS_WALLLOOP*/ { sfx_MISC, "sfx\\misc\\wallloop.wav", nullptr }, -/*LS_LMAG*/ { sfx_MISC | sfx_HELLFIRE, "sfx\\misc\\lmag.wav", nullptr }, -/*TSFX_BMAID1*/ { sfx_STREAM, "sfx\\towners\\bmaid01.wav", nullptr }, -/*TSFX_BMAID2*/ { sfx_STREAM, "sfx\\towners\\bmaid02.wav", nullptr }, -/*TSFX_BMAID3*/ { sfx_STREAM, "sfx\\towners\\bmaid03.wav", nullptr }, -/*TSFX_BMAID4*/ { sfx_STREAM, "sfx\\towners\\bmaid04.wav", nullptr }, -/*TSFX_BMAID5*/ { sfx_STREAM, "sfx\\towners\\bmaid05.wav", nullptr }, -/*TSFX_BMAID6*/ { sfx_STREAM, "sfx\\towners\\bmaid06.wav", nullptr }, -/*TSFX_BMAID7*/ { sfx_STREAM, "sfx\\towners\\bmaid07.wav", nullptr }, -/*TSFX_BMAID8*/ { sfx_STREAM, "sfx\\towners\\bmaid08.wav", nullptr }, -/*TSFX_BMAID9*/ { sfx_STREAM, "sfx\\towners\\bmaid09.wav", nullptr }, -/*TSFX_BMAID10*/ { sfx_STREAM, "sfx\\towners\\bmaid10.wav", nullptr }, -/*TSFX_BMAID11*/ { sfx_STREAM, "sfx\\towners\\bmaid11.wav", nullptr }, -/*TSFX_BMAID12*/ { sfx_STREAM, "sfx\\towners\\bmaid12.wav", nullptr }, -/*TSFX_BMAID13*/ { sfx_STREAM, "sfx\\towners\\bmaid13.wav", nullptr }, -/*TSFX_BMAID14*/ { sfx_STREAM, "sfx\\towners\\bmaid14.wav", nullptr }, -/*TSFX_BMAID15*/ { sfx_STREAM, "sfx\\towners\\bmaid15.wav", nullptr }, -/*TSFX_BMAID16*/ { sfx_STREAM, "sfx\\towners\\bmaid16.wav", nullptr }, -/*TSFX_BMAID17*/ { sfx_STREAM, "sfx\\towners\\bmaid17.wav", nullptr }, -/*TSFX_BMAID18*/ { sfx_STREAM, "sfx\\towners\\bmaid18.wav", nullptr }, -/*TSFX_BMAID19*/ { sfx_STREAM, "sfx\\towners\\bmaid19.wav", nullptr }, -/*TSFX_BMAID20*/ { sfx_STREAM, "sfx\\towners\\bmaid20.wav", nullptr }, -/*TSFX_BMAID21*/ { sfx_STREAM, "sfx\\towners\\bmaid21.wav", nullptr }, -/*TSFX_BMAID22*/ { sfx_STREAM, "sfx\\towners\\bmaid22.wav", nullptr }, -/*TSFX_BMAID23*/ { sfx_STREAM, "sfx\\towners\\bmaid23.wav", nullptr }, -/*TSFX_BMAID24*/ { sfx_STREAM, "sfx\\towners\\bmaid24.wav", nullptr }, -/*TSFX_BMAID25*/ { sfx_STREAM, "sfx\\towners\\bmaid25.wav", nullptr }, -/*TSFX_BMAID26*/ { sfx_STREAM, "sfx\\towners\\bmaid26.wav", nullptr }, -/*TSFX_BMAID27*/ { sfx_STREAM, "sfx\\towners\\bmaid27.wav", nullptr }, -/*TSFX_BMAID28*/ { sfx_STREAM, "sfx\\towners\\bmaid28.wav", nullptr }, -/*TSFX_BMAID29*/ { sfx_STREAM, "sfx\\towners\\bmaid29.wav", nullptr }, -/*TSFX_BMAID30*/ { sfx_STREAM, "sfx\\towners\\bmaid30.wav", nullptr }, -/*TSFX_BMAID31*/ { sfx_STREAM, "sfx\\towners\\bmaid31.wav", nullptr }, -/*TSFX_BMAID32*/ { sfx_STREAM, "sfx\\towners\\bmaid32.wav", nullptr }, -/*TSFX_BMAID33*/ { sfx_STREAM, "sfx\\towners\\bmaid33.wav", nullptr }, -/*TSFX_BMAID34*/ { sfx_STREAM, "sfx\\towners\\bmaid34.wav", nullptr }, -/*TSFX_BMAID35*/ { sfx_STREAM, "sfx\\towners\\bmaid35.wav", nullptr }, -/*TSFX_BMAID36*/ { sfx_STREAM, "sfx\\towners\\bmaid36.wav", nullptr }, -/*TSFX_BMAID37*/ { sfx_STREAM, "sfx\\towners\\bmaid37.wav", nullptr }, -/*TSFX_BMAID38*/ { sfx_STREAM, "sfx\\towners\\bmaid38.wav", nullptr }, -/*TSFX_BMAID39*/ { sfx_STREAM, "sfx\\towners\\bmaid39.wav", nullptr }, -/*TSFX_BMAID40*/ { sfx_STREAM, "sfx\\towners\\bmaid40.wav", nullptr }, -/*TSFX_SMITH1*/ { sfx_STREAM, "sfx\\towners\\bsmith01.wav", nullptr }, -/*TSFX_SMITH2*/ { sfx_STREAM, "sfx\\towners\\bsmith02.wav", nullptr }, -/*TSFX_SMITH3*/ { sfx_STREAM, "sfx\\towners\\bsmith03.wav", nullptr }, -/*TSFX_SMITH4*/ { sfx_STREAM, "sfx\\towners\\bsmith04.wav", nullptr }, -/*TSFX_SMITH5*/ { sfx_STREAM, "sfx\\towners\\bsmith05.wav", nullptr }, -/*TSFX_SMITH6*/ { sfx_STREAM, "sfx\\towners\\bsmith06.wav", nullptr }, -/*TSFX_SMITH7*/ { sfx_STREAM, "sfx\\towners\\bsmith07.wav", nullptr }, -/*TSFX_SMITH8*/ { sfx_STREAM, "sfx\\towners\\bsmith08.wav", nullptr }, -/*TSFX_SMITH9*/ { sfx_STREAM, "sfx\\towners\\bsmith09.wav", nullptr }, -/*TSFX_SMITH10*/ { sfx_STREAM, "sfx\\towners\\bsmith10.wav", nullptr }, -/*TSFX_SMITH11*/ { sfx_STREAM, "sfx\\towners\\bsmith11.wav", nullptr }, -/*TSFX_SMITH12*/ { sfx_STREAM, "sfx\\towners\\bsmith12.wav", nullptr }, -/*TSFX_SMITH13*/ { sfx_STREAM, "sfx\\towners\\bsmith13.wav", nullptr }, -/*TSFX_SMITH14*/ { sfx_STREAM, "sfx\\towners\\bsmith14.wav", nullptr }, -/*TSFX_SMITH15*/ { sfx_STREAM, "sfx\\towners\\bsmith15.wav", nullptr }, -/*TSFX_SMITH16*/ { sfx_STREAM, "sfx\\towners\\bsmith16.wav", nullptr }, -/*TSFX_SMITH17*/ { sfx_STREAM, "sfx\\towners\\bsmith17.wav", nullptr }, -/*TSFX_SMITH18*/ { sfx_STREAM, "sfx\\towners\\bsmith18.wav", nullptr }, -/*TSFX_SMITH19*/ { sfx_STREAM, "sfx\\towners\\bsmith19.wav", nullptr }, -/*TSFX_SMITH20*/ { sfx_STREAM, "sfx\\towners\\bsmith20.wav", nullptr }, -/*TSFX_SMITH21*/ { sfx_STREAM, "sfx\\towners\\bsmith21.wav", nullptr }, -/*TSFX_SMITH22*/ { sfx_STREAM, "sfx\\towners\\bsmith22.wav", nullptr }, -/*TSFX_SMITH23*/ { sfx_STREAM, "sfx\\towners\\bsmith23.wav", nullptr }, -/*TSFX_SMITH24*/ { sfx_STREAM, "sfx\\towners\\bsmith24.wav", nullptr }, -/*TSFX_SMITH25*/ { sfx_STREAM, "sfx\\towners\\bsmith25.wav", nullptr }, -/*TSFX_SMITH26*/ { sfx_STREAM, "sfx\\towners\\bsmith26.wav", nullptr }, -/*TSFX_SMITH27*/ { sfx_STREAM, "sfx\\towners\\bsmith27.wav", nullptr }, -/*TSFX_SMITH28*/ { sfx_STREAM, "sfx\\towners\\bsmith28.wav", nullptr }, -/*TSFX_SMITH29*/ { sfx_STREAM, "sfx\\towners\\bsmith29.wav", nullptr }, -/*TSFX_SMITH30*/ { sfx_STREAM, "sfx\\towners\\bsmith30.wav", nullptr }, -/*TSFX_SMITH31*/ { sfx_STREAM, "sfx\\towners\\bsmith31.wav", nullptr }, -/*TSFX_SMITH32*/ { sfx_STREAM, "sfx\\towners\\bsmith32.wav", nullptr }, -/*TSFX_SMITH33*/ { sfx_STREAM, "sfx\\towners\\bsmith33.wav", nullptr }, -/*TSFX_SMITH34*/ { sfx_STREAM, "sfx\\towners\\bsmith34.wav", nullptr }, -/*TSFX_SMITH35*/ { sfx_STREAM, "sfx\\towners\\bsmith35.wav", nullptr }, -/*TSFX_SMITH36*/ { sfx_STREAM, "sfx\\towners\\bsmith36.wav", nullptr }, -/*TSFX_SMITH37*/ { sfx_STREAM, "sfx\\towners\\bsmith37.wav", nullptr }, -/*TSFX_SMITH38*/ { sfx_STREAM, "sfx\\towners\\bsmith38.wav", nullptr }, -/*TSFX_SMITH39*/ { sfx_STREAM, "sfx\\towners\\bsmith39.wav", nullptr }, -/*TSFX_SMITH40*/ { sfx_STREAM, "sfx\\towners\\bsmith40.wav", nullptr }, -/*TSFX_SMITH41*/ { sfx_STREAM, "sfx\\towners\\bsmith41.wav", nullptr }, -/*TSFX_SMITH42*/ { sfx_STREAM, "sfx\\towners\\bsmith42.wav", nullptr }, -/*TSFX_SMITH43*/ { sfx_STREAM, "sfx\\towners\\bsmith43.wav", nullptr }, -/*TSFX_SMITH44*/ { sfx_STREAM, "sfx\\towners\\bsmith44.wav", nullptr }, -/*TSFX_SMITH45*/ { sfx_STREAM, "sfx\\towners\\bsmith45.wav", nullptr }, -/*TSFX_SMITH46*/ { sfx_STREAM, "sfx\\towners\\bsmith46.wav", nullptr }, -/*TSFX_SMITH47*/ { sfx_STREAM, "sfx\\towners\\bsmith47.wav", nullptr }, -/*TSFX_SMITH48*/ { sfx_STREAM, "sfx\\towners\\bsmith48.wav", nullptr }, -/*TSFX_SMITH49*/ { sfx_STREAM, "sfx\\towners\\bsmith49.wav", nullptr }, -/*TSFX_SMITH50*/ { sfx_STREAM, "sfx\\towners\\bsmith50.wav", nullptr }, -/*TSFX_SMITH51*/ { sfx_STREAM, "sfx\\towners\\bsmith51.wav", nullptr }, -/*TSFX_SMITH52*/ { sfx_STREAM, "sfx\\towners\\bsmith52.wav", nullptr }, -/*TSFX_SMITH53*/ { sfx_STREAM, "sfx\\towners\\bsmith53.wav", nullptr }, -/*TSFX_SMITH54*/ { sfx_STREAM, "sfx\\towners\\bsmith54.wav", nullptr }, -/*TSFX_SMITH55*/ { sfx_STREAM, "sfx\\towners\\bsmith55.wav", nullptr }, -/*TSFX_SMITH56*/ { sfx_STREAM, "sfx\\towners\\bsmith56.wav", nullptr }, -/*TSFX_COW1*/ { sfx_MISC, "sfx\\towners\\cow1.wav", nullptr }, -/*TSFX_COW2*/ { sfx_MISC, "sfx\\towners\\cow2.wav", nullptr }, -// /*TSFX_COW3*/ { sfx_MISC, "sfx\\towners\\cow3.wav", nullptr }, -// /*TSFX_COW4*/ { sfx_MISC, "sfx\\towners\\cow4.wav", nullptr }, -// /*TSFX_COW5*/ { sfx_MISC, "sfx\\towners\\cow5.wav", nullptr }, -// /*TSFX_COW6*/ { sfx_MISC, "sfx\\towners\\cow6.wav", nullptr }, -/*TSFX_COW7*/ { sfx_MISC | sfx_HELLFIRE, "sfx\\towners\\cow7.wav", nullptr }, -/*TSFX_COW8*/ { sfx_MISC | sfx_HELLFIRE, "sfx\\towners\\cow8.wav", nullptr }, -/*TSFX_DEADGUY*/ { sfx_STREAM, "sfx\\towners\\deadguy2.wav", nullptr }, -/*TSFX_DRUNK1*/ { sfx_STREAM, "sfx\\towners\\drunk01.wav", nullptr }, -/*TSFX_DRUNK2*/ { sfx_STREAM, "sfx\\towners\\drunk02.wav", nullptr }, -/*TSFX_DRUNK3*/ { sfx_STREAM, "sfx\\towners\\drunk03.wav", nullptr }, -/*TSFX_DRUNK4*/ { sfx_STREAM, "sfx\\towners\\drunk04.wav", nullptr }, -/*TSFX_DRUNK5*/ { sfx_STREAM, "sfx\\towners\\drunk05.wav", nullptr }, -/*TSFX_DRUNK6*/ { sfx_STREAM, "sfx\\towners\\drunk06.wav", nullptr }, -/*TSFX_DRUNK7*/ { sfx_STREAM, "sfx\\towners\\drunk07.wav", nullptr }, -/*TSFX_DRUNK8*/ { sfx_STREAM, "sfx\\towners\\drunk08.wav", nullptr }, -/*TSFX_DRUNK9*/ { sfx_STREAM, "sfx\\towners\\drunk09.wav", nullptr }, -/*TSFX_DRUNK10*/ { sfx_STREAM, "sfx\\towners\\drunk10.wav", nullptr }, -/*TSFX_DRUNK11*/ { sfx_STREAM, "sfx\\towners\\drunk11.wav", nullptr }, -/*TSFX_DRUNK12*/ { sfx_STREAM, "sfx\\towners\\drunk12.wav", nullptr }, -/*TSFX_DRUNK13*/ { sfx_STREAM, "sfx\\towners\\drunk13.wav", nullptr }, -/*TSFX_DRUNK14*/ { sfx_STREAM, "sfx\\towners\\drunk14.wav", nullptr }, -/*TSFX_DRUNK15*/ { sfx_STREAM, "sfx\\towners\\drunk15.wav", nullptr }, -/*TSFX_DRUNK16*/ { sfx_STREAM, "sfx\\towners\\drunk16.wav", nullptr }, -/*TSFX_DRUNK17*/ { sfx_STREAM, "sfx\\towners\\drunk17.wav", nullptr }, -/*TSFX_DRUNK18*/ { sfx_STREAM, "sfx\\towners\\drunk18.wav", nullptr }, -/*TSFX_DRUNK19*/ { sfx_STREAM, "sfx\\towners\\drunk19.wav", nullptr }, -/*TSFX_DRUNK20*/ { sfx_STREAM, "sfx\\towners\\drunk20.wav", nullptr }, -/*TSFX_DRUNK21*/ { sfx_STREAM, "sfx\\towners\\drunk21.wav", nullptr }, -/*TSFX_DRUNK22*/ { sfx_STREAM, "sfx\\towners\\drunk22.wav", nullptr }, -/*TSFX_DRUNK23*/ { sfx_STREAM, "sfx\\towners\\drunk23.wav", nullptr }, -/*TSFX_DRUNK24*/ { sfx_STREAM, "sfx\\towners\\drunk24.wav", nullptr }, -/*TSFX_DRUNK25*/ { sfx_STREAM, "sfx\\towners\\drunk25.wav", nullptr }, -/*TSFX_DRUNK26*/ { sfx_STREAM, "sfx\\towners\\drunk26.wav", nullptr }, -/*TSFX_DRUNK27*/ { sfx_STREAM, "sfx\\towners\\drunk27.wav", nullptr }, -/*TSFX_DRUNK28*/ { sfx_STREAM, "sfx\\towners\\drunk28.wav", nullptr }, -/*TSFX_DRUNK29*/ { sfx_STREAM, "sfx\\towners\\drunk29.wav", nullptr }, -/*TSFX_DRUNK30*/ { sfx_STREAM, "sfx\\towners\\drunk30.wav", nullptr }, -/*TSFX_DRUNK31*/ { sfx_STREAM, "sfx\\towners\\drunk31.wav", nullptr }, -/*TSFX_DRUNK32*/ { sfx_STREAM, "sfx\\towners\\drunk32.wav", nullptr }, -/*TSFX_DRUNK33*/ { sfx_STREAM, "sfx\\towners\\drunk33.wav", nullptr }, -/*TSFX_DRUNK34*/ { sfx_STREAM, "sfx\\towners\\drunk34.wav", nullptr }, -/*TSFX_DRUNK35*/ { sfx_STREAM, "sfx\\towners\\drunk35.wav", nullptr }, -/*TSFX_HEALER1*/ { sfx_STREAM, "sfx\\towners\\healer01.wav", nullptr }, -/*TSFX_HEALER2*/ { sfx_STREAM, "sfx\\towners\\healer02.wav", nullptr }, -/*TSFX_HEALER3*/ { sfx_STREAM, "sfx\\towners\\healer03.wav", nullptr }, -/*TSFX_HEALER4*/ { sfx_STREAM, "sfx\\towners\\healer04.wav", nullptr }, -/*TSFX_HEALER5*/ { sfx_STREAM, "sfx\\towners\\healer05.wav", nullptr }, -/*TSFX_HEALER6*/ { sfx_STREAM, "sfx\\towners\\healer06.wav", nullptr }, -/*TSFX_HEALER7*/ { sfx_STREAM, "sfx\\towners\\healer07.wav", nullptr }, -/*TSFX_HEALER8*/ { sfx_STREAM, "sfx\\towners\\healer08.wav", nullptr }, -/*TSFX_HEALER9*/ { sfx_STREAM, "sfx\\towners\\healer09.wav", nullptr }, -/*TSFX_HEALER10*/ { sfx_STREAM, "sfx\\towners\\healer10.wav", nullptr }, -/*TSFX_HEALER11*/ { sfx_STREAM, "sfx\\towners\\healer11.wav", nullptr }, -/*TSFX_HEALER12*/ { sfx_STREAM, "sfx\\towners\\healer12.wav", nullptr }, -/*TSFX_HEALER13*/ { sfx_STREAM, "sfx\\towners\\healer13.wav", nullptr }, -/*TSFX_HEALER14*/ { sfx_STREAM, "sfx\\towners\\healer14.wav", nullptr }, -/*TSFX_HEALER15*/ { sfx_STREAM, "sfx\\towners\\healer15.wav", nullptr }, -/*TSFX_HEALER16*/ { sfx_STREAM, "sfx\\towners\\healer16.wav", nullptr }, -/*TSFX_HEALER17*/ { sfx_STREAM, "sfx\\towners\\healer17.wav", nullptr }, -/*TSFX_HEALER18*/ { sfx_STREAM, "sfx\\towners\\healer18.wav", nullptr }, -/*TSFX_HEALER19*/ { sfx_STREAM, "sfx\\towners\\healer19.wav", nullptr }, -/*TSFX_HEALER20*/ { sfx_STREAM, "sfx\\towners\\healer20.wav", nullptr }, -/*TSFX_HEALER21*/ { sfx_STREAM, "sfx\\towners\\healer21.wav", nullptr }, -/*TSFX_HEALER22*/ { sfx_STREAM, "sfx\\towners\\healer22.wav", nullptr }, -/*TSFX_HEALER23*/ { sfx_STREAM, "sfx\\towners\\healer23.wav", nullptr }, -/*TSFX_HEALER24*/ { sfx_STREAM, "sfx\\towners\\healer24.wav", nullptr }, -/*TSFX_HEALER25*/ { sfx_STREAM, "sfx\\towners\\healer25.wav", nullptr }, -/*TSFX_HEALER26*/ { sfx_STREAM, "sfx\\towners\\healer26.wav", nullptr }, -/*TSFX_HEALER27*/ { sfx_STREAM, "sfx\\towners\\healer27.wav", nullptr }, -/*TSFX_HEALER28*/ { sfx_STREAM, "sfx\\towners\\healer28.wav", nullptr }, -/*TSFX_HEALER29*/ { sfx_STREAM, "sfx\\towners\\healer29.wav", nullptr }, -/*TSFX_HEALER30*/ { sfx_STREAM, "sfx\\towners\\healer30.wav", nullptr }, -/*TSFX_HEALER31*/ { sfx_STREAM, "sfx\\towners\\healer31.wav", nullptr }, -/*TSFX_HEALER32*/ { sfx_STREAM, "sfx\\towners\\healer32.wav", nullptr }, -/*TSFX_HEALER33*/ { sfx_STREAM, "sfx\\towners\\healer33.wav", nullptr }, -/*TSFX_HEALER34*/ { sfx_STREAM, "sfx\\towners\\healer34.wav", nullptr }, -/*TSFX_HEALER35*/ { sfx_STREAM, "sfx\\towners\\healer35.wav", nullptr }, -/*TSFX_HEALER36*/ { sfx_STREAM, "sfx\\towners\\healer36.wav", nullptr }, -/*TSFX_HEALER37*/ { sfx_STREAM, "sfx\\towners\\healer37.wav", nullptr }, -/*TSFX_HEALER38*/ { sfx_STREAM, "sfx\\towners\\healer38.wav", nullptr }, -/*TSFX_HEALER39*/ { sfx_STREAM, "sfx\\towners\\healer39.wav", nullptr }, -/*TSFX_HEALER40*/ { sfx_STREAM, "sfx\\towners\\healer40.wav", nullptr }, -/*TSFX_HEALER41*/ { sfx_STREAM, "sfx\\towners\\healer41.wav", nullptr }, -/*TSFX_HEALER42*/ { sfx_STREAM, "sfx\\towners\\healer42.wav", nullptr }, -/*TSFX_HEALER43*/ { sfx_STREAM, "sfx\\towners\\healer43.wav", nullptr }, -/*TSFX_HEALER44*/ { sfx_STREAM, "sfx\\towners\\healer44.wav", nullptr }, -/*TSFX_HEALER45*/ { sfx_STREAM, "sfx\\towners\\healer45.wav", nullptr }, -/*TSFX_HEALER46*/ { sfx_STREAM, "sfx\\towners\\healer46.wav", nullptr }, -/*TSFX_HEALER47*/ { sfx_STREAM, "sfx\\towners\\healer47.wav", nullptr }, -/*TSFX_PEGBOY1*/ { sfx_STREAM, "sfx\\towners\\pegboy01.wav", nullptr }, -/*TSFX_PEGBOY2*/ { sfx_STREAM, "sfx\\towners\\pegboy02.wav", nullptr }, -/*TSFX_PEGBOY3*/ { sfx_STREAM, "sfx\\towners\\pegboy03.wav", nullptr }, -/*TSFX_PEGBOY4*/ { sfx_STREAM, "sfx\\towners\\pegboy04.wav", nullptr }, -/*TSFX_PEGBOY5*/ { sfx_STREAM, "sfx\\towners\\pegboy05.wav", nullptr }, -/*TSFX_PEGBOY6*/ { sfx_STREAM, "sfx\\towners\\pegboy06.wav", nullptr }, -/*TSFX_PEGBOY7*/ { sfx_STREAM, "sfx\\towners\\pegboy07.wav", nullptr }, -/*TSFX_PEGBOY8*/ { sfx_STREAM, "sfx\\towners\\pegboy08.wav", nullptr }, -/*TSFX_PEGBOY9*/ { sfx_STREAM, "sfx\\towners\\pegboy09.wav", nullptr }, -/*TSFX_PEGBOY10*/ { sfx_STREAM, "sfx\\towners\\pegboy10.wav", nullptr }, -/*TSFX_PEGBOY11*/ { sfx_STREAM, "sfx\\towners\\pegboy11.wav", nullptr }, -/*TSFX_PEGBOY12*/ { sfx_STREAM, "sfx\\towners\\pegboy12.wav", nullptr }, -/*TSFX_PEGBOY13*/ { sfx_STREAM, "sfx\\towners\\pegboy13.wav", nullptr }, -/*TSFX_PEGBOY14*/ { sfx_STREAM, "sfx\\towners\\pegboy14.wav", nullptr }, -/*TSFX_PEGBOY15*/ { sfx_STREAM, "sfx\\towners\\pegboy15.wav", nullptr }, -/*TSFX_PEGBOY16*/ { sfx_STREAM, "sfx\\towners\\pegboy16.wav", nullptr }, -/*TSFX_PEGBOY17*/ { sfx_STREAM, "sfx\\towners\\pegboy17.wav", nullptr }, -/*TSFX_PEGBOY18*/ { sfx_STREAM, "sfx\\towners\\pegboy18.wav", nullptr }, -/*TSFX_PEGBOY19*/ { sfx_STREAM, "sfx\\towners\\pegboy19.wav", nullptr }, -/*TSFX_PEGBOY20*/ { sfx_STREAM, "sfx\\towners\\pegboy20.wav", nullptr }, -/*TSFX_PEGBOY21*/ { sfx_STREAM, "sfx\\towners\\pegboy21.wav", nullptr }, -/*TSFX_PEGBOY22*/ { sfx_STREAM, "sfx\\towners\\pegboy22.wav", nullptr }, -/*TSFX_PEGBOY23*/ { sfx_STREAM, "sfx\\towners\\pegboy23.wav", nullptr }, -/*TSFX_PEGBOY24*/ { sfx_STREAM, "sfx\\towners\\pegboy24.wav", nullptr }, -/*TSFX_PEGBOY25*/ { sfx_STREAM, "sfx\\towners\\pegboy25.wav", nullptr }, -/*TSFX_PEGBOY26*/ { sfx_STREAM, "sfx\\towners\\pegboy26.wav", nullptr }, -/*TSFX_PEGBOY27*/ { sfx_STREAM, "sfx\\towners\\pegboy27.wav", nullptr }, -/*TSFX_PEGBOY28*/ { sfx_STREAM, "sfx\\towners\\pegboy28.wav", nullptr }, -/*TSFX_PEGBOY29*/ { sfx_STREAM, "sfx\\towners\\pegboy29.wav", nullptr }, -/*TSFX_PEGBOY30*/ { sfx_STREAM, "sfx\\towners\\pegboy30.wav", nullptr }, -/*TSFX_PEGBOY31*/ { sfx_STREAM, "sfx\\towners\\pegboy31.wav", nullptr }, -/*TSFX_PEGBOY32*/ { sfx_STREAM, "sfx\\towners\\pegboy32.wav", nullptr }, -/*TSFX_PEGBOY33*/ { sfx_STREAM, "sfx\\towners\\pegboy33.wav", nullptr }, -/*TSFX_PEGBOY34*/ { sfx_STREAM, "sfx\\towners\\pegboy34.wav", nullptr }, -/*TSFX_PEGBOY35*/ { sfx_STREAM, "sfx\\towners\\pegboy35.wav", nullptr }, -/*TSFX_PEGBOY36*/ { sfx_STREAM, "sfx\\towners\\pegboy36.wav", nullptr }, -/*TSFX_PEGBOY37*/ { sfx_STREAM, "sfx\\towners\\pegboy37.wav", nullptr }, -/*TSFX_PEGBOY38*/ { sfx_STREAM, "sfx\\towners\\pegboy38.wav", nullptr }, -/*TSFX_PEGBOY39*/ { sfx_STREAM, "sfx\\towners\\pegboy39.wav", nullptr }, -/*TSFX_PEGBOY40*/ { sfx_STREAM, "sfx\\towners\\pegboy40.wav", nullptr }, -/*TSFX_PEGBOY41*/ { sfx_STREAM, "sfx\\towners\\pegboy41.wav", nullptr }, -/*TSFX_PEGBOY42*/ { sfx_STREAM, "sfx\\towners\\pegboy42.wav", nullptr }, -/*TSFX_PEGBOY43*/ { sfx_STREAM, "sfx\\towners\\pegboy43.wav", nullptr }, -/*TSFX_PRIEST0*/ { sfx_STREAM, "sfx\\towners\\priest00.wav", nullptr }, -/*TSFX_PRIEST1*/ { sfx_STREAM, "sfx\\towners\\priest01.wav", nullptr }, -/*TSFX_PRIEST2*/ { sfx_STREAM, "sfx\\towners\\priest02.wav", nullptr }, -/*TSFX_PRIEST3*/ { sfx_STREAM, "sfx\\towners\\priest03.wav", nullptr }, -/*TSFX_PRIEST4*/ { sfx_STREAM, "sfx\\towners\\priest04.wav", nullptr }, -/*TSFX_PRIEST5*/ { sfx_STREAM, "sfx\\towners\\priest05.wav", nullptr }, -/*TSFX_PRIEST6*/ { sfx_STREAM, "sfx\\towners\\priest06.wav", nullptr }, -/*TSFX_PRIEST7*/ { sfx_STREAM, "sfx\\towners\\priest07.wav", nullptr }, -/*TSFX_STORY0*/ { sfx_STREAM, "sfx\\towners\\storyt00.wav", nullptr }, -/*TSFX_STORY1*/ { sfx_STREAM, "sfx\\towners\\storyt01.wav", nullptr }, -/*TSFX_STORY2*/ { sfx_STREAM, "sfx\\towners\\storyt02.wav", nullptr }, -/*TSFX_STORY3*/ { sfx_STREAM, "sfx\\towners\\storyt03.wav", nullptr }, -/*TSFX_STORY4*/ { sfx_STREAM, "sfx\\towners\\storyt04.wav", nullptr }, -/*TSFX_STORY5*/ { sfx_STREAM, "sfx\\towners\\storyt05.wav", nullptr }, -/*TSFX_STORY6*/ { sfx_STREAM, "sfx\\towners\\storyt06.wav", nullptr }, -/*TSFX_STORY7*/ { sfx_STREAM, "sfx\\towners\\storyt07.wav", nullptr }, -/*TSFX_STORY8*/ { sfx_STREAM, "sfx\\towners\\storyt08.wav", nullptr }, -/*TSFX_STORY9*/ { sfx_STREAM, "sfx\\towners\\storyt09.wav", nullptr }, -/*TSFX_STORY10*/ { sfx_STREAM, "sfx\\towners\\storyt10.wav", nullptr }, -/*TSFX_STORY11*/ { sfx_STREAM, "sfx\\towners\\storyt11.wav", nullptr }, -/*TSFX_STORY12*/ { sfx_STREAM, "sfx\\towners\\storyt12.wav", nullptr }, -/*TSFX_STORY13*/ { sfx_STREAM, "sfx\\towners\\storyt13.wav", nullptr }, -/*TSFX_STORY14*/ { sfx_STREAM, "sfx\\towners\\storyt14.wav", nullptr }, -/*TSFX_STORY15*/ { sfx_STREAM, "sfx\\towners\\storyt15.wav", nullptr }, -/*TSFX_STORY16*/ { sfx_STREAM, "sfx\\towners\\storyt16.wav", nullptr }, -/*TSFX_STORY17*/ { sfx_STREAM, "sfx\\towners\\storyt17.wav", nullptr }, -/*TSFX_STORY18*/ { sfx_STREAM, "sfx\\towners\\storyt18.wav", nullptr }, -/*TSFX_STORY19*/ { sfx_STREAM, "sfx\\towners\\storyt19.wav", nullptr }, -/*TSFX_STORY20*/ { sfx_STREAM, "sfx\\towners\\storyt20.wav", nullptr }, -/*TSFX_STORY21*/ { sfx_STREAM, "sfx\\towners\\storyt21.wav", nullptr }, -/*TSFX_STORY22*/ { sfx_STREAM, "sfx\\towners\\storyt22.wav", nullptr }, -/*TSFX_STORY23*/ { sfx_STREAM, "sfx\\towners\\storyt23.wav", nullptr }, -/*TSFX_STORY24*/ { sfx_STREAM, "sfx\\towners\\storyt24.wav", nullptr }, -/*TSFX_STORY25*/ { sfx_STREAM, "sfx\\towners\\storyt25.wav", nullptr }, -/*TSFX_STORY26*/ { sfx_STREAM, "sfx\\towners\\storyt26.wav", nullptr }, -/*TSFX_STORY27*/ { sfx_STREAM, "sfx\\towners\\storyt27.wav", nullptr }, -/*TSFX_STORY28*/ { sfx_STREAM, "sfx\\towners\\storyt28.wav", nullptr }, -/*TSFX_STORY29*/ { sfx_STREAM, "sfx\\towners\\storyt29.wav", nullptr }, -/*TSFX_STORY30*/ { sfx_STREAM, "sfx\\towners\\storyt30.wav", nullptr }, -/*TSFX_STORY31*/ { sfx_STREAM, "sfx\\towners\\storyt31.wav", nullptr }, -/*TSFX_STORY32*/ { sfx_STREAM, "sfx\\towners\\storyt32.wav", nullptr }, -/*TSFX_STORY33*/ { sfx_STREAM, "sfx\\towners\\storyt33.wav", nullptr }, -/*TSFX_STORY34*/ { sfx_STREAM, "sfx\\towners\\storyt34.wav", nullptr }, -/*TSFX_STORY35*/ { sfx_STREAM, "sfx\\towners\\storyt35.wav", nullptr }, -/*TSFX_STORY36*/ { sfx_STREAM, "sfx\\towners\\storyt36.wav", nullptr }, -/*TSFX_STORY37*/ { sfx_STREAM, "sfx\\towners\\storyt37.wav", nullptr }, -/*TSFX_STORY38*/ { sfx_STREAM, "sfx\\towners\\storyt38.wav", nullptr }, -/*TSFX_TAVERN0*/ { sfx_STREAM, "sfx\\towners\\tavown00.wav", nullptr }, -/*TSFX_TAVERN1*/ { sfx_STREAM, "sfx\\towners\\tavown01.wav", nullptr }, -/*TSFX_TAVERN2*/ { sfx_STREAM, "sfx\\towners\\tavown02.wav", nullptr }, -/*TSFX_TAVERN3*/ { sfx_STREAM, "sfx\\towners\\tavown03.wav", nullptr }, -/*TSFX_TAVERN4*/ { sfx_STREAM, "sfx\\towners\\tavown04.wav", nullptr }, -/*TSFX_TAVERN5*/ { sfx_STREAM, "sfx\\towners\\tavown05.wav", nullptr }, -/*TSFX_TAVERN6*/ { sfx_STREAM, "sfx\\towners\\tavown06.wav", nullptr }, -/*TSFX_TAVERN7*/ { sfx_STREAM, "sfx\\towners\\tavown07.wav", nullptr }, -/*TSFX_TAVERN8*/ { sfx_STREAM, "sfx\\towners\\tavown08.wav", nullptr }, -/*TSFX_TAVERN9*/ { sfx_STREAM, "sfx\\towners\\tavown09.wav", nullptr }, -/*TSFX_TAVERN10*/ { sfx_STREAM, "sfx\\towners\\tavown10.wav", nullptr }, -/*TSFX_TAVERN11*/ { sfx_STREAM, "sfx\\towners\\tavown11.wav", nullptr }, -/*TSFX_TAVERN12*/ { sfx_STREAM, "sfx\\towners\\tavown12.wav", nullptr }, -/*TSFX_TAVERN13*/ { sfx_STREAM, "sfx\\towners\\tavown13.wav", nullptr }, -/*TSFX_TAVERN14*/ { sfx_STREAM, "sfx\\towners\\tavown14.wav", nullptr }, -/*TSFX_TAVERN15*/ { sfx_STREAM, "sfx\\towners\\tavown15.wav", nullptr }, -/*TSFX_TAVERN16*/ { sfx_STREAM, "sfx\\towners\\tavown16.wav", nullptr }, -/*TSFX_TAVERN17*/ { sfx_STREAM, "sfx\\towners\\tavown17.wav", nullptr }, -/*TSFX_TAVERN18*/ { sfx_STREAM, "sfx\\towners\\tavown18.wav", nullptr }, -/*TSFX_TAVERN19*/ { sfx_STREAM, "sfx\\towners\\tavown19.wav", nullptr }, -/*TSFX_TAVERN20*/ { sfx_STREAM, "sfx\\towners\\tavown20.wav", nullptr }, -/*TSFX_TAVERN21*/ { sfx_STREAM, "sfx\\towners\\tavown21.wav", nullptr }, -/*TSFX_TAVERN22*/ { sfx_STREAM, "sfx\\towners\\tavown22.wav", nullptr }, -/*TSFX_TAVERN23*/ { sfx_STREAM, "sfx\\towners\\tavown23.wav", nullptr }, -/*TSFX_TAVERN24*/ { sfx_STREAM, "sfx\\towners\\tavown24.wav", nullptr }, -/*TSFX_TAVERN25*/ { sfx_STREAM, "sfx\\towners\\tavown25.wav", nullptr }, -/*TSFX_TAVERN26*/ { sfx_STREAM, "sfx\\towners\\tavown26.wav", nullptr }, -/*TSFX_TAVERN27*/ { sfx_STREAM, "sfx\\towners\\tavown27.wav", nullptr }, -/*TSFX_TAVERN28*/ { sfx_STREAM, "sfx\\towners\\tavown28.wav", nullptr }, -/*TSFX_TAVERN29*/ { sfx_STREAM, "sfx\\towners\\tavown29.wav", nullptr }, -/*TSFX_TAVERN30*/ { sfx_STREAM, "sfx\\towners\\tavown30.wav", nullptr }, -/*TSFX_TAVERN31*/ { sfx_STREAM, "sfx\\towners\\tavown31.wav", nullptr }, -/*TSFX_TAVERN32*/ { sfx_STREAM, "sfx\\towners\\tavown32.wav", nullptr }, -/*TSFX_TAVERN33*/ { sfx_STREAM, "sfx\\towners\\tavown33.wav", nullptr }, -/*TSFX_TAVERN34*/ { sfx_STREAM, "sfx\\towners\\tavown34.wav", nullptr }, -/*TSFX_TAVERN35*/ { sfx_STREAM, "sfx\\towners\\tavown35.wav", nullptr }, -/*TSFX_TAVERN36*/ { sfx_STREAM, "sfx\\towners\\tavown36.wav", nullptr }, -/*TSFX_TAVERN37*/ { sfx_STREAM, "sfx\\towners\\tavown37.wav", nullptr }, -/*TSFX_TAVERN38*/ { sfx_STREAM, "sfx\\towners\\tavown38.wav", nullptr }, -/*TSFX_TAVERN39*/ { sfx_STREAM, "sfx\\towners\\tavown39.wav", nullptr }, -/*TSFX_TAVERN40*/ { sfx_STREAM, "sfx\\towners\\tavown40.wav", nullptr }, -/*TSFX_TAVERN41*/ { sfx_STREAM, "sfx\\towners\\tavown41.wav", nullptr }, -/*TSFX_TAVERN42*/ { sfx_STREAM, "sfx\\towners\\tavown42.wav", nullptr }, -/*TSFX_TAVERN43*/ { sfx_STREAM, "sfx\\towners\\tavown43.wav", nullptr }, -/*TSFX_TAVERN44*/ { sfx_STREAM, "sfx\\towners\\tavown44.wav", nullptr }, -/*TSFX_TAVERN45*/ { sfx_STREAM, "sfx\\towners\\tavown45.wav", nullptr }, -/*TSFX_WITCH1*/ { sfx_STREAM, "sfx\\towners\\witch01.wav", nullptr }, -/*TSFX_WITCH2*/ { sfx_STREAM, "sfx\\towners\\witch02.wav", nullptr }, -/*TSFX_WITCH3*/ { sfx_STREAM, "sfx\\towners\\witch03.wav", nullptr }, -/*TSFX_WITCH4*/ { sfx_STREAM, "sfx\\towners\\witch04.wav", nullptr }, -/*TSFX_WITCH5*/ { sfx_STREAM, "sfx\\towners\\witch05.wav", nullptr }, -/*TSFX_WITCH6*/ { sfx_STREAM, "sfx\\towners\\witch06.wav", nullptr }, -/*TSFX_WITCH7*/ { sfx_STREAM, "sfx\\towners\\witch07.wav", nullptr }, -/*TSFX_WITCH8*/ { sfx_STREAM, "sfx\\towners\\witch08.wav", nullptr }, -/*TSFX_WITCH9*/ { sfx_STREAM, "sfx\\towners\\witch09.wav", nullptr }, -/*TSFX_WITCH10*/ { sfx_STREAM, "sfx\\towners\\witch10.wav", nullptr }, -/*TSFX_WITCH11*/ { sfx_STREAM, "sfx\\towners\\witch11.wav", nullptr }, -/*TSFX_WITCH12*/ { sfx_STREAM, "sfx\\towners\\witch12.wav", nullptr }, -/*TSFX_WITCH13*/ { sfx_STREAM, "sfx\\towners\\witch13.wav", nullptr }, -/*TSFX_WITCH14*/ { sfx_STREAM, "sfx\\towners\\witch14.wav", nullptr }, -/*TSFX_WITCH15*/ { sfx_STREAM, "sfx\\towners\\witch15.wav", nullptr }, -/*TSFX_WITCH16*/ { sfx_STREAM, "sfx\\towners\\witch16.wav", nullptr }, -/*TSFX_WITCH17*/ { sfx_STREAM, "sfx\\towners\\witch17.wav", nullptr }, -/*TSFX_WITCH18*/ { sfx_STREAM, "sfx\\towners\\witch18.wav", nullptr }, -/*TSFX_WITCH19*/ { sfx_STREAM, "sfx\\towners\\witch19.wav", nullptr }, -/*TSFX_WITCH20*/ { sfx_STREAM, "sfx\\towners\\witch20.wav", nullptr }, -/*TSFX_WITCH21*/ { sfx_STREAM, "sfx\\towners\\witch21.wav", nullptr }, -/*TSFX_WITCH22*/ { sfx_STREAM, "sfx\\towners\\witch22.wav", nullptr }, -/*TSFX_WITCH23*/ { sfx_STREAM, "sfx\\towners\\witch23.wav", nullptr }, -/*TSFX_WITCH24*/ { sfx_STREAM, "sfx\\towners\\witch24.wav", nullptr }, -/*TSFX_WITCH25*/ { sfx_STREAM, "sfx\\towners\\witch25.wav", nullptr }, -/*TSFX_WITCH26*/ { sfx_STREAM, "sfx\\towners\\witch26.wav", nullptr }, -/*TSFX_WITCH27*/ { sfx_STREAM, "sfx\\towners\\witch27.wav", nullptr }, -/*TSFX_WITCH28*/ { sfx_STREAM, "sfx\\towners\\witch28.wav", nullptr }, -/*TSFX_WITCH29*/ { sfx_STREAM, "sfx\\towners\\witch29.wav", nullptr }, -/*TSFX_WITCH30*/ { sfx_STREAM, "sfx\\towners\\witch30.wav", nullptr }, -/*TSFX_WITCH31*/ { sfx_STREAM, "sfx\\towners\\witch31.wav", nullptr }, -/*TSFX_WITCH32*/ { sfx_STREAM, "sfx\\towners\\witch32.wav", nullptr }, -/*TSFX_WITCH33*/ { sfx_STREAM, "sfx\\towners\\witch33.wav", nullptr }, -/*TSFX_WITCH34*/ { sfx_STREAM, "sfx\\towners\\witch34.wav", nullptr }, -/*TSFX_WITCH35*/ { sfx_STREAM, "sfx\\towners\\witch35.wav", nullptr }, -/*TSFX_WITCH36*/ { sfx_STREAM, "sfx\\towners\\witch36.wav", nullptr }, -/*TSFX_WITCH37*/ { sfx_STREAM, "sfx\\towners\\witch37.wav", nullptr }, -/*TSFX_WITCH38*/ { sfx_STREAM, "sfx\\towners\\witch38.wav", nullptr }, -/*TSFX_WITCH39*/ { sfx_STREAM, "sfx\\towners\\witch39.wav", nullptr }, -/*TSFX_WITCH40*/ { sfx_STREAM, "sfx\\towners\\witch40.wav", nullptr }, -/*TSFX_WITCH41*/ { sfx_STREAM, "sfx\\towners\\witch41.wav", nullptr }, -/*TSFX_WITCH42*/ { sfx_STREAM, "sfx\\towners\\witch42.wav", nullptr }, -/*TSFX_WITCH43*/ { sfx_STREAM, "sfx\\towners\\witch43.wav", nullptr }, -/*TSFX_WITCH44*/ { sfx_STREAM, "sfx\\towners\\witch44.wav", nullptr }, -/*TSFX_WITCH45*/ { sfx_STREAM, "sfx\\towners\\witch45.wav", nullptr }, -/*TSFX_WITCH46*/ { sfx_STREAM, "sfx\\towners\\witch46.wav", nullptr }, -/*TSFX_WITCH47*/ { sfx_STREAM, "sfx\\towners\\witch47.wav", nullptr }, -/*TSFX_WITCH48*/ { sfx_STREAM, "sfx\\towners\\witch48.wav", nullptr }, -/*TSFX_WITCH49*/ { sfx_STREAM, "sfx\\towners\\witch49.wav", nullptr }, -/*TSFX_WITCH50*/ { sfx_STREAM, "sfx\\towners\\witch50.wav", nullptr }, -/*TSFX_WOUND*/ { sfx_STREAM, "sfx\\towners\\wound01.wav", nullptr }, -/*PS_MAGE1*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage01.wav", nullptr }, -/*PS_MAGE2*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage02.wav", nullptr }, -/*PS_MAGE3*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage03.wav", nullptr }, -/*PS_MAGE4*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage04.wav", nullptr }, -/*PS_MAGE5*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage05.wav", nullptr }, -/*PS_MAGE6*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage06.wav", nullptr }, -/*PS_MAGE7*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage07.wav", nullptr }, -/*PS_MAGE8*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage08.wav", nullptr }, -/*PS_MAGE9*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage09.wav", nullptr }, -/*PS_MAGE10*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage10.wav", nullptr }, -/*PS_MAGE11*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage11.wav", nullptr }, -/*PS_MAGE12*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage12.wav", nullptr }, -/*PS_MAGE13*/ { sfx_SORCERER, "sfx\\sorceror\\mage13.wav", nullptr }, -/*PS_MAGE14*/ { sfx_SORCERER, "sfx\\sorceror\\mage14.wav", nullptr }, -/*PS_MAGE15*/ { sfx_SORCERER, "sfx\\sorceror\\mage15.wav", nullptr }, -/*PS_MAGE16*/ { sfx_SORCERER, "sfx\\sorceror\\mage16.wav", nullptr }, -/*PS_MAGE17*/ { sfx_SORCERER, "sfx\\sorceror\\mage17.wav", nullptr }, -/*PS_MAGE18*/ { sfx_SORCERER, "sfx\\sorceror\\mage18.wav", nullptr }, -/*PS_MAGE19*/ { sfx_SORCERER, "sfx\\sorceror\\mage19.wav", nullptr }, -/*PS_MAGE20*/ { sfx_SORCERER, "sfx\\sorceror\\mage20.wav", nullptr }, -/*PS_MAGE21*/ { sfx_SORCERER, "sfx\\sorceror\\mage21.wav", nullptr }, -/*PS_MAGE22*/ { sfx_SORCERER, "sfx\\sorceror\\mage22.wav", nullptr }, -/*PS_MAGE23*/ { sfx_SORCERER, "sfx\\sorceror\\mage23.wav", nullptr }, -/*PS_MAGE24*/ { sfx_SORCERER, "sfx\\sorceror\\mage24.wav", nullptr }, -/*PS_MAGE25*/ { sfx_SORCERER, "sfx\\sorceror\\mage25.wav", nullptr }, -/*PS_MAGE26*/ { sfx_SORCERER, "sfx\\sorceror\\mage26.wav", nullptr }, -/*PS_MAGE27*/ { sfx_SORCERER, "sfx\\sorceror\\mage27.wav", nullptr }, -/*PS_MAGE28*/ { sfx_SORCERER, "sfx\\sorceror\\mage28.wav", nullptr }, -/*PS_MAGE29*/ { sfx_SORCERER, "sfx\\sorceror\\mage29.wav", nullptr }, -/*PS_MAGE30*/ { sfx_SORCERER, "sfx\\sorceror\\mage30.wav", nullptr }, -/*PS_MAGE31*/ { sfx_SORCERER, "sfx\\sorceror\\mage31.wav", nullptr }, -/*PS_MAGE32*/ { sfx_SORCERER, "sfx\\sorceror\\mage32.wav", nullptr }, -/*PS_MAGE33*/ { sfx_SORCERER, "sfx\\sorceror\\mage33.wav", nullptr }, -/*PS_MAGE34*/ { sfx_SORCERER, "sfx\\sorceror\\mage34.wav", nullptr }, -/*PS_MAGE35*/ { sfx_SORCERER, "sfx\\sorceror\\mage35.wav", nullptr }, -/*PS_MAGE36*/ { sfx_SORCERER, "sfx\\sorceror\\mage36.wav", nullptr }, -/*PS_MAGE37*/ { sfx_SORCERER, "sfx\\sorceror\\mage37.wav", nullptr }, -/*PS_MAGE38*/ { sfx_SORCERER, "sfx\\sorceror\\mage38.wav", nullptr }, -/*PS_MAGE39*/ { sfx_SORCERER, "sfx\\sorceror\\mage39.wav", nullptr }, -/*PS_MAGE40*/ { sfx_SORCERER, "sfx\\sorceror\\mage40.wav", nullptr }, -/*PS_MAGE41*/ { sfx_SORCERER, "sfx\\sorceror\\mage41.wav", nullptr }, -/*PS_MAGE42*/ { sfx_SORCERER, "sfx\\sorceror\\mage42.wav", nullptr }, -/*PS_MAGE43*/ { sfx_SORCERER, "sfx\\sorceror\\mage43.wav", nullptr }, -/*PS_MAGE44*/ { sfx_SORCERER, "sfx\\sorceror\\mage44.wav", nullptr }, -/*PS_MAGE45*/ { sfx_SORCERER, "sfx\\sorceror\\mage45.wav", nullptr }, -/*PS_MAGE46*/ { sfx_SORCERER, "sfx\\sorceror\\mage46.wav", nullptr }, -/*PS_MAGE47*/ { sfx_SORCERER, "sfx\\sorceror\\mage47.wav", nullptr }, -/*PS_MAGE48*/ { sfx_SORCERER, "sfx\\sorceror\\mage48.wav", nullptr }, -/*PS_MAGE49*/ { sfx_SORCERER, "sfx\\sorceror\\mage49.wav", nullptr }, -/*PS_MAGE50*/ { sfx_SORCERER, "sfx\\sorceror\\mage50.wav", nullptr }, -/*PS_MAGE51*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage51.wav", nullptr }, -/*PS_MAGE52*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage52.wav", nullptr }, -/*PS_MAGE53*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage53.wav", nullptr }, -/*PS_MAGE54*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage54.wav", nullptr }, -/*PS_MAGE55*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage55.wav", nullptr }, -/*PS_MAGE56*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage56.wav", nullptr }, -/*PS_MAGE57*/ { sfx_SORCERER, "sfx\\sorceror\\mage57.wav", nullptr }, -/*PS_MAGE58*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage58.wav", nullptr }, -/*PS_MAGE59*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage59.wav", nullptr }, -/*PS_MAGE60*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage60.wav", nullptr }, -/*PS_MAGE61*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage61.wav", nullptr }, -/*PS_MAGE62*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage62.wav", nullptr }, -/*PS_MAGE63*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage63.wav", nullptr }, -/*PS_MAGE64*/ { sfx_SORCERER, "sfx\\sorceror\\mage64.wav", nullptr }, -/*PS_MAGE65*/ { sfx_SORCERER, "sfx\\sorceror\\mage65.wav", nullptr }, -/*PS_MAGE66*/ { sfx_SORCERER, "sfx\\sorceror\\mage66.wav", nullptr }, -/*PS_MAGE67*/ { sfx_SORCERER, "sfx\\sorceror\\mage67.wav", nullptr }, -/*PS_MAGE68*/ { sfx_SORCERER, "sfx\\sorceror\\mage68.wav", nullptr }, -/*PS_MAGE69*/ { sfx_SORCERER, "sfx\\sorceror\\mage69.wav", nullptr }, -/*PS_MAGE69B*/ { sfx_SORCERER, "sfx\\sorceror\\mage69b.wav", nullptr }, -/*PS_MAGE70*/ { sfx_SORCERER, "sfx\\sorceror\\mage70.wav", nullptr }, -/*PS_MAGE71*/ { sfx_SORCERER, "sfx\\sorceror\\mage71.wav", nullptr }, -/*PS_MAGE72*/ { sfx_SORCERER, "sfx\\sorceror\\mage72.wav", nullptr }, -/*PS_MAGE73*/ { sfx_SORCERER, "sfx\\sorceror\\mage73.wav", nullptr }, -/*PS_MAGE74*/ { sfx_SORCERER, "sfx\\sorceror\\mage74.wav", nullptr }, -/*PS_MAGE75*/ { sfx_SORCERER, "sfx\\sorceror\\mage75.wav", nullptr }, -/*PS_MAGE76*/ { sfx_SORCERER, "sfx\\sorceror\\mage76.wav", nullptr }, -/*PS_MAGE77*/ { sfx_SORCERER, "sfx\\sorceror\\mage77.wav", nullptr }, -/*PS_MAGE78*/ { sfx_SORCERER, "sfx\\sorceror\\mage78.wav", nullptr }, -/*PS_MAGE79*/ { sfx_SORCERER, "sfx\\sorceror\\mage79.wav", nullptr }, -/*PS_MAGE80*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage80.wav", nullptr }, -/*PS_MAGE81*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage81.wav", nullptr }, -/*PS_MAGE82*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage82.wav", nullptr }, -/*PS_MAGE83*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage83.wav", nullptr }, -/*PS_MAGE84*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage84.wav", nullptr }, -/*PS_MAGE85*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage85.wav", nullptr }, -/*PS_MAGE86*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage86.wav", nullptr }, -/*PS_MAGE87*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage87.wav", nullptr }, -/*PS_MAGE88*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage88.wav", nullptr }, -/*PS_MAGE89*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage89.wav", nullptr }, -/*PS_MAGE90*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage90.wav", nullptr }, -/*PS_MAGE91*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage91.wav", nullptr }, -/*PS_MAGE92*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage92.wav", nullptr }, -/*PS_MAGE93*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage93.wav", nullptr }, -/*PS_MAGE94*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage94.wav", nullptr }, -/*PS_MAGE95*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage95.wav", nullptr }, -/*PS_MAGE96*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage96.wav", nullptr }, -/*PS_MAGE97*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage97.wav", nullptr }, -/*PS_MAGE98*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage98.wav", nullptr }, -/*PS_MAGE99*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage99.wav", nullptr }, -/*PS_MAGE100*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage100.wav", nullptr }, -/*PS_MAGE101*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage101.wav", nullptr }, -/*PS_MAGE102*/ { sfx_STREAM | sfx_SORCERER, "sfx\\sorceror\\mage102.wav", nullptr }, -/*PS_ROGUE1*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue01.wav", nullptr }, -/*PS_ROGUE2*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue02.wav", nullptr }, -/*PS_ROGUE3*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue03.wav", nullptr }, -/*PS_ROGUE4*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue04.wav", nullptr }, -/*PS_ROGUE5*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue05.wav", nullptr }, -/*PS_ROGUE6*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue06.wav", nullptr }, -/*PS_ROGUE7*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue07.wav", nullptr }, -/*PS_ROGUE8*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue08.wav", nullptr }, -/*PS_ROGUE9*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue09.wav", nullptr }, -/*PS_ROGUE10*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue10.wav", nullptr }, -/*PS_ROGUE11*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue11.wav", nullptr }, -/*PS_ROGUE12*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue12.wav", nullptr }, -/*PS_ROGUE13*/ { sfx_ROGUE, "sfx\\rogue\\rogue13.wav", nullptr }, -/*PS_ROGUE14*/ { sfx_ROGUE, "sfx\\rogue\\rogue14.wav", nullptr }, -/*PS_ROGUE15*/ { sfx_ROGUE, "sfx\\rogue\\rogue15.wav", nullptr }, -/*PS_ROGUE16*/ { sfx_ROGUE, "sfx\\rogue\\rogue16.wav", nullptr }, -/*PS_ROGUE17*/ { sfx_ROGUE, "sfx\\rogue\\rogue17.wav", nullptr }, -/*PS_ROGUE18*/ { sfx_ROGUE, "sfx\\rogue\\rogue18.wav", nullptr }, -/*PS_ROGUE19*/ { sfx_ROGUE, "sfx\\rogue\\rogue19.wav", nullptr }, -/*PS_ROGUE20*/ { sfx_ROGUE, "sfx\\rogue\\rogue20.wav", nullptr }, -/*PS_ROGUE21*/ { sfx_ROGUE, "sfx\\rogue\\rogue21.wav", nullptr }, -/*PS_ROGUE22*/ { sfx_ROGUE, "sfx\\rogue\\rogue22.wav", nullptr }, -/*PS_ROGUE23*/ { sfx_ROGUE, "sfx\\rogue\\rogue23.wav", nullptr }, -/*PS_ROGUE24*/ { sfx_ROGUE, "sfx\\rogue\\rogue24.wav", nullptr }, -/*PS_ROGUE25*/ { sfx_ROGUE, "sfx\\rogue\\rogue25.wav", nullptr }, -/*PS_ROGUE26*/ { sfx_ROGUE, "sfx\\rogue\\rogue26.wav", nullptr }, -/*PS_ROGUE27*/ { sfx_ROGUE, "sfx\\rogue\\rogue27.wav", nullptr }, -/*PS_ROGUE28*/ { sfx_ROGUE, "sfx\\rogue\\rogue28.wav", nullptr }, -/*PS_ROGUE29*/ { sfx_ROGUE, "sfx\\rogue\\rogue29.wav", nullptr }, -/*PS_ROGUE30*/ { sfx_ROGUE, "sfx\\rogue\\rogue30.wav", nullptr }, -/*PS_ROGUE31*/ { sfx_ROGUE, "sfx\\rogue\\rogue31.wav", nullptr }, -/*PS_ROGUE32*/ { sfx_ROGUE, "sfx\\rogue\\rogue32.wav", nullptr }, -/*PS_ROGUE33*/ { sfx_ROGUE, "sfx\\rogue\\rogue33.wav", nullptr }, -/*PS_ROGUE34*/ { sfx_ROGUE, "sfx\\rogue\\rogue34.wav", nullptr }, -/*PS_ROGUE35*/ { sfx_ROGUE, "sfx\\rogue\\rogue35.wav", nullptr }, -/*PS_ROGUE36*/ { sfx_ROGUE, "sfx\\rogue\\rogue36.wav", nullptr }, -/*PS_ROGUE37*/ { sfx_ROGUE, "sfx\\rogue\\rogue37.wav", nullptr }, -/*PS_ROGUE38*/ { sfx_ROGUE, "sfx\\rogue\\rogue38.wav", nullptr }, -/*PS_ROGUE39*/ { sfx_ROGUE, "sfx\\rogue\\rogue39.wav", nullptr }, -/*PS_ROGUE40*/ { sfx_ROGUE, "sfx\\rogue\\rogue40.wav", nullptr }, -/*PS_ROGUE41*/ { sfx_ROGUE, "sfx\\rogue\\rogue41.wav", nullptr }, -/*PS_ROGUE42*/ { sfx_ROGUE, "sfx\\rogue\\rogue42.wav", nullptr }, -/*PS_ROGUE43*/ { sfx_ROGUE, "sfx\\rogue\\rogue43.wav", nullptr }, -/*PS_ROGUE44*/ { sfx_ROGUE, "sfx\\rogue\\rogue44.wav", nullptr }, -/*PS_ROGUE45*/ { sfx_ROGUE, "sfx\\rogue\\rogue45.wav", nullptr }, -/*PS_ROGUE46*/ { sfx_ROGUE, "sfx\\rogue\\rogue46.wav", nullptr }, -/*PS_ROGUE47*/ { sfx_ROGUE, "sfx\\rogue\\rogue47.wav", nullptr }, -/*PS_ROGUE48*/ { sfx_ROGUE, "sfx\\rogue\\rogue48.wav", nullptr }, -/*PS_ROGUE49*/ { sfx_ROGUE, "sfx\\rogue\\rogue49.wav", nullptr }, -/*PS_ROGUE50*/ { sfx_ROGUE, "sfx\\rogue\\rogue50.wav", nullptr }, -/*PS_ROGUE51*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue51.wav", nullptr }, -/*PS_ROGUE52*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue52.wav", nullptr }, -/*PS_ROGUE53*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue53.wav", nullptr }, -/*PS_ROGUE54*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue54.wav", nullptr }, -/*PS_ROGUE55*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue55.wav", nullptr }, -/*PS_ROGUE56*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue56.wav", nullptr }, -/*PS_ROGUE57*/ { sfx_ROGUE, "sfx\\rogue\\rogue57.wav", nullptr }, -/*PS_ROGUE58*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue58.wav", nullptr }, -/*PS_ROGUE59*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue59.wav", nullptr }, -/*PS_ROGUE60*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue60.wav", nullptr }, -/*PS_ROGUE61*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue61.wav", nullptr }, -/*PS_ROGUE62*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue62.wav", nullptr }, -/*PS_ROGUE63*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue63.wav", nullptr }, -/*PS_ROGUE64*/ { sfx_ROGUE, "sfx\\rogue\\rogue64.wav", nullptr }, -/*PS_ROGUE65*/ { sfx_ROGUE, "sfx\\rogue\\rogue65.wav", nullptr }, -/*PS_ROGUE66*/ { sfx_ROGUE, "sfx\\rogue\\rogue66.wav", nullptr }, -/*PS_ROGUE67*/ { sfx_ROGUE, "sfx\\rogue\\rogue67.wav", nullptr }, -/*PS_ROGUE68*/ { sfx_ROGUE, "sfx\\rogue\\rogue68.wav", nullptr }, -/*PS_ROGUE69*/ { sfx_ROGUE, "sfx\\rogue\\rogue69.wav", nullptr }, -/*PS_ROGUE69B*/ { sfx_ROGUE, "sfx\\rogue\\rogue69b.wav", nullptr }, -/*PS_ROGUE70*/ { sfx_ROGUE, "sfx\\rogue\\rogue70.wav", nullptr }, -/*PS_ROGUE71*/ { sfx_ROGUE, "sfx\\rogue\\rogue71.wav", nullptr }, -/*PS_ROGUE72*/ { sfx_ROGUE, "sfx\\rogue\\rogue72.wav", nullptr }, -/*PS_ROGUE73*/ { sfx_ROGUE, "sfx\\rogue\\rogue73.wav", nullptr }, -/*PS_ROGUE74*/ { sfx_ROGUE, "sfx\\rogue\\rogue74.wav", nullptr }, -/*PS_ROGUE75*/ { sfx_ROGUE, "sfx\\rogue\\rogue75.wav", nullptr }, -/*PS_ROGUE76*/ { sfx_ROGUE, "sfx\\rogue\\rogue76.wav", nullptr }, -/*PS_ROGUE77*/ { sfx_ROGUE, "sfx\\rogue\\rogue77.wav", nullptr }, -/*PS_ROGUE78*/ { sfx_ROGUE, "sfx\\rogue\\rogue78.wav", nullptr }, -/*PS_ROGUE79*/ { sfx_ROGUE, "sfx\\rogue\\rogue79.wav", nullptr }, -/*PS_ROGUE80*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue80.wav", nullptr }, -/*PS_ROGUE81*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue81.wav", nullptr }, -/*PS_ROGUE82*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue82.wav", nullptr }, -/*PS_ROGUE83*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue83.wav", nullptr }, -/*PS_ROGUE84*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue84.wav", nullptr }, -/*PS_ROGUE85*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue85.wav", nullptr }, -/*PS_ROGUE86*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue86.wav", nullptr }, -/*PS_ROGUE87*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue87.wav", nullptr }, -/*PS_ROGUE88*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue88.wav", nullptr }, -/*PS_ROGUE89*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue89.wav", nullptr }, -/*PS_ROGUE90*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue90.wav", nullptr }, -/*PS_ROGUE91*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue91.wav", nullptr }, -/*PS_ROGUE92*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue92.wav", nullptr }, -/*PS_ROGUE93*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue93.wav", nullptr }, -/*PS_ROGUE94*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue94.wav", nullptr }, -/*PS_ROGUE95*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue95.wav", nullptr }, -/*PS_ROGUE96*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue96.wav", nullptr }, -/*PS_ROGUE97*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue97.wav", nullptr }, -/*PS_ROGUE98*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue98.wav", nullptr }, -/*PS_ROGUE99*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue99.wav", nullptr }, -/*PS_ROGUE100*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue100.wav", nullptr }, -/*PS_ROGUE101*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue101.wav", nullptr }, -/*PS_ROGUE102*/ { sfx_STREAM | sfx_ROGUE, "sfx\\rogue\\rogue102.wav", nullptr }, -/*PS_WARR1*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior01.wav", nullptr }, -/*PS_WARR2*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior02.wav", nullptr }, -/*PS_WARR3*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior03.wav", nullptr }, -/*PS_WARR4*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior04.wav", nullptr }, -/*PS_WARR5*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior05.wav", nullptr }, -/*PS_WARR6*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior06.wav", nullptr }, -/*PS_WARR7*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior07.wav", nullptr }, -/*PS_WARR8*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior08.wav", nullptr }, -/*PS_WARR9*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior09.wav", nullptr }, -/*PS_WARR10*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior10.wav", nullptr }, -/*PS_WARR11*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior11.wav", nullptr }, -/*PS_WARR12*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior12.wav", nullptr }, -/*PS_WARR13*/ { sfx_WARRIOR, "sfx\\warrior\\warior13.wav", nullptr }, -/*PS_WARR14*/ { sfx_WARRIOR, "sfx\\warrior\\warior14.wav", nullptr }, -/*PS_WARR14B*/ { sfx_WARRIOR, "sfx\\warrior\\wario14b.wav", nullptr }, -/*PS_WARR14C*/ { sfx_WARRIOR, "sfx\\warrior\\wario14c.wav", nullptr }, -/*PS_WARR15*/ { sfx_WARRIOR, "sfx\\warrior\\warior15.wav", nullptr }, -/*PS_WARR15B*/ { sfx_WARRIOR, "sfx\\warrior\\wario15b.wav", nullptr }, -/*PS_WARR15C*/ { sfx_WARRIOR, "sfx\\warrior\\wario15c.wav", nullptr }, -/*PS_WARR16*/ { sfx_WARRIOR, "sfx\\warrior\\warior16.wav", nullptr }, -/*PS_WARR16B*/ { sfx_WARRIOR, "sfx\\warrior\\wario16b.wav", nullptr }, -/*PS_WARR16C*/ { sfx_WARRIOR, "sfx\\warrior\\wario16c.wav", nullptr }, -/*PS_WARR17*/ { sfx_WARRIOR, "sfx\\warrior\\warior17.wav", nullptr }, -/*PS_WARR18*/ { sfx_WARRIOR, "sfx\\warrior\\warior18.wav", nullptr }, -/*PS_WARR19*/ { sfx_WARRIOR, "sfx\\warrior\\warior19.wav", nullptr }, -/*PS_WARR20*/ { sfx_WARRIOR, "sfx\\warrior\\warior20.wav", nullptr }, -/*PS_WARR21*/ { sfx_WARRIOR, "sfx\\warrior\\warior21.wav", nullptr }, -/*PS_WARR22*/ { sfx_WARRIOR, "sfx\\warrior\\warior22.wav", nullptr }, -/*PS_WARR23*/ { sfx_WARRIOR, "sfx\\warrior\\warior23.wav", nullptr }, -/*PS_WARR24*/ { sfx_WARRIOR, "sfx\\warrior\\warior24.wav", nullptr }, -/*PS_WARR25*/ { sfx_WARRIOR, "sfx\\warrior\\warior25.wav", nullptr }, -/*PS_WARR26*/ { sfx_WARRIOR, "sfx\\warrior\\warior26.wav", nullptr }, -/*PS_WARR27*/ { sfx_WARRIOR, "sfx\\warrior\\warior27.wav", nullptr }, -/*PS_WARR28*/ { sfx_WARRIOR, "sfx\\warrior\\warior28.wav", nullptr }, -/*PS_WARR29*/ { sfx_WARRIOR, "sfx\\warrior\\warior29.wav", nullptr }, -/*PS_WARR30*/ { sfx_WARRIOR, "sfx\\warrior\\warior30.wav", nullptr }, -/*PS_WARR31*/ { sfx_WARRIOR, "sfx\\warrior\\warior31.wav", nullptr }, -/*PS_WARR32*/ { sfx_WARRIOR, "sfx\\warrior\\warior32.wav", nullptr }, -/*PS_WARR33*/ { sfx_WARRIOR, "sfx\\warrior\\warior33.wav", nullptr }, -/*PS_WARR34*/ { sfx_WARRIOR, "sfx\\warrior\\warior34.wav", nullptr }, -/*PS_WARR35*/ { sfx_WARRIOR, "sfx\\warrior\\warior35.wav", nullptr }, -/*PS_WARR36*/ { sfx_WARRIOR, "sfx\\warrior\\warior36.wav", nullptr }, -/*PS_WARR37*/ { sfx_WARRIOR, "sfx\\warrior\\warior37.wav", nullptr }, -/*PS_WARR38*/ { sfx_WARRIOR, "sfx\\warrior\\warior38.wav", nullptr }, -/*PS_WARR39*/ { sfx_WARRIOR, "sfx\\warrior\\warior39.wav", nullptr }, -/*PS_WARR40*/ { sfx_WARRIOR, "sfx\\warrior\\warior40.wav", nullptr }, -/*PS_WARR41*/ { sfx_WARRIOR, "sfx\\warrior\\warior41.wav", nullptr }, -/*PS_WARR42*/ { sfx_WARRIOR, "sfx\\warrior\\warior42.wav", nullptr }, -/*PS_WARR43*/ { sfx_WARRIOR, "sfx\\warrior\\warior43.wav", nullptr }, -/*PS_WARR44*/ { sfx_WARRIOR, "sfx\\warrior\\warior44.wav", nullptr }, -/*PS_WARR45*/ { sfx_WARRIOR, "sfx\\warrior\\warior45.wav", nullptr }, -/*PS_WARR46*/ { sfx_WARRIOR, "sfx\\warrior\\warior46.wav", nullptr }, -/*PS_WARR47*/ { sfx_WARRIOR, "sfx\\warrior\\warior47.wav", nullptr }, -/*PS_WARR48*/ { sfx_WARRIOR, "sfx\\warrior\\warior48.wav", nullptr }, -/*PS_WARR49*/ { sfx_WARRIOR, "sfx\\warrior\\warior49.wav", nullptr }, -/*PS_WARR50*/ { sfx_WARRIOR, "sfx\\warrior\\warior50.wav", nullptr }, -/*PS_WARR51*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior51.wav", nullptr }, -/*PS_WARR52*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior52.wav", nullptr }, -/*PS_WARR53*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior53.wav", nullptr }, -/*PS_WARR54*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior54.wav", nullptr }, -/*PS_WARR55*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior55.wav", nullptr }, -/*PS_WARR56*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior56.wav", nullptr }, -/*PS_WARR57*/ { sfx_WARRIOR, "sfx\\warrior\\warior57.wav", nullptr }, -/*PS_WARR58*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior58.wav", nullptr }, -/*PS_WARR59*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior59.wav", nullptr }, -/*PS_WARR60*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior60.wav", nullptr }, -/*PS_WARR61*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior61.wav", nullptr }, -/*PS_WARR62*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior62.wav", nullptr }, -/*PS_WARR63*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior63.wav", nullptr }, -/*PS_WARR64*/ { sfx_WARRIOR, "sfx\\warrior\\warior64.wav", nullptr }, -/*PS_WARR65*/ { sfx_WARRIOR, "sfx\\warrior\\warior65.wav", nullptr }, -/*PS_WARR66*/ { sfx_WARRIOR, "sfx\\warrior\\warior66.wav", nullptr }, -/*PS_WARR67*/ { sfx_WARRIOR, "sfx\\warrior\\warior67.wav", nullptr }, -/*PS_WARR68*/ { sfx_WARRIOR, "sfx\\warrior\\warior68.wav", nullptr }, -/*PS_WARR69*/ { sfx_WARRIOR, "sfx\\warrior\\warior69.wav", nullptr }, -/*PS_WARR69B*/ { sfx_WARRIOR, "sfx\\warrior\\wario69b.wav", nullptr }, -/*PS_WARR70*/ { sfx_WARRIOR, "sfx\\warrior\\warior70.wav", nullptr }, -/*PS_WARR71*/ { sfx_WARRIOR, "sfx\\warrior\\warior71.wav", nullptr }, -/*PS_WARR72*/ { sfx_WARRIOR, "sfx\\warrior\\warior72.wav", nullptr }, -/*PS_WARR73*/ { sfx_WARRIOR, "sfx\\warrior\\warior73.wav", nullptr }, -/*PS_WARR74*/ { sfx_WARRIOR, "sfx\\warrior\\warior74.wav", nullptr }, -/*PS_WARR75*/ { sfx_WARRIOR, "sfx\\warrior\\warior75.wav", nullptr }, -/*PS_WARR76*/ { sfx_WARRIOR, "sfx\\warrior\\warior76.wav", nullptr }, -/*PS_WARR77*/ { sfx_WARRIOR, "sfx\\warrior\\warior77.wav", nullptr }, -/*PS_WARR78*/ { sfx_WARRIOR, "sfx\\warrior\\warior78.wav", nullptr }, -/*PS_WARR79*/ { sfx_WARRIOR, "sfx\\warrior\\warior79.wav", nullptr }, -/*PS_WARR80*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior80.wav", nullptr }, -/*PS_WARR81*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior81.wav", nullptr }, -/*PS_WARR82*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior82.wav", nullptr }, -/*PS_WARR83*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior83.wav", nullptr }, -/*PS_WARR84*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior84.wav", nullptr }, -/*PS_WARR85*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior85.wav", nullptr }, -/*PS_WARR86*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior86.wav", nullptr }, -/*PS_WARR87*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior87.wav", nullptr }, -/*PS_WARR88*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior88.wav", nullptr }, -/*PS_WARR89*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior89.wav", nullptr }, -/*PS_WARR90*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior90.wav", nullptr }, -/*PS_WARR91*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior91.wav", nullptr }, -/*PS_WARR92*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior92.wav", nullptr }, -/*PS_WARR93*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior93.wav", nullptr }, -/*PS_WARR94*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior94.wav", nullptr }, -/*PS_WARR95*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior95.wav", nullptr }, -/*PS_WARR95B*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\wario95b.wav", nullptr }, -/*PS_WARR95C*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\wario95c.wav", nullptr }, -/*PS_WARR95D*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\wario95d.wav", nullptr }, -/*PS_WARR95E*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\wario95e.wav", nullptr }, -/*PS_WARR95F*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\wario95f.wav", nullptr }, -/*PS_WARR96B*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\wario96b.wav", nullptr }, -/*PS_WARR97*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\wario97.wav", nullptr }, -/*PS_WARR98*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\wario98.wav", nullptr }, -/*PS_WARR99*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\warior99.wav", nullptr }, -/*PS_WARR100*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\wario100.wav", nullptr }, -/*PS_WARR101*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\wario101.wav", nullptr }, -/*PS_WARR102*/ { sfx_STREAM | sfx_WARRIOR, "sfx\\warrior\\wario102.wav", nullptr }, -/*PS_MONK1*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk01.wav", nullptr }, -/*PS_MONK8*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk08.wav", nullptr }, -/*PS_MONK9*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk09.wav", nullptr }, -/*PS_MONK10*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk10.wav", nullptr }, -/*PS_MONK11*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk11.wav", nullptr }, -/*PS_MONK12*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk12.wav", nullptr }, -/*PS_MONK13*/ { sfx_MONK, "sfx\\monk\\monk13.wav", nullptr }, -/*PS_MONK14*/ { sfx_MONK, "sfx\\monk\\monk14.wav", nullptr }, -/*PS_MONK15*/ { sfx_MONK, "sfx\\monk\\monk15.wav", nullptr }, -/*PS_MONK16*/ { sfx_MONK, "sfx\\monk\\monk16.wav", nullptr }, -/*PS_MONK24*/ { sfx_MONK, "sfx\\monk\\monk24.wav", nullptr }, -/*PS_MONK27*/ { sfx_MONK, "sfx\\monk\\monk27.wav", nullptr }, -/*PS_MONK29*/ { sfx_MONK, "sfx\\monk\\monk29.wav", nullptr }, -/*PS_MONK34*/ { sfx_MONK, "sfx\\monk\\monk34.wav", nullptr }, -/*PS_MONK35*/ { sfx_MONK, "sfx\\monk\\monk35.wav", nullptr }, -/*PS_MONK43*/ { sfx_MONK, "sfx\\monk\\monk43.wav", nullptr }, -/*PS_MONK46*/ { sfx_MONK, "sfx\\monk\\monk46.wav", nullptr }, -/*PS_MONK49*/ { sfx_MONK, "sfx\\monk\\monk49.wav", nullptr }, -/*PS_MONK50*/ { sfx_MONK, "sfx\\monk\\monk50.wav", nullptr }, -/*PS_MONK52*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk52.wav", nullptr }, -/*PS_MONK54*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk54.wav", nullptr }, -/*PS_MONK55*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk55.wav", nullptr }, -/*PS_MONK56*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk56.wav", nullptr }, -/*PS_MONK61*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk61.wav", nullptr }, -/*PS_MONK62*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk62.wav", nullptr }, -/*PS_MONK68*/ { sfx_MONK, "sfx\\monk\\monk68.wav", nullptr }, -/*PS_MONK69*/ { sfx_MONK, "sfx\\monk\\monk69.wav", nullptr }, -/*PS_MONK69B*/ { sfx_MONK, "sfx\\monk\\monk69b.wav", nullptr }, -/*PS_MONK70*/ { sfx_MONK, "sfx\\monk\\monk70.wav", nullptr }, -/*PS_MONK71*/ { sfx_MONK, "sfx\\monk\\monk71.wav", nullptr }, -/*PS_MONK79*/ { sfx_MONK, "sfx\\monk\\monk79.wav", nullptr }, -/*PS_MONK80*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk80.wav", nullptr }, -/*PS_MONK82*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk82.wav", nullptr }, -/*PS_MONK83*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk83.wav", nullptr }, -/*PS_MONK87*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk87.wav", nullptr }, -/*PS_MONK88*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk88.wav", nullptr }, -/*PS_MONK89*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk89.wav", nullptr }, -/*PS_MONK91*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk91.wav", nullptr }, -/*PS_MONK92*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk92.wav", nullptr }, -/*PS_MONK94*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk94.wav", nullptr }, -/*PS_MONK95*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk95.wav", nullptr }, -/*PS_MONK96*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk96.wav", nullptr }, -/*PS_MONK97*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk97.wav", nullptr }, -/*PS_MONK98*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk98.wav", nullptr }, -/*PS_MONK99*/ { sfx_STREAM | sfx_MONK, "sfx\\monk\\monk99.wav", nullptr }, -/*PS_NAR1*/ { sfx_STREAM, "sfx\\narrator\\nar01.wav", nullptr }, -/*PS_NAR2*/ { sfx_STREAM, "sfx\\narrator\\nar02.wav", nullptr }, -/*PS_NAR3*/ { sfx_STREAM, "sfx\\narrator\\nar03.wav", nullptr }, -/*PS_NAR4*/ { sfx_STREAM, "sfx\\narrator\\nar04.wav", nullptr }, -/*PS_NAR5*/ { sfx_STREAM, "sfx\\narrator\\nar05.wav", nullptr }, -/*PS_NAR6*/ { sfx_STREAM, "sfx\\narrator\\nar06.wav", nullptr }, -/*PS_NAR7*/ { sfx_STREAM, "sfx\\narrator\\nar07.wav", nullptr }, -/*PS_NAR8*/ { sfx_STREAM, "sfx\\narrator\\nar08.wav", nullptr }, -/*PS_NAR9*/ { sfx_STREAM, "sfx\\narrator\\nar09.wav", nullptr }, -/*PS_DIABLVLINT*/ { sfx_STREAM, "sfx\\misc\\lvl16int.wav", nullptr }, -/*USFX_CLEAVER*/ { sfx_STREAM, "sfx\\monsters\\butcher.wav", nullptr }, -/*USFX_GARBUD1*/ { sfx_STREAM, "sfx\\monsters\\garbud01.wav", nullptr }, -/*USFX_GARBUD2*/ { sfx_STREAM, "sfx\\monsters\\garbud02.wav", nullptr }, -/*USFX_GARBUD3*/ { sfx_STREAM, "sfx\\monsters\\garbud03.wav", nullptr }, -/*USFX_GARBUD4*/ { sfx_STREAM, "sfx\\monsters\\garbud04.wav", nullptr }, -/*USFX_IZUAL1*/ { sfx_STREAM, "sfx\\monsters\\izual01.wav", nullptr }, -/*USFX_LACH1*/ { sfx_STREAM, "sfx\\monsters\\lach01.wav", nullptr }, -/*USFX_LACH2*/ { sfx_STREAM, "sfx\\monsters\\lach02.wav", nullptr }, -/*USFX_LACH3*/ { sfx_STREAM, "sfx\\monsters\\lach03.wav", nullptr }, -/*USFX_LAZ1*/ { sfx_STREAM, "sfx\\monsters\\laz01.wav", nullptr }, -/*USFX_LAZ2*/ { sfx_STREAM, "sfx\\monsters\\laz02.wav", nullptr }, -/*USFX_SKING1*/ { sfx_STREAM, "sfx\\monsters\\sking01.wav", nullptr }, -/*USFX_SNOT1*/ { sfx_STREAM, "sfx\\monsters\\snot01.wav", nullptr }, -/*USFX_SNOT2*/ { sfx_STREAM, "sfx\\monsters\\snot02.wav", nullptr }, -/*USFX_SNOT3*/ { sfx_STREAM, "sfx\\monsters\\snot03.wav", nullptr }, -/*USFX_WARLRD1*/ { sfx_STREAM, "sfx\\monsters\\warlrd01.wav", nullptr }, -/*USFX_WLOCK1*/ { sfx_STREAM, "sfx\\monsters\\wlock01.wav", nullptr }, -/*USFX_ZHAR1*/ { sfx_STREAM, "sfx\\monsters\\zhar01.wav", nullptr }, -/*USFX_ZHAR2*/ { sfx_STREAM, "sfx\\monsters\\zhar02.wav", nullptr }, -/*USFX_DIABLOD*/ { sfx_STREAM, "sfx\\monsters\\diablod.wav", nullptr }, -/*TSFX_FARMER1*/ { sfx_STREAM, "sfx\\hellfire\\farmer1.wav", nullptr }, -/*TSFX_FARMER2*/ { sfx_STREAM, "sfx\\hellfire\\farmer2.wav", nullptr }, -/*TSFX_FARMER2A*/ { sfx_STREAM, "sfx\\hellfire\\farmer2a.wav", nullptr }, -/*TSFX_FARMER3*/ { sfx_STREAM, "sfx\\hellfire\\farmer3.wav", nullptr }, -/*TSFX_FARMER4*/ { sfx_STREAM, "sfx\\hellfire\\farmer4.wav", nullptr }, -/*TSFX_FARMER5*/ { sfx_STREAM, "sfx\\hellfire\\farmer5.wav", nullptr }, -/*TSFX_FARMER6*/ { sfx_STREAM, "sfx\\hellfire\\farmer6.wav", nullptr }, -/*TSFX_FARMER7*/ { sfx_STREAM, "sfx\\hellfire\\farmer7.wav", nullptr }, -/*TSFX_FARMER8*/ { sfx_STREAM, "sfx\\hellfire\\farmer8.wav", nullptr }, -/*TSFX_FARMER9*/ { sfx_STREAM, "sfx\\hellfire\\farmer9.wav", nullptr }, -/*TSFX_TEDDYBR1*/ { sfx_STREAM, "sfx\\hellfire\\teddybr1.wav", nullptr }, -/*TSFX_TEDDYBR2*/ { sfx_STREAM, "sfx\\hellfire\\teddybr2.wav", nullptr }, -/*TSFX_TEDDYBR3*/ { sfx_STREAM, "sfx\\hellfire\\teddybr3.wav", nullptr }, -/*TSFX_TEDDYBR4*/ { sfx_STREAM, "sfx\\hellfire\\teddybr4.wav", nullptr }, -/*USFX_DEFILER1*/ { sfx_STREAM, "sfx\\hellfire\\defiler1.wav", nullptr }, -/*USFX_DEFILER2*/ { sfx_STREAM, "sfx\\hellfire\\defiler2.wav", nullptr }, -/*USFX_DEFILER3*/ { sfx_STREAM, "sfx\\hellfire\\defiler3.wav", nullptr }, -/*USFX_DEFILER4*/ { sfx_STREAM, "sfx\\hellfire\\defiler4.wav", nullptr }, -/*USFX_DEFILER8*/ { sfx_STREAM, "sfx\\hellfire\\defiler8.wav", nullptr }, -/*USFX_DEFILER6*/ { sfx_STREAM, "sfx\\hellfire\\defiler6.wav", nullptr }, -/*USFX_DEFILER7*/ { sfx_STREAM, "sfx\\hellfire\\defiler7.wav", nullptr }, -/*USFX_NAKRUL1*/ { sfx_STREAM, "sfx\\hellfire\\nakrul1.wav", nullptr }, -/*USFX_NAKRUL2*/ { sfx_STREAM, "sfx\\hellfire\\nakrul2.wav", nullptr }, -/*USFX_NAKRUL3*/ { sfx_STREAM, "sfx\\hellfire\\nakrul3.wav", nullptr }, -/*USFX_NAKRUL4*/ { sfx_STREAM, "sfx\\hellfire\\nakrul4.wav", nullptr }, -/*USFX_NAKRUL5*/ { sfx_STREAM, "sfx\\hellfire\\nakrul5.wav", nullptr }, -/*USFX_NAKRUL6*/ { sfx_STREAM, "sfx\\hellfire\\nakrul6.wav", nullptr }, -/*PS_NARATR3*/ { sfx_STREAM, "sfx\\hellfire\\naratr3.wav", nullptr }, -/*TSFX_COWSUT1*/ { sfx_STREAM, "sfx\\hellfire\\cowsut1.wav", nullptr }, -/*TSFX_COWSUT2*/ { sfx_STREAM, "sfx\\hellfire\\cowsut2.wav", nullptr }, -/*TSFX_COWSUT3*/ { sfx_STREAM, "sfx\\hellfire\\cowsut3.wav", nullptr }, -/*TSFX_COWSUT4*/ { sfx_STREAM, "sfx\\hellfire\\cowsut4.wav", nullptr }, -/*TSFX_COWSUT4A*/ { sfx_STREAM, "sfx\\hellfire\\cowsut4a.wav", nullptr }, -/*TSFX_COWSUT5*/ { sfx_STREAM, "sfx\\hellfire\\cowsut5.wav", nullptr }, -/*TSFX_COWSUT6*/ { sfx_STREAM, "sfx\\hellfire\\cowsut6.wav", nullptr }, -/*TSFX_COWSUT7*/ { sfx_STREAM, "sfx\\hellfire\\cowsut7.wav", nullptr }, -/*TSFX_COWSUT8*/ { sfx_STREAM, "sfx\\hellfire\\cowsut8.wav", nullptr }, -/*TSFX_COWSUT9*/ { sfx_STREAM, "sfx\\hellfire\\cowsut9.wav", nullptr }, -/*TSFX_COWSUT10*/ { sfx_STREAM, "sfx\\hellfire\\cowsut10.wav", nullptr }, -/*TSFX_COWSUT11*/ { sfx_STREAM, "sfx\\hellfire\\cowsut11.wav", nullptr }, -/*TSFX_COWSUT12*/ { sfx_STREAM, "sfx\\hellfire\\cowsut12.wav", nullptr }, -/*USFX_SKLJRN1*/ { sfx_STREAM, "sfx\\hellfire\\skljrn1.wav", nullptr }, -/*PS_NARATR6*/ { sfx_STREAM, "sfx\\hellfire\\naratr6.wav", nullptr }, -/*PS_NARATR7*/ { sfx_STREAM, "sfx\\hellfire\\naratr7.wav", nullptr }, -/*PS_NARATR8*/ { sfx_STREAM, "sfx\\hellfire\\naratr8.wav", nullptr }, -/*PS_NARATR5*/ { sfx_STREAM, "sfx\\hellfire\\naratr5.wav", nullptr }, -/*PS_NARATR9*/ { sfx_STREAM, "sfx\\hellfire\\naratr9.wav", nullptr }, -/*PS_NARATR4*/ { sfx_STREAM, "sfx\\hellfire\\naratr4.wav", nullptr }, -/*TSFX_TRADER1*/ { sfx_STREAM, "sfx\\hellfire\\trader1.wav", nullptr }, -/*IS_CROPEN*/ { sfx_MISC | sfx_HELLFIRE, "sfx\\items\\cropen.wav", nullptr }, -/*IS_CRCLOS*/ { sfx_MISC | sfx_HELLFIRE, "sfx\\items\\crclos.wav", nullptr }, - // clang-format on -}; +std::vector sgSFX; void StreamPlay(TSFX *pSFX, int lVolume, int lPan) { @@ -979,7 +49,7 @@ void StreamPlay(TSFX *pSFX, int lVolume, int lPan) if (lVolume > VOLUME_MAX) lVolume = VOLUME_MAX; if (pSFX->pSnd == nullptr) - pSFX->pSnd = sound_file_load(pSFX->pszName, AllowStreaming); + pSFX->pSnd = sound_file_load(pSFX->pszName.c_str(), AllowStreaming); if (pSFX->pSnd->DSB.IsLoaded()) pSFX->pSnd->DSB.PlayWithVolumeAndPan(lVolume, sound_get_or_set_sound_volume(1), lPan); sgpStreamSFX = pSFX; @@ -1018,41 +88,65 @@ void PlaySfxPriv(TSFX *pSFX, bool loc, Point position) } if (pSFX->pSnd == nullptr) - pSFX->pSnd = sound_file_load(pSFX->pszName); + pSFX->pSnd = sound_file_load(pSFX->pszName.c_str()); if (pSFX->pSnd != nullptr && pSFX->pSnd->DSB.IsLoaded()) snd_play_snd(pSFX->pSnd.get(), lVolume, lPan); } -_sfx_id RndSFX(_sfx_id psfx) +SfxID RndSFX(SfxID psfx) { - int nRand; - switch (psfx) { - case PS_WARR69: - case PS_MAGE69: - case PS_ROGUE69: - case PS_MONK69: - case PS_SWING: - case LS_ACID: - case IS_MAGIC: - case IS_BHIT: - nRand = 2; - break; - case PS_WARR14: - case PS_WARR15: - case PS_WARR16: - case PS_WARR2: - case PS_ROGUE14: - case PS_MAGE14: - case PS_MONK14: - nRand = 3; - break; + case SfxID::Warrior69: + case SfxID::Sorceror69: + case SfxID::Rogue69: + case SfxID::Monk69: + case SfxID::Swing: + case SfxID::SpellAcid: + case SfxID::OperateShrine: + return PickRandomlyAmong({ psfx, static_cast(static_cast(psfx) + 1) }); + case SfxID::Warrior14: + case SfxID::Warrior15: + case SfxID::Warrior16: + case SfxID::Warrior2: + case SfxID::Rogue14: + case SfxID::Sorceror14: + case SfxID::Monk14: + return PickRandomlyAmong({ psfx, static_cast(static_cast(psfx) + 1), static_cast(static_cast(psfx) + 2) }); default: return psfx; } +} + +tl::expected ParseSfxFlag(std::string_view value) +{ + if (value == "Stream") return sfx_STREAM; + if (value == "Misc") return sfx_MISC; + if (value == "Ui") return sfx_UI; + if (value == "Monk") return sfx_MONK; + if (value == "Rogue") return sfx_ROGUE; + if (value == "Warrior") return sfx_WARRIOR; + if (value == "Sorcerer") return sfx_SORCERER; + if (value == "Hellfire") return sfx_HELLFIRE; + return tl::make_unexpected("Unknown enum value"); +} - return static_cast<_sfx_id>(psfx + GenerateRnd(nRand)); +void LoadEffectsData() +{ + const std::string_view filename = "txtdata\\sound\\effects.tsv"; + DataFile dataFile = DataFile::loadOrDie(filename); + dataFile.skipHeaderOrDie(filename); + + sgSFX.clear(); + sgSFX.reserve(dataFile.numRecords()); + for (DataFileRecord record : dataFile) { + RecordReader reader { record, filename }; + TSFX &item = sgSFX.emplace_back(); + reader.advance(); // Skip the first column (effect ID). + reader.readEnumList("flags", item.bFlags, ParseSfxFlag); + reader.readString("path", item.pszName); + } + sgSFX.shrink_to_fit(); } void PrivSoundInit(uint8_t bLoadMask) @@ -1061,6 +155,8 @@ void PrivSoundInit(uint8_t bLoadMask) return; } + if (sgSFX.empty()) LoadEffectsData(); + for (auto &sfx : sgSFX) { if (sfx.bFlags == 0 || sfx.pSnd != nullptr) { continue; @@ -1078,15 +174,17 @@ void PrivSoundInit(uint8_t bLoadMask) continue; } - sfx.pSnd = sound_file_load(sfx.pszName); + sfx.pSnd = sound_file_load(sfx.pszName.c_str()); } } } // namespace -bool effect_is_playing(int nSFX) +bool effect_is_playing(SfxID nSFX) { - TSFX *sfx = &sgSFX[nSFX]; + if (!gbSndInited) return false; + + TSFX *sfx = &sgSFX[static_cast(nSFX)]; if (sfx->pSnd != nullptr) return sfx->pSnd->isPlaying(); @@ -1104,26 +202,30 @@ void stream_stop() } } -void PlaySFX(_sfx_id psfx) +void PlaySFX(SfxID psfx) { psfx = RndSFX(psfx); - PlaySfxPriv(&sgSFX[psfx], false, { 0, 0 }); + if (!gbSndInited) return; + + PlaySfxPriv(&sgSFX[static_cast(psfx)], false, { 0, 0 }); } -void PlaySfxLoc(_sfx_id psfx, Point position, bool randomizeByCategory) +void PlaySfxLoc(SfxID psfx, Point position, bool randomizeByCategory) { if (randomizeByCategory) { psfx = RndSFX(psfx); } - if (psfx >= 0 && psfx <= 3) { - TSnd *pSnd = sgSFX[psfx].pSnd.get(); + if (!gbSndInited) return; + + if (IsAnyOf(psfx, SfxID::Walk, SfxID::ShootBow, SfxID::CastSpell, SfxID::Swing)) { + TSnd *pSnd = sgSFX[static_cast(psfx)].pSnd.get(); if (pSnd != nullptr) pSnd->start_tc = 0; } - PlaySfxPriv(&sgSFX[psfx], true, position); + PlaySfxPriv(&sgSFX[static_cast(psfx)], true, position); } void sound_stop() @@ -1193,24 +295,25 @@ void ui_sound_init() PrivSoundInit(sfx_UI); } -void effects_play_sound(_sfx_id id) +void effects_play_sound(SfxID id) { if (!gbSndInited || !gbSoundOn) { return; } - TSFX &sfx = sgSFX[id]; + TSFX &sfx = sgSFX[static_cast(id)]; if (sfx.pSnd != nullptr && !sfx.pSnd->isPlaying()) { snd_play_snd(sfx.pSnd.get(), 0, 0); } } -int GetSFXLength(int nSFX) +int GetSFXLength(SfxID nSFX) { - if (sgSFX[nSFX].pSnd == nullptr) - sgSFX[nSFX].pSnd = sound_file_load(sgSFX[nSFX].pszName, - /*stream=*/AllowStreaming && (sgSFX[nSFX].bFlags & sfx_STREAM) != 0); - return sgSFX[nSFX].pSnd->DSB.GetLength(); + TSFX &sfx = sgSFX[static_cast(nSFX)]; + if (sfx.pSnd == nullptr) + sfx.pSnd = sound_file_load(sfx.pszName.c_str(), + /*stream=*/AllowStreaming && (sfx.bFlags & sfx_STREAM) != 0); + return sfx.pSnd->DSB.GetLength(); } } // namespace devilution diff --git a/Source/effects.h b/Source/effects.h index d1ba35b62f2..0addb5b4a24 100644 --- a/Source/effects.h +++ b/Source/effects.h @@ -7,8 +7,8 @@ #include #include +#include -#include "engine.h" #include "engine/sound.h" namespace devilution { @@ -121,939 +121,933 @@ enum class HeroSpeech : uint8_t { LAST = AuughUh }; -enum _sfx_id : int16_t { - PS_WALK1, - PS_BFIRE, - PS_TMAG, - PS_SWING, - PS_SWING2, - PS_DEAD, - IS_STING1, - IS_FBALLBOW, - IS_QUESTDN, - IS_BARLFIRE, - IS_BARREL, - IS_POPPOP8, - IS_POPPOP5, - IS_POPPOP3, - IS_POPPOP2, - IS_BHIT, - IS_BHIT1, - IS_CHEST, - IS_DOORCLOS, - IS_DOOROPEN, - IS_FANVL, - IS_FAXE, - IS_FBLST, - IS_FBODY, - IS_FBOOK, - IS_FBOW, - IS_FCAP, - IS_FHARM, - IS_FLARM, - IS_FMUSH, - IS_FPOT, - IS_FRING, - IS_FROCK, - IS_FSCRL, - IS_FSHLD, - IS_FSIGN, - IS_FSTAF, - IS_FSWOR, - IS_GOLD, - IS_IANVL, - IS_IAXE, - IS_IBLST, - IS_IBODY, - IS_IBOOK, - IS_IBOW, - IS_ICAP, - IS_IGRAB, - IS_IHARM, - IS_ILARM, - IS_IMUSH, - IS_IPOT, - IS_IRING, - IS_IROCK, - IS_ISCROL, - IS_ISHIEL, - IS_ISIGN, - IS_ISTAF, - IS_ISWORD, - IS_LEVER, - IS_MAGIC, - IS_MAGIC1, - IS_RBOOK, - IS_SARC, - IS_TITLEMOV, - IS_TITLSLCT, - IS_TRAP, - IS_CAST2, - IS_CAST4, - IS_CAST6, - IS_CAST7, - IS_CAST8, - IS_REPAIR, - LS_ACID, - LS_ACIDS, - LS_APOC, - LS_BLODSTAR, - LS_BLSIMPT, - LS_BONESP, - LS_BSIMPCT, - LS_CALDRON, - LS_CBOLT, - LS_DSERP, - LS_ELECIMP1, - LS_ELEMENTL, - LS_ETHEREAL, - LS_FBOLT1, - LS_FIRIMP2, - LS_FLAMWAVE, - LS_FOUNTAIN, - LS_GOLUM, - LS_GSHRINE, - LS_GUARD, - LS_GUARDLAN, - LS_HOLYBOLT, - LS_INFRAVIS, - LS_INVISIBL, - LS_LNING1, - LS_MSHIELD, - LS_NESTXPLD, - LS_NOVA, - LS_PUDDLE, - LS_RESUR, - LS_SCURIMP, - LS_SENTINEL, - LS_SPOUTSTR, - LS_TRAPDIS, - LS_TELEPORT, - LS_WALLLOOP, - LS_LMAG, - TSFX_BMAID1, - TSFX_BMAID2, - TSFX_BMAID3, - TSFX_BMAID4, - TSFX_BMAID5, - TSFX_BMAID6, - TSFX_BMAID7, - TSFX_BMAID8, - TSFX_BMAID9, - TSFX_BMAID10, - TSFX_BMAID11, - TSFX_BMAID12, - TSFX_BMAID13, - TSFX_BMAID14, - TSFX_BMAID15, - TSFX_BMAID16, - TSFX_BMAID17, - TSFX_BMAID18, - TSFX_BMAID19, - TSFX_BMAID20, - TSFX_BMAID21, - TSFX_BMAID22, - TSFX_BMAID23, - TSFX_BMAID24, - TSFX_BMAID25, - TSFX_BMAID26, - TSFX_BMAID27, - TSFX_BMAID28, - TSFX_BMAID29, - TSFX_BMAID30, - TSFX_BMAID31, - TSFX_BMAID32, - TSFX_BMAID33, - TSFX_BMAID34, - TSFX_BMAID35, - TSFX_BMAID36, - TSFX_BMAID37, - TSFX_BMAID38, - TSFX_BMAID39, - TSFX_BMAID40, - TSFX_SMITH1, - TSFX_SMITH2, - TSFX_SMITH3, - TSFX_SMITH4, - TSFX_SMITH5, - TSFX_SMITH6, - TSFX_SMITH7, - TSFX_SMITH8, - TSFX_SMITH9, - TSFX_SMITH10, - TSFX_SMITH11, - TSFX_SMITH12, - TSFX_SMITH13, - TSFX_SMITH14, - TSFX_SMITH15, - TSFX_SMITH16, - TSFX_SMITH17, - TSFX_SMITH18, - TSFX_SMITH19, - TSFX_SMITH20, - TSFX_SMITH21, - TSFX_SMITH22, - TSFX_SMITH23, - TSFX_SMITH24, - TSFX_SMITH25, - TSFX_SMITH26, - TSFX_SMITH27, - TSFX_SMITH28, - TSFX_SMITH29, - TSFX_SMITH30, - TSFX_SMITH31, - TSFX_SMITH32, - TSFX_SMITH33, - TSFX_SMITH34, - TSFX_SMITH35, - TSFX_SMITH36, - TSFX_SMITH37, - TSFX_SMITH38, - TSFX_SMITH39, - TSFX_SMITH40, - TSFX_SMITH41, - TSFX_SMITH42, - TSFX_SMITH43, - TSFX_SMITH44, - TSFX_SMITH45, - TSFX_SMITH46, - TSFX_SMITH47, - TSFX_SMITH48, - TSFX_SMITH49, - TSFX_SMITH50, - TSFX_SMITH51, - TSFX_SMITH52, - TSFX_SMITH53, - TSFX_SMITH54, - TSFX_SMITH55, - TSFX_SMITH56, - TSFX_COW1, - TSFX_COW2, - /* - TSFX_COW3, - TSFX_COW4, - TSFX_COW5, - TSFX_COW6, - */ - TSFX_COW7, - TSFX_COW8, - TSFX_DEADGUY, - TSFX_DRUNK1, - TSFX_DRUNK2, - TSFX_DRUNK3, - TSFX_DRUNK4, - TSFX_DRUNK5, - TSFX_DRUNK6, - TSFX_DRUNK7, - TSFX_DRUNK8, - TSFX_DRUNK9, - TSFX_DRUNK10, - TSFX_DRUNK11, - TSFX_DRUNK12, - TSFX_DRUNK13, - TSFX_DRUNK14, - TSFX_DRUNK15, - TSFX_DRUNK16, - TSFX_DRUNK17, - TSFX_DRUNK18, - TSFX_DRUNK19, - TSFX_DRUNK20, - TSFX_DRUNK21, - TSFX_DRUNK22, - TSFX_DRUNK23, - TSFX_DRUNK24, - TSFX_DRUNK25, - TSFX_DRUNK26, - TSFX_DRUNK27, - TSFX_DRUNK28, - TSFX_DRUNK29, - TSFX_DRUNK30, - TSFX_DRUNK31, - TSFX_DRUNK32, - TSFX_DRUNK33, - TSFX_DRUNK34, - TSFX_DRUNK35, - TSFX_HEALER1, - TSFX_HEALER2, - TSFX_HEALER3, - TSFX_HEALER4, - TSFX_HEALER5, - TSFX_HEALER6, - TSFX_HEALER7, - TSFX_HEALER8, - TSFX_HEALER9, - TSFX_HEALER10, - TSFX_HEALER11, - TSFX_HEALER12, - TSFX_HEALER13, - TSFX_HEALER14, - TSFX_HEALER15, - TSFX_HEALER16, - TSFX_HEALER17, - TSFX_HEALER18, - TSFX_HEALER19, - TSFX_HEALER20, - TSFX_HEALER21, - TSFX_HEALER22, - TSFX_HEALER23, - TSFX_HEALER24, - TSFX_HEALER25, - TSFX_HEALER26, - TSFX_HEALER27, - TSFX_HEALER28, - TSFX_HEALER29, - TSFX_HEALER30, - TSFX_HEALER31, - TSFX_HEALER32, - TSFX_HEALER33, - TSFX_HEALER34, - TSFX_HEALER35, - TSFX_HEALER36, - TSFX_HEALER37, - TSFX_HEALER38, - TSFX_HEALER39, - TSFX_HEALER40, - TSFX_HEALER41, - TSFX_HEALER42, - TSFX_HEALER43, - TSFX_HEALER44, - TSFX_HEALER45, - TSFX_HEALER46, - TSFX_HEALER47, - TSFX_PEGBOY1, - TSFX_PEGBOY2, - TSFX_PEGBOY3, - TSFX_PEGBOY4, - TSFX_PEGBOY5, - TSFX_PEGBOY6, - TSFX_PEGBOY7, - TSFX_PEGBOY8, - TSFX_PEGBOY9, - TSFX_PEGBOY10, - TSFX_PEGBOY11, - TSFX_PEGBOY12, - TSFX_PEGBOY13, - TSFX_PEGBOY14, - TSFX_PEGBOY15, - TSFX_PEGBOY16, - TSFX_PEGBOY17, - TSFX_PEGBOY18, - TSFX_PEGBOY19, - TSFX_PEGBOY20, - TSFX_PEGBOY21, - TSFX_PEGBOY22, - TSFX_PEGBOY23, - TSFX_PEGBOY24, - TSFX_PEGBOY25, - TSFX_PEGBOY26, - TSFX_PEGBOY27, - TSFX_PEGBOY28, - TSFX_PEGBOY29, - TSFX_PEGBOY30, - TSFX_PEGBOY31, - TSFX_PEGBOY32, - TSFX_PEGBOY33, - TSFX_PEGBOY34, - TSFX_PEGBOY35, - TSFX_PEGBOY36, - TSFX_PEGBOY37, - TSFX_PEGBOY38, - TSFX_PEGBOY39, - TSFX_PEGBOY40, - TSFX_PEGBOY41, - TSFX_PEGBOY42, - TSFX_PEGBOY43, - TSFX_PRIEST0, - TSFX_PRIEST1, - TSFX_PRIEST2, - TSFX_PRIEST3, - TSFX_PRIEST4, - TSFX_PRIEST5, - TSFX_PRIEST6, - TSFX_PRIEST7, - TSFX_STORY0, - TSFX_STORY1, - TSFX_STORY2, - TSFX_STORY3, - TSFX_STORY4, - TSFX_STORY5, - TSFX_STORY6, - TSFX_STORY7, - TSFX_STORY8, - TSFX_STORY9, - TSFX_STORY10, - TSFX_STORY11, - TSFX_STORY12, - TSFX_STORY13, - TSFX_STORY14, - TSFX_STORY15, - TSFX_STORY16, - TSFX_STORY17, - TSFX_STORY18, - TSFX_STORY19, - TSFX_STORY20, - TSFX_STORY21, - TSFX_STORY22, - TSFX_STORY23, - TSFX_STORY24, - TSFX_STORY25, - TSFX_STORY26, - TSFX_STORY27, - TSFX_STORY28, - TSFX_STORY29, - TSFX_STORY30, - TSFX_STORY31, - TSFX_STORY32, - TSFX_STORY33, - TSFX_STORY34, - TSFX_STORY35, - TSFX_STORY36, - TSFX_STORY37, - TSFX_STORY38, - TSFX_TAVERN0, - TSFX_TAVERN1, - TSFX_TAVERN2, - TSFX_TAVERN3, - TSFX_TAVERN4, - TSFX_TAVERN5, - TSFX_TAVERN6, - TSFX_TAVERN7, - TSFX_TAVERN8, - TSFX_TAVERN9, - TSFX_TAVERN10, - TSFX_TAVERN11, - TSFX_TAVERN12, - TSFX_TAVERN13, - TSFX_TAVERN14, - TSFX_TAVERN15, - TSFX_TAVERN16, - TSFX_TAVERN17, - TSFX_TAVERN18, - TSFX_TAVERN19, - TSFX_TAVERN20, - TSFX_TAVERN21, - TSFX_TAVERN22, - TSFX_TAVERN23, - TSFX_TAVERN24, - TSFX_TAVERN25, - TSFX_TAVERN26, - TSFX_TAVERN27, - TSFX_TAVERN28, - TSFX_TAVERN29, - TSFX_TAVERN30, - TSFX_TAVERN31, - TSFX_TAVERN32, - TSFX_TAVERN33, - TSFX_TAVERN34, - TSFX_TAVERN35, - TSFX_TAVERN36, - TSFX_TAVERN37, - TSFX_TAVERN38, - TSFX_TAVERN39, - TSFX_TAVERN40, - TSFX_TAVERN41, - TSFX_TAVERN42, - TSFX_TAVERN43, - TSFX_TAVERN44, - TSFX_TAVERN45, - TSFX_WITCH1, - TSFX_WITCH2, - TSFX_WITCH3, - TSFX_WITCH4, - TSFX_WITCH5, - TSFX_WITCH6, - TSFX_WITCH7, - TSFX_WITCH8, - TSFX_WITCH9, - TSFX_WITCH10, - TSFX_WITCH11, - TSFX_WITCH12, - TSFX_WITCH13, - TSFX_WITCH14, - TSFX_WITCH15, - TSFX_WITCH16, - TSFX_WITCH17, - TSFX_WITCH18, - TSFX_WITCH19, - TSFX_WITCH20, - TSFX_WITCH21, - TSFX_WITCH22, - TSFX_WITCH23, - TSFX_WITCH24, - TSFX_WITCH25, - TSFX_WITCH26, - TSFX_WITCH27, - TSFX_WITCH28, - TSFX_WITCH29, - TSFX_WITCH30, - TSFX_WITCH31, - TSFX_WITCH32, - TSFX_WITCH33, - TSFX_WITCH34, - TSFX_WITCH35, - TSFX_WITCH36, - TSFX_WITCH37, - TSFX_WITCH38, - TSFX_WITCH39, - TSFX_WITCH40, - TSFX_WITCH41, - TSFX_WITCH42, - TSFX_WITCH43, - TSFX_WITCH44, - TSFX_WITCH45, - TSFX_WITCH46, - TSFX_WITCH47, - TSFX_WITCH48, - TSFX_WITCH49, - TSFX_WITCH50, - TSFX_WOUND, - PS_MAGE1, - PS_MAGE2, - PS_MAGE3, - PS_MAGE4, - PS_MAGE5, - PS_MAGE6, - PS_MAGE7, - PS_MAGE8, - PS_MAGE9, - PS_MAGE10, - PS_MAGE11, - PS_MAGE12, - PS_MAGE13, - PS_MAGE14, - PS_MAGE15, - PS_MAGE16, - PS_MAGE17, - PS_MAGE18, - PS_MAGE19, - PS_MAGE20, - PS_MAGE21, - PS_MAGE22, - PS_MAGE23, - PS_MAGE24, - PS_MAGE25, - PS_MAGE26, - PS_MAGE27, - PS_MAGE28, - PS_MAGE29, - PS_MAGE30, - PS_MAGE31, - PS_MAGE32, - PS_MAGE33, - PS_MAGE34, - PS_MAGE35, - PS_MAGE36, - PS_MAGE37, - PS_MAGE38, - PS_MAGE39, - PS_MAGE40, - PS_MAGE41, - PS_MAGE42, - PS_MAGE43, - PS_MAGE44, - PS_MAGE45, - PS_MAGE46, - PS_MAGE47, - PS_MAGE48, - PS_MAGE49, - PS_MAGE50, - PS_MAGE51, - PS_MAGE52, - PS_MAGE53, - PS_MAGE54, - PS_MAGE55, - PS_MAGE56, - PS_MAGE57, - PS_MAGE58, - PS_MAGE59, - PS_MAGE60, - PS_MAGE61, - PS_MAGE62, - PS_MAGE63, - PS_MAGE64, - PS_MAGE65, - PS_MAGE66, - PS_MAGE67, - PS_MAGE68, - PS_MAGE69, - PS_MAGE69B, - PS_MAGE70, - PS_MAGE71, - PS_MAGE72, - PS_MAGE73, - PS_MAGE74, - PS_MAGE75, - PS_MAGE76, - PS_MAGE77, - PS_MAGE78, - PS_MAGE79, - PS_MAGE80, - PS_MAGE81, - PS_MAGE82, - PS_MAGE83, - PS_MAGE84, - PS_MAGE85, - PS_MAGE86, - PS_MAGE87, - PS_MAGE88, - PS_MAGE89, - PS_MAGE90, - PS_MAGE91, - PS_MAGE92, - PS_MAGE93, - PS_MAGE94, - PS_MAGE95, - PS_MAGE96, - PS_MAGE97, - PS_MAGE98, - PS_MAGE99, - PS_MAGE100, - PS_MAGE101, - PS_MAGE102, - PS_ROGUE1, - PS_ROGUE2, - PS_ROGUE3, - PS_ROGUE4, - PS_ROGUE5, - PS_ROGUE6, - PS_ROGUE7, - PS_ROGUE8, - PS_ROGUE9, - PS_ROGUE10, - PS_ROGUE11, - PS_ROGUE12, - PS_ROGUE13, - PS_ROGUE14, - PS_ROGUE15, - PS_ROGUE16, - PS_ROGUE17, - PS_ROGUE18, - PS_ROGUE19, - PS_ROGUE20, - PS_ROGUE21, - PS_ROGUE22, - PS_ROGUE23, - PS_ROGUE24, - PS_ROGUE25, - PS_ROGUE26, - PS_ROGUE27, - PS_ROGUE28, - PS_ROGUE29, - PS_ROGUE30, - PS_ROGUE31, - PS_ROGUE32, - PS_ROGUE33, - PS_ROGUE34, - PS_ROGUE35, - PS_ROGUE36, - PS_ROGUE37, - PS_ROGUE38, - PS_ROGUE39, - PS_ROGUE40, - PS_ROGUE41, - PS_ROGUE42, - PS_ROGUE43, - PS_ROGUE44, - PS_ROGUE45, - PS_ROGUE46, - PS_ROGUE47, - PS_ROGUE48, - PS_ROGUE49, - PS_ROGUE50, - PS_ROGUE51, - PS_ROGUE52, - PS_ROGUE53, - PS_ROGUE54, - PS_ROGUE55, - PS_ROGUE56, - PS_ROGUE57, - PS_ROGUE58, - PS_ROGUE59, - PS_ROGUE60, - PS_ROGUE61, - PS_ROGUE62, - PS_ROGUE63, - PS_ROGUE64, - PS_ROGUE65, - PS_ROGUE66, - PS_ROGUE67, - PS_ROGUE68, - PS_ROGUE69, - PS_ROGUE69B, - PS_ROGUE70, - PS_ROGUE71, - PS_ROGUE72, - PS_ROGUE73, - PS_ROGUE74, - PS_ROGUE75, - PS_ROGUE76, - PS_ROGUE77, - PS_ROGUE78, - PS_ROGUE79, - PS_ROGUE80, - PS_ROGUE81, - PS_ROGUE82, - PS_ROGUE83, - PS_ROGUE84, - PS_ROGUE85, - PS_ROGUE86, - PS_ROGUE87, - PS_ROGUE88, - PS_ROGUE89, - PS_ROGUE90, - PS_ROGUE91, - PS_ROGUE92, - PS_ROGUE93, - PS_ROGUE94, - PS_ROGUE95, - PS_ROGUE96, - PS_ROGUE97, - PS_ROGUE98, - PS_ROGUE99, - PS_ROGUE100, - PS_ROGUE101, - PS_ROGUE102, - PS_WARR1, - PS_WARR2, - PS_WARR3, - PS_WARR4, - PS_WARR5, - PS_WARR6, - PS_WARR7, - PS_WARR8, - PS_WARR9, - PS_WARR10, - PS_WARR11, - PS_WARR12, - PS_WARR13, - PS_WARR14, - PS_WARR14B, - PS_WARR14C, - PS_WARR15, - PS_WARR15B, - PS_WARR15C, - PS_WARR16, - PS_WARR16B, - PS_WARR16C, - PS_WARR17, - PS_WARR18, - PS_WARR19, - PS_WARR20, - PS_WARR21, - PS_WARR22, - PS_WARR23, - PS_WARR24, - PS_WARR25, - PS_WARR26, - PS_WARR27, - PS_WARR28, - PS_WARR29, - PS_WARR30, - PS_WARR31, - PS_WARR32, - PS_WARR33, - PS_WARR34, - PS_WARR35, - PS_WARR36, - PS_WARR37, - PS_WARR38, - PS_WARR39, - PS_WARR40, - PS_WARR41, - PS_WARR42, - PS_WARR43, - PS_WARR44, - PS_WARR45, - PS_WARR46, - PS_WARR47, - PS_WARR48, - PS_WARR49, - PS_WARR50, - PS_WARR51, - PS_WARR52, - PS_WARR53, - PS_WARR54, - PS_WARR55, - PS_WARR56, - PS_WARR57, - PS_WARR58, - PS_WARR59, - PS_WARR60, - PS_WARR61, - PS_WARR62, - PS_WARR63, - PS_WARR64, - PS_WARR65, - PS_WARR66, - PS_WARR67, - PS_WARR68, - PS_WARR69, - PS_WARR69B, - PS_WARR70, - PS_WARR71, - PS_WARR72, - PS_WARR73, - PS_WARR74, - PS_WARR75, - PS_WARR76, - PS_WARR77, - PS_WARR78, - PS_WARR79, - PS_WARR80, - PS_WARR81, - PS_WARR82, - PS_WARR83, - PS_WARR84, - PS_WARR85, - PS_WARR86, - PS_WARR87, - PS_WARR88, - PS_WARR89, - PS_WARR90, - PS_WARR91, - PS_WARR92, - PS_WARR93, - PS_WARR94, - PS_WARR95, - PS_WARR95B, - PS_WARR95C, - PS_WARR95D, - PS_WARR95E, - PS_WARR95F, - PS_WARR96B, - PS_WARR97, - PS_WARR98, - PS_WARR99, - PS_WARR100, - PS_WARR101, - PS_WARR102, - PS_MONK1, - PS_MONK8, - PS_MONK9, - PS_MONK10, - PS_MONK11, - PS_MONK12, - PS_MONK13, - PS_MONK14, - PS_MONK15, - PS_MONK16, - PS_MONK24, - PS_MONK27, - PS_MONK29, - PS_MONK34, - PS_MONK35, - PS_MONK43, - PS_MONK46, - PS_MONK49, - PS_MONK50, - PS_MONK52, - PS_MONK54, - PS_MONK55, - PS_MONK56, - PS_MONK61, - PS_MONK62, - PS_MONK68, - PS_MONK69, - PS_MONK69B, - PS_MONK70, - PS_MONK71, - PS_MONK79, - PS_MONK80, - PS_MONK82, - PS_MONK83, - PS_MONK87, - PS_MONK88, - PS_MONK89, - PS_MONK91, - PS_MONK92, - PS_MONK94, - PS_MONK95, - PS_MONK96, - PS_MONK97, - PS_MONK98, - PS_MONK99, - PS_NAR1, - PS_NAR2, - PS_NAR3, - PS_NAR4, - PS_NAR5, - PS_NAR6, - PS_NAR7, - PS_NAR8, - PS_NAR9, - PS_DIABLVLINT, - USFX_CLEAVER, - USFX_GARBUD1, - USFX_GARBUD2, - USFX_GARBUD3, - USFX_GARBUD4, - USFX_IZUAL1, - USFX_LACH1, - USFX_LACH2, - USFX_LACH3, - USFX_LAZ1, - USFX_LAZ2, - USFX_SKING1, - USFX_SNOT1, - USFX_SNOT2, - USFX_SNOT3, - USFX_WARLRD1, - USFX_WLOCK1, - USFX_ZHAR1, - USFX_ZHAR2, - USFX_DIABLOD, - TSFX_FARMER1, - TSFX_FARMER2, - TSFX_FARMER2A, - TSFX_FARMER3, - TSFX_FARMER4, - TSFX_FARMER5, - TSFX_FARMER6, - TSFX_FARMER7, - TSFX_FARMER8, - TSFX_FARMER9, - TSFX_TEDDYBR1, - TSFX_TEDDYBR2, - TSFX_TEDDYBR3, - TSFX_TEDDYBR4, - USFX_DEFILER1, - USFX_DEFILER2, - USFX_DEFILER3, - USFX_DEFILER4, - USFX_DEFILER8, - USFX_DEFILER6, - USFX_DEFILER7, - USFX_NAKRUL1, - USFX_NAKRUL2, - USFX_NAKRUL3, - USFX_NAKRUL4, - USFX_NAKRUL5, - USFX_NAKRUL6, - PS_NARATR3, - TSFX_COWSUT1, - TSFX_COWSUT2, - TSFX_COWSUT3, - TSFX_COWSUT4, - TSFX_COWSUT4A, - TSFX_COWSUT5, - TSFX_COWSUT6, - TSFX_COWSUT7, - TSFX_COWSUT8, - TSFX_COWSUT9, - TSFX_COWSUT10, - TSFX_COWSUT11, - TSFX_COWSUT12, - USFX_SKLJRN1, - PS_NARATR6, - PS_NARATR7, - PS_NARATR8, - PS_NARATR5, - PS_NARATR9, - PS_NARATR4, - TSFX_TRADER1, - IS_CROPEN, - IS_CRCLOS, - SFX_NONE = -1, +enum class SfxID : int16_t { + Walk, + ShootBow, + CastSpell, + Swing, + Swing2, + WarriorDeath, + ShootBow2, + ShootFireballBow, + QuestDone, + BarrelExpload, + BarrelBreak, + PodExpload, + PodPop, + UrnExpload, + UrnBreak, + BrutalHit, // Unused + BrutalHit1, // Unused + ChestOpen, + DoorClose, + DoorOpen, + ItemAnvilFlip, + ItemAxeFlip, + ItemBloodStoneFlip, + ItemBodyPartFlip, + ItemBookFlip, + ItemBowFlip, + ItemCapFlip, + ItemArmorFlip, + ItemLeatherFlip, + ItemMushroomFlip, + ItemPotionFlip, + ItemRingFlip, + ItemRockFlip, + ItemScrollFlip, + ItemShieldFlip, + ItemSignFlip, + ItemStaffFlip, + ItemSwordFlip, + ItemGold, + ItemAnvil, + ItemAxe, + ItemBloodStone, + ItemBodyPart, + ItemBook, + ItemBow, + ItemCap, + GrabItem, + ItemArmor, + ItemLeather, + ItemMushroom, + ItemPotion, + ItemRing, + ItemRock, + ItemScroll, + ItemShield, + ItemSign, + ItemStaff, + ItemSword, + OperateLever, + OperateShrine, + OperateShrine1, + ReadBook, + Sarcophagus, + MenuMove, + MenuSelect, + TriggerTrap, + CastFire, + CastLightning, + CastSkill, + SpellEnd, + CastHealing, + SpellRepair, + SpellAcid, + SpellAcid1, + SpellApocalypse, + SpellBloodStar, + SpellBloodStarHit, + SpellBoneSpirit, + SpellBoneSpiritHit, + OperateCaldron, + SpellChargedBolt, + SpellDoomSerpents, // Unused + SpellLightningHit, + SpellElemental, + SpellEtherealize, + SpellFirebolt, + SpellFireHit, + SpellFlameWave, + OperateFountain, + SpellGolem, + OperateGoatShrine, + SpellGuardian, + SpellGuardianHit, // Unused + SpellHolyBolt, + SpellInfravision, + SpellInvisibility, // Unused + SpellLightning, + SpellManaShield, + BigExplosion, + SpellNova, + SpellPuddle, + SpellResurrect, + SpellStoneCurse, + SpellPortal, + SpellInferno, + SpellTrapDisarm, + SpellTeleport, + SpellFireWall, + SpellLightningWall, + Gillian1, + Gillian2, + Gillian3, + Gillian4, + Gillian5, + Gillian6, + Gillian7, + Gillian8, + Gillian9, + Gillian10, + Gillian11, + Gillian12, + Gillian13, + Gillian14, + Gillian15, + Gillian16, + Gillian17, + Gillian18, + Gillian19, + Gillian20, + Gillian21, + Gillian22, + Gillian23, + Gillian24, + Gillian25, + Gillian26, + Gillian27, + Gillian28, + Gillian29, + Gillian30, + Gillian31, + Gillian32, + Gillian33, + Gillian34, + Gillian35, + Gillian36, + Gillian37, + Gillian38, + Gillian39, + Gillian40, + Griswold1, + Griswold2, + Griswold3, + Griswold4, + Griswold5, + Griswold6, + Griswold7, + Griswold8, + Griswold9, + Griswold10, + Griswold11, + Griswold12, + Griswold13, + Griswold14, + Griswold15, + Griswold16, + Griswold17, + Griswold18, + Griswold19, + Griswold20, + Griswold21, + Griswold22, + Griswold23, + Griswold24, + Griswold25, + Griswold26, + Griswold27, + Griswold28, + Griswold29, + Griswold30, + Griswold31, + Griswold32, + Griswold33, + Griswold34, + Griswold35, + Griswold36, + Griswold37, + Griswold38, + Griswold39, + Griswold40, + Griswold41, + Griswold42, + Griswold43, + Griswold44, + Griswold45, + Griswold46, + Griswold47, + Griswold48, + Griswold49, + Griswold50, + Griswold51, + Griswold52, + Griswold53, + Griswold54, + Griswold55, + Griswold56, + Cow1, + Cow2, + Pig, + Duck, // Unused + WoundedTownsmanOld, // Unused + Farnham1, + Farnham2, + Farnham3, + Farnham4, + Farnham5, + Farnham6, + Farnham7, + Farnham8, + Farnham9, + Farnham10, + Farnham11, + Farnham12, + Farnham13, + Farnham14, + Farnham15, + Farnham16, + Farnham17, + Farnham18, + Farnham19, + Farnham20, + Farnham21, + Farnham22, + Farnham23, + Farnham24, + Farnham25, + Farnham26, + Farnham27, + Farnham28, + Farnham29, + Farnham30, + Farnham31, + Farnham32, + Farnham33, + Farnham34, + Farnham35, + Pepin1, + Pepin2, + Pepin3, + Pepin4, + Pepin5, + Pepin6, + Pepin7, + Pepin8, + Pepin9, + Pepin10, + Pepin11, + Pepin12, + Pepin13, + Pepin14, + Pepin15, + Pepin16, + Pepin17, + Pepin18, + Pepin19, + Pepin20, + Pepin21, + Pepin22, + Pepin23, + Pepin24, + Pepin25, + Pepin26, + Pepin27, + Pepin28, + Pepin29, + Pepin30, + Pepin31, + Pepin32, + Pepin33, + Pepin34, + Pepin35, + Pepin36, + Pepin37, + Pepin38, + Pepin39, + Pepin40, + Pepin41, + Pepin42, + Pepin43, + Pepin44, + Pepin45, + Pepin46, + Pepin47, + Wirt1, + Wirt2, + Wirt3, + Wirt4, + Wirt5, + Wirt6, + Wirt7, + Wirt8, + Wirt9, + Wirt10, + Wirt11, + Wirt12, + Wirt13, + Wirt14, + Wirt15, + Wirt16, + Wirt17, + Wirt18, + Wirt19, + Wirt20, + Wirt21, + Wirt22, + Wirt23, + Wirt24, + Wirt25, + Wirt26, + Wirt27, + Wirt28, + Wirt29, + Wirt30, + Wirt31, + Wirt32, + Wirt33, + Wirt34, + Wirt35, + Wirt36, + Wirt37, + Wirt38, + Wirt39, + Wirt40, + Wirt41, + Wirt42, + Wirt43, + Tremain0, // Unused + Tremain1, // Unused + Tremain2, // Unused + Tremain3, // Unused + Tremain4, // Unused + Tremain5, // Unused + Tremain6, // Unused + Tremain7, // Unused + Cain0, + Cain1, + Cain2, + Cain3, + Cain4, + Cain5, + Cain6, + Cain7, + Cain8, + Cain9, + Cain10, + Cain11, + Cain12, + Cain13, + Cain14, + Cain15, + Cain16, + Cain17, + Cain18, + Cain19, + Cain20, + Cain21, + Cain22, + Cain23, + Cain24, + Cain25, + Cain26, + Cain27, + Cain28, + Cain29, + Cain30, + Cain31, + Cain32, + Cain33, + Cain34, + Cain35, + Cain36, + Cain37, + Cain38, + Ogden0, + Ogden1, + Ogden2, + Ogden3, + Ogden4, + Ogden5, + Ogden6, + Ogden7, + Ogden8, + Ogden9, + Ogden10, + Ogden11, + Ogden12, + Ogden13, + Ogden14, + Ogden15, + Ogden16, + Ogden17, + Ogden18, + Ogden19, + Ogden20, + Ogden21, + Ogden22, + Ogden23, + Ogden24, + Ogden25, + Ogden26, + Ogden27, + Ogden28, + Ogden29, + Ogden30, + Ogden31, + Ogden32, + Ogden33, + Ogden34, + Ogden35, + Ogden36, + Ogden37, + Ogden38, + Ogden39, + Ogden40, + Ogden41, + Ogden42, + Ogden43, + Ogden44, + Ogden45, + Adria1, + Adria2, + Adria3, + Adria4, + Adria5, + Adria6, + Adria7, + Adria8, + Adria9, + Adria10, + Adria11, + Adria12, + Adria13, + Adria14, + Adria15, + Adria16, + Adria17, + Adria18, + Adria19, + Adria20, + Adria21, + Adria22, + Adria23, + Adria24, + Adria25, + Adria26, + Adria27, + Adria28, + Adria29, + Adria30, + Adria31, + Adria32, + Adria33, + Adria34, + Adria35, + Adria36, + Adria37, + Adria38, + Adria39, + Adria40, + Adria41, + Adria42, + Adria43, + Adria44, + Adria45, + Adria46, + Adria47, + Adria48, + Adria49, + Adria50, + WoundedTownsman, + Sorceror1, + Sorceror2, + Sorceror3, + Sorceror4, + Sorceror5, + Sorceror6, + Sorceror7, + Sorceror8, + Sorceror9, + Sorceror10, + Sorceror11, + Sorceror12, + Sorceror13, + Sorceror14, + Sorceror15, + Sorceror16, + Sorceror17, + Sorceror18, + Sorceror19, + Sorceror20, + Sorceror21, + Sorceror22, + Sorceror23, + Sorceror24, + Sorceror25, + Sorceror26, + Sorceror27, + Sorceror28, + Sorceror29, + Sorceror30, + Sorceror31, + Sorceror32, + Sorceror33, + Sorceror34, + Sorceror35, + Sorceror36, + Sorceror37, + Sorceror38, + Sorceror39, + Sorceror40, + Sorceror41, + Sorceror42, + Sorceror43, + Sorceror44, + Sorceror45, + Sorceror46, + Sorceror47, + Sorceror48, + Sorceror49, + Sorceror50, + Sorceror51, + Sorceror52, + Sorceror53, + Sorceror54, + Sorceror55, + Sorceror56, + Sorceror57, + Sorceror58, + Sorceror59, + Sorceror60, + Sorceror61, + Sorceror62, + Sorceror63, + Sorceror64, + Sorceror65, + Sorceror66, + Sorceror67, + Sorceror68, + Sorceror69, + Sorceror69b, + Sorceror70, + Sorceror71, + Sorceror72, + Sorceror73, + Sorceror74, + Sorceror75, + Sorceror76, + Sorceror77, + Sorceror78, + Sorceror79, + Sorceror80, + Sorceror81, + Sorceror82, + Sorceror83, + Sorceror84, + Sorceror85, + Sorceror86, + Sorceror87, + Sorceror88, + Sorceror89, + Sorceror90, + Sorceror91, + Sorceror92, + Sorceror93, + Sorceror94, + Sorceror95, + Sorceror96, + Sorceror97, + Sorceror98, + Sorceror99, + Sorceror100, + Sorceror101, + Sorceror102, + Rogue1, + Rogue2, + Rogue3, + Rogue4, + Rogue5, + Rogue6, + Rogue7, + Rogue8, + Rogue9, + Rogue10, + Rogue11, + Rogue12, + Rogue13, + Rogue14, + Rogue15, + Rogue16, + Rogue17, + Rogue18, + Rogue19, + Rogue20, + Rogue21, + Rogue22, + Rogue23, + Rogue24, + Rogue25, + Rogue26, + Rogue27, + Rogue28, + Rogue29, + Rogue30, + Rogue31, + Rogue32, + Rogue33, + Rogue34, + Rogue35, + Rogue36, + Rogue37, + Rogue38, + Rogue39, + Rogue40, + Rogue41, + Rogue42, + Rogue43, + Rogue44, + Rogue45, + Rogue46, + Rogue47, + Rogue48, + Rogue49, + Rogue50, + Rogue51, + Rogue52, + Rogue53, + Rogue54, + Rogue55, + Rogue56, + Rogue57, + Rogue58, + Rogue59, + Rogue60, + Rogue61, + Rogue62, + Rogue63, + Rogue64, + Rogue65, + Rogue66, + Rogue67, + Rogue68, + Rogue69, + Rogue69b, + Rogue70, + Rogue71, + Rogue72, + Rogue73, + Rogue74, + Rogue75, + Rogue76, + Rogue77, + Rogue78, + Rogue79, + Rogue80, + Rogue81, + Rogue82, + Rogue83, + Rogue84, + Rogue85, + Rogue86, + Rogue87, + Rogue88, + Rogue89, + Rogue90, + Rogue91, + Rogue92, + Rogue93, + Rogue94, + Rogue95, + Rogue96, + Rogue97, + Rogue98, + Rogue99, + Rogue100, + Rogue101, + Rogue102, + Warrior1, + Warrior2, + Warrior3, + Warrior4, + Warrior5, + Warrior6, + Warrior7, + Warrior8, + Warrior9, + Warrior10, + Warrior11, + Warrior12, + Warrior13, + Warrior14, + Warrior14b, + Warrior14c, + Warrior15, + Warrior15b, + Warrior15c, + Warrior16, + Warrior16b, + Warrior16c, + Warrior17, + Warrior18, + Warrior19, + Warrior20, + Warrior21, + Warrior22, + Warrior23, + Warrior24, + Warrior25, + Warrior26, + Warrior27, + Warrior28, + Warrior29, + Warrior30, + Warrior31, + Warrior32, + Warrior33, + Warrior34, + Warrior35, + Warrior36, + Warrior37, + Warrior38, + Warrior39, + Warrior40, + Warrior41, + Warrior42, + Warrior43, + Warrior44, + Warrior45, + Warrior46, + Warrior47, + Warrior48, + Warrior49, + Warrior50, + Warrior51, + Warrior52, + Warrior53, + Warrior54, + Warrior55, + Warrior56, + Warrior57, + Warrior58, + Warrior59, + Warrior60, + Warrior61, + Warrior62, + Warrior63, + Warrior64, + Warrior65, + Warrior66, + Warrior67, + Warrior68, + Warrior69, + Warrior69b, + Warrior70, + Warrior71, + Warrior72, + Warrior73, + Warrior74, + Warrior75, + Warrior76, + Warrior77, + Warrior78, + Warrior79, + Warrior80, + Warrior81, + Warrior82, + Warrior83, + Warrior84, + Warrior85, + Warrior86, + Warrior87, + Warrior88, + Warrior89, + Warrior90, + Warrior91, + Warrior92, + Warrior93, + Warrior94, + Warrior95, + Warrior95b, + Warrior95c, + Warrior95d, + Warrior95e, + Warrior95f, + Warrior96b, + Warrior97, + Warrior98, + Warrior99, + Warrior100, + Warrior101, + Warrior102, + Monk1, + Monk8, + Monk9, + Monk10, + Monk11, + Monk12, + Monk13, + Monk14, + Monk15, + Monk16, + Monk24, + Monk27, + Monk29, + Monk34, + Monk35, + Monk43, + Monk46, + Monk49, + Monk50, + Monk52, + Monk54, + Monk55, + Monk56, + Monk61, + Monk62, + Monk68, + Monk69, + Monk69b, + Monk70, + Monk71, + Monk79, + Monk80, + Monk82, + Monk83, + Monk87, + Monk88, + Monk89, + Monk91, + Monk92, + Monk94, + Monk95, + Monk96, + Monk97, + Monk98, + Monk99, + Narrator1, + Narrator2, + Narrator3, + Narrator4, + Narrator5, + Narrator6, + Narrator7, + Narrator8, + Narrator9, + DiabloGreeting, + ButcherGreeting, + Gharbad1, + Gharbad2, + Gharbad3, + Gharbad4, + Izual, // Unused + Lachdanan1, + Lachdanan2, + Lachdanan3, + LazarusGreeting, + LazarusGreetingShort, // Unused + LeoricGreeting, + Snotspill1, + Snotspill2, + Snotspill3, + Warlord, + Warlock, // Unused + Zhar1, + Zhar2, + DiabloDeath, + Farmer1, + Farmer2, + Farmer2a, + Farmer3, + Farmer4, + Farmer5, + Farmer6, + Farmer7, + Farmer8, + Farmer9, + Celia1, + Celia2, + Celia3, + Celia4, + Defiler1, + Defiler2, + Defiler3, + Defiler4, + Defiler8, + Defiler6, + Defiler7, + NaKrul1, + NaKrul2, + NaKrul3, + NaKrul4, + NaKrul5, + NaKrul6, + NarratorHF3, + CompleteNut1, + CompleteNut2, + CompleteNut3, + CompleteNut4, + CompleteNut4a, + CompleteNut5, + CompleteNut6, + CompleteNut7, + CompleteNut8, + CompleteNut9, + CompleteNut10, + CompleteNut11, + CompleteNut12, + NarratorHF6, + NarratorHF7, + NarratorHF8, + NarratorHF5, + NarratorHF9, + NarratorHF4, + CryptDoorOpen, + CryptDoorClose, + + LAST = SfxID::CryptDoorClose, + None = -1, }; enum sfx_flag : uint8_t { @@ -1071,23 +1065,23 @@ enum sfx_flag : uint8_t { struct TSFX { uint8_t bFlags; - const char *pszName; + std::string pszName; std::unique_ptr pSnd; }; extern int sfxdelay; -extern _sfx_id sfxdnum; +extern SfxID sfxdnum; -bool effect_is_playing(int nSFX); +bool effect_is_playing(SfxID nSFX); void stream_stop(); -void PlaySFX(_sfx_id psfx); -void PlaySfxLoc(_sfx_id psfx, Point position, bool randomizeByCategory = true); +void PlaySFX(SfxID psfx); +void PlaySfxLoc(SfxID psfx, Point position, bool randomizeByCategory = true); void sound_stop(); void sound_update(); void effects_cleanup_sfx(); void sound_init(); void ui_sound_init(); -void effects_play_sound(_sfx_id); -int GetSFXLength(int nSFX); +void effects_play_sound(SfxID); +int GetSFXLength(SfxID nSFX); } // namespace devilution diff --git a/Source/effects_stubs.cpp b/Source/effects_stubs.cpp index 40d8e430cc0..29fd401104b 100644 --- a/Source/effects_stubs.cpp +++ b/Source/effects_stubs.cpp @@ -5,38 +5,35 @@ namespace devilution { int sfxdelay; -_sfx_id sfxdnum; +SfxID sfxdnum; -// Disable clang-format here because our config says: -// AllowShortFunctionsOnASingleLine: None -// clang-format off -bool effect_is_playing(int nSFX) { return false; } +bool effect_is_playing(SfxID nSFX) { return false; } void stream_stop() { } -void PlaySFX(_sfx_id psfx) +void PlaySFX(SfxID psfx) { switch (psfx) { - case PS_WARR69: - case PS_MAGE69: - case PS_ROGUE69: - case PS_MONK69: - case PS_SWING: - case LS_ACID: - case IS_MAGIC: - case IS_BHIT: - case PS_WARR14: - case PS_WARR15: - case PS_WARR16: - case PS_WARR2: - case PS_ROGUE14: - case PS_MAGE14: - case PS_MONK14: + case SfxID::Warrior69: + case SfxID::Sorceror69: + case SfxID::Rogue69: + case SfxID::Monk69: + case SfxID::Swing: + case SfxID::SpellAcid: + case SfxID::OperateShrine: + case SfxID::BrutalHit: + case SfxID::Warrior14: + case SfxID::Warrior15: + case SfxID::Warrior16: + case SfxID::Warrior2: + case SfxID::Rogue14: + case SfxID::Sorceror14: + case SfxID::Monk14: AdvanceRndSeed(); break; default: break; } } -void PlaySfxLoc(_sfx_id psfx, Point position, bool randomizeByCategory) +void PlaySfxLoc(SfxID psfx, Point position, bool randomizeByCategory) { if (!randomizeByCategory) return; @@ -48,8 +45,7 @@ void sound_update() { } void effects_cleanup_sfx() { } void sound_init() { } void ui_sound_init() { } -void effects_play_sound(_sfx_id id) { } -int GetSFXLength(int nSFX) { return 0; } -// clang-format off +void effects_play_sound(SfxID id) { } +int GetSFXLength(SfxID nSFX) { return 0; } } // namespace devilution diff --git a/Source/encrypt.cpp b/Source/encrypt.cpp index 48101ff1356..3af23e2afb6 100644 --- a/Source/encrypt.cpp +++ b/Source/encrypt.cpp @@ -18,6 +18,14 @@ namespace devilution { namespace { +struct TDataInfo { + std::byte *srcData; + uint32_t srcOffset; + std::byte *destData; + uint32_t destOffset; + uint32_t size; +}; + unsigned int PkwareBufferRead(char *buf, unsigned int *size, void *param) // NOLINT(readability-non-const-parameter) { auto *pInfo = reinterpret_cast(param); @@ -43,66 +51,9 @@ void PkwareBufferWrite(char *buf, unsigned int *size, void *param) // NOLINT(rea pInfo->destOffset += *size; } -const std::array, 5> hashtable = []() { - uint32_t seed = 0x00100001; - std::array, 5> ret = {}; - - for (int i = 0; i < 256; i++) { - for (int j = 0; j < 5; j++) { // NOLINT(modernize-loop-convert) - seed = (125 * seed + 3) % 0x2AAAAB; - uint32_t ch = (seed & 0xFFFF); - seed = (125 * seed + 3) % 0x2AAAAB; - ret[j][i] = ch << 16 | (seed & 0xFFFF); - } - } - return ret; -}(); - } // namespace -void Decrypt(uint32_t *castBlock, uint32_t size, uint32_t key) -{ - uint32_t seed = 0xEEEEEEEE; - for (uint32_t i = 0; i < (size >> 2); i++) { - uint32_t t = SDL_SwapLE32(*castBlock); - seed += hashtable[4][(key & 0xFF)]; - t ^= seed + key; - *castBlock = t; - seed += t + (seed << 5) + 3; - castBlock++; - key = (((key << 0x15) ^ 0xFFE00000) + 0x11111111) | (key >> 0x0B); - } -} - -void Encrypt(uint32_t *castBlock, uint32_t size, uint32_t key) -{ - uint32_t seed = 0xEEEEEEEE; - for (unsigned i = 0; i < (size >> 2); i++) { - uint32_t ch = *castBlock; - uint32_t t = ch; - seed += hashtable[4][(key & 0xFF)]; - t ^= seed + key; - *castBlock = SDL_SwapLE32(t); - castBlock++; - seed += ch + (seed << 5) + 3; - key = (((key << 0x15) ^ 0xFFE00000) + 0x11111111) | (key >> 0x0B); - } -} - -uint32_t Hash(const char *s, int type) -{ - uint32_t seed1 = 0x7FED7FED; - uint32_t seed2 = 0xEEEEEEEE; - while (s != nullptr && (*s != '\0')) { - int8_t ch = *s++; - ch = toupper(ch); - seed1 = hashtable[type][ch] ^ (seed1 + seed2); - seed2 += ch + seed1 + (seed2 << 5) + 3; - } - return seed1; -} - -uint32_t PkwareCompress(byte *srcData, uint32_t size) +uint32_t PkwareCompress(std::byte *srcData, uint32_t size) { std::unique_ptr ptr = std::make_unique(CMP_BUFFER_SIZE); @@ -110,7 +61,7 @@ uint32_t PkwareCompress(byte *srcData, uint32_t size) if (destSize < 2 * 4096) destSize = 2 * 4096; - std::unique_ptr destData { new byte[destSize] }; + std::unique_ptr destData { new std::byte[destSize] }; TDataInfo param; param.srcData = srcData; @@ -131,10 +82,10 @@ uint32_t PkwareCompress(byte *srcData, uint32_t size) return size; } -void PkwareDecompress(byte *inBuff, uint32_t recvSize, int maxBytes) +void PkwareDecompress(std::byte *inBuff, uint32_t recvSize, int maxBytes) { std::unique_ptr ptr = std::make_unique(CMP_BUFFER_SIZE); - std::unique_ptr outBuff { new byte[maxBytes] }; + std::unique_ptr outBuff { new std::byte[maxBytes] }; TDataInfo info; info.srcData = inBuff; diff --git a/Source/encrypt.h b/Source/encrypt.h index b6ec9c7930d..66f841a3f25 100644 --- a/Source/encrypt.h +++ b/Source/encrypt.h @@ -5,24 +5,12 @@ */ #pragma once +#include #include -#include "utils/stdcompat/cstddef.hpp" - namespace devilution { -struct TDataInfo { - byte *srcData; - uint32_t srcOffset; - byte *destData; - uint32_t destOffset; - uint32_t size; -}; - -void Decrypt(uint32_t *castBlock, uint32_t size, uint32_t key); -void Encrypt(uint32_t *castBlock, uint32_t size, uint32_t key); -uint32_t Hash(const char *s, int type); -uint32_t PkwareCompress(byte *srcData, uint32_t size); -void PkwareDecompress(byte *inBuff, uint32_t recvSize, int maxBytes); +uint32_t PkwareCompress(std::byte *srcData, uint32_t size); +void PkwareDecompress(std::byte *inBuff, uint32_t recvSize, int maxBytes); } // namespace devilution diff --git a/Source/engine.cpp b/Source/engine.cpp index 909733cd80e..c1705f7ccf3 100644 --- a/Source/engine.cpp +++ b/Source/engine.cpp @@ -11,11 +11,11 @@ * - Video playback */ -#include #include #include #include +#include "engine/palette.h" #include "lighting.h" #include "movie.h" #include "options.h" @@ -26,7 +26,7 @@ namespace { void DrawHalfTransparentUnalignedBlendedRectTo(const Surface &out, unsigned sx, unsigned sy, unsigned width, unsigned height) { uint8_t *pix = out.at(static_cast(sx), static_cast(sy)); - const std::array &lookupTable = paletteTransparencyLookup[0]; + const uint8_t *const lookupTable = paletteTransparencyLookup[0]; const unsigned skipX = out.pitch() - width; for (unsigned y = 0; y < height; ++y) { for (unsigned x = 0; x < width; ++x, ++pix) { @@ -52,11 +52,7 @@ void DrawHalfTransparentAligned32BlendedRectTo(const Surface &out, unsigned sx, while (height-- > 0) { for (unsigned i = 0; i < width; ++i, ++pix) { const uint32_t v = *pix; -#if SDL_BYTEORDER == SDL_LIL_ENDIAN *pix = lookupTable[v & 0xFFFF] | (lookupTable[(v >> 16) & 0xFFFF] << 16); -#else - *pix = lookupTable[(v >> 16) & 0xFFFF] | (lookupTable[v & 0xFFFF] << 16); -#endif } pix += skipX; } @@ -94,6 +90,13 @@ void DrawHalfTransparentBlendedRectTo(const Surface &out, unsigned sx, unsigned } // namespace +void FillRect(const Surface &out, int x, int y, int width, int height, uint8_t colorIndex) +{ + for (int j = 0; j < height; j++) { + DrawHorizontalLine(out, { x, y + j }, width, colorIndex); + } +} + void DrawHorizontalLine(const Surface &out, Point from, int width, std::uint8_t colorIndex) { if (from.y < 0 || from.y >= out.h() || from.x >= out.w() || width <= 0 || from.x + width <= 0) @@ -163,6 +166,15 @@ void DrawHalfTransparentRectTo(const Surface &out, int sx, int sy, int width, in DrawHalfTransparentBlendedRectTo(out, sx, sy, width, height); } +void SetHalfTransparentPixel(const Surface &out, Point position, uint8_t color) +{ + if (out.InBounds(position)) { + uint8_t *pix = out.at(position.x, position.y); + const auto &lookupTable = paletteTransparencyLookup[color]; + *pix = lookupTable[*pix]; + } +} + void UnsafeDrawBorder2px(const Surface &out, Rectangle rect, uint8_t color) { const size_t width = rect.size.width; diff --git a/Source/engine.h b/Source/engine.h index 6750e6c62ca..e246b95f8c7 100644 --- a/Source/engine.h +++ b/Source/engine.h @@ -16,7 +16,6 @@ #include #include #include -#include #include // We include `cinttypes` here so that it is included before `inttypes.h` @@ -25,6 +24,7 @@ // defines for `PRIuMAX` et al. SDL transitively includes `inttypes.h`. // See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=97044 #include +#include #include @@ -36,50 +36,26 @@ #include "engine/point.hpp" #include "engine/size.hpp" #include "engine/surface.hpp" -#include "utils/stdcompat/cstddef.hpp" - -#define TILE_WIDTH 64 -#define TILE_HEIGHT 32 +#include "utils/attributes.h" namespace devilution { -#if __cplusplus >= 201703L template -constexpr bool IsAnyOf(const V &v, X x, Xs... xs) +DVL_ALWAYS_INLINE constexpr bool IsAnyOf(const V &v, X x, Xs... xs) { return v == x || ((v == xs) || ...); } template -constexpr bool IsNoneOf(const V &v, X x, Xs... xs) +DVL_ALWAYS_INLINE constexpr bool IsNoneOf(const V &v, X x, Xs... xs) { return v != x && ((v != xs) && ...); } -#else -template -constexpr bool IsAnyOf(const V &v, X x) -{ - return v == x; -} -template -constexpr bool IsAnyOf(const V &v, X x, Xs... xs) -{ - return IsAnyOf(v, x) || IsAnyOf(v, xs...); -} - -template -constexpr bool IsNoneOf(const V &v, X x) -{ - return v != x; -} - -template -constexpr bool IsNoneOf(const V &v, X x, Xs... xs) -{ - return IsNoneOf(v, x) && IsNoneOf(v, xs...); -} -#endif +/** + * @brief Fill a rectangle with the given color. + */ +void FillRect(const Surface &out, int x, int y, int width, int height, uint8_t colorIndex); /** * @brief Draw a horizontal line segment in the target buffer (left to right) @@ -106,8 +82,7 @@ void DrawVerticalLine(const Surface &out, Point from, int height, std::uint8_t c void UnsafeDrawVerticalLine(const Surface &out, Point from, int height, std::uint8_t colorIndex); /** - * Draws a half-transparent rectangle by blacking out odd pixels on odd lines, - * even pixels on even lines. + * Draws a half-transparent rectangle by palette blending with black. * * @brief Render a transparent black rectangle * @param out Target buffer @@ -118,6 +93,16 @@ void UnsafeDrawVerticalLine(const Surface &out, Point from, int height, std::uin */ void DrawHalfTransparentRectTo(const Surface &out, int sx, int sy, int width, int height); +/** + * Draws a half-transparent pixel + * + * @brief Render a transparent pixel + * @param out Target buffer + * @param position Screen coordinates + * @param col Pixel color + */ +void SetHalfTransparentPixel(const Surface &out, Point position, uint8_t color); + /** * Draws a 2px inset border. * diff --git a/Source/engine/actor_position.cpp b/Source/engine/actor_position.cpp index 70a1fd5df64..31d4b91421d 100644 --- a/Source/engine/actor_position.cpp +++ b/Source/engine/actor_position.cpp @@ -108,9 +108,7 @@ constexpr std::array WalkParameters { { DisplacementOf ActorPosition::CalculateWalkingOffset(Direction dir, const AnimationInfo &animInfo) const { DisplacementOf offset = CalculateWalkingOffsetShifted4(dir, animInfo); - offset.deltaX >>= 4; - offset.deltaY >>= 4; - return offset; + return { static_cast(offset.deltaX >> 4), static_cast(offset.deltaY >> 4) }; } DisplacementOf ActorPosition::CalculateWalkingOffsetShifted4(Direction dir, const AnimationInfo &animInfo) const diff --git a/Source/engine/animationinfo.cpp b/Source/engine/animationinfo.cpp index cf6b10f59f8..79f18d76196 100644 --- a/Source/engine/animationinfo.cpp +++ b/Source/engine/animationinfo.cpp @@ -6,12 +6,12 @@ #include "animationinfo.h" +#include #include #include "appfat.h" #include "nthread.h" #include "utils/log.hpp" -#include "utils/stdcompat/algorithm.hpp" namespace devilution { @@ -182,7 +182,7 @@ void AnimationInfo::changeAnimationData(OptionalClxSpriteList celSprite, int8_t if (numberOfFrames != this->numberOfFrames || ticksPerFrame != this->ticksPerFrame) { // Ensure that the currentFrame is still valid and that we disable ADL cause the calculcated values (for example tickModifier_) could be wrong if (numberOfFrames >= 1) - currentFrame = clamp(currentFrame, 0, numberOfFrames - 1); + currentFrame = std::clamp(currentFrame, 0, numberOfFrames - 1); else currentFrame = -1; diff --git a/Source/engine/assets.cpp b/Source/engine/assets.cpp index 15b5278b228..d69026b6fef 100644 --- a/Source/engine/assets.cpp +++ b/Source/engine/assets.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "init.h" #include "utils/file_util.h" @@ -52,9 +53,9 @@ SDL_RWops *OpenOptionalRWops(const std::string &path) return SDL_RWFromFile(path.c_str(), "rb"); }; -bool FindMpqFile(const char *filename, MpqArchive **archive, uint32_t *fileNumber) +bool FindMpqFile(std::string_view filename, MpqArchive **archive, uint32_t *fileNumber) { - const MpqArchive::FileHash fileHash = MpqArchive::CalculateFileHash(filename); + const MpqFileHash fileHash = CalculateMpqFileHash(filename); const auto at = [=](std::optional &src) -> bool { if (src && src->GetFileNumber(fileHash, *fileNumber)) { *archive = &(*src); @@ -71,16 +72,17 @@ bool FindMpqFile(const char *filename, MpqArchive **archive, uint32_t *fileNumbe } // namespace #ifdef UNPACKED_MPQS -AssetRef FindAsset(const char *filename) +AssetRef FindAsset(std::string_view filename) { AssetRef result; + if (filename.empty() || filename.back() == '\\') + return result; result.path[0] = '\0'; - const string_view filenameStr = filename; char pathBuf[AssetRef::PathBufSize]; char *const pathEnd = pathBuf + AssetRef::PathBufSize; - char *const relativePath = &pathBuf[AssetRef::PathBufSize - filenameStr.size() - 1]; - *BufCopy(relativePath, filenameStr) = '\0'; + char *const relativePath = &pathBuf[AssetRef::PathBufSize - filename.size() - 1]; + *BufCopy(relativePath, filename) = '\0'; #ifndef _WIN32 std::replace(relativePath, pathEnd, '\\', '/'); @@ -88,7 +90,7 @@ AssetRef FindAsset(const char *filename) // Absolute path: if (relativePath[0] == '/') { if (FileExists(relativePath)) { - *BufCopy(result.path, string_view(relativePath, filenameStr.size())) = '\0'; + *BufCopy(result.path, std::string_view(relativePath, filename.size())) = '\0'; } return result; } @@ -96,7 +98,7 @@ AssetRef FindAsset(const char *filename) // Unpacked MPQ file: char *const unpackedMpqPath = FindUnpackedMpqFile(relativePath); if (unpackedMpqPath != nullptr) { - *BufCopy(result.path, string_view(unpackedMpqPath, pathEnd - unpackedMpqPath)) = '\0'; + *BufCopy(result.path, std::string_view(unpackedMpqPath, pathEnd - unpackedMpqPath)) = '\0'; return result; } @@ -105,15 +107,18 @@ AssetRef FindAsset(const char *filename) char *assetsPath = relativePath - assetsPathPrefix.size(); std::memcpy(assetsPath, assetsPathPrefix.data(), assetsPathPrefix.size()); if (FileExists(assetsPath)) { - *BufCopy(result.path, string_view(assetsPath, pathEnd - assetsPath)) = '\0'; + *BufCopy(result.path, std::string_view(assetsPath, pathEnd - assetsPath)) = '\0'; } return result; } #else -AssetRef FindAsset(const char *filename) +AssetRef FindAsset(std::string_view filename) { AssetRef result; - std::string relativePath = filename; + if (filename.empty() || filename.back() == '\\') + return result; + + std::string relativePath { filename }; #ifndef _WIN32 std::replace(relativePath.begin(), relativePath.end(), '\\', '/'); #endif @@ -177,7 +182,7 @@ AssetHandle OpenAsset(AssetRef &&ref, bool threadsafe) #endif } -AssetHandle OpenAsset(const char *filename, bool threadsafe) +AssetHandle OpenAsset(std::string_view filename, bool threadsafe) { AssetRef ref = FindAsset(filename); if (!ref.ok()) @@ -185,7 +190,7 @@ AssetHandle OpenAsset(const char *filename, bool threadsafe) return OpenAsset(std::move(ref), threadsafe); } -AssetHandle OpenAsset(const char *filename, size_t &fileSize, bool threadsafe) +AssetHandle OpenAsset(std::string_view filename, size_t &fileSize, bool threadsafe) { AssetRef ref = FindAsset(filename); if (!ref.ok()) @@ -194,7 +199,7 @@ AssetHandle OpenAsset(const char *filename, size_t &fileSize, bool threadsafe) return OpenAsset(std::move(ref), threadsafe); } -SDL_RWops *OpenAssetAsSdlRwOps(const char *filename, bool threadsafe) +SDL_RWops *OpenAssetAsSdlRwOps(std::string_view filename, bool threadsafe) { #ifdef UNPACKED_MPQS AssetRef ref = FindAsset(filename); @@ -206,4 +211,26 @@ SDL_RWops *OpenAssetAsSdlRwOps(const char *filename, bool threadsafe) #endif } +tl::expected LoadAsset(std::string_view path) +{ + AssetRef ref = FindAsset(path); + if (!ref.ok()) { + return tl::make_unexpected(StrCat("Asset not found: ", path)); + } + + const size_t size = ref.size(); + std::unique_ptr data { new char[size] }; + + AssetHandle handle = OpenAsset(std::move(ref)); + if (!handle.ok()) { + return tl::make_unexpected(StrCat("Failed to open asset: ", path, "\n", handle.error())); + } + + if (size > 0 && !handle.read(data.get(), size)) { + return tl::make_unexpected(StrCat("Read failed: ", path, "\n", handle.error())); + } + + return AssetData { std::move(data), size }; +} + } // namespace devilution diff --git a/Source/engine/assets.hpp b/Source/engine/assets.hpp index 12f78b35b7c..03a135d9a1b 100644 --- a/Source/engine/assets.hpp +++ b/Source/engine/assets.hpp @@ -4,8 +4,10 @@ #include #include #include +#include #include +#include #include "appfat.h" #include "diablo.h" @@ -96,7 +98,7 @@ struct AssetRef { // An MPQ file reference: MpqArchive *archive = nullptr; uint32_t fileNumber; - const char *filename; + std::string_view filename; // Alternatively, a direct SDL_RWops handle: SDL_RWops *directHandle = nullptr; @@ -147,7 +149,7 @@ struct AssetRef { int32_t error; return archive->GetUnpackedFileSize(fileNumber, error); } - return SDL_RWsize(directHandle); + return static_cast(SDL_RWsize(directHandle)); } }; @@ -190,7 +192,11 @@ struct AssetHandle { bool read(void *buffer, size_t len) { +#if SDL_VERSION_ATLEAST(2, 0, 0) return handle->read(handle, buffer, len, 1) == 1; +#else + return handle->read(handle, buffer, static_cast(len), 1) == 1; +#endif } bool seek(long pos) @@ -212,12 +218,12 @@ struct AssetHandle { }; #endif -[[noreturn]] inline void FailedToOpenFileError(const char *path, const char *error) +[[noreturn]] inline void FailedToOpenFileError(std::string_view path, std::string_view error) { app_fatal(StrCat("Failed to open file:\n", path, "\n\n", error)); } -inline bool ValidatAssetRef(const char *path, const AssetRef &ref) +inline bool ValidatAssetRef(std::string_view path, const AssetRef &ref) { if (ref.ok()) return true; @@ -227,7 +233,7 @@ inline bool ValidatAssetRef(const char *path, const AssetRef &ref) return false; } -inline bool ValidateHandle(const char *path, const AssetHandle &handle) +inline bool ValidateHandle(std::string_view path, const AssetHandle &handle) { if (handle.ok()) return true; @@ -237,12 +243,24 @@ inline bool ValidateHandle(const char *path, const AssetHandle &handle) return false; } -AssetRef FindAsset(const char *filename); +AssetRef FindAsset(std::string_view filename); AssetHandle OpenAsset(AssetRef &&ref, bool threadsafe = false); -AssetHandle OpenAsset(const char *filename, bool threadsafe = false); -AssetHandle OpenAsset(const char *filename, size_t &fileSize, bool threadsafe = false); +AssetHandle OpenAsset(std::string_view filename, bool threadsafe = false); +AssetHandle OpenAsset(std::string_view filename, size_t &fileSize, bool threadsafe = false); + +SDL_RWops *OpenAssetAsSdlRwOps(std::string_view filename, bool threadsafe = false); + +struct AssetData { + std::unique_ptr data; + size_t size; + + explicit operator std::string_view() const + { + return std::string_view(data.get(), size); + } +}; -SDL_RWops *OpenAssetAsSdlRwOps(const char *filename, bool threadsafe = false); +tl::expected LoadAsset(std::string_view path); } // namespace devilution diff --git a/Source/engine/demomode.cpp b/Source/engine/demomode.cpp index 128de0f2cf1..5a0a658b089 100644 --- a/Source/engine/demomode.cpp +++ b/Source/engine/demomode.cpp @@ -2,7 +2,10 @@ #include #include -#include +#include +#include + +#include #ifdef USE_SDL1 #include "utils/sdl2_to_1_2_backports.h" @@ -30,33 +33,29 @@ namespace devilution { namespace { +constexpr uint8_t Version = 3; + enum class LoadingStatus : uint8_t { Success, FileNotFound, UnsupportedVersion, }; -enum class DemoMsgType : uint8_t { - GameTick = 0, - Rendering = 1, - Message = 2, -}; - struct MouseMotionEventData { uint16_t x; uint16_t y; }; struct MouseButtonEventData { - uint8_t button; uint16_t x; uint16_t y; uint16_t mod; + uint8_t button; }; struct MouseWheelEventData { - int32_t x; - int32_t y; + int16_t x; + int16_t y; uint16_t mod; }; @@ -66,18 +65,44 @@ struct KeyEventData { }; struct DemoMsg { - DemoMsgType type; + enum EventType : uint8_t { + GameTick = 0, + Rendering = 1, + + // Inputs: + MinEvent = 8, + + QuitEvent = 8, + MouseMotionEvent = 9, + MouseButtonDownEvent = 10, + MouseButtonUpEvent = 11, + MouseWheelEvent = 12, + KeyDownEvent = 13, + KeyUpEvent = 14, + + MinCustomEvent = 64, + }; + + EventType type; uint8_t progressToNextGameTick; - uint32_t eventType; union { MouseMotionEventData motion; MouseButtonEventData button; MouseWheelEventData wheel; KeyEventData key; }; + + [[nodiscard]] bool isEvent() const + { + return type >= MinEvent; + } }; +FILE *DemoFile; +int DemoFileVersion; int DemoNumber = -1; +std::optional CurrentDemoMessage; + bool Timedemo = false; int RecordNumber = -1; bool CreateDemoReference = false; @@ -111,16 +136,15 @@ struct { } DemoSettings; FILE *DemoRecording; -std::deque Demo_Message_Queue; uint32_t DemoModeLastTick = 0; int LogicTick = 0; -int StartTime = 0; +uint32_t StartTime = 0; uint16_t DemoGraphicsWidth = 640; uint16_t DemoGraphicsHeight = 480; -void ReadSettings(FILE *in, uint8_t version) +void ReadSettings(FILE *in, uint8_t version) // NOLINT(readability-identifier-length) { DemoGraphicsWidth = ReadLE16(in); DemoGraphicsHeight = ReadLE16(in); @@ -151,29 +175,61 @@ void ReadSettings(FILE *in, uint8_t version) } else { DemoSettings = {}; } + + std::string message = fmt::format("⚙️\n{}={}x{}", _("Resolution"), DemoGraphicsWidth, DemoGraphicsHeight); + for (const auto &[key, value] : std::initializer_list> { + { _("Run in Town"), DemoSettings.runInTown }, + { _("Theo Quest"), DemoSettings.theoQuest }, + { _("Cow Quest"), DemoSettings.cowQuest }, + { _("Auto Gold Pickup"), DemoSettings.autoGoldPickup }, + { _("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 }, + { _("Auto Equip Shields"), DemoSettings.autoEquipShields }, + { _("Auto Equip Jewelry"), DemoSettings.autoEquipJewelry }, + { _("Randomize Quests"), DemoSettings.randomizeQuests }, + { _("Show Item Labels"), DemoSettings.showItemLabels }, + { _("Auto Refill Belt"), DemoSettings.autoRefillBelt }, + { _("Disable Crippling Shrines"), DemoSettings.disableCripplingShrines } }) { + fmt::format_to(std::back_inserter(message), "\n{}={:d}", key, value); + } + for (const auto &[key, value] : std::initializer_list> { + { _("Heal Potion Pickup"), DemoSettings.numHealPotionPickup }, + { _("Full Heal Potion Pickup"), DemoSettings.numFullHealPotionPickup }, + { _("Mana Potion Pickup"), DemoSettings.numManaPotionPickup }, + { _("Full Mana Potion Pickup"), DemoSettings.numFullManaPotionPickup }, + { _("Rejuvenation Potion Pickup"), DemoSettings.numRejuPotionPickup }, + { _("Full Rejuvenation Potion Pickup"), DemoSettings.numFullRejuPotionPickup } }) { + fmt::format_to(std::back_inserter(message), "\n{}={}", key, value); + } + Log("{}", message); } void WriteSettings(FILE *out) { WriteLE16(out, gnScreenWidth); WriteLE16(out, gnScreenHeight); - WriteByte(out, *sgOptions.Gameplay.runInTown); - WriteByte(out, *sgOptions.Gameplay.theoQuest); - WriteByte(out, *sgOptions.Gameplay.cowQuest); - WriteByte(out, *sgOptions.Gameplay.autoGoldPickup); - WriteByte(out, *sgOptions.Gameplay.autoElixirPickup); - WriteByte(out, *sgOptions.Gameplay.autoOilPickup); - WriteByte(out, *sgOptions.Gameplay.autoPickupInTown); - WriteByte(out, *sgOptions.Gameplay.adriaRefillsMana); - WriteByte(out, *sgOptions.Gameplay.autoEquipWeapons); - WriteByte(out, *sgOptions.Gameplay.autoEquipArmor); - WriteByte(out, *sgOptions.Gameplay.autoEquipHelms); - WriteByte(out, *sgOptions.Gameplay.autoEquipShields); - WriteByte(out, *sgOptions.Gameplay.autoEquipJewelry); - WriteByte(out, *sgOptions.Gameplay.randomizeQuests); - WriteByte(out, *sgOptions.Gameplay.showItemLabels); - WriteByte(out, *sgOptions.Gameplay.autoRefillBelt); - WriteByte(out, *sgOptions.Gameplay.disableCripplingShrines); + WriteByte(out, static_cast(*sgOptions.Gameplay.runInTown)); + WriteByte(out, static_cast(*sgOptions.Gameplay.theoQuest)); + WriteByte(out, static_cast(*sgOptions.Gameplay.cowQuest)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoGoldPickup)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoElixirPickup)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoOilPickup)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoPickupInTown)); + WriteByte(out, static_cast(*sgOptions.Gameplay.adriaRefillsMana)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoEquipWeapons)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoEquipArmor)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoEquipHelms)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoEquipShields)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoEquipJewelry)); + WriteByte(out, static_cast(*sgOptions.Gameplay.randomizeQuests)); + WriteByte(out, static_cast(*sgOptions.Gameplay.showItemLabels)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoRefillBelt)); + WriteByte(out, static_cast(*sgOptions.Gameplay.disableCripplingShrines)); WriteByte(out, *sgOptions.Gameplay.numHealPotionPickup); WriteByte(out, *sgOptions.Gameplay.numFullHealPotionPickup); WriteByte(out, *sgOptions.Gameplay.numManaPotionPickup); @@ -185,41 +241,45 @@ void WriteSettings(FILE *out) #if SDL_VERSION_ATLEAST(2, 0, 0) bool CreateSdlEvent(const DemoMsg &dmsg, SDL_Event &event, uint16_t &modState) { - event.type = dmsg.eventType; - switch (static_cast(dmsg.eventType)) { - case SDL_MOUSEMOTION: + const uint8_t type = dmsg.type; + switch (type) { + case DemoMsg::MouseMotionEvent: + event.type = SDL_MOUSEMOTION; event.motion.which = 0; event.motion.x = dmsg.motion.x; event.motion.y = dmsg.motion.y; return true; - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: + case DemoMsg::MouseButtonDownEvent: + case DemoMsg::MouseButtonUpEvent: + event.type = type == DemoMsg::MouseButtonDownEvent ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP; event.button.which = 0; event.button.button = dmsg.button.button; - event.button.state = dmsg.eventType == SDL_MOUSEBUTTONDOWN ? SDL_PRESSED : SDL_RELEASED; + event.button.state = type == DemoMsg::MouseButtonDownEvent ? SDL_PRESSED : SDL_RELEASED; event.button.x = dmsg.button.x; event.button.y = dmsg.button.y; modState = dmsg.button.mod; return true; - case SDL_MOUSEWHEEL: + case DemoMsg::MouseWheelEvent: + event.type = SDL_MOUSEWHEEL; event.wheel.which = 0; event.wheel.x = dmsg.wheel.x; event.wheel.y = dmsg.wheel.y; modState = dmsg.wheel.mod; return true; - case SDL_KEYDOWN: - case SDL_KEYUP: - event.key.state = dmsg.eventType == SDL_KEYDOWN ? SDL_PRESSED : SDL_RELEASED; + case DemoMsg::KeyDownEvent: + case DemoMsg::KeyUpEvent: + event.type = type == DemoMsg::KeyDownEvent ? SDL_KEYDOWN : SDL_KEYUP; + event.key.state = type == DemoMsg::KeyDownEvent ? SDL_PRESSED : SDL_RELEASED; event.key.keysym.sym = dmsg.key.sym; event.key.keysym.mod = dmsg.key.mod; return true; default: - if (dmsg.eventType >= SDL_USEREVENT) { - event.type = CustomEventToSdlEvent(static_cast(dmsg.eventType - SDL_USEREVENT)); + if (type >= DemoMsg::MinCustomEvent) { + event.type = CustomEventToSdlEvent(static_cast(type - DemoMsg::MinCustomEvent)); return true; } event.type = static_cast(0); - LogWarn("Unsupported demo event (type={:x})", dmsg.eventType); + LogWarn("Unsupported demo event (type={})", type); return false; } } @@ -274,24 +334,25 @@ uint8_t Sdl2ToSdl1MouseButton(uint8_t button) bool CreateSdlEvent(const DemoMsg &dmsg, SDL_Event &event, uint16_t &modState) { - switch (dmsg.eventType) { - case 0x400: + const uint8_t type = dmsg.type; + switch (type) { + case DemoMsg::MouseMotionEvent: event.type = SDL_MOUSEMOTION; event.motion.which = 0; event.motion.x = dmsg.motion.x; event.motion.y = dmsg.motion.y; return true; - case 0x401: - case 0x402: - event.type = dmsg.eventType == 0x401 ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP; + case DemoMsg::MouseButtonDownEvent: + case DemoMsg::MouseButtonUpEvent: + event.type = type == DemoMsg::MouseButtonDownEvent ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP; event.button.which = 0; event.button.button = Sdl2ToSdl1MouseButton(dmsg.button.button); - event.button.state = dmsg.eventType == 0x401 ? SDL_PRESSED : SDL_RELEASED; + event.button.state = type == DemoMsg::MouseButtonDownEvent ? SDL_PRESSED : SDL_RELEASED; event.button.x = dmsg.button.x; event.button.y = dmsg.button.y; modState = dmsg.button.mod; return true; - case 0x403: // SDL_MOUSEWHEEL + case DemoMsg::MouseWheelEvent: if (dmsg.wheel.y == 0) { LogWarn("Demo: unsupported event (mouse wheel y == 0)"); return false; @@ -301,160 +362,196 @@ bool CreateSdlEvent(const DemoMsg &dmsg, SDL_Event &event, uint16_t &modState) event.button.button = dmsg.wheel.y > 0 ? SDL_BUTTON_WHEELUP : SDL_BUTTON_WHEELDOWN; modState = dmsg.wheel.mod; return true; - case 0x300: - case 0x301: - event.type = dmsg.eventType == 0x300 ? SDL_KEYDOWN : SDL_KEYUP; + case DemoMsg::KeyDownEvent: + case DemoMsg::KeyUpEvent: + event.type = type == DemoMsg::KeyDownEvent ? SDL_KEYDOWN : SDL_KEYUP; event.key.which = 0; - event.key.state = dmsg.eventType == 0x300 ? SDL_PRESSED : SDL_RELEASED; + event.key.state = type == DemoMsg::KeyDownEvent ? SDL_PRESSED : SDL_RELEASED; event.key.keysym.sym = Sdl2ToSdl1Key(dmsg.key.sym); event.key.keysym.mod = static_cast(dmsg.key.mod); return true; default: - if (dmsg.eventType >= 0x8000) { - event.type = CustomEventToSdlEvent(static_cast(dmsg.eventType - 0x8000)); + if (type >= DemoMsg::MinCustomEvent) { + event.type = CustomEventToSdlEvent(static_cast(type - DemoMsg::MinCustomEvent)); return true; } event.type = static_cast(0); - LogWarn("Demo: unsupported event (type={:x})", dmsg.eventType); + LogWarn("Demo: unsupported event (type={:x})", type); return false; } } #endif -void LogDemoMessage(const DemoMsg &msg) +uint8_t MapPreV2DemoMsgEventType(uint16_t type) { -#ifdef LOG_DEMOMODE_MESSAGES - const uint8_t progressToNextGameTick = msg.progressToNextGameTick; - switch (msg.type) { - case DemoMsgType::Message: { - const uint32_t eventType = msg.eventType; - switch (eventType) { - case 0x400: // SDL_MOUSEMOTION -#ifdef LOG_DEMOMODE_MESSAGES_MOUSEMOTION - Log("🖱️ Message {:>3} MOUSEMOTION {} {}", progressToNextGameTick, - msg.motion.x, msg.motion.y); -#endif - break; - case 0x401: // SDL_MOUSEBUTTONDOWN - case 0x402: // SDL_MOUSEBUTTONUP - Log("🖱️ Message {:>3} {} {} {} {} 0x{:x}", progressToNextGameTick, - eventType == 0x401 ? "MOUSEBUTTONDOWN" : "MOUSEBUTTONUP", - msg.button.button, msg.button.x, msg.button.y, msg.button.mod); - break; - case 0x403: // SDL_MOUSEWHEEL - Log("🖱️ Message {:>3} MOUSEWHEEL {} {} 0x{:x}", progressToNextGameTick, - msg.wheel.x, msg.wheel.y, msg.wheel.mod); - break; - case 0x300: // SDL_KEYDOWN - case 0x301: // SDL_KEYUP - Log("🔤 Message {:>3} {} 0x{:x} 0x{:x}", progressToNextGameTick, - eventType == 0x300 ? "KEYDOWN" : "KEYUP", - msg.key.sym, msg.key.mod); - break; - case 0x100: // SDL_QUIT - Log("❎ Message {:>3} QUIT", progressToNextGameTick); - break; - default: - Log("📨 Message {:>3} USEREVENT 0x{:x}", progressToNextGameTick, eventType); - break; + switch (type) { + case 0x100: + return DemoMsg::QuitEvent; + case 0x300: + return DemoMsg::KeyDownEvent; + case 0x301: + return DemoMsg::KeyUpEvent; + case 0x400: + return DemoMsg::MouseMotionEvent; + case 0x401: + return DemoMsg::MouseButtonDownEvent; + case 0x402: + return DemoMsg::MouseButtonUpEvent; + case 0x403: + return DemoMsg::MouseWheelEvent; + + default: + if (type < 0x8000) { // SDL_USEREVENT + app_fatal(StrCat("Unknown event ", type)); } - } break; - case DemoMsgType::GameTick: + return DemoMsg::MinCustomEvent + (type - 0x8000); + } +} + +void LogDemoMessage(const DemoMsg &dmsg) +{ +#ifdef LOG_DEMOMODE_MESSAGES + const uint8_t progressToNextGameTick = dmsg.progressToNextGameTick; + switch (dmsg.type) { + case DemoMsg::GameTick: #ifdef LOG_DEMOMODE_MESSAGES_GAMETICK Log("⏲️ GameTick {:>3}", progressToNextGameTick); #endif break; - case DemoMsgType::Rendering: + case DemoMsg::Rendering: #ifdef LOG_DEMOMODE_MESSAGES_RENDERING Log("🖼️ Rendering {:>3}", progressToNextGameTick); #endif break; + case DemoMsg::MouseMotionEvent: +#ifdef LOG_DEMOMODE_MESSAGES_MOUSEMOTION + Log("🖱️ Message {:>3} MOUSEMOTION {} {}", progressToNextGameTick, + dmsg.motion.x, dmsg.motion.y); +#endif + break; + case DemoMsg::MouseButtonDownEvent: + case DemoMsg::MouseButtonUpEvent: + Log("🖱️ Message {:>3} {} {} {} {} 0x{:x}", progressToNextGameTick, + dmsg.type == DemoMsg::MouseButtonDownEvent ? "MOUSEBUTTONDOWN" : "MOUSEBUTTONUP", + dmsg.button.button, dmsg.button.x, dmsg.button.y, dmsg.button.mod); + break; + case DemoMsg::MouseWheelEvent: + Log("🖱️ Message {:>3} MOUSEWHEEL {} {} 0x{:x}", progressToNextGameTick, + dmsg.wheel.x, dmsg.wheel.y, dmsg.wheel.mod); + break; + case DemoMsg::KeyDownEvent: + case DemoMsg::KeyUpEvent: + Log("🔤 Message {:>3} {} 0x{:x} 0x{:x}", progressToNextGameTick, + dmsg.type == DemoMsg::KeyDownEvent ? "KEYDOWN" : "KEYUP", + dmsg.key.sym, dmsg.key.mod); + break; + case DemoMsg::QuitEvent: + Log("❎ Message {:>3} QUIT", progressToNextGameTick); + break; default: - LogError("INVALID DEMO MODE MESSAGE {} {:>3}", static_cast(msg.type), progressToNextGameTick); + Log("📨 Message {:>3} USEREVENT {}", progressToNextGameTick, static_cast(dmsg.type)); break; } #endif // LOG_DEMOMODE_MESSAGES } -LoadingStatus LoadDemoMessages(int i) +void CloseDemoFile() { - const std::string path = StrCat(paths::PrefPath(), "demo_", i, ".dmo"); - FILE *demofile = OpenFile(path.c_str(), "rb"); - if (demofile == nullptr) { - return LoadingStatus::FileNotFound; + if (DemoFile != nullptr) { + std::fclose(DemoFile); + DemoFile = nullptr; } +} - const uint8_t version = ReadByte(demofile); - if (version != 0 && version != 1) { +LoadingStatus OpenDemoFile(int demoNumber) +{ + CloseDemoFile(); + const std::string path = StrCat(paths::PrefPath(), "demo_", demoNumber, ".dmo"); + DemoFile = OpenFile(path.c_str(), "rb"); + if (DemoFile == nullptr) { + return LoadingStatus::FileNotFound; + } + DemoFileVersion = ReadByte(DemoFile); + if (DemoFileVersion > Version) { return LoadingStatus::UnsupportedVersion; } + DemoNumber = demoNumber; - gSaveNumber = ReadLE32(demofile); - ReadSettings(demofile, version); + gSaveNumber = ReadLE32(DemoFile); + ReadSettings(DemoFile, DemoFileVersion); - while (true) { - const uint32_t typeNum = ReadLE32(demofile); - if (std::feof(demofile)) - break; - const auto type = static_cast(typeNum); - - const uint8_t progressToNextGameTick = ReadByte(demofile); - - switch (type) { - case DemoMsgType::Message: { - const uint32_t eventType = ReadLE32(demofile); - DemoMsg msg { type, progressToNextGameTick, eventType, {} }; - switch (eventType) { - case 0x400: // SDL_MOUSEMOTION - msg.motion.x = ReadLE16(demofile); - msg.motion.y = ReadLE16(demofile); - break; - case 0x401: // SDL_MOUSEBUTTONDOWN - case 0x402: // SDL_MOUSEBUTTONUP - msg.button.button = ReadByte(demofile); - msg.button.x = ReadLE16(demofile); - msg.button.y = ReadLE16(demofile); - msg.button.mod = ReadLE16(demofile); - break; - case 0x403: // SDL_MOUSEWHEEL - msg.wheel.x = ReadLE32(demofile); - msg.wheel.y = ReadLE32(demofile); - msg.wheel.mod = ReadLE16(demofile); - break; - case 0x300: // SDL_KEYDOWN - case 0x301: // SDL_KEYUP - msg.key.sym = static_cast(ReadLE32(demofile)); - msg.key.mod = static_cast(ReadLE16(demofile)); - break; - case 0x100: // SDL_QUIT - break; - default: - if (eventType < 0x8000) { // SDL_USEREVENT - app_fatal(StrCat("Unknown event ", eventType)); - } - break; - } - Demo_Message_Queue.push_back(msg); + return LoadingStatus::Success; +} + +std::optional ReadDemoMessage() +{ + const uint8_t typeNum = DemoFileVersion >= 2 ? ReadByte(DemoFile) : ReadLE32(DemoFile); + + if (std::feof(DemoFile) != 0) { + CloseDemoFile(); + return std::nullopt; + } + + // Events with the high bit 1 are Rendering events with the rest of the bits used + // to encode `progressToNextGameTick` inline. + if ((typeNum & 0b10000000) != 0) { + DemoModeLastTick = SDL_GetTicks(); + return DemoMsg { DemoMsg::Rendering, static_cast(typeNum & 0b01111111u), {} }; + } + const uint8_t progressToNextGameTick = ReadByte(DemoFile); + + switch (typeNum) { + case DemoMsg::GameTick: + case DemoMsg::Rendering: + DemoModeLastTick = SDL_GetTicks(); + return DemoMsg { static_cast(typeNum), progressToNextGameTick, {} }; + default: { + const uint8_t eventType = DemoFileVersion >= 2 ? typeNum : MapPreV2DemoMsgEventType(static_cast(ReadLE32(DemoFile))); + DemoMsg result { static_cast(eventType), progressToNextGameTick, {} }; + switch (eventType) { + case DemoMsg::MouseMotionEvent: { + result.motion.x = ReadLE16(DemoFile); + result.motion.y = ReadLE16(DemoFile); + } break; + case DemoMsg::MouseButtonDownEvent: + case DemoMsg::MouseButtonUpEvent: { + result.button.button = ReadByte(DemoFile); + result.button.x = ReadLE16(DemoFile); + result.button.y = ReadLE16(DemoFile); + result.button.mod = ReadLE16(DemoFile); + } break; + case DemoMsg::MouseWheelEvent: { + result.wheel.x = DemoFileVersion >= 2 ? ReadLE16(DemoFile) : static_cast(ReadLE32(DemoFile)); + result.wheel.y = DemoFileVersion >= 2 ? ReadLE16(DemoFile) : static_cast(ReadLE32(DemoFile)); + result.wheel.mod = ReadLE16(DemoFile); + } break; + case DemoMsg::KeyDownEvent: + case DemoMsg::KeyUpEvent: { + result.key.sym = static_cast(ReadLE32(DemoFile)); + result.key.mod = static_cast(ReadLE16(DemoFile)); + } break; + case DemoMsg::QuitEvent: // SDL_QUIT break; - } default: - Demo_Message_Queue.push_back(DemoMsg { type, progressToNextGameTick, 0, {} }); + if (eventType < DemoMsg::MinCustomEvent) { + app_fatal(StrCat("Unknown event ", eventType)); + } break; } + DemoModeLastTick = SDL_GetTicks(); + return result; + } break; } - - std::fclose(demofile); - - DemoModeLastTick = SDL_GetTicks(); - - return LoadingStatus::Success; } -void RecordEventHeader(const SDL_Event &event) +void WriteDemoMsgHeader(DemoMsg::EventType type) { - WriteLE32(DemoRecording, static_cast(DemoMsgType::Message)); + if (type == DemoMsg::Rendering && ProgressToNextGameTick <= 127) { + WriteByte(DemoRecording, ProgressToNextGameTick | 0b10000000); + return; + } + WriteByte(DemoRecording, type); WriteByte(DemoRecording, ProgressToNextGameTick); - WriteLE32(DemoRecording, event.type); } } // namespace @@ -463,11 +560,10 @@ namespace demo { void InitPlayBack(int demoNumber, bool timedemo) { - DemoNumber = demoNumber; Timedemo = timedemo; ControlMode = ControlTypes::KeyboardAndMouse; - LoadingStatus status = LoadDemoMessages(demoNumber); + const LoadingStatus status = OpenDemoFile(demoNumber); switch (status) { case LoadingStatus::Success: return; @@ -540,29 +636,33 @@ bool IsRecording() bool GetRunGameLoop(bool &drawGame, bool &processInput) { - if (Demo_Message_Queue.empty()) + if (CurrentDemoMessage == std::nullopt && DemoFile != nullptr) + CurrentDemoMessage = ReadDemoMessage(); + if (CurrentDemoMessage == std::nullopt) app_fatal("Demo queue empty"); - const DemoMsg dmsg = Demo_Message_Queue.front(); + + const DemoMsg &dmsg = *CurrentDemoMessage; + + if (CurrentDemoMessage->isEvent()) + app_fatal("Unexpected event demo message in GetRunGameLoop"); LogDemoMessage(dmsg); - if (dmsg.type == DemoMsgType::Message) - app_fatal("Unexpected Message"); if (Timedemo) { // disable additonal rendering to speedup replay - drawGame = dmsg.type == DemoMsgType::GameTick && !HeadlessMode; + drawGame = dmsg.type == DemoMsg::GameTick && !HeadlessMode; } else { int currentTickCount = SDL_GetTicks(); int ticksElapsed = currentTickCount - DemoModeLastTick; bool tickDue = ticksElapsed >= gnTickDelay; drawGame = false; if (tickDue) { - if (dmsg.type == DemoMsgType::GameTick) { + if (dmsg.type == DemoMsg::GameTick) { DemoModeLastTick = currentTickCount; } } else { int32_t fraction = ticksElapsed * AnimationInfo::baseValueFraction / gnTickDelay; - fraction = clamp(fraction, 0, AnimationInfo::baseValueFraction); + fraction = std::clamp(fraction, 0, AnimationInfo::baseValueFraction); uint8_t progressToNextGameTick = static_cast(fraction); - if (dmsg.type == DemoMsgType::GameTick || dmsg.progressToNextGameTick > progressToNextGameTick) { + if (dmsg.type == DemoMsg::GameTick || dmsg.progressToNextGameTick > progressToNextGameTick) { // we are ahead of the replay => add a additional rendering for smoothness if (gbRunGame && PauseMode == 0 && (gbIsMultiplayer || !gmenu_is_active()) && gbProcessPlayers) // if game is not running or paused there is no next gametick in the near future ProgressToNextGameTick = progressToNextGameTick; @@ -573,10 +673,11 @@ bool GetRunGameLoop(bool &drawGame, bool &processInput) } } ProgressToNextGameTick = dmsg.progressToNextGameTick; - Demo_Message_Queue.pop_front(); - if (dmsg.type == DemoMsgType::GameTick) + const bool isGameTick = dmsg.type == DemoMsg::GameTick; + CurrentDemoMessage = std::nullopt; + if (isGameTick) LogicTick++; - return dmsg.type == DemoMsgType::GameTick; + return isGameTick; } bool FetchMessage(SDL_Event *event, uint16_t *modState) @@ -591,7 +692,8 @@ bool FetchMessage(SDL_Event *event, uint16_t *modState) return true; } if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE) { - Demo_Message_Queue.clear(); + CloseDemoFile(); + CurrentDemoMessage = std::nullopt; DemoNumber = -1; Timedemo = false; last_tick = SDL_GetTicks(); @@ -608,13 +710,15 @@ bool FetchMessage(SDL_Event *event, uint16_t *modState) } } - if (!Demo_Message_Queue.empty()) { - const DemoMsg dmsg = Demo_Message_Queue.front(); + if (CurrentDemoMessage == std::nullopt && DemoFile != nullptr) + CurrentDemoMessage = ReadDemoMessage(); + if (CurrentDemoMessage != std::nullopt) { + const DemoMsg &dmsg = *CurrentDemoMessage; LogDemoMessage(dmsg); - if (dmsg.type == DemoMsgType::Message) { + if (dmsg.isEvent()) { const bool hasEvent = CreateSdlEvent(dmsg, *event, *modState); ProgressToNextGameTick = dmsg.progressToNextGameTick; - Demo_Message_Queue.pop_front(); + CurrentDemoMessage = std::nullopt; return hasEvent; } } @@ -624,8 +728,10 @@ bool FetchMessage(SDL_Event *event, uint16_t *modState) void RecordGameLoopResult(bool runGameLoop) { - WriteLE32(DemoRecording, static_cast(runGameLoop ? DemoMsgType::GameTick : DemoMsgType::Rendering)); - WriteByte(DemoRecording, ProgressToNextGameTick); + WriteDemoMsgHeader(runGameLoop ? DemoMsg::GameTick : DemoMsg::Rendering); + + if (runGameLoop && !IsRunning()) + LogicTick++; } void RecordMessage(const SDL_Event &event, uint16_t modState) @@ -636,49 +742,64 @@ void RecordMessage(const SDL_Event &event, uint16_t modState) return; switch (event.type) { case SDL_MOUSEMOTION: - RecordEventHeader(event); + WriteDemoMsgHeader(DemoMsg::MouseMotionEvent); WriteLE16(DemoRecording, event.motion.x); WriteLE16(DemoRecording, event.motion.y); break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: - RecordEventHeader(event); - WriteByte(DemoRecording, event.button.button); - WriteLE16(DemoRecording, event.button.x); - WriteLE16(DemoRecording, event.button.y); - WriteLE16(DemoRecording, modState); +#ifdef USE_SDL1 + if (event.button.button == SDL_BUTTON_WHEELUP || event.button.button == SDL_BUTTON_WHEELDOWN) { + WriteDemoMsgHeader(DemoMsg::MouseWheelEvent); + WriteLE16(DemoRecording, 0); + WriteLE16(DemoRecording, event.button.button == SDL_BUTTON_WHEELUP ? 1 : -1); + WriteLE16(DemoRecording, modState); + } else { +#endif + WriteDemoMsgHeader(event.type == SDL_MOUSEBUTTONDOWN ? DemoMsg::MouseButtonDownEvent : DemoMsg::MouseButtonUpEvent); + WriteByte(DemoRecording, event.button.button); + WriteLE16(DemoRecording, event.button.x); + WriteLE16(DemoRecording, event.button.y); + WriteLE16(DemoRecording, modState); +#ifdef USE_SDL1 + } +#endif break; #ifndef USE_SDL1 case SDL_MOUSEWHEEL: - RecordEventHeader(event); - WriteLE32(DemoRecording, event.wheel.x); - WriteLE32(DemoRecording, event.wheel.y); + WriteDemoMsgHeader(DemoMsg::MouseWheelEvent); + if (event.wheel.x < std::numeric_limits::min() + || event.wheel.x > std::numeric_limits::max() + || event.wheel.y < std::numeric_limits::min() + || event.wheel.y > std::numeric_limits::max()) { + app_fatal(fmt::format("Mouse wheel event x/y out of int16_t range. x={} y={}", + event.wheel.x, event.wheel.y)); + } + WriteLE16(DemoRecording, event.wheel.x); + WriteLE16(DemoRecording, event.wheel.y); WriteLE16(DemoRecording, modState); break; #endif case SDL_KEYDOWN: case SDL_KEYUP: - RecordEventHeader(event); + WriteDemoMsgHeader(event.type == SDL_KEYDOWN ? DemoMsg::KeyDownEvent : DemoMsg::KeyUpEvent); WriteLE32(DemoRecording, static_cast(event.key.keysym.sym)); WriteLE16(DemoRecording, static_cast(event.key.keysym.mod)); break; #ifndef USE_SDL1 case SDL_WINDOWEVENT: if (event.window.type == SDL_WINDOWEVENT_CLOSE) { - SDL_Event quitEvent; - quitEvent.type = SDL_QUIT; - RecordEventHeader(quitEvent); + WriteDemoMsgHeader(DemoMsg::QuitEvent); } break; #endif case SDL_QUIT: - RecordEventHeader(event); + WriteDemoMsgHeader(DemoMsg::QuitEvent); break; default: if (IsCustomEvent(event.type)) { - SDL_Event stableCustomEvent; - stableCustomEvent.type = SDL_USEREVENT + static_cast(GetCustomEvent(event.type)); - RecordEventHeader(stableCustomEvent); + WriteDemoMsgHeader(static_cast( + DemoMsg::MinCustomEvent + static_cast(GetCustomEvent(event.type)))); } break; } @@ -686,6 +807,12 @@ void RecordMessage(const SDL_Event &event, uint16_t modState) void NotifyGameLoopStart() { + LogicTick = 0; + + if (IsRunning()) { + StartTime = SDL_GetTicks(); + } + if (IsRecording()) { const std::string path = StrCat(paths::PrefPath(), "demo_", RecordNumber, ".dmo"); DemoRecording = OpenFile(path.c_str(), "wb"); @@ -694,16 +821,10 @@ void NotifyGameLoopStart() LogError("Failed to open {} for writing", path); return; } - constexpr uint8_t Version = 1; WriteByte(DemoRecording, Version); WriteLE32(DemoRecording, gSaveNumber); WriteSettings(DemoRecording); } - - if (IsRunning()) { - StartTime = SDL_GetTicks(); - LogicTick = 0; - } } void NotifyGameLoopEnd() @@ -719,7 +840,7 @@ void NotifyGameLoopEnd() } if (IsRunning() && !HeadlessMode) { - float seconds = (SDL_GetTicks() - StartTime) / 1000.0f; + const float seconds = (SDL_GetTicks() - StartTime) / 1000.0F; SDL_Log("%d frames, %.2f seconds: %.1f fps", LogicTick, seconds, LogicTick / seconds); gbRunGameResult = false; gbRunGame = false; @@ -739,6 +860,11 @@ void NotifyGameLoopEnd() } } +uint32_t SimulateMillisecondsSinceStartup() +{ + return LogicTick * 50; +} + } // namespace demo } // namespace devilution diff --git a/Source/engine/demomode.h b/Source/engine/demomode.h index a18040635a8..59629c9d1ec 100644 --- a/Source/engine/demomode.h +++ b/Source/engine/demomode.h @@ -28,6 +28,8 @@ void RecordMessage(const SDL_Event &event, uint16_t modState); void NotifyGameLoopStart(); void NotifyGameLoopEnd(); + +uint32_t SimulateMillisecondsSinceStartup(); #else inline void OverrideOptions() { @@ -60,6 +62,10 @@ inline void NotifyGameLoopStart() inline void NotifyGameLoopEnd() { } +inline uint32_t SimulateMillisecondsSinceStartup() +{ + return 0; +} #endif } // namespace demo diff --git a/Source/engine/direction.cpp b/Source/engine/direction.cpp index 45d1eb4f406..a54f68d4ea9 100644 --- a/Source/engine/direction.cpp +++ b/Source/engine/direction.cpp @@ -2,7 +2,7 @@ namespace devilution { -string_view DirectionToString(Direction direction) +std::string_view DirectionToString(Direction direction) { switch (direction) { case Direction::South: diff --git a/Source/engine/direction.hpp b/Source/engine/direction.hpp index b9a42d0610b..f5d057583b4 100644 --- a/Source/engine/direction.hpp +++ b/Source/engine/direction.hpp @@ -1,9 +1,10 @@ #pragma once #include +#include #include -#include "utils/stdcompat/string_view.hpp" +#include "utils/attributes.h" namespace devilution { @@ -20,26 +21,26 @@ enum class Direction : std::uint8_t { }; /** Maps from direction to a left turn from the direction. */ -constexpr Direction Left(Direction facing) +DVL_ALWAYS_INLINE constexpr Direction Left(Direction facing) { // Direction left[8] = { Direction::SouthEast, Direction::South, Direction::SouthWest, Direction::West, Direction::NorthWest, Direction::North, Direction::NorthEast, Direction::East }; return static_cast((static_cast>(facing) + 7) % 8); } /** Maps from direction to a right turn from the direction. */ -constexpr Direction Right(Direction facing) +DVL_ALWAYS_INLINE constexpr Direction Right(Direction facing) { // Direction right[8] = { Direction::SouthWest, Direction::West, Direction::NorthWest, Direction::North, Direction::NorthEast, Direction::East, Direction::SouthEast, Direction::South }; return static_cast((static_cast>(facing) + 1) % 8); } /** Maps from direction to the opposite direction. */ -constexpr Direction Opposite(Direction facing) +DVL_ALWAYS_INLINE constexpr Direction Opposite(Direction facing) { // Direction opposite[8] = { Direction::North, Direction::NorthEast, Direction::East, Direction::SouthEast, Direction::South, Direction::SouthWest, Direction::West, Direction::NorthWest }; return static_cast((static_cast>(facing) + 4) % 8); } -string_view DirectionToString(Direction direction); +std::string_view DirectionToString(Direction direction); } // namespace devilution diff --git a/Source/engine/displacement.hpp b/Source/engine/displacement.hpp index f636bd6b0cc..4056335dcbe 100644 --- a/Source/engine/displacement.hpp +++ b/Source/engine/displacement.hpp @@ -7,7 +7,7 @@ #include "engine/direction.hpp" #include "engine/size.hpp" -#include "utils/stdcompat/abs.hpp" +#include "utils/attributes.h" namespace devilution { @@ -24,50 +24,50 @@ struct DisplacementOf { DisplacementOf() = default; template - constexpr DisplacementOf(DisplacementOf other) + DVL_ALWAYS_INLINE constexpr DisplacementOf(DisplacementOf other) : deltaX(other.deltaX) , deltaY(other.deltaY) { } - constexpr DisplacementOf(DeltaT deltaX, DeltaT deltaY) + DVL_ALWAYS_INLINE constexpr DisplacementOf(DeltaT deltaX, DeltaT deltaY) : deltaX(deltaX) , deltaY(deltaY) { } - explicit constexpr DisplacementOf(DeltaT delta) + DVL_ALWAYS_INLINE explicit constexpr DisplacementOf(DeltaT delta) : deltaX(delta) , deltaY(delta) { } template - explicit constexpr DisplacementOf(const SizeOf &size) + DVL_ALWAYS_INLINE explicit constexpr DisplacementOf(const SizeOf &size) : deltaX(size.width) , deltaY(size.height) { } - explicit constexpr DisplacementOf(Direction direction) + DVL_ALWAYS_INLINE explicit constexpr DisplacementOf(Direction direction) : DisplacementOf(fromDirection(direction)) { } template - constexpr bool operator==(const DisplacementOf &other) const + DVL_ALWAYS_INLINE constexpr bool operator==(const DisplacementOf &other) const { return deltaX == other.deltaX && deltaY == other.deltaY; } template - constexpr bool operator!=(const DisplacementOf &other) const + DVL_ALWAYS_INLINE constexpr bool operator!=(const DisplacementOf &other) const { return !(*this == other); } template - constexpr DisplacementOf &operator+=(DisplacementOf displacement) + DVL_ALWAYS_INLINE constexpr DisplacementOf &operator+=(DisplacementOf displacement) { deltaX += displacement.deltaX; deltaY += displacement.deltaY; @@ -75,21 +75,21 @@ struct DisplacementOf { } template - constexpr DisplacementOf &operator-=(DisplacementOf displacement) + DVL_ALWAYS_INLINE constexpr DisplacementOf &operator-=(DisplacementOf displacement) { deltaX -= displacement.deltaX; deltaY -= displacement.deltaY; return *this; } - constexpr DisplacementOf &operator*=(const int factor) + DVL_ALWAYS_INLINE constexpr DisplacementOf &operator*=(const int factor) { deltaX *= factor; deltaY *= factor; return *this; } - constexpr DisplacementOf &operator*=(const float factor) + DVL_ALWAYS_INLINE constexpr DisplacementOf &operator*=(const float factor) { deltaX = static_cast(deltaX * factor); deltaY = static_cast(deltaY * factor); @@ -97,30 +97,41 @@ struct DisplacementOf { } template - constexpr DisplacementOf &operator*=(const DisplacementOf factor) + DVL_ALWAYS_INLINE constexpr DisplacementOf &operator*=(const DisplacementOf factor) { deltaX = static_cast(deltaX * factor.deltaX); deltaY = static_cast(deltaY * factor.deltaY); return *this; } - constexpr DisplacementOf &operator/=(const int factor) + DVL_ALWAYS_INLINE constexpr DisplacementOf &operator/=(const int factor) { deltaX /= factor; deltaY /= factor; return *this; } - constexpr DisplacementOf &operator/=(const float factor) + DVL_ALWAYS_INLINE constexpr DisplacementOf &operator/=(const float factor) { deltaX = static_cast(deltaX / factor); deltaY = static_cast(deltaY / factor); return *this; } - float magnitude() const + DVL_ALWAYS_INLINE float magnitude() const { - return static_cast(hypot(deltaX, deltaY)); + // If [x * x <= max], then [x <= max / x] for x > 0 + assert(deltaX == 0 || std::abs(deltaX) <= std::numeric_limits::max() / std::abs(deltaX)); + assert(deltaY == 0 || std::abs(deltaY) <= std::numeric_limits::max() / std::abs(deltaY)); + + // If [x + y <= max], then [x <= max - y] + assert(deltaX * deltaX <= std::numeric_limits::max() - deltaY * deltaY); + + // Maximum precision of float is 24 bits + assert(deltaX * deltaX + deltaY * deltaY < (1 << 24)); + + // We do not use `std::hypot` here because it is slower and we do not need the extra precision. + return sqrtf(static_cast(deltaX * deltaX + deltaY * deltaY)); } /** @@ -134,7 +145,7 @@ struct DisplacementOf { * * @return A representation of the original displacement in screen coordinates. */ - constexpr DisplacementOf worldToScreen() const + DVL_ALWAYS_INLINE constexpr DisplacementOf worldToScreen() const { static_assert(std::is_signed::value, "DeltaT must be signed for transformations involving a rotation"); return { (deltaY - deltaX) * 32, (deltaY + deltaX) * -16 }; @@ -147,7 +158,7 @@ struct DisplacementOf { * * @return A representation of the original displacement in world coordinates. */ - constexpr DisplacementOf screenToWorld() const + DVL_ALWAYS_INLINE constexpr DisplacementOf screenToWorld() const { static_assert(std::is_signed::value, "DeltaT must be signed for transformations involving a rotation"); return { (2 * deltaY + deltaX) / -64, (2 * deltaY - deltaX) / -64 }; @@ -198,7 +209,7 @@ struct DisplacementOf { */ [[nodiscard]] Displacement normalized() const; - [[nodiscard]] constexpr DisplacementOf Rotate(int quadrants) const + [[nodiscard]] DVL_ALWAYS_INLINE constexpr DisplacementOf Rotate(int quadrants) const { static_assert(std::is_signed::value, "DeltaT must be signed for Rotate"); constexpr DeltaT Sines[] = { 0, 1, 0, -1 }; @@ -230,7 +241,7 @@ struct DisplacementOf { } private: - static constexpr DisplacementOf fromDirection(Direction direction) + DVL_ALWAYS_INLINE static constexpr DisplacementOf fromDirection(Direction direction) { static_assert(std::is_signed::value, "DeltaT must be signed for conversion from Direction"); switch (direction) { @@ -273,76 +284,76 @@ std::ostream &operator<<(std::ostream &stream, const DisplacementOf -constexpr DisplacementOf operator+(DisplacementOf a, DisplacementOf b) +DVL_ALWAYS_INLINE constexpr DisplacementOf operator+(DisplacementOf a, DisplacementOf b) { a += b; return a; } template -constexpr DisplacementOf operator-(DisplacementOf a, DisplacementOf b) +DVL_ALWAYS_INLINE constexpr DisplacementOf operator-(DisplacementOf a, DisplacementOf b) { a -= b; return a; } template -constexpr DisplacementOf operator*(DisplacementOf a, const int factor) +DVL_ALWAYS_INLINE constexpr DisplacementOf operator*(DisplacementOf a, const int factor) { a *= factor; return a; } template -constexpr DisplacementOf operator*(DisplacementOf a, const float factor) +DVL_ALWAYS_INLINE constexpr DisplacementOf operator*(DisplacementOf a, const float factor) { a *= factor; return a; } template -constexpr DisplacementOf operator*(DisplacementOf a, const DisplacementOf factor) +DVL_ALWAYS_INLINE constexpr DisplacementOf operator*(DisplacementOf a, const DisplacementOf factor) { a *= factor; return a; } template -constexpr DisplacementOf operator/(DisplacementOf a, const int factor) +DVL_ALWAYS_INLINE constexpr DisplacementOf operator/(DisplacementOf a, const int factor) { a /= factor; return a; } template -constexpr DisplacementOf operator/(DisplacementOf a, const float factor) +DVL_ALWAYS_INLINE constexpr DisplacementOf operator/(DisplacementOf a, const float factor) { a /= factor; return a; } template -constexpr DisplacementOf operator-(DisplacementOf a) +DVL_ALWAYS_INLINE constexpr DisplacementOf operator-(DisplacementOf a) { return { -a.deltaX, -a.deltaY }; } template -constexpr DisplacementOf operator<<(DisplacementOf a, unsigned factor) +DVL_ALWAYS_INLINE constexpr DisplacementOf operator<<(DisplacementOf a, unsigned factor) { return { a.deltaX << factor, a.deltaY << factor }; } template -constexpr DisplacementOf operator>>(DisplacementOf a, unsigned factor) +DVL_ALWAYS_INLINE constexpr DisplacementOf operator>>(DisplacementOf a, unsigned factor) { return { a.deltaX >> factor, a.deltaY >> factor }; } template -constexpr DisplacementOf abs(DisplacementOf a) +DVL_ALWAYS_INLINE constexpr DisplacementOf abs(DisplacementOf a) { - return { abs(a.deltaX), abs(a.deltaY) }; + return DisplacementOf(std::abs(a.deltaX), std::abs(a.deltaY)); } template diff --git a/Source/engine/events.cpp b/Source/engine/events.cpp index b6fac7f5011..7bf6051485b 100644 --- a/Source/engine/events.cpp +++ b/Source/engine/events.cpp @@ -8,6 +8,7 @@ #include "interfac.h" #include "movie.h" #include "options.h" +#include "panels/console.hpp" #include "utils/log.hpp" #ifdef USE_SDL1 @@ -100,6 +101,7 @@ bool FetchMessage_Real(SDL_Event *event, uint16_t *modState) case SDL_TEXTEDITING: case SDL_TEXTINPUT: case SDL_WINDOWEVENT: + case SDL_MOUSEWHEEL: #else case SDL_ACTIVEEVENT: #endif @@ -119,18 +121,6 @@ bool FetchMessage_Real(SDL_Event *event, uint16_t *modState) *event = e; break; #ifndef USE_SDL1 - case SDL_MOUSEWHEEL: - event->type = SDL_KEYDOWN; - if (e.wheel.y > 0) { - event->key.keysym.sym = (SDL_GetModState() & KMOD_CTRL) != 0 ? SDLK_KP_PLUS : SDLK_UP; - } else if (e.wheel.y < 0) { - event->key.keysym.sym = (SDL_GetModState() & KMOD_CTRL) != 0 ? SDLK_KP_MINUS : SDLK_DOWN; - } else if (e.wheel.x > 0) { - event->key.keysym.sym = SDLK_LEFT; - } else if (e.wheel.x < 0) { - event->key.keysym.sym = SDLK_RIGHT; - } - break; #if SDL_VERSION_ATLEAST(2, 0, 4) case SDL_AUDIODEVICEADDED: return FalseAvail("SDL_AUDIODEVICEADDED", e.adevice.which); diff --git a/Source/engine/load_cl2.hpp b/Source/engine/load_cl2.hpp index a83e5216fc4..95dec272b75 100644 --- a/Source/engine/load_cl2.hpp +++ b/Source/engine/load_cl2.hpp @@ -60,7 +60,7 @@ OwnedClxSpriteSheet LoadMultipleCl2Sheet(tl::function_ref if (!handle.ok() || !handle.read(&data[accumulatedSize], size)) { FailedToOpenFileError(paths[i].data(), handle.error()); } - WriteLE32(&data[i * 4], accumulatedSize); + WriteLE32(&data[i * 4], static_cast(accumulatedSize)); accumulatedSize += size; } #ifdef UNPACKED_MPQS diff --git a/Source/engine/load_file.hpp b/Source/engine/load_file.hpp index 9168afe539f..1c91b2ef618 100644 --- a/Source/engine/load_file.hpp +++ b/Source/engine/load_file.hpp @@ -13,7 +13,6 @@ #include "engine/assets.hpp" #include "mpq/mpq_common.hpp" #include "utils/static_vector.hpp" -#include "utils/stdcompat/cstddef.hpp" #include "utils/str_cat.hpp" namespace devilution { @@ -58,7 +57,7 @@ void LoadFileInMem(const char *path, std::array &data) * @param numRead Number of T elements read * @return Buffer with content of file */ -template +template std::unique_ptr LoadFileInMem(const char *path, std::size_t *numRead = nullptr) { size_t size; @@ -95,10 +94,10 @@ struct MultiFileLoader { * @param pathFn a function that returns the path for the given index * @param outOffsets a buffer index for the start of each file will be written here, then the total file size at the end. * @param filterFn a function that returns whether to load a file for the given index - * @return std::unique_ptr the buffer with all the files + * @return std::unique_ptr the buffer with all the files */ template - [[nodiscard]] std::unique_ptr operator()(size_t numFiles, PathFn &&pathFn, uint32_t *outOffsets, + [[nodiscard]] std::unique_ptr operator()(size_t numFiles, PathFn &&pathFn, uint32_t *outOffsets, FilterFn filterFn = DefaultFilterFn {}) { StaticVector, MaxFiles> paths; @@ -124,8 +123,8 @@ struct MultiFileLoader { totalSize += size; ++j; } - outOffsets[files.size()] = totalSize; - std::unique_ptr buf { new byte[totalSize] }; + outOffsets[files.size()] = static_cast(totalSize); + std::unique_ptr buf { new std::byte[totalSize] }; for (size_t i = 0, j = 0; i < numFiles; ++i) { if (!filterFn(i)) continue; diff --git a/Source/engine/load_pcx.hpp b/Source/engine/load_pcx.hpp index 21387860fbc..6c8dcecb473 100644 --- a/Source/engine/load_pcx.hpp +++ b/Source/engine/load_pcx.hpp @@ -1,11 +1,11 @@ #pragma once #include +#include #include #include "engine/clx_sprite.hpp" -#include "utils/stdcompat/optional.hpp" #ifdef UNPACKED_MPQS #define DEVILUTIONX_PCX_EXT ".clx" diff --git a/Source/engine/palette.cpp b/Source/engine/palette.cpp index 5a77e49ade7..850ced74a37 100644 --- a/Source/engine/palette.cpp +++ b/Source/engine/palette.cpp @@ -24,7 +24,11 @@ namespace devilution { std::array logical_palette; std::array system_palette; std::array orig_palette; -std::array, 256> paletteTransparencyLookup; + +// This array is read from a lot on every frame. +// We do not use `std::array` here to improve debug build performance. +// In a debug build, `std::array` accesses are function calls. +Uint8 paletteTransparencyLookup[256][256]; #if DEVILUTIONX_PALETTE_TRANSPARENCY_BLACK_16_LUT uint16_t paletteTransparencyLookupBlack16[65536]; @@ -38,7 +42,7 @@ bool sgbFadedIn = true; void LoadGamma() { int gammaValue = *sgOptions.Graphics.gammaCorrection; - gammaValue = clamp(gammaValue, 30, 100); + gammaValue = std::clamp(gammaValue, 30, 100); sgOptions.Graphics.gammaCorrection.SetValue(gammaValue - gammaValue % 5); } @@ -103,11 +107,7 @@ void GenerateBlendedLookupTable(std::array &palette, int skipFro #if DEVILUTIONX_PALETTE_TRANSPARENCY_BLACK_16_LUT for (unsigned i = 0; i < 256; ++i) { for (unsigned j = 0; j < 256; ++j) { -#if SDL_BYTEORDER == SDL_LIL_ENDIAN const std::uint16_t index = i | (j << 8); -#else - const std::uint16_t index = j | (i << 8); -#endif paletteTransparencyLookupBlack16[index] = paletteTransparencyLookup[0][i] | (paletteTransparencyLookup[0][j] << 8); } } @@ -124,10 +124,10 @@ void CycleColors(int from, int to) std::rotate(system_palette.begin() + from, system_palette.begin() + from + 1, system_palette.begin() + to + 1); for (auto &palette : paletteTransparencyLookup) { - std::rotate(palette.begin() + from, palette.begin() + from + 1, palette.begin() + to + 1); + std::rotate(std::begin(palette) + from, std::begin(palette) + from + 1, std::begin(palette) + to + 1); } - std::rotate(paletteTransparencyLookup.begin() + from, paletteTransparencyLookup.begin() + from + 1, paletteTransparencyLookup.begin() + to + 1); + std::rotate(&paletteTransparencyLookup[from][0], &paletteTransparencyLookup[from + 1][0], &paletteTransparencyLookup[to + 1][0]); } /** @@ -140,10 +140,10 @@ void CycleColorsReverse(int from, int to) std::rotate(system_palette.begin() + from, system_palette.begin() + to, system_palette.begin() + to + 1); for (auto &palette : paletteTransparencyLookup) { - std::rotate(palette.begin() + from, palette.begin() + to, palette.begin() + to + 1); + std::rotate(std::begin(palette) + from, std::begin(palette) + to, std::begin(palette) + to + 1); } - std::rotate(paletteTransparencyLookup.begin() + from, paletteTransparencyLookup.begin() + to, paletteTransparencyLookup.begin() + to + 1); + std::rotate(&paletteTransparencyLookup[from][0], &paletteTransparencyLookup[to][0], &paletteTransparencyLookup[to + 1][0]); } } // namespace @@ -226,12 +226,12 @@ void LoadRndLvlPal(dungeon_type l) return; } - int rv = GenerateRnd(4) + 1; if (l == DTYPE_CRYPT) { LoadPalette("nlevels\\l5data\\l5base.pal"); return; } + int rv = RandomIntBetween(1, 4); char szFileName[27]; if (l == DTYPE_NEST) { if (!*sgOptions.Graphics.alternateNestArt) { diff --git a/Source/engine/palette.h b/Source/engine/palette.h index b1c33dc8bb4..7989f3af10a 100644 --- a/Source/engine/palette.h +++ b/Source/engine/palette.h @@ -36,7 +36,7 @@ extern std::array logical_palette; extern std::array system_palette; extern std::array orig_palette; /** Lookup table for transparency */ -extern std::array, 256> paletteTransparencyLookup; +extern Uint8 paletteTransparencyLookup[256][256]; #if DEVILUTIONX_PALETTE_TRANSPARENCY_BLACK_16_LUT /** diff --git a/Source/engine/path.cpp b/Source/engine/path.cpp index 47660bf5347..5f2f4a97401 100644 --- a/Source/engine/path.cpp +++ b/Source/engine/path.cpp @@ -340,7 +340,7 @@ bool IsTileWalkable(Point position, bool ignoreDoors) } } - return !IsTileSolid(position); + return IsTileNotSolid(position); } bool IsTileOccupied(Point position) diff --git a/Source/engine/path.h b/Source/engine/path.h index 1b8ac76f0ae..613609e46b1 100644 --- a/Source/engine/path.h +++ b/Source/engine/path.h @@ -7,13 +7,13 @@ #include #include +#include #include #include #include "engine/direction.hpp" #include "engine/point.hpp" -#include "utils/stdcompat/optional.hpp" namespace devilution { diff --git a/Source/engine/point.hpp b/Source/engine/point.hpp index e336edeaff1..5bd8bae0a3d 100644 --- a/Source/engine/point.hpp +++ b/Source/engine/point.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #ifdef BUILD_TESTING @@ -8,8 +9,7 @@ #include "engine/direction.hpp" #include "engine/displacement.hpp" -#include "utils/stdcompat/abs.hpp" -#include "utils/stdcompat/algorithm.hpp" +#include "utils/attributes.h" namespace devilution { @@ -29,66 +29,73 @@ struct PointOf { PointOf() = default; template - constexpr PointOf(PointOf other) + DVL_ALWAYS_INLINE constexpr PointOf(PointOf other) : x(other.x) , y(other.y) { } - constexpr PointOf(CoordT x, CoordT y) + DVL_ALWAYS_INLINE constexpr PointOf(CoordT x, CoordT y) : x(x) , y(y) { } template - constexpr bool operator==(const PointOf &other) const + DVL_ALWAYS_INLINE constexpr bool operator==(const PointOf &other) const { return x == other.x && y == other.y; } template - constexpr bool operator!=(const PointOf &other) const + DVL_ALWAYS_INLINE constexpr bool operator!=(const PointOf &other) const { return !(*this == other); } template - constexpr PointOf &operator+=(const DisplacementOf &displacement) + DVL_ALWAYS_INLINE constexpr PointOf &operator+=(const DisplacementOf &displacement) { x += displacement.deltaX; y += displacement.deltaY; return *this; } - constexpr PointOf &operator+=(Direction direction) + DVL_ALWAYS_INLINE constexpr PointOf &operator+=(Direction direction) { return (*this) += DisplacementOf::type>(direction); } template - constexpr PointOf &operator-=(const DisplacementOf &displacement) + DVL_ALWAYS_INLINE constexpr PointOf &operator-=(const DisplacementOf &displacement) { x -= displacement.deltaX; y -= displacement.deltaY; return *this; } - constexpr PointOf &operator*=(const float factor) + DVL_ALWAYS_INLINE constexpr PointOf &operator*=(const float factor) { x = static_cast(x * factor); y = static_cast(y * factor); return *this; } - constexpr PointOf &operator*=(const int factor) + DVL_ALWAYS_INLINE constexpr PointOf &operator*=(const int factor) { x *= factor; y *= factor; return *this; } - constexpr PointOf operator-() const + DVL_ALWAYS_INLINE constexpr PointOf &operator/=(const int factor) + { + x /= factor; + y /= factor; + return *this; + } + + DVL_ALWAYS_INLINE constexpr PointOf operator-() const { static_assert(std::is_signed::value, "CoordT must be signed"); return { -x, -y }; @@ -104,9 +111,7 @@ struct PointOf { constexpr int ApproxDistance(PointOf other) const { const Displacement offset = abs(Point(*this) - Point(other)); - auto minMax = std::minmax(offset.deltaX, offset.deltaY); - int min = minMax.first; - int max = minMax.second; + const auto [min, max] = std::minmax(offset.deltaX, offset.deltaY); int approx = max * 1007 + min * 441; if (max < (min * 16)) @@ -132,24 +137,24 @@ struct PointOf { } template - constexpr int ManhattanDistance(PointOf other) const + DVL_ALWAYS_INLINE constexpr int ManhattanDistance(PointOf other) const { - return abs(static_cast(x) - static_cast(other.x)) - + abs(static_cast(y) - static_cast(other.y)); + return std::abs(static_cast(x) - static_cast(other.x)) + + std::abs(static_cast(y) - static_cast(other.y)); } template - constexpr int WalkingDistance(PointOf other) const + DVL_ALWAYS_INLINE constexpr int WalkingDistance(PointOf other) const { return std::max( - abs(static_cast(x) - static_cast(other.x)), - abs(static_cast(y) - static_cast(other.y))); + std::abs(static_cast(x) - static_cast(other.x)), + std::abs(static_cast(y) - static_cast(other.y))); } /** * @brief Converts a coordinate in megatiles to the northmost of the 4 corresponding world tiles */ - constexpr PointOf megaToWorld() const + DVL_ALWAYS_INLINE constexpr PointOf megaToWorld() const { return { static_cast(16 + 2 * x), static_cast(16 + 2 * y) }; } @@ -157,7 +162,7 @@ struct PointOf { /** * @brief Converts a coordinate in world tiles back to the corresponding megatile */ - constexpr PointOf worldToMega() const + DVL_ALWAYS_INLINE constexpr PointOf worldToMega() const { return { static_cast((x - 16) / 2), static_cast((y - 16) / 2) }; } @@ -178,51 +183,51 @@ std::ostream &operator<<(std::ostream &stream, const PointOf &point #endif template -constexpr PointOf operator+(PointOf a, DisplacementOf displacement) +DVL_ALWAYS_INLINE constexpr PointOf operator+(PointOf a, DisplacementOf displacement) { a += displacement; return a; } template -constexpr PointOf operator+(PointOf a, Direction direction) +DVL_ALWAYS_INLINE constexpr PointOf operator+(PointOf a, Direction direction) { a += direction; return a; } template -constexpr DisplacementOf operator-(PointOf a, PointOf b) +DVL_ALWAYS_INLINE constexpr DisplacementOf operator-(PointOf a, PointOf b) { static_assert(std::is_signed::value == std::is_signed::value, "points must have the same signedness"); return { static_cast(a.x - b.x), static_cast(a.y - b.y) }; } template -constexpr PointOf operator-(PointOf a, DisplacementOf displacement) +DVL_ALWAYS_INLINE constexpr PointOf operator-(PointOf a, DisplacementOf displacement) { a -= displacement; return a; } template -constexpr PointOf operator*(PointOf a, const float factor) +DVL_ALWAYS_INLINE constexpr PointOf operator*(PointOf a, const float factor) { a *= factor; return a; } template -constexpr PointOf operator*(PointOf a, const int factor) +DVL_ALWAYS_INLINE constexpr PointOf operator*(PointOf a, const int factor) { a *= factor; return a; } template -constexpr PointOf abs(PointOf a) +DVL_ALWAYS_INLINE constexpr PointOf abs(PointOf a) { - return { abs(a.x), abs(a.y) }; + return { std::abs(a.x), std::abs(a.y) }; } } // namespace devilution diff --git a/Source/engine/points_in_rectangle_range.hpp b/Source/engine/points_in_rectangle_range.hpp index bb766984595..f94bf052c20 100644 --- a/Source/engine/points_in_rectangle_range.hpp +++ b/Source/engine/points_in_rectangle_range.hpp @@ -30,7 +30,7 @@ class PointsInRectangleIteratorBase { { } - void Increment() + DVL_ALWAYS_INLINE void Increment() { ++minorIndex; if (minorIndex >= majorDimension) { @@ -67,7 +67,7 @@ class PointsInRectangleIteratorBase { }; template -class PointsInRectangleRange { +class PointsInRectangle { public: using const_iterator = class PointsInRectangleIterator : public PointsInRectangleIteratorBase { public: @@ -82,19 +82,19 @@ class PointsInRectangleRange { { } - value_type operator*() const + DVL_ALWAYS_INLINE value_type operator*() const { // Row-major iteration e.g. {0, 0}, {1, 0}, {2, 0}, {0, 1}, {1, 1}, ... return this->origin + Displacement { this->minorIndex, this->majorIndex }; } // Equality comparable concepts - bool operator==(const PointsInRectangleIterator &rhs) const + DVL_ALWAYS_INLINE bool operator==(const PointsInRectangleIterator &rhs) const { return this->majorIndex == rhs.majorIndex && this->minorIndex == rhs.minorIndex; } - bool operator!=(const PointsInRectangleIterator &rhs) const + DVL_ALWAYS_INLINE bool operator!=(const PointsInRectangleIterator &rhs) const { return !(*this == rhs); } @@ -126,7 +126,7 @@ class PointsInRectangleRange { } // Forward concepts - PointsInRectangleIterator &operator++() + DVL_ALWAYS_INLINE PointsInRectangleIterator &operator++() { this->Increment(); return *this; @@ -188,7 +188,7 @@ class PointsInRectangleRange { } }; - constexpr PointsInRectangleRange(RectangleOf region) + constexpr PointsInRectangle(RectangleOf region) : region(region) { } @@ -240,13 +240,7 @@ class PointsInRectangleRange { }; template -constexpr PointsInRectangleRange PointsInRectangle(RectangleOf region) -{ - return PointsInRectangleRange { region }; -} - -template -class PointsInRectangleColMajorRange { +class PointsInRectangleColMajor { public: using const_iterator = class PointsInRectangleIteratorColMajor : public PointsInRectangleIteratorBase { public: @@ -261,19 +255,19 @@ class PointsInRectangleColMajorRange { { } - value_type operator*() const + DVL_ALWAYS_INLINE value_type operator*() const { // Col-major iteration e.g. {0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, ... return this->origin + Displacement { this->majorIndex, this->minorIndex }; } // Equality comparable concepts - bool operator==(const PointsInRectangleIteratorColMajor &rhs) const + DVL_ALWAYS_INLINE bool operator==(const PointsInRectangleIteratorColMajor &rhs) const { return this->majorIndex == rhs.majorIndex && this->minorIndex == rhs.minorIndex; } - bool operator!=(const PointsInRectangleIteratorColMajor &rhs) const + DVL_ALWAYS_INLINE bool operator!=(const PointsInRectangleIteratorColMajor &rhs) const { return !(*this == rhs); } @@ -305,7 +299,7 @@ class PointsInRectangleColMajorRange { } // Forward concepts - PointsInRectangleIteratorColMajor &operator++() + DVL_ALWAYS_INLINE PointsInRectangleIteratorColMajor &operator++() { this->Increment(); return *this; @@ -368,7 +362,7 @@ class PointsInRectangleColMajorRange { }; // gcc6 needs a defined constructor? - constexpr PointsInRectangleColMajorRange(RectangleOf region) + constexpr PointsInRectangleColMajor(RectangleOf region) : region(region) { } @@ -419,10 +413,4 @@ class PointsInRectangleColMajorRange { RectangleOf region; }; -template -constexpr PointsInRectangleColMajorRange PointsInRectangleColMajor(RectangleOf region) -{ - return PointsInRectangleColMajorRange { region }; -} - } // namespace devilution diff --git a/Source/engine/random.cpp b/Source/engine/random.cpp index c7ea5026d2a..0b0f9754d4e 100644 --- a/Source/engine/random.cpp +++ b/Source/engine/random.cpp @@ -1,64 +1,63 @@ -#include "engine/random.hpp" - -#include -#include -#include - -#include "utils/stdcompat/abs.hpp" - -namespace devilution { - -/** Current game seed */ -uint32_t sglGameSeed; - -/** Borland C/C++ psuedo-random number generator needed for vanilla compatibility */ -std::linear_congruential_engine diabloGenerator; - -void SetRndSeed(uint32_t seed) -{ - diabloGenerator.seed(seed); - sglGameSeed = seed; -} - -uint32_t GetLCGEngineState() -{ - return sglGameSeed; -} - -void DiscardRandomValues(unsigned count) -{ - while (count != 0) { - GenerateSeed(); - count--; - } -} - -uint32_t GenerateSeed() -{ - sglGameSeed = diabloGenerator(); - return sglGameSeed; -} - -int32_t AdvanceRndSeed() -{ - const int32_t seed = static_cast(GenerateSeed()); - // since abs(INT_MIN) is undefined behavior, handle this value specially - return seed == std::numeric_limits::min() ? std::numeric_limits::min() : abs(seed); -} - -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; -} - -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; -} - -} // namespace devilution +#include "engine/random.hpp" + +#include +#include +#include +#include + +namespace devilution { + +/** Current game seed */ +uint32_t sglGameSeed; + +/** Borland C/C++ psuedo-random number generator needed for vanilla compatibility */ +std::linear_congruential_engine diabloGenerator; + +void SetRndSeed(uint32_t seed) +{ + diabloGenerator.seed(seed); + sglGameSeed = seed; +} + +uint32_t GetLCGEngineState() +{ + return sglGameSeed; +} + +void DiscardRandomValues(unsigned count) +{ + while (count != 0) { + GenerateSeed(); + count--; + } +} + +uint32_t GenerateSeed() +{ + sglGameSeed = diabloGenerator(); + return sglGameSeed; +} + +int32_t AdvanceRndSeed() +{ + const int32_t seed = static_cast(GenerateSeed()); + // since abs(INT_MIN) is undefined behavior, handle this value specially + return seed == std::numeric_limits::min() ? std::numeric_limits::min() : std::abs(seed); +} + +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; +} + +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; +} + +} // namespace devilution diff --git a/Source/engine/rectangle.hpp b/Source/engine/rectangle.hpp index bcc840a975c..708b015ee3b 100644 --- a/Source/engine/rectangle.hpp +++ b/Source/engine/rectangle.hpp @@ -2,6 +2,7 @@ #include "engine/point.hpp" #include "engine/size.hpp" +#include "utils/attributes.h" namespace devilution { @@ -12,7 +13,7 @@ struct RectangleOf { RectangleOf() = default; - constexpr RectangleOf(PointOf position, SizeOf size) + DVL_ALWAYS_INLINE constexpr RectangleOf(PointOf position, SizeOf size) : position(position) , size(size) { @@ -26,7 +27,7 @@ struct RectangleOf { * @param center center point of the target rectangle * @param radius a non-negative value indicating how many tiles to include around the center */ - explicit constexpr RectangleOf(PointOf center, SizeT radius) + DVL_ALWAYS_INLINE explicit constexpr RectangleOf(PointOf center, SizeT radius) : position(center - DisplacementOf { radius }) , size(static_cast(2 * radius + 1)) { @@ -37,7 +38,7 @@ struct RectangleOf { * Works correctly even if the point uses a different underlying numeric type */ template - constexpr bool contains(PointOf point) const + DVL_ALWAYS_INLINE constexpr bool contains(PointOf point) const { return contains(point.x, point.y); } diff --git a/Source/engine/render/automap_render.cpp b/Source/engine/render/automap_render.cpp index 9dc5de0484a..37e6e110319 100644 --- a/Source/engine/render/automap_render.cpp +++ b/Source/engine/render/automap_render.cpp @@ -4,6 +4,7 @@ * Line drawing routines for the automap. */ #include "engine/render/automap_render.hpp" +#include "automap.h" #include @@ -24,36 +25,72 @@ template void DrawMapLine(const Surface &out, Point from, int height, std::uint8_t colorIndex) { while (height-- > 0) { - out.SetPixel({ from.x, from.y + 1 }, 0); - out.SetPixel(from, colorIndex); + SetMapPixel(out, { from.x, from.y + 1 }, 0); + SetMapPixel(out, from, colorIndex); from.x += static_cast(DirX); - out.SetPixel({ from.x, from.y + 1 }, 0); - out.SetPixel(from, colorIndex); + SetMapPixel(out, { from.x, from.y + 1 }, 0); + SetMapPixel(out, from, colorIndex); from.x += static_cast(DirX); from.y += static_cast(DirY); } - out.SetPixel({ from.x, from.y + 1 }, 0); - out.SetPixel(from, colorIndex); + SetMapPixel(out, { from.x, from.y + 1 }, 0); + SetMapPixel(out, from, colorIndex); } template void DrawMapLineSteep(const Surface &out, Point from, int width, std::uint8_t colorIndex) { while (width-- > 0) { - out.SetPixel({ from.x, from.y + 1 }, 0); - out.SetPixel(from, colorIndex); + SetMapPixel(out, { from.x, from.y + 1 }, 0); + SetMapPixel(out, from, colorIndex); from.y += static_cast(DirY); - out.SetPixel({ from.x, from.y + 1 }, 0); - out.SetPixel(from, colorIndex); + SetMapPixel(out, { from.x, from.y + 1 }, 0); + SetMapPixel(out, from, colorIndex); from.y += static_cast(DirY); from.x += static_cast(DirX); } - out.SetPixel({ from.x, from.y + 1 }, 0); - out.SetPixel(from, colorIndex); + SetMapPixel(out, { from.x, from.y + 1 }, 0); + SetMapPixel(out, from, colorIndex); } } // namespace +void DrawMapLineNS(const Surface &out, Point from, int height, std::uint8_t colorIndex) +{ + if (from.x < 0 || from.x >= out.w() || from.y >= out.h() || height <= 0 || from.y + height <= 0) + return; + + if (from.y < 0) { + height += from.y; + from.y = 0; + } + + if (from.y + height > out.h()) + height = out.h() - from.y; + + for (int i = 0; i < height; ++i) { + SetMapPixel(out, { from.x, from.y + i }, colorIndex); + } +} + +void DrawMapLineWE(const Surface &out, Point from, int width, std::uint8_t colorIndex) +{ + if (from.y < 0 || from.y >= out.h() || from.x >= out.w() || width <= 0 || from.x + width <= 0) + return; + + if (from.x < 0) { + width += from.x; + from.x = 0; + } + + if (from.x + width > out.w()) + width = out.w() - from.x; + + for (int i = 0; i < width; ++i) { + SetMapPixel(out, { from.x + i, from.y }, colorIndex); + } +} + void DrawMapLineNE(const Surface &out, Point from, int height, std::uint8_t colorIndex) { DrawMapLine(out, from, height, colorIndex); @@ -94,4 +131,46 @@ void DrawMapLineSteepSW(const Surface &out, Point from, int width, std::uint8_t DrawMapLineSteep(out, from, width, colorIndex); } +/** + * @brief Draws a line from first point to second point, unrestricted to the standard automap angles. Doesn't include shadow. + */ +void DrawMapFreeLine(const Surface &out, Point from, Point to, uint8_t colorIndex) +{ + int dx = std::abs(to.x - from.x); + int dy = std::abs(to.y - from.y); + int sx = from.x < to.x ? 1 : -1; + int sy = from.y < to.y ? 1 : -1; + int err = dx - dy; + + while (true) { + out.SetPixel(from, colorIndex); + + if (from.x == to.x && from.y == to.y) { + break; + } + + int e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + from.x += sx; + } + if (e2 < dx) { + err += dx; + from.y += sy; + } + } +} + +void SetMapPixel(const Surface &out, Point position, uint8_t color) +{ + if (GetAutomapType() == AutomapType::Minimap && !MinimapRect.contains(position)) + return; + + if (GetAutomapType() == AutomapType::Transparent) { + SetHalfTransparentPixel(out, position, color); + } else { + out.SetPixel(position, color); + } +} + } // namespace devilution diff --git a/Source/engine/render/automap_render.hpp b/Source/engine/render/automap_render.hpp index df342eac78e..75b73479b6d 100644 --- a/Source/engine/render/automap_render.hpp +++ b/Source/engine/render/automap_render.hpp @@ -17,6 +17,8 @@ namespace devilution { +void DrawMapLineNS(const Surface &out, Point from, int height, std::uint8_t colorIndex); +void DrawMapLineWE(const Surface &out, Point from, int height, std::uint8_t colorIndex); /** * @brief Draw a line in the target buffer from the given point towards north east at an `atan(1/2)` angle. * @@ -88,5 +90,13 @@ void DrawMapLineSteepNW(const Surface &out, Point from, int width, std::uint8_t * The end point is at `{ from.x - (width + 1), from.y + 2 * width }`. */ void DrawMapLineSteepSW(const Surface &out, Point from, int width, std::uint8_t colorIndex); +void DrawMapFreeLine(const Surface &out, Point from, Point to, uint8_t colorIndex); + +/** + * @brief Draw an automap pixel. + * + * Draw either an opaque pixel or a transparent pixel, depending on the automap mode. + */ +void SetMapPixel(const Surface &out, Point position, uint8_t color); } // namespace devilution diff --git a/Source/engine/render/blit_impl.hpp b/Source/engine/render/blit_impl.hpp index 5066b7d51ed..c336ea0b1db 100644 --- a/Source/engine/render/blit_impl.hpp +++ b/Source/engine/render/blit_impl.hpp @@ -72,7 +72,7 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void BlitFillBlended(uint8_t *dst, unsigned { assert(length != 0); const uint8_t *end = dst + length; - const std::array &tbl = paletteTransparencyLookup[color]; + const uint8_t *const tbl = paletteTransparencyLookup[color]; while (dst + 3 < end) { *dst = tbl[*dst]; ++dst; diff --git a/Source/engine/render/clx_render.cpp b/Source/engine/render/clx_render.cpp index 74cc8b52c31..e538749f6e3 100644 --- a/Source/engine/render/clx_render.cpp +++ b/Source/engine/render/clx_render.cpp @@ -212,45 +212,47 @@ void DoRenderBackwards( template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderOutlineForPixel(uint8_t *dst, int dstPitch, uint8_t color) { - if (North) + if constexpr (North) dst[-dstPitch] = color; - if (West) + if constexpr (West) dst[-1] = color; - if (East) + if constexpr (East) dst[1] = color; - if (South) + if constexpr (South) dst[dstPitch] = color; } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderOutlineForPixel(uint8_t *dst, int dstPitch, uint8_t srcColor, uint8_t color) { - if (SkipColorIndexZero && srcColor == 0) - return; + if constexpr (SkipColorIndexZero) { + if (srcColor == 0) + return; + } RenderOutlineForPixel(dst, dstPitch, color); } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderOutlineForPixels(uint8_t *dst, int dstPitch, int width, uint8_t color) { - if (North) + if constexpr (North) std::memset(dst - dstPitch, color, width); - if (West && East) + if constexpr (West && East) std::memset(dst - 1, color, width + 2); - else if (West) + else if constexpr (West) std::memset(dst - 1, color, width); - else if (East) + else if constexpr (East) std::memset(dst + 1, color, width); - if (South) + if constexpr (South) std::memset(dst + dstPitch, color, width); } template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderOutlineForPixels(uint8_t *dst, int dstPitch, int width, const uint8_t *src, uint8_t color) { - if (SkipColorIndexZero) { + if constexpr (SkipColorIndexZero) { while (width-- > 0) RenderOutlineForPixel(dst++, dstPitch, *src++, color); } else { @@ -264,7 +266,7 @@ void RenderClxOutlinePixelsCheckFirstColumn( const uint8_t *src, uint8_t width, uint8_t color) { if (dstX == -1) { - if (Fill) { + if constexpr (Fill) { RenderOutlineForPixel( dst++, dstPitch, color); } else { @@ -274,7 +276,7 @@ void RenderClxOutlinePixelsCheckFirstColumn( --width; } if (width > 0) { - if (Fill) { + if constexpr (Fill) { RenderOutlineForPixel(dst++, dstPitch, color); } else { RenderOutlineForPixel(dst++, dstPitch, *src++, color); @@ -282,7 +284,7 @@ void RenderClxOutlinePixelsCheckFirstColumn( --width; } if (width > 0) { - if (Fill) { + if constexpr (Fill) { RenderOutlineForPixels(dst, dstPitch, width, color); } else { RenderOutlineForPixels(dst, dstPitch, width, src, color); @@ -300,7 +302,7 @@ void RenderClxOutlinePixelsCheckLastColumn( const int numSpecialPixels = (lastPixel ? 1 : 0) + (oobPixel ? 1 : 0); if (width > numSpecialPixels) { width -= numSpecialPixels; - if (Fill) { + if constexpr (Fill) { RenderOutlineForPixels(dst, dstPitch, width, color); } else { RenderOutlineForPixels(dst, dstPitch, width, src, color); @@ -309,14 +311,14 @@ void RenderClxOutlinePixelsCheckLastColumn( dst += width; } if (lastPixel) { - if (Fill) { + if constexpr (Fill) { RenderOutlineForPixel(dst++, dstPitch, color); } else { RenderOutlineForPixel(dst++, dstPitch, *src++, color); } } if (oobPixel) { - if (Fill) { + if constexpr (Fill) { RenderOutlineForPixel(dst, dstPitch, color); } else { RenderOutlineForPixel(dst, dstPitch, *src, color); @@ -329,16 +331,27 @@ void RenderClxOutlinePixels( uint8_t *dst, int dstPitch, int dstX, int dstW, const uint8_t *src, uint8_t width, uint8_t color) { - if (SkipColorIndexZero && Fill && *src == 0) - return; + if constexpr (SkipColorIndexZero && Fill) { + if (*src == 0) + return; + } - if (CheckFirstColumn && dstX <= 0) { - RenderClxOutlinePixelsCheckFirstColumn( - dst, dstPitch, dstX, src, width, color); - } else if (CheckLastColumn && dstX + width >= dstW) { - RenderClxOutlinePixelsCheckLastColumn( - dst, dstPitch, dstX, dstW, src, width, color); - } else if (Fill) { + if constexpr (CheckFirstColumn) { + if (dstX <= 0) { + RenderClxOutlinePixelsCheckFirstColumn( + dst, dstPitch, dstX, src, width, color); + return; + } + } + if constexpr (CheckLastColumn) { + if (dstX + width >= dstW) { + RenderClxOutlinePixelsCheckLastColumn( + dst, dstPitch, dstX, dstW, src, width, color); + return; + } + } + + if constexpr (Fill) { RenderOutlineForPixels(dst, dstPitch, width, color); } else { RenderOutlineForPixels(dst, dstPitch, width, src, color); @@ -370,7 +383,7 @@ const uint8_t *RenderClxOutlineRowClipped( // NOLINT(readability-function-cognit dst += w; }; - if (ClipWidth) { + if constexpr (ClipWidth) { auto remainingLeftClip = clipX.left - skipSize.xOffset; if (skipSize.xOffset > clipX.left) { position.x += static_cast(skipSize.xOffset - clipX.left); @@ -409,7 +422,11 @@ const uint8_t *RenderClxOutlineRowClipped( // NOLINT(readability-function-cognit if (IsClxOpaque(v)) { const bool fill = IsClxOpaqueFill(v); v = fill ? GetClxOpaqueFillWidth(v) : GetClxOpaquePixelsWidth(v); - renderPixels(fill, ClipWidth ? std::min(remainingWidth, static_cast(v)) : v); + if constexpr (ClipWidth) { + renderPixels(fill, std::min(remainingWidth, static_cast(v))); + } else { + renderPixels(fill, v); + } } else { dst += v; } @@ -417,14 +434,14 @@ const uint8_t *RenderClxOutlineRowClipped( // NOLINT(readability-function-cognit position.x += v; } - if (ClipWidth) { + if constexpr (ClipWidth) { remainingWidth += clipX.right; if (remainingWidth > 0) { skipSize.xOffset = static_cast(srcWidth) - remainingWidth; return SkipRestOfLineWithOverrun(src, static_cast(srcWidth), skipSize); } } - skipSize = GetSkipSize(remainingWidth, srcWidth); + skipSize = GetSkipSize(remainingWidth, static_cast(srcWidth)); return src; } @@ -647,6 +664,59 @@ void ClxApplyTrans(ClxSpriteSheet sheet, const uint8_t *trn) } } +bool IsPointWithinClx(Point position, ClxSprite clx) +{ + const uint8_t *src = clx.pixelData(); + const uint8_t *end = src + clx.pixelDataSize(); + const uint16_t width = clx.width(); + + int xCur = 0; + int yCur = clx.height() - 1; + while (src < end) { + if (yCur != position.y) { + SkipSize skipSize {}; + skipSize.xOffset = xCur; + src = SkipRestOfLineWithOverrun(src, width, skipSize); + yCur -= skipSize.wholeLines; + xCur = skipSize.xOffset; + if (yCur < position.y) + return false; + continue; + } + + while (xCur < width) { + uint8_t val = *src++; + if (!IsClxOpaque(val)) { + // ignore transparent + xCur += val; + if (xCur > position.x) + return false; + continue; + } + + if (IsClxOpaqueFill(val)) { + val = GetClxOpaqueFillWidth(val); + uint8_t color = *src++; + if (xCur <= position.x && position.x < xCur + val) + return color != 0; // ignore shadows + xCur += val; + } else { + val = GetClxOpaquePixelsWidth(val); + for (uint8_t pixel = 0; pixel < val; pixel++) { + uint8_t color = *src++; + if (xCur == position.x) + return color != 0; // ignore shadows + xCur++; + } + } + } + + return false; + } + + return false; +} + std::pair ClxMeasureSolidHorizontalBounds(ClxSprite clx) { const uint8_t *src = clx.pixelData(); diff --git a/Source/engine/render/clx_render.hpp b/Source/engine/render/clx_render.hpp index 4a8f06416ea..d7546815ab4 100644 --- a/Source/engine/render/clx_render.hpp +++ b/Source/engine/render/clx_render.hpp @@ -110,6 +110,11 @@ inline void ClxDrawLightBlended(const Surface &out, Point position, ClxSprite cl ClxDrawBlendedTRN(out, position, clx, LightTables[LightTableIndex].data()); } +/** + * Returns if cursor is within the CLX sprite (ignores shadow) + */ +bool IsPointWithinClx(Point position, ClxSprite clx); + /** * Returns a pair of X coordinates containing the start (inclusive) and end (exclusive) * of fully transparent columns in the sprite. diff --git a/Source/engine/render/dun_render.cpp b/Source/engine/render/dun_render.cpp index 1d39d2d9e5a..565a1fce5b2 100644 --- a/Source/engine/render/dun_render.cpp +++ b/Source/engine/render/dun_render.cpp @@ -19,10 +19,10 @@ #include #include "engine/render/blit_impl.hpp" +#include "levels/dun_tile.hpp" #include "lighting.h" #include "options.h" #include "utils/attributes.h" -#include "utils/stdcompat/algorithm.hpp" #ifdef DEBUG_STR #include "engine/render/text_render.hpp" #endif @@ -35,19 +35,19 @@ namespace devilution { namespace { /** Width of a tile rendering primitive. */ -constexpr int_fast16_t Width = TILE_WIDTH / 2; +constexpr int_fast16_t Width = DunFrameWidth; /** Height of a tile rendering primitive (except triangles). */ -constexpr int_fast16_t Height = TILE_HEIGHT; +constexpr int_fast16_t Height = DunFrameHeight; /** Height of the lower triangle of a triangular or a trapezoid tile. */ -constexpr int_fast16_t LowerHeight = TILE_HEIGHT / 2; +constexpr int_fast16_t LowerHeight = DunFrameHeight / 2; /** Height of the upper triangle of a triangular tile. */ -constexpr int_fast16_t TriangleUpperHeight = TILE_HEIGHT / 2 - 1; +constexpr int_fast16_t TriangleUpperHeight = DunFrameHeight / 2 - 1; /** Height of the upper rectangle of a trapezoid tile. */ -constexpr int_fast16_t TrapezoidUpperHeight = TILE_HEIGHT / 2; +constexpr int_fast16_t TrapezoidUpperHeight = DunFrameHeight / 2; constexpr int_fast16_t TriangleHeight = LowerHeight + TriangleUpperHeight; @@ -62,7 +62,7 @@ int_fast16_t GetTileHeight(TileType tile) } #ifdef DEBUG_STR -std::pair GetTileDebugStr(TileType tile) +std::pair GetTileDebugStr(TileType tile) { // clang-format off switch (tile) { @@ -139,7 +139,7 @@ DVL_ALWAYS_INLINE int8_t InitPrefix(int8_t y) template std::string prefixDebugString(int8_t prefix) { std::string out(32, OpaquePrefix ? '0' : '1'); - const uint8_t clamped = clamp(prefix, 0, 32); + const uint8_t clamped = std::clamp(prefix, 0, 32); out.replace(0, clamped, clamped, OpaquePrefix ? '1' : '0'); StrAppend(out, " prefix=", prefix, " OpaquePrefix=", OpaquePrefix, " PrefixIncrement=", PrefixIncrement); return out; @@ -215,7 +215,7 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLineTransparent(uint8_t *DVL_REST template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLineTransparentOrOpaque(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, uint_fast8_t width, const uint8_t *DVL_RESTRICT tbl) { - if (Transparent) { + if constexpr (Transparent) { RenderLineTransparent(dst, src, width, tbl); } else { RenderLineOpaque(dst, src, width, tbl); @@ -225,12 +225,12 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLineTransparentOrOpaque(uint8_t * template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLineTransparentAndOpaque(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, uint_fast8_t prefixWidth, uint_fast8_t width, const uint8_t *DVL_RESTRICT tbl) { - if (OpaquePrefix) { + if constexpr (OpaquePrefix) { RenderLineOpaque(dst, src, prefixWidth, tbl); - if (!SkipTransparentPixels) + if constexpr (!SkipTransparentPixels) RenderLineTransparent(dst + prefixWidth, src + prefixWidth, width - prefixWidth, tbl); } else { - if (!SkipTransparentPixels) + if constexpr (!SkipTransparentPixels) RenderLineTransparent(dst, src, prefixWidth, tbl); RenderLineOpaque(dst + prefixWidth, src + prefixWidth, width - prefixWidth, tbl); } @@ -239,21 +239,21 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLineTransparentAndOpaque(uint8_t template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderLine(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, uint_fast8_t n, const uint8_t *DVL_RESTRICT tbl, int8_t prefix) { - if (PrefixIncrement == 0) { + if constexpr (PrefixIncrement == 0) { RenderLineTransparentOrOpaque(dst, src, n, tbl); } else if (prefix >= static_cast(n)) { - // We clamp the prefix to (0, n] and avoid calling `RenderLineTransparent/Opaque` with width=0. - if (OpaquePrefix) { + // We std::clamp the prefix to (0, n] and avoid calling `RenderLineTransparent/Opaque` with width=0. + if constexpr (OpaquePrefix) { RenderLineOpaque(dst, src, n, tbl); } else { - if (!SkipTransparentPixels) + if constexpr (!SkipTransparentPixels) RenderLineTransparent(dst, src, n, tbl); } } else if (prefix <= 0) { - if (!OpaquePrefix) { + if constexpr (!OpaquePrefix) { RenderLineOpaque(dst, src, n, tbl); } else { - if (!SkipTransparentPixels) + if constexpr (!SkipTransparentPixels) RenderLineTransparent(dst, src, n, tbl); } } else { @@ -728,13 +728,13 @@ DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderRightTriangle(uint8_t *DVL_RESTRI template DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void RenderTrapezoidUpperHalf(uint8_t *DVL_RESTRICT dst, uint16_t dstPitch, const uint8_t *DVL_RESTRICT src, const uint8_t *DVL_RESTRICT tbl) { - if (PrefixIncrement != 0) { + if constexpr (PrefixIncrement != 0) { // The first and the last line are always fully transparent/opaque (or vice-versa). // We handle them specially to avoid calling the blitter with width=0. const uint8_t *srcEnd = src + Width * TrapezoidUpperHeight; constexpr bool FirstLineIsTransparent = OpaquePrefix ^ (PrefixIncrement < 0); - if (FirstLineIsTransparent) { - if (!SkipTransparentPixels) + if constexpr (FirstLineIsTransparent) { + if constexpr (!SkipTransparentPixels) RenderLineTransparent(dst, src, Width, tbl); } else { RenderLineOpaque(dst, src, Width, tbl); @@ -1093,7 +1093,7 @@ void RenderBlackTileFull(uint8_t *DVL_RESTRICT dst, uint16_t dstPitch) #ifdef DUN_RENDER_STATS std::unordered_map DunRenderStats; -string_view TileTypeToString(TileType tileType) +std::string_view TileTypeToString(TileType tileType) { // clang-format off switch (tileType) { @@ -1108,7 +1108,7 @@ string_view TileTypeToString(TileType tileType) // clang-format on } -string_view MaskTypeToString(MaskType maskType) +std::string_view MaskTypeToString(MaskType maskType) { // clang-format off switch (maskType) { @@ -1174,8 +1174,8 @@ void RenderTile(const Surface &out, Point position, } #ifdef DEBUG_STR - const std::pair debugStr = GetTileDebugStr(tile); - DrawString(out, debugStr.first, Rectangle { Point { position.x + 2, position.y - 29 }, Size { 28, 28 } }, debugStr.second); + const auto [debugStr, flags] = GetTileDebugStr(tile); + DrawString(out, debugStr, Rectangle { Point { position.x + 2, position.y - 29 }, Size { 28, 28 } }, { .flags = flags }); #endif } diff --git a/Source/engine/render/dun_render.hpp b/Source/engine/render/dun_render.hpp index 0f7f84e916e..88d4ed0c57b 100644 --- a/Source/engine/render/dun_render.hpp +++ b/Source/engine/render/dun_render.hpp @@ -9,7 +9,9 @@ #include -#include "engine.h" +#include "engine/point.hpp" +#include "engine/surface.hpp" +#include "levels/dun_tile.hpp" // #define DUN_RENDER_STATS #ifdef DUN_RENDER_STATS @@ -18,72 +20,6 @@ namespace devilution { -/** - * Level tile type. - * - * The tile type determines data encoding and the shape. - * - * Each tile type has its own encoding but they all encode data in the order - * of bottom-to-top (bottom row first). - */ -enum class TileType : uint8_t { - /** - * 🮆 A 32x32 square. Stored as an array of pixels. - */ - Square, - - /** - * 🮆 A 32x32 square with transparency. RLE encoded. - * - * Each run starts with an int8_t value. - * If positive, it is followed by this many pixels. - * If negative, it indicates `-value` fully transparent pixels, which are omitted. - * - * Runs do not cross row boundaries. - */ - TransparentSquare, - - /** - *🭮 Left-pointing 32x31 triangle. Encoded as 31 varying-width rows with 2 padding bytes before every even row. - * - * The smallest rows (bottom and top) are 2px wide, the largest row is 16px wide (middle row). - * - * Encoding: - * for i in [0, 30]: - * - 2 unused bytes if i is even - * - row (only the pixels within the triangle) - */ - LeftTriangle, - - /** - * 🭬Right-pointing 32x31 triangle. Encoded as 31 varying-width rows with 2 padding bytes after every even row. - * - * The smallest rows (bottom and top) are 2px wide, the largest row is 16px wide (middle row). - * - * Encoding: - * for i in [0, 30]: - * - row (only the pixels within the triangle) - * - 2 unused bytes if i is even - */ - RightTriangle, - - /** - * 🭓 Left-pointing 32x32 trapezoid: a 32x16 rectangle and the 16x16 bottom part of `LeftTriangle`. - * - * Begins with triangle part, which uses the `LeftTriangle` encoding, - * and is followed by a flat array of pixels for the top rectangle part. - */ - LeftTrapezoid, - - /** - * 🭞 Right-pointing 32x32 trapezoid: 32x16 rectangle and the 16x16 bottom part of `RightTriangle`. - * - * Begins with the triangle part, which uses the `RightTriangle` encoding, - * and is followed by a flat array of pixels for the top rectangle part. - */ - RightTrapezoid, -}; - /** * @brief Specifies the mask to use for rendering. */ @@ -205,35 +141,6 @@ enum class MaskType : uint8_t { LeftFoliage, }; -/** - * Specifies the current MIN block of the level CEL file, as used during rendering of the level tiles. - */ -class LevelCelBlock { -public: - explicit LevelCelBlock(uint16_t data) - : data_(data) - { - } - - [[nodiscard]] bool hasValue() const - { - return data_ != 0; - } - - [[nodiscard]] TileType type() const - { - return static_cast((data_ & 0x7000) >> 12); - } - - [[nodiscard]] uint16_t frame() const - { - return data_ & 0xFFF; - } - -private: - uint16_t data_; -}; - #ifdef DUN_RENDER_STATS struct DunRenderType { TileType tileType; @@ -251,9 +158,9 @@ struct DunRenderTypeHash { }; extern std::unordered_map DunRenderStats; -string_view TileTypeToString(TileType tileType); +std::string_view TileTypeToString(TileType tileType); -string_view MaskTypeToString(MaskType maskType); +std::string_view MaskTypeToString(MaskType maskType); #endif /** diff --git a/Source/engine/render/scrollrt.cpp b/Source/engine/render/scrollrt.cpp index dfdd273d10c..9198a85155c 100644 --- a/Source/engine/render/scrollrt.cpp +++ b/Source/engine/render/scrollrt.cpp @@ -5,6 +5,7 @@ */ #include "engine/render/scrollrt.h" +#include #include #include "DiabloUI/ui_flags.hpp" @@ -12,6 +13,7 @@ #include "controls/plrctrls.h" #include "cursor.h" #include "dead.h" +#include "diablo_msg.hpp" #include "doom.h" #include "engine/backbuffer_state.hpp" #include "engine/dx.h" @@ -19,18 +21,21 @@ #include "engine/render/dun_render.hpp" #include "engine/render/text_render.hpp" #include "engine/trn.hpp" -#include "error.h" +#include "engine/world_tile.hpp" #include "gmenu.h" #include "help.h" #include "hwcursor.hpp" #include "init.h" #include "inv.h" #include "lighting.h" +#include "lua/lua.hpp" #include "minitext.h" #include "missiles.h" #include "nthread.h" #include "options.h" #include "panels/charpanel.hpp" +#include "panels/console.hpp" +#include "panels/spell_list.hpp" #include "plrmsg.h" #include "qol/chatlog.h" #include "qol/floatingnumbers.h" @@ -74,20 +79,10 @@ bool frameflag; namespace { -/** - * @brief Hash algorithm for point - */ -struct PointHash { - std::size_t operator()(Point const &s) const noexcept - { - return s.x ^ (s.y << 1); - } -}; - /** * @brief Contains all Missile at rendering position */ -std::unordered_multimap MissilesAtRenderingTile; +std::unordered_multimap MissilesAtRenderingTile; /** * @brief Could the missile (at the next game tick) collide? This method is a simplified version of CheckMissileCol (for example without random). @@ -171,28 +166,8 @@ void UpdateMissilesRendererData() } } -/** - * @brief Keeps track of which tiles have been rendered already. - */ -Bitset2d dRendered; - int lastFpsUpdateInMs; -const char *const PlayerModeNames[] = { - "standing", - "walking (1)", - "walking (2)", - "walking (3)", - "attacking (melee)", - "attacking (ranged)", - "blocking", - "getting hit", - "dying", - "casting a spell", - "changing levels", - "quitting" -}; - Rectangle PrevCursorRect; void BlitCursor(uint8_t *dst, uint32_t dstPitch, uint8_t *src, uint32_t srcPitch, uint32_t srcWidth, uint32_t srcHeight) @@ -263,13 +238,15 @@ void DrawCursor(const Surface &out) // Copy the buffer before the item cursor and its 1px outline are drawn to a temporary buffer. const int outlineWidth = !MyPlayer->HoldItem.isEmpty() ? 1 : 0; + Displacement offset = !MyPlayer->HoldItem.isEmpty() ? Displacement { cursSize / 2 } : Displacement { 0 }; + Point cursPosition = MousePosition - offset; Rectangle &rect = cursor.rect; - rect.position.x = MousePosition.x - outlineWidth; + rect.position.x = cursPosition.x - outlineWidth; rect.size.width = cursSize.width + 2 * outlineWidth; Clip(rect.position.x, rect.size.width, out.w()); - rect.position.y = MousePosition.y - outlineWidth; + rect.position.y = cursPosition.y - outlineWidth; rect.size.height = cursSize.height + 2 * outlineWidth; Clip(rect.position.y, rect.size.height, out.h()); @@ -277,7 +254,7 @@ void DrawCursor(const Surface &out) return; BlitCursor(cursor.behindBuffer, rect.size.width, &out[rect.position], out.pitch(), rect.size.width, rect.size.height); - DrawSoftwareCursor(out, MousePosition + Displacement { 0, cursSize.height - 1 }, pcurs); + DrawSoftwareCursor(out, cursPosition + Displacement { 0, cursSize.height - 1 }, pcurs); } /** @@ -309,10 +286,10 @@ void DrawMissilePrivate(const Surface &out, const Missile &missile, Point target * @param targetBufferPosition Output buffer coordinates * @param pre Is the sprite in the background */ -void DrawMissile(const Surface &out, Point tilePosition, Point targetBufferPosition, bool pre) +void DrawMissile(const Surface &out, WorldTilePosition tilePosition, Point targetBufferPosition, bool pre) { - const auto range = MissilesAtRenderingTile.equal_range(tilePosition); - for (auto it = range.first; it != range.second; it++) { + const auto [begin, end] = MissilesAtRenderingTile.equal_range(tilePosition); + for (auto it = begin; it != end; ++it) { DrawMissilePrivate(out, *it->second, targetBufferPosition, pre); } } @@ -353,8 +330,13 @@ void DrawMonster(const Surface &out, Point tilePosition, Point targetBufferPosit /** * @brief Helper for rendering a specific player icon (Mana Shield or Reflect) */ -void DrawPlayerIconHelper(const Surface &out, MissileGraphicID missileGraphicId, Point position, bool lighting, bool infraVision) +void DrawPlayerIconHelper(const Surface &out, MissileGraphicID missileGraphicId, Point position, const Player &player, bool infraVision) { + bool lighting = &player != MyPlayer; + + if (player.isWalking()) + position += GetOffsetForWalking(player.AnimInfo, player._pdir); + position.x -= GetMissileSpriteData(missileGraphicId).animWidth2; const ClxSprite sprite = (*GetMissileSpriteData(missileGraphicId).sprites).list()[0]; @@ -382,9 +364,9 @@ void DrawPlayerIconHelper(const Surface &out, MissileGraphicID missileGraphicId, void DrawPlayerIcons(const Surface &out, const Player &player, Point position, bool infraVision) { if (player.pManaShield) - DrawPlayerIconHelper(out, MissileGraphicID::ManaShield, position, &player != MyPlayer, infraVision); + DrawPlayerIconHelper(out, MissileGraphicID::ManaShield, position, player, infraVision); if (player.wReflections > 0) - DrawPlayerIconHelper(out, MissileGraphicID::Reflect, position + Displacement { 0, 16 }, &player != MyPlayer, infraVision); + DrawPlayerIconHelper(out, MissileGraphicID::Reflect, position + Displacement { 0, 16 }, player, infraVision); } /** @@ -400,11 +382,10 @@ void DrawPlayer(const Surface &out, const Player &player, Point tilePosition, Po return; } - const ClxSprite sprite = player.previewCelSprite ? *player.previewCelSprite : player.AnimInfo.currentSprite(); - - Point spriteBufferPosition = targetBufferPosition - Displacement { CalculateWidth2(sprite.width()), 0 }; + const ClxSprite sprite = player.currentSprite(); + Point spriteBufferPosition = targetBufferPosition + player.getRenderingOffset(sprite); - if (static_cast(pcursplr) < Players.size() && &player == &Players[pcursplr]) + if (&player == PlayerUnderCursor) ClxDrawOutlineSkipColorZero(out, 165, spriteBufferPosition, sprite); if (&player == MyPlayer && IsNoneOf(leveltype, DTYPE_NEST, DTYPE_CRYPT)) { @@ -453,34 +434,16 @@ void DrawDeadPlayer(const Surface &out, Point tilePosition, Point targetBufferPo /** * @brief Render an object sprite * @param out Output buffer + * @param objectToDraw Dungeone object to draw * @param tilePosition dPiece coordinates * @param targetBufferPosition Output buffer coordinates * @param pre Is the sprite in the background */ -void DrawObject(const Surface &out, Point tilePosition, Point targetBufferPosition, bool pre) +void DrawObject(const Surface &out, const Object &objectToDraw, Point tilePosition, Point targetBufferPosition) { - if (LightTableIndex >= LightsMax) { - return; - } - - Object *object = FindObjectAtPosition(tilePosition); - if (object == nullptr) { - return; - } - - const Object &objectToDraw = *object; - if (objectToDraw._oPreFlag != pre) { - return; - } + const ClxSprite sprite = objectToDraw.currentSprite(); - const ClxSprite sprite = (*objectToDraw._oAnimData)[objectToDraw._oAnimFrame - 1]; - - Point screenPosition = targetBufferPosition - Displacement { CalculateWidth2(sprite.width()), 0 }; - if (objectToDraw.position != tilePosition) { - // drawing a large or offset object, calculate the correct position for the center of the sprite - Displacement worldOffset = objectToDraw.position - tilePosition; - screenPosition -= worldOffset.worldToScreen(); - } + const Point screenPosition = targetBufferPosition + objectToDraw.getRenderingOffset(sprite, tilePosition); if (&objectToDraw == ObjectUnderCursor) { ClxDrawOutlineSkipColorZero(out, 194, screenPosition, sprite); @@ -647,26 +610,17 @@ void DrawFloor(const Surface &out, Point tilePosition, Point targetBufferPositio * @param targetBufferPosition Output buffer coordinates * @param pre Is the sprite in the background */ -void DrawItem(const Surface &out, Point tilePosition, Point targetBufferPosition, bool pre) +void DrawItem(const Surface &out, int8_t itemIndex, Point targetBufferPosition) { - int8_t bItem = dItem[tilePosition.x][tilePosition.y]; - - if (bItem <= 0) - return; - - auto &item = Items[bItem - 1]; - if (item._iPostDraw == pre) - return; - + const Item &item = Items[itemIndex]; const ClxSprite sprite = item.AnimInfo.currentSprite(); - int px = targetBufferPosition.x - CalculateWidth2(sprite.width()); - const Point position { px, targetBufferPosition.y }; - if (stextflag == TalkID::None && (bItem - 1 == pcursitem || AutoMapShowItems)) { + const Point position = targetBufferPosition + item.getRenderingOffset(sprite); + if (stextflag == TalkID::None && (itemIndex == pcursitem || AutoMapShowItems)) { ClxDrawOutlineSkipColorZero(out, GetOutlineColor(item, false), position, sprite); } ClxDrawLight(out, position, sprite); if (item.AnimInfo.isLastFrame() || item._iCurs == ICURS_MAGIC_ROCK) - AddItemToLabelQueue(bItem - 1, position); + AddItemToLabelQueue(itemIndex, position); } /** @@ -679,14 +633,13 @@ void DrawMonsterHelper(const Surface &out, Point tilePosition, Point targetBuffe { int mi = dMonster[tilePosition.x][tilePosition.y]; bool isNegativeMonster = mi < 0; - mi = abs(mi) - 1; + mi = std::abs(mi) - 1; if (leveltype == DTYPE_TOWN) { if (isNegativeMonster) return; auto &towner = Towners[mi]; - int px = targetBufferPosition.x - CalculateWidth2(towner._tAnimWidth); - const Point position { px, targetBufferPosition.y }; + const Point position = targetBufferPosition + towner.getRenderingOffset(); const ClxSprite sprite = towner.currentSprite(); if (mi == pcursmonst) { ClxDrawOutlineSkipColorZero(out, 166, position, sprite); @@ -710,21 +663,20 @@ void DrawMonsterHelper(const Surface &out, Point tilePosition, Point targetBuffe const ClxSprite sprite = monster.animInfo.currentSprite(); - Displacement offset = {}; + Displacement offset = monster.getRenderingOffset(sprite); if (monster.isWalking()) { bool isSideWalkingToLeft = monster.mode == MonsterMode::MoveSideways && monster.direction == Direction::West; if (isNegativeMonster && !isSideWalkingToLeft) return; if (!isNegativeMonster && isSideWalkingToLeft) return; - offset = GetOffsetForWalking(monster.animInfo, monster.direction); if (isSideWalkingToLeft) offset -= Displacement { 64, 0 }; } else if (isNegativeMonster) { return; } - const Point monsterRenderPosition { targetBufferPosition + offset - Displacement { CalculateWidth2(sprite.width()), 0 } }; + const Point monsterRenderPosition = targetBufferPosition + offset; if (mi == pcursmonst) { ClxDrawOutlineSkipColorZero(out, 233, monsterRenderPosition, sprite); } @@ -740,14 +692,7 @@ void DrawMonsterHelper(const Surface &out, Point tilePosition, Point targetBuffe */ void DrawPlayerHelper(const Surface &out, const Player &player, Point tilePosition, Point targetBufferPosition) { - Displacement offset = {}; - if (player.isWalking()) { - offset = GetOffsetForWalking(player.AnimInfo, player._pdir); - } - - const Point playerRenderPosition { targetBufferPosition + offset }; - - DrawPlayer(out, player, tilePosition, playerRenderPosition); + DrawPlayer(out, player, tilePosition, targetBufferPosition); } /** @@ -759,17 +704,12 @@ void DrawPlayerHelper(const Surface &out, const Player &player, Point tilePositi void DrawDungeon(const Surface &out, Point tilePosition, Point targetBufferPosition) { assert(InDungeonBounds(tilePosition)); - - if (dRendered.test(tilePosition.x, tilePosition.y)) - return; - dRendered.set(tilePosition.x, tilePosition.y); - LightTableIndex = dLight[tilePosition.x][tilePosition.y]; DrawCell(out, tilePosition, targetBufferPosition); - int8_t bDead = dCorpse[tilePosition.x][tilePosition.y]; - int8_t bMap = dTransVal[tilePosition.x][tilePosition.y]; + const int8_t bDead = dCorpse[tilePosition.x][tilePosition.y]; + const int8_t bMap = dTransVal[tilePosition.x][tilePosition.y]; #ifdef _DEBUG if (DebugVision && IsTileLit(tilePosition)) { @@ -782,7 +722,7 @@ void DrawDungeon(const Surface &out, Point tilePosition, Point targetBufferPosit } if (LightTableIndex < LightsMax && bDead != 0) { - Corpse &corpse = Corpses[(bDead & 0x1F) - 1]; + const Corpse &corpse = Corpses[(bDead & 0x1F) - 1]; const Point position { targetBufferPosition.x - CalculateWidth2(corpse.width), targetBufferPosition.y }; const ClxSprite sprite = corpse.spritesForDirection(static_cast((bDead >> 5) & 7))[corpse.frame]; if (corpse.translationPaletteIndex != 0) { @@ -792,8 +732,17 @@ void DrawDungeon(const Surface &out, Point tilePosition, Point targetBufferPosit ClxDrawLight(out, position, sprite); } } - DrawObject(out, tilePosition, targetBufferPosition, true); - DrawItem(out, tilePosition, targetBufferPosition, true); + + const int8_t bItem = dItem[tilePosition.x][tilePosition.y]; + const Object *object = LightTableIndex < LightsMax + ? FindObjectAtPosition(tilePosition) + : nullptr; + if (object != nullptr && object->_oPreFlag) { + DrawObject(out, *object, tilePosition, targetBufferPosition); + } + if (bItem > 0 && !Items[bItem - 1]._iPostDraw) { + DrawItem(out, static_cast(bItem - 1), targetBufferPosition); + } if (TileContainsDeadPlayer(tilePosition)) { DrawDeadPlayer(out, tilePosition, targetBufferPosition); @@ -806,8 +755,13 @@ void DrawDungeon(const Surface &out, Point tilePosition, Point targetBufferPosit DrawMonsterHelper(out, tilePosition, targetBufferPosition); } DrawMissile(out, tilePosition, targetBufferPosition, false); - DrawObject(out, tilePosition, targetBufferPosition, false); - DrawItem(out, tilePosition, targetBufferPosition, false); + + if (object != nullptr && !object->_oPreFlag) { + DrawObject(out, *object, tilePosition, targetBufferPosition); + } + if (bItem > 0 && Items[bItem - 1]._iPostDraw) { + DrawItem(out, static_cast(bItem - 1), targetBufferPosition); + } if (leveltype != DTYPE_TOWN) { char bArch = dSpecial[tilePosition.x][tilePosition.y]; @@ -875,7 +829,7 @@ void DrawFloor(const Surface &out, Point tilePosition, Point targetBufferPositio } } -bool IsWall(Point position) +[[nodiscard]] DVL_ALWAYS_INLINE bool IsWall(Point position) { return TileHasAny(dPiece[position.x][position.y], TileProperties::Solid) || dSpecial[position.x][position.y] != 0; } @@ -892,11 +846,12 @@ void DrawTileContent(const Surface &out, Point tilePosition, Point targetBufferP { // Keep evaluating until MicroTiles can't affect screen rows += MicroTileLen; - dRendered.reset(); for (int i = 0; i < rows; i++) { + bool skip = false; for (int j = 0; j < columns; j++) { if (InDungeonBounds(tilePosition)) { + bool skipNext = false; #ifdef _DEBUG DebugCoordsMap[tilePosition.x + tilePosition.y * MAXDUNX] = targetBufferPosition; #endif @@ -908,10 +863,14 @@ void DrawTileContent(const Surface &out, Point tilePosition, Point targetBufferP if (IsWall(tilePosition) && (IsWall(tilePosition + Displacement { 1, 0 }) || (tilePosition.x > 0 && IsWall(tilePosition + Displacement { -1, 0 })))) { // Part of a wall aligned on the x-axis if (IsTileNotSolid(tilePosition + Displacement { 1, -1 }) && IsTileNotSolid(tilePosition + Displacement { 0, -1 })) { // Has walkable area behind it DrawDungeon(out, tilePosition + Direction::East, { targetBufferPosition.x + TILE_WIDTH, targetBufferPosition.y }); + skipNext = true; } } } - DrawDungeon(out, tilePosition, targetBufferPosition); + if (!skip) { + DrawDungeon(out, tilePosition, targetBufferPosition); + } + skip = skipNext; } tilePosition += Direction::East; targetBufferPosition.x += TILE_WIDTH; @@ -1101,19 +1060,18 @@ void DrawGame(const Surface &fullOut, Point position, Displacement offset) #ifdef DUN_RENDER_STATS std::vector> sortedStats(DunRenderStats.begin(), DunRenderStats.end()); - std::sort(sortedStats.begin(), sortedStats.end(), - [](const std::pair &a, const std::pair &b) { - return a.first.maskType == b.first.maskType - ? static_cast(a.first.tileType) < static_cast(b.first.tileType) - : static_cast(a.first.maskType) < static_cast(b.first.maskType); - }); + c_sort(sortedStats, [](const std::pair &a, const std::pair &b) { + return a.first.maskType == b.first.maskType + ? static_cast(a.first.tileType) < static_cast(b.first.tileType) + : static_cast(a.first.maskType) < static_cast(b.first.maskType); + }); Point pos { 100, 20 }; for (size_t i = 0; i < sortedStats.size(); ++i) { const auto &stat = sortedStats[i]; - DrawString(out, StrCat(i, "."), Rectangle(pos, Size { 20, 16 }), UiFlags::AlignRight); + DrawString(out, StrCat(i, "."), Rectangle(pos, Size { 20, 16 }), { .flags = UiFlags::AlignRight }); DrawString(out, MaskTypeToString(stat.first.maskType), { pos.x + 24, pos.y }); DrawString(out, TileTypeToString(stat.first.tileType), { pos.x + 184, pos.y }); - DrawString(out, FormatInteger(stat.second), Rectangle({ pos.x + 354, pos.y }, Size(40, 16)), UiFlags::AlignRight); + DrawString(out, FormatInteger(stat.second), Rectangle({ pos.x + 354, pos.y }, Size(40, 16)), { .flags = UiFlags::AlignRight }); pos.y += 16; } #endif @@ -1143,11 +1101,10 @@ void DrawView(const Surface &out, Point startPosition) char debugGridTextBuffer[10]; bool megaTiles = IsDebugGridInMegatiles(); - for (auto m : DebugCoordsMap) { - Point dunCoords = { m.first % MAXDUNX, m.first / MAXDUNX }; + for (auto [dunCoordVal, pixelCoords] : DebugCoordsMap) { + Point dunCoords = { dunCoordVal % MAXDUNX, dunCoordVal / MAXDUNX }; if (megaTiles && (dunCoords.x % 2 == 1 || dunCoords.y % 2 == 1)) continue; - Point pixelCoords = m.second; if (megaTiles) pixelCoords += Displacement { 0, TILE_HEIGHT / 2 }; if (*sgOptions.Graphics.zoom) @@ -1156,18 +1113,19 @@ void DrawView(const Surface &out, Point startPosition) Size tileSize = { TILE_WIDTH, TILE_HEIGHT }; if (*sgOptions.Graphics.zoom) tileSize *= 2; - DrawString(out, debugGridTextBuffer, { pixelCoords - Displacement { 0, tileSize.height }, tileSize }, UiFlags::ColorRed | UiFlags::AlignCenter | UiFlags::VerticalCenter); + DrawString(out, debugGridTextBuffer, { pixelCoords - Displacement { 0, tileSize.height }, tileSize }, + { .flags = UiFlags::ColorRed | UiFlags::AlignCenter | UiFlags::VerticalCenter }); } if (DebugGrid) { auto DrawDebugSquare = [&out](Point center, Displacement hor, Displacement ver, uint8_t col) { auto DrawLine = [&out](Point from, Point to, uint8_t col) { int dx = to.x - from.x; int dy = to.y - from.y; - int steps = abs(dx) > abs(dy) ? abs(dx) : abs(dy); + int steps = std::abs(dx) > std::abs(dy) ? std::abs(dx) : std::abs(dy); float ix = dx / (float)steps; float iy = dy / (float)steps; - float sx = from.x; - float sy = from.y; + float sx = static_cast(from.x); + float sy = static_cast(from.y); for (int i = 0; i <= steps; i++, sx += ix, sy += iy) out.SetPixel({ (int)sx, (int)sy }, col); @@ -1230,9 +1188,9 @@ void DrawView(const Surface &out, Point startPosition) DrawSpellList(out); } if (dropGoldFlag) { - DrawGoldSplit(out, dropGoldValue); + DrawGoldSplit(out); } - DrawGoldWithdraw(out, WithdrawGoldValue); + DrawGoldWithdraw(out); if (HelpFlag) { DrawHelp(out); } @@ -1240,7 +1198,7 @@ void DrawView(const Surface &out, Point startPosition) DrawChatLog(out); } if (IsDiabloMsgAvailable()) { - DrawDiabloMsg(out); + DrawDiabloMsg(out.subregionY(0, out.h() - GetMainPanel().size.height)); } if (MyPlayerIsDead) { RedBack(out); @@ -1264,7 +1222,7 @@ void DrawView(const Surface &out, Point startPosition) void DrawFPS(const Surface &out) { static int framesSinceLastUpdate = 0; - static string_view formatted {}; + static std::string_view formatted {}; if (!frameflag || !gbActive) { return; @@ -1283,9 +1241,9 @@ void DrawFPS(const Surface &out) const char *end = fps >= 100 * FpsPow10 ? BufCopy(buf, fps / FpsPow10, " FPS") : BufCopy(buf, fps / FpsPow10, ".", fps % FpsPow10, " FPS"); - formatted = { buf, static_cast(end - buf) }; + formatted = { buf, static_cast(end - buf) }; }; - DrawString(out, formatted, Point { 8, 68 }, UiFlags::ColorRed); + DrawString(out, formatted, Point { 8, 68 }, { .flags = UiFlags::ColorRed }); } /** @@ -1341,7 +1299,8 @@ void DrawMain(const Surface &out, int dwHgt, bool drawDesc, bool drawHp, bool dr // When chat input is displayed, the belt is hidden and the chat moves up. DoBlitScreen(mainPanelPosition.x + 171, mainPanelPosition.y + 6, 298, 116); } else { - DoBlitScreen(mainPanelPosition.x + 176, mainPanelPosition.y + 46, 288, 63); + DoBlitScreen(mainPanelPosition.x + InfoBoxTopLeft.deltaX, mainPanelPosition.y + InfoBoxTopLeft.deltaY, + InfoBoxSize.width, InfoBoxSize.height); } } if (drawMana) { @@ -1521,6 +1480,20 @@ void CalcViewportGeometry() tileColums = (screenWidth - renderStart.x + TILE_WIDTH - 1) / TILE_WIDTH; } +Point GetScreenPosition(Point tile) +{ + Point firstTile = ViewPosition; + Displacement offset = {}; + CalcFirstTilePosition(firstTile, offset); + + Displacement delta = firstTile - tile; + + Point position {}; + position += delta.worldToScreen(); + position += offset; + return position; +} + extern SDL_Surface *PalSurface; void ClearScreenBuffer() @@ -1686,8 +1659,14 @@ void DrawAndBlit() DrawFPS(out); + LuaEvent("GameDrawComplete"); + DrawMain(out, hgt, drawInfoBox, drawHealth, drawMana, drawBelt, drawControlButtons); +#ifdef _DEBUG + DrawConsole(out); +#endif + RedrawComplete(); for (PanelDrawComponent component : enum_values()) { if (IsRedrawComponent(component)) { diff --git a/Source/engine/render/scrollrt.h b/Source/engine/render/scrollrt.h index 73f25b50803..d13c8cf14ff 100644 --- a/Source/engine/render/scrollrt.h +++ b/Source/engine/render/scrollrt.h @@ -60,6 +60,12 @@ void CalcTileOffset(int *offsetX, int *offsetY); void TilesInView(int *columns, int *rows); void CalcViewportGeometry(); +/** + * @brief Calculate the screen position of a given tile + * @param tile Position of a dungeon tile + */ +Point GetScreenPosition(Point tile); + /** * @brief Render the whole screen black */ diff --git a/Source/engine/render/text_render.cpp b/Source/engine/render/text_render.cpp index e81942eb669..578b90e9baf 100644 --- a/Source/engine/render/text_render.cpp +++ b/Source/engine/render/text_render.cpp @@ -8,8 +8,10 @@ #include #include #include +#include #include #include +#include #include @@ -23,10 +25,10 @@ #include "engine/palette.h" #include "engine/point.hpp" #include "engine/render/clx_render.hpp" +#include "utils/algorithm/container.hpp" #include "utils/display.h" #include "utils/language.h" #include "utils/sdl_compat.h" -#include "utils/stdcompat/optional.hpp" #include "utils/utf8.hpp" namespace devilution { @@ -44,13 +46,14 @@ constexpr std::array LineHeights = { 12, 26, 38, 42, 50, 22 }; constexpr int SmallFontTallLineHeight = 16; std::array BaseLineOffset = { -3, -2, -3, -6, -7, 3 }; -std::array ColorTranslations = { +std::array ColorTranslations = { "fonts\\goldui.trn", "fonts\\grayui.trn", "fonts\\golduis.trn", "fonts\\grayuis.trn", - nullptr, + nullptr, // ColorDialogWhite + nullptr, // ColorDialogRed "fonts\\yellow.trn", nullptr, @@ -64,55 +67,46 @@ std::array ColorTranslations = { "fonts\\buttonface.trn", "fonts\\buttonpushed.trn", + "fonts\\gamedialogwhite.trn", + "fonts\\gamedialogyellow.trn", + "fonts\\gamedialogred.trn", }; -std::array>, 15> ColorTranslationsData; - -GameFontTables GetSizeFromFlags(UiFlags flags) -{ - if (HasAnyOf(flags, UiFlags::FontSize24)) - return GameFont24; - else if (HasAnyOf(flags, UiFlags::FontSize30)) - return GameFont30; - else if (HasAnyOf(flags, UiFlags::FontSize42)) - return GameFont42; - else if (HasAnyOf(flags, UiFlags::FontSize46)) - return GameFont46; - else if (HasAnyOf(flags, UiFlags::FontSizeDialog)) - return FontSizeDialog; - - return GameFont12; -} +std::array>, 19> ColorTranslationsData; text_color GetColorFromFlags(UiFlags flags) { if (HasAnyOf(flags, UiFlags::ColorWhite)) return ColorWhite; - else if (HasAnyOf(flags, UiFlags::ColorBlue)) + if (HasAnyOf(flags, UiFlags::ColorBlue)) return ColorBlue; - else if (HasAnyOf(flags, UiFlags::ColorOrange)) + if (HasAnyOf(flags, UiFlags::ColorOrange)) return ColorOrange; - else if (HasAnyOf(flags, UiFlags::ColorRed)) + if (HasAnyOf(flags, UiFlags::ColorRed)) return ColorRed; - else if (HasAnyOf(flags, UiFlags::ColorBlack)) + if (HasAnyOf(flags, UiFlags::ColorBlack)) return ColorBlack; - else if (HasAnyOf(flags, UiFlags::ColorGold)) + if (HasAnyOf(flags, UiFlags::ColorGold)) return ColorGold; - else if (HasAnyOf(flags, UiFlags::ColorUiGold)) + if (HasAnyOf(flags, UiFlags::ColorUiGold)) return ColorUiGold; - else if (HasAnyOf(flags, UiFlags::ColorUiSilver)) + if (HasAnyOf(flags, UiFlags::ColorUiSilver)) return ColorUiSilver; - else if (HasAnyOf(flags, UiFlags::ColorUiGoldDark)) + if (HasAnyOf(flags, UiFlags::ColorUiGoldDark)) return ColorUiGoldDark; - else if (HasAnyOf(flags, UiFlags::ColorUiSilverDark)) + if (HasAnyOf(flags, UiFlags::ColorUiSilverDark)) return ColorUiSilverDark; - else if (HasAnyOf(flags, UiFlags::ColorDialogWhite)) - return ColorDialogWhite; - else if (HasAnyOf(flags, UiFlags::ColorYellow)) + if (HasAnyOf(flags, UiFlags::ColorDialogWhite)) + return gbRunGame ? ColorInGameDialogWhite : ColorDialogWhite; + if (HasAnyOf(flags, UiFlags::ColorDialogYellow)) + return ColorInGameDialogYellow; + if (HasAnyOf(flags, UiFlags::ColorDialogRed)) + return ColorInGameDialogRed; + if (HasAnyOf(flags, UiFlags::ColorYellow)) return ColorYellow; - else if (HasAnyOf(flags, UiFlags::ColorButtonface)) + if (HasAnyOf(flags, UiFlags::ColorButtonface)) return ColorButtonface; - else if (HasAnyOf(flags, UiFlags::ColorButtonpushed)) + if (HasAnyOf(flags, UiFlags::ColorButtonpushed)) return ColorButtonpushed; return ColorWhitegold; @@ -138,11 +132,16 @@ bool IsSmallFontTallRow(uint16_t row) return IsCJK(row) || IsHangul(row); } -void GetFontPath(GameFontTables size, uint16_t row, string_view ext, char *out) +void GetFontPath(GameFontTables size, uint16_t row, std::string_view ext, char *out) { *fmt::format_to(out, R"(fonts\{}-{:02x}{})", FontSizes[size], row, ext) = '\0'; } +void GetFontPath(std::string_view language_code, GameFontTables size, uint16_t row, std::string_view ext, char *out) +{ + *fmt::format_to(out, R"(fonts\{}\{}-{:02x}{})", language_code, FontSizes[size], row, ext) = '\0'; +} + uint32_t GetFontId(GameFontTables size, uint16_t row) { return (size << 16) | row; @@ -161,11 +160,23 @@ OptionalClxSpriteList LoadFont(GameFontTables size, text_color color, uint16_t r return OptionalClxSpriteList(*hotFont->second); } + OptionalOwnedClxSpriteList &font = Fonts[fontId]; char path[32]; - GetFontPath(size, row, ".clx", &path[0]); - OptionalOwnedClxSpriteList &font = Fonts[fontId]; - font = LoadOptionalClx(path); + // Try loading the language-specific variant first: + const std::string_view language_code = GetLanguageCode(); + const std::string_view language_tag = language_code.substr(0, 2); + if (language_tag == "zh" || language_tag == "ja" || language_tag == "ko") { + GetFontPath(language_code, size, row, ".clx", &path[0]); + font = LoadOptionalClx(path); + } + if (!font) { + // Fall back to the base variant: + GetFontPath(size, row, ".clx", &path[0]); + font = LoadOptionalClx(path); + } + +#ifndef UNPACKED_MPQS if (!font) { // Could be an old devilutionx.mpq or fonts.mpq with PCX instead of CLX. // @@ -175,6 +186,7 @@ OptionalClxSpriteList LoadFont(GameFontTables size, text_color color, uint16_t r GetFontPath(size, row, "", &pcxPath[0]); font = LoadPcxSpriteList(pcxPath, /*numFramesOrFrameHeight=*/256, /*transparentColor=*/1); } +#endif if (!font) { LogError("Error loading font: {}", path); @@ -211,9 +223,8 @@ class CurrentFont { uint32_t currentUnicodeRow_ = 0; }; -void DrawFont(const Surface &out, Point position, const ClxSpriteList font, text_color color, int frame, bool outline) +void DrawFont(const Surface &out, Point position, ClxSprite glyph, text_color color, bool outline) { - ClxSprite glyph = font[frame]; if (outline) { ClxDrawOutlineSkipColorZero(out, 0, { position.x, position.y + glyph.height() - 1 }, glyph); } @@ -224,11 +235,6 @@ void DrawFont(const Surface &out, Point position, const ClxSpriteList font, text } } -bool IsWhitespace(char32_t c) -{ - return IsAnyOf(c, U' ', U' ', ZWSP); -} - bool IsFullWidthPunct(char32_t c) { return IsAnyOf(c, U',', U'、', U'。', U'?', U'!'); @@ -239,19 +245,19 @@ bool IsBreakAllowed(char32_t codepoint, char32_t nextCodepoint) return IsFullWidthPunct(codepoint) && !IsFullWidthPunct(nextCodepoint); } -std::size_t CountNewlines(string_view fmt, const DrawStringFormatArg *args, std::size_t argsLen) +std::size_t CountNewlines(std::string_view fmt, const DrawStringFormatArg *args, std::size_t argsLen) { - std::size_t result = std::count(fmt.begin(), fmt.end(), '\n'); + std::size_t result = c_count(fmt, '\n'); for (std::size_t i = 0; i < argsLen; ++i) { - if (args[i].GetType() == DrawStringFormatArg::Type::StringView) - result += std::count(args[i].GetFormatted().begin(), args[i].GetFormatted().end(), '\n'); + if (std::holds_alternative(args[i].value())) + result += c_count(args[i].GetFormatted(), '\n'); } return result; } class FmtArgParser { public: - FmtArgParser(string_view fmt, + FmtArgParser(std::string_view fmt, DrawStringFormatArg *args, size_t len, size_t offset = 0) @@ -262,14 +268,14 @@ class FmtArgParser { { } - std::optional operator()(string_view &rest) + std::optional operator()(std::string_view &rest) { std::optional result; if (rest[0] != '{') return result; std::size_t closingBracePos = rest.find('}', 1); - if (closingBracePos == string_view::npos) { + if (closingBracePos == std::string_view::npos) { LogError("Unclosed format argument: {}", fmt_); return result; } @@ -292,8 +298,8 @@ class FmtArgParser { result = std::nullopt; } else { if (!args_[*result].HasFormatted()) { - const auto fmtStr = positional ? "{}" : fmt::string_view(rest.data(), fmtLen); - args_[*result].SetFormatted(fmt::format(fmt::runtime(fmtStr), args_[*result].GetIntValue())); + const auto fmtStr = positional ? "{}" : std::string_view(rest.data(), fmtLen); + args_[*result].SetFormatted(fmt::format(fmt::runtime(fmtStr), std::get(args_[*result].value()))); } rest.remove_prefix(fmtLen); } @@ -306,13 +312,13 @@ class FmtArgParser { } private: - string_view fmt_; + std::string_view fmt_; DrawStringFormatArg *args_; std::size_t len_; std::size_t next_; }; -bool ContainsSmallFontTallCodepoints(string_view text) +bool ContainsSmallFontTallCodepoints(std::string_view text) { while (!text.empty()) { const char32_t next = ConsumeFirstUtf8CodePoint(&text); @@ -326,14 +332,14 @@ bool ContainsSmallFontTallCodepoints(string_view text) return false; } -int GetLineHeight(string_view fmt, DrawStringFormatArg *args, std::size_t argsLen, GameFontTables fontIndex) +int GetLineHeight(std::string_view fmt, DrawStringFormatArg *args, std::size_t argsLen, GameFontTables fontIndex) { constexpr std::array LineHeights = { 12, 26, 38, 42, 50, 22 }; if (fontIndex == GameFont12 && IsSmallFontTall()) { char32_t prev = U'\0'; char32_t next; FmtArgParser fmtArgParser { fmt, args, argsLen }; - string_view rest = fmt; + std::string_view rest = fmt; while (!rest.empty()) { if ((prev == U'{' || prev == U'}') && static_cast(prev) == rest[0]) { rest.remove_prefix(1); @@ -388,15 +394,31 @@ int GetLineStartX(UiFlags flags, const Rectangle &rect, int lineWidth) return rect.position.x; } -uint32_t DoDrawString(const Surface &out, string_view text, Rectangle rect, Point &characterPosition, - int spacing, int lineHeight, int lineWidth, int rightMargin, int bottomMargin, - UiFlags flags, GameFontTables size, text_color color, bool outline) +uint32_t DoDrawString(const Surface &out, std::string_view text, Rectangle rect, Point &characterPosition, + int lineWidth, int rightMargin, int bottomMargin, GameFontTables size, text_color color, bool outline, + TextRenderOptions &opts) { CurrentFont currentFont; char32_t next; - string_view remaining = text; + 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) { + OptionalClxSpriteList baseFont = LoadFont(size, color, 0); + if (baseFont) + DrawFont(out, position, (*baseFont)['|'], color, outline); + } + if (opts.renderedCursorPositionOut != nullptr) { + *opts.renderedCursorPositionOut = position; + } + } + }; + for (; !remaining.empty() && remaining[0] != '\0' && (next = DecodeFirstUtf8CodePoint(remaining, &cpLen)) != Utf8DecodeError; remaining.remove_prefix(cpLen)) { @@ -413,26 +435,41 @@ uint32_t DoDrawString(const Surface &out, string_view text, Rectangle rect, Poin const uint8_t frame = next & 0xFF; const uint16_t width = (*currentFont.sprite)[frame].width(); if (next == U'\n' || characterPosition.x + width > rightMargin) { - const int nextLineY = characterPosition.y + lineHeight; + if (next == '\n') + maybeDrawCursor(); + const int nextLineY = characterPosition.y + opts.lineHeight; if (nextLineY >= bottomMargin) break; characterPosition.y = nextLineY; - if (HasAnyOf(flags, (UiFlags::AlignCenter | UiFlags::AlignRight))) { + if (HasAnyOf(opts.flags, (UiFlags::AlignCenter | UiFlags::AlignRight))) { lineWidth = width; if (remaining.size() > cpLen) - lineWidth += spacing + GetLineWidth(remaining.substr(cpLen), size, spacing); + lineWidth += opts.spacing + GetLineWidth(remaining.substr(cpLen), size, opts.spacing); } - characterPosition.x = GetLineStartX(flags, rect, lineWidth); + characterPosition.x = GetLineStartX(opts.flags, rect, lineWidth); if (next == U'\n') continue; } - DrawFont(out, characterPosition, *currentFont.sprite, color, frame, outline); - characterPosition.x += width + spacing; + const ClxSprite glyph = (*currentFont.sprite)[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 : opts.spacing), glyph.height(), + opts.highlightColor); + } + + DrawFont(out, characterPosition, glyph, color, outline); + maybeDrawCursor(); + characterPosition.x += width + opts.spacing; } - return remaining.data() - text.data(); + maybeDrawCursor(); + return static_cast(remaining.data() - text.data()); } } // namespace @@ -447,7 +484,7 @@ void UnloadFonts() Fonts.clear(); } -int GetLineWidth(string_view text, GameFontTables size, int spacing, int *charactersInLine) +int GetLineWidth(std::string_view text, GameFontTables size, int spacing, int *charactersInLine) { int lineWidth = 0; CurrentFont currentFont; @@ -480,7 +517,7 @@ int GetLineWidth(string_view text, GameFontTables size, int spacing, int *charac return lineWidth != 0 ? (lineWidth - spacing) : 0; } -int GetLineWidth(string_view fmt, DrawStringFormatArg *args, std::size_t argsLen, size_t argsOffset, GameFontTables size, int spacing, int *charactersInLine) +int GetLineWidth(std::string_view fmt, DrawStringFormatArg *args, std::size_t argsLen, size_t argsOffset, GameFontTables size, int spacing, int *charactersInLine) { int lineWidth = 0; CurrentFont currentFont; @@ -490,7 +527,7 @@ int GetLineWidth(string_view fmt, DrawStringFormatArg *args, std::size_t argsLen char32_t next; FmtArgParser fmtArgParser { fmt, args, argsLen, argsOffset }; - string_view rest = fmt; + std::string_view rest = fmt; while (!rest.empty()) { if ((prev == U'{' || prev == U'}') && static_cast(prev) == rest[0]) { rest.remove_prefix(1); @@ -533,7 +570,7 @@ int GetLineWidth(string_view fmt, DrawStringFormatArg *args, std::size_t argsLen return lineWidth != 0 ? (lineWidth - spacing) : 0; } -int GetLineHeight(string_view text, GameFontTables fontIndex) +int GetLineHeight(std::string_view text, GameFontTables fontIndex) { if (fontIndex == GameFont12 && IsSmallFontTall() && ContainsSmallFontTallCodepoints(text)) { return SmallFontTallLineHeight; @@ -552,7 +589,7 @@ int AdjustSpacingToFitHorizontally(int &lineWidth, int maxSpacing, int character return maxSpacing - spacingRedux; } -std::string WordWrapString(string_view text, unsigned width, GameFontTables size, int spacing) +std::string WordWrapString(std::string_view text, unsigned width, GameFontTables size, int spacing) { std::string output; if (text.empty() || text[0] == '\0') @@ -561,16 +598,15 @@ std::string WordWrapString(string_view text, unsigned width, GameFontTables size output.reserve(text.size()); const char *begin = text.data(); const char *processedEnd = text.data(); - string_view::size_type lastBreakablePos = string_view::npos; - std::size_t lastBreakableLen; - bool lastBreakableKeep = false; + std::string_view::size_type lastBreakablePos = std::string_view::npos; + std::size_t lastBreakableLen = 0; unsigned lineWidth = 0; CurrentFont currentFont; char32_t codepoint = U'\0'; // the current codepoint char32_t nextCodepoint; // the next codepoint std::size_t nextCodepointLen; - string_view remaining = text; + std::string_view remaining = text; nextCodepoint = DecodeFirstUtf8CodePoint(remaining, &nextCodepointLen); do { codepoint = nextCodepoint; @@ -581,7 +617,7 @@ std::string WordWrapString(string_view text, unsigned width, GameFontTables size nextCodepoint = !remaining.empty() ? DecodeFirstUtf8CodePoint(remaining, &nextCodepointLen) : U'\0'; if (codepoint == U'\n') { // Existing line break, scan next line - lastBreakablePos = string_view::npos; + lastBreakablePos = std::string_view::npos; lineWidth = 0; output.append(processedEnd, remaining.data()); processedEnd = remaining.data(); @@ -600,34 +636,35 @@ std::string WordWrapString(string_view text, unsigned width, GameFontTables size lineWidth += (*currentFont.sprite)[frame].width() + spacing; } - const bool isWhitespace = IsWhitespace(codepoint); - if (isWhitespace || IsBreakAllowed(codepoint, nextCodepoint)) { + if (IsBreakableWhitespace(codepoint)) { lastBreakablePos = remaining.data() - begin - codepointLen; lastBreakableLen = codepointLen; - lastBreakableKeep = !isWhitespace; continue; } if (lineWidth - spacing <= width) { + if (IsBreakAllowed(codepoint, nextCodepoint)) { + lastBreakablePos = remaining.data() - begin; + lastBreakableLen = 0; + } + continue; // String is still within the limit, continue to the next symbol } - if (lastBreakablePos == string_view::npos) { // Single word longer than width - continue; + if (lastBreakablePos == std::string_view::npos) { // Single word longer than width + lastBreakablePos = remaining.data() - begin - codepointLen; + lastBreakableLen = 0; } // Break line and continue to next line const char *end = &text[lastBreakablePos]; - if (lastBreakableKeep) { - end += lastBreakableLen; - } output.append(processedEnd, end); output += '\n'; // Restart from the beginning of the new line. remaining = text.substr(lastBreakablePos + lastBreakableLen); processedEnd = remaining.data(); - lastBreakablePos = string_view::npos; + lastBreakablePos = std::string_view::npos; lineWidth = 0; nextCodepoint = !remaining.empty() ? DecodeFirstUtf8CodePoint(remaining, &nextCodepointLen) : U'\0'; } while (!remaining.empty() && remaining[0] != '\0'); @@ -638,87 +675,88 @@ std::string WordWrapString(string_view text, unsigned width, GameFontTables size /** * @todo replace Rectangle with cropped Surface */ -uint32_t DrawString(const Surface &out, string_view text, const Rectangle &rect, UiFlags flags, int spacing, int lineHeight) +uint32_t DrawString(const Surface &out, std::string_view text, const Rectangle &rect, TextRenderOptions opts) { - GameFontTables size = GetSizeFromFlags(flags); - text_color color = GetColorFromFlags(flags); + const GameFontTables size = GetFontSizeFromUiFlags(opts.flags); + const text_color color = GetColorFromFlags(opts.flags); int charactersInLine = 0; int lineWidth = 0; - if (HasAnyOf(flags, (UiFlags::AlignCenter | UiFlags::AlignRight | UiFlags::KerningFitSpacing))) - lineWidth = GetLineWidth(text, size, spacing, &charactersInLine); + if (HasAnyOf(opts.flags, (UiFlags::AlignCenter | UiFlags::AlignRight | UiFlags::KerningFitSpacing))) + lineWidth = GetLineWidth(text, size, opts.spacing, &charactersInLine); - int maxSpacing = spacing; - if (HasAnyOf(flags, UiFlags::KerningFitSpacing)) - spacing = AdjustSpacingToFitHorizontally(lineWidth, maxSpacing, charactersInLine, rect.size.width); + const int maxSpacing = opts.spacing; + if (HasAnyOf(opts.flags, UiFlags::KerningFitSpacing)) + opts.spacing = AdjustSpacingToFitHorizontally(lineWidth, maxSpacing, charactersInLine, rect.size.width); - Point characterPosition { GetLineStartX(flags, rect, lineWidth), rect.position.y }; + Point characterPosition { GetLineStartX(opts.flags, rect, lineWidth), rect.position.y }; const int initialX = characterPosition.x; const int rightMargin = rect.position.x + rect.size.width; const int bottomMargin = rect.size.height != 0 ? std::min(rect.position.y + rect.size.height + BaseLineOffset[size], out.h()) : out.h(); - if (lineHeight == -1) - lineHeight = GetLineHeight(text, size); + if (opts.lineHeight == -1) + opts.lineHeight = GetLineHeight(text, size); - if (HasAnyOf(flags, UiFlags::VerticalCenter)) { - int textHeight = (std::count(text.cbegin(), text.cend(), '\n') + 1) * lineHeight; + if (HasAnyOf(opts.flags, UiFlags::VerticalCenter)) { + const int textHeight = static_cast((c_count(text, '\n') + 1) * opts.lineHeight); characterPosition.y += std::max(0, (rect.size.height - textHeight) / 2); } characterPosition.y += BaseLineOffset[size]; - const bool outlined = HasAnyOf(flags, UiFlags::Outlined); + const bool outlined = HasAnyOf(opts.flags, UiFlags::Outlined); const Surface clippedOut = ClipSurface(out, rect); - const uint32_t bytesDrawn = DoDrawString(clippedOut, text, rect, characterPosition, spacing, lineHeight, lineWidth, rightMargin, bottomMargin, flags, size, color, outlined); + // Only draw the PentaCursor if the cursor is not at the end. + if (HasAnyOf(opts.flags, UiFlags::PentaCursor) && static_cast(opts.cursorPosition) == text.size()) { + opts.cursorPosition = -1; + } + + const uint32_t bytesDrawn = DoDrawString(clippedOut, text, rect, characterPosition, + lineWidth, rightMargin, bottomMargin, size, color, outlined, opts); - if (HasAnyOf(flags, UiFlags::PentaCursor)) { + if (HasAnyOf(opts.flags, UiFlags::PentaCursor)) { const ClxSprite sprite = (*pSPentSpn2Cels)[PentSpn2Spin()]; - MaybeWrap(characterPosition, sprite.width(), rightMargin, initialX, lineHeight); - ClxDraw(clippedOut, characterPosition + Displacement { 0, lineHeight - BaseLineOffset[size] }, sprite); - } else if (HasAnyOf(flags, UiFlags::TextCursor) && GetAnimationFrame(2, 500) != 0) { - MaybeWrap(characterPosition, 2, rightMargin, initialX, lineHeight); - OptionalClxSpriteList baseFont = LoadFont(size, color, 0); - if (baseFont) - DrawFont(clippedOut, characterPosition, *baseFont, color, '|', outlined); + MaybeWrap(characterPosition, sprite.width(), rightMargin, initialX, opts.lineHeight); + ClxDraw(clippedOut, characterPosition + Displacement { 0, opts.lineHeight - BaseLineOffset[size] }, sprite); } return bytesDrawn; } -void DrawStringWithColors(const Surface &out, string_view fmt, DrawStringFormatArg *args, std::size_t argsLen, const Rectangle &rect, UiFlags flags, int spacing, int lineHeight) +void DrawStringWithColors(const Surface &out, std::string_view fmt, DrawStringFormatArg *args, std::size_t argsLen, const Rectangle &rect, TextRenderOptions opts) { - GameFontTables size = GetSizeFromFlags(flags); - text_color color = GetColorFromFlags(flags); + const GameFontTables size = GetFontSizeFromUiFlags(opts.flags); + const text_color color = GetColorFromFlags(opts.flags); int charactersInLine = 0; int lineWidth = 0; - if (HasAnyOf(flags, (UiFlags::AlignCenter | UiFlags::AlignRight | UiFlags::KerningFitSpacing))) - lineWidth = GetLineWidth(fmt, args, argsLen, 0, size, spacing, &charactersInLine); + if (HasAnyOf(opts.flags, (UiFlags::AlignCenter | UiFlags::AlignRight | UiFlags::KerningFitSpacing))) + lineWidth = GetLineWidth(fmt, args, argsLen, 0, size, opts.spacing, &charactersInLine); - int maxSpacing = spacing; - if (HasAnyOf(flags, UiFlags::KerningFitSpacing)) - spacing = AdjustSpacingToFitHorizontally(lineWidth, maxSpacing, charactersInLine, rect.size.width); + const int maxSpacing = opts.spacing; + if (HasAnyOf(opts.flags, UiFlags::KerningFitSpacing)) + opts.spacing = AdjustSpacingToFitHorizontally(lineWidth, maxSpacing, charactersInLine, rect.size.width); - Point characterPosition { GetLineStartX(flags, rect, lineWidth), rect.position.y }; + Point characterPosition { GetLineStartX(opts.flags, rect, lineWidth), rect.position.y }; const int initialX = characterPosition.x; const int rightMargin = rect.position.x + rect.size.width; const int bottomMargin = rect.size.height != 0 ? std::min(rect.position.y + rect.size.height + BaseLineOffset[size], out.h()) : out.h(); - if (lineHeight == -1) - lineHeight = GetLineHeight(fmt, args, argsLen, size); + if (opts.lineHeight == -1) + opts.lineHeight = GetLineHeight(fmt, args, argsLen, size); - if (HasAnyOf(flags, UiFlags::VerticalCenter)) { - int textHeight = (CountNewlines(fmt, args, argsLen) + 1) * lineHeight; + if (HasAnyOf(opts.flags, UiFlags::VerticalCenter)) { + const int textHeight = static_cast((CountNewlines(fmt, args, argsLen) + 1) * opts.lineHeight); characterPosition.y += std::max(0, (rect.size.height - textHeight) / 2); } characterPosition.y += BaseLineOffset[size]; - const bool outlined = HasAnyOf(flags, UiFlags::Outlined); + const bool outlined = HasAnyOf(opts.flags, UiFlags::Outlined); const Surface clippedOut = ClipSurface(out, rect); @@ -726,7 +764,7 @@ void DrawStringWithColors(const Surface &out, string_view fmt, DrawStringFormatA char32_t prev = U'\0'; char32_t next; - string_view remaining = fmt; + std::string_view remaining = fmt; FmtArgParser fmtArgParser { fmt, args, argsLen }; size_t cpLen; for (; !remaining.empty() && remaining[0] != '\0' @@ -738,8 +776,8 @@ void DrawStringWithColors(const Surface &out, string_view fmt, DrawStringFormatA const std::optional fmtArgPos = fmtArgParser(remaining); if (fmtArgPos) { - DoDrawString(clippedOut, args[*fmtArgPos].GetFormatted(), rect, characterPosition, spacing, lineHeight, lineWidth, rightMargin, bottomMargin, flags, size, - GetColorFromFlags(args[*fmtArgPos].GetFlags()), outlined); + DoDrawString(clippedOut, args[*fmtArgPos].GetFormatted(), rect, characterPosition, lineWidth, rightMargin, bottomMargin, size, + GetColorFromFlags(args[*fmtArgPos].GetFlags()), outlined, opts); // `fmtArgParser` has already consumed `remaining`. Ensure the loop doesn't consume any more. cpLen = 0; // The loop assigns `prev = next`. We want `prev` to be `\0` after this. @@ -758,35 +796,30 @@ void DrawStringWithColors(const Surface &out, string_view fmt, DrawStringFormatA const uint8_t frame = next & 0xFF; const uint16_t width = (*currentFont.sprite)[frame].width(); if (next == U'\n' || characterPosition.x + width > rightMargin) { - const int nextLineY = characterPosition.y + lineHeight; + const int nextLineY = characterPosition.y + opts.lineHeight; if (nextLineY >= bottomMargin) break; characterPosition.y = nextLineY; - if (HasAnyOf(flags, (UiFlags::AlignCenter | UiFlags::AlignRight))) { + if (HasAnyOf(opts.flags, (UiFlags::AlignCenter | UiFlags::AlignRight))) { lineWidth = width; if (remaining.size() > cpLen) - lineWidth += spacing + GetLineWidth(remaining.substr(cpLen), args, argsLen, fmtArgParser.offset(), size, spacing); + lineWidth += opts.spacing + GetLineWidth(remaining.substr(cpLen), args, argsLen, fmtArgParser.offset(), size, opts.spacing); } - characterPosition.x = GetLineStartX(flags, rect, lineWidth); + characterPosition.x = GetLineStartX(opts.flags, rect, lineWidth); if (next == U'\n') continue; } - DrawFont(clippedOut, characterPosition, *currentFont.sprite, color, frame, outlined); - characterPosition.x += width + spacing; + DrawFont(clippedOut, characterPosition, (*currentFont.sprite)[frame], color, outlined); + characterPosition.x += width + opts.spacing; } - if (HasAnyOf(flags, UiFlags::PentaCursor)) { + if (HasAnyOf(opts.flags, UiFlags::PentaCursor)) { const ClxSprite sprite = (*pSPentSpn2Cels)[PentSpn2Spin()]; - MaybeWrap(characterPosition, sprite.width(), rightMargin, initialX, lineHeight); - ClxDraw(clippedOut, characterPosition + Displacement { 0, lineHeight - BaseLineOffset[size] }, sprite); - } else if (HasAnyOf(flags, UiFlags::TextCursor) && GetAnimationFrame(2, 500) != 0) { - MaybeWrap(characterPosition, 2, rightMargin, initialX, lineHeight); - OptionalClxSpriteList baseFont = LoadFont(size, color, 0); - if (baseFont) - DrawFont(clippedOut, characterPosition, *baseFont, color, '|', outlined); + MaybeWrap(characterPosition, sprite.width(), rightMargin, initialX, opts.lineHeight); + ClxDraw(clippedOut, characterPosition + Displacement { 0, opts.lineHeight - BaseLineOffset[size] }, sprite); } } @@ -795,4 +828,9 @@ uint8_t PentSpn2Spin() return (SDL_GetTicks() / 50) % 8; } +bool IsBreakableWhitespace(char32_t c) +{ + return IsAnyOf(c, U' ', U' ', ZWSP); +} + } // namespace devilution diff --git a/Source/engine/render/text_render.hpp b/Source/engine/render/text_render.hpp index 1a2abc9ea61..0a04698b6f5 100644 --- a/Source/engine/render/text_render.hpp +++ b/Source/engine/render/text_render.hpp @@ -6,8 +6,11 @@ #pragma once #include +#include #include +#include #include +#include #include #include @@ -15,9 +18,9 @@ #include "DiabloUI/ui_flags.hpp" #include "engine.h" #include "engine/clx_sprite.hpp" +#include "engine/palette.h" #include "engine/rectangle.hpp" -#include "utils/stdcompat/optional.hpp" -#include "utils/stdcompat/string_view.hpp" +#include "utils/enum_traits.h" namespace devilution { @@ -36,7 +39,8 @@ enum text_color : uint8_t { ColorUiGoldDark, ColorUiSilverDark, - ColorDialogWhite, + ColorDialogWhite, // Dialog white in main menu + ColorDialogRed, ColorYellow, ColorGold, @@ -50,41 +54,50 @@ enum text_color : uint8_t { ColorButtonface, ColorButtonpushed, + + ColorInGameDialogWhite, // Dialog white in-game + ColorInGameDialogYellow, // Dialog yellow in-game + ColorInGameDialogRed, // Dialog red in-game }; +constexpr GameFontTables GetFontSizeFromUiFlags(UiFlags flags) +{ + if (HasAnyOf(flags, UiFlags::FontSize24)) + return GameFont24; + if (HasAnyOf(flags, UiFlags::FontSize30)) + return GameFont30; + if (HasAnyOf(flags, UiFlags::FontSize42)) + return GameFont42; + if (HasAnyOf(flags, UiFlags::FontSize46)) + return GameFont46; + if (HasAnyOf(flags, UiFlags::FontSizeDialog)) + return FontSizeDialog; + return GameFont12; +} + /** * @brief A format argument for `DrawStringWithColors`. */ class DrawStringFormatArg { public: - enum class Type : uint8_t { - StringView, - Int - }; - - DrawStringFormatArg(string_view value, UiFlags flags) - : type_(Type::StringView) - , string_view_value_(value) + using Value = std::variant; + + DrawStringFormatArg(std::string_view value, UiFlags flags) + : value_(value) , flags_(flags) { } DrawStringFormatArg(int value, UiFlags flags) - : type_(Type::Int) - , int_value_(value) + : value_(value) , flags_(flags) { } - Type GetType() const + std::string_view GetFormatted() const { - return type_; - } - - string_view GetFormatted() const - { - if (type_ == Type::StringView) - return string_view_value_; + if (std::holds_alternative(value_)) + return std::get(value_); return formatted_; } @@ -95,12 +108,12 @@ class DrawStringFormatArg { bool HasFormatted() const { - return type_ == Type::StringView || !formatted_.empty(); + return std::holds_alternative(value_) || !formatted_.empty(); } - int GetIntValue() const + const Value &value() const { - return int_value_; + return value_; } UiFlags GetFlags() const @@ -109,16 +122,41 @@ class DrawStringFormatArg { } private: - Type type_; - union { - string_view string_view_value_; - int int_value_; - }; - + Value value_; UiFlags flags_; std::string formatted_; }; +/** @brief Text rendering options. */ +struct TextRenderOptions { + /** @brief A combination of UiFlags to describe font size, color, alignment, etc. See ui_items.h for available options */ + UiFlags flags = UiFlags::None; + + /** + * @brief Additional space to add between characters. + * + * This value may be adjusted if the flag UiFlags::KerningFitSpacing is set. + */ + int spacing = 1; + + /** @brief Allows overriding the default line height, useful for multi-line strings. */ + int lineHeight = -1; + + /** @brief If non-negative, draws a blinking cursor after the given byte index.*/ + int cursorPosition = -1; + + /** @brief Highlight text background in this range. */ + struct { + int begin; + int end; + } highlightRange = { 0, 0 }; + + uint8_t highlightColor = PAL8_RED + 6; + + /** @brief If a cursor is rendered, the surface coordinates are saved here. */ + std::optional *renderedCursorPositionOut = nullptr; +}; + /** * @brief Small text selection cursor. * @@ -136,7 +174,7 @@ void LoadSmallSelectionSpinner(); * @param charactersInLine Receives characters read until newline or terminator * @return Line width in pixels */ -int GetLineWidth(string_view text, GameFontTables size = GameFont12, int spacing = 1, int *charactersInLine = nullptr); +int GetLineWidth(std::string_view text, GameFontTables size = GameFont12, int spacing = 1, int *charactersInLine = nullptr); /** * @brief Calculate pixel width of first line of text, respecting kerning @@ -149,9 +187,9 @@ int GetLineWidth(string_view text, GameFontTables size = GameFont12, int spacing * @param charactersInLine Receives characters read until newline or terminator * @return Line width in pixels */ -int GetLineWidth(string_view fmt, DrawStringFormatArg *args, size_t argsLen, size_t argsOffset, GameFontTables size, int spacing, int *charactersInLine = nullptr); +int GetLineWidth(std::string_view fmt, DrawStringFormatArg *args, size_t argsLen, size_t argsOffset, GameFontTables size, int spacing, int *charactersInLine = nullptr); -int GetLineHeight(string_view text, GameFontTables fontIndex); +int GetLineHeight(std::string_view text, GameFontTables fontIndex); /** * @brief Builds a multi-line version of the given text so it'll fit within the given width. @@ -165,7 +203,7 @@ int GetLineHeight(string_view text, GameFontTables fontIndex); * @param spacing Any adjustment to apply between each character * @return A copy of the source text with newlines inserted where appropriate */ -[[nodiscard]] std::string WordWrapString(string_view text, unsigned width, GameFontTables size = GameFont12, int spacing = 1); +[[nodiscard]] std::string WordWrapString(std::string_view text, unsigned width, GameFontTables size = GameFont12, int spacing = 1); /** * @brief Draws a line of text within a clipping rectangle (positioned relative to the origin of the output buffer). @@ -180,13 +218,10 @@ int GetLineHeight(string_view text, GameFontTables fontIndex); * @param out The screen buffer to draw on. * @param text String to be drawn. * @param rect Clipping region relative to the output buffer describing where to draw the text and when to wrap long lines. - * @param flags A combination of UiFlags to describe font size, color, alignment, etc. See ui_items.h for available options - * @param spacing Additional space to add between characters. - * This value may be adjusted if the flag UIS_FIT_SPACING is passed in the flags parameter. - * @param lineHeight Allows overriding the default line height, useful for multi-line strings. + * @param opts Rendering options. * @return The number of bytes rendered, including characters "drawn" outside the buffer. */ -uint32_t DrawString(const Surface &out, string_view text, const Rectangle &rect, UiFlags flags = UiFlags::None, int spacing = 1, int lineHeight = -1); +uint32_t DrawString(const Surface &out, std::string_view text, const Rectangle &rect, TextRenderOptions opts = {}); /** * @brief Draws a line of text at the given position relative to the origin of the output buffer. @@ -198,39 +233,36 @@ uint32_t DrawString(const Surface &out, string_view text, const Rectangle &rect, * @param out The screen buffer to draw on. * @param text String to be drawn. * @param position Location of the top left corner of the string relative to the top left corner of the output buffer. - * @param flags A combination of UiFlags to describe font size, color, alignment, etc. See ui_items.h for available options - * @param spacing Additional space to add between characters. - * This value may be adjusted if the flag UIS_FIT_SPACING is passed in the flags parameter. - * @param lineHeight Allows overriding the default line height, useful for multi-line strings. + * @param opts Rendering options. */ -inline void DrawString(const Surface &out, string_view text, const Point &position, UiFlags flags = UiFlags::None, int spacing = 1, int lineHeight = -1) +inline void DrawString(const Surface &out, std::string_view text, const Point &position, TextRenderOptions opts = {}) { - DrawString(out, text, { position, { out.w() - position.x, 0 } }, flags, spacing, lineHeight); + DrawString(out, text, { position, { out.w() - position.x, 0 } }, opts); } /** * @brief Draws a line of text with different colors for certain parts of the text. * - * DrawStringWithColors(out, "Press {} to start", {{"Ⓧ", UiFlags::ColorBlue}}, UiFlags::ColorWhite) + * DrawStringWithColors(out, "Press {} to start", {{"Ⓧ", UiFlags::ColorBlue}}, {.flags = UiFlags::ColorWhite}) * * @param out Output buffer to draw the text on. * @param fmt An fmt::format string. * @param args Format arguments. * @param argsLen Number of format arguments. * @param rect Clipping region relative to the output buffer describing where to draw the text and when to wrap long lines. - * @param flags A combination of UiFlags to describe font size, color, alignment, etc. See ui_items.h for available options - * @param spacing Additional space to add between characters. - * This value may be adjusted if the flag UIS_FIT_SPACING is passed in the flags parameter. - * @param lineHeight Allows overriding the default line height, useful for multi-line strings. + * @param opts Rendering options. */ -void DrawStringWithColors(const Surface &out, string_view fmt, DrawStringFormatArg *args, std::size_t argsLen, const Rectangle &rect, UiFlags flags = UiFlags::None, int spacing = 1, int lineHeight = -1); +void DrawStringWithColors(const Surface &out, std::string_view fmt, DrawStringFormatArg *args, std::size_t argsLen, const Rectangle &rect, TextRenderOptions opts = {}); -inline void DrawStringWithColors(const Surface &out, string_view fmt, std::vector args, const Rectangle &rect, UiFlags flags = UiFlags::None, int spacing = 1, int lineHeight = -1) +inline void DrawStringWithColors(const Surface &out, std::string_view fmt, std::vector args, const Rectangle &rect, TextRenderOptions opts = {}) { - return DrawStringWithColors(out, fmt, args.data(), args.size(), rect, flags, spacing, lineHeight); + return DrawStringWithColors(out, fmt, args.data(), args.size(), rect, opts); } uint8_t PentSpn2Spin(); void UnloadFonts(); +/** @brief Whether this character can be substituted by a newline when word-wrapping. */ +bool IsBreakableWhitespace(char32_t c); + } // namespace devilution diff --git a/Source/engine/size.hpp b/Source/engine/size.hpp index 1750a8cc0a3..06655978f65 100644 --- a/Source/engine/size.hpp +++ b/Source/engine/size.hpp @@ -1,5 +1,7 @@ #pragma once +#include "utils/attributes.h" + #ifdef BUILD_TESTING #include #endif @@ -13,80 +15,80 @@ struct SizeOf { SizeOf() = default; - constexpr SizeOf(SizeT width, SizeT height) + DVL_ALWAYS_INLINE constexpr SizeOf(SizeT width, SizeT height) : width(width) , height(height) { } - explicit constexpr SizeOf(SizeT size) + DVL_ALWAYS_INLINE explicit constexpr SizeOf(SizeT size) : width(size) , height(size) { } - bool operator==(const SizeOf &other) const + DVL_ALWAYS_INLINE bool operator==(const SizeOf &other) const { return width == other.width && height == other.height; } - bool operator!=(const SizeOf &other) const + DVL_ALWAYS_INLINE bool operator!=(const SizeOf &other) const { return !(*this == other); } - constexpr SizeOf &operator+=(SizeT factor) + DVL_ALWAYS_INLINE constexpr SizeOf &operator+=(SizeT factor) { width += factor; height += factor; return *this; } - constexpr SizeOf &operator-=(SizeT factor) + DVL_ALWAYS_INLINE constexpr SizeOf &operator-=(SizeT factor) { return *this += -factor; } - constexpr SizeOf &operator*=(SizeT factor) + DVL_ALWAYS_INLINE constexpr SizeOf &operator*=(SizeT factor) { width *= factor; height *= factor; return *this; } - constexpr SizeOf &operator*=(float factor) + DVL_ALWAYS_INLINE constexpr SizeOf &operator*=(float factor) { width = static_cast(width * factor); height = static_cast(height * factor); return *this; } - constexpr SizeOf &operator/=(SizeT factor) + DVL_ALWAYS_INLINE constexpr SizeOf &operator/=(SizeT factor) { width /= factor; height /= factor; return *this; } - constexpr friend SizeOf operator+(SizeOf a, SizeT factor) + DVL_ALWAYS_INLINE constexpr friend SizeOf operator+(SizeOf a, SizeT factor) { a += factor; return a; } - constexpr friend SizeOf operator-(SizeOf a, SizeT factor) + DVL_ALWAYS_INLINE constexpr friend SizeOf operator-(SizeOf a, SizeT factor) { a -= factor; return a; } - constexpr friend SizeOf operator*(SizeOf a, SizeT factor) + DVL_ALWAYS_INLINE constexpr friend SizeOf operator*(SizeOf a, SizeT factor) { a *= factor; return a; } - constexpr friend SizeOf operator/(SizeOf a, SizeT factor) + DVL_ALWAYS_INLINE constexpr friend SizeOf operator/(SizeOf a, SizeT factor) { a /= factor; return a; diff --git a/Source/engine/sound.cpp b/Source/engine/sound.cpp index 81dbc303ea4..b9125881849 100644 --- a/Source/engine/sound.cpp +++ b/Source/engine/sound.cpp @@ -5,10 +5,12 @@ */ #include "engine/sound.h" +#include #include #include #include #include +#include #include @@ -18,8 +20,6 @@ #include "utils/log.hpp" #include "utils/math.h" #include "utils/sdl_mutex.h" -#include "utils/stdcompat/algorithm.hpp" -#include "utils/stdcompat/optional.hpp" #include "utils/stdcompat/shared_ptr_array.hpp" #include "utils/str_cat.hpp" #include "utils/stubs.h" @@ -152,7 +152,7 @@ const char *const MusicTracks[NUM_MUSIC] = { int CapVolume(int volume) { - return clamp(volume, VOLUME_MIN, VOLUME_MAX); + return std::clamp(volume, VOLUME_MIN, VOLUME_MAX); } } // namespace diff --git a/Source/engine/sound_defs.hpp b/Source/engine/sound_defs.hpp index d8e1b92617a..8a2a425de8e 100644 --- a/Source/engine/sound_defs.hpp +++ b/Source/engine/sound_defs.hpp @@ -2,8 +2,6 @@ #include -#include "utils/stdcompat/string_view.hpp" - #define VOLUME_MIN -1600 #define VOLUME_MAX 0 #define VOLUME_STEPS 64 diff --git a/Source/engine/sound_position.cpp b/Source/engine/sound_position.cpp index 088890a13b0..9e0afdacd24 100644 --- a/Source/engine/sound_position.cpp +++ b/Source/engine/sound_position.cpp @@ -11,7 +11,7 @@ bool CalculateSoundPosition(Point soundPosition, int *plVolume, int *plPan) const Displacement delta = soundPosition - playerPosition; const int pan = (delta.deltaX - delta.deltaY) * 256; - *plPan = clamp(pan, PAN_MIN, PAN_MAX); + *plPan = std::clamp(pan, PAN_MIN, PAN_MAX); const int volume = playerPosition.ApproxDistance(soundPosition) * -64; diff --git a/Source/engine/sound_stubs.cpp b/Source/engine/sound_stubs.cpp index c4fa7302dc7..f766e9376d3 100644 --- a/Source/engine/sound_stubs.cpp +++ b/Source/engine/sound_stubs.cpp @@ -8,15 +8,10 @@ bool gbMusicOn; bool gbSoundOn; _music_id sgnMusicTrack = NUM_MUSIC; -// Disable clang-format here because our config says: -// AllowShortFunctionsOnASingleLine: None -// clang-format off void ClearDuplicateSounds() { } void snd_play_snd(TSnd *pSnd, int lVolume, int lPan) { } std::unique_ptr sound_file_load(const char *path, bool stream) { return nullptr; } -TSnd::~TSnd() -{ -} +TSnd::~TSnd() { } void snd_init() { } void snd_deinit() { } void music_stop() { } @@ -27,6 +22,5 @@ int sound_get_or_set_sound_volume(int volume) { return 0; } void music_mute() { } void music_unmute() { } _music_id GetLevelMusic(dungeon_type dungeonType) { return TMUSIC_TOWN; } -// clang-format on } // namespace devilution diff --git a/Source/engine/surface.hpp b/Source/engine/surface.hpp index 8053513ca2d..7599b948bf2 100644 --- a/Source/engine/surface.hpp +++ b/Source/engine/surface.hpp @@ -108,6 +108,17 @@ struct Surface { return Surface(surface, MakeSdlRect(region.x + x, region.y + y, w, h)); } + /** + * @brief Returns a buffer that starts at `x` of width `w`. + */ + Surface subregionX(int x, int w) const + { + SDL_Rect subregion = region; + subregion.x += static_cast(x); + subregion.w = static_cast(w); + return Surface(surface, subregion); + } + /** * @brief Returns a buffer that starts at `y` of height `h`. */ diff --git a/Source/engine/trn.hpp b/Source/engine/trn.hpp index 394bb5bc1e6..3f682a7427e 100644 --- a/Source/engine/trn.hpp +++ b/Source/engine/trn.hpp @@ -6,9 +6,9 @@ #pragma once #include +#include #include "player.h" -#include "utils/stdcompat/optional.hpp" namespace devilution { diff --git a/Source/gamemenu.cpp b/Source/gamemenu.cpp index 89fe16bb2f5..691a06f86b5 100644 --- a/Source/gamemenu.cpp +++ b/Source/gamemenu.cpp @@ -6,11 +6,11 @@ #include "gamemenu.h" #include "cursor.h" +#include "diablo_msg.hpp" #include "engine/backbuffer_state.hpp" #include "engine/events.hpp" #include "engine/sound.h" #include "engine/sound_defs.hpp" -#include "error.h" #include "gmenu.h" #include "init.h" #include "loadsave.h" @@ -20,6 +20,9 @@ #include "utils/language.h" namespace devilution { + +bool isGameMenuOpen = false; + namespace { // Forward-declare menu handlers, used by the global menu structs below. @@ -241,7 +244,7 @@ void GamemenuSoundVolume(bool bActivate) gbSoundOn = true; } } - PlaySFX(IS_TITLEMOV); + PlaySFX(SfxID::MenuMove); GamemenuGetSound(); } @@ -330,8 +333,10 @@ void gamemenu_save_game(bool /*bActivate*/) InitDiabloMsg(EMSG_SAVING); RedrawEverything(); DrawAndBlit(); + uint32_t currentTime = SDL_GetTicks(); SaveGame(); ClrDiabloMsg(); + InitDiabloMsg(EMSG_GAME_SAVED, currentTime + 1000 - SDL_GetTicks()); RedrawEverything(); NewCursor(CURSOR_HAND); if (CornerStone.activated) { @@ -344,6 +349,7 @@ void gamemenu_save_game(bool /*bActivate*/) void gamemenu_on() { + isGameMenuOpen = true; if (!gbIsMultiplayer) { gmenu_set_items(sgSingleMenu, GamemenuUpdateSingle); } else { @@ -354,6 +360,7 @@ void gamemenu_on() void gamemenu_off() { + isGameMenuOpen = false; gmenu_set_items(nullptr, nullptr); } diff --git a/Source/gamemenu.h b/Source/gamemenu.h index 3aa63b89ea8..070ebafea98 100644 --- a/Source/gamemenu.h +++ b/Source/gamemenu.h @@ -14,4 +14,6 @@ void gamemenu_quit_game(bool bActivate); void gamemenu_load_game(bool bActivate); void gamemenu_save_game(bool bActivate); +extern bool isGameMenuOpen; + } // namespace devilution diff --git a/Source/gmenu.cpp b/Source/gmenu.cpp index d410435e2ff..4772421866d 100644 --- a/Source/gmenu.cpp +++ b/Source/gmenu.cpp @@ -5,7 +5,9 @@ */ #include "gmenu.h" +#include #include +#include #include "DiabloUI/ui_flags.hpp" #include "control.h" @@ -19,8 +21,6 @@ #include "options.h" #include "stores.h" #include "utils/language.h" -#include "utils/stdcompat/algorithm.hpp" -#include "utils/stdcompat/optional.hpp" #include "utils/ui_fwd.h" namespace devilution { @@ -79,7 +79,7 @@ void GmenuUpDown(bool isDown) } if (sgpCurrItem->enabled()) { if (i != 0) - PlaySFX(IS_TITLEMOV); + PlaySFX(SfxID::MenuMove); return; } } @@ -129,7 +129,8 @@ void GmenuDrawMenuItem(const Surface &out, TMenuItem *pItem, int y) int x = (gnScreenWidth - w) / 2; UiFlags style = pItem->enabled() ? UiFlags::ColorGold : UiFlags::ColorBlack; - DrawString(out, _(pItem->pszStr), Point { x, y }, style | UiFlags::FontSize46, 2); + DrawString(out, _(pItem->pszStr), Point { x, y }, + { .flags = style | UiFlags::FontSize46, .spacing = 2 }); if (pItem == sgpCurrItem) { const ClxSprite sprite = (*PentSpin_cel)[PentSpn2Spin()]; ClxDraw(out, { x - 54, y + 51 }, sprite); @@ -161,7 +162,7 @@ bool GmenuMouseIsOverSlider() int GmenuGetSliderFill() { - return clamp(MousePosition.x - SliderValueLeft - GetUIRectangle().position.x, SliderFillMin, SliderFillMax); + return std::clamp(MousePosition.x - SliderValueLeft - GetUIRectangle().position.x, SliderFillMin, SliderFillMax); } } // namespace @@ -174,7 +175,8 @@ void gmenu_draw_pause(const Surface &out) RedBack(out); if (sgpCurrentMenu == nullptr) { LightTableIndex = 0; - DrawString(out, _("Pause"), { { 0, 0 }, { gnScreenWidth, GetMainPanel().position.y } }, UiFlags::FontSize46 | UiFlags::ColorGold | UiFlags::AlignCenter | UiFlags::VerticalCenter, 2); + DrawString(out, _("Pause"), { { 0, 0 }, { gnScreenWidth, GetMainPanel().position.y } }, + { .flags = UiFlags::FontSize46 | UiFlags::ColorGold | UiFlags::AlignCenter | UiFlags::VerticalCenter, .spacing = 2 }); } } @@ -272,12 +274,12 @@ bool gmenu_presskeys(SDL_Keycode vkey) case SDLK_KP_ENTER: case SDLK_RETURN: if (sgpCurrItem->enabled()) { - PlaySFX(IS_TITLEMOV); + PlaySFX(SfxID::MenuMove); sgpCurrItem->fnMenu(true); } break; case SDLK_ESCAPE: - PlaySFX(IS_TITLEMOV); + PlaySFX(SfxID::MenuMove); gmenu_set_items(nullptr, nullptr); break; case SDLK_SPACE: @@ -349,7 +351,7 @@ bool gmenu_left_mouse(bool isDown) return true; } sgpCurrItem = pItem; - PlaySFX(IS_TITLEMOV); + PlaySFX(SfxID::MenuMove); if (pItem->isSlider()) { isDraggingSlider = GmenuMouseIsOverSlider(); gmenu_on_mouse_move(); diff --git a/Source/help.cpp b/Source/help.cpp index b860fafc46b..b9ecaf318fe 100644 --- a/Source/help.cpp +++ b/Source/help.cpp @@ -4,6 +4,7 @@ * Implementation of the in-game help text. */ #include +#include #include #include "DiabloUI/ui_flags.hpp" @@ -14,7 +15,6 @@ #include "qol/chatlog.h" #include "stores.h" #include "utils/language.h" -#include "utils/stdcompat/string_view.hpp" namespace devilution { @@ -195,7 +195,7 @@ void DrawHelp(const Surface &out) const int lineHeight = LineHeight(); const int blankLineHeight = BlankLineHeight(); - string_view title; + std::string_view title; if (gbIsHellfire) title = gbIsSpawn ? _("Shareware Hellfire Help") : _("Hellfire Help"); else @@ -207,7 +207,7 @@ void DrawHelp(const Surface &out) DrawString(out, title, { { sx, sy + PaddingTop + blankLineHeight }, { ContentTextWidth, lineHeight } }, - UiFlags::ColorWhitegold | UiFlags::AlignCenter); + { .flags = UiFlags::ColorWhitegold | UiFlags::AlignCenter }); const int titleBottom = sy + HeaderHeight(); DrawSLine(out, titleBottom); @@ -215,7 +215,7 @@ void DrawHelp(const Surface &out) const int numLines = NumVisibleLines(); const int contentY = titleBottom + DividerLineMarginY() + ContentPaddingY(); for (int i = 0; i < numLines; i++) { - const string_view line = HelpTextLines[i + SkipLines]; + const std::string_view line = HelpTextLines[i + SkipLines]; if (line.empty()) { continue; } @@ -227,12 +227,13 @@ void DrawHelp(const Surface &out) style = UiFlags::ColorBlue; } - DrawString(out, line.substr(offset), { { sx, contentY + i * lineHeight }, { ContentTextWidth, lineHeight } }, style, /*spacing=*/1, lineHeight); + DrawString(out, line.substr(offset), { { sx, contentY + i * lineHeight }, { ContentTextWidth, lineHeight } }, + { .flags = style, .lineHeight = lineHeight }); } DrawString(out, _("Press ESC to end or the arrow keys to scroll."), { { sx, contentY + ContentsTextHeight() + ContentPaddingY() + blankLineHeight }, { ContentTextWidth, lineHeight } }, - UiFlags::ColorWhitegold | UiFlags::AlignCenter); + { .flags = UiFlags::ColorWhitegold | UiFlags::AlignCenter }); DrawHelpSlider(out); } diff --git a/Source/init.cpp b/Source/init.cpp index a069d71c892..f8602e56523 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -12,7 +12,7 @@ #include #include -#if (defined(_WIN64) || defined(_WIN32)) && !defined(__UWP__) && !defined(NXDK) +#if defined(_WIN32) && !defined(__UWP__) && !defined(DEVILUTIONX_WINDOWS_NO_WCHAR) #include #endif @@ -33,6 +33,7 @@ #include "utils/utf8.hpp" #ifndef UNPACKED_MPQS +#include "mpq/mpq_common.hpp" #include "mpq/mpq_reader.hpp" #endif @@ -79,7 +80,7 @@ namespace { constexpr char ExtraFontsVersion[] = "1\n"; #ifdef UNPACKED_MPQS -std::optional FindUnpackedMpqData(const std::vector &paths, string_view mpqName) +std::optional FindUnpackedMpqData(const std::vector &paths, std::string_view mpqName) { std::string targetPath; for (const std::string &path : paths) { @@ -94,7 +95,7 @@ std::optional FindUnpackedMpqData(const std::vector &p return std::nullopt; } #else -std::optional LoadMPQ(const std::vector &paths, string_view mpqName) +std::optional LoadMPQ(const std::vector &paths, std::string_view mpqName) { std::optional archive; std::string mpqAbsPath; @@ -133,7 +134,7 @@ std::vector GetMPQSearchPaths() // add `XDG_DATA_DIRS`. const char *xdgDataDirs = std::getenv("XDG_DATA_DIRS"); if (xdgDataDirs != nullptr) { - for (const string_view path : SplitByChar(xdgDataDirs, ':')) { + for (const std::string_view path : SplitByChar(xdgDataDirs, ':')) { std::string fullPath(path); if (!path.empty() && path.back() != '/') fullPath += '/'; @@ -146,7 +147,7 @@ std::vector GetMPQSearchPaths() } #elif defined(NXDK) paths.emplace_back("D:\\"); -#elif (defined(_WIN64) || defined(_WIN32)) && !defined(__UWP__) && !defined(NXDK) +#elif defined(_WIN32) && !defined(__UWP__) && !defined(DEVILUTIONX_WINDOWS_NO_WCHAR) char gogpath[_FSG_PATH_MAX]; fsg_get_gog_game_path(gogpath, "1412601690"); if (strlen(gogpath) > 0) { @@ -155,7 +156,9 @@ std::vector GetMPQSearchPaths() } #endif - paths.emplace_back(""); // PWD + if (paths.empty() || !paths.back().empty()) { + paths.emplace_back(); // PWD + } if (SDL_LOG_PRIORITY_VERBOSE >= SDL_LogGetPriority(SDL_LOG_CATEGORY_APPLICATION)) { LogVerbose("Paths:\n base: {}\n pref: {}\n config: {}\n assets: {}", @@ -182,7 +185,7 @@ bool CheckExtraFontsVersion(AssetRef &&ref) if (!handle.read(version_contents.get(), size)) return true; - return string_view { version_contents.get(), size } != ExtraFontsVersion; + return std::string_view { version_contents.get(), size } != ExtraFontsVersion; } } // namespace @@ -201,7 +204,7 @@ bool AreExtraFontsOutOfDate(const std::string &path) bool AreExtraFontsOutOfDate(MpqArchive &archive) { const char filename[] = "fonts\\VERSION"; - const MpqArchive::FileHash fileHash = MpqArchive::CalculateFileHash(filename); + const MpqFileHash fileHash = CalculateMpqFileHash(filename); uint32_t fileNumber; if (!archive.GetFileNumber(fileHash, fileNumber)) return true; @@ -266,7 +269,7 @@ void LoadLanguageArchive() lang_mpq = std::nullopt; #endif - string_view code = GetLanguageCode(); + std::string_view code = GetLanguageCode(); if (code != "en") { std::string langMpqName { code }; #ifdef UNPACKED_MPQS @@ -403,8 +406,14 @@ void MainWndProc(const SDL_Event &event) case SDL_WINDOWEVENT_FOCUS_GAINED: diablo_focus_unpause(); break; + case SDL_WINDOWEVENT_MOVED: + case SDL_WINDOWEVENT_RESIZED: + case SDL_WINDOWEVENT_MAXIMIZED: + case SDL_WINDOWEVENT_ENTER: + case SDL_WINDOWEVENT_TAKE_FOCUS: + break; default: - LogVerbose("Unhandled SDL_WINDOWEVENT event: ", event.window.event); + LogVerbose("Unhandled SDL_WINDOWEVENT event: {:d}", event.window.event); break; } #else diff --git a/Source/init.h b/Source/init.h index 2edcd387e23..d5c9921c439 100644 --- a/Source/init.h +++ b/Source/init.h @@ -5,8 +5,9 @@ */ #pragma once +#include + #include "utils/attributes.h" -#include "utils/stdcompat/optional.hpp" #ifdef UNPACKED_MPQS #include diff --git a/Source/interfac.cpp b/Source/interfac.cpp index 6208c43a346..601473e5d2b 100644 --- a/Source/interfac.cpp +++ b/Source/interfac.cpp @@ -5,6 +5,7 @@ */ #include +#include #include @@ -25,7 +26,6 @@ #include "pfile.h" #include "plrmsg.h" #include "utils/sdl_geometry.h" -#include "utils/stdcompat/optional.hpp" namespace devilution { @@ -46,8 +46,8 @@ const int BarPos[3][2] = { { 53, 37 }, { 53, 421 }, { 53, 37 } }; OptionalOwnedClxSpriteList ArtCutsceneWidescreen; -uint32_t CustomEventsBegin = SDL_USEREVENT; -constexpr uint32_t NumCustomEvents = WM_LAST - WM_FIRST + 1; +SdlEventType CustomEventsBegin = SDL_USEREVENT; +constexpr uint16_t NumCustomEvents = WM_LAST - WM_FIRST + 1; Cutscenes GetCutSceneFromLevelType(dungeon_type type) { @@ -234,17 +234,17 @@ void RegisterCustomEvents() #endif } -bool IsCustomEvent(uint32_t eventType) +bool IsCustomEvent(SdlEventType eventType) { return eventType >= CustomEventsBegin && eventType < CustomEventsBegin + NumCustomEvents; } -interface_mode GetCustomEvent(uint32_t eventType) +interface_mode GetCustomEvent(SdlEventType eventType) { return static_cast(eventType - CustomEventsBegin); } -uint32_t CustomEventToSdlEvent(interface_mode eventType) +SdlEventType CustomEventToSdlEvent(interface_mode eventType) { return CustomEventsBegin + eventType; } @@ -288,7 +288,7 @@ void ShowProgress(interface_mode uMsg) IsProgress = true; gbSomebodyWonGameKludge = false; - plrmsg_delay(true); + uint32_t delayStart = SDL_GetTicks(); EventHandler previousHandler = SetEventHandler(DisableInputEventHandler); @@ -501,7 +501,7 @@ void ShowProgress(interface_mode uMsg) IsProgress = false; NetSendCmdLocParam2(true, CMD_PLAYER_JOINLEVEL, myPlayer.position.tile, myPlayer.plrlevel, myPlayer.plrIsOnSetLevel ? 1 : 0); - plrmsg_delay(false); + DelayPlrMessages(SDL_GetTicks() - delayStart); if (gbSomebodyWonGameKludge && myPlayer.isOnLevel(16)) { PrepDoEnding(); diff --git a/Source/interfac.h b/Source/interfac.h index ab33c521dbe..eeabe434c5b 100644 --- a/Source/interfac.h +++ b/Source/interfac.h @@ -14,7 +14,7 @@ namespace devilution { /** * @brief Custom events. */ -enum interface_mode : uint16_t { +enum interface_mode : uint8_t { WM_DIABNEXTLVL = 0, WM_DIABPREVLVL, WM_DIABRTNLVL, @@ -32,11 +32,17 @@ enum interface_mode : uint16_t { void RegisterCustomEvents(); -bool IsCustomEvent(uint32_t eventType); +#if SDL_VERSION_ATLEAST(2, 0, 0) +using SdlEventType = uint16_t; +#else +using SdlEventType = uint8_t; +#endif -interface_mode GetCustomEvent(uint32_t eventType); +bool IsCustomEvent(SdlEventType eventType); -uint32_t CustomEventToSdlEvent(interface_mode eventType); +interface_mode GetCustomEvent(SdlEventType eventType); + +SdlEventType CustomEventToSdlEvent(interface_mode eventType); enum Cutscenes : uint8_t { CutStart, diff --git a/Source/inv.cpp b/Source/inv.cpp index 8350fc7aaa8..5310670eae4 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -4,7 +4,9 @@ * Implementation of player inventory. */ #include +#include #include +#include #include #include @@ -31,7 +33,6 @@ #include "utils/format_int.hpp" #include "utils/language.h" #include "utils/sdl_geometry.h" -#include "utils/stdcompat/optional.hpp" #include "utils/str_cat.hpp" #include "utils/utf8.hpp" @@ -44,21 +45,21 @@ bool invflag; * arranged as follows: * * @code{.unparsed} - * 00 01 - * 02 03 06 + * 00 00 + * 00 00 03 * - * 07 08 19 20 13 14 - * 09 10 21 22 15 16 - * 11 12 23 24 17 18 + * 04 04 06 06 05 05 + * 04 04 06 06 05 05 + * 04 04 06 06 05 05 * - * 04 05 + * 01 02 * - * 25 26 27 28 29 30 31 32 33 34 - * 35 36 37 38 39 40 41 42 43 44 - * 45 46 47 48 49 50 51 52 53 54 - * 55 56 57 58 59 60 61 62 63 64 + * 07 08 09 10 11 12 13 14 15 16 + * 17 18 19 20 21 22 23 24 25 26 + * 27 28 29 30 31 32 33 34 35 36 + * 37 38 39 40 41 42 43 44 45 46 * - * 65 66 67 68 69 70 71 72 + * 47 48 49 50 51 52 53 54 * @endcode */ const Rectangle InvRect[] = { @@ -133,7 +134,7 @@ OptionalOwnedClxSpriteList pInvCels; * @param invListIndex The item's InvList index (it's expected this already has +1 added to it since InvGrid can't store a 0 index) * @param itemSize Size of item */ -void AddItemToInvGrid(Player &player, int invGridIndex, int invListIndex, Size itemSize) +void AddItemToInvGrid(Player &player, int invGridIndex, int invListIndex, Size itemSize, bool sendNetworkMessage) { const int pitch = 10; for (int y = 0; y < itemSize.height; y++) { @@ -146,7 +147,7 @@ void AddItemToInvGrid(Player &player, int invGridIndex, int invListIndex, Size i } } - if (&player == MyPlayer) { + if (sendNetworkMessage) { NetSendCmdChInvItem(false, invGridIndex); } } @@ -255,25 +256,25 @@ bool CanEquip(Player &player, const Item &item, inv_body_loc bodyLocation) } } -void ChangeEquipment(Player &player, inv_body_loc bodyLocation, const Item &item) +void ChangeEquipment(Player &player, inv_body_loc bodyLocation, const Item &item, bool sendNetworkMessage) { player.InvBody[bodyLocation] = item; - if (&player == MyPlayer) { + if (sendNetworkMessage) { NetSendCmdChItem(false, bodyLocation, true); } } -bool AutoEquip(Player &player, const Item &item, inv_body_loc bodyLocation, bool persistItem) +bool AutoEquip(Player &player, const Item &item, inv_body_loc bodyLocation, bool persistItem, bool sendNetworkMessage) { if (!CanEquip(player, item, bodyLocation)) { return false; } if (persistItem) { - ChangeEquipment(player, bodyLocation, item); + ChangeEquipment(player, bodyLocation, item, sendNetworkMessage); - if (*sgOptions.Audio.autoEquipSound && &player == MyPlayer) { + if (sendNetworkMessage && *sgOptions.Audio.autoEquipSound) { PlaySFX(ItemInvSnds[ItemCAnimTbl[item._iCurs]]); } @@ -283,36 +284,43 @@ bool AutoEquip(Player &player, const Item &item, inv_body_loc bodyLocation, bool return true; } -int FindSlotUnderCursor(Point cursorPosition, Size itemSize) +int FindTargetSlotUnderItemCursor(Point cursorPosition, Size itemSize) { - int i = cursorPosition.x; - int j = cursorPosition.y; - - if (!IsHardwareCursor()) { - // offset the cursor position to match the hot pixel we'd use for a hardware cursor - i += itemSize.width * INV_SLOT_HALF_SIZE_PX; - j += itemSize.height * INV_SLOT_HALF_SIZE_PX; + Displacement panelOffset = Point { 0, 0 } - GetRightPanel().position; + for (int r = SLOTXY_EQUIPPED_FIRST; r <= SLOTXY_EQUIPPED_LAST; r++) { + if (InvRect[r].contains(cursorPosition + panelOffset)) + return r; } - - for (int r = 0; r < NUM_XY_SLOTS; r++) { - int xo = GetRightPanel().position.x; - int yo = GetRightPanel().position.y; - if (r >= SLOTXY_BELT_FIRST) { - xo = GetMainPanel().position.x; - yo = GetMainPanel().position.y; + for (int r = SLOTXY_INV_FIRST; r <= SLOTXY_INV_LAST; r++) { + if (InvRect[r].contains(cursorPosition + panelOffset)) { + // When trying to paste into the inventory we need to determine the top left cell of the nearest area that could fit the item, not the slot under the center/hot pixel. + if (itemSize.height <= 1 && itemSize.width <= 1) { + // top left cell of a 1x1 item is the same cell as the hot pixel, no work to do + return r; + } + // Otherwise work out how far the central cell is from the top-left cell + Displacement hotPixelCellOffset = { (itemSize.width - 1) / 2, (itemSize.height - 1) / 2 }; + // For even dimension items we need to work out if the cursor is in the left/right (or top/bottom) half of the central cell and adjust the offset so the item lands in the area most covered by the cursor. + if (itemSize.width % 2 == 0 && InvRect[r].contains(cursorPosition + panelOffset + Displacement { INV_SLOT_HALF_SIZE_PX, 0 })) { + // hot pixel was in the left half of the cell, so we want to increase the offset to preference the column to the left + hotPixelCellOffset.deltaX++; + } + if (itemSize.height % 2 == 0 && InvRect[r].contains(cursorPosition + panelOffset + Displacement { 0, INV_SLOT_HALF_SIZE_PX })) { + // hot pixel was in the top half of the cell, so we want to increase the offset to preference the row above + hotPixelCellOffset.deltaY++; + } + // Then work out the top left cell of the nearest area that could fit this item (as pasting on the edge of the inventory would otherwise put it out of bounds) + int hotPixelCell = r - SLOTXY_INV_FIRST; + int targetRow = std::clamp((hotPixelCell / InventorySizeInSlots.width) - hotPixelCellOffset.deltaY, 0, InventorySizeInSlots.height - itemSize.height); + int targetColumn = std::clamp((hotPixelCell % InventorySizeInSlots.width) - hotPixelCellOffset.deltaX, 0, InventorySizeInSlots.width - itemSize.width); + return SLOTXY_INV_FIRST + targetRow * InventorySizeInSlots.width + targetColumn; } + } - if (r == SLOTXY_INV_FIRST) { - if (itemSize.width % 2 == 0) - i -= INV_SLOT_HALF_SIZE_PX; - if (itemSize.height % 2 == 0) - j -= INV_SLOT_HALF_SIZE_PX; - } - if (InvRect[r].contains(i - xo, j - yo)) { + panelOffset = Point { 0, 0 } - GetMainPanel().position; + for (int r = SLOTXY_BELT_FIRST; r <= SLOTXY_BELT_LAST; r++) { + if (InvRect[r].contains(cursorPosition + panelOffset)) return r; - } - if (r == SLOTXY_INV_LAST && itemSize.height % 2 == 0) - j += INV_SLOT_HALF_SIZE_PX; } return NUM_XY_SLOTS; } @@ -321,7 +329,7 @@ void CheckInvPaste(Player &player, Point cursorPosition) { Size itemSize = GetInventorySize(player.HoldItem); - int slot = FindSlotUnderCursor(cursorPosition, itemSize); + int slot = FindTargetSlotUnderItemCursor(cursorPosition, itemSize); if (slot == NUM_XY_SLOTS) return; @@ -358,30 +366,29 @@ void CheckInvPaste(Player &player, Point cursorPosition) } } } else { - int yy = std::max(INV_ROW_SLOT_SIZE * ((ii / INV_ROW_SLOT_SIZE) - ((itemSize.height - 1) / 2)), 0); - for (int j = 0; j < itemSize.height; j++) { - if (yy >= InventoryGridCells) - return; - int xx = std::max((ii % INV_ROW_SLOT_SIZE) - ((itemSize.width - 1) / 2), 0); - for (int i = 0; i < itemSize.width; i++) { - if (xx >= INV_ROW_SLOT_SIZE) - return; - if (player.InvGrid[xx + yy] != 0) { - int8_t iv = abs(player.InvGrid[xx + yy]); + // check that the item we're pasting only overlaps one other item (or is going into empty space) + unsigned originCell = static_cast(slot - SLOTXY_INV_FIRST); + for (unsigned rowOffset = 0; rowOffset < static_cast(itemSize.height * InventorySizeInSlots.width); rowOffset += InventorySizeInSlots.width) { + for (unsigned columnOffset = 0; columnOffset < static_cast(itemSize.width); columnOffset++) { + unsigned testCell = originCell + rowOffset + columnOffset; + // FindTargetSlotUnderItemCursor returns the top left slot of the inventory region that fits the item, we can be confident this calculation is not going to read out of range. + assert(testCell < sizeof(player.InvGrid)); + if (player.InvGrid[testCell] != 0) { + int8_t iv = std::abs(player.InvGrid[testCell]); if (it != 0) { - if (it != iv) + if (it != iv) { + // Found two different items that would be displaced by the held item, can't paste the item here. return; + } } else { it = iv; } } - xx++; } - yy += INV_ROW_SLOT_SIZE; } } } else if (il == ILOC_BELT) { - if (!CanBePlacedOnBelt(player.HoldItem)) + if (!CanBePlacedOnBelt(player, player.HoldItem)) return; } else if (desiredIl != il) { return; @@ -419,7 +426,7 @@ void CheckInvPaste(Player &player, Point cursorPosition) }; inv_body_loc slot = iLocToInvLoc(il); Item previouslyEquippedItem = player.InvBody[slot]; - ChangeEquipment(player, slot, player.HoldItem.pop()); + ChangeEquipment(player, slot, player.HoldItem.pop(), &player == MyPlayer); if (!previouslyEquippedItem.isEmpty()) { player.HoldItem = previouslyEquippedItem; } @@ -439,7 +446,7 @@ void CheckInvPaste(Player &player, Point cursorPosition) if (dequipTwoHandedWeapon) { RemoveEquipment(player, otherHand, false); } - ChangeEquipment(player, pasteHand, player.HoldItem.pop()); + ChangeEquipment(player, pasteHand, player.HoldItem.pop(), &player == MyPlayer); if (!previouslyEquippedItem.isEmpty()) { player.HoldItem = previouslyEquippedItem; } @@ -465,14 +472,14 @@ void CheckInvPaste(Player &player, Point cursorPosition) if (player.InvBody[INVLOC_HAND_RIGHT].isEmpty()) { Item previouslyEquippedItem = player.InvBody[INVLOC_HAND_LEFT]; - ChangeEquipment(player, INVLOC_HAND_LEFT, player.HoldItem.pop()); + ChangeEquipment(player, INVLOC_HAND_LEFT, player.HoldItem.pop(), &player == MyPlayer); if (!previouslyEquippedItem.isEmpty()) { player.HoldItem = previouslyEquippedItem; } } else { Item previouslyEquippedItem = player.InvBody[INVLOC_HAND_RIGHT]; RemoveEquipment(player, INVLOC_HAND_RIGHT, false); - ChangeEquipment(player, INVLOC_HAND_LEFT, player.HoldItem); + ChangeEquipment(player, INVLOC_HAND_LEFT, player.HoldItem, &player == MyPlayer); player.HoldItem = previouslyEquippedItem; } break; @@ -525,13 +532,8 @@ void CheckInvPaste(Player &player, Point cursorPosition) itemIndex = 0; } } - int ii = slot - SLOTXY_INV_FIRST; - - // Calculate top-left position of item for InvGrid and then add item to InvGrid - int xx = std::max(ii % INV_ROW_SLOT_SIZE - ((itemSize.width - 1) / 2), 0); - int yy = std::max(INV_ROW_SLOT_SIZE * (ii / INV_ROW_SLOT_SIZE - ((itemSize.height - 1) / 2)), 0); - AddItemToInvGrid(player, xx + yy, it, itemSize); + AddItemToInvGrid(player, slot - SLOTXY_INV_FIRST, it, itemSize, &player == MyPlayer); } break; case ILOC_BELT: { @@ -554,8 +556,6 @@ void CheckInvPaste(Player &player, Point cursorPosition) } CalcPlrInv(player, true); if (&player == MyPlayer) { - if (player.HoldItem.isEmpty() && !IsHardwareCursor()) - SetCursorPos(MousePosition + Displacement { itemSize * INV_SLOT_HALF_SIZE_PX }); NewCursor(player.HoldItem); } } @@ -566,10 +566,7 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool return; } - if (dropGoldFlag) { - CloseGoldDrop(); - dropGoldValue = 0; - } + CloseGoldDrop(); uint32_t r = 0; for (; r < NUM_XY_SLOTS; r++) { @@ -697,8 +694,8 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool holdItem = player.InvList[iv - 1]; if (automaticMove) { - if (CanBePlacedOnBelt(holdItem)) { - automaticallyMoved = AutoPlaceItemInBelt(player, holdItem, true); + if (CanBePlacedOnBelt(player, holdItem)) { + automaticallyMoved = AutoPlaceItemInBelt(player, holdItem, true, &player == MyPlayer); } else if (CanEquip(holdItem)) { /* * Move the respective InvBodyItem to inventory before moving the item from inventory @@ -768,7 +765,7 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool } } holdItem = player.InvList[iv - 1]; - automaticallyMoved = automaticallyEquipped = AutoEquip(player, holdItem); + automaticallyMoved = automaticallyEquipped = AutoEquip(player, holdItem, true, &player == MyPlayer); } } @@ -804,12 +801,12 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool if (automaticallyEquipped) { PlaySFX(ItemInvSnds[ItemCAnimTbl[holdItem._iCurs]]); } else if (!automaticMove || automaticallyMoved) { - PlaySFX(IS_IGRAB); + PlaySFX(SfxID::GrabItem); } if (automaticMove) { if (!automaticallyMoved) { - if (CanBePlacedOnBelt(holdItem) || automaticallyUnequip) { + if (CanBePlacedOnBelt(player, holdItem) || automaticallyUnequip) { player.SaySpecific(HeroSpeech::IHaveNoRoom); } else { player.SaySpecific(HeroSpeech::ICantDoThat); @@ -819,11 +816,6 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool holdItem.clear(); } else { NewCursor(holdItem); - if (!IsHardwareCursor() && !dropItem) { - // For a hardware cursor, we set the "hot point" to the center of the item instead. - Size cursSize = GetInvItemSize(holdItem._iCurs + CURSOR_FIRSTITEM); - SetCursorPos(cursorPosition - Displacement(cursSize / 2)); - } } } } @@ -961,14 +953,13 @@ void StartGoldDrop() { CloseGoldWithdraw(); - initialDropGoldIndex = pcursinvitem; + const int8_t invIndex = pcursinvitem; - Player &myPlayer = *MyPlayer; + const Player &myPlayer = *MyPlayer; - if (pcursinvitem <= INVITEM_INV_LAST) - initialDropGoldValue = myPlayer.InvList[pcursinvitem - INVITEM_INV_FIRST]._ivalue; - else - initialDropGoldValue = myPlayer.SpdList[pcursinvitem - INVITEM_BELT_FIRST]._ivalue; + const int max = (invIndex <= INVITEM_INV_LAST) + ? myPlayer.InvList[invIndex - INVITEM_INV_FIRST]._ivalue + : myPlayer.SpdList[invIndex - INVITEM_BELT_FIRST]._ivalue; if (talkflag) control_reset_talk(); @@ -977,9 +968,7 @@ void StartGoldDrop() SDL_Rect rect = MakeSdlRect(start.x, start.y, 180, 20); SDL_SetTextInputRect(&rect); - dropGoldFlag = true; - dropGoldValue = 0; - SDL_StartTextInput(); + OpenGoldDrop(invIndex, max); } int CreateGoldItemInInventorySlot(Player &player, int slotIndex, int value) @@ -1034,11 +1023,11 @@ void InvDrawSlotBack(const Surface &out, Point targetPosition, Size size, item_q } } -bool CanBePlacedOnBelt(const Item &item) +bool CanBePlacedOnBelt(const Player &player, const Item &item) { return FitsInBeltSlot(item) && item._itype != ItemType::Gold - && MyPlayer->CanUseItem(item) + && player.CanUseItem(item) && item.isUsable(); } @@ -1137,7 +1126,7 @@ void DrawInv(const Surface &out) out, GetPanelPosition(UiPanels::Inventory, InvRect[i + SLOTXY_INV_FIRST].position) + Displacement { 0, InventorySlotSizeInPixels.height }, InventorySlotSizeInPixels, - myPlayer.InvList[abs(myPlayer.InvGrid[i]) - 1]._iMagical); + myPlayer.InvList[std::abs(myPlayer.InvGrid[i]) - 1]._iMagical); } } @@ -1190,7 +1179,8 @@ void DrawInvBelt(const Surface &out) if (myPlayer.SpdList[i].isUsable() && myPlayer.SpdList[i]._itype != ItemType::Gold) { - DrawString(out, StrCat(i + 1), { position - Displacement { 0, 12 }, InventorySlotSizeInPixels }, UiFlags::ColorWhite | UiFlags::AlignRight); + DrawString(out, StrCat(i + 1), { position - Displacement { 0, 12 }, InventorySlotSizeInPixels }, + { .flags = UiFlags::ColorWhite | UiFlags::AlignRight }); } } } @@ -1204,9 +1194,9 @@ void RemoveEquipment(Player &player, inv_body_loc bodyLocation, bool hiPri) player.InvBody[bodyLocation].clear(); } -bool AutoPlaceItemInBelt(Player &player, const Item &item, bool persistItem) +bool AutoPlaceItemInBelt(Player &player, const Item &item, bool persistItem, bool sendNetworkMessage) { - if (!CanBePlacedOnBelt(item)) { + if (!CanBePlacedOnBelt(player, item)) { return false; } @@ -1216,8 +1206,8 @@ bool AutoPlaceItemInBelt(Player &player, const Item &item, bool persistItem) beltItem = item; player.CalcScrolls(); RedrawComponent(PanelDrawComponent::Belt); - if (&player == MyPlayer) { - size_t beltIndex = std::distance(&player.SpdList[0], &beltItem); + if (sendNetworkMessage) { + const auto beltIndex = static_cast(std::distance(&player.SpdList[0], &beltItem)); NetSendCmdChBeltItem(false, beltIndex); } } @@ -1229,14 +1219,14 @@ bool AutoPlaceItemInBelt(Player &player, const Item &item, bool persistItem) return false; } -bool AutoEquip(Player &player, const Item &item, bool persistItem) +bool AutoEquip(Player &player, const Item &item, bool persistItem, bool sendNetworkMessage) { if (!CanEquip(item)) { return false; } for (int bodyLocation = INVLOC_HEAD; bodyLocation < NUM_INVLOC; bodyLocation++) { - if (AutoEquip(player, item, (inv_body_loc)bodyLocation, persistItem)) { + if (AutoEquip(player, item, (inv_body_loc)bodyLocation, persistItem, sendNetworkMessage)) { return true; } } @@ -1271,18 +1261,59 @@ bool AutoEquipEnabled(const Player &player, const Item &item) return true; } -bool AutoPlaceItemInInventory(Player &player, const Item &item, bool persistItem) +namespace { +/** + * @brief Checks whether the given item can be placed on the specified player's inventory slot. + * If 'persistItem' is 'True', the item is also placed in the inventory slot. + * @param player The player whose inventory will be checked. + * @param slotIndex The 0-based index of the slot to put the item on. + * @param item The item to be checked. + * @param persistItem Pass 'True' to actually place the item in the inventory slot. The default is 'False'. + * @return 'True' in case the item can be placed on the specified player's inventory slot and 'False' otherwise. + */ +bool AutoPlaceItemInInventorySlot(Player &player, int slotIndex, const Item &item, bool persistItem, bool sendNetworkMessage) +{ + int yy = (slotIndex > 0) ? (10 * (slotIndex / 10)) : 0; + + Size itemSize = GetInventorySize(item); + for (int j = 0; j < itemSize.height; j++) { + if (yy >= InventoryGridCells) { + return false; + } + int xx = (slotIndex > 0) ? (slotIndex % 10) : 0; + for (int i = 0; i < itemSize.width; i++) { + if (xx >= 10 || player.InvGrid[xx + yy] != 0) { + return false; + } + xx++; + } + yy += 10; + } + + if (persistItem) { + player.InvList[player._pNumInv] = item; + player._pNumInv++; + + AddItemToInvGrid(player, slotIndex, player._pNumInv, itemSize, sendNetworkMessage); + player.CalcScrolls(); + } + + return true; +} +} // namespace + +bool AutoPlaceItemInInventory(Player &player, const Item &item, bool persistItem, bool sendNetworkMessage) { Size itemSize = GetInventorySize(item); if (itemSize.height == 1) { for (int i = 30; i <= 39; i++) { - if (AutoPlaceItemInInventorySlot(player, i, item, persistItem)) + if (AutoPlaceItemInInventorySlot(player, i, item, persistItem, sendNetworkMessage)) return true; } for (int x = 9; x >= 0; x--) { for (int y = 2; y >= 0; y--) { - if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem)) + if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem, sendNetworkMessage)) return true; } } @@ -1292,14 +1323,14 @@ bool AutoPlaceItemInInventory(Player &player, const Item &item, bool persistItem if (itemSize.height == 2) { for (int x = 10 - itemSize.width; x >= 0; x -= itemSize.width) { for (int y = 0; y < 3; y++) { - if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem)) + if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem, sendNetworkMessage)) return true; } } if (itemSize.width == 2) { for (int x = 7; x >= 0; x -= 2) { for (int y = 0; y < 3; y++) { - if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem)) + if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem, sendNetworkMessage)) return true; } } @@ -1309,7 +1340,7 @@ bool AutoPlaceItemInInventory(Player &player, const Item &item, bool persistItem if (itemSize == Size { 1, 3 }) { for (int i = 0; i < 20; i++) { - if (AutoPlaceItemInInventorySlot(player, i, item, persistItem)) + if (AutoPlaceItemInInventorySlot(player, i, item, persistItem, sendNetworkMessage)) return true; } return false; @@ -1317,12 +1348,12 @@ bool AutoPlaceItemInInventory(Player &player, const Item &item, bool persistItem if (itemSize == Size { 2, 3 }) { for (int i = 0; i < 9; i++) { - if (AutoPlaceItemInInventorySlot(player, i, item, persistItem)) + if (AutoPlaceItemInInventorySlot(player, i, item, persistItem, sendNetworkMessage)) return true; } for (int i = 10; i < 19; i++) { - if (AutoPlaceItemInInventorySlot(player, i, item, persistItem)) + if (AutoPlaceItemInInventorySlot(player, i, item, persistItem, sendNetworkMessage)) return true; } return false; @@ -1331,36 +1362,6 @@ bool AutoPlaceItemInInventory(Player &player, const Item &item, bool persistItem app_fatal(StrCat("Unknown item size: ", itemSize.width, "x", itemSize.height)); } -bool AutoPlaceItemInInventorySlot(Player &player, int slotIndex, const Item &item, bool persistItem) -{ - int yy = (slotIndex > 0) ? (10 * (slotIndex / 10)) : 0; - - Size itemSize = GetInventorySize(item); - for (int j = 0; j < itemSize.height; j++) { - if (yy >= InventoryGridCells) { - return false; - } - int xx = (slotIndex > 0) ? (slotIndex % 10) : 0; - for (int i = 0; i < itemSize.width; i++) { - if (xx >= 10 || player.InvGrid[xx + yy] != 0) { - return false; - } - xx++; - } - yy += 10; - } - - if (persistItem) { - player.InvList[player._pNumInv] = item; - player._pNumInv++; - - AddItemToInvGrid(player, slotIndex, player._pNumInv, itemSize); - player.CalcScrolls(); - } - - return true; -} - int RoomForGold() { int amount = 0; @@ -1461,7 +1462,7 @@ void CheckInvSwap(Player &player, const Item &item, int invGridIndex) for (int x = 0; x < itemSize.width; x++) { int gridIndex = rowGridIndex + x; if (player.InvGrid[gridIndex] != 0) - return abs(player.InvGrid[gridIndex]); + return std::abs(player.InvGrid[gridIndex]); } } player._pNumInv++; @@ -1494,7 +1495,7 @@ void CheckInvSwap(Player &player, const Item &item, int invGridIndex) void CheckInvRemove(Player &player, int invGridIndex) { - int invListIndex = abs(player.InvGrid[invGridIndex]) - 1; + int invListIndex = std::abs(player.InvGrid[invGridIndex]) - 1; if (invListIndex >= 0) { player.RemoveInvItem(invListIndex); @@ -1549,10 +1550,7 @@ void CheckInvScrn(bool isShiftHeld, bool isCtrlHeld) void InvGetItem(Player &player, int ii) { auto &item = Items[ii]; - if (dropGoldFlag) { - CloseGoldDrop(); - dropGoldValue = 0; - } + CloseGoldDrop(); if (dItem[item.position.x][item.position.y] == 0) return; @@ -1565,7 +1563,7 @@ void InvGetItem(Player &player, int ii) if (MyPlayer == &player) { // Non-gold items (or gold when you have a full inventory) go to the hand then provide audible feedback on // paste. To give the same feedback for auto-placed gold we play the sound effect now. - PlaySFX(IS_GOLD); + PlaySFX(SfxID::ItemGold); } } else { // The item needs to go into the players hand @@ -1623,10 +1621,7 @@ void AutoGetItem(Player &player, Item *itemPointer, int ii) { Item &item = *itemPointer; - if (dropGoldFlag) { - CloseGoldDrop(); - dropGoldValue = 0; - } + CloseGoldDrop(); if (dItem[item.position.x][item.position.y] == 0) return; @@ -1644,22 +1639,22 @@ void AutoGetItem(Player &player, Item *itemPointer, int ii) SetPlrHandGoldCurs(item); } } else { - done = AutoEquipEnabled(player, item) && AutoEquip(player, item); + done = AutoEquipEnabled(player, item) && AutoEquip(player, item, true, &player == MyPlayer); if (done) { autoEquipped = true; } if (!done) { - done = AutoPlaceItemInBelt(player, item, true); + done = AutoPlaceItemInBelt(player, item, true, &player == MyPlayer); } if (!done) { - done = AutoPlaceItemInInventory(player, item, true); + done = AutoPlaceItemInInventory(player, item, true, &player == MyPlayer); } } if (done) { if (!autoEquipped && *sgOptions.Audio.itemPickupSound && &player == MyPlayer) { - PlaySFX(IS_IGRAB); + PlaySFX(SfxID::GrabItem); } CleanupItems(ii); @@ -1751,7 +1746,7 @@ int ClampDurability(const Item &item, int durability) if (item._iMaxDur == 0) return 0; - return clamp(durability, 1, item._iMaxDur); + return std::clamp(durability, 1, item._iMaxDur); } int16_t ClampToHit(const Item &item, int16_t toHit) @@ -1782,8 +1777,8 @@ int SyncDropItem(Point position, _item_indexes idx, uint16_t icreateinfo, int is item._iIdentified = true; item._iMaxDur = mdur; item._iDurability = ClampDurability(item, dur); - item._iMaxCharges = clamp(mch, 0, item._iMaxCharges); - item._iCharges = clamp(ch, 0, item._iMaxCharges); + item._iMaxCharges = std::clamp(mch, 0, item._iMaxCharges); + item._iCharges = std::clamp(ch, 0, item._iMaxCharges); if (gbIsHellfire) { item._iPLToHit = ClampToHit(item, toHit); item._iMaxDam = ClampMaxDam(item, maxDam); @@ -1793,7 +1788,7 @@ int SyncDropItem(Point position, _item_indexes idx, uint16_t icreateinfo, int is return PlaceItemInWorld(std::move(item), position); } -int SyncDropEar(Point position, uint16_t icreateinfo, uint32_t iseed, uint8_t cursval, string_view heroname) +int SyncDropEar(Point position, uint16_t icreateinfo, uint32_t iseed, uint8_t cursval, std::string_view heroname) { if (ActiveItemCount >= MAXITEMS) return -1; @@ -1855,7 +1850,7 @@ int8_t CheckInvHLight() rv = INVLOC_CHEST; pi = &myPlayer.InvBody[rv]; } else if (r >= SLOTXY_INV_FIRST && r <= SLOTXY_INV_LAST) { - int8_t itemId = abs(myPlayer.InvGrid[r - SLOTXY_INV_FIRST]); + int8_t itemId = std::abs(myPlayer.InvGrid[r - SLOTXY_INV_FIRST]); if (itemId == 0) return -1; int ii = itemId - 1; @@ -2020,7 +2015,7 @@ bool UseInvItem(int cii) } if (item->IDidx == IDI_FUNGALTM) { - PlaySFX(IS_IBOOK); + PlaySFX(SfxID::ItemBook); player.Say(HeroSpeech::ThatDidntDoAnything, SpeechDelay); return true; } @@ -2051,10 +2046,7 @@ bool UseInvItem(int cii) return true; } - if (dropGoldFlag) { - CloseGoldDrop(); - dropGoldValue = 0; - } + CloseGoldDrop(); if (item->isScroll() && leveltype == DTYPE_TOWN && !GetSpellData(item->_iSpell).isAllowedInTown()) { return true; @@ -2071,11 +2063,11 @@ bool UseInvItem(int cii) int idata = ItemCAnimTbl[item->_iCurs]; if (item->_iMiscId == IMISC_BOOK) - PlaySFX(IS_RBOOK); + PlaySFX(SfxID::ReadBook); else if (&player == MyPlayer) PlaySFX(ItemInvSnds[idata]); - UseItem(player.getId(), item->_iMiscId, item->_iSpell, cii); + UseItem(player, item->_iMiscId, item->_iSpell, cii); if (speedlist) { if (player.SpdList[c]._iMiscId == IMISC_NOTE) { @@ -2118,8 +2110,8 @@ void CloseStash() if (itemTile) { NetSendCmdPItem(true, CMD_PUTITEM, *itemTile, myPlayer.HoldItem); } else { - if (!AutoPlaceItemInBelt(myPlayer, myPlayer.HoldItem, true) - && !AutoPlaceItemInInventory(myPlayer, myPlayer.HoldItem, true) + if (!AutoPlaceItemInBelt(myPlayer, myPlayer.HoldItem, true, true) + && !AutoPlaceItemInInventory(myPlayer, myPlayer.HoldItem, true, true) && !AutoPlaceItemInStash(myPlayer, myPlayer.HoldItem, true)) { // This can fail for max gold, arena potions and a stash that has been arranged // to not have room for the item all 3 cases are extremely unlikely @@ -2139,7 +2131,7 @@ void DoTelekinesis() if (ObjectUnderCursor != nullptr && !ObjectUnderCursor->IsDisabled()) NetSendCmdLoc(MyPlayerId, true, CMD_OPOBJT, cursPosition); if (pcursitem != -1) - NetSendCmdGItem(true, CMD_REQUESTAGITEM, MyPlayerId, pcursitem); + NetSendCmdGItem(true, CMD_REQUESTAGITEM, *MyPlayer, pcursitem); if (pcursmonst != -1) { auto &monter = Monsters[pcursmonst]; if (!M_Talker(monter) && monter.talkMsg == TEXT_NONE) diff --git a/Source/inv.h b/Source/inv.h index b65d649707c..e2adba649f1 100644 --- a/Source/inv.h +++ b/Source/inv.h @@ -12,12 +12,14 @@ #include "inv_iterators.hpp" #include "items.h" #include "player.h" +#include "utils/algorithm/container.hpp" namespace devilution { #define INV_SLOT_SIZE_PX 28 #define INV_SLOT_HALF_SIZE_PX (INV_SLOT_SIZE_PX / 2) -#define INV_ROW_SLOT_SIZE 10 +constexpr Size InventorySizeInSlots { 10, 4 }; +#define INV_ROW_SLOT_SIZE InventorySizeInSlots.width constexpr Size InventorySlotSizeInPixels { INV_SLOT_SIZE_PX }; enum inv_item : int8_t { @@ -43,12 +45,14 @@ enum inv_item : int8_t { enum inv_xy_slot : uint8_t { // clang-format off SLOTXY_HEAD = 0, + SLOTXY_EQUIPPED_FIRST = SLOTXY_HEAD, SLOTXY_RING_LEFT = 1, SLOTXY_RING_RIGHT = 2, SLOTXY_AMULET = 3, SLOTXY_HAND_LEFT = 4, SLOTXY_HAND_RIGHT = 5, SLOTXY_CHEST = 6, + SLOTXY_EQUIPPED_LAST = SLOTXY_CHEST, // regular inventory SLOTXY_INV_FIRST = 7, @@ -79,7 +83,7 @@ enum item_color : uint8_t { }; extern bool invflag; -extern const Rectangle InvRect[73]; +extern const Rectangle InvRect[NUM_XY_SLOTS]; void InvDrawSlotBack(const Surface &out, Point targetPosition, Size size, item_quality itemQuality); /** @@ -88,7 +92,7 @@ void InvDrawSlotBack(const Surface &out, Point targetPosition, Size size, item_q * @param item The item to be checked. * @return 'True' in case the item can be placed on the belt and 'False' otherwise. */ -bool CanBePlacedOnBelt(const Item &item); +bool CanBePlacedOnBelt(const Player &player, const Item &item); /** * @brief Function type which performs an operation on the given item. @@ -130,9 +134,11 @@ bool AutoEquipEnabled(const Player &player, const Item &item); * @param item The item to equip. * @param persistItem Indicates whether or not the item should be persisted in the player's body. Pass 'False' to check * whether the player can equip the item but you don't want the item to actually be equipped. 'True' by default. + * @param sendNetworkMessage Set to true if you want an equip sound and network message to be generated if the equipment + * changes. Should only be set if a local player is equipping an item in a play session (not when creating a new game) * @return 'True' if the item was equipped and 'False' otherwise. */ -bool AutoEquip(Player &player, const Item &item, bool persistItem = true); +bool AutoEquip(Player &player, const Item &item, bool persistItem = true, bool sendNetworkMessage = false); /** * @brief Checks whether the given item can be placed on the specified player's inventory. @@ -140,20 +146,11 @@ bool AutoEquip(Player &player, const Item &item, bool persistItem = true); * @param player The player whose inventory will be checked. * @param item The item to be checked. * @param persistItem Pass 'True' to actually place the item in the inventory. The default is 'False'. + * @param sendNetworkMessage Set to true if you want a network message to be generated if the item is persisted. + * Should only be set if a local player is placing an item in a play session (not when creating a new game) * @return 'True' in case the item can be placed on the player's inventory and 'False' otherwise. */ -bool AutoPlaceItemInInventory(Player &player, const Item &item, bool persistItem = false); - -/** - * @brief Checks whether the given item can be placed on the specified player's inventory slot. - * If 'persistItem' is 'True', the item is also placed in the inventory slot. - * @param player The player whose inventory will be checked. - * @param slotIndex The 0-based index of the slot to put the item on. - * @param item The item to be checked. - * @param persistItem Pass 'True' to actually place the item in the inventory slot. The default is 'False'. - * @return 'True' in case the item can be placed on the specified player's inventory slot and 'False' otherwise. - */ -bool AutoPlaceItemInInventorySlot(Player &player, int slotIndex, const Item &item, bool persistItem); +bool AutoPlaceItemInInventory(Player &player, const Item &item, bool persistItem = false, bool sendNetworkMessage = false); /** * @brief Checks whether the given item can be placed on the specified player's belt. Returns 'True' when the item can be placed @@ -162,9 +159,11 @@ bool AutoPlaceItemInInventorySlot(Player &player, int slotIndex, const Item &ite * @param player The player on whose belt will be checked. * @param item The item to be checked. * @param persistItem Pass 'True' to actually place the item in the belt. The default is 'False'. + * @param sendNetworkMessage Set to true if you want a network message to be generated if the item is persisted. + * Should only be set if a local player is placing an item in a play session (not when creating a new game) * @return 'True' in case the item can be placed on the player's belt and 'False' otherwise. */ -bool AutoPlaceItemInBelt(Player &player, const Item &item, bool persistItem = false); +bool AutoPlaceItemInBelt(Player &player, const Item &item, bool persistItem = false, bool sendNetworkMessage = false); /** * @brief Calculate the maximum aditional gold that may fit in the user's inventory @@ -223,7 +222,7 @@ int ClampDurability(const Item &item, int durability); int16_t ClampToHit(const Item &item, int16_t toHit); uint8_t ClampMaxDam(const Item &item, uint8_t maxDam); int SyncDropItem(Point position, _item_indexes idx, uint16_t icreateinfo, int iseed, int id, int dur, int mdur, int ch, int mch, int ivalue, uint32_t ibuff, int toHit, int maxDam); -int SyncDropEar(Point position, uint16_t icreateinfo, uint32_t iseed, uint8_t cursval, string_view heroname); +int SyncDropEar(Point position, uint16_t icreateinfo, uint32_t iseed, uint8_t cursval, std::string_view heroname); int8_t CheckInvHLight(); bool CanUseScroll(Player &player, SpellID spell); void ConsumeStaffCharge(Player &player); @@ -244,27 +243,27 @@ Size GetInventorySize(const Item &item); * @brief Checks whether the player has an inventory item matching the predicate. */ template -bool HasInventoryItem(Player &player, Predicate &&predicate) +bool HasInventoryItem(const Player &player, Predicate &&predicate) { const InventoryPlayerItemsRange items { player }; - return std::find_if(items.begin(), items.end(), std::forward(predicate)) != items.end(); + return c_find_if(items, std::forward(predicate)) != items.end(); } /** * @brief Checks whether the player has a belt item matching the predicate. */ template -bool HasBeltItem(Player &player, Predicate &&predicate) +bool HasBeltItem(const Player &player, Predicate &&predicate) { const BeltPlayerItemsRange items { player }; - return std::find_if(items.begin(), items.end(), std::forward(predicate)) != items.end(); + return c_find_if(items, std::forward(predicate)) != items.end(); } /** * @brief Checks whether the player has an inventory or a belt item matching the predicate. */ template -bool HasInventoryOrBeltItem(Player &player, Predicate &&predicate) +bool HasInventoryOrBeltItem(const Player &player, Predicate &&predicate) { return HasInventoryItem(player, predicate) || HasBeltItem(player, predicate); } @@ -272,7 +271,7 @@ bool HasInventoryOrBeltItem(Player &player, Predicate &&predicate) /** * @brief Checks whether the player has an inventory item with the given ID (IDidx). */ -inline bool HasInventoryItemWithId(Player &player, _item_indexes id) +inline bool HasInventoryItemWithId(const Player &player, _item_indexes id) { return HasInventoryItem(player, [id](const Item &item) { return item.IDidx == id; @@ -282,7 +281,7 @@ inline bool HasInventoryItemWithId(Player &player, _item_indexes id) /** * @brief Checks whether the player has a belt item with the given ID (IDidx). */ -inline bool HasBeltItemWithId(Player &player, _item_indexes id) +inline bool HasBeltItemWithId(const Player &player, _item_indexes id) { return HasBeltItem(player, [id](const Item &item) { return item.IDidx == id; @@ -292,7 +291,7 @@ inline bool HasBeltItemWithId(Player &player, _item_indexes id) /** * @brief Checks whether the player has an inventory or a belt item with the given ID (IDidx). */ -inline bool HasInventoryOrBeltItemWithId(Player &player, _item_indexes id) +inline bool HasInventoryOrBeltItemWithId(const Player &player, _item_indexes id) { return HasInventoryItemWithId(player, id) || HasBeltItemWithId(player, id); } @@ -306,7 +305,7 @@ template bool RemoveInventoryItem(Player &player, Predicate &&predicate) { const InventoryPlayerItemsRange items { player }; - const auto it = std::find_if(items.begin(), items.end(), std::forward(predicate)); + const auto it = c_find_if(items, std::forward(predicate)); if (it == items.end()) return false; player.RemoveInvItem(static_cast(it.index())); @@ -322,7 +321,7 @@ template bool RemoveBeltItem(Player &player, Predicate &&predicate) { const BeltPlayerItemsRange items { player }; - const auto it = std::find_if(items.begin(), items.end(), std::forward(predicate)); + const auto it = c_find_if(items, std::forward(predicate)); if (it == items.end()) return false; player.RemoveSpdBarItem(static_cast(it.index())); diff --git a/Source/inv_iterators.hpp b/Source/inv_iterators.hpp index 3447e2d3fe0..b11c9310d90 100644 --- a/Source/inv_iterators.hpp +++ b/Source/inv_iterators.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -13,19 +14,23 @@ namespace devilution { /** * @brief A range over non-empty items in a container. */ +template class ItemsContainerRange { + static_assert(std::is_same_v || std::is_same_v, + "The template argument must be `Item` or `const Item`"); + public: class Iterator { public: using iterator_category = std::forward_iterator_tag; using difference_type = int; - using value_type = Item; + using value_type = ItemT; using pointer = value_type *; using reference = value_type &; Iterator() = default; - Iterator(Item *items, std::size_t count, std::size_t index) + Iterator(ItemT *items, std::size_t count, std::size_t index) : items_(items) , count_(count) , index_(index) @@ -85,12 +90,12 @@ class ItemsContainerRange { } } - Item *items_ = nullptr; + ItemT *items_ = nullptr; std::size_t count_ = 0; std::size_t index_ = 0; }; - ItemsContainerRange(Item *items, std::size_t count) + ItemsContainerRange(ItemT *items, std::size_t count) : items_(items) , count_(count) { @@ -107,26 +112,30 @@ class ItemsContainerRange { } private: - Item *items_; + ItemT *items_; std::size_t count_; }; /** * @brief A range over non-empty items in a list of containers. */ +template class ItemsContainerListRange { + static_assert(std::is_same_v || std::is_same_v, + "The template argument must be `Item` or `const Item`"); + public: class Iterator { public: using iterator_category = std::forward_iterator_tag; using difference_type = int; - using value_type = Item; + using value_type = ItemT; using pointer = value_type *; using reference = value_type &; Iterator() = default; - explicit Iterator(std::vector iterators) + explicit Iterator(std::vector::Iterator> iterators) : iterators_(std::move(iterators)) { advancePastEmpty(); @@ -173,7 +182,7 @@ class ItemsContainerListRange { } } - std::vector iterators_; + std::vector::Iterator> iterators_; std::size_t current_ = 0; }; }; @@ -181,21 +190,27 @@ class ItemsContainerListRange { /** * @brief A range over equipped player items. */ +template class EquippedPlayerItemsRange { + static_assert(std::is_same_v || std::is_same_v, + "The template argument must be `Player` or `const Player`"); + using ItemT = std::conditional_t, const Item, Item>; + using Iterator = typename ItemsContainerRange::Iterator; + public: - explicit EquippedPlayerItemsRange(Player &player) + explicit EquippedPlayerItemsRange(PlayerT &player) : player_(&player) { } - [[nodiscard]] ItemsContainerRange::Iterator begin() const + [[nodiscard]] Iterator begin() const { - return ItemsContainerRange::Iterator { &player_->InvBody[0], containerSize(), 0 }; + return Iterator { &player_->InvBody[0], containerSize(), 0 }; } - [[nodiscard]] ItemsContainerRange::Iterator end() const + [[nodiscard]] Iterator end() const { - return ItemsContainerRange::Iterator { nullptr, containerSize(), containerSize() }; + return Iterator { nullptr, containerSize(), containerSize() }; } private: @@ -204,27 +219,33 @@ class EquippedPlayerItemsRange { return sizeof(player_->InvBody) / sizeof(player_->InvBody[0]); } - Player *player_; + PlayerT *player_; }; /** * @brief A range over non-equipped inventory player items. */ +template class InventoryPlayerItemsRange { + static_assert(std::is_same_v || std::is_same_v, + "The template argument must be `Player` or `const Player`"); + using ItemT = std::conditional_t, const Item, Item>; + using Iterator = typename ItemsContainerRange::Iterator; + public: - explicit InventoryPlayerItemsRange(Player &player) + explicit InventoryPlayerItemsRange(PlayerT &player) : player_(&player) { } - [[nodiscard]] ItemsContainerRange::Iterator begin() const + [[nodiscard]] Iterator begin() const { - return ItemsContainerRange::Iterator { &player_->InvList[0], containerSize(), 0 }; + return Iterator { &player_->InvList[0], containerSize(), 0 }; } - [[nodiscard]] ItemsContainerRange::Iterator end() const + [[nodiscard]] Iterator end() const { - return ItemsContainerRange::Iterator { nullptr, containerSize(), containerSize() }; + return Iterator { nullptr, containerSize(), containerSize() }; } private: @@ -233,27 +254,33 @@ class InventoryPlayerItemsRange { return static_cast(player_->_pNumInv); } - Player *player_; + PlayerT *player_; }; /** * @brief A range over belt player items. */ +template class BeltPlayerItemsRange { + static_assert(std::is_same_v || std::is_same_v, + "The template argument must be `Player` or `const Player`"); + using ItemT = std::conditional_t, const Item, Item>; + using Iterator = typename ItemsContainerRange::Iterator; + public: - explicit BeltPlayerItemsRange(Player &player) + explicit BeltPlayerItemsRange(PlayerT &player) : player_(&player) { } - [[nodiscard]] ItemsContainerRange::Iterator begin() const + [[nodiscard]] Iterator begin() const { - return ItemsContainerRange::Iterator { &player_->SpdList[0], containerSize(), 0 }; + return Iterator { &player_->SpdList[0], containerSize(), 0 }; } - [[nodiscard]] ItemsContainerRange::Iterator end() const + [[nodiscard]] Iterator end() const { - return ItemsContainerRange::Iterator { nullptr, containerSize(), containerSize() }; + return Iterator { nullptr, containerSize(), containerSize() }; } private: @@ -262,61 +289,73 @@ class BeltPlayerItemsRange { return sizeof(player_->SpdList) / sizeof(player_->SpdList[0]); } - Player *player_; + PlayerT *player_; }; /** * @brief A range over non-equipped player items in the following order: Inventory, Belt. */ +template class InventoryAndBeltPlayerItemsRange { + static_assert(std::is_same_v || std::is_same_v, + "The template argument must be `Player` or `const Player`"); + using ItemT = std::conditional_t, const Item, Item>; + using Iterator = typename ItemsContainerListRange::Iterator; + public: - explicit InventoryAndBeltPlayerItemsRange(Player &player) + explicit InventoryAndBeltPlayerItemsRange(PlayerT &player) : player_(&player) { } - [[nodiscard]] ItemsContainerListRange::Iterator begin() const + [[nodiscard]] Iterator begin() const { - return ItemsContainerListRange::Iterator({ + return Iterator({ InventoryPlayerItemsRange(*player_).begin(), BeltPlayerItemsRange(*player_).begin(), }); } - [[nodiscard]] ItemsContainerListRange::Iterator end() const + [[nodiscard]] Iterator end() const { - return ItemsContainerListRange::Iterator({ + return Iterator({ InventoryPlayerItemsRange(*player_).end(), BeltPlayerItemsRange(*player_).end(), }); } private: - Player *player_; + PlayerT *player_; }; /** * @brief A range over non-empty player items in the following order: Equipped, Inventory, Belt. */ +template class PlayerItemsRange { + static_assert(std::is_same_v || std::is_same_v, + "The template argument must be `Player` or `const Player`"); + using ItemT = std::conditional_t, const Item, Item>; + using Iterator = typename ItemsContainerListRange::Iterator; + public: - explicit PlayerItemsRange(Player &player) + explicit PlayerItemsRange(PlayerT &player) : player_(&player) { } - [[nodiscard]] ItemsContainerListRange::Iterator begin() const + [[nodiscard]] Iterator begin() const { - return ItemsContainerListRange::Iterator({ + return Iterator({ EquippedPlayerItemsRange(*player_).begin(), InventoryPlayerItemsRange(*player_).begin(), BeltPlayerItemsRange(*player_).begin(), }); } - [[nodiscard]] ItemsContainerListRange::Iterator end() const + [[nodiscard]] Iterator end() const { - return ItemsContainerListRange::Iterator({ + return Iterator({ EquippedPlayerItemsRange(*player_).end(), InventoryPlayerItemsRange(*player_).end(), BeltPlayerItemsRange(*player_).end(), @@ -324,7 +363,7 @@ class PlayerItemsRange { } private: - Player *player_; + PlayerT *player_; }; } // namespace devilution diff --git a/Source/itemdat.cpp b/Source/itemdat.cpp index c34091a038a..11767914fd1 100644 --- a/Source/itemdat.cpp +++ b/Source/itemdat.cpp @@ -5,545 +5,602 @@ */ #include "itemdat.h" -#include "utils/language.h" + +#include +#include + +#include + +#include "data/file.hpp" +#include "data/iterators.hpp" +#include "data/record_reader.hpp" +#include "spelldat.h" +#include "utils/str_cat.hpp" namespace devilution { -string_view ItemTypeToString(ItemType itemType) +/** Contains the data related to each item ID. */ +std::vector AllItemsList; + +/** Contains the data related to each unique item ID. */ +std::vector UniqueItems; + +/** Contains the data related to each item prefix. */ +std::vector ItemPrefixes; + +/** Contains the data related to each item suffix. */ +std::vector ItemSuffixes; + +namespace { + +tl::expected ParseItemDropRate(std::string_view value) { - switch (itemType) { - case ItemType::Misc: - return "Misc"; - case ItemType::Sword: - return "Sword"; - case ItemType::Axe: - return "Axe"; - case ItemType::Bow: - return "Bow"; - case ItemType::Mace: - return "Mace"; - case ItemType::Shield: - return "Shield"; - case ItemType::LightArmor: - return "LightArmor"; - case ItemType::Helm: - return "Helm"; - case ItemType::MediumArmor: - return "MediumArmor"; - case ItemType::HeavyArmor: - return "HeavyArmor"; - case ItemType::Staff: - return "Staff"; - case ItemType::Gold: - return "Gold"; - case ItemType::Ring: - return "Ring"; - case ItemType::Amulet: - return "Amulet"; - case ItemType::None: - return "None"; + if (value == "Never") return IDROP_NEVER; + if (value == "Regular") return IDROP_REGULAR; + if (value == "Double") return IDROP_DOUBLE; + return tl::make_unexpected("Unknown enum value"); +} + +tl::expected ParseItemClass(std::string_view value) +{ + if (value == "None") return ICLASS_NONE; + if (value == "Weapon") return ICLASS_WEAPON; + if (value == "Armor") return ICLASS_ARMOR; + if (value == "Misc") return ICLASS_MISC; + if (value == "Gold") return ICLASS_GOLD; + if (value == "Quest") return ICLASS_QUEST; + return tl::make_unexpected("Unknown enum value"); +} + +tl::expected ParseItemEquipType(std::string_view value) +{ + if (value == "None") return ILOC_NONE; + if (value == "One-handed") return ILOC_ONEHAND; + if (value == "Two-handed") return ILOC_TWOHAND; + if (value == "Armor") return ILOC_ARMOR; + if (value == "Helm") return ILOC_HELM; + if (value == "Ring") return ILOC_RING; + if (value == "Amulet") return ILOC_AMULET; + if (value == "Unequippable") return ILOC_UNEQUIPABLE; + if (value == "Belt") return ILOC_BELT; + return tl::make_unexpected("Unknown enum value"); +} + +tl::expected ParseItemCursorGraphic(std::string_view value) +{ + if (value == "POTION_OF_FULL_MANA") return ICURS_POTION_OF_FULL_MANA; + if (value == "SCROLL_OF") return ICURS_SCROLL_OF; + if (value == "GOLD_SMALL") return ICURS_GOLD_SMALL; + if (value == "GOLD_MEDIUM") return ICURS_GOLD_MEDIUM; + if (value == "GOLD_LARGE") return ICURS_GOLD_LARGE; + if (value == "RING_OF_TRUTH") return ICURS_RING_OF_TRUTH; + if (value == "RING") return ICURS_RING; + if (value == "SPECTRAL_ELIXIR") return ICURS_SPECTRAL_ELIXIR; + if (value == "ARENA_POTION") return ICURS_ARENA_POTION; + if (value == "GOLDEN_ELIXIR") return ICURS_GOLDEN_ELIXIR; + if (value == "EMPYREAN_BAND") return ICURS_EMPYREAN_BAND; + if (value == "EAR_SORCERER") return ICURS_EAR_SORCERER; + if (value == "EAR_WARRIOR") return ICURS_EAR_WARRIOR; + if (value == "EAR_ROGUE") return ICURS_EAR_ROGUE; + if (value == "BLOOD_STONE") return ICURS_BLOOD_STONE; + if (value == "OIL") return ICURS_OIL; + if (value == "ELIXIR_OF_VITALITY") return ICURS_ELIXIR_OF_VITALITY; + if (value == "POTION_OF_HEALING") return ICURS_POTION_OF_HEALING; + if (value == "POTION_OF_FULL_REJUVENATION") return ICURS_POTION_OF_FULL_REJUVENATION; + if (value == "ELIXIR_OF_MAGIC") return ICURS_ELIXIR_OF_MAGIC; + if (value == "POTION_OF_FULL_HEALING") return ICURS_POTION_OF_FULL_HEALING; + if (value == "ELIXIR_OF_DEXTERITY") return ICURS_ELIXIR_OF_DEXTERITY; + if (value == "POTION_OF_REJUVENATION") return ICURS_POTION_OF_REJUVENATION; + if (value == "ELIXIR_OF_STRENGTH") return ICURS_ELIXIR_OF_STRENGTH; + if (value == "POTION_OF_MANA") return ICURS_POTION_OF_MANA; + if (value == "BRAIN") return ICURS_BRAIN; + if (value == "OPTIC_AMULET") return ICURS_OPTIC_AMULET; + if (value == "AMULET") return ICURS_AMULET; + if (value == "DAGGER") return ICURS_DAGGER; + if (value == "BLADE") return ICURS_BLADE; + if (value == "BASTARD_SWORD") return ICURS_BASTARD_SWORD; + if (value == "MACE") return ICURS_MACE; + if (value == "LONG_SWORD") return ICURS_LONG_SWORD; + if (value == "BROAD_SWORD") return ICURS_BROAD_SWORD; + if (value == "FALCHION") return ICURS_FALCHION; + if (value == "MORNING_STAR") return ICURS_MORNING_STAR; + if (value == "SHORT_SWORD") return ICURS_SHORT_SWORD; + if (value == "CLAYMORE") return ICURS_CLAYMORE; + if (value == "CLUB") return ICURS_CLUB; + if (value == "SABRE") return ICURS_SABRE; + if (value == "SPIKED_CLUB") return ICURS_SPIKED_CLUB; + if (value == "SCIMITAR") return ICURS_SCIMITAR; + if (value == "FULL_HELM") return ICURS_FULL_HELM; + if (value == "MAGIC_ROCK") return ICURS_MAGIC_ROCK; + if (value == "THE_UNDEAD_CROWN") return ICURS_THE_UNDEAD_CROWN; + if (value == "HELM") return ICURS_HELM; + if (value == "BUCKLER") return ICURS_BUCKLER; + if (value == "VIEL_OF_STEEL") return ICURS_VIEL_OF_STEEL; + if (value == "BOOK_GREY") return ICURS_BOOK_GREY; + if (value == "BOOK_RED") return ICURS_BOOK_RED; + if (value == "BOOK_BLUE") return ICURS_BOOK_BLUE; + if (value == "BLACK_MUSHROOM") return ICURS_BLACK_MUSHROOM; + if (value == "SKULL_CAP") return ICURS_SKULL_CAP; + if (value == "CAP") return ICURS_CAP; + if (value == "HARLEQUIN_CREST") return ICURS_HARLEQUIN_CREST; + if (value == "CROWN") return ICURS_CROWN; + if (value == "MAP_OF_THE_STARS") return ICURS_MAP_OF_THE_STARS; + if (value == "FUNGAL_TOME") return ICURS_FUNGAL_TOME; + if (value == "GREAT_HELM") return ICURS_GREAT_HELM; + if (value == "BATTLE_AXE") return ICURS_BATTLE_AXE; + if (value == "HUNTERS_BOW") return ICURS_HUNTERS_BOW; + if (value == "FIELD_PLATE") return ICURS_FIELD_PLATE; + if (value == "SMALL_SHIELD") return ICURS_SMALL_SHIELD; + if (value == "CLEAVER") return ICURS_CLEAVER; + if (value == "STUDDED_LEATHER_ARMOR") return ICURS_STUDDED_LEATHER_ARMOR; + if (value == "SHORT_STAFF") return ICURS_SHORT_STAFF; + if (value == "TWO_HANDED_SWORD") return ICURS_TWO_HANDED_SWORD; + if (value == "CHAIN_MAIL") return ICURS_CHAIN_MAIL; + if (value == "SMALL_AXE") return ICURS_SMALL_AXE; + if (value == "KITE_SHIELD") return ICURS_KITE_SHIELD; + if (value == "SCALE_MAIL") return ICURS_SCALE_MAIL; + if (value == "SHORT_BOW") return ICURS_SHORT_BOW; + if (value == "LONG_BATTLE_BOW") return ICURS_LONG_BATTLE_BOW; + if (value == "LONG_WAR_BOW") return ICURS_LONG_WAR_BOW; + if (value == "WAR_HAMMER") return ICURS_WAR_HAMMER; + if (value == "MAUL") return ICURS_MAUL; + if (value == "LONG_STAFF") return ICURS_LONG_STAFF; + if (value == "WAR_STAFF") return ICURS_WAR_STAFF; + if (value == "TAVERN_SIGN") return ICURS_TAVERN_SIGN; + if (value == "HARD_LEATHER_ARMOR") return ICURS_HARD_LEATHER_ARMOR; + if (value == "RAGS") return ICURS_RAGS; + if (value == "QUILTED_ARMOR") return ICURS_QUILTED_ARMOR; + if (value == "FLAIL") return ICURS_FLAIL; + if (value == "TOWER_SHIELD") return ICURS_TOWER_SHIELD; + if (value == "COMPOSITE_BOW") return ICURS_COMPOSITE_BOW; + if (value == "GREAT_SWORD") return ICURS_GREAT_SWORD; + if (value == "LEATHER_ARMOR") return ICURS_LEATHER_ARMOR; + if (value == "SPLINT_MAIL") return ICURS_SPLINT_MAIL; + if (value == "ROBE") return ICURS_ROBE; + if (value == "ANVIL_OF_FURY") return ICURS_ANVIL_OF_FURY; + if (value == "BROAD_AXE") return ICURS_BROAD_AXE; + if (value == "LARGE_AXE") return ICURS_LARGE_AXE; + if (value == "GREAT_AXE") return ICURS_GREAT_AXE; + if (value == "AXE") return ICURS_AXE; + if (value == "LARGE_SHIELD") return ICURS_LARGE_SHIELD; + if (value == "GOTHIC_SHIELD") return ICURS_GOTHIC_SHIELD; + if (value == "CLOAK") return ICURS_CLOAK; + if (value == "CAPE") return ICURS_CAPE; + if (value == "FULL_PLATE_MAIL") return ICURS_FULL_PLATE_MAIL; + if (value == "GOTHIC_PLATE") return ICURS_GOTHIC_PLATE; + if (value == "BREAST_PLATE") return ICURS_BREAST_PLATE; + if (value == "RING_MAIL") return ICURS_RING_MAIL; + if (value == "STAFF_OF_LAZARUS") return ICURS_STAFF_OF_LAZARUS; + if (value == "ARKAINES_VALOR") return ICURS_ARKAINES_VALOR; + if (value == "SHORT_WAR_BOW") return ICURS_SHORT_WAR_BOW; + if (value == "COMPOSITE_STAFF") return ICURS_COMPOSITE_STAFF; + if (value == "SHORT_BATTLE_BOW") return ICURS_SHORT_BATTLE_BOW; + if (value == "GOLD") return ICURS_GOLD; + if (value == "AURIC_AMULET") return ICURS_AURIC_AMULET; + if (value == "RUNE_BOMB") return ICURS_RUNE_BOMB; + if (value == "THEODORE") return ICURS_THEODORE; + if (value == "TORN_NOTE_1") return ICURS_TORN_NOTE_1; + if (value == "TORN_NOTE_2") return ICURS_TORN_NOTE_2; + if (value == "TORN_NOTE_3") return ICURS_TORN_NOTE_3; + if (value == "RECONSTRUCTED_NOTE") return ICURS_RECONSTRUCTED_NOTE; + if (value == "RUNE_OF_FIRE") return ICURS_RUNE_OF_FIRE; + if (value == "GREATER_RUNE_OF_FIRE") return ICURS_GREATER_RUNE_OF_FIRE; + if (value == "RUNE_OF_LIGHTNING") return ICURS_RUNE_OF_LIGHTNING; + if (value == "GREATER_RUNE_OF_LIGHTNING") return ICURS_GREATER_RUNE_OF_LIGHTNING; + if (value == "RUNE_OF_STONE") return ICURS_RUNE_OF_STONE; + if (value == "GREY_SUIT") return ICURS_GREY_SUIT; + if (value == "BROWN_SUIT") return ICURS_BROWN_SUIT; + if (value == "BOVINE") return ICURS_BOVINE; + return tl::make_unexpected("Unknown enum value"); +} + +tl::expected ParseItemType(std::string_view value) +{ + if (value == "Misc") return ItemType::Misc; + if (value == "Sword") return ItemType::Sword; + if (value == "Axe") return ItemType::Axe; + if (value == "Bow") return ItemType::Bow; + if (value == "Mace") return ItemType::Mace; + if (value == "Shield") return ItemType::Shield; + if (value == "LightArmor") return ItemType::LightArmor; + if (value == "Helm") return ItemType::Helm; + if (value == "MediumArmor") return ItemType::MediumArmor; + if (value == "HeavyArmor") return ItemType::HeavyArmor; + if (value == "Staff") return ItemType::Staff; + if (value == "Gold") return ItemType::Gold; + if (value == "Ring") return ItemType::Ring; + if (value == "Amulet") return ItemType::Amulet; + if (value == "None") return ItemType::None; + return tl::make_unexpected("Unknown enum value"); +} + +tl::expected ParseUniqueBaseItem(std::string_view value) +{ + if (value == "NONE") return UITYPE_NONE; + if (value == "SHORTBOW") return UITYPE_SHORTBOW; + if (value == "LONGBOW") return UITYPE_LONGBOW; + if (value == "HUNTBOW") return UITYPE_HUNTBOW; + if (value == "COMPBOW") return UITYPE_COMPBOW; + if (value == "WARBOW") return UITYPE_WARBOW; + if (value == "BATTLEBOW") return UITYPE_BATTLEBOW; + if (value == "DAGGER") return UITYPE_DAGGER; + if (value == "FALCHION") return UITYPE_FALCHION; + if (value == "CLAYMORE") return UITYPE_CLAYMORE; + if (value == "BROADSWR") return UITYPE_BROADSWR; + if (value == "SABRE") return UITYPE_SABRE; + if (value == "SCIMITAR") return UITYPE_SCIMITAR; + if (value == "LONGSWR") return UITYPE_LONGSWR; + if (value == "BASTARDSWR") return UITYPE_BASTARDSWR; + if (value == "TWOHANDSWR") return UITYPE_TWOHANDSWR; + if (value == "GREATSWR") return UITYPE_GREATSWR; + if (value == "CLEAVER") return UITYPE_CLEAVER; + if (value == "LARGEAXE") return UITYPE_LARGEAXE; + if (value == "BROADAXE") return UITYPE_BROADAXE; + if (value == "SMALLAXE") return UITYPE_SMALLAXE; + if (value == "BATTLEAXE") return UITYPE_BATTLEAXE; + if (value == "GREATAXE") return UITYPE_GREATAXE; + if (value == "MACE") return UITYPE_MACE; + if (value == "MORNSTAR") return UITYPE_MORNSTAR; + if (value == "SPIKCLUB") return UITYPE_SPIKCLUB; + if (value == "MAUL") return UITYPE_MAUL; + if (value == "WARHAMMER") return UITYPE_WARHAMMER; + if (value == "FLAIL") return UITYPE_FLAIL; + if (value == "LONGSTAFF") return UITYPE_LONGSTAFF; + if (value == "SHORTSTAFF") return UITYPE_SHORTSTAFF; + if (value == "COMPSTAFF") return UITYPE_COMPSTAFF; + if (value == "QUARSTAFF") return UITYPE_QUARSTAFF; + if (value == "WARSTAFF") return UITYPE_WARSTAFF; + if (value == "SKULLCAP") return UITYPE_SKULLCAP; + if (value == "HELM") return UITYPE_HELM; + if (value == "GREATHELM") return UITYPE_GREATHELM; + if (value == "CROWN") return UITYPE_CROWN; + if (value == "RAGS") return UITYPE_RAGS; + if (value == "STUDARMOR") return UITYPE_STUDARMOR; + if (value == "CLOAK") return UITYPE_CLOAK; + if (value == "ROBE") return UITYPE_ROBE; + if (value == "CHAINMAIL") return UITYPE_CHAINMAIL; + if (value == "LEATHARMOR") return UITYPE_LEATHARMOR; + if (value == "BREASTPLATE") return UITYPE_BREASTPLATE; + if (value == "CAPE") return UITYPE_CAPE; + if (value == "PLATEMAIL") return UITYPE_PLATEMAIL; + if (value == "FULLPLATE") return UITYPE_FULLPLATE; + if (value == "BUCKLER") return UITYPE_BUCKLER; + if (value == "SMALLSHIELD") return UITYPE_SMALLSHIELD; + if (value == "LARGESHIELD") return UITYPE_LARGESHIELD; + if (value == "KITESHIELD") return UITYPE_KITESHIELD; + if (value == "GOTHSHIELD") return UITYPE_GOTHSHIELD; + if (value == "RING") return UITYPE_RING; + if (value == "AMULET") return UITYPE_AMULET; + if (value == "SKCROWN") return UITYPE_SKCROWN; + if (value == "INFRARING") return UITYPE_INFRARING; + if (value == "OPTAMULET") return UITYPE_OPTAMULET; + if (value == "TRING") return UITYPE_TRING; + if (value == "HARCREST") return UITYPE_HARCREST; + if (value == "MAPOFDOOM") return UITYPE_MAPOFDOOM; + if (value == "ELIXIR") return UITYPE_ELIXIR; + if (value == "ARMOFVAL") return UITYPE_ARMOFVAL; + if (value == "STEELVEIL") return UITYPE_STEELVEIL; + if (value == "GRISWOLD") return UITYPE_GRISWOLD; + if (value == "LGTFORGE") return UITYPE_LGTFORGE; + if (value == "LAZSTAFF") return UITYPE_LAZSTAFF; + if (value == "BOVINE") return UITYPE_BOVINE; + if (value == "INVALID") return UITYPE_INVALID; + return tl::make_unexpected("Unknown enum value"); +} + +tl::expected ParseItemSpecialEffect(std::string_view value) +{ + if (value == "RandomStealLife") return ItemSpecialEffect::RandomStealLife; + if (value == "RandomArrowVelocity") return ItemSpecialEffect::RandomArrowVelocity; + if (value == "FireArrows") return ItemSpecialEffect::FireArrows; + if (value == "FireDamage") return ItemSpecialEffect::FireDamage; + if (value == "LightningDamage") return ItemSpecialEffect::LightningDamage; + if (value == "DrainLife") return ItemSpecialEffect::DrainLife; + if (value == "MultipleArrows") return ItemSpecialEffect::MultipleArrows; + if (value == "Knockback") return ItemSpecialEffect::Knockback; + if (value == "StealMana3") return ItemSpecialEffect::StealMana3; + if (value == "StealMana5") return ItemSpecialEffect::StealMana5; + if (value == "StealLife3") return ItemSpecialEffect::StealLife3; + if (value == "StealLife5") return ItemSpecialEffect::StealLife5; + if (value == "QuickAttack") return ItemSpecialEffect::QuickAttack; + if (value == "FastAttack") return ItemSpecialEffect::FastAttack; + if (value == "FasterAttack") return ItemSpecialEffect::FasterAttack; + if (value == "FastestAttack") return ItemSpecialEffect::FastestAttack; + if (value == "FastHitRecovery") return ItemSpecialEffect::FastHitRecovery; + if (value == "FasterHitRecovery") return ItemSpecialEffect::FasterHitRecovery; + if (value == "FastestHitRecovery") return ItemSpecialEffect::FastestHitRecovery; + if (value == "FastBlock") return ItemSpecialEffect::FastBlock; + if (value == "LightningArrows") return ItemSpecialEffect::LightningArrows; + if (value == "Thorns") return ItemSpecialEffect::Thorns; + if (value == "NoMana") return ItemSpecialEffect::NoMana; + if (value == "HalfTrapDamage") return ItemSpecialEffect::HalfTrapDamage; + if (value == "TripleDemonDamage") return ItemSpecialEffect::TripleDemonDamage; + if (value == "ZeroResistance") return ItemSpecialEffect::ZeroResistance; + return tl::make_unexpected("Unknown enum value"); +} + +tl::expected ParseItemMiscId(std::string_view value) +{ + if (value == "NONE") return IMISC_NONE; + if (value == "USEFIRST") return IMISC_USEFIRST; + if (value == "FULLHEAL") return IMISC_FULLHEAL; + if (value == "HEAL") return IMISC_HEAL; + if (value == "MANA") return IMISC_MANA; + if (value == "FULLMANA") return IMISC_FULLMANA; + if (value == "ELIXSTR") return IMISC_ELIXSTR; + if (value == "ELIXMAG") return IMISC_ELIXMAG; + if (value == "ELIXDEX") return IMISC_ELIXDEX; + if (value == "ELIXVIT") return IMISC_ELIXVIT; + if (value == "REJUV") return IMISC_REJUV; + if (value == "FULLREJUV") return IMISC_FULLREJUV; + if (value == "USELAST") return IMISC_USELAST; + if (value == "SCROLL") return IMISC_SCROLL; + if (value == "SCROLLT") return IMISC_SCROLLT; + if (value == "STAFF") return IMISC_STAFF; + if (value == "BOOK") return IMISC_BOOK; + if (value == "RING") return IMISC_RING; + if (value == "AMULET") return IMISC_AMULET; + if (value == "UNIQUE") return IMISC_UNIQUE; + if (value == "OILFIRST") return IMISC_OILFIRST; + if (value == "OILOF") return IMISC_OILOF; + if (value == "OILACC") return IMISC_OILACC; + if (value == "OILMAST") return IMISC_OILMAST; + if (value == "OILSHARP") return IMISC_OILSHARP; + if (value == "OILDEATH") return IMISC_OILDEATH; + if (value == "OILSKILL") return IMISC_OILSKILL; + if (value == "OILBSMTH") return IMISC_OILBSMTH; + if (value == "OILFORT") return IMISC_OILFORT; + if (value == "OILPERM") return IMISC_OILPERM; + if (value == "OILHARD") return IMISC_OILHARD; + if (value == "OILIMP") return IMISC_OILIMP; + if (value == "OILLAST") return IMISC_OILLAST; + if (value == "MAPOFDOOM") return IMISC_MAPOFDOOM; + if (value == "EAR") return IMISC_EAR; + if (value == "SPECELIX") return IMISC_SPECELIX; + if (value == "RUNEFIRST") return IMISC_RUNEFIRST; + if (value == "RUNEF") return IMISC_RUNEF; + if (value == "RUNEL") return IMISC_RUNEL; + if (value == "GR_RUNEL") return IMISC_GR_RUNEL; + if (value == "GR_RUNEF") return IMISC_GR_RUNEF; + if (value == "RUNES") return IMISC_RUNES; + if (value == "RUNELAST") return IMISC_RUNELAST; + if (value == "AURIC") return IMISC_AURIC; + if (value == "NOTE") return IMISC_NOTE; + if (value == "ARENAPOT") return IMISC_ARENAPOT; + return tl::make_unexpected("Unknown enum value"); +} + +tl::expected ParseItemEffectType(std::string_view value) +{ + if (value == "TOHIT") return IPL_TOHIT; + if (value == "TOHIT_CURSE") return IPL_TOHIT_CURSE; + if (value == "DAMP") return IPL_DAMP; + if (value == "DAMP_CURSE") return IPL_DAMP_CURSE; + if (value == "TOHIT_DAMP") return IPL_TOHIT_DAMP; + if (value == "TOHIT_DAMP_CURSE") return IPL_TOHIT_DAMP_CURSE; + if (value == "ACP") return IPL_ACP; + if (value == "ACP_CURSE") return IPL_ACP_CURSE; + if (value == "FIRERES") return IPL_FIRERES; + if (value == "LIGHTRES") return IPL_LIGHTRES; + if (value == "MAGICRES") return IPL_MAGICRES; + if (value == "ALLRES") return IPL_ALLRES; + if (value == "SPLLVLADD") return IPL_SPLLVLADD; + if (value == "CHARGES") return IPL_CHARGES; + if (value == "FIREDAM") return IPL_FIREDAM; + if (value == "LIGHTDAM") return IPL_LIGHTDAM; + if (value == "STR") return IPL_STR; + if (value == "STR_CURSE") return IPL_STR_CURSE; + if (value == "MAG") return IPL_MAG; + if (value == "MAG_CURSE") return IPL_MAG_CURSE; + if (value == "DEX") return IPL_DEX; + if (value == "DEX_CURSE") return IPL_DEX_CURSE; + if (value == "VIT") return IPL_VIT; + if (value == "VIT_CURSE") return IPL_VIT_CURSE; + if (value == "ATTRIBS") return IPL_ATTRIBS; + if (value == "ATTRIBS_CURSE") return IPL_ATTRIBS_CURSE; + if (value == "GETHIT_CURSE") return IPL_GETHIT_CURSE; + if (value == "GETHIT") return IPL_GETHIT; + if (value == "LIFE") return IPL_LIFE; + if (value == "LIFE_CURSE") return IPL_LIFE_CURSE; + if (value == "MANA") return IPL_MANA; + if (value == "MANA_CURSE") return IPL_MANA_CURSE; + if (value == "DUR") return IPL_DUR; + if (value == "DUR_CURSE") return IPL_DUR_CURSE; + if (value == "INDESTRUCTIBLE") return IPL_INDESTRUCTIBLE; + if (value == "LIGHT") return IPL_LIGHT; + if (value == "LIGHT_CURSE") return IPL_LIGHT_CURSE; + if (value == "MULT_ARROWS") return IPL_MULT_ARROWS; + if (value == "FIRE_ARROWS") return IPL_FIRE_ARROWS; + if (value == "LIGHT_ARROWS") return IPL_LIGHT_ARROWS; + if (value == "INVCURS") return IPL_INVCURS; + if (value == "THORNS") return IPL_THORNS; + if (value == "NOMANA") return IPL_NOMANA; + if (value == "FIREBALL") return IPL_FIREBALL; + if (value == "ABSHALFTRAP") return IPL_ABSHALFTRAP; + if (value == "KNOCKBACK") return IPL_KNOCKBACK; + if (value == "STEALMANA") return IPL_STEALMANA; + if (value == "STEALLIFE") return IPL_STEALLIFE; + if (value == "TARGAC") return IPL_TARGAC; + if (value == "FASTATTACK") return IPL_FASTATTACK; + if (value == "FASTRECOVER") return IPL_FASTRECOVER; + if (value == "FASTBLOCK") return IPL_FASTBLOCK; + if (value == "DAMMOD") return IPL_DAMMOD; + if (value == "RNDARROWVEL") return IPL_RNDARROWVEL; + if (value == "SETDAM") return IPL_SETDAM; + if (value == "SETDUR") return IPL_SETDUR; + if (value == "NOMINSTR") return IPL_NOMINSTR; + if (value == "SPELL") return IPL_SPELL; + if (value == "ONEHAND") return IPL_ONEHAND; + if (value == "3XDAMVDEM") return IPL_3XDAMVDEM; + if (value == "ALLRESZERO") return IPL_ALLRESZERO; + if (value == "DRAINLIFE") return IPL_DRAINLIFE; + if (value == "RNDSTEALLIFE") return IPL_RNDSTEALLIFE; + if (value == "SETAC") return IPL_SETAC; + if (value == "ADDACLIFE") return IPL_ADDACLIFE; + if (value == "ADDMANAAC") return IPL_ADDMANAAC; + if (value == "AC_CURSE") return IPL_AC_CURSE; + if (value == "LASTDIABLO") return IPL_LASTDIABLO; + if (value == "FIRERES_CURSE") return IPL_FIRERES_CURSE; + if (value == "LIGHTRES_CURSE") return IPL_LIGHTRES_CURSE; + if (value == "MAGICRES_CURSE") return IPL_MAGICRES_CURSE; + if (value == "DEVASTATION") return IPL_DEVASTATION; + if (value == "DECAY") return IPL_DECAY; + if (value == "PERIL") return IPL_PERIL; + if (value == "JESTERS") return IPL_JESTERS; + if (value == "CRYSTALLINE") return IPL_CRYSTALLINE; + if (value == "DOPPELGANGER") return IPL_DOPPELGANGER; + if (value == "ACDEMON") return IPL_ACDEMON; + if (value == "ACUNDEAD") return IPL_ACUNDEAD; + if (value == "MANATOLIFE") return IPL_MANATOLIFE; + if (value == "LIFETOMANA") return IPL_LIFETOMANA; + return tl::make_unexpected("Unknown enum value"); +} + +tl::expected ParseAffixItemType(std::string_view value) +{ + if (value == "Misc") return AffixItemType::Misc; + if (value == "Bow") return AffixItemType::Bow; + if (value == "Staff") return AffixItemType::Staff; + if (value == "Weapon") return AffixItemType::Weapon; + if (value == "Shield") return AffixItemType::Shield; + if (value == "Armor") return AffixItemType::Armor; + return tl::make_unexpected("Unknown enum value"); +} + +tl::expected ParseAffixAlignment(std::string_view value) +{ + if (value == "Any") return GOE_ANY; + if (value == "Evil") return GOE_EVIL; + if (value == "Good") return GOE_GOOD; + return tl::make_unexpected("Unknown enum value"); +} + +void LoadItemDat() +{ + const std::string_view filename = "txtdata\\items\\itemdat.tsv"; + DataFile dataFile = DataFile::loadOrDie(filename); + dataFile.skipHeaderOrDie(filename); + + AllItemsList.clear(); + AllItemsList.reserve(dataFile.numRecords()); + for (DataFileRecord record : dataFile) { + RecordReader reader { record, filename }; + ItemData &item = AllItemsList.emplace_back(); + reader.advance(); // Skip the first column (item ID). + reader.read("dropRate", item.iRnd, ParseItemDropRate); + reader.read("class", item.iClass, ParseItemClass); + reader.read("equipType", item.iLoc, ParseItemEquipType); + reader.read("cursorGraphic", item.iCurs, ParseItemCursorGraphic); + reader.read("itemType", item.itype, ParseItemType); + reader.read("uniqueBaseItem", item.iItemId, ParseUniqueBaseItem); + reader.readString("name", item.iName); + reader.readString("shortName", item.iSName); + reader.readInt("minMonsterLevel", item.iMinMLvl); + reader.readInt("durability", item.iDurability); + reader.readInt("minDamage", item.iMinDam); + reader.readInt("maxDamage", item.iMaxDam); + reader.readInt("minArmor", item.iMinAC); + reader.readInt("maxArmor", item.iMaxAC); + reader.readInt("minStrength", item.iMinStr); + reader.readInt("minMagic", item.iMinMag); + reader.readInt("minDexterity", item.iMinDex); + reader.readEnumList("specialEffects", item.iFlags, ParseItemSpecialEffect); + reader.read("miscId", item.iMiscId, ParseItemMiscId); + reader.read("spell", item.iSpell, ParseSpellId); + reader.readBool("usable", item.iUsable); + reader.readInt("value", item.iValue); } - return ""; + AllItemsList.shrink_to_fit(); } -/** Contains the data related to each item ID. */ -const ItemData AllItemsList[] = { - // clang-format off -// _item_indexes iRnd, iClass, iLoc, iCurs, itype, iItemId, iName, iSName, iMinMLvl, iDurability, iMinDam, iMaxDam, iMinAC, iMaxAC, iMinStr, iMinMag, iMinDex, iFlags, iMiscId, iSpell, iUsable, iValue -/*IDI_GOLD */ { IDROP_REGULAR, ICLASS_GOLD, ILOC_UNEQUIPABLE, ICURS_GOLD, ItemType::Gold, UITYPE_NONE, N_("Gold"), nullptr, 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, true, 0 }, -/*IDI_WARRIOR */ { IDROP_NEVER, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_SHORT_SWORD, ItemType::Sword, UITYPE_NONE, N_("Short Sword"), nullptr, 2, 20, 2, 6, 0, 0, 18, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 50 }, -/*IDI_WARRSHLD */ { IDROP_NEVER, ICLASS_ARMOR, ILOC_ONEHAND, ICURS_BUCKLER, ItemType::Shield, UITYPE_NONE, N_("Buckler"), nullptr, 2, 10, 0, 0, 3, 3, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 50 }, -/*IDI_WARRCLUB */ { IDROP_NEVER, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_CLUB, ItemType::Mace, UITYPE_SPIKCLUB, N_("Club"), nullptr, 1, 20, 1, 6, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 20 }, -/*IDI_ROGUE */ { IDROP_NEVER, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_SHORT_BOW, ItemType::Bow, UITYPE_NONE, N_("Short Bow"), nullptr, 1, 30, 1, 4, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 100 }, -/*IDI_SORCERER */ { IDROP_NEVER, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_SHORT_STAFF, ItemType::Staff, UITYPE_NONE, N_("Short Staff of Mana"), nullptr, 1, 25, 2, 4, 0, 0, 0, 20, 0, ItemSpecialEffect::None, IMISC_STAFF, SpellID::Mana, false, 520 }, -/*IDI_CLEAVER */ { IDROP_NEVER, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_CLEAVER, ItemType::Axe, UITYPE_CLEAVER, N_("Cleaver"), nullptr, 10, 10, 4, 24, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_UNIQUE, SpellID::Null, false, 2000 }, -/*IDI_SKCROWN */ { IDROP_NEVER, ICLASS_ARMOR, ILOC_HELM, ICURS_THE_UNDEAD_CROWN, ItemType::Helm, UITYPE_SKCROWN, N_("The Undead Crown"), nullptr, 0, 50, 0, 0, 15, 15, 0, 0, 0, ItemSpecialEffect::RandomStealLife, IMISC_UNIQUE, SpellID::Null, false, 10000 }, -/*IDI_INFRARING */ { IDROP_NEVER, ICLASS_MISC, ILOC_RING, ICURS_EMPYREAN_BAND, ItemType::Ring, UITYPE_INFRARING, N_("Empyrean Band"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_UNIQUE, SpellID::Null, false, 8000 }, -/*IDI_ROCK */ { IDROP_NEVER, ICLASS_QUEST, ILOC_UNEQUIPABLE, ICURS_MAGIC_ROCK, ItemType::Misc, UITYPE_NONE, N_("Magic Rock"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, -/*IDI_OPTAMULET */ { IDROP_NEVER, ICLASS_MISC, ILOC_AMULET, ICURS_OPTIC_AMULET, ItemType::Amulet, UITYPE_OPTAMULET, N_("Optic Amulet"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_UNIQUE, SpellID::Null, false, 5000 }, -/*IDI_TRING */ { IDROP_NEVER, ICLASS_MISC, ILOC_RING, ICURS_RING_OF_TRUTH, ItemType::Ring, UITYPE_TRING, N_("Ring of Truth"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_UNIQUE, SpellID::Null, false, 1000 }, -/*IDI_BANNER */ { IDROP_NEVER, ICLASS_QUEST, ILOC_UNEQUIPABLE, ICURS_TAVERN_SIGN, ItemType::Misc, UITYPE_NONE, N_("Tavern Sign"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, -/*IDI_HARCREST */ { IDROP_NEVER, ICLASS_ARMOR, ILOC_HELM, ICURS_HARLEQUIN_CREST, ItemType::Helm, UITYPE_HARCREST, N_("Harlequin Crest"), nullptr, 0, 15, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_UNIQUE, SpellID::Null, false, 15 }, -/*IDI_STEELVEIL */ { IDROP_NEVER, ICLASS_ARMOR, ILOC_HELM, ICURS_VIEL_OF_STEEL, ItemType::Helm, UITYPE_STEELVEIL, N_("Veil of Steel"), nullptr, 0, 60, 0, 0, 18, 18, 0, 0, 0, ItemSpecialEffect::None, IMISC_UNIQUE, SpellID::Null, false, 0 }, -/*IDI_GLDNELIX */ { IDROP_NEVER, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_GOLDEN_ELIXIR, ItemType::Misc, UITYPE_ELIXIR, N_("Golden Elixir"), nullptr, 15, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, -/*IDI_ANVIL */ { IDROP_NEVER, ICLASS_QUEST, ILOC_UNEQUIPABLE, ICURS_ANVIL_OF_FURY, ItemType::Misc, UITYPE_NONE, N_("Anvil of Fury"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, -/*IDI_MUSHROOM */ { IDROP_NEVER, ICLASS_QUEST, ILOC_UNEQUIPABLE, ICURS_BLACK_MUSHROOM, ItemType::Misc, UITYPE_NONE, N_("Black Mushroom"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, -/*IDI_BRAIN */ { IDROP_NEVER, ICLASS_QUEST, ILOC_UNEQUIPABLE, ICURS_BRAIN, ItemType::Misc, UITYPE_NONE, N_("Brain"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, -/*IDI_FUNGALTM */ { IDROP_NEVER, ICLASS_QUEST, ILOC_UNEQUIPABLE, ICURS_FUNGAL_TOME, ItemType::Misc, UITYPE_NONE, N_("Fungal Tome"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, -/*IDI_SPECELIX */ { IDROP_NEVER, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SPECTRAL_ELIXIR, ItemType::Misc, UITYPE_ELIXIR, N_("Spectral Elixir"), nullptr, 15, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_SPECELIX, SpellID::Null, true, 0 }, -/*IDI_BLDSTONE */ { IDROP_NEVER, ICLASS_QUEST, ILOC_UNEQUIPABLE, ICURS_BLOOD_STONE, ItemType::Misc, UITYPE_NONE, N_("Blood Stone"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, -/*IDI_MAPOFDOOM */ { IDROP_NEVER, ICLASS_QUEST, ILOC_UNEQUIPABLE, ICURS_MAP_OF_THE_STARS, ItemType::Misc, UITYPE_MAPOFDOOM, N_("Cathedral Map"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_MAPOFDOOM, SpellID::Null, true, 0 }, -/*IDI_EAR */ { IDROP_NEVER, ICLASS_QUEST, ILOC_UNEQUIPABLE, ICURS_EAR_SORCERER, ItemType::Misc, UITYPE_NONE, N_("Heart"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_EAR, SpellID::Null, false, 0 }, -/*IDI_HEAL */ { IDROP_NEVER, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_POTION_OF_HEALING, ItemType::Misc, UITYPE_NONE, N_("Potion of Healing"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_HEAL, SpellID::Null, true, 50 }, -/*IDI_MANA */ { IDROP_NEVER, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_POTION_OF_MANA, ItemType::Misc, UITYPE_NONE, N_("Potion of Mana"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_MANA, SpellID::Null, true, 50 }, -/*IDI_IDENTIFY */ { IDROP_NEVER, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Identify"), nullptr, 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_SCROLL, SpellID::Identify, true, 200 }, -/*IDI_PORTAL */ { IDROP_NEVER, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Town Portal"), nullptr, 4, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_SCROLL, SpellID::TownPortal, true, 200 }, -/*IDI_ARMOFVAL */ { IDROP_NEVER, ICLASS_ARMOR, ILOC_ARMOR, ICURS_ARKAINES_VALOR, ItemType::MediumArmor, UITYPE_ARMOFVAL, N_("Arkaine's Valor"), nullptr, 0, 40, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_UNIQUE, SpellID::Null, false, 0 }, -/*IDI_FULLHEAL */ { IDROP_NEVER, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_POTION_OF_FULL_HEALING, ItemType::Misc, UITYPE_NONE, N_("Potion of Full Healing"), nullptr, 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_FULLHEAL, SpellID::Null, true, 150 }, -/*IDI_FULLMANA */ { IDROP_NEVER, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_POTION_OF_FULL_MANA, ItemType::Misc, UITYPE_NONE, N_("Potion of Full Mana"), nullptr, 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_FULLMANA, SpellID::Null, true, 150 }, -/*IDI_GRISWOLD */ { IDROP_NEVER, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_BROAD_SWORD, ItemType::Sword, UITYPE_GRISWOLD, N_("Griswold's Edge"), nullptr, 8, 50, 4, 12, 0, 0, 40, 0, 0, ItemSpecialEffect::None, IMISC_UNIQUE, SpellID::Null, false, 750 }, -/*IDI_LGTFORGE */ { IDROP_NEVER, ICLASS_ARMOR, ILOC_ARMOR, ICURS_BOVINE, ItemType::HeavyArmor, UITYPE_BOVINE, N_("Bovine Plate"), nullptr, 0, 40, 0, 0, 0, 0, 50, 0, 0, ItemSpecialEffect::None, IMISC_UNIQUE, SpellID::Null, false, 0 }, -/*IDI_LAZSTAFF */ { IDROP_NEVER, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_STAFF_OF_LAZARUS, ItemType::Misc, UITYPE_LAZSTAFF, N_("Staff of Lazarus"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, -/*IDI_RESURRECT */ { IDROP_NEVER, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Resurrect"), nullptr, 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_SCROLLT, SpellID::Resurrect, true, 250 }, -/*IDI_OIL */ { IDROP_NEVER, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_OIL, ItemType::Misc, UITYPE_NONE, N_("Blacksmith Oil"), nullptr, 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_OILBSMTH, SpellID::Null, true, 100 }, -/*IDI_SHORTSTAFF */ { IDROP_NEVER, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_SHORT_STAFF, ItemType::Staff, UITYPE_NONE, N_("Short Staff"), nullptr, 1, 25, 2, 4, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 20 }, -/*IDI_BARDSWORD */ { IDROP_NEVER, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_SHORT_SWORD, ItemType::Sword, UITYPE_NONE, N_("Sword"), nullptr, 2, 8, 1, 5, 0, 0, 15, 0, 20, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 20 }, -/*IDI_BARDDAGGER */ { IDROP_NEVER, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_DAGGER, ItemType::Sword, UITYPE_NONE, N_("Dagger"), nullptr, 1, 16, 1, 4, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 20 }, -/*IDI_RUNEBOMB */ { IDROP_NEVER, ICLASS_QUEST, ILOC_UNEQUIPABLE, ICURS_RUNE_BOMB, ItemType::Misc, UITYPE_NONE, N_("Rune Bomb"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, -/*IDI_THEODORE */ { IDROP_NEVER, ICLASS_QUEST, ILOC_UNEQUIPABLE, ICURS_THEODORE, ItemType::Misc, UITYPE_NONE, N_("Theodore"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, -/*IDI_AURIC */ { IDROP_NEVER, ICLASS_MISC, ILOC_AMULET, ICURS_AURIC_AMULET, ItemType::Misc, UITYPE_NONE, N_("Auric Amulet"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_AURIC, SpellID::Null, false, 100 }, -/*IDI_NOTE1 */ { IDROP_NEVER, ICLASS_QUEST, ILOC_UNEQUIPABLE, ICURS_TORN_NOTE_1, ItemType::Misc, UITYPE_NONE, N_("Torn Note 1"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, -/*IDI_NOTE2 */ { IDROP_NEVER, ICLASS_QUEST, ILOC_UNEQUIPABLE, ICURS_TORN_NOTE_2, ItemType::Misc, UITYPE_NONE, N_("Torn Note 2"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, -/*IDI_NOTE3 */ { IDROP_NEVER, ICLASS_QUEST, ILOC_UNEQUIPABLE, ICURS_TORN_NOTE_3, ItemType::Misc, UITYPE_NONE, N_("Torn Note 3"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, -/*IDI_FULLNOTE */ { IDROP_NEVER, ICLASS_QUEST, ILOC_UNEQUIPABLE, ICURS_RECONSTRUCTED_NOTE, ItemType::Misc, UITYPE_NONE, N_("Reconstructed Note"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NOTE, SpellID::Null, true, 0 }, -/*IDI_BROWNSUIT */ { IDROP_NEVER, ICLASS_QUEST, ILOC_UNEQUIPABLE, ICURS_BROWN_SUIT, ItemType::Misc, UITYPE_NONE, N_("Brown Suit"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, -/*IDI_GREYSUIT */ { IDROP_NEVER, ICLASS_QUEST, ILOC_UNEQUIPABLE, ICURS_GREY_SUIT, ItemType::Misc, UITYPE_NONE, N_("Grey Suit"), nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_HELM, ICURS_CAP, ItemType::Helm, UITYPE_NONE, N_("Cap"), N_("Cap"), 1, 15, 0, 0, 1, 3, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 15 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_HELM, ICURS_SKULL_CAP, ItemType::Helm, UITYPE_SKULLCAP, N_("Skull Cap"), N_("Cap"), 4, 20, 0, 0, 2, 4, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 25 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_HELM, ICURS_HELM, ItemType::Helm, UITYPE_HELM, N_("Helm"), N_("Helm"), 8, 30, 0, 0, 4, 6, 25, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 40 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_HELM, ICURS_FULL_HELM, ItemType::Helm, UITYPE_NONE, N_("Full Helm"), N_("Helm"), 12, 35, 0, 0, 6, 8, 35, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 90 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_HELM, ICURS_CROWN, ItemType::Helm, UITYPE_CROWN, N_("Crown"), N_("Crown"), 16, 40, 0, 0, 8, 12, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 200 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_HELM, ICURS_GREAT_HELM, ItemType::Helm, UITYPE_GREATHELM, N_("Great Helm"), N_("Helm"), 20, 60, 0, 0, 10, 15, 50, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 400 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ARMOR, ICURS_CAPE, ItemType::LightArmor, UITYPE_CAPE, N_("Cape"), N_("Cape"), 1, 12, 0, 0, 1, 5, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 10 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ARMOR, ICURS_RAGS, ItemType::LightArmor, UITYPE_RAGS, N_("Rags"), N_("Rags"), 1, 6, 0, 0, 2, 6, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 5 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ARMOR, ICURS_CLOAK, ItemType::LightArmor, UITYPE_CLOAK, N_("Cloak"), N_("Cloak"), 2, 18, 0, 0, 3, 7, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 40 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ARMOR, ICURS_ROBE, ItemType::LightArmor, UITYPE_ROBE, N_("Robe"), N_("Robe"), 3, 24, 0, 0, 4, 7, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 75 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ARMOR, ICURS_QUILTED_ARMOR, ItemType::LightArmor, UITYPE_NONE, N_("Quilted Armor"), N_("Armor"), 4, 30, 0, 0, 7, 10, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 200 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ARMOR, ICURS_LEATHER_ARMOR, ItemType::LightArmor, UITYPE_LEATHARMOR, N_("Leather Armor"), N_("Armor"), 6, 35, 0, 0, 10, 13, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 300 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ARMOR, ICURS_HARD_LEATHER_ARMOR, ItemType::LightArmor, UITYPE_NONE, N_("Hard Leather Armor"), N_("Armor"), 7, 40, 0, 0, 11, 14, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 450 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ARMOR, ICURS_STUDDED_LEATHER_ARMOR, ItemType::LightArmor, UITYPE_STUDARMOR, N_("Studded Leather Armor"), N_("Armor"), 9, 45, 0, 0, 15, 17, 20, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 700 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ARMOR, ICURS_RING_MAIL, ItemType::MediumArmor, UITYPE_NONE, N_("Ring Mail"), N_("Mail"), 11, 50, 0, 0, 17, 20, 25, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 900 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ARMOR, ICURS_CHAIN_MAIL, ItemType::MediumArmor, UITYPE_CHAINMAIL, N_("Chain Mail"), N_("Mail"), 13, 55, 0, 0, 18, 22, 30, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 1250 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ARMOR, ICURS_SCALE_MAIL, ItemType::MediumArmor, UITYPE_NONE, N_("Scale Mail"), N_("Mail"), 15, 60, 0, 0, 23, 28, 35, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 2300 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ARMOR, ICURS_BREAST_PLATE, ItemType::HeavyArmor, UITYPE_BREASTPLATE, N_("Breast Plate"), N_("Plate"), 16, 80, 0, 0, 20, 24, 40, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 2800 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ARMOR, ICURS_SPLINT_MAIL, ItemType::MediumArmor, UITYPE_NONE, N_("Splint Mail"), N_("Mail"), 17, 65, 0, 0, 30, 35, 40, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 3250 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ARMOR, ICURS_FIELD_PLATE, ItemType::HeavyArmor, UITYPE_PLATEMAIL, N_("Plate Mail"), N_("Plate"), 19, 75, 0, 0, 42, 50, 60, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 4600 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ARMOR, ICURS_FIELD_PLATE, ItemType::HeavyArmor, UITYPE_NONE, N_("Field Plate"), N_("Plate"), 21, 80, 0, 0, 40, 45, 65, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 5800 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ARMOR, ICURS_GOTHIC_PLATE, ItemType::HeavyArmor, UITYPE_NONE, N_("Gothic Plate"), N_("Plate"), 23, 100, 0, 0, 50, 60, 80, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 8000 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ARMOR, ICURS_FULL_PLATE_MAIL, ItemType::HeavyArmor, UITYPE_FULLPLATE, N_("Full Plate Mail"), N_("Plate"), 25, 90, 0, 0, 60, 75, 90, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 6500 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ONEHAND, ICURS_BUCKLER, ItemType::Shield, UITYPE_BUCKLER, N_("Buckler"), N_("Shield"), 1, 16, 0, 0, 1, 5, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 30 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ONEHAND, ICURS_SMALL_SHIELD, ItemType::Shield, UITYPE_SMALLSHIELD, N_("Small Shield"), N_("Shield"), 5, 24, 0, 0, 3, 8, 25, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 90 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ONEHAND, ICURS_LARGE_SHIELD, ItemType::Shield, UITYPE_LARGESHIELD, N_("Large Shield"), N_("Shield"), 9, 32, 0, 0, 5, 10, 40, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 200 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ONEHAND, ICURS_KITE_SHIELD, ItemType::Shield, UITYPE_KITESHIELD, N_("Kite Shield"), N_("Shield"), 14, 40, 0, 0, 8, 15, 50, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 400 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ONEHAND, ICURS_TOWER_SHIELD, ItemType::Shield, UITYPE_GOTHSHIELD, N_("Tower Shield"), N_("Shield"), 20, 50, 0, 0, 12, 20, 60, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 850 }, -/* */ { IDROP_REGULAR, ICLASS_ARMOR, ILOC_ONEHAND, ICURS_GOTHIC_SHIELD, ItemType::Shield, UITYPE_GOTHSHIELD, N_("Gothic Shield"), N_("Shield"), 23, 60, 0, 0, 14, 18, 80, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 2300 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_POTION_OF_HEALING, ItemType::Misc, UITYPE_NONE, N_("Potion of Healing"), nullptr, 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_HEAL, SpellID::Null, true, 50 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_POTION_OF_FULL_HEALING, ItemType::Misc, UITYPE_NONE, N_("Potion of Full Healing"), nullptr, 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_FULLHEAL, SpellID::Null, true, 150 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_POTION_OF_MANA, ItemType::Misc, UITYPE_NONE, N_("Potion of Mana"), nullptr, 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_MANA, SpellID::Null, true, 50 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_POTION_OF_FULL_MANA, ItemType::Misc, UITYPE_NONE, N_("Potion of Full Mana"), nullptr, 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_FULLMANA, SpellID::Null, true, 150 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_POTION_OF_REJUVENATION, ItemType::Misc, UITYPE_NONE, N_("Potion of Rejuvenation"), nullptr, 3, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_REJUV, SpellID::Null, true, 120 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_POTION_OF_FULL_REJUVENATION, ItemType::Misc, UITYPE_NONE, N_("Potion of Full Rejuvenation"), nullptr, 7, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_FULLREJUV, SpellID::Null, true, 600 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_OIL, ItemType::Misc, UITYPE_NONE, N_("Blacksmith Oil"), nullptr, 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_OILBSMTH, SpellID::Null, true, 100 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_OIL, ItemType::Misc, UITYPE_NONE, N_("Oil of Accuracy"), nullptr, 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_OILACC, SpellID::Null, true, 500 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_OIL, ItemType::Misc, UITYPE_NONE, N_("Oil of Sharpness"), nullptr, 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_OILSHARP, SpellID::Null, true, 500 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_OIL, ItemType::Misc, UITYPE_NONE, N_("Oil"), nullptr, 10, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_OILOF, SpellID::Null, true, 0 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_ELIXIR_OF_STRENGTH, ItemType::Misc, UITYPE_NONE, N_("Elixir of Strength"), nullptr, 15, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_ELIXSTR, SpellID::Null, true, 5000 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_ELIXIR_OF_MAGIC, ItemType::Misc, UITYPE_NONE, N_("Elixir of Magic"), nullptr, 15, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_ELIXMAG, SpellID::Null, true, 5000 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_ELIXIR_OF_DEXTERITY, ItemType::Misc, UITYPE_NONE, N_("Elixir of Dexterity"), nullptr, 15, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_ELIXDEX, SpellID::Null, true, 5000 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_ELIXIR_OF_VITALITY, ItemType::Misc, UITYPE_NONE, N_("Elixir of Vitality"), nullptr, 20, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_ELIXVIT, SpellID::Null, true, 5000 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Healing"), nullptr, 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_SCROLL, SpellID::Healing, true, 50 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Search"), nullptr, 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_SCROLL, SpellID::Search, true, 50 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Lightning"), nullptr, 4, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_SCROLLT, SpellID::Lightning, true, 150 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Identify"), nullptr, 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_SCROLL, SpellID::Identify, true, 100 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Resurrect"), nullptr, 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_SCROLLT, SpellID::Resurrect, true, 250 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Fire Wall"), nullptr, 4, 0, 0, 0, 0, 0, 0, 17, 0, ItemSpecialEffect::None, IMISC_SCROLLT, SpellID::FireWall, true, 400 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Inferno"), nullptr, 1, 0, 0, 0, 0, 0, 0, 19, 0, ItemSpecialEffect::None, IMISC_SCROLLT, SpellID::Inferno, true, 100 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Town Portal"), nullptr, 4, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_SCROLL, SpellID::TownPortal, true, 200 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Flash"), nullptr, 6, 0, 0, 0, 0, 0, 0, 21, 0, ItemSpecialEffect::None, IMISC_SCROLLT, SpellID::Flash, true, 500 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Infravision"), nullptr, 8, 0, 0, 0, 0, 0, 0, 23, 0, ItemSpecialEffect::None, IMISC_SCROLL, SpellID::Infravision, true, 600 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Phasing"), nullptr, 6, 0, 0, 0, 0, 0, 0, 25, 0, ItemSpecialEffect::None, IMISC_SCROLL, SpellID::Phasing, true, 200 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Mana Shield"), nullptr, 8, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_SCROLL, SpellID::ManaShield, true, 1200 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Flame Wave"), nullptr, 10, 0, 0, 0, 0, 0, 0, 29, 0, ItemSpecialEffect::None, IMISC_SCROLLT, SpellID::FlameWave, true, 650 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Fireball"), nullptr, 8, 0, 0, 0, 0, 0, 0, 31, 0, ItemSpecialEffect::None, IMISC_SCROLLT, SpellID::Fireball, true, 300 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Stone Curse"), nullptr, 6, 0, 0, 0, 0, 0, 0, 33, 0, ItemSpecialEffect::None, IMISC_SCROLLT, SpellID::StoneCurse, true, 800 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Chain Lightning"), nullptr, 10, 0, 0, 0, 0, 0, 0, 35, 0, ItemSpecialEffect::None, IMISC_SCROLLT, SpellID::ChainLightning, true, 750 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Guardian"), nullptr, 12, 0, 0, 0, 0, 0, 0, 47, 0, ItemSpecialEffect::None, IMISC_SCROLLT, SpellID::Guardian, true, 950 }, -/* */ { IDROP_NEVER, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, "Non Item", nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Nova"), nullptr, 14, 0, 0, 0, 0, 0, 0, 57, 0, ItemSpecialEffect::None, IMISC_SCROLL, SpellID::Nova, true, 1300 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Golem"), nullptr, 10, 0, 0, 0, 0, 0, 0, 51, 0, ItemSpecialEffect::None, IMISC_SCROLLT, SpellID::Golem, true, 1100 }, -/* */ { IDROP_NEVER, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, "Scroll of None", nullptr, 99, 0, 0, 0, 0, 0, 0, 61, 0, ItemSpecialEffect::None, IMISC_SCROLLT, SpellID::Null, true, 1000 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Teleport"), nullptr, 14, 0, 0, 0, 0, 0, 0, 81, 0, ItemSpecialEffect::None, IMISC_SCROLL, SpellID::Teleport, true, 3000 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_SCROLL_OF, ItemType::Misc, UITYPE_NONE, N_("Scroll of Apocalypse"), nullptr, 22, 0, 0, 0, 0, 0, 0, 117, 0, ItemSpecialEffect::None, IMISC_SCROLL, SpellID::Apocalypse, true, 2000 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_BOOK_BLUE, ItemType::Misc, UITYPE_NONE, N_("Book of "), nullptr, 2, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_BOOK, SpellID::Null, true, 0 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_BOOK_BLUE, ItemType::Misc, UITYPE_NONE, N_("Book of "), nullptr, 8, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_BOOK, SpellID::Null, true, 0 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_BOOK_BLUE, ItemType::Misc, UITYPE_NONE, N_("Book of "), nullptr, 14, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_BOOK, SpellID::Null, true, 0 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_BOOK_BLUE, ItemType::Misc, UITYPE_NONE, N_("Book of "), nullptr, 20, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_BOOK, SpellID::Null, true, 0 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_DAGGER, ItemType::Sword, UITYPE_DAGGER, N_("Dagger"), N_("Dagger"), 1, 16, 1, 4, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 60 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_SHORT_SWORD, ItemType::Sword, UITYPE_NONE, N_("Short Sword"), N_("Sword"), 1, 24, 2, 6, 0, 0, 18, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 120 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_FALCHION, ItemType::Sword, UITYPE_FALCHION, N_("Falchion"), N_("Sword"), 2, 20, 4, 8, 0, 0, 30, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 250 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_SCIMITAR, ItemType::Sword, UITYPE_SCIMITAR, N_("Scimitar"), N_("Sword"), 4, 28, 3, 7, 0, 0, 23, 0, 23, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 200 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_CLAYMORE, ItemType::Sword, UITYPE_CLAYMORE, N_("Claymore"), N_("Sword"), 5, 36, 1, 12, 0, 0, 35, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 450 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_BLADE, ItemType::Sword, UITYPE_NONE, N_("Blade"), N_("Blade"), 4, 30, 3, 8, 0, 0, 25, 0, 30, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 280 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_SABRE, ItemType::Sword, UITYPE_SABRE, N_("Sabre"), N_("Sabre"), 1, 45, 1, 8, 0, 0, 17, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 170 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_LONG_SWORD, ItemType::Sword, UITYPE_LONGSWR, N_("Long Sword"), N_("Sword"), 6, 40, 2, 10, 0, 0, 30, 0, 30, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 350 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_BROAD_SWORD, ItemType::Sword, UITYPE_BROADSWR, N_("Broad Sword"), N_("Sword"), 8, 50, 4, 12, 0, 0, 40, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 750 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_BASTARD_SWORD, ItemType::Sword, UITYPE_BASTARDSWR, N_("Bastard Sword"), N_("Sword"), 10, 60, 6, 15, 0, 0, 50, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 1000 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_TWO_HANDED_SWORD, ItemType::Sword, UITYPE_TWOHANDSWR, N_("Two-Handed Sword"), N_("Sword"), 14, 75, 8, 16, 0, 0, 65, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 1800 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_GREAT_SWORD, ItemType::Sword, UITYPE_GREATSWR, N_("Great Sword"), N_("Sword"), 17, 100, 10, 20, 0, 0, 75, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 3000 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_SMALL_AXE, ItemType::Axe, UITYPE_SMALLAXE, N_("Small Axe"), N_("Axe"), 2, 24, 2, 10, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 150 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_AXE, ItemType::Axe, UITYPE_NONE, N_("Axe"), N_("Axe"), 4, 32, 4, 12, 0, 0, 22, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 450 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_LARGE_AXE, ItemType::Axe, UITYPE_LARGEAXE, N_("Large Axe"), N_("Axe"), 6, 40, 6, 16, 0, 0, 30, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 750 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_BROAD_AXE, ItemType::Axe, UITYPE_BROADAXE, N_("Broad Axe"), N_("Axe"), 8, 50, 8, 20, 0, 0, 50, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 1000 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_BATTLE_AXE, ItemType::Axe, UITYPE_BATTLEAXE, N_("Battle Axe"), N_("Axe"), 10, 60, 10, 25, 0, 0, 65, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 1500 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_GREAT_AXE, ItemType::Axe, UITYPE_GREATAXE, N_("Great Axe"), N_("Axe"), 12, 75, 12, 30, 0, 0, 80, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 2500 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_MACE, ItemType::Mace, UITYPE_MACE, N_("Mace"), N_("Mace"), 2, 32, 1, 8, 0, 0, 16, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 200 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_MORNING_STAR, ItemType::Mace, UITYPE_MORNSTAR, N_("Morning Star"), N_("Mace"), 3, 40, 1, 10, 0, 0, 26, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 300 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_WAR_HAMMER, ItemType::Mace, UITYPE_WARHAMMER, N_("War Hammer"), N_("Hammer"), 5, 50, 5, 9, 0, 0, 40, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 600 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_SPIKED_CLUB, ItemType::Mace, UITYPE_SPIKCLUB, N_("Spiked Club"), N_("Club"), 4, 20, 3, 6, 0, 0, 18, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 225 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_CLUB, ItemType::Mace, UITYPE_SPIKCLUB, N_("Club"), N_("Club"), 1, 20, 1, 6, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 20 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_ONEHAND, ICURS_FLAIL, ItemType::Mace, UITYPE_FLAIL, N_("Flail"), N_("Flail"), 7, 36, 2, 12, 0, 0, 30, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 500 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_MAUL, ItemType::Mace, UITYPE_MAUL, N_("Maul"), N_("Maul"), 10, 50, 6, 20, 0, 0, 55, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 900 }, -/* */ { IDROP_DOUBLE, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_SHORT_BOW, ItemType::Bow, UITYPE_SHORTBOW, N_("Short Bow"), N_("Bow"), 1, 30, 1, 4, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 100 }, -/* */ { IDROP_DOUBLE, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_HUNTERS_BOW, ItemType::Bow, UITYPE_HUNTBOW, N_("Hunter's Bow"), N_("Bow"), 3, 40, 2, 5, 0, 0, 20, 0, 35, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 350 }, -/* */ { IDROP_DOUBLE, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_HUNTERS_BOW, ItemType::Bow, UITYPE_LONGBOW, N_("Long Bow"), N_("Bow"), 5, 35, 1, 6, 0, 0, 25, 0, 30, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 250 }, -/* */ { IDROP_DOUBLE, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_COMPOSITE_BOW, ItemType::Bow, UITYPE_COMPBOW, N_("Composite Bow"), N_("Bow"), 7, 45, 3, 6, 0, 0, 25, 0, 40, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 600 }, -/* */ { IDROP_DOUBLE, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_SHORT_BATTLE_BOW, ItemType::Bow, UITYPE_NONE, N_("Short Battle Bow"), N_("Bow"), 9, 45, 3, 7, 0, 0, 30, 0, 50, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 750 }, -/* */ { IDROP_DOUBLE, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_LONG_BATTLE_BOW, ItemType::Bow, UITYPE_BATTLEBOW, N_("Long Battle Bow"), N_("Bow"), 11, 50, 1, 10, 0, 0, 30, 0, 60, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 1000 }, -/* */ { IDROP_DOUBLE, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_SHORT_WAR_BOW, ItemType::Bow, UITYPE_NONE, N_("Short War Bow"), N_("Bow"), 15, 55, 4, 8, 0, 0, 35, 0, 70, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 1500 }, -/* */ { IDROP_DOUBLE, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_LONG_WAR_BOW, ItemType::Bow, UITYPE_WARBOW, N_("Long War Bow"), N_("Bow"), 19, 60, 1, 14, 0, 0, 45, 0, 80, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 2000 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_SHORT_STAFF, ItemType::Staff, UITYPE_SHORTSTAFF, N_("Short Staff"), N_("Staff"), 1, 25, 2, 4, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_STAFF, SpellID::Null, false, 30 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_LONG_STAFF, ItemType::Staff, UITYPE_LONGSTAFF, N_("Long Staff"), N_("Staff"), 4, 35, 4, 8, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_STAFF, SpellID::Null, false, 100 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_COMPOSITE_STAFF, ItemType::Staff, UITYPE_COMPSTAFF, N_("Composite Staff"), N_("Staff"), 6, 45, 5, 10, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_STAFF, SpellID::Null, false, 500 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_SHORT_STAFF, ItemType::Staff, UITYPE_QUARSTAFF, N_("Quarter Staff"), N_("Staff"), 9, 55, 6, 12, 0, 0, 20, 0, 0, ItemSpecialEffect::None, IMISC_STAFF, SpellID::Null, false, 1000 }, -/* */ { IDROP_REGULAR, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_WAR_STAFF, ItemType::Staff, UITYPE_WARSTAFF, N_("War Staff"), N_("Staff"), 12, 75, 8, 16, 0, 0, 30, 0, 0, ItemSpecialEffect::None, IMISC_STAFF, SpellID::Null, false, 1500 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_RING, ICURS_RING, ItemType::Ring, UITYPE_RING, N_("Ring"), N_("Ring"), 5, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_RING, SpellID::Null, false, 1000 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_RING, ICURS_RING, ItemType::Ring, UITYPE_RING, N_("Ring"), N_("Ring"), 10, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_RING, SpellID::Null, false, 1000 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_RING, ICURS_RING, ItemType::Ring, UITYPE_RING, N_("Ring"), N_("Ring"), 15, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_RING, SpellID::Null, false, 1000 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_AMULET, ICURS_AMULET, ItemType::Amulet, UITYPE_AMULET, N_("Amulet"), N_("Amulet"), 8, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_AMULET, SpellID::Null, false, 1200 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_AMULET, ICURS_AMULET, ItemType::Amulet, UITYPE_AMULET, N_("Amulet"), N_("Amulet"), 16, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_AMULET, SpellID::Null, false, 1200 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_RUNE_OF_FIRE, ItemType::Misc, UITYPE_NONE, N_("Rune of Fire"), N_("Rune"), 1, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_RUNEF, SpellID::Null, true, 100 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_RUNE_OF_LIGHTNING, ItemType::Misc, UITYPE_NONE, N_("Rune of Lightning"), N_("Rune"), 3, 0, 0, 0, 0, 0, 0, 13, 0, ItemSpecialEffect::None, IMISC_RUNEL, SpellID::Null, true, 200 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_GREATER_RUNE_OF_FIRE, ItemType::Misc, UITYPE_NONE, N_("Greater Rune of Fire"), N_("Rune"), 7, 0, 0, 0, 0, 0, 0, 42, 0, ItemSpecialEffect::None, IMISC_GR_RUNEF, SpellID::Null, true, 400 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_GREATER_RUNE_OF_LIGHTNING, ItemType::Misc, UITYPE_NONE, N_("Greater Rune of Lightning"), N_("Rune"), 7, 0, 0, 0, 0, 0, 0, 42, 0, ItemSpecialEffect::None, IMISC_GR_RUNEL, SpellID::Null, true, 500 }, -/* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_RUNE_OF_STONE, ItemType::Misc, UITYPE_NONE, N_("Rune of Stone"), N_("Rune"), 7, 0, 0, 0, 0, 0, 0, 25, 0, ItemSpecialEffect::None, IMISC_RUNES, SpellID::Null, true, 300 }, -/*IDI_SORCERER */ { IDROP_NEVER, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_SHORT_STAFF, ItemType::Staff, UITYPE_NONE, N_("Short Staff of Charged Bolt"), nullptr, 1, 25, 2, 4, 0, 0, 0, 20, 0, ItemSpecialEffect::None, IMISC_STAFF, SpellID::ChargedBolt, false, 520 }, -/*IDI_ARENAPOT */ { IDROP_NEVER, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_ARENA_POTION, ItemType::Misc, UITYPE_NONE, N_("Arena Potion"), nullptr, 7, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_ARENAPOT, SpellID::Null, true, 0 }, -/* */ { IDROP_NEVER, ICLASS_NONE, ILOC_INVALID, ICURS_POTION_OF_FULL_MANA, ItemType::Misc, UITYPE_NONE, nullptr, nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, - // clang-format on -}; +void ReadItemPower(RecordReader &reader, std::string_view fieldName, ItemPower &power) +{ + reader.read(fieldName, power.type, ParseItemEffectType); + reader.readOptionalInt(StrCat(fieldName, ".value1"), power.param1); + reader.readOptionalInt(StrCat(fieldName, ".value2"), power.param2); +} -/** Contains the data related to each item prefix. */ -const PLStruct ItemPrefixes[] = { - // clang-format off -// PLName, { type, param1, param2 } PLMinLvl, PLIType, PLGOE, PLDouble, PLOk, minVal, maxVal, multVal - // TRANSLATORS: Item prefix section. -{ N_("Tin"), { IPL_TOHIT_CURSE, 6, 10 }, 3, AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, true, false, 0, 0, -3 }, -{ N_("Brass"), { IPL_TOHIT_CURSE, 1, 5 }, 1, AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, true, false, 0, 0, -2 }, -{ N_("Bronze"), { IPL_TOHIT, 1, 5 }, 1, AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, true, true, 100, 500, 2 }, -{ N_("Iron"), { IPL_TOHIT, 6, 10 }, 4, AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, true, true, 600, 1000, 3 }, -{ N_("Steel"), { IPL_TOHIT, 11, 15 }, 6, AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, true, true, 1100, 1500, 5 }, -{ N_("Silver"), { IPL_TOHIT, 16, 20 }, 9, AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_GOOD, true, true, 1600, 2000, 7 }, -{ N_("Gold"), { IPL_TOHIT, 21, 30 }, 12, AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_GOOD, true, true, 2100, 3000, 9 }, -{ N_("Platinum"), { IPL_TOHIT, 31, 40 }, 16, AffixItemType::Weapon | AffixItemType::Bow, GOE_GOOD, true, true, 3100, 4000, 11 }, -{ N_("Mithril"), { IPL_TOHIT, 41, 60 }, 20, AffixItemType::Weapon | AffixItemType::Bow, GOE_GOOD, true, true, 4100, 6000, 13 }, -{ N_("Meteoric"), { IPL_TOHIT, 61, 80 }, 23, AffixItemType::Weapon | AffixItemType::Bow, GOE_ANY, true, true, 6100, 10000, 15 }, -{ N_("Weird"), { IPL_TOHIT, 81, 100 }, 35, AffixItemType::Weapon | AffixItemType::Bow, GOE_ANY, true, true, 10100, 14000, 17 }, -{ N_("Strange"), { IPL_TOHIT, 101, 150 }, 60, AffixItemType::Weapon | AffixItemType::Bow, GOE_ANY, true, true, 14100, 20000, 20 }, -{ N_("Useless"), { IPL_DAMP_CURSE, 100, 100 }, 5, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, true, false, 0, 0, -8 }, -{ N_("Bent"), { IPL_DAMP_CURSE, 50, 75 }, 3, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, true, false, 0, 0, -4 }, -{ N_("Weak"), { IPL_DAMP_CURSE, 25, 45 }, 1, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, true, false, 0, 0, -3 }, -{ N_("Jagged"), { IPL_DAMP, 20, 35 }, 4, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, true, true, 250, 450, 3 }, -{ N_("Deadly"), { IPL_DAMP, 36, 50 }, 6, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, true, true, 500, 700, 4 }, -{ N_("Heavy"), { IPL_DAMP, 51, 65 }, 9, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, true, true, 750, 950, 5 }, -{ N_("Vicious"), { IPL_DAMP, 66, 80 }, 12, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_EVIL, true, true, 1000, 1450, 8 }, -{ N_("Brutal"), { IPL_DAMP, 81, 95 }, 16, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, true, true, 1500, 1950, 10 }, -{ N_("Massive"), { IPL_DAMP, 96, 110 }, 20, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, true, true, 2000, 2450, 13 }, -{ N_("Savage"), { IPL_DAMP, 111, 125 }, 23, AffixItemType::Weapon | AffixItemType::Bow, GOE_ANY, true, true, 2500, 3000, 15 }, -{ N_("Ruthless"), { IPL_DAMP, 126, 150 }, 35, AffixItemType::Weapon | AffixItemType::Bow, GOE_ANY, true, true, 10100, 15000, 17 }, -{ N_("Merciless"), { IPL_DAMP, 151, 175 }, 60, AffixItemType::Weapon | AffixItemType::Bow, GOE_ANY, true, true, 15000, 20000, 20 }, -{ N_("Clumsy"), { IPL_TOHIT_DAMP_CURSE, 50, 75 }, 5, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, true, false, 0, 0, -7 }, -{ N_("Dull"), { IPL_TOHIT_DAMP_CURSE, 25, 45 }, 1, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, true, false, 0, 0, -5 }, -{ N_("Sharp"), { IPL_TOHIT_DAMP, 20, 35 }, 1, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, true, false, 350, 950, 5 }, -{ N_("Fine"), { IPL_TOHIT_DAMP, 36, 50 }, 6, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, true, true, 1100, 1700, 7 }, -{ N_("Warrior's"), { IPL_TOHIT_DAMP, 51, 65 }, 10, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, true, true, 1850, 2450, 13 }, -{ N_("Soldier's"), { IPL_TOHIT_DAMP, 66, 80 }, 15, AffixItemType::Weapon | AffixItemType::Staff, GOE_ANY, true, true, 2600, 3950, 17 }, -{ N_("Lord's"), { IPL_TOHIT_DAMP, 81, 95 }, 19, AffixItemType::Weapon | AffixItemType::Staff, GOE_ANY, true, true, 4100, 5950, 21 }, -{ N_("Knight's"), { IPL_TOHIT_DAMP, 96, 110 }, 23, AffixItemType::Weapon | AffixItemType::Staff, GOE_ANY, true, true, 6100, 8450, 26 }, -{ N_("Master's"), { IPL_TOHIT_DAMP, 111, 125 }, 28, AffixItemType::Weapon | AffixItemType::Staff, GOE_ANY, true, true, 8600, 13000, 30 }, -{ N_("Champion's"), { IPL_TOHIT_DAMP, 126, 150 }, 40, AffixItemType::Weapon | AffixItemType::Staff, GOE_ANY, true, true, 15200, 24000, 33 }, -{ N_("King's"), { IPL_TOHIT_DAMP, 151, 175 }, 28, AffixItemType::Weapon | AffixItemType::Staff, GOE_ANY, true, true, 24100, 35000, 38 }, -{ N_("Vulnerable"), { IPL_ACP_CURSE, 51, 100 }, 3, AffixItemType::Armor | AffixItemType::Shield, GOE_ANY, true, false, 0, 0, -3 }, -{ N_("Rusted"), { IPL_ACP_CURSE, 25, 50 }, 1, AffixItemType::Armor | AffixItemType::Shield, GOE_ANY, true, false, 0, 0, -2 }, -{ N_("Fine"), { IPL_ACP, 20, 30 }, 1, AffixItemType::Armor | AffixItemType::Shield, GOE_ANY, true, true, 20, 100, 2 }, -{ N_("Strong"), { IPL_ACP, 31, 40 }, 3, AffixItemType::Armor | AffixItemType::Shield, GOE_ANY, true, true, 120, 200, 3 }, -{ N_("Grand"), { IPL_ACP, 41, 55 }, 6, AffixItemType::Armor | AffixItemType::Shield, GOE_ANY, true, true, 220, 300, 5 }, -{ N_("Valiant"), { IPL_ACP, 56, 70 }, 10, AffixItemType::Armor | AffixItemType::Shield, GOE_ANY, true, true, 320, 400, 7 }, -{ N_("Glorious"), { IPL_ACP, 71, 90 }, 14, AffixItemType::Armor | AffixItemType::Shield, GOE_GOOD, true, true, 420, 600, 9 }, -{ N_("Blessed"), { IPL_ACP, 91, 110 }, 19, AffixItemType::Armor | AffixItemType::Shield, GOE_GOOD, true, true, 620, 800, 11 }, -{ N_("Saintly"), { IPL_ACP, 111, 130 }, 24, AffixItemType::Armor | AffixItemType::Shield, GOE_GOOD, true, true, 820, 1200, 13 }, -{ N_("Awesome"), { IPL_ACP, 131, 150 }, 28, AffixItemType::Armor | AffixItemType::Shield, GOE_GOOD, true, true, 1220, 2000, 15 }, -{ N_("Holy"), { IPL_ACP, 151, 170 }, 35, AffixItemType::Armor | AffixItemType::Shield, GOE_GOOD, true, true, 5200, 6000, 17 }, -{ N_("Godly"), { IPL_ACP, 171, 200 }, 60, AffixItemType::Armor | AffixItemType::Shield, GOE_GOOD, true, true, 6200, 7000, 20 }, -{ N_("Red"), { IPL_FIRERES, 10, 20 }, 4, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 500, 1500, 2 }, -{ N_("Crimson"), { IPL_FIRERES, 21, 30 }, 10, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 2100, 3000, 2 }, -{ N_("Crimson"), { IPL_FIRERES, 31, 40 }, 16, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 3100, 4000, 2 }, -{ N_("Garnet"), { IPL_FIRERES, 41, 50 }, 20, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 8200, 12000, 3 }, -{ N_("Ruby"), { IPL_FIRERES, 51, 60 }, 26, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 17100, 20000, 5 }, -{ N_("Blue"), { IPL_LIGHTRES, 10, 20 }, 4, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 500, 1500, 2 }, -{ N_("Azure"), { IPL_LIGHTRES, 21, 30 }, 10, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 2100, 3000, 2 }, -{ N_("Lapis"), { IPL_LIGHTRES, 31, 40 }, 16, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 3100, 4000, 2 }, -{ N_("Cobalt"), { IPL_LIGHTRES, 41, 50 }, 20, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 8200, 12000, 3 }, -{ N_("Sapphire"), { IPL_LIGHTRES, 51, 60 }, 26, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 17100, 20000, 5 }, -{ N_("White"), { IPL_MAGICRES, 10, 20 }, 4, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 500, 1500, 2 }, -{ N_("Pearl"), { IPL_MAGICRES, 21, 30 }, 10, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 2100, 3000, 2 }, -{ N_("Ivory"), { IPL_MAGICRES, 31, 40 }, 16, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 3100, 4000, 2 }, -{ N_("Crystal"), { IPL_MAGICRES, 41, 50 }, 20, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 8200, 12000, 3 }, -{ N_("Diamond"), { IPL_MAGICRES, 51, 60 }, 26, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 17100, 20000, 5 }, -{ N_("Topaz"), { IPL_ALLRES, 10, 15 }, 8, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 2000, 5000, 3 }, -{ N_("Amber"), { IPL_ALLRES, 16, 20 }, 12, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 7400, 10000, 3 }, -{ N_("Jade"), { IPL_ALLRES, 21, 30 }, 18, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 11000, 15000, 3 }, -{ N_("Obsidian"), { IPL_ALLRES, 31, 40 }, 24, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 24000, 40000, 4 }, -{ N_("Emerald"), { IPL_ALLRES, 41, 50 }, 31, AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, false, true, 61000, 75000, 7 }, -{ N_("Hyena's"), { IPL_MANA_CURSE, 11, 25 }, 4, AffixItemType::Staff | AffixItemType::Misc, GOE_ANY, false, false, 100, 1000, -2 }, -{ N_("Frog's"), { IPL_MANA_CURSE, 1, 10 }, 1, AffixItemType::Staff | AffixItemType::Misc, GOE_EVIL, false, false, 0, 0, -2 }, -{ N_("Spider's"), { IPL_MANA, 10, 15 }, 1, AffixItemType::Staff | AffixItemType::Misc, GOE_EVIL, false, true, 500, 1000, 2 }, -{ N_("Raven's"), { IPL_MANA, 15, 20 }, 5, AffixItemType::Staff | AffixItemType::Misc, GOE_ANY, false, true, 1100, 2000, 3 }, -{ N_("Snake's"), { IPL_MANA, 21, 30 }, 9, AffixItemType::Staff | AffixItemType::Misc, GOE_ANY, false, true, 2100, 4000, 5 }, -{ N_("Serpent's"), { IPL_MANA, 30, 40 }, 15, AffixItemType::Staff | AffixItemType::Misc, GOE_ANY, false, true, 4100, 6000, 7 }, -{ N_("Drake's"), { IPL_MANA, 41, 50 }, 21, AffixItemType::Staff | AffixItemType::Misc, GOE_ANY, false, true, 6100, 10000, 9 }, -{ N_("Dragon's"), { IPL_MANA, 51, 60 }, 27, AffixItemType::Staff | AffixItemType::Misc, GOE_ANY, false, true, 10100, 15000, 11 }, -{ N_("Wyrm's"), { IPL_MANA, 61, 80 }, 35, AffixItemType::Staff, GOE_ANY, false, true, 15100, 19000, 12 }, -{ N_("Hydra's"), { IPL_MANA, 81, 100 }, 60, AffixItemType::Staff, GOE_ANY, false, true, 19100, 30000, 13 }, -{ N_("Angel's"), { IPL_SPLLVLADD, 1, 1 }, 15, AffixItemType::Staff, GOE_GOOD, false, true, 25000, 25000, 2 }, -{ N_("Arch-Angel's"), { IPL_SPLLVLADD, 2, 2 }, 25, AffixItemType::Staff, GOE_GOOD, false, true, 50000, 50000, 3 }, -{ N_("Plentiful"), { IPL_CHARGES, 2, 2 }, 4, AffixItemType::Staff, GOE_ANY, false, true, 2000, 2000, 2 }, -{ N_("Bountiful"), { IPL_CHARGES, 3, 3 }, 9, AffixItemType::Staff, GOE_ANY, false, true, 3000, 3000, 3 }, -{ N_("Flaming"), { IPL_FIREDAM, 1, 10 }, 7, AffixItemType::Weapon | AffixItemType::Staff, GOE_ANY, false, true, 5000, 5000, 2 }, -{ N_("Lightning"), { IPL_LIGHTDAM, 2, 20 }, 18, AffixItemType::Weapon | AffixItemType::Staff, GOE_ANY, false, true, 10000, 10000, 2 }, -{ N_("Jester's"), { IPL_JESTERS, 1, 1 }, 7, AffixItemType::Weapon, GOE_ANY, false, true, 1200, 1200, 3 }, -{ N_("Crystalline"), { IPL_CRYSTALLINE, 30, 70 }, 5, AffixItemType::Weapon, GOE_ANY, false, true, 1000, 3000, 3 }, - // TRANSLATORS: Item prefix section end. -{ N_("Doppelganger's"), { IPL_DOPPELGANGER, 81, 95 }, 11, AffixItemType::Weapon | AffixItemType::Staff, GOE_ANY, false, true, 2000, 2400, 10 }, -{ "", { IPL_INVALID, 0, 0 }, 0, AffixItemType::None, GOE_ANY, false, false, 0, 0, 0 }, - // clang-format on -}; +void LoadUniqueItemDat() +{ + const std::string_view filename = "txtdata\\items\\unique_itemdat.tsv"; + DataFile dataFile = DataFile::loadOrDie(filename); + dataFile.skipHeaderOrDie(filename); -/** Contains the data related to each item suffix. */ -const PLStruct ItemSuffixes[] = { - // clang-format off -// PLName, { type, param1, param2 } PLMinLvl, PLIType, PLGOE, PLDouble, PLOk, minVal, maxVal, multVal - // TRANSLATORS: Item suffix section. All items will have a word binding word. (Format: {:s} of {:s} - e.g. Rags of Valor) -{ N_("quality"), { IPL_DAMMOD, 1, 2 }, 2, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, false, true, 100, 200, 2 }, -{ N_("maiming"), { IPL_DAMMOD, 3, 5 }, 7, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, false, true, 1300, 1500, 3 }, -{ N_("slaying"), { IPL_DAMMOD, 6, 8 }, 15, AffixItemType::Weapon, GOE_ANY, false, true, 2600, 3000, 5 }, -{ N_("gore"), { IPL_DAMMOD, 9, 12 }, 25, AffixItemType::Weapon, GOE_ANY, false, true, 4100, 5000, 8 }, -{ N_("carnage"), { IPL_DAMMOD, 13, 16 }, 35, AffixItemType::Weapon, GOE_ANY, false, true, 5100, 10000, 10 }, -{ N_("slaughter"), { IPL_DAMMOD, 17, 20 }, 60, AffixItemType::Weapon, GOE_ANY, false, true, 10100, 15000, 13 }, -{ N_("pain"), { IPL_GETHIT_CURSE, 2, 4 }, 4, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Misc, GOE_EVIL, false, false, 0, 0, -4 }, -{ N_("tears"), { IPL_GETHIT_CURSE, 1, 1 }, 2, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Misc, GOE_EVIL, false, false, 0, 0, -2 }, -{ N_("health"), { IPL_GETHIT, 1, 1 }, 2, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Misc, GOE_GOOD, false, true, 200, 200, 2 }, -{ N_("protection"), { IPL_GETHIT, 2, 2 }, 6, AffixItemType::Armor | AffixItemType::Shield, GOE_GOOD, false, true, 400, 800, 4 }, -{ N_("absorption"), { IPL_GETHIT, 3, 3 }, 12, AffixItemType::Armor | AffixItemType::Shield, GOE_GOOD, false, true, 1001, 2500, 10 }, -{ N_("deflection"), { IPL_GETHIT, 4, 4 }, 20, AffixItemType::Armor, GOE_GOOD, false, true, 2500, 6500, 15 }, -{ N_("osmosis"), { IPL_GETHIT, 5, 6 }, 50, AffixItemType::Armor, GOE_GOOD, false, true, 7500, 10000, 20 }, -{ N_("frailty"), { IPL_STR_CURSE, 6, 10 }, 3, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_EVIL, false, false, 0, 0, -3 }, -{ N_("weakness"), { IPL_STR_CURSE, 1, 5 }, 1, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_EVIL, false, false, 0, 0, -2 }, -{ N_("strength"), { IPL_STR, 1, 5 }, 1, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 200, 1000, 2 }, -{ N_("might"), { IPL_STR, 6, 10 }, 5, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 1200, 2000, 3 }, -{ N_("power"), { IPL_STR, 11, 15 }, 11, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 2200, 3000, 4 }, -{ N_("giants"), { IPL_STR, 16, 20 }, 17, AffixItemType::Armor | AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 3200, 5000, 7 }, -{ N_("titans"), { IPL_STR, 21, 30 }, 23, AffixItemType::Weapon | AffixItemType::Misc, GOE_ANY, false, true, 5200, 10000, 10 }, -{ N_("paralysis"), { IPL_DEX_CURSE, 6, 10 }, 3, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_EVIL, false, false, 0, 0, -3 }, -{ N_("atrophy"), { IPL_DEX_CURSE, 1, 5 }, 1, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_EVIL, false, false, 0, 0, -2 }, -{ N_("dexterity"), { IPL_DEX, 1, 5 }, 1, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 200, 1000, 2 }, -{ N_("skill"), { IPL_DEX, 6, 10 }, 5, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 1200, 2000, 3 }, -{ N_("accuracy"), { IPL_DEX, 11, 15 }, 11, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 2200, 3000, 4 }, -{ N_("precision"), { IPL_DEX, 16, 20 }, 17, AffixItemType::Armor | AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 3200, 5000, 7 }, -{ N_("perfection"), { IPL_DEX, 21, 30 }, 23, AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 5200, 10000, 10 }, -{ N_("the fool"), { IPL_MAG_CURSE, 6, 10 }, 3, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_EVIL, false, false, 0, 0, -3 }, -{ N_("dyslexia"), { IPL_MAG_CURSE, 1, 5 }, 1, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_EVIL, false, false, 0, 0, -2 }, -{ N_("magic"), { IPL_MAG, 1, 5 }, 1, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 200, 1000, 2 }, -{ N_("the mind"), { IPL_MAG, 6, 10 }, 5, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 1200, 2000, 3 }, -{ N_("brilliance"), { IPL_MAG, 11, 15 }, 11, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 2200, 3000, 4 }, -{ N_("sorcery"), { IPL_MAG, 16, 20 }, 17, AffixItemType::Armor | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 3200, 5000, 7 }, -{ N_("wizardry"), { IPL_MAG, 21, 30 }, 23, AffixItemType::Staff | AffixItemType::Misc, GOE_ANY, false, true, 5200, 10000, 10 }, -{ N_("illness"), { IPL_VIT_CURSE, 6, 10 }, 3, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_EVIL, false, false, 0, 0, -3 }, -{ N_("disease"), { IPL_VIT_CURSE, 1, 5 }, 1, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_EVIL, false, false, 0, 0, -2 }, -{ N_("vitality"), { IPL_VIT, 1, 5 }, 1, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_GOOD, false, true, 200, 1000, 2 }, -{ N_("zest"), { IPL_VIT, 6, 10 }, 5, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_GOOD, false, true, 1200, 2000, 3 }, -{ N_("vim"), { IPL_VIT, 11, 15 }, 11, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_GOOD, false, true, 2200, 3000, 4 }, -{ N_("vigor"), { IPL_VIT, 16, 20 }, 17, AffixItemType::Armor | AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_GOOD, false, true, 3200, 5000, 7 }, -{ N_("life"), { IPL_VIT, 21, 30 }, 23, AffixItemType::Misc, GOE_GOOD, false, true, 5200, 10000, 10 }, -{ N_("trouble"), { IPL_ATTRIBS_CURSE, 6, 10 }, 12, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_EVIL, false, false, 0, 0, -10 }, -{ N_("the pit"), { IPL_ATTRIBS_CURSE, 1, 5 }, 5, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_EVIL, false, false, 0, 0, -5 }, -{ N_("the sky"), { IPL_ATTRIBS, 1, 3 }, 5, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 800, 4000, 5 }, -{ N_("the moon"), { IPL_ATTRIBS, 4, 7 }, 11, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 4800, 8000, 10 }, -{ N_("the stars"), { IPL_ATTRIBS, 8, 11 }, 17, AffixItemType::Armor | AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 8800, 12000, 15 }, -{ N_("the heavens"), { IPL_ATTRIBS, 12, 15 }, 25, AffixItemType::Weapon | AffixItemType::Bow | AffixItemType::Misc, GOE_ANY, false, true, 12800, 20000, 20 }, -{ N_("the zodiac"), { IPL_ATTRIBS, 16, 20 }, 30, AffixItemType::Misc, GOE_ANY, false, true, 20800, 40000, 30 }, -{ N_("the vulture"), { IPL_LIFE_CURSE, 11, 25 }, 4, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Misc, GOE_EVIL, false, false, 0, 0, -4 }, -{ N_("the jackal"), { IPL_LIFE_CURSE, 1, 10 }, 1, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Misc, GOE_EVIL, false, false, 0, 0, -2 }, -{ N_("the fox"), { IPL_LIFE, 10, 15 }, 1, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Misc, GOE_ANY, false, true, 100, 1000, 2 }, -{ N_("the jaguar"), { IPL_LIFE, 16, 20 }, 5, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Misc, GOE_ANY, false, true, 1100, 2000, 3 }, -{ N_("the eagle"), { IPL_LIFE, 21, 30 }, 9, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Misc, GOE_ANY, false, true, 2100, 4000, 5 }, -{ N_("the wolf"), { IPL_LIFE, 30, 40 }, 15, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Misc, GOE_ANY, false, true, 4100, 6000, 7 }, -{ N_("the tiger"), { IPL_LIFE, 41, 50 }, 21, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Misc, GOE_ANY, false, true, 6100, 10000, 9 }, -{ N_("the lion"), { IPL_LIFE, 51, 60 }, 27, AffixItemType::Armor | AffixItemType::Misc, GOE_ANY, false, true, 10100, 15000, 11 }, -{ N_("the mammoth"), { IPL_LIFE, 61, 80 }, 35, AffixItemType::Armor, GOE_ANY, false, true, 15100, 19000, 12 }, -{ N_("the whale"), { IPL_LIFE, 81, 100 }, 60, AffixItemType::Armor, GOE_ANY, false, true, 19100, 30000, 13 }, -{ N_("fragility"), { IPL_DUR_CURSE, 100, 100 }, 3, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon, GOE_EVIL, false, false, 0, 0, -4 }, -{ N_("brittleness"), { IPL_DUR_CURSE, 26, 75 }, 1, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon, GOE_EVIL, false, false, 0, 0, -2 }, -{ N_("sturdiness"), { IPL_DUR, 26, 75 }, 1, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff, GOE_ANY, false, true, 100, 100, 2 }, -{ N_("craftsmanship"), { IPL_DUR, 51, 100 }, 6, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff, GOE_ANY, false, true, 200, 200, 2 }, -{ N_("structure"), { IPL_DUR, 101, 200 }, 12, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff, GOE_ANY, false, true, 300, 300, 2 }, -{ N_("the ages"), { IPL_INDESTRUCTIBLE }, 25, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon | AffixItemType::Staff, GOE_ANY, false, true, 600, 600, 5 }, -{ N_("the dark"), { IPL_LIGHT_CURSE, 4, 4 }, 6, AffixItemType::Armor | AffixItemType::Weapon | AffixItemType::Misc, GOE_EVIL, false, false, 0, 0, -3 }, -{ N_("the night"), { IPL_LIGHT_CURSE, 2, 2 }, 3, AffixItemType::Armor | AffixItemType::Weapon | AffixItemType::Misc, GOE_EVIL, false, false, 0, 0, -2 }, -{ N_("light"), { IPL_LIGHT, 2, 2 }, 4, AffixItemType::Armor | AffixItemType::Weapon | AffixItemType::Misc, GOE_GOOD, false, true, 750, 750, 2 }, -{ N_("radiance"), { IPL_LIGHT, 4, 4 }, 8, AffixItemType::Armor | AffixItemType::Weapon | AffixItemType::Misc, GOE_GOOD, false, true, 1500, 1500, 3 }, -{ N_("flame"), { IPL_FIRE_ARROWS, 1, 3 }, 1, AffixItemType::Bow, GOE_ANY, false, true, 2000, 2000, 2 }, -{ N_("fire"), { IPL_FIRE_ARROWS, 1, 6 }, 11, AffixItemType::Bow, GOE_ANY, false, true, 4000, 4000, 4 }, -{ N_("burning"), { IPL_FIRE_ARROWS, 1, 16 }, 35, AffixItemType::Bow, GOE_ANY, false, true, 6000, 6000, 6 }, -{ N_("shock"), { IPL_LIGHT_ARROWS, 1, 6 }, 13, AffixItemType::Bow, GOE_ANY, false, true, 6000, 6000, 2 }, -{ N_("lightning"), { IPL_LIGHT_ARROWS, 1, 10 }, 21, AffixItemType::Bow, GOE_ANY, false, true, 8000, 8000, 4 }, -{ N_("thunder"), { IPL_LIGHT_ARROWS, 1, 20 }, 60, AffixItemType::Bow, GOE_ANY, false, true, 12000, 12000, 6 }, -{ N_("many"), { IPL_DUR, 100, 100 }, 3, AffixItemType::Bow, GOE_ANY, false, true, 750, 750, 2 }, -{ N_("plenty"), { IPL_DUR, 200, 200 }, 7, AffixItemType::Bow, GOE_ANY, false, true, 1500, 1500, 3 }, -{ N_("thorns"), { IPL_THORNS, 1, 3 }, 1, AffixItemType::Armor | AffixItemType::Shield, GOE_ANY, false, true, 500, 500, 2 }, -{ N_("corruption"), { IPL_NOMANA }, 5, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Weapon, GOE_EVIL, false, false, -1000, -1000, 2 }, -{ N_("thieves"), { IPL_ABSHALFTRAP }, 11, AffixItemType::Armor | AffixItemType::Shield | AffixItemType::Misc, GOE_ANY, false, true, 1500, 1500, 2 }, -{ N_("the bear"), { IPL_KNOCKBACK }, 5, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_EVIL, false, true, 750, 750, 2 }, -{ N_("the bat"), { IPL_STEALMANA, 3, 3 }, 8, AffixItemType::Weapon, GOE_ANY, false, true, 7500, 7500, 3 }, -{ N_("vampires"), { IPL_STEALMANA, 5, 5 }, 19, AffixItemType::Weapon, GOE_ANY, false, true, 15000, 15000, 3 }, -{ N_("the leech"), { IPL_STEALLIFE, 3, 3 }, 8, AffixItemType::Weapon, GOE_ANY, false, true, 7500, 7500, 3 }, -{ N_("blood"), { IPL_STEALLIFE, 5, 5 }, 19, AffixItemType::Weapon, GOE_ANY, false, true, 15000, 15000, 3 }, -{ N_("piercing"), { IPL_TARGAC, 1, 1 }, 1, AffixItemType::Weapon | AffixItemType::Bow, GOE_ANY, false, true, 1000, 1000, 3 }, -{ N_("puncturing"), { IPL_TARGAC, 2, 2 }, 9, AffixItemType::Weapon | AffixItemType::Bow, GOE_ANY, false, true, 2000, 2000, 6 }, -{ N_("bashing"), { IPL_TARGAC, 3, 3 }, 17, AffixItemType::Weapon, GOE_ANY, false, true, 4000, 4000, 12 }, -{ N_("readiness"), { IPL_FASTATTACK, 1, 1 }, 1, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, false, true, 2000, 2000, 2 }, -{ N_("swiftness"), { IPL_FASTATTACK, 2, 2 }, 10, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, false, true, 4000, 4000, 4 }, -{ N_("speed"), { IPL_FASTATTACK, 3, 3 }, 19, AffixItemType::Weapon | AffixItemType::Staff, GOE_ANY, false, true, 8000, 8000, 8 }, -{ N_("haste"), { IPL_FASTATTACK, 4, 4 }, 27, AffixItemType::Weapon | AffixItemType::Staff, GOE_ANY, false, true, 16000, 16000, 16 }, -{ N_("balance"), { IPL_FASTRECOVER, 1, 1 }, 1, AffixItemType::Armor | AffixItemType::Misc, GOE_ANY, false, true, 2000, 2000, 2 }, -{ N_("stability"), { IPL_FASTRECOVER, 2, 2 }, 10, AffixItemType::Armor | AffixItemType::Misc, GOE_ANY, false, true, 4000, 4000, 4 }, -{ N_("harmony"), { IPL_FASTRECOVER, 3, 3 }, 20, AffixItemType::Armor | AffixItemType::Misc, GOE_ANY, false, true, 8000, 8000, 8 }, -{ N_("blocking"), { IPL_FASTBLOCK, 1, 1 }, 5, AffixItemType::Shield, GOE_ANY, false, true, 4000, 4000, 4 }, -{ N_("devastation"), { IPL_DEVASTATION, 1, 1 }, 1, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, false, true, 1200, 1200, 3 }, -{ N_("decay"), { IPL_DECAY, 150, 250 }, 1, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, false, true, 200, 200, 2 }, - // TRANSLATORS: Item suffix section end. -{ N_("peril"), { IPL_PERIL, 1, 1 }, 5, AffixItemType::Weapon | AffixItemType::Staff | AffixItemType::Bow, GOE_ANY, false, true, 500, 500, 1 }, -{ "", { }, 0, AffixItemType::None, GOE_ANY, false, false, 0, 0, 0 }, - // clang-format on -}; + UniqueItems.clear(); + UniqueItems.reserve(dataFile.numRecords()); + for (DataFileRecord record : dataFile) { + RecordReader reader { record, filename }; + UniqueItem &item = UniqueItems.emplace_back(); + reader.readString("name", item.UIName); + reader.read("uniqueBaseItem", item.UIItemId, ParseUniqueBaseItem); + reader.readInt("minLevel", item.UIMinLvl); + reader.readInt("value", item.UIValue); -/** Contains the data related to each unique item ID. */ -const UniqueItem UniqueItems[] = { - // clang-format off -// UIName, UIItemId, UIMinLvl, UINumPL, UIValue, { ItemPower[0], ItemPower[1], ItemPower[2], ItemPower[3], ItemPower[4], ItemPower[5] } - // TRANSLATORS: Unique Item section -{ N_("The Butcher's Cleaver"), UITYPE_CLEAVER, 1, 3, 3650, { { IPL_STR, 10, 10 }, { IPL_SETDAM, 4, 24 }, { IPL_SETDUR, 10, 10 }, { }, { }, { } } }, -{ N_("The Undead Crown"), UITYPE_SKCROWN, 1, 3, 16650, { { IPL_RNDSTEALLIFE }, { IPL_SETAC, 8, 8 }, { IPL_INVCURS, 77 }, { }, { }, { } } }, -{ N_("Empyrean Band"), UITYPE_INFRARING, 1, 4, 8000, { { IPL_ATTRIBS, 2, 2 }, { IPL_LIGHT, 2, 2 }, { IPL_FASTRECOVER, 1, 1 }, { IPL_ABSHALFTRAP }, { }, { } } }, -{ N_("Optic Amulet"), UITYPE_OPTAMULET, 1, 5, 9750, { { IPL_LIGHT, 2, 2 }, { IPL_LIGHTRES, 20, 20 }, { IPL_GETHIT, 1, 1 }, { IPL_MAG, 5, 5 }, { IPL_INVCURS, 44 }, { } } }, -{ N_("Ring of Truth"), UITYPE_TRING, 1, 4, 9100, { { IPL_LIFE, 10, 10 }, { IPL_GETHIT, 1, 1 }, { IPL_ALLRES, 10, 10 }, { IPL_INVCURS, 10 }, { }, { } } }, -{ N_("Harlequin Crest"), UITYPE_HARCREST, 1, 6, 4000, { { IPL_AC_CURSE, 3, 3 }, { IPL_GETHIT, 1, 1 }, { IPL_ATTRIBS, 2, 2 }, { IPL_LIFE, 7, 7 }, { IPL_MANA, 7, 7 }, { IPL_INVCURS, 81 } } }, -{ N_("Veil of Steel"), UITYPE_STEELVEIL, 1, 6, 63800, { { IPL_ALLRES, 50, 50 }, { IPL_LIGHT_CURSE, 2, 2 }, { IPL_ACP, 60, 60 }, { IPL_MANA_CURSE, 30, 30 }, { IPL_STR, 15, 15 }, { IPL_VIT, 15, 15 } } }, -{ N_("Arkaine's Valor"), UITYPE_ARMOFVAL, 1, 4, 42000, { { IPL_SETAC, 25, 25 }, { IPL_VIT, 10, 10 }, { IPL_GETHIT, 3, 3 }, { IPL_FASTRECOVER, 3, 3 }, { }, { } } }, -{ N_("Griswold's Edge"), UITYPE_GRISWOLD, 1, 6, 42000, { { IPL_FIREDAM, 1, 10 }, { IPL_TOHIT, 25, 25 }, { IPL_FASTATTACK, 2, 2 }, { IPL_KNOCKBACK }, { IPL_MANA, 20, 20 }, { IPL_LIFE_CURSE, 20, 20 } } }, -{ N_("Bovine Plate"), UITYPE_BOVINE, 1, 6, 400, { { IPL_SETAC, 150, 150 }, { IPL_INDESTRUCTIBLE }, { IPL_LIGHT, 5, 5 }, { IPL_ALLRES, 30, 30 }, { IPL_MANA_CURSE, 50, 50 }, { IPL_SPLLVLADD, -2, -2 } } }, -{ N_("The Rift Bow"), UITYPE_SHORTBOW, 1, 3, 1800, { { IPL_RNDARROWVEL }, { IPL_DAMMOD, 2, 2 }, { IPL_DEX_CURSE, 3, 3 }, { }, { }, { } } }, -{ N_("The Needler"), UITYPE_SHORTBOW, 2, 4, 8900, { { IPL_TOHIT, 50, 50 }, { IPL_SETDAM, 1, 3 }, { IPL_FASTATTACK, 2, 2 }, { IPL_INVCURS, 158 }, { }, { } } }, -{ N_("The Celestial Bow"), UITYPE_LONGBOW, 2, 4, 1200, { { IPL_NOMINSTR }, { IPL_DAMMOD, 2, 2 }, { IPL_SETAC, 5, 5 }, { IPL_INVCURS, 133 }, { }, { } } }, -{ N_("Deadly Hunter"), UITYPE_COMPBOW, 3, 4, 8750, { { IPL_3XDAMVDEM, 10, 10 }, { IPL_TOHIT, 20, 20 }, { IPL_MAG_CURSE, 5, 5 }, { IPL_INVCURS, 108 }, { }, { } } }, -{ N_("Bow of the Dead"), UITYPE_COMPBOW, 5, 6, 2500, { { IPL_TOHIT, 10, 10 }, { IPL_DEX, 4, 4 }, { IPL_VIT_CURSE, 3, 3 }, { IPL_LIGHT_CURSE, 2, 2 }, { IPL_SETDUR, 30, 30 }, { IPL_INVCURS, 108 } } }, -{ N_("The Blackoak Bow"), UITYPE_LONGBOW, 5, 4, 2500, { { IPL_DEX, 10, 10 }, { IPL_VIT_CURSE, 10, 10 }, { IPL_DAMP, 50, 50 }, { IPL_LIGHT_CURSE, 1, 1 }, { }, { } } }, -{ N_("Flamedart"), UITYPE_HUNTBOW, 10, 4, 14250, { { IPL_FIRE_ARROWS, 0, 0 }, { IPL_FIREDAM, 1, 6 }, { IPL_TOHIT, 20, 20 }, { IPL_FIRERES, 40, 40 }, { }, { } } }, -{ N_("Fleshstinger"), UITYPE_LONGBOW, 13, 4, 16500, { { IPL_DEX, 15, 15 }, { IPL_TOHIT, 40, 40 }, { IPL_DAMP, 80, 80 }, { IPL_DUR, 6, 6 }, { }, { } } }, -{ N_("Windforce"), UITYPE_WARBOW, 17, 4, 37750, { { IPL_STR, 5, 5 }, { IPL_DAMP, 200, 200 }, { IPL_KNOCKBACK }, { IPL_INVCURS, 164 }, { }, { } } }, -{ N_("Eaglehorn"), UITYPE_BATTLEBOW, 26, 5, 42500, { { IPL_DEX, 20, 20 }, { IPL_TOHIT, 50, 50 }, { IPL_DAMP, 100, 100 }, { IPL_INDESTRUCTIBLE }, { IPL_INVCURS, 108 }, { } } }, -{ N_("Gonnagal's Dirk"), UITYPE_DAGGER, 1, 5, 7040, { { IPL_DEX_CURSE, 5, 5 }, { IPL_DAMMOD, 4, 4 }, { IPL_FASTATTACK, 2, 2 }, { IPL_FIRERES, 25, 25 }, { IPL_INVCURS, 54 }, { } } }, -{ N_("The Defender"), UITYPE_SABRE, 1, 3, 2000, { { IPL_SETAC, 5, 5 }, { IPL_VIT, 5, 5 }, { IPL_TOHIT_CURSE, 5, 5 }, { }, { }, { } } }, -{ N_("Gryphon's Claw"), UITYPE_FALCHION, 1, 4, 1000, { { IPL_DAMP, 100, 100 }, { IPL_MAG_CURSE, 2, 2 }, { IPL_DEX_CURSE, 5, 5 }, { IPL_INVCURS, 68 }, { }, { } } }, -{ N_("Black Razor"), UITYPE_DAGGER, 1, 4, 2000, { { IPL_DAMP, 150, 150 }, { IPL_VIT, 2, 2 }, { IPL_SETDUR, 5, 5 }, { IPL_INVCURS, 53 }, { }, { } } }, -{ N_("Gibbous Moon"), UITYPE_BROADSWR, 2, 4, 6660, { { IPL_ATTRIBS, 2, 2 }, { IPL_DAMP, 25, 25 }, { IPL_MANA, 15, 15 }, { IPL_LIGHT_CURSE, 3, 3 }, { }, { } } }, -{ N_("Ice Shank"), UITYPE_LONGSWR, 3, 3, 5250, { { IPL_FIRERES, 40, 40 }, { IPL_SETDUR, 15, 15 }, { IPL_STR, 5, 10 }, { }, { }, { } } }, -{ N_("The Executioner's Blade"), UITYPE_FALCHION, 3, 5, 7080, { { IPL_DAMP, 150, 150 }, { IPL_LIFE_CURSE, 10, 10 }, { IPL_LIGHT_CURSE, 1, 1 }, { IPL_DUR, 200, 200 }, { IPL_INVCURS, 58 }, { } } }, -{ N_("The Bonesaw"), UITYPE_CLAYMORE, 6, 6, 4400, { { IPL_DAMMOD, 10, 10 }, { IPL_STR, 10, 10 }, { IPL_MAG_CURSE, 5, 5 }, { IPL_DEX_CURSE, 5, 5 }, { IPL_LIFE, 10, 10 }, { IPL_MANA_CURSE, 10, 10 } } }, -{ N_("Shadowhawk"), UITYPE_BROADSWR, 8, 4, 13750, { { IPL_LIGHT_CURSE, 2, 2 }, { IPL_STEALLIFE, 5, 5 }, { IPL_TOHIT, 15, 15 }, { IPL_ALLRES, 5, 5 }, { }, { } } }, -{ N_("Wizardspike"), UITYPE_DAGGER, 11, 5, 12920, { { IPL_MAG, 15, 15 }, { IPL_MANA, 35, 35 }, { IPL_TOHIT, 25, 25 }, { IPL_ALLRES, 15, 15 }, { IPL_INVCURS, 50 }, { } } }, -{ N_("Lightsabre"), UITYPE_SABRE, 13, 4, 19150, { { IPL_LIGHT, 2, 2 }, { IPL_LIGHTDAM, 1, 10 }, { IPL_TOHIT, 20, 20 }, { IPL_LIGHTRES, 50, 50 }, { }, { } } }, -{ N_("The Falcon's Talon"), UITYPE_SCIMITAR, 15, 5, 7867, { { IPL_FASTATTACK, 4, 4 }, { IPL_TOHIT, 20, 20 }, { IPL_DAMP_CURSE, 33, 33 }, { IPL_DEX, 10, 10 }, { IPL_INVCURS, 68 }, { } } }, -{ N_("Inferno"), UITYPE_LONGSWR, 17, 4, 34600, { { IPL_FIREDAM, 2, 12 }, { IPL_LIGHT, 3, 3 }, { IPL_MANA, 20, 20 }, { IPL_FIRERES, 80, 80 }, { }, { } } }, -{ N_("Doombringer"), UITYPE_BASTARDSWR, 19, 5, 18250, { { IPL_TOHIT, 25, 25 }, { IPL_DAMP, 250, 250 }, { IPL_ATTRIBS_CURSE, 5, 5 }, { IPL_LIFE_CURSE, 25, 25 }, { IPL_LIGHT_CURSE, 2, 2 }, { } } }, -{ N_("The Grizzly"), UITYPE_TWOHANDSWR, 23, 6, 50000, { { IPL_STR, 20, 20 }, { IPL_VIT_CURSE, 5, 5 }, { IPL_DAMP, 200, 200 }, { IPL_KNOCKBACK }, { IPL_DUR, 100, 100 }, { IPL_INVCURS, 160 } } }, -{ N_("The Grandfather"), UITYPE_GREATSWR, 27, 6, 119800, { { IPL_ONEHAND }, { IPL_ATTRIBS, 5, 5 }, { IPL_TOHIT, 20, 20 }, { IPL_DAMP, 70, 70 }, { IPL_LIFE, 20, 20 }, { IPL_INVCURS, 161 } } }, -{ N_("The Mangler"), UITYPE_LARGEAXE, 2, 5, 2850, { { IPL_DAMP, 200, 200 }, { IPL_DEX_CURSE, 5, 5 }, { IPL_MAG_CURSE, 5, 5 }, { IPL_MANA_CURSE, 10, 10 }, { IPL_INVCURS, 144 }, { } } }, -{ N_("Sharp Beak"), UITYPE_LARGEAXE, 2, 4, 2850, { { IPL_LIFE, 20, 20 }, { IPL_MAG_CURSE, 10, 10 }, { IPL_MANA_CURSE, 10, 10 }, { IPL_INVCURS, 143 }, { }, { } } }, -{ N_("BloodSlayer"), UITYPE_BROADAXE, 3, 5, 2500, { { IPL_DAMP, 100, 100 }, { IPL_3XDAMVDEM, 50, 50 }, { IPL_ATTRIBS_CURSE, 5, 5 }, { IPL_SPLLVLADD, -1, -1 }, { IPL_INVCURS, 144 }, { } } }, -{ N_("The Celestial Axe"), UITYPE_BATTLEAXE, 4, 4, 14100, { { IPL_NOMINSTR }, { IPL_TOHIT, 15, 15 }, { IPL_LIFE, 15, 15 }, { IPL_STR_CURSE, 15, 15 }, { }, { } } }, -{ N_("Wicked Axe"), UITYPE_LARGEAXE, 5, 6, 31150, { { IPL_TOHIT, 30, 30 }, { IPL_DEX, 10, 10 }, { IPL_VIT_CURSE, 10, 10 }, { IPL_GETHIT, 1, 6 }, { IPL_INDESTRUCTIBLE }, { IPL_INVCURS, 143 } } }, -{ N_("Stonecleaver"), UITYPE_BROADAXE, 7, 5, 23900, { { IPL_LIFE, 30, 30 }, { IPL_TOHIT, 20, 20 }, { IPL_DAMP, 50, 50 }, { IPL_LIGHTRES, 40, 40 }, { IPL_INVCURS, 104 }, { } } }, -{ N_("Aguinara's Hatchet"), UITYPE_SMALLAXE, 12, 3, 24800, { { IPL_SPLLVLADD, 1, 1 }, { IPL_MAG, 10, 10 }, { IPL_MAGICRES, 80, 80 }, { }, { }, { } } }, -{ N_("Hellslayer"), UITYPE_BATTLEAXE, 15, 5, 26200, { { IPL_STR, 8, 8 }, { IPL_VIT, 8, 8 }, { IPL_DAMP, 100, 100 }, { IPL_LIFE, 25, 25 }, { IPL_MANA_CURSE, 25, 25 }, { } } }, -{ N_("Messerschmidt's Reaver"), UITYPE_GREATAXE, 25, 6, 58000, { { IPL_DAMP, 200, 200 }, { IPL_DAMMOD, 15, 15 }, { IPL_ATTRIBS, 5, 5 }, { IPL_LIFE_CURSE, 50, 50 }, { IPL_FIREDAM, 2, 12 }, { IPL_INVCURS, 163 } } }, -{ N_("Crackrust"), UITYPE_MACE, 1, 5, 11375, { { IPL_ATTRIBS, 2, 2 }, { IPL_INDESTRUCTIBLE }, { IPL_ALLRES, 15, 15 }, { IPL_DAMP, 50, 50 }, { IPL_SPLLVLADD, -1, -1 }, { } } }, -{ N_("Hammer of Jholm"), UITYPE_MAUL, 1, 4, 8700, { { IPL_DAMP, 4, 10 }, { IPL_INDESTRUCTIBLE }, { IPL_STR, 3, 3 }, { IPL_TOHIT, 15, 15 }, { }, { } } }, -{ N_("Civerb's Cudgel"), UITYPE_MACE, 1, 3, 2000, { { IPL_3XDAMVDEM, 35, 35 }, { IPL_DEX_CURSE, 5, 5 }, { IPL_MAG_CURSE, 2, 2 }, { }, { }, { } } }, -{ N_("The Celestial Star"), UITYPE_FLAIL, 2, 5, 7810, { { IPL_NOMINSTR }, { IPL_LIGHT, 2, 2 }, { IPL_DAMMOD, 10, 10 }, { IPL_AC_CURSE, 8, 8 }, { IPL_INVCURS, 131 }, { } } }, -{ N_("Baranar's Star"), UITYPE_MORNSTAR, 5, 6, 6850, { { IPL_TOHIT, 12, 12 }, { IPL_DAMP, 80, 80 }, { IPL_FASTATTACK, 1, 1 }, { IPL_VIT, 4, 4 }, { IPL_DEX_CURSE, 4, 4 }, { IPL_SETDUR, 60, 60 } } }, -{ N_("Gnarled Root"), UITYPE_SPIKCLUB, 9, 6, 9820, { { IPL_TOHIT, 20, 20 }, { IPL_DAMP, 300, 300 }, { IPL_DEX, 10, 10 }, { IPL_MAG, 5, 5 }, { IPL_ALLRES, 10, 10 }, { IPL_AC_CURSE, 10, 10 } } }, -{ N_("The Cranium Basher"), UITYPE_MAUL, 12, 6, 36500, { { IPL_DAMMOD, 20, 20 }, { IPL_STR, 15, 15 }, { IPL_INDESTRUCTIBLE }, { IPL_MANA_CURSE, 150, 150 }, { IPL_ALLRES, 5, 5 }, { IPL_INVCURS, 122 } } }, -{ N_("Schaefer's Hammer"), UITYPE_WARHAMMER, 16, 6, 56125, { { IPL_DAMP_CURSE, 100, 100 }, { IPL_LIGHTDAM, 1, 50 }, { IPL_LIFE, 50, 50 }, { IPL_TOHIT, 30, 30 }, { IPL_LIGHTRES, 80, 80 }, { IPL_LIGHT, 1, 1 } } }, -{ N_("Dreamflange"), UITYPE_MACE, 26, 5, 26450, { { IPL_MAG, 30, 30 }, { IPL_MANA, 50, 50 }, { IPL_MAGICRES, 50, 50 }, { IPL_LIGHT, 2, 2 }, { IPL_SPLLVLADD, 1, 1 }, { } } }, -{ N_("Staff of Shadows"), UITYPE_LONGSTAFF, 2, 5, 1250, { { IPL_MAG_CURSE, 10, 10 }, { IPL_TOHIT, 10, 10 }, { IPL_DAMP, 60, 60 }, { IPL_LIGHT_CURSE, 2, 2 }, { IPL_FASTATTACK, 1, 1 }, { } } }, -{ N_("Immolator"), UITYPE_LONGSTAFF, 4, 4, 3900, { { IPL_FIRERES, 20, 20 }, { IPL_FIREDAM, 4, 4 }, { IPL_MANA, 10, 10 }, { IPL_VIT_CURSE, 5, 5 }, { }, { } } }, -{ N_("Storm Spire"), UITYPE_WARSTAFF, 8, 4, 22500, { { IPL_LIGHTRES, 50, 50 }, { IPL_LIGHTDAM, 2, 8 }, { IPL_STR, 10, 10 }, { IPL_MAG_CURSE, 10, 10 }, { }, { } } }, -{ N_("Gleamsong"), UITYPE_SHORTSTAFF, 8, 4, 6520, { { IPL_MANA, 25, 25 }, { IPL_STR_CURSE, 3, 3 }, { IPL_VIT_CURSE, 3, 3 }, { IPL_SPELL, 10, 76 }, { }, { } } }, -{ N_("Thundercall"), UITYPE_COMPSTAFF, 14, 5, 22250, { { IPL_TOHIT, 35, 35 }, { IPL_LIGHTDAM, 1, 10 }, { IPL_SPELL, 3, 76 }, { IPL_LIGHTRES, 30, 30 }, { IPL_LIGHT, 2, 2 }, { } } }, -{ N_("The Protector"), UITYPE_SHORTSTAFF, 16, 6, 17240, { { IPL_VIT, 5, 5 }, { IPL_GETHIT, 5, 5 }, { IPL_SETAC, 40, 40 }, { IPL_SPELL, 2, 86 }, { IPL_THORNS, 1, 3 }, { IPL_INVCURS, 162 } } }, -{ N_("Naj's Puzzler"), UITYPE_LONGSTAFF, 18, 5, 34000, { { IPL_MAG, 20, 20 }, { IPL_DEX, 10, 10 }, { IPL_ALLRES, 20, 20 }, { IPL_SPELL, 23, 57 }, { IPL_LIFE_CURSE, 25, 25 }, { } } }, -{ N_("Mindcry"), UITYPE_QUARSTAFF, 20, 4, 41500, { { IPL_MAG, 15, 15 }, { IPL_SPELL, 13, 69 }, { IPL_ALLRES, 15, 15 }, { IPL_SPLLVLADD, 1, 1 }, { }, { } } }, -{ N_("Rod of Onan"), UITYPE_WARSTAFF, 22, 3, 44167, { { IPL_SPELL, 21, 50 }, { IPL_DAMP, 100, 100 }, { IPL_ATTRIBS, 5, 5 }, { }, { }, { } } }, -{ N_("Helm of Spirits"), UITYPE_HELM, 1, 2, 7525, { { IPL_STEALLIFE, 5, 5 }, { IPL_INVCURS, 77 }, { }, { }, { }, { } } }, -{ N_("Thinking Cap"), UITYPE_SKULLCAP, 6, 5, 2020, { { IPL_MANA, 30, 30 }, { IPL_SPLLVLADD, 2, 2 }, { IPL_ALLRES, 20, 20 }, { IPL_SETDUR, 1, 1 }, { IPL_INVCURS, 93 }, { } } }, -{ N_("OverLord's Helm"), UITYPE_HELM, 7, 6, 12500, { { IPL_STR, 20, 20 }, { IPL_DEX, 15, 15 }, { IPL_VIT, 5, 5 }, { IPL_MAG_CURSE, 20, 20 }, { IPL_SETDUR, 15, 15 }, { IPL_INVCURS, 99 } } }, -{ N_("Fool's Crest"), UITYPE_HELM, 12, 5, 10150, { { IPL_ATTRIBS_CURSE, 4, 4 }, { IPL_LIFE, 100, 100 }, { IPL_GETHIT_CURSE, 1, 6 }, { IPL_THORNS, 1, 3 }, { IPL_INVCURS, 80 }, { } } }, -{ N_("Gotterdamerung"), UITYPE_GREATHELM, 21, 6, 54900, { { IPL_ATTRIBS, 20, 20 }, { IPL_SETAC, 60, 60 }, { IPL_GETHIT, 4, 4 }, { IPL_ALLRESZERO }, { IPL_LIGHT_CURSE, 4, 4 }, { IPL_INVCURS, 85 } } }, -{ N_("Royal Circlet"), UITYPE_CROWN, 27, 5, 24875, { { IPL_ATTRIBS, 10, 10 }, { IPL_MANA, 40, 40 }, { IPL_SETAC, 40, 40 }, { IPL_LIGHT, 1, 1 }, { IPL_INVCURS, 79 }, { } } }, -{ N_("Torn Flesh of Souls"), UITYPE_RAGS, 2, 5, 4825, { { IPL_SETAC, 8, 8 }, { IPL_VIT, 10, 10 }, { IPL_GETHIT, 1, 1 }, { IPL_INDESTRUCTIBLE }, { IPL_INVCURS, 92 }, { } } }, -{ N_("The Gladiator's Bane"), UITYPE_STUDARMOR, 6, 4, 3450, { { IPL_SETAC, 25, 25 }, { IPL_GETHIT, 2, 2 }, { IPL_DUR, 200, 200 }, { IPL_ATTRIBS_CURSE, 3, 3 }, { }, { } } }, -{ N_("The Rainbow Cloak"), UITYPE_CLOAK, 2, 6, 4900, { { IPL_SETAC, 10, 10 }, { IPL_ATTRIBS, 1, 1 }, { IPL_ALLRES, 10, 10 }, { IPL_LIFE, 5, 5 }, { IPL_DUR, 50, 50 }, { IPL_INVCURS, 138 } } }, -{ N_("Leather of Aut"), UITYPE_LEATHARMOR, 4, 5, 10550, { { IPL_SETAC, 15, 15 }, { IPL_STR, 5, 5 }, { IPL_MAG_CURSE, 5, 5 }, { IPL_DEX, 5, 5 }, { IPL_INDESTRUCTIBLE }, { } } }, -{ N_("Wisdom's Wrap"), UITYPE_ROBE, 5, 6, 6200, { { IPL_MAG, 5, 5 }, { IPL_MANA, 10, 10 }, { IPL_LIGHTRES, 25, 25 }, { IPL_SETAC, 15, 15 }, { IPL_GETHIT, 1, 1 }, { IPL_INVCURS, 138 } } }, -{ N_("Sparking Mail"), UITYPE_CHAINMAIL, 9, 2, 15750, { { IPL_SETAC, 30, 30 }, { IPL_LIGHTDAM, 1, 10 }, { }, { }, { }, { } } }, -{ N_("Scavenger Carapace"), UITYPE_BREASTPLATE, 13, 4, 14000, { { IPL_GETHIT, 15, 15 }, { IPL_AC_CURSE, 30, 30 }, { IPL_DEX, 5, 5 }, { IPL_LIGHTRES, 40, 40 }, { }, { } } }, -{ N_("Nightscape"), UITYPE_CAPE, 16, 6, 11600, { { IPL_FASTRECOVER, 2, 2 }, { IPL_LIGHT_CURSE, 4, 4 }, { IPL_SETAC, 15, 15 }, { IPL_DEX, 3, 3 }, { IPL_ALLRES, 20, 20 }, { IPL_INVCURS, 138 } } }, -{ N_("Naj's Light Plate"), UITYPE_PLATEMAIL, 19, 6, 78700, { { IPL_NOMINSTR }, { IPL_MAG, 5, 5 }, { IPL_MANA, 20, 20 }, { IPL_ALLRES, 20, 20 }, { IPL_SPLLVLADD, 1, 1 }, { IPL_INVCURS, 159 } } }, -{ N_("Demonspike Coat"), UITYPE_FULLPLATE, 25, 5, 251175, { { IPL_SETAC, 100, 100 }, { IPL_GETHIT, 6, 6 }, { IPL_STR, 10, 10 }, { IPL_INDESTRUCTIBLE }, { IPL_FIRERES, 50, 50 }, { } } }, -{ N_("The Deflector"), UITYPE_BUCKLER, 1, 5, 1500, { { IPL_SETAC, 7, 7 }, { IPL_ALLRES, 10, 10 }, { IPL_DAMP_CURSE, 20, 20 }, { IPL_TOHIT_CURSE, 5, 5 }, { IPL_INVCURS, 83 }, { } } }, -{ N_("Split Skull Shield"), UITYPE_BUCKLER, 1, 6, 2025, { { IPL_SETAC, 10, 10 }, { IPL_LIFE, 10, 10 }, { IPL_STR, 2, 2 }, { IPL_LIGHT_CURSE, 1, 1 }, { IPL_SETDUR, 15, 15 }, { IPL_INVCURS, 116 } } }, -{ N_("Dragon's Breach"), UITYPE_KITESHIELD, 2, 6, 19200, { { IPL_FIRERES, 25, 25 }, { IPL_STR, 5, 5 }, { IPL_SETAC, 20, 20 }, { IPL_MAG_CURSE, 5, 5 }, { IPL_INDESTRUCTIBLE }, { IPL_INVCURS, 117 } } }, -{ N_("Blackoak Shield"), UITYPE_SMALLSHIELD, 4, 6, 5725, { { IPL_DEX, 10, 10 }, { IPL_VIT_CURSE, 10, 10 }, { IPL_SETAC, 18, 18 }, { IPL_LIGHT_CURSE, 1, 1 }, { IPL_DUR, 150, 150 }, { IPL_INVCURS, 146 } } }, -{ N_("Holy Defender"), UITYPE_LARGESHIELD, 10, 6, 13800, { { IPL_SETAC, 15, 15 }, { IPL_GETHIT, 2, 2 }, { IPL_FIRERES, 20, 20 }, { IPL_DUR, 200, 200 }, { IPL_FASTBLOCK, 1, 1 }, { IPL_INVCURS, 146 } } }, -{ N_("Stormshield"), UITYPE_GOTHSHIELD, 24, 6, 49000, { { IPL_SETAC, 40, 40 }, { IPL_GETHIT_CURSE, 4, 4 }, { IPL_STR, 10, 10 }, { IPL_INDESTRUCTIBLE }, { IPL_FASTBLOCK, 1, 1 }, { IPL_INVCURS, 148 } } }, -{ N_("Bramble"), UITYPE_RING, 1, 4, 1000, { { IPL_ATTRIBS_CURSE, 2, 2 }, { IPL_DAMMOD, 3, 3 }, { IPL_MANA, 10, 10 }, { IPL_INVCURS, 9 }, { }, { } } }, -{ N_("Ring of Regha"), UITYPE_RING, 1, 6, 4175, { { IPL_MAG, 10, 10 }, { IPL_MAGICRES, 10, 10 }, { IPL_LIGHT, 1, 1 }, { IPL_STR_CURSE, 3, 3 }, { IPL_DEX_CURSE, 3, 3 }, { IPL_INVCURS, 11 } } }, -{ N_("The Bleeder"), UITYPE_RING, 2, 4, 8500, { { IPL_MAGICRES, 20, 20 }, { IPL_MANA, 30, 30 }, { IPL_LIFE_CURSE, 10, 10 }, { IPL_INVCURS, 8 }, { }, { } } }, -{ N_("Constricting Ring"), UITYPE_RING, 5, 3, 62000, { { IPL_ALLRES, 75, 75 }, { IPL_DRAINLIFE }, { IPL_INVCURS, 14 }, { }, { }, { } } }, -{ N_("Ring of Engagement"), UITYPE_RING, 11, 5, 12476, { { IPL_GETHIT, 1, 2 }, { IPL_THORNS, 1, 3 }, { IPL_SETAC, 5, 5 }, { IPL_TARGAC, 2, 2 }, { IPL_INVCURS, 13 }, { } } }, -{ N_("Giant's Knuckle"), UITYPE_RING, 8, 3, 8000, { { IPL_STR, 60, 60 }, { IPL_DEX_CURSE, 30, 30 }, { IPL_INVCURS, 179 }, { }, { }, { } } }, -{ N_("Mercurial Ring"), UITYPE_RING, 8, 3, 8000, { { IPL_DEX, 60, 60 }, { IPL_STR_CURSE, 30, 30 }, { IPL_INVCURS, 176 }, { }, { }, { } } }, -{ N_("Xorine's Ring"), UITYPE_RING, 8, 3, 8000, { { IPL_MAG, 60, 60 }, { IPL_STR_CURSE, 30, 30 }, { IPL_INVCURS, 168 }, { }, { }, { } } }, -{ N_("Karik's Ring"), UITYPE_RING, 8, 3, 8000, { { IPL_VIT, 60, 60 }, { IPL_MAG_CURSE, 30, 30 }, { IPL_INVCURS, 173 }, { }, { }, { } } }, -{ N_("Ring of Magma"), UITYPE_RING, 8, 4, 8000, { { IPL_FIRERES, 60, 60 }, { IPL_LIGHTRES_CURSE, 30, 30 }, { IPL_MAGICRES_CURSE, 30, 30 }, { IPL_INVCURS, 184 }, { }, { } } }, -{ N_("Ring of the Mystics"), UITYPE_RING, 8, 4, 8000, { { IPL_MAGICRES, 60, 60 }, { IPL_FIRERES_CURSE, 30, 30 }, { IPL_LIGHTRES_CURSE, 30, 30 }, { IPL_INVCURS, 181 }, { }, { } } }, -{ N_("Ring of Thunder"), UITYPE_RING, 8, 4, 8000, { { IPL_LIGHTRES, 60, 60 }, { IPL_FIRERES_CURSE, 30, 30 }, { IPL_MAGICRES_CURSE, 30, 30 }, { IPL_INVCURS, 177 }, { }, { } } }, -{ N_("Amulet of Warding"), UITYPE_AMULET, 12, 3, 30000, { { IPL_ALLRES, 40, 40 }, { IPL_LIFE_CURSE, 100, 100 }, { IPL_INVCURS, 170 }, { }, { }, { } } }, -{ N_("Gnat Sting"), UITYPE_HUNTBOW, 15, 5, 30000, { { IPL_MULT_ARROWS, 3, 3 }, { IPL_SETDAM, 1, 2 }, { IPL_FASTATTACK, 1, 1 }, { IPL_INDESTRUCTIBLE }, { IPL_INVCURS, 210 }, { } } }, -{ N_("Flambeau"), UITYPE_COMPBOW, 11, 4, 30000, { { IPL_FIREBALL, 15, 20 }, { IPL_SETDAM, 0, 0 }, { IPL_INDESTRUCTIBLE }, { IPL_INVCURS, 209 }, { }, { } } }, -{ N_("Armor of Gloom"), UITYPE_FULLPLATE, 25, 5, 200000, { { IPL_NOMINSTR }, { IPL_SETAC, 225, 225 }, { IPL_ALLRESZERO }, { IPL_LIGHT_CURSE, 2, 2 }, { IPL_INVCURS, 203 }, { } } }, -{ N_("Blitzen"), UITYPE_COMPBOW, 13, 4, 30000, { { IPL_ADDACLIFE, 10, 15 }, { IPL_SETDAM, 0, 0 }, { IPL_INDESTRUCTIBLE }, { IPL_INVCURS, 219 }, { }, { } } }, -{ N_("Thunderclap"), UITYPE_WARHAMMER, 13, 6, 30000, { { IPL_ADDMANAAC, 3, 6 }, { IPL_STR, 20, 20 }, { IPL_LIGHTRES, 30, 30 }, { IPL_LIGHT, 2, 2 }, { IPL_INDESTRUCTIBLE }, { IPL_INVCURS, 205 } } }, -{ N_("Shirotachi"), UITYPE_GREATSWR, 21, 4, 36000, { { IPL_ONEHAND }, { IPL_FASTATTACK, 4, 4 }, { IPL_TARGAC, 2, 2 }, { IPL_LIGHTDAM, 6, 6 }, { }, { } } }, -{ N_("Eater of Souls"), UITYPE_TWOHANDSWR, 23, 6, 42000, { { IPL_INDESTRUCTIBLE }, { IPL_LIFE, 50, 50 }, { IPL_STEALLIFE, 5, 5 }, { IPL_STEALMANA, 5, 5 }, { IPL_DRAINLIFE }, { IPL_INVCURS, 200 } } }, -{ N_("Diamondedge"), UITYPE_LONGSWR, 17, 6, 42000, { { IPL_SETDUR, 10, 10 }, { IPL_TOHIT, 50, 50 }, { IPL_DAMP, 100, 100 }, { IPL_LIGHTRES, 50, 50 }, { IPL_SETAC, 10, 10 }, { IPL_INVCURS, 206 } } }, -{ N_("Bone Chain Armor"), UITYPE_CHAINMAIL, 13, 3, 36000, { { IPL_SETAC, 40, 40 }, { IPL_ACUNDEAD }, { IPL_INVCURS, 204 }, { }, { }, { } } }, -{ N_("Demon Plate Armor"), UITYPE_FULLPLATE, 25, 3, 80000, { { IPL_SETAC, 80, 80 }, { IPL_ACDEMON }, { IPL_INVCURS, 225 }, { }, { }, { } } }, -{ N_("Acolyte's Amulet"), UITYPE_AMULET, 10, 2, 10000, { { IPL_MANATOLIFE, 50, 50 }, { IPL_INVCURS, 183 }, { }, { }, { }, { } } }, - // TRANSLATORS: Unique Item section end. -{ N_("Gladiator's Ring"), UITYPE_RING, 10, 2, 10000, { { IPL_LIFETOMANA, 40, 40 }, { IPL_INVCURS, 186 }, { }, { }, { }, { } } }, -{ "", UITYPE_INVALID, 0, 0, 0, { { }, { }, { }, { }, { }, { } } }, - // clang-format on -}; + // powers (up to 6) + item.UINumPL = 0; + for (size_t i = 0; i < 6; ++i) { + if (reader.value().empty()) + break; + ReadItemPower(reader, StrCat("power", i), item.powers[item.UINumPL++]); + } + } + UniqueItems.shrink_to_fit(); +} + +void LoadItemAffixesDat(std::string_view filename, std::vector &out) +{ + DataFile dataFile = DataFile::loadOrDie(filename); + dataFile.skipHeaderOrDie(filename); + + out.clear(); + out.reserve(dataFile.numRecords()); + for (DataFileRecord record : dataFile) { + RecordReader reader { record, filename }; + PLStruct &item = out.emplace_back(); + reader.readString("name", item.PLName); + ReadItemPower(reader, "power", item.power); + reader.readInt("minLevel", item.PLMinLvl); + reader.readEnumList("itemTypes", item.PLIType, ParseAffixItemType); + reader.read("alignment", item.PLGOE, ParseAffixAlignment); + reader.readBool("doubleChance", item.PLDouble); + reader.readBool("useful", item.PLOk); + reader.readInt("minVal", item.minVal); + reader.readInt("maxVal", item.maxVal); + reader.readInt("multVal", item.multVal); + } + out.shrink_to_fit(); +} + +} // namespace + +void LoadItemData() +{ + LoadItemDat(); + LoadUniqueItemDat(); + LoadItemAffixesDat("txtdata\\items\\item_prefixes.tsv", ItemPrefixes); + LoadItemAffixesDat("txtdata\\items\\item_suffixes.tsv", ItemSuffixes); +} + +std::string_view ItemTypeToString(ItemType itemType) +{ + switch (itemType) { + case ItemType::Misc: return "Misc"; + case ItemType::Sword: return "Sword"; + case ItemType::Axe: return "Axe"; + case ItemType::Bow: return "Bow"; + case ItemType::Mace: return "Mace"; + case ItemType::Shield: return "Shield"; + case ItemType::LightArmor: return "LightArmor"; + case ItemType::Helm: return "Helm"; + case ItemType::MediumArmor: return "MediumArmor"; + case ItemType::HeavyArmor: return "HeavyArmor"; + case ItemType::Staff: return "Staff"; + case ItemType::Gold: return "Gold"; + case ItemType::Ring: return "Ring"; + case ItemType::Amulet: return "Amulet"; + case ItemType::None: return "None"; + } + return ""; +} } // namespace devilution diff --git a/Source/itemdat.h b/Source/itemdat.h index 24dc2efd52d..adeb35221b0 100644 --- a/Source/itemdat.h +++ b/Source/itemdat.h @@ -6,10 +6,11 @@ #pragma once #include +#include +#include #include "objdat.h" #include "spelldat.h" -#include "utils/stdcompat/string_view.hpp" namespace devilution { @@ -253,7 +254,7 @@ enum class ItemType : int8_t { None = -1, }; -string_view ItemTypeToString(ItemType itemType); +std::string_view ItemTypeToString(ItemType itemType); enum unique_base_item : int8_t { UITYPE_NONE, @@ -443,8 +444,8 @@ struct ItemData { enum item_cursor_graphic iCurs; enum ItemType itype; enum unique_base_item iItemId; - const char *iName; - const char *iSName; + std::string iName; + std::string iSName; uint8_t iMinMLvl; uint8_t iDurability; uint8_t iMinDam; @@ -572,7 +573,7 @@ struct ItemPower { }; struct PLStruct { - const char *PLName; + std::string PLName; ItemPower power; int8_t PLMinLvl; AffixItemType PLIType; // AffixItemType as bit flags @@ -585,7 +586,7 @@ struct PLStruct { }; struct UniqueItem { - const char *UIName; + std::string UIName; enum unique_base_item UIItemId; int8_t UIMinLvl; uint8_t UINumPL; @@ -593,9 +594,11 @@ struct UniqueItem { ItemPower powers[6]; }; -extern const ItemData AllItemsList[]; -extern const PLStruct ItemPrefixes[]; -extern const PLStruct ItemSuffixes[]; -extern const UniqueItem UniqueItems[]; +extern std::vector AllItemsList; +extern std::vector ItemPrefixes; +extern std::vector ItemSuffixes; +extern std::vector UniqueItems; + +void LoadItemData(); } // namespace devilution diff --git a/Source/items.cpp b/Source/items.cpp index 86e02f4d614..f136bff40df 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -45,7 +45,6 @@ #include "utils/language.h" #include "utils/log.hpp" #include "utils/math.h" -#include "utils/stdcompat/algorithm.hpp" #include "utils/str_case.hpp" #include "utils/str_cat.hpp" #include "utils/utf8.hpp" @@ -89,50 +88,50 @@ int8_t ItemCAnimTbl[] = { }; /** Maps of drop sounds effect of placing the item in the inventory. */ -_sfx_id ItemInvSnds[] = { - IS_IHARM, - IS_IAXE, - IS_IPOT, - IS_IBOW, - IS_GOLD, - IS_ICAP, - IS_ISWORD, - IS_ISHIEL, - IS_ISWORD, - IS_IROCK, - IS_IAXE, - IS_ISTAF, - IS_IRING, - IS_ICAP, - IS_ILARM, - IS_ISHIEL, - IS_ISCROL, - IS_IHARM, - IS_IBOOK, - IS_IHARM, - IS_IPOT, - IS_IPOT, - IS_IPOT, - IS_IPOT, - IS_IPOT, - IS_IPOT, - IS_IPOT, - IS_IPOT, - IS_IBODY, - IS_IBODY, - IS_IMUSH, - IS_ISIGN, - IS_IBLST, - IS_IANVL, - IS_ISTAF, - IS_IROCK, - IS_ISCROL, - IS_ISCROL, - IS_IROCK, - IS_IMUSH, - IS_IHARM, - IS_ILARM, - IS_ILARM, +SfxID ItemInvSnds[] = { + SfxID::ItemArmor, + SfxID::ItemAxe, + SfxID::ItemPotion, + SfxID::ItemBow, + SfxID::ItemGold, + SfxID::ItemCap, + SfxID::ItemSword, + SfxID::ItemShield, + SfxID::ItemSword, + SfxID::ItemRock, + SfxID::ItemAxe, + SfxID::ItemStaff, + SfxID::ItemRing, + SfxID::ItemCap, + SfxID::ItemLeather, + SfxID::ItemShield, + SfxID::ItemScroll, + SfxID::ItemArmor, + SfxID::ItemBook, + SfxID::ItemArmor, + SfxID::ItemPotion, + SfxID::ItemPotion, + SfxID::ItemPotion, + SfxID::ItemPotion, + SfxID::ItemPotion, + SfxID::ItemPotion, + SfxID::ItemPotion, + SfxID::ItemPotion, + SfxID::ItemBodyPart, + SfxID::ItemBodyPart, + SfxID::ItemMushroom, + SfxID::ItemSign, + SfxID::ItemBloodStone, + SfxID::ItemAnvil, + SfxID::ItemStaff, + SfxID::ItemRock, + SfxID::ItemScroll, + SfxID::ItemScroll, + SfxID::ItemRock, + SfxID::ItemMushroom, + SfxID::ItemArmor, + SfxID::ItemLeather, + SfxID::ItemLeather, }; namespace { @@ -277,50 +276,50 @@ int8_t ItemAnimLs[] = { 15, }; /** Maps of drop sounds effect of dropping the item on ground. */ -_sfx_id ItemDropSnds[] = { - IS_FHARM, - IS_FAXE, - IS_FPOT, - IS_FBOW, - IS_GOLD, - IS_FCAP, - IS_FSWOR, - IS_FSHLD, - IS_FSWOR, - IS_FROCK, - IS_FAXE, - IS_FSTAF, - IS_FRING, - IS_FCAP, - IS_FLARM, - IS_FSHLD, - IS_FSCRL, - IS_FHARM, - IS_FBOOK, - IS_FLARM, - IS_FPOT, - IS_FPOT, - IS_FPOT, - IS_FPOT, - IS_FPOT, - IS_FPOT, - IS_FPOT, - IS_FPOT, - IS_FBODY, - IS_FBODY, - IS_FMUSH, - IS_FSIGN, - IS_FBLST, - IS_FANVL, - IS_FSTAF, - IS_FROCK, - IS_FSCRL, - IS_FSCRL, - IS_FROCK, - IS_FMUSH, - IS_FHARM, - IS_FLARM, - IS_FLARM, +SfxID ItemDropSnds[] = { + SfxID::ItemArmorFlip, + SfxID::ItemAxeFlip, + SfxID::ItemPotionFlip, + SfxID::ItemBowFlip, + SfxID::ItemGold, + SfxID::ItemCapFlip, + SfxID::ItemSwordFlip, + SfxID::ItemShieldFlip, + SfxID::ItemSwordFlip, + SfxID::ItemRockFlip, + SfxID::ItemAxeFlip, + SfxID::ItemStaffFlip, + SfxID::ItemRingFlip, + SfxID::ItemCapFlip, + SfxID::ItemLeatherFlip, + SfxID::ItemShieldFlip, + SfxID::ItemScrollFlip, + SfxID::ItemArmorFlip, + SfxID::ItemBookFlip, + SfxID::ItemLeatherFlip, + SfxID::ItemPotionFlip, + SfxID::ItemPotionFlip, + SfxID::ItemPotionFlip, + SfxID::ItemPotionFlip, + SfxID::ItemPotionFlip, + SfxID::ItemPotionFlip, + SfxID::ItemPotionFlip, + SfxID::ItemPotionFlip, + SfxID::ItemBodyPartFlip, + SfxID::ItemBodyPartFlip, + SfxID::ItemMushroomFlip, + SfxID::ItemSignFlip, + SfxID::ItemBloodStoneFlip, + SfxID::ItemAnvilFlip, + SfxID::ItemStaffFlip, + SfxID::ItemRockFlip, + SfxID::ItemScrollFlip, + SfxID::ItemScrollFlip, + SfxID::ItemRockFlip, + SfxID::ItemMushroomFlip, + SfxID::ItemArmorFlip, + SfxID::ItemLeatherFlip, + SfxID::ItemLeatherFlip, }; /** Maps from Griswold premium item number to a quality level delta as added to the base quality level. */ int premiumlvladd[] = { @@ -651,9 +650,9 @@ void GetBookSpell(Item &item, int lvl) if (s == maxSpells) s = 1; } - const string_view spellName = GetSpellData(bs).sNameText; - const size_t iNameLen = string_view(item._iName).size(); - const size_t iINameLen = string_view(item._iIName).size(); + const std::string_view spellName = GetSpellData(bs).sNameText; + const size_t iNameLen = std::string_view(item._iName).size(); + const size_t iINameLen = std::string_view(item._iIName).size(); CopyUtf8(item._iName + iNameLen, spellName, sizeof(item._iName) - iNameLen); CopyUtf8(item._iIName + iINameLen, spellName, sizeof(item._iIName) - iINameLen); item._iSpell = bs; @@ -1087,7 +1086,7 @@ int GetStaffPrefixId(int lvl, bool onlygood, bool hellfireItem) if (FlipCoin(10) || onlygood) { int nl = 0; int l[256]; - for (int j = 0; ItemPrefixes[j].power.type != IPL_INVALID; j++) { + for (int j = 0, n = static_cast(ItemPrefixes.size()); j < n; ++j) { if (!IsPrefixValidForItemType(j, AffixItemType::Staff, hellfireItem) || ItemPrefixes[j].PLMinLvl > lvl) continue; if (onlygood && !ItemPrefixes[j].PLOk) @@ -1108,12 +1107,12 @@ int GetStaffPrefixId(int lvl, bool onlygood, bool hellfireItem) std::string GenerateStaffName(const ItemData &baseItemData, SpellID spellId, bool translate) { - string_view baseName = translate ? _(baseItemData.iName) : baseItemData.iName; - string_view spellName = translate ? pgettext("spell", GetSpellData(spellId).sNameText) : GetSpellData(spellId).sNameText; - string_view normalFmt = translate ? pgettext("spell", /* TRANSLATORS: Constructs item names. Format: {Item} of {Spell}. Example: War Staff of Firewall */ "{0} of {1}") : "{0} of {1}"; + std::string_view baseName = translate ? _(baseItemData.iName) : baseItemData.iName; + std::string_view spellName = translate ? pgettext("spell", GetSpellData(spellId).sNameText) : GetSpellData(spellId).sNameText; + std::string_view normalFmt = translate ? pgettext("spell", /* TRANSLATORS: Constructs item names. Format: {Item} of {Spell}. Example: War Staff of Firewall */ "{0} of {1}") : "{0} of {1}"; std::string name = fmt::format(fmt::runtime(normalFmt), baseName, spellName); if (!StringInPanel(name.c_str())) { - string_view shortName = translate ? _(baseItemData.iSName) : baseItemData.iSName; + std::string_view shortName = translate ? _(baseItemData.iSName) : baseItemData.iSName; name = fmt::format(fmt::runtime(normalFmt), shortName, spellName); } return name; @@ -1121,14 +1120,14 @@ std::string GenerateStaffName(const ItemData &baseItemData, SpellID spellId, boo std::string GenerateStaffNameMagical(const ItemData &baseItemData, SpellID spellId, int preidx, bool translate, std::optional forceNameLengthCheck) { - string_view baseName = translate ? _(baseItemData.iName) : baseItemData.iName; - string_view magicFmt = translate ? pgettext("spell", /* TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Spell}. Example: King's War Staff of Firewall */ "{0} {1} of {2}") : "{0} {1} of {2}"; - string_view spellName = translate ? pgettext("spell", GetSpellData(spellId).sNameText) : GetSpellData(spellId).sNameText; - string_view prefixName = translate ? _(ItemPrefixes[preidx].PLName) : ItemPrefixes[preidx].PLName; + std::string_view baseName = translate ? _(baseItemData.iName) : baseItemData.iName; + std::string_view magicFmt = translate ? pgettext("spell", /* TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Spell}. Example: King's War Staff of Firewall */ "{0} {1} of {2}") : "{0} {1} of {2}"; + std::string_view spellName = translate ? pgettext("spell", GetSpellData(spellId).sNameText) : GetSpellData(spellId).sNameText; + std::string_view prefixName = translate ? _(ItemPrefixes[preidx].PLName) : ItemPrefixes[preidx].PLName; std::string identifiedName = fmt::format(fmt::runtime(magicFmt), prefixName, baseName, spellName); if (forceNameLengthCheck ? *forceNameLengthCheck : !StringInPanel(identifiedName.c_str())) { - string_view shortName = translate ? _(baseItemData.iSName) : baseItemData.iSName; + std::string_view shortName = translate ? _(baseItemData.iSName) : baseItemData.iSName; identifiedName = fmt::format(fmt::runtime(magicFmt), prefixName, shortName, spellName); } return identifiedName; @@ -1157,16 +1156,16 @@ void GetStaffPower(const Player &player, Item &item, int lvl, SpellID bs, bool o CalcItemValue(item); } -std::string GenerateMagicItemName(const string_view &baseNamel, const PLStruct *pPrefix, const PLStruct *pSufix, bool translate) +std::string GenerateMagicItemName(const std::string_view &baseNamel, const PLStruct *pPrefix, const PLStruct *pSufix, bool translate) { if (pPrefix != nullptr && pSufix != nullptr) { - string_view fmt = translate ? _(/* TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Suffix}. Example: King's Long Sword of the Whale */ "{0} {1} of {2}") : "{0} {1} of {2}"; + std::string_view fmt = translate ? _(/* TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Suffix}. Example: King's Long Sword of the Whale */ "{0} {1} of {2}") : "{0} {1} of {2}"; return fmt::format(fmt::runtime(fmt), translate ? _(pPrefix->PLName) : pPrefix->PLName, baseNamel, translate ? _(pSufix->PLName) : pSufix->PLName); } else if (pPrefix != nullptr) { - string_view fmt = translate ? _(/* TRANSLATORS: Constructs item names. Format: {Prefix} {Item}. Example: King's Long Sword */ "{0} {1}") : "{0} {1}"; + std::string_view fmt = translate ? _(/* TRANSLATORS: Constructs item names. Format: {Prefix} {Item}. Example: King's Long Sword */ "{0} {1}") : "{0} {1}"; return fmt::format(fmt::runtime(fmt), translate ? _(pPrefix->PLName) : pPrefix->PLName, baseNamel); } else if (pSufix != nullptr) { - string_view fmt = translate ? _(/* TRANSLATORS: Constructs item names. Format: {Item} of {Suffix}. Example: Long Sword of the Whale */ "{0} of {1}") : "{0} of {1}"; + std::string_view fmt = translate ? _(/* TRANSLATORS: Constructs item names. Format: {Item} of {Suffix}. Example: Long Sword of the Whale */ "{0} of {1}") : "{0} of {1}"; return fmt::format(fmt::runtime(fmt), baseNamel, translate ? _(pSufix->PLName) : pSufix->PLName); } @@ -1195,7 +1194,7 @@ void GetItemPowerPrefixAndSuffix(int minlvl, int maxlvl, AffixItemType flgs, boo onlygood = true; if (allocatePrefix) { int nt = 0; - for (int j = 0; ItemPrefixes[j].power.type != IPL_INVALID; j++) { + for (int j = 0, n = static_cast(ItemPrefixes.size()); j < n; ++j) { if (!IsPrefixValidForItemType(j, flgs, hellfireItem)) continue; if (ItemPrefixes[j].PLMinLvl < minlvl || ItemPrefixes[j].PLMinLvl > maxlvl) @@ -1219,7 +1218,7 @@ void GetItemPowerPrefixAndSuffix(int minlvl, int maxlvl, AffixItemType flgs, boo } if (allocateSuffix) { int nl = 0; - for (int j = 0; ItemSuffixes[j].power.type != IPL_INVALID; j++) { + for (int j = 0, n = static_cast(ItemSuffixes.size()); j < n; ++j) { if (IsSuffixValidForItemType(j, flgs, hellfireItem) && ItemSuffixes[j].PLMinLvl >= minlvl && ItemSuffixes[j].PLMinLvl <= maxlvl && !((goe == GOE_GOOD && ItemSuffixes[j].PLGOE == GOE_EVIL) || (goe == GOE_EVIL && ItemSuffixes[j].PLGOE == GOE_GOOD)) @@ -1320,7 +1319,7 @@ void GetOilType(Item &item, int maxLvl) cnt = 0; for (size_t j = 0; j < sizeof(OilLevels) / sizeof(OilLevels[0]); j++) { if (OilLevels[j] <= maxLvl) { - rnd[cnt] = j; + rnd[cnt] = static_cast(j); cnt++; } } @@ -1452,7 +1451,7 @@ _unique_items CheckUnique(Item &item, int lvl, int uper, bool recreate) return UITEM_INVALID; int numu = 0; - for (int j = 0; UniqueItems[j].UIItemId != UITYPE_INVALID; j++) { + for (int j = 0, n = static_cast(UniqueItems.size()); j < n; ++j) { if (!IsUniqueAvailable(j)) break; if (UniqueItems[j].UIItemId == AllItemsList[item.IDidx].iItemId @@ -1825,8 +1824,8 @@ void printItemMiscGenericGamepad(const Item &item, const bool isOil, bool isCast void printItemMiscGamepad(const Item &item, bool isOil, bool isCastOnTarget) { - string_view activateButton; - string_view castButton; + std::string_view activateButton; + std::string_view castButton; switch (GamepadType) { case GamepadLayout::Generic: printItemMiscGenericGamepad(item, isOil, isCastOnTarget); @@ -1992,7 +1991,7 @@ void SpawnOnePremium(Item &premiumItem, int plvl, const Player &player) dexterity += dexterity / 5; magic += magic / 5; - plvl = clamp(plvl, 1, 30); + plvl = std::clamp(plvl, 1, 30); int maxCount = 150; const bool unlimited = !gbIsHellfire; // TODO: This could lead to an infinite loop if a suitable item can never be generated @@ -2276,7 +2275,7 @@ StringOrView GetTranslatedItemName(const Item &item) return _(baseItemData.iName); } else if (item._iMiscId == IMISC_BOOK) { std::string name; - const string_view spellName = pgettext("spell", GetSpellData(item._iSpell).sNameText); + const std::string_view spellName = pgettext("spell", GetSpellData(item._iSpell).sNameText); StrAppend(name, _(baseItemData.iName)); StrAppend(name, spellName); return name; @@ -2364,6 +2363,7 @@ std::string GetTranslatedItemNameMagical(const Item &item, bool hellfireItem, bo affixItemType = AffixItemType::Staff; else if (!hellfireItem && FlipCoin(4)) { affixItemType = AffixItemType::Staff; + minlvl = maxlvl / 2; } else { DiscardRandomValues(2); // Spell and Charges @@ -2624,6 +2624,8 @@ void CalcPlrItemVals(Player &player, bool loadgfx) } } + const uint8_t playerLevel = player.getCharacterLevel(); + if (mind == 0 && maxd == 0) { mind = 1; maxd = 1; @@ -2637,20 +2639,20 @@ void CalcPlrItemVals(Player &player, bool loadgfx) } if (player._pClass == HeroClass::Monk) { - mind = std::max(mind, player._pLevel / 2); - maxd = std::max(maxd, (int)player._pLevel); + mind = std::max(mind, playerLevel / 2); + maxd = std::max(maxd, playerLevel); } } if (HasAnyOf(player._pSpellFlags, SpellFlag::RageActive)) { - sadd += 2 * player._pLevel; - dadd += player._pLevel + player._pLevel / 2; - vadd += 2 * player._pLevel; + sadd += 2 * playerLevel; + dadd += playerLevel + playerLevel / 2; + vadd += 2 * playerLevel; } if (HasAnyOf(player._pSpellFlags, SpellFlag::RageCooldown)) { - sadd -= 2 * player._pLevel; - dadd -= player._pLevel + player._pLevel / 2; - vadd -= 2 * player._pLevel; + sadd -= 2 * playerLevel; + dadd -= playerLevel + playerLevel / 2; + vadd -= 2 * playerLevel; } player._pIMinDam = mind; @@ -2664,7 +2666,7 @@ void CalcPlrItemVals(Player &player, bool loadgfx) player._pIBonusDamMod = dmod; player._pIGetHit = ghit; - lrad = clamp(lrad, 2, 15); + lrad = std::clamp(lrad, 2, 15); if (player._pLightRad != lrad) { ChangeLightRadius(player.lightId, lrad); @@ -2678,29 +2680,29 @@ void CalcPlrItemVals(Player &player, bool loadgfx) player._pVitality = std::max(0, vadd + player._pBaseVit); if (player._pClass == HeroClass::Rogue) { - player._pDamageMod = player._pLevel * (player._pStrength + player._pDexterity) / 200; + player._pDamageMod = playerLevel * (player._pStrength + player._pDexterity) / 200; } else if (player._pClass == HeroClass::Monk) { - player._pDamageMod = player._pLevel * (player._pStrength + player._pDexterity) / 150; + player._pDamageMod = playerLevel * (player._pStrength + player._pDexterity) / 150; if ((!player.InvBody[INVLOC_HAND_LEFT].isEmpty() && player.InvBody[INVLOC_HAND_LEFT]._itype != ItemType::Staff) || (!player.InvBody[INVLOC_HAND_RIGHT].isEmpty() && player.InvBody[INVLOC_HAND_RIGHT]._itype != ItemType::Staff)) player._pDamageMod /= 2; // Monks get half the normal damage bonus if they're holding a non-staff weapon } else if (player._pClass == HeroClass::Bard) { if (player.InvBody[INVLOC_HAND_LEFT]._itype == ItemType::Sword || player.InvBody[INVLOC_HAND_RIGHT]._itype == ItemType::Sword) - player._pDamageMod = player._pLevel * (player._pStrength + player._pDexterity) / 150; + player._pDamageMod = playerLevel * (player._pStrength + player._pDexterity) / 150; else if (player.InvBody[INVLOC_HAND_LEFT]._itype == ItemType::Bow || player.InvBody[INVLOC_HAND_RIGHT]._itype == ItemType::Bow) { - player._pDamageMod = player._pLevel * (player._pStrength + player._pDexterity) / 250; + player._pDamageMod = playerLevel * (player._pStrength + player._pDexterity) / 250; } else { - player._pDamageMod = player._pLevel * player._pStrength / 100; + player._pDamageMod = playerLevel * player._pStrength / 100; } } else if (player._pClass == HeroClass::Barbarian) { if (player.InvBody[INVLOC_HAND_LEFT]._itype == ItemType::Axe || player.InvBody[INVLOC_HAND_RIGHT]._itype == ItemType::Axe) { - player._pDamageMod = player._pLevel * player._pStrength / 75; + player._pDamageMod = playerLevel * player._pStrength / 75; } else if (player.InvBody[INVLOC_HAND_LEFT]._itype == ItemType::Mace || player.InvBody[INVLOC_HAND_RIGHT]._itype == ItemType::Mace) { - player._pDamageMod = player._pLevel * player._pStrength / 75; + player._pDamageMod = playerLevel * player._pStrength / 75; } else if (player.InvBody[INVLOC_HAND_LEFT]._itype == ItemType::Bow || player.InvBody[INVLOC_HAND_RIGHT]._itype == ItemType::Bow) { - player._pDamageMod = player._pLevel * player._pStrength / 300; + player._pDamageMod = playerLevel * player._pStrength / 300; } else { - player._pDamageMod = player._pLevel * player._pStrength / 100; + player._pDamageMod = playerLevel * player._pStrength / 100; } if (player.InvBody[INVLOC_HAND_LEFT]._itype == ItemType::Shield || player.InvBody[INVLOC_HAND_RIGHT]._itype == ItemType::Shield) { @@ -2709,11 +2711,11 @@ void CalcPlrItemVals(Player &player, bool loadgfx) else if (player.InvBody[INVLOC_HAND_RIGHT]._itype == ItemType::Shield) player._pIAC -= player.InvBody[INVLOC_HAND_RIGHT]._iAC / 2; } else if (IsNoneOf(player.InvBody[INVLOC_HAND_LEFT]._itype, ItemType::Staff, ItemType::Bow) && IsNoneOf(player.InvBody[INVLOC_HAND_RIGHT]._itype, ItemType::Staff, ItemType::Bow)) { - player._pDamageMod += player._pLevel * player._pVitality / 100; + player._pDamageMod += playerLevel * player._pVitality / 100; } - player._pIAC += player._pLevel / 4; + player._pIAC += playerLevel / 4; } else { - player._pDamageMod = player._pLevel * player._pStrength / 100; + player._pDamageMod = playerLevel * player._pStrength / 100; } player._pISpells = spl; @@ -2724,15 +2726,15 @@ void CalcPlrItemVals(Player &player, bool loadgfx) player._pIEnAc = enac; if (player._pClass == HeroClass::Barbarian) { - mr += player._pLevel; - fr += player._pLevel; - lr += player._pLevel; + mr += playerLevel; + fr += playerLevel; + lr += playerLevel; } if (HasAnyOf(player._pSpellFlags, SpellFlag::RageCooldown)) { - mr -= player._pLevel; - fr -= player._pLevel; - lr -= player._pLevel; + mr -= playerLevel; + fr -= playerLevel; + lr -= playerLevel; } if (HasAnyOf(iflgs, ItemSpecialEffect::ZeroResistance)) { @@ -2742,14 +2744,15 @@ void CalcPlrItemVals(Player &player, bool loadgfx) lr = 0; } - player._pMagResist = clamp(mr, 0, MaxResistance); - player._pFireResist = clamp(fr, 0, MaxResistance); - player._pLghtResist = clamp(lr, 0, MaxResistance); + player._pMagResist = std::clamp(mr, 0, MaxResistance); + player._pFireResist = std::clamp(fr, 0, MaxResistance); + player._pLghtResist = std::clamp(lr, 0, MaxResistance); - vadd = (vadd * PlayersData[static_cast(player._pClass)].itmLife) >> 6; + const ClassAttributes &playerClassAttributes = player.getClassAttributes(); + vadd = (vadd * playerClassAttributes.itmLife) >> 6; ihp += (vadd << 6); // BUGFIX: blood boil can cause negative shifts here (see line 757) - madd = (madd * PlayersData[static_cast(player._pClass)].itmMana) >> 6; + madd = (madd * playerClassAttributes.itmMana) >> 6; imana += (madd << 6); player._pMaxHP = ihp + player._pMaxHPBase; @@ -2834,18 +2837,18 @@ void CalcPlrItemVals(Player &player, bool loadgfx) PlayerArmorGraphic animArmorId = PlayerArmorGraphic::Light; if (player.InvBody[INVLOC_CHEST]._itype == ItemType::HeavyArmor && player.InvBody[INVLOC_CHEST]._iStatFlag) { if (player._pClass == HeroClass::Monk && player.InvBody[INVLOC_CHEST]._iMagical == ITEM_QUALITY_UNIQUE) - player._pIAC += player._pLevel / 2; + player._pIAC += playerLevel / 2; animArmorId = PlayerArmorGraphic::Heavy; } else if (player.InvBody[INVLOC_CHEST]._itype == ItemType::MediumArmor && player.InvBody[INVLOC_CHEST]._iStatFlag) { if (player._pClass == HeroClass::Monk) { if (player.InvBody[INVLOC_CHEST]._iMagical == ITEM_QUALITY_UNIQUE) - player._pIAC += player._pLevel * 2; + player._pIAC += playerLevel * 2; else - player._pIAC += player._pLevel / 2; + player._pIAC += playerLevel / 2; } animArmorId = PlayerArmorGraphic::Medium; } else if (player._pClass == HeroClass::Monk) { - player._pIAC += player._pLevel * 2; + player._pIAC += playerLevel * 2; } const uint8_t gfxNum = static_cast(animWeaponId) | static_cast(animArmorId); @@ -2970,6 +2973,17 @@ void SetPlrHandGoldCurs(Item &gold) gold._iCurs = GetGoldCursor(gold._ivalue); } +namespace { +void CreateStartingItem(Player &player, _item_indexes itemData) +{ + Item item; + InitializeItem(item, itemData); + GenerateNewSeed(item); + item.updateRequiredStatsCacheForPlayer(player); + AutoEquip(player, item) || AutoPlaceItemInBelt(player, item, true) || AutoPlaceItemInInventory(player, item, true); +} +} // namespace + void CreatePlrItems(Player &player) { for (auto &item : player.InvBody) { @@ -2990,90 +3004,40 @@ void CreatePlrItems(Player &player) item.clear(); } - switch (player._pClass) { - case HeroClass::Warrior: - InitializeItem(player.InvBody[INVLOC_HAND_LEFT], IDI_WARRIOR); - GenerateNewSeed(player.InvBody[INVLOC_HAND_LEFT]); + const PlayerStartingLoadoutData &loadout = GetPlayerStartingLoadoutForClass(player._pClass); - InitializeItem(player.InvBody[INVLOC_HAND_RIGHT], IDI_WARRSHLD); - GenerateNewSeed(player.InvBody[INVLOC_HAND_RIGHT]); + if (loadout.spell != SpellID::Null && loadout.spellLevel > 0) { + player._pMemSpells = GetSpellBitmask(loadout.spell); + player._pRSplType = SpellType::Spell; + player._pRSpell = loadout.spell; + player._pSplLvl[static_cast(loadout.spell)] = loadout.spellLevel; + } else { + player._pMemSpells = 0; + } - { - Item club; - InitializeItem(club, IDI_WARRCLUB); - GenerateNewSeed(club); - AutoPlaceItemInInventorySlot(player, 0, club, true); + if (loadout.skill != SpellID::Null) { + player._pAblSpells = GetSpellBitmask(loadout.skill); + if (player._pRSplType == SpellType::Invalid) { + player._pRSplType = SpellType::Skill; + player._pRSpell = loadout.skill; } + } - InitializeItem(player.SpdList[0], IDI_HEAL); - GenerateNewSeed(player.SpdList[0]); - - InitializeItem(player.SpdList[1], IDI_HEAL); - GenerateNewSeed(player.SpdList[1]); - break; - case HeroClass::Rogue: - InitializeItem(player.InvBody[INVLOC_HAND_LEFT], IDI_ROGUE); - GenerateNewSeed(player.InvBody[INVLOC_HAND_LEFT]); - - InitializeItem(player.SpdList[0], IDI_HEAL); - GenerateNewSeed(player.SpdList[0]); - - InitializeItem(player.SpdList[1], IDI_HEAL); - GenerateNewSeed(player.SpdList[1]); - break; - case HeroClass::Sorcerer: - InitializeItem(player.InvBody[INVLOC_HAND_LEFT], gbIsHellfire ? IDI_SORCERER : IDI_SORCERER_DIABLO); - GenerateNewSeed(player.InvBody[INVLOC_HAND_LEFT]); - - InitializeItem(player.SpdList[0], gbIsHellfire ? IDI_HEAL : IDI_MANA); - GenerateNewSeed(player.SpdList[0]); - - InitializeItem(player.SpdList[1], gbIsHellfire ? IDI_HEAL : IDI_MANA); - GenerateNewSeed(player.SpdList[1]); - break; - - case HeroClass::Monk: - InitializeItem(player.InvBody[INVLOC_HAND_LEFT], IDI_SHORTSTAFF); - GenerateNewSeed(player.InvBody[INVLOC_HAND_LEFT]); - InitializeItem(player.SpdList[0], IDI_HEAL); - GenerateNewSeed(player.SpdList[0]); - - InitializeItem(player.SpdList[1], IDI_HEAL); - GenerateNewSeed(player.SpdList[1]); - break; - case HeroClass::Bard: - InitializeItem(player.InvBody[INVLOC_HAND_LEFT], IDI_BARDSWORD); - GenerateNewSeed(player.InvBody[INVLOC_HAND_LEFT]); - - InitializeItem(player.InvBody[INVLOC_HAND_RIGHT], IDI_BARDDAGGER); - GenerateNewSeed(player.InvBody[INVLOC_HAND_RIGHT]); - InitializeItem(player.SpdList[0], IDI_HEAL); - GenerateNewSeed(player.SpdList[0]); - - InitializeItem(player.SpdList[1], IDI_HEAL); - GenerateNewSeed(player.SpdList[1]); - break; - case HeroClass::Barbarian: - InitializeItem(player.InvBody[INVLOC_HAND_LEFT], IDI_BARBARIAN); - GenerateNewSeed(player.InvBody[INVLOC_HAND_LEFT]); - - InitializeItem(player.InvBody[INVLOC_HAND_RIGHT], IDI_WARRSHLD); - GenerateNewSeed(player.InvBody[INVLOC_HAND_RIGHT]); - InitializeItem(player.SpdList[0], IDI_HEAL); - GenerateNewSeed(player.SpdList[0]); - - InitializeItem(player.SpdList[1], IDI_HEAL); - GenerateNewSeed(player.SpdList[1]); - break; + for (auto &itemChoice : loadout.items) { + _item_indexes itemData = gbIsHellfire && itemChoice.hellfire != _item_indexes::IDI_NONE ? itemChoice.hellfire : itemChoice.diablo; + if (itemData != _item_indexes::IDI_NONE) + CreateStartingItem(player, itemData); } - Item &goldItem = player.InvList[player._pNumInv]; - MakeGoldStack(goldItem, 100); + if (loadout.gold > 0) { + Item &goldItem = player.InvList[player._pNumInv]; + MakeGoldStack(goldItem, loadout.gold); - player._pNumInv++; - player.InvGrid[30] = player._pNumInv; + player._pNumInv++; + player.InvGrid[30] = player._pNumInv; - player._pGold = goldItem._ivalue; + player._pGold = goldItem._ivalue; + } CalcPlrItemVals(player, false); } @@ -3399,7 +3363,7 @@ void RecreateItem(const Player &player, Item &item, _item_indexes idx, uint16_t gbIsHellfire = tmpIsHellfire; } -void RecreateEar(Item &item, uint16_t ic, uint32_t iseed, uint8_t bCursval, string_view heroName) +void RecreateEar(Item &item, uint16_t ic, uint32_t iseed, uint8_t bCursval, std::string_view heroName) { InitializeItem(item, IDI_EAR); @@ -3661,7 +3625,7 @@ void DoRepair(Player &player, int cii) { Item *pi; - PlaySfxLoc(IS_REPAIR, player.position.tile); + PlaySfxLoc(SfxID::SpellRepair, player.position.tile); if (cii >= NUM_INVLOC) { pi = &player.InvList[cii - NUM_INVLOC]; @@ -3669,7 +3633,7 @@ void DoRepair(Player &player, int cii) pi = &player.InvBody[cii]; } - RepairItem(*pi, player._pLevel); + RepairItem(*pi, player.getCharacterLevel()); CalcPlrInv(player, true); } @@ -3877,7 +3841,7 @@ bool DoOil(Player &player, int cii) case IPL_NOMINSTR: return _("no strength requirement"); case IPL_INVCURS: - return { string_view(" ") }; + return { std::string_view(" ") }; case IPL_ADDACLIFE: if (item._iFMinDam == item._iFMaxDam) return fmt::format(fmt::runtime(_("lightning damage: {:d}")), item._iFMinDam); @@ -3921,7 +3885,7 @@ void DrawUniqueInfo(const Surface &out) Rectangle rect { position + Displacement { 32, 56 }, { 257, 0 } }; const UniqueItem &uitem = UniqueItems[curruitem._iUid]; - DrawString(out, _(uitem.UIName), rect, UiFlags::AlignCenter); + DrawString(out, _(uitem.UIName), rect, { .flags = UiFlags::AlignCenter }); const Rectangle dividerLineRect { position + Displacement { 26, 25 }, { 267, 3 } }; out.BlitFrom(out, MakeSdlRect(dividerLineRect), dividerLineRect.position + Displacement { 0, 5 * 12 + 13 }); @@ -3932,7 +3896,8 @@ void DrawUniqueInfo(const Surface &out) if (power.type == IPL_INVALID) break; rect.position.y += 2 * 12; - DrawString(out, PrintItemPower(power.type, curruitem), rect, UiFlags::ColorWhite | UiFlags::AlignCenter); + DrawString(out, PrintItemPower(power.type, curruitem), rect, + { .flags = UiFlags::ColorWhite | UiFlags::AlignCenter }); } } @@ -4016,9 +3981,8 @@ void PrintItemDur(const Item &item) PrintItemInfo(item); } -void UseItem(size_t pnum, item_misc_id mid, SpellID spellID, int spellFrom) +void UseItem(Player &player, item_misc_id mid, SpellID spellID, int spellFrom) { - Player &player = Players[pnum]; std::optional prepareSpellID; switch (mid) { @@ -4213,13 +4177,13 @@ void SpawnSmith(int lvl) constexpr int PinnedItemCount = 0; int maxValue = 140000; - int maxItems = 20; + int maxItems = 19; if (gbIsHellfire) { maxValue = 200000; - maxItems = 25; + maxItems = 24; } - int iCnt = GenerateRnd(maxItems - 10) + 10; + int iCnt = RandomIntBetween(10, maxItems); for (int i = 0; i < iCnt; i++) { Item &newItem = smithitem[i]; @@ -4242,7 +4206,7 @@ void SpawnSmith(int lvl) void SpawnPremium(const Player &player) { - int8_t lvl = player._pLevel; + int lvl = player.getCharacterLevel(); int maxItems = gbIsHellfire ? SMITH_PREMIUM_ITEMS : 6; if (numpremium < maxItems) { for (int i = 0; i < maxItems; i++) { @@ -4281,9 +4245,8 @@ void SpawnWitch(int lvl) constexpr std::array<_item_indexes, MaxPinnedBookCount> PinnedBookTypes = { IDI_BOOK1, IDI_BOOK2, IDI_BOOK3, IDI_BOOK4 }; int bookCount = 0; - const int pinnedBookCount = gbIsHellfire ? GenerateRnd(MaxPinnedBookCount) : 0; - const int reservedItems = gbIsHellfire ? 10 : 17; - const int itemCount = GenerateRnd(WITCH_ITEMS - reservedItems) + 10; + const int pinnedBookCount = gbIsHellfire ? RandomIntLessThan(MaxPinnedBookCount) : 0; + const int itemCount = RandomIntBetween(10, gbIsHellfire ? 24 : 17); const int maxValue = gbIsHellfire ? 200000 : 140000; for (int i = 0; i < WITCH_ITEMS; i++) { @@ -4457,11 +4420,11 @@ void SpawnBoy(int lvl) void SpawnHealer(int lvl) { - constexpr int PinnedItemCount = 2; + constexpr size_t PinnedItemCount = 2; constexpr std::array<_item_indexes, PinnedItemCount + 1> PinnedItemTypes = { IDI_HEAL, IDI_FULLHEAL, IDI_RESURRECT }; - const int itemCount = GenerateRnd(gbIsHellfire ? 10 : 8) + 10; + const auto itemCount = static_cast(RandomIntBetween(10, gbIsHellfire ? 19 : 17)); - for (int i = 0; i < 20; i++) { + for (size_t i = 0; i < sizeof(healitem) / sizeof(healitem[0]); ++i) { Item &item = healitem[i]; item = {}; @@ -4613,8 +4576,7 @@ void PutItemRecord(uint32_t nSeed, uint16_t wCI, int nIndex) std::mt19937 BetterRng; std::string DebugSpawnItem(std::string itemName) { - if (ActiveItemCount >= MAXITEMS) - return "No space to generate the item!"; + if (ActiveItemCount >= MAXITEMS) return "No space to generate the item!"; const int max_time = 3000; const int max_iter = 1000000; @@ -4660,18 +4622,17 @@ std::string DebugSpawnItem(std::string itemName) std::string DebugSpawnUniqueItem(std::string itemName) { - if (ActiveItemCount >= MAXITEMS) - return "No space to generate the item!"; + if (ActiveItemCount >= MAXITEMS) return "No space to generate the item!"; AsciiStrToLower(itemName); UniqueItem uniqueItem; bool foundUnique = false; int uniqueIndex = 0; - for (int j = 0; UniqueItems[j].UIItemId != UITYPE_INVALID; j++) { + for (int j = 0, n = static_cast(UniqueItems.size()); j < n; ++j) { if (!IsUniqueAvailable(j)) break; - const std::string tmp = AsciiStrToLower(UniqueItems[j].UIName); + const std::string tmp = AsciiStrToLower(std::string_view(UniqueItems[j].UIName)); if (tmp.find(itemName) != std::string::npos) { itemName = tmp; uniqueItem = UniqueItems[j]; @@ -4680,8 +4641,7 @@ std::string DebugSpawnUniqueItem(std::string itemName) break; } } - if (!foundUnique) - return "No unique found!"; + if (!foundUnique) return "No unique item found!"; _item_indexes uniqueBaseIndex = IDI_GOLD; for (std::underlying_type_t<_item_indexes> j = IDI_GOLD; j <= IDI_LAST; j++) { @@ -4693,8 +4653,7 @@ std::string DebugSpawnUniqueItem(std::string itemName) } } - if (uniqueBaseIndex == IDI_GOLD) - return "Base item not available"; + if (uniqueBaseIndex == IDI_GOLD) return "Base item not available!"; auto &baseItemData = AllItemsList[static_cast(uniqueBaseIndex)]; @@ -4737,6 +4696,7 @@ std::string DebugSpawnUniqueItem(std::string itemName) GetSuperItemSpace(pos, ii); item._iIdentified = true; NetSendCmdPItem(false, CMD_SPAWNITEM, item.position, item); + return StrCat("Item generated successfully - iterations: ", i); } #endif @@ -4789,7 +4749,7 @@ void Item::updateRequiredStatsCacheForPlayer(const Player &player) StringOrView Item::getName() const { if (isEmpty()) { - return string_view(""); + return std::string_view(""); } else if (!_iIdentified || _iCreateInfo == 0 || _iMagical == ITEM_QUALITY_NORMAL) { return GetTranslatedItemName(*this); } else if (_iMagical == ITEM_QUALITY_UNIQUE) { @@ -4842,15 +4802,14 @@ void RechargeItem(Item &item, Player &player) if (item._iCharges == item._iMaxCharges) return; - int r = GetSpellStaffLevel(item._iSpell); - r = GenerateRnd(player._pLevel / r) + 1; + int rechargeStrength = RandomIntBetween(1, player.getCharacterLevel() / GetSpellStaffLevel(item._iSpell)); do { item._iMaxCharges--; if (item._iMaxCharges == 0) { return; } - item._iCharges += r; + item._iCharges += rechargeStrength; } while (item._iCharges < item._iMaxCharges); item._iCharges = std::min(item._iCharges, item._iMaxCharges); @@ -4916,12 +4875,12 @@ bool ApplyOilToItem(Item &item, Player &player) switch (player._pOilType) { case IMISC_OILACC: if (item._iPLToHit < 50) { - item._iPLToHit += GenerateRnd(2) + 1; + item._iPLToHit += RandomIntBetween(1, 2); } break; case IMISC_OILMAST: if (item._iPLToHit < 100) { - item._iPLToHit += GenerateRnd(3) + 3; + item._iPLToHit += RandomIntBetween(3, 5); } break; case IMISC_OILSHARP: @@ -4936,7 +4895,7 @@ bool ApplyOilToItem(Item &item, Player &player) } break; case IMISC_OILSKILL: - r = GenerateRnd(6) + 5; + r = RandomIntBetween(5, 10); item._iMinStr = std::max(0, item._iMinStr - r); item._iMinMag = std::max(0, item._iMinMag - r); item._iMinDex = std::max(0, item._iMinDex - r); @@ -4957,7 +4916,7 @@ bool ApplyOilToItem(Item &item, Player &player) break; case IMISC_OILFORT: if (item._iMaxDur != DUR_INDESTRUCTIBLE && item._iMaxDur < 200) { - r = GenerateRnd(41) + 10; + r = RandomIntBetween(10, 50); item._iMaxDur += r; item._iDurability += r; } @@ -4968,12 +4927,12 @@ bool ApplyOilToItem(Item &item, Player &player) break; case IMISC_OILHARD: if (item._iAC < 60) { - item._iAC += GenerateRnd(2) + 1; + item._iAC += RandomIntBetween(1, 2); } break; case IMISC_OILIMP: if (item._iAC < 120) { - item._iAC += GenerateRnd(3) + 3; + item._iAC += RandomIntBetween(3, 5); } break; default: diff --git a/Source/items.h b/Source/items.h index 5e862bfbf84..f5b49c5083e 100644 --- a/Source/items.h +++ b/Source/items.h @@ -6,6 +6,7 @@ #pragma once #include +#include #include "DiabloUI/ui_flags.hpp" #include "engine.h" @@ -13,7 +14,6 @@ #include "engine/point.hpp" #include "itemdat.h" #include "monster.h" -#include "utils/stdcompat/optional.hpp" #include "utils/string_or_view.hpp" namespace devilution { @@ -303,10 +303,6 @@ struct Item { */ bool isWeapon() const { - if (this->isEmpty()) { - return false; - } - switch (this->_itype) { case ItemType::Axe: case ItemType::Bow: @@ -326,10 +322,6 @@ struct Item { */ bool isArmor() const { - if (this->isEmpty()) { - return false; - } - switch (this->_itype) { case ItemType::HeavyArmor: case ItemType::LightArmor: @@ -341,13 +333,22 @@ struct Item { } } + /** + * @brief Checks whether this item is gold. + * @return 'True' in case the item is gold and 'False' otherwise. + */ + bool isGold() const + { + return this->_itype == ItemType::Gold; + } + /** * @brief Checks whether this item is a helm. * @return 'True' in case the item is a helm and 'False' otherwise. */ bool isHelm() const { - return !this->isEmpty() && this->_itype == ItemType::Helm; + return this->_itype == ItemType::Helm; } /** @@ -356,7 +357,7 @@ struct Item { */ bool isShield() const { - return !this->isEmpty() && this->_itype == ItemType::Shield; + return this->_itype == ItemType::Shield; } /** @@ -365,10 +366,6 @@ struct Item { */ bool isJewelry() const { - if (this->isEmpty()) { - return false; - } - switch (this->_itype) { case ItemType::Amulet: case ItemType::Ring: @@ -454,6 +451,11 @@ struct Item { /** @brief Returns the translated item name to display (respects identified flag) */ StringOrView getName() const; + + [[nodiscard]] Displacement getRenderingOffset(const ClxSprite sprite) const + { + return { -CalculateWidth2(sprite.width()), 0 }; + } }; struct ItemGetRecordStruct { @@ -515,7 +517,7 @@ void CreateRndItem(Point position, bool onlygood, bool sendmsg, bool delta); void CreateRndUseful(Point position, bool sendmsg); void CreateTypeItem(Point position, bool onlygood, ItemType itemType, int imisc, bool sendmsg, bool delta, bool spawn = false); void RecreateItem(const Player &player, Item &item, _item_indexes idx, uint16_t icreateinfo, uint32_t iseed, int ivalue, bool isHellfire); -void RecreateEar(Item &item, uint16_t ic, uint32_t iseed, uint8_t bCursval, string_view heroName); +void RecreateEar(Item &item, uint16_t ic, uint32_t iseed, uint8_t bCursval, std::string_view heroName); void CornerstoneSave(); void CornerstoneLoad(Point position); void SpawnQuestItem(_item_indexes itemid, Point position, int randarea, int selflag, bool sendmsg); @@ -537,7 +539,7 @@ bool DoOil(Player &player, int cii); void DrawUniqueInfo(const Surface &out); void PrintItemDetails(const Item &item); void PrintItemDur(const Item &item); -void UseItem(size_t pnum, item_misc_id Mid, SpellID spellID, int spellFrom); +void UseItem(Player &player, item_misc_id Mid, SpellID spellID, int spellFrom); bool UseItemOpensHive(const Item &item, Point position); bool UseItemOpensGrave(const Item &item, Point position); void SpawnSmith(int lvl); @@ -577,6 +579,6 @@ std::string DebugSpawnUniqueItem(std::string itemName); extern int MaxGold; extern int8_t ItemCAnimTbl[]; -extern _sfx_id ItemInvSnds[]; +extern SfxID ItemInvSnds[]; } // namespace devilution diff --git a/Source/levels/crypt.cpp b/Source/levels/crypt.cpp index 136dbd1fdf4..80415f5dcce 100644 --- a/Source/levels/crypt.cpp +++ b/Source/levels/crypt.cpp @@ -689,7 +689,7 @@ void SetCryptRoom() auto dunData = LoadFileInMem("nlevels\\l5data\\uberroom.dun"); - SetPiece = { position, WorldTileSize(SDL_SwapLE16(dunData[0]), SDL_SwapLE16(dunData[1])) }; + SetPiece = { position, GetDunSize(dunData.get()) }; PlaceDunTiles(dunData.get(), position, 0); } @@ -700,7 +700,7 @@ void SetCornerRoom() auto dunData = LoadFileInMem("nlevels\\l5data\\cornerstone.dun"); - SetPiece = { position, WorldTileSize(SDL_SwapLE16(dunData[0]), SDL_SwapLE16(dunData[1])) }; + SetPiece = { position, GetDunSize(dunData.get()) }; PlaceDunTiles(dunData.get(), position, 0); } diff --git a/Source/levels/drlg_l1.cpp b/Source/levels/drlg_l1.cpp index b518b31ae11..5d1b8f5c832 100644 --- a/Source/levels/drlg_l1.cpp +++ b/Source/levels/drlg_l1.cpp @@ -372,7 +372,7 @@ void FillFloor() if (dungeon[i][j] != Floor || Protected.test(i, j)) continue; - int rv = GenerateRnd(3); + int rv = RandomIntLessThan(3); if (rv == 1) dungeon[i][j] = Floor22; else if (rv == 2) @@ -381,15 +381,22 @@ void FillFloor() } } -void LoadQuestSetPieces() +void InitSetPiece() { + std::unique_ptr setPieceData; if (Quests[Q_BUTCHER].IsAvailable()) { - pSetPiece = LoadFileInMem("levels\\l1data\\rnd6.dun"); + setPieceData = LoadFileInMem("levels\\l1data\\rnd6.dun"); } else if (Quests[Q_SKELKING].IsAvailable() && !UseMultiplayerQuests()) { - pSetPiece = LoadFileInMem("levels\\l1data\\skngdo.dun"); + setPieceData = LoadFileInMem("levels\\l1data\\skngdo.dun"); } else if (Quests[Q_LTBANNER].IsAvailable()) { - pSetPiece = LoadFileInMem("levels\\l1data\\banner2.dun"); + setPieceData = LoadFileInMem("levels\\l1data\\banner2.dun"); + } else { + return; // no setpiece needed for this level } + + WorldTilePosition setPiecePosition = SelectChamber(); + PlaceDunTiles(setPieceData.get(), setPiecePosition, Floor); + SetPiece = { setPiecePosition, GetDunSize(setPieceData.get()) }; } void InitDungeonPieces() @@ -1015,8 +1022,8 @@ void FillChambers() } else if (CornerStone.isAvailable()) { SetCornerRoom(); } - } else if (pSetPiece != nullptr) { - SetSetPieceRoom(SelectChamber(), Floor); + } else { + InitSetPiece(); } } @@ -1170,8 +1177,6 @@ void GenerateLevel(lvl_entry entry) break; } - LoadQuestSetPieces(); - while (true) { DRLG_InitTrans(); @@ -1189,8 +1194,6 @@ void GenerateLevel(lvl_entry entry) break; } - FreeQuestSetPieces(); - for (int j = 0; j < DMAXY; j++) { for (int i = 0; i < DMAXX; i++) { if (dungeon[i][j] == EntranceStairs) { diff --git a/Source/levels/drlg_l2.cpp b/Source/levels/drlg_l2.cpp index 13b549003a9..b2093da7b3f 100644 --- a/Source/levels/drlg_l2.cpp +++ b/Source/levels/drlg_l2.cpp @@ -5,8 +5,11 @@ */ #include "levels/drlg_l2.h" +#include +#include #include #include +#include #include "diablo.h" #include "engine/load_file.hpp" @@ -16,8 +19,6 @@ #include "levels/setmaps.h" #include "player.h" #include "quests.h" -#include "utils/stdcompat/algorithm.hpp" -#include "utils/stdcompat/optional.hpp" namespace devilution { @@ -1628,15 +1629,23 @@ void PlaceMiniSetRandom1x1(uint8_t search, uint8_t replace, int rndper) PlaceMiniSetRandom({ { 1, 1 }, { { search } }, { { replace } } }, rndper); } -void LoadQuestSetPieces() +void InitSetPiece() { + std::unique_ptr setPieceData; + if (Quests[Q_BLIND].IsAvailable()) { - pSetPiece = LoadFileInMem("levels\\l2data\\blind1.dun"); + setPieceData = LoadFileInMem("levels\\l2data\\blind1.dun"); } else if (Quests[Q_BLOOD].IsAvailable()) { - pSetPiece = LoadFileInMem("levels\\l2data\\blood1.dun"); + setPieceData = LoadFileInMem("levels\\l2data\\blood1.dun"); } else if (Quests[Q_SCHAMB].IsAvailable()) { - pSetPiece = LoadFileInMem("levels\\l2data\\bonestr2.dun"); + setPieceData = LoadFileInMem("levels\\l2data\\bonestr2.dun"); + } else { + return; // no setpiece needed for this level } + + WorldTilePosition setPiecePosition = SetPieceRoom.position; + PlaceDunTiles(setPieceData.get(), setPiecePosition, 3); + SetPiece = { setPiecePosition, GetDunSize(setPieceData.get()) }; } void InitDungeonPieces() @@ -1787,10 +1796,10 @@ void CreateRoom(WorldTilePosition topLeft, WorldTilePosition bottomRight, int nR roomTopLeft.y = bottomRight.y - roomSize.height; } - roomTopLeft.x = clamp(roomTopLeft.x, 1, 38); - roomTopLeft.y = clamp(roomTopLeft.y, 1, 38); - roomBottomRight.x = clamp(roomBottomRight.x, 1, 38); - roomBottomRight.y = clamp(roomBottomRight.y, 1, 38); + roomTopLeft.x = std::clamp(roomTopLeft.x, 1, 38); + roomTopLeft.y = std::clamp(roomTopLeft.y, 1, 38); + roomBottomRight.x = std::clamp(roomBottomRight.x, 1, 38); + roomBottomRight.y = std::clamp(roomBottomRight.y, 1, 38); DefineRoom(roomTopLeft, roomBottomRight, static_cast(size)); @@ -1908,8 +1917,8 @@ void ConnectHall(const HallNode &node) if (predungeon[beginning.x][beginning.y] != ',') fInroom = true; } - int nDx = abs(end.x - beginning.x); - int nDy = abs(end.y - beginning.y); + int nDx = std::abs(end.x - beginning.x); + int nDy = std::abs(end.y - beginning.y); if (nDx > nDy) { int nRp = std::min(2 * nDx, 30); if (GenerateRnd(100) < nRp) { @@ -2660,8 +2669,6 @@ bool PlaceStairs(lvl_entry entry) void GenerateLevel(lvl_entry entry) { - LoadQuestSetPieces(); - while (true) { nRoomCnt = 0; InitDungeonFlags(); @@ -2670,15 +2677,13 @@ void GenerateLevel(lvl_entry entry) continue; } FixTilesPatterns(); - SetSetPieceRoom(SetPieceRoom.position, 3); + InitSetPiece(); FloodTransparencyValues(3); FixTransparency(); if (PlaceStairs(entry)) break; } - FreeQuestSetPieces(); - FixLockout(); FixDoors(); FixDirtTiles(); diff --git a/Source/levels/drlg_l3.cpp b/Source/levels/drlg_l3.cpp index 03bb0e1722e..1c6801b99dd 100644 --- a/Source/levels/drlg_l3.cpp +++ b/Source/levels/drlg_l3.cpp @@ -757,8 +757,8 @@ void CreateBlock(int x, int y, int obs, int dir) int x2; int y2; - int blksizex = GenerateRnd(2) + 3; - int blksizey = GenerateRnd(2) + 3; + int blksizex = RandomIntBetween(3, 4); + int blksizey = RandomIntBetween(3, 4); if (dir == 0) { y2 = y - 1; @@ -1102,10 +1102,10 @@ void River() if (dungeon[rx][ry] == 7) { dircheck = 0; if (dir < 2) { - river[2][riveramt] = GenerateRnd(2) + 17; + river[2][riveramt] = PickRandomlyAmong({ 17, 18 }); } if (dir > 1) { - river[2][riveramt] = GenerateRnd(2) + 15; + river[2][riveramt] = PickRandomlyAmong({ 15, 16 }); } river[0][riveramt] = rx; river[1][riveramt] = ry; @@ -1502,12 +1502,12 @@ bool PlacePool() } /** - * @brief Fill lava pools correctly, cause River() only generates the edges. + * @brief Fill lava pools correctly, because River() only generates the edges. */ void PoolFix() { for (Point tile : PointsInRectangle(Rectangle { { 1, 1 }, { DMAXX - 2, DMAXY - 2 } })) { - // Check if the tile is a the default dirt ceiling tile + // Check if the tile is the default dirt ceiling tile if (dungeon[tile.x][tile.y] != 8) continue; @@ -1796,16 +1796,11 @@ void Fence() FenceDoorFix(); } -void LoadQuestSetPieces() -{ - if (Quests[Q_ANVIL].IsAvailable()) - pSetPiece = LoadFileInMem("levels\\l3data\\anvil.dun"); -} - bool PlaceAnvil() { + std::unique_ptr setPieceData = LoadFileInMem("levels\\l3data\\anvil.dun"); // growing the size by 2 to allow a 1 tile border on all sides - WorldTileSize areaSize = WorldTileSize(SDL_SwapLE16(pSetPiece[0]), SDL_SwapLE16(pSetPiece[1])) + 2; + WorldTileSize areaSize = GetDunSize(setPieceData.get()) + 2; WorldTileCoord sx = GenerateRnd(DMAXX - areaSize.width); WorldTileCoord sy = GenerateRnd(DMAXY - areaSize.height); @@ -1832,7 +1827,7 @@ bool PlaceAnvil() break; } - PlaceDunTiles(pSetPiece.get(), { sx + 1, sy + 1 }, 7); + PlaceDunTiles(setPieceData.get(), { sx + 1, sy + 1 }, 7); SetPiece = { { sx, sy }, areaSize }; for (WorldTilePosition tile : PointsInRectangle(SetPiece)) { @@ -1993,8 +1988,6 @@ bool PlaceStairs(lvl_entry entry) void GenerateLevel(lvl_entry entry) { - LoadQuestSetPieces(); - while (true) { InitDungeonFlags(); int x1 = GenerateRnd(20) + 10; @@ -2029,8 +2022,6 @@ void GenerateLevel(lvl_entry entry) break; } - FreeQuestSetPieces(); - if (leveltype == DTYPE_NEST) { PlaceMiniSetRandom(L6ISLE1, 70); PlaceMiniSetRandom(L6ISLE2, 70); diff --git a/Source/levels/drlg_l4.cpp b/Source/levels/drlg_l4.cpp index 76a040c35db..883d0e0486f 100644 --- a/Source/levels/drlg_l4.cpp +++ b/Source/levels/drlg_l4.cpp @@ -158,13 +158,21 @@ void ApplyShadowsPatterns() } } -void LoadQuestSetPieces() +void InitSetPiece() { + std::unique_ptr setPieceData; + if (Quests[Q_WARLORD].IsAvailable()) { - pSetPiece = LoadFileInMem("levels\\l4data\\warlord.dun"); + setPieceData = LoadFileInMem("levels\\l4data\\warlord.dun"); } else if (currlevel == 15 && UseMultiplayerQuests()) { - pSetPiece = LoadFileInMem("levels\\l4data\\vile1.dun"); + setPieceData = LoadFileInMem("levels\\l4data\\vile1.dun"); + } else { + return; // no setpiece needed for this level } + + WorldTilePosition setPiecePosition = SetPieceRoom.position; + PlaceDunTiles(setPieceData.get(), setPiecePosition, 6); + SetPiece = { setPiecePosition, GetDunSize(setPieceData.get()) }; } void InitDungeonFlags() @@ -304,7 +312,7 @@ void MakeDmt() { for (int y = 0; y < DMAXY - 1; y++) { for (int x = 0; x < DMAXX - 1; x++) { - int val = (DungeonMask.test(x + 1, y + 1) << 3) | (DungeonMask.test(x, y + 1) << 2) | (DungeonMask.test(x + 1, y) << 1) | DungeonMask.test(x, y); + int val = (DungeonMask.test(x + 1, y + 1) << 3) | (DungeonMask.test(x, y + 1) << 2) | (DungeonMask.test(x + 1, y) << 1) | (DungeonMask.test(x, y) << 0); dungeon[x][y] = L4ConvTbl[val]; } } @@ -846,7 +854,7 @@ void Substitution() if (FlipCoin(10)) { uint8_t c = dungeon[x][y]; if (L4BTYPES[c] == 6 && !Protected.test(x, y)) { - dungeon[x][y] = GenerateRnd(3) + 95; + dungeon[x][y] = PickRandomlyAmong({ 95, 96, 97 }); } } } @@ -1130,8 +1138,6 @@ bool PlaceStairs(lvl_entry entry) void GenerateLevel(lvl_entry entry) { - LoadQuestSetPieces(); - while (true) { DRLG_InitTrans(); @@ -1160,7 +1166,7 @@ void GenerateLevel(lvl_entry entry) AddWall(); FloodTransparencyValues(6); FixTransparency(); - SetSetPieceRoom(SetPieceRoom.position, 6); + InitSetPiece(); if (currlevel == 16) { LoadDiabQuads(true); } @@ -1168,8 +1174,6 @@ void GenerateLevel(lvl_entry entry) break; } - FreeQuestSetPieces(); - GeneralFix(); if (currlevel != 16) { diff --git a/Source/levels/dun_tile.hpp b/Source/levels/dun_tile.hpp new file mode 100644 index 00000000000..04983058420 --- /dev/null +++ b/Source/levels/dun_tile.hpp @@ -0,0 +1,107 @@ +#pragma once + +#include + +#define TILE_WIDTH 64 +#define TILE_HEIGHT 32 + +namespace devilution { + +/** + * Level tile type. + * + * The tile type determines data encoding and the shape. + * + * Each tile type has its own encoding but they all encode data in the order + * of bottom-to-top (bottom row first). + */ +enum class TileType : uint8_t { + /** + * 🮆 A 32x32 square. Stored as an array of pixels. + */ + Square, + + /** + * 🮆 A 32x32 square with transparency. RLE encoded. + * + * Each run starts with an int8_t value. + * If positive, it is followed by this many pixels. + * If negative, it indicates `-value` fully transparent pixels, which are omitted. + * + * Runs do not cross row boundaries. + */ + TransparentSquare, + + /** + *🭮 Left-pointing 32x31 triangle. Encoded as 31 varying-width rows with 2 padding bytes before every even row. + * + * The smallest rows (bottom and top) are 2px wide, the largest row is 16px wide (middle row). + * + * Encoding: + * for i in [0, 30]: + * - 2 unused bytes if i is even + * - row (only the pixels within the triangle) + */ + LeftTriangle, + + /** + * 🭬Right-pointing 32x31 triangle. Encoded as 31 varying-width rows with 2 padding bytes after every even row. + * + * The smallest rows (bottom and top) are 2px wide, the largest row is 16px wide (middle row). + * + * Encoding: + * for i in [0, 30]: + * - row (only the pixels within the triangle) + * - 2 unused bytes if i is even + */ + RightTriangle, + + /** + * 🭓 Left-pointing 32x32 trapezoid: a 32x16 rectangle and the 16x16 bottom part of `LeftTriangle`. + * + * Begins with triangle part, which uses the `LeftTriangle` encoding, + * and is followed by a flat array of pixels for the top rectangle part. + */ + LeftTrapezoid, + + /** + * 🭞 Right-pointing 32x32 trapezoid: 32x16 rectangle and the 16x16 bottom part of `RightTriangle`. + * + * Begins with the triangle part, which uses the `RightTriangle` encoding, + * and is followed by a flat array of pixels for the top rectangle part. + */ + RightTrapezoid, +}; + +/** + * Specifies the current MIN block of the level CEL file, as used during rendering of the level tiles. + */ +struct LevelCelBlock { + uint16_t data; + + [[nodiscard]] bool hasValue() const + { + return data != 0; + } + + [[nodiscard]] TileType type() const + { + return static_cast((data & 0x7000) >> 12); + } + + /** + * @brief Returns the 1-based index of the frame in `pDungeonCels`. + */ + [[nodiscard]] uint16_t frame() const + { + return data & 0xFFF; + } +}; + +/** Width of a tile rendering primitive. */ +constexpr int_fast16_t DunFrameWidth = TILE_WIDTH / 2; + +/** Height of a tile rendering primitive (except triangles). */ +constexpr int_fast16_t DunFrameHeight = TILE_HEIGHT; + +} // namespace devilution diff --git a/Source/levels/gendung.cpp b/Source/levels/gendung.cpp index 7b7cf835fd4..6faf76ef0a9 100644 --- a/Source/levels/gendung.cpp +++ b/Source/levels/gendung.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "engine/load_file.hpp" #include "engine/random.hpp" @@ -22,11 +23,10 @@ uint8_t pdungeon[DMAXX][DMAXY]; Bitset2d Protected; WorldTileRectangle SetPieceRoom; WorldTileRectangle SetPiece; -std::unique_ptr pSetPiece; OptionalOwnedClxSpriteList pSpecialCels; std::unique_ptr pMegaTiles; -std::unique_ptr pDungeonCels; -std::array SOLData; +std::unique_ptr pDungeonCels; +TileProperties SOLData[MAXTILES]; WorldTilePosition dminPosition; WorldTilePosition dmaxPosition; dungeon_type leveltype; @@ -265,17 +265,22 @@ void FindTransparencyValues(Point floor, uint8_t floorID) // Algorithm adapted from https://en.wikipedia.org/wiki/Flood_fill#Span_Filling // Modified to include diagonally adjacent tiles that would otherwise not be visited // Also, Wikipedia's selection for the initial seed is incorrect - using Seed = std::tuple; - std::stack seedStack; - seedStack.push(std::make_tuple(floor.x, floor.x + 1, floor.y, 1)); + struct Seed { + int scanStart; + int scanEnd; + int y; + int dy; + }; + std::stack> seedStack; + seedStack.push({ floor.x, floor.x + 1, floor.y, 1 }); - const auto isInside = [&](int x, int y) { + const auto isInside = [floorID](int x, int y) { if (dTransVal[x][y] != 0) return false; return IsFloor({ x, y }, floorID); }; - const auto set = [&](int x, int y) { + const auto set = [floorID](int x, int y) { FillTransparencyValues({ x, y }, floorID); }; @@ -285,17 +290,16 @@ void FindTransparencyValues(Point floor, uint8_t floorID) Point up = p + Displacement { 0, -1 }; Point upOver = up + direction; if (!isInside(up.x, up.y) && isInside(upOver.x, upOver.y)) - seedStack.push(std::make_tuple(upOver.x, upOver.x + 1, upOver.y, -1)); + seedStack.push({ upOver.x, upOver.x + 1, upOver.y, -1 }); Point down = p + Displacement { 0, 1 }; Point downOver = down + direction; if (!isInside(down.x, down.y) && isInside(downOver.x, downOver.y)) - seedStack.push(std::make_tuple(downOver.x, downOver.x + 1, downOver.y, 1)); + seedStack.push(Seed { downOver.x, downOver.x + 1, downOver.y, 1 }); }; while (!seedStack.empty()) { - int scanStart, scanEnd, y, dy; - std::tie(scanStart, scanEnd, y, dy) = seedStack.top(); + const auto [scanStart, scanEnd, y, dy] = seedStack.top(); seedStack.pop(); int scanLeft = scanStart; @@ -307,7 +311,7 @@ void FindTransparencyValues(Point floor, uint8_t floorID) checkDiagonals({ scanLeft, y }, left); } if (scanLeft < scanStart) - seedStack.push(std::make_tuple(scanLeft, scanStart - 1, y - dy, -dy)); + seedStack.push(Seed { scanLeft, scanStart - 1, y - dy, -dy }); int scanRight = scanStart; while (scanRight < scanEnd) { @@ -315,9 +319,9 @@ void FindTransparencyValues(Point floor, uint8_t floorID) set(scanRight, y); scanRight++; } - seedStack.push(std::make_tuple(scanLeft, scanRight - 1, y + dy, dy)); + seedStack.push(Seed { scanLeft, scanRight - 1, y + dy, dy }); if (scanRight - 1 > scanEnd) - seedStack.push(std::make_tuple(scanEnd + 1, scanRight - 1, y - dy, -dy)); + seedStack.push(Seed { scanEnd + 1, scanRight - 1, y - dy, -dy }); if (scanLeft < scanRight) checkDiagonals({ scanRight - 1, y }, right); @@ -412,11 +416,6 @@ void CreateDungeon(uint32_t rseed, lvl_entry entry) Make_SetPC(SetPiece); } -bool TileHasAny(int tileId, TileProperties property) -{ - return HasAnyOf(SOLData[tileId], property); -} - void LoadLevelSOLData() { switch (leveltype) { @@ -494,7 +493,7 @@ void SetDungeonMicros() for (size_t i = 0; i < tileCount / blocks; i++) { uint16_t *pieces = &levelPieces[blocks * i]; for (size_t block = 0; block < blocks; block++) { - DPieceMicros[i].mt[block] = SDL_SwapLE16(pieces[blocks - 2 + (block & 1) - (block & 0xE)]); + DPieceMicros[i].mt[block] = LevelCelBlock { SDL_SwapLE16(pieces[blocks - 2 + (block & 1) - (block & 0xE)]) }; } } } @@ -537,20 +536,18 @@ void DRLG_CopyTrans(int sx, int sy, int dx, int dy) void LoadTransparency(const uint16_t *dunData) { - int width = SDL_SwapLE16(dunData[0]); - int height = SDL_SwapLE16(dunData[1]); + WorldTileSize size = GetDunSize(dunData); - int layer2Offset = 2 + width * height; + int layer2Offset = 2 + size.width * size.height; // The rest of the layers are at dPiece scale - width *= 2; - height *= 2; + size *= static_cast(2); - const uint16_t *transparentLayer = &dunData[layer2Offset + width * height * 3]; + const uint16_t *transparentLayer = &dunData[layer2Offset + size.width * size.height * 3]; - for (int j = 0; j < height; j++) { - for (int i = 0; i < width; i++) { - dTransVal[16 + i][16 + j] = SDL_SwapLE16(*transparentLayer); + for (WorldTileCoord j = 0; j < size.height; j++) { + for (WorldTileCoord i = 0; i < size.width; i++) { + dTransVal[16 + i][16 + j] = static_cast(SDL_SwapLE16(*transparentLayer)); transparentLayer++; } } @@ -569,6 +566,7 @@ void LoadDungeonBase(const char *path, Point spawn, int floorId, int dirtId) LoadTransparency(dunData.get()); SetMapMonsters(dunData.get(), Point(0, 0).megaToWorld()); + InitAllMonsterGFX(); SetMapObjects(dunData.get(), 0, 0); } @@ -630,14 +628,13 @@ std::optional PlaceMiniSet(const Miniset &miniset, int tries, bool drlg1Q void PlaceDunTiles(const uint16_t *dunData, Point position, int floorId) { - int width = SDL_SwapLE16(dunData[0]); - int height = SDL_SwapLE16(dunData[1]); + WorldTileSize size = GetDunSize(dunData); const uint16_t *tileLayer = &dunData[2]; - for (int j = 0; j < height; j++) { - for (int i = 0; i < width; i++) { - auto tileId = static_cast(SDL_SwapLE16(tileLayer[j * width + i])); + for (WorldTileCoord j = 0; j < size.height; j++) { + for (WorldTileCoord i = 0; i < size.width; i++) { + auto tileId = static_cast(SDL_SwapLE16(tileLayer[j * size.width + i])); if (tileId != 0) { dungeon[position.x + i][position.y + j] = tileId; Protected.set(position.x + i, position.y + j); @@ -702,18 +699,9 @@ void DRLG_HoldThemeRooms() } } -void SetSetPieceRoom(WorldTilePosition position, int floorId) -{ - if (pSetPiece == nullptr) - return; - - PlaceDunTiles(pSetPiece.get(), position, floorId); - SetPiece = { position, WorldTileSize(SDL_SwapLE16(pSetPiece[0]), SDL_SwapLE16(pSetPiece[1])) }; -} - -void FreeQuestSetPieces() +WorldTileSize GetDunSize(const uint16_t *dunData) { - pSetPiece = nullptr; + return WorldTileSize(static_cast(SDL_SwapLE16(dunData[0])), static_cast(SDL_SwapLE16(dunData[1]))); } void DRLG_LPass3(int lv) diff --git a/Source/levels/gendung.h b/Source/levels/gendung.h index 19f0a4330ca..443a6ee340d 100644 --- a/Source/levels/gendung.h +++ b/Source/levels/gendung.h @@ -7,17 +7,17 @@ #include #include +#include -#include "engine.h" #include "engine/clx_sprite.hpp" #include "engine/point.hpp" #include "engine/rectangle.hpp" #include "engine/render/scrollrt.h" #include "engine/world_tile.hpp" +#include "levels/dun_tile.hpp" #include "utils/attributes.h" #include "utils/bitset2d.hpp" #include "utils/enum_traits.h" -#include "utils/stdcompat/optional.hpp" namespace devilution { @@ -123,7 +123,7 @@ enum _difficulty : uint8_t { struct THEME_LOC { RectangleOf room; - int16_t ttval; + int8_t ttval; }; struct MegaTile { @@ -134,7 +134,7 @@ struct MegaTile { }; struct MICROS { - uint16_t mt[16]; + LevelCelBlock mt[16]; }; struct ShadowStruct { @@ -158,16 +158,14 @@ extern Bitset2d Protected; extern WorldTileRectangle SetPieceRoom; /** Specifies the active set quest piece in coordinate. */ extern WorldTileRectangle SetPiece; -/** Contains the contents of the single player quest DUN file. */ -extern std::unique_ptr pSetPiece; extern OptionalOwnedClxSpriteList pSpecialCels; /** Specifies the tile definitions of the active dungeon type; (e.g. levels/l1data/l1.til). */ extern DVL_API_FOR_TEST std::unique_ptr pMegaTiles; -extern std::unique_ptr pDungeonCels; +extern std::unique_ptr pDungeonCels; /** * List tile properties */ -extern DVL_API_FOR_TEST std::array SOLData; +extern DVL_API_FOR_TEST TileProperties SOLData[MAXTILES]; /** Specifies the minimum X,Y-coordinates of the map. */ extern WorldTilePosition dminPosition; /** Specifies the maximum X,Y-coordinates of the map. */ @@ -236,7 +234,7 @@ std::optional GetSizeForThemeRoom(); dungeon_type GetLevelType(int level); void CreateDungeon(uint32_t rseed, lvl_entry entry); -constexpr bool InDungeonBounds(Point position) +DVL_ALWAYS_INLINE constexpr bool InDungeonBounds(Point position) { return position.x >= 0 && position.x < MAXDUNX && position.y >= 0 && position.y < MAXDUNY; } @@ -334,7 +332,11 @@ struct Miniset { } }; -bool TileHasAny(int tileId, TileProperties property); +[[nodiscard]] DVL_ALWAYS_INLINE bool TileHasAny(int tileId, TileProperties property) +{ + return HasAnyOf(SOLData[tileId], property); +} + void LoadLevelSOLData(); void SetDungeonMicros(); void DRLG_InitTrans(); @@ -354,8 +356,10 @@ std::optional PlaceMiniSet(const Miniset &miniset, int tries = 199, bool void PlaceDunTiles(const uint16_t *dunData, Point position, int floorId = 0); void DRLG_PlaceThemeRooms(int minSize, int maxSize, int floor, int freq, bool rndSize); void DRLG_HoldThemeRooms(); -void SetSetPieceRoom(WorldTilePosition position, int floorId); -void FreeQuestSetPieces(); +/** + * @brief Returns ths size in tiles of the specified ".dun" Data + */ +WorldTileSize GetDunSize(const uint16_t *dunData); void DRLG_LPass3(int lv); /** diff --git a/Source/levels/themes.cpp b/Source/levels/themes.cpp index afaf0b9107d..699ed2ea651 100644 --- a/Source/levels/themes.cpp +++ b/Source/levels/themes.cpp @@ -17,6 +17,7 @@ #include "monster.h" #include "objects.h" #include "quests.h" +#include "utils/algorithm/container.hpp" #include "utils/str_cat.hpp" namespace devilution { @@ -38,13 +39,13 @@ bool treasureFlag; int themex; int themey; -int themeVar1; +size_t themeVar1; bool TFit_Shrine(int i) { int xp = 0; int yp = 0; - int found = 0; + size_t found = 0; while (found == 0) { Point testPosition { xp, yp }; @@ -87,8 +88,7 @@ bool TFit_Shrine(int i) bool CheckThemeObj5(Point origin, int8_t regionId) { - const auto searchArea = PointsInRectangle(Rectangle { origin, 2 }); - return std::all_of(searchArea.cbegin(), searchArea.cend(), [regionId](Point testPosition) { + return c_all_of(PointsInRectangle(Rectangle { origin, 2 }), [regionId](Point testPosition) { // note out-of-bounds tiles are not solid, this function relies on the guard in TFit_Obj5 and dungeon border if (IsTileSolid(testPosition)) { return false; @@ -154,8 +154,7 @@ bool TFit_GoatShrine(int t) bool CheckThemeObj3(Point origin, int8_t regionId, unsigned frequency = 0) { - const auto searchArea = PointsInRectangle(Rectangle { origin, 1 }); - return std::all_of(searchArea.cbegin(), searchArea.cend(), [regionId, frequency](Point testPosition) { + return c_all_of(PointsInRectangle(Rectangle { origin, 1 }), [regionId, frequency](Point testPosition) { if (!InDungeonBounds(testPosition)) { return false; } @@ -301,7 +300,7 @@ bool SpecialThemeFit(int i, theme_id t) return rv; } -bool CheckThemeRoom(int tv) +bool CheckThemeRoom(int8_t tv) { for (int i = 0; i < numtrigs; i++) { if (dTransVal[trigs[i].position.x][trigs[i].position.y] == tv) @@ -349,7 +348,7 @@ bool CheckThemeRoom(int tv) */ void PlaceThemeMonsts(int t, int f) { - int scattertypes[138]; + size_t scattertypes[138]; int numscattypes = 0; for (size_t i = 0; i < LevelMonsterTypeCount; i++) { @@ -358,7 +357,7 @@ void PlaceThemeMonsts(int t, int f) numscattypes++; } } - int mtype = scattertypes[GenerateRnd(numscattypes)]; + size_t mtype = scattertypes[GenerateRnd(numscattypes)]; for (int yp = 0; yp < MAXDUNY; yp++) { for (int xp = 0; xp < MAXDUNX; xp++) { if (dTransVal[xp][yp] == themes[t].ttval && IsTileNotSolid({ xp, yp }) && dItem[xp][yp] == 0 && !IsObjectAtPosition({ xp, yp })) { @@ -845,7 +844,7 @@ void InitThemes() constexpr theme_id ThemeGood[4] = { THEME_GOATSHRINE, THEME_SHRINE, THEME_SKELROOM, THEME_LIBRARY }; if (leveltype == DTYPE_CATHEDRAL) { - for (size_t i = 0; i < 256 && numthemes < MAXTHEMES; i++) { + for (int8_t i = 0; numthemes < MAXTHEMES; i++) { if (CheckThemeRoom(i)) { themes[numthemes].ttval = i; theme_id j = ThemeGood[GenerateRnd(4)]; @@ -855,6 +854,8 @@ void InitThemes() themes[numthemes].ttype = j; numthemes++; } + if (i == std::numeric_limits::max()) + break; } return; } diff --git a/Source/levels/themes.h b/Source/levels/themes.h index c14289a1aff..53bf20512ff 100644 --- a/Source/levels/themes.h +++ b/Source/levels/themes.h @@ -14,7 +14,7 @@ namespace devilution { struct ThemeStruct { theme_id ttype; - int16_t ttval; + int8_t ttval; }; extern int numthemes; diff --git a/Source/levels/town.cpp b/Source/levels/town.cpp index ab6b44cfb73..786d8060115 100644 --- a/Source/levels/town.cpp +++ b/Source/levels/town.cpp @@ -24,20 +24,18 @@ void FillSector(const char *path, int xi, int yy) { auto dunData = LoadFileInMem(path); - int width = SDL_SwapLE16(dunData[0]); - int height = SDL_SwapLE16(dunData[1]); - + WorldTileSize size = GetDunSize(dunData.get()); const uint16_t *tileLayer = &dunData[2]; - for (int j = 0; j < height; j++) { + for (WorldTileCoord j = 0; j < size.height; j++) { int xx = xi; - for (int i = 0; i < width; i++) { + for (WorldTileCoord i = 0; i < size.width; i++) { int v1 = 218; int v2 = 218; int v3 = 218; int v4 = 218; - int tileId = SDL_SwapLE16(tileLayer[j * width + i]) - 1; + int tileId = SDL_SwapLE16(tileLayer[j * size.width + i]) - 1; if (tileId >= 0) { MegaTile mega = pMegaTiles[tileId]; v1 = SDL_SwapLE16(mega.micro1); @@ -221,7 +219,7 @@ void DrlgTPass3() dungeon[16][35] = 7; dungeon[17][35] = 7; for (int x = 36; x < 46; x++) { - FillTile(x, 78, GenerateRnd(4) + 1); + FillTile(x, 78, PickRandomlyAmong({ 1, 2, 3, 4 })); } } if (gbIsHellfire) { diff --git a/Source/levels/trigs.cpp b/Source/levels/trigs.cpp index 75765d23e35..e91f90b1c17 100644 --- a/Source/levels/trigs.cpp +++ b/Source/levels/trigs.cpp @@ -5,6 +5,7 @@ */ #include "levels/trigs.h" +#include #include #include @@ -12,8 +13,9 @@ #include "control.h" #include "controls/plrctrls.h" #include "cursor.h" -#include "error.h" +#include "diablo_msg.hpp" #include "init.h" +#include "utils/algorithm/container.hpp" #include "utils/language.h" #include "utils/utf8.hpp" @@ -87,11 +89,11 @@ bool IsWarpOpen(dungeon_type type) return true; if (gbIsHellfire) { - if (type == DTYPE_CATACOMBS && myPlayer._pLevel >= 10) + if (type == DTYPE_CATACOMBS && myPlayer.getCharacterLevel() >= 10) return true; - if (type == DTYPE_CAVES && myPlayer._pLevel >= 15) + if (type == DTYPE_CAVES && myPlayer.getCharacterLevel() >= 15) return true; - if (type == DTYPE_HELL && myPlayer._pLevel >= 20) + if (type == DTYPE_HELL && myPlayer.getCharacterLevel() >= 20) return true; if (type == DTYPE_NEST && IsAnyOf(Quests[Q_FARMER]._qactive, QUEST_DONE, QUEST_HIVE_DONE)) return true; @@ -443,8 +445,8 @@ bool ForceL2Trig() if (dPiece[cursPosition.x][cursPosition.y] == tileId) { for (int j = 0; j < numtrigs; j++) { if (trigs[j]._tmsg == WM_DIABPREVLVL) { - int dx = abs(trigs[j].position.x - cursPosition.x); - int dy = abs(trigs[j].position.y - cursPosition.y); + int dx = std::abs(trigs[j].position.x - cursPosition.x); + int dy = std::abs(trigs[j].position.y - cursPosition.y); if (dx < 4 && dy < 4) { InfoString = fmt::format(fmt::runtime(_("Up to level {:d}")), currlevel - 1); cursPosition = trigs[j].position; @@ -472,8 +474,8 @@ bool ForceL2Trig() if (dPiece[cursPosition.x][cursPosition.y] == tileId) { for (int j = 0; j < numtrigs; j++) { if (trigs[j]._tmsg == WM_DIABTWARPUP) { - int dx = abs(trigs[j].position.x - cursPosition.x); - int dy = abs(trigs[j].position.y - cursPosition.y); + int dx = std::abs(trigs[j].position.x - cursPosition.x); + int dy = std::abs(trigs[j].position.y - cursPosition.y); if (dx < 4 && dy < 4) { InfoString = _("Up to town"); cursPosition = trigs[j].position; @@ -495,8 +497,8 @@ bool ForceL3Trig() InfoString = fmt::format(fmt::runtime(_("Up to level {:d}")), currlevel - 1); for (int j = 0; j < numtrigs; j++) { if (trigs[j]._tmsg == WM_DIABPREVLVL) { - int dx = abs(trigs[j].position.x - cursPosition.x); - int dy = abs(trigs[j].position.y - cursPosition.y); + int dx = std::abs(trigs[j].position.x - cursPosition.x); + int dy = std::abs(trigs[j].position.y - cursPosition.y); if (dx < 4 && dy < 4) { cursPosition = trigs[j].position; return true; @@ -524,8 +526,8 @@ bool ForceL3Trig() if (dPiece[cursPosition.x][cursPosition.y] == tileId) { for (int j = 0; j < numtrigs; j++) { if (trigs[j]._tmsg == WM_DIABTWARPUP) { - int dx = abs(trigs[j].position.x - cursPosition.x); - int dy = abs(trigs[j].position.y - cursPosition.y); + int dx = std::abs(trigs[j].position.x - cursPosition.x); + int dy = std::abs(trigs[j].position.y - cursPosition.y); if (dx < 4 && dy < 4) { InfoString = _("Up to town"); cursPosition = trigs[j].position; @@ -571,8 +573,8 @@ bool ForceL4Trig() if (dPiece[cursPosition.x][cursPosition.y] == tileId) { for (int j = 0; j < numtrigs; j++) { if (trigs[j]._tmsg == WM_DIABTWARPUP) { - int dx = abs(trigs[j].position.x - cursPosition.x); - int dy = abs(trigs[j].position.y - cursPosition.y); + int dx = std::abs(trigs[j].position.x - cursPosition.x); + int dy = std::abs(trigs[j].position.y - cursPosition.y); if (dx < 4 && dy < 4) { InfoString = _("Up to town"); cursPosition = trigs[j].position; @@ -633,8 +635,8 @@ bool ForceHiveTrig() if (dPiece[cursPosition.x][cursPosition.y] == tileId) { for (int j = 0; j < numtrigs; j++) { if (trigs[j]._tmsg == WM_DIABTWARPUP) { - int dx = abs(trigs[j].position.x - cursPosition.x); - int dy = abs(trigs[j].position.y - cursPosition.y); + int dx = std::abs(trigs[j].position.x - cursPosition.x); + int dy = std::abs(trigs[j].position.y - cursPosition.y); if (dx < 4 && dy < 4) { InfoString = _("Up to town"); cursPosition = trigs[j].position; @@ -682,8 +684,8 @@ bool ForceCryptTrig() if (dPiece[cursPosition.x][cursPosition.y] == tileId) { for (int j = 0; j < numtrigs; j++) { if (trigs[j]._tmsg == WM_DIABTWARPUP) { - int dx = abs(trigs[j].position.x - cursPosition.x); - int dy = abs(trigs[j].position.y - cursPosition.y); + int dx = std::abs(trigs[j].position.x - cursPosition.x); + int dy = std::abs(trigs[j].position.y - cursPosition.y); if (dx < 4 && dy < 4) { InfoString = _("Up to town"); cursPosition = trigs[j].position; @@ -891,19 +893,19 @@ void CheckTriggers() diablo_message abortflag; auto position = myPlayer.position.tile; - if (trigs[i]._tlvl == 5 && myPlayer._pLevel < 8) { + if (trigs[i]._tlvl == 5 && myPlayer.getCharacterLevel() < 8) { abort = true; position.y += 1; abortflag = EMSG_REQUIRES_LVL_8; } - if (IsAnyOf(trigs[i]._tlvl, 9, 17) && myPlayer._pLevel < 13) { + if (IsAnyOf(trigs[i]._tlvl, 9, 17) && myPlayer.getCharacterLevel() < 13) { abort = true; position.x += 1; abortflag = EMSG_REQUIRES_LVL_13; } - if (IsAnyOf(trigs[i]._tlvl, 13, 21) && myPlayer._pLevel < 17) { + if (IsAnyOf(trigs[i]._tlvl, 13, 21) && myPlayer.getCharacterLevel() < 17) { abort = true; position.y += 1; abortflag = EMSG_REQUIRES_LVL_17; @@ -934,10 +936,9 @@ bool EntranceBoundaryContains(Point entrance, Point position) { constexpr Displacement entranceOffsets[7] = { { 0, 0 }, { -1, 0 }, { 0, -1 }, { -1, -1 }, { -2, -1 }, { -1, -2 }, { -2, -2 } }; - return std::any_of( - std::begin(entranceOffsets), - std::end(entranceOffsets), - [&](auto offset) { return entrance + offset == position; }); + return c_any_of( + entranceOffsets, + [=](Displacement offset) { return entrance + offset == position; }); } } // namespace devilution diff --git a/Source/lighting.cpp b/Source/lighting.cpp index 777c7010d4b..00b614b261d 100644 --- a/Source/lighting.cpp +++ b/Source/lighting.cpp @@ -14,6 +14,7 @@ #include "engine/load_file.hpp" #include "engine/points_in_rectangle_range.hpp" #include "player.h" +#include "utils/attributes.h" namespace devilution { @@ -94,7 +95,7 @@ void RotateRadius(DisplacementOf &offset, DisplacementOf &dist, } } -void SetLight(Point position, uint8_t v) +DVL_ALWAYS_INLINE void SetLight(Point position, uint8_t v) { if (LoadingMapObjects) dPreLight[position.x][position.y] = v; @@ -102,7 +103,7 @@ void SetLight(Point position, uint8_t v) dLight[position.x][position.y] = v; } -uint8_t GetLight(Point position) +DVL_ALWAYS_INLINE uint8_t GetLight(Point position) { if (LoadingMapObjects) return dPreLight[position.x][position.y]; @@ -347,7 +348,7 @@ void MakeLightTable() if (leveltype == DTYPE_HELL) { // Blood wall lighting - const int shades = LightTables.size() - 1; + const auto shades = static_cast(LightTables.size() - 1); for (int i = 0; i < shades; i++) { auto &lightTable = LightTables[i]; constexpr int range = 16; @@ -373,17 +374,17 @@ void MakeLightTable() // Generate light falloffs ranges const float maxDarkness = 15; const float maxBrightness = 0; - for (size_t radius = 0; radius < NumLightRadiuses; radius++) { - size_t maxDistance = (radius + 1) * 8; - for (size_t distance = 0; distance < 128; distance++) { + for (unsigned radius = 0; radius < NumLightRadiuses; radius++) { + const unsigned maxDistance = (radius + 1) * 8; + for (unsigned distance = 0; distance < 128; distance++) { if (distance > maxDistance) { LightFalloffs[radius][distance] = 15; } else { - const float factor = static_cast(distance) / maxDistance; + const float factor = static_cast(distance) / static_cast(maxDistance); float scaled; if (IsAnyOf(leveltype, DTYPE_NEST, DTYPE_CRYPT)) { // quardratic falloff with over exposure - const float brightness = radius * 1.25; + const float brightness = static_cast(radius) * 1.25F; scaled = factor * factor * brightness + (maxDarkness - brightness); scaled = std::max(maxBrightness, scaled); } else { @@ -593,7 +594,7 @@ void SavePreLighting() memcpy(dPreLight, dLight, sizeof(dPreLight)); } -void ActivateVision(Point position, int r, int id) +void ActivateVision(Point position, int r, size_t id) { auto &vision = VisionList[id]; vision.position.tile = position; @@ -605,7 +606,7 @@ void ActivateVision(Point position, int r, int id) UpdateVision = true; } -void ChangeVisionRadius(int id, int r) +void ChangeVisionRadius(size_t id, int r) { auto &vision = VisionList[id]; vision.hasChanged = true; @@ -615,7 +616,7 @@ void ChangeVisionRadius(int id, int r) UpdateVision = true; } -void ChangeVisionXY(int id, Point position) +void ChangeVisionXY(size_t id, Point position) { auto &vision = VisionList[id]; vision.hasChanged = true; @@ -633,7 +634,7 @@ void ProcessVisionList() TransList = {}; for (const Player &player : Players) { - int id = player.getId(); + const size_t id = player.getId(); if (!VisionActive[id]) continue; Light &vision = VisionList[id]; @@ -648,7 +649,7 @@ void ProcessVisionList() } } for (const Player &player : Players) { - int id = player.getId(); + const size_t id = player.getId(); if (!VisionActive[id]) continue; Light &vision = VisionList[id]; diff --git a/Source/lighting.h b/Source/lighting.h index 444cff9c0a3..f4404a0678f 100644 --- a/Source/lighting.h +++ b/Source/lighting.h @@ -7,6 +7,8 @@ #include #include +#include +#include #include #include @@ -15,8 +17,6 @@ #include "engine.h" #include "engine/point.hpp" #include "utils/attributes.h" -#include "utils/stdcompat/invoke_result_t.hpp" -#include "utils/stdcompat/optional.hpp" namespace devilution { @@ -74,9 +74,9 @@ void ChangeLightOffset(int i, DisplacementOf offset); void ChangeLight(int i, Point position, uint8_t radius); void ProcessLightList(); void SavePreLighting(); -void ActivateVision(Point position, int r, int id); -void ChangeVisionRadius(int id, int r); -void ChangeVisionXY(int id, Point position); +void ActivateVision(Point position, int r, size_t id); +void ChangeVisionRadius(size_t id, int r); +void ChangeVisionXY(size_t id, Point position); void ProcessVisionList(); void lighting_color_cycling(); @@ -110,9 +110,9 @@ bool DoCrawl(unsigned radius, tl::function_ref function); bool DoCrawl(unsigned minRadius, unsigned maxRadius, tl::function_ref function); template -auto Crawl(unsigned radius, F function) -> invoke_result_t +auto Crawl(unsigned radius, F function) -> std::invoke_result_t { - invoke_result_t result; + std::invoke_result_t result; DoCrawl(radius, [&result, &function](Displacement displacement) -> bool { result = function(displacement); return !result; @@ -121,9 +121,9 @@ auto Crawl(unsigned radius, F function) -> invoke_result_t -auto Crawl(unsigned minRadius, unsigned maxRadius, F function) -> invoke_result_t +auto Crawl(unsigned minRadius, unsigned maxRadius, F function) -> std::invoke_result_t { - invoke_result_t result; + std::invoke_result_t result; DoCrawl(minRadius, maxRadius, [&result, &function](Displacement displacement) -> bool { result = function(displacement); return !result; diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index e43fff4b5cf..c3b9eca1df6 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -34,6 +34,7 @@ #include "playerdat.hpp" #include "qol/stash.h" #include "stores.h" +#include "utils/algorithm/container.hpp" #include "utils/endian.hpp" #include "utils/language.h" @@ -80,7 +81,7 @@ T SwapBE(T in) } class LoadHelper { - std::unique_ptr m_buffer_; + std::unique_ptr m_buffer_; size_t m_cur_ = 0; size_t m_size_; @@ -150,7 +151,7 @@ class LoadHelper { { static_assert(sizeof(TSource) > sizeof(TDesired), "Can only narrow to a smaller type"); TSource value = SwapLE(Next()) + modifier; - return static_cast(clamp(value, std::numeric_limits::min(), std::numeric_limits::max())); + return static_cast(std::clamp(value, std::numeric_limits::min(), std::numeric_limits::max())); } bool NextBool8() @@ -167,7 +168,7 @@ class LoadHelper { class SaveHelper { SaveWriter &m_mpqWriter; const char *m_szFileName_; - std::unique_ptr m_buffer_; + std::unique_ptr m_buffer_; size_t m_cur_ = 0; size_t m_capacity_; @@ -175,7 +176,7 @@ class SaveHelper { SaveHelper(SaveWriter &mpqWriter, const char *szFileName, size_t bufferLen) : m_mpqWriter(mpqWriter) , m_szFileName_(szFileName) - , m_buffer_(new byte[codec_get_encoded_len(bufferLen)]) + , m_buffer_(new std::byte[codec_get_encoded_len(bufferLen)]) , m_capacity_(bufferLen) { } @@ -317,7 +318,11 @@ void LoadItemData(LoadHelper &file, Item &item) else item._iDamAcFlags = ItemSpecialEffectHf::None; UpdateHellfireFlag(item, item._iIName); +} +void LoadAndValidateItemData(LoadHelper &file, Item &item) +{ + LoadItemData(file, item); RemoveInvalidItem(item); } @@ -411,9 +416,7 @@ void LoadPlayer(LoadHelper &file, Player &player) player._pBaseVit = file.NextLE(); player._pStatPts = file.NextLE(); player._pDamageMod = file.NextLE(); - player._pBaseToBlk = file.NextLE(); - if (player._pBaseToBlk == 0) - player._pBaseToBlk = PlayersData[static_cast(player._pClass)].blockBonus; + file.Skip(); // Skip _pBaseToBlk - always a copy of PlayerData.blockBonus player._pHPBase = file.NextLE(); player._pMaxHPBase = file.NextLE(); player._pHitPoints = file.NextLE(); @@ -424,12 +427,12 @@ void LoadPlayer(LoadHelper &file, Player &player) player._pMana = file.NextLE(); player._pMaxMana = file.NextLE(); file.Skip(); // Skip _pManaPer - always derived from mana and maxMana - player._pLevel = file.NextLE(); - player._pMaxLvl = file.NextLE(); - file.Skip(2); // Alignment + player.setCharacterLevel(file.NextLE()); + file.Skip(); // Skip _pMaxLevel - unused + file.Skip(2); // Alignment player._pExperience = file.NextLE(); - file.Skip(); // Skip _pMaxExp - unused - player._pNextExper = file.NextLE(); // This can be calculated based on pLevel (which in turn could be calculated based on pExperience) + file.Skip(); // Skip _pMaxExp - unused + file.Skip(); // Skip _pNextExper, we retrieve it when needed based on _pLevel player._pArmorClass = file.NextLE(); player._pMagResist = file.NextLE(); player._pFireResist = file.NextLE(); @@ -488,10 +491,10 @@ void LoadPlayer(LoadHelper &file, Player &player) file.Skip(); // skip _pBWidth for (Item &item : player.InvBody) - LoadItemData(file, item); + LoadAndValidateItemData(file, item); for (Item &item : player.InvList) - LoadItemData(file, item); + LoadAndValidateItemData(file, item); player._pNumInv = file.NextLE(); @@ -499,9 +502,9 @@ void LoadPlayer(LoadHelper &file, Player &player) cell = file.NextLE(); for (Item &item : player.SpdList) - LoadItemData(file, item); + LoadAndValidateItemData(file, item); - LoadItemData(file, player.HoldItem); + LoadAndValidateItemData(file, player.HoldItem); player._pIMinDam = file.NextLE(); player._pIMaxDam = file.NextLE(); @@ -823,13 +826,13 @@ void LoadObject(LoadHelper &file, Object &object) void LoadItem(LoadHelper &file, Item &item) { - LoadItemData(file, item); + LoadAndValidateItemData(file, item); GetItemFrm(item); } void LoadPremium(LoadHelper &file, int i) { - LoadItemData(file, premiumitems[i]); + LoadAndValidateItemData(file, premiumitems[i]); } void LoadQuest(LoadHelper *file, int i) @@ -895,7 +898,7 @@ void LoadPortal(LoadHelper *file, int i) pPortal->ltype = GetLevelType(pPortal->level); } -void GetLevelNames(string_view prefix, char *out) +void GetLevelNames(std::string_view prefix, char *out) { char suf; uint8_t num; @@ -968,6 +971,11 @@ void LoadMatchingItems(LoadHelper &file, const Player &player, const int n, Item if ((heroItem.dwBuff & CF_HELLFIRE) != (unpackedItem.dwBuff & CF_HELLFIRE)) { unpackedItem = {}; RecreateItem(player, unpackedItem, heroItem.IDidx, heroItem._iCreateInfo, heroItem._iSeed, heroItem._ivalue, (heroItem.dwBuff & CF_HELLFIRE) != 0); + unpackedItem._iIdentified = heroItem._iIdentified; + unpackedItem._iMaxDur = heroItem._iMaxDur; + unpackedItem._iDurability = ClampDurability(unpackedItem, heroItem._iDurability); + unpackedItem._iMaxCharges = std::clamp(heroItem._iMaxCharges, 0, unpackedItem._iMaxCharges); + unpackedItem._iCharges = std::clamp(heroItem._iCharges, 0, unpackedItem._iMaxCharges); } if (!IsShopPriceValid(unpackedItem)) { unpackedItem.clear(); @@ -1223,7 +1231,7 @@ void SavePlayer(SaveHelper &file, const Player &player) file.WriteLE(player._pStatPts); file.WriteLE(player._pDamageMod); - file.WriteLE(player._pBaseToBlk); + file.WriteLE(player.getBaseToBlock()); // set _pBaseToBlk for backwards compatibility file.WriteLE(player._pHPBase); file.WriteLE(player._pMaxHPBase); file.WriteLE(player._pHitPoints); @@ -1234,12 +1242,12 @@ void SavePlayer(SaveHelper &file, const Player &player) file.WriteLE(player._pMana); file.WriteLE(player._pMaxMana); file.Skip(); // Skip _pManaPer - file.WriteLE(player._pLevel); - file.WriteLE(player._pMaxLvl); - file.Skip(2); // Alignment + file.WriteLE(player.getCharacterLevel()); + file.Skip(); // skip _pMaxLevel, this value is uninitialised in most cases in Diablo/Hellfire so there's no point setting it. + file.Skip(2); // Alignment file.WriteLE(player._pExperience); - file.Skip(); // Skip _pMaxExp - file.WriteLE(player._pNextExper); + file.Skip(); // Skip _pMaxExp + file.WriteLE(player.getNextExperienceThreshold()); // set _pNextExper for backwards compatibility file.WriteLE(player._pArmorClass); file.WriteLE(player._pMagResist); file.WriteLE(player._pFireResist); @@ -2069,7 +2077,7 @@ void LoadStash() auto itemCount = file.NextLE(); Stash.stashList.resize(itemCount); for (unsigned i = 0; i < itemCount; i++) { - LoadItemData(file, Stash.stashList[i]); + LoadAndValidateItemData(file, Stash.stashList[i]); } Stash.SetPage(file.NextLE()); @@ -2337,14 +2345,14 @@ void SaveStash(SaveWriter &stashWriter) file.WriteLE(Stash.gold); std::vector pagesToSave; - for (const auto &stashPage : Stash.stashGrids) { - if (std::any_of(stashPage.second.cbegin(), stashPage.second.cend(), [](const auto &row) { - return std::any_of(row.cbegin(), row.cend(), [](auto cell) { + for (const auto &[page, grid] : Stash.stashGrids) { + if (c_any_of(grid, [](const auto &row) { + return c_any_of(row, [](StashStruct::StashCell cell) { return cell > 0; }); })) { // found a page that contains at least one item - pagesToSave.push_back(stashPage.first); + pagesToSave.push_back(page); } }; @@ -2401,7 +2409,7 @@ void SaveGameData(SaveWriter &saveWriter) file.WriteBE(ViewPosition.y); file.WriteLE(invflag ? 1 : 0); file.WriteLE(chrflag ? 1 : 0); - file.WriteBE(ActiveMonsterCount); + file.WriteBE(static_cast(ActiveMonsterCount)); file.WriteBE(ActiveItemCount); // ActiveMissileCount will be a value from 0-125 (for vanilla compatibility). Writing an unsigned value here to avoid // warnings about casting from unsigned to signed, but there's no sign extension issues when reading this as a signed @@ -2461,7 +2469,7 @@ void SaveGameData(SaveWriter &saveWriter) for (int i = 0; i < ActiveLightCount; i++) SaveLighting(&file, &Lights[ActiveLights[i]]); - int visionCount = Players.size(); + const auto visionCount = static_cast(Players.size()); file.WriteBE(visionCount + 1); // VisionId file.WriteBE(visionCount); @@ -2559,7 +2567,7 @@ void SaveLevel(SaveWriter &saveWriter) } } - file.WriteBE(ActiveMonsterCount); + file.WriteBE(static_cast(ActiveMonsterCount)); file.WriteBE(ActiveItemCount); file.WriteBE(ActiveObjectCount); diff --git a/Source/lua/autocomplete.cpp b/Source/lua/autocomplete.cpp new file mode 100644 index 00000000000..0a86a9e38c5 --- /dev/null +++ b/Source/lua/autocomplete.cpp @@ -0,0 +1,210 @@ +#ifdef _DEBUG +#include "lua/autocomplete.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "appfat.h" +#include "engine/assets.hpp" +#include "lua/lua.hpp" +#include "lua/metadoc.hpp" +#include "utils/algorithm/container.hpp" +#include "utils/str_cat.hpp" +#include "utils/str_split.hpp" + +namespace devilution { + +namespace { + +std::string_view GetLastToken(std::string_view text) +{ + if (text.empty()) + return {}; + size_t i = text.size(); + while (i > 0 && text[i - 1] != ' ' && text[i - 1] != '(' && text[i - 1] != ',') + --i; + return text.substr(i); +} + +struct ValueInfo { + bool callable = false; + std::string signature; + std::string docstring; +}; + +sol::protected_function LoadLuaFunctionSignatureGetter(sol::state &lua) +{ + tl::expected src = LoadAsset("lua_internal\\get_lua_function_signature.lua"); + if (!src.has_value()) { + app_fatal(src.error()); + } + const sol::object obj = SafeCallResult(lua.safe_script(std::string_view(src.value())), /*optional=*/false); + if (obj.get_type() != sol::type::function) { + app_fatal("Lua: expected a function"); + } + return obj.as(); +} + +std::string GetNativeLuaFunctionSignature(const sol::object &fn) +{ + sol::state &lua = GetLuaState(); + constexpr std::string_view LuaFunctionSignatureGetterKey = "__DEVILUTIONX_GET_LUA_SIGNATURE"; + sol::object getter = lua[LuaFunctionSignatureGetterKey]; + if (getter.get_type() == sol::type::lua_nil) { + getter = lua[LuaFunctionSignatureGetterKey] = LoadLuaFunctionSignatureGetter(lua); + } + const sol::object obj = SafeCallResult(getter.as()(fn), /*optional=*/false); + if (obj.get_type() != sol::type::string) { + app_fatal(StrCat("Lua: Expected a string, got ", sol::utility::to_string(obj))); + } + return obj.as(); +} + +std::string GetFunctionSignature(const sol::object &value) +{ + value.push(value.lua_state()); + const bool isC = lua_iscfunction(value.lua_state(), -1) != 0; + lua_pop(value.lua_state(), 1); + return isC ? "(...)" : GetNativeLuaFunctionSignature(value); +} + +void RemoveFirstArgumentFromFunctionSignature(std::string &signature) +{ + if (signature == "(...)") return; + size_t firstArgEnd = signature.find_first_of(",)"); + if (firstArgEnd == std::string::npos) return; + ++firstArgEnd; + if (firstArgEnd == signature.size()) { + signature = "()"; + return; + } + if (signature[firstArgEnd] == ' ') ++firstArgEnd; + signature.replace(0, firstArgEnd, "("); +} + +ValueInfo GetValueInfo(const sol::table &table, std::string_view key, const sol::object &value) +{ + ValueInfo info; + if (std::optional signature = GetSignature(table, key); signature.has_value()) { + info.signature = *std::move(signature); + } + if (std::optional docstring = GetDocstring(table, key); docstring.has_value()) { + info.docstring = *std::move(docstring); + } + if (value.get_type() == sol::type::function) { + info.callable = true; + if (info.signature.empty()) info.signature = GetFunctionSignature(value); + return info; + } + if (!value.is()) return info; + const auto valueAsTable = value.as(); + const auto metatable = valueAsTable.get>(sol::metatable_key); + if (!metatable || !metatable->is()) + return info; + const auto metatableTbl = metatable->as(); + const auto callFn = metatableTbl.get>(sol::meta_function::call); + info.callable = callFn.has_value(); + if (info.callable && info.signature.empty()) { + if (info.signature.empty()) { + info.signature = GetFunctionSignature(*callFn); + // Remove the first argument (the table passed to `__call`): + RemoveFirstArgumentFromFunctionSignature(info.signature); + } + } + return info; +} + +void SuggestionsFromTable(const sol::table &table, std::string_view prefix, + size_t maxSuggestions, std::unordered_set &out) +{ + for (const auto &[key, value] : table) { + if (key.get_type() == sol::type::string) { + std::string keyStr = key.as(); + if (!keyStr.starts_with(prefix) || keyStr.size() == prefix.size()) + continue; + if (keyStr.starts_with("__") && !prefix.starts_with("__")) + continue; + // sol-internal keys -- we don't have fonts for these so skip them. + if (keyStr.find("♻") != std::string::npos + || keyStr.find("☢") != std::string::npos + || keyStr.find("🔩") != std::string::npos) + continue; + ValueInfo info = GetValueInfo(table, keyStr, value); + std::string completionText = keyStr.substr(prefix.size()); + LuaAutocompleteSuggestion suggestion { std::move(keyStr), std::move(completionText) }; + if (info.callable) { + suggestion.completionText.append("()"); + suggestion.cursorAdjust = -1; + } + if (!info.signature.empty()) { + StrAppend(suggestion.displayText, info.signature); + } + if (!info.docstring.empty()) { + std::string_view firstLine = info.docstring; + if (const size_t newlinePos = firstLine.find('\n'); newlinePos != std::string_view::npos) { + firstLine = firstLine.substr(0, newlinePos); + } + StrAppend(suggestion.displayText, " - ", firstLine); + } + out.insert(std::move(suggestion)); + if (out.size() == maxSuggestions) + break; + } + } + const auto fallback = table.get>(sol::metatable_key); + if (fallback.has_value() && fallback->get_type() == sol::type::table) { + SuggestionsFromTable(fallback->as(), prefix, maxSuggestions, out); + } +} + +} // namespace + +void GetLuaAutocompleteSuggestions(std::string_view text, const sol::environment &lua, + size_t maxSuggestions, std::vector &out) +{ + out.clear(); + if (text.empty()) return; + std::string_view token = GetLastToken(text); + const char prevChar = token.data() == text.data() ? '\0' : *(token.data() - 1); + if (prevChar == '(' || prevChar == ',') return; + const size_t dotPos = token.rfind('.'); + const std::string_view prefix = token.substr(dotPos + 1); + token.remove_suffix(token.size() - (dotPos == std::string_view::npos ? 0 : dotPos)); + + std::unordered_set suggestions; + const auto addSuggestions = [&](const sol::table &table) { + SuggestionsFromTable(table, prefix, maxSuggestions, suggestions); + }; + + if (token.empty()) { + if (prevChar == '.') return; + addSuggestions(lua); + const auto fallback = lua.get>("_G"); + if (fallback.has_value() && fallback->get_type() == sol::type::table) { + addSuggestions(fallback->as()); + } + } else { + std::optional obj = lua; + for (const std::string_view part : SplitByChar(token, '.')) { + obj = obj->as().get>(part); + if (!obj.has_value() || obj->get_type() != sol::type::table) + return; + } + if (obj->get_type() == sol::type::table) { + addSuggestions(obj->as()); + } + } + + out.insert(out.end(), suggestions.begin(), suggestions.end()); + c_sort(out); +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/autocomplete.hpp b/Source/lua/autocomplete.hpp new file mode 100644 index 00000000000..464d40039f6 --- /dev/null +++ b/Source/lua/autocomplete.hpp @@ -0,0 +1,44 @@ +#pragma once +#ifdef _DEBUG +#include +#include +#include +#include + +#include + +namespace devilution { + +struct LuaAutocompleteSuggestion { + std::string displayText; + std::string completionText; + int cursorAdjust = 0; + + bool operator==(const LuaAutocompleteSuggestion &other) const + { + return displayText == other.displayText; + } + + bool operator<(const LuaAutocompleteSuggestion &other) const + { + return displayText < other.displayText; + } +}; + +void GetLuaAutocompleteSuggestions( + std::string_view text, const sol::environment &lua, + size_t maxSuggestions, std::vector &out); + +} // namespace devilution + +namespace std { +template <> +struct hash { + size_t operator()(const devilution::LuaAutocompleteSuggestion &suggestion) const + { + return hash()(suggestion.displayText); + } +}; +} // namespace std + +#endif // _DEBUG diff --git a/Source/lua/lua.cpp b/Source/lua/lua.cpp new file mode 100644 index 00000000000..18c33b03eb3 --- /dev/null +++ b/Source/lua/lua.cpp @@ -0,0 +1,277 @@ +#include "lua/lua.hpp" + +#include +#include +#include + +#include + +#include + +#include "appfat.h" +#include "engine/assets.hpp" +#include "lua/modules/audio.hpp" +#include "lua/modules/log.hpp" +#include "lua/modules/render.hpp" +#include "plrmsg.h" +#include "utils/console.h" +#include "utils/log.hpp" +#include "utils/str_cat.hpp" + +#ifdef _DEBUG +#include "lua/modules/dev.hpp" +#include "lua/repl.hpp" +#endif + +namespace devilution { + +namespace { + +struct LuaState { + sol::state sol = {}; + sol::table commonPackages = {}; + std::unordered_map compiledScripts = {}; + sol::environment sandbox = {}; + sol::table events = {}; +}; + +std::optional CurrentLuaState; + +// A Lua function that we use to generate a `require` implementation. +constexpr std::string_view RequireGenSrc = R"lua( +function requireGen(env, loaded, loadFn) + return function(packageName) + local p = loaded[packageName] + if p == nil then + local loader = loadFn(packageName) + setEnvironment(loader, env) + if type(loader) == "string" then + error(loader) + end + p = loader(packageName) + loaded[packageName] = p + end + return p + end +end +)lua"; + +sol::object LuaLoadScriptFromAssets(std::string_view packageName) +{ + LuaState &luaState = *CurrentLuaState; + constexpr std::string_view PathPrefix = "lua\\"; + constexpr std::string_view PathSuffix = ".lua"; + std::string path; + path.reserve(PathPrefix.size() + packageName.size() + PathSuffix.size()); + StrAppend(path, PathPrefix, packageName, PathSuffix); + std::replace(path.begin() + PathPrefix.size(), path.end() - PathSuffix.size(), '.', '\\'); + + auto iter = luaState.compiledScripts.find(path); + if (iter != luaState.compiledScripts.end()) { + return luaState.sol.load(iter->second.as_string_view(), path, sol::load_mode::binary); + } + + tl::expected assetData = LoadAsset(path); + if (!assetData.has_value()) { + sol::stack::push(luaState.sol.lua_state(), assetData.error()); + return sol::stack_object(luaState.sol.lua_state(), -1); + } + sol::load_result result = luaState.sol.load(std::string_view(*assetData), path, sol::load_mode::text); + if (!result.valid()) { + sol::stack::push(luaState.sol.lua_state(), + StrCat("Lua error when loading ", path, ": ", result.get())); + return sol::stack_object(luaState.sol.lua_state(), -1); + } + const sol::function fn = result; + luaState.compiledScripts[path] = fn.dump(); + return result; +} + +int LuaPrint(lua_State *state) +{ + const int n = lua_gettop(state); + for (int i = 1; i <= n; i++) { + size_t l; + const char *s = luaL_tolstring(state, i, &l); + if (i > 1) + printInConsole("\t"); + printInConsole(std::string_view(s, l)); + lua_pop(state, 1); + } + printNewlineInConsole(); + return 0; +} + +void LuaWarn(void *userData, const char *message, int continued) +{ + static std::string warnBuffer; + warnBuffer.append(message); + if (continued != 0) + return; + LogWarn("{}", warnBuffer); + warnBuffer.clear(); +} + +sol::object RunScript(std::optional env, std::string_view packageName, bool optional) +{ + sol::object result = LuaLoadScriptFromAssets(packageName); + // We return a string on error: + if (result.get_type() == sol::type::string) { + if (!optional) + app_fatal(result.as()); + LogError("{}", result.as()); + return sol::lua_nil; + } + auto fn = result.as(); + if (env.has_value()) { + sol::set_environment(*env, fn); + } + return SafeCallResult(fn(), optional); +} + +void LuaPanic(sol::optional message) +{ + LogError("Lua is in a panic state and will now abort() the application:\n{}", + message.value_or("unknown error")); +} + +} // namespace + +void Sol2DebugPrintStack(lua_State *state) +{ + LogDebug("{}", sol::detail::debug::dump_types(state)); +} + +void Sol2DebugPrintSection(const std::string &message, lua_State *state) +{ + LogDebug("-- {} -- [ {} ]", message, sol::detail::debug::dump_types(state)); +} + +sol::environment CreateLuaSandbox() +{ + sol::state &lua = CurrentLuaState->sol; + sol::environment sandbox(CurrentLuaState->sol, sol::create); + + // Registering globals + sandbox.set( + "print", LuaPrint, + "_DEBUG", +#ifdef _DEBUG + true, +#else + false, +#endif + "_VERSION", LUA_VERSION); + + // Register safe built-in globals. + for (const std::string_view global : { + // Built-ins: + "assert", "warn", "error", "ipairs", "next", "pairs", "pcall", + "select", "tonumber", "tostring", "type", "xpcall", + "rawequal", "rawget", "rawset", "setmetatable", + // Built-in packages: +#ifdef _DEBUG + "debug", +#endif + "base", "coroutine", "table", "string", "math", "utf8" }) { + const sol::object obj = lua[global]; + if (obj.get_type() == sol::type::lua_nil) { + app_fatal(StrCat("Missing Lua global [", global, "]")); + } + sandbox[global] = obj; + } + + // We only allow datetime-related functions from `os`: + const sol::table os = lua["os"]; + sandbox.create_named("os", + "date", os["date"], + "difftime", os["difftime"], + "time", os["time"]); + + sandbox["require"] = lua["requireGen"](sandbox, CurrentLuaState->commonPackages, LuaLoadScriptFromAssets); + + return sandbox; +} + +void LuaInitialize() +{ + CurrentLuaState.emplace(LuaState { .sol = { sol::c_call } }); + sol::state &lua = CurrentLuaState->sol; + lua_setwarnf(lua.lua_state(), LuaWarn, /*ud=*/nullptr); + lua.open_libraries( + sol::lib::base, + sol::lib::coroutine, + sol::lib::debug, + sol::lib::math, + sol::lib::os, + sol::lib::package, + sol::lib::string, + sol::lib::table, + sol::lib::utf8); + + // Registering devilutionx object table + SafeCallResult(lua.safe_script(RequireGenSrc), /*optional=*/false); + + // Loaded without a sandbox. + CurrentLuaState->events = RunScript(/*env=*/std::nullopt, "devilutionx.events", /*optional=*/false); + + CurrentLuaState->commonPackages = lua.create_table_with( +#ifdef _DEBUG + "devilutionx.dev", LuaDevModule(lua), +#endif + "devilutionx.version", PROJECT_VERSION, + "devilutionx.log", LuaLogModule(lua), + "devilutionx.audio", LuaAudioModule(lua), + "devilutionx.render", LuaRenderModule(lua), + "devilutionx.message", [](std::string_view text) { EventPlrMsg(text, UiFlags::ColorRed); }, + // These packages are loaded without a sandbox: + "devilutionx.events", CurrentLuaState->events, + "inspect", RunScript(/*env=*/std::nullopt, "inspect", /*optional=*/false)); + + // Used by the custom require implementation. + lua["setEnvironment"] = [](const sol::environment &env, const sol::function &fn) { sol::set_environment(env, fn); }; + + RunScript(CreateLuaSandbox(), "user", /*optional=*/true); + + LuaEvent("GameBoot"); +} + +void LuaShutdown() +{ +#ifdef _DEBUG + LuaReplShutdown(); +#endif + CurrentLuaState = std::nullopt; +} + +void LuaEvent(std::string_view name) +{ + 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(), /*optional=*/true); +} + +sol::state &GetLuaState() +{ + return CurrentLuaState->sol; +} + +sol::object SafeCallResult(sol::protected_function_result result, bool optional) +{ + const bool valid = result.valid(); + if (!valid) { + const std::string error = result.get_type() == sol::type::string + ? StrCat("Lua error: ", result.get()) + : "Unknown Lua error"; + if (!optional) + app_fatal(error); + LogError(error); + } + return result; +} + +} // namespace devilution diff --git a/Source/lua/lua.hpp b/Source/lua/lua.hpp new file mode 100644 index 00000000000..3efb8fcec2c --- /dev/null +++ b/Source/lua/lua.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +#include +#include + +namespace devilution { + +void LuaInitialize(); +void LuaShutdown(); +void LuaEvent(std::string_view name); +sol::state &GetLuaState(); +sol::environment CreateLuaSandbox(); +sol::object SafeCallResult(sol::protected_function_result result, bool optional); + +} // namespace devilution diff --git a/Source/lua/metadoc.hpp b/Source/lua/metadoc.hpp new file mode 100644 index 00000000000..9f69da4dd5b --- /dev/null +++ b/Source/lua/metadoc.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +#include + +#include "utils/str_cat.hpp" + +namespace devilution { + +inline std::string LuaSignatureKey(std::string_view key) +{ + return StrCat("__sig_", key); +} + +inline std::string LuaDocstringKey(std::string_view key) +{ + return StrCat("__doc_", key); +} + +template +void SetDocumented(sol::table &table, std::string_view key, std::string_view signature, std::string_view doc, T &&value) +{ + table[key] = std::forward(value); + table[LuaSignatureKey(key)] = signature; + table[LuaDocstringKey(key)] = doc; +} + +template +void SetWithSignature(sol::table &table, std::string_view key, std::string_view signature, T &&value) +{ + table[key] = std::forward(value); + table[LuaSignatureKey(key)] = signature; +} + +inline std::optional GetSignature(const sol::table &table, std::string_view key) +{ + return table.get>(LuaSignatureKey(key)); +} + +inline std::optional GetDocstring(const sol::table &table, std::string_view key) +{ + return table.get>(LuaDocstringKey(key)); +} + +} // namespace devilution diff --git a/Source/lua/modules/audio.cpp b/Source/lua/modules/audio.cpp new file mode 100644 index 00000000000..e090ded0589 --- /dev/null +++ b/Source/lua/modules/audio.cpp @@ -0,0 +1,31 @@ +#include "lua/modules/render.hpp" + +#include + +#include "effects.h" +#include "lua/metadoc.hpp" + +namespace devilution { + +namespace { + +bool IsValidSfx(int16_t psfx) +{ + return psfx >= 0 && psfx <= static_cast(SfxID::LAST); +} + +} // namespace + +sol::table LuaAudioModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetWithSignature(table, + "playSfx", "(id: number)", + [](int16_t psfx) { if (IsValidSfx(psfx)) PlaySFX(static_cast(psfx)); }); + SetWithSignature(table, + "playSfxLoc", "(id: number, x: number, y: number)", + [](int16_t psfx, int x, int y) { if (IsValidSfx(psfx)) PlaySfxLoc(static_cast(psfx), { x, y }); }); + return table; +} + +} // namespace devilution diff --git a/Source/lua/modules/audio.hpp b/Source/lua/modules/audio.hpp new file mode 100644 index 00000000000..5dd09a62966 --- /dev/null +++ b/Source/lua/modules/audio.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace devilution { + +sol::table LuaAudioModule(sol::state_view &lua); + +} // namespace devilution diff --git a/Source/lua/modules/dev.cpp b/Source/lua/modules/dev.cpp new file mode 100644 index 00000000000..e3de74633d7 --- /dev/null +++ b/Source/lua/modules/dev.cpp @@ -0,0 +1,33 @@ +#ifdef _DEBUG +#include "lua/modules/dev.hpp" + +#include + +#include "lua/metadoc.hpp" +#include "lua/modules/dev/display.hpp" +#include "lua/modules/dev/items.hpp" +#include "lua/modules/dev/level.hpp" +#include "lua/modules/dev/monsters.hpp" +#include "lua/modules/dev/player.hpp" +#include "lua/modules/dev/quests.hpp" +#include "lua/modules/dev/search.hpp" +#include "lua/modules/dev/towners.hpp" + +namespace devilution { + +sol::table LuaDevModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "display", "", "Debugging HUD and rendering commands.", LuaDevDisplayModule(lua)); + SetDocumented(table, "items", "", "Item-related commands.", LuaDevItemsModule(lua)); + SetDocumented(table, "level", "", "Level-related commands.", LuaDevLevelModule(lua)); + SetDocumented(table, "monsters", "", "Monster-related commands.", LuaDevMonstersModule(lua)); + SetDocumented(table, "player", "", "Player-related commands.", LuaDevPlayerModule(lua)); + SetDocumented(table, "quests", "", "Quest-related commands.", LuaDevQuestsModule(lua)); + SetDocumented(table, "search", "", "Search the map for monsters / items / objects.", LuaDevSearchModule(lua)); + SetDocumented(table, "towners", "", "Town NPC commands.", LuaDevTownersModule(lua)); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev.hpp b/Source/lua/modules/dev.hpp new file mode 100644 index 00000000000..ca71c644031 --- /dev/null +++ b/Source/lua/modules/dev.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/display.cpp b/Source/lua/modules/dev/display.cpp new file mode 100644 index 00000000000..9ae89f1c908 --- /dev/null +++ b/Source/lua/modules/dev/display.cpp @@ -0,0 +1,133 @@ +#ifdef _DEBUG +#include "lua/modules/dev/display.hpp" + +#include +#include +#include + +#include + +#include "debug.h" +#include "lighting.h" +#include "lua/metadoc.hpp" +#include "player.h" +#include "utils/str_cat.hpp" + +namespace devilution { +namespace { + +std::string DebugCmdShowGrid(std::optional on) +{ + DebugGrid = on.value_or(!DebugGrid); + return StrCat("Tile grid highlighting: ", DebugGrid ? "On" : "Off"); +} + +std::string DebugCmdVision(std::optional on) +{ + DebugVision = on.value_or(!DebugVision); + return StrCat("Vision highlighting: ", DebugVision ? "On" : "Off"); +} + +std::string DebugCmdPath(std::optional on) +{ + DebugPath = on.value_or(!DebugPath); + return StrCat("Path highlighting: ", DebugPath ? "On" : "Off"); +} + +std::string DebugCmdFullbright(std::optional on) +{ + ToggleLighting(); + return StrCat("Fullbright: ", DisableLighting ? "On" : "Off"); +} + +std::string DebugCmdShowTileData(std::optional dataType) +{ + static const std::array DataTypes { + "dPiece", + "dTransVal", + "dLight", + "dPreLight", + "dFlags", + "dPlayer", + "dMonster", + "dCorpse", + "dObject", + "dItem", + "dSpecial", + "coords", + "cursorcoords", + "objectindex", + "solid", + "transparent", + "trap", + "AutomapView", + "dungeon", + "pdungeon", + "Protected", + }; + if (!dataType.has_value()) { + std::string result = "Valid values for the first argument:\nclear"; + for (const std::string_view &str : DataTypes) + StrAppend(result, ", ", str); + return result; + } + if (*dataType == "clear") { + SetDebugGridTextType(DebugGridTextItem::None); + return "Tile data cleared."; + } + bool found = false; + int index = 0; + for (const std::string_view ¶m : DataTypes) { + index++; + if (*dataType != param) + continue; + found = true; + auto newGridText = static_cast(index); + if (newGridText == GetDebugGridTextType()) { + SetDebugGridTextType(DebugGridTextItem::None); + return "Tile data: Off"; + } + SetDebugGridTextType(newGridText); + break; + } + if (!found) { + std::string result = "Invalid name! Valid names are:\nclear"; + for (const std::string_view &str : DataTypes) + StrAppend(result, ", ", str); + return result; + } + + return "Tile data: On"; +} + +std::string DebugCmdScrollView(std::optional on) +{ + DebugScrollViewEnabled = on.value_or(!DebugScrollViewEnabled); + if (!DebugScrollViewEnabled) + InitMultiView(); + return StrCat("Scroll view: ", DebugScrollViewEnabled ? "On" : "Off"); +} + +std::string DebugCmdToggleFPS(std::optional on) +{ + frameflag = on.value_or(!frameflag); + return StrCat("FPS counter: ", frameflag ? "On" : "Off"); +} + +} // namespace + +sol::table LuaDevDisplayModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "fps", "(name: string = nil)", "Toggle FPS display.", &DebugCmdToggleFPS); + SetDocumented(table, "fullbright", "(on: boolean = nil)", "Toggle light shading.", &DebugCmdFullbright); + SetDocumented(table, "grid", "(on: boolean = nil)", "Toggle showing the grid.", &DebugCmdShowGrid); + SetDocumented(table, "path", "(on: boolean = nil)", "Toggle path debug rendering.", &DebugCmdPath); + SetDocumented(table, "scrollView", "(on: boolean = nil)", "Toggle view scrolling via Shift+Mouse.", &DebugCmdScrollView); + SetDocumented(table, "tileData", "(name: string = nil)", "Toggle showing tile data.", &DebugCmdShowTileData); + SetDocumented(table, "vision", "(on: boolean = nil)", "Toggle vision debug rendering.", &DebugCmdVision); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/display.hpp b/Source/lua/modules/dev/display.hpp new file mode 100644 index 00000000000..fb7adb669df --- /dev/null +++ b/Source/lua/modules/dev/display.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevDisplayModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/items.cpp b/Source/lua/modules/dev/items.cpp new file mode 100644 index 00000000000..6d6d95100cd --- /dev/null +++ b/Source/lua/modules/dev/items.cpp @@ -0,0 +1,73 @@ +#ifdef _DEBUG +#include "lua/modules/dev/items.hpp" + +#include + +#include + +#include "cursor.h" +#include "items.h" +#include "lua/metadoc.hpp" +#include "pack.h" +#include "player.h" +#include "utils/str_cat.hpp" + +namespace devilution { + +namespace { + +std::string DebugCmdItemInfo() +{ + Player &myPlayer = *MyPlayer; + Item *pItem = nullptr; + if (!myPlayer.HoldItem.isEmpty()) { + pItem = &myPlayer.HoldItem; + } else if (pcursinvitem != -1) { + if (pcursinvitem <= INVITEM_INV_LAST) + pItem = &myPlayer.InvList[pcursinvitem - INVITEM_INV_FIRST]; + else + pItem = &myPlayer.SpdList[pcursinvitem - INVITEM_BELT_FIRST]; + } else if (pcursitem != -1) { + pItem = &Items[pcursitem]; + } + if (pItem != nullptr) { + std::string_view netPackValidation { "N/A" }; + if (gbIsMultiplayer) { + ItemNetPack itemPack; + Item unpacked; + PackNetItem(*pItem, itemPack); + netPackValidation = UnPackNetItem(myPlayer, itemPack, unpacked) ? "Success" : "Failure"; + } + return StrCat("Name: ", pItem->_iIName, + "\nIDidx: ", pItem->IDidx, " (", AllItemsList[pItem->IDidx].iName, ")", + "\nSeed: ", pItem->_iSeed, + "\nCreateInfo: ", pItem->_iCreateInfo, + "\nLevel: ", pItem->_iCreateInfo & CF_LEVEL, + "\nOnly Good: ", ((pItem->_iCreateInfo & CF_ONLYGOOD) == 0) ? "False" : "True", + "\nUnique Monster: ", ((pItem->_iCreateInfo & CF_UPER15) == 0) ? "False" : "True", + "\nDungeon Item: ", ((pItem->_iCreateInfo & CF_UPER1) == 0) ? "False" : "True", + "\nUnique Item: ", ((pItem->_iCreateInfo & CF_UNIQUE) == 0) ? "False" : "True", + "\nSmith: ", ((pItem->_iCreateInfo & CF_SMITH) == 0) ? "False" : "True", + "\nSmith Premium: ", ((pItem->_iCreateInfo & CF_SMITHPREMIUM) == 0) ? "False" : "True", + "\nBoy: ", ((pItem->_iCreateInfo & CF_BOY) == 0) ? "False" : "True", + "\nWitch: ", ((pItem->_iCreateInfo & CF_WITCH) == 0) ? "False" : "True", + "\nHealer: ", ((pItem->_iCreateInfo & CF_HEALER) == 0) ? "False" : "True", + "\nPregen: ", ((pItem->_iCreateInfo & CF_PREGEN) == 0) ? "False" : "True", + "\nNet Validation: ", netPackValidation); + } + return StrCat("Num items: ", ActiveItemCount); +} + +} // namespace + +sol::table LuaDevItemsModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "info", "()", "Show info of currently selected item.", &DebugCmdItemInfo); + SetDocumented(table, "spawn", "(name: string)", "Attempt to generate an item.", &DebugSpawnItem); + SetDocumented(table, "spawnUnique", "(name: string)", "Attempt to generate a unique item.", &DebugSpawnUniqueItem); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/items.hpp b/Source/lua/modules/dev/items.hpp new file mode 100644 index 00000000000..9e7b868f6ad --- /dev/null +++ b/Source/lua/modules/dev/items.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevItemsModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/level.cpp b/Source/lua/modules/dev/level.cpp new file mode 100644 index 00000000000..be3d6b6d00b --- /dev/null +++ b/Source/lua/modules/dev/level.cpp @@ -0,0 +1,133 @@ +#ifdef _DEBUG +#include "lua/modules/dev/level.hpp" + +#include +#include +#include +#include + +#include + +#include "diablo.h" +#include "levels/gendung.h" +#include "lua/metadoc.hpp" +#include "lua/modules/dev/level/map.hpp" +#include "lua/modules/dev/level/warp.hpp" +#include "monster.h" +#include "objects.h" +#include "player.h" +#include "utils/endian_stream.hpp" +#include "utils/file_util.h" + +namespace devilution { + +namespace { + +std::string ExportDun() +{ + const std::string levelName = StrCat(currlevel, "-", glSeedTbl[currlevel], ".dun"); + FILE *dunFile = OpenFile(levelName.c_str(), "ab"); + + WriteLE16(dunFile, DMAXX); + WriteLE16(dunFile, DMAXY); + + /** Tiles. */ + for (int y = 0; y < DMAXY; y++) { + for (int x = 0; x < DMAXX; x++) { + WriteLE16(dunFile, dungeon[x][y]); + } + } + + /** Padding */ + for (int y = 16; y < MAXDUNY - 16; y++) { + for (int x = 16; x < MAXDUNX - 16; x++) { + WriteLE16(dunFile, 0); + } + } + + /** Monsters */ + for (int y = 16; y < MAXDUNY - 16; y++) { + for (int x = 16; x < MAXDUNX - 16; x++) { + uint16_t monsterId = 0; + if (dMonster[x][y] > 0) { + for (int i = 0; i < 157; i++) { + if (MonstConvTbl[i] == Monsters[std::abs(dMonster[x][y]) - 1].type().type) { + monsterId = i + 1; + break; + } + } + } + WriteLE16(dunFile, monsterId); + } + } + + /** Objects */ + for (int y = 16; y < MAXDUNY - 16; y++) { + for (int x = 16; x < MAXDUNX - 16; x++) { + uint16_t objectId = 0; + Object *object = FindObjectAtPosition({ x, y }, false); + if (object != nullptr) { + for (int i = 0; i < 147; i++) { + if (ObjTypeConv[i] == object->_otype) { + objectId = i; + break; + } + } + } + WriteLE16(dunFile, objectId); + } + } + + /** Transparency */ + for (int y = 16; y < MAXDUNY - 16; y++) { + for (int x = 16; x < MAXDUNX - 16; x++) { + WriteLE16(dunFile, dTransVal[x][y]); + } + } + std::fclose(dunFile); + + return StrCat("Successfully exported ", levelName, "."); +} + +std::string DebugCmdResetLevel(uint8_t level, std::optional seed) +{ + Player &myPlayer = *MyPlayer; + if (level > (gbIsHellfire ? 24 : 16)) + return StrCat("Level ", level, " does not exist!"); + if (myPlayer.isOnLevel(level)) + return "Unable to reset dungeon levels occupied by players!"; + + myPlayer._pLvlVisited[level] = false; + DeltaClearLevel(level); + + if (seed.has_value()) { + glSeedTbl[level] = *seed; + return StrCat("Successfully reset level ", level, " with seed ", *seed, "."); + } + return StrCat("Successfully reset level ", level, "."); +} + +std::string DebugCmdLevelSeed(std::optional level) +{ + constexpr size_t NumLevels = sizeof(glSeedTbl) / sizeof(glSeedTbl[0]); + if (level.has_value() && *level >= NumLevels) { + return StrCat("level out of range, max: ", NumLevels - 1); + } + return StrCat(glSeedTbl[level.value_or(currlevel)]); +} + +} // namespace + +sol::table LuaDevLevelModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "exportDun", "()", "Save the current level as a dun-file.", &ExportDun); + SetDocumented(table, "map", "", "Automap-related commands.", LuaDevLevelMapModule(lua)); + SetDocumented(table, "reset", "(n: number, seed: number = nil)", "Resets specified level.", &DebugCmdResetLevel); + SetDocumented(table, "seed", "(level: number = nil)", "Get the seed of the current or given level.", &DebugCmdLevelSeed); + SetDocumented(table, "warp", "", "Warp to a level or a custom map.", LuaDevLevelWarpModule(lua)); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/level.hpp b/Source/lua/modules/dev/level.hpp new file mode 100644 index 00000000000..61253bc0db1 --- /dev/null +++ b/Source/lua/modules/dev/level.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevLevelModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/level/map.cpp b/Source/lua/modules/dev/level/map.cpp new file mode 100644 index 00000000000..b1067fcad5b --- /dev/null +++ b/Source/lua/modules/dev/level/map.cpp @@ -0,0 +1,41 @@ +#ifdef _DEBUG +#include "lua/modules/dev/level/map.hpp" + +#include + +#include + +#include "automap.h" +#include "lua/metadoc.hpp" + +namespace devilution { +namespace { + +std::string DebugCmdMapReveal() +{ + for (int x = 0; x < DMAXX; x++) + for (int y = 0; y < DMAXY; y++) + UpdateAutomapExplorer({ x, y }, MAP_EXP_SHRINE); + return "Automap fully explored."; +} + +std::string DebugCmdMapHide() +{ + for (int x = 0; x < DMAXX; x++) + for (int y = 0; y < DMAXY; y++) + AutomapView[x][y] = MAP_EXP_NONE; + return "Automap exploration removed."; +} + +} // namespace + +sol::table LuaDevLevelMapModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "hide", "()", "Hide the map.", &DebugCmdMapHide); + SetDocumented(table, "reveal", "()", "Reveal the map.", &DebugCmdMapReveal); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/level/map.hpp b/Source/lua/modules/dev/level/map.hpp new file mode 100644 index 00000000000..895bdbcda66 --- /dev/null +++ b/Source/lua/modules/dev/level/map.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevLevelMapModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/level/warp.cpp b/Source/lua/modules/dev/level/warp.cpp new file mode 100644 index 00000000000..655a36289f0 --- /dev/null +++ b/Source/lua/modules/dev/level/warp.cpp @@ -0,0 +1,82 @@ +#ifdef _DEBUG +#include "lua/modules/dev/level/warp.hpp" + +#include +#include +#include + +#include + +#include "debug.h" +#include "interfac.h" +#include "levels/setmaps.h" +#include "lua/metadoc.hpp" +#include "player.h" +#include "quests.h" +#include "utils/str_cat.hpp" + +namespace devilution { +namespace { + +std::string DebugCmdWarpToDungeonLevel(uint8_t level) +{ + Player &myPlayer = *MyPlayer; + if (level > (gbIsHellfire ? 24 : 16)) + return StrCat("Level ", level, " does not exist!"); + if (!setlevel && myPlayer.isOnLevel(level)) + return StrCat("You are already on level ", level, "!"); + + StartNewLvl(myPlayer, (level != 21) ? interface_mode::WM_DIABNEXTLVL : interface_mode::WM_DIABTOWNWARP, level); + return StrCat("Moved you to level ", level, "."); +} + +std::string DebugCmdWarpToQuestLevel(uint8_t level) +{ + if (level < 1) + return StrCat("Quest level number must be 1 or higher!"); + if (setlevel && setlvlnum == level) + return StrCat("You are already on quest level", level, "!"); + + for (Quest &quest : Quests) { + if (level != quest._qslvl) + continue; + + setlvltype = quest._qlvltype; + StartNewLvl(*MyPlayer, WM_DIABSETLVL, level); + + return StrCat("Moved you to quest level ", QuestLevelNames[level], "."); + } + + return StrCat("Quest level ", level, " does not exist!"); +} + +std::string DebugCmdWarpToCustomMap(std::string_view path, int dunType, int x, int y) +{ + if (path.empty()) return "path is required"; + if (dunType < DTYPE_CATHEDRAL || dunType > DTYPE_LAST) return "invalid dunType"; + + const Point spawn { x, y }; + if (!InDungeonBounds(spawn)) return "spawn location is out of bounds"; + + TestMapPath = StrCat(path, ".dun"); + setlvltype = static_cast(dunType); + ViewPosition = spawn; + + StartNewLvl(*MyPlayer, WM_DIABSETLVL, SL_NONE); + + return StrCat("Moved you to ", TestMapPath, "."); +} + +} // namespace + +sol::table LuaDevLevelWarpModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "dungeon", "(n: number)", "Go to dungeon level (0 for town).", &DebugCmdWarpToDungeonLevel); + SetDocumented(table, "map", "(path: string, dunType: number, x: number, y: number)", "Go to custom {path}.dun level", &DebugCmdWarpToCustomMap); + SetDocumented(table, "quest", "(n: number)", "Go to quest level.", &DebugCmdWarpToQuestLevel); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/level/warp.hpp b/Source/lua/modules/dev/level/warp.hpp new file mode 100644 index 00000000000..7ff3401a774 --- /dev/null +++ b/Source/lua/modules/dev/level/warp.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevLevelWarpModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/monsters.cpp b/Source/lua/modules/dev/monsters.cpp new file mode 100644 index 00000000000..42b0ccd8488 --- /dev/null +++ b/Source/lua/modules/dev/monsters.cpp @@ -0,0 +1,175 @@ +#ifdef _DEBUG +#include "lua/modules/dev/monsters.hpp" + +#include +#include + +#include + +#include "levels/gendung.h" +#include "lighting.h" +#include "lua/metadoc.hpp" +#include "monstdat.h" +#include "monster.h" +#include "player.h" +#include "utils/str_case.hpp" +#include "utils/str_cat.hpp" + +namespace devilution { + +namespace { + +std::string DebugCmdSpawnUniqueMonster(std::string name, std::optional countOpt) +{ + if (leveltype == DTYPE_TOWN) return "Can't spawn monsters in town"; + if (name.empty()) return "name is required"; + const unsigned count = countOpt.value_or(1); + if (count < 1) return "count must be positive"; + + AsciiStrToLower(name); + + int mtype = -1; + UniqueMonsterType uniqueIndex = UniqueMonsterType::None; + for (size_t i = 0; i < UniqueMonstersData.size(); ++i) { + const auto &mondata = UniqueMonstersData[i]; + const std::string monsterName = AsciiStrToLower(std::string_view(mondata.mName)); + if (monsterName.find(name) == std::string::npos) + continue; + mtype = mondata.mtype; + uniqueIndex = static_cast(i); + if (monsterName == name) // to support partial name matching but always choose the correct monster if full name is given + break; + } + + if (mtype == -1) return "Monster not found"; + + size_t id = MaxLvlMTypes - 1; + bool found = false; + + for (size_t i = 0; i < LevelMonsterTypeCount; i++) { + if (LevelMonsterTypes[i].type == mtype) { + id = i; + found = true; + break; + } + } + + if (!found) { + if (LevelMonsterTypeCount == MaxLvlMTypes) + LevelMonsterTypeCount--; // we are running out of monster types, so override last used monster type + id = AddMonsterType(uniqueIndex, PLACE_SCATTER); + CMonster &monsterType = LevelMonsterTypes[id]; + InitMonsterGFX(monsterType); + monsterType.corpseId = 1; + } + + Player &myPlayer = *MyPlayer; + + unsigned spawnedMonster = 0; + + auto ret = Crawl(0, MaxCrawlRadius, [&](Displacement displacement) -> std::optional { + Point pos = myPlayer.position.tile + displacement; + if (dPlayer[pos.x][pos.y] != 0 || dMonster[pos.x][pos.y] != 0) + return {}; + if (!IsTileWalkable(pos)) + return {}; + + Monster *monster = AddMonster(pos, myPlayer._pdir, id, true); + if (monster == nullptr) + return StrCat("Spawned ", spawnedMonster, " monsters. (Unable to spawn more)"); + PrepareUniqueMonst(*monster, uniqueIndex, 0, 0, UniqueMonstersData[static_cast(uniqueIndex)]); + monster->corpseId = 1; + spawnedMonster += 1; + + if (spawnedMonster >= count) + return StrCat("Spawned ", spawnedMonster, " monsters."); + + return {}; + }); + + if (!ret.has_value()) + ret = StrCat("Spawned ", spawnedMonster, " monsters. (Unable to spawn more)"); + return *ret; +} + +std::string DebugCmdSpawnMonster(std::string name, std::optional countOpt) +{ + if (leveltype == DTYPE_TOWN) return "Can't spawn monsters in town"; + if (name.empty()) return "name is required"; + const unsigned count = countOpt.value_or(1); + if (count < 1) return "count must be positive"; + + AsciiStrToLower(name); + + int mtype = -1; + + for (int i = 0; i < NUM_MTYPES; i++) { + const auto &mondata = MonstersData[i]; + const std::string monsterName = AsciiStrToLower(std::string_view(mondata.name)); + if (monsterName.find(name) == std::string::npos) + continue; + mtype = i; + if (monsterName == name) // to support partial name matching but always choose the correct monster if full name is given + break; + } + + if (mtype == -1) return "Monster not found"; + if (!MyPlayer->isLevelOwnedByLocalClient()) return "You are not the level owner."; + + size_t id = MaxLvlMTypes - 1; + bool found = false; + + for (size_t i = 0; i < LevelMonsterTypeCount; i++) { + if (LevelMonsterTypes[i].type == mtype) { + id = i; + found = true; + break; + } + } + + if (!found) { + if (LevelMonsterTypeCount == MaxLvlMTypes) + LevelMonsterTypeCount--; // we are running out of monster types, so override last used monster type + id = AddMonsterType(static_cast<_monster_id>(mtype), PLACE_SCATTER); + CMonster &monsterType = LevelMonsterTypes[id]; + InitMonsterGFX(monsterType); + monsterType.corpseId = 1; + } + + Player &myPlayer = *MyPlayer; + + size_t monstersToSpawn = std::min(MaxMonsters - ActiveMonsterCount, count); + if (monstersToSpawn == 0) + return "Can't spawn any monsters"; + + size_t spawnedMonster = 0; + Crawl(0, MaxCrawlRadius, [&](Displacement displacement) { + Point pos = myPlayer.position.tile + displacement; + if (dPlayer[pos.x][pos.y] != 0 || dMonster[pos.x][pos.y] != 0) + return false; + if (!IsTileWalkable(pos)) + return false; + + SpawnMonster(pos, myPlayer._pdir, id); + spawnedMonster += 1; + + return spawnedMonster == monstersToSpawn; + }); + + if (monstersToSpawn != count) + return StrCat("Spawned ", spawnedMonster, " monsters. (Unable to spawn more)"); + return StrCat("Spawned ", spawnedMonster, " monsters."); +} + +} // namespace + +sol::table LuaDevMonstersModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "spawn", "(name: string, count: number = 1)", "Spawn monster(s)", &DebugCmdSpawnMonster); + SetDocumented(table, "spawnUnique", "(name: string, count: number = 1)", "Spawn unique monster(s)", &DebugCmdSpawnUniqueMonster); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/monsters.hpp b/Source/lua/modules/dev/monsters.hpp new file mode 100644 index 00000000000..cbb6a5d1346 --- /dev/null +++ b/Source/lua/modules/dev/monsters.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevMonstersModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/player.cpp b/Source/lua/modules/dev/player.cpp new file mode 100644 index 00000000000..31d870a492d --- /dev/null +++ b/Source/lua/modules/dev/player.cpp @@ -0,0 +1,109 @@ +#ifdef _DEBUG +#include "lua/modules/dev/player.hpp" + +#include +#include + +#include + +#include "debug.h" +#include "engine/assets.hpp" +#include "lua/metadoc.hpp" +#include "lua/modules/dev/player/gold.hpp" +#include "lua/modules/dev/player/spells.hpp" +#include "lua/modules/dev/player/stats.hpp" +#include "player.h" + +namespace devilution { + +namespace { + +std::string DebugCmdArrow(std::string_view effect) +{ + Player &myPlayer = *MyPlayer; + + myPlayer._pIFlags &= ~ItemSpecialEffect::FireArrows; + myPlayer._pIFlags &= ~ItemSpecialEffect::LightningArrows; + + if (effect == "normal") { + // we removed the parameter at the top + } else if (effect == "fire") { + myPlayer._pIFlags |= ItemSpecialEffect::FireArrows; + } else if (effect == "lightning") { + myPlayer._pIFlags |= ItemSpecialEffect::LightningArrows; + } else if (effect == "spectral") { + myPlayer._pIFlags |= (ItemSpecialEffect::FireArrows | ItemSpecialEffect::LightningArrows); + } else { + return "Invalid effect!"; + } + + return StrCat("Arrows changed to: ", effect); +} + +std::string DebugCmdGodMode(std::optional on) +{ + DebugGodMode = on.value_or(!DebugGodMode); + return StrCat("God mode: ", DebugGodMode ? "On" : "Off"); +} + +std::string DebugCmdPlayerInfo(std::optional id) +{ + const uint8_t playerId = id.value_or(0); + if (playerId >= Players.size()) + return StrCat("Invalid player ID (max: ", Players.size() - 1, ")"); + Player &player = Players[playerId]; + if (!player.plractive) + return StrCat("Player ", playerId, " is not active!"); + + const Point target = player.GetTargetPosition(); + return StrCat("Plr ", playerId, " is ", player._pName, + "\nLvl: ", player.plrlevel, " Changing: ", player._pLvlChanging, + "\nTile.x: ", player.position.tile.x, " Tile.y: ", player.position.tile.y, " Target.x: ", target.x, " Target.y: ", target.y, + "\nMode: ", player._pmode, " destAction: ", player.destAction, " walkpath[0]: ", player.walkpath[0], + "\nInvincible: ", player._pInvincible ? 1 : 0, " HitPoints: ", player._pHitPoints); +} + +std::string DebugSetPlayerTrn(std::string_view path) +{ + if (!path.empty()) { + if (const AssetRef ref = FindAsset(path); !ref.ok()) { + const char *error = ref.error(); + return error == nullptr || *error == '\0' ? StrCat("File not found: ", path) : error; + } + } + debugTRN = path; + Player &player = *MyPlayer; + InitPlayerGFX(player); + StartStand(player, player._pdir); + return path.empty() ? "TRN unset" : "TRN set"; +} + +sol::table LuaDevPlayerTrnModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "mon", "(name: string)", "Set player TRN to monsters\\${name}.trn", + [](std::string_view name) { return DebugSetPlayerTrn(StrCat("monsters\\", name, ".trn")); }); + SetDocumented(table, "plr", "(name: string)", "Set player TRN to plrgfx\\${name}.trn", + [](std::string_view name) { return DebugSetPlayerTrn(StrCat("plrgfx\\", name, ".trn")); }); + SetDocumented(table, "clear", "()", "Unset player TRN", + []() { return DebugSetPlayerTrn(""); }); + return table; +} + +} // namespace + +sol::table LuaDevPlayerModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "arrow", "(effect: 'normal'|'fire'|'lightning'|'explosion')", "Set arrow effect.", &DebugCmdArrow); + SetDocumented(table, "god", "(on: boolean = nil)", "Toggle god mode.", &DebugCmdGodMode); + SetDocumented(table, "gold", "", "Adjust player gold.", LuaDevPlayerGoldModule(lua)); + SetDocumented(table, "info", "(id: number = 0)", "Show player info.", &DebugCmdPlayerInfo); + SetDocumented(table, "spells", "", "Adjust player spells.", LuaDevPlayerSpellsModule(lua)); + SetDocumented(table, "stats", "", "Adjust player stats (Strength, HP, etc).", LuaDevPlayerStatsModule(lua)); + SetDocumented(table, "trn", "", "Set player TRN to '${name}.trn'", LuaDevPlayerTrnModule(lua)); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/player.hpp b/Source/lua/modules/dev/player.hpp new file mode 100644 index 00000000000..e5c7d7522cd --- /dev/null +++ b/Source/lua/modules/dev/player.hpp @@ -0,0 +1,11 @@ +#pragma once +#ifdef _DEBUG + +#include + +namespace devilution { + +sol::table LuaDevPlayerModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/player/gold.cpp b/Source/lua/modules/dev/player/gold.cpp new file mode 100644 index 00000000000..679f2a0479b --- /dev/null +++ b/Source/lua/modules/dev/player/gold.cpp @@ -0,0 +1,101 @@ +#ifdef _DEBUG +#include "lua/modules/dev/player/gold.hpp" + +#include +#include +#include + +#include + +#include "items.h" +#include "lua/metadoc.hpp" +#include "player.h" + +namespace devilution { +namespace { + +std::string DebugCmdGiveGoldCheat(std::optional amount) +{ + int goldToAdd = amount.value_or(GOLD_MAX_LIMIT * InventoryGridCells); + if (goldToAdd <= 0) return "amount must be positive"; + Player &myPlayer = *MyPlayer; + const int goldAmountBefore = myPlayer._pGold; + for (int8_t &itemIndex : myPlayer.InvGrid) { + if (itemIndex < 0) + continue; + + Item &item = myPlayer.InvList[itemIndex != 0 ? itemIndex - 1 : myPlayer._pNumInv]; + + if (itemIndex != 0) { + if ((!item.isGold() && !item.isEmpty()) || (item.isGold() && item._ivalue == GOLD_MAX_LIMIT)) + continue; + } else { + if (item.isEmpty()) { + MakeGoldStack(item, 0); + myPlayer._pNumInv++; + itemIndex = myPlayer._pNumInv; + } + } + + int goldThatCanBeAdded = (GOLD_MAX_LIMIT - item._ivalue); + if (goldThatCanBeAdded >= goldToAdd) { + item._ivalue += goldToAdd; + myPlayer._pGold += goldToAdd; + break; + } + + item._ivalue += goldThatCanBeAdded; + goldToAdd -= goldThatCanBeAdded; + myPlayer._pGold += goldThatCanBeAdded; + } + + CalcPlrInv(myPlayer, true); + + return StrCat("Set your gold to ", myPlayer._pGold, ", added ", myPlayer._pGold - goldAmountBefore, "."); +} + +std::string DebugCmdTakeGoldCheat(std::optional amount) +{ + Player &myPlayer = *MyPlayer; + int goldToRemove = amount.value_or(GOLD_MAX_LIMIT * InventoryGridCells); + if (goldToRemove <= 0) return "amount must be positive"; + + const int goldAmountBefore = myPlayer._pGold; + for (auto itemIndex : myPlayer.InvGrid) { + itemIndex -= 1; + + if (itemIndex < 0) + continue; + + Item &item = myPlayer.InvList[itemIndex]; + if (!item.isGold()) + continue; + + if (item._ivalue >= goldToRemove) { + myPlayer._pGold -= goldToRemove; + item._ivalue -= goldToRemove; + if (item._ivalue == 0) + myPlayer.RemoveInvItem(itemIndex); + break; + } + + myPlayer._pGold -= item._ivalue; + goldToRemove -= item._ivalue; + myPlayer.RemoveInvItem(itemIndex); + } + + return StrCat("Set your gold to ", myPlayer._pGold, ", removed ", goldAmountBefore - myPlayer._pGold, "."); +} + +} // namespace + +sol::table LuaDevPlayerGoldModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "give", "(amount: number = MAX)", "Gives the player gold.", &DebugCmdGiveGoldCheat); + SetDocumented(table, "take", "(amount: number = MAX)", "Takes the player's gold away.", &DebugCmdTakeGoldCheat); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/player/gold.hpp b/Source/lua/modules/dev/player/gold.hpp new file mode 100644 index 00000000000..2bff3241836 --- /dev/null +++ b/Source/lua/modules/dev/player/gold.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevPlayerGoldModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/player/spells.cpp b/Source/lua/modules/dev/player/spells.cpp new file mode 100644 index 00000000000..c46700a888b --- /dev/null +++ b/Source/lua/modules/dev/player/spells.cpp @@ -0,0 +1,39 @@ +#ifdef _DEBUG +#include "lua/modules/dev/player/spells.hpp" + +#include +#include + +#include + +#include "lua/metadoc.hpp" +#include "msg.h" +#include "spelldat.h" +#include "spells.h" +#include "utils/str_cat.hpp" + +namespace devilution { +namespace { +std::string DebugCmdSetSpellsLevel(uint8_t level) +{ + for (uint8_t i = static_cast(SpellID::Firebolt); i < MAX_SPELLS; i++) { + if (GetSpellBookLevel(static_cast(i)) != -1) { + NetSendCmdParam2(true, CMD_CHANGE_SPELL_LEVEL, i, level); + } + } + if (level == 0) + MyPlayer->_pMemSpells = 0; + + return StrCat("Set all spell levels to ", level); +} +} // namespace + +sol::table LuaDevPlayerSpellsModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "setLevel", "(level: number)", "Set spell level for all spells.", &DebugCmdSetSpellsLevel); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/player/spells.hpp b/Source/lua/modules/dev/player/spells.hpp new file mode 100644 index 00000000000..2f1fd1777e5 --- /dev/null +++ b/Source/lua/modules/dev/player/spells.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevPlayerSpellsModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/player/stats.cpp b/Source/lua/modules/dev/player/stats.cpp new file mode 100644 index 00000000000..db50a9395b1 --- /dev/null +++ b/Source/lua/modules/dev/player/stats.cpp @@ -0,0 +1,100 @@ +#ifdef _DEBUG +#include "lua/modules/dev/player/stats.hpp" + +#include + +#include + +#include "engine/backbuffer_state.hpp" +#include "lua/metadoc.hpp" +#include "player.h" +#include "utils/str_cat.hpp" + +namespace devilution { + +namespace { + +std::string DebugCmdLevelUp(std::optional levels) +{ + if (!levels.has_value()) *levels = 1; + if (*levels <= 0) return "amount must be positive"; + Player &myPlayer = *MyPlayer; + for (int i = 0; i < *levels; i++) + NetSendCmd(true, CMD_CHEAT_EXPERIENCE); + return StrCat("New character level: ", myPlayer.getCharacterLevel() + *levels); +} + +std::string DebugCmdMaxStats() +{ + Player &myPlayer = *MyPlayer; + ModifyPlrStr(myPlayer, myPlayer.GetMaximumAttributeValue(CharacterAttribute::Strength) - myPlayer._pBaseStr); + ModifyPlrMag(myPlayer, myPlayer.GetMaximumAttributeValue(CharacterAttribute::Magic) - myPlayer._pBaseMag); + ModifyPlrDex(myPlayer, myPlayer.GetMaximumAttributeValue(CharacterAttribute::Dexterity) - myPlayer._pBaseDex); + ModifyPlrVit(myPlayer, myPlayer.GetMaximumAttributeValue(CharacterAttribute::Vitality) - myPlayer._pBaseVit); + return "Set all character base attributes to maximum."; +} + +std::string DebugCmdMinStats() +{ + Player &myPlayer = *MyPlayer; + ModifyPlrStr(myPlayer, -myPlayer._pBaseStr); + ModifyPlrMag(myPlayer, -myPlayer._pBaseMag); + ModifyPlrDex(myPlayer, -myPlayer._pBaseDex); + ModifyPlrVit(myPlayer, -myPlayer._pBaseVit); + return "Set all character base attributes to minimum."; +} + +std::string DebugCmdRefillHealthMana() +{ + Player &myPlayer = *MyPlayer; + myPlayer.RestoreFullLife(); + myPlayer.RestoreFullMana(); + RedrawComponent(PanelDrawComponent::Health); + RedrawComponent(PanelDrawComponent::Mana); + return StrCat("Restored life and mana to full."); +} + +std::string DebugCmdChangeHealth(int change) +{ + Player &myPlayer = *MyPlayer; + if (change == 0) + return StrCat("Enter a value not equal to 0 to change life!"); + + int newHealth = myPlayer._pHitPoints + (change * 64); + SetPlayerHitPoints(myPlayer, newHealth); + if (newHealth <= 0) + SyncPlrKill(myPlayer, DeathReason::MonsterOrTrap); + + return StrCat("Changed life by ", change); +} + +std::string DebugCmdChangeMana(int change) +{ + Player &myPlayer = *MyPlayer; + if (change == 0) + return StrCat("Enter a value not equal to 0 to change mana!"); + + int newMana = myPlayer._pMana + (change * 64); + myPlayer._pMana = newMana; + myPlayer._pManaBase = myPlayer._pMana + myPlayer._pMaxManaBase - myPlayer._pMaxMana; + RedrawComponent(PanelDrawComponent::Mana); + + return StrCat("Changed mana by ", change); +} + +} // namespace + +sol::table LuaDevPlayerStatsModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "adjustHealth", "(amount: number)", "Adjust HP (amount can be negative)", &DebugCmdChangeHealth); + SetDocumented(table, "adjustMana", "(amount: number)", "Adjust mana (amount can be negative)", &DebugCmdChangeMana); + SetDocumented(table, "levelUp", "(amount: number = 1)", "Level the player up.", &DebugCmdLevelUp); + SetDocumented(table, "rejuvenate", "()", "Refill health", &DebugCmdRefillHealthMana); + SetDocumented(table, "setAttrToMax", "()", "Set Str, Mag, Dex, and Vit to maximum.", &DebugCmdMaxStats); + SetDocumented(table, "setAttrToMin", "()", "Set Str, Mag, Dex, and Vit to minimum.", &DebugCmdMinStats); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/player/stats.hpp b/Source/lua/modules/dev/player/stats.hpp new file mode 100644 index 00000000000..8ae5f45ab1c --- /dev/null +++ b/Source/lua/modules/dev/player/stats.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevPlayerStatsModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/quests.cpp b/Source/lua/modules/dev/quests.cpp new file mode 100644 index 00000000000..a3329e82d86 --- /dev/null +++ b/Source/lua/modules/dev/quests.cpp @@ -0,0 +1,73 @@ +#ifdef _DEBUG +#include "lua/modules/dev/quests.hpp" + +#include +#include + +#include + +#include "engine.h" +#include "lua/metadoc.hpp" +#include "quests.h" +#include "utils/str_cat.hpp" + +namespace devilution { +namespace { + +std::string DebugCmdEnableQuest(uint8_t questId) +{ + if (questId >= MAXQUESTS) return StrCat("Quest ", questId, " does not exist!"); + Quest &quest = Quests[questId]; + + if (IsNoneOf(quest._qactive, QUEST_NOTAVAIL, QUEST_INIT)) + return StrCat(QuestsData[questId]._qlstr, " is already active!"); + + quest._qactive = QUEST_ACTIVE; + quest._qlog = true; + + return StrCat(QuestsData[questId]._qlstr, " activated."); +} + +std::string DebugCmdEnableQuests() +{ + for (Quest &quest : Quests) { + if (IsNoneOf(quest._qactive, QUEST_NOTAVAIL, QUEST_INIT)) continue; + quest._qactive = QUEST_ACTIVE; + quest._qlog = true; + } + return "Activated all quests."; +} + +std::string DebugCmdQuestInfo(const uint8_t questId) +{ + if (questId >= MAXQUESTS) return StrCat("Quest ", questId, " does not exist!"); + const Quest &quest = Quests[questId]; + return StrCat("Quest id=", quest._qidx, " ", QuestsData[quest._qidx]._qlstr, + " active=", quest._qactive, " var1=", quest._qvar1, " var2=", quest._qvar2); +} + +std::string DebugCmdQuestsInfo() +{ + std::string ret; + for (const Quest &quest : Quests) { + StrAppend(ret, "Quest id=", quest._qidx, " ", QuestsData[quest._qidx]._qlstr, + " active=", quest._qactive, " var1=", quest._qvar1, " var2=", quest._qvar2, "\n"); + } + if (!ret.empty()) ret.pop_back(); + return ret; +} + +} // namespace + +sol::table LuaDevQuestsModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "activate", "(id: number)", "Activate the given quest.", &DebugCmdEnableQuest); + SetDocumented(table, "activateAll", "()", "Activate all available quests.", &DebugCmdEnableQuests); + SetDocumented(table, "all", "()", "Information on all available quest.", &DebugCmdQuestsInfo); + SetDocumented(table, "info", "(id: number)", "Information on the given quest.", &DebugCmdQuestInfo); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/quests.hpp b/Source/lua/modules/dev/quests.hpp new file mode 100644 index 00000000000..5edb641b5d9 --- /dev/null +++ b/Source/lua/modules/dev/quests.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevQuestsModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/search.cpp b/Source/lua/modules/dev/search.cpp new file mode 100644 index 00000000000..5718af21500 --- /dev/null +++ b/Source/lua/modules/dev/search.cpp @@ -0,0 +1,56 @@ +#ifdef _DEBUG +#include "lua/modules/dev/quests.hpp" + +#include + +#include + +#include "debug.h" +#include "lua/metadoc.hpp" +#include "utils/str_case.hpp" +#include "utils/str_cat.hpp" + +namespace devilution { +namespace { + +std::string DebugCmdSearchMonster(std::string_view name) +{ + if (name.empty()) return "Missing monster name!"; + AddDebugAutomapMonsterHighlight(AsciiStrToLower(name)); + return StrCat("Added automap marker for monster ", name, "."); +} + +std::string DebugCmdSearchItem(std::string_view name) +{ + if (name.empty()) return "Missing item name!"; + AddDebugAutomapItemHighlight(AsciiStrToLower(name)); + return StrCat("Added automap marker for item ", name, "."); +} + +std::string DebugCmdSearchObject(std::string_view name) +{ + if (name.empty()) return "Missing object name!"; + AddDebugAutomapObjectHighlight(AsciiStrToLower(name)); + return StrCat("Added automap marker for object ", name, "."); +} + +std::string DebugCmdClearSearch() +{ + ClearDebugAutomapHighlights(); + return "Removed all automap search markers."; +} + +} // namespace + +sol::table LuaDevSearchModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "clear", "()", "Clear search results from the map.", &DebugCmdClearSearch); + SetDocumented(table, "item", "(name: string)", "Search the map for an item.", &DebugCmdSearchItem); + SetDocumented(table, "monster", "(name: string)", "Search the map for a monster.", &DebugCmdSearchMonster); + SetDocumented(table, "object", "(name: string)", "Search the map for an object.", &DebugCmdSearchObject); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/search.hpp b/Source/lua/modules/dev/search.hpp new file mode 100644 index 00000000000..446ae075aa9 --- /dev/null +++ b/Source/lua/modules/dev/search.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevSearchModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/towners.cpp b/Source/lua/modules/dev/towners.cpp new file mode 100644 index 00000000000..87610258b73 --- /dev/null +++ b/Source/lua/modules/dev/towners.cpp @@ -0,0 +1,93 @@ +#ifdef _DEBUG +#include "lua/modules/dev/towners.hpp" + +#include + +#include + +#include "lua/metadoc.hpp" +#include "player.h" +#include "spells.h" +#include "towners.h" +#include "utils/str_cat.hpp" + +namespace devilution { +namespace { + +std::unordered_map TownerShortNameToTownerId = { + { "griswold", _talker_id::TOWN_SMITH }, + { "smith", _talker_id::TOWN_SMITH }, + { "pepin", _talker_id::TOWN_HEALER }, + { "healer", _talker_id::TOWN_HEALER }, + { "ogden", _talker_id::TOWN_TAVERN }, + { "tavern", _talker_id::TOWN_TAVERN }, + { "cain", _talker_id::TOWN_STORY }, + { "story", _talker_id::TOWN_STORY }, + { "farnham", _talker_id::TOWN_DRUNK }, + { "drunk", _talker_id::TOWN_DRUNK }, + { "adria", _talker_id::TOWN_WITCH }, + { "witch", _talker_id::TOWN_WITCH }, + { "gillian", _talker_id::TOWN_BMAID }, + { "bmaid", _talker_id::TOWN_BMAID }, + { "wirt", _talker_id ::TOWN_PEGBOY }, + { "pegboy", _talker_id ::TOWN_PEGBOY }, + { "lester", _talker_id ::TOWN_FARMER }, + { "farmer", _talker_id ::TOWN_FARMER }, + { "girl", _talker_id ::TOWN_GIRL }, + { "nut", _talker_id::TOWN_COWFARM }, + { "cowfarm", _talker_id::TOWN_COWFARM }, +}; + +std::string DebugCmdVisitTowner(std::string_view name) +{ + Player &myPlayer = *MyPlayer; + + if (setlevel || !myPlayer.isOnLevel(0)) + return StrCat("This command is only available in Town!"); + + if (name.empty()) { + std::string ret; + ret = StrCat("Please provide the name of a Towner: "); + for (const auto &[name, _] : TownerShortNameToTownerId) { + ret += ' '; + ret.append(name); + } + return ret; + } + + auto it = TownerShortNameToTownerId.find(name); + if (it == TownerShortNameToTownerId.end()) + return StrCat(name, " is invalid!"); + + for (const Towner &towner : Towners) { + if (towner._ttype != it->second) continue; + CastSpell( + *MyPlayer, + SpellID::Teleport, + myPlayer.position.tile, + towner.position, + /*spllvl=*/1); + return StrCat("Moved you to ", name, "."); + } + + return StrCat("Unable to locate ", name, "!"); +} + +std::string DebugCmdTalkToTowner(std::string_view name) +{ + if (!DebugTalkToTowner(name)) return StrCat("Towner not found!"); + return StrCat("Opened ", name, " talk window."); +} + +} // namespace + +sol::table LuaDevTownersModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + SetDocumented(table, "talk", "(name: string)", "Talk to towner.", &DebugCmdTalkToTowner); + SetDocumented(table, "visit", "(name: string)", "Teleport to towner.", &DebugCmdVisitTowner); + return table; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/dev/towners.hpp b/Source/lua/modules/dev/towners.hpp new file mode 100644 index 00000000000..6a5c7409a0b --- /dev/null +++ b/Source/lua/modules/dev/towners.hpp @@ -0,0 +1,10 @@ +#pragma once +#ifdef _DEBUG +#include + +namespace devilution { + +sol::table LuaDevTownersModule(sol::state_view &lua); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/modules/log.cpp b/Source/lua/modules/log.cpp new file mode 100644 index 00000000000..414db639ebe --- /dev/null +++ b/Source/lua/modules/log.cpp @@ -0,0 +1,93 @@ +#include "lua/modules/log.hpp" + +#include + +#include +#include +#include +#include + +#include "utils/log.hpp" + +#ifdef USE_SDL1 +#include "utils/sdl2_to_1_2_backports.h" +#endif + +namespace devilution { + +namespace { + +void LuaLogMessage(LogPriority priority, std::string_view fmt, sol::variadic_args args) +{ + std::string formatted; + FMT_TRY + { + fmt::dynamic_format_arg_store store; + for (const sol::stack_proxy arg : args) { + switch (arg.get_type()) { + case sol::type::boolean: + store.push_back(arg.as()); + break; + case sol::type::number: + if (lua_isinteger(arg.lua_state(), arg.stack_index())) { + store.push_back(lua_tointeger(arg.lua_state(), arg.stack_index())); + } else { + store.push_back(lua_tonumber(arg.lua_state(), arg.stack_index())); + } + break; + case sol::type::string: + store.push_back(arg.as()); + break; + default: + store.push_back(sol::utility::to_string(sol::stack_object(arg))); + break; + } + } + formatted = fmt::vformat(fmt, store); + } + FMT_CATCH(const fmt::format_error &e) + { +#if FMT_EXCEPTIONS + // e.what() is undefined if exceptions are disabled, so we wrap the whole block + // with an `FMT_EXCEPTIONS` check. + std::string error = StrCat("Format error, fmt: ", fmt, " error: ", e.what()); + SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "%s", error.c_str()); + return; +#endif + } + SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, static_cast(priority), "%s", formatted.c_str()); +} + +void LuaLogInfo(std::string_view fmt, sol::variadic_args args) +{ + LuaLogMessage(LogPriority::Info, fmt, std::move(args)); +} +void LuaLogVerbose(std::string_view fmt, sol::variadic_args args) +{ + LuaLogMessage(LogPriority::Verbose, fmt, std::move(args)); +} +void LuaLogDebug(std::string_view fmt, sol::variadic_args args) +{ + LuaLogMessage(LogPriority::Debug, fmt, std::move(args)); +} +void LuaLogWarn(std::string_view fmt, sol::variadic_args args) +{ + LuaLogMessage(LogPriority::Warn, fmt, std::move(args)); +} +void LuaLogError(std::string_view fmt, sol::variadic_args args) +{ + LuaLogMessage(LogPriority::Error, fmt, std::move(args)); +} +} // namespace + +sol::table LuaLogModule(sol::state_view &lua) +{ + return lua.create_table_with( + "info", LuaLogInfo, + "verbose", LuaLogVerbose, + "debug", LuaLogDebug, + "warn", LuaLogWarn, + "error", LuaLogError); +} + +} // namespace devilution diff --git a/Source/lua/modules/log.hpp b/Source/lua/modules/log.hpp new file mode 100644 index 00000000000..5791e0ddcc3 --- /dev/null +++ b/Source/lua/modules/log.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace devilution { + +sol::table LuaLogModule(sol::state_view &lua); + +} // namespace devilution diff --git a/Source/lua/modules/render.cpp b/Source/lua/modules/render.cpp new file mode 100644 index 00000000000..ebe375357a0 --- /dev/null +++ b/Source/lua/modules/render.cpp @@ -0,0 +1,16 @@ +#include "lua/modules/render.hpp" + +#include + +#include "engine/dx.h" +#include "engine/render/text_render.hpp" + +namespace devilution { + +sol::table LuaRenderModule(sol::state_view &lua) +{ + return lua.create_table_with( + "string", [](std::string_view text, int x, int y) { DrawString(GlobalBackBuffer(), text, { x, y }); }); +} + +} // namespace devilution diff --git a/Source/lua/modules/render.hpp b/Source/lua/modules/render.hpp new file mode 100644 index 00000000000..23a323a6723 --- /dev/null +++ b/Source/lua/modules/render.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace devilution { + +sol::table LuaRenderModule(sol::state_view &lua); + +} // namespace devilution diff --git a/Source/lua/repl.cpp b/Source/lua/repl.cpp new file mode 100644 index 00000000000..3d60a8198a3 --- /dev/null +++ b/Source/lua/repl.cpp @@ -0,0 +1,111 @@ +#ifdef _DEBUG +#include "lua/repl.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +#include "lua/lua.hpp" +#include "panels/console.hpp" +#include "utils/str_cat.hpp" + +namespace devilution { + +namespace { + +std::optional replEnv; + +void LuaConsoleWarn(void *userData, const char *message, int continued) +{ + static std::string warnBuffer; + warnBuffer.append(message); + if (continued != 0) + return; + PrintWarningToConsole(warnBuffer); + warnBuffer.clear(); +} + +int LuaPrintToConsole(lua_State *state) +{ + std::string result; + const int n = lua_gettop(state); + for (int i = 1; i <= n; i++) { + size_t l; + const char *s = luaL_tolstring(state, i, &l); + if (i > 1) + result += '\t'; + result.append(s, l); + lua_pop(state, 1); + } + PrintToConsole(result); + return 0; +} + +void CreateReplEnvironment() +{ + sol::environment env = CreateLuaSandbox(); + env["print"] = LuaPrintToConsole; + lua_setwarnf(env.lua_state(), LuaConsoleWarn, /*ud=*/nullptr); + replEnv.emplace(env); +} + +sol::protected_function_result TryRunLuaAsExpressionThenStatement(std::string_view code) +{ + // Try to compile as an expression first. This also how the `lua` repl is implemented. + sol::state &lua = GetLuaState(); + std::string expression = StrCat("return ", code, ";"); + sol::detail::typical_chunk_name_t basechunkname = {}; + sol::load_status status = static_cast( + luaL_loadbufferx(lua.lua_state(), expression.data(), expression.size(), + sol::detail::make_chunk_name(expression, sol::detail::default_chunk_name(), basechunkname), "text")); + if (status != sol::load_status::ok) { + // Try as a statement: + status = static_cast( + luaL_loadbufferx(lua.lua_state(), code.data(), code.size(), + sol::detail::make_chunk_name(code, sol::detail::default_chunk_name(), basechunkname), "text")); + if (status != sol::load_status::ok) { + return sol::protected_function_result( + lua.lua_state(), sol::absolute_index(lua.lua_state(), -1), 0, 1, static_cast(status)); + } + } + sol::stack_aligned_protected_function fn(lua.lua_state(), -1); + sol::set_environment(GetLuaReplEnvironment(), fn); + return fn(); +} + +} // namespace + +sol::environment &GetLuaReplEnvironment() +{ + if (!replEnv.has_value()) + CreateReplEnvironment(); + return *replEnv; +} + +tl::expected RunLuaReplLine(std::string_view code) +{ + const sol::protected_function_result result = TryRunLuaAsExpressionThenStatement(code); + if (!result.valid()) { + if (result.get_type() == sol::type::string) { + return tl::make_unexpected(result.get()); + } + return tl::make_unexpected("Unknown Lua error"); + } + if (result.get_type() == sol::type::none) { + return std::string {}; + } + return sol::utility::to_string(sol::stack_object(result)); +} + +void LuaReplShutdown() +{ + replEnv = std::nullopt; +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/lua/repl.hpp b/Source/lua/repl.hpp new file mode 100644 index 00000000000..8f6ba12048c --- /dev/null +++ b/Source/lua/repl.hpp @@ -0,0 +1,19 @@ +#pragma once +#ifdef _DEBUG + +#include +#include + +#include +#include + +namespace devilution { + +tl::expected RunLuaReplLine(std::string_view code); + +sol::environment &GetLuaReplEnvironment(); + +void LuaReplShutdown(); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/menu.cpp b/Source/menu.cpp index ae3a02405c2..7979e860cd9 100644 --- a/Source/menu.cpp +++ b/Source/menu.cpp @@ -124,7 +124,6 @@ bool mainmenu_select_hero_dialog(GameData *gameData) &gSaveNumber); } if (dlgresult == SELHERO_PREVIOUS) { - SErrSetLastError(1223); return false; } diff --git a/Source/minitext.cpp b/Source/minitext.cpp index fda565d2077..43762666795 100644 --- a/Source/minitext.cpp +++ b/Source/minitext.cpp @@ -4,7 +4,9 @@ * Implementation of scrolling dialog text. */ #include +#include #include +#include #include #include "DiabloUI/ui_flags.hpp" @@ -18,8 +20,7 @@ #include "playerdat.hpp" #include "textdat.h" #include "utils/language.h" -#include "utils/stdcompat/optional.hpp" -#include "utils/stdcompat/string_view.hpp" +#include "utils/timer.hpp" namespace devilution { @@ -30,7 +31,7 @@ namespace { /** Vertical speed of the scrolling text in ms/px */ int qtextSpd; /** Start time of scrolling */ -Uint32 ScrollStart; +uint32_t ScrollStart; /** Graphics for the window border */ OptionalOwnedClxSpriteList pTextBoxCels; @@ -39,7 +40,7 @@ const int LineHeight = 38; std::vector TextLines; -void LoadText(string_view text) +void LoadText(std::string_view text) { TextLines.clear(); @@ -60,15 +61,15 @@ void LoadText(string_view text) * @param nSFX The index of the sound in the sgSFX table * @return ms/px */ -uint32_t CalculateTextSpeed(int nSFX) +uint32_t CalculateTextSpeed(SfxID nSFX) { - const int numLines = TextLines.size(); + const auto numLines = static_cast(TextLines.size()); #ifndef NOSOUND - Uint32 sfxFrames = GetSFXLength(nSFX); + uint32_t sfxFrames = GetSFXLength(nSFX); #else // Sound is disabled -- estimate length from the number of lines. - Uint32 sfxFrames = numLines * 3000; + uint32_t sfxFrames = numLines * 3000; #endif assert(sfxFrames != 0); @@ -81,11 +82,11 @@ uint32_t CalculateTextSpeed(int nSFX) int CalculateTextPosition() { - uint32_t currTime = SDL_GetTicks(); + const uint32_t currTime = GetMillisecondsSinceStartup(); - int y = (currTime - ScrollStart) / qtextSpd - 260; + const int y = (currTime - ScrollStart) / qtextSpd - 260; - int textHeight = LineHeight * TextLines.size(); + const auto textHeight = static_cast(LineHeight * TextLines.size()); if (y >= textHeight) qtextflag = false; @@ -115,7 +116,8 @@ void DrawQTextContent(const Surface &out) continue; } - DrawString(out, line, { { sx, sy + i * LineHeight }, { 543, LineHeight } }, UiFlags::FontSize30 | UiFlags::ColorGold); + DrawString(out, line, { { sx, sy + i * LineHeight }, { 543, LineHeight } }, + { .flags = UiFlags::FontSize30 | UiFlags::ColorGold }); } } @@ -133,28 +135,28 @@ void InitQuestText() void InitQTextMsg(_speech_id m) { - _sfx_id sfxnr = Speeches[m].sfxnr; - const _sfx_id *classSounds = herosounds[static_cast(MyPlayer->_pClass)]; + SfxID sfxnr = Speeches[m].sfxnr; + const SfxID *classSounds = herosounds[static_cast(MyPlayer->_pClass)]; switch (sfxnr) { - case PS_WARR1: + case SfxID::Warrior1: sfxnr = classSounds[static_cast(HeroSpeech::ChamberOfBoneLore)]; break; - case PS_WARR10: + case SfxID::Warrior10: sfxnr = classSounds[static_cast(HeroSpeech::ValorLore)]; break; - case PS_WARR11: + case SfxID::Warrior11: sfxnr = classSounds[static_cast(HeroSpeech::HallsOfTheBlindLore)]; break; - case PS_WARR12: + case SfxID::Warrior12: sfxnr = classSounds[static_cast(HeroSpeech::WarlordOfBloodLore)]; break; - case PS_WARR54: + case SfxID::Warrior54: sfxnr = classSounds[static_cast(HeroSpeech::InSpirituSanctum)]; break; - case PS_WARR55: + case SfxID::Warrior55: sfxnr = classSounds[static_cast(HeroSpeech::PraedictumOtium)]; break; - case PS_WARR56: + case SfxID::Warrior56: sfxnr = classSounds[static_cast(HeroSpeech::EfficioObitusUtInimicus)]; break; default: @@ -165,7 +167,7 @@ void InitQTextMsg(_speech_id m) LoadText(_(Speeches[m].txtstr)); qtextflag = true; qtextSpd = CalculateTextSpeed(sfxnr); - ScrollStart = SDL_GetTicks(); + ScrollStart = GetMillisecondsSinceStartup(); } PlaySFX(sfxnr); } diff --git a/Source/misdat.cpp b/Source/misdat.cpp index ef92bcd8377..8fa823a9fe6 100644 --- a/Source/misdat.cpp +++ b/Source/misdat.cpp @@ -5,15 +5,26 @@ */ #include "misdat.h" +#include #include +#include -#include "engine/load_cl2.hpp" -#include "engine/load_clx.hpp" +#include + +#include "data/file.hpp" +#include "data/iterators.hpp" +#include "data/record_reader.hpp" #include "missiles.h" #include "mpq/mpq_common.hpp" #include "utils/file_name_generator.hpp" #include "utils/str_cat.hpp" +#ifdef UNPACKED_MPQS +#include "engine/load_clx.hpp" +#else +#include "engine/load_cl2.hpp" +#endif + namespace devilution { namespace { @@ -29,247 +40,177 @@ constexpr auto Invisible = MissileDataFlags::Invisible; /** Data related to each missile ID. */ const MissileData MissilesData[] = { // clang-format off -// id mAddProc, mProc, mlSFX, miSFX, mFileNum, flags, MovementDistribution; -/*Arrow*/ { &AddArrow, &ProcessArrow, SFX_NONE, SFX_NONE, MissileGraphicID::Arrow, Physical | Arrow, MissileMovementDistribution::Blockable }, -/*Firebolt*/ { &AddFirebolt, &ProcessGenericProjectile, LS_FBOLT1, LS_FIRIMP2, MissileGraphicID::Fireball, Fire, MissileMovementDistribution::Blockable }, -/*Guardian*/ { &AddGuardian, &ProcessGuardian, LS_GUARD, LS_GUARDLAN, MissileGraphicID::Guardian, Physical, MissileMovementDistribution::Disabled }, -/*Phasing*/ { &AddPhasing, &ProcessTeleport, LS_TELEPORT, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*NovaBall*/ { &AddNovaBall, &ProcessNovaBall, SFX_NONE, SFX_NONE, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Unblockable }, -/*FireWall*/ { &AddFireWall, &ProcessFireWall, LS_WALLLOOP, LS_FIRIMP2, MissileGraphicID::FireWall, Fire, MissileMovementDistribution::Disabled }, -/*Fireball*/ { &AddFireball, &ProcessFireball, LS_FBOLT1, LS_FIRIMP2, MissileGraphicID::Fireball, Fire, MissileMovementDistribution::Blockable }, -/*LightningControl*/ { &AddLightningControl, &ProcessLightningControl, SFX_NONE, SFX_NONE, MissileGraphicID::Lightning, Lightning | Invisible, MissileMovementDistribution::Disabled }, -/*Lightning*/ { &AddLightning, &ProcessLightning, LS_LNING1, LS_ELECIMP1, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Disabled }, -/*MagmaBallExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, SFX_NONE, SFX_NONE, MissileGraphicID::MagmaBallExplosion, Physical, MissileMovementDistribution::Disabled }, -/*TownPortal*/ { &AddTownPortal, &ProcessTownPortal, LS_SENTINEL, LS_ELEMENTL, MissileGraphicID::TownPortal, Magic, MissileMovementDistribution::Disabled }, -/*FlashBottom*/ { &AddFlashBottom, &ProcessFlashBottom, LS_NOVA, LS_ELECIMP1, MissileGraphicID::FlashBottom, Magic, MissileMovementDistribution::Disabled }, -/*FlashTop*/ { &AddFlashTop, &ProcessFlashTop, SFX_NONE, SFX_NONE, MissileGraphicID::FlashTop, Magic, MissileMovementDistribution::Disabled }, -/*ManaShield*/ { &AddManaShield, nullptr, LS_MSHIELD, SFX_NONE, MissileGraphicID::ManaShield, Magic | Invisible, MissileMovementDistribution::Disabled }, -/*FlameWave*/ { &AddFlameWave, &ProcessFlameWave, SFX_NONE, SFX_NONE, MissileGraphicID::FireWall, Fire, MissileMovementDistribution::Unblockable }, -/*ChainLightning*/ { &AddChainLightning, &ProcessChainLightning, LS_LNING1, LS_ELECIMP1, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Disabled }, -/*ChainBall*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Disabled }, -/*BloodHit*/ { nullptr, nullptr, LS_BLODSTAR, LS_BLSIMPT, MissileGraphicID::BloodHit, Physical, MissileMovementDistribution::Disabled }, -/*BoneHit*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::BoneHit, Physical, MissileMovementDistribution::Disabled }, -/*MetalHit*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::MetalHit, Physical, MissileMovementDistribution::Disabled }, -/*Rhino*/ { &AddRhino, &ProcessRhino, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical, MissileMovementDistribution::Blockable }, -/*MagmaBall*/ { &AddMagmaBall, &ProcessGenericProjectile, SFX_NONE, SFX_NONE, MissileGraphicID::MagmaBall, Fire, MissileMovementDistribution::Blockable }, -/*ThinLightningControl*/ { &AddLightningControl, &ProcessLightningControl, SFX_NONE, SFX_NONE, MissileGraphicID::ThinLightning, Lightning | Invisible, MissileMovementDistribution::Disabled }, -/*ThinLightning*/ { &AddLightning, &ProcessLightning, SFX_NONE, SFX_NONE, MissileGraphicID::ThinLightning, Lightning, MissileMovementDistribution::Disabled }, -/*BloodStar*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SFX_NONE, SFX_NONE, MissileGraphicID::BloodStar, Magic, MissileMovementDistribution::Blockable }, -/*BloodStarExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, SFX_NONE, SFX_NONE, MissileGraphicID::BloodStarExplosion, Magic, MissileMovementDistribution::Disabled }, -/*Teleport*/ { &AddTeleport, &ProcessTeleport, LS_ELEMENTL, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*FireArrow*/ { &AddElementalArrow, &ProcessElementalArrow, SFX_NONE, SFX_NONE, MissileGraphicID::FireArrow, Fire | Arrow, MissileMovementDistribution::Blockable }, -/*DoomSerpents*/ { nullptr, nullptr, LS_DSERP, SFX_NONE, MissileGraphicID::DoomSerpents, Magic | Invisible, MissileMovementDistribution::Disabled }, -/*FireOnly*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::FireWall, Fire, MissileMovementDistribution::Disabled }, -/*StoneCurse*/ { &AddStoneCurse, &ProcessStoneCurse, LS_SCURIMP, SFX_NONE, MissileGraphicID::None, Magic | Invisible, MissileMovementDistribution::Disabled }, -/*BloodRitual*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical, MissileMovementDistribution::Disabled }, -/*Invisibility*/ { nullptr, nullptr, LS_INVISIBL, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*Golem*/ { &AddGolem, nullptr, LS_GOLUM, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*Etherealize*/ { nullptr, nullptr, LS_ETHEREAL, SFX_NONE, MissileGraphicID::Etherealize, Physical, MissileMovementDistribution::Disabled }, -/*Spurt*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::Spurt, Physical, MissileMovementDistribution::Disabled }, -/*ApocalypseBoom*/ { &AddApocalypseBoom, &ProcessApocalypseBoom, SFX_NONE, SFX_NONE, MissileGraphicID::ApocalypseBoom, Physical, MissileMovementDistribution::Disabled }, -/*Healing*/ { &AddHealing, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*FireWallControl*/ { &AddFireWallControl, &ProcessFireWallControl, SFX_NONE, SFX_NONE, MissileGraphicID::FireWall, Fire | Invisible, MissileMovementDistribution::Disabled }, -/*Infravision*/ { &AddInfravision, &ProcessInfravision, LS_INFRAVIS, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*Identify*/ { &AddIdentify, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*FlameWaveControl*/ { &AddFlameWaveControl, &ProcessFlameWaveControl, LS_FLAMWAVE, SFX_NONE, MissileGraphicID::FireWall, Fire, MissileMovementDistribution::Disabled }, -/*Nova*/ { &AddNova, &ProcessNova, LS_NOVA, SFX_NONE, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Disabled }, -/*Rage*/ { &AddRage, &ProcessRage, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*Apocalypse*/ { &AddApocalypse, &ProcessApocalypse, LS_APOC, SFX_NONE, MissileGraphicID::ApocalypseBoom, Magic, MissileMovementDistribution::Disabled }, -/*ItemRepair*/ { &AddItemRepair, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*StaffRecharge*/ { &AddStaffRecharge, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*TrapDisarm*/ { &AddTrapDisarm, nullptr, LS_TRAPDIS, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*Inferno*/ { &AddInferno, &ProcessInferno, LS_SPOUTSTR, SFX_NONE, MissileGraphicID::Inferno, Fire, MissileMovementDistribution::Disabled }, -/*InfernoControl*/ { &AddInfernoControl, &ProcessInfernoControl, SFX_NONE, SFX_NONE, MissileGraphicID::None, Fire | Invisible, MissileMovementDistribution::Disabled }, -/*FireMan*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical, MissileMovementDistribution::Blockable }, -/*Krull*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::Krull, Fire | Arrow, MissileMovementDistribution::Blockable }, -/*ChargedBolt*/ { &AddChargedBolt, &ProcessChargedBolt, LS_CBOLT, SFX_NONE, MissileGraphicID::ChargedBolt, Lightning, MissileMovementDistribution::Blockable }, -/*HolyBolt*/ { &AddHolyBolt, &ProcessHolyBolt, LS_HOLYBOLT, LS_ELECIMP1, MissileGraphicID::HolyBolt, Physical, MissileMovementDistribution::Blockable }, -/*Resurrect*/ { &AddResurrect, nullptr, SFX_NONE, LS_RESUR, MissileGraphicID::None, Magic | Invisible, MissileMovementDistribution::Disabled }, -/*Telekinesis*/ { &AddTelekinesis, nullptr, LS_ETHEREAL, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*LightningArrow*/ { &AddElementalArrow, &ProcessElementalArrow, SFX_NONE, SFX_NONE, MissileGraphicID::LightningArrow, Lightning | Arrow, MissileMovementDistribution::Blockable }, -/*Acid*/ { &AddAcid, &ProcessGenericProjectile, LS_ACID, SFX_NONE, MissileGraphicID::Acid, Acid, MissileMovementDistribution::Blockable }, -/*AcidSplat*/ { &AddMissileExplosion, &ProcessAcidSplate, SFX_NONE, SFX_NONE, MissileGraphicID::AcidSplat, Acid, MissileMovementDistribution::Disabled }, -/*AcidPuddle*/ { &AddAcidPuddle, &ProcessAcidPuddle, LS_PUDDLE, SFX_NONE, MissileGraphicID::AcidPuddle, Acid, MissileMovementDistribution::Disabled }, -/*HealOther*/ { &AddHealOther, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*Elemental*/ { &AddElemental, &ProcessElemental, LS_ELEMENTL, SFX_NONE, MissileGraphicID::Elemental, Fire, MissileMovementDistribution::Unblockable }, -/*ResurrectBeam*/ { &AddResurrectBeam, &ProcessResurrectBeam, SFX_NONE, SFX_NONE, MissileGraphicID::Resurrect, Physical, MissileMovementDistribution::Disabled }, -/*BoneSpirit*/ { &AddBoneSpirit, &ProcessBoneSpirit, LS_BONESP, LS_BSIMPCT, MissileGraphicID::BoneSpirit, Magic, MissileMovementDistribution::Blockable }, -/*WeaponExplosion*/ { &AddWeaponExplosion, &ProcessWeaponExplosion, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical, MissileMovementDistribution::Disabled }, -/*RedPortal*/ { &AddRedPortal, &ProcessRedPortal, LS_SENTINEL, LS_ELEMENTL, MissileGraphicID::RedPortal, Physical, MissileMovementDistribution::Disabled }, -/*DiabloApocalypseBoom*/ { &AddApocalypseBoom, &ProcessApocalypseBoom, SFX_NONE, SFX_NONE, MissileGraphicID::DiabloApocalypseBoom, Physical, MissileMovementDistribution::Disabled }, -/*DiabloApocalypse*/ { &AddDiabloApocalypse, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*Mana*/ { &AddMana, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*Magi*/ { &AddMagi, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*LightningWall*/ { &AddLightningWall, &ProcessLightningWall, LS_LMAG, LS_ELECIMP1, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Disabled }, -/*LightningWallControl*/ { &AddFireWallControl, &ProcessLightningWallControl, SFX_NONE, SFX_NONE, MissileGraphicID::Lightning, Lightning | Invisible, MissileMovementDistribution::Disabled }, -/*Immolation*/ { &AddNova, &ProcessImmolation, LS_FBOLT1, LS_FIRIMP2, MissileGraphicID::Fireball, Fire, MissileMovementDistribution::Disabled }, -/*SpectralArrow*/ { &AddSpectralArrow, &ProcessSpectralArrow, SFX_NONE, SFX_NONE, MissileGraphicID::Arrow, Physical | Arrow, MissileMovementDistribution::Disabled }, -/*FireballBow*/ { &AddImmolation, &ProcessFireball, IS_FBALLBOW, LS_FIRIMP2, MissileGraphicID::Fireball, Fire, MissileMovementDistribution::Blockable }, -/*LightningBow*/ { &AddLightningBow, &ProcessLightningBow, IS_FBALLBOW, SFX_NONE, MissileGraphicID::Lightning, Lightning | Invisible, MissileMovementDistribution::Disabled }, -/*ChargedBoltBow*/ { &AddChargedBoltBow, &ProcessChargedBolt, LS_CBOLT, SFX_NONE, MissileGraphicID::ChargedBolt, Lightning, MissileMovementDistribution::Blockable }, -/*HolyBoltBow*/ { &AddHolyBolt, &ProcessHolyBolt, LS_HOLYBOLT, LS_ELECIMP1, MissileGraphicID::HolyBolt, Physical, MissileMovementDistribution::Blockable }, -/*Warp*/ { &AddWarp, &ProcessTeleport, LS_ETHEREAL, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*Reflect*/ { &AddReflect, nullptr, LS_MSHIELD, SFX_NONE, MissileGraphicID::Reflect, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*Berserk*/ { &AddBerserk, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*RingOfFire*/ { &AddRingOfFire, &ProcessRingOfFire, SFX_NONE, SFX_NONE, MissileGraphicID::FireWall, Fire | Invisible, MissileMovementDistribution::Disabled }, -/*StealPotions*/ { &AddStealPotions, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*StealMana*/ { &AddStealMana, nullptr, IS_CAST7, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*RingOfLightning*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::Lightning, Lightning | Invisible, MissileMovementDistribution::Disabled }, -/*Search*/ { &AddSearch, &ProcessSearch, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*Aura*/ { nullptr, nullptr, SFX_NONE, LS_ELECIMP1, MissileGraphicID::FlashBottom, Magic | Invisible, MissileMovementDistribution::Disabled }, -/*Aura2*/ { nullptr, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::FlashTop, Magic | Invisible, MissileMovementDistribution::Disabled }, -/*SpiralFireball*/ { nullptr, nullptr, LS_FBOLT1, LS_FIRIMP2, MissileGraphicID::Fireball, Fire, MissileMovementDistribution::Disabled }, -/*RuneOfFire*/ { &AddRuneOfFire, &ProcessRune, SFX_NONE, SFX_NONE, MissileGraphicID::Rune, Physical, MissileMovementDistribution::Disabled }, -/*RuneOfLight*/ { &AddRuneOfLight, &ProcessRune, SFX_NONE, SFX_NONE, MissileGraphicID::Rune, Physical, MissileMovementDistribution::Disabled }, -/*RuneOfNova*/ { &AddRuneOfNova, &ProcessRune, SFX_NONE, SFX_NONE, MissileGraphicID::Rune, Physical, MissileMovementDistribution::Disabled }, -/*RuneOfImmolation*/ { &AddRuneOfImmolation, &ProcessRune, SFX_NONE, SFX_NONE, MissileGraphicID::Rune, Physical, MissileMovementDistribution::Disabled }, -/*RuneOfStone*/ { &AddRuneOfStone, &ProcessRune, SFX_NONE, SFX_NONE, MissileGraphicID::Rune, Physical, MissileMovementDistribution::Disabled }, -/*BigExplosion*/ { &AddBigExplosion, &ProcessBigExplosion, LS_NESTXPLD, LS_NESTXPLD, MissileGraphicID::BigExplosion, Fire, MissileMovementDistribution::Disabled }, -/*HorkSpawn*/ { &AddHorkSpawn, &ProcessHorkSpawn, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*Jester*/ { &AddJester, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*OpenNest*/ { &AddOpenNest, nullptr, SFX_NONE, SFX_NONE, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, -/*OrangeFlare*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SFX_NONE, SFX_NONE, MissileGraphicID::OrangeFlare, Magic, MissileMovementDistribution::Blockable }, -/*BlueFlare*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SFX_NONE, SFX_NONE, MissileGraphicID::BlueFlare2, Magic, MissileMovementDistribution::Blockable }, -/*RedFlare*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SFX_NONE, SFX_NONE, MissileGraphicID::RedFlare, Magic, MissileMovementDistribution::Blockable }, -/*YellowFlare*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SFX_NONE, SFX_NONE, MissileGraphicID::YellowFlare, Magic, MissileMovementDistribution::Blockable }, -/*BlueFlare2*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SFX_NONE, SFX_NONE, MissileGraphicID::BlueFlare2, Magic, MissileMovementDistribution::Blockable }, -/*YellowExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, LS_FIRIMP2, SFX_NONE, MissileGraphicID::YellowFlareExplosion, Physical, MissileMovementDistribution::Disabled }, -/*RedExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, LS_FIRIMP2, SFX_NONE, MissileGraphicID::RedFlareExplosion, Physical, MissileMovementDistribution::Disabled }, -/*BlueExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, LS_FIRIMP2, SFX_NONE, MissileGraphicID::BlueFlareExplosion, Physical, MissileMovementDistribution::Disabled }, -/*BlueExplosion2*/ { &AddMissileExplosion, &ProcessMissileExplosion, LS_FIRIMP2, SFX_NONE, MissileGraphicID::BlueFlareExplosion2, Physical, MissileMovementDistribution::Disabled }, -/*OrangeExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, LS_FIRIMP2, SFX_NONE, MissileGraphicID::OrangeFlareExplosion, Physical, MissileMovementDistribution::Disabled }, +// id mAddProc, mProc, mlSFX, miSFX, mFileNum, flags, MovementDistribution; +/*Arrow*/ { &AddArrow, &ProcessArrow, SfxID::None, SfxID::None, MissileGraphicID::Arrow, Physical | Arrow, MissileMovementDistribution::Blockable }, +/*Firebolt*/ { &AddFirebolt, &ProcessGenericProjectile, SfxID::SpellFirebolt, SfxID::SpellFireHit, MissileGraphicID::Fireball, Fire, MissileMovementDistribution::Blockable }, +/*Guardian*/ { &AddGuardian, &ProcessGuardian, SfxID::SpellGuardian, SfxID::None, MissileGraphicID::Guardian, Physical, MissileMovementDistribution::Disabled }, +/*Phasing*/ { &AddPhasing, &ProcessTeleport, SfxID::SpellTeleport, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*NovaBall*/ { &AddNovaBall, &ProcessNovaBall, SfxID::None, SfxID::None, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Unblockable }, +/*FireWall*/ { &AddFireWall, &ProcessFireWall, SfxID::SpellFireWall, SfxID::SpellFireHit, MissileGraphicID::FireWall, Fire, MissileMovementDistribution::Disabled }, +/*Fireball*/ { &AddFireball, &ProcessFireball, SfxID::SpellFirebolt, SfxID::SpellFireHit, MissileGraphicID::Fireball, Fire, MissileMovementDistribution::Blockable }, +/*LightningControl*/ { &AddLightningControl, &ProcessLightningControl, SfxID::None, SfxID::None, MissileGraphicID::Lightning, Lightning | Invisible, MissileMovementDistribution::Disabled }, +/*Lightning*/ { &AddLightning, &ProcessLightning, SfxID::SpellLightning, SfxID::SpellLightningHit, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Disabled }, +/*MagmaBallExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, SfxID::None, SfxID::None, MissileGraphicID::MagmaBallExplosion, Physical, MissileMovementDistribution::Disabled }, +/*TownPortal*/ { &AddTownPortal, &ProcessTownPortal, SfxID::SpellPortal, SfxID::None, MissileGraphicID::TownPortal, Magic, MissileMovementDistribution::Disabled }, +/*FlashBottom*/ { &AddFlashBottom, &ProcessFlashBottom, SfxID::SpellNova, SfxID::SpellLightningHit, MissileGraphicID::FlashBottom, Magic, MissileMovementDistribution::Disabled }, +/*FlashTop*/ { &AddFlashTop, &ProcessFlashTop, SfxID::None, SfxID::None, MissileGraphicID::FlashTop, Magic, MissileMovementDistribution::Disabled }, +/*ManaShield*/ { &AddManaShield, nullptr, SfxID::SpellManaShield, SfxID::None, MissileGraphicID::ManaShield, Magic | Invisible, MissileMovementDistribution::Disabled }, +/*FlameWave*/ { &AddFlameWave, &ProcessFlameWave, SfxID::None, SfxID::None, MissileGraphicID::FireWall, Fire, MissileMovementDistribution::Unblockable }, +/*ChainLightning*/ { &AddChainLightning, &ProcessChainLightning, SfxID::SpellLightning, SfxID::SpellLightningHit, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Disabled }, +/*ChainBall*/ { nullptr, nullptr, SfxID::None, SfxID::None, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Disabled }, +/*BloodHit*/ { nullptr, nullptr, SfxID::SpellBloodStar, SfxID::SpellBloodStarHit, MissileGraphicID::BloodHit, Physical, MissileMovementDistribution::Disabled }, +/*BoneHit*/ { nullptr, nullptr, SfxID::None, SfxID::None, MissileGraphicID::BoneHit, Physical, MissileMovementDistribution::Disabled }, +/*MetalHit*/ { nullptr, nullptr, SfxID::None, SfxID::None, MissileGraphicID::MetalHit, Physical, MissileMovementDistribution::Disabled }, +/*Rhino*/ { &AddRhino, &ProcessRhino, SfxID::None, SfxID::None, MissileGraphicID::None, Physical, MissileMovementDistribution::Blockable }, +/*MagmaBall*/ { &AddMagmaBall, &ProcessGenericProjectile, SfxID::None, SfxID::None, MissileGraphicID::MagmaBall, Fire, MissileMovementDistribution::Blockable }, +/*ThinLightningControl*/ { &AddLightningControl, &ProcessLightningControl, SfxID::None, SfxID::None, MissileGraphicID::ThinLightning, Lightning | Invisible, MissileMovementDistribution::Disabled }, +/*ThinLightning*/ { &AddLightning, &ProcessLightning, SfxID::None, SfxID::None, MissileGraphicID::ThinLightning, Lightning, MissileMovementDistribution::Disabled }, +/*BloodStar*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SfxID::None, SfxID::None, MissileGraphicID::BloodStar, Magic, MissileMovementDistribution::Blockable }, +/*BloodStarExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, SfxID::None, SfxID::None, MissileGraphicID::BloodStarExplosion, Magic, MissileMovementDistribution::Disabled }, +/*Teleport*/ { &AddTeleport, &ProcessTeleport, SfxID::SpellElemental, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*FireArrow*/ { &AddElementalArrow, &ProcessElementalArrow, SfxID::None, SfxID::None, MissileGraphicID::FireArrow, Fire | Arrow, MissileMovementDistribution::Blockable }, +/*DoomSerpents*/ { nullptr, nullptr, SfxID::SpellDoomSerpents, SfxID::None, MissileGraphicID::DoomSerpents, Magic | Invisible, MissileMovementDistribution::Disabled }, +/*FireOnly*/ { nullptr, nullptr, SfxID::None, SfxID::None, MissileGraphicID::FireWall, Fire, MissileMovementDistribution::Disabled }, +/*StoneCurse*/ { &AddStoneCurse, &ProcessStoneCurse, SfxID::SpellStoneCurse, SfxID::None, MissileGraphicID::None, Magic | Invisible, MissileMovementDistribution::Disabled }, +/*BloodRitual*/ { nullptr, nullptr, SfxID::None, SfxID::None, MissileGraphicID::None, Physical, MissileMovementDistribution::Disabled }, +/*Invisibility*/ { nullptr, nullptr, SfxID::SpellInvisibility, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Golem*/ { &AddGolem, nullptr, SfxID::SpellGolem, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Etherealize*/ { nullptr, nullptr, SfxID::SpellEtherealize, SfxID::None, MissileGraphicID::Etherealize, Physical, MissileMovementDistribution::Disabled }, +/*Spurt*/ { nullptr, nullptr, SfxID::None, SfxID::None, MissileGraphicID::Spurt, Physical, MissileMovementDistribution::Disabled }, +/*ApocalypseBoom*/ { &AddApocalypseBoom, &ProcessApocalypseBoom, SfxID::None, SfxID::None, MissileGraphicID::ApocalypseBoom, Physical, MissileMovementDistribution::Disabled }, +/*Healing*/ { &AddHealing, nullptr, SfxID::None, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*FireWallControl*/ { &AddFireWallControl, &ProcessFireWallControl, SfxID::None, SfxID::None, MissileGraphicID::FireWall, Fire | Invisible, MissileMovementDistribution::Disabled }, +/*Infravision*/ { &AddInfravision, &ProcessInfravision, SfxID::SpellInfravision, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Identify*/ { &AddIdentify, nullptr, SfxID::None, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*FlameWaveControl*/ { &AddFlameWaveControl, &ProcessFlameWaveControl, SfxID::SpellFlameWave, SfxID::None, MissileGraphicID::FireWall, Fire, MissileMovementDistribution::Disabled }, +/*Nova*/ { &AddNova, &ProcessNova, SfxID::SpellNova, SfxID::None, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Disabled }, +/*Rage*/ { &AddRage, &ProcessRage, SfxID::None, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Apocalypse*/ { &AddApocalypse, &ProcessApocalypse, SfxID::SpellApocalypse, SfxID::None, MissileGraphicID::ApocalypseBoom, Magic, MissileMovementDistribution::Disabled }, +/*ItemRepair*/ { &AddItemRepair, nullptr, SfxID::None, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*StaffRecharge*/ { &AddStaffRecharge, nullptr, SfxID::None, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*TrapDisarm*/ { &AddTrapDisarm, nullptr, SfxID::SpellTrapDisarm, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Inferno*/ { &AddInferno, &ProcessInferno, SfxID::SpellInferno, SfxID::None, MissileGraphicID::Inferno, Fire, MissileMovementDistribution::Disabled }, +/*InfernoControl*/ { &AddInfernoControl, &ProcessInfernoControl, SfxID::None, SfxID::None, MissileGraphicID::None, Fire | Invisible, MissileMovementDistribution::Disabled }, +/*FireMan*/ { nullptr, nullptr, SfxID::None, SfxID::None, MissileGraphicID::None, Physical, MissileMovementDistribution::Blockable }, +/*Krull*/ { nullptr, nullptr, SfxID::None, SfxID::None, MissileGraphicID::Krull, Fire | Arrow, MissileMovementDistribution::Blockable }, +/*ChargedBolt*/ { &AddChargedBolt, &ProcessChargedBolt, SfxID::SpellChargedBolt, SfxID::None, MissileGraphicID::ChargedBolt, Lightning, MissileMovementDistribution::Blockable }, +/*HolyBolt*/ { &AddHolyBolt, &ProcessHolyBolt, SfxID::SpellHolyBolt, SfxID::SpellLightningHit, MissileGraphicID::HolyBolt, Physical, MissileMovementDistribution::Blockable }, +/*Resurrect*/ { &AddResurrect, nullptr, SfxID::None, SfxID::SpellResurrect, MissileGraphicID::None, Magic | Invisible, MissileMovementDistribution::Disabled }, +/*Telekinesis*/ { &AddTelekinesis, nullptr, SfxID::SpellEtherealize, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*LightningArrow*/ { &AddElementalArrow, &ProcessElementalArrow, SfxID::None, SfxID::None, MissileGraphicID::LightningArrow, Lightning | Arrow, MissileMovementDistribution::Blockable }, +/*Acid*/ { &AddAcid, &ProcessGenericProjectile, SfxID::SpellAcid, SfxID::None, MissileGraphicID::Acid, Acid, MissileMovementDistribution::Blockable }, +/*AcidSplat*/ { &AddMissileExplosion, &ProcessAcidSplate, SfxID::None, SfxID::None, MissileGraphicID::AcidSplat, Acid, MissileMovementDistribution::Disabled }, +/*AcidPuddle*/ { &AddAcidPuddle, &ProcessAcidPuddle, SfxID::SpellPuddle, SfxID::None, MissileGraphicID::AcidPuddle, Acid, MissileMovementDistribution::Disabled }, +/*HealOther*/ { &AddHealOther, nullptr, SfxID::None, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Elemental*/ { &AddElemental, &ProcessElemental, SfxID::SpellElemental, SfxID::None, MissileGraphicID::Elemental, Fire, MissileMovementDistribution::Unblockable }, +/*ResurrectBeam*/ { &AddResurrectBeam, &ProcessResurrectBeam, SfxID::None, SfxID::None, MissileGraphicID::Resurrect, Physical, MissileMovementDistribution::Disabled }, +/*BoneSpirit*/ { &AddBoneSpirit, &ProcessBoneSpirit, SfxID::SpellBoneSpirit, SfxID::SpellBoneSpiritHit, MissileGraphicID::BoneSpirit, Magic, MissileMovementDistribution::Blockable }, +/*WeaponExplosion*/ { &AddWeaponExplosion, &ProcessWeaponExplosion, SfxID::None, SfxID::None, MissileGraphicID::None, Physical, MissileMovementDistribution::Disabled }, +/*RedPortal*/ { &AddRedPortal, &ProcessRedPortal, SfxID::SpellPortal, SfxID::None, MissileGraphicID::RedPortal, Physical, MissileMovementDistribution::Disabled }, +/*DiabloApocalypseBoom*/ { &AddApocalypseBoom, &ProcessApocalypseBoom, SfxID::None, SfxID::None, MissileGraphicID::DiabloApocalypseBoom, Physical, MissileMovementDistribution::Disabled }, +/*DiabloApocalypse*/ { &AddDiabloApocalypse, nullptr, SfxID::None, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Mana*/ { &AddMana, nullptr, SfxID::None, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Magi*/ { &AddMagi, nullptr, SfxID::None, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*LightningWall*/ { &AddLightningWall, &ProcessLightningWall, SfxID::SpellLightningWall, SfxID::SpellLightningHit, MissileGraphicID::Lightning, Lightning, MissileMovementDistribution::Disabled }, +/*LightningWallControl*/ { &AddFireWallControl, &ProcessLightningWallControl, SfxID::None, SfxID::None, MissileGraphicID::Lightning, Lightning | Invisible, MissileMovementDistribution::Disabled }, +/*Immolation*/ { &AddNova, &ProcessImmolation, SfxID::SpellFirebolt, SfxID::SpellFireHit, MissileGraphicID::Fireball, Fire, MissileMovementDistribution::Disabled }, +/*SpectralArrow*/ { &AddSpectralArrow, &ProcessSpectralArrow, SfxID::None, SfxID::None, MissileGraphicID::Arrow, Physical | Arrow, MissileMovementDistribution::Disabled }, +/*FireballBow*/ { &AddImmolation, &ProcessFireball, SfxID::ShootFireballBow, SfxID::SpellFireHit, MissileGraphicID::Fireball, Fire, MissileMovementDistribution::Blockable }, +/*LightningBow*/ { &AddLightningBow, &ProcessLightningBow, SfxID::ShootFireballBow, SfxID::None, MissileGraphicID::Lightning, Lightning | Invisible, MissileMovementDistribution::Disabled }, +/*ChargedBoltBow*/ { &AddChargedBoltBow, &ProcessChargedBolt, SfxID::SpellChargedBolt, SfxID::None, MissileGraphicID::ChargedBolt, Lightning, MissileMovementDistribution::Blockable }, +/*HolyBoltBow*/ { &AddHolyBolt, &ProcessHolyBolt, SfxID::SpellHolyBolt, SfxID::SpellLightningHit, MissileGraphicID::HolyBolt, Physical, MissileMovementDistribution::Blockable }, +/*Warp*/ { &AddWarp, &ProcessTeleport, SfxID::SpellEtherealize, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Reflect*/ { &AddReflect, nullptr, SfxID::SpellManaShield, SfxID::None, MissileGraphicID::Reflect, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Berserk*/ { &AddBerserk, nullptr, SfxID::None, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*RingOfFire*/ { &AddRingOfFire, &ProcessRingOfFire, SfxID::None, SfxID::None, MissileGraphicID::FireWall, Fire | Invisible, MissileMovementDistribution::Disabled }, +/*StealPotions*/ { &AddStealPotions, nullptr, SfxID::None, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*StealMana*/ { &AddStealMana, nullptr, SfxID::SpellEnd, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*RingOfLightning*/ { nullptr, nullptr, SfxID::None, SfxID::None, MissileGraphicID::Lightning, Lightning | Invisible, MissileMovementDistribution::Disabled }, +/*Search*/ { &AddSearch, &ProcessSearch, SfxID::None, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Aura*/ { nullptr, nullptr, SfxID::None, SfxID::SpellLightningHit, MissileGraphicID::FlashBottom, Magic | Invisible, MissileMovementDistribution::Disabled }, +/*Aura2*/ { nullptr, nullptr, SfxID::None, SfxID::None, MissileGraphicID::FlashTop, Magic | Invisible, MissileMovementDistribution::Disabled }, +/*SpiralFireball*/ { nullptr, nullptr, SfxID::SpellFirebolt, SfxID::SpellFireHit, MissileGraphicID::Fireball, Fire, MissileMovementDistribution::Disabled }, +/*RuneOfFire*/ { &AddRuneOfFire, &ProcessRune, SfxID::None, SfxID::None, MissileGraphicID::Rune, Physical, MissileMovementDistribution::Disabled }, +/*RuneOfLight*/ { &AddRuneOfLight, &ProcessRune, SfxID::None, SfxID::None, MissileGraphicID::Rune, Physical, MissileMovementDistribution::Disabled }, +/*RuneOfNova*/ { &AddRuneOfNova, &ProcessRune, SfxID::None, SfxID::None, MissileGraphicID::Rune, Physical, MissileMovementDistribution::Disabled }, +/*RuneOfImmolation*/ { &AddRuneOfImmolation, &ProcessRune, SfxID::None, SfxID::None, MissileGraphicID::Rune, Physical, MissileMovementDistribution::Disabled }, +/*RuneOfStone*/ { &AddRuneOfStone, &ProcessRune, SfxID::None, SfxID::None, MissileGraphicID::Rune, Physical, MissileMovementDistribution::Disabled }, +/*BigExplosion*/ { &AddBigExplosion, &ProcessBigExplosion, SfxID::BigExplosion, SfxID::BigExplosion, MissileGraphicID::BigExplosion, Fire, MissileMovementDistribution::Disabled }, +/*HorkSpawn*/ { &AddHorkSpawn, &ProcessHorkSpawn, SfxID::None, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*Jester*/ { &AddJester, nullptr, SfxID::None, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*OpenNest*/ { &AddOpenNest, nullptr, SfxID::None, SfxID::None, MissileGraphicID::None, Physical | Invisible, MissileMovementDistribution::Disabled }, +/*OrangeFlare*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SfxID::None, SfxID::None, MissileGraphicID::OrangeFlare, Magic, MissileMovementDistribution::Blockable }, +/*BlueFlare*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SfxID::None, SfxID::None, MissileGraphicID::BlueFlare2, Magic, MissileMovementDistribution::Blockable }, +/*RedFlare*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SfxID::None, SfxID::None, MissileGraphicID::RedFlare, Magic, MissileMovementDistribution::Blockable }, +/*YellowFlare*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SfxID::None, SfxID::None, MissileGraphicID::YellowFlare, Magic, MissileMovementDistribution::Blockable }, +/*BlueFlare2*/ { &AddGenericMagicMissile, &ProcessGenericProjectile, SfxID::None, SfxID::None, MissileGraphicID::BlueFlare2, Magic, MissileMovementDistribution::Blockable }, +/*YellowExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, SfxID::SpellFireHit, SfxID::None, MissileGraphicID::YellowFlareExplosion, Physical, MissileMovementDistribution::Disabled }, +/*RedExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, SfxID::SpellFireHit, SfxID::None, MissileGraphicID::RedFlareExplosion, Physical, MissileMovementDistribution::Disabled }, +/*BlueExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, SfxID::SpellFireHit, SfxID::None, MissileGraphicID::BlueFlareExplosion, Physical, MissileMovementDistribution::Disabled }, +/*BlueExplosion2*/ { &AddMissileExplosion, &ProcessMissileExplosion, SfxID::SpellFireHit, SfxID::None, MissileGraphicID::BlueFlareExplosion2, Physical, MissileMovementDistribution::Disabled }, +/*OrangeExplosion*/ { &AddMissileExplosion, &ProcessMissileExplosion, SfxID::SpellFireHit, SfxID::None, MissileGraphicID::OrangeFlareExplosion, Physical, MissileMovementDistribution::Disabled }, // clang-format on }; namespace { -constexpr std::array Repeat(uint8_t v) // NOLINT(readability-identifier-length) +/** Data related to each missile graphic ID. */ +std::vector MissileSpriteData; +std::vector> MissileAnimDelays; +std::vector> MissileAnimLengths; + +size_t ToIndex(std::vector> &all, const std::array &value) { - return { v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v }; + for (size_t i = 0; i < all.size(); ++i) { + if (all[i] == value) return i; + } + all.push_back(value); + return all.size() - 1; } -const std::array MissileAnimDelays[] { - {}, - Repeat(1), - Repeat(2), - { 0, 1 }, - { 1 }, -}; +tl::expected ParseMissileGraphicsFlag(std::string_view value) +{ + if (value.empty()) return MissileGraphicsFlags::None; + if (value == "MonsterOwned") return MissileGraphicsFlags::MonsterOwned; + if (value == "NotAnimated") return MissileGraphicsFlags::NotAnimated; + return tl::make_unexpected("Unknown enum value"); +} -const std::array MissileAnimLengths[] { - {}, - Repeat(1), - Repeat(4), - Repeat(5), - Repeat(6), - Repeat(7), - Repeat(8), - Repeat(9), - Repeat(10), - Repeat(12), - Repeat(13), - Repeat(14), - Repeat(15), - Repeat(16), - Repeat(17), - Repeat(19), - Repeat(20), - { 9, 4 }, - { 15, 14, 3 }, - { 13, 11 }, - { 16, 16, 16, 16, 16, 16, 16, 16, 8 } -}; +void LoadMissileSpriteData() +{ + const std::string_view filename = "txtdata\\missiles\\missile_sprites.tsv"; + DataFile dataFile = DataFile::loadOrDie(filename); + dataFile.skipHeaderOrDie(filename); -constexpr uint8_t AnimLen_0 = 0; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_1 = 1; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_4 = 2; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_5 = 3; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_6 = 4; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_7 = 5; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_8 = 6; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_9 = 7; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_10 = 8; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_12 = 9; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_13 = 10; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_14 = 11; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_15 = 12; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_16 = 13; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_17 = 14; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_19 = 15; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_20 = 16; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_9_4 = 17; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_15_14_3 = 18; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_13_11 = 19; // NOLINT(readability-identifier-naming) -constexpr uint8_t AnimLen_16x8_8 = 20; // NOLINT(readability-identifier-naming) + MissileAnimDelays.clear(); + MissileAnimLengths.clear(); + MissileSpriteData.clear(); + MissileSpriteData.reserve(dataFile.numRecords()); -} // namespace + for (DataFileRecord record : dataFile) { + RecordReader reader { record, filename }; + MissileFileData &item = MissileSpriteData.emplace_back(); + reader.advance(); // skip id + reader.readInt("width", item.animWidth); + reader.readInt("width2", item.animWidth2); + reader.readString("name", item.name); + reader.readInt("numFrames", item.animFAmt); + reader.read("flags", item.flags, ParseMissileGraphicsFlag); -/** Data related to each missile graphic ID. */ -MissileFileData MissileSpriteData[] = { - // clang-format off -// id sprites, animWidth, animWidth2, name, animFAmt, flags, animDelayIdx, animLenIdx -/*Arrow*/ { {}, 96, 16, "arrows", 1, MissileGraphicsFlags::NotAnimated, 0, AnimLen_16 }, -/*Fireball*/ { {}, 96, 16, "fireba", 16, MissileGraphicsFlags::None, 0, AnimLen_14 }, -/*Guardian*/ { {}, 96, 16, "guard", 3, MissileGraphicsFlags::None, 1, AnimLen_15_14_3 }, -/*Lightning*/ { {}, 96, 16, "lghning", 1, MissileGraphicsFlags::None, 0, AnimLen_8 }, -/*FireWall*/ { {}, 128, 32, "firewal", 2, MissileGraphicsFlags::None, 0, AnimLen_13_11 }, -/*MagmaBallExplosion*/ { {}, 128, 32, "magblos", 1, MissileGraphicsFlags::None, 1, AnimLen_10 }, -/*TownPortal*/ { {}, 96, 16, "portal", 2, MissileGraphicsFlags::None, 3, AnimLen_16 }, -/*FlashBottom*/ { {}, 160, 48, "bluexfr", 1, MissileGraphicsFlags::None, 0, AnimLen_19 }, -/*FlashTop*/ { {}, 160, 48, "bluexbk", 1, MissileGraphicsFlags::None, 0, AnimLen_19 }, -/*ManaShield*/ { {}, 96, 16, "manashld", 1, MissileGraphicsFlags::NotAnimated, 0, AnimLen_1 }, -/*BloodHit*/ { {}, 96, 16, {}, 4, MissileGraphicsFlags::None, 0, AnimLen_15 }, -/*BoneHit*/ { {}, 128, 32, {}, 3, MissileGraphicsFlags::None, 2, AnimLen_8 }, -/*MetalHit*/ { {}, 96, 16, {}, 3, MissileGraphicsFlags::None, 2, AnimLen_10 }, -/*FireArrow*/ { {}, 96, 16, "farrow", 16, MissileGraphicsFlags::None, 0, AnimLen_4 }, -/*DoomSerpents*/ { {}, 96, 16, "doom", 9, MissileGraphicsFlags::MonsterOwned, 1, AnimLen_15 }, -/*Golem*/ { {}, 0, 0, {}, 1, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_0 }, -/*Spurt*/ { {}, 128, 32, {}, 2, MissileGraphicsFlags::None, 2, AnimLen_8 }, -/*ApocalypseBoom*/ { {}, 96, 16, "newexp", 1, MissileGraphicsFlags::None, 1, AnimLen_15 }, -/*StoneCurseShatter*/ { {}, 128, 32, "shatter1", 1, MissileGraphicsFlags::None, 1, AnimLen_12 }, -/*BigExplosion*/ { {}, 160, 48, "bigexp", 1, MissileGraphicsFlags::None, 0, AnimLen_15 }, -/*Inferno*/ { {}, 96, 16, "inferno", 1, MissileGraphicsFlags::None, 0, AnimLen_20 }, -/*ThinLightning*/ { {}, 96, 16, "thinlght", 1, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_8 }, -/*BloodStar*/ { {}, 128, 32, "flare", 1, MissileGraphicsFlags::None, 0, AnimLen_16 }, -/*BloodStarExplosion*/ { {}, 128, 32, "flareexp", 1, MissileGraphicsFlags::None, 0, AnimLen_7 }, -/*MagmaBall*/ { {}, 128, 32, "magball", 8, MissileGraphicsFlags::MonsterOwned, 1, AnimLen_16 }, -/*Krull*/ { {}, 96, 16, "krull", 1, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_14 }, -/*ChargedBolt*/ { {}, 64, 0, "miniltng", 1, MissileGraphicsFlags::None, 1, AnimLen_8 }, -/*HolyBolt*/ { {}, 96, 16, "holy", 16, MissileGraphicsFlags::None, 4, AnimLen_14 }, -/*HolyBoltExplosion*/ { {}, 160, 48, "holyexpl", 1, MissileGraphicsFlags::None, 0, AnimLen_8 }, -/*LightningArrow*/ { {}, 96, 16, "larrow", 16, MissileGraphicsFlags::None, 0, AnimLen_4 }, -/*FireArrowExplosion*/ { {}, 64, 0, {}, 1, MissileGraphicsFlags::None, 0, AnimLen_6 }, -/*Acid*/ { {}, 96, 16, "acidbf", 16, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_8 }, -/*AcidSplat*/ { {}, 96, 16, "acidspla", 1, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_8 }, -/*AcidPuddle*/ { {}, 96, 16, "acidpud", 2, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_9_4 }, -/*Etherealize*/ { {}, 96, 16, {}, 1, MissileGraphicsFlags::None, 0, AnimLen_1 }, -/*Elemental*/ { {}, 96, 16, "firerun", 8, MissileGraphicsFlags::None, 1, AnimLen_12 }, -/*Resurrect*/ { {}, 96, 16, "ressur1", 1, MissileGraphicsFlags::None, 0, AnimLen_16 }, -/*BoneSpirit*/ { {}, 96, 16, "sklball", 9, MissileGraphicsFlags::None, 1, AnimLen_16x8_8 }, -/*RedPortal*/ { {}, 96, 16, "rportal", 2, MissileGraphicsFlags::None, 0, AnimLen_16 }, -/*DiabloApocalypseBoom*/ { {}, 160, 48, "fireplar", 1, MissileGraphicsFlags::MonsterOwned, 1, AnimLen_17 }, -/*BloodStarBlue*/ { {}, 96, 16, "scubmisb", 1, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_16 }, -/*BloodStarBlueExplosion*/ { {}, 128, 32, "scbsexpb", 1, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_6 }, -/*BloodStarYellow*/ { {}, 96, 16, "scubmisc", 1, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_16 }, -/*BloodStarYellowExplosion*/ { {}, 128, 32, "scbsexpc", 1, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_6 }, -/*BloodStarRed*/ { {}, 96, 16, "scubmisd", 1, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_16 }, -/*BloodStarRedExplosion*/ { {}, 128, 32, "scbsexpd", 1, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_6 }, -/*HorkSpawn*/ { {}, 96, 16, "spawns", 8, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_9 }, -/*Reflect*/ { {}, 160, 64, "reflect", 1, MissileGraphicsFlags::NotAnimated, 0, AnimLen_1 }, -/*OrangeFlare*/ { {}, 96, 8, "ms_ora", 16, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_15 }, -/*BlueFlare*/ { {}, 96, 8, "ms_bla", 16, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_15 }, -/*RedFlare*/ { {}, 96, 8, "ms_reb", 16, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_15 }, -/*YellowFlare*/ { {}, 96, 8, "ms_yeb", 16, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_15 }, -/*Rune*/ { {}, 96, 8, "rglows1", 1, MissileGraphicsFlags::None, 0, AnimLen_10 }, -/*YellowFlareExplosion*/ { {}, 220, 78, "ex_yel2", 1, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_10 }, -/*BlueFlareExplosion*/ { {}, 212, 86, "ex_blu2", 1, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_10 }, -/*RedFlareExplosion*/ { {}, 292, 114, "ex_red3", 1, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_7 }, -/*BlueFlare2*/ { {}, 96, 8, "ms_blb", 16, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_15 }, -/*OrangeFlareExplosion*/ { {}, 96, -12, "ex_ora1", 1, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_13 }, -/*BlueFlareExplosion2*/ { {}, 292, 114, "ex_blu3", 1, MissileGraphicsFlags::MonsterOwned, 0, AnimLen_7 }, -/*None*/ { {}, 0, 0, {}, 0, MissileGraphicsFlags::None, 0, 0 }, - // clang-format on -}; + std::array arr; + reader.readIntArray("frameDelay", arr); + item.animDelayIdx = static_cast(ToIndex(MissileAnimDelays, arr)); + + reader.readIntArray("frameLength", arr); + item.animLenIdx = static_cast(ToIndex(MissileAnimLengths, arr)); + } + + MissileSpriteData.shrink_to_fit(); + MissileAnimDelays.shrink_to_fit(); + MissileAnimLengths.shrink_to_fit(); +} + +} // namespace uint8_t MissileFileData::animDelay(uint8_t dir) const { @@ -305,13 +246,23 @@ void MissileFileData::LoadGFX() #endif } +MissileFileData &GetMissileSpriteData(MissileGraphicID graphicId) +{ + return MissileSpriteData[static_cast>(graphicId)]; +} + +void LoadMissileData() +{ + LoadMissileSpriteData(); +} + void InitMissileGFX(bool loadHellfireGraphics) { if (HeadlessMode) return; - for (size_t mi = 0; MissileSpriteData[mi].animFAmt != 0; mi++) { - if (!loadHellfireGraphics && mi > static_cast(MissileGraphicID::BloodStarRedExplosion)) + for (size_t mi = 0; mi < MissileSpriteData.size(); ++mi) { + if (!loadHellfireGraphics && mi >= static_cast(MissileGraphicID::HorkSpawn)) break; if (MissileSpriteData[mi].flags == MissileGraphicsFlags::MonsterOwned) continue; diff --git a/Source/misdat.h b/Source/misdat.h index aef08e95ed1..e5c451b37ff 100644 --- a/Source/misdat.h +++ b/Source/misdat.h @@ -5,17 +5,15 @@ */ #pragma once +#include #include +#include #include -#include #include "effects.h" -#include "engine.h" #include "engine/clx_sprite.hpp" #include "spelldat.h" #include "utils/enum_traits.h" -#include "utils/stdcompat/cstddef.hpp" -#include "utils/stdcompat/string_view.hpp" namespace devilution { @@ -132,8 +130,14 @@ use_enum_as_flags(MissileDataFlags); struct MissileData { void (*mAddProc)(Missile &, AddMissileParameter &); void (*mProc)(Missile &); - _sfx_id mlSFX; - _sfx_id miSFX; + /** + * @brief Sound emitted when cast. + */ + SfxID mlSFX; + /** + * @brief Sound emitted on impact. + */ + SfxID miSFX; MissileGraphicID mFileNum; MissileDataFlags flags; MissileMovementDistribution movementDistribution; @@ -166,7 +170,7 @@ struct MissileFileData { OptionalOwnedClxSpriteListOrSheet sprites; uint16_t animWidth; int8_t animWidth2; - char name[9]; + std::string name; uint8_t animFAmt; MissileGraphicsFlags flags; uint8_t animDelayIdx; @@ -200,15 +204,12 @@ extern const MissileData MissilesData[]; inline const MissileData &GetMissileData(MissileID missileId) { - return MissilesData[static_cast::type>(missileId)]; + return MissilesData[static_cast>(missileId)]; } -extern MissileFileData MissileSpriteData[]; +MissileFileData &GetMissileSpriteData(MissileGraphicID graphicId); -inline MissileFileData &GetMissileSpriteData(MissileGraphicID graphicId) -{ - return MissileSpriteData[static_cast::type>(graphicId)]; -} +void LoadMissileData(); void InitMissileGFX(bool loadHellfireGraphics = false); void FreeMissileGFX(); diff --git a/Source/missiles.cpp b/Source/missiles.cpp index a5dfe4a95ba..4e0e090bc3d 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -6,6 +6,7 @@ #include "missiles.h" #include +#include #include #include "control.h" @@ -103,7 +104,7 @@ constexpr Direction16 Direction16Flip(Direction16 x, Direction16 pivot) return static_cast(ret); } -void UpdateMissileVelocity(Missile &missile, Point destination, int velocityInPixels) +void UpdateMissileVelocity(Missile &missile, WorldTilePosition destination, int velocityInPixels) { missile.position.velocity = { 0, 0 }; @@ -111,7 +112,7 @@ void UpdateMissileVelocity(Missile &missile, Point destination, int velocityInPi return; // Get the normalized vector in isometric projection - Displacement fixed16NormalVector = (missile.position.tile - destination).worldToNormalScreen(); + Displacement fixed16NormalVector = (Point { missile.position.tile } - Point { destination }).worldToNormalScreen(); // Multiplying by the target velocity gives us a scaled velocity vector. missile.position.velocity = fixed16NormalVector * velocityInPixels; @@ -201,16 +202,15 @@ int ProjectileTrapDamage(Missile &missile) return currlevel + GenerateRnd(2 * currlevel); } -bool MonsterMHit(int pnum, int monsterId, int mindam, int maxdam, int dist, MissileID t, DamageType damageType, bool shift) +bool MonsterMHit(const Player &player, int monsterId, int mindam, int maxdam, int dist, MissileID t, DamageType damageType, bool shift) { auto &monster = Monsters[monsterId]; if (!monster.isPossibleToHit() || monster.isImmune(t, damageType)) return false; - int hit = GenerateRnd(100); + int hit = RandomIntLessThan(100); int hper = 0; - const Player &player = Players[pnum]; const MissileData &missileData = GetMissileData(t); if (missileData.isArrow()) { hper = player.GetRangedPiercingToHit(); @@ -220,7 +220,7 @@ bool MonsterMHit(int pnum, int monsterId, int mindam, int maxdam, int dist, Miss hper = player.GetMagicToHit() - (monster.level(sgGameInitInfo.nDifficulty) * 2) - dist; } - hper = clamp(hper, 5, 95); + hper = std::clamp(hper, 5, 95); if (monster.mode == MonsterMode::Petrified) hit = 0; @@ -280,10 +280,8 @@ bool MonsterMHit(int pnum, int monsterId, int mindam, int maxdam, int dist, Miss return true; } -bool Plr2PlrMHit(const Player &player, int p, int mindam, int maxdam, int dist, MissileID mtype, DamageType damageType, bool shift, bool *blocked) +bool Plr2PlrMHit(const Player &player, Player &target, int mindam, int maxdam, int dist, MissileID mtype, DamageType damageType, bool shift, bool *blocked) { - Player &target = Players[p]; - if (sgGameInitInfo.bFriendlyFire == 0 && player.friendlyMode) return false; @@ -332,11 +330,11 @@ bool Plr2PlrMHit(const Player &player, int p, int mindam, int maxdam, int dist, - target.GetArmor(); } else { hit = player.GetMagicToHit() - - (target._pLevel * 2) + - (target.getCharacterLevel() * 2) - dist; } - hit = clamp(hit, 5, 95); + hit = std::clamp(hit, 5, 95); if (hper >= hit) { return false; @@ -347,8 +345,8 @@ bool Plr2PlrMHit(const Player &player, int p, int mindam, int maxdam, int dist, blkper = GenerateRnd(100); } - int blk = target.GetBlockChance() - (player._pLevel * 2); - blk = clamp(blk, 0, 100); + int blk = target.GetBlockChance() - (player.getCharacterLevel() * 2); + blk = std::clamp(blk, 0, 100); int dam; if (mtype == MissileID::BoneSpirit) { @@ -365,7 +363,7 @@ bool Plr2PlrMHit(const Player &player, int p, int mindam, int maxdam, int dist, if (resper > 0) { dam -= (dam * resper) / 100; if (&player == MyPlayer) - NetSendCmdDamage(true, p, dam, damageType); + NetSendCmdDamage(true, target, dam, damageType); target.Say(HeroSpeech::ArghClang); return true; } @@ -375,7 +373,7 @@ bool Plr2PlrMHit(const Player &player, int p, int mindam, int maxdam, int dist, *blocked = true; } else { if (&player == MyPlayer) - NetSendCmdDamage(true, p, dam, damageType); + NetSendCmdDamage(true, target, dam, damageType); StartPlrHit(target, dam, false); } @@ -412,8 +410,8 @@ void CheckMissileCol(Missile &missile, DamageType damageType, int minDamage, int bool isMonsterHit = false; int mid = dMonster[mx][my]; - if (mid > 0 || (mid != 0 && Monsters[abs(mid) - 1].mode == MonsterMode::Petrified)) { - mid = abs(mid) - 1; + if (mid > 0 || (mid != 0 && Monsters[std::abs(mid) - 1].mode == MonsterMode::Petrified)) { + mid = std::abs(mid) - 1; if (missile.IsTrap() || (missile._micaster == TARGET_PLAYERS && ( // or was fired by a monster and Monsters[mid].isPlayerMinion() != Monsters[missile._misource].isPlayerMinion() // the monsters are on opposing factions @@ -423,7 +421,7 @@ void CheckMissileCol(Missile &missile, DamageType damageType, int minDamage, int // then the missile can potentially hit this target isMonsterHit = MonsterTrapHit(mid, minDamage, maxDamage, missile._midist, missile._mitype, damageType, isDamageShifted); } else if (IsAnyOf(missile._micaster, TARGET_BOTH, TARGET_MONSTERS)) { - isMonsterHit = MonsterMHit(missile._misource, mid, minDamage, maxDamage, missile._midist, missile._mitype, damageType, isDamageShifted); + isMonsterHit = MonsterMHit(*missile.sourcePlayer(), mid, minDamage, maxDamage, missile._midist, missile._mitype, damageType, isDamageShifted); } } @@ -435,19 +433,19 @@ void CheckMissileCol(Missile &missile, DamageType damageType, int minDamage, int bool isPlayerHit = false; bool blocked = false; - const int8_t pid = dPlayer[mx][my]; - if (pid > 0) { + Player *player = PlayerAtPosition({ mx, my }, true); + if (player != nullptr) { if (missile._micaster != TARGET_BOTH && !missile.IsTrap()) { if (missile._micaster == TARGET_MONSTERS) { - if ((pid - 1) != missile._misource) - isPlayerHit = Plr2PlrMHit(Players[missile._misource], pid - 1, minDamage, maxDamage, missile._midist, missile._mitype, damageType, isDamageShifted, &blocked); + if (player->getId() != missile._misource) + isPlayerHit = Plr2PlrMHit(Players[missile._misource], *player, minDamage, maxDamage, missile._midist, missile._mitype, damageType, isDamageShifted, &blocked); } else { Monster &monster = Monsters[missile._misource]; - isPlayerHit = PlayerMHit(pid - 1, &monster, missile._midist, minDamage, maxDamage, missile._mitype, damageType, isDamageShifted, DeathReason::MonsterOrTrap, &blocked); + isPlayerHit = PlayerMHit(*player, &monster, missile._midist, minDamage, maxDamage, missile._mitype, damageType, isDamageShifted, DeathReason::MonsterOrTrap, &blocked); } } else { DeathReason deathReason = (!missile.IsTrap() && (missile._miAnimType == MissileGraphicID::FireWall || missile._miAnimType == MissileGraphicID::Lightning)) ? DeathReason::Player : DeathReason::MonsterOrTrap; - isPlayerHit = PlayerMHit(pid - 1, nullptr, missile._midist, minDamage, maxDamage, missile._mitype, damageType, isDamageShifted, deathReason, &blocked); + isPlayerHit = PlayerMHit(*player, nullptr, missile._midist, minDamage, maxDamage, missile._mitype, damageType, isDamageShifted, deathReason, &blocked); } } @@ -472,7 +470,7 @@ void CheckMissileCol(Missile &missile, DamageType damageType, int minDamage, int } const MissileData &missileData = GetMissileData(missile._mitype); - if (missile._mirange == 0 && missileData.miSFX != -1) + if (missile._mirange == 0 && missileData.miSFX != SfxID::None) PlaySfxLoc(missileData.miSFX, missile.position.tile); } @@ -494,7 +492,7 @@ bool MoveMissile(Missile &missile, tl::function_ref checkTile, bool // Did the missile skip a tile? if (possibleVisitTiles > 1) { auto speed = abs(missile.position.velocity); - float denominator = (2 * speed.deltaY >= speed.deltaX) ? 2 * speed.deltaY : speed.deltaX; + const auto denominator = static_cast((2 * speed.deltaY >= speed.deltaX) ? 2 * speed.deltaY : speed.deltaX); auto incVelocity = missile.position.velocity * ((32 << 16) / denominator); auto traveled = missile.position.traveled - missile.position.velocity; // Adjust the traveled vector to start on the next smallest multiple of incVelocity @@ -526,7 +524,7 @@ bool MoveMissile(Missile &missile, tl::function_ref checkTile, bool // skip collision logic if the missile is on a corner between tiles if (pixelsTraveled.deltaY % 16 == 0 && pixelsTraveled.deltaX % 32 == 0 - && abs(pixelsTraveled.deltaY / 16) % 2 != abs(pixelsTraveled.deltaX / 32) % 2) { + && std::abs(pixelsTraveled.deltaY / 16) % 2 != std::abs(pixelsTraveled.deltaX / 32) % 2) { continue; } @@ -593,10 +591,19 @@ void MoveMissileAndCheckMissileCol(Missile &missile, DamageType damageType, int void SetMissAnim(Missile &missile, MissileGraphicID animtype) { - int dir = missile._mimfnum; + const int dir = missile._mimfnum; - if (animtype > MissileGraphicID::None) { - animtype = MissileGraphicID::None; + if (animtype >= MissileGraphicID::None) { + missile._miAnimType = MissileGraphicID::None; + missile._miAnimData = std::nullopt; + missile._miAnimWidth = 0; + missile._miAnimWidth2 = 0; + missile._miAnimFlags = MissileGraphicsFlags::None; + missile._miAnimDelay = 0; + missile._miAnimLen = 0; + missile._miAnimCnt = 0; + missile._miAnimFrame = 1; + return; } const MissileFileData &missileData = GetMissileSpriteData(animtype); @@ -671,7 +678,7 @@ bool GuardianTryFireAt(Missile &missile, Point target) return false; Player &player = Players[missile._misource]; - int dmg = GenerateRnd(10) + (player._pLevel / 2) + 1; + int dmg = GenerateRnd(10) + (player.getCharacterLevel() / 2) + 1; dmg = ScaleSpellEffect(dmg, missile._mispllvl); Direction dir = GetDirection(position, target); @@ -682,7 +689,7 @@ bool GuardianTryFireAt(Missile &missile, Point target) return true; } -bool GrowWall(int playerId, Point position, Point target, MissileID type, int spellLevel, int damage) +bool GrowWall(int id, Point position, Point target, MissileID type, int spellLevel, int damage) { int dp = dPiece[position.x][position.y]; assert(dp <= MAXTILES && dp >= 0); @@ -691,7 +698,7 @@ bool GrowWall(int playerId, Point position, Point target, MissileID type, int sp return false; } - AddMissile(position, position, Players[playerId]._pdir, type, TARGET_BOTH, playerId, damage, spellLevel); + AddMissile(position, position, Direction::South, type, TARGET_BOTH, id, damage, spellLevel); return true; } @@ -778,36 +785,33 @@ bool IsMissileBlockedByTile(Point tile) return object != nullptr && !object->_oMissFlag; } -void GetDamageAmt(SpellID i, int *mind, int *maxd) +DamageRange GetDamageAmt(SpellID spell, int spellLevel) { assert(MyPlayer != nullptr); - assert(i >= SpellID::FIRST && i <= SpellID::LAST); + assert(spell >= SpellID::FIRST && spell <= SpellID::LAST); Player &myPlayer = *MyPlayer; - const int sl = myPlayer.GetSpellLevel(i); - - switch (i) { - case SpellID::Firebolt: - *mind = (myPlayer._pMagic / 8) + sl + 1; - *maxd = *mind + 9; - break; + switch (spell) { + case SpellID::Firebolt: { + const int min = (myPlayer._pMagic / 8) + spellLevel + 1; + return { min, min + 9 }; + } case SpellID::Healing: case SpellID::HealOther: /// BUGFIX: healing calculation is unused - *mind = AddClassHealingBonus(myPlayer._pLevel + sl + 1, myPlayer._pClass) - 1; - *maxd = AddClassHealingBonus((4 * myPlayer._pLevel) + (6 * sl) + 10, myPlayer._pClass) - 1; - break; + return { + AddClassHealingBonus(myPlayer.getCharacterLevel() + spellLevel + 1, myPlayer._pClass) - 1, + AddClassHealingBonus((4 * myPlayer.getCharacterLevel()) + (6 * spellLevel) + 10, myPlayer._pClass) - 1 + }; case SpellID::RuneOfLight: case SpellID::Lightning: - *mind = 2; - *maxd = 2 + myPlayer._pLevel; - break; - case SpellID::Flash: - *mind = ScaleSpellEffect(myPlayer._pLevel, sl); - *mind += *mind / 2; - *maxd = *mind * 2; - break; + return { 2, 2 + myPlayer.getCharacterLevel() }; + case SpellID::Flash: { + int min = ScaleSpellEffect(myPlayer.getCharacterLevel(), spellLevel); + min += min / 2; + return { min, min * 2 }; + }; case SpellID::Identify: case SpellID::TownPortal: case SpellID::StoneCurse: @@ -831,74 +835,67 @@ void GetDamageAmt(SpellID i, int *mind, int *maxd) case SpellID::Berserk: case SpellID::Search: case SpellID::RuneOfStone: - *mind = -1; - *maxd = -1; - break; + return { -1, -1 }; case SpellID::FireWall: case SpellID::LightningWall: - case SpellID::RingOfFire: - *mind = 2 * myPlayer._pLevel + 4; - *maxd = *mind + 36; - break; + case SpellID::RingOfFire: { + const int min = 2 * myPlayer.getCharacterLevel() + 4; + return { min, min + 36 }; + } case SpellID::Fireball: case SpellID::RuneOfFire: { - int base = (2 * myPlayer._pLevel) + 4; - *mind = ScaleSpellEffect(base, sl); - *maxd = ScaleSpellEffect(base + 36, sl); + const int base = (2 * myPlayer.getCharacterLevel()) + 4; + return { + ScaleSpellEffect(base, spellLevel), + ScaleSpellEffect(base + 36, spellLevel) + }; } break; case SpellID::Guardian: { - int base = (myPlayer._pLevel / 2) + 1; - *mind = ScaleSpellEffect(base, sl); - *maxd = ScaleSpellEffect(base + 9, sl); + const int base = (myPlayer.getCharacterLevel() / 2) + 1; + return { + ScaleSpellEffect(base, spellLevel), + ScaleSpellEffect(base + 9, spellLevel) + }; } break; case SpellID::ChainLightning: - *mind = 4; - *maxd = 4 + (2 * myPlayer._pLevel); - break; - case SpellID::FlameWave: - *mind = 6 * (myPlayer._pLevel + 1); - *maxd = *mind + 54; - break; + return { 4, 4 + (2 * myPlayer.getCharacterLevel()) }; + case SpellID::FlameWave: { + const int min = 6 * (myPlayer.getCharacterLevel() + 1); + return { min, min + 54 }; + } case SpellID::Nova: case SpellID::Immolation: case SpellID::RuneOfImmolation: case SpellID::RuneOfNova: - *mind = ScaleSpellEffect((myPlayer._pLevel + 5) / 2, sl) * 5; - *maxd = ScaleSpellEffect((myPlayer._pLevel + 30) / 2, sl) * 5; - break; - case SpellID::Inferno: - *mind = 3; - *maxd = myPlayer._pLevel + 4; - *maxd += *maxd / 2; - break; + return { + ScaleSpellEffect((myPlayer.getCharacterLevel() + 5) / 2, spellLevel) * 5, + ScaleSpellEffect((myPlayer.getCharacterLevel() + 30) / 2, spellLevel) * 5 + }; + case SpellID::Inferno: { + int max = myPlayer.getCharacterLevel() + 4; + max += max / 2; + return { 3, max }; + } case SpellID::Golem: - *mind = 11; - *maxd = 17; - break; + return { 11, 17 }; case SpellID::Apocalypse: - *mind = myPlayer._pLevel; - *maxd = *mind * 6; - break; + return { myPlayer.getCharacterLevel(), myPlayer.getCharacterLevel() * 6 }; case SpellID::Elemental: - *mind = ScaleSpellEffect(2 * myPlayer._pLevel + 4, sl); - /// BUGFIX: add here '*mind /= 2;' - *maxd = ScaleSpellEffect(2 * myPlayer._pLevel + 40, sl); - /// BUGFIX: add here '*maxd /= 2;' - break; + /// BUGFIX: Divide min and max by 2 + return { + ScaleSpellEffect(2 * myPlayer.getCharacterLevel() + 4, spellLevel), + ScaleSpellEffect(2 * myPlayer.getCharacterLevel() + 40, spellLevel) + }; case SpellID::ChargedBolt: - *mind = 1; - *maxd = *mind + (myPlayer._pMagic / 4); - break; + return { 1, 1 + (myPlayer._pMagic / 4) }; case SpellID::HolyBolt: - *mind = myPlayer._pLevel + 9; - *maxd = *mind + 9; - break; - case SpellID::BloodStar: - *mind = (myPlayer._pMagic / 2) + 3 * sl - (myPlayer._pMagic / 8); - *maxd = *mind; - break; + return { myPlayer.getCharacterLevel() + 9, myPlayer.getCharacterLevel() + 18 }; + case SpellID::BloodStar: { + const int min = (myPlayer._pMagic / 2) + 3 * spellLevel - (myPlayer._pMagic / 8); + return { min, min }; + } default: - break; + return { -1, -1 }; } } @@ -947,7 +944,7 @@ bool MonsterTrapHit(int monsterId, int mindam, int maxdam, int dist, MissileID t int hit = GenerateRnd(100); int hper = 90 - monster.armorClass - dist; - hper = clamp(hper, 5, 95); + hper = std::clamp(hper, 5, 95); if (monster.tryLiftGargoyle()) return true; if (hit >= hper && monster.mode != MonsterMode::Petrified) { @@ -978,12 +975,10 @@ bool MonsterTrapHit(int monsterId, int mindam, int maxdam, int dist, MissileID t return true; } -bool PlayerMHit(int pnum, Monster *monster, int dist, int mind, int maxd, MissileID mtype, DamageType damageType, bool shift, DeathReason deathReason, bool *blocked) +bool PlayerMHit(Player &player, Monster *monster, int dist, int mind, int maxd, MissileID mtype, DamageType damageType, bool shift, DeathReason deathReason, bool *blocked) { *blocked = false; - Player &player = Players[pnum]; - if (player._pHitPoints >> 6 <= 0) { return false; } @@ -1008,14 +1003,14 @@ bool PlayerMHit(int pnum, Monster *monster, int dist, int mind, int maxd, Missil int tac = player.GetArmor(); if (monster != nullptr) { hper = monster->toHit - + ((monster->level(sgGameInitInfo.nDifficulty) - player._pLevel) * 2) + + ((monster->level(sgGameInitInfo.nDifficulty) - player.getCharacterLevel()) * 2) + 30 - (dist * 2) - tac; } else { hper = 100 - (tac / 2) - (dist * 2); } } else if (monster != nullptr) { - hper += (monster->level(sgGameInitInfo.nDifficulty) * 2) - (player._pLevel * 2) - (dist * 2); + hper += (monster->level(sgGameInitInfo.nDifficulty) * 2) - (player.getCharacterLevel() * 2) - (dist * 2); } int minhit = 10; @@ -1039,8 +1034,8 @@ bool PlayerMHit(int pnum, Monster *monster, int dist, int mind, int maxd, Missil int blkper = player.GetBlockChance(false); if (monster != nullptr) - blkper -= (monster->level(sgGameInitInfo.nDifficulty) - player._pLevel) * 2; - blkper = clamp(blkper, 0, 100); + blkper -= (monster->level(sgGameInitInfo.nDifficulty) - player.getCharacterLevel()) * 2; + blkper = std::clamp(blkper, 0, 100); int8_t resper; switch (damageType) { @@ -1162,8 +1157,8 @@ void InitMissiles() void AddOpenNest(Missile &missile, AddMissileParameter ¶meter) { - for (int x : { 80, 81 }) { - for (int y : { 62, 63 }) { + for (WorldTileCoord x : { 80, 81 }) { + for (WorldTileCoord y : { 62, 63 }) { AddMissile({ x, y }, { 80, 62 }, parameter.midir, MissileID::BigExplosion, missile._micaster, missile._misource, missile._midam, 0); } } @@ -1177,7 +1172,7 @@ void AddRuneOfFire(Missile &missile, AddMissileParameter ¶meter) void AddRuneOfLight(Missile &missile, AddMissileParameter ¶meter) { - int lvl = (missile.sourceType() == MissileSource::Player) ? missile.sourcePlayer()->_pLevel : 0; + int lvl = (missile.sourceType() == MissileSource::Player) ? missile.sourcePlayer()->getCharacterLevel() : 0; int dmg = 16 * (GenerateRndSum(10, 2) + lvl + 2); missile._midam = dmg; AddRune(missile, parameter.dst, MissileID::LightningWall); @@ -1207,7 +1202,7 @@ void AddReflect(Missile &missile, AddMissileParameter & /*parameter*/) Player &player = *missile.sourcePlayer(); - int add = (missile._mispllvl != 0 ? missile._mispllvl : 2) * player._pLevel; + int add = (missile._mispllvl != 0 ? missile._mispllvl : 2) * player.getCharacterLevel(); if (player.wReflections + add >= std::numeric_limits::max()) add = 0; player.wReflections += add; @@ -1229,7 +1224,7 @@ void AddBerserk(Missile &missile, AddMissileParameter ¶meter) return false; } - int monsterId = abs(dMonster[target.x][target.y]) - 1; + int monsterId = std::abs(dMonster[target.x][target.y]) - 1; if (monsterId < 0) return false; @@ -1252,7 +1247,7 @@ void AddBerserk(Missile &missile, AddMissileParameter ¶meter) parameter.dst, 0, 5); if (targetMonsterPosition) { - auto &monster = Monsters[abs(dMonster[targetMonsterPosition->x][targetMonsterPosition->y]) - 1]; + auto &monster = Monsters[std::abs(dMonster[targetMonsterPosition->x][targetMonsterPosition->y]) - 1]; Player &player = *missile.sourcePlayer(); const int slvl = player.GetSpellLevel(SpellID::Berserk); monster.flags |= MFLAG_BERSERK | MFLAG_GOLEM; @@ -1277,7 +1272,7 @@ void AddHorkSpawn(Missile &missile, AddMissileParameter ¶meter) void AddJester(Missile &missile, AddMissileParameter ¶meter) { MissileID spell = MissileID::Firebolt; - switch (GenerateRnd(10)) { + switch (RandomIntLessThan(10)) { case 0: case 1: spell = MissileID::Firebolt; @@ -1318,14 +1313,13 @@ void AddStealPotions(Missile &missile, AddMissileParameter & /*parameter*/) Point target = missile.position.start + displacement; if (!InDungeonBounds(target)) return false; - int8_t pnum = dPlayer[target.x][target.y]; - if (pnum == 0) + Player *player = PlayerAtPosition(target); + if (player == nullptr) return false; - Player &player = Players[abs(pnum) - 1]; bool hasPlayedSFX = false; for (int si = 0; si < MaxBeltItems; si++) { - Item &beltItem = player.SpdList[si]; + Item &beltItem = player->SpdList[si]; _item_indexes ii = IDI_NONE; if (beltItem._itype == ItemType::Misc) { if (FlipCoin()) @@ -1336,7 +1330,7 @@ void AddStealPotions(Missile &missile, AddMissileParameter & /*parameter*/) break; case IMISC_HEAL: case IMISC_MANA: - player.RemoveSpdBarItem(si); + player->RemoveSpdBarItem(si); break; case IMISC_FULLMANA: ii = ItemMiscIdIdx(IMISC_MANA); @@ -1368,7 +1362,7 @@ void AddStealPotions(Missile &missile, AddMissileParameter & /*parameter*/) beltItem._iStatFlag = true; } if (!hasPlayedSFX) { - PlaySfxLoc(IS_POPPOP2, target); + PlaySfxLoc(SfxID::PodPop, target); hasPlayedSFX = true; } } @@ -1388,13 +1382,13 @@ void AddStealMana(Missile &missile, AddMissileParameter & /*parameter*/) missile.position.start, 0, 2); if (trappedPlayerPosition) { - Player &player = Players[abs(dPlayer[trappedPlayerPosition->x][trappedPlayerPosition->y]) - 1]; + Player &player = Players[std::abs(dPlayer[trappedPlayerPosition->x][trappedPlayerPosition->y]) - 1]; player._pMana = 0; player._pManaBase = player._pMana + player._pMaxManaBase - player._pMaxMana; CalcPlrInv(player, false); RedrawComponent(PanelDrawComponent::Mana); - PlaySfxLoc(TSFX_COW7, *trappedPlayerPosition); + PlaySfxLoc(SfxID::Pig, *trappedPlayerPosition); } missile._miDelFlag = true; @@ -1408,9 +1402,9 @@ void AddSpectralArrow(Missile &missile, AddMissileParameter ¶meter) const Player &player = *missile.sourcePlayer(); if (player._pClass == HeroClass::Rogue) - av += (player._pLevel - 1) / 4; + av += (player.getCharacterLevel() - 1) / 4; else if (player._pClass == HeroClass::Warrior || player._pClass == HeroClass::Bard) - av += (player._pLevel - 1) / 8; + av += (player.getCharacterLevel() - 1) / 8; if (HasAnyOf(player._pIFlags, ItemSpecialEffect::QuickAttack)) av++; @@ -1506,7 +1500,7 @@ void AddWarp(Missile &missile, AddMissileParameter ¶meter) void AddLightningWall(Missile &missile, AddMissileParameter ¶meter) { UpdateMissileVelocity(missile, parameter.dst, 16); - missile._miAnimFrame = GenerateRnd(8) + 1; + missile._miAnimFrame = RandomIntBetween(1, 8); missile._mirange = 255 * (missile._mispllvl + 1); switch (missile.sourceType()) { case MissileSource::Trap: @@ -1527,7 +1521,7 @@ void AddLightningWall(Missile &missile, AddMissileParameter ¶meter) void AddBigExplosion(Missile &missile, AddMissileParameter & /*parameter*/) { if (missile.sourceType() == MissileSource::Player) { - int dmg = 2 * (missile.sourcePlayer()->_pLevel + GenerateRndSum(10, 2)) + 4; + int dmg = 2 * (missile.sourcePlayer()->getCharacterLevel() + GenerateRndSum(10, 2)) + 4; dmg = ScaleSpellEffect(dmg, missile._mispllvl); missile._midam = dmg; @@ -1543,7 +1537,7 @@ void AddBigExplosion(Missile &missile, AddMissileParameter & /*parameter*/) void AddImmolation(Missile &missile, AddMissileParameter ¶meter) { - Point dst = parameter.dst; + WorldTilePosition dst = parameter.dst; if (missile.position.start == parameter.dst) { dst += parameter.midir; } @@ -1559,12 +1553,12 @@ void AddImmolation(Missile &missile, AddMissileParameter ¶meter) void AddLightningBow(Missile &missile, AddMissileParameter ¶meter) { - Point dst = parameter.dst; + WorldTilePosition dst = parameter.dst; if (missile.position.start == parameter.dst) { dst += parameter.midir; } UpdateMissileVelocity(missile, dst, 32); - missile._miAnimFrame = GenerateRnd(8) + 1; + missile._miAnimFrame = RandomIntBetween(1, 8); missile._mirange = 255; if (missile._misource < 0) { missile.var1 = missile.position.start.x; @@ -1581,8 +1575,8 @@ void AddMana(Missile &missile, AddMissileParameter & /*parameter*/) Player &player = Players[missile._misource]; int manaAmount = (GenerateRnd(10) + 1) << 6; - for (int i = 0; i < player._pLevel; i++) { - manaAmount += (GenerateRnd(4) + 1) << 6; + for (int i = 0; i < player.getCharacterLevel(); i++) { + manaAmount += RandomIntBetween(1, 4) << 6; } for (int i = 0; i < missile._mispllvl; i++) { manaAmount += (GenerateRnd(6) + 1) << 6; @@ -1626,7 +1620,7 @@ void AddSearch(Missile &missile, AddMissileParameter & /*parameter*/) AutoMapShowItems = true; int lvl = 2; if (missile._misource >= 0) - lvl = player._pLevel * 2; + lvl = player.getCharacterLevel() * 2; missile._mirange = lvl + 10 * missile._mispllvl + 245; for (auto &other : Missiles) { @@ -1643,7 +1637,7 @@ void AddSearch(Missile &missile, AddMissileParameter & /*parameter*/) void AddChargedBoltBow(Missile &missile, AddMissileParameter ¶meter) { - Point dst = parameter.dst; + WorldTilePosition dst = parameter.dst; missile._mirnd = GenerateRnd(15) + 1; if (missile._micaster != TARGET_MONSTERS) { missile._midam = 15; @@ -1652,7 +1646,7 @@ void AddChargedBoltBow(Missile &missile, AddMissileParameter ¶meter) if (missile.position.start == dst) { dst += parameter.midir; } - missile._miAnimFrame = GenerateRnd(8) + 1; + missile._miAnimFrame = RandomIntBetween(1, 8); missile._mlid = AddLight(missile.position.start, 5); UpdateMissileVelocity(missile, dst, 8); missile.var1 = 5; @@ -1662,7 +1656,7 @@ void AddChargedBoltBow(Missile &missile, AddMissileParameter ¶meter) void AddElementalArrow(Missile &missile, AddMissileParameter ¶meter) { - Point dst = parameter.dst; + WorldTilePosition dst = parameter.dst; if (missile.position.start == dst) { dst += parameter.midir; } @@ -1670,9 +1664,9 @@ void AddElementalArrow(Missile &missile, AddMissileParameter ¶meter) if (missile._micaster == TARGET_MONSTERS) { const Player &player = Players[missile._misource]; if (player._pClass == HeroClass::Rogue) - av += (player._pLevel) / 4; + av += (player.getCharacterLevel()) / 4; else if (IsAnyOf(player._pClass, HeroClass::Warrior, HeroClass::Bard)) - av += (player._pLevel) / 8; + av += (player.getCharacterLevel()) / 8; if (gbIsHellfire) { if (HasAnyOf(player._pIFlags, ItemSpecialEffect::QuickAttack)) @@ -1699,7 +1693,7 @@ void AddElementalArrow(Missile &missile, AddMissileParameter ¶meter) void AddArrow(Missile &missile, AddMissileParameter ¶meter) { - Point dst = parameter.dst; + WorldTilePosition dst = parameter.dst; if (missile.position.start == dst) { dst += parameter.midir; } @@ -1708,12 +1702,12 @@ void AddArrow(Missile &missile, AddMissileParameter ¶meter) const Player &player = Players[missile._misource]; if (HasAnyOf(player._pIFlags, ItemSpecialEffect::RandomArrowVelocity)) { - av = GenerateRnd(32) + 16; + av = RandomIntBetween(16, 47); } if (player._pClass == HeroClass::Rogue) - av += (player._pLevel - 1) / 4; + av += (player.getCharacterLevel() - 1) / 4; else if (player._pClass == HeroClass::Warrior || player._pClass == HeroClass::Bard) - av += (player._pLevel - 1) / 8; + av += (player.getCharacterLevel() - 1) / 8; if (gbIsHellfire) { if (HasAnyOf(player._pIFlags, ItemSpecialEffect::QuickAttack)) @@ -1739,7 +1733,7 @@ void UpdateVileMissPos(Missile &missile, Point dst) for (int i = -k; i <= k; i++) { int xx = i + dst.x; if (PosOkPlayer(*MyPlayer, { xx, yy })) { - missile.position.tile = { xx, yy }; + missile.position.tile = WorldTilePosition(xx, yy); return; } } @@ -1787,7 +1781,7 @@ void AddPhasing(Missile &missile, AddMissileParameter ¶meter) void AddFirebolt(Missile &missile, AddMissileParameter ¶meter) { - Point dst = parameter.dst; + WorldTilePosition dst = parameter.dst; if (missile.position.start == dst) { dst += parameter.midir; } @@ -1872,9 +1866,9 @@ void AddTeleport(Missile &missile, AddMissileParameter ¶meter) void AddNovaBall(Missile &missile, AddMissileParameter ¶meter) { UpdateMissileVelocity(missile, parameter.dst, 16); - missile._miAnimFrame = GenerateRnd(8) + 1; + missile._miAnimFrame = RandomIntBetween(1, 8); missile._mirange = 255; - const Point position { missile._misource < 0 ? missile.position.start : Point(Players[missile._misource].position.tile) }; + const WorldTilePosition position = missile._misource < 0 ? missile.position.start : Players[missile._misource].position.tile; missile.var1 = position.x; missile.var2 = position.y; } @@ -1882,7 +1876,7 @@ void AddNovaBall(Missile &missile, AddMissileParameter ¶meter) void AddFireWall(Missile &missile, AddMissileParameter ¶meter) { missile._midam = GenerateRndSum(10, 2) + 2; - missile._midam += missile._misource >= 0 ? Players[missile._misource]._pLevel : currlevel; // BUGFIX: missing parenthesis around ternary (fixed) + missile._midam += missile._misource >= 0 ? Players[missile._misource].getCharacterLevel() : currlevel; // BUGFIX: missing parenthesis around ternary (fixed) missile._midam <<= 3; UpdateMissileVelocity(missile, parameter.dst, 16); int i = missile._mispllvl; @@ -1897,7 +1891,7 @@ void AddFireWall(Missile &missile, AddMissileParameter ¶meter) void AddFireball(Missile &missile, AddMissileParameter ¶meter) { - Point dst = parameter.dst; + WorldTilePosition dst = parameter.dst; if (missile.position.start == dst) { dst += parameter.midir; } @@ -1906,7 +1900,7 @@ void AddFireball(Missile &missile, AddMissileParameter ¶meter) sp += std::min(missile._mispllvl * 2, 34); Player &player = Players[missile._misource]; - int dmg = 2 * (player._pLevel + GenerateRndSum(10, 2)) + 4; + int dmg = 2 * (player.getCharacterLevel() + GenerateRndSum(10, 2)) + 4; missile._midam = ScaleSpellEffect(dmg, missile._mispllvl); } UpdateMissileVelocity(missile, dst, sp); @@ -1922,7 +1916,7 @@ void AddLightningControl(Missile &missile, AddMissileParameter ¶meter) missile.var1 = missile.position.start.x; missile.var2 = missile.position.start.y; UpdateMissileVelocity(missile, parameter.dst, 32); - missile._miAnimFrame = GenerateRnd(8) + 1; + missile._miAnimFrame = RandomIntBetween(1, 8); missile._mirange = 256; } @@ -1932,7 +1926,7 @@ void AddLightning(Missile &missile, AddMissileParameter ¶meter) SyncPositionWithParent(missile, parameter); - missile._miAnimFrame = GenerateRnd(8) + 1; + missile._miAnimFrame = RandomIntBetween(1, 8); if (missile._micaster == TARGET_PLAYERS || missile.IsTrap()) { if (missile.IsTrap() || Monsters[missile._misource].type().type == MT_FAMILIAR) @@ -2044,7 +2038,7 @@ void AddFlashBottom(Missile &missile, AddMissileParameter & /*parameter*/) switch (missile.sourceType()) { case MissileSource::Player: { Player &player = *missile.sourcePlayer(); - int dmg = GenerateRndSum(20, player._pLevel + 1) + player._pLevel + 1; + int dmg = GenerateRndSum(20, player.getCharacterLevel() + 1) + player.getCharacterLevel() + 1; missile._midam = ScaleSpellEffect(dmg, missile._mispllvl); missile._midam += missile._midam / 2; } break; @@ -2063,7 +2057,7 @@ void AddFlashTop(Missile &missile, AddMissileParameter & /*parameter*/) { if (missile._micaster == TARGET_MONSTERS) { if (!missile.IsTrap()) { - int dmg = Players[missile._misource]._pLevel + 1; + int dmg = Players[missile._misource].getCharacterLevel() + 1; dmg += GenerateRndSum(20, dmg); missile._midam = ScaleSpellEffect(dmg, missile._mispllvl); missile._midam += missile._midam / 2; @@ -2093,7 +2087,7 @@ void AddManaShield(Missile &missile, AddMissileParameter ¶meter) void AddFlameWave(Missile &missile, AddMissileParameter ¶meter) { - missile._midam = GenerateRnd(10) + Players[missile._misource]._pLevel + 1; + missile._midam = GenerateRnd(10) + Players[missile._misource].getCharacterLevel() + 1; UpdateMissileVelocity(missile, parameter.dst, 16); missile._mirange = 255; @@ -2141,7 +2135,7 @@ void AddGuardian(Missile &missile, AddMissileParameter ¶meter) missile.position.start = *spawnPosition; missile._mlid = AddLight(missile.position.tile, 1); - missile._mirange = missile._mispllvl + (player._pLevel / 2); + missile._mirange = missile._mispllvl + (player.getCharacterLevel() / 2); if (missile._mirange > 30) missile._mirange = 30; @@ -2203,7 +2197,7 @@ void AddRhino(Missile &missile, AddMissileParameter ¶meter) void AddGenericMagicMissile(Missile &missile, AddMissileParameter ¶meter) { - Point dst = parameter.dst; + WorldTilePosition dst = parameter.dst; if (missile.position.start == dst) { dst += parameter.midir; } @@ -2288,7 +2282,7 @@ void AddStoneCurse(Missile &missile, AddMissileParameter ¶meter) return false; } - int monsterId = abs(dMonster[target.x][target.y]) - 1; + int monsterId = std::abs(dMonster[target.x][target.y]) - 1; if (monsterId < 0) { return false; } @@ -2313,7 +2307,7 @@ void AddStoneCurse(Missile &missile, AddMissileParameter ¶meter) } // Petrify the targeted monster - int monsterId = abs(dMonster[targetMonsterPosition->x][targetMonsterPosition->y]) - 1; + int monsterId = std::abs(dMonster[targetMonsterPosition->x][targetMonsterPosition->y]) - 1; auto &monster = Monsters[monsterId]; if (monster.mode == MonsterMode::Petrified) { @@ -2372,7 +2366,7 @@ void AddHealing(Missile &missile, AddMissileParameter & /*parameter*/) Player &player = Players[missile._misource]; int hp = GenerateRnd(10) + 1; - hp += GenerateRndSum(4, player._pLevel) + player._pLevel; + hp += GenerateRndSum(4, player.getCharacterLevel()) + player.getCharacterLevel(); hp += GenerateRndSum(6, missile._mispllvl) + missile._mispllvl; hp <<= 6; @@ -2403,14 +2397,14 @@ void AddHealOther(Missile &missile, AddMissileParameter & /*parameter*/) void AddElemental(Missile &missile, AddMissileParameter ¶meter) { - Point dst = parameter.dst; + WorldTilePosition dst = parameter.dst; if (missile.position.start == dst) { dst += parameter.midir; } Player &player = Players[missile._misource]; - int dmg = 2 * (player._pLevel + GenerateRndSum(10, 2)) + 4; + int dmg = 2 * (player.getCharacterLevel() + GenerateRndSum(10, 2)) + 4; missile._midam = ScaleSpellEffect(dmg, missile._mispllvl) / 2; UpdateMissileVelocity(missile, dst, 16); @@ -2486,7 +2480,7 @@ void AddNova(Missile &missile, AddMissileParameter ¶meter) if (!missile.IsTrap()) { Player &player = Players[missile._misource]; - int dmg = GenerateRndSum(6, 5) + player._pLevel + 5; + int dmg = GenerateRndSum(6, 5) + player.getCharacterLevel() + 5; missile._midam = ScaleSpellEffect(dmg / 2, missile._mispllvl); } else { missile._midam = (currlevel / 2) + GenerateRndSum(3, 3); @@ -2499,17 +2493,17 @@ void AddRage(Missile &missile, AddMissileParameter ¶meter) { Player &player = Players[missile._misource]; - if (HasAnyOf(player._pSpellFlags, SpellFlag::RageActive | SpellFlag::RageCooldown) || player._pHitPoints <= player._pLevel << 6) { + if (HasAnyOf(player._pSpellFlags, SpellFlag::RageActive | SpellFlag::RageCooldown) || player._pHitPoints <= player.getCharacterLevel() << 6) { missile._miDelFlag = true; parameter.spellFizzled = true; return; } - int tmp = 3 * player._pLevel; + int tmp = 3 * player.getCharacterLevel(); tmp <<= 7; player._pSpellFlags |= SpellFlag::RageActive; missile.var2 = tmp; - int lvl = player._pLevel * 2; + int lvl = player.getCharacterLevel() * 2; missile._mirange = lvl + 10 * missile._mispllvl + 245; CalcPlrItemVals(player, true); RedrawEverything(); @@ -2576,7 +2570,7 @@ void AddApocalypse(Missile &missile, AddMissileParameter & /*parameter*/) missile.var4 = std::max(missile.position.start.x - 8, 1); missile.var5 = std::min(missile.position.start.x + 8, MAXDUNX - 1); missile.var6 = missile.var4; - int playerLevel = player._pLevel; + int playerLevel = player.getCharacterLevel(); missile._midam = GenerateRndSum(6, playerLevel) + playerLevel; missile._mirange = 255; } @@ -2591,7 +2585,7 @@ void AddInferno(Missile &missile, AddMissileParameter ¶meter) missile._mirange = missile.var2 + 20; missile._mlid = AddLight(missile.position.start, 1); if (missile._micaster == TARGET_MONSTERS) { - int i = GenerateRnd(Players[missile._misource]._pLevel) + GenerateRnd(2); + int i = GenerateRnd(Players[missile._misource].getCharacterLevel()) + GenerateRnd(2); missile._midam = 8 * i + 16 + ((8 * i + 16) / 2); } else { auto &monster = Monsters[missile._misource]; @@ -2601,7 +2595,7 @@ void AddInferno(Missile &missile, AddMissileParameter ¶meter) void AddInfernoControl(Missile &missile, AddMissileParameter ¶meter) { - Point dst = parameter.dst; + WorldTilePosition dst = parameter.dst; if (missile.position.start == parameter.dst) { dst += parameter.midir; } @@ -2613,14 +2607,14 @@ void AddInfernoControl(Missile &missile, AddMissileParameter ¶meter) void AddChargedBolt(Missile &missile, AddMissileParameter ¶meter) { - Point dst = parameter.dst; + WorldTilePosition dst = parameter.dst; missile._mirnd = GenerateRnd(15) + 1; missile._midam = (missile._micaster == TARGET_MONSTERS) ? (GenerateRnd(Players[missile._misource]._pMagic / 4) + 1) : 15; if (missile.position.start == dst) { dst += parameter.midir; } - missile._miAnimFrame = GenerateRnd(8) + 1; + missile._miAnimFrame = RandomIntBetween(1, 8); missile._mlid = AddLight(missile.position.start, 5); UpdateMissileVelocity(missile, dst, 8); @@ -2631,7 +2625,7 @@ void AddChargedBolt(Missile &missile, AddMissileParameter ¶meter) void AddHolyBolt(Missile &missile, AddMissileParameter ¶meter) { - Point dst = parameter.dst; + WorldTilePosition dst = parameter.dst; if (missile.position.start == dst) { dst += parameter.midir; } @@ -2648,7 +2642,7 @@ void AddHolyBolt(Missile &missile, AddMissileParameter ¶meter) missile.var1 = missile.position.start.x; missile.var2 = missile.position.start.y; missile._mlid = AddLight(missile.position.start, 8); - missile._midam = GenerateRnd(10) + player._pLevel + 9; + missile._midam = GenerateRnd(10) + player.getCharacterLevel() + 9; } void AddResurrect(Missile &missile, AddMissileParameter & /*parameter*/) @@ -2681,7 +2675,7 @@ void AddTelekinesis(Missile &missile, AddMissileParameter & /*parameter*/) void AddBoneSpirit(Missile &missile, AddMissileParameter ¶meter) { - Point dst = parameter.dst; + WorldTilePosition dst = parameter.dst; if (missile.position.start == dst) { dst += parameter.midir; } @@ -2715,9 +2709,9 @@ void AddDiabloApocalypse(Missile &missile, AddMissileParameter & /*parameter*/) missile._miDelFlag = true; } -Missile *AddMissile(Point src, Point dst, Direction midir, MissileID mitype, +Missile *AddMissile(WorldTilePosition src, WorldTilePosition dst, Direction midir, MissileID mitype, mienemy_type micaster, int id, int midam, int spllvl, - Missile *parent, std::optional<_sfx_id> lSFX) + Missile *parent, std::optional lSFX) { if (Missiles.size() >= Missiles.max_size()) { return nullptr; @@ -2757,7 +2751,7 @@ Missile *AddMissile(Point src, Point dst, Direction midir, MissileID mitype, lSFX = missileData.mlSFX; } - if (*lSFX != SFX_NONE) { + if (*lSFX != SfxID::None) { PlaySfxLoc(*lSFX, missile.position.start); } @@ -3086,10 +3080,7 @@ void ProcessHorkSpawn(Missile &missile) if (spawnPosition) { auto facing = static_cast(missile.var1); - Monster *monster = AddMonster(*spawnPosition, facing, 1, true); - if (monster != nullptr) { - M_StartStand(*monster, facing); - } + SpawnMonster(*spawnPosition, facing, 1); } } else { missile._midist++; @@ -3103,9 +3094,9 @@ void ProcessRune(Missile &missile) { Point position = missile.position.tile; int mid = dMonster[position.x][position.y]; - int pid = dPlayer[position.x][position.y]; - if (mid != 0 || pid != 0) { - Point targetPosition = mid != 0 ? Monsters[abs(mid) - 1].position.tile : Players[abs(pid) - 1].position.tile; + Player *player = PlayerAtPosition(position); + if (mid != 0 || player != nullptr) { + Point targetPosition = mid != 0 ? Monsters[std::abs(mid) - 1].position.tile : player->position.tile; Direction dir = GetDirection(position, targetPosition); missile._miDelFlag = true; @@ -3148,7 +3139,7 @@ void ProcessRingOfFire(Missile &missile) { missile._miDelFlag = true; int8_t src = missile._misource; - uint8_t lvl = missile._micaster == TARGET_MONSTERS ? Players[src]._pLevel : currlevel; + uint8_t lvl = missile._micaster == TARGET_MONSTERS ? Players[src].getCharacterLevel() : currlevel; int dmg = 16 * (GenerateRndSum(10, 2) + lvl + 2) / 2; if (missile.limitReached) @@ -3184,7 +3175,7 @@ void ProcessSearch(Missile &missile) const Player &player = Players[missile._misource]; missile._miDelFlag = true; - PlaySfxLoc(IS_CAST7, player.position.tile); + PlaySfxLoc(SfxID::SpellEnd, player.position.tile); if (&player == MyPlayer) AutoMapShowItems = false; } @@ -3198,7 +3189,7 @@ void ProcessLightningWallControl(Missile &missile) } int id = missile._misource; - int lvl = !missile.IsTrap() ? Players[id]._pLevel : 0; + int lvl = !missile.IsTrap() ? Players[id].getCharacterLevel() : 0; int dmg = 16 * (GenerateRndSum(10, 2) + lvl + 2); { @@ -3310,7 +3301,7 @@ void ProcessLightningControl(Missile &missile) dam = GenerateRnd(currlevel) + 2 * currlevel; } else if (missile._micaster == TARGET_MONSTERS) { // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. - dam = (GenerateRnd(2) + GenerateRnd(Players[missile._misource]._pLevel) + 2) << 6; + dam = (GenerateRnd(2) + GenerateRnd(Players[missile._misource].getCharacterLevel()) + 2) << 6; } else { auto &monster = Monsters[missile._misource]; dam = 2 * (monster.minDamage + GenerateRnd(monster.maxDamage - monster.minDamage + 1)); @@ -3650,7 +3641,7 @@ void ProcessTeleport(Missile &missile) player.position.old = player.position.tile; PlrDoTrans(player.position.tile); missile.var1 = 1; - dPlayer[player.position.tile.x][player.position.tile.y] = id + 1; + player.occupyTile(player.position.tile, false); if (leveltype != DTYPE_TOWN) { ChangeLightXY(player.lightId, player.position.tile); ChangeVisionXY(player.getId(), player.position.tile); @@ -3730,7 +3721,7 @@ void ProcessRhino(Missile &missile) monster.position.future = newPos; monster.position.old = newPos; monster.position.tile = newPos; - dMonster[newPos.x][newPos.y] = -(monst + 1); + monster.occupyTile(newPos, true); if (monster.isUnique()) ChangeLightXY(missile._mlid, newPos); MoveMissilePos(missile); @@ -3798,7 +3789,7 @@ void ProcessApocalypse(Missile &missile) continue; int id = missile._misource; - AddMissile({ k, j }, { k, j }, Players[id]._pdir, MissileID::ApocalypseBoom, TARGET_MONSTERS, id, missile._midam, 0); + AddMissile(WorldTilePosition(k, j), WorldTilePosition(k, j), Players[id]._pdir, MissileID::ApocalypseBoom, TARGET_MONSTERS, id, missile._midam, 0); missile.var2 = j; missile.var4 = k + 1; return; @@ -3866,7 +3857,7 @@ void ProcessRage(Missile &missile) if (HasAnyOf(player._pSpellFlags, SpellFlag::RageActive)) { player._pSpellFlags &= ~SpellFlag::RageActive; player._pSpellFlags |= SpellFlag::RageCooldown; - int lvl = player._pLevel * 2; + int lvl = player.getCharacterLevel() * 2; missile._mirange = lvl + 10 * missile._mispllvl + 245; } else { player._pSpellFlags &= ~SpellFlag::RageCooldown; diff --git a/Source/missiles.h b/Source/missiles.h index cec648b72b8..7ab4e9dee8e 100644 --- a/Source/missiles.h +++ b/Source/missiles.h @@ -7,34 +7,36 @@ #include #include +#include #include "engine.h" #include "engine/point.hpp" +#include "engine/world_tile.hpp" #include "misdat.h" #include "monster.h" #include "player.h" #include "spelldat.h" -#include "utils/stdcompat/optional.hpp" namespace devilution { constexpr WorldTilePosition GolemHoldingCell = Point { 1, 0 }; struct MissilePosition { - Point tile; /** Sprite's pixel offset from tile. */ Displacement offset; /** Pixel velocity while moving */ Displacement velocity; - /** Start position */ - Point start; - /** Start position */ + /** Pixels traveled as a numerator of 65,536. */ Displacement traveled; + WorldTilePosition tile; + /** Start position */ + WorldTilePosition start; + /** * @brief Specifies the location (tile) while rendering */ - Point tileForRendering; + WorldTilePosition tileForRendering; /** * @brief Specifies the location (offset) while rendering */ @@ -176,7 +178,11 @@ struct Missile { extern std::list Missiles; extern bool MissilePreFlag; -void GetDamageAmt(SpellID i, int *mind, int *maxd); +struct DamageRange { + int min; + int max; +}; +DamageRange GetDamageAmt(SpellID spell, int spellLevel); /** * @brief Returns the direction a vector from p1(x1, y1) to p2(x2, y2) is pointing to. @@ -199,7 +205,7 @@ void GetDamageAmt(SpellID i, int *mind, int *maxd); */ Direction16 GetDirection16(Point p1, Point p2); bool MonsterTrapHit(int monsterId, int mindam, int maxdam, int dist, MissileID t, DamageType damageType, bool shift); -bool PlayerMHit(int pnum, Monster *monster, int dist, int mind, int maxd, MissileID mtype, DamageType damageType, bool shift, DeathReason deathReason, bool *blocked); +bool PlayerMHit(Player &player, Monster *monster, int dist, int mind, int maxd, MissileID mtype, DamageType damageType, bool shift, DeathReason deathReason, bool *blocked); /** * @brief Could the missile collide with solid objects? (like walls or closed doors) @@ -236,7 +242,7 @@ inline void SetMissDir(Missile &missile, Direction16 dir) void InitMissiles(); struct AddMissileParameter { - Point dst; + WorldTilePosition dst; Direction midir; Missile *pParent; bool spellFizzled; @@ -386,9 +392,21 @@ void AddTelekinesis(Missile &missile, AddMissileParameter ¶meter); void AddBoneSpirit(Missile &missile, AddMissileParameter ¶meter); void AddRedPortal(Missile &missile, AddMissileParameter ¶meter); void AddDiabloApocalypse(Missile &missile, AddMissileParameter ¶meter); -Missile *AddMissile(Point src, Point dst, Direction midir, MissileID mitype, +Missile *AddMissile(WorldTilePosition src, WorldTilePosition dst, Direction midir, MissileID mitype, mienemy_type micaster, int id, int midam, int spllvl, - Missile *parent = nullptr, std::optional<_sfx_id> lSFX = std::nullopt); + Missile *parent = nullptr, std::optional lSFX = std::nullopt); +inline Missile *AddMissile(WorldTilePosition src, WorldTilePosition dst, Direction midir, MissileID mitype, + mienemy_type micaster, const Player &player, int midam, int spllvl, + Missile *parent = nullptr, std::optional lSFX = std::nullopt) +{ + return AddMissile(src, dst, midir, mitype, micaster, player.getId(), midam, spllvl, parent, lSFX); +} +inline Missile *AddMissile(WorldTilePosition src, WorldTilePosition dst, Direction midir, MissileID mitype, + mienemy_type micaster, const Monster &monster, int midam, int spllvl, + Missile *parent = nullptr, std::optional lSFX = std::nullopt) +{ + return AddMissile(src, dst, midir, mitype, micaster, static_cast(monster.getId()), midam, spllvl, parent, lSFX); +} void ProcessElementalArrow(Missile &missile); void ProcessArrow(Missile &missile); void ProcessGenericProjectile(Missile &missile); diff --git a/Source/monstdat.cpp b/Source/monstdat.cpp index 74d89344dd0..83689353f86 100644 --- a/Source/monstdat.cpp +++ b/Source/monstdat.cpp @@ -6,7 +6,12 @@ #include "monstdat.h" #include +#include +#include + +#include "data/file.hpp" +#include "data/record_reader.hpp" #include "items.h" #include "monster.h" #include "textdat.h" @@ -22,155 +27,20 @@ constexpr uint16_t Uniq(_unique_items item) return static_cast(T_UNIQ) + item; } +std::vector MonsterSpritePaths; + } // namespace +const char *MonsterData::spritePath() const +{ + return MonsterSpritePaths[static_cast(spriteId)].c_str(); +} + /** Contains the data related to each monster ID. */ -const MonsterData MonstersData[] = { - // clang-format off -// _monster_id name, assetsSuffix, soundSuffix, trnFile, availability, width, image, hasSpecial, hasSpecialSound, frames[6], rate[6], minDunLvl, maxDunLvl, level, hitPointsMinimum, hitPointsMaximum, ai, abilityFlags, intelligence, toHit, animFrameNum, minDamage, maxDamage, toHitSpecial, animFrameNumSpecial, minDamageSpecial, maxDamageSpecial, armorClass, monsterClass, resistance, resistanceHell, selectionType, treasure, exp +std::vector MonstersData; -// TRANSLATORS: Monster Block start -/* MT_NZOMBIE */ { P_("monster", "Zombie"), "zombie\\zombie", nullptr, nullptr, MonsterAvailability::Always, 128, 799, false, false, { 11, 24, 12, 6, 16, 0 }, { 4, 1, 1, 1, 1, 1 }, 1, 2, 1, 4, 7, MonsterAIID::Zombie, 0, 0, 10, 8, 2, 5, 0, 0, 0, 0, 5, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC, 3, 0, 54 }, -/* MT_BZOMBIE */ { P_("monster", "Ghoul"), "zombie\\zombie", nullptr, "zombie\\bluered", MonsterAvailability::Always, 128, 799, false, false, { 11, 24, 12, 6, 16, 0 }, { 4, 1, 1, 1, 1, 1 }, 2, 3, 2, 7, 11, MonsterAIID::Zombie, 0, 1, 10, 8, 3, 10, 0, 0, 0, 0, 10, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC, 3, 0, 58 }, -/* MT_GZOMBIE */ { P_("monster", "Rotting Carcass"), "zombie\\zombie", nullptr, "zombie\\grey", MonsterAvailability::Always, 128, 799, false, false, { 11, 24, 12, 6, 16, 0 }, { 4, 1, 1, 1, 1, 1 }, 2, 4, 4, 15, 25, MonsterAIID::Zombie, 0, 2, 25, 8, 5, 15, 0, 0, 0, 0, 15, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC | RESIST_FIRE, 3, 0, 136 }, -/* MT_YZOMBIE */ { P_("monster", "Black Death"), "zombie\\zombie", nullptr, "zombie\\yellow", MonsterAvailability::Always, 128, 799, false, false, { 11, 24, 12, 6, 16, 0 }, { 4, 1, 1, 1, 1, 1 }, 3, 5, 6, 25, 40, MonsterAIID::Zombie, 0, 3, 30, 8, 6, 22, 0, 0, 0, 0, 20, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC | RESIST_LIGHTNING, 3, 0, 240 }, -/* MT_RFALLSP */ { P_("monster", "Fallen One"), "falspear\\phall", nullptr, "falspear\\fallent", MonsterAvailability::Always, 128, 543, true, true, { 11, 11, 13, 11, 18, 13 }, { 3, 1, 1, 1, 1, 1 }, 1, 2, 1, 1, 4, MonsterAIID::Fallen, 0, 0, 15, 7, 1, 3, 0, 5, 0, 0, 0, MonsterClass::Animal, 0, 0, 3, 0, 46 }, -/* MT_DFALLSP */ { P_("monster", "Carver"), "falspear\\phall", nullptr, "falspear\\dark", MonsterAvailability::Always, 128, 543, true, true, { 11, 11, 13, 11, 18, 13 }, { 3, 1, 1, 1, 1, 1 }, 2, 3, 3, 4, 8, MonsterAIID::Fallen, 0, 2, 20, 7, 2, 5, 0, 5, 0, 0, 5, MonsterClass::Animal, 0, 0, 3, 0, 80 }, -/* MT_YFALLSP */ { P_("monster", "Devil Kin"), "falspear\\phall", nullptr, nullptr, MonsterAvailability::Always, 128, 543, true, true, { 11, 11, 13, 11, 18, 13 }, { 3, 1, 1, 1, 1, 1 }, 2, 4, 5, 12, 24, MonsterAIID::Fallen, 0, 2, 25, 7, 3, 7, 0, 5, 0, 0, 10, MonsterClass::Animal, 0, RESIST_FIRE, 3, 0, 155 }, -/* MT_BFALLSP */ { P_("monster", "Dark One"), "falspear\\phall", nullptr, "falspear\\blue", MonsterAvailability::Always, 128, 543, true, true, { 11, 11, 13, 11, 18, 13 }, { 3, 1, 1, 1, 1, 1 }, 3, 5, 7, 20, 36, MonsterAIID::Fallen, 0, 3, 30, 7, 4, 8, 0, 5, 0, 0, 15, MonsterClass::Animal, 0, RESIST_LIGHTNING, 3, 0, 255 }, -/* MT_WSKELAX */ { P_("monster", "Skeleton"), "skelaxe\\sklax", nullptr, "skelaxe\\white", MonsterAvailability::Always, 128, 553, true, false, { 12, 8, 13, 6, 17, 16 }, { 5, 1, 1, 1, 1, 1 }, 1, 2, 1, 2, 4, MonsterAIID::SkeletonMelee, 0, 0, 20, 8, 1, 4, 0, 0, 0, 0, 0, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC, 3, 0, 64 }, -/* MT_TSKELAX */ { P_("monster", "Corpse Axe"), "skelaxe\\sklax", nullptr, "skelaxe\\skelt", MonsterAvailability::Always, 128, 553, true, false, { 12, 8, 13, 6, 17, 16 }, { 4, 1, 1, 1, 1, 1 }, 2, 3, 2, 4, 7, MonsterAIID::SkeletonMelee, 0, 1, 25, 8, 3, 5, 0, 0, 0, 0, 0, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC, 3, 0, 68 }, -/* MT_RSKELAX */ { P_("monster", "Burning Dead"), "skelaxe\\sklax", nullptr, nullptr, MonsterAvailability::Always, 128, 553, true, false, { 12, 8, 13, 6, 17, 16 }, { 2, 1, 1, 1, 1, 1 }, 2, 4, 4, 8, 12, MonsterAIID::SkeletonMelee, 0, 2, 30, 8, 3, 7, 0, 0, 0, 0, 5, MonsterClass::Undead, IMMUNE_MAGIC | RESIST_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 3, 0, 154 }, -/* MT_XSKELAX */ { P_("monster", "Horror"), "skelaxe\\sklax", nullptr, "skelaxe\\black", MonsterAvailability::Always, 128, 553, true, false, { 12, 8, 13, 6, 17, 16 }, { 3, 1, 1, 1, 1, 1 }, 3, 5, 6, 12, 20, MonsterAIID::SkeletonMelee, 0, 3, 35, 8, 4, 9, 0, 0, 0, 0, 15, MonsterClass::Undead, IMMUNE_MAGIC | RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_LIGHTNING, 3, 0, 264 }, -/* MT_RFALLSD */ { P_("monster", "Fallen One"), "falsword\\fall", nullptr, "falsword\\fallent", MonsterAvailability::Always, 128, 623, true, true, { 12, 12, 13, 11, 14, 15 }, { 3, 1, 1, 1, 1, 1 }, 1, 2, 1, 2, 5, MonsterAIID::Fallen, 0, 0, 15, 8, 1, 4, 0, 5, 0, 0, 10, MonsterClass::Animal, 0, 0, 3, 0, 52 }, -/* MT_DFALLSD */ { P_("monster", "Carver"), "falsword\\fall", nullptr, "falsword\\dark", MonsterAvailability::Always, 128, 623, true, true, { 12, 12, 13, 11, 14, 15 }, { 3, 1, 1, 1, 1, 1 }, 2, 3, 3, 5, 9, MonsterAIID::Fallen, 0, 1, 20, 8, 2, 7, 0, 5, 0, 0, 15, MonsterClass::Animal, 0, 0, 3, 0, 90 }, -/* MT_YFALLSD */ { P_("monster", "Devil Kin"), "falsword\\fall", nullptr, nullptr, MonsterAvailability::Always, 128, 623, true, true, { 12, 12, 13, 11, 14, 15 }, { 3, 1, 1, 1, 1, 1 }, 2, 4, 5, 16, 24, MonsterAIID::Fallen, 0, 2, 25, 8, 4, 10, 0, 5, 0, 0, 20, MonsterClass::Animal, 0, RESIST_FIRE, 3, 0, 180 }, -/* MT_BFALLSD */ { P_("monster", "Dark One"), "falsword\\fall", nullptr, "falsword\\blue", MonsterAvailability::Always, 128, 623, true, true, { 12, 12, 13, 11, 14, 15 }, { 3, 1, 1, 1, 1, 1 }, 3, 5, 7, 24, 36, MonsterAIID::Fallen, 0, 3, 30, 8, 4, 12, 0, 5, 0, 0, 25, MonsterClass::Animal, 0, RESIST_LIGHTNING, 3, 0, 280 }, -/* MT_NSCAV */ { P_("monster", "Scavenger"), "scav\\scav", nullptr, nullptr, MonsterAvailability::Always, 128, 410, true, false, { 12, 8, 12, 6, 20, 11 }, { 2, 1, 1, 1, 1, 1 }, 1, 3, 2, 3, 6, MonsterAIID::Scavenger, 0, 0, 20, 7, 1, 5, 0, 0, 0, 0, 10, MonsterClass::Animal, 0, RESIST_FIRE, 3, 0, 80 }, -/* MT_BSCAV */ { P_("monster", "Plague Eater"), "scav\\scav", nullptr, "scav\\scavbr", MonsterAvailability::Always, 128, 410, true, false, { 12, 8, 12, 6, 20, 11 }, { 2, 1, 1, 1, 1, 1 }, 2, 4, 4, 12, 24, MonsterAIID::Scavenger, 0, 1, 30, 7, 1, 8, 0, 0, 0, 0, 20, MonsterClass::Animal, 0, RESIST_LIGHTNING, 3, 0, 188 }, -/* MT_WSCAV */ { P_("monster", "Shadow Beast"), "scav\\scav", nullptr, "scav\\scavbe", MonsterAvailability::Always, 128, 410, true, false, { 12, 8, 12, 6, 20, 11 }, { 2, 1, 1, 1, 1, 1 }, 3, 5, 6, 24, 36, MonsterAIID::Scavenger, 0, 2, 35, 7, 3, 12, 0, 0, 0, 0, 25, MonsterClass::Animal, 0, RESIST_FIRE, 3, 0, 375 }, -/* MT_YSCAV */ { P_("monster", "Bone Gasher"), "scav\\scav", nullptr, "scav\\scavw", MonsterAvailability::Always, 128, 410, true, false, { 12, 8, 12, 6, 20, 11 }, { 2, 1, 1, 1, 1, 1 }, 4, 6, 8, 28, 40, MonsterAIID::Scavenger, 0, 3, 35, 7, 5, 15, 0, 0, 0, 0, 30, MonsterClass::Animal, RESIST_MAGIC, RESIST_LIGHTNING, 3, 0, 552 }, -/* MT_WSKELBW */ { P_("monster", "Skeleton"), "skelbow\\sklbw", nullptr, "skelbow\\white", MonsterAvailability::Always, 128, 567, true, false, { 9, 8, 16, 5, 16, 16 }, { 4, 1, 1, 1, 1, 1 }, 2, 3, 3, 2, 4, MonsterAIID::SkeletonRanged, 0, 0, 15, 12, 1, 2, 0, 0, 0, 0, 0, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC, 3, 0, 110 }, -/* MT_TSKELBW */ { P_("monster", "Corpse Bow"), "skelbow\\sklbw", nullptr, "skelbow\\skelt", MonsterAvailability::Always, 128, 567, true, false, { 9, 8, 16, 5, 16, 16 }, { 4, 1, 1, 1, 1, 1 }, 2, 4, 5, 8, 16, MonsterAIID::SkeletonRanged, 0, 1, 25, 12, 1, 4, 0, 0, 0, 0, 0, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC, 3, 0, 210 }, -/* MT_RSKELBW */ { P_("monster", "Burning Dead"), "skelbow\\sklbw", nullptr, nullptr, MonsterAvailability::Always, 128, 567, true, false, { 9, 8, 16, 5, 16, 16 }, { 2, 1, 1, 1, 1, 1 }, 3, 5, 7, 10, 24, MonsterAIID::SkeletonRanged, 0, 2, 30, 12, 1, 6, 0, 0, 0, 0, 5, MonsterClass::Undead, IMMUNE_MAGIC | RESIST_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 3, 0, 364 }, -/* MT_XSKELBW */ { P_("monster", "Horror"), "skelbow\\sklbw", nullptr, "skelbow\\black", MonsterAvailability::Always, 128, 567, true, false, { 9, 8, 16, 5, 16, 16 }, { 3, 1, 1, 1, 1, 1 }, 4, 6, 9, 15, 45, MonsterAIID::SkeletonRanged, 0, 3, 35, 12, 2, 9, 0, 0, 0, 0, 15, MonsterClass::Undead, IMMUNE_MAGIC | RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_LIGHTNING, 3, 0, 594 }, -/* MT_WSKELSD */ { P_("monster", "Skeleton Captain"), "skelsd\\sklsr", nullptr, "skelsd\\white", MonsterAvailability::Always, 128, 575, true, true, { 13, 8, 12, 7, 15, 16 }, { 4, 1, 1, 1, 1, 1 }, 1, 3, 2, 3, 6, MonsterAIID::SkeletonMelee, 0, 0, 20, 8, 2, 7, 0, 0, 0, 0, 10, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC, 3, 0, 90 }, -/* MT_TSKELSD */ { P_("monster", "Corpse Captain"), "skelsd\\sklsr", nullptr, "skelsd\\skelt", MonsterAvailability::Always, 128, 575, true, false, { 13, 8, 12, 7, 15, 16 }, { 4, 1, 1, 1, 1, 1 }, 2, 4, 4, 12, 20, MonsterAIID::SkeletonMelee, 0, 1, 30, 8, 3, 9, 0, 0, 0, 0, 5, MonsterClass::Undead, IMMUNE_MAGIC, IMMUNE_MAGIC, 3, 0, 200 }, -/* MT_RSKELSD */ { P_("monster", "Burning Dead Captain"), "skelsd\\sklsr", nullptr, nullptr, MonsterAvailability::Always, 128, 575, true, false, { 13, 8, 12, 7, 15, 16 }, { 4, 1, 1, 1, 1, 1 }, 3, 5, 6, 16, 30, MonsterAIID::SkeletonMelee, 0, 2, 35, 8, 4, 10, 0, 0, 0, 0, 15, MonsterClass::Undead, IMMUNE_MAGIC | RESIST_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 3, 0, 393 }, -/* MT_XSKELSD */ { P_("monster", "Horror Captain"), "skelsd\\sklsr", nullptr, "skelsd\\black", MonsterAvailability::Always, 128, 575, true, false, { 13, 8, 12, 7, 15, 16 }, { 4, 1, 1, 1, 1, 1 }, 4, 6, 8, 35, 50, MonsterAIID::SkeletonMelee, MFLAG_SEARCH, 3, 40, 8, 5, 14, 0, 0, 0, 0, 30, MonsterClass::Undead, IMMUNE_MAGIC | RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_LIGHTNING, 3, 0, 604 }, -/* MT_INVILORD*/ { P_("monster", "Invisible Lord"), "tsneak\\tsneak", nullptr, nullptr, MonsterAvailability::Never, 128, 800, false, false, { 13, 13, 15, 11, 16, 0 }, { 2, 1, 1, 1, 1, 1 }, 19, 20, 14, 278, 278, MonsterAIID::SkeletonMelee, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 65, 8, 16, 30, 0, 0, 0, 0, 60, MonsterClass::Demon, RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, 3, 0, 2000 }, -/* MT_SNEAK */ { P_("monster", "Hidden"), "sneak\\sneak", nullptr, nullptr, MonsterAvailability::Retail, 128, 992, true, false, { 16, 8, 12, 8, 24, 15 }, { 2, 1, 1, 1, 1, 1 }, 2, 5, 5, 8, 24, MonsterAIID::Sneak, MFLAG_HIDDEN, 0, 35, 8, 3, 6, 0, 0, 0, 0, 25, MonsterClass::Demon, 0, 0, 3, 0, 278 }, -/* MT_STALKER */ { P_("monster", "Stalker"), "sneak\\sneak", nullptr, "sneak\\sneakv2", MonsterAvailability::Retail, 128, 992, true, false, { 16, 8, 12, 8, 24, 15 }, { 2, 1, 1, 1, 1, 1 }, 5, 7, 9, 30, 45, MonsterAIID::Sneak, MFLAG_HIDDEN | MFLAG_SEARCH, 1, 40, 8, 8, 16, 0, 0, 0, 0, 30, MonsterClass::Demon, 0, 0, 3, 0, 630 }, -/* MT_UNSEEN */ { P_("monster", "Unseen"), "sneak\\sneak", nullptr, "sneak\\sneakv3", MonsterAvailability::Retail, 128, 992, true, false, { 16, 8, 12, 8, 24, 15 }, { 2, 1, 1, 1, 1, 1 }, 6, 8, 11, 35, 50, MonsterAIID::Sneak, MFLAG_HIDDEN | MFLAG_SEARCH, 2, 45, 8, 12, 20, 0, 0, 0, 0, 30, MonsterClass::Demon, RESIST_MAGIC, IMMUNE_MAGIC, 3, 0, 935 }, -/* MT_ILLWEAV */ { P_("monster", "Illusion Weaver"), "sneak\\sneak", nullptr, "sneak\\sneakv1", MonsterAvailability::Retail, 128, 992, true, false, { 16, 8, 12, 8, 24, 15 }, { 2, 1, 1, 1, 1, 1 }, 8, 10, 13, 40, 60, MonsterAIID::Sneak, MFLAG_HIDDEN | MFLAG_SEARCH, 3, 60, 8, 16, 24, 0, 0, 0, 0, 30, MonsterClass::Demon, RESIST_MAGIC | RESIST_FIRE, IMMUNE_MAGIC | RESIST_FIRE, 3, 0, 1500 }, -/* MT_LRDSAYTR*/ { P_("monster", "Satyr Lord"), "goatlord\\goatl", "newsfx\\satyr", nullptr, MonsterAvailability::Retail, 160, 800, false, false, { 13, 13, 14, 9, 16, 0 }, { 2, 1, 1, 1, 1, 1 }, 21, 22, 28, 160, 200, MonsterAIID::SkeletonMelee, MFLAG_SEARCH, 3, 90, 8, 20, 30, 0, 0, 0, 0, 70, MonsterClass::Animal, RESIST_FIRE | RESIST_LIGHTNING, RESIST_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 3, 0, 2800 }, -/* MT_NGOATMC */ { P_("monster", "Flesh Clan"), "goatmace\\goat", nullptr, nullptr, MonsterAvailability::Retail, 128, 1030, true, false, { 12, 8, 12, 6, 20, 12 }, { 2, 1, 1, 1, 1, 1 }, 4, 6, 8, 30, 45, MonsterAIID::GoatMelee, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 0, 50, 8, 4, 10, 0, 0, 0, 0, 40, MonsterClass::Demon, 0, 0, 3, 0, 460 }, -/* MT_BGOATMC */ { P_("monster", "Stone Clan"), "goatmace\\goat", nullptr, "goatmace\\beige", MonsterAvailability::Retail, 128, 1030, true, false, { 12, 8, 12, 6, 20, 12 }, { 2, 1, 1, 1, 1, 1 }, 5, 7, 10, 40, 55, MonsterAIID::GoatMelee, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 1, 60, 8, 6, 12, 0, 0, 0, 0, 40, MonsterClass::Demon, RESIST_MAGIC, IMMUNE_MAGIC, 3, 0, 685 }, -/* MT_RGOATMC */ { P_("monster", "Fire Clan"), "goatmace\\goat", nullptr, "goatmace\\red", MonsterAvailability::Retail, 128, 1030, true, false, { 12, 8, 12, 6, 20, 12 }, { 2, 1, 1, 1, 1, 1 }, 6, 8, 12, 50, 65, MonsterAIID::GoatMelee, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 2, 70, 8, 8, 16, 0, 0, 0, 0, 45, MonsterClass::Demon, RESIST_FIRE, IMMUNE_FIRE, 3, 0, 906 }, -/* MT_GGOATMC */ { P_("monster", "Night Clan"), "goatmace\\goat", nullptr, "goatmace\\gray", MonsterAvailability::Retail, 128, 1030, true, false, { 12, 8, 12, 6, 20, 12 }, { 2, 1, 1, 1, 1, 1 }, 7, 9, 14, 55, 70, MonsterAIID::GoatMelee, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 80, 8, 10, 20, 15, 0, 30, 30, 50, MonsterClass::Demon, RESIST_MAGIC, IMMUNE_MAGIC, 3, 0, 1190 }, -/* MT_FIEND */ { P_("monster", "Fiend"), "bat\\bat", nullptr, "bat\\red", MonsterAvailability::Always, 96, 364, false, false, { 9, 13, 10, 9, 13, 0 }, { 1, 1, 1, 1, 1, 1 }, 2, 3, 3, 3, 6, MonsterAIID::Bat, 0, 0, 35, 5, 1, 6, 0, 0, 0, 0, 0, MonsterClass::Animal, 0, 0, 6, T_NODROP, 102 }, -/* MT_BLINK */ { P_("monster", "Blink"), "bat\\bat", nullptr, nullptr, MonsterAvailability::Always, 96, 364, false, false, { 9, 13, 10, 9, 13, 0 }, { 1, 1, 1, 1, 1, 1 }, 3, 5, 7, 12, 28, MonsterAIID::Bat, 0, 1, 45, 5, 1, 8, 0, 0, 0, 0, 15, MonsterClass::Animal, 0, 0, 6, T_NODROP, 340 }, -/* MT_GLOOM */ { P_("monster", "Gloom"), "bat\\bat", nullptr, "bat\\grey", MonsterAvailability::Always, 96, 364, false, false, { 9, 13, 10, 9, 13, 0 }, { 1, 1, 1, 1, 1, 1 }, 4, 6, 9, 28, 36, MonsterAIID::Bat, MFLAG_SEARCH, 2, 70, 5, 4, 12, 0, 0, 0, 0, 35, MonsterClass::Animal, RESIST_MAGIC, RESIST_MAGIC, 6, T_NODROP, 509 }, -/* MT_FAMILIAR*/ { P_("monster", "Familiar"), "bat\\bat", nullptr, "bat\\orange", MonsterAvailability::Always, 96, 364, false, false, { 9, 13, 10, 9, 13, 0 }, { 1, 1, 1, 1, 1, 1 }, 6, 8, 13, 20, 35, MonsterAIID::Bat, MFLAG_SEARCH, 3, 50, 5, 4, 16, 0, 0, 0, 0, 35, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_LIGHTNING, RESIST_MAGIC | IMMUNE_LIGHTNING, 6, T_NODROP, 448 }, -/* MT_NGOATBW */ { P_("monster", "Flesh Clan"), "goatbow\\goatb", nullptr, nullptr, MonsterAvailability::Retail, 128, 1040, false, false, { 12, 8, 16, 6, 20, 0 }, { 3, 1, 1, 1, 1, 1 }, 4, 6, 8, 20, 35, MonsterAIID::GoatRanged, MFLAG_CAN_OPEN_DOOR, 0, 35, 13, 1, 7, 0, 0, 0, 0, 35, MonsterClass::Demon, 0, 0, 3, 0, 448 }, -/* MT_BGOATBW */ { P_("monster", "Stone Clan"), "goatbow\\goatb", nullptr, "goatbow\\beige", MonsterAvailability::Retail, 128, 1040, false, false, { 12, 8, 16, 6, 20, 0 }, { 3, 1, 1, 1, 1, 1 }, 5, 7, 10, 30, 40, MonsterAIID::GoatRanged, MFLAG_CAN_OPEN_DOOR, 1, 40, 13, 2, 9, 0, 0, 0, 0, 35, MonsterClass::Demon, RESIST_MAGIC, IMMUNE_MAGIC, 3, 0, 645 }, -/* MT_RGOATBW */ { P_("monster", "Fire Clan"), "goatbow\\goatb", nullptr, "goatbow\\red", MonsterAvailability::Retail, 128, 1040, false, false, { 12, 8, 16, 6, 20, 0 }, { 3, 1, 1, 1, 1, 1 }, 6, 8, 12, 40, 50, MonsterAIID::GoatRanged, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 2, 45, 13, 3, 11, 0, 0, 0, 0, 35, MonsterClass::Demon, RESIST_FIRE, IMMUNE_FIRE, 3, 0, 822 }, -/* MT_GGOATBW */ { P_("monster", "Night Clan"), "goatbow\\goatb", nullptr, "goatbow\\gray", MonsterAvailability::Retail, 128, 1040, false, false, { 12, 8, 16, 6, 20, 0 }, { 3, 1, 1, 1, 1, 1 }, 7, 9, 14, 50, 65, MonsterAIID::GoatRanged, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 50, 13, 4, 13, 15, 0, 0, 0, 40, MonsterClass::Demon, RESIST_MAGIC, IMMUNE_MAGIC, 3, 0, 1092 }, -/* MT_NACID */ { P_("monster", "Acid Beast"), "acid\\acid", nullptr, nullptr, MonsterAvailability::Retail, 128, 716, true, true, { 13, 8, 12, 8, 16, 12 }, { 1, 1, 1, 1, 1, 1 }, 6, 8, 11, 40, 66, MonsterAIID::Acid, 0, 0, 40, 8, 4, 12, 25, 8, 0, 0, 30, MonsterClass::Animal, IMMUNE_ACID, IMMUNE_MAGIC | IMMUNE_ACID, 3, 0, 846 }, -/* MT_RACID */ { P_("monster", "Poison Spitter"), "acid\\acid", nullptr, "acid\\acidblk", MonsterAvailability::Retail, 128, 716, true, true, { 13, 8, 12, 8, 16, 12 }, { 1, 1, 1, 1, 1, 1 }, 8, 10, 15, 60, 85, MonsterAIID::Acid, 0, 1, 45, 8, 4, 16, 25, 8, 0, 0, 30, MonsterClass::Animal, IMMUNE_ACID, IMMUNE_MAGIC | IMMUNE_ACID, 3, 0, 1248 }, -/* MT_BACID */ { P_("monster", "Pit Beast"), "acid\\acid", nullptr, "acid\\acidb", MonsterAvailability::Retail, 128, 716, true, true, { 13, 8, 12, 8, 16, 12 }, { 1, 1, 1, 1, 1, 1 }, 10, 12, 21, 80, 110, MonsterAIID::Acid, 0, 2, 55, 8, 8, 18, 35, 8, 0, 0, 35, MonsterClass::Animal, RESIST_MAGIC | IMMUNE_ACID, IMMUNE_MAGIC | RESIST_LIGHTNING | IMMUNE_ACID, 3, 0, 2060 }, -/* MT_XACID */ { P_("monster", "Lava Maw"), "acid\\acid", nullptr, "acid\\acidr", MonsterAvailability::Retail, 128, 716, true, true, { 13, 8, 12, 8, 16, 12 }, { 1, 1, 1, 1, 1, 1 }, 12, 14, 25, 100, 150, MonsterAIID::Acid, 0, 3, 65, 8, 10, 20, 40, 8, 0, 0, 35, MonsterClass::Animal, RESIST_MAGIC | IMMUNE_FIRE | IMMUNE_ACID, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_ACID, 3, 0, 2940 }, -/* MT_SKING */ { P_("monster", "Skeleton King"), "sking\\sking", nullptr, "skelaxe\\white", MonsterAvailability::Never, 160, 1010, true, true, { 8, 6, 16, 6, 16, 6 }, { 2, 1, 1, 1, 1, 2 }, 4, 4, 9, 140, 140, MonsterAIID::SkeletonKing, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 60, 8, 6, 16, 0, 0, 0, 0, 70, MonsterClass::Undead, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 7, Uniq(UITEM_SKCROWN), 570 }, -/* MT_CLEAVER */ { P_("monster", "The Butcher"), "fatc\\fatc", nullptr, nullptr, MonsterAvailability::Never, 128, 980, false, false, { 10, 8, 12, 6, 16, 0 }, { 1, 1, 1, 1, 1, 1 }, 1, 1, 1, 320, 320, MonsterAIID::Butcher, 0, 3, 50, 8, 6, 12, 0, 0, 0, 0, 50, MonsterClass::Demon, RESIST_FIRE | RESIST_LIGHTNING, RESIST_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 3, Uniq(UITEM_CLEAVER), 710 }, -/* MT_FAT */ { P_("monster", "Overlord"), "fat\\fat", nullptr, nullptr, MonsterAvailability::Retail, 128, 1130, true, false, { 8, 10, 15, 6, 16, 10 }, { 4, 1, 1, 1, 1, 1 }, 5, 7, 10, 60, 80, MonsterAIID::Fat, 0, 0, 55, 8, 6, 12, 0, 0, 0, 0, 55, MonsterClass::Demon, 0, RESIST_FIRE, 3, 0, 635 }, -/* MT_MUDMAN, */ { P_("monster", "Mud Man"), "fat\\fat", nullptr, "fat\\blue", MonsterAvailability::Retail, 128, 1130, true, false, { 8, 10, 15, 6, 16, 10 }, { 4, 1, 1, 1, 1, 1 }, 7, 9, 14, 100, 125, MonsterAIID::Fat, MFLAG_SEARCH, 1, 60, 8, 8, 16, 0, 0, 0, 0, 60, MonsterClass::Demon, 0, IMMUNE_LIGHTNING, 3, 0, 1165 }, -/* MT_TOAD */ { P_("monster", "Toad Demon"), "fat\\fat", nullptr, "fat\\fatb", MonsterAvailability::Retail, 128, 1130, true, false, { 8, 10, 15, 6, 16, 10 }, { 4, 1, 1, 1, 1, 1 }, 8, 10, 16, 135, 160, MonsterAIID::Fat, MFLAG_SEARCH, 2, 70, 8, 8, 16, 40, 0, 8, 20, 65, MonsterClass::Demon, IMMUNE_MAGIC, IMMUNE_MAGIC | RESIST_LIGHTNING, 3, 0, 1380 }, -/* MT_FLAYED */ { P_("monster", "Flayed One"), "fat\\fat", nullptr, "fat\\fatf", MonsterAvailability::Retail, 128, 1130, true, false, { 8, 10, 15, 6, 16, 10 }, { 4, 1, 1, 1, 1, 1 }, 10, 12, 20, 160, 200, MonsterAIID::Fat, MFLAG_SEARCH, 3, 85, 8, 10, 20, 0, 0, 0, 0, 70, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 3, 0, 2058 }, -/* MT_WYRM */ { P_("monster", "Wyrm"), "worm\\worm", nullptr, nullptr, MonsterAvailability::Never, 160, 2420, false, false, { 13, 13, 13, 11, 19, 0 }, { 1, 1, 1, 1, 1, 1 }, 5, 7, 11, 60, 90, MonsterAIID::SkeletonMelee, 0, 0, 40, 8, 4, 10, 0, 0, 0, 0, 25, MonsterClass::Animal, RESIST_MAGIC, RESIST_MAGIC, 3, 0, 660 }, -/* MT_CAVSLUG */ { P_("monster", "Cave Slug"), "worm\\worm", nullptr, nullptr, MonsterAvailability::Never, 160, 2420, false, false, { 13, 13, 13, 11, 19, 0 }, { 1, 1, 1, 1, 1, 1 }, 6, 8, 13, 75, 110, MonsterAIID::SkeletonMelee, 0, 1, 50, 8, 6, 13, 0, 0, 0, 0, 30, MonsterClass::Animal, RESIST_MAGIC, RESIST_MAGIC, 3, 0, 994 }, -/* MT_DVLWYRM */ { P_("monster", "Devil Wyrm"), "worm\\worm", nullptr, nullptr, MonsterAvailability::Never, 160, 2420, false, false, { 13, 13, 13, 11, 19, 0 }, { 1, 1, 1, 1, 1, 1 }, 7, 9, 15, 100, 140, MonsterAIID::SkeletonMelee, 0, 2, 55, 8, 8, 16, 0, 0, 0, 0, 30, MonsterClass::Animal, RESIST_MAGIC | RESIST_FIRE, RESIST_MAGIC | RESIST_FIRE, 3, 0, 1320 }, -/* MT_DEVOUR */ { P_("monster", "Devourer"), "worm\\worm", nullptr, nullptr, MonsterAvailability::Never, 160, 2420, false, false, { 13, 13, 13, 11, 19, 0 }, { 1, 1, 1, 1, 1, 1 }, 8, 10, 17, 125, 200, MonsterAIID::SkeletonMelee, 0, 3, 60, 8, 10, 20, 0, 0, 0, 0, 35, MonsterClass::Animal, RESIST_MAGIC | RESIST_FIRE, RESIST_MAGIC | RESIST_FIRE, 3, 0, 1827 }, -/* MT_NMAGMA */ { P_("monster", "Magma Demon"), "magma\\magma", nullptr, nullptr, MonsterAvailability::Retail, 128, 1680, true, true, { 8, 10, 14, 7, 18, 18 }, { 2, 1, 1, 1, 1, 1 }, 8, 9, 13, 50, 70, MonsterAIID::Magma, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 0, 45, 4, 2, 10, 50, 13, 0, 0, 45, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 7, 0, 1076 }, -/* MT_YMAGMA */ { P_("monster", "Blood Stone"), "magma\\magma", nullptr, "magma\\yellow", MonsterAvailability::Retail, 128, 1680, true, true, { 8, 10, 14, 7, 18, 18 }, { 2, 1, 1, 1, 1, 1 }, 8, 10, 14, 55, 75, MonsterAIID::Magma, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 1, 50, 4, 2, 12, 50, 14, 0, 0, 45, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 7, 0, 1309 }, -/* MT_BMAGMA */ { P_("monster", "Hell Stone"), "magma\\magma", nullptr, "magma\\blue", MonsterAvailability::Retail, 128, 1680, true, true, { 8, 10, 14, 7, 18, 18 }, { 2, 1, 1, 1, 1, 1 }, 9, 11, 16, 60, 80, MonsterAIID::Magma, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 2, 60, 4, 2, 20, 60, 14, 0, 0, 50, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 7, 0, 1680 }, -/* MT_WMAGMA */ { P_("monster", "Lava Lord"), "magma\\magma", nullptr, "magma\\wierd", MonsterAvailability::Retail, 128, 1680, true, true, { 8, 10, 14, 7, 18, 18 }, { 2, 1, 1, 1, 1, 1 }, 9, 11, 18, 70, 85, MonsterAIID::Magma, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 75, 4, 4, 24, 60, 14, 0, 0, 60, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 7, 0, 2124 }, -/* MT_HORNED */ { P_("monster", "Horned Demon"), "rhino\\rhino", nullptr, nullptr, MonsterAvailability::Retail, 160, 1630, true, true, { 8, 8, 14, 6, 16, 6 }, { 2, 1, 1, 1, 1, 1 }, 7, 9, 13, 40, 80, MonsterAIID::Rhino, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 0, 60, 7, 2, 16, 100, 0, 5, 32, 40, MonsterClass::Animal, 0, RESIST_FIRE, 7, 0, 1172 }, -/* MT_MUDRUN */ { P_("monster", "Mud Runner"), "rhino\\rhino", nullptr, "rhino\\orange", MonsterAvailability::Retail, 160, 1630, true, true, { 8, 8, 14, 6, 16, 6 }, { 2, 1, 1, 1, 1, 1 }, 8, 10, 15, 50, 90, MonsterAIID::Rhino, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 1, 70, 7, 6, 18, 100, 0, 12, 36, 45, MonsterClass::Animal, 0, RESIST_FIRE, 7, 0, 1404 }, -/* MT_FROSTC */ { P_("monster", "Frost Charger"), "rhino\\rhino", nullptr, "rhino\\blue", MonsterAvailability::Retail, 160, 1630, true, true, { 8, 8, 14, 6, 16, 6 }, { 2, 1, 1, 1, 1, 1 }, 9, 11, 17, 60, 100, MonsterAIID::Rhino, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 2, 80, 7, 8, 20, 100, 0, 20, 40, 50, MonsterClass::Animal, IMMUNE_MAGIC | RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_LIGHTNING, 7, 0, 1720 }, -/* MT_OBLORD */ { P_("monster", "Obsidian Lord"), "rhino\\rhino", nullptr, "rhino\\rhinob", MonsterAvailability::Retail, 160, 1630, true, true, { 8, 8, 14, 6, 16, 6 }, { 2, 1, 1, 1, 1, 1 }, 10, 12, 19, 70, 110, MonsterAIID::Rhino, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 90, 7, 10, 22, 100, 0, 20, 50, 55, MonsterClass::Animal, IMMUNE_MAGIC | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 7, 0, 1809 }, -/* MT_BONEDMN */ { P_("monster", "oldboned"), "demskel\\demskl", nullptr, nullptr, MonsterAvailability::Never, 128, 1740, true, true, { 10, 8, 20, 6, 24, 16 }, { 3, 1, 1, 1, 1, 1 }, 24, 24, 12, 70, 70, MonsterAIID::Storm, 0, 0, 60, 8, 6, 14, 12, 0, 0, 0, 50, MonsterClass::Demon, IMMUNE_MAGIC, IMMUNE_MAGIC, 7, 0, 1344 }, -/* MT_REDDTH */ { P_("monster", "Red Death"), "thin\\thin", nullptr, "thin\\thinv3", MonsterAvailability::Never, 160, 1740, true, true, { 8, 8, 18, 4, 17, 14 }, { 3, 1, 1, 1, 1, 1 }, 8, 10, 16, 96, 96, MonsterAIID::Storm, 0, 1, 75, 5, 10, 20, 0, 0, 0, 0, 60, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 7, 0, 2168 }, -/* MT_LTCHDMN */ { P_("monster", "Litch Demon"), "thin\\thin", nullptr, "thin\\thinv3", MonsterAvailability::Never, 160, 1740, true, true, { 8, 8, 18, 4, 17, 14 }, { 3, 1, 1, 1, 1, 1 }, 9, 11, 18, 110, 110, MonsterAIID::Storm, 0, 2, 80, 5, 10, 24, 0, 0, 0, 0, 45, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_LIGHTNING, IMMUNE_MAGIC | IMMUNE_LIGHTNING, 7, 0, 2736 }, -/* MT_UDEDBLRG*/ { P_("monster", "Undead Balrog"), "thin\\thin", nullptr, "thin\\thinv3", MonsterAvailability::Never, 160, 1740, true, true, { 8, 8, 18, 4, 17, 14 }, { 3, 1, 1, 1, 1, 1 }, 11, 13, 22, 130, 130, MonsterAIID::Storm, 0, 3, 85, 5, 12, 30, 0, 0, 0, 0, 65, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, 7, 0, 3575 }, -/* MT_INCIN */ { P_("monster", "Incinerator"), "fireman\\firem", nullptr, nullptr, MonsterAvailability::Never, 128, 1460, true, false, { 14, 19, 20, 8, 14, 23 }, { 1, 1, 1, 1, 1, 1 }, 21, 22, 16, 30, 45, MonsterAIID::FireMan, 0, 0, 75, 8, 8, 16, 0, 0, 0, 0, 25, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 3, 0, 1888 }, -/* MT_FLAMLRD */ { P_("monster", "Flame Lord"), "fireman\\firem", nullptr, nullptr, MonsterAvailability::Never, 128, 1460, true, false, { 14, 19, 20, 8, 14, 23 }, { 1, 1, 1, 1, 1, 1 }, 22, 23, 18, 40, 55, MonsterAIID::FireMan, 0, 1, 75, 8, 10, 20, 0, 0, 0, 0, 25, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 3, 0, 2250 }, -/* MT_DOOMFIRE*/ { P_("monster", "Doom Fire"), "fireman\\firem", nullptr, nullptr, MonsterAvailability::Never, 128, 1460, true, false, { 14, 19, 20, 8, 14, 23 }, { 1, 1, 1, 1, 1, 1 }, 23, 24, 20, 50, 65, MonsterAIID::FireMan, 0, 2, 80, 8, 12, 24, 0, 0, 0, 0, 30, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 3, 0, 2740 }, -/* MT_HELLBURN*/ { P_("monster", "Hell Burner"), "fireman\\firem", nullptr, nullptr, MonsterAvailability::Never, 128, 1460, true, false, { 14, 19, 20, 8, 14, 23 }, { 1, 1, 1, 1, 1, 1 }, 24, 24, 22, 60, 80, MonsterAIID::FireMan, 0, 3, 85, 8, 15, 30, 0, 0, 0, 0, 30, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 3, 0, 3355 }, -/* MT_STORM */ { P_("monster", "Red Storm"), "thin\\thin", nullptr, "thin\\thinv3", MonsterAvailability::Retail, 160, 1740, true, true, { 8, 8, 18, 4, 17, 14 }, { 3, 1, 1, 1, 1, 1 }, 9, 11, 18, 55, 110, MonsterAIID::Storm, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 0, 80, 5, 8, 18, 75, 8, 4, 16, 30, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_LIGHTNING, 7, 0, 2160 }, -/* MT_RSTORM */ { P_("monster", "Storm Rider"), "thin\\thin", nullptr, nullptr, MonsterAvailability::Retail, 160, 1740, true, true, { 8, 8, 18, 4, 17, 14 }, { 3, 1, 1, 1, 1, 1 }, 10, 12, 20, 60, 120, MonsterAIID::Storm, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 1, 80, 5, 8, 18, 80, 8, 4, 16, 30, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_LIGHTNING, IMMUNE_MAGIC | IMMUNE_LIGHTNING, 7, 0, 2391 }, -/* MT_STORML */ { P_("monster", "Storm Lord"), "thin\\thin", nullptr, "thin\\thinv2", MonsterAvailability::Retail, 160, 1740, true, true, { 8, 8, 18, 4, 17, 14 }, { 3, 1, 1, 1, 1, 1 }, 11, 13, 22, 75, 135, MonsterAIID::Storm, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 2, 85, 5, 12, 24, 75, 8, 4, 16, 35, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_LIGHTNING, IMMUNE_MAGIC | IMMUNE_LIGHTNING, 7, 0, 2775 }, -/* MT_MAEL */ { P_("monster", "Maelstrom"), "thin\\thin", nullptr, "thin\\thinv1", MonsterAvailability::Retail, 160, 1740, true, true, { 8, 8, 18, 4, 17, 14 }, { 3, 1, 1, 1, 1, 1 }, 12, 14, 24, 90, 150, MonsterAIID::Storm, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 90, 5, 12, 28, 75, 8, 4, 16, 40, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_LIGHTNING, IMMUNE_MAGIC | IMMUNE_LIGHTNING, 7, 0, 3177 }, -/* MT_BIGFALL */ { P_("monster", "Devil Kin Brute"), "bigfall\\fallg", "newsfx\\kbrute", nullptr, MonsterAvailability::Retail, 128, 800, true, false, { 10, 8, 11, 8, 17, 0 }, { 1, 1, 1, 1, 2, 2 }, 21, 22, 27, 120, 160, MonsterAIID::SkeletonMelee, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 100, 6, 18, 24, 0, 0, 0, 0, 70, MonsterClass::Animal, RESIST_FIRE | RESIST_LIGHTNING, RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, 3, 0, 2400 }, -/* MT_WINGED */ { P_("monster", "Winged-Demon"), "gargoyle\\gargo", nullptr, nullptr, MonsterAvailability::Retail, 160, 1650, true, false, { 14, 14, 14, 10, 18, 14 }, { 1, 1, 1, 1, 1, 2 }, 5, 7, 9, 45, 60, MonsterAIID::Gargoyle, MFLAG_CAN_OPEN_DOOR, 0, 50, 7, 10, 16, 0, 0, 0, 0, 45, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 6, 0, 662 }, -/* MT_GARGOYLE*/ { P_("monster", "Gargoyle"), "gargoyle\\gargo", nullptr, "gargoyle\\gare", MonsterAvailability::Retail, 160, 1650, true, false, { 14, 14, 14, 10, 18, 14 }, { 1, 1, 1, 1, 1, 2 }, 7, 9, 13, 60, 90, MonsterAIID::Gargoyle, MFLAG_CAN_OPEN_DOOR, 1, 65, 7, 10, 16, 0, 0, 0, 0, 45, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_LIGHTNING, 6, 0, 1205 }, -/* MT_BLOODCLW*/ { P_("monster", "Blood Claw"), "gargoyle\\gargo", nullptr, "gargoyle\\gargbr", MonsterAvailability::Retail, 160, 1650, true, false, { 14, 14, 14, 10, 18, 14 }, { 1, 1, 1, 1, 1, 1 }, 9, 11, 19, 75, 125, MonsterAIID::Gargoyle, MFLAG_CAN_OPEN_DOOR, 2, 80, 7, 14, 22, 0, 0, 0, 0, 50, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 6, 0, 1873 }, -/* MT_DEATHW */ { P_("monster", "Death Wing"), "gargoyle\\gargo", nullptr, "gargoyle\\gargb", MonsterAvailability::Retail, 160, 1650, true, false, { 14, 14, 14, 10, 18, 14 }, { 1, 1, 1, 1, 1, 1 }, 10, 12, 23, 90, 150, MonsterAIID::Gargoyle, MFLAG_CAN_OPEN_DOOR, 3, 95, 7, 16, 28, 0, 0, 0, 0, 60, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_LIGHTNING, IMMUNE_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, 6, 0, 2278 }, -/* MT_MEGA */ { P_("monster", "Slayer"), "mega\\mega", nullptr, nullptr, MonsterAvailability::Retail, 160, 2220, true, true, { 6, 7, 14, 1, 24, 5 }, { 3, 1, 1, 1, 2, 1 }, 10, 12, 20, 120, 140, MonsterAIID::Mega, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 0, 100, 8, 12, 20, 0, 3, 0, 0, 60, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE, RESIST_MAGIC | IMMUNE_FIRE, 7, 0, 2300 }, -/* MT_GUARD */ { P_("monster", "Guardian"), "mega\\mega", nullptr, "mega\\guard", MonsterAvailability::Retail, 160, 2220, true, true, { 6, 7, 14, 1, 24, 5 }, { 3, 1, 1, 1, 2, 1 }, 11, 13, 22, 140, 160, MonsterAIID::Mega, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 1, 110, 8, 14, 22, 0, 3, 0, 0, 65, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE, RESIST_MAGIC | IMMUNE_FIRE, 7, 0, 2714 }, -/* MT_VTEXLRD */ { P_("monster", "Vortex Lord"), "mega\\mega", nullptr, "mega\\vtexl", MonsterAvailability::Retail, 160, 2220, true, true, { 6, 7, 14, 1, 24, 5 }, { 3, 1, 1, 1, 2, 1 }, 12, 14, 24, 160, 180, MonsterAIID::Mega, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 2, 120, 8, 18, 24, 0, 3, 0, 0, 70, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE, RESIST_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 7, 0, 3252 }, -/* MT_BALROG */ { P_("monster", "Balrog"), "mega\\mega", nullptr, "mega\\balr", MonsterAvailability::Retail, 160, 2220, true, true, { 6, 7, 14, 1, 24, 5 }, { 3, 1, 1, 1, 2, 1 }, 13, 15, 26, 180, 200, MonsterAIID::Mega, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 130, 8, 22, 30, 0, 3, 0, 0, 75, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE, RESIST_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 7, 0, 3643 }, -/* MT_NSNAKE */ { P_("monster", "Cave Viper"), "snake\\snake", nullptr, nullptr, MonsterAvailability::Retail, 160, 1270, false, false, { 12, 11, 13, 5, 18, 0 }, { 2, 1, 1, 1, 1, 1 }, 11, 13, 21, 100, 150, MonsterAIID::Snake, MFLAG_SEARCH, 0, 90, 8, 8, 20, 0, 0, 0, 0, 60, MonsterClass::Demon, IMMUNE_MAGIC, IMMUNE_MAGIC, 7, 0, 2725 }, -/* MT_RSNAKE */ { P_("monster", "Fire Drake"), "snake\\snake", nullptr, "snake\\snakr", MonsterAvailability::Retail, 160, 1270, false, false, { 12, 11, 13, 5, 18, 0 }, { 2, 1, 1, 1, 1, 1 }, 12, 14, 23, 120, 170, MonsterAIID::Snake, MFLAG_SEARCH, 1, 105, 8, 12, 24, 0, 0, 0, 0, 65, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_FIRE, IMMUNE_MAGIC | IMMUNE_FIRE, 7, 0, 3139 }, -/* MT_BSNAKE */ { P_("monster", "Gold Viper"), "snake\\snake", nullptr, "snake\\snakg", MonsterAvailability::Retail, 160, 1270, false, false, { 12, 11, 13, 5, 18, 0 }, { 2, 1, 1, 1, 1, 1 }, 13, 14, 25, 140, 180, MonsterAIID::Snake, MFLAG_SEARCH, 2, 120, 8, 15, 26, 0, 0, 0, 0, 70, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_LIGHTNING, 7, 0, 3540 }, -/* MT_GSNAKE */ { P_("monster", "Azure Drake"), "snake\\snake", nullptr, "snake\\snakb", MonsterAvailability::Retail, 160, 1270, false, false, { 12, 11, 13, 5, 18, 0 }, { 2, 1, 1, 1, 1, 1 }, 15, 16, 27, 160, 200, MonsterAIID::Snake, MFLAG_SEARCH, 3, 130, 8, 18, 30, 0, 0, 0, 0, 75, MonsterClass::Demon, RESIST_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, 7, 0, 3791 }, -/* MT_NBLACK */ { P_("monster", "Black Knight"), "black\\black", nullptr, nullptr, MonsterAvailability::Retail, 160, 2120, false, false, { 8, 8, 16, 4, 24, 0 }, { 2, 1, 1, 1, 1, 1 }, 12, 14, 24, 150, 150, MonsterAIID::SkeletonMelee, MFLAG_SEARCH, 0, 110, 8, 15, 20, 0, 0, 0, 0, 75, MonsterClass::Demon, RESIST_MAGIC | RESIST_LIGHTNING, RESIST_MAGIC | IMMUNE_LIGHTNING, 7, 0, 3360 }, -/* MT_RTBLACK */ { P_("monster", "Doom Guard"), "black\\black", nullptr, "black\\blkkntrt", MonsterAvailability::Retail, 160, 2120, false, false, { 8, 8, 16, 4, 24, 0 }, { 2, 1, 1, 1, 1, 1 }, 13, 15, 26, 165, 165, MonsterAIID::SkeletonMelee, MFLAG_SEARCH, 0, 130, 8, 18, 25, 0, 0, 0, 0, 75, MonsterClass::Demon, RESIST_MAGIC | RESIST_FIRE, RESIST_MAGIC | IMMUNE_FIRE, 7, 0, 3650 }, -/* MT_BTBLACK */ { P_("monster", "Steel Lord"), "black\\black", nullptr, "black\\blkkntbt", MonsterAvailability::Retail, 160, 2120, false, false, { 8, 8, 16, 4, 24, 0 }, { 2, 1, 1, 1, 1, 1 }, 14, 16, 28, 180, 180, MonsterAIID::SkeletonMelee, MFLAG_SEARCH, 1, 120, 8, 20, 30, 0, 0, 0, 0, 80, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 7, 0, 4252 }, -/* MT_RBLACK */ { P_("monster", "Blood Knight"), "black\\black", nullptr, "black\\blkkntbe", MonsterAvailability::Retail, 160, 2120, false, false, { 8, 8, 16, 4, 24, 0 }, { 2, 1, 1, 1, 1, 1 }, 13, 14, 30, 200, 200, MonsterAIID::SkeletonMelee, MFLAG_SEARCH, 1, 130, 8, 25, 35, 0, 0, 0, 0, 85, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, IMMUNE_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, 7, 0, 5130 }, -/* MT_UNRAV */ { P_("monster", "The Shredded"), "unrav\\unrav", "newsfx\\shred", nullptr, MonsterAvailability::Retail, 96, 484, false, false, { 10, 10, 12, 5, 16, 0 }, { 1, 1, 1, 1, 1, 1 }, 17, 18, 23, 70, 90, MonsterAIID::SkeletonMelee, 0, 0, 75, 7, 4, 12, 0, 0, 0, 0, 65, MonsterClass::Undead, RESIST_FIRE | RESIST_LIGHTNING, RESIST_FIRE | RESIST_LIGHTNING, 3, 0, 900 }, -/* MT_HOLOWONE*/ { P_("monster", "Hollow One"), "unrav\\unrav", "acid\\acid", nullptr, MonsterAvailability::Never, 96, 484, false, false, { 10, 10, 12, 5, 16, 0 }, { 1, 1, 1, 1, 1, 1 }, 18, 19, 27, 135, 240, MonsterAIID::SkeletonMelee, 0, 1, 75, 7, 12, 24, 0, 0, 0, 0, 75, MonsterClass::Undead, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 3, 0, 4374 }, -/* MT_PAINMSTR*/ { P_("monster", "Pain Master"), "unrav\\unrav", "acid\\acid", nullptr, MonsterAvailability::Never, 96, 484, false, false, { 10, 10, 12, 5, 16, 0 }, { 1, 1, 1, 1, 1, 1 }, 19, 20, 29, 110, 200, MonsterAIID::SkeletonMelee, 0, 2, 80, 7, 16, 30, 0, 0, 0, 0, 80, MonsterClass::Undead, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 3, 0, 5147 }, -/* MT_REALWEAV*/ { P_("monster", "Reality Weaver"), "unrav\\unrav", "acid\\acid", nullptr, MonsterAvailability::Never, 96, 484, false, false, { 10, 10, 12, 5, 16, 0 }, { 1, 1, 1, 1, 1, 1 }, 20, 20, 30, 135, 240, MonsterAIID::SkeletonMelee, 0, 3, 85, 7, 20, 35, 0, 0, 0, 0, 85, MonsterClass::Undead, RESIST_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, RESIST_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 3, 0, 5925 }, -/* MT_SUCCUBUS*/ { P_("monster", "Succubus"), "succ\\scbs", nullptr, nullptr, MonsterAvailability::Retail, 128, 980, false, false, { 14, 8, 16, 7, 24, 0 }, { 1, 1, 1, 1, 1, 1 }, 12, 14, 24, 120, 150, MonsterAIID::Succubus, MFLAG_CAN_OPEN_DOOR, 0, 100, 10, 1, 20, 0, 0, 0, 0, 60, MonsterClass::Demon, RESIST_MAGIC, IMMUNE_MAGIC | RESIST_FIRE, 3, 0, 3696 }, -/* MT_SNOWWICH*/ { P_("monster", "Snow Witch"), "succ\\scbs", nullptr, "succ\\succb", MonsterAvailability::Retail, 128, 980, false, false, { 14, 8, 16, 7, 24, 0 }, { 1, 1, 1, 1, 1, 1 }, 13, 15, 26, 135, 175, MonsterAIID::Succubus, MFLAG_CAN_OPEN_DOOR, 1, 110, 10, 1, 24, 0, 0, 0, 0, 65, MonsterClass::Demon, RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_LIGHTNING, 3, 0, 4084 }, -/* MT_HLSPWN */ { P_("monster", "Hell Spawn"), "succ\\scbs", nullptr, "succ\\succrw", MonsterAvailability::Retail, 128, 980, false, false, { 14, 8, 16, 7, 24, 0 }, { 1, 1, 1, 1, 1, 1 }, 14, 16, 28, 150, 200, MonsterAIID::Succubus, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 2, 115, 10, 1, 30, 0, 0, 0, 0, 75, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 3, 0, 4480 }, -/* MT_SOLBRNR */ { P_("monster", "Soul Burner"), "succ\\scbs", nullptr, "succ\\succbw", MonsterAvailability::Retail, 128, 980, false, false, { 14, 8, 16, 7, 24, 0 }, { 1, 1, 1, 1, 1, 1 }, 15, 16, 30, 140, 225, MonsterAIID::Succubus, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 120, 10, 1, 35, 0, 0, 0, 0, 85, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 3, 0, 4644 }, -/* MT_COUNSLR */ { P_("monster", "Counselor"), "mage\\mage", nullptr, nullptr, MonsterAvailability::Retail, 128, 2000, true, false, { 12, 1, 20, 8, 28, 20 }, { 1, 1, 1, 1, 1, 1 }, 13, 14, 25, 70, 70, MonsterAIID::Counselor, MFLAG_CAN_OPEN_DOOR, 0, 90, 8, 8, 20, 0, 0, 0, 0, 0, MonsterClass::Demon, RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, 7, 0, 4070 }, -/* MT_MAGISTR */ { P_("monster", "Magistrate"), "mage\\mage", nullptr, "mage\\cnselg", MonsterAvailability::Retail, 128, 2000, true, false, { 12, 1, 20, 8, 28, 20 }, { 1, 1, 1, 1, 1, 1 }, 14, 15, 27, 85, 85, MonsterAIID::Counselor, MFLAG_CAN_OPEN_DOOR, 1, 100, 8, 10, 24, 0, 0, 0, 0, 0, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 7, 0, 4478 }, -/* MT_CABALIST*/ { P_("monster", "Cabalist"), "mage\\mage", nullptr, "mage\\cnselgd", MonsterAvailability::Retail, 128, 2000, true, false, { 12, 1, 20, 8, 28, 20 }, { 1, 1, 1, 1, 1, 1 }, 15, 16, 29, 120, 120, MonsterAIID::Counselor, MFLAG_CAN_OPEN_DOOR, 2, 110, 8, 14, 30, 0, 0, 0, 0, 0, MonsterClass::Demon, RESIST_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, IMMUNE_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, 7, 0, 4929 }, -/* MT_ADVOCATE*/ { P_("monster", "Advocate"), "mage\\mage", nullptr, "mage\\cnselbk", MonsterAvailability::Retail, 128, 2000, true, false, { 12, 1, 20, 8, 28, 20 }, { 1, 1, 1, 1, 1, 1 }, 16, 16, 30, 145, 145, MonsterAIID::Counselor, MFLAG_CAN_OPEN_DOOR, 3, 120, 8, 15, 25, 0, 0, 0, 0, 0, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 7, 0, 4968 }, -/* MT_GOLEM */ { P_("monster", "Golem"), "golem\\golem", "golem\\golm", nullptr, MonsterAvailability::Never, 96, 386, true, false, { 0, 16, 12, 0, 12, 20 }, { 1, 1, 1, 1, 1, 1 }, 1, 1, 12, 1, 1, MonsterAIID::Golem, MFLAG_CAN_OPEN_DOOR, 0, 0, 7, 1, 1, 0, 0, 0, 0, 1, MonsterClass::Demon, 0, 0, 0, 0, 0 }, -/* MT_DIABLO */ { P_("monster", "The Dark Lord"), "diablo\\diablo", nullptr, nullptr, MonsterAvailability::Never, 160, 2000, true, true, { 16, 6, 16, 6, 16, 16 }, { 1, 1, 1, 1, 1, 1 }, 26, 26, 45, 3333, 3333, MonsterAIID::Diablo, MFLAG_KNOCKBACK | MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 220, 4, 30, 60, 0, 11, 0, 0, 90, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, 7, 0, 31666 }, -/* MT_DARKMAGE*/ { P_("monster", "The Arch-Litch Malignus"), "darkmage\\dmage", "darkmage\\dmag", nullptr, MonsterAvailability::Never, 128, 1060, true, false, { 6, 1, 21, 6, 23, 18 }, { 1, 1, 1, 1, 1, 1 }, 21, 21, 30, 160, 160, MonsterAIID::Counselor, MFLAG_CAN_OPEN_DOOR, 3, 120, 8, 20, 40, 0, 0, 0, 0, 70, MonsterClass::Demon, RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 7, 0, 4968 }, -/* MT_HELLBOAR*/ { P_("monster", "Hellboar"), "fork\\fork", "newsfx\\hboar", nullptr, MonsterAvailability::Retail, 188, 800, false, false, { 10, 10, 15, 6, 16, 0 }, { 2, 1, 1, 1, 1, 1 }, 17, 18, 23, 80, 100, MonsterAIID::SkeletonMelee, MFLAG_KNOCKBACK | MFLAG_SEARCH, 2, 70, 7, 16, 24, 0, 0, 0, 0, 60, MonsterClass::Demon, 0, RESIST_FIRE | RESIST_LIGHTNING, 3, 0, 750 }, -/* MT_STINGER */ { P_("monster", "Stinger"), "scorp\\scorp", "newsfx\\stingr", nullptr, MonsterAvailability::Retail, 64, 305, false, false, { 10, 10, 12, 6, 15, 0 }, { 2, 1, 1, 1, 1, 1 }, 17, 18, 22, 30, 40, MonsterAIID::SkeletonMelee, 0, 3, 85, 8, 1, 20, 0, 0, 0, 0, 50, MonsterClass::Animal, 0, RESIST_LIGHTNING, 1, 0, 500 }, -/* MT_PSYCHORB*/ { P_("monster", "Psychorb"), "eye\\eye", "newsfx\\psyco", nullptr, MonsterAvailability::Retail, 156, 800, false, false, { 12, 13, 13, 7, 21, 0 }, { 2, 1, 1, 1, 1, 1 }, 17, 18, 22, 20, 30, MonsterAIID::Psychorb, 0, 3, 80, 8, 10, 10, 0, 0, 0, 0, 40, MonsterClass::Animal, 0, RESIST_FIRE, 6, 0, 450 }, -/* MT_ARACHNON*/ { P_("monster", "Arachnon"), "spider\\spider", "newsfx\\slord", nullptr, MonsterAvailability::Retail, 148, 800, false, false, { 12, 10, 15, 6, 20, 0 }, { 2, 1, 1, 1, 1, 1 }, 17, 18, 22, 60, 80, MonsterAIID::SkeletonMelee, MFLAG_SEARCH, 3, 50, 8, 5, 15, 0, 0, 0, 0, 50, MonsterClass::Animal, 0, RESIST_LIGHTNING, 7, 0, 500 }, -/* MT_FELLTWIN*/ { P_("monster", "Felltwin"), "tsneak\\tsneak", "newsfx\\ftwin", nullptr, MonsterAvailability::Retail, 128, 800, false, false, { 13, 13, 15, 11, 16, 0 }, { 2, 1, 1, 1, 1, 1 }, 17, 18, 22, 50, 70, MonsterAIID::SkeletonMelee, MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 70, 8, 10, 18, 0, 0, 0, 0, 50, MonsterClass::Demon, 0, RESIST_FIRE | RESIST_LIGHTNING, 3, 0, 600 }, -/* MT_HORKSPWN*/ { P_("monster", "Hork Spawn"), "spawn\\spawn", "newsfx\\hspawn", nullptr, MonsterAvailability::Retail, 164, 520, false, true, { 15, 12, 14, 11, 14, 0 }, { 1, 1, 1, 1, 1, 1 }, 18, 19, 22, 30, 30, MonsterAIID::SkeletonMelee, 0, 3, 60, 8, 10, 25, 0, 0, 0, 0, 25, MonsterClass::Demon, RESIST_MAGIC, RESIST_MAGIC, 3, 0, 250 }, -/* MT_VENMTAIL*/ { P_("monster", "Venomtail"), "wscorp\\wscorp", "newsfx\\stingr", nullptr, MonsterAvailability::Retail, 86, 305, false, false, { 10, 10, 12, 6, 15, 0 }, { 2, 1, 1, 1, 1, 1 }, 19, 20, 24, 40, 50, MonsterAIID::SkeletonMelee, 0, 3, 85, 8, 1, 30, 0, 0, 0, 0, 60, MonsterClass::Animal, RESIST_LIGHTNING, IMMUNE_LIGHTNING, 1, 0, 1000 }, -/* MT_NECRMORB*/ { P_("monster", "Necromorb"), "eye2\\eye2", "newsfx\\psyco", nullptr, MonsterAvailability::Retail, 140, 800, false, false, { 12, 13, 13, 7, 21, 0 }, { 2, 1, 1, 1, 1, 1 }, 19, 20, 24, 30, 40, MonsterAIID::Necromorb, 0, 3, 80, 8, 20, 20, 0, 0, 0, 0, 50, MonsterClass::Animal, RESIST_FIRE, IMMUNE_FIRE | RESIST_LIGHTNING, 6, 0, 1100 }, -/* MT_SPIDLORD*/ { P_("monster", "Spider Lord"), "bspidr\\bspidr", "newsfx\\slord", nullptr, MonsterAvailability::Retail, 148, 800, true, true, { 12, 10, 15, 6, 20, 10 }, { 2, 1, 1, 1, 1, 1 }, 19, 20, 24, 80, 100, MonsterAIID::Acid, MFLAG_SEARCH, 3, 60, 8, 8, 20, 75, 8, 10, 10, 60, MonsterClass::Animal, RESIST_LIGHTNING, RESIST_FIRE | IMMUNE_LIGHTNING, 7, 0, 1250 }, -/* MT_LASHWORM*/ { P_("monster", "Lashworm"), "clasp\\clasp", "newsfx\\lworm", nullptr, MonsterAvailability::Retail, 176, 800, false, false, { 10, 12, 15, 6, 16, 0 }, { 1, 1, 1, 1, 1, 1 }, 19, 20, 20, 30, 30, MonsterAIID::SkeletonMelee, 0, 3, 90, 8, 12, 20, 0, 0, 0, 0, 50, MonsterClass::Animal, 0, RESIST_FIRE, 3, 0, 600 }, -/* MT_TORCHANT*/ { P_("monster", "Torchant"), "antworm\\worm", "newsfx\\tchant", nullptr, MonsterAvailability::Retail, 192, 800, false, false, { 14, 12, 12, 6, 20, 0 }, { 2, 1, 1, 1, 1, 1 }, 19, 20, 22, 60, 80, MonsterAIID::Torchant, 0, 3, 75, 8, 20, 30, 0, 0, 0, 0, 70, MonsterClass::Animal, IMMUNE_FIRE, RESIST_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 7, 0, 1250 }, -/* MT_HORKDMN */ { P_("monster", "Hork Demon"), "horkd\\horkd", "newsfx\\hdemon", nullptr, MonsterAvailability::Never, 138, 800, true, true, { 15, 8, 16, 6, 16, 9 }, { 2, 1, 1, 1, 1, 2 }, 19, 19, 27, 120, 160, MonsterAIID::SkeletonMelee, 0, 3, 60, 8, 20, 35, 80, 8, 0, 0, 80, MonsterClass::Demon, RESIST_LIGHTNING, RESIST_MAGIC | IMMUNE_LIGHTNING, 7, 0, 2000 }, -/* MT_DEFILER */ { P_("monster", "Hell Bug"), "hellbug\\hellbg", "newsfx\\defile", nullptr, MonsterAvailability::Never, 198, 800, true, true, { 8, 8, 14, 6, 14, 12 }, { 1, 1, 1, 1, 1, 1 }, 20, 20, 30, 240, 240, MonsterAIID::SkeletonMelee, MFLAG_SEARCH, 3, 110, 8, 20, 30, 90, 8, 50, 60, 80, MonsterClass::Demon, RESIST_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, RESIST_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 7, 0, 5000 }, -/* MT_GRAVEDIG*/ { P_("monster", "Gravedigger"), "gravdg\\gravdg", "newsfx\\gdiggr", nullptr, MonsterAvailability::Retail, 124, 800, true, true, { 24, 24, 12, 6, 16, 16 }, { 2, 1, 1, 1, 1, 1 }, 21, 21, 26, 120, 240, MonsterAIID::Scavenger, MFLAG_CAN_OPEN_DOOR, 3, 80, 6, 2, 12, 0, 0, 0, 0, 20, MonsterClass::Undead, IMMUNE_LIGHTNING, RESIST_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, 3, 0, 2000 }, -/* MT_TOMBRAT */ { P_("monster", "Tomb Rat"), "rat\\rat", "newsfx\\tmbrat", nullptr, MonsterAvailability::Retail, 104, 550, false, false, { 11, 8, 12, 6, 20, 0 }, { 2, 1, 1, 1, 1, 1 }, 21, 22, 24, 80, 120, MonsterAIID::SkeletonMelee, 0, 3, 120, 8, 12, 25, 0, 0, 0, 0, 30, MonsterClass::Animal, 0, RESIST_FIRE | RESIST_LIGHTNING, 3, 0, 1800 }, -/* MT_FIREBAT */ { P_("monster", "Firebat"), "hellbat\\helbat", "newsfx\\helbat", nullptr, MonsterAvailability::Retail, 96, 550, false, false, { 18, 16, 14, 6, 18, 11 }, { 2, 1, 1, 1, 1, 1 }, 21, 22, 24, 60, 80, MonsterAIID::FireBat, 0, 3, 100, 8, 15, 20, 0, 0, 0, 0, 70, MonsterClass::Animal, IMMUNE_FIRE, RESIST_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 7, 0, 2400 }, -/* MT_SKLWING */ { P_("monster", "Skullwing"), "demskel\\demskl", "newsfx\\swing", "skelaxe\\skelt", MonsterAvailability::Retail, 128, 1740, true, false, { 10, 8, 20, 6, 24, 16 }, { 3, 1, 1, 1, 1, 1 }, 21, 22, 27, 70, 70, MonsterAIID::SkeletonMelee, 0, 0, 75, 7, 15, 20, 75, 9, 15, 20, 80, MonsterClass::Undead, RESIST_FIRE | RESIST_LIGHTNING, RESIST_FIRE | RESIST_LIGHTNING, 7, 0, 3000 }, -/* MT_LICH */ { P_("monster", "Lich"), "lich\\lich", "newsfx\\lich", nullptr, MonsterAvailability::Retail, 96, 800, false, true, { 12, 10, 10, 7, 21, 0 }, { 2, 1, 1, 1, 2, 1 }, 21, 22, 25, 80, 100, MonsterAIID::Lich, 0, 3, 100, 8, 15, 20, 0, 0, 0, 0, 60, MonsterClass::Undead, RESIST_LIGHTNING, RESIST_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, 3, 0, 3000 }, -/* MT_CRYPTDMN*/ { P_("monster", "Crypt Demon"), "bubba\\bubba", "newsfx\\crypt", nullptr, MonsterAvailability::Retail, 154, 800, false, true, { 8, 18, 12, 8, 21, 0 }, { 3, 1, 1, 1, 1, 1 }, 22, 23, 28, 200, 240, MonsterAIID::SkeletonMelee, 0, 3, 100, 8, 20, 40, 0, 0, 0, 0, 85, MonsterClass::Demon, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, 3, 0, 3200 }, -/* MT_HELLBAT */ { P_("monster", "Hellbat"), "hellbat2\\bhelbt", "newsfx\\helbat", nullptr, MonsterAvailability::Retail, 96, 550, true, false, { 18, 16, 14, 6, 18, 11 }, { 2, 1, 1, 1, 1, 1 }, 23, 24, 29, 100, 140, MonsterAIID::Torchant, 0, 3, 110, 8, 30, 30, 0, 0, 0, 0, 80, MonsterClass::Demon, RESIST_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, RESIST_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 7, 0, 3600 }, -/* MT_BONEDEMN*/ { P_("monster", "Bone Demon"), "demskel\\demskl", "newsfx\\swing", nullptr, MonsterAvailability::Retail, 128, 1740, true, true, { 10, 8, 20, 6, 24, 16 }, { 3, 1, 1, 1, 1, 1 }, 23, 24, 30, 240, 280, MonsterAIID::BoneDemon, 0, 0, 100, 8, 40, 50, 160, 12, 50, 50, 50, MonsterClass::Undead, IMMUNE_FIRE | IMMUNE_LIGHTNING, IMMUNE_FIRE | IMMUNE_LIGHTNING, 7, 0, 5000 }, -/* MT_ARCHLICH*/ { P_("monster", "Arch Lich"), "lich2\\lich2", "newsfx\\lich", nullptr, MonsterAvailability::Retail, 136, 800, false, true, { 12, 10, 10, 7, 21, 0 }, { 2, 1, 1, 1, 2, 1 }, 23, 24, 30, 180, 200, MonsterAIID::ArchLich, 0, 3, 120, 8, 30, 30, 0, 0, 0, 0, 75, MonsterClass::Undead, RESIST_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 3, 0, 4000 }, -/* MT_BICLOPS */ { P_("monster", "Biclops"), "byclps\\byclps", "newsfx\\biclop", nullptr, MonsterAvailability::Retail, 180, 800, false, false, { 10, 11, 16, 6, 16, 0 }, { 2, 1, 1, 1, 2, 1 }, 23, 24, 30, 200, 240, MonsterAIID::SkeletonMelee, MFLAG_KNOCKBACK | MFLAG_CAN_OPEN_DOOR, 3, 90, 8, 40, 50, 0, 0, 0, 0, 80, MonsterClass::Demon, RESIST_LIGHTNING, RESIST_FIRE | RESIST_LIGHTNING, 3, 0, 4000 }, -/* MT_FLESTHNG*/ { P_("monster", "Flesh Thing"), "flesh\\flesh", "newsfx\\flesht", nullptr, MonsterAvailability::Retail, 164, 800, false, true, { 15, 24, 15, 6, 16, 0 }, { 1, 1, 1, 1, 1, 1 }, 23, 24, 28, 300, 400, MonsterAIID::SkeletonMelee, 0, 3, 150, 8, 12, 18, 0, 0, 0, 0, 70, MonsterClass::Demon, RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, 3, 0, 4000 }, -/* MT_REAPER */ { P_("monster", "Reaper"), "reaper\\reap", "newsfx\\reaper", nullptr, MonsterAvailability::Retail, 180, 800, false, false, { 12, 10, 14, 6, 16, 0 }, { 2, 1, 1, 1, 1, 1 }, 23, 24, 30, 260, 300, MonsterAIID::SkeletonMelee, 0, 3, 120, 8, 30, 35, 0, 0, 0, 0, 90, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 3, 0, 6000 }, -// TRANSLATORS: Monster Block end -/* MT_NAKRUL */ { P_("monster", "Na-Krul"), "nkr\\nkr", "newsfx\\nakrul", nullptr, MonsterAvailability::Never, 226, 1200, true, true, { 2, 6, 16, 3, 16, 16 }, { 1, 1, 1, 1, 1, 1 }, 31, 31, 40, 1332, 1332, MonsterAIID::SkeletonMelee, MFLAG_KNOCKBACK | MFLAG_SEARCH | MFLAG_CAN_OPEN_DOOR, 3, 150, 7, 40, 50, 150, 10, 40, 50, 125, MonsterClass::Demon, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, 7, 0, 13333 }, - // clang-format on -}; +/** Contains the data related to each unique monster ID. */ +std::vector UniqueMonstersData; /** * Map between .DUN file value and monster type enum @@ -335,114 +205,388 @@ const _monster_id MonstConvTbl[] = { MT_LRDSAYTR, }; -/** Contains the data related to each unique monster ID. */ -const UniqueMonsterData UniqueMonstersData[] = { - // clang-format off -// mtype, mName, mTrnName, mlevel, mmaxhp, mAi, mint, mMinDamage, mMaxDamage, mMagicRes, monsterPack, customToHit, customArmorClass, mtalkmsg - // TRANSLATORS: Unique Monster Block start -{ MT_NGOATMC, P_("monster", "Gharbad the Weak"), "bsdb", 4, 120, MonsterAIID::Gharbad, 3, 8, 16, IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_GARBUD1 }, -{ MT_SKING, P_("monster", "Skeleton King"), "genrl", 0, 240, MonsterAIID::SkeletonKing, 3, 6, 16, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Independent, 0, 0, TEXT_NONE }, -{ MT_COUNSLR, P_("monster", "Zhar the Mad"), "general", 8, 360, MonsterAIID::Zhar, 3, 16, 40, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_ZHAR1 }, -{ MT_BFALLSP, P_("monster", "Snotspill"), "bng", 4, 220, MonsterAIID::Snotspill, 3, 10, 18, RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_BANNER10 }, -{ MT_ADVOCATE, P_("monster", "Arch-Bishop Lazarus"), "general", 0, 600, MonsterAIID::Lazarus, 3, 30, 50, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_VILE13 }, -{ MT_HLSPWN, P_("monster", "Red Vex"), "redv", 0, 400, MonsterAIID::LazarusSuccubus, 3, 30, 50, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_VILE13 }, -{ MT_HLSPWN, P_("monster", "Black Jade"), "blkjd", 0, 400, MonsterAIID::LazarusSuccubus, 3, 30, 50, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_VILE13 }, -{ MT_RBLACK, P_("monster", "Lachdanan"), "bhka", 14, 500, MonsterAIID::Lachdanan, 3, 0, 0, 0, UniqueMonsterPack::None, 0, 0, TEXT_VEIL9 }, -{ MT_BTBLACK, P_("monster", "Warlord of Blood"), "general", 13, 850, MonsterAIID::Warlord, 3, 35, 50, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_WARLRD9 }, -{ MT_CLEAVER, P_("monster", "The Butcher"), "genrl", 0, 220, MonsterAIID::Butcher, 3, 6, 12, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_HORKDMN, P_("monster", "Hork Demon"), "genrl", 19, 300, MonsterAIID::HorkDemon, 3, 20, 35, RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_DEFILER, P_("monster", "The Defiler"), "genrl", 20, 480, MonsterAIID::SkeletonMelee, 3, 30, 40, RESIST_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_NAKRUL, P_("monster", "Na-Krul"), "genrl", 0, 1332, MonsterAIID::SkeletonMelee, 3, 40, 50, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_TSKELAX, P_("monster", "Bonehead Keenaxe"), "bhka", 2, 91, MonsterAIID::SkeletonMelee, 2, 4, 10, IMMUNE_MAGIC, UniqueMonsterPack::Leashed, 100, 0, TEXT_NONE }, -{ MT_RFALLSD, P_("monster", "Bladeskin the Slasher"), "bsts", 2, 51, MonsterAIID::Fallen, 0, 6, 18, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 45, TEXT_NONE }, -{ MT_NZOMBIE, P_("monster", "Soulpus"), "general", 2, 133, MonsterAIID::Zombie, 0, 4, 8, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_RFALLSP, P_("monster", "Pukerat the Unclean"), "ptu", 2, 77, MonsterAIID::Fallen, 3, 1, 5, RESIST_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_WSKELAX, P_("monster", "Boneripper"), "br", 2, 54, MonsterAIID::Bat, 0, 6, 15, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_NZOMBIE, P_("monster", "Rotfeast the Hungry"), "eth", 2, 85, MonsterAIID::SkeletonMelee, 3, 4, 12, IMMUNE_MAGIC, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_DFALLSD, P_("monster", "Gutshank the Quick"), "gtq", 3, 66, MonsterAIID::Bat, 2, 6, 16, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_TSKELSD, P_("monster", "Brokenhead Bangshield"), "bhbs", 3, 108, MonsterAIID::SkeletonMelee, 3, 12, 20, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_YFALLSP, P_("monster", "Bongo"), "bng", 3, 178, MonsterAIID::Fallen, 3, 9, 21, 0, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_BZOMBIE, P_("monster", "Rotcarnage"), "rcrn", 3, 102, MonsterAIID::Zombie, 3, 9, 24, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 45, TEXT_NONE }, -{ MT_NSCAV, P_("monster", "Shadowbite"), "shbt", 2, 60, MonsterAIID::SkeletonMelee, 3, 3, 20, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_WSKELBW, P_("monster", "Deadeye"), "de", 2, 49, MonsterAIID::GoatRanged, 0, 6, 9, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_RSKELAX, P_("monster", "Madeye the Dead"), "mtd", 4, 75, MonsterAIID::Bat, 0, 9, 21, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 30, TEXT_NONE }, -{ MT_BSCAV, P_("monster", "El Chupacabras"), "general", 3, 120, MonsterAIID::GoatMelee, 0, 10, 18, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_TSKELBW, P_("monster", "Skullfire"), "skfr", 3, 125, MonsterAIID::GoatRanged, 1, 6, 10, IMMUNE_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_SNEAK, P_("monster", "Warpskull"), "tspo", 3, 117, MonsterAIID::Sneak, 2, 6, 18, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_GZOMBIE, P_("monster", "Goretongue"), "pmr", 3, 156, MonsterAIID::SkeletonMelee, 1, 15, 30, IMMUNE_MAGIC, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_WSCAV, P_("monster", "Pulsecrawler"), "bhka", 4, 150, MonsterAIID::Scavenger, 0, 16, 20, IMMUNE_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 45, TEXT_NONE }, -{ MT_BLINK, P_("monster", "Moonbender"), "general", 4, 135, MonsterAIID::Bat, 0, 9, 27, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_BLINK, P_("monster", "Wrathraven"), "general", 5, 135, MonsterAIID::Bat, 2, 9, 22, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_YSCAV, P_("monster", "Spineeater"), "general", 4, 180, MonsterAIID::Scavenger, 1, 18, 25, IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_RSKELBW, P_("monster", "Blackash the Burning"), "bashtb", 4, 120, MonsterAIID::GoatRanged, 0, 6, 16, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_BFALLSD, P_("monster", "Shadowcrow"), "general", 5, 270, MonsterAIID::Sneak, 2, 12, 25, 0, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_LRDSAYTR, P_("monster", "Blightstone the Weak"), "bhka", 4, 360, MonsterAIID::SkeletonMelee, 0, 4, 12, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 70, 0, TEXT_NONE }, -{ MT_FAT, P_("monster", "Bilefroth the Pit Master"), "bftp", 6, 210, MonsterAIID::Bat, 1, 16, 23, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_NGOATBW, P_("monster", "Bloodskin Darkbow"), "bsdb", 5, 207, MonsterAIID::GoatRanged, 0, 3, 16, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 55, TEXT_NONE }, -{ MT_GLOOM, P_("monster", "Foulwing"), "db", 5, 246, MonsterAIID::Rhino, 3, 12, 28, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_XSKELSD, P_("monster", "Shadowdrinker"), "shdr", 5, 300, MonsterAIID::Sneak, 1, 18, 26, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 45, TEXT_NONE }, -{ MT_UNSEEN, P_("monster", "Hazeshifter"), "bhka", 5, 285, MonsterAIID::Sneak, 3, 18, 30, IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_NACID, P_("monster", "Deathspit"), "bfds", 6, 303, MonsterAIID::AcidUnique, 0, 12, 32, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_RGOATMC, P_("monster", "Bloodgutter"), "bgbl", 6, 315, MonsterAIID::Bat, 1, 24, 34, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_BGOATMC, P_("monster", "Deathshade Fleshmaul"), "dsfm", 6, 276, MonsterAIID::Rhino, 0, 12, 24, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::None, 0, 65, TEXT_NONE }, -{ MT_WYRM, P_("monster", "Warmaggot the Mad"), "general", 6, 246, MonsterAIID::Bat, 3, 15, 30, RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_STORM, P_("monster", "Glasskull the Jagged"), "bhka", 7, 354, MonsterAIID::Storm, 0, 18, 30, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_RGOATBW, P_("monster", "Blightfire"), "blf", 7, 321, MonsterAIID::Succubus, 2, 13, 21, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_GARGOYLE, P_("monster", "Nightwing the Cold"), "general", 7, 342, MonsterAIID::Bat, 1, 18, 26, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_GGOATBW, P_("monster", "Gorestone"), "general", 7, 303, MonsterAIID::GoatRanged, 1, 15, 28, RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 70, 0, TEXT_NONE }, -{ MT_BMAGMA, P_("monster", "Bronzefist Firestone"), "general", 8, 360, MonsterAIID::Magma, 0, 30, 36, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_INCIN, P_("monster", "Wrathfire the Doomed"), "wftd", 8, 270, MonsterAIID::SkeletonMelee, 2, 20, 30, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_NMAGMA, P_("monster", "Firewound the Grim"), "bhka", 8, 303, MonsterAIID::Magma, 0, 18, 22, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_MUDMAN, P_("monster", "Baron Sludge"), "bsm", 8, 315, MonsterAIID::Sneak, 3, 25, 34, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 75, TEXT_NONE }, -{ MT_GGOATMC, P_("monster", "Blighthorn Steelmace"), "bhsm", 7, 250, MonsterAIID::Rhino, 0, 20, 28, RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 45, TEXT_NONE }, -{ MT_RACID, P_("monster", "Chaoshowler"), "general", 8, 240, MonsterAIID::AcidUnique, 0, 12, 20, 0, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_REDDTH, P_("monster", "Doomgrin the Rotting"), "general", 8, 405, MonsterAIID::Storm, 3, 25, 50, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_FLAMLRD, P_("monster", "Madburner"), "general", 9, 270, MonsterAIID::Storm, 0, 20, 40, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_LTCHDMN, P_("monster", "Bonesaw the Litch"), "general", 9, 495, MonsterAIID::Storm, 2, 30, 55, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_MUDRUN, P_("monster", "Breakspine"), "general", 9, 351, MonsterAIID::Rhino, 0, 25, 34, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_REDDTH, P_("monster", "Devilskull Sharpbone"), "general", 9, 444, MonsterAIID::Storm, 1, 25, 40, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_STORM, P_("monster", "Brokenstorm"), "general", 9, 411, MonsterAIID::Storm, 2, 25, 36, IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_RSTORM, P_("monster", "Stormbane"), "general", 9, 555, MonsterAIID::Storm, 3, 30, 30, IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_TOAD, P_("monster", "Oozedrool"), "general", 9, 483, MonsterAIID::Fat, 3, 25, 30, RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_BLOODCLW, P_("monster", "Goldblight of the Flame"), "general", 10, 405, MonsterAIID::Gargoyle, 0, 15, 35, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 80, TEXT_NONE }, -{ MT_OBLORD, P_("monster", "Blackstorm"), "general", 10, 525, MonsterAIID::Rhino, 3, 20, 40, IMMUNE_MAGIC | IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 90, TEXT_NONE }, -{ MT_RACID, P_("monster", "Plaguewrath"), "general", 10, 450, MonsterAIID::AcidUnique, 2, 20, 30, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_RSTORM, P_("monster", "The Flayer"), "general", 10, 501, MonsterAIID::Storm, 1, 20, 35, RESIST_MAGIC | RESIST_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_FROSTC, P_("monster", "Bluehorn"), "general", 11, 477, MonsterAIID::Rhino, 1, 25, 30, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 90, TEXT_NONE }, -{ MT_HELLBURN, P_("monster", "Warpfire Hellspawn"), "general", 11, 525, MonsterAIID::FireMan, 3, 10, 40, RESIST_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_NSNAKE, P_("monster", "Fangspeir"), "general", 11, 444, MonsterAIID::SkeletonMelee, 1, 15, 32, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_UDEDBLRG, P_("monster", "Festerskull"), "general", 11, 600, MonsterAIID::Storm, 2, 15, 30, IMMUNE_MAGIC, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_NBLACK, P_("monster", "Lionskull the Bent"), "general", 12, 525, MonsterAIID::SkeletonMelee, 2, 25, 25, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_COUNSLR, P_("monster", "Blacktongue"), "general", 12, 360, MonsterAIID::Counselor, 3, 15, 30, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_DEATHW, P_("monster", "Viletouch"), "general", 12, 525, MonsterAIID::Gargoyle, 3, 20, 40, IMMUNE_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_RSNAKE, P_("monster", "Viperflame"), "general", 12, 570, MonsterAIID::SkeletonMelee, 1, 25, 35, IMMUNE_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_BSNAKE, P_("monster", "Fangskin"), "bhka", 14, 681, MonsterAIID::SkeletonMelee, 2, 15, 50, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_SUCCUBUS, P_("monster", "Witchfire the Unholy"), "general", 12, 444, MonsterAIID::Succubus, 3, 10, 20, IMMUNE_MAGIC | IMMUNE_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_BALROG, P_("monster", "Blackskull"), "bhka", 13, 750, MonsterAIID::SkeletonMelee, 3, 25, 40, IMMUNE_MAGIC | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_UNRAV, P_("monster", "Soulslash"), "general", 12, 450, MonsterAIID::SkeletonMelee, 0, 25, 25, IMMUNE_MAGIC, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_VTEXLRD, P_("monster", "Windspawn"), "general", 12, 711, MonsterAIID::SkeletonMelee, 1, 35, 40, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_GSNAKE, P_("monster", "Lord of the Pit"), "general", 13, 762, MonsterAIID::SkeletonMelee, 2, 25, 42, RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_RTBLACK, P_("monster", "Rustweaver"), "general", 13, 400, MonsterAIID::SkeletonMelee, 3, 1, 60, IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_HOLOWONE, P_("monster", "Howlingire the Shade"), "general", 13, 450, MonsterAIID::SkeletonMelee, 2, 40, 75, RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_MAEL, P_("monster", "Doomcloud"), "general", 13, 612, MonsterAIID::Storm, 1, 1, 60, RESIST_FIRE | IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_PAINMSTR, P_("monster", "Bloodmoon Soulfire"), "general", 13, 684, MonsterAIID::SkeletonMelee, 1, 15, 40, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_SNOWWICH, P_("monster", "Witchmoon"), "general", 13, 310, MonsterAIID::Succubus, 3, 30, 40, RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_VTEXLRD, P_("monster", "Gorefeast"), "general", 13, 771, MonsterAIID::SkeletonMelee, 3, 20, 55, RESIST_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_RTBLACK, P_("monster", "Graywar the Slayer"), "general", 14, 672, MonsterAIID::SkeletonMelee, 1, 30, 50, RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_MAGISTR, P_("monster", "Dreadjudge"), "general", 14, 540, MonsterAIID::Counselor, 1, 30, 40, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_HLSPWN, P_("monster", "Stareye the Witch"), "general", 14, 726, MonsterAIID::Succubus, 2, 30, 50, IMMUNE_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_BTBLACK, P_("monster", "Steelskull the Hunter"), "general", 14, 831, MonsterAIID::SkeletonMelee, 3, 40, 50, RESIST_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_RBLACK, P_("monster", "Sir Gorash"), "general", 16, 1050, MonsterAIID::SkeletonMelee, 1, 20, 60, 0, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_CABALIST, P_("monster", "The Vizier"), "general", 15, 850, MonsterAIID::Counselor, 2, 25, 40, IMMUNE_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_REALWEAV, P_("monster", "Zamphir"), "general", 15, 891, MonsterAIID::SkeletonMelee, 2, 30, 50, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_HLSPWN, P_("monster", "Bloodlust"), "general", 15, 825, MonsterAIID::Succubus, 1, 20, 55, IMMUNE_MAGIC | IMMUNE_LIGHTNING, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_HLSPWN, P_("monster", "Webwidow"), "general", 16, 774, MonsterAIID::Succubus, 1, 20, 50, IMMUNE_MAGIC | IMMUNE_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_SOLBRNR, P_("monster", "Fleshdancer"), "general", 16, 999, MonsterAIID::Succubus, 3, 30, 50, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, -{ MT_OBLORD, P_("monster", "Grimspike"), "general", 19, 534, MonsterAIID::Sneak, 1, 25, 40, IMMUNE_MAGIC | RESIST_FIRE, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -// TRANSLATORS: Unique Monster Block end -{ MT_STORML, P_("monster", "Doomlock"), "general", 28, 534, MonsterAIID::Sneak, 1, 35, 55, IMMUNE_MAGIC | RESIST_FIRE | RESIST_LIGHTNING, UniqueMonsterPack::Leashed, 0, 0, TEXT_NONE }, -{ MT_INVALID, nullptr, nullptr, 0, 0, MonsterAIID::Invalid, 0, 0, 0, 0, UniqueMonsterPack::None, 0, 0, TEXT_NONE }, - // clang-format on -}; +namespace { + +tl::expected<_monster_id, std::string> ParseMonsterId(std::string_view value) +{ + if (value == "MT_NZOMBIE") return MT_NZOMBIE; + if (value == "MT_BZOMBIE") return MT_BZOMBIE; + if (value == "MT_GZOMBIE") return MT_GZOMBIE; + if (value == "MT_YZOMBIE") return MT_YZOMBIE; + if (value == "MT_RFALLSP") return MT_RFALLSP; + if (value == "MT_DFALLSP") return MT_DFALLSP; + if (value == "MT_YFALLSP") return MT_YFALLSP; + if (value == "MT_BFALLSP") return MT_BFALLSP; + if (value == "MT_WSKELAX") return MT_WSKELAX; + if (value == "MT_TSKELAX") return MT_TSKELAX; + if (value == "MT_RSKELAX") return MT_RSKELAX; + if (value == "MT_XSKELAX") return MT_XSKELAX; + if (value == "MT_RFALLSD") return MT_RFALLSD; + if (value == "MT_DFALLSD") return MT_DFALLSD; + if (value == "MT_YFALLSD") return MT_YFALLSD; + if (value == "MT_BFALLSD") return MT_BFALLSD; + if (value == "MT_NSCAV") return MT_NSCAV; + if (value == "MT_BSCAV") return MT_BSCAV; + if (value == "MT_WSCAV") return MT_WSCAV; + if (value == "MT_YSCAV") return MT_YSCAV; + if (value == "MT_WSKELBW") return MT_WSKELBW; + if (value == "MT_TSKELBW") return MT_TSKELBW; + if (value == "MT_RSKELBW") return MT_RSKELBW; + if (value == "MT_XSKELBW") return MT_XSKELBW; + if (value == "MT_WSKELSD") return MT_WSKELSD; + if (value == "MT_TSKELSD") return MT_TSKELSD; + if (value == "MT_RSKELSD") return MT_RSKELSD; + if (value == "MT_XSKELSD") return MT_XSKELSD; + if (value == "MT_SNEAK") return MT_SNEAK; + if (value == "MT_STALKER") return MT_STALKER; + if (value == "MT_UNSEEN") return MT_UNSEEN; + if (value == "MT_ILLWEAV") return MT_ILLWEAV; + if (value == "MT_NGOATMC") return MT_NGOATMC; + if (value == "MT_BGOATMC") return MT_BGOATMC; + if (value == "MT_RGOATMC") return MT_RGOATMC; + if (value == "MT_GGOATMC") return MT_GGOATMC; + if (value == "MT_FIEND") return MT_FIEND; + if (value == "MT_GLOOM") return MT_GLOOM; + if (value == "MT_BLINK") return MT_BLINK; + if (value == "MT_FAMILIAR") return MT_FAMILIAR; + if (value == "MT_NGOATBW") return MT_NGOATBW; + if (value == "MT_BGOATBW") return MT_BGOATBW; + if (value == "MT_RGOATBW") return MT_RGOATBW; + if (value == "MT_GGOATBW") return MT_GGOATBW; + if (value == "MT_NACID") return MT_NACID; + if (value == "MT_RACID") return MT_RACID; + if (value == "MT_BACID") return MT_BACID; + if (value == "MT_XACID") return MT_XACID; + if (value == "MT_SKING") return MT_SKING; + if (value == "MT_FAT") return MT_FAT; + if (value == "MT_MUDMAN") return MT_MUDMAN; + if (value == "MT_TOAD") return MT_TOAD; + if (value == "MT_FLAYED") return MT_FLAYED; + if (value == "MT_WYRM") return MT_WYRM; + if (value == "MT_CAVSLUG") return MT_CAVSLUG; + if (value == "MT_DEVOUR") return MT_DEVOUR; + if (value == "MT_DVLWYRM") return MT_DVLWYRM; + if (value == "MT_NMAGMA") return MT_NMAGMA; + if (value == "MT_YMAGMA") return MT_YMAGMA; + if (value == "MT_BMAGMA") return MT_BMAGMA; + if (value == "MT_WMAGMA") return MT_WMAGMA; + if (value == "MT_HORNED") return MT_HORNED; + if (value == "MT_MUDRUN") return MT_MUDRUN; + if (value == "MT_FROSTC") return MT_FROSTC; + if (value == "MT_OBLORD") return MT_OBLORD; + if (value == "MT_BONEDMN") return MT_BONEDMN; + if (value == "MT_REDDTH") return MT_REDDTH; + if (value == "MT_LTCHDMN") return MT_LTCHDMN; + if (value == "MT_UDEDBLRG") return MT_UDEDBLRG; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INCIN") return MT_INCIN; + if (value == "MT_FLAMLRD") return MT_FLAMLRD; + if (value == "MT_DOOMFIRE") return MT_DOOMFIRE; + if (value == "MT_HELLBURN") return MT_HELLBURN; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_RSTORM") return MT_RSTORM; + if (value == "MT_STORM") return MT_STORM; + if (value == "MT_STORML") return MT_STORML; + if (value == "MT_MAEL") return MT_MAEL; + if (value == "MT_WINGED") return MT_WINGED; + if (value == "MT_GARGOYLE") return MT_GARGOYLE; + if (value == "MT_BLOODCLW") return MT_BLOODCLW; + if (value == "MT_DEATHW") return MT_DEATHW; + if (value == "MT_MEGA") return MT_MEGA; + if (value == "MT_GUARD") return MT_GUARD; + if (value == "MT_VTEXLRD") return MT_VTEXLRD; + if (value == "MT_BALROG") return MT_BALROG; + if (value == "MT_NSNAKE") return MT_NSNAKE; + if (value == "MT_RSNAKE") return MT_RSNAKE; + if (value == "MT_GSNAKE") return MT_GSNAKE; + if (value == "MT_BSNAKE") return MT_BSNAKE; + if (value == "MT_NBLACK") return MT_NBLACK; + if (value == "MT_RTBLACK") return MT_RTBLACK; + if (value == "MT_BTBLACK") return MT_BTBLACK; + if (value == "MT_RBLACK") return MT_RBLACK; + if (value == "MT_UNRAV") return MT_UNRAV; + if (value == "MT_HOLOWONE") return MT_HOLOWONE; + if (value == "MT_PAINMSTR") return MT_PAINMSTR; + if (value == "MT_REALWEAV") return MT_REALWEAV; + if (value == "MT_SUCCUBUS") return MT_SUCCUBUS; + if (value == "MT_SNOWWICH") return MT_SNOWWICH; + if (value == "MT_HLSPWN") return MT_HLSPWN; + if (value == "MT_SOLBRNR") return MT_SOLBRNR; + if (value == "MT_COUNSLR") return MT_COUNSLR; + if (value == "MT_MAGISTR") return MT_MAGISTR; + if (value == "MT_CABALIST") return MT_CABALIST; + if (value == "MT_ADVOCATE") return MT_ADVOCATE; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_DIABLO") return MT_DIABLO; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_GOLEM") return MT_GOLEM; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_INVALID") return MT_INVALID; + if (value == "MT_BIGFALL") return MT_BIGFALL; + if (value == "MT_DARKMAGE") return MT_DARKMAGE; + if (value == "MT_HELLBOAR") return MT_HELLBOAR; + if (value == "MT_STINGER") return MT_STINGER; + if (value == "MT_PSYCHORB") return MT_PSYCHORB; + if (value == "MT_ARACHNON") return MT_ARACHNON; + if (value == "MT_FELLTWIN") return MT_FELLTWIN; + if (value == "MT_HORKSPWN") return MT_HORKSPWN; + if (value == "MT_VENMTAIL") return MT_VENMTAIL; + if (value == "MT_NECRMORB") return MT_NECRMORB; + if (value == "MT_SPIDLORD") return MT_SPIDLORD; + if (value == "MT_LASHWORM") return MT_LASHWORM; + if (value == "MT_TORCHANT") return MT_TORCHANT; + if (value == "MT_HORKDMN") return MT_HORKDMN; + if (value == "MT_DEFILER") return MT_DEFILER; + if (value == "MT_GRAVEDIG") return MT_GRAVEDIG; + if (value == "MT_TOMBRAT") return MT_TOMBRAT; + if (value == "MT_FIREBAT") return MT_FIREBAT; + if (value == "MT_SKLWING") return MT_SKLWING; + if (value == "MT_LICH") return MT_LICH; + if (value == "MT_CRYPTDMN") return MT_CRYPTDMN; + if (value == "MT_HELLBAT") return MT_HELLBAT; + if (value == "MT_BONEDEMN") return MT_BONEDEMN; + if (value == "MT_LICH") return MT_LICH; + if (value == "MT_BICLOPS") return MT_BICLOPS; + if (value == "MT_FLESTHNG") return MT_FLESTHNG; + if (value == "MT_REAPER") return MT_REAPER; + if (value == "MT_NAKRUL") return MT_NAKRUL; + if (value == "MT_CLEAVER") return MT_CLEAVER; + if (value == "MT_INVILORD") return MT_INVILORD; + if (value == "MT_LRDSAYTR") return MT_LRDSAYTR; + return tl::make_unexpected("Unknown enum value"); +} + +tl::expected ParseMonsterAvailability(std::string_view value) +{ + if (value == "Always") return MonsterAvailability::Always; + if (value == "Never") return MonsterAvailability::Never; + if (value == "Retail") return MonsterAvailability::Retail; + return tl::make_unexpected("Expected one of: Always, Never, or Retail"); +} + +tl::expected ParseAiId(std::string_view value) +{ + if (value == "Zombie") return MonsterAIID::Zombie; + if (value == "Fat") return MonsterAIID::Fat; + if (value == "SkeletonMelee") return MonsterAIID::SkeletonMelee; + if (value == "SkeletonRanged") return MonsterAIID::SkeletonRanged; + if (value == "Scavenger") return MonsterAIID::Scavenger; + if (value == "Rhino") return MonsterAIID::Rhino; + if (value == "GoatMelee") return MonsterAIID::GoatMelee; + if (value == "GoatRanged") return MonsterAIID::GoatRanged; + if (value == "Fallen") return MonsterAIID::Fallen; + if (value == "Magma") return MonsterAIID::Magma; + if (value == "SkeletonKing") return MonsterAIID::SkeletonKing; + if (value == "Bat") return MonsterAIID::Bat; + if (value == "Gargoyle") return MonsterAIID::Gargoyle; + if (value == "Butcher") return MonsterAIID::Butcher; + if (value == "Succubus") return MonsterAIID::Succubus; + if (value == "Sneak") return MonsterAIID::Sneak; + if (value == "Storm") return MonsterAIID::Storm; + if (value == "FireMan") return MonsterAIID::FireMan; + if (value == "Gharbad") return MonsterAIID::Gharbad; + if (value == "Acid") return MonsterAIID::Acid; + if (value == "AcidUnique") return MonsterAIID::AcidUnique; + if (value == "Golem") return MonsterAIID::Golem; + if (value == "Zhar") return MonsterAIID::Zhar; + if (value == "Snotspill") return MonsterAIID::Snotspill; + if (value == "Snake") return MonsterAIID::Snake; + if (value == "Counselor") return MonsterAIID::Counselor; + if (value == "Mega") return MonsterAIID::Mega; + if (value == "Diablo") return MonsterAIID::Diablo; + if (value == "Lazarus") return MonsterAIID::Lazarus; + if (value == "LazarusSuccubus") return MonsterAIID::LazarusSuccubus; + if (value == "Lachdanan") return MonsterAIID::Lachdanan; + if (value == "Warlord") return MonsterAIID::Warlord; + if (value == "FireBat") return MonsterAIID::FireBat; + if (value == "Torchant") return MonsterAIID::Torchant; + if (value == "HorkDemon") return MonsterAIID::HorkDemon; + if (value == "Lich") return MonsterAIID::Lich; + if (value == "ArchLich") return MonsterAIID::ArchLich; + if (value == "Psychorb") return MonsterAIID::Psychorb; + if (value == "Necromorb") return MonsterAIID::Necromorb; + if (value == "BoneDemon") return MonsterAIID::BoneDemon; + return tl::make_unexpected("Unknown enum value"); +} + +tl::expected ParseMonsterFlag(std::string_view value) +{ + if (value == "HIDDEN") return MFLAG_HIDDEN; + if (value == "LOCK_ANIMATION") return MFLAG_LOCK_ANIMATION; + if (value == "ALLOW_SPECIAL") return MFLAG_ALLOW_SPECIAL; + if (value == "TARGETS_MONSTER") return MFLAG_TARGETS_MONSTER; + if (value == "GOLEM") return MFLAG_GOLEM; + if (value == "QUEST_COMPLETE") return MFLAG_QUEST_COMPLETE; + if (value == "KNOCKBACK") return MFLAG_KNOCKBACK; + if (value == "SEARCH") return MFLAG_SEARCH; + if (value == "CAN_OPEN_DOOR") return MFLAG_CAN_OPEN_DOOR; + if (value == "NO_ENEMY") return MFLAG_NO_ENEMY; + if (value == "BERSERK") return MFLAG_BERSERK; + if (value == "NOLIFESTEAL") return MFLAG_NOLIFESTEAL; + return tl::make_unexpected("Unknown enum value"); +} + +tl::expected ParseMonsterClass(std::string_view value) +{ + if (value == "Undead") return MonsterClass::Undead; + if (value == "Demon") return MonsterClass::Demon; + if (value == "Animal") return MonsterClass::Animal; + return tl::make_unexpected("Unknown enum value"); +} + +tl::expected ParseMonsterResistance(std::string_view value) +{ + if (value == "RESIST_MAGIC") return RESIST_MAGIC; + if (value == "RESIST_FIRE") return RESIST_FIRE; + if (value == "RESIST_LIGHTNING") return RESIST_LIGHTNING; + if (value == "IMMUNE_MAGIC") return IMMUNE_MAGIC; + if (value == "IMMUNE_FIRE") return IMMUNE_FIRE; + if (value == "IMMUNE_LIGHTNING") return IMMUNE_LIGHTNING; + if (value == "IMMUNE_ACID") return IMMUNE_ACID; + return tl::make_unexpected("Unknown enum value"); +} + +tl::expected ParseUniqueMonsterPack(std::string_view value) +{ + if (value == "None") return UniqueMonsterPack::None; + if (value == "Independent") return UniqueMonsterPack::Independent; + if (value == "Leashed") return UniqueMonsterPack::Leashed; + return tl::make_unexpected("Unknown enum value"); +} + +void LoadMonstDat() +{ + const std::string_view filename = "txtdata\\monsters\\monstdat.tsv"; + DataFile dataFile = DataFile::loadOrDie(filename); + dataFile.skipHeaderOrDie(filename); + + MonstersData.clear(); + MonstersData.reserve(dataFile.numRecords()); + std::unordered_map spritePathToId; + for (DataFileRecord record : dataFile) { + RecordReader reader { record, filename }; + MonsterData &monster = MonstersData.emplace_back(); + reader.advance(); // Skip the first column (monster ID). + reader.readString("name", monster.name); + { + std::string assetsSuffix; + reader.readString("assetsSuffix", assetsSuffix); + const auto [it, inserted] = spritePathToId.emplace(assetsSuffix, spritePathToId.size()); + if (inserted) + MonsterSpritePaths.push_back(it->first); + monster.spriteId = static_cast(it->second); + } + reader.readString("soundSuffix", monster.soundSuffix); + reader.readString("trnFile", monster.trnFile); + reader.read("availability", monster.availability, ParseMonsterAvailability); + reader.readInt("width", monster.width); + reader.readInt("image", monster.image); + reader.readBool("hasSpecial", monster.hasSpecial); + reader.readBool("hasSpecialSound", monster.hasSpecialSound); + reader.readIntArray("frames", monster.frames); + reader.readIntArray("rate", monster.rate); + reader.readInt("minDunLvl", monster.minDunLvl); + reader.readInt("maxDunLvl", monster.maxDunLvl); + reader.readInt("level", monster.level); + reader.readInt("hitPointsMinimum", monster.hitPointsMinimum); + reader.readInt("hitPointsMaximum", monster.hitPointsMaximum); + reader.read("ai", monster.ai, ParseAiId); + reader.readEnumList("abilityFlags", monster.abilityFlags, ParseMonsterFlag); + reader.readInt("intelligence", monster.intelligence); + reader.readInt("toHit", monster.toHit); + reader.readInt("animFrameNum", monster.animFrameNum); + reader.readInt("minDamage", monster.minDamage); + reader.readInt("maxDamage", monster.maxDamage); + reader.readInt("toHitSpecial", monster.toHitSpecial); + reader.readInt("animFrameNumSpecial", monster.animFrameNumSpecial); + reader.readInt("minDamageSpecial", monster.minDamageSpecial); + reader.readInt("maxDamageSpecial", monster.maxDamageSpecial); + reader.readInt("armorClass", monster.armorClass); + reader.read("monsterClass", monster.monsterClass, ParseMonsterClass); + reader.readEnumList("resistance", monster.resistance, ParseMonsterResistance); + reader.readEnumList("resistanceHell", monster.resistanceHell, ParseMonsterResistance); + reader.readInt("selectionType", monster.selectionType); + + // treasure + // TODO: Replace this hack with proper parsing once items have been migrated to data files. + reader.read("treasure", monster.treasure, [](std::string_view value) -> tl::expected { + if (value.empty()) return 0; + if (value == "None") return T_NODROP; + if (value == "Uniq(SKCROWN)") return Uniq(UITEM_SKCROWN); + if (value == "Uniq(CLEAVER)") return Uniq(UITEM_CLEAVER); + return tl::make_unexpected("Invalid value. NOTE: Parser is incomplete"); + }); + + reader.readInt("exp", monster.exp); + } + MonstersData.shrink_to_fit(); +} + +void LoadUniqueMonstDat() +{ + const std::string_view filename = "txtdata\\monsters\\unique_monstdat.tsv"; + DataFile dataFile = DataFile::loadOrDie(filename); + dataFile.skipHeaderOrDie(filename); + + UniqueMonstersData.clear(); + UniqueMonstersData.reserve(dataFile.numRecords()); + for (DataFileRecord record : dataFile) { + RecordReader reader { record, filename }; + UniqueMonsterData &monster = UniqueMonstersData.emplace_back(); + reader.read("type", monster.mtype, ParseMonsterId); + reader.readString("name", monster.mName); + reader.readString("trn", monster.mTrnName); + reader.readInt("level", monster.mlevel); + reader.readInt("maxHp", monster.mmaxhp); + reader.read("ai", monster.mAi, ParseAiId); + reader.readInt("intelligence", monster.mint); + reader.readInt("minDamage", monster.mMinDamage); + reader.readInt("maxDamage", monster.mMaxDamage); + reader.readEnumList("resistance", monster.mMagicRes, ParseMonsterResistance); + reader.read("monsterPack", monster.monsterPack, ParseUniqueMonsterPack); + reader.readInt("customToHit", monster.customToHit); + reader.readInt("customArmorClass", monster.customArmorClass); + + // talkMessage + // TODO: Replace this hack with proper parsing once messages have been migrated to data files. + reader.read("talkMessage", monster.mtalkmsg, [](std::string_view value) -> tl::expected<_speech_id, std::string> { + if (value.empty()) return TEXT_NONE; + if (value == "TEXT_GARBUD1") return TEXT_GARBUD1; + if (value == "TEXT_ZHAR1") return TEXT_ZHAR1; + if (value == "TEXT_BANNER10") return TEXT_BANNER10; + if (value == "TEXT_VILE13") return TEXT_VILE13; + if (value == "TEXT_VEIL9") return TEXT_VEIL9; + if (value == "TEXT_WARLRD9") return TEXT_WARLRD9; + return tl::make_unexpected("Invalid value. NOTE: Parser is incomplete"); + }); + } + UniqueMonstersData.shrink_to_fit(); +} + +} // namespace + +void LoadMonsterData() +{ + LoadMonstDat(); + LoadUniqueMonstDat(); +} + +size_t GetNumMonsterSprites() +{ + return MonsterSpritePaths.size(); +} } // namespace devilution diff --git a/Source/monstdat.h b/Source/monstdat.h index 258744bc949..f137f6d9b34 100644 --- a/Source/monstdat.h +++ b/Source/monstdat.h @@ -6,6 +6,7 @@ #pragma once #include +#include #include "textdat.h" @@ -88,10 +89,10 @@ enum class MonsterAvailability : uint8_t { }; struct MonsterData { - const char *name; - const char *assetsSuffix; - const char *soundSuffix; - const char *trnFile; + std::string name; + std::string soundSuffix; + std::string trnFile; + uint16_t spriteId; MonsterAvailability availability; uint16_t width; uint16_t image; @@ -129,6 +130,18 @@ struct MonsterData { /** Using monster_treasure */ uint16_t treasure; uint16_t exp; + + [[nodiscard]] const char *spritePath() const; + + [[nodiscard]] const char *soundPath() const + { + return !soundSuffix.empty() ? soundSuffix.c_str() : spritePath(); + } + + [[nodiscard]] bool hasAnim(size_t index) const + { + return frames[index] != 0; + } }; enum _monster_id : int16_t { @@ -294,8 +307,8 @@ enum class UniqueMonsterPack : uint8_t { struct UniqueMonsterData { _monster_id mtype; - const char *mName; - const char *mTrnName; + std::string mName; + std::string mTrnName; uint8_t mlevel; uint16_t mmaxhp; MonsterAIID mAi; @@ -314,8 +327,17 @@ struct UniqueMonsterData { _speech_id mtalkmsg; }; -extern const MonsterData MonstersData[]; +extern std::vector MonstersData; extern const _monster_id MonstConvTbl[]; -extern const UniqueMonsterData UniqueMonstersData[]; +extern std::vector UniqueMonstersData; + +void LoadMonsterData(); + +/** + * @brief Returns the number of the monster sprite files. + * + * Different monsters can use the same sprite with different TRNs, these count as 1. + */ +size_t GetNumMonsterSprites(); } // namespace devilution diff --git a/Source/monster.cpp b/Source/monster.cpp index 01055fcbd2a..b17d8e2268c 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -6,10 +6,12 @@ #include "monster.h" #include +#include #include #include #include +#include #include #include @@ -41,7 +43,8 @@ #include "utils/cl2_to_clx.hpp" #include "utils/file_name_generator.hpp" #include "utils/language.h" -#include "utils/stdcompat/string_view.hpp" +#include "utils/log.hpp" +#include "utils/static_vector.hpp" #include "utils/str_cat.hpp" #include "utils/utf8.hpp" @@ -96,15 +99,28 @@ size_t GetNumAnims(const MonsterData &monsterData) return monsterData.hasSpecial ? 6 : 5; } +size_t GetNumAnimsWithGraphics(const MonsterData &monsterData) +{ + // Monster graphics can be missing for certain actions, + // e.g. Golem has no standing graphics. + const size_t numAnims = GetNumAnims(monsterData); + size_t result = 0; + for (size_t i = 0; i < numAnims; ++i) { + if (monsterData.hasAnim(i)) + ++result; + } + return result; +} + void InitMonsterTRN(CMonster &monst) { char path[64]; - *BufCopy(path, "monsters\\", monst.data->trnFile, ".trn") = '\0'; + *BufCopy(path, "monsters\\", monst.data().trnFile, ".trn") = '\0'; std::array colorTranslations; LoadFileInMem(path, colorTranslations); std::replace(colorTranslations.begin(), colorTranslations.end(), 255, 0); - const size_t numAnims = GetNumAnims(*monst.data); + const size_t numAnims = GetNumAnims(monst.data()); for (size_t i = 0; i < numAnims; i++) { if (i == 1 && IsAnyOf(monst.type, MT_COUNSLR, MT_MAGISTR, MT_CABALIST, MT_ADVOCATE)) { continue; @@ -125,7 +141,7 @@ void InitMonster(Monster &monster, Direction rd, size_t typeIndex, Point positio monster.position.tile = position; monster.position.future = position; monster.position.old = position; - monster.levelType = typeIndex; + monster.levelType = static_cast(typeIndex); monster.mode = MonsterMode::Stand; monster.animInfo = {}; monster.changeAnimationData(MonsterGraphic::Stand); @@ -215,7 +231,7 @@ bool CanPlaceMonster(Point position) && !IsTileOccupied(position); } -void PlaceMonster(int i, size_t typeIndex, Point position) +void PlaceMonster(size_t i, size_t typeIndex, Point position) { if (LevelMonsterTypes[typeIndex].type == MT_NAKRUL) { for (size_t j = 0; j < ActiveMonsterCount; j++) { @@ -224,15 +240,16 @@ void PlaceMonster(int i, size_t typeIndex, Point position) } } } - dMonster[position.x][position.y] = i + 1; + Monster &monster = Monsters[i]; + monster.occupyTile(position, false); auto rd = static_cast(GenerateRnd(8)); - InitMonster(Monsters[i], rd, typeIndex, position); + InitMonster(monster, rd, typeIndex, position); } -void PlaceGroup(size_t typeIndex, unsigned num, Monster *leader = nullptr, bool leashed = false) +void PlaceGroup(size_t typeIndex, size_t num, Monster *leader = nullptr, bool leashed = false) { - unsigned placed = 0; + uint8_t placed = 0; for (int try1 = 0; try1 < 10; try1++) { while (placed != 0) { @@ -266,7 +283,7 @@ void PlaceGroup(size_t typeIndex, unsigned num, Monster *leader = nullptr, bool for (unsigned try2 = 0; j < num && try2 < 100; xp += Displacement(static_cast(GenerateRnd(8))).deltaX, yp += Displacement(static_cast(GenerateRnd(8))).deltaX) { /// BUGFIX: `yp += Point.y` if (!CanPlaceMonster({ xp, yp }) || (dTransVal[xp][yp] != dTransVal[x1][y1]) - || (leashed && (abs(xp - x1) >= 4 || abs(yp - y1) >= 4))) { + || (leashed && (std::abs(xp - x1) >= 4 || std::abs(yp - y1) >= 4))) { try2++; continue; } @@ -400,28 +417,6 @@ void PlaceUniqueMonst(UniqueMonsterType uniqindex, size_t minionType, int bosspa PrepareUniqueMonst(monster, uniqindex, minionType, bosspacksize, uniqueMonsterData); } -size_t AddMonsterType(_monster_id type, placeflag placeflag) -{ - const size_t typeIndex = GetMonsterTypeIndex(type); - CMonster &monsterType = LevelMonsterTypes[typeIndex]; - - if (typeIndex == LevelMonsterTypeCount) { - LevelMonsterTypeCount++; - monsterType.type = type; - monstimgtot += MonstersData[type].image; - InitMonsterGFX(monsterType); - InitMonsterSND(monsterType); - } - - monsterType.placeFlags |= placeflag; - return typeIndex; -} - -inline size_t AddMonsterType(UniqueMonsterType uniqueType, placeflag placeflag) -{ - return AddMonsterType(UniqueMonstersData[static_cast(uniqueType)].mtype, placeflag); -} - void ClearMVars(Monster &monster) { monster.var1 = 0; @@ -452,7 +447,7 @@ void ClrAllMonsters() void PlaceUniqueMonsters() { - for (size_t u = 0; UniqueMonstersData[u].mtype != -1; u++) { + for (size_t u = 0; u < UniqueMonstersData.size(); ++u) { if (UniqueMonstersData[u].mlevel != currlevel) continue; @@ -600,10 +595,10 @@ void StartMonsterGotHit(Monster &monster) monster.position.tile = monster.position.old; monster.position.future = monster.position.old; M_ClearSquares(monster); - dMonster[monster.position.tile.x][monster.position.tile.y] = monster.getId() + 1; + monster.occupyTile(monster.position.tile, false); } -bool IsRanged(Monster &monster) +DVL_ALWAYS_INLINE bool IsRanged(Monster &monster) { return IsAnyOf(monster.ai, MonsterAIID::SkeletonRanged, MonsterAIID::GoatRanged, MonsterAIID::Succubus, MonsterAIID::LazarusSuccubus); } @@ -719,10 +714,10 @@ void WalkNorthwards(Monster &monster, int xadd, int yadd, Direction endDir) const auto fx = static_cast(xadd + monster.position.tile.x); const auto fy = static_cast(yadd + monster.position.tile.y); - dMonster[fx][fy] = -(monster.getId() + 1); monster.mode = MonsterMode::MoveNorthwards; monster.position.old = monster.position.tile; monster.position.future = { fx, fy }; + monster.occupyTile(monster.position.future, true); monster.var1 = xadd; monster.var2 = yadd; monster.var3 = static_cast(endDir); @@ -734,13 +729,13 @@ void WalkSouthwards(Monster &monster, int xoff, int yoff, int xadd, int yadd, Di const auto fx = static_cast(xadd + monster.position.tile.x); const auto fy = static_cast(yadd + monster.position.tile.y); - dMonster[monster.position.tile.x][monster.position.tile.y] = -(monster.getId() + 1); monster.var1 = monster.position.tile.x; monster.var2 = monster.position.tile.y; monster.position.old = monster.position.tile; monster.position.tile = { fx, fy }; monster.position.future = { fx, fy }; - dMonster[fx][fy] = monster.getId() + 1; + monster.occupyTile(monster.position.old, true); + monster.occupyTile(monster.position.tile, false); if (monster.lightId != NO_LIGHT) ChangeLightXY(monster.lightId, monster.position.tile); monster.mode = MonsterMode::MoveSouthwards; @@ -758,11 +753,11 @@ void WalkSideways(Monster &monster, int xoff, int yoff, int xadd, int yadd, int if (monster.lightId != NO_LIGHT) ChangeLightXY(monster.lightId, { x, y }); - dMonster[monster.position.tile.x][monster.position.tile.y] = -(monster.getId() + 1); - dMonster[fx][fy] = monster.getId() + 1; monster.position.temp = { x, y }; monster.position.old = monster.position.tile; monster.position.future = { fx, fy }; + monster.occupyTile(monster.position.tile, true); + monster.occupyTile(monster.position.future, false); monster.mode = MonsterMode::MoveSideways; monster.var1 = fx; monster.var2 = fy; @@ -824,7 +819,7 @@ void StartEating(Monster &monster) void DiabloDeath(Monster &diablo, bool sendmsg) { - PlaySFX(USFX_DIABLOD); + PlaySFX(SfxID::DiabloDeath); auto &quest = Quests[Q_DIABLO]; quest._qactive = QUEST_DONE; if (sendmsg) @@ -843,7 +838,7 @@ void DiabloDeath(Monster &diablo, bool sendmsg) monster.position.tile = monster.position.old; monster.position.future = monster.position.tile; M_ClearSquares(monster); - dMonster[monster.position.tile.x][monster.position.tile.y] = monsterId + 1; + monster.occupyTile(monster.position.tile, false); } AddLight(diablo.position.tile, 8); DoVision(diablo.position.tile, 8, MAP_EXP_NONE, true); @@ -868,7 +863,7 @@ void SpawnLoot(Monster &monster, bool sendmsg) if (Quests[Q_GARBUD].IsAvailable() && monster.uniqueType == UniqueMonsterType::Garbud) { CreateTypeItem(monster.position.tile + Displacement { 1, 1 }, true, ItemType::Mace, IMISC_NONE, sendmsg, false); } else if (monster.uniqueType == UniqueMonsterType::Defiler) { - if (effect_is_playing(USFX_DEFILER8)) + if (effect_is_playing(SfxID::Defiler8)) stream_stop(); SpawnMapOfDoom(monster.position.tile, sendmsg); Quests[Q_DEFILER]._qactive = QUEST_DONE; @@ -880,9 +875,9 @@ void SpawnLoot(Monster &monster, bool sendmsg) CreateAmulet(monster.position.tile, 13, sendmsg, false); } } else if (monster.type().type == MT_NAKRUL) { - int nSFX = IsUberRoomOpened ? USFX_NAKRUL4 : USFX_NAKRUL5; + SfxID nSFX = IsUberRoomOpened ? SfxID::NaKrul4 : SfxID::NaKrul5; if (sgGameInitInfo.bCowQuest != 0) - nSFX = USFX_NAKRUL6; + nSFX = SfxID::NaKrul6; if (effect_is_playing(nSFX)) stream_stop(); UberDiabloMonsterIndex = -2; @@ -928,7 +923,7 @@ void Teleport(Monster &monster) M_ClearSquares(monster); dMonster[monster.position.tile.x][monster.position.tile.y] = 0; - dMonster[position->x][position->y] = monster.getId() + 1; + monster.occupyTile(*position, false); monster.position.old = *position; monster.direction = GetMonsterDirection(monster); @@ -1047,7 +1042,7 @@ bool MonsterWalk(Monster &monster, MonsterMode variant) dMonster[monster.position.tile.x][monster.position.tile.y] = 0; monster.position.tile.x += monster.var1; monster.position.tile.y += monster.var2; - dMonster[monster.position.tile.x][monster.position.tile.y] = monster.getId() + 1; + monster.occupyTile(monster.position.tile, false); break; case MonsterMode::MoveSouthwards: dMonster[monster.var1][monster.var2] = 0; @@ -1056,7 +1051,7 @@ bool MonsterWalk(Monster &monster, MonsterMode variant) dMonster[monster.position.tile.x][monster.position.tile.y] = 0; monster.position.tile = WorldTilePosition { static_cast(monster.var1), static_cast(monster.var2) }; // dMonster is set here for backwards comparability, without it the monster would be invisible if loaded from a vanilla save. - dMonster[monster.position.tile.x][monster.position.tile.y] = monster.getId() + 1; + monster.occupyTile(monster.position.tile, false); break; default: break; @@ -1094,7 +1089,7 @@ void MonsterAttackMonster(Monster &attacker, Monster &target, int hper, int mind ApplyMonsterDamage(DamageType::Physical, target, dam); if (attacker.isPlayerMinion()) { - int playerId = attacker.getId(); + size_t playerId = attacker.getId(); const Player &player = Players[playerId]; target.tag(player); } @@ -1158,7 +1153,7 @@ void MonsterAttackPlayer(Monster &monster, Player &player, int hit, int minDam, ac += 40; if (HasAnyOf(player.pDamAcFlags, ItemSpecialEffectHf::ACAgainstUndead) && monster.data().monsterClass == MonsterClass::Undead) ac += 20; - hit += 2 * (monster.level(sgGameInitInfo.nDifficulty) - player._pLevel) + hit += 2 * (monster.level(sgGameInitInfo.nDifficulty) - player.getCharacterLevel()) + 30 - ac; int minhit = GetMinHit(); @@ -1168,7 +1163,7 @@ void MonsterAttackPlayer(Monster &monster, Player &player, int hit, int minDam, blkper = GenerateRnd(100); } int blk = player.GetBlockChance() - (monster.level(sgGameInitInfo.nDifficulty) * 2); - blk = clamp(blk, 0, 100); + blk = std::clamp(blk, 0, 100); if (hper >= hit) return; if (blkper < blk) { @@ -1232,7 +1227,7 @@ void MonsterAttackPlayer(Monster &monster, Player &player, int hit, int minDam, player.position.tile = newPosition; FixPlayerLocation(player, player._pdir); FixPlrWalkTags(player); - dPlayer[newPosition.x][newPosition.y] = player.getId() + 1; + player.occupyTile(newPosition, false); SetPlayerOld(player); } } @@ -1288,7 +1283,7 @@ bool MonsterRangedAttack(Monster &monster) monster.direction, missileType, TARGET_PLAYERS, - monster.getId(), + monster, monster.var2, 0); } @@ -1313,7 +1308,7 @@ bool MonsterRangedSpecialAttack(Monster &monster) monster.direction, static_cast(monster.var1), TARGET_PLAYERS, - monster.getId(), + monster, monster.var3, 0) != nullptr) { @@ -1543,9 +1538,9 @@ void MonsterPetrified(Monster &monster) } } -Monster *AddSkeleton(Point position, Direction dir, bool inMap) +std::optional GetRandomSkeletonTypeIndex() { - size_t typeCount = 0; + int32_t typeCount = 0; size_t skeletonIndexes[SkeletonTypes.size()]; for (size_t i = 0; i < LevelMonsterTypeCount; i++) { if (IsSkel(LevelMonsterTypes[i].type)) { @@ -1554,18 +1549,20 @@ Monster *AddSkeleton(Point position, Direction dir, bool inMap) } if (typeCount == 0) { - return nullptr; + return {}; } const size_t typeIndex = skeletonIndexes[GenerateRnd(typeCount)]; - return AddMonster(position, dir, typeIndex, inMap); + return typeIndex; } -void SpawnSkeleton(Point position, Direction dir) +Monster *AddSkeleton(Point position, Direction dir, bool inMap) { - Monster *skeleton = AddSkeleton(position, dir, true); - if (skeleton != nullptr) - StartSpecialStand(*skeleton, dir); + auto typeIndex = GetRandomSkeletonTypeIndex(); + if (!typeIndex) + return nullptr; + + return AddMonster(position, dir, *typeIndex, inMap); } bool IsLineNotSolid(Point startPoint, Point endPoint) @@ -2181,11 +2178,10 @@ void RhinoAi(Monster &monster) if (distanceToEnemy >= 5 && v < 2 * monster.intelligence + 43 && LineClear([&monster](Point position) { return IsTileAvailable(monster, position); }, monster.position.tile, monster.enemyPosition)) { - size_t monsterId = monster.getId(); - if (AddMissile(monster.position.tile, monster.enemyPosition, md, MissileID::Rhino, TARGET_PLAYERS, monsterId, 0, 0) != nullptr) { + if (AddMissile(monster.position.tile, monster.enemyPosition, md, MissileID::Rhino, TARGET_PLAYERS, monster, 0, 0) != nullptr) { if (monster.data().hasSpecialSound) PlayEffect(monster, MonsterSound::Special); - dMonster[monster.position.tile.x][monster.position.tile.y] = -(monsterId + 1); + monster.occupyTile(monster.position.tile, true); monster.mode = MonsterMode::Charge; } } else { @@ -2301,7 +2297,10 @@ void LeoricAi(Monster &monster) && LineClearMissile(monster.position.tile, monster.enemyPosition)) { Point newPosition = monster.position.tile + md; if (IsTileAvailable(monster, newPosition) && ActiveMonsterCount < MaxMonsters) { - SpawnSkeleton(newPosition, md); + auto typeIndex = GetRandomSkeletonTypeIndex(); + if (typeIndex) { + SpawnMonster(newPosition, md, *typeIndex, true); + } StartSpecialStand(monster, md); } } else { @@ -2348,9 +2347,8 @@ void BatAi(Monster &monster) && distanceToEnemy >= 5 && v < 4 * monster.intelligence + 33 && LineClear([&monster](Point position) { return IsTileAvailable(monster, position); }, monster.position.tile, monster.enemyPosition)) { - size_t monsterId = monster.getId(); - if (AddMissile(monster.position.tile, monster.enemyPosition, md, MissileID::Rhino, TARGET_PLAYERS, monsterId, 0, 0) != nullptr) { - dMonster[monster.position.tile.x][monster.position.tile.y] = -(monsterId + 1); + if (AddMissile(monster.position.tile, monster.enemyPosition, md, MissileID::Rhino, TARGET_PLAYERS, monster, 0, 0) != nullptr) { + monster.occupyTile(monster.position.tile, true); monster.mode = MonsterMode::Charge; } } else if (distanceToEnemy >= 2) { @@ -2365,7 +2363,7 @@ void BatAi(Monster &monster) monster.goal = MonsterGoal::Retreat; monster.goalVar1 = 0; if (monster.type().type == MT_FAMILIAR) { - AddMissile(monster.enemyPosition, { monster.enemyPosition.x + 1, 0 }, Direction::South, MissileID::Lightning, TARGET_PLAYERS, monster.getId(), GenerateRnd(10) + 1, 0); + AddMissile(monster.enemyPosition, monster.enemyPosition + Direction::SouthEast, Direction::South, MissileID::Lightning, TARGET_PLAYERS, monster, GenerateRnd(10) + 1, 0); } } @@ -2509,7 +2507,7 @@ void GharbadAi(Monster &monster) if (IsTileVisible(monster.position.tile)) { if (monster.talkMsg == TEXT_GARBUD4) { - if (!effect_is_playing(USFX_GARBUD4) && monster.goal == MonsterGoal::Talking) { + if (!effect_is_playing(SfxID::Gharbad4) && monster.goal == MonsterGoal::Talking) { monster.goal = MonsterGoal::Normal; monster.activeForTicks = UINT8_MAX; monster.talkMsg = TEXT_NONE; @@ -2545,7 +2543,7 @@ void SnotSpilAi(Monster &monster) if (IsTileVisible(monster.position.tile)) { if (monster.talkMsg == TEXT_BANNER12) { - if (!effect_is_playing(USFX_SNOT3) && monster.goal == MonsterGoal::Talking) { + if (!effect_is_playing(SfxID::Snotspill3) && monster.goal == MonsterGoal::Talking) { ObjChangeMap(SetPiece.position.x, SetPiece.position.y, SetPiece.position.x + SetPiece.size.width + 1, SetPiece.position.y + SetPiece.size.height + 1); Quests[Q_LTBANNER]._qvar1 = 3; NetSendCmdQuest(true, Quests[Q_LTBANNER]); @@ -2574,10 +2572,9 @@ void SnakeAi(Monster &monster) unsigned distanceToEnemy = monster.distanceToEnemy(); if (distanceToEnemy >= 2) { if (distanceToEnemy < 3 && LineClear([&monster](Point position) { return IsTileAvailable(monster, position); }, monster.position.tile, monster.enemyPosition) && static_cast(monster.var1) != MonsterMode::Charge) { - size_t monsterId = monster.getId(); - if (AddMissile(monster.position.tile, monster.enemyPosition, md, MissileID::Rhino, TARGET_PLAYERS, monsterId, 0, 0) != nullptr) { + if (AddMissile(monster.position.tile, monster.enemyPosition, md, MissileID::Rhino, TARGET_PLAYERS, monster, 0, 0) != nullptr) { PlayEffect(monster, MonsterSound::Attack); - dMonster[monster.position.tile.x][monster.position.tile.y] = -(monsterId + 1); + monster.occupyTile(monster.position.tile, true); monster.mode = MonsterMode::Charge; } } else if (static_cast(monster.var1) == MonsterMode::Delay || GenerateRnd(100) >= 35 - 2 * monster.intelligence) { @@ -2668,9 +2665,8 @@ void CounselorAi(Monster &monster) } else if (static_cast(monster.var1) == MonsterMode::Delay || GenerateRnd(100) < 2 * monster.intelligence + 20) { StartRangedAttack(monster, MissileID::Null, 0); - size_t monsterId = monster.getId(); - AddMissile(monster.position.tile, { 0, 0 }, monster.direction, MissileID::FlashBottom, TARGET_PLAYERS, monsterId, 4, 0); - AddMissile(monster.position.tile, { 0, 0 }, monster.direction, MissileID::FlashTop, TARGET_PLAYERS, monsterId, 4, 0); + AddMissile(monster.position.tile, { 0, 0 }, monster.direction, MissileID::FlashBottom, TARGET_PLAYERS, monster, 4, 0); + AddMissile(monster.position.tile, { 0, 0 }, monster.direction, MissileID::FlashTop, TARGET_PLAYERS, monster, 4, 0); } else AiDelay(monster, GenerateRnd(10) + 2 * (5 - monster.intelligence)); } @@ -2696,7 +2692,7 @@ void ZharAi(Monster &monster) if (IsTileVisible(monster.position.tile)) { if (monster.talkMsg == TEXT_ZHAR2) { - if (!effect_is_playing(USFX_ZHAR2) && monster.goal == MonsterGoal::Talking) { + if (!effect_is_playing(SfxID::Zhar2) && monster.goal == MonsterGoal::Talking) { monster.activeForTicks = UINT8_MAX; monster.talkMsg = TEXT_NONE; monster.goal = MonsterGoal::Normal; @@ -2792,7 +2788,7 @@ void LazarusAi(Monster &monster) NetSendCmdQuest(true, Quests[Q_BETRAYER]); } - if (monster.talkMsg == TEXT_VILE13 && !effect_is_playing(USFX_LAZ1) && monster.goal == MonsterGoal::Talking) { + if (monster.talkMsg == TEXT_VILE13 && !effect_is_playing(SfxID::LazarusGreeting) && monster.goal == MonsterGoal::Talking) { ObjChangeMap(1, 18, 20, 24); RedoPlayerVision(); Quests[Q_BETRAYER]._qvar1 = 6; @@ -2862,13 +2858,13 @@ void LachdananAi(Monster &monster) if (IsTileVisible(monster.position.tile)) { if (monster.talkMsg == TEXT_VEIL11) { - if (!effect_is_playing(USFX_LACH3) && monster.goal == MonsterGoal::Talking) { + if (!effect_is_playing(SfxID::Lachdanan3) && monster.goal == MonsterGoal::Talking) { monster.talkMsg = TEXT_NONE; Quests[Q_VEIL]._qactive = QUEST_DONE; NetSendCmdQuest(true, Quests[Q_VEIL]); MonsterDeath(monster, monster.direction, true); delta_kill_monster(monster, monster.position.tile, *MyPlayer); - NetSendCmdLocParam1(false, CMD_MONSTDEATH, monster.position.tile, monster.getId()); + NetSendCmdLocParam1(false, CMD_MONSTDEATH, monster.position.tile, static_cast(monster.getId())); } } } @@ -2886,7 +2882,7 @@ void WarlordAi(Monster &monster) if (IsTileVisible(monster.position.tile)) { if (monster.talkMsg == TEXT_WARLRD9 && monster.goal == MonsterGoal::Inquiring) monster.mode = MonsterMode::Talk; - if (monster.talkMsg == TEXT_WARLRD9 && !effect_is_playing(USFX_WARLRD1) && monster.goal == MonsterGoal::Talking) { + if (monster.talkMsg == TEXT_WARLRD9 && !effect_is_playing(SfxID::Warlord) && monster.goal == MonsterGoal::Talking) { monster.activeForTicks = UINT8_MAX; monster.talkMsg = TEXT_NONE; monster.goal = MonsterGoal::Normal; @@ -2958,7 +2954,7 @@ void HorkDemonAi(Monster &monster) monster.checkStandAnimationIsLoaded(monster.direction); } -string_view GetMonsterTypeText(const MonsterData &monsterData) +std::string_view GetMonsterTypeText(const MonsterData &monsterData) { switch (monsterData.monsterClass) { case MonsterClass::Animal: @@ -2974,7 +2970,7 @@ string_view GetMonsterTypeText(const MonsterData &monsterData) void ActivateSpawn(Monster &monster, Point position, Direction dir) { - dMonster[position.x][position.y] = monster.getId() + 1; + monster.occupyTile(position, false); monster.position.tile = position; monster.position.future = position; monster.position.old = position; @@ -2983,46 +2979,46 @@ void ActivateSpawn(Monster &monster, Point position, Direction dir) /** Maps from monster AI ID to monster AI function. */ void (*AiProc[])(Monster &monster) = { - /*MonsterAIID::Zombie */ &ZombieAi, - /*MonsterAIID::Fat */ &OverlordAi, - /*MonsterAIID::SkeletonMelee */ &SkeletonAi, - /*MonsterAIID::SkeletonRanged */ &SkeletonBowAi, - /*MonsterAIID::Scavenger */ &ScavengerAi, - /*MonsterAIID::Rhino */ &RhinoAi, - /*MonsterAIID::GoatMelee */ &AiAvoidance, - /*MonsterAIID::GoatRanged */ &AiRanged, - /*MonsterAIID::Fallen */ &FallenAi, - /*MonsterAIID::Magma */ &AiRangedAvoidance, - /*MonsterAIID::SkeletonKing */ &LeoricAi, - /*MonsterAIID::Bat */ &BatAi, - /*MonsterAIID::Gargoyle */ &GargoyleAi, - /*MonsterAIID::Butcher */ &ButcherAi, - /*MonsterAIID::Succubus */ &AiRanged, - /*MonsterAIID::Sneak */ &SneakAi, - /*MonsterAIID::Storm */ &AiRangedAvoidance, - /*MonsterAIID::FireMan */ nullptr, - /*MonsterAIID::Gharbad */ &GharbadAi, - /*MonsterAIID::Acid */ &AiRangedAvoidance, - /*MonsterAIID::AcidUnique */ &AiRanged, - /*MonsterAIID::Golem */ &GolumAi, - /*MonsterAIID::Zhar */ &ZharAi, - /*MonsterAIID::Snotspill */ &SnotSpilAi, - /*MonsterAIID::Snake */ &SnakeAi, - /*MonsterAIID::Counselor */ &CounselorAi, - /*MonsterAIID::Mega */ &MegaAi, - /*MonsterAIID::Diablo */ &AiRangedAvoidance, - /*MonsterAIID::Lazarus */ &LazarusAi, - /*MonsterAIID::LazarusSuccubus */ &LazarusMinionAi, - /*MonsterAIID::Lachdanan */ &LachdananAi, - /*MonsterAIID::Warlord */ &WarlordAi, - /*MonsterAIID::FireBat */ &AiRanged, - /*MonsterAIID::Torchant */ &AiRanged, - /*MonsterAIID::HorkDemon */ &HorkDemonAi, - /*MonsterAIID::Lich */ &AiRanged, - /*MonsterAIID::ArchLich */ &AiRanged, - /*MonsterAIID::Psychorb */ &AiRanged, - /*MonsterAIID::Necromorb*/ &AiRanged, - /*MonsterAIID::BoneDemon*/ &AiRangedAvoidance + /*MonsterAIID::Zombie */ &ZombieAi, + /*MonsterAIID::Fat */ &OverlordAi, + /*MonsterAIID::SkeletonMelee */ &SkeletonAi, + /*MonsterAIID::SkeletonRanged */ &SkeletonBowAi, + /*MonsterAIID::Scavenger */ &ScavengerAi, + /*MonsterAIID::Rhino */ &RhinoAi, + /*MonsterAIID::GoatMelee */ &AiAvoidance, + /*MonsterAIID::GoatRanged */ &AiRanged, + /*MonsterAIID::Fallen */ &FallenAi, + /*MonsterAIID::Magma */ &AiRangedAvoidance, + /*MonsterAIID::SkeletonKing */ &LeoricAi, + /*MonsterAIID::Bat */ &BatAi, + /*MonsterAIID::Gargoyle */ &GargoyleAi, + /*MonsterAIID::Butcher */ &ButcherAi, + /*MonsterAIID::Succubus */ &AiRanged, + /*MonsterAIID::Sneak */ &SneakAi, + /*MonsterAIID::Storm */ &AiRangedAvoidance, + /*MonsterAIID::FireMan */ nullptr, + /*MonsterAIID::Gharbad */ &GharbadAi, + /*MonsterAIID::Acid */ &AiRangedAvoidance, + /*MonsterAIID::AcidUnique */ &AiRanged, + /*MonsterAIID::Golem */ &GolumAi, + /*MonsterAIID::Zhar */ &ZharAi, + /*MonsterAIID::Snotspill */ &SnotSpilAi, + /*MonsterAIID::Snake */ &SnakeAi, + /*MonsterAIID::Counselor */ &CounselorAi, + /*MonsterAIID::Mega */ &MegaAi, + /*MonsterAIID::Diablo */ &AiRangedAvoidance, + /*MonsterAIID::Lazarus */ &LazarusAi, + /*MonsterAIID::LazarusSuccubus*/ &LazarusMinionAi, + /*MonsterAIID::Lachdanan */ &LachdananAi, + /*MonsterAIID::Warlord */ &WarlordAi, + /*MonsterAIID::FireBat */ &AiRanged, + /*MonsterAIID::Torchant */ &AiRanged, + /*MonsterAIID::HorkDemon */ &HorkDemonAi, + /*MonsterAIID::Lich */ &AiRanged, + /*MonsterAIID::ArchLich */ &AiRanged, + /*MonsterAIID::Psychorb */ &AiRanged, + /*MonsterAIID::Necromorb */ &AiRanged, + /*MonsterAIID::BoneDemon */ &AiRangedAvoidance }; bool IsRelativeMoveOK(const Monster &monster, Point position, Direction mdir) @@ -3101,8 +3097,89 @@ bool UpdateModeStance(Monster &monster) } } +MonsterSpritesData LoadMonsterSpritesData(const MonsterData &monsterData) +{ + const size_t numAnims = GetNumAnims(monsterData); + + MonsterSpritesData result; + result.data = MultiFileLoader {}( + numAnims, + FileNameWithCharAffixGenerator({ "monsters\\", monsterData.spritePath() }, DEVILUTIONX_CL2_EXT, Animletter), + result.offsets.data(), + [&monsterData](size_t index) { return monsterData.hasAnim(index); }); + +#ifndef UNPACKED_MPQS + // Convert CL2 to CLX: + std::vector> clxData; + size_t accumulatedSize = 0; + for (size_t i = 0, j = 0; i < numAnims; ++i) { + if (!monsterData.hasAnim(i)) + continue; + const uint32_t begin = result.offsets[j]; + const uint32_t end = result.offsets[j + 1]; + clxData.emplace_back(); + Cl2ToClx(reinterpret_cast(&result.data[begin]), end - begin, + PointerOrValue { monsterData.width }, clxData.back()); + result.offsets[j] = static_cast(accumulatedSize); + accumulatedSize += clxData.back().size(); + ++j; + } + result.offsets[clxData.size()] = static_cast(accumulatedSize); + result.data = nullptr; + result.data = std::unique_ptr(new std::byte[accumulatedSize]); + for (size_t i = 0; i < clxData.size(); ++i) { + memcpy(&result.data[result.offsets[i]], clxData[i].data(), clxData[i].size()); + } +#endif + + return result; +} + +void EnsureMonsterIndexIsActive(size_t monsterId) +{ + assert(monsterId < MaxMonsters); + for (size_t index = 0; index < MaxMonsters; index++) { + if (ActiveMonsters[index] != monsterId) + continue; + if (index < ActiveMonsterCount) + return; // monster is already active + int oldId = ActiveMonsters[ActiveMonsterCount]; + ActiveMonsters[ActiveMonsterCount] = static_cast(monsterId); + ActiveMonsters[index] = oldId; + ActiveMonsterCount += 1; + } +} + } // namespace +size_t AddMonsterType(_monster_id type, placeflag placeflag) +{ + const size_t typeIndex = GetMonsterTypeIndex(type); + CMonster &monsterType = LevelMonsterTypes[typeIndex]; + + if (typeIndex == LevelMonsterTypeCount) { + LevelMonsterTypeCount++; + monsterType.type = type; + const MonsterData &monsterData = MonstersData[type]; + monstimgtot += monsterData.image; + + const size_t numAnims = GetNumAnims(monsterData); + for (size_t i = 0; i < numAnims; ++i) { + AnimStruct &anim = monsterType.anims[i]; + anim.frames = monsterData.frames[i]; + if (monsterData.hasAnim(i)) { + anim.rate = monsterData.rate[i]; + anim.width = monsterData.width; + } + } + + InitMonsterSND(monsterType); + } + + monsterType.placeFlags |= placeflag; + return typeIndex; +} + void InitTRNForUniqueMonster(Monster &monster) { char filestr[64]; @@ -3216,7 +3293,7 @@ void InitLevelMonsters() totalmonsters = MaxMonsters; for (size_t i = 0; i < MaxMonsters; i++) { - ActiveMonsters[i] = i; + ActiveMonsters[i] = static_cast(i); } uniquetrans = 0; @@ -3320,10 +3397,10 @@ void InitMonsterSND(CMonster &monsterType) }; const MonsterData &data = MonstersData[monsterType.type]; - string_view soundSuffix = data.soundSuffix != nullptr ? data.soundSuffix : data.assetsSuffix; + std::string_view soundSuffix = data.soundPath(); for (int i = 0; i < 4; i++) { - string_view prefix = prefixes[i]; + std::string_view prefix = prefixes[i]; if (prefix == "s" && !data.hasSpecialSound) continue; @@ -3335,75 +3412,32 @@ void InitMonsterSND(CMonster &monsterType) } } -void InitMonsterGFX(CMonster &monsterType) +void InitMonsterGFX(CMonster &monsterType, MonsterSpritesData &&spritesData) { + if (HeadlessMode) + return; + const _monster_id mtype = monsterType.type; const MonsterData &monsterData = MonstersData[mtype]; - const size_t numAnims = GetNumAnims(monsterData); - const auto hasAnim = [&monsterData](size_t index) { - return monsterData.frames[index] != 0; - }; - constexpr size_t MaxAnims = 6; - std::array animOffsets; - if (!HeadlessMode) { - monsterType.animData = MultiFileLoader {}( - numAnims, - FileNameWithCharAffixGenerator({ "monsters\\", monsterData.assetsSuffix }, DEVILUTIONX_CL2_EXT, Animletter), - animOffsets.data(), - hasAnim); - } - -#ifndef UNPACKED_MPQS - if (!HeadlessMode) { - // Convert CL2 to CLX: - std::vector> clxData; - size_t accumulatedSize = 0; - for (size_t i = 0, j = 0; i < numAnims; ++i) { - if (!hasAnim(i)) - continue; - const uint32_t begin = animOffsets[j]; - const uint32_t end = animOffsets[j + 1]; - clxData.emplace_back(); - Cl2ToClx(reinterpret_cast(&monsterType.animData[begin]), end - begin, - PointerOrValue { monsterData.width }, clxData.back()); - animOffsets[j] = accumulatedSize; - accumulatedSize += clxData.back().size(); - ++j; - } - animOffsets[clxData.size()] = accumulatedSize; - monsterType.animData = nullptr; - monsterType.animData = std::unique_ptr(new byte[accumulatedSize]); - for (size_t i = 0; i < clxData.size(); ++i) { - memcpy(&monsterType.animData[animOffsets[i]], clxData[i].data(), clxData[i].size()); - } - } -#endif + if (spritesData.data == nullptr) + spritesData = LoadMonsterSpritesData(monsterData); + monsterType.animData = std::move(spritesData.data); + const size_t numAnims = GetNumAnims(monsterData); for (size_t i = 0, j = 0; i < numAnims; ++i) { - AnimStruct &anim = monsterType.anims[i]; - if (!hasAnim(i)) { - anim.frames = 0; + if (!monsterData.hasAnim(i)) { + monsterType.anims[i].sprites = std::nullopt; continue; } - anim.frames = monsterData.frames[i]; - anim.rate = monsterData.rate[i]; - anim.width = monsterData.width; - if (!HeadlessMode) { - const uint32_t begin = animOffsets[j]; - const uint32_t end = animOffsets[j + 1]; - auto spritesData = reinterpret_cast(&monsterType.animData[begin]); - const uint16_t numLists = GetNumListsFromClxListOrSheetBuffer(spritesData, end - begin); - anim.sprites = ClxSpriteListOrSheet { spritesData, numLists }; - } + const uint32_t begin = spritesData.offsets[j]; + const uint32_t end = spritesData.offsets[j + 1]; + auto spritesData = reinterpret_cast(&monsterType.animData[begin]); + const uint16_t numLists = GetNumListsFromClxListOrSheetBuffer(spritesData, end - begin); + monsterType.anims[i].sprites = ClxSpriteListOrSheet { spritesData, numLists }; ++j; } - monsterType.data = &monsterData; - - if (HeadlessMode) - return; - - if (monsterData.trnFile != nullptr) { + if (!monsterData.trnFile.empty()) { InitMonsterTRN(monsterType); } @@ -3450,6 +3484,48 @@ void InitMonsterGFX(CMonster &monsterType) GetMissileSpriteData(MissileGraphicID::DiabloApocalypseBoom).LoadGFX(); } +void InitAllMonsterGFX() +{ + if (HeadlessMode) + return; + + using LevelMonsterTypeIndices = StaticVector; + std::vector monstersBySprite(GetNumMonsterSprites()); + for (size_t i = 0; i < LevelMonsterTypeCount; ++i) { + monstersBySprite[static_cast(LevelMonsterTypes[i].data().spriteId)].emplace_back(i); + } + size_t totalUniqueBytes = 0; + size_t totalBytes = 0; + for (const LevelMonsterTypeIndices &monsterTypes : monstersBySprite) { + if (monsterTypes.empty()) + continue; + CMonster &firstMonster = LevelMonsterTypes[monsterTypes[0]]; + if (firstMonster.animData != nullptr) + continue; + MonsterSpritesData spritesData = LoadMonsterSpritesData(firstMonster.data()); + const size_t spritesDataSize = spritesData.offsets[GetNumAnimsWithGraphics(firstMonster.data())]; + for (size_t i = 1; i < monsterTypes.size(); ++i) { + MonsterSpritesData spritesDataCopy { std::unique_ptr { new std::byte[spritesDataSize] }, spritesData.offsets }; + memcpy(spritesDataCopy.data.get(), spritesData.data.get(), spritesDataSize); + InitMonsterGFX(LevelMonsterTypes[monsterTypes[i]], std::move(spritesDataCopy)); + } + LogVerbose("Loaded monster graphics: {:15s} {:>4d} KiB x{:d}", firstMonster.data().spritePath(), spritesDataSize / 1024, monsterTypes.size()); + totalUniqueBytes += spritesDataSize; + totalBytes += spritesDataSize * monsterTypes.size(); + InitMonsterGFX(firstMonster, std::move(spritesData)); + } + LogVerbose(" Total monster graphics: {:>4d} KiB {:>4d} KiB", totalUniqueBytes / 1024, totalBytes / 1024); + + if (totalUniqueBytes > 0) { + // we loaded new sprites, check if we need to update existing monsters + for (size_t i = 0; i < ActiveMonsterCount; i++) { + Monster &monster = Monsters[ActiveMonsters[i]]; + if (!monster.animInfo.sprites) + SyncMonsterAnim(monster); + } + } +} + void WeakenNaKrul() { if (currlevel != 24 || static_cast(UberDiabloMonsterIndex) >= ActiveMonsterCount) @@ -3491,14 +3567,14 @@ void InitMonsters() if (!setlevel) { if (!gbIsSpawn) PlaceUniqueMonsters(); - int na = 0; + size_t na = 0; for (int s = 16; s < 96; s++) { for (int t = 16; t < 96; t++) { if (!IsTileSolid({ s, t })) na++; } } - int numplacemonsters = na / 30; + size_t numplacemonsters = na / 30; if (gbIsMultiplayer) numplacemonsters += numplacemonsters / 2; if (ActiveMonsterCount + numplacemonsters > MaxMonsters - 10) @@ -3529,6 +3605,8 @@ void InitMonsters() DoUnVision(trigs[i].position + Displacement { s, t }, 15); } } + + InitAllMonsterGFX(); } void SetMapMonsters(const uint16_t *dunData, Point startPosition) @@ -3547,20 +3625,18 @@ void SetMapMonsters(const uint16_t *dunData, Point startPosition) PlaceUniqueMonst(UniqueMonsterType::BlackJade, 0, 0); } - int width = SDL_SwapLE16(dunData[0]); - int height = SDL_SwapLE16(dunData[1]); + WorldTileSize size = GetDunSize(dunData); - int layer2Offset = 2 + width * height; + int layer2Offset = 2 + size.width * size.height; // The rest of the layers are at dPiece scale - width *= 2; - height *= 2; + size *= static_cast(2); - const uint16_t *monsterLayer = &dunData[layer2Offset + width * height]; + const uint16_t *monsterLayer = &dunData[layer2Offset + size.width * size.height]; - for (int j = 0; j < height; j++) { - for (int i = 0; i < width; i++) { - auto monsterId = static_cast(SDL_SwapLE16(monsterLayer[j * width + i])); + for (WorldTileCoord j = 0; j < size.height; j++) { + for (WorldTileCoord i = 0; i < size.width; i++) { + auto monsterId = static_cast(SDL_SwapLE16(monsterLayer[j * size.width + i])); if (monsterId != 0) { const size_t typeIndex = AddMonsterType(MonstConvTbl[monsterId - 1], PLACE_SPECIAL); PlaceMonster(ActiveMonsterCount++, typeIndex, startPosition + Displacement { i, j }); @@ -3574,7 +3650,7 @@ Monster *AddMonster(Point position, Direction dir, size_t typeIndex, bool inMap) if (ActiveMonsterCount < MaxMonsters) { Monster &monster = Monsters[ActiveMonsters[ActiveMonsterCount++]]; if (inMap) - dMonster[position.x][position.y] = monster.getId() + 1; + monster.occupyTile(position, false); InitMonster(monster, dir, typeIndex, position); return &monster; } @@ -3582,6 +3658,62 @@ Monster *AddMonster(Point position, Direction dir, size_t typeIndex, bool inMap) return nullptr; } +void SpawnMonster(Point position, Direction dir, size_t typeIndex, bool startSpecialStand /*= false*/) +{ + if (ActiveMonsterCount >= MaxMonsters) + return; + + // The command is only executed for the level owner, to prevent desyncs in multiplayer. + if (!MyPlayer->isLevelOwnedByLocalClient()) + return; + + size_t monsterIndex = ActiveMonsters[ActiveMonsterCount]; + ActiveMonsterCount += 1; + uint32_t seed = GetLCGEngineState(); + // Update local state immediately to increase ActiveMonsterCount instantly (this allows multiple monsters to be spawned in one game tick) + InitializeSpawnedMonster(position, dir, typeIndex, monsterIndex, seed); + NetSendCmdSpawnMonster(position, dir, static_cast(typeIndex), static_cast(monsterIndex), seed); +} + +void LoadDeltaSpawnedMonster(size_t typeIndex, size_t monsterId, uint32_t seed) +{ + SetRndSeed(seed); + EnsureMonsterIndexIsActive(monsterId); + WorldTilePosition position = GolemHoldingCell; + Monster &monster = Monsters[monsterId]; + M_ClearSquares(monster); + InitMonster(monster, Direction::South, typeIndex, position); +} + +void InitializeSpawnedMonster(Point position, Direction dir, size_t typeIndex, size_t monsterId, uint32_t seed) +{ + SetRndSeed(seed); + EnsureMonsterIndexIsActive(monsterId); + Monster &monster = Monsters[monsterId]; + M_ClearSquares(monster); + + // When we receive a network message, the position we got for the new monster may already be occupied. + // That's why we check for the next free tile for the monster. + auto freePosition = Crawl(0, MaxCrawlRadius, [&](Displacement displacement) -> std::optional { + Point posToCheck = position + displacement; + if (IsTileAvailable(posToCheck)) + return posToCheck; + return {}; + }); + + assert(freePosition); + assert(!MyPlayer->isLevelOwnedByLocalClient() || (freePosition && position == *freePosition)); + position = freePosition.value_or(position); + + monster.occupyTile(position, false); + InitMonster(monster, dir, typeIndex, position); + + if (IsSkel(monster.type().type)) + StartSpecialStand(monster, dir); + else + M_StartStand(monster, dir); +} + void AddDoppelganger(Monster &monster) { Point target = { 0, 0 }; @@ -3593,7 +3725,7 @@ void AddDoppelganger(Monster &monster) } if (target != Point { 0, 0 }) { const size_t typeIndex = GetMonsterTypeIndex(monster.type().type); - AddMonster(target, monster.direction, typeIndex, true); + SpawnMonster(target, monster.direction, typeIndex); } } @@ -3605,12 +3737,12 @@ void ApplyMonsterDamage(DamageType damageType, Monster &monster, int damage) if (monster.hitPoints >> 6 <= 0) { delta_kill_monster(monster, monster.position.tile, *MyPlayer); - NetSendCmdLocParam1(false, CMD_MONSTDEATH, monster.position.tile, monster.getId()); + NetSendCmdLocParam1(false, CMD_MONSTDEATH, monster.position.tile, static_cast(monster.getId())); return; } delta_monster_hp(monster, *MyPlayer); - NetSendCmdMonDmg(false, monster.getId(), damage); + NetSendCmdMonDmg(false, static_cast(monster.getId()), damage); } bool M_Talker(const Monster &monster) @@ -3716,11 +3848,11 @@ void MonsterDeath(Monster &monster, Direction md, bool sendmsg) monster.position.tile = monster.position.old; monster.position.future = monster.position.old; M_ClearSquares(monster); - dMonster[monster.position.tile.x][monster.position.tile.y] = monster.getId() + 1; + monster.occupyTile(monster.position.tile, false); CheckQuestKill(monster, sendmsg); M_FallenFear(monster.position.tile); if (IsAnyOf(monster.type().type, MT_NACID, MT_RACID, MT_BACID, MT_XACID, MT_SPIDLORD)) - AddMissile(monster.position.tile, { 0, 0 }, Direction::South, MissileID::AcidPuddle, TARGET_PLAYERS, monster.getId(), monster.intelligence + 1, 0); + AddMissile(monster.position.tile, { 0, 0 }, Direction::South, MissileID::AcidPuddle, TARGET_PLAYERS, monster, monster.intelligence + 1, 0); } void StartMonsterDeath(Monster &monster, const Player &player, bool sendmsg) @@ -3894,7 +4026,7 @@ void GolumAi(Monster &golem) int mex = golem.position.tile.x - enemy.position.future.x; int mey = golem.position.tile.y - enemy.position.future.y; golem.direction = GetDirection(golem.position.tile, enemy.position.tile); - if (abs(mex) < 2 && abs(mey) < 2) { + if (std::abs(mex) < 2 && std::abs(mey) < 2) { golem.enemyPosition = enemy.position.tile; if (enemy.activeForTicks == 0) { enemy.activeForTicks = UINT8_MAX; @@ -3981,20 +4113,20 @@ void ProcessMonsters() if (IsTileVisible(monster.position.tile) && monster.activeForTicks == 0) { if (monster.type().type == MT_CLEAVER) { - PlaySFX(USFX_CLEAVER); + PlaySFX(SfxID::ButcherGreeting); } if (monster.type().type == MT_NAKRUL) { if (sgGameInitInfo.bCowQuest != 0) { - PlaySFX(USFX_NAKRUL6); + PlaySFX(SfxID::NaKrul6); } else { if (IsUberRoomOpened) - PlaySFX(USFX_NAKRUL4); + PlaySFX(SfxID::NaKrul4); else - PlaySFX(USFX_NAKRUL5); + PlaySFX(SfxID::NaKrul5); } } if (monster.type().type == MT_DEFILER) - PlaySFX(USFX_DEFILER8); + PlaySFX(SfxID::Defiler8); UpdateEnemy(monster); } @@ -4036,6 +4168,7 @@ void FreeMonsters() { for (CMonster &monsterType : LevelMonsterTypes) { monsterType.animData = nullptr; + monsterType.corpseId = 0; for (AnimStruct &animData : monsterType.anims) { animData.sprites = std::nullopt; } @@ -4092,7 +4225,7 @@ bool LineClear(tl::function_ref clear, Point startPoint, Point endP int dx = endPoint.x - position.x; int dy = endPoint.y - position.y; - if (abs(dx) > abs(dy)) { + if (std::abs(dx) > std::abs(dy)) { if (dx < 0) { std::swap(position, endPoint); dx = -dx; @@ -4165,7 +4298,7 @@ void SyncMonsterAnim(Monster &monster) #ifdef _DEBUG // fix for saves with debug monsters having type originally not on the level CMonster &monsterType = LevelMonsterTypes[monster.levelType]; - if (monsterType.data == nullptr) { + if (monsterType.corpseId == 0) { InitMonsterGFX(monsterType); monsterType.corpseId = 1; } @@ -4224,7 +4357,7 @@ void M_FallenFear(Point position) int m = dMonster[tile.x][tile.y]; if (m == 0) continue; - Monster &monster = Monsters[abs(m) - 1]; + Monster &monster = Monsters[std::abs(m) - 1]; if (monster.ai != MonsterAIID::Fallen || monster.hitPoints >> 6 <= 0) continue; @@ -4282,21 +4415,21 @@ void PrintMonstHistory(int mt) if ((res & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING)) != 0) { std::string resists = std::string(_("Resists:")); if ((res & RESIST_MAGIC) != 0) - AppendStrView(resists, _(" Magic")); + resists.append(_(" Magic")); if ((res & RESIST_FIRE) != 0) - AppendStrView(resists, _(" Fire")); + resists.append(_(" Fire")); if ((res & RESIST_LIGHTNING) != 0) - AppendStrView(resists, _(" Lightning")); + resists.append(_(" Lightning")); AddPanelString(resists); } if ((res & (IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING)) != 0) { std::string immune = std::string(_("Immune:")); if ((res & IMMUNE_MAGIC) != 0) - AppendStrView(immune, _(" Magic")); + immune.append(_(" Magic")); if ((res & IMMUNE_FIRE) != 0) - AppendStrView(immune, _(" Fire")); + immune.append(_(" Fire")); if ((res & IMMUNE_LIGHTNING) != 0) - AppendStrView(immune, _(" Lightning")); + immune.append(_(" Lightning")); AddPanelString(immune); } } @@ -4353,13 +4486,11 @@ void PlayEffect(Monster &monster, MonsterSound mode) void MissToMonst(Missile &missile, Point position) { - int monsterId = missile._misource; - - assert(static_cast(monsterId) < MaxMonsters); - auto &monster = Monsters[monsterId]; + assert(static_cast(missile._misource) < MaxMonsters); + auto &monster = Monsters[missile._misource]; Point oldPosition = missile.position.tile; - dMonster[position.x][position.y] = monsterId + 1; + monster.occupyTile(position, false); monster.direction = static_cast(missile._mimfnum); monster.position.tile = position; M_StartStand(monster, monster.direction); @@ -4369,25 +4500,24 @@ void MissToMonst(Missile &missile, Point position) return; if ((monster.flags & MFLAG_TARGETS_MONSTER) == 0) { - if (dPlayer[oldPosition.x][oldPosition.y] <= 0) + Player *player = PlayerAtPosition(oldPosition, true); + if (player == nullptr) return; - int pnum = dPlayer[oldPosition.x][oldPosition.y] - 1; - Player &player = Players[pnum]; - MonsterAttackPlayer(monster, player, 500, monster.minDamageSpecial, monster.maxDamageSpecial); + MonsterAttackPlayer(monster, *player, 500, monster.minDamageSpecial, monster.maxDamageSpecial); if (IsAnyOf(monster.type().type, MT_NSNAKE, MT_RSNAKE, MT_BSNAKE, MT_GSNAKE)) return; - if (player._pmode != PM_GOTHIT && player._pmode != PM_DEATH) - StartPlrHit(player, 0, true); + if (player->_pmode != PM_GOTHIT && player->_pmode != PM_DEATH) + StartPlrHit(*player, 0, true); Point newPosition = oldPosition + monster.direction; - if (PosOkPlayer(player, newPosition)) { - player.position.tile = newPosition; - FixPlayerLocation(player, player._pdir); - FixPlrWalkTags(player); - dPlayer[newPosition.x][newPosition.y] = pnum + 1; - SetPlayerOld(player); + if (PosOkPlayer(*player, newPosition)) { + player->position.tile = newPosition; + FixPlayerLocation(*player, player->_pdir); + FixPlrWalkTags(*player); + player->occupyTile(newPosition, false); + SetPlayerOld(*player); } return; } @@ -4404,10 +4534,8 @@ void MissToMonst(Missile &missile, Point position) Point newPosition = oldPosition + monster.direction; if (IsTileAvailable(*target, newPosition)) { - monsterId = dMonster[oldPosition.x][oldPosition.y]; - dMonster[newPosition.x][newPosition.y] = monsterId; + monster.occupyTile(newPosition, false); dMonster[oldPosition.x][oldPosition.y] = 0; - monsterId--; monster.position.tile = newPosition; monster.position.future = newPosition; } @@ -4426,7 +4554,7 @@ Monster *FindMonsterAtPosition(Point position, bool ignoreMovingMonsters) return nullptr; } - return &Monsters[abs(monsterId) - 1]; + return &Monsters[std::abs(monsterId) - 1]; } Monster *FindUniqueMonster(UniqueMonsterType monsterType) @@ -4450,7 +4578,7 @@ bool IsTileAvailable(const Monster &monster, Point position) bool IsSkel(_monster_id mt) { - return std::find(std::begin(SkeletonTypes), std::end(SkeletonTypes), mt) != std::end(SkeletonTypes); + return c_find(SkeletonTypes, mt) != SkeletonTypes.end(); } bool IsGoat(_monster_id mt) @@ -4562,7 +4690,7 @@ void TalktoMonster(Player &player, Monster &monster) void SpawnGolem(Player &player, Monster &golem, Point position, Missile &missile) { - dMonster[position.x][position.y] = golem.getId() + 1; + golem.occupyTile(position, false); golem.position.tile = position; golem.position.future = position; golem.position.old = position; @@ -4570,7 +4698,7 @@ void SpawnGolem(Player &player, Monster &golem, Point position, Missile &missile golem.maxHitPoints = 2 * (320 * missile._mispllvl + player._pMaxMana / 3); golem.hitPoints = golem.maxHitPoints; golem.armorClass = 25; - golem.toHit = 5 * (missile._mispllvl + 8) + 2 * player._pLevel; + golem.toHit = 5 * (missile._mispllvl + 8) + 2 * player.getCharacterLevel(); golem.minDamage = 2 * (missile._mispllvl + 4); golem.maxDamage = 2 * (missile._mispllvl + 8); golem.flags |= MFLAG_GOLEM; @@ -4637,7 +4765,7 @@ void Monster::setLeader(const Monster *leader) return; } - this->leader = leader->getId(); + this->leader = static_cast(leader->getId()); leaderRelation = LeaderRelation::Leashed; ai = leader->ai; } @@ -4757,4 +4885,10 @@ unsigned int Monster::toHitSpecial(_difficulty difficulty) const return baseToHitSpecial; } +void Monster::occupyTile(Point position, bool isMoving) const +{ + int16_t id = static_cast(this->getId() + 1); + dMonster[position.x][position.y] = isMoving ? -id : id; +} + } // namespace devilution diff --git a/Source/monster.h b/Source/monster.h index a8dba5a4676..f857a48ab27 100644 --- a/Source/monster.h +++ b/Source/monster.h @@ -175,16 +175,26 @@ enum class MonsterSound : uint8_t { Special }; +struct MonsterSpritesData { + static constexpr size_t MaxAnims = 6; + std::unique_ptr data; + std::array offsets; +}; + struct CMonster { - std::unique_ptr animData; + std::unique_ptr animData; AnimStruct anims[6]; std::unique_ptr sounds[4][2]; - const MonsterData *data; _monster_id type; /** placeflag enum as a flags*/ uint8_t placeFlags; - int8_t corpseId; + int8_t corpseId = 0; + + const MonsterData &data() const + { + return MonstersData[type]; + } /** * @brief Returns AnimStruct for specified graphic @@ -313,7 +323,7 @@ struct Monster { // note: missing field _mAFNum const MonsterData &data() const { - return *type().data; + return type().data(); } /** @@ -321,7 +331,7 @@ struct Monster { // note: missing field _mAFNum * Internally it returns a name stored in global array of monsters' data. * @return Monster's name */ - string_view name() const + std::string_view name() const { if (uniqueType != UniqueMonsterType::None) return pgettext("monster", UniqueMonstersData[static_cast(uniqueType)].mName); @@ -445,6 +455,21 @@ struct Monster { // note: missing field _mAFNum * But for graphics and rendering we show the old/real mode. */ [[nodiscard]] MonsterMode getVisualMonsterMode() const; + + [[nodiscard]] Displacement getRenderingOffset(const ClxSprite sprite) const + { + Displacement offset = { -CalculateWidth2(sprite.width()), 0 }; + if (isWalking()) + offset += GetOffsetForWalking(animInfo, direction); + return offset; + } + + /** + * @brief Sets a tile/dMonster to be occupied by the monster + * @param position tile to update + * @param isMoving specifies whether the monster is moving or not (true/moving results in a negative index in dMonster) + */ + void occupyTile(Point position, bool isMoving) const; }; extern size_t LevelMonsterTypeCount; @@ -457,13 +482,33 @@ extern bool sgbSaveSoundOn; void PrepareUniqueMonst(Monster &monster, UniqueMonsterType monsterType, size_t miniontype, int bosspacksize, const UniqueMonsterData &uniqueMonsterData); void InitLevelMonsters(); void GetLevelMTypes(); +size_t AddMonsterType(_monster_id type, placeflag placeflag); +inline size_t AddMonsterType(UniqueMonsterType uniqueType, placeflag placeflag) +{ + return AddMonsterType(UniqueMonstersData[static_cast(uniqueType)].mtype, placeflag); +} void InitMonsterSND(CMonster &monsterType); -void InitMonsterGFX(CMonster &monsterType); +void InitMonsterGFX(CMonster &monsterType, MonsterSpritesData &&spritesData = {}); +void InitAllMonsterGFX(); void WeakenNaKrul(); void InitGolems(); void InitMonsters(); void SetMapMonsters(const uint16_t *dunData, Point startPosition); Monster *AddMonster(Point position, Direction dir, size_t mtype, bool inMap); +/** + * @brief Spawns a new monsters (dynamically/not on level load). + * The command is only executed for the level owner, to prevent desyncs in multiplayer. + * The level owner sends a CMD_SPAWNMONSTER-message to the other players. + */ +void SpawnMonster(Point position, Direction dir, size_t typeIndex, bool startSpecialStand = false); +/** + * @brief Loads data for a dynamically spawned monster when entering a level in multiplayer. + */ +void LoadDeltaSpawnedMonster(size_t typeIndex, size_t monsterId, uint32_t seed); +/** + * @brief Initialize a spanwed monster (from a network message or from SpawnMonster-function). + */ +void InitializeSpawnedMonster(Point position, Direction dir, size_t typeIndex, size_t monsterId, uint32_t seed); void AddDoppelganger(Monster &monster); void ApplyMonsterDamage(DamageType damageType, Monster &monster, int damage); bool M_Talker(const Monster &monster); diff --git a/Source/mpq/mpq_common.cpp b/Source/mpq/mpq_common.cpp new file mode 100644 index 00000000000..9a11a11796a --- /dev/null +++ b/Source/mpq/mpq_common.cpp @@ -0,0 +1,18 @@ +#include "mpq/mpq_common.hpp" + +#include + +#include + +namespace devilution { + +#if !defined(UNPACKED_MPQS) || !defined(UNPACKED_SAVES) +MpqFileHash CalculateMpqFileHash(std::string_view filename) +{ + MpqFileHash fileHash; + libmpq__file_hash_s(filename.data(), filename.size(), &fileHash[0], &fileHash[1], &fileHash[2]); + return fileHash; +} +#endif + +} // namespace devilution diff --git a/Source/mpq/mpq_common.hpp b/Source/mpq/mpq_common.hpp index 1fe0fbe5eb9..1eacece6703 100644 --- a/Source/mpq/mpq_common.hpp +++ b/Source/mpq/mpq_common.hpp @@ -1,7 +1,9 @@ #pragma once +#include #include #include +#include #include "utils/endian.hpp" @@ -87,4 +89,10 @@ struct MpqBlockEntry { }; #pragma pack(pop) +using MpqFileHash = std::array; + +#if !defined(UNPACKED_MPQS) || !defined(UNPACKED_SAVES) +MpqFileHash CalculateMpqFileHash(std::string_view filename); +#endif + } // namespace devilution diff --git a/Source/mpq/mpq_reader.cpp b/Source/mpq/mpq_reader.cpp index 85f0e1629ab..23828cbecb4 100644 --- a/Source/mpq/mpq_reader.cpp +++ b/Source/mpq/mpq_reader.cpp @@ -1,11 +1,11 @@ #include "mpq/mpq_reader.hpp" #include +#include +#include #include -#include "utils/stdcompat/optional.hpp" - namespace devilution { std::optional MpqArchive::Open(const char *path, int32_t &error) @@ -34,13 +34,6 @@ const char *MpqArchive::ErrorMessage(int32_t errorCode) return libmpq__strerror(errorCode); } -MpqArchive::FileHash MpqArchive::CalculateFileHash(const char *filename) -{ - FileHash fileHash; - libmpq__file_hash(filename, &fileHash[0], &fileHash[1], &fileHash[2]); - return fileHash; -} - MpqArchive &MpqArchive::operator=(MpqArchive &&other) noexcept { path_ = std::move(other.path_); @@ -57,16 +50,16 @@ MpqArchive::~MpqArchive() libmpq__archive_close(archive_); } -bool MpqArchive::GetFileNumber(MpqArchive::FileHash fileHash, uint32_t &fileNumber) +bool MpqArchive::GetFileNumber(MpqFileHash fileHash, uint32_t &fileNumber) { return libmpq__file_number_from_hash(archive_, fileHash[0], fileHash[1], fileHash[2], &fileNumber) == 0; } -std::unique_ptr MpqArchive::ReadFile(const char *filename, std::size_t &fileSize, int32_t &error) +std::unique_ptr MpqArchive::ReadFile(std::string_view filename, std::size_t &fileSize, int32_t &error) { - std::unique_ptr result; + std::unique_ptr result; std::uint32_t fileNumber; - error = libmpq__file_number(archive_, filename, &fileNumber); + error = libmpq__file_number_s(archive_, filename.data(), filename.size(), &fileNumber); if (error != 0) return result; @@ -79,7 +72,7 @@ std::unique_ptr MpqArchive::ReadFile(const char *filename, std::size_t & if (error != 0) return result; - result = std::make_unique(unpackedSize); + result = std::make_unique(static_cast(unpackedSize)); const std::size_t blockSize = GetBlockSize(fileNumber, 0, error); if (error != 0) @@ -89,8 +82,8 @@ std::unique_ptr MpqArchive::ReadFile(const char *filename, std::size_t & if (error != 0) return result; - error = libmpq__file_read_with_filename_and_temporary_buffer( - archive_, fileNumber, filename, reinterpret_cast(result.get()), unpackedSize, + error = libmpq__file_read_with_filename_and_temporary_buffer_s( + archive_, fileNumber, filename.data(), filename.size(), reinterpret_cast(result.get()), unpackedSize, tmp.data(), static_cast(blockSize), nullptr); if (error != 0) { result = nullptr; @@ -99,11 +92,11 @@ std::unique_ptr MpqArchive::ReadFile(const char *filename, std::size_t & } CloseBlockOffsetTable(fileNumber); - fileSize = unpackedSize; + fileSize = static_cast(unpackedSize); return result; } -int32_t MpqArchive::ReadBlock(uint32_t fileNumber, uint32_t blockNumber, uint8_t *out, uint32_t outSize) +int32_t MpqArchive::ReadBlock(uint32_t fileNumber, uint32_t blockNumber, uint8_t *out, size_t outSize) { std::vector &tmpBuf = GetTemporaryBuffer(outSize); return libmpq__block_read_with_temporary_buffer( @@ -116,7 +109,7 @@ std::size_t MpqArchive::GetUnpackedFileSize(uint32_t fileNumber, int32_t &error) { libmpq__off_t unpackedSize; error = libmpq__file_size_unpacked(archive_, fileNumber, &unpackedSize); - return unpackedSize; + return static_cast(unpackedSize); } uint32_t MpqArchive::GetNumBlocks(uint32_t fileNumber, int32_t &error) @@ -126,9 +119,9 @@ uint32_t MpqArchive::GetNumBlocks(uint32_t fileNumber, int32_t &error) return numBlocks; } -int32_t MpqArchive::OpenBlockOffsetTable(uint32_t fileNumber, const char *filename) +int32_t MpqArchive::OpenBlockOffsetTable(uint32_t fileNumber, std::string_view filename) { - return libmpq__block_open_offset_with_filename(archive_, fileNumber, filename); + return libmpq__block_open_offset_with_filename_s(archive_, fileNumber, filename.data(), filename.size()); } int32_t MpqArchive::CloseBlockOffsetTable(uint32_t fileNumber) @@ -141,13 +134,13 @@ std::size_t MpqArchive::GetBlockSize(uint32_t fileNumber, uint32_t blockNumber, { libmpq__off_t blockSize; error = libmpq__block_size_unpacked(archive_, fileNumber, blockNumber, &blockSize); - return blockSize; + return static_cast(blockSize); } -bool MpqArchive::HasFile(const char *filename) const +bool MpqArchive::HasFile(std::string_view filename) const { std::uint32_t fileNumber; - int32_t error = libmpq__file_number(archive_, filename, &fileNumber); + int32_t error = libmpq__file_number_s(archive_, filename.data(), filename.size(), &fileNumber); return error == 0; } diff --git a/Source/mpq/mpq_reader.hpp b/Source/mpq/mpq_reader.hpp index 995eef113fe..eb6fa46d0a7 100644 --- a/Source/mpq/mpq_reader.hpp +++ b/Source/mpq/mpq_reader.hpp @@ -4,11 +4,12 @@ #include #include #include +#include #include +#include #include -#include "utils/stdcompat/cstddef.hpp" -#include "utils/stdcompat/optional.hpp" +#include "mpq/mpq_common.hpp" // Forward-declare so that we can avoid exposing libmpq. struct mpq_archive; @@ -25,9 +26,6 @@ class MpqArchive { static const char *ErrorMessage(int32_t errorCode); - using FileHash = std::array; - static FileHash CalculateFileHash(const char *filename); - MpqArchive(MpqArchive &&other) noexcept : path_(std::move(other.path_)) , archive_(other.archive_) @@ -41,25 +39,25 @@ class MpqArchive { ~MpqArchive(); // Returns false if the file does not exit. - bool GetFileNumber(FileHash fileHash, uint32_t &fileNumber); + bool GetFileNumber(MpqFileHash fileHash, uint32_t &fileNumber); - std::unique_ptr ReadFile(const char *filename, std::size_t &fileSize, int32_t &error); + std::unique_ptr ReadFile(std::string_view filename, std::size_t &fileSize, int32_t &error); // Returns error code. - int32_t ReadBlock(uint32_t fileNumber, uint32_t blockNumber, uint8_t *out, uint32_t outSize); + int32_t ReadBlock(uint32_t fileNumber, uint32_t blockNumber, uint8_t *out, size_t outSize); std::size_t GetUnpackedFileSize(uint32_t fileNumber, int32_t &error); uint32_t GetNumBlocks(uint32_t fileNumber, int32_t &error); - int32_t OpenBlockOffsetTable(uint32_t fileNumber, const char *filename); + int32_t OpenBlockOffsetTable(uint32_t fileNumber, std::string_view filename); int32_t CloseBlockOffsetTable(uint32_t fileNumber); // Requires the block offset table to be open std::size_t GetBlockSize(uint32_t fileNumber, uint32_t blockNumber, int32_t &error); - bool HasFile(const char *filename) const; + bool HasFile(std::string_view filename) const; private: MpqArchive(std::string path, mpq_archive_s *archive) diff --git a/Source/mpq/mpq_sdl_rwops.cpp b/Source/mpq/mpq_sdl_rwops.cpp index 529cb39c2f8..a49a04f8ed0 100644 --- a/Source/mpq/mpq_sdl_rwops.cpp +++ b/Source/mpq/mpq_sdl_rwops.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace devilution { @@ -14,13 +15,13 @@ struct Data { std::optional ownedArchive; MpqArchive *mpqArchive; uint32_t fileNumber; - uint32_t blockSize; - uint32_t lastBlockSize; + size_t blockSize; + size_t lastBlockSize; uint32_t numBlocks; - uint32_t size; + size_t size; // State: - uint32_t position; + size_t position; bool blockRead; std::unique_ptr blockData; }; @@ -55,25 +56,25 @@ static Sint64 MpqFileRwSize(struct SDL_RWops *context) static OffsetType MpqFileRwSeek(struct SDL_RWops *context, OffsetType offset, int whence) { Data &data = *GetData(context); - OffsetType newPosition; + size_t newPosition; switch (whence) { case RW_SEEK_SET: - newPosition = offset; + newPosition = static_cast(offset); break; case RW_SEEK_CUR: - newPosition = data.position + offset; + newPosition = static_cast(data.position + offset); break; case RW_SEEK_END: - newPosition = data.size + offset; + newPosition = static_cast(data.size + offset); break; default: return -1; } - if (newPosition == static_cast(data.position)) - return newPosition; + if (newPosition == data.position) + return static_cast(newPosition); - if (newPosition > static_cast(data.size)) { + if (newPosition > data.size) { SDL_SetError("MpqFileRwSeek beyond EOF (%d > %u)", static_cast(newPosition), data.size); return -1; } @@ -88,14 +89,14 @@ static OffsetType MpqFileRwSeek(struct SDL_RWops *context, OffsetType offset, in data.position = newPosition; - return newPosition; + return static_cast(newPosition); } static SizeType MpqFileRwRead(struct SDL_RWops *context, void *ptr, SizeType size, SizeType maxnum) { Data &data = *GetData(context); - const SizeType totalSize = size * maxnum; - uint32_t remainingSize = totalSize; + const size_t totalSize = size * maxnum; + size_t remainingSize = totalSize; auto *out = static_cast(ptr); @@ -103,13 +104,13 @@ static SizeType MpqFileRwRead(struct SDL_RWops *context, void *ptr, SizeType siz data.blockData = std::unique_ptr { new uint8_t[data.blockSize] }; } - uint32_t blockNumber = data.position / data.blockSize; + uint32_t blockNumber = static_cast(data.position / data.blockSize); while (remainingSize > 0) { if (data.position == data.size) { break; } - const uint32_t currentBlockSize = blockNumber + 1 == data.numBlocks ? data.lastBlockSize : data.blockSize; + const size_t currentBlockSize = blockNumber + 1 == data.numBlocks ? data.lastBlockSize : data.blockSize; if (!data.blockRead) { const int32_t error = data.mpqArchive->ReadBlock(data.fileNumber, blockNumber, data.blockData.get(), currentBlockSize); @@ -120,8 +121,8 @@ static SizeType MpqFileRwRead(struct SDL_RWops *context, void *ptr, SizeType siz data.blockRead = true; } - const uint32_t blockPosition = data.position - blockNumber * data.blockSize; - const uint32_t remainingBlockSize = currentBlockSize - blockPosition; + const size_t blockPosition = data.position - blockNumber * data.blockSize; + const size_t remainingBlockSize = currentBlockSize - blockPosition; if (remainingSize < remainingBlockSize) { std::memcpy(out, data.blockData.get() + blockPosition, remainingSize); @@ -137,7 +138,7 @@ static SizeType MpqFileRwRead(struct SDL_RWops *context, void *ptr, SizeType siz data.blockRead = false; } - return (totalSize - remainingSize) / size; + return static_cast((totalSize - remainingSize) / size); } static int MpqFileRwClose(struct SDL_RWops *context) @@ -153,7 +154,7 @@ static int MpqFileRwClose(struct SDL_RWops *context) } // namespace -SDL_RWops *SDL_RWops_FromMpqFile(MpqArchive &mpqArchive, uint32_t fileNumber, const char *filename, bool threadsafe) +SDL_RWops *SDL_RWops_FromMpqFile(MpqArchive &mpqArchive, uint32_t fileNumber, std::string_view filename, bool threadsafe) { auto result = std::make_unique(); std::memset(result.get(), 0, sizeof(*result)); @@ -205,7 +206,7 @@ SDL_RWops *SDL_RWops_FromMpqFile(MpqArchive &mpqArchive, uint32_t fileNumber, co } data->numBlocks = numBlocks; - const std::uint32_t blockSize = archive.GetBlockSize(fileNumber, 0, error); + const size_t blockSize = archive.GetBlockSize(fileNumber, 0, error); if (error != 0) { SDL_SetError("MpqFileRwRead GetBlockSize: %s", MpqArchive::ErrorMessage(error)); return nullptr; diff --git a/Source/mpq/mpq_sdl_rwops.hpp b/Source/mpq/mpq_sdl_rwops.hpp index e5ead283b06..ea57da27b60 100644 --- a/Source/mpq/mpq_sdl_rwops.hpp +++ b/Source/mpq/mpq_sdl_rwops.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include @@ -8,6 +9,6 @@ namespace devilution { -SDL_RWops *SDL_RWops_FromMpqFile(MpqArchive &mpqArchive, uint32_t fileNumber, const char *filename, bool threadsafe); +SDL_RWops *SDL_RWops_FromMpqFile(MpqArchive &mpqArchive, uint32_t fileNumber, std::string_view filename, bool threadsafe); } // namespace devilution diff --git a/Source/mpq/mpq_writer.cpp b/Source/mpq/mpq_writer.cpp index 1d3ab8b5082..cd13ef6e34f 100644 --- a/Source/mpq/mpq_writer.cpp +++ b/Source/mpq/mpq_writer.cpp @@ -6,6 +6,8 @@ #include #include +#include + #include "appfat.h" #include "encrypt.h" #include "engine.h" @@ -95,11 +97,13 @@ MpqWriter::MpqWriter(const char *path) const char *mode = "wb"; if (exists) { mode = "r+b"; - if (!GetFileSize(path, &size_)) { + std::uintmax_t fileSize; + if (!GetFileSize(path, &fileSize)) { error = R"(GetFileSize failed: "{}")"; LogError(error, path, std::strerror(errno)); goto on_error; } + size_ = static_cast(fileSize); LogVerbose("GetFileSize(\"{}\") = {}", path, size_); } if (!stream_.Open(path, mode)) { @@ -125,8 +129,7 @@ MpqWriter::MpqWriter(const char *path) error = "Failed to read block table"; goto on_error; } - uint32_t key = Hash("(block table)", 3); - Decrypt(reinterpret_cast(blockTable_.get()), fhdr.blockEntriesCount * sizeof(MpqBlockEntry), key); + libmpq__decrypt_block(reinterpret_cast(blockTable_.get()), fhdr.blockEntriesCount * sizeof(MpqBlockEntry), LIBMPQ_BLOCK_TABLE_HASH_KEY); } hashTable_ = std::make_unique(HashEntriesCount); @@ -138,8 +141,7 @@ MpqWriter::MpqWriter(const char *path) error = "Failed to read hash entries"; goto on_error; } - uint32_t key = Hash("(hash table)", 3); - Decrypt(reinterpret_cast(hashTable_.get()), fhdr.hashEntriesCount * sizeof(MpqHashEntry), key); + libmpq__decrypt_block(reinterpret_cast(hashTable_.get()), fhdr.hashEntriesCount * sizeof(MpqHashEntry), LIBMPQ_HASH_TABLE_HASH_KEY); } #ifndef CAN_SEEKP_BEYOND_EOF @@ -179,9 +181,9 @@ MpqWriter::~MpqWriter() LogVerbose("Closing failed {}", name_); } -uint32_t MpqWriter::FetchHandle(const char *filename) const +uint32_t MpqWriter::FetchHandle(std::string_view filename) const { - return GetHashIndex(Hash(filename, 0), Hash(filename, 1), Hash(filename, 2)); + return GetHashIndex(CalculateMpqFileHash(filename)); } void MpqWriter::InitDefaultMpqHeader(MpqFileHeader *hdr) @@ -305,15 +307,15 @@ uint32_t MpqWriter::FindFreeBlock(uint32_t size) return result; } -uint32_t MpqWriter::GetHashIndex(uint32_t index, uint32_t hashA, uint32_t hashB) const // NOLINT(bugprone-easily-swappable-parameters) +uint32_t MpqWriter::GetHashIndex(MpqFileHash fileHash) const // NOLINT(bugprone-easily-swappable-parameters) { uint32_t i = HashEntriesCount; - for (unsigned idx = index & 0x7FF; hashTable_[idx].block != MpqHashEntry::NullBlock; idx = (idx + 1) & 0x7FF) { + for (unsigned idx = fileHash[0] & 0x7FF; hashTable_[idx].block != MpqHashEntry::NullBlock; idx = (idx + 1) & 0x7FF) { if (i-- == 0) break; - if (hashTable_[idx].hashA != hashA) + if (hashTable_[idx].hashA != fileHash[1]) continue; - if (hashTable_[idx].hashB != hashB) + if (hashTable_[idx].hashB != fileHash[2]) continue; if (hashTable_[idx].block == MpqHashEntry::DeletedBlock) continue; @@ -329,14 +331,12 @@ bool MpqWriter::WriteHeaderAndTables() return WriteHeader() && WriteBlockTable() && WriteHashTable(); } -MpqBlockEntry *MpqWriter::AddFile(const char *filename, MpqBlockEntry *block, uint32_t blockIndex) +MpqBlockEntry *MpqWriter::AddFile(std::string_view filename, MpqBlockEntry *block, uint32_t blockIndex) { - uint32_t h1 = Hash(filename, 0); - uint32_t h2 = Hash(filename, 1); - uint32_t h3 = Hash(filename, 2); - if (GetHashIndex(h1, h2, h3) != HashEntryNotFound) + const MpqFileHash fileHash = CalculateMpqFileHash(filename); + if (GetHashIndex(fileHash) != HashEntryNotFound) app_fatal(StrCat("Hash collision between \"", filename, "\" and existing file\n")); - unsigned int hIdx = h1 & 0x7FF; + unsigned int hIdx = fileHash[0] & 0x7FF; bool hasSpace = false; for (unsigned i = 0; i < HashEntriesCount; ++i) { @@ -353,8 +353,8 @@ MpqBlockEntry *MpqWriter::AddFile(const char *filename, MpqBlockEntry *block, ui block = NewBlock(&blockIndex); MpqHashEntry &entry = hashTable_[hIdx]; - entry.hashA = h2; - entry.hashB = h3; + entry.hashA = fileHash[1]; + entry.hashB = fileHash[2]; entry.locale = 0; entry.platform = 0; entry.block = blockIndex; @@ -362,15 +362,8 @@ MpqBlockEntry *MpqWriter::AddFile(const char *filename, MpqBlockEntry *block, ui return block; } -bool MpqWriter::WriteFileContents(const char *filename, const byte *fileData, size_t fileSize, MpqBlockEntry *block) +bool MpqWriter::WriteFileContents(const std::byte *fileData, uint32_t fileSize, MpqBlockEntry *block) { - const char *tmp; - while ((tmp = strchr(filename, ':')) != nullptr) - filename = tmp + 1; - while ((tmp = strchr(filename, '\\')) != nullptr) - filename = tmp + 1; - Hash(filename, 3); - const uint32_t numSectors = (fileSize + (BlockSize - 1)) / BlockSize; const uint32_t offsetTableByteSize = sizeof(uint32_t) * (numSectors + 1); block->offset = FindFreeBlock(fileSize + offsetTableByteSize); @@ -408,7 +401,7 @@ bool MpqWriter::WriteFileContents(const char *filename, const byte *fileData, si #endif uint32_t destSize = offsetTableByteSize; - byte mpqBuf[BlockSize]; + std::byte mpqBuf[BlockSize]; size_t curSector = 0; while (true) { uint32_t len = std::min(fileSize, BlockSize); @@ -451,7 +444,7 @@ bool MpqWriter::WriteHeader() memset(&fhdr, 0, sizeof(fhdr)); fhdr.signature = MpqFileHeader::DiabloSignature; fhdr.headerSize = MpqFileHeader::DiabloSize; - fhdr.fileSize = static_cast(size_); + fhdr.fileSize = size_; fhdr.version = 0; fhdr.blockSizeFactor = BlockSizeFactor; fhdr.hashEntriesOffset = MpqHashEntryOffset; @@ -465,23 +458,23 @@ bool MpqWriter::WriteHeader() bool MpqWriter::WriteBlockTable() { - Encrypt(reinterpret_cast(blockTable_.get()), BlockEntrySize, Hash("(block table)", 3)); + libmpq__encrypt_block(reinterpret_cast(blockTable_.get()), BlockEntrySize, LIBMPQ_BLOCK_TABLE_HASH_KEY); const bool success = stream_.Write(reinterpret_cast(blockTable_.get()), BlockEntrySize); - Decrypt(reinterpret_cast(blockTable_.get()), BlockEntrySize, Hash("(block table)", 3)); + libmpq__decrypt_block(reinterpret_cast(blockTable_.get()), BlockEntrySize, LIBMPQ_BLOCK_TABLE_HASH_KEY); return success; } bool MpqWriter::WriteHashTable() { - Encrypt(reinterpret_cast(hashTable_.get()), HashEntrySize, Hash("(hash table)", 3)); + libmpq__encrypt_block(reinterpret_cast(hashTable_.get()), HashEntrySize, LIBMPQ_HASH_TABLE_HASH_KEY); const bool success = stream_.Write(reinterpret_cast(hashTable_.get()), HashEntrySize); - Decrypt(reinterpret_cast(hashTable_.get()), HashEntrySize, Hash("(hash table)", 3)); + libmpq__decrypt_block(reinterpret_cast(hashTable_.get()), HashEntrySize, LIBMPQ_HASH_TABLE_HASH_KEY); return success; } -void MpqWriter::RemoveHashEntry(const char *filename) +void MpqWriter::RemoveHashEntry(std::string_view filename) { - uint32_t hIdx = FetchHandle(filename); + const uint32_t hIdx = FetchHandle(filename); if (hIdx == HashEntryNotFound) { return; } @@ -504,20 +497,20 @@ void MpqWriter::RemoveHashEntries(bool (*fnGetName)(uint8_t, char *)) } } -bool MpqWriter::WriteFile(const char *filename, const byte *data, size_t size) +bool MpqWriter::WriteFile(std::string_view filename, const std::byte *data, size_t size) { MpqBlockEntry *blockEntry; RemoveHashEntry(filename); blockEntry = AddFile(filename, nullptr, 0); - if (!WriteFileContents(filename, data, size, blockEntry)) { + if (!WriteFileContents(data, static_cast(size), blockEntry)) { RemoveHashEntry(filename); return false; } return true; } -void MpqWriter::RenameFile(const char *name, const char *newName) // NOLINT(bugprone-easily-swappable-parameters) +void MpqWriter::RenameFile(std::string_view name, std::string_view newName) // NOLINT(bugprone-easily-swappable-parameters) { uint32_t index = FetchHandle(name); if (index == HashEntryNotFound) { @@ -531,7 +524,7 @@ void MpqWriter::RenameFile(const char *name, const char *newName) // NOLINT(bugp AddFile(newName, blockEntry, block); } -bool MpqWriter::HasFile(const char *name) const +bool MpqWriter::HasFile(std::string_view name) const { return FetchHandle(name) != HashEntryNotFound; } diff --git a/Source/mpq/mpq_writer.hpp b/Source/mpq/mpq_writer.hpp index f707ad23e2e..9d72150ed7c 100644 --- a/Source/mpq/mpq_writer.hpp +++ b/Source/mpq/mpq_writer.hpp @@ -5,11 +5,12 @@ */ #pragma once +#include #include +#include #include "mpq/mpq_common.hpp" #include "utils/logged_fstream.hpp" -#include "utils/stdcompat/cstddef.hpp" namespace devilution { class MpqWriter { @@ -23,21 +24,21 @@ class MpqWriter { MpqWriter &operator=(MpqWriter &&other) = default; ~MpqWriter(); - bool HasFile(const char *name) const; + bool HasFile(std::string_view name) const; - void RemoveHashEntry(const char *filename); + void RemoveHashEntry(std::string_view filename); void RemoveHashEntries(bool (*fnGetName)(uint8_t, char *)); - bool WriteFile(const char *filename, const byte *data, size_t size); - void RenameFile(const char *name, const char *newName); + bool WriteFile(std::string_view filename, const std::byte *data, size_t size); + void RenameFile(std::string_view name, std::string_view newName); private: bool IsValidMpqHeader(MpqFileHeader *hdr) const; - uint32_t GetHashIndex(uint32_t index, uint32_t hashA, uint32_t hashB) const; - uint32_t FetchHandle(const char *filename) const; + uint32_t GetHashIndex(MpqFileHash fileHash) const; + uint32_t FetchHandle(std::string_view filename) const; bool ReadMPQHeader(MpqFileHeader *hdr); - MpqBlockEntry *AddFile(const char *filename, MpqBlockEntry *block, uint32_t blockIndex); - bool WriteFileContents(const char *filename, const byte *fileData, size_t fileSize, MpqBlockEntry *block); + MpqBlockEntry *AddFile(std::string_view filename, MpqBlockEntry *block, uint32_t blockIndex); + bool WriteFileContents(const std::byte *fileData, uint32_t fileSize, MpqBlockEntry *block); // Returns an unused entry in the block entry table. MpqBlockEntry *NewBlock(uint32_t *blockIndex = nullptr); @@ -56,7 +57,7 @@ class MpqWriter { LoggedFStream stream_; std::string name_; - std::uintmax_t size_ {}; + uint32_t size_ {}; std::unique_ptr hashTable_; std::unique_ptr blockTable_; diff --git a/Source/msg.cpp b/Source/msg.cpp index b537e8d9e9f..c8dc860c2de 100644 --- a/Source/msg.cpp +++ b/Source/msg.cpp @@ -1,9 +1,10 @@ /** * @file msg.cpp * - * Implementation of function for sending and reciving network messages. + * Implementation of function for sending and receiving network messages. */ #include +#include #include #include #include @@ -33,6 +34,7 @@ #include "nthread.h" #include "objects.h" #include "options.h" +#include "pack.h" #include "pfile.h" #include "plrmsg.h" #include "spells.h" @@ -44,8 +46,44 @@ #include "utils/str_cat.hpp" #include "utils/utf8.hpp" +#define ValidateField(logValue, condition) \ + do { \ + if (!(condition)) { \ + LogFailedPacket(#condition, #logValue, logValue); \ + EventFailedPacket(player._pName); \ + return false; \ + } \ + } while (0) + +#define ValidateFields(logValue1, logValue2, condition) \ + do { \ + if (!(condition)) { \ + LogFailedPacket(#condition, #logValue1, logValue1, #logValue2, logValue2); \ + EventFailedPacket(player._pName); \ + return false; \ + } \ + } while (0) + namespace devilution { +void EventFailedPacket(const char *playerName) +{ + std::string message = fmt::format("Player '{}' sent an invalid packet.", playerName); + EventPlrMsg(message); +} + +template +void LogFailedPacket(const char *condition, const char *name, T value) +{ + LogDebug("Remote player packet validation failed: ValidateField({}: {}, {})", name, value, condition); +} + +template +void LogFailedPacket(const char *condition, const char *name1, T1 value1, const char *name2, T2 value2) +{ + LogDebug("Remote player packet validation failed: ValidateFields({}: {}, {}: {}, {})", name1, value1, name2, value2, condition); +} + // #define LOG_RECEIVED_MESSAGES uint8_t gbBufferMsgs; @@ -54,7 +92,7 @@ int dwRecCount; namespace { #ifdef LOG_RECEIVED_MESSAGES -string_view CmdIdString(_cmd_id cmd) +std::string_view CmdIdString(_cmd_id cmd) { // clang-format off switch (cmd) { @@ -139,6 +177,7 @@ string_view CmdIdString(_cmd_id cmd) case CMD_NAKRUL: return "CMD_NAKRUL"; case CMD_OPENHIVE: return "CMD_OPENHIVE"; case CMD_OPENGRAVE: return "CMD_OPENGRAVE"; + case CMD_SPAWNMONSTER: return "CMD_SPAWNMONSTER"; case FAKE_CMD_SETID: return "FAKE_CMD_SETID"; case FAKE_CMD_DROPID: return "FAKE_CMD_DROPID"; case CMD_INVALID: return "CMD_INVALID"; @@ -149,8 +188,8 @@ string_view CmdIdString(_cmd_id cmd) #endif // LOG_RECEIVED_MESSAGES struct TMegaPkt { - uint32_t spaceLeft; - byte data[32000]; + size_t spaceLeft; + std::byte data[32000]; TMegaPkt() : spaceLeft(sizeof(data)) @@ -172,9 +211,15 @@ struct DObjectStr { _cmd_id bCmd; }; +struct DSpawnedMonster { + size_t typeIndex; + uint32_t seed; +}; + struct DLevel { TCmdPItem item[MAXITEMS]; std::unordered_map object; + std::unordered_map spawnedMonsters; DMonsterStr monster[MaxMonsters]; }; @@ -220,7 +265,7 @@ uint8_t sbLastCmd; /** * @brief buffer used to receive level deltas, size is the worst expected case assuming every object on a level was touched */ -byte sgRecvBuf[1U + sizeof(DLevel::item) + sizeof(uint8_t) + (sizeof(WorldTilePosition) + sizeof(_cmd_id)) * MAXOBJECTS + sizeof(DLevel::monster)]; +std::byte sgRecvBuf[1U + sizeof(DLevel::item) + sizeof(uint8_t) + (sizeof(WorldTilePosition) + sizeof(_cmd_id)) * MAXOBJECTS + sizeof(DLevel::monster)]; _cmd_id sgbRecvCmd; std::unordered_map LocalLevels; DJunk sgJunk; @@ -338,7 +383,7 @@ void PrePacket() { uint8_t playerId = std::numeric_limits::max(); for (TMegaPkt &pkt : MegaPktList) { - byte *data = pkt.data; + std::byte *data = pkt.data; size_t spaceLeft = sizeof(pkt.data); while (spaceLeft != pkt.spaceLeft) { auto cmdId = static_cast<_cmd_id>(*data); @@ -375,12 +420,11 @@ void PrePacket() } } -void SendPacket(int pnum, const void *packet, size_t dwSize) +void SendPacket(uint8_t pnum, const void *packet, size_t dwSize) { - TFakeCmdPlr cmd; - if (pnum != sgnCurrMegaPlayer) { sgnCurrMegaPlayer = pnum; + TFakeCmdPlr cmd; cmd.bCmd = FAKE_CMD_SETID; cmd.bPlr = pnum; SendPacket(pnum, &cmd, sizeof(cmd)); @@ -393,14 +437,18 @@ void SendPacket(int pnum, const void *packet, size_t dwSize) currMegaPkt.spaceLeft -= dwSize; } +void SendPacket(const Player &player, const void *packet, size_t dwSize) +{ + SendPacket(player.getId(), packet, dwSize); +} + int WaitForTurns() { uint32_t turns; if (sgbDeltaChunks == 0) { nthread_send_and_recv_turn(0, 0); - if (!SNetGetOwnerTurnsWaiting(&turns) && SErrGetLastError() == STORM_ERROR_NOT_IN_GAME) - return 100; + SNetGetOwnerTurnsWaiting(&turns); if (SDL_GetTicks() - sgdwOwnerWait <= 2000 && turns < gdwTurnsInTransit) return 0; sgbDeltaChunks++; @@ -426,11 +474,11 @@ int WaitForTurns() return 100 * sgbDeltaChunks / MAX_CHUNKS; } -byte *DeltaExportItem(byte *dst, const TCmdPItem *src) +std::byte *DeltaExportItem(std::byte *dst, const TCmdPItem *src) { for (int i = 0; i < MAXITEMS; i++, src++) { if (src->bCmd == CMD_INVALID) { - *dst++ = byte { 0xFF }; + *dst++ = std::byte { 0xFF }; } else { memcpy(dst, src, sizeof(TCmdPItem)); dst += sizeof(TCmdPItem); @@ -440,11 +488,11 @@ byte *DeltaExportItem(byte *dst, const TCmdPItem *src) return dst; } -size_t DeltaImportItem(const byte *src, TCmdPItem *dst) +size_t DeltaImportItem(const std::byte *src, TCmdPItem *dst) { size_t size = 0; for (int i = 0; i < MAXITEMS; i++, dst++) { - if (src[size] == byte { 0xFF }) { + if (src[size] == std::byte { 0xFF }) { memset(dst, 0xFF, sizeof(TCmdPItem)); size++; } else { @@ -456,19 +504,19 @@ size_t DeltaImportItem(const byte *src, TCmdPItem *dst) return size; } -byte *DeltaExportObject(byte *dst, const std::unordered_map &src) +std::byte *DeltaExportObject(std::byte *dst, const std::unordered_map &src) { - *dst++ = static_cast(src.size()); - for (auto &pair : src) { - *dst++ = static_cast(pair.first.x); - *dst++ = static_cast(pair.first.y); - *dst++ = static_cast(pair.second.bCmd); + *dst++ = static_cast(src.size()); + for (const auto &[position, obj] : src) { + *dst++ = static_cast(position.x); + *dst++ = static_cast(position.y); + *dst++ = static_cast(obj.bCmd); } return dst; } -const byte *DeltaImportObjects(const byte *src, std::unordered_map &dst) +const std::byte *DeltaImportObjects(const std::byte *src, std::unordered_map &dst) { dst.clear(); @@ -484,11 +532,11 @@ const byte *DeltaImportObjects(const byte *src, std::unordered_mapposition.x == 0xFF) { - *dst++ = byte { 0xFF }; + *dst++ = std::byte { 0xFF }; } else { memcpy(dst, src, sizeof(DMonsterStr)); dst += sizeof(DMonsterStr); @@ -498,11 +546,11 @@ byte *DeltaExportMonster(byte *dst, const DMonsterStr *src) return dst; } -void DeltaImportMonster(const byte *src, DMonsterStr *dst) +size_t DeltaImportMonster(const std::byte *src, DMonsterStr *dst) { size_t size = 0; for (size_t i = 0; i < MaxMonsters; i++, dst++) { - if (src[size] == byte { 0xFF }) { + if (src[size] == std::byte { 0xFF }) { memset(dst, 0xFF, sizeof(DMonsterStr)); size++; } else { @@ -510,13 +558,50 @@ void DeltaImportMonster(const byte *src, DMonsterStr *dst) size += sizeof(DMonsterStr); } } + + return size; } -byte *DeltaExportJunk(byte *dst) +std::byte *DeltaExportSpawnedMonsters(std::byte *dst, const std::unordered_map &spawnedMonsters) +{ + auto &size = *reinterpret_cast(dst); + size = static_cast(spawnedMonsters.size()); + dst += sizeof(uint16_t); + + for (const auto &deltaSpawnedMonster : spawnedMonsters) { + auto &monsterId = *reinterpret_cast(dst); + monsterId = static_cast(deltaSpawnedMonster.first); + dst += sizeof(uint16_t); + + memcpy(dst, &deltaSpawnedMonster.second, sizeof(DSpawnedMonster)); + dst += sizeof(DSpawnedMonster); + } + + return dst; +} + +const std::byte *DeltaImportSpawnedMonsters(const std::byte *src, std::unordered_map &spawnedMonsters) +{ + uint16_t size = *reinterpret_cast(src); + src += sizeof(uint16_t); + + for (size_t i = 0; i < size; i++) { + uint16_t monsterId = *reinterpret_cast(src); + src += sizeof(uint16_t); + DSpawnedMonster spawnedMonster; + memcpy(&spawnedMonster, src, sizeof(DSpawnedMonster)); + src += sizeof(DSpawnedMonster); + spawnedMonsters.emplace(monsterId, spawnedMonster); + } + + return src; +} + +std::byte *DeltaExportJunk(std::byte *dst) { for (auto &portal : sgJunk.portal) { if (portal.x == 0xFF) { - *dst++ = byte { 0xFF }; + *dst++ = std::byte { 0xFF }; } else { memcpy(dst, &portal, sizeof(DPortal)); dst += sizeof(DPortal); @@ -541,10 +626,10 @@ byte *DeltaExportJunk(byte *dst) return dst; } -void DeltaImportJunk(const byte *src) +void DeltaImportJunk(const std::byte *src) { for (int i = 0; i < MAXPORTAL; i++) { - if (*src == byte { 0xFF }) { + if (*src == std::byte { 0xFF }) { memset(&sgJunk.portal[i], 0xFF, sizeof(DPortal)); src++; } else { @@ -564,17 +649,17 @@ void DeltaImportJunk(const byte *src) } } -uint32_t CompressData(byte *buffer, byte *end) +uint32_t CompressData(std::byte *buffer, std::byte *end) { #ifdef USE_PKWARE const auto size = static_cast(end - buffer - 1); const uint32_t pkSize = PkwareCompress(buffer + 1, size); - *buffer = size != pkSize ? byte { 1 } : byte { 0 }; + *buffer = size != pkSize ? std::byte { 1 } : std::byte { 0 }; return pkSize + 1; #else - *buffer = byte { 0 }; + *buffer = std::byte { 0 }; return end - buffer; #endif } @@ -582,11 +667,11 @@ uint32_t CompressData(byte *buffer, byte *end) void DeltaImportData(_cmd_id cmd, uint32_t recvOffset) { #ifdef USE_PKWARE - if (sgRecvBuf[0] != byte { 0 }) + if (sgRecvBuf[0] != std::byte { 0 }) PkwareDecompress(&sgRecvBuf[1], recvOffset, sizeof(sgRecvBuf) - 1); #endif - const byte *src = &sgRecvBuf[1]; + const std::byte *src = &sgRecvBuf[1]; if (cmd == CMD_DLEVEL_JUNK) { DeltaImportJunk(src); } else if (cmd == CMD_DLEVEL) { @@ -595,7 +680,8 @@ void DeltaImportData(_cmd_id cmd, uint32_t recvOffset) DLevel &deltaLevel = GetDeltaLevel(i); src += DeltaImportItem(src, deltaLevel.item); src = DeltaImportObjects(src, deltaLevel.object); - DeltaImportMonster(src, deltaLevel.monster); + src += DeltaImportMonster(src, deltaLevel.monster); + src = DeltaImportSpawnedMonsters(src, deltaLevel.spawnedMonsters); } else { app_fatal(StrCat("Unkown network message type: ", cmd)); } @@ -603,7 +689,7 @@ void DeltaImportData(_cmd_id cmd, uint32_t recvOffset) sgbDeltaChunks++; } -size_t OnLevelData(int pnum, const TCmd *pCmd) +size_t OnLevelData(uint8_t pnum, const TCmd *pCmd) { const auto &message = *reinterpret_cast(pCmd); const uint16_t wBytes = SDL_SwapLE16(message.wBytes); @@ -646,12 +732,12 @@ size_t OnLevelData(int pnum, const TCmd *pCmd) return wBytes + sizeof(message); } -void DeltaSyncGolem(const TCmdGolem &message, int pnum, uint8_t level) +void DeltaSyncGolem(const TCmdGolem &message, const Player &player, uint8_t level) { if (!gbIsMultiplayer) return; - DMonsterStr &monster = GetDeltaLevel(level).monster[pnum]; + DMonsterStr &monster = GetDeltaLevel(level).monster[player.getId()]; monster.position.x = message._mx; monster.position.y = message._my; monster._mactive = UINT8_MAX; @@ -782,28 +868,7 @@ void DeltaPutItem(const TCmdPItem &message, Point position, const Player &player } } -bool IOwnLevel(const Player &player) -{ - for (const Player &other : Players) { - if (!other.plractive) - continue; - if (other._pLvlChanging) - continue; - if (other._pmode == PM_NEWLVL) - continue; - if (other.plrlevel != player.plrlevel) - continue; - if (other.plrIsOnSetLevel != player.plrIsOnSetLevel) - continue; - if (&other == MyPlayer && gbBufferMsgs != 0) - continue; - return &other == MyPlayer; - } - - return false; -} - -void DeltaOpenPortal(int pnum, Point position, uint8_t bLevel, dungeon_type bLType, bool bSetLvl) +void DeltaOpenPortal(size_t pnum, Point position, uint8_t bLevel, dungeon_type bLType, bool bSetLvl) { sgJunk.portal[pnum].x = position.x; sgJunk.portal[pnum].y = position.y; @@ -823,7 +888,7 @@ void NetSendCmdGItem2(bool usonly, _cmd_id bCmd, uint8_t mast, uint8_t pnum, con if (!usonly) { cmd.dwTime = 0; - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); return; } @@ -834,17 +899,17 @@ void NetSendCmdGItem2(bool usonly, _cmd_id bCmd, uint8_t mast, uint8_t pnum, con return; } - tmsg_add((byte *)&cmd, sizeof(cmd)); + tmsg_add((std::byte *)&cmd, sizeof(cmd)); } -bool NetSendCmdReq2(_cmd_id bCmd, uint8_t mast, uint8_t pnum, const TCmdGItem &item) +bool NetSendCmdReq2(_cmd_id bCmd, const Player &player, uint8_t pnum, const TCmdGItem &item) { TCmdGItem cmd; memcpy(&cmd, &item, sizeof(cmd)); cmd.bCmd = bCmd; cmd.bPnum = pnum; - cmd.bMaster = mast; + cmd.bMaster = player.getId(); int ticks = SDL_GetTicks(); if (cmd.dwTime == 0) @@ -852,7 +917,7 @@ bool NetSendCmdReq2(_cmd_id bCmd, uint8_t mast, uint8_t pnum, const TCmdGItem &i else if (ticks - SDL_SwapLE32(cmd.dwTime) > 5000) return false; - tmsg_add((byte *)&cmd, sizeof(cmd)); + tmsg_add((std::byte *)&cmd, sizeof(cmd)); return true; } @@ -864,7 +929,7 @@ void NetSendCmdExtra(const TCmdGItem &item) memcpy(&cmd, &item, sizeof(cmd)); cmd.dwTime = 0; cmd.bCmd = CMD_ITEMEXTRA; - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); } size_t OnWalk(const TCmd *pCmd, Player &player) @@ -881,50 +946,50 @@ size_t OnWalk(const TCmd *pCmd, Player &player) return sizeof(message); } -size_t OnAddStrength(const TCmd *pCmd, size_t pnum) +size_t OnAddStrength(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); if (gbBufferMsgs == 1) - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); else if (message.wParam1 <= 256) - ModifyPlrStr(Players[pnum], SDL_SwapLE16(message.wParam1)); + ModifyPlrStr(player, SDL_SwapLE16(message.wParam1)); return sizeof(message); } -size_t OnAddMagic(const TCmd *pCmd, size_t pnum) +size_t OnAddMagic(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); if (gbBufferMsgs == 1) - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); else if (message.wParam1 <= 256) - ModifyPlrMag(Players[pnum], SDL_SwapLE16(message.wParam1)); + ModifyPlrMag(player, SDL_SwapLE16(message.wParam1)); return sizeof(message); } -size_t OnAddDexterity(const TCmd *pCmd, int pnum) +size_t OnAddDexterity(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); if (gbBufferMsgs == 1) - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); else if (message.wParam1 <= 256) - ModifyPlrDex(Players[pnum], SDL_SwapLE16(message.wParam1)); + ModifyPlrDex(player, SDL_SwapLE16(message.wParam1)); return sizeof(message); } -size_t OnAddVitality(const TCmd *pCmd, size_t pnum) +size_t OnAddVitality(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); if (gbBufferMsgs == 1) - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); else if (message.wParam1 <= 256) - ModifyPlrVit(Players[pnum], SDL_SwapLE16(message.wParam1)); + ModifyPlrVit(player, SDL_SwapLE16(message.wParam1)); return sizeof(message); } @@ -960,14 +1025,30 @@ bool IsGItemValid(const TCmdGItem &message) return IsItemAvailable(static_cast<_item_indexes>(SDL_SwapLE16(message.def.wIndx))); } -bool IsPItemValid(const TCmdPItem &message) +bool IsPItemValid(const TCmdPItem &message, const Player &player) { const Point position { message.x, message.y }; if (!InDungeonBounds(position)) return false; - return IsItemAvailable(static_cast<_item_indexes>(SDL_SwapLE16(message.def.wIndx))); + auto idx = static_cast<_item_indexes>(SDL_SwapLE16(message.def.wIndx)); + + if (idx != IDI_EAR) { + uint16_t creationFlags = SDL_SwapLE16(message.item.wCI); + uint32_t dwBuff = SDL_SwapLE16(message.item.dwBuff); + + if (idx != IDI_GOLD) + ValidateField(creationFlags, IsCreationFlagComboValid(creationFlags)); + if ((creationFlags & CF_TOWN) != 0) + ValidateField(creationFlags, IsTownItemValid(creationFlags, player)); + else if ((creationFlags & CF_USEFUL) == CF_UPER15) + ValidateFields(creationFlags, dwBuff, IsUniqueMonsterItemValid(creationFlags, dwBuff)); + else + ValidateFields(creationFlags, dwBuff, IsDungeonItemValid(creationFlags, dwBuff)); + } + + return IsItemAvailable(idx); } void PrepareItemForNetwork(const Item &item, TCmdGItem &message) @@ -1082,7 +1163,7 @@ size_t OnRequestGetItem(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); - if (gbBufferMsgs != 1 && IOwnLevel(player) && IsGItemValid(message)) { + if (gbBufferMsgs != 1 && player.isLevelOwnedByLocalClient() && IsGItemValid(message)) { const Point position { message.x, message.y }; const uint32_t dwSeed = SDL_SwapLE32(message.def.dwSeed); const uint16_t wCI = SDL_SwapLE16(message.def.wCI); @@ -1090,7 +1171,7 @@ size_t OnRequestGetItem(const TCmd *pCmd, Player &player) if (GetItemRecord(dwSeed, wCI, wIndx)) { int ii = -1; if (InDungeonBounds(position)) { - ii = abs(dItem[position.x][position.y]) - 1; + ii = std::abs(dItem[position.x][position.y]) - 1; if (ii >= 0 && !Items[ii].keyAttributesMatch(dwSeed, wIndx, wCI)) { ii = -1; } @@ -1111,7 +1192,7 @@ size_t OnRequestGetItem(const TCmd *pCmd, Player &player) else InvGetItem(*MyPlayer, ii); SetItemRecord(dwSeed, wCI, wIndx); - } else if (!NetSendCmdReq2(CMD_REQUESTGITEM, MyPlayerId, message.bPnum, message)) { + } else if (!NetSendCmdReq2(CMD_REQUESTGITEM, *MyPlayer, message.bPnum, message)) { NetSendCmdExtra(message); } } @@ -1120,12 +1201,12 @@ size_t OnRequestGetItem(const TCmd *pCmd, Player &player) return sizeof(message); } -size_t OnGetItem(const TCmd *pCmd, size_t pnum) +size_t OnGetItem(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } else if (IsGItemValid(message)) { const Point position { message.x, message.y }; const uint32_t dwSeed = SDL_SwapLE32(message.def.dwSeed); @@ -1174,7 +1255,7 @@ size_t OnRequestAutoGetItem(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); - if (gbBufferMsgs != 1 && IOwnLevel(player) && IsGItemValid(message)) { + if (gbBufferMsgs != 1 && player.isLevelOwnedByLocalClient() && IsGItemValid(message)) { const Point position { message.x, message.y }; const uint32_t dwSeed = SDL_SwapLE32(message.def.dwSeed); const uint16_t wCI = SDL_SwapLE16(message.def.wCI); @@ -1187,7 +1268,7 @@ size_t OnRequestAutoGetItem(const TCmd *pCmd, Player &player) else AutoGetItem(*MyPlayer, &Items[message.bCursitem], message.bCursitem); SetItemRecord(dwSeed, wCI, wIndx); - } else if (!NetSendCmdReq2(CMD_REQUESTAGITEM, MyPlayerId, message.bPnum, message)) { + } else if (!NetSendCmdReq2(CMD_REQUESTAGITEM, *MyPlayer, message.bPnum, message)) { NetSendCmdExtra(message); } } @@ -1196,12 +1277,12 @@ size_t OnRequestAutoGetItem(const TCmd *pCmd, Player &player) return sizeof(message); } -size_t OnAutoGetItem(const TCmd *pCmd, size_t pnum) +size_t OnAutoGetItem(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } else if (IsGItemValid(message)) { const Point position { message.x, message.y }; if (DeltaGetItem(message, message.bLevel)) { @@ -1227,15 +1308,15 @@ size_t OnAutoGetItem(const TCmd *pCmd, size_t pnum) return sizeof(message); } -size_t OnItemExtra(const TCmd *pCmd, size_t pnum) +size_t OnItemExtra(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } else if (IsGItemValid(message)) { DeltaGetItem(message, message.bLevel); - if (Players[pnum].isOnActiveLevel()) { + if (player.isOnActiveLevel()) { const Point position { message.x, message.y }; SyncGetItem(position, SDL_SwapLE32(message.def.dwSeed), static_cast<_item_indexes>(SDL_SwapLE16(message.def.wIndx)), SDL_SwapLE16(message.def.wCI)); } @@ -1244,15 +1325,14 @@ size_t OnItemExtra(const TCmd *pCmd, size_t pnum) return sizeof(message); } -size_t OnPutItem(const TCmd *pCmd, size_t pnum) +size_t OnPutItem(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); - } else if (IsPItemValid(message)) { + SendPacket(player, &message, sizeof(message)); + } else if (IsPItemValid(message, player)) { const Point position { message.x, message.y }; - Player &player = Players[pnum]; bool isSelf = &player == MyPlayer; const int32_t dwSeed = SDL_SwapLE32(message.def.dwSeed); const uint16_t wCI = SDL_SwapLE16(message.def.wCI); @@ -1285,14 +1365,13 @@ size_t OnPutItem(const TCmd *pCmd, size_t pnum) return sizeof(message); } -size_t OnSyncPutItem(const TCmd *pCmd, size_t pnum) +size_t OnSyncPutItem(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); if (gbBufferMsgs == 1) - SendPacket(pnum, &message, sizeof(message)); - else if (IsPItemValid(message)) { - Player &player = Players[pnum]; + SendPacket(player, &message, sizeof(message)); + else if (IsPItemValid(message, player)) { const int32_t dwSeed = SDL_SwapLE32(message.def.dwSeed); const uint16_t wCI = SDL_SwapLE16(message.def.wCI); const _item_indexes wIndx = static_cast<_item_indexes>(SDL_SwapLE16(message.def.wIndx)); @@ -1568,13 +1647,11 @@ size_t OnSpellPlayer(const TCmd *pCmd, Player &player) return sizeof(message); } -size_t OnKnockback(const TCmd *pCmd, size_t pnum) +size_t OnKnockback(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const uint16_t monsterIdx = SDL_SwapLE16(message.wParam1); - Player &player = Players[pnum]; - if (gbBufferMsgs != 1 && player.isOnActiveLevel() && monsterIdx < MaxMonsters) { Monster &monster = Monsters[monsterIdx]; M_GetKnockback(monster); @@ -1584,16 +1661,16 @@ size_t OnKnockback(const TCmd *pCmd, size_t pnum) return sizeof(message); } -size_t OnResurrect(const TCmd *pCmd, size_t pnum) +size_t OnResurrect(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const uint16_t playerIdx = SDL_SwapLE16(message.wParam1); if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } else if (playerIdx < Players.size()) { - DoResurrect(pnum, Players[playerIdx]); - if (pnum == MyPlayerId) + DoResurrect(player, Players[playerIdx]); + if (&player == MyPlayer) pfile_update(true); } @@ -1629,14 +1706,14 @@ size_t OnTalkXY(const TCmd *pCmd, Player &player) return sizeof(message); } -size_t OnNewLevel(const TCmd *pCmd, size_t pnum) +size_t OnNewLevel(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const uint16_t eventIdx = SDL_SwapLE16(message.wParam1); if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); - } else if (pnum != MyPlayerId) { + SendPacket(player, &message, sizeof(message)); + } else if (&player != MyPlayer) { if (eventIdx < WM_FIRST || eventIdx > WM_LAST) return sizeof(message); @@ -1647,34 +1724,33 @@ size_t OnNewLevel(const TCmd *pCmd, size_t pnum) return sizeof(message); } - StartNewLvl(Players[pnum], mode, levelId); + StartNewLvl(player, mode, levelId); } return sizeof(message); } -size_t OnWarp(const TCmd *pCmd, size_t pnum) +size_t OnWarp(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const uint16_t portalIdx = SDL_SwapLE16(message.wParam1); if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } else if (portalIdx < MAXPORTAL) { - StartWarpLvl(Players[pnum], portalIdx); + StartWarpLvl(player, portalIdx); } return sizeof(message); } -size_t OnMonstDeath(const TCmd *pCmd, size_t pnum) +size_t OnMonstDeath(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const Point position { message.x, message.y }; const uint16_t monsterIdx = SDL_SwapLE16(message.wParam1); if (gbBufferMsgs != 1) { - Player &player = Players[pnum]; if (&player != MyPlayer && InDungeonBounds(position) && monsterIdx < MaxMonsters) { Monster &monster = Monsters[monsterIdx]; if (player.isOnActiveLevel()) @@ -1682,43 +1758,41 @@ size_t OnMonstDeath(const TCmd *pCmd, size_t pnum) delta_kill_monster(monster, position, player); } } else { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } return sizeof(message); } -size_t OnKillGolem(const TCmd *pCmd, size_t pnum) +size_t OnKillGolem(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const Point position { message.x, message.y }; if (gbBufferMsgs != 1) { - Player &player = Players[pnum]; if (&player != MyPlayer && InDungeonBounds(position)) { - Monster &monster = Monsters[pnum]; + Monster &monster = Monsters[player.getId()]; if (player.isOnActiveLevel()) M_SyncStartKill(monster, position, player); delta_kill_monster(monster, position, player); // BUGFIX: should be p->wParam1, plrlevel will be incorrect if golem is killed because player changed levels } } else { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } return sizeof(message); } -size_t OnAwakeGolem(const TCmd *pCmd, size_t pnum) +size_t OnAwakeGolem(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const Point position { message._mx, message._my }; if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } else if (InDungeonBounds(position)) { - Player &player = Players[pnum]; if (!player.isOnActiveLevel()) { - DeltaSyncGolem(message, pnum, message._currlevel); + DeltaSyncGolem(message, player, message._currlevel); } else if (&player != MyPlayer) { // Check if this player already has an active golem for (auto &missile : Missiles) { @@ -1727,20 +1801,19 @@ size_t OnAwakeGolem(const TCmd *pCmd, size_t pnum) } } - AddMissile(player.position.tile, position, message._mdir, MissileID::Golem, TARGET_MONSTERS, pnum, 0, 1); + AddMissile(player.position.tile, position, message._mdir, MissileID::Golem, TARGET_MONSTERS, player, 0, 1); } } return sizeof(message); } -size_t OnMonstDamage(const TCmd *pCmd, size_t pnum) +size_t OnMonstDamage(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const uint16_t monsterIdx = SDL_SwapLE16(message.wMon); if (gbBufferMsgs != 1) { - Player &player = Players[pnum]; if (&player != MyPlayer) { if (player.isOnActiveLevel() && monsterIdx < MaxMonsters) { auto &monster = Monsters[monsterIdx]; @@ -1754,25 +1827,24 @@ size_t OnMonstDamage(const TCmd *pCmd, size_t pnum) } } } else { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } return sizeof(message); } -size_t OnPlayerDeath(const TCmd *pCmd, size_t pnum) +size_t OnPlayerDeath(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const DeathReason deathReason = static_cast(SDL_SwapLE16(message.wParam1)); if (gbBufferMsgs != 1) { - Player &player = Players[pnum]; if (&player != MyPlayer) StartPlayerKill(player, deathReason); else pfile_update(true); } else { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } return sizeof(message); @@ -1793,14 +1865,13 @@ size_t OnPlayerDamage(const TCmd *pCmd, Player &player) return sizeof(message); } -size_t OnOperateObject(const TCmd &pCmd, size_t pnum) +size_t OnOperateObject(const TCmd &pCmd, Player &player) { const auto &message = reinterpret_cast(pCmd); if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } else { - Player &player = Players[pnum]; WorldTilePosition position { message.x, message.y }; assert(InDungeonBounds(position)); if (player.isOnActiveLevel()) { @@ -1814,14 +1885,13 @@ size_t OnOperateObject(const TCmd &pCmd, size_t pnum) return sizeof(message); } -size_t OnBreakObject(const TCmd &pCmd, size_t pnum) +size_t OnBreakObject(const TCmd &pCmd, Player &player) { const auto &message = reinterpret_cast(pCmd); if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } else { - Player &player = Players[pnum]; WorldTilePosition position { message.x, message.y }; assert(InDungeonBounds(position)); if (player.isOnActiveLevel()) { @@ -1835,10 +1905,9 @@ size_t OnBreakObject(const TCmd &pCmd, size_t pnum) return sizeof(message); } -size_t OnChangePlayerItems(const TCmd *pCmd, size_t pnum) +size_t OnChangePlayerItems(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); - Player &player = Players[pnum]; if (message.bLoc >= NUM_INVLOC) return sizeof(message); @@ -1846,7 +1915,7 @@ size_t OnChangePlayerItems(const TCmd *pCmd, size_t pnum) auto bodyLocation = static_cast(message.bLoc); if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } else if (&player != MyPlayer && IsItemAvailable(static_cast<_item_indexes>(SDL_SwapLE16(message.def.wIndx)))) { Item &item = player.InvBody[message.bLoc]; item = {}; @@ -1859,31 +1928,29 @@ size_t OnChangePlayerItems(const TCmd *pCmd, size_t pnum) return sizeof(message); } -size_t OnDeletePlayerItems(const TCmd *pCmd, size_t pnum) +size_t OnDeletePlayerItems(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); if (gbBufferMsgs != 1) { - Player &player = Players[pnum]; if (&player != MyPlayer && message.bLoc < NUM_INVLOC) inv_update_rem_item(player, static_cast(message.bLoc)); } else { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } return sizeof(message); } -size_t OnChangeInventoryItems(const TCmd *pCmd, int pnum) +size_t OnChangeInventoryItems(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); - Player &player = Players[pnum]; if (message.bLoc >= InventoryGridCells) return sizeof(message); if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } else if (&player != MyPlayer && IsItemAvailable(static_cast<_item_indexes>(SDL_SwapLE16(message.def.wIndx)))) { Item item {}; RecreateItem(player, message, item); @@ -1893,14 +1960,13 @@ size_t OnChangeInventoryItems(const TCmd *pCmd, int pnum) return sizeof(message); } -size_t OnDeleteInventoryItems(const TCmd *pCmd, int pnum) +size_t OnDeleteInventoryItems(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const uint16_t invGridIndex = SDL_SwapLE16(message.wParam1); - Player &player = Players[pnum]; if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } else if (&player != MyPlayer && invGridIndex < InventoryGridCells) { CheckInvRemove(player, invGridIndex); } @@ -1908,16 +1974,15 @@ size_t OnDeleteInventoryItems(const TCmd *pCmd, int pnum) return sizeof(message); } -size_t OnChangeBeltItems(const TCmd *pCmd, int pnum) +size_t OnChangeBeltItems(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); - Player &player = Players[pnum]; if (message.bLoc >= MaxBeltItems) return sizeof(message); if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } else if (&player != MyPlayer && IsItemAvailable(static_cast<_item_indexes>(SDL_SwapLE16(message.def.wIndx)))) { Item &item = player.SpdList[message.bLoc]; item = {}; @@ -1927,14 +1992,13 @@ size_t OnChangeBeltItems(const TCmd *pCmd, int pnum) return sizeof(message); } -size_t OnDeleteBeltItems(const TCmd *pCmd, int pnum) +size_t OnDeleteBeltItems(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const uint16_t spdBarIndex = SDL_SwapLE16(message.wParam1); - Player &player = Players[pnum]; if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } else if (&player != MyPlayer && spdBarIndex < MaxBeltItems) { player.RemoveSpdBarItem(spdBarIndex); } @@ -1942,43 +2006,41 @@ size_t OnDeleteBeltItems(const TCmd *pCmd, int pnum) return sizeof(message); } -size_t OnPlayerLevel(const TCmd *pCmd, size_t pnum) +size_t OnPlayerLevel(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const uint16_t playerLevel = SDL_SwapLE16(message.wParam1); if (gbBufferMsgs != 1) { - Player &player = Players[pnum]; - if (playerLevel <= MaxCharacterLevel && &player != MyPlayer) - player._pLevel = static_cast(playerLevel); + if (playerLevel <= player.getMaxCharacterLevel() && &player != MyPlayer) + player.setCharacterLevel(static_cast(playerLevel)); } else { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } return sizeof(message); } -size_t OnDropItem(const TCmd *pCmd, size_t pnum) +size_t OnDropItem(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); - } else if (IsPItemValid(message)) { - DeltaPutItem(message, { message.x, message.y }, Players[pnum]); + SendPacket(player, &message, sizeof(message)); + } else if (IsPItemValid(message, player)) { + DeltaPutItem(message, { message.x, message.y }, player); } return sizeof(message); } -size_t OnSpawnItem(const TCmd *pCmd, size_t pnum) +size_t OnSpawnItem(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); - } else if (IsPItemValid(message)) { - Player &player = Players[pnum]; + SendPacket(player, &message, sizeof(message)); + } else if (IsPItemValid(message, player)) { if (player.isOnActiveLevel() && &player != MyPlayer) { SyncDropItem(message); } @@ -1989,43 +2051,41 @@ size_t OnSpawnItem(const TCmd *pCmd, size_t pnum) return sizeof(message); } -size_t OnSendPlayerInfo(const TCmd *pCmd, size_t pnum) +size_t OnSendPlayerInfo(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const uint16_t wBytes = SDL_SwapLE16(message.wBytes); if (gbBufferMsgs == 1) - SendPacket(pnum, &message, wBytes + sizeof(message)); + SendPacket(player, &message, wBytes + sizeof(message)); else - recv_plrinfo(pnum, message, message.bCmd == CMD_ACK_PLRINFO); + recv_plrinfo(player, message, message.bCmd == CMD_ACK_PLRINFO); return wBytes + sizeof(message); } -size_t OnPlayerJoinLevel(const TCmd *pCmd, size_t pnum) +size_t OnPlayerJoinLevel(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const Point position { message.x, message.y }; if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); return sizeof(message); } - const uint16_t playerLevel = SDL_SwapLE16(message.wParam1); + const uint8_t playerLevel = static_cast(SDL_SwapLE16(message.wParam1)); bool isSetLevel = message.wParam2 != 0; if (!IsValidLevel(playerLevel, isSetLevel) || !InDungeonBounds(position)) { return sizeof(message); } - Player &player = Players[pnum]; - player._pLvlChanging = false; if (player._pName[0] != '\0' && !player.plractive) { ResetPlayerGFX(player); player.plractive = true; gbActivePlayers++; - EventPlrMsg(fmt::format(fmt::runtime(_("Player '{:s}' (level {:d}) just joined the game")), player._pName, player._pLevel)); + EventPlrMsg(fmt::format(fmt::runtime(_("Player '{:s}' (level {:d}) just joined the game")), player._pName, player.getCharacterLevel())); } if (player.plractive && &player != MyPlayer) { @@ -2055,24 +2115,23 @@ size_t OnPlayerJoinLevel(const TCmd *pCmd, size_t pnum) return sizeof(message); } -size_t OnActivatePortal(const TCmd *pCmd, size_t pnum) +size_t OnActivatePortal(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const Point position { message.x, message.y }; - const uint16_t level = SDL_SwapLE16(message.wParam1); + const uint8_t level = static_cast(SDL_SwapLE16(message.wParam1)); const uint16_t dungeonTypeIdx = SDL_SwapLE16(message.wParam2); const bool isSetLevel = message.wParam3 != 0; if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } else if (InDungeonBounds(position) && IsValidLevel(level, isSetLevel) && dungeonTypeIdx <= DTYPE_LAST) { auto dungeonType = static_cast(dungeonTypeIdx); - ActivatePortal(pnum, position, level, dungeonType, isSetLevel); - Player &player = Players[pnum]; + ActivatePortal(player, position, level, dungeonType, isSetLevel); if (&player != MyPlayer) { if (leveltype == DTYPE_TOWN) { - AddInTownPortal(pnum); + AddPortalInTown(player); } else if (player.isOnActiveLevel()) { bool addPortal = true; for (auto &missile : Missiles) { @@ -2082,106 +2141,102 @@ size_t OnActivatePortal(const TCmd *pCmd, size_t pnum) } } if (addPortal) { - AddWarpMissile(pnum, position, false); + AddPortalMissile(player, position, false); } } else { - RemovePortalMissile(pnum); + RemovePortalMissile(player); } } - DeltaOpenPortal(pnum, position, level, dungeonType, isSetLevel); + DeltaOpenPortal(player.getId(), position, level, dungeonType, isSetLevel); } return sizeof(message); } -size_t OnDeactivatePortal(const TCmd *pCmd, size_t pnum) +size_t OnDeactivatePortal(const TCmd *pCmd, Player &player) { if (gbBufferMsgs == 1) { - SendPacket(pnum, pCmd, sizeof(*pCmd)); + SendPacket(player, pCmd, sizeof(*pCmd)); } else { - if (PortalOnLevel(pnum)) - RemovePortalMissile(pnum); - DeactivatePortal(pnum); - delta_close_portal(pnum); + if (PortalOnLevel(player)) + RemovePortalMissile(player); + DeactivatePortal(player); + delta_close_portal(player); } return sizeof(*pCmd); } -size_t OnRestartTown(const TCmd *pCmd, size_t pnum) +size_t OnRestartTown(const TCmd *pCmd, Player &player) { if (gbBufferMsgs == 1) { - SendPacket(pnum, pCmd, sizeof(*pCmd)); + SendPacket(player, pCmd, sizeof(*pCmd)); } else { - if (pnum == MyPlayerId) { + if (&player == MyPlayer) { MyPlayerIsDead = false; gamemenu_off(); } - RestartTownLvl(Players[pnum]); + RestartTownLvl(player); } return sizeof(*pCmd); } -size_t OnSetStrength(const TCmd *pCmd, size_t pnum) +size_t OnSetStrength(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const uint16_t value = SDL_SwapLE16(message.wParam1); if (gbBufferMsgs != 1) { - Player &player = Players[pnum]; if (value <= 750 && &player != MyPlayer) SetPlrStr(player, value); } else { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } return sizeof(message); } -size_t OnSetDexterity(const TCmd *pCmd, size_t pnum) +size_t OnSetDexterity(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const uint16_t value = SDL_SwapLE16(message.wParam1); if (gbBufferMsgs != 1) { - Player &player = Players[pnum]; if (value <= 750 && &player != MyPlayer) SetPlrDex(player, value); } else { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } return sizeof(message); } -size_t OnSetMagic(const TCmd *pCmd, size_t pnum) +size_t OnSetMagic(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const uint16_t value = SDL_SwapLE16(message.wParam1); if (gbBufferMsgs != 1) { - Player &player = Players[pnum]; if (value <= 750 && &player != MyPlayer) SetPlrMag(player, value); } else { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } return sizeof(message); } -size_t OnSetVitality(const TCmd *pCmd, size_t pnum) +size_t OnSetVitality(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); const uint16_t value = SDL_SwapLE16(message.wParam1); if (gbBufferMsgs != 1) { - Player &player = Players[pnum]; if (value <= 750 && &player != MyPlayer) SetPlrVit(player, value); } else { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } return sizeof(message); @@ -2191,7 +2246,7 @@ size_t OnString(const TCmd *pCmd, Player &player) { auto *p = (TCmdString *)pCmd; - int len = strlen(p->str); + size_t len = strlen(p->str); if (gbBufferMsgs == 0) SendPlrMsg(player, p->str); @@ -2205,46 +2260,45 @@ size_t OnFriendlyMode(const TCmd *pCmd, Player &player) // NOLINT(misc-unused-pa return sizeof(*pCmd); } -size_t OnSyncQuest(const TCmd *pCmd, size_t pnum) +size_t OnSyncQuest(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); if (gbBufferMsgs == 1) { - SendPacket(pnum, &message, sizeof(message)); + SendPacket(player, &message, sizeof(message)); } else { - if (pnum != MyPlayerId && message.q < MAXQUESTS && message.qstate <= QUEST_HIVE_DONE) + if (&player != MyPlayer && message.q < MAXQUESTS && message.qstate <= QUEST_HIVE_DONE) SetMultiQuest(message.q, message.qstate, message.qlog != 0, message.qvar1, message.qvar2, message.qmsg); } return sizeof(message); } -size_t OnCheatExperience(const TCmd *pCmd, size_t pnum) // NOLINT(misc-unused-parameters) +size_t OnCheatExperience(const TCmd *pCmd, Player &player) // NOLINT(misc-unused-parameters) { #ifdef _DEBUG if (gbBufferMsgs == 1) - SendPacket(pnum, pCmd, sizeof(*pCmd)); - else if (Players[pnum]._pLevel < MaxCharacterLevel) { - Players[pnum]._pExperience = Players[pnum]._pNextExper; + SendPacket(player, pCmd, sizeof(*pCmd)); + else if (!player.isMaxCharacterLevel()) { + player._pExperience = player.getNextExperienceThreshold(); if (*sgOptions.Gameplay.experienceBar) { RedrawEverything(); } - NextPlrLevel(Players[pnum]); + NextPlrLevel(player); } #endif return sizeof(*pCmd); } -size_t OnChangeSpellLevel(const TCmd *pCmd, size_t pnum) // NOLINT(misc-unused-parameters) +size_t OnChangeSpellLevel(const TCmd *pCmd, Player &player) // NOLINT(misc-unused-parameters) { const auto &message = *reinterpret_cast(pCmd); const SpellID spellID = static_cast(SDL_SwapLE16(message.wParam1)); const uint8_t spellLevel = std::min(static_cast(SDL_SwapLE16(message.wParam2)), MaxSpellLevel); if (gbBufferMsgs == 1) { - SendPacket(pnum, pCmd, sizeof(*pCmd)); + SendPacket(player, pCmd, sizeof(*pCmd)); } else { - Player &player = Players[pnum]; player._pMemSpells |= GetSpellBitmask(spellID); player._pSplLvl[static_cast(spellID)] = spellLevel; } @@ -2287,7 +2341,7 @@ size_t OnNakrul(const TCmd *pCmd) { if (gbBufferMsgs != 1) { if (currlevel == 24) { - PlaySfxLoc(IS_CROPEN, { UberRow, UberCol }); + PlaySfxLoc(SfxID::CryptDoorOpen, { UberRow, UberCol }); SyncNakrulRoom(); } IsUberRoomOpened = true; @@ -2297,10 +2351,10 @@ size_t OnNakrul(const TCmd *pCmd) return sizeof(*pCmd); } -size_t OnOpenHive(const TCmd *pCmd, size_t pnum) +size_t OnOpenHive(const TCmd *pCmd, Player &player) { if (gbBufferMsgs != 1) { - AddMissile({ 0, 0 }, { 0, 0 }, Direction::South, MissileID::OpenNest, TARGET_MONSTERS, pnum, 0, 0); + AddMissile({ 0, 0 }, { 0, 0 }, Direction::South, MissileID::OpenNest, TARGET_MONSTERS, player, 0, 0); TownOpenHive(); InitTownTriggers(); } @@ -2314,11 +2368,31 @@ size_t OnOpenGrave(const TCmd *pCmd) TownOpenGrave(); InitTownTriggers(); if (leveltype == DTYPE_TOWN) - PlaySFX(IS_SARC); + PlaySFX(SfxID::Sarcophagus); } return sizeof(*pCmd); } +size_t OnSpawnMonster(const TCmd *pCmd, const Player &player) +{ + const auto &message = *reinterpret_cast(pCmd); + if (gbBufferMsgs == 1) + return sizeof(message); + + const Point position { message.x, message.y }; + + size_t typeIndex = static_cast(SDL_SwapLE16(message.typeIndex)); + size_t monsterId = static_cast(SDL_SwapLE16(message.monsterId)); + + DLevel &deltaLevel = GetDeltaLevel(player); + + deltaLevel.spawnedMonsters[monsterId] = { typeIndex, message.seed }; + + if (player.isOnActiveLevel() && &player != MyPlayer) + InitializeSpawnedMonster(position, message.dir, typeIndex, monsterId, message.seed); + return sizeof(message); +} + } // namespace void PrepareItemForNetwork(const Item &item, TItem &messageItem) @@ -2350,11 +2424,11 @@ void RecreateItem(const Player &player, const TItem &messageItem, Item &item) item._iIdentified = true; item._iMaxDur = messageItem.bMDur; item._iDurability = ClampDurability(item, messageItem.bDur); - item._iMaxCharges = clamp(messageItem.bMCh, 0, item._iMaxCharges); - item._iCharges = clamp(messageItem.bCh, 0, item._iMaxCharges); + item._iMaxCharges = std::clamp(messageItem.bMCh, 0, item._iMaxCharges); + item._iCharges = std::clamp(messageItem.bCh, 0, item._iMaxCharges); if (gbIsHellfire) { - item._iPLToHit = ClampToHit(item, SDL_SwapLE16(messageItem.wToHit)); - item._iMaxDam = ClampMaxDam(item, SDL_SwapLE16(messageItem.wMaxDam)); + item._iPLToHit = ClampToHit(item, static_cast(SDL_SwapLE16(messageItem.wToHit))); + item._iMaxDam = ClampMaxDam(item, static_cast(SDL_SwapLE16(messageItem.wMaxDam))); } item.dwBuff = dwBuff; } @@ -2364,7 +2438,7 @@ void ClearLastSentPlayerCmd() lastSentPlayerCmd = {}; } -void msg_send_drop_pkt(int pnum, int reason) +void msg_send_drop_pkt(uint8_t pnum, int reason) { TFakeDropPlr cmd; @@ -2417,36 +2491,37 @@ void run_delta_info() FreePackets(); } -void DeltaExportData(int pnum) +void DeltaExportData(uint8_t pnum) { - for (auto &it : DeltaLevels) { - DLevel &deltaLevel = it.second; - + for (const auto &[levelNum, deltaLevel] : DeltaLevels) { const size_t bufferSize = 1U /* marker byte, always 0 */ + sizeof(uint8_t) /* level id */ + sizeof(deltaLevel.item) /* items spawned during dungeon generation which have been picked up, and items dropped by a player during a game */ + sizeof(uint8_t) /* count of object interactions which caused a state change since dungeon generation */ + (sizeof(WorldTilePosition) + sizeof(DObjectStr)) * deltaLevel.object.size() /* location/action pairs for the object interactions */ - + sizeof(deltaLevel.monster); /* latest monster state */ - std::unique_ptr dst { new byte[bufferSize] }; + + sizeof(deltaLevel.monster) /* latest monster state */ + + sizeof(uint16_t) /* spanwned monster count */ + + (sizeof(uint16_t) + sizeof(DSpawnedMonster)) * MaxMonsters; /* spanwned monsters */ + std::unique_ptr dst { new std::byte[bufferSize] }; - byte *dstEnd = &dst.get()[1]; - *dstEnd = static_cast(it.first); + std::byte *dstEnd = &dst.get()[1]; + *dstEnd = static_cast(levelNum); dstEnd += sizeof(uint8_t); dstEnd = DeltaExportItem(dstEnd, deltaLevel.item); dstEnd = DeltaExportObject(dstEnd, deltaLevel.object); dstEnd = DeltaExportMonster(dstEnd, deltaLevel.monster); + dstEnd = DeltaExportSpawnedMonsters(dstEnd, deltaLevel.spawnedMonsters); uint32_t size = CompressData(dst.get(), dstEnd); multi_send_zero_packet(pnum, CMD_DLEVEL, dst.get(), size); } - byte dst[sizeof(DJunk) + 1]; - byte *dstEnd = &dst[1]; + std::byte dst[sizeof(DJunk) + 1]; + std::byte *dstEnd = &dst[1]; dstEnd = DeltaExportJunk(dstEnd); uint32_t size = CompressData(dst, dstEnd); multi_send_zero_packet(pnum, CMD_DLEVEL_JUNK, dst, size); - byte src[1] = { static_cast(0) }; + std::byte src[1] = { static_cast(0) }; multi_send_zero_packet(pnum, CMD_DLEVEL_END, src, 1); } @@ -2609,6 +2684,10 @@ void DeltaLoadLevel() uint8_t localLevel = GetLevelForMultiplayer(*MyPlayer); DLevel &deltaLevel = GetDeltaLevel(localLevel); if (leveltype != DTYPE_TOWN) { + for (auto &deltaSpawnedMonster : deltaLevel.spawnedMonsters) { + LoadDeltaSpawnedMonster(deltaSpawnedMonster.second.typeIndex, deltaSpawnedMonster.first, deltaSpawnedMonster.second.seed); + assert(deltaLevel.monster[deltaSpawnedMonster.first].position.x != 0xFF); + } for (size_t i = 0; i < MaxMonsters; i++) { if (deltaLevel.monster[i].position.x == 0xFF) continue; @@ -2641,7 +2720,7 @@ void DeltaLoadLevel() } else { decode_enemy(monster, deltaLevel.monster[i]._menemy); if (monster.position.tile != Point { 0, 0 } && monster.position.tile != GolemHoldingCell) - dMonster[monster.position.tile.x][monster.position.tile.y] = i + 1; + monster.occupyTile(monster.position.tile, false); if (monster.type().type == MT_GOLEM) { GolumAi(monster); monster.flags |= (MFLAG_TARGETS_MONSTER | MFLAG_GOLEM); @@ -2730,9 +2809,9 @@ void NetSendCmd(bool bHiPri, _cmd_id bCmd) cmd.bCmd = bCmd; if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); } void NetSendCmdGolem(uint8_t mx, uint8_t my, Direction dir, uint8_t menemy, int hp, uint8_t cl) @@ -2746,10 +2825,24 @@ void NetSendCmdGolem(uint8_t mx, uint8_t my, Direction dir, uint8_t menemy, int cmd._menemy = menemy; cmd._mhitpoints = hp; cmd._currlevel = cl; - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); +} + +void NetSendCmdSpawnMonster(Point position, Direction dir, uint16_t typeIndex, uint16_t monsterId, uint32_t seed) +{ + TCmdSpawnMonster cmd; + + cmd.bCmd = CMD_SPAWNMONSTER; + cmd.x = position.x; + cmd.y = position.y; + cmd.dir = dir; + cmd.typeIndex = SDL_SwapLE16(typeIndex); + cmd.monsterId = SDL_SwapLE16(monsterId); + cmd.seed = SDL_SwapLE32(seed); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); } -void NetSendCmdLoc(size_t playerId, bool bHiPri, _cmd_id bCmd, Point position) +void NetSendCmdLoc(uint8_t playerId, bool bHiPri, _cmd_id bCmd, Point position) { if (playerId == MyPlayerId && WasPlayerCmdAlreadyRequested(bCmd, position)) return; @@ -2760,9 +2853,9 @@ void NetSendCmdLoc(size_t playerId, bool bHiPri, _cmd_id bCmd, Point position) cmd.x = position.x; cmd.y = position.y; if (bHiPri) - NetSendHiPri(playerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(playerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(playerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(playerId, (std::byte *)&cmd, sizeof(cmd)); MyPlayer->UpdatePreviewCelSprite(bCmd, position, 0, 0); } @@ -2779,9 +2872,9 @@ void NetSendCmdLocParam1(bool bHiPri, _cmd_id bCmd, Point position, uint16_t wPa cmd.y = position.y; cmd.wParam1 = SDL_SwapLE16(wParam1); if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); MyPlayer->UpdatePreviewCelSprite(bCmd, position, wParam1, 0); } @@ -2799,9 +2892,9 @@ void NetSendCmdLocParam2(bool bHiPri, _cmd_id bCmd, Point position, uint16_t wPa cmd.wParam1 = SDL_SwapLE16(wParam1); cmd.wParam2 = SDL_SwapLE16(wParam2); if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); MyPlayer->UpdatePreviewCelSprite(bCmd, position, wParam1, wParam2); } @@ -2820,9 +2913,9 @@ void NetSendCmdLocParam3(bool bHiPri, _cmd_id bCmd, Point position, uint16_t wPa cmd.wParam2 = SDL_SwapLE16(wParam2); cmd.wParam3 = SDL_SwapLE16(wParam3); if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); MyPlayer->UpdatePreviewCelSprite(bCmd, position, wParam1, wParam2); } @@ -2842,9 +2935,9 @@ void NetSendCmdLocParam4(bool bHiPri, _cmd_id bCmd, Point position, uint16_t wPa cmd.wParam3 = SDL_SwapLE16(wParam3); cmd.wParam4 = SDL_SwapLE16(wParam4); if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); MyPlayer->UpdatePreviewCelSprite(bCmd, position, wParam1, wParam3); } @@ -2865,9 +2958,9 @@ void NetSendCmdLocParam5(bool bHiPri, _cmd_id bCmd, Point position, uint16_t wPa cmd.wParam4 = SDL_SwapLE16(wParam4); cmd.wParam5 = SDL_SwapLE16(wParam5); if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); MyPlayer->UpdatePreviewCelSprite(bCmd, position, wParam1, wParam3); } @@ -2882,9 +2975,9 @@ void NetSendCmdParam1(bool bHiPri, _cmd_id bCmd, uint16_t wParam1) cmd.bCmd = bCmd; cmd.wParam1 = SDL_SwapLE16(wParam1); if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); MyPlayer->UpdatePreviewCelSprite(bCmd, {}, wParam1, 0); } @@ -2897,9 +2990,9 @@ void NetSendCmdParam2(bool bHiPri, _cmd_id bCmd, uint16_t wParam1, uint16_t wPar cmd.wParam1 = SDL_SwapLE16(wParam1); cmd.wParam2 = SDL_SwapLE16(wParam2); if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); } void NetSendCmdParam5(bool bHiPri, _cmd_id bCmd, uint16_t wParam1, uint16_t wParam2, uint16_t wParam3, uint16_t wParam4, uint16_t wParam5) @@ -2916,9 +3009,9 @@ void NetSendCmdParam5(bool bHiPri, _cmd_id bCmd, uint16_t wParam1, uint16_t wPar cmd.wParam4 = SDL_SwapLE16(wParam4); cmd.wParam5 = SDL_SwapLE16(wParam5); if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); MyPlayer->UpdatePreviewCelSprite(bCmd, {}, wParam1, wParam2); } @@ -2935,13 +3028,15 @@ void NetSendCmdQuest(bool bHiPri, const Quest &quest) cmd.qmsg = quest._qmsg; if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); } -void NetSendCmdGItem(bool bHiPri, _cmd_id bCmd, uint8_t pnum, uint8_t ii) +void NetSendCmdGItem(bool bHiPri, _cmd_id bCmd, const Player &player, uint8_t ii) { + uint8_t pnum = player.getId(); + TCmdGItem cmd; cmd.bCmd = bCmd; @@ -2955,9 +3050,9 @@ void NetSendCmdGItem(bool bHiPri, _cmd_id bCmd, uint8_t pnum, uint8_t ii) PrepareItemForNetwork(Items[ii], cmd); if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); } void NetSendCmdPItem(bool bHiPri, _cmd_id bCmd, Point position, const Item &item) @@ -2972,9 +3067,9 @@ void NetSendCmdPItem(bool bHiPri, _cmd_id bCmd, Point position, const Item &item ItemLimbo = item; if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); } void NetSendCmdChItem(bool bHiPri, uint8_t bLoc, bool forceSpellChange) @@ -2989,9 +3084,9 @@ void NetSendCmdChItem(bool bHiPri, uint8_t bLoc, bool forceSpellChange) PrepareItemForNetwork(item, cmd); if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); } void NetSendCmdDelItem(bool bHiPri, uint8_t bLoc) @@ -3001,9 +3096,9 @@ void NetSendCmdDelItem(bool bHiPri, uint8_t bLoc) cmd.bLoc = bLoc; cmd.bCmd = CMD_DELPLRITEMS; if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); } void NetSyncInvItem(const Player &player, int invListIndex) @@ -3023,7 +3118,7 @@ void NetSendCmdChInvItem(bool bHiPri, int invGridIndex) { TCmdChItem cmd {}; - int8_t invListIndex = abs(MyPlayer->InvGrid[invGridIndex]) - 1; + int8_t invListIndex = std::abs(MyPlayer->InvGrid[invGridIndex]) - 1; const Item &item = MyPlayer->InvList[invListIndex]; cmd.bCmd = CMD_CHANGEINVITEMS; @@ -3031,9 +3126,9 @@ void NetSendCmdChInvItem(bool bHiPri, int invGridIndex) PrepareItemForNetwork(item, cmd); if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); } void NetSendCmdChBeltItem(bool bHiPri, int beltIndex) @@ -3047,23 +3142,23 @@ void NetSendCmdChBeltItem(bool bHiPri, int beltIndex) PrepareItemForNetwork(item, cmd); if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); } -void NetSendCmdDamage(bool bHiPri, uint8_t bPlr, uint32_t dwDam, DamageType damageType) +void NetSendCmdDamage(bool bHiPri, const Player &player, uint32_t dwDam, DamageType damageType) { TCmdDamage cmd; cmd.bCmd = CMD_PLRDAMAGE; - cmd.bPlr = bPlr; + cmd.bPlr = player.getId(); cmd.dwDam = dwDam; cmd.damageType = damageType; if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); } void NetSendCmdMonDmg(bool bHiPri, uint16_t wMon, uint32_t dwDam) @@ -3074,9 +3169,9 @@ void NetSendCmdMonDmg(bool bHiPri, uint16_t wMon, uint32_t dwDam) cmd.wMon = wMon; cmd.dwDam = dwDam; if (bHiPri) - NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); else - NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); } void NetSendCmdString(uint32_t pmask, const char *pszStr) @@ -3085,15 +3180,15 @@ void NetSendCmdString(uint32_t pmask, const char *pszStr) cmd.bCmd = CMD_STRING; CopyUtf8(cmd.str, pszStr, sizeof(cmd.str)); - multi_send_msg_packet(pmask, (byte *)&cmd, strlen(cmd.str) + 2); + multi_send_msg_packet(pmask, (std::byte *)&cmd, strlen(cmd.str) + 2); } -void delta_close_portal(int pnum) +void delta_close_portal(const Player &player) { - memset(&sgJunk.portal[pnum], 0xFF, sizeof(sgJunk.portal[pnum])); + memset(&sgJunk.portal[player.getId()], 0xFF, sizeof(sgJunk.portal[player.getId()])); } -size_t ParseCmd(size_t pnum, const TCmd *pCmd) +size_t ParseCmd(uint8_t pnum, const TCmd *pCmd) { sbLastCmd = pCmd->bCmd; if (sgwPackPlrOffsetTbl[pnum] != 0 && sbLastCmd != CMD_ACK_PLRINFO && sbLastCmd != CMD_SEND_PLRINFO) @@ -3107,37 +3202,37 @@ size_t ParseCmd(size_t pnum, const TCmd *pCmd) switch (pCmd->bCmd) { case CMD_SYNCDATA: - return OnSyncData(pCmd, pnum); + return OnSyncData(pCmd, player); case CMD_WALKXY: return OnWalk(pCmd, player); case CMD_ADDSTR: - return OnAddStrength(pCmd, pnum); + return OnAddStrength(pCmd, player); case CMD_ADDDEX: - return OnAddDexterity(pCmd, pnum); + return OnAddDexterity(pCmd, player); case CMD_ADDMAG: - return OnAddMagic(pCmd, pnum); + return OnAddMagic(pCmd, player); case CMD_ADDVIT: - return OnAddVitality(pCmd, pnum); + return OnAddVitality(pCmd, player); case CMD_GOTOGETITEM: return OnGotoGetItem(pCmd, player); case CMD_REQUESTGITEM: return OnRequestGetItem(pCmd, player); case CMD_GETITEM: - return OnGetItem(pCmd, pnum); + return OnGetItem(pCmd, player); case CMD_GOTOAGETITEM: return OnGotoAutoGetItem(pCmd, player); case CMD_REQUESTAGITEM: return OnRequestAutoGetItem(pCmd, player); case CMD_AGETITEM: - return OnAutoGetItem(pCmd, pnum); + return OnAutoGetItem(pCmd, player); case CMD_ITEMEXTRA: - return OnItemExtra(pCmd, pnum); + return OnItemExtra(pCmd, player); case CMD_PUTITEM: - return OnPutItem(pCmd, pnum); + return OnPutItem(pCmd, player); case CMD_SYNCPUTITEM: - return OnSyncPutItem(pCmd, pnum); + return OnSyncPutItem(pCmd, player); case CMD_SPAWNITEM: - return OnSpawnItem(pCmd, pnum); + return OnSpawnItem(pCmd, player); case CMD_ATTACKXY: return OnAttackTile(pCmd, player); case CMD_SATTACKXY: @@ -3167,9 +3262,9 @@ size_t ParseCmd(size_t pnum, const TCmd *pCmd) case CMD_SPELLPID: return OnSpellPlayer(pCmd, player); case CMD_KNOCKBACK: - return OnKnockback(pCmd, pnum); + return OnKnockback(pCmd, player); case CMD_RESURRECT: - return OnResurrect(pCmd, pnum); + return OnResurrect(pCmd, player); case CMD_HEALOTHER: return OnHealOther(pCmd, player); case CMD_TALKXY: @@ -3177,72 +3272,72 @@ size_t ParseCmd(size_t pnum, const TCmd *pCmd) case CMD_DEBUG: return OnDebug(pCmd); case CMD_NEWLVL: - return OnNewLevel(pCmd, pnum); + return OnNewLevel(pCmd, player); case CMD_WARP: - return OnWarp(pCmd, pnum); + return OnWarp(pCmd, player); case CMD_MONSTDEATH: - return OnMonstDeath(pCmd, pnum); + return OnMonstDeath(pCmd, player); case CMD_KILLGOLEM: - return OnKillGolem(pCmd, pnum); + return OnKillGolem(pCmd, player); case CMD_AWAKEGOLEM: - return OnAwakeGolem(pCmd, pnum); + return OnAwakeGolem(pCmd, player); case CMD_MONSTDAMAGE: - return OnMonstDamage(pCmd, pnum); + return OnMonstDamage(pCmd, player); case CMD_PLRDEAD: - return OnPlayerDeath(pCmd, pnum); + return OnPlayerDeath(pCmd, player); case CMD_PLRDAMAGE: return OnPlayerDamage(pCmd, player); case CMD_OPENDOOR: case CMD_CLOSEDOOR: case CMD_OPERATEOBJ: - return OnOperateObject(*pCmd, pnum); + return OnOperateObject(*pCmd, player); case CMD_BREAKOBJ: - return OnBreakObject(*pCmd, pnum); + return OnBreakObject(*pCmd, player); case CMD_CHANGEPLRITEMS: - return OnChangePlayerItems(pCmd, pnum); + return OnChangePlayerItems(pCmd, player); case CMD_DELPLRITEMS: - return OnDeletePlayerItems(pCmd, pnum); + return OnDeletePlayerItems(pCmd, player); case CMD_CHANGEINVITEMS: - return OnChangeInventoryItems(pCmd, pnum); + return OnChangeInventoryItems(pCmd, player); case CMD_DELINVITEMS: - return OnDeleteInventoryItems(pCmd, pnum); + return OnDeleteInventoryItems(pCmd, player); case CMD_CHANGEBELTITEMS: - return OnChangeBeltItems(pCmd, pnum); + return OnChangeBeltItems(pCmd, player); case CMD_DELBELTITEMS: - return OnDeleteBeltItems(pCmd, pnum); + return OnDeleteBeltItems(pCmd, player); case CMD_PLRLEVEL: - return OnPlayerLevel(pCmd, pnum); + return OnPlayerLevel(pCmd, player); case CMD_DROPITEM: - return OnDropItem(pCmd, pnum); + return OnDropItem(pCmd, player); case CMD_ACK_PLRINFO: case CMD_SEND_PLRINFO: - return OnSendPlayerInfo(pCmd, pnum); + return OnSendPlayerInfo(pCmd, player); case CMD_PLAYER_JOINLEVEL: - return OnPlayerJoinLevel(pCmd, pnum); + return OnPlayerJoinLevel(pCmd, player); case CMD_ACTIVATEPORTAL: - return OnActivatePortal(pCmd, pnum); + return OnActivatePortal(pCmd, player); case CMD_DEACTIVATEPORTAL: - return OnDeactivatePortal(pCmd, pnum); + return OnDeactivatePortal(pCmd, player); case CMD_RETOWN: - return OnRestartTown(pCmd, pnum); + return OnRestartTown(pCmd, player); case CMD_SETSTR: - return OnSetStrength(pCmd, pnum); + return OnSetStrength(pCmd, player); case CMD_SETMAG: - return OnSetMagic(pCmd, pnum); + return OnSetMagic(pCmd, player); case CMD_SETDEX: - return OnSetDexterity(pCmd, pnum); + return OnSetDexterity(pCmd, player); case CMD_SETVIT: - return OnSetVitality(pCmd, pnum); + return OnSetVitality(pCmd, player); case CMD_STRING: return OnString(pCmd, player); case CMD_FRIENDLYMODE: return OnFriendlyMode(pCmd, player); case CMD_SYNCQUEST: - return OnSyncQuest(pCmd, pnum); + return OnSyncQuest(pCmd, player); case CMD_CHEAT_EXPERIENCE: - return OnCheatExperience(pCmd, pnum); + return OnCheatExperience(pCmd, player); case CMD_CHANGE_SPELL_LEVEL: - return OnChangeSpellLevel(pCmd, pnum); + return OnChangeSpellLevel(pCmd, player); case CMD_SETSHIELD: return OnSetShield(pCmd, player); case CMD_REMSHIELD: @@ -3252,9 +3347,11 @@ size_t ParseCmd(size_t pnum, const TCmd *pCmd) case CMD_NAKRUL: return OnNakrul(pCmd); case CMD_OPENHIVE: - return OnOpenHive(pCmd, pnum); + return OnOpenHive(pCmd, player); case CMD_OPENGRAVE: return OnOpenGrave(pCmd); + case CMD_SPAWNMONSTER: + return OnSpawnMonster(pCmd, player); default: break; } diff --git a/Source/msg.h b/Source/msg.h index 0d4f3d3a6aa..c6229ad7cac 100644 --- a/Source/msg.h +++ b/Source/msg.h @@ -1,7 +1,7 @@ /** * @file msg.h * - * Interface of function for sending and reciving network messages. + * Interface of function for sending and receiving network messages. */ #pragma once @@ -416,6 +416,10 @@ enum _cmd_id : uint8_t { CMD_NAKRUL, CMD_OPENHIVE, CMD_OPENGRAVE, + // Spawn a monster at target location. + // + // body (TCmdSpawnMonster) + CMD_SPAWNMONSTER, // Fake command; set current player for succeeding mega pkt buffer messages. // // body (TFakeCmdPlr) @@ -514,6 +518,16 @@ struct TCmdGolem { uint8_t _currlevel; }; +struct TCmdSpawnMonster { + _cmd_id bCmd; + uint8_t x; + uint8_t y; + Direction dir; + uint16_t typeIndex; + uint16_t monsterId; + uint32_t seed; +}; + struct TCmdQuest { _cmd_id bCmd; int8_t q; @@ -708,25 +722,20 @@ struct TPktHdr { struct TPkt { TPktHdr hdr; - byte body[493]; + std::byte body[493]; }; #pragma pack(pop) -struct TBuffer { - uint32_t dwNextWriteOffset; - byte bData[4096]; -}; - extern uint8_t gbBufferMsgs; extern int dwRecCount; void PrepareItemForNetwork(const Item &item, TItem &messageItem); void PrepareEarForNetwork(const Item &item, TEar &ear); void RecreateItem(const Player &player, const TItem &messageItem, Item &item); -void msg_send_drop_pkt(int pnum, int reason); +void msg_send_drop_pkt(uint8_t pnum, int reason); bool msg_wait_resync(); void run_delta_info(); -void DeltaExportData(int pnum); +void DeltaExportData(uint8_t pnum); void DeltaSyncJunk(); void delta_init(); void DeltaClearLevel(uint8_t level); @@ -743,7 +752,8 @@ void DeltaLoadLevel(); void ClearLastSentPlayerCmd(); void NetSendCmd(bool bHiPri, _cmd_id bCmd); void NetSendCmdGolem(uint8_t mx, uint8_t my, Direction dir, uint8_t menemy, int hp, uint8_t cl); -void NetSendCmdLoc(size_t playerId, bool bHiPri, _cmd_id bCmd, Point position); +void NetSendCmdSpawnMonster(Point position, Direction dir, uint16_t typeIndex, uint16_t monsterId, uint32_t seed); +void NetSendCmdLoc(uint8_t playerId, bool bHiPri, _cmd_id bCmd, Point position); void NetSendCmdLocParam1(bool bHiPri, _cmd_id bCmd, Point position, uint16_t wParam1); void NetSendCmdLocParam2(bool bHiPri, _cmd_id bCmd, Point position, uint16_t wParam1, uint16_t wParam2); void NetSendCmdLocParam3(bool bHiPri, _cmd_id bCmd, Point position, uint16_t wParam1, uint16_t wParam2, uint16_t wParam3); @@ -753,17 +763,17 @@ void NetSendCmdParam1(bool bHiPri, _cmd_id bCmd, uint16_t wParam1); void NetSendCmdParam2(bool bHiPri, _cmd_id bCmd, uint16_t wParam1, uint16_t wParam2); void NetSendCmdParam5(bool bHiPri, _cmd_id bCmd, uint16_t wParam1, uint16_t wParam2, uint16_t wParam3, uint16_t wParam4, uint16_t wParam5); void NetSendCmdQuest(bool bHiPri, const Quest &quest); -void NetSendCmdGItem(bool bHiPri, _cmd_id bCmd, uint8_t pnum, uint8_t ii); +void NetSendCmdGItem(bool bHiPri, _cmd_id bCmd, const Player &player, uint8_t ii); void NetSendCmdPItem(bool bHiPri, _cmd_id bCmd, Point position, const Item &item); void NetSyncInvItem(const Player &player, int invListIndex); void NetSendCmdChItem(bool bHiPri, uint8_t bLoc, bool forceSpellChange = false); void NetSendCmdDelItem(bool bHiPri, uint8_t bLoc); void NetSendCmdChInvItem(bool bHiPri, int invGridIndex); void NetSendCmdChBeltItem(bool bHiPri, int invGridIndex); -void NetSendCmdDamage(bool bHiPri, uint8_t bPlr, uint32_t dwDam, DamageType damageType); +void NetSendCmdDamage(bool bHiPri, const Player &player, uint32_t dwDam, DamageType damageType); void NetSendCmdMonDmg(bool bHiPri, uint16_t wMon, uint32_t dwDam); void NetSendCmdString(uint32_t pmask, const char *pszStr); -void delta_close_portal(int pnum); -size_t ParseCmd(size_t pnum, const TCmd *pCmd); +void delta_close_portal(const Player &player); +size_t ParseCmd(uint8_t pnum, const TCmd *pCmd); } // namespace devilution diff --git a/Source/multi.cpp b/Source/multi.cpp index 32452f5851b..91b85afabd1 100644 --- a/Source/multi.cpp +++ b/Source/multi.cpp @@ -4,8 +4,10 @@ * Implementation of functions for keeping multiplaye games in sync. */ +#include #include #include +#include #include #include @@ -28,14 +30,11 @@ #include "tmsg.h" #include "utils/endian.hpp" #include "utils/language.h" -#include "utils/stdcompat/cstddef.hpp" -#include "utils/stdcompat/string_view.hpp" #include "utils/str_cat.hpp" namespace devilution { bool gbSomebodyWonGameKludge; -TBuffer highPriorityBuffer; uint16_t sgwPackPlrOffsetTbl[MAX_PLRS]; bool sgbPlayerTurnBitTbl[MAX_PLRS]; bool sgbPlayerLeftGameTbl[MAX_PLRS]; @@ -47,7 +46,6 @@ GameData sgGameInitInfo; bool gbSelectProvider; int sglTimeoutStart; int sgdwPlayerLeftReasonTbl[MAX_PLRS]; -TBuffer lowPriorityBuffer; uint32_t sgdwGameLoops; /** * Specifies the maximum number of players in a game, where 1 @@ -76,6 +74,14 @@ const event_type EventTypes[3] = { namespace { +struct TBuffer { + size_t dwNextWriteOffset; + std::byte bData[4096]; +}; + +TBuffer highPriorityBuffer; +TBuffer lowPriorityBuffer; + constexpr uint16_t HeaderCheckVal = #if SDL_BYTEORDER == SDL_LIL_ENDIAN LoadBE16("ip"); @@ -88,27 +94,27 @@ uint32_t sgbSentThisCycle; void BufferInit(TBuffer *pBuf) { pBuf->dwNextWriteOffset = 0; - pBuf->bData[0] = byte { 0 }; + pBuf->bData[0] = std::byte { 0 }; } -void CopyPacket(TBuffer *buf, const byte *packet, size_t size) +void CopyPacket(TBuffer *buf, const std::byte *packet, size_t size) { if (buf->dwNextWriteOffset + size + 2 > 0x1000) { return; } - byte *p = &buf->bData[buf->dwNextWriteOffset]; + std::byte *p = &buf->bData[buf->dwNextWriteOffset]; buf->dwNextWriteOffset += size + 1; - *p = static_cast(size); + *p = static_cast(size); p++; memcpy(p, packet, size); - p[size] = byte { 0 }; + p[size] = std::byte { 0 }; } -byte *CopyBufferedPackets(byte *destination, TBuffer *source, size_t *size) +std::byte *CopyBufferedPackets(std::byte *destination, TBuffer *source, size_t *size) { if (source->dwNextWriteOffset != 0) { - byte *srcPtr = source->bData; + std::byte *srcPtr = source->bData; while (true) { auto chunkSize = static_cast(*srcPtr); if (chunkSize == 0) @@ -121,8 +127,8 @@ byte *CopyBufferedPackets(byte *destination, TBuffer *source, size_t *size) srcPtr += chunkSize; *size -= chunkSize; } - memcpy(source->bData, srcPtr, (source->bData - srcPtr) + source->dwNextWriteOffset + 1); - source->dwNextWriteOffset += static_cast(source->bData - srcPtr); + memmove(source->bData, srcPtr, (source->bData - srcPtr) + source->dwNextWriteOffset + 1); + source->dwNextWriteOffset += source->bData - srcPtr; return destination; } return destination; @@ -153,17 +159,17 @@ void NetReceivePlayerData(TPkt *pkt) bool IsNetPlayerValid(const Player &player) { - return player._pLevel >= 1 - && player._pLevel <= MaxCharacterLevel - && static_cast(player._pClass) < enum_size::value + // we no longer check character level here, players with out-of-range clevels are not allowed to join the game and we don't observe change clevel messages that would set it out of range + // (there's no code path that would result in _pLevel containing an out of range value in the DevilutionX code) + return static_cast(player._pClass) < enum_size::value && player.plrlevel < NUMLEVELS && InDungeonBounds(player.position.tile) - && !string_view(player._pName).empty(); + && !std::string_view(player._pName).empty(); } void CheckPlayerInfoTimeouts() { - for (size_t i = 0; i < Players.size(); i++) { + for (uint8_t i = 0; i < Players.size(); i++) { Player &player = Players[i]; if (&player == MyPlayer) { continue; @@ -192,7 +198,7 @@ void CheckPlayerInfoTimeouts() } } -void SendPacket(int playerId, const byte *packet, size_t size) +void SendPacket(uint8_t playerId, const std::byte *packet, size_t size) { TPkt pkt; @@ -208,13 +214,13 @@ void MonsterSeeds() { sgdwGameLoops++; const uint32_t seed = (sgdwGameLoops >> 8) | (sgdwGameLoops << 24); - for (size_t i = 0; i < MaxMonsters; i++) + for (uint32_t i = 0; i < MaxMonsters; i++) Monsters[i].aiSeed = seed + i; } -void HandleTurnUpperBit(size_t pnum) +void HandleTurnUpperBit(uint8_t pnum) { - size_t i; + uint8_t i; for (i = 0; i < Players.size(); i++) { if ((player_state[i] & PS_CONNECTED) != 0 && i != pnum) @@ -228,7 +234,7 @@ void HandleTurnUpperBit(size_t pnum) } } -void ParseTurn(size_t pnum, uint32_t turn) +void ParseTurn(uint8_t pnum, uint32_t turn) { if ((turn & 0x80000000) != 0) HandleTurnUpperBit(pnum); @@ -241,10 +247,8 @@ void ParseTurn(size_t pnum, uint32_t turn) } } -void PlayerLeftMsg(int pnum, bool left) +void PlayerLeftMsg(Player &player, bool left) { - Player &player = Players[pnum]; - if (&player == InspectPlayer) InspectPlayer = MyPlayer; @@ -254,13 +258,13 @@ void PlayerLeftMsg(int pnum, bool left) return; FixPlrWalkTags(player); - RemovePortalMissile(pnum); - DeactivatePortal(pnum); - delta_close_portal(pnum); + RemovePortalMissile(player); + DeactivatePortal(player); + delta_close_portal(player); RemovePlrMissiles(player); if (left) { - string_view pszFmt = _("Player '{:s}' just left the game"); - switch (sgdwPlayerLeftReasonTbl[pnum]) { + std::string_view pszFmt = _("Player '{:s}' just left the game"); + switch (sgdwPlayerLeftReasonTbl[player.getId()]) { case LEAVE_ENDING: pszFmt = _("Player '{:s}' killed Diablo and left the game!"); gbSomebodyWonGameKludge = true; @@ -279,12 +283,12 @@ void PlayerLeftMsg(int pnum, bool left) void ClearPlayerLeftState() { - for (size_t i = 0; i < Players.size(); i++) { + for (uint8_t i = 0; i < Players.size(); i++) { if (sgbPlayerLeftGameTbl[i]) { if (gbBufferMsgs == 1) msg_send_drop_pkt(i, sgdwPlayerLeftReasonTbl[i]); else - PlayerLeftMsg(i, true); + PlayerLeftMsg(Players[i], true); sgbPlayerLeftGameTbl[i] = false; sgdwPlayerLeftReasonTbl[i] = 0; @@ -294,7 +298,7 @@ void ClearPlayerLeftState() void CheckDropPlayer() { - for (size_t i = 0; i < Players.size(); i++) { + for (uint8_t i = 0; i < Players.size(); i++) { if ((player_state[i] & PS_ACTIVE) == 0 && (player_state[i] & PS_CONNECTED) != 0) { SNetDropPlayer(i, LEAVE_DROP); } @@ -324,9 +328,9 @@ void BeginTimeout() CheckDropPlayer(); } -void HandleAllPackets(size_t pnum, const byte *data, size_t size) +void HandleAllPackets(uint8_t pnum, const std::byte *data, size_t size) { - for (unsigned offset = 0; offset < size;) { + for (size_t offset = 0; offset < size;) { size_t messageSize = ParseCmd(pnum, reinterpret_cast(&data[offset])); if (messageSize == 0) { break; @@ -338,7 +342,7 @@ void HandleAllPackets(size_t pnum, const byte *data, size_t size) void ProcessTmsgs() { while (true) { - std::unique_ptr msg; + std::unique_ptr msg; uint8_t size = tmsg_get(&msg); if (size == 0) break; @@ -347,12 +351,12 @@ void ProcessTmsgs() } } -void SendPlayerInfo(int pnum, _cmd_id cmd) +void SendPlayerInfo(uint8_t pnum, _cmd_id cmd) { PlayerNetPack packed; Player &myPlayer = *MyPlayer; PackNetPlayer(packed, myPlayer); - multi_send_zero_packet(pnum, cmd, reinterpret_cast(&packed), sizeof(PlayerNetPack)); + multi_send_zero_packet(pnum, cmd, reinterpret_cast(&packed), sizeof(PlayerNetPack)); } void SetupLocalPositions() @@ -495,7 +499,7 @@ void InitGameInfo() sgGameInitInfo.fullQuests = (!gbIsMultiplayer || *sgOptions.Gameplay.multiplayerFullQuests) ? 1 : 0; } -void NetSendLoPri(int playerId, const byte *data, size_t size) +void NetSendLoPri(uint8_t playerId, const std::byte *data, size_t size) { if (data != nullptr && size != 0) { CopyPacket(&lowPriorityBuffer, data, size); @@ -503,7 +507,7 @@ void NetSendLoPri(int playerId, const byte *data, size_t size) } } -void NetSendHiPri(int playerId, const byte *data, size_t size) +void NetSendHiPri(uint8_t playerId, const std::byte *data, size_t size) { if (data != nullptr && size != 0) { CopyPacket(&highPriorityBuffer, data, size); @@ -513,29 +517,29 @@ void NetSendHiPri(int playerId, const byte *data, size_t size) shareNextHighPriorityMessage = false; TPkt pkt; NetReceivePlayerData(&pkt); - byte *destination = pkt.body; + std::byte *destination = pkt.body; size_t remainingSpace = gdwNormalMsgSize - sizeof(TPktHdr); destination = CopyBufferedPackets(destination, &highPriorityBuffer, &remainingSpace); destination = CopyBufferedPackets(destination, &lowPriorityBuffer, &remainingSpace); remainingSpace = sync_all_monsters(destination, remainingSpace); const size_t len = gdwNormalMsgSize - remainingSpace; pkt.hdr.wLen = SDL_SwapLE16(static_cast(len)); - if (!SNetSendMessage(SNPLAYER_OTHERS, &pkt.hdr, static_cast(len))) + if (!SNetSendMessage(SNPLAYER_OTHERS, &pkt.hdr, len)) nthread_terminate_game("SNetSendMessage"); } } -void multi_send_msg_packet(uint32_t pmask, const byte *data, size_t size) +void multi_send_msg_packet(uint32_t pmask, const std::byte *data, size_t size) { TPkt pkt; NetReceivePlayerData(&pkt); const size_t len = size + sizeof(pkt.hdr); pkt.hdr.wLen = SDL_SwapLE16(static_cast(len)); memcpy(pkt.body, data, size); - size_t playerID = 0; - for (size_t v = 1; playerID < Players.size(); playerID++, v <<= 1) { + uint8_t playerID = 0; + for (uint32_t v = 1; playerID < Players.size(); playerID++, v <<= 1) { if ((v & pmask) != 0) { - if (!SNetSendMessage(playerID, &pkt.hdr, len) && SErrGetLastError() != STORM_ERROR_INVALID_PLAYER) { + if (!SNetSendMessage(playerID, &pkt.hdr, len)) { nthread_terminate_game("SNetSendMessage"); return; } @@ -545,7 +549,7 @@ void multi_send_msg_packet(uint32_t pmask, const byte *data, size_t size) void multi_msg_countdown() { - for (size_t i = 0; i < Players.size(); i++) { + for (uint8_t i = 0; i < Players.size(); i++) { if ((player_state[i] & PS_TURN_ARRIVED) != 0) { if (gdwMsgLenTbl[i] == sizeof(int32_t)) ParseTurn(i, *(int32_t *)glpMsgTbl[i]); @@ -553,7 +557,7 @@ void multi_msg_countdown() } } -void multi_player_left(int pnum, int reason) +void multi_player_left(uint8_t pnum, int reason) { sgbPlayerLeftGameTbl[pnum] = true; sgdwPlayerLeftReasonTbl[pnum] = reason; @@ -573,7 +577,7 @@ bool multi_handle_delta() return false; } - for (size_t i = 0; i < Players.size(); i++) { + for (uint8_t i = 0; i < Players.size(); i++) { if (sgbSendDeltaTbl[i]) { sgbSendDeltaTbl[i] = false; DeltaExportData(i); @@ -614,7 +618,7 @@ void multi_process_network_packets() uint8_t playerId = std::numeric_limits::max(); TPktHdr *pkt; - uint32_t dwMsgSize = 0; + size_t dwMsgSize = 0; while (SNetReceiveMessage(&playerId, (void **)&pkt, &dwMsgSize)) { dwRecCount++; ClearPlayerLeftState(); @@ -661,7 +665,7 @@ void multi_process_network_packets() if (player.isWalking()) player.position.temp = syncPosition; SetPlayerOld(player); - dPlayer[player.position.tile.x][player.position.tile.y] = playerId + 1; + player.occupyTile(player.position.tile, false); } if (player.position.future.WalkingDistance(player.position.tile) > 1) { player.position.future = player.position.tile; @@ -676,14 +680,12 @@ void multi_process_network_packets() } } } - HandleAllPackets(playerId, (const byte *)(pkt + 1), dwMsgSize - sizeof(TPktHdr)); + HandleAllPackets(playerId, (const std::byte *)(pkt + 1), dwMsgSize - sizeof(TPktHdr)); } - if (SErrGetLastError() != STORM_ERROR_NO_MESSAGES_WAITING) - nthread_terminate_game("SNetReceiveMsg"); CheckPlayerInfoTimeouts(); } -void multi_send_zero_packet(size_t pnum, _cmd_id bCmd, const byte *data, size_t size) +void multi_send_zero_packet(uint8_t pnum, _cmd_id bCmd, const std::byte *data, size_t size) { assert(pnum != MyPlayerId); assert(data != nullptr); @@ -694,17 +696,19 @@ void multi_send_zero_packet(size_t pnum, _cmd_id bCmd, const byte *data, size_t pkt.hdr.wCheck = HeaderCheckVal; auto &message = *reinterpret_cast(pkt.body); message.bCmd = bCmd; - message.wOffset = SDL_SwapLE16(offset); + assert(offset <= 0x0ffff); + message.wOffset = SDL_SwapLE16(static_cast(offset)); size_t dwBody = gdwLargestMsgSize - sizeof(pkt.hdr) - sizeof(message); dwBody = std::min(dwBody, size - offset); assert(dwBody <= 0x0ffff); - message.wBytes = SDL_SwapLE16(dwBody); + message.wBytes = SDL_SwapLE16(static_cast(dwBody)); memcpy(&pkt.body[sizeof(message)], &data[offset], dwBody); const size_t dwMsg = sizeof(pkt.hdr) + sizeof(message) + dwBody; - pkt.hdr.wLen = SDL_SwapLE16(dwMsg); + assert(dwMsg <= 0x0ffff); + pkt.hdr.wLen = SDL_SwapLE16(static_cast(dwMsg)); if (!SNetSendMessage(pnum, &pkt, dwMsg)) { nthread_terminate_game("SNetSendMessage2"); @@ -794,20 +798,19 @@ bool NetInit(bool bSinglePlayer) Player &myPlayer = *MyPlayer; // separator for marking messages from a different game AddMessageToChatLog(_("New Game"), nullptr, UiFlags::ColorRed); - AddMessageToChatLog(fmt::format(fmt::runtime(_("Player '{:s}' (level {:d}) just joined the game")), myPlayer._pName, myPlayer._pLevel)); + AddMessageToChatLog(fmt::format(fmt::runtime(_("Player '{:s}' (level {:d}) just joined the game")), myPlayer._pName, myPlayer.getCharacterLevel())); return true; } -void recv_plrinfo(int pnum, const TCmdPlrInfoHdr &header, bool recv) +void recv_plrinfo(Player &player, const TCmdPlrInfoHdr &header, bool recv) { static PlayerNetPack PackedPlayerBuffer[MAX_PLRS]; - assert(pnum >= 0 && pnum < MAX_PLRS); - Player &player = Players[pnum]; if (&player == MyPlayer) { return; } + uint8_t pnum = player.getId(); auto &packedPlayer = PackedPlayerBuffer[pnum]; if (sgwPackPlrOffsetTbl[pnum] != SDL_SwapLE16(header.wOffset)) { @@ -828,7 +831,7 @@ void recv_plrinfo(int pnum, const TCmdPlrInfoHdr &header, bool recv) } sgwPackPlrOffsetTbl[pnum] = 0; - PlayerLeftMsg(pnum, false); + PlayerLeftMsg(player, false); if (!UnPackNetPlayer(packedPlayer, player)) { player = {}; SNetDropPlayer(pnum, LEAVE_DROP); @@ -843,13 +846,13 @@ void recv_plrinfo(int pnum, const TCmdPlrInfoHdr &header, bool recv) player.plractive = true; gbActivePlayers++; - string_view szEvent; + std::string_view szEvent; if (sgbPlayerTurnBitTbl[pnum]) { szEvent = _("Player '{:s}' (level {:d}) just joined the game"); } else { szEvent = _("Player '{:s}' (level {:d}) is already in the game"); } - EventPlrMsg(fmt::format(fmt::runtime(szEvent), player._pName, player._pLevel)); + EventPlrMsg(fmt::format(fmt::runtime(szEvent), player._pName, player.getCharacterLevel())); SyncInitPlr(player); diff --git a/Source/multi.h b/Source/multi.h index 0772828ee03..b71c1aef914 100644 --- a/Source/multi.h +++ b/Source/multi.h @@ -14,6 +14,9 @@ namespace devilution { +// Defined in player.h, forward declared here to allow for functions which operate in the context of a player. +struct Player; + // must be unsigned to generate unsigned comparisons with pnum #define MAX_PLRS 4 @@ -56,11 +59,11 @@ extern uint32_t player_state[MAX_PLRS]; extern bool IsLoopback; void InitGameInfo(); -void NetSendLoPri(int playerId, const byte *data, size_t size); -void NetSendHiPri(int playerId, const byte *data, size_t size); -void multi_send_msg_packet(uint32_t pmask, const byte *data, size_t size); +void NetSendLoPri(uint8_t playerId, const std::byte *data, size_t size); +void NetSendHiPri(uint8_t playerId, const std::byte *data, size_t size); +void multi_send_msg_packet(uint32_t pmask, const std::byte *data, size_t size); void multi_msg_countdown(); -void multi_player_left(int pnum, int reason); +void multi_player_left(uint8_t pnum, int reason); void multi_net_ping(); /** @@ -68,9 +71,9 @@ void multi_net_ping(); */ bool multi_handle_delta(); void multi_process_network_packets(); -void multi_send_zero_packet(size_t pnum, _cmd_id bCmd, const byte *data, size_t size); +void multi_send_zero_packet(uint8_t pnum, _cmd_id bCmd, const std::byte *data, size_t size); void NetClose(); bool NetInit(bool bSinglePlayer); -void recv_plrinfo(int pnum, const TCmdPlrInfoHdr &header, bool recv); +void recv_plrinfo(Player &player, const TCmdPlrInfoHdr &header, bool recv); } // namespace devilution diff --git a/Source/nthread.cpp b/Source/nthread.cpp index edc7fa3a66d..b3507826a49 100644 --- a/Source/nthread.cpp +++ b/Source/nthread.cpp @@ -67,14 +67,7 @@ void NthreadHandler() void nthread_terminate_game(const char *pszFcn) { - uint32_t sErr = SErrGetLastError(); - if (sErr == STORM_ERROR_INVALID_PLAYER) { - return; - } - if (sErr != STORM_ERROR_GAME_TERMINATED && sErr != STORM_ERROR_NOT_IN_GAME) { - app_fatal(StrCat(pszFcn, ":\n", pszFcn)); - } - + app_fatal(pszFcn); gbGameDestroyed = true; } @@ -121,8 +114,6 @@ bool nthread_recv_turns(bool *pfSendAsync) return true; } if (!SNetReceiveTurns(MAX_PLRS, (char **)glpMsgTbl, gdwMsgLenTbl, &player_state[0])) { - if (SErrGetLastError() != STORM_ERROR_NO_MESSAGES_WAITING) - nthread_terminate_game("SNetReceiveTurns"); sgbTicsOutOfSync = false; sgbSyncCountdown = 1; sgbPacketCountdown = 1; @@ -258,7 +249,7 @@ void nthread_UpdateProgressToNextGameTick() } int ticksAdvanced = gnTickDelay - ticksMissing; int32_t fraction = ticksAdvanced * AnimationInfo::baseValueFraction / gnTickDelay; - fraction = clamp(fraction, 0, AnimationInfo::baseValueFraction); + fraction = std::clamp(fraction, 0, AnimationInfo::baseValueFraction); ProgressToNextGameTick = static_cast(fraction); } diff --git a/Source/objdat.cpp b/Source/objdat.cpp index 274582d2682..bbac168df5c 100644 --- a/Source/objdat.cpp +++ b/Source/objdat.cpp @@ -233,8 +233,8 @@ const ObjectData AllObjects[109] = { /*OBJ_SHRINEL*/ { OFILE_LSHRINEG, 0, 0, DTYPE_NONE, THEME_SHRINE, Q_INVALID, Light, 1, 11, 128, 3 }, /*OBJ_SHRINER*/ { OFILE_RSHRINEG, 0, 0, DTYPE_NONE, THEME_SHRINE, Q_INVALID, Light, 1, 11, 128, 3 }, /*OBJ_SKELBOOK*/ { OFILE_BOOK2, 0, 0, DTYPE_NONE, THEME_SKELROOM, Q_INVALID, Solid | MissilesPassThrough | Light, 4, 0, 96, 3 }, -/*OBJ_BOOKCASEL*/ { OFILE_BCASE, 0, 0, DTYPE_NONE, THEME_LIBRARY, Q_INVALID, Light, 3, 0, 96, 3 }, -/*OBJ_BOOKCASER*/ { OFILE_BCASE, 0, 0, DTYPE_NONE, THEME_LIBRARY, Q_INVALID, Light, 4, 0, 96, 3 }, +/*OBJ_BOOKCASEL*/ { OFILE_BCASE, 0, 0, DTYPE_NONE, THEME_LIBRARY, Q_INVALID, Solid | Light, 3, 0, 96, 3 }, +/*OBJ_BOOKCASER*/ { OFILE_BCASE, 0, 0, DTYPE_NONE, THEME_LIBRARY, Q_INVALID, Solid | Light, 4, 0, 96, 3 }, /*OBJ_BOOKSTAND*/ { OFILE_BOOK2, 0, 0, DTYPE_NONE, THEME_LIBRARY, Q_INVALID, Solid | MissilesPassThrough | Light, 1, 0, 96, 3 }, /*OBJ_BOOKCANDLE*/ { OFILE_CANDLE2, 0, 0, DTYPE_NONE, THEME_LIBRARY, Q_INVALID, Animated | Solid | MissilesPassThrough | Light, 2, 4, 96, 0 }, /*OBJ_BLOODFTN*/ { OFILE_BLOODFNT, 0, 0, DTYPE_NONE, THEME_BLOODFOUNTAIN, Q_INVALID, Animated | Solid | MissilesPassThrough | Light, 2, 10, 96, 3 }, diff --git a/Source/objects.cpp b/Source/objects.cpp index 0b78927770b..4dc47694f50 100644 --- a/Source/objects.cpp +++ b/Source/objects.cpp @@ -4,6 +4,7 @@ * Implementation of object functionality, interaction, spawning, loading, etc. */ #include +#include #include #include @@ -17,12 +18,12 @@ #ifdef _DEBUG #include "debug.h" #endif +#include "diablo_msg.hpp" #include "engine/backbuffer_state.hpp" #include "engine/load_cel.hpp" #include "engine/load_file.hpp" #include "engine/points_in_rectangle_range.hpp" #include "engine/random.hpp" -#include "error.h" #include "init.h" #include "inv.h" #include "inv_iterators.hpp" @@ -39,6 +40,7 @@ #include "stores.h" #include "towners.h" #include "track.h" +#include "utils/algorithm/container.hpp" #include "utils/language.h" #include "utils/log.hpp" #include "utils/str_cat.hpp" @@ -154,83 +156,6 @@ const char *const ShrineNames[] = { // TRANSLATORS: Shrine Name Block end N_("Murphy's"), }; -/** Specifies the minimum dungeon level on which each shrine will appear. */ -char shrinemin[] = { - 1, // Mysterious - 1, // Hidden - 1, // Gloomy - 1, // Weird - 1, // Magical - 1, // Stone - 1, // Religious - 1, // Enchanted - 1, // Thaumaturgic - 1, // Fascinating - 1, // Cryptic - 1, // Magical - 1, // Eldritch - 1, // Eerie - 1, // Divine - 1, // Holy - 1, // Sacred - 1, // Spiritual - 1, // Spooky - 1, // Abandoned - 1, // Creepy - 1, // Quiet - 1, // Secluded - 1, // Ornate - 1, // Glimmering - 1, // Tainted - 1, // Oily - 1, // Glowing - 1, // Mendicant's - 1, // Sparkling - 1, // Town - 1, // Shimmering - 1, // Solar, - 1, // Murphy's -}; - -#define MAX_LVLS 24 - -/** Specifies the maximum dungeon level on which each shrine will appear. */ -char shrinemax[] = { - MAX_LVLS, // Mysterious - MAX_LVLS, // Hidden - MAX_LVLS, // Gloomy - MAX_LVLS, // Weird - MAX_LVLS, // Magical - MAX_LVLS, // Stone - MAX_LVLS, // Religious - 8, // Enchanted - MAX_LVLS, // Thaumaturgic - MAX_LVLS, // Fascinating - MAX_LVLS, // Cryptic - MAX_LVLS, // Magical - MAX_LVLS, // Eldritch - MAX_LVLS, // Eerie - MAX_LVLS, // Divine - MAX_LVLS, // Holy - MAX_LVLS, // Sacred - MAX_LVLS, // Spiritual - MAX_LVLS, // Spooky - MAX_LVLS, // Abandoned - MAX_LVLS, // Creepy - MAX_LVLS, // Quiet - MAX_LVLS, // Secluded - MAX_LVLS, // Ornate - MAX_LVLS, // Glimmering - MAX_LVLS, // Tainted - MAX_LVLS, // Oily - MAX_LVLS, // Glowing - MAX_LVLS, // Mendicant's - MAX_LVLS, // Sparkling - MAX_LVLS, // Town - MAX_LVLS, // Shimmering - MAX_LVLS, // Solar, - MAX_LVLS, // Murphy's -}; /** * Specifies the game type for which each shrine may appear. @@ -306,19 +231,24 @@ _speech_id StoryText[3][3] = { { TEXT_BOOK31, TEXT_BOOK32, TEXT_BOOK33 } }; -bool RndLocOk(int xp, int yp) +bool RndLocOk(Point p) { - if (dMonster[xp][yp] != 0) + if (dMonster[p.x][p.y] != 0) return false; - if (dPlayer[xp][yp] != 0) + if (dPlayer[p.x][p.y] != 0) return false; - if (IsObjectAtPosition({ xp, yp })) + if (IsObjectAtPosition(p)) return false; - if (TileContainsSetPiece({ xp, yp })) + if (TileContainsSetPiece(p)) return false; - if (TileHasAny(dPiece[xp][yp], TileProperties::Solid)) + if (TileHasAny(dPiece[p.x][p.y], TileProperties::Solid)) return false; - return IsNoneOf(leveltype, DTYPE_CATHEDRAL, DTYPE_CRYPT) || dPiece[xp][yp] <= 125 || dPiece[xp][yp] >= 143; + return IsNoneOf(leveltype, DTYPE_CATHEDRAL, DTYPE_CRYPT) || dPiece[p.x][p.y] <= 125 || dPiece[p.x][p.y] >= 143; +} + +bool IsAreaOk(Rectangle rect) +{ + return c_all_of(PointsInRectangle(rect), &RndLocOk); } bool CanPlaceWallTrap(int xp, int yp) @@ -339,15 +269,7 @@ void InitRndLocObj(int min, int max, _object_id objtype) while (true) { int xp = GenerateRnd(80) + 16; int yp = GenerateRnd(80) + 16; - if (RndLocOk(xp - 1, yp - 1) - && RndLocOk(xp, yp - 1) - && RndLocOk(xp + 1, yp - 1) - && RndLocOk(xp - 1, yp) - && RndLocOk(xp, yp) - && RndLocOk(xp + 1, yp) - && RndLocOk(xp - 1, yp + 1) - && RndLocOk(xp, yp + 1) - && RndLocOk(xp + 1, yp + 1)) { + if (IsAreaOk(Rectangle { { xp - 1, yp - 1 }, { 3, 3 } })) { AddObject(objtype, { xp, yp }); break; } @@ -362,18 +284,7 @@ void InitRndLocBigObj(int min, int max, _object_id objtype) while (true) { int xp = GenerateRnd(80) + 16; int yp = GenerateRnd(80) + 16; - if (RndLocOk(xp - 1, yp - 2) - && RndLocOk(xp, yp - 2) - && RndLocOk(xp + 1, yp - 2) - && RndLocOk(xp - 1, yp - 1) - && RndLocOk(xp, yp - 1) - && RndLocOk(xp + 1, yp - 1) - && RndLocOk(xp - 1, yp) - && RndLocOk(xp, yp) - && RndLocOk(xp + 1, yp) - && RndLocOk(xp - 1, yp + 1) - && RndLocOk(xp, yp + 1) - && RndLocOk(xp + 1, yp + 1)) { + if (IsAreaOk(Rectangle { { xp - 1, yp - 2 }, { 3, 4 } })) { AddObject(objtype, { xp, yp }); break; } @@ -383,14 +294,8 @@ void InitRndLocBigObj(int min, int max, _object_id objtype) bool CanPlaceRandomObject(Point position, Displacement standoff) { - for (int yy = -standoff.deltaY; yy <= standoff.deltaY; yy++) { - for (int xx = -standoff.deltaX; xx <= standoff.deltaX; xx++) { - Point tile = position + Displacement { xx, yy }; - if (!RndLocOk(tile.x, tile.y)) - return false; - } - } - return true; + return IsAreaOk(Rectangle { position - standoff, + Size { standoff.deltaX * 2 + 1, standoff.deltaY * 2 + 1 } }); } std::optional GetRandomObjectPosition(Displacement standoff) @@ -506,7 +411,7 @@ void InitRndBarrels() do { xp = GenerateRnd(80) + 16; yp = GenerateRnd(80) + 16; - } while (!RndLocOk(xp, yp)); + } while (!RndLocOk({ xp, yp })); _object_id o = FlipCoin(4) ? explosiveBarrelId : barrelId; AddObject(o, { xp, yp }); bool found = true; @@ -524,7 +429,7 @@ void InitRndBarrels() int dir = GenerateRnd(8); xp += bxadd[dir]; yp += byadd[dir]; - found = RndLocOk(xp, yp); + found = RndLocOk({ xp, yp }); t++; if (found) break; @@ -654,20 +559,18 @@ void LoadMapObjects(const char *path, Point start, WorldTileRectangle mapRange = auto dunData = LoadFileInMem(path); - int width = SDL_SwapLE16(dunData[0]); - int height = SDL_SwapLE16(dunData[1]); + WorldTileSize size = GetDunSize(dunData.get()); - int layer2Offset = 2 + width * height; + int layer2Offset = 2 + size.width * size.height; // The rest of the layers are at dPiece scale - width *= 2; - height *= 2; + size *= static_cast(2); - const uint16_t *objectLayer = &dunData[layer2Offset + width * height * 2]; + const uint16_t *objectLayer = &dunData[layer2Offset + size.width * size.height * 2]; - for (int j = 0; j < height; j++) { - for (int i = 0; i < width; i++) { - auto objectId = static_cast(SDL_SwapLE16(objectLayer[j * width + i])); + for (WorldTileCoord j = 0; j < size.height; j++) { + for (WorldTileCoord i = 0; i < size.width; i++) { + auto objectId = static_cast(SDL_SwapLE16(objectLayer[j * size.width + i])); if (objectId != 0) { Point mapPos = start + Displacement { i, j }; Object *mapObject = AddObject(ObjTypeConv[objectId], mapPos); @@ -773,13 +676,13 @@ void SetupObject(Object &object, Point position, _object_id ot) object.position = position; if (!HeadlessMode) { - const auto &found = std::find(std::begin(ObjFileList), std::end(ObjFileList), ofi); + const auto &found = c_find(ObjFileList, ofi); if (found == std::end(ObjFileList)) { LogCritical("Unable to find object_graphic_id {} in list of objects to load, level generation error.", static_cast(ofi)); return; } - const int j = std::distance(std::begin(ObjFileList), found); + const size_t j = std::distance(std::begin(ObjFileList), found); if (pObjCels[j]) { object._oAnimData.emplace(*pObjCels[j]); @@ -845,15 +748,7 @@ void AddNakrulLever() while (true) { int xp = GenerateRnd(80) + 16; int yp = GenerateRnd(80) + 16; - if (RndLocOk(xp - 1, yp - 1) - && RndLocOk(xp, yp - 1) - && RndLocOk(xp + 1, yp - 1) - && RndLocOk(xp - 1, yp) - && RndLocOk(xp, yp) - && RndLocOk(xp + 1, yp) - && RndLocOk(xp - 1, yp + 1) - && RndLocOk(xp, yp + 1) - && RndLocOk(xp + 1, yp + 1)) { + if (IsAreaOk(Rectangle { { xp - 1, yp - 1 }, { 3, 3 } })) { break; } } @@ -969,23 +864,18 @@ void AddLazStand() int cnt = 0; int xp; int yp; - bool found = false; - while (!found) { - found = true; + while (true) { xp = GenerateRnd(80) + 16; yp = GenerateRnd(80) + 16; - for (int yy = -3; yy <= 3; yy++) { - for (int xx = -2; xx <= 3; xx++) { - if (!RndLocOk(xp + xx, yp + yy)) - found = false; - } - } - if (!found) { + + if (!IsAreaOk(Rectangle { { xp - 2, yp - 3 }, { 6, 7 } })) { cnt++; if (cnt > 10000) { InitRndLocObj(1, 1, OBJ_LAZSTAND); return; } + } else { + break; } } AddObject(OBJ_LAZSTAND, { xp, yp }); @@ -1298,13 +1188,13 @@ void AddDoor(Object &door) void AddSarcophagus(Object &sarcophagus) { - dObject[sarcophagus.position.x][sarcophagus.position.y - 1] = -(sarcophagus.GetId() + 1); + dObject[sarcophagus.position.x][sarcophagus.position.y - 1] = -(static_cast(sarcophagus.GetId()) + 1); sarcophagus._oVar1 = GenerateRnd(10); sarcophagus._oRndSeed = AdvanceRndSeed(); if (sarcophagus._oVar1 >= 8) { Monster *monster = PreSpawnSkeleton(); if (monster != nullptr) { - sarcophagus._oVar2 = monster->getId(); + sarcophagus._oVar2 = static_cast(monster->getId()); } else { sarcophagus._oVar2 = -1; } @@ -1388,7 +1278,7 @@ void AddBarrel(Object &barrel) if (barrel._oVar2 >= 8) { Monster *skeleton = PreSpawnSkeleton(); if (skeleton != nullptr) { - barrel._oVar4 = skeleton->getId(); + barrel._oVar4 = static_cast(skeleton->getId()); } else { barrel._oVar4 = -1; } @@ -1405,7 +1295,7 @@ void AddShrine(Object &shrine) int shrines = gbIsHellfire ? NumberOfShrineTypes : 26; for (int j = 0; j < shrines; j++) { - slist[j] = currlevel >= shrinemin[j] && currlevel <= shrinemax[j]; + slist[j] = j != ShrineEnchanted || IsAnyOf(leveltype, DTYPE_CATHEDRAL, DTYPE_CATACOMBS); if (gbIsMultiplayer && shrineavail[j] == ShrineTypeSingle) { slist[j] = false; } else if (!gbIsMultiplayer && shrineavail[j] == ShrineTypeMulti) { @@ -1435,9 +1325,10 @@ void AddLargeFountain(Object &fountain) { int ox = fountain.position.x; int oy = fountain.position.y; - dObject[ox][oy - 1] = -(fountain.GetId() + 1); - dObject[ox - 1][oy] = -(fountain.GetId() + 1); - dObject[ox - 1][oy - 1] = -(fountain.GetId() + 1); + uint8_t id = -(static_cast(fountain.GetId()) + 1); + dObject[ox][oy - 1] = id; + dObject[ox - 1][oy] = id; + dObject[ox - 1][oy - 1] = id; fountain._oRndSeed = AdvanceRndSeed(); } @@ -1528,13 +1419,7 @@ Point GetRndObjLoc(int randarea) randarea--; x = GenerateRnd(MAXDUNX); y = GenerateRnd(MAXDUNY); - bool failed = false; - for (int i = 0; i < randarea && !failed; i++) { - for (int j = 0; j < randarea && !failed; j++) { - failed = !RndLocOk(i + x, j + y); - } - } - if (!failed) + if (IsAreaOk(Rectangle { { x, y }, { randarea, randarea } })) break; } return { x, y }; @@ -1624,7 +1509,7 @@ void UpdateCircle(Object &circle) Quests[Q_BETRAYER]._qvar1 = 4; NetSendCmdQuest(true, Quests[Q_BETRAYER]); } - AddMissile(playerOnCircle->position.tile, { 35, 46 }, Direction::South, MissileID::Phasing, TARGET_BOTH, playerOnCircle->getId(), 0, 0); + AddMissile(playerOnCircle->position.tile, { 35, 46 }, Direction::South, MissileID::Phasing, TARGET_BOTH, *playerOnCircle, 0, 0); if (playerOnCircle == MyPlayer) { LastMouseButtonAction = MouseActionType::None; sgbMouseDown = CLICK_NONE; @@ -1729,9 +1614,10 @@ void UpdateFlameTrap(Object &trap) constexpr MissileID TrapMissile = MissileID::FireWallControl; if (dMonster[x][y] > 0) MonsterTrapHit(dMonster[x][y] - 1, mindam / 2, maxdam / 2, 0, TrapMissile, GetMissileData(TrapMissile).damageType(), false); - if (dPlayer[x][y] > 0) { + Player *player = PlayerAtPosition({ x, y }, true); + if (player != nullptr) { bool unused; - PlayerMHit(dPlayer[x][y] - 1, nullptr, 0, mindam, maxdam, TrapMissile, GetMissileData(TrapMissile).damageType(), false, DeathReason::MonsterOrTrap, &unused); + PlayerMHit(*player, nullptr, 0, mindam, maxdam, TrapMissile, GetMissileData(TrapMissile).damageType(), false, DeathReason::MonsterOrTrap, &unused); } if (trap._oAnimFrame == trap._oAnimLen) @@ -1865,16 +1751,16 @@ void OperateDoor(Object &door, bool sendflag) bool openDoor = door._oVar4 == DOOR_CLOSED; if (!openDoor && !IsDoorClear(door)) { - PlaySfxLoc(isCrypt ? IS_CRCLOS : IS_DOORCLOS, door.position); + PlaySfxLoc(isCrypt ? SfxID::CryptDoorClose : SfxID::DoorClose, door.position); door._oVar4 = DOOR_BLOCKED; return; } if (openDoor) { - PlaySfxLoc(isCrypt ? IS_CROPEN : IS_DOOROPEN, door.position); + PlaySfxLoc(isCrypt ? SfxID::CryptDoorOpen : SfxID::DoorOpen, door.position); OpenDoor(door); } else { - PlaySfxLoc(isCrypt ? IS_CRCLOS : IS_DOORCLOS, door.position); + PlaySfxLoc(isCrypt ? SfxID::CryptDoorClose : SfxID::DoorClose, door.position); CloseDoor(door); } @@ -1927,12 +1813,12 @@ void OperateLever(Object &object, bool sendmsg) return; } - PlaySfxLoc(IS_LEVER, object.position); + PlaySfxLoc(SfxID::OperateLever, object.position); UpdateLeverState(object); if (currlevel == 24) { - PlaySfxLoc(IS_CROPEN, { UberRow, UberCol }); + PlaySfxLoc(SfxID::CryptDoorOpen, { UberRow, UberCol }); Quests[Q_NAKRUL]._qactive = QUEST_DONE; NetSendCmdQuest(true, Quests[Q_NAKRUL]); } @@ -1967,7 +1853,7 @@ void OperateBook(Player &player, Object &book, bool sendmsg) circle._oVar6 = 4; ObjectAtPosition({ 35, 36 })._oVar5++; - AddMissile(player.position.tile, target, Direction::South, MissileID::Phasing, TARGET_BOTH, player.getId(), 0, 0); + AddMissile(player.position.tile, target, Direction::South, MissileID::Phasing, TARGET_BOTH, player, 0, 0); } book._oSelFlag = 0; @@ -2000,7 +1886,7 @@ void OperateBook(Player &player, Object &book, bool sendmsg) Quests[Q_SCHAMB]._qactive = QUEST_DONE; NetSendCmdQuest(true, Quests[Q_SCHAMB]); } - PlaySfxLoc(IS_QUESTDN, book.position); + PlaySfxLoc(SfxID::QuestDone, book.position); InitDiabloMsg(EMSG_BONECHAMB); AddMissile( player.position.tile, @@ -2008,7 +1894,7 @@ void OperateBook(Player &player, Object &book, bool sendmsg) player._pdir, MissileID::Guardian, TARGET_MONSTERS, - player.getId(), + player, 0, 0); } @@ -2121,7 +2007,7 @@ void OperateChest(const Player &player, Object &chest, bool sendLootMsg) return; } - PlaySfxLoc(IS_CHEST, chest.position); + PlaySfxLoc(SfxID::ChestOpen, chest.position); chest._oSelFlag = 0; chest._oAnimFrame += 2; SetRndSeed(chest._oRndSeed); @@ -2163,6 +2049,7 @@ void OperateChest(const Player &player, Object &chest, bool sendLootMsg) mtype = MissileID::Arrow; } AddMissile(chest.position, player.position.tile, mdir, mtype, TARGET_PLAYERS, -1, 0, 0); + PlaySfxLoc(SfxID::TriggerTrap, chest.position); chest._oTrapFlag = false; } if (&player == MyPlayer) @@ -2189,7 +2076,7 @@ void OperateMushroomPatch(const Player &player, Object &mushroomPatch) mushroomPatch._oSelFlag = 0; mushroomPatch._oAnimFrame++; - PlaySfxLoc(IS_CHEST, mushroomPatch.position); + PlaySfxLoc(SfxID::ChestOpen, mushroomPatch.position); Point pos = GetSuperItemLoc(mushroomPatch.position); if (&player == MyPlayer) { @@ -2220,7 +2107,7 @@ void OperateInnSignChest(const Player &player, Object &questContainer, bool send questContainer._oSelFlag = 0; questContainer._oAnimFrame += 2; - PlaySfxLoc(IS_CHEST, questContainer.position); + PlaySfxLoc(SfxID::ChestOpen, questContainer.position); if (sendmsg) { Point pos = GetSuperItemLoc(questContainer.position); @@ -2258,7 +2145,7 @@ void OperateSlainHero(const Player &player, Object &corpse, bool sendmsg) void OperateTrapLever(Object &flameLever) { - PlaySfxLoc(IS_LEVER, flameLever.position); + PlaySfxLoc(SfxID::OperateLever, flameLever.position); if (flameLever._oAnimFrame == 1) { flameLever._oAnimFrame = 2; @@ -2290,7 +2177,7 @@ void OperateSarcophagus(Object &sarcophagus, bool sendMsg, bool sendLootMsg) return; } - PlaySfxLoc(IS_SARC, sarcophagus.position); + PlaySfxLoc(SfxID::Sarcophagus, sarcophagus.position); sarcophagus._oSelFlag = 0; sarcophagus._oAnimFlag = true; sarcophagus._oAnimDelay = 3; @@ -2325,19 +2212,19 @@ void OperatePedestal(Player &player, Object &pedestal, bool sendmsg) pedestal._oAnimFrame++; pedestal._oVar6++; if (pedestal._oVar6 == 1) { - PlaySfxLoc(LS_PUDDLE, pedestal.position); + PlaySfxLoc(SfxID::SpellPuddle, pedestal.position); ObjChangeMap(SetPiece.position.x, SetPiece.position.y + 3, SetPiece.position.x + 2, SetPiece.position.y + 7); if (sendmsg) SpawnQuestItem(IDI_BLDSTONE, SetPiece.position.megaToWorld() + Displacement { 3, 10 }, 0, 1, true); } if (pedestal._oVar6 == 2) { - PlaySfxLoc(LS_PUDDLE, pedestal.position); + PlaySfxLoc(SfxID::SpellPuddle, pedestal.position); ObjChangeMap(SetPiece.position.x + 6, SetPiece.position.y + 3, SetPiece.position.x + SetPiece.size.width, SetPiece.position.y + 7); if (sendmsg) SpawnQuestItem(IDI_BLDSTONE, SetPiece.position.megaToWorld() + Displacement { 15, 10 }, 0, 1, true); } if (pedestal._oVar6 == 3) { - PlaySfxLoc(LS_BLODSTAR, pedestal.position); + PlaySfxLoc(SfxID::SpellBloodStar, pedestal.position); ObjChangeMap(pedestal._oVar1, pedestal._oVar2, pedestal._oVar3, pedestal._oVar4); LoadMapObjects("levels\\l2data\\blood2.dun", SetPiece.position.megaToWorld()); if (sendmsg) @@ -2496,7 +2383,7 @@ void OperateShrineMagical(const Player &player) player._pdir, MissileID::ManaShield, TARGET_MONSTERS, - player.getId(), + player, 0, 2 * leveltype); @@ -2653,7 +2540,7 @@ void OperateShrineCryptic(Player &player) player._pdir, MissileID::Nova, TARGET_MONSTERS, - player.getId(), + player, 0, 2 * leveltype); @@ -2745,7 +2632,7 @@ void OperateShrineDivine(Player &player, Point spawnPosition) void OperateShrineHoly(const Player &player) { - AddMissile(player.position.tile, { 0, 0 }, Direction::South, MissileID::Phasing, TARGET_MONSTERS, player.getId(), 0, 2 * leveltype); + AddMissile(player.position.tile, { 0, 0 }, Direction::South, MissileID::Phasing, TARGET_MONSTERS, player, 0, 2 * leveltype); if (&player != MyPlayer) return; @@ -2964,7 +2851,7 @@ void OperateShrineMendicant(Player &player) return; int gold = player._pGold / 2; - AddPlrExperience(player, player._pLevel, gold); + player.addExperience(gold); TakePlrsMoney(gold); RedrawEverything(); @@ -2973,7 +2860,7 @@ void OperateShrineMendicant(Player &player) } /** - * @brief Grants experience to the player based on their current level while also triggering a magic trap + * @brief Grants experience to the player based on the current dungeon level while also triggering a magic trap * @param player The player that will be affected by the shrine * @param spawnPosition The trap results in casting flash from this location targeting the player */ @@ -2982,7 +2869,7 @@ void OperateShrineSparkling(Player &player, Point spawnPosition) if (&player != MyPlayer) return; - AddPlrExperience(player, player._pLevel, 1000 * currlevel); + player.addExperience(1000 * currlevel); AddMissile( spawnPosition, @@ -3015,7 +2902,7 @@ void OperateShrineTown(const Player &player, Point spawnPosition) player._pdir, MissileID::TownPortal, TARGET_MONSTERS, - player.getId(), + player, 0, 0); @@ -3086,15 +2973,12 @@ void OperateShrineMurphys(Player &player) InitDiabloMsg(EMSG_SHRINE_MURPHYS); } -void OperateShrine(Player &player, Object &shrine, _sfx_id sType) +void OperateShrine(Player &player, Object &shrine, SfxID sType) { if (shrine._oSelFlag == 0) return; - if (dropGoldFlag) { - CloseGoldDrop(); - dropGoldValue = 0; - } + CloseGoldDrop(); SetRndSeed(shrine._oRndSeed); shrine._oSelFlag = 0; @@ -3216,7 +3100,7 @@ void OperateBookStand(Object &bookStand, bool sendmsg, bool sendLootMsg) return; } - PlaySfxLoc(IS_ISCROL, bookStand.position); + PlaySfxLoc(SfxID::ItemScroll, bookStand.position); bookStand._oSelFlag = 0; bookStand._oAnimFrame += 2; SetRndSeed(bookStand._oRndSeed); @@ -3234,7 +3118,7 @@ void OperateBookcase(Object &bookcase, bool sendmsg, bool sendLootMsg) return; } - PlaySfxLoc(IS_ISCROL, bookcase.position); + PlaySfxLoc(SfxID::ItemScroll, bookcase.position); bookcase._oSelFlag = 0; bookcase._oAnimFrame -= 2; SetRndSeed(bookcase._oRndSeed); @@ -3295,7 +3179,7 @@ int FindValidShrine() { for (;;) { int rv = GenerateRnd(gbIsHellfire ? NumberOfShrineTypes : 26); - if (currlevel < shrinemin[rv] || currlevel > shrinemax[rv] || rv == ShrineThaumaturgic) + if ((rv == ShrineEnchanted && !IsAnyOf(leveltype, DTYPE_CATHEDRAL, DTYPE_CATACOMBS)) || rv == ShrineThaumaturgic) continue; if (gbIsMultiplayer && shrineavail[rv] == ShrineTypeSingle) continue; @@ -3305,7 +3189,7 @@ int FindValidShrine() } } -void OperateGoatShrine(Player &player, Object &object, _sfx_id sType) +void OperateGoatShrine(Player &player, Object &object, SfxID sType) { SetRndSeed(object._oRndSeed); object._oVar1 = FindValidShrine(); @@ -3314,7 +3198,7 @@ void OperateGoatShrine(Player &player, Object &object, _sfx_id sType) RedrawEverything(); } -void OperateCauldron(Player &player, Object &object, _sfx_id sType) +void OperateCauldron(Player &player, Object &object, SfxID sType) { SetRndSeed(object._oRndSeed); object._oVar1 = FindValidShrine(); @@ -3333,7 +3217,7 @@ bool OperateFountains(Player &player, Object &fountain) return false; if (player._pHitPoints < player._pMaxHP) { - PlaySfxLoc(LS_FOUNTAIN, fountain.position); + PlaySfxLoc(SfxID::OperateFountain, fountain.position); player._pHitPoints += 64; player._pHPBase += 64; if (player._pHitPoints > player._pMaxHP) { @@ -3342,14 +3226,14 @@ bool OperateFountains(Player &player, Object &fountain) } applied = true; } else - PlaySfxLoc(LS_FOUNTAIN, fountain.position); + PlaySfxLoc(SfxID::OperateFountain, fountain.position); break; case OBJ_PURIFYINGFTN: if (&player != MyPlayer) return false; if (player._pMana < player._pMaxMana) { - PlaySfxLoc(LS_FOUNTAIN, fountain.position); + PlaySfxLoc(SfxID::OperateFountain, fountain.position); player._pMana += 64; player._pManaBase += 64; @@ -3360,12 +3244,12 @@ bool OperateFountains(Player &player, Object &fountain) applied = true; } else - PlaySfxLoc(LS_FOUNTAIN, fountain.position); + PlaySfxLoc(SfxID::OperateFountain, fountain.position); break; case OBJ_MURKYFTN: if (fountain._oSelFlag == 0) break; - PlaySfxLoc(LS_FOUNTAIN, fountain.position); + PlaySfxLoc(SfxID::OperateFountain, fountain.position); fountain._oSelFlag = 0; AddMissile( player.position.tile, @@ -3373,7 +3257,7 @@ bool OperateFountains(Player &player, Object &fountain) player._pdir, MissileID::Infravision, TARGET_MONSTERS, - player.getId(), + player, 0, 2 * leveltype); applied = true; @@ -3383,31 +3267,31 @@ bool OperateFountains(Player &player, Object &fountain) case OBJ_TEARFTN: { if (fountain._oSelFlag == 0) break; - PlaySfxLoc(LS_FOUNTAIN, fountain.position); + PlaySfxLoc(SfxID::OperateFountain, fountain.position); fountain._oSelFlag = 0; if (&player != MyPlayer) return false; - unsigned randomValue = (fountain._oRndSeed >> 16) % 12; - unsigned fromStat = randomValue / 3; + const unsigned randomValue = (fountain._oRndSeed >> 16) % 12; + const unsigned fromStat = randomValue / 3; unsigned toStat = randomValue % 3; if (toStat >= fromStat) toStat++; - std::pair alterations[] = { { fromStat, -1 }, { toStat, 1 } }; - for (auto alteration : alterations) { - switch (alteration.first) { + const std::pair alterations[] = { { fromStat, -1 }, { toStat, 1 } }; + for (const auto &[stat, delta] : alterations) { + switch (stat) { case 0: - ModifyPlrStr(player, alteration.second); + ModifyPlrStr(player, delta); break; case 1: - ModifyPlrMag(player, alteration.second); + ModifyPlrMag(player, delta); break; case 2: - ModifyPlrDex(player, alteration.second); + ModifyPlrDex(player, delta); break; case 3: - ModifyPlrVit(player, alteration.second); + ModifyPlrVit(player, delta); break; } } @@ -3480,7 +3364,7 @@ void OperateStoryBook(Object &storyBook) return; } storyBook._oAnimFrame = storyBook._oVar4; - PlaySfxLoc(IS_ISCROL, storyBook.position); + PlaySfxLoc(SfxID::ItemScroll, storyBook.position); auto msg = static_cast<_speech_id>(storyBook._oVar2); if (storyBook._oVar8 != 0 && currlevel == 24) { if (!IsUberLeverActivated && Quests[Q_NAKRUL]._qactive != QUEST_DONE && OperateNakrulBook(storyBook._oVar8)) { @@ -3557,7 +3441,7 @@ void BreakCrux(Object &crux, bool sendmsg) if (!AreAllCruxesOfTypeBroken(crux._oVar8)) return; - PlaySfxLoc(IS_LEVER, crux.position); + PlaySfxLoc(SfxID::OperateLever, crux.position); ObjChangeMap(crux._oVar1, crux._oVar2, crux._oVar3, crux._oVar4); } @@ -3580,20 +3464,21 @@ void BreakBarrel(const Player &player, Object &barrel, bool forcebreak, bool sen if (barrel.isExplosive()) { if (barrel._otype == _object_id::OBJ_URNEX) - PlaySfxLoc(IS_POPPOP3, barrel.position); + PlaySfxLoc(SfxID::UrnExpload, barrel.position); else if (barrel._otype == _object_id::OBJ_PODEX) - PlaySfxLoc(IS_POPPOP8, barrel.position); + PlaySfxLoc(SfxID::PodExpload, barrel.position); else - PlaySfxLoc(IS_BARLFIRE, barrel.position); + PlaySfxLoc(SfxID::BarrelExpload, barrel.position); for (int yp = barrel.position.y - 1; yp <= barrel.position.y + 1; yp++) { for (int xp = barrel.position.x - 1; xp <= barrel.position.x + 1; xp++) { constexpr MissileID TrapMissile = MissileID::Firebolt; if (dMonster[xp][yp] > 0) { MonsterTrapHit(dMonster[xp][yp] - 1, 1, 4, 0, TrapMissile, GetMissileData(TrapMissile).damageType(), false); } - if (dPlayer[xp][yp] > 0) { + Player *adjacentPlayer = PlayerAtPosition({ xp, yp }, true); + if (adjacentPlayer != nullptr) { bool unused; - PlayerMHit(dPlayer[xp][yp] - 1, nullptr, 0, 8, 16, TrapMissile, GetMissileData(TrapMissile).damageType(), false, DeathReason::MonsterOrTrap, &unused); + PlayerMHit(*adjacentPlayer, nullptr, 0, 8, 16, TrapMissile, GetMissileData(TrapMissile).damageType(), false, DeathReason::MonsterOrTrap, &unused); } // don't really need to exclude large objects as explosive barrels are single tile objects, but using considerLargeObjects == false as this matches the old logic. Object *adjacentObject = FindObjectAtPosition({ xp, yp }, false); @@ -3604,11 +3489,11 @@ void BreakBarrel(const Player &player, Object &barrel, bool forcebreak, bool sen } } else { if (barrel._otype == _object_id::OBJ_URN) - PlaySfxLoc(IS_POPPOP2, barrel.position); + PlaySfxLoc(SfxID::UrnBreak, barrel.position); else if (barrel._otype == _object_id::OBJ_POD) - PlaySfxLoc(IS_POPPOP5, barrel.position); + PlaySfxLoc(SfxID::PodPop, barrel.position); else - PlaySfxLoc(IS_BARREL, barrel.position); + PlaySfxLoc(SfxID::BarrelBreak, barrel.position); SetRndSeed(barrel._oRndSeed); if (barrel._oVar2 <= 1) { if (barrel._oVar3 == 0) @@ -3693,7 +3578,7 @@ void ResyncDoors(WorldTilePosition p1, WorldTilePosition p2, bool sendmsg) const WorldTileSize size { static_cast(p2.x - p1.x), static_cast(p2.y - p1.y) }; const WorldTileRectangle area { p1, size }; - for (WorldTilePosition p : PointsInRectangleRange { area }) { + for (const WorldTilePosition p : PointsInRectangle { area }) { Object *obj = FindObjectAtPosition(p); if (obj == nullptr) continue; @@ -3722,7 +3607,7 @@ void UpdateState(Object &object, int frame) unsigned int Object::GetId() const { - return abs(dObject[position.x][position.y]) - 1; + return std::abs(dObject[position.x][position.y]) - 1; } bool Object::IsDisabled() const @@ -3736,7 +3621,7 @@ bool Object::IsDisabled() const if (!IsShrine()) { return false; } - return IsAnyOf(static_cast(_oVar1), shrine_type::ShrineFascinating, shrine_type::ShrineOrnate, shrine_type::ShrineSacred); + return IsAnyOf(static_cast(_oVar1), shrine_type::ShrineFascinating, shrine_type::ShrineOrnate, shrine_type::ShrineSacred, shrine_type::ShrineMurphys); } Object *FindObjectAtPosition(Point position, bool considerLargeObjects) @@ -3748,7 +3633,7 @@ Object *FindObjectAtPosition(Point position, bool considerLargeObjects) auto objectId = dObject[position.x][position.y]; if (objectId > 0 || (considerLargeObjects && objectId != 0)) { - return &Objects[abs(objectId) - 1]; + return &Objects[std::abs(objectId) - 1]; } // nothing at this position, return a nullptr @@ -4065,20 +3950,18 @@ void SetMapObjects(const uint16_t *dunData, int startx, int starty) ClrAllObjects(); - int width = SDL_SwapLE16(dunData[0]); - int height = SDL_SwapLE16(dunData[1]); + WorldTileSize size = GetDunSize(dunData); - int layer2Offset = 2 + width * height; + int layer2Offset = 2 + size.width * size.height; // The rest of the layers are at dPiece scale - width *= 2; - height *= 2; + size *= static_cast(2); - const uint16_t *objectLayer = &dunData[layer2Offset + width * height * 2]; + const uint16_t *objectLayer = &dunData[layer2Offset + size.width * size.height * 2]; - for (int j = 0; j < height; j++) { - for (int i = 0; i < width; i++) { - auto objectId = static_cast(SDL_SwapLE16(objectLayer[j * width + i])); + for (WorldTileCoord j = 0; j < size.height; j++) { + for (WorldTileCoord i = 0; i < size.width; i++) { + auto objectId = static_cast(SDL_SwapLE16(objectLayer[j * size.width + i])); if (objectId != 0) { const ObjectData &objectData = AllObjects[ObjTypeConv[objectId]]; filesWidths[objectData.ofindex] = objectData.animWidth; @@ -4088,9 +3971,9 @@ void SetMapObjects(const uint16_t *dunData, int startx, int starty) LoadLevelObjects(filesWidths); - for (int j = 0; j < height; j++) { - for (int i = 0; i < width; i++) { - auto objectId = static_cast(SDL_SwapLE16(objectLayer[j * width + i])); + for (WorldTileCoord j = 0; j < size.height; j++) { + for (WorldTileCoord i = 0; i < size.width; i++) { + auto objectId = static_cast(SDL_SwapLE16(objectLayer[j * size.width + i])); if (objectId != 0) { AddObject(ObjTypeConv[objectId], { startx + 16 + i, starty + 16 + j }); } @@ -4284,7 +4167,7 @@ void OperateTrap(Object &trap) Direction dir = GetDirection(trap.position, target); AddMissile(trap.position, target, dir, static_cast(trap._oVar3), TARGET_PLAYERS, -1, 0, 0); - PlaySfxLoc(IS_TRAP, triggerPosition); + PlaySfxLoc(SfxID::TriggerTrap, triggerPosition); } void ProcessObjects() @@ -4517,7 +4400,7 @@ void OperateObject(Player &player, Object &object) break; case OBJ_SHRINEL: case OBJ_SHRINER: - OperateShrine(player, object, IS_MAGIC); + OperateShrine(player, object, SfxID::OperateShrine); break; case OBJ_SKELBOOK: case OBJ_BOOKSTAND: @@ -4535,10 +4418,10 @@ void OperateObject(Player &player, Object &object) OperateArmorStand(object, sendmsg, sendmsg); break; case OBJ_GOATSHRINE: - OperateGoatShrine(player, object, LS_GSHRINE); + OperateGoatShrine(player, object, SfxID::OperateGoatShrine); break; case OBJ_CAULDRON: - OperateCauldron(player, object, LS_CALDRON); + OperateCauldron(player, object, SfxID::OperateCaldron); break; case OBJ_BLOODFTN: case OBJ_PURIFYINGFTN: @@ -4721,7 +4604,7 @@ void SyncOpObject(Player &player, int cmd, Object &object) break; case OBJ_SHRINEL: case OBJ_SHRINER: - OperateShrine(player, object, IS_MAGIC); + OperateShrine(player, object, SfxID::OperateShrine); break; case OBJ_SKELBOOK: case OBJ_BOOKSTAND: @@ -4739,14 +4622,14 @@ void SyncOpObject(Player &player, int cmd, Object &object) OperateArmorStand(object, sendmsg, false); break; case OBJ_GOATSHRINE: - OperateGoatShrine(player, object, LS_GSHRINE); + OperateGoatShrine(player, object, SfxID::OperateGoatShrine); break; case OBJ_LAZSTAND: if (!sendmsg) UpdateState(object, object._oAnimFrame + 1); break; case OBJ_CAULDRON: - OperateCauldron(player, object, LS_CALDRON); + OperateCauldron(player, object, SfxID::OperateCaldron); break; case OBJ_MURKYFTN: case OBJ_TEARFTN: @@ -4826,13 +4709,13 @@ void SyncObjectAnim(Object &object) object_graphic_id index = AllObjects[object._otype].ofindex; if (!HeadlessMode) { - const auto &found = std::find(std::begin(ObjFileList), std::end(ObjFileList), index); + const auto &found = c_find(ObjFileList, index); if (found == std::end(ObjFileList)) { LogCritical("Unable to find object_graphic_id {} in list of objects to load, level generation error.", static_cast(index)); return; } - const int i = std::distance(std::begin(ObjFileList), found); + const size_t i = std::distance(std::begin(ObjFileList), found); if (pObjCels[i]) { object._oAnimData.emplace(*pObjCels[i]); @@ -4990,7 +4873,7 @@ StringOrView Object::name() const default: break; } - return string_view(); + return std::string_view(); } void GetObjectStr(const Object &object) diff --git a/Source/objects.h b/Source/objects.h index 241559ce3ac..305534b9a48 100644 --- a/Source/objects.h +++ b/Source/objects.h @@ -5,6 +5,7 @@ */ #pragma once +#include #include #include "engine/clx_sprite.hpp" @@ -252,6 +253,21 @@ struct Object { * @brief Returns the name of the object as shown in the info box */ [[nodiscard]] StringOrView name() const; + + [[nodiscard]] ClxSprite currentSprite() const + { + return (*_oAnimData)[_oAnimFrame - 1]; + } + [[nodiscard]] Displacement getRenderingOffset(const ClxSprite sprite, Point tilePosition) const + { + Displacement offset = Displacement { -CalculateWidth2(sprite.width()), 0 }; + if (position != tilePosition) { + // drawing a large or offset object, calculate the correct position for the center of the sprite + Displacement worldOffset = position - tilePosition; + offset -= worldOffset.worldToScreen(); + } + return offset; + } }; extern DVL_API_FOR_TEST Object Objects[MAXOBJECTS]; @@ -290,7 +306,7 @@ inline bool IsObjectAtPosition(Point position) */ inline Object &ObjectAtPosition(Point position) { - return Objects[abs(dObject[position.x][position.y]) - 1]; + return Objects[std::abs(dObject[position.x][position.y]) - 1]; } /** diff --git a/Source/options.cpp b/Source/options.cpp index 66c5a26bc8d..bc8b74eea7e 100644 --- a/Source/options.cpp +++ b/Source/options.cpp @@ -4,6 +4,7 @@ * Load and save options from the diablo.ini file. */ +#include #include #include @@ -24,12 +25,12 @@ #include "platform/locale.hpp" #include "qol/monhealthbar.h" #include "qol/xpbar.h" +#include "utils/algorithm/container.hpp" #include "utils/display.h" #include "utils/file_util.h" #include "utils/language.h" #include "utils/log.hpp" #include "utils/paths.h" -#include "utils/stdcompat/algorithm.hpp" #include "utils/str_cat.hpp" #include "utils/str_split.hpp" #include "utils/utf8.hpp" @@ -161,7 +162,7 @@ float GetIniFloat(const char *sectionName, const char *keyName, float defaultVal return (float)GetIni().GetDoubleValue(sectionName, keyName, defaultValue); } -bool GetIniValue(string_view sectionName, string_view keyName, char *string, int stringSize, const char *defaultString = "") +bool GetIniValue(std::string_view sectionName, std::string_view keyName, char *string, size_t stringSize, const char *defaultString = "") { std::string sectionNameStr { sectionName }; std::string keyNameStr { keyName }; @@ -211,7 +212,7 @@ void SetIniValue(const char *sectionName, const char *keyName, const char *value ini.SetValue(sectionName, keyName, value, nullptr, true); } -void SetIniValue(string_view sectionName, string_view keyName, string_view value) +void SetIniValue(std::string_view sectionName, std::string_view keyName, std::string_view value) { std::string sectionNameStr { sectionName }; std::string keyNameStr { keyName }; @@ -309,6 +310,7 @@ void OptionShowFPSChanged() void OptionLanguageCodeChanged() { + UnloadFonts(); LanguageInitialize(); LoadLanguageArchive(); } @@ -411,11 +413,11 @@ void SaveOptions() SaveIni(); } -string_view OptionEntryBase::GetName() const +std::string_view OptionEntryBase::GetName() const { return _(name); } -string_view OptionEntryBase::GetDescription() const +std::string_view OptionEntryBase::GetDescription() const { return _(description); } @@ -433,11 +435,11 @@ void OptionEntryBase::NotifyValueChanged() callback(); } -void OptionEntryBoolean::LoadFromIni(string_view category) +void OptionEntryBoolean::LoadFromIni(std::string_view category) { value = GetIniBool(category.data(), key.data(), defaultValue); } -void OptionEntryBoolean::SaveToIni(string_view category) const +void OptionEntryBoolean::SaveToIni(std::string_view category) const { SetIniValue(category.data(), key.data(), value); } @@ -450,7 +452,7 @@ OptionEntryType OptionEntryBoolean::GetType() const { return OptionEntryType::Boolean; } -string_view OptionEntryBoolean::GetValueDescription() const +std::string_view OptionEntryBoolean::GetValueDescription() const { return value ? _("ON") : _("OFF"); } @@ -459,16 +461,16 @@ OptionEntryType OptionEntryListBase::GetType() const { return OptionEntryType::List; } -string_view OptionEntryListBase::GetValueDescription() const +std::string_view OptionEntryListBase::GetValueDescription() const { return GetListDescription(GetActiveListIndex()); } -void OptionEntryEnumBase::LoadFromIni(string_view category) +void OptionEntryEnumBase::LoadFromIni(std::string_view category) { value = GetIniInt(category.data(), key.data(), defaultValue); } -void OptionEntryEnumBase::SaveToIni(string_view category) const +void OptionEntryEnumBase::SaveToIni(std::string_view category) const { SetIniValue(category.data(), key.data(), value); } @@ -477,7 +479,7 @@ void OptionEntryEnumBase::SetValueInternal(int value) this->value = value; this->NotifyValueChanged(); } -void OptionEntryEnumBase::AddEntry(int value, string_view name) +void OptionEntryEnumBase::AddEntry(int value, std::string_view name) { entryValues.push_back(value); entryNames.push_back(name); @@ -486,13 +488,13 @@ size_t OptionEntryEnumBase::GetListSize() const { return entryValues.size(); } -string_view OptionEntryEnumBase::GetListDescription(size_t index) const +std::string_view OptionEntryEnumBase::GetListDescription(size_t index) const { return _(entryNames[index].data()); } size_t OptionEntryEnumBase::GetActiveListIndex() const { - auto iterator = std::find(entryValues.begin(), entryValues.end(), value); + auto iterator = c_find(entryValues, value); if (iterator == entryValues.end()) return 0; return std::distance(entryValues.begin(), iterator); @@ -503,16 +505,15 @@ void OptionEntryEnumBase::SetActiveListIndex(size_t index) this->NotifyValueChanged(); } -void OptionEntryIntBase::LoadFromIni(string_view category) +void OptionEntryIntBase::LoadFromIni(std::string_view category) { value = GetIniInt(category.data(), key.data(), defaultValue); - if (std::find(entryValues.begin(), entryValues.end(), value) == entryValues.end()) { - entryValues.push_back(value); - std::sort(entryValues.begin(), entryValues.end()); + if (c_find(entryValues, value) == entryValues.end()) { + entryValues.insert(c_lower_bound(entryValues, value), value); entryNames.clear(); } } -void OptionEntryIntBase::SaveToIni(string_view category) const +void OptionEntryIntBase::SaveToIni(std::string_view category) const { SetIniValue(category.data(), key.data(), value); } @@ -529,7 +530,7 @@ size_t OptionEntryIntBase::GetListSize() const { return entryValues.size(); } -string_view OptionEntryIntBase::GetListDescription(size_t index) const +std::string_view OptionEntryIntBase::GetListDescription(size_t index) const { if (entryNames.empty()) { for (auto value : entryValues) { @@ -540,7 +541,7 @@ string_view OptionEntryIntBase::GetListDescription(size_t index) const } size_t OptionEntryIntBase::GetActiveListIndex() const { - auto iterator = std::find(entryValues.begin(), entryValues.end(), value); + auto iterator = c_find(entryValues, value); if (iterator == entryValues.end()) return 0; return std::distance(entryValues.begin(), iterator); @@ -551,15 +552,15 @@ void OptionEntryIntBase::SetActiveListIndex(size_t index) this->NotifyValueChanged(); } -string_view OptionCategoryBase::GetKey() const +std::string_view OptionCategoryBase::GetKey() const { return key; } -string_view OptionCategoryBase::GetName() const +std::string_view OptionCategoryBase::GetName() const { return _(name); } -string_view OptionCategoryBase::GetDescription() const +std::string_view OptionCategoryBase::GetDescription() const { return _(description); } @@ -678,11 +679,11 @@ OptionEntryResolution::OptionEntryResolution() : OptionEntryListBase("", OptionEntryFlags::CantChangeInGame | OptionEntryFlags::RecreateUI, N_("Resolution"), N_("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.")) { } -void OptionEntryResolution::LoadFromIni(string_view category) +void OptionEntryResolution::LoadFromIni(std::string_view category) { size = { GetIniInt(category.data(), "Width", DEFAULT_WIDTH), GetIniInt(category.data(), "Height", DEFAULT_HEIGHT) }; } -void OptionEntryResolution::SaveToIni(string_view category) const +void OptionEntryResolution::SaveToIni(std::string_view category) const { SetIniValue(category.data(), "Width", size.width); SetIniValue(category.data(), "Height", size.height); @@ -773,12 +774,11 @@ void OptionEntryResolution::CheckResolutionsAreInitialized() const #endif // Sort by width then by height - std::sort(sizes.begin(), sizes.end(), - [](const Size &x, const Size &y) -> bool { - if (x.width == y.width) - return x.height > y.height; - return x.width > y.width; - }); + c_sort(sizes, [](const Size &x, const Size &y) -> bool { + if (x.width == y.width) + return x.height > y.height; + return x.width > y.width; + }); // Remove duplicate entries sizes.erase(std::unique(sizes.begin(), sizes.end()), sizes.end()); @@ -798,7 +798,7 @@ size_t OptionEntryResolution::GetListSize() const CheckResolutionsAreInitialized(); return resolutions.size(); } -string_view OptionEntryResolution::GetListDescription(size_t index) const +std::string_view OptionEntryResolution::GetListDescription(size_t index) const { CheckResolutionsAreInitialized(); return resolutions[index].second; @@ -806,7 +806,7 @@ string_view OptionEntryResolution::GetListDescription(size_t index) const size_t OptionEntryResolution::GetActiveListIndex() const { CheckResolutionsAreInitialized(); - auto found = std::find_if(resolutions.begin(), resolutions.end(), [this](const auto &x) { return x.first == this->size; }); + auto found = c_find_if(resolutions, [this](const auto &x) { return x.first == this->size; }); if (found == resolutions.end()) return 0; return std::distance(resolutions.begin(), found); @@ -825,7 +825,7 @@ OptionEntryResampler::OptionEntryResampler() N_("Resampler"), N_("Audio resampler")) { } -void OptionEntryResampler::LoadFromIni(string_view category) +void OptionEntryResampler::LoadFromIni(std::string_view category) { char resamplerStr[32]; if (GetIniValue(category, key, resamplerStr, sizeof(resamplerStr))) { @@ -840,7 +840,7 @@ void OptionEntryResampler::LoadFromIni(string_view category) UpdateDependentOptions(); } -void OptionEntryResampler::SaveToIni(string_view category) const +void OptionEntryResampler::SaveToIni(std::string_view category) const { SetIniValue(category, key, ResamplerToString(resampler_)); } @@ -850,7 +850,7 @@ size_t OptionEntryResampler::GetListSize() const return NumResamplers; } -string_view OptionEntryResampler::GetListDescription(size_t index) const +std::string_view OptionEntryResampler::GetListDescription(size_t index) const { return ResamplerToString(static_cast(index)); } @@ -882,14 +882,14 @@ OptionEntryAudioDevice::OptionEntryAudioDevice() : OptionEntryListBase("Device", OptionEntryFlags::CantChangeInGame, N_("Device"), N_("Audio device")) { } -void OptionEntryAudioDevice::LoadFromIni(string_view category) +void OptionEntryAudioDevice::LoadFromIni(std::string_view category) { char deviceStr[100]; GetIniValue(category, key, deviceStr, sizeof(deviceStr), ""); deviceName_ = deviceStr; } -void OptionEntryAudioDevice::SaveToIni(string_view category) const +void OptionEntryAudioDevice::SaveToIni(std::string_view category) const { #if SDL_VERSION_ATLEAST(2, 0, 0) SetIniValue(category, key, deviceName_); @@ -905,17 +905,17 @@ size_t OptionEntryAudioDevice::GetListSize() const #endif } -string_view OptionEntryAudioDevice::GetListDescription(size_t index) const +std::string_view OptionEntryAudioDevice::GetListDescription(size_t index) const { constexpr int MaxWidth = 500; - string_view deviceName = GetDeviceName(index); + std::string_view deviceName = GetDeviceName(index); if (deviceName.empty()) return "System Default"; while (GetLineWidth(deviceName, GameFont24, 1) > MaxWidth) { size_t lastSymbolIndex = FindLastUtf8Symbols(deviceName); - deviceName = string_view(deviceName.data(), lastSymbolIndex); + deviceName = std::string_view(deviceName.data(), lastSymbolIndex); } return deviceName; @@ -924,7 +924,7 @@ string_view OptionEntryAudioDevice::GetListDescription(size_t index) const size_t OptionEntryAudioDevice::GetActiveListIndex() const { for (size_t i = 0; i < GetListSize(); i++) { - string_view deviceName = GetDeviceName(i); + std::string_view deviceName = GetDeviceName(i); if (deviceName == deviceName_) return i; } @@ -937,11 +937,11 @@ void OptionEntryAudioDevice::SetActiveListIndex(size_t index) NotifyValueChanged(); } -string_view OptionEntryAudioDevice::GetDeviceName(size_t index) const +std::string_view OptionEntryAudioDevice::GetDeviceName(size_t index) const { #if SDL_VERSION_ATLEAST(2, 0, 0) if (index != 0) - return SDL_GetAudioDeviceName(index - 1, false); + return SDL_GetAudioDeviceName(static_cast(index) - 1, false); #endif return ""; } @@ -1048,8 +1048,8 @@ GameplayOptions::GameplayOptions() , cowQuest("Cow Quest", OptionEntryFlags::CantChangeInGame | OptionEntryFlags::OnlyHellfire, N_("Cow Quest"), N_("Enable Jersey's quest. Lester the farmer is replaced by the Complete Nut."), false) , friendlyFire("Friendly Fire", OptionEntryFlags::CantChangeInMultiPlayer, N_("Friendly Fire"), N_("Allow arrow/spell damage between players in multiplayer even when the friendly mode is on."), true) , multiplayerFullQuests("MultiplayerFullQuests", OptionEntryFlags::CantChangeInMultiPlayer, N_("Full quests in Multiplayer"), N_("Enables the full/uncut singleplayer version of quests."), false) - , testBard("Test Bard", OptionEntryFlags::CantChangeInGame, N_("Test Bard"), N_("Force the Bard character type to appear in the hero selection menu."), false) - , testBarbarian("Test Barbarian", OptionEntryFlags::CantChangeInGame, N_("Test Barbarian"), N_("Force the Barbarian character type to appear in the hero selection menu."), false) + , testBard("Test Bard", OptionEntryFlags::CantChangeInGame | OptionEntryFlags::OnlyHellfire, N_("Test Bard"), N_("Force the Bard character type to appear in the hero selection menu."), false) + , testBarbarian("Test Barbarian", OptionEntryFlags::CantChangeInGame | OptionEntryFlags::OnlyHellfire, N_("Test Barbarian"), N_("Force the Barbarian character type to appear in the hero selection menu."), false) , experienceBar("Experience Bar", OptionEntryFlags::None, N_("Experience Bar"), N_("Experience Bar is added to the UI at the bottom of the screen."), false) , showItemGraphicsInStores("Show Item Graphics in Stores", OptionEntryFlags::None, N_("Show Item Graphics in Stores"), N_("Show item graphics to the left of item descriptions in store menus."), false) , showHealthValues("Show health values", OptionEntryFlags::None, N_("Show health values"), N_("Displays current / max health value on health globe."), false) @@ -1069,7 +1069,7 @@ GameplayOptions::GameplayOptions() , showMonsterType("Show Monster Type", OptionEntryFlags::None, N_("Show Monster Type"), N_("Hovering over a monster will display the type of monster in the description box in the UI."), false) , showItemLabels("Show Item Labels", OptionEntryFlags::None, N_("Show Item Labels"), N_("Show labels for items on the ground when enabled."), false) , autoRefillBelt("Auto Refill Belt", OptionEntryFlags::None, N_("Auto Refill Belt"), N_("Refill belt from inventory when belt item is consumed."), false) - , disableCripplingShrines("Disable Crippling Shrines", OptionEntryFlags::None, N_("Disable Crippling Shrines"), N_("When enabled Cauldrons, Fascinating Shrines, Goat Shrines, Ornate Shrines and Sacred Shrines are not able to be clicked on and labeled as disabled."), false) + , disableCripplingShrines("Disable Crippling Shrines", OptionEntryFlags::None, N_("Disable Crippling Shrines"), N_("When enabled Cauldrons, Fascinating Shrines, Goat Shrines, Ornate Shrines, Sacred Shrines and Murphy's Shrines are not able to be clicked on and labeled as disabled."), false) , quickCast("Quick Cast", OptionEntryFlags::None, N_("Quick Cast"), N_("Spell hotkeys instantly cast the spell, rather than switching the readied spell."), false) , numHealPotionPickup("Heal Potion Pickup", OptionEntryFlags::None, N_("Heal Potion Pickup"), N_("Number of Healing potions to pick up automatically."), 0, { 0, 1, 2, 4, 8, 16 }) , numFullHealPotionPickup("Full Heal Potion Pickup", OptionEntryFlags::None, N_("Full Heal Potion Pickup"), N_("Number of Full Healing potions to pick up automatically."), 0, { 0, 1, 2, 4, 8, 16 }) @@ -1165,7 +1165,7 @@ OptionEntryLanguageCode::OptionEntryLanguageCode() : OptionEntryListBase("Code", OptionEntryFlags::CantChangeInGame | OptionEntryFlags::RecreateUI, N_("Language"), N_("Define what language to use in game.")) { } -void OptionEntryLanguageCode::LoadFromIni(string_view category) +void OptionEntryLanguageCode::LoadFromIni(std::string_view category) { if (GetIniValue(category, key, szCode, sizeof(szCode))) { if (HasTranslation(szCode)) { @@ -1208,7 +1208,7 @@ void OptionEntryLanguageCode::LoadFromIni(string_view category) LogVerbose("No suitable translation found"); strcpy(szCode, "en"); } -void OptionEntryLanguageCode::SaveToIni(string_view category) const +void OptionEntryLanguageCode::SaveToIni(std::string_view category) const { SetIniValue(category, key, szCode); } @@ -1248,7 +1248,7 @@ void OptionEntryLanguageCode::CheckLanguagesAreInitialized() const } // Ensures that the ini specified language is present in languages list even if unknown (for example if someone starts to translate a new language) - if (std::find_if(languages.begin(), languages.end(), [this](const auto &x) { return x.first == this->szCode; }) == languages.end()) { + if (c_find_if(languages, [this](const auto &x) { return x.first == this->szCode; }) == languages.end()) { languages.emplace_back(szCode, szCode); } } @@ -1258,7 +1258,7 @@ size_t OptionEntryLanguageCode::GetListSize() const CheckLanguagesAreInitialized(); return languages.size(); } -string_view OptionEntryLanguageCode::GetListDescription(size_t index) const +std::string_view OptionEntryLanguageCode::GetListDescription(size_t index) const { CheckLanguagesAreInitialized(); return languages[index].second; @@ -1266,7 +1266,7 @@ string_view OptionEntryLanguageCode::GetListDescription(size_t index) const size_t OptionEntryLanguageCode::GetActiveListIndex() const { CheckLanguagesAreInitialized(); - auto found = std::find_if(languages.begin(), languages.end(), [this](const auto &x) { return x.first == this->szCode; }); + auto found = c_find_if(languages, [this](const auto &x) { return x.first == this->szCode; }); if (found == languages.end()) return 0; return std::distance(languages.begin(), found); @@ -1292,7 +1292,7 @@ std::vector LanguageOptions::GetEntries() KeymapperOptions::KeymapperOptions() : OptionCategoryBase("Keymapping", N_("Keymapping"), N_("Keymapping Settings")) { - // Insert all supported keys: a-z, 0-9 and F1-F12. + // Insert all supported keys: a-z, 0-9 and F1-F24. keyIDToKeyName.reserve(('Z' - 'A' + 1) + ('9' - '0' + 1) + 12); for (char c = 'A'; c <= 'Z'; ++c) { keyIDToKeyName.emplace(c, std::string(1, c)); @@ -1303,22 +1303,60 @@ KeymapperOptions::KeymapperOptions() for (int i = 0; i < 12; ++i) { keyIDToKeyName.emplace(SDLK_F1 + i, StrCat("F", i + 1)); } + for (int i = 0; i < 12; ++i) { + keyIDToKeyName.emplace(SDLK_F13 + i, StrCat("F", i + 13)); + } + + keyIDToKeyName.emplace(SDLK_KP_0, "KEYPADNUM 0"); + for (int i = 0; i < 9; i++) { + keyIDToKeyName.emplace(SDLK_KP_1 + i, StrCat("KEYPADNUM ", i + 1)); + } keyIDToKeyName.emplace(SDLK_LALT, "LALT"); keyIDToKeyName.emplace(SDLK_RALT, "RALT"); + keyIDToKeyName.emplace(SDLK_SPACE, "SPACE"); + keyIDToKeyName.emplace(SDLK_RCTRL, "RCONTROL"); keyIDToKeyName.emplace(SDLK_LCTRL, "LCONTROL"); + keyIDToKeyName.emplace(SDLK_PRINTSCREEN, "PRINT"); keyIDToKeyName.emplace(SDLK_PAUSE, "PAUSE"); keyIDToKeyName.emplace(SDLK_TAB, "TAB"); keyIDToKeyName.emplace(SDL_BUTTON_MIDDLE | KeymapperMouseButtonMask, "MMOUSE"); keyIDToKeyName.emplace(SDL_BUTTON_X1 | KeymapperMouseButtonMask, "X1MOUSE"); keyIDToKeyName.emplace(SDL_BUTTON_X2 | KeymapperMouseButtonMask, "X2MOUSE"); + keyIDToKeyName.emplace(MouseScrollUpButton, "SCROLlUPMOUSE"); + keyIDToKeyName.emplace(MouseScrollDownButton, "SCROLLDOWNMOUSE"); + keyIDToKeyName.emplace(MouseScrollLeftButton, "SCROLlLEFTMOUSE"); + keyIDToKeyName.emplace(MouseScrollRightButton, "SCROLLRIGHTMOUSE"); + + keyIDToKeyName.emplace(SDLK_BACKQUOTE, "`"); + keyIDToKeyName.emplace(SDLK_LEFTBRACKET, "["); + keyIDToKeyName.emplace(SDLK_RIGHTBRACKET, "]"); + keyIDToKeyName.emplace(SDLK_BACKSLASH, "\\"); + keyIDToKeyName.emplace(SDLK_SEMICOLON, ";"); + keyIDToKeyName.emplace(SDLK_QUOTE, "'"); + keyIDToKeyName.emplace(SDLK_COMMA, ","); + keyIDToKeyName.emplace(SDLK_PERIOD, "."); + keyIDToKeyName.emplace(SDLK_SLASH, "/"); + + keyIDToKeyName.emplace(SDLK_BACKSPACE, "BACKSPACE"); + keyIDToKeyName.emplace(SDLK_CAPSLOCK, "CAPSLOCK"); + keyIDToKeyName.emplace(SDLK_SCROLLLOCK, "SCROLLLOCK"); + keyIDToKeyName.emplace(SDLK_INSERT, "INSERT"); + keyIDToKeyName.emplace(SDLK_DELETE, "DELETE"); + keyIDToKeyName.emplace(SDLK_HOME, "HOME"); + keyIDToKeyName.emplace(SDLK_END, "END"); + + keyIDToKeyName.emplace(SDLK_KP_DIVIDE, "KEYPAD /"); + keyIDToKeyName.emplace(SDLK_KP_MULTIPLY, "KEYPAD *"); + keyIDToKeyName.emplace(SDLK_KP_ENTER, "KEYPAD ENTER"); + keyIDToKeyName.emplace(SDLK_KP_PERIOD, "KEYPAD DECIMAL"); keyNameToKeyID.reserve(keyIDToKeyName.size()); - for (const auto &kv : keyIDToKeyName) { - keyNameToKeyID.emplace(kv.second, kv.first); + for (const auto &[key, value] : keyIDToKeyName) { + keyNameToKeyID.emplace(value, key); } } @@ -1331,7 +1369,7 @@ std::vector KeymapperOptions::GetEntries() return entries; } -KeymapperOptions::Action::Action(string_view key, const char *name, const char *description, uint32_t defaultKey, std::function actionPressed, std::function actionReleased, std::function enable, unsigned index) +KeymapperOptions::Action::Action(std::string_view key, const char *name, const char *description, uint32_t defaultKey, std::function actionPressed, std::function actionReleased, std::function enable, unsigned index) : OptionEntryBase(key, OptionEntryFlags::None, name, description) , defaultKey(defaultKey) , actionPressed(std::move(actionPressed)) @@ -1340,12 +1378,12 @@ KeymapperOptions::Action::Action(string_view key, const char *name, const char * , dynamicIndex(index) { if (index != 0) { - dynamicKey = fmt::format(fmt::runtime(fmt::string_view(key.data(), key.size())), index); + dynamicKey = fmt::format(fmt::runtime(std::string_view(key.data(), key.size())), index); this->key = dynamicKey; } } -string_view KeymapperOptions::Action::GetName() const +std::string_view KeymapperOptions::Action::GetName() const { if (dynamicIndex == 0) return _(name); @@ -1353,7 +1391,7 @@ string_view KeymapperOptions::Action::GetName() const return dynamicName; } -void KeymapperOptions::Action::LoadFromIni(string_view category) +void KeymapperOptions::Action::LoadFromIni(std::string_view category) { std::array result; if (!GetIniValue(category.data(), key.data(), result.data(), result.size())) { @@ -1379,21 +1417,22 @@ void KeymapperOptions::Action::LoadFromIni(string_view category) // actions while keeping the same order as they have been added. SetValue(keyIt->second); } -void KeymapperOptions::Action::SaveToIni(string_view category) const +void KeymapperOptions::Action::SaveToIni(std::string_view category) const { if (boundKey == SDLK_UNKNOWN) { // Just add an empty config entry if the action is unbound. SetIniValue(category.data(), key.data(), ""); + return; } auto keyNameIt = sgOptions.Keymapper.keyIDToKeyName.find(boundKey); if (keyNameIt == sgOptions.Keymapper.keyIDToKeyName.end()) { - LogVerbose("Keymapper: no name found for key '{}'", key); + LogVerbose("Keymapper: no name found for key {} bound to {}", boundKey, key); return; } SetIniValue(category.data(), key.data(), keyNameIt->second.c_str()); } -string_view KeymapperOptions::Action::GetValueDescription() const +std::string_view KeymapperOptions::Action::GetValueDescription() const { if (boundKey == SDLK_UNKNOWN) return ""; @@ -1433,7 +1472,7 @@ bool KeymapperOptions::Action::SetValue(int value) return true; } -void KeymapperOptions::AddAction(string_view key, const char *name, const char *description, uint32_t defaultKey, std::function actionPressed, std::function actionReleased, std::function enable, unsigned index) +void KeymapperOptions::AddAction(std::string_view key, const char *name, const char *description, uint32_t defaultKey, std::function actionPressed, std::function actionReleased, std::function enable, unsigned index) { actions.emplace_front(key, name, description, defaultKey, std::move(actionPressed), std::move(actionReleased), std::move(enable), index); } @@ -1492,7 +1531,7 @@ bool KeymapperOptions::IsNumberEntryKey(SDL_Keycode vkey) const return ((vkey >= SDLK_0 && vkey <= SDLK_9) || vkey == SDLK_BACKSPACE); } -string_view KeymapperOptions::KeyNameForAction(string_view actionName) const +std::string_view KeymapperOptions::KeyNameForAction(std::string_view actionName) const { for (const Action &action : actions) { if (action.key == actionName && action.boundKey != SDLK_UNKNOWN) { @@ -1502,7 +1541,7 @@ string_view KeymapperOptions::KeyNameForAction(string_view actionName) const return ""; } -uint32_t KeymapperOptions::KeyForAction(string_view actionName) const +uint32_t KeymapperOptions::KeyForAction(std::string_view actionName) const { for (const Action &action : actions) { if (action.key == actionName && action.boundKey != SDLK_UNKNOWN) { @@ -1550,7 +1589,7 @@ std::vector PadmapperOptions::GetEntries() return entries; } -PadmapperOptions::Action::Action(string_view key, const char *name, const char *description, ControllerButtonCombo defaultInput, std::function actionPressed, std::function actionReleased, std::function enable, unsigned index) +PadmapperOptions::Action::Action(std::string_view key, const char *name, const char *description, ControllerButtonCombo defaultInput, std::function actionPressed, std::function actionReleased, std::function enable, unsigned index) : OptionEntryBase(key, OptionEntryFlags::None, name, description) , defaultInput(defaultInput) , actionPressed(std::move(actionPressed)) @@ -1559,12 +1598,12 @@ PadmapperOptions::Action::Action(string_view key, const char *name, const char * , dynamicIndex(index) { if (index != 0) { - dynamicKey = fmt::format(fmt::runtime(fmt::string_view(key.data(), key.size())), index); + dynamicKey = fmt::format(fmt::runtime(std::string_view(key.data(), key.size())), index); this->key = dynamicKey; } } -string_view PadmapperOptions::Action::GetName() const +std::string_view PadmapperOptions::Action::GetName() const { if (dynamicIndex == 0) return _(name); @@ -1572,7 +1611,7 @@ string_view PadmapperOptions::Action::GetName() const return dynamicName; } -void PadmapperOptions::Action::LoadFromIni(string_view category) +void PadmapperOptions::Action::LoadFromIni(std::string_view category) { std::array result; if (!GetIniValue(category.data(), key.data(), result.data(), result.size())) { @@ -1619,7 +1658,7 @@ void PadmapperOptions::Action::LoadFromIni(string_view category) // the actions while keeping the same order as they have been added. SetValue(input); } -void PadmapperOptions::Action::SaveToIni(string_view category) const +void PadmapperOptions::Action::SaveToIni(std::string_view category) const { if (boundInput.button == ControllerButton_NONE) { // Just add an empty config entry if the action is unbound. @@ -1628,13 +1667,13 @@ void PadmapperOptions::Action::SaveToIni(string_view category) const } std::string inputName = sgOptions.Padmapper.buttonToButtonName[static_cast(boundInput.button)]; if (inputName.empty()) { - LogVerbose("Padmapper: no name found for key '{}'", key); + LogVerbose("Padmapper: no name found for button {} bound to {}", static_cast(boundInput.button), key); return; } if (boundInput.modifier != ControllerButton_NONE) { const std::string &modifierName = sgOptions.Padmapper.buttonToButtonName[static_cast(boundInput.modifier)]; if (modifierName.empty()) { - LogVerbose("Padmapper: no name found for key '{}'", key); + LogVerbose("Padmapper: no name found for modifier button {} bound to {}", static_cast(boundInput.button), key); return; } inputName = StrCat(modifierName, "+", inputName); @@ -1650,18 +1689,18 @@ void PadmapperOptions::Action::UpdateValueDescription() const boundInputShortDescription = ""; return; } - string_view buttonName = ToString(boundInput.button); + std::string_view buttonName = ToString(boundInput.button); if (boundInput.modifier == ControllerButton_NONE) { boundInputDescription = std::string(buttonName); boundInputShortDescription = std::string(Shorten(buttonName)); return; } - string_view modifierName = ToString(boundInput.modifier); + std::string_view modifierName = ToString(boundInput.modifier); boundInputDescription = StrCat(modifierName, "+", buttonName); boundInputShortDescription = StrCat(Shorten(modifierName), "+", Shorten(buttonName)); } -string_view PadmapperOptions::Action::Shorten(string_view buttonName) const +std::string_view PadmapperOptions::Action::Shorten(std::string_view buttonName) const { size_t index = 0; size_t chars = 0; @@ -1672,15 +1711,15 @@ string_view PadmapperOptions::Action::Shorten(string_view buttonName) const break; index++; } - return string_view(buttonName.data(), index); + return std::string_view(buttonName.data(), index); } -string_view PadmapperOptions::Action::GetValueDescription() const +std::string_view PadmapperOptions::Action::GetValueDescription() const { return GetValueDescription(false); } -string_view PadmapperOptions::Action::GetValueDescription(bool useShortName) const +std::string_view PadmapperOptions::Action::GetValueDescription(bool useShortName) const { if (GamepadType != boundInputDescriptionType) UpdateValueDescription(); @@ -1697,7 +1736,7 @@ bool PadmapperOptions::Action::SetValue(ControllerButtonCombo value) return true; } -void PadmapperOptions::AddAction(string_view key, const char *name, const char *description, ControllerButtonCombo defaultInput, std::function actionPressed, std::function actionReleased, std::function enable, unsigned index) +void PadmapperOptions::AddAction(std::string_view key, const char *name, const char *description, ControllerButtonCombo defaultInput, std::function actionPressed, std::function actionReleased, std::function enable, unsigned index) { if (committed) return; @@ -1749,7 +1788,7 @@ void PadmapperOptions::ReleaseAllActiveButtons() } } -bool PadmapperOptions::IsActive(string_view actionName) const +bool PadmapperOptions::IsActive(std::string_view actionName) const { for (const Action &action : actions) { if (action.key != actionName) @@ -1760,7 +1799,7 @@ bool PadmapperOptions::IsActive(string_view actionName) const return false; } -string_view PadmapperOptions::ActionNameTriggeredByButtonEvent(ControllerButtonEvent ctrlEvent) const +std::string_view PadmapperOptions::ActionNameTriggeredByButtonEvent(ControllerButtonEvent ctrlEvent) const { if (!gbRunGame) return ""; @@ -1775,7 +1814,7 @@ string_view PadmapperOptions::ActionNameTriggeredByButtonEvent(ControllerButtonE return releaseAction->key; } -string_view PadmapperOptions::InputNameForAction(string_view actionName, bool useShortName) const +std::string_view PadmapperOptions::InputNameForAction(std::string_view actionName, bool useShortName) const { for (const Action &action : actions) { if (action.key == actionName && action.boundInput.button != ControllerButton_NONE) { @@ -1785,7 +1824,7 @@ string_view PadmapperOptions::InputNameForAction(string_view actionName, bool us return ""; } -ControllerButtonCombo PadmapperOptions::ButtonComboForAction(string_view actionName) const +ControllerButtonCombo PadmapperOptions::ButtonComboForAction(std::string_view actionName) const { for (const auto &action : actions) { if (action.key == actionName && action.boundInput.button != ControllerButton_NONE) { @@ -1832,10 +1871,10 @@ bool PadmapperOptions::CanDeferToMovementHandler(const Action &action) const return false; if (spselflag) { - const string_view prefix { "QuickSpell" }; - const string_view key { action.key }; + const std::string_view prefix { "QuickSpell" }; + const std::string_view key { action.key }; if (key.size() >= prefix.size()) { - const string_view truncated { key.data(), prefix.size() }; + const std::string_view truncated { key.data(), prefix.size() }; if (truncated == prefix) return false; } @@ -1849,11 +1888,15 @@ bool PadmapperOptions::CanDeferToMovementHandler(const Action &action) const } namespace { +#ifdef DEVILUTIONX_RESAMPLER_SPEEX constexpr char ResamplerSpeex[] = "Speex"; +#endif +#ifdef DVL_AULIB_SUPPORTS_SDL_RESAMPLER constexpr char ResamplerSDL[] = "SDL"; +#endif } // namespace -string_view ResamplerToString(Resampler resampler) +std::string_view ResamplerToString(Resampler resampler) { switch (resampler) { #ifdef DEVILUTIONX_RESAMPLER_SPEEX @@ -1869,7 +1912,7 @@ string_view ResamplerToString(Resampler resampler) } } -std::optional ResamplerFromString(string_view resampler) +std::optional ResamplerFromString(std::string_view resampler) { #ifdef DEVILUTIONX_RESAMPLER_SPEEX if (resampler == ResamplerSpeex) diff --git a/Source/options.h b/Source/options.h index af297fb13c2..6f265838382 100644 --- a/Source/options.h +++ b/Source/options.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include @@ -14,8 +16,6 @@ #include "engine/sound_defs.hpp" #include "pack.h" #include "utils/enum_traits.h" -#include "utils/stdcompat/optional.hpp" -#include "utils/stdcompat/string_view.hpp" namespace devilution { @@ -57,8 +57,8 @@ enum class Resampler : uint8_t { #endif }; -string_view ResamplerToString(Resampler resampler); -std::optional ResamplerFromString(string_view resampler); +std::string_view ResamplerToString(Resampler resampler); +std::optional ResamplerFromString(std::string_view resampler); enum class FloatingNumbers : uint8_t { /** @brief Show no floating numbers. */ @@ -100,28 +100,28 @@ use_enum_as_flags(OptionEntryFlags); class OptionEntryBase { public: - OptionEntryBase(string_view key, OptionEntryFlags flags, const char *name, const char *description) + OptionEntryBase(std::string_view key, OptionEntryFlags flags, const char *name, const char *description) : flags(flags) , key(key) , name(name) , description(description) { } - [[nodiscard]] virtual string_view GetName() const; - [[nodiscard]] string_view GetDescription() const; + [[nodiscard]] virtual std::string_view GetName() const; + [[nodiscard]] std::string_view GetDescription() const; [[nodiscard]] virtual OptionEntryType GetType() const = 0; [[nodiscard]] OptionEntryFlags GetFlags() const; void SetValueChangedCallback(std::function callback); - [[nodiscard]] virtual string_view GetValueDescription() const = 0; - virtual void LoadFromIni(string_view category) = 0; - virtual void SaveToIni(string_view category) const = 0; + [[nodiscard]] virtual std::string_view GetValueDescription() const = 0; + virtual void LoadFromIni(std::string_view category) = 0; + virtual void SaveToIni(std::string_view category) const = 0; OptionEntryFlags flags; protected: - string_view key; + std::string_view key; const char *name; const char *description; void NotifyValueChanged(); @@ -132,7 +132,7 @@ class OptionEntryBase { class OptionEntryBoolean : public OptionEntryBase { public: - OptionEntryBoolean(string_view key, OptionEntryFlags flags, const char *name, const char *description, bool defaultValue) + OptionEntryBoolean(std::string_view key, OptionEntryFlags flags, const char *name, const char *description, bool defaultValue) : OptionEntryBase(key, flags, name, description) , defaultValue(defaultValue) , value(defaultValue) @@ -145,9 +145,9 @@ class OptionEntryBoolean : public OptionEntryBase { void SetValue(bool value); [[nodiscard]] OptionEntryType GetType() const override; - [[nodiscard]] string_view GetValueDescription() const override; - void LoadFromIni(string_view category) override; - void SaveToIni(string_view category) const override; + [[nodiscard]] std::string_view GetValueDescription() const override; + void LoadFromIni(std::string_view category) override; + void SaveToIni(std::string_view category) const override; private: bool defaultValue; @@ -157,15 +157,15 @@ class OptionEntryBoolean : public OptionEntryBase { class OptionEntryListBase : public OptionEntryBase { public: [[nodiscard]] virtual size_t GetListSize() const = 0; - [[nodiscard]] virtual string_view GetListDescription(size_t index) const = 0; + [[nodiscard]] virtual std::string_view GetListDescription(size_t index) const = 0; [[nodiscard]] virtual size_t GetActiveListIndex() const = 0; virtual void SetActiveListIndex(size_t index) = 0; [[nodiscard]] OptionEntryType GetType() const override; - [[nodiscard]] string_view GetValueDescription() const override; + [[nodiscard]] std::string_view GetValueDescription() const override; protected: - OptionEntryListBase(string_view key, OptionEntryFlags flags, const char *name, const char *description) + OptionEntryListBase(std::string_view key, OptionEntryFlags flags, const char *name, const char *description) : OptionEntryBase(key, flags, name, description) { } @@ -173,16 +173,16 @@ class OptionEntryListBase : public OptionEntryBase { class OptionEntryEnumBase : public OptionEntryListBase { public: - void LoadFromIni(string_view category) override; - void SaveToIni(string_view category) const override; + void LoadFromIni(std::string_view category) override; + void SaveToIni(std::string_view category) const override; [[nodiscard]] size_t GetListSize() const override; - [[nodiscard]] string_view GetListDescription(size_t index) const override; + [[nodiscard]] std::string_view GetListDescription(size_t index) const override; [[nodiscard]] size_t GetActiveListIndex() const override; void SetActiveListIndex(size_t index) override; protected: - OptionEntryEnumBase(string_view key, OptionEntryFlags flags, const char *name, const char *description, int defaultValue) + OptionEntryEnumBase(std::string_view key, OptionEntryFlags flags, const char *name, const char *description, int defaultValue) : OptionEntryListBase(key, flags, name, description) , defaultValue(defaultValue) , value(defaultValue) @@ -195,23 +195,23 @@ class OptionEntryEnumBase : public OptionEntryListBase { } void SetValueInternal(int value); - void AddEntry(int value, string_view name); + void AddEntry(int value, std::string_view name); private: int defaultValue; int value; - std::vector entryNames; + std::vector entryNames; std::vector entryValues; }; template class OptionEntryEnum : public OptionEntryEnumBase { public: - OptionEntryEnum(string_view key, OptionEntryFlags flags, const char *name, const char *description, T defaultValue, std::initializer_list> entries) + OptionEntryEnum(std::string_view key, OptionEntryFlags flags, const char *name, const char *description, T defaultValue, std::initializer_list> entries) : OptionEntryEnumBase(key, flags, name, description, static_cast(defaultValue)) { - for (auto entry : entries) { - AddEntry(static_cast(entry.first), entry.second); + for (auto &&[key, value] : entries) { + AddEntry(static_cast(key), value); } } [[nodiscard]] T operator*() const @@ -226,16 +226,16 @@ class OptionEntryEnum : public OptionEntryEnumBase { class OptionEntryIntBase : public OptionEntryListBase { public: - void LoadFromIni(string_view category) override; - void SaveToIni(string_view category) const override; + void LoadFromIni(std::string_view category) override; + void SaveToIni(std::string_view category) const override; [[nodiscard]] size_t GetListSize() const override; - [[nodiscard]] string_view GetListDescription(size_t index) const override; + [[nodiscard]] std::string_view GetListDescription(size_t index) const override; [[nodiscard]] size_t GetActiveListIndex() const override; void SetActiveListIndex(size_t index) override; protected: - OptionEntryIntBase(string_view key, OptionEntryFlags flags, const char *name, const char *description, int defaultValue) + OptionEntryIntBase(std::string_view key, OptionEntryFlags flags, const char *name, const char *description, int defaultValue) : OptionEntryListBase(key, flags, name, description) , defaultValue(defaultValue) , value(defaultValue) @@ -260,14 +260,14 @@ class OptionEntryIntBase : public OptionEntryListBase { template class OptionEntryInt : public OptionEntryIntBase { public: - OptionEntryInt(string_view key, OptionEntryFlags flags, const char *name, const char *description, T defaultValue, std::initializer_list entries) + OptionEntryInt(std::string_view key, OptionEntryFlags flags, const char *name, const char *description, T defaultValue, std::initializer_list entries) : OptionEntryIntBase(key, flags, name, description, static_cast(defaultValue)) { for (auto entry : entries) { AddEntry(static_cast(entry)); } } - OptionEntryInt(string_view key, OptionEntryFlags flags, const char *name, const char *description, T defaultValue) + OptionEntryInt(std::string_view key, OptionEntryFlags flags, const char *name, const char *description, T defaultValue) : OptionEntryInt(key, flags, name, description, defaultValue, { defaultValue }) { } @@ -285,20 +285,20 @@ class OptionEntryLanguageCode : public OptionEntryListBase { public: OptionEntryLanguageCode(); - void LoadFromIni(string_view category) override; - void SaveToIni(string_view category) const override; + void LoadFromIni(std::string_view category) override; + void SaveToIni(std::string_view category) const override; [[nodiscard]] size_t GetListSize() const override; - [[nodiscard]] string_view GetListDescription(size_t index) const override; + [[nodiscard]] std::string_view GetListDescription(size_t index) const override; [[nodiscard]] size_t GetActiveListIndex() const override; void SetActiveListIndex(size_t index) override; - string_view operator*() const + std::string_view operator*() const { return szCode; } - OptionEntryLanguageCode &operator=(string_view code) + OptionEntryLanguageCode &operator=(std::string_view code) { assert(code.size() < 6); memcpy(szCode, code.data(), code.size()); @@ -318,11 +318,11 @@ class OptionEntryResolution : public OptionEntryListBase { public: OptionEntryResolution(); - void LoadFromIni(string_view category) override; - void SaveToIni(string_view category) const override; + void LoadFromIni(std::string_view category) override; + void SaveToIni(std::string_view category) const override; [[nodiscard]] size_t GetListSize() const override; - [[nodiscard]] string_view GetListDescription(size_t index) const override; + [[nodiscard]] std::string_view GetListDescription(size_t index) const override; [[nodiscard]] size_t GetActiveListIndex() const override; void SetActiveListIndex(size_t index) override; void InvalidateList(); @@ -344,11 +344,11 @@ class OptionEntryResampler : public OptionEntryListBase { public: OptionEntryResampler(); - void LoadFromIni(string_view category) override; - void SaveToIni(string_view category) const override; + void LoadFromIni(std::string_view category) override; + void SaveToIni(std::string_view category) const override; [[nodiscard]] size_t GetListSize() const override; - [[nodiscard]] string_view GetListDescription(size_t index) const override; + [[nodiscard]] std::string_view GetListDescription(size_t index) const override; [[nodiscard]] size_t GetActiveListIndex() const override; void SetActiveListIndex(size_t index) override; @@ -367,18 +367,18 @@ class OptionEntryAudioDevice : public OptionEntryListBase { public: OptionEntryAudioDevice(); - void LoadFromIni(string_view category) override; - void SaveToIni(string_view category) const override; + void LoadFromIni(std::string_view category) override; + void SaveToIni(std::string_view category) const override; [[nodiscard]] size_t GetListSize() const override; - [[nodiscard]] string_view GetListDescription(size_t index) const override; + [[nodiscard]] std::string_view GetListDescription(size_t index) const override; [[nodiscard]] size_t GetActiveListIndex() const override; void SetActiveListIndex(size_t index) override; std::string operator*() const { for (size_t i = 0; i < GetListSize(); i++) { - string_view deviceName = GetDeviceName(i); + std::string_view deviceName = GetDeviceName(i); if (deviceName == deviceName_) return deviceName_; } @@ -386,27 +386,27 @@ class OptionEntryAudioDevice : public OptionEntryListBase { } private: - string_view GetDeviceName(size_t index) const; + std::string_view GetDeviceName(size_t index) const; std::string deviceName_; }; struct OptionCategoryBase { - OptionCategoryBase(string_view key, const char *name, const char *description) + OptionCategoryBase(std::string_view key, const char *name, const char *description) : key(key) , name(name) , description(description) { } - [[nodiscard]] string_view GetKey() const; - [[nodiscard]] string_view GetName() const; - [[nodiscard]] string_view GetDescription() const; + [[nodiscard]] std::string_view GetKey() const; + [[nodiscard]] std::string_view GetName() const; + [[nodiscard]] std::string_view GetDescription() const; virtual std::vector GetEntries() = 0; protected: - string_view key; + std::string_view key; const char *name; const char *description; }; @@ -642,6 +642,10 @@ struct LanguageOptions : OptionCategoryBase { }; constexpr uint32_t KeymapperMouseButtonMask = 1 << 31; +constexpr uint32_t MouseScrollUpButton = 65536 | KeymapperMouseButtonMask; +constexpr uint32_t MouseScrollDownButton = 65537 | KeymapperMouseButtonMask; +constexpr uint32_t MouseScrollLeftButton = 65538 | KeymapperMouseButtonMask; +constexpr uint32_t MouseScrollRightButton = 65539 | KeymapperMouseButtonMask; /** The Keymapper maps keys to actions. */ struct KeymapperOptions : OptionCategoryBase { @@ -655,18 +659,18 @@ struct KeymapperOptions : OptionCategoryBase { // The implicit copy constructor would copy that reference instead of referencing the copy. Action(const Action &) = delete; - Action(string_view key, const char *name, const char *description, uint32_t defaultKey, std::function actionPressed, std::function actionReleased, std::function enable, unsigned index); + Action(std::string_view key, const char *name, const char *description, uint32_t defaultKey, std::function actionPressed, std::function actionReleased, std::function enable, unsigned index); - [[nodiscard]] string_view GetName() const override; + [[nodiscard]] std::string_view GetName() const override; [[nodiscard]] OptionEntryType GetType() const override { return OptionEntryType::Key; } - void LoadFromIni(string_view category) override; - void SaveToIni(string_view category) const override; + void LoadFromIni(std::string_view category) override; + void SaveToIni(std::string_view category) const override; - [[nodiscard]] string_view GetValueDescription() const override; + [[nodiscard]] std::string_view GetValueDescription() const override; bool SetValue(int value); @@ -687,7 +691,7 @@ struct KeymapperOptions : OptionCategoryBase { std::vector GetEntries() override; void AddAction( - string_view key, const char *name, const char *description, uint32_t defaultKey, + std::string_view key, const char *name, const char *description, uint32_t defaultKey, std::function actionPressed, std::function actionReleased = nullptr, std::function enable = nullptr, @@ -697,8 +701,8 @@ struct KeymapperOptions : OptionCategoryBase { void KeyReleased(SDL_Keycode key) const; bool IsTextEntryKey(SDL_Keycode vkey) const; bool IsNumberEntryKey(SDL_Keycode vkey) const; - string_view KeyNameForAction(string_view actionName) const; - uint32_t KeyForAction(string_view actionName) const; + std::string_view KeyNameForAction(std::string_view actionName) const; + uint32_t KeyForAction(std::string_view actionName) const; private: std::forward_list actions; @@ -715,23 +719,23 @@ struct PadmapperOptions : OptionCategoryBase { */ class Action final : public OptionEntryBase { public: - Action(string_view key, const char *name, const char *description, ControllerButtonCombo defaultInput, std::function actionPressed, std::function actionReleased, std::function enable, unsigned index); + Action(std::string_view key, const char *name, const char *description, ControllerButtonCombo defaultInput, std::function actionPressed, std::function actionReleased, std::function enable, unsigned index); // OptionEntryBase::key may be referencing Action::dynamicKey. // The implicit copy constructor would copy that reference instead of referencing the copy. Action(const Action &) = delete; - [[nodiscard]] string_view GetName() const override; + [[nodiscard]] std::string_view GetName() const override; [[nodiscard]] OptionEntryType GetType() const override { return OptionEntryType::PadButton; } - void LoadFromIni(string_view category) override; - void SaveToIni(string_view category) const override; + void LoadFromIni(std::string_view category) override; + void SaveToIni(std::string_view category) const override; - [[nodiscard]] string_view GetValueDescription() const override; - [[nodiscard]] string_view GetValueDescription(bool useShortName) const; + [[nodiscard]] std::string_view GetValueDescription() const override; + [[nodiscard]] std::string_view GetValueDescription(bool useShortName) const; bool SetValue(ControllerButtonCombo value); @@ -749,7 +753,7 @@ struct PadmapperOptions : OptionCategoryBase { mutable std::string dynamicName; void UpdateValueDescription() const; - string_view Shorten(string_view buttonName) const; + std::string_view Shorten(std::string_view buttonName) const; friend struct PadmapperOptions; }; @@ -758,7 +762,7 @@ struct PadmapperOptions : OptionCategoryBase { std::vector GetEntries() override; void AddAction( - string_view key, const char *name, const char *description, ControllerButtonCombo defaultInput, + std::string_view key, const char *name, const char *description, ControllerButtonCombo defaultInput, std::function actionPressed, std::function actionReleased = nullptr, std::function enable = nullptr, @@ -767,10 +771,10 @@ struct PadmapperOptions : OptionCategoryBase { void ButtonPressed(ControllerButton button); void ButtonReleased(ControllerButton button, bool invokeAction = true); void ReleaseAllActiveButtons(); - bool IsActive(string_view actionName) const; - string_view ActionNameTriggeredByButtonEvent(ControllerButtonEvent ctrlEvent) const; - string_view InputNameForAction(string_view actionName, bool useShortName = false) const; - ControllerButtonCombo ButtonComboForAction(string_view actionName) const; + bool IsActive(std::string_view actionName) const; + std::string_view ActionNameTriggeredByButtonEvent(ControllerButtonEvent ctrlEvent) const; + std::string_view InputNameForAction(std::string_view actionName, bool useShortName = false) const; + ControllerButtonCombo ButtonComboForAction(std::string_view actionName) const; private: std::forward_list actions; diff --git a/Source/pack.cpp b/Source/pack.cpp index c0101117119..e9039b8fd9e 100644 --- a/Source/pack.cpp +++ b/Source/pack.cpp @@ -75,22 +75,13 @@ void VerifyGoldSeeds(Player &player) } } -void PackNetItem(const Item &item, ItemNetPack &packedItem) -{ - packedItem.def.wIndx = static_cast<_item_indexes>(SDL_SwapLE16(item.IDidx)); - packedItem.def.wCI = SDL_SwapLE16(item._iCreateInfo); - packedItem.def.dwSeed = SDL_SwapLE32(item._iSeed); - if (item.IDidx != IDI_EAR) - PrepareItemForNetwork(item, packedItem.item); - else - PrepareEarForNetwork(item, packedItem.ear); -} - bool hasMultipleFlags(uint16_t flags) { return (flags & (flags - 1)) > 0; } +} // namespace + bool IsCreationFlagComboValid(uint16_t iCreateInfo) { iCreateInfo = iCreateInfo & ~CF_LEVEL; @@ -98,41 +89,47 @@ bool IsCreationFlagComboValid(uint16_t iCreateInfo) const bool isPregenItem = (iCreateInfo & CF_PREGEN) != 0; const bool isUsefulItem = (iCreateInfo & CF_USEFUL) == CF_USEFUL; - if (isPregenItem) + if (isPregenItem) { + // Pregen flags are discarded when an item is picked up, therefore impossible to have in the inventory return false; + } if (isUsefulItem && (iCreateInfo & ~CF_USEFUL) != 0) return false; - if (isTownItem && hasMultipleFlags(iCreateInfo)) + if (isTownItem && hasMultipleFlags(iCreateInfo)) { + // Items from town can only have 1 towner flag return false; + } return true; } -bool IsTownItemValid(uint16_t iCreateInfo) +bool IsTownItemValid(uint16_t iCreateInfo, const Player &player) { const uint8_t level = iCreateInfo & CF_LEVEL; const bool isBoyItem = (iCreateInfo & CF_BOY) != 0; + const uint8_t maxTownItemLevel = 30; - if (isBoyItem && level <= MaxCharacterLevel) + // Wirt items in multiplayer are equal to the level of the player, therefore they cannot exceed the max character level + if (isBoyItem && level <= player.getMaxCharacterLevel()) return true; - return level <= 30; + return level <= maxTownItemLevel; } bool IsUniqueMonsterItemValid(uint16_t iCreateInfo, uint32_t dwBuff) { const uint8_t level = iCreateInfo & CF_LEVEL; - const bool isHellfireItem = (dwBuff & CF_HELLFIRE) != 0; - for (int i = 0; UniqueMonstersData[i].mName != nullptr; i++) { - const auto &uniqueMonsterData = UniqueMonstersData[i]; + // Check all unique monster levels to see if they match the item level + for (const UniqueMonsterData &uniqueMonsterData : UniqueMonstersData) { const auto &uniqueMonsterLevel = static_cast(MonstersData[uniqueMonsterData.mtype].level); - if (!isHellfireItem && IsAnyOf(uniqueMonsterData.mtype, MT_HORKDMN, MT_DEFILER, MT_NAKRUL)) { - // These monsters don't appear in Diablo + if (IsAnyOf(uniqueMonsterData.mtype, MT_DEFILER, MT_NAKRUL, MT_HORKDMN)) { + // These monsters don't use their mlvl for item generation continue; } if (level == uniqueMonsterLevel) { + // If the ilvl matches the mlvl, we confirm the item is legitimate return true; } } @@ -145,53 +142,64 @@ bool IsDungeonItemValid(uint16_t iCreateInfo, uint32_t dwBuff) const uint8_t level = iCreateInfo & CF_LEVEL; const bool isHellfireItem = (dwBuff & CF_HELLFIRE) != 0; + // Check all monster levels to see if they match the item level for (int16_t i = 0; i < static_cast(NUM_MTYPES); i++) { const auto &monsterData = MonstersData[i]; auto monsterLevel = static_cast(monsterData.level); if (i != MT_DIABLO && monsterData.availability == MonsterAvailability::Never) { + // Skip monsters that are unable to appear in the game continue; } if (i == MT_DIABLO && !isHellfireItem) { + // Adjust The Dark Lord's mlvl if the item isn't a Hellfire item to match the Diablo mlvl monsterLevel -= 15; } if (level == monsterLevel) { + // If the ilvl matches the mlvl, we confirm the item is legitimate return true; } } - return level <= 30; + if (isHellfireItem) { + uint8_t hellfireMaxDungeonLevel = 24; + + // Hellfire adjusts the currlevel minus 7 in dungeon levels 20-24 for generating items + hellfireMaxDungeonLevel -= 7; + return level <= (hellfireMaxDungeonLevel * 2); + } + + uint8_t diabloMaxDungeonLevel = 16; + + // Diablo doesn't have containers that drop items in dungeon level 16, therefore we decrement by 1 + diabloMaxDungeonLevel--; + return level <= (diabloMaxDungeonLevel * 2); } -bool UnPackNetItem(const Player &player, const ItemNetPack &packedItem, Item &item) +bool RecreateHellfireSpellBook(const Player &player, const ItemNetPack &packedItem, Item &item) { - item = {}; - _item_indexes idx = static_cast<_item_indexes>(SDL_SwapLE16(packedItem.def.wIndx)); - if (idx < 0 || idx > IDI_LAST) - return true; - if (idx == IDI_EAR) { - RecreateEar(item, SDL_SwapLE16(packedItem.ear.wCI), SDL_SwapLE32(packedItem.ear.dwSeed), packedItem.ear.bCursval, packedItem.ear.heroname); + Item spellBook {}; + RecreateItem(player, packedItem.item, spellBook); + + // Hellfire uses the spell book level when generating items via CreateSpellBook() + int spellBookLevel = GetSpellBookLevel(spellBook._iSpell); + + // CreateSpellBook() adds 1 to the spell level for ilvl + spellBookLevel++; + + if (spellBookLevel >= 1 && (spellBook._iCreateInfo & CF_LEVEL) == spellBookLevel * 2) { + // The ilvl matches the result for a spell book drop, so we confirm the item is legitimate + item = spellBook; return true; } - uint16_t creationFlags = SDL_SwapLE16(packedItem.item.wCI); - uint32_t dwBuff = SDL_SwapLE16(packedItem.item.dwBuff); - ValidateField(creationFlags, IsCreationFlagComboValid(creationFlags)); - if ((creationFlags & CF_TOWN) != 0) - ValidateField(creationFlags, IsTownItemValid(creationFlags)); - else if ((creationFlags & CF_USEFUL) == CF_UPER15) - ValidateFields(creationFlags, dwBuff, IsUniqueMonsterItemValid(creationFlags, dwBuff)); - else - ValidateFields(creationFlags, dwBuff, IsDungeonItemValid(creationFlags, dwBuff)); - - RecreateItem(player, packedItem.item, item); + ValidateFields(spellBook._iCreateInfo, spellBook.dwBuff, IsDungeonItemValid(spellBook._iCreateInfo, spellBook.dwBuff)); + item = spellBook; return true; } -} // namespace - void PackItem(ItemPack &packedItem, const Item &item, bool isHellfire) { packedItem = {}; @@ -255,7 +263,7 @@ void PackPlayer(PlayerPack &packed, const Player &player) packed.pBaseMag = player._pBaseMag; packed.pBaseDex = player._pBaseDex; packed.pBaseVit = player._pBaseVit; - packed.pLevel = player._pLevel; + packed.pLevel = player.getCharacterLevel(); packed.pStatPts = player._pStatPts; packed.pExperience = SDL_SwapLE32(player._pExperience); packed.pGold = SDL_SwapLE32(player._pGold); @@ -289,6 +297,21 @@ void PackPlayer(PlayerPack &packed, const Player &player) packed.bIsHellfire = gbIsHellfire ? 1 : 0; } +void PackNetItem(const Item &item, ItemNetPack &packedItem) +{ + if (item.isEmpty()) { + packedItem.def.wIndx = static_cast<_item_indexes>(0xFFFF); + return; + } + packedItem.def.wIndx = static_cast<_item_indexes>(SDL_SwapLE16(item.IDidx)); + packedItem.def.wCI = SDL_SwapLE16(item._iCreateInfo); + packedItem.def.dwSeed = SDL_SwapLE32(item._iSeed); + if (item.IDidx != IDI_EAR) + PrepareItemForNetwork(item, packedItem.item); + else + PrepareEarForNetwork(item, packedItem.ear); +} + void PackNetPlayer(PlayerNetPack &packed, const Player &player) { packed.plrlevel = player.plrlevel; @@ -300,7 +323,7 @@ void PackNetPlayer(PlayerNetPack &packed, const Player &player) packed.pBaseMag = player._pBaseMag; packed.pBaseDex = player._pBaseDex; packed.pBaseVit = player._pBaseVit; - packed.pLevel = player._pLevel; + packed.pLevel = player.getCharacterLevel(); packed.pStatPts = player._pStatPts; packed.pExperience = SDL_SwapLE32(player._pExperience); packed.pHPBase = SDL_SwapLE32(player._pHPBase); @@ -340,7 +363,8 @@ void PackNetPlayer(PlayerNetPack &packed, const Player &player) packed.pMana = SDL_SwapLE32(player._pMana); packed.pMaxMana = SDL_SwapLE32(player._pMaxMana); packed.pDamageMod = SDL_SwapLE32(player._pDamageMod); - packed.pBaseToBlk = SDL_SwapLE32(player._pBaseToBlk); + // we pack base to block as a basic check that remote players are using the same playerdat values as we are + packed.pBaseToBlk = SDL_SwapLE32(player.getBaseToBlock()); packed.pIMinDam = SDL_SwapLE32(player._pIMinDam); packed.pIMaxDam = SDL_SwapLE32(player._pIMaxDam); packed.pIAC = SDL_SwapLE32(player._pIAC); @@ -358,6 +382,11 @@ void PackNetPlayer(PlayerNetPack &packed, const Player &player) void UnPackItem(const ItemPack &packedItem, const Player &player, Item &item, bool isHellfire) { + if (packedItem.idx == 0xFFFF) { + item.clear(); + return; + } + auto idx = static_cast<_item_indexes>(SDL_SwapLE16(packedItem.idx)); if (gbIsSpawn) { @@ -404,15 +433,8 @@ void UnPackItem(const ItemPack &packedItem, const Player &player, Item &item, bo item._iIdentified = (packedItem.bId & 1) != 0; item._iMaxDur = packedItem.bMDur; item._iDurability = ClampDurability(item, packedItem.bDur); - item._iMaxCharges = clamp(packedItem.bMCh, 0, item._iMaxCharges); - item._iCharges = clamp(packedItem.bCh, 0, item._iMaxCharges); - - RemoveInvalidItem(item); - - if (isHellfire) - item.dwBuff |= CF_HELLFIRE; - else - item.dwBuff &= ~CF_HELLFIRE; + item._iMaxCharges = std::clamp(packedItem.bMCh, 0, item._iMaxCharges); + item._iCharges = std::clamp(packedItem.bCh, 0, item._iMaxCharges); } } @@ -421,17 +443,17 @@ void UnPackPlayer(const PlayerPack &packed, Player &player) Point position { packed.px, packed.py }; player = {}; - player._pLevel = clamp(packed.pLevel, 1, MaxCharacterLevel); + player.setCharacterLevel(packed.pLevel); player._pMaxHPBase = SDL_SwapLE32(packed.pMaxHPBase); player._pHPBase = SDL_SwapLE32(packed.pHPBase); - player._pHPBase = clamp(player._pHPBase, 0, player._pMaxHPBase); + player._pHPBase = std::clamp(player._pHPBase, 0, player._pMaxHPBase); player._pMaxHP = player._pMaxHPBase; player._pHitPoints = player._pHPBase; player.position.tile = position; player.position.future = position; - player.setLevel(clamp(packed.plrlevel, 0, NUMLEVELS)); + player.setLevel(std::clamp(packed.plrlevel, 0, NUMLEVELS)); - player._pClass = static_cast(clamp(packed.pClass, 0, enum_size::value - 1)); + player._pClass = static_cast(std::clamp(packed.pClass, 0, enum_size::value - 1)); ClrPlrPath(player); player.destAction = ACTION_NONE; @@ -452,7 +474,6 @@ void UnPackPlayer(const PlayerPack &packed, Player &player) player._pExperience = SDL_SwapLE32(packed.pExperience); player._pGold = SDL_SwapLE32(packed.pGold); - player._pBaseToBlk = PlayersData[static_cast(player._pClass)].blockBonus; if ((int)(player._pHPBase & 0xFFFFFFC0) < 64) player._pHPBase = 64; @@ -488,6 +509,34 @@ void UnPackPlayer(const PlayerPack &packed, Player &player) player.pDiabloKillLevel = SDL_SwapLE32(packed.pDiabloKillLevel); } +bool UnPackNetItem(const Player &player, const ItemNetPack &packedItem, Item &item) +{ + item = {}; + _item_indexes idx = static_cast<_item_indexes>(SDL_SwapLE16(packedItem.def.wIndx)); + if (idx < 0 || idx > IDI_LAST) + return true; + if (idx == IDI_EAR) { + RecreateEar(item, SDL_SwapLE16(packedItem.ear.wCI), SDL_SwapLE32(packedItem.ear.dwSeed), packedItem.ear.bCursval, packedItem.ear.heroname); + return true; + } + + uint16_t creationFlags = SDL_SwapLE16(packedItem.item.wCI); + uint32_t dwBuff = SDL_SwapLE16(packedItem.item.dwBuff); + if (idx != IDI_GOLD) + ValidateField(creationFlags, IsCreationFlagComboValid(creationFlags)); + if ((creationFlags & CF_TOWN) != 0) + ValidateField(creationFlags, IsTownItemValid(creationFlags, player)); + else if ((creationFlags & CF_USEFUL) == CF_UPER15) + ValidateFields(creationFlags, dwBuff, IsUniqueMonsterItemValid(creationFlags, dwBuff)); + else if ((dwBuff & CF_HELLFIRE) != 0 && AllItemsList[idx].iMiscId == IMISC_BOOK) + return RecreateHellfireSpellBook(player, packedItem, item); + else + ValidateFields(creationFlags, dwBuff, IsDungeonItemValid(creationFlags, dwBuff)); + + RecreateItem(player, packedItem.item, item); + return true; +} + bool UnPackNetPlayer(const PlayerNetPack &packed, Player &player) { CopyUtf8(player._pName, packed.pName, sizeof(player._pName)); @@ -498,7 +547,7 @@ bool UnPackNetPlayer(const PlayerNetPack &packed, Player &player) Point position { packed.px, packed.py }; ValidateFields(position.x, position.y, InDungeonBounds(position)); ValidateField(packed.plrlevel, packed.plrlevel < NUMLEVELS); - ValidateField(packed.pLevel, packed.pLevel >= 1 && packed.pLevel <= MaxCharacterLevel); + ValidateField(packed.pLevel, packed.pLevel >= 1 && packed.pLevel <= player.getMaxCharacterLevel()); int32_t baseHpMax = SDL_SwapLE32(packed.pMaxHPBase); int32_t baseHp = SDL_SwapLE32(packed.pHPBase); @@ -513,9 +562,9 @@ bool UnPackNetPlayer(const PlayerNetPack &packed, Player &player) ValidateFields(packed.pClass, packed.pBaseDex, packed.pBaseDex <= player.GetMaximumAttributeValue(CharacterAttribute::Dexterity)); ValidateFields(packed.pClass, packed.pBaseVit, packed.pBaseVit <= player.GetMaximumAttributeValue(CharacterAttribute::Vitality)); - ValidateField(packed._pNumInv, packed._pNumInv < InventoryGridCells); + ValidateField(packed._pNumInv, packed._pNumInv <= InventoryGridCells); - player._pLevel = packed.pLevel; + player.setCharacterLevel(packed.pLevel); player.position.tile = position; player.position.future = position; player.plrlevel = packed.plrlevel; @@ -541,7 +590,6 @@ bool UnPackNetPlayer(const PlayerNetPack &packed, Player &player) player._pStatPts = packed.pStatPts; player._pExperience = SDL_SwapLE32(packed.pExperience); - player._pBaseToBlk = PlayersData[static_cast(player._pClass)].blockBonus; player._pMaxManaBase = baseManaMax; player._pManaBase = baseMana; player._pMemSpells = SDL_SwapLE64(packed.pMemSpells); @@ -556,6 +604,28 @@ bool UnPackNetPlayer(const PlayerNetPack &packed, Player &player) for (int i = 0; i < NUM_INVLOC; i++) { if (!UnPackNetItem(player, packed.InvBody[i], player.InvBody[i])) return false; + if (player.InvBody[i].isEmpty()) + continue; + auto loc = static_cast(player.GetItemLocation(player.InvBody[i])); + switch (i) { + case INVLOC_HEAD: + ValidateField(loc, loc == ILOC_HELM); + break; + case INVLOC_RING_LEFT: + case INVLOC_RING_RIGHT: + ValidateField(loc, loc == ILOC_RING); + break; + case INVLOC_AMULET: + ValidateField(loc, loc == ILOC_AMULET); + break; + case INVLOC_HAND_LEFT: + case INVLOC_HAND_RIGHT: + ValidateField(loc, IsAnyOf(loc, ILOC_ONEHAND, ILOC_TWOHAND)); + break; + case INVLOC_CHEST: + ValidateField(loc, loc == ILOC_ARMOR); + break; + } } player._pNumInv = packed._pNumInv; @@ -568,8 +638,17 @@ bool UnPackNetPlayer(const PlayerNetPack &packed, Player &player) player.InvGrid[i] = packed.InvGrid[i]; for (int i = 0; i < MaxBeltItems; i++) { - if (!UnPackNetItem(player, packed.SpdList[i], player.SpdList[i])) + Item &item = player.SpdList[i]; + if (!UnPackNetItem(player, packed.SpdList[i], item)) return false; + if (item.isEmpty()) + continue; + Size beltItemSize = GetInventorySize(item); + int8_t beltItemType = static_cast(item._itype); + bool beltItemUsable = item.isUsable(); + ValidateFields(beltItemSize.width, beltItemSize.height, (beltItemSize == Size { 1, 1 })); + ValidateField(beltItemType, item._itype != ItemType::Gold); + ValidateField(beltItemUsable, beltItemUsable); } CalcPlrInv(player, false); @@ -584,7 +663,7 @@ bool UnPackNetPlayer(const PlayerNetPack &packed, Player &player) ValidateFields(player._pMana, SDL_SwapLE32(packed.pMana), player._pMana == SDL_SwapLE32(packed.pMana)); ValidateFields(player._pMaxMana, SDL_SwapLE32(packed.pMaxMana), player._pMaxMana == SDL_SwapLE32(packed.pMaxMana)); ValidateFields(player._pDamageMod, SDL_SwapLE32(packed.pDamageMod), player._pDamageMod == SDL_SwapLE32(packed.pDamageMod)); - ValidateFields(player._pBaseToBlk, SDL_SwapLE32(packed.pBaseToBlk), player._pBaseToBlk == SDL_SwapLE32(packed.pBaseToBlk)); + ValidateFields(player.getBaseToBlock(), SDL_SwapLE32(packed.pBaseToBlk), player.getBaseToBlock() == SDL_SwapLE32(packed.pBaseToBlk)); ValidateFields(player._pIMinDam, SDL_SwapLE32(packed.pIMinDam), player._pIMinDam == SDL_SwapLE32(packed.pIMinDam)); ValidateFields(player._pIMaxDam, SDL_SwapLE32(packed.pIMaxDam), player._pIMaxDam == SDL_SwapLE32(packed.pIMaxDam)); ValidateFields(player._pIAC, SDL_SwapLE32(packed.pIAC), player._pIAC == SDL_SwapLE32(packed.pIAC)); diff --git a/Source/pack.h b/Source/pack.h index 2cf72c5f765..5ff70a1c5e5 100644 --- a/Source/pack.h +++ b/Source/pack.h @@ -45,7 +45,7 @@ struct PlayerPack { uint8_t pBaseMag; uint8_t pBaseDex; uint8_t pBaseVit; - int8_t pLevel; + uint8_t pLevel; uint8_t pStatPts; uint32_t pExperience; int32_t pGold; @@ -142,6 +142,10 @@ struct PlayerNetPack { }; #pragma pack(pop) +bool IsCreationFlagComboValid(uint16_t iCreateInfo); +bool IsTownItemValid(uint16_t iCreateInfo, const Player &player); +bool IsUniqueMonsterItemValid(uint16_t iCreateInfo, uint32_t dwBuff); +bool IsDungeonItemValid(uint16_t iCreateInfo, uint32_t dwBuff); void PackPlayer(PlayerPack &pPack, const Player &player); void UnPackPlayer(const PlayerPack &pPack, Player &player); void PackNetPlayer(PlayerNetPack &packed, const Player &player); @@ -165,4 +169,20 @@ void PackItem(ItemPack &packedItem, const Item &item, bool isHellfire); */ void UnPackItem(const ItemPack &packedItem, const Player &player, Item &item, bool isHellfire); +/** + * @brief Save the attributes needed to recreate this item into an ItemNetPack struct + * @param item The source item + * @param packedItem The destination packed struct + */ +void PackNetItem(const Item &item, ItemNetPack &packedItem); + +/** + * @brief Expand a ItemPack in to a Item + * @param player The player holding the item + * @param packedItem The source packed item + * @param item The destination item + * @return True if the item is valid + */ +bool UnPackNetItem(const Player &player, const ItemNetPack &packedItem, Item &item); + } // namespace devilution diff --git a/Source/panels/charpanel.cpp b/Source/panels/charpanel.cpp index 79d5216bff6..10c3e513963 100644 --- a/Source/panels/charpanel.cpp +++ b/Source/panels/charpanel.cpp @@ -15,6 +15,7 @@ #include "panels/ui_panels.hpp" #include "player.h" #include "playerdat.hpp" +#include "utils/algorithm/container.hpp" #include "utils/display.h" #include "utils/format_int.hpp" #include "utils/language.h" @@ -122,10 +123,10 @@ PanelEntry panelEntries[] = { { "", { 9, 14 }, 150, 0, []() { return StyledText { UiFlags::ColorWhite, InspectPlayer->_pName }; } }, { "", { 161, 14 }, 149, 0, - []() { return StyledText { UiFlags::ColorWhite, std::string(_(PlayersData[static_cast(InspectPlayer->_pClass)].className)) }; } }, + []() { return StyledText { UiFlags::ColorWhite, std::string(InspectPlayer->getClassName()) }; } }, { N_("Level"), { 57, 52 }, 57, 45, - []() { return StyledText { UiFlags::ColorWhite, StrCat(InspectPlayer->_pLevel) }; } }, + []() { return StyledText { UiFlags::ColorWhite, StrCat(InspectPlayer->getCharacterLevel()) }; } }, { N_("Experience"), { TopRightLabelX, 52 }, 99, 91, []() { int spacing = ((InspectPlayer->_pExperience >= 1000000000) ? 0 : 1); @@ -133,11 +134,12 @@ PanelEntry panelEntries[] = { } }, { N_("Next level"), { TopRightLabelX, 80 }, 99, 198, []() { - if (InspectPlayer->_pLevel == MaxCharacterLevel) { + if (InspectPlayer->isMaxCharacterLevel()) { return StyledText { UiFlags::ColorWhitegold, std::string(_("None")) }; } - int spacing = ((InspectPlayer->_pNextExper >= 1000000000) ? 0 : 1); - return StyledText { UiFlags::ColorWhite, FormatInteger(InspectPlayer->_pNextExper), spacing }; + uint32_t nextExperienceThreshold = InspectPlayer->getNextExperienceThreshold(); + int spacing = ((nextExperienceThreshold >= 1000000000) ? 0 : 1); + return StyledText { UiFlags::ColorWhite, FormatInteger(nextExperienceThreshold), spacing }; } }, { N_("Base"), { LeftColumnLabelX, /* set dynamically */ 0 }, 0, 44, {} }, @@ -167,14 +169,14 @@ PanelEntry panelEntries[] = { []() { return StyledText { UiFlags::ColorWhite, FormatInteger(InspectPlayer->_pGold) }; } }, { N_("Armor class"), { RightColumnLabelX, 163 }, 57, RightColumnLabelWidth, - []() { return StyledText { GetValueColor(InspectPlayer->_pIBonusAC), StrCat(InspectPlayer->GetArmor() + InspectPlayer->_pLevel * 2) }; } }, + []() { return StyledText { GetValueColor(InspectPlayer->_pIBonusAC), StrCat(InspectPlayer->GetArmor() + InspectPlayer->getCharacterLevel() * 2) }; } }, { N_("To hit"), { RightColumnLabelX, 191 }, 57, RightColumnLabelWidth, []() { return StyledText { GetValueColor(InspectPlayer->_pIBonusToHit), StrCat(InspectPlayer->InvBody[INVLOC_HAND_LEFT]._itype == ItemType::Bow ? InspectPlayer->GetRangedToHit() : InspectPlayer->GetMeleeToHit(), "%") }; } }, { N_("Damage"), { RightColumnLabelX, 219 }, 57, RightColumnLabelWidth, []() { - std::pair dmg = GetDamage(); - int spacing = ((dmg.first >= 100) ? -1 : 1); - return StyledText { GetValueColor(InspectPlayer->_pIBonusDam), StrCat(dmg.first, "-", dmg.second), spacing }; + const auto [dmgMin, dmgMax] = GetDamage(); + int spacing = ((dmgMin >= 100) ? -1 : 1); + return StyledText { GetValueColor(InspectPlayer->_pIBonusDam), StrCat(dmgMin, "-", dmgMax), spacing }; } }, { N_("Life"), { LeftColumnLabelX, 284 }, 45, LeftColumnLabelWidth, @@ -217,8 +219,8 @@ void DrawShadowString(const Surface &out, const PanelEntry &entry) return; constexpr int Spacing = 0; - const string_view textStr = LanguageTranslate(entry.label); - string_view text; + const std::string_view textStr = LanguageTranslate(entry.label); + std::string_view text; std::string wrapped; if (entry.labelLength > 0) { wrapped = WordWrapString(textStr, entry.labelLength, GameFont12, Spacing); @@ -238,13 +240,15 @@ void DrawShadowString(const Surface &out, const PanelEntry &entry) labelPosition += Displacement { -entry.labelLength - (IsSmallFontTall() ? 2 : 3), 0 }; } - // If the text is less tall then the field, we center it vertically relative to the field. + // If the text is less tall than the field, we center it vertically relative to the field. // Otherwise, we draw from the top of the field. - const int textHeight = (std::count(wrapped.begin(), wrapped.end(), '\n') + 1) * GetLineHeight(wrapped, GameFont12); + const int textHeight = static_cast((c_count(wrapped, '\n') + 1) * GetLineHeight(wrapped, GameFont12)); const int labelHeight = std::max(PanelFieldHeight, textHeight); - DrawString(out, text, { labelPosition + Displacement { -2, 2 }, { entry.labelLength, labelHeight } }, style | UiFlags::ColorBlack, Spacing); - DrawString(out, text, { labelPosition, { entry.labelLength, labelHeight } }, style | UiFlags::ColorWhite, Spacing); + DrawString(out, text, { labelPosition + Displacement { -2, 2 }, { entry.labelLength, labelHeight } }, + { .flags = style | UiFlags::ColorBlack, .spacing = Spacing }); + DrawString(out, text, { labelPosition, { entry.labelLength, labelHeight } }, + { .flags = style | UiFlags::ColorWhite, .spacing = Spacing }); } void DrawStatButtons(const Surface &out) @@ -309,7 +313,7 @@ void DrawChr(const Surface &out) out, tmp.text, { entry.position + Displacement { pos.x, pos.y + PanelFieldPaddingTop }, { entry.length, PanelFieldInnerHeight } }, - UiFlags::AlignCenter | UiFlags::VerticalCenter | tmp.style, tmp.spacing); + { .flags = UiFlags::AlignCenter | UiFlags::VerticalCenter | tmp.style, .spacing = tmp.spacing }); } } DrawStatButtons(out); diff --git a/Source/panels/console.cpp b/Source/panels/console.cpp new file mode 100644 index 00000000000..3d7e414f82c --- /dev/null +++ b/Source/panels/console.cpp @@ -0,0 +1,657 @@ +#ifdef _DEBUG +#include "panels/console.hpp" + +#include +#include +#include + +#include +#include + +#ifdef USE_SDL1 +#include "utils/sdl2_to_1_2_backports.h" +#endif + +#include "DiabloUI/text_input.hpp" +#include "control.h" +#include "engine.h" +#include "engine/assets.hpp" +#include "engine/displacement.hpp" +#include "engine/dx.h" +#include "engine/palette.h" +#include "engine/rectangle.hpp" +#include "engine/render/text_render.hpp" +#include "engine/size.hpp" +#include "engine/surface.hpp" +#include "lua/autocomplete.hpp" +#include "lua/repl.hpp" +#include "utils/algorithm/container.hpp" +#include "utils/sdl_geometry.h" +#include "utils/str_cat.hpp" +#include "utils/str_split.hpp" + +namespace devilution { + +namespace { + +constexpr std::string_view Prompt = "> "; +constexpr std::string_view HelpText = + // Displayed as the first console message + "Lua console\n" + "Shift+Enter to insert a newline, PageUp/Down to scroll," + " Up/Down to fill the input from history," + " Shift+Up/Down to fill the input from output history," + " Ctrl+L to clear history, Esc to close."; +std::optional> ConsolePrelude; + +bool IsConsoleVisible; +char ConsoleInputBuffer[4096]; +TextInputCursorState ConsoleInputCursor; +TextInputState ConsoleInputState { + TextInputState::Options { + .value = ConsoleInputBuffer, + .cursor = &ConsoleInputCursor, + .maxLength = sizeof(ConsoleInputBuffer) - 1, + } +}; + +enum class InputTextState { + UpToDate, + Edited, + RestoredFromHistory +}; + +InputTextState CurrentInputTextState = InputTextState::UpToDate; +std::string WrappedInputText { Prompt }; +std::vector AutocompleteSuggestions; +int AutocompleteSuggestionsMaxWidth = -1; +int AutocompleteSuggestionFocusIndex = -1; +constexpr size_t MaxSuggestions = 12; + +struct ConsoleLine { + enum Type : uint8_t { + Help, + Input, + Output, + Warning, + Error + }; + + Type type; + std::string text; + std::string wrapped = {}; + int numLines = 0; + + [[nodiscard]] std::string_view textWithoutPrompt() const + { + std::string_view result = text; + if (type == ConsoleLine::Input) { + result.remove_prefix(Prompt.size()); + } + return result; + } +}; + +std::vector ConsoleLines; +size_t NumPreparedConsoleLines; +int ConsoleLinesTotalHeight; + +// Index of the currently filled input/output, counting from end. +int HistoryIndex = -1; + +// Draft input, saved when navigating history. +std::string DraftInput; + +Rectangle OuterRect; +Rectangle InputRect; +int InputRectHeight; +constexpr int LineHeight = 20; +constexpr int TextPaddingYTop = 0; +constexpr int TextPaddingYBottom = 4; +constexpr int TextPaddingX = 4; +constexpr uint8_t BorderColor = PAL8_YELLOW; +bool FirstRender; + +constexpr UiFlags TextUiFlags = UiFlags::FontSizeDialog; +constexpr UiFlags InputTextUiFlags = TextUiFlags | UiFlags::ColorDialogWhite; +constexpr UiFlags OutputTextUiFlags = TextUiFlags | UiFlags::ColorDialogWhite; +constexpr UiFlags WarningTextUiFlags = TextUiFlags | UiFlags::ColorDialogYellow; +constexpr UiFlags ErrorTextUiFlags = TextUiFlags | UiFlags::ColorDialogRed; +constexpr UiFlags AutocompleteSuggestionsTextUiFlags = TextUiFlags | UiFlags::ColorDialogWhite; +constexpr UiFlags AutocompleteSuggestionsFocusedTextUiFlags = TextUiFlags | UiFlags::ColorDialogYellow; + +constexpr int TextSpacing = 0; +constexpr GameFontTables TextFontSize = GetFontSizeFromUiFlags(InputTextUiFlags); +constexpr GameFontTables AutocompleteSuggestionsTextFontSize = GetFontSizeFromUiFlags(AutocompleteSuggestionsTextUiFlags); + +// Scroll offset from the bottom (in pages), to be applied on next render. +int PendingScrollPages; +// Scroll offset from the bottom in pixels. +int ScrollOffset; +constexpr int ScrollStep = LineHeight * 3; + +void CloseConsole() +{ + IsConsoleVisible = false; + SDL_StopTextInput(); +} + +int GetConsoleLinesInnerWidth() +{ + return OuterRect.size.width - 2 * TextPaddingX; +} + +void PrepareForRender(ConsoleLine &consoleLine) +{ + consoleLine.wrapped = WordWrapString(consoleLine.text, GetConsoleLinesInnerWidth(), TextFontSize, TextSpacing); + consoleLine.numLines += static_cast(c_count(consoleLine.wrapped, '\n')) + 1; + ConsoleLinesTotalHeight += consoleLine.numLines * LineHeight; +} + +void AddConsoleLine(ConsoleLine &&consoleLine) +{ + ConsoleLines.emplace_back(std::move(consoleLine)); +} + +void SendInput() +{ + RunInConsole(ConsoleInputState.value()); + ConsoleInputState.clear(); + DraftInput.clear(); + HistoryIndex = -1; +} + +void DrawAutocompleteSuggestions(const Surface &out, const std::vector &suggestions, Point position) +{ + const int maxInnerWidth = out.w() - TextPaddingX * 2; + if (AutocompleteSuggestionsMaxWidth == -1) { + int maxWidth = 0; + for (const LuaAutocompleteSuggestion &suggestion : suggestions) { + maxWidth = std::max(maxWidth, GetLineWidth(suggestion.displayText, AutocompleteSuggestionsTextFontSize, TextSpacing)); + } + AutocompleteSuggestionsMaxWidth = std::min(maxWidth, maxInnerWidth); + } + + const int outerWidth = AutocompleteSuggestionsMaxWidth + TextPaddingX * 2; + + if (position.x + outerWidth > out.w()) { + position.x = out.w() - outerWidth; + } + const int height = static_cast(suggestions.size()) * LineHeight + TextPaddingYBottom + TextPaddingYTop; + + position.y -= height; + position.y = std::max(LineHeight, position.y); + + FillRect(out, position.x, position.y, outerWidth, height, PAL16_BLUE + 14); + size_t i = 0; + + Point textPosition { position.x + TextPaddingX, position.y + TextPaddingYTop }; + for (const LuaAutocompleteSuggestion &suggestion : suggestions) { + if (static_cast(i) == AutocompleteSuggestionFocusIndex) { + const int extraTop = i == 0 ? TextPaddingYTop : 0; + const int extraHeight = extraTop + TextPaddingYBottom; + FillRect(out, position.x, textPosition.y - extraTop, outerWidth, LineHeight + extraHeight, PAL16_BLUE + 8); + } + const int textHeight = LineHeight + TextPaddingYBottom; + DrawString( + out.subregion(textPosition.x, textPosition.y, maxInnerWidth, textHeight), suggestion.displayText, + Rectangle { Point { 0, 0 }, Size { maxInnerWidth, textHeight } }, + TextRenderOptions { + .flags = AutocompleteSuggestionsTextUiFlags, + .spacing = TextSpacing, + }); + textPosition.y += LineHeight; + ++i; + } +} + +bool IsBreakStart(std::string_view str, size_t &breakLen) +{ + const char32_t cp = DecodeFirstUtf8CodePoint(str, &breakLen); + return cp == U'\n' || IsBreakableWhitespace(cp); +} + +void DrawInputText(const Surface &out, + Rectangle rect, std::string_view originalInputText, std::string_view wrappedInputText) +{ + int lineY = 0; + int numRendered = -static_cast(Prompt.size()); + bool prevIsOriginalWhitespace = false; + + const Surface inputTextSurface = out.subregion(rect.position.x, rect.position.y, rect.size.width, rect.size.height); + std::optional renderedCursorPositionOut; + for (const std::string_view line : SplitByChar(wrappedInputText, '\n')) { + const int lineCursorPosition = static_cast(ConsoleInputCursor.position) - numRendered; + const bool isCursorOnPrevLine = lineCursorPosition == 0 && !prevIsOriginalWhitespace && numRendered > 0; + DrawString( + inputTextSurface, line, { 0, lineY }, + TextRenderOptions { + .flags = InputTextUiFlags, + .spacing = TextSpacing, + .cursorPosition = isCursorOnPrevLine ? -1 : lineCursorPosition, + .highlightRange = { static_cast(ConsoleInputCursor.selection.begin) - numRendered, static_cast(ConsoleInputCursor.selection.end) - numRendered }, + .renderedCursorPositionOut = &renderedCursorPositionOut }); + lineY += LineHeight; + numRendered += static_cast(line.size()); + + size_t whitespaceLength; + prevIsOriginalWhitespace = static_cast(numRendered) < originalInputText.size() + && IsBreakStart(originalInputText.substr(static_cast(numRendered)), whitespaceLength); + if (prevIsOriginalWhitespace) { + // If we replaced an original whitespace with a newline, count the original whitespace as rendered. + numRendered += static_cast(whitespaceLength); + } + if (numRendered < 0 && IsBreakStart(Prompt.substr(Prompt.size() - static_cast(-numRendered)), whitespaceLength)) { + // If we replaced the whitespace in a prompt with a newline, count it as rendered. + numRendered += static_cast(whitespaceLength); + } + } + + if (!AutocompleteSuggestions.empty() && renderedCursorPositionOut.has_value()) { + Point position = *renderedCursorPositionOut; + position.x += rect.position.x; + position.y += rect.position.y; + DrawAutocompleteSuggestions(out, AutocompleteSuggestions, position); + } +} + +void DrawConsoleLines(const Surface &out) +{ + const int innerHeight = out.h() - 4; // Extra space for letters like g. + if (PendingScrollPages) { + ScrollOffset += innerHeight * PendingScrollPages; + PendingScrollPages = 0; + } + + if (NumPreparedConsoleLines != ConsoleLines.size()) { + for (size_t i = NumPreparedConsoleLines; i < ConsoleLines.size(); ++i) { + PrepareForRender(ConsoleLines[i]); + } + NumPreparedConsoleLines = ConsoleLines.size(); + ScrollOffset = 0; + } + + ScrollOffset = std::clamp(ScrollOffset, 0, std::max(0, ConsoleLinesTotalHeight - innerHeight)); + + int lineYEnd = innerHeight + ScrollOffset; + // NOLINTNEXTLINE(modernize-loop-convert) + for (auto it = ConsoleLines.rbegin(), itEnd = ConsoleLines.rend(); it != itEnd; ++it) { + ConsoleLine &consoleLine = *it; + const int linesYBegin = lineYEnd - LineHeight * consoleLine.numLines; + if (linesYBegin > innerHeight) { + lineYEnd = linesYBegin; + continue; + } + size_t end = consoleLine.wrapped.size(); + while (true) { + const size_t begin = consoleLine.wrapped.rfind('\n', end - 1) + 1; + const std::string_view line = std::string_view(consoleLine.wrapped.data() + begin, end - begin); + lineYEnd -= LineHeight; + switch (consoleLine.type) { + case ConsoleLine::Input: + DrawString(out, line, { 0, lineYEnd }, + TextRenderOptions { .flags = InputTextUiFlags, .spacing = TextSpacing }); + break; + case ConsoleLine::Output: + case ConsoleLine::Help: + DrawString(out, line, { 0, lineYEnd }, + TextRenderOptions { .flags = OutputTextUiFlags, .spacing = TextSpacing }); + break; + case ConsoleLine::Warning: + DrawString(out, line, { 0, lineYEnd }, + TextRenderOptions { .flags = WarningTextUiFlags, .spacing = TextSpacing }); + break; + case ConsoleLine::Error: + DrawString(out, line, { 0, lineYEnd }, + TextRenderOptions { .flags = ErrorTextUiFlags, .spacing = TextSpacing }); + break; + } + if (lineYEnd < 0 || begin == 0) + break; + end = begin - 1; + } + } +} + +const ConsoleLine &GetConsoleLineFromEnd(int index) +{ + return *(ConsoleLines.rbegin() + index); +} + +void SetHistoryIndex(int index) +{ + CurrentInputTextState = InputTextState::RestoredFromHistory; + HistoryIndex = static_cast(std::ssize(ConsoleLines)) - (index + 1); + if (HistoryIndex == -1) { + ConsoleInputState.assign(DraftInput); + return; + } + const ConsoleLine &line = ConsoleLines[index]; + ConsoleInputState.assign(line.textWithoutPrompt()); +} + +void PrevHistoryItem(tl::function_ref filter) +{ + if (HistoryIndex == -1) { + DraftInput = ConsoleInputState.value(); + } + const int n = static_cast(std::ssize(ConsoleLines)); + for (int i = HistoryIndex + 1; i < n; ++i) { + const int index = n - (i + 1); + if (filter(ConsoleLines[index])) { + SetHistoryIndex(index); + return; + } + } +} + +void NextHistoryItem(tl::function_ref filter) +{ + const int n = static_cast(std::ssize(ConsoleLines)); + for (int i = n - HistoryIndex; i < n; ++i) { + if (filter(ConsoleLines[i])) { + SetHistoryIndex(i); + return; + } + } + if (HistoryIndex != -1) { + SetHistoryIndex(n); + } +} + +bool IsHistoryInputLine(const ConsoleLine &line) +{ + if (line.type != ConsoleLine::Input) + return false; + std::string_view text = line.text; + text.remove_prefix(Prompt.size()); + if (text.empty()) + return false; + return HistoryIndex == -1 || GetConsoleLineFromEnd(HistoryIndex).textWithoutPrompt() != text; +} + +void PrevInput() +{ + PrevHistoryItem(IsHistoryInputLine); +} + +void NextInput() +{ + NextHistoryItem(IsHistoryInputLine); +} + +bool IsHistoryOutputLine(const ConsoleLine &line) +{ + return !line.text.empty() + && (line.type == ConsoleLine::Output || line.type == ConsoleLine::Warning || line.type == ConsoleLine::Error) + && (HistoryIndex == -1 + || GetConsoleLineFromEnd(HistoryIndex).textWithoutPrompt() != line.text); +} + +void PrevOutput() +{ + PrevHistoryItem(IsHistoryOutputLine); +} + +void NextOutput() +{ + NextHistoryItem(IsHistoryOutputLine); +} + +void AddInitialConsoleLines() +{ + if (ConsolePrelude->has_value()) { + std::string_view prelude { **ConsolePrelude }; + if (!prelude.empty() && prelude.back() == '\n') + prelude.remove_suffix(1); + AddConsoleLine(ConsoleLine { .type = ConsoleLine::Help, .text = StrCat(HelpText, "\n", prelude) }); + } else { + AddConsoleLine(ConsoleLine { .type = ConsoleLine::Help, .text = std::string(HelpText) }); + AddConsoleLine(ConsoleLine { .type = ConsoleLine::Error, .text = ConsolePrelude->error() }); + } +} + +void ClearConsole() +{ + ConsoleLines.clear(); + HistoryIndex = -1; + ScrollOffset = 0; + NumPreparedConsoleLines = 0; + ConsoleLinesTotalHeight = 0; + AddInitialConsoleLines(); +} + +} // namespace + +bool IsConsoleOpen() +{ + return IsConsoleVisible; +} + +void OpenConsole() +{ + IsConsoleVisible = true; + FirstRender = true; +} + +void AcceptSuggestion() +{ + const LuaAutocompleteSuggestion &suggestion = AutocompleteSuggestions[AutocompleteSuggestionFocusIndex]; + ConsoleInputState.type(suggestion.completionText); + if (suggestion.cursorAdjust == -1) { + ConsoleInputState.moveCursorLeft(/*word=*/false); + } +} + +bool ConsoleHandleEvent(const SDL_Event &event) +{ + if (!IsConsoleVisible) { + // Make console open on the top-left keyboard key even if it is not a backtick. + if (event.type == SDL_KEYDOWN && event.key.keysym.scancode == SDL_SCANCODE_GRAVE) { + OpenConsole(); + return true; + } + return false; + } + if (HandleTextInputEvent(event, ConsoleInputState)) { + CurrentInputTextState = InputTextState::Edited; + return true; + } + const auto modState = SDL_GetModState(); + const bool isShift = (modState & KMOD_SHIFT) != 0; + switch (event.type) { + case SDL_KEYDOWN: + switch (event.key.keysym.sym) { + case SDLK_ESCAPE: + if (!AutocompleteSuggestions.empty()) { + AutocompleteSuggestions.clear(); + AutocompleteSuggestionFocusIndex = -1; + } else { + CloseConsole(); + } + return true; + case SDLK_UP: + if (AutocompleteSuggestionFocusIndex != -1) { + AutocompleteSuggestionFocusIndex = std::max( + 0, AutocompleteSuggestionFocusIndex - 1); + } else { + isShift ? PrevOutput() : PrevInput(); + } + return true; + case SDLK_DOWN: + if (AutocompleteSuggestionFocusIndex != -1) { + AutocompleteSuggestionFocusIndex = std::min( + static_cast(AutocompleteSuggestions.size()) - 1, + AutocompleteSuggestionFocusIndex + 1); + } else { + isShift ? NextOutput() : NextInput(); + } + return true; + case SDLK_TAB: + if (AutocompleteSuggestionFocusIndex != -1) { + AcceptSuggestion(); + CurrentInputTextState = InputTextState::Edited; + } + return true; + case SDLK_RETURN: + case SDLK_KP_ENTER: + if (isShift) { + ConsoleInputState.type("\n"); + } else { + if (AutocompleteSuggestionFocusIndex != -1) { + AcceptSuggestion(); + } else { + SendInput(); + } + } + CurrentInputTextState = InputTextState::Edited; + return true; + case SDLK_PAGEUP: + ++PendingScrollPages; + return true; + case SDLK_PAGEDOWN: + --PendingScrollPages; + return true; + case SDLK_l: + ClearConsole(); + return true; + default: + return false; + } + break; +#if SDL_VERSION_ATLEAST(2, 0, 0) + case SDL_MOUSEWHEEL: + if (event.wheel.y > 0) { + ScrollOffset += ScrollStep; + } else if (event.wheel.y < 0) { + ScrollOffset -= ScrollStep; + } + return true; +#else + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + if (event.button.button == SDL_BUTTON_WHEELUP) { + ScrollOffset += ScrollStep; + return true; + } + if (event.button.button == SDL_BUTTON_WHEELDOWN) { + ScrollOffset -= ScrollStep; + return true; + } + return false; +#endif + default: + return false; + } + return false; +} + +void DrawConsole(const Surface &out) +{ + if (!IsConsoleVisible) + return; + + OuterRect.position = { 0, 0 }; + OuterRect.size = { out.w(), out.h() - GetMainPanel().size.height - 2 }; + + const std::string_view originalInputText = ConsoleInputState.value(); + if (CurrentInputTextState != InputTextState::UpToDate) { + WrappedInputText = WordWrapString(StrCat(Prompt, originalInputText), OuterRect.size.width - 2 * TextPaddingX, TextFontSize, TextSpacing); + if (CurrentInputTextState == InputTextState::RestoredFromHistory) { + AutocompleteSuggestions.clear(); + } else { + GetLuaAutocompleteSuggestions(originalInputText.substr(0, ConsoleInputCursor.position), GetLuaReplEnvironment(), /*maxSuggestions=*/MaxSuggestions, AutocompleteSuggestions); + } + AutocompleteSuggestionsMaxWidth = -1; + AutocompleteSuggestionFocusIndex = AutocompleteSuggestions.empty() ? -1 : 0; + CurrentInputTextState = InputTextState::UpToDate; + } + + const int numLines = static_cast(c_count(WrappedInputText, '\n')) + 1; + InputRectHeight = std::min(OuterRect.size.height, numLines * LineHeight + TextPaddingYTop + TextPaddingYBottom); + const int inputTextHeight = InputRectHeight - (TextPaddingYTop + TextPaddingYBottom); + + InputRect.position = { 0, OuterRect.size.height - InputRectHeight }; + InputRect.size = { OuterRect.size.width, InputRectHeight }; + const Rectangle inputTextRect { + { InputRect.position.x + TextPaddingX, InputRect.position.y + TextPaddingYTop }, + { InputRect.size.width - 2 * TextPaddingX, inputTextHeight } + }; + + if (FirstRender) { + const SDL_Rect sdlInputRect = MakeSdlRect(InputRect); + SDL_SetTextInputRect(&sdlInputRect); + SDL_StartTextInput(); + FirstRender = false; + if (ConsoleLines.empty()) { + InitConsole(); + } + } + + const Rectangle bgRect = OuterRect; + DrawHalfTransparentRectTo(out, bgRect.position.x, bgRect.position.y, bgRect.size.width, bgRect.size.height); + + DrawConsoleLines( + out.subregion( + TextPaddingX, + TextPaddingYTop, + GetConsoleLinesInnerWidth(), + OuterRect.size.height - inputTextRect.size.height - 8)); + + DrawHorizontalLine(out, InputRect.position - Displacement { 0, 1 }, InputRect.size.width, BorderColor); + DrawInputText( + out, + Rectangle( + inputTextRect.position, + Size { + // Extra space for the cursor on the right: + inputTextRect.size.width + TextPaddingX, + // Extra space for letters like g. + inputTextRect.size.height + TextPaddingYBottom }), + originalInputText, + WrappedInputText); + + SDL_Rect sdlRect = MakeSdlRect(OuterRect); + BltFast(&sdlRect, &sdlRect); +} + +void InitConsole() +{ + if (!ConsoleLines.empty()) + return; + ConsolePrelude = LoadAsset("lua\\repl_prelude.lua"); + AddInitialConsoleLines(); + if (ConsolePrelude->has_value()) + RunLuaReplLine(std::string_view(**ConsolePrelude)); +} + +void RunInConsole(std::string_view code) +{ + AddConsoleLine(ConsoleLine { .type = ConsoleLine::Input, .text = StrCat(Prompt, code) }); + tl::expected result = RunLuaReplLine(code); + + if (result.has_value()) { + if (!result->empty()) { + AddConsoleLine(ConsoleLine { .type = ConsoleLine::Output, .text = *std::move(result) }); + } + } else { + if (!result.error().empty()) { + AddConsoleLine(ConsoleLine { .type = ConsoleLine::Error, .text = std::move(result).error() }); + } else { + AddConsoleLine(ConsoleLine { .type = ConsoleLine::Error, .text = "Unknown error" }); + } + } +} + +void PrintToConsole(std::string_view text) +{ + AddConsoleLine(ConsoleLine { .type = ConsoleLine::Output, .text = std::string(text) }); +} + +void PrintWarningToConsole(std::string_view text) +{ + AddConsoleLine(ConsoleLine { .type = ConsoleLine::Warning, .text = std::string(text) }); +} + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/panels/console.hpp b/Source/panels/console.hpp new file mode 100644 index 00000000000..234ba647738 --- /dev/null +++ b/Source/panels/console.hpp @@ -0,0 +1,22 @@ +#ifdef _DEBUG +#pragma once + +#include + +#include + +#include "engine/surface.hpp" + +namespace devilution { + +void InitConsole(); +bool IsConsoleOpen(); +void OpenConsole(); +bool ConsoleHandleEvent(const SDL_Event &event); +void DrawConsole(const Surface &out); +void RunInConsole(std::string_view code); +void PrintToConsole(std::string_view text); +void PrintWarningToConsole(std::string_view text); + +} // namespace devilution +#endif // _DEBUG diff --git a/Source/panels/info_box.hpp b/Source/panels/info_box.hpp index 3fbf583c2c3..d74b93992d5 100644 --- a/Source/panels/info_box.hpp +++ b/Source/panels/info_box.hpp @@ -5,14 +5,14 @@ namespace devilution { /** - * @brief Info box frame + * @brief Fixed size info box frame * * Used in stores, the quest log, the help window, and the unique item info window. */ extern OptionalOwnedClxSpriteList pSTextBoxCels; /** - * @brief Info box scrollbar graphics. + * @brief Dynamic size info box frame and scrollbar graphics. * * Used in stores and `DrawDiabloMsg`. */ diff --git a/Source/panels/mainpanel.cpp b/Source/panels/mainpanel.cpp index b3867117e30..b5d6e19ca31 100644 --- a/Source/panels/mainpanel.cpp +++ b/Source/panels/mainpanel.cpp @@ -1,6 +1,7 @@ #include "panels/mainpanel.hpp" #include +#include #include "control.h" #include "engine/clx_sprite.hpp" @@ -11,7 +12,6 @@ #include "utils/language.h" #include "utils/sdl_compat.h" #include "utils/sdl_geometry.h" -#include "utils/stdcompat/optional.hpp" #include "utils/surface_to_clx.hpp" namespace devilution { @@ -25,13 +25,15 @@ OptionalOwnedClxSpriteList PanelButton; OptionalOwnedClxSpriteList PanelButtonGrime; OptionalOwnedClxSpriteList PanelButtonDownGrime; -void DrawButtonText(const Surface &out, string_view text, Rectangle placement, UiFlags style, int spacing = 1) +void DrawButtonText(const Surface &out, std::string_view text, Rectangle placement, UiFlags style, int spacing = 1) { - DrawString(out, text, { placement.position + Displacement { 0, 1 }, placement.size }, UiFlags::AlignCenter | UiFlags::KerningFitSpacing | UiFlags::ColorBlack, spacing); - DrawString(out, text, placement, UiFlags::AlignCenter | UiFlags::KerningFitSpacing | style, spacing); + DrawString(out, text, { placement.position + Displacement { 0, 1 }, placement.size }, + { .flags = UiFlags::AlignCenter | UiFlags::KerningFitSpacing | UiFlags::ColorBlack, .spacing = spacing }); + DrawString(out, text, placement, + { .flags = UiFlags::AlignCenter | UiFlags::KerningFitSpacing | style, .spacing = spacing }); } -void DrawButtonOnPanel(Point position, string_view text, int frame) +void DrawButtonOnPanel(Point position, std::string_view text, int frame) { RenderClxSprite(*pBtmBuff, (*PanelButton)[frame], position); int spacing = 2; @@ -44,7 +46,7 @@ void DrawButtonOnPanel(Point position, string_view text, int frame) DrawButtonText(*pBtmBuff, text, { position, { (*PanelButton)[0].width(), 0 } }, UiFlags::ColorButtonface, spacing); } -void RenderMainButton(const Surface &out, int buttonId, string_view text, int frame) +void RenderMainButton(const Surface &out, int buttonId, std::string_view text, int frame) { Point panelPosition { PanBtnPos[buttonId].x + 4, PanBtnPos[buttonId].y + 17 }; DrawButtonOnPanel(panelPosition, text, frame); @@ -97,7 +99,7 @@ void LoadMainPanel() constexpr size_t NumOtherPlayers = 3; // Render the unpressed voice buttons to pBtmBuff. - string_view text = _("voice"); + std::string_view text = _("voice"); const int textWidth = GetLineWidth(text, GameFont12, 1); for (size_t i = 0; i < NumOtherPlayers; ++i) { Point position { 176, static_cast(GetMainPanel().size.height + 101 + 18 * i) }; diff --git a/Source/panels/spell_book.cpp b/Source/panels/spell_book.cpp index 417436684df..aab487ba249 100644 --- a/Source/panels/spell_book.cpp +++ b/Source/panels/spell_book.cpp @@ -1,6 +1,7 @@ #include "panels/spell_book.hpp" #include +#include #include @@ -19,18 +20,25 @@ #include "player.h" #include "spelldat.h" #include "utils/language.h" -#include "utils/stdcompat/optional.hpp" namespace devilution { namespace { -OptionalOwnedClxSpriteList pSBkBtnCel; -OptionalOwnedClxSpriteList pSpellBkCel; +OptionalOwnedClxSpriteList spellBookButtons; +OptionalOwnedClxSpriteList spellBookBackground; const size_t SpellBookPages = 6; const size_t SpellBookPageEntries = 7; +constexpr uint16_t SpellBookButtonWidthDiablo = 76; +constexpr uint16_t SpellBookButtonWidthHellfire = 61; + +uint16_t SpellBookButtonWidth() +{ + return gbIsHellfire ? SpellBookButtonWidthHellfire : SpellBookButtonWidthDiablo; +} + /** Maps from spellbook page number and position to SpellID. */ const SpellID SpellPages[SpellBookPages][SpellBookPageEntries] = { { SpellID::Null, SpellID::Firebolt, SpellID::ChargedBolt, SpellID::HolyBolt, SpellID::Healing, SpellID::HealOther, SpellID::Inferno }, @@ -66,13 +74,13 @@ SpellID GetSpellFromSpellPage(size_t page, size_t entry) constexpr Size SpellBookDescription { 250, 43 }; constexpr int SpellBookDescriptionPaddingHorizontal = 2; -void PrintSBookStr(const Surface &out, Point position, string_view text, UiFlags flags = UiFlags::None) +void PrintSBookStr(const Surface &out, Point position, std::string_view text, UiFlags flags = UiFlags::None) { DrawString(out, text, Rectangle(GetPanelPosition(UiPanels::Spell, position + Displacement { SPLICONLENGTH, 0 }), SpellBookDescription) .inset({ SpellBookDescriptionPaddingHorizontal, 0 }), - UiFlags::ColorWhite | flags); + { .flags = UiFlags::ColorWhite | flags }); } SpellType GetSBookTrans(SpellID ii, bool townok) @@ -102,35 +110,52 @@ SpellType GetSBookTrans(SpellID ii, bool townok) return st; } +StringOrView GetSpellPowerText(SpellID spell, int spellLevel) +{ + if (spellLevel == 0) { + return _("Unusable"); + } + if (spell == SpellID::BoneSpirit) { + return _(/* TRANSLATORS: UI constraints, keep short please.*/ "Dmg: 1/3 target hp"); + } + const auto [min, max] = GetDamageAmt(spell, spellLevel); + if (min == -1) { + return StringOrView {}; + } + if (spell == SpellID::Healing || spell == SpellID::HealOther) { + return fmt::format(fmt::runtime(_(/* TRANSLATORS: UI constraints, keep short please.*/ "Heals: {:d} - {:d}")), min, max); + } + return fmt::format(fmt::runtime(_(/* TRANSLATORS: UI constraints, keep short please.*/ "Damage: {:d} - {:d}")), min, max); +} + } // namespace void InitSpellBook() { - pSpellBkCel = LoadCel("data\\spellbk", static_cast(SidePanelSize.width)); - pSBkBtnCel = LoadCel("data\\spellbkb", gbIsHellfire ? 61 : 76); + spellBookBackground = LoadCel("data\\spellbk", static_cast(SidePanelSize.width)); + spellBookButtons = LoadCel("data\\spellbkb", SpellBookButtonWidth()); LoadSmallSpellIcons(); } void FreeSpellBook() { FreeSmallSpellIcons(); - pSBkBtnCel = std::nullopt; - pSpellBkCel = std::nullopt; + spellBookButtons = std::nullopt; + spellBookBackground = std::nullopt; } void DrawSpellBook(const Surface &out) { - ClxDraw(out, GetPanelPosition(UiPanels::Spell, { 0, 351 }), (*pSpellBkCel)[0]); - if (gbIsHellfire && sbooktab < 5) { - ClxDraw(out, GetPanelPosition(UiPanels::Spell, { 61 * sbooktab + 7, 348 }), (*pSBkBtnCel)[sbooktab]); - } else { - // BUGFIX: rendering of page 3 and page 4 buttons are both off-by-one pixel (fixed). - int sx = 76 * sbooktab + 7; - if (sbooktab == 2 || sbooktab == 3) { - sx++; - } - ClxDraw(out, GetPanelPosition(UiPanels::Spell, { sx, 348 }), (*pSBkBtnCel)[sbooktab]); - } + constexpr int SpellBookButtonX = 7; + constexpr int SpellBookButtonY = 348; + ClxDraw(out, GetPanelPosition(UiPanels::Spell, { 0, 351 }), (*spellBookBackground)[0]); + const int buttonX = gbIsHellfire && sbooktab < 5 + ? SpellBookButtonWidthHellfire * sbooktab + : SpellBookButtonWidthDiablo * sbooktab + // BUGFIX: rendering of page 3 and page 4 buttons are both off-by-one pixel (fixed). + + (sbooktab == 2 || sbooktab == 3 ? 1 : 0); + + ClxDraw(out, GetPanelPosition(UiPanels::Spell, { SpellBookButtonX + buttonX, SpellBookButtonY }), (*spellBookButtons)[sbooktab]); Player &player = *InspectPlayer; uint64_t spl = player._pMemSpells | player._pISpells | player._pAblSpells; @@ -158,32 +183,17 @@ void DrawSpellBook(const Surface &out) PrintSBookStr(out, line1, _("Skill")); break; case SpellType::Charges: { - int charges = player.InvBody[INVLOC_HAND_LEFT]._iCharges; + const int charges = player.InvBody[INVLOC_HAND_LEFT]._iCharges; PrintSBookStr(out, line1, fmt::format(fmt::runtime(ngettext("Staff ({:d} charge)", "Staff ({:d} charges)", charges)), charges)); } break; default: { - int mana = GetManaAmount(player, sn) >> 6; - int lvl = player.GetSpellLevel(sn); + const int mana = GetManaAmount(player, sn) >> 6; + const int lvl = player.GetSpellLevel(sn); PrintSBookStr(out, line0, fmt::format(fmt::runtime(pgettext(/* TRANSLATORS: UI constraints, keep short please.*/ "spellbook", "Level {:d}")), lvl), UiFlags::AlignRight); - if (lvl == 0) { - PrintSBookStr(out, line1, _("Unusable"), UiFlags::AlignRight); - } else { - if (sn != SpellID::BoneSpirit) { - int min; - int max; - GetDamageAmt(sn, &min, &max); - if (min != -1) { - if (sn == SpellID::Healing || sn == SpellID::HealOther) { - PrintSBookStr(out, line1, fmt::format(fmt::runtime(_(/* TRANSLATORS: UI constraints, keep short please.*/ "Heals: {:d} - {:d}")), min, max), UiFlags::AlignRight); - } else { - PrintSBookStr(out, line1, fmt::format(fmt::runtime(_(/* TRANSLATORS: UI constraints, keep short please.*/ "Damage: {:d} - {:d}")), min, max), UiFlags::AlignRight); - } - } - } else { - PrintSBookStr(out, line1, _(/* TRANSLATORS: UI constraints, keep short please.*/ "Dmg: 1/3 target hp"), UiFlags::AlignRight); - } - PrintSBookStr(out, line1, fmt::format(fmt::runtime(pgettext(/* TRANSLATORS: UI constraints, keep short please.*/ "spellbook", "Mana: {:d}")), mana)); + if (const StringOrView text = GetSpellPowerText(sn, lvl); !text.empty()) { + PrintSBookStr(out, line1, text, UiFlags::AlignRight); } + PrintSBookStr(out, line1, fmt::format(fmt::runtime(pgettext(/* TRANSLATORS: UI constraints, keep short please.*/ "spellbook", "Mana: {:d}")), mana)); } break; } } @@ -220,17 +230,17 @@ void CheckSBook() // The width of the panel excluding the border is 305 pixels. This does not cleanly divide by 4 meaning Diablo tabs // end up with an extra pixel somewhere around the buttons. Vanilla Diablo had the buttons left-aligned, devilutionX // instead justifies the buttons and puts the gap between buttons 2/3. See DrawSpellBook - const int TabWidth = gbIsHellfire ? 61 : 76; + const int buttonWidth = SpellBookButtonWidth(); // Tabs are drawn in a row near the bottom of the panel Rectangle tabArea = { GetPanelPosition(UiPanels::Spell, { 7, 320 }), Size { 305, 29 } }; if (tabArea.contains(MousePosition)) { int hitColumn = MousePosition.x - tabArea.position.x; // Clicking on the gutter currently activates tab 3. Could make it do nothing by checking for == here and return early. - if (!gbIsHellfire && hitColumn > TabWidth * 2) { + if (!gbIsHellfire && hitColumn > buttonWidth * 2) { // Subtract 1 pixel to account for the gutter between buttons 2/3 hitColumn--; } - sbooktab = hitColumn / TabWidth; + sbooktab = hitColumn / buttonWidth; } } diff --git a/Source/panels/spell_icons.cpp b/Source/panels/spell_icons.cpp index 6d65a063ee4..b1b0e9dea2a 100644 --- a/Source/panels/spell_icons.cpp +++ b/Source/panels/spell_icons.cpp @@ -1,6 +1,7 @@ #include "panels/spell_icons.hpp" #include +#include #include "engine.h" #include "engine/load_cel.hpp" @@ -8,7 +9,6 @@ #include "engine/palette.h" #include "engine/render/clx_render.hpp" #include "init.h" -#include "utils/stdcompat/optional.hpp" namespace devilution { @@ -25,59 +25,61 @@ OptionalOwnedClxSpriteList LargeSpellIcons; uint8_t SplTransTbl[256]; /** Maps from SpellID to spelicon.cel frame number. */ -const uint8_t SpellITbl[] = { - 26, - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 27, - 12, - 11, - 17, - 15, - 13, - 17, - 18, - 10, - 19, - 14, - 20, - 22, - 23, - 24, - 21, - 25, - 28, - 36, - 37, - 38, - 41, - 40, - 39, - 9, - 35, - 29, - 50, - 50, - 49, - 45, - 46, - 42, - 44, - 47, - 48, - 43, - 34, - 34, - 34, - 34, - 34, +const SpellIcon SpellITbl[] = { + // clang-format off +/* SpellID::Null */ SpellIcon::Empty, +/* SpellID::Firebolt */ SpellIcon::Firebolt, +/* SpellID::Healing */ SpellIcon::Healing, +/* SpellID::Lightning */ SpellIcon::Lightning, +/* SpellID::Flash */ SpellIcon::Flash, +/* SpellID::Identify */ SpellIcon::Identify, +/* SpellID::FireWall */ SpellIcon::FireWall, +/* SpellID::TownPortal */ SpellIcon::TownPortal, +/* SpellID::StoneCurse */ SpellIcon::StoneCurse, +/* SpellID::Infravision */ SpellIcon::Infravision, +/* SpellID::Phasing */ SpellIcon::Phasing, +/* SpellID::ManaShield */ SpellIcon::ManaShield, +/* SpellID::Fireball */ SpellIcon::Fireball, +/* SpellID::Guardian */ SpellIcon::DoomSerpents, +/* SpellID::ChainLightning */ SpellIcon::ChainLightning, +/* SpellID::FlameWave */ SpellIcon::FlameWave, +/* SpellID::DoomSerpents */ SpellIcon::DoomSerpents, +/* SpellID::BloodRitual */ SpellIcon::BloodRitual, +/* SpellID::Nova */ SpellIcon::Nova, +/* SpellID::Invisibility */ SpellIcon::Invisibility, +/* SpellID::Inferno */ SpellIcon::Inferno, +/* SpellID::Golem */ SpellIcon::Golem, +/* SpellID::Rage */ SpellIcon::BloodBoil, +/* SpellID::Teleport */ SpellIcon::Teleport, +/* SpellID::Apocalypse */ SpellIcon::Apocalypse, +/* SpellID::Etherealize */ SpellIcon::Etherealize, +/* SpellID::ItemRepair */ SpellIcon::ItemRepair, +/* SpellID::StaffRecharge */ SpellIcon::StaffRecharge, +/* SpellID::TrapDisarm */ SpellIcon::TrapDisarm, +/* SpellID::Elemental */ SpellIcon::Elemental, +/* SpellID::ChargedBolt */ SpellIcon::ChargedBolt, +/* SpellID::HolyBolt */ SpellIcon::HolyBolt, +/* SpellID::Resurrect */ SpellIcon::Resurrect, +/* SpellID::Telekinesis */ SpellIcon::Telekinesis, +/* SpellID::HealOther */ SpellIcon::HealOther, +/* SpellID::BloodStar */ SpellIcon::BloodStar, +/* SpellID::BoneSpirit */ SpellIcon::BoneSpirit, +/* SpellID::Mana */ SpellIcon::Mana, +/* SpellID::Magi */ SpellIcon::Mana, +/* SpellID::Jester */ SpellIcon::Jester, +/* SpellID::LightningWall */ SpellIcon::LightningWall, +/* SpellID::Immolation */ SpellIcon::Immolation, +/* SpellID::Warp */ SpellIcon::Warp, +/* SpellID::Reflect */ SpellIcon::Reflect, +/* SpellID::Berserk */ SpellIcon::Berserk, +/* SpellID::RingOfFire */ SpellIcon::RingOfFire, +/* SpellID::Search */ SpellIcon::Search, +/* SpellID::RuneOfFire */ SpellIcon::PentaStar, +/* SpellID::RuneOfLight */ SpellIcon::PentaStar, +/* SpellID::RuneOfNova */ SpellIcon::PentaStar, +/* SpellID::RuneOfImmolation */ SpellIcon::PentaStar, +/* SpellID::RuneOfStone */ SpellIcon::PentaStar, + // clang-format on }; } // namespace @@ -128,12 +130,17 @@ void FreeSmallSpellIcons() SmallSpellIcons = std::nullopt; } +uint8_t GetSpellIconFrame(SpellID spell) +{ + return static_cast(SpellITbl[static_cast(spell)]); +} + void DrawLargeSpellIcon(const Surface &out, Point position, SpellID spell) { #ifdef UNPACKED_MPQS ClxDrawTRN(out, position, (*LargeSpellIconsBackground)[0], SplTransTbl); #endif - ClxDrawTRN(out, position, (*LargeSpellIcons)[SpellITbl[static_cast(spell)]], SplTransTbl); + ClxDrawTRN(out, position, (*LargeSpellIcons)[GetSpellIconFrame(spell)], SplTransTbl); } void DrawSmallSpellIcon(const Surface &out, Point position, SpellID spell) @@ -141,7 +148,7 @@ void DrawSmallSpellIcon(const Surface &out, Point position, SpellID spell) #ifdef UNPACKED_MPQS ClxDrawTRN(out, position, (*SmallSpellIconsBackground)[0], SplTransTbl); #endif - ClxDrawTRN(out, position, (*SmallSpellIcons)[SpellITbl[static_cast(spell)]], SplTransTbl); + ClxDrawTRN(out, position, (*SmallSpellIcons)[GetSpellIconFrame(spell)], SplTransTbl); } void DrawLargeSpellIconBorder(const Surface &out, Point position, uint8_t color) diff --git a/Source/panels/spell_icons.hpp b/Source/panels/spell_icons.hpp index d7d154150f8..3a303c0c04d 100644 --- a/Source/panels/spell_icons.hpp +++ b/Source/panels/spell_icons.hpp @@ -11,6 +11,60 @@ namespace devilution { +enum class SpellIcon : uint8_t { + Firebolt, + Healing, + Lightning, + Flash, + Identify, + FireWall, + TownPortal, + StoneCurse, + Infravision, + HealOther, + Nova, + Fireball, + ManaShield, + FlameWave, + Inferno, + ChainLightning, + Sentinel, // unused + DoomSerpents, + BloodRitual, // unused + Invisibility, // unused + Golem, + Etherealize, + BloodBoil, + Teleport, + Apocalypse, + ItemRepair, + Empty, + Phasing, + StaffRecharge, + BoneSpirit, + RedSkull, // unused + Pentagram, // unused + FireCloud, // unused + LongHorn, // unused + PentaStar, // unused + BloodStar, + TrapDisarm, + Elemental, + ChargedBolt, + Telekinesis, + Resurrect, + HolyBolt, + Warp, + Search, + Reflect, + LightningWall, + Immolation, + Berserk, + RingOfFire, + Jester, + Mana, +}; + /** * Draw a large (56x56) spell icon onto the given buffer. * diff --git a/Source/panels/spell_list.cpp b/Source/panels/spell_list.cpp index 162d4dcd411..26da82c5452 100644 --- a/Source/panels/spell_list.cpp +++ b/Source/panels/spell_list.cpp @@ -15,6 +15,7 @@ #include "panels/spell_icons.hpp" #include "player.h" #include "spells.h" +#include "utils/algorithm/container.hpp" #include "utils/language.h" #include "utils/str_cat.hpp" #include "utils/utf8.hpp" @@ -25,7 +26,7 @@ namespace devilution { namespace { -void PrintSBookSpellType(const Surface &out, Point position, string_view text, uint8_t rectColorIndex) +void PrintSBookSpellType(const Surface &out, Point position, std::string_view text, uint8_t rectColorIndex) { DrawLargeSpellIconBorder(out, position, rectColorIndex); @@ -33,16 +34,16 @@ void PrintSBookSpellType(const Surface &out, Point position, string_view text, u position += Displacement { SPLICONLENGTH / 2 - GetLineWidth(text) / 2, (IsSmallFontTall() ? -19 : -15) }; // Then draw the text over the top - DrawString(out, text, position, UiFlags::ColorWhite | UiFlags::Outlined); + DrawString(out, text, position, { .flags = UiFlags::ColorWhite | UiFlags::Outlined }); } -void PrintSBookHotkey(const Surface &out, Point position, const string_view text) +void PrintSBookHotkey(const Surface &out, Point position, const std::string_view text) { // Align the hot key text with the top-right corner of the spell icon position += Displacement { SPLICONLENGTH - (GetLineWidth(text.data()) + 5), 5 - SPLICONLENGTH }; // Then draw the text over the top - DrawString(out, text, position, UiFlags::ColorWhite | UiFlags::Outlined); + DrawString(out, text, position, { .flags = UiFlags::ColorWhite | UiFlags::Outlined }); } bool GetSpellListSelection(SpellID &pSpell, SpellType &pSplType) @@ -64,7 +65,7 @@ bool GetSpellListSelection(SpellID &pSpell, SpellType &pSplType) return false; } -std::optional GetHotkeyName(SpellID spellId, SpellType spellType, bool useShortName = false) +std::optional GetHotkeyName(SpellID spellId, SpellType spellType, bool useShortName = false) { Player &myPlayer = *MyPlayer; for (size_t t = 0; t < NumHotkeys; t++) { @@ -106,16 +107,16 @@ void DrawSpell(const Surface &out) const Point position = GetMainPanel().position + Displacement { 565, 119 }; DrawLargeSpellIcon(out, position, spl); - std::optional hotkeyName = GetHotkeyName(spl, myPlayer._pRSplType, true); + std::optional hotkeyName = GetHotkeyName(spl, myPlayer._pRSplType, true); if (hotkeyName) PrintSBookHotkey(out, position, *hotkeyName); } void DrawSpellList(const Surface &out) { - InfoString = {}; + InfoString = StringOrView {}; - Player &myPlayer = *MyPlayer; + const Player &myPlayer = *MyPlayer; for (auto &spellListItem : GetSpellListItems()) { const SpellID spellId = spellListItem.id; @@ -134,7 +135,7 @@ void DrawSpellList(const Surface &out) SetSpellTrans(transType); DrawLargeSpellIcon(out, spellListItem.location, spellId); - std::optional shortHotkeyName = GetHotkeyName(spellId, spellListItem.type, true); + std::optional shortHotkeyName = GetHotkeyName(spellId, spellListItem.type, true); if (shortHotkeyName) PrintSBookHotkey(out, spellListItem.location, *shortHotkeyName); @@ -170,8 +171,7 @@ void DrawSpellList(const Surface &out) } PrintSBookSpellType(out, spellListItem.location, _("Scroll"), spellColor); InfoString = fmt::format(fmt::runtime(_("Scroll of {:s}")), pgettext("spell", spellDataItem.sNameText)); - const InventoryAndBeltPlayerItemsRange items { myPlayer }; - const int scrollCount = std::count_if(items.begin(), items.end(), [spellId](const Item &item) { + const int scrollCount = c_count_if(InventoryAndBeltPlayerItemsRange { myPlayer }, [spellId](const Item &item) { return item.isScrollOf(spellId); }); AddPanelString(fmt::format(fmt::runtime(ngettext("{:d} Scroll", "{:d} Scrolls", scrollCount)), scrollCount)); @@ -188,7 +188,7 @@ void DrawSpellList(const Surface &out) case SpellType::Invalid: break; } - std::optional fullHotkeyName = GetHotkeyName(spellId, spellListItem.type); + std::optional fullHotkeyName = GetHotkeyName(spellId, spellListItem.type); if (fullHotkeyName) { AddPanelString(fmt::format(fmt::runtime(_("Spell Hotkey {:s}")), *fullHotkeyName)); } @@ -274,6 +274,13 @@ void SetSpeedSpell(size_t slot) return; } Player &myPlayer = *MyPlayer; + + if (myPlayer._pSplHotKey[slot] == pSpell && myPlayer._pSplTHotKey[slot] == pSplType) { + // Unset spell hotkey + myPlayer._pSplHotKey[slot] = SpellID::Invalid; + return; + } + for (size_t i = 0; i < NumHotkeys; ++i) { if (myPlayer._pSplHotKey[i] == pSpell && myPlayer._pSplTHotKey[i] == pSplType) myPlayer._pSplHotKey[i] = SpellID::Invalid; @@ -282,7 +289,7 @@ void SetSpeedSpell(size_t slot) myPlayer._pSplTHotKey[slot] = pSplType; } -void ToggleSpell(size_t slot) +bool IsValidSpeedSpell(size_t slot) { uint64_t spells; @@ -290,7 +297,7 @@ void ToggleSpell(size_t slot) const SpellID spellId = myPlayer._pSplHotKey[slot]; if (!IsValidSpell(spellId)) { - return; + return false; } switch (myPlayer._pSplTHotKey[slot]) { @@ -307,11 +314,17 @@ void ToggleSpell(size_t slot) spells = myPlayer._pISpells; break; case SpellType::Invalid: - return; + return false; } - if ((spells & GetSpellBitmask(spellId)) != 0) { - myPlayer._pRSpell = spellId; + return (spells & GetSpellBitmask(spellId)) != 0; +} + +void ToggleSpell(size_t slot) +{ + if (IsValidSpeedSpell(slot)) { + Player &myPlayer = *MyPlayer; + myPlayer._pRSpell = myPlayer._pSplHotKey[slot]; myPlayer._pRSplType = myPlayer._pSplTHotKey[slot]; RedrawEverything(); } diff --git a/Source/panels/spell_list.hpp b/Source/panels/spell_list.hpp index 9491639f6bd..8dba57c0229 100644 --- a/Source/panels/spell_list.hpp +++ b/Source/panels/spell_list.hpp @@ -16,11 +16,16 @@ struct SpellListItem { bool isSelected; }; +/** + * @brief draws the current right mouse button spell. + * @param out screen buffer representing the main UI panel + */ void DrawSpell(const Surface &out); void DrawSpellList(const Surface &out); std::vector GetSpellListItems(); void SetSpell(); void SetSpeedSpell(size_t slot); +bool IsValidSpeedSpell(size_t slot); void ToggleSpell(size_t slot); /** diff --git a/Source/pfile.cpp b/Source/pfile.cpp index 1c169cc3943..f2baa6c75ce 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -24,9 +25,9 @@ #include "utils/endian.hpp" #include "utils/file_util.h" #include "utils/language.h" +#include "utils/parse_int.hpp" #include "utils/paths.h" -#include "utils/stdcompat/abs.hpp" -#include "utils/stdcompat/string_view.hpp" +#include "utils/stdcompat/filesystem.hpp" #include "utils/str_cat.hpp" #include "utils/str_split.hpp" #include "utils/utf8.hpp" @@ -51,7 +52,7 @@ namespace { /** List of character names for the character selection screen. */ char hero_names[MAX_CHARACTERS][PlayerNameLength]; -std::string GetSavePath(uint32_t saveNum, string_view savePrefix = {}) +std::string GetSavePath(uint32_t saveNum, std::string_view savePrefix = {}) { return StrCat(paths::PrefPath(), savePrefix, gbIsSpawn @@ -78,7 +79,7 @@ std::string GetStashSavePath() ); } -bool GetSaveNames(uint8_t index, string_view prefix, char *out) +bool GetSaveNames(uint8_t index, std::string_view prefix, char *out) { char suf; if (index < giNumberOfLevels) @@ -143,7 +144,7 @@ bool ReadHero(SaveReader &archive, PlayerPack *pPack) void EncodeHero(SaveWriter &saveWriter, const PlayerPack *pack) { size_t packedLen = codec_get_encoded_len(sizeof(*pack)); - std::unique_ptr packed { new byte[packedLen] }; + std::unique_ptr packed { new std::byte[packedLen] }; memcpy(packed.get(), pack, sizeof(*pack)); codec_encode(packed.get(), sizeof(*pack), packedLen, pfile_get_password()); @@ -164,14 +165,24 @@ SaveWriter GetStashWriter() void CopySaveFile(uint32_t saveNum, std::string targetPath) { const std::string savePath = GetSavePath(saveNum); +#if defined(UNPACKED_SAVES) +#ifdef DVL_NO_FILESYSTEM +#error "UNPACKED_SAVES requires either DISABLE_DEMOMODE or C++17 " +#endif + CreateDir(targetPath.c_str()); + for (const std::filesystem::directory_entry &entry : std::filesystem::directory_iterator(savePath)) { + CopyFileOverwrite(entry.path().string().c_str(), (targetPath + entry.path().filename().string()).c_str()); + } +#else CopyFileOverwrite(savePath.c_str(), targetPath.c_str()); +#endif } #endif void Game2UiPlayer(const Player &player, _uiheroinfo *heroinfo, bool bHasSaveFile) { CopyUtf8(heroinfo->name, player._pName, sizeof(heroinfo->name)); - heroinfo->level = player._pLevel; + heroinfo->level = player.getCharacterLevel(); heroinfo->heroclass = player._pClass; heroinfo->strength = player._pStrength; heroinfo->magic = player._pMagic; @@ -232,7 +243,7 @@ std::optional CreateSaveReader(std::string &&path) #ifndef DISABLE_DEMOMODE struct CompareInfo { - std::unique_ptr &data; + std::unique_ptr &data; size_t currentPosition; size_t size; bool isTownLevel; @@ -255,14 +266,14 @@ struct CompareCounter { } }; -inline bool string_ends_with(string_view value, string_view suffix) +inline bool string_ends_with(std::string_view value, std::string_view suffix) { if (suffix.size() > value.size()) return false; return std::equal(suffix.rbegin(), suffix.rend(), value.rbegin()); } -void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInfo &compareInfoReference, CompareInfo &compareInfoActual, std::unordered_map &foundDiffs) +void CreateDetailDiffs(std::string_view prefix, std::string_view memoryMapFile, CompareInfo &compareInfoReference, CompareInfo &compareInfoActual, std::unordered_map &foundDiffs) { // Note: Detail diffs are currently only supported in unit tests std::string memoryMapFileAssetName = StrCat(paths::BasePath(), "/test/fixtures/memory_map/", memoryMapFile, ".txt"); @@ -273,10 +284,16 @@ void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInf return; } - size_t readBytes = SDL_RWsize(handle); - std::unique_ptr memoryMapFileData { new byte[readBytes] }; + size_t readBytes = static_cast(SDL_RWsize(handle)); + std::unique_ptr memoryMapFileData { new std::byte[readBytes] }; +#if SDL_VERSION_ATLEAST(2, 0, 0) SDL_RWread(handle, memoryMapFileData.get(), readBytes, 1); - const string_view buffer(reinterpret_cast(memoryMapFileData.get()), readBytes); +#else + SDL_RWread(handle, memoryMapFileData.get(), static_cast(readBytes), 1); +#endif + SDL_RWclose(handle); + + const std::string_view buffer(reinterpret_cast(memoryMapFileData.get()), readBytes); std::unordered_map counter; @@ -284,8 +301,10 @@ void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInf auto it = counter.find(counterAsString); if (it != counter.end()) return it->second; - int countFromMapFile = std::stoi(counterAsString); - return CompareCounter { countFromMapFile, countFromMapFile }; + const ParseIntResult countFromMapFile = ParseInt(counterAsString); + if (!countFromMapFile.has_value()) + app_fatal(StrCat("Failed to parse ", counterAsString, " as int")); + return CompareCounter { countFromMapFile.value(), countFromMapFile.value() }; }; auto addDiff = [&](const std::string &diffKey) { auto it = foundDiffs.find(diffKey); @@ -298,9 +317,9 @@ void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInf auto compareBytes = [&](size_t countBytes) { if (compareInfoReference.dataExists && compareInfoReference.currentPosition + countBytes > compareInfoReference.size) - app_fatal(StrCat("Comparsion failed. Too less bytes in reference to compare. Location: ", prefix)); + app_fatal(StrCat("Comparison failed. Not enough bytes in reference to compare. Location: ", prefix)); if (compareInfoActual.dataExists && compareInfoActual.currentPosition + countBytes > compareInfoActual.size) - app_fatal(StrCat("Comparsion failed. Too less bytes in actual to compare. Location: ", prefix)); + app_fatal(StrCat("Comparison failed. Not enough bytes in actual to compare. Location: ", prefix)); bool result = true; if (compareInfoReference.dataExists && compareInfoActual.dataExists) result = memcmp(compareInfoReference.data.get() + compareInfoReference.currentPosition, compareInfoActual.data.get() + compareInfoActual.currentPosition, countBytes) == 0; @@ -325,7 +344,7 @@ void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInf return value; }; - for (string_view line : SplitByChar(buffer, '\n')) { + for (std::string_view line : SplitByChar(buffer, '\n')) { if (!line.empty() && line.back() == '\r') line.remove_suffix(1); if (line.empty()) @@ -336,7 +355,7 @@ void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInf if (it == end) continue; - string_view command = *it; + std::string_view command = *it; bool dataExistsReference = compareInfoReference.dataExists; bool dataExistsActual = compareInfoActual.dataExists; @@ -363,7 +382,10 @@ void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInf if (command == "R" || command == "LT" || command == "LC" || command == "LC_LE") { const auto bitsAsString = std::string(*++it); const auto comment = std::string(*++it); - size_t bytes = static_cast(std::stoi(bitsAsString) / 8); + const ParseIntResult parsedBytes = ParseInt(bitsAsString); + if (!parsedBytes.has_value()) + app_fatal(StrCat("Failed to parse ", bitsAsString, " as size_t")); + const size_t bytes = static_cast(parsedBytes.value() / 8); if (command == "LT") { int32_t valueReference = read32BitInt(compareInfoReference, false); @@ -386,10 +408,13 @@ void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInf } else if (command == "M") { const auto countAsString = std::string(*++it); const auto bitsAsString = std::string(*++it); - string_view comment = *++it; + std::string_view comment = *++it; CompareCounter count = getCounter(countAsString); - size_t bytes = static_cast(std::stoi(bitsAsString) / 8); + const ParseIntResult parsedBytes = ParseInt(bitsAsString); + if (!parsedBytes.has_value()) + app_fatal(StrCat("Failed to parse ", bitsAsString, " as size_t")); + const size_t bytes = static_cast(parsedBytes.value() / 8); for (int i = 0; i < count.max(); i++) { count.checkIfDataExists(i, compareInfoReference, compareInfoActual); if (!compareBytes(bytes)) { @@ -465,8 +490,8 @@ HeroCompareResult CompareSaves(const std::string &actualSavePath, const std::str app_fatal(StrCat("Comparsion failed. Uncompared bytes in reference. File: ", compareTarget.fileName)); if (compareInfoActual.currentPosition != fileSizeActual) app_fatal(StrCat("Comparsion failed. Uncompared bytes in actual. File: ", compareTarget.fileName)); - for (auto entry : foundDiffs) { - StrAppend(message, "\nDiff found in ", entry.first, " count: ", entry.second); + for (const auto &[location, count] : foundDiffs) { + StrAppend(message, "\nDiff found in ", location, " count: ", count); } } return { compareResult ? HeroCompareResult::Same : HeroCompareResult::Difference, message }; @@ -490,12 +515,23 @@ void pfile_write_hero(SaveWriter &saveWriter, bool writeGameData) } } +void RemoveAllInvalidItems(Player &player) +{ + for (int i = 0; i < NUM_INVLOC; i++) + RemoveInvalidItem(player.InvBody[i]); + for (int i = 0; i < player._pNumInv; i++) + RemoveInvalidItem(player.InvList[i]); + for (int i = 0; i < MaxBeltItems; i++) + RemoveInvalidItem(player.SpdList[i]); + RemoveEmptyInventory(player); +} + } // namespace #ifdef UNPACKED_SAVES -std::unique_ptr SaveReader::ReadFile(const char *filename, std::size_t &fileSize, int32_t &error) +std::unique_ptr SaveReader::ReadFile(const char *filename, std::size_t &fileSize, int32_t &error) { - std::unique_ptr result; + std::unique_ptr result; error = 0; const std::string path = dir_ + filename; uintmax_t size; @@ -509,7 +545,7 @@ std::unique_ptr SaveReader::ReadFile(const char *filename, std::size_t & error = 1; return nullptr; } - result.reset(new byte[size]); + result.reset(new std::byte[size]); if (std::fread(result.get(), size, 1, file) != 1) { std::fclose(file); error = 1; @@ -519,7 +555,7 @@ std::unique_ptr SaveReader::ReadFile(const char *filename, std::size_t & return result; } -bool SaveWriter::WriteFile(const char *filename, const byte *data, size_t size) +bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t size) { const std::string path = dir_ + filename; FILE *file = OpenFile(path.c_str(), "wb"); @@ -554,12 +590,12 @@ std::optional OpenStashArchive() return CreateSaveReader(GetStashSavePath()); } -std::unique_ptr ReadArchive(SaveReader &archive, const char *pszName, size_t *pdwLen) +std::unique_ptr ReadArchive(SaveReader &archive, const char *pszName, size_t *pdwLen) { int32_t error; std::size_t length; - std::unique_ptr result = archive.ReadFile(pszName, length, error); + std::unique_ptr result = archive.ReadFile(pszName, length, error); if (error != 0) return nullptr; @@ -645,7 +681,7 @@ bool pfile_ui_set_hero_infos(bool (*uiAddHeroInfo)(_uiheroinfo *)) UnPackPlayer(pkplr, player); LoadHeroItems(player); - RemoveEmptyInventory(player); + RemoveAllInvalidItems(player); CalcPlrInv(player, false); Game2UiPlayer(player, &uihero, hasSaveGame); @@ -657,12 +693,13 @@ bool pfile_ui_set_hero_infos(bool (*uiAddHeroInfo)(_uiheroinfo *)) return true; } -void pfile_ui_set_class_stats(unsigned int playerClass, _uidefaultstats *classStats) +void pfile_ui_set_class_stats(HeroClass playerClass, _uidefaultstats *classStats) { - classStats->strength = PlayersData[playerClass].baseStr; - classStats->magic = PlayersData[playerClass].baseMag; - classStats->dexterity = PlayersData[playerClass].baseDex; - classStats->vitality = PlayersData[playerClass].baseVit; + const ClassAttributes &classAttributes = GetClassAttributes(playerClass); + classStats->strength = classAttributes.baseStr; + classStats->magic = classAttributes.baseMag; + classStats->dexterity = classAttributes.baseDex; + classStats->vitality = classAttributes.baseVit; } uint32_t pfile_ui_get_first_unused_save_num() @@ -731,7 +768,7 @@ void pfile_read_player_from_save(uint32_t saveNum, Player &player) UnPackPlayer(pkplr, player); LoadHeroItems(player); - RemoveEmptyInventory(player); + RemoveAllInvalidItems(player); CalcPlrInv(player, false); } diff --git a/Source/pfile.h b/Source/pfile.h index 4f17612ee84..c025f0c9349 100644 --- a/Source/pfile.h +++ b/Source/pfile.h @@ -35,7 +35,7 @@ struct SaveReader { return dir_; } - std::unique_ptr ReadFile(const char *filename, std::size_t &fileSize, int32_t &error); + std::unique_ptr ReadFile(const char *filename, std::size_t &fileSize, int32_t &error); bool HasFile(const char *path) { @@ -52,7 +52,7 @@ struct SaveWriter { { } - bool WriteFile(const char *filename, const byte *data, size_t size); + bool WriteFile(const char *filename, const std::byte *data, size_t size); bool HasFile(const char *path) { @@ -96,7 +96,7 @@ struct HeroCompareResult { std::optional OpenSaveArchive(uint32_t saveNum); std::optional OpenStashArchive(); const char *pfile_get_password(); -std::unique_ptr ReadArchive(SaveReader &archive, const char *pszName, size_t *pdwLen = nullptr); +std::unique_ptr ReadArchive(SaveReader &archive, const char *pszName, size_t *pdwLen = nullptr); void pfile_write_hero(bool writeGameData = false); #ifndef DISABLE_DEMOMODE @@ -116,7 +116,7 @@ HeroCompareResult pfile_compare_hero_demo(int demo, bool logDetails); void sfile_write_stash(); bool pfile_ui_set_hero_infos(bool (*uiAddHeroInfo)(_uiheroinfo *)); -void pfile_ui_set_class_stats(unsigned int playerClass, _uidefaultstats *classStats); +void pfile_ui_set_class_stats(HeroClass playerClass, _uidefaultstats *classStats); uint32_t pfile_ui_get_first_unused_save_num(); bool pfile_ui_save_create(_uiheroinfo *heroinfo); bool pfile_delete_save(_uiheroinfo *heroInfo); @@ -124,7 +124,7 @@ void pfile_read_player_from_save(uint32_t saveNum, Player &player); void pfile_save_level(); void pfile_convert_levels(); void pfile_remove_temp_files(); -std::unique_ptr pfile_read(const char *pszName, size_t *pdwLen); +std::unique_ptr pfile_read(const char *pszName, size_t *pdwLen); void pfile_update(bool forceSave); } // namespace devilution diff --git a/Source/platform/ctr/keyboard.cpp b/Source/platform/ctr/keyboard.cpp index 087bbc65c22..6b1cf26f1c2 100644 --- a/Source/platform/ctr/keyboard.cpp +++ b/Source/platform/ctr/keyboard.cpp @@ -7,8 +7,8 @@ constexpr size_t MAX_TEXT_LENGTH = 255; struct vkbdEvent { - devilution::string_view hintText; - devilution::string_view inText; + std::string_view hintText; + std::string_view inText; char *outText; size_t maxLength; }; @@ -16,7 +16,7 @@ struct vkbdEvent { static vkbdEvent events[16]; static int eventCount = 0; -void ctr_vkbdInput(devilution::string_view hintText, devilution::string_view inText, char *outText, size_t maxLength) +void ctr_vkbdInput(std::string_view hintText, std::string_view inText, char *outText, size_t maxLength) { if (eventCount >= sizeof(events)) return; diff --git a/Source/platform/ctr/keyboard.h b/Source/platform/ctr/keyboard.h index 65bbd93284a..067a3e85e63 100644 --- a/Source/platform/ctr/keyboard.h +++ b/Source/platform/ctr/keyboard.h @@ -1,6 +1,8 @@ -#include <3ds.h> +#pragma once + +#include -#include "utils/stdcompat/string_view.hpp" +#include <3ds.h> /** * @brief Queues a request for user input for the next call to ctr_vkbdFlush() @@ -10,7 +12,7 @@ * @param outText Pointer to a buffer to receive user input * @param maxLength Size of the buffer */ -void ctr_vkbdInput(devilution::string_view title, devilution::string_view inText, char *outText, size_t maxLength); +void ctr_vkbdInput(std::string_view title, std::string_view inText, char *outText, size_t maxLength); /** * @brief Processes pending requests for user input diff --git a/Source/platform/ctr/sockets.cpp b/Source/platform/ctr/sockets.cpp index ce57a5a0275..4bff0f938a8 100644 --- a/Source/platform/ctr/sockets.cpp +++ b/Source/platform/ctr/sockets.cpp @@ -3,6 +3,12 @@ #include #include +// This header must be included before any 3DS code +// because 3DS SDK defines a macro with the same name +// as an fmt template parameter in some versions of fmt. +// See https://github.com/fmtlib/fmt/issues/3632 +#include + #include <3ds.h> #include "utils/log.hpp" diff --git a/Source/platform/locale.cpp b/Source/platform/locale.cpp index c00790e56f8..dba90787dde 100644 --- a/Source/platform/locale.cpp +++ b/Source/platform/locale.cpp @@ -1,9 +1,11 @@ #include "locale.hpp" +#include #include +#include #ifdef __ANDROID__ -#include "SDL.h" +#include #include #elif defined(__vita__) #include @@ -12,7 +14,7 @@ #include #elif defined(__3DS__) #include "platform/ctr/locale.hpp" -#elif defined(_WIN32) && !defined(NXDK) +#elif defined(_WIN32) && !defined(DEVILUTIONX_WINDOWS_NO_WCHAR) // Suppress definitions of `min` and `max` macros by : #define NOMINMAX 1 #define WIN32_LEAN_AND_MEAN @@ -26,14 +28,11 @@ #include #endif -#include "utils/stdcompat/algorithm.hpp" -#include "utils/stdcompat/string_view.hpp" - namespace devilution { namespace { -#if (defined(_WIN32) && WINVER >= 0x0600 && !defined(NXDK)) || (defined(__APPLE__) && defined(USE_COREFOUNDATION)) -std::string IetfToPosix(string_view langCode) +#if (defined(_WIN32) && WINVER >= 0x0600 && !defined(DEVILUTIONX_WINDOWS_NO_WCHAR)) || (defined(__APPLE__) && defined(USE_COREFOUNDATION)) +std::string IetfToPosix(std::string_view langCode) { /* * Handle special case for simplified/traditional Chinese. IETF/BCP-47 specifies that only the script should be @@ -113,7 +112,7 @@ std::vector GetLocales() // use default #elif defined(__3DS__) locales.push_back(n3ds::GetLocale()); -#elif defined(_WIN32) && !defined(NXDK) +#elif defined(_WIN32) && !defined(DEVILUTIONX_WINDOWS_NO_WCHAR) #if WINVER >= 0x0600 auto wideCharToUtf8 = [](PWSTR wideString) { // WideCharToMultiByte potentially leaves the buffer unterminated, default initialise here as a workaround @@ -179,10 +178,10 @@ std::vector GetLocales() CFRelease(languages); #else - constexpr auto svOrEmpty = [](const char *cString) -> string_view { + constexpr auto svOrEmpty = [](const char *cString) -> std::string_view { return cString != nullptr ? cString : ""; }; - string_view languages = svOrEmpty(std::getenv("LANGUAGE")); + std::string_view languages = svOrEmpty(std::getenv("LANGUAGE")); if (languages.empty()) { languages = svOrEmpty(std::getenv("LANG")); if (languages.empty()) { diff --git a/Source/platform/switch/keyboard.cpp b/Source/platform/switch/keyboard.cpp index 12ca59f288d..e6a38cc2f09 100644 --- a/Source/platform/switch/keyboard.cpp +++ b/Source/platform/switch/keyboard.cpp @@ -8,7 +8,7 @@ #include "utils/utf8.hpp" -static void switch_keyboard_get(devilution::string_view guide_text, devilution::string_view initial_text, char *buf, unsigned buf_len) +static void switch_keyboard_get(std::string_view guide_text, std::string_view initial_text, char *buf, unsigned buf_len) { Result rc = 0; @@ -62,7 +62,7 @@ static void switch_create_and_push_sdlkey_event(uint32_t event_type, SDL_Scancod SDL_PushEvent(&event); } -void switch_start_text_input(devilution::string_view guide_text, devilution::string_view initial_text, unsigned max_length) +void switch_start_text_input(std::string_view guide_text, std::string_view initial_text, unsigned max_length) { char text[max_length] = { '\0' }; switch_keyboard_get(guide_text, initial_text, text, sizeof(text)); diff --git a/Source/platform/switch/keyboard.h b/Source/platform/switch/keyboard.h index 97269286531..cb3917abb77 100644 --- a/Source/platform/switch/keyboard.h +++ b/Source/platform/switch/keyboard.h @@ -1,6 +1,6 @@ #pragma once -#include "utils/stdcompat/string_view.hpp" +#include /** * @brief Prompts the user for text input, pushes the user-provided text to the event queue, then returns @@ -8,4 +8,4 @@ * @param initial_text An optional prefilled value for the input * @param max_length How many bytes of input to accept from the user (certain characters will take multiple bytes) */ -void switch_start_text_input(devilution::string_view guide_text, devilution::string_view initial_text, unsigned max_length); +void switch_start_text_input(std::string_view guide_text, std::string_view initial_text, unsigned max_length); diff --git a/Source/platform/vita/keyboard.cpp b/Source/platform/vita/keyboard.cpp index 325ed5b3c61..14205b572a7 100644 --- a/Source/platform/vita/keyboard.cpp +++ b/Source/platform/vita/keyboard.cpp @@ -83,7 +83,7 @@ static int vita_input_thread(void *ime_buffer) return 0; } -static int vita_keyboard_get(devilution::string_view guide_text, devilution::string_view initial_text, unsigned max_len, SceWChar16 *buf) +static int vita_keyboard_get(std::string_view guide_text, std::string_view initial_text, unsigned max_len, SceWChar16 *buf) { SceWChar16 title[SCE_IME_DIALOG_MAX_TITLE_LENGTH]; SceWChar16 text[SCE_IME_DIALOG_MAX_TEXT_LENGTH]; @@ -116,7 +116,7 @@ static int vita_keyboard_get(devilution::string_view guide_text, devilution::str return 1; } -void vita_start_text_input(devilution::string_view guide_text, devilution::string_view initial_text, unsigned max_length) +void vita_start_text_input(std::string_view guide_text, std::string_view initial_text, unsigned max_length) { SceWChar16 ime_buffer[SCE_IME_DIALOG_MAX_TEXT_LENGTH]; if (vita_keyboard_get(guide_text, initial_text, max_length, ime_buffer)) { diff --git a/Source/platform/vita/keyboard.h b/Source/platform/vita/keyboard.h index 2f837275637..87daa3fd51d 100644 --- a/Source/platform/vita/keyboard.h +++ b/Source/platform/vita/keyboard.h @@ -1,5 +1,5 @@ #pragma once -#include "utils/stdcompat/string_view.hpp" +#include -void vita_start_text_input(devilution::string_view guide_text, devilution::string_view initial_text, unsigned max_length); +void vita_start_text_input(std::string_view guide_text, std::string_view initial_text, unsigned max_length); diff --git a/Source/player.cpp b/Source/player.cpp index 1662a61c8c7..5b93ef5329f 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -4,6 +4,7 @@ * Implementation of player functionality, leveling, actions, creation, loading, etc. */ #include +#include #include #include @@ -36,7 +37,6 @@ #include "objects.h" #include "options.h" #include "player.h" -#include "playerdat.hpp" #include "qol/autopickup.h" #include "qol/floatingnumbers.h" #include "qol/stash.h" @@ -50,7 +50,7 @@ namespace devilution { -size_t MyPlayerId; +uint8_t MyPlayerId; Player *MyPlayer; std::vector Players; Player *InspectPlayer; @@ -77,17 +77,16 @@ void UpdatePlayerLightOffset(Player &player) void WalkNorthwards(Player &player, const DirectionSettings &walkParams) { - dPlayer[player.position.future.x][player.position.future.y] = -(player.getId() + 1); + player.occupyTile(player.position.future, true); player.position.temp = player.position.tile + walkParams.tileAdd; } void WalkSouthwards(Player &player, const DirectionSettings & /*walkParams*/) { - const size_t playerId = player.getId(); - dPlayer[player.position.tile.x][player.position.tile.y] = -(playerId + 1); player.position.temp = player.position.tile; player.position.tile = player.position.future; // Move player to the next tile to maintain correct render order - dPlayer[player.position.tile.x][player.position.tile.y] = playerId + 1; + player.occupyTile(player.position.temp, true); + player.occupyTile(player.position.tile, false); // BUGFIX: missing `if (leveltype != DTYPE_TOWN) {` for call to ChangeLightXY and PM_ChangeLightOff. ChangeLightXY(player.lightId, player.position.tile); UpdatePlayerLightOffset(player); @@ -97,9 +96,8 @@ void WalkSideways(Player &player, const DirectionSettings &walkParams) { Point const nextPosition = player.position.tile + walkParams.map; - const size_t playerId = player.getId(); - dPlayer[player.position.tile.x][player.position.tile.y] = -(playerId + 1); - dPlayer[player.position.future.x][player.position.future.y] = playerId + 1; + player.occupyTile(player.position.tile, true); + player.occupyTile(player.position.future, false); if (leveltype != DTYPE_TOWN) { ChangeLightXY(player.lightId, nextPosition); @@ -415,7 +413,7 @@ void InitLevelChange(Player &player) FixPlrWalkTags(player); SetPlayerOld(player); if (&player == MyPlayer) { - dPlayer[player.position.tile.x][player.position.tile.y] = player.getId() + 1; + player.occupyTile(player.position.tile, false); } else { player._pLvlVisited[player.plrlevel] = true; } @@ -438,7 +436,7 @@ bool DoWalk(Player &player, int variant) if (*sgOptions.Audio.walkingSound && (leveltype != DTYPE_TOWN || sgGameInitInfo.bRunInTown == 0)) { if (player.AnimInfo.currentFrame == 0 || player.AnimInfo.currentFrame == 4) { - PlaySfxLoc(PS_WALK1, player.position.tile); + PlaySfxLoc(SfxID::Walk, player.position.tile); } } @@ -453,7 +451,7 @@ bool DoWalk(Player &player, int variant) case PM_WALK_NORTHWARDS: dPlayer[player.position.tile.x][player.position.tile.y] = 0; player.position.tile = player.position.temp; - dPlayer[player.position.tile.x][player.position.tile.y] = player.getId() + 1; + player.occupyTile(player.position.tile, false); break; case PM_WALK_SOUTHWARDS: dPlayer[player.position.temp.x][player.position.temp.y] = 0; @@ -462,7 +460,7 @@ bool DoWalk(Player &player, int variant) dPlayer[player.position.tile.x][player.position.tile.y] = 0; player.position.tile = player.position.temp; // dPlayer is set here for backwards comparability, without it the player would be invisible if loaded from a vanilla save. - dPlayer[player.position.tile.x][player.position.tile.y] = player.getId() + 1; + player.occupyTile(player.position.tile, false); break; } @@ -577,10 +575,10 @@ bool PlrHitMonst(Player &player, Monster &monster, bool adjacentDamage = false) return false; if (adjacentDamage) { - if (player._pLevel > 20) + if (player.getCharacterLevel() > 20) hper -= 30; else - hper -= (35 - player._pLevel) * 2; + hper -= (35 - player.getCharacterLevel()) * 2; } int hit = GenerateRnd(100); @@ -589,7 +587,7 @@ bool PlrHitMonst(Player &player, Monster &monster, bool adjacentDamage = false) } hper += player.GetMeleePiercingToHit() - player.CalculateArmorPierce(monster.armorClass, true); - hper = clamp(hper, 5, 95); + hper = std::clamp(hper, 5, 95); if (monster.tryLiftGargoyle()) return true; @@ -603,7 +601,7 @@ bool PlrHitMonst(Player &player, Monster &monster, bool adjacentDamage = false) if (gbIsHellfire && HasAllOf(player._pIFlags, ItemSpecialEffect::FireDamage | ItemSpecialEffect::LightningDamage)) { int midam = player._pIFMinDam + GenerateRnd(player._pIFMaxDam - player._pIFMinDam); - AddMissile(player.position.tile, player.position.temp, player._pdir, MissileID::SpectralArrow, TARGET_MONSTERS, player.getId(), midam, 0); + AddMissile(player.position.tile, player.position.temp, player._pdir, MissileID::SpectralArrow, TARGET_MONSTERS, player, midam, 0); } int mind = player._pIMinDam; int maxd = player._pIMaxDam; @@ -613,7 +611,7 @@ bool PlrHitMonst(Player &player, Monster &monster, bool adjacentDamage = false) int dam2 = dam << 6; dam += player._pDamageMod; if (player._pClass == HeroClass::Warrior || player._pClass == HeroClass::Barbarian) { - if (GenerateRnd(100) < player._pLevel) { + if (GenerateRnd(100) < player.getCharacterLevel()) { dam *= 2; } } @@ -753,15 +751,15 @@ bool PlrHitPlr(Player &attacker, Player &target) int hit = GenerateRnd(100); int hper = attacker.GetMeleeToHit() - target.GetArmor(); - hper = clamp(hper, 5, 95); + hper = std::clamp(hper, 5, 95); int blk = 100; if ((target._pmode == PM_STAND || target._pmode == PM_ATTACK) && target._pBlockFlag) { blk = GenerateRnd(100); } - int blkper = target.GetBlockChance() - (attacker._pLevel * 2); - blkper = clamp(blkper, 0, 100); + int blkper = target.GetBlockChance() - (attacker.getCharacterLevel() * 2); + blkper = std::clamp(blkper, 0, 100); if (hit >= hper) { return false; @@ -780,7 +778,7 @@ bool PlrHitPlr(Player &attacker, Player &target) dam += attacker._pIBonusDamMod + attacker._pDamageMod; if (attacker._pClass == HeroClass::Warrior || attacker._pClass == HeroClass::Barbarian) { - if (GenerateRnd(100) < attacker._pLevel) { + if (GenerateRnd(100) < attacker.getCharacterLevel()) { dam *= 2; } } @@ -798,7 +796,7 @@ bool PlrHitPlr(Player &attacker, Player &target) RedrawComponent(PanelDrawComponent::Health); } if (&attacker == MyPlayer) { - NetSendCmdDamage(true, target.getId(), skdam, DamageType::Physical); + NetSendCmdDamage(true, target, skdam, DamageType::Physical); } StartPlrHit(target, skdam, false); @@ -818,7 +816,7 @@ bool PlrHitObj(const Player &player, Object &targetObject) bool DoAttack(Player &player) { if (player.AnimInfo.currentFrame == player._pAFNum - 2) { - PlaySfxLoc(PS_SWING, player.position.tile); + PlaySfxLoc(SfxID::Swing, player.position.tile); } bool didhit = false; @@ -835,12 +833,11 @@ bool DoAttack(Player &player) } if (!gbIsHellfire || !HasAllOf(player._pIFlags, ItemSpecialEffect::FireDamage | ItemSpecialEffect::LightningDamage)) { - const size_t playerId = player.getId(); if (HasAnyOf(player._pIFlags, ItemSpecialEffect::FireDamage)) { - AddMissile(position, { 1, 0 }, Direction::South, MissileID::WeaponExplosion, TARGET_MONSTERS, playerId, 0, 0); + AddMissile(position, { 1, 0 }, Direction::South, MissileID::WeaponExplosion, TARGET_MONSTERS, player, 0, 0); } if (HasAnyOf(player._pIFlags, ItemSpecialEffect::LightningDamage)) { - AddMissile(position, { 2, 0 }, Direction::South, MissileID::WeaponExplosion, TARGET_MONSTERS, playerId, 0, 0); + AddMissile(position, { 2, 0 }, Direction::South, MissileID::WeaponExplosion, TARGET_MONSTERS, player, 0, 0); } } @@ -854,17 +851,7 @@ bool DoAttack(Player &player) didhit = PlrHitObj(player, *object); } } - if ((player._pClass == HeroClass::Monk - && (player.InvBody[INVLOC_HAND_LEFT]._itype == ItemType::Staff || player.InvBody[INVLOC_HAND_RIGHT]._itype == ItemType::Staff)) - || (player._pClass == HeroClass::Bard - && player.InvBody[INVLOC_HAND_LEFT]._itype == ItemType::Sword && player.InvBody[INVLOC_HAND_RIGHT]._itype == ItemType::Sword) - || (player._pClass == HeroClass::Barbarian - && (player.InvBody[INVLOC_HAND_LEFT]._itype == ItemType::Axe || player.InvBody[INVLOC_HAND_RIGHT]._itype == ItemType::Axe - || (((player.InvBody[INVLOC_HAND_LEFT]._itype == ItemType::Mace && player.InvBody[INVLOC_HAND_LEFT]._iLoc == ILOC_TWOHAND) - || (player.InvBody[INVLOC_HAND_RIGHT]._itype == ItemType::Mace && player.InvBody[INVLOC_HAND_RIGHT]._iLoc == ILOC_TWOHAND) - || (player.InvBody[INVLOC_HAND_LEFT]._itype == ItemType::Sword && player.InvBody[INVLOC_HAND_LEFT]._iLoc == ILOC_TWOHAND) - || (player.InvBody[INVLOC_HAND_RIGHT]._itype == ItemType::Sword && player.InvBody[INVLOC_HAND_RIGHT]._iLoc == ILOC_TWOHAND)) - && !(player.InvBody[INVLOC_HAND_LEFT]._itype == ItemType::Shield || player.InvBody[INVLOC_HAND_RIGHT]._itype == ItemType::Shield))))) { + if (player.CanCleave()) { // playing as a class/weapon with cleave position = player.position.tile + Right(player._pdir); monster = FindMonsterAtPosition(position); @@ -943,12 +930,12 @@ bool DoRangeAttack(Player &player) player._pdir, mistype, TARGET_MONSTERS, - player.getId(), + player, dmg, 0); if (arrow == 0 && mistype != MissileID::SpectralArrow) { - PlaySfxLoc(arrows != 1 ? IS_STING1 : PS_BFIRE, player.position.tile); + PlaySfxLoc(arrows != 1 ? SfxID::ShootBow2 : SfxID::ShootBow, player.position.tile); } if (DamageWeapon(player, 40)) { @@ -1055,12 +1042,10 @@ bool DoSpell(Player &player) { if (player.AnimInfo.currentFrame == player._pSFNum) { CastSpell( - player.getId(), + player, player.executedSpell.spellId, - player.position.tile.x, - player.position.tile.y, - player.position.temp.x, - player.position.temp.y, + player.position.tile, + player.position.temp, player.executedSpell.spellLevel); if (IsAnyOf(player.executedSpell.spellType, SpellType::Scroll, SpellType::Charges)) { @@ -1111,11 +1096,11 @@ bool DoDeath(Player &player) bool IsPlayerAdjacentToObject(Player &player, Object &object) { - int x = abs(player.position.tile.x - object.position.x); - int y = abs(player.position.tile.y - object.position.y); + int x = std::abs(player.position.tile.x - object.position.x); + int y = std::abs(player.position.tile.y - object.position.y); if (y > 1 && object.position.y >= 1 && FindObjectAtPosition(object.position + Direction::NorthEast) == &object) { // special case for activating a large object from the north-east side - y = abs(player.position.tile.y - object.position.y + 1); + y = std::abs(player.position.tile.y - object.position.y + 1); } return x <= 1 && y <= 1; } @@ -1197,12 +1182,12 @@ void CheckNewPath(Player &player, bool pmWillBeCalled) if (&player == MyPlayer) { if (player.destAction == ACTION_ATTACKMON || player.destAction == ACTION_ATTACKPLR) { if (player.destAction == ACTION_ATTACKMON) { - x = abs(player.position.future.x - monster->position.future.x); - y = abs(player.position.future.y - monster->position.future.y); + x = std::abs(player.position.future.x - monster->position.future.x); + y = std::abs(player.position.future.y - monster->position.future.y); d = GetDirection(player.position.future, monster->position.future); } else { - x = abs(player.position.future.x - target->position.future.x); - y = abs(player.position.future.y - target->position.future.y); + x = std::abs(player.position.future.x - target->position.future.x); + y = std::abs(player.position.future.y - target->position.future.y); d = GetDirection(player.position.future, target->position.future); } @@ -1270,8 +1255,8 @@ void CheckNewPath(Player &player, bool pmWillBeCalled) StartAttack(player, d, pmWillBeCalled); break; case ACTION_ATTACKMON: - x = abs(player.position.tile.x - monster->position.future.x); - y = abs(player.position.tile.y - monster->position.future.y); + x = std::abs(player.position.tile.x - monster->position.future.x); + y = std::abs(player.position.tile.y - monster->position.future.y); if (x <= 1 && y <= 1) { d = GetDirection(player.position.future, monster->position.future); if (monster->talkMsg != TEXT_NONE && monster->talkMsg != TEXT_VILE14) { @@ -1282,8 +1267,8 @@ void CheckNewPath(Player &player, bool pmWillBeCalled) } break; case ACTION_ATTACKPLR: - x = abs(player.position.tile.x - target->position.future.x); - y = abs(player.position.tile.y - target->position.future.y); + x = std::abs(player.position.tile.x - target->position.future.x); + y = std::abs(player.position.tile.y - target->position.future.y); if (x <= 1 && y <= 1) { d = GetDirection(player.position.future, target->position.future); StartAttack(player, d, pmWillBeCalled); @@ -1353,20 +1338,20 @@ void CheckNewPath(Player &player, bool pmWillBeCalled) break; case ACTION_PICKUPITEM: if (&player == MyPlayer) { - x = abs(player.position.tile.x - item->position.x); - y = abs(player.position.tile.y - item->position.y); + x = std::abs(player.position.tile.x - item->position.x); + y = std::abs(player.position.tile.y - item->position.y); if (x <= 1 && y <= 1 && pcurs == CURSOR_HAND && !item->_iRequest) { - NetSendCmdGItem(true, CMD_REQUESTGITEM, player.getId(), targetId); + NetSendCmdGItem(true, CMD_REQUESTGITEM, player, targetId); item->_iRequest = true; } } break; case ACTION_PICKUPAITEM: if (&player == MyPlayer) { - x = abs(player.position.tile.x - item->position.x); - y = abs(player.position.tile.y - item->position.y); + x = std::abs(player.position.tile.x - item->position.x); + y = std::abs(player.position.tile.y - item->position.y); if (x <= 1 && y <= 1 && pcurs == CURSOR_HAND) { - NetSendCmdGItem(true, CMD_REQUESTAGITEM, player.getId(), targetId); + NetSendCmdGItem(true, CMD_REQUESTAGITEM, player, targetId); } } break; @@ -1392,16 +1377,16 @@ void CheckNewPath(Player &player, bool pmWillBeCalled) StartAttack(player, d, pmWillBeCalled); player.destAction = ACTION_NONE; } else if (player.destAction == ACTION_ATTACKMON) { - x = abs(player.position.tile.x - monster->position.future.x); - y = abs(player.position.tile.y - monster->position.future.y); + x = std::abs(player.position.tile.x - monster->position.future.x); + y = std::abs(player.position.tile.y - monster->position.future.y); if (x <= 1 && y <= 1) { d = GetDirection(player.position.future, monster->position.future); StartAttack(player, d, pmWillBeCalled); } player.destAction = ACTION_NONE; } else if (player.destAction == ACTION_ATTACKPLR) { - x = abs(player.position.tile.x - target->position.future.x); - y = abs(player.position.tile.y - target->position.future.y); + x = std::abs(player.position.tile.x - target->position.future.x); + y = std::abs(player.position.tile.y - target->position.future.y); if (x <= 1 && y <= 1) { d = GetDirection(player.position.future, target->position.future); StartAttack(player, d, pmWillBeCalled); @@ -1473,10 +1458,11 @@ void ValidatePlayer() assert(MyPlayer != nullptr); Player &myPlayer = *MyPlayer; - if (myPlayer._pLevel > MaxCharacterLevel) - myPlayer._pLevel = MaxCharacterLevel; - if (myPlayer._pExperience > myPlayer._pNextExper) { - myPlayer._pExperience = myPlayer._pNextExper; + // Player::setCharacterLevel ensures that the player level is within the expected range in case someone has edited their character level in memory + myPlayer.setCharacterLevel(myPlayer.getCharacterLevel()); + // This lets us catch cases where someone is editing experience directly through memory modification and reset their experience back to the expected cap. + if (myPlayer._pExperience > myPlayer.getNextExperienceThreshold()) { + myPlayer._pExperience = myPlayer.getNextExperienceThreshold(); if (*sgOptions.Gameplay.experienceBar) { RedrawEverything(); } @@ -1625,8 +1611,8 @@ void Player::RemoveInvItem(int iv, bool calcScrolls) // Locate the first grid index containing this item and notify remote clients for (size_t i = 0; i < InventoryGridCells; i++) { int8_t itemIndex = InvGrid[i]; - if (abs(itemIndex) - 1 == iv) { - NetSendCmdParam1(false, CMD_DELINVITEMS, i); + if (std::abs(itemIndex) - 1 == iv) { + NetSendCmdParam1(false, CMD_DELINVITEMS, static_cast(i)); break; } } @@ -1634,7 +1620,7 @@ void Player::RemoveInvItem(int iv, bool calcScrolls) // Iterate through invGrid and remove every reference to item for (int8_t &itemIndex : InvGrid) { - if (abs(itemIndex) - 1 == iv) { + if (std::abs(itemIndex) - 1 == iv) { itemIndex = 0; } } @@ -1674,9 +1660,9 @@ void Player::RemoveSpdBarItem(int iv) RedrawEverything(); } -[[nodiscard]] size_t Player::getId() const +[[nodiscard]] uint8_t Player::getId() const { - return std::distance(&Players[0], this); + return static_cast(std::distance(&Players[0], this)); } int Player::GetBaseAttributeValue(CharacterAttribute attribute) const @@ -1713,16 +1699,16 @@ int Player::GetCurrentAttributeValue(CharacterAttribute attribute) const int Player::GetMaximumAttributeValue(CharacterAttribute attribute) const { - PlayerData plrData = PlayersData[static_cast(_pClass)]; + const ClassAttributes &attr = getClassAttributes(); switch (attribute) { case CharacterAttribute::Strength: - return plrData.maxStr; + return attr.maxStr; case CharacterAttribute::Magic: - return plrData.maxMag; + return attr.maxMag; case CharacterAttribute::Dexterity: - return plrData.maxDex; + return attr.maxDex; case CharacterAttribute::Vitality: - return plrData.maxVit; + return attr.maxVit; } app_fatal("Unsupported attribute"); } @@ -1764,9 +1750,9 @@ bool Player::IsPositionInPath(Point pos) void Player::Say(HeroSpeech speechId) const { - _sfx_id soundEffect = herosounds[static_cast(_pClass)][static_cast(speechId)]; + SfxID soundEffect = herosounds[static_cast(_pClass)][static_cast(speechId)]; - if (soundEffect == SFX_NONE) + if (soundEffect == SfxID::None) return; PlaySfxLoc(soundEffect, position.tile); @@ -1774,9 +1760,9 @@ void Player::Say(HeroSpeech speechId) const void Player::SaySpecific(HeroSpeech speechId) const { - _sfx_id soundEffect = herosounds[static_cast(_pClass)][static_cast(speechId)]; + SfxID soundEffect = herosounds[static_cast(_pClass)][static_cast(speechId)]; - if (soundEffect == SFX_NONE || effect_is_playing(soundEffect)) + if (soundEffect == SfxID::None || effect_is_playing(soundEffect)) return; PlaySfxLoc(soundEffect, position.tile, false); @@ -1834,7 +1820,7 @@ void Player::RestorePartialMana() void Player::ReadySpellFromEquipment(inv_body_loc bodyLocation, bool forceSpell) { auto &item = InvBody[bodyLocation]; - if (item._itype == ItemType::Staff && IsValidSpell(item._iSpell) && item._iCharges > 0) { + if (item._itype == ItemType::Staff && IsValidSpell(item._iSpell) && item._iCharges > 0 && item._iStatFlag) { if (forceSpell || _pRSpell == SpellID::Invalid || _pRSplType == SpellType::Invalid) { _pRSpell = item._iSpell; _pRSplType = SpellType::Charges; @@ -2055,28 +2041,71 @@ void Player::UpdatePreviewCelSprite(_cmd_id cmdId, Point point, uint16_t wParam1 } } +void Player::setCharacterLevel(uint8_t level) +{ + this->_pLevel = std::clamp(level, 1U, getMaxCharacterLevel()); +} + +uint8_t Player::getMaxCharacterLevel() const +{ + return GetMaximumCharacterLevel(); +} + +uint32_t Player::getNextExperienceThreshold() const +{ + return GetNextExperienceThresholdForLevel(this->getCharacterLevel()); +} + int32_t Player::calculateBaseLife() const { - const PlayerData &playerData = PlayersData[static_cast(_pClass)]; - return playerData.adjLife + (playerData.lvlLife * _pLevel) + (playerData.chrLife * _pBaseVit); + const ClassAttributes &attr = getClassAttributes(); + return attr.adjLife + (attr.lvlLife * getCharacterLevel()) + (attr.chrLife * _pBaseVit); } int32_t Player::calculateBaseMana() const { - const PlayerData &playerData = PlayersData[static_cast(_pClass)]; - return playerData.adjMana + (playerData.lvlMana * _pLevel) + (playerData.chrMana * _pBaseMag); + const ClassAttributes &attr = getClassAttributes(); + return attr.adjMana + (attr.lvlMana * getCharacterLevel()) + (attr.chrMana * _pBaseMag); +} + +void Player::occupyTile(Point position, bool isMoving) const +{ + int16_t id = this->getId(); + id += 1; + dPlayer[position.x][position.y] = isMoving ? -id : id; +} + +bool Player::isLevelOwnedByLocalClient() const +{ + for (const Player &other : Players) { + if (!other.plractive) + continue; + if (other._pLvlChanging) + continue; + if (other._pmode == PM_NEWLVL) + continue; + if (other.plrlevel != this->plrlevel) + continue; + if (other.plrIsOnSetLevel != this->plrIsOnSetLevel) + continue; + if (&other == MyPlayer && gbBufferMsgs != 0) + continue; + return &other == MyPlayer; + } + + return false; } -Player *PlayerAtPosition(Point position) +Player *PlayerAtPosition(Point position, bool ignoreMovingPlayers /*= false*/) { if (!InDungeonBounds(position)) return nullptr; auto playerIndex = dPlayer[position.x][position.y]; - if (playerIndex == 0) + if (playerIndex == 0 || (ignoreMovingPlayers && playerIndex < 0)) return nullptr; - return &Players[abs(playerIndex) - 1]; + return &Players[std::abs(playerIndex) - 1]; } void LoadPlrGFX(Player &player, player_graphic graphic) @@ -2091,7 +2120,7 @@ void LoadPlrGFX(Player &player, player_graphic graphic) const HeroClass cls = GetPlayerSpriteClass(player._pClass); const PlayerWeaponGraphic animWeaponId = GetPlayerWeaponGraphic(graphic, static_cast(player._pgfxnum & 0xF)); - const char *path = PlayersData[static_cast(cls)].classPath; + const char *path = PlayersSpriteData[static_cast(cls)].classPath; const char *szCel; switch (graphic) { @@ -2140,12 +2169,9 @@ void LoadPlrGFX(Player &player, player_graphic graphic) app_fatal("PLR:2"); } - if (HeadlessMode) - return; - char prefix[3] = { CharChar[static_cast(cls)], ArmourChar[player._pgfxnum >> 4], WepChar[static_cast(animWeaponId)] }; char pszName[256]; - *fmt::format_to(pszName, R"(plrgfx\{0}\{1}\{1}{2})", path, string_view(prefix, 3), szCel) = 0; + *fmt::format_to(pszName, R"(plrgfx\{0}\{1}\{1}{2})", path, std::string_view(prefix, 3), szCel) = 0; const uint16_t animationWidth = GetPlayerSpriteWidth(cls, graphic, animWeaponId); animationData.sprites = LoadCl2Sheet(pszName, animationWidth); std::optional> trn = GetClassTRN(player); @@ -2192,7 +2218,7 @@ void NewPlrAnim(Player &player, player_graphic graphic, Direction dir, Animation if (!HeadlessMode) { sprites = player.AnimationData[static_cast(graphic)].spritesForDirection(dir); if (player.previewCelSprite && (*sprites)[0] == *player.previewCelSprite && !player.isWalking()) { - previewShownGameTickFragments = clamp(AnimationInfo::baseValueFraction - player.progressToNextGameTickWhenPreviewWasSet, 0, AnimationInfo::baseValueFraction); + previewShownGameTickFragments = std::clamp(AnimationInfo::baseValueFraction - player.progressToNextGameTickWhenPreviewWasSet, 0, AnimationInfo::baseValueFraction); } } @@ -2277,25 +2303,23 @@ void CreatePlayer(Player &player, HeroClass c) player = {}; SetRndSeed(SDL_GetTicks()); - const PlayerData &playerData = PlayersData[static_cast(c)]; - - player._pLevel = 1; + player.setCharacterLevel(1); player._pClass = c; - player._pBaseStr = playerData.baseStr; + const ClassAttributes &attr = player.getClassAttributes(); + + player._pBaseStr = attr.baseStr; player._pStrength = player._pBaseStr; - player._pBaseMag = playerData.baseMag; + player._pBaseMag = attr.baseMag; player._pMagic = player._pBaseMag; - player._pBaseDex = playerData.baseDex; + player._pBaseDex = attr.baseDex; player._pDexterity = player._pBaseDex; - player._pBaseVit = playerData.baseVit; + player._pBaseVit = attr.baseVit; player._pVitality = player._pBaseVit; - player._pBaseToBlk = playerData.blockBonus; - player._pHitPoints = player.calculateBaseLife(); player._pMaxHP = player._pHitPoints; player._pHPBase = player._pHitPoints; @@ -2306,55 +2330,23 @@ void CreatePlayer(Player &player, HeroClass c) player._pManaBase = player._pMana; player._pMaxManaBase = player._pMana; - player._pMaxLvl = player._pLevel; player._pExperience = 0; - player._pNextExper = ExpLvlsTbl[1]; player._pArmorClass = 0; player._pLightRad = 10; player._pInfraFlag = false; - player._pRSplType = SpellType::Skill; - SpellID s = playerData.skill; - player._pAblSpells = GetSpellBitmask(s); - player._pRSpell = s; - - if (c == HeroClass::Sorcerer) { - player._pMemSpells = GetSpellBitmask(SpellID::Firebolt); - player._pRSplType = SpellType::Spell; - player._pRSpell = SpellID::Firebolt; - } else { - player._pMemSpells = 0; - } - for (uint8_t &spellLevel : player._pSplLvl) { spellLevel = 0; } player._pSpellFlags = SpellFlag::None; - - if (player._pClass == HeroClass::Sorcerer) { - player._pSplLvl[static_cast(SpellID::Firebolt)] = 2; - } + player._pRSplType = SpellType::Invalid; // Initializing the hotkey bindings to no selection std::fill(player._pSplHotKey, player._pSplHotKey + NumHotkeys, SpellID::Invalid); - PlayerWeaponGraphic animWeaponId = PlayerWeaponGraphic::Unarmed; - switch (c) { - case HeroClass::Warrior: - case HeroClass::Bard: - case HeroClass::Barbarian: - animWeaponId = PlayerWeaponGraphic::SwordShield; - break; - case HeroClass::Rogue: - animWeaponId = PlayerWeaponGraphic::Bow; - break; - case HeroClass::Sorcerer: - case HeroClass::Monk: - animWeaponId = PlayerWeaponGraphic::Staff; - break; - } - player._pgfxnum = static_cast(animWeaponId); + // CreatePlrItems calls AutoEquip which will overwrite the player graphic if required + player._pgfxnum = static_cast(PlayerWeaponGraphic::Unarmed); for (bool &levelVisited : player._pLvlVisited) { levelVisited = false; @@ -2388,8 +2380,7 @@ int CalcStatDiff(Player &player) void NextPlrLevel(Player &player) { - player._pLevel++; - player._pMaxLvl++; + player.setCharacterLevel(player.getCharacterLevel() + 1); CalcPlrInv(player, true); @@ -2398,9 +2389,7 @@ void NextPlrLevel(Player &player) } else { player._pStatPts += 5; } - player._pNextExper = ExpLvlsTbl[std::min(player._pLevel, MaxCharacterLevel - 1)]; - - int hp = PlayersData[static_cast(player._pClass)].lvlLife; + int hp = player.getClassAttributes().lvlLife; player._pMaxHP += hp; player._pHitPoints = player._pMaxHP; @@ -2411,7 +2400,7 @@ void NextPlrLevel(Player &player) RedrawComponent(PanelDrawComponent::Health); } - int mana = PlayersData[static_cast(player._pClass)].lvlMana; + int mana = player.getClassAttributes().lvlMana; player._pMaxMana += mana; player._pMaxManaBase += mana; @@ -2429,56 +2418,50 @@ void NextPlrLevel(Player &player) FocusOnCharInfo(); CalcPlrInv(player, true); + PlaySFX(SfxID::ItemArmor); + PlaySFX(SfxID::ItemSign); } -void AddPlrExperience(Player &player, int lvl, int exp) +void Player::_addExperience(uint32_t experience, int levelDelta) { - if (&player != MyPlayer || player._pHitPoints <= 0) + if (this != MyPlayer || _pHitPoints <= 0) return; - if (player._pLevel >= MaxCharacterLevel) { - player._pLevel = MaxCharacterLevel; + if (isMaxCharacterLevel()) { return; } - // Adjust xp based on difference in level between player and monster - uint32_t clampedExp = std::max(static_cast(exp * (1 + (lvl - player._pLevel) / 10.0)), 0); + // Adjust xp based on difference between the players current level and the target level (usually a monster level) + uint32_t clampedExp = static_cast(std::clamp(static_cast(experience * (1 + levelDelta / 10.0)), 0, std::numeric_limits::max())); // Prevent power leveling if (gbIsMultiplayer) { - const uint32_t clampedPlayerLevel = clamp(static_cast(player._pLevel), 1, MaxCharacterLevel); - // for low level characters experience gain is capped to 1/20 of current levels xp // for high level characters experience gain is capped to 200 * current level - this is a smaller value than 1/20 of the exp needed for the next level after level 5. - clampedExp = std::min({ clampedExp, /* level 0-5: */ ExpLvlsTbl[clampedPlayerLevel] / 20U, /* level 6-50: */ 200U * clampedPlayerLevel }); + clampedExp = std::min({ clampedExp, /* level 1-5: */ getNextExperienceThreshold() / 20U, /* level 6-50: */ 200U * getCharacterLevel() }); } - const uint32_t MaxExperience = ExpLvlsTbl[MaxCharacterLevel - 1]; + const uint32_t maxExperience = GetNextExperienceThresholdForLevel(getMaxCharacterLevel()); - // Overflow is only possible if a kill grants more than (2^32-1 - MaxExperience) XP in one go, which doesn't happen in normal gameplay. Clamp to experience required to reach max level - player._pExperience = std::min(player._pExperience + clampedExp, MaxExperience); + // ensure we only add enough experience to reach the max experience cap so we don't overflow + _pExperience += std::min(clampedExp, maxExperience - _pExperience); if (*sgOptions.Gameplay.experienceBar) { RedrawEverything(); } // Increase player level if applicable - int newLvl = player._pLevel; - while (newLvl < MaxCharacterLevel && player._pExperience >= ExpLvlsTbl[newLvl]) { - newLvl++; - } - if (newLvl != player._pLevel) { - for (int i = newLvl - player._pLevel; i > 0; i--) { - NextPlrLevel(player); - } + while (!isMaxCharacterLevel() && _pExperience >= getNextExperienceThreshold()) { + // NextPlrLevel increments character level which changes the next experience threshold + NextPlrLevel(*this); } - NetSendCmdParam1(false, CMD_PLRLEVEL, player._pLevel); + NetSendCmdParam1(false, CMD_PLRLEVEL, getCharacterLevel()); } -void AddPlrMonstExper(int lvl, int exp, char pmask) +void AddPlrMonstExper(int lvl, unsigned exp, char pmask) { - int totplrs = 0; + unsigned totplrs = 0; for (size_t i = 0; i < Players.size(); i++) { if (((1 << i) & pmask) != 0) { totplrs++; @@ -2486,9 +2469,9 @@ void AddPlrMonstExper(int lvl, int exp, char pmask) } if (totplrs != 0) { - int e = exp / totplrs; + unsigned e = exp / totplrs; if ((pmask & (1 << MyPlayerId)) != 0) - AddPlrExperience(*MyPlayer, lvl, e); + MyPlayer->addExperience(e, lvl); } } @@ -2543,10 +2526,8 @@ void InitPlayer(Player &player, bool firstTime) ActivateVision(player.position.tile, player._pLightRad, player.getId()); } - SpellID s = PlayersData[static_cast(player._pClass)].skill; - player._pAblSpells = GetSpellBitmask(s); + player._pAblSpells = GetSpellBitmask(GetPlayerStartingLoadoutForClass(player._pClass).skill); - player._pNextExper = ExpLvlsTbl[std::min(player._pLevel, MaxCharacterLevel - 1)]; player._pInvincible = false; if (&player == MyPlayer) { @@ -2612,7 +2593,7 @@ void StartStand(Player &player, Direction dir) player._pmode = PM_STAND; FixPlayerLocation(player, dir); FixPlrWalkTags(player); - dPlayer[player.position.tile.x][player.position.tile.y] = player.getId() + 1; + player.occupyTile(player.position.tile, false); SetPlayerOld(player); } @@ -2623,7 +2604,7 @@ void StartPlrBlock(Player &player, Direction dir) return; } - PlaySfxLoc(IS_ISWORD, player.position.tile); + PlaySfxLoc(SfxID::ItemSword, player.position.tile); int8_t skippedAnimationFrames = 0; if (HasAnyOf(player._pIFlags, ItemSpecialEffect::FastBlock)) { @@ -2661,10 +2642,10 @@ void StartPlrHit(Player &player, int dam, bool forcehit) RedrawComponent(PanelDrawComponent::Health); if (player._pClass == HeroClass::Barbarian) { - if (dam >> 6 < player._pLevel + player._pLevel / 4 && !forcehit) { + if (dam >> 6 < player.getCharacterLevel() + player.getCharacterLevel() / 4 && !forcehit) { return; } - } else if (dam >> 6 < player._pLevel && !forcehit) { + } else if (dam >> 6 < player.getCharacterLevel() && !forcehit) { return; } @@ -2686,7 +2667,7 @@ void StartPlrHit(Player &player, int dam, bool forcehit) player._pmode = PM_GOTHIT; FixPlayerLocation(player, pd); FixPlrWalkTags(player); - dPlayer[player.position.tile.x][player.position.tile.y] = player.getId() + 1; + player.occupyTile(player.position.tile, false); SetPlayerOld(player); } @@ -2779,7 +2760,7 @@ StartPlayerKill(Player &player, DeathReason deathReason) ear._iCreateInfo = player._pName[0] << 8 | player._pName[1]; ear._iSeed = player._pName[2] << 24 | player._pName[3] << 16 | player._pName[4] << 8 | player._pName[5]; - ear._ivalue = player._pLevel; + ear._ivalue = player.getCharacterLevel(); if (FindGetItem(ear._iSeed, IDI_EAR, ear._iCreateInfo) == -1) { DeadItem(player, std::move(ear), { 0, 0 }); @@ -2802,25 +2783,41 @@ StartPlayerKill(Player &player, DeathReason deathReason) void StripTopGold(Player &player) { for (Item &item : InventoryPlayerItemsRange { player }) { - if (item._itype == ItemType::Gold) { - if (item._ivalue > MaxGold) { - Item excessGold; - MakeGoldStack(excessGold, item._ivalue - MaxGold); - item._ivalue = MaxGold; - - if (!GoldAutoPlace(player, excessGold)) { - DeadItem(player, std::move(excessGold), { 0, 0 }); - } - } - } + if (item._itype != ItemType::Gold) + continue; + if (item._ivalue <= MaxGold) + continue; + Item excessGold; + MakeGoldStack(excessGold, item._ivalue - MaxGold); + item._ivalue = MaxGold; + + if (GoldAutoPlace(player, excessGold)) + continue; + if (!player.HoldItem.isEmpty() && ActiveItemCount + 1 >= MAXITEMS) + continue; + DeadItem(player, std::move(excessGold), { 0, 0 }); } player._pGold = CalculateGold(player); + + if (player.HoldItem.isEmpty()) + return; + if (AutoEquip(player, player.HoldItem, false)) + return; + if (AutoPlaceItemInInventory(player, player.HoldItem)) + return; + if (AutoPlaceItemInBelt(player, player.HoldItem)) + return; + std::optional itemTile = FindAdjacentPositionForItem(player.position.tile, player._pdir); + if (itemTile) + return; + DeadItem(player, std::move(player.HoldItem), { 0, 0 }); + NewCursor(CURSOR_HAND); } void ApplyPlrDamage(DamageType damageType, Player &player, int dam, int minHP /*= 0*/, int frac /*= 0*/, DeathReason deathReason /*= DeathReason::MonsterOrTrap*/) { int totalDamage = (dam << 6) + frac; - if (&player == MyPlayer) { + if (&player == MyPlayer && player._pHitPoints > 0) { AddFloatingNumber(damageType, player, totalDamage); } if (totalDamage > 0 && player.pManaShield) { @@ -3001,16 +2998,16 @@ void ProcessPlayers() sfxdelay--; if (sfxdelay == 0) { switch (sfxdnum) { - case USFX_DEFILER1: + case SfxID::Defiler1: InitQTextMsg(TEXT_DEFILER1); break; - case USFX_DEFILER2: + case SfxID::Defiler2: InitQTextMsg(TEXT_DEFILER2); break; - case USFX_DEFILER3: + case SfxID::Defiler3: InitQTextMsg(TEXT_DEFILER3); break; - case USFX_DEFILER4: + case SfxID::Defiler4: InitQTextMsg(TEXT_DEFILER4); break; default: @@ -3103,12 +3100,9 @@ bool PosOkPlayer(const Player &player, Point position) return false; if (!IsTileWalkable(position)) return false; - if (dPlayer[position.x][position.y] != 0) { - auto &otherPlayer = Players[abs(dPlayer[position.x][position.y]) - 1]; - if (&otherPlayer != &player && otherPlayer._pHitPoints != 0) { - return false; - } - } + Player *otherPlayer = PlayerAtPosition(position); + if (otherPlayer != nullptr && otherPlayer != &player && otherPlayer->_pHitPoints != 0) + return false; if (dMonster[position.x][position.y] != 0) { if (leveltype == DTYPE_TOWN) { @@ -3234,9 +3228,9 @@ void CheckPlrSpell(bool isShiftHeld, SpellID spellID, SpellType spellType) } else if (pcursmonst != -1 && !isShiftHeld) { LastMouseButtonAction = MouseActionType::SpellMonsterTarget; NetSendCmdParam5(true, CMD_SPELLID, pcursmonst, static_cast(spellID), static_cast(spellType), spellLevel, spellFrom); - } else if (pcursplr != -1 && !isShiftHeld && !myPlayer.friendlyMode) { + } else if (PlayerUnderCursor != nullptr && !isShiftHeld && !myPlayer.friendlyMode) { LastMouseButtonAction = MouseActionType::SpellPlayerTarget; - NetSendCmdParam5(true, CMD_SPELLPID, pcursplr, static_cast(spellID), static_cast(spellType), spellLevel, spellFrom); + NetSendCmdParam5(true, CMD_SPELLPID, PlayerUnderCursor->getId(), static_cast(spellID), static_cast(spellType), spellLevel, spellFrom); } else { LastMouseButtonAction = MouseActionType::Spell; NetSendCmdLocParam4(true, CMD_SPELLXY, cursPosition, static_cast(spellID), static_cast(spellType), spellLevel, spellFrom); @@ -3280,7 +3274,7 @@ void SyncInitPlrPos(Player &player) }(); player.position.tile = position; - dPlayer[position.x][position.y] = player.getId() + 1; + player.occupyTile(position, false); player.position.future = position; if (&player == MyPlayer) { @@ -3302,16 +3296,16 @@ void CheckStats(Player &player) int maxStatPoint = player.GetMaximumAttributeValue(attribute); switch (attribute) { case CharacterAttribute::Strength: - player._pBaseStr = clamp(player._pBaseStr, 0, maxStatPoint); + player._pBaseStr = std::clamp(player._pBaseStr, 0, maxStatPoint); break; case CharacterAttribute::Magic: - player._pBaseMag = clamp(player._pBaseMag, 0, maxStatPoint); + player._pBaseMag = std::clamp(player._pBaseMag, 0, maxStatPoint); break; case CharacterAttribute::Dexterity: - player._pBaseDex = clamp(player._pBaseDex, 0, maxStatPoint); + player._pBaseDex = std::clamp(player._pBaseDex, 0, maxStatPoint); break; case CharacterAttribute::Vitality: - player._pBaseVit = clamp(player._pBaseVit, 0, maxStatPoint); + player._pBaseVit = std::clamp(player._pBaseVit, 0, maxStatPoint); break; } } @@ -3319,7 +3313,7 @@ void CheckStats(Player &player) void ModifyPlrStr(Player &player, int l) { - l = clamp(l, 0 - player._pBaseStr, player.GetMaximumAttributeValue(CharacterAttribute::Strength) - player._pBaseStr); + l = std::clamp(l, 0 - player._pBaseStr, player.GetMaximumAttributeValue(CharacterAttribute::Strength) - player._pBaseStr); player._pStrength += l; player._pBaseStr += l; @@ -3333,13 +3327,13 @@ void ModifyPlrStr(Player &player, int l) void ModifyPlrMag(Player &player, int l) { - l = clamp(l, 0 - player._pBaseMag, player.GetMaximumAttributeValue(CharacterAttribute::Magic) - player._pBaseMag); + l = std::clamp(l, 0 - player._pBaseMag, player.GetMaximumAttributeValue(CharacterAttribute::Magic) - player._pBaseMag); player._pMagic += l; player._pBaseMag += l; int ms = l; - ms *= PlayersData[static_cast(player._pClass)].chrMana; + ms *= player.getClassAttributes().chrMana; player._pMaxManaBase += ms; player._pMaxMana += ms; @@ -3357,7 +3351,7 @@ void ModifyPlrMag(Player &player, int l) void ModifyPlrDex(Player &player, int l) { - l = clamp(l, 0 - player._pBaseDex, player.GetMaximumAttributeValue(CharacterAttribute::Dexterity) - player._pBaseDex); + l = std::clamp(l, 0 - player._pBaseDex, player.GetMaximumAttributeValue(CharacterAttribute::Dexterity) - player._pBaseDex); player._pDexterity += l; player._pBaseDex += l; @@ -3370,13 +3364,13 @@ void ModifyPlrDex(Player &player, int l) void ModifyPlrVit(Player &player, int l) { - l = clamp(l, 0 - player._pBaseVit, player.GetMaximumAttributeValue(CharacterAttribute::Vitality) - player._pBaseVit); + l = std::clamp(l, 0 - player._pBaseVit, player.GetMaximumAttributeValue(CharacterAttribute::Vitality) - player._pBaseVit); player._pVitality += l; player._pBaseVit += l; int ms = l; - ms *= PlayersData[static_cast(player._pClass)].chrLife; + ms *= player.getClassAttributes().chrLife; player._pHPBase += ms; player._pMaxHPBase += ms; @@ -3411,7 +3405,7 @@ void SetPlrMag(Player &player, int v) player._pBaseMag = v; int m = v; - m *= PlayersData[static_cast(player._pClass)].chrMana; + m *= player.getClassAttributes().chrMana; player._pMaxManaBase = m; player._pMaxMana = m; @@ -3429,7 +3423,7 @@ void SetPlrVit(Player &player, int v) player._pBaseVit = v; int hp = v; - hp *= PlayersData[static_cast(player._pClass)].chrLife; + hp *= player.getClassAttributes().chrLife; player._pHPBase = hp; player._pMaxHPBase = hp; @@ -3471,11 +3465,11 @@ void PlayDungMsgs() myPlayer.pDungMsgs |= DungMsgHell; } else if (!setlevel && currlevel == 16 && !myPlayer._pLvlVisited[16] && (myPlayer.pDungMsgs & DungMsgDiablo) == 0) { sfxdelay = 40; - sfxdnum = PS_DIABLVLINT; + sfxdnum = SfxID::DiabloGreeting; myPlayer.pDungMsgs |= DungMsgDiablo; } else if (!setlevel && currlevel == 17 && !myPlayer._pLvlVisited[17] && (myPlayer.pDungMsgs2 & 1) == 0) { sfxdelay = 10; - sfxdnum = USFX_DEFILER1; + sfxdnum = SfxID::Defiler1; Quests[Q_DEFILER]._qactive = QUEST_ACTIVE; Quests[Q_DEFILER]._qlog = true; Quests[Q_DEFILER]._qmsg = TEXT_DEFILER1; @@ -3483,14 +3477,14 @@ void PlayDungMsgs() myPlayer.pDungMsgs2 |= 1; } else if (!setlevel && currlevel == 19 && !myPlayer._pLvlVisited[19] && (myPlayer.pDungMsgs2 & 4) == 0) { sfxdelay = 10; - sfxdnum = USFX_DEFILER3; + sfxdnum = SfxID::Defiler3; myPlayer.pDungMsgs2 |= 4; } else if (!setlevel && currlevel == 21 && !myPlayer._pLvlVisited[21] && (myPlayer.pDungMsgs & 32) == 0) { myPlayer.Say(HeroSpeech::ThisIsAPlaceOfGreatPower, 30); myPlayer.pDungMsgs |= 32; } else if (setlevel && setlvlnum == SL_SKELKING && !gbIsSpawn && !myPlayer._pSLvlVisited[SL_SKELKING] && Quests[Q_SKELKING]._qactive == QUEST_ACTIVE) { sfxdelay = 10; - sfxdnum = USFX_SKING1; + sfxdnum = SfxID::LeoricGreeting; } else { sfxdelay = 0; } diff --git a/Source/player.h b/Source/player.h index a89263c1e8f..86631078f08 100644 --- a/Source/player.h +++ b/Source/player.h @@ -22,22 +22,20 @@ #include "items.h" #include "levels/gendung.h" #include "multi.h" +#include "playerdat.hpp" #include "spelldat.h" #include "utils/attributes.h" #include "utils/enum_traits.h" -#include "utils/stdcompat/algorithm.hpp" namespace devilution { constexpr int InventoryGridCells = 40; constexpr int MaxBeltItems = 8; constexpr int MaxResistance = 75; -constexpr int MaxCharacterLevel = 50; constexpr uint8_t MaxSpellLevel = 15; constexpr int PlayerNameLength = 32; constexpr size_t NumHotkeys = 12; -constexpr int BaseHitChance = 50; /** Walking directions */ enum { @@ -54,17 +52,6 @@ enum { // clang-format on }; -enum class HeroClass : uint8_t { - Warrior, - Rogue, - Sorcerer, - Monk, - Bard, - Barbarian, - - LAST = Barbarian -}; - enum class CharacterAttribute : uint8_t { Strength, Magic, @@ -173,7 +160,7 @@ enum class DeathReason { }; /** Maps from armor animation to letter used in graphic files. */ -constexpr std::array ArmourChar = { +constexpr std::array ArmourChar = { 'l', // light 'm', // medium 'h', // heavy @@ -249,7 +236,6 @@ struct Player { int _pBaseVit; int _pStatPts; int _pDamageMod; - int _pBaseToBlk; int _pHPBase; int _pMaxHPBase; int _pHitPoints; @@ -274,7 +260,6 @@ struct Player { int _pILMinDam; int _pILMaxDam; uint32_t _pExperience; - uint32_t _pNextExper; PLR_MODE _pmode; int8_t walkpath[MaxPathLength]; bool plractive; @@ -319,8 +304,11 @@ struct Player { ActorPosition position; Direction _pdir; // Direction faced by player (direction enum) HeroClass _pClass; - int8_t _pLevel; - int8_t _pMaxLvl; + +private: + uint8_t _pLevel = 1; // Use get/setCharacterLevel to ensure this attribute stays within the accepted range + +public: uint8_t _pgfxnum; // Bitmask indicating what variant of the sprite the player is using. The 3 lower bits define weapon (PlayerWeaponGraphic) and the higher bits define armour (starting with PlayerArmorGraphic) int8_t _pISplLvlAdd; /** @brief Specifies whether players are in non-PvP mode. */ @@ -377,6 +365,37 @@ struct Player { uint16_t wReflections; ItemSpecialEffectHf pDamAcFlags; + /** + * @brief Convenience function to get the base stats/bonuses for this player's class + */ + [[nodiscard]] const ClassAttributes &getClassAttributes() const + { + return GetClassAttributes(_pClass); + } + + [[nodiscard]] const PlayerCombatData &getPlayerCombatData() const + { + return GetPlayerCombatDataForClass(_pClass); + } + + [[nodiscard]] const PlayerData &getPlayerData() const + { + return GetPlayerDataForClass(_pClass); + } + + /** + * @brief Gets the translated name for the character's class + */ + [[nodiscard]] std::string_view getClassName() const + { + return _(getPlayerData().className); + } + + [[nodiscard]] int getBaseToBlock() const + { + return getPlayerCombatData().baseToBlock; + } + void CalcScrolls(); bool CanUseItem(const Item &item) const @@ -386,6 +405,50 @@ struct Player { && _pDexterity >= item._iMinDex; } + bool CanCleave() + { + switch (_pClass) { + case HeroClass::Warrior: + case HeroClass::Rogue: + case HeroClass::Sorcerer: + return false; + case HeroClass::Monk: + return isEquipped(ItemType::Staff); + case HeroClass::Bard: + return InvBody[INVLOC_HAND_LEFT]._itype == ItemType::Sword && InvBody[INVLOC_HAND_RIGHT]._itype == ItemType::Sword; + case HeroClass::Barbarian: + return isEquipped(ItemType::Axe) || (!isEquipped(ItemType::Shield) && (isEquipped(ItemType::Mace, true) || isEquipped(ItemType::Sword, true))); + default: + return false; + } + } + + bool isEquipped(ItemType itemType, bool isTwoHanded = false) + { + switch (itemType) { + case ItemType::Sword: + case ItemType::Axe: + case ItemType::Bow: + case ItemType::Mace: + case ItemType::Shield: + case ItemType::Staff: + return (InvBody[INVLOC_HAND_LEFT]._itype == itemType && (!isTwoHanded || InvBody[INVLOC_HAND_LEFT]._iLoc == ILOC_TWOHAND)) + || (InvBody[INVLOC_HAND_RIGHT]._itype == itemType && (!isTwoHanded || InvBody[INVLOC_HAND_LEFT]._iLoc == ILOC_TWOHAND)); + case ItemType::LightArmor: + case ItemType::MediumArmor: + case ItemType::HeavyArmor: + return InvBody[INVLOC_CHEST]._itype == itemType; + case ItemType::Helm: + return InvBody[INVLOC_HEAD]._itype == itemType; + case ItemType::Ring: + return InvBody[INVLOC_RING_LEFT]._itype == itemType || InvBody[INVLOC_RING_RIGHT]._itype == itemType; + case ItemType::Amulet: + return InvBody[INVLOC_AMULET]._itype == itemType; + default: + return false; + } + } + /** * @brief Remove an item from player inventory * @param iv invList index of item to be removed @@ -396,7 +459,7 @@ struct Player { /** * @brief Returns the network identifier for this player */ - [[nodiscard]] size_t getId() const; + [[nodiscard]] uint8_t getId() const; void RemoveSpdBarItem(int iv); @@ -512,10 +575,7 @@ struct Player { */ int GetMeleeToHit() const { - int hper = _pLevel + _pDexterity / 2 + _pIBonusToHit + BaseHitChance; - if (_pClass == HeroClass::Warrior) - hper += 20; - return hper; + return getCharacterLevel() + _pDexterity / 2 + _pIBonusToHit + getPlayerCombatData().baseMeleeToHit; } /** @@ -535,12 +595,7 @@ struct Player { */ int GetRangedToHit() const { - int hper = _pLevel + _pDexterity + _pIBonusToHit + BaseHitChance; - if (_pClass == HeroClass::Rogue) - hper += 20; - else if (_pClass == HeroClass::Warrior || _pClass == HeroClass::Bard) - hper += 10; - return hper; + return getCharacterLevel() + _pDexterity + _pIBonusToHit + getPlayerCombatData().baseRangedToHit; } int GetRangedPiercingToHit() const @@ -557,12 +612,7 @@ struct Player { */ int GetMagicToHit() const { - int hper = _pMagic + BaseHitChance; - if (_pClass == HeroClass::Sorcerer) - hper += 20; - else if (_pClass == HeroClass::Bard) - hper += 10; - return hper; + return _pMagic + getPlayerCombatData().baseMagicToHit; } /** @@ -571,9 +621,9 @@ struct Player { */ int GetBlockChance(bool useLevel = true) const { - int blkper = _pDexterity + _pBaseToBlk; + int blkper = _pDexterity + getBaseToBlock(); if (useLevel) - blkper += _pLevel * 2; + blkper += getCharacterLevel() * 2; return blkper; } @@ -639,7 +689,7 @@ struct Player { // Maximum achievable HP is approximately 1200. Diablo uses fixed point integers where the last 6 bits are // fractional values. This means that we will never overflow HP values normally by doing this multiplication // as the max value is representable in 17 bits and the multiplication result will be at most 23 bits - _pHPPer = clamp(_pHitPoints * 80 / _pMaxHP, 0, 80); // hp should never be greater than maxHP but just in case + _pHPPer = std::clamp(_pHitPoints * 80 / _pMaxHP, 0, 80); // hp should never be greater than maxHP but just in case } return _pHPPer; @@ -650,7 +700,7 @@ struct Player { if (_pMaxMana <= 0) { _pManaPer = 0; } else { - _pManaPer = clamp(_pMana * 80 / _pMaxMana, 0, 80); + _pManaPer = std::clamp(_pMana * 80 / _pMaxMana, 0, 80); } return _pManaPer; @@ -730,6 +780,18 @@ struct Player { void getAnimationFramesAndTicksPerFrame(player_graphic graphics, int8_t &numberOfFrames, int8_t &ticksPerFrame) const; + [[nodiscard]] ClxSprite currentSprite() const + { + return previewCelSprite ? *previewCelSprite : AnimInfo.currentSprite(); + } + [[nodiscard]] Displacement getRenderingOffset(const ClxSprite sprite) const + { + Displacement offset = { -CalculateWidth2(sprite.width()), 0 }; + if (isWalking()) + offset += GetOffsetForWalking(AnimInfo, _pdir); + return offset; + } + /** * @brief Updates previewCelSprite according to new requested command * @param cmdId What command is requested @@ -739,6 +801,50 @@ struct Player { */ void UpdatePreviewCelSprite(_cmd_id cmdId, Point point, uint16_t wParam1, uint16_t wParam2); + [[nodiscard]] uint8_t getCharacterLevel() const + { + return _pLevel; + } + + /** + * @brief Sets the character level to the target level or nearest valid value. + * @param level New character level, will be clamped to the allowed range + */ + void setCharacterLevel(uint8_t level); + + [[nodiscard]] uint8_t getMaxCharacterLevel() const; + + [[nodiscard]] bool isMaxCharacterLevel() const + { + return getCharacterLevel() >= getMaxCharacterLevel(); + } + +private: + void _addExperience(uint32_t experience, int levelDelta); + +public: + /** + * @brief Adds experience to the local player based on the current game mode + * @param experience base value to add, this will be adjusted to prevent power leveling in multiplayer games + */ + void addExperience(uint32_t experience) + { + _addExperience(experience, 0); + } + + /** + * @brief Adds experience to the local player based on the difference between the monster level + * and current level, then also applying the power level cap in multiplayer games. + * @param experience base value to add, will be scaled up/down by the difference between player and monster level + * @param monsterLevel level of the monster that has rewarded this experience + */ + void addExperience(uint32_t experience, int monsterLevel) + { + _addExperience(experience, monsterLevel - getCharacterLevel()); + } + + [[nodiscard]] uint32_t getNextExperienceThreshold() const; + /** @brief Checks if the player is on the same level as the local player (MyPlayer). */ bool isOnActiveLevel() const { @@ -778,9 +884,19 @@ struct Player { /** @brief Returns a character's mana based on starting mana, character level, and base magic. */ int32_t calculateBaseMana() const; + + /** + * @brief Sets a tile/dPlayer to be occupied by the player + * @param position tile to update + * @param isMoving specifies whether the player is moving or not (true/moving results in a negative index in dPlayer) + */ + void occupyTile(Point position, bool isMoving) const; + + /** @brief Checks if the player level is owned by local client. */ + bool isLevelOwnedByLocalClient() const; }; -extern DVL_API_FOR_TEST size_t MyPlayerId; +extern DVL_API_FOR_TEST uint8_t MyPlayerId; extern DVL_API_FOR_TEST Player *MyPlayer; extern DVL_API_FOR_TEST std::vector Players; /** @brief What Player items and stats should be displayed? Normally this is identical to MyPlayer but can differ when /inspect was used. */ @@ -792,7 +908,7 @@ inline bool IsInspectingPlayer() } extern bool MyPlayerIsDead; -Player *PlayerAtPosition(Point position); +Player *PlayerAtPosition(Point position, bool ignoreMovingPlayers = false); void LoadPlrGFX(Player &player, player_graphic graphic); void InitPlayerGFX(Player &player); @@ -816,8 +932,7 @@ int CalcStatDiff(Player &player); #ifdef _DEBUG void NextPlrLevel(Player &player); #endif -void AddPlrExperience(Player &player, int lvl, int exp); -void AddPlrMonstExper(int lvl, int exp, char pmask); +void AddPlrMonstExper(int lvl, unsigned int exp, char pmask); void ApplyPlrDamage(DamageType damageType, Player &player, int dam, int minHP = 0, int frac = 0, DeathReason deathReason = DeathReason::MonsterOrTrap); void InitPlayer(Player &player, bool FirstTime); void InitMultiView(); diff --git a/Source/playerdat.cpp b/Source/playerdat.cpp index ab7292c83fc..321496cde35 100644 --- a/Source/playerdat.cpp +++ b/Source/playerdat.cpp @@ -6,119 +6,345 @@ #include "playerdat.hpp" +#include +#include +#include +#include #include +#include +#include +#include + +#include "data/file.hpp" #include "items.h" #include "player.h" #include "textdat.h" #include "utils/language.h" +#include "utils/static_vector.hpp" +#include "utils/str_cat.hpp" namespace devilution { -/** Specifies the experience point limit of each level. */ -const uint32_t ExpLvlsTbl[MaxCharacterLevel] = { - 0, - 2000, - 4620, - 8040, - 12489, - 18258, - 25712, - 35309, - 47622, - 63364, - 83419, - 108879, - 141086, - 181683, - 231075, - 313656, - 424067, - 571190, - 766569, - 1025154, - 1366227, - 1814568, - 2401895, - 3168651, - 4166200, - 5459523, - 7130496, - 9281874, - 12042092, - 15571031, - 20066900, - 25774405, - 32994399, - 42095202, - 53525811, - 67831218, - 85670061, - 107834823, - 135274799, - 169122009, - 210720231, - 261657253, - 323800420, - 399335440, - 490808349, - 601170414, - 733825617, - 892680222, - 1082908612, - 1310707109 +namespace { + +class ExperienceData { + /** Specifies the experience point limit of each level. */ + std::vector levelThresholds; + +public: + uint8_t getMaxLevel() const + { + return static_cast(std::min(levelThresholds.size(), std::numeric_limits::max())); + } + + DVL_REINITIALIZES void clear() + { + levelThresholds.clear(); + } + + [[nodiscard]] uint32_t getThresholdForLevel(unsigned level) const + { + if (level > 0) + return levelThresholds[std::min(level - 1, getMaxLevel())]; + + return 0; + } + + void setThresholdForLevel(unsigned level, uint32_t experience) + { + if (level > 0) { + if (level > levelThresholds.size()) { + // To avoid ValidatePlayer() resetting players to 0 experience we need to use the maximum possible value here + // As long as the file has no gaps it'll get initialised properly. + levelThresholds.resize(level, std::numeric_limits::max()); + } + + levelThresholds[static_cast(level - 1)] = experience; + } + } +} ExperienceData; + +enum class ExperienceColumn { + Level, + Experience, + LAST = Experience }; -const _sfx_id herosounds[enum_size::value][enum_size::value] = { +tl::expected mapExperienceColumnFromName(std::string_view name) +{ + if (name == "Level") { + return ExperienceColumn::Level; + } + if (name == "Experience") { + return ExperienceColumn::Experience; + } + return tl::unexpected { ColumnDefinition::Error::UnknownColumn }; +} + +void ReloadExperienceData() +{ + constexpr std::string_view filename = "txtdata\\Experience.tsv"; + auto dataFileResult = DataFile::load(filename); + if (!dataFileResult.has_value()) { + DataFile::reportFatalError(dataFileResult.error(), filename); + } + DataFile &dataFile = dataFileResult.value(); + + constexpr unsigned ExpectedColumnCount = enum_size::value; + + std::array columns; + auto parseHeaderResult = dataFile.parseHeader(columns.data(), columns.data() + columns.size(), mapExperienceColumnFromName); + + if (!parseHeaderResult.has_value()) { + DataFile::reportFatalError(parseHeaderResult.error(), filename); + } + + ExperienceData.clear(); + for (DataFileRecord record : dataFile) { + uint8_t level = 0; + uint32_t experience = 0; + bool skipRecord = false; + + FieldIterator fieldIt = record.begin(); + FieldIterator endField = record.end(); + for (auto &column : columns) { + fieldIt += column.skipLength; + + if (fieldIt == endField) { + DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename); + } + + DataFileField field = *fieldIt; + + switch (static_cast(column)) { + case ExperienceColumn::Level: { + auto parseIntResult = field.parseInt(level); + + if (!parseIntResult.has_value()) { + if (*field == "MaxLevel") { + skipRecord = true; + } else { + DataFile::reportFatalFieldError(parseIntResult.error(), filename, "Level", field); + } + } + } break; + + case ExperienceColumn::Experience: { + auto parseIntResult = field.parseInt(experience); + + if (!parseIntResult.has_value()) { + DataFile::reportFatalFieldError(parseIntResult.error(), filename, "Experience", field); + } + } break; + + default: + break; + } + + if (skipRecord) + break; + + ++fieldIt; + } + + if (!skipRecord) + ExperienceData.setThresholdForLevel(level, experience); + } +} + +void LoadClassData(std::string_view classPath, ClassAttributes &attributes, PlayerCombatData &combat) +{ + const std::string filename = StrCat("txtdata\\classes\\", classPath, "\\attributes.tsv"); + tl::expected dataFileResult = DataFile::load(filename); + if (!dataFileResult.has_value()) { + DataFile::reportFatalError(dataFileResult.error(), filename); + } + DataFile &dataFile = dataFileResult.value(); + + if (tl::expected result = dataFile.skipHeader(); + !result.has_value()) { + DataFile::reportFatalError(result.error(), filename); + } + + auto recordIt = dataFile.begin(); + const auto recordEnd = dataFile.end(); + + const auto getValueField = [&](std::string_view expectedKey) { + if (recordIt == recordEnd) { + app_fatal(fmt::format("Missing field {} in {}", expectedKey, filename)); + } + DataFileRecord record = *recordIt; + FieldIterator fieldIt = record.begin(); + const FieldIterator endField = record.end(); + + const std::string_view key = (*fieldIt).value(); + if (key != expectedKey) { + app_fatal(fmt::format("Unexpected field in {}: got {}, expected {}", filename, key, expectedKey)); + } + + ++fieldIt; + if (fieldIt == endField) { + DataFile::reportFatalError(DataFile::Error::NotEnoughColumns, filename); + } + return *fieldIt; + }; + + const auto valueReader = [&](auto &&readFn) { + return [&](std::string_view expectedKey, auto &outValue) { + DataFileField valueField = getValueField(expectedKey); + if (const tl::expected result = readFn(valueField, outValue); + !result.has_value()) { + DataFile::reportFatalFieldError(result.error(), filename, "Value", valueField); + } + ++recordIt; + }; + }; + + const auto readInt = valueReader([](DataFileField &valueField, auto &outValue) { + return valueField.parseInt(outValue); + }); + const auto readDecimal = valueReader([](DataFileField &valueField, auto &outValue) { + return valueField.parseFixed6(outValue); + }); + + readInt("baseStr", attributes.baseStr); + readInt("baseMag", attributes.baseMag); + readInt("baseDex", attributes.baseDex); + readInt("baseVit", attributes.baseVit); + readInt("maxStr", attributes.maxStr); + readInt("maxMag", attributes.maxMag); + readInt("maxDex", attributes.maxDex); + readInt("maxVit", attributes.maxVit); + readInt("blockBonus", combat.baseToBlock); + readDecimal("adjLife", attributes.adjLife); + readDecimal("adjMana", attributes.adjMana); + readDecimal("lvlLife", attributes.lvlLife); + readDecimal("lvlMana", attributes.lvlMana); + readDecimal("chrLife", attributes.chrLife); + readDecimal("chrMana", attributes.chrMana); + readDecimal("itmLife", attributes.itmLife); + readDecimal("itmMana", attributes.itmMana); + readInt("baseMagicToHit", combat.baseMagicToHit); + readInt("baseMeleeToHit", combat.baseMeleeToHit); + readInt("baseRangedToHit", combat.baseRangedToHit); +} + +std::vector ClassAttributesPerClass; + +std::vector PlayersCombatData; + +void LoadClassesAttributes() +{ + const std::array classPaths { "warrior", "rogue", "sorcerer", "monk", "bard", "barbarian" }; + ClassAttributesPerClass.clear(); + ClassAttributesPerClass.reserve(classPaths.size()); + PlayersCombatData.clear(); + PlayersCombatData.reserve(classPaths.size()); + for (std::string_view path : classPaths) { + LoadClassData(path, ClassAttributesPerClass.emplace_back(), PlayersCombatData.emplace_back()); + } +} + +/** Contains the data related to each player class. */ +const PlayerData PlayersData[] = { // clang-format off - { PS_WARR1, PS_WARR2, PS_WARR3, PS_WARR4, PS_WARR5, PS_WARR6, PS_WARR7, PS_WARR8, PS_WARR9, PS_WARR10, PS_WARR11, PS_WARR12, PS_WARR13, PS_WARR14, PS_WARR15, PS_WARR16, PS_WARR17, PS_WARR18, PS_WARR19, PS_WARR20, PS_WARR21, PS_WARR22, PS_WARR23, PS_WARR24, PS_WARR25, PS_WARR26, PS_WARR27, PS_WARR28, PS_WARR29, PS_WARR30, PS_WARR31, PS_WARR32, PS_WARR33, PS_WARR34, PS_WARR35, PS_WARR36, PS_WARR37, PS_WARR38, PS_WARR39, PS_WARR40, PS_WARR41, PS_WARR42, PS_WARR43, PS_WARR44, PS_WARR45, PS_WARR46, PS_WARR47, PS_WARR48, PS_WARR49, PS_WARR50, PS_WARR51, PS_WARR52, PS_WARR53, PS_WARR54, PS_WARR55, PS_WARR56, PS_WARR57, PS_WARR58, PS_WARR59, PS_WARR60, PS_WARR61, PS_WARR62, PS_WARR63, PS_WARR64, PS_WARR65, PS_WARR66, PS_WARR67, PS_WARR68, PS_WARR69, PS_WARR70, PS_WARR71, PS_WARR72, PS_WARR73, PS_WARR74, PS_WARR75, PS_WARR76, PS_WARR77, PS_WARR78, PS_WARR79, PS_WARR80, PS_WARR81, PS_WARR82, PS_WARR83, PS_WARR84, PS_WARR85, PS_WARR86, PS_WARR87, PS_WARR88, PS_WARR89, PS_WARR90, PS_WARR91, PS_WARR92, PS_WARR93, PS_WARR94, PS_WARR95, PS_WARR96B, PS_WARR97, PS_WARR98, PS_WARR99, PS_WARR100, PS_WARR101, PS_WARR102, PS_DEAD }, - { PS_ROGUE1, PS_ROGUE2, PS_ROGUE3, PS_ROGUE4, PS_ROGUE5, PS_ROGUE6, PS_ROGUE7, PS_ROGUE8, PS_ROGUE9, PS_ROGUE10, PS_ROGUE11, PS_ROGUE12, PS_ROGUE13, PS_ROGUE14, PS_ROGUE15, PS_ROGUE16, PS_ROGUE17, PS_ROGUE18, PS_ROGUE19, PS_ROGUE20, PS_ROGUE21, PS_ROGUE22, PS_ROGUE23, PS_ROGUE24, PS_ROGUE25, PS_ROGUE26, PS_ROGUE27, PS_ROGUE28, PS_ROGUE29, PS_ROGUE30, PS_ROGUE31, PS_ROGUE32, PS_ROGUE33, PS_ROGUE34, PS_ROGUE35, PS_ROGUE36, PS_ROGUE37, PS_ROGUE38, PS_ROGUE39, PS_ROGUE40, PS_ROGUE41, PS_ROGUE42, PS_ROGUE43, PS_ROGUE44, PS_ROGUE45, PS_ROGUE46, PS_ROGUE47, PS_ROGUE48, PS_ROGUE49, PS_ROGUE50, PS_ROGUE51, PS_ROGUE52, PS_ROGUE53, PS_ROGUE54, PS_ROGUE55, PS_ROGUE56, PS_ROGUE57, PS_ROGUE58, PS_ROGUE59, PS_ROGUE60, PS_ROGUE61, PS_ROGUE62, PS_ROGUE63, PS_ROGUE64, PS_ROGUE65, PS_ROGUE66, PS_ROGUE67, PS_ROGUE68, PS_ROGUE69, PS_ROGUE70, PS_ROGUE71, PS_ROGUE72, PS_ROGUE73, PS_ROGUE74, PS_ROGUE75, PS_ROGUE76, PS_ROGUE77, PS_ROGUE78, PS_ROGUE79, PS_ROGUE80, PS_ROGUE81, PS_ROGUE82, PS_ROGUE83, PS_ROGUE84, PS_ROGUE85, PS_ROGUE86, PS_ROGUE87, PS_ROGUE88, PS_ROGUE89, PS_ROGUE90, PS_ROGUE91, PS_ROGUE92, PS_ROGUE93, PS_ROGUE94, PS_ROGUE95, PS_ROGUE96, PS_ROGUE97, PS_ROGUE98, PS_ROGUE99, PS_ROGUE100, PS_ROGUE101, PS_ROGUE102, PS_ROGUE71 }, - { PS_MAGE1, PS_MAGE2, PS_MAGE3, PS_MAGE4, PS_MAGE5, PS_MAGE6, PS_MAGE7, PS_MAGE8, PS_MAGE9, PS_MAGE10, PS_MAGE11, PS_MAGE12, PS_MAGE13, PS_MAGE14, PS_MAGE15, PS_MAGE16, PS_MAGE17, PS_MAGE18, PS_MAGE19, PS_MAGE20, PS_MAGE21, PS_MAGE22, PS_MAGE23, PS_MAGE24, PS_MAGE25, PS_MAGE26, PS_MAGE27, PS_MAGE28, PS_MAGE29, PS_MAGE30, PS_MAGE31, PS_MAGE32, PS_MAGE33, PS_MAGE34, PS_MAGE35, PS_MAGE36, PS_MAGE37, PS_MAGE38, PS_MAGE39, PS_MAGE40, PS_MAGE41, PS_MAGE42, PS_MAGE43, PS_MAGE44, PS_MAGE45, PS_MAGE46, PS_MAGE47, PS_MAGE48, PS_MAGE49, PS_MAGE50, PS_MAGE51, PS_MAGE52, PS_MAGE53, PS_MAGE54, PS_MAGE55, PS_MAGE56, PS_MAGE57, PS_MAGE58, PS_MAGE59, PS_MAGE60, PS_MAGE61, PS_MAGE62, PS_MAGE63, PS_MAGE64, PS_MAGE65, PS_MAGE66, PS_MAGE67, PS_MAGE68, PS_MAGE69, PS_MAGE70, PS_MAGE71, PS_MAGE72, PS_MAGE73, PS_MAGE74, PS_MAGE75, PS_MAGE76, PS_MAGE77, PS_MAGE78, PS_MAGE79, PS_MAGE80, PS_MAGE81, PS_MAGE82, PS_MAGE83, PS_MAGE84, PS_MAGE85, PS_MAGE86, PS_MAGE87, PS_MAGE88, PS_MAGE89, PS_MAGE90, PS_MAGE91, PS_MAGE92, PS_MAGE93, PS_MAGE94, PS_MAGE95, PS_MAGE96, PS_MAGE97, PS_MAGE98, PS_MAGE99, PS_MAGE100, PS_MAGE101, PS_MAGE102, PS_MAGE71 }, - { PS_MONK1, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, PS_MONK8, PS_MONK9, PS_MONK10, PS_MONK11, PS_MONK12, PS_MONK13, PS_MONK14, PS_MONK15, PS_MONK16, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, PS_MONK24, SFX_NONE, SFX_NONE, PS_MONK27, SFX_NONE, PS_MONK29, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, PS_MONK34, PS_MONK35, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, PS_MONK43, SFX_NONE, SFX_NONE, PS_MONK46, SFX_NONE, SFX_NONE, PS_MONK49, PS_MONK50, SFX_NONE, PS_MONK52, SFX_NONE, PS_MONK54, PS_MONK55, PS_MONK56, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, PS_MONK61, PS_MONK62, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, PS_MONK68, PS_MONK69, PS_MONK70, PS_MONK71, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, SFX_NONE, PS_MONK79, PS_MONK80, SFX_NONE, PS_MONK82, PS_MONK83, SFX_NONE, SFX_NONE, SFX_NONE, PS_MONK87, PS_MONK88, PS_MONK89, SFX_NONE, PS_MONK91, PS_MONK92, SFX_NONE, PS_MONK94, PS_MONK95, PS_MONK96, PS_MONK97, PS_MONK98, PS_MONK99, SFX_NONE, SFX_NONE, SFX_NONE, PS_MONK71 }, - { PS_ROGUE1, PS_ROGUE2, PS_ROGUE3, PS_ROGUE4, PS_ROGUE5, PS_ROGUE6, PS_ROGUE7, PS_ROGUE8, PS_ROGUE9, PS_ROGUE10, PS_ROGUE11, PS_ROGUE12, PS_ROGUE13, PS_ROGUE14, PS_ROGUE15, PS_ROGUE16, PS_ROGUE17, PS_ROGUE18, PS_ROGUE19, PS_ROGUE20, PS_ROGUE21, PS_ROGUE22, PS_ROGUE23, PS_ROGUE24, PS_ROGUE25, PS_ROGUE26, PS_ROGUE27, PS_ROGUE28, PS_ROGUE29, PS_ROGUE30, PS_ROGUE31, PS_ROGUE32, PS_ROGUE33, PS_ROGUE34, PS_ROGUE35, PS_ROGUE36, PS_ROGUE37, PS_ROGUE38, PS_ROGUE39, PS_ROGUE40, PS_ROGUE41, PS_ROGUE42, PS_ROGUE43, PS_ROGUE44, PS_ROGUE45, PS_ROGUE46, PS_ROGUE47, PS_ROGUE48, PS_ROGUE49, PS_ROGUE50, PS_ROGUE51, PS_ROGUE52, PS_ROGUE53, PS_ROGUE54, PS_ROGUE55, PS_ROGUE56, PS_ROGUE57, PS_ROGUE58, PS_ROGUE59, PS_ROGUE60, PS_ROGUE61, PS_ROGUE62, PS_ROGUE63, PS_ROGUE64, PS_ROGUE65, PS_ROGUE66, PS_ROGUE67, PS_ROGUE68, PS_ROGUE69, PS_ROGUE70, PS_ROGUE71, PS_ROGUE72, PS_ROGUE73, PS_ROGUE74, PS_ROGUE75, PS_ROGUE76, PS_ROGUE77, PS_ROGUE78, PS_ROGUE79, PS_ROGUE80, PS_ROGUE81, PS_ROGUE82, PS_ROGUE83, PS_ROGUE84, PS_ROGUE85, PS_ROGUE86, PS_ROGUE87, PS_ROGUE88, PS_ROGUE89, PS_ROGUE90, PS_ROGUE91, PS_ROGUE92, PS_ROGUE93, PS_ROGUE94, PS_ROGUE95, PS_ROGUE96, PS_ROGUE97, PS_ROGUE98, PS_ROGUE99, PS_ROGUE100, PS_ROGUE101, PS_ROGUE102, PS_ROGUE71 }, - { PS_WARR1, PS_WARR2, PS_WARR3, PS_WARR4, PS_WARR5, PS_WARR6, PS_WARR7, PS_WARR8, PS_WARR9, PS_WARR10, PS_WARR11, PS_WARR12, PS_WARR13, PS_WARR14, PS_WARR15, PS_WARR16, PS_WARR17, PS_WARR18, PS_WARR19, PS_WARR20, PS_WARR21, PS_WARR22, PS_WARR23, PS_WARR24, PS_WARR25, PS_WARR26, PS_WARR27, PS_WARR28, PS_WARR29, PS_WARR30, PS_WARR31, PS_WARR32, PS_WARR33, PS_WARR34, PS_WARR35, PS_WARR36, PS_WARR37, PS_WARR38, PS_WARR39, PS_WARR40, PS_WARR41, PS_WARR42, PS_WARR43, PS_WARR44, PS_WARR45, PS_WARR46, PS_WARR47, PS_WARR48, PS_WARR49, PS_WARR50, PS_WARR51, PS_WARR52, PS_WARR53, PS_WARR54, PS_WARR55, PS_WARR56, PS_WARR57, PS_WARR58, PS_WARR59, PS_WARR60, PS_WARR61, PS_WARR62, PS_WARR63, PS_WARR64, PS_WARR65, PS_WARR66, PS_WARR67, PS_WARR68, PS_WARR69, PS_WARR70, PS_WARR71, PS_WARR72, PS_WARR73, PS_WARR74, PS_WARR75, PS_WARR76, PS_WARR77, PS_WARR78, PS_WARR79, PS_WARR80, PS_WARR81, PS_WARR82, PS_WARR83, PS_WARR84, PS_WARR85, PS_WARR86, PS_WARR87, PS_WARR88, PS_WARR89, PS_WARR90, PS_WARR91, PS_WARR92, PS_WARR93, PS_WARR94, PS_WARR95, PS_WARR96B, PS_WARR97, PS_WARR98, PS_WARR99, PS_WARR100, PS_WARR101, PS_WARR102, PS_WARR71 }, +// HeroClass className +// TRANSLATORS: Player Block start +/* HeroClass::Warrior */ { N_("Warrior"), }, +/* HeroClass::Rogue */ { N_("Rogue"), }, +/* HeroClass::Sorcerer */ { N_("Sorcerer"), }, +/* HeroClass::Monk */ { N_("Monk"), }, +/* HeroClass::Bard */ { N_("Bard"), }, +// TRANSLATORS: Player Block end +/* HeroClass::Barbarian */ { N_("Barbarian"), }, // clang-format on }; -/** Contains the data related to each player class. */ -const PlayerData PlayersData[] = { +const std::array::value> PlayersStartingLoadoutData { { // clang-format off -// HeroClass className, classPath, baseStr, baseMag, baseDex, baseVit, maxStr, maxMag, maxDex, maxVit, blockBonus, adjLife, adjMana, lvlLife, lvlMana, chrLife, chrMana, itmLife, itmMana, skill, +// HeroClass skill, spell, spellLevel, items[0].diablo, items[0].hellfire, items[1].diablo, items[1].hellfire, items[2].diablo, items[2].hellfire, items[3].diablo, items[3].hellfire, items[4].diablo, items[4].hellfire, gold, +/* HeroClass::Warrior */ { SpellID::ItemRepair, SpellID::Null, 0, { { { IDI_WARRIOR, IDI_WARRIOR, }, { IDI_WARRSHLD, IDI_WARRSHLD, }, { IDI_WARRCLUB, IDI_WARRCLUB, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, }, }, 100, }, +/* HeroClass::Rogue */ { SpellID::TrapDisarm, SpellID::Null, 0, { { { IDI_ROGUE, IDI_ROGUE, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, { IDI_NONE, IDI_NONE, }, }, }, 100, }, +/* HeroClass::Sorcerer */ { SpellID::StaffRecharge, SpellID::Firebolt, 2, { { { IDI_SORCERER_DIABLO, IDI_SORCERER, }, { IDI_MANA, IDI_HEAL, }, { IDI_MANA, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, { IDI_NONE, IDI_NONE, }, }, }, 100, }, +/* HeroClass::Monk */ { SpellID::Search, SpellID::Null, 0, { { { IDI_SHORTSTAFF, IDI_SHORTSTAFF, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, { IDI_NONE, IDI_NONE, }, }, }, 100, }, +/* HeroClass::Bard */ { SpellID::Identify, SpellID::Null, 0, { { { IDI_BARDSWORD, IDI_BARDSWORD, }, { IDI_BARDDAGGER, IDI_BARDDAGGER, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, }, }, 100, }, +/* HeroClass::Barbarian */ { SpellID::Rage, SpellID::Null, 0, { { { IDI_BARBARIAN, IDI_BARBARIAN, }, { IDI_WARRSHLD, IDI_WARRSHLD, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, }, }, 100, } + // clang-format on +} }; -// TRANSLATORS: Player Block start -/* HeroClass::Warrior */ { N_("Warrior"), "warrior", 30, 10, 20, 25, 250, 50, 60, 100, 30, (18 << 6), -(1 << 6), (2 << 6), (1 << 6), (2 << 6), (1 << 6), (2 << 6), (1 << 6), SpellID::ItemRepair }, -/* HeroClass::Rogue */ { N_("Rogue"), "rogue", 20, 15, 30, 20, 55, 70, 250, 80, 20, (23 << 6), static_cast(5.5F * 64), (2 << 6), (2 << 6), (1 << 6), (1 << 6), static_cast(1.5F * 64), static_cast(1.5F * 64), SpellID::TrapDisarm }, -/* HeroClass::Sorcerer */ { N_("Sorcerer"), "sorceror", 15, 35, 15, 20, 45, 250, 85, 80, 10, (9 << 6), -(2 << 6), (1 << 6), (2 << 6), (1 << 6), (2 << 6), (1 << 6), (2 << 6), SpellID::StaffRecharge }, -/* HeroClass::Monk */ { N_("Monk"), "monk", 25, 15, 25, 20, 150, 80, 150, 80, 25, (23 << 6), static_cast(5.5F * 64), (2 << 6), (2 << 6), (1 << 6), (1 << 6), static_cast(1.5F * 64), static_cast(1.5F * 64), SpellID::Search, }, -/* HeroClass::Bard */ { N_("Bard"), "rogue", 20, 20, 25, 20, 120, 120, 120, 100, 25, (23 << 6), (3 << 6), (2 << 6), (2 << 6), (1 << 6), static_cast(1.5F * 64), static_cast(1.5F * 64), static_cast(1.75F * 64), SpellID::Identify }, -/* HeroClass::Barbarian */ { N_("Barbarian"), "warrior", 40, 0, 20, 25, 255, 0, 55, 150, 30, (18 << 6), (0 << 6), (2 << 6), (0 << 6), (2 << 6), (1 << 6), static_cast(2.5F * 64), (1 << 6), SpellID::Rage }, +} // namespace + +const ClassAttributes &GetClassAttributes(HeroClass playerClass) +{ + return ClassAttributesPerClass[static_cast(playerClass)]; +} + +void LoadPlayerDataFiles() +{ + ReloadExperienceData(); + LoadClassesAttributes(); +} + +uint32_t GetNextExperienceThresholdForLevel(unsigned level) +{ + return ExperienceData.getThresholdForLevel(level); +} + +uint8_t GetMaximumCharacterLevel() +{ + return ExperienceData.getMaxLevel(); +} + +const PlayerData &GetPlayerDataForClass(HeroClass playerClass) +{ + return PlayersData[static_cast(playerClass)]; +} + +const SfxID herosounds[enum_size::value][enum_size::value] = { + // clang-format off + { SfxID::Warrior1, SfxID::Warrior2, SfxID::Warrior3, SfxID::Warrior4, SfxID::Warrior5, SfxID::Warrior6, SfxID::Warrior7, SfxID::Warrior8, SfxID::Warrior9, SfxID::Warrior10, SfxID::Warrior11, SfxID::Warrior12, SfxID::Warrior13, SfxID::Warrior14, SfxID::Warrior15, SfxID::Warrior16, SfxID::Warrior17, SfxID::Warrior18, SfxID::Warrior19, SfxID::Warrior20, SfxID::Warrior21, SfxID::Warrior22, SfxID::Warrior23, SfxID::Warrior24, SfxID::Warrior25, SfxID::Warrior26, SfxID::Warrior27, SfxID::Warrior28, SfxID::Warrior29, SfxID::Warrior30, SfxID::Warrior31, SfxID::Warrior32, SfxID::Warrior33, SfxID::Warrior34, SfxID::Warrior35, SfxID::Warrior36, SfxID::Warrior37, SfxID::Warrior38, SfxID::Warrior39, SfxID::Warrior40, SfxID::Warrior41, SfxID::Warrior42, SfxID::Warrior43, SfxID::Warrior44, SfxID::Warrior45, SfxID::Warrior46, SfxID::Warrior47, SfxID::Warrior48, SfxID::Warrior49, SfxID::Warrior50, SfxID::Warrior51, SfxID::Warrior52, SfxID::Warrior53, SfxID::Warrior54, SfxID::Warrior55, SfxID::Warrior56, SfxID::Warrior57, SfxID::Warrior58, SfxID::Warrior59, SfxID::Warrior60, SfxID::Warrior61, SfxID::Warrior62, SfxID::Warrior63, SfxID::Warrior64, SfxID::Warrior65, SfxID::Warrior66, SfxID::Warrior67, SfxID::Warrior68, SfxID::Warrior69, SfxID::Warrior70, SfxID::Warrior71, SfxID::Warrior72, SfxID::Warrior73, SfxID::Warrior74, SfxID::Warrior75, SfxID::Warrior76, SfxID::Warrior77, SfxID::Warrior78, SfxID::Warrior79, SfxID::Warrior80, SfxID::Warrior81, SfxID::Warrior82, SfxID::Warrior83, SfxID::Warrior84, SfxID::Warrior85, SfxID::Warrior86, SfxID::Warrior87, SfxID::Warrior88, SfxID::Warrior89, SfxID::Warrior90, SfxID::Warrior91, SfxID::Warrior92, SfxID::Warrior93, SfxID::Warrior94, SfxID::Warrior95, SfxID::Warrior96b, SfxID::Warrior97, SfxID::Warrior98, SfxID::Warrior99, SfxID::Warrior100, SfxID::Warrior101, SfxID::Warrior102, SfxID::WarriorDeath }, + { SfxID::Rogue1, SfxID::Rogue2, SfxID::Rogue3, SfxID::Rogue4, SfxID::Rogue5, SfxID::Rogue6, SfxID::Rogue7, SfxID::Rogue8, SfxID::Rogue9, SfxID::Rogue10, SfxID::Rogue11, SfxID::Rogue12, SfxID::Rogue13, SfxID::Rogue14, SfxID::Rogue15, SfxID::Rogue16, SfxID::Rogue17, SfxID::Rogue18, SfxID::Rogue19, SfxID::Rogue20, SfxID::Rogue21, SfxID::Rogue22, SfxID::Rogue23, SfxID::Rogue24, SfxID::Rogue25, SfxID::Rogue26, SfxID::Rogue27, SfxID::Rogue28, SfxID::Rogue29, SfxID::Rogue30, SfxID::Rogue31, SfxID::Rogue32, SfxID::Rogue33, SfxID::Rogue34, SfxID::Rogue35, SfxID::Rogue36, SfxID::Rogue37, SfxID::Rogue38, SfxID::Rogue39, SfxID::Rogue40, SfxID::Rogue41, SfxID::Rogue42, SfxID::Rogue43, SfxID::Rogue44, SfxID::Rogue45, SfxID::Rogue46, SfxID::Rogue47, SfxID::Rogue48, SfxID::Rogue49, SfxID::Rogue50, SfxID::Rogue51, SfxID::Rogue52, SfxID::Rogue53, SfxID::Rogue54, SfxID::Rogue55, SfxID::Rogue56, SfxID::Rogue57, SfxID::Rogue58, SfxID::Rogue59, SfxID::Rogue60, SfxID::Rogue61, SfxID::Rogue62, SfxID::Rogue63, SfxID::Rogue64, SfxID::Rogue65, SfxID::Rogue66, SfxID::Rogue67, SfxID::Rogue68, SfxID::Rogue69, SfxID::Rogue70, SfxID::Rogue71, SfxID::Rogue72, SfxID::Rogue73, SfxID::Rogue74, SfxID::Rogue75, SfxID::Rogue76, SfxID::Rogue77, SfxID::Rogue78, SfxID::Rogue79, SfxID::Rogue80, SfxID::Rogue81, SfxID::Rogue82, SfxID::Rogue83, SfxID::Rogue84, SfxID::Rogue85, SfxID::Rogue86, SfxID::Rogue87, SfxID::Rogue88, SfxID::Rogue89, SfxID::Rogue90, SfxID::Rogue91, SfxID::Rogue92, SfxID::Rogue93, SfxID::Rogue94, SfxID::Rogue95, SfxID::Rogue96, SfxID::Rogue97, SfxID::Rogue98, SfxID::Rogue99, SfxID::Rogue100, SfxID::Rogue101, SfxID::Rogue102, SfxID::Rogue71 }, + { SfxID::Sorceror1, SfxID::Sorceror2, SfxID::Sorceror3, SfxID::Sorceror4, SfxID::Sorceror5, SfxID::Sorceror6, SfxID::Sorceror7, SfxID::Sorceror8, SfxID::Sorceror9, SfxID::Sorceror10, SfxID::Sorceror11, SfxID::Sorceror12, SfxID::Sorceror13, SfxID::Sorceror14, SfxID::Sorceror15, SfxID::Sorceror16, SfxID::Sorceror17, SfxID::Sorceror18, SfxID::Sorceror19, SfxID::Sorceror20, SfxID::Sorceror21, SfxID::Sorceror22, SfxID::Sorceror23, SfxID::Sorceror24, SfxID::Sorceror25, SfxID::Sorceror26, SfxID::Sorceror27, SfxID::Sorceror28, SfxID::Sorceror29, SfxID::Sorceror30, SfxID::Sorceror31, SfxID::Sorceror32, SfxID::Sorceror33, SfxID::Sorceror34, SfxID::Sorceror35, SfxID::Sorceror36, SfxID::Sorceror37, SfxID::Sorceror38, SfxID::Sorceror39, SfxID::Sorceror40, SfxID::Sorceror41, SfxID::Sorceror42, SfxID::Sorceror43, SfxID::Sorceror44, SfxID::Sorceror45, SfxID::Sorceror46, SfxID::Sorceror47, SfxID::Sorceror48, SfxID::Sorceror49, SfxID::Sorceror50, SfxID::Sorceror51, SfxID::Sorceror52, SfxID::Sorceror53, SfxID::Sorceror54, SfxID::Sorceror55, SfxID::Sorceror56, SfxID::Sorceror57, SfxID::Sorceror58, SfxID::Sorceror59, SfxID::Sorceror60, SfxID::Sorceror61, SfxID::Sorceror62, SfxID::Sorceror63, SfxID::Sorceror64, SfxID::Sorceror65, SfxID::Sorceror66, SfxID::Sorceror67, SfxID::Sorceror68, SfxID::Sorceror69, SfxID::Sorceror70, SfxID::Sorceror71, SfxID::Sorceror72, SfxID::Sorceror73, SfxID::Sorceror74, SfxID::Sorceror75, SfxID::Sorceror76, SfxID::Sorceror77, SfxID::Sorceror78, SfxID::Sorceror79, SfxID::Sorceror80, SfxID::Sorceror81, SfxID::Sorceror82, SfxID::Sorceror83, SfxID::Sorceror84, SfxID::Sorceror85, SfxID::Sorceror86, SfxID::Sorceror87, SfxID::Sorceror88, SfxID::Sorceror89, SfxID::Sorceror90, SfxID::Sorceror91, SfxID::Sorceror92, SfxID::Sorceror93, SfxID::Sorceror94, SfxID::Sorceror95, SfxID::Sorceror96, SfxID::Sorceror97, SfxID::Sorceror98, SfxID::Sorceror99, SfxID::Sorceror100, SfxID::Sorceror101, SfxID::Sorceror102, SfxID::Sorceror71 }, + { SfxID::Monk1, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk8, SfxID::Monk9, SfxID::Monk10, SfxID::Monk11, SfxID::Monk12, SfxID::Monk13, SfxID::Monk14, SfxID::Monk15, SfxID::Monk16, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk24, SfxID::None, SfxID::None, SfxID::Monk27, SfxID::None, SfxID::Monk29, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk34, SfxID::Monk35, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk43, SfxID::None, SfxID::None, SfxID::Monk46, SfxID::None, SfxID::None, SfxID::Monk49, SfxID::Monk50, SfxID::None, SfxID::Monk52, SfxID::None, SfxID::Monk54, SfxID::Monk55, SfxID::Monk56, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk61, SfxID::Monk62, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk68, SfxID::Monk69, SfxID::Monk70, SfxID::Monk71, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk79, SfxID::Monk80, SfxID::None, SfxID::Monk82, SfxID::Monk83, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk87, SfxID::Monk88, SfxID::Monk89, SfxID::None, SfxID::Monk91, SfxID::Monk92, SfxID::None, SfxID::Monk94, SfxID::Monk95, SfxID::Monk96, SfxID::Monk97, SfxID::Monk98, SfxID::Monk99, SfxID::None, SfxID::None, SfxID::None, SfxID::Monk71 }, + { SfxID::Rogue1, SfxID::Rogue2, SfxID::Rogue3, SfxID::Rogue4, SfxID::Rogue5, SfxID::Rogue6, SfxID::Rogue7, SfxID::Rogue8, SfxID::Rogue9, SfxID::Rogue10, SfxID::Rogue11, SfxID::Rogue12, SfxID::Rogue13, SfxID::Rogue14, SfxID::Rogue15, SfxID::Rogue16, SfxID::Rogue17, SfxID::Rogue18, SfxID::Rogue19, SfxID::Rogue20, SfxID::Rogue21, SfxID::Rogue22, SfxID::Rogue23, SfxID::Rogue24, SfxID::Rogue25, SfxID::Rogue26, SfxID::Rogue27, SfxID::Rogue28, SfxID::Rogue29, SfxID::Rogue30, SfxID::Rogue31, SfxID::Rogue32, SfxID::Rogue33, SfxID::Rogue34, SfxID::Rogue35, SfxID::Rogue36, SfxID::Rogue37, SfxID::Rogue38, SfxID::Rogue39, SfxID::Rogue40, SfxID::Rogue41, SfxID::Rogue42, SfxID::Rogue43, SfxID::Rogue44, SfxID::Rogue45, SfxID::Rogue46, SfxID::Rogue47, SfxID::Rogue48, SfxID::Rogue49, SfxID::Rogue50, SfxID::Rogue51, SfxID::Rogue52, SfxID::Rogue53, SfxID::Rogue54, SfxID::Rogue55, SfxID::Rogue56, SfxID::Rogue57, SfxID::Rogue58, SfxID::Rogue59, SfxID::Rogue60, SfxID::Rogue61, SfxID::Rogue62, SfxID::Rogue63, SfxID::Rogue64, SfxID::Rogue65, SfxID::Rogue66, SfxID::Rogue67, SfxID::Rogue68, SfxID::Rogue69, SfxID::Rogue70, SfxID::Rogue71, SfxID::Rogue72, SfxID::Rogue73, SfxID::Rogue74, SfxID::Rogue75, SfxID::Rogue76, SfxID::Rogue77, SfxID::Rogue78, SfxID::Rogue79, SfxID::Rogue80, SfxID::Rogue81, SfxID::Rogue82, SfxID::Rogue83, SfxID::Rogue84, SfxID::Rogue85, SfxID::Rogue86, SfxID::Rogue87, SfxID::Rogue88, SfxID::Rogue89, SfxID::Rogue90, SfxID::Rogue91, SfxID::Rogue92, SfxID::Rogue93, SfxID::Rogue94, SfxID::Rogue95, SfxID::Rogue96, SfxID::Rogue97, SfxID::Rogue98, SfxID::Rogue99, SfxID::Rogue100, SfxID::Rogue101, SfxID::Rogue102, SfxID::Rogue71 }, + { SfxID::Warrior1, SfxID::Warrior2, SfxID::Warrior3, SfxID::Warrior4, SfxID::Warrior5, SfxID::Warrior6, SfxID::Warrior7, SfxID::Warrior8, SfxID::Warrior9, SfxID::Warrior10, SfxID::Warrior11, SfxID::Warrior12, SfxID::Warrior13, SfxID::Warrior14, SfxID::Warrior15, SfxID::Warrior16, SfxID::Warrior17, SfxID::Warrior18, SfxID::Warrior19, SfxID::Warrior20, SfxID::Warrior21, SfxID::Warrior22, SfxID::Warrior23, SfxID::Warrior24, SfxID::Warrior25, SfxID::Warrior26, SfxID::Warrior27, SfxID::Warrior28, SfxID::Warrior29, SfxID::Warrior30, SfxID::Warrior31, SfxID::Warrior32, SfxID::Warrior33, SfxID::Warrior34, SfxID::Warrior35, SfxID::Warrior36, SfxID::Warrior37, SfxID::Warrior38, SfxID::Warrior39, SfxID::Warrior40, SfxID::Warrior41, SfxID::Warrior42, SfxID::Warrior43, SfxID::Warrior44, SfxID::Warrior45, SfxID::Warrior46, SfxID::Warrior47, SfxID::Warrior48, SfxID::Warrior49, SfxID::Warrior50, SfxID::Warrior51, SfxID::Warrior52, SfxID::Warrior53, SfxID::Warrior54, SfxID::Warrior55, SfxID::Warrior56, SfxID::Warrior57, SfxID::Warrior58, SfxID::Warrior59, SfxID::Warrior60, SfxID::Warrior61, SfxID::Warrior62, SfxID::Warrior63, SfxID::Warrior64, SfxID::Warrior65, SfxID::Warrior66, SfxID::Warrior67, SfxID::Warrior68, SfxID::Warrior69, SfxID::Warrior70, SfxID::Warrior71, SfxID::Warrior72, SfxID::Warrior73, SfxID::Warrior74, SfxID::Warrior75, SfxID::Warrior76, SfxID::Warrior77, SfxID::Warrior78, SfxID::Warrior79, SfxID::Warrior80, SfxID::Warrior81, SfxID::Warrior82, SfxID::Warrior83, SfxID::Warrior84, SfxID::Warrior85, SfxID::Warrior86, SfxID::Warrior87, SfxID::Warrior88, SfxID::Warrior89, SfxID::Warrior90, SfxID::Warrior91, SfxID::Warrior92, SfxID::Warrior93, SfxID::Warrior94, SfxID::Warrior95, SfxID::Warrior96b, SfxID::Warrior97, SfxID::Warrior98, SfxID::Warrior99, SfxID::Warrior100, SfxID::Warrior101, SfxID::Warrior102, SfxID::Warrior71 }, // clang-format on }; +const PlayerCombatData &GetPlayerCombatDataForClass(HeroClass clazz) +{ + return PlayersCombatData[static_cast(clazz)]; +} + +const PlayerStartingLoadoutData &GetPlayerStartingLoadoutForClass(HeroClass clazz) +{ + return PlayersStartingLoadoutData[static_cast(clazz)]; +} + /** Contains the data related to each player class. */ const PlayerSpriteData PlayersSpriteData[] = { // clang-format off -// HeroClass stand, walk, attack, bow, swHit, block, lightning, fire, magic, death - -// TRANSLATORS: Player Block -/* HeroClass::Warrior */ { 96, 96, 128, 96, 96, 96, 96, 96, 96, 128 }, -/* HeroClass::Rogue */ { 96, 96, 128, 128, 96, 96, 96, 96, 96, 128 }, -/* HeroClass::Sorcerer */ { 96, 96, 128, 128, 96, 96, 128, 128, 128, 128 }, -/* HeroClass::Monk */ { 112, 112, 130, 130, 98, 98, 114, 114, 114, 160 }, -/* HeroClass::Bard */ { 96, 96, 128, 128, 96, 96, 96, 96, 96, 128 }, -/* HeroClass::Barbarian */ { 96, 96, 128, 96, 96, 96, 96, 96, 96, 128 }, +// HeroClass classPath, stand, walk, attack, bow, swHit, block, lightning, fire, magic, death + +/* HeroClass::Warrior */ { "warrior", 96, 96, 128, 96, 96, 96, 96, 96, 96, 128 }, +/* HeroClass::Rogue */ { "rogue", 96, 96, 128, 128, 96, 96, 96, 96, 96, 128 }, +/* HeroClass::Sorcerer */ { "sorceror", 96, 96, 128, 128, 96, 96, 128, 128, 128, 128 }, +/* HeroClass::Monk */ { "monk", 112, 112, 130, 130, 98, 98, 114, 114, 114, 160 }, +/* HeroClass::Bard */ { "rogue", 96, 96, 128, 128, 96, 96, 96, 96, 96, 128 }, +/* HeroClass::Barbarian */ { "warrior", 96, 96, 128, 96, 96, 96, 96, 96, 96, 128 }, // clang-format on }; const PlayerAnimData PlayersAnimData[] = { // clang-format off -// HeroClass unarmedFrames, unarmedActionFrame, unarmedShieldFrames, unarmedShieldActionFrame, swordFrames, swordActionFrame, swordShieldFrames, swordShieldActionFrame, bowFrames, bowActionFrame, axeFrames, axeActionFrame, maceFrames, maceActionFrame, maceShieldFrames, maceShieldActionFrame, staffFrames, staffActionFrame, idleFrames, walkingFrames, blockingFrames, deathFrames, castingFrames, recoveryFrames, townIdleFrames, townWalkingFrames, castingActionFrame -/* HeroClass::Warrior */ { 16, 9, 16, 9, 16, 9, 16, 9, 16, 11, 20, 10, 16, 9, 16, 9, 16, 11, 10, 8, 2, 20, 20, 6, 20, 8, 14 }, -/* HeroClass::Rogue */ { 18, 10, 18, 10, 18, 10, 18, 10, 12, 7, 22, 13, 18, 10, 18, 10, 16, 11, 8, 8, 4, 20, 16, 7, 20, 8, 12 }, -/* HeroClass::Sorcerer */ { 20, 12, 16, 9, 16, 12, 16, 12, 20, 16, 24, 16, 16, 12, 16, 12, 16, 12, 8, 8, 6, 20, 12, 8, 20, 8, 8 }, -/* HeroClass::Monk */ { 12, 7, 12, 7, 16, 12, 16, 12, 20, 14, 23, 14, 16, 12, 16, 12, 13, 8, 8, 8, 3, 20, 18, 6, 20, 8, 13 }, -/* HeroClass::Bard */ { 18, 10, 18, 10, 18, 10, 18, 10, 12, 11, 22, 13, 18, 10, 18, 10, 16, 11, 8, 8, 4, 20, 16, 7, 20, 8, 12 }, -/* HeroClass::Barbarian */ { 16, 9, 16, 9, 16, 9, 16, 9, 16, 11, 20, 8, 16, 8, 16, 8, 16, 11, 10, 8, 2, 20, 20, 6, 20, 8, 14 }, +// HeroClass unarmedFrames, unarmedActionFrame, unarmedShieldFrames, unarmedShieldActionFrame, swordFrames, swordActionFrame, swordShieldFrames, swordShieldActionFrame, bowFrames, bowActionFrame, axeFrames, axeActionFrame, maceFrames, maceActionFrame, maceShieldFrames, maceShieldActionFrame, staffFrames, staffActionFrame, idleFrames, walkingFrames, blockingFrames, deathFrames, castingFrames, recoveryFrames, townIdleFrames, townWalkingFrames, castingActionFrame +/* HeroClass::Warrior */ { 16, 9, 16, 9, 16, 9, 16, 9, 16, 11, 20, 10, 16, 9, 16, 9, 16, 11, 10, 8, 2, 20, 20, 6, 20, 8, 14 }, +/* HeroClass::Rogue */ { 18, 10, 18, 10, 18, 10, 18, 10, 12, 7, 22, 13, 18, 10, 18, 10, 16, 11, 8, 8, 4, 20, 16, 7, 20, 8, 12 }, +/* HeroClass::Sorcerer */ { 20, 12, 16, 9, 16, 12, 16, 12, 20, 16, 24, 16, 16, 12, 16, 12, 16, 12, 8, 8, 6, 20, 12, 8, 20, 8, 8 }, +/* HeroClass::Monk */ { 12, 7, 12, 7, 16, 12, 16, 12, 20, 14, 23, 14, 16, 12, 16, 12, 13, 8, 8, 8, 3, 20, 18, 6, 20, 8, 13 }, +/* HeroClass::Bard */ { 18, 10, 18, 10, 18, 10, 18, 10, 12, 11, 22, 13, 18, 10, 18, 10, 16, 11, 8, 8, 4, 20, 16, 7, 20, 8, 12 }, +/* HeroClass::Barbarian */ { 16, 9, 16, 9, 16, 9, 16, 9, 16, 11, 20, 8, 16, 8, 16, 8, 16, 11, 10, 8, 2, 20, 20, 6, 20, 8, 14 }, // clang-format on }; diff --git a/Source/playerdat.hpp b/Source/playerdat.hpp index 7e10c3af40e..4b0bb3fd3d4 100644 --- a/Source/playerdat.hpp +++ b/Source/playerdat.hpp @@ -7,16 +7,31 @@ #include -#include "player.h" -#include "textdat.h" +#include "effects.h" +#include "itemdat.h" +#include "spelldat.h" namespace devilution { +enum class HeroClass : uint8_t { + Warrior, + Rogue, + Sorcerer, + Monk, + Bard, + Barbarian, + + LAST = Barbarian +}; + struct PlayerData { /* Class Name */ const char *className; - /* Class Directory Path */ - const char *classPath; + /* Class Skill */ + SpellID skill = SpellID::Null; +}; + +struct ClassAttributes { /* Class Starting Strength Stat */ uint8_t baseStr; /* Class Starting Magic Stat */ @@ -33,8 +48,6 @@ struct PlayerData { uint8_t maxDex; /* Class Maximum Vitality Stat */ uint8_t maxVit; - /* Class Block Bonus % */ - uint8_t blockBonus; /* Class Life Adjustment */ int16_t adjLife; /* Class Mana Adjustment */ @@ -51,11 +64,53 @@ struct PlayerData { int16_t itmLife; /* Mana from item bonus Magic */ int16_t itmMana; +}; + +const ClassAttributes &GetClassAttributes(HeroClass playerClass); + +struct PlayerCombatData { + /* Class starting chance to Block (used as a %) */ + uint8_t baseToBlock; + /* Class starting chance to hit when using melee attacks (used as a %) */ + uint8_t baseMeleeToHit; + /* Class starting chance to hit when using ranged weapons (used as a %) */ + uint8_t baseRangedToHit; + /* Class starting chance to hit when using spells (used as a %) */ + uint8_t baseMagicToHit; +}; + +/** + * @brief Data used to set known skills and provide initial equipment when starting a new game + * + * Items will be created in order starting with item 1, 2, etc. If the item can be equipped it + * will be placed in the first available slot, otherwise if it fits on the belt it will be + * placed in the first free space, finally being placed in the first free inventory position. + * + * The active game mode at the time we're creating a new character controls the choice of item + * type. ItemType.hellfire is used if we're in Hellfire mode, ItemType.diablo otherwise. + */ +struct PlayerStartingLoadoutData { /* Class Skill */ SpellID skill; + /* Starting Spell (if any) */ + SpellID spell; + /* Initial level of the starting spell */ + uint8_t spellLevel; + + struct ItemType { + _item_indexes diablo; + _item_indexes hellfire; + }; + + std::array items; + + /* Initial gold amount, up to a single stack (5000 gold) */ + uint16_t gold; }; struct PlayerSpriteData { + /* Class Directory Path */ + const char *classPath; /* Sprite width: Stand */ uint8_t stand; /* Sprite width: Walk */ @@ -135,9 +190,17 @@ struct PlayerAnimData { int8_t castingActionFrame; }; -extern const _sfx_id herosounds[enum_size::value][enum_size::value]; -extern const uint32_t ExpLvlsTbl[MaxCharacterLevel]; -extern const PlayerData PlayersData[]; +/** + * @brief Attempts to load data values from external files, currently only Experience.tsv is supported. + */ +void LoadPlayerDataFiles(); + +extern const SfxID herosounds[enum_size::value][enum_size::value]; +uint32_t GetNextExperienceThresholdForLevel(unsigned level); +uint8_t GetMaximumCharacterLevel(); +const PlayerData &GetPlayerDataForClass(HeroClass clazz); +const PlayerCombatData &GetPlayerCombatDataForClass(HeroClass clazz); +const PlayerStartingLoadoutData &GetPlayerStartingLoadoutForClass(HeroClass clazz); extern const PlayerSpriteData PlayersSpriteData[]; extern const PlayerAnimData PlayersAnimData[]; diff --git a/Source/plrmsg.cpp b/Source/plrmsg.cpp index 84fdd190d00..a1facb39ffb 100644 --- a/Source/plrmsg.cpp +++ b/Source/plrmsg.cpp @@ -15,6 +15,7 @@ #include "inv.h" #include "qol/chatlog.h" #include "qol/stash.h" +#include "utils/algorithm/container.hpp" #include "utils/language.h" #include "utils/utf8.hpp" @@ -23,23 +24,23 @@ namespace devilution { namespace { struct PlayerMessage { - /** Time message was recived */ + /** Time message was received */ Uint32 time; /** The default text color */ UiFlags style; /** The text message to display on screen */ std::string text; - /** First portion of text that should be rendered in gold */ - string_view from; + /** Length of first portion of text that should be rendered in gold */ + size_t prefixLength; /** The line height of the text */ int lineHeight; }; std::array Messages; -int CountLinesOfText(string_view text) +int CountLinesOfText(std::string_view text) { - return 1 + std::count(text.begin(), text.end(), '\n'); + return static_cast(1 + c_count(text, '\n')); } PlayerMessage &GetNextMessage() @@ -51,42 +52,34 @@ PlayerMessage &GetNextMessage() } // namespace -void plrmsg_delay(bool delay) +void DelayPlrMessages(uint32_t delayTime) { - static uint32_t plrmsgTicks; - - if (delay) { - plrmsgTicks = -SDL_GetTicks(); - return; - } - - plrmsgTicks += SDL_GetTicks(); for (PlayerMessage &message : Messages) - message.time += plrmsgTicks; + message.time += delayTime; } -void EventPlrMsg(string_view text, UiFlags style) +void EventPlrMsg(std::string_view text, UiFlags style) { PlayerMessage &message = GetNextMessage(); message.style = style; message.time = SDL_GetTicks(); message.text = std::string(text); - message.from = string_view(message.text.data(), 0); + message.prefixLength = 0; message.lineHeight = GetLineHeight(message.text, GameFont12) + 3; AddMessageToChatLog(text); } -void SendPlrMsg(Player &player, string_view text) +void SendPlrMsg(Player &player, std::string_view text) { PlayerMessage &message = GetNextMessage(); - std::string from = fmt::format(fmt::runtime(_("{:s} (lvl {:d}): ")), player._pName, player._pLevel); + std::string from = fmt::format(fmt::runtime(_("{:s} (lvl {:d}): ")), player._pName, player.getCharacterLevel()); message.style = UiFlags::ColorWhite; message.time = SDL_GetTicks(); message.text = from + std::string(text); - message.from = string_view(message.text.data(), from.size()); + message.prefixLength = from.size(); message.lineHeight = GetLineHeight(message.text, GameFont12) + 3; AddMessageToChatLog(text, &player); } @@ -128,8 +121,13 @@ void DrawPlrMsg(const Surface &out) y -= message.lineHeight * chatlines; DrawHalfTransparentRectTo(out, x - 3, y, width + 6, message.lineHeight * chatlines); - DrawString(out, text, { { x, y }, { width, 0 } }, message.style, 1, message.lineHeight); - DrawString(out, message.from, { { x, y }, { width, 0 } }, UiFlags::ColorWhitegold, 1, message.lineHeight); + + std::vector args { + { std::string_view(text.data(), message.prefixLength), UiFlags::ColorWhitegold }, + { std::string_view(text.data() + message.prefixLength, text.size() - message.prefixLength), message.style } + }; + DrawStringWithColors(out, "{:s}{:s}", args, { { x, y }, { width, 0 } }, + { .flags = UiFlags::None, .lineHeight = message.lineHeight }); } } diff --git a/Source/plrmsg.h b/Source/plrmsg.h index a32f8922616..e8a17b6d2c3 100644 --- a/Source/plrmsg.h +++ b/Source/plrmsg.h @@ -5,20 +5,21 @@ */ #pragma once -#include "SDL.h" #include #include +#include + +#include #include "DiabloUI/ui_flags.hpp" #include "engine.h" #include "player.h" -#include "utils/stdcompat/string_view.hpp" namespace devilution { -void plrmsg_delay(bool delay); -void EventPlrMsg(string_view text, UiFlags style = UiFlags::ColorWhitegold); -void SendPlrMsg(Player &player, string_view text); +void DelayPlrMessages(uint32_t delayTime); +void EventPlrMsg(std::string_view text, UiFlags style = UiFlags::ColorWhitegold); +void SendPlrMsg(Player &player, std::string_view text); void InitPlrMsg(); void DrawPlrMsg(const Surface &out); diff --git a/Source/portal.cpp b/Source/portal.cpp index c730774772d..c663ea84c6e 100644 --- a/Source/portal.cpp +++ b/Source/portal.cpp @@ -21,8 +21,8 @@ namespace { /** Current portal number (a portal array index). */ size_t portalindex; -/** Coordinate of each players portal in town. */ -Point WarpDrop[MAXPORTAL] = { +/** Coordinate of each player's portal in town. */ +Point PortalTownPosition[MAXPORTAL] = { { 57, 40 }, { 59, 40 }, { 61, 40 }, @@ -47,9 +47,9 @@ void SetPortalStats(int i, bool o, Point position, int lvl, dungeon_type lvltype Portals[i].setlvl = isSetLevel; } -void AddWarpMissile(int i, Point position, bool sync) +void AddPortalMissile(const Player &player, Point position, bool sync) { - auto *missile = AddMissile({ 0, 0 }, position, Direction::South, MissileID::TownPortal, TARGET_MONSTERS, i, 0, 0, /*parent=*/nullptr, SFX_NONE); + auto *missile = AddMissile({ 0, 0 }, position, Direction::South, MissileID::TownPortal, TARGET_MONSTERS, player, 0, 0, /*parent=*/nullptr, SfxID::None); if (missile != nullptr) { // Don't show portal opening animation if we sync existing portals if (sync) @@ -65,50 +65,54 @@ void SyncPortals() for (int i = 0; i < MAXPORTAL; i++) { if (!Portals[i].open) continue; + Player &player = Players[i]; if (leveltype == DTYPE_TOWN) - AddWarpMissile(i, WarpDrop[i], true); + AddPortalMissile(player, PortalTownPosition[i], true); else { int lvl = currlevel; if (setlevel) lvl = setlvlnum; if (Portals[i].level == lvl && Portals[i].setlvl == setlevel) - AddWarpMissile(i, Portals[i].position, true); + AddPortalMissile(player, Portals[i].position, true); } } } -void AddInTownPortal(int i) +void AddPortalInTown(const Player &player) { - AddWarpMissile(i, WarpDrop[i], false); + AddPortalMissile(player, PortalTownPosition[player.getId()], false); } -void ActivatePortal(int i, Point position, int lvl, dungeon_type dungeonType, bool isSetLevel) +void ActivatePortal(const Player &player, Point position, int lvl, dungeon_type dungeonType, bool isSetLevel) { - Portals[i].open = true; + Portal &portal = Portals[player.getId()]; + portal.open = true; if (lvl != 0) { - Portals[i].position = position; - Portals[i].level = lvl; - Portals[i].ltype = dungeonType; - Portals[i].setlvl = isSetLevel; + portal.position = position; + portal.level = lvl; + portal.ltype = dungeonType; + portal.setlvl = isSetLevel; } } -void DeactivatePortal(int i) +void DeactivatePortal(const Player &player) { - Portals[i].open = false; + Portals[player.getId()].open = false; } -bool PortalOnLevel(size_t i) +bool PortalOnLevel(const Player &player) { - if (Portals[i].setlvl == setlevel && Portals[i].level == setlevel ? static_cast(setlvlnum) : currlevel) + const Portal &portal = Portals[player.getId()]; + if (portal.setlvl == setlevel && portal.level == (setlevel ? static_cast(setlvlnum) : currlevel)) return true; return leveltype == DTYPE_TOWN; } -void RemovePortalMissile(int id) +void RemovePortalMissile(const Player &player) { + size_t id = player.getId(); Missiles.remove_if([id](Missile &missile) { if (missile._mitype == MissileID::TownPortal && missile._misource == id) { dFlags[missile.position.tile.x][missile.position.tile.y] &= ~DungeonFlag::Missile; @@ -152,14 +156,14 @@ void GetPortalLevel() if (portalindex == MyPlayerId) { NetSendCmd(true, CMD_DEACTIVATEPORTAL); - DeactivatePortal(portalindex); + DeactivatePortal(*MyPlayer); } } void GetPortalLvlPos() { if (leveltype == DTYPE_TOWN) { - ViewPosition = WarpDrop[portalindex] + Displacement { 1, 1 }; + ViewPosition = PortalTownPosition[portalindex] + Displacement { 1, 1 }; } else { ViewPosition = Portals[portalindex].position; diff --git a/Source/portal.h b/Source/portal.h index 69aa21e7d55..30e05f6a8e0 100644 --- a/Source/portal.h +++ b/Source/portal.h @@ -10,6 +10,9 @@ namespace devilution { +// Defined in player.h, forward declared here to allow for functions which operate in the context of a player. +struct Player; + #define MAXPORTAL 4 struct Portal { @@ -24,13 +27,13 @@ extern Portal Portals[MAXPORTAL]; void InitPortals(); void SetPortalStats(int i, bool o, Point position, int lvl, dungeon_type lvltype, bool isSetLevel); -void AddWarpMissile(int i, Point position, bool sync); +void AddPortalMissile(const Player &player, Point position, bool sync); void SyncPortals(); -void AddInTownPortal(int i); -void ActivatePortal(int i, Point position, int lvl, dungeon_type lvltype, bool sp); -void DeactivatePortal(int i); -bool PortalOnLevel(size_t i); -void RemovePortalMissile(int id); +void AddPortalInTown(const Player &player); +void ActivatePortal(const Player &player, Point position, int lvl, dungeon_type lvltype, bool sp); +void DeactivatePortal(const Player &player); +bool PortalOnLevel(const Player &player); +void RemovePortalMissile(const Player &player); void SetCurrentPortal(size_t p); void GetPortalLevel(); void GetPortalLvlPos(); diff --git a/Source/qol/autopickup.cpp b/Source/qol/autopickup.cpp index 8da479d348d..aef078ac6d8 100644 --- a/Source/qol/autopickup.cpp +++ b/Source/qol/autopickup.cpp @@ -3,11 +3,14 @@ * * QoL feature for automatically picking up gold */ +#include "qol/autopickup.h" + +#include #include "inv_iterators.hpp" #include "options.h" #include "player.h" -#include +#include "utils/algorithm/container.hpp" namespace devilution { namespace { @@ -34,8 +37,8 @@ bool HasRoomForGold() int NumMiscItemsInInv(int iMiscId) { - InventoryAndBeltPlayerItemsRange items { *MyPlayer }; - return std::count_if(items.begin(), items.end(), [iMiscId](const Item &item) { return item._iMiscId == iMiscId; }); + return c_count_if(InventoryAndBeltPlayerItemsRange { *MyPlayer }, + [iMiscId](const Item &item) { return item._iMiscId == iMiscId; }); } bool DoPickup(Item item) @@ -44,7 +47,7 @@ bool DoPickup(Item item) return true; if (item._itype == ItemType::Misc - && (AutoPlaceItemInInventory(*MyPlayer, item, false) || AutoPlaceItemInBelt(*MyPlayer, item, false))) { + && (AutoPlaceItemInInventory(*MyPlayer, item) || AutoPlaceItemInBelt(*MyPlayer, item))) { switch (item._iMiscId) { case IMISC_HEAL: return *sgOptions.Gameplay.numHealPotionPickup > NumMiscItemsInInv(item._iMiscId); @@ -100,7 +103,7 @@ void AutoPickup(const Player &player) int itemIndex = dItem[tile.x][tile.y] - 1; auto &item = Items[itemIndex]; if (DoPickup(item)) { - NetSendCmdGItem(true, CMD_REQUESTAGITEM, player.getId(), itemIndex); + NetSendCmdGItem(true, CMD_REQUESTAGITEM, player, itemIndex); item._iRequest = true; } } diff --git a/Source/qol/autopickup.h b/Source/qol/autopickup.h index 089b003e1c8..924ea11c2ab 100644 --- a/Source/qol/autopickup.h +++ b/Source/qol/autopickup.h @@ -6,6 +6,8 @@ #pragma once +#include "player.h" + namespace devilution { void AutoPickup(const Player &player); diff --git a/Source/qol/chatlog.cpp b/Source/qol/chatlog.cpp index fbd635c8b4e..0dc9f42bbf1 100644 --- a/Source/qol/chatlog.cpp +++ b/Source/qol/chatlog.cpp @@ -4,8 +4,9 @@ * Implementation of the in-game chat log. */ #include - +#include #include +#include #include #include @@ -14,9 +15,9 @@ #include "automap.h" #include "chatlog.h" #include "control.h" +#include "diablo_msg.hpp" #include "doom.h" #include "engine/render/text_render.hpp" -#include "error.h" #include "gamemenu.h" #include "help.h" #include "init.h" @@ -24,7 +25,6 @@ #include "minitext.h" #include "stores.h" #include "utils/language.h" -#include "utils/stdcompat/string_view.hpp" namespace devilution { @@ -38,11 +38,10 @@ struct ColoredText { struct MultiColoredText { std::string text; std::vector colors; - int offset = 0; }; bool UnreadFlag = false; -unsigned int SkipLines; +size_t SkipLines; unsigned int MessageCounter = 0; std::vector ChatLogLines; @@ -116,25 +115,35 @@ void ToggleChatLog() } } -void AddMessageToChatLog(string_view message, Player *player, UiFlags flags) +void AddMessageToChatLog(std::string_view message, Player *player, UiFlags flags) { MessageCounter++; time_t timeResult = time(nullptr); const std::tm *localtimeResult = localtime(&timeResult); std::string timestamp = localtimeResult != nullptr ? fmt::format("[#{:d}] {:02}:{:02}:{:02}", MessageCounter, localtimeResult->tm_hour, localtimeResult->tm_min, localtimeResult->tm_sec) : fmt::format("[#{:d}] ", MessageCounter); - int oldSize = ChatLogLines.size(); - ChatLogLines.emplace_back(MultiColoredText { "", { {} } }); + size_t oldSize = ChatLogLines.size(); if (player == nullptr) { ChatLogLines.emplace_back(MultiColoredText { "{0} {1}", { { timestamp, UiFlags::ColorRed }, { std::string(message), flags } } }); } else { - std::string playerInfo = fmt::format(fmt::runtime(_("{:s} (lvl {:d}): ")), player->_pName, player->_pLevel); - ChatLogLines.emplace_back(MultiColoredText { std::string(message), { {} }, 20 }); + std::string playerInfo = fmt::format(fmt::runtime(_("{:s} (lvl {:d}): ")), player->_pName, player->getCharacterLevel()); UiFlags nameColor = player == MyPlayer ? UiFlags::ColorWhitegold : UiFlags::ColorBlue; - ChatLogLines.emplace_back(MultiColoredText { "{0} - {1}", { { timestamp, UiFlags::ColorRed }, { playerInfo, nameColor } } }); + std::string prefix = timestamp + " - " + playerInfo; + std::string text = WordWrapString(prefix + std::string(message), ContentTextWidth); + std::vector lines; + std::stringstream ss(text); + + for (std::string s; getline(ss, s, '\n');) { + lines.push_back(s); + } + for (int i = static_cast(lines.size()) - 1; i >= 1; i--) { + ChatLogLines.emplace_back(MultiColoredText { lines[i], {} }); + } + lines[0].erase(0, prefix.length()); + ChatLogLines.emplace_back(MultiColoredText { "{0} - {1}{2}", { { timestamp, UiFlags::ColorRed }, { playerInfo, nameColor }, { lines[0], UiFlags::ColorWhite } } }); } - unsigned int diff = ChatLogLines.size() - oldSize; + size_t diff = ChatLogLines.size() - oldSize; // only autoscroll when on top of the log if (SkipLines != 0) { SkipLines += diff; @@ -159,13 +168,14 @@ void DrawChatLog(const Surface &out) DrawString(out, fmt::format(fmt::runtime(_("Chat History (Messages: {:d})")), MessageCounter), { { sx, sy + PaddingTop + blankLineHeight }, { ContentTextWidth, lineHeight } }, - (UnreadFlag ? UiFlags::ColorRed : UiFlags::ColorWhitegold) | UiFlags::AlignCenter); + { .flags = (UnreadFlag ? UiFlags::ColorRed : UiFlags::ColorWhitegold) | UiFlags::AlignCenter }); time_t timeResult = time(nullptr); const std::tm *localtimeResult = localtime(&timeResult); if (localtimeResult != nullptr) { std::string timestamp = fmt::format("{:02}:{:02}:{:02}", localtimeResult->tm_hour, localtimeResult->tm_min, localtimeResult->tm_sec); - DrawString(out, timestamp, { { sx, sy + PaddingTop + blankLineHeight }, { ContentTextWidth, lineHeight } }, UiFlags::ColorWhitegold); + DrawString(out, timestamp, { { sx, sy + PaddingTop + blankLineHeight }, { ContentTextWidth, lineHeight } }, + { .flags = UiFlags::ColorWhitegold }); } const int titleBottom = sy + HeaderHeight(); @@ -177,18 +187,19 @@ void DrawChatLog(const Surface &out) if (i + SkipLines >= ChatLogLines.size()) break; MultiColoredText &text = ChatLogLines[ChatLogLines.size() - (i + SkipLines + 1)]; - const string_view line = text.text; + const std::string_view line = text.text; std::vector args; for (auto &x : text.colors) { args.emplace_back(DrawStringFormatArg { x.text, x.color }); } - DrawStringWithColors(out, line, args, { { (sx + text.offset), contentY + i * lineHeight }, { ContentTextWidth - text.offset * 2, lineHeight } }, UiFlags::ColorWhite, /*spacing=*/1, lineHeight); + DrawStringWithColors(out, line, args, { { sx, contentY + i * lineHeight }, { ContentTextWidth, lineHeight } }, + { .flags = UiFlags::ColorWhite, .lineHeight = lineHeight }); } DrawString(out, _("Press ESC to end or the arrow keys to scroll."), { { sx, contentY + ContentsTextHeight() + ContentPaddingY() + blankLineHeight }, { ContentTextWidth, lineHeight } }, - UiFlags::ColorWhitegold | UiFlags::AlignCenter); + { .flags = UiFlags::ColorWhitegold | UiFlags::AlignCenter }); } void ChatLogScrollUp() diff --git a/Source/qol/chatlog.h b/Source/qol/chatlog.h index dd48db35d87..bbe06521e75 100644 --- a/Source/qol/chatlog.h +++ b/Source/qol/chatlog.h @@ -13,7 +13,7 @@ namespace devilution { extern bool ChatLogFlag; void ToggleChatLog(); -void AddMessageToChatLog(string_view message, Player *player = nullptr, UiFlags flags = UiFlags::ColorWhite); +void AddMessageToChatLog(std::string_view message, Player *player = nullptr, UiFlags flags = UiFlags::ColorWhite); void DrawChatLog(const Surface &out); void ChatLogScrollUp(); void ChatLogScrollDown(); diff --git a/Source/qol/floatingnumbers.cpp b/Source/qol/floatingnumbers.cpp index 1aee55f45e2..dc0dfb3725e 100644 --- a/Source/qol/floatingnumbers.cpp +++ b/Source/qol/floatingnumbers.cpp @@ -24,7 +24,7 @@ struct FloatingNumber { UiFlags style; DamageType type; int value; - int index; + size_t index; bool reverseDirection; }; @@ -91,7 +91,7 @@ void UpdateFloatingData(FloatingNumber &num) } } -void AddFloatingNumber(Point pos, Displacement offset, DamageType type, int value, int index, bool damageToPlayer) +void AddFloatingNumber(Point pos, Displacement offset, DamageType type, int value, size_t index, bool damageToPlayer) { // 45 deg angles to avoid jitter caused by px alignment Displacement goodAngles[] = { @@ -190,7 +190,8 @@ void DrawFloatingNumbers(const Surface &out, Point viewPosition, Displacement of float mul = 1 - (timeLeft / 2500.0f); screenPosition += floatingNum.endOffset * mul; - DrawString(out, floatingNum.text, Rectangle { screenPosition, { lineWidth, 0 } }, floatingNum.style); + DrawString(out, floatingNum.text, Rectangle { screenPosition, { lineWidth, 0 } }, + { .flags = floatingNum.style }); } ClearExpiredNumbers(); @@ -198,7 +199,7 @@ void DrawFloatingNumbers(const Surface &out, Point viewPosition, Displacement of void ClearFloatingNumbers() { - srand(time(nullptr)); + srand(static_cast(time(nullptr))); FloatingQueue.clear(); } diff --git a/Source/qol/itemlabels.cpp b/Source/qol/itemlabels.cpp index b409eab5151..7a485129e29 100644 --- a/Source/qol/itemlabels.cpp +++ b/Source/qol/itemlabels.cpp @@ -1,8 +1,10 @@ #include "itemlabels.h" #include +#include #include #include +#include #include #include @@ -16,9 +18,9 @@ #include "options.h" #include "qol/stash.h" #include "stores.h" +#include "utils/algorithm/container.hpp" #include "utils/format_int.hpp" #include "utils/language.h" -#include "utils/stdcompat/string_view.hpp" namespace devilution { @@ -36,11 +38,16 @@ bool highlightKeyPressed = false; bool isLabelHighlighted = false; std::array, ITEMTYPES> labelCenterOffsets; -const int BorderX = 4; // minimal horizontal space between labels -const int BorderY = 2; // minimal vertical space between labels -const int MarginX = 2; // horizontal margins between text and edges of the label -const int MarginY = 1; // vertical margins between text and edges of the label -const int Height = 11 + MarginY * 2; // going above 13 scatters labels of items that are next to each other +const int BorderX = 4; // minimal horizontal space between labels +const int BorderY = 2; // minimal vertical space between labels +const int MarginX = 2; // horizontal margins between text and edges of the label + +// Vertical space between the text and the edges of the label. +int TextMarginTop() { return IsSmallFontTall() ? 1 : -1; } +int TextMarginBottom() { return IsSmallFontTall() ? 1 : 3; } + +// The total height of the label box. +int LabelHeight() { return (IsSmallFontTall() ? 16 : 11) + TextMarginBottom() + TextMarginTop(); } /** * @brief The set of used X coordinates for a certain Y coordinate. @@ -49,7 +56,7 @@ class UsedX { public: [[nodiscard]] bool contains(int val) const { - return std::find(data_.begin(), data_.end(), val) != data_.end(); + return c_find(data_, val) != data_.end(); } void insert(int val) @@ -111,8 +118,8 @@ void AddItemToLabelQueue(int id, Point position) nameWidth += MarginX * 2; int index = ItemCAnimTbl[item._iCurs]; if (!labelCenterOffsets[index]) { - std::pair itemBounds = ClxMeasureSolidHorizontalBounds((*item.AnimInfo.sprites)[item.AnimInfo.currentFrame]); - labelCenterOffsets[index].emplace((itemBounds.first + itemBounds.second) / 2); + const auto [xBegin, xEnd] = ClxMeasureSolidHorizontalBounds((*item.AnimInfo.sprites)[item.AnimInfo.currentFrame]); + labelCenterOffsets[index].emplace((xBegin + xEnd) / 2); } position.x += *labelCenterOffsets[index]; @@ -121,7 +128,7 @@ void AddItemToLabelQueue(int id, Point position) position *= 2; } position.x -= nameWidth / 2; - position.y -= Height; + position.y -= LabelHeight(); labelQueue.push_back(ItemLabel { id, nameWidth, position, std::move(textOnGround) }); } @@ -137,13 +144,6 @@ bool IsMouseOverGameArea() return true; } -void FillRect(const Surface &out, int x, int y, int width, int height, Uint8 col) -{ - for (int j = 0; j < height; j++) { - DrawHorizontalLine(out, { x, y + j }, width, col); - } -} - void DrawItemNameLabels(const Surface &out) { const Surface clippedOut = out.subregionY(0, gnViewportHeight); @@ -151,6 +151,8 @@ void DrawItemNameLabels(const Surface &out) if (labelQueue.empty()) return; UsedX usedX; + const int labelHeight = LabelHeight(); + const int labelMarginTop = TextMarginTop(); for (unsigned i = 0; i < labelQueue.size(); ++i) { usedX.clear(); @@ -161,7 +163,7 @@ void DrawItemNameLabels(const Surface &out) for (unsigned j = 0; j < i; ++j) { ItemLabel &a = labelQueue[i]; ItemLabel &b = labelQueue[j]; - if (abs(b.pos.y - a.pos.y) < Height + BorderY) { + if (std::abs(b.pos.y - a.pos.y) < labelHeight + BorderY) { const int widthA = a.width + BorderX + MarginX * 2; const int widthB = b.width + BorderX + MarginX * 2; int newpos = b.pos.x; @@ -186,7 +188,8 @@ void DrawItemNameLabels(const Surface &out) for (const ItemLabel &label : labelQueue) { Item &item = Items[label.id]; - if (MousePosition.x >= label.pos.x && MousePosition.x < label.pos.x + label.width && MousePosition.y >= label.pos.y + MarginY && MousePosition.y < label.pos.y + MarginY + Height) { + if (MousePosition.x >= label.pos.x && MousePosition.x < label.pos.x + label.width + && MousePosition.y >= label.pos.y && MousePosition.y < label.pos.y + labelHeight) { if (!gmenu_is_active() && PauseMode == 0 && !MyPlayerIsDead @@ -199,10 +202,11 @@ void DrawItemNameLabels(const Surface &out) } } if (pcursitem == label.id && stextflag == TalkID::None) - FillRect(clippedOut, label.pos.x, label.pos.y + MarginY, label.width, Height, PAL8_BLUE + 6); + FillRect(clippedOut, label.pos.x, label.pos.y, label.width, labelHeight, PAL8_BLUE + 6); else - DrawHalfTransparentRectTo(clippedOut, label.pos.x, label.pos.y + MarginY, label.width, Height); - DrawString(clippedOut, label.text, { { label.pos.x + MarginX, label.pos.y }, { label.width, Height } }, item.getTextColor()); + DrawHalfTransparentRectTo(clippedOut, label.pos.x, label.pos.y, label.width, labelHeight); + DrawString(clippedOut, label.text, { { label.pos.x + MarginX, label.pos.y + labelMarginTop }, { label.width, labelHeight } }, + { .flags = item.getTextColor() }); } labelQueue.clear(); } diff --git a/Source/qol/monhealthbar.cpp b/Source/qol/monhealthbar.cpp index 61657db83c5..978721baebd 100644 --- a/Source/qol/monhealthbar.cpp +++ b/Source/qol/monhealthbar.cpp @@ -130,17 +130,20 @@ void DrawMonsterHealthBar(const Surface &out) } UiFlags style = UiFlags::AlignCenter | UiFlags::VerticalCenter; - DrawString(out, monster.name(), { position + Displacement { -1, 1 }, { width, height } }, style | UiFlags::ColorBlack); + DrawString(out, monster.name(), { position + Displacement { -1, 1 }, { width, height } }, + { .flags = style | UiFlags::ColorBlack }); if (monster.isUnique()) style |= UiFlags::ColorWhitegold; else if (monster.leader != Monster::NoLeader) style |= UiFlags::ColorBlue; else style |= UiFlags::ColorWhite; - DrawString(out, monster.name(), { position, { width, height } }, style); + DrawString(out, monster.name(), { position, { width, height } }, + { .flags = style }); if (multiplier > 0) - DrawString(out, StrCat("x", multiplier), { position, { width - 2, height } }, UiFlags::ColorWhite | UiFlags::AlignRight | UiFlags::VerticalCenter); + DrawString(out, StrCat("x", multiplier), { position, { width - 2, height } }, + { .flags = UiFlags::ColorWhite | UiFlags::AlignRight | UiFlags::VerticalCenter }); if (monster.isUnique() || MonsterKillCounts[monster.type().type] >= 15) { monster_resistance immunes[] = { IMMUNE_MAGIC, IMMUNE_FIRE, IMMUNE_LIGHTNING }; monster_resistance resists[] = { RESIST_MAGIC, RESIST_FIRE, RESIST_LIGHTNING }; diff --git a/Source/qol/stash.cpp b/Source/qol/stash.cpp index 14e5105e3a2..ab573d5861a 100644 --- a/Source/qol/stash.cpp +++ b/Source/qol/stash.cpp @@ -1,10 +1,12 @@ #include "qol/stash.h" +#include #include #include #include +#include "DiabloUI/text_input.hpp" #include "control.h" #include "controls/plrctrls.h" #include "cursor.h" @@ -16,11 +18,11 @@ #include "engine/render/text_render.hpp" #include "engine/size.hpp" #include "hwcursor.hpp" +#include "inv.h" #include "minitext.h" #include "stores.h" #include "utils/format_int.hpp" #include "utils/language.h" -#include "utils/stdcompat/algorithm.hpp" #include "utils/str_cat.hpp" #include "utils/utf8.hpp" @@ -29,14 +31,15 @@ namespace devilution { bool IsStashOpen; StashStruct Stash; bool IsWithdrawGoldOpen; -int WithdrawGoldValue; namespace { constexpr unsigned CountStashPages = 100; constexpr unsigned LastStashPage = CountStashPages - 1; -int InitialWithdrawGoldValue; +char GoldWithdrawText[21]; +TextInputCursorState GoldWithdrawCursor; +std::optional GoldWithdrawInputState; constexpr Size ButtonSize { 27, 16 }; /** Contains mappings for the buttons in the stash (2 navigation buttons, withdraw gold buttons, 2 navigation buttons) */ @@ -50,7 +53,8 @@ constexpr Rectangle StashButtonRect[] = { // clang-format on }; -constexpr PointsInRectangleRange StashGridRange { { { 0, 0 }, Size { 10, 10 } } }; +constexpr Size StashGridSize { 10, 10 }; +constexpr PointsInRectangle StashGridRange { { { 0, 0 }, StashGridSize } }; OptionalOwnedClxSpriteList StashPanelArt; OptionalOwnedClxSpriteList StashNavButtonArt; @@ -68,7 +72,7 @@ void AddItemToStashGrid(unsigned page, Point position, uint16_t stashListIndex, } } -Point FindSlotUnderCursor(Point cursorPosition) +std::optional FindTargetSlotUnderItemCursor(Point cursorPosition, Size itemSize) { for (auto point : StashGridRange) { Rectangle cell { @@ -77,11 +81,30 @@ Point FindSlotUnderCursor(Point cursorPosition) }; if (cell.contains(cursorPosition)) { + // When trying to paste into the stash we need to determine the top left cell of the nearest area that could fit the item, not the slot under the center/hot pixel. + if (itemSize.height <= 1 && itemSize.width <= 1) { + // top left cell of a 1x1 item is the same cell as the hot pixel, no work to do + return point; + } + // Otherwise work out how far the central cell is from the top-left cell + Displacement hotPixelCellOffset = { (itemSize.width - 1) / 2, (itemSize.height - 1) / 2 }; + // For even dimension items we need to work out if the cursor is in the left/right (or top/bottom) half of the central cell and adjust the offset so the item lands in the area most covered by the cursor. + if (itemSize.width % 2 == 0 && cell.contains(cursorPosition + Displacement { INV_SLOT_HALF_SIZE_PX, 0 })) { + // hot pixel was in the left half of the cell, so we want to increase the offset to preference the column to the left + hotPixelCellOffset.deltaX++; + } + if (itemSize.height % 2 == 0 && cell.contains(cursorPosition + Displacement { 0, INV_SLOT_HALF_SIZE_PX })) { + // hot pixel was in the top half of the cell, so we want to increase the offset to preference the row above + hotPixelCellOffset.deltaY++; + } + // Then work out the top left cell of the nearest area that could fit this item (as pasting on the edge of the stash would otherwise put it out of bounds) + point.y = std::clamp(point.y - hotPixelCellOffset.deltaY, 0, StashGridSize.height - itemSize.height); + point.x = std::clamp(point.x - hotPixelCellOffset.deltaX, 0, StashGridSize.width - itemSize.width); return point; } } - return InvalidStashPoint; + return {}; } bool IsItemAllowedInStash(const Item &item) @@ -93,14 +116,6 @@ void CheckStashPaste(Point cursorPosition) { Player &player = *MyPlayer; - const Size itemSize = GetInventorySize(player.HoldItem); - const Displacement hotPixelOffset = Displacement(itemSize * INV_SLOT_HALF_SIZE_PX); - if (IsHardwareCursor()) { - // It's more natural to select the top left cell of the region the sprite is overlapping when putting an item - // into an inventory grid, so compensate for the adjusted hot pixel of hardware cursors. - cursorPosition -= hotPixelOffset; - } - if (!IsItemAllowedInStash(player.HoldItem)) return; @@ -109,25 +124,19 @@ void CheckStashPaste(Point cursorPosition) return; Stash.gold += player.HoldItem._ivalue; player.HoldItem.clear(); - PlaySFX(IS_GOLD); + PlaySFX(SfxID::ItemGold); Stash.dirty = true; - if (!IsHardwareCursor()) { - // To make software cursors behave like hardware cursors we need to adjust the hand cursor position manually - SetCursorPos(cursorPosition + hotPixelOffset); - } NewCursor(CURSOR_HAND); return; } - // Make the hot pixel the center of the top-left cell of the item, this favors the cell which contains more of the - // item sprite - Point firstSlot = FindSlotUnderCursor(cursorPosition + Displacement(INV_SLOT_HALF_SIZE_PX)); - if (firstSlot == InvalidStashPoint) + const Size itemSize = GetInventorySize(player.HoldItem); + + std::optional targetSlot = FindTargetSlotUnderItemCursor(cursorPosition, itemSize); + if (!targetSlot) return; - if (firstSlot.x + itemSize.width > 10 || firstSlot.y + itemSize.height > 10) { - return; // Item does not fit - } + Point firstSlot = *targetSlot; // Check that no more than 1 item is replaced by the move StashStruct::StashCell stashIndex = StashStruct::EmptyCell; @@ -144,6 +153,7 @@ void CheckStashPaste(Point cursorPosition) PlaySFX(ItemInvSnds[ItemCAnimTbl[player.HoldItem._iCurs]]); + // Need to set the item anchor position to the bottom left so drawing code functions correctly. player.HoldItem.position = firstSlot + Displacement { 0, itemSize.height - 1 }; if (stashIndex == StashStruct::EmptyCell) { @@ -151,8 +161,9 @@ void CheckStashPaste(Point cursorPosition) // stashList will have at most 10 000 items, up to 65 535 are supported with uint16_t indexes stashIndex = static_cast(Stash.stashList.size() - 1); } else { - // remove item from stash grid + // swap the held item and whatever was in the stash at this position std::swap(Stash.stashList[stashIndex], player.HoldItem); + // then clear the space occupied by the old item for (auto &row : Stash.GetCurrentGrid()) { for (auto &itemId : row) { if (itemId - 1 == stashIndex) @@ -161,14 +172,11 @@ void CheckStashPaste(Point cursorPosition) } } + // Finally mark the area now occupied by the pasted item in the current page/grid. AddItemToStashGrid(Stash.GetPage(), firstSlot, stashIndex, itemSize); Stash.dirty = true; - if (player.HoldItem.isEmpty() && !IsHardwareCursor()) { - // To make software cursors behave like hardware cursors we need to adjust the hand cursor position manually - SetCursorPos(cursorPosition + hotPixelOffset); - } NewCursor(player.HoldItem); } @@ -176,10 +184,7 @@ void CheckStashCut(Point cursorPosition, bool automaticMove) { Player &player = *MyPlayer; - if (IsWithdrawGoldOpen) { - IsWithdrawGoldOpen = false; - WithdrawGoldValue = 0; - } + CloseGoldWithdraw(); Point slot = InvalidStashPoint; @@ -210,10 +215,10 @@ void CheckStashCut(Point cursorPosition, bool automaticMove) if (iv != StashStruct::EmptyCell) { holdItem = Stash.stashList[iv]; if (automaticMove) { - if (CanBePlacedOnBelt(holdItem)) { - automaticallyMoved = AutoPlaceItemInBelt(player, holdItem, true); + if (CanBePlacedOnBelt(player, holdItem)) { + automaticallyMoved = AutoPlaceItemInBelt(player, holdItem, true, true); } else { - automaticallyMoved = automaticallyEquipped = AutoEquip(player, holdItem); + automaticallyMoved = AutoEquip(player, holdItem, true, true); } } @@ -228,12 +233,12 @@ void CheckStashCut(Point cursorPosition, bool automaticMove) if (automaticallyEquipped) { PlaySFX(ItemInvSnds[ItemCAnimTbl[holdItem._iCurs]]); } else if (!automaticMove || automaticallyMoved) { - PlaySFX(IS_IGRAB); + PlaySFX(SfxID::GrabItem); } if (automaticMove) { if (!automaticallyMoved) { - if (CanBePlacedOnBelt(holdItem)) { + if (CanBePlacedOnBelt(player, holdItem)) { player.SaySpecific(HeroSpeech::IHaveNoRoom); } else { player.SaySpecific(HeroSpeech::ICantDoThat); @@ -243,11 +248,6 @@ void CheckStashCut(Point cursorPosition, bool automaticMove) holdItem.clear(); } else { NewCursor(holdItem); - if (!IsHardwareCursor()) { - // For a hardware cursor, we set the "hot point" to the center of the item instead. - Size cursSize = GetInvItemSize(holdItem._iCurs + CURSOR_FIRSTITEM); - SetCursorPos(cursorPosition - Displacement(cursSize / 2)); - } } } } @@ -276,8 +276,6 @@ void FreeStashGFX() void InitStash() { - InitialWithdrawGoldValue = 0; - if (!HeadlessMode) { StashPanelArt = LoadClx("data\\stash.clx"); StashNavButtonArt = LoadClx("data\\stashnavbtns.clx"); @@ -400,8 +398,10 @@ void DrawStash(const Surface &out) Point position = GetPanelPosition(UiPanels::Stash); UiFlags style = UiFlags::VerticalCenter | UiFlags::ColorWhite; - DrawString(out, StrCat(Stash.GetPage() + 1), { position + Displacement { 132, 0 }, { 57, 11 } }, UiFlags::AlignCenter | style); - DrawString(out, FormatInteger(Stash.gold), { position + Displacement { 122, 19 }, { 107, 13 } }, UiFlags::AlignRight | style); + DrawString(out, StrCat(Stash.GetPage() + 1), { position + Displacement { 132, 0 }, { 57, 11 } }, + { .flags = UiFlags::AlignCenter | style }); + DrawString(out, FormatInteger(Stash.gold), { position + Displacement { 122, 19 }, { 107, 13 } }, + { .flags = UiFlags::AlignRight | style }); } void CheckStashItem(Point mousePosition, bool isShiftHeld, bool isCtrlHeld) @@ -473,7 +473,7 @@ bool UseStashItem(uint16_t c) return true; } if (item->IDidx == IDI_FUNGALTM) { - PlaySFX(IS_IBOOK); + PlaySFX(SfxID::ItemBook); MyPlayer->Say(HeroSpeech::ThatDidntDoAnything, SpeechDelay); return true; } @@ -486,10 +486,7 @@ bool UseStashItem(uint16_t c) return true; } - if (IsWithdrawGoldOpen) { - IsWithdrawGoldOpen = false; - WithdrawGoldValue = 0; - } + CloseGoldWithdraw(); if (item->isScroll()) { return true; @@ -500,11 +497,11 @@ bool UseStashItem(uint16_t c) } if (item->_iMiscId == IMISC_BOOK) - PlaySFX(IS_RBOOK); + PlaySFX(SfxID::ReadBook); else PlaySFX(ItemInvSnds[ItemCAnimTbl[item->_iCurs]]); - UseItem(MyPlayerId, item->_iMiscId, item->_iSpell, -1); + UseItem(*MyPlayer, item->_iMiscId, item->_iSpell, -1); if (Stash.stashList[c]._iMiscId == IMISC_MAPOFDOOM) return true; @@ -538,8 +535,7 @@ void StashStruct::RemoveStashItem(StashStruct::StashCell iv) if (lastItemIndex != iv) { stashList[iv] = stashList[lastItemIndex]; - for (auto &pair : Stash.stashGrids) { - auto &grid = pair.second; + for (auto &[_, grid] : Stash.stashGrids) { for (auto &row : grid) { for (StashStruct::StashCell &itemId : row) { if (itemId == lastItemIndex + 1) { @@ -590,8 +586,6 @@ void StartGoldWithdraw() { CloseGoldDrop(); - InitialWithdrawGoldValue = std::min(RoomForGold(), Stash.gold); - if (talkflag) control_reset_talk(); @@ -600,7 +594,16 @@ void StartGoldWithdraw() SDL_SetTextInputRect(&rect); IsWithdrawGoldOpen = true; - WithdrawGoldValue = 0; + GoldWithdrawText[0] = '\0'; + GoldWithdrawInputState.emplace(NumberInputState::Options { + .textOptions { + .value = GoldWithdrawText, + .cursor = &GoldWithdrawCursor, + .maxLength = sizeof(GoldWithdrawText) - 1, + }, + .min = 0, + .max = std::min(RoomForGold(), Stash.gold), + }); SDL_StartTextInput(); } @@ -613,25 +616,32 @@ void WithdrawGoldKeyPress(SDL_Keycode vkey) return; } - if ((vkey == SDLK_RETURN) || (vkey == SDLK_KP_ENTER)) { - if (WithdrawGoldValue > 0) { - WithdrawGold(myPlayer, WithdrawGoldValue); - PlaySFX(IS_GOLD); + switch (vkey) { + case SDLK_RETURN: + case SDLK_KP_ENTER: + if (const int value = GoldWithdrawInputState->value(); value != 0) { + WithdrawGold(myPlayer, value); + PlaySFX(SfxID::ItemGold); } CloseGoldWithdraw(); - } else if (vkey == SDLK_ESCAPE) { + break; + case SDLK_ESCAPE: CloseGoldWithdraw(); - } else if (vkey == SDLK_BACKSPACE) { - WithdrawGoldValue /= 10; + break; + default: + break; } } -void DrawGoldWithdraw(const Surface &out, int amount) +void DrawGoldWithdraw(const Surface &out) { if (!IsWithdrawGoldOpen) { return; } + const std::string_view amountText = GoldWithdrawText; + const TextInputCursorState &cursor = GoldWithdrawCursor; + const int dialogX = 30; ClxDraw(out, GetPanelPosition(UiPanels::Stash, { dialogX, 178 }), (*pGBoxBuff)[0]); @@ -642,38 +652,31 @@ void DrawGoldWithdraw(const Surface &out, int amount) // 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::Stash, { dialogX + 31, 75 }), { 200, 50 } }, UiFlags::ColorWhitegold | UiFlags::AlignCenter, 1, 17); + DrawString(out, wrapped, { GetPanelPosition(UiPanels::Stash, { dialogX + 31, 75 }), { 200, 50 } }, + { .flags = UiFlags::ColorWhitegold | UiFlags::AlignCenter, .lineHeight = 17 }); - std::string value = ""; - if (amount > 0) { - value = StrCat(amount); - } // 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, value, GetPanelPosition(UiPanels::Stash, { dialogX + 37, 128 }), UiFlags::ColorWhite | UiFlags::PentaCursor); + DrawString(out, amountText, GetPanelPosition(UiPanels::Stash, { 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 CloseGoldWithdraw() { if (!IsWithdrawGoldOpen) return; - IsWithdrawGoldOpen = false; - WithdrawGoldValue = 0; SDL_StopTextInput(); + IsWithdrawGoldOpen = false; + GoldWithdrawInputState = std::nullopt; } -void GoldWithdrawNewText(string_view text) +bool HandleGoldWithdrawTextInputEvent(const SDL_Event &event) { - for (char vkey : text) { - int digit = vkey - '0'; - if (digit >= 0 && digit <= 9) { - int newGoldValue = WithdrawGoldValue * 10; - newGoldValue += digit; - if (newGoldValue <= InitialWithdrawGoldValue) { - WithdrawGoldValue = newGoldValue; - } - } - } + return HandleNumberInputEvent(event, *GoldWithdrawInputState); } bool AutoPlaceItemInStash(Player &player, const Item &item, bool persistItem) diff --git a/Source/qol/stash.h b/Source/qol/stash.h index 40e5d45eef2..241a570d902 100644 --- a/Source/qol/stash.h +++ b/Source/qol/stash.h @@ -88,9 +88,9 @@ void CheckStashButtonPress(Point mousePosition); void StartGoldWithdraw(); void WithdrawGoldKeyPress(SDL_Keycode vkey); -void DrawGoldWithdraw(const Surface &out, int amount); +void DrawGoldWithdraw(const Surface &out); void CloseGoldWithdraw(); -void GoldWithdrawNewText(string_view text); +bool HandleGoldWithdrawTextInputEvent(const SDL_Event &event); /** * @brief Checks whether the given item can be placed on the specified player's stash. diff --git a/Source/qol/xpbar.cpp b/Source/qol/xpbar.cpp index 64761060357..6b1efc356b7 100644 --- a/Source/qol/xpbar.cpp +++ b/Source/qol/xpbar.cpp @@ -76,21 +76,21 @@ void DrawXPBar(const Surface &out) RenderClxSprite(out, (*xpbarArt)[0], back); - const int8_t charLevel = player._pLevel; - - if (charLevel == MaxCharacterLevel) { + if (player.isMaxCharacterLevel()) { // Draw a nice golden bar for max level characters. DrawBar(out, position, BarWidth, GoldGradient); return; } - const uint64_t prevXp = ExpLvlsTbl[charLevel - 1]; + const uint8_t charLevel = player.getCharacterLevel(); + + const uint64_t prevXp = GetNextExperienceThresholdForLevel(charLevel - 1); if (player._pExperience < prevXp) return; uint64_t prevXpDelta1 = player._pExperience - prevXp; - uint64_t prevXpDelta = ExpLvlsTbl[charLevel] - prevXp; + uint64_t prevXpDelta = GetNextExperienceThresholdForLevel(charLevel) - prevXp; uint64_t fullBar = BarWidth * prevXpDelta1 / prevXpDelta; // Figure out how much to fill the last pixel of the XP bar, to make it gradually appear with gained XP @@ -100,10 +100,10 @@ void DrawXPBar(const Surface &out) const uint64_t fade = (prevXpDelta1 - lastFullPx) * (SilverGradient.size() - 1) / onePx; // Draw beginning of bar full brightness - DrawBar(out, position, fullBar, SilverGradient); + DrawBar(out, position, static_cast(fullBar), SilverGradient); // End pixels appear gradually - DrawEndCap(out, position + Displacement { static_cast(fullBar), 0 }, fade, SilverGradient); + DrawEndCap(out, position + Displacement { static_cast(fullBar), 0 }, static_cast(fade), SilverGradient); } bool CheckXPBarInfo() @@ -120,15 +120,15 @@ bool CheckXPBarInfo() const Player &player = *MyPlayer; - const int8_t charLevel = player._pLevel; + const uint8_t charLevel = player.getCharacterLevel(); AddPanelString(fmt::format(fmt::runtime(_("Level {:d}")), charLevel)); - if (charLevel == MaxCharacterLevel) { + if (player.isMaxCharacterLevel()) { // Show a maximum level indicator for max level players. InfoColor = UiFlags::ColorWhitegold; - AddPanelString(fmt::format(fmt::runtime(_("Experience: {:s}")), FormatInteger(ExpLvlsTbl[charLevel - 1]))); + AddPanelString(fmt::format(fmt::runtime(_("Experience: {:s}")), FormatInteger(player._pExperience))); AddPanelString(_("Maximum Level")); return true; @@ -137,8 +137,9 @@ bool CheckXPBarInfo() InfoColor = UiFlags::ColorWhite; AddPanelString(fmt::format(fmt::runtime(_("Experience: {:s}")), FormatInteger(player._pExperience))); - AddPanelString(fmt::format(fmt::runtime(_("Next Level: {:s}")), FormatInteger(ExpLvlsTbl[charLevel]))); - AddPanelString(fmt::format(fmt::runtime(_("{:s} to Level {:d}")), FormatInteger(ExpLvlsTbl[charLevel] - player._pExperience), charLevel + 1)); + uint32_t nextExperienceThreshold = player.getNextExperienceThreshold(); + AddPanelString(fmt::format(fmt::runtime(_("Next Level: {:s}")), FormatInteger(nextExperienceThreshold))); + AddPanelString(fmt::format(fmt::runtime(_("{:s} to Level {:d}")), FormatInteger(nextExperienceThreshold - player._pExperience), charLevel + 1)); return true; } diff --git a/Source/quests.cpp b/Source/quests.cpp index 5ba5a67c6a6..53fbf6d84f0 100644 --- a/Source/quests.cpp +++ b/Source/quests.cpp @@ -31,6 +31,10 @@ #include "utils/language.h" #include "utils/utf8.hpp" +#ifdef _DEBUG +#include "debug.h" +#endif + namespace devilution { bool QuestLogIsOpen; @@ -143,7 +147,7 @@ void DrawWarLord(Point position) { auto dunData = LoadFileInMem("levels\\l4data\\warlord2.dun"); - SetPiece = { position, WorldTileSize(SDL_SwapLE16(dunData[0]), SDL_SwapLE16(dunData[1])) }; + SetPiece = { position, GetDunSize(dunData.get()) }; PlaceDunTiles(dunData.get(), position, 6); } @@ -152,7 +156,7 @@ void DrawSChamber(quest_id q, Point position) { auto dunData = LoadFileInMem("levels\\l2data\\bonestr1.dun"); - SetPiece = { position, WorldTileSize(SDL_SwapLE16(dunData[0]), SDL_SwapLE16(dunData[1])) }; + SetPiece = { position, GetDunSize(dunData.get()) }; PlaceDunTiles(dunData.get(), position, 3); @@ -163,16 +167,15 @@ void DrawLTBanner(Point position) { auto dunData = LoadFileInMem("levels\\l1data\\banner1.dun"); - int width = SDL_SwapLE16(dunData[0]); - int height = SDL_SwapLE16(dunData[1]); + WorldTileSize size = GetDunSize(dunData.get()); - SetPiece = { position, WorldTileSize(SDL_SwapLE16(dunData[0]), SDL_SwapLE16(dunData[1])) }; + SetPiece = { position, size }; const uint16_t *tileLayer = &dunData[2]; - for (int j = 0; j < height; j++) { - for (int i = 0; i < width; i++) { - auto tileId = static_cast(SDL_SwapLE16(tileLayer[j * width + i])); + for (WorldTileCoord j = 0; j < size.height; j++) { + for (WorldTileCoord i = 0; i < size.width; i++) { + auto tileId = static_cast(SDL_SwapLE16(tileLayer[j * size.width + i])); if (tileId != 0) { pdungeon[position.x + i][position.y + j] = tileId; } @@ -193,7 +196,7 @@ void DrawBlood(Point position) { auto dunData = LoadFileInMem("levels\\l2data\\blood2.dun"); - SetPiece = { position, WorldTileSize(SDL_SwapLE16(dunData[0]), SDL_SwapLE16(dunData[1])) }; + SetPiece = { position, GetDunSize(dunData.get()) }; PlaceDunTiles(dunData.get(), position, 0); } @@ -214,14 +217,15 @@ int QuestLogMouseToEntry() return -1; } -void PrintQLString(const Surface &out, int x, int y, string_view str, bool marked, bool disabled = false) +void PrintQLString(const Surface &out, int x, int y, std::string_view str, bool marked, bool disabled = false) { int width = GetLineWidth(str); x += std::max((257 - width) / 2, 0); if (marked) { ClxDraw(out, GetPanelPosition(UiPanels::Quest, { x - 20, y + 13 }), (*pSPentSpn2Cels)[PentSpn2Spin()]); } - DrawString(out, str, { GetPanelPosition(UiPanels::Quest, { x, y }), { 257, 0 } }, disabled ? UiFlags::ColorWhitegold : UiFlags::ColorWhite); + DrawString(out, str, { GetPanelPosition(UiPanels::Quest, { x, y }), { 257, 0 } }, + { .flags = disabled ? UiFlags::ColorWhitegold : UiFlags::ColorWhite }); if (marked) { ClxDraw(out, GetPanelPosition(UiPanels::Quest, { x + width + 7, y + 13 }), (*pSPentSpn2Cels)[PentSpn2Spin()]); } @@ -229,7 +233,7 @@ void PrintQLString(const Surface &out, int x, int y, string_view str, bool marke void StartPWaterPurify() { - PlaySfxLoc(IS_QUESTDN, MyPlayer->position.tile); + PlaySfxLoc(SfxID::QuestDone, MyPlayer->position.tile); LoadPalette("levels\\l3data\\l3pwater.pal", false); UpdatePWaterPalette(); WaterDone = 32; @@ -340,7 +344,7 @@ void CheckQuests() && (quest._qactive == QUEST_ACTIVE || quest._qactive == QUEST_DONE) && (quest._qvar2 == 0 || quest._qvar2 == 2)) { // Spawn a portal at the quest trigger location - AddMissile(quest.position, quest.position, Direction::South, MissileID::RedPortal, TARGET_MONSTERS, MyPlayerId, 0, 0); + AddMissile(quest.position, quest.position, Direction::South, MissileID::RedPortal, TARGET_MONSTERS, *MyPlayer, 0, 0); quest._qvar2 = 1; if (quest._qactive == QUEST_ACTIVE && quest._qvar1 == 2) { quest._qvar1 = 3; @@ -352,7 +356,7 @@ void CheckQuests() && setlvlnum == SL_VILEBETRAYER && quest._qvar2 == 4) { Point portalLocation { 35, 32 }; - AddMissile(portalLocation, portalLocation, Direction::South, MissileID::RedPortal, TARGET_MONSTERS, MyPlayerId, 0, 0); + AddMissile(portalLocation, portalLocation, Direction::South, MissileID::RedPortal, TARGET_MONSTERS, *MyPlayer, 0, 0); quest._qvar2 = 3; } @@ -457,7 +461,7 @@ void CheckQuestKill(const Monster &monster, bool sendmsg) } else { InitVPTriggers(); betrayerQuest._qvar2 = 4; - AddMissile({ 35, 32 }, { 35, 32 }, Direction::South, MissileID::RedPortal, TARGET_MONSTERS, MyPlayerId, 0, 0); + AddMissile({ 35, 32 }, { 35, 32 }, Direction::South, MissileID::RedPortal, TARGET_MONSTERS, myPlayer, 0, 0); } if (sendmsg) { NetSendCmdQuest(true, betrayerQuest); @@ -521,6 +525,11 @@ int GetMapReturnLevel() Point GetMapReturnPosition() { +#ifdef _DEBUG + if (!TestMapPath.empty()) + return ViewPosition; +#endif + switch (setlvlnum) { case SL_SKELKING: return Quests[Q_SKELKING].position + Direction::SouthEast; @@ -877,7 +886,7 @@ void QuestlogUp() if (SelectedQuest < 0) { SelectedQuest = FirstFinishedQuest - 1; } - PlaySFX(IS_TITLEMOV); + PlaySFX(SfxID::MenuMove); } } @@ -890,13 +899,13 @@ void QuestlogDown() if (SelectedQuest == FirstFinishedQuest) { SelectedQuest = 0; } - PlaySFX(IS_TITLEMOV); + PlaySFX(SfxID::MenuMove); } } void QuestlogEnter() { - PlaySFX(IS_TITLSLCT); + PlaySFX(SfxID::MenuSelect); if (EncounteredQuestCount != 0 && SelectedQuest >= 0 && SelectedQuest < FirstFinishedQuest) InitQTextMsg(Quests[EncounteredQuests[SelectedQuest]]._qmsg); QuestLogIsOpen = false; diff --git a/Source/sha.h b/Source/sha.h index 689d210ded0..441e6e5ca5d 100644 --- a/Source/sha.h +++ b/Source/sha.h @@ -5,10 +5,9 @@ */ #pragma once +#include #include -#include "utils/stdcompat/cstddef.hpp" - namespace devilution { constexpr size_t BlockSize = 16; diff --git a/Source/spelldat.cpp b/Source/spelldat.cpp index 86d263c11f4..a48c6916060 100644 --- a/Source/spelldat.cpp +++ b/Source/spelldat.cpp @@ -4,7 +4,15 @@ * Implementation of all spell data. */ #include "spelldat.h" -#include "utils/language.h" + +#include +#include + +#include + +#include "data/file.hpp" +#include "data/iterators.hpp" +#include "data/record_reader.hpp" namespace devilution { @@ -14,65 +22,244 @@ const auto Lightning = SpellDataFlags::Lightning; const auto Magic = SpellDataFlags::Magic; const auto Targeted = SpellDataFlags::Targeted; const auto AllowedInTown = SpellDataFlags::AllowedInTown; + +void AddNullSpell() +{ + SpellData &null = SpellsData.emplace_back(); + null.sSFX = SfxID::None; + null.bookCost10 = null.staffCost10 = null.sManaCost = 0; + null.flags = SpellDataFlags::Fire; + null.sBookLvl = null.sStaffLvl = 0; + null.minInt = 0; + null.sMissiles[0] = null.sMissiles[1] = MissileID::Null; + null.sManaAdj = null.sMinMana = 0; + null.sStaffMin = 40; + null.sStaffMax = 80; +} + +// A temporary solution for parsing soundID until we have a more general one. +tl::expected ParseSpellSoundId(std::string_view value) +{ + if (value == "CastFire") return SfxID::CastFire; + if (value == "CastHealing") return SfxID::CastHealing; + if (value == "CastLightning") return SfxID::CastLightning; + if (value == "CastSkill") return SfxID::CastSkill; + return tl::make_unexpected("Unknown enum value (only a few are supported for now)"); +} + +tl::expected ParseSpellDataFlag(std::string_view value) +{ + if (value == "Fire") return SpellDataFlags::Fire; + if (value == "Lightning") return SpellDataFlags::Lightning; + if (value == "Magic") return SpellDataFlags::Magic; + if (value == "Targeted") return SpellDataFlags::Targeted; + if (value == "AllowedInTown") return SpellDataFlags::AllowedInTown; + return tl::make_unexpected("Unknown enum value"); +} + +tl::expected ParseMissileId(std::string_view value) +{ + if (value == "Arrow") return MissileID::Arrow; + if (value == "Firebolt") return MissileID::Firebolt; + if (value == "Guardian") return MissileID::Guardian; + if (value == "Phasing") return MissileID::Phasing; + if (value == "NovaBall") return MissileID::NovaBall; + if (value == "FireWall") return MissileID::FireWall; + if (value == "Fireball") return MissileID::Fireball; + if (value == "LightningControl") return MissileID::LightningControl; + if (value == "Lightning") return MissileID::Lightning; + if (value == "MagmaBallExplosion") return MissileID::MagmaBallExplosion; + if (value == "TownPortal") return MissileID::TownPortal; + if (value == "FlashBottom") return MissileID::FlashBottom; + if (value == "FlashTop") return MissileID::FlashTop; + if (value == "ManaShield") return MissileID::ManaShield; + if (value == "FlameWave") return MissileID::FlameWave; + if (value == "ChainLightning") return MissileID::ChainLightning; + if (value == "ChainBall") return MissileID::ChainBall; + if (value == "BloodHit") return MissileID::BloodHit; + if (value == "BoneHit") return MissileID::BoneHit; + if (value == "MetalHit") return MissileID::MetalHit; + if (value == "Rhino") return MissileID::Rhino; + if (value == "MagmaBall") return MissileID::MagmaBall; + if (value == "ThinLightningControl") return MissileID::ThinLightningControl; + if (value == "ThinLightning") return MissileID::ThinLightning; + if (value == "BloodStar") return MissileID::BloodStar; + if (value == "BloodStarExplosion") return MissileID::BloodStarExplosion; + if (value == "Teleport") return MissileID::Teleport; + if (value == "FireArrow") return MissileID::FireArrow; + if (value == "DoomSerpents") return MissileID::DoomSerpents; + if (value == "FireOnly") return MissileID::FireOnly; + if (value == "StoneCurse") return MissileID::StoneCurse; + if (value == "BloodRitual") return MissileID::BloodRitual; + if (value == "Invisibility") return MissileID::Invisibility; + if (value == "Golem") return MissileID::Golem; + if (value == "Etherealize") return MissileID::Etherealize; + if (value == "Spurt") return MissileID::Spurt; + if (value == "ApocalypseBoom") return MissileID::ApocalypseBoom; + if (value == "Healing") return MissileID::Healing; + if (value == "FireWallControl") return MissileID::FireWallControl; + if (value == "Infravision") return MissileID::Infravision; + if (value == "Identify") return MissileID::Identify; + if (value == "FlameWaveControl") return MissileID::FlameWaveControl; + if (value == "Nova") return MissileID::Nova; + if (value == "Rage") return MissileID::Rage; + if (value == "Apocalypse") return MissileID::Apocalypse; + if (value == "ItemRepair") return MissileID::ItemRepair; + if (value == "StaffRecharge") return MissileID::StaffRecharge; + if (value == "TrapDisarm") return MissileID::TrapDisarm; + if (value == "Inferno") return MissileID::Inferno; + if (value == "InfernoControl") return MissileID::InfernoControl; + if (value == "FireMan") return MissileID::FireMan; + if (value == "Krull") return MissileID::Krull; + if (value == "ChargedBolt") return MissileID::ChargedBolt; + if (value == "HolyBolt") return MissileID::HolyBolt; + if (value == "Resurrect") return MissileID::Resurrect; + if (value == "Telekinesis") return MissileID::Telekinesis; + if (value == "LightningArrow") return MissileID::LightningArrow; + if (value == "Acid") return MissileID::Acid; + if (value == "AcidSplat") return MissileID::AcidSplat; + if (value == "AcidPuddle") return MissileID::AcidPuddle; + if (value == "HealOther") return MissileID::HealOther; + if (value == "Elemental") return MissileID::Elemental; + if (value == "ResurrectBeam") return MissileID::ResurrectBeam; + if (value == "BoneSpirit") return MissileID::BoneSpirit; + if (value == "WeaponExplosion") return MissileID::WeaponExplosion; + if (value == "RedPortal") return MissileID::RedPortal; + if (value == "DiabloApocalypseBoom") return MissileID::DiabloApocalypseBoom; + if (value == "DiabloApocalypse") return MissileID::DiabloApocalypse; + if (value == "Mana") return MissileID::Mana; + if (value == "Magi") return MissileID::Magi; + if (value == "LightningWall") return MissileID::LightningWall; + if (value == "LightningWallControl") return MissileID::LightningWallControl; + if (value == "Immolation") return MissileID::Immolation; + if (value == "SpectralArrow") return MissileID::SpectralArrow; + if (value == "FireballBow") return MissileID::FireballBow; + if (value == "LightningBow") return MissileID::LightningBow; + if (value == "ChargedBoltBow") return MissileID::ChargedBoltBow; + if (value == "HolyBoltBow") return MissileID::HolyBoltBow; + if (value == "Warp") return MissileID::Warp; + if (value == "Reflect") return MissileID::Reflect; + if (value == "Berserk") return MissileID::Berserk; + if (value == "RingOfFire") return MissileID::RingOfFire; + if (value == "StealPotions") return MissileID::StealPotions; + if (value == "StealMana") return MissileID::StealMana; + if (value == "RingOfLightning") return MissileID::RingOfLightning; + if (value == "Search") return MissileID::Search; + if (value == "Aura") return MissileID::Aura; + if (value == "Aura2") return MissileID::Aura2; + if (value == "SpiralFireball") return MissileID::SpiralFireball; + if (value == "RuneOfFire") return MissileID::RuneOfFire; + if (value == "RuneOfLight") return MissileID::RuneOfLight; + if (value == "RuneOfNova") return MissileID::RuneOfNova; + if (value == "RuneOfImmolation") return MissileID::RuneOfImmolation; + if (value == "RuneOfStone") return MissileID::RuneOfStone; + if (value == "BigExplosion") return MissileID::BigExplosion; + if (value == "HorkSpawn") return MissileID::HorkSpawn; + if (value == "Jester") return MissileID::Jester; + if (value == "OpenNest") return MissileID::OpenNest; + if (value == "OrangeFlare") return MissileID::OrangeFlare; + if (value == "BlueFlare") return MissileID::BlueFlare; + if (value == "RedFlare") return MissileID::RedFlare; + if (value == "YellowFlare") return MissileID::YellowFlare; + if (value == "BlueFlare2") return MissileID::BlueFlare2; + if (value == "YellowExplosion") return MissileID::YellowExplosion; + if (value == "RedExplosion") return MissileID::RedExplosion; + if (value == "BlueExplosion") return MissileID::BlueExplosion; + if (value == "BlueExplosion2") return MissileID::BlueExplosion2; + if (value == "OrangeExplosion") return MissileID::OrangeExplosion; + return tl::make_unexpected("Unknown enum value"); +} + } // namespace /** Data related to each spell ID. */ -const SpellData SpellsData[] = { - // clang-format off -// id sNameText, sSFX, bookCost10, staffCost10, sManaCost, flags, sBookLvl, sStaffLvl, minInt, { sMissiles[2] } sManaAdj, sMinMana, sStaffMin, sStaffMax -/*SpellID::Null*/ { nullptr, SFX_NONE, 0, 0, 0, Fire, 0, 0, 0, { MissileID::Null, MissileID::Null, }, 0, 0, 40, 80 }, -/*SpellID::Firebolt*/ { P_("spell", "Firebolt"), IS_CAST2, 100, 5, 6, Fire | Targeted, 1, 1, 15, { MissileID::Firebolt, MissileID::Null, }, 1, 3, 40, 80 }, -/*SpellID::Healing*/ { P_("spell", "Healing"), IS_CAST8, 100, 5, 5, Magic | AllowedInTown, 1, 1, 17, { MissileID::Healing, MissileID::Null, }, 3, 1, 20, 40 }, -/*SpellID::Lightning*/ { P_("spell", "Lightning"), IS_CAST4, 300, 15, 10, Lightning | Targeted, 4, 3, 20, { MissileID::LightningControl, MissileID::Null, }, 1, 6, 20, 60 }, -/*SpellID::Flash*/ { P_("spell", "Flash"), IS_CAST4, 750, 50, 30, Lightning, 5, 4, 33, { MissileID::FlashBottom, MissileID::FlashTop }, 2, 16, 20, 40 }, -/*SpellID::Identify*/ { P_("spell", "Identify"), IS_CAST6, 0, 10, 13, Magic | AllowedInTown, -1, -1, 23, { MissileID::Identify, MissileID::Null, }, 2, 1, 8, 12 }, -/*SpellID::FireWall*/ { P_("spell", "Fire Wall"), IS_CAST2, 600, 40, 28, Fire | Targeted, 3, 2, 27, { MissileID::FireWallControl, MissileID::Null, }, 2, 16, 8, 16 }, -/*SpellID::TownPortal*/ { P_("spell", "Town Portal"), IS_CAST6, 300, 20, 35, Magic | Targeted, 3, 3, 20, { MissileID::TownPortal, MissileID::Null, }, 3, 18, 8, 12 }, -/*SpellID::StoneCurse*/ { P_("spell", "Stone Curse"), IS_CAST2, 1200, 80, 60, Magic | Targeted, 6, 5, 51, { MissileID::StoneCurse, MissileID::Null, }, 3, 40, 8, 16 }, -/*SpellID::Infravision*/ { P_("spell", "Infravision"), IS_CAST8, 0, 60, 40, Magic, -1, -1, 36, { MissileID::Infravision, MissileID::Null, }, 5, 20, 0, 0 }, -/*SpellID::Phasing*/ { P_("spell", "Phasing"), IS_CAST2, 350, 20, 12, Magic, 7, 6, 39, { MissileID::Phasing, MissileID::Null, }, 2, 4, 40, 80 }, -/*SpellID::ManaShield*/ { P_("spell", "Mana Shield"), IS_CAST2, 1600, 120, 33, Magic, 6, 5, 25, { MissileID::ManaShield, MissileID::Null, }, 0, 33, 4, 10 }, -/*SpellID::Fireball*/ { P_("spell", "Fireball"), IS_CAST2, 800, 30, 16, Fire | Targeted, 8, 7, 48, { MissileID::Fireball, MissileID::Null, }, 1, 10, 40, 80 }, -/*SpellID::Guardian*/ { P_("spell", "Guardian"), IS_CAST2, 1400, 95, 50, Fire | Targeted, 9, 8, 61, { MissileID::Guardian, MissileID::Null, }, 2, 30, 16, 32 }, -/*SpellID::ChainLightning*/ { P_("spell", "Chain Lightning"), IS_CAST2, 1100, 75, 30, Lightning, 8, 7, 54, { MissileID::ChainLightning, MissileID::Null, }, 1, 18, 20, 60 }, -/*SpellID::FlameWave*/ { P_("spell", "Flame Wave"), IS_CAST2, 1000, 65, 35, Fire | Targeted, 9, 8, 54, { MissileID::FlameWaveControl, MissileID::Null, }, 3, 20, 20, 40 }, -/*SpellID::DoomSerpents*/ { P_("spell", "Doom Serpents"), IS_CAST2, 0, 0, 0, Lightning, -1, -1, 0, { MissileID::Null, MissileID::Null, }, 0, 0, 40, 80 }, -/*SpellID::BloodRitual*/ { P_("spell", "Blood Ritual"), IS_CAST2, 0, 0, 0, Magic, -1, -1, 0, { MissileID::Null, MissileID::Null, }, 0, 0, 40, 80 }, -/*SpellID::Nova*/ { P_("spell", "Nova"), IS_CAST4, 2100, 130, 60, Magic, 14, 10, 87, { MissileID::Nova, MissileID::Null, }, 3, 35, 16, 32 }, -/*SpellID::Invisibility*/ { P_("spell", "Invisibility"), IS_CAST2, 0, 0, 0, Magic, -1, -1, 0, { MissileID::Null, MissileID::Null, }, 0, 0, 40, 80 }, -/*SpellID::Inferno*/ { P_("spell", "Inferno"), IS_CAST2, 200, 10, 11, Fire | Targeted, 3, 2, 20, { MissileID::InfernoControl, MissileID::Null, }, 1, 6, 20, 40 }, -/*SpellID::Golem*/ { P_("spell", "Golem"), IS_CAST2, 1800, 110, 100, Fire | Targeted, 11, 9, 81, { MissileID::Golem, MissileID::Null, }, 6, 60, 16, 32 }, -/*SpellID::Rage*/ { P_("spell", "Rage"), IS_CAST8, 0, 0, 15, Magic, -1, -1, 0, { MissileID::Rage, MissileID::Null, }, 1, 1, 0, 0 }, -/*SpellID::Teleport*/ { P_("spell", "Teleport"), IS_CAST6, 2000, 125, 35, Magic | Targeted, 14, 12, 105, { MissileID::Teleport, MissileID::Null, }, 3, 15, 16, 32 }, -/*SpellID::Apocalypse*/ { P_("spell", "Apocalypse"), IS_CAST2, 3000, 200, 150, Fire, 19, 15, 149, { MissileID::Apocalypse, MissileID::Null, }, 6, 90, 8, 12 }, -/*SpellID::Etherealize*/ { P_("spell", "Etherealize"), IS_CAST2, 2600, 160, 100, Magic, -1, -1, 93, { MissileID::Etherealize, MissileID::Null, }, 0, 100, 2, 6 }, -/*SpellID::ItemRepair*/ { P_("spell", "Item Repair"), IS_CAST6, 0, 0, 0, Magic | AllowedInTown, -1, -1, 255, { MissileID::ItemRepair, MissileID::Null, }, 0, 0, 40, 80 }, -/*SpellID::StaffRecharge*/ { P_("spell", "Staff Recharge"), IS_CAST6, 0, 0, 0, Magic | AllowedInTown, -1, -1, 255, { MissileID::StaffRecharge, MissileID::Null, }, 0, 0, 40, 80 }, -/*SpellID::TrapDisarm*/ { P_("spell", "Trap Disarm"), IS_CAST6, 0, 0, 0, Magic, -1, -1, 255, { MissileID::TrapDisarm, MissileID::Null, }, 0, 0, 40, 80 }, -/*SpellID::Elemental*/ { P_("spell", "Elemental"), IS_CAST2, 1050, 70, 35, Fire, 8, 6, 68, { MissileID::Elemental, MissileID::Null, }, 2, 20, 20, 60 }, -/*SpellID::ChargedBolt*/ { P_("spell", "Charged Bolt"), IS_CAST2, 100, 5, 6, Lightning | Targeted, 1, 1, 25, { MissileID::ChargedBolt, MissileID::Null, }, 1, 6, 40, 80 }, -/*SpellID::HolyBolt*/ { P_("spell", "Holy Bolt"), IS_CAST2, 100, 5, 7, Magic | Targeted, 1, 1, 20, { MissileID::HolyBolt, MissileID::Null, }, 1, 3, 40, 80 }, -/*SpellID::Resurrect*/ { P_("spell", "Resurrect"), IS_CAST8, 400, 25, 20, Magic | AllowedInTown, -1, 5, 30, { MissileID::Resurrect, MissileID::Null, }, 0, 20, 4, 10 }, -/*SpellID::Telekinesis*/ { P_("spell", "Telekinesis"), IS_CAST2, 250, 20, 15, Magic, 2, 2, 33, { MissileID::Telekinesis, MissileID::Null, }, 2, 8, 20, 40 }, -/*SpellID::HealOther*/ { P_("spell", "Heal Other"), IS_CAST8, 100, 5, 5, Magic | AllowedInTown, 1, 1, 17, { MissileID::HealOther, MissileID::Null, }, 3, 1, 20, 40 }, -/*SpellID::BloodStar*/ { P_("spell", "Blood Star"), IS_CAST2, 2750, 180, 25, Magic, 14, 13, 70, { MissileID::BloodStar, MissileID::Null, }, 2, 14, 20, 60 }, -/*SpellID::BoneSpirit*/ { P_("spell", "Bone Spirit"), IS_CAST2, 1150, 80, 24, Magic, 9, 7, 34, { MissileID::BoneSpirit, MissileID::Null, }, 1, 12, 20, 60 }, -/*SpellID::Mana*/ { P_("spell", "Mana"), IS_CAST8, 100, 5, 255, Magic | AllowedInTown, -1, 5, 17, { MissileID::Mana, MissileID::Null, }, 3, 1, 12, 24 }, -/*SpellID::Magi*/ { P_("spell", "the Magi"), IS_CAST8, 10000, 20, 255, Magic | AllowedInTown, -1, 20, 45, { MissileID::Magi, MissileID::Null, }, 3, 1, 15, 30 }, -/*SpellID::Jester*/ { P_("spell", "the Jester"), IS_CAST8, 10000, 20, 255, Magic | Targeted, -1, 4, 30, { MissileID::Jester, MissileID::Null, }, 3, 1, 15, 30 }, -/*SpellID::LightningWall*/ { P_("spell", "Lightning Wall"), IS_CAST4, 600, 40, 28, Lightning | Targeted, 3, 2, 27, { MissileID::LightningWallControl, MissileID::Null, }, 2, 16, 8, 16 }, -/*SpellID::Immolation*/ { P_("spell", "Immolation"), IS_CAST2, 2100, 130, 60, Fire, 14, 10, 87, { MissileID::Immolation, MissileID::Null, }, 3, 35, 16, 32 }, -/*SpellID::Warp*/ { P_("spell", "Warp"), IS_CAST6, 300, 20, 35, Magic, 3, 3, 25, { MissileID::Warp, MissileID::Null, }, 3, 18, 8, 12 }, -/*SpellID::Reflect*/ { P_("spell", "Reflect"), IS_CAST6, 300, 20, 35, Magic, 3, 3, 25, { MissileID::Reflect, MissileID::Null, }, 3, 15, 8, 12 }, -/*SpellID::Berserk*/ { P_("spell", "Berserk"), IS_CAST6, 300, 20, 35, Magic | Targeted, 3, 3, 35, { MissileID::Berserk, MissileID::Null, }, 3, 15, 8, 12 }, -/*SpellID::RingOfFire*/ { P_("spell", "Ring of Fire"), IS_CAST2, 600, 40, 28, Fire, 5, 5, 27, { MissileID::RingOfFire, MissileID::Null, }, 2, 16, 8, 16 }, -/*SpellID::Search*/ { P_("spell", "Search"), IS_CAST6, 300, 20, 15, Magic, 1, 3, 25, { MissileID::Search, MissileID::Null, }, 1, 1, 8, 12 }, -/*SpellID::RuneOfFire*/ { P_("spell", "Rune of Fire"), IS_CAST8, 800, 30, 255, Magic | Targeted, -1, -1, 48, { MissileID::RuneOfFire, MissileID::Null, }, 1, 10, 40, 80 }, -/*SpellID::RuneOfLight*/ { P_("spell", "Rune of Light"), IS_CAST8, 800, 30, 255, Magic | Targeted, -1, -1, 48, { MissileID::RuneOfLight, MissileID::Null, }, 1, 10, 40, 80 }, -/*SpellID::RuneOfNova*/ { P_("spell", "Rune of Nova"), IS_CAST8, 800, 30, 255, Magic | Targeted, -1, -1, 48, { MissileID::RuneOfNova, MissileID::Null, }, 1, 10, 40, 80 }, -/*SpellID::RuneOfImmolation*/ { P_("spell", "Rune of Immolation"), IS_CAST8, 800, 30, 255, Magic | Targeted, -1, -1, 48, { MissileID::RuneOfImmolation, MissileID::Null, }, 1, 10, 40, 80 }, -/*SpellID::RuneOfStone*/ { P_("spell", "Rune of Stone"), IS_CAST8, 800, 30, 255, Magic | Targeted, -1, -1, 48, { MissileID::RuneOfStone, MissileID::Null, }, 1, 10, 40, 80 }, - // clang-format on -}; +std::vector SpellsData; + +tl::expected ParseSpellId(std::string_view value) +{ + if (value == "Null") return SpellID::Null; + if (value == "Firebolt") return SpellID::Firebolt; + if (value == "Healing") return SpellID::Healing; + if (value == "Lightning") return SpellID::Lightning; + if (value == "Flash") return SpellID::Flash; + if (value == "Identify") return SpellID::Identify; + if (value == "FireWall") return SpellID::FireWall; + if (value == "TownPortal") return SpellID::TownPortal; + if (value == "StoneCurse") return SpellID::StoneCurse; + if (value == "Infravision") return SpellID::Infravision; + if (value == "Phasing") return SpellID::Phasing; + if (value == "ManaShield") return SpellID::ManaShield; + if (value == "Fireball") return SpellID::Fireball; + if (value == "Guardian") return SpellID::Guardian; + if (value == "ChainLightning") return SpellID::ChainLightning; + if (value == "FlameWave") return SpellID::FlameWave; + if (value == "DoomSerpents") return SpellID::DoomSerpents; + if (value == "BloodRitual") return SpellID::BloodRitual; + if (value == "Nova") return SpellID::Nova; + if (value == "Invisibility") return SpellID::Invisibility; + if (value == "Inferno") return SpellID::Inferno; + if (value == "Golem") return SpellID::Golem; + if (value == "Rage") return SpellID::Rage; + if (value == "Teleport") return SpellID::Teleport; + if (value == "Apocalypse") return SpellID::Apocalypse; + if (value == "Etherealize") return SpellID::Etherealize; + if (value == "ItemRepair") return SpellID::ItemRepair; + if (value == "StaffRecharge") return SpellID::StaffRecharge; + if (value == "TrapDisarm") return SpellID::TrapDisarm; + if (value == "Elemental") return SpellID::Elemental; + if (value == "ChargedBolt") return SpellID::ChargedBolt; + if (value == "HolyBolt") return SpellID::HolyBolt; + if (value == "Resurrect") return SpellID::Resurrect; + if (value == "Telekinesis") return SpellID::Telekinesis; + if (value == "HealOther") return SpellID::HealOther; + if (value == "BloodStar") return SpellID::BloodStar; + if (value == "BoneSpirit") return SpellID::BoneSpirit; + if (value == "Mana") return SpellID::Mana; + if (value == "Magi") return SpellID::Magi; + if (value == "Jester") return SpellID::Jester; + if (value == "LightningWall") return SpellID::LightningWall; + if (value == "Immolation") return SpellID::Immolation; + if (value == "Warp") return SpellID::Warp; + if (value == "Reflect") return SpellID::Reflect; + if (value == "Berserk") return SpellID::Berserk; + if (value == "RingOfFire") return SpellID::RingOfFire; + if (value == "Search") return SpellID::Search; + if (value == "RuneOfFire") return SpellID::RuneOfFire; + if (value == "RuneOfLight") return SpellID::RuneOfLight; + if (value == "RuneOfNova") return SpellID::RuneOfNova; + if (value == "RuneOfImmolation") return SpellID::RuneOfImmolation; + if (value == "RuneOfStone") return SpellID::RuneOfStone; + return tl::make_unexpected("Unknown enum value"); +} + +void LoadSpellData() +{ + SpellsData.clear(); + const std::string_view filename = "txtdata\\spells\\spelldat.tsv"; + DataFile dataFile = DataFile::loadOrDie(filename); + SpellsData.reserve(dataFile.numRecords() + 1); + AddNullSpell(); + dataFile.skipHeaderOrDie(filename); + for (DataFileRecord record : dataFile) { + RecordReader reader { record, filename }; + SpellData &item = SpellsData.emplace_back(); + reader.advance(); // skip id + reader.readString("name", item.sNameText); + reader.read("soundId", item.sSFX, ParseSpellSoundId); + reader.readInt("bookCost10", item.bookCost10); + reader.readInt("staffCost10", item.staffCost10); + reader.readInt("manaCost", item.sManaCost); + reader.readEnumList("flags", item.flags, ParseSpellDataFlag); + reader.readInt("bookLevel", item.sBookLvl); + reader.readInt("staffLevel", item.sStaffLvl); + reader.readInt("minIntelligence", item.minInt); + reader.readEnumArray("missiles", /*fillMissing=*/std::make_optional(MissileID::Null), item.sMissiles, ParseMissileId); + reader.readInt("manaMultiplier", item.sManaAdj); + reader.readInt("minMana", item.sMinMana); + reader.readInt("staffMin", item.sStaffMin); + reader.readInt("staffMax", item.sStaffMax); + } + SpellsData.shrink_to_fit(); +} } // namespace devilution diff --git a/Source/spelldat.h b/Source/spelldat.h index aec379e4d23..3e9db791279 100644 --- a/Source/spelldat.h +++ b/Source/spelldat.h @@ -6,7 +6,12 @@ #pragma once #include +#include +#include #include +#include + +#include #include "effects.h" #include "utils/enum_traits.h" @@ -85,6 +90,8 @@ enum class SpellID : int8_t { Invalid = -1, }; +tl::expected ParseSpellId(std::string_view value); + enum class MagicType : uint8_t { Fire, Lightning, @@ -216,8 +223,8 @@ enum class SpellDataFlags : uint8_t { use_enum_as_flags(SpellDataFlags); struct SpellData { - const char *sNameText; - _sfx_id sSFX; + std::string sNameText; + SfxID sSFX; uint16_t bookCost10; uint8_t staffCost10; uint8_t sManaCost; @@ -257,11 +264,13 @@ struct SpellData { } }; -extern const SpellData SpellsData[]; +extern std::vector SpellsData; inline const SpellData &GetSpellData(SpellID spellId) { return SpellsData[static_cast::type>(spellId)]; } +void LoadSpellData(); + } // namespace devilution diff --git a/Source/spells.cpp b/Source/spells.cpp index 5e21dfcd4f1..795ea66ef5b 100644 --- a/Source/spells.cpp +++ b/Source/spells.cpp @@ -13,6 +13,7 @@ #include "engine/backbuffer_state.hpp" #include "engine/point.hpp" #include "engine/random.hpp" +#include "engine/world_tile.hpp" #include "gamemenu.h" #include "inv.h" #include "missiles.h" @@ -121,7 +122,7 @@ int GetManaAmount(const Player &player, SpellID sn) } if (sn == SpellID::Healing || sn == SpellID::HealOther) { - ma = (GetSpellData(SpellID::Healing).sManaCost + 2 * player._pLevel - adj); + ma = (GetSpellData(SpellID::Healing).sManaCost + 2 * player.getCharacterLevel() - adj); } else if (GetSpellData(sn).sManaCost == 255) { ma = (player._pMaxManaBase >> 6) - adj; } else { @@ -208,9 +209,8 @@ SpellCheckResult CheckSpell(const Player &player, SpellID sn, SpellType st, bool return SpellCheckResult::Success; } -void CastSpell(int id, SpellID spl, int sx, int sy, int dx, int dy, int spllvl) +void CastSpell(Player &player, SpellID spl, WorldTilePosition src, WorldTilePosition dst, int spllvl) { - Player &player = Players[id]; Direction dir = player._pdir; if (IsWallSpell(spl)) { dir = player.tempDirection; @@ -219,12 +219,12 @@ void CastSpell(int id, SpellID spl, int sx, int sy, int dx, int dy, int spllvl) bool fizzled = false; const SpellData &spellData = GetSpellData(spl); for (size_t i = 0; i < sizeof(spellData.sMissiles) / sizeof(spellData.sMissiles[0]) && spellData.sMissiles[i] != MissileID::Null; i++) { - Missile *missile = AddMissile({ sx, sy }, { dx, dy }, dir, spellData.sMissiles[i], TARGET_MONSTERS, id, 0, spllvl); + Missile *missile = AddMissile(src, dst, dir, spellData.sMissiles[i], TARGET_MONSTERS, player, 0, spllvl); fizzled |= (missile == nullptr); } if (spl == SpellID::ChargedBolt) { for (int i = (spllvl / 2) + 3; i > 0; i--) { - Missile *missile = AddMissile({ sx, sy }, { dx, dy }, dir, MissileID::ChargedBolt, TARGET_MONSTERS, id, 0, spllvl); + Missile *missile = AddMissile(src, dst, dir, MissileID::ChargedBolt, TARGET_MONSTERS, player, 0, spllvl); fizzled |= (missile == nullptr); } } @@ -233,13 +233,9 @@ void CastSpell(int id, SpellID spl, int sx, int sy, int dx, int dy, int spllvl) } } -void DoResurrect(size_t pnum, Player &target) +void DoResurrect(Player &player, Player &target) { - if (pnum >= Players.size()) { - return; - } - - AddMissile(target.position.tile, target.position.tile, Direction::South, MissileID::ResurrectBeam, TARGET_MONSTERS, pnum, 0, 0); + AddMissile(target.position.tile, target.position.tile, Direction::South, MissileID::ResurrectBeam, TARGET_MONSTERS, player, 0, 0); if (target._pHitPoints != 0) return; @@ -282,7 +278,7 @@ void DoHealOther(const Player &caster, Player &target) } int hp = (GenerateRnd(10) + 1) << 6; - for (int i = 0; i < caster._pLevel; i++) { + for (unsigned i = 0; i < caster.getCharacterLevel(); i++) { hp += (GenerateRnd(4) + 1) << 6; } for (int i = 0; i < caster.GetSpellLevel(SpellID::HealOther); i++) { diff --git a/Source/spells.h b/Source/spells.h index 6714100469b..81064f9a63c 100644 --- a/Source/spells.h +++ b/Source/spells.h @@ -7,6 +7,7 @@ #include +#include "engine/world_tile.hpp" #include "player.h" namespace devilution { @@ -34,13 +35,13 @@ SpellCheckResult CheckSpell(const Player &player, SpellID sn, SpellType st, bool * @param player The player whose readied spell is to be checked. */ void EnsureValidReadiedSpell(Player &player); -void CastSpell(int id, SpellID spl, int sx, int sy, int dx, int dy, int spllvl); +void CastSpell(Player &player, SpellID spl, WorldTilePosition src, WorldTilePosition dst, int spllvl); /** * @param pnum player index * @param rid target player index */ -void DoResurrect(size_t pnum, Player &target); +void DoResurrect(Player &player, 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 d4e87fd7ac5..c37dff5cef7 100644 --- a/Source/stores.cpp +++ b/Source/stores.cpp @@ -7,6 +7,7 @@ #include #include +#include #include @@ -26,7 +27,6 @@ #include "towners.h" #include "utils/format_int.hpp" #include "utils/language.h" -#include "utils/stdcompat/string_view.hpp" #include "utils/str_cat.hpp" #include "utils/utf8.hpp" @@ -105,23 +105,23 @@ bool RenderGold; /** Does the current panel have a scrollbar */ bool stextscrl; -/** Remember last scoll position */ +/** Remember last scroll position */ int stextvhold; -/** Scoll position */ +/** Scroll position */ int stextsval; -/** Next scoll position */ +/** Next scroll position */ int stextdown; -/** Previous scoll position */ +/** Previous scroll position */ int stextup; -/** Count down for the push state of the scroll up button */ +/** Countdown for the push state of the scroll up button */ int8_t stextscrlubtn; -/** Count down for the push state of the scroll down button */ +/** Countdown for the push state of the scroll down button */ int8_t stextscrldbtn; /** Remember current store while displaying a dialog */ TalkID stextshold; -/** Temporary item used to hold the the item being traided */ +/** Temporary item used to hold the item being traded */ Item StoreItem; /** Maps from towner IDs to NPC names. */ @@ -245,12 +245,12 @@ void AddSTextVal(size_t y, int val) stext[y]._sval = val; } -void AddSText(uint8_t x, size_t y, string_view text, UiFlags flags, bool sel, int cursId = -1, bool cursIndent = false) +void AddSText(uint8_t x, size_t y, std::string_view text, UiFlags flags, bool sel, int cursId = -1, bool cursIndent = false) { stext[y]._sx = x; stext[y]._syoff = 0; stext[y].text.clear(); - AppendStrView(stext[y].text, text); + stext[y].text.append(text); stext[y].flags = flags; stext[y].type = sel ? STextStruct::Selectable : STextStruct::Label; stext[y].cursId = cursId; @@ -267,7 +267,7 @@ void AddOptionsBackButton() void AddItemListBackButton(bool selectable = false) { const int line = BackButtonLine(); - string_view text = _("Back"); + std::string_view text = _("Back"); if (!selectable && IsSmallFontTall()) { AddSText(0, line, text, UiFlags::ColorWhite | UiFlags::AlignRight, selectable); } else { @@ -284,18 +284,18 @@ void PrintStoreItem(const Item &item, int l, UiFlags flags, bool cursIndent = fa if (item._iIdentified) { if (item._iMagical != ITEM_QUALITY_UNIQUE) { if (item._iPrePower != -1) { - AppendStrView(productLine, PrintItemPower(item._iPrePower, item)); + productLine.append(PrintItemPower(item._iPrePower, item)); } } if (item._iSufPower != -1) { if (!productLine.empty()) - AppendStrView(productLine, _(", ")); - AppendStrView(productLine, PrintItemPower(item._iSufPower, item)); + productLine.append(_(", ")); + productLine.append(PrintItemPower(item._iSufPower, item)); } } if (item._iMiscId == IMISC_STAFF && item._iMaxCharges != 0) { if (!productLine.empty()) - AppendStrView(productLine, _(", ")); + productLine.append(_(", ")); productLine.append(fmt::format(fmt::runtime(_("Charges: {:d}/{:d}")), item._iCharges, item._iMaxCharges)); } if (!productLine.empty()) { @@ -312,7 +312,7 @@ void PrintStoreItem(const Item &item, int l, UiFlags flags, bool cursIndent = fa if (item._iMaxDur != DUR_INDESTRUCTIBLE && item._iMaxDur != 0) productLine += fmt::format(fmt::runtime(_("Dur: {:d}/{:d}, ")), item._iDurability, item._iMaxDur); else - AppendStrView(productLine, _("Indestructible, ")); + productLine.append(_("Indestructible, ")); } int8_t str = item._iMinStr; @@ -320,9 +320,9 @@ void PrintStoreItem(const Item &item, int l, UiFlags flags, bool cursIndent = fa int8_t dex = item._iMinDex; if (str == 0 && mag == 0 && dex == 0) { - AppendStrView(productLine, _("No required attributes")); + productLine.append(_("No required attributes")); } else { - AppendStrView(productLine, _("Required:")); + productLine.append(_("Required:")); if (str != 0) productLine.append(fmt::format(fmt::runtime(_(" {:d} Str")), str)); if (mag != 0) @@ -337,15 +337,41 @@ bool StoreAutoPlace(Item &item, bool persistItem) { Player &player = *MyPlayer; - if (AutoEquipEnabled(player, item) && AutoEquip(player, item, persistItem)) { + if (AutoEquipEnabled(player, item) && AutoEquip(player, item, persistItem, true)) { return true; } - if (AutoPlaceItemInBelt(player, item, persistItem)) { + if (AutoPlaceItemInBelt(player, item, persistItem, true)) { return true; } - return AutoPlaceItemInInventory(player, item, persistItem); + return AutoPlaceItemInInventory(player, item, persistItem, true); +} + +void ScrollVendorStore(Item *itemData, int storeLimit, int idx, int selling = true) +{ + ClearSText(5, 21); + stextup = 5; + + for (int l = 5; l < 20 && idx < storeLimit; l += 4) { + const Item &item = itemData[idx]; + if (!item.isEmpty()) { + UiFlags itemColor = item.getTextColorWithStatCheck(); + AddSText(20, l, item.getName(), itemColor, true, item._iCurs, true); + AddSTextVal(l, item._iIdentified ? item._iIvalue : item._ivalue); + PrintStoreItem(item, l + 1, itemColor, true); + stextdown = l; + } else { + l -= 4; + } + idx++; + } + if (selling) { + if (stextsel != -1 && !stext[stextsel].isSelectable() && stextsel != BackButtonLine()) + stextsel = stextdown; + } else { + stextsmax = std::max(static_cast(storeLimit) - 4, 0); + } } void StartSmith() @@ -367,22 +393,7 @@ void StartSmith() void ScrollSmithBuy(int idx) { - ClearSText(5, 21); - stextup = 5; - - for (int l = 5; l < 20; l += 4) { - if (!smithitem[idx].isEmpty()) { - UiFlags itemColor = smithitem[idx].getTextColorWithStatCheck(); - AddSText(20, l, smithitem[idx].getName(), itemColor, true, smithitem[idx]._iCurs, true); - AddSTextVal(l, smithitem[idx]._iIvalue); - PrintStoreItem(smithitem[idx], l + 1, itemColor, true); - stextdown = l; - idx++; - } - } - - if (stextsel != -1 && !stext[stextsel].isSelectable() && stextsel != BackButtonLine()) - stextsel = stextdown; + ScrollVendorStore(smithitem, static_cast(std::size(smithitem)), idx); } uint32_t TotalPlayerGold() @@ -422,29 +433,13 @@ void StartSmithBuy() void ScrollSmithPremiumBuy(int boughtitems) { - ClearSText(5, 21); - stextup = 5; - int idx = 0; for (; boughtitems != 0; idx++) { if (!premiumitems[idx].isEmpty()) boughtitems--; } - for (int l = 5; l < 20 && idx < SMITH_PREMIUM_ITEMS; l += 4) { - if (!premiumitems[idx].isEmpty()) { - UiFlags itemColor = premiumitems[idx].getTextColorWithStatCheck(); - AddSText(20, l, premiumitems[idx].getName(), itemColor, true, premiumitems[idx]._iCurs, true); - AddSTextVal(l, premiumitems[idx]._iIvalue); - PrintStoreItem(premiumitems[idx], l + 1, itemColor, true); - stextdown = l; - } else { - l -= 4; - } - idx++; - } - if (stextsel != -1 && !stext[stextsel].isSelectable() && stextsel != BackButtonLine()) - stextsel = stextdown; + ScrollVendorStore(premiumitems, static_cast(std::size(premiumitems)), idx); } bool StartSmithPremiumBuy() @@ -511,30 +506,7 @@ bool SmithSellOk(int i) void ScrollSmithSell(int idx) { - ClearSText(5, 21); - stextup = 5; - - for (int l = 5; l < 20; l += 4) { - if (idx >= storenumh) - break; - if (!storehold[idx].isEmpty()) { - UiFlags itemColor = storehold[idx].getTextColorWithStatCheck(); - - if (storehold[idx]._iMagical != ITEM_QUALITY_NORMAL && storehold[idx]._iIdentified) { - AddSText(20, l, storehold[idx].getName(), itemColor, true, storehold[idx]._iCurs, true); - AddSTextVal(l, storehold[idx]._iIvalue); - } else { - AddSText(20, l, storehold[idx].getName(), itemColor, true, storehold[idx]._iCurs, true); - AddSTextVal(l, storehold[idx]._ivalue); - } - - PrintStoreItem(storehold[idx], l + 1, itemColor, true); - stextdown = l; - } - idx++; - } - - stextsmax = std::max(storenumh - 4, 0); + ScrollVendorStore(storehold, storenumh, idx, false); } void StartSmithSell() @@ -607,14 +579,17 @@ void StartSmithSell() bool SmithRepairOk(int i) { const Player &myPlayer = *MyPlayer; + const Item &item = myPlayer.InvList[i]; - if (myPlayer.InvList[i].isEmpty()) + if (item.isEmpty()) + return false; + if (item._itype == ItemType::Misc) return false; - if (myPlayer.InvList[i]._itype == ItemType::Misc) + if (item._itype == ItemType::Gold) return false; - if (myPlayer.InvList[i]._itype == ItemType::Gold) + if (item._iDurability == item._iMaxDur) return false; - if (myPlayer.InvList[i]._iDurability == myPlayer.InvList[i]._iMaxDur) + if (item._iMaxDur == DUR_INDESTRUCTIBLE) return false; return true; @@ -689,7 +664,7 @@ void FillManaPlayer() Player &myPlayer = *MyPlayer; if (myPlayer._pMana != myPlayer._pMaxMana) { - PlaySFX(IS_CAST8); + PlaySFX(SfxID::CastHealing); } myPlayer._pMana = myPlayer._pMaxMana; myPlayer._pManaBase = myPlayer._pMaxManaBase; @@ -714,22 +689,7 @@ void StartWitch() void ScrollWitchBuy(int idx) { - ClearSText(5, 21); - stextup = 5; - - for (int l = 5; l < 20; l += 4) { - if (!witchitem[idx].isEmpty()) { - UiFlags itemColor = witchitem[idx].getTextColorWithStatCheck(); - AddSText(20, l, witchitem[idx].getName(), itemColor, true, witchitem[idx]._iCurs, true); - AddSTextVal(l, witchitem[idx]._iIvalue); - PrintStoreItem(witchitem[idx], l + 1, itemColor, true); - stextdown = l; - idx++; - } - } - - if (stextsel != -1 && !stext[stextsel].isSelectable() && stextsel != BackButtonLine()) - stextsel = stextdown; + ScrollVendorStore(witchitem, static_cast(std::size(witchitem)), idx); } void WitchBookLevel(Item &bookItem) @@ -969,7 +929,7 @@ void StoreConfirm(Item &item) AddSTextVal(8, item._iIvalue); PrintStoreItem(item, 9, itemColor); - string_view prompt; + std::string_view prompt; switch (stextshold) { case TalkID::BoyBuy: @@ -1054,7 +1014,7 @@ void HealPlayer() Player &myPlayer = *MyPlayer; if (myPlayer._pHitPoints != myPlayer._pMaxHP) { - PlaySFX(IS_CAST8); + PlaySFX(SfxID::CastHealing); } myPlayer._pHitPoints = myPlayer._pMaxHP; myPlayer._pHPBase = myPlayer._pMaxHPBase; @@ -1078,21 +1038,7 @@ void StartHealer() void ScrollHealerBuy(int idx) { - ClearSText(5, 21); - stextup = 5; - for (int l = 5; l < 20; l += 4) { - if (!healitem[idx].isEmpty()) { - UiFlags itemColor = healitem[idx].getTextColorWithStatCheck(); - AddSText(20, l, healitem[idx].getName(), itemColor, true, healitem[idx]._iCurs, true); - AddSTextVal(l, healitem[idx]._iIvalue); - PrintStoreItem(healitem[idx], l + 1, itemColor, true); - stextdown = l; - idx++; - } - } - - if (stextsel != -1 && !stext[stextsel].isSelectable() && stextsel != BackButtonLine()) - stextsel = stextdown; + ScrollVendorStore(healitem, static_cast(std::size(healitem)), idx); } void StartHealerBuy() @@ -2119,7 +2065,7 @@ int TakeGold(Player &player, int cost, bool skipMaxPiles) return cost; } -void DrawSelector(const Surface &out, const Rectangle &rect, string_view text, UiFlags flags) +void DrawSelector(const Surface &out, const Rectangle &rect, std::string_view text, UiFlags flags) { int lineWidth = GetLineWidth(text); @@ -2181,7 +2127,7 @@ void SetupTownStores() { Player &myPlayer = *MyPlayer; - int l = myPlayer._pLevel / 2; + int l = myPlayer.getCharacterLevel() / 2; if (!gbIsMultiplayer) { l = 0; for (int i = 0; i < NUMLEVELS; i++) { @@ -2192,11 +2138,11 @@ void SetupTownStores() SetRndSeed(glSeedTbl[currlevel] * SDL_GetTicks()); } - l = clamp(l + 2, 6, 16); + l = std::clamp(l + 2, 6, 16); SpawnSmith(l); SpawnWitch(l); SpawnHealer(l); - SpawnBoy(myPlayer._pLevel); + SpawnBoy(myPlayer.getCharacterLevel()); SpawnPremium(myPlayer); } @@ -2212,7 +2158,7 @@ void FreeStoreMem() } } -void PrintSString(const Surface &out, int margin, int line, string_view text, UiFlags flags, int price, int cursId, bool cursIndent) +void PrintSString(const Surface &out, int margin, int line, std::string_view text, UiFlags flags, int price, int cursId, bool cursIndent) { const Point uiPosition = GetUIRectangle().position; int sx = uiPosition.x + 32 + margin; @@ -2254,13 +2200,13 @@ void PrintSString(const Surface &out, int margin, int line, string_view text, Ui if (*sgOptions.Gameplay.showItemGraphicsInStores && cursIndent) { const Rectangle textRect { { rect.position.x + HalfCursWidth + 8, rect.position.y }, { rect.size.width - HalfCursWidth + 8, rect.size.height } }; - DrawString(out, text, textRect, flags); + DrawString(out, text, textRect, { .flags = flags }); } else { - DrawString(out, text, rect, flags); + DrawString(out, text, rect, { .flags = flags }); } if (price > 0) - DrawString(out, FormatInteger(price), rect, flags | UiFlags::AlignRight); + DrawString(out, FormatInteger(price), rect, { .flags = flags | UiFlags::AlignRight }); if (stextsel == line) { DrawSelector(out, rect, text, flags); @@ -2546,7 +2492,7 @@ void StoreESC() void StoreUp() { - PlaySFX(IS_TITLEMOV); + PlaySFX(SfxID::MenuMove); if (stextsel == -1) { return; } @@ -2583,7 +2529,7 @@ void StoreUp() void StoreDown() { - PlaySFX(IS_TITLEMOV); + PlaySFX(SfxID::MenuMove); if (stextsel == -1) { return; } @@ -2620,7 +2566,7 @@ void StoreDown() void StorePrior() { - PlaySFX(IS_TITLEMOV); + PlaySFX(SfxID::MenuMove); if (stextsel != -1 && stextscrl) { if (stextsel == stextup) { stextsval = std::max(stextsval - 4, 0); @@ -2632,7 +2578,7 @@ void StorePrior() void StoreNext() { - PlaySFX(IS_TITLEMOV); + PlaySFX(SfxID::MenuMove); if (stextsel != -1 && stextscrl) { if (stextsel == stextdown) { if (stextsval < stextsmax) @@ -2670,7 +2616,7 @@ void StoreEnter() return; } - PlaySFX(IS_TITLSLCT); + PlaySFX(SfxID::MenuSelect); switch (stextflag) { case TalkID::Smith: SmithEnter(); @@ -2749,19 +2695,26 @@ void StoreEnter() void CheckStoreBtn() { const Point uiPosition = GetUIRectangle().position; + const Rectangle windowRect { { uiPosition.x + 344, uiPosition.y + PaddingTop - 7 }, { 271, 303 } }; + const Rectangle windowRectFull { { uiPosition.x + 24, uiPosition.y + PaddingTop - 7 }, { 591, 303 } }; + + if (!stextsize) { + if (!windowRect.contains(MousePosition)) { + while (stextflag != TalkID::None) + StoreESC(); + } + } else { + if (!windowRectFull.contains(MousePosition)) { + while (stextflag != TalkID::None) + StoreESC(); + } + } + if (qtextflag) { qtextflag = false; if (leveltype == DTYPE_TOWN) stream_stop(); - } else if (stextsel != -1 && MousePosition.y >= (PaddingTop + uiPosition.y) && MousePosition.y <= (320 + uiPosition.y)) { - if (!stextsize) { - if (MousePosition.x < 344 + uiPosition.x || MousePosition.x > 616 + uiPosition.x) - return; - } else { - if (MousePosition.x < 24 + uiPosition.x || MousePosition.x > 616 + uiPosition.x) - return; - } - + } else if (stextsel != -1) { const int relativeY = MousePosition.y - (uiPosition.y + PaddingTop); if (stextscrl && MousePosition.x > 600 + uiPosition.x) { diff --git a/Source/stores.h b/Source/stores.h index 30d8baebc74..631ca9a285a 100644 --- a/Source/stores.h +++ b/Source/stores.h @@ -6,13 +6,13 @@ #pragma once #include +#include #include "DiabloUI/ui_flags.hpp" #include "control.h" #include "engine.h" #include "engine/clx_sprite.hpp" #include "utils/attributes.h" -#include "utils/stdcompat/optional.hpp" namespace devilution { @@ -88,7 +88,7 @@ void SetupTownStores(); void FreeStoreMem(); -void PrintSString(const Surface &out, int margin, int line, string_view text, UiFlags flags, int price = 0, int cursId = -1, bool cursIndent = false); +void PrintSString(const Surface &out, int margin, int line, std::string_view text, UiFlags flags, int price = 0, int cursId = -1, bool cursIndent = false); void DrawSLine(const Surface &out, int sy); void DrawSTextHelp(); void ClearSText(int s, int e); diff --git a/Source/storm/storm_net.cpp b/Source/storm/storm_net.cpp index fff17ebaef8..1692ca1f6d8 100644 --- a/Source/storm/storm_net.cpp +++ b/Source/storm/storm_net.cpp @@ -23,36 +23,21 @@ namespace devilution { namespace { std::unique_ptr dvlnet_inst; bool GameIsPublic = {}; -thread_local uint32_t dwLastError = 0; #ifndef NONET SdlMutex storm_net_mutex; #endif } // namespace -uint32_t SErrGetLastError() -{ - return dwLastError; -} - -void SErrSetLastError(uint32_t dwErrCode) -{ - dwLastError = dwErrCode; -} - -bool SNetReceiveMessage(uint8_t *senderplayerid, void **data, uint32_t *databytes) +bool SNetReceiveMessage(uint8_t *senderplayerid, void **data, size_t *databytes) { #ifndef NONET std::lock_guard lg(storm_net_mutex); #endif - if (!dvlnet_inst->SNetReceiveMessage(senderplayerid, data, databytes)) { - SErrSetLastError(STORM_ERROR_NO_MESSAGES_WAITING); - return false; - } - return true; + return dvlnet_inst->SNetReceiveMessage(senderplayerid, data, databytes); } -bool SNetSendMessage(int playerID, void *data, unsigned int databytes) +bool SNetSendMessage(uint8_t playerID, void *data, size_t databytes) { #ifndef NONET std::lock_guard lg(storm_net_mutex); @@ -67,14 +52,10 @@ bool SNetReceiveTurns(int arraysize, char **arraydata, size_t *arraydatabytes, u #endif if (arraysize != MAX_PLRS) UNIMPLEMENTED(); - if (!dvlnet_inst->SNetReceiveTurns(arraydata, arraydatabytes, arrayplayerstatus)) { - SErrSetLastError(STORM_ERROR_NO_MESSAGES_WAITING); - return false; - } - return true; + return dvlnet_inst->SNetReceiveTurns(arraydata, arraydatabytes, arrayplayerstatus); } -bool SNetSendTurn(char *data, unsigned int databytes) +bool SNetSendTurn(char *data, size_t databytes) { #ifndef NONET std::lock_guard lg(storm_net_mutex); @@ -117,7 +98,7 @@ bool SNetDestroy() return true; } -bool SNetDropPlayer(int playerid, uint32_t flags) +bool SNetDropPlayer(uint8_t playerid, uint32_t flags) { #ifndef NONET std::lock_guard lg(storm_net_mutex); diff --git a/Source/storm/storm_net.hpp b/Source/storm/storm_net.hpp index 22a6ed81a7b..b4e34c053dd 100644 --- a/Source/storm/storm_net.hpp +++ b/Source/storm/storm_net.hpp @@ -39,7 +39,7 @@ struct _SNETEVENT { uint32_t eventid; uint32_t playerid; void *data; - uint32_t databytes; + size_t databytes; }; #define PS_CONNECTED 0x10000 @@ -61,7 +61,7 @@ bool SNetDestroy(); * * Returns true if the function was called successfully and false otherwise. */ -bool SNetDropPlayer(int playerid, uint32_t flags); +bool SNetDropPlayer(uint8_t playerid, uint32_t flags); /* SNetGetTurnsInTransit @ 115 * @@ -87,7 +87,7 @@ bool SNetJoinGame(char *gameName, char *gamePassword, int *playerid); */ bool SNetLeaveGame(int type); -bool SNetReceiveMessage(uint8_t *senderplayerid, void **data, uint32_t *databytes); +bool SNetReceiveMessage(uint8_t *senderplayerid, void **data, size_t *databytes); bool SNetReceiveTurns(int arraysize, char **arraydata, size_t *arraydatabytes, uint32_t *arrayplayerstatus); typedef void (*SEVTHANDLER)(struct _SNETEVENT *); @@ -107,11 +107,10 @@ typedef void (*SEVTHANDLER)(struct _SNETEVENT *); * * Returns true if the function was called successfully and false otherwise. */ -bool SNetSendMessage(int playerID, void *data, unsigned int databytes); +bool SNetSendMessage(uint8_t playerID, void *data, size_t databytes); // Macro values to target specific players -#define SNPLAYER_ALL -1 -#define SNPLAYER_OTHERS -2 +constexpr uint8_t SNPLAYER_OTHERS = 0xFF; /* SNetSendTurn @ 128 * @@ -124,16 +123,7 @@ bool SNetSendMessage(int playerID, void *data, unsigned int databytes); * * Returns true if the function was called successfully and false otherwise. */ -bool SNetSendTurn(char *data, unsigned int databytes); - -uint32_t SErrGetLastError(); -void SErrSetLastError(uint32_t dwErrCode); - -// Values for dwErrCode -#define STORM_ERROR_GAME_TERMINATED 0x85100069 -#define STORM_ERROR_INVALID_PLAYER 0x8510006a -#define STORM_ERROR_NO_MESSAGES_WAITING 0x8510006b -#define STORM_ERROR_NOT_IN_GAME 0x85100070 +bool SNetSendTurn(char *data, size_t databytes); bool SNetGetOwnerTurnsWaiting(uint32_t *); bool SNetUnregisterEventHandler(event_type); diff --git a/Source/storm/storm_svid.cpp b/Source/storm/storm_svid.cpp index b9689a1bc01..f1371e3578f 100644 --- a/Source/storm/storm_svid.cpp +++ b/Source/storm/storm_svid.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -19,7 +20,6 @@ #include "utils/log.hpp" #include "utils/sdl_compat.h" #include "utils/sdl_wrap.h" -#include "utils/stdcompat/optional.hpp" namespace devilution { namespace { @@ -285,7 +285,7 @@ bool SVidPlayBegin(const char *filename, int flags) if (renderer != nullptr) { int renderWidth = static_cast(SVidWidth); int renderHeight = static_cast(SVidHeight); - texture = SDLWrap::CreateTexture(renderer, SDL_PIXELFORMAT_RGB888, SDL_TEXTUREACCESS_STREAMING, renderWidth, renderHeight); + texture = SDLWrap::CreateTexture(renderer, DEVILUTIONX_DISPLAY_TEXTURE_FORMAT, SDL_TEXTUREACCESS_STREAMING, renderWidth, renderHeight); if (SDL_RenderSetLogicalSize(renderer, renderWidth, renderHeight) <= -1) { ErrSdl(); } diff --git a/Source/sync.cpp b/Source/sync.cpp index c058e82dd62..5ab54e17ce3 100644 --- a/Source/sync.cpp +++ b/Source/sync.cpp @@ -152,8 +152,7 @@ void SyncPlrInv(TSyncHeader *pHdr) void SyncMonster(bool isOwner, const TSyncMonster &monsterSync) { - const int monsterId = monsterSync._mndx; - Monster &monster = Monsters[monsterId]; + Monster &monster = Monsters[monsterSync._mndx]; if (monster.hitPoints <= 0 || monster.mode == MonsterMode::Death) { return; } @@ -183,14 +182,14 @@ void SyncMonster(bool isOwner, const TSyncMonster &monsterSync) Direction md = GetDirection(monster.position.tile, position); if (DirOK(monster, md)) { M_ClearSquares(monster); - dMonster[monster.position.tile.x][monster.position.tile.y] = monsterId + 1; + monster.occupyTile(monster.position.tile, false); Walk(monster, md); monster.activeForTicks = UINT8_MAX; } } } else if (dMonster[position.x][position.y] == 0) { M_ClearSquares(monster); - dMonster[position.x][position.y] = monsterId + 1; + monster.occupyTile(position, false); monster.position.tile = position; if (monster.lightId != NO_LIGHT) ChangeLightXY(monster.lightId, position); @@ -250,7 +249,7 @@ bool IsTSyncMonsterValidate(const TSyncMonster &monsterSync) } // namespace -uint32_t sync_all_monsters(byte *pbBuf, uint32_t dwMaxLen) +size_t sync_all_monsters(std::byte *pbBuf, size_t dwMaxLen) { if (ActiveMonsterCount < 1) { return dwMaxLen; @@ -294,7 +293,7 @@ uint32_t sync_all_monsters(byte *pbBuf, uint32_t dwMaxLen) return dwMaxLen; } -uint32_t OnSyncData(const TCmd *pCmd, size_t pnum) +uint32_t OnSyncData(const TCmd *pCmd, const Player &player) { const auto &header = *reinterpret_cast(pCmd); const uint16_t wLen = SDL_SwapLE16(header.wLen); @@ -304,7 +303,7 @@ uint32_t OnSyncData(const TCmd *pCmd, size_t pnum) if (gbBufferMsgs == 1) { return wLen + sizeof(header); } - if (pnum == MyPlayerId) { + if (&player == MyPlayer) { return wLen + sizeof(header); } @@ -316,13 +315,14 @@ uint32_t OnSyncData(const TCmd *pCmd, size_t pnum) if (IsValidLevelForMultiplayer(level)) { const auto *monsterSyncs = reinterpret_cast(pCmd + sizeof(header)); + bool isOwner = player.getId() > MyPlayerId; for (int i = 0; i < monsterCount; i++) { if (!IsTSyncMonsterValidate(monsterSyncs[i])) continue; if (syncLocalLevel) { - SyncMonster(pnum > MyPlayerId, monsterSyncs[i]); + SyncMonster(isOwner, monsterSyncs[i]); } delta_sync_monster(monsterSyncs[i], level); diff --git a/Source/sync.h b/Source/sync.h index 36a421a1476..7aacd41d688 100644 --- a/Source/sync.h +++ b/Source/sync.h @@ -5,14 +5,13 @@ */ #pragma once +#include #include -#include "utils/stdcompat/cstddef.hpp" - namespace devilution { -uint32_t sync_all_monsters(byte *pbBuf, uint32_t dwMaxLen); -uint32_t OnSyncData(const TCmd *pCmd, size_t pnum); +size_t sync_all_monsters(std::byte *pbBuf, size_t dwMaxLen); +uint32_t OnSyncData(const TCmd *pCmd, const Player &player); void sync_init(); } // namespace devilution diff --git a/Source/textdat.cpp b/Source/textdat.cpp index 1849495b8b0..d72c532a390 100644 --- a/Source/textdat.cpp +++ b/Source/textdat.cpp @@ -13,916 +13,907 @@ namespace devilution { /** Contains the data related to each speech ID. */ const Speech Speeches[] = { { N_(/* TRANSLATORS: Quest dialog spoken by Cain */ " 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..."), - true, TSFX_STORY1 }, + true, SfxID::Cain1 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden */ "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..."), - true, TSFX_TAVERN21 }, + true, SfxID::Ogden21 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden */ "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..."), - true, TSFX_TAVERN22 }, + true, SfxID::Ogden22 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden (Quest End) */ "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."), - true, TSFX_TAVERN23 }, + true, SfxID::Ogden23 }, { N_(/* TRANSLATORS: Quest dialog spoken by Pepin */ "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..."), - true, TSFX_HEALER1 }, + true, SfxID::Pepin1 }, { N_(/* TRANSLATORS: Quest dialog spoken by Gillian */ "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."), - true, TSFX_BMAID1 }, + true, SfxID::Gillian1 }, { N_(/* TRANSLATORS: Quest dialog spoken by Griswold */ "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!"), - true, TSFX_SMITH1 }, + true, SfxID::Griswold1 }, { N_(/* TRANSLATORS: Quest dialog spoken by Farnham */ "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!"), - true, TSFX_DRUNK1 }, + true, SfxID::Farnham1 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria */ "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."), - true, TSFX_WITCH1 }, + true, SfxID::Adria1 }, { N_(/* TRANSLATORS: Quest dialog spoken by Wirt */ "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..."), - true, TSFX_PEGBOY1 }, + true, SfxID::Wirt1 }, { N_(/* TRANSLATORS: Quest dialog spoken by The Skeleton King (Hostile) */ "The warmth of life has entered my tomb. Prepare yourself, mortal, to serve my Master for eternity!"), - false, USFX_SKING1 }, + false, SfxID::LeoricGreeting }, { N_(/* TRANSLATORS: Quest dialog spoken by Cain */ "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..."), - true, TSFX_STORY2 }, + true, SfxID::Cain2 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden */ "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?"), - true, TSFX_TAVERN24 }, + true, SfxID::Ogden24 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden (Quest End) */ "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."), - true, TSFX_TAVERN25 }, + true, SfxID::Ogden25 }, { N_(/* TRANSLATORS: Quest dialog spoken by Pepin */ "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..."), - true, TSFX_HEALER2 }, + true, SfxID::Pepin2 }, { N_(/* TRANSLATORS: Quest dialog spoken by Gillian */ "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."), - true, TSFX_BMAID2 }, + true, SfxID::Gillian2 }, { N_(/* TRANSLATORS: Quest dialog spoken by Griswold */ "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."), - true, TSFX_SMITH2 }, + true, SfxID::Griswold2 }, { N_(/* TRANSLATORS: Quest dialog spoken by Farnham */ "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..."), - true, TSFX_DRUNK2 }, + true, SfxID::Farnham2 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria */ "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."), - true, TSFX_WITCH2 }, + true, SfxID::Adria2 }, { N_(/* TRANSLATORS: Quest dialog spoken by Wirt */ "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."), - true, TSFX_PEGBOY2 }, + true, SfxID::Wirt2 }, { N_(/* TRANSLATORS: Quest dialog spoken by Snotspill (Hostile) */ "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!"), - true, USFX_SNOT1 }, + true, SfxID::Snotspill1 }, { N_(/* TRANSLATORS: Quest dialog spoken by Snotspill (Hostile) */ "You kill uglies, get banner. You bring to me, or else..."), - true, USFX_SNOT2 }, + true, SfxID::Snotspill2 }, { N_(/* TRANSLATORS: Quest dialog spoken by Snotspill (Hostile) */ "You give! Yes, good! Go now, we strong. We kill all with big Magic!"), - true, USFX_SNOT3 }, + true, SfxID::Snotspill3 }, { N_(/* TRANSLATORS: Quest dialog spoken by Cain */ "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!"), - true, TSFX_STORY36 }, + true, SfxID::Cain36 }, { N_(/* TRANSLATORS: Quest dialog spoken by Cain */ "You must hurry and rescue Albrecht from the hands of Lazarus. The prince and the people of this kingdom are counting on you!"), - true, TSFX_STORY37 }, + true, SfxID::Cain37 }, { N_(/* TRANSLATORS: Quest dialog spoken by Cain */ "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!"), - true, TSFX_STORY38 }, + true, SfxID::Cain38 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden */ "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."), - true, TSFX_TAVERN1 }, - { "", true, TSFX_STORY38 }, - { "", true, TSFX_STORY38 }, + true, SfxID::Ogden1 }, + { "", true, SfxID::Cain38 }, + { "", true, SfxID::Cain38 }, { N_(/* TRANSLATORS: Quest dialog spoken by Pepin */ "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..."), - true, TSFX_HEALER3 }, + true, SfxID::Pepin3 }, { N_(/* TRANSLATORS: Quest dialog spoken by Gillian */ "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."), - true, TSFX_BMAID3 }, + true, SfxID::Gillian3 }, { N_(/* TRANSLATORS: Quest dialog spoken by Griswold */ "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!"), - true, TSFX_SMITH3 }, + true, SfxID::Griswold3 }, { N_(/* TRANSLATORS: Quest dialog spoken by Farnham */ "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..."), - true, TSFX_DRUNK3 }, + true, SfxID::Farnham3 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria */ "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."), - true, TSFX_WITCH3 }, + true, SfxID::Adria3 }, { N_(/* TRANSLATORS: Quest dialog spoken by Wirt */ "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."), - true, TSFX_PEGBOY3 }, + true, SfxID::Wirt3 }, { N_(/* TRANSLATORS: Quest dialog spoken by Lazarus (Hostile) */ "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!"), - false, USFX_LAZ1 }, - { "", false, USFX_LAZ1 }, + false, SfxID::LazarusGreeting }, + { "", false, SfxID::LazarusGreeting }, { N_(/* TRANSLATORS: Quest dialog spoken by Cain */ "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."), - true, TSFX_STORY4 }, + true, SfxID::Cain4 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden */ "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."), - true, TSFX_TAVERN2 }, + true, SfxID::Ogden2 }, { N_(/* TRANSLATORS: Quest dialog spoken by Pepin */ "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."), - true, TSFX_HEALER20 }, + true, SfxID::Pepin20 }, { N_(/* TRANSLATORS: Quest dialog spoken by Pepin */ "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."), - true, TSFX_HEALER21 }, + true, SfxID::Pepin21 }, { N_(/* TRANSLATORS: Quest dialog spoken by Pepin */ "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."), - true, TSFX_HEALER22 }, + true, SfxID::Pepin22 }, { N_(/* TRANSLATORS: Quest dialog spoken by Gillian */ "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?"), - true, TSFX_BMAID4 }, + true, SfxID::Gillian4 }, { N_(/* TRANSLATORS: Quest dialog spoken by Griswold */ "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."), - true, TSFX_SMITH4 }, - { N_(/* TRANSLATORS: Quest dialog spoken by Farnham */ "You drink water?"), true, TSFX_DRUNK4 }, + true, SfxID::Griswold4 }, + { N_(/* TRANSLATORS: Quest dialog spoken by Farnham */ "You drink water?"), true, SfxID::Farnham4 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria */ "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."), - true, TSFX_WITCH4 }, + true, SfxID::Adria4 }, { N_(/* TRANSLATORS: Quest dialog spoken by Wirt */ "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!"), - true, TSFX_PEGBOY4 }, + true, SfxID::Wirt4 }, { N_(/* TRANSLATORS: Quest dialog spoken by Cain */ "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?"), - true, TSFX_STORY7 }, + true, SfxID::Cain7 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden */ "I am afraid that I don't know anything about that, good master. Cain has many books that may be of some help."), - true, TSFX_TAVERN5 }, + true, SfxID::Ogden5 }, { N_(/* TRANSLATORS: Quest dialog spoken by Pepin */ "This sounds like a very dangerous place. If you venture there, please take great care."), - true, TSFX_HEALER5 }, + true, SfxID::Pepin5 }, { N_(/* TRANSLATORS: Quest dialog spoken by Gillian */ "I am afraid that I haven't heard anything about that. Perhaps Cain the Storyteller could be of some help."), - true, TSFX_BMAID6 }, + true, SfxID::Gillian6 }, { N_(/* TRANSLATORS: Quest dialog spoken by Griswold */ "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."), - true, TSFX_SMITH7 }, + true, SfxID::Griswold7 }, { N_(/* TRANSLATORS: Quest dialog spoken by Farnham */ "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."), - true, TSFX_DRUNK7 }, + true, SfxID::Farnham7 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria */ "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."), - true, TSFX_WITCH7 }, + true, SfxID::Adria7 }, { N_(/* TRANSLATORS: Quest dialog spoken by Wirt */ "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?"), - true, TSFX_PEGBOY7 }, + true, SfxID::Wirt7 }, { N_(/* TRANSLATORS: Quest dialog spoken by Cain */ "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."), - true, TSFX_STORY10 }, + true, SfxID::Cain10 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden */ "Yes, Farnham has mumbled something about a hulking brute who wielded a fierce weapon. I believe he called him a butcher."), - true, TSFX_TAVERN8 }, + true, SfxID::Ogden8 }, { N_(/* TRANSLATORS: Quest dialog spoken by Pepin */ "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..."), - true, TSFX_HEALER8 }, + true, SfxID::Pepin8 }, { N_(/* TRANSLATORS: Quest dialog spoken by Gillian */ "When Farnham said something about a butcher killing people, I immediately discounted it. But since you brought it up, maybe it is true."), - true, TSFX_BMAID8 }, + true, SfxID::Gillian8 }, { N_(/* TRANSLATORS: Quest dialog spoken by Griswold */ "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."), - true, TSFX_SMITH10 }, + true, SfxID::Griswold10 }, { N_(/* TRANSLATORS: Quest dialog spoken by Farnham (*sad face*) */ "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!"), - true, TSFX_DRUNK10 }, + true, SfxID::Farnham10 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria */ "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."), - true, TSFX_WITCH10 }, + true, SfxID::Adria10 }, { N_(/* TRANSLATORS: Quest dialog spoken by Wirt */ "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."), - true, TSFX_PEGBOY10 }, + true, SfxID::Wirt10 }, { N_(/* TRANSLATORS: Quest dialog spoken by Wounded Townsman (Dying) */ "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..."), - true, TSFX_WOUND }, - { "", true, USFX_CLEAVER }, + true, SfxID::WoundedTownsman }, + { "", true, SfxID::ButcherGreeting }, { N_(/* TRANSLATORS: Quest dialog spoken by Cain */ "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..."), - true, TSFX_STORY12 }, + true, SfxID::Cain12 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden */ "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."), - true, TSFX_TAVERN10 }, + true, SfxID::Ogden10 }, { N_(/* TRANSLATORS: Quest dialog spoken by Pepin */ "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?"), - true, TSFX_HEALER10 }, + true, SfxID::Pepin10 }, { N_(/* TRANSLATORS: Quest dialog spoken by Gillian */ "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."), - true, TSFX_BMAID10 }, + true, SfxID::Gillian10 }, { N_(/* TRANSLATORS: Quest dialog spoken by Griswold */ "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."), - true, TSFX_SMITH12 }, + true, SfxID::Griswold12 }, { N_(/* TRANSLATORS: Quest dialog spoken by Farnham */ "Look here... that's pretty funny, huh? Get it? Blind - look here?"), - true, TSFX_DRUNK12 }, + true, SfxID::Farnham12 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria */ "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."), - true, TSFX_WITCH12 }, + true, SfxID::Adria12 }, { N_(/* TRANSLATORS: Quest dialog spoken by Wirt */ "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."), - true, TSFX_PEGBOY11 }, + true, SfxID::Wirt11 }, { N_(/* TRANSLATORS: Quest dialog spoken by Cain */ "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."), - true, TSFX_STORY13 }, + true, SfxID::Cain13 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden */ "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."), - true, TSFX_TAVERN11 }, + true, SfxID::Ogden11 }, { N_(/* TRANSLATORS: Quest dialog spoken by Pepin */ "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."), - true, TSFX_HEALER11 }, + true, SfxID::Pepin11 }, { N_(/* TRANSLATORS: Quest dialog spoken by Gillian */ "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."), - true, TSFX_BMAID11 }, + true, SfxID::Gillian11 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden */ "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."), - true, TSFX_SMITH13 }, + true, SfxID::Griswold13 }, { N_(/* TRANSLATORS: Quest dialog spoken by Farnham */ " 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!"), - true, TSFX_DRUNK13 }, + true, SfxID::Farnham13 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria */ "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."), - true, TSFX_WITCH13 }, + true, SfxID::Adria13 }, { N_(/* TRANSLATORS: Quest dialog spoken by Wirt */ "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."), - true, TSFX_PEGBOY12 }, + true, SfxID::Wirt12 }, { N_(/* TRANSLATORS: Quest dialog spoken by Lachdanan (in despair) */ "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."), - true, USFX_LACH1 }, + true, SfxID::Lachdanan1 }, { N_(/* TRANSLATORS: Quest dialog spoken by Lachdanan (in despair) */ "You have not found the Golden Elixir. I fear that I am doomed for eternity. Please, keep trying..."), - true, USFX_LACH2 }, + true, SfxID::Lachdanan2 }, { N_(/* TRANSLATORS: Quest dialog spoken by Lachdanan (Quest End) */ "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..."), - true, USFX_LACH3 }, + true, SfxID::Lachdanan3 }, { N_(/* TRANSLATORS: Quest dialog spoken by Cain */ "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..."), - true, TSFX_STORY14 }, + true, SfxID::Cain14 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden */ "Don't you think that Griswold would be a better person to ask about this? He's quite handy, you know."), - true, TSFX_TAVERN12 }, + true, SfxID::Ogden12 }, { N_(/* TRANSLATORS: Quest dialog spoken by Pepin */ "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."), - true, TSFX_HEALER12 }, + true, SfxID::Pepin12 }, { N_(/* TRANSLATORS: Quest dialog spoken by Gillian */ "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."), - true, TSFX_BMAID12 }, + true, SfxID::Gillian12 }, { N_(/* TRANSLATORS: Quest dialog spoken by Griswold */ "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!"), - true, TSFX_SMITH21 }, + true, SfxID::Griswold21 }, { N_(/* TRANSLATORS: Quest dialog spoken by Griswold */ "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."), - true, TSFX_SMITH22 }, + true, SfxID::Griswold22 }, { N_(/* TRANSLATORS: Quest dialog spoken by Griswold */ "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."), - true, TSFX_SMITH23 }, + true, SfxID::Griswold23 }, { N_(/* TRANSLATORS: Quest dialog spoken by Farnham */ "Griswold can't sell his anvil. What will he do then? And I'd be angry too if someone took my anvil!"), - true, TSFX_DRUNK14 }, + true, SfxID::Farnham14 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria */ "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."), - true, TSFX_WITCH14 }, + true, SfxID::Adria14 }, { N_(/* TRANSLATORS: Quest dialog spoken by Wirt */ "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."), - true, TSFX_PEGBOY13 }, + true, SfxID::Wirt13 }, { N_(/* TRANSLATORS: Quest dialog spoken by Cain */ "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..."), - true, TSFX_STORY15 }, + true, SfxID::Cain15 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden */ "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."), - true, TSFX_TAVERN13 }, + true, SfxID::Ogden13 }, { N_(/* TRANSLATORS: Quest dialog spoken by Pepin */ "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..."), - true, TSFX_HEALER13 }, + true, SfxID::Pepin13 }, { N_(/* TRANSLATORS: Quest dialog spoken by Gillian */ "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."), - true, TSFX_BMAID13 }, + true, SfxID::Gillian13 }, { N_(/* TRANSLATORS: Quest dialog spoken by Griswold */ "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."), - true, TSFX_SMITH14 }, - { N_(/* TRANSLATORS: Quest dialog "spoken" by Farnham */ "Zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz..."), true, TSFX_DRUNK15 }, + true, SfxID::Griswold14 }, + { N_(/* TRANSLATORS: Quest dialog "spoken" by Farnham */ "Zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz..."), true, SfxID::Farnham15 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria */ "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."), - true, TSFX_WITCH15 }, + true, SfxID::Adria15 }, { N_(/* TRANSLATORS: Quest dialog spoken by Wirt */ "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."), - true, TSFX_PEGBOY14 }, + true, SfxID::Wirt14 }, { N_(/* TRANSLATORS: Quest dialog spoken by Cain */ "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."), - true, TSFX_STORY18 }, + true, SfxID::Cain18 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden */ "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."), - true, TSFX_TAVERN16 }, + true, SfxID::Ogden16 }, { N_(/* TRANSLATORS: Quest dialog spoken by Pepin */ "Cain would be able to tell you much more about something like this than I would ever wish to know."), - true, TSFX_HEALER16 }, + true, SfxID::Pepin16 }, { N_(/* TRANSLATORS: Quest dialog spoken by Gillian */ "If you are to battle such a fierce opponent, may Light be your guide and your defender. I will keep you in my thoughts."), - true, TSFX_BMAID16 }, + true, SfxID::Gillian16 }, { N_(/* TRANSLATORS: Quest dialog spoken by Griswold */ "Dark and wicked legends surrounds the one Warlord of Blood. Be well prepared, my friend, for he shows no mercy or quarter."), - true, TSFX_SMITH17 }, + true, SfxID::Griswold17 }, { N_(/* TRANSLATORS: Quest dialog spoken by Farnham */ "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?"), - true, TSFX_DRUNK17 }, + true, SfxID::Farnham17 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria */ "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."), - true, TSFX_WITCH18 }, + true, SfxID::Adria18 }, { N_(/* TRANSLATORS: Quest dialog spoken by Wirt */ "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..."), - true, TSFX_PEGBOY17 }, + true, SfxID::Wirt17 }, { N_(/* TRANSLATORS: Quest dialog spoken by Warlord of Blood (Hostile) */ "My blade sings for your blood, mortal, and by my dark masters it shall not be denied."), - false, USFX_WARLRD1 }, + false, SfxID::Warlord }, { N_(/* TRANSLATORS: Quest dialog spoken by Cain */ "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."), - true, TSFX_STORY20 }, + true, SfxID::Cain20 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden */ "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..."), - true, TSFX_TAVERN18 }, + true, SfxID::Ogden18 }, { N_(/* TRANSLATORS: Quest dialog spoken by Pepin */ "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!"), - true, TSFX_HEALER18 }, + true, SfxID::Pepin18 }, { N_(/* TRANSLATORS: Quest dialog spoken by Gillian */ "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."), - true, TSFX_BMAID18 }, + true, SfxID::Gillian18 }, { N_(/* TRANSLATORS: Quest dialog spoken by Griswold */ "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."), - true, TSFX_SMITH24 }, + true, SfxID::Griswold24 }, { N_(/* TRANSLATORS: Quest dialog spoken by Griswold */ "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."), - true, TSFX_SMITH25 }, + true, SfxID::Griswold25 }, { N_(/* TRANSLATORS: Quest dialog spoken by Griswold(Quest End) */ "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."), - true, TSFX_SMITH26 }, + true, SfxID::Griswold26 }, { N_(/* TRANSLATORS: Quest dialog spoken by Farnham */ "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..."), - true, TSFX_DRUNK19 }, + true, SfxID::Farnham19 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria */ "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."), - true, TSFX_WITCH20 }, + true, SfxID::Adria20 }, { N_(/* TRANSLATORS: Quest dialog spoken by Wirt */ "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."), - true, TSFX_PEGBOY18 }, + true, SfxID::Wirt18 }, { N_(/* TRANSLATORS: Quest dialog spoken by Cain */ "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."), - true, TSFX_STORY21 }, + true, SfxID::Cain21 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden */ "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!"), - true, TSFX_TAVERN19 }, + true, SfxID::Ogden19 }, { N_(/* TRANSLATORS: Quest dialog spoken by Pepin */ "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."), - true, TSFX_HEALER26 }, + true, SfxID::Pepin26 }, { N_(/* TRANSLATORS: Quest dialog spoken by Pepin */ "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."), - true, TSFX_HEALER27 }, + true, SfxID::Pepin27 }, { N_(/* TRANSLATORS: Quest dialog spoken by Farnham */ "I think Ogden might have some mushrooms in the storage cellar. Why don't you ask him?"), - true, TSFX_BMAID19 }, + true, SfxID::Gillian19 }, { N_(/* TRANSLATORS: Quest dialog spoken by Griswold */ "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."), - true, TSFX_SMITH19 }, + true, SfxID::Griswold19 }, { N_(/* TRANSLATORS: Quest dialog spoken by Farnham */ "Ogden mixes a MEAN black mushroom, but I get sick if I drink that. Listen, listen... here's the secret - moderation is the key!"), - true, TSFX_DRUNK20 }, + true, SfxID::Farnham20 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria */ "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?"), - true, TSFX_WITCH22 }, + true, SfxID::Adria22 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria */ "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."), - true, TSFX_WITCH23 }, + true, SfxID::Adria23 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria */ "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."), - true, TSFX_WITCH24 }, + true, SfxID::Adria24 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria */ "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?"), - true, TSFX_WITCH25 }, + true, SfxID::Adria25 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria (Quest End) */ "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..."), - true, TSFX_WITCH26 }, + true, SfxID::Adria26 }, { N_(/* TRANSLATORS: Quest dialog spoken by Wirt */ "I don't have any mushrooms of any size or color for sale. How about something a bit more useful?"), - true, TSFX_PEGBOY19 }, + true, SfxID::Wirt19 }, { N_(/* TRANSLATORS: Quest dialog spoken by Cain (currently unused) */ "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!"), - true, TSFX_STORY22 }, + true, SfxID::Cain22 }, { N_(/* TRANSLATORS: Quest dialog spoken by Cain (currently unused) */ "Our time is running short! I sense his dark power building and only you can stop him from attaining his full might."), - true, TSFX_STORY23 }, + true, SfxID::Cain23 }, { N_(/* TRANSLATORS: Quest dialog spoken by Cain (currently unused) */ "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."), - true, TSFX_STORY24 }, + true, SfxID::Cain24 }, { N_(/* TRANSLATORS: Quest dialog spoken by Ogden (currently unused) */ "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."), - true, TSFX_TAVERN20 }, + true, SfxID::Ogden20 }, { N_(/* TRANSLATORS: Quest dialog spoken by Pepin (currently unused) */ "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."), - true, TSFX_HEALER19 }, + true, SfxID::Pepin19 }, { N_(/* TRANSLATORS: Quest dialog spoken by Gillian (currently unused) */ "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."), - true, TSFX_BMAID20 }, + true, SfxID::Gillian20 }, { N_(/* TRANSLATORS: Quest dialog spoken by Griswold (currently unused) */ "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."), - true, TSFX_SMITH20 }, + true, SfxID::Griswold20 }, { N_(/* TRANSLATORS: Quest dialog spoken by Farnham (currently unused) */ "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."), - true, TSFX_DRUNK21 }, + true, SfxID::Farnham21 }, { N_(/* TRANSLATORS: Quest dialog spoken by Adria (currently unused) */ "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..."), - true, TSFX_WITCH21 }, + true, SfxID::Adria21 }, { N_(/* TRANSLATORS: Quest dialog spoken by Wirt (currently unused) */ "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."), - true, TSFX_PEGBOY20 }, + true, SfxID::Wirt20 }, { N_(/* TRANSLATORS: Quest dialog spoken by Gharbad the Weak */ "Pleeeease, no hurt. No Kill. Keep alive and next time good bring to you."), - true, USFX_GARBUD1 }, + true, SfxID::Gharbad1 }, { N_(/* TRANSLATORS: Quest dialog spoken by Gharbad the Weak */ "Something for you I am making. Again, not kill Gharbad. Live and give good. \n \nYou take this as proof I keep word..."), - true, USFX_GARBUD2 }, + true, SfxID::Gharbad2 }, { N_(/* TRANSLATORS: Quest dialog spoken by Gharbad the Weak */ "Nothing yet! Almost done. \n \nVery powerful, very strong. Live! Live! \n \nNo pain and promise I keep!"), - true, USFX_GARBUD3 }, + true, SfxID::Gharbad3 }, { N_(/* TRANSLATORS: Quest dialog spoken by Gharbad the Weak (Hostile) */ "This too good for you. Very Powerful! You want - you take!"), - true, USFX_GARBUD4 }, + true, SfxID::Gharbad4 }, { N_(/* TRANSLATORS: Quest dialog spoken by Zhar the Mad (annoyed / Hostile) */ "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!"), - true, USFX_ZHAR1 }, - { N_(/* TRANSLATORS: Quest dialog spoken by Zhar the Mad (Hostile) */ "Arrrrgh! Your curiosity will be the death of you!!!"), true, USFX_ZHAR2 }, - { N_(/* TRANSLATORS: Neutral dialog spoken by Cain */ "Hello, my friend. Stay awhile and listen..."), false, TSFX_STORY25 }, + true, SfxID::Zhar1 }, + { N_(/* TRANSLATORS: Quest dialog spoken by Zhar the Mad (Hostile) */ "Arrrrgh! Your curiosity will be the death of you!!!"), true, SfxID::Zhar2 }, + { N_(/* TRANSLATORS: Neutral dialog spoken by Cain */ "Hello, my friend. Stay awhile and listen..."), false, SfxID::Cain25 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Cain (Gossip) */ "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."), - true, TSFX_STORY26 }, + true, SfxID::Cain26 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Cain (Gossip) */ "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."), - true, TSFX_STORY27 }, + true, SfxID::Cain27 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Cain (Gossip) */ "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."), - true, TSFX_STORY28 }, + true, SfxID::Cain28 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Cain (Gossip) */ "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."), - true, TSFX_STORY29 }, + true, SfxID::Cain29 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Cain (Gossip) */ "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."), - true, TSFX_STORY30 }, + true, SfxID::Cain30 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Cain (Gossip) */ "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."), - true, TSFX_STORY31 }, + true, SfxID::Cain31 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Cain (Gossip) */ "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."), - true, TSFX_STORY33 }, + true, SfxID::Cain33 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Cain (Gossip) */ "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."), - true, TSFX_STORY34 }, + true, SfxID::Cain34 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Cain (Gossip) */ "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."), - true, TSFX_STORY35 }, + true, SfxID::Cain35 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Ogden */ "Greetings, good master. Welcome to the Tavern of the Rising Sun!"), - false, TSFX_TAVERN36 }, + false, SfxID::Ogden36 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Ogden (Gossip) */ "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."), - true, TSFX_TAVERN37 }, + true, SfxID::Ogden37 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Ogden (Gossip) */ "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."), - true, TSFX_TAVERN38 }, + true, SfxID::Ogden38 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Ogden (Gossip) */ "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."), - true, TSFX_TAVERN39 }, + true, SfxID::Ogden39 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Ogden (Gossip) */ "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."), - true, TSFX_TAVERN40 }, + true, SfxID::Ogden40 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Ogden (Gossip) */ "If you want to know more about the history of our village, the storyteller Cain knows quite a bit about the past."), - true, TSFX_TAVERN41 }, + true, SfxID::Ogden41 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Ogden (Gossip) */ "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."), - true, TSFX_TAVERN43 }, + true, SfxID::Ogden43 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Ogden (Gossip) */ "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..."), - true, TSFX_TAVERN44 }, + true, SfxID::Ogden44 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Ogden (Gossip) */ "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."), - true, TSFX_TAVERN45 }, - { N_(/* TRANSLATORS: Neutral dialog spoken by Pepin */ "What ails you, my friend?"), false, TSFX_HEALER37 }, + true, SfxID::Ogden45 }, + { N_(/* TRANSLATORS: Neutral dialog spoken by Pepin */ "What ails you, my friend?"), false, SfxID::Pepin37 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Pepin (Gossip) */ "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."), - true, TSFX_HEALER38 }, + true, SfxID::Pepin38 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Pepin (Gossip) */ "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."), - true, TSFX_HEALER39 }, + true, SfxID::Pepin39 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Pepin (Gossip) */ "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."), - true, TSFX_HEALER40 }, + true, SfxID::Pepin40 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Pepin (Gossip) */ "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."), - true, TSFX_HEALER41 }, + true, SfxID::Pepin41 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Pepin (Gossip) */ "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."), - true, TSFX_HEALER42 }, + true, SfxID::Pepin42 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Pepin (Gossip) */ "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."), - true, TSFX_HEALER43 }, + true, SfxID::Pepin43 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Pepin (Gossip) */ "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."), - true, TSFX_HEALER45 }, + true, SfxID::Pepin45 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Pepin (Gossip) */ "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."), - true, TSFX_HEALER46 }, + true, SfxID::Pepin46 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Pepin (Gossip) */ "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."), - true, TSFX_HEALER47 }, - { N_(/* TRANSLATORS: Neutral dialog spoken by Gillian */ "Good day! How may I serve you?"), false, TSFX_BMAID31 }, + true, SfxID::Pepin47 }, + { N_(/* TRANSLATORS: Neutral dialog spoken by Gillian */ "Good day! How may I serve you?"), false, SfxID::Gillian31 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Gillian (Gossip) */ "My grandmother had a dream that you would come and talk to me. She has visions, you know and can see into the future."), - true, TSFX_BMAID32 }, + true, SfxID::Gillian32 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Gillian (Gossip) */ "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."), - true, TSFX_BMAID33 }, + true, SfxID::Gillian33 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Gillian (Gossip) */ "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."), - true, TSFX_BMAID34 }, + true, SfxID::Gillian34 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Gillian (Gossip) */ "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."), - true, TSFX_BMAID35 }, + true, SfxID::Gillian35 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Gillian (Gossip) */ "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."), - true, TSFX_BMAID36 }, + true, SfxID::Gillian36 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Gillian (Gossip) */ "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."), - true, TSFX_BMAID37 }, + true, SfxID::Gillian37 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Gillian (Gossip) */ "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."), - true, TSFX_BMAID39 }, + true, SfxID::Gillian39 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Gillian (Gossip) */ "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."), - true, TSFX_BMAID40 }, - { N_(/* TRANSLATORS: Neutral dialog spoken by Griswold */ "Well, what can I do for ya?"), false, TSFX_SMITH44 }, + true, SfxID::Gillian40 }, + { N_(/* TRANSLATORS: Neutral dialog spoken by Griswold */ "Well, what can I do for ya?"), false, SfxID::Griswold44 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) */ "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!"), - true, TSFX_SMITH45 }, + true, SfxID::Griswold45 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) */ "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!"), - true, TSFX_SMITH46 }, + true, SfxID::Griswold46 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) */ "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!"), - true, TSFX_SMITH47 }, + true, SfxID::Griswold47 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) */ "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."), - true, TSFX_SMITH48 }, + true, SfxID::Griswold48 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) */ "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."), - true, TSFX_SMITH49 }, + true, SfxID::Griswold49 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) */ "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."), - true, TSFX_SMITH50 }, + true, SfxID::Griswold50 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) */ "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!"), - true, TSFX_SMITH51 }, + true, SfxID::Griswold51 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) */ "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."), - true, TSFX_SMITH52 }, + true, SfxID::Griswold52 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) */ "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."), - true, TSFX_SMITH53 }, + true, SfxID::Griswold53 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) */ "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."), - true, TSFX_SMITH55 }, + true, SfxID::Griswold55 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) */ "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."), - true, TSFX_SMITH56 }, - { N_(/* TRANSLATORS: Neutral dialog spoken by Farnham */ "Can't a fella drink in peace?"), false, TSFX_DRUNK27 }, + true, SfxID::Griswold56 }, + { N_(/* TRANSLATORS: Neutral dialog spoken by Farnham */ "Can't a fella drink in peace?"), false, SfxID::Farnham27 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) */ "The gal who brings the drinks? Oh, yeah, what a pretty lady. So nice, too."), - true, TSFX_DRUNK28 }, + true, SfxID::Farnham28 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) */ "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."), - true, TSFX_DRUNK29 }, + true, SfxID::Farnham29 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) */ "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."), - true, TSFX_DRUNK30 }, + true, SfxID::Farnham30 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) */ "Griswold? Good old Griswold. I love him like a brother! We fought together, you know, back when... we... Lazarus... Lazarus... Lazarus!!!"), - true, TSFX_DRUNK31 }, + true, SfxID::Farnham31 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) */ "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..."), - true, TSFX_DRUNK32 }, + true, SfxID::Farnham32 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) */ "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..."), - true, TSFX_DRUNK34 }, + true, SfxID::Farnham34 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) */ "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..."), - true, TSFX_DRUNK35 }, + true, SfxID::Farnham35 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) */ "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..."), - true, TSFX_DRUNK23 }, + true, SfxID::Farnham23 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) */ "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."), - true, TSFX_DRUNK24 }, + true, SfxID::Farnham24 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) */ "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..."), - true, TSFX_DRUNK25 }, + true, SfxID::Farnham25 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Farnham (Gossip) */ "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."), - true, TSFX_DRUNK26 }, - { N_(/* TRANSLATORS: Neutral dialog spoken by Adria */ "I sense a soul in search of answers..."), false, TSFX_WITCH38 }, + true, SfxID::Farnham26 }, + { N_(/* TRANSLATORS: Neutral dialog spoken by Adria */ "I sense a soul in search of answers..."), false, SfxID::Adria38 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Adria (Gossip) */ "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."), - true, TSFX_WITCH39 }, + true, SfxID::Adria39 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Adria (Gossip) */ "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."), - true, TSFX_WITCH40 }, + true, SfxID::Adria40 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Adria (Gossip) */ "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."), - true, TSFX_WITCH41 }, + true, SfxID::Adria41 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Adria (Gossip) */ "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."), - true, TSFX_WITCH42 }, + true, SfxID::Adria42 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Adria (Gossip) */ "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."), - true, TSFX_WITCH43 }, + true, SfxID::Adria43 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Adria (Gossip) */ "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."), - true, TSFX_WITCH44 }, + true, SfxID::Adria44 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Adria (Gossip) */ "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."), - true, TSFX_WITCH45 }, + true, SfxID::Adria45 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Adria (Gossip) */ "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."), - true, TSFX_WITCH46 }, + true, SfxID::Adria46 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Adria (Gossip) */ "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."), - true, TSFX_WITCH47 }, + true, SfxID::Adria47 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Adria (Gossip) */ "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."), - true, TSFX_WITCH49 }, + true, SfxID::Adria49 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Adria (Gossip) */ "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."), - true, TSFX_WITCH50 }, - { N_(/* TRANSLATORS: Neutral dialog spoken by Wirt */ "Pssst... over here..."), false, TSFX_PEGBOY32 }, + true, SfxID::Adria50 }, + { N_(/* TRANSLATORS: Neutral dialog spoken by Wirt */ "Pssst... over here..."), false, SfxID::Wirt32 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) */ "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."), - true, TSFX_PEGBOY33 }, + true, SfxID::Wirt33 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) */ "Don't trust everything the drunk says. Too many ales have fogged his vision and his good sense."), - true, TSFX_PEGBOY34 }, + true, SfxID::Wirt34 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) */ "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..."), - true, TSFX_PEGBOY35 }, + true, SfxID::Wirt35 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) */ "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."), - true, TSFX_PEGBOY36 }, + true, SfxID::Wirt36 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) */ "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..."), - true, TSFX_PEGBOY37 }, + true, SfxID::Wirt37 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) */ "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."), - true, TSFX_PEGBOY38 }, + true, SfxID::Wirt38 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) */ "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..."), - true, TSFX_PEGBOY39 }, + true, SfxID::Wirt39 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) */ "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..."), - true, TSFX_PEGBOY40 }, + true, SfxID::Wirt40 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) */ "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."), - true, TSFX_PEGBOY42 }, + true, SfxID::Wirt42 }, { N_(/* TRANSLATORS: Neutral dialog spoken by Wirt (Gossip) */ "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..."), - true, TSFX_PEGBOY43 }, + true, SfxID::Wirt43 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "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."), - true, PS_WARR1 }, + true, SfxID::Warrior1 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "...and so, locked beyond the Gateway of Blood and past the Hall of Fire, Valor awaits for the Hero of Light to awaken..."), - true, PS_WARR10 }, + true, SfxID::Warrior10 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "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."), - true, PS_WARR11 }, + true, SfxID::Warrior11 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "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."), - true, PS_WARR12 }, + true, SfxID::Warrior12 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "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."), - true, PS_WARR1 }, + true, SfxID::Warrior1 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "...and so, locked beyond the Gateway of Blood and past the Hall of Fire, Valor awaits for the Hero of Light to awaken..."), - true, PS_WARR10 }, + true, SfxID::Warrior10 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "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."), - true, PS_WARR11 }, + true, SfxID::Warrior11 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "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."), - true, PS_WARR12 }, + true, SfxID::Warrior12 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "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."), - true, PS_WARR1 }, + true, SfxID::Warrior1 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "...and so, locked beyond the Gateway of Blood and past the Hall of Fire, Valor awaits for the Hero of Light to awaken..."), - true, PS_WARR10 }, + true, SfxID::Warrior10 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "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."), - true, PS_WARR11 }, + true, SfxID::Warrior11 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "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."), - true, PS_WARR12 }, - { "", false, TSFX_COW1 }, - { "", false, TSFX_COW2 }, - /* - { "", false, TSFX_COW3 }, - { "", false, TSFX_COW4 }, - { "", false, TSFX_COW5 }, - { "", false, TSFX_COW6 }, - { "", false, TSFX_COW7 }, - { "", false, TSFX_COW8 }, -*/ + true, SfxID::Warrior12 }, + { "", false, SfxID::Cow1 }, + { "", false, SfxID::Cow2 }, { N_(/* TRANSLATORS: Book read aloud */ "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."), - true, PS_NAR1 }, + true, SfxID::Narrator1 }, { N_(/* TRANSLATORS: Book read aloud */ "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."), - true, PS_NAR2 }, + true, SfxID::Narrator2 }, { N_(/* TRANSLATORS: Book read aloud */ "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."), - true, PS_NAR3 }, + true, SfxID::Narrator3 }, { N_(/* TRANSLATORS: Book read aloud */ "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."), - true, PS_NAR4 }, + true, SfxID::Narrator4 }, { N_(/* TRANSLATORS: Book read aloud */ "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."), - true, PS_NAR5 }, + true, SfxID::Narrator5 }, { N_(/* TRANSLATORS: Book read aloud */ "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..."), - true, PS_NAR6 }, + true, SfxID::Narrator6 }, { N_(/* TRANSLATORS: Book read aloud */ "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."), - true, PS_NAR7 }, + true, SfxID::Narrator7 }, { N_(/* TRANSLATORS: Book read aloud */ "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."), - true, PS_NAR8 }, + true, SfxID::Narrator8 }, { N_(/* TRANSLATORS: Book read aloud */ "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."), - true, PS_NAR9 }, + true, SfxID::Narrator9 }, { N_(/* TRANSLATORS: Neutral Text spoken by Ogden */ "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."), - true, TSFX_TAVERN0 }, + true, SfxID::Ogden0 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "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."), - true, PS_WARR1 }, + true, SfxID::Warrior1 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "...and so, locked beyond the Gateway of Blood and past the Hall of Fire, Valor awaits for the Hero of Light to awaken..."), - true, PS_WARR10 }, + true, SfxID::Warrior10 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "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."), - true, PS_WARR11 }, + true, SfxID::Warrior11 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "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."), - true, PS_WARR12 }, + true, SfxID::Warrior12 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "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."), - true, PS_WARR1 }, + true, SfxID::Warrior1 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "...and so, locked beyond the Gateway of Blood and past the Hall of Fire, Valor awaits for the Hero of Light to awaken..."), - true, PS_WARR10 }, + true, SfxID::Warrior10 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "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."), - true, PS_WARR11 }, + true, SfxID::Warrior11 }, { N_(/* TRANSLATORS: Quest text spoken aloud from a book by player */ "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."), - true, PS_WARR12 }, + true, SfxID::Warrior12 }, { N_(/* TRANSLATORS: Quest text spoken by Adria */ "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."), - true, TSFX_WITCH19 }, + true, SfxID::Adria19 }, { N_(/* TRANSLATORS: Quest text spoken by Griswold */ "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."), - true, TSFX_SMITH18 }, + true, SfxID::Griswold18 }, { N_(/* TRANSLATORS: Quest text spoken by Pepin */ "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."), - true, TSFX_HEALER17 }, + true, SfxID::Pepin17 }, { N_(/* TRANSLATORS: Quest text spoken by Adria */ "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."), - true, TSFX_WITCH9 }, + true, SfxID::Adria9 }, { N_(/* TRANSLATORS: Quest text spoken by Ogden */ "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."), - true, TSFX_TAVERN17 }, + true, SfxID::Ogden17 }, { N_(/* TRANSLATORS: Quest text spoken by Cain */ "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."), - true, TSFX_STORY19 }, + true, SfxID::Cain19 }, { N_(/* TRANSLATORS: Quest text spoken by Farnham */ "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."), - true, TSFX_DRUNK21 }, + true, SfxID::Farnham21 }, { N_(/* TRANSLATORS: Quest text spoken by Gillian */ "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."), - true, TSFX_BMAID27 }, + true, SfxID::Gillian27 }, { N_(/* TRANSLATORS: Quest text spoken by Wirt */ "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?"), - true, TSFX_PEGBOY7 }, + true, SfxID::Wirt7 }, { N_(/* TRANSLATORS: Quest text spoken by Adria */ "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."), - true, TSFX_WITCH9 }, + true, SfxID::Adria9 }, { N_(/* TRANSLATORS: Neutral text spoken by Farmer (Gossip) */ "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..."), - true, TSFX_FARMER1 }, + true, SfxID::Farmer1 }, { N_(/* TRANSLATORS: Neutral text spoken by Farmer (Gossip) */ "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."), - true, TSFX_FARMER2 }, + true, SfxID::Farmer2 }, { N_(/* TRANSLATORS: Neutral text spoken by Farmer (Gossip) */ "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."), - true, TSFX_FARMER3 }, + true, SfxID::Farmer3 }, { N_(/* TRANSLATORS: Neutral text spoken by Farmer (Gossip) */ "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."), - true, TSFX_FARMER4 }, + true, SfxID::Farmer4 }, { N_(/* TRANSLATORS: Neutral text spoken by Farmer (Gossip) */ "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?"), - true, TSFX_FARMER5 }, - { N_(/* TRANSLATORS: Quest text spoken by Little Girl */ "Waaaah! (sniff) Waaaah! (sniff)"), true, TSFX_TEDDYBR1 }, + true, SfxID::Farmer5 }, + { N_(/* TRANSLATORS: Quest text spoken by Little Girl */ "Waaaah! (sniff) Waaaah! (sniff)"), true, SfxID::Celia1 }, { N_(/* TRANSLATORS: Quest text spoken by Little Girl */ "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!"), - true, TSFX_TEDDYBR2 }, + true, SfxID::Celia2 }, { N_(/* TRANSLATORS: Quest text spoken by Little Girl */ "Didja find him? You gotta find Theodore, please! He's just little. He can't take care of himself! Please!"), - true, TSFX_TEDDYBR3 }, + true, SfxID::Celia3 }, { N_(/* TRANSLATORS: Quest text spoken by Little Girl (Quest End) */ "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!"), - true, TSFX_TEDDYBR4 }, + true, SfxID::Celia4 }, { N_(/* TRANSLATORS: Quest text spoken by Defiler (Hostile) */ "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..."), - true, USFX_DEFILER6 }, + true, SfxID::Defiler6 }, { N_(/* TRANSLATORS: Quest text spoken by Defiler (Hostile) */ "Have you been enjoying yourself, little mammal? How pathetic. Your little world will be no challenge at all."), - true, USFX_DEFILER2 }, + true, SfxID::Defiler2 }, { N_(/* TRANSLATORS: Quest text spoken by Defiler (Hostile) */ "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."), - true, USFX_DEFILER7 }, + true, SfxID::Defiler7 }, { N_(/* TRANSLATORS: Quest text spoken by Defiler (Hostile) */ "Ah, I can smell you...you are close! Close! Ssss...the scent of blood and fear...how enticing..."), - true, USFX_DEFILER4 }, - { "", true, USFX_DEFILER8 }, - { "", true, USFX_NAKRUL1 }, - { "", true, USFX_NAKRUL2 }, - { "", true, USFX_NAKRUL3 }, - { "", true, USFX_NAKRUL4 }, - { "", true, USFX_NAKRUL5 }, + true, SfxID::Defiler4 }, + { "", true, SfxID::Defiler8 }, + { "", true, SfxID::NaKrul1 }, + { "", true, SfxID::NaKrul2 }, + { "", true, SfxID::NaKrul3 }, + { "", true, SfxID::NaKrul4 }, + { "", true, SfxID::NaKrul5 }, { N_(/* TRANSLATORS: Quest text spoken by Narrator */ "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."), - true, PS_NARATR3 }, - { N_(/* TRANSLATORS: Quest text spoken by Complete Nut */ "Moo."), true, TSFX_COWSUT1 }, - { N_(/* TRANSLATORS: Quest text spoken by Complete Nut */ "I said, Moo."), true, TSFX_COWSUT2 }, - { N_(/* TRANSLATORS: Quest text spoken by Complete Nut */ "Look I'm just a cow, OK?"), true, TSFX_COWSUT3 }, + true, SfxID::NarratorHF3 }, + { N_(/* TRANSLATORS: Quest text spoken by Complete Nut */ "Moo."), true, SfxID::CompleteNut1 }, + { N_(/* TRANSLATORS: Quest text spoken by Complete Nut */ "I said, Moo."), true, SfxID::CompleteNut2 }, + { N_(/* TRANSLATORS: Quest text spoken by Complete Nut */ "Look I'm just a cow, OK?"), true, SfxID::CompleteNut3 }, { N_(/* TRANSLATORS: Quest text spoken by Complete Nut */ "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."), - true, TSFX_COWSUT4 }, + true, SfxID::CompleteNut4 }, { N_(/* TRANSLATORS: Quest text spoken by Complete Nut */ "What are you wasting time for? Go get my suit! And hurry! That Holstein over there keeps winking at me!"), - true, TSFX_COWSUT5 }, + true, SfxID::CompleteNut5 }, { N_(/* TRANSLATORS: Quest text spoken by Complete Nut */ "Hey, have you got my suit there? Quick, pass it over! These ears itch like you wouldn't believe!"), - true, TSFX_COWSUT6 }, + true, SfxID::CompleteNut6 }, { N_(/* TRANSLATORS: Quest text spoken by Complete Nut */ "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."), - true, TSFX_COWSUT7 }, + true, SfxID::CompleteNut7 }, { N_(/* TRANSLATORS: Quest text spoken by Complete Nut */ "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."), - true, TSFX_COWSUT8 }, + true, SfxID::CompleteNut8 }, { N_(/* TRANSLATORS: Quest text spoken by Complete Nut */ "Look. I'm a cow. And you, you're monster bait. Get some experience under your belt! We'll talk..."), - true, TSFX_COWSUT9 }, - { "", true, TSFX_TRADER1 }, + true, SfxID::CompleteNut9 }, + { "", true, SfxID::None }, { N_(/* TRANSLATORS: Quest text spoken by Farmer */ "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?"), - true, TSFX_FARMER2A }, + true, SfxID::Farmer2a }, { N_(/* TRANSLATORS: Quest text spoken by Farmer */ "Oh, I could use your help, but perhaps after you've saved the catacombs from the desecration of those beasts."), - true, TSFX_FARMER6 }, + true, SfxID::Farmer6 }, { N_(/* TRANSLATORS: Quest text spoken by Farmer */ "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."), - true, TSFX_FARMER7 }, + true, SfxID::Farmer7 }, { N_(/* TRANSLATORS: Quest text spoken by Farmer */ "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?"), - true, TSFX_FARMER8 }, + true, SfxID::Farmer8 }, { N_(/* TRANSLATORS: Quest text spoken by Farmer */ "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!"), - true, TSFX_FARMER9 }, + true, SfxID::Farmer9 }, { N_(/* TRANSLATORS: Quest text spoken by Complete Nut */ "Me, I'm a self-made cow. Make something of yourself, and... then we'll talk."), - true, TSFX_COWSUT10 }, + true, SfxID::CompleteNut10 }, { N_(/* TRANSLATORS: Quest text spoken by Complete Nut */ "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..."), - true, TSFX_COWSUT11 }, + true, SfxID::CompleteNut11 }, { N_(/* TRANSLATORS: Quest text spoken by Complete Nut */ "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."), - true, TSFX_COWSUT12 }, + true, SfxID::CompleteNut12 }, { N_(/* TRANSLATORS: Quest text spoken by Complete Nut */ "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."), - true, TSFX_COWSUT4A }, - { N_(/* TRANSLATORS: Quest text spoken by Unknown, Maybe Farmer */ "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."), - true, USFX_SKLJRN1 }, + true, SfxID::CompleteNut4a }, + { "", true, SfxID::None }, { N_(/* TRANSLATORS: Quest text read aloud from book */ "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."), - true, PS_NARATR6 }, + true, SfxID::NarratorHF6 }, { N_(/* TRANSLATORS: Quest text read aloud from book */ "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."), - true, PS_NARATR7 }, + true, SfxID::NarratorHF7 }, { N_(/* TRANSLATORS: Quest text read aloud from book */ "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."), - true, PS_NARATR8 }, + true, SfxID::NarratorHF8 }, { N_(/* TRANSLATORS: Quest text read aloud from book */ "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."), - true, PS_NARATR5 }, + true, SfxID::NarratorHF5 }, { N_(/* TRANSLATORS: Quest text read aloud from book */ "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."), - true, PS_NARATR9 }, + true, SfxID::NarratorHF9 }, { N_(/* TRANSLATORS: Quest text read aloud from book */ "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."), - true, PS_NARATR4 }, - { N_(/* TRANSLATORS: Quest text read aloud from book by player */ "In Spiritu Sanctum."), true, PS_WARR54 }, - { N_(/* TRANSLATORS: Quest text read aloud from book by player */ "Praedictum Otium."), true, PS_WARR55 }, - { N_(/* TRANSLATORS: Quest text read aloud from book by player */ "Efficio Obitus Ut Inimicus."), true, PS_WARR56 }, - { N_("In Spiritu Sanctum."), true, PS_WARR54 }, - { N_("Praedictum Otium."), true, PS_WARR55 }, - { N_("Efficio Obitus Ut Inimicus."), true, PS_WARR56 }, - { N_("In Spiritu Sanctum."), true, PS_WARR54 }, - { N_("Praedictum Otium."), true, PS_WARR55 }, - { N_("Efficio Obitus Ut Inimicus."), true, PS_WARR56 }, - { N_("In Spiritu Sanctum."), true, PS_WARR54 }, - { N_("Praedictum Otium."), true, PS_WARR55 }, - { N_("Efficio Obitus Ut Inimicus."), true, PS_WARR56 }, - { N_("In Spiritu Sanctum."), true, PS_WARR54 }, - { N_("Praedictum Otium."), true, PS_WARR55 }, - { N_("Efficio Obitus Ut Inimicus."), true, PS_WARR56 }, + true, SfxID::NarratorHF4 }, + { N_(/* TRANSLATORS: Quest text read aloud from book by player */ "In Spiritu Sanctum."), true, SfxID::Warrior54 }, + { N_(/* TRANSLATORS: Quest text read aloud from book by player */ "Praedictum Otium."), true, SfxID::Warrior55 }, + { N_(/* TRANSLATORS: Quest text read aloud from book by player */ "Efficio Obitus Ut Inimicus."), true, SfxID::Warrior56 }, + { N_("In Spiritu Sanctum."), true, SfxID::Warrior54 }, + { N_("Praedictum Otium."), true, SfxID::Warrior55 }, + { N_("Efficio Obitus Ut Inimicus."), true, SfxID::Warrior56 }, + { N_("In Spiritu Sanctum."), true, SfxID::Warrior54 }, + { N_("Praedictum Otium."), true, SfxID::Warrior55 }, + { N_("Efficio Obitus Ut Inimicus."), true, SfxID::Warrior56 }, + { N_("In Spiritu Sanctum."), true, SfxID::Warrior54 }, + { N_("Praedictum Otium."), true, SfxID::Warrior55 }, + { N_("Efficio Obitus Ut Inimicus."), true, SfxID::Warrior56 }, + { N_("In Spiritu Sanctum."), true, SfxID::Warrior54 }, + { N_("Praedictum Otium."), true, SfxID::Warrior55 }, + { N_("Efficio Obitus Ut Inimicus."), true, SfxID::Warrior56 }, /** - { N_("Please help....! I barely escaped from....... The Butcher...! He killed.... My wife... My children! I beg of you...... In the name of God......... Avenge them........."), true, TSFX_DEADGUY }, - { N_("*retching* Listen, listen. I don't even like worms. Don't tell me about yer worms! I don't want yer worms! Nope, no thanks! *slight hiccup/gag* No worms for me."), true, TSFX_DRUNK5 }, - { N_("*SNORE*"), true, TSFX_DRUNK6 }, - { N_("Sounds like a good idea to me. Ya better get started right awaaay."), true, TSFX_DRUNK8 }, - { N_("*laugh* Did you ever hear I love that King tale? I love that one."), true, TSFX_DRUNK9 }, - { N_("(Crying) No, you can't make me go back there. I won't let you take me. Too much pain, too many dead. Can't... get the blood out of my eyes... my mouth... *wails*"), true, TSFX_DRUNK11 }, - { N_("You're gonna hunt down a demon? Is that what you said? I know I didn't hear that 'cuz nobody hunts down a demon, no! Nobody!"), true, TSFX_DRUNK16 }, - { N_("Hey, lemme see that. *blows nose* Thanks!"), true, TSFX_DRUNK18 }, - { N_("Hey you there, come here, listen up. You know about the island where angels watch? Pick the right rocks, but you better shield your eyes; shield everything! I know, 'cause I been there and... Mmmm, ale."), true, TSFX_DRUNK22 }, - { N_("I haven't ever see a priest around here. If I did I'd kick him right in the *BBBBUUURRRPPP* Can't even keep a church free of those Hell spawn - bastards. What good are they, those holy men? Liars! Liars!"), true, TSFX_DRUNK33 }, + { N_("Please help....! I barely escaped from....... The Butcher...! He killed.... My wife... My children! I beg of you...... In the name of God......... Avenge them........."), true, SfxID::WoundedTownsmanOld }, + { N_("*retching* Listen, listen. I don't even like worms. Don't tell me about yer worms! I don't want yer worms! Nope, no thanks! *slight hiccup/gag* No worms for me."), true, SfxID::Farnham5 }, + { N_("*SNORE*"), true, SfxID::Farnham6 }, + { N_("Sounds like a good idea to me. Ya better get started right awaaay."), true, SfxID::Farnham8 }, + { N_("*laugh* Did you ever hear I love that King tale? I love that one."), true, SfxID::Farnham9 }, + { N_("(Crying) No, you can't make me go back there. I won't let you take me. Too much pain, too many dead. Can't... get the blood out of my eyes... my mouth... *wails*"), true, SfxID::Farnham11 }, + { N_("You're gonna hunt down a demon? Is that what you said? I know I didn't hear that 'cuz nobody hunts down a demon, no! Nobody!"), true, SfxID::Farnham16 }, + { N_("Hey, lemme see that. *blows nose* Thanks!"), true, SfxID::Farnham18 }, + { N_("Hey you there, come here, listen up. You know about the island where angels watch? Pick the right rocks, but you better shield your eyes; shield everything! I know, 'cause I been there and... Mmmm, ale."), true, SfxID::Farnham22 }, + { N_("I haven't ever see a priest around here. If I did I'd kick him right in the *BBBBUUURRRPPP* Can't even keep a church free of those Hell spawn - bastards. What good are they, those holy men? Liars! Liars!"), true, SfxID::Farnham33 }, { N_("Wait, before you say anything, my grandmother had a dream with giant, slithering creatures in it. She also saw Pepin running from a house in the town. Do you think this means anything?"), - true, TSFX_BMAID5 }, + true, SfxID::Gillian5 }, { N_("This is something that is far beyond anything I have ever learned. I can only think of one person in all of Tristram who could help you... Cain, of course."), - true, TSFX_BMAID7 }, + true, SfxID::Gillian7 }, { N_("How could you even think of going back to that place? What could be so important?"), - true, TSFX_BMAID9 }, + true, SfxID::Gillian9 }, { N_("Those are the words of a drunkard. I don't see how they could be true."), - true, TSFX_BMAID14 }, + true, SfxID::Gillian14 }, { N_("I heard that the priest Tremain was going to perform an exorcism, but I didn't go. I don't see why we all have to spend every moment captive to what is lurking underneath our town. I know that many have died at the hands of these monsters, but we need to try to go on. I know that you have come here to free Tristram from the clutches of darkness, and I hope that one day we can live in peace again."), - true, TSFX_BMAID15 }, + true, SfxID::Gillian15 }, { N_("A treasure map? Do you have it with you? Let me see... Hmm, it looks like this is pretty old, and some of the buildings in the town are not on this map. Oh, I wish I could go and look for the treasure with you, but I have to start work soon."), - true, TSFX_BMAID17 }, + true, SfxID::Gillian17 }, { N_("Beg pardon, but have you heard what has been happening? Some of the men have vanished from the village. Dica's sons, Inaius and Roof, claim to have seen strange lights(= blood stars) glowing deep within the woods a few days ago, but no one has heard from them since. I, too, have seen a crimson shimmering from beyond the ash groves, but I've been too afraid to approach it. I fear that Ogden or our healer Pepin may be next. Won't you please find out what has happened?"), - true, TSFX_BMAID21 }, + true, SfxID::Gillian21 }, { N_("Have you looked into what has caused the strange lights yet? There are more men missing since last we spoke, and I'm beginning to fear that soon we may all vanish."), - true, TSFX_BMAID22 }, + true, SfxID::Gillian22 }, { N_("Demons that assume the form of beautiful women, you say? That's horrible! Thank the Light that you solved this mystery before any more of our men were lured to their deaths. Thank you for keeping us safe from the powers of darkness once again."), - true, TSFX_BMAID23 }, + true, SfxID::Gillian23 }, { N_("Pray your pardon, but I've something to tell you that you may find interesting. It was the strangest thing. While drinking at the tavern, Farnham was rambling about something called Azurewrath. He also said something about a fallen angel. It was hard to understand him because he was very drunk and disoriented, but I seem to remember something about a key in a barrel. He also kept covering his face and repeating the word 'Izual' over and over again."), - true, TSFX_BMAID24 }, + true, SfxID::Gillian24 }, { N_("You know, now that I think about it... maybe it wasn't a key in a barrel, but a barrel that was the key. Does that make any more sense?"), - true, TSFX_BMAID25 }, + true, SfxID::Gillian25 }, { N_("Azurewrath and Izual. I don't know why I didn't remember that story earlier. I've heard it often enough from my grandmother and Cain the Storyteller. Well, I hope that my information was still more of a help than a hindrance. May Light guide you, my friend."), - true, TSFX_BMAID26 }, + true, SfxID::Gillian26 }, { N_("I know that my grandmother’s story seems strange, but the graveyard does have many mysteries surrounding it. It couldn’t hurt you to put something there and see what happens, could it? Maybe you will find out something that will help us all."), - true, TSFX_BMAID28 }, + true, SfxID::Gillian28 }, { N_("My grandmother had a dream about you last night, she said that in her dream you used your bare hands to defeat one of those foul monsters that lurk under the church. When it died, it exploded into fabulous treasure! Although she was a bit frightened by her dream, she was able to describe the beast quite clearly to me."), - true, TSFX_BMAID29 }, + true, SfxID::Gillian29 }, { N_("My grandmother had that exact same dream again. She said you are quite brave and seemed very pleased with your prize."), - true, TSFX_BMAID30 }, + true, SfxID::Gillian30 }, { N_("I really don't know much about the priest Tremain. He never visits the tavern, preferring to keep to the company of Pepin and Cain. Perhaps it is because they, too, have more scholarly pursuits."), - true, TSFX_BMAID38 }, + true, SfxID::Gillian38 }, { N_("Roof and Inaius are missing? Light protect us! Is there no place that is safe? Our only hope of returning to a peaceful life rests in you. Please, you must find those boys and bring them home to their family."), - true, TSFX_HEALER4 }, + true, SfxID::Pepin4 }, { N_("Horazon was insane. There are forces with which one does not interfere. It would not surprise me if you found only the charred remains of this damned fool."), - true, TSFX_HEALER6 }, + true, SfxID::Pepin6 }, { N_("Farnham is often confused, but he speaks a powerful name when the word Izual passes his lips. Cain would be able to tell you in much greater detail the legend of this warrior."), - true, TSFX_HEALER7 }, + true, SfxID::Pepin7 }, { N_("I suppose it isn't beyond the realm of possibility if you could bear being in that room again. Your description of the atrocities committed there would be enough to keep me far from it."), - true, TSFX_HEALER9 }, + true, SfxID::Pepin9 }, { N_("For once I can vouch for Farnham's extraordinary claim. There are many mentions in the books that I have been reading about a place of great healing where warriors of light would go to mend the wounds sustained in the Sin War. If you could find this place it would most assuredly be to the benefit of us all."), - true, TSFX_HEALER14 }, + true, SfxID::Pepin14 }, { N_("I was asked to assist in the exorcism. My skills were able to ease the poor man's suffering as Tremain drove the demon from his body. While I was treating him for an exceptionally high fever, he spoke of a place of searing heat. The tortured fellow cried out about Hell and falling into a pit of flame. I could not make any sense of it, and thankfully he soon recovered."), - true, TSFX_HEALER15 }, + true, SfxID::Pepin15 }, { N_("Good hero, a moment of your time please. While attending one of the townsfolk who had taken quite ill, I noticed something odd about his home. There were strange sounds and a sickly sweet smell rising from the cellar. Thinking perhaps these fumes had something to do with his sickness. In his cellar were monstrous worms shifting and squirming up from the underground. I beg of you, slay these creatures before they can make their way into the town. I left the door to his house open for you. It is the one opposite of mine."), - true, TSFX_HEALER23 }, + true, SfxID::Pepin23 }, { N_("I fear that the worms could soon overrun the village. I know that they are coming up from under the house that is opposite mine. Just the thought of those slimy beasts oozing into my house makes me want to be ill. Please, rid us of them."), - true, TSFX_HEALER24 }, + true, SfxID::Pepin24 }, { N_("Once again you have saved this humble town from the encroaching evil. We are, as always, forever in your debt."), - true, TSFX_HEALER25 }, + true, SfxID::Pepin25 }, { N_("My friend, I must speak with you. While going to help a sick villager I came upon a demon in the town. I fled from the house and accidentally left the door open in my haste. I pray that the vial creature is still in there. Please help us before it comes for us all."), - true, TSFX_HEALER28 }, + true, SfxID::Pepin28 }, { N_("These creatures must be banished from our town. To my shame I left the door open, but that should make all the easier for you to find the house."), - true, TSFX_HEALER29 }, + true, SfxID::Pepin29 }, { N_("Once again the town is in your debt. It is only your strength and force of will that can leads us out of this evil time."), - true, TSFX_HEALER30 }, + true, SfxID::Pepin30 }, { N_("Once again I require your strong arm and quick quirks to aid the people of this town. I am in need of certain reagents to help fight the plagues that the demons have unleashed on the land by their very presence. If you could gather these few items, it will save many lives!"), - true, TSFX_HEALER31 }, + true, SfxID::Pepin31 }, { N_("The diseases spread quickly and many are dying. Please, help me by finding the reagents so that I can use it to make an antidote."), - true, TSFX_HEALER32 }, + true, SfxID::Pepin32 }, { N_("Thank you so much! You bring hope and light to this dark and troubled times. I wish for you to have this in the hopes that will aid you in your battle against the darkness."), - true, TSFX_HEALER33 }, + true, SfxID::Pepin33 }, { N_("I know that I ask much of you, but I must now ask that you find a pool of clear water. Take these containers, fill them and return them me as soon as you can. With the clear water I can create an elixir of wondrous power that will benefit us all."), - true, TSFX_HEALER34 }, + true, SfxID::Pepin34 }, { N_("Have you brought what I need? The clear water will allow me to create a very power elixir, fill the containers that I gave you and return with them."), - true, TSFX_HEALER35 }, + true, SfxID::Pepin35 }, { N_("Very good, my friend! Very good! Just give a minute to mix these ingredients... Perfect! Here is some of the elixir as promised."), - true, TSFX_HEALER36 }, + true, SfxID::Pepin36 }, { N_("His Holiness is a wondrous man of great knowledge and understanding. He has shown me many cures for rare and deadly diseases. He brings me books and reagents for my work whenever he can. But I fear that he may some day take on a task that is too great even for him."), - true, TSFX_HEALER44 }, + true, SfxID::Pepin44 }, { N_("I've seen no such things in my shop, but I will keep a close watch for them. Perhaps if they come this way, they won't take kindly to the fires of my forge."), - true, TSFX_SMITH5 }, + true, SfxID::Griswold5 }, { N_("I admit that I too have seen these strange lights, but I have not felt pulled towards them. I'll be sure to steer clear of them and will tell everyone that visits here to do likewise."), - true, TSFX_SMITH6 }, + true, SfxID::Griswold6 }, { N_("A bold tale indeed! My limited time beneath the cathedral leaves me poorly equipped to offer you any help with this. But, as always, you'll find Cain well versed in legends and folklore."), - true, TSFX_SMITH8 }, + true, SfxID::Griswold8 }, { N_("The blade Azurewrath... It's legend! It was cast by the angelic weaponsmith Cinadide and tempered within the fires of judgment! Whoever wields this weapon will find the legions of Hell at his feet! If you found this blade, I would begin to truly believe that you could end the nightmare that has befallen our town."), - true, TSFX_SMITH9 }, + true, SfxID::Griswold9 }, { N_("When I found Wirt, he was very near a room that sounds like the vile pit you've described. The stench of death was heavy in the air and, consumed as I was with getting the lad to safety, I did not go further than I had to. If he claims to know something of that place, I would not discount his word easily."), - true, TSFX_SMITH11 }, - { N_("Farnham speaks of a place that exists, at least in legend. Warriors would go to a place at the edge of Hell to gird themselves for battle against the armies of darkness. If the stories are true, untold treasures could lie upon this island of the sunless sea."), - true, TSFX_SMITH15 }, + true, SfxID::TSfxID::SFX_SMITH11 }, + { N_("Farnham speaks of a place that exists, at least in legend. Warriors would go to a place at the edge of Hell to gird themselves for battle against the armies of darkness. If the stories are true, SfxID::untold treasures could lie upon this island of the sunless sea."), + true, SfxID::Griswold15 }, { N_("Ah, you speak of an ancient and evil weapon. Tread lightly in this area, for the legends of Shadowfang are as black as a moonless winter night. Crafted within the Hellforge, Shadowfang can rend the very soul from whoever it strikes. I do not envy you if is in your mind to defeat the one who wields it. May light protect you, brave hero."), - true, TSFX_SMITH16 }, + true, SfxID::Griswold16 }, { N_("I'm in luck! A caravan has stopped just outside of the village and is taking supplies to the lands of the East. Certain items will bring a special price... if you can get them for me."), - true, TSFX_SMITH27 }, + true, SfxID::Griswold27 }, { N_("We can both turn a nice profit if you can deliver the right goods to me. What do you say?"), - true, TSFX_SMITH28 }, + true, SfxID::Griswold28 }, { N_("You're just in time. The caravan is leaving tonight! Here's your cut, friend."), - true, TSFX_SMITH29 }, + true, SfxID::Griswold29 }, { N_("I believe that I may have found a way to greatly improve some weapons. If you can bring me what I need, I'd be willing to try out my idea and, if it works, it's yours!"), - true, TSFX_SMITH30 }, + true, SfxID::Griswold30 }, { N_("I still think I can improve the right weapon!"), - true, TSFX_SMITH31 }, + true, SfxID::Griswold31 }, { N_("Let me just do this... and this... and this. Ah, it works! Take this back and give it a try."), - true, TSFX_SMITH32 }, + true, SfxID::Griswold32 }, { N_("I'm working on a method for strengthening armor. Unfortunately, I don't have what I need just now. If you could possibly bring me what I require, the first one is yours!"), - true, TSFX_SMITH33 }, + true, SfxID::Griswold33 }, { N_("I still think I can strengthen the right armor. You know what you need to bring me."), - true, TSFX_SMITH34 }, + true, SfxID::Griswold34 }, { N_("Good find! See if this works any better for you."), - true, TSFX_SMITH35 }, + true, SfxID::Griswold35 }, { N_("If you come across any enchanted equipment, I could try to learn how it was crafted and then use those methods for my own creations. I should probably start with something easy."), - true, TSFX_SMITH36 }, + true, SfxID::Griswold36 }, { N_("I still think that I can strip the knowledge of enchantments... if you bring me a certain item."), - true, TSFX_SMITH37 }, + true, SfxID::Griswold37 }, { N_("Thank you, oh great and mighty customer... err... uhh... champion! This will take some study... Done!"), - true, TSFX_SMITH38 }, + true, SfxID::Griswold38 }, { N_("Keep your eyes open for a figurine made of iron. I have uncovered some old records concerning a specific type of figurine and the secrets of metalcrafting that it holds."), - true, TSFX_SMITH39 }, + true, SfxID::Griswold39 }, { N_("I know that the right figurine could be quite powerful. If you see what I'm looking for, get it and bring it to me straight away!"), - true, TSFX_SMITH40 }, + true, SfxID::Griswold40 }, { N_("The secrets of this metal are fantastic! Oh, how I could make the right item gleam with power."), - true, TSFX_SMITH41 }, + true, SfxID::Griswold41 }, { N_("If you deliver to me the right item so I can combine it with this metal, I could craft something worthy of the warriors of Heaven!"), - true, TSFX_SMITH42 }, + true, SfxID::Griswold42 }, { N_("Your efforts are not in vain. It seems that all is as I had hoped. I trust you will find this useful in your battles below?"), - true, TSFX_SMITH43 }, + true, SfxID::Griswold43 }, { N_("Haha, they have you hunting worms now? What's next? Leaf collecting? Picking up mushrooms? Look, friend, you have a whole church full of demons over there to worry about. I don't see how a few little worms could be so bad."), - 1, 5, TSFX_PEGBOY5 }, + 1, 5, SfxID::Wirt5 }, { N_("You know, I've been looking for those lights, but I can't find them. If Gillian says they're there, I believe her, but I have yet to see them for myself."), - 1, 5, TSFX_PEGBOY6 }, + 1, 5, SfxID::Wirt6 }, { N_("The care and feeding of demons is definitely not an interest of mine. Here is a piece of friendly advice - if you get the chance, kill anything you see down there."), - 1, 5, TSFX_PEGBOY8 }, + 1, 5, SfxID::Wirt8 }, { N_("If you were to find any trace of Izual or the blade Azurewrath, even I would be impressed. That is definitely one of a kind."), - 1, 5, TSFX_PEGBOY9 }, + 1, 5, SfxID::Wirt9 }, { N_("This is one time that you should listen to Farnham. I have heard of this place, and I know a few sorcerers who have tried to create a portal to get there."), - 1, 5, TSFX_PEGBOY15 }, + 1, 5, SfxID::Wirt15 }, { N_("I don't know who this Fleshdoom is, but I have heard rumors of an ebon blade that cleaves a soul from the body. Even I would not try to sell that thing, no matter what the profit. If you find it, you should do as Tremain says and destroy it as quickly as possible."), - 1, 5, TSFX_PEGBOY16 }, + 1, 5, SfxID::Wirt16 }, { N_("Here over... pssst... Chamber butcher the from spell portal town the cast! Saying am I what see?!"), - 1, 5, TSFX_PEGBOY21 }, + 1, 5, SfxID::Wirt21 }, { N_("Yet out it figured you haven't?! Understand could you even so simple it make to tried I! Chamber butcher the from spell portal town the cast!"), - 1, 5, TSFX_PEGBOY22 }, + 1, 5, SfxID::Wirt22 }, { N_("Hmm, where is that stupid map? It was supposed to be between the rock and the tree before the bridge. It should be right... Oh, hello there... didn't, uh, see you standing there..."), - 1, 5, TSFX_PEGBOY23 }, + 1, 5, SfxID::Wirt23 }, { N_("Rock? Tree? Bridge? I have no idea what you're talking about."), - 1, 5, TSFX_PEGBOY24 }, + 1, 5, SfxID::Wirt24 }, { N_("Look, I know my prices are high, but one of my contacts had an... accident, and I need some help refilling my more mundane inventory. I'll trade you a quality item if you can just complete this little list for me."), - 1, 5, TSFX_PEGBOY25 }, + 1, 5, SfxID::Wirt25 }, { N_("Listen, I'll make it worth your time if you will get me what I asked you for. Besides, you know you want to find out what I'm going to give you in return."), - 1, 5, TSFX_PEGBOY26 }, + 1, 5, SfxID::Wirt26 }, { N_("Thanks! This should hold me over until I can find a new errand boy. Too bad you're so busy. Oh yeah, here's your part of the bargain."), - 1, 5, TSFX_PEGBOY27 }, + 1, 5, SfxID::Wirt27 }, { N_("Pssst... over here... I have something very special for sale today! I'm not exactly sure what it is, but I can just tell that it's great. I'm gonna offer you a bargain - a thousand gold takes it. Right now, no questions, and no returns!"), - 1, 5, TSFX_PEGBOY28 }, + 1, 5, SfxID::Wirt28 }, { N_("Hey, you! Yeah, I'm talking to you! I... acquired this strange book. I know it must do something big, but you need a crystal eyepiece to read it, so it's useless to me. However, I would be willing to trade you this rare and mystical tome, uh, for just a few things."), - 1, 5, TSFX_PEGBOY29 }, + 1, 5, SfxID::Wirt29 }, { N_("Look, this very special book that I have can be yours for so little. Don't tell anyone what a great deal I'm giving you or it would ruin my reputation."), - 1, 5, TSFX_PEGBOY30 }, + 1, 5, SfxID::Wirt30 }, { N_("Amazing, you actually found everything I wanted! Well, here's the book, but you're going to have to find a crystal eyepiece to read it. Good luck!"), - 1, 5, TSFX_PEGBOY31 }, + 1, 5, SfxID::Wirt31 }, { N_("Yeah, Tremain! He gets around, doesn't he? Or haven't you heard? My friends in some of the other towns say that he passes through, picking up a few books here, a pinch of bat claw there. Never seems to have the problems most do getting in and out of Tristram, that's for sure."), - 1, 5, TSFX_PEGBOY41 }, + 1, 5, SfxID::Wirt41 }, { N_("All I can do now is pray for us all."), - 1, 5, TSFX_PRIEST0 }, + 1, 5, SfxID::Tremain0 }, { N_("I seek a champion to undertake a serious duty, and the people of this town speak well of your courage and skill. The Archbishop Lazarus, once King Leoric's most trusted advisor and a member of our order, has taken the path of evil. Not long ago, Lazarus led a party of simple townsfolk into the labyrinth to find the king's missing son, Albrecht. Only a few of them escaped with their lives. Curse me for a fool! I should have suspected his veiled treachery then! For I have learned that it was Lazarus himself who kidnapped Leoric's son and has since hidden him within the labyrinth. I still don't understand why the Archbishop has turned to the darkness, or what his interest is in Albrecht. Unless... he means to sacrifice him at the full moon. That must be what he has planned! The survivors of his rescue party say that Lazarus was last seen in the deepest bowels of the labyrinth, some sixteen levels beneath the Cathedral. You must hurry, and save the prince from the sacrificial blade of this demented fiend!"), - 1, 5, TSFX_PRIEST1 }, + 1, 5, SfxID::Tremain1 }, { N_("Why do you delay?! Time is of the essence! The prince and the people of this kingdom are counting on you!"), - 1, 5, TSFX_PRIEST2 }, + 1, 5, SfxID::Tremain2 }, { N_("So, Lazarus has paid the price for his betrayal and justice is served! For your services this day, I bestow this mace unto you. Its name is Lightforge, and it is the holiest of our order's artifacts. As I am the last of this order, I entrust it to you. May the Light guide you."), - 1, 5, TSFX_PRIEST3 }, + 1, 5, SfxID::Tremain3 }, { N_("This is terrible! Lazarus will surely burn in Hell for his horrific deed! Although the boy that you describe may not be our prince, I believe that Albrecht may yet be in danger. Whatever vile power lies beneath the ground, has assuredly secured its foothold in our world. All I can do now is pray for us all."), - 1, 5, TSFX_PRIEST4 }, + 1, 5, SfxID::Tremain4 }, { N_("I have had a most disturbing experience that I must share with you, my friend. Earlier today, I was called upon to help one of the men that escaped from the labyrinth. He was deranged, violent, and kept lashing out at all of those who tried to calm him. I suspected that he was possessed by some sort of demonic entity, and so began to drive the evil from within him. After many hours, I was able to exorcise a demon who called himself Fleshdoom, but the hellion fled into the labyrinth. You may think that I am mad, but after speaking with the man and battling with Fleshdoom, I believe that the labyrinth has somehow become a gateway to the underworld. As you descend deeper, you may find yourself upon the doorstep of Hell itself. Finally, the man who was possessed retained memories of an ancient demonblade named Shadowfang. If you find the demon Fleshdoom, beware this foul sword. While I fear the dangers below grow even greater, you must find Fleshdoom and slay him. Bring the sword to me and I can destroy it, but do not wield it. For its power can corrupt, absolutely."), - 1, 5, TSFX_PRIEST5 }, + 1, 5, SfxID::Tremain5 }, { N_("Fleshdoom's demise is a great good to the world, yet Shadowfang remains! It must be found and destroyed! Do not attempt to use the demonblade, champion. It will corrupt and madden any mortal who wields it. I alone can end its dark evil."), - 1, 5, TSFX_PRIEST6 }, + 1, 5, SfxID::Tremain6 }, { N_("Light be praised! You found the cursed demonblade! Only its destruction can ensure the safety of us all! Wait... what treachery is this?! Ow, it burns! Hellfire, consuming me! You must take this, to the Hellforge and cast it in before-NOOOOOOO!"), - 1, 5, TSFX_PRIEST7 }, + 1, 5, SfxID::Tremain7 }, { N_("The evil that you move against is the Dark Lord of Terror, known to mortal man as... Diablo. It was he who was imprisoned within the labyrinth many centuries ago. Find Diablo... or we may never have a chance to rid the world of his evil again!"), - 1, 5, TSFX_STORY0 }, + 1, 5, SfxID::Cain0 }, { N_("Yes, you speak of Lazarus, one who was once well-respected in our land. Even before King Leoric descended into madness and lost his son, the Archishop Lazarus was acting... strangely. Lazarus seemed to be motivated by unseen forces, clinging to shadows, and acting out some dark plan that was not his own."), - 1, 5, TSFX_STORY3 }, + 1, 5, SfxID::Cain3 }, { N_("Worms that rise up from the ground? Many of the ancient writings speak of poisonous insects, and foul creatures of the skies and seas, plaguing mankind. It is no surprise that the very earth would offer up a similar peril, now that the denizens of the underworld are upon us."), - 1, 5, TSFX_STORY5 }, - { N_("While the barmaid may seem a bit confused at times, it is true that men have been disappearing from the village. It reminds me of ancient writings about a demonic temptress called Andariel. Known as the Maiden of Anguish, her thirst for mortal blood was so great, that she sought to learn a spell to breach the mortal realm. By seducing one of the dark sorcerers of the netherworld, Andariel took the knowledge she needed and so began to lure mortal men into her dark corner of Hell. If these legends are true, she's not a creature to be taken lightly."), - 1, 5, TSFX_STORY6 }, + 1, 5, SfxID::Cain5 }, + { N_("While the barmaid may seem a bit confused at times, it is true that men have been disappearing from the village. It reminds me of ancient writings about a demonic temptress called Andariel. Known as the Maiden of Anguish, her thirst for mortal blood was so great, that she sought to learn a spell to breach the mortal realm. By seducing one of the dark sorcerers of the netherworld, Andariel took the knowledge she needed and so began to lure mortal men into her dark corner of Hell. If these legends are true, SfxID::she's not a creature to be taken lightly."), + 1, 5, SfxID::Cain6 }, { N_("So, you seek knowledge concerning the wars of Hell, do you? Cryptic tomes speak of great battles that determined which of the demonic lords are to rule over Hell. They also mention a bitter rivalry between two of these lords - Azmodan, who led the Horned Death against the armies of Light, and Belial, known as the Lord of Lies. Their hatred of each other is eternal - the reasons for their loathing, lost even to themselves. Tales abound, that the mad wizard Horazon somehow trapped the lieutenants of these two Lords of Hell within his sanctum. There can be no more dangerous a path to tread, than the one that falls alongside of demons. Should you also seek this path, watch your life, and your soul - very carefully - my friend."), - 1, 5, TSFX_STORY8 }, + 1, 5, SfxID::Cain8 }, { N_("Farnham has taken to drinking quite heavily since his encounters in the labyrinth. But within the ramblings of this drunken man, rests a legend well steeped in myth and mystery. Sit for a moment, my friend, and let me tell you of Izual and Azurewrath. The saga of Izual takes place during the lost battles in Hell. Izual was an angel who was given charge of the holy ruin blade, Azurewrath. Leading a daring assault on the Hellforge, Izual was set upon by hundreds of blackened demons, and was fatally wounded! The fiends cast the dying angel into a dark pit, where the powers of chaos transformed him, as he drowned in a whirlpool of burning blood! Evil possessed Izual! The feathers of his wings burned away to reveal leathery skin, and horns ripped through the flesh of his head! When he finally arose from that black pit, Izual was an angel no more! Transformed into a creature of evil, he was once again given charge of Azurewrath, assuring that the blade would never again be used against the denizens of Hell!"), - 1, 5, TSFX_STORY9 }, + 1, 5, SfxID::Cain9 }, { N_("You must've been speaking with Wirt! His is a sad story indeed. That poor child was taken into the labyrinth by the demons, ripped from the very arms of his mother, Canace. May her soul rest in peace. The boy managed to escape, but only after they had chewed off his leg, and he has not been quite sane since. He often speaks in riddles, but his knowledge of the labyrinth... may hold some truth."), - 1, 5, TSFX_STORY11 }, + 1, 5, SfxID::Cain11 }, { N_("Hmm, an island, where angels watched? Although Farnham's memory is often cloudy these days, that does seem familiar. Perhaps the ancient chronicles of the Sin War can help us... Ah, yes, here is something. In a time long-forgotten, a sea of blue rested on the edge of the fiery netherworld. This was an oasis for those who acted as the watchmen over the gates of Hell. Angels and warriors of Light could use this place to heal themselves, and gather their strength. It was also a staging area where they could train and prepare for the Sin War. Legends speak of a trinity of rocks that hide the path to this island of the sunless sea."), - 1, 5, TSFX_STORY16 }, + 1, 5, SfxID::Cain16 }, { N_("The priest Tremain is a holy man from an ancient order. Their dealings with the evil forces at work are well respected, and well documented. I, too, have heard legends that speak of the cursed demonblade called Shadowfang. It is said to consume the tortured souls of its victims. These souls are trapped within its ebon blade and augment its unholy power. I have also read of a great Hellforge, where even the mightiest weapon could be created... or destroyed. Tread carefully when dealing with Shadowfang and its master, lest you be drawn into the sword as well!"), - 1, 5, TSFX_STORY17 }, + 1, 5, SfxID::Cain17 }, { N_("That sounds quite disgusting, and I'm afraid that I haven't heard anything about any worms. Perhaps Cain the Storyteller could be of some help."), - 1, 5, TSFX_TAVERN3 }, + 1, 5, SfxID::Ogden3 }, { N_("Gillian has been going on and on about strange lights in the trees, but I haven't been able to make heads nor tails of her story. I certainly haven't seen any strange lights, and if I did, they would be the least of my worries."), - 1, 5, TSFX_TAVERN4 }, + 1, 5, SfxID::Ogden4 }, { N_("These sound like dark creatures indeed. I am ignorant to matters of this nature, but I would assume that our storyteller may know of such legends."), - 1, 5, TSFX_TAVERN6 }, + 1, 5, SfxID::Ogden6 }, { N_("There is an old story of an angel named Izual, but I don't remember much more than that."), - 1, 5, TSFX_TAVERN7 }, + 1, 5, SfxID::Ogden7 }, { N_("Wirt is talking backwards again? I hate it when he does that! I don't have time to help you decipher his riddle, but I will tell you one thing - don't get involved with that rapscallion."), - 1, 5, TSFX_TAVERN9 }, + 1, 5, SfxID::Ogden9 }, { N_("You know, sometimes I wonder how much faith you can put into what Farnham says. He spends so much time reliving his memories of the labyrinth and just being plain drunk, that he doesn't always make much sense. I guess you could ask around, though."), - 1, 5, TSFX_TAVERN14 }, + 1, 5, SfxID::Ogden14 }, { N_("I saw the exorcism! It was incredible how Tremain drove the evil spirit from that man's racked and tortured body! I pray that something that horrific never happens to anyone here ever again."), - 1, 5, TSFX_TAVERN15 }, + 1, 5, SfxID::Ogden15 }, { N_("Your brave tales of fighting demons have reminded me of something, good master. A traveller passed through here some time ago. He was quite proud of a very unique weapon he held, and while becoming quite drunk, boasted of its power. I never saw him again, and I suspect that he ventured too close to the Cathedral."), - 1, 5, TSFX_TAVERN26 }, + 1, 5, SfxID::Ogden26 }, { N_("Have you ever seen that traveller I told you about? The one with the enchanted weapon? Well, I suppose that he left our village as so many others have, or he must be dead."), - 1, 5, TSFX_TAVERN27 }, + 1, 5, SfxID::Ogden27 }, { N_("Yes, that is certainly the weapon that he wore. You know, I've been thinking about something else that traveller said. He inferred that the true powers of the weapon would be unleashed only when it tasted the blood of a very specific kind of creature."), - 1, 5, TSFX_TAVERN28 }, + 1, 5, SfxID::Ogden28 }, { N_("Were the stories the traveller told us true? Oh, I see you don't know yet. Well, I clearly recall that he said by slaying a certain monster, the weapon you found would become something quite special."), - 1, 5, TSFX_TAVERN29 }, + 1, 5, SfxID::Ogden29 }, { N_("While I understand that your purpose may be of a higher calling, surely some well-earned gold would be welcomed. Some townspeople from a nearby village have offered a reward for the destruction of a creature that razed their homes and property. They demand positive proof that the creature has been slain, and I can only think of one way to do that."), - 1, 5, TSFX_TAVERN30 }, + 1, 5, SfxID::Ogden30 }, { N_("You can say whatever you want, but without proof I cannot give you the reward."), - 1, 5, TSFX_TAVERN31 }, + 1, 5, SfxID::Ogden31 }, { N_("Oh, disgusting! But certainly proof enough for anyone. Here's your reward as promised."), - 1, 5, TSFX_TAVERN32 }, + 1, 5, SfxID::Ogden32 }, { N_("Good master, a moment of your time, please? My tavern was broken into during the night. No one was hurt but many items were stolen, including a chest belonging to a mysterious wanderer who once stayed here. I found the body of a thief near the Cathedral with telltale wounds from one of the creatures below mocking his broken body. Some of the stolen goods were near the body, but the chest, and the key to open it, were nowhere in sight. If you should find those keys while on your brave crusade, you should recover them. Then, if you find the chest, you can open it and see what secrets it contains. Perhaps it will be something that can help you on your quests."), - 1, 5, TSFX_TAVERN33 }, + 1, 5, SfxID::Ogden33 }, { N_("Remember, good master, the chest is locked and is useless without the key. If you do find the chest and get the key, you should see what's inside. I don't expect the owner back here again."), - 1, 5, TSFX_TAVERN34 }, + 1, 5, SfxID::Ogden34 }, { N_("I see that you have found the key - now you just need to find the chest that it belongs to."), - 1, 5, TSFX_TAVERN35 }, + 1, 5, SfxID::Ogden35 }, { N_("Seek out the domicile to which Pepin the Healer has directed you. It is there that your missions will become clear, and your methods evident."), - 1, 5, TSFX_WITCH5 }, + 1, 5, SfxID::Adria5 }, { N_("The succubus is a demon that feasts upon the essence of mortal men. You will find that Andariel has quite an appetite. The town will soon be devoid of any adult males, unless you stop her feeding now."), - 1, 5, TSFX_WITCH6 }, + 1, 5, SfxID::Adria6 }, { N_("You must destroy the two demons that Horazon has trapped within his sanctum at all costs! Should they escape from their imprisonment, they will create a link to this world that will make what lies below the Cathedral seem like a children's tale."), - 1, 5, TSFX_WITCH8 }, + 1, 5, SfxID::Adria8 }, { N_("The lair of the Butcher is steeped in demonic power, and casting any spell or passage there could cause unexpected results."), - 1, 5, TSFX_WITCH11 }, + 1, 5, SfxID::Adria11 }, { N_("This legendary place is real and great rewards await you should you find it. Nothing more can I say."), - 1, 5, TSFX_WITCH16 }, + 1, 5, SfxID::Adria16 }, { N_("You must be ever watchful, for Fleshdoom is an enemy who is both cruel and quick. His ebon blade is composed of the essence of evil. If you can destroy both of them, it would do much to weaken his dark master."), - 1, 5, TSFX_WITCH17 }, + 1, 5, SfxID::Adria17 }, { N_("Hello, good friend, I have been working on a means to increase the strength of my potions. I believe that I could brew better potions if you could just find me the correct reagents."), - 1, 5, TSFX_WITCH27 }, + 1, 5, SfxID::Adria27 }, { N_("Remember, with the correct reagents, my potions are more powerful."), - 1, 5, TSFX_WITCH28 }, + 1, 5, SfxID::Adria28 }, { N_("Excellent! Just a pinch should do it... Ahh, here is a sample."), - 1, 5, TSFX_WITCH29 }, + 1, 5, SfxID::Adria29 }, { N_("It seems that there are many uses for the scrolls which are found beneath the church. I should be able to scribe a book of magic if I had enough of the proper scrolls to study."), - 1, 5, TSFX_WITCH30 }, + 1, 5, SfxID::Adria30 }, { N_("Remember, with enough scrolls, I can create a book of magic for you."), - 1, 5, TSFX_WITCH31 }, + 1, 5, SfxID::Adria31 }, { N_("Yes, I can see that when I combine these scrolls just so... I will teach what I have learned so that you may also know more secrets of this magic."), - 1, 5, TSFX_WITCH32 }, + 1, 5, SfxID::Adria32 }, { N_("I have a quest for you if you are brave enough to accept it. Great power can be found in some of the devices that may be hidden below the church. With the right ones, I could work wonders!"), - 1, 5, TSFX_WITCH33 }, + 1, 5, SfxID::Adria33 }, { N_("Remember, the right device in my hands can become quite powerful."), - 1, 5, TSFX_WITCH34 }, + 1, 5, SfxID::Adria34 }, { N_("This is amazing! The mystic energies in this crystal are powerful, but unfocused. If you could find for me a specific staff to channel its power through..."), - 1, 5, TSFX_WITCH35 }, + 1, 5, SfxID::Adria35 }, { N_("The crystal holds a magic that is strong, but you need to find the correct staff with which to focus its energy!"), - 1, 5, TSFX_WITCH36 }, + 1, 5, SfxID::Adria36 }, { N_("Hand me that staff and stand back! Vita te chlorum an duriam! Ahhhh, it is complete."), - 1, 5, TSFX_WITCH37 }, + 1, 5, SfxID::Adria37 }, { N_("Faith is absolute belief in the unseen. The priest Tremain is from a holy order long asleep in this land. He keeps a promise and a charge issued ages ago and sustains a union with realms that even my vision cannot reach. He knows much, but not as much as he believes."), - 1, 5, TSFX_WITCH48 }, + 1, 5, SfxID::Adria48 }, */ }; } // namespace devilution diff --git a/Source/textdat.h b/Source/textdat.h index a3faf9ad08f..5b90360af52 100644 --- a/Source/textdat.h +++ b/Source/textdat.h @@ -427,7 +427,7 @@ enum _speech_id : int16_t { struct Speech { const char *txtstr; bool scrlltxt; - _sfx_id sfxnr; + SfxID sfxnr; }; extern const Speech Speeches[]; diff --git a/Source/tmsg.cpp b/Source/tmsg.cpp index d83b4f24892..48a77e99d56 100644 --- a/Source/tmsg.cpp +++ b/Source/tmsg.cpp @@ -16,12 +16,12 @@ namespace { struct TMsg { uint32_t time; - std::unique_ptr body; + std::unique_ptr body; uint8_t len; - TMsg(uint32_t time, const byte *data, uint8_t len) + TMsg(uint32_t time, const std::byte *data, uint8_t len) : time(time) - , body(new byte[len]) + , body(new std::byte[len]) , len(len) { memcpy(body.get(), data, len); @@ -32,7 +32,7 @@ std::list TimedMsgList; } // namespace -uint8_t tmsg_get(std::unique_ptr *msg) +uint8_t tmsg_get(std::unique_ptr *msg) { if (TimedMsgList.empty()) return 0; @@ -47,7 +47,7 @@ uint8_t tmsg_get(std::unique_ptr *msg) return len; } -void tmsg_add(const byte *msg, uint8_t len) +void tmsg_add(const std::byte *msg, uint8_t len) { uint32_t time = SDL_GetTicks() + gnTickDelay * 10; TimedMsgList.emplace_back(time, msg, len); diff --git a/Source/tmsg.h b/Source/tmsg.h index 597888696c8..6692186a6cd 100644 --- a/Source/tmsg.h +++ b/Source/tmsg.h @@ -5,15 +5,14 @@ */ #pragma once +#include #include #include -#include "utils/stdcompat/cstddef.hpp" - namespace devilution { -uint8_t tmsg_get(std::unique_ptr *msg); -void tmsg_add(const byte *msg, uint8_t bLen); +uint8_t tmsg_get(std::unique_ptr *msg); +void tmsg_add(const std::byte *msg, uint8_t bLen); void tmsg_start(); void tmsg_cleanup(); diff --git a/Source/towners.cpp b/Source/towners.cpp index 37cd6779abc..dee785323f0 100644 --- a/Source/towners.cpp +++ b/Source/towners.cpp @@ -21,7 +21,7 @@ int CowMsg; int CowClicks; /** Specifies the active sound effect ID for interacting with cows. */ -_sfx_id CowPlaying = SFX_NONE; +SfxID CowPlaying = SfxID::None; struct TownerData { _talker_id type; @@ -40,7 +40,7 @@ void NewTownerAnim(Towner &towner, ClxSpriteList sprites, uint8_t numFrames, int towner._tAnimDelay = delay; } -void InitTownerInfo(int i, const TownerData &townerData) +void InitTownerInfo(int16_t i, const TownerData &townerData) { auto &towner = Towners[i]; @@ -77,8 +77,7 @@ void InitSmith(Towner &towner, const TownerData &townerData) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3 // clang-format on }; - towner.animOrder = AnimOrder; - towner.animOrderSize = sizeof(AnimOrder); + towner.animOrder = { AnimOrder }; LoadTownerAnimations(towner, "towners\\smith\\smithn", 16, 3); towner.name = _("Griswold the Blacksmith"); towner.gossip = PickRandomlyAmong({ TEXT_GRISWOLD2, TEXT_GRISWOLD3, TEXT_GRISWOLD4, TEXT_GRISWOLD5, TEXT_GRISWOLD6, TEXT_GRISWOLD7, TEXT_GRISWOLD8, TEXT_GRISWOLD9, TEXT_GRISWOLD10, TEXT_GRISWOLD12, TEXT_GRISWOLD13 }); @@ -100,8 +99,7 @@ void InitBarOwner(Towner &towner, const TownerData &townerData) 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 // clang-format on }; - towner.animOrder = AnimOrder; - towner.animOrderSize = sizeof(AnimOrder); + towner.animOrder = { AnimOrder }; LoadTownerAnimations(towner, "towners\\twnf\\twnfn", 16, 3); towner.name = _("Ogden the Tavern owner"); towner.gossip = PickRandomlyAmong({ TEXT_OGDEN2, TEXT_OGDEN3, TEXT_OGDEN4, TEXT_OGDEN5, TEXT_OGDEN6, TEXT_OGDEN8, TEXT_OGDEN9, TEXT_OGDEN10 }); @@ -110,8 +108,7 @@ void InitBarOwner(Towner &towner, const TownerData &townerData) void InitTownDead(Towner &towner, const TownerData &townerData) { towner._tAnimWidth = 96; - towner.animOrder = nullptr; - towner.animOrderSize = 0; + towner.animOrder = {}; LoadTownerAnimations(towner, "towners\\butch\\deadguy", 8, 6); towner.name = _("Wounded Townsman"); } @@ -132,8 +129,7 @@ void InitWitch(Towner &towner, const TownerData &townerData) 0, 1, 0, 18, 17, 18, 0, 1, 0, 1, 2 // clang-format on }; - towner.animOrder = AnimOrder; - towner.animOrderSize = sizeof(AnimOrder); + towner.animOrder = { AnimOrder }; LoadTownerAnimations(towner, "towners\\townwmn1\\witch", 19, 6); towner.name = _("Adria the Witch"); towner.gossip = PickRandomlyAmong({ TEXT_ADRIA2, TEXT_ADRIA3, TEXT_ADRIA4, TEXT_ADRIA5, TEXT_ADRIA6, TEXT_ADRIA7, TEXT_ADRIA8, TEXT_ADRIA9, TEXT_ADRIA10, TEXT_ADRIA12, TEXT_ADRIA13 }); @@ -142,8 +138,7 @@ void InitWitch(Towner &towner, const TownerData &townerData) void InitBarmaid(Towner &towner, const TownerData &townerData) { towner._tAnimWidth = 96; - towner.animOrder = nullptr; - towner.animOrderSize = 0; + towner.animOrder = {}; LoadTownerAnimations(towner, "towners\\townwmn1\\wmnn", 18, 6); towner.name = _("Gillian the Barmaid"); towner.gossip = PickRandomlyAmong({ TEXT_GILLIAN2, TEXT_GILLIAN3, TEXT_GILLIAN4, TEXT_GILLIAN5, TEXT_GILLIAN6, TEXT_GILLIAN7, TEXT_GILLIAN9, TEXT_GILLIAN10 }); @@ -152,8 +147,7 @@ void InitBarmaid(Towner &towner, const TownerData &townerData) void InitBoy(Towner &towner, const TownerData &townerData) { towner._tAnimWidth = 96; - towner.animOrder = nullptr; - towner.animOrderSize = 0; + towner.animOrder = {}; LoadTownerAnimations(towner, "towners\\townboy\\pegkid1", 20, 6); towner.name = _("Wirt the Peg-legged boy"); towner.gossip = PickRandomlyAmong({ TEXT_WIRT2, TEXT_WIRT3, TEXT_WIRT4, TEXT_WIRT5, TEXT_WIRT6, TEXT_WIRT7, TEXT_WIRT8, TEXT_WIRT9, TEXT_WIRT11, TEXT_WIRT12 }); @@ -175,8 +169,7 @@ void InitHealer(Towner &towner, const TownerData &townerData) 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 // clang-format on }; - towner.animOrder = AnimOrder; - towner.animOrderSize = sizeof(AnimOrder); + towner.animOrder = { AnimOrder }; LoadTownerAnimations(towner, "towners\\healer\\healer", 20, 6); towner.name = _("Pepin the Healer"); towner.gossip = PickRandomlyAmong({ TEXT_PEPIN2, TEXT_PEPIN3, TEXT_PEPIN4, TEXT_PEPIN5, TEXT_PEPIN6, TEXT_PEPIN7, TEXT_PEPIN9, TEXT_PEPIN10, TEXT_PEPIN11 }); @@ -193,8 +186,7 @@ void InitTeller(Towner &towner, const TownerData &townerData) 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 // clang-format on }; - towner.animOrder = AnimOrder; - towner.animOrderSize = sizeof(AnimOrder); + towner.animOrder = { AnimOrder }; LoadTownerAnimations(towner, "towners\\strytell\\strytell", 25, 3); towner.name = _("Cain the Elder"); towner.gossip = PickRandomlyAmong({ TEXT_STORY2, TEXT_STORY3, TEXT_STORY4, TEXT_STORY5, TEXT_STORY6, TEXT_STORY7, TEXT_STORY9, TEXT_STORY10, TEXT_STORY11 }); @@ -210,8 +202,7 @@ void InitDrunk(Towner &towner, const TownerData &townerData) 0, 1, 2, 3, 4, 4, 4, 3, 2, 1 // clang-format on }; - towner.animOrder = AnimOrder; - towner.animOrderSize = sizeof(AnimOrder); + towner.animOrder = { AnimOrder }; LoadTownerAnimations(towner, "towners\\drunk\\twndrunk", 18, 3); towner.name = _("Farnham the Drunk"); towner.gossip = PickRandomlyAmong({ TEXT_FARNHAM2, TEXT_FARNHAM3, TEXT_FARNHAM4, TEXT_FARNHAM5, TEXT_FARNHAM6, TEXT_FARNHAM8, TEXT_FARNHAM9, TEXT_FARNHAM10, TEXT_FARNHAM11, TEXT_FARNHAM12, TEXT_FARNHAM13 }); @@ -220,15 +211,14 @@ void InitDrunk(Towner &towner, const TownerData &townerData) void InitCows(Towner &towner, const TownerData &townerData) { towner._tAnimWidth = 128; - towner.animOrder = nullptr; - towner.animOrderSize = 0; + towner.animOrder = {}; NewTownerAnim(towner, (*CowSprites)[static_cast(townerData.dir)], 12, 3); towner._tAnimFrame = GenerateRnd(11); towner.name = _("Cow"); const Point position = townerData.position; - int cowId = dMonster[position.x][position.y]; + int16_t cowId = dMonster[position.x][position.y]; // Cows are large sprites so take up multiple tiles. Vanilla Diablo/Hellfire allowed the player to stand adjacent // to a cow facing an ordinal direction (the two top-right cows) which leads to visual clipping. It's easier to @@ -246,8 +236,7 @@ void InitCows(Towner &towner, const TownerData &townerData) void InitFarmer(Towner &towner, const TownerData &townerData) { towner._tAnimWidth = 96; - towner.animOrder = nullptr; - towner.animOrderSize = 0; + towner.animOrder = {}; LoadTownerAnimations(towner, "towners\\farmer\\farmrn2", 15, 3); towner.name = _("Lester the farmer"); } @@ -259,8 +248,7 @@ void InitCowFarmer(Towner &towner, const TownerData &townerData) celPath = "towners\\farmer\\mfrmrn2"; } towner._tAnimWidth = 96; - towner.animOrder = nullptr; - towner.animOrderSize = 0; + towner.animOrder = {}; LoadTownerAnimations(towner, celPath, 15, 3); towner.name = _("Complete Nut"); } @@ -268,8 +256,7 @@ void InitCowFarmer(Towner &towner, const TownerData &townerData) void InitGirl(Towner &towner, const TownerData &townerData) { towner._tAnimWidth = 96; - towner.animOrder = nullptr; - towner.animOrderSize = 0; + towner.animOrder = {}; LoadTownerAnimations(towner, "towners\\girl\\girlw1", 20, 6); towner.name = _("Celia"); } @@ -577,17 +564,17 @@ void TalkToStoryteller(Player &player, Towner & /*storyteller*/) void TalkToCow(Player &player, Towner &cow) { - if (CowPlaying != SFX_NONE && effect_is_playing(CowPlaying)) + if (CowPlaying != SfxID::None && effect_is_playing(CowPlaying)) return; CowClicks++; - CowPlaying = TSFX_COW1; + CowPlaying = SfxID::Cow1; if (CowClicks == 4) { if (gbIsSpawn) CowClicks = 0; - CowPlaying = TSFX_COW2; + CowPlaying = SfxID::Cow2; } else if (CowClicks >= 8 && !gbIsSpawn) { CowClicks = 4; @@ -622,7 +609,7 @@ void TalkToFarmer(Player &player, Towner &farmer) break; } - if (!player._pLvlVisited[9] && player._pLevel < 15) { + if (!player._pLvlVisited[9] && player.getCharacterLevel() < 15) { _speech_id qt = TEXT_FARMER8; if (player._pLvlVisited[2]) qt = TEXT_FARMER5; @@ -713,20 +700,8 @@ void TalkToCowFarmer(Player &player, Towner &cowFarmer) NetSendCmdQuest(true, quest); break; case QUEST_HIVE_ACTIVE: - if (!player._pLvlVisited[9] && player._pLevel < 15) { - _speech_id qt = TEXT_JERSEY12; - switch (GenerateRnd(4)) { - case 0: - qt = TEXT_JERSEY9; - break; - case 1: - qt = TEXT_JERSEY10; - break; - case 2: - qt = TEXT_JERSEY11; - break; - } - InitQTextMsg(qt); + if (!player._pLvlVisited[9] && player.getCharacterLevel() < 15) { + InitQTextMsg(PickRandomlyAmong({ TEXT_JERSEY9, TEXT_JERSEY10, TEXT_JERSEY11, TEXT_JERSEY12 })); break; } @@ -854,7 +829,7 @@ void InitTowners() CowSprites.emplace(LoadCelSheet("towners\\animals\\cow", 128)); - int i = 0; + int16_t i = 0; for (const auto &townerData : TownersData) { if (!IsTownerPresent(townerData.type)) continue; @@ -887,9 +862,9 @@ void ProcessTowners() towner._tAnimCnt = 0; - if (towner.animOrderSize > 0) { + if (!towner.animOrder.empty()) { towner._tAnimFrameCnt++; - if (towner._tAnimFrameCnt > towner.animOrderSize - 1) + if (towner._tAnimFrameCnt > towner.animOrder.size() - 1) towner._tAnimFrameCnt = 0; towner._tAnimFrame = towner.animOrder[towner._tAnimFrameCnt]; @@ -935,12 +910,12 @@ void UpdateCowFarmerAnimAfterQuestComplete() } #ifdef _DEBUG -bool DebugTalkToTowner(std::string targetName) +bool DebugTalkToTowner(std::string_view targetName) { SetupTownStores(); - AsciiStrToLower(targetName); + const std::string lowercaseName = AsciiStrToLower(targetName); Player &myPlayer = *MyPlayer; - for (auto &townerData : TownersData) { + for (const TownerData &townerData : TownersData) { if (!IsTownerPresent(townerData.type)) continue; // cows have an init function that differs from the rest and isn't compatible with this code, skip them :( @@ -950,7 +925,7 @@ bool DebugTalkToTowner(std::string targetName) townerData.init(fakeTowner, townerData); fakeTowner.position = myPlayer.position.tile; const std::string npcName = AsciiStrToLower(fakeTowner.name); - if (npcName.find(targetName) != std::string::npos) { + if (npcName.find(lowercaseName) != std::string::npos) { townerData.talk(myPlayer, fakeTowner); return true; } diff --git a/Source/towners.h b/Source/towners.h index 0a3c1b5e007..1788f53a155 100644 --- a/Source/towners.h +++ b/Source/towners.h @@ -5,14 +5,15 @@ */ #pragma once -#include "utils/stdcompat/string_view.hpp" +#include #include #include +#include +#include #include "items.h" #include "player.h" #include "quests.h" -#include "utils/stdcompat/cstddef.hpp" namespace devilution { @@ -39,10 +40,10 @@ struct Towner { OptionalOwnedClxSpriteList ownedAnim; OptionalClxSpriteList anim; /** Specifies the animation frame sequence. */ - const uint8_t *animOrder; // unowned + std::span animOrder; void (*talk)(Player &player, Towner &towner); - string_view name; + std::string_view name; /** Tile position of NPC */ Point position; @@ -58,13 +59,16 @@ struct Towner { /** Current frame of animation. */ uint8_t _tAnimFrame; uint8_t _tAnimFrameCnt; - uint8_t animOrderSize; _talker_id _ttype; - ClxSprite currentSprite() const + [[nodiscard]] ClxSprite currentSprite() const { return (*anim)[_tAnimFrame]; } + [[nodiscard]] Displacement getRenderingOffset() const + { + return { -CalculateWidth2(_tAnimWidth), 0 }; + } }; extern Towner Towners[NUM_TOWNERS]; @@ -84,7 +88,7 @@ void UpdateGirlAnimAfterQuestComplete(); void UpdateCowFarmerAnimAfterQuestComplete(); #ifdef _DEBUG -bool DebugTalkToTowner(std::string targetName); +bool DebugTalkToTowner(std::string_view targetName); #endif extern _speech_id QuestDialogTable[NUM_TOWNER_TYPES][MAXQUESTS]; diff --git a/Source/track.cpp b/Source/track.cpp index c7198ce9ba2..f139d577fb8 100644 --- a/Source/track.cpp +++ b/Source/track.cpp @@ -49,12 +49,12 @@ void InvalidateTargets() if (ObjectUnderCursor != nullptr && ObjectUnderCursor->_oSelFlag < 1) ObjectUnderCursor = nullptr; - if (pcursplr != -1) { - Player &targetPlayer = Players[pcursplr]; + if (PlayerUnderCursor != nullptr) { + const Player &targetPlayer = *PlayerUnderCursor; if (targetPlayer._pmode == PM_DEATH || targetPlayer._pmode == PM_QUIT || !targetPlayer.plractive || !targetPlayer.isOnActiveLevel() || targetPlayer._pHitPoints >> 6 <= 0 || !IsTileLit(targetPlayer.position.tile)) - pcursplr = -1; + PlayerUnderCursor = nullptr; } } @@ -91,8 +91,8 @@ void RepeatMouseAction() NetSendCmdParam1(true, rangedAttack ? CMD_RATTACKID : CMD_ATTACKID, pcursmonst); break; case MouseActionType::AttackPlayerTarget: - if (pcursplr != -1 && !myPlayer.friendlyMode) - NetSendCmdParam1(true, rangedAttack ? CMD_RATTACKPID : CMD_ATTACKPID, pcursplr); + if (PlayerUnderCursor != nullptr && !myPlayer.friendlyMode) + NetSendCmdParam1(true, rangedAttack ? CMD_RATTACKPID : CMD_ATTACKPID, PlayerUnderCursor->getId()); break; case MouseActionType::Spell: if (ControlMode != ControlTypes::KeyboardAndMouse) { @@ -105,7 +105,7 @@ void RepeatMouseAction() CheckPlrSpell(false); break; case MouseActionType::SpellPlayerTarget: - if (pcursplr != -1 && !myPlayer.friendlyMode) + if (PlayerUnderCursor != nullptr && !myPlayer.friendlyMode) CheckPlrSpell(false); break; case MouseActionType::OperateObject: diff --git a/Source/translation_dummy.cpp b/Source/translation_dummy.cpp new file mode 100644 index 00000000000..cbfdbcd64a2 --- /dev/null +++ b/Source/translation_dummy.cpp @@ -0,0 +1,834 @@ +/** + * @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" + +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_RFALLSD_NAME = P_("monster", "Fallen One"); +const char *MT_DFALLSD_NAME = P_("monster", "Carver"); +const char *MT_YFALLSD_NAME = P_("monster", "Devil Kin"); +const char *MT_BFALLSD_NAME = P_("monster", "Dark One"); +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_WSKELBW_NAME = P_("monster", "Skeleton"); +const char *MT_TSKELBW_NAME = P_("monster", "Corpse Bow"); +const char *MT_RSKELBW_NAME = P_("monster", "Burning Dead"); +const char *MT_XSKELBW_NAME = P_("monster", "Horror"); +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_NGOATBW_NAME = P_("monster", "Flesh Clan"); +const char *MT_BGOATBW_NAME = P_("monster", "Stone Clan"); +const char *MT_RGOATBW_NAME = P_("monster", "Fire Clan"); +const char *MT_GGOATBW_NAME = P_("monster", "Night Clan"); +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 *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_HORKDMN_NAME = P_("monster", "Hork Demon"); +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 *MT_NAKRUL_NAME = P_("monster", "Na-Krul"); +const char *GHARBAD_THE_WEAK_NAME = P_("monster", "Gharbad the Weak"); +const char *SKELETON_KING_NAME = P_("monster", "Skeleton King"); +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 *THE_BUTCHER_NAME = P_("monster", "The Butcher"); +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_("Heart"); +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_48_SHORT_NAME = N_("Cap"); +const char *ITEM_49_NAME = N_("Skull Cap"); +const char *ITEM_49_SHORT_NAME = N_("Cap"); +const char *ITEM_50_NAME = N_("Helm"); +const char *ITEM_50_SHORT_NAME = N_("Helm"); +const char *ITEM_51_NAME = N_("Full Helm"); +const char *ITEM_51_SHORT_NAME = N_("Helm"); +const char *ITEM_52_NAME = N_("Crown"); +const char *ITEM_52_SHORT_NAME = N_("Crown"); +const char *ITEM_53_NAME = N_("Great Helm"); +const char *ITEM_53_SHORT_NAME = N_("Helm"); +const char *ITEM_54_NAME = N_("Cape"); +const char *ITEM_54_SHORT_NAME = N_("Cape"); +const char *ITEM_55_NAME = N_("Rags"); +const char *ITEM_55_SHORT_NAME = N_("Rags"); +const char *ITEM_56_NAME = N_("Cloak"); +const char *ITEM_56_SHORT_NAME = N_("Cloak"); +const char *ITEM_57_NAME = N_("Robe"); +const char *ITEM_57_SHORT_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_59_SHORT_NAME = N_("Armor"); +const char *ITEM_60_NAME = N_("Hard Leather Armor"); +const char *ITEM_60_SHORT_NAME = N_("Armor"); +const char *ITEM_61_NAME = N_("Studded Leather Armor"); +const char *ITEM_61_SHORT_NAME = N_("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_63_SHORT_NAME = N_("Mail"); +const char *ITEM_64_NAME = N_("Scale Mail"); +const char *ITEM_64_SHORT_NAME = N_("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_66_SHORT_NAME = N_("Mail"); +const char *ITEM_67_NAME = N_("Plate Mail"); +const char *ITEM_67_SHORT_NAME = N_("Plate"); +const char *ITEM_68_NAME = N_("Field Plate"); +const char *ITEM_68_SHORT_NAME = N_("Plate"); +const char *ITEM_69_NAME = N_("Gothic Plate"); +const char *ITEM_69_SHORT_NAME = N_("Plate"); +const char *ITEM_70_NAME = N_("Full Plate Mail"); +const char *ITEM_70_SHORT_NAME = N_("Plate"); +const char *ITEM_71_NAME = N_("Buckler"); +const char *ITEM_71_SHORT_NAME = N_("Shield"); +const char *ITEM_72_NAME = N_("Small Shield"); +const char *ITEM_72_SHORT_NAME = N_("Shield"); +const char *ITEM_73_NAME = N_("Large Shield"); +const char *ITEM_73_SHORT_NAME = N_("Shield"); +const char *ITEM_74_NAME = N_("Kite Shield"); +const char *ITEM_74_SHORT_NAME = N_("Shield"); +const char *ITEM_75_NAME = N_("Tower Shield"); +const char *ITEM_75_SHORT_NAME = N_("Shield"); +const char *ITEM_76_NAME = N_("Gothic Shield"); +const char *ITEM_76_SHORT_NAME = N_("Shield"); +const char *ITEM_77_NAME = N_("Potion of Healing"); +const char *ITEM_78_NAME = N_("Potion of Full Healing"); +const char *ITEM_79_NAME = N_("Potion of Mana"); +const char *ITEM_80_NAME = N_("Potion of Full Mana"); +const char *ITEM_81_NAME = N_("Potion of Rejuvenation"); +const char *ITEM_82_NAME = N_("Potion of Full Rejuvenation"); +const char *ITEM_83_NAME = N_("Blacksmith Oil"); +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_94_NAME = N_("Scroll of Identify"); +const char *ITEM_95_NAME = N_("Scroll of Resurrect"); +const char *ITEM_96_NAME = N_("Scroll of Fire Wall"); +const char *ITEM_97_NAME = N_("Scroll of Inferno"); +const char *ITEM_98_NAME = N_("Scroll of Town Portal"); +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_114_NAME = N_("Book of "); +const char *ITEM_115_NAME = N_("Book of "); +const char *ITEM_116_NAME = N_("Book of "); +const char *ITEM_117_NAME = N_("Book of "); +const char *ITEM_118_NAME = N_("Dagger"); +const char *ITEM_118_SHORT_NAME = N_("Dagger"); +const char *ITEM_119_NAME = N_("Short Sword"); +const char *ITEM_119_SHORT_NAME = N_("Sword"); +const char *ITEM_120_NAME = N_("Falchion"); +const char *ITEM_120_SHORT_NAME = N_("Sword"); +const char *ITEM_121_NAME = N_("Scimitar"); +const char *ITEM_121_SHORT_NAME = N_("Sword"); +const char *ITEM_122_NAME = N_("Claymore"); +const char *ITEM_122_SHORT_NAME = N_("Sword"); +const char *ITEM_123_NAME = N_("Blade"); +const char *ITEM_123_SHORT_NAME = N_("Blade"); +const char *ITEM_124_NAME = N_("Sabre"); +const char *ITEM_124_SHORT_NAME = N_("Sabre"); +const char *ITEM_125_NAME = N_("Long Sword"); +const char *ITEM_125_SHORT_NAME = N_("Sword"); +const char *ITEM_126_NAME = N_("Broad Sword"); +const char *ITEM_126_SHORT_NAME = N_("Sword"); +const char *ITEM_127_NAME = N_("Bastard Sword"); +const char *ITEM_127_SHORT_NAME = N_("Sword"); +const char *ITEM_128_NAME = N_("Two-Handed Sword"); +const char *ITEM_128_SHORT_NAME = N_("Sword"); +const char *ITEM_129_NAME = N_("Great Sword"); +const char *ITEM_129_SHORT_NAME = N_("Sword"); +const char *ITEM_130_NAME = N_("Small Axe"); +const char *ITEM_130_SHORT_NAME = N_("Axe"); +const char *ITEM_131_NAME = N_("Axe"); +const char *ITEM_131_SHORT_NAME = N_("Axe"); +const char *ITEM_132_NAME = N_("Large Axe"); +const char *ITEM_132_SHORT_NAME = N_("Axe"); +const char *ITEM_133_NAME = N_("Broad Axe"); +const char *ITEM_133_SHORT_NAME = N_("Axe"); +const char *ITEM_134_NAME = N_("Battle Axe"); +const char *ITEM_134_SHORT_NAME = N_("Axe"); +const char *ITEM_135_NAME = N_("Great Axe"); +const char *ITEM_135_SHORT_NAME = N_("Axe"); +const char *ITEM_136_NAME = N_("Mace"); +const char *ITEM_136_SHORT_NAME = N_("Mace"); +const char *ITEM_137_NAME = N_("Morning Star"); +const char *ITEM_137_SHORT_NAME = N_("Mace"); +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_139_SHORT_NAME = N_("Club"); +const char *ITEM_140_NAME = N_("Club"); +const char *ITEM_140_SHORT_NAME = N_("Club"); +const char *ITEM_141_NAME = N_("Flail"); +const char *ITEM_141_SHORT_NAME = N_("Flail"); +const char *ITEM_142_NAME = N_("Maul"); +const char *ITEM_142_SHORT_NAME = N_("Maul"); +const char *ITEM_143_NAME = N_("Short Bow"); +const char *ITEM_143_SHORT_NAME = N_("Bow"); +const char *ITEM_144_NAME = N_("Hunter's Bow"); +const char *ITEM_144_SHORT_NAME = N_("Bow"); +const char *ITEM_145_NAME = N_("Long Bow"); +const char *ITEM_145_SHORT_NAME = N_("Bow"); +const char *ITEM_146_NAME = N_("Composite Bow"); +const char *ITEM_146_SHORT_NAME = N_("Bow"); +const char *ITEM_147_NAME = N_("Short Battle Bow"); +const char *ITEM_147_SHORT_NAME = N_("Bow"); +const char *ITEM_148_NAME = N_("Long Battle Bow"); +const char *ITEM_148_SHORT_NAME = N_("Bow"); +const char *ITEM_149_NAME = N_("Short War Bow"); +const char *ITEM_149_SHORT_NAME = N_("Bow"); +const char *ITEM_150_NAME = N_("Long War Bow"); +const char *ITEM_150_SHORT_NAME = N_("Bow"); +const char *ITEM_151_NAME = N_("Short Staff"); +const char *ITEM_151_SHORT_NAME = N_("Staff"); +const char *ITEM_152_NAME = N_("Long Staff"); +const char *ITEM_152_SHORT_NAME = N_("Staff"); +const char *ITEM_153_NAME = N_("Composite Staff"); +const char *ITEM_153_SHORT_NAME = N_("Staff"); +const char *ITEM_154_NAME = N_("Quarter Staff"); +const char *ITEM_154_SHORT_NAME = N_("Staff"); +const char *ITEM_155_NAME = N_("War Staff"); +const char *ITEM_155_SHORT_NAME = N_("Staff"); +const char *ITEM_156_NAME = N_("Ring"); +const char *ITEM_156_SHORT_NAME = N_("Ring"); +const char *ITEM_157_NAME = N_("Ring"); +const char *ITEM_157_SHORT_NAME = N_("Ring"); +const char *ITEM_158_NAME = N_("Ring"); +const char *ITEM_158_SHORT_NAME = N_("Ring"); +const char *ITEM_159_NAME = N_("Amulet"); +const char *ITEM_159_SHORT_NAME = N_("Amulet"); +const char *ITEM_160_NAME = N_("Amulet"); +const char *ITEM_160_SHORT_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_162_SHORT_NAME = N_("Rune"); +const char *ITEM_163_NAME = N_("Greater Rune of Fire"); +const char *ITEM_163_SHORT_NAME = N_("Rune"); +const char *ITEM_164_NAME = N_("Greater Rune of Lightning"); +const char *ITEM_164_SHORT_NAME = N_("Rune"); +const char *ITEM_165_NAME = N_("Rune of Stone"); +const char *ITEM_165_SHORT_NAME = N_("Rune"); +const char *IDI_SORCERER_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_1_NAME = N_("The Undead Crown"); +const char *UNIQUE_ITEM_2_NAME = N_("Empyrean Band"); +const char *UNIQUE_ITEM_3_NAME = N_("Optic Amulet"); +const char *UNIQUE_ITEM_4_NAME = N_("Ring of Truth"); +const char *UNIQUE_ITEM_5_NAME = N_("Harlequin Crest"); +const char *UNIQUE_ITEM_6_NAME = N_("Veil of Steel"); +const char *UNIQUE_ITEM_7_NAME = N_("Arkaine's Valor"); +const char *UNIQUE_ITEM_8_NAME = N_("Griswold's Edge"); +const char *UNIQUE_ITEM_9_NAME = N_("Bovine Plate"); +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 *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_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_6_NAME = N_("Gold"); +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_37_NAME = N_("Fine"); +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_49_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_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_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 *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_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 *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"); diff --git a/Source/utils/algorithm/container.hpp b/Source/utils/algorithm/container.hpp new file mode 100644 index 00000000000..9d9212e98d7 --- /dev/null +++ b/Source/utils/algorithm/container.hpp @@ -0,0 +1,118 @@ +#pragma once + +#include +#include +#include +#include + +namespace devilution { + +// Internal namespace that sets up ADL lookup and the container Iterator type. +namespace container_internal { +using std::begin; +using std::end; + +template +using Iterator = decltype(begin(std::declval())); + +template +using Difference = typename std::iterator_traits>::difference_type; + +template +Iterator c_begin(C &c) +{ + return begin(c); +} + +template +Iterator c_end(C &c) +{ + return end(c); +} + +} // namespace container_internal + +template +bool c_any_of(const C &c, Predicate &&predicate) +{ + return std::any_of(container_internal::begin(c), + container_internal::end(c), + std::forward(predicate)); +} + +template +bool c_all_of(const C &c, Predicate &&predicate) +{ + return std::all_of(container_internal::begin(c), + container_internal::end(c), + std::forward(predicate)); +} + +template +bool c_none_of(const C &c, Predicate &&predicate) +{ + return std::none_of(container_internal::begin(c), + container_internal::end(c), + std::forward(predicate)); +} + +template +container_internal::Iterator +c_find(C &c, T &&value) +{ + return std::find(container_internal::begin(c), + container_internal::end(c), + std::forward(value)); +} + +template +container_internal::Iterator +c_find_if(C &c, Predicate &&predicate) +{ + return std::find_if(container_internal::begin(c), + container_internal::end(c), + std::forward(predicate)); +} + +template +container_internal::Difference +c_count_if(const C &c, Predicate &&predicate) +{ + return std::count_if(container_internal::c_begin(c), + container_internal::c_end(c), + std::forward(predicate)); +} + +template +container_internal::Difference +c_count(const C &c, T &&value) +{ + return std::count(container_internal::c_begin(c), + container_internal::c_end(c), + std::forward(value)); +} + +template +void c_sort(C &c) +{ + std::sort(container_internal::c_begin(c), + container_internal::c_end(c)); +} + +template +void c_sort(C &c, LessThan &&comp) +{ + std::sort(container_internal::c_begin(c), + container_internal::c_end(c), + std::forward(comp)); +} + +template +container_internal::Iterator c_lower_bound(C &c, T &&value) +{ + return std::lower_bound(container_internal::c_begin(c), + container_internal::c_end(c), + std::forward(value)); +} + +} // namespace devilution diff --git a/Source/utils/cel_to_clx.cpp b/Source/utils/cel_to_clx.cpp index 3217a25a9f5..76fdcc3aaf5 100644 --- a/Source/utils/cel_to_clx.cpp +++ b/Source/utils/cel_to_clx.cpp @@ -62,7 +62,7 @@ OwnedClxSpriteListOrSheet CelToClx(const uint8_t *data, size_t size, PointerOrVa numFrames = maybeNumFrames; } else { numFrames = LoadLE32(data); - WriteLE32(&cl2Data[4 * group], cl2Data.size()); + WriteLE32(&cl2Data[4 * group], static_cast(cl2Data.size())); } // CL2 header: frame count, frame offset for each frame, file size @@ -110,7 +110,7 @@ OwnedClxSpriteListOrSheet CelToClx(const uint8_t *data, size_t size, PointerOrVa } ++frameHeight; } - WriteLE16(&cl2Data[frameHeaderPos + 4], frameHeight); + WriteLE16(&cl2Data[frameHeaderPos + 4], static_cast(frameHeight)); memset(&cl2Data[frameHeaderPos + 6], 0, 4); AppendClxTransparentRun(transparentRunWidth, cl2Data); } diff --git a/Source/utils/cl2_to_clx.cpp b/Source/utils/cl2_to_clx.cpp index a7fdb934523..4c820450291 100644 --- a/Source/utils/cl2_to_clx.cpp +++ b/Source/utils/cl2_to_clx.cpp @@ -55,7 +55,7 @@ uint16_t Cl2ToClx(const uint8_t *data, size_t size, } else { groupBegin = &data[LoadLE32(&data[group * 4])]; numFrames = LoadLE32(groupBegin); - WriteLE32(&clxData[4 * group], clxData.size()); + WriteLE32(&clxData[4 * group], static_cast(clxData.size())); } // CLX header: frame count, frame offset for each frame, file size @@ -126,7 +126,7 @@ uint16_t Cl2ToClx(const uint8_t *data, size_t size, } AppendClxTransparentRun(transparentRunWidth, clxData); - WriteLE16(&clxData[frameHeaderPos + 4], frameHeight); + WriteLE16(&clxData[frameHeaderPos + 4], static_cast(frameHeight)); memset(&clxData[frameHeaderPos + 6], 0, 4); } diff --git a/Source/utils/clx_encode.hpp b/Source/utils/clx_encode.hpp index 5b23f9463aa..337e6b9c726 100644 --- a/Source/utils/clx_encode.hpp +++ b/Source/utils/clx_encode.hpp @@ -46,7 +46,7 @@ inline void AppendClxPixelsRun(const uint8_t *src, unsigned width, std::vector &out) +inline void AppendClxPixelsOrFillRun(const uint8_t *src, size_t length, std::vector &out) { const uint8_t *begin = src; const uint8_t *prevColorBegin = src; @@ -61,7 +61,7 @@ inline void AppendClxPixelsOrFillRun(const uint8_t *src, unsigned length, std::v // 3 appears to be optimal for most of our data (much better than 2, rarely very slightly worse than 4). constexpr unsigned MinFillRunLength = 3; if (prevColorRunLength >= MinFillRunLength) { - AppendClxPixelsRun(begin, prevColorBegin - begin, out); + AppendClxPixelsRun(begin, static_cast(prevColorBegin - begin), out); AppendClxFillRun(prevColor, prevColorRunLength, out); begin = src; } @@ -76,10 +76,10 @@ inline void AppendClxPixelsOrFillRun(const uint8_t *src, unsigned length, std::v // is followed by transparent pixels. // Width=2 Fill command takes 2 bytes, while the Pixels command is 3 bytes. if (prevColorRunLength >= 2) { - AppendClxPixelsRun(begin, prevColorBegin - begin, out); + AppendClxPixelsRun(begin, static_cast(prevColorBegin - begin), out); AppendClxFillRun(prevColor, prevColorRunLength, out); } else { - AppendClxPixelsRun(begin, prevColorBegin - begin + prevColorRunLength, out); + AppendClxPixelsRun(begin, static_cast(prevColorBegin - begin + prevColorRunLength), out); } } diff --git a/Source/utils/console.cpp b/Source/utils/console.cpp index 5e37407e645..a8924b05de0 100644 --- a/Source/utils/console.cpp +++ b/Source/utils/console.cpp @@ -1,6 +1,6 @@ #include "./console.h" -#if (defined(_WIN64) || defined(_WIN32)) && !defined(NXDK) +#if defined(_WIN32) && !defined(DEVILUTIONX_WINDOWS_NO_WCHAR) #include #include #include @@ -25,17 +25,17 @@ HANDLE GetStderrHandle() return handle; } -void WriteToStderr(string_view str) +void WriteToStderr(std::string_view str) { HANDLE handle = GetStderrHandle(); if (handle == NULL) return; - WriteConsole(handle, str.data(), str.size(), NULL, NULL); + WriteConsole(handle, str.data(), static_cast(str.size()), NULL, NULL); } } // namespace -void printInConsole(string_view str) +void printInConsole(std::string_view str) { OutputDebugString(std::string(str).c_str()); WriteToStderr(str); @@ -72,7 +72,7 @@ void vprintfInConsole(const char *fmt, va_list ap) namespace devilution { -void printInConsole(string_view str) +void printInConsole(std::string_view str) { std::fwrite(str.data(), sizeof(char), str.size(), stderr); } diff --git a/Source/utils/console.h b/Source/utils/console.h index 263a99b539d..77341d2cf31 100644 --- a/Source/utils/console.h +++ b/Source/utils/console.h @@ -2,13 +2,13 @@ #include #include +#include #include "utils/attributes.h" -#include "utils/stdcompat/string_view.hpp" namespace devilution { -void printInConsole(string_view str); +void printInConsole(std::string_view str); void printNewlineInConsole(); void printfInConsole(const char *fmt, ...) DVL_PRINTF_ATTRIBUTE(1, 2); void vprintfInConsole(const char *fmt, std::va_list ap) DVL_PRINTF_ATTRIBUTE(1, 0); diff --git a/Source/utils/display.cpp b/Source/utils/display.cpp index 18c180e278b..41c1bae8e3b 100644 --- a/Source/utils/display.cpp +++ b/Source/utils/display.cpp @@ -1,6 +1,7 @@ #include "utils/display.h" #include +#include #include #ifdef __vita__ @@ -387,7 +388,7 @@ void ReinitializeTexture() auto quality = StrCat(static_cast(*sgOptions.Graphics.scaleQuality)); SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, quality.c_str()); - texture = SDLWrap::CreateTexture(renderer, SDL_PIXELFORMAT_RGB888, SDL_TEXTUREACCESS_STREAMING, gnScreenWidth, gnScreenHeight); + texture = SDLWrap::CreateTexture(renderer, DEVILUTIONX_DISPLAY_TEXTURE_FORMAT, SDL_TEXTUREACCESS_STREAMING, gnScreenWidth, gnScreenHeight); } void ReinitializeIntegerScale() diff --git a/Source/utils/endian_stream.hpp b/Source/utils/endian_stream.hpp index 76c9e16949a..0e40d320ce4 100644 --- a/Source/utils/endian_stream.hpp +++ b/Source/utils/endian_stream.hpp @@ -13,7 +13,7 @@ namespace devilution { inline void LoggedFread(void *buffer, size_t size, FILE *stream) { - if (std::fread(buffer, size, 1, stream) != 1) { + if (std::fread(buffer, size, 1, stream) != 1 && !std::feof(stream)) { LogError("fread failed: {}", std::strerror(errno)); } } diff --git a/Source/utils/enum_traits.h b/Source/utils/enum_traits.h index 7ad2f3fb75e..67496a07e3c 100644 --- a/Source/utils/enum_traits.h +++ b/Source/utils/enum_traits.h @@ -8,6 +8,8 @@ #include #include +#include "utils/attributes.h" + namespace devilution { template @@ -27,17 +29,17 @@ class enum_values { { } - const T operator*() const + [[nodiscard]] DVL_ALWAYS_INLINE const T operator*() const { return static_cast(m_value); } - void operator++() + DVL_ALWAYS_INLINE void operator++() { m_value++; } - bool operator!=(Iterator rhs) const + [[nodiscard]] DVL_ALWAYS_INLINE bool operator!=(Iterator rhs) const { return m_value != rhs.m_value; } @@ -45,13 +47,13 @@ class enum_values { }; template -typename enum_values::Iterator begin(enum_values) +[[nodiscard]] DVL_ALWAYS_INLINE typename enum_values::Iterator begin(enum_values) { return typename enum_values::Iterator(static_cast::type>(T::FIRST)); } template -typename enum_values::Iterator end(enum_values) +[[nodiscard]] DVL_ALWAYS_INLINE typename enum_values::Iterator end(enum_values) { return typename enum_values::Iterator(static_cast::type>(T::LAST) + 1); } @@ -66,54 +68,54 @@ struct is_flags_enum : std::false_type { }; template ::value && is_flags_enum::value, bool> = true> -constexpr EnumType operator|(EnumType lhs, EnumType rhs) +[[nodiscard]] DVL_ALWAYS_INLINE constexpr EnumType operator|(EnumType lhs, EnumType rhs) { using T = std::underlying_type_t; return static_cast(static_cast(lhs) | static_cast(rhs)); } template ::value && is_flags_enum::value, bool> = true> -constexpr EnumType operator|=(EnumType &lhs, EnumType rhs) +DVL_ALWAYS_INLINE constexpr EnumType operator|=(EnumType &lhs, EnumType rhs) { lhs = lhs | rhs; return lhs; } template ::value && is_flags_enum::value, bool> = true> -constexpr EnumType operator&(EnumType lhs, EnumType rhs) +[[nodiscard]] DVL_ALWAYS_INLINE constexpr EnumType operator&(EnumType lhs, EnumType rhs) { using T = std::underlying_type_t; return static_cast(static_cast(lhs) & static_cast(rhs)); } template ::value && is_flags_enum::value, bool> = true> -constexpr EnumType operator&=(EnumType &lhs, EnumType rhs) +DVL_ALWAYS_INLINE constexpr EnumType operator&=(EnumType &lhs, EnumType rhs) { lhs = lhs & rhs; return lhs; } template ::value && is_flags_enum::value, bool> = true> -constexpr EnumType operator~(EnumType value) +[[nodiscard]] DVL_ALWAYS_INLINE constexpr EnumType operator~(EnumType value) { using T = std::underlying_type_t; return static_cast(~static_cast(value)); } template ::value && is_flags_enum::value, bool> = true> -constexpr bool HasAnyOf(EnumType lhs, EnumType test) +[[nodiscard]] DVL_ALWAYS_INLINE constexpr bool HasAnyOf(EnumType lhs, EnumType test) { return (lhs & test) != static_cast(0); // Some flags enums may not use a None value outside this check so we don't require an EnumType::None definition here. } template ::value && is_flags_enum::value, bool> = true> -constexpr bool HasAllOf(EnumType lhs, EnumType test) +[[nodiscard]] DVL_ALWAYS_INLINE constexpr bool HasAllOf(EnumType lhs, EnumType test) { return (lhs & test) == test; } template ::value && is_flags_enum::value, bool> = true> -constexpr bool HasNoneOf(EnumType lhs, EnumType test) +[[nodiscard]] DVL_ALWAYS_INLINE constexpr bool HasNoneOf(EnumType lhs, EnumType test) { return !HasAnyOf(lhs, test); } diff --git a/Source/utils/file_name_generator.hpp b/Source/utils/file_name_generator.hpp index 59e5e6e4152..b140ee637c9 100644 --- a/Source/utils/file_name_generator.hpp +++ b/Source/utils/file_name_generator.hpp @@ -2,15 +2,15 @@ #include #include +#include -#include "utils/stdcompat/string_view.hpp" #include "utils/str_cat.hpp" namespace devilution { class BaseFileNameGenerator { public: - BaseFileNameGenerator(std::initializer_list prefixes, string_view suffix) + BaseFileNameGenerator(std::initializer_list prefixes, std::string_view suffix) : suffix_(suffix) , prefixEnd_(Append(buf_, prefixes)) { @@ -23,14 +23,14 @@ class BaseFileNameGenerator { } protected: - static char *Append(char *buf, std::initializer_list strings) + static char *Append(char *buf, std::initializer_list strings) { - for (string_view str : strings) + for (std::string_view str : strings) buf = BufCopy(buf, str); return buf; } - [[nodiscard]] string_view Suffix() const + [[nodiscard]] std::string_view Suffix() const { return suffix_; } @@ -44,7 +44,7 @@ class BaseFileNameGenerator { } private: - string_view suffix_; + std::string_view suffix_; char *prefixEnd_; char buf_[256]; }; @@ -59,7 +59,7 @@ class BaseFileNameGenerator { */ class FileNameGenerator : public BaseFileNameGenerator { public: - FileNameGenerator(std::initializer_list prefixes, string_view suffix, unsigned min = 1) + FileNameGenerator(std::initializer_list prefixes, std::string_view suffix, unsigned min = 1) : BaseFileNameGenerator(prefixes, suffix) , min_(min) { @@ -86,7 +86,7 @@ class FileNameGenerator : public BaseFileNameGenerator { */ class FileNameWithCharAffixGenerator : public BaseFileNameGenerator { public: - FileNameWithCharAffixGenerator(std::initializer_list prefixes, string_view suffix, const char *chars) + FileNameWithCharAffixGenerator(std::initializer_list prefixes, std::string_view suffix, const char *chars) : BaseFileNameGenerator(prefixes, suffix) , chars_(chars) { diff --git a/Source/utils/file_util.cpp b/Source/utils/file_util.cpp index b6859a2ac9d..5796fd25b5d 100644 --- a/Source/utils/file_util.cpp +++ b/Source/utils/file_util.cpp @@ -1,24 +1,24 @@ #include "utils/file_util.h" +#include #include #include #include - -#include +#include #include +#include #include #include #include "utils/log.hpp" #include "utils/stdcompat/filesystem.hpp" -#include "utils/stdcompat/string_view.hpp" #ifdef USE_SDL1 #include "utils/sdl2_to_1_2_backports.h" #endif -#if defined(_WIN64) || defined(_WIN32) +#ifdef _WIN32 #include // Suppress definitions of `min` and `max` macros by : @@ -26,12 +26,12 @@ #define WIN32_LEAN_AND_MEAN #include -#ifndef NXDK +#ifndef DEVILUTIONX_WINDOWS_NO_WCHAR #include #endif #endif -#if (_POSIX_C_SOURCE >= 200112L || defined(_BSD_SOURCE) || defined(__APPLE__)) && !defined(NXDK) +#if (_POSIX_C_SOURCE >= 200112L || defined(_BSD_SOURCE) || defined(__APPLE__)) && !defined(DEVILUTIONX_WINDOWS_NO_WCHAR) #include #include #endif @@ -49,37 +49,37 @@ namespace devilution { -#if (defined(_WIN64) || defined(_WIN32)) && !defined(NXDK) -std::unique_ptr ToWideChar(string_view path) +#if defined(_WIN32) && !defined(DEVILUTIONX_WINDOWS_NO_WCHAR) +std::unique_ptr ToWideChar(std::string_view path) { constexpr std::uint32_t flags = MB_ERR_INVALID_CHARS; - const int utf16Size = ::MultiByteToWideChar(CP_UTF8, flags, path.data(), path.size(), nullptr, 0); + const int utf16Size = ::MultiByteToWideChar(CP_UTF8, flags, path.data(), static_cast(path.size()), nullptr, 0); if (utf16Size == 0) return nullptr; std::unique_ptr utf16 { new wchar_t[utf16Size + 1] }; - if (::MultiByteToWideChar(CP_UTF8, flags, path.data(), path.size(), &utf16[0], utf16Size) != utf16Size) + if (::MultiByteToWideChar(CP_UTF8, flags, path.data(), static_cast(path.size()), &utf16[0], utf16Size) != utf16Size) return nullptr; utf16[utf16Size] = L'\0'; return utf16; } #endif -string_view Dirname(string_view path) +std::string_view Dirname(std::string_view path) { while (path.size() > 1 && path.back() == DirectorySeparator) path.remove_suffix(1); if (path.size() == 1 && path.back() == DirectorySeparator) return DIRECTORY_SEPARATOR_STR; const size_t sep = path.find_last_of(DIRECTORY_SEPARATOR_STR); - if (sep == string_view::npos) + if (sep == std::string_view::npos) return "."; - return string_view { path.data(), sep }; + return std::string_view { path.data(), sep }; } bool FileExists(const char *path) { -#if defined(_WIN64) || defined(_WIN32) -#if defined(NXDK) +#ifdef _WIN32 +#ifdef DEVILUTIONX_WINDOWS_NO_WCHAR const bool exists = ::GetFileAttributesA(path) != INVALID_FILE_ATTRIBUTES; #else const auto pathUtf16 = ToWideChar(path); @@ -93,10 +93,10 @@ bool FileExists(const char *path) if (::GetLastError() == ERROR_FILE_NOT_FOUND || ::GetLastError() == ERROR_PATH_NOT_FOUND) { ::SetLastError(ERROR_SUCCESS); } else { -#if defined(NXDK) - LogError("GetFileAttributesA: error code {}", ::GetLastError()); +#ifdef DEVILUTIONX_WINDOWS_NO_WCHAR + LogError("GetFileAttributesA({}): error code {}", path, ::GetLastError()); #else - LogError("PathFileExistsW: error code {}", ::GetLastError()); + LogError("PathFileExistsW({}): error code {}", path, ::GetLastError()); #endif } return false; @@ -116,11 +116,11 @@ bool FileExists(const char *path) #endif } -#if defined(_WIN64) || defined(_WIN32) +#ifdef _WIN32 namespace { DWORD WindowsGetFileAttributes(const char *path) { -#if defined(NXDK) +#ifdef DEVILUTIONX_WINDOWS_NO_WCHAR const DWORD attr = ::GetFileAttributesA(path); #else const auto pathUtf16 = ToWideChar(path); @@ -134,7 +134,7 @@ DWORD WindowsGetFileAttributes(const char *path) if (::GetLastError() == ERROR_FILE_NOT_FOUND || ::GetLastError() == ERROR_PATH_NOT_FOUND) { ::SetLastError(ERROR_SUCCESS); } else { -#if defined(NXDK) +#ifdef DEVILUTIONX_WINDOWS_NO_WCHAR LogError("GetFileAttributesA: error code {}", ::GetLastError()); #else LogError("GetFileAttributesW: error code {}", ::GetLastError()); @@ -149,7 +149,7 @@ DWORD WindowsGetFileAttributes(const char *path) bool DirectoryExists(const char *path) { -#if defined(_WIN64) || defined(_WIN32) +#ifdef _WIN32 const DWORD attr = WindowsGetFileAttributes(path); return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; #elif defined(DVL_HAS_FILESYSTEM) @@ -163,7 +163,7 @@ bool DirectoryExists(const char *path) bool FileExistsAndIsWriteable(const char *path) { -#if defined(_WIN64) || defined(_WIN32) +#ifdef _WIN32 const DWORD attr = WindowsGetFileAttributes(path); return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_READONLY) == 0; #elif (_POSIX_C_SOURCE >= 200112L || defined(_BSD_SOURCE) || defined(__APPLE__)) && !defined(__ANDROID__) @@ -181,9 +181,28 @@ bool FileExistsAndIsWriteable(const char *path) bool GetFileSize(const char *path, std::uintmax_t *size) { -#if defined(_WIN64) || defined(_WIN32) +#ifdef _WIN32 +#if defined(WINVER) && WINVER <= 0x0500 && (!defined(_WIN32_WINNT) || _WIN32_WINNT == 0) + HANDLE handle = ::CreateFileA(path, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + if (handle == INVALID_HANDLE_VALUE) { + LogError("File not found: {}", GetLastError()); + return false; + } + DWORD fileSizeHigh; + const DWORD fileSizeLow = ::GetFileSize(handle, &fileSizeHigh); + if (fileSizeLow == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) { + LogError("GetFileSize failed for {}: {}", path, GetLastError()); + ::CloseHandle(handle); + return false; + } + *size = (static_cast(fileSizeHigh) << 32) | fileSizeLow; + ::CloseHandle(handle); + return true; +#else WIN32_FILE_ATTRIBUTE_DATA attr; -#if defined(NXDK) +#ifdef DEVILUTIONX_WINDOWS_NO_WCHAR if (!GetFileAttributesExA(path, GetFileExInfoStandard, &attr)) { return false; } @@ -200,6 +219,7 @@ bool GetFileSize(const char *path, std::uintmax_t *size) // C4293 in msvc when shifting a 32 bit type by 32 bits. *size = static_cast(attr.nFileSizeHigh) << (sizeof(attr.nFileSizeHigh) * 8) | attr.nFileSizeLow; return true; +#endif #else struct ::stat statResult; if (::stat(path, &statResult) == -1) @@ -219,8 +239,8 @@ bool CreateDir(const char *path) return false; } return true; -#elif defined(_WIN64) || defined(_WIN32) -#ifdef NXDK +#elif defined(_WIN32) +#ifdef DEVILUTIONX_WINDOWS_NO_WCHAR if (::CreateDirectoryA(path, /*lpSecurityAttributes=*/nullptr) != 0) return true; if (::GetLastError() == ERROR_ALREADY_EXISTS) @@ -265,7 +285,7 @@ void RecursivelyCreateDir(const char *path) std::string cur { path }; do { paths.push_back(cur); - string_view parent = Dirname(cur); + std::string_view parent = Dirname(cur); if (parent == cur) break; cur.assign(parent.data(), parent.size()); @@ -279,14 +299,25 @@ void RecursivelyCreateDir(const char *path) bool ResizeFile(const char *path, std::uintmax_t size) { -#if defined(_WIN64) || defined(_WIN32) +#ifdef _WIN32 +#if defined(WINVER) && WINVER <= 0x0500 && (!defined(_WIN32_WINNT) || _WIN32_WINNT == 0) + if (size > std::numeric_limits::max()) { + return false; + } + auto lisize = static_cast(size); +#else LARGE_INTEGER lisize; lisize.QuadPart = static_cast(size); if (lisize.QuadPart < 0) { return false; } -#ifdef NXDK +#endif +#ifdef DEVILUTIONX_WINDOWS_NO_WCHAR HANDLE file = ::CreateFileA(path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + if (file == INVALID_HANDLE_VALUE) { + LogError("CreateFileA({}) failed: {}", path, ::GetLastError()); + return false; + } #else const auto pathUtf16 = ToWideChar(path); if (pathUtf16 == nullptr) { @@ -294,10 +325,26 @@ bool ResizeFile(const char *path, std::uintmax_t size) return false; } HANDLE file = ::CreateFileW(&pathUtf16[0], GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); -#endif if (file == INVALID_HANDLE_VALUE) { + LogError("CreateFileW({}) failed: {}", path, ::GetLastError()); return false; - } else if (::SetFilePointerEx(file, lisize, NULL, FILE_BEGIN) == 0 || ::SetEndOfFile(file) == 0) { + } +#endif +#if defined(WINVER) && WINVER <= 0x0500 && (!defined(_WIN32_WINNT) || _WIN32_WINNT == 0) + if (::SetFilePointer(file, lisize, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { + LogError("SetFilePointer(file, {}, NULL, FILE_BEGIN) failed: {}", lisize, ::GetLastError()); + ::CloseHandle(file); + return false; + } +#else + if (::SetFilePointerEx(file, lisize, NULL, FILE_BEGIN) == 0) { + LogError("SetFilePointerEx(file, {}, NULL, FILE_BEGIN) failed: {}", size, ::GetLastError()); + ::CloseHandle(file); + return false; + } +#endif + if (::SetEndOfFile(file) == 0) { + LogError("SetEndOfFile failed: {}", ::GetLastError()); ::CloseHandle(file); return false; } @@ -312,9 +359,10 @@ bool ResizeFile(const char *path, std::uintmax_t size) void RenameFile(const char *from, const char *to) { -#if defined(NXDK) +#ifdef _WIN32 +#ifdef DEVILUTIONX_WINDOWS_NO_WCHAR ::MoveFile(from, to); -#elif defined(_WIN64) || defined(_WIN32) +#else const auto fromUtf16 = ToWideChar(from); const auto toUtf16 = ToWideChar(to); if (fromUtf16 == nullptr || toUtf16 == nullptr) { @@ -322,6 +370,7 @@ void RenameFile(const char *from, const char *to) return; } ::MoveFileW(&fromUtf16[0], &toUtf16[0]); +#endif // _WIN32 #elif defined(DVL_HAS_FILESYSTEM) std::error_code ec; std::filesystem::rename(std::filesystem::u8path(from), std::filesystem::u8path(to), ec); @@ -332,11 +381,12 @@ void RenameFile(const char *from, const char *to) void CopyFileOverwrite(const char *from, const char *to) { -#if defined(NXDK) +#ifdef _WIN32 +#ifdef DEVILUTIONX_WINDOWS_NO_WCHAR if (!::CopyFile(from, to, /*bFailIfExists=*/false)) { LogError("Failed to copy {} to {}", from, to); } -#elif defined(_WIN64) || defined(_WIN32) +#else const auto fromUtf16 = ToWideChar(from); const auto toUtf16 = ToWideChar(to); if (fromUtf16 == nullptr || toUtf16 == nullptr) { @@ -346,6 +396,7 @@ void CopyFileOverwrite(const char *from, const char *to) if (!::CopyFileW(&fromUtf16[0], &toUtf16[0], /*bFailIfExists=*/false)) { LogError("Failed to copy {} to {}", from, to); } +#endif // _WIN32 #elif defined(__APPLE__) ::copyfile(from, to, nullptr, COPYFILE_ALL); #elif defined(DVL_HAS_FILESYSTEM) @@ -381,9 +432,9 @@ void CopyFileOverwrite(const char *from, const char *to) void RemoveFile(const char *path) { -#if defined(NXDK) +#ifdef DEVILUTIONX_WINDOWS_NO_WCHAR ::DeleteFileA(path); -#elif defined(_WIN64) || defined(_WIN32) +#elif defined(_WIN32) const auto pathUtf16 = ToWideChar(path); if (pathUtf16 == nullptr) { LogError("UTF-8 -> UTF-16 conversion error code {}", ::GetLastError()); @@ -407,7 +458,7 @@ void RemoveFile(const char *path) FILE *OpenFile(const char *path, const char *mode) { -#if (defined(_WIN64) || defined(_WIN32)) && !defined(NXDK) +#if defined(_WIN32) && !defined(DEVILUTIONX_WINDOWS_NO_WCHAR) std::unique_ptr pathUtf16; std::unique_ptr modeUtf16; if ((pathUtf16 = ToWideChar(path)) == nullptr diff --git a/Source/utils/file_util.h b/Source/utils/file_util.h index 6485775e161..a8a31116420 100644 --- a/Source/utils/file_util.h +++ b/Source/utils/file_util.h @@ -4,8 +4,7 @@ #include #include #include - -#include "utils/stdcompat/string_view.hpp" +#include namespace devilution { @@ -25,7 +24,7 @@ inline bool FileExists(const std::string &str) } bool DirectoryExists(const char *path); -string_view Dirname(string_view path); +std::string_view Dirname(std::string_view path); bool FileExistsAndIsWriteable(const char *path); bool GetFileSize(const char *path, std::uintmax_t *size); @@ -43,8 +42,8 @@ void CopyFileOverwrite(const char *from, const char *to); void RemoveFile(const char *path); FILE *OpenFile(const char *path, const char *mode); -#if (defined(_WIN64) || defined(_WIN32)) && !defined(NXDK) -std::unique_ptr ToWideChar(string_view path); +#if defined(_WIN32) && !defined(DEVILUTIONX_WINDOWS_NO_WCHAR) +std::unique_ptr ToWideChar(std::string_view path); #endif } // namespace devilution diff --git a/Source/utils/format_int.cpp b/Source/utils/format_int.cpp index 8bcfa61cf01..bb5f4052b0a 100644 --- a/Source/utils/format_int.cpp +++ b/Source/utils/format_int.cpp @@ -1,7 +1,8 @@ #include "utils/format_int.hpp" +#include + #include "utils/language.h" -#include "utils/stdcompat/string_view.hpp" #include "utils/str_cat.hpp" namespace devilution { @@ -23,7 +24,7 @@ std::string FormatInteger(int n) return out; } - const string_view separator = _(/* TRANSLATORS: Thousands separator */ ","); + const std::string_view separator = _(/* TRANSLATORS: Thousands separator */ ","); out.reserve(len + separator.size() * (numLen - 1) / GroupSize); if (n < 0) { out += '-'; @@ -36,7 +37,7 @@ std::string FormatInteger(int n) out.append(begin, mlen); begin += mlen; for (; begin != end; begin += GroupSize) { - AppendStrView(out, separator); + out.append(separator); out.append(begin, GroupSize); } diff --git a/Source/utils/intrusive_optional.hpp b/Source/utils/intrusive_optional.hpp index 4bfd47f6446..40523b473a5 100644 --- a/Source/utils/intrusive_optional.hpp +++ b/Source/utils/intrusive_optional.hpp @@ -1,10 +1,10 @@ #pragma once +#include #include #include #include "appfat.h" -#include "utils/stdcompat/optional.hpp" /// An optional that uses a field of the stored class and some value to store nullopt. #define DEFINE_INTRUSIVE_OPTIONAL_IMPL(OPTIONAL_CLASS, VALUE_CLASS, FIELD, NULL_VALUE, CONSTEXPR) \ diff --git a/Source/utils/language.cpp b/Source/utils/language.cpp index 18157d51042..614fa66b7b6 100644 --- a/Source/utils/language.cpp +++ b/Source/utils/language.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -9,10 +10,10 @@ #include "engine/assets.hpp" #include "options.h" +#include "utils/algorithm/container.hpp" #include "utils/file_util.h" #include "utils/log.hpp" #include "utils/paths.h" -#include "utils/stdcompat/string_view.hpp" #ifdef USE_SDL1 #include "utils/sdl2_to_1_2_backports.h" @@ -39,14 +40,14 @@ using TranslationRef = uint32_t; struct StringHash { size_t operator()(const char *str) const noexcept { - return std::hash {}(str); + return std::hash {}(str); } }; struct StringEq { bool operator()(const char *lhs, const char *rhs) const noexcept { - return string_view(lhs) == string_view(rhs); + return std::string_view(lhs) == std::string_view(rhs); } }; @@ -61,7 +62,7 @@ TranslationRef EncodeTranslationRef(uint32_t offset, uint32_t size) return (offset << TranslationRefSizeBits) | size; } -string_view GetTranslation(TranslationRef ref) +std::string_view GetTranslation(TranslationRef ref) { return { &translationValues[ref >> TranslationRefSizeBits], ref & TranslationRefSizeMask }; } @@ -103,13 +104,13 @@ void SwapLE(MoEntry &entry) entry.offset = SDL_SwapLE32(entry.offset); } -string_view TrimLeft(string_view str) +std::string_view TrimLeft(std::string_view str) { str.remove_prefix(std::min(str.find_first_not_of(" \t"), str.size())); return str; } -string_view TrimRight(string_view str) +std::string_view TrimRight(std::string_view str) { str.remove_suffix(str.size() - (str.find_last_not_of(" \t") + 1)); return str; @@ -128,16 +129,16 @@ tl::function_ref GetLocalPluralId = PluralIfNotOne; /** * Match plural=(n != 1);" */ -void SetPluralForm(string_view expression) +void SetPluralForm(std::string_view expression) { - const string_view key = "plural="; - const string_view::size_type keyPos = expression.find(key); - if (keyPos == string_view::npos) + const std::string_view key = "plural="; + const std::string_view::size_type keyPos = expression.find(key); + if (keyPos == std::string_view::npos) return; expression.remove_prefix(keyPos + key.size()); - const string_view::size_type semicolonPos = expression.find(';'); - if (semicolonPos != string_view::npos) { + const std::string_view::size_type semicolonPos = expression.find(';'); + if (semicolonPos != std::string_view::npos) { expression.remove_suffix(expression.size() - semicolonPos); } @@ -215,19 +216,19 @@ void SetPluralForm(string_view expression) /** * Parse "nplurals=2;" */ -void ParsePluralForms(string_view string) +void ParsePluralForms(std::string_view string) { - const string_view pluralsKey = "nplurals"; - const string_view::size_type pluralsPos = string.find(pluralsKey); - if (pluralsPos == string_view::npos) + const std::string_view pluralsKey = "nplurals"; + const std::string_view::size_type pluralsPos = string.find(pluralsKey); + if (pluralsPos == std::string_view::npos) return; string.remove_prefix(pluralsPos + pluralsKey.size()); - const string_view::size_type eqPos = string.find('='); - if (eqPos == string_view::npos) + const std::string_view::size_type eqPos = string.find('='); + if (eqPos == std::string_view::npos) return; - string_view value = string.substr(eqPos + 1); + std::string_view value = string.substr(eqPos + 1); if (value.empty() || value[0] < '0') return; @@ -240,16 +241,16 @@ void ParsePluralForms(string_view string) SetPluralForm(value); } -void ParseMetadata(string_view metadata) +void ParseMetadata(std::string_view metadata) { - string_view::size_type delim; + std::string_view::size_type delim; - while (!metadata.empty() && ((delim = metadata.find(':')) != string_view::npos)) { - const string_view key = TrimLeft(string_view(metadata.data(), delim)); - string_view val = TrimLeft(string_view(metadata.data() + delim + 1, metadata.size() - delim - 1)); + while (!metadata.empty() && ((delim = metadata.find(':')) != std::string_view::npos)) { + const std::string_view key = TrimLeft(std::string_view(metadata.data(), delim)); + std::string_view val = TrimLeft(std::string_view(metadata.data() + delim + 1, metadata.size() - delim - 1)); - if ((delim = val.find('\n')) != string_view::npos) { - val = string_view(val.data(), delim); + if ((delim = val.find('\n')) != std::string_view::npos) { + val = std::string_view(val.data(), delim); metadata.remove_prefix(val.data() - metadata.data() + val.size() + 1); } else { metadata.remove_prefix(metadata.size()); @@ -271,7 +272,7 @@ bool ReadEntry(AssetHandle &handle, const MoEntry &e, char *result) return handle.read(result, e.length); } -bool CopyData(void *dst, const byte *data, size_t dataSize, size_t offset, size_t length) +bool CopyData(void *dst, const std::byte *data, size_t dataSize, size_t offset, size_t length) { if (offset + length > dataSize) return false; @@ -279,7 +280,7 @@ bool CopyData(void *dst, const byte *data, size_t dataSize, size_t offset, size_ return true; } -bool ReadEntry(const byte *data, size_t dataSize, const MoEntry &e, char *result) +bool ReadEntry(const std::byte *data, size_t dataSize, const MoEntry &e, char *result) { if (!CopyData(result, data, dataSize, e.offset, e.length)) return false; @@ -289,14 +290,14 @@ bool ReadEntry(const byte *data, size_t dataSize, const MoEntry &e, char *result } // namespace -string_view LanguageParticularTranslate(string_view context, string_view message) +std::string_view LanguageParticularTranslate(std::string_view context, std::string_view message) { constexpr const char Glue = '\004'; std::string key = std::string(context); key.reserve(key.size() + 1 + message.size()); key += Glue; - AppendStrView(key, message); + key.append(message); auto it = translation[0].find(key.c_str()); if (it == translation[0].end()) { @@ -306,7 +307,7 @@ string_view LanguageParticularTranslate(string_view context, string_view message return GetTranslation(it->second); } -string_view LanguagePluralTranslate(const char *singular, string_view plural, int count) +std::string_view LanguagePluralTranslate(const char *singular, std::string_view plural, int count) { int n = GetLocalPluralId(count); @@ -320,7 +321,7 @@ string_view LanguagePluralTranslate(const char *singular, string_view plural, in return GetTranslation(it->second); } -string_view LanguageTranslate(const char *key) +std::string_view LanguageTranslate(const char *key) { auto it = translation[0].find(key); if (it == translation[0].end()) { @@ -338,12 +339,12 @@ bool HasTranslation(const std::string &locale) return true; } - return std::any_of(Extensions.cbegin(), Extensions.cend(), [locale](const char *extension) { + return c_any_of(Extensions, [locale](const char *extension) { return FindAsset((locale + extension).c_str()).ok(); }); } -string_view GetLanguageCode() +std::string_view GetLanguageCode() { if (!forceLocale.empty()) return forceLocale; @@ -352,7 +353,7 @@ string_view GetLanguageCode() bool IsSmallFontTall() { - const string_view code = GetLanguageCode().substr(0, 2); + const std::string_view code = GetLanguageCode().substr(0, 2); return code == "zh" || code == "ja" || code == "ko"; } @@ -409,9 +410,9 @@ void LanguageInitialize() const bool readWholeFile = handle.handle->type == SDL_RWOPS_UNKNOWN; #endif - std::unique_ptr data; + std::unique_ptr data; if (readWholeFile) { - data.reset(new byte[fileSize]); + data.reset(new std::byte[fileSize]); if (!handle.read(data.get(), fileSize)) return; handle = {}; @@ -492,10 +493,10 @@ void LanguageInitialize() : ReadEntry(handle, src[i], keyPtr) && ReadEntry(handle, dst[i], valuePtr)) { // Plural keys also have a plural form but it does not participate in lookup. // Plural values are \0-terminated. - string_view value { valuePtr, dst[i].length + 1 }; + std::string_view value { valuePtr, dst[i].length + 1 }; for (size_t j = 0; j < PluralForms && !value.empty(); j++) { const size_t formValueEnd = value.find('\0'); - translation[j].emplace(keyPtr, EncodeTranslationRef(value.data() - &translationValues[0], formValueEnd)); + translation[j].emplace(keyPtr, EncodeTranslationRef(static_cast(value.data() - &translationValues[0]), static_cast(formValueEnd))); value.remove_prefix(formValueEnd + 1); } diff --git a/Source/utils/language.h b/Source/utils/language.h index 2233b910780..dc40b5e9555 100644 --- a/Source/utils/language.h +++ b/Source/utils/language.h @@ -1,8 +1,7 @@ #pragma once #include - -#include "utils/stdcompat/string_view.hpp" +#include #define _(x) LanguageTranslate(x) #define ngettext(x, y, z) LanguagePluralTranslate(x, y, z) @@ -12,7 +11,7 @@ extern std::string forceLocale; -devilution::string_view GetLanguageCode(); +std::string_view GetLanguageCode(); bool HasTranslation(const std::string &locale); void LanguageInitialize(); @@ -22,8 +21,8 @@ void LanguageInitialize(); * * @return guaranteed to be null-terminated. */ -devilution::string_view LanguageTranslate(const char *key); -inline devilution::string_view LanguageTranslate(const std::string &key) +std::string_view LanguageTranslate(const char *key); +inline std::string_view LanguageTranslate(const std::string &key) { return LanguageTranslate(key.c_str()); } @@ -33,14 +32,14 @@ inline devilution::string_view LanguageTranslate(const std::string &key) * * @return guaranteed to be null-terminated if `plural` is. */ -devilution::string_view LanguagePluralTranslate(const char *singular, devilution::string_view plural, int count); +std::string_view LanguagePluralTranslate(const char *singular, std::string_view plural, int count); /** * @brief Returns the translation for the given key and context identifier. * * @return guaranteed to be null-terminated. */ -devilution::string_view LanguageParticularTranslate(devilution::string_view context, devilution::string_view message); +std::string_view LanguageParticularTranslate(std::string_view context, std::string_view message); // Chinese and Japanese, and Korean small font is 16px instead of a 12px one for readability. bool IsSmallFontTall(); diff --git a/Source/utils/log.hpp b/Source/utils/log.hpp index 4958e31fad1..97642434859 100644 --- a/Source/utils/log.hpp +++ b/Source/utils/log.hpp @@ -1,20 +1,21 @@ #pragma once +#include + #include #include #include -#include "utils/stdcompat/string_view.hpp" #include "utils/str_cat.hpp" #ifdef USE_SDL1 -#include "sdl2_to_1_2_backports.h" +#include "utils/sdl2_to_1_2_backports.h" #endif namespace devilution { // Local definition to fix compilation issue due to header conflict. -[[noreturn]] void app_fatal(string_view); +[[noreturn]] void app_fatal(std::string_view); enum class LogCategory { Application = SDL_LOG_CATEGORY_APPLICATION, @@ -42,7 +43,7 @@ enum class LogPriority { namespace detail { template -std::string format(string_view fmt, Args &&...args) +std::string format(std::string_view fmt, Args &&...args) { FMT_TRY { @@ -63,99 +64,99 @@ std::string format(string_view fmt, Args &&...args) } // namespace detail template -void Log(string_view fmt, Args &&...args) +void Log(std::string_view fmt, Args &&...args) { auto str = detail::format(fmt, std::forward(args)...); SDL_Log("%s", str.c_str()); } template -void LogVerbose(LogCategory category, string_view fmt, Args &&...args) +void LogVerbose(LogCategory category, std::string_view fmt, Args &&...args) { auto str = detail::format(fmt, std::forward(args)...); SDL_LogVerbose(static_cast(category), "%s", str.c_str()); } template -void LogVerbose(string_view fmt, Args &&...args) +void LogVerbose(std::string_view fmt, Args &&...args) { LogVerbose(defaultCategory, fmt, std::forward(args)...); } template -void LogDebug(LogCategory category, string_view fmt, Args &&...args) +void LogDebug(LogCategory category, std::string_view fmt, Args &&...args) { auto str = detail::format(fmt, std::forward(args)...); SDL_LogDebug(static_cast(category), "%s", str.c_str()); } template -void LogDebug(string_view fmt, Args &&...args) +void LogDebug(std::string_view fmt, Args &&...args) { LogDebug(defaultCategory, fmt, std::forward(args)...); } template -void LogInfo(LogCategory category, string_view fmt, Args &&...args) +void LogInfo(LogCategory category, std::string_view fmt, Args &&...args) { auto str = detail::format(fmt, std::forward(args)...); SDL_LogInfo(static_cast(category), "%s", str.c_str()); } template -void LogInfo(string_view fmt, Args &&...args) +void LogInfo(std::string_view fmt, Args &&...args) { LogInfo(defaultCategory, fmt, std::forward(args)...); } template -void LogWarn(LogCategory category, string_view fmt, Args &&...args) +void LogWarn(LogCategory category, std::string_view fmt, Args &&...args) { auto str = detail::format(fmt, std::forward(args)...); SDL_LogWarn(static_cast(category), "%s", str.c_str()); } template -void LogWarn(string_view fmt, Args &&...args) +void LogWarn(std::string_view fmt, Args &&...args) { LogWarn(defaultCategory, fmt, std::forward(args)...); } template -void LogError(LogCategory category, string_view fmt, Args &&...args) +void LogError(LogCategory category, std::string_view fmt, Args &&...args) { auto str = detail::format(fmt, std::forward(args)...); SDL_LogError(static_cast(category), "%s", str.c_str()); } template -void LogError(string_view fmt, Args &&...args) +void LogError(std::string_view fmt, Args &&...args) { LogError(defaultCategory, fmt, std::forward(args)...); } template -void LogCritical(LogCategory category, string_view fmt, Args &&...args) +void LogCritical(LogCategory category, std::string_view fmt, Args &&...args) { auto str = detail::format(fmt, std::forward(args)...); SDL_LogCritical(static_cast(category), "%s", str.c_str()); } template -void LogCritical(string_view fmt, Args &&...args) +void LogCritical(std::string_view fmt, Args &&...args) { LogCritical(defaultCategory, fmt, std::forward(args)...); } template -void LogMessageV(LogCategory category, LogPriority priority, string_view fmt, Args &&...args) +void LogMessageV(LogCategory category, LogPriority priority, std::string_view fmt, Args &&...args) { auto str = detail::format(fmt, std::forward(args)...); SDL_LogMessageV(static_cast(category), static_cast(priority), "%s", str.c_str()); } template -void LogMessageV(string_view fmt, Args &&...args) +void LogMessageV(std::string_view fmt, Args &&...args) { LogMessageV(defaultCategory, fmt, std::forward(args)...); } diff --git a/Source/utils/logged_fstream.hpp b/Source/utils/logged_fstream.hpp index d7bdfc736e2..80d358b0dba 100644 --- a/Source/utils/logged_fstream.hpp +++ b/Source/utils/logged_fstream.hpp @@ -3,12 +3,11 @@ #include #include #include +#include #include #include "utils/file_util.h" #include "utils/log.hpp" -#include "utils/stdcompat/optional.hpp" - namespace devilution { // A wrapper around `FILE *` that logs errors. @@ -17,7 +16,7 @@ struct LoggedFStream { bool Open(const char *path, const char *mode) { s_ = OpenFile(path, mode); - return CheckError("fopen(\"{}\", \"{}\")", path, mode); + return CheckError(s_ != nullptr, "fopen(\"{}\", \"{}\")", path, mode); } void Close() diff --git a/Source/utils/parse_int.cpp b/Source/utils/parse_int.cpp new file mode 100644 index 00000000000..1eb064b9de2 --- /dev/null +++ b/Source/utils/parse_int.cpp @@ -0,0 +1,34 @@ +#include "parse_int.hpp" + +#include + +namespace devilution { + +uint8_t ParseFixed6Fraction(std::string_view str, const char **endOfParse) +{ + unsigned numDigits = 0; + uint32_t decimalFraction = 0; + + // Read at most 7 digits, at that threshold we're able to determine an exact rounding for 6 bit fixed point numbers + while (!str.empty() && numDigits < 7) { + if (str[0] < '0' || str[0] > '9') { + break; + } + decimalFraction = decimalFraction * 10 + str[0] - '0'; + ++numDigits; + str.remove_prefix(1); + } + if (endOfParse != nullptr) { + // to mimic the behaviour of std::from_chars consume all remaining digits in case the value was overly precise. + *endOfParse = std::find_if_not(str.data(), str.data() + str.size(), [](char character) { return character >= '0' && character <= '9'; }); + } + // to ensure rounding to nearest we normalise all values to 7 decimal places + while (numDigits < 7) { + decimalFraction *= 10; + ++numDigits; + } + // we add half the step between representable values to use integer truncation as a substitute for rounding to nearest. + return (decimalFraction + 78125) / 156250; +} + +} // namespace devilution diff --git a/Source/utils/parse_int.hpp b/Source/utils/parse_int.hpp new file mode 100644 index 00000000000..76311b41b15 --- /dev/null +++ b/Source/utils/parse_int.hpp @@ -0,0 +1,115 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace devilution { + +enum class ParseIntError { + ParseError = 1, + OutOfRange +}; + +template +using ParseIntResult = tl::expected; + +template +ParseIntResult ParseInt( + std::string_view str, IntT min = std::numeric_limits::min(), + IntT max = std::numeric_limits::max(), const char **endOfParse = nullptr) +{ + IntT value; + const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.size(), value); + if (endOfParse != nullptr) { + *endOfParse = result.ptr; + } + if (result.ec == std::errc::invalid_argument) + return tl::unexpected(ParseIntError::ParseError); + if (result.ec == std::errc::result_out_of_range || value < min || value > max) + return tl::unexpected(ParseIntError::OutOfRange); + if (result.ec != std::errc()) + return tl::unexpected(ParseIntError::ParseError); + return value; +} + +/** + * @brief Parses a sequence of decimal characters into a 6 bit fixed point number in the range [0, 1.0] + * @param str a potentially empty string of base 10 digits, optionally followed by non-digit characters + * @param[out] endOfParse equivalent to std::from_chars_result::ptr, used to tell where parsing stopped + * @return a value in the range [0, 64], representing a 2.6 fixed value in the range [0, 1.0] + */ +uint8_t ParseFixed6Fraction(std::string_view str, const char **endOfParse = nullptr); + +template +ParseIntResult ParseFixed6(std::string_view str, const char **endOfParse = nullptr) +{ + if (endOfParse != nullptr) { + // To allow for early returns we set the end pointer to the start of the string, which is the common case for errors. + *endOfParse = str.data(); + } + + if (str.empty()) { + return tl::unexpected { ParseIntError::ParseError }; + } + + constexpr IntT minIntegerValue = std::numeric_limits::min() >> 6; + constexpr IntT maxIntegerValue = std::numeric_limits::max() >> 6; + + const char *currentChar; // will be set by the call to parseInt + ParseIntResult integerParseResult = ParseInt(str, minIntegerValue, maxIntegerValue, ¤tChar); + + bool isNegative = std::is_signed_v && str[0] == '-'; + bool haveDigits = integerParseResult.has_value() || integerParseResult.error() == ParseIntError::OutOfRange; + if (haveDigits) { + str.remove_prefix(static_cast(std::distance(str.data(), currentChar))); + } else if (isNegative) { + str.remove_prefix(1); + } + + // if the string has no leading digits we still need to try parse the fraction part + uint8_t fractionPart = 0; + if (!str.empty() && str[0] == '.') { + // got a fractional part to read too + str.remove_prefix(1); // skip past the decimal point + + fractionPart = ParseFixed6Fraction(str, ¤tChar); + haveDigits = haveDigits || str.data() != currentChar; + } + + if (!haveDigits) { + // early return in case we got a string like "-.abc", don't want to set the end pointer in this case + return tl::unexpected { ParseIntError::ParseError }; + } + + if (endOfParse != nullptr) { + *endOfParse = currentChar; + } + + if (!integerParseResult.has_value() && integerParseResult.error() == ParseIntError::OutOfRange) { + // if the integer parsing gave us an out of range value then we've done a bit of unnecessary + // work parsing the fraction part, but it saves duplicating code. + return integerParseResult; + } + // integerParseResult could be a ParseError at this point because of a string like ".123" or "-.1" + // so we need to default to 0 (and use the result of the minus sign check when it's relevant) + IntT integerPart = integerParseResult.value_or(0); + + // rounding could give us a value of 64 for the fraction part (e.g. 0.993 rounds to 1.0) so we need to ensure this doesn't overflow + if (fractionPart >= 64 && (integerPart >= maxIntegerValue || (std::is_signed_v && integerPart <= minIntegerValue))) { + return tl::unexpected { ParseIntError::OutOfRange }; + } else { + IntT fixedValue = integerPart << 6; + if (isNegative) { + fixedValue -= fractionPart; + } else { + fixedValue += fractionPart; + } + return fixedValue; + } +} + +} // namespace devilution diff --git a/Source/utils/paths.cpp b/Source/utils/paths.cpp index e80343635ab..2f6415bea11 100644 --- a/Source/utils/paths.cpp +++ b/Source/utils/paths.cpp @@ -84,7 +84,7 @@ const std::string &PrefPath() prefPath = FromSDL(SDL_GetPrefPath("diasurgical", "devilution")); #if !defined(__amigaos__) if (FileExistsAndIsWriteable("diablo.ini")) { - prefPath = std::string("." DIRECTORY_SEPARATOR_STR); + prefPath = std::string(); } #endif #endif @@ -103,7 +103,7 @@ const std::string &ConfigPath() configPath = FromSDL(SDL_GetPrefPath("diasurgical", "devilution")); #if !defined(__amigaos__) if (FileExistsAndIsWriteable("diablo.ini")) { - configPath = std::string("." DIRECTORY_SEPARATOR_STR); + configPath = std::string(); } #endif #endif diff --git a/Source/utils/paths.h b/Source/utils/paths.h index 639059a742c..ca4cc36003b 100644 --- a/Source/utils/paths.h +++ b/Source/utils/paths.h @@ -1,9 +1,8 @@ #pragma once +#include #include -#include "utils/stdcompat/optional.hpp" - namespace devilution { namespace paths { diff --git a/Source/utils/pcx_to_clx.cpp b/Source/utils/pcx_to_clx.cpp index e235762c952..cb94648cce9 100644 --- a/Source/utils/pcx_to_clx.cpp +++ b/Source/utils/pcx_to_clx.cpp @@ -1,5 +1,6 @@ #include "utils/pcx_to_clx.hpp" +#include #include #include @@ -13,7 +14,6 @@ #include "utils/clx_encode.hpp" #include "utils/endian.hpp" #include "utils/pcx.hpp" -#include "utils/stdcompat/cstddef.hpp" #ifdef DEBUG_PCX_TO_CL2_SIZE #include diff --git a/Source/utils/pcx_to_clx.hpp b/Source/utils/pcx_to_clx.hpp index 71ffc586f4a..2eca4cc3b37 100644 --- a/Source/utils/pcx_to_clx.hpp +++ b/Source/utils/pcx_to_clx.hpp @@ -1,10 +1,10 @@ #pragma once #include +#include #include "engine/assets.hpp" #include "engine/clx_sprite.hpp" -#include "utils/stdcompat/optional.hpp" namespace devilution { diff --git a/Source/utils/push_aulib_decoder.cpp b/Source/utils/push_aulib_decoder.cpp index 575394061ed..84593ca7867 100644 --- a/Source/utils/push_aulib_decoder.cpp +++ b/Source/utils/push_aulib_decoder.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include @@ -12,34 +14,33 @@ namespace devilution { -void PushAulibDecoder::PushSamples(const std::int16_t *data, unsigned size) noexcept +namespace { + +float SampleToFloat(int16_t sample) +{ + constexpr float Factor = 1.0F / (std::numeric_limits::max() + 1); + return sample * Factor; +} + +float SampleToFloat(uint8_t sample) { - AudioQueueItem item; - item.data.reset(new std::int16_t[size]); - std::memcpy(item.data.get(), data, size * sizeof(data[0])); - item.len = size; - item.pos = item.data.get(); - const std::lock_guard lock(queue_mutex_); - queue_.push(std::move(item)); + constexpr float Factor = 2.0F / std::numeric_limits::max(); + return (sample * Factor) - 1; } -void PushAulibDecoder::PushSamples(const std::uint8_t *data, unsigned size) noexcept +template +void ToFloats(const T *samples, float *out, unsigned count) { - AudioQueueItem item; - item.data.reset(new std::int16_t[size]); - constexpr std::int16_t Center = 128; - constexpr std::int16_t Scale = 256; - for (unsigned i = 0; i < size; ++i) - item.data[i] = static_cast((data[i] - Center) * Scale); - item.len = size; - item.pos = item.data.get(); - const std::lock_guard lock(queue_mutex_); - queue_.push(std::move(item)); + std::transform(samples, samples + count, out, [](T sample) { + return SampleToFloat(sample); + }); } +} // namespace + void PushAulibDecoder::DiscardPendingSamples() noexcept { - const std::lock_guard lock(queue_mutex_); + const auto lock = std::lock_guard(queue_mutex_); queue_ = std::queue(); } @@ -68,26 +69,25 @@ int PushAulibDecoder::doDecoding(float buf[], int len, bool &callAgain) { callAgain = false; - const auto writeFloats = [&buf](const std::int16_t *samples, unsigned count) { - constexpr float Scale = std::numeric_limits::max() + 1.F; - for (unsigned i = 0; i < count; ++i) { - buf[i] = static_cast(samples[i]) / Scale; - } + constexpr auto WriteFloats = [](PushAulibDecoder::AudioQueueItem &item, float *out, unsigned count) { + std::visit([&](const auto &samples) { ToFloats(&samples[item.pos], out, count); }, item.data); }; unsigned remaining = len; { - const std::lock_guard lock(queue_mutex_); + const auto lock = std::lock_guard(queue_mutex_); AudioQueueItem *item; while ((item = Next()) != nullptr) { if (static_cast(remaining) <= item->len) { - writeFloats(item->pos, remaining); + WriteFloats(*item, buf, remaining); item->pos += remaining; item->len -= remaining; + if (item->len == 0) + queue_.pop(); return len; } - writeFloats(item->pos, item->len); + WriteFloats(*item, buf, item->len); buf += item->len; remaining -= static_cast(item->len); queue_.pop(); diff --git a/Source/utils/push_aulib_decoder.h b/Source/utils/push_aulib_decoder.h index 8943dd812e7..b227d00907b 100644 --- a/Source/utils/push_aulib_decoder.h +++ b/Source/utils/push_aulib_decoder.h @@ -2,8 +2,11 @@ #include #include +#include #include +#include #include +#include #include #include @@ -23,8 +26,14 @@ class PushAulibDecoder final : public ::Aulib::Decoder { { } - void PushSamples(const std::uint8_t *data, unsigned size) noexcept; - void PushSamples(const std::int16_t *data, unsigned size) noexcept; + template + void PushSamples(const T *data, unsigned size) noexcept + { + AudioQueueItem item { data, size }; + const auto lock = std::lock_guard(queue_mutex_); + queue_.push(std::move(item)); + } + void DiscardPendingSamples() noexcept; bool open(SDL_RWops *rwops) override; @@ -48,9 +57,21 @@ class PushAulibDecoder final : public ::Aulib::Decoder { private: struct AudioQueueItem { - std::unique_ptr data; + std::variant< + std::unique_ptr, + std::unique_ptr> + data; unsigned len; - const std::int16_t *pos; + unsigned pos; + + template + AudioQueueItem(const T *data, unsigned size) + : data { std::unique_ptr { new T[size] } } + , len { size } + , pos { 0 } + { + std::memcpy(std::get>(this->data).get(), data, size * sizeof(T)); + } }; const int numChannels_; diff --git a/Source/utils/screen_reader.cpp b/Source/utils/screen_reader.cpp new file mode 100644 index 00000000000..43e3ecdf993 --- /dev/null +++ b/Source/utils/screen_reader.cpp @@ -0,0 +1,54 @@ +#include "utils/screen_reader.hpp" + +#include +#include + +#ifdef _WIN32 +#include "utils/file_util.h" +#include +#else +#include +#endif + +namespace devilution { + +#ifndef _WIN32 +SPDConnection *Speechd; +#endif + +void InitializeScreenReader() +{ +#ifdef _WIN32 + Tolk_Load(); +#else + Speechd = spd_open("DevilutionX", "DevilutionX", NULL, SPD_MODE_SINGLE); +#endif +} + +void ShutDownScreenReader() +{ +#ifdef _WIN32 + Tolk_Unload(); +#else + spd_close(Speechd); +#endif +} + +void SpeakText(std::string_view text) +{ + static std::string SpokenText; + + if (SpokenText == text) + return; + + SpokenText = text; + +#ifdef _WIN32 + const auto textUtf16 = ToWideChar(SpokenText); + Tolk_Output(&textUtf16[0], true); +#else + spd_say(Speechd, SPD_TEXT, SpokenText.c_str()); +#endif +} + +} // namespace devilution diff --git a/Source/utils/screen_reader.hpp b/Source/utils/screen_reader.hpp new file mode 100644 index 00000000000..64a029c1ccd --- /dev/null +++ b/Source/utils/screen_reader.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +namespace devilution { + +#ifdef SCREEN_READER_INTEGRATION +void InitializeScreenReader(); +void ShutDownScreenReader(); +void SpeakText(std::string_view text); +#else +constexpr void InitializeScreenReader() +{ +} + +constexpr void ShutDownScreenReader() +{ +} + +constexpr void SpeakText(std::string_view text) +{ +} +#endif + +} // namespace devilution diff --git a/Source/utils/sdl2_to_1_2_backports.cpp b/Source/utils/sdl2_to_1_2_backports.cpp index 7e4879de240..af280f13334 100644 --- a/Source/utils/sdl2_to_1_2_backports.cpp +++ b/Source/utils/sdl2_to_1_2_backports.cpp @@ -1,18 +1,24 @@ -#include "./sdl2_to_1_2_backports.h" +#include "utils/sdl2_to_1_2_backports.h" #include #include +#include +#include -#include "./console.h" - -#if defined(_WIN32) && !defined(NXDK) +#if defined(_WIN32) #define WIN32_LEAN_AND_MEAN #define NOMINMAX 1 +#ifndef DEVILUTIONX_WINDOWS_NO_WCHAR #define UNICODE 1 #include +#endif #include #endif +#include + +#include "utils/console.h" + #define DEFAULT_PRIORITY SDL_LOG_PRIORITY_CRITICAL #define DEFAULT_ASSERT_PRIORITY SDL_LOG_PRIORITY_WARN #define DEFAULT_APPLICATION_PRIORITY SDL_LOG_PRIORITY_INFO @@ -505,7 +511,7 @@ Sint64 SDL_RWsize(SDL_RWops *context) return end - begin; } -#if defined(_WIN32) && !defined(NXDK) +#if defined(_WIN32) && !defined(DEVILUTIONX_WINDOWS_NO_WCHAR) namespace { @@ -696,7 +702,7 @@ char *SDL_GetPrefPath(const char *org, const char *app) #else namespace { -#if !defined(__QNXNTO__) && !defined(__amigaos__) +#if !defined(__QNXNTO__) && !defined(__amigaos__) && !(defined(WINVER) && WINVER <= 0x0500 && (!defined(_WIN32_WINNT) || _WIN32_WINNT == 0)) char *readSymLink(const char *path) { // From sdl2-2.0.9/src/filesystem/unix/SDL_sysfilesystem.c @@ -736,7 +742,24 @@ char *SDL_GetBasePath() char *retval = NULL; -#if defined(__FREEBSD__) +#if defined(WINVER) && WINVER <= 0x0500 && (!defined(_WIN32_WINNT) || _WIN32_WINNT == 0) + TCHAR buffer[MAX_PATH] = { 0 }; + GetModuleFileName(NULL, buffer, MAX_PATH); + size_t len = std::string_view(buffer).size(); + while (len > 0) { + if (buffer[len - 1] == '\\') { + break; + } + --len; + } + buffer[len] = '\0'; + retval = static_cast(SDL_malloc(len + 1)); + if (!retval) { + SDL_OutOfMemory(); + return NULL; + } + SDL_memcpy(retval, buffer, len + 1); +#elif defined(__FREEBSD__) char fullpath[PATH_MAX]; size_t buflen = sizeof(fullpath); const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; @@ -747,8 +770,7 @@ char *SDL_GetBasePath() return NULL; } } -#endif -#if defined(__OPENBSD__) +#elif defined(__OPENBSD__) char **retvalargs; size_t len; const int mib[] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV }; @@ -765,8 +787,7 @@ char *SDL_GetBasePath() SDL_free(retvalargs); } -#endif -#if defined(__SOLARIS__) +#elif defined(__SOLARIS__) const char *path = getexecname(); if ((path != NULL) && (path[0] == '/')) { /* must be absolute path... */ retval = SDL_strdup(path); @@ -775,8 +796,7 @@ char *SDL_GetBasePath() return NULL; } } -#endif -#if defined(__3DS__) +#elif defined(__3DS__) retval = SDL_strdup("file:sdmc:/3ds/devilutionx/"); #elif defined(__amigaos__) retval = SDL_strdup("PROGDIR:"); @@ -841,6 +861,12 @@ char *SDL_GetPrefPath(const char *org, const char *app) * * https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html */ +#if defined(WINVER) && WINVER <= 0x0500 && (!defined(_WIN32_WINNT) || _WIN32_WINNT == 0) + // On Windows9x there is no such thing as PrefPath. Simply use the current directory. + char *result = (char *)SDL_malloc(1); + *result = '\0'; + return result; +#else const char *envr = SDL_getenv("XDG_DATA_HOME"); const char *append; char *retval = NULL; @@ -892,9 +918,9 @@ char *SDL_GetPrefPath(const char *org, const char *app) } if (*org) { - SDL_snprintf(retval, len, "%s%s%s/%s", envr, append, org, app); + *fmt::format_to_n(retval, len - 1, "{}{}{}/{}", envr, append, org, app).out = '\0'; } else { - SDL_snprintf(retval, len, "%s%s%s", envr, append, app); + *fmt::format_to_n(retval, len - 1, "{}{}{}", envr, append, app).out = '\0'; } for (ptr = retval + 1; *ptr; ptr++) { @@ -920,6 +946,7 @@ char *SDL_GetPrefPath(const char *org, const char *app) } return retval; +#endif } #endif diff --git a/Source/utils/sdl2_to_1_2_backports.h b/Source/utils/sdl2_to_1_2_backports.h index 93a5ac94960..a1b9e31d574 100644 --- a/Source/utils/sdl2_to_1_2_backports.h +++ b/Source/utils/sdl2_to_1_2_backports.h @@ -51,6 +51,8 @@ #define SDLK_LGUI SDLK_LSUPER #define SDLK_RGUI SDLK_RSUPER +#define SDL_SCANCODE_GRAVE 53 + // Haptic events are not supported in SDL1. #define SDL_INIT_HAPTIC 0 @@ -89,6 +91,7 @@ void SDL_LogInfo(int category, const char *fmt, ...) DVL_PRINTF_ATTRIBUTE(2, 3); void SDL_LogWarn(int category, const char *fmt, ...) DVL_PRINTF_ATTRIBUTE(2, 3); void SDL_LogError(int category, const char *fmt, ...) DVL_PRINTF_ATTRIBUTE(2, 3); void SDL_LogCritical(int category, const char *fmt, ...) DVL_PRINTF_ATTRIBUTE(2, 3); +void SDL_LogMessage(int category, SDL_LogPriority priority, const char *fmt, ...) DVL_PRINTF_ATTRIBUTE(3, 4); void SDL_LogMessageV(int category, SDL_LogPriority priority, const char *fmt, va_list ap) DVL_PRINTF_ATTRIBUTE(3, 0); void SDL_LogSetAllPriority(SDL_LogPriority priority); diff --git a/Source/utils/sdl_bilinear_scale.cpp b/Source/utils/sdl_bilinear_scale.cpp index 9d9b1a2f7b1..9164d709751 100644 --- a/Source/utils/sdl_bilinear_scale.cpp +++ b/Source/utils/sdl_bilinear_scale.cpp @@ -148,7 +148,7 @@ void BilinearScale32(SDL_Surface *src, SDL_Surface *dst) } } -void BilinearDownscaleByHalf8(const SDL_Surface *src, const std::array, 256> &paletteBlendingTable, SDL_Surface *dst, uint8_t transparentIndex) +void BilinearDownscaleByHalf8(const SDL_Surface *src, const Uint8 paletteBlendingTable[256][256], SDL_Surface *dst, uint8_t transparentIndex) { const auto *const srcPixelsBegin = static_cast(src->pixels) + static_cast(src->clip_rect.y * src->pitch + src->clip_rect.x); diff --git a/Source/utils/sdl_bilinear_scale.hpp b/Source/utils/sdl_bilinear_scale.hpp index 220d9ddcb31..b356703638e 100644 --- a/Source/utils/sdl_bilinear_scale.hpp +++ b/Source/utils/sdl_bilinear_scale.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -23,6 +22,6 @@ void BilinearScale32(SDL_Surface *src, SDL_Surface *dst); * @brief Streamlined bilinear downscaling using blended transparency table. * Requires `src` and `dst` to have the same pixel format (INDEX8). */ -void BilinearDownscaleByHalf8(const SDL_Surface *src, const std::array, 256> &paletteBlendingTable, SDL_Surface *dst, uint8_t transparentIndex); +void BilinearDownscaleByHalf8(const SDL_Surface *src, const Uint8 paletteBlendingTable[256][256], SDL_Surface *dst, uint8_t transparentIndex); } // namespace devilution diff --git a/Source/utils/soundsample.cpp b/Source/utils/soundsample.cpp index 5ff865b6c60..ac34d536969 100644 --- a/Source/utils/soundsample.cpp +++ b/Source/utils/soundsample.cpp @@ -32,7 +32,7 @@ constexpr float LogBase = 10.0; * Picked so that a volume change of -10 dB results in half perceived loudness. * VolumeScale = -1000 / log(0.5) */ -constexpr float VolumeScale = 3321.9281; +constexpr float VolumeScale = 3321.9281F; /** * Min and max volume range, in millibel. @@ -140,7 +140,7 @@ int SoundSample::SetChunk(ArraySharedPtr fileData, std::size_t dwB isMp3_ = isMp3; file_data_ = std::move(fileData); file_data_size_ = dwBytes; - SDL_RWops *buf = SDL_RWFromConstMem(file_data_.get(), dwBytes); + SDL_RWops *buf = SDL_RWFromConstMem(file_data_.get(), static_cast(dwBytes)); if (buf == nullptr) { return -1; } @@ -170,7 +170,7 @@ int SoundSample::GetLength() const { if (!stream_) return 0; - return std::chrono::duration_cast(stream_->duration()).count(); + return static_cast(std::chrono::duration_cast(stream_->duration()).count()); } } // namespace devilution diff --git a/Source/utils/static_vector.hpp b/Source/utils/static_vector.hpp index a4874dfb305..bf89e5ba411 100644 --- a/Source/utils/static_vector.hpp +++ b/Source/utils/static_vector.hpp @@ -1,11 +1,11 @@ #pragma once +#include #include #include #include #include "appfat.h" -#include "utils/stdcompat/cstddef.hpp" namespace devilution { @@ -43,6 +43,11 @@ class StaticVector { return size_; } + [[nodiscard]] bool empty() const + { + return size_ == 0; + } + [[nodiscard]] T &back() { return (*this)[size_ - 1]; @@ -73,34 +78,22 @@ class StaticVector { ~StaticVector() { for (std::size_t pos = 0; pos < size_; ++pos) { -#if __cplusplus >= 201703L std::destroy_at(data_[pos].ptr()); -#else - data_[pos].ptr()->~T(); -#endif } } private: struct AlignedStorage { - alignas(alignof(T)) byte data[sizeof(T)]; + alignas(alignof(T)) std::byte data[sizeof(T)]; const T *ptr() const { -#if __cplusplus >= 201703L return std::launder(reinterpret_cast(data)); -#else - return reinterpret_cast(data); -#endif } T *ptr() { -#if __cplusplus >= 201703L return std::launder(reinterpret_cast(data)); -#else - return reinterpret_cast(data); -#endif } }; AlignedStorage data_[N]; diff --git a/Source/utils/stdcompat/abs.hpp b/Source/utils/stdcompat/abs.hpp deleted file mode 100644 index eaf350cc616..00000000000 --- a/Source/utils/stdcompat/abs.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -namespace devilution { - -template -constexpr T abs(const T &a) -{ -#if defined(__GNUC__) || defined(__GNUG__) || defined(_MSC_VER) - return std::abs(a); -#else - return (a < 0) ? -a : a; -#endif -} - -} // namespace devilution diff --git a/Source/utils/stdcompat/algorithm.hpp b/Source/utils/stdcompat/algorithm.hpp deleted file mode 100644 index acf315cfee7..00000000000 --- a/Source/utils/stdcompat/algorithm.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include // IWYU pragma: export - -namespace devilution { -#if defined(__cplusplus) && __cplusplus >= 201703L -using std::clamp; // NOLINT(misc-unused-using-decls) -#else -template -constexpr const T &clamp(const T &x, const T &lower, const T &upper) -{ - return std::min(std::max(x, lower), upper); -} -#endif -} // namespace devilution diff --git a/Source/utils/stdcompat/cstddef.hpp b/Source/utils/stdcompat/cstddef.hpp deleted file mode 100644 index fead367b8d5..00000000000 --- a/Source/utils/stdcompat/cstddef.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include // IWYU pragma: export - -#if defined(__cplusplus) && __cplusplus >= 201703L -namespace devilution { -using byte = std::byte; -} // namespace devilution -#else -#include -namespace devilution { -using byte = std::uint8_t; -} // namespace devilution -#endif diff --git a/Source/utils/stdcompat/filesystem.hpp b/Source/utils/stdcompat/filesystem.hpp index 938f029f7df..b3f19a652dc 100644 --- a/Source/utils/stdcompat/filesystem.hpp +++ b/Source/utils/stdcompat/filesystem.hpp @@ -6,7 +6,8 @@ || (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000) #define DVL_NO_FILESYSTEM #endif -#elif defined(NXDK) || (defined(_MSVC_LANG) && _MSVC_LANG < 201703L) +#elif defined(NXDK) || (defined(_MSVC_LANG) && _MSVC_LANG < 201703L) \ + || (defined(WINVER) && WINVER <= 0x0500 && (!defined(_WIN32_WINNT) || _WIN32_WINNT == 0)) #define DVL_NO_FILESYSTEM #endif diff --git a/Source/utils/stdcompat/invoke_result_t.hpp b/Source/utils/stdcompat/invoke_result_t.hpp deleted file mode 100644 index 06480a6889e..00000000000 --- a/Source/utils/stdcompat/invoke_result_t.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -namespace devilution { - -#if defined(__cplusplus) && __cplusplus >= 201703L -using std::invoke_result_t; -#else -template -using invoke_result_t = typename std::result_of::type; -#endif - -} // namespace devilution diff --git a/Source/utils/stdcompat/optional.hpp b/Source/utils/stdcompat/optional.hpp deleted file mode 100644 index 90af7a27580..00000000000 --- a/Source/utils/stdcompat/optional.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#ifdef __has_include -#if defined(__cplusplus) && (__cplusplus >= 201606L || _MSC_VER >= 1930) && __has_include() -#include // IWYU pragma: export -#elif __has_include() -#include // IWYU pragma: export -#define optional experimental::optional -#define nullopt experimental::nullopt -#define nullopt_t experimental::nullopt_t -#else -#error "Missing support for or " -#endif -#else -#error "__has_include unavailable" -#endif diff --git a/Source/utils/stdcompat/shared_ptr_array.hpp b/Source/utils/stdcompat/shared_ptr_array.hpp index 597638dd3c6..b01be4463b3 100644 --- a/Source/utils/stdcompat/shared_ptr_array.hpp +++ b/Source/utils/stdcompat/shared_ptr_array.hpp @@ -6,7 +6,7 @@ namespace devilution { // Apple Clang 12 has a buggy implementation that fails to compile `std::shared_ptr(new T[size])`. -#if (__cplusplus >= 201611L && (!defined(__clang_major__) || __clang_major__ >= 13)) && !defined(NXDK) && !defined(__ANDROID__) +#if (__cplusplus >= 201611L && (!defined(__clang_major__) || __clang_major__ >= 13)) && !defined(NXDK) template using ArraySharedPtr = std::shared_ptr; diff --git a/Source/utils/stdcompat/string_view.hpp b/Source/utils/stdcompat/string_view.hpp deleted file mode 100644 index b6e901a84e6..00000000000 --- a/Source/utils/stdcompat/string_view.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#ifdef __has_include -#if defined(__cplusplus) && (__cplusplus >= 201703L || _MSC_VER >= 1930) && __has_include() // should be 201606L, but STL headers disagree - -#include -#include // IWYU pragma: export -namespace devilution { -using string_view = std::string_view; - -inline void AppendStrView(std::string &out, string_view str) -{ - out.append(str); -} -} // namespace devilution -#elif __has_include() - -#include // IWYU pragma: export -namespace devilution { -using string_view = std::experimental::string_view; - -inline void AppendStrView(std::string &out, string_view str) -{ - out.append(str.data(), str.size()); -} -} // namespace devilution -#else - -#error "Missing support for or " -#endif -#else - -#error "__has_include unavailable" -#endif diff --git a/Source/utils/str_case.hpp b/Source/utils/str_case.hpp index 59564c4abe0..0311c2c459e 100644 --- a/Source/utils/str_case.hpp +++ b/Source/utils/str_case.hpp @@ -1,14 +1,13 @@ #pragma once #include - -#include "utils/stdcompat/string_view.hpp" +#include namespace devilution { void AsciiStrToLower(std::string &str); -[[nodiscard]] inline std::string AsciiStrToLower(string_view str) +[[nodiscard]] inline std::string AsciiStrToLower(std::string_view str) { std::string copy { str.data(), str.size() }; AsciiStrToLower(copy); diff --git a/Source/utils/str_cat.cpp b/Source/utils/str_cat.cpp index 410d09d2ca3..ebb99c2d0d7 100644 --- a/Source/utils/str_cat.cpp +++ b/Source/utils/str_cat.cpp @@ -4,14 +4,25 @@ namespace devilution { -char *BufCopy(char *out, int value) +char *BufCopy(char *out, long long value) +{ + const fmt::format_int formatted { value }; + std::memcpy(out, formatted.data(), formatted.size()); + return out + formatted.size(); +} +char *BufCopy(char *out, unsigned long long value) { const fmt::format_int formatted { value }; std::memcpy(out, formatted.data(), formatted.size()); return out + formatted.size(); } -void StrAppend(std::string &out, int value) +void StrAppend(std::string &out, long long value) +{ + const fmt::format_int formatted { value }; + out.append(formatted.data(), formatted.size()); +} +void StrAppend(std::string &out, unsigned long long value) { const fmt::format_int formatted { value }; out.append(formatted.data(), formatted.size()); diff --git a/Source/utils/str_cat.hpp b/Source/utils/str_cat.hpp index b37d2e03544..b8a03fc9590 100644 --- a/Source/utils/str_cat.hpp +++ b/Source/utils/str_cat.hpp @@ -2,38 +2,96 @@ #include #include +#include #include -#include "utils/stdcompat/string_view.hpp" - namespace devilution { /** * @brief Writes the integer to the given buffer. * @return char* end of the buffer */ -char *BufCopy(char *out, int value); +char *BufCopy(char *out, long long value); +inline char *BufCopy(char *out, long value) +{ + return BufCopy(out, static_cast(value)); +} +inline char *BufCopy(char *out, int value) +{ + return BufCopy(out, static_cast(value)); +} +inline char *BufCopy(char *out, short value) +{ + return BufCopy(out, static_cast(value)); +} + +/** + * @brief Writes the integer to the given buffer. + * @return char* end of the buffer + */ +char *BufCopy(char *out, unsigned long long value); +inline char *BufCopy(char *out, unsigned long value) +{ + return BufCopy(out, static_cast(value)); +} +inline char *BufCopy(char *out, unsigned int value) +{ + return BufCopy(out, static_cast(value)); +} +inline char *BufCopy(char *out, unsigned short value) +{ + return BufCopy(out, static_cast(value)); +} + +/** + * @brief Appends the integer to the given string. + */ +void StrAppend(std::string &out, long long value); +inline void StrAppend(std::string &out, long value) +{ + StrAppend(out, static_cast(value)); +} +inline void StrAppend(std::string &out, int value) +{ + StrAppend(out, static_cast(value)); +} +inline void StrAppend(std::string &out, short value) +{ + StrAppend(out, static_cast(value)); +} /** * @brief Appends the integer to the given string. */ -void StrAppend(std::string &out, int value); +void StrAppend(std::string &out, unsigned long long value); +inline void StrAppend(std::string &out, unsigned long value) +{ + StrAppend(out, static_cast(value)); +} +inline void StrAppend(std::string &out, unsigned int value) +{ + StrAppend(out, static_cast(value)); +} +inline void StrAppend(std::string &out, unsigned short value) +{ + StrAppend(out, static_cast(value)); +} /** - * @brief Copies the given string_view to the given buffer. + * @brief Copies the given std::string_view to the given buffer. */ -inline char *BufCopy(char *out, string_view value) +inline char *BufCopy(char *out, std::string_view value) { std::memcpy(out, value.data(), value.size()); return out + value.size(); } /** - * @brief Copies the given string_view to the given string. + * @brief Copies the given std::string_view to the given string. */ -inline void StrAppend(std::string &out, string_view value) +inline void StrAppend(std::string &out, std::string_view value) { - AppendStrView(out, value); + out.append(value); } /** @@ -43,7 +101,7 @@ inline void StrAppend(std::string &out, string_view value) */ inline char *BufCopy(char *out, const char *str) { - return BufCopy(out, string_view(str != nullptr ? str : "(nullptr)")); + return BufCopy(out, std::string_view(str != nullptr ? str : "(nullptr)")); } /** @@ -51,41 +109,22 @@ inline char *BufCopy(char *out, const char *str) */ inline void StrAppend(std::string &out, const char *str) { - AppendStrView(out, string_view(str != nullptr ? str : "(nullptr)")); + out.append(std::string_view(str != nullptr ? str : "(nullptr)")); } -#if __cplusplus >= 201703L template typename std::enable_if<(sizeof...(Args) > 1), char *>::type BufCopy(char *out, Args &&...args) { return ((out = BufCopy(out, std::forward(args))), ...); } -#else -template -inline typename std::enable_if<(sizeof...(Args) > 0), char *>::type -BufCopy(char *out, Arg &&arg, Args &&...args) -{ - return BufCopy(BufCopy(out, std::forward(arg)), std::forward(args)...); -} -#endif -#if __cplusplus >= 201703L template typename std::enable_if<(sizeof...(Args) > 1), void>::type StrAppend(std::string &out, Args &&...args) { (StrAppend(out, std::forward(args)), ...); } -#else -template -typename std::enable_if<(sizeof...(Args) > 0), void>::type -StrAppend(std::string &out, Arg &&arg, Args &&...args) -{ - StrAppend(out, std::forward(arg)); - StrAppend(out, std::forward(args)...); -} -#endif template std::string StrCat(Args &&...args) diff --git a/Source/utils/str_split.hpp b/Source/utils/str_split.hpp index d56d550902d..20abbf97f8e 100644 --- a/Source/utils/str_split.hpp +++ b/Source/utils/str_split.hpp @@ -1,40 +1,41 @@ #pragma once #include +#include #include -#include "utils/stdcompat/string_view.hpp" - namespace devilution { class SplitByCharIterator { public: using iterator_category = std::forward_iterator_tag; - using value_type = string_view; + using value_type = std::string_view; using reference = std::add_lvalue_reference::type; using pointer = std::add_pointer::type; - static SplitByCharIterator begin(string_view text, char split_by) // NOLINT(readability-identifier-naming) + static SplitByCharIterator begin(std::string_view text, char split_by) // NOLINT(readability-identifier-naming) { return SplitByCharIterator(split_by, text, text.substr(0, text.find(split_by))); } - static SplitByCharIterator end(string_view text, char split_by) // NOLINT(readability-identifier-naming) - { - return SplitByCharIterator(split_by, text, text.substr(text.size())); - } + // End iterator + SplitByCharIterator() = default; - [[nodiscard]] string_view operator*() const + [[nodiscard]] std::string_view operator*() const { return slice_; } - [[nodiscard]] const string_view *operator->() const + [[nodiscard]] const std::string_view *operator->() const { return &slice_; } SplitByCharIterator &operator++() { + if (slice_.data() + slice_.size() == text_.data() + text_.size()) { + slice_ = {}; + return *this; + } slice_ = text_.substr(slice_.data() - text_.data() + slice_.size()); if (!slice_.empty()) slice_.remove_prefix(1); // skip the split_by char @@ -60,21 +61,21 @@ class SplitByCharIterator { } private: - SplitByCharIterator(char split_by, string_view text, string_view slice) + SplitByCharIterator(char split_by, std::string_view text, std::string_view slice) : split_by_(split_by) , text_(text) , slice_(slice) { } - const char split_by_; - const string_view text_; - string_view slice_; + const char split_by_ = '\0'; + const std::string_view text_; + std::string_view slice_; }; class SplitByChar { public: - explicit SplitByChar(string_view text, char split_by) + explicit SplitByChar(std::string_view text, char split_by) : text_(text) , split_by_(split_by) { @@ -87,11 +88,11 @@ class SplitByChar { [[nodiscard]] SplitByCharIterator end() const // NOLINT(readability-identifier-naming) { - return SplitByCharIterator::end(text_, split_by_); + return {}; } private: - const string_view text_; + const std::string_view text_; const char split_by_; }; diff --git a/Source/utils/string_or_view.hpp b/Source/utils/string_or_view.hpp index 97bcf8e5b14..1d38c75bd05 100644 --- a/Source/utils/string_or_view.hpp +++ b/Source/utils/string_or_view.hpp @@ -1,94 +1,62 @@ #pragma once #include +#include #include - -#include "utils/stdcompat/string_view.hpp" +#include namespace devilution { class StringOrView { public: StringOrView() - : owned_(false) - , view_() + : rep_ { std::string_view {} } { } + StringOrView(StringOrView &&) noexcept = default; + StringOrView(std::string &&str) - : owned_(true) - , str_(std::move(str)) + : rep_ { std::move(str) } { } - StringOrView(string_view str) - : owned_(false) - , view_(str) + StringOrView(std::string_view str) + : rep_ { str } { } - StringOrView(StringOrView &&other) noexcept - : owned_(other.owned_) - { - if (other.owned_) { - new (&str_) std::string(std::move(other.str_)); - } else { - new (&view_) string_view(other.view_); - } - } + StringOrView &operator=(StringOrView &&) noexcept = default; - StringOrView &operator=(StringOrView &&other) noexcept + StringOrView &operator=(std::string &&value) noexcept { - if (owned_) { - if (other.owned_) { - str_ = std::move(other.str_); - } else { - str_.~basic_string(); - owned_ = false; - new (&view_) string_view(other.view_); - } - } else { - if (other.owned_) { - view_.~string_view(); - owned_ = true; - new (&str_) std::string(std::move(other.str_)); - } else { - view_ = other.view_; - } - } + rep_ = std::move(value); return *this; } - ~StringOrView() + StringOrView &operator=(std::string_view value) noexcept { - if (owned_) { - str_.~basic_string(); - } else { - view_.~string_view(); - } + rep_ = value; + return *this; } bool empty() const { - return owned_ ? str_.empty() : view_.empty(); + return std::visit([](auto &&val) -> bool { return val.empty(); }, rep_); } - string_view str() const + std::string_view str() const { - return owned_ ? str_ : view_; + return std::visit([](auto &&val) -> std::string_view { return val; }, rep_); } - operator string_view() const + operator std::string_view() const { return str(); } private: - bool owned_; - union { - std::string str_; - string_view view_; - }; + std::variant rep_; }; } // namespace devilution diff --git a/Source/utils/surface_to_clx.hpp b/Source/utils/surface_to_clx.hpp index 0e1daedeef9..f0edb87b1a8 100644 --- a/Source/utils/surface_to_clx.hpp +++ b/Source/utils/surface_to_clx.hpp @@ -1,10 +1,10 @@ #pragma once #include +#include #include "engine/clx_sprite.hpp" #include "engine/surface.hpp" -#include "utils/stdcompat/optional.hpp" namespace devilution { diff --git a/Source/utils/timer.cpp b/Source/utils/timer.cpp new file mode 100644 index 00000000000..a9665d9ede6 --- /dev/null +++ b/Source/utils/timer.cpp @@ -0,0 +1,10 @@ +#include "engine/demomode.h" + +namespace devilution { + +uint32_t GetMillisecondsSinceStartup() +{ + return (demo::IsRunning() || demo::IsRecording()) ? demo::SimulateMillisecondsSinceStartup() : SDL_GetTicks(); +} + +} // namespace devilution diff --git a/Source/utils/timer.hpp b/Source/utils/timer.hpp new file mode 100644 index 00000000000..1bc7cde9146 --- /dev/null +++ b/Source/utils/timer.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace devilution { + +uint32_t GetMillisecondsSinceStartup(); + +} diff --git a/Source/utils/ui_fwd.h b/Source/utils/ui_fwd.h index 15412cbe6f8..60ec87454df 100644 --- a/Source/utils/ui_fwd.h +++ b/Source/utils/ui_fwd.h @@ -31,6 +31,6 @@ void ReinitializeIntegerScale(); #endif void ReinitializeRenderer(); void ResizeWindow(); -void UiErrorOkDialog(string_view caption, string_view text, bool error = true); +void UiErrorOkDialog(std::string_view caption, std::string_view text, bool error = true); } // namespace devilution diff --git a/Source/utils/utf8.cpp b/Source/utils/utf8.cpp index 73f994cff53..2c28694ffef 100644 --- a/Source/utils/utf8.cpp +++ b/Source/utils/utf8.cpp @@ -6,27 +6,9 @@ #include -#include "utils/attributes.h" - namespace devilution { -namespace { - -/** Truncates `str` to at most `len` at a code point boundary. */ -string_view TruncateUtf8(string_view str, std::size_t len) -{ - if (str.size() > len) { - std::size_t truncIndex = len; - while (truncIndex > 0 && IsTrailUtf8CodeUnit(str[truncIndex])) - truncIndex--; - str.remove_suffix(str.size() - truncIndex); - } - return str; -} - -} // namespace - -char32_t DecodeFirstUtf8CodePoint(string_view input, std::size_t *len) +char32_t DecodeFirstUtf8CodePoint(std::string_view input, std::size_t *len) { uint32_t codepoint = 0; uint8_t state = UTF8_ACCEPT; @@ -45,7 +27,18 @@ char32_t DecodeFirstUtf8CodePoint(string_view input, std::size_t *len) return Utf8DecodeError; } -void CopyUtf8(char *dest, string_view source, std::size_t bytes) +std::string_view TruncateUtf8(std::string_view str, std::size_t len) +{ + if (str.size() > len) { + std::size_t truncIndex = len; + while (truncIndex > 0 && IsTrailUtf8CodeUnit(str[truncIndex])) + truncIndex--; + str.remove_suffix(str.size() - truncIndex); + } + return str; +} + +void CopyUtf8(char *dest, std::string_view source, std::size_t bytes) { source = TruncateUtf8(source, bytes - 1); std::memcpy(dest, source.data(), source.size()); diff --git a/Source/utils/utf8.hpp b/Source/utils/utf8.hpp index 2864f2836a3..1b9d23cab9c 100644 --- a/Source/utils/utf8.hpp +++ b/Source/utils/utf8.hpp @@ -2,10 +2,9 @@ #include #include +#include #include -#include "utils/stdcompat/string_view.hpp" - namespace devilution { constexpr char32_t Utf8DecodeError = 0xD83F; @@ -16,12 +15,12 @@ constexpr char32_t Utf8DecodeError = 0xD83F; * Sets `len` to the length of the code point in bytes. * Returns `Utf8DecodeError` on error. */ -char32_t DecodeFirstUtf8CodePoint(string_view input, std::size_t *len); +char32_t DecodeFirstUtf8CodePoint(std::string_view input, std::size_t *len); /** * Decodes and removes the first code point from UTF8-encoded input. */ -inline char32_t ConsumeFirstUtf8CodePoint(string_view *input) +inline char32_t ConsumeFirstUtf8CodePoint(std::string_view *input) { std::size_t len; const char32_t result = DecodeFirstUtf8CodePoint(*input, &len); @@ -53,10 +52,18 @@ inline bool IsTrailUtf8CodeUnit(char x) return static_cast(x) < static_cast('\xC0'); } +/** + * @brief Returns the number of code units for a code point starting at *src; + */ +inline size_t Utf8CodePointLen(const char *src) +{ + return "\1\1\1\1\1\1\1\1\1\1\1\1\2\2\3\4"[static_cast(*src) >> 4]; +} + /** * Returns the start byte index of the last code point in a UTF-8 string. */ -inline std::size_t FindLastUtf8Symbols(string_view input) +inline std::size_t FindLastUtf8Symbols(std::string_view input) { if (input.empty()) return 0; @@ -73,8 +80,11 @@ inline std::size_t FindLastUtf8Symbols(string_view input) * @param source The source string * @param bytes Max number of bytes to copy */ -void CopyUtf8(char *dest, string_view source, std::size_t bytes); +void CopyUtf8(char *dest, std::string_view source, std::size_t bytes); void AppendUtf8(char32_t codepoint, std::string &out); +/** @brief Truncates `str` to at most `len` at a code point boundary. */ +std::string_view TruncateUtf8(std::string_view str, std::size_t len); + } // namespace devilution diff --git a/Translations/es.po b/Translations/es.po index d5284a99288..c3e15f1e5d0 100644 --- a/Translations/es.po +++ b/Translations/es.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: DevilutionX\n" -"POT-Creation-Date: 2023-01-17 12:39-0300\n" -"PO-Revision-Date: 2023-07-29 11:57-0300\n" +"POT-Creation-Date: 2023-11-30 09:14-0300\n" +"PO-Revision-Date: 2023-11-30 09:33-0300\n" "Last-Translator: Emiliano Augusto Gonzalez \n" "Language-Team: Spanish\n" "Language: es\n" @@ -15,7 +15,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 3.3.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" @@ -102,7 +102,7 @@ msgstr "Productor Asociado" msgid "Diablo Strike Team" msgstr "Diablo Strike Team" -#: Source/DiabloUI/credits_lines.cpp:77 Source/gamemenu.cpp:69 +#: Source/DiabloUI/credits_lines.cpp:77 Source/gamemenu.cpp:74 msgid "Music" msgstr "Música" @@ -317,256 +317,398 @@ msgstr "El Anillo de los Mil" msgid "\tNo souls were sold in the making of this game." msgstr "\tNingún alma fue vendida para la realización de este juego." -#: Source/DiabloUI/dialogs.cpp:83 Source/DiabloUI/dialogs.cpp:95 -#: Source/DiabloUI/selconn.cpp:82 Source/DiabloUI/selgame.cpp:169 -#: Source/DiabloUI/selgame.cpp:333 Source/DiabloUI/selgame.cpp:359 -#: Source/DiabloUI/selgame.cpp:501 Source/DiabloUI/selgame.cpp:578 -#: Source/DiabloUI/selhero.cpp:170 Source/DiabloUI/selhero.cpp:195 -#: Source/DiabloUI/selhero.cpp:280 Source/DiabloUI/selhero.cpp:520 +#: Source/DiabloUI/dialogs.cpp:84 Source/DiabloUI/dialogs.cpp:96 +#: Source/DiabloUI/hero/selhero.cpp:176 Source/DiabloUI/hero/selhero.cpp:202 +#: Source/DiabloUI/hero/selhero.cpp:287 Source/DiabloUI/hero/selhero.cpp:527 +#: Source/DiabloUI/multi/selconn.cpp:82 Source/DiabloUI/multi/selgame.cpp:173 +#: Source/DiabloUI/multi/selgame.cpp:336 Source/DiabloUI/multi/selgame.cpp:362 +#: Source/DiabloUI/multi/selgame.cpp:504 Source/DiabloUI/multi/selgame.cpp:581 #: Source/DiabloUI/selok.cpp:71 msgid "OK" msgstr "OK" -#: Source/DiabloUI/mainmenu.cpp:37 +#: Source/DiabloUI/hero/selhero.cpp:154 +msgid "Choose Class" +msgstr "Elija Clase" + +#. TRANSLATORS: Player Block start +#. HeroClass::Warrior +#: Source/DiabloUI/hero/selhero.cpp:158 Source/playerdat.cpp:254 +msgid "Warrior" +msgstr "Guerrero" + +#: Source/DiabloUI/hero/selhero.cpp:159 Source/playerdat.cpp:255 +msgid "Rogue" +msgstr "Arpía" + +#: Source/DiabloUI/hero/selhero.cpp:160 Source/playerdat.cpp:256 +msgid "Sorcerer" +msgstr "Hechicero" + +#: Source/DiabloUI/hero/selhero.cpp:162 Source/playerdat.cpp:257 +msgid "Monk" +msgstr "Monje" + +#: Source/DiabloUI/hero/selhero.cpp:164 Source/playerdat.cpp:258 +msgid "Bard" +msgstr "Bardo" + +#. TRANSLATORS: Player Block end +#. HeroClass::Barbarian +#: Source/DiabloUI/hero/selhero.cpp:167 Source/playerdat.cpp:260 +msgid "Barbarian" +msgstr "Bárbaro" + +#: Source/DiabloUI/hero/selhero.cpp:179 Source/DiabloUI/hero/selhero.cpp:205 +#: Source/DiabloUI/hero/selhero.cpp:290 Source/DiabloUI/hero/selhero.cpp:535 +#: Source/DiabloUI/multi/selconn.cpp:85 Source/DiabloUI/progress.cpp:44 +msgid "Cancel" +msgstr "Cancelar" + +#: Source/DiabloUI/hero/selhero.cpp:185 Source/DiabloUI/hero/selhero.cpp:275 +msgid "New Multi Player Hero" +msgstr "Nuevo Héroe Multijugador" + +#: Source/DiabloUI/hero/selhero.cpp:185 Source/DiabloUI/hero/selhero.cpp:275 +msgid "New Single Player Hero" +msgstr "Nuevo Héroe para un Jugador" + +#: Source/DiabloUI/hero/selhero.cpp:194 +msgid "Save File Exists" +msgstr "Existe juego guardado" + +#: Source/DiabloUI/hero/selhero.cpp:197 Source/gamemenu.cpp:45 +msgid "Load Game" +msgstr "Cargar" + +#: Source/DiabloUI/hero/selhero.cpp:198 Source/gamemenu.cpp:44 +#: Source/gamemenu.cpp:55 Source/multi.cpp:792 +msgid "New Game" +msgstr "Nueva Partida" + +#: Source/DiabloUI/hero/selhero.cpp:208 Source/DiabloUI/hero/selhero.cpp:541 +msgid "Single Player Characters" +msgstr "Personajes de un Jugador" + +#: Source/DiabloUI/hero/selhero.cpp:267 +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 "" +"El Ladrón y el Hechicero solo están disponibles en la versión comercial " +"completa de Diablo. Visite https://www.gog.com/game/diablo para comprar." + +#: Source/DiabloUI/hero/selhero.cpp:281 Source/DiabloUI/hero/selhero.cpp:284 +msgid "Enter Name" +msgstr "Introduzca nombre" + +#: Source/DiabloUI/hero/selhero.cpp:313 +msgid "" +"Invalid name. A name cannot contain spaces, reserved characters, or reserved " +"words.\n" +msgstr "" +"Nombre inválido. Un nombre no puede contener espacios, caracteres reservados " +"ni palabras reservadas.\n" + +#. TRANSLATORS: Error Message +#: Source/DiabloUI/hero/selhero.cpp:320 +msgid "Unable to create character." +msgstr "Imposible crear personaje." + +#: Source/DiabloUI/hero/selhero.cpp:486 +msgid "Level:" +msgstr "Nivel:" + +#: Source/DiabloUI/hero/selhero.cpp:490 +msgid "Strength:" +msgstr "Fuerza:" + +#: Source/DiabloUI/hero/selhero.cpp:490 +msgid "Magic:" +msgstr "Magia:" + +#: Source/DiabloUI/hero/selhero.cpp:490 +msgid "Dexterity:" +msgstr "Destreza:" + +#: Source/DiabloUI/hero/selhero.cpp:490 +msgid "Vitality:" +msgstr "Vitalidad:" + +#: Source/DiabloUI/hero/selhero.cpp:492 +msgid "Savegame:" +msgstr "Juego guardado:" + +#: Source/DiabloUI/hero/selhero.cpp:511 +msgid "Select Hero" +msgstr "Elije Héroe" + +#: Source/DiabloUI/hero/selhero.cpp:519 +msgid "New Hero" +msgstr "Nuevo Héroe" + +#: Source/DiabloUI/hero/selhero.cpp:530 +msgid "Delete" +msgstr "Eliminar" + +#: Source/DiabloUI/hero/selhero.cpp:539 +msgid "Multi Player Characters" +msgstr "Personajes Multi Jugador" + +#: Source/DiabloUI/hero/selhero.cpp:590 +msgid "Delete Multi Player Hero" +msgstr "Eliminar Héroe Multijugador" + +#: Source/DiabloUI/hero/selhero.cpp:592 +msgid "Delete Single Player Hero" +msgstr "Eliminar Héroe" + +#: Source/DiabloUI/hero/selhero.cpp:594 +#, c++-format +msgid "Are you sure you want to delete the character \"{:s}\"?" +msgstr "¿Está seguro de que desea borrar el personaje \"{:s}\"?" + +#: Source/DiabloUI/mainmenu.cpp:38 msgid "Single Player" msgstr "Un Jugador" -#: Source/DiabloUI/mainmenu.cpp:38 +#: Source/DiabloUI/mainmenu.cpp:39 msgid "Multi Player" msgstr "Multijugador" -#: Source/DiabloUI/mainmenu.cpp:39 Source/DiabloUI/settingsmenu.cpp:363 +#: Source/DiabloUI/mainmenu.cpp:40 Source/DiabloUI/settingsmenu.cpp:365 msgid "Settings" msgstr "Ajustes" -#: Source/DiabloUI/mainmenu.cpp:40 +#: Source/DiabloUI/mainmenu.cpp:41 msgid "Support" msgstr "Soporte" -#: Source/DiabloUI/mainmenu.cpp:41 +#: Source/DiabloUI/mainmenu.cpp:42 msgid "Show Credits" msgstr "Créditos" -#: Source/DiabloUI/mainmenu.cpp:43 +#: Source/DiabloUI/mainmenu.cpp:44 msgid "Exit Hellfire" msgstr "Salir" -#: Source/DiabloUI/mainmenu.cpp:43 +#: Source/DiabloUI/mainmenu.cpp:44 msgid "Exit Diablo" msgstr "Salir" -#: Source/DiabloUI/mainmenu.cpp:61 +#: Source/DiabloUI/mainmenu.cpp:62 msgid "Shareware" msgstr "Shareware" -#: Source/DiabloUI/progress.cpp:44 Source/DiabloUI/selconn.cpp:85 -#: Source/DiabloUI/selhero.cpp:173 Source/DiabloUI/selhero.cpp:198 -#: Source/DiabloUI/selhero.cpp:283 Source/DiabloUI/selhero.cpp:528 -msgid "Cancel" -msgstr "Cancelar" - -#: Source/DiabloUI/selconn.cpp:14 +#: Source/DiabloUI/multi/selconn.cpp:14 msgid "Client-Server (TCP)" msgstr "Cliente-Servidor (TCP)" -#: Source/DiabloUI/selconn.cpp:15 +#: Source/DiabloUI/multi/selconn.cpp:15 msgid "Offline" msgstr "Fuera de línea" -#: Source/DiabloUI/selconn.cpp:56 Source/DiabloUI/selgame.cpp:645 -#: Source/DiabloUI/selgame.cpp:669 +#: Source/DiabloUI/multi/selconn.cpp:56 Source/DiabloUI/multi/selgame.cpp:648 +#: Source/DiabloUI/multi/selgame.cpp:672 msgid "Multi Player Game" msgstr "Juego Multijugador" -#: Source/DiabloUI/selconn.cpp:62 +#: Source/DiabloUI/multi/selconn.cpp:62 msgid "Requirements:" msgstr "Requisitos:" -#: Source/DiabloUI/selconn.cpp:68 +#: Source/DiabloUI/multi/selconn.cpp:68 msgid "no gateway needed" msgstr "sin puerta de enlace" -#: Source/DiabloUI/selconn.cpp:74 +#: Source/DiabloUI/multi/selconn.cpp:74 msgid "Select Connection" msgstr "Seleccionar conexión" -#: Source/DiabloUI/selconn.cpp:77 +#: Source/DiabloUI/multi/selconn.cpp:77 msgid "Change Gateway" msgstr "Cambiar puerta de enlace" -#: Source/DiabloUI/selconn.cpp:110 +#: Source/DiabloUI/multi/selconn.cpp:110 msgid "All computers must be connected to a TCP-compatible network." msgstr "" "Todas las computadoras deben estar conectadas a una red compatible con TCP." -#: Source/DiabloUI/selconn.cpp:114 +#: Source/DiabloUI/multi/selconn.cpp:114 msgid "All computers must be connected to the internet." msgstr "Todas las computadoras deben estar conectadas a Internet." -#: Source/DiabloUI/selconn.cpp:118 +#: Source/DiabloUI/multi/selconn.cpp:118 msgid "Play by yourself with no network exposure." msgstr "Juegar solo sin exposición a la red." -#: Source/DiabloUI/selconn.cpp:123 +#: Source/DiabloUI/multi/selconn.cpp:123 +#, c++-format msgid "Players Supported: {:d}" msgstr "Jugadores soportados: {:d}" -#: Source/DiabloUI/selgame.cpp:84 Source/options.cpp:564 Source/options.cpp:603 -#: Source/quests.cpp:51 +#: Source/DiabloUI/multi/selgame.cpp:86 Source/options.cpp:572 +#: Source/options.cpp:611 Source/quests.cpp:57 msgid "Diablo" msgstr "Diablo" -#: Source/DiabloUI/selgame.cpp:87 +#: Source/DiabloUI/multi/selgame.cpp:89 msgid "Diablo Shareware" msgstr "Diablo Shareware" -#: Source/DiabloUI/selgame.cpp:90 Source/options.cpp:566 Source/options.cpp:617 +#: Source/DiabloUI/multi/selgame.cpp:92 Source/options.cpp:574 +#: Source/options.cpp:625 msgid "Hellfire" msgstr "Hellfire" -#: Source/DiabloUI/selgame.cpp:93 +#: Source/DiabloUI/multi/selgame.cpp:95 msgid "Hellfire Shareware" msgstr "Hellfire Shareware" -#: Source/DiabloUI/selgame.cpp:96 +#: Source/DiabloUI/multi/selgame.cpp:98 msgid "The host is running a different game than you." msgstr "El equipo está ejecutando un juego diferente al tuyo." -#: Source/DiabloUI/selgame.cpp:98 +#: Source/DiabloUI/multi/selgame.cpp:100 +#, c++-format msgid "The host is running a different game mode ({:s}) than you." msgstr "El equipo está ejecutando un modo de juego ({:s}) diferente al tuyo." #. TRANSLATORS: Error message when somebody tries to join a game running another version. -#: Source/DiabloUI/selgame.cpp:100 +#: Source/DiabloUI/multi/selgame.cpp:102 +#, c++-format msgid "Your version {:s} does not match the host {:d}.{:d}.{:d}." msgstr "Su versión {:s} no coincide con el anfitrión {:d}.{:d}.{:d}." -#: Source/DiabloUI/selgame.cpp:135 Source/DiabloUI/selgame.cpp:564 +#: Source/DiabloUI/multi/selgame.cpp:139 Source/DiabloUI/multi/selgame.cpp:567 msgid "Description:" msgstr "Descripción:" -#: Source/DiabloUI/selgame.cpp:141 +#: Source/DiabloUI/multi/selgame.cpp:145 msgid "Select Action" msgstr "Seleccione acción" -#: Source/DiabloUI/selgame.cpp:144 Source/DiabloUI/selgame.cpp:321 -#: Source/DiabloUI/selgame.cpp:482 +#: Source/DiabloUI/multi/selgame.cpp:148 Source/DiabloUI/multi/selgame.cpp:324 +#: Source/DiabloUI/multi/selgame.cpp:485 msgid "Create Game" msgstr "Crear Juego" -#: Source/DiabloUI/selgame.cpp:146 +#: Source/DiabloUI/multi/selgame.cpp:150 msgid "Create Public Game" msgstr "Crear Juego Público" -#: Source/DiabloUI/selgame.cpp:147 +#: Source/DiabloUI/multi/selgame.cpp:151 msgid "Join Game" msgstr "Unirse al Juego" -#: Source/DiabloUI/selgame.cpp:151 +#: Source/DiabloUI/multi/selgame.cpp:155 msgid "Public Games" msgstr "Juegos público" -#: Source/DiabloUI/selgame.cpp:156 Source/error.cpp:62 +#: Source/DiabloUI/multi/selgame.cpp:160 Source/diablo_msg.cpp:71 msgid "Loading..." msgstr "Cargando..." #. TRANSLATORS: type of dungeon (i.e. Cathedral, Caves) -#: Source/DiabloUI/selgame.cpp:158 Source/discord/discord.cpp:72 -#: Source/options.cpp:585 Source/panels/charpanel.cpp:143 +#: Source/DiabloUI/multi/selgame.cpp:162 Source/discord/discord.cpp:78 +#: Source/options.cpp:593 Source/panels/charpanel.cpp:138 msgid "None" msgstr "Ninguno" -#: Source/DiabloUI/selgame.cpp:172 Source/DiabloUI/selgame.cpp:336 -#: Source/DiabloUI/selgame.cpp:362 Source/DiabloUI/selgame.cpp:504 -#: Source/DiabloUI/selgame.cpp:581 +#: Source/DiabloUI/multi/selgame.cpp:176 Source/DiabloUI/multi/selgame.cpp:339 +#: Source/DiabloUI/multi/selgame.cpp:365 Source/DiabloUI/multi/selgame.cpp:507 +#: Source/DiabloUI/multi/selgame.cpp:584 msgid "CANCEL" msgstr "CANCELAR" -#: Source/DiabloUI/selgame.cpp:212 +#: Source/DiabloUI/multi/selgame.cpp:215 msgid "Create a new game with a difficulty setting of your choice." msgstr "Crear un nuevo juego con la dificultad que elijas." -#: Source/DiabloUI/selgame.cpp:215 +#: Source/DiabloUI/multi/selgame.cpp:218 msgid "" -"Create a new public game that anyone can join with a difficulty setting of your " -"choice." +"Create a new public game that anyone can join with a difficulty setting of " +"your choice." msgstr "" -"Crear un nuevo juego público donde cualquiera puede unirse con la dificultad que " -"elijas." +"Crear un nuevo juego público donde cualquiera puede unirse con la dificultad " +"que elijas." -#: Source/DiabloUI/selgame.cpp:219 +#: Source/DiabloUI/multi/selgame.cpp:222 msgid "Enter Game ID to join a game already in progress." msgstr "Introduzca un ID de juego para unirse a un juego en curso." -#: Source/DiabloUI/selgame.cpp:221 +#: Source/DiabloUI/multi/selgame.cpp:224 msgid "Enter an IP or a hostname to join a game already in progress." -msgstr "Introduzca una IP o un nombre de equipo para unirse a un juego en curso." +msgstr "" +"Introduzca una IP o un nombre de equipo para unirse a un juego en curso." -#: Source/DiabloUI/selgame.cpp:226 +#: Source/DiabloUI/multi/selgame.cpp:229 msgid "Join the public game already in progress." msgstr "Unirse a un juego público que ya esté en curso." -#: Source/DiabloUI/selgame.cpp:232 Source/DiabloUI/selgame.cpp:326 -#: Source/DiabloUI/selgame.cpp:387 Source/DiabloUI/selgame.cpp:493 -#: Source/DiabloUI/selgame.cpp:513 Source/automap.cpp:760 -#: Source/discord/discord.cpp:101 +#: Source/DiabloUI/multi/selgame.cpp:235 Source/DiabloUI/multi/selgame.cpp:329 +#: Source/DiabloUI/multi/selgame.cpp:390 Source/DiabloUI/multi/selgame.cpp:496 +#: Source/DiabloUI/multi/selgame.cpp:516 Source/automap.cpp:1459 +#: Source/discord/discord.cpp:106 msgid "Normal" msgstr "Normal" -#: Source/DiabloUI/selgame.cpp:235 Source/DiabloUI/selgame.cpp:327 -#: Source/DiabloUI/selgame.cpp:391 Source/automap.cpp:763 -#: Source/discord/discord.cpp:101 +#: Source/DiabloUI/multi/selgame.cpp:238 Source/DiabloUI/multi/selgame.cpp:330 +#: Source/DiabloUI/multi/selgame.cpp:394 Source/automap.cpp:1462 +#: Source/discord/discord.cpp:106 msgid "Nightmare" msgstr "Pesadilla" -#: Source/DiabloUI/selgame.cpp:238 Source/DiabloUI/selgame.cpp:328 -#: Source/DiabloUI/selgame.cpp:395 Source/automap.cpp:766 -#: Source/discord/discord.cpp:67 Source/discord/discord.cpp:101 +#: Source/DiabloUI/multi/selgame.cpp:241 Source/DiabloUI/multi/selgame.cpp:331 +#: Source/DiabloUI/multi/selgame.cpp:398 Source/automap.cpp:1465 +#: Source/discord/discord.cpp:73 Source/discord/discord.cpp:106 msgid "Hell" msgstr "Infierno" #. TRANSLATORS: {:s} means: Game Difficulty. -#: Source/DiabloUI/selgame.cpp:241 Source/automap.cpp:770 +#: Source/DiabloUI/multi/selgame.cpp:244 Source/automap.cpp:1469 +#, c++-format msgid "Difficulty: {:s}" msgstr "Dificultad {:s}" -#: Source/DiabloUI/selgame.cpp:245 Source/gamemenu.cpp:165 +#: Source/DiabloUI/multi/selgame.cpp:248 Source/gamemenu.cpp:170 msgid "Speed: Normal" msgstr "Velocidad: Normal" -#: Source/DiabloUI/selgame.cpp:248 Source/gamemenu.cpp:163 +#: Source/DiabloUI/multi/selgame.cpp:251 Source/gamemenu.cpp:168 msgid "Speed: Fast" msgstr "Velocidad: Rápido" -#: Source/DiabloUI/selgame.cpp:251 Source/gamemenu.cpp:161 +#: Source/DiabloUI/multi/selgame.cpp:254 Source/gamemenu.cpp:166 msgid "Speed: Faster" msgstr "Velocidad: Más rápido" -#: Source/DiabloUI/selgame.cpp:254 Source/gamemenu.cpp:159 +#: Source/DiabloUI/multi/selgame.cpp:257 Source/gamemenu.cpp:164 msgid "Speed: Fastest" msgstr "Velocidad: Lo más rápido" -#: Source/DiabloUI/selgame.cpp:262 +#: Source/DiabloUI/multi/selgame.cpp:265 msgid "Players: " msgstr "Jugadores: " -#: Source/DiabloUI/selgame.cpp:324 +#: Source/DiabloUI/multi/selgame.cpp:327 msgid "Select Difficulty" msgstr "Seleccione dificultad" -#: Source/DiabloUI/selgame.cpp:342 +#: Source/DiabloUI/multi/selgame.cpp:345 +#, c++-format msgid "Join {:s} Games" msgstr "Unirse a los juegos {:s}" -#: Source/DiabloUI/selgame.cpp:347 +#: Source/DiabloUI/multi/selgame.cpp:350 msgid "Enter Game ID" msgstr "Introduzca su ID de juego" -#: Source/DiabloUI/selgame.cpp:349 +#: Source/DiabloUI/multi/selgame.cpp:352 msgid "Enter address" msgstr "Introduzca la dirección" -#: Source/DiabloUI/selgame.cpp:388 +#: Source/DiabloUI/multi/selgame.cpp:391 msgid "" "Normal Difficulty\n" "This is where a starting character should begin the quest to defeat Diablo." @@ -575,60 +717,60 @@ msgstr "" "Aquí es donde un personaje inicial debe comenzar la búsqueda para derrotar a " "Diablo." -#: Source/DiabloUI/selgame.cpp:392 +#: Source/DiabloUI/multi/selgame.cpp:395 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." +"The denizens of the Labyrinth have been bolstered and will prove to be a " +"greater challenge. This is recommended for experienced characters only." msgstr "" "Dificultad de pesadilla\n" "Los habitantes del Laberinto se han reforzado y demostrarán ser un desafío " "mayor. Esto se recomienda solo para personajes experimentados." -#: Source/DiabloUI/selgame.cpp:396 +#: Source/DiabloUI/multi/selgame.cpp:399 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." +"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 "" "Dificultad del infierno\n" "La más poderosa de las criaturas del inframundo acecha en la entrada al " -"infierno. Solo los personajes más experimentados deberían aventurarse en este " -"reino." +"infierno. Solo los personajes más experimentados deberían aventurarse en " +"este reino." -#: Source/DiabloUI/selgame.cpp:411 +#: Source/DiabloUI/multi/selgame.cpp:414 msgid "" -"Your character must reach level 20 before you can enter a multiplayer game of " -"Nightmare difficulty." +"Your character must reach level 20 before you can enter a multiplayer game " +"of Nightmare difficulty." msgstr "" -"Su personaje debe alcanzar el nivel 20 antes de que pueda ingresar a un juego " -"multi jugador de dificultad Pesadilla." +"Su personaje debe alcanzar el nivel 20 antes de que pueda ingresar a un " +"juego multi jugador de dificultad Pesadilla." -#: Source/DiabloUI/selgame.cpp:413 +#: Source/DiabloUI/multi/selgame.cpp:416 msgid "" -"Your character must reach level 30 before you can enter a multiplayer game of " -"Hell difficulty." +"Your character must reach level 30 before you can enter a multiplayer game " +"of Hell difficulty." msgstr "" -"Su personaje debe alcanzar el nivel 30 antes de que pueda ingresar a una partida " -"multi jugador de dificultad infernal." +"Su personaje debe alcanzar el nivel 30 antes de que pueda ingresar a una " +"partida multi jugador de dificultad infernal." -#: Source/DiabloUI/selgame.cpp:491 +#: Source/DiabloUI/multi/selgame.cpp:494 msgid "Select Game Speed" msgstr "Seleccionar velocidad de juego" -#: Source/DiabloUI/selgame.cpp:494 Source/DiabloUI/selgame.cpp:517 +#: Source/DiabloUI/multi/selgame.cpp:497 Source/DiabloUI/multi/selgame.cpp:520 msgid "Fast" msgstr "Rápido" -#: Source/DiabloUI/selgame.cpp:495 Source/DiabloUI/selgame.cpp:521 +#: Source/DiabloUI/multi/selgame.cpp:498 Source/DiabloUI/multi/selgame.cpp:524 msgid "Faster" msgstr "Más rápido" -#: Source/DiabloUI/selgame.cpp:496 Source/DiabloUI/selgame.cpp:525 +#: Source/DiabloUI/multi/selgame.cpp:499 Source/DiabloUI/multi/selgame.cpp:528 msgid "Fastest" msgstr "Lo más rápido" -#: Source/DiabloUI/selgame.cpp:514 +#: Source/DiabloUI/multi/selgame.cpp:517 msgid "" "Normal Speed\n" "This is where a starting character should begin the quest to defeat Diablo." @@ -637,27 +779,27 @@ msgstr "" "Aquí es donde un personaje inicial debe comenzar la búsqueda para derrotar a " "Diablo." -#: Source/DiabloUI/selgame.cpp:518 +#: Source/DiabloUI/multi/selgame.cpp:521 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." +"The denizens of the Labyrinth have been hastened and will prove to be a " +"greater challenge. This is recommended for experienced characters only." msgstr "" "Velocidad Rápida\n" "Los habitantes del Laberinto se han acelerado y demostrarán ser un desafío " "mayor. Esto se recomienda solo para personajes experimentados." -#: Source/DiabloUI/selgame.cpp:522 +#: Source/DiabloUI/multi/selgame.cpp:525 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." +"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 "" "Velocidad más rápida\n" "La mayoría de los monstruos de la mazmorra te buscarán más rápido que nunca. " "Solo un campeón experimentado debería probar suerte a esta velocidad." -#: Source/DiabloUI/selgame.cpp:526 +#: Source/DiabloUI/multi/selgame.cpp:529 msgid "" "Fastest Speed\n" "The minions of the underworld will rush to attack without hesitation. Only a " @@ -667,140 +809,10 @@ msgstr "" "Los esbirros del inframundo se apresurarán a atacar sin dudarlo. Solo un " "verdadero demonio de la velocidad debería entrar a este ritmo." -#: Source/DiabloUI/selgame.cpp:570 Source/DiabloUI/selgame.cpp:575 +#: Source/DiabloUI/multi/selgame.cpp:573 Source/DiabloUI/multi/selgame.cpp:578 msgid "Enter Password" msgstr "Introduzca la contraseña" -#: Source/DiabloUI/selhero.cpp:148 -msgid "Choose Class" -msgstr "Elija Clase" - -#: Source/DiabloUI/selhero.cpp:152 Source/panels/charpanel.cpp:26 -msgid "Warrior" -msgstr "Guerrero" - -#: Source/DiabloUI/selhero.cpp:153 Source/panels/charpanel.cpp:27 -msgid "Rogue" -msgstr "Arpía" - -#: Source/DiabloUI/selhero.cpp:154 Source/panels/charpanel.cpp:28 -msgid "Sorcerer" -msgstr "Hechicero" - -#: Source/DiabloUI/selhero.cpp:156 Source/panels/charpanel.cpp:29 -msgid "Monk" -msgstr "Monje" - -#: Source/DiabloUI/selhero.cpp:159 Source/panels/charpanel.cpp:30 -msgid "Bard" -msgstr "Bardo" - -#: Source/DiabloUI/selhero.cpp:162 Source/panels/charpanel.cpp:31 -msgid "Barbarian" -msgstr "Bárbaro" - -#: Source/DiabloUI/selhero.cpp:179 Source/DiabloUI/selhero.cpp:268 -msgid "New Multi Player Hero" -msgstr "Nuevo Héroe Multijugador" - -#: Source/DiabloUI/selhero.cpp:179 Source/DiabloUI/selhero.cpp:268 -msgid "New Single Player Hero" -msgstr "Nuevo Héroe para un Jugador" - -#: Source/DiabloUI/selhero.cpp:187 -msgid "Save File Exists" -msgstr "Existe juego guardado" - -#: Source/DiabloUI/selhero.cpp:190 Source/gamemenu.cpp:40 -msgid "Load Game" -msgstr "Cargar" - -#: Source/DiabloUI/selhero.cpp:191 Source/gamemenu.cpp:39 Source/gamemenu.cpp:50 -#: Source/multi.cpp:779 -msgid "New Game" -msgstr "Nueva Partida" - -#: Source/DiabloUI/selhero.cpp:201 Source/DiabloUI/selhero.cpp:534 -msgid "Single Player Characters" -msgstr "Personajes de un Jugador" - -#: Source/DiabloUI/selhero.cpp:260 -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 "" -"El Ladrón y el Hechicero solo están disponibles en la versión comercial completa " -"de Diablo. Visite https://www.gog.com/game/diablo para comprar." - -#: Source/DiabloUI/selhero.cpp:274 Source/DiabloUI/selhero.cpp:277 -msgid "Enter Name" -msgstr "Introduzca nombre" - -#: Source/DiabloUI/selhero.cpp:306 -msgid "" -"Invalid name. A name cannot contain spaces, reserved characters, or reserved " -"words.\n" -msgstr "" -"Nombre inválido. Un nombre no puede contener espacios, caracteres reservados ni " -"palabras reservadas.\n" - -#. TRANSLATORS: Error Message -#: Source/DiabloUI/selhero.cpp:313 -msgid "Unable to create character." -msgstr "Imposible crear personaje." - -#: Source/DiabloUI/selhero.cpp:479 -msgid "Level:" -msgstr "Nivel:" - -#: Source/DiabloUI/selhero.cpp:483 -msgid "Strength:" -msgstr "Fuerza:" - -#: Source/DiabloUI/selhero.cpp:483 -msgid "Magic:" -msgstr "Magia:" - -#: Source/DiabloUI/selhero.cpp:483 -msgid "Dexterity:" -msgstr "Destreza:" - -#: Source/DiabloUI/selhero.cpp:483 -msgid "Vitality:" -msgstr "Vitalidad:" - -#: Source/DiabloUI/selhero.cpp:485 -msgid "Savegame:" -msgstr "Juego guardado:" - -#: Source/DiabloUI/selhero.cpp:504 -msgid "Select Hero" -msgstr "Elije Héroe" - -#: Source/DiabloUI/selhero.cpp:512 -msgid "New Hero" -msgstr "Nuevo Héroe" - -#: Source/DiabloUI/selhero.cpp:523 -msgid "Delete" -msgstr "Eliminar" - -#: Source/DiabloUI/selhero.cpp:532 -msgid "Multi Player Characters" -msgstr "Personajes Multi Jugador" - -#: Source/DiabloUI/selhero.cpp:583 -msgid "Delete Multi Player Hero" -msgstr "Eliminar Héroe Multijugador" - -#: Source/DiabloUI/selhero.cpp:585 -msgid "Delete Single Player Hero" -msgstr "Eliminar Héroe" - -#: Source/DiabloUI/selhero.cpp:587 -msgid "Are you sure you want to delete the character \"{:s}\"?" -msgstr "¿Está seguro de que desea borrar el personaje \"{:s}\"?" - #: Source/DiabloUI/selstart.cpp:41 msgid "Enter Hellfire" msgstr "Entrar a Hellfire" @@ -809,63 +821,63 @@ msgstr "Entrar a Hellfire" msgid "Switch to Diablo" msgstr "Cambiar a Diablo" -#: Source/DiabloUI/selyesno.cpp:57 Source/stores.cpp:1029 +#: Source/DiabloUI/selyesno.cpp:57 Source/stores.cpp:961 msgid "Yes" msgstr "Si" -#: Source/DiabloUI/selyesno.cpp:58 Source/stores.cpp:1030 +#: Source/DiabloUI/selyesno.cpp:58 Source/stores.cpp:962 msgid "No" msgstr "No" -#: Source/DiabloUI/settingsmenu.cpp:145 +#: Source/DiabloUI/settingsmenu.cpp:146 msgid "Press gamepad buttons to change." msgstr "Presiona los botones del gamepad para cambiar." -#: Source/DiabloUI/settingsmenu.cpp:421 +#: Source/DiabloUI/settingsmenu.cpp:422 msgid "Bound key:" msgstr "Enlazar tecla:" -#: Source/DiabloUI/settingsmenu.cpp:457 +#: Source/DiabloUI/settingsmenu.cpp:458 msgid "Press any key to change." msgstr "Presiona una tecla para cambiar." -#: Source/DiabloUI/settingsmenu.cpp:459 +#: Source/DiabloUI/settingsmenu.cpp:460 msgid "Unbind key" msgstr "Liberar tecla" -#: Source/DiabloUI/settingsmenu.cpp:463 +#: Source/DiabloUI/settingsmenu.cpp:464 msgid "Bound button combo:" msgstr "Agrupar combinación de botones:" -#: Source/DiabloUI/settingsmenu.cpp:472 +#: Source/DiabloUI/settingsmenu.cpp:473 msgid "Unbind button combo" msgstr "Desagrupar combinación de botones" -#: Source/DiabloUI/settingsmenu.cpp:516 Source/gamemenu.cpp:63 +#: Source/DiabloUI/settingsmenu.cpp:517 Source/gamemenu.cpp:68 msgid "Previous Menu" msgstr "Menú Anterior" #: Source/DiabloUI/support_lines.cpp:8 msgid "" -"We maintain a chat server at Discord.gg/devilutionx Follow the links to join our " -"community where we talk about things related to Diablo, and the Hellfire " +"We maintain a chat server at Discord.gg/devilutionx Follow the links to join " +"our community where we talk about things related to Diablo, and the Hellfire " "expansion." msgstr "" -"Mantenemos un servidor en Discord.gg/devilutionx Sigue los enlaces para unirte a " -"nuestra comunidad donde hablamos de cosas relacionadas con Diablo, y la " -"expansión Hellfire." +"Mantenemos un servidor en Discord.gg/devilutionx Sigue los enlaces para " +"unirte a nuestra comunidad donde hablamos de cosas relacionadas con Diablo, " +"y la expansión 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." +"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 es mantenido por Diasurgical, los problemas y fallos pueden ser " -"reportados en esta dirección: https://github.com/diasurgical/devilutionX Para " -"ayudarnos a servirle mejor, por favor asegúrese de incluir el número de versión, " -"el sistema operativo y la naturaleza del problema." +"reportados en esta dirección: https://github.com/diasurgical/devilutionX " +"Para ayudarnos a servirle mejor, por favor asegúrese de incluir el número de " +"versión, el sistema operativo y la naturaleza del problema." #: Source/DiabloUI/support_lines.cpp:13 msgid "Disclaimer:" @@ -873,30 +885,31 @@ msgstr "Descargo de responsabilidad:" #: 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." +"\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 no está soportado ni mantenido por Blizzard Entertainment, ni por " -"GOG.com. Ni Blizzard Entertainment ni GOG.com han probado o certificado la " -"calidad o compatibilidad de DevilutionX. Todas las preguntas relacionadas con " -"DevilutionX deben dirigirse a Diasurgical, no a Blizzard Entertainment ni a GOG." -"com." +"\tDevilutionX no está soportado ni mantenido por Blizzard Entertainment, ni " +"por GOG.com. Ni Blizzard Entertainment ni GOG.com han probado o certificado " +"la calidad o compatibilidad de DevilutionX. Todas las preguntas relacionadas " +"con DevilutionX deben dirigirse a Diasurgical, no a Blizzard Entertainment " +"ni a 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." +"\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 "" -"\tEsta adaptación hace uso de Charis SIL, New Athena Unicode, Unifont, y Noto " -"que están licenciados bajo la SIL Open Font License, así como Twitmoji que está " -"licenciado bajo CC-BY 4.0. También hace uso de SDL que está licenciado bajo la " -"licencia zlib. Para más detalles, consulte el Léame." +"\tEsta adaptación hace uso de Charis SIL, New Athena Unicode, Unifont, y " +"Noto que están licenciados bajo la SIL Open Font License, así como Twitmoji " +"que está licenciado bajo CC-BY 4.0. También hace uso de SDL que está " +"licenciado bajo la licencia zlib. Para más detalles, consulte el Léame." -#: Source/DiabloUI/title.cpp:57 +#: Source/DiabloUI/title.cpp:59 msgid "Copyright © 1996-2001 Blizzard Entertainment" msgstr "Copyright © 1996-2001 Blizzard Entertainment" @@ -906,6 +919,7 @@ msgstr "Error" #. TRANSLATORS: Error message that displays relevant information for bug report #: Source/appfat.cpp:67 +#, c++-format msgid "" "{:s}\n" "\n" @@ -916,6 +930,7 @@ msgstr "" "El error ocurrió en: {:s} línea {:d}" #: Source/appfat.cpp:76 +#, c++-format msgid "" "Unable to open main data archive ({:s}).\n" "\n" @@ -931,6 +946,7 @@ msgstr "Error de Archivo de Datos" #. TRANSLATORS: Error when Program is not allowed to write data #: Source/appfat.cpp:87 +#, c++-format msgid "" "Unable to write to location:\n" "{:s}" @@ -942,91 +958,98 @@ msgstr "" msgid "Read-Only Directory Error" msgstr "Error de Directorio de Solo Lectura" -#: Source/automap.cpp:717 +#: Source/automap.cpp:1414 msgid "Game: " msgstr "Juego: " -#: Source/automap.cpp:725 +#: Source/automap.cpp:1422 +msgid "Offline Game" +msgstr "Juego sin conexión" + +#: Source/automap.cpp:1424 msgid "Password: " msgstr "Contraseña: " -#: Source/automap.cpp:728 +#: Source/automap.cpp:1427 msgid "Public Game" msgstr "Juego público" -#: Source/automap.cpp:742 +#: Source/automap.cpp:1441 +#, c++-format msgid "Level: Nest {:d}" msgstr "Nivel: Guarida {:d}" -#: Source/automap.cpp:745 +#: Source/automap.cpp:1444 +#, c++-format msgid "Level: Crypt {:d}" msgstr "Nivel: Cripta {:d}" -#: Source/automap.cpp:748 Source/discord/discord.cpp:67 Source/objects.cpp:151 +#: Source/automap.cpp:1447 Source/discord/discord.cpp:73 Source/objects.cpp:153 msgid "Town" msgstr "Pueblo" -#: Source/automap.cpp:751 +#: Source/automap.cpp:1450 +#, c++-format msgid "Level: {:d}" msgstr "Nivel: {:d}" -#: Source/control.cpp:158 +#: Source/control.cpp:174 msgid "Tab" msgstr "Tab" -#: Source/control.cpp:158 +#: Source/control.cpp:174 msgid "Esc" msgstr "Esc" -#: Source/control.cpp:158 +#: Source/control.cpp:174 msgid "Enter" msgstr "Intro" -#: Source/control.cpp:161 +#: Source/control.cpp:177 msgid "Character Information" msgstr "Información del Personaje" -#: Source/control.cpp:162 +#: Source/control.cpp:178 msgid "Quests log" msgstr "Registro de Misiones" -#: Source/control.cpp:163 +#: Source/control.cpp:179 msgid "Automap" msgstr "Automapa" -#: Source/control.cpp:164 +#: Source/control.cpp:180 msgid "Main Menu" msgstr "Menú Principal" -#: Source/control.cpp:165 Source/diablo.cpp:1690 Source/diablo.cpp:2008 +#: Source/control.cpp:181 Source/diablo.cpp:1735 Source/diablo.cpp:2068 msgid "Inventory" msgstr "Inventario" -#: Source/control.cpp:166 +#: Source/control.cpp:182 msgid "Spell book" msgstr "Libro de Hechizos" -#: Source/control.cpp:167 +#: Source/control.cpp:183 msgid "Send Message" msgstr "Enviar Mensaje" -#: Source/control.cpp:326 +#: Source/control.cpp:369 msgid "Available Commands:" msgstr "Comandos Disponibles:" -#: Source/control.cpp:334 +#: Source/control.cpp:377 Source/control.cpp:567 msgid "Command " msgstr "Comando " -#: Source/control.cpp:334 -msgid " is unkown." +#: Source/control.cpp:377 Source/control.cpp:567 +msgid " is unknown." msgstr " es desconocido." -#: Source/control.cpp:337 Source/control.cpp:338 +#: Source/control.cpp:380 Source/control.cpp:381 msgid "Description: " msgstr "Descripción: " -#: Source/control.cpp:337 +#: Source/control.cpp:380 msgid "" "\n" "Parameters: No additional parameter needed." @@ -1034,7 +1057,7 @@ msgstr "" "\n" "Parametros No se necesitan parámetros adicionales." -#: Source/control.cpp:338 +#: Source/control.cpp:381 msgid "" "\n" "Parameters: " @@ -1042,268 +1065,371 @@ msgstr "" "\n" "Parametros: " -#: Source/control.cpp:358 +#: Source/control.cpp:401 Source/control.cpp:433 msgid "Arenas are only supported in multiplayer." msgstr "Las Arenas solo están soportadas en multijugador." -#: Source/control.cpp:363 +#: Source/control.cpp:406 msgid "What arena do you want to visit?" msgstr "¿Cuál arena quieres visitar?" -#: Source/control.cpp:371 +#: Source/control.cpp:414 msgid "Invalid arena-number. Valid numbers are:" msgstr "Número de Arena inválido. Los números válidos son:" -#: Source/control.cpp:377 +#: Source/control.cpp:420 msgid "To enter a arena, you need to be in town or another arena." msgstr "Para entrar a una arena debes estar en el pueblo o en otra arena." -#: Source/control.cpp:387 -msgid "/help" -msgstr "/ayuda" +#: Source/control.cpp:458 +msgid "Inspecting only supported in multiplayer." +msgstr "Inspección solo está soportado en multijugador." + +#: Source/control.cpp:463 Source/control.cpp:754 +msgid "Stopped inspecting players." +msgstr "Parar jugadores de inspectores." + +#: Source/control.cpp:478 +msgid "No players found with such a name" +msgstr "No se encontraron jugadoras con tal nombre" -#: Source/control.cpp:387 +#: Source/control.cpp:484 +msgid "Inspecting player: " +msgstr "Jugador inspector: " + +#: Source/control.cpp:553 msgid "Prints help overview or help for a specific command." msgstr "" -"Imprime una descripción general de la ayuda o ayuda para un comando específico." - -#: Source/control.cpp:387 -msgid "({command})" -msgstr "({command})" +"Imprime una descripción general de la ayuda o ayuda para un comando " +"específico." -#: Source/control.cpp:388 -msgid "/arena" -msgstr "/arena" +#: Source/control.cpp:553 +msgid "[command]" +msgstr "[comando]" -#: Source/control.cpp:388 +#: Source/control.cpp:554 msgid "Enter a PvP Arena." msgstr "Enttrar a una Arena PvP." -#: Source/control.cpp:388 -msgid "{arena-number}" -msgstr "{arena-number}" +#: Source/control.cpp:554 +msgid "" +msgstr "{arena-número}" + +#: Source/control.cpp:555 +msgid "Gives Arena Potions." +msgstr "Da pociones de arena." + +#: Source/control.cpp:555 +msgid "" +msgstr "" -#: Source/control.cpp:398 -msgid "Command \"" -msgstr "Comando \"" +#: Source/control.cpp:556 +msgid "Inspects stats and equipment of another player." +msgstr "Inspecciona las estadísticas y el equipamiento de otro jugador." -#: Source/control.cpp:826 +#: Source/control.cpp:556 +msgid "" +msgstr "" + +#: Source/control.cpp:557 +msgid "Show seed infos for current level." +msgstr "Mostrar información inicial para el nivel actual." + +#: Source/control.cpp:1049 msgid "Player friendly" msgstr "Jugador amigable" -#: Source/control.cpp:828 +#: Source/control.cpp:1051 msgid "Player attack" msgstr "Ataque del jugador" -#: Source/control.cpp:831 +#: Source/control.cpp:1054 +#, c++-format msgid "Hotkey: {:s}" msgstr "Tecla de acceso rápido: {:s}" -#: Source/control.cpp:838 +#: Source/control.cpp:1061 msgid "Select current spell button" msgstr "Seleccionar botón de hechizo actual" -#: Source/control.cpp:841 +#: Source/control.cpp:1064 msgid "Hotkey: 's'" msgstr "Tecla de acceso rápido: 's'" -#: Source/control.cpp:847 Source/panels/spell_list.cpp:147 +#: Source/control.cpp:1070 Source/panels/spell_list.cpp:152 +#, c++-format msgid "{:s} Skill" msgstr "Habilidad {:s}" -#: Source/control.cpp:850 Source/panels/spell_list.cpp:154 +#: Source/control.cpp:1073 Source/panels/spell_list.cpp:159 +#, c++-format msgid "{:s} Spell" msgstr "Hechizo {:s}" -#: Source/control.cpp:852 Source/panels/spell_list.cpp:159 +#: Source/control.cpp:1075 Source/panels/spell_list.cpp:164 msgid "Spell Level 0 - Unusable" msgstr "Nivel 0 - Inutilizable" -#: Source/control.cpp:852 Source/panels/spell_list.cpp:161 +#: Source/control.cpp:1075 Source/panels/spell_list.cpp:166 +#, c++-format msgid "Spell Level {:d}" msgstr "Nivel {:d}" -#: Source/control.cpp:855 Source/panels/spell_list.cpp:168 +#: Source/control.cpp:1078 Source/panels/spell_list.cpp:173 +#, c++-format msgid "Scroll of {:s}" msgstr "Pergamino de {:s}" -#: Source/control.cpp:860 Source/panels/spell_list.cpp:173 +#: Source/control.cpp:1082 Source/panels/spell_list.cpp:177 +#, c++-format msgid "{:d} Scroll" msgid_plural "{:d} Scrolls" msgstr[0] "{:d} Pergamino" msgstr[1] "{:d} Pergaminos" -#: Source/control.cpp:863 Source/panels/spell_list.cpp:180 +#: Source/control.cpp:1085 Source/panels/spell_list.cpp:184 +#, c++-format msgid "Staff of {:s}" msgstr "Bastón de {:s}" -#: Source/control.cpp:864 Source/panels/spell_list.cpp:182 +#: Source/control.cpp:1086 Source/panels/spell_list.cpp:186 +#, c++-format msgid "{:d} Charge" msgid_plural "{:d} Charges" msgstr[0] "{:d} Carga" msgstr[1] "{:d} Cargas" -#: Source/control.cpp:992 Source/inv.cpp:1924 Source/items.cpp:3367 +#: Source/control.cpp:1205 Source/inv.cpp:1873 Source/items.cpp:3607 +#, c++-format msgid "{:s} gold piece" msgid_plural "{:s} gold pieces" msgstr[0] "{:s} pieza de oro" msgstr[1] "{:s} piezas de oro" -#: Source/control.cpp:994 +#: Source/control.cpp:1207 msgid "Requirements not met" msgstr "Requisitos no cumplidos" -#: Source/control.cpp:1026 +#: Source/control.cpp:1236 +#, c++-format msgid "{:s}, Level: {:d}" msgstr "{:s}, Nivel: {:d}" -#: Source/control.cpp:1027 +#: Source/control.cpp:1237 +#, c++-format msgid "Hit Points {:d} of {:d}" msgstr "Vida {:d} de {:d}" -#: Source/control.cpp:1061 +#: Source/control.cpp:1268 msgid "Level Up" msgstr "Subir Nivel" #. TRANSLATORS: {:s} is a number with separators. Dialog is shown when splitting a stash of Gold. -#: Source/control.cpp:1169 +#: Source/control.cpp:1381 +#, 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] "Tiene {:s} moneda de oro. ¿Cuanto quiere eliminar?" msgstr[1] "Tiene {:s} monedas de oro. ¿Cuantas quiere eliminar?" -#: Source/cursor.cpp:244 +#: Source/cursor.cpp:641 msgid "Town Portal" msgstr "Portal del pueblo" -#: Source/cursor.cpp:245 +#: Source/cursor.cpp:642 +#, c++-format msgid "from {:s}" msgstr "de {:s}" -#: Source/cursor.cpp:258 +#: Source/cursor.cpp:655 msgid "Portal to" msgstr "Portal a" -#: Source/cursor.cpp:259 +#: Source/cursor.cpp:656 msgid "The Unholy Altar" msgstr "El Altar Impío" -#: Source/cursor.cpp:259 +#: Source/cursor.cpp:656 msgid "level 15" msgstr "nivel 15" -#: Source/diablo.cpp:123 -msgid "I need help! Come Here!" +#. TRANSLATORS: Error message when a data file is missing or corrupt. Arguments are {file name} +#: Source/data/file.cpp:45 +#, c++-format +msgid "Unable to load data from file {0}" +msgstr "No se pueden cargar datos del archivo {0}" + +#. TRANSLATORS: Error message when a data file is empty or only contains the header row. Arguments are {file name} +#: Source/data/file.cpp:50 +#, c++-format +msgid "{0} is incomplete, please check the file contents." +msgstr "{0} está incompleto, verifique el contenido del archivo." + +#. TRANSLATORS: Error message when a data file doesn't contain the expected columns. Arguments are {file name} +#: Source/data/file.cpp:55 +#, c++-format +msgid "" +"Your {0} file doesn't have the expected columns, please make sure it matches " +"the documented format." +msgstr "" +"Su archivo {0} no tiene las columnas esperadas; asegúrese de que coincida " +"con el formato documentado." + +#. TRANSLATORS: Error message when parsing a data file and a text value is encountered when a number is expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} +#: Source/data/file.cpp:70 +#, c++-format +msgid "Non-numeric value {0} for {1} in {2} at row {3} and column {4}" +msgstr "Valor no numérico {0} para {1} en {2} en la fila {3} y la columna {4}" + +#. TRANSLATORS: Error message when parsing a data file and we find a number larger than expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} +#: Source/data/file.cpp:76 +#, c++-format +msgid "Out of range value {0} for {1} in {2} at row {3} and column {4}" +msgstr "" +"Valor fuera de rango {0} para {1} en {2} en la fila {3} y la columna {4}" + +#. TRANSLATORS: Error message when we find an unrecognised value in a key column. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} +#: Source/data/file.cpp:82 +#, c++-format +msgid "Invalid value {0} for {1} in {2} at row {3} and column {4}" +msgstr "Valor no válido {0} para {1} en {2} en la fila {3} y la columna {4}" + +#: Source/diablo.cpp:132 +msgid "I need help! Come here!" msgstr "¡Necesito ayuda! ¡Ven aquí!" -#: Source/diablo.cpp:124 +#: Source/diablo.cpp:133 msgid "Follow me." msgstr "Sígueme." -#: Source/diablo.cpp:125 +#: Source/diablo.cpp:134 msgid "Here's something for you." msgstr "Aquí hay algo para ti." -#: Source/diablo.cpp:126 +#: Source/diablo.cpp:135 msgid "Now you DIE!" msgstr "¡Ahora MUERES!" +#: Source/diablo.cpp:136 +msgid "Heal yourself!" +msgstr "¡Cúrate a ti mismo!" + +#: Source/diablo.cpp:137 +msgid "Watch out!" +msgstr "¡Cuidado!" + +#: Source/diablo.cpp:138 +msgid "Thanks." +msgstr "Gracias ." + +#: Source/diablo.cpp:139 +msgid "Retreat!" +msgstr "¡Retírate!" + +#: Source/diablo.cpp:140 +msgid "Sorry." +msgstr "Lo siento." + +#: Source/diablo.cpp:141 +msgid "I'm waiting." +msgstr "Estpy esperando." + #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:905 +#: Source/diablo.cpp:912 msgid "Print this message and exit" msgstr "Imprime este mensaje y sal" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:906 +#: Source/diablo.cpp:913 msgid "Print the version and exit" msgstr "Imprime la versión y sal" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:907 +#: Source/diablo.cpp:914 msgid "Specify the folder of diabdat.mpq" msgstr "Especifique la carpeta de diabdat.mpq" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:908 +#: Source/diablo.cpp:915 msgid "Specify the folder of save files" msgstr "Especifique la carpeta de archivos guardados" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:909 +#: Source/diablo.cpp:916 msgid "Specify the location of diablo.ini" msgstr "Especifique la ubicación de diablo.ini" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:910 +#: Source/diablo.cpp:917 msgid "Specify the language code (e.g. en or pt_BR)" msgstr "Especifique el código de idioma (ej. en ó pt_BR)" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:911 +#: Source/diablo.cpp:918 msgid "Skip startup videos" msgstr "Omitir videos de inicio" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:912 +#: Source/diablo.cpp:919 msgid "Display frames per second" msgstr "Mostrar fotogramas por segundo" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:913 +#: Source/diablo.cpp:920 msgid "Enable verbose logging" msgstr "Habilitar el registro detallado" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:915 +#: Source/diablo.cpp:922 msgid "Record a demo file" msgstr "Grabar un archivo de demostración" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:916 +#: Source/diablo.cpp:923 msgid "Play a demo file" msgstr "Jugar un archivo de demostración" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:917 +#: Source/diablo.cpp:924 msgid "Disable all frame limiting during demo playback" msgstr "Desactivar la limitación de FPS durante la reproducción de la demo" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:920 +#: Source/diablo.cpp:927 msgid "Game selection:" msgstr "Selección de juego:" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:922 +#: Source/diablo.cpp:929 msgid "Force Shareware mode" msgstr "Forzar modo shareware" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:923 +#: Source/diablo.cpp:930 msgid "Force Diablo mode" msgstr "Forzar modo Diablo" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:924 +#: Source/diablo.cpp:931 msgid "Force Hellfire mode" msgstr "Forzar modo Hellfire" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:925 +#: Source/diablo.cpp:932 msgid "Hellfire options:" msgstr "Opciones de Hellfire:" -#: Source/diablo.cpp:935 +#: Source/diablo.cpp:942 msgid "Report bugs at https://github.com/diasurgical/devilutionX/" msgstr "Reportar errores a https://github.com/diasurgical/devilutionX/" -#: Source/diablo.cpp:1092 +#: Source/diablo.cpp:1113 msgid "Please update devilutionx.mpq and fonts.mpq to the latest version" msgstr "" "Por favor a\n" "ctualice devilutionx.mpq y fonts.mpq a la última versión" -#: Source/diablo.cpp:1094 +#: Source/diablo.cpp:1115 msgid "" "Failed to load UI resources.\n" "\n" @@ -1314,735 +1440,860 @@ msgstr "" "Asegúrese que Devilutionx.mpq se encuentra en la carpeta del juego y está " "actualizado." -#: Source/diablo.cpp:1098 +#: Source/diablo.cpp:1119 msgid "Please update fonts.mpq to the latest version" msgstr "" "Por favor ac\n" "tualice fonts.mpq a la última versión" -#: Source/diablo.cpp:1409 +#: Source/diablo.cpp:1437 msgid "-- Network timeout --" msgstr "-- Tiempo de espera de la red terminado --" -#: Source/diablo.cpp:1410 +#: Source/diablo.cpp:1438 msgid "-- Waiting for players --" msgstr "-- Esperando jugadores --" -#: Source/diablo.cpp:1433 +#: Source/diablo.cpp:1461 msgid "No help available" msgstr "No hay ayuda disponible" -#: Source/diablo.cpp:1434 +#: Source/diablo.cpp:1462 msgid "while in stores" msgstr "mientras que en las tiendas" -#: Source/diablo.cpp:1576 Source/diablo.cpp:1840 +#: Source/diablo.cpp:1613 Source/diablo.cpp:1900 +#, c++-format msgid "Belt item {}" msgstr "Objeto del cinturón {}" -#: Source/diablo.cpp:1577 Source/diablo.cpp:1841 +#: Source/diablo.cpp:1614 Source/diablo.cpp:1901 msgid "Use Belt item." msgstr "Usar objeto del cinturón." -#: Source/diablo.cpp:1592 Source/diablo.cpp:1856 +#: Source/diablo.cpp:1629 Source/diablo.cpp:1916 +#, c++-format msgid "Quick spell {}" msgstr "Hechizo rápido {}" -#: Source/diablo.cpp:1593 Source/diablo.cpp:1857 +#: Source/diablo.cpp:1630 Source/diablo.cpp:1917 msgid "Hotkey for skill or spell." msgstr "Tecla rápida para habilidad o hechizo." -#: Source/diablo.cpp:1611 Source/diablo.cpp:1984 +#: Source/diablo.cpp:1648 Source/diablo.cpp:2044 msgid "Use health potion" msgstr "Usar poción de curación" -#: Source/diablo.cpp:1612 Source/diablo.cpp:1985 +#: Source/diablo.cpp:1649 Source/diablo.cpp:2045 msgid "Use health potions from belt." msgstr "Usar pociones de salud del cinturón." -#: Source/diablo.cpp:1619 Source/diablo.cpp:1992 +#: Source/diablo.cpp:1656 Source/diablo.cpp:2052 msgid "Use mana potion" msgstr "Usar poción de maná" -#: Source/diablo.cpp:1620 Source/diablo.cpp:1993 +#: Source/diablo.cpp:1657 Source/diablo.cpp:2053 msgid "Use mana potions from belt." msgstr "Usar poción de maná del cinturón." -#: Source/diablo.cpp:1627 Source/diablo.cpp:2038 +#: Source/diablo.cpp:1664 Source/diablo.cpp:2098 msgid "Speedbook" msgstr "Libro rápido" -#: Source/diablo.cpp:1628 Source/diablo.cpp:2039 +#: Source/diablo.cpp:1665 Source/diablo.cpp:2099 msgid "Open Speedbook." msgstr "Abrir libro rápido." -#: Source/diablo.cpp:1635 Source/diablo.cpp:2173 +#: Source/diablo.cpp:1672 Source/diablo.cpp:2231 msgid "Quick save" msgstr "Grabación rápida" -#: Source/diablo.cpp:1636 Source/diablo.cpp:2174 +#: Source/diablo.cpp:1673 Source/diablo.cpp:2232 msgid "Saves the game." msgstr "Grabar el juego." -#: Source/diablo.cpp:1643 Source/diablo.cpp:2181 +#: Source/diablo.cpp:1680 Source/diablo.cpp:2239 msgid "Quick load" msgstr "Carga rápida" -#: Source/diablo.cpp:1644 Source/diablo.cpp:2182 +#: Source/diablo.cpp:1681 Source/diablo.cpp:2240 msgid "Loads the game." msgstr "Cargar el Juego." -#: Source/diablo.cpp:1652 +#: Source/diablo.cpp:1689 msgid "Quit game" msgstr "Salir" -#: Source/diablo.cpp:1653 +#: Source/diablo.cpp:1690 msgid "Closes the game." msgstr "Cerrar el juego." -#: Source/diablo.cpp:1659 +#: Source/diablo.cpp:1696 msgid "Stop hero" msgstr "Detener héroe" -#: Source/diablo.cpp:1660 +#: Source/diablo.cpp:1697 msgid "Stops walking and cancel pending actions." msgstr "Dejar de caminar y cancelar acciones pendientes." -#: Source/diablo.cpp:1667 Source/diablo.cpp:2189 +#: Source/diablo.cpp:1704 Source/diablo.cpp:2247 msgid "Item highlighting" msgstr "Resaltado de elementos" -#: Source/diablo.cpp:1668 Source/diablo.cpp:2190 +#: Source/diablo.cpp:1705 Source/diablo.cpp:2248 msgid "Show/hide items on ground." msgstr "Mostrar/ocultar elementos en el suelo." -#: Source/diablo.cpp:1674 Source/diablo.cpp:2196 +#: Source/diablo.cpp:1711 Source/diablo.cpp:2254 msgid "Toggle item highlighting" msgstr "Alternar resaltado de elementos" -#: Source/diablo.cpp:1675 Source/diablo.cpp:2197 +#: Source/diablo.cpp:1712 Source/diablo.cpp:2255 msgid "Permanent show/hide items on ground." msgstr "Mostrar/ocultar elementos permanentes en el suelo." -#: Source/diablo.cpp:1681 Source/diablo.cpp:2048 +#: Source/diablo.cpp:1718 Source/diablo.cpp:2108 msgid "Toggle automap" msgstr "Alternar mapa automático" -#: Source/diablo.cpp:1682 Source/diablo.cpp:2049 +#: Source/diablo.cpp:1719 Source/diablo.cpp:2109 msgid "Toggles if automap is displayed." msgstr "Alterna si se muestra el mapa automático." -#: Source/diablo.cpp:1691 Source/diablo.cpp:2009 +#: Source/diablo.cpp:1726 +msgid "Cycle map type" +msgstr "Tipo de mapa de ciclo" + +#: Source/diablo.cpp:1727 +msgid "Opaque -> Transparent -> Minimap -> None" +msgstr "Opaco -> Transparente -> Minimapa -> Ninguno" + +#: Source/diablo.cpp:1736 Source/diablo.cpp:2069 msgid "Open Inventory screen." msgstr "Abrir la pantalla de Inventario." -#: Source/diablo.cpp:1698 Source/diablo.cpp:2000 +#: Source/diablo.cpp:1743 Source/diablo.cpp:2060 msgid "Character" msgstr "Personaje" -#: Source/diablo.cpp:1699 Source/diablo.cpp:2001 +#: Source/diablo.cpp:1744 Source/diablo.cpp:2061 msgid "Open Character screen." msgstr "Abrir pantalla de Personaje." -#: Source/diablo.cpp:1706 Source/diablo.cpp:2018 +#: Source/diablo.cpp:1751 Source/diablo.cpp:2078 msgid "Quest log" msgstr "Registro de Misiones" -#: Source/diablo.cpp:1707 Source/diablo.cpp:2019 +#: Source/diablo.cpp:1752 Source/diablo.cpp:2079 msgid "Open Quest log." msgstr "Abrir Registro de Misiones." -#: Source/diablo.cpp:1714 Source/diablo.cpp:2028 +#: Source/diablo.cpp:1759 Source/diablo.cpp:2088 msgid "Spellbook" msgstr "Libro de Hechizos" -#: Source/diablo.cpp:1715 Source/diablo.cpp:2029 +#: Source/diablo.cpp:1760 Source/diablo.cpp:2089 msgid "Open Spellbook." msgstr "Abrir Libro de Hechizos." -#: Source/diablo.cpp:1723 +#: Source/diablo.cpp:1768 +#, c++-format msgid "Quick Message {}" msgstr "Mensaje rápido {}" -#: Source/diablo.cpp:1724 +#: Source/diablo.cpp:1769 msgid "Use Quick Message in chat." msgstr "Usar Mensaje rápido en el chat." -#: Source/diablo.cpp:1733 Source/diablo.cpp:2203 +#: Source/diablo.cpp:1778 Source/diablo.cpp:2261 msgid "Hide Info Screens" msgstr "Ocultar las pantallas de información" -#: Source/diablo.cpp:1734 Source/diablo.cpp:2204 +#: Source/diablo.cpp:1779 Source/diablo.cpp:2262 msgid "Hide all info screens." msgstr "Ocultar todas las pantallas de información." -#: Source/diablo.cpp:1754 Source/diablo.cpp:2224 Source/options.cpp:932 +#: Source/diablo.cpp:1802 Source/diablo.cpp:2285 Source/options.cpp:986 msgid "Zoom" msgstr "Ampliar" -#: Source/diablo.cpp:1755 Source/diablo.cpp:2225 +#: Source/diablo.cpp:1803 Source/diablo.cpp:2286 msgid "Zoom Game Screen." msgstr "Ampliar de la pantalla del juego." -#: Source/diablo.cpp:1765 Source/diablo.cpp:2235 +#: Source/diablo.cpp:1813 Source/diablo.cpp:2296 msgid "Pause Game" msgstr "Pausar Juego" -#: Source/diablo.cpp:1766 Source/diablo.cpp:2236 +#: Source/diablo.cpp:1814 Source/diablo.cpp:1820 Source/diablo.cpp:2297 msgid "Pauses the game." msgstr "Pausar el juego." -#: Source/diablo.cpp:1771 Source/diablo.cpp:2241 +#: Source/diablo.cpp:1819 +msgid "Pause Game (Alternate)" +msgstr "Pausar juego (alternativo)" + +#: Source/diablo.cpp:1825 Source/diablo.cpp:2302 msgid "Decrease Gamma" msgstr "Disminuir Gamma" -#: Source/diablo.cpp:1772 Source/diablo.cpp:2242 +#: Source/diablo.cpp:1826 Source/diablo.cpp:2303 msgid "Reduce screen brightness." msgstr "Reducir el brillo de la pantalla." -#: Source/diablo.cpp:1779 Source/diablo.cpp:2249 +#: Source/diablo.cpp:1833 Source/diablo.cpp:2310 msgid "Increase Gamma" msgstr "Incrementar Gamma" -#: Source/diablo.cpp:1780 Source/diablo.cpp:2250 +#: Source/diablo.cpp:1834 Source/diablo.cpp:2311 msgid "Increase screen brightness." msgstr "Aumentar el brillo de la pantalla." -#: Source/diablo.cpp:1787 Source/diablo.cpp:2257 +#: Source/diablo.cpp:1841 Source/diablo.cpp:2318 msgid "Help" msgstr "Ayuda" -#: Source/diablo.cpp:1788 Source/diablo.cpp:2258 +#: Source/diablo.cpp:1842 Source/diablo.cpp:2319 msgid "Open Help Screen." msgstr "Abrir pantalla de ayuda." -#: Source/diablo.cpp:1795 Source/diablo.cpp:2265 +#: Source/diablo.cpp:1849 Source/diablo.cpp:2326 msgid "Screenshot" msgstr "Captura de pantalla" -#: Source/diablo.cpp:1796 Source/diablo.cpp:2266 +#: Source/diablo.cpp:1850 Source/diablo.cpp:2327 msgid "Takes a screenshot." msgstr "Toma una captura de pantalla." -#: Source/diablo.cpp:1802 Source/diablo.cpp:2272 +#: Source/diablo.cpp:1856 Source/diablo.cpp:2333 msgid "Game info" msgstr "Información del Juego" -#: Source/diablo.cpp:1803 Source/diablo.cpp:2273 +#: Source/diablo.cpp:1857 Source/diablo.cpp:2334 msgid "Displays game infos." msgstr "Mostrar información del juego." #. TRANSLATORS: {:s} means: Character Name, Game Version, Game Difficulty. -#: Source/diablo.cpp:1807 Source/diablo.cpp:2277 +#: Source/diablo.cpp:1861 Source/diablo.cpp:2338 +#, c++-format msgid "{:s} {:s}" msgstr "{:s} {:s}" -#: Source/diablo.cpp:1816 Source/diablo.cpp:2286 +#: Source/diablo.cpp:1870 Source/diablo.cpp:2347 msgid "Chat Log" msgstr "Log del Chat" -#: Source/diablo.cpp:1817 Source/diablo.cpp:2287 +#: Source/diablo.cpp:1871 Source/diablo.cpp:2348 msgid "Displays chat log." msgstr "Mostrar log del Chat." -#: Source/diablo.cpp:1875 +#: Source/diablo.cpp:1879 +msgid "Console" +msgstr "Consola" + +#: Source/diablo.cpp:1880 +msgid "Opens Lua console." +msgstr "Abrir consola de LUA." + +#: Source/diablo.cpp:1935 msgid "Primary action" msgstr "Acción primaria" -#: Source/diablo.cpp:1876 +#: Source/diablo.cpp:1936 msgid "Attack monsters, talk to towners, lift and place inventory items." msgstr "" -"Ataca a los monstruos, habla con los habitantes, levanta y coloca artículos de " -"inventario." +"Ataca a los monstruos, habla con los habitantes, levanta y coloca artículos " +"de inventario." -#: Source/diablo.cpp:1890 +#: Source/diablo.cpp:1950 msgid "Secondary action" msgstr "Ación secundaria" -#: Source/diablo.cpp:1891 +#: Source/diablo.cpp:1951 msgid "Open chests, interact with doors, pick up items." msgstr "Abre cofres, interactúa con las puertas, recoge objetos." -#: Source/diablo.cpp:1905 +#: Source/diablo.cpp:1965 msgid "Spell action" msgstr "Acción de hechizo" -#: Source/diablo.cpp:1906 +#: Source/diablo.cpp:1966 msgid "Cast the active spell." msgstr "Lanzar hechizo activo." -#: Source/diablo.cpp:1920 +#: Source/diablo.cpp:1980 msgid "Cancel action" msgstr "Cancelar acción" -#: Source/diablo.cpp:1921 +#: Source/diablo.cpp:1981 msgid "Close menus." msgstr "Cerrar menús." -#: Source/diablo.cpp:1946 +#: Source/diablo.cpp:2006 msgid "Move up" msgstr "Ascender" -#: Source/diablo.cpp:1947 +#: Source/diablo.cpp:2007 msgid "Moves the player character up." msgstr "Mueve el personaje del jugador hacia arriba." -#: Source/diablo.cpp:1952 +#: Source/diablo.cpp:2012 msgid "Move down" msgstr "Descender" -#: Source/diablo.cpp:1953 +#: Source/diablo.cpp:2013 msgid "Moves the player character down." msgstr "Mueve el personaje del jugador hacia abajo." -#: Source/diablo.cpp:1958 +#: Source/diablo.cpp:2018 msgid "Move left" msgstr "Mover a la izquierda" -#: Source/diablo.cpp:1959 +#: Source/diablo.cpp:2019 msgid "Moves the player character left." msgstr "Mueve el personaje del jugador a la izquierda." -#: Source/diablo.cpp:1964 +#: Source/diablo.cpp:2024 msgid "Move right" msgstr "Mover a la derecha" -#: Source/diablo.cpp:1965 +#: Source/diablo.cpp:2025 msgid "Moves the player character right." msgstr "Mueve el personaje del jugador a la derecha." -#: Source/diablo.cpp:1970 +#: Source/diablo.cpp:2030 msgid "Stand ground" msgstr "Estar en tierra" -#: Source/diablo.cpp:1971 +#: Source/diablo.cpp:2031 msgid "Hold to prevent the player from moving." msgstr "Mantén presionado para evitar que el jugador se mueva." -#: Source/diablo.cpp:1976 +#: Source/diablo.cpp:2036 msgid "Toggle stand ground" msgstr "Alternar mantener en tierra" -#: Source/diablo.cpp:1977 +#: Source/diablo.cpp:2037 msgid "Toggle whether the player moves." msgstr "Alternar mientras el jugador se mueve." -#: Source/diablo.cpp:2054 +#: Source/diablo.cpp:2114 msgid "Move mouse up" msgstr "Mover el mouse hacia arriba" -#: Source/diablo.cpp:2055 +#: Source/diablo.cpp:2115 msgid "Simulates upward mouse movement." msgstr "Simula el movimiento ascendente del ratón." -#: Source/diablo.cpp:2060 +#: Source/diablo.cpp:2120 msgid "Move mouse down" msgstr "Mover el ratón hacia abajo" -#: Source/diablo.cpp:2061 +#: Source/diablo.cpp:2121 msgid "Simulates downward mouse movement." msgstr "Simula el movimiento del ratón hacia abajo." -#: Source/diablo.cpp:2066 +#: Source/diablo.cpp:2126 msgid "Move mouse left" msgstr "Mover el mouse hacia la izquierda" -#: Source/diablo.cpp:2067 +#: Source/diablo.cpp:2127 msgid "Simulates leftward mouse movement." msgstr "Simula el movimiento del ratón hacia la izquierda." -#: Source/diablo.cpp:2072 +#: Source/diablo.cpp:2132 msgid "Move mouse right" msgstr "Mover el ratón a la derecha" -#: Source/diablo.cpp:2073 +#: Source/diablo.cpp:2133 msgid "Simulates rightward mouse movement." msgstr "Simula el movimiento del ratón hacia la derecha." -#: Source/diablo.cpp:2078 Source/diablo.cpp:2092 +#: Source/diablo.cpp:2151 Source/diablo.cpp:2158 msgid "Left mouse click" msgstr "Clic izquierdo del ratón" -#: Source/diablo.cpp:2079 Source/diablo.cpp:2093 +#: Source/diablo.cpp:2152 Source/diablo.cpp:2159 msgid "Simulates the left mouse button." msgstr "Simula el botón izquierdo del ratón." -#: Source/diablo.cpp:2106 Source/diablo.cpp:2119 +#: Source/diablo.cpp:2176 Source/diablo.cpp:2183 msgid "Right mouse click" msgstr "Clic derecho del mouse" -#: Source/diablo.cpp:2107 Source/diablo.cpp:2120 +#: Source/diablo.cpp:2177 Source/diablo.cpp:2184 msgid "Simulates the right mouse button." msgstr "Simula el botón derecho del ratón." -#: Source/diablo.cpp:2132 +#: Source/diablo.cpp:2190 msgid "Gamepad hotspell menu" msgstr "Menú de hechizos de gamepad" -#: Source/diablo.cpp:2133 +#: Source/diablo.cpp:2191 msgid "Hold to set or use spell hotkeys." msgstr "" "Mantener presionado para configurar o usar las teclas de acceso rápido de " "hechizos." -#: Source/diablo.cpp:2139 +#: Source/diablo.cpp:2197 msgid "Gamepad menu navigator" msgstr "Navegador de menú Gamepad" -#: Source/diablo.cpp:2140 +#: Source/diablo.cpp:2198 msgid "Hold to access gamepad menu navigation." msgstr "Mantén presionado para acceder a la navegación del menú del gamepad." -#: Source/diablo.cpp:2155 Source/diablo.cpp:2164 +#: Source/diablo.cpp:2213 Source/diablo.cpp:2222 msgid "Toggle game menu" msgstr "Alternar el menú del juego" -#: Source/diablo.cpp:2156 Source/diablo.cpp:2165 +#: Source/diablo.cpp:2214 Source/diablo.cpp:2223 msgid "Opens the game menu." msgstr "Abre el menú del juego." -#: Source/discord/discord.cpp:67 -msgid "Cathedral" -msgstr "Catedral" - -#: Source/discord/discord.cpp:67 -msgid "Catacombs" -msgstr "Catacumbas" +#: Source/diablo_msg.cpp:62 +msgid "Game saved" +msgstr "Juego guardado" -#: Source/discord/discord.cpp:67 -msgid "Caves" -msgstr "Cuevas" +#: Source/diablo_msg.cpp:63 +msgid "No multiplayer functions in demo" +msgstr "No hay funciones multi jugador en la demostración" -#: Source/discord/discord.cpp:67 -msgid "Nest" -msgstr "Nido" +#: Source/diablo_msg.cpp:64 +msgid "Direct Sound Creation Failed" +msgstr "Falló la creación de Direct Sound" -#: Source/discord/discord.cpp:67 -msgid "Crypt" -msgstr "Crípta" - -#. TRANSLATORS: dungeon type and floor number i.e. "Cathedral 3" -#: Source/discord/discord.cpp:83 -msgid "{} {}" -msgstr "{} {}" - -#. TRANSLATORS: Discord character, i.e. "Lv 6 Warrior" -#: Source/discord/discord.cpp:91 -msgid "Lv {} {}" -msgstr "Niv {} {}" - -#. TRANSLATORS: Discord state i.e. "Nightmare difficulty" -#: Source/discord/discord.cpp:103 -msgid "{} difficulty" -msgstr "{} dificultad" - -#. TRANSLATORS: Discord activity, not in game -#: Source/discord/discord.cpp:184 -msgid "In Menu" -msgstr "En Menú" - -#: Source/dvlnet/loopback.cpp:118 -msgid "loopback" -msgstr "loopback" - -#: Source/dvlnet/tcp_client.cpp:65 -msgid "Unable to connect" -msgstr "No puede conectarse" - -#: Source/dvlnet/tcp_client.cpp:91 -msgid "error: read 0 bytes from server" -msgstr "error: leidos 0 bytes del servidor" - -#: Source/error.cpp:53 -msgid "No automap available in town" -msgstr "Automapa no disponible en la ciudad" - -#: Source/error.cpp:54 -msgid "No multiplayer functions in demo" -msgstr "No hay funciones multi jugador en la demostración" - -#: Source/error.cpp:55 -msgid "Direct Sound Creation Failed" -msgstr "Falló la creación de Direct Sound" - -#: Source/error.cpp:56 +#: Source/diablo_msg.cpp:65 msgid "Not available in shareware version" msgstr "No disponible en la versión shareware" -#: Source/error.cpp:57 +#: Source/diablo_msg.cpp:66 msgid "Not enough space to save" msgstr "No hay suficiente espacio para grabar" -#: Source/error.cpp:58 +#: Source/diablo_msg.cpp:67 msgid "No Pause in town" msgstr "Sin Pausa en el pueblo" -#: Source/error.cpp:59 +#: Source/diablo_msg.cpp:68 msgid "Copying to a hard disk is recommended" msgstr "Se recomienda copiar a un disco duro" -#: Source/error.cpp:60 +#: Source/diablo_msg.cpp:69 msgid "Multiplayer sync problem" msgstr "Problema de sincronización multi jugador" -#: Source/error.cpp:61 +#: Source/diablo_msg.cpp:70 msgid "No pause in multiplayer" msgstr "Sin pausa en multi jugador" -#: Source/error.cpp:63 +#: Source/diablo_msg.cpp:72 msgid "Saving..." msgstr "Grabando..." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:64 +#: Source/diablo_msg.cpp:73 msgid "Some are weakened as one grows strong" msgstr "Algunos se debilitan a medida que uno se hace más fuerte" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:65 +#: Source/diablo_msg.cpp:74 msgid "New strength is forged through destruction" msgstr "Nueva fuerza se forja a través de la destrucción" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:66 +#: Source/diablo_msg.cpp:75 msgid "Those who defend seldom attack" msgstr "Aquellos que defienden raras veces atacan" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:67 +#: Source/diablo_msg.cpp:76 msgid "The sword of justice is swift and sharp" msgstr "La espada de la justicia es rápida y afilada" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:68 +#: Source/diablo_msg.cpp:77 msgid "While the spirit is vigilant the body thrives" msgstr "Mientras el espíritu está alerta, el cuerpo prospera" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:69 +#: Source/diablo_msg.cpp:78 msgid "The powers of mana refocused renews" msgstr "Los poderes del maná reenfocado se renuevan" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:70 +#: Source/diablo_msg.cpp:79 msgid "Time cannot diminish the power of steel" msgstr "El tiempo no puede disminuir el poder del acero" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:71 +#: Source/diablo_msg.cpp:80 msgid "Magic is not always what it seems to be" msgstr "La Magia no siempre es lo que parece" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:72 +#: Source/diablo_msg.cpp:81 msgid "What once was opened now is closed" msgstr "Lo que una vez estuvo abierto ahora está cerrado" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:73 +#: Source/diablo_msg.cpp:82 msgid "Intensity comes at the cost of wisdom" msgstr "La intensidad viene a costa de la sabiduría" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:74 +#: Source/diablo_msg.cpp:83 msgid "Arcane power brings destruction" msgstr "El poder arcano trae destrucción" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:75 +#: Source/diablo_msg.cpp:84 msgid "That which cannot be held cannot be harmed" msgstr "Lo que no se puede sujetar, no se puede dañar" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:76 +#: Source/diablo_msg.cpp:85 msgid "Crimson and Azure become as the sun" msgstr "Crarmesí y Azur se vuelven como el sol" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:77 +#: Source/diablo_msg.cpp:86 msgid "Knowledge and wisdom at the cost of self" msgstr "Conocimiento y sabiduría a costa de uno mismo" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:78 +#: Source/diablo_msg.cpp:87 msgid "Drink and be refreshed" msgstr "Bebe y refréscate" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:79 +#: Source/diablo_msg.cpp:88 msgid "Wherever you go, there you are" msgstr "Dondequiera que vayas, ahí estás" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:80 +#: Source/diablo_msg.cpp:89 msgid "Energy comes at the cost of wisdom" msgstr "La energía viene a costa de la sabiduría" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:81 +#: Source/diablo_msg.cpp:90 msgid "Riches abound when least expected" msgstr "Las riquezas abundan cuando menos se espera" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:82 +#: Source/diablo_msg.cpp:91 msgid "Where avarice fails, patience gains reward" msgstr "Donde la avaricia falla, la paciencia gana recompensa" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:83 +#: Source/diablo_msg.cpp:92 msgid "Blessed by a benevolent companion!" msgstr "¡Bendecido por un compañero benevolente!" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:84 +#: Source/diablo_msg.cpp:93 msgid "The hands of men may be guided by fate" msgstr "Las manos de los hombres pueden ser guiadas por el destino" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:85 +#: Source/diablo_msg.cpp:94 msgid "Strength is bolstered by heavenly faith" msgstr "La fuerza se ve reforzada por la fe celestial" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:86 +#: Source/diablo_msg.cpp:95 msgid "The essence of life flows from within" msgstr "La esencia de la vida fluye desde dentro" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:87 +#: Source/diablo_msg.cpp:96 msgid "The way is made clear when viewed from above" msgstr "El camino se aclara cuando se ve desde arriba" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:88 +#: Source/diablo_msg.cpp:97 msgid "Salvation comes at the cost of wisdom" msgstr "La salvación viene a costa de la sabiduría" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:89 +#: Source/diablo_msg.cpp:98 msgid "Mysteries are revealed in the light of reason" msgstr "Los misterios se revelan a la luz de la razón" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:90 +#: Source/diablo_msg.cpp:99 msgid "Those who are last may yet be first" msgstr "Los que son los últimos pueden ser los primeros" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:91 +#: Source/diablo_msg.cpp:100 msgid "Generosity brings its own rewards" msgstr "La generosidad trae sus propias recompensas" -#: Source/error.cpp:92 +#: Source/diablo_msg.cpp:101 msgid "You must be at least level 8 to use this." msgstr "Debes tener al menos el nivel 8 para usar esto." -#: Source/error.cpp:93 +#: Source/diablo_msg.cpp:102 msgid "You must be at least level 13 to use this." msgstr "Debes tener al menos el nivel 13 para usar esto." -#: Source/error.cpp:94 +#: Source/diablo_msg.cpp:103 msgid "You must be at least level 17 to use this." msgstr "Debes tener al menos el nivel 17 para usar esto." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:95 +#: Source/diablo_msg.cpp:104 msgid "Arcane knowledge gained!" msgstr "¡Conocimiento arcano adquirido!" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:96 +#: Source/diablo_msg.cpp:105 msgid "That which does not kill you..." msgstr "Eso que no te mata ..." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:97 +#: Source/diablo_msg.cpp:106 msgid "Knowledge is power." msgstr "El conocimiento es poder." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:98 +#: Source/diablo_msg.cpp:107 msgid "Give and you shall receive." msgstr "Da y recibirás." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:99 +#: Source/diablo_msg.cpp:108 msgid "Some experience is gained by touch." msgstr "Se adquiere algo de experiencia mediante el tacto." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:100 +#: Source/diablo_msg.cpp:109 msgid "There's no place like home." msgstr "No hay lugar como el hogar." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:101 +#: Source/diablo_msg.cpp:110 msgid "Spiritual energy is restored." msgstr "Se restaura la energía espiritual." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:102 +#: Source/diablo_msg.cpp:111 msgid "You feel more agile." msgstr "Te sientes más ágil." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:103 +#: Source/diablo_msg.cpp:112 msgid "You feel stronger." msgstr "Te sientes más fuerte." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:104 +#: Source/diablo_msg.cpp:113 msgid "You feel wiser." msgstr "Te sientes más sabio." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:105 +#: Source/diablo_msg.cpp:114 msgid "You feel refreshed." msgstr "Te sientes renovado." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:106 +#: Source/diablo_msg.cpp:115 msgid "That which can break will." msgstr "Aquello que puede romper la voluntad." -#: Source/gamemenu.cpp:37 +#: Source/discord/discord.cpp:73 +msgid "Cathedral" +msgstr "Catedral" + +#: Source/discord/discord.cpp:73 +msgid "Catacombs" +msgstr "Catacumbas" + +#: Source/discord/discord.cpp:73 +msgid "Caves" +msgstr "Cuevas" + +#: Source/discord/discord.cpp:73 +msgid "Nest" +msgstr "Nido" + +#: Source/discord/discord.cpp:73 +msgid "Crypt" +msgstr "Crípta" + +#. TRANSLATORS: dungeon type and floor number i.e. "Cathedral 3" +#: Source/discord/discord.cpp:89 +#, c++-format +msgid "{} {}" +msgstr "{} {}" + +#. TRANSLATORS: Discord character, i.e. "Lv 6 Warrior" +#: Source/discord/discord.cpp:96 +#, c++-format +msgid "Lv {} {}" +msgstr "Niv {} {}" + +#. TRANSLATORS: Discord state i.e. "Nightmare difficulty" +#: Source/discord/discord.cpp:108 +#, c++-format +msgid "{} difficulty" +msgstr "{} dificultad" + +#. TRANSLATORS: Discord activity, not in game +#: Source/discord/discord.cpp:189 +msgid "In Menu" +msgstr "En Menú" + +#: Source/dvlnet/loopback.cpp:117 +msgid "loopback" +msgstr "loopback" + +#: Source/dvlnet/tcp_client.cpp:81 +msgid "Unable to connect" +msgstr "No puede conectarse" + +#: Source/dvlnet/tcp_client.cpp:119 +msgid "error: read 0 bytes from server" +msgstr "error: leidos 0 bytes del servidor" + +#: Source/engine/demomode.cpp:179 Source/options.cpp:679 +msgid "Resolution" +msgstr "Resolución" + +#: Source/engine/demomode.cpp:181 Source/options.cpp:1045 +msgid "Run in Town" +msgstr "Correr en el pueblo" + +#: Source/engine/demomode.cpp:182 Source/options.cpp:1047 +msgid "Theo Quest" +msgstr "La misión de Theo" + +#: Source/engine/demomode.cpp:183 Source/options.cpp:1048 +msgid "Cow Quest" +msgstr "La misión de la vaca" + +#: Source/engine/demomode.cpp:184 Source/options.cpp:1058 +msgid "Auto Gold Pickup" +msgstr "Auto recoger oro" + +#: Source/engine/demomode.cpp:185 Source/options.cpp:1059 +msgid "Auto Elixir Pickup" +msgstr "Auto recoger elixir" + +#: Source/engine/demomode.cpp:186 Source/options.cpp:1060 +msgid "Auto Oil Pickup" +msgstr "Recolección automática de aceite" + +#: Source/engine/demomode.cpp:187 Source/options.cpp:1061 +msgid "Auto Pickup in Town" +msgstr "Auto recoger en el pueblo" + +#: Source/engine/demomode.cpp:188 Source/options.cpp:1062 +msgid "Adria Refills Mana" +msgstr "Adria recarga maná" + +#: Source/engine/demomode.cpp:189 Source/options.cpp:1063 +msgid "Auto Equip Weapons" +msgstr "Auto equiparse con armas" + +#: Source/engine/demomode.cpp:190 Source/options.cpp:1064 +msgid "Auto Equip Armor" +msgstr "Auto equiparse con armadura" + +#: Source/engine/demomode.cpp:191 Source/options.cpp:1065 +msgid "Auto Equip Helms" +msgstr "Auto equiparse con yelmo" + +#: Source/engine/demomode.cpp:192 Source/options.cpp:1066 +msgid "Auto Equip Shields" +msgstr "Auto equiparse con escudos" + +#: Source/engine/demomode.cpp:193 Source/options.cpp:1067 +msgid "Auto Equip Jewelry" +msgstr "Auto equiparse con joyería" + +#: Source/engine/demomode.cpp:194 Source/options.cpp:1068 +msgid "Randomize Quests" +msgstr "Misiones aleatorias" + +#: Source/engine/demomode.cpp:195 Source/options.cpp:1070 +msgid "Show Item Labels" +msgstr "Mostrar etiquetas de artículos" + +#: Source/engine/demomode.cpp:196 Source/options.cpp:1071 +msgid "Auto Refill Belt" +msgstr "Auto recargar cinturón" + +# La traduccion debe ser corta para que entre en el cuadro de dialogo +#: Source/engine/demomode.cpp:197 Source/options.cpp:1072 +msgid "Disable Crippling Shrines" +msgstr "Desact. santuarios paralizantes" + +#: Source/engine/demomode.cpp:201 Source/options.cpp:1074 +msgid "Heal Potion Pickup" +msgstr "Recoger poción de salud" + +#: Source/engine/demomode.cpp:202 Source/options.cpp:1075 +msgid "Full Heal Potion Pickup" +msgstr "Recoger poción de salud completa" + +#: Source/engine/demomode.cpp:203 Source/options.cpp:1076 +msgid "Mana Potion Pickup" +msgstr "Recoger poción de maná" + +#: Source/engine/demomode.cpp:204 Source/options.cpp:1077 +msgid "Full Mana Potion Pickup" +msgstr "Recoger poción de maná completo" + +#: Source/engine/demomode.cpp:205 Source/options.cpp:1078 +msgid "Rejuvenation Potion Pickup" +msgstr "Recoger poción de rejuvenecimiento" + +# La traduccion debe ser corta para que entre en el cuadro de dialogo +#: Source/engine/demomode.cpp:206 Source/options.cpp:1079 +msgid "Full Rejuvenation Potion Pickup" +msgstr "Recoger poción de rejuvenec. completo" + +#: Source/gamemenu.cpp:42 msgid "Save Game" msgstr "Guardar" -#: Source/gamemenu.cpp:38 Source/gamemenu.cpp:49 +#: Source/gamemenu.cpp:43 Source/gamemenu.cpp:54 msgid "Options" msgstr "Opciones" -#: Source/gamemenu.cpp:41 Source/gamemenu.cpp:52 +#: Source/gamemenu.cpp:46 Source/gamemenu.cpp:57 msgid "Quit Game" msgstr "Salir" -#: Source/gamemenu.cpp:51 +#: Source/gamemenu.cpp:56 msgid "Restart In Town" msgstr "Reiniciar en el pueblo" -#: Source/gamemenu.cpp:61 +#: Source/gamemenu.cpp:66 msgid "Gamma" msgstr "Gama" # Mantener corto. Texto de menú -#: Source/gamemenu.cpp:62 Source/gamemenu.cpp:171 +#: Source/gamemenu.cpp:67 Source/gamemenu.cpp:176 msgid "Speed" msgstr "Veloc." -#: Source/gamemenu.cpp:70 +#: Source/gamemenu.cpp:75 msgid "Music Disabled" msgstr "Música Desactivada" -#: Source/gamemenu.cpp:74 +#: Source/gamemenu.cpp:79 msgid "Sound" msgstr "Sonido" -#: Source/gamemenu.cpp:75 +#: Source/gamemenu.cpp:80 msgid "Sound Disabled" msgstr "Sonido Desactivado" -#: Source/gmenu.cpp:176 +#: Source/gmenu.cpp:178 msgid "Pause" msgstr "Pausa" @@ -2109,7 +2360,8 @@ msgstr "1 - 8: Usar elemento del Cinturón" #: Source/help.cpp:43 msgid "F5, F6, F7, F8: Set hotkey for skill or spell" msgstr "" -"F5, F6, F7, F8: Establecer tecla de acceso rápido para habilidad o hechizo" +"F5, F6, F7, F8: Establecer tecla de acceso rápido para habilidad o " +"hechizo" #: Source/help.cpp:44 msgid "Shift + Left Mouse Button: Attack without moving" @@ -2118,15 +2370,16 @@ msgstr "Mayús + Clic izquierdo: Atacar sin moverse" #: Source/help.cpp:45 msgid "Shift + Left Mouse Button (on character screen): Assign all stat points" msgstr "" -"Mayús + Click Izquierdo (en la pantalla del personaje): Asignar todos los puntos " -"de estadística" +"Mayús + Click Izquierdo (en la pantalla del personaje): Asignar todos los " +"puntos de estadística" #: Source/help.cpp:46 msgid "" -"Shift + Left Mouse Button (on inventory): Move item to belt or equip/unequip item" +"Shift + Left Mouse Button (on inventory): Move item to belt or equip/unequip " +"item" msgstr "" -"Mayús + Click Izquierdo (en el inventario) Mover objeto al cinturón o equipar/" -"desequipar objeto" +"Mayús + Click Izquierdo (en el inventario) Mover objeto al cinturón o " +"equipar/desequipar objeto" #: Source/help.cpp:47 msgid "Shift + Left Mouse Button (on belt): Move item to inventory" @@ -2138,8 +2391,8 @@ msgstr "$Movimiento:" #: Source/help.cpp:50 msgid "" -"If you hold the mouse button down while moving, the character will continue to " -"move in that direction." +"If you hold the mouse button down while moving, the character will continue " +"to move in that direction." msgstr "" "Si mantienes presionado el botón del mouse mientras se mueve, el personaje " "continuará moviéndose en esa dirección." @@ -2150,11 +2403,11 @@ msgstr "$Combate:" #: Source/help.cpp:54 msgid "" -"Holding down the shift key and then left-clicking allows the character to attack " -"without moving." +"Holding down the shift key and then left-clicking allows the character to " +"attack without moving." msgstr "" -"Manteniendo presionado la tecla Mayús y luego hacer clic con el botón izquierdo " -"permite que el personaje ataque sin moverse." +"Manteniendo presionado la tecla Mayús y luego hacer clic con el botón " +"izquierdo permite que el personaje ataque sin moverse." #: Source/help.cpp:57 msgid "$Auto-map:" @@ -2162,14 +2415,14 @@ msgstr "$Automapa:" #: Source/help.cpp:58 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." +"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 "" "Para acceder al auto-mapa, haga clic en el botón 'MAP' en la barra de " -"información o presione 'TAB' en el teclado. Acercar y alejar el mapa se realiza " -"con las teclas + y -. Para desplazarse por el mapa se utilizan las teclas de " -"flecha." +"información o presione 'TAB' en el teclado. Acercar y alejar el mapa se " +"realiza con las teclas + y -. Para desplazarse por el mapa se utilizan las " +"teclas de flecha." #: Source/help.cpp:63 msgid "$Picking up Objects:" @@ -2178,16 +2431,16 @@ msgstr "$Recoger Objetos:" #: Source/help.cpp:64 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." +"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 "" -"Los elementos utilizables que son de tamaño pequeño, como pociones o pergaminos, " -"se colocan automáticamente en su 'cinturón' ubicado en la parte superior de la " -"barra de Interfaz. Cuando se coloca un objeto en el cinturón, aparece un pequeño " -"número en ese recuadro. Los artículos se pueden usar presionando el número " -"correspondiente o haciendo clic derecho en el objeto." +"Los elementos utilizables que son de tamaño pequeño, como pociones o " +"pergaminos, se colocan automáticamente en su 'cinturón' ubicado en la parte " +"superior de la barra de Interfaz. Cuando se coloca un objeto en el cinturón, " +"aparece un pequeño número en ese recuadro. Los artículos se pueden usar " +"presionando el número correspondiente o haciendo clic derecho en el objeto." #: Source/help.cpp:70 msgid "$Gold:" @@ -2195,8 +2448,8 @@ msgstr "$Oro:" #: Source/help.cpp:71 msgid "" -"You can select a specific amount of gold to drop by right-clicking on a pile of " -"gold in your inventory." +"You can select a specific amount of gold to drop by right-clicking on a pile " +"of gold in your inventory." msgstr "" "Puedes seleccionar una cantidad específica de oro para soltar haciendo clic " "derecho en un montón de oro de su inventario." @@ -2207,17 +2460,18 @@ msgstr "$Habilidades y Hechizos:" #: Source/help.cpp:75 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." +"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 "" -"Puedes acceder a tu lista de habilidades y hechizos haciendo clic izquierdo en " -"el botón 'HECHIZOS' en la barra de la interfaz. Aquí se enumeran los hechizos " -"memorizados y los disponibles a través de las varas. Al hacer clic con el botón " -"izquierdo en el hechizo que desea lanzar, se preparará el hechizo. Se puede " -"lanzar un hechizo preparado simplemente haciendo clic con el botón derecho en el " -"área de juego." +"Puedes acceder a tu lista de habilidades y hechizos haciendo clic izquierdo " +"en el botón 'HECHIZOS' en la barra de la interfaz. Aquí se enumeran los " +"hechizos memorizados y los disponibles a través de las varas. Al hacer clic " +"con el botón izquierdo en el hechizo que desea lanzar, se preparará el " +"hechizo. Se puede lanzar un hechizo preparado simplemente haciendo clic con " +"el botón derecho en el área de juego." #: Source/help.cpp:81 msgid "$Using the Speedbook for Spells:" @@ -2226,13 +2480,13 @@ msgstr "$Uso del libro rápido para Hechizos:" #: Source/help.cpp:82 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." +"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 "" -"Al hacer clic con el botón izquierdo en el botón 'hechizo listo' se abrirá el " -"'libro rápido' que le permite seleccionar una habilidad o hechizo de uso " -"inmediato. Para usar la habilidad o el hechizo, simplemente haga clic con el " -"botón derecho en el área de juego principal." +"Al hacer clic con el botón izquierdo en el botón 'hechizo listo' se abrirá " +"el 'libro rápido' que le permite seleccionar una habilidad o hechizo de uso " +"inmediato. Para usar la habilidad o el hechizo, simplemente haga clic con " +"el botón derecho en el área de juego principal." #: Source/help.cpp:86 msgid "" @@ -2249,13 +2503,13 @@ msgstr "$Configurar teclas de acceso rápido para Hechizos:" #: Source/help.cpp:89 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." +"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 "" -"Puede asignar hasta cuatro teclas de Acceso Rápido para habilidades, hechizos o " -"pergaminos. Empiece por abrir el 'libro rápido' como se describe en la sección " -"anterior. Presione las teclas F5, F6, F7 o F8 después de resaltar el hechizo que " -"desea asignar." +"Puede asignar hasta cuatro teclas de Acceso Rápido para habilidades, " +"hechizos o pergaminos. Empiece por abrir el 'libro rápido' como se describe " +"en la sección anterior. Presione las teclas F5, F6, F7 o F8 después de " +"resaltar el hechizo que desea asignar." #: Source/help.cpp:94 msgid "$Spell Books:" @@ -2263,11 +2517,11 @@ msgstr "$Libros de hechizos:" #: Source/help.cpp:95 msgid "" -"Reading more than one book increases your knowledge of that spell, allowing you " -"to cast the spell more effectively." +"Reading more than one book increases your knowledge of that spell, allowing " +"you to cast the spell more effectively." msgstr "" -"Leer más de un libro aumenta su conocimiento de ese hechizo, lo que le permite " -"lanzar el hechizo de manera más efectiva." +"Leer más de un libro aumenta su conocimiento de ese hechizo, lo que le " +"permite lanzar el hechizo de manera más efectiva." #: Source/help.cpp:200 msgid "Shareware Hellfire Help" @@ -2285,19 +2539,19 @@ msgstr "Ayuda de Diablo Shareware" msgid "Diablo Help" msgstr "Ayuda de Diablo" -#: Source/help.cpp:233 Source/qol/chatlog.cpp:189 +#: Source/help.cpp:234 Source/qol/chatlog.cpp:200 msgid "Press ESC to end or the arrow keys to scroll." msgstr "Presione ESC para finalizar o las teclas de flecha para desplazarse." -#: Source/init.cpp:248 Source/init.cpp:288 +#: Source/init.cpp:298 Source/init.cpp:338 msgid "diabdat.mpq or spawn.mpq" msgstr "diabdat.mpq o spawn.mpq" -#: Source/init.cpp:269 Source/init.cpp:309 +#: Source/init.cpp:319 Source/init.cpp:359 msgid "Some Hellfire MPQs are missing" msgstr "Faltan algunos MPQ de Hellfire" -#: Source/init.cpp:269 Source/init.cpp:309 +#: Source/init.cpp:319 Source/init.cpp:359 msgid "" "Not all Hellfire MPQs were found.\n" "Please copy all the hf*.mpq files." @@ -2305,9562 +2559,9747 @@ msgstr "" "No se encontraron todos los MPQ de Hellfire.\n" "Copie todos los archivos hf * .mpq." -#: Source/init.cpp:318 +#: Source/init.cpp:368 msgid "Unable to create main window" msgstr "No se pudo crear la ventana principal" -#: Source/itemdat.cpp:53 Source/itemdat.cpp:235 Source/panels/charpanel.cpp:171 -msgid "Gold" -msgstr "Oro" +#: Source/inv.cpp:2118 +msgid "No room for item" +msgstr "No hay espacio para el artículo" -#: Source/itemdat.cpp:54 Source/itemdat.cpp:172 -msgid "Short Sword" -msgstr "Espada Corta" +#: Source/items.cpp:174 Source/translation_dummy.cpp:360 +msgid "Oil of Accuracy" +msgstr "Aceite de Precisión" -#: Source/itemdat.cpp:55 Source/itemdat.cpp:124 -msgid "Buckler" -msgstr "Rodela" +#: Source/items.cpp:175 +msgid "Oil of Mastery" +msgstr "Aceite de Maestría" -#: Source/itemdat.cpp:56 Source/itemdat.cpp:192 Source/itemdat.cpp:193 -msgid "Club" -msgstr "Porra" +#: Source/items.cpp:176 Source/translation_dummy.cpp:361 +msgid "Oil of Sharpness" +msgstr "Aceite de Nitidez" -#: Source/itemdat.cpp:57 Source/itemdat.cpp:196 -msgid "Short Bow" -msgstr "Arco Corto" +#: Source/items.cpp:177 +msgid "Oil of Death" +msgstr "Aceite de Muerte" -#: Source/itemdat.cpp:58 -msgid "Short Staff of Mana" -msgstr "Bastón Corto de Maná" +#: Source/items.cpp:178 +msgid "Oil of Skill" +msgstr "Aceite de Habilidad" -#: Source/itemdat.cpp:59 -msgid "Cleaver" -msgstr "Cuchilla de carnicero" +#: Source/items.cpp:179 Source/translation_dummy.cpp:282 +#: Source/translation_dummy.cpp:359 +msgid "Blacksmith Oil" +msgstr "Aceite de Herrero" -#: Source/itemdat.cpp:60 Source/itemdat.cpp:434 -msgid "The Undead Crown" -msgstr "La Corona de los Muertos Vivientes" +#: Source/items.cpp:180 +msgid "Oil of Fortitude" +msgstr "Aceite de Entereza" -#: Source/itemdat.cpp:61 Source/itemdat.cpp:435 -msgid "Empyrean Band" -msgstr "Banda Empírea" +#: Source/items.cpp:181 +msgid "Oil of Permanence" +msgstr "Aceite de Permanencia" -#: Source/itemdat.cpp:62 -msgid "Magic Rock" -msgstr "Roca Mágica" +#: Source/items.cpp:182 +msgid "Oil of Hardening" +msgstr "Aceite de Endurecimiento" -#: Source/itemdat.cpp:63 Source/itemdat.cpp:436 -msgid "Optic Amulet" -msgstr "Amuleto Óptico" +#: Source/items.cpp:183 +msgid "Oil of Imperviousness" +msgstr "Aceite de Impermeabilidad" -#: Source/itemdat.cpp:64 Source/itemdat.cpp:437 -msgid "Ring of Truth" -msgstr "Anillo de la Verdad" +#. TRANSLATORS: Constructs item names. Format: {Item} of {Spell}. Example: War Staff of Firewall +#: Source/items.cpp:1112 +#, c++-format +msgctxt "spell" +msgid "{0} of {1}" +msgstr "{0} de {1}" -#: Source/itemdat.cpp:65 -msgid "Tavern Sign" -msgstr "Cartel de la Taberna" +#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Spell}. Example: King's War Staff of Firewall +#: Source/items.cpp:1124 +#, c++-format +msgctxt "spell" +msgid "{0} {1} of {2}" +msgstr "{1} {0} de {2}" -#: Source/itemdat.cpp:66 Source/itemdat.cpp:438 -msgid "Harlequin Crest" -msgstr "Cresta del Arlequín" +#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Suffix}. Example: King's Long Sword of the Whale +#: Source/items.cpp:1162 +#, c++-format +msgid "{0} {1} of {2}" +msgstr "{1} {0} {2}" -#: Source/itemdat.cpp:67 Source/itemdat.cpp:439 -msgid "Veil of Steel" -msgstr "Velo de Acero" +#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item}. Example: King's Long Sword +#: Source/items.cpp:1165 +#, c++-format +msgid "{0} {1}" +msgstr "{1} {0}" -#: Source/itemdat.cpp:68 -msgid "Golden Elixir" -msgstr "Elixir Dorado" +#. TRANSLATORS: Constructs item names. Format: {Item} of {Suffix}. Example: Long Sword of the Whale +#: Source/items.cpp:1168 +#, c++-format +msgid "{0} of {1}" +msgstr "{0} {1}" -#: Source/itemdat.cpp:69 Source/quests.cpp:56 -msgid "Anvil of Fury" -msgstr "Yunque de Furia" +#: Source/items.cpp:1699 Source/items.cpp:1707 +msgid "increases a weapon's" +msgstr "en el arma aumenta" -#: Source/itemdat.cpp:70 Source/quests.cpp:47 -msgid "Black Mushroom" -msgstr "Hongo Negro" +#: Source/items.cpp:1700 +msgid "chance to hit" +msgstr "probabilidad de acertar" -#: Source/itemdat.cpp:71 -msgid "Brain" -msgstr "Cerebro" +#: Source/items.cpp:1703 +msgid "greatly increases a" +msgstr "aumenta enormemente un" -#: Source/itemdat.cpp:72 -msgid "Fungal Tome" -msgstr "Tomo de Hongos" +#: Source/items.cpp:1704 +msgid "weapon's chance to hit" +msgstr "posibilidad de que el arma golpee" -#: Source/itemdat.cpp:73 -msgid "Spectral Elixir" -msgstr "Elixir Espectral" +#: Source/items.cpp:1708 +msgid "damage potential" +msgstr "daño potencial" -#: Source/itemdat.cpp:74 -msgid "Blood Stone" -msgstr "Piedra de Sangre" +#: Source/items.cpp:1711 +msgid "greatly increases a weapon's" +msgstr "aumenta en gran medida la de un arma" -#: Source/itemdat.cpp:75 -msgid "Cathedral Map" -msgstr "Mapa de la Catedral" +#: Source/items.cpp:1712 +msgid "damage potential - not bows" +msgstr "daño potencial - no arcos" -#: Source/itemdat.cpp:76 -msgid "Heart" -msgstr "Corazón" +#: Source/items.cpp:1715 +msgid "reduces attributes needed" +msgstr "reduce los atributos necesarios" -#: Source/itemdat.cpp:77 Source/itemdat.cpp:130 -msgid "Potion of Healing" -msgstr "Poción Curativa" +#: Source/items.cpp:1716 +msgid "to use armor or weapons" +msgstr "para usar armaduras o armas" -#: Source/itemdat.cpp:78 Source/itemdat.cpp:132 -msgid "Potion of Mana" -msgstr "Poción de Maná" +#: Source/items.cpp:1719 +#, no-c-format +msgid "restores 20% of an" +msgstr "restaura el 20% de la" -#: Source/itemdat.cpp:79 Source/itemdat.cpp:147 -msgid "Scroll of Identify" -msgstr "Pergamino de la Identidad" +#: Source/items.cpp:1720 +msgid "item's durability" +msgstr "durabilidad del artículo" -#: Source/itemdat.cpp:80 Source/itemdat.cpp:151 -msgid "Scroll of Town Portal" -msgstr "Pergamino del Portal de la Ciudad" +#: Source/items.cpp:1723 +msgid "increases an item's" +msgstr "aumenta la de un artículo" -#: Source/itemdat.cpp:81 Source/itemdat.cpp:440 -msgid "Arkaine's Valor" -msgstr "Valor de Arkaine" +#: Source/items.cpp:1724 +msgid "current and max durability" +msgstr "durabilidad actual y máxima" -#: Source/itemdat.cpp:82 Source/itemdat.cpp:131 -msgid "Potion of Full Healing" -msgstr "Poción Curativa Completa" +#: Source/items.cpp:1727 +msgid "makes an item indestructible" +msgstr "hace que un artículo sea indestructible" -#: Source/itemdat.cpp:83 Source/itemdat.cpp:133 -msgid "Potion of Full Mana" -msgstr "Poción de Maná Completa" +#: Source/items.cpp:1730 +msgid "increases the armor class" +msgstr "aumenta la clase de armadura" -#: Source/itemdat.cpp:84 Source/itemdat.cpp:441 -msgid "Griswold's Edge" -msgstr "Hoja de Griswold" +#: Source/items.cpp:1731 +msgid "of armor and shields" +msgstr "de armaduras y escudos" -#: Source/itemdat.cpp:85 Source/itemdat.cpp:442 -msgid "Bovine Plate" -msgstr "Armadura de Bovino" +#: Source/items.cpp:1734 +msgid "greatly increases the armor" +msgstr "aumenta enormemente la armadura" -#: Source/itemdat.cpp:86 -msgid "Staff of Lazarus" -msgstr "Bastón de Lazarus" +#: Source/items.cpp:1735 +msgid "class of armor and shields" +msgstr "clase de armaduras y escudos" -#: Source/itemdat.cpp:87 Source/itemdat.cpp:148 -msgid "Scroll of Resurrect" -msgstr "Pergamino de Resurrección" +#: Source/items.cpp:1738 Source/items.cpp:1745 +msgid "sets fire trap" +msgstr "pone trampa de fuego" -#: Source/itemdat.cpp:88 Source/itemdat.cpp:136 Source/items.cpp:176 -msgid "Blacksmith Oil" -msgstr "Aceite de Herrero" +#: Source/items.cpp:1742 +msgid "sets lightning trap" +msgstr "establece trampa de rayos" -#: Source/itemdat.cpp:89 Source/itemdat.cpp:204 -msgid "Short Staff" -msgstr "Bastón Corto" +#: Source/items.cpp:1748 +msgid "sets petrification trap" +msgstr "establece trampa de petrificación" -#: 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 "Espada" +#: Source/items.cpp:1751 +msgid "restore all life" +msgstr "restaura toda la vida" -#: Source/itemdat.cpp:91 Source/itemdat.cpp:171 -msgid "Dagger" -msgstr "Daga" +#: Source/items.cpp:1754 +msgid "restore some life" +msgstr "restaura algo de vida" -#: Source/itemdat.cpp:92 -msgid "Rune Bomb" -msgstr "Bomba Rúnica" +#: Source/items.cpp:1757 +msgid "restore some mana" +msgstr "restaura algo de maná" -#: Source/itemdat.cpp:93 -msgid "Theodore" -msgstr "Teodoro" +#: Source/items.cpp:1760 +msgid "restore all mana" +msgstr "restaura todo el maná" -#: Source/itemdat.cpp:94 -msgid "Auric Amulet" -msgstr "Amuleto Áurico" +#: Source/items.cpp:1763 +msgid "increase strength" +msgstr "aumenta la fuerza" -#: Source/itemdat.cpp:95 -msgid "Torn Note 1" -msgstr "Nota Rasgada 1" +#: Source/items.cpp:1766 +msgid "increase magic" +msgstr "aumenta la magia" -#: Source/itemdat.cpp:96 -msgid "Torn Note 2" -msgstr "Nota Rasgada 2" +#: Source/items.cpp:1769 +msgid "increase dexterity" +msgstr "aumenta la destreza" -#: Source/itemdat.cpp:97 -msgid "Torn Note 3" -msgstr "Nota Rasgada 3" +#: Source/items.cpp:1772 +msgid "increase vitality" +msgstr "aumenta la vitalidad" -#: Source/itemdat.cpp:98 -msgid "Reconstructed Note" -msgstr "Nota Reconstruida" +#: Source/items.cpp:1775 +msgid "restore some life and mana" +msgstr "restaura algo de vida y maná" -#: Source/itemdat.cpp:99 -msgid "Brown Suit" -msgstr "Traje Marrón" +#: Source/items.cpp:1778 Source/items.cpp:1781 +msgid "restore all life and mana" +msgstr "restaura toda la vida y maná" -#: Source/itemdat.cpp:100 -msgid "Grey Suit" -msgstr "Traje Gris" +#: Source/items.cpp:1782 +msgid "(works only in arenas)" +msgstr "(funciona solo en arenas)" -#: Source/itemdat.cpp:101 Source/itemdat.cpp:102 -msgid "Cap" -msgstr "Gorro" +#: Source/items.cpp:1796 +msgid "Right-click to view" +msgstr "Clic derecho para ver" -#: Source/itemdat.cpp:102 -msgid "Skull Cap" -msgstr "Gorro Metálico" +#: Source/items.cpp:1799 +msgid "Right-click to use" +msgstr "Clic derecho para usar" -#: Source/itemdat.cpp:103 Source/itemdat.cpp:104 Source/itemdat.cpp:106 -msgid "Helm" -msgstr "Yelmo" +#: Source/items.cpp:1801 +msgid "" +"Right-click to read, then\n" +"left-click to target" +msgstr "" +"Haga clic derecho para leer, luego\n" +"clic izquierdo para apuntar" -#: Source/itemdat.cpp:104 -msgid "Full Helm" -msgstr "Yelmo Completo" +#: Source/items.cpp:1803 +msgid "Right-click to read" +msgstr "Haga clic derecho para leer" -#: Source/itemdat.cpp:105 -msgid "Crown" -msgstr "Corona" +#: Source/items.cpp:1810 +msgid "Activate to view" +msgstr "Activar para ver" -#: Source/itemdat.cpp:106 -msgid "Great Helm" -msgstr "Gran Yelmo" +#: Source/items.cpp:1814 Source/items.cpp:1852 +msgid "Open inventory to use" +msgstr "Abrir inventario para usar" -#: Source/itemdat.cpp:107 -msgid "Cape" -msgstr "Capa" +#: Source/items.cpp:1816 +msgid "Activate to use" +msgstr "Activar para usar" -#: Source/itemdat.cpp:108 -msgid "Rags" -msgstr "Andrajo" +#: Source/items.cpp:1819 +msgid "" +"Select from spell book, then\n" +"cast spell to read" +msgstr "" +"Seleccione del libro de hechizos, luego\n" +"lance el hechizo para leer" -#: Source/itemdat.cpp:109 -msgid "Cloak" -msgstr "Manto" +#: Source/items.cpp:1821 +msgid "Activate to read" +msgstr "Activar para leer" -#: Source/itemdat.cpp:110 -msgid "Robe" -msgstr "Túnica" +#: Source/items.cpp:1848 +#, c++-format +msgid "{} to view" +msgstr "{} para ver" -#: Source/itemdat.cpp:111 -msgid "Quilted Armor" -msgstr "Armadura Acolchada" +#: Source/items.cpp:1854 +#, c++-format +msgid "{} to use" +msgstr "{} para usar" -#: Source/itemdat.cpp:111 Source/itemdat.cpp:112 Source/itemdat.cpp:113 -#: Source/itemdat.cpp:114 Source/objects.cpp:4857 -msgid "Armor" -msgstr "Armadura" +#: Source/items.cpp:1857 +#, c++-format +msgid "" +"Select from spell book,\n" +"then {} to read" +msgstr "" +"Seleccione del libro de hechizos,\n" +"luego {} para leer" -#: Source/itemdat.cpp:112 -msgid "Leather Armor" -msgstr "Armadura de Cuero" +#: Source/items.cpp:1859 +#, c++-format +msgid "{} to read" +msgstr "{} para leer" -#: Source/itemdat.cpp:113 -msgid "Hard Leather Armor" -msgstr "Armadura de Cuero Duro" +#: Source/items.cpp:1866 +#, c++-format +msgctxt "player" +msgid "Level: {:d}" +msgstr "Nivel: {:d}" -#: Source/itemdat.cpp:114 -msgid "Studded Leather Armor" -msgstr "Cuero Tachonado" +#: Source/items.cpp:1870 +msgid "Doubles gold capacity" +msgstr "Duplica la capacidad de oro" -#: Source/itemdat.cpp:115 -msgid "Ring Mail" -msgstr "Cota de Anillos" +#: Source/items.cpp:1902 Source/stores.cpp:325 +msgid "Required:" +msgstr "Requiere:" -#: Source/itemdat.cpp:115 Source/itemdat.cpp:116 Source/itemdat.cpp:117 -#: Source/itemdat.cpp:119 -msgid "Mail" -msgstr "Cota" +#: Source/items.cpp:1904 Source/stores.cpp:327 +#, c++-format +msgid " {:d} Str" +msgstr " {:d} Fue" -#: Source/itemdat.cpp:116 -msgid "Chain Mail" -msgstr "Cota de Malla" +#: Source/items.cpp:1906 Source/stores.cpp:329 +#, c++-format +msgid " {:d} Mag" +msgstr " {:d} Mag" -#: Source/itemdat.cpp:117 -msgid "Scale Mail" -msgstr "Cota de Escamas" +#: Source/items.cpp:1908 Source/stores.cpp:331 +#, c++-format +msgid " {:d} Dex" +msgstr " {:d} Des" -#: Source/itemdat.cpp:118 -msgid "Breast Plate" -msgstr "Coraza Blindada" +#. TRANSLATORS: {:s} will be a Character Name +#: Source/items.cpp:2283 +#, c++-format +msgid "Ear of {:s}" +msgstr "Oído de {:s}" -#: Source/itemdat.cpp:118 Source/itemdat.cpp:120 Source/itemdat.cpp:121 -#: Source/itemdat.cpp:122 Source/itemdat.cpp:123 -msgid "Plate" -msgstr "Coraza" +#: Source/items.cpp:3673 +#, c++-format +msgid "chance to hit: {:+d}%" +msgstr "probabilidad de acertar: {:+d}%" -#: Source/itemdat.cpp:119 -msgid "Splint Mail" -msgstr "Cota de Láminas" +#: Source/items.cpp:3676 +#, no-c-format, c++-format +msgid "{:+d}% damage" +msgstr "{:+d}% daño" -#: Source/itemdat.cpp:120 -msgid "Plate Mail" -msgstr "Cota de Placas" +#: Source/items.cpp:3679 Source/items.cpp:3863 +#, c++-format +msgid "to hit: {:+d}%, {:+d}% damage" +msgstr "al golpear: {:+d}%, {:+d} daño" -#: Source/itemdat.cpp:121 -msgid "Field Plate" -msgstr "Coraza de Campaña" +#: Source/items.cpp:3682 +#, no-c-format, c++-format +msgid "{:+d}% armor" +msgstr "{:+d}% armadura" -#: Source/itemdat.cpp:122 -msgid "Gothic Plate" -msgstr "Coraza Gótica" +#: Source/items.cpp:3685 +#, c++-format +msgid "armor class: {:d}" +msgstr "clase de armadura: {:d}" -#: Source/itemdat.cpp:123 -msgid "Full Plate Mail" -msgstr "Cota de Placas Completa" +#: Source/items.cpp:3689 +#, c++-format +msgid "Resist Fire: {:+d}%" +msgstr "Resistencia al Fuego: {:+d}%" -#: 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 "Escudo" +#: Source/items.cpp:3691 +#, c++-format +msgid "Resist Fire: {:+d}% MAX" +msgstr "Resistencia al Fuego: {:+d}% MAX" -#: Source/itemdat.cpp:125 -msgid "Small Shield" -msgstr "Escudo Pequeño" +#: Source/items.cpp:3695 +#, c++-format +msgid "Resist Lightning: {:+d}%" +msgstr "Resistencia a Relámpagos: {:+d}%" -#: Source/itemdat.cpp:126 -msgid "Large Shield" -msgstr "Escudo Grande" +#: Source/items.cpp:3697 +#, c++-format +msgid "Resist Lightning: {:+d}% MAX" +msgstr "Resistencia a Relámpagos: {:+d}% MAX" -#: Source/itemdat.cpp:127 -msgid "Kite Shield" -msgstr "Escudo de Vértices" +#: Source/items.cpp:3701 +#, c++-format +msgid "Resist Magic: {:+d}%" +msgstr "Resistencia a la Magia: {:+d}%" -#: Source/itemdat.cpp:128 -msgid "Tower Shield" -msgstr "Escudo de la Torre" +#: Source/items.cpp:3703 +#, c++-format +msgid "Resist Magic: {:+d}% MAX" +msgstr "Resistencia a la Magia: {:+d}% MAX" -#: Source/itemdat.cpp:129 -msgid "Gothic Shield" -msgstr "Escudo Gótico" +#: Source/items.cpp:3706 +#, c++-format +msgid "Resist All: {:+d}%" +msgstr "Resistencia a Todo: {:+d}%" -#: Source/itemdat.cpp:134 -msgid "Potion of Rejuvenation" -msgstr "Poción Rejuvenecedora" +#: Source/items.cpp:3708 +#, c++-format +msgid "Resist All: {:+d}% MAX" +msgstr "Resistencia a Todo: {:+d}% MAX" -#: Source/itemdat.cpp:135 -msgid "Potion of Full Rejuvenation" -msgstr "Poción Rejuvenecedora Completa" +#: Source/items.cpp:3711 +#, c++-format +msgid "spells are increased {:d} level" +msgid_plural "spells are increased {:d} levels" +msgstr[0] "los hechizos aumentan {:d} nivel" +msgstr[1] "los hechizos aumentan {:d} niveles" -#: Source/itemdat.cpp:137 Source/items.cpp:171 -msgid "Oil of Accuracy" -msgstr "Aceite de Precisión" +#: Source/items.cpp:3713 +#, c++-format +msgid "spells are decreased {:d} level" +msgid_plural "spells are decreased {:d} levels" +msgstr[0] "los hechizos se reducen {:d} nivel" +msgstr[1] "los hechizos se reducen {:d} niveles" -#: Source/itemdat.cpp:138 Source/items.cpp:173 -msgid "Oil of Sharpness" -msgstr "Aceite de Nitidez" +#: Source/items.cpp:3715 +msgid "spell levels unchanged (?)" +msgstr "niveles de hechizo sin cambios (?)" -#: Source/itemdat.cpp:139 -msgid "Oil" -msgstr "Aceite" +#: Source/items.cpp:3717 +msgid "Extra charges" +msgstr "Cargas extras" -#: Source/itemdat.cpp:140 -msgid "Elixir of Strength" -msgstr "Elixir de Fuerza" +#: Source/items.cpp:3719 +#, c++-format +msgid "{:d} {:s} charge" +msgid_plural "{:d} {:s} charges" +msgstr[0] "{:d} {:s} carga" +msgstr[1] "{:d} {:s} cargas" -#: Source/itemdat.cpp:141 -msgid "Elixir of Magic" -msgstr "Elixir de Magia" +#: Source/items.cpp:3722 +#, c++-format +msgid "Fire hit damage: {:d}" +msgstr "Daño por impacto de fuego: {:d}" -#: Source/itemdat.cpp:142 -msgid "Elixir of Dexterity" -msgstr "Elixir de Destreza" +#: Source/items.cpp:3724 +#, c++-format +msgid "Fire hit damage: {:d}-{:d}" +msgstr "Daño por impacto de fuego: {:d}- {:d}" -#: Source/itemdat.cpp:143 -msgid "Elixir of Vitality" -msgstr "Elixir de Vitalidad" +#: Source/items.cpp:3727 +#, c++-format +msgid "Lightning hit damage: {:d}" +msgstr "Daño por impacto de rayo: {:d}" -#: Source/itemdat.cpp:144 -msgid "Scroll of Healing" -msgstr "Pergamino de curación" +#: Source/items.cpp:3729 +#, c++-format +msgid "Lightning hit damage: {:d}-{:d}" +msgstr "Daño por impacto de rayo: {:d}- {:d}" -#: Source/itemdat.cpp:145 -msgid "Scroll of Search" -msgstr "Pergamino de Búsqueda" +#: Source/items.cpp:3732 +#, c++-format +msgid "{:+d} to strength" +msgstr "{:+d} a la fuerza" -#: Source/itemdat.cpp:146 -msgid "Scroll of Lightning" -msgstr "Pergamino de Relámpago" +#: Source/items.cpp:3735 +#, c++-format +msgid "{:+d} to magic" +msgstr "{:+d} a la magia" -#: Source/itemdat.cpp:149 -msgid "Scroll of Fire Wall" -msgstr "Pergamino de Muro de Fuego" +#: Source/items.cpp:3738 +#, c++-format +msgid "{:+d} to dexterity" +msgstr "{:+d} a la destreza" -#: Source/itemdat.cpp:150 -msgid "Scroll of Inferno" -msgstr "Pergamino de Infierno" +#: Source/items.cpp:3741 +#, c++-format +msgid "{:+d} to vitality" +msgstr "{:+d} a la vitalidad" -#: Source/itemdat.cpp:152 -msgid "Scroll of Flash" -msgstr "Pergamino de Flash" +#: Source/items.cpp:3744 +#, c++-format +msgid "{:+d} to all attributes" +msgstr "{:+d} a todos los atributos" -#: Source/itemdat.cpp:153 -msgid "Scroll of Infravision" -msgstr "Pergamino de Infravisión" +#: Source/items.cpp:3747 +#, c++-format +msgid "{:+d} damage from enemies" +msgstr "{:+d} de daño de enemigos" -#: Source/itemdat.cpp:154 -msgid "Scroll of Phasing" -msgstr "Pergamino de Fase" +#: Source/items.cpp:3750 +#, c++-format +msgid "Hit Points: {:+d}" +msgstr "Puntos de Vida: {:+d}" -#: Source/itemdat.cpp:155 -msgid "Scroll of Mana Shield" -msgstr "Pergamino de Escudo de Maná" +#: Source/items.cpp:3753 +#, c++-format +msgid "Mana: {:+d}" +msgstr "Maná: {:+d}" -#: Source/itemdat.cpp:156 -msgid "Scroll of Flame Wave" -msgstr "Pergamino de Ola de Llamas" +#: Source/items.cpp:3755 +msgid "high durability" +msgstr "alta durabilidad" -#: Source/itemdat.cpp:157 -msgid "Scroll of Fireball" -msgstr "Pergamino de Bola de Fuego" +#: Source/items.cpp:3757 +msgid "decreased durability" +msgstr "durabilidad disminuida" -#: Source/itemdat.cpp:158 -msgid "Scroll of Stone Curse" -msgstr "Pergamino de Maldición de Piedra" +#: Source/items.cpp:3759 +msgid "indestructible" +msgstr "indestructible" -#: Source/itemdat.cpp:159 -msgid "Scroll of Chain Lightning" -msgstr "Pergamino de Cadena de Relámpagos" +#: Source/items.cpp:3761 +#, no-c-format, c++-format +msgid "+{:d}% light radius" +msgstr "+{:d}% radio de luz" -#: Source/itemdat.cpp:160 -msgid "Scroll of Guardian" -msgstr "Pergamino de Guardián" +#: Source/items.cpp:3763 +#, no-c-format, c++-format +msgid "-{:d}% light radius" +msgstr "-{:d}% radio de luz" -#: Source/itemdat.cpp:162 -msgid "Scroll of Nova" -msgstr "Pergamino de Nova" +#: Source/items.cpp:3765 +msgid "multiple arrows per shot" +msgstr "múltiples flechas por disparo" -#: Source/itemdat.cpp:163 -msgid "Scroll of Golem" -msgstr "Pergamino de Golem" +#: Source/items.cpp:3768 +#, c++-format +msgid "fire arrows damage: {:d}" +msgstr "daño de las flechas de fuego: {:d}" -#: Source/itemdat.cpp:165 -msgid "Scroll of Teleport" -msgstr "Pergamino de Teletransporte" +#: Source/items.cpp:3770 +#, c++-format +msgid "fire arrows damage: {:d}-{:d}" +msgstr "daño de las flechas de fuego: {:d}-{:d}" -#: Source/itemdat.cpp:166 -msgid "Scroll of Apocalypse" -msgstr "Pergamino de Apocalipsis" +#: Source/items.cpp:3773 +#, c++-format +msgid "lightning arrows damage {:d}" +msgstr "daño de las flechas de rayo {:d}" -#: Source/itemdat.cpp:167 Source/itemdat.cpp:168 Source/itemdat.cpp:169 -#: Source/itemdat.cpp:170 -msgid "Book of " -msgstr "Libro de " +#: Source/items.cpp:3775 +#, c++-format +msgid "lightning arrows damage {:d}-{:d}" +msgstr "daño de las flechas de rayo {:d}-{:d}" -#: Source/itemdat.cpp:173 -msgid "Falchion" -msgstr "Chafarote" +#: Source/items.cpp:3778 +#, c++-format +msgid "fireball damage: {:d}" +msgstr "daño de bola de fuego: {:d}" -#: Source/itemdat.cpp:174 -msgid "Scimitar" -msgstr "Cimitarra" +#: Source/items.cpp:3780 +#, c++-format +msgid "fireball damage: {:d}-{:d}" +msgstr "daño de bola de fuego: {:d}-{:d}" -#: Source/itemdat.cpp:175 -msgid "Claymore" -msgstr "Claymore" +#: Source/items.cpp:3782 +msgid "attacker takes 1-3 damage" +msgstr "el atacante recibe 1-3 daños" -#: Source/itemdat.cpp:176 -msgid "Blade" -msgstr "Hoja" +#: Source/items.cpp:3784 +msgid "user loses all mana" +msgstr "el usuario pierde todo el maná" -#: Source/itemdat.cpp:177 -msgid "Sabre" -msgstr "Sable" +#: Source/items.cpp:3786 +msgid "absorbs half of trap damage" +msgstr "absorbe la mitad del daño de la trampa" -#: Source/itemdat.cpp:178 -msgid "Long Sword" -msgstr "Espada Larga" +#: Source/items.cpp:3788 +msgid "knocks target back" +msgstr "hace retroceder al objetivo" -#: Source/itemdat.cpp:179 -msgid "Broad Sword" -msgstr "Espada Ancha" +#: Source/items.cpp:3790 +#, no-c-format +msgid "+200% damage vs. demons" +msgstr "+200% de daño contra demonios" -#: Source/itemdat.cpp:180 -msgid "Bastard Sword" -msgstr "Espada Bastarda" +#: Source/items.cpp:3792 +msgid "All Resistance equals 0" +msgstr "Toda la Resistencia es igual a 0" -#: Source/itemdat.cpp:181 -msgid "Two-Handed Sword" -msgstr "Mandoble" +#: Source/items.cpp:3795 +#, no-c-format +msgid "hit steals 3% mana" +msgstr "el golpe roba 3% de maná" -#: Source/itemdat.cpp:182 -msgid "Great Sword" -msgstr "Gran Espada" +#: Source/items.cpp:3797 +#, no-c-format +msgid "hit steals 5% mana" +msgstr "el golpe roba 5% de maná" -#: Source/itemdat.cpp:183 -msgid "Small Axe" -msgstr "Hacha Pequeña" +#: Source/items.cpp:3801 +#, no-c-format +msgid "hit steals 3% life" +msgstr "el golpe roba el 3% de vida" -#: 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 "Hacha" +#: Source/items.cpp:3803 +#, no-c-format +msgid "hit steals 5% life" +msgstr "el golpe roba 5% de vida" -#: Source/itemdat.cpp:185 -msgid "Large Axe" -msgstr "Hacha Grande" +#: Source/items.cpp:3806 +msgid "penetrates target's armor" +msgstr "penetra la armadura del objetivo" -#: Source/itemdat.cpp:186 -msgid "Broad Axe" -msgstr "Hacha Ancha" +#: Source/items.cpp:3809 +msgid "quick attack" +msgstr "ataque rápido" -#: Source/itemdat.cpp:187 -msgid "Battle Axe" -msgstr "Hacha de Batalla" +#: Source/items.cpp:3811 +msgid "fast attack" +msgstr "ataque rápido" -#: Source/itemdat.cpp:188 -msgid "Great Axe" -msgstr "Gran Hacha" +#: Source/items.cpp:3813 +msgid "faster attack" +msgstr "ataque más rápido" -#: Source/itemdat.cpp:189 Source/itemdat.cpp:190 -msgid "Mace" -msgstr "Maza" +#: Source/items.cpp:3815 +msgid "fastest attack" +msgstr "ataque más rápido posible" -#: Source/itemdat.cpp:190 -msgid "Morning Star" -msgstr "Estrella del Alba" +#: Source/items.cpp:3816 Source/items.cpp:3824 Source/items.cpp:3873 +msgid "Another ability (NW)" +msgstr "Otra habilidad (NW)" -#: Source/itemdat.cpp:191 -msgid "War Hammer" -msgstr "Martillo de Guerra" +#: Source/items.cpp:3819 +msgid "fast hit recovery" +msgstr "recuperación rápida de golpes" -#: Source/itemdat.cpp:191 -msgid "Hammer" -msgstr "Martillo" +#: Source/items.cpp:3821 +msgid "faster hit recovery" +msgstr "recuperación de golpes más rápida" -#: Source/itemdat.cpp:192 -msgid "Spiked Club" -msgstr "Porra con puntas" +#: Source/items.cpp:3823 +msgid "fastest hit recovery" +msgstr "recuperación de golpe más rápida posible" -#: Source/itemdat.cpp:194 -msgid "Flail" -msgstr "Rompecabezas" +#: Source/items.cpp:3826 +msgid "fast block" +msgstr "bloqueo rapido" -#: Source/itemdat.cpp:195 -msgid "Maul" -msgstr "Almádena" +#: Source/items.cpp:3828 +#, c++-format +msgid "adds {:d} point to damage" +msgid_plural "adds {:d} points to damage" +msgstr[0] "agrega {:d} punto al daño" +msgstr[1] "agrega {:d} puntos al daño" -#: 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 "Arco" +#: Source/items.cpp:3830 +msgid "fires random speed arrows" +msgstr "dispara flechas de velocidad aleatoria" -#: Source/itemdat.cpp:197 -msgid "Hunter's Bow" -msgstr "Arco de Cazador" +#: Source/items.cpp:3832 +msgid "unusual item damage" +msgstr "daño inusual del artículo" -#: Source/itemdat.cpp:198 -msgid "Long Bow" -msgstr "Arco Largo" +#: Source/items.cpp:3834 +msgid "altered durability" +msgstr "durabilidad alterada" -#: Source/itemdat.cpp:199 -msgid "Composite Bow" -msgstr "Arco Compuesto" +#: Source/items.cpp:3836 +msgid "one handed sword" +msgstr "espada de una mano" -#: Source/itemdat.cpp:200 -msgid "Short Battle Bow" -msgstr "Arco Corto de Batalla" +#: Source/items.cpp:3838 +msgid "constantly lose hit points" +msgstr "pierde puntos de vida constantemente" -#: Source/itemdat.cpp:201 -msgid "Long Battle Bow" -msgstr "Arco Largo de Batalla" +#: Source/items.cpp:3840 +msgid "life stealing" +msgstr "robo de vida" -#: Source/itemdat.cpp:202 -msgid "Short War Bow" -msgstr "Arco Corto de Guerra" +#: Source/items.cpp:3842 +msgid "no strength requirement" +msgstr "sin requisito de fuerza" -#: Source/itemdat.cpp:203 -msgid "Long War Bow" -msgstr "Arco Largo de Guerra" +#: Source/items.cpp:3847 +#, c++-format +msgid "lightning damage: {:d}" +msgstr "daño por rayo: {:d}" -#: 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:179 -msgid "Staff" -msgstr "Bastón" +#: Source/items.cpp:3849 +#, c++-format +msgid "lightning damage: {:d}-{:d}" +msgstr "daño por rayo: {:d}-{:d}" -#: Source/itemdat.cpp:205 -msgid "Long Staff" -msgstr "Bastón Largo" +#: Source/items.cpp:3851 +msgid "charged bolts on hits" +msgstr "rayos por cada golpe" -#: Source/itemdat.cpp:206 -msgid "Composite Staff" -msgstr "Bastón Plegable" +#: Source/items.cpp:3853 +msgid "occasional triple damage" +msgstr "triple daño ocasional" -#: Source/itemdat.cpp:207 -msgid "Quarter Staff" -msgstr "Bastón de Mando" +#: Source/items.cpp:3855 +#, no-c-format, c++-format +msgid "decaying {:+d}% damage" +msgstr "{:+d}% de daño de descomposición" -#: Source/itemdat.cpp:208 -msgid "War Staff" -msgstr "Bastón de Guerra" - -#: Source/itemdat.cpp:209 Source/itemdat.cpp:210 Source/itemdat.cpp:211 -msgid "Ring" -msgstr "Anillo" +#: Source/items.cpp:3857 +msgid "2x dmg to monst, 1x to you" +msgstr "2x dañ al mes, 1x a ti" -#: Source/itemdat.cpp:212 Source/itemdat.cpp:213 -msgid "Amulet" -msgstr "Amuleto" +#: Source/items.cpp:3859 +#, no-c-format +msgid "Random 0 - 600% damage" +msgstr "Daño aleatorio 0 - 600%" -#: Source/itemdat.cpp:214 -msgid "Rune of Fire" -msgstr "Runa de Fuego" +#: Source/items.cpp:3861 +#, no-c-format, c++-format +msgid "low dur, {:+d}% damage" +msgstr "baja dur, {:+d}% de daño" -#: Source/itemdat.cpp:214 Source/itemdat.cpp:215 Source/itemdat.cpp:216 -#: Source/itemdat.cpp:217 Source/itemdat.cpp:218 -msgid "Rune" -msgstr "Runa" +#: Source/items.cpp:3865 +msgid "extra AC vs demons" +msgstr "extra CA contra demonios" -#: Source/itemdat.cpp:215 -msgid "Rune of Lightning" -msgstr "Runa de Relámpago" +#: Source/items.cpp:3867 +msgid "extra AC vs undead" +msgstr "extra CA contra muertos vivientes" -#: Source/itemdat.cpp:216 -msgid "Greater Rune of Fire" -msgstr "Gran Runa de Fuego" +#: Source/items.cpp:3869 +msgid "50% Mana moved to Health" +msgstr "50% de Maná se movió a Salud" -#: Source/itemdat.cpp:217 -msgid "Greater Rune of Lightning" -msgstr "Gran Runa de Relámpago" +#: Source/items.cpp:3871 +msgid "40% Health moved to Mana" +msgstr "40% de Salud se movió a Maná" -#: Source/itemdat.cpp:218 -msgid "Rune of Stone" -msgstr "Runa de Piedra" +#: Source/items.cpp:3912 Source/items.cpp:3953 +#, c++-format +msgid "damage: {:d} Indestructible" +msgstr "daño: {:d} Indestructible" -#: Source/itemdat.cpp:219 -msgid "Short Staff of Charged Bolt" -msgstr "Bastón Corto de la Centella" +#. TRANSLATORS: Dur: is durability +#: Source/items.cpp:3914 Source/items.cpp:3955 +#, c++-format +msgid "damage: {:d} Dur: {:d}/{:d}" +msgstr "daño: {:d} Dur: {:d}/{:d}" -# ** Adjetivo sensible a cambio de género, terminaciones o/a -#. TRANSLATORS: Item prefix section. -#: Source/itemdat.cpp:229 -msgid "Tin" -msgstr "de estaño" +#: Source/items.cpp:3917 Source/items.cpp:3958 +#, c++-format +msgid "damage: {:d}-{:d} Indestructible" +msgstr "daño: {:d}-{:d} Indestructible" -#: Source/itemdat.cpp:230 -msgid "Brass" -msgstr "de latón" +#. TRANSLATORS: Dur: is durability +#: Source/items.cpp:3919 Source/items.cpp:3960 +#, c++-format +msgid "damage: {:d}-{:d} Dur: {:d}/{:d}" +msgstr "daño: {:d}-{:d} Dur: {:d}/{:d}" -#: Source/itemdat.cpp:231 -msgid "Bronze" -msgstr "de bronce" +#: Source/items.cpp:3924 Source/items.cpp:3970 +#, c++-format +msgid "armor: {:d} Indestructible" +msgstr "defensa: {:d} Indestructible" -#: Source/itemdat.cpp:232 -msgid "Iron" -msgstr "de hierro" +#. TRANSLATORS: Dur: is durability +#: Source/items.cpp:3926 Source/items.cpp:3972 +#, c++-format +msgid "armor: {:d} Dur: {:d}/{:d}" +msgstr "defensa: {:d} Dur: {:d}/{:d}" -#: Source/itemdat.cpp:233 -msgid "Steel" -msgstr "de acero" +#: Source/items.cpp:3929 Source/items.cpp:3963 Source/items.cpp:3976 +#: Source/stores.cpp:299 +#, c++-format +msgid "Charges: {:d}/{:d}" +msgstr "Cargas: {:d}/{:d}" -#: Source/itemdat.cpp:234 -msgid "Silver" -msgstr "de plata" +#: Source/items.cpp:3938 +msgid "unique item" +msgstr "artículo único" -#: Source/itemdat.cpp:236 -msgid "Platinum" -msgstr "de platino" +#: Source/items.cpp:3966 Source/items.cpp:3974 Source/items.cpp:3980 +msgid "Not Identified" +msgstr "No Identificado" -#: Source/itemdat.cpp:237 -msgid "Mithril" -msgstr "de mithril" +#: Source/levels/setmaps.cpp:27 +msgid "Skeleton King's Lair" +msgstr "Guarida del Rey Esqueleto" -# ** -#: Source/itemdat.cpp:238 -msgid "Meteoric" -msgstr "meteórico" +#: Source/levels/setmaps.cpp:28 +msgid "Chamber of Bone" +msgstr "Cámara de Hueso" -# ** -#: Source/itemdat.cpp:239 Source/objects.cpp:124 -msgid "Weird" -msgstr "misterioso" +#. TRANSLATORS: Quest Map +#: Source/levels/setmaps.cpp:29 Source/quests.cpp:103 +msgid "Maze" +msgstr "Laberinto" -# ** -#: Source/itemdat.cpp:240 -msgid "Strange" -msgstr "extraño" +#: Source/levels/setmaps.cpp:30 Source/quests.cpp:65 +msgid "Poisoned Water Supply" +msgstr "Red de Agua Envenenada" -# ** -#: Source/itemdat.cpp:241 -msgid "Useless" -msgstr "estropeado" +#: Source/levels/setmaps.cpp:31 +msgid "Archbishop Lazarus' Lair" +msgstr "Guarida del Arzobispo Lazarus" -# ** -#: Source/itemdat.cpp:242 -msgid "Bent" -msgstr "mellado" +#: Source/levels/setmaps.cpp:32 +msgid "Church Arena" +msgstr "Arena de la Iglesia" -# ** -#: Source/itemdat.cpp:243 -msgid "Weak" -msgstr "desgastado" +#: Source/levels/setmaps.cpp:33 +msgid "Hell Arena" +msgstr "Arena Hellfire" -# ** -#: Source/itemdat.cpp:244 -msgid "Jagged" -msgstr "dentado" +#: Source/levels/setmaps.cpp:34 +msgid "Circle of Life Arena" +msgstr "Arena Círculo de la Vida" -#: Source/itemdat.cpp:245 -msgid "Deadly" -msgstr "mortal" +#: Source/levels/trigs.cpp:352 +msgid "Down to dungeon" +msgstr "Bajar a la mazmorra" -# ** -#: Source/itemdat.cpp:246 -msgid "Heavy" -msgstr "pesado" +#: Source/levels/trigs.cpp:361 +msgid "Down to catacombs" +msgstr "Bajar a las catacumbas" -#: Source/itemdat.cpp:247 -msgid "Vicious" -msgstr "atroz" +#: Source/levels/trigs.cpp:371 +msgid "Down to caves" +msgstr "Bajar a las cuevas" -#: Source/itemdat.cpp:248 -msgid "Brutal" -msgstr "brutal" +#: Source/levels/trigs.cpp:381 +msgid "Down to hell" +msgstr "Bajar al infierno" -#: Source/itemdat.cpp:249 -msgid "Massive" -msgstr "descomunal" +#: Source/levels/trigs.cpp:391 +msgid "Down to Hive" +msgstr "Bajar a la colmena" -#: Source/itemdat.cpp:250 -msgid "Savage" -msgstr "cruel" +#: Source/levels/trigs.cpp:401 +msgid "Down to Crypt" +msgstr "Bajar a la Cripta" -#: Source/itemdat.cpp:251 -msgid "Ruthless" -msgstr "implacable" +#: Source/levels/trigs.cpp:416 Source/levels/trigs.cpp:451 +#: Source/levels/trigs.cpp:497 Source/levels/trigs.cpp:549 +#, c++-format +msgid "Up to level {:d}" +msgstr "Sube al nivel {:d}" -# ** -#: Source/itemdat.cpp:252 -msgid "Merciless" -msgstr "despiadado" +#: Source/levels/trigs.cpp:418 Source/levels/trigs.cpp:480 +#: Source/levels/trigs.cpp:532 Source/levels/trigs.cpp:579 +#: Source/levels/trigs.cpp:641 Source/levels/trigs.cpp:690 +#: Source/levels/trigs.cpp:797 +msgid "Up to town" +msgstr "Sube al pueblo" -# ** -#: Source/itemdat.cpp:253 -msgid "Clumsy" -msgstr "roñoso" +#: Source/levels/trigs.cpp:429 Source/levels/trigs.cpp:462 +#: Source/levels/trigs.cpp:514 Source/levels/trigs.cpp:561 +#: Source/levels/trigs.cpp:623 +#, c++-format +msgid "Down to level {:d}" +msgstr "Baja al nivel {:d}" -# ** -#: Source/itemdat.cpp:254 -msgid "Dull" -msgstr "mohoso" +#: Source/levels/trigs.cpp:592 +msgid "Down to Diablo" +msgstr "Baja a Diablo" -# ** -#: Source/itemdat.cpp:255 -msgid "Sharp" -msgstr "afilado" +#: Source/levels/trigs.cpp:610 +#, c++-format +msgid "Up to Nest level {:d}" +msgstr "Sube al nivel de la Colmena {:d}" -# ** -#: Source/itemdat.cpp:256 Source/itemdat.cpp:266 -msgid "Fine" -msgstr "fino" +#: Source/levels/trigs.cpp:658 +#, c++-format +msgid "Up to Crypt level {:d}" +msgstr "Sube al nivel de la Cripta {:d}" -#: Source/itemdat.cpp:257 -msgid "Warrior's" -msgstr "de guerrero" +#: Source/levels/trigs.cpp:668 Source/quests.cpp:74 +msgid "Cornerstone of the World" +msgstr "Piedra Angular del Mundo" -#: Source/itemdat.cpp:258 -msgid "Soldier's" -msgstr "de soldado" +#: Source/levels/trigs.cpp:673 +#, c++-format +msgid "Down to Crypt level {:d}" +msgstr "Baja al nivel de la Cripta {:d}" -#: Source/itemdat.cpp:259 -msgid "Lord's" -msgstr "del señor" +#: Source/levels/trigs.cpp:721 Source/levels/trigs.cpp:735 +#: Source/levels/trigs.cpp:749 +#, c++-format +msgid "Back to Level {:d}" +msgstr "Volver al nivel {:d}" -#: Source/itemdat.cpp:260 -msgid "Knight's" -msgstr "de caballero" +#: Source/loadsave.cpp:2102 Source/loadsave.cpp:2634 +msgid "Unable to open save file archive" +msgstr "No se puede abrir el archivo guardado" -#: Source/itemdat.cpp:261 -msgid "Master's" -msgstr "de maestro" +#: Source/loadsave.cpp:2105 +msgid "Invalid save file" +msgstr "Archivo guardado no válido" -#: Source/itemdat.cpp:262 -msgid "Champion's" -msgstr "de campeón" +#: Source/loadsave.cpp:2136 +msgid "Player is on a Hellfire only level" +msgstr "El jugador está en un nivel único Hellfire" -#: Source/itemdat.cpp:263 -msgid "King's" -msgstr "real" +#: Source/loadsave.cpp:2392 +msgid "Invalid game state" +msgstr "Estado de juego no válido" -#: Source/itemdat.cpp:264 -msgid "Vulnerable" -msgstr "frágil" +#: Source/menu.cpp:155 +msgid "Unable to display mainmenu" +msgstr "No se puede mostrar el menú principal" -# ** -#: Source/itemdat.cpp:265 -msgid "Rusted" -msgstr "oxidado" +#: Source/monster.cpp:2989 +msgid "Animal" +msgstr "Animal" -#: Source/itemdat.cpp:267 -msgid "Strong" -msgstr "fuerte" +#: Source/monster.cpp:2991 +msgid "Demon" +msgstr "Demonio" -# ** -#: Source/itemdat.cpp:268 -msgid "Grand" -msgstr "grandioso" +#: Source/monster.cpp:2993 +msgid "Undead" +msgstr "Muerto viviente" -#: Source/itemdat.cpp:269 -msgid "Valiant" -msgstr "valiente" +#: Source/monster.cpp:4294 +#, c++-format +msgid "Type: {:s} Kills: {:d}" +msgstr "Tipo: {:s} Muertes: {:d}" -# ** -#: Source/itemdat.cpp:270 -msgid "Glorious" -msgstr "glorioso" +#: Source/monster.cpp:4296 +#, c++-format +msgid "Total kills: {:d}" +msgstr "Muertes totales: {:d}" -# ** -#: Source/itemdat.cpp:271 -msgid "Blessed" -msgstr "bendito" +#: Source/monster.cpp:4328 +#, c++-format +msgid "Hit Points: {:d}-{:d}" +msgstr "Puntos de Golpe: {:d}- {:d}" -# ** -#: Source/itemdat.cpp:272 -msgid "Saintly" -msgstr "santo" +#: Source/monster.cpp:4333 +msgid "No magic resistance" +msgstr "Sin resistencia mágica" -# ** -#: Source/itemdat.cpp:273 -msgid "Awesome" -msgstr "asombroso" +#: Source/monster.cpp:4336 +msgid "Resists:" +msgstr "Resiste:" -# ** -#: Source/itemdat.cpp:274 Source/objects.cpp:136 -msgid "Holy" -msgstr "sagrado" +#: Source/monster.cpp:4338 Source/monster.cpp:4348 +msgid " Magic" +msgstr " Magia" -# ** -#: Source/itemdat.cpp:275 -msgid "Godly" -msgstr "piadoso" +#: Source/monster.cpp:4340 Source/monster.cpp:4350 +msgid " Fire" +msgstr " Fuego" -# ** -#: Source/itemdat.cpp:276 -msgid "Red" -msgstr "rojo" +#: Source/monster.cpp:4342 Source/monster.cpp:4352 +msgid " Lightning" +msgstr " Rayo" -#: Source/itemdat.cpp:277 Source/itemdat.cpp:278 -msgid "Crimson" -msgstr "carmesí" +#: Source/monster.cpp:4346 +msgid "Immune:" +msgstr "Inmune:" -#: Source/itemdat.cpp:279 -msgid "Garnet" -msgstr "granate" +#: Source/monster.cpp:4363 +#, c++-format +msgid "Type: {:s}" +msgstr "Tipo: {:s}" -#: Source/itemdat.cpp:280 -msgid "Ruby" -msgstr "rubí" +#: Source/monster.cpp:4368 Source/monster.cpp:4374 +msgid "No resistances" +msgstr "Sin resistencias" -#: Source/itemdat.cpp:281 -msgid "Blue" -msgstr "azul" +#: Source/monster.cpp:4369 Source/monster.cpp:4378 +msgid "No Immunities" +msgstr "Sin Inmunidades" -#: Source/itemdat.cpp:282 -msgid "Azure" -msgstr "celeste" +#: Source/monster.cpp:4372 +msgid "Some Magic Resistances" +msgstr "Algunas Resistencias Mágicas" -#: Source/itemdat.cpp:283 -msgid "Lapis" -msgstr "lapislázuli" +#: Source/monster.cpp:4376 +msgid "Some Magic Immunities" +msgstr "Algunas Inmunidades Mágicas" -#: Source/itemdat.cpp:284 -msgid "Cobalt" -msgstr "cobalto" +#: Source/mpq/mpq_writer.cpp:161 +msgid "Failed to open archive for writing." +msgstr "No se pudo abrir el archivo para escribir." -#: Source/itemdat.cpp:285 -msgid "Sapphire" -msgstr "zafiro" +#: Source/msg.cpp:811 +msgid "Trying to drop a floor item?" +msgstr "¿Está intentando lanzar un objeto al suelo?" -# ** -#: Source/itemdat.cpp:286 -msgid "White" -msgstr "blanco" +#: Source/msg.cpp:1429 +#, c++-format +msgid "{:s} has cast an invalid spell." +msgstr "{:s} ha lanzado un hechizo inválido." -#: Source/itemdat.cpp:287 -msgid "Pearl" -msgstr "perla" +#: Source/msg.cpp:1433 +#, c++-format +msgid "{:s} has cast an illegal spell." +msgstr "{:s} ha lanzado un hechizo ilegal." -#: Source/itemdat.cpp:288 -msgid "Ivory" -msgstr "marfil" +#: Source/msg.cpp:2064 Source/multi.cpp:793 Source/multi.cpp:843 +#, c++-format +msgid "Player '{:s}' (level {:d}) just joined the game" +msgstr "Jugador ' {:s}' (nivel {:d}) acaba de unirse" -#: Source/itemdat.cpp:289 -msgid "Crystal" -msgstr "cristal" +#: Source/msg.cpp:2425 +msgid "The game ended" +msgstr "El juego terminó" -#: Source/itemdat.cpp:290 -msgid "Diamond" -msgstr "diamante" +#: Source/msg.cpp:2431 +msgid "Unable to get level data" +msgstr "No se pueden obtener datos del nivel" -#: Source/itemdat.cpp:291 -msgid "Topaz" -msgstr "topacio" +#: Source/multi.cpp:260 +#, c++-format +msgid "Player '{:s}' just left the game" +msgstr "El jugador ' {:s}' acaba de salir del juego" -#: Source/itemdat.cpp:292 -msgid "Amber" -msgstr "ámbar" +#: Source/multi.cpp:263 +#, c++-format +msgid "Player '{:s}' killed Diablo and left the game!" +msgstr "¡El jugador ' {:s}' mató a Diablo y abandonó el juego!" -#: Source/itemdat.cpp:293 -msgid "Jade" -msgstr "jade" +#: Source/multi.cpp:267 +#, c++-format +msgid "Player '{:s}' dropped due to timeout" +msgstr "El jugador ' {:s}' desconectado debido al tiempo de espera" -#: Source/itemdat.cpp:294 -msgid "Obsidian" -msgstr "obsidiana" +#: Source/multi.cpp:845 +#, c++-format +msgid "Player '{:s}' (level {:d}) is already in the game" +msgstr "El jugador ' {:s}' (nivel {:d}) ya está en el juego" -#: Source/itemdat.cpp:295 -msgid "Emerald" -msgstr "esmeralda" +#. TRANSLATORS: Shrine Name Block +#: Source/objects.cpp:123 +msgid "Mysterious" +msgstr "Misterioso" -#: Source/itemdat.cpp:296 -msgid "Hyena's" -msgstr "de la hiena" +#: Source/objects.cpp:124 +msgid "Hidden" +msgstr "Oculto" -#: Source/itemdat.cpp:297 -msgid "Frog's" -msgstr "de la rana" +#: Source/objects.cpp:125 +msgid "Gloomy" +msgstr "Sombrío" -#: Source/itemdat.cpp:298 -msgid "Spider's" -msgstr "de la araña" +# ** +#: Source/objects.cpp:126 Source/translation_dummy.cpp:610 +msgid "Weird" +msgstr "misterioso" -#: Source/itemdat.cpp:299 -msgid "Raven's" -msgstr "del cuervo" +#: Source/objects.cpp:127 Source/objects.cpp:134 +msgid "Magical" +msgstr "Mágico" -#: Source/itemdat.cpp:300 -msgid "Snake's" -msgstr "de la culebra" +#: Source/objects.cpp:128 +msgid "Stone" +msgstr "Piedra" -#: Source/itemdat.cpp:301 -msgid "Serpent's" -msgstr "de la serpiente" +#: Source/objects.cpp:129 +msgid "Religious" +msgstr "Religioso" -#: Source/itemdat.cpp:302 -msgid "Drake's" -msgstr "del pequeño dragón" +#: Source/objects.cpp:130 +msgid "Enchanted" +msgstr "Encantada" -#: Source/itemdat.cpp:303 -msgid "Dragon's" -msgstr "del dragón" +#: Source/objects.cpp:131 +msgid "Thaumaturgic" +msgstr "Taumatúrgico" -#: Source/itemdat.cpp:304 -msgid "Wyrm's" -msgstr "del gran dragón" +#: Source/objects.cpp:132 +msgid "Fascinating" +msgstr "Fascinante" -#: Source/itemdat.cpp:305 -msgid "Hydra's" -msgstr "de la hidra" +#: Source/objects.cpp:133 +msgid "Cryptic" +msgstr "Críptico" -#: Source/itemdat.cpp:306 -msgid "Angel's" -msgstr "del ángel" +#: Source/objects.cpp:135 +msgid "Eldritch" +msgstr "Espeluznante" -#: Source/itemdat.cpp:307 -msgid "Arch-Angel's" -msgstr "del arcángel" +#: Source/objects.cpp:136 +msgid "Eerie" +msgstr "Inquietante" -#: Source/itemdat.cpp:308 -msgid "Plentiful" -msgstr "cuantioso" +#: Source/objects.cpp:137 +msgid "Divine" +msgstr "Divino" -#: Source/itemdat.cpp:309 -msgid "Bountiful" -msgstr "colmado" +# ** +#: Source/objects.cpp:138 Source/translation_dummy.cpp:645 +msgid "Holy" +msgstr "sagrado" -#: Source/itemdat.cpp:310 -msgid "Flaming" -msgstr "llameante" +#: Source/objects.cpp:139 +msgid "Sacred" +msgstr "Sagrado" -#: Source/itemdat.cpp:311 -msgid "Lightning" -msgstr "centelleante" +#: Source/objects.cpp:140 +msgid "Spiritual" +msgstr "Espiritual" -#: Source/itemdat.cpp:312 -msgid "Jester's" -msgstr "del bufón" +#: Source/objects.cpp:141 +msgid "Spooky" +msgstr "Escalofriante" -# ** -#: Source/itemdat.cpp:313 -msgid "Crystalline" -msgstr "cristalino" +#: Source/objects.cpp:142 +msgid "Abandoned" +msgstr "Abandonado" -#. TRANSLATORS: Item prefix section end. -#: Source/itemdat.cpp:315 -msgid "Doppelganger's" -msgstr "del doppelgänger" +#: Source/objects.cpp:143 +msgid "Creepy" +msgstr "Siniestro" -#. 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 "de la calidad" +#: Source/objects.cpp:144 +msgid "Quiet" +msgstr "Tranquilo" -#: Source/itemdat.cpp:326 -msgid "maiming" -msgstr "de la mutilación" +#: Source/objects.cpp:145 +msgid "Secluded" +msgstr "Aislado" -#: Source/itemdat.cpp:327 -msgid "slaying" -msgstr "del asesinato" +#: Source/objects.cpp:146 +msgid "Ornate" +msgstr "Decorado" -#: Source/itemdat.cpp:328 -msgid "gore" -msgstr "de sangre" +#: Source/objects.cpp:147 +msgid "Glimmering" +msgstr "Resplandeciente" -#: Source/itemdat.cpp:329 -msgid "carnage" -msgstr "de la matanza" +#: Source/objects.cpp:148 +msgid "Tainted" +msgstr "Contaminado" -#: Source/itemdat.cpp:330 -msgid "slaughter" -msgstr "de la tortura" +#: Source/objects.cpp:149 +msgid "Oily" +msgstr "Aceitoso" -#: Source/itemdat.cpp:331 -msgid "pain" -msgstr "del dolor" +#: Source/objects.cpp:150 +msgid "Glowing" +msgstr "Brillante" -#: Source/itemdat.cpp:332 -msgid "tears" -msgstr "del llanto" +#: Source/objects.cpp:151 +msgid "Mendicant's" +msgstr "Del Mendicante" -#: Source/itemdat.cpp:333 -msgid "health" -msgstr "de salud" +#: Source/objects.cpp:152 +msgid "Sparkling" +msgstr "Centelleante" -#: Source/itemdat.cpp:334 -msgid "protection" -msgstr "de protección" +#: Source/objects.cpp:154 +msgid "Shimmering" +msgstr "Reluciente" -#: Source/itemdat.cpp:335 -msgid "absorption" -msgstr "de absorción" +#: Source/objects.cpp:155 +msgid "Solar" +msgstr "Solar" -#: Source/itemdat.cpp:336 -msgid "deflection" -msgstr "de desvío" +#. TRANSLATORS: Shrine Name Block end +#: Source/objects.cpp:157 +msgid "Murphy's" +msgstr "De Murphy" -#: Source/itemdat.cpp:337 -msgid "osmosis" -msgstr "de ósmosis" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:210 +msgid "The Great Conflict" +msgstr "El Gran Conflicto" -#: Source/itemdat.cpp:338 -msgid "frailty" -msgstr "de la endeblez" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:211 +msgid "The Wages of Sin are War" +msgstr "La Paga del Pecado es la Guerra" -#: Source/itemdat.cpp:339 -msgid "weakness" -msgstr "de la debilidad" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:212 +msgid "The Tale of the Horadrim" +msgstr "El Cuento de los Horadrim" -#: Source/itemdat.cpp:340 -msgid "strength" -msgstr "de fuerza" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:213 +msgid "The Dark Exile" +msgstr "El Exilio Oscuro" -#: Source/itemdat.cpp:341 -msgid "might" -msgstr "del poder" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:214 +msgid "The Sin War" +msgstr "La Guerra del Pecado" -#: Source/itemdat.cpp:342 -msgid "power" -msgstr "del buey" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:215 +msgid "The Binding of the Three" +msgstr "La Unión de los Tres" -#: Source/itemdat.cpp:343 -msgid "giants" -msgstr "del gigante" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:216 +msgid "The Realms Beyond" +msgstr "Los Reinos del Más Allá" -#: Source/itemdat.cpp:344 -msgid "titans" -msgstr "del titán" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:217 +msgid "Tale of the Three" +msgstr "Cuento de los Tres" -#: Source/itemdat.cpp:345 -msgid "paralysis" -msgstr "de la parálisis" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:218 +msgid "The Black King" +msgstr "El Rey Negro" -#: Source/itemdat.cpp:346 -msgid "atrophy" -msgstr "de la atrofia" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:219 +msgid "Journal: The Ensorcellment" +msgstr "Diario: El Hechizado" -#: Source/itemdat.cpp:347 -msgid "dexterity" -msgstr "de la destreza" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:220 +msgid "Journal: The Meeting" +msgstr "Diario: El Encuentro" -#: Source/itemdat.cpp:348 -msgid "skill" -msgstr "de la habilidad" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:221 +msgid "Journal: The Tirade" +msgstr "Diario: La Diatriba" -#: Source/itemdat.cpp:349 -msgid "accuracy" -msgstr "de la exactitud" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:222 +msgid "Journal: His Power Grows" +msgstr "Diario: Su Poder Crece" -#: Source/itemdat.cpp:350 -msgid "precision" -msgstr "de la precisión" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:223 +msgid "Journal: NA-KRUL" +msgstr "Diario: NA-KRUL" -#: Source/itemdat.cpp:351 -msgid "perfection" -msgstr "de la perfección" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:224 +msgid "Journal: The End" +msgstr "Diario: El Fin" -#: Source/itemdat.cpp:352 -msgid "the fool" -msgstr "del mentecato" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:225 +msgid "A Spellbook" +msgstr "Un libro de Hechizos" -#: Source/itemdat.cpp:353 -msgid "dyslexia" -msgstr "de la dislexia" +#: Source/objects.cpp:4765 +msgid "Crucified Skeleton" +msgstr "Esqueleto Crucificado" -#: Source/itemdat.cpp:354 -msgid "magic" -msgstr "de la energía" +#: Source/objects.cpp:4769 +msgid "Lever" +msgstr "Palanca" -#: Source/itemdat.cpp:355 -msgid "the mind" -msgstr "de la mente" +#: Source/objects.cpp:4779 +msgid "Open Door" +msgstr "Puerta Abierta" -#: Source/itemdat.cpp:356 -msgid "brilliance" -msgstr "de la brillantez" +#: Source/objects.cpp:4781 +msgid "Closed Door" +msgstr "Puerta Cerrada" -#: Source/itemdat.cpp:357 -msgid "sorcery" -msgstr "de la brujería" +#: Source/objects.cpp:4783 +msgid "Blocked Door" +msgstr "Puerta Bloqueada" -#: Source/itemdat.cpp:358 -msgid "wizardry" -msgstr "de la hechicería" +#: Source/objects.cpp:4788 +msgid "Ancient Tome" +msgstr "Tomo Antiguo" -#: Source/itemdat.cpp:359 -msgid "illness" -msgstr "de la dolencia" +#: Source/objects.cpp:4790 +msgid "Book of Vileness" +msgstr "Libro de la Vileza" -#: Source/itemdat.cpp:360 -msgid "disease" -msgstr "de la enfermedad" +#: Source/objects.cpp:4795 +msgid "Skull Lever" +msgstr "Palanca de Cráneo" -#: Source/itemdat.cpp:361 -msgid "vitality" -msgstr "de la vitalidad" +#: Source/objects.cpp:4797 +msgid "Mythical Book" +msgstr "Libro Mítico" -#: Source/itemdat.cpp:362 -msgid "zest" -msgstr "de la vivacidad" +#: Source/objects.cpp:4800 +msgid "Small Chest" +msgstr "Cofre Pequeño" -#: Source/itemdat.cpp:363 -msgid "vim" -msgstr "del brío" +#: Source/objects.cpp:4803 +msgid "Chest" +msgstr "Cofre" -#: Source/itemdat.cpp:364 -msgid "vigor" -msgstr "del vigor" +#: Source/objects.cpp:4807 +msgid "Large Chest" +msgstr "Arcón" -#: Source/itemdat.cpp:365 -msgid "life" -msgstr "de la vida" +#: Source/objects.cpp:4810 +msgid "Sarcophagus" +msgstr "Sarcófago" -#: Source/itemdat.cpp:366 -msgid "trouble" -msgstr "del problema" +#: Source/objects.cpp:4812 +msgid "Bookshelf" +msgstr "Estante para Libros" -#: Source/itemdat.cpp:367 -msgid "the pit" -msgstr "del hoyo" +#: Source/objects.cpp:4815 +msgid "Bookcase" +msgstr "Estantería" -#: Source/itemdat.cpp:368 -msgid "the sky" -msgstr "del cielo" +#: Source/objects.cpp:4818 +msgid "Barrel" +msgstr "Barril" -#: Source/itemdat.cpp:369 -msgid "the moon" -msgstr "de la luna" +#: Source/objects.cpp:4821 +msgid "Pod" +msgstr "Vaina" -#: Source/itemdat.cpp:370 -msgid "the stars" -msgstr "de las estrellas" +#: Source/objects.cpp:4824 +msgid "Urn" +msgstr "Urna" -#: Source/itemdat.cpp:371 -msgid "the heavens" -msgstr "de los cielos" +#. TRANSLATORS: {:s} will be a name from the Shrine block above +#: Source/objects.cpp:4827 +#, c++-format +msgid "{:s} Shrine" +msgstr "Santuario {:s}" -#: Source/itemdat.cpp:372 -msgid "the zodiac" -msgstr "del zodiaco" +#: Source/objects.cpp:4829 +msgid "Skeleton Tome" +msgstr "Tomo de Esqueleto" -#: Source/itemdat.cpp:373 -msgid "the vulture" -msgstr "del buitre" +#: Source/objects.cpp:4831 +msgid "Library Book" +msgstr "Libro de la Biblioteca" -#: Source/itemdat.cpp:374 -msgid "the jackal" -msgstr "del chacal" +#: Source/objects.cpp:4833 +msgid "Blood Fountain" +msgstr "Fuente de Sangre" -#: Source/itemdat.cpp:375 -msgid "the fox" -msgstr "del zorro" +#: Source/objects.cpp:4835 +msgid "Decapitated Body" +msgstr "Cuerpo Decapitado" -#: Source/itemdat.cpp:376 -msgid "the jaguar" -msgstr "del jaguar" +#: Source/objects.cpp:4837 +msgid "Book of the Blind" +msgstr "Libro de los Ciegos" -#: Source/itemdat.cpp:377 -msgid "the eagle" -msgstr "del águila" +#: Source/objects.cpp:4839 +msgid "Book of Blood" +msgstr "Libro de Sangre" -#: Source/itemdat.cpp:378 -msgid "the wolf" -msgstr "del lobo" +#: Source/objects.cpp:4841 +msgid "Purifying Spring" +msgstr "Manantial Purificante" -#: Source/itemdat.cpp:379 -msgid "the tiger" -msgstr "del tigre" +#: Source/objects.cpp:4844 Source/translation_dummy.cpp:316 +#: Source/translation_dummy.cpp:318 Source/translation_dummy.cpp:320 +#: Source/translation_dummy.cpp:322 +msgid "Armor" +msgstr "Armadura" -#: Source/itemdat.cpp:380 -msgid "the lion" -msgstr "del león" +#: Source/objects.cpp:4846 Source/objects.cpp:4863 +msgid "Weapon Rack" +msgstr "Estante de Armas" -#: Source/itemdat.cpp:381 -msgid "the mammoth" -msgstr "del mamut" +#: Source/objects.cpp:4848 +msgid "Goat Shrine" +msgstr "Santuario de las Cabras" -#: Source/itemdat.cpp:382 -msgid "the whale" -msgstr "de la ballena" +#: Source/objects.cpp:4850 +msgid "Cauldron" +msgstr "Caldero" -#: Source/itemdat.cpp:383 -msgid "fragility" -msgstr "de la fragilidad" +#: Source/objects.cpp:4852 +msgid "Murky Pool" +msgstr "Piscina Turbia" -#: Source/itemdat.cpp:384 -msgid "brittleness" -msgstr "de la inconsistencia" +#: Source/objects.cpp:4854 +msgid "Fountain of Tears" +msgstr "Fuente de las Lágrimas" -#: Source/itemdat.cpp:385 -msgid "sturdiness" -msgstr "de la robustez" +#: Source/objects.cpp:4856 +msgid "Steel Tome" +msgstr "Tomo de Acero" -#: Source/itemdat.cpp:386 -msgid "craftsmanship" -msgstr "de la artesanía" +#: Source/objects.cpp:4858 +msgid "Pedestal of Blood" +msgstr "Pedestal de Sangre" -#: Source/itemdat.cpp:387 -msgid "structure" -msgstr "de la solidez" +#: Source/objects.cpp:4865 +msgid "Mushroom Patch" +msgstr "Parche de Hongos" -#: Source/itemdat.cpp:388 -msgid "the ages" -msgstr "de las eras" +#: Source/objects.cpp:4867 +msgid "Vile Stand" +msgstr "Vil Estante" -#: Source/itemdat.cpp:389 -msgid "the dark" -msgstr "de la oscuridad" +#: Source/objects.cpp:4869 +msgid "Slain Hero" +msgstr "Héroe Asesinado" -#: Source/itemdat.cpp:390 -msgid "the night" -msgstr "de la noche" +#. TRANSLATORS: {:s} will either be a chest or a door +#: Source/objects.cpp:4881 +#, c++-format +msgid "Trapped {:s}" +msgstr "{:s} Trampa" -#: Source/itemdat.cpp:391 -msgid "light" -msgstr "de la luz" +#. TRANSLATORS: If user enabled diablo.ini setting "Disable Crippling Shrines" is set to 1; also used for Na-Kruls lever +#: Source/objects.cpp:4886 +#, c++-format +msgid "{:s} (disabled)" +msgstr "{:s} (deshabilitado)" -#: Source/itemdat.cpp:392 -msgid "radiance" -msgstr "del resplandor" +#: Source/options.cpp:457 Source/options.cpp:581 Source/options.cpp:587 +msgid "ON" +msgstr "ENCENDIDO" -#: Source/itemdat.cpp:393 -msgid "flame" -msgstr "de la llama" +#: Source/options.cpp:457 Source/options.cpp:579 Source/options.cpp:585 +msgid "OFF" +msgstr "APAGADO" -#: Source/itemdat.cpp:394 -msgid "fire" -msgstr "del fuego" +#: Source/options.cpp:569 +msgid "Start Up" +msgstr "Inicio" -#: Source/itemdat.cpp:395 -msgid "burning" -msgstr "de la combustión" +#: Source/options.cpp:569 +msgid "Start Up Settings" +msgstr "Configuraciones de inicio" -#: Source/itemdat.cpp:396 -msgid "shock" -msgstr "de la conmoción" +#: Source/options.cpp:570 +msgid "Game Mode" +msgstr "Modo de juego" -#: Source/itemdat.cpp:397 -msgid "lightning" -msgstr "del relámpago" +#: Source/options.cpp:570 +msgid "Play Diablo or Hellfire." +msgstr "Jugar Diablo o Hellfire." -#: Source/itemdat.cpp:398 -msgid "thunder" -msgstr "del trueno" +#: Source/options.cpp:576 +msgid "Restrict to Shareware" +msgstr "Restringir a modo shareware" -#: Source/itemdat.cpp:399 -msgid "many" -msgstr "de la abundancia" +#: Source/options.cpp:576 +msgid "" +"Makes the game compatible with the demo. Enables multiplayer with friends " +"who don't own a full copy of Diablo." +msgstr "" +"Hace que el juego sea compatible con la demostración. Habilita el modo " +"multijugador con amigos que no poseen una copia completa de Diablo." -#: Source/itemdat.cpp:400 -msgid "plenty" -msgstr "de la infinidad" +#: Source/options.cpp:577 Source/options.cpp:583 +msgid "Intro" +msgstr "Introducción" -#: Source/itemdat.cpp:401 -msgid "thorns" -msgstr "de espinas" +#: Source/options.cpp:577 Source/options.cpp:583 +msgid "Shown Intro cinematic." +msgstr "Mostrar cinemática de introducción." -#: Source/itemdat.cpp:402 -msgid "corruption" -msgstr "de la corrupción" +#: Source/options.cpp:589 +msgid "Splash" +msgstr "Pantalla de bienvenida" -#: Source/itemdat.cpp:403 -msgid "thieves" -msgstr "del ladrón" - -#: Source/itemdat.cpp:404 -msgid "the bear" -msgstr "del oso" +#: Source/options.cpp:589 +msgid "Shown splash screen." +msgstr "Mostrar pantalla de bienvenida." -#: Source/itemdat.cpp:405 -msgid "the bat" -msgstr "del murciélago" +# La traducción no es exacta para reducir el largo. +#: Source/options.cpp:591 +msgid "Logo and Title Screen" +msgstr "Logo y título" -#: Source/itemdat.cpp:406 -msgid "vampires" -msgstr "del vampiro" +#: Source/options.cpp:592 +msgid "Title Screen" +msgstr "Pantalla de título" -#: Source/itemdat.cpp:407 -msgid "the leech" -msgstr "de la sanguijuela" +#: Source/options.cpp:611 +msgid "Diablo specific Settings" +msgstr "Configuraciones específicas de Diablo" -#: Source/itemdat.cpp:408 -msgid "blood" -msgstr "de la langosta" +#: Source/options.cpp:625 +msgid "Hellfire specific Settings" +msgstr "Configuraciones específicas de Hellfire" -#: Source/itemdat.cpp:409 -msgid "piercing" -msgstr "de la perforación" +#: Source/options.cpp:639 +msgid "Audio" +msgstr "Audio" -#: Source/itemdat.cpp:410 -msgid "puncturing" -msgstr "del pinchazo" +#: Source/options.cpp:639 +msgid "Audio Settings" +msgstr "Configuraciones de audio" -#: Source/itemdat.cpp:411 -msgid "bashing" -msgstr "del golpe" +#: Source/options.cpp:642 +msgid "Walking Sound" +msgstr "Sonido al caminar" -#: Source/itemdat.cpp:412 -msgid "readiness" -msgstr "de la buena voluntad" +#: Source/options.cpp:642 +msgid "Player emits sound when walking." +msgstr "Los jugadores emiten sonido cuando caminan." -#: Source/itemdat.cpp:413 -msgid "swiftness" -msgstr "de la presteza" +#: Source/options.cpp:643 +msgid "Auto Equip Sound" +msgstr "Sonido de auto equipo" -#: Source/itemdat.cpp:414 -msgid "speed" -msgstr "de la velocidad" +#: Source/options.cpp:643 +msgid "Automatically equipping items on pickup emits the equipment sound." +msgstr "" +"Al equiparse automáticamente con un objeto se emite el sonido de " +"equipamiento." -#: Source/itemdat.cpp:415 -msgid "haste" -msgstr "de la rapidez" +#: Source/options.cpp:644 +msgid "Item Pickup Sound" +msgstr "Sonido de recoger objeto" -#: Source/itemdat.cpp:416 -msgid "balance" -msgstr "del equilibrio" +#: Source/options.cpp:644 +msgid "Picking up items emits the items pickup sound." +msgstr "Al recoger un objeto se emite el sonido de recoger." -#: Source/itemdat.cpp:417 -msgid "stability" -msgstr "de la estabilidad" +#: Source/options.cpp:645 +msgid "Sample Rate" +msgstr "Frecuencia de muestreo" -#: Source/itemdat.cpp:418 -msgid "harmony" -msgstr "de la armonía" +#: Source/options.cpp:645 +msgid "Output sample rate (Hz)." +msgstr "Frecuencia de muestreo de salida (Hz)." -#: Source/itemdat.cpp:419 -msgid "blocking" -msgstr "de bloqueo" +#: Source/options.cpp:646 +msgid "Channels" +msgstr "Canales" -#: Source/itemdat.cpp:420 -msgid "devastation" -msgstr "de la devastación" +#: Source/options.cpp:646 +msgid "Number of output channels." +msgstr "Número de canales de salida." -#: Source/itemdat.cpp:421 -msgid "decay" -msgstr "de la decadencia" +#: Source/options.cpp:647 +msgid "Buffer Size" +msgstr "Tamaño del buffer" -#. TRANSLATORS: Item suffix section end. -#: Source/itemdat.cpp:423 -msgid "peril" -msgstr "del riesgo" +#: Source/options.cpp:647 +msgid "Buffer size (number of frames per channel)." +msgstr "Tamaño del buffer (numero de cuadros por segundo)." -#. TRANSLATORS: Unique Item section -#: Source/itemdat.cpp:433 -msgid "The Butcher's Cleaver" -msgstr "La Cuchilla del Carnicero" +#: Source/options.cpp:648 +msgid "Resampling Quality" +msgstr "Calidad de remuestreo" -#: Source/itemdat.cpp:443 -msgid "The Rift Bow" -msgstr "El Arco de la Desaveniencia" +#: Source/options.cpp:648 +msgid "Quality of the resampler, from 0 (lowest) to 10 (highest)." +msgstr "Calidad de remuestreo, desde 0 (el más bajo) a 10 (el más alto)." -#: Source/itemdat.cpp:444 -msgid "The Needler" -msgstr "El Insoportable" +#: Source/options.cpp:679 +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 "" +"Afecta a la resolución interna del juego y determina su área de visión. " +"Nota: Esto puede diferir de la resolución de la pantalla cuando se utiliza " +"Aumento de escala, Escala de entera o Ajustar a la pantalla." -#: Source/itemdat.cpp:445 -msgid "The Celestial Bow" -msgstr "El Arco Celestial" +#: Source/options.cpp:825 +msgid "Resampler" +msgstr "Remuestreador" -#: Source/itemdat.cpp:446 -msgid "Deadly Hunter" -msgstr "Cazador Mortal" +#: Source/options.cpp:825 +msgid "Audio resampler" +msgstr "Remuestreador de audio" -#: Source/itemdat.cpp:447 -msgid "Bow of the Dead" -msgstr "Arco de la Muerte" +#: Source/options.cpp:882 +msgid "Device" +msgstr "Dispositivo" -#: Source/itemdat.cpp:448 -msgid "The Blackoak Bow" -msgstr "El Arco de Roble negro" +#: Source/options.cpp:882 +msgid "Audio device" +msgstr "Dispositivo de audio" -#: Source/itemdat.cpp:449 -msgid "Flamedart" -msgstr "Dardo de Fuego" +#: Source/options.cpp:950 +msgid "Graphics" +msgstr "Gráficos" -#: Source/itemdat.cpp:450 -msgid "Fleshstinger" -msgstr "Aguijoneador de Carne" +#: Source/options.cpp:950 +msgid "Graphics Settings" +msgstr "Configuración de gráficos" -#: Source/itemdat.cpp:451 -msgid "Windforce" -msgstr "Fuerza del Viento" +#: Source/options.cpp:951 +msgid "Fullscreen" +msgstr "Pantalla completa" -#: Source/itemdat.cpp:452 -msgid "Eaglehorn" -msgstr "Cuerno de Águila" +#: Source/options.cpp:951 +msgid "Display the game in windowed or fullscreen mode." +msgstr "Muestra el juego en modo ventana o pantalla completa." -#: Source/itemdat.cpp:453 -msgid "Gonnagal's Dirk" -msgstr "Puñal de Gonnagal" +#: Source/options.cpp:953 +msgid "Fit to Screen" +msgstr "Ajustar a la pantalla" -#: Source/itemdat.cpp:454 -msgid "The Defender" -msgstr "El Defensor" +#: Source/options.cpp:953 +msgid "" +"Automatically adjust the game window to your current desktop screen aspect " +"ratio and resolution." +msgstr "" +"Ajusta automáticamente la venta del juego a la relación de aspecto y " +"resolución del escritorio actual." -#: Source/itemdat.cpp:455 -msgid "Gryphon's Claw" -msgstr "Garra del Grifo" +#: Source/options.cpp:956 +msgid "Upscale" +msgstr "Aumento de escala" -#: Source/itemdat.cpp:456 -msgid "Black Razor" -msgstr "Navaja Negra" +#: Source/options.cpp:956 +msgid "" +"Enables image scaling from the game resolution to your monitor resolution. " +"Prevents changing the monitor resolution and allows window resizing." +msgstr "" +"Permite escalar la imagen de la resolución del juego a la resolución de su " +"monitor. Evita cambiar la resolución del monitor y permite cambiar el tamaño " +"de la ventana." -#: Source/itemdat.cpp:457 -msgid "Gibbous Moon" -msgstr "Luna Gibosa" +#: Source/options.cpp:963 +msgid "Scaling Quality" +msgstr "Calidad de escalado" -#: Source/itemdat.cpp:458 -msgid "Ice Shank" -msgstr "Mango de Hielo" +#: Source/options.cpp:963 +msgid "Enables optional filters to the output image when upscaling." +msgstr "" +"Habilita filtros opcionales para la imagen de salida al ampliar la escala." -#: Source/itemdat.cpp:459 -msgid "The Executioner's Blade" -msgstr "La Espada del Verdugo" +#: Source/options.cpp:965 +msgid "Nearest Pixel" +msgstr "Pixel más cercano" -#: Source/itemdat.cpp:460 -msgid "The Bonesaw" -msgstr "La Sierra de Hueso" +#: Source/options.cpp:966 +msgid "Bilinear" +msgstr "Bilineal" -#: Source/itemdat.cpp:461 -msgid "Shadowhawk" -msgstr "Halcón de las Sombras" +#: Source/options.cpp:967 +msgid "Anisotropic" +msgstr "Anisotrópico" -#: Source/itemdat.cpp:462 -msgid "Wizardspike" -msgstr "Pico de Mago" +#: Source/options.cpp:969 +msgid "Integer Scaling" +msgstr "Escalado entero" -#: Source/itemdat.cpp:463 -msgid "Lightsabre" -msgstr "Sable de Luz" +#: Source/options.cpp:969 +msgid "Scales the image using whole number pixel ratio." +msgstr "Escala la imagen usando una proporción de píxeles de números enteros." -#: Source/itemdat.cpp:464 -msgid "The Falcon's Talon" -msgstr "La Garra del Halcón" +#: Source/options.cpp:976 +msgid "Vertical Sync" +msgstr "Sincronismo vertical" -#: Source/itemdat.cpp:465 -msgid "Inferno" -msgstr "Infierno" +#: Source/options.cpp:977 +msgid "" +"Forces waiting for Vertical Sync. Prevents tearing effect when drawing a " +"frame. Disabling it can help with mouse lag on some systems." +msgstr "" +"Fuerzas la espera de sincronización vertical. Evita el efecto de desgarro al " +"dibujar un marco. Deshabilitarlo puede ayudar con el retraso del mouse en " +"algunos sistemas." -#: Source/itemdat.cpp:466 -msgid "Doombringer" -msgstr "Portador de Destrucción" +#: Source/options.cpp:986 +msgid "Zoom on when enabled." +msgstr "Acercar cuando está habilitado." -#: Source/itemdat.cpp:467 -msgid "The Grizzly" -msgstr "El Grizzly" +#: Source/options.cpp:987 +msgid "Color Cycling" +msgstr "Ciclo de color" -#: Source/itemdat.cpp:468 -msgid "The Grandfather" -msgstr "El Abuelo" +#: Source/options.cpp:987 +msgid "Color cycling effect used for water, lava, and acid animation." +msgstr "" +"Efecto de ciclo de color usado para la animación del agua, lava y ácido." -#: Source/itemdat.cpp:469 -msgid "The Mangler" -msgstr "El Destrozador" +#: Source/options.cpp:988 +msgid "Alternate nest art" +msgstr "Alternar arte alternativa" -#: Source/itemdat.cpp:470 -msgid "Sharp Beak" -msgstr "Pico Afilado" +#: Source/options.cpp:988 +msgid "The game will use an alternative palette for Hellfire’s nest tileset." +msgstr "" +"El juego usará una paleta alternativa para el juego de fichas alternativo de " +"Hellfire." -#: Source/itemdat.cpp:471 -msgid "BloodSlayer" -msgstr "Asesino de Sangre" +#: Source/options.cpp:990 +msgid "Hardware Cursor" +msgstr "Cursor por hardware" -#: Source/itemdat.cpp:472 -msgid "The Celestial Axe" -msgstr "El Hacha Celestial" +#: Source/options.cpp:990 +msgid "Use a hardware cursor" +msgstr "Usa el cursor por hardware" -#: Source/itemdat.cpp:473 -msgid "Wicked Axe" -msgstr "Hacha Malvada" +# la traduccion debe ser corta para que entre en el cuado de menu +#: Source/options.cpp:991 +msgid "Hardware Cursor For Items" +msgstr "Cursor por hw para objetos" -#: Source/itemdat.cpp:474 -msgid "Stonecleaver" -msgstr "Cuchilla de Piedra" +#: Source/options.cpp:991 +msgid "Use a hardware cursor for items." +msgstr "Usa el cursos por hardware para los objetos." -#: Source/itemdat.cpp:475 -msgid "Aguinara's Hatchet" -msgstr "Destral de Aguinara" +# Mantener corto. Menu de configuración +#: Source/options.cpp:992 +msgid "Hardware Cursor Maximum Size" +msgstr "Tamaño máx del cursor por hardw" -#: Source/itemdat.cpp:476 -msgid "Hellslayer" -msgstr "Asesino del Infierno" +#: Source/options.cpp:992 +msgid "" +"Maximum width / height for the hardware cursor. Larger cursors fall back to " +"software." +msgstr "" +"Máximo ancho/alto del cursor por hardware. Cursores muy grandes se " +"sustituyen por software." -#: Source/itemdat.cpp:477 -msgid "Messerschmidt's Reaver" -msgstr "Atracador de Messerschmidt" +#: Source/options.cpp:994 +msgid "FPS Limiter" +msgstr "Limitador de FPS" -#: Source/itemdat.cpp:478 -msgid "Crackrust" -msgstr "Crackrust" +#: Source/options.cpp:994 +msgid "FPS is limited to avoid high CPU load. Limit considers refresh rate." +msgstr "" +"Los FPS están limitados para evitar altas cargas de CPU. El límite considera " +"la frecuencia de actualización." -#: Source/itemdat.cpp:479 -msgid "Hammer of Jholm" -msgstr "Martillo de Jholm" +#: Source/options.cpp:995 +msgid "Show FPS" +msgstr "Mostrar los FPS" -#: Source/itemdat.cpp:480 -msgid "Civerb's Cudgel" -msgstr "Garrote de Civerb" +#: Source/options.cpp:995 +msgid "Displays the FPS in the upper left corner of the screen." +msgstr "Muestra los FPS en la esquina superior izquierda de la pantalla." -#: Source/itemdat.cpp:481 -msgid "The Celestial Star" -msgstr "La Estrella Celestial" +#: Source/options.cpp:1043 +msgid "Gameplay" +msgstr "Juego" -#: Source/itemdat.cpp:482 -msgid "Baranar's Star" -msgstr "Estrella de Baranar" +#: Source/options.cpp:1043 +msgid "Gameplay Settings" +msgstr "Configuraciones durante el juego" -#: Source/itemdat.cpp:483 -msgid "Gnarled Root" -msgstr "Raíz Nudosa" +#: Source/options.cpp:1045 +msgid "" +"Enable jogging/fast walking in town for Diablo and Hellfire. This option was " +"introduced in the expansion." +msgstr "" +"Habillita trotar/caminar veloz en el pueblo para Diablo y Hellfire. Esta " +"opción fue introducida en la expansión." -#: Source/itemdat.cpp:484 -msgid "The Cranium Basher" -msgstr "El Aplastador de Cráneo" +#: Source/options.cpp:1046 +msgid "Grab Input" +msgstr "Capturar entrada" -#: Source/itemdat.cpp:485 -msgid "Schaefer's Hammer" -msgstr "Martillo de Schaefer" +#: Source/options.cpp:1046 +msgid "When enabled mouse is locked to the game window." +msgstr "Cuando está habilitado el mouse se captura en la ventana de juego." -#: Source/itemdat.cpp:486 -msgid "Dreamflange" -msgstr "Brida de Ensueño" +#: Source/options.cpp:1047 +msgid "Enable Little Girl quest." +msgstr "Habilita la misión de la Niñita." -#: Source/itemdat.cpp:487 -msgid "Staff of Shadows" -msgstr "Bastón de las Sombras" +#: Source/options.cpp:1048 +msgid "" +"Enable Jersey's quest. Lester the farmer is replaced by the Complete Nut." +msgstr "" +"Habilita la misión de Jersey. El granjero Lester es reemplazado por el Loco " +"de Remate." -#: Source/itemdat.cpp:488 -msgid "Immolator" -msgstr "Inmolador" +#: Source/options.cpp:1049 +msgid "Friendly Fire" +msgstr "Fuego amigo" -#: Source/itemdat.cpp:489 -msgid "Storm Spire" -msgstr "Aguja de la Tormenta" +#: Source/options.cpp:1049 +msgid "" +"Allow arrow/spell damage between players in multiplayer even when the " +"friendly mode is on." +msgstr "" +"Permite que las flechas/hechizos produzcan daño entre los jugadores en un " +"juego multijugador aun cuando el modo amigo está habilitado." -#: Source/itemdat.cpp:490 -msgid "Gleamsong" -msgstr "Canción de Brillo" +#: Source/options.cpp:1050 +msgid "Full quests in Multiplayer" +msgstr "Misiones completas en multijugador" -#: Source/itemdat.cpp:491 -msgid "Thundercall" -msgstr "Llamada de Trueno" +#: Source/options.cpp:1050 +msgid "Enables the full/uncut singleplayer version of quests." +msgstr "" +"Habilita la versión completa/sin cortes de las misiones para un jugador." -#: Source/itemdat.cpp:492 -msgid "The Protector" -msgstr "El Protector" +#: Source/options.cpp:1051 +msgid "Test Bard" +msgstr "Probar Bardo" -#: Source/itemdat.cpp:493 -msgid "Naj's Puzzler" -msgstr "Rompecabezas de Naj" +#: Source/options.cpp:1051 +msgid "Force the Bard character type to appear in the hero selection menu." +msgstr "" +"Fuerza al tipo de personaje Bardo a aparecer en el menú de selección del " +"personaje." -#: Source/itemdat.cpp:494 -msgid "Mindcry" -msgstr "Mindcry" +#: Source/options.cpp:1052 +msgid "Test Barbarian" +msgstr "Probar Bárbaro" -#: Source/itemdat.cpp:495 -msgid "Rod of Onan" -msgstr "Vara de Onan" +#: Source/options.cpp:1052 +msgid "" +"Force the Barbarian character type to appear in the hero selection menu." +msgstr "" +"Fuerza al tipo de personaje Bárbaro a aparecer en el menú de selección del " +"personaje." -#: Source/itemdat.cpp:496 -msgid "Helm of Spirits" -msgstr "Yelmo de los Espíritus" +#: Source/options.cpp:1053 +msgid "Experience Bar" +msgstr "Barra de experiencia" -#: Source/itemdat.cpp:497 -msgid "Thinking Cap" -msgstr "Gorro del Pensamiento" +#: Source/options.cpp:1053 +msgid "Experience Bar is added to the UI at the bottom of the screen." +msgstr "" +"Se agrega la barra de experiencia a la UI en la parte inferior de la " +"pantalla." -#: Source/itemdat.cpp:498 -msgid "OverLord's Helm" -msgstr "Yelmo del Señor Supremo" +#: Source/options.cpp:1054 +msgid "Show Item Graphics in Stores" +msgstr "Mostrar gráficos de artículos en tiendas" -#: Source/itemdat.cpp:499 -msgid "Fool's Crest" -msgstr "Cresta del Tonto" +#: Source/options.cpp:1054 +msgid "Show item graphics to the left of item descriptions in store menus." +msgstr "" +"Mostrar gráficos de artículos a la izquierda de las descripciones de los " +"artículos en los menús de la tienda." -#: Source/itemdat.cpp:500 -msgid "Gotterdamerung" -msgstr "El Ocaso de los Dioses" +#: Source/options.cpp:1055 +msgid "Show health values" +msgstr "Muestra valores de salud" -#: Source/itemdat.cpp:501 -msgid "Royal Circlet" -msgstr "Aro Real" +#: Source/options.cpp:1055 +msgid "Displays current / max health value on health globe." +msgstr "Muestra el valor de salud actual/máximo en el globo de salud." -#: Source/itemdat.cpp:502 -msgid "Torn Flesh of Souls" -msgstr "Carne de Almas Desgarradas" +#: Source/options.cpp:1056 +msgid "Show mana values" +msgstr "Muestra valores de maná" -#: Source/itemdat.cpp:503 -msgid "The Gladiator's Bane" -msgstr "El Flagelo del Gladiador" +#: Source/options.cpp:1056 +msgid "Displays current / max mana value on mana globe." +msgstr "Muestra el valor de maná actual/máximo en el globo de maná." -#: Source/itemdat.cpp:504 -msgid "The Rainbow Cloak" -msgstr "La Capa Arcoíris" +#: Source/options.cpp:1057 +msgid "Enemy Health Bar" +msgstr "Barra de salud del enemigo" -#: Source/itemdat.cpp:505 -msgid "Leather of Aut" -msgstr "Armadura de Cuero" +#: Source/options.cpp:1057 +msgid "Enemy Health Bar is displayed at the top of the screen." +msgstr "" +"Se muestra la barra de salud del enemigo en la parte superior de la pantalla." -#: Source/itemdat.cpp:506 -msgid "Wisdom's Wrap" -msgstr "Manto de la Sabiduría" +#: Source/options.cpp:1058 +msgid "Gold is automatically collected when in close proximity to the player." +msgstr "" +"El oro es automáticamente recogido cuando se encuentra en proximidad del " +"jugador." -#: Source/itemdat.cpp:507 -msgid "Sparking Mail" -msgstr "Coraza Brillante" +#: Source/options.cpp:1059 +msgid "" +"Elixirs are automatically collected when in close proximity to the player." +msgstr "" +"Los elixires son automáticamente recogidos cuando se encuentran en " +"proximidad del jugador." -#: Source/itemdat.cpp:508 -msgid "Scavenger Carapace" -msgstr "Caparazón de Carroñero" +#: Source/options.cpp:1060 +msgid "Oils are automatically collected when in close proximity to the player." +msgstr "Los aceites se recogen automáticamente cuando está cerca del jugador." -#: Source/itemdat.cpp:509 -msgid "Nightscape" -msgstr "Paisaje Nocturno" +#: Source/options.cpp:1061 +msgid "Automatically pickup items in town." +msgstr "Automáticamente recoge los objetos en el pueblo." -#: Source/itemdat.cpp:510 -msgid "Naj's Light Plate" -msgstr "Armadura Liviana de Naj" +#: Source/options.cpp:1062 +msgid "Adria will refill your mana when you visit her shop." +msgstr "Adria recargará tu maná cuando la visites en su tienda." -#: Source/itemdat.cpp:511 -msgid "Demonspike Coat" -msgstr "Manto de Demonspike" +#: Source/options.cpp:1063 +msgid "" +"Weapons will be automatically equipped on pickup or purchase if enabled." +msgstr "" +"Si está habilitado las armas serán automáticamente equipadas al recogerse o " +"comprarse." -#: Source/itemdat.cpp:512 -msgid "The Deflector" -msgstr "El Deflector" +#: Source/options.cpp:1064 +msgid "Armor will be automatically equipped on pickup or purchase if enabled." +msgstr "" +"Si está habilitado las armaduras serán automáticamente equipadas al " +"recogerse o comprarse." -#: Source/itemdat.cpp:513 -msgid "Split Skull Shield" -msgstr "Escudo de Cráneo Dividido" +#: Source/options.cpp:1065 +msgid "Helms will be automatically equipped on pickup or purchase if enabled." +msgstr "" +"Si está habilitado los yelmos serán automáticamente equipadas al recogerse o " +"comprarse." -#: Source/itemdat.cpp:514 -msgid "Dragon's Breach" -msgstr "Brecha del Dragón" +#: Source/options.cpp:1066 +msgid "" +"Shields will be automatically equipped on pickup or purchase if enabled." +msgstr "" +"Si está habilitado los escudos serán automáticamente equipadas al recogerse " +"o comprarse." -#: Source/itemdat.cpp:515 -msgid "Blackoak Shield" -msgstr "Escudo de Roble Negro" +#: Source/options.cpp:1067 +msgid "" +"Jewelry will be automatically equipped on pickup or purchase if enabled." +msgstr "" +"Si está habilitado las joyerías serán automáticamente equipadas al recogerse " +"o comprarse." -#: Source/itemdat.cpp:516 -msgid "Holy Defender" -msgstr "Santo Defensor" +#: Source/options.cpp:1068 +msgid "Randomly selecting available quests for new games." +msgstr "" +"Selecciona aleatoriamente las misiones disponibles en las nuevas partidas." -#: Source/itemdat.cpp:517 -msgid "Stormshield" -msgstr "Escudo de Tormenta" +#: Source/options.cpp:1069 +msgid "Show Monster Type" +msgstr "Mostrar el tipo de monstruo" -#: Source/itemdat.cpp:518 -msgid "Bramble" -msgstr "Zarza" +#: Source/options.cpp:1069 +msgid "" +"Hovering over a monster will display the type of monster in the description " +"box in the UI." +msgstr "" +"Al pasar el cursor sobre un monstruo, se mostrará el tipo de monstruo en el " +"cuadro de descripción de la interfaz de usuario." -#: Source/itemdat.cpp:519 -msgid "Ring of Regha" -msgstr "Anillo de Regha" +#: Source/options.cpp:1070 +msgid "Show labels for items on the ground when enabled." +msgstr "Mostrar etiquetas para artículos en el suelo cuando está habilitado." -#: Source/itemdat.cpp:520 -msgid "The Bleeder" -msgstr "El Sangrante" +#: Source/options.cpp:1071 +msgid "Refill belt from inventory when belt item is consumed." +msgstr "" +"Recarga el cinturón desde el inventario cuando el objeto ha sido consumido." -#: Source/itemdat.cpp:521 -msgid "Constricting Ring" -msgstr "Anillo de Constricción" +#: Source/options.cpp:1072 +msgid "" +"When enabled Cauldrons, Fascinating Shrines, Goat Shrines, Ornate Shrines, " +"Sacred Shrines and Murphy's Shrines are not able to be clicked on and " +"labeled as disabled." +msgstr "" +"Cuando están habilitados, no se puede hacer clic en Calderos, Santuarios " +"fascinantes, Santuarios de cabras, Santuarios ornamentados, Santuarios " +"sagrados y Santuarios de Murphy y etiquetarlos como deshabilitados." -#: Source/itemdat.cpp:522 -msgid "Ring of Engagement" -msgstr "Anillo de Compromiso" +#: Source/options.cpp:1073 +msgid "Quick Cast" +msgstr "Lanzamiento rápido" -#: Source/itemdat.cpp:523 -msgid "Giant's Knuckle" -msgstr "Nudillo de Gigante" +#: Source/options.cpp:1073 +msgid "" +"Spell hotkeys instantly cast the spell, rather than switching the readied " +"spell." +msgstr "" +"Las teclas rápidas de hechizos los lanzan inmediatamente en vez de cambiar a " +"hechizo leído." -#: Source/itemdat.cpp:524 -msgid "Mercurial Ring" -msgstr "Anillo Mercurial" +#: Source/options.cpp:1074 +msgid "Number of Healing potions to pick up automatically." +msgstr "Número de pociones de salud que se recogerán automáticamente." -#: Source/itemdat.cpp:525 -msgid "Xorine's Ring" -msgstr "Anillo de Xorine" +#: Source/options.cpp:1075 +msgid "Number of Full Healing potions to pick up automatically." +msgstr "Número de pociones de salud completa que se recogerán automáticamente." -#: Source/itemdat.cpp:526 -msgid "Karik's Ring" -msgstr "Anillo de Karik" +#: Source/options.cpp:1076 +msgid "Number of Mana potions to pick up automatically." +msgstr "Número de pociones de maná que se recogerán automáticamente." -#: Source/itemdat.cpp:527 -msgid "Ring of Magma" -msgstr "Anillo de Magma" +#: Source/options.cpp:1077 +msgid "Number of Full Mana potions to pick up automatically." +msgstr "Número de pociones de maná completo que se recogerán automáticamente." -#: Source/itemdat.cpp:528 -msgid "Ring of the Mystics" -msgstr "Anillo de los Místicos" +#: Source/options.cpp:1078 +msgid "Number of Rejuvenation potions to pick up automatically." +msgstr "" +"Número de pociones de rejuvenecimiento que se recogerán automáticamente." -#: Source/itemdat.cpp:529 -msgid "Ring of Thunder" -msgstr "Anillo de Trueno" +#: Source/options.cpp:1079 +msgid "Number of Full Rejuvenation potions to pick up automatically." +msgstr "" +"Número de pociones de rejuvenecimiento completo que se recogerán " +"automáticamente." -#: Source/itemdat.cpp:530 -msgid "Amulet of Warding" -msgstr "Amuleto de Protección" +#: Source/options.cpp:1080 +msgid "Enable floating numbers" +msgstr "Habilitar números flotantes" -#: Source/itemdat.cpp:531 -msgid "Gnat Sting" -msgstr "Picadura de Mosquito" +#: Source/options.cpp:1080 +msgid "Enables floating numbers on gaining XP / dealing damage etc." +msgstr "Habilita números flotantes al ganar XP/infligir daño, etc." -#: Source/itemdat.cpp:532 -msgid "Flambeau" -msgstr "Antorcha" +#: Source/options.cpp:1082 +msgid "Off" +msgstr "Apagado" -#: Source/itemdat.cpp:533 -msgid "Armor of Gloom" -msgstr "Armadura de Penumbra" +#: Source/options.cpp:1083 +msgid "Random Angles" +msgstr "Ángulos aleatorios" -#: Source/itemdat.cpp:534 -msgid "Blitzen" -msgstr "Rayo" +#: Source/options.cpp:1084 +msgid "Vertical Only" +msgstr "Solo vertical" -#: Source/itemdat.cpp:535 -msgid "Thunderclap" -msgstr "Tronido" +#: Source/options.cpp:1135 +msgid "Controller" +msgstr "Controlador" -#: Source/itemdat.cpp:536 -msgid "Shirotachi" -msgstr "Shirotachi" +#: Source/options.cpp:1135 +msgid "Controller Settings" +msgstr "Configuraciones del controlador" -#: Source/itemdat.cpp:537 -msgid "Eater of Souls" -msgstr "Devorador de Almas" +#: Source/options.cpp:1144 +msgid "Network" +msgstr "Red" -#: Source/itemdat.cpp:538 -msgid "Diamondedge" -msgstr "Filo Diamantado" +#: Source/options.cpp:1144 +msgid "Network Settings" +msgstr "Configuraciones de red" -#: Source/itemdat.cpp:539 -msgid "Bone Chain Armor" -msgstr "Cota de Malla de Hueso" +#: Source/options.cpp:1156 +msgid "Chat" +msgstr "Chat" -#: Source/itemdat.cpp:540 -msgid "Demon Plate Armor" -msgstr "Armadura de Placas de Demonio" +#: Source/options.cpp:1156 +msgid "Chat Settings" +msgstr "Configuraciones del chat" -#: Source/itemdat.cpp:541 -msgid "Acolyte's Amulet" -msgstr "Amuleto de Acólito" +#: Source/options.cpp:1165 Source/options.cpp:1281 +msgid "Language" +msgstr "Idioma" -#. TRANSLATORS: Unique Item section end. -#: Source/itemdat.cpp:543 -msgid "Gladiator's Ring" -msgstr "Anillo de Gladiador" +#: Source/options.cpp:1165 +msgid "Define what language to use in game." +msgstr "Define el idioma que se utilizará en el juego." -#: Source/items.cpp:172 -msgid "Oil of Mastery" -msgstr "Aceite de Maestría" +#: Source/options.cpp:1281 +msgid "Language Settings" +msgstr "Configuración de idioma" -#: Source/items.cpp:174 -msgid "Oil of Death" -msgstr "Aceite de Muerte" +#: Source/options.cpp:1293 +msgid "Keymapping" +msgstr "Mapeo de teclas" -#: Source/items.cpp:175 -msgid "Oil of Skill" -msgstr "Aceite de Habilidad" +#: Source/options.cpp:1293 +msgid "Keymapping Settings" +msgstr "Configuración de mapeo de teclas" -#: Source/items.cpp:177 -msgid "Oil of Fortitude" -msgstr "Aceite de Entereza" +#: Source/options.cpp:1551 +msgid "Padmapping" +msgstr "Mapeo del pad" -#: Source/items.cpp:178 -msgid "Oil of Permanence" -msgstr "Aceite de Permanencia" +#: Source/options.cpp:1551 +msgid "Padmapping Settings" +msgstr "Configuración de mapeo del pad" -#: Source/items.cpp:179 -msgid "Oil of Hardening" -msgstr "Aceite de Endurecimiento" +#: Source/panels/charpanel.cpp:128 +msgid "Level" +msgstr "Nivel" -#: Source/items.cpp:180 -msgid "Oil of Imperviousness" -msgstr "Aceite de Impermeabilidad" +#: Source/panels/charpanel.cpp:130 +msgid "Experience" +msgstr "Experiencia" -#. TRANSLATORS: Constructs item names. Format: {Item} of {Spell}. Example: War Staff of Firewall -#: Source/items.cpp:1088 -msgctxt "spell" -msgid "{0} of {1}" -msgstr "{0} de {1}" +#: Source/panels/charpanel.cpp:135 +msgid "Next level" +msgstr "Siguiente Nivel" -#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Spell}. Example: King's War Staff of Firewall -#: Source/items.cpp:1096 -msgctxt "spell" -msgid "{0} {1} of {2}" -msgstr "{1} {0} de {2}" +#: Source/panels/charpanel.cpp:145 +msgid "Base" +msgstr "Base" -#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Suffix}. Example: King's Long Sword of the Whale -#: Source/items.cpp:1112 -msgid "{0} {1} of {2}" -msgstr "{1} {0} {2}" +#: Source/panels/charpanel.cpp:146 +msgid "Now" +msgstr "Ahora" -#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item}. Example: King's Long Sword -#: Source/items.cpp:1115 -msgid "{0} {1}" -msgstr "{1} {0}" +#: Source/panels/charpanel.cpp:147 +msgid "Strength" +msgstr "Fuerza" -#. TRANSLATORS: Constructs item names. Format: {Item} of {Suffix}. Example: Long Sword of the Whale -#: Source/items.cpp:1118 -msgid "{0} of {1}" -msgstr "{0} {1}" +#: Source/panels/charpanel.cpp:151 +msgid "Magic" +msgstr "Magia" -#: Source/items.cpp:1627 Source/items.cpp:1635 -msgid "increases a weapon's" -msgstr "en el arma aumenta" +#: Source/panels/charpanel.cpp:155 +msgid "Dexterity" +msgstr "Destreza" -#: Source/items.cpp:1628 -msgid "chance to hit" -msgstr "probabilidad de acertar" +#: Source/panels/charpanel.cpp:158 +msgid "Vitality" +msgstr "Vitalidad" -#: Source/items.cpp:1631 -msgid "greatly increases a" -msgstr "aumenta enormemente un" +#: Source/panels/charpanel.cpp:161 +msgid "Points to distribute" +msgstr "Puntos a distribuir" -#: Source/items.cpp:1632 -msgid "weapon's chance to hit" -msgstr "posibilidad de que el arma golpee" +#: Source/panels/charpanel.cpp:167 Source/translation_dummy.cpp:247 +#: Source/translation_dummy.cpp:606 +msgid "Gold" +msgstr "Oro" -#: Source/items.cpp:1636 -msgid "damage potential" -msgstr "daño potencial" +#: Source/panels/charpanel.cpp:171 +msgid "Armor class" +msgstr "Clase armadura" -#: Source/items.cpp:1639 -msgid "greatly increases a weapon's" -msgstr "aumenta en gran medida la de un arma" +#: Source/panels/charpanel.cpp:173 +msgid "To hit" +msgstr "Atacar" -#: Source/items.cpp:1640 -msgid "damage potential - not bows" -msgstr "daño potencial - no arcos" +#: Source/panels/charpanel.cpp:175 +msgid "Damage" +msgstr "Daño" -#: Source/items.cpp:1643 -msgid "reduces attributes needed" -msgstr "reduce los atributos necesarios" +#: Source/panels/charpanel.cpp:182 +msgid "Life" +msgstr "Vida" -#: Source/items.cpp:1644 -msgid "to use armor or weapons" -msgstr "para usar armaduras o armas" +#: Source/panels/charpanel.cpp:186 +msgid "Mana" +msgstr "Maná" -#: Source/items.cpp:1647 -#, no-c-format -msgid "restores 20% of an" -msgstr "restaura el 20% de la" +#: Source/panels/charpanel.cpp:191 +msgid "Resist magic" +msgstr "Resist magia" -#: Source/items.cpp:1648 -msgid "item's durability" -msgstr "durabilidad del artículo" +#: Source/panels/charpanel.cpp:193 +msgid "Resist fire" +msgstr "Resist fuego" -#: Source/items.cpp:1651 -msgid "increases an item's" -msgstr "aumenta la de un artículo" +#: Source/panels/charpanel.cpp:195 +msgid "Resist lightning" +msgstr "Resist rayos" -#: Source/items.cpp:1652 -msgid "current and max durability" -msgstr "durabilidad actual y máxima" +#: Source/panels/mainpanel.cpp:87 +msgid "char" +msgstr "personaje" -#: Source/items.cpp:1655 -msgid "makes an item indestructible" -msgstr "hace que un artículo sea indestructible" +#: Source/panels/mainpanel.cpp:88 +msgid "quests" +msgstr "misiones" -#: Source/items.cpp:1658 -msgid "increases the armor class" -msgstr "aumenta la clase de armadura" +#: Source/panels/mainpanel.cpp:89 +msgid "map" +msgstr "mapa" -#: Source/items.cpp:1659 -msgid "of armor and shields" -msgstr "de armaduras y escudos" +#: Source/panels/mainpanel.cpp:90 +msgid "menu" +msgstr "menú" -#: Source/items.cpp:1662 -msgid "greatly increases the armor" -msgstr "aumenta enormemente la armadura" +#: Source/panels/mainpanel.cpp:91 +msgid "inv" +msgstr "inv" -#: Source/items.cpp:1663 -msgid "class of armor and shields" -msgstr "clase de armaduras y escudos" +#: Source/panels/mainpanel.cpp:92 +msgid "spells" +msgstr "hechizos" -#: Source/items.cpp:1666 Source/items.cpp:1673 -msgid "sets fire trap" -msgstr "pone trampa de fuego" +#: Source/panels/mainpanel.cpp:102 Source/panels/mainpanel.cpp:128 +#: Source/panels/mainpanel.cpp:130 +msgid "voice" +msgstr "voz" -#: Source/items.cpp:1670 -msgid "sets lightning trap" -msgstr "establece trampa de rayos" +#: Source/panels/mainpanel.cpp:123 Source/panels/mainpanel.cpp:125 +#: Source/panels/mainpanel.cpp:127 +msgid "mute" +msgstr "silenciar" -#: Source/items.cpp:1676 -msgid "sets petrification trap" -msgstr "establece trampa de petrificación" +#: Source/panels/spell_book.cpp:116 +msgid "Unusable" +msgstr "Inutilizable" -#: Source/items.cpp:1679 -msgid "restore all life" -msgstr "restaura toda la vida" +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:119 +msgid "Dmg: 1/3 target hp" +msgstr "Daño: 1/3 tgt hp" -#: Source/items.cpp:1682 -msgid "restore some life" -msgstr "restaura algo de vida" +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:126 +#, c++-format +msgid "Heals: {:d} - {:d}" +msgstr "Sana: {:d} - {:d}" -#: Source/items.cpp:1685 -msgid "restore some mana" -msgstr "restaura algo de maná" +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:128 +#, c++-format +msgid "Damage: {:d} - {:d}" +msgstr "Daño: {:d} - {:d}" -#: Source/items.cpp:1688 -msgid "restore all mana" -msgstr "restaura todo el maná" +#: Source/panels/spell_book.cpp:183 Source/panels/spell_list.cpp:151 +msgid "Skill" +msgstr "Habilidad" -#: Source/items.cpp:1691 -msgid "increase strength" -msgstr "aumenta la fuerza" +#: Source/panels/spell_book.cpp:187 +#, c++-format +msgid "Staff ({:d} charge)" +msgid_plural "Staff ({:d} charges)" +msgstr[0] "Bastón ({:d} carga)" +msgstr[1] "Bastón ({:d} cargas)" -#: Source/items.cpp:1694 -msgid "increase magic" -msgstr "aumenta la magia" +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:192 +#, c++-format +msgctxt "spellbook" +msgid "Level {:d}" +msgstr "Nivel: {:d}" -#: Source/items.cpp:1697 -msgid "increase dexterity" -msgstr "aumenta la destreza" +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:196 +#, c++-format +msgctxt "spellbook" +msgid "Mana: {:d}" +msgstr "Maná: {:d}" -#: Source/items.cpp:1700 -msgid "increase vitality" -msgstr "aumenta la vitalidad" +#: Source/panels/spell_list.cpp:158 +msgid "Spell" +msgstr "Hechizo" -#: Source/items.cpp:1703 -msgid "restore some life and mana" -msgstr "restaura algo de vida y maná" +#: Source/panels/spell_list.cpp:161 +msgid "Damages undead only" +msgstr "Solo daña muertos vivientes" -#: Source/items.cpp:1706 -msgid "restore all life and mana" -msgstr "restaura toda la vida y maná" +#: Source/panels/spell_list.cpp:172 +msgid "Scroll" +msgstr "Pergamino" -#: Source/items.cpp:1720 -msgid "Right-click to view" -msgstr "Clic derecho para ver" +#: Source/panels/spell_list.cpp:183 Source/translation_dummy.cpp:459 +#: Source/translation_dummy.cpp:461 Source/translation_dummy.cpp:463 +#: Source/translation_dummy.cpp:465 Source/translation_dummy.cpp:467 +msgid "Staff" +msgstr "Bastón" -#: Source/items.cpp:1723 -msgid "Right-click to use" -msgstr "Clic derecho para usar" +#: Source/panels/spell_list.cpp:193 +#, c++-format +msgid "Spell Hotkey {:s}" +msgstr "Tecla de acceso rápido de Hechizo {:s}" -#: Source/items.cpp:1725 -msgid "" -"Right-click to read, then\n" -"left-click to target" -msgstr "" -"Haga clic derecho para leer, luego\n" -"clic izquierdo para apuntar" +#: Source/pfile.cpp:756 +msgid "Unable to open archive" +msgstr "No se puede abrir el archivo" -#: Source/items.cpp:1727 -msgid "Right-click to read" -msgstr "Haga clic derecho para leer" +#: Source/pfile.cpp:758 +msgid "Unable to load character" +msgstr "No se puede cargar el personaje" -#: Source/items.cpp:1734 -msgid "Activate to view" -msgstr "Activar para ver" +#: Source/plrmsg.cpp:85 Source/qol/chatlog.cpp:129 +#, c++-format +msgid "{:s} (lvl {:d}): " +msgstr "{:s} (nivel {:d}): " -#: Source/items.cpp:1738 Source/items.cpp:1776 -msgid "Open inventory to use" -msgstr "Abrir inventario para usar" +#: Source/qol/chatlog.cpp:169 +#, c++-format +msgid "Chat History (Messages: {:d})" +msgstr "Historia del Chat (Mensajes: {:d})" -#: Source/items.cpp:1740 -msgid "Activate to use" -msgstr "Activar para usar" +#: Source/qol/itemlabels.cpp:107 +#, c++-format +msgid "{:s} gold" +msgstr "{:s} oro" -#: Source/items.cpp:1743 -msgid "" -"Select from spell book, then\n" -"cast spell to read" -msgstr "" -"Seleccione del libro de hechizos, luego\n" -"lance el hechizo para leer" +#: Source/qol/stash.cpp:650 +msgid "How many gold pieces do you want to withdraw?" +msgstr "¿Cuántas piezas de oro quieres retirar?" -#: Source/items.cpp:1745 -msgid "Activate to read" -msgstr "Activar para leer" +#: Source/qol/xpbar.cpp:125 +#, c++-format +msgid "Level {:d}" +msgstr "Nivel: {:d}" -#: Source/items.cpp:1772 -msgid "{} to view" -msgstr "{} para ver" +#: Source/qol/xpbar.cpp:131 Source/qol/xpbar.cpp:139 +#, c++-format +msgid "Experience: {:s}" +msgstr "Experiencia: {:s}" -#: Source/items.cpp:1778 -msgid "{} to use" -msgstr "{} para usar" +#: Source/qol/xpbar.cpp:132 +msgid "Maximum Level" +msgstr "Nivel Máximo" -#: Source/items.cpp:1781 -msgid "" -"Select from spell book,\n" -"then {} to read" -msgstr "" -"Seleccione del libro de hechizos,\n" -"luego {} para leer" +#: Source/qol/xpbar.cpp:141 +#, c++-format +msgid "Next Level: {:s}" +msgstr "Siguiente Nivel: {:s}" -#: Source/items.cpp:1783 -msgid "{} to read" -msgstr "{} para leer" +#: Source/qol/xpbar.cpp:142 +#, c++-format +msgid "{:s} to Level {:d}" +msgstr "{:s} al Nivel {:d}" -#: Source/items.cpp:1790 -msgctxt "player" -msgid "Level: {:d}" -msgstr "Nivel: {:d}" +#. TRANSLATORS: Quest Name Block +#: Source/quests.cpp:52 +msgid "The Magic Rock" +msgstr "La Roca Mágica" -#: Source/items.cpp:1794 -msgid "Doubles gold capacity" -msgstr "Duplica la capacidad de oro" +#: Source/quests.cpp:53 Source/translation_dummy.cpp:264 +msgid "Black Mushroom" +msgstr "Hongo Negro" -#: Source/items.cpp:1825 Source/stores.cpp:322 -msgid "Required:" -msgstr "Requiere:" +#: Source/quests.cpp:54 +msgid "Gharbad The Weak" +msgstr "Gharbad el Débil" -#: Source/items.cpp:1827 Source/stores.cpp:324 -msgid " {:d} Str" -msgstr " {:d} Fue" +#: Source/quests.cpp:55 +msgid "Zhar the Mad" +msgstr "Zhar el Loco" -#: Source/items.cpp:1829 Source/stores.cpp:326 -msgid " {:d} Mag" -msgstr " {:d} Mag" +#: Source/quests.cpp:56 +msgid "Lachdanan" +msgstr "Lachdanan" -#: Source/items.cpp:1831 Source/stores.cpp:328 -msgid " {:d} Dex" -msgstr " {:d} Des" +#: Source/quests.cpp:58 +msgid "The Butcher" +msgstr "El Carnicero" -#. TRANSLATORS: {:s} will be a Character Name -#: Source/items.cpp:3126 Source/player.cpp:3003 -msgid "Ear of {:s}" -msgstr "Oído de {:s}" +#: Source/quests.cpp:59 +msgid "Ogden's Sign" +msgstr "Signo de Ogden" -#: Source/items.cpp:3433 -msgid "chance to hit: {:+d}%" -msgstr "probabilidad de acertar: {:+d}%" +#: Source/quests.cpp:60 +msgid "Halls of the Blind" +msgstr "Pasillos de los Ciegos" -#: Source/items.cpp:3436 -#, no-c-format -msgid "{:+d}% damage" -msgstr "{:+d}% daño" +#: Source/quests.cpp:61 +msgid "Valor" +msgstr "Valor" -#: Source/items.cpp:3439 Source/items.cpp:3623 -msgid "to hit: {:+d}%, {:+d}% damage" -msgstr "al golpear: {:+d}%, {:+d} daño" +#: Source/quests.cpp:62 Source/translation_dummy.cpp:263 +msgid "Anvil of Fury" +msgstr "Yunque de Furia" -#: Source/items.cpp:3442 -#, no-c-format -msgid "{:+d}% armor" -msgstr "{:+d}% armadura" +#: Source/quests.cpp:63 +msgid "Warlord of Blood" +msgstr "Señor de la Guerra de Sangre" -#: Source/items.cpp:3445 -msgid "armor class: {:d}" -msgstr "clase de armadura: {:d}" +#: Source/quests.cpp:64 +msgid "The Curse of King Leoric" +msgstr "La Maldición del Rey Leoric" -#: Source/items.cpp:3449 -msgid "Resist Fire: {:+d}%" -msgstr "Resistencia al Fuego: {:+d}%" +#. TRANSLATORS: Quest Map +#: Source/quests.cpp:66 Source/quests.cpp:102 +msgid "The Chamber of Bone" +msgstr "La Cámara de Hueso" -#: Source/items.cpp:3451 -msgid "Resist Fire: {:+d}% MAX" -msgstr "Resistencia al Fuego: {:+d}% MAX" +#: Source/quests.cpp:67 +msgid "Archbishop Lazarus" +msgstr "Arzobispo Lazarus" -#: Source/items.cpp:3455 -msgid "Resist Lightning: {:+d}%" -msgstr "Resistencia a Relámpagos: {:+d}%" +#: Source/quests.cpp:68 +msgid "Grave Matters" +msgstr "Asuntos de Tumbas" -#: Source/items.cpp:3457 -msgid "Resist Lightning: {:+d}% MAX" -msgstr "Resistencia a Relámpagos: {:+d}% MAX" +#: Source/quests.cpp:69 +msgid "Farmer's Orchard" +msgstr "Huerto del Granjero" -#: Source/items.cpp:3461 -msgid "Resist Magic: {:+d}%" -msgstr "Resistencia a la Magia: {:+d}%" +#: Source/quests.cpp:70 +msgid "Little Girl" +msgstr "Niñita" -#: Source/items.cpp:3463 -msgid "Resist Magic: {:+d}% MAX" -msgstr "Resistencia a la Magia: {:+d}% MAX" +#: Source/quests.cpp:71 +msgid "Wandering Trader" +msgstr "Comerciante Errante" -#: Source/items.cpp:3466 -msgid "Resist All: {:+d}%" -msgstr "Resistencia a Todo: {:+d}%" +#: Source/quests.cpp:72 +msgid "The Defiler" +msgstr "El Profanador" -#: Source/items.cpp:3468 -msgid "Resist All: {:+d}% MAX" -msgstr "Resistencia a Todo: {:+d}% MAX" +#: Source/quests.cpp:73 +msgid "Na-Krul" +msgstr "Na-Krul" -#: Source/items.cpp:3471 -msgid "spells are increased {:d} level" -msgid_plural "spells are increased {:d} levels" -msgstr[0] "los hechizos aumentan {:d} nivel" -msgstr[1] "los hechizos aumentan {:d} niveles" +#. TRANSLATORS: Quest Name Block end +#: Source/quests.cpp:75 +msgid "The Jersey's Jersey" +msgstr "El Jersey de Jersey" -#: Source/items.cpp:3473 -msgid "spells are decreased {:d} level" -msgid_plural "spells are decreased {:d} levels" -msgstr[0] "los hechizos se reducen {:d} nivel" -msgstr[1] "los hechizos se reducen {:d} niveles" +#. TRANSLATORS: Quest Map +#: Source/quests.cpp:101 +msgid "King Leoric's Tomb" +msgstr "Tumba del Rey Leoric" -#: Source/items.cpp:3475 -msgid "spell levels unchanged (?)" -msgstr "niveles de hechizo sin cambios (?)" +#. TRANSLATORS: Quest Map +#: Source/quests.cpp:104 +msgid "A Dark Passage" +msgstr "Un Pasadizo Oscuro" -#: Source/items.cpp:3477 -msgid "Extra charges" -msgstr "Cargas extras" +#. TRANSLATORS: Quest Map +#: Source/quests.cpp:105 +msgid "Unholy Altar" +msgstr "Altar Impío" -#: Source/items.cpp:3479 -msgid "{:d} {:s} charge" -msgid_plural "{:d} {:s} charges" -msgstr[0] "{:d} {:s} carga" -msgstr[1] "{:d} {:s} cargas" +#. TRANSLATORS: Used for Quest Portals. {:s} is a Map Name +#: Source/quests.cpp:405 +#, c++-format +msgid "To {:s}" +msgstr "A {:s}" -#: Source/items.cpp:3482 -msgid "Fire hit damage: {:d}" -msgstr "Daño por impacto de fuego: {:d}" +#: Source/spelldat.cpp:29 +msgctxt "spell" +msgid "Firebolt" +msgstr "Flecha de fuego" -#: Source/items.cpp:3484 -msgid "Fire hit damage: {:d}-{:d}" -msgstr "Daño por impacto de fuego: {:d}- {:d}" +#: Source/spelldat.cpp:30 +msgctxt "spell" +msgid "Healing" +msgstr "Curación" -#: Source/items.cpp:3487 -msgid "Lightning hit damage: {:d}" -msgstr "Daño por impacto de rayo: {:d}" +#: Source/spelldat.cpp:31 +msgctxt "spell" +msgid "Lightning" +msgstr "Rayo" -#: Source/items.cpp:3489 -msgid "Lightning hit damage: {:d}-{:d}" -msgstr "Daño por impacto de rayo: {:d}- {:d}" +#: Source/spelldat.cpp:32 +msgctxt "spell" +msgid "Flash" +msgstr "Destello" -#: Source/items.cpp:3492 -msgid "{:+d} to strength" -msgstr "{:+d} a la fuerza" +#: Source/spelldat.cpp:33 +msgctxt "spell" +msgid "Identify" +msgstr "Identificar" -#: Source/items.cpp:3495 -msgid "{:+d} to magic" -msgstr "{:+d} a la magia" +#: Source/spelldat.cpp:34 +msgctxt "spell" +msgid "Fire Wall" +msgstr "Muro de Fuego" -#: Source/items.cpp:3498 -msgid "{:+d} to dexterity" -msgstr "{:+d} a la destreza" +#: Source/spelldat.cpp:35 +msgctxt "spell" +msgid "Town Portal" +msgstr "Portal al pueblo" -#: Source/items.cpp:3501 -msgid "{:+d} to vitality" -msgstr "{:+d} a la vitalidad" +#: Source/spelldat.cpp:36 +msgctxt "spell" +msgid "Stone Curse" +msgstr "Maldición de Piedra" -#: Source/items.cpp:3504 -msgid "{:+d} to all attributes" -msgstr "{:+d} a todos los atributos" +#: Source/spelldat.cpp:37 +msgctxt "spell" +msgid "Infravision" +msgstr "Infravisión" -#: Source/items.cpp:3507 -msgid "{:+d} damage from enemies" -msgstr "{:+d} de daño de enemigos" +#: Source/spelldat.cpp:38 +msgctxt "spell" +msgid "Phasing" +msgstr "Ajuste de Fase" -#: Source/items.cpp:3510 -msgid "Hit Points: {:+d}" -msgstr "Puntos de Vida: {:+d}" +#: Source/spelldat.cpp:39 +msgctxt "spell" +msgid "Mana Shield" +msgstr "Escudo de Maná" -#: Source/items.cpp:3513 -msgid "Mana: {:+d}" -msgstr "Maná: {:+d}" +#: Source/spelldat.cpp:40 +msgctxt "spell" +msgid "Fireball" +msgstr "Bola de Fuego" -#: Source/items.cpp:3515 -msgid "high durability" -msgstr "alta durabilidad" +#: Source/spelldat.cpp:41 +msgctxt "spell" +msgid "Guardian" +msgstr "Guardián" -#: Source/items.cpp:3517 -msgid "decreased durability" -msgstr "durabilidad disminuida" +#: Source/spelldat.cpp:42 +msgctxt "spell" +msgid "Chain Lightning" +msgstr "Cadena de Relámpagos" -#: Source/items.cpp:3519 -msgid "indestructible" -msgstr "indestructible" +#: Source/spelldat.cpp:43 +msgctxt "spell" +msgid "Flame Wave" +msgstr "Ola de Llamas" -#: Source/items.cpp:3521 -#, no-c-format -msgid "+{:d}% light radius" -msgstr "+{:d}% radio de luz" +#: Source/spelldat.cpp:44 +msgctxt "spell" +msgid "Doom Serpents" +msgstr "Serpientes de la Condenación" -#: Source/items.cpp:3523 -#, no-c-format -msgid "-{:d}% light radius" -msgstr "-{:d}% radio de luz" +#: Source/spelldat.cpp:45 +msgctxt "spell" +msgid "Blood Ritual" +msgstr "Ritual de Sangre" -#: Source/items.cpp:3525 -msgid "multiple arrows per shot" -msgstr "múltiples flechas por disparo" +#: Source/spelldat.cpp:46 +msgctxt "spell" +msgid "Nova" +msgstr "Nova" -#: Source/items.cpp:3528 -msgid "fire arrows damage: {:d}" -msgstr "daño de las flechas de fuego: {:d}" +#: Source/spelldat.cpp:47 +msgctxt "spell" +msgid "Invisibility" +msgstr "Invisibilidad" -#: Source/items.cpp:3530 -msgid "fire arrows damage: {:d}-{:d}" -msgstr "daño de las flechas de fuego: {:d}-{:d}" +#: Source/spelldat.cpp:48 +msgctxt "spell" +msgid "Inferno" +msgstr "Infierno" -#: Source/items.cpp:3533 -msgid "lightning arrows damage {:d}" -msgstr "daño de las flechas de rayo {:d}" +#: Source/spelldat.cpp:49 +msgctxt "spell" +msgid "Golem" +msgstr "Gólem" -#: Source/items.cpp:3535 -msgid "lightning arrows damage {:d}-{:d}" -msgstr "daño de las flechas de rayo {:d}-{:d}" +#: Source/spelldat.cpp:50 +msgctxt "spell" +msgid "Rage" +msgstr "Furia" -#: Source/items.cpp:3538 -msgid "fireball damage: {:d}" -msgstr "daño de bola de fuego: {:d}" +#: Source/spelldat.cpp:51 +msgctxt "spell" +msgid "Teleport" +msgstr "Teletransporte" -#: Source/items.cpp:3540 -msgid "fireball damage: {:d}-{:d}" -msgstr "daño de bola de fuego: {:d}-{:d}" +#: Source/spelldat.cpp:52 +msgctxt "spell" +msgid "Apocalypse" +msgstr "Apocalipsis" -#: Source/items.cpp:3542 -msgid "attacker takes 1-3 damage" -msgstr "el atacante recibe 1-3 daños" +#: Source/spelldat.cpp:53 +msgctxt "spell" +msgid "Etherealize" +msgstr "Etéreo" -#: Source/items.cpp:3544 -msgid "user loses all mana" -msgstr "el usuario pierde todo el maná" +#: Source/spelldat.cpp:54 +msgctxt "spell" +msgid "Item Repair" +msgstr "Reparación de Artículo" -#: Source/items.cpp:3546 -msgid "absorbs half of trap damage" -msgstr "absorbe la mitad del daño de la trampa" +#: Source/spelldat.cpp:55 +msgctxt "spell" +msgid "Staff Recharge" +msgstr "Recarga de Bastón" -#: Source/items.cpp:3548 -msgid "knocks target back" -msgstr "hace retroceder al objetivo" +#: Source/spelldat.cpp:56 +msgctxt "spell" +msgid "Trap Disarm" +msgstr "Desarmar Trampa" -#: Source/items.cpp:3550 -#, no-c-format -msgid "+200% damage vs. demons" -msgstr "+200% de daño contra demonios" +#: Source/spelldat.cpp:57 +msgctxt "spell" +msgid "Elemental" +msgstr "Elemental" -#: Source/items.cpp:3552 -msgid "All Resistance equals 0" -msgstr "Toda la Resistencia es igual a 0" +#: Source/spelldat.cpp:58 +msgctxt "spell" +msgid "Charged Bolt" +msgstr "Rayo Cargado" -#: Source/items.cpp:3555 -#, no-c-format -msgid "hit steals 3% mana" -msgstr "el golpe roba 3% de maná" +#: Source/spelldat.cpp:59 +msgctxt "spell" +msgid "Holy Bolt" +msgstr "Rayo Santo" -#: Source/items.cpp:3557 -#, no-c-format -msgid "hit steals 5% mana" -msgstr "el golpe roba 5% de maná" +#: Source/spelldat.cpp:60 +msgctxt "spell" +msgid "Resurrect" +msgstr "Resucitar" -#: Source/items.cpp:3561 -#, no-c-format -msgid "hit steals 3% life" -msgstr "el golpe roba el 3% de vida" +#: Source/spelldat.cpp:61 +msgctxt "spell" +msgid "Telekinesis" +msgstr "Telequinesis" -#: Source/items.cpp:3563 -#, no-c-format -msgid "hit steals 5% life" -msgstr "el golpe roba 5% de vida" +#: Source/spelldat.cpp:62 +msgctxt "spell" +msgid "Heal Other" +msgstr "Sanar a Otros" -#: Source/items.cpp:3566 -msgid "penetrates target's armor" -msgstr "penetra la armadura del objetivo" +#: Source/spelldat.cpp:63 +msgctxt "spell" +msgid "Blood Star" +msgstr "Estrella de Sangre" -#: Source/items.cpp:3569 -msgid "quick attack" -msgstr "ataque rápido" +#: Source/spelldat.cpp:64 +msgctxt "spell" +msgid "Bone Spirit" +msgstr "Espíritu de Hueso" -#: Source/items.cpp:3571 -msgid "fast attack" -msgstr "ataque rápido" +#: Source/spelldat.cpp:65 +msgctxt "spell" +msgid "Mana" +msgstr "Maná" -#: Source/items.cpp:3573 -msgid "faster attack" -msgstr "ataque más rápido" +#: Source/spelldat.cpp:66 +msgctxt "spell" +msgid "the Magi" +msgstr "los Magos" -#: Source/items.cpp:3575 -msgid "fastest attack" -msgstr "ataque más rápido posible" +#: Source/spelldat.cpp:67 +msgctxt "spell" +msgid "the Jester" +msgstr "el Bufón" -#: Source/items.cpp:3576 Source/items.cpp:3584 Source/items.cpp:3633 -msgid "Another ability (NW)" -msgstr "Otra habilidad (NW)" +#: Source/spelldat.cpp:68 +msgctxt "spell" +msgid "Lightning Wall" +msgstr "Pared de Relámpagos" -#: Source/items.cpp:3579 -msgid "fast hit recovery" -msgstr "recuperación rápida de golpes" +#: Source/spelldat.cpp:69 +msgctxt "spell" +msgid "Immolation" +msgstr "Inmolación" -#: Source/items.cpp:3581 -msgid "faster hit recovery" -msgstr "recuperación de golpes más rápida" +#: Source/spelldat.cpp:70 +msgctxt "spell" +msgid "Warp" +msgstr "Deformación" -#: Source/items.cpp:3583 -msgid "fastest hit recovery" -msgstr "recuperación de golpe más rápida posible" +#: Source/spelldat.cpp:71 +msgctxt "spell" +msgid "Reflect" +msgstr "Reflejar" -#: Source/items.cpp:3586 -msgid "fast block" -msgstr "bloqueo rapido" +#: Source/spelldat.cpp:72 +msgctxt "spell" +msgid "Berserk" +msgstr "Berserk" -#: Source/items.cpp:3588 -msgid "adds {:d} point to damage" -msgid_plural "adds {:d} points to damage" -msgstr[0] "agrega {:d} punto al daño" -msgstr[1] "agrega {:d} puntos al daño" +#: Source/spelldat.cpp:73 +msgctxt "spell" +msgid "Ring of Fire" +msgstr "Anillo de Fuego" -#: Source/items.cpp:3590 -msgid "fires random speed arrows" -msgstr "dispara flechas de velocidad aleatoria" +#: Source/spelldat.cpp:74 +msgctxt "spell" +msgid "Search" +msgstr "Buscar" -#: Source/items.cpp:3592 -msgid "unusual item damage" -msgstr "daño inusual del artículo" +#: Source/spelldat.cpp:75 +msgctxt "spell" +msgid "Rune of Fire" +msgstr "Runa de Fuego" -#: Source/items.cpp:3594 -msgid "altered durability" -msgstr "durabilidad alterada" +#: Source/spelldat.cpp:76 +msgctxt "spell" +msgid "Rune of Light" +msgstr "Runa de Luz" -#: Source/items.cpp:3596 -msgid "one handed sword" -msgstr "espada de una mano" +#: Source/spelldat.cpp:77 +msgctxt "spell" +msgid "Rune of Nova" +msgstr "Runa de Nova" -#: Source/items.cpp:3598 -msgid "constantly lose hit points" -msgstr "pierde puntos de vida constantemente" +#: Source/spelldat.cpp:78 +msgctxt "spell" +msgid "Rune of Immolation" +msgstr "Runa de Inmolación" -#: Source/items.cpp:3600 -msgid "life stealing" -msgstr "robo de vida" +#: Source/spelldat.cpp:79 +msgctxt "spell" +msgid "Rune of Stone" +msgstr "Runa de Piedra" -#: Source/items.cpp:3602 -msgid "no strength requirement" -msgstr "sin requisito de fuerza" +#: Source/stores.cpp:129 +msgid "Griswold" +msgstr "Griswold" -#: Source/items.cpp:3607 -msgid "lightning damage: {:d}" -msgstr "daño por rayo: {:d}" +#: Source/stores.cpp:130 +msgid "Pepin" +msgstr "Pepin" -#: Source/items.cpp:3609 -msgid "lightning damage: {:d}-{:d}" -msgstr "daño por rayo: {:d}-{:d}" +#: Source/stores.cpp:132 +msgid "Ogden" +msgstr "Ogden" -#: Source/items.cpp:3611 -msgid "charged bolts on hits" -msgstr "rayos por cada golpe" +#: Source/stores.cpp:133 +msgid "Cain" +msgstr "Cain" -#: Source/items.cpp:3613 -msgid "occasional triple damage" -msgstr "triple daño ocasional" +#: Source/stores.cpp:134 +msgid "Farnham" +msgstr "Farnham" -#: Source/items.cpp:3615 -#, no-c-format -msgid "decaying {:+d}% damage" -msgstr "{:+d}% de daño de descomposición" +#: Source/stores.cpp:135 +msgid "Adria" +msgstr "Adria" -#: Source/items.cpp:3617 -msgid "2x dmg to monst, 1x to you" -msgstr "2x dañ al mes, 1x a ti" +#: Source/stores.cpp:136 Source/stores.cpp:1261 +msgid "Gillian" +msgstr "Gillian" -#: Source/items.cpp:3619 -#, no-c-format -msgid "Random 0 - 600% damage" -msgstr "Daño aleatorio 0 - 600%" +#: Source/stores.cpp:137 +msgid "Wirt" +msgstr "Wirt" -#: Source/items.cpp:3621 -#, no-c-format -msgid "low dur, {:+d}% damage" -msgstr "baja dur, {:+d}% de daño" +#: Source/stores.cpp:263 Source/stores.cpp:270 +msgid "Back" +msgstr "Atrás" -#: Source/items.cpp:3625 -msgid "extra AC vs demons" -msgstr "extra CA contra demonios" +#: Source/stores.cpp:292 Source/stores.cpp:298 +msgid ", " +msgstr ", " -#: Source/items.cpp:3627 -msgid "extra AC vs undead" -msgstr "extra CA contra muertos vivientes" +#: Source/stores.cpp:309 +#, c++-format +msgid "Damage: {:d}-{:d} " +msgstr "Daño: {:d}-{:d} " -#: Source/items.cpp:3629 -msgid "50% Mana moved to Health" -msgstr "50% de Maná se movió a Salud" +#: Source/stores.cpp:311 +#, c++-format +msgid "Armor: {:d} " +msgstr "Defensa: {:d} " -#: Source/items.cpp:3631 -msgid "40% Health moved to Mana" -msgstr "40% de Salud se movió a Maná" +#: Source/stores.cpp:313 +#, c++-format +msgid "Dur: {:d}/{:d}, " +msgstr "Dur: {:d}/{:d}, " -#: Source/items.cpp:3671 Source/items.cpp:3712 -msgid "damage: {:d} Indestructible" -msgstr "daño: {:d} Indestructible" +#: Source/stores.cpp:315 +msgid "Indestructible, " +msgstr "Indestructible, " -#. TRANSLATORS: Dur: is durability -#: Source/items.cpp:3673 Source/items.cpp:3714 -msgid "damage: {:d} Dur: {:d}/{:d}" -msgstr "daño: {:d} Dur: {:d}/{:d}" +#: Source/stores.cpp:323 +msgid "No required attributes" +msgstr "No requiere atributos" -#: Source/items.cpp:3676 Source/items.cpp:3717 -msgid "damage: {:d}-{:d} Indestructible" -msgstr "daño: {:d}-{:d} Indestructible" +#: Source/stores.cpp:381 Source/stores.cpp:1029 Source/stores.cpp:1248 +msgid "Welcome to the" +msgstr "Bienvenido a la" -#. TRANSLATORS: Dur: is durability -#: Source/items.cpp:3678 Source/items.cpp:3719 -msgid "damage: {:d}-{:d} Dur: {:d}/{:d}" -msgstr "daño: {:d}-{:d} Dur: {:d}/{:d}" +#: Source/stores.cpp:382 +msgid "Blacksmith's shop" +msgstr "Herrería" -#: Source/items.cpp:3683 Source/items.cpp:3729 -msgid "armor: {:d} Indestructible" -msgstr "defensa: {:d} Indestructible" +#: Source/stores.cpp:383 Source/stores.cpp:680 Source/stores.cpp:1031 +#: Source/stores.cpp:1074 Source/stores.cpp:1250 Source/stores.cpp:1262 +#: Source/stores.cpp:1275 +msgid "Would you like to:" +msgstr "Te gustaría:" -#. TRANSLATORS: Dur: is durability -#: Source/items.cpp:3685 Source/items.cpp:3731 -msgid "armor: {:d} Dur: {:d}/{:d}" -msgstr "defensa: {:d} Dur: {:d}/{:d}" +#: Source/stores.cpp:384 +msgid "Talk to Griswold" +msgstr "Hablar" -#: Source/items.cpp:3688 Source/items.cpp:3722 Source/items.cpp:3735 -#: Source/stores.cpp:296 -msgid "Charges: {:d}/{:d}" -msgstr "Cargas: {:d}/{:d}" +#: Source/stores.cpp:385 +msgid "Buy basic items" +msgstr "Comprar" -#: Source/items.cpp:3697 -msgid "unique item" -msgstr "artículo único" +#: Source/stores.cpp:386 +msgid "Buy premium items" +msgstr "Comprar mercancía selecta" -#: Source/items.cpp:3725 Source/items.cpp:3733 Source/items.cpp:3739 -msgid "Not Identified" -msgstr "No Identificado" +#: Source/stores.cpp:387 Source/stores.cpp:683 +msgid "Sell items" +msgstr "Vender" -#: Source/levels/setmaps.cpp:25 -msgid "Skeleton King's Lair" -msgstr "Guarida del Rey Esqueleto" +#: Source/stores.cpp:388 +msgid "Repair items" +msgstr "Reparar" -#: Source/levels/setmaps.cpp:26 -msgid "Chamber of Bone" -msgstr "Cámara de Hueso" +#: Source/stores.cpp:389 +msgid "Leave the shop" +msgstr "Dejar la herrería" -#. TRANSLATORS: Quest Map -#: Source/levels/setmaps.cpp:27 Source/quests.cpp:97 -msgid "Maze" -msgstr "Laberinto" +#: Source/stores.cpp:417 Source/stores.cpp:719 Source/stores.cpp:1051 +msgid "I have these items for sale:" +msgstr "Tengo esto a la venta:" -#: Source/levels/setmaps.cpp:28 Source/quests.cpp:59 -msgid "Poisoned Water Supply" -msgstr "Red de Agua Envenenada" +#: Source/stores.cpp:466 +msgid "I have these premium items for sale:" +msgstr "Ésta es mi mercancía de primera calidad:" -#: Source/levels/setmaps.cpp:29 -msgid "Archbishop Lazarus' Lair" -msgstr "Guarida del Arzobispo Lazarus" +#: Source/stores.cpp:562 Source/stores.cpp:812 +msgid "You have nothing I want." +msgstr "No tienes nada que me interese." -#: Source/levels/setmaps.cpp:30 -msgid "Church Arena" -msgstr "Arena de la Iglesia" +#: Source/stores.cpp:573 Source/stores.cpp:824 +msgid "Which item is for sale?" +msgstr "¿Qué tienes a la venta?" -#: Source/levels/setmaps.cpp:31 -msgid "Hell Arena" -msgstr "Arena Hellfire" +#: Source/stores.cpp:641 +msgid "You have nothing to repair." +msgstr "No tienes nada que reparar." -#: Source/levels/setmaps.cpp:32 -msgid "Circle of Life Arena" -msgstr "Arena Círculo de la Vida" +#: Source/stores.cpp:652 +msgid "Repair which item?" +msgstr "¿Qué objeto quieres reparar?" -#: Source/levels/trigs.cpp:348 -msgid "Down to dungeon" -msgstr "Bajar a la mazmorra" +#: Source/stores.cpp:679 +msgid "Witch's shack" +msgstr "Choza de la Bruja" -#: Source/levels/trigs.cpp:357 -msgid "Down to catacombs" -msgstr "Bajar a las catacumbas" +#: Source/stores.cpp:681 +msgid "Talk to Adria" +msgstr "Hablar" -#: Source/levels/trigs.cpp:367 -msgid "Down to caves" -msgstr "Bajar a las cuevas" +#: Source/stores.cpp:682 Source/stores.cpp:1033 +msgid "Buy items" +msgstr "Comprar" -#: Source/levels/trigs.cpp:377 -msgid "Down to hell" -msgstr "Bajar al infierno" +#: Source/stores.cpp:684 +msgid "Recharge staves" +msgstr "Recargar bastones" -#: Source/levels/trigs.cpp:387 -msgid "Down to Hive" -msgstr "Bajar a la colmena" +#: Source/stores.cpp:685 +msgid "Leave the shack" +msgstr "Dejar la choza" -#: Source/levels/trigs.cpp:397 -msgid "Down to Crypt" -msgstr "Bajar a la Cripta" +#: Source/stores.cpp:886 +msgid "You have nothing to recharge." +msgstr "No tienes nada que recargar." -#: Source/levels/trigs.cpp:412 Source/levels/trigs.cpp:447 -#: Source/levels/trigs.cpp:493 Source/levels/trigs.cpp:545 -msgid "Up to level {:d}" -msgstr "Sube al nivel {:d}" +#: Source/stores.cpp:897 +msgid "Recharge which item?" +msgstr "¿Qué objeto quieres recargar?" -#: Source/levels/trigs.cpp:414 Source/levels/trigs.cpp:476 -#: Source/levels/trigs.cpp:528 Source/levels/trigs.cpp:575 -#: Source/levels/trigs.cpp:637 Source/levels/trigs.cpp:686 -#: Source/levels/trigs.cpp:793 -msgid "Up to town" -msgstr "Sube al pueblo" +#: Source/stores.cpp:910 +msgid "You do not have enough gold" +msgstr "No tienes oro suficiente" -#: Source/levels/trigs.cpp:425 Source/levels/trigs.cpp:458 -#: Source/levels/trigs.cpp:510 Source/levels/trigs.cpp:557 -#: Source/levels/trigs.cpp:619 -msgid "Down to level {:d}" -msgstr "Baja al nivel {:d}" +#: Source/stores.cpp:918 +msgid "You do not have enough room in inventory" +msgstr "No tienes espacio suficiente en el inventario" -#: Source/levels/trigs.cpp:588 -msgid "Down to Diablo" -msgstr "Baja a Diablo" +#: Source/stores.cpp:936 +msgid "Do we have a deal?" +msgstr "¿Tenemos un trato?" -#: Source/levels/trigs.cpp:606 -msgid "Up to Nest level {:d}" -msgstr "Sube al nivel de la Colmena {:d}" +#: Source/stores.cpp:939 +msgid "Are you sure you want to identify this item?" +msgstr "¿Seguro que quieres identificar esto?" -#: Source/levels/trigs.cpp:654 -msgid "Up to Crypt level {:d}" -msgstr "Sube al nivel de la Cripta {:d}" +#: Source/stores.cpp:945 +msgid "Are you sure you want to buy this item?" +msgstr "¿Seguro que quieres comprar esto?" -#: Source/levels/trigs.cpp:664 Source/quests.cpp:68 -msgid "Cornerstone of the World" -msgstr "Piedra Angular del Mundo" +#: Source/stores.cpp:948 +msgid "Are you sure you want to recharge this item?" +msgstr "¿Seguro que quieres recargar esto?" -#: Source/levels/trigs.cpp:669 -msgid "Down to Crypt level {:d}" -msgstr "Baja al nivel de la Cripta {:d}" +#: Source/stores.cpp:952 +msgid "Are you sure you want to sell this item?" +msgstr "¿Seguro que quieres vender esto?" -#: Source/levels/trigs.cpp:717 Source/levels/trigs.cpp:731 -#: Source/levels/trigs.cpp:745 -msgid "Back to Level {:d}" -msgstr "Volver al nivel {:d}" +#: Source/stores.cpp:955 +msgid "Are you sure you want to repair this item?" +msgstr "¿Seguro que quieres reparar esto?" -#: Source/loadsave.cpp:2029 Source/loadsave.cpp:2548 -msgid "Unable to open save file archive" -msgstr "No se puede abrir el archivo guardado" +#: Source/stores.cpp:969 Source/towners.cpp:152 +msgid "Wirt the Peg-legged boy" +msgstr "Wirt el Patapalo" -#: Source/loadsave.cpp:2032 -msgid "Invalid save file" -msgstr "Archivo guardado no válido" +#: Source/stores.cpp:972 Source/stores.cpp:979 +msgid "Talk to Wirt" +msgstr "Hablar" -#: Source/loadsave.cpp:2063 -msgid "Player is on a Hellfire only level" -msgstr "El jugador está en un nivel único Hellfire" +#: Source/stores.cpp:973 +msgid "I have something for sale," +msgstr "Tengo algo en venta," -#: Source/loadsave.cpp:2309 -msgid "Invalid game state" -msgstr "Estado de juego no válido" +#: Source/stores.cpp:974 +msgid "but it will cost 50 gold" +msgstr "te costará 50 de oro" -#: Source/menu.cpp:154 -msgid "Unable to display mainmenu" -msgstr "No se puede mostrar el menú principal" +#: Source/stores.cpp:975 +msgid "just to take a look. " +msgstr "sólo echar un vistazo. " -#. TRANSLATORS: Monster Block start -#. MT_NZOMBIE -#: Source/monstdat.cpp:31 -msgctxt "monster" -msgid "Zombie" -msgstr "Zombi" +#: Source/stores.cpp:976 +msgid "What have you got?" +msgstr "¿Qué es lo que tienes?" -#: Source/monstdat.cpp:32 -msgctxt "monster" -msgid "Ghoul" -msgstr "Necrófago" +#: Source/stores.cpp:977 Source/stores.cpp:980 Source/stores.cpp:1077 +#: Source/stores.cpp:1265 +msgid "Say goodbye" +msgstr "Despedirte" -#: Source/monstdat.cpp:33 -msgctxt "monster" -msgid "Rotting Carcass" -msgstr "Cádaver Podrido" +#: Source/stores.cpp:990 +msgid "I have this item for sale:" +msgstr "Esto es lo que tengo:" -#: Source/monstdat.cpp:34 -msgctxt "monster" -msgid "Black Death" -msgstr "Muerte Negra" +#: Source/stores.cpp:1007 +msgid "Leave" +msgstr "Irse" -#: Source/monstdat.cpp:35 Source/monstdat.cpp:43 -msgctxt "monster" -msgid "Fallen One" -msgstr "El Caído" +#: Source/stores.cpp:1030 +msgid "Healer's home" +msgstr "Botica" -#: Source/monstdat.cpp:36 Source/monstdat.cpp:44 -msgctxt "monster" -msgid "Carver" -msgstr "Trinchante" +#: Source/stores.cpp:1032 +msgid "Talk to Pepin" +msgstr "Hablar" -#: Source/monstdat.cpp:37 Source/monstdat.cpp:45 -msgctxt "monster" -msgid "Devil Kin" -msgstr "Pariente del Demonio" +#: Source/stores.cpp:1034 +msgid "Leave Healer's home" +msgstr "Dejar la Botica" -#: Source/monstdat.cpp:38 Source/monstdat.cpp:46 -msgctxt "monster" -msgid "Dark One" -msgstr "El Oscuro" +#: Source/stores.cpp:1073 +msgid "The Town Elder" +msgstr "El Sabio del Pueblo" -#: Source/monstdat.cpp:39 Source/monstdat.cpp:51 -msgctxt "monster" -msgid "Skeleton" -msgstr "Esqueleto" +#: Source/stores.cpp:1075 +msgid "Talk to Cain" +msgstr "Hablar" -#: Source/monstdat.cpp:40 -msgctxt "monster" -msgid "Corpse Axe" -msgstr "Hacha Cadáver" +#: Source/stores.cpp:1076 +msgid "Identify an item" +msgstr "Identificar objetos" -#: Source/monstdat.cpp:41 Source/monstdat.cpp:53 -msgctxt "monster" -msgid "Burning Dead" -msgstr "Muerte Ardiente" +#: Source/stores.cpp:1169 +msgid "You have nothing to identify." +msgstr "No tienes nada que identificar." -#: Source/monstdat.cpp:42 Source/monstdat.cpp:54 -msgctxt "monster" -msgid "Horror" -msgstr "Horror" +#: Source/stores.cpp:1180 +msgid "Identify which item?" +msgstr "¿Qué objeto quieres identificar?" -#: Source/monstdat.cpp:47 -msgctxt "monster" -msgid "Scavenger" -msgstr "Carroñero" +#: Source/stores.cpp:1195 +msgid "This item is:" +msgstr "Este objeto es:" -#: Source/monstdat.cpp:48 -msgctxt "monster" -msgid "Plague Eater" -msgstr "Devorador de la Plaga" +#: Source/stores.cpp:1198 +msgid "Done" +msgstr "Hecho" -#: Source/monstdat.cpp:49 -msgctxt "monster" -msgid "Shadow Beast" -msgstr "Bestia de las Sombras" +#: Source/stores.cpp:1207 +#, c++-format +msgid "Talk to {:s}" +msgstr "Habla con {:s}" -#: Source/monstdat.cpp:50 -msgctxt "monster" -msgid "Bone Gasher" -msgstr "Desgarrador de Huesos" +#: Source/stores.cpp:1210 +#, c++-format +msgid "Talking to {:s}" +msgstr "Hablando con {:s}" -#: Source/monstdat.cpp:52 -msgctxt "monster" -msgid "Corpse Bow" -msgstr "Arquero Cadáver" +#: Source/stores.cpp:1211 +msgid "is not available" +msgstr "no está disponible" -#: Source/monstdat.cpp:55 -msgctxt "monster" -msgid "Skeleton Captain" -msgstr "Capitán Esqueleto" +#: Source/stores.cpp:1212 +msgid "in the shareware" +msgstr "en el shareware" -#: Source/monstdat.cpp:56 -msgctxt "monster" -msgid "Corpse Captain" -msgstr "Capitán Cadáver" +#: Source/stores.cpp:1213 +msgid "version" +msgstr "versión" -#: Source/monstdat.cpp:57 -msgctxt "monster" -msgid "Burning Dead Captain" -msgstr "Capitán Muerte Ardiente" +#: Source/stores.cpp:1240 +msgid "Gossip" +msgstr "Chismorrear" -#: Source/monstdat.cpp:58 -msgctxt "monster" -msgid "Horror Captain" -msgstr "Capitán del Horror" +#: Source/stores.cpp:1249 +msgid "Rising Sun" +msgstr "Taberna del Sol Naciente" -#: Source/monstdat.cpp:59 -msgctxt "monster" -msgid "Invisible Lord" -msgstr "Señor Invisible" +#: Source/stores.cpp:1251 +msgid "Talk to Ogden" +msgstr "Hablar" -#: Source/monstdat.cpp:60 -msgctxt "monster" -msgid "Hidden" -msgstr "Oculto" +#: Source/stores.cpp:1252 +msgid "Leave the tavern" +msgstr "Dejar la taberna" -#: Source/monstdat.cpp:61 -msgctxt "monster" -msgid "Stalker" -msgstr "Acechador" +#: Source/stores.cpp:1263 +msgid "Talk to Gillian" +msgstr "Hablar" -#: Source/monstdat.cpp:62 -msgctxt "monster" -msgid "Unseen" -msgstr "Invisible" +#: Source/stores.cpp:1264 +msgid "Access Storage" +msgstr "Examinar Alijo" -#: Source/monstdat.cpp:63 -msgctxt "monster" -msgid "Illusion Weaver" -msgstr "Tejedor de Ilusiones" +#: Source/stores.cpp:1274 Source/towners.cpp:207 +msgid "Farnham the Drunk" +msgstr "Farnham el Borracho" -#: Source/monstdat.cpp:64 -msgctxt "monster" -msgid "Satyr Lord" -msgstr "Señor Sátiro" +#: Source/stores.cpp:1276 +msgid "Talk to Farnham" +msgstr "Hablar" -#: Source/monstdat.cpp:65 Source/monstdat.cpp:73 -msgctxt "monster" -msgid "Flesh Clan" -msgstr "Clan de Carne" +#: Source/stores.cpp:1277 +msgid "Say Goodbye" +msgstr "Despedirte" -#: Source/monstdat.cpp:66 Source/monstdat.cpp:74 -msgctxt "monster" -msgid "Stone Clan" -msgstr "Clan de Piedra" +#: Source/stores.cpp:2410 +#, c++-format +msgid "Your gold: {:s}" +msgstr "Tu oro: {:s}" -#: Source/monstdat.cpp:67 Source/monstdat.cpp:75 -msgctxt "monster" -msgid "Fire Clan" -msgstr "Clan de Fuego" +#. 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 "" +" Ah, la historia de nuestro Rey, ¿verdad? La trágica caída de Leoric fue un " +"duro golpe para esta tierra. La gente siempre amó al Rey, y ahora vive con " +"un miedo mortal hacia él. La pregunta que me sigo haciendo es cómo pudo " +"haber caído tan lejos de la Luz, ya que Leoric siempre había sido el más " +"santo de los hombres. Sólo los poderes más viles del Infierno podrían " +"destruir tan completamente a un hombre desde dentro ..." -#: Source/monstdat.cpp:68 Source/monstdat.cpp:76 -msgctxt "monster" -msgid "Night Clan" -msgstr "Clan Nocturno" +#. 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 "" +"¡El pueblo necesita tu ayuda, buen maestro! Hace algunos meses, el hijo del " +"rey Leoric, el Príncipe Albrecht, fue secuestrado. El rey se enfureció y " +"recorrió el pueblo en busca de su hijo desaparecido. Con cada día que " +"pasaba, Leoric parecía hundirse cada vez más en la locura. Trató de culpar a " +"los habitantes inocentes de la desaparición del niño y los ejecutó " +"brutalmente. Menos de la mitad de nosotros sobrevivimos a su locura ...\n" +" \n" +"Los Caballeros y Sacerdotes del Rey intentaron aplacarlo, pero él se volvió " +"contra ellos y, lamentablemente, se vieron obligados a matarlo. Con su " +"último aliento, el Rey lanzó una terrible maldición sobre sus antiguos " +"seguidores. Juró que lo servirían en la oscuridad para siempre ...\n" +" \n" +"¡Aquí es donde las cosas toman un giro aún más oscuro de lo que creía " +"posible! Nuestro antiguo Rey se ha levantado de su sueño eterno y ahora " +"comanda una legión de esbirros de muertos vivientes dentro del Laberinto. Su " +"cuerpo fue enterrado en una tumba tres niveles debajo de la Catedral. Por " +"favor, buen maestro, tranquilice su alma destruyendo su forma ahora " +"maldita ..." -#: Source/monstdat.cpp:69 -msgctxt "monster" -msgid "Fiend" -msgstr "Maligno" +#. 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 "" +"Como le dije, buen maestro, el Rey fue sepultado tres niveles más abajo. " +"Está ahí abajo, esperando en la pútrida oscuridad su oportunidad de destruir " +"esta tierra ..." -#: Source/monstdat.cpp:70 -msgctxt "monster" -msgid "Blink" -msgstr "Murciélago destellante" +#. 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 "" +"La maldición de nuestro Rey ha terminado, pero me temo que fue solo una " +"parte de un mal mayor en acción. Sin embargo, aún podemos salvarnos de la " +"oscuridad que consume nuestra tierra, porque tu victoria es un buen augurio. " +"Que la Luz te guíe en tu camino, buen maestro." -#: Source/monstdat.cpp:71 -msgctxt "monster" -msgid "Gloom" -msgstr "Murciélago oscuro" +#. 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 "" +"La pérdida de su hijo fue demasiado para el Rey Leoric. Hice lo que pude " +"para aliviar su locura, pero al final lo superó. Una maldición negra se " +"cierne sobre este reino desde ese día, pero tal vez si liberaras su espíritu " +"de su prisión terrenal, la maldición se levantaría ..." -#: Source/monstdat.cpp:72 -msgctxt "monster" -msgid "Familiar" -msgstr "Familiar" +#. 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 "" +"No me gusta pensar en cómo murió el Rey. Me gusta recordarlo como el " +"gobernante amable y justo que era. Su muerte fue tan triste y parecía muy " +"mal, de alguna manera." -#: Source/monstdat.cpp:77 -msgctxt "monster" -msgid "Acid Beast" -msgstr "Bestia Ácida" +#. 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 "" +"Hice muchas de las armas y la mayor parte de las armaduras que el rey Leoric " +"usó para equipar a sus caballeros. Incluso le elaboré una enorme espada a " +"dos manos del mejor mithril, así como una corona de campo a juego. Todavía " +"no puedo creer cómo murió ¡Pero debe haber sido alguna fuerza siniestra lo " +"que lo volvió loco!" -#: Source/monstdat.cpp:78 -msgctxt "monster" -msgid "Poison Spitter" -msgstr "Escupidor de Veneno" +#. 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 "" +"Eso no me importa. Escucha, ningún esqueleto será MI rey. Leoric es el Rey. " +"Rey, ¿me escuchas? ¡VIVA EL REY!" -#: Source/monstdat.cpp:79 -msgctxt "monster" -msgid "Pit Beast" -msgstr "Bestia del Foso" +#. 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 "" +"Los muertos que caminan entre los vivos siguen al Rey maldito. Tiene el " +"poder de crear aún más guerreros para un ejército de muertos vivientes en " +"constante crecimiento. Si no detienes su reinado, seguramente marchará a " +"través de esta tierra y matará a todos los que todavía viven aquí." -#: Source/monstdat.cpp:80 -msgctxt "monster" -msgid "Lava Maw" -msgstr "Fauces de Lava" +#. 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 "" +"Mira, tengo un negocio aquí. No vendo información, y no me importa un Rey " +"que ha estado muerto más tiempo que yo vivo. Si necesitas algo para usar " +"contra este Rey de los muertos vivientes, entonces puedo ayudarte ..." -#: Source/monstdat.cpp:81 Source/monstdat.cpp:342 -msgctxt "monster" -msgid "Skeleton King" -msgstr "Rey Esqueleto" +#. 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 "" +"El calor de la vida ha entrado en mi tumba. ¡Prepárate, mortal, para servir " +"a mi Maestro por la eternidad!" -#: Source/monstdat.cpp:82 Source/monstdat.cpp:350 -msgctxt "monster" -msgid "The Butcher" -msgstr "El Carnicero" +#. 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 "" +"Veo que este comportamiento extraño también te desconcierta. Supongo que, " +"dado que muchos demonios temen la luz del sol y creen que tienen un gran " +"poder, es posible que el sol naciente representado en el letrero del que " +"hablas les haya llevado a creer que también tiene algunos poderes arcanos. " +"Mmm, quizás no todos sean tan inteligentes como nos temíamos ..." -#: Source/monstdat.cpp:83 -msgctxt "monster" -msgid "Overlord" -msgstr "Cacique" +#. 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 "" +"Maestro, tengo una experiencia extraña que contarle. Sé que tiene un gran " +"conocimiento de esas monstruosidades que habitan el laberinto, y esto es " +"algo que no puedo entender, por mi vida ... Me desperté durante la noche por " +"un sonido de rascado justo afuera de mi taberna. Cuando miré desde mi " +"habitación, vi las formas de pequeñas criaturas parecidas a demonios en el " +"patio de la posada. Poco tiempo después, se fueron corriendo, no sin antes " +"robar el cartel de mi posada. No sé por qué los demonios robarían mi cartel " +"y dejarían a mi familia en paz ... es extraño, ¿no?" -#: Source/monstdat.cpp:84 -msgctxt "monster" -msgid "Mud Man" -msgstr "Hombre de Barro" +#. 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 "" +"Oh, no tenías que traer mi letrero, pero supongo que me ahorra el gasto de " +"hacer otro. Bueno, déjame ver, ¿qué puedo darte como tarifa por encontrarlo? " +"Hmmm, qué tenemos aquí ... ¡ah, sí! Este gorro fue dejado en una de las " +"habitaciones por un mago que se quedó aquí hace algún tiempo. Quizás pueda " +"tener algún valor para ti." -#: Source/monstdat.cpp:85 -msgctxt "monster" -msgid "Toad Demon" -msgstr "Demonio Sapo" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Dios mío, los demonios que corren por la aldea de noche, saquean nuestras " +"casas, ¿no es nada sagrado? Espero que Ogden y Garda estén bien. Supongo que " +"vendrían a verme si les hubieran hecho daño ..." -#: Source/monstdat.cpp:86 -msgctxt "monster" -msgid "Flayed One" -msgstr "El Desollado" +#. 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 "" +"¡Oh Dios! ¿Es ahí donde fue el letrero? Mi Abuela y yo debimos haber dormido " +"todo el rato. Gracias a la Luz que esos monstruos no atacaron la posada." -#: Source/monstdat.cpp:87 -msgctxt "monster" -msgid "Wyrm" -msgstr "Pequeño Dragón" +#. 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 "" +"¿Dices que los demonios robaron el letrero de Ogden? Eso no se parece mucho " +"a las atrocidades de las que he oído, o visto. \n" +" \n" +"A los demonios les preocupa arrancarte el corazón, no tu letrero." -#: Source/monstdat.cpp:88 -msgctxt "monster" -msgid "Cave Slug" -msgstr "Babosa de la Cueva" +#. 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 "" +"¿Sabes lo que pienso? Alguien tomó ese letrero y querrán mucho dinero por " +"él. Si yo fuera Ogden ... y no lo soy, pero si fuera ... compraría un nuevo " +"cartel con un bonito dibujo. Quizás una buena jarra de cerveza o un trozo de " +"queso ..." -#: Source/monstdat.cpp:89 -msgctxt "monster" -msgid "Devil Wyrm" -msgstr "Pequeño Dragón Demoníaco" +#. 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 "" +"Ningún mortal puede comprender verdaderamente la mente del demonio. \n" +" \n" +"Nunca dejes que sus acciones erráticas te confundan, ya que ese también " +"puede ser su plan." -#: Source/monstdat.cpp:90 -msgctxt "monster" -msgid "Devourer" -msgstr "Devorador" +#. 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 "" +"¿Qué? ¿Está diciendo que me llevé eso? Supongo que Griswold también está de " +"su lado. \n" +" \n" +"Mira, superé el simple robo de letreros hace meses. No puede obtener " +"ganancias con un trozo de madera." -#: Source/monstdat.cpp:91 -msgctxt "monster" -msgid "Magma Demon" -msgstr "Demonio de Magma" +#. 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 "" +"¡Oye, tú eres el que mata a todos! ¡Consígueme el Estandarte Mágico o " +"atacamos! ¡No dejes con vida! Matas a los grandes feos y devuelves la magia. " +"Pasa la esquina y la puerta, encuentra a los feos. ¡Das, te vas!" -#: Source/monstdat.cpp:92 -msgctxt "monster" -msgid "Blood Stone" -msgstr "Piedra de Sangre" +#. TRANSLATORS: Quest dialog spoken by Snotspill (Hostile) +#: Source/textdat.cpp:57 +msgid "You kill uglies, get banner. You bring to me, or else..." +msgstr "Matas a los feos, obtienes estandarte. Me lo traes, o si no ..." -#: Source/monstdat.cpp:93 -msgctxt "monster" -msgid "Hell Stone" -msgstr "Piedra del Infierno" +#. 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 "" +"¡Das! ¡Si, bien! Vete ahora, somos fuertes. ¡Matamos a todos con gran Magia!" -#: Source/monstdat.cpp:94 -msgctxt "monster" -msgid "Lava Lord" -msgstr "Señor de la Lava" +#. 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 "" +"Esto no augura nada bueno, ya que confirma mis temores más oscuros. Si bien " +"no me permití creer las leyendas antiguas, no puedo negarlas ahora. Quizás " +"ha llegado el momento de revelar quién soy.\n" +" \n" +"Mi verdadero nombre es Deckard Cain el Sabio, y soy el último descendiente " +"de una antigua Hermandad que se dedicó a salvaguardar los secretos de un mal " +"atemporal. Un mal que obviamente ahora se ha liberado.\n" +" \n" +"El arzobispo Lazarus, una vez el consejero más confiable del Rey Leoric, " +"condujo a un grupo de simples habitantes del pueblo al Laberinto para " +"encontrar al hijo desaparecido del Rey, Albrecht. Pasó bastante tiempo antes " +"de que regresaran, y solo unos pocos escaparon con vida.\n" +" \n" +"¡Maldito sea por tonto! Entonces debería haber sospechado su velada " +"traición. Debe haber sido el mismo Lazarus quien secuestró a Albrecht y " +"desde entonces lo ha escondido dentro del Laberinto. No entiendo por qué el " +"Arzobispo se volvió hacia la oscuridad, ni cuál es su interés en el niño. ¡a " +"menos que tenga la intención de sacrificarlo a sus amos oscuros!\n" +" \n" +"¡Eso debe ser lo que ha planeado! Los sobrevivientes de su 'grupo de " +"rescate' dicen que Lazarus fue visto por última vez corriendo hacia las " +"entrañas más profundas del laberinto. ¡Debes darte prisa y salvar al " +"príncipe de la espada sacrifical de este demonio demente!" -#: Source/monstdat.cpp:95 -msgctxt "monster" -msgid "Horned Demon" -msgstr "Demonio Cornudo" +#. 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 "" +"Debes darte prisa y rescatar a Albrecht de las manos de Lazarus. ¡El " +"príncipe y la gente de este reino cuentan contigo!" -#: Source/monstdat.cpp:96 -msgctxt "monster" -msgid "Mud Runner" -msgstr "Corredor de Barro" +#. 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 "" +"Tu historia es bastante sombría, amigo. Lazarus seguramente arderá en el " +"Infierno por su horrible acto. El chico que describe no es nuestro príncipe, " +"pero creo que Albrecht aún puede estar en peligro. El símbolo de poder del " +"que hablas debe ser un portal en el corazón mismo del laberinto.\n" +" \n" +"Debes saber esto, amigo mío: el mal contra el que te mueves es el oscuro " +"Señor del Terror. Es conocido por los hombres mortales como Diablo. Fue él " +"quien fue encarcelado dentro del Laberinto hace muchos siglos y me temo que " +"busca sembrar una vez más el caos en el reino de la humanidad. ¡Debes " +"aventurarte a través del portal y destruir a Diablo antes de que sea " +"demasiado tarde!" -#: Source/monstdat.cpp:97 -msgctxt "monster" -msgid "Frost Charger" -msgstr "Cargador de Escarcha" - -#: Source/monstdat.cpp:98 -msgctxt "monster" -msgid "Obsidian Lord" -msgstr "Señor de Obsidiana" +#. 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 "" +"Lazarus fue el Arzobispo que condujo a muchos de los habitantes del pueblo " +"al laberinto. Ese día perdí muchos buenos amigos y Lazarus nunca regresó. " +"Supongo que lo mataron junto con la mayoría de los demás. Si quiere hacerme " +"un favor, buen maestro, por favor no hable con Farnham sobre ese día." -#: Source/monstdat.cpp:99 -msgctxt "monster" -msgid "oldboned" -msgstr "envejecido" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Me sorprendió cuando me enteré de lo que la gente del pueblo planeaba hacer " +"esa noche. Pensé que, de todas las personas, Lazarus habría tenido más " +"sentido común. Era Arzobispo y siempre pareció preocuparse mucho por la " +"gente del pueblo de Tristram. Tantos resultaron heridos, no pude salvarlos a " +"todos ..." -#: Source/monstdat.cpp:100 -msgctxt "monster" -msgid "Red Death" -msgstr "Muerte Roja" +#. 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 "" +"Recuerdo a Lazarus como un hombre muy amable y generoso. Habló en el funeral " +"de mi madre y nos apoyó a mi abuela y a mí en un momento muy difícil. Rezo " +"todas las noches para que, de alguna manera, todavía esté vivo y a salvo." -#: Source/monstdat.cpp:101 -msgctxt "monster" -msgid "Litch Demon" -msgstr "Demonio Exánime" +#. 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 "" +"Estaba allí cuando Lazarus nos condujo al laberinto. Habló de la santa " +"retribución, pero cuando empezamos a luchar contra esos engendros del " +"infierno, ni siquiera levantó su maza contra ellos. ¡Simplemente corrió más " +"profundamente en las oscuras e interminables cámaras que estaban llenas de " +"los sirvientes de la oscuridad!" -#: Source/monstdat.cpp:102 -msgctxt "monster" -msgid "Undead Balrog" -msgstr "Balrog Zombi" +#. 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 "" +"Te apuñalan, luego muerden y luego te rodean. ¡Mentiroso! ¡MENTIROSO! ¡Están " +"todos muertos! ¡Muertos! ¿Me escuchas? Siguen cayendo y cayendo ... su " +"sangre se derrama por todo el suelo ... todo es culpa suya ..." -#: Source/monstdat.cpp:103 -msgctxt "monster" -msgid "Incinerator" -msgstr "Incinerador" +#. 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 "" +"No conocía a este Lazarus de quien hablas, pero siento un gran conflicto " +"dentro de su ser. Representa un gran peligro y no se detendrá ante nada para " +"servir a los poderes de las tinieblas que lo han reclamado como suyo." -#: Source/monstdat.cpp:104 -msgctxt "monster" -msgid "Flame Lord" -msgstr "Señor de las Llamas" +#. 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 "" +"Sí, el justo Lazarus, que fue taaan eficaz contra esos monstruos de allí. No " +"ayudó a salvar mi pierna, ¿verdad? Mira, te daré un consejo gratis. " +"Pregúntale a Farnham, él estaba allí." -#: Source/monstdat.cpp:105 -msgctxt "monster" -msgid "Doom Fire" -msgstr "Fuego de Destrucción" +#. 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 "" +"Abandona tu tonta búsqueda. ¡Todo lo que les espera es la ira de mi Maestro! " +"Es demasiado tarde para salvar al niño. ¡Ahora te unirás a él en el Infierno!" -#: Source/monstdat.cpp:106 -msgctxt "monster" -msgid "Hell Burner" -msgstr "Quemador del Infierno" +#. 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 "" +"Hmm, no sé qué puedo decirte realmente que sea de alguna ayuda. El agua que " +"llena nuestros pozos proviene de un manantial subterráneo. He oído hablar de " +"un túnel que conduce a un gran lago - tal vez sean lo mismo. " +"Desafortunadamente, no sé qué pasaría si nuestro suministro de agua " +"estuviera contaminado." -#: Source/monstdat.cpp:107 -msgctxt "monster" -msgid "Red Storm" -msgstr "Tormenta Roja" +#. 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 "" +"Siempre he tratado de mantener una gran cantidad de alimentos y bebidas en " +"nuestro sótano de almacenamiento, pero como todo el pueblo no tiene una " +"fuente de agua dulce, incluso nuestras tiendas se secarán pronto. \n" +" \n" +"Por favor, haz lo que puedas o no sé qué haremos." -#: Source/monstdat.cpp:108 -msgctxt "monster" -msgid "Storm Rider" -msgstr "Jinete de Tormenta" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"¡Me alegro de haberte encontrado a tiempo! Nuestros pozos se han vuelto " +"salobres, estancados y algunos de los habitantes del pueblo han enfermado al " +"beber de ellos. Nuestras reservas de agua dulce se están secando " +"rápidamente. Creo que hay un pasadizo que conduce a los manantiales que " +"sirven a nuestro pueblo. Por favor, averigua qué ha causado esta calamidad, " +"o seguramente todos moriremos." -#: Source/monstdat.cpp:109 -msgctxt "monster" -msgid "Storm Lord" -msgstr "Señor de las Tormentas" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Por favor, debes darse prisa. Cada hora que pasa nos acerca a no tener agua " +"para beber. \n" +" \n" +"No podemos sobrevivir por mucho tiempo sin tu ayuda." -#: Source/monstdat.cpp:110 -msgctxt "monster" -msgid "Maelstrom" -msgstr "Remolino" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"¿Qué dices? ¿La mera presencia de los demonios provocó que el agua se " +"contaminara? Oh, verdaderamente un gran mal acecha debajo de nuestro pueblo, " +"pero tu perseverancia y coraje nos dan esperanza. Por favor, toma este " +"anillo, tal vez te ayude a destruir a esas criaturas tan viles." -#: Source/monstdat.cpp:111 -msgctxt "monster" -msgid "Devil Kin Brute" -msgstr "Pariente del Demonio Bruto" +#. 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 "" +"Mi abuela está muy débil y Garda dice que no podemos beber el agua de los " +"pozos. Por favor, ¿puedes hacer algo para ayudarnos?" -#: Source/monstdat.cpp:112 -msgctxt "monster" -msgid "Winged-Demon" -msgstr "Demonio Alado" +#. 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 "" +"Pepin te ha dicho la verdad. Necesitaremos agua dulce con urgencia, y " +"pronto. He intentado limpiar uno de los pozos más pequeños, pero huele a " +"suciedad estancada. Debe estar obstruido en la fuente." -#: Source/monstdat.cpp:113 -msgctxt "monster" -msgid "Gargoyle" -msgstr "Gárgola" +#. TRANSLATORS: Quest dialog spoken by Farnham +#: Source/textdat.cpp:100 +msgid "You drink water?" +msgstr "¿Tu bebes agua?" -#: Source/monstdat.cpp:114 -msgctxt "monster" -msgid "Blood Claw" -msgstr "Garra de Sangre" +#. 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 "" +"La gente de Tristram morirá si no puedes devolver agua fresca a sus pozos. \n" +" \n" +"Sepa esto: los demonios están en el centro de este asunto, pero siguen " +"ignorando lo que han engendrado." -#: Source/monstdat.cpp:115 -msgctxt "monster" -msgid "Death Wing" -msgstr "Ala de la Muerte" +#. 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 "" +"Por una vez, estoy contigo. Mi negocio se seca, por así decirlo, si no tengo " +"un mercado al que vender. ¡Será mejor que averigüe lo que está pasando, y " +"pronto!" -#: Source/monstdat.cpp:116 -msgctxt "monster" -msgid "Slayer" -msgstr "Asesino" +#. 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 "" +"¿Un libro que habla de una cámara de huesos humanos? Bueno, una Cámara de " +"Hueso se menciona en ciertos escritos arcaicos que estudié en las " +"bibliotecas del Este. Estos tomos inferían que cuando los Señores del " +"inframundo desearan proteger grandes tesoros, crearían dominios donde " +"aquellos que murieran en el intento de robar ese tesoro estarían obligados a " +"defenderlo para siempre. ¿Un final retorcido, pero extrañamente apropiado?" -#: Source/monstdat.cpp:117 -msgctxt "monster" -msgid "Guardian" -msgstr "Guardián" +#. 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 "" +"Me temo que no sé nada de eso, buen maestro. Caín tiene muchos libros que " +"pueden ser de alguna ayuda." -#: Source/monstdat.cpp:118 -msgctxt "monster" -msgid "Vortex Lord" -msgstr "Señor del Vórtice" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: Source/textdat.cpp:109 +msgid "" +"This sounds like a very dangerous place. If you venture there, please take " +"great care." +msgstr "Parece un lugar muy peligroso. Si te aventuras allí ten mucho cuidado." -#: Source/monstdat.cpp:119 -msgctxt "monster" -msgid "Balrog" -msgstr "Balrog" +#. 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 "" +"Me temo que no he escuchado nada al respecto. Quizás Caín el Narrador podría " +"ser de alguna ayuda." -#: Source/monstdat.cpp:120 -msgctxt "monster" -msgid "Cave Viper" -msgstr "Víbora de Cueva" +#. 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 "" +"No sé nada de este lugar, pero puedes intentar preguntarle a Caín. Habla de " +"muchas cosas y no me sorprendería que tuviera algunas respuestas a tu " +"pregunta." -#: Source/monstdat.cpp:121 -msgctxt "monster" -msgid "Fire Drake" -msgstr "Dragón de Fuego" +#. 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 "" +"Bien, escucha. Ahí está esta cámara de madera ¿ves? Y su esposa, ya sabes, " +"ella, le dice al árbol ... porque tienes que esperar. Entonces digo, eso " +"podría funcionar en su contra, pero si crees que voy a PAGAR por esto ... " +"tú ... eh ... sí." -#: Source/monstdat.cpp:122 -msgctxt "monster" -msgid "Gold Viper" -msgstr "Víbora de Oro" +#. 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 "" +"Te convertirás en un sirviente eterno de los señores oscuros si pereces " +"dentro de este dominio maldito. \n" +" \n" +"Ingresa a la Cámara de Hueso bajo tu propio riesgo." -#: Source/monstdat.cpp:123 -msgctxt "monster" -msgid "Azure Drake" -msgstr "Dragón Azur" +#. 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 "" +"¿Un tesoro vasto y misterioso, dices? Tal vez podría estar interesado en " +"recoger algunas cosas de ti ... o mejor aún, ¿no necesita algunos " +"suministros raros y costosos para superar este calvario?" -#: Source/monstdat.cpp:124 -msgctxt "monster" -msgid "Black Knight" -msgstr "Caballero Negro" +#. 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 "" +"Parece que el Arzobispo Lazarus incitó a muchos de los habitantes del pueblo " +"a aventurarse en el Laberinto para encontrar al hijo desaparecido del Rey. " +"Jugó con sus miedos y los convirtió en una multitud frenética. Ninguno de " +"ellos estaba preparado para lo que había dentro de la tierra fría ... " +"Lazarus los abandonó allí - dejados en las garras de indescriptibles " +"horrores - para morir." -#: Source/monstdat.cpp:125 -msgctxt "monster" -msgid "Doom Guard" -msgstr "Guardia de la Destrucción" +#. 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 "" +"Sí, Farnham ha murmurado algo sobre un enorme bruto que empuñaba un arma " +"feroz. Creo que lo llamó carnicero." -#: Source/monstdat.cpp:126 -msgctxt "monster" -msgid "Steel Lord" -msgstr "Señor de Acero" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Por la Luz, conozco a este vil demonio. Hubo muchos que llevaron las " +"cicatrices de su ira en sus cuerpos cuando los pocos supervivientes de la " +"carga encabezada por Lazarus salieron arrastrándose de la Catedral. No sé " +"qué utilizó para cortar a sus víctimas, pero no pudo haber sido de este " +"mundo. Dejó heridas infectadas por la enfermedad e incluso yo las encontré " +"casi imposibles de tratar. Cuidado si planeas luchar contra este demonio ..." -#: Source/monstdat.cpp:127 -msgctxt "monster" -msgid "Blood Knight" -msgstr "Caballero de Sangre" +#. 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 "" +"Cuando Farnham dijo algo sobre un carnicero matando gente, inmediatamente lo " +"descarté. Pero ya que lo mencionaste, tal vez sea cierto." -#: Source/monstdat.cpp:128 -msgctxt "monster" -msgid "The Shredded" -msgstr "El Destrozado" - -#: Source/monstdat.cpp:129 -msgctxt "monster" -msgid "Hollow One" -msgstr "El Hueco" - -#: Source/monstdat.cpp:130 -msgctxt "monster" -msgid "Pain Master" -msgstr "Maestro del Dolor" +#. 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 "" +"Vi lo que Farnham llama el Carnicero mientras se abría camino a través de " +"los cuerpos de mis amigos. Blandió una cuchilla del tamaño de un hacha, " +"cortando miembros y cortando a hombres valientes donde estaban. Me separaron " +"de la refriega una multitud de pequeños demonios que chillaban y, de alguna " +"manera, encontré la escalera que conducía hacia afuera. Nunca volví a ver a " +"esa horrible bestia, pero su rostro manchado de sangre me persigue hasta el " +"día de hoy." -#: Source/monstdat.cpp:131 -msgctxt "monster" -msgid "Reality Weaver" -msgstr "Tejedora de Realidad" +#. 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 "" +"¡Grande! Una cuchilla grande matando a todos mis amigos. No pude detenerlo, " +"tuve que huir, no pude salvarlos. Atrapado en una habitación con tantos " +"cuerpos ... tantos amigos ... ¡NOOOOOOOOOO!" -#: Source/monstdat.cpp:132 -msgctxt "monster" -msgid "Succubus" -msgstr "Súcubo" +#. 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 "" +"El Carnicero es una criatura sádica que se deleita con la tortura y el dolor " +"de los demás. Has visto su obra en el borracho Farnham. Su destrucción hará " +"mucho para garantizar la seguridad de esta aldea." -#: Source/monstdat.cpp:133 -msgctxt "monster" -msgid "Snow Witch" -msgstr "Bruja de la Nieve" +#. 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 "" +"Sé más de lo que piensas sobre ese demonio espantoso. Sus amiguitos me " +"agarraron y lograron sacarme la pierna antes de que Griswold me sacara de " +"ese agujero. \n" +" \n" +"Lo diré sin rodeos: mátalo antes de que te mate y agregue tu cadáver a su " +"colección." -#: Source/monstdat.cpp:134 -msgctxt "monster" -msgid "Hell Spawn" -msgstr "Engendro del Infierno" +#. 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 "" +"Por favor escúchame. El arzobispo Lazarus, nos llevó hasta aquí para " +"encontrar al príncipe perdido. ¡El bastardo nos llevó a una trampa! Ahora " +"todo el mundo está muerto ... asesinados por un demonio que llamó el " +"Carnicero. ¡Vénganos! Encuentra a este Carnicero y mátalo para que nuestras " +"almas finalmente descansen ..." -#: Source/monstdat.cpp:135 -msgctxt "monster" -msgid "Soul Burner" -msgstr "Quemador de Almas" +#. 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 "" +"Recitas una rima interesante escrita en un estilo que me recuerda a otras " +"obras. Déjame pensar ahora, ¿qué fue?\n" +" \n" +"... La oscuridad envuelve lo Oculto. Ojos que brillan sin ser vistos con " +"solo el sonido de las garras como navajas raspando brevemente para " +"atormentar a esas pobres almas que han quedado ciegas por toda la eternidad. " +"La prisión para los condenados se llama las Cámaras de los Ciegos ..." -#: Source/monstdat.cpp:136 -msgctxt "monster" -msgid "Counselor" -msgstr "Consejero" +#. 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 "" +"Nunca me interesó mucho la poesía. De vez en cuando, tenía motivos para " +"contratar juglares cuando la posada iba bien, pero eso parece que fue hace " +"mucho tiempo. \n" +" \n" +"¿Qué? Oh, sí ... eh, bueno, supongo que podrías ver lo que alguien más sabe." -#: Source/monstdat.cpp:137 -msgctxt "monster" -msgid "Magistrate" -msgstr "Magistrado" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Esto parece familiar, de alguna manera. Me parece recordar haber leído algo " +"muy parecido a ese poema mientras investigaba la historia de las aflicciones " +"demoníacas. Hablaba de un lugar de gran maldad que ... espera, no vas a ir " +"allí, ¿verdad?" -#: Source/monstdat.cpp:138 -msgctxt "monster" -msgid "Cabalist" -msgstr "Cabalista" +#. 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 "" +"Si tienes preguntas sobre la ceguera, debes hablar con Pepin. Sé que le dio " +"a mi abuela una poción que ayudó a aclarar su visión, así que tal vez él " +"también pueda ayudarte." -#: Source/monstdat.cpp:139 -msgctxt "monster" -msgid "Advocate" -msgstr "Defensor" +#. 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 "" +"Me temo que no he escuchado ni visto un lugar que coincida con su vívida " +"descripción, amigo mío. Quizás Caín el Narrador podría ser de alguna ayuda." -#: Source/monstdat.cpp:140 -msgctxt "monster" -msgid "Golem" -msgstr "Gólem" +#. TRANSLATORS: Quest dialog spoken by Farnham +#: Source/textdat.cpp:150 +msgid "Look here... that's pretty funny, huh? Get it? Blind - look here?" +msgstr "" +"Mira aquí ... eso es muy gracioso, ¿eh? ¿Conseguirlo? Ciego, ¿mira aquí?" -#: Source/monstdat.cpp:141 -msgctxt "monster" -msgid "The Dark Lord" -msgstr "El Señor Oscuro" +#. 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 "" +"Este es un lugar de gran angustia y terror, y por eso sirve bien a su amo. \n" +" \n" +"Pisa con cuidado o puede que tu mismo te quedes mucho más tiempo de lo que " +"habías anticipado." -#: Source/monstdat.cpp:142 -msgctxt "monster" -msgid "The Arch-Litch Malignus" -msgstr "El Archi Exánime Maligno" +#. 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 "" +"Veamos, ¿te estoy vendiendo algo? No. ¿Me estás dando dinero para contarte " +"esto? No. ¿Ahora te vas y vas a hablar con el narrador que vive para este " +"tipo de cosas? Si." -#: Source/monstdat.cpp:143 -msgctxt "monster" -msgid "Hellboar" -msgstr "Jabinfierno" +#. 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 "" +"¿Afirmas haber hablado con Lachdanan? Fue un gran héroe durante su vida. " +"Lachdanan fue un hombre honorable y justo que sirvió fielmente a su Rey " +"durante años. Pero claro, eso ya lo sabes.\n" +" \n" +"De aquellos que quedaron atrapados en las garras de la Maldición del Rey, " +"Lachdanan sería el menos propenso a someterse a la oscuridad sin luchar, así " +"que supongo que tu historia podría ser cierta. Si estuviera en tu lugar, " +"amigo mío, encontraría la manera de liberarlo de su tortura." -#: Source/monstdat.cpp:144 -msgctxt "monster" -msgid "Stinger" -msgstr "Escorpió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 "" +"¡Hablas de un valiente guerrero muerto hace mucho tiempo! No diré nada de " +"hablar con los difuntos en el patio de mi posada, muchas gracias." -#: Source/monstdat.cpp:145 -msgctxt "monster" -msgid "Psychorb" -msgstr "Psychorb" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Un elixir dorado, dices. Nunca antes había preparado una poción de ese " +"color, así que no puedo decirte cómo te afectaría si intentaras beberla. " +"Como su sanador, le recomiendo encarecidamente que, si encuentra un elixir " +"de este tipo, haga lo que le pide Lachdanan y NO trate de usarlo." -#: Source/monstdat.cpp:146 -msgctxt "monster" -msgid "Arachnon" -msgstr "Arachnon" +#. 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 "" +"Nunca antes había oído hablar de un Lachdanan. Lo siento, pero no creo que " +"pueda ser de mucha ayuda." -#: Source/monstdat.cpp:147 -msgctxt "monster" -msgid "Felltwin" -msgstr "Felltwin" +#. 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 "" +"Si realmente es Lachdanan a quien encontraste, te aconsejo que lo ayudes. " +"Traté con él en varias ocasiones y descubrí que era honesto y leal por " +"naturaleza. La maldición que cayó sobre los seguidores del Rey Leoric caería " +"especialmente sobre él." -#: Source/monstdat.cpp:148 -msgctxt "monster" -msgid "Hork Spawn" -msgstr "Engendro de Hork" +#. 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 "" +" Lachdanan está muerto. Todo el mundo lo sabe, y no puedes engañarme para " +"que piense de otra manera. No puedes hablar con los muertos. ¡Lo sé!" -#: Source/monstdat.cpp:149 -msgctxt "monster" -msgid "Venomtail" -msgstr "Cola Venenosa" +#. 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 "" +"Es posible que encuentres a personas atrapadas dentro del Laberinto, como " +"Lachdanan. \n" +" \n" +"Siento en él honor y una gran culpa. Ayúdale y ayudarás a todo Tristram." -#: Source/monstdat.cpp:150 -msgctxt "monster" -msgid "Necromorb" -msgstr "Necromorb" +#. 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 "" +"Espera, déjame adivinar. Cain fue tragado por una gigantesca fisura que se " +"abrió debajo de él. Fue incinerado en una bola de fuego del infierno y ya no " +"puede responder a tus preguntas. Oh, ¿no es eso lo que pasó? Entonces " +"supongo que comprarás algo o seguirás tu camino." -#: Source/monstdat.cpp:151 -msgctxt "monster" -msgid "Spider Lord" -msgstr "Señor Araña" +#. 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 "" +"Por favor, no me mates, solo escúchame. Una vez fui Capitán de los " +"Caballeros del Rey Leoric, defendiendo las leyes de esta tierra con justicia " +"y honor. Entonces su oscura Maldición cayó sobre nosotros por el papel que " +"jugamos en su trágica muerte. Mientras mis compañeros Caballeros sucumbían a " +"su retorcido destino, huí de la cámara funeraria del Rey, buscando alguna " +"forma de liberarme de la Maldición. Fallé...\n" +" \n" +"He oído hablar de un Elixir Dorado que podría levantar la Maldición y " +"permitir que mi alma descanse, pero no he podido encontrarlo. Mi fuerza " +"ahora se desvanece, y con ella también lo último de mi humanidad. Por favor " +"ayúdame y encuentra el Elixir. Te pagaré tus esfuerzos, lo juro por mi honor." -#: Source/monstdat.cpp:152 -msgctxt "monster" -msgid "Lashworm" -msgstr "Gusano de Pestañas" +#. 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 "" +"No has encontrado el Elixir Dorado. Temo estar condenado por la eternidad. " +"Por favor, sigue intentándolo ..." -#: Source/monstdat.cpp:153 -msgctxt "monster" -msgid "Torchant" -msgstr "Torchant" +#. 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 "" +"Has salvado mi alma de la condenación, y por eso estoy en deuda contigo. Si " +"alguna vez hay una forma de recompensarte desde más allá de la tumba, la " +"encontraré, pero por ahora, toma mi yelmo. En el viaje que estoy a punto de " +"emprender, lo utilizaré poco. Que te proteja contra los poderes oscuros de " +"abajo. Ve con la Luz, amigo mío ..." -#: Source/monstdat.cpp:154 Source/monstdat.cpp:351 -msgctxt "monster" -msgid "Hork Demon" -msgstr "Demonio Hork" +#. 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 "" +"Griswold habla de El yunque de la furia, un artefacto legendario buscado " +"durante mucho tiempo, pero nunca encontrado. Elaborado a partir de los " +"huesos metálicos de los demonios del Pozo de la Navaja, el Yunque de la " +"Furia se fundió alrededor de los cráneos de los cinco magos más poderosos " +"del inframundo. Tallado con runas de poder y caos, cualquier arma o armadura " +"forjada en este Yunque se sumergirá en el reino del Caos, dándole " +"propiedades mágicas. Se dice que la naturaleza impredecible del Caos " +"dificulta saber cuál será el resultado de esta herrería ..." -#: Source/monstdat.cpp:155 -msgctxt "monster" -msgid "Hell Bug" -msgstr "Insecto del Infierno" +#. 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 "" +"¿No crees que Griswold sería una mejor persona para preguntarle sobre esto? " +"Es bastante hábil, ¿sabes?." -#: Source/monstdat.cpp:156 -msgctxt "monster" -msgid "Gravedigger" -msgstr "Sepulturero" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Si hubiera estado buscando información sobre el Mortero de Curación o el " +"Cáliz de Plata de la Purificación, podría haberte ayudado, amigo mío. Sin " +"embargo, en este asunto, sería mejor que hablaras con Griswold o Caín." -#: Source/monstdat.cpp:157 -msgctxt "monster" -msgid "Tomb Rat" -msgstr "Rata de Tumba" +#. 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 "" +"El padre de Griswold solía contarnos a algunos de nosotros, cuando éramos " +"pequeños, acerca de un yunque gigante que se usaba para fabricar armas " +"poderosas. Dijo que cuando se golpeaba un martillo en este yunque, el suelo " +"temblaba con gran furia. Cada vez que la tierra se mueve, siempre recuerdo " +"esa historia." -#: Source/monstdat.cpp:158 -msgctxt "monster" -msgid "Firebat" -msgstr "Murciélago de Fuego" - -#: Source/monstdat.cpp:159 -msgctxt "monster" -msgid "Skullwing" -msgstr "Calavera Alada" - -#: Source/monstdat.cpp:160 -msgctxt "monster" -msgid "Lich" -msgstr "Lich" - -#: Source/monstdat.cpp:161 -msgctxt "monster" -msgid "Crypt Demon" -msgstr "Demonio de la Cripta" - -#: Source/monstdat.cpp:162 -msgctxt "monster" -msgid "Hellbat" -msgstr "Murciélago Infernal" - -#: Source/monstdat.cpp:163 -msgctxt "monster" -msgid "Bone Demon" -msgstr "Demonio de Hueso" - -#: Source/monstdat.cpp:164 -msgctxt "monster" -msgid "Arch Lich" -msgstr "Arch Lich" - -#: Source/monstdat.cpp:165 -msgctxt "monster" -msgid "Biclops" -msgstr "Biclope" - -#: Source/monstdat.cpp:166 -msgctxt "monster" -msgid "Flesh Thing" -msgstr "Cosa de Carne" - -#: Source/monstdat.cpp:167 -msgctxt "monster" -msgid "Reaper" -msgstr "Segador" - -#. TRANSLATORS: Monster Block end -#. MT_NAKRUL -#: Source/monstdat.cpp:169 Source/monstdat.cpp:353 -msgctxt "monster" -msgid "Na-Krul" -msgstr "Na-Krul" - -#. TRANSLATORS: Unique Monster Block start -#: Source/monstdat.cpp:341 -msgctxt "monster" -msgid "Gharbad the Weak" -msgstr "Gharbad el Débil" - -#: Source/monstdat.cpp:343 -msgctxt "monster" -msgid "Zhar the Mad" -msgstr "Zhar el Loco" - -#: Source/monstdat.cpp:344 -msgctxt "monster" -msgid "Snotspill" -msgstr "Derrame de Mocos" - -#: Source/monstdat.cpp:345 -msgctxt "monster" -msgid "Arch-Bishop Lazarus" -msgstr "Arzobispo Lazarus" - -#: Source/monstdat.cpp:346 -msgctxt "monster" -msgid "Red Vex" -msgstr "Molestia Roja" - -#: Source/monstdat.cpp:347 -msgctxt "monster" -msgid "Black Jade" -msgstr "Jade Negro" - -#: Source/monstdat.cpp:348 -msgctxt "monster" -msgid "Lachdanan" -msgstr "Lachdanan" - -#: Source/monstdat.cpp:349 -msgctxt "monster" -msgid "Warlord of Blood" -msgstr "Señor de la Guerra de Sangre" - -#: Source/monstdat.cpp:352 -msgctxt "monster" -msgid "The Defiler" -msgstr "El Profanador" - -#: Source/monstdat.cpp:354 -msgctxt "monster" -msgid "Bonehead Keenaxe" -msgstr "Hacha Afilada de Cabeza de Hueso" - -#: Source/monstdat.cpp:355 -msgctxt "monster" -msgid "Bladeskin the Slasher" -msgstr "Bladeskin el Descuartizador" - -#: Source/monstdat.cpp:356 -msgctxt "monster" -msgid "Soulpus" -msgstr "Pus del Alma" - -#: Source/monstdat.cpp:357 -msgctxt "monster" -msgid "Pukerat the Unclean" -msgstr "Pukerat el Inmundo" - -#: Source/monstdat.cpp:358 -msgctxt "monster" -msgid "Boneripper" -msgstr "Desgarrador de Huesos" - -#: Source/monstdat.cpp:359 -msgctxt "monster" -msgid "Rotfeast the Hungry" -msgstr "Rotfeast el Hambriento" +#. 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 "" +"¡Saludos! ¡Siempre es un placer ver a uno de mis mejores clientes! Sé que te " +"has estado aventurando más profundamente en el Laberinto, y hay una historia " +"que me contaron que puede que valga la pena escucharla ...\n" +" \n" +"Uno de los hombres que regresó del Laberinto me habló de un yunque místico " +"que encontró durante su fuga. Su descripción me recordó las leyendas que " +"había escuchado en mi juventud sobre la ardiente Forja Infernal donde se " +"fabrican poderosas armas mágicas. ¡La leyenda decía que en lo profundo de la " +"Forja Infernal descansaba el Yunque de la Furia! Este Yunque contenía la " +"esencia misma del inframundo demoníaco ...\n" +" \n" +"Se dice que cualquier arma fabricada sobre el Yunque en llamas está imbuida " +"de un gran poder. Si este yunque es de hecho el Yunque de la Furia, ¡podría " +"crearte en un arma capaz de derrotar incluso al señor más oscuro del " +"infierno! \n" +" \n" +"¡Encuentra el Yunque para mí y me pondré manos a la obra!" -#: Source/monstdat.cpp:360 -msgctxt "monster" -msgid "Gutshank the Quick" -msgstr "Gutshank el Veloz" +#. 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 "" +"Nada todavía, ¿eh? Bueno, sigue buscando. Un arma forjada en el Yunque " +"podría ser tu mejor esperanza, y estoy seguro de que puedo crearte una de " +"proporciones legendarias." -#: Source/monstdat.cpp:361 -msgctxt "monster" -msgid "Brokenhead Bangshield" -msgstr "Brokenhead Bangshield" +#. 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 "" +"¡Casi no puedo creerlo! Este es el Yunque de la Furia. Buen trabajo, amigo. " +"¡Ahora les mostraremos a esos bastardos que no hay armas en el infierno más " +"mortíferas que las fabricadas por los hombres! Toma esto y que la Luz te " +"proteja." -#: Source/monstdat.cpp:362 -msgctxt "monster" -msgid "Bongo" -msgstr "Bongo" +#. 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 "" +"Griswold no puede vender su yunque. ¿Qué hará entonces? ¡Y también me " +"enojaría si alguien me quitara el yunque!" -#: Source/monstdat.cpp:363 -msgctxt "monster" -msgid "Rotcarnage" -msgstr "Carnicería de Putrefacción" +#. 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 "" +"Hay muchos artefactos dentro del Laberinto que tienen poderes más allá de la " +"comprensión de los mortales. Algunos de estos tienen un poder fantástico que " +"puede ser utilizado tanto por la Luz como por la Oscuridad. Asegurar el " +"Yunque desde abajo podría cambiar el curso de la Guerra del Pecado hacia la " +"Luz." -#: Source/monstdat.cpp:364 -msgctxt "monster" -msgid "Shadowbite" -msgstr "Mordedura de Sombra" +#. 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 "" +"Si encuentras este artefacto para Griswold, podrías poner en serios " +"problemas a mi negocio. Awwww, nunca lo encontrarás." -#: Source/monstdat.cpp:365 -msgctxt "monster" -msgid "Deadeye" -msgstr "Vigota" +#. 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 "" +"La Puerta de la Sangre y las Cámaras del Fuego son hitos de origen místico. " +"Dondequiera que resida este libro que lea, seguramente es un lugar de gran " +"poder.\n" +" \n" +"Las leyendas hablan de un pedestal que está tallado en piedra de obsidiana y " +"tiene un charco de sangre hirviendo sobre su superficie incrustada de " +"huesos. También hay alusiones a unas Piedras de sangre que abrirán una " +"puerta que guarda un antiguo tesoro ...\n" +" \n" +"La naturaleza de este tesoro está envuelta en especulaciones, amigo mío, " +"pero se dice que el antiguo héroe Arkaine colocó la armadura sagrada Valor " +"en una bóveda secreta. Arkaine fue el primer mortal en cambiar el rumbo de " +"la Guerra del Pecado y perseguir a las legiones de la oscuridad de regreso a " +"los Infiernos Ardientes.\n" +" \n" +"Justo antes de que Arkaine muriera, su armadura estaba escondida en una " +"bóveda secreta. Se dice que cuando se necesite de nuevo esta armadura " +"sagrada, un héroe se levantará para don Valor una vez más. Quizás eres ese " +"héroe ..." -#: Source/monstdat.cpp:366 -msgctxt "monster" -msgid "Madeye the Dead" -msgstr "Madeye el Muerto" +#. 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 "" +"Todos los niños escuchan la historia del guerrero Arkaine y su armadura " +"mística conocida como Valor. Si pudieras encontrar su lugar de descanso, " +"estarías bien protegido contra el mal en el Laberinto." -#: Source/monstdat.cpp:367 -msgctxt "monster" -msgid "El Chupacabras" -msgstr "El Chupacabras" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Hmm ... suena como algo que debería recordar, pero he estado tan ocupado " +"aprendiendo nuevas curas y creando mejores elixires que debí haberlo " +"olvidado. Lo siento ..." -#: Source/monstdat.cpp:368 -msgctxt "monster" -msgid "Skullfire" -msgstr "Calavera de Fuego" +#. 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 "" +"La historia de la armadura mágica llamada Valor es algo de lo que a menudo " +"escuché decir a los chicos. Será mejor que pregunte a uno de los hombres del " +"pueblo." -#: Source/monstdat.cpp:369 -msgctxt "monster" -msgid "Warpskull" -msgstr "Warpskull" +#. 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 "" +"La armadura conocida como Valor podría ser la que incline la balanza a tu " +"favor. Les diré que muchos lo han buscado, incluyéndome a mí. Arkaine lo " +"escondió bien, amigo mío, y se necesitará más que un poco de suerte para " +"descubrir los secretos que lo han mantenido oculto, oh, he aquí hace muchos " +"años." -#: Source/monstdat.cpp:370 -msgctxt "monster" -msgid "Goretongue" -msgstr "Lengua Sangrienta" +#. TRANSLATORS: Quest dialog "spoken" by Farnham +#: Source/textdat.cpp:208 +msgid "Zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz..." +msgstr "Zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz ..." -#: Source/monstdat.cpp:371 -msgctxt "monster" -msgid "Pulsecrawler" -msgstr "Rastreador de Pulsos" +#. 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 "" +"Si encuentras estas Piedras de Sangre, úsalas con cuidado. \n" +" \n" +"El camino está plagado de peligros y tu única esperanza reside en la " +"confianza en ti mismo." -#: Source/monstdat.cpp:372 -msgctxt "monster" -msgid "Moonbender" -msgstr "Moonbender" +#. 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 "" +"¿Tienes la intención de encontrar la armadura conocida como Valor? \n" +" \n" +"Nadie ha descubierto nunca dónde escondió Arkaine las cosas, y si mis " +"contactos no pudieron encontrarlo, dudo seriamente que tú tampoco lo hagas." -#: Source/monstdat.cpp:373 -msgctxt "monster" -msgid "Wrathraven" -msgstr "Cuervo de la Ira" +#. 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 "" +"Solo conozco una leyenda que habla de un guerrero como el que usted " +"describe. Su historia se encuentra dentro de las antiguas crónicas de la " +"Guerra del Pecado ...\n" +" \n" +"Manchado por mil años de guerra, sangre y muerte, el Señor de la Guerra de " +"la Sangre se alza sobre una montaña de sus destrozadas víctimas. Su espada " +"oscura grita una maldición negra a los vivos; una torturada invitación a " +"cualquiera que se presente ante este Verdugo del Infierno.\n" +" \n" +"También está escrito que, aunque una vez fue un mortal que luchó junto a la " +"Legión de la Oscuridad durante la Guerra del Pecado, perdió su humanidad " +"debido a su insaciable hambre de sangre." -#: Source/monstdat.cpp:374 -msgctxt "monster" -msgid "Spineeater" -msgstr "Comedor de Columna" +#. 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 "" +"Me temo que no he oído nada sobre un guerrero tan despiadado, buen maestro. " +"Espero que no tengas que pelear con él, porque parece extremadamente " +"peligroso." -#: Source/monstdat.cpp:375 -msgctxt "monster" -msgid "Blackash the Burning" -msgstr "Blackash el Quemado" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Cain podría contarte mucho más sobre algo como esto de lo que yo jamás " +"desearía saber." -#: Source/monstdat.cpp:376 -msgctxt "monster" -msgid "Shadowcrow" -msgstr "Cuervo de Sombra" +#. 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 "" +"Si vas a luchar contra un oponente tan feroz, que la Luz sea tu guía y tu " +"defensora. Te llevaré en mis pensamientos." -#: Source/monstdat.cpp:377 -msgctxt "monster" -msgid "Blightstone the Weak" -msgstr "Blightstone el Débil" +#. 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 "" +"Leyendas oscuras y malvadas rodean al único Señor de la Guerra de la Sangre. " +"Debes estar bien preparado, amigo mío, porque no muestra piedad ni cuartel." -#: Source/monstdat.cpp:378 -msgctxt "monster" -msgid "Bilefroth the Pit Master" -msgstr "Bilefroth el Maestro del Foso" +#. 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 "" +"¿Siempre tienes que hablar de Sangre? ¿Qué pasa con las flores, el sol y esa " +"chica bonita que trae las bebidas? Escucha, amigo, eres obsesivo, ¿lo sabías?" -#: Source/monstdat.cpp:379 -msgctxt "monster" -msgid "Bloodskin Darkbow" -msgstr "Arco oscuro Piel de Sangre" +#. 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 "" +"Su destreza con la espada es asombrosa, y ha vivido durante miles de años " +"conociendo solo la guerra. Lo siento ... no puedo ver si lo derrotarás." -#: Source/monstdat.cpp:380 -msgctxt "monster" -msgid "Foulwing" -msgstr "Foulwing" +#. 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 "" +"Nunca he tratado con este Señor de la Guerra del que hablas, pero parece que " +"necesite muchas espadas. No me importaría abastecer a sus ejércitos ..." -#: Source/monstdat.cpp:381 -msgctxt "monster" -msgid "Shadowdrinker" -msgstr "Bebedor de Sombras" +#. 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 "" +"Mi espada canta por tu sangre, mortal, y mis oscuros maestros no la negarán." -#: Source/monstdat.cpp:382 -msgctxt "monster" -msgid "Hazeshifter" -msgstr "Cambiador de Neblina" +#. 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 "" +"Griswold habla de la Piedra del Cielo que estaba destinada al enclave " +"ubicado en el este. Se estaba llevando allí para estudiarla más a fondo. " +"Esta piedra brillaba con una energía que de alguna manera otorgaba una " +"visión más allá de la que un hombre normal podría poseer. No sé qué secretos " +"encierra, amigo mío, pero encontrar esta piedra sin duda resultaría de lo " +"más valioso." -#: Source/monstdat.cpp:383 -msgctxt "monster" -msgid "Deathspit" -msgstr "Escupidor de Muerte" +#. 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 "" +"La caravana se detuvo aquí para hacerse con algunos suministros para su " +"viaje hacia el este. Les vendí una gran variedad de frutas frescas y " +"excelentes mollejas que Garda acababa de hornear. Es una lástima lo que les " +"pasó ..." -#: Source/monstdat.cpp:384 -msgctxt "monster" -msgid "Bloodgutter" -msgstr "Bloodgutter" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"No sé qué es lo que pensaron que podían ver con esa piedra, pero diré esto: " +"Si caen rocas del cielo, ¡es mejor que tengas cuidado!" -#: Source/monstdat.cpp:385 -msgctxt "monster" -msgid "Deathshade Fleshmaul" -msgstr "Deathshade Fleshmaul" +#. 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 "" +"Bueno, una caravana de personas muy importantes se detuvo aquí, pero eso fue " +"hace bastante tiempo. Tenían acentos extraños y estaban iniciando un largo " +"viaje, según recuerdo. \n" +" \n" +"No veo cómo podrías esperar encontrar algo que hubieran estado cargando." -#: Source/monstdat.cpp:386 -msgctxt "monster" -msgid "Warmaggot the Mad" -msgstr "Warmaggot el Loco" +#. 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 "" +"Quédate un momento, tengo una historia que puede resultarte interesante. Una " +"caravana que se dirigía a los reinos del este pasó por aquí hace algún " +"tiempo. ¡Supuestamente llevaba un pedazo de los cielos que había caído a la " +"tierra! La caravana fue emboscada por jinetes encapuchados justo al norte de " +"aquí a lo largo de la carretera. Busqué entre los escombros esta roca " +"celeste, pero no la encontré por ningún lado. Si la encuentra, creo que " +"puedo crear algo útil a partir de ella." -#: Source/monstdat.cpp:387 -msgctxt "monster" -msgid "Glasskull the Jagged" -msgstr "Glasskull el Dentado" +#. 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 "" +"Todavía estoy esperando que me traigas esa piedra del cielo. Sé que puedo " +"hacer algo poderoso con eso." -#: Source/monstdat.cpp:388 -msgctxt "monster" -msgid "Blightfire" -msgstr "Añublo" +#. 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 "" +"Déjame ver eso - sí ... sí, es como yo creía. Dame un momento...\n" +" \n" +"Ah, aquí tienes. Acomodé pedazos de la piedra dentro de un anillo de plata " +"que me dejó mi padre. Espero que te sirva bien." -#: Source/monstdat.cpp:389 -msgctxt "monster" -msgid "Nightwing the Cold" -msgstr "Nightwing el Frio" +#. 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 "" +"Solía tener un bonito anillo; era muy caro, con azul, verde, rojo y " +"plateado. Sin embargo, no recuerdo qué le pasó. Realmente extraño ese " +"anillo ..." -#: Source/monstdat.cpp:390 -msgctxt "monster" -msgid "Gorestone" -msgstr "Piedra de Sangre" +#. 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 "" +"La Piedra del Cielo es muy poderosa, y si alguien que no sea Griswold te " +"pidiera que la encontraras, yo lo evitaría. El aprovechará sus poderes y su " +"uso será para el bien de todos nosotros." -#: Source/monstdat.cpp:391 -msgctxt "monster" -msgid "Bronzefist Firestone" -msgstr "Piedra de Fuego Puño de Bronce" +#. 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 "" +"Si alguien puede hacer algo con esa roca, ese es Griswold. Él sabe lo que " +"hace y, por mucho que trato de robar a sus clientes, respeto la calidad de " +"su trabajo." -#: Source/monstdat.cpp:392 -msgctxt "monster" -msgid "Wrathfire the Doomed" -msgstr "Wrathfire el Condenado" +#. 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 "" +"¿La bruja Adria busca un hongo negro? Sé tanto sobre Hongos Negros como " +"sobre Arenques Rojos. Quizás Pepin el Curandero podría contarte más, pero " +"esto es algo que no se puede encontrar en ninguna de mis historias o libros." -#: Source/monstdat.cpp:393 -msgctxt "monster" -msgid "Firewound the Grim" -msgstr "Firewound el Siniestro" +#. 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 "" +"Déjame decirte esto. Tanto Garda como yo nunca, NUNCA serviríamos hongos " +"negros a nuestros invitados de honor. Si Adria quiere hongos en su estofado, " +"entonces es asunto suyo, pero no puedo ayudarte a encontrar ninguno. Hongos " +"negros ... ¡repugnantes!" -#: Source/monstdat.cpp:394 -msgctxt "monster" -msgid "Baron Sludge" -msgstr "Barón Lodo" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"La bruja me dijo que estabas buscando el cerebro de un demonio para ayudarme " +"a crear mi elixir. Debería ser de gran valor para los muchos que resultan " +"heridos por esas horribles bestias, si pudiera descubrir los secretos que " +"sospecho guarda su alquimia. Si puedes quitarle el cerebro a un demonio " +"cuando lo mates, te agradecería que me lo trajeras." -#: Source/monstdat.cpp:395 -msgctxt "monster" -msgid "Blighthorn Steelmace" -msgstr "Maza de acero Cuerno de Plaga" +#. 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 "" +"Excelente, esto es justo lo que tenía en mente. Pude terminar el elixir sin " +"esto, pero no está de más tener esto para estudiar. ¿Podrías llevarle esto a " +"la bruja? Creo que lo está esperando." -#: Source/monstdat.cpp:396 -msgctxt "monster" -msgid "Chaoshowler" -msgstr "Aullador del Caos" +#. 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 "" +"Creo que Ogden podría tener algunos hongos en el sótano de almacenamiento. " +"¿Por qué no le preguntas?" -#: Source/monstdat.cpp:397 -msgctxt "monster" -msgid "Doomgrin the Rotting" -msgstr "Doomgrin el Podrido" +#. 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 "" +"Si Adria no tiene uno de estos, puedes apostar que es algo raro. No puedo " +"ofrecerte más ayuda que esa, pero suena como ... ¡un hongo enorme, " +"gigantesco, hinchado y desmesurado! Bueno, buena caza, supongo." -#: Source/monstdat.cpp:398 -msgctxt "monster" -msgid "Madburner" -msgstr "Quemador Loco" +#. 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 "" +"Ogden mezcla un INFAME hongo negro, pero me enfermaré si bebo eso. Escucha, " +"escucha ... aquí está el secreto: ¡La moderación es la clave!" -#: Source/monstdat.cpp:399 -msgctxt "monster" -msgid "Bonesaw the Litch" -msgstr "Bonesaw el Litch" +#. 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 "" +"¿Qué tenemos aquí? Interesante, parece un libro de reactivos. Mantén los " +"ojos abiertos para un hongo negro. Debe ser bastante grande y fácil de " +"identificar. Si lo encuentras, tráemelo, ¿quieres?" -#: Source/monstdat.cpp:400 -msgctxt "monster" -msgid "Breakspine" -msgstr "Rompe Columna" +#. 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 "" +"Es un hongo negro grande el que necesito. Ahora corre y consíguelo para que " +"pueda usarlo en un brebaje especial en el que estoy trabajando." -#: Source/monstdat.cpp:401 -msgctxt "monster" -msgid "Devilskull Sharpbone" -msgstr "Hueso Afilado del Cráneo del Diablo" +#. 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 "" +"Sí, será perfecto para una infusión que estoy creando. Por cierto, el " +"sanador está buscando el cerebro de algún demonio para poder tratar a los " +"que han sido afectados por su ponzoña venenosa. Creo que tiene la intención " +"de hacer un elixir con eso. Si lo ayudas a encontrar lo que necesita, fíjate " +"si puedes conseguirme una muestra del elixir." -#: Source/monstdat.cpp:402 -msgctxt "monster" -msgid "Brokenstorm" -msgstr "Tormenta Rota" +#. 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 "" +"¿Por qué has traído eso aquí? No necesito el cerebro de un demonio en este " +"momento. Necesito algo del elixir en el que está trabajando el Curandero. " +"Necesita ese órgano grotesco que estás sosteniendo, y luego tráeme el " +"elixir. Simple cuando lo piensas, ¿no?" -#: Source/monstdat.cpp:403 -msgctxt "monster" -msgid "Stormbane" -msgstr "Stormbane" +#. 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 "" +"¿Qué? ¿Ahora me traes ese elixir del sanador? Pude terminar mi infusión sin " +"él. ¿Por qué no te lo quedas? ..." -#: Source/monstdat.cpp:404 -msgctxt "monster" -msgid "Oozedrool" -msgstr "Exuda Baba" +#. 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 "" +"No tengo hongos de ningún tamaño o color a la venta. ¿Qué tal algo un poco " +"más útil?" -#: Source/monstdat.cpp:405 -msgctxt "monster" -msgid "Goldblight of the Flame" -msgstr "Dorado de la Llama" +#. 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 "" +"Entonces, la leyenda del Mapa es real. ¡Incluso yo nunca creí realmente nada " +"de eso! Supongo que es hora de que te diga la verdad sobre quién soy, amigo " +"mío. Verás, no soy todo lo que parezco ...\n" +" \n" +"Mi verdadero nombre es Deckard Cain el Sabio, y soy el último descendiente " +"de una antigua Hermandad que se dedicó a conservar y salvaguardar los " +"secretos de un mal atemporal. Un mal que obviamente ahora ha sido " +"liberado ...\n" +" \n" +"El mal contra el que te mueves es el oscuro Señor del Terror, conocido por " +"los mortales como Diablo. Fue él quien fue encarcelado dentro del Laberinto " +"hace muchos siglos. El Mapa que tienes ahora fue creado hace siglos para " +"marcar el momento en que Diablo se levantaría nuevamente de su " +"encarcelamiento. Cuando las dos estrellas en ese mapa se alineen, Diablo " +"estará en el apogeo de su poder. Será casi invencible ...\n" +" \n" +"¡Ahora estás en una carrera contra el tiempo, amigo! Encuentra a Diablo y " +"destrúyelo antes de que las estrellas se alineen, ¡porque es posible que " +"nunca más tengamos la oportunidad de librar al mundo de su maldad!" -#: Source/monstdat.cpp:406 -msgctxt "monster" -msgid "Blackstorm" -msgstr "Tormenta Negra" +#. 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 "" +"¡Nuestro tiempo se está acabando! Siento que su poder oscuro se está " +"acumulando y solo tú puedes evitar que logre todo su poder." -#: Source/monstdat.cpp:407 -msgctxt "monster" -msgid "Plaguewrath" -msgstr "Ira de la Peste" +#. 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 "" +"Estoy seguro de que hiciste todo lo posible, pero me temo que ni siquiera tu " +"fuerza y voluntad serán suficientes. Diablo está ahora en el apogeo de su " +"poder terrenal, y necesitarás todo tu coraje y fuerza para derrotarlo. Que " +"la Luz te proteja y te guíe, amigo mío. Ayudaré en todo lo que pueda." -#: Source/monstdat.cpp:408 -msgctxt "monster" -msgid "The Flayer" -msgstr "El Desollador" +#. 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 "" +"Si la bruja no puede ayudarte y te sugiere que veas a Caín, ¿qué te hace " +"pensar que yo sabría algo? Parece que este es un asunto muy serio. Debes " +"darte prisa y ver al narrador como sugiere Adria." -#: Source/monstdat.cpp:409 -msgctxt "monster" -msgid "Bluehorn" -msgstr "Cuerno Azul" +#. TRANSLATORS: Quest dialog spoken by Pepin (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 "" +"No puedo hacer mucho de lo escrito en este mapa, pero tal vez Adria o Caín " +"podrían ayudarlo a descifrar a qué se refiere esto. \n" +" \n" +"Puedo ver que es un mapa de las estrellas en nuestro cielo, pero está más " +"allá de mis talentos." -#: Source/monstdat.cpp:410 -msgctxt "monster" -msgid "Warpfire Hellspawn" -msgstr "Engendro del Infierno del Fuego de la Disformidad" +#. 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 "" +"La mejor persona para preguntar sobre ese tipo de cosas sería nuestro " +"narrador. \n" +" \n" +"Caín está muy bien informado sobre los escritos antiguos, y ese es " +"fácilmente el pedazo de papel más antiguo que he visto en mi vida." -#: Source/monstdat.cpp:411 -msgctxt "monster" -msgid "Fangspeir" -msgstr "Colmillo" +#. 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 "" +"Nunca antes había visto un mapa de este tipo. ¿Dónde lo conseguiste? Aunque " +"no tengo ni idea de cómo leer esto, Caín o Adria pueden darte las respuestas " +"que buscas." -#: Source/monstdat.cpp:412 -msgctxt "monster" -msgid "Festerskull" -msgstr "Festerskull" +#. 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 "" +"Escúchame, acércate. No sé si sabes lo que yo sé, pero realmente tienes algo " +"aquí. Eso es un mapa." -#: Source/monstdat.cpp:413 -msgctxt "monster" -msgid "Lionskull the Bent" -msgstr "Lionskull el Doblado" +#. 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 "" +"¡Oh! Me temo que esto no augura nada bueno. Este mapa de las estrellas " +"presagia un gran desastre, pero sus secretos no son míos como para " +"contarlos. Ha llegado el momento de que tengas una conversación muy seria " +"con el Narrador ..." -#: Source/monstdat.cpp:414 -msgctxt "monster" -msgid "Blacktongue" -msgstr "Lengua Negra" +#. 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 "" +"He estado buscando un mapa, pero ciertamente no es así. Deberías mostrárselo " +"a Adria; probablemente ella pueda decirte lo que es. Diré una cosa; parece " +"viejo, y viejo por lo general significa valioso." -#: Source/monstdat.cpp:415 -msgctxt "monster" -msgid "Viletouch" -msgstr "Toque Vil" +#. 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 "" +"Por favooor, no herir. No matar. Mantener con vida y la próxima vez te " +"ayudaré." -#: Source/monstdat.cpp:416 -msgctxt "monster" -msgid "Viperflame" -msgstr "Vivora Llamenate" +#. 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 "" +"Estoy haciendo algo nuevo para ti. Nuevamente, no mates a Gharbad. Vive y " +"haz el bien. \n" +" \n" +"Toma esto como prueba de que cumplo la palabra ..." -#: Source/monstdat.cpp:417 -msgctxt "monster" -msgid "Fangskin" -msgstr "Fangskin" +#. 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 "" +"¡Nada aún! Casi termino. \n" +" \n" +"Muy poderoso, muy fuerte. ¡Vivir! ¡Vivir! \n" +" \n" +"¡Sin dolor y la promesa mantengo!" -#: Source/monstdat.cpp:418 -msgctxt "monster" -msgid "Witchfire the Unholy" -msgstr "Witchfire el Profano" +#. 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 "Esto es demasiado bueno para ti. ¡Muy poderoso! Tu quieres - tu tomas!" -#: Source/monstdat.cpp:419 -msgctxt "monster" -msgid "Blackskull" -msgstr "Calavera Negra" +#. 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 "" +"¿Qué? ¿Por qué estás aquí? Todas estas interrupciones son suficientes para " +"volverse loco. Toma, toma esto y déjame con mi trabajo. ¡No me molestes más!" -#: Source/monstdat.cpp:420 -msgctxt "monster" -msgid "Soulslash" -msgstr "Corte de Alma" +#. TRANSLATORS: Quest dialog spoken by Zhar the Mad (Hostile) +#: Source/textdat.cpp:307 +msgid "Arrrrgh! Your curiosity will be the death of you!!!" +msgstr "¡Arrrrgh! ¡Tu curiosidad te matará !!!" -#: Source/monstdat.cpp:421 -msgctxt "monster" -msgid "Windspawn" -msgstr "Engendro del Viento" +#. TRANSLATORS: Neutral dialog spoken by Cain +#: Source/textdat.cpp:308 +msgid "Hello, my friend. Stay awhile and listen..." +msgstr "Hola mi amigo. Quédate un rato y escucha ..." -#: Source/monstdat.cpp:422 -msgctxt "monster" -msgid "Lord of the Pit" -msgstr "Señor del Pozo" +#. 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 "" +"Mientras te adentres más profundo en el Laberinto, es posible que encuentres " +"tomos de gran conocimiento escondidos allí. \n" +" \n" +"Léelos atentamente porque pueden decirte cosas que incluso yo no puedo." -#: Source/monstdat.cpp:423 -msgctxt "monster" -msgid "Rustweaver" -msgstr "Tejedor de Óxido" +#. 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 "" +"Conozco muchos mitos y leyendas que pueden contener respuestas a preguntas " +"que puedan surgir en tus viajes al Laberinto. Si te encuentras con desafíos " +"y preguntas sobre las que buscas conocimiento, búscame y te diré lo que " +"pueda." -#: Source/monstdat.cpp:424 -msgctxt "monster" -msgid "Howlingire the Shade" -msgstr "Howlingire la Sombra" +#. 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 "" +"Griswold: un hombre de gran acción y gran coraje. Apuesto a que nunca te " +"contó de la vez que entró en el Laberinto para salvar a Wirt, ¿verdad? Él " +"conocía los peligros que se encuentran allí, pero, de nuevo, tu también. Es " +"un hábil artesano, y si dice poder ayudarlo de alguna manera, puede contar " +"con su honestidad y habilidad." -#: Source/monstdat.cpp:425 -msgctxt "monster" -msgid "Doomcloud" -msgstr "Nube de la Fatalidad" +#. 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 "" +"Ogden es propietario y dirige la Posada y Taberna del Sol Naciente desde " +"hace casi cuatro años. Lo compró unos pocos meses antes de que todo se fuera " +"al diablo. Él y su esposa Garda no tienen dinero para irse, ya que " +"invirtieron todo lo que tenían para ganarse la vida aquí. Es un buen hombre " +"con un profundo sentido de responsabilidad." -#: Source/monstdat.cpp:426 -msgctxt "monster" -msgid "Bloodmoon Soulfire" -msgstr "Fuego del Alma de la Luna de Sangre" +#. 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 "" +"Pobre Farnham. Es un inquietante recordatorio de la condenada asamblea que " +"entró en la Catedral con Lazarus en ese día oscuro. Escapó con vida, pero su " +"coraje y gran parte de su cordura quedaron en algún pozo oscuro. Hoy en día " +"encuentra consuelo solo en el fondo de su jarra, pero hay fragmentos " +"ocasionales de verdad enterrados en sus constantes divagaciones." -#: Source/monstdat.cpp:427 -msgctxt "monster" -msgid "Witchmoon" -msgstr "Luna de la Bruja" +#. 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 "" +"La bruja, Adria, es una anomalía aquí en Tristram. Llegó poco después de que " +"la Catedral fuera profanada mientras la mayoría de los demás huían. Ella " +"hizo construir una pequeña cabaña en las afueras del pueblo, aparentemente " +"de la noche a la mañana, y tiene acceso a muchos artefactos extraños y " +"arcanos y tomos de conocimiento que ni siquiera yo había visto antes." -#: Source/monstdat.cpp:428 -msgctxt "monster" -msgid "Gorefeast" -msgstr "Fiesta de Sangre" +#. 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 "" +"La historia de Wirt es aterradora y trágica. Fue arrancado de los brazos de " +"su madre y arrastrado al laberinto por los pequeños e inmundos demonios que " +"empuñan malvadas lanzas. Se llevaron a muchos otros niños ese día, incluido " +"el hijo del Rey Leoric. Los Caballeros del palacio bajaron, pero nunca " +"regresaron. El herrero encontró al niño, pero solo después de que las " +"horribles bestias comenzaran a torturarlo para sus sádicos placeres." -#: Source/monstdat.cpp:429 -msgctxt "monster" -msgid "Graywar the Slayer" -msgstr "Graywar el Asesino" +#. 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 "" +"Ah, Pepin. Lo considero un verdadero amigo, quizás el más cercano que tengo " +"aquí. A veces está un poco confundido, pero nunca ha existido un alma más " +"cariñosa o considerada. Sus conocimientos y habilidades son igualados por " +"pocos, y su puerta siempre está abierta." -#: Source/monstdat.cpp:430 -msgctxt "monster" -msgid "Dreadjudge" -msgstr "Juez Aterrador" +#. 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 "" +"Gillian es una buena mujer. Muy querida por su buen humor y su risa rápida, " +"ocupa un lugar especial en mi corazón. Se queda en la taberna para ayudar a " +"su abuela anciana, que está demasiado enferma para viajar. A veces temo por " +"su seguridad, pero sé que cualquier hombre de la aldea preferiría morir " +"antes que verla lastimada." -#: Source/monstdat.cpp:431 -msgctxt "monster" -msgid "Stareye the Witch" -msgstr "Stareye la Bruja" +#. TRANSLATORS: Neutral dialog spoken by Ogden +#: Source/textdat.cpp:327 +msgid "Greetings, good master. Welcome to the Tavern of the Rising Sun!" +msgstr "Saludos, buen maestro. ¡Bienvenido a la Taberna del Sol Naciente!" -#: Source/monstdat.cpp:432 -msgctxt "monster" -msgid "Steelskull the Hunter" -msgstr "Steelskull el Cazador" +#. 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 "" +"Muchos aventureros han honrado las mesas de mi taberna, y diez veces se han " +"contado tantas historias como bebido cerveza. Lo único que escuché de todos " +"ellos fue el estar de acuerdo con este viejo axioma. Quizás te ayude. Puedes " +"cortar la carne, pero debes triturar el hueso." -#: Source/monstdat.cpp:433 -msgctxt "monster" -msgid "Sir Gorash" -msgstr "Sir Gorash" +#. 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 "" +"Griswold, el herrero, está muy bien informado sobre armas y armaduras. Si " +"alguna vez necesitas trabajar en tu equipo, definitivamente es el hombre " +"para ver." -#: Source/monstdat.cpp:434 -msgctxt "monster" -msgid "The Vizier" -msgstr "El Visir" +#. 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 "" +"Farnham pasa demasiado tiempo aquí, ahogando sus penas en cerveza barata. Lo " +"echaría, pero sufrió tanto durante su tiempo en el Laberinto." -#: Source/monstdat.cpp:435 -msgctxt "monster" -msgid "Zamphir" -msgstr "Zamphir" +#. 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 "" +"Adria es sabia para su edad, pero debo admitirlo: Ella me asusta un poco. \n" +" \n" +"Bueno, no importa. Si alguna vez necesitas intercambiar objetos de " +"hechicería, ella mantiene una choza extrañamente bien abastecida al otro " +"lado del río." -#: Source/monstdat.cpp:436 -msgctxt "monster" -msgid "Bloodlust" -msgstr "Bloodlust" +#. 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 "" +"Si quieres saber más sobre la historia de nuestro pueblo, el narrador Cain " +"sabe bastante sobre el pasado." -#: Source/monstdat.cpp:437 -msgctxt "monster" -msgid "Webwidow" -msgstr "Webwidow" +#. 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 "" +"Wirt es un canalla y un pequeño sinvergüenza. Siempre se estaba metiendo en " +"problemas, y no es de extrañar lo que le sucedió. \n" +" \n" +"Probablemente se fue a hacer el tonto por ahí donde no debería haber estado. " +"Lo siento por el chico, pero no acepto la compañía que tiene." -#: Source/monstdat.cpp:438 -msgctxt "monster" -msgid "Fleshdancer" -msgstr "Bailarín de la Carne" +#. 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 "" +"Pepin es un buen hombre y, sin duda, el más generoso del pueblo. Siempre " +"está atendiendo las necesidades de los demás, pero los problemas de una u " +"otra clase parecen seguirlo dondequiera que vaya ..." -#: Source/monstdat.cpp:439 -msgctxt "monster" -msgid "Grimspike" -msgstr "Pico Sombrío" +#. 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 "" +"Gillian, mi Camarera? Si no fuera por su sentido del deber hacia su abuela, " +"habría huido de aquí hace mucho tiempo. \n" +" \n" +"Dios sabe que le rogué que se fuera, diciéndole que cuidaría de la anciana, " +"pero ella es demasiado dulce y cariñosa para haberlo hecho." -#. TRANSLATORS: Unique Monster Block end -#: Source/monstdat.cpp:441 -msgctxt "monster" -msgid "Doomlock" -msgstr "Cerradura de la Condenación" +#. TRANSLATORS: Neutral dialog spoken by Pepin +#: Source/textdat.cpp:345 +msgid "What ails you, my friend?" +msgstr "¿Qué te aflige, amigo mío?" -#: Source/monster.cpp:2961 -msgid "Animal" -msgstr "Animal" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"He hecho un descubrimiento muy interesante. A diferencia de nosotros, las " +"criaturas del Laberinto pueden curarse a sí mismas sin la ayuda de pociones " +"o magia. Si hieres a uno de los monstruos, asegúrate de que esté muerto o " +"podría regenerarse." -#: Source/monster.cpp:2963 -msgid "Demon" -msgstr "Demonio" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Antes de que fuera tomada por, bueno, lo que sea que esté al acecho debajo, " +"la Catedral era un lugar de gran aprendizaje. Allí se pueden encontrar " +"muchos libros. Si encuentra alguno, debe leerlos todos, ya que algunos " +"pueden tener secretos sobre el funcionamiento del Laberinto." -#: Source/monster.cpp:2965 -msgid "Undead" -msgstr "Muerto viviente" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Griswold sabe tanto sobre el arte de la guerra como yo sobre el arte de " +"curar. Es un comerciante astuto, pero su trabajo es insuperable. Oh, supongo " +"que puede deberse a que es el único herrero que queda aquí." -#: Source/monster.cpp:4202 -msgid "Type: {:s} Kills: {:d}" -msgstr "Tipo: {:s} Muertes: {:d}" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Caín es un verdadero amigo y un sabio sensato. Mantiene una vasta biblioteca " +"y tiene una habilidad innata para discernir la verdadera naturaleza de " +"muchas cosas. Si alguna vez tienes alguna pregunta, él es la persona a la " +"que debes dirigirse." -#: Source/monster.cpp:4204 -msgid "Total kills: {:d}" -msgstr "Muertes totales: {:d}" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Incluso mis habilidades no han podido curar completamente a Farnham. Oh, he " +"podido reparar su cuerpo, pero su mente y su espíritu están más allá de " +"cualquier cosa que pueda hacer." -#: Source/monster.cpp:4236 -msgid "Hit Points: {:d}-{:d}" -msgstr "Puntos de Golpe: {:d}- {:d}" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Si bien utilizo algunas formas limitadas de magia para crear las pociones y " +"elixires que guardo aquí, Adria es una verdadera hechicera. Parece que nunca " +"duerme y siempre tiene acceso a muchos libros y artefactos místicos. Creo " +"que su cabaña puede ser mucho más de lo que parece ser, pero parece que " +"nunca puedo entrar al lugar." -#: Source/monster.cpp:4241 -msgid "No magic resistance" -msgstr "Sin resistencia mágica" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Pobre Wirt. Hice todo lo posible por el niño, pero sé que desprecia ese " +"broche de madera que me vi obligado a sujetar a su pierna. Sus heridas eran " +"horribles. Nadie, y especialmente un niño tan pequeño, debería tener que " +"sufrir como él lo hizo." -#: Source/monster.cpp:4244 -msgid "Resists:" -msgstr "Resiste:" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Realmente no entiendo por qué Ogden se queda aquí en Tristram. Sufre de una " +"leve condición nerviosa, pero es un hombre inteligente y trabajador que le " +"iría muy bien donde quiera que fuera. Supongo que puede ser el miedo a los " +"muchos asesinatos que ocurren en el campo circundante, o quizás los deseos " +"de su esposa lo que lo mantiene a él y a su familia donde están." -#: Source/monster.cpp:4246 Source/monster.cpp:4256 -msgid " Magic" -msgstr " Magia" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"La camarera de Ogden es una chica dulce. Su abuela está bastante enferma y " +"sufre delirios \n" +" \n" +"Ella afirma que son visiones, pero no tengo pruebas de eso de una forma u " +"otra." -#: Source/monster.cpp:4248 Source/monster.cpp:4258 -msgid " Fire" -msgstr " Fuego" +#. TRANSLATORS: Neutral dialog spoken by Gillian +#: Source/textdat.cpp:364 +msgid "Good day! How may I serve you?" +msgstr "¡Buenos días! ¿Cómo puedo servirte?" -#: Source/monster.cpp:4250 Source/monster.cpp:4260 -msgid " Lightning" -msgstr " Rayo" +#. 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 "" +"Mi abuela soñó que vendrías a hablar conmigo. Ella tiene visiones, sabes, y " +"puedes ver el futuro." -#: Source/monster.cpp:4254 -msgid "Immune:" -msgstr "Inmune:" +#. 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 "" +"¡La mujer de las afueras del pueblo es una bruja! Parece bastante agradable " +"y su nombre, Adria, es muy agradable al oído, pero le temo. \n" +" \n" +"Se necesitaría alguien bastante valiente, como tú, para ver lo que está " +"haciendo ahí fuera." -#: Source/monster.cpp:4271 -msgid "Type: {:s}" -msgstr "Tipo: {:s}" +#. 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 "" +"Nuestro herrero es un motivo de orgullo para la gente de Tristram. No solo " +"es un maestro artesano que ha ganado muchos concursos dentro de su gremio, " +"sino que recibió elogios de nuestro Rey Leoric en persona, que su alma " +"descanse en paz. Griswold también es un gran héroe; pregúntale a Caín." -#: Source/monster.cpp:4276 Source/monster.cpp:4282 -msgid "No resistances" -msgstr "Sin resistencias" +#. 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 "" +"Cain ha sido el narrador de Tristram desde que tengo uso de razón. Él sabe " +"mucho y puede decirte casi cualquier cosa sobre casi todo." -#: Source/monster.cpp:4277 Source/monster.cpp:4286 -msgid "No Immunities" -msgstr "Sin Inmunidades" +#. 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 "" +"Farnham es un borracho que llena su barriga de cerveza y los oídos de los " +"demás de tonterías. \n" +" \n" +"Sé que tanto Pepin como Ogden sienten simpatía por él, pero me frustra tanto " +"verlo caer cada vez más en un aturdido estupor cada noche." -#: Source/monster.cpp:4280 -msgid "Some Magic Resistances" -msgstr "Algunas Resistencias Mágicas" +#. 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 "" +"Pepin salvó la vida de mi abuela y sé que nunca podré pagarle por eso. Su " +"habilidad para curar cualquier enfermedad es más poderosa que la espada más " +"potente y más misteriosa que cualquier hechizo que puedas nombrar. Si alguna " +"vez necesitas curarte, Pepin puede ayudarte." -#: Source/monster.cpp:4284 -msgid "Some Magic Immunities" -msgstr "Algunas Inmunidades Mágicas" +#. 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 "" +"Crecí con la madre de Wirt, Canace. Aunque solo se sintió levemente herida " +"cuando esas horribles criaturas se lo robaron, nunca se recuperó. Creo que " +"murió con el corazón roto. Wirt se ha convertido en un joven mezquino que " +"solo busca sacar provecho del sudor de los demás. Sé que sufrió y ha visto " +"horrores que ni siquiera puedo imaginar, pero algo de esa oscuridad aún se " +"cierne sobre él." -#: Source/mpq/mpq_writer.cpp:171 -msgid "Failed to open archive for writing." -msgstr "No se pudo abrir el archivo para escribir." +#. 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 "" +"Ogden y su esposa nos han llevado a mi abuela y a mí a su casa e incluso me " +"han dejado ganar algunas piezas de oro trabajando en la posada. Les debo " +"mucho y espero algún día dejar este lugar y ayudarlos a comenzar un gran " +"hotel en el este." -#: Source/msg.cpp:777 -msgid "Trying to drop a floor item?" -msgstr "¿Está intentando lanzar un objeto al suelo?" +#. TRANSLATORS: Neutral dialog spoken by Griswold +#: Source/textdat.cpp:381 +msgid "Well, what can I do for ya?" +msgstr "Bueno, ¿qué puedo hacer por ti?" -#: Source/msg.cpp:1459 Source/msg.cpp:1500 Source/msg.cpp:1650 Source/msg.cpp:1689 -msgid "{:s} has cast an invalid spell." -msgstr "{:s} ha lanzado un hechizo inválido." +#. 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 "" +"Si estás buscando un buen arma, déjame mostrarte esto. Toma tu arma básica " +"contundente, como una maza. Funciona como un encanto contra la mayoría de " +"esos horrores eternos que hay allí, ¡y no hay nada mejor para destrozar " +"pequeños esqueletos delgados!" -#: Source/msg.cpp:1463 Source/msg.cpp:1504 Source/msg.cpp:1539 Source/msg.cpp:1654 -#: Source/msg.cpp:1693 Source/msg.cpp:1728 Source/msg.cpp:1763 -msgid "{:s} has cast an illegal spell." -msgstr "{:s} ha lanzado un hechizo ilegal." +#. 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 "" +"¿El hacha? Sí, esa es un buen arma, equilibrada contra cualquier enemigo. " +"Mira cómo corta el aire y luego imagina una bonita y gorda cabeza de demonio " +"en su camino. Sin embargo, ten en cuenta que el giro es lento, ¡pero habla " +"de asestar un golpe fuerte!" -#: Source/msg.cpp:2235 Source/multi.cpp:780 Source/multi.cpp:831 -msgid "Player '{:s}' (level {:d}) just joined the game" -msgstr "Jugador ' {:s}' (nivel {:d}) acaba de unirse" +#. 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 "" +"Mira ese filo, ese equilibrio. Una espada en la mano derecha, y contra el " +"enemigo correcto, es el amo de todas las armas. Su hoja afilada encuentra " +"poco para cortar o perforar en los muertos vivientes, pero contra un enemigo " +"vivo que respira, ¡una espada cortará su carne mucho mejor!" -#: Source/msg.cpp:2581 -msgid "The game ended" -msgstr "El juego terminó" +#. 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 "" +"Tus armas y armaduras mostrarán los signos de tus luchas contra la " +"Oscuridad. Si me las traes, con un poco de trabajo y una fragua en caliente, " +"puedo restaurarlas a su mejor forma de lucha." -#: Source/msg.cpp:2587 -msgid "Unable to get level data" -msgstr "No se pueden obtener datos del nivel" +#. 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 "" +"Mientras prácticamente tengo que pasar de contrabando los metales y " +"herramientas que necesito de las caravanas que bordean los límites de " +"nuestro maldito pueblo, esa bruja, Adria, siempre parece conseguir lo que " +"necesita. Si supiera lo más mínimo sobre cómo aprovechar la magia como ella " +"lo hizo, podría hacer algunas cosas realmente increíbles." -#: Source/multi.cpp:254 -msgid "Player '{:s}' just left the game" -msgstr "El jugador ' {:s}' acaba de salir del juego" +#. 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 "" +"Gillian es una buena chica. Es una pena que su abuela tenga tan mala salud o " +"haría los arreglos para sacarlos a ambos de aquí en una de las caravanas " +"comerciales." -#: Source/multi.cpp:257 -msgid "Player '{:s}' killed Diablo and left the game!" -msgstr "¡El jugador ' {:s}' mató a Diablo y abandonó el juego!" +#. 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 "" +"A veces pienso que Caín habla demasiado, pero supongo que esa es su vocación " +"en la vida. ¡Si pudiera doblar el acero tan bien como puede doblar tu oreja, " +"podría hacer una armadura de placas lo suficientemente buena para un " +"Emperador!" -#: Source/multi.cpp:261 -msgid "Player '{:s}' dropped due to timeout" -msgstr "El jugador ' {:s}' desconectado debido al tiempo de espera" +#. 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 "" +"Estaba con Farnham esa noche que Lazarus nos condujo al Laberinto. Nunca " +"volví a ver al Arzobispo y es posible que no hubiera sobrevivido si Farnham " +"no hubiera estado a mi lado. Me temo que el ataque dejó su alma tan lisiada " +"como, bueno, otro dejó mi pierna. No puedo pelear esta batalla por él ahora, " +"pero lo haría si pudiera." -#: Source/multi.cpp:833 -msgid "Player '{:s}' (level {:d}) is already in the game" -msgstr "El jugador ' {:s}' (nivel {:d}) ya está en el juego" +#. 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 "" +"Un buen hombre que antepone las necesidades de los demás a las suyas. No " +"encontraras a nadie en Tristram, ni en ningún otro lugar, que tenga algo " +"malo que decir sobre el sanador." -#. TRANSLATORS: Shrine Name Block -#: Source/objects.cpp:121 -msgid "Mysterious" -msgstr "Misterioso" +#. 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 "" +"Ese chico se va a meter en serios problemas ... o creo que debería decirlo, " +"de nuevo. He tratado de interesarle en trabajar aquí y aprender un oficio " +"honesto, pero prefiere las altas ganancias de comerciar con bienes de origen " +"dudoso. No puedo reprocharle eso después de lo que le sucedió, pero desearía " +"que al menos tuviera cuidado." -#: Source/objects.cpp:122 -msgid "Hidden" -msgstr "Oculto" +#. 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 "" +"El Posadero tiene pocos negocios y ninguna forma real de obtener ganancias. " +"Se las arregla para llegar a fin de mes proporcionando comida y alojamiento " +"a aquellos que, de vez en cuando, deambulan por la aldea, pero es tan " +"probable que se escapen por noche como que le paguen. Si no fuera por las " +"reservas de cereales y carnes secas que guarda en su bodega, la mayoría de " +"nosotros habríamos muerto de hambre durante ese primer año en que todo el " +"campo fue invadido por demonios." -#: Source/objects.cpp:123 -msgid "Gloomy" -msgstr "Sombrío" +#. TRANSLATORS: Neutral dialog spoken by Farnham +#: Source/textdat.cpp:404 +msgid "Can't a fella drink in peace?" +msgstr "¿No puede un colega beber en paz?" -#: Source/objects.cpp:125 Source/objects.cpp:132 -msgid "Magical" -msgstr "Mágico" +#. 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 "" +"¿La chica que trae las bebidas? Oh, sí, qué hermosa dama. Muy agradable " +"también." -#: Source/objects.cpp:126 -msgid "Stone" -msgstr "Piedra" +#. 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 "" +"¿Por qué esa vieja bruja no hace algo para variar? Claro, claro, tiene sus " +"cosas, pero escúchame ... es antinatural. Nunca la he visto comer o beber, y " +"no puedes confiar en alguien que no bebe al menos un poco." -#: Source/objects.cpp:127 -msgid "Religious" -msgstr "Religioso" +#. 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 "" +"Caín no es lo que dice ser. Claro, claro, él cuenta una buena historia ... " +"algunas de ellas son realmente aterradoras o divertidas ... pero creo que él " +"sabe más de lo que sabe." -#: Source/objects.cpp:128 -msgid "Enchanted" -msgstr "Encantada" +#. 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 "" +"Griswold? El bueno de Griswold. ¡Lo quiero como a un hermano! Luchamos " +"juntos, ya sabes, cuando ... nosotros ... Lazarus ... Lazarus ... Lazarus!!!" -#: Source/objects.cpp:129 -msgid "Thaumaturgic" -msgstr "Taumatúrgico" +#. 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 "" +"Jejeje, me gusta Pepin. Realmente lo intenta, ya sabes. Escucha, debes " +"asegurarte de conocerlo. Buen tipo como la gente que siempre quiere ayudar. " +"Oye, supongo que sería un poco propio de ti, ¿eh héroe? Yo también fui un " +"héroe ..." -#: Source/objects.cpp:130 -msgid "Fascinating" -msgstr "Fascinante" +#. 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 "" +"Wirt es un niño que tiene incluso más problemas que yo, y yo sé todo sobre " +"los problemas. Escucha, ese chico es afable, pero ha estado allí, ¿sabes? " +"¡Perdió una pierna! Tiene que caminar sobre un trozo de madera. Es tan " +"triste, tan triste ..." -#: Source/objects.cpp:131 -msgid "Cryptic" -msgstr "Críptico" +#. 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 "" +"Ogden es el mejor hombre del pueblo. No creo que yo le guste mucho a su " +"esposa, pero mientras ella siga golpeando barriles, supongo que estará bien. " +"Parece que he pasado más tiempo con Ogden que la mayoría, pero es tan bueno " +"conmigo ..." -#: Source/objects.cpp:133 -msgid "Eldritch" -msgstr "Espeluznante" +#. 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 "" +"Quiero decirte algo, porque sé todo sobre estas cosas. Es mi especialidad. " +"Esto de aquí es lo mejor ... ¡looo mejor! Esa otra cerveza no es buena ya " +"que esos estúpidos perros ..." -#: Source/objects.cpp:134 -msgid "Eerie" -msgstr "Inquietante" +#. 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 "" +"Nunca nadie me esc... escucha. En algún lugar, no estoy muy seguro, pero en " +"algún lugar debajo de la iglesia hay un gran montón de oro. Reluciendo y " +"brillando y esperando a que alguien lo consiga." -#: Source/objects.cpp:135 -msgid "Divine" -msgstr "Divino" +#. 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 "" +"Sé que tienes tus propias ideas, y sé que no vas a creer esto, pero ese arma " +"que tienes allí, ¡simplemente no es buena contra esos grandes brutos! Oh, no " +"me importa lo que diga Griswold, no pueden hacer nada como solían hacer en " +"los viejos tiempos ..." -#: Source/objects.cpp:137 -msgid "Sacred" -msgstr "Sagrado" +#. 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 "" +"Si yo fuera tú ... y no lo soy ... pero si lo fuera, vendería todas esas " +"cosas que tienes y me largaría de aquí. Ese chico de ahí fuera ... Siempre " +"tiene algo bueno, pero tienes que darle algo de oro o ni siquiera te " +"mostrará lo que tiene." -#: Source/objects.cpp:138 -msgid "Spiritual" -msgstr "Espiritual" +#. TRANSLATORS: Neutral dialog spoken by Adria +#: Source/textdat.cpp:427 +msgid "I sense a soul in search of answers..." +msgstr "Siento un alma en busca de respuestas ..." -#: Source/objects.cpp:139 -msgid "Spooky" -msgstr "Escalofriante" +#. 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 "" +"La sabiduría se gana, no se da. Si descubres un tomo de conocimiento, devora " +"sus palabras. Si ya tienes conocimiento de los misterios arcanos escritos en " +"un libro, recuerda, ese nivel de maestría siempre puede aumentar." -#: Source/objects.cpp:140 -msgid "Abandoned" -msgstr "Abandonado" +#. 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 "" +"El mayor poder es a menudo el de menor duración. Puede encontrar antiguas " +"palabras de poder escritas en rollos de pergamino. La fuerza de estos " +"pergaminos radica en la capacidad del aprendiz o del adepto para lanzarlos " +"con igual habilidad. Su debilidad es que primero se deben leer en voz alta y " +"nunca se pueden tener listos en la mente. Debes saber también que estos " +"pergaminos se pueden leer una sola vez, así que utilízalos con cuidado." -#: Source/objects.cpp:141 -msgid "Creepy" -msgstr "Siniestro" +#. 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 "" +"Aunque el calor del sol es inconmensurable, la mera llama de una vela es de " +"mayor peligro. Ninguna energía, por grande que sea, puede usarse sin el " +"enfoque adecuado. Para muchos hechizos, los Bastones encantados pueden " +"cargarse muchas veces con energías mágicas. Tengo la capacidad de restaurar " +"su poder, pero sé que nada se hace sin un precio." -#: Source/objects.cpp:142 -msgid "Quiet" -msgstr "Tranquilo" +#. 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 "" +"La suma de nuestro conocimiento está en la suma de su gente. Si encuentras " +"un libro o un pergamino que no puedas descifrar, no dude en traérmelo. Si " +"puedo encontrarle sentido, compartiré lo que encuentre." -#: Source/objects.cpp:143 -msgid "Secluded" -msgstr "Aislado" +#. 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 "" +"Para un hombre que solo conoce el Hierro, no hay mayor magia que el Acero. " +"El herrero Griswold es más hechicero de lo que él cree. Su habilidad para " +"fusionar el fuego y el metal es inigualable en esta tierra." -#: Source/objects.cpp:144 -msgid "Ornate" -msgstr "Decorado" +#. 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 "" +"La corrupción tiene la fuerza del engaño, pero la inocencia tiene el poder " +"de la pureza. La joven Gillian tiene un corazón puro, anteponiendo las " +"necesidades de su matriarca sobre las suyas. Ella me teme, pero es solo " +"porque no me comprende." -#: Source/objects.cpp:145 -msgid "Glimmering" -msgstr "Resplandeciente" +#. 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 "" +"Un cofre abierto en la oscuridad no guarda mayor tesoro que cuando se abre a " +"la luz. El narrador Caín es un enigma, pero solo para quienes no miran. Su " +"conocimiento de lo que hay debajo de la catedral es mucho mayor de lo que él " +"mismo se permite darse cuenta." -#: Source/objects.cpp:146 -msgid "Tainted" -msgstr "Contaminado" +#. 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 "" +"Cuanto más alto pongas tu fe en un hombre, más debe caer. Farnham ha perdido " +"su alma, pero no ante ningún demonio. Se perdió cuando vio a la gente del " +"pueblo traicionados por el Arzobispo Lazarus. Él tiene conocimientos para " +"cosechar, pero debes separar los hechos de las fantasías." -#: Source/objects.cpp:147 -msgid "Oily" -msgstr "Aceitoso" +#. 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 "" +"La mano, el corazón y la mente pueden realizar milagros cuando están en " +"perfecta armonía. El sanador Pepin ve el interior del cuerpo de una manera " +"que ni siquiera yo puedo. Su capacidad para restaurar a los enfermos y " +"heridos se ve magnificada por su comprensión en la creación de elixires y " +"pociones. Es un aliado tan grande como tú en Tristram." -#: Source/objects.cpp:148 -msgid "Glowing" -msgstr "Brillante" +#. 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 "" +"Hay mucho sobre el futuro que no podemos ver, pero cuando llegue serán los " +"niños quienes lo manejen. El niño Wirt tiene algo oscuro en el alma, pero no " +"representa una amenaza para el pueblo o su gente. Sus tratos secretos con " +"los rufianes y los inconfesables gremios de los pueblos cercanos le permiten " +"acceder a muchos dispositivos que no se pueden encontrar fácilmente en " +"Tristram. Si bien sus métodos pueden ser reprochables, Wirt puede ayudarte " +"en tu batalla contra la Oscuridad que nos invade." -#: Source/objects.cpp:149 -msgid "Mendicant's" -msgstr "Del Mendicante" +#. 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 "" +"Las paredes de tierra y el dosel de paja no crean una casa. El posadero " +"Ogden tiene un mayor propósito en este pueblo de lo que muchos creen. " +"Proporciona refugio para Gillian y su matriarca, mantiene la vida que le " +"dejó Farnham y proporciona un ancla para todos los que quedan en el pueblo a " +"lo que Tristram fue una vez. Su taberna, y los placeres sencillos que aún se " +"pueden encontrar allí, dan una idea de una vida que la gente de aquí " +"recuerda. Es ese recuerdo el que sigue alimentando sus esperanzas de éxito." -#: Source/objects.cpp:150 -msgid "Sparkling" -msgstr "Centelleante" +#. TRANSLATORS: Neutral dialog spoken by Wirt +#: Source/textdat.cpp:450 +msgid "Pssst... over here..." +msgstr "Pssst ... por aquí ..." -#: Source/objects.cpp:152 -msgid "Shimmering" -msgstr "Reluciente" +#. 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 "" +"No todo el mundo en Tristram tiene un uso, o un mercado, para todo lo que " +"encontrará en el laberinto. Ni siquiera yo, por más difícil que sea de " +"creer. \n" +" \n" +"A veces, solo tú podrás encontrar un propósito para algunas cosas." -#: Source/objects.cpp:153 -msgid "Solar" -msgstr "Solar" +#. 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 "" +"No te fíes de todo lo que dice el borracho. Demasiadas cervezas han empañado " +"su visión y su sentido común." -#. TRANSLATORS: Shrine Name Block end -#: Source/objects.cpp:155 -msgid "Murphy's" -msgstr "De Murphy" +#. 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 "" +"Por si no lo has notado, no compro nada de Tristram. Soy un importador de " +"productos de calidad. Si quieres vender basura, tendrás que ver a Griswold, " +"Pepin o esa bruja, Adria. Estoy seguro de que te sacarán de las manos todo " +"lo que les puedas llevar ..." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:285 -msgid "The Great Conflict" -msgstr "El Gran Conflicto" +#. 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 "" +"Supongo que le debo la vida al herrero, lo que queda de ella. Claro, " +"Griswold me ofreció un aprendizaje en la herrería, y es un tipo bastante " +"agradable, pero nunca obtendré suficiente dinero para ... bueno, digamos que " +"tengo planes definidos que requieren una gran cantidad de oro." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:286 -msgid "The Wages of Sin are War" -msgstr "La Paga del Pecado es la Guerra" +#. 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 "" +"Si tuviera unos años más, la colmaría con todas las riquezas que pudiera " +"reunir, y déjame asegurarte que puedo conseguir algunas cosas muy buenas. " +"Gillian es una hermosa chica que debería salir de Tristram tan pronto como " +"sea seguro. Hmmm ... tal vez me la lleve conmigo cuando me vaya ..." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:287 -msgid "The Tale of the Horadrim" -msgstr "El Cuento de los Horadrim" +#. 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 "" +"Caín sabe demasiado. Me asusta de muerte, incluso más que esa mujer al otro " +"lado del río. No deja de contarme lo afortunado que soy de estar vivo y cómo " +"mi historia está predicha en la leyenda. Creo que está loco." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:288 -msgid "The Dark Exile" -msgstr "El Exilio Oscuro" +#. 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 "" +"Farnham: ahora hay un hombre con problemas graves y sé todo acerca de lo " +"graves que pueden ser los problemas. Confió demasiado en la integridad de un " +"hombre, y Lazarus lo llevó a las mismas fauces de la muerte. Oh, sé lo que " +"es ahí abajo, así que ni siquiera empieces a contarme tus planes para " +"destruir el mal que habita en ese Laberinto. Cuida tus piernas ..." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:289 -msgid "The Sin War" -msgstr "La Guerra del Pecado" +#. 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 "" +"Siempre que no necesites volver a colocar nada, el viejo Pepin es tan bueno " +"como pocos. \n" +" \n" +"Si hubiera tenido algunas de esas pociones que él prepara, aún podría tener " +"mi pierna ..." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:290 -msgid "The Binding of the Three" -msgstr "La Unión de los Tres" +#. 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 "" +"Adria realmente me molesta. Claro, Cain es espeluznante en lo que puede " +"contarte sobre el pasado, pero esa bruja puede ver tu pasado. Ella siempre " +"tiene alguna forma de conseguir lo que necesita también. Adria consigue más " +"mercadería de la que he visto pasar por las puertas del Bazar del Rey " +"durante el Festival Mayor." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:291 -msgid "The Realms Beyond" -msgstr "Los Reinos del Más Allá" +#. 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 "" +"Ogden es un tonto por quedarse aquí. Podría sacarlo del pueblo por un precio " +"muy razonable, pero él insiste en intentar salir adelante con esa estúpida " +"taberna. Supongo que al menos le da a Gillian un lugar para trabajar, y su " +"esposa Garda hace un excelente Pastel de cordero ..." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:292 -msgid "Tale of the Three" -msgstr "Cuento de los Tres" +#. TRANSLATORS: Quest text spoken aloud from a book by player +#: Source/textdat.cpp:471 Source/textdat.cpp:479 Source/textdat.cpp:487 +#: Source/textdat.cpp:517 Source/textdat.cpp:525 +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 "" +"Más allá del Salón de los Héroes se encuentra la Cámara de Hueso. La muerte " +"eterna aguarda a cualquiera que busque robar los tesoros guardados dentro de " +"esta habitación. Así habla el Señor del Terror, y así está escrito." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:293 -msgid "The Black King" -msgstr "El Rey Negro" +#. 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:519 Source/textdat.cpp:527 +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 "" +"... y así, encerrado más allá de la Puerta de la Sangre y más allá del Salón " +"del Fuego, Valor espera a que el Héroe de la Luz despierte ..." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:294 -msgid "Journal: The Ensorcellment" -msgstr "Diario: El Hechizado" +#. 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:521 Source/textdat.cpp:529 +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 "" +"Puedo ver lo que tú no ves.\n" +"Visión lechosa, luego los ojos se pudren.\n" +"Cuando te vuelvas se habrán ido,\n" +"Susurrando su canción oculta.\n" +"Entonces ves lo que no puede ser,\n" +"Las sombras se mueven donde debería estar la luz.\n" +"Fuera de la oscuridad, fuera de la mente,\n" +"Arrojados a los Pasillos del Ciego." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:295 -msgid "Journal: The Meeting" -msgstr "Diario: El Encuentro" +#. TRANSLATORS: Quest text spoken aloud from a book by player +#: Source/textdat.cpp:477 Source/textdat.cpp:485 Source/textdat.cpp:493 +#: Source/textdat.cpp:523 Source/textdat.cpp:531 +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 "" +"Las armerías del Infierno son el hogar del Señor de la Guerra de la Sangre. " +"A su paso yacían los cuerpos mutilados de miles. Tanto los ángeles como los " +"hombres han sido cortados para cumplir con sus sacrificios interminables a " +"los Oscuros que gritan por una cosa: sangre." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:296 -msgid "Journal: The Tirade" -msgstr "Diario: La Diatriba" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:497 +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 "" +"Presta atención y da testimonio de las verdades que se encuentran aquí, " +"porque son el último legado de los Horadrim. Hay una guerra que continúa " +"incluso ahora, más allá de los campos que conocemos, entre los reinos " +"utópicos de los Altos Cielos y los caóticos pozos de los Infiernos " +"Ardientes. Esta guerra se conoce como el Gran Conflicto, y ha durado y " +"ardido durante más tiempo que cualquiera de las estrellas del cielo. Ninguno " +"de los bandos logrará dominar mientras las fuerzas de la Luz y la Oscuridad " +"compitan constantemente por el control de toda la creación." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:297 -msgid "Journal: His Power Grows" -msgstr "Diario: Su Poder Crece" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:499 +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 "" +"Presta atención y da testimonio de las verdades que se encuentran aquí, " +"porque son el último legado de los Horadrim. Cuando el Conflicto Eterno " +"entre los Altos Cielos y los Infiernos Ardientes cae sobre suelo mortal, se " +"llama la Guerra del Pecado. Ángeles y demonios caminan entre la humanidad " +"disfrazados, luchando en secreto, lejos de las miradas indiscretas de los " +"mortales. Algunos mortales atrevidos y poderosos incluso se han aliado con " +"ambos bandos y han ayudado a dictar el curso de la Guerra del Pecado." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:298 -msgid "Journal: NA-KRUL" -msgstr "Diario: NA-KRUL" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:501 +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 "" +"Presta atención y da testimonio de las verdades que se encuentran aquí, " +"porque son el último legado de los Horadrim. Hace casi trescientos años, se " +"supo que los Tres Malignos Principales de los Infiernos Ardientes habían " +"llegado misteriosamente a nuestro mundo. Los Tres Hermanos asolaron las " +"tierras del este durante décadas, mientras que la humanidad quedó temblando " +"a su paso. Nuestra Orden, los Horadrim, fue fundada por un grupo de magos " +"secretos para perseguir y capturar a los Tres Malignos de una vez por " +"todas.\n" +" \n" +"El Horadrim original capturó a dos de los Tres dentro de poderosos " +"artefactos conocidos como Piedras del Alma y los enterró profundamente bajo " +"las desoladas arenas del este. El tercer Maligno escapó de la captura y huyó " +"hacia el oeste con muchos de los Horadrim persiguiéndolo. El Tercer Mal, " +"conocido como Diablo, el Señor del Terror, finalmente fue capturado, su " +"esencia colocada en una Piedra del Alma y enterrada dentro de este " +"Laberinto.\n" +" \n" +"Se advierte que debe evitarse que la Piedra del Alma sea descubierta por " +"aquellos que no son de la fe. Si Diablo fuera liberado, buscaría un cuerpo " +"que fuera fácil de controlar, y que fuera muy débil, tal vez el de un " +"anciano o un niño." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:299 -msgid "Journal: The End" -msgstr "Diario: El Fin" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:503 +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 "" +"Así sucedió que hubo una gran revolución dentro de los Infiernos Ardientes " +"conocida como El Exilio Oscuro. Los Malignos Menores derrocaron a los Tres " +"Malignos Principales y desterraron sus formas espirituales al reino de los " +"mortales. Los demonios Belial (el Señor de las Mentiras) y Azmodan (el Señor " +"del Pecado) lucharon para reclamar el poder del Infierno durante la ausencia " +"de los Tres Hermanos. Todo el Infierno se polarizó entre las facciones de " +"Belial y Azmodan mientras las fuerzas de los Altos Cielos golpeaban " +"continuamente las mismas Puertas del Infierno." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:300 -msgid "A Spellbook" -msgstr "Un libro de Hechizos" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:505 +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 "" +"Muchos demonios viajaron al reino de los mortales en busca de los Tres " +"Hermanos. Estos demonios fueron seguidos al plano mortal por Ángeles que los " +"cazaron por las vastas pueblos del Este. Los Ángeles se aliaron con una " +"Orden secreta de magos mortales llamada Los Horadrim, quienes rápidamente se " +"volvieron expertos en cazar demonios. Estos también se hicieron muchos " +"enemigos oscuros en los inframundos." -#: Source/objects.cpp:4757 -msgid "Crucified Skeleton" -msgstr "Esqueleto Crucificado" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:507 +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 "" +"Así sucedió que los Tres Malignos Principales fueron desterrados en forma " +"espiritual al reino de los mortales y después de sembrar el caos en el Este " +"durante décadas, fueron perseguidos por la Orden maldita de los mortales " +"Horadrim. Los Horadrim usaron artefactos llamados Piedras del Alma para " +"contener la esencia de Mephisto, el Señor del Odio y su hermano Baal, el " +"Señor de la Destrucción. El hermano menor, Diablo, el Señor del Terror, " +"escapó hacia el oeste.\n" +" \n" +"Finalmente, los Horadrim capturaron a Diablo dentro de una Piedra del Alma y " +"lo enterraron bajo una antigua y olvidada Catedral. Allí, el Señor del " +"Terror duerme y espera el momento de su renacimiento. Sepan que buscará un " +"cuerpo de juventud y poder para poseer, uno que sea inocente y fácil de " +"controlar. Luego se levantará para liberar a sus Hermanos y avivar una vez " +"más las llamas de la Guerra del Pecado ..." -#: Source/objects.cpp:4762 -msgid "Lever" -msgstr "Palanca" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:509 +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 "" +"Todas las alabanzas a Diablo, Señor del Terror y Sobreviviente del Exilio " +"Oscuro. Cuando despertó de su largo letargo, mi Señor y Maestro me habló de " +"secretos que pocos mortales conocen. Me dijo que los reinos de los Altos " +"Cielos y los pozos de los Infiernos Abrasadores se enzarzan en una guerra " +"eterna. Él reveló los poderes que han traído esta discordia a los reinos del " +"hombre. Mi señor ha llamado a la batalla por este mundo, y a todos los que " +"existen aquí, la Guerra del Pecado." -#: Source/objects.cpp:4773 -msgid "Open Door" -msgstr "Puerta Abierta" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:511 +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 "" +"Gloria y Aprobación a Diablo, Señor del Terror y Líder de los Tres. Mi Señor " +"me habló de sus dos hermanos, Mefisto y Baal, que fueron desterrados a este " +"mundo hace mucho tiempo. Mi Señor desea esperar el momento oportuno y " +"aprovechar su asombroso poder para liberar a sus hermanos cautivos de sus " +"tumbas bajo las arenas del este. Una vez que mi Señor libere a sus Hermanos, " +"la Guerra del Pecado volverá a conocer la furia de los Tres." -#: Source/objects.cpp:4775 -msgid "Closed Door" -msgstr "Puerta Cerrada" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:513 +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 "" +"Aclamación y Sacrificio a Diablo, Señor del Terror y Destructor de Almas. " +"Cuando desperté a mi Maestro de su sueño, intentó poseer la forma de un " +"mortal. Diablo intentó reclamar el cuerpo del Rey Leoric, pero mi Maestro " +"estaba demasiado débil por su encarcelamiento. Mi señor necesitaba un ancla " +"simple e inocente a este mundo, y por eso encontró que el niño Albrecht era " +"perfecto para la tarea. Mientras el buen Rey Leoric estaba enloquecido por " +"la posesión fallida de Diablo, yo secuestré a su hijo Albrecht y lo llevé " +"ante mi Maestro. Ahora espero la llamada de Diablo y rezo para ser " +"recompensado cuando él finalmente emerja como el Señor de este mundo." -#: Source/objects.cpp:4777 -msgid "Blocked Door" -msgstr "Puerta Bloqueada" +#. TRANSLATORS: Neutral Text spoken by Ogden +#: Source/textdat.cpp:515 +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 "" +"¡Gracias a Dios que has vuelto!\n" +"Mucho ha cambiado desde que viviste aquí, amigo. Todo estaba en paz hasta " +"que llegaron los jinetes oscuros y destruyeron nuestra aldea. Muchos fueron " +"asesinados donde estaban, y los que tomaron las armas fueron asesinados o " +"arrastrados para convertirse en esclavos, o algo peor. La iglesia en las " +"afueras del pueblo ha sido profanada y se utiliza para rituales oscuros. Los " +"gritos que resuenan en la noche son inhumanos, pero es posible que algunos " +"de nuestros habitantes hayan sobrevivido. Sigue el camino que se encuentra " +"entre mi taberna y la herrería para encontrar la iglesia y salvar a quien " +"puedas. \n" +" \n" +"Quizás pueda contarte más si volvemos a hablar. Buena suerte." -#: Source/objects.cpp:4782 -msgid "Ancient Tome" -msgstr "Tomo Antiguo" +#. TRANSLATORS: Quest text spoken by Adria +#: Source/textdat.cpp:533 +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 "" +"Mantén tu búsqueda. Encontrar un tesoro perdido no es fácil. Encontrar un " +"tesoro que está escondido, aún menos. Te dejaré con esto. No permitas que " +"las arenas del tiempo confundan tu búsqueda." -#: Source/objects.cpp:4784 -msgid "Book of Vileness" -msgstr "Libro de la Vileza" +#. TRANSLATORS: Quest text spoken by Griswold +#: Source/textdat.cpp:535 +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 "" +"¿Un qué? ¡Esto es una tontería!. No hay ningún tesoro enterrado aquí en " +"Tristram. ¡Déjame ver eso! Ah, mira, estos dibujos son inexactos. No " +"coinciden en absoluto con nuestro pueblo. Me concentraría en lo que hay " +"debajo de la catedral y no en lo que hay debajo de nuestra capa superficial " +"del suelo." -#: Source/objects.cpp:4789 -msgid "Skull Lever" -msgstr "Palanca de Cráneo" +#. TRANSLATORS: Quest text spoken by Pepin +#: Source/textdat.cpp:537 +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 "" +"Realmente no tengo tiempo para discutir sobre algún mapa que estás buscando. " +"Tengo muchos enfermos que necesitan mi ayuda y la tuya también." -#: Source/objects.cpp:4792 -msgid "Mythical Book" -msgstr "Libro Mítico" +#. TRANSLATORS: Quest text spoken by Adria +#: Source/textdat.cpp:539 Source/textdat.cpp:551 +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 "" +"El otrora orgulloso Iswall está atrapado en las profundidades de este mundo. " +"Su honor fue despojado y su rostro alterado. Está atrapado en un tormento " +"inmortal. Encargado de ocultar lo mismo que podría liberarlo." -#: Source/objects.cpp:4796 -msgid "Small Chest" -msgstr "Cofre Pequeño" +#. TRANSLATORS: Quest text spoken by Ogden +#: Source/textdat.cpp:541 +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 "" +"Apuesto a que Wirt te vio venir y fingió un acto para poder reírse de ti más " +"tarde, cuando estabas corriendo por el pueblo con la nariz en la tierra. Yo " +"lo ignoraría." -#: Source/objects.cpp:4800 -msgid "Chest" -msgstr "Cofre" +#. TRANSLATORS: Quest text spoken by Cain +#: Source/textdat.cpp:543 +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 "" +"Hubo un tiempo en que este pueblo era una parada frecuente para los viajeros " +"de todas partes. Mucho ha cambiado desde entonces. Pero las cuevas " +"escondidas y los tesoros enterrados son fantasías comunes de cualquier niño. " +"Wirt rara vez se entrega a juegos juveniles. Así que puede que sea sólo su " +"imaginación." -#: Source/objects.cpp:4805 -msgid "Large Chest" -msgstr "Arcón" +#. TRANSLATORS: Quest text spoken by Farnham +#: Source/textdat.cpp:545 +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 "" +"Escucha. Acércate. No sé si sabes lo que yo sé, pero realmente tienes algo " +"aquí. Eso es un mapa." -#: Source/objects.cpp:4809 -msgid "Sarcophagus" -msgstr "Sarcófago" +#. TRANSLATORS: Quest text spoken by Gillian +#: Source/textdat.cpp:547 +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 "" +"Mi abuela a menudo me cuenta historias sobre las extrañas fuerzas que " +"habitan el cementerio fuera de la iglesia. Y puede que le interese escuchar " +"uno de ellos. Dijo que si dejaba la ofrenda adecuada en el cementerio, " +"entraba a la catedral para rezar por los muertos y luego regresaba, la " +"ofrenda se alteraría de alguna manera extraña. No sé si esto es solo la " +"charla de una anciana enferma, pero todo parece posible en estos días." -#: Source/objects.cpp:4812 -msgid "Bookshelf" -msgstr "Estante para Libros" +#. TRANSLATORS: Quest text spoken by Wirt +#: Source/textdat.cpp:549 +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 "" +"Mmmm ¿Un tesoro vasto y misterioso, dices? Mmmm. Tal vez podría estar " +"interesado en adquirir algunas cosas tuyas... o mejor aún, ¿no necesitas " +"algunos suministros raros y costosos para superar esta prueba?" -#: Source/objects.cpp:4816 -msgid "Bookcase" -msgstr "Estantería" +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:553 +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 "" +"Entonces, eres el héroe del que todos han estado hablando. ¿Quizás podrías " +"ayudar a un simple granjero pobre a salir de un lío terrible? En el borde de " +"mi huerto, al sur de aquí, ¡hay una cosa horrible que se hincha en el suelo! " +"No puedo llegar a mis cultivos ni a mis fardos de heno, y mis pobres vacas " +"se morirán de hambre. La bruja me dio esto y dijo que volaría esa cosa fuera " +"de mi campo. Si pudieras destruirlo, te estaré eternamente agradecido. Lo " +"haría yo mismo, pero alguien tiene que quedarse aquí con las vacas ..." -#: Source/objects.cpp:4820 -msgid "Barrel" -msgstr "Barril" +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:555 +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 "" +"Sabía que no podía ser tan simple como lo decía la bruja. Es un mundo triste " +"cuando ni siquiera puedes confiar en tus vecinos." -#: Source/objects.cpp:4824 -msgid "Pod" -msgstr "Vaina" +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:557 +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 "" +"¿Se ha ido? ¿Lo enviaste de vuelta a los oscuros recovecos del Hades que lo " +"engendraron? ¿Tu que? ¡Oh, no me digas que lo perdiste! Esas cosas no son " +"baratas, ¿sabes? Tienes que encontrarlo y luego sacar ese horror de nuestro " +"pueblo." -#: Source/objects.cpp:4828 -msgid "Urn" -msgstr "Urna" +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:559 +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 "" +"¡Escuché la explosión desde aquí! Muchas gracias a ti, amable extraño. Con " +"todas estas cosas que salen de la tierra, los monstruos que se apoderan de " +"la iglesia, y esas cosas, estos son tiempos difíciles. No soy más que un " +"agricultor pobre, pero toma esto con gran agradecimiento." -#. TRANSLATORS: {:s} will be a name from the Shrine block above -#: Source/objects.cpp:4832 -msgid "{:s} Shrine" -msgstr "Santuario {:s}" +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:561 +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 "" +"Oh, qué problema tengo ... tal vez ... No, no podría imponértelo, con todos " +"los otros problemas. Quizás, después de haber limpiado la iglesia de algunas " +"de esas criaturas, ¿podrías regresar ... y dedicar un poco de tiempo para " +"ayudar a un pobre agricultor?" -#: Source/objects.cpp:4835 -msgid "Skeleton Tome" -msgstr "Tomo de Esqueleto" +#. TRANSLATORS: Quest text spoken by Little Girl +#: Source/textdat.cpp:563 +msgid "Waaaah! (sniff) Waaaah! (sniff)" +msgstr "Waaaah! (sniff) Waaaah! (sniff)" -#: Source/objects.cpp:4838 -msgid "Library Book" -msgstr "Libro de la Biblioteca" +#. TRANSLATORS: Quest text spoken by Little Girl +#: Source/textdat.cpp:564 +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 "" +"¡Perdí a Theo! ¡Perdí a mi mejor amigo! Estábamos jugando junto al río, y " +"Theo dijo que quería ir a ver la gran cosa verde. Dije que no deberíamos, " +"pero nos colamos allí, ¡y de repente salió este INSECTO! ¡Huimos, pero Theo " +"se cayó y el insecto lo agarró y se lo llevó!" + +#. TRANSLATORS: Quest text spoken by Little Girl +#: Source/textdat.cpp:566 +msgid "" +"Didja find him? You gotta find Theodore, please! He's just little. He " +"can't take care of himself! Please!" +msgstr "" +"¿Didja lo encontró? ¡Tienes que encontrar a Theodore, por favor! Es solo " +"pequeño. ¡No puede cuidarse solo! ¡Por favor!" -#: Source/objects.cpp:4841 -msgid "Blood Fountain" -msgstr "Fuente de Sangre" +#. TRANSLATORS: Quest text spoken by Little Girl (Quest End) +#: Source/textdat.cpp:568 +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 "" +"¡Lo encontraste! ¡Lo encontraste! ¡Gracias! Oh Theo, ¿esos bichos " +"desagradables te asustaron? ¡Oye! ¡Puaj! ¡Hay algo pegado a tu pelaje! ¡Agh! " +"¡Vamos, Theo, vámonos a casa! ¡Gracias de nuevo, héroe!" -#: Source/objects.cpp:4844 -msgid "Decapitated Body" -msgstr "Cuerpo Decapitado" +#. TRANSLATORS: Quest text spoken by Defiler (Hostile) +#: Source/textdat.cpp:570 +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 "" +"Llevamos mucho tiempo dormidos y ha llegado el momento de despertar. Después " +"de nuestro largo sueño, nos llena un gran hambre. Pronto, ahora, nos " +"alimentaremos ..." -#: Source/objects.cpp:4847 -msgid "Book of the Blind" -msgstr "Libro de los Ciegos" +#. TRANSLATORS: Quest text spoken by Defiler (Hostile) +#: Source/textdat.cpp:572 +msgid "" +"Have you been enjoying yourself, little mammal? How pathetic. Your little " +"world will be no challenge at all." +msgstr "" +"¿Te has estado divirtiendo, pequeño mamífero? Que patético. Tu pequeño mundo " +"no será ningún desafío." -#: Source/objects.cpp:4850 -msgid "Book of Blood" -msgstr "Libro de Sangre" +#. TRANSLATORS: Quest text spoken by Defiler (Hostile) +#: Source/textdat.cpp:574 +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 "" +"Estas tierras serán contaminadas y nuestra prole invadirá los campos que los " +"hombres llaman hogar. Nuestros zarcillos envolverán este mundo y nos " +"deleitaremos con la carne de sus habitantes. El hombre se convertirá en " +"nuestro esclavos y sustento." -#: Source/objects.cpp:4853 -msgid "Purifying Spring" -msgstr "Manantial Purificante" +#. TRANSLATORS: Quest text spoken by Defiler (Hostile) +#: Source/textdat.cpp:576 +msgid "" +"Ah, I can smell you...you are close! Close! Ssss...the scent of blood and " +"fear...how enticing..." +msgstr "" +"Ah, te puedo oler... ¡estás cerca! ¡Cerca! Ssss... el olor a sangre y " +"miedo... qué tentador..." -#: Source/objects.cpp:4860 Source/objects.cpp:4885 -msgid "Weapon Rack" -msgstr "Estante de Armas" +#. TRANSLATORS: Quest text spoken by Narrator +#: Source/textdat.cpp:584 +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 "" +"Y en el año de la Luz Dorada, se decretó que se levantara una gran Catedral. " +"La piedra angular de este lugar sagrado debía ser tallada en la piedra " +"translúcida Antyrael, llamada así por el Ángel que compartía su poder con " +"los Horadrim.\n" +" \n" +"En el Año designado a las Sombras, el suelo tembló y la Catedral se hizo " +"añicos y cayó. Cuando comenzó la construcción de catacumbas y castillos y el " +"hombre se enfrentó a los estragos de la Guerra del Pecado, las ruinas fueron " +"escarbadas en busca de sus piedras. Y así fue como la piedra angular " +"desapareció de los ojos del hombre.\n" +" \n" +"La piedra era de este mundo , y de todos los mundos , ya que la Luz está " +"tanto dentro de todas las cosas como más allá de todas las cosas. La luz y " +"la unidad son los productos de este fundamento santo, una unidad de " +"propósito y una unidad de posesión." -#: Source/objects.cpp:4863 -msgid "Goat Shrine" -msgstr "Santuario de las Cabras" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:586 +msgid "Moo." +msgstr "Muu." -#: Source/objects.cpp:4866 -msgid "Cauldron" -msgstr "Caldero" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:587 +msgid "I said, Moo." +msgstr "Dije, Muu." -#: Source/objects.cpp:4869 -msgid "Murky Pool" -msgstr "Piscina Turbia" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:588 +msgid "Look I'm just a cow, OK?" +msgstr "Mira, solo soy una vaca, ¿de acuerdo?" -#: Source/objects.cpp:4872 -msgid "Fountain of Tears" -msgstr "Fuente de las Lágrimas" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:589 +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 "" +"Bien, bien. Realmente no soy una vaca. Normalmente no ando así; pero, estaba " +"sentado en casa ocupándome de mis propios asuntos y, de repente, estos " +"insectos, enredaderas, bulbos y cosas empezaron a salir del suelo ... ¡fue " +"horrible! Si tan solo tuviera algo normal para ponerme, no estaría tan mal. " +"¡Oye! ¿Podrías volver a mi casa y traerme mi traje? El marrón, no el gris, " +"es para la noche. Lo haría yo mismo, pero no quiero que nadie me vea así. " +"Ten, toma esto, puede que lo necesites ... para matar a esas cosas que han " +"crecido demasiado. No puedes perderte, mi casa está justo al sur de la " +"bifurcación del río ... ya sabes ... la del huerto descuidado." -#: Source/objects.cpp:4875 -msgid "Steel Tome" -msgstr "Tomo de Acero" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:591 +msgid "" +"What are you wasting time for? Go get my suit! And hurry! That Holstein " +"over there keeps winking at me!" +msgstr "" +"¿En qué estás perdiendo el tiempo? ¡Ve por mi traje! ¡Y date prisa! ¡Ese " +"Holstein de allí no deja de guiñarme el ojo!" -#: Source/objects.cpp:4878 -msgid "Pedestal of Blood" -msgstr "Pedestal de Sangre" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:593 +msgid "" +"Hey, have you got my suit there? Quick, pass it over! These ears itch like " +"you wouldn't believe!" +msgstr "" +"Oye, ¿tienes mi traje ahí? ¡Rápido, pásalo! ¡Estos oídos pican como no lo " +"creerías!" -#: Source/objects.cpp:4888 -msgid "Mushroom Patch" -msgstr "Parche de Hongos" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:595 +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 "" +"¡No no no no! ¡Este es mi traje GRIS! ¡Es para la noche! ¡Ocasiones " +"formales! No puedo usar ESTO. ¿Qué eres, una especie de bicho raro? Necesito " +"el traje MARRÓN." -#: Source/objects.cpp:4891 -msgid "Vile Stand" -msgstr "Vil Estante" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:597 +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 "" +"Ahh, eso es MUCHO mejor. ¡Uf! ¡Por fin, algo de dignidad! ¿Mis astas están " +"rectas? Bien. Mira, muchas gracias por ayudarme. Ten, toma esto como un " +"regalo; y, ya sabes ... un pequeño consejo de moda ... te vendría bien un " +"poco ... podrías usar un nuevo ... ¿sabes lo que quiero decir? Todo el " +"motivo de los aventureros es tan ... retro. Solo un consejo, ¿eh? Chao." -#: Source/objects.cpp:4894 -msgid "Slain Hero" -msgstr "Héroe Asesinado" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:599 +msgid "" +"Look. I'm a cow. And you, you're monster bait. Get some experience under " +"your belt! We'll talk..." +msgstr "" +"Mirar Soy una vaca. Y tú, eres un cebo para monstruos. ¡Consigue algo de " +"experiencia debajo de tu cinturón! Hablaremos ..." -#. TRANSLATORS: {:s} will either be a chest or a door -#: Source/objects.cpp:4901 -msgid "Trapped {:s}" -msgstr "{:s} Trampa" +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:602 +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 "" +"Realmente debe ser una tarea temible la que te he propuesto. Si hubiera " +"alguna manera de que pudiera ... ¿ayudaría una jarra de leche fresca?" -#. TRANSLATORS: If user enabled diablo.ini setting "Disable Crippling Shrines" is set to 1; also used for Na-Kruls lever -#: Source/objects.cpp:4906 -msgid "{:s} (disabled)" -msgstr "{:s} (deshabilitado)" +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:604 +msgid "" +"Oh, I could use your help, but perhaps after you've saved the catacombs from " +"the desecration of those beasts." +msgstr "" +"Oh, me vendría bien tu ayuda, pero quizás después de que hayas salvado las " +"catacumbas de la profanación de esas bestias." -#: Source/options.cpp:448 Source/options.cpp:573 Source/options.cpp:579 -msgid "ON" -msgstr "ENCENDIDO" +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:606 +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 "" +"Necesito que se haga algo, pero no podría obligar a un perfecto desconocido. " +"Quizás después de que hayas estado aquí un tiempo me sienta más cómodo " +"pidiendo un favor." -#: Source/options.cpp:448 Source/options.cpp:571 Source/options.cpp:577 -msgid "OFF" -msgstr "APAGADO" +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:608 +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 "" +"Veo en ti el potencial de grandeza. ¿Quizás en algún momento mientras estás " +"cumpliendo tu destino, podrías pasar y hacerme un pequeño favor?" -#: Source/options.cpp:561 -msgid "Start Up" -msgstr "Inicio" +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:610 +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 "" +"Creo que probablemente podrías ayudarme, pero quizás después de que te hayas " +"vuelto un poco más poderoso. ¡No quisiera dañar la única oportunidad que " +"tiene el pueblo de destruir la amenaza en la iglesia!" -#: Source/options.cpp:561 -msgid "Start Up Settings" -msgstr "Configuraciones de inicio" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:612 +msgid "" +"Me, I'm a self-made cow. Make something of yourself, and... then we'll talk." +msgstr "" +"Yo soy una vaca hecha a sí misma. Haz algo de ti mismo y ... luego " +"hablaremos." -#: Source/options.cpp:562 -msgid "Game Mode" -msgstr "Modo de juego" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:614 +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 "" +"¡No tengo que dar explicaciones a todos los turistas que pasan! ¿No tienes " +"algunos monstruos que matar? Quizás hablemos más tarde. Si sigues vivo .." -#: Source/options.cpp:562 -msgid "Play Diablo or Hellfire." -msgstr "Jugar Diablo o Hellfire." +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:616 +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 "" +"Deja de molestarme. Busco a alguien realmente heroico. Y no lo eres. No " +"puedo confiar en ti, vas a ser devorado por monstruos en cualquier " +"momento ... Necesito a alguien que sea un héroe experimentado." -#: Source/options.cpp:568 -msgid "Restrict to Shareware" -msgstr "Restringir a modo shareware" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:618 +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 "" +"Muy bien, iré al grano. No quise guiarte mal. Estaba sentado en casa, " +"sintiéndome malhumorado, cuando las cosas se pusieron realmente inestables; " +"¡Toda una estampida de monstruos salió del suelo! Solo me acobarde. Estaba " +"usando esta camiseta cuando salí corriendo por la puerta, y ahora me veo " +"terriblemente ridículo. Si tan solo tuviera algo normal para ponerme, no " +"estaría tan mal. ¡Oye! ¿Puedes volver a mi casa y traerme mi traje? El " +"marrón, no el gris, es para la noche. Lo haría yo mismo, pero no quiero que " +"nadie me vea así. Ten, toma esto, puede que lo necesites ... para matar esas " +"cosas que han crecido demasiado. No puedes perderte, mi casa está justo al " +"sur de la bifurcación del río ... ya sabes ... la del huerto descuidado." -#: Source/options.cpp:568 +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:621 msgid "" -"Makes the game compatible with the demo. Enables multiplayer with friends who " -"don't own a full copy of Diablo." +"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 "" -"Hace que el juego sea compatible con la demostración. Habilita el modo " -"multijugador con amigos que no poseen una copia completa de Diablo." +"He intentado hechizos, amenazas, abjuración y regateo con esta criatura " +"repugnante, sin éxito. Mis métodos para esclavizar a los demonios menores " +"parecen no tener ningún efecto sobre esta temible bestia." -#: Source/options.cpp:569 Source/options.cpp:575 -msgid "Intro" -msgstr "Introducción" +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:623 +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 "" +"Mi hogar se está corrompiendo lentamente por la vileza de este prisionero no " +"deseado. Las criptas están\tllenas de sombras que se mueven un poco más allá " +"de las esquinas de mi visión. El leve roce de las garras baila en los bordes " +"de mi oído. Están buscando, creo, este diario." -#: Source/options.cpp:569 Source/options.cpp:575 -msgid "Shown Intro cinematic." -msgstr "Mostrar cinemática de introducción." +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:625 +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 "" +"En su despotricar, la criatura ha dejado escapar su nombre: Na-Krul. Intenté " +"investigar el nombre, pero los demonios más pequeños de alguna manera " +"destruyeron mi biblioteca. Na-Krul ... El nombre me llena de un pavor frío. " +"Prefiero pensar en él solo como La Criatura en lugar de reflexionar sobre su " +"verdadero nombre." -#: Source/options.cpp:581 -msgid "Splash" -msgstr "Pantalla de bienvenida" +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:627 +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 "" +"Los aullidos de furia de la criatura atrapada me impiden conseguir el sueño " +"que tanto necesitaba. Se enfurece contra quien la envió al Vacío, y me " +"maldice por haberla atrapado aquí. Sus palabras me llenan el corazón de " +"terror y, sin embargo, no puedo bloquear su voz." -#: Source/options.cpp:581 -msgid "Shown splash screen." -msgstr "Mostrar pantalla de bienvenida." +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:629 +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 "" +"Mi tiempo se acaba rápidamente. Debo registrar las formas de debilitar al " +"demonio y luego ocultar ese texto, no sea que sus secuaces encuentren alguna " +"manera de usar mi conocimiento para liberar a su señor. Espero que quien " +"encuentre este diario busque el conocimiento." -# La traducción no es exacta para reducir el largo. -#: Source/options.cpp:583 -msgid "Logo and Title Screen" -msgstr "Logo y título" +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:631 +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 "" +"Quien encuentre este pergamino está encargado de detener a la criatura " +"demoníaca que se encuentra dentro de estos muros. Mi tiempo se acabado. " +"Incluso ahora, sus infernales secuaces se aferran a la frágil puerta detrás " +"de la cual me escondo.\n" +" \n" +"He restringido al demonio con magia arcana y lo he encerrado dentro de " +"grandes muros, pero me temo que eso no será suficiente.\n" +" \n" +"Los hechizos que se encuentran en mis tres grimorios te proporcionarán una " +"entrada protegida a su dominio, pero solo si se lanzan en la secuencia " +"adecuada. Las palancas en la entrada quitarán las barreras y liberarán al " +"demonio ¡No las toques! Usa solo estos hechizos para lograr entrar o su " +"poder puede ser demasiado grande para derrotarlo." -#: Source/options.cpp:584 -msgid "Title Screen" -msgstr "Pantalla de título" +#. TRANSLATORS: Quest text read aloud from book by player +#: Source/textdat.cpp:633 Source/textdat.cpp:636 Source/textdat.cpp:639 +#: Source/textdat.cpp:642 Source/textdat.cpp:645 +msgid "In Spiritu Sanctum." +msgstr "In Spiritu Sanctum." -#: Source/options.cpp:603 -msgid "Diablo specific Settings" -msgstr "Configuraciones específicas de Diablo" +#. TRANSLATORS: Quest text read aloud from book by player +#: Source/textdat.cpp:634 Source/textdat.cpp:637 Source/textdat.cpp:640 +#: Source/textdat.cpp:643 Source/textdat.cpp:646 +msgid "Praedictum Otium." +msgstr "Praedictum Otium." -#: Source/options.cpp:617 -msgid "Hellfire specific Settings" -msgstr "Configuraciones específicas de Hellfire" +#. TRANSLATORS: Quest text read aloud from book by player +#: Source/textdat.cpp:635 Source/textdat.cpp:638 Source/textdat.cpp:641 +#: Source/textdat.cpp:644 Source/textdat.cpp:647 +msgid "Efficio Obitus Ut Inimicus." +msgstr "Efficio Obitus Ut Inimicus." -#: Source/options.cpp:631 -msgid "Audio" -msgstr "Audio" +#: Source/towners.cpp:82 +msgid "Griswold the Blacksmith" +msgstr "Griswold el Herrero" -#: Source/options.cpp:631 -msgid "Audio Settings" -msgstr "Configuraciones de audio" +#: Source/towners.cpp:104 +msgid "Ogden the Tavern owner" +msgstr "Ogden el Tabernero" -#: Source/options.cpp:634 -msgid "Walking Sound" -msgstr "Sonido al caminar" +#: Source/towners.cpp:113 +msgid "Wounded Townsman" +msgstr "Aldeano Herido" -#: Source/options.cpp:634 -msgid "Player emits sound when walking." -msgstr "Los jugadores emiten sonido cuando caminan." +#: Source/towners.cpp:134 +msgid "Adria the Witch" +msgstr "Adria la Bruja" -#: Source/options.cpp:635 -msgid "Auto Equip Sound" -msgstr "Sonido de auto equipo" +#: Source/towners.cpp:143 +msgid "Gillian the Barmaid" +msgstr "Gillian la Camarera" + +#: Source/towners.cpp:174 +msgid "Pepin the Healer" +msgstr "Pepin el Curandero" -#: Source/options.cpp:635 -msgid "Automatically equipping items on pickup emits the equipment sound." -msgstr "" -"Al equiparse automáticamente con un objeto se emite el sonido de equipamiento." +#: Source/towners.cpp:191 +msgid "Cain the Elder" +msgstr "Caín el Sabio" -#: Source/options.cpp:636 -msgid "Item Pickup Sound" -msgstr "Sonido de recoger objeto" +#: Source/towners.cpp:218 +msgid "Cow" +msgstr "Vaca" -#: Source/options.cpp:636 -msgid "Picking up items emits the items pickup sound." -msgstr "Al recoger un objeto se emite el sonido de recoger." +#: Source/towners.cpp:241 +msgid "Lester the farmer" +msgstr "Lester el granjero" -#: Source/options.cpp:637 -msgid "Sample Rate" -msgstr "Frecuencia de muestreo" +#: Source/towners.cpp:253 +msgid "Complete Nut" +msgstr "Loco de Remate" -#: Source/options.cpp:637 -msgid "Output sample rate (Hz)." -msgstr "Frecuencia de muestreo de salida (Hz)." +#: Source/towners.cpp:261 +msgid "Celia" +msgstr "Celia" -#: Source/options.cpp:638 -msgid "Channels" -msgstr "Canales" +#: Source/towners.cpp:274 +msgid "Slain Townsman" +msgstr "Aldeano Asesinado" -#: Source/options.cpp:638 -msgid "Number of output channels." -msgstr "Número de canales de salida." +#: Source/translation_dummy.cpp:9 +msgctxt "monster" +msgid "Zombie" +msgstr "Zombi" -#: Source/options.cpp:639 -msgid "Buffer Size" -msgstr "Tamaño del buffer" +#: Source/translation_dummy.cpp:10 +msgctxt "monster" +msgid "Ghoul" +msgstr "Necrófago" -#: Source/options.cpp:639 -msgid "Buffer size (number of frames per channel)." -msgstr "Tamaño del buffer (numero de cuadros por segundo)." +#: Source/translation_dummy.cpp:11 +msgctxt "monster" +msgid "Rotting Carcass" +msgstr "Cádaver Podrido" -#: Source/options.cpp:640 -msgid "Resampling Quality" -msgstr "Calidad de remuestreo" +#: Source/translation_dummy.cpp:12 +msgctxt "monster" +msgid "Black Death" +msgstr "Muerte Negra" -#: Source/options.cpp:640 -msgid "Quality of the resampler, from 0 (lowest) to 10 (highest)." -msgstr "Calidad de remuestreo, desde 0 (el más bajo) a 10 (el más alto)." +#: Source/translation_dummy.cpp:13 Source/translation_dummy.cpp:21 +msgctxt "monster" +msgid "Fallen One" +msgstr "El Caído" -#: Source/options.cpp:671 -msgid "Resolution" -msgstr "Resolución" +#: Source/translation_dummy.cpp:14 Source/translation_dummy.cpp:22 +msgctxt "monster" +msgid "Carver" +msgstr "Trinchante" -#: Source/options.cpp:671 -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 "" -"Afecta a la resolución interna del juego y determina su área de visión. Nota: " -"Esto puede diferir de la resolución de la pantalla cuando se utiliza Aumento de " -"escala, Escala de entera o Ajustar a la pantalla." +#: Source/translation_dummy.cpp:15 Source/translation_dummy.cpp:23 +msgctxt "monster" +msgid "Devil Kin" +msgstr "Pariente del Demonio" -#: Source/options.cpp:771 -msgid "Resampler" -msgstr "Remuestreador" +#: Source/translation_dummy.cpp:16 Source/translation_dummy.cpp:24 +msgctxt "monster" +msgid "Dark One" +msgstr "El Oscuro" -#: Source/options.cpp:771 -msgid "Audio resampler" -msgstr "Remuestreador de audio" +#: Source/translation_dummy.cpp:17 Source/translation_dummy.cpp:29 +msgctxt "monster" +msgid "Skeleton" +msgstr "Esqueleto" -#: Source/options.cpp:828 -msgid "Device" -msgstr "Dispositivo" +#: Source/translation_dummy.cpp:18 +msgctxt "monster" +msgid "Corpse Axe" +msgstr "Hacha Cadáver" -#: Source/options.cpp:828 -msgid "Audio device" -msgstr "Dispositivo de audio" +#: Source/translation_dummy.cpp:19 Source/translation_dummy.cpp:31 +msgctxt "monster" +msgid "Burning Dead" +msgstr "Muerte Ardiente" -#: Source/options.cpp:896 -msgid "Graphics" -msgstr "Gráficos" +#: Source/translation_dummy.cpp:20 Source/translation_dummy.cpp:32 +msgctxt "monster" +msgid "Horror" +msgstr "Horror" -#: Source/options.cpp:896 -msgid "Graphics Settings" -msgstr "Configuración de gráficos" +#: Source/translation_dummy.cpp:25 +msgctxt "monster" +msgid "Scavenger" +msgstr "Carroñero" -#: Source/options.cpp:897 -msgid "Fullscreen" -msgstr "Pantalla completa" +#: Source/translation_dummy.cpp:26 +msgctxt "monster" +msgid "Plague Eater" +msgstr "Devorador de la Plaga" -#: Source/options.cpp:897 -msgid "Display the game in windowed or fullscreen mode." -msgstr "Muestra el juego en modo ventana o pantalla completa." +#: Source/translation_dummy.cpp:27 +msgctxt "monster" +msgid "Shadow Beast" +msgstr "Bestia de las Sombras" -#: Source/options.cpp:899 -msgid "Fit to Screen" -msgstr "Ajustar a la pantalla" +#: Source/translation_dummy.cpp:28 +msgctxt "monster" +msgid "Bone Gasher" +msgstr "Desgarrador de Huesos" -#: Source/options.cpp:899 -msgid "" -"Automatically adjust the game window to your current desktop screen aspect ratio " -"and resolution." -msgstr "" -"Ajusta automáticamente la venta del juego a la relación de aspecto y resolución " -"del escritorio actual." +#: Source/translation_dummy.cpp:30 +msgctxt "monster" +msgid "Corpse Bow" +msgstr "Arquero Cadáver" -#: Source/options.cpp:902 -msgid "Upscale" -msgstr "Aumento de escala" +#: Source/translation_dummy.cpp:33 +msgctxt "monster" +msgid "Skeleton Captain" +msgstr "Capitán Esqueleto" -#: Source/options.cpp:902 -msgid "" -"Enables image scaling from the game resolution to your monitor resolution. " -"Prevents changing the monitor resolution and allows window resizing." -msgstr "" -"Permite escalar la imagen de la resolución del juego a la resolución de su " -"monitor. Evita cambiar la resolución del monitor y permite cambiar el tamaño de " -"la ventana." +#: Source/translation_dummy.cpp:34 +msgctxt "monster" +msgid "Corpse Captain" +msgstr "Capitán Cadáver" -#: Source/options.cpp:909 -msgid "Scaling Quality" -msgstr "Calidad de escalado" +#: Source/translation_dummy.cpp:35 +msgctxt "monster" +msgid "Burning Dead Captain" +msgstr "Capitán Muerte Ardiente" -#: Source/options.cpp:909 -msgid "Enables optional filters to the output image when upscaling." -msgstr "Habilita filtros opcionales para la imagen de salida al ampliar la escala." +#: Source/translation_dummy.cpp:36 +msgctxt "monster" +msgid "Horror Captain" +msgstr "Capitán del Horror" -#: Source/options.cpp:911 -msgid "Nearest Pixel" -msgstr "Pixel más cercano" +#: Source/translation_dummy.cpp:37 +msgctxt "monster" +msgid "Invisible Lord" +msgstr "Señor Invisible" -#: Source/options.cpp:912 -msgid "Bilinear" -msgstr "Bilineal" +#: Source/translation_dummy.cpp:38 +msgctxt "monster" +msgid "Hidden" +msgstr "Oculto" -#: Source/options.cpp:913 -msgid "Anisotropic" -msgstr "Anisotrópico" +#: Source/translation_dummy.cpp:39 +msgctxt "monster" +msgid "Stalker" +msgstr "Acechador" -#: Source/options.cpp:915 -msgid "Integer Scaling" -msgstr "Escalado entero" +#: Source/translation_dummy.cpp:40 +msgctxt "monster" +msgid "Unseen" +msgstr "Invisible" -#: Source/options.cpp:915 -msgid "Scales the image using whole number pixel ratio." -msgstr "Escala la imagen usando una proporción de píxeles de números enteros." +#: Source/translation_dummy.cpp:41 +msgctxt "monster" +msgid "Illusion Weaver" +msgstr "Tejedor de Ilusiones" -#: Source/options.cpp:922 -msgid "Vertical Sync" -msgstr "Sincronismo vertical" +#: Source/translation_dummy.cpp:42 +msgctxt "monster" +msgid "Satyr Lord" +msgstr "Señor Sátiro" -#: Source/options.cpp:923 -msgid "" -"Forces waiting for Vertical Sync. Prevents tearing effect when drawing a frame. " -"Disabling it can help with mouse lag on some systems." -msgstr "" -"Fuerzas la espera de sincronización vertical. Evita el efecto de desgarro al " -"dibujar un marco. Deshabilitarlo puede ayudar con el retraso del mouse en " -"algunos sistemas." +#: Source/translation_dummy.cpp:43 Source/translation_dummy.cpp:51 +msgctxt "monster" +msgid "Flesh Clan" +msgstr "Clan de Carne" -#: Source/options.cpp:932 -msgid "Zoom on when enabled." -msgstr "Acercar cuando está habilitado." +#: Source/translation_dummy.cpp:44 Source/translation_dummy.cpp:52 +msgctxt "monster" +msgid "Stone Clan" +msgstr "Clan de Piedra" -#: Source/options.cpp:933 -msgid "Color Cycling" -msgstr "Ciclo de color" +#: Source/translation_dummy.cpp:45 Source/translation_dummy.cpp:53 +msgctxt "monster" +msgid "Fire Clan" +msgstr "Clan de Fuego" -#: Source/options.cpp:933 -msgid "Color cycling effect used for water, lava, and acid animation." -msgstr "Efecto de ciclo de color usado para la animación del agua, lava y ácido." +#: Source/translation_dummy.cpp:46 Source/translation_dummy.cpp:54 +msgctxt "monster" +msgid "Night Clan" +msgstr "Clan Nocturno" -#: Source/options.cpp:934 -msgid "Alternate nest art" -msgstr "Alternar arte alternativa" +#: Source/translation_dummy.cpp:47 +msgctxt "monster" +msgid "Fiend" +msgstr "Maligno" -#: Source/options.cpp:934 -msgid "The game will use an alternative palette for Hellfire’s nest tileset." -msgstr "" -"El juego usará una paleta alternativa para el juego de fichas alternativo de " -"Hellfire." +#: Source/translation_dummy.cpp:48 +msgctxt "monster" +msgid "Blink" +msgstr "Murciélago destellante" -#: Source/options.cpp:936 -msgid "Hardware Cursor" -msgstr "Cursor por hardware" +#: Source/translation_dummy.cpp:49 +msgctxt "monster" +msgid "Gloom" +msgstr "Murciélago oscuro" -#: Source/options.cpp:936 -msgid "Use a hardware cursor" -msgstr "Usa el cursor por hardware" +#: Source/translation_dummy.cpp:50 +msgctxt "monster" +msgid "Familiar" +msgstr "Familiar" -# la traduccion debe ser corta para que entre en el cuado de menu -#: Source/options.cpp:937 -msgid "Hardware Cursor For Items" -msgstr "Cursor por hw para objetos" +#: Source/translation_dummy.cpp:55 +msgctxt "monster" +msgid "Acid Beast" +msgstr "Bestia Ácida" -#: Source/options.cpp:937 -msgid "Use a hardware cursor for items." -msgstr "Usa el cursos por hardware para los objetos." +#: Source/translation_dummy.cpp:56 +msgctxt "monster" +msgid "Poison Spitter" +msgstr "Escupidor de Veneno" -# Mantener corto. Menu de configuración -#: Source/options.cpp:938 -msgid "Hardware Cursor Maximum Size" -msgstr "Tamaño máx del cursor por hardw" +#: Source/translation_dummy.cpp:57 +msgctxt "monster" +msgid "Pit Beast" +msgstr "Bestia del Foso" -#: Source/options.cpp:938 -msgid "" -"Maximum width / height for the hardware cursor. Larger cursors fall back to " -"software." -msgstr "" -"Máximo ancho/alto del cursor por hardware. Cursores muy grandes se sustituyen " -"por software." +#: Source/translation_dummy.cpp:58 +msgctxt "monster" +msgid "Lava Maw" +msgstr "Fauces de Lava" -#: Source/options.cpp:940 -msgid "FPS Limiter" -msgstr "Limitador de FPS" +#: Source/translation_dummy.cpp:59 Source/translation_dummy.cpp:148 +msgctxt "monster" +msgid "Skeleton King" +msgstr "Rey Esqueleto" -#: Source/options.cpp:940 -msgid "FPS is limited to avoid high CPU load. Limit considers refresh rate." -msgstr "" -"Los FPS están limitados para evitar altas cargas de CPU. El límite considera la " -"frecuencia de actualización." +#: Source/translation_dummy.cpp:60 Source/translation_dummy.cpp:156 +msgctxt "monster" +msgid "The Butcher" +msgstr "El Carnicero" -#: Source/options.cpp:941 -msgid "Show FPS" -msgstr "Mostrar los FPS" +#: Source/translation_dummy.cpp:61 +msgctxt "monster" +msgid "Overlord" +msgstr "Cacique" -#: Source/options.cpp:941 -msgid "Displays the FPS in the upper left corner of the screen." -msgstr "Muestra los FPS en la esquina superior izquierda de la pantalla." +#: Source/translation_dummy.cpp:62 +msgctxt "monster" +msgid "Mud Man" +msgstr "Hombre de Barro" -#: Source/options.cpp:942 -msgid "Show health values" -msgstr "Muestra valores de salud" +#: Source/translation_dummy.cpp:63 +msgctxt "monster" +msgid "Toad Demon" +msgstr "Demonio Sapo" -#: Source/options.cpp:942 -msgid "Displays current / max health value on health globe." -msgstr "Muestra el valor de salud actual/máximo en el globo de salud." +#: Source/translation_dummy.cpp:64 +msgctxt "monster" +msgid "Flayed One" +msgstr "El Desollado" -#: Source/options.cpp:943 -msgid "Show mana values" -msgstr "Muestra valores de maná" +#: Source/translation_dummy.cpp:65 +msgctxt "monster" +msgid "Wyrm" +msgstr "Pequeño Dragón" -#: Source/options.cpp:943 -msgid "Displays current / max mana value on mana globe." -msgstr "Muestra el valor de maná actual/máximo en el globo de maná." +#: Source/translation_dummy.cpp:66 +msgctxt "monster" +msgid "Cave Slug" +msgstr "Babosa de la Cueva" -#: Source/options.cpp:993 -msgid "Gameplay" -msgstr "Juego" +#: Source/translation_dummy.cpp:67 +msgctxt "monster" +msgid "Devil Wyrm" +msgstr "Pequeño Dragón Demoníaco" -#: Source/options.cpp:993 -msgid "Gameplay Settings" -msgstr "Configuraciones durante el juego" +#: Source/translation_dummy.cpp:68 +msgctxt "monster" +msgid "Devourer" +msgstr "Devorador" -#: Source/options.cpp:995 -msgid "Run in Town" -msgstr "Correr en el pueblo" +#: Source/translation_dummy.cpp:69 +msgctxt "monster" +msgid "Magma Demon" +msgstr "Demonio de Magma" -#: Source/options.cpp:995 -msgid "" -"Enable jogging/fast walking in town for Diablo and Hellfire. This option was " -"introduced in the expansion." -msgstr "" -"Habillita trotar/caminar veloz en el pueblo para Diablo y Hellfire. Esta opción " -"fue introducida en la expansión." +#: Source/translation_dummy.cpp:70 +msgctxt "monster" +msgid "Blood Stone" +msgstr "Piedra de Sangre" -#: Source/options.cpp:996 -msgid "Grab Input" -msgstr "Capturar entrada" +#: Source/translation_dummy.cpp:71 +msgctxt "monster" +msgid "Hell Stone" +msgstr "Piedra del Infierno" -#: Source/options.cpp:996 -msgid "When enabled mouse is locked to the game window." -msgstr "Cuando está habilitado el mouse se captura en la ventana de juego." +#: Source/translation_dummy.cpp:72 +msgctxt "monster" +msgid "Lava Lord" +msgstr "Señor de la Lava" -#: Source/options.cpp:997 -msgid "Theo Quest" -msgstr "La misión de Theo" +#: Source/translation_dummy.cpp:73 +msgctxt "monster" +msgid "Horned Demon" +msgstr "Demonio Cornudo" -#: Source/options.cpp:997 -msgid "Enable Little Girl quest." -msgstr "Habilita la misión de la Niñita." +#: Source/translation_dummy.cpp:74 +msgctxt "monster" +msgid "Mud Runner" +msgstr "Corredor de Barro" -#: Source/options.cpp:998 -msgid "Cow Quest" -msgstr "La misión de la vaca" +#: Source/translation_dummy.cpp:75 +msgctxt "monster" +msgid "Frost Charger" +msgstr "Cargador de Escarcha" -#: Source/options.cpp:998 -msgid "Enable Jersey's quest. Lester the farmer is replaced by the Complete Nut." -msgstr "" -"Habilita la misión de Jersey. El granjero Lester es reemplazado por el Loco de " -"Remate." +#: Source/translation_dummy.cpp:76 +msgctxt "monster" +msgid "Obsidian Lord" +msgstr "Señor de Obsidiana" -#: Source/options.cpp:999 -msgid "Friendly Fire" -msgstr "Fuego amigo" +#: Source/translation_dummy.cpp:77 +msgctxt "monster" +msgid "oldboned" +msgstr "envejecido" -#: Source/options.cpp:999 -msgid "" -"Allow arrow/spell damage between players in multiplayer even when the friendly " -"mode is on." -msgstr "" -"Permite que las flechas/hechizos produzcan daño entre los jugadores en un juego " -"multijugador aun cuando el modo amigo está habilitado." +#: Source/translation_dummy.cpp:78 +msgctxt "monster" +msgid "Red Death" +msgstr "Muerte Roja" -#: Source/options.cpp:1000 -msgid "Test Bard" -msgstr "Probar Bardo" +#: Source/translation_dummy.cpp:79 +msgctxt "monster" +msgid "Litch Demon" +msgstr "Demonio Exánime" -#: Source/options.cpp:1000 -msgid "Force the Bard character type to appear in the hero selection menu." -msgstr "" -"Fuerza al tipo de personaje Bardo a aparecer en el menú de selección del " -"personaje." +#: Source/translation_dummy.cpp:80 +msgctxt "monster" +msgid "Undead Balrog" +msgstr "Balrog Zombi" -#: Source/options.cpp:1001 -msgid "Test Barbarian" -msgstr "Probar Bárbaro" +#: Source/translation_dummy.cpp:81 +msgctxt "monster" +msgid "Incinerator" +msgstr "Incinerador" -#: Source/options.cpp:1001 -msgid "Force the Barbarian character type to appear in the hero selection menu." -msgstr "" -"Fuerza al tipo de personaje Bárbaro a aparecer en el menú de selección del " -"personaje." +#: Source/translation_dummy.cpp:82 +msgctxt "monster" +msgid "Flame Lord" +msgstr "Señor de las Llamas" -#: Source/options.cpp:1002 -msgid "Experience Bar" -msgstr "Barra de experiencia" +#: Source/translation_dummy.cpp:83 +msgctxt "monster" +msgid "Doom Fire" +msgstr "Fuego de Destrucción" -#: Source/options.cpp:1002 -msgid "Experience Bar is added to the UI at the bottom of the screen." -msgstr "" -"Se agrega la barra de experiencia a la UI en la parte inferior de la pantalla." +#: Source/translation_dummy.cpp:84 +msgctxt "monster" +msgid "Hell Burner" +msgstr "Quemador del Infierno" -#: Source/options.cpp:1003 -msgid "Enemy Health Bar" -msgstr "Barra de salud del enemigo" +#: Source/translation_dummy.cpp:85 +msgctxt "monster" +msgid "Red Storm" +msgstr "Tormenta Roja" -#: Source/options.cpp:1003 -msgid "Enemy Health Bar is displayed at the top of the screen." -msgstr "" -"Se muestra la barra de salud del enemigo en la parte superior de la pantalla." +#: Source/translation_dummy.cpp:86 +msgctxt "monster" +msgid "Storm Rider" +msgstr "Jinete de Tormenta" -#: Source/options.cpp:1004 -msgid "Auto Gold Pickup" -msgstr "Auto recoger oro" +#: Source/translation_dummy.cpp:87 +msgctxt "monster" +msgid "Storm Lord" +msgstr "Señor de las Tormentas" -#: Source/options.cpp:1004 -msgid "Gold is automatically collected when in close proximity to the player." -msgstr "" -"El oro es automáticamente recogido cuando se encuentra en proximidad del jugador." +#: Source/translation_dummy.cpp:88 +msgctxt "monster" +msgid "Maelstrom" +msgstr "Remolino" -#: Source/options.cpp:1005 -msgid "Auto Elixir Pickup" -msgstr "Auto recoger elixir" +#: Source/translation_dummy.cpp:89 +msgctxt "monster" +msgid "Devil Kin Brute" +msgstr "Pariente del Demonio Bruto" -#: Source/options.cpp:1005 -msgid "Elixirs are automatically collected when in close proximity to the player." -msgstr "" -"Los elixires son automáticamente recogidos cuando se encuentran en proximidad " -"del jugador." +#: Source/translation_dummy.cpp:90 +msgctxt "monster" +msgid "Winged-Demon" +msgstr "Demonio Alado" -#: Source/options.cpp:1006 -msgid "Auto Oil Pickup" -msgstr "Recolección automática de aceite" +#: Source/translation_dummy.cpp:91 +msgctxt "monster" +msgid "Gargoyle" +msgstr "Gárgola" -#: Source/options.cpp:1006 -msgid "Oils are automatically collected when in close proximity to the player." -msgstr "Los aceites se recogen automáticamente cuando está cerca del jugador." +#: Source/translation_dummy.cpp:92 +msgctxt "monster" +msgid "Blood Claw" +msgstr "Garra de Sangre" -#: Source/options.cpp:1007 -msgid "Auto Pickup in Town" -msgstr "Auto recoger en el pueblo" +#: Source/translation_dummy.cpp:93 +msgctxt "monster" +msgid "Death Wing" +msgstr "Ala de la Muerte" -#: Source/options.cpp:1007 -msgid "Automatically pickup items in town." -msgstr "Automáticamente recoge los objetos en el pueblo." +#: Source/translation_dummy.cpp:94 +msgctxt "monster" +msgid "Slayer" +msgstr "Asesino" -#: Source/options.cpp:1008 -msgid "Adria Refills Mana" -msgstr "Adria recarga maná" +#: Source/translation_dummy.cpp:95 +msgctxt "monster" +msgid "Guardian" +msgstr "Guardián" -#: Source/options.cpp:1008 -msgid "Adria will refill your mana when you visit her shop." -msgstr "Adria recargará tu maná cuando la visites en su tienda." +#: Source/translation_dummy.cpp:96 +msgctxt "monster" +msgid "Vortex Lord" +msgstr "Señor del Vórtice" -#: Source/options.cpp:1009 -msgid "Auto Equip Weapons" -msgstr "Auto equiparse con armas" +#: Source/translation_dummy.cpp:97 +msgctxt "monster" +msgid "Balrog" +msgstr "Balrog" -#: Source/options.cpp:1009 -msgid "Weapons will be automatically equipped on pickup or purchase if enabled." -msgstr "" -"Si está habilitado las armas serán automáticamente equipadas al recogerse o " -"comprarse." +#: Source/translation_dummy.cpp:98 +msgctxt "monster" +msgid "Cave Viper" +msgstr "Víbora de Cueva" -#: Source/options.cpp:1010 -msgid "Auto Equip Armor" -msgstr "Auto equiparse con armadura" +#: Source/translation_dummy.cpp:99 +msgctxt "monster" +msgid "Fire Drake" +msgstr "Dragón de Fuego" -#: Source/options.cpp:1010 -msgid "Armor will be automatically equipped on pickup or purchase if enabled." -msgstr "" -"Si está habilitado las armaduras serán automáticamente equipadas al recogerse o " -"comprarse." +#: Source/translation_dummy.cpp:100 +msgctxt "monster" +msgid "Gold Viper" +msgstr "Víbora de Oro" -#: Source/options.cpp:1011 -msgid "Auto Equip Helms" -msgstr "Auto equiparse con yelmo" +#: Source/translation_dummy.cpp:101 +msgctxt "monster" +msgid "Azure Drake" +msgstr "Dragón Azur" -#: Source/options.cpp:1011 -msgid "Helms will be automatically equipped on pickup or purchase if enabled." -msgstr "" -"Si está habilitado los yelmos serán automáticamente equipadas al recogerse o " -"comprarse." +#: Source/translation_dummy.cpp:102 +msgctxt "monster" +msgid "Black Knight" +msgstr "Caballero Negro" -#: Source/options.cpp:1012 -msgid "Auto Equip Shields" -msgstr "Auto equiparse con escudos" +#: Source/translation_dummy.cpp:103 +msgctxt "monster" +msgid "Doom Guard" +msgstr "Guardia de la Destrucción" -#: Source/options.cpp:1012 -msgid "Shields will be automatically equipped on pickup or purchase if enabled." -msgstr "" -"Si está habilitado los escudos serán automáticamente equipadas al recogerse o " -"comprarse." +#: Source/translation_dummy.cpp:104 +msgctxt "monster" +msgid "Steel Lord" +msgstr "Señor de Acero" -#: Source/options.cpp:1013 -msgid "Auto Equip Jewelry" -msgstr "Auto equiparse con joyería" +#: Source/translation_dummy.cpp:105 +msgctxt "monster" +msgid "Blood Knight" +msgstr "Caballero de Sangre" -#: Source/options.cpp:1013 -msgid "Jewelry will be automatically equipped on pickup or purchase if enabled." -msgstr "" -"Si está habilitado las joyerías serán automáticamente equipadas al recogerse o " -"comprarse." +#: Source/translation_dummy.cpp:106 +msgctxt "monster" +msgid "The Shredded" +msgstr "El Destrozado" -#: Source/options.cpp:1014 -msgid "Randomize Quests" -msgstr "Misiones aleatorias" +#: Source/translation_dummy.cpp:107 +msgctxt "monster" +msgid "Hollow One" +msgstr "El Hueco" -#: Source/options.cpp:1014 -msgid "Randomly selecting available quests for new games." -msgstr "Selecciona aleatoriamente las misiones disponibles en las nuevas partidas." +#: Source/translation_dummy.cpp:108 +msgctxt "monster" +msgid "Pain Master" +msgstr "Maestro del Dolor" -#: Source/options.cpp:1015 -msgid "Show Monster Type" -msgstr "Mostrar el tipo de monstruo" +#: Source/translation_dummy.cpp:109 +msgctxt "monster" +msgid "Reality Weaver" +msgstr "Tejedora de Realidad" -#: Source/options.cpp:1015 -msgid "" -"Hovering over a monster will display the type of monster in the description box " -"in the UI." -msgstr "" -"Al pasar el cursor sobre un monstruo, se mostrará el tipo de monstruo en el " -"cuadro de descripción de la interfaz de usuario." +#: Source/translation_dummy.cpp:110 +msgctxt "monster" +msgid "Succubus" +msgstr "Súcubo" -#: Source/options.cpp:1016 -msgid "Show Item Labels" -msgstr "Mostrar etiquetas de artículos" +#: Source/translation_dummy.cpp:111 +msgctxt "monster" +msgid "Snow Witch" +msgstr "Bruja de la Nieve" -#: Source/options.cpp:1016 -msgid "Show labels for items on the ground when enabled." -msgstr "Mostrar etiquetas para artículos en el suelo cuando está habilitado." +#: Source/translation_dummy.cpp:112 +msgctxt "monster" +msgid "Hell Spawn" +msgstr "Engendro del Infierno" -#: Source/options.cpp:1017 -msgid "Auto Refill Belt" -msgstr "Auto recargar cinturón" +#: Source/translation_dummy.cpp:113 +msgctxt "monster" +msgid "Soul Burner" +msgstr "Quemador de Almas" -#: Source/options.cpp:1017 -msgid "Refill belt from inventory when belt item is consumed." -msgstr "" -"Recarga el cinturón desde el inventario cuando el objeto ha sido consumido." +#: Source/translation_dummy.cpp:114 +msgctxt "monster" +msgid "Counselor" +msgstr "Consejero" -# La traduccion debe ser corta para que entre en el cuadro de dialogo -#: Source/options.cpp:1018 -msgid "Disable Crippling Shrines" -msgstr "Desact. santuarios paralizantes" +#: Source/translation_dummy.cpp:115 +msgctxt "monster" +msgid "Magistrate" +msgstr "Magistrado" -#: Source/options.cpp:1018 -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 "" -"Cuando está habilitado no se puede hacer click en calderos, santuarios " -"fascinantes, santuarios ornamentados y santuarios sagrados, y serán etiquetados " -"como desactivados." +#: Source/translation_dummy.cpp:116 +msgctxt "monster" +msgid "Cabalist" +msgstr "Cabalista" -#: Source/options.cpp:1019 -msgid "Quick Cast" -msgstr "Lanzamiento rápido" +#: Source/translation_dummy.cpp:117 +msgctxt "monster" +msgid "Advocate" +msgstr "Defensor" -#: Source/options.cpp:1019 -msgid "" -"Spell hotkeys instantly cast the spell, rather than switching the readied spell." -msgstr "" -"Las teclas rápidas de hechizos los lanzan inmediatamente en vez de cambiar a " -"hechizo leído." +#: Source/translation_dummy.cpp:118 +msgctxt "monster" +msgid "Golem" +msgstr "Gólem" -#: Source/options.cpp:1020 -msgid "Heal Potion Pickup" -msgstr "Recoger poción de salud" +#: Source/translation_dummy.cpp:119 +msgctxt "monster" +msgid "The Dark Lord" +msgstr "El Señor Oscuro" -#: Source/options.cpp:1020 -msgid "Number of Healing potions to pick up automatically." -msgstr "Número de pociones de salud que se recogerán automáticamente." +#: Source/translation_dummy.cpp:120 +msgctxt "monster" +msgid "The Arch-Litch Malignus" +msgstr "El Archi Exánime Maligno" -#: Source/options.cpp:1021 -msgid "Full Heal Potion Pickup" -msgstr "Recoger poción de salud completa" +#: Source/translation_dummy.cpp:121 +msgctxt "monster" +msgid "Hellboar" +msgstr "Jabinfierno" -#: Source/options.cpp:1021 -msgid "Number of Full Healing potions to pick up automatically." -msgstr "Número de pociones de salud completa que se recogerán automáticamente." +#: Source/translation_dummy.cpp:122 +msgctxt "monster" +msgid "Stinger" +msgstr "Escorpión" -#: Source/options.cpp:1022 -msgid "Mana Potion Pickup" -msgstr "Recoger poción de maná" +#: Source/translation_dummy.cpp:123 +msgctxt "monster" +msgid "Psychorb" +msgstr "Psychorb" -#: Source/options.cpp:1022 -msgid "Number of Mana potions to pick up automatically." -msgstr "Número de pociones de maná que se recogerán automáticamente." +#: Source/translation_dummy.cpp:124 +msgctxt "monster" +msgid "Arachnon" +msgstr "Arachnon" -#: Source/options.cpp:1023 -msgid "Full Mana Potion Pickup" -msgstr "Recoger poción de maná completo" +#: Source/translation_dummy.cpp:125 +msgctxt "monster" +msgid "Felltwin" +msgstr "Felltwin" -#: Source/options.cpp:1023 -msgid "Number of Full Mana potions to pick up automatically." -msgstr "Número de pociones de maná completo que se recogerán automáticamente." +#: Source/translation_dummy.cpp:126 +msgctxt "monster" +msgid "Hork Spawn" +msgstr "Engendro de Hork" -#: Source/options.cpp:1024 -msgid "Rejuvenation Potion Pickup" -msgstr "Recoger poción de rejuvenecimiento" +#: Source/translation_dummy.cpp:127 +msgctxt "monster" +msgid "Venomtail" +msgstr "Cola Venenosa" -#: Source/options.cpp:1024 -msgid "Number of Rejuvenation potions to pick up automatically." -msgstr "Número de pociones de rejuvenecimiento que se recogerán automáticamente." +#: Source/translation_dummy.cpp:128 +msgctxt "monster" +msgid "Necromorb" +msgstr "Necromorb" -# La traduccion debe ser corta para que entre en el cuadro de dialogo -#: Source/options.cpp:1025 -msgid "Full Rejuvenation Potion Pickup" -msgstr "Recoger poción de rejuvenec. completo" +#: Source/translation_dummy.cpp:129 +msgctxt "monster" +msgid "Spider Lord" +msgstr "Señor Araña" -#: Source/options.cpp:1025 -msgid "Number of Full Rejuvenation potions to pick up automatically." -msgstr "" -"Número de pociones de rejuvenecimiento completo que se recogerán automáticamente." +#: Source/translation_dummy.cpp:130 +msgctxt "monster" +msgid "Lashworm" +msgstr "Gusano de Pestañas" -#: Source/options.cpp:1070 -msgid "Controller" -msgstr "Controlador" +#: Source/translation_dummy.cpp:131 +msgctxt "monster" +msgid "Torchant" +msgstr "Torchant" -#: Source/options.cpp:1070 -msgid "Controller Settings" -msgstr "Configuraciones del controlador" +#: Source/translation_dummy.cpp:132 Source/translation_dummy.cpp:157 +msgctxt "monster" +msgid "Hork Demon" +msgstr "Demonio Hork" -#: Source/options.cpp:1079 -msgid "Network" -msgstr "Red" +#: Source/translation_dummy.cpp:133 +msgctxt "monster" +msgid "Hell Bug" +msgstr "Insecto del Infierno" -#: Source/options.cpp:1079 -msgid "Network Settings" -msgstr "Configuraciones de red" +#: Source/translation_dummy.cpp:134 +msgctxt "monster" +msgid "Gravedigger" +msgstr "Sepulturero" -#: Source/options.cpp:1091 -msgid "Chat" -msgstr "Chat" +#: Source/translation_dummy.cpp:135 +msgctxt "monster" +msgid "Tomb Rat" +msgstr "Rata de Tumba" -#: Source/options.cpp:1091 -msgid "Chat Settings" -msgstr "Configuraciones del chat" +#: Source/translation_dummy.cpp:136 +msgctxt "monster" +msgid "Firebat" +msgstr "Murciélago de Fuego" -#: Source/options.cpp:1100 Source/options.cpp:1216 -msgid "Language" -msgstr "Idioma" +#: Source/translation_dummy.cpp:137 +msgctxt "monster" +msgid "Skullwing" +msgstr "Calavera Alada" -#: Source/options.cpp:1100 -msgid "Define what language to use in game." -msgstr "Define el idioma que se utilizará en el juego." +#: Source/translation_dummy.cpp:138 +msgctxt "monster" +msgid "Lich" +msgstr "Lich" -#: Source/options.cpp:1216 -msgid "Language Settings" -msgstr "Configuración de idioma" +#: Source/translation_dummy.cpp:139 +msgctxt "monster" +msgid "Crypt Demon" +msgstr "Demonio de la Cripta" -#: Source/options.cpp:1228 -msgid "Keymapping" -msgstr "Mapeo de teclas" +#: Source/translation_dummy.cpp:140 +msgctxt "monster" +msgid "Hellbat" +msgstr "Murciélago Infernal" -#: Source/options.cpp:1228 -msgid "Keymapping Settings" -msgstr "Configuración de mapeo de teclas" +#: Source/translation_dummy.cpp:141 +msgctxt "monster" +msgid "Bone Demon" +msgstr "Demonio de Hueso" -#: Source/options.cpp:1438 -msgid "Padmapping" -msgstr "Mapeo del pad" +#: Source/translation_dummy.cpp:142 +msgctxt "monster" +msgid "Arch Lich" +msgstr "Arch Lich" -#: Source/options.cpp:1438 -msgid "Padmapping Settings" -msgstr "Configuración de mapeo del pad" +#: Source/translation_dummy.cpp:143 +msgctxt "monster" +msgid "Biclops" +msgstr "Biclope" -#: Source/panels/charpanel.cpp:133 -msgid "Level" -msgstr "Nivel" +#: Source/translation_dummy.cpp:144 +msgctxt "monster" +msgid "Flesh Thing" +msgstr "Cosa de Carne" -#: Source/panels/charpanel.cpp:135 -msgid "Experience" -msgstr "Experiencia" +#: Source/translation_dummy.cpp:145 +msgctxt "monster" +msgid "Reaper" +msgstr "Segador" -#: Source/panels/charpanel.cpp:140 -msgid "Next level" -msgstr "Siguiente Nivel" +#: Source/translation_dummy.cpp:146 Source/translation_dummy.cpp:159 +msgctxt "monster" +msgid "Na-Krul" +msgstr "Na-Krul" -#: Source/panels/charpanel.cpp:149 -msgid "Base" -msgstr "Base" +#: Source/translation_dummy.cpp:147 +msgctxt "monster" +msgid "Gharbad the Weak" +msgstr "Gharbad el Débil" -#: Source/panels/charpanel.cpp:150 -msgid "Now" -msgstr "Ahora" +#: Source/translation_dummy.cpp:149 +msgctxt "monster" +msgid "Zhar the Mad" +msgstr "Zhar el Loco" -#: Source/panels/charpanel.cpp:151 -msgid "Strength" -msgstr "Fuerza" +#: Source/translation_dummy.cpp:150 +msgctxt "monster" +msgid "Snotspill" +msgstr "Derrame de Mocos" -#: Source/panels/charpanel.cpp:155 -msgid "Magic" -msgstr "Magia" +#: Source/translation_dummy.cpp:151 +msgctxt "monster" +msgid "Arch-Bishop Lazarus" +msgstr "Arzobispo Lazarus" -#: Source/panels/charpanel.cpp:159 -msgid "Dexterity" -msgstr "Destreza" +#: Source/translation_dummy.cpp:152 +msgctxt "monster" +msgid "Red Vex" +msgstr "Molestia Roja" -#: Source/panels/charpanel.cpp:162 -msgid "Vitality" -msgstr "Vitalidad" +#: Source/translation_dummy.cpp:153 +msgctxt "monster" +msgid "Black Jade" +msgstr "Jade Negro" -#: Source/panels/charpanel.cpp:165 -msgid "Points to distribute" -msgstr "Puntos a distribuir" +#: Source/translation_dummy.cpp:154 +msgctxt "monster" +msgid "Lachdanan" +msgstr "Lachdanan" -#: Source/panels/charpanel.cpp:175 -msgid "Armor class" -msgstr "Clase armadura" +#: Source/translation_dummy.cpp:155 +msgctxt "monster" +msgid "Warlord of Blood" +msgstr "Señor de la Guerra de Sangre" -#: Source/panels/charpanel.cpp:177 -msgid "To hit" -msgstr "Atacar" +#: Source/translation_dummy.cpp:158 +msgctxt "monster" +msgid "The Defiler" +msgstr "El Profanador" -#: Source/panels/charpanel.cpp:179 -msgid "Damage" -msgstr "Daño" +#: Source/translation_dummy.cpp:160 +msgctxt "monster" +msgid "Bonehead Keenaxe" +msgstr "Hacha Afilada de Cabeza de Hueso" -#: Source/panels/charpanel.cpp:186 -msgid "Life" -msgstr "Vida" +#: Source/translation_dummy.cpp:161 +msgctxt "monster" +msgid "Bladeskin the Slasher" +msgstr "Bladeskin el Descuartizador" -#: Source/panels/charpanel.cpp:190 -msgid "Mana" -msgstr "Maná" +#: Source/translation_dummy.cpp:162 +msgctxt "monster" +msgid "Soulpus" +msgstr "Pus del Alma" -#: Source/panels/charpanel.cpp:195 -msgid "Resist magic" -msgstr "Resist magia" +#: Source/translation_dummy.cpp:163 +msgctxt "monster" +msgid "Pukerat the Unclean" +msgstr "Pukerat el Inmundo" + +#: Source/translation_dummy.cpp:164 +msgctxt "monster" +msgid "Boneripper" +msgstr "Desgarrador de Huesos" -#: Source/panels/charpanel.cpp:197 -msgid "Resist fire" -msgstr "Resist fuego" +#: Source/translation_dummy.cpp:165 +msgctxt "monster" +msgid "Rotfeast the Hungry" +msgstr "Rotfeast el Hambriento" -#: Source/panels/charpanel.cpp:199 -msgid "Resist lightning" -msgstr "Resist rayos" +#: Source/translation_dummy.cpp:166 +msgctxt "monster" +msgid "Gutshank the Quick" +msgstr "Gutshank el Veloz" -#: Source/panels/mainpanel.cpp:83 -msgid "char" -msgstr "personaje" +#: Source/translation_dummy.cpp:167 +msgctxt "monster" +msgid "Brokenhead Bangshield" +msgstr "Brokenhead Bangshield" -#: Source/panels/mainpanel.cpp:84 -msgid "quests" -msgstr "misiones" +#: Source/translation_dummy.cpp:168 +msgctxt "monster" +msgid "Bongo" +msgstr "Bongo" -#: Source/panels/mainpanel.cpp:85 -msgid "map" -msgstr "mapa" +#: Source/translation_dummy.cpp:169 +msgctxt "monster" +msgid "Rotcarnage" +msgstr "Carnicería de Putrefacción" -#: Source/panels/mainpanel.cpp:86 -msgid "menu" -msgstr "menú" +#: Source/translation_dummy.cpp:170 +msgctxt "monster" +msgid "Shadowbite" +msgstr "Mordedura de Sombra" -#: Source/panels/mainpanel.cpp:87 -msgid "inv" -msgstr "inv" +#: Source/translation_dummy.cpp:171 +msgctxt "monster" +msgid "Deadeye" +msgstr "Vigota" -#: Source/panels/mainpanel.cpp:88 -msgid "spells" -msgstr "hechizos" +#: Source/translation_dummy.cpp:172 +msgctxt "monster" +msgid "Madeye the Dead" +msgstr "Madeye el Muerto" -#: Source/panels/mainpanel.cpp:99 Source/panels/mainpanel.cpp:122 -#: Source/panels/mainpanel.cpp:124 -msgid "voice" -msgstr "voz" +#: Source/translation_dummy.cpp:173 +msgctxt "monster" +msgid "El Chupacabras" +msgstr "El Chupacabras" -#: Source/panels/mainpanel.cpp:117 Source/panels/mainpanel.cpp:119 -#: Source/panels/mainpanel.cpp:121 -msgid "mute" -msgstr "silenciar" +#: Source/translation_dummy.cpp:174 +msgctxt "monster" +msgid "Skullfire" +msgstr "Calavera de Fuego" -#: Source/panels/spell_book.cpp:146 Source/panels/spell_list.cpp:146 -msgid "Skill" -msgstr "Habilidad" +#: Source/translation_dummy.cpp:175 +msgctxt "monster" +msgid "Warpskull" +msgstr "Warpskull" -#: Source/panels/spell_book.cpp:150 -msgid "Staff ({:d} charge)" -msgid_plural "Staff ({:d} charges)" -msgstr[0] "Bastón ({:d} carga)" -msgstr[1] "Bastón ({:d} cargas)" +#: Source/translation_dummy.cpp:176 +msgctxt "monster" +msgid "Goretongue" +msgstr "Lengua Sangrienta" -#. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:155 -msgctxt "spellbook" -msgid "Level {:d}" -msgstr "Nivel: {:d}" +#: Source/translation_dummy.cpp:177 +msgctxt "monster" +msgid "Pulsecrawler" +msgstr "Rastreador de Pulsos" -#: Source/panels/spell_book.cpp:157 -msgid "Unusable" -msgstr "Inutilizable" +#: Source/translation_dummy.cpp:178 +msgctxt "monster" +msgid "Moonbender" +msgstr "Moonbender" -#. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:165 -msgid "Heals: {:d} - {:d}" -msgstr "Sana: {:d} - {:d}" +#: Source/translation_dummy.cpp:179 +msgctxt "monster" +msgid "Wrathraven" +msgstr "Cuervo de la Ira" -#. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:167 -msgid "Damage: {:d} - {:d}" -msgstr "Daño: {:d} - {:d}" +#: Source/translation_dummy.cpp:180 +msgctxt "monster" +msgid "Spineeater" +msgstr "Comedor de Columna" -#. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:171 -msgid "Dmg: 1/3 target hp" -msgstr "Daño: 1/3 tgt hp" +#: Source/translation_dummy.cpp:181 +msgctxt "monster" +msgid "Blackash the Burning" +msgstr "Blackash el Quemado" -#. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:173 -msgctxt "spellbook" -msgid "Mana: {:d}" -msgstr "Maná: {:d}" +#: Source/translation_dummy.cpp:182 +msgctxt "monster" +msgid "Shadowcrow" +msgstr "Cuervo de Sombra" -#: Source/panels/spell_list.cpp:153 -msgid "Spell" -msgstr "Hechizo" +#: Source/translation_dummy.cpp:183 +msgctxt "monster" +msgid "Blightstone the Weak" +msgstr "Blightstone el Débil" -#: Source/panels/spell_list.cpp:156 -msgid "Damages undead only" -msgstr "Solo daña muertos vivientes" +#: Source/translation_dummy.cpp:184 +msgctxt "monster" +msgid "Bilefroth the Pit Master" +msgstr "Bilefroth el Maestro del Foso" -#: Source/panels/spell_list.cpp:167 -msgid "Scroll" -msgstr "Pergamino" +#: Source/translation_dummy.cpp:185 +msgctxt "monster" +msgid "Bloodskin Darkbow" +msgstr "Arco oscuro Piel de Sangre" -#: Source/panels/spell_list.cpp:188 -msgid "Spell Hotkey {:s}" -msgstr "Tecla de acceso rápido de Hechizo {:s}" +#: Source/translation_dummy.cpp:186 +msgctxt "monster" +msgid "Foulwing" +msgstr "Foulwing" -#: Source/pfile.cpp:671 -msgid "Unable to open archive" -msgstr "No se puede abrir el archivo" +#: Source/translation_dummy.cpp:187 +msgctxt "monster" +msgid "Shadowdrinker" +msgstr "Bebedor de Sombras" -#: Source/pfile.cpp:673 -msgid "Unable to load character" -msgstr "No se puede cargar el personaje" +#: Source/translation_dummy.cpp:188 +msgctxt "monster" +msgid "Hazeshifter" +msgstr "Cambiador de Neblina" -#: Source/plrmsg.cpp:83 Source/qol/chatlog.cpp:131 -msgid "{:s} (lvl {:d}): " -msgstr "{:s} (nivel {:d}): " +#: Source/translation_dummy.cpp:189 +msgctxt "monster" +msgid "Deathspit" +msgstr "Escupidor de Muerte" -#: Source/qol/chatlog.cpp:160 -msgid "Chat History (Messages: {:d})" -msgstr "Historia del Chat (Mensajes: {:d})" +#: Source/translation_dummy.cpp:190 +msgctxt "monster" +msgid "Bloodgutter" +msgstr "Bloodgutter" -#: Source/qol/itemlabels.cpp:99 -msgid "{:s} gold" -msgstr "{:s} oro" +#: Source/translation_dummy.cpp:191 +msgctxt "monster" +msgid "Deathshade Fleshmaul" +msgstr "Deathshade Fleshmaul" -#: Source/qol/stash.cpp:630 -msgid "How many gold pieces do you want to withdraw?" -msgstr "¿Cuántas piezas de oro quieres retirar?" +#: Source/translation_dummy.cpp:192 +msgctxt "monster" +msgid "Warmaggot the Mad" +msgstr "Warmaggot el Loco" -#: Source/qol/xpbar.cpp:123 -msgid "Level {:d}" -msgstr "Nivel: {:d}" +#: Source/translation_dummy.cpp:193 +msgctxt "monster" +msgid "Glasskull the Jagged" +msgstr "Glasskull el Dentado" -#: Source/qol/xpbar.cpp:129 Source/qol/xpbar.cpp:137 -msgid "Experience: {:s}" -msgstr "Experiencia: {:s}" +#: Source/translation_dummy.cpp:194 +msgctxt "monster" +msgid "Blightfire" +msgstr "Añublo" -#: Source/qol/xpbar.cpp:130 -msgid "Maximum Level" -msgstr "Nivel Máximo" +#: Source/translation_dummy.cpp:195 +msgctxt "monster" +msgid "Nightwing the Cold" +msgstr "Nightwing el Frio" -#: Source/qol/xpbar.cpp:138 -msgid "Next Level: {:s}" -msgstr "Siguiente Nivel: {:s}" +#: Source/translation_dummy.cpp:196 +msgctxt "monster" +msgid "Gorestone" +msgstr "Piedra de Sangre" -#: Source/qol/xpbar.cpp:139 -msgid "{:s} to Level {:d}" -msgstr "{:s} al Nivel {:d}" +#: Source/translation_dummy.cpp:197 +msgctxt "monster" +msgid "Bronzefist Firestone" +msgstr "Piedra de Fuego Puño de Bronce" -#. TRANSLATORS: Quest Name Block -#: Source/quests.cpp:46 -msgid "The Magic Rock" -msgstr "La Roca Mágica" +#: Source/translation_dummy.cpp:198 +msgctxt "monster" +msgid "Wrathfire the Doomed" +msgstr "Wrathfire el Condenado" -#: Source/quests.cpp:48 -msgid "Gharbad The Weak" -msgstr "Gharbad el Débil" +#: Source/translation_dummy.cpp:199 +msgctxt "monster" +msgid "Firewound the Grim" +msgstr "Firewound el Siniestro" -#: Source/quests.cpp:49 -msgid "Zhar the Mad" -msgstr "Zhar el Loco" +#: Source/translation_dummy.cpp:200 +msgctxt "monster" +msgid "Baron Sludge" +msgstr "Barón Lodo" -#: Source/quests.cpp:50 -msgid "Lachdanan" -msgstr "Lachdanan" +#: Source/translation_dummy.cpp:201 +msgctxt "monster" +msgid "Blighthorn Steelmace" +msgstr "Maza de acero Cuerno de Plaga" -#: Source/quests.cpp:52 -msgid "The Butcher" -msgstr "El Carnicero" +#: Source/translation_dummy.cpp:202 +msgctxt "monster" +msgid "Chaoshowler" +msgstr "Aullador del Caos" -#: Source/quests.cpp:53 -msgid "Ogden's Sign" -msgstr "Signo de Ogden" +#: Source/translation_dummy.cpp:203 +msgctxt "monster" +msgid "Doomgrin the Rotting" +msgstr "Doomgrin el Podrido" -#: Source/quests.cpp:54 -msgid "Halls of the Blind" -msgstr "Pasillos de los Ciegos" +#: Source/translation_dummy.cpp:204 +msgctxt "monster" +msgid "Madburner" +msgstr "Quemador Loco" -#: Source/quests.cpp:55 -msgid "Valor" -msgstr "Valor" +#: Source/translation_dummy.cpp:205 +msgctxt "monster" +msgid "Bonesaw the Litch" +msgstr "Bonesaw el Litch" -#: Source/quests.cpp:57 -msgid "Warlord of Blood" -msgstr "Señor de la Guerra de Sangre" +#: Source/translation_dummy.cpp:206 +msgctxt "monster" +msgid "Breakspine" +msgstr "Rompe Columna" -#: Source/quests.cpp:58 -msgid "The Curse of King Leoric" -msgstr "La Maldición del Rey Leoric" +#: Source/translation_dummy.cpp:207 +msgctxt "monster" +msgid "Devilskull Sharpbone" +msgstr "Hueso Afilado del Cráneo del Diablo" -#. TRANSLATORS: Quest Map -#: Source/quests.cpp:60 Source/quests.cpp:96 -msgid "The Chamber of Bone" -msgstr "La Cámara de Hueso" +#: Source/translation_dummy.cpp:208 +msgctxt "monster" +msgid "Brokenstorm" +msgstr "Tormenta Rota" -#: Source/quests.cpp:61 -msgid "Archbishop Lazarus" -msgstr "Arzobispo Lazarus" +#: Source/translation_dummy.cpp:209 +msgctxt "monster" +msgid "Stormbane" +msgstr "Stormbane" -#: Source/quests.cpp:62 -msgid "Grave Matters" -msgstr "Asuntos de Tumbas" +#: Source/translation_dummy.cpp:210 +msgctxt "monster" +msgid "Oozedrool" +msgstr "Exuda Baba" -#: Source/quests.cpp:63 -msgid "Farmer's Orchard" -msgstr "Huerto del Granjero" +#: Source/translation_dummy.cpp:211 +msgctxt "monster" +msgid "Goldblight of the Flame" +msgstr "Dorado de la Llama" -#: Source/quests.cpp:64 -msgid "Little Girl" -msgstr "Niñita" +#: Source/translation_dummy.cpp:212 +msgctxt "monster" +msgid "Blackstorm" +msgstr "Tormenta Negra" -#: Source/quests.cpp:65 -msgid "Wandering Trader" -msgstr "Comerciante Errante" +#: Source/translation_dummy.cpp:213 +msgctxt "monster" +msgid "Plaguewrath" +msgstr "Ira de la Peste" -#: Source/quests.cpp:66 -msgid "The Defiler" -msgstr "El Profanador" +#: Source/translation_dummy.cpp:214 +msgctxt "monster" +msgid "The Flayer" +msgstr "El Desollador" -#: Source/quests.cpp:67 -msgid "Na-Krul" -msgstr "Na-Krul" +#: Source/translation_dummy.cpp:215 +msgctxt "monster" +msgid "Bluehorn" +msgstr "Cuerno Azul" -#. TRANSLATORS: Quest Name Block end -#: Source/quests.cpp:69 -msgid "The Jersey's Jersey" -msgstr "El Jersey de Jersey" +#: Source/translation_dummy.cpp:216 +msgctxt "monster" +msgid "Warpfire Hellspawn" +msgstr "Engendro del Infierno del Fuego de la Disformidad" -#. TRANSLATORS: Quest Map -#: Source/quests.cpp:95 -msgid "King Leoric's Tomb" -msgstr "Tumba del Rey Leoric" +#: Source/translation_dummy.cpp:217 +msgctxt "monster" +msgid "Fangspeir" +msgstr "Colmillo" -#. TRANSLATORS: Quest Map -#: Source/quests.cpp:98 -msgid "A Dark Passage" -msgstr "Un Pasadizo Oscuro" +#: Source/translation_dummy.cpp:218 +msgctxt "monster" +msgid "Festerskull" +msgstr "Festerskull" -#. TRANSLATORS: Quest Map -#: Source/quests.cpp:99 -msgid "Unholy Altar" -msgstr "Altar Impío" +#: Source/translation_dummy.cpp:219 +msgctxt "monster" +msgid "Lionskull the Bent" +msgstr "Lionskull el Doblado" -#. TRANSLATORS: Used for Quest Portals. {:s} is a Map Name -#: Source/quests.cpp:394 -msgid "To {:s}" -msgstr "A {:s}" +#: Source/translation_dummy.cpp:220 +msgctxt "monster" +msgid "Blacktongue" +msgstr "Lengua Negra" -#: Source/spelldat.cpp:16 -msgctxt "spell" -msgid "Firebolt" -msgstr "Flecha de fuego" +#: Source/translation_dummy.cpp:221 +msgctxt "monster" +msgid "Viletouch" +msgstr "Toque Vil" -#: Source/spelldat.cpp:17 -msgctxt "spell" -msgid "Healing" -msgstr "Curación" +#: Source/translation_dummy.cpp:222 +msgctxt "monster" +msgid "Viperflame" +msgstr "Vivora Llamenate" -#: Source/spelldat.cpp:18 -msgctxt "spell" -msgid "Lightning" -msgstr "Rayo" +#: Source/translation_dummy.cpp:223 +msgctxt "monster" +msgid "Fangskin" +msgstr "Fangskin" -#: Source/spelldat.cpp:19 -msgctxt "spell" -msgid "Flash" -msgstr "Destello" +#: Source/translation_dummy.cpp:224 +msgctxt "monster" +msgid "Witchfire the Unholy" +msgstr "Witchfire el Profano" -#: Source/spelldat.cpp:20 -msgctxt "spell" -msgid "Identify" -msgstr "Identificar" +#: Source/translation_dummy.cpp:225 +msgctxt "monster" +msgid "Blackskull" +msgstr "Calavera Negra" -#: Source/spelldat.cpp:21 -msgctxt "spell" -msgid "Fire Wall" -msgstr "Muro de Fuego" +#: Source/translation_dummy.cpp:226 +msgctxt "monster" +msgid "Soulslash" +msgstr "Corte de Alma" -#: Source/spelldat.cpp:22 -msgctxt "spell" -msgid "Town Portal" -msgstr "Portal al pueblo" +#: Source/translation_dummy.cpp:227 +msgctxt "monster" +msgid "Windspawn" +msgstr "Engendro del Viento" -#: Source/spelldat.cpp:23 -msgctxt "spell" -msgid "Stone Curse" -msgstr "Maldición de Piedra" +#: Source/translation_dummy.cpp:228 +msgctxt "monster" +msgid "Lord of the Pit" +msgstr "Señor del Pozo" -#: Source/spelldat.cpp:24 -msgctxt "spell" -msgid "Infravision" -msgstr "Infravisión" +#: Source/translation_dummy.cpp:229 +msgctxt "monster" +msgid "Rustweaver" +msgstr "Tejedor de Óxido" -#: Source/spelldat.cpp:25 -msgctxt "spell" -msgid "Phasing" -msgstr "Ajuste de Fase" +#: Source/translation_dummy.cpp:230 +msgctxt "monster" +msgid "Howlingire the Shade" +msgstr "Howlingire la Sombra" -#: Source/spelldat.cpp:26 -msgctxt "spell" -msgid "Mana Shield" -msgstr "Escudo de Maná" +#: Source/translation_dummy.cpp:231 +msgctxt "monster" +msgid "Doomcloud" +msgstr "Nube de la Fatalidad" -#: Source/spelldat.cpp:27 -msgctxt "spell" -msgid "Fireball" -msgstr "Bola de Fuego" +#: Source/translation_dummy.cpp:232 +msgctxt "monster" +msgid "Bloodmoon Soulfire" +msgstr "Fuego del Alma de la Luna de Sangre" -#: Source/spelldat.cpp:28 -msgctxt "spell" -msgid "Guardian" -msgstr "Guardián" +#: Source/translation_dummy.cpp:233 +msgctxt "monster" +msgid "Witchmoon" +msgstr "Luna de la Bruja" -#: Source/spelldat.cpp:29 -msgctxt "spell" -msgid "Chain Lightning" -msgstr "Cadena de Relámpagos" +#: Source/translation_dummy.cpp:234 +msgctxt "monster" +msgid "Gorefeast" +msgstr "Fiesta de Sangre" -#: Source/spelldat.cpp:30 -msgctxt "spell" -msgid "Flame Wave" -msgstr "Ola de Llamas" +#: Source/translation_dummy.cpp:235 +msgctxt "monster" +msgid "Graywar the Slayer" +msgstr "Graywar el Asesino" -#: Source/spelldat.cpp:31 -msgctxt "spell" -msgid "Doom Serpents" -msgstr "Serpientes de la Condenación" +#: Source/translation_dummy.cpp:236 +msgctxt "monster" +msgid "Dreadjudge" +msgstr "Juez Aterrador" -#: Source/spelldat.cpp:32 -msgctxt "spell" -msgid "Blood Ritual" -msgstr "Ritual de Sangre" +#: Source/translation_dummy.cpp:237 +msgctxt "monster" +msgid "Stareye the Witch" +msgstr "Stareye la Bruja" -#: Source/spelldat.cpp:33 -msgctxt "spell" -msgid "Nova" -msgstr "Nova" +#: Source/translation_dummy.cpp:238 +msgctxt "monster" +msgid "Steelskull the Hunter" +msgstr "Steelskull el Cazador" -#: Source/spelldat.cpp:34 -msgctxt "spell" -msgid "Invisibility" -msgstr "Invisibilidad" +#: Source/translation_dummy.cpp:239 +msgctxt "monster" +msgid "Sir Gorash" +msgstr "Sir Gorash" -#: Source/spelldat.cpp:35 -msgctxt "spell" -msgid "Inferno" -msgstr "Infierno" +#: Source/translation_dummy.cpp:240 +msgctxt "monster" +msgid "The Vizier" +msgstr "El Visir" -#: Source/spelldat.cpp:36 -msgctxt "spell" -msgid "Golem" -msgstr "Gólem" +#: Source/translation_dummy.cpp:241 +msgctxt "monster" +msgid "Zamphir" +msgstr "Zamphir" -#: Source/spelldat.cpp:37 -msgctxt "spell" -msgid "Rage" -msgstr "Furia" +#: Source/translation_dummy.cpp:242 +msgctxt "monster" +msgid "Bloodlust" +msgstr "Bloodlust" -#: Source/spelldat.cpp:38 -msgctxt "spell" -msgid "Teleport" -msgstr "Teletransporte" +#: Source/translation_dummy.cpp:243 +msgctxt "monster" +msgid "Webwidow" +msgstr "Webwidow" -#: Source/spelldat.cpp:39 -msgctxt "spell" -msgid "Apocalypse" -msgstr "Apocalipsis" +#: Source/translation_dummy.cpp:244 +msgctxt "monster" +msgid "Fleshdancer" +msgstr "Bailarín de la Carne" -#: Source/spelldat.cpp:40 -msgctxt "spell" -msgid "Etherealize" -msgstr "Etéreo" +#: Source/translation_dummy.cpp:245 +msgctxt "monster" +msgid "Grimspike" +msgstr "Pico Sombrío" -#: Source/spelldat.cpp:41 -msgctxt "spell" -msgid "Item Repair" -msgstr "Reparación de Artículo" +#: Source/translation_dummy.cpp:246 +msgctxt "monster" +msgid "Doomlock" +msgstr "Cerradura de la Condenación" -#: Source/spelldat.cpp:42 -msgctxt "spell" -msgid "Staff Recharge" -msgstr "Recarga de Bastón" +#: Source/translation_dummy.cpp:248 Source/translation_dummy.cpp:394 +msgid "Short Sword" +msgstr "Espada Corta" -#: Source/spelldat.cpp:43 -msgctxt "spell" -msgid "Trap Disarm" -msgstr "Desarmar Trampa" +#: Source/translation_dummy.cpp:249 Source/translation_dummy.cpp:341 +msgid "Buckler" +msgstr "Rodela" -#: Source/spelldat.cpp:44 -msgctxt "spell" -msgid "Elemental" -msgstr "Elemental" +#: Source/translation_dummy.cpp:250 Source/translation_dummy.cpp:435 +#: Source/translation_dummy.cpp:436 Source/translation_dummy.cpp:437 +msgid "Club" +msgstr "Porra" -#: Source/spelldat.cpp:45 -msgctxt "spell" -msgid "Charged Bolt" -msgstr "Rayo Cargado" +#: Source/translation_dummy.cpp:251 Source/translation_dummy.cpp:442 +msgid "Short Bow" +msgstr "Arco Corto" -#: Source/spelldat.cpp:46 -msgctxt "spell" -msgid "Holy Bolt" -msgstr "Rayo Santo" +#: Source/translation_dummy.cpp:252 +msgid "Short Staff of Mana" +msgstr "Bastón Corto de Maná" -#: Source/spelldat.cpp:47 -msgctxt "spell" -msgid "Resurrect" -msgstr "Resucitar" +#: Source/translation_dummy.cpp:253 +msgid "Cleaver" +msgstr "Cuchilla de carnicero" -#: Source/spelldat.cpp:48 -msgctxt "spell" -msgid "Telekinesis" -msgstr "Telequinesis" +#: Source/translation_dummy.cpp:254 Source/translation_dummy.cpp:491 +msgid "The Undead Crown" +msgstr "La Corona de los Muertos Vivientes" -#: Source/spelldat.cpp:49 -msgctxt "spell" -msgid "Heal Other" -msgstr "Sanar a Otros" +#: Source/translation_dummy.cpp:255 Source/translation_dummy.cpp:492 +msgid "Empyrean Band" +msgstr "Banda Empírea" -#: Source/spelldat.cpp:50 -msgctxt "spell" -msgid "Blood Star" -msgstr "Estrella de Sangre" +#: Source/translation_dummy.cpp:256 +msgid "Magic Rock" +msgstr "Roca Mágica" -#: Source/spelldat.cpp:51 -msgctxt "spell" -msgid "Bone Spirit" -msgstr "Espíritu de Hueso" +#: Source/translation_dummy.cpp:257 Source/translation_dummy.cpp:493 +msgid "Optic Amulet" +msgstr "Amuleto Óptico" -#: Source/spelldat.cpp:52 -msgctxt "spell" -msgid "Mana" -msgstr "Maná" +#: Source/translation_dummy.cpp:258 Source/translation_dummy.cpp:494 +msgid "Ring of Truth" +msgstr "Anillo de la Verdad" -#: Source/spelldat.cpp:53 -msgctxt "spell" -msgid "the Magi" -msgstr "los Magos" +#: Source/translation_dummy.cpp:259 +msgid "Tavern Sign" +msgstr "Cartel de la Taberna" -#: Source/spelldat.cpp:54 -msgctxt "spell" -msgid "the Jester" -msgstr "el Bufón" +#: Source/translation_dummy.cpp:260 Source/translation_dummy.cpp:495 +msgid "Harlequin Crest" +msgstr "Cresta del Arlequín" -#: Source/spelldat.cpp:55 -msgctxt "spell" -msgid "Lightning Wall" -msgstr "Pared de Relámpagos" +#: Source/translation_dummy.cpp:261 Source/translation_dummy.cpp:496 +msgid "Veil of Steel" +msgstr "Velo de Acero" -#: Source/spelldat.cpp:56 -msgctxt "spell" -msgid "Immolation" -msgstr "Inmolación" +#: Source/translation_dummy.cpp:262 +msgid "Golden Elixir" +msgstr "Elixir Dorado" -#: Source/spelldat.cpp:57 -msgctxt "spell" -msgid "Warp" -msgstr "Deformación" +#: Source/translation_dummy.cpp:265 +msgid "Brain" +msgstr "Cerebro" -#: Source/spelldat.cpp:58 -msgctxt "spell" -msgid "Reflect" -msgstr "Reflejar" +#: Source/translation_dummy.cpp:266 +msgid "Fungal Tome" +msgstr "Tomo de Hongos" -#: Source/spelldat.cpp:59 -msgctxt "spell" -msgid "Berserk" -msgstr "Berserk" +#: Source/translation_dummy.cpp:267 +msgid "Spectral Elixir" +msgstr "Elixir Espectral" -#: Source/spelldat.cpp:60 -msgctxt "spell" -msgid "Ring of Fire" -msgstr "Anillo de Fuego" +#: Source/translation_dummy.cpp:268 +msgid "Blood Stone" +msgstr "Piedra de Sangre" -#: Source/spelldat.cpp:61 -msgctxt "spell" -msgid "Search" -msgstr "Buscar" +#: Source/translation_dummy.cpp:269 +msgid "Cathedral Map" +msgstr "Mapa de la Catedral" -#: Source/spelldat.cpp:62 -msgctxt "spell" -msgid "Rune of Fire" -msgstr "Runa de Fuego" +#: Source/translation_dummy.cpp:270 +msgid "Heart" +msgstr "Corazón" -#: Source/spelldat.cpp:63 -msgctxt "spell" -msgid "Rune of Light" -msgstr "Runa de Luz" +#: Source/translation_dummy.cpp:271 Source/translation_dummy.cpp:353 +msgid "Potion of Healing" +msgstr "Poción Curativa" -#: Source/spelldat.cpp:64 -msgctxt "spell" -msgid "Rune of Nova" -msgstr "Runa de Nova" +#: Source/translation_dummy.cpp:272 Source/translation_dummy.cpp:355 +msgid "Potion of Mana" +msgstr "Poción de Maná" -#: Source/spelldat.cpp:65 -msgctxt "spell" -msgid "Rune of Immolation" -msgstr "Runa de Inmolación" +#: Source/translation_dummy.cpp:273 Source/translation_dummy.cpp:370 +msgid "Scroll of Identify" +msgstr "Pergamino de la Identidad" -#: Source/spelldat.cpp:66 -msgctxt "spell" -msgid "Rune of Stone" -msgstr "Runa de Piedra" +#: Source/translation_dummy.cpp:274 Source/translation_dummy.cpp:374 +msgid "Scroll of Town Portal" +msgstr "Pergamino del Portal de la Ciudad" -#: Source/stores.cpp:130 -msgid "Griswold" -msgstr "Griswold" +#: Source/translation_dummy.cpp:275 Source/translation_dummy.cpp:497 +msgid "Arkaine's Valor" +msgstr "Valor de Arkaine" -#: Source/stores.cpp:131 -msgid "Pepin" -msgstr "Pepin" +#: Source/translation_dummy.cpp:276 Source/translation_dummy.cpp:354 +msgid "Potion of Full Healing" +msgstr "Poción Curativa Completa" -#: Source/stores.cpp:133 -msgid "Ogden" -msgstr "Ogden" +#: Source/translation_dummy.cpp:277 Source/translation_dummy.cpp:356 +msgid "Potion of Full Mana" +msgstr "Poción de Maná Completa" -#: Source/stores.cpp:134 -msgid "Cain" -msgstr "Cain" +#: Source/translation_dummy.cpp:278 Source/translation_dummy.cpp:498 +msgid "Griswold's Edge" +msgstr "Hoja de Griswold" -#: Source/stores.cpp:135 -msgid "Farnham" -msgstr "Farnham" +#: Source/translation_dummy.cpp:279 Source/translation_dummy.cpp:499 +msgid "Bovine Plate" +msgstr "Armadura de Bovino" -#: Source/stores.cpp:136 -msgid "Adria" -msgstr "Adria" +#: Source/translation_dummy.cpp:280 +msgid "Staff of Lazarus" +msgstr "Bastón de Lazarus" -#: Source/stores.cpp:137 Source/stores.cpp:1349 -msgid "Gillian" -msgstr "Gillian" +#: Source/translation_dummy.cpp:281 Source/translation_dummy.cpp:371 +msgid "Scroll of Resurrect" +msgstr "Pergamino de Resurrección" -#: Source/stores.cpp:138 -msgid "Wirt" -msgstr "Wirt" +#: Source/translation_dummy.cpp:283 Source/translation_dummy.cpp:458 +msgid "Short Staff" +msgstr "Bastón Corto" -#: Source/stores.cpp:260 Source/stores.cpp:267 -msgid "Back" -msgstr "Atrás" +#: Source/translation_dummy.cpp:284 Source/translation_dummy.cpp:395 +#: Source/translation_dummy.cpp:397 Source/translation_dummy.cpp:399 +#: Source/translation_dummy.cpp:401 Source/translation_dummy.cpp:407 +#: Source/translation_dummy.cpp:409 Source/translation_dummy.cpp:411 +#: Source/translation_dummy.cpp:413 Source/translation_dummy.cpp:415 +msgid "Sword" +msgstr "Espada" -#: Source/stores.cpp:289 Source/stores.cpp:295 -msgid ", " -msgstr ", " +#: Source/translation_dummy.cpp:285 Source/translation_dummy.cpp:392 +#: Source/translation_dummy.cpp:393 +msgid "Dagger" +msgstr "Daga" -#: Source/stores.cpp:306 -msgid "Damage: {:d}-{:d} " -msgstr "Daño: {:d}-{:d} " +#: Source/translation_dummy.cpp:286 +msgid "Rune Bomb" +msgstr "Bomba Rúnica" -#: Source/stores.cpp:308 -msgid "Armor: {:d} " -msgstr "Defensa: {:d} " +#: Source/translation_dummy.cpp:287 +msgid "Theodore" +msgstr "Teodoro" -#: Source/stores.cpp:310 -msgid "Dur: {:d}/{:d}, " -msgstr "Dur: {:d}/{:d}, " +#: Source/translation_dummy.cpp:288 +msgid "Auric Amulet" +msgstr "Amuleto Áurico" -#: Source/stores.cpp:312 -msgid "Indestructible, " -msgstr "Indestructible, " +#: Source/translation_dummy.cpp:289 +msgid "Torn Note 1" +msgstr "Nota Rasgada 1" -#: Source/stores.cpp:320 -msgid "No required attributes" -msgstr "No requiere atributos" +#: Source/translation_dummy.cpp:290 +msgid "Torn Note 2" +msgstr "Nota Rasgada 2" -#: Source/stores.cpp:352 Source/stores.cpp:1102 Source/stores.cpp:1336 -msgid "Welcome to the" -msgstr "Bienvenido a la" +#: Source/translation_dummy.cpp:291 +msgid "Torn Note 3" +msgstr "Nota Rasgada 3" -#: Source/stores.cpp:353 -msgid "Blacksmith's shop" -msgstr "Herrería" +#: Source/translation_dummy.cpp:292 +msgid "Reconstructed Note" +msgstr "Nota Reconstruida" -#: Source/stores.cpp:354 Source/stores.cpp:708 Source/stores.cpp:1104 -#: Source/stores.cpp:1162 Source/stores.cpp:1338 Source/stores.cpp:1350 -#: Source/stores.cpp:1363 -msgid "Would you like to:" -msgstr "Te gustaría:" +#: Source/translation_dummy.cpp:293 +msgid "Brown Suit" +msgstr "Traje Marrón" -#: Source/stores.cpp:355 -msgid "Talk to Griswold" -msgstr "Hablar" +#: Source/translation_dummy.cpp:294 +msgid "Grey Suit" +msgstr "Traje Gris" -#: Source/stores.cpp:356 -msgid "Buy basic items" -msgstr "Comprar" +#: Source/translation_dummy.cpp:295 Source/translation_dummy.cpp:296 +#: Source/translation_dummy.cpp:298 +msgid "Cap" +msgstr "Gorro" -#: Source/stores.cpp:357 -msgid "Buy premium items" -msgstr "Comprar mercancía selecta" +#: Source/translation_dummy.cpp:297 +msgid "Skull Cap" +msgstr "Gorro Metálico" -#: Source/stores.cpp:358 Source/stores.cpp:711 -msgid "Sell items" -msgstr "Vender" +#: Source/translation_dummy.cpp:299 Source/translation_dummy.cpp:300 +#: Source/translation_dummy.cpp:302 Source/translation_dummy.cpp:306 +msgid "Helm" +msgstr "Yelmo" -#: Source/stores.cpp:359 -msgid "Repair items" -msgstr "Reparar" +#: Source/translation_dummy.cpp:301 +msgid "Full Helm" +msgstr "Yelmo Completo" -#: Source/stores.cpp:360 -msgid "Leave the shop" -msgstr "Dejar la herrería" +#: Source/translation_dummy.cpp:303 Source/translation_dummy.cpp:304 +msgid "Crown" +msgstr "Corona" -#: Source/stores.cpp:409 Source/stores.cpp:768 Source/stores.cpp:1139 -msgid "I have these items for sale:" -msgstr "Tengo esto a la venta:" +#: Source/translation_dummy.cpp:305 +msgid "Great Helm" +msgstr "Gran Yelmo" -#: Source/stores.cpp:474 -msgid "I have these premium items for sale:" -msgstr "Ésta es mi mercancía de primera calidad:" +#: Source/translation_dummy.cpp:307 Source/translation_dummy.cpp:308 +msgid "Cape" +msgstr "Capa" -#: Source/stores.cpp:593 Source/stores.cpp:861 -msgid "You have nothing I want." -msgstr "No tienes nada que me interese." +#: Source/translation_dummy.cpp:309 Source/translation_dummy.cpp:310 +msgid "Rags" +msgstr "Andrajo" -#: Source/stores.cpp:604 Source/stores.cpp:873 -msgid "Which item is for sale?" -msgstr "¿Qué tienes a la venta?" +#: Source/translation_dummy.cpp:311 Source/translation_dummy.cpp:312 +msgid "Cloak" +msgstr "Manto" -#: Source/stores.cpp:669 -msgid "You have nothing to repair." -msgstr "No tienes nada que reparar." +#: Source/translation_dummy.cpp:313 Source/translation_dummy.cpp:314 +msgid "Robe" +msgstr "Túnica" -#: Source/stores.cpp:680 -msgid "Repair which item?" -msgstr "¿Qué objeto quieres reparar?" +#: Source/translation_dummy.cpp:315 +msgid "Quilted Armor" +msgstr "Armadura Acolchada" -#: Source/stores.cpp:707 -msgid "Witch's shack" -msgstr "Choza de la Bruja" +#: Source/translation_dummy.cpp:317 +msgid "Leather Armor" +msgstr "Armadura de Cuero" -#: Source/stores.cpp:709 -msgid "Talk to Adria" -msgstr "Hablar" +#: Source/translation_dummy.cpp:319 +msgid "Hard Leather Armor" +msgstr "Armadura de Cuero Duro" -#: Source/stores.cpp:710 Source/stores.cpp:1106 -msgid "Buy items" -msgstr "Comprar" +#: Source/translation_dummy.cpp:321 +msgid "Studded Leather Armor" +msgstr "Cuero Tachonado" -#: Source/stores.cpp:712 -msgid "Recharge staves" -msgstr "Recargar bastones" +#: Source/translation_dummy.cpp:323 +msgid "Ring Mail" +msgstr "Cota de Anillos" -#: Source/stores.cpp:713 -msgid "Leave the shack" -msgstr "Dejar la choza" +#: Source/translation_dummy.cpp:324 Source/translation_dummy.cpp:326 +#: Source/translation_dummy.cpp:328 Source/translation_dummy.cpp:332 +msgid "Mail" +msgstr "Cota" -#: Source/stores.cpp:935 -msgid "You have nothing to recharge." -msgstr "No tienes nada que recargar." +#: Source/translation_dummy.cpp:325 +msgid "Chain Mail" +msgstr "Cota de Malla" -#: Source/stores.cpp:946 -msgid "Recharge which item?" -msgstr "¿Qué objeto quieres recargar?" +#: Source/translation_dummy.cpp:327 +msgid "Scale Mail" +msgstr "Cota de Escamas" -#: Source/stores.cpp:959 -msgid "You do not have enough gold" -msgstr "No tienes oro suficiente" +#: Source/translation_dummy.cpp:329 +msgid "Breast Plate" +msgstr "Coraza Blindada" -#: Source/stores.cpp:967 -msgid "You do not have enough room in inventory" -msgstr "No tienes espacio suficiente en el inventario" +#: Source/translation_dummy.cpp:330 Source/translation_dummy.cpp:334 +#: Source/translation_dummy.cpp:336 Source/translation_dummy.cpp:338 +#: Source/translation_dummy.cpp:340 +msgid "Plate" +msgstr "Coraza" -#: Source/stores.cpp:1004 -msgid "Do we have a deal?" -msgstr "¿Tenemos un trato?" +#: Source/translation_dummy.cpp:331 +msgid "Splint Mail" +msgstr "Cota de Láminas" -#: Source/stores.cpp:1007 -msgid "Are you sure you want to identify this item?" -msgstr "¿Seguro que quieres identificar esto?" +#: Source/translation_dummy.cpp:333 +msgid "Plate Mail" +msgstr "Cota de Placas" -#: Source/stores.cpp:1013 -msgid "Are you sure you want to buy this item?" -msgstr "¿Seguro que quieres comprar esto?" +#: Source/translation_dummy.cpp:335 +msgid "Field Plate" +msgstr "Coraza de Campaña" -#: Source/stores.cpp:1016 -msgid "Are you sure you want to recharge this item?" -msgstr "¿Seguro que quieres recargar esto?" +#: Source/translation_dummy.cpp:337 +msgid "Gothic Plate" +msgstr "Coraza Gótica" -#: Source/stores.cpp:1020 -msgid "Are you sure you want to sell this item?" -msgstr "¿Seguro que quieres vender esto?" +#: Source/translation_dummy.cpp:339 +msgid "Full Plate Mail" +msgstr "Cota de Placas Completa" -#: Source/stores.cpp:1023 -msgid "Are you sure you want to repair this item?" -msgstr "¿Seguro que quieres reparar esto?" +#: Source/translation_dummy.cpp:342 Source/translation_dummy.cpp:344 +#: Source/translation_dummy.cpp:346 Source/translation_dummy.cpp:348 +#: Source/translation_dummy.cpp:350 Source/translation_dummy.cpp:352 +msgid "Shield" +msgstr "Escudo" -#: Source/stores.cpp:1037 Source/towners.cpp:152 -msgid "Wirt the Peg-legged boy" -msgstr "Wirt el Patapalo" +#: Source/translation_dummy.cpp:343 +msgid "Small Shield" +msgstr "Escudo Pequeño" -#: Source/stores.cpp:1040 Source/stores.cpp:1047 -msgid "Talk to Wirt" -msgstr "Hablar" +#: Source/translation_dummy.cpp:345 +msgid "Large Shield" +msgstr "Escudo Grande" -#: Source/stores.cpp:1041 -msgid "I have something for sale," -msgstr "Tengo algo en venta," +#: Source/translation_dummy.cpp:347 +msgid "Kite Shield" +msgstr "Escudo de Vértices" -#: Source/stores.cpp:1042 -msgid "but it will cost 50 gold" -msgstr "te costará 50 de oro" +#: Source/translation_dummy.cpp:349 +msgid "Tower Shield" +msgstr "Escudo de la Torre" -#: Source/stores.cpp:1043 -msgid "just to take a look. " -msgstr "sólo echar un vistazo. " +#: Source/translation_dummy.cpp:351 +msgid "Gothic Shield" +msgstr "Escudo Gótico" -#: Source/stores.cpp:1044 -msgid "What have you got?" -msgstr "¿Qué es lo que tienes?" +#: Source/translation_dummy.cpp:357 +msgid "Potion of Rejuvenation" +msgstr "Poción Rejuvenecedora" -#: Source/stores.cpp:1045 Source/stores.cpp:1048 Source/stores.cpp:1165 -#: Source/stores.cpp:1353 -msgid "Say goodbye" -msgstr "Despedirte" +#: Source/translation_dummy.cpp:358 +msgid "Potion of Full Rejuvenation" +msgstr "Poción Rejuvenecedora Completa" -#: Source/stores.cpp:1058 -msgid "I have this item for sale:" -msgstr "Esto es lo que tengo:" +#: Source/translation_dummy.cpp:362 +msgid "Oil" +msgstr "Aceite" -#: Source/stores.cpp:1080 -msgid "Leave" -msgstr "Irse" +#: Source/translation_dummy.cpp:363 +msgid "Elixir of Strength" +msgstr "Elixir de Fuerza" -#: Source/stores.cpp:1103 -msgid "Healer's home" -msgstr "Botica" +#: Source/translation_dummy.cpp:364 +msgid "Elixir of Magic" +msgstr "Elixir de Magia" -#: Source/stores.cpp:1105 -msgid "Talk to Pepin" -msgstr "Hablar" +#: Source/translation_dummy.cpp:365 +msgid "Elixir of Dexterity" +msgstr "Elixir de Destreza" -#: Source/stores.cpp:1107 -msgid "Leave Healer's home" -msgstr "Dejar la Botica" +#: Source/translation_dummy.cpp:366 +msgid "Elixir of Vitality" +msgstr "Elixir de Vitalidad" -#: Source/stores.cpp:1161 -msgid "The Town Elder" -msgstr "El Sabio del Pueblo" +#: Source/translation_dummy.cpp:367 +msgid "Scroll of Healing" +msgstr "Pergamino de curación" -#: Source/stores.cpp:1163 -msgid "Talk to Cain" -msgstr "Hablar" +#: Source/translation_dummy.cpp:368 +msgid "Scroll of Search" +msgstr "Pergamino de Búsqueda" -#: Source/stores.cpp:1164 -msgid "Identify an item" -msgstr "Identificar objetos" +#: Source/translation_dummy.cpp:369 +msgid "Scroll of Lightning" +msgstr "Pergamino de Relámpago" -#: Source/stores.cpp:1257 -msgid "You have nothing to identify." -msgstr "No tienes nada que identificar." +#: Source/translation_dummy.cpp:372 +msgid "Scroll of Fire Wall" +msgstr "Pergamino de Muro de Fuego" -#: Source/stores.cpp:1268 -msgid "Identify which item?" -msgstr "¿Qué objeto quieres identificar?" +#: Source/translation_dummy.cpp:373 +msgid "Scroll of Inferno" +msgstr "Pergamino de Infierno" -#: Source/stores.cpp:1283 -msgid "This item is:" -msgstr "Este objeto es:" +#: Source/translation_dummy.cpp:375 +msgid "Scroll of Flash" +msgstr "Pergamino de Flash" -#: Source/stores.cpp:1286 -msgid "Done" -msgstr "Hecho" +#: Source/translation_dummy.cpp:376 +msgid "Scroll of Infravision" +msgstr "Pergamino de Infravisión" -#: Source/stores.cpp:1295 -msgid "Talk to {:s}" -msgstr "Habla con {:s}" +#: Source/translation_dummy.cpp:377 +msgid "Scroll of Phasing" +msgstr "Pergamino de Fase" -#: Source/stores.cpp:1298 -msgid "Talking to {:s}" -msgstr "Hablando con {:s}" +#: Source/translation_dummy.cpp:378 +msgid "Scroll of Mana Shield" +msgstr "Pergamino de Escudo de Maná" -#: Source/stores.cpp:1299 -msgid "is not available" -msgstr "no está disponible" +#: Source/translation_dummy.cpp:379 +msgid "Scroll of Flame Wave" +msgstr "Pergamino de Ola de Llamas" -#: Source/stores.cpp:1300 -msgid "in the shareware" -msgstr "en el shareware" +#: Source/translation_dummy.cpp:380 +msgid "Scroll of Fireball" +msgstr "Pergamino de Bola de Fuego" -#: Source/stores.cpp:1301 -msgid "version" -msgstr "versión" +#: Source/translation_dummy.cpp:381 +msgid "Scroll of Stone Curse" +msgstr "Pergamino de Maldición de Piedra" -#: Source/stores.cpp:1328 -msgid "Gossip" -msgstr "Chismorrear" +#: Source/translation_dummy.cpp:382 +msgid "Scroll of Chain Lightning" +msgstr "Pergamino de Cadena de Relámpagos" -#: Source/stores.cpp:1337 -msgid "Rising Sun" -msgstr "Taberna del Sol Naciente" +#: Source/translation_dummy.cpp:383 +msgid "Scroll of Guardian" +msgstr "Pergamino de Guardián" -#: Source/stores.cpp:1339 -msgid "Talk to Ogden" -msgstr "Hablar" +#: Source/translation_dummy.cpp:384 +msgid "Scroll of Nova" +msgstr "Pergamino de Nova" -#: Source/stores.cpp:1340 -msgid "Leave the tavern" -msgstr "Dejar la taberna" +#: Source/translation_dummy.cpp:385 +msgid "Scroll of Golem" +msgstr "Pergamino de Golem" -#: Source/stores.cpp:1351 -msgid "Talk to Gillian" -msgstr "Hablar" +#: Source/translation_dummy.cpp:386 +msgid "Scroll of Teleport" +msgstr "Pergamino de Teletransporte" -#: Source/stores.cpp:1352 -msgid "Access Storage" -msgstr "Examinar Alijo" +#: Source/translation_dummy.cpp:387 +msgid "Scroll of Apocalypse" +msgstr "Pergamino de Apocalipsis" -#: Source/stores.cpp:1362 Source/towners.cpp:207 -msgid "Farnham the Drunk" -msgstr "Farnham el Borracho" +#: Source/translation_dummy.cpp:388 Source/translation_dummy.cpp:389 +#: Source/translation_dummy.cpp:390 Source/translation_dummy.cpp:391 +msgid "Book of " +msgstr "Libro de " -#: Source/stores.cpp:1364 -msgid "Talk to Farnham" -msgstr "Hablar" +#: Source/translation_dummy.cpp:396 +msgid "Falchion" +msgstr "Chafarote" -#: Source/stores.cpp:1365 -msgid "Say Goodbye" -msgstr "Despedirte" +#: Source/translation_dummy.cpp:398 +msgid "Scimitar" +msgstr "Cimitarra" -#: Source/stores.cpp:2476 -msgid "Your gold: {:s}" -msgstr "Tu oro: {:s}" +#: Source/translation_dummy.cpp:400 +msgid "Claymore" +msgstr "Claymore" -#. 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 "" -" Ah, la historia de nuestro Rey, ¿verdad? La trágica caída de Leoric fue un duro " -"golpe para esta tierra. La gente siempre amó al Rey, y ahora vive con un miedo " -"mortal hacia él. La pregunta que me sigo haciendo es cómo pudo haber caído tan " -"lejos de la Luz, ya que Leoric siempre había sido el más santo de los hombres. " -"Sólo los poderes más viles del Infierno podrían destruir tan completamente a un " -"hombre desde dentro ..." +#: Source/translation_dummy.cpp:402 Source/translation_dummy.cpp:403 +msgid "Blade" +msgstr "Hoja" -#. 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 "" -"¡El pueblo necesita tu ayuda, buen maestro! Hace algunos meses, el hijo del rey " -"Leoric, el Príncipe Albrecht, fue secuestrado. El rey se enfureció y recorrió el " -"pueblo en busca de su hijo desaparecido. Con cada día que pasaba, Leoric parecía " -"hundirse cada vez más en la locura. Trató de culpar a los habitantes inocentes " -"de la desaparición del niño y los ejecutó brutalmente. Menos de la mitad de " -"nosotros sobrevivimos a su locura ...\n" -" \n" -"Los Caballeros y Sacerdotes del Rey intentaron aplacarlo, pero él se volvió " -"contra ellos y, lamentablemente, se vieron obligados a matarlo. Con su último " -"aliento, el Rey lanzó una terrible maldición sobre sus antiguos seguidores. Juró " -"que lo servirían en la oscuridad para siempre ...\n" -" \n" -"¡Aquí es donde las cosas toman un giro aún más oscuro de lo que creía posible! " -"Nuestro antiguo Rey se ha levantado de su sueño eterno y ahora comanda una " -"legión de esbirros de muertos vivientes dentro del Laberinto. Su cuerpo fue " -"enterrado en una tumba tres niveles debajo de la Catedral. Por favor, buen " -"maestro, tranquilice su alma destruyendo su forma ahora maldita ..." +#: Source/translation_dummy.cpp:404 Source/translation_dummy.cpp:405 +msgid "Sabre" +msgstr "Sable" -#. 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 "" -"Como le dije, buen maestro, el Rey fue sepultado tres niveles más abajo. Está " -"ahí abajo, esperando en la pútrida oscuridad su oportunidad de destruir esta " -"tierra ..." +#: Source/translation_dummy.cpp:406 +msgid "Long Sword" +msgstr "Espada Larga" -#. 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 "" -"La maldición de nuestro Rey ha terminado, pero me temo que fue solo una parte de " -"un mal mayor en acción. Sin embargo, aún podemos salvarnos de la oscuridad que " -"consume nuestra tierra, porque tu victoria es un buen augurio. Que la Luz te " -"guíe en tu camino, buen maestro." +#: Source/translation_dummy.cpp:408 +msgid "Broad Sword" +msgstr "Espada Ancha" -#. 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 "" -"La pérdida de su hijo fue demasiado para el Rey Leoric. Hice lo que pude para " -"aliviar su locura, pero al final lo superó. Una maldición negra se cierne sobre " -"este reino desde ese día, pero tal vez si liberaras su espíritu de su prisión " -"terrenal, la maldición se levantaría ..." +#: Source/translation_dummy.cpp:410 +msgid "Bastard Sword" +msgstr "Espada Bastarda" -#. 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 "" -"No me gusta pensar en cómo murió el Rey. Me gusta recordarlo como el gobernante " -"amable y justo que era. Su muerte fue tan triste y parecía muy mal, de alguna " -"manera." +#: Source/translation_dummy.cpp:412 +msgid "Two-Handed Sword" +msgstr "Mandoble" -#. 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 "" -"Hice muchas de las armas y la mayor parte de las armaduras que el rey Leoric usó " -"para equipar a sus caballeros. Incluso le elaboré una enorme espada a dos manos " -"del mejor mithril, así como una corona de campo a juego. Todavía no puedo creer " -"cómo murió ¡Pero debe haber sido alguna fuerza siniestra lo que lo volvió loco!" +#: Source/translation_dummy.cpp:414 +msgid "Great Sword" +msgstr "Gran Espada" -#. 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 "" -"Eso no me importa. Escucha, ningún esqueleto será MI rey. Leoric es el Rey. Rey, " -"¿me escuchas? ¡VIVA EL REY!" +#: Source/translation_dummy.cpp:416 +msgid "Small Axe" +msgstr "Hacha Pequeña" -#. 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 "" -"Los muertos que caminan entre los vivos siguen al Rey maldito. Tiene el poder de " -"crear aún más guerreros para un ejército de muertos vivientes en constante " -"crecimiento. Si no detienes su reinado, seguramente marchará a través de esta " -"tierra y matará a todos los que todavía viven aquí." +#: Source/translation_dummy.cpp:417 Source/translation_dummy.cpp:418 +#: Source/translation_dummy.cpp:419 Source/translation_dummy.cpp:421 +#: Source/translation_dummy.cpp:423 Source/translation_dummy.cpp:425 +#: Source/translation_dummy.cpp:427 +msgid "Axe" +msgstr "Hacha" -#. 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 "" -"Mira, tengo un negocio aquí. No vendo información, y no me importa un Rey que ha " -"estado muerto más tiempo que yo vivo. Si necesitas algo para usar contra este " -"Rey de los muertos vivientes, entonces puedo ayudarte ..." +#: Source/translation_dummy.cpp:420 +msgid "Large Axe" +msgstr "Hacha Grande" -#. 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 "" -"El calor de la vida ha entrado en mi tumba. ¡Prepárate, mortal, para servir a mi " -"Maestro por la eternidad!" +#: Source/translation_dummy.cpp:422 +msgid "Broad Axe" +msgstr "Hacha Ancha" -#. 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 "" -"Veo que este comportamiento extraño también te desconcierta. Supongo que, dado " -"que muchos demonios temen la luz del sol y creen que tienen un gran poder, es " -"posible que el sol naciente representado en el letrero del que hablas les haya " -"llevado a creer que también tiene algunos poderes arcanos. Mmm, quizás no todos " -"sean tan inteligentes como nos temíamos ..." +#: Source/translation_dummy.cpp:424 +msgid "Battle Axe" +msgstr "Hacha de Batalla" -#. 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 "" -"Maestro, tengo una experiencia extraña que contarle. Sé que tiene un gran " -"conocimiento de esas monstruosidades que habitan el laberinto, y esto es algo " -"que no puedo entender, por mi vida ... Me desperté durante la noche por un " -"sonido de rascado justo afuera de mi taberna. Cuando miré desde mi habitación, " -"vi las formas de pequeñas criaturas parecidas a demonios en el patio de la " -"posada. Poco tiempo después, se fueron corriendo, no sin antes robar el cartel " -"de mi posada. No sé por qué los demonios robarían mi cartel y dejarían a mi " -"familia en paz ... es extraño, ¿no?" +#: Source/translation_dummy.cpp:426 +msgid "Great Axe" +msgstr "Gran Hacha" -#. 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 "" -"Oh, no tenías que traer mi letrero, pero supongo que me ahorra el gasto de hacer " -"otro. Bueno, déjame ver, ¿qué puedo darte como tarifa por encontrarlo? Hmmm, qué " -"tenemos aquí ... ¡ah, sí! Este gorro fue dejado en una de las habitaciones por " -"un mago que se quedó aquí hace algún tiempo. Quizás pueda tener algún valor para " -"ti." +#: Source/translation_dummy.cpp:428 Source/translation_dummy.cpp:429 +#: Source/translation_dummy.cpp:431 +msgid "Mace" +msgstr "Maza" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Dios mío, los demonios que corren por la aldea de noche, saquean nuestras casas, " -"¿no es nada sagrado? Espero que Ogden y Garda estén bien. Supongo que vendrían a " -"verme si les hubieran hecho daño ..." +#: Source/translation_dummy.cpp:430 +msgid "Morning Star" +msgstr "Estrella del Alba" -#. 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 "" -"¡Oh Dios! ¿Es ahí donde fue el letrero? Mi Abuela y yo debimos haber dormido " -"todo el rato. Gracias a la Luz que esos monstruos no atacaron la posada." +#: Source/translation_dummy.cpp:432 +msgid "War Hammer" +msgstr "Martillo de Guerra" -#. 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 "" -"¿Dices que los demonios robaron el letrero de Ogden? Eso no se parece mucho a " -"las atrocidades de las que he oído, o visto. \n" -" \n" -"A los demonios les preocupa arrancarte el corazón, no tu letrero." +#: Source/translation_dummy.cpp:433 +msgid "Hammer" +msgstr "Martillo" -#. 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 "" -"¿Sabes lo que pienso? Alguien tomó ese letrero y querrán mucho dinero por él. Si " -"yo fuera Ogden ... y no lo soy, pero si fuera ... compraría un nuevo cartel con " -"un bonito dibujo. Quizás una buena jarra de cerveza o un trozo de queso ..." +#: Source/translation_dummy.cpp:434 +msgid "Spiked Club" +msgstr "Porra con puntas" -#. 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 "" -"Ningún mortal puede comprender verdaderamente la mente del demonio. \n" -" \n" -"Nunca dejes que sus acciones erráticas te confundan, ya que ese también puede " -"ser su plan." +#: Source/translation_dummy.cpp:438 Source/translation_dummy.cpp:439 +msgid "Flail" +msgstr "Rompecabezas" -#. 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 "" -"¿Qué? ¿Está diciendo que me llevé eso? Supongo que Griswold también está de su " -"lado. \n" -" \n" -"Mira, superé el simple robo de letreros hace meses. No puede obtener ganancias " -"con un trozo de madera." +#: Source/translation_dummy.cpp:440 Source/translation_dummy.cpp:441 +msgid "Maul" +msgstr "Almádena" -#. 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 "" -"¡Oye, tú eres el que mata a todos! ¡Consígueme el Estandarte Mágico o atacamos! " -"¡No dejes con vida! Matas a los grandes feos y devuelves la magia. Pasa la " -"esquina y la puerta, encuentra a los feos. ¡Das, te vas!" +#: Source/translation_dummy.cpp:443 Source/translation_dummy.cpp:445 +#: Source/translation_dummy.cpp:447 Source/translation_dummy.cpp:449 +#: Source/translation_dummy.cpp:451 Source/translation_dummy.cpp:453 +#: Source/translation_dummy.cpp:455 Source/translation_dummy.cpp:457 +msgid "Bow" +msgstr "Arco" -#. TRANSLATORS: Quest dialog spoken by Snotspill (Hostile) -#: Source/textdat.cpp:57 -msgid "You kill uglies, get banner. You bring to me, or else..." -msgstr "Matas a los feos, obtienes estandarte. Me lo traes, o si no ..." +#: Source/translation_dummy.cpp:444 +msgid "Hunter's Bow" +msgstr "Arco de Cazador" -#. 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 "" -"¡Das! ¡Si, bien! Vete ahora, somos fuertes. ¡Matamos a todos con gran Magia!" +#: Source/translation_dummy.cpp:446 +msgid "Long Bow" +msgstr "Arco Largo" -#. 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 "" -"Esto no augura nada bueno, ya que confirma mis temores más oscuros. Si bien no " -"me permití creer las leyendas antiguas, no puedo negarlas ahora. Quizás ha " -"llegado el momento de revelar quién soy.\n" -" \n" -"Mi verdadero nombre es Deckard Cain el Sabio, y soy el último descendiente de " -"una antigua Hermandad que se dedicó a salvaguardar los secretos de un mal " -"atemporal. Un mal que obviamente ahora se ha liberado.\n" -" \n" -"El arzobispo Lazarus, una vez el consejero más confiable del Rey Leoric, condujo " -"a un grupo de simples habitantes del pueblo al Laberinto para encontrar al hijo " -"desaparecido del Rey, Albrecht. Pasó bastante tiempo antes de que regresaran, y " -"solo unos pocos escaparon con vida.\n" -" \n" -"¡Maldito sea por tonto! Entonces debería haber sospechado su velada traición. " -"Debe haber sido el mismo Lazarus quien secuestró a Albrecht y desde entonces lo " -"ha escondido dentro del Laberinto. No entiendo por qué el Arzobispo se volvió " -"hacia la oscuridad, ni cuál es su interés en el niño. ¡a menos que tenga la " -"intención de sacrificarlo a sus amos oscuros!\n" -" \n" -"¡Eso debe ser lo que ha planeado! Los sobrevivientes de su 'grupo de rescate' " -"dicen que Lazarus fue visto por última vez corriendo hacia las entrañas más " -"profundas del laberinto. ¡Debes darte prisa y salvar al príncipe de la espada " -"sacrifical de este demonio demente!" +#: Source/translation_dummy.cpp:448 +msgid "Composite Bow" +msgstr "Arco Compuesto" -#. 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 "" -"Debes darte prisa y rescatar a Albrecht de las manos de Lazarus. ¡El príncipe y " -"la gente de este reino cuentan contigo!" +#: Source/translation_dummy.cpp:450 +msgid "Short Battle Bow" +msgstr "Arco Corto de Batalla" -#. 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 "" -"Tu historia es bastante sombría, amigo. Lazarus seguramente arderá en el " -"Infierno por su horrible acto. El chico que describe no es nuestro príncipe, " -"pero creo que Albrecht aún puede estar en peligro. El símbolo de poder del que " -"hablas debe ser un portal en el corazón mismo del laberinto.\n" -" \n" -"Debes saber esto, amigo mío: el mal contra el que te mueves es el oscuro Señor " -"del Terror. Es conocido por los hombres mortales como Diablo. Fue él quien fue " -"encarcelado dentro del Laberinto hace muchos siglos y me temo que busca sembrar " -"una vez más el caos en el reino de la humanidad. ¡Debes aventurarte a través del " -"portal y destruir a Diablo antes de que sea demasiado tarde!" +#: Source/translation_dummy.cpp:452 +msgid "Long Battle Bow" +msgstr "Arco Largo de Batalla" -#. 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 "" -"Lazarus fue el Arzobispo que condujo a muchos de los habitantes del pueblo al " -"laberinto. Ese día perdí muchos buenos amigos y Lazarus nunca regresó. Supongo " -"que lo mataron junto con la mayoría de los demás. Si quiere hacerme un favor, " -"buen maestro, por favor no hable con Farnham sobre ese día." +#: Source/translation_dummy.cpp:454 +msgid "Short War Bow" +msgstr "Arco Corto de Guerra" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Me sorprendió cuando me enteré de lo que la gente del pueblo planeaba hacer esa " -"noche. Pensé que, de todas las personas, Lazarus habría tenido más sentido " -"común. Era Arzobispo y siempre pareció preocuparse mucho por la gente del pueblo " -"de Tristram. Tantos resultaron heridos, no pude salvarlos a todos ..." +#: Source/translation_dummy.cpp:456 +msgid "Long War Bow" +msgstr "Arco Largo de Guerra" -#. 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 "" -"Recuerdo a Lazarus como un hombre muy amable y generoso. Habló en el funeral de " -"mi madre y nos apoyó a mi abuela y a mí en un momento muy difícil. Rezo todas " -"las noches para que, de alguna manera, todavía esté vivo y a salvo." +#: Source/translation_dummy.cpp:460 +msgid "Long Staff" +msgstr "Bastón Largo" -#. 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 "" -"Estaba allí cuando Lazarus nos condujo al laberinto. Habló de la santa " -"retribución, pero cuando empezamos a luchar contra esos engendros del infierno, " -"ni siquiera levantó su maza contra ellos. ¡Simplemente corrió más profundamente " -"en las oscuras e interminables cámaras que estaban llenas de los sirvientes de " -"la oscuridad!" +#: Source/translation_dummy.cpp:462 +msgid "Composite Staff" +msgstr "Bastón Plegable" -#. 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 "" -"Te apuñalan, luego muerden y luego te rodean. ¡Mentiroso! ¡MENTIROSO! ¡Están " -"todos muertos! ¡Muertos! ¿Me escuchas? Siguen cayendo y cayendo ... su sangre se " -"derrama por todo el suelo ... todo es culpa suya ..." +#: Source/translation_dummy.cpp:464 +msgid "Quarter Staff" +msgstr "Bastón de Mando" -#. 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 "" -"No conocía a este Lazarus de quien hablas, pero siento un gran conflicto dentro " -"de su ser. Representa un gran peligro y no se detendrá ante nada para servir a " -"los poderes de las tinieblas que lo han reclamado como suyo." +#: Source/translation_dummy.cpp:466 +msgid "War Staff" +msgstr "Bastón de Guerra" -#. 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 "" -"Sí, el justo Lazarus, que fue taaan eficaz contra esos monstruos de allí. No " -"ayudó a salvar mi pierna, ¿verdad? Mira, te daré un consejo gratis. Pregúntale a " -"Farnham, él estaba allí." +#: Source/translation_dummy.cpp:468 Source/translation_dummy.cpp:469 +#: Source/translation_dummy.cpp:470 Source/translation_dummy.cpp:471 +#: Source/translation_dummy.cpp:472 Source/translation_dummy.cpp:473 +msgid "Ring" +msgstr "Anillo" -#. 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 "" -"Abandona tu tonta búsqueda. ¡Todo lo que les espera es la ira de mi Maestro! Es " -"demasiado tarde para salvar al niño. ¡Ahora te unirás a él en el Infierno!" +#: Source/translation_dummy.cpp:474 Source/translation_dummy.cpp:475 +#: Source/translation_dummy.cpp:476 Source/translation_dummy.cpp:477 +msgid "Amulet" +msgstr "Amuleto" -#. 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 "" -"Hmm, no sé qué puedo decirte realmente que sea de alguna ayuda. El agua que " -"llena nuestros pozos proviene de un manantial subterráneo. He oído hablar de un " -"túnel que conduce a un gran lago - tal vez sean lo mismo. Desafortunadamente, no " -"sé qué pasaría si nuestro suministro de agua estuviera contaminado." +#: Source/translation_dummy.cpp:478 +msgid "Rune of Fire" +msgstr "Runa de Fuego" -#. 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 "" -"Siempre he tratado de mantener una gran cantidad de alimentos y bebidas en " -"nuestro sótano de almacenamiento, pero como todo el pueblo no tiene una fuente " -"de agua dulce, incluso nuestras tiendas se secarán pronto. \n" -" \n" -"Por favor, haz lo que puedas o no sé qué haremos." +#: Source/translation_dummy.cpp:479 Source/translation_dummy.cpp:481 +#: Source/translation_dummy.cpp:483 Source/translation_dummy.cpp:485 +#: Source/translation_dummy.cpp:487 +msgid "Rune" +msgstr "Runa" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"¡Me alegro de haberte encontrado a tiempo! Nuestros pozos se han vuelto " -"salobres, estancados y algunos de los habitantes del pueblo han enfermado al " -"beber de ellos. Nuestras reservas de agua dulce se están secando rápidamente. " -"Creo que hay un pasadizo que conduce a los manantiales que sirven a nuestro " -"pueblo. Por favor, averigua qué ha causado esta calamidad, o seguramente todos " -"moriremos." +#: Source/translation_dummy.cpp:480 +msgid "Rune of Lightning" +msgstr "Runa de Relámpago" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Por favor, debes darse prisa. Cada hora que pasa nos acerca a no tener agua para " -"beber. \n" -" \n" -"No podemos sobrevivir por mucho tiempo sin tu ayuda." +#: Source/translation_dummy.cpp:482 +msgid "Greater Rune of Fire" +msgstr "Gran Runa de Fuego" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"¿Qué dices? ¿La mera presencia de los demonios provocó que el agua se " -"contaminara? Oh, verdaderamente un gran mal acecha debajo de nuestro pueblo, " -"pero tu perseverancia y coraje nos dan esperanza. Por favor, toma este anillo, " -"tal vez te ayude a destruir a esas criaturas tan viles." +#: Source/translation_dummy.cpp:484 +msgid "Greater Rune of Lightning" +msgstr "Gran Runa de Relámpago" -#. 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 "" -"Mi abuela está muy débil y Garda dice que no podemos beber el agua de los pozos. " -"Por favor, ¿puedes hacer algo para ayudarnos?" +#: Source/translation_dummy.cpp:486 +msgid "Rune of Stone" +msgstr "Runa de Piedra" -#. 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 "" -"Pepin te ha dicho la verdad. Necesitaremos agua dulce con urgencia, y pronto. He " -"intentado limpiar uno de los pozos más pequeños, pero huele a suciedad " -"estancada. Debe estar obstruido en la fuente." +#: Source/translation_dummy.cpp:488 +msgid "Short Staff of Charged Bolt" +msgstr "Bastón Corto de la Centella" -#. TRANSLATORS: Quest dialog spoken by Farnham -#: Source/textdat.cpp:100 -msgid "You drink water?" -msgstr "¿Tu bebes agua?" +#: Source/translation_dummy.cpp:489 +msgid "Arena Potion" +msgstr "Poción de arena" -#. 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 "" -"La gente de Tristram morirá si no puedes devolver agua fresca a sus pozos. \n" -" \n" -"Sepa esto: los demonios están en el centro de este asunto, pero siguen ignorando " -"lo que han engendrado." +#: Source/translation_dummy.cpp:490 +msgid "The Butcher's Cleaver" +msgstr "La Cuchilla del Carnicero" + +#: Source/translation_dummy.cpp:500 +msgid "The Rift Bow" +msgstr "El Arco de la Desaveniencia" -#. 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 "" -"Por una vez, estoy contigo. Mi negocio se seca, por así decirlo, si no tengo un " -"mercado al que vender. ¡Será mejor que averigüe lo que está pasando, y pronto!" +#: Source/translation_dummy.cpp:501 +msgid "The Needler" +msgstr "El Insoportable" -#. 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 "" -"¿Un libro que habla de una cámara de huesos humanos? Bueno, una Cámara de Hueso " -"se menciona en ciertos escritos arcaicos que estudié en las bibliotecas del " -"Este. Estos tomos inferían que cuando los Señores del inframundo desearan " -"proteger grandes tesoros, crearían dominios donde aquellos que murieran en el " -"intento de robar ese tesoro estarían obligados a defenderlo para siempre. ¿Un " -"final retorcido, pero extrañamente apropiado?" +#: Source/translation_dummy.cpp:502 +msgid "The Celestial Bow" +msgstr "El Arco Celestial" -#. 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 "" -"Me temo que no sé nada de eso, buen maestro. Caín tiene muchos libros que pueden " -"ser de alguna ayuda." +#: Source/translation_dummy.cpp:503 +msgid "Deadly Hunter" +msgstr "Cazador Mortal" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: Source/textdat.cpp:109 -msgid "" -"This sounds like a very dangerous place. If you venture there, please take great " -"care." -msgstr "Parece un lugar muy peligroso. Si te aventuras allí ten mucho cuidado." +#: Source/translation_dummy.cpp:504 +msgid "Bow of the Dead" +msgstr "Arco de la Muerte" -#. 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 "" -"Me temo que no he escuchado nada al respecto. Quizás Caín el Narrador podría ser " -"de alguna ayuda." +#: Source/translation_dummy.cpp:505 +msgid "The Blackoak Bow" +msgstr "El Arco de Roble negro" -#. 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 "" -"No sé nada de este lugar, pero puedes intentar preguntarle a Caín. Habla de " -"muchas cosas y no me sorprendería que tuviera algunas respuestas a tu pregunta." +#: Source/translation_dummy.cpp:506 +msgid "Flamedart" +msgstr "Dardo de Fuego" -#. 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 "" -"Bien, escucha. Ahí está esta cámara de madera ¿ves? Y su esposa, ya sabes, ella, " -"le dice al árbol ... porque tienes que esperar. Entonces digo, eso podría " -"funcionar en su contra, pero si crees que voy a PAGAR por esto ... tú ... eh ... " -"sí." +#: Source/translation_dummy.cpp:507 +msgid "Fleshstinger" +msgstr "Aguijoneador de Carne" -#. 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 "" -"Te convertirás en un sirviente eterno de los señores oscuros si pereces dentro " -"de este dominio maldito. \n" -" \n" -"Ingresa a la Cámara de Hueso bajo tu propio riesgo." +#: Source/translation_dummy.cpp:508 +msgid "Windforce" +msgstr "Fuerza del Viento" -#. 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 "" -"¿Un tesoro vasto y misterioso, dices? Tal vez podría estar interesado en recoger " -"algunas cosas de ti ... o mejor aún, ¿no necesita algunos suministros raros y " -"costosos para superar este calvario?" +#: Source/translation_dummy.cpp:509 +msgid "Eaglehorn" +msgstr "Cuerno de Águila" -#. 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 "" -"Parece que el Arzobispo Lazarus incitó a muchos de los habitantes del pueblo a " -"aventurarse en el Laberinto para encontrar al hijo desaparecido del Rey. Jugó " -"con sus miedos y los convirtió en una multitud frenética. Ninguno de ellos " -"estaba preparado para lo que había dentro de la tierra fría ... Lazarus los " -"abandonó allí - dejados en las garras de indescriptibles horrores - para morir." +#: Source/translation_dummy.cpp:510 +msgid "Gonnagal's Dirk" +msgstr "Puñal de Gonnagal" -#. 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 "" -"Sí, Farnham ha murmurado algo sobre un enorme bruto que empuñaba un arma feroz. " -"Creo que lo llamó carnicero." +#: Source/translation_dummy.cpp:511 +msgid "The Defender" +msgstr "El Defensor" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Por la Luz, conozco a este vil demonio. Hubo muchos que llevaron las cicatrices " -"de su ira en sus cuerpos cuando los pocos supervivientes de la carga encabezada " -"por Lazarus salieron arrastrándose de la Catedral. No sé qué utilizó para cortar " -"a sus víctimas, pero no pudo haber sido de este mundo. Dejó heridas infectadas " -"por la enfermedad e incluso yo las encontré casi imposibles de tratar. Cuidado " -"si planeas luchar contra este demonio ..." +#: Source/translation_dummy.cpp:512 +msgid "Gryphon's Claw" +msgstr "Garra del Grifo" -#. 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 "" -"Cuando Farnham dijo algo sobre un carnicero matando gente, inmediatamente lo " -"descarté. Pero ya que lo mencionaste, tal vez sea cierto." +#: Source/translation_dummy.cpp:513 +msgid "Black Razor" +msgstr "Navaja Negra" -#. 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 "" -"Vi lo que Farnham llama el Carnicero mientras se abría camino a través de los " -"cuerpos de mis amigos. Blandió una cuchilla del tamaño de un hacha, cortando " -"miembros y cortando a hombres valientes donde estaban. Me separaron de la " -"refriega una multitud de pequeños demonios que chillaban y, de alguna manera, " -"encontré la escalera que conducía hacia afuera. Nunca volví a ver a esa horrible " -"bestia, pero su rostro manchado de sangre me persigue hasta el día de hoy." +#: Source/translation_dummy.cpp:514 +msgid "Gibbous Moon" +msgstr "Luna Gibosa" -#. 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 "" -"¡Grande! Una cuchilla grande matando a todos mis amigos. No pude detenerlo, tuve " -"que huir, no pude salvarlos. Atrapado en una habitación con tantos cuerpos ... " -"tantos amigos ... ¡NOOOOOOOOOO!" +#: Source/translation_dummy.cpp:515 +msgid "Ice Shank" +msgstr "Mango de Hielo" -#. 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 "" -"El Carnicero es una criatura sádica que se deleita con la tortura y el dolor de " -"los demás. Has visto su obra en el borracho Farnham. Su destrucción hará mucho " -"para garantizar la seguridad de esta aldea." +#: Source/translation_dummy.cpp:516 +msgid "The Executioner's Blade" +msgstr "La Espada del Verdugo" -#. 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 "" -"Sé más de lo que piensas sobre ese demonio espantoso. Sus amiguitos me agarraron " -"y lograron sacarme la pierna antes de que Griswold me sacara de ese agujero. \n" -" \n" -"Lo diré sin rodeos: mátalo antes de que te mate y agregue tu cadáver a su " -"colección." +#: Source/translation_dummy.cpp:517 +msgid "The Bonesaw" +msgstr "La Sierra de Hueso" -#. 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 "" -"Por favor escúchame. El arzobispo Lazarus, nos llevó hasta aquí para encontrar " -"al príncipe perdido. ¡El bastardo nos llevó a una trampa! Ahora todo el mundo " -"está muerto ... asesinados por un demonio que llamó el Carnicero. ¡Vénganos! " -"Encuentra a este Carnicero y mátalo para que nuestras almas finalmente " -"descansen ..." +#: Source/translation_dummy.cpp:518 +msgid "Shadowhawk" +msgstr "Halcón de las Sombras" -#. 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 "" -"Recitas una rima interesante escrita en un estilo que me recuerda a otras obras. " -"Déjame pensar ahora, ¿qué fue?\n" -" \n" -"... La oscuridad envuelve lo Oculto. Ojos que brillan sin ser vistos con solo el " -"sonido de las garras como navajas raspando brevemente para atormentar a esas " -"pobres almas que han quedado ciegas por toda la eternidad. La prisión para los " -"condenados se llama las Cámaras de los Ciegos ..." +#: Source/translation_dummy.cpp:519 +msgid "Wizardspike" +msgstr "Pico de Mago" -#. 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 "" -"Nunca me interesó mucho la poesía. De vez en cuando, tenía motivos para " -"contratar juglares cuando la posada iba bien, pero eso parece que fue hace mucho " -"tiempo. \n" -" \n" -"¿Qué? Oh, sí ... eh, bueno, supongo que podrías ver lo que alguien más sabe." +#: Source/translation_dummy.cpp:520 +msgid "Lightsabre" +msgstr "Sable de Luz" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Esto parece familiar, de alguna manera. Me parece recordar haber leído algo muy " -"parecido a ese poema mientras investigaba la historia de las aflicciones " -"demoníacas. Hablaba de un lugar de gran maldad que ... espera, no vas a ir allí, " -"¿verdad?" +#: Source/translation_dummy.cpp:521 +msgid "The Falcon's Talon" +msgstr "La Garra del Halcón" -#. 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 "" -"Si tienes preguntas sobre la ceguera, debes hablar con Pepin. Sé que le dio a mi " -"abuela una poción que ayudó a aclarar su visión, así que tal vez él también " -"pueda ayudarte." +#: Source/translation_dummy.cpp:522 +msgid "Inferno" +msgstr "Infierno" -#. 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 "" -"Me temo que no he escuchado ni visto un lugar que coincida con su vívida " -"descripción, amigo mío. Quizás Caín el Narrador podría ser de alguna ayuda." +#: Source/translation_dummy.cpp:523 +msgid "Doombringer" +msgstr "Portador de Destrucción" -#. TRANSLATORS: Quest dialog spoken by Farnham -#: Source/textdat.cpp:150 -msgid "Look here... that's pretty funny, huh? Get it? Blind - look here?" -msgstr "Mira aquí ... eso es muy gracioso, ¿eh? ¿Conseguirlo? Ciego, ¿mira aquí?" +#: Source/translation_dummy.cpp:524 +msgid "The Grizzly" +msgstr "El Grizzly" -#. 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 "" -"Este es un lugar de gran angustia y terror, y por eso sirve bien a su amo. \n" -" \n" -"Pisa con cuidado o puede que tu mismo te quedes mucho más tiempo de lo que " -"habías anticipado." +#: Source/translation_dummy.cpp:525 +msgid "The Grandfather" +msgstr "El Abuelo" -#. 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 "" -"Veamos, ¿te estoy vendiendo algo? No. ¿Me estás dando dinero para contarte esto? " -"No. ¿Ahora te vas y vas a hablar con el narrador que vive para este tipo de " -"cosas? Si." +#: Source/translation_dummy.cpp:526 +msgid "The Mangler" +msgstr "El Destrozador" -#. 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 "" -"¿Afirmas haber hablado con Lachdanan? Fue un gran héroe durante su vida. " -"Lachdanan fue un hombre honorable y justo que sirvió fielmente a su Rey durante " -"años. Pero claro, eso ya lo sabes.\n" -" \n" -"De aquellos que quedaron atrapados en las garras de la Maldición del Rey, " -"Lachdanan sería el menos propenso a someterse a la oscuridad sin luchar, así que " -"supongo que tu historia podría ser cierta. Si estuviera en tu lugar, amigo mío, " -"encontraría la manera de liberarlo de su tortura." +#: Source/translation_dummy.cpp:527 +msgid "Sharp Beak" +msgstr "Pico Afilado" -#. 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 "" -"¡Hablas de un valiente guerrero muerto hace mucho tiempo! No diré nada de hablar " -"con los difuntos en el patio de mi posada, muchas gracias." +#: Source/translation_dummy.cpp:528 +msgid "BloodSlayer" +msgstr "Asesino de Sangre" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Un elixir dorado, dices. Nunca antes había preparado una poción de ese color, " -"así que no puedo decirte cómo te afectaría si intentaras beberla. Como su " -"sanador, le recomiendo encarecidamente que, si encuentra un elixir de este tipo, " -"haga lo que le pide Lachdanan y NO trate de usarlo." +#: Source/translation_dummy.cpp:529 +msgid "The Celestial Axe" +msgstr "El Hacha Celestial" -#. 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 "" -"Nunca antes había oído hablar de un Lachdanan. Lo siento, pero no creo que pueda " -"ser de mucha ayuda." +#: Source/translation_dummy.cpp:530 +msgid "Wicked Axe" +msgstr "Hacha Malvada" -#. 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 "" -"Si realmente es Lachdanan a quien encontraste, te aconsejo que lo ayudes. Traté " -"con él en varias ocasiones y descubrí que era honesto y leal por naturaleza. La " -"maldición que cayó sobre los seguidores del Rey Leoric caería especialmente " -"sobre él." +#: Source/translation_dummy.cpp:531 +msgid "Stonecleaver" +msgstr "Cuchilla de Piedra" -#. 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 "" -" Lachdanan está muerto. Todo el mundo lo sabe, y no puedes engañarme para que " -"piense de otra manera. No puedes hablar con los muertos. ¡Lo sé!" +#: Source/translation_dummy.cpp:532 +msgid "Aguinara's Hatchet" +msgstr "Destral de Aguinara" -#. 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 "" -"Es posible que encuentres a personas atrapadas dentro del Laberinto, como " -"Lachdanan. \n" -" \n" -"Siento en él honor y una gran culpa. Ayúdale y ayudarás a todo Tristram." +#: Source/translation_dummy.cpp:533 +msgid "Hellslayer" +msgstr "Asesino del Infierno" + +#: Source/translation_dummy.cpp:534 +msgid "Messerschmidt's Reaver" +msgstr "Atracador de Messerschmidt" -#. 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 "" -"Espera, déjame adivinar. Cain fue tragado por una gigantesca fisura que se abrió " -"debajo de él. Fue incinerado en una bola de fuego del infierno y ya no puede " -"responder a tus preguntas. Oh, ¿no es eso lo que pasó? Entonces supongo que " -"comprarás algo o seguirás tu camino." +#: Source/translation_dummy.cpp:535 +msgid "Crackrust" +msgstr "Crackrust" -#. 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 "" -"Por favor, no me mates, solo escúchame. Una vez fui Capitán de los Caballeros " -"del Rey Leoric, defendiendo las leyes de esta tierra con justicia y honor. " -"Entonces su oscura Maldición cayó sobre nosotros por el papel que jugamos en su " -"trágica muerte. Mientras mis compañeros Caballeros sucumbían a su retorcido " -"destino, huí de la cámara funeraria del Rey, buscando alguna forma de liberarme " -"de la Maldición. Fallé...\n" -" \n" -"He oído hablar de un Elixir Dorado que podría levantar la Maldición y permitir " -"que mi alma descanse, pero no he podido encontrarlo. Mi fuerza ahora se " -"desvanece, y con ella también lo último de mi humanidad. Por favor ayúdame y " -"encuentra el Elixir. Te pagaré tus esfuerzos, lo juro por mi honor." +#: Source/translation_dummy.cpp:536 +msgid "Hammer of Jholm" +msgstr "Martillo de Jholm" -#. 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 "" -"No has encontrado el Elixir Dorado. Temo estar condenado por la eternidad. Por " -"favor, sigue intentándolo ..." +#: Source/translation_dummy.cpp:537 +msgid "Civerb's Cudgel" +msgstr "Garrote de Civerb" -#. 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 "" -"Has salvado mi alma de la condenación, y por eso estoy en deuda contigo. Si " -"alguna vez hay una forma de recompensarte desde más allá de la tumba, la " -"encontraré, pero por ahora, toma mi yelmo. En el viaje que estoy a punto de " -"emprender, lo utilizaré poco. Que te proteja contra los poderes oscuros de " -"abajo. Ve con la Luz, amigo mío ..." +#: Source/translation_dummy.cpp:538 +msgid "The Celestial Star" +msgstr "La Estrella Celestial" -#. 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 "" -"Griswold habla de El yunque de la furia, un artefacto legendario buscado durante " -"mucho tiempo, pero nunca encontrado. Elaborado a partir de los huesos metálicos " -"de los demonios del Pozo de la Navaja, el Yunque de la Furia se fundió alrededor " -"de los cráneos de los cinco magos más poderosos del inframundo. Tallado con " -"runas de poder y caos, cualquier arma o armadura forjada en este Yunque se " -"sumergirá en el reino del Caos, dándole propiedades mágicas. Se dice que la " -"naturaleza impredecible del Caos dificulta saber cuál será el resultado de esta " -"herrería ..." +#: Source/translation_dummy.cpp:539 +msgid "Baranar's Star" +msgstr "Estrella de Baranar" -#. 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 "" -"¿No crees que Griswold sería una mejor persona para preguntarle sobre esto? Es " -"bastante hábil, ¿sabes?." +#: Source/translation_dummy.cpp:540 +msgid "Gnarled Root" +msgstr "Raíz Nudosa" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Si hubiera estado buscando información sobre el Mortero de Curación o el Cáliz " -"de Plata de la Purificación, podría haberte ayudado, amigo mío. Sin embargo, en " -"este asunto, sería mejor que hablaras con Griswold o Caín." +#: Source/translation_dummy.cpp:541 +msgid "The Cranium Basher" +msgstr "El Aplastador de Cráneo" -#. 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 "" -"El padre de Griswold solía contarnos a algunos de nosotros, cuando éramos " -"pequeños, acerca de un yunque gigante que se usaba para fabricar armas " -"poderosas. Dijo que cuando se golpeaba un martillo en este yunque, el suelo " -"temblaba con gran furia. Cada vez que la tierra se mueve, siempre recuerdo esa " -"historia." +#: Source/translation_dummy.cpp:542 +msgid "Schaefer's Hammer" +msgstr "Martillo de Schaefer" -#. 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 "" -"¡Saludos! ¡Siempre es un placer ver a uno de mis mejores clientes! Sé que te has " -"estado aventurando más profundamente en el Laberinto, y hay una historia que me " -"contaron que puede que valga la pena escucharla ...\n" -" \n" -"Uno de los hombres que regresó del Laberinto me habló de un yunque místico que " -"encontró durante su fuga. Su descripción me recordó las leyendas que había " -"escuchado en mi juventud sobre la ardiente Forja Infernal donde se fabrican " -"poderosas armas mágicas. ¡La leyenda decía que en lo profundo de la Forja " -"Infernal descansaba el Yunque de la Furia! Este Yunque contenía la esencia misma " -"del inframundo demoníaco ...\n" -" \n" -"Se dice que cualquier arma fabricada sobre el Yunque en llamas está imbuida de " -"un gran poder. Si este yunque es de hecho el Yunque de la Furia, ¡podría crearte " -"en un arma capaz de derrotar incluso al señor más oscuro del infierno! \n" -" \n" -"¡Encuentra el Yunque para mí y me pondré manos a la obra!" +#: Source/translation_dummy.cpp:543 +msgid "Dreamflange" +msgstr "Brida de Ensueño" -#. 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 "" -"Nada todavía, ¿eh? Bueno, sigue buscando. Un arma forjada en el Yunque podría " -"ser tu mejor esperanza, y estoy seguro de que puedo crearte una de proporciones " -"legendarias." +#: Source/translation_dummy.cpp:544 +msgid "Staff of Shadows" +msgstr "Bastón de las Sombras" -#. 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 "" -"¡Casi no puedo creerlo! Este es el Yunque de la Furia. Buen trabajo, amigo. " -"¡Ahora les mostraremos a esos bastardos que no hay armas en el infierno más " -"mortíferas que las fabricadas por los hombres! Toma esto y que la Luz te proteja." +#: Source/translation_dummy.cpp:545 +msgid "Immolator" +msgstr "Inmolador" -#. 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 "" -"Griswold no puede vender su yunque. ¿Qué hará entonces? ¡Y también me enojaría " -"si alguien me quitara el yunque!" +#: Source/translation_dummy.cpp:546 +msgid "Storm Spire" +msgstr "Aguja de la Tormenta" -#. 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 "" -"Hay muchos artefactos dentro del Laberinto que tienen poderes más allá de la " -"comprensión de los mortales. Algunos de estos tienen un poder fantástico que " -"puede ser utilizado tanto por la Luz como por la Oscuridad. Asegurar el Yunque " -"desde abajo podría cambiar el curso de la Guerra del Pecado hacia la Luz." +#: Source/translation_dummy.cpp:547 +msgid "Gleamsong" +msgstr "Canción de Brillo" -#. 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 "" -"Si encuentras este artefacto para Griswold, podrías poner en serios problemas a " -"mi negocio. Awwww, nunca lo encontrarás." +#: Source/translation_dummy.cpp:548 +msgid "Thundercall" +msgstr "Llamada de Trueno" -#. 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 "" -"La Puerta de la Sangre y las Cámaras del Fuego son hitos de origen místico. " -"Dondequiera que resida este libro que lea, seguramente es un lugar de gran " -"poder.\n" -" \n" -"Las leyendas hablan de un pedestal que está tallado en piedra de obsidiana y " -"tiene un charco de sangre hirviendo sobre su superficie incrustada de huesos. " -"También hay alusiones a unas Piedras de sangre que abrirán una puerta que guarda " -"un antiguo tesoro ...\n" -" \n" -"La naturaleza de este tesoro está envuelta en especulaciones, amigo mío, pero se " -"dice que el antiguo héroe Arkaine colocó la armadura sagrada Valor en una bóveda " -"secreta. Arkaine fue el primer mortal en cambiar el rumbo de la Guerra del " -"Pecado y perseguir a las legiones de la oscuridad de regreso a los Infiernos " -"Ardientes.\n" -" \n" -"Justo antes de que Arkaine muriera, su armadura estaba escondida en una bóveda " -"secreta. Se dice que cuando se necesite de nuevo esta armadura sagrada, un héroe " -"se levantará para don Valor una vez más. Quizás eres ese héroe ..." +#: Source/translation_dummy.cpp:549 +msgid "The Protector" +msgstr "El Protector" -#. 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 "" -"Todos los niños escuchan la historia del guerrero Arkaine y su armadura mística " -"conocida como Valor. Si pudieras encontrar su lugar de descanso, estarías bien " -"protegido contra el mal en el Laberinto." +#: Source/translation_dummy.cpp:550 +msgid "Naj's Puzzler" +msgstr "Rompecabezas de Naj" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Hmm ... suena como algo que debería recordar, pero he estado tan ocupado " -"aprendiendo nuevas curas y creando mejores elixires que debí haberlo olvidado. " -"Lo siento ..." +#: Source/translation_dummy.cpp:551 +msgid "Mindcry" +msgstr "Mindcry" -#. 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 "" -"La historia de la armadura mágica llamada Valor es algo de lo que a menudo " -"escuché decir a los chicos. Será mejor que pregunte a uno de los hombres del " -"pueblo." +#: Source/translation_dummy.cpp:552 +msgid "Rod of Onan" +msgstr "Vara de Onan" -#. 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 "" -"La armadura conocida como Valor podría ser la que incline la balanza a tu favor. " -"Les diré que muchos lo han buscado, incluyéndome a mí. Arkaine lo escondió bien, " -"amigo mío, y se necesitará más que un poco de suerte para descubrir los secretos " -"que lo han mantenido oculto, oh, he aquí hace muchos años." +#: Source/translation_dummy.cpp:553 +msgid "Helm of Spirits" +msgstr "Yelmo de los Espíritus" -#. TRANSLATORS: Quest dialog "spoken" by Farnham -#: Source/textdat.cpp:208 -msgid "Zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz..." -msgstr "Zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz ..." +#: Source/translation_dummy.cpp:554 +msgid "Thinking Cap" +msgstr "Gorro del Pensamiento" -#. 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 "" -"Si encuentras estas Piedras de Sangre, úsalas con cuidado. \n" -" \n" -"El camino está plagado de peligros y tu única esperanza reside en la confianza " -"en ti mismo." +#: Source/translation_dummy.cpp:555 +msgid "OverLord's Helm" +msgstr "Yelmo del Señor Supremo" -#. 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 "" -"¿Tienes la intención de encontrar la armadura conocida como Valor? \n" -" \n" -"Nadie ha descubierto nunca dónde escondió Arkaine las cosas, y si mis contactos " -"no pudieron encontrarlo, dudo seriamente que tú tampoco lo hagas." +#: Source/translation_dummy.cpp:556 +msgid "Fool's Crest" +msgstr "Cresta del Tonto" -#. 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 "" -"Solo conozco una leyenda que habla de un guerrero como el que usted describe. Su " -"historia se encuentra dentro de las antiguas crónicas de la Guerra del " -"Pecado ...\n" -" \n" -"Manchado por mil años de guerra, sangre y muerte, el Señor de la Guerra de la " -"Sangre se alza sobre una montaña de sus destrozadas víctimas. Su espada oscura " -"grita una maldición negra a los vivos; una torturada invitación a cualquiera que " -"se presente ante este Verdugo del Infierno.\n" -" \n" -"También está escrito que, aunque una vez fue un mortal que luchó junto a la " -"Legión de la Oscuridad durante la Guerra del Pecado, perdió su humanidad debido " -"a su insaciable hambre de sangre." +#: Source/translation_dummy.cpp:557 +msgid "Gotterdamerung" +msgstr "El Ocaso de los Dioses" -#. 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 "" -"Me temo que no he oído nada sobre un guerrero tan despiadado, buen maestro. " -"Espero que no tengas que pelear con él, porque parece extremadamente peligroso." +#: Source/translation_dummy.cpp:558 +msgid "Royal Circlet" +msgstr "Aro Real" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Cain podría contarte mucho más sobre algo como esto de lo que yo jamás desearía " -"saber." +#: Source/translation_dummy.cpp:559 +msgid "Torn Flesh of Souls" +msgstr "Carne de Almas Desgarradas" -#. 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 "" -"Si vas a luchar contra un oponente tan feroz, que la Luz sea tu guía y tu " -"defensora. Te llevaré en mis pensamientos." +#: Source/translation_dummy.cpp:560 +msgid "The Gladiator's Bane" +msgstr "El Flagelo del Gladiador" -#. 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 "" -"Leyendas oscuras y malvadas rodean al único Señor de la Guerra de la Sangre. " -"Debes estar bien preparado, amigo mío, porque no muestra piedad ni cuartel." +#: Source/translation_dummy.cpp:561 +msgid "The Rainbow Cloak" +msgstr "La Capa Arcoíris" -#. 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 "" -"¿Siempre tienes que hablar de Sangre? ¿Qué pasa con las flores, el sol y esa " -"chica bonita que trae las bebidas? Escucha, amigo, eres obsesivo, ¿lo sabías?" +#: Source/translation_dummy.cpp:562 +msgid "Leather of Aut" +msgstr "Armadura de Cuero" -#. 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 "" -"Su destreza con la espada es asombrosa, y ha vivido durante miles de años " -"conociendo solo la guerra. Lo siento ... no puedo ver si lo derrotarás." +#: Source/translation_dummy.cpp:563 +msgid "Wisdom's Wrap" +msgstr "Manto de la Sabiduría" + +#: Source/translation_dummy.cpp:564 +msgid "Sparking Mail" +msgstr "Coraza Brillante" + +#: Source/translation_dummy.cpp:565 +msgid "Scavenger Carapace" +msgstr "Caparazón de Carroñero" -#. 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 "" -"Nunca he tratado con este Señor de la Guerra del que hablas, pero parece que " -"necesite muchas espadas. No me importaría abastecer a sus ejércitos ..." +#: Source/translation_dummy.cpp:566 +msgid "Nightscape" +msgstr "Paisaje Nocturno" -#. 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 "" -"Mi espada canta por tu sangre, mortal, y mis oscuros maestros no la negarán." +#: Source/translation_dummy.cpp:567 +msgid "Naj's Light Plate" +msgstr "Armadura Liviana de Naj" -#. 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 "" -"Griswold habla de la Piedra del Cielo que estaba destinada al enclave ubicado en " -"el este. Se estaba llevando allí para estudiarla más a fondo. Esta piedra " -"brillaba con una energía que de alguna manera otorgaba una visión más allá de la " -"que un hombre normal podría poseer. No sé qué secretos encierra, amigo mío, pero " -"encontrar esta piedra sin duda resultaría de lo más valioso." +#: Source/translation_dummy.cpp:568 +msgid "Demonspike Coat" +msgstr "Manto de Demonspike" -#. 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 "" -"La caravana se detuvo aquí para hacerse con algunos suministros para su viaje " -"hacia el este. Les vendí una gran variedad de frutas frescas y excelentes " -"mollejas que Garda acababa de hornear. Es una lástima lo que les pasó ..." +#: Source/translation_dummy.cpp:569 +msgid "The Deflector" +msgstr "El Deflector" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"No sé qué es lo que pensaron que podían ver con esa piedra, pero diré esto: Si " -"caen rocas del cielo, ¡es mejor que tengas cuidado!" +#: Source/translation_dummy.cpp:570 +msgid "Split Skull Shield" +msgstr "Escudo de Cráneo Dividido" -#. 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 "" -"Bueno, una caravana de personas muy importantes se detuvo aquí, pero eso fue " -"hace bastante tiempo. Tenían acentos extraños y estaban iniciando un largo " -"viaje, según recuerdo. \n" -" \n" -"No veo cómo podrías esperar encontrar algo que hubieran estado cargando." +#: Source/translation_dummy.cpp:571 +msgid "Dragon's Breach" +msgstr "Brecha del Dragó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 "" -"Quédate un momento, tengo una historia que puede resultarte interesante. Una " -"caravana que se dirigía a los reinos del este pasó por aquí hace algún tiempo. " -"¡Supuestamente llevaba un pedazo de los cielos que había caído a la tierra! La " -"caravana fue emboscada por jinetes encapuchados justo al norte de aquí a lo " -"largo de la carretera. Busqué entre los escombros esta roca celeste, pero no la " -"encontré por ningún lado. Si la encuentra, creo que puedo crear algo útil a " -"partir de ella." +#: Source/translation_dummy.cpp:572 +msgid "Blackoak Shield" +msgstr "Escudo de Roble Negro" -#. 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 "" -"Todavía estoy esperando que me traigas esa piedra del cielo. Sé que puedo hacer " -"algo poderoso con eso." +#: Source/translation_dummy.cpp:573 +msgid "Holy Defender" +msgstr "Santo Defensor" -#. 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 "" -"Déjame ver eso - sí ... sí, es como yo creía. Dame un momento...\n" -" \n" -"Ah, aquí tienes. Acomodé pedazos de la piedra dentro de un anillo de plata que " -"me dejó mi padre. Espero que te sirva bien." +#: Source/translation_dummy.cpp:574 +msgid "Stormshield" +msgstr "Escudo de Tormenta" -#. 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 "" -"Solía tener un bonito anillo; era muy caro, con azul, verde, rojo y plateado. " -"Sin embargo, no recuerdo qué le pasó. Realmente extraño ese anillo ..." +#: Source/translation_dummy.cpp:575 +msgid "Bramble" +msgstr "Zarza" -#. 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 "" -"La Piedra del Cielo es muy poderosa, y si alguien que no sea Griswold te pidiera " -"que la encontraras, yo lo evitaría. El aprovechará sus poderes y su uso será " -"para el bien de todos nosotros." +#: Source/translation_dummy.cpp:576 +msgid "Ring of Regha" +msgstr "Anillo de Regha" -#. 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 "" -"Si alguien puede hacer algo con esa roca, ese es Griswold. Él sabe lo que hace " -"y, por mucho que trato de robar a sus clientes, respeto la calidad de su trabajo." +#: Source/translation_dummy.cpp:577 +msgid "The Bleeder" +msgstr "El Sangrante" -#. 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 "" -"¿La bruja Adria busca un hongo negro? Sé tanto sobre Hongos Negros como sobre " -"Arenques Rojos. Quizás Pepin el Curandero podría contarte más, pero esto es algo " -"que no se puede encontrar en ninguna de mis historias o libros." +#: Source/translation_dummy.cpp:578 +msgid "Constricting Ring" +msgstr "Anillo de Constricción" -#. 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 "" -"Déjame decirte esto. Tanto Garda como yo nunca, NUNCA serviríamos hongos negros " -"a nuestros invitados de honor. Si Adria quiere hongos en su estofado, entonces " -"es asunto suyo, pero no puedo ayudarte a encontrar ninguno. Hongos negros ... " -"¡repugnantes!" +#: Source/translation_dummy.cpp:579 +msgid "Ring of Engagement" +msgstr "Anillo de Compromiso" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"La bruja me dijo que estabas buscando el cerebro de un demonio para ayudarme a " -"crear mi elixir. Debería ser de gran valor para los muchos que resultan heridos " -"por esas horribles bestias, si pudiera descubrir los secretos que sospecho " -"guarda su alquimia. Si puedes quitarle el cerebro a un demonio cuando lo mates, " -"te agradecería que me lo trajeras." +#: Source/translation_dummy.cpp:580 +msgid "Giant's Knuckle" +msgstr "Nudillo de Gigante" -#. 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 "" -"Excelente, esto es justo lo que tenía en mente. Pude terminar el elixir sin " -"esto, pero no está de más tener esto para estudiar. ¿Podrías llevarle esto a la " -"bruja? Creo que lo está esperando." +#: Source/translation_dummy.cpp:581 +msgid "Mercurial Ring" +msgstr "Anillo Mercurial" -#. 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 "" -"Creo que Ogden podría tener algunos hongos en el sótano de almacenamiento. ¿Por " -"qué no le preguntas?" +#: Source/translation_dummy.cpp:582 +msgid "Xorine's Ring" +msgstr "Anillo de Xorine" -#. 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 "" -"Si Adria no tiene uno de estos, puedes apostar que es algo raro. No puedo " -"ofrecerte más ayuda que esa, pero suena como ... ¡un hongo enorme, gigantesco, " -"hinchado y desmesurado! Bueno, buena caza, supongo." +#: Source/translation_dummy.cpp:583 +msgid "Karik's Ring" +msgstr "Anillo de Karik" -#. 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 "" -"Ogden mezcla un INFAME hongo negro, pero me enfermaré si bebo eso. Escucha, " -"escucha ... aquí está el secreto: ¡La moderación es la clave!" +#: Source/translation_dummy.cpp:584 +msgid "Ring of Magma" +msgstr "Anillo de Magma" -#. 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 "" -"¿Qué tenemos aquí? Interesante, parece un libro de reactivos. Mantén los ojos " -"abiertos para un hongo negro. Debe ser bastante grande y fácil de identificar. " -"Si lo encuentras, tráemelo, ¿quieres?" +#: Source/translation_dummy.cpp:585 +msgid "Ring of the Mystics" +msgstr "Anillo de los Místicos" -#. 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 "" -"Es un hongo negro grande el que necesito. Ahora corre y consíguelo para que " -"pueda usarlo en un brebaje especial en el que estoy trabajando." +#: Source/translation_dummy.cpp:586 +msgid "Ring of Thunder" +msgstr "Anillo de Trueno" -#. 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 "" -"Sí, será perfecto para una infusión que estoy creando. Por cierto, el sanador " -"está buscando el cerebro de algún demonio para poder tratar a los que han sido " -"afectados por su ponzoña venenosa. Creo que tiene la intención de hacer un " -"elixir con eso. Si lo ayudas a encontrar lo que necesita, fíjate si puedes " -"conseguirme una muestra del elixir." +#: Source/translation_dummy.cpp:587 +msgid "Amulet of Warding" +msgstr "Amuleto de Protección" -#. 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 "" -"¿Por qué has traído eso aquí? No necesito el cerebro de un demonio en este " -"momento. Necesito algo del elixir en el que está trabajando el Curandero. " -"Necesita ese órgano grotesco que estás sosteniendo, y luego tráeme el elixir. " -"Simple cuando lo piensas, ¿no?" +#: Source/translation_dummy.cpp:588 +msgid "Gnat Sting" +msgstr "Picadura de Mosquito" -#. 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 "" -"¿Qué? ¿Ahora me traes ese elixir del sanador? Pude terminar mi infusión sin él. " -"¿Por qué no te lo quedas? ..." +#: Source/translation_dummy.cpp:589 +msgid "Flambeau" +msgstr "Antorcha" -#. 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 "" -"No tengo hongos de ningún tamaño o color a la venta. ¿Qué tal algo un poco más " -"útil?" +#: Source/translation_dummy.cpp:590 +msgid "Armor of Gloom" +msgstr "Armadura de Penumbra" -#. 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 "" -"Entonces, la leyenda del Mapa es real. ¡Incluso yo nunca creí realmente nada de " -"eso! Supongo que es hora de que te diga la verdad sobre quién soy, amigo mío. " -"Verás, no soy todo lo que parezco ...\n" -" \n" -"Mi verdadero nombre es Deckard Cain el Sabio, y soy el último descendiente de " -"una antigua Hermandad que se dedicó a conservar y salvaguardar los secretos de " -"un mal atemporal. Un mal que obviamente ahora ha sido liberado ...\n" -" \n" -"El mal contra el que te mueves es el oscuro Señor del Terror, conocido por los " -"mortales como Diablo. Fue él quien fue encarcelado dentro del Laberinto hace " -"muchos siglos. El Mapa que tienes ahora fue creado hace siglos para marcar el " -"momento en que Diablo se levantaría nuevamente de su encarcelamiento. Cuando las " -"dos estrellas en ese mapa se alineen, Diablo estará en el apogeo de su poder. " -"Será casi invencible ...\n" -" \n" -"¡Ahora estás en una carrera contra el tiempo, amigo! Encuentra a Diablo y " -"destrúyelo antes de que las estrellas se alineen, ¡porque es posible que nunca " -"más tengamos la oportunidad de librar al mundo de su maldad!" +#: Source/translation_dummy.cpp:591 +msgid "Blitzen" +msgstr "Rayo" -#. 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 "" -"¡Nuestro tiempo se está acabando! Siento que su poder oscuro se está acumulando " -"y solo tú puedes evitar que logre todo su poder." +#: Source/translation_dummy.cpp:592 +msgid "Thunderclap" +msgstr "Tronido" -#. 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 "" -"Estoy seguro de que hiciste todo lo posible, pero me temo que ni siquiera tu " -"fuerza y voluntad serán suficientes. Diablo está ahora en el apogeo de su poder " -"terrenal, y necesitarás todo tu coraje y fuerza para derrotarlo. Que la Luz te " -"proteja y te guíe, amigo mío. Ayudaré en todo lo que pueda." +#: Source/translation_dummy.cpp:593 +msgid "Shirotachi" +msgstr "Shirotachi" -#. 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 "" -"Si la bruja no puede ayudarte y te sugiere que veas a Caín, ¿qué te hace pensar " -"que yo sabría algo? Parece que este es un asunto muy serio. Debes darte prisa y " -"ver al narrador como sugiere Adria." +#: Source/translation_dummy.cpp:594 +msgid "Eater of Souls" +msgstr "Devorador de Almas" -#. TRANSLATORS: Quest dialog spoken by Pepin (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 "" -"No puedo hacer mucho de lo escrito en este mapa, pero tal vez Adria o Caín " -"podrían ayudarlo a descifrar a qué se refiere esto. \n" -" \n" -"Puedo ver que es un mapa de las estrellas en nuestro cielo, pero está más allá " -"de mis talentos." +#: Source/translation_dummy.cpp:595 +msgid "Diamondedge" +msgstr "Filo Diamantado" -#. 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 "" -"La mejor persona para preguntar sobre ese tipo de cosas sería nuestro " -"narrador. \n" -" \n" -"Caín está muy bien informado sobre los escritos antiguos, y ese es fácilmente el " -"pedazo de papel más antiguo que he visto en mi vida." +#: Source/translation_dummy.cpp:596 +msgid "Bone Chain Armor" +msgstr "Cota de Malla de Hueso" -#. 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 "" -"Nunca antes había visto un mapa de este tipo. ¿Dónde lo conseguiste? Aunque no " -"tengo ni idea de cómo leer esto, Caín o Adria pueden darte las respuestas que " -"buscas." +#: Source/translation_dummy.cpp:597 +msgid "Demon Plate Armor" +msgstr "Armadura de Placas de Demonio" -#. 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 "" -"Escúchame, acércate. No sé si sabes lo que yo sé, pero realmente tienes algo " -"aquí. Eso es un mapa." +#: Source/translation_dummy.cpp:598 +msgid "Acolyte's Amulet" +msgstr "Amuleto de Acólito" + +#: Source/translation_dummy.cpp:599 +msgid "Gladiator's Ring" +msgstr "Anillo de Gladiador" -#. 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 "" -"¡Oh! Me temo que esto no augura nada bueno. Este mapa de las estrellas presagia " -"un gran desastre, pero sus secretos no son míos como para contarlos. Ha llegado " -"el momento de que tengas una conversación muy seria con el Narrador ..." +# ** Adjetivo sensible a cambio de género, terminaciones o/a +#: Source/translation_dummy.cpp:600 +msgid "Tin" +msgstr "de estaño" -#. 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 "" -"He estado buscando un mapa, pero ciertamente no es así. Deberías mostrárselo a " -"Adria; probablemente ella pueda decirte lo que es. Diré una cosa; parece viejo, " -"y viejo por lo general significa valioso." +#: Source/translation_dummy.cpp:601 +msgid "Brass" +msgstr "de latón" -#. 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 "" -"Por favooor, no herir. No matar. Mantener con vida y la próxima vez te ayudaré." +#: Source/translation_dummy.cpp:602 +msgid "Bronze" +msgstr "de bronce" -#. 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 "" -"Estoy haciendo algo nuevo para ti. Nuevamente, no mates a Gharbad. Vive y haz el " -"bien. \n" -" \n" -"Toma esto como prueba de que cumplo la palabra ..." +#: Source/translation_dummy.cpp:603 +msgid "Iron" +msgstr "de hierro" -#. 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 "" -"¡Nada aún! Casi termino. \n" -" \n" -"Muy poderoso, muy fuerte. ¡Vivir! ¡Vivir! \n" -" \n" -"¡Sin dolor y la promesa mantengo!" +#: Source/translation_dummy.cpp:604 +msgid "Steel" +msgstr "de acero" -#. 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 "Esto es demasiado bueno para ti. ¡Muy poderoso! Tu quieres - tu tomas!" +#: Source/translation_dummy.cpp:605 +msgid "Silver" +msgstr "de plata" -#. 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 "" -"¿Qué? ¿Por qué estás aquí? Todas estas interrupciones son suficientes para " -"volverse loco. Toma, toma esto y déjame con mi trabajo. ¡No me molestes más!" +#: Source/translation_dummy.cpp:607 +msgid "Platinum" +msgstr "de platino" -#. TRANSLATORS: Quest dialog spoken by Zhar the Mad (Hostile) -#: Source/textdat.cpp:307 -msgid "Arrrrgh! Your curiosity will be the death of you!!!" -msgstr "¡Arrrrgh! ¡Tu curiosidad te matará !!!" +#: Source/translation_dummy.cpp:608 +msgid "Mithril" +msgstr "de mithril" -#. TRANSLATORS: Neutral dialog spoken by Cain -#: Source/textdat.cpp:308 -msgid "Hello, my friend. Stay awhile and listen..." -msgstr "Hola mi amigo. Quédate un rato y escucha ..." +# ** +#: Source/translation_dummy.cpp:609 +msgid "Meteoric" +msgstr "meteórico" -#. 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 "" -"Mientras te adentres más profundo en el Laberinto, es posible que encuentres " -"tomos de gran conocimiento escondidos allí. \n" -" \n" -"Léelos atentamente porque pueden decirte cosas que incluso yo no puedo." +# ** +#: Source/translation_dummy.cpp:611 +msgid "Strange" +msgstr "extraño" -#. 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 "" -"Conozco muchos mitos y leyendas que pueden contener respuestas a preguntas que " -"puedan surgir en tus viajes al Laberinto. Si te encuentras con desafíos y " -"preguntas sobre las que buscas conocimiento, búscame y te diré lo que pueda." +# ** +#: Source/translation_dummy.cpp:612 +msgid "Useless" +msgstr "estropeado" -#. 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 "" -"Griswold: un hombre de gran acción y gran coraje. Apuesto a que nunca te contó " -"de la vez que entró en el Laberinto para salvar a Wirt, ¿verdad? Él conocía los " -"peligros que se encuentran allí, pero, de nuevo, tu también. Es un hábil " -"artesano, y si dice poder ayudarlo de alguna manera, puede contar con su " -"honestidad y habilidad." +# ** +#: Source/translation_dummy.cpp:613 +msgid "Bent" +msgstr "mellado" -#. 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 "" -"Ogden es propietario y dirige la Posada y Taberna del Sol Naciente desde hace " -"casi cuatro años. Lo compró unos pocos meses antes de que todo se fuera al " -"diablo. Él y su esposa Garda no tienen dinero para irse, ya que invirtieron todo " -"lo que tenían para ganarse la vida aquí. Es un buen hombre con un profundo " -"sentido de responsabilidad." +# ** +#: Source/translation_dummy.cpp:614 +msgid "Weak" +msgstr "desgastado" -#. 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 "" -"Pobre Farnham. Es un inquietante recordatorio de la condenada asamblea que entró " -"en la Catedral con Lazarus en ese día oscuro. Escapó con vida, pero su coraje y " -"gran parte de su cordura quedaron en algún pozo oscuro. Hoy en día encuentra " -"consuelo solo en el fondo de su jarra, pero hay fragmentos ocasionales de verdad " -"enterrados en sus constantes divagaciones." +# ** +#: Source/translation_dummy.cpp:615 +msgid "Jagged" +msgstr "dentado" -#. 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 "" -"La bruja, Adria, es una anomalía aquí en Tristram. Llegó poco después de que la " -"Catedral fuera profanada mientras la mayoría de los demás huían. Ella hizo " -"construir una pequeña cabaña en las afueras del pueblo, aparentemente de la " -"noche a la mañana, y tiene acceso a muchos artefactos extraños y arcanos y tomos " -"de conocimiento que ni siquiera yo había visto antes." +#: Source/translation_dummy.cpp:616 +msgid "Deadly" +msgstr "mortal" -#. 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 "" -"La historia de Wirt es aterradora y trágica. Fue arrancado de los brazos de su " -"madre y arrastrado al laberinto por los pequeños e inmundos demonios que empuñan " -"malvadas lanzas. Se llevaron a muchos otros niños ese día, incluido el hijo del " -"Rey Leoric. Los Caballeros del palacio bajaron, pero nunca regresaron. El " -"herrero encontró al niño, pero solo después de que las horribles bestias " -"comenzaran a torturarlo para sus sádicos placeres." +# ** +#: Source/translation_dummy.cpp:617 +msgid "Heavy" +msgstr "pesado" -#. 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 "" -"Ah, Pepin. Lo considero un verdadero amigo, quizás el más cercano que tengo " -"aquí. A veces está un poco confundido, pero nunca ha existido un alma más " -"cariñosa o considerada. Sus conocimientos y habilidades son igualados por pocos, " -"y su puerta siempre está abierta." +#: Source/translation_dummy.cpp:618 +msgid "Vicious" +msgstr "atroz" -#. 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 "" -"Gillian es una buena mujer. Muy querida por su buen humor y su risa rápida, " -"ocupa un lugar especial en mi corazón. Se queda en la taberna para ayudar a su " -"abuela anciana, que está demasiado enferma para viajar. A veces temo por su " -"seguridad, pero sé que cualquier hombre de la aldea preferiría morir antes que " -"verla lastimada." +#: Source/translation_dummy.cpp:619 +msgid "Brutal" +msgstr "brutal" -#. TRANSLATORS: Neutral dialog spoken by Ogden -#: Source/textdat.cpp:327 -msgid "Greetings, good master. Welcome to the Tavern of the Rising Sun!" -msgstr "Saludos, buen maestro. ¡Bienvenido a la Taberna del Sol Naciente!" +#: Source/translation_dummy.cpp:620 +msgid "Massive" +msgstr "descomunal" -#. 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 "" -"Muchos aventureros han honrado las mesas de mi taberna, y diez veces se han " -"contado tantas historias como bebido cerveza. Lo único que escuché de todos " -"ellos fue el estar de acuerdo con este viejo axioma. Quizás te ayude. Puedes " -"cortar la carne, pero debes triturar el hueso." +#: Source/translation_dummy.cpp:621 +msgid "Savage" +msgstr "cruel" -#. 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 "" -"Griswold, el herrero, está muy bien informado sobre armas y armaduras. Si alguna " -"vez necesitas trabajar en tu equipo, definitivamente es el hombre para ver." +#: Source/translation_dummy.cpp:622 +msgid "Ruthless" +msgstr "implacable" -#. 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 "" -"Farnham pasa demasiado tiempo aquí, ahogando sus penas en cerveza barata. Lo " -"echaría, pero sufrió tanto durante su tiempo en el Laberinto." +# ** +#: Source/translation_dummy.cpp:623 +msgid "Merciless" +msgstr "despiadado" -#. 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 "" -"Adria es sabia para su edad, pero debo admitirlo: Ella me asusta un poco. \n" -" \n" -"Bueno, no importa. Si alguna vez necesitas intercambiar objetos de hechicería, " -"ella mantiene una choza extrañamente bien abastecida al otro lado del río." +# ** +#: Source/translation_dummy.cpp:624 +msgid "Clumsy" +msgstr "roñoso" -#. 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 "" -"Si quieres saber más sobre la historia de nuestro pueblo, el narrador Cain sabe " -"bastante sobre el pasado." +# ** +#: Source/translation_dummy.cpp:625 +msgid "Dull" +msgstr "mohoso" -#. 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 "" -"Wirt es un canalla y un pequeño sinvergüenza. Siempre se estaba metiendo en " -"problemas, y no es de extrañar lo que le sucedió. \n" -" \n" -"Probablemente se fue a hacer el tonto por ahí donde no debería haber estado. Lo " -"siento por el chico, pero no acepto la compañía que tiene." +# ** +#: Source/translation_dummy.cpp:626 +msgid "Sharp" +msgstr "afilado" -#. 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 "" -"Pepin es un buen hombre y, sin duda, el más generoso del pueblo. Siempre está " -"atendiendo las necesidades de los demás, pero los problemas de una u otra clase " -"parecen seguirlo dondequiera que vaya ..." +# ** +#: Source/translation_dummy.cpp:627 Source/translation_dummy.cpp:637 +msgid "Fine" +msgstr "fino" -#. 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 "" -"Gillian, mi Camarera? Si no fuera por su sentido del deber hacia su abuela, " -"habría huido de aquí hace mucho tiempo. \n" -" \n" -"Dios sabe que le rogué que se fuera, diciéndole que cuidaría de la anciana, pero " -"ella es demasiado dulce y cariñosa para haberlo hecho." +#: Source/translation_dummy.cpp:628 +msgid "Warrior's" +msgstr "de guerrero" -#. TRANSLATORS: Neutral dialog spoken by Pepin -#: Source/textdat.cpp:345 -msgid "What ails you, my friend?" -msgstr "¿Qué te aflige, amigo mío?" +#: Source/translation_dummy.cpp:629 +msgid "Soldier's" +msgstr "de soldado" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" -"He hecho un descubrimiento muy interesante. A diferencia de nosotros, las " -"criaturas del Laberinto pueden curarse a sí mismas sin la ayuda de pociones o " -"magia. Si hieres a uno de los monstruos, asegúrate de que esté muerto o podría " -"regenerarse." +#: Source/translation_dummy.cpp:630 +msgid "Lord's" +msgstr "del señor" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" -"Antes de que fuera tomada por, bueno, lo que sea que esté al acecho debajo, la " -"Catedral era un lugar de gran aprendizaje. Allí se pueden encontrar muchos " -"libros. Si encuentra alguno, debe leerlos todos, ya que algunos pueden tener " -"secretos sobre el funcionamiento del Laberinto." +#: Source/translation_dummy.cpp:631 +msgid "Knight's" +msgstr "de caballero" + +#: Source/translation_dummy.cpp:632 +msgid "Master's" +msgstr "de maestro" + +#: Source/translation_dummy.cpp:633 +msgid "Champion's" +msgstr "de campeón" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" -"Griswold sabe tanto sobre el arte de la guerra como yo sobre el arte de curar. " -"Es un comerciante astuto, pero su trabajo es insuperable. Oh, supongo que puede " -"deberse a que es el único herrero que queda aquí." +#: Source/translation_dummy.cpp:634 +msgid "King's" +msgstr "real" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" -"Caín es un verdadero amigo y un sabio sensato. Mantiene una vasta biblioteca y " -"tiene una habilidad innata para discernir la verdadera naturaleza de muchas " -"cosas. Si alguna vez tienes alguna pregunta, él es la persona a la que debes " -"dirigirse." +#: Source/translation_dummy.cpp:635 +msgid "Vulnerable" +msgstr "frágil" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" -"Incluso mis habilidades no han podido curar completamente a Farnham. Oh, he " -"podido reparar su cuerpo, pero su mente y su espíritu están más allá de " -"cualquier cosa que pueda hacer." +# ** +#: Source/translation_dummy.cpp:636 +msgid "Rusted" +msgstr "oxidado" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" -"Si bien utilizo algunas formas limitadas de magia para crear las pociones y " -"elixires que guardo aquí, Adria es una verdadera hechicera. Parece que nunca " -"duerme y siempre tiene acceso a muchos libros y artefactos místicos. Creo que su " -"cabaña puede ser mucho más de lo que parece ser, pero parece que nunca puedo " -"entrar al lugar." +#: Source/translation_dummy.cpp:638 +msgid "Strong" +msgstr "fuerte" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" -"Pobre Wirt. Hice todo lo posible por el niño, pero sé que desprecia ese broche " -"de madera que me vi obligado a sujetar a su pierna. Sus heridas eran horribles. " -"Nadie, y especialmente un niño tan pequeño, debería tener que sufrir como él lo " -"hizo." +# ** +#: Source/translation_dummy.cpp:639 +msgid "Grand" +msgstr "grandioso" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" -"Realmente no entiendo por qué Ogden se queda aquí en Tristram. Sufre de una leve " -"condición nerviosa, pero es un hombre inteligente y trabajador que le iría muy " -"bien donde quiera que fuera. Supongo que puede ser el miedo a los muchos " -"asesinatos que ocurren en el campo circundante, o quizás los deseos de su esposa " -"lo que lo mantiene a él y a su familia donde están." +#: Source/translation_dummy.cpp:640 +msgid "Valiant" +msgstr "valiente" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" -"La camarera de Ogden es una chica dulce. Su abuela está bastante enferma y sufre " -"delirios \n" -" \n" -"Ella afirma que son visiones, pero no tengo pruebas de eso de una forma u otra." +# ** +#: Source/translation_dummy.cpp:641 +msgid "Glorious" +msgstr "glorioso" -#. TRANSLATORS: Neutral dialog spoken by Gillian -#: Source/textdat.cpp:364 -msgid "Good day! How may I serve you?" -msgstr "¡Buenos días! ¿Cómo puedo servirte?" +# ** +#: Source/translation_dummy.cpp:642 +msgid "Blessed" +msgstr "bendito" -#. 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 "" -"Mi abuela soñó que vendrías a hablar conmigo. Ella tiene visiones, sabes, y " -"puedes ver el futuro." +# ** +#: Source/translation_dummy.cpp:643 +msgid "Saintly" +msgstr "santo" -#. 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 "" -"¡La mujer de las afueras del pueblo es una bruja! Parece bastante agradable y su " -"nombre, Adria, es muy agradable al oído, pero le temo. \n" -" \n" -"Se necesitaría alguien bastante valiente, como tú, para ver lo que está haciendo " -"ahí fuera." +# ** +#: Source/translation_dummy.cpp:644 +msgid "Awesome" +msgstr "asombroso" -#. 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 "" -"Nuestro herrero es un motivo de orgullo para la gente de Tristram. No solo es un " -"maestro artesano que ha ganado muchos concursos dentro de su gremio, sino que " -"recibió elogios de nuestro Rey Leoric en persona, que su alma descanse en paz. " -"Griswold también es un gran héroe; pregúntale a Caín." +# ** +#: Source/translation_dummy.cpp:646 +msgid "Godly" +msgstr "piadoso" -#. 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 "" -"Cain ha sido el narrador de Tristram desde que tengo uso de razón. Él sabe mucho " -"y puede decirte casi cualquier cosa sobre casi todo." +# ** +#: Source/translation_dummy.cpp:647 +msgid "Red" +msgstr "rojo" -#. 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 "" -"Farnham es un borracho que llena su barriga de cerveza y los oídos de los demás " -"de tonterías. \n" -" \n" -"Sé que tanto Pepin como Ogden sienten simpatía por él, pero me frustra tanto " -"verlo caer cada vez más en un aturdido estupor cada noche." +#: Source/translation_dummy.cpp:648 Source/translation_dummy.cpp:649 +msgid "Crimson" +msgstr "carmesí" -#. 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 "" -"Pepin salvó la vida de mi abuela y sé que nunca podré pagarle por eso. Su " -"habilidad para curar cualquier enfermedad es más poderosa que la espada más " -"potente y más misteriosa que cualquier hechizo que puedas nombrar. Si alguna vez " -"necesitas curarte, Pepin puede ayudarte." +#: Source/translation_dummy.cpp:650 +msgid "Garnet" +msgstr "granate" -#. 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 "" -"Crecí con la madre de Wirt, Canace. Aunque solo se sintió levemente herida " -"cuando esas horribles criaturas se lo robaron, nunca se recuperó. Creo que murió " -"con el corazón roto. Wirt se ha convertido en un joven mezquino que solo busca " -"sacar provecho del sudor de los demás. Sé que sufrió y ha visto horrores que ni " -"siquiera puedo imaginar, pero algo de esa oscuridad aún se cierne sobre él." +#: Source/translation_dummy.cpp:651 +msgid "Ruby" +msgstr "rubí" -#. 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 "" -"Ogden y su esposa nos han llevado a mi abuela y a mí a su casa e incluso me han " -"dejado ganar algunas piezas de oro trabajando en la posada. Les debo mucho y " -"espero algún día dejar este lugar y ayudarlos a comenzar un gran hotel en el " -"este." +#: Source/translation_dummy.cpp:652 +msgid "Blue" +msgstr "azul" -#. TRANSLATORS: Neutral dialog spoken by Griswold -#: Source/textdat.cpp:381 -msgid "Well, what can I do for ya?" -msgstr "Bueno, ¿qué puedo hacer por ti?" +#: Source/translation_dummy.cpp:653 +msgid "Azure" +msgstr "celeste" -#. 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 "" -"Si estás buscando un buen arma, déjame mostrarte esto. Toma tu arma básica " -"contundente, como una maza. Funciona como un encanto contra la mayoría de esos " -"horrores eternos que hay allí, ¡y no hay nada mejor para destrozar pequeños " -"esqueletos delgados!" +#: Source/translation_dummy.cpp:654 +msgid "Lapis" +msgstr "lapislázuli" -#. 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 "" -"¿El hacha? Sí, esa es un buen arma, equilibrada contra cualquier enemigo. Mira " -"cómo corta el aire y luego imagina una bonita y gorda cabeza de demonio en su " -"camino. Sin embargo, ten en cuenta que el giro es lento, ¡pero habla de asestar " -"un golpe fuerte!" +#: Source/translation_dummy.cpp:655 +msgid "Cobalt" +msgstr "cobalto" -#. 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 "" -"Mira ese filo, ese equilibrio. Una espada en la mano derecha, y contra el " -"enemigo correcto, es el amo de todas las armas. Su hoja afilada encuentra poco " -"para cortar o perforar en los muertos vivientes, pero contra un enemigo vivo que " -"respira, ¡una espada cortará su carne mucho mejor!" +#: Source/translation_dummy.cpp:656 +msgid "Sapphire" +msgstr "zafiro" -#. 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 "" -"Tus armas y armaduras mostrarán los signos de tus luchas contra la Oscuridad. Si " -"me las traes, con un poco de trabajo y una fragua en caliente, puedo " -"restaurarlas a su mejor forma de lucha." +# ** +#: Source/translation_dummy.cpp:657 +msgid "White" +msgstr "blanco" -#. 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 "" -"Mientras prácticamente tengo que pasar de contrabando los metales y herramientas " -"que necesito de las caravanas que bordean los límites de nuestro maldito pueblo, " -"esa bruja, Adria, siempre parece conseguir lo que necesita. Si supiera lo más " -"mínimo sobre cómo aprovechar la magia como ella lo hizo, podría hacer algunas " -"cosas realmente increíbles." +#: Source/translation_dummy.cpp:658 +msgid "Pearl" +msgstr "perla" -#. 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 "" -"Gillian es una buena chica. Es una pena que su abuela tenga tan mala salud o " -"haría los arreglos para sacarlos a ambos de aquí en una de las caravanas " -"comerciales." +#: Source/translation_dummy.cpp:659 +msgid "Ivory" +msgstr "marfil" -#. 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 "" -"A veces pienso que Caín habla demasiado, pero supongo que esa es su vocación en " -"la vida. ¡Si pudiera doblar el acero tan bien como puede doblar tu oreja, podría " -"hacer una armadura de placas lo suficientemente buena para un Emperador!" +#: Source/translation_dummy.cpp:660 +msgid "Crystal" +msgstr "cristal" -#. 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 "" -"Estaba con Farnham esa noche que Lazarus nos condujo al Laberinto. Nunca volví a " -"ver al Arzobispo y es posible que no hubiera sobrevivido si Farnham no hubiera " -"estado a mi lado. Me temo que el ataque dejó su alma tan lisiada como, bueno, " -"otro dejó mi pierna. No puedo pelear esta batalla por él ahora, pero lo haría si " -"pudiera." +#: Source/translation_dummy.cpp:661 +msgid "Diamond" +msgstr "diamante" -#. 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 "" -"Un buen hombre que antepone las necesidades de los demás a las suyas. No " -"encontraras a nadie en Tristram, ni en ningún otro lugar, que tenga algo malo " -"que decir sobre el sanador." +#: Source/translation_dummy.cpp:662 +msgid "Topaz" +msgstr "topacio" -#. 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 "" -"Ese chico se va a meter en serios problemas ... o creo que debería decirlo, de " -"nuevo. He tratado de interesarle en trabajar aquí y aprender un oficio honesto, " -"pero prefiere las altas ganancias de comerciar con bienes de origen dudoso. No " -"puedo reprocharle eso después de lo que le sucedió, pero desearía que al menos " -"tuviera cuidado." +#: Source/translation_dummy.cpp:663 +msgid "Amber" +msgstr "ámbar" -#. 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 "" -"El Posadero tiene pocos negocios y ninguna forma real de obtener ganancias. Se " -"las arregla para llegar a fin de mes proporcionando comida y alojamiento a " -"aquellos que, de vez en cuando, deambulan por la aldea, pero es tan probable que " -"se escapen por noche como que le paguen. Si no fuera por las reservas de " -"cereales y carnes secas que guarda en su bodega, la mayoría de nosotros " -"habríamos muerto de hambre durante ese primer año en que todo el campo fue " -"invadido por demonios." +#: Source/translation_dummy.cpp:664 +msgid "Jade" +msgstr "jade" -#. TRANSLATORS: Neutral dialog spoken by Farnham -#: Source/textdat.cpp:404 -msgid "Can't a fella drink in peace?" -msgstr "¿No puede un colega beber en paz?" +#: Source/translation_dummy.cpp:665 +msgid "Obsidian" +msgstr "obsidiana" -#. 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 "" -"¿La chica que trae las bebidas? Oh, sí, qué hermosa dama. Muy agradable también." +#: Source/translation_dummy.cpp:666 +msgid "Emerald" +msgstr "esmeralda" -#. 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 "" -"¿Por qué esa vieja bruja no hace algo para variar? Claro, claro, tiene sus " -"cosas, pero escúchame ... es antinatural. Nunca la he visto comer o beber, y no " -"puedes confiar en alguien que no bebe al menos un poco." +#: Source/translation_dummy.cpp:667 +msgid "Hyena's" +msgstr "de la hiena" -#. 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 "" -"Caín no es lo que dice ser. Claro, claro, él cuenta una buena historia ... " -"algunas de ellas son realmente aterradoras o divertidas ... pero creo que él " -"sabe más de lo que sabe." +#: Source/translation_dummy.cpp:668 +msgid "Frog's" +msgstr "de la rana" -#. 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 "" -"Griswold? El bueno de Griswold. ¡Lo quiero como a un hermano! Luchamos juntos, " -"ya sabes, cuando ... nosotros ... Lazarus ... Lazarus ... Lazarus!!!" +#: Source/translation_dummy.cpp:669 +msgid "Spider's" +msgstr "de la araña" + +#: Source/translation_dummy.cpp:670 +msgid "Raven's" +msgstr "del cuervo" -#. 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 "" -"Jejeje, me gusta Pepin. Realmente lo intenta, ya sabes. Escucha, debes " -"asegurarte de conocerlo. Buen tipo como la gente que siempre quiere ayudar. Oye, " -"supongo que sería un poco propio de ti, ¿eh héroe? Yo también fui un héroe ..." +#: Source/translation_dummy.cpp:671 +msgid "Snake's" +msgstr "de la culebra" -#. 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 "" -"Wirt es un niño que tiene incluso más problemas que yo, y yo sé todo sobre los " -"problemas. Escucha, ese chico es afable, pero ha estado allí, ¿sabes? ¡Perdió " -"una pierna! Tiene que caminar sobre un trozo de madera. Es tan triste, tan " -"triste ..." +#: Source/translation_dummy.cpp:672 +msgid "Serpent's" +msgstr "de la serpiente" -#. 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 "" -"Ogden es el mejor hombre del pueblo. No creo que yo le guste mucho a su esposa, " -"pero mientras ella siga golpeando barriles, supongo que estará bien. Parece que " -"he pasado más tiempo con Ogden que la mayoría, pero es tan bueno conmigo ..." +#: Source/translation_dummy.cpp:673 +msgid "Drake's" +msgstr "del pequeño dragón" -#. 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 "" -"Quiero decirte algo, porque sé todo sobre estas cosas. Es mi especialidad. Esto " -"de aquí es lo mejor ... ¡looo mejor! Esa otra cerveza no es buena ya que esos " -"estúpidos perros ..." +#: Source/translation_dummy.cpp:674 +msgid "Dragon's" +msgstr "del dragón" -#. 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 "" -"Nunca nadie me esc... escucha. En algún lugar, no estoy muy seguro, pero en " -"algún lugar debajo de la iglesia hay un gran montón de oro. Reluciendo y " -"brillando y esperando a que alguien lo consiga." +#: Source/translation_dummy.cpp:675 +msgid "Wyrm's" +msgstr "del gran dragón" -#. 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 "" -"Sé que tienes tus propias ideas, y sé que no vas a creer esto, pero ese arma que " -"tienes allí, ¡simplemente no es buena contra esos grandes brutos! Oh, no me " -"importa lo que diga Griswold, no pueden hacer nada como solían hacer en los " -"viejos tiempos ..." +#: Source/translation_dummy.cpp:676 +msgid "Hydra's" +msgstr "de la hidra" -#. 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 "" -"Si yo fuera tú ... y no lo soy ... pero si lo fuera, vendería todas esas cosas " -"que tienes y me largaría de aquí. Ese chico de ahí fuera ... Siempre tiene algo " -"bueno, pero tienes que darle algo de oro o ni siquiera te mostrará lo que tiene." +#: Source/translation_dummy.cpp:677 +msgid "Angel's" +msgstr "del ángel" -#. TRANSLATORS: Neutral dialog spoken by Adria -#: Source/textdat.cpp:427 -msgid "I sense a soul in search of answers..." -msgstr "Siento un alma en busca de respuestas ..." +#: Source/translation_dummy.cpp:678 +msgid "Arch-Angel's" +msgstr "del arcángel" -#. 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 "" -"La sabiduría se gana, no se da. Si descubres un tomo de conocimiento, devora sus " -"palabras. Si ya tienes conocimiento de los misterios arcanos escritos en un " -"libro, recuerda, ese nivel de maestría siempre puede aumentar." +#: Source/translation_dummy.cpp:679 +msgid "Plentiful" +msgstr "cuantioso" -#. 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 "" -"El mayor poder es a menudo el de menor duración. Puede encontrar antiguas " -"palabras de poder escritas en rollos de pergamino. La fuerza de estos pergaminos " -"radica en la capacidad del aprendiz o del adepto para lanzarlos con igual " -"habilidad. Su debilidad es que primero se deben leer en voz alta y nunca se " -"pueden tener listos en la mente. Debes saber también que estos pergaminos se " -"pueden leer una sola vez, así que utilízalos con cuidado." +#: Source/translation_dummy.cpp:680 +msgid "Bountiful" +msgstr "colmado" -#. 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 "" -"Aunque el calor del sol es inconmensurable, la mera llama de una vela es de " -"mayor peligro. Ninguna energía, por grande que sea, puede usarse sin el enfoque " -"adecuado. Para muchos hechizos, los Bastones encantados pueden cargarse muchas " -"veces con energías mágicas. Tengo la capacidad de restaurar su poder, pero sé " -"que nada se hace sin un precio." +#: Source/translation_dummy.cpp:681 +msgid "Flaming" +msgstr "llameante" -#. 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 "" -"La suma de nuestro conocimiento está en la suma de su gente. Si encuentras un " -"libro o un pergamino que no puedas descifrar, no dude en traérmelo. Si puedo " -"encontrarle sentido, compartiré lo que encuentre." +#: Source/translation_dummy.cpp:682 +msgid "Lightning" +msgstr "centelleante" -#. 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 "" -"Para un hombre que solo conoce el Hierro, no hay mayor magia que el Acero. El " -"herrero Griswold es más hechicero de lo que él cree. Su habilidad para fusionar " -"el fuego y el metal es inigualable en esta tierra." +#: Source/translation_dummy.cpp:683 +msgid "Jester's" +msgstr "del bufón" -#. 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 "" -"La corrupción tiene la fuerza del engaño, pero la inocencia tiene el poder de la " -"pureza. La joven Gillian tiene un corazón puro, anteponiendo las necesidades de " -"su matriarca sobre las suyas. Ella me teme, pero es solo porque no me comprende." +# ** +#: Source/translation_dummy.cpp:684 +msgid "Crystalline" +msgstr "cristalino" -#. 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 "" -"Un cofre abierto en la oscuridad no guarda mayor tesoro que cuando se abre a la " -"luz. El narrador Caín es un enigma, pero solo para quienes no miran. Su " -"conocimiento de lo que hay debajo de la catedral es mucho mayor de lo que él " -"mismo se permite darse cuenta." +#: Source/translation_dummy.cpp:685 +msgid "Doppelganger's" +msgstr "del doppelgänger" -#. 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 "" -"Cuanto más alto pongas tu fe en un hombre, más debe caer. Farnham ha perdido su " -"alma, pero no ante ningún demonio. Se perdió cuando vio a la gente del pueblo " -"traicionados por el Arzobispo Lazarus. Él tiene conocimientos para cosechar, " -"pero debes separar los hechos de las fantasías." +#: Source/translation_dummy.cpp:686 +msgid "quality" +msgstr "de la calidad" -#. 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 "" -"La mano, el corazón y la mente pueden realizar milagros cuando están en perfecta " -"armonía. El sanador Pepin ve el interior del cuerpo de una manera que ni " -"siquiera yo puedo. Su capacidad para restaurar a los enfermos y heridos se ve " -"magnificada por su comprensión en la creación de elixires y pociones. Es un " -"aliado tan grande como tú en Tristram." +#: Source/translation_dummy.cpp:687 +msgid "maiming" +msgstr "de la mutilación" -#. 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 "" -"Hay mucho sobre el futuro que no podemos ver, pero cuando llegue serán los niños " -"quienes lo manejen. El niño Wirt tiene algo oscuro en el alma, pero no " -"representa una amenaza para el pueblo o su gente. Sus tratos secretos con los " -"rufianes y los inconfesables gremios de los pueblos cercanos le permiten acceder " -"a muchos dispositivos que no se pueden encontrar fácilmente en Tristram. Si bien " -"sus métodos pueden ser reprochables, Wirt puede ayudarte en tu batalla contra la " -"Oscuridad que nos invade." +#: Source/translation_dummy.cpp:688 +msgid "slaying" +msgstr "del asesinato" -#. 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 "" -"Las paredes de tierra y el dosel de paja no crean una casa. El posadero Ogden " -"tiene un mayor propósito en este pueblo de lo que muchos creen. Proporciona " -"refugio para Gillian y su matriarca, mantiene la vida que le dejó Farnham y " -"proporciona un ancla para todos los que quedan en el pueblo a lo que Tristram " -"fue una vez. Su taberna, y los placeres sencillos que aún se pueden encontrar " -"allí, dan una idea de una vida que la gente de aquí recuerda. Es ese recuerdo el " -"que sigue alimentando sus esperanzas de éxito." +#: Source/translation_dummy.cpp:689 +msgid "gore" +msgstr "de sangre" -#. TRANSLATORS: Neutral dialog spoken by Wirt -#: Source/textdat.cpp:450 -msgid "Pssst... over here..." -msgstr "Pssst ... por aquí ..." +#: Source/translation_dummy.cpp:690 +msgid "carnage" +msgstr "de la matanza" -#. 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 "" -"No todo el mundo en Tristram tiene un uso, o un mercado, para todo lo que " -"encontrará en el laberinto. Ni siquiera yo, por más difícil que sea de creer. \n" -" \n" -"A veces, solo tú podrás encontrar un propósito para algunas cosas." +#: Source/translation_dummy.cpp:691 +msgid "slaughter" +msgstr "de la tortura" -#. 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 "" -"No te fíes de todo lo que dice el borracho. Demasiadas cervezas han empañado su " -"visión y su sentido común." +#: Source/translation_dummy.cpp:692 +msgid "pain" +msgstr "del dolor" -#. 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 "" -"Por si no lo has notado, no compro nada de Tristram. Soy un importador de " -"productos de calidad. Si quieres vender basura, tendrás que ver a Griswold, " -"Pepin o esa bruja, Adria. Estoy seguro de que te sacarán de las manos todo lo " -"que les puedas llevar ..." +#: Source/translation_dummy.cpp:693 +msgid "tears" +msgstr "del llanto" -#. 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 "" -"Supongo que le debo la vida al herrero, lo que queda de ella. Claro, Griswold me " -"ofreció un aprendizaje en la herrería, y es un tipo bastante agradable, pero " -"nunca obtendré suficiente dinero para ... bueno, digamos que tengo planes " -"definidos que requieren una gran cantidad de oro." +#: Source/translation_dummy.cpp:694 +msgid "health" +msgstr "de salud" -#. 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 "" -"Si tuviera unos años más, la colmaría con todas las riquezas que pudiera reunir, " -"y déjame asegurarte que puedo conseguir algunas cosas muy buenas. Gillian es una " -"hermosa chica que debería salir de Tristram tan pronto como sea seguro. Hmmm ... " -"tal vez me la lleve conmigo cuando me vaya ..." +#: Source/translation_dummy.cpp:695 +msgid "protection" +msgstr "de protección" -#. 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 "" -"Caín sabe demasiado. Me asusta de muerte, incluso más que esa mujer al otro lado " -"del río. No deja de contarme lo afortunado que soy de estar vivo y cómo mi " -"historia está predicha en la leyenda. Creo que está loco." +#: Source/translation_dummy.cpp:696 +msgid "absorption" +msgstr "de absorción" -#. 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 "" -"Farnham: ahora hay un hombre con problemas graves y sé todo acerca de lo graves " -"que pueden ser los problemas. Confió demasiado en la integridad de un hombre, y " -"Lazarus lo llevó a las mismas fauces de la muerte. Oh, sé lo que es ahí abajo, " -"así que ni siquiera empieces a contarme tus planes para destruir el mal que " -"habita en ese Laberinto. Cuida tus piernas ..." +#: Source/translation_dummy.cpp:697 +msgid "deflection" +msgstr "de desvío" -#. 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 "" -"Siempre que no necesites volver a colocar nada, el viejo Pepin es tan bueno como " -"pocos. \n" -" \n" -"Si hubiera tenido algunas de esas pociones que él prepara, aún podría tener mi " -"pierna ..." +#: Source/translation_dummy.cpp:698 +msgid "osmosis" +msgstr "de ósmosis" -#. 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 "" -"Adria realmente me molesta. Claro, Cain es espeluznante en lo que puede contarte " -"sobre el pasado, pero esa bruja puede ver tu pasado. Ella siempre tiene alguna " -"forma de conseguir lo que necesita también. Adria consigue más mercadería de la " -"que he visto pasar por las puertas del Bazar del Rey durante el Festival Mayor." +#: Source/translation_dummy.cpp:699 +msgid "frailty" +msgstr "de la endeblez" -#. 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 "" -"Ogden es un tonto por quedarse aquí. Podría sacarlo del pueblo por un precio muy " -"razonable, pero él insiste en intentar salir adelante con esa estúpida taberna. " -"Supongo que al menos le da a Gillian un lugar para trabajar, y su esposa Garda " -"hace un excelente Pastel de cordero ..." +#: Source/translation_dummy.cpp:700 +msgid "weakness" +msgstr "de la debilidad" -#. 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 "" -"Más allá del Salón de los Héroes se encuentra la Cámara de Hueso. La muerte " -"eterna aguarda a cualquiera que busque robar los tesoros guardados dentro de " -"esta habitación. Así habla el Señor del Terror, y así está escrito." +#: Source/translation_dummy.cpp:701 +msgid "strength" +msgstr "de fuerza" -#. 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 "" -"... y así, encerrado más allá de la Puerta de la Sangre y más allá del Salón del " -"Fuego, Valor espera a que el Héroe de la Luz despierte ..." +#: Source/translation_dummy.cpp:702 +msgid "might" +msgstr "del poder" -#. 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 "" -"Puedo ver lo que tú no ves.\n" -"Visión lechosa, luego los ojos se pudren.\n" -"Cuando te vuelvas se habrán ido,\n" -"Susurrando su canción oculta.\n" -"Entonces ves lo que no puede ser,\n" -"Las sombras se mueven donde debería estar la luz.\n" -"Fuera de la oscuridad, fuera de la mente,\n" -"Arrojados a los Pasillos del Ciego." +#: Source/translation_dummy.cpp:703 +msgid "power" +msgstr "del buey" + +#: Source/translation_dummy.cpp:704 +msgid "giants" +msgstr "del gigante" -#. 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 "" -"Las armerías del Infierno son el hogar del Señor de la Guerra de la Sangre. A su " -"paso yacían los cuerpos mutilados de miles. Tanto los ángeles como los hombres " -"han sido cortados para cumplir con sus sacrificios interminables a los Oscuros " -"que gritan por una cosa: sangre." +#: Source/translation_dummy.cpp:705 +msgid "titans" +msgstr "del titán" -#. 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 "" -"Presta atención y da testimonio de las verdades que se encuentran aquí, porque " -"son el último legado de los Horadrim. Hay una guerra que continúa incluso ahora, " -"más allá de los campos que conocemos, entre los reinos utópicos de los Altos " -"Cielos y los caóticos pozos de los Infiernos Ardientes. Esta guerra se conoce " -"como el Gran Conflicto, y ha durado y ardido durante más tiempo que cualquiera " -"de las estrellas del cielo. Ninguno de los bandos logrará dominar mientras las " -"fuerzas de la Luz y la Oscuridad compitan constantemente por el control de toda " -"la creación." +#: Source/translation_dummy.cpp:706 +msgid "paralysis" +msgstr "de la parálisis" -#. 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 "" -"Presta atención y da testimonio de las verdades que se encuentran aquí, porque " -"son el último legado de los Horadrim. Cuando el Conflicto Eterno entre los Altos " -"Cielos y los Infiernos Ardientes cae sobre suelo mortal, se llama la Guerra del " -"Pecado. Ángeles y demonios caminan entre la humanidad disfrazados, luchando en " -"secreto, lejos de las miradas indiscretas de los mortales. Algunos mortales " -"atrevidos y poderosos incluso se han aliado con ambos bandos y han ayudado a " -"dictar el curso de la Guerra del Pecado." +#: Source/translation_dummy.cpp:707 +msgid "atrophy" +msgstr "de la atrofia" -#. 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 "" -"Presta atención y da testimonio de las verdades que se encuentran aquí, porque " -"son el último legado de los Horadrim. Hace casi trescientos años, se supo que " -"los Tres Malignos Principales de los Infiernos Ardientes habían llegado " -"misteriosamente a nuestro mundo. Los Tres Hermanos asolaron las tierras del este " -"durante décadas, mientras que la humanidad quedó temblando a su paso. Nuestra " -"Orden, los Horadrim, fue fundada por un grupo de magos secretos para perseguir y " -"capturar a los Tres Malignos de una vez por todas.\n" -" \n" -"El Horadrim original capturó a dos de los Tres dentro de poderosos artefactos " -"conocidos como Piedras del Alma y los enterró profundamente bajo las desoladas " -"arenas del este. El tercer Maligno escapó de la captura y huyó hacia el oeste " -"con muchos de los Horadrim persiguiéndolo. El Tercer Mal, conocido como Diablo, " -"el Señor del Terror, finalmente fue capturado, su esencia colocada en una Piedra " -"del Alma y enterrada dentro de este Laberinto.\n" -" \n" -"Se advierte que debe evitarse que la Piedra del Alma sea descubierta por " -"aquellos que no son de la fe. Si Diablo fuera liberado, buscaría un cuerpo que " -"fuera fácil de controlar, y que fuera muy débil, tal vez el de un anciano o un " -"niño." +#: Source/translation_dummy.cpp:708 +msgid "dexterity" +msgstr "de la destreza" -#. 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 "" -"Así sucedió que hubo una gran revolución dentro de los Infiernos Ardientes " -"conocida como El Exilio Oscuro. Los Malignos Menores derrocaron a los Tres " -"Malignos Principales y desterraron sus formas espirituales al reino de los " -"mortales. Los demonios Belial (el Señor de las Mentiras) y Azmodan (el Señor del " -"Pecado) lucharon para reclamar el poder del Infierno durante la ausencia de los " -"Tres Hermanos. Todo el Infierno se polarizó entre las facciones de Belial y " -"Azmodan mientras las fuerzas de los Altos Cielos golpeaban continuamente las " -"mismas Puertas del Infierno." +#: Source/translation_dummy.cpp:709 +msgid "skill" +msgstr "de la habilidad" -#. 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 "" -"Muchos demonios viajaron al reino de los mortales en busca de los Tres Hermanos. " -"Estos demonios fueron seguidos al plano mortal por Ángeles que los cazaron por " -"las vastas pueblos del Este. Los Ángeles se aliaron con una Orden secreta de " -"magos mortales llamada Los Horadrim, quienes rápidamente se volvieron expertos " -"en cazar demonios. Estos también se hicieron muchos enemigos oscuros en los " -"inframundos." +#: Source/translation_dummy.cpp:710 +msgid "accuracy" +msgstr "de la exactitud" -#. 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 "" -"Así sucedió que los Tres Malignos Principales fueron desterrados en forma " -"espiritual al reino de los mortales y después de sembrar el caos en el Este " -"durante décadas, fueron perseguidos por la Orden maldita de los mortales " -"Horadrim. Los Horadrim usaron artefactos llamados Piedras del Alma para contener " -"la esencia de Mephisto, el Señor del Odio y su hermano Baal, el Señor de la " -"Destrucción. El hermano menor, Diablo, el Señor del Terror, escapó hacia el " -"oeste.\n" -" \n" -"Finalmente, los Horadrim capturaron a Diablo dentro de una Piedra del Alma y lo " -"enterraron bajo una antigua y olvidada Catedral. Allí, el Señor del Terror " -"duerme y espera el momento de su renacimiento. Sepan que buscará un cuerpo de " -"juventud y poder para poseer, uno que sea inocente y fácil de controlar. Luego " -"se levantará para liberar a sus Hermanos y avivar una vez más las llamas de la " -"Guerra del Pecado ..." +#: Source/translation_dummy.cpp:711 +msgid "precision" +msgstr "de la precisió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 "" -"Todas las alabanzas a Diablo, Señor del Terror y Sobreviviente del Exilio " -"Oscuro. Cuando despertó de su largo letargo, mi Señor y Maestro me habló de " -"secretos que pocos mortales conocen. Me dijo que los reinos de los Altos Cielos " -"y los pozos de los Infiernos Abrasadores se enzarzan en una guerra eterna. Él " -"reveló los poderes que han traído esta discordia a los reinos del hombre. Mi " -"señor ha llamado a la batalla por este mundo, y a todos los que existen aquí, la " -"Guerra del Pecado." +#: Source/translation_dummy.cpp:712 +msgid "perfection" +msgstr "de la perfección" -#. 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 "" -"Gloria y Aprobación a Diablo, Señor del Terror y Líder de los Tres. Mi Señor me " -"habló de sus dos hermanos, Mefisto y Baal, que fueron desterrados a este mundo " -"hace mucho tiempo. Mi Señor desea esperar el momento oportuno y aprovechar su " -"asombroso poder para liberar a sus hermanos cautivos de sus tumbas bajo las " -"arenas del este. Una vez que mi Señor libere a sus Hermanos, la Guerra del " -"Pecado volverá a conocer la furia de los Tres." +#: Source/translation_dummy.cpp:713 +msgid "the fool" +msgstr "del mentecato" -#. 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 "" -"Aclamación y Sacrificio a Diablo, Señor del Terror y Destructor de Almas. Cuando " -"desperté a mi Maestro de su sueño, intentó poseer la forma de un mortal. Diablo " -"intentó reclamar el cuerpo del Rey Leoric, pero mi Maestro estaba demasiado " -"débil por su encarcelamiento. Mi señor necesitaba un ancla simple e inocente a " -"este mundo, y por eso encontró que el niño Albrecht era perfecto para la tarea. " -"Mientras el buen Rey Leoric estaba enloquecido por la posesión fallida de " -"Diablo, yo secuestré a su hijo Albrecht y lo llevé ante mi Maestro. Ahora espero " -"la llamada de Diablo y rezo para ser recompensado cuando él finalmente emerja " -"como el Señor de este mundo." +#: Source/translation_dummy.cpp:714 +msgid "dyslexia" +msgstr "de la dislexia" -#. 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 "" -"¡Gracias a Dios que has vuelto!\n" -"Mucho ha cambiado desde que viviste aquí, amigo. Todo estaba en paz hasta que " -"llegaron los jinetes oscuros y destruyeron nuestra aldea. Muchos fueron " -"asesinados donde estaban, y los que tomaron las armas fueron asesinados o " -"arrastrados para convertirse en esclavos, o algo peor. La iglesia en las afueras " -"del pueblo ha sido profanada y se utiliza para rituales oscuros. Los gritos que " -"resuenan en la noche son inhumanos, pero es posible que algunos de nuestros " -"habitantes hayan sobrevivido. Sigue el camino que se encuentra entre mi taberna " -"y la herrería para encontrar la iglesia y salvar a quien puedas. \n" -" \n" -"Quizás pueda contarte más si volvemos a hablar. Buena suerte." +#: Source/translation_dummy.cpp:715 +msgid "magic" +msgstr "de la energía" -#. 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 "" -"Más allá del Salón de los Héroes se encuentra la Cámara de Hueso. La muerte " -"eterna aguarda a cualquiera que busque robar los tesoros guardados dentro de " -"esta habitación. Así habla el Señor del Terror, y así está escrito." +#: Source/translation_dummy.cpp:716 +msgid "the mind" +msgstr "de la mente" -#. 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 "" -"Las armerías del Infierno son el hogar del Señor de la Guerra de la Sangre. A su " -"paso yacían los cuerpos mutilados de miles. Tanto los Ángeles como los hombres " -"han sido liquidados para cumplir con sus sacrificios interminables a los Oscuros " -"que gritan por una cosa: sangre." +#: Source/translation_dummy.cpp:717 +msgid "brilliance" +msgstr "de la brillantez" -#. 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 "" -"Mantén tu búsqueda. Encontrar un tesoro perdido no es fácil. Encontrar un tesoro " -"que está escondido, aún menos. Te dejaré con esto. No permitas que las arenas " -"del tiempo confundan tu búsqueda." +#: Source/translation_dummy.cpp:718 +msgid "sorcery" +msgstr "de la brujería" -#. 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 "" -"¿Un qué? ¡Esto es una tontería!. No hay ningún tesoro enterrado aquí en " -"Tristram. ¡Déjame ver eso! Ah, mira, estos dibujos son inexactos. No coinciden " -"en absoluto con nuestro pueblo. Me concentraría en lo que hay debajo de la " -"catedral y no en lo que hay debajo de nuestra capa superficial del suelo." +#: Source/translation_dummy.cpp:719 +msgid "wizardry" +msgstr "de la hechicería" -#. TRANSLATORS: Quest text spoken by Pepin -#: 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 "" -"Realmente no tengo tiempo para discutir sobre algún mapa que estás buscando. " -"Tengo muchos enfermos que necesitan mi ayuda y la tuya también." +#: Source/translation_dummy.cpp:720 +msgid "illness" +msgstr "de la dolencia" -#. 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 "" -"El otrora orgulloso Iswall está atrapado en las profundidades de este mundo. Su " -"honor fue despojado y su rostro alterado. Está atrapado en un tormento inmortal. " -"Encargado de ocultar lo mismo que podría liberarlo." +#: Source/translation_dummy.cpp:721 +msgid "disease" +msgstr "de la enfermedad" -#. 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 "" -"Apuesto a que Wirt te vio venir y fingió un acto para poder reírse de ti más " -"tarde, cuando estabas corriendo por el pueblo con la nariz en la tierra. Yo lo " -"ignoraría." +#: Source/translation_dummy.cpp:722 +msgid "vitality" +msgstr "de la vitalidad" -#. 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 "" -"Hubo un tiempo en que este pueblo era una parada frecuente para los viajeros de " -"todas partes. Mucho ha cambiado desde entonces. Pero las cuevas escondidas y los " -"tesoros enterrados son fantasías comunes de cualquier niño. Wirt rara vez se " -"entrega a juegos juveniles. Así que puede que sea sólo su imaginación." +#: Source/translation_dummy.cpp:723 +msgid "zest" +msgstr "de la vivacidad" -#. 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 "" -"Escucha. Acércate. No sé si sabes lo que yo sé, pero realmente tienes algo aquí. " -"Eso es un mapa." +#: Source/translation_dummy.cpp:724 +msgid "vim" +msgstr "del brío" -#. 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 "" -"Mi abuela a menudo me cuenta historias sobre las extrañas fuerzas que habitan el " -"cementerio fuera de la iglesia. Y puede que le interese escuchar uno de ellos. " -"Dijo que si dejaba la ofrenda adecuada en el cementerio, entraba a la catedral " -"para rezar por los muertos y luego regresaba, la ofrenda se alteraría de alguna " -"manera extraña. No sé si esto es solo la charla de una anciana enferma, pero " -"todo parece posible en estos días." +#: Source/translation_dummy.cpp:725 +msgid "vigor" +msgstr "del vigor" -#. 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 "" -"Mmmm ¿Un tesoro vasto y misterioso, dices? Mmmm. Tal vez podría estar interesado " -"en adquirir algunas cosas tuyas... o mejor aún, ¿no necesitas algunos " -"suministros raros y costosos para superar esta prueba?" +#: Source/translation_dummy.cpp:726 +msgid "life" +msgstr "de la vida" -#. 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 "" -"Entonces, eres el héroe del que todos han estado hablando. ¿Quizás podrías " -"ayudar a un simple granjero pobre a salir de un lío terrible? En el borde de mi " -"huerto, al sur de aquí, ¡hay una cosa horrible que se hincha en el suelo! No " -"puedo llegar a mis cultivos ni a mis fardos de heno, y mis pobres vacas se " -"morirán de hambre. La bruja me dio esto y dijo que volaría esa cosa fuera de mi " -"campo. Si pudieras destruirlo, te estaré eternamente agradecido. Lo haría yo " -"mismo, pero alguien tiene que quedarse aquí con las vacas ..." +#: Source/translation_dummy.cpp:727 +msgid "trouble" +msgstr "del problema" -#. 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 "" -"Sabía que no podía ser tan simple como lo decía la bruja. Es un mundo triste " -"cuando ni siquiera puedes confiar en tus vecinos." +#: Source/translation_dummy.cpp:728 +msgid "the pit" +msgstr "del hoyo" -#. 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 "" -"¿Se ha ido? ¿Lo enviaste de vuelta a los oscuros recovecos del Hades que lo " -"engendraron? ¿Tu que? ¡Oh, no me digas que lo perdiste! Esas cosas no son " -"baratas, ¿sabes? Tienes que encontrarlo y luego sacar ese horror de nuestro " -"pueblo." +#: Source/translation_dummy.cpp:729 +msgid "the sky" +msgstr "del cielo" -#. 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 "" -"¡Escuché la explosión desde aquí! Muchas gracias a ti, amable extraño. Con todas " -"estas cosas que salen de la tierra, los monstruos que se apoderan de la iglesia, " -"y esas cosas, estos son tiempos difíciles. No soy más que un agricultor pobre, " -"pero toma esto con gran agradecimiento." +#: Source/translation_dummy.cpp:730 +msgid "the moon" +msgstr "de la luna" -#. 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 "" -"Oh, qué problema tengo ... tal vez ... No, no podría imponértelo, con todos los " -"otros problemas. Quizás, después de haber limpiado la iglesia de algunas de esas " -"criaturas, ¿podrías regresar ... y dedicar un poco de tiempo para ayudar a un " -"pobre agricultor?" +#: Source/translation_dummy.cpp:731 +msgid "the stars" +msgstr "de las estrellas" -#. TRANSLATORS: Quest text spoken by Little Girl -#: Source/textdat.cpp:571 -msgid "Waaaah! (sniff) Waaaah! (sniff)" -msgstr "Waaaah! (sniff) Waaaah! (sniff)" +#: Source/translation_dummy.cpp:732 +msgid "the heavens" +msgstr "de los cielos" -#. 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 "" -"¡Perdí a Theo! ¡Perdí a mi mejor amigo! Estábamos jugando junto al río, y Theo " -"dijo que quería ir a ver la gran cosa verde. Dije que no deberíamos, pero nos " -"colamos allí, ¡y de repente salió este INSECTO! ¡Huimos, pero Theo se cayó y el " -"insecto lo agarró y se lo llevó!" +#: Source/translation_dummy.cpp:733 +msgid "the zodiac" +msgstr "del zodiaco" -#. 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 "" -"¿Didja lo encontró? ¡Tienes que encontrar a Theodore, por favor! Es solo " -"pequeño. ¡No puede cuidarse solo! ¡Por favor!" +#: Source/translation_dummy.cpp:734 +msgid "the vulture" +msgstr "del buitre" -#. 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 "" -"¡Lo encontraste! ¡Lo encontraste! ¡Gracias! Oh Theo, ¿esos bichos desagradables " -"te asustaron? ¡Oye! ¡Puaj! ¡Hay algo pegado a tu pelaje! ¡Agh! ¡Vamos, Theo, " -"vámonos a casa! ¡Gracias de nuevo, héroe!" +#: Source/translation_dummy.cpp:735 +msgid "the jackal" +msgstr "del chacal" -#. 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 "" -"Llevamos mucho tiempo dormidos y ha llegado el momento de despertar. Después de " -"nuestro largo sueño, nos llena un gran hambre. Pronto, ahora, nos " -"alimentaremos ..." +#: Source/translation_dummy.cpp:736 +msgid "the fox" +msgstr "del zorro" -#. 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 "" -"¿Te has estado divirtiendo, pequeño mamífero? Que patético. Tu pequeño mundo no " -"será ningún desafío." +#: Source/translation_dummy.cpp:737 +msgid "the jaguar" +msgstr "del jaguar" -#. 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 "" -"Estas tierras serán contaminadas y nuestra prole invadirá los campos que los " -"hombres llaman hogar. Nuestros zarcillos envolverán este mundo y nos " -"deleitaremos con la carne de sus habitantes. El hombre se convertirá en nuestro " -"esclavos y sustento." +#: Source/translation_dummy.cpp:738 +msgid "the eagle" +msgstr "del águila" -#. 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 "" -"Ah, te puedo oler... ¡estás cerca! ¡Cerca! Ssss... el olor a sangre y miedo... " -"qué tentador..." +#: Source/translation_dummy.cpp:739 +msgid "the wolf" +msgstr "del lobo" + +#: Source/translation_dummy.cpp:740 +msgid "the tiger" +msgstr "del tigre" + +#: Source/translation_dummy.cpp:741 +msgid "the lion" +msgstr "del león" + +#: Source/translation_dummy.cpp:742 +msgid "the mammoth" +msgstr "del mamut" -#. 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 "" -"Y en el año de la Luz Dorada, se decretó que se levantara una gran Catedral. La " -"piedra angular de este lugar sagrado debía ser tallada en la piedra translúcida " -"Antyrael, llamada así por el Ángel que compartía su poder con los Horadrim.\n" -" \n" -"En el Año designado a las Sombras, el suelo tembló y la Catedral se hizo añicos " -"y cayó. Cuando comenzó la construcción de catacumbas y castillos y el hombre se " -"enfrentó a los estragos de la Guerra del Pecado, las ruinas fueron escarbadas en " -"busca de sus piedras. Y así fue como la piedra angular desapareció de los ojos " -"del hombre.\n" -" \n" -"La piedra era de este mundo , y de todos los mundos , ya que la Luz está tanto " -"dentro de todas las cosas como más allá de todas las cosas. La luz y la unidad " -"son los productos de este fundamento santo, una unidad de propósito y una unidad " -"de posesión." +#: Source/translation_dummy.cpp:743 +msgid "the whale" +msgstr "de la ballena" -#. TRANSLATORS: Quest text spoken by Complete Nut -#: Source/textdat.cpp:594 -msgid "Moo." -msgstr "Muu." +#: Source/translation_dummy.cpp:744 +msgid "fragility" +msgstr "de la fragilidad" -#. TRANSLATORS: Quest text spoken by Complete Nut -#: Source/textdat.cpp:595 -msgid "I said, Moo." -msgstr "Dije, Muu." +#: Source/translation_dummy.cpp:745 +msgid "brittleness" +msgstr "de la inconsistencia" -#. TRANSLATORS: Quest text spoken by Complete Nut -#: Source/textdat.cpp:596 -msgid "Look I'm just a cow, OK?" -msgstr "Mira, solo soy una vaca, ¿de acuerdo?" +#: Source/translation_dummy.cpp:746 +msgid "sturdiness" +msgstr "de la robustez" -#. 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 "" -"Bien, bien. Realmente no soy una vaca. Normalmente no ando así; pero, estaba " -"sentado en casa ocupándome de mis propios asuntos y, de repente, estos insectos, " -"enredaderas, bulbos y cosas empezaron a salir del suelo ... ¡fue horrible! Si " -"tan solo tuviera algo normal para ponerme, no estaría tan mal. ¡Oye! ¿Podrías " -"volver a mi casa y traerme mi traje? El marrón, no el gris, es para la noche. Lo " -"haría yo mismo, pero no quiero que nadie me vea así. Ten, toma esto, puede que " -"lo necesites ... para matar a esas cosas que han crecido demasiado. No puedes " -"perderte, mi casa está justo al sur de la bifurcación del río ... ya sabes ... " -"la del huerto descuidado." +#: Source/translation_dummy.cpp:747 +msgid "craftsmanship" +msgstr "de la artesanía" -#. 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 "" -"¿En qué estás perdiendo el tiempo? ¡Ve por mi traje! ¡Y date prisa! ¡Ese " -"Holstein de allí no deja de guiñarme el ojo!" +#: Source/translation_dummy.cpp:748 +msgid "structure" +msgstr "de la solidez" -#. 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 "" -"Oye, ¿tienes mi traje ahí? ¡Rápido, pásalo! ¡Estos oídos pican como no lo " -"creerías!" +#: Source/translation_dummy.cpp:749 +msgid "the ages" +msgstr "de las eras" -#. 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 "" -"¡No no no no! ¡Este es mi traje GRIS! ¡Es para la noche! ¡Ocasiones formales! No " -"puedo usar ESTO. ¿Qué eres, una especie de bicho raro? Necesito el traje MARRÓN." +#: Source/translation_dummy.cpp:750 +msgid "the dark" +msgstr "de la oscuridad" -#. 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 "" -"Ahh, eso es MUCHO mejor. ¡Uf! ¡Por fin, algo de dignidad! ¿Mis astas están " -"rectas? Bien. Mira, muchas gracias por ayudarme. Ten, toma esto como un regalo; " -"y, ya sabes ... un pequeño consejo de moda ... te vendría bien un poco ... " -"podrías usar un nuevo ... ¿sabes lo que quiero decir? Todo el motivo de los " -"aventureros es tan ... retro. Solo un consejo, ¿eh? Chao." +#: Source/translation_dummy.cpp:751 +msgid "the night" +msgstr "de la noche" -#. 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 "" -"Mirar Soy una vaca. Y tú, eres un cebo para monstruos. ¡Consigue algo de " -"experiencia debajo de tu cinturón! Hablaremos ..." +#: Source/translation_dummy.cpp:752 +msgid "light" +msgstr "de la luz" -#. 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 "" -"Realmente debe ser una tarea temible la que te he propuesto. Si hubiera alguna " -"manera de que pudiera ... ¿ayudaría una jarra de leche fresca?" +#: Source/translation_dummy.cpp:753 +msgid "radiance" +msgstr "del resplandor" -#. 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 "" -"Oh, me vendría bien tu ayuda, pero quizás después de que hayas salvado las " -"catacumbas de la profanación de esas bestias." +#: Source/translation_dummy.cpp:754 +msgid "flame" +msgstr "de la llama" -#. 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 "" -"Necesito que se haga algo, pero no podría obligar a un perfecto desconocido. " -"Quizás después de que hayas estado aquí un tiempo me sienta más cómodo pidiendo " -"un favor." +#: Source/translation_dummy.cpp:755 +msgid "fire" +msgstr "del fuego" -#. 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 "" -"Veo en ti el potencial de grandeza. ¿Quizás en algún momento mientras estás " -"cumpliendo tu destino, podrías pasar y hacerme un pequeño favor?" +#: Source/translation_dummy.cpp:756 +msgid "burning" +msgstr "de la combustión" -#. 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 "" -"Creo que probablemente podrías ayudarme, pero quizás después de que te hayas " -"vuelto un poco más poderoso. ¡No quisiera dañar la única oportunidad que tiene " -"el pueblo de destruir la amenaza en la iglesia!" +#: Source/translation_dummy.cpp:757 +msgid "shock" +msgstr "de la conmoción" -#. 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 "" -"Yo soy una vaca hecha a sí misma. Haz algo de ti mismo y ... luego hablaremos." +#: Source/translation_dummy.cpp:758 +msgid "lightning" +msgstr "del relámpago" -#. 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 "" -"¡No tengo que dar explicaciones a todos los turistas que pasan! ¿No tienes " -"algunos monstruos que matar? Quizás hablemos más tarde. Si sigues vivo .." +#: Source/translation_dummy.cpp:759 +msgid "thunder" +msgstr "del trueno" -#. 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 "" -"Deja de molestarme. Busco a alguien realmente heroico. Y no lo eres. No puedo " -"confiar en ti, vas a ser devorado por monstruos en cualquier momento ... " -"Necesito a alguien que sea un héroe experimentado." +#: Source/translation_dummy.cpp:760 +msgid "many" +msgstr "de la abundancia" -#. 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 "" -"Muy bien, iré al grano. No quise guiarte mal. Estaba sentado en casa, " -"sintiéndome malhumorado, cuando las cosas se pusieron realmente inestables; " -"¡Toda una estampida de monstruos salió del suelo! Solo me acobarde. Estaba " -"usando esta camiseta cuando salí corriendo por la puerta, y ahora me veo " -"terriblemente ridículo. Si tan solo tuviera algo normal para ponerme, no estaría " -"tan mal. ¡Oye! ¿Puedes volver a mi casa y traerme mi traje? El marrón, no el " -"gris, es para la noche. Lo haría yo mismo, pero no quiero que nadie me vea así. " -"Ten, toma esto, puede que lo necesites ... para matar esas cosas que han crecido " -"demasiado. No puedes perderte, mi casa está justo al sur de la bifurcación del " -"río ... ya sabes ... la del huerto descuidado." +#: Source/translation_dummy.cpp:761 +msgid "plenty" +msgstr "de la infinidad" -#. 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 "" -"Hoy nublado y fresco. Lanzando las redes de la nigromancia en el vacío caerán " -"dos nuevas subespecies de terror volador; un buen día de trabajo. Debes recordar " -"pedir más guano de murciélago y velas negras de Adria; Me estoy quedando un poco " -"bajo." +#: Source/translation_dummy.cpp:762 +msgid "thorns" +msgstr "de espinas" -#. 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 "" -"He intentado hechizos, amenazas, abjuración y regateo con esta criatura " -"repugnante, sin éxito. Mis métodos para esclavizar a los demonios menores " -"parecen no tener ningún efecto sobre esta temible bestia." +#: Source/translation_dummy.cpp:763 +msgid "corruption" +msgstr "de la corrupción" -#. 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 "" -"Mi hogar se está corrompiendo lentamente por la vileza de este prisionero no " -"deseado. Las criptas están\tllenas de sombras que se mueven un poco más allá de " -"las esquinas de mi visión. El leve roce de las garras baila en los bordes de mi " -"oído. Están buscando, creo, este diario." +#: Source/translation_dummy.cpp:764 +msgid "thieves" +msgstr "del ladrón" -#. 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 "" -"En su despotricar, la criatura ha dejado escapar su nombre: Na-Krul. Intenté " -"investigar el nombre, pero los demonios más pequeños de alguna manera " -"destruyeron mi biblioteca. Na-Krul ... El nombre me llena de un pavor frío. " -"Prefiero pensar en él solo como La Criatura en lugar de reflexionar sobre su " -"verdadero nombre." +#: Source/translation_dummy.cpp:765 +msgid "the bear" +msgstr "del oso" -#. 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 "" -"Los aullidos de furia de la criatura atrapada me impiden conseguir el sueño que " -"tanto necesitaba. Se enfurece contra quien la envió al Vacío, y me maldice por " -"haberla atrapado aquí. Sus palabras me llenan el corazón de terror y, sin " -"embargo, no puedo bloquear su voz." +#: Source/translation_dummy.cpp:766 +msgid "the bat" +msgstr "del murciélago" -#. 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 "" -"Mi tiempo se acaba rápidamente. Debo registrar las formas de debilitar al " -"demonio y luego ocultar ese texto, no sea que sus secuaces encuentren alguna " -"manera de usar mi conocimiento para liberar a su señor. Espero que quien " -"encuentre este diario busque el conocimiento." +#: Source/translation_dummy.cpp:767 +msgid "vampires" +msgstr "del vampiro" -#. 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 "" -"Quien encuentre este pergamino está encargado de detener a la criatura demoníaca " -"que se encuentra dentro de estos muros. Mi tiempo se acabado. Incluso ahora, sus " -"infernales secuaces se aferran a la frágil puerta detrás de la cual me escondo.\n" -" \n" -"He restringido al demonio con magia arcana y lo he encerrado dentro de grandes " -"muros, pero me temo que eso no será suficiente.\n" -" \n" -"Los hechizos que se encuentran en mis tres grimorios te proporcionarán una " -"entrada protegida a su dominio, pero solo si se lanzan en la secuencia adecuada. " -"Las palancas en la entrada quitarán las barreras y liberarán al demonio ¡No las " -"toques! Usa solo estos hechizos para lograr entrar o su poder puede ser " -"demasiado grande para derrotarlo." +#: Source/translation_dummy.cpp:768 +msgid "the leech" +msgstr "de la sanguijuela" -#. 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." +#: Source/translation_dummy.cpp:769 +msgid "blood" +msgstr "de la langosta" -#. 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." +#: Source/translation_dummy.cpp:770 +msgid "piercing" +msgstr "de la perforación" -#. 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/translation_dummy.cpp:771 +msgid "puncturing" +msgstr "del pinchazo" -#: Source/towners.cpp:81 -msgid "Griswold the Blacksmith" -msgstr "Griswold el Herrero" +#: Source/translation_dummy.cpp:772 +msgid "bashing" +msgstr "del golpe" -#: Source/towners.cpp:103 -msgid "Ogden the Tavern owner" -msgstr "Ogden el Tabernero" +#: Source/translation_dummy.cpp:773 +msgid "readiness" +msgstr "de la buena voluntad" -#: Source/towners.cpp:112 -msgid "Wounded Townsman" -msgstr "Aldeano Herido" +#: Source/translation_dummy.cpp:774 +msgid "swiftness" +msgstr "de la presteza" -#: Source/towners.cpp:134 -msgid "Adria the Witch" -msgstr "Adria la Bruja" +#: Source/translation_dummy.cpp:775 +msgid "speed" +msgstr "de la velocidad" -#: Source/towners.cpp:143 -msgid "Gillian the Barmaid" -msgstr "Gillian la Camarera" +#: Source/translation_dummy.cpp:776 +msgid "haste" +msgstr "de la rapidez" -#: Source/towners.cpp:174 -msgid "Pepin the Healer" -msgstr "Pepin el Curandero" +#: Source/translation_dummy.cpp:777 +msgid "balance" +msgstr "del equilibrio" -#: Source/towners.cpp:191 -msgid "Cain the Elder" -msgstr "Caín el Sabio" +#: Source/translation_dummy.cpp:778 +msgid "stability" +msgstr "de la estabilidad" -#: Source/towners.cpp:218 -msgid "Cow" -msgstr "Vaca" +#: Source/translation_dummy.cpp:779 +msgid "harmony" +msgstr "de la armonía" -#: Source/towners.cpp:242 -msgid "Lester the farmer" -msgstr "Lester el granjero" +#: Source/translation_dummy.cpp:780 +msgid "blocking" +msgstr "de bloqueo" -#: Source/towners.cpp:255 -msgid "Complete Nut" -msgstr "Loco de Remate" +#: Source/translation_dummy.cpp:781 +msgid "devastation" +msgstr "de la devastación" -#: Source/towners.cpp:264 -msgid "Celia" -msgstr "Celia" +#: Source/translation_dummy.cpp:782 +msgid "decay" +msgstr "de la decadencia" -#: Source/towners.cpp:277 -msgid "Slain Townsman" -msgstr "Aldeano Asesinado" +#: Source/translation_dummy.cpp:783 +msgid "peril" +msgstr "del riesgo" #. TRANSLATORS: Thousands separator -#: Source/utils/format_int.cpp:29 +#: Source/utils/format_int.cpp:27 msgid "," msgstr "," +#~ msgid "/help" +#~ msgstr "/ayuda" + +#~ msgid "({command})" +#~ msgstr "({command})" + +#~ msgid "/arena" +#~ msgstr "/arena" + +#~ msgid "Command \"" +#~ msgstr "Comando \"" + +#~ msgid "No automap available in town" +#~ msgstr "Automapa no disponible en la ciudad" + +#~ 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 "" +#~ "Más allá del Salón de los Héroes se encuentra la Cámara de Hueso. La " +#~ "muerte eterna aguarda a cualquiera que busque robar los tesoros guardados " +#~ "dentro de esta habitación. Así habla el Señor del Terror, y así está " +#~ "escrito." + +#~ 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 "" +#~ "Las armerías del Infierno son el hogar del Señor de la Guerra de la " +#~ "Sangre. A su paso yacían los cuerpos mutilados de miles. Tanto los " +#~ "Ángeles como los hombres han sido liquidados para cumplir con sus " +#~ "sacrificios interminables a los Oscuros que gritan por una cosa: sangre." + +#~ 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 "" +#~ "Hoy nublado y fresco. Lanzando las redes de la nigromancia en el vacío " +#~ "caerán dos nuevas subespecies de terror volador; un buen día de trabajo. " +#~ "Debes recordar pedir más guano de murciélago y velas negras de Adria; Me " +#~ "estoy quedando un poco bajo." + #~ msgid "left-click to target" #~ msgstr "clic izquierdo para apuntar" diff --git a/Translations/fr.po b/Translations/fr.po index fddf44324e8..17157ab81d9 100644 --- a/Translations/fr.po +++ b/Translations/fr.po @@ -3468,7 +3468,7 @@ msgstr "Lune Gibbeuse" #: Source/itemdat.cpp:458 msgid "Ice Shank" -msgstr "Tringle de Glace" +msgstr "Surin de Glace" #: Source/itemdat.cpp:459 msgid "The Executioner's Blade" diff --git a/Translations/ja.po b/Translations/ja.po index 869eff32d2f..f20754b7909 100644 --- a/Translations/ja.po +++ b/Translations/ja.po @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: DevilutionX\n" -"POT-Creation-Date: 2023-07-16 15:50+0900\n" +"POT-Creation-Date: 2023-09-30 17:22+0900\n" "PO-Revision-Date: \n" "Last-Translator: bubio \n" "Language-Team: \n" @@ -329,27 +329,29 @@ msgstr "クラス​選択" #. TRANSLATORS: Player Block start #. HeroClass::Warrior -#: Source/DiabloUI/hero/selhero.cpp:159 Source/playerdat.cpp:89 +#: Source/DiabloUI/hero/selhero.cpp:159 Source/playerdat.cpp:254 msgid "Warrior" msgstr "ウォー​リアー" -#: Source/DiabloUI/hero/selhero.cpp:160 Source/playerdat.cpp:90 +#: Source/DiabloUI/hero/selhero.cpp:160 Source/playerdat.cpp:255 msgid "Rogue" msgstr "ローグ" -#: Source/DiabloUI/hero/selhero.cpp:161 Source/playerdat.cpp:91 +#: Source/DiabloUI/hero/selhero.cpp:161 Source/playerdat.cpp:256 msgid "Sorcerer" msgstr "ソーサラー" -#: Source/DiabloUI/hero/selhero.cpp:163 Source/playerdat.cpp:92 +#: Source/DiabloUI/hero/selhero.cpp:163 Source/playerdat.cpp:257 msgid "Monk" msgstr "モンク" -#: Source/DiabloUI/hero/selhero.cpp:166 Source/playerdat.cpp:93 +#: Source/DiabloUI/hero/selhero.cpp:165 Source/playerdat.cpp:258 msgid "Bard" msgstr "バード" -#: Source/DiabloUI/hero/selhero.cpp:169 Source/playerdat.cpp:94 +#. TRANSLATORS: Player Block end +#. HeroClass::Barbarian +#: Source/DiabloUI/hero/selhero.cpp:168 Source/playerdat.cpp:260 msgid "Barbarian" msgstr "バーバリアン" @@ -538,8 +540,8 @@ msgstr "ひとり​で​プレイ​し​て​も、ネットワーク​に msgid "Players Supported: {:d}" msgstr "サポート​プレイヤー​数: {:d}" -#: Source/DiabloUI/multi/selgame.cpp:86 Source/options.cpp:571 -#: Source/options.cpp:610 Source/quests.cpp:53 +#: Source/DiabloUI/multi/selgame.cpp:86 Source/options.cpp:572 +#: Source/options.cpp:611 Source/quests.cpp:57 msgid "Diablo" msgstr "ディアブロ" @@ -547,8 +549,8 @@ msgstr "ディアブロ" msgid "Diablo Shareware" msgstr "ディアブロ​・​シェアウェア" -#: Source/DiabloUI/multi/selgame.cpp:92 Source/options.cpp:573 -#: Source/options.cpp:624 +#: Source/DiabloUI/multi/selgame.cpp:92 Source/options.cpp:574 +#: Source/options.cpp:625 msgid "Hellfire" msgstr "ヘルファイア" @@ -594,13 +596,13 @@ msgstr "ゲーム​に​参加" msgid "Public Games" msgstr "パブリック​・​ゲーム" -#: Source/DiabloUI/multi/selgame.cpp:158 Source/error.cpp:63 +#: Source/DiabloUI/multi/selgame.cpp:158 Source/error.cpp:69 msgid "Loading..." msgstr "ロード​中​…" #. TRANSLATORS: type of dungeon (i.e. Cathedral, Caves) #: Source/DiabloUI/multi/selgame.cpp:160 Source/discord/discord.cpp:73 -#: Source/options.cpp:592 Source/panels/charpanel.cpp:137 +#: Source/options.cpp:593 Source/panels/charpanel.cpp:138 msgid "None" msgstr "なし" @@ -635,25 +637,25 @@ msgstr "すでに​進行中​の​パブリック​・​ゲーム​に​ #: Source/DiabloUI/multi/selgame.cpp:234 Source/DiabloUI/multi/selgame.cpp:328 #: Source/DiabloUI/multi/selgame.cpp:389 Source/DiabloUI/multi/selgame.cpp:495 -#: Source/DiabloUI/multi/selgame.cpp:515 Source/automap.cpp:768 -#: Source/discord/discord.cpp:102 +#: Source/DiabloUI/multi/selgame.cpp:515 Source/automap.cpp:1461 +#: Source/discord/discord.cpp:101 msgid "Normal" msgstr "通常" #: Source/DiabloUI/multi/selgame.cpp:237 Source/DiabloUI/multi/selgame.cpp:329 -#: Source/DiabloUI/multi/selgame.cpp:393 Source/automap.cpp:771 -#: Source/discord/discord.cpp:102 +#: Source/DiabloUI/multi/selgame.cpp:393 Source/automap.cpp:1464 +#: Source/discord/discord.cpp:101 msgid "Nightmare" msgstr "ナイトメア" #: Source/DiabloUI/multi/selgame.cpp:240 Source/DiabloUI/multi/selgame.cpp:330 -#: Source/DiabloUI/multi/selgame.cpp:397 Source/automap.cpp:774 -#: Source/discord/discord.cpp:68 Source/discord/discord.cpp:102 +#: Source/DiabloUI/multi/selgame.cpp:397 Source/automap.cpp:1467 +#: Source/discord/discord.cpp:68 Source/discord/discord.cpp:101 msgid "Hell" msgstr "ヘル" #. TRANSLATORS: {:s} means: Game Difficulty. -#: Source/DiabloUI/multi/selgame.cpp:243 Source/automap.cpp:778 +#: Source/DiabloUI/multi/selgame.cpp:243 Source/automap.cpp:1471 msgid "Difficulty: {:s}" msgstr "難易度 {:s}" @@ -803,39 +805,39 @@ msgstr "ヘルファイア​へ" msgid "Switch to Diablo" msgstr "ディアブロ​に​切り替え" -#: Source/DiabloUI/selyesno.cpp:57 Source/stores.cpp:1001 +#: Source/DiabloUI/selyesno.cpp:57 Source/stores.cpp:958 msgid "Yes" msgstr "はい" -#: Source/DiabloUI/selyesno.cpp:58 Source/stores.cpp:1002 +#: Source/DiabloUI/selyesno.cpp:58 Source/stores.cpp:959 msgid "No" msgstr "いいえ" #: Source/DiabloUI/settingsmenu.cpp:146 msgid "Press gamepad buttons to change." -msgstr "ゲームパッドのボタンを押して変更。" +msgstr "ゲーム​パッド​の​ボタン​を​押し​て​変更。" -#: Source/DiabloUI/settingsmenu.cpp:424 +#: Source/DiabloUI/settingsmenu.cpp:422 msgid "Bound key:" msgstr "キーバインド:" -#: Source/DiabloUI/settingsmenu.cpp:460 +#: Source/DiabloUI/settingsmenu.cpp:458 msgid "Press any key to change." msgstr "いずれ​か​の​キー​を​押す​と​変更​さ​れ​ます。" -#: Source/DiabloUI/settingsmenu.cpp:462 +#: Source/DiabloUI/settingsmenu.cpp:460 msgid "Unbind key" msgstr "キーバインド​解除" -#: Source/DiabloUI/settingsmenu.cpp:466 +#: Source/DiabloUI/settingsmenu.cpp:464 msgid "Bound button combo:" -msgstr "コンボボタンのバインド:" +msgstr "コンボ​ボタン​の​バインド:" -#: Source/DiabloUI/settingsmenu.cpp:475 +#: Source/DiabloUI/settingsmenu.cpp:473 msgid "Unbind button combo" -msgstr "コンボボタンのバインド解除" +msgstr "コンボ​ボタン​の​バインド​解除" -#: Source/DiabloUI/settingsmenu.cpp:519 Source/gamemenu.cpp:65 +#: Source/DiabloUI/settingsmenu.cpp:517 Source/gamemenu.cpp:65 msgid "Previous Menu" msgstr "前​の​メニュー" @@ -891,7 +893,7 @@ msgstr "" "Twitmoji​を​使用​し​て​い​ます。また、この​移植​版​で​は、zlib-license​で​ライセンス​さ​れ​" "て​いる​SDL​も​使用​し​て​い​ます。詳細​は、ReadMe​を​ご覧​ください。" -#: Source/DiabloUI/title.cpp:57 +#: Source/DiabloUI/title.cpp:59 msgid "Copyright © 1996-2001 Blizzard Entertainment" msgstr "Copyright © 1996-2001 Blizzard Entertainment" @@ -937,95 +939,95 @@ msgstr "" msgid "Read-Only Directory Error" msgstr "Read-Only Directory Error" -#: Source/automap.cpp:723 +#: Source/automap.cpp:1416 msgid "Game: " msgstr "ゲーム: " -#: Source/automap.cpp:731 +#: Source/automap.cpp:1424 msgid "Offline Game" -msgstr "オフラインゲーム" +msgstr "オフライン​ゲーム" -#: Source/automap.cpp:733 +#: Source/automap.cpp:1426 msgid "Password: " msgstr "パスワード: " -#: Source/automap.cpp:736 +#: Source/automap.cpp:1429 msgid "Public Game" msgstr "パブリック​ゲーム" -#: Source/automap.cpp:750 +#: Source/automap.cpp:1443 msgid "Level: Nest {:d}" msgstr "レベル: 巣 {:d}" -#: Source/automap.cpp:753 +#: Source/automap.cpp:1446 msgid "Level: Crypt {:d}" msgstr "レベル: 地下​聖堂 {:d}" -#: Source/automap.cpp:756 Source/discord/discord.cpp:68 Source/objects.cpp:151 +#: Source/automap.cpp:1449 Source/discord/discord.cpp:68 Source/objects.cpp:153 msgid "Town" msgstr "タウン" -#: Source/automap.cpp:759 +#: Source/automap.cpp:1452 msgid "Level: {:d}" msgstr "レベル: {:d}" -#: Source/control.cpp:160 +#: Source/control.cpp:163 msgid "Tab" msgstr "Tab" -#: Source/control.cpp:160 +#: Source/control.cpp:163 msgid "Esc" msgstr "Esc" -#: Source/control.cpp:160 +#: Source/control.cpp:163 msgid "Enter" msgstr "入力" -#: Source/control.cpp:163 +#: Source/control.cpp:166 msgid "Character Information" msgstr "キャラクター​情報" -#: Source/control.cpp:164 +#: Source/control.cpp:167 msgid "Quests log" msgstr "クエスト​の​記録" -#: Source/control.cpp:165 +#: Source/control.cpp:168 msgid "Automap" msgstr "オート​マップ" -#: Source/control.cpp:166 +#: Source/control.cpp:169 msgid "Main Menu" msgstr "メインメニュー" -#: Source/control.cpp:167 Source/diablo.cpp:1701 Source/diablo.cpp:2019 +#: Source/control.cpp:170 Source/diablo.cpp:1736 Source/diablo.cpp:2054 msgid "Inventory" msgstr "所持​品​の​一覧" -#: Source/control.cpp:168 +#: Source/control.cpp:171 msgid "Spell book" msgstr "スペル​ブック" -#: Source/control.cpp:169 +#: Source/control.cpp:172 msgid "Send Message" msgstr "メッセージ​送信" -#: Source/control.cpp:348 +#: Source/control.cpp:353 msgid "Available Commands:" msgstr "使用​できる​コマンド: " -#: Source/control.cpp:356 +#: Source/control.cpp:361 msgid "Command " msgstr "コマンド " -#: Source/control.cpp:356 +#: Source/control.cpp:361 msgid " is unkown." msgstr "は​不明。" -#: Source/control.cpp:359 Source/control.cpp:360 +#: Source/control.cpp:364 Source/control.cpp:365 msgid "Description: " msgstr "説明: " -#: Source/control.cpp:359 +#: Source/control.cpp:364 msgid "" "\n" "Parameters: No additional parameter needed." @@ -1033,7 +1035,7 @@ msgstr "" "\n" "​パラメータ: 追加​の​パラメータ​は​必要​あり​ませ​ん。" -#: Source/control.cpp:360 +#: Source/control.cpp:365 msgid "" "\n" "Parameters: " @@ -1041,309 +1043,341 @@ msgstr "" "\n" "​パラメータ: " -#: Source/control.cpp:380 Source/control.cpp:412 +#: Source/control.cpp:385 Source/control.cpp:417 msgid "Arenas are only supported in multiplayer." msgstr "アリーナ​は​マルチ​プレイ​のみ​対応​し​て​い​ます。" -#: Source/control.cpp:385 +#: Source/control.cpp:390 msgid "What arena do you want to visit?" msgstr "行っ​て​み​たい​アリーナ​は​どこ​です​か?" -#: Source/control.cpp:393 +#: Source/control.cpp:398 msgid "Invalid arena-number. Valid numbers are:" msgstr "アリーナ​番号​が​無効​です。有効​な​番号​は​次​の​とおり​です: " -#: Source/control.cpp:399 +#: Source/control.cpp:404 msgid "To enter a arena, you need to be in town or another arena." msgstr "アリーナ​に​入る​に​は、街​か​他​の​アリーナ​に​いる​必要​が​あり​ます。" -#: Source/control.cpp:436 +#: Source/control.cpp:442 msgid "Inspecting only supported in multiplayer." -msgstr "インスペクトはマルチプレーヤーのみ対応。" +msgstr "インスペクト​は​マルチプレーヤー​のみ​対応。" -#: Source/control.cpp:441 Source/control.cpp:730 +#: Source/control.cpp:447 Source/control.cpp:743 msgid "Stopped inspecting players." -msgstr "プレイヤーのインスペクとを中止。" - -#: Source/control.cpp:451 -msgid "Inspecting player: " -msgstr "プレイヤーのインスペクト : " +msgstr "プレイヤー​の​インスペク​と​を​中止。" -#: Source/control.cpp:460 +#: Source/control.cpp:462 msgid "No players found with such a name" -msgstr "ぞのような名前のプレイヤーは見つかりませんでした" +msgstr "ぞ​の​よう​な​名前​の​プレイヤー​は​見つかり​ませ​ん​でし​た" -#: Source/control.cpp:524 +#: Source/control.cpp:468 +msgid "Inspecting player: " +msgstr "プレイヤー​の​インスペクト : " + +#: Source/control.cpp:537 msgid "/help" msgstr "/help" -#: Source/control.cpp:524 +#: Source/control.cpp:537 msgid "Prints help overview or help for a specific command." msgstr "ヘルプ​の​概要​また​は​特定​の​コマンド​の​ヘルプ​を​表示​し​ます。" -#: Source/control.cpp:524 +#: Source/control.cpp:537 msgid "[command]" -msgstr "[コマンド]" +msgstr "[​コマンド​]" -#: Source/control.cpp:525 +#: Source/control.cpp:538 msgid "/arena" msgstr "/arena" -#: Source/control.cpp:525 +#: Source/control.cpp:538 msgid "Enter a PvP Arena." msgstr "PvP​アリーナ​に​入る。" -#: Source/control.cpp:525 +#: Source/control.cpp:538 msgid "" -msgstr "<アリーナ番号>" +msgstr "<​アリーナ​番号​>" -#: Source/control.cpp:526 +#: Source/control.cpp:539 msgid "/arenapot" msgstr "/arenapot" -#: Source/control.cpp:526 +#: Source/control.cpp:539 msgid "Gives Arena Potions." -msgstr "アリーナポーションを与える。" +msgstr "アリーナ​ポーション​を​与える。" -#: Source/control.cpp:526 +#: Source/control.cpp:539 msgid "" -msgstr "<番号>" +msgstr "<​番号​>" -#: Source/control.cpp:527 +#: Source/control.cpp:540 msgid "/inspect" -msgstr "アリーナポーションを与える。" +msgstr "アリーナ​ポーション​を​与える。" -#: Source/control.cpp:527 +#: Source/control.cpp:540 msgid "Inspects stats and equipment of another player." -msgstr "他のプレーヤーの体力や装備を調べる。" +msgstr "他​の​プレーヤー​の​体力​や​装備​を​調べる。" -#: Source/control.cpp:527 +#: Source/control.cpp:540 msgid "" -msgstr "<プレイヤー名>" +msgstr "<​プレイヤー​名​>" -#: Source/control.cpp:528 +#: Source/control.cpp:541 msgid "/seedinfo" msgstr "/seedinfo" -#: Source/control.cpp:528 +#: Source/control.cpp:541 msgid "Show seed infos for current level." -msgstr "現在のレベルのシード情報を表示。" +msgstr "現在​の​レベル​の​シード​情報​を​表示。" -#: Source/control.cpp:538 +#: Source/control.cpp:551 msgid "Command \"" msgstr "コマンド “" -#: Source/control.cpp:1012 +#: Source/control.cpp:1039 msgid "Player friendly" msgstr "プレイヤー​は​味方" -#: Source/control.cpp:1014 +#: Source/control.cpp:1041 msgid "Player attack" msgstr "プレイヤー​の​攻撃" -#: Source/control.cpp:1017 +#: Source/control.cpp:1044 msgid "Hotkey: {:s}" msgstr "ホットキー: {:s}" -#: Source/control.cpp:1024 +#: Source/control.cpp:1051 msgid "Select current spell button" msgstr "正しい​呪文​ボタン​を​選択​し​て​ください" -#: Source/control.cpp:1027 +#: Source/control.cpp:1054 msgid "Hotkey: 's'" msgstr "ホットキー: ’s’" -#: Source/control.cpp:1033 Source/panels/spell_list.cpp:151 +#: Source/control.cpp:1060 Source/panels/spell_list.cpp:152 msgid "{:s} Skill" msgstr "{:s} スキル" -#: Source/control.cpp:1036 Source/panels/spell_list.cpp:158 +#: Source/control.cpp:1063 Source/panels/spell_list.cpp:159 msgid "{:s} Spell" msgstr "{:s} スペル" -#: Source/control.cpp:1038 Source/panels/spell_list.cpp:163 +#: Source/control.cpp:1065 Source/panels/spell_list.cpp:164 msgid "Spell Level 0 - Unusable" msgstr "スペル​レベル 0 - 使え​ない" -#: Source/control.cpp:1038 Source/panels/spell_list.cpp:165 +#: Source/control.cpp:1065 Source/panels/spell_list.cpp:166 msgid "Spell Level {:d}" msgstr "スペル​レベル {:d}" -#: Source/control.cpp:1041 Source/panels/spell_list.cpp:172 +#: Source/control.cpp:1068 Source/panels/spell_list.cpp:173 msgid "Scroll of {:s}" msgstr "{:s}​の​スクロール" -#: Source/control.cpp:1046 Source/panels/spell_list.cpp:177 +#: Source/control.cpp:1072 Source/panels/spell_list.cpp:177 msgid "{:d} Scroll" msgid_plural "{:d} Scrolls" msgstr[0] "{:d} スクロール" -#: Source/control.cpp:1049 Source/panels/spell_list.cpp:184 +#: Source/control.cpp:1075 Source/panels/spell_list.cpp:184 msgid "Staff of {:s}" msgstr "{:s}​・​スタッフ" -#: Source/control.cpp:1050 Source/panels/spell_list.cpp:186 +#: Source/control.cpp:1076 Source/panels/spell_list.cpp:186 msgid "{:d} Charge" msgid_plural "{:d} Charges" msgstr[0] "{:d}チャージ" -#: Source/control.cpp:1175 Source/inv.cpp:1878 Source/items.cpp:3643 +#: Source/control.cpp:1201 Source/inv.cpp:1884 Source/items.cpp:3607 msgid "{:s} gold piece" msgid_plural "{:s} gold pieces" msgstr[0] "{:s} ゴールド" -#: Source/control.cpp:1177 +#: Source/control.cpp:1203 msgid "Requirements not met" msgstr "能力​値​が​不足​し​て​い​ます" -#: Source/control.cpp:1206 +#: Source/control.cpp:1232 msgid "{:s}, Level: {:d}" msgstr "{:s}, Level: {:d}" -#: Source/control.cpp:1207 +#: Source/control.cpp:1233 msgid "Hit Points {:d} of {:d}" msgstr "ヒット​ポイント: {:d}/{:d}" -#: Source/control.cpp:1238 +#: Source/control.cpp:1264 msgid "Level Up" msgstr "レベルアップ" #. TRANSLATORS: {:s} is a number with separators. Dialog is shown when splitting a stash of Gold. -#: Source/control.cpp:1346 +#: Source/control.cpp:1372 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/cursor.cpp:326 +#: Source/cursor.cpp:640 msgid "Town Portal" msgstr "タウン​ポータル" -#: Source/cursor.cpp:327 +#: Source/cursor.cpp:641 msgid "from {:s}" msgstr "{:s}​から" -#: Source/cursor.cpp:340 +#: Source/cursor.cpp:654 msgid "Portal to" msgstr "ポータル" -#: Source/cursor.cpp:341 +#: Source/cursor.cpp:655 msgid "The Unholy Altar" msgstr "ラザルス​の​間" -#: Source/cursor.cpp:341 +#: Source/cursor.cpp:655 msgid "level 15" msgstr "レベル​15" -#: Source/diablo.cpp:125 +#. TRANSLATORS: Error message when a data file is missing or corrupt. Arguments are {file name} +#: Source/data/file.cpp:35 +msgid "Unable to load data from file {0}" +msgstr "Unable to load data from file {0}" + +#. TRANSLATORS: Error message when a data file is empty or only contains the header row. Arguments are {file name} +#: Source/data/file.cpp:40 +msgid "{0} is incomplete, please check the file contents." +msgstr "" + +#. TRANSLATORS: Error message when a data file doesn't contain the expected columns. Arguments are {file name} +#: Source/data/file.cpp:45 +msgid "" +"Your {0} file doesn't have the expected columns, please make sure it matches " +"the documented format." +msgstr "" + +#. TRANSLATORS: Error message when parsing a data file and a text value is encountered when a number is expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} +#: Source/data/file.cpp:56 +msgid "Non-numeric value {0} for {1} in {2} at row {3} and column {4}" +msgstr "" + +#. TRANSLATORS: Error message when parsing a data file and we find a number larger than expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} +#: Source/data/file.cpp:61 +msgid "Out of range value {0} for {1} in {2} at row {3} and column {4}" +msgstr "" + +#. TRANSLATORS: Error message when we find an unrecognised value in a key column. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} +#: Source/data/file.cpp:66 +msgid "Invalid value {0} for {1} in {2} at row {3} and column {4}" +msgstr "" + +#: Source/diablo.cpp:128 msgid "I need help! Come Here!" msgstr "助け​て​ください。ここ​に​来​て​!" -#: Source/diablo.cpp:126 +#: Source/diablo.cpp:129 msgid "Follow me." msgstr "つい​て​き​て。" -#: Source/diablo.cpp:127 +#: Source/diablo.cpp:130 msgid "Here's something for you." msgstr "これ​は、あなた​の​ため​に​用意​し​た​もの​です。" -#: Source/diablo.cpp:128 +#: Source/diablo.cpp:131 msgid "Now you DIE!" msgstr "さあ、死ぬ​ん​だ​!" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:917 +#: Source/diablo.cpp:926 msgid "Print this message and exit" msgstr "Print this message and exit" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:918 +#: Source/diablo.cpp:927 msgid "Print the version and exit" msgstr "Print the version and exit" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:919 +#: Source/diablo.cpp:928 msgid "Specify the folder of diabdat.mpq" msgstr "Specify the folder of diabdat.mpq" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:920 +#: Source/diablo.cpp:929 msgid "Specify the folder of save files" msgstr "Specify the folder of save files" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:921 +#: Source/diablo.cpp:930 msgid "Specify the location of diablo.ini" msgstr "Specify the location of diablo.ini" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:922 +#: Source/diablo.cpp:931 msgid "Specify the language code (e.g. en or pt_BR)" -msgstr "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:923 +#: Source/diablo.cpp:932 msgid "Skip startup videos" msgstr "Skip startup videos" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:924 +#: Source/diablo.cpp:933 msgid "Display frames per second" msgstr "Display frames per second" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:925 +#: Source/diablo.cpp:934 msgid "Enable verbose logging" msgstr "Enable verbose logging" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:927 +#: Source/diablo.cpp:936 msgid "Record a demo file" msgstr "Record a demo file" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:928 +#: Source/diablo.cpp:937 msgid "Play a demo file" msgstr "Play a demo file" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:929 +#: Source/diablo.cpp:938 msgid "Disable all frame limiting during demo playback" msgstr "Disable all frame limiting during demo playback" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:932 +#: Source/diablo.cpp:941 msgid "Game selection:" msgstr "Game selection:" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:934 +#: Source/diablo.cpp:943 msgid "Force Shareware mode" msgstr "Force Shareware mode" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:935 +#: Source/diablo.cpp:944 msgid "Force Diablo mode" msgstr "Force Diablo mode" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:936 +#: Source/diablo.cpp:945 msgid "Force Hellfire mode" msgstr "Force Hellfire mode" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:937 +#: Source/diablo.cpp:946 msgid "Hellfire options:" msgstr "Hellfire options:" -#: Source/diablo.cpp:947 +#: Source/diablo.cpp:956 msgid "Report bugs at https://github.com/diasurgical/devilutionX/" msgstr "Report bugs at https://github.com/diasurgical/devilutionX/" -#: Source/diablo.cpp:1103 +#: Source/diablo.cpp:1127 msgid "Please update devilutionx.mpq and fonts.mpq to the latest version" msgstr "devilutionx.mpq​と​fonts.mpq​を​最新​の​バージョン​に​更新​し​て​ください。" -#: Source/diablo.cpp:1105 +#: Source/diablo.cpp:1129 msgid "" "Failed to load UI resources.\n" "\n" @@ -1354,386 +1388,394 @@ msgstr "" "devilutionx.mpq​が​ゲーム​フォルダ​内​に​あり、最新​の​状態​で​ある​こと​を​確認​し​て​くださ" "い。" -#: Source/diablo.cpp:1109 +#: Source/diablo.cpp:1133 msgid "Please update fonts.mpq to the latest version" msgstr "fonts.mpq​を​最新​の​バージョン​に​更新​し​て​ください。" -#: Source/diablo.cpp:1423 +#: Source/diablo.cpp:1450 msgid "-- Network timeout --" msgstr "— Network timeout —" -#: Source/diablo.cpp:1424 +#: Source/diablo.cpp:1451 msgid "-- Waiting for players --" msgstr "— Waiting for players —" -#: Source/diablo.cpp:1447 +#: Source/diablo.cpp:1474 msgid "No help available" msgstr "ヘルプ​は​使え​ませ​ん" -#: Source/diablo.cpp:1448 +#: Source/diablo.cpp:1475 msgid "while in stores" msgstr "店​で" -#: Source/diablo.cpp:1587 Source/diablo.cpp:1851 +#: Source/diablo.cpp:1614 Source/diablo.cpp:1886 msgid "Belt item {}" msgstr "ベルト​アイテム {}" -#: Source/diablo.cpp:1588 Source/diablo.cpp:1852 +#: Source/diablo.cpp:1615 Source/diablo.cpp:1887 msgid "Use Belt item." msgstr "ベルト​アイテム​を​使い​ます。" -#: Source/diablo.cpp:1603 Source/diablo.cpp:1867 +#: Source/diablo.cpp:1630 Source/diablo.cpp:1902 msgid "Quick spell {}" msgstr "クイック​スペル {}" -#: Source/diablo.cpp:1604 Source/diablo.cpp:1868 +#: Source/diablo.cpp:1631 Source/diablo.cpp:1903 msgid "Hotkey for skill or spell." msgstr "スキル​や​スペル​の​ホットキー​です。" -#: Source/diablo.cpp:1622 Source/diablo.cpp:1995 +#: Source/diablo.cpp:1649 Source/diablo.cpp:2030 msgid "Use health potion" msgstr "ヘルス​ポーション​を​使用​する" -#: Source/diablo.cpp:1623 Source/diablo.cpp:1996 +#: Source/diablo.cpp:1650 Source/diablo.cpp:2031 msgid "Use health potions from belt." msgstr "ベルト​から​ヘルス​ポーション​を​使用​する。" -#: Source/diablo.cpp:1630 Source/diablo.cpp:2003 +#: Source/diablo.cpp:1657 Source/diablo.cpp:2038 msgid "Use mana potion" msgstr "マナポーション​を​使用​する" -#: Source/diablo.cpp:1631 Source/diablo.cpp:2004 +#: Source/diablo.cpp:1658 Source/diablo.cpp:2039 msgid "Use mana potions from belt." msgstr "ベルト​から​マナポーション​を​使用​する。" -#: Source/diablo.cpp:1638 Source/diablo.cpp:2049 +#: Source/diablo.cpp:1665 Source/diablo.cpp:2084 msgid "Speedbook" msgstr "スピード​ブック" -#: Source/diablo.cpp:1639 Source/diablo.cpp:2050 +#: Source/diablo.cpp:1666 Source/diablo.cpp:2085 msgid "Open Speedbook." msgstr "スピード​ブック​を​開き​ます。" -#: Source/diablo.cpp:1646 Source/diablo.cpp:2182 +#: Source/diablo.cpp:1673 Source/diablo.cpp:2217 msgid "Quick save" msgstr "クイック​セーブ" -#: Source/diablo.cpp:1647 Source/diablo.cpp:2183 +#: Source/diablo.cpp:1674 Source/diablo.cpp:2218 msgid "Saves the game." msgstr "ゲーム​を​セーブ​し​ます。" -#: Source/diablo.cpp:1654 Source/diablo.cpp:2190 +#: Source/diablo.cpp:1681 Source/diablo.cpp:2225 msgid "Quick load" msgstr "クイック​ロード" -#: Source/diablo.cpp:1655 Source/diablo.cpp:2191 +#: Source/diablo.cpp:1682 Source/diablo.cpp:2226 msgid "Loads the game." msgstr "ゲーム​を​ロード​し​ます。" -#: Source/diablo.cpp:1663 +#: Source/diablo.cpp:1690 msgid "Quit game" msgstr "ゲーム​の​終了" -#: Source/diablo.cpp:1664 +#: Source/diablo.cpp:1691 msgid "Closes the game." msgstr "ゲーム​を​終了​し​ます。" -#: Source/diablo.cpp:1670 +#: Source/diablo.cpp:1697 msgid "Stop hero" msgstr "ヒーロー​停止" -#: Source/diablo.cpp:1671 +#: Source/diablo.cpp:1698 msgid "Stops walking and cancel pending actions." msgstr "歩行​を​停止​し、保留中​の​アクション​を​キャンセル​し​ます。" -#: Source/diablo.cpp:1678 Source/diablo.cpp:2198 +#: Source/diablo.cpp:1705 Source/diablo.cpp:2233 msgid "Item highlighting" msgstr "アイテム​の​強調​表示" -#: Source/diablo.cpp:1679 Source/diablo.cpp:2199 +#: Source/diablo.cpp:1706 Source/diablo.cpp:2234 msgid "Show/hide items on ground." msgstr "地上​に​ある​アイテム​の​表示​/​非表示​を​切り替え​ます。" -#: Source/diablo.cpp:1685 Source/diablo.cpp:2205 +#: Source/diablo.cpp:1712 Source/diablo.cpp:2240 msgid "Toggle item highlighting" msgstr "アイテム​の​ハイライト​の​切り替え" -#: Source/diablo.cpp:1686 Source/diablo.cpp:2206 +#: Source/diablo.cpp:1713 Source/diablo.cpp:2241 msgid "Permanent show/hide items on ground." msgstr "地上​に​ある​アイテム​の​常時​表示​/​非表示​を​切り替え​ます。" -#: Source/diablo.cpp:1692 Source/diablo.cpp:2059 +#: Source/diablo.cpp:1719 Source/diablo.cpp:2094 msgid "Toggle automap" msgstr "オート​マップ​の​切り替え" -#: Source/diablo.cpp:1693 Source/diablo.cpp:2060 +#: Source/diablo.cpp:1720 Source/diablo.cpp:2095 msgid "Toggles if automap is displayed." msgstr "オート​マップ​を​表示​する​か​どう​か​を​切り替え​ます。" -#: Source/diablo.cpp:1702 Source/diablo.cpp:2020 +#: Source/diablo.cpp:1727 +msgid "Cycle map type" +msgstr "マップタイプの切替" + +#: Source/diablo.cpp:1728 +msgid "Opaque -> Transparent -> Minimap -> None" +msgstr "不透過 -> 透過 -> ミニマップ -> なし" + +#: Source/diablo.cpp:1737 Source/diablo.cpp:2055 msgid "Open Inventory screen." msgstr "インベントリ​画面​を​開き​ます。" -#: Source/diablo.cpp:1709 Source/diablo.cpp:2011 +#: Source/diablo.cpp:1744 Source/diablo.cpp:2046 msgid "Character" msgstr "キャラクター" -#: Source/diablo.cpp:1710 Source/diablo.cpp:2012 +#: Source/diablo.cpp:1745 Source/diablo.cpp:2047 msgid "Open Character screen." msgstr "キャラクター​画面​を​開き​ます。" -#: Source/diablo.cpp:1717 Source/diablo.cpp:2029 +#: Source/diablo.cpp:1752 Source/diablo.cpp:2064 msgid "Quest log" msgstr "クエストログ" -#: Source/diablo.cpp:1718 Source/diablo.cpp:2030 +#: Source/diablo.cpp:1753 Source/diablo.cpp:2065 msgid "Open Quest log." msgstr "クエストログ​を​開き​ます。" -#: Source/diablo.cpp:1725 Source/diablo.cpp:2039 +#: Source/diablo.cpp:1760 Source/diablo.cpp:2074 msgid "Spellbook" msgstr "スペル​ブック" -#: Source/diablo.cpp:1726 Source/diablo.cpp:2040 +#: Source/diablo.cpp:1761 Source/diablo.cpp:2075 msgid "Open Spellbook." msgstr "スペル​ブック​を​開き​ます。" -#: Source/diablo.cpp:1734 +#: Source/diablo.cpp:1769 msgid "Quick Message {}" msgstr "クイック​メッセージ {}" -#: Source/diablo.cpp:1735 +#: Source/diablo.cpp:1770 msgid "Use Quick Message in chat." msgstr "チャット​で​クイック​メッセージ​を​使用​し​ます。" -#: Source/diablo.cpp:1744 Source/diablo.cpp:2212 +#: Source/diablo.cpp:1779 Source/diablo.cpp:2247 msgid "Hide Info Screens" msgstr "情報​画面​の​非表示" -#: Source/diablo.cpp:1745 Source/diablo.cpp:2213 +#: Source/diablo.cpp:1780 Source/diablo.cpp:2248 msgid "Hide all info screens." msgstr "すべて​の​情報​画面​を​非表示​に​し​ます。" -#: Source/diablo.cpp:1765 Source/diablo.cpp:2233 Source/options.cpp:986 +#: Source/diablo.cpp:1800 Source/diablo.cpp:2268 Source/options.cpp:986 msgid "Zoom" msgstr "拡大" -#: Source/diablo.cpp:1766 Source/diablo.cpp:2234 +#: Source/diablo.cpp:1801 Source/diablo.cpp:2269 msgid "Zoom Game Screen." msgstr "ゲーム​画面​を​拡大​し​ます。" -#: Source/diablo.cpp:1776 Source/diablo.cpp:2244 +#: Source/diablo.cpp:1811 Source/diablo.cpp:2279 msgid "Pause Game" msgstr "ゲーム​の​一時停止" -#: Source/diablo.cpp:1777 Source/diablo.cpp:2245 +#: Source/diablo.cpp:1812 Source/diablo.cpp:2280 msgid "Pauses the game." msgstr "ゲーム​を​一時停止​し​ます。" -#: Source/diablo.cpp:1782 Source/diablo.cpp:2250 +#: Source/diablo.cpp:1817 Source/diablo.cpp:2285 msgid "Decrease Gamma" msgstr "ガンマ​値​を​下げる" -#: Source/diablo.cpp:1783 Source/diablo.cpp:2251 +#: Source/diablo.cpp:1818 Source/diablo.cpp:2286 msgid "Reduce screen brightness." msgstr "画面​の​輝度​を​下げ​ます。" -#: Source/diablo.cpp:1790 Source/diablo.cpp:2258 +#: Source/diablo.cpp:1825 Source/diablo.cpp:2293 msgid "Increase Gamma" msgstr "ガンマ​値​を​上げる" -#: Source/diablo.cpp:1791 Source/diablo.cpp:2259 +#: Source/diablo.cpp:1826 Source/diablo.cpp:2294 msgid "Increase screen brightness." msgstr "画面​の​輝度​を​上げ​ます。" -#: Source/diablo.cpp:1798 Source/diablo.cpp:2266 +#: Source/diablo.cpp:1833 Source/diablo.cpp:2301 msgid "Help" msgstr "ヘルプ" -#: Source/diablo.cpp:1799 Source/diablo.cpp:2267 +#: Source/diablo.cpp:1834 Source/diablo.cpp:2302 msgid "Open Help Screen." msgstr "ヘルプ​画面​を​開き​ます。" -#: Source/diablo.cpp:1806 Source/diablo.cpp:2274 +#: Source/diablo.cpp:1841 Source/diablo.cpp:2309 msgid "Screenshot" msgstr "スクリーンショット" -#: Source/diablo.cpp:1807 Source/diablo.cpp:2275 +#: Source/diablo.cpp:1842 Source/diablo.cpp:2310 msgid "Takes a screenshot." msgstr "スクリーンショット​を​撮り​ます。" -#: Source/diablo.cpp:1813 Source/diablo.cpp:2281 +#: Source/diablo.cpp:1848 Source/diablo.cpp:2316 msgid "Game info" msgstr "ゲーム​情報" -#: Source/diablo.cpp:1814 Source/diablo.cpp:2282 +#: Source/diablo.cpp:1849 Source/diablo.cpp:2317 msgid "Displays game infos." msgstr "ゲーム​情報​を​表示​し​ます。" #. TRANSLATORS: {:s} means: Character Name, Game Version, Game Difficulty. -#: Source/diablo.cpp:1818 Source/diablo.cpp:2286 +#: Source/diablo.cpp:1853 Source/diablo.cpp:2321 msgid "{:s} {:s}" msgstr "{:s} {:s}" -#: Source/diablo.cpp:1827 Source/diablo.cpp:2295 +#: Source/diablo.cpp:1862 Source/diablo.cpp:2330 msgid "Chat Log" msgstr "チャットログ" -#: Source/diablo.cpp:1828 Source/diablo.cpp:2296 +#: Source/diablo.cpp:1863 Source/diablo.cpp:2331 msgid "Displays chat log." msgstr "チャットログ​を​表示​し​ます。" -#: Source/diablo.cpp:1886 +#: Source/diablo.cpp:1921 msgid "Primary action" msgstr "主​アクション" -#: Source/diablo.cpp:1887 +#: Source/diablo.cpp:1922 msgid "Attack monsters, talk to towners, lift and place inventory items." msgstr "" "モンスター​を​攻撃​し​たり、街​の​人​に​話しかけ​たり、インベントリー​アイテム​を​持ち上" "げ​て​置い​たり。" -#: Source/diablo.cpp:1901 +#: Source/diablo.cpp:1936 msgid "Secondary action" msgstr "副​アクション" -#: Source/diablo.cpp:1902 +#: Source/diablo.cpp:1937 msgid "Open chests, interact with doors, pick up items." msgstr "チェスト​を​開け​たり、ドア​と​対話​し​たり、アイテム​を​拾っ​たり。" -#: Source/diablo.cpp:1916 +#: Source/diablo.cpp:1951 msgid "Spell action" msgstr "スペル​・​アクション" -#: Source/diablo.cpp:1917 +#: Source/diablo.cpp:1952 msgid "Cast the active spell." msgstr "アクティブ​な​呪文​を​唱える。" -#: Source/diablo.cpp:1931 +#: Source/diablo.cpp:1966 msgid "Cancel action" msgstr "キャンセル​・​アクション" -#: Source/diablo.cpp:1932 +#: Source/diablo.cpp:1967 msgid "Close menus." msgstr "メニュー​を​閉じる。" -#: Source/diablo.cpp:1957 +#: Source/diablo.cpp:1992 msgid "Move up" msgstr "上​へ​移動" -#: Source/diablo.cpp:1958 +#: Source/diablo.cpp:1993 msgid "Moves the player character up." msgstr "プレイヤー​キャラクター​を​上​方向​に​移動​し​ます。" -#: Source/diablo.cpp:1963 +#: Source/diablo.cpp:1998 msgid "Move down" msgstr "下​へ​移動" -#: Source/diablo.cpp:1964 +#: Source/diablo.cpp:1999 msgid "Moves the player character down." msgstr "プレイヤー​キャラクター​を​下​方向​に​移動​し​ます。" -#: Source/diablo.cpp:1969 +#: Source/diablo.cpp:2004 msgid "Move left" msgstr "左​に​移動" -#: Source/diablo.cpp:1970 +#: Source/diablo.cpp:2005 msgid "Moves the player character left." msgstr "プレイヤー​キャラクター​を​左​方向​に​移動​し​ます。" -#: Source/diablo.cpp:1975 +#: Source/diablo.cpp:2010 msgid "Move right" msgstr "右​へ​移動" -#: Source/diablo.cpp:1976 +#: Source/diablo.cpp:2011 msgid "Moves the player character right." msgstr "プレイヤー​キャラクター​を​右​方向​に​移動​し​ます。" -#: Source/diablo.cpp:1981 +#: Source/diablo.cpp:2016 msgid "Stand ground" msgstr "踏ん張る" -#: Source/diablo.cpp:1982 +#: Source/diablo.cpp:2017 msgid "Hold to prevent the player from moving." msgstr "ホールド​する​と​プレーヤー​が​動か​なく​なり​ます。" -#: Source/diablo.cpp:1987 +#: Source/diablo.cpp:2022 msgid "Toggle stand ground" msgstr "踏ん張る​の​切り替え" -#: Source/diablo.cpp:1988 +#: Source/diablo.cpp:2023 msgid "Toggle whether the player moves." msgstr "プレイヤー​移動​の​切り替え" -#: Source/diablo.cpp:2065 +#: Source/diablo.cpp:2100 msgid "Move mouse up" msgstr "マウス​を​上​に​移動" -#: Source/diablo.cpp:2066 +#: Source/diablo.cpp:2101 msgid "Simulates upward mouse movement." msgstr "マウス​の​上​方向​の​動き​を​シミュレート​し​ます。" -#: Source/diablo.cpp:2071 +#: Source/diablo.cpp:2106 msgid "Move mouse down" msgstr "マウス​を​下​に​移動" -#: Source/diablo.cpp:2072 +#: Source/diablo.cpp:2107 msgid "Simulates downward mouse movement." msgstr "マウス​の​下​方向​の​動き​を​シミュレート​し​ます。" -#: Source/diablo.cpp:2077 +#: Source/diablo.cpp:2112 msgid "Move mouse left" msgstr "マウス​を​左​へ​移動" -#: Source/diablo.cpp:2078 +#: Source/diablo.cpp:2113 msgid "Simulates leftward mouse movement." msgstr "マウス​の​左​方向​の​動き​を​シミュレート​し​ます。" -#: Source/diablo.cpp:2083 +#: Source/diablo.cpp:2118 msgid "Move mouse right" msgstr "マウス​を​右​へ​移動" -#: Source/diablo.cpp:2084 +#: Source/diablo.cpp:2119 msgid "Simulates rightward mouse movement." msgstr "マウス​の​右​方向​の​動き​を​シミュレート​し​ます。" -#: Source/diablo.cpp:2102 Source/diablo.cpp:2109 +#: Source/diablo.cpp:2137 Source/diablo.cpp:2144 msgid "Left mouse click" msgstr "マウス​の​左​クリック" -#: Source/diablo.cpp:2103 Source/diablo.cpp:2110 +#: Source/diablo.cpp:2138 Source/diablo.cpp:2145 msgid "Simulates the left mouse button." msgstr "マウス​の​左​ボタン​を​シミュレート​し​ます。" -#: Source/diablo.cpp:2127 Source/diablo.cpp:2134 +#: Source/diablo.cpp:2162 Source/diablo.cpp:2169 msgid "Right mouse click" msgstr "マウス​の​右​クリック" -#: Source/diablo.cpp:2128 Source/diablo.cpp:2135 +#: Source/diablo.cpp:2163 Source/diablo.cpp:2170 msgid "Simulates the right mouse button." msgstr "マウス​の​右​ボタン​を​シミュレート​し​ます。" -#: Source/diablo.cpp:2141 +#: Source/diablo.cpp:2176 msgid "Gamepad hotspell menu" msgstr "ゲーム​パッド​の​ホット​スペル​メニュー" -#: Source/diablo.cpp:2142 +#: Source/diablo.cpp:2177 msgid "Hold to set or use spell hotkeys." msgstr "ホールド​し​て​呪文​ホットキー​を​設定​また​は​使用​し​ます。" -#: Source/diablo.cpp:2148 +#: Source/diablo.cpp:2183 msgid "Gamepad menu navigator" msgstr "ゲーム​パッド​メニュー​ナビゲーター" -#: Source/diablo.cpp:2149 +#: Source/diablo.cpp:2184 msgid "Hold to access gamepad menu navigation." msgstr "長押し​で​ゲーム​パッド​メニュー​ナビゲーション​に​アクセス​し​ます。" -#: Source/diablo.cpp:2164 Source/diablo.cpp:2173 +#: Source/diablo.cpp:2199 Source/diablo.cpp:2208 msgid "Toggle game menu" msgstr "ゲーム​メニュー​の​切り替え" -#: Source/diablo.cpp:2165 Source/diablo.cpp:2174 +#: Source/diablo.cpp:2200 Source/diablo.cpp:2209 msgid "Opens the game menu." msgstr "ゲーム​メニュー​を​開き​ます。" @@ -1763,281 +1805,377 @@ msgid "{} {}" msgstr "{} {}" #. TRANSLATORS: Discord character, i.e. "Lv 6 Warrior" -#: Source/discord/discord.cpp:92 +#: Source/discord/discord.cpp:91 msgid "Lv {} {}" msgstr "Lv {} {}" #. TRANSLATORS: Discord state i.e. "Nightmare difficulty" -#: Source/discord/discord.cpp:104 +#: Source/discord/discord.cpp:103 msgid "{} difficulty" msgstr "{} 難易度" #. TRANSLATORS: Discord activity, not in game -#: Source/discord/discord.cpp:185 +#: Source/discord/discord.cpp:184 msgid "In Menu" msgstr "メニュー​に​ある" -#: Source/dvlnet/loopback.cpp:118 +#: Source/dvlnet/loopback.cpp:117 msgid "loopback" msgstr "loopback" -#: Source/dvlnet/tcp_client.cpp:67 +#: Source/dvlnet/tcp_client.cpp:81 msgid "Unable to connect" msgstr "Unable to connect" -#: Source/dvlnet/tcp_client.cpp:93 +#: Source/dvlnet/tcp_client.cpp:119 msgid "error: read 0 bytes from server" msgstr "error: read 0 bytes from server" -#: Source/error.cpp:54 -msgid "No automap available in town" -msgstr "町中​で​は​オート​マップ​を​使え​ませ​ん" +#: Source/engine/demomode.cpp:179 Source/options.cpp:679 +msgid "Resolution" +msgstr "解像度" + +#: Source/engine/demomode.cpp:181 Source/options.cpp:1045 +msgid "Run in Town" +msgstr "街​で​走る" + +#: Source/engine/demomode.cpp:182 Source/options.cpp:1047 +msgid "Theo Quest" +msgstr "テオクエスト" + +#: Source/engine/demomode.cpp:183 Source/options.cpp:1048 +msgid "Cow Quest" +msgstr "牛​クエスト" + +#: Source/engine/demomode.cpp:184 Source/options.cpp:1058 +msgid "Auto Gold Pickup" +msgstr "自動​ゴールド​回収" + +#: Source/engine/demomode.cpp:185 Source/options.cpp:1059 +msgid "Auto Elixir Pickup" +msgstr "自動​エリクサー​回収" + +#: Source/engine/demomode.cpp:186 Source/options.cpp:1060 +msgid "Auto Oil Pickup" +msgstr "自動​オイル​回収" -#: Source/error.cpp:55 +#: Source/engine/demomode.cpp:187 Source/options.cpp:1061 +msgid "Auto Pickup in Town" +msgstr "街中​で​自動​回収" + +#: Source/engine/demomode.cpp:188 Source/options.cpp:1062 +msgid "Adria Refills Mana" +msgstr "エイドリア​の​マナ​補充" + +#: Source/engine/demomode.cpp:189 Source/options.cpp:1063 +msgid "Auto Equip Weapons" +msgstr "武器​の​自動​装備" + +#: Source/engine/demomode.cpp:190 Source/options.cpp:1064 +msgid "Auto Equip Armor" +msgstr "アーマー​の​自動​装備" + +#: Source/engine/demomode.cpp:191 Source/options.cpp:1065 +msgid "Auto Equip Helms" +msgstr "ヘルム​の​自動​装備" + +#: Source/engine/demomode.cpp:192 Source/options.cpp:1066 +msgid "Auto Equip Shields" +msgstr "シールド​の​自動​装備" + +#: Source/engine/demomode.cpp:193 Source/options.cpp:1067 +msgid "Auto Equip Jewelry" +msgstr "ジュエリー​の​自動​装備" + +#: Source/engine/demomode.cpp:194 Source/options.cpp:1068 +msgid "Randomize Quests" +msgstr "クエスト​の​ランダム化" + +#: Source/engine/demomode.cpp:195 Source/options.cpp:1070 +msgid "Show Item Labels" +msgstr "アイテム​ラベル​を​表示" + +#: Source/engine/demomode.cpp:196 Source/options.cpp:1071 +msgid "Auto Refill Belt" +msgstr "ベルト​の​自動​補充" + +#: Source/engine/demomode.cpp:197 Source/options.cpp:1072 +msgid "Disable Crippling Shrines" +msgstr "被害​を​与える​祭壇​を​無効化" + +#: Source/engine/demomode.cpp:201 Source/options.cpp:1074 +msgid "Heal Potion Pickup" +msgstr "ヒーリング​・​ポーション​回収" + +#: Source/engine/demomode.cpp:202 Source/options.cpp:1075 +msgid "Full Heal Potion Pickup" +msgstr "フルヒーリング​・​ポーション​回収" + +#: Source/engine/demomode.cpp:203 Source/options.cpp:1076 +msgid "Mana Potion Pickup" +msgstr "マナ​・​ポーション​を​回収" + +#: Source/engine/demomode.cpp:204 Source/options.cpp:1077 +msgid "Full Mana Potion Pickup" +msgstr "フルマナ​・​ポーション​回収" + +#: Source/engine/demomode.cpp:205 Source/options.cpp:1078 +msgid "Rejuvenation Potion Pickup" +msgstr "回復​ポーション​回収" + +#: Source/engine/demomode.cpp:206 Source/options.cpp:1079 +msgid "Full Rejuvenation Potion Pickup" +msgstr "フル​回復ポーション​回収" + +#: Source/error.cpp:60 +msgid "Game saved" +msgstr "ゲーム​の​セーブ" + +#: Source/error.cpp:61 msgid "No multiplayer functions in demo" msgstr "デモ​で​は​マルチ​プレイ​機能​は​あり​ませ​ん" -#: Source/error.cpp:56 +#: Source/error.cpp:62 msgid "Direct Sound Creation Failed" msgstr "Direct Sound Creation Failed" -#: Source/error.cpp:57 +#: Source/error.cpp:63 msgid "Not available in shareware version" msgstr "シェアウェア​版​で​は​使用​でき​ませ​ん" -#: Source/error.cpp:58 +#: Source/error.cpp:64 msgid "Not enough space to save" msgstr "保存​する​ため​の​十分​な​スペース​が​あり​ませ​ん" -#: Source/error.cpp:59 +#: Source/error.cpp:65 msgid "No Pause in town" msgstr "町中​で​は​ポーズ​でき​ませ​ん" -#: Source/error.cpp:60 +#: Source/error.cpp:66 msgid "Copying to a hard disk is recommended" msgstr "Copying to a hard disk is recommended" -#: Source/error.cpp:61 +#: Source/error.cpp:67 msgid "Multiplayer sync problem" msgstr "Multiplayer sync problem" -#: Source/error.cpp:62 +#: Source/error.cpp:68 msgid "No pause in multiplayer" msgstr "マルチ​プレイ​で​は​ポーズ​が​でき​ませ​ん" -#: Source/error.cpp:64 +#: Source/error.cpp:70 msgid "Saving..." msgstr "セーブ​中​…" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:65 +#: Source/error.cpp:71 msgid "Some are weakened as one grows strong" msgstr "幾多​の​犠牲​の​上、新た​なる​力​は​得​られる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:66 +#: Source/error.cpp:72 msgid "New strength is forged through destruction" msgstr "新た​なる​力、破壊​に​より​鍛え​られる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:67 +#: Source/error.cpp:73 msgid "Those who defend seldom attack" msgstr "厚​き​盾、矛先​は​鈍る" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:68 +#: Source/error.cpp:74 msgid "The sword of justice is swift and sharp" msgstr "義​を​以​て​振ら​れる​剣、鋭​き​刃​は​宿る" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:69 +#: Source/error.cpp:75 msgid "While the spirit is vigilant the body thrives" msgstr "油断​おこたら​ず​ば、その​身​は​守ら​れる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:70 +#: Source/error.cpp:76 msgid "The powers of mana refocused renews" msgstr "広がり​し​マナ​の​力、再び​寄り集う" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:71 +#: Source/error.cpp:77 msgid "Time cannot diminish the power of steel" msgstr "時​の​流れ、鋼​の​力​を​奪う​は​叶わ​ず" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:72 +#: Source/error.cpp:78 msgid "Magic is not always what it seems to be" msgstr "目​に​見える​もの、常​なら​ざる​が​魔術​なり" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:73 +#: Source/error.cpp:79 msgid "What once was opened now is closed" msgstr "開か​れ​し​物、今、再び​閉じる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:74 +#: Source/error.cpp:80 msgid "Intensity comes at the cost of wisdom" msgstr "高揚​は​知識​の​代価​に​与え​られる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:75 +#: Source/error.cpp:81 msgid "Arcane power brings destruction" msgstr "神秘​の​力​が​災い​を​もたらす" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:76 +#: Source/error.cpp:82 msgid "That which cannot be held cannot be harmed" msgstr "その​何れ​か、保た​れる​こと​なく​傷つく​こと​なく" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:77 +#: Source/error.cpp:83 msgid "Crimson and Azure become as the sun" msgstr "深紅​と​淡青、太陽​と​なる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:78 +#: Source/error.cpp:84 msgid "Knowledge and wisdom at the cost of self" msgstr "知恵​と​知識​に​己​を​賭けよ" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:79 +#: Source/error.cpp:85 msgid "Drink and be refreshed" msgstr "これ​を​飲み、しばし​休ま​れよ" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:80 +#: Source/error.cpp:86 msgid "Wherever you go, there you are" msgstr "何処​へ​行く​とも、汝​は​汝" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:81 +#: Source/error.cpp:87 msgid "Energy comes at the cost of wisdom" msgstr "その​力、知識​の​代価​に​与え​られる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:82 +#: Source/error.cpp:88 msgid "Riches abound when least expected" msgstr "ちり​も​積もれ​ば​山​と​なる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:83 +#: Source/error.cpp:89 msgid "Where avarice fails, patience gains reward" msgstr "強欲​は​朽ち、忍耐​は​報わ​れる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:84 +#: Source/error.cpp:90 msgid "Blessed by a benevolent companion!" msgstr "仲間​より​の​慈愛​の​祈り​を​受ける" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:85 +#: Source/error.cpp:91 msgid "The hands of men may be guided by fate" msgstr "人​の​手​は、時折​運命​に​よっ​て​導か​れる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:86 +#: Source/error.cpp:92 msgid "Strength is bolstered by heavenly faith" msgstr "その​力、神​へ​の​信仰​に​よっ​て​支え​られる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:87 +#: Source/error.cpp:93 msgid "The essence of life flows from within" msgstr "生命​の​本質、その​内​より​湧き出る" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:88 +#: Source/error.cpp:94 msgid "The way is made clear when viewed from above" msgstr "天​より​の​眺め、道​は​明らか​と​なる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:89 +#: Source/error.cpp:95 msgid "Salvation comes at the cost of wisdom" msgstr "救済​は​知識​の​代価​に​与え​られる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:90 +#: Source/error.cpp:96 msgid "Mysteries are revealed in the light of reason" msgstr "神秘​は​道理​の​光​の​下、明らか​に​さ​れる" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:91 +#: Source/error.cpp:97 msgid "Those who are last may yet be first" msgstr "人​より​先んずれ​ど​も​…" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:92 +#: Source/error.cpp:98 msgid "Generosity brings its own rewards" msgstr "寛大​なる​心、いつ​の​日​か​報わ​れん" -#: Source/error.cpp:93 +#: Source/error.cpp:99 msgid "You must be at least level 8 to use this." msgstr "この​レベル​は、最低​8​レベル​以上​で​なく​て​は​なり​ませ​ん。" -#: Source/error.cpp:94 +#: Source/error.cpp:100 msgid "You must be at least level 13 to use this." msgstr "この​レベル​は、最低​13​レベル​以上​で​なく​て​は​なり​ませ​ん。" -#: Source/error.cpp:95 +#: Source/error.cpp:101 msgid "You must be at least level 17 to use this." msgstr "この​レベル​は、最低​17​レベル​以上​で​なく​て​は​なり​ませ​ん。" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:96 +#: Source/error.cpp:102 msgid "Arcane knowledge gained!" msgstr "神秘​なる​知識​が​授け​られ​た​!" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:97 +#: Source/error.cpp:103 msgid "That which does not kill you..." msgstr "死な​ない​もの​は​…" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:98 +#: Source/error.cpp:104 msgid "Knowledge is power." msgstr "知識​は​力​なり" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:99 +#: Source/error.cpp:105 msgid "Give and you shall receive." msgstr "与えよ、さらば​与え​られ​ん" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:100 +#: Source/error.cpp:106 msgid "Some experience is gained by touch." msgstr "触れる​こと​で​得​られる​経験​も​ある。" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:101 +#: Source/error.cpp:107 msgid "There's no place like home." msgstr "家​の​よう​な​場所​は​ない。" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:102 +#: Source/error.cpp:108 msgid "Spiritual energy is restored." msgstr "精神的​な​エネルギー​が​回復​する。" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:103 +#: Source/error.cpp:109 msgid "You feel more agile." msgstr "より​機敏​な​感じ​が​する。" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:104 +#: Source/error.cpp:110 msgid "You feel stronger." msgstr "強く​なっ​た​と​感じる。" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:105 +#: Source/error.cpp:111 msgid "You feel wiser." msgstr "賢く​なっ​た​と​感じる。" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:106 +#: Source/error.cpp:112 msgid "You feel refreshed." msgstr "爽快感​が​ある。" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:107 +#: Source/error.cpp:113 msgid "That which can break will." msgstr "壊せる​もの​は​壊す。" @@ -2315,19 +2453,19 @@ msgstr "シェアウェア ディアブロ ヘルプ" msgid "Diablo Help" msgstr "ディアブロ ヘルプ" -#: Source/help.cpp:233 Source/qol/chatlog.cpp:189 +#: Source/help.cpp:233 Source/qol/chatlog.cpp:188 msgid "Press ESC to end or the arrow keys to scroll." msgstr "ESC​を​押し​て​終了​する​か、矢印​キー​で​スクロール​し​ます。" -#: Source/init.cpp:295 Source/init.cpp:335 +#: Source/init.cpp:298 Source/init.cpp:338 msgid "diabdat.mpq or spawn.mpq" msgstr "diabdat.mpq​また​は​spawn.mpq" -#: Source/init.cpp:316 Source/init.cpp:356 +#: Source/init.cpp:319 Source/init.cpp:359 msgid "Some Hellfire MPQs are missing" msgstr "いくつ​か​の​Hellfire MPQ​が​欠落​し​て​い​ます" -#: Source/init.cpp:316 Source/init.cpp:356 +#: Source/init.cpp:319 Source/init.cpp:359 msgid "" "Not all Hellfire MPQs were found.\n" "Please copy all the hf*.mpq files." @@ -2335,15 +2473,15 @@ msgstr "" "すべて​の​Hellfire MPQ​が​見つから​ない。\n" "​すべて​の hf​*​.mpq ファイル​を​コピー​し​て​ください。" -#: Source/init.cpp:365 +#: Source/init.cpp:368 msgid "Unable to create main window" msgstr "Unable to create main window" -#: Source/inv.cpp:2126 +#: Source/inv.cpp:2132 msgid "No room for item" -msgstr "アイテムが入る余地がない" +msgstr "アイテム​が​入る​余地​が​ない" -#: Source/itemdat.cpp:53 Source/itemdat.cpp:236 Source/panels/charpanel.cpp:165 +#: Source/itemdat.cpp:53 Source/itemdat.cpp:236 Source/panels/charpanel.cpp:167 msgid "Gold" msgstr "ゴールド" @@ -2407,11 +2545,11 @@ msgstr "ベイル​・​オブ​・​スチール" msgid "Golden Elixir" msgstr "ゴールデン​・​エリクサー" -#: Source/itemdat.cpp:69 Source/quests.cpp:58 +#: Source/itemdat.cpp:69 Source/quests.cpp:62 msgid "Anvil of Fury" msgstr "憤激​の​鉄床" -#: Source/itemdat.cpp:70 Source/quests.cpp:49 +#: Source/itemdat.cpp:70 Source/quests.cpp:53 msgid "Black Mushroom" msgstr "黒い​キノコ" @@ -2483,7 +2621,7 @@ msgstr "ラザルス​の​杖" msgid "Scroll of Resurrect" msgstr "リザレクション​・​スクロール" -#: Source/itemdat.cpp:88 Source/itemdat.cpp:136 Source/items.cpp:180 +#: Source/itemdat.cpp:88 Source/itemdat.cpp:136 Source/items.cpp:179 msgid "Blacksmith Oil" msgstr "鍛冶屋​の​オイル" @@ -2583,7 +2721,7 @@ msgid "Quilted Armor" msgstr "キルテッドアーマー" #: Source/itemdat.cpp:111 Source/itemdat.cpp:112 Source/itemdat.cpp:113 -#: Source/itemdat.cpp:114 Source/objects.cpp:4964 +#: Source/itemdat.cpp:114 Source/objects.cpp:4850 msgid "Armor" msgstr "防具" @@ -2672,17 +2810,17 @@ msgstr "ゴシック​シールド" #: Source/itemdat.cpp:134 msgid "Potion of Rejuvenation" -msgstr "リジュベネーション​・​ポーション" +msgstr "回復ポーション" #: Source/itemdat.cpp:135 msgid "Potion of Full Rejuvenation" -msgstr "フルリジュベネーション​・​ポーション" +msgstr "フル回復​ポーション" -#: Source/itemdat.cpp:137 Source/items.cpp:175 +#: Source/itemdat.cpp:137 Source/items.cpp:174 msgid "Oil of Accuracy" msgstr "アキュラシー​・​オイル" -#: Source/itemdat.cpp:138 Source/items.cpp:177 +#: Source/itemdat.cpp:138 Source/items.cpp:176 msgid "Oil of Sharpness" msgstr "シャープネス​・​オイル" @@ -2971,7 +3109,7 @@ msgstr "ショート​スタッフ​・​オブ​・​チャージド​ボ #: Source/itemdat.cpp:220 msgid "Arena Potion" -msgstr "アリーナポーション" +msgstr "アリーナ​ポーション" #. TRANSLATORS: Item prefix section. #: Source/itemdat.cpp:230 @@ -3010,7 +3148,7 @@ msgstr "ミスリル" msgid "Meteoric" msgstr "メテオ​リック" -#: Source/itemdat.cpp:240 Source/objects.cpp:124 +#: Source/itemdat.cpp:240 Source/objects.cpp:126 msgid "Weird" msgstr "ウィアード" @@ -3146,7 +3284,7 @@ msgstr "セイントリィ" msgid "Awesome" msgstr "オウサム" -#: Source/itemdat.cpp:275 Source/objects.cpp:136 +#: Source/itemdat.cpp:275 Source/objects.cpp:138 msgid "Holy" msgstr "ホーリー" @@ -4107,199 +4245,199 @@ msgstr "アコライト​の​アミュレット" msgid "Gladiator's Ring" msgstr "グラディエーターズ​・​リング" -#: Source/items.cpp:176 +#: Source/items.cpp:175 msgid "Oil of Mastery" msgstr "マスタリー​・​オイル" -#: Source/items.cpp:178 +#: Source/items.cpp:177 msgid "Oil of Death" msgstr "デス​・​オイル" -#: Source/items.cpp:179 +#: Source/items.cpp:178 msgid "Oil of Skill" msgstr "スキル​・​オイル" -#: Source/items.cpp:181 +#: Source/items.cpp:180 msgid "Oil of Fortitude" msgstr "フォルチュード​・​オイル" -#: Source/items.cpp:182 +#: Source/items.cpp:181 msgid "Oil of Permanence" msgstr "パーマネンス​・​オイル" -#: Source/items.cpp:183 +#: Source/items.cpp:182 msgid "Oil of Hardening" msgstr "ハーデニング​・​オイル" -#: Source/items.cpp:184 +#: Source/items.cpp:183 msgid "Oil of Imperviousness" msgstr "インプレビュスネス​・​オイル" #. TRANSLATORS: Constructs item names. Format: {Item} of {Spell}. Example: War Staff of Firewall -#: Source/items.cpp:1113 +#: Source/items.cpp:1112 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:1125 +#: Source/items.cpp:1124 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:1163 +#: Source/items.cpp:1162 msgid "{0} {1} of {2}" msgstr "{2}​の​{0}{1}" #. TRANSLATORS: Constructs item names. Format: {Prefix} {Item}. Example: King's Long Sword -#: Source/items.cpp:1166 +#: Source/items.cpp:1165 msgid "{0} {1}" msgstr "{0} {1}" #. TRANSLATORS: Constructs item names. Format: {Item} of {Suffix}. Example: Long Sword of the Whale -#: Source/items.cpp:1169 +#: Source/items.cpp:1168 msgid "{0} of {1}" msgstr "{1}​の​{0}" -#: Source/items.cpp:1700 Source/items.cpp:1708 +#: Source/items.cpp:1699 Source/items.cpp:1707 msgid "increases a weapon's" msgstr "武器​の​命中​率​を" -#: Source/items.cpp:1701 +#: Source/items.cpp:1700 msgid "chance to hit" msgstr "上昇​さ​せる" -#: Source/items.cpp:1704 +#: Source/items.cpp:1703 msgid "greatly increases a" msgstr "武器​の​命中​率​を" -#: Source/items.cpp:1705 +#: Source/items.cpp:1704 msgid "weapon's chance to hit" msgstr "大幅​に​上昇​さ​せる" -#: Source/items.cpp:1709 +#: Source/items.cpp:1708 msgid "damage potential" msgstr "増大​さ​せる" -#: Source/items.cpp:1712 +#: Source/items.cpp:1711 msgid "greatly increases a weapon's" msgstr "武器​の​ダメージ​を​大幅​に" -#: Source/items.cpp:1713 +#: Source/items.cpp:1712 msgid "damage potential - not bows" msgstr "増大​さ​せる​-​ボウ​を​除く" -#: Source/items.cpp:1716 +#: Source/items.cpp:1715 msgid "reduces attributes needed" msgstr "防具​や​武器​の​必要​能力​値" -#: Source/items.cpp:1717 +#: Source/items.cpp:1716 msgid "to use armor or weapons" msgstr "の​値​を​引き下げる" -#: Source/items.cpp:1720 +#: Source/items.cpp:1719 #, no-c-format msgid "restores 20% of an" msgstr "を​20​%​回復" -#: Source/items.cpp:1721 +#: Source/items.cpp:1720 msgid "item's durability" msgstr "アイテム​の​耐久​度" -#: Source/items.cpp:1724 +#: Source/items.cpp:1723 msgid "increases an item's" msgstr "次​の​もの​を​増大​さ​せ​ます:" -#: Source/items.cpp:1725 +#: Source/items.cpp:1724 msgid "current and max durability" msgstr "20​%​増大​さ​せる" -#: Source/items.cpp:1728 +#: Source/items.cpp:1727 msgid "makes an item indestructible" msgstr "アイテム​を​壊れ​ない​よう​に​し​ます" -#: Source/items.cpp:1731 +#: Source/items.cpp:1730 msgid "increases the armor class" msgstr "防具​と​盾​の​防御​力​を" -#: Source/items.cpp:1732 +#: Source/items.cpp:1731 msgid "of armor and shields" msgstr "増大​さ​せる" -#: Source/items.cpp:1735 +#: Source/items.cpp:1734 msgid "greatly increases the armor" msgstr "防具​と​盾​の​防御​力​を" -#: Source/items.cpp:1736 +#: Source/items.cpp:1735 msgid "class of armor and shields" msgstr "大幅​に​増大​さ​せる" -#: Source/items.cpp:1739 Source/items.cpp:1746 +#: Source/items.cpp:1738 Source/items.cpp:1745 msgid "sets fire trap" msgstr "ファイヤー​トラップ​を​設置" -#: Source/items.cpp:1743 +#: Source/items.cpp:1742 msgid "sets lightning trap" msgstr "ライトニング​トラップ​を​設置" -#: Source/items.cpp:1749 +#: Source/items.cpp:1748 msgid "sets petrification trap" msgstr "石化​トラップ​を​設置" -#: Source/items.cpp:1752 +#: Source/items.cpp:1751 msgid "restore all life" msgstr "ライフ​を​全​回復" -#: Source/items.cpp:1755 +#: Source/items.cpp:1754 msgid "restore some life" msgstr "ライフ​を​回復" -#: Source/items.cpp:1758 +#: Source/items.cpp:1757 msgid "restore some mana" msgstr "マナ​を​回復" -#: Source/items.cpp:1761 +#: Source/items.cpp:1760 msgid "restore all mana" msgstr "マナ​を​全​回復" -#: Source/items.cpp:1764 +#: Source/items.cpp:1763 msgid "increase strength" msgstr "STR を​増大" -#: Source/items.cpp:1767 +#: Source/items.cpp:1766 msgid "increase magic" msgstr "MAG を​増大" -#: Source/items.cpp:1770 +#: Source/items.cpp:1769 msgid "increase dexterity" msgstr "DEX を​増大" -#: Source/items.cpp:1773 +#: Source/items.cpp:1772 msgid "increase vitality" msgstr "VIT を​増大" -#: Source/items.cpp:1776 +#: Source/items.cpp:1775 msgid "restore some life and mana" msgstr "ライフ​と​マナ​を​回復" -#: Source/items.cpp:1779 Source/items.cpp:1782 +#: Source/items.cpp:1778 Source/items.cpp:1781 msgid "restore all life and mana" msgstr "ライフ​と​マナ​を​全​回復" -#: Source/items.cpp:1783 +#: Source/items.cpp:1782 msgid "(works only in arenas)" -msgstr "(アリーナのみ)" +msgstr "(​アリーナ​のみ​)" -#: Source/items.cpp:1797 +#: Source/items.cpp:1796 msgid "Right-click to view" msgstr "右​クリック​で​表示" -#: Source/items.cpp:1800 +#: Source/items.cpp:1799 msgid "Right-click to use" msgstr "右​クリック​で​使用" -#: Source/items.cpp:1802 +#: Source/items.cpp:1801 msgid "" "Right-click to read, then\n" "left-click to target" @@ -4307,23 +4445,23 @@ msgstr "" "右​クリック​で​読み、\n" "​左​クリック​で​ターゲット" -#: Source/items.cpp:1804 +#: Source/items.cpp:1803 msgid "Right-click to read" msgstr "右​クリック​で​読む" -#: Source/items.cpp:1811 +#: Source/items.cpp:1810 msgid "Activate to view" msgstr "アクティベート​し​て​表示​する" -#: Source/items.cpp:1815 Source/items.cpp:1853 +#: Source/items.cpp:1814 Source/items.cpp:1852 msgid "Open inventory to use" msgstr "使用​する​インベントリ​を​開く" -#: Source/items.cpp:1817 +#: Source/items.cpp:1816 msgid "Activate to use" msgstr "アクティベート​し​て​使う" -#: Source/items.cpp:1820 +#: Source/items.cpp:1819 msgid "" "Select from spell book, then\n" "cast spell to read" @@ -4331,19 +4469,19 @@ msgstr "" "スペル​ブック​から​選択​し​\n" "​読ん​で​キャスト" -#: Source/items.cpp:1822 +#: Source/items.cpp:1821 msgid "Activate to read" msgstr "アクティベート​し​て​読む" -#: Source/items.cpp:1849 +#: Source/items.cpp:1848 msgid "{} to view" msgstr "{} を​見る" -#: Source/items.cpp:1855 +#: Source/items.cpp:1854 msgid "{} to use" msgstr "{} を​使用​する" -#: Source/items.cpp:1858 +#: Source/items.cpp:1857 msgid "" "Select from spell book,\n" "then {} to read" @@ -4351,421 +4489,421 @@ msgstr "" "スペル​ブック​から​選択、\n" "​そして​{}​を​読む" -#: Source/items.cpp:1860 +#: Source/items.cpp:1859 msgid "{} to read" msgstr "{} を​読む" -#: Source/items.cpp:1867 +#: Source/items.cpp:1866 msgctxt "player" msgid "Level: {:d}" msgstr "レベル: {:d}" -#: Source/items.cpp:1871 +#: Source/items.cpp:1870 msgid "Doubles gold capacity" msgstr "ゴールド​の​容量​を​2​倍​に​する" -#: Source/items.cpp:1903 Source/stores.cpp:325 +#: Source/items.cpp:1902 Source/stores.cpp:325 msgid "Required:" msgstr "必要​能力:" -#: Source/items.cpp:1905 Source/stores.cpp:327 +#: Source/items.cpp:1904 Source/stores.cpp:327 msgid " {:d} Str" msgstr " {:d} Str" -#: Source/items.cpp:1907 Source/stores.cpp:329 +#: Source/items.cpp:1906 Source/stores.cpp:329 msgid " {:d} Mag" msgstr " {:d} Mag" -#: Source/items.cpp:1909 Source/stores.cpp:331 +#: Source/items.cpp:1908 Source/stores.cpp:331 msgid " {:d} Dex" msgstr " {:d} Dex" #. TRANSLATORS: {:s} will be a Character Name -#: Source/items.cpp:2284 +#: Source/items.cpp:2283 msgid "Ear of {:s}" msgstr "{:s}​の​耳" -#: Source/items.cpp:3709 +#: Source/items.cpp:3673 msgid "chance to hit: {:+d}%" msgstr "命中​率: {:+d}​%" -#: Source/items.cpp:3712 +#: Source/items.cpp:3676 #, no-c-format msgid "{:+d}% damage" msgstr "{:+d}​%​ダメージ" -#: Source/items.cpp:3715 Source/items.cpp:3899 +#: Source/items.cpp:3679 Source/items.cpp:3863 msgid "to hit: {:+d}%, {:+d}% damage" msgstr "命中​率:{:+d}​%​, {:+d}​% ダメージ" -#: Source/items.cpp:3718 +#: Source/items.cpp:3682 #, no-c-format msgid "{:+d}% armor" msgstr "{:+d}​%​防護​力" -#: Source/items.cpp:3721 +#: Source/items.cpp:3685 msgid "armor class: {:d}" msgstr "防御​力: {:d}" -#: Source/items.cpp:3725 +#: Source/items.cpp:3689 msgid "Resist Fire: {:+d}%" msgstr "耐火​炎: {:+d}​%" -#: Source/items.cpp:3727 +#: Source/items.cpp:3691 msgid "Resist Fire: {:+d}% MAX" msgstr "耐火​炎: {:+d}​% MAX" -#: Source/items.cpp:3731 +#: Source/items.cpp:3695 msgid "Resist Lightning: {:+d}%" msgstr "耐​電撃: {:+d}​%" -#: Source/items.cpp:3733 +#: Source/items.cpp:3697 msgid "Resist Lightning: {:+d}% MAX" msgstr "耐​電撃: {:+d}​% MAX" -#: Source/items.cpp:3737 +#: Source/items.cpp:3701 msgid "Resist Magic: {:+d}%" msgstr "耐​魔法: {:+d}​%" -#: Source/items.cpp:3739 +#: Source/items.cpp:3703 msgid "Resist Magic: {:+d}% MAX" msgstr "耐​魔法: {:+d}​% MAX" -#: Source/items.cpp:3742 +#: Source/items.cpp:3706 msgid "Resist All: {:+d}%" msgstr "全​耐性: {:+d}​%" -#: Source/items.cpp:3744 +#: Source/items.cpp:3708 msgid "Resist All: {:+d}% MAX" msgstr "全​耐性: {:+d}​% MAX" -#: Source/items.cpp:3747 +#: Source/items.cpp:3711 msgid "spells are increased {:d} level" msgid_plural "spells are increased {:d} levels" msgstr[0] "全呪文{:d} レベルアップ" -#: Source/items.cpp:3749 +#: Source/items.cpp:3713 msgid "spells are decreased {:d} level" msgid_plural "spells are decreased {:d} levels" msgstr[0] "全呪文{:d}レベルダウン" -#: Source/items.cpp:3751 +#: Source/items.cpp:3715 msgid "spell levels unchanged (?)" msgstr "呪文​の​レベル​に​変化​なし​(​?​)" -#: Source/items.cpp:3753 +#: Source/items.cpp:3717 msgid "Extra charges" msgstr "追加​チャージ" -#: Source/items.cpp:3755 +#: Source/items.cpp:3719 msgid "{:d} {:s} charge" msgid_plural "{:d} {:s} charges" msgstr[0] "{:d} {:s}チャージ" -#: Source/items.cpp:3758 +#: Source/items.cpp:3722 msgid "Fire hit damage: {:d}" msgstr "追加​火炎​ダメージ:{:d}" -#: Source/items.cpp:3760 +#: Source/items.cpp:3724 msgid "Fire hit damage: {:d}-{:d}" msgstr "追加​火炎​ダメージ: {:d}-{:d}" -#: Source/items.cpp:3763 +#: Source/items.cpp:3727 msgid "Lightning hit damage: {:d}" msgstr "追加​電撃​ダメージ: {:d}" -#: Source/items.cpp:3765 +#: Source/items.cpp:3729 msgid "Lightning hit damage: {:d}-{:d}" msgstr "追加​電撃​ダメージ: {:d}-{:d}" -#: Source/items.cpp:3768 +#: Source/items.cpp:3732 msgid "{:+d} to strength" msgstr "STR{:+d}" -#: Source/items.cpp:3771 +#: Source/items.cpp:3735 msgid "{:+d} to magic" msgstr "MAG{:+d}" -#: Source/items.cpp:3774 +#: Source/items.cpp:3738 msgid "{:+d} to dexterity" msgstr "DEX{:+d}" -#: Source/items.cpp:3777 +#: Source/items.cpp:3741 msgid "{:+d} to vitality" msgstr "VIT{:+d}" -#: Source/items.cpp:3780 +#: Source/items.cpp:3744 msgid "{:+d} to all attributes" msgstr "全​能力​値​{:+d}" -#: Source/items.cpp:3783 +#: Source/items.cpp:3747 msgid "{:+d} damage from enemies" msgstr "敵​から​受ける​ダメージ​{:+d}" -#: Source/items.cpp:3786 +#: Source/items.cpp:3750 msgid "Hit Points: {:+d}" msgstr "ヒット​ポイント: {:+d}" -#: Source/items.cpp:3789 +#: Source/items.cpp:3753 msgid "Mana: {:+d}" msgstr "マナ: {:+d}" -#: Source/items.cpp:3791 +#: Source/items.cpp:3755 msgid "high durability" msgstr "高い​耐久​度" -#: Source/items.cpp:3793 +#: Source/items.cpp:3757 msgid "decreased durability" msgstr "低い​耐久​度" -#: Source/items.cpp:3795 +#: Source/items.cpp:3759 msgid "indestructible" msgstr "壊れ​ない" -#: Source/items.cpp:3797 +#: Source/items.cpp:3761 #, no-c-format msgid "+{:d}% light radius" msgstr "視界​+{:d}​%" -#: Source/items.cpp:3799 +#: Source/items.cpp:3763 #, no-c-format msgid "-{:d}% light radius" msgstr "視界​-{:d}​%" -#: Source/items.cpp:3801 +#: Source/items.cpp:3765 msgid "multiple arrows per shot" msgstr "一回​の​射撃​で​複数​の​矢​を​放つ" -#: Source/items.cpp:3804 +#: Source/items.cpp:3768 msgid "fire arrows damage: {:d}" msgstr "火炎​矢​の​ダメージ: {:d}" -#: Source/items.cpp:3806 +#: Source/items.cpp:3770 msgid "fire arrows damage: {:d}-{:d}" msgstr "火炎​矢​の​ダメージ: {:d}-{:d}" -#: Source/items.cpp:3809 +#: Source/items.cpp:3773 msgid "lightning arrows damage {:d}" msgstr "電撃​矢​の​ダメージ: {:d}" -#: Source/items.cpp:3811 +#: Source/items.cpp:3775 msgid "lightning arrows damage {:d}-{:d}" msgstr "電撃​矢​の​ダメージ: {:d}-{:d}" -#: Source/items.cpp:3814 +#: Source/items.cpp:3778 msgid "fireball damage: {:d}" msgstr "ファイヤーボールダメージ: {:d}" -#: Source/items.cpp:3816 +#: Source/items.cpp:3780 msgid "fireball damage: {:d}-{:d}" msgstr "ファイヤーボールダメージ: {:d}-{:d}" -#: Source/items.cpp:3818 +#: Source/items.cpp:3782 msgid "attacker takes 1-3 damage" msgstr "ダメージ​時​に​反撃:1-3" -#: Source/items.cpp:3820 +#: Source/items.cpp:3784 msgid "user loses all mana" msgstr "全て​の​マナ​を​失う" -#: Source/items.cpp:3822 +#: Source/items.cpp:3786 msgid "absorbs half of trap damage" msgstr "罠​に​よる​ダメージ​半減" -#: Source/items.cpp:3824 +#: Source/items.cpp:3788 msgid "knocks target back" msgstr "ノック​バック​効果" -#: Source/items.cpp:3826 +#: Source/items.cpp:3790 #, no-c-format msgid "+200% damage vs. demons" msgstr "対​悪魔​族​ダメージ​+200​%" -#: Source/items.cpp:3828 +#: Source/items.cpp:3792 msgid "All Resistance equals 0" msgstr "全て​の​耐性​を​失う" -#: Source/items.cpp:3831 +#: Source/items.cpp:3795 #, no-c-format msgid "hit steals 3% mana" msgstr "マナ​を​3​%​吸収" -#: Source/items.cpp:3833 +#: Source/items.cpp:3797 #, no-c-format msgid "hit steals 5% mana" msgstr "マナ​を​5​%​吸収" -#: Source/items.cpp:3837 +#: Source/items.cpp:3801 #, no-c-format msgid "hit steals 3% life" msgstr "ライフ​を​3​%​吸収" -#: Source/items.cpp:3839 +#: Source/items.cpp:3803 #, no-c-format msgid "hit steals 5% life" msgstr "ライフ​を​5​%​吸収" -#: Source/items.cpp:3842 +#: Source/items.cpp:3806 msgid "penetrates target's armor" msgstr "ターゲット​の​アーマー​を​貫通" -#: Source/items.cpp:3845 +#: Source/items.cpp:3809 msgid "quick attack" msgstr "速い​攻撃" -#: Source/items.cpp:3847 +#: Source/items.cpp:3811 msgid "fast attack" msgstr "高速​攻撃" -#: Source/items.cpp:3849 +#: Source/items.cpp:3813 msgid "faster attack" msgstr "超​高速​攻撃" -#: Source/items.cpp:3851 +#: Source/items.cpp:3815 msgid "fastest attack" msgstr "神速​攻撃" -#: Source/items.cpp:3852 Source/items.cpp:3860 Source/items.cpp:3909 +#: Source/items.cpp:3816 Source/items.cpp:3824 Source/items.cpp:3873 msgid "Another ability (NW)" msgstr "別​の​能力​(​NW​)" -#: Source/items.cpp:3855 +#: Source/items.cpp:3819 msgid "fast hit recovery" msgstr "高速​体勢​回復" -#: Source/items.cpp:3857 +#: Source/items.cpp:3821 msgid "faster hit recovery" msgstr "早い​体勢​回復" -#: Source/items.cpp:3859 +#: Source/items.cpp:3823 msgid "fastest hit recovery" msgstr "神速​体勢​回復" -#: Source/items.cpp:3862 +#: Source/items.cpp:3826 msgid "fast block" msgstr "早い​防御" -#: Source/items.cpp:3864 +#: Source/items.cpp:3828 msgid "adds {:d} point to damage" msgid_plural "adds {:d} points to damage" msgstr[0] "追加ダメージ: {:d}" -#: Source/items.cpp:3866 +#: Source/items.cpp:3830 msgid "fires random speed arrows" msgstr "発射​間隔​が​一定​で​ない" -#: Source/items.cpp:3868 +#: Source/items.cpp:3832 msgid "unusual item damage" msgstr "魔法​の​アイテム​に​ダメージ" -#: Source/items.cpp:3870 +#: Source/items.cpp:3834 msgid "altered durability" msgstr "特殊​な​耐久​度" -#: Source/items.cpp:3872 +#: Source/items.cpp:3836 msgid "one handed sword" msgstr "片手​持ち​ソード" -#: Source/items.cpp:3874 +#: Source/items.cpp:3838 msgid "constantly lose hit points" msgstr "少し​ずつ​ライフ​を​失う" -#: Source/items.cpp:3876 +#: Source/items.cpp:3840 msgid "life stealing" msgstr "直接​攻撃​で​ライフ​吸収" -#: Source/items.cpp:3878 +#: Source/items.cpp:3842 msgid "no strength requirement" msgstr "筋力​を​必要​と​し​ない" -#: Source/items.cpp:3883 +#: Source/items.cpp:3847 msgid "lightning damage: {:d}" msgstr "電撃​ダメージ: {:d}" -#: Source/items.cpp:3885 +#: Source/items.cpp:3849 msgid "lightning damage: {:d}-{:d}" msgstr "電撃​ダメージ: {:d}-{:d}" -#: Source/items.cpp:3887 +#: Source/items.cpp:3851 msgid "charged bolts on hits" msgstr "チャージド​ボルト" -#: Source/items.cpp:3889 +#: Source/items.cpp:3853 msgid "occasional triple damage" msgstr "たまに​3​倍​の​ダメージ" -#: Source/items.cpp:3891 +#: Source/items.cpp:3855 #, no-c-format msgid "decaying {:+d}% damage" msgstr "崩壊​{:+d}​%​ダメージ" -#: Source/items.cpp:3893 +#: Source/items.cpp:3857 msgid "2x dmg to monst, 1x to you" msgstr "モンスター​に​2​倍​の​ダメージ、自分​に​1​倍​の​ダメージ" -#: Source/items.cpp:3895 +#: Source/items.cpp:3859 #, no-c-format msgid "Random 0 - 600% damage" msgstr "ランダム​に​0​~​600​%​の​ダメージ" -#: Source/items.cpp:3897 +#: Source/items.cpp:3861 #, no-c-format msgid "low dur, {:+d}% damage" msgstr "低い​耐久性、{:+d}​%​の​ダメージ" -#: Source/items.cpp:3901 +#: Source/items.cpp:3865 msgid "extra AC vs demons" msgstr "対​悪魔​族​の​追加​AC" -#: Source/items.cpp:3903 +#: Source/items.cpp:3867 msgid "extra AC vs undead" msgstr "対​アンデッド​用​の​追加​AC" -#: Source/items.cpp:3905 +#: Source/items.cpp:3869 msgid "50% Mana moved to Health" msgstr "50​%​の​マナ​を​ヘルス​に​移動" -#: Source/items.cpp:3907 +#: Source/items.cpp:3871 msgid "40% Health moved to Mana" msgstr "40​%​の​ヘルス​を​マナ​に​移動" -#: Source/items.cpp:3947 Source/items.cpp:3988 +#: Source/items.cpp:3911 Source/items.cpp:3952 msgid "damage: {:d} Indestructible" msgstr "ダメージ:{:d} 壊れ​ない" #. TRANSLATORS: Dur: is durability -#: Source/items.cpp:3949 Source/items.cpp:3990 +#: Source/items.cpp:3913 Source/items.cpp:3954 msgid "damage: {:d} Dur: {:d}/{:d}" msgstr "ダメージ:{:d} 耐久​度:{:d}/{:d}" -#: Source/items.cpp:3952 Source/items.cpp:3993 +#: Source/items.cpp:3916 Source/items.cpp:3957 msgid "damage: {:d}-{:d} Indestructible" msgstr "ダメージ: {:d}-{:d} 壊れ​ない" #. TRANSLATORS: Dur: is durability -#: Source/items.cpp:3954 Source/items.cpp:3995 +#: Source/items.cpp:3918 Source/items.cpp:3959 msgid "damage: {:d}-{:d} Dur: {:d}/{:d}" msgstr "ダメージ:{:d}-{:d} 耐久​度:{:d}/{:d}" -#: Source/items.cpp:3959 Source/items.cpp:4005 +#: Source/items.cpp:3923 Source/items.cpp:3969 msgid "armor: {:d} Indestructible" msgstr "防御​力:{:d} 壊れ​ない" #. TRANSLATORS: Dur: is durability -#: Source/items.cpp:3961 Source/items.cpp:4007 +#: Source/items.cpp:3925 Source/items.cpp:3971 msgid "armor: {:d} Dur: {:d}/{:d}" msgstr "防御​力: {:d} 耐久​度: {:d}/{:d}" -#: Source/items.cpp:3964 Source/items.cpp:3998 Source/items.cpp:4011 +#: Source/items.cpp:3928 Source/items.cpp:3962 Source/items.cpp:3975 #: Source/stores.cpp:299 msgid "Charges: {:d}/{:d}" msgstr "チャージ: {:d}/{:d}" -#: Source/items.cpp:3973 +#: Source/items.cpp:3937 msgid "unique item" msgstr "ユニーク​アイテム" -#: Source/items.cpp:4001 Source/items.cpp:4009 Source/items.cpp:4015 +#: Source/items.cpp:3965 Source/items.cpp:3973 Source/items.cpp:3979 msgid "Not Identified" msgstr "未​鑑定" @@ -4778,11 +4916,11 @@ msgid "Chamber of Bone" msgstr "納骨堂" #. TRANSLATORS: Quest Map -#: Source/levels/setmaps.cpp:29 Source/quests.cpp:99 +#: Source/levels/setmaps.cpp:29 Source/quests.cpp:103 msgid "Maze" msgstr "迷宮" -#: Source/levels/setmaps.cpp:30 Source/quests.cpp:61 +#: Source/levels/setmaps.cpp:30 Source/quests.cpp:65 msgid "Poisoned Water Supply" msgstr "汚れ​た​水源" @@ -4802,86 +4940,86 @@ msgstr "ヘル​・​アリーナ" msgid "Circle of Life Arena" msgstr "サークル​・​オブ​・​ライフ​・​アリーナ" -#: Source/levels/trigs.cpp:350 +#: Source/levels/trigs.cpp:352 msgid "Down to dungeon" msgstr "ダンジョン​へ​の​入り口" -#: Source/levels/trigs.cpp:359 +#: Source/levels/trigs.cpp:361 msgid "Down to catacombs" msgstr "カタコンベ​へ​の​入り口" -#: Source/levels/trigs.cpp:369 +#: Source/levels/trigs.cpp:371 msgid "Down to caves" msgstr "ケイブ​へ​の​入り口" -#: Source/levels/trigs.cpp:379 +#: Source/levels/trigs.cpp:381 msgid "Down to hell" msgstr "ヘル​へ​の​入り口" -#: Source/levels/trigs.cpp:389 +#: Source/levels/trigs.cpp:391 msgid "Down to Hive" msgstr "ハイブ​へ​の​入り口" -#: Source/levels/trigs.cpp:399 +#: Source/levels/trigs.cpp:401 msgid "Down to Crypt" msgstr "地下​聖堂​へ​の​入り口" -#: Source/levels/trigs.cpp:414 Source/levels/trigs.cpp:449 -#: Source/levels/trigs.cpp:495 Source/levels/trigs.cpp:547 +#: Source/levels/trigs.cpp:416 Source/levels/trigs.cpp:451 +#: Source/levels/trigs.cpp:497 Source/levels/trigs.cpp:549 msgid "Up to level {:d}" msgstr "レベル​{:d}​へ​上がる" -#: Source/levels/trigs.cpp:416 Source/levels/trigs.cpp:478 -#: Source/levels/trigs.cpp:530 Source/levels/trigs.cpp:577 -#: Source/levels/trigs.cpp:639 Source/levels/trigs.cpp:688 -#: Source/levels/trigs.cpp:795 +#: Source/levels/trigs.cpp:418 Source/levels/trigs.cpp:480 +#: Source/levels/trigs.cpp:532 Source/levels/trigs.cpp:579 +#: Source/levels/trigs.cpp:641 Source/levels/trigs.cpp:690 +#: Source/levels/trigs.cpp:797 msgid "Up to town" msgstr "町​へ​戻る" -#: Source/levels/trigs.cpp:427 Source/levels/trigs.cpp:460 -#: Source/levels/trigs.cpp:512 Source/levels/trigs.cpp:559 -#: Source/levels/trigs.cpp:621 +#: Source/levels/trigs.cpp:429 Source/levels/trigs.cpp:462 +#: Source/levels/trigs.cpp:514 Source/levels/trigs.cpp:561 +#: Source/levels/trigs.cpp:623 msgid "Down to level {:d}" msgstr "レベル​{:d}​へ​下りる" -#: Source/levels/trigs.cpp:590 +#: Source/levels/trigs.cpp:592 msgid "Down to Diablo" msgstr "ディアブロ​の​もと​へ" -#: Source/levels/trigs.cpp:608 +#: Source/levels/trigs.cpp:610 msgid "Up to Nest level {:d}" msgstr "巣​レベル {:d}​へ​上がる" -#: Source/levels/trigs.cpp:656 +#: Source/levels/trigs.cpp:658 msgid "Up to Crypt level {:d}" msgstr "地下​聖堂​レベル {:d}​へ​上がる" -#: Source/levels/trigs.cpp:666 Source/quests.cpp:70 +#: Source/levels/trigs.cpp:668 Source/quests.cpp:74 msgid "Cornerstone of the World" msgstr "世界​の​礎" -#: Source/levels/trigs.cpp:671 +#: Source/levels/trigs.cpp:673 msgid "Down to Crypt level {:d}" msgstr "地下​聖堂​へ​の​入り口 レベル {:d}" -#: Source/levels/trigs.cpp:719 Source/levels/trigs.cpp:733 -#: Source/levels/trigs.cpp:747 +#: Source/levels/trigs.cpp:721 Source/levels/trigs.cpp:735 +#: Source/levels/trigs.cpp:749 msgid "Back to Level {:d}" msgstr "レベル​{:d}​へ​戻る" -#: Source/loadsave.cpp:2092 Source/loadsave.cpp:2624 +#: Source/loadsave.cpp:2093 Source/loadsave.cpp:2625 msgid "Unable to open save file archive" msgstr "Unable to open save file archive" -#: Source/loadsave.cpp:2095 +#: Source/loadsave.cpp:2096 msgid "Invalid save file" msgstr "Invalid save file" -#: Source/loadsave.cpp:2126 +#: Source/loadsave.cpp:2127 msgid "Player is on a Hellfire only level" msgstr "プレイヤー​は​Hellfire​のみ​の​レベル​に​い​ます" -#: Source/loadsave.cpp:2382 +#: Source/loadsave.cpp:2383 msgid "Invalid game state" msgstr "無効​な​ゲーム​状態" @@ -6010,71 +6148,71 @@ msgctxt "monster" msgid "Doomlock" msgstr "ドゥーム​ロック" -#: Source/monster.cpp:2964 +#: Source/monster.cpp:2966 msgid "Animal" msgstr "アニマル" -#: Source/monster.cpp:2966 +#: Source/monster.cpp:2968 msgid "Demon" msgstr "デーモン" -#: Source/monster.cpp:2968 +#: Source/monster.cpp:2970 msgid "Undead" msgstr "アンデッド" -#: Source/monster.cpp:4240 +#: Source/monster.cpp:4242 msgid "Type: {:s} Kills: {:d}" msgstr "タイプ: {:s} 殺傷​数: {:d}" -#: Source/monster.cpp:4242 +#: Source/monster.cpp:4244 msgid "Total kills: {:d}" msgstr "殺傷​数: {:d}" -#: Source/monster.cpp:4274 +#: Source/monster.cpp:4276 msgid "Hit Points: {:d}-{:d}" msgstr "ヒット​ポイント: {:d}-{:d}" -#: Source/monster.cpp:4279 +#: Source/monster.cpp:4281 msgid "No magic resistance" msgstr "魔法​耐性​無し" -#: Source/monster.cpp:4282 +#: Source/monster.cpp:4284 msgid "Resists:" msgstr "耐性:" -#: Source/monster.cpp:4284 Source/monster.cpp:4294 +#: Source/monster.cpp:4286 Source/monster.cpp:4296 msgid " Magic" msgstr " 魔法" -#: Source/monster.cpp:4286 Source/monster.cpp:4296 +#: Source/monster.cpp:4288 Source/monster.cpp:4298 msgid " Fire" msgstr " 火炎" -#: Source/monster.cpp:4288 Source/monster.cpp:4298 +#: Source/monster.cpp:4290 Source/monster.cpp:4300 msgid " Lightning" msgstr " ライトニング" -#: Source/monster.cpp:4292 +#: Source/monster.cpp:4294 msgid "Immune:" msgstr "無効:" -#: Source/monster.cpp:4309 +#: Source/monster.cpp:4311 msgid "Type: {:s}" msgstr "タイプ: {:s}" -#: Source/monster.cpp:4314 Source/monster.cpp:4320 +#: Source/monster.cpp:4316 Source/monster.cpp:4322 msgid "No resistances" msgstr "耐性​無し" -#: Source/monster.cpp:4315 Source/monster.cpp:4324 +#: Source/monster.cpp:4317 Source/monster.cpp:4326 msgid "No Immunities" msgstr "無効​無し" -#: Source/monster.cpp:4318 +#: Source/monster.cpp:4320 msgid "Some Magic Resistances" msgstr "魔法​耐性​あり​(​属性​不明​)" -#: Source/monster.cpp:4322 +#: Source/monster.cpp:4324 msgid "Some Magic Immunities" msgstr "魔法​無効化​(​属性​不明​)" @@ -6082,27 +6220,27 @@ msgstr "魔法​無効化​(​属性​不明​)" msgid "Failed to open archive for writing." msgstr "書き込み​の​ため​の​アーカイブ​を​開く​の​に​失敗​し​まし​た。" -#: Source/msg.cpp:770 +#: Source/msg.cpp:771 msgid "Trying to drop a floor item?" msgstr "フロア​アイテム​を​落とそう​と​し​て​いる​の​か?" -#: Source/msg.cpp:1374 +#: Source/msg.cpp:1375 msgid "{:s} has cast an invalid spell." msgstr "{:s} が​無効​な​呪文​を​唱え​た。" -#: Source/msg.cpp:1378 +#: Source/msg.cpp:1379 msgid "{:s} has cast an illegal spell." msgstr "{:s} が​不正​な​呪文​を​唱え​た。" -#: Source/msg.cpp:2028 Source/multi.cpp:797 Source/multi.cpp:847 +#: Source/msg.cpp:2029 Source/multi.cpp:797 Source/multi.cpp:848 msgid "Player '{:s}' (level {:d}) just joined the game" msgstr "プレイヤー​「​{:s}​」​(​レベル​{:d}​)​が​ゲーム​に​参加​し​まし​た" -#: Source/msg.cpp:2395 +#: Source/msg.cpp:2396 msgid "The game ended" msgstr "ゲーム​終了" -#: Source/msg.cpp:2401 +#: Source/msg.cpp:2402 msgid "Unable to get level data" msgstr "Unable to get level data" @@ -6118,396 +6256,396 @@ msgstr "プレイヤー​「​{:s}​」​が​Diablo​を​殺し​て msgid "Player '{:s}' dropped due to timeout" msgstr "プレイヤー​「​{:s}​」​が​タイムアウト​で​脱落" -#: Source/multi.cpp:849 +#: Source/multi.cpp:850 msgid "Player '{:s}' (level {:d}) is already in the game" msgstr "プレイヤー​「​{:s}​」​(​レベル​{:d}​)​は​すでに​ゲーム​に​参加​し​て​い​ます" #. TRANSLATORS: Shrine Name Block -#: Source/objects.cpp:121 +#: Source/objects.cpp:123 msgid "Mysterious" msgstr "奇怪​な" -#: Source/objects.cpp:122 +#: Source/objects.cpp:124 msgid "Hidden" msgstr "ヒドゥン" -#: Source/objects.cpp:123 +#: Source/objects.cpp:125 msgid "Gloomy" msgstr "漆黒​の" -#: Source/objects.cpp:125 Source/objects.cpp:132 +#: Source/objects.cpp:127 Source/objects.cpp:134 msgid "Magical" msgstr "魔術​の" -#: Source/objects.cpp:126 +#: Source/objects.cpp:128 msgid "Stone" msgstr "石​の" -#: Source/objects.cpp:127 +#: Source/objects.cpp:129 msgid "Religious" msgstr "信仰​の" -#: Source/objects.cpp:128 +#: Source/objects.cpp:130 msgid "Enchanted" msgstr "帯​魔​の" -#: Source/objects.cpp:129 +#: Source/objects.cpp:131 msgid "Thaumaturgic" msgstr "奇跡​の" -#: Source/objects.cpp:130 +#: Source/objects.cpp:132 msgid "Fascinating" msgstr "魅了​の" -#: Source/objects.cpp:131 +#: Source/objects.cpp:133 msgid "Cryptic" msgstr "秘密​の" -#: Source/objects.cpp:133 +#: Source/objects.cpp:135 msgid "Eldritch" msgstr "妖魔​の" -#: Source/objects.cpp:134 +#: Source/objects.cpp:136 msgid "Eerie" msgstr "不審​な" -#: Source/objects.cpp:135 +#: Source/objects.cpp:137 msgid "Divine" msgstr "神々しい" -#: Source/objects.cpp:137 +#: Source/objects.cpp:139 msgid "Sacred" msgstr "厳粛​なる" -#: Source/objects.cpp:138 +#: Source/objects.cpp:140 msgid "Spiritual" msgstr "魂​の" -#: Source/objects.cpp:139 +#: Source/objects.cpp:141 msgid "Spooky" msgstr "憑霊​の" -#: Source/objects.cpp:140 +#: Source/objects.cpp:142 msgid "Abandoned" msgstr "荒涼​たる" -#: Source/objects.cpp:141 +#: Source/objects.cpp:143 msgid "Creepy" msgstr "陰湿​な" -#: Source/objects.cpp:142 +#: Source/objects.cpp:144 msgid "Quiet" msgstr "終了" -#: Source/objects.cpp:143 +#: Source/objects.cpp:145 msgid "Secluded" msgstr "隠遁​の" -#: Source/objects.cpp:144 +#: Source/objects.cpp:146 msgid "Ornate" msgstr "華麗​なる" -#: Source/objects.cpp:145 +#: Source/objects.cpp:147 msgid "Glimmering" msgstr "燐光​の" -#: Source/objects.cpp:146 +#: Source/objects.cpp:148 msgid "Tainted" msgstr "穢れ​た" -#: Source/objects.cpp:147 +#: Source/objects.cpp:149 msgid "Oily" msgstr "油性" -#: Source/objects.cpp:148 +#: Source/objects.cpp:150 msgid "Glowing" msgstr "光る" -#: Source/objects.cpp:149 +#: Source/objects.cpp:151 msgid "Mendicant's" msgstr "メンディキャンズ" -#: Source/objects.cpp:150 +#: Source/objects.cpp:152 msgid "Sparkling" msgstr "スパークリング" -#: Source/objects.cpp:152 +#: Source/objects.cpp:154 msgid "Shimmering" msgstr "煌めき" -#: Source/objects.cpp:153 +#: Source/objects.cpp:155 msgid "Solar" msgstr "ソーラー" #. TRANSLATORS: Shrine Name Block end -#: Source/objects.cpp:155 +#: Source/objects.cpp:157 msgid "Murphy's" msgstr "マーフィーズ" #. TRANSLATORS: Book Title -#: Source/objects.cpp:285 +#: Source/objects.cpp:210 msgid "The Great Conflict" msgstr "偉大​なる​闘争" #. TRANSLATORS: Book Title -#: Source/objects.cpp:286 +#: Source/objects.cpp:211 msgid "The Wages of Sin are War" msgstr "罪​深き​闘い" #. TRANSLATORS: Book Title -#: Source/objects.cpp:287 +#: Source/objects.cpp:212 msgid "The Tale of the Horadrim" msgstr "ホラドリム​の​物語" #. TRANSLATORS: Book Title -#: Source/objects.cpp:288 +#: Source/objects.cpp:213 msgid "The Dark Exile" msgstr "闇​の​追放" #. TRANSLATORS: Book Title -#: Source/objects.cpp:289 +#: Source/objects.cpp:214 msgid "The Sin War" msgstr "罪​深き​闘い" #. TRANSLATORS: Book Title -#: Source/objects.cpp:290 +#: Source/objects.cpp:215 msgid "The Binding of the Three" msgstr "三​兄弟​の​呪縛" #. TRANSLATORS: Book Title -#: Source/objects.cpp:291 +#: Source/objects.cpp:216 msgid "The Realms Beyond" msgstr "彼方​の​王国" #. TRANSLATORS: Book Title -#: Source/objects.cpp:292 +#: Source/objects.cpp:217 msgid "Tale of the Three" msgstr "三​大​邪悪​の​物語" #. TRANSLATORS: Book Title -#: Source/objects.cpp:293 +#: Source/objects.cpp:218 msgid "The Black King" msgstr "暗黒​の​王" #. TRANSLATORS: Book Title -#: Source/objects.cpp:294 +#: Source/objects.cpp:219 msgid "Journal: The Ensorcellment" msgstr "ジャーナル: 包囲" #. TRANSLATORS: Book Title -#: Source/objects.cpp:295 +#: Source/objects.cpp:220 msgid "Journal: The Meeting" msgstr "ジャーナル: 会議" #. TRANSLATORS: Book Title -#: Source/objects.cpp:296 +#: Source/objects.cpp:221 msgid "Journal: The Tirade" msgstr "ジャーナル: ティラード" #. TRANSLATORS: Book Title -#: Source/objects.cpp:297 +#: Source/objects.cpp:222 msgid "Journal: His Power Grows" msgstr "ジャーナル: 勢力​拡大" #. TRANSLATORS: Book Title -#: Source/objects.cpp:298 +#: Source/objects.cpp:223 msgid "Journal: NA-KRUL" msgstr "ジャーナル: ナ​・​クルル" #. TRANSLATORS: Book Title -#: Source/objects.cpp:299 +#: Source/objects.cpp:224 msgid "Journal: The End" msgstr "ジャーナル: 終焉" #. TRANSLATORS: Book Title -#: Source/objects.cpp:300 +#: Source/objects.cpp:225 msgid "A Spellbook" msgstr "スペル​ブック" -#: Source/objects.cpp:4885 +#: Source/objects.cpp:4771 msgid "Crucified Skeleton" msgstr "はりつけ​の​スケルトン" -#: Source/objects.cpp:4889 +#: Source/objects.cpp:4775 msgid "Lever" msgstr "レバー" -#: Source/objects.cpp:4899 +#: Source/objects.cpp:4785 msgid "Open Door" msgstr "開い​た​ドア" -#: Source/objects.cpp:4901 +#: Source/objects.cpp:4787 msgid "Closed Door" msgstr "閉じ​た​ドア" -#: Source/objects.cpp:4903 +#: Source/objects.cpp:4789 msgid "Blocked Door" msgstr "閉じ​られ​ない​ドア" -#: Source/objects.cpp:4908 +#: Source/objects.cpp:4794 msgid "Ancient Tome" msgstr "太古​の​書" -#: Source/objects.cpp:4910 +#: Source/objects.cpp:4796 msgid "Book of Vileness" msgstr "外道​の​書" -#: Source/objects.cpp:4915 +#: Source/objects.cpp:4801 msgid "Skull Lever" msgstr "ドクロ​の​レバー" -#: Source/objects.cpp:4917 +#: Source/objects.cpp:4803 msgid "Mythical Book" msgstr "神話​の​書" -#: Source/objects.cpp:4920 +#: Source/objects.cpp:4806 msgid "Small Chest" msgstr "小さな​チェスト" -#: Source/objects.cpp:4923 +#: Source/objects.cpp:4809 msgid "Chest" msgstr "チェスト" -#: Source/objects.cpp:4927 +#: Source/objects.cpp:4813 msgid "Large Chest" msgstr "大きな​チェスト" -#: Source/objects.cpp:4930 +#: Source/objects.cpp:4816 msgid "Sarcophagus" msgstr "棺" -#: Source/objects.cpp:4932 +#: Source/objects.cpp:4818 msgid "Bookshelf" msgstr "読​台" -#: Source/objects.cpp:4935 +#: Source/objects.cpp:4821 msgid "Bookcase" msgstr "本棚" -#: Source/objects.cpp:4938 +#: Source/objects.cpp:4824 msgid "Barrel" msgstr "樽" -#: Source/objects.cpp:4941 +#: Source/objects.cpp:4827 msgid "Pod" msgstr "ポッド" -#: Source/objects.cpp:4944 +#: Source/objects.cpp:4830 msgid "Urn" msgstr "Urn" #. TRANSLATORS: {:s} will be a name from the Shrine block above -#: Source/objects.cpp:4947 +#: Source/objects.cpp:4833 msgid "{:s} Shrine" msgstr "{:s} 祭壇" -#: Source/objects.cpp:4949 +#: Source/objects.cpp:4835 msgid "Skeleton Tome" msgstr "スケルトン​の​書" -#: Source/objects.cpp:4951 +#: Source/objects.cpp:4837 msgid "Library Book" msgstr "蔵書" -#: Source/objects.cpp:4953 +#: Source/objects.cpp:4839 msgid "Blood Fountain" msgstr "血色​の​泉" -#: Source/objects.cpp:4955 +#: Source/objects.cpp:4841 msgid "Decapitated Body" msgstr "首​無し​の​死体" -#: Source/objects.cpp:4957 +#: Source/objects.cpp:4843 msgid "Book of the Blind" msgstr "盲目​の​書" -#: Source/objects.cpp:4959 +#: Source/objects.cpp:4845 msgid "Book of Blood" msgstr "血潮​の​書" -#: Source/objects.cpp:4961 +#: Source/objects.cpp:4847 msgid "Purifying Spring" msgstr "清浄​なる​泉" -#: Source/objects.cpp:4966 Source/objects.cpp:4983 +#: Source/objects.cpp:4852 Source/objects.cpp:4869 msgid "Weapon Rack" msgstr "武器​架" -#: Source/objects.cpp:4968 +#: Source/objects.cpp:4854 msgid "Goat Shrine" msgstr "山羊​の​祭壇" -#: Source/objects.cpp:4970 +#: Source/objects.cpp:4856 msgid "Cauldron" msgstr "地獄​の​大釜" -#: Source/objects.cpp:4972 +#: Source/objects.cpp:4858 msgid "Murky Pool" msgstr "濁っ​た​池" -#: Source/objects.cpp:4974 +#: Source/objects.cpp:4860 msgid "Fountain of Tears" msgstr "涙​の​源泉" -#: Source/objects.cpp:4976 +#: Source/objects.cpp:4862 msgid "Steel Tome" msgstr "刀剣​の​書" -#: Source/objects.cpp:4978 +#: Source/objects.cpp:4864 msgid "Pedestal of Blood" msgstr "血​の​台座" -#: Source/objects.cpp:4985 +#: Source/objects.cpp:4871 msgid "Mushroom Patch" msgstr "キノコ​の​群生" -#: Source/objects.cpp:4987 +#: Source/objects.cpp:4873 msgid "Vile Stand" msgstr "忌まわし​き​台座" -#: Source/objects.cpp:4989 +#: Source/objects.cpp:4875 msgid "Slain Hero" msgstr "死​せ​る​勇者" #. TRANSLATORS: {:s} will either be a chest or a door -#: Source/objects.cpp:5001 +#: Source/objects.cpp:4887 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:5006 +#: Source/objects.cpp:4892 msgid "{:s} (disabled)" msgstr "{:s} (​無効​)" -#: Source/options.cpp:455 Source/options.cpp:580 Source/options.cpp:586 +#: Source/options.cpp:457 Source/options.cpp:581 Source/options.cpp:587 msgid "ON" msgstr "オン" -#: Source/options.cpp:455 Source/options.cpp:578 Source/options.cpp:584 +#: Source/options.cpp:457 Source/options.cpp:579 Source/options.cpp:585 msgid "OFF" msgstr "オフ" -#: Source/options.cpp:568 +#: Source/options.cpp:569 msgid "Start Up" msgstr "起動" -#: Source/options.cpp:568 +#: Source/options.cpp:569 msgid "Start Up Settings" msgstr "起動​時​の​設定" -#: Source/options.cpp:569 +#: Source/options.cpp:570 msgid "Game Mode" msgstr "ゲーム​モード" -#: Source/options.cpp:569 +#: Source/options.cpp:570 msgid "Play Diablo or Hellfire." msgstr "ディアブロ、また​は​ヘルファイア​を​選択​し​ます。" -#: Source/options.cpp:575 +#: Source/options.cpp:576 msgid "Restrict to Shareware" msgstr "シェアウェア​の​制限" -#: Source/options.cpp:575 +#: Source/options.cpp:576 msgid "" "Makes the game compatible with the demo. Enables multiplayer with friends " "who don't own a full copy of Diablo." @@ -6515,107 +6653,103 @@ msgstr "" "ゲーム​を​体験版​と​互換性​の​ある​もの​に​し​ます。ディアブロ​の​製品​版​を​持っ​て​い​ない​友" "人​と​の​マルチ​プレイ​が​可能​に​なり​ます。" -#: Source/options.cpp:576 Source/options.cpp:582 +#: Source/options.cpp:577 Source/options.cpp:583 msgid "Intro" msgstr "イントロ" -#: Source/options.cpp:576 Source/options.cpp:582 +#: Source/options.cpp:577 Source/options.cpp:583 msgid "Shown Intro cinematic." msgstr "イントロ​・​シネマ​ティック​を​表示​し​ます。" -#: Source/options.cpp:588 +#: Source/options.cpp:589 msgid "Splash" msgstr "スプラッシュ" -#: Source/options.cpp:588 +#: Source/options.cpp:589 msgid "Shown splash screen." msgstr "スプラッシュ​画面​を​設定​し​ます。" -#: Source/options.cpp:590 +#: Source/options.cpp:591 msgid "Logo and Title Screen" msgstr "ロゴ​と​タイトル​画面" -#: Source/options.cpp:591 +#: Source/options.cpp:592 msgid "Title Screen" msgstr "タイトル​画面" -#: Source/options.cpp:610 +#: Source/options.cpp:611 msgid "Diablo specific Settings" msgstr "ディアブロ​固有​の​設定" -#: Source/options.cpp:624 +#: Source/options.cpp:625 msgid "Hellfire specific Settings" msgstr "ヘルファイア​固有​の​設定" -#: Source/options.cpp:638 +#: Source/options.cpp:639 msgid "Audio" msgstr "オーディオ" -#: Source/options.cpp:638 +#: Source/options.cpp:639 msgid "Audio Settings" msgstr "オーディオ​設定" -#: Source/options.cpp:641 +#: Source/options.cpp:642 msgid "Walking Sound" msgstr "歩行​音" -#: Source/options.cpp:641 +#: Source/options.cpp:642 msgid "Player emits sound when walking." msgstr "歩行​時​に​音​が​出る​よう​に​なり​ます。" -#: Source/options.cpp:642 +#: Source/options.cpp:643 msgid "Auto Equip Sound" msgstr "自動​装備​サウンド" -#: Source/options.cpp:642 +#: Source/options.cpp:643 msgid "Automatically equipping items on pickup emits the equipment sound." msgstr "ピックアップ​時​に​自動的​に​アイテム​を​装備​する​と、装備​音​が​鳴り​ます。" -#: Source/options.cpp:643 +#: Source/options.cpp:644 msgid "Item Pickup Sound" msgstr "アイテム​回収​音" -#: Source/options.cpp:643 +#: Source/options.cpp:644 msgid "Picking up items emits the items pickup sound." msgstr "アイテム​を​拾う​と、効果音​が​鳴り​ます。" -#: Source/options.cpp:644 +#: Source/options.cpp:645 msgid "Sample Rate" msgstr "サンプリングレート" -#: Source/options.cpp:644 +#: Source/options.cpp:645 msgid "Output sample rate (Hz)." msgstr "出力​サンプリングレート (​Hz​)。" -#: Source/options.cpp:645 +#: Source/options.cpp:646 msgid "Channels" msgstr "チャンネル" -#: Source/options.cpp:645 +#: Source/options.cpp:646 msgid "Number of output channels." msgstr "出力​チャンネル​数​です。" -#: Source/options.cpp:646 +#: Source/options.cpp:647 msgid "Buffer Size" msgstr "バッファ​サイズ" -#: Source/options.cpp:646 +#: Source/options.cpp:647 msgid "Buffer size (number of frames per channel)." msgstr "バッファ​サイズ​(​1​チャンネル​あたり​の​フレーム​数​)​です。" -#: Source/options.cpp:647 +#: Source/options.cpp:648 msgid "Resampling Quality" msgstr "再​サンプリング​品質" -#: Source/options.cpp:647 +#: Source/options.cpp:648 msgid "Quality of the resampler, from 0 (lowest) to 10 (highest)." msgstr "再​サンプラー​の​品質​を​0​(​最低​)​から​10​(​最高​)​まで​で​設定​し​ます。" -#: Source/options.cpp:678 -msgid "Resolution" -msgstr "解像度" - -#: Source/options.cpp:678 +#: Source/options.cpp:679 msgid "" "Affect the game's internal resolution and determine your view area. Note: " "This can differ from screen resolution, when Upscaling, Integer Scaling or " @@ -6794,10 +6928,6 @@ msgstr "ゲーム​プレイ" msgid "Gameplay Settings" msgstr "ゲーム​プレイ​設定" -#: Source/options.cpp:1045 -msgid "Run in Town" -msgstr "街​で​走る" - #: Source/options.cpp:1045 msgid "" "Enable jogging/fast walking in town for Diablo and Hellfire. This option was " @@ -6814,18 +6944,10 @@ msgstr "入力​捕捉" msgid "When enabled mouse is locked to the game window." msgstr "有効​に​する​と、マウス​が​ゲーム​ウィンドウ​に​ロック​さ​れ​ます。" -#: Source/options.cpp:1047 -msgid "Theo Quest" -msgstr "テオクエスト" - #: Source/options.cpp:1047 msgid "Enable Little Girl quest." msgstr "リトル​ガール​クエスト​を​有効​に​し​ます。" -#: Source/options.cpp:1048 -msgid "Cow Quest" -msgstr "牛​クエスト" - #: Source/options.cpp:1048 msgid "" "Enable Jersey's quest. Lester the farmer is replaced by the Complete Nut." @@ -6847,11 +6969,11 @@ msgstr "" #: Source/options.cpp:1050 msgid "Full quests in Multiplayer" -msgstr "マルチプレイでのフルクエスト" +msgstr "マルチ​プレイ​で​の​フルクエスト" #: Source/options.cpp:1050 msgid "Enables the full/uncut singleplayer version of quests." -msgstr "シングルプレイヤー版クエストのフル/ノーカットを有効にする。" +msgstr "シングルプレイヤー​版​クエスト​の​フル​/​ノーカット​を​有効​に​する。" #: Source/options.cpp:1051 msgid "Test Bard" @@ -6881,11 +7003,11 @@ msgstr "画面​下​の​UI​に​経験値​バー​を​追加​し #: Source/options.cpp:1054 msgid "Show Item Graphics in Stores" -msgstr "ストアでアイテム画像を表示する" +msgstr "ストア​で​アイテム​画像​を​表示​する" #: Source/options.cpp:1054 msgid "Show item graphics to the left of item descriptions in store menus." -msgstr "ストアメニューのアイテム説明の左側にアイテム画像を表示します。" +msgstr "ストア​メニュー​の​アイテム​説明​の​左側​に​アイテム​画像​を​表示​し​ます。" #: Source/options.cpp:1055 msgid "Show health values" @@ -6911,94 +7033,50 @@ msgstr "敵​の​ヘルスバー" msgid "Enemy Health Bar is displayed at the top of the screen." msgstr "画面​上部​に​敵​の​ヘルスバー​を​表示​し​ます。" -#: Source/options.cpp:1058 -msgid "Auto Gold Pickup" -msgstr "自動​ゴールド​回収" - #: Source/options.cpp:1058 msgid "Gold is automatically collected when in close proximity to the player." msgstr "プレイヤー​付近​の​ゴールド​を​自動的​に​回収​し​ます。" -#: Source/options.cpp:1059 -msgid "Auto Elixir Pickup" -msgstr "自動​エリクサー​回収" - #: Source/options.cpp:1059 msgid "" "Elixirs are automatically collected when in close proximity to the player." msgstr "プレイヤー​付近​の​エリクサー​を​自動的​に​回収​し​ます。" -#: Source/options.cpp:1060 -msgid "Auto Oil Pickup" -msgstr "自動​オイル​回収" - #: Source/options.cpp:1060 msgid "Oils are automatically collected when in close proximity to the player." msgstr "プレイヤー​付近​の​オイル​を​自動的​に​回収​し​ます。" -#: Source/options.cpp:1061 -msgid "Auto Pickup in Town" -msgstr "街中​で​自動​回収" - #: Source/options.cpp:1061 msgid "Automatically pickup items in town." msgstr "街中​で​自動的​に​アイテム​を​回収​し​ます。" -#: Source/options.cpp:1062 -msgid "Adria Refills Mana" -msgstr "エイドリア​の​マナ​補充" - #: Source/options.cpp:1062 msgid "Adria will refill your mana when you visit her shop." msgstr "エイドリア​の​店​を​訪れる​と、マナ​を​補充​し​て​くれ​ます。" -#: Source/options.cpp:1063 -msgid "Auto Equip Weapons" -msgstr "武器​の​自動​装備" - #: Source/options.cpp:1063 msgid "" "Weapons will be automatically equipped on pickup or purchase if enabled." msgstr "有効​に​する​と、武器​を​ピックアップ​や​購入​時​に​自動的​に​装備​さ​れ​ます。" -#: Source/options.cpp:1064 -msgid "Auto Equip Armor" -msgstr "アーマー​の​自動​装備" - #: Source/options.cpp:1064 msgid "Armor will be automatically equipped on pickup or purchase if enabled." msgstr "有効​に​する​と、アーマー​を​ピックアップ​や​購入​時​に​自動的​に​装備​さ​れ​ます。" -#: Source/options.cpp:1065 -msgid "Auto Equip Helms" -msgstr "ヘルム​の​自動​装備" - #: Source/options.cpp:1065 msgid "Helms will be automatically equipped on pickup or purchase if enabled." msgstr "有効​に​する​と、ヘルム​を​ピックアップ​や​購入​時​に​自動的​に​装備​さ​れ​ます。" -#: Source/options.cpp:1066 -msgid "Auto Equip Shields" -msgstr "シールド​の​自動​装備" - #: Source/options.cpp:1066 msgid "" "Shields will be automatically equipped on pickup or purchase if enabled." msgstr "有効​に​する​と、シールド​を​ピックアップ​や​購入​時​に​自動的​に​装備​さ​れ​ます。" -#: Source/options.cpp:1067 -msgid "Auto Equip Jewelry" -msgstr "ジュエリー​の​自動​装備" - #: Source/options.cpp:1067 msgid "" "Jewelry will be automatically equipped on pickup or purchase if enabled." msgstr "有効​に​する​と、ジュエリー​を​ピックアップ​や​購入​時​に​自動的​に​装備​さ​れ​ます。" -#: Source/options.cpp:1068 -msgid "Randomize Quests" -msgstr "クエスト​の​ランダム化" - #: Source/options.cpp:1068 msgid "Randomly selecting available quests for new games." msgstr "新規​ゲーム​で​クエスト​が​ランダム​に​選択​さ​れ​ます。" @@ -7015,33 +7093,22 @@ msgstr "" "モンスター​に​カーソル​を​合わせる​と、UI​の​説明​欄​に​モンスター​の​種類​が​表示​さ​れ​ま" "す。" -#: Source/options.cpp:1070 -msgid "Show Item Labels" -msgstr "アイテム​ラベル​を​表示" - #: Source/options.cpp:1070 msgid "Show labels for items on the ground when enabled." msgstr "有効​な​場合、アイテム​の​ラベル​を​表示​し​ます。" -#: Source/options.cpp:1071 -msgid "Auto Refill Belt" -msgstr "ベルト​の​自動​補充" - #: Source/options.cpp:1071 msgid "Refill belt from inventory when belt item is consumed." msgstr "ベルト​アイテム​が​消費​さ​れる​と、在庫​から​ベルト​を​補充​し​ます。" -#: Source/options.cpp:1072 -msgid "Disable Crippling Shrines" -msgstr "被害​を​与える​祭壇​を​無効化" - #: Source/options.cpp:1072 msgid "" -"When enabled Cauldrons, Fascinating Shrines, Goat Shrines, Ornate Shrines " -"and Sacred Shrines are not able to be clicked on and labeled as disabled." +"When enabled Cauldrons, Fascinating Shrines, Goat Shrines, Ornate Shrines, " +"Sacred Shrines and Murphy's Shrines are not able to be clicked on and " +"labeled as disabled." msgstr "" -"有効​に​する​と、地獄​の​大釜、魅了​の​祭壇、山羊​の​祭壇、華麗​なる​祭壇、厳粛​なる​祭壇​" -"が​クリック​でき​なく​なり、無効​と​表示​さ​れ​ます。" +"有効​に​する​と、地獄​の​大釜、魅了​の​祭壇、山羊​の​祭壇、華麗​なる​祭壇、神聖​なる​祭壇​" +"と​マーフィー​の​祭壇​が​クリック​でき​なく​なり、無効​と​表示​さ​れ​ます。" #: Source/options.cpp:1073 msgid "Quick Cast" @@ -7055,61 +7122,37 @@ msgstr "" "呪文​の​ホットキー​は、準備​さ​れ​た​呪文​を​切り替える​の​で​は​なく、瞬時​に​呪文​を​唱え​ま" "す。" -#: Source/options.cpp:1074 -msgid "Heal Potion Pickup" -msgstr "ヒーリング​・​ポーション​回収" - #: Source/options.cpp:1074 msgid "Number of Healing potions to pick up automatically." msgstr "自動的​に​拾う​ヒーリング​・​ポーション​の​数​です。" -#: Source/options.cpp:1075 -msgid "Full Heal Potion Pickup" -msgstr "フルヒーリング​・​ポーション​回収" - #: Source/options.cpp:1075 msgid "Number of Full Healing potions to pick up automatically." msgstr "自動的​に​拾う​フルヒーリング​・​ポーション​の​数​です。" -#: Source/options.cpp:1076 -msgid "Mana Potion Pickup" -msgstr "マナ​・​ポーション​を​回収" - #: Source/options.cpp:1076 msgid "Number of Mana potions to pick up automatically." msgstr "自動的​に​拾う​マナ​・​ポーション​の​数​です。" -#: Source/options.cpp:1077 -msgid "Full Mana Potion Pickup" -msgstr "フルマナ​・​ポーション​回収" - #: Source/options.cpp:1077 msgid "Number of Full Mana potions to pick up automatically." msgstr "自動的​に​拾う​フルマナ​・​ポーション​の​数​です。" -#: Source/options.cpp:1078 -msgid "Rejuvenation Potion Pickup" -msgstr "リジュベネーション​・​ポーション​回収" - #: Source/options.cpp:1078 msgid "Number of Rejuvenation potions to pick up automatically." -msgstr "自動的​に​拾う​リジュベネーション​・​ポーション​の​数​です。" - -#: Source/options.cpp:1079 -msgid "Full Rejuvenation Potion Pickup" -msgstr "フルリジュベネーション​・​ポーション​回収" +msgstr "自動的​に​拾う​回復​ポーション​の​数​です。" #: Source/options.cpp:1079 msgid "Number of Full Rejuvenation potions to pick up automatically." -msgstr "自動的​に​拾う​フルリジュベネーション​・​ポーション​の​数​です。" +msgstr "自動的​に​拾う​フル回復​ポーション​の​数​です。" #: Source/options.cpp:1080 msgid "Enable floating numbers" -msgstr "浮動小数点数を有効にする" +msgstr "浮動​小数​点数​を​有効​に​する" #: Source/options.cpp:1080 msgid "Enables floating numbers on gaining XP / dealing damage etc." -msgstr "経験値やダメージなどに浮動小数点数を使用する。" +msgstr "経験値​や​ダメージ​など​に​浮動​小数​点数​を​使用​する。" #: Source/options.cpp:1082 msgid "Off" @@ -7117,11 +7160,11 @@ msgstr "オフ" #: Source/options.cpp:1083 msgid "Random Angles" -msgstr "ランダムアングル" +msgstr "ランダム​アングル" #: Source/options.cpp:1084 msgid "Vertical Only" -msgstr "垂直のみ" +msgstr "垂直​のみ" #: Source/options.cpp:1135 msgid "Controller" @@ -7175,75 +7218,75 @@ msgstr "パッド​・​マッピング" msgid "Padmapping Settings" msgstr "パッド​・​マップ​設定" -#: Source/panels/charpanel.cpp:127 +#: Source/panels/charpanel.cpp:128 msgid "Level" msgstr "レベル" -#: Source/panels/charpanel.cpp:129 +#: Source/panels/charpanel.cpp:130 msgid "Experience" msgstr "経験値" -#: Source/panels/charpanel.cpp:134 +#: Source/panels/charpanel.cpp:135 msgid "Next level" msgstr "次​の​レベル" -#: Source/panels/charpanel.cpp:143 +#: Source/panels/charpanel.cpp:145 msgid "Base" msgstr "基本" -#: Source/panels/charpanel.cpp:144 +#: Source/panels/charpanel.cpp:146 msgid "Now" msgstr "現在" -#: Source/panels/charpanel.cpp:145 +#: Source/panels/charpanel.cpp:147 msgid "Strength" msgstr "STR" -#: Source/panels/charpanel.cpp:149 +#: Source/panels/charpanel.cpp:151 msgid "Magic" msgstr "MAG" -#: Source/panels/charpanel.cpp:153 +#: Source/panels/charpanel.cpp:155 msgid "Dexterity" msgstr "DEX" -#: Source/panels/charpanel.cpp:156 +#: Source/panels/charpanel.cpp:158 msgid "Vitality" msgstr "VIT" -#: Source/panels/charpanel.cpp:159 +#: Source/panels/charpanel.cpp:161 msgid "Points to distribute" msgstr "配布​ポイント" -#: Source/panels/charpanel.cpp:169 +#: Source/panels/charpanel.cpp:171 msgid "Armor class" msgstr "防御​力" -#: Source/panels/charpanel.cpp:171 +#: Source/panels/charpanel.cpp:173 msgid "To hit" msgstr "命中​率" -#: Source/panels/charpanel.cpp:173 +#: Source/panels/charpanel.cpp:175 msgid "Damage" msgstr "ダメージ" -#: Source/panels/charpanel.cpp:180 +#: Source/panels/charpanel.cpp:182 msgid "Life" msgstr "ライフ" -#: Source/panels/charpanel.cpp:184 +#: Source/panels/charpanel.cpp:186 msgid "Mana" msgstr "マナ" -#: Source/panels/charpanel.cpp:189 +#: Source/panels/charpanel.cpp:191 msgid "Resist magic" msgstr "耐​魔法" -#: Source/panels/charpanel.cpp:191 +#: Source/panels/charpanel.cpp:193 msgid "Resist fire" msgstr "耐火​炎" -#: Source/panels/charpanel.cpp:193 +#: Source/panels/charpanel.cpp:195 msgid "Resist lightning" msgstr "耐​電撃" @@ -7281,55 +7324,55 @@ msgstr "ボイス" msgid "mute" msgstr "ミュート" -#: Source/panels/spell_book.cpp:158 Source/panels/spell_list.cpp:150 -msgid "Skill" -msgstr "スキル" - -#: Source/panels/spell_book.cpp:162 -msgid "Staff ({:d} charge)" -msgid_plural "Staff ({:d} charges)" -msgstr[0] "スタッフ ({:d} チェージ)" - -#. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:167 -msgctxt "spellbook" -msgid "Level {:d}" -msgstr "Lvl {:d}" - -#: Source/panels/spell_book.cpp:169 +#: Source/panels/spell_book.cpp:116 msgid "Unusable" msgstr "使え​ない" #. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:177 +#: Source/panels/spell_book.cpp:119 +msgid "Dmg: 1/3 target hp" +msgstr "1/3 ターゲット​HP" + +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:126 msgid "Heals: {:d} - {:d}" msgstr "回復​量 {:d} - {:d}" #. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:179 +#: Source/panels/spell_book.cpp:128 msgid "Damage: {:d} - {:d}" msgstr "ダメージ {:d} - {:d}" +#: Source/panels/spell_book.cpp:183 Source/panels/spell_list.cpp:151 +msgid "Skill" +msgstr "スキル" + +#: Source/panels/spell_book.cpp:187 +msgid "Staff ({:d} charge)" +msgid_plural "Staff ({:d} charges)" +msgstr[0] "スタッフ ({:d} チェージ)" + #. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:183 -msgid "Dmg: 1/3 target hp" -msgstr "1/3 ターゲット​HP" +#: Source/panels/spell_book.cpp:192 +msgctxt "spellbook" +msgid "Level {:d}" +msgstr "Lvl {:d}" #. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:185 +#: Source/panels/spell_book.cpp:196 msgctxt "spellbook" msgid "Mana: {:d}" msgstr "マナ {:d}" -#: Source/panels/spell_list.cpp:157 +#: Source/panels/spell_list.cpp:158 msgid "Spell" msgstr "スペル" -#: Source/panels/spell_list.cpp:160 +#: Source/panels/spell_list.cpp:161 msgid "Damages undead only" msgstr "アンデッド​系​に​のみ​ダメージ" -#: Source/panels/spell_list.cpp:171 +#: Source/panels/spell_list.cpp:172 msgid "Scroll" msgstr "スクロール" @@ -7337,27 +7380,27 @@ msgstr "スクロール" msgid "Spell Hotkey {:s}" msgstr "スペルホットキー {:s}" -#: Source/pfile.cpp:723 +#: Source/pfile.cpp:743 msgid "Unable to open archive" msgstr "Unable to open archive" -#: Source/pfile.cpp:725 +#: Source/pfile.cpp:745 msgid "Unable to load character" msgstr "Unable to load character" -#: Source/plrmsg.cpp:84 Source/qol/chatlog.cpp:131 +#: Source/plrmsg.cpp:85 Source/qol/chatlog.cpp:130 msgid "{:s} (lvl {:d}): " msgstr "{:s} (​レベル {:d}): " -#: Source/qol/chatlog.cpp:160 +#: Source/qol/chatlog.cpp:159 msgid "Chat History (Messages: {:d})" msgstr "チャット​履歴 (​メッセージ: {:d}​)" -#: Source/qol/itemlabels.cpp:105 +#: Source/qol/itemlabels.cpp:107 msgid "{:s} gold" msgstr "{:s} ゴールド" -#: Source/qol/stash.cpp:640 +#: Source/qol/stash.cpp:639 msgid "How many gold pieces do you want to withdraw?" msgstr "ゴールド​を​どれ​だけ​引き出し​ます​か?" @@ -7373,110 +7416,110 @@ msgstr "経験値: {:s}" msgid "Maximum Level" msgstr "最大​レベル" -#: Source/qol/xpbar.cpp:140 +#: Source/qol/xpbar.cpp:141 msgid "Next Level: {:s}" msgstr "次​の​レベル: {:s}" -#: Source/qol/xpbar.cpp:141 +#: Source/qol/xpbar.cpp:142 msgid "{:s} to Level {:d}" msgstr "{:s}​レベル​{:d}​へ" #. TRANSLATORS: Quest Name Block -#: Source/quests.cpp:48 +#: Source/quests.cpp:52 msgid "The Magic Rock" msgstr "天界​の​石" -#: Source/quests.cpp:50 +#: Source/quests.cpp:54 msgid "Gharbad The Weak" msgstr "弱虫​ガーバッド" -#: Source/quests.cpp:51 +#: Source/quests.cpp:55 msgid "Zhar the Mad" msgstr "狂人​ザール" -#: Source/quests.cpp:52 +#: Source/quests.cpp:56 msgid "Lachdanan" msgstr "ラック​ダナン" -#: Source/quests.cpp:54 +#: Source/quests.cpp:58 msgid "The Butcher" msgstr "ブッチャー" -#: Source/quests.cpp:55 +#: Source/quests.cpp:59 msgid "Ogden's Sign" msgstr "オグデン​の​看板" -#: Source/quests.cpp:56 +#: Source/quests.cpp:60 msgid "Halls of the Blind" msgstr "盲目​の​広間" -#: Source/quests.cpp:57 +#: Source/quests.cpp:61 msgid "Valor" msgstr "ヴァロー" -#: Source/quests.cpp:59 +#: Source/quests.cpp:63 msgid "Warlord of Blood" msgstr "鮮血​の​将軍" -#: Source/quests.cpp:60 +#: Source/quests.cpp:64 msgid "The Curse of King Leoric" msgstr "レオリック​王​の​呪い" #. TRANSLATORS: Quest Map -#: Source/quests.cpp:62 Source/quests.cpp:98 +#: Source/quests.cpp:66 Source/quests.cpp:102 msgid "The Chamber of Bone" msgstr "納骨堂" -#: Source/quests.cpp:63 +#: Source/quests.cpp:67 msgid "Archbishop Lazarus" msgstr "大司教​ラザルス" -#: Source/quests.cpp:64 +#: Source/quests.cpp:68 msgid "Grave Matters" msgstr "墓​の​問題" -#: Source/quests.cpp:65 +#: Source/quests.cpp:69 msgid "Farmer's Orchard" msgstr "ファーマーズ​・​オーチャード" -#: Source/quests.cpp:66 +#: Source/quests.cpp:70 msgid "Little Girl" msgstr "小さな​女の子" -#: Source/quests.cpp:67 +#: Source/quests.cpp:71 msgid "Wandering Trader" msgstr "ワンダリング​・​トレーダー" -#: Source/quests.cpp:68 +#: Source/quests.cpp:72 msgid "The Defiler" msgstr "デファイラー" -#: Source/quests.cpp:69 +#: Source/quests.cpp:73 msgid "Na-Krul" msgstr "ナ​・​クルル" #. TRANSLATORS: Quest Name Block end -#: Source/quests.cpp:71 +#: Source/quests.cpp:75 msgid "The Jersey's Jersey" msgstr "ジャージ​の​ジャージ" #. TRANSLATORS: Quest Map -#: Source/quests.cpp:97 +#: Source/quests.cpp:101 msgid "King Leoric's Tomb" msgstr "レオリック​王​の​墓所" #. TRANSLATORS: Quest Map -#: Source/quests.cpp:100 +#: Source/quests.cpp:104 msgid "A Dark Passage" msgstr "暗き​通廊" #. TRANSLATORS: Quest Map -#: Source/quests.cpp:101 +#: Source/quests.cpp:105 msgid "Unholy Altar" msgstr "ラザルス​の​間" #. TRANSLATORS: Used for Quest Portals. {:s} is a Map Name -#: Source/quests.cpp:401 +#: Source/quests.cpp:405 msgid "To {:s}" msgstr "{:s}​へ" @@ -7759,7 +7802,7 @@ msgstr "ファーンハム" msgid "Adria" msgstr "エイドリア" -#: Source/stores.cpp:136 Source/stores.cpp:1315 +#: Source/stores.cpp:136 Source/stores.cpp:1258 msgid "Gillian" msgstr "ジリアン" @@ -7795,262 +7838,262 @@ msgstr "壊れ​ない​, " msgid "No required attributes" msgstr "必要​能力:​無し" -#: Source/stores.cpp:355 Source/stores.cpp:1069 Source/stores.cpp:1302 +#: Source/stores.cpp:381 Source/stores.cpp:1026 Source/stores.cpp:1245 msgid "Welcome to the" msgstr "ようこそ​!" -#: Source/stores.cpp:356 +#: Source/stores.cpp:382 msgid "Blacksmith's shop" msgstr "グリズウォルド​の​鍛冶屋​へ" -#: Source/stores.cpp:357 Source/stores.cpp:705 Source/stores.cpp:1071 -#: Source/stores.cpp:1128 Source/stores.cpp:1304 Source/stores.cpp:1316 -#: Source/stores.cpp:1329 +#: Source/stores.cpp:383 Source/stores.cpp:677 Source/stores.cpp:1028 +#: Source/stores.cpp:1071 Source/stores.cpp:1247 Source/stores.cpp:1259 +#: Source/stores.cpp:1272 msgid "Would you like to:" msgstr "何​を​する​?" -#: Source/stores.cpp:358 +#: Source/stores.cpp:384 msgid "Talk to Griswold" msgstr "グリズウォルド​と​話す" -#: Source/stores.cpp:359 +#: Source/stores.cpp:385 msgid "Buy basic items" msgstr "ベーシック​アイテム​を​買う" -#: Source/stores.cpp:360 +#: Source/stores.cpp:386 msgid "Buy premium items" msgstr "プレミアム​アイテム​を​買う" -#: Source/stores.cpp:361 Source/stores.cpp:708 +#: Source/stores.cpp:387 Source/stores.cpp:680 msgid "Sell items" msgstr "アイテム​を​売る" -#: Source/stores.cpp:362 +#: Source/stores.cpp:388 msgid "Repair items" msgstr "アイテム​の​修理" -#: Source/stores.cpp:363 +#: Source/stores.cpp:389 msgid "Leave the shop" msgstr "店​を​出る" -#: Source/stores.cpp:406 Source/stores.cpp:759 Source/stores.cpp:1105 +#: Source/stores.cpp:417 Source/stores.cpp:716 Source/stores.cpp:1048 msgid "I have these items for sale:" msgstr "さて、何​が​欲しい​ん​だ​い" -#: Source/stores.cpp:471 +#: Source/stores.cpp:466 msgid "I have these premium items for sale:" msgstr "どれ​も​珍しい​アイテム​だろう" -#: Source/stores.cpp:590 Source/stores.cpp:852 +#: Source/stores.cpp:562 Source/stores.cpp:809 msgid "You have nothing I want." msgstr "何​も​買える​物​は​ない​よう​だ​が。" -#: Source/stores.cpp:601 Source/stores.cpp:864 +#: Source/stores.cpp:573 Source/stores.cpp:821 msgid "Which item is for sale?" msgstr "何​を​売っ​て​くれる​ん​だ​い​?" -#: Source/stores.cpp:666 +#: Source/stores.cpp:638 msgid "You have nothing to repair." msgstr "何​も​壊れ​ちゃ​い​ない​ぞ。" -#: Source/stores.cpp:677 +#: Source/stores.cpp:649 msgid "Repair which item?" msgstr "どれ​を​直す​ん​だ​?" -#: Source/stores.cpp:704 +#: Source/stores.cpp:676 msgid "Witch's shack" msgstr "魔女​の​小屋" -#: Source/stores.cpp:706 +#: Source/stores.cpp:678 msgid "Talk to Adria" msgstr "エイドリア​と​話す" -#: Source/stores.cpp:707 Source/stores.cpp:1073 +#: Source/stores.cpp:679 Source/stores.cpp:1030 msgid "Buy items" msgstr "アイテム​を​買う" -#: Source/stores.cpp:709 +#: Source/stores.cpp:681 msgid "Recharge staves" msgstr "リチャージ​する" -#: Source/stores.cpp:710 +#: Source/stores.cpp:682 msgid "Leave the shack" msgstr "小屋​を​立ち去る" -#: Source/stores.cpp:926 +#: Source/stores.cpp:883 msgid "You have nothing to recharge." msgstr "リチャージ​できる​もの​は​無い​よ。" -#: Source/stores.cpp:937 +#: Source/stores.cpp:894 msgid "Recharge which item?" msgstr "どれ​に​リチャージ​する​ん​だ​い​?" -#: Source/stores.cpp:950 +#: Source/stores.cpp:907 msgid "You do not have enough gold" msgstr "所持​金​が​足り​ませ​ん" -#: Source/stores.cpp:958 +#: Source/stores.cpp:915 msgid "You do not have enough room in inventory" msgstr "置く​場所​が​あり​ませ​ん" -#: Source/stores.cpp:976 +#: Source/stores.cpp:933 msgid "Do we have a deal?" msgstr "よろしい​です​か​?" -#: Source/stores.cpp:979 +#: Source/stores.cpp:936 msgid "Are you sure you want to identify this item?" msgstr "これ​を​鑑定​する​の​だ​ね​?" -#: Source/stores.cpp:985 +#: Source/stores.cpp:942 msgid "Are you sure you want to buy this item?" msgstr "これ​を​買う​の​だ​ね​?" -#: Source/stores.cpp:988 +#: Source/stores.cpp:945 msgid "Are you sure you want to recharge this item?" msgstr "これ​に​リチャージ​する​ん​だ​ね​?" -#: Source/stores.cpp:992 +#: Source/stores.cpp:949 msgid "Are you sure you want to sell this item?" msgstr "これ​を​売っ​て​くれる​ん​だ​ね​?" -#: Source/stores.cpp:995 +#: Source/stores.cpp:952 msgid "Are you sure you want to repair this item?" msgstr "これ​を​直す​ん​だ​な​?" -#: Source/stores.cpp:1009 Source/towners.cpp:158 +#: Source/stores.cpp:966 Source/towners.cpp:152 msgid "Wirt the Peg-legged boy" msgstr "義足​の​少年​ワート" -#: Source/stores.cpp:1012 Source/stores.cpp:1019 +#: Source/stores.cpp:969 Source/stores.cpp:976 msgid "Talk to Wirt" msgstr "ワート​と​話す" -#: Source/stores.cpp:1013 +#: Source/stores.cpp:970 msgid "I have something for sale," msgstr "俺​は​いい​もの​を​持っ​て​いる。" -#: Source/stores.cpp:1014 +#: Source/stores.cpp:971 msgid "but it will cost 50 gold" msgstr "で​も、50​ゴールド​払わ​なきゃ" -#: Source/stores.cpp:1015 +#: Source/stores.cpp:972 msgid "just to take a look. " msgstr "見せ​て​やら​ない​ぜ。" -#: Source/stores.cpp:1016 +#: Source/stores.cpp:973 msgid "What have you got?" msgstr "払う​?" -#: Source/stores.cpp:1017 Source/stores.cpp:1020 Source/stores.cpp:1131 -#: Source/stores.cpp:1319 +#: Source/stores.cpp:974 Source/stores.cpp:977 Source/stores.cpp:1074 +#: Source/stores.cpp:1262 msgid "Say goodbye" msgstr "立ち去る" -#: Source/stores.cpp:1030 +#: Source/stores.cpp:987 msgid "I have this item for sale:" msgstr "さて、何​が​欲しい​ん​だ​い" -#: Source/stores.cpp:1047 +#: Source/stores.cpp:1004 msgid "Leave" msgstr "去る" -#: Source/stores.cpp:1070 +#: Source/stores.cpp:1027 msgid "Healer's home" msgstr "治療​師​の​家" -#: Source/stores.cpp:1072 +#: Source/stores.cpp:1029 msgid "Talk to Pepin" msgstr "ペピン​と​話す" -#: Source/stores.cpp:1074 +#: Source/stores.cpp:1031 msgid "Leave Healer's home" msgstr "治療​師​の​家​を​去る" -#: Source/stores.cpp:1127 +#: Source/stores.cpp:1070 msgid "The Town Elder" msgstr "町​の​語り部" -#: Source/stores.cpp:1129 +#: Source/stores.cpp:1072 msgid "Talk to Cain" msgstr "ケイン​と​話す" -#: Source/stores.cpp:1130 +#: Source/stores.cpp:1073 msgid "Identify an item" msgstr "鑑定​し​て​もらう" -#: Source/stores.cpp:1223 +#: Source/stores.cpp:1166 msgid "You have nothing to identify." msgstr "何​も​鑑定​する​もの​は​無い​よう​だ​が。" -#: Source/stores.cpp:1234 +#: Source/stores.cpp:1177 msgid "Identify which item?" msgstr "何​を​鑑定​する​の​か​ね​?" -#: Source/stores.cpp:1249 +#: Source/stores.cpp:1192 msgid "This item is:" msgstr "何​を​する​?" -#: Source/stores.cpp:1252 +#: Source/stores.cpp:1195 msgid "Done" msgstr "終わる" -#: Source/stores.cpp:1261 +#: Source/stores.cpp:1204 msgid "Talk to {:s}" msgstr "{:s}​と​話す" -#: Source/stores.cpp:1264 +#: Source/stores.cpp:1207 msgid "Talking to {:s}" msgstr "{:s}​と​話し​て​いる" -#: Source/stores.cpp:1265 +#: Source/stores.cpp:1208 msgid "is not available" msgstr "使用​不能" -#: Source/stores.cpp:1266 +#: Source/stores.cpp:1209 msgid "in the shareware" msgstr "シェアウェア​で" -#: Source/stores.cpp:1267 +#: Source/stores.cpp:1210 msgid "version" msgstr "バージョン" -#: Source/stores.cpp:1294 +#: Source/stores.cpp:1237 msgid "Gossip" msgstr "噂話" -#: Source/stores.cpp:1303 +#: Source/stores.cpp:1246 msgid "Rising Sun" msgstr "日の出​亭" -#: Source/stores.cpp:1305 +#: Source/stores.cpp:1248 msgid "Talk to Ogden" msgstr "オグデン​と​話す" -#: Source/stores.cpp:1306 +#: Source/stores.cpp:1249 msgid "Leave the tavern" msgstr "宿屋​を​去る" -#: Source/stores.cpp:1317 +#: Source/stores.cpp:1260 msgid "Talk to Gillian" msgstr "ジリアン​と​話す" -#: Source/stores.cpp:1318 +#: Source/stores.cpp:1261 msgid "Access Storage" msgstr "スレレージ​に​アクセス" -#: Source/stores.cpp:1328 Source/towners.cpp:216 +#: Source/stores.cpp:1271 Source/towners.cpp:207 msgid "Farnham the Drunk" msgstr "よっぱらい​の​ファーンハム" -#: Source/stores.cpp:1330 +#: Source/stores.cpp:1273 msgid "Talk to Farnham" msgstr "ファーンハム​と​話す" -#: Source/stores.cpp:1331 +#: Source/stores.cpp:1274 msgid "Say Goodbye" msgstr "立ち去る" -#: Source/stores.cpp:2464 +#: Source/stores.cpp:2407 msgid "Your gold: {:s}" msgstr "所持​金: {:s}" @@ -10176,10 +10219,9 @@ msgid "" "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 @@ -11609,59 +11651,62 @@ msgstr "Praedictum Otium." msgid "Efficio Obitus Ut Inimicus." msgstr "Efficio Obitus Ut Inimicus." -#: Source/towners.cpp:83 +#: Source/towners.cpp:82 msgid "Griswold the Blacksmith" msgstr "鍛冶屋​の​グリズウォルド" -#: Source/towners.cpp:106 +#: Source/towners.cpp:104 msgid "Ogden the Tavern owner" msgstr "宿屋​の​オグデン" -#: Source/towners.cpp:116 +#: Source/towners.cpp:113 msgid "Wounded Townsman" msgstr "傷つい​た​住人" -#: Source/towners.cpp:138 +#: Source/towners.cpp:134 msgid "Adria the Witch" msgstr "魔女​の​エイドリア" -#: Source/towners.cpp:148 +#: Source/towners.cpp:143 msgid "Gillian the Barmaid" msgstr "ウェイトレス​の​ジリアン" -#: Source/towners.cpp:181 +#: Source/towners.cpp:174 msgid "Pepin the Healer" msgstr "治療​師​の​ペピン" -#: Source/towners.cpp:199 +#: Source/towners.cpp:191 msgid "Cain the Elder" msgstr "語り部​の​ケイン" -#: Source/towners.cpp:228 +#: Source/towners.cpp:218 msgid "Cow" msgstr "牛" -#: Source/towners.cpp:252 +#: Source/towners.cpp:241 msgid "Lester the farmer" msgstr "農家​の​レスター" -#: Source/towners.cpp:265 +#: Source/towners.cpp:253 msgid "Complete Nut" msgstr "コンプリート​・​ナット" -#: Source/towners.cpp:274 +#: Source/towners.cpp:261 msgid "Celia" msgstr "セリア" -#: Source/towners.cpp:287 +#: Source/towners.cpp:274 msgid "Slain Townsman" msgstr "住人​の​遺体" #. TRANSLATORS: Thousands separator -#: Source/utils/format_int.cpp:26 +#: Source/utils/format_int.cpp:27 msgid "," msgstr "," +#~ msgid "No automap available in town" +#~ msgstr "町中​で​は​オート​マップ​を​使え​ませ​ん" + #~ msgid "({command})" #~ msgstr "(​{command}​)" diff --git a/Translations/pl.po b/Translations/pl.po index fc99e65a591..42cc085597a 100644 --- a/Translations/pl.po +++ b/Translations/pl.po @@ -3,11 +3,12 @@ # @qndel, 2021. # @zutmkr, 2021. # @BuildTools, 2021. +# @nelchael, 2023 # msgid "" msgstr "" "Project-Id-Version: DevilutionX\n" -"POT-Creation-Date: 2022-04-19 10:47+0200\n" +"POT-Creation-Date: 2023-11-16 22:50+0100\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: \n" @@ -17,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 " "|| n%100>14) ? 1 : 2);\n" -"X-Generator: Poedit 3.0.1\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" @@ -104,7 +105,7 @@ msgstr "Młodszy Producent" msgid "Diablo Strike Team" msgstr "Diablo Strike Team" -#: Source/DiabloUI/credits_lines.cpp:77 Source/gamemenu.cpp:67 +#: Source/DiabloUI/credits_lines.cpp:77 Source/gamemenu.cpp:74 msgid "Music" msgstr "Muzyka" @@ -317,16 +318,157 @@ msgstr "Krąg Tysiąca" msgid "\tNo souls were sold in the making of this game." msgstr "\tŻadne dusze nie zostały zaprzedane podczas tworzenia tej gry." -#: 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 +#: Source/DiabloUI/dialogs.cpp:84 Source/DiabloUI/dialogs.cpp:96 +#: Source/DiabloUI/hero/selhero.cpp:177 Source/DiabloUI/hero/selhero.cpp:203 +#: Source/DiabloUI/hero/selhero.cpp:288 Source/DiabloUI/hero/selhero.cpp:528 +#: Source/DiabloUI/multi/selconn.cpp:82 Source/DiabloUI/multi/selgame.cpp:173 +#: Source/DiabloUI/multi/selgame.cpp:337 Source/DiabloUI/multi/selgame.cpp:363 +#: Source/DiabloUI/multi/selgame.cpp:505 Source/DiabloUI/multi/selgame.cpp:582 +#: Source/DiabloUI/selok.cpp:71 msgid "OK" msgstr "OK" +#: Source/DiabloUI/hero/selhero.cpp:155 +msgid "Choose Class" +msgstr "Wybierz Klasę" + +#. TRANSLATORS: Player Block start +#. HeroClass::Warrior +#: Source/DiabloUI/hero/selhero.cpp:159 Source/playerdat.cpp:254 +msgid "Warrior" +msgstr "Wojownik" + +#: Source/DiabloUI/hero/selhero.cpp:160 Source/playerdat.cpp:255 +msgid "Rogue" +msgstr "Łotrzyca" + +#: Source/DiabloUI/hero/selhero.cpp:161 Source/playerdat.cpp:256 +msgid "Sorcerer" +msgstr "Czarodziej" + +#: Source/DiabloUI/hero/selhero.cpp:163 Source/playerdat.cpp:257 +msgid "Monk" +msgstr "Mnich" + +#: Source/DiabloUI/hero/selhero.cpp:165 Source/playerdat.cpp:258 +msgid "Bard" +msgstr "Barda" + +#. TRANSLATORS: Player Block end +#. HeroClass::Barbarian +#: Source/DiabloUI/hero/selhero.cpp:168 Source/playerdat.cpp:260 +msgid "Barbarian" +msgstr "Barbarzyńca" + +#: Source/DiabloUI/hero/selhero.cpp:180 Source/DiabloUI/hero/selhero.cpp:206 +#: Source/DiabloUI/hero/selhero.cpp:291 Source/DiabloUI/hero/selhero.cpp:536 +#: Source/DiabloUI/multi/selconn.cpp:85 Source/DiabloUI/progress.cpp:44 +msgid "Cancel" +msgstr "Anuluj" + +#: Source/DiabloUI/hero/selhero.cpp:186 Source/DiabloUI/hero/selhero.cpp:276 +msgid "New Multi Player Hero" +msgstr "Nowa Postać - Gra Sieciowa" + +#: Source/DiabloUI/hero/selhero.cpp:186 Source/DiabloUI/hero/selhero.cpp:276 +msgid "New Single Player Hero" +msgstr "Nowa Postać - Gra Jednoosobowa" + +#: Source/DiabloUI/hero/selhero.cpp:195 +msgid "Save File Exists" +msgstr "Znaleziono Plik Zapisu" + +#: Source/DiabloUI/hero/selhero.cpp:198 Source/gamemenu.cpp:45 +msgid "Load Game" +msgstr "Wczytaj Grę" + +#: Source/DiabloUI/hero/selhero.cpp:199 Source/gamemenu.cpp:44 +#: Source/gamemenu.cpp:55 Source/multi.cpp:792 +msgid "New Game" +msgstr "Nowa Gra" + +#: Source/DiabloUI/hero/selhero.cpp:209 Source/DiabloUI/hero/selhero.cpp:542 +msgid "Single Player Characters" +msgstr "Postacie - Gra Jednoosobowa" + +#: Source/DiabloUI/hero/selhero.cpp:268 +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 "" +"Łotrzyca i Czarodziej są dostępni tylko w pełnej wersji gry Diablo. Odwiedź " +"https://www.gog.com/game/diablo by ją zakupić." + +#: Source/DiabloUI/hero/selhero.cpp:282 Source/DiabloUI/hero/selhero.cpp:285 +msgid "Enter Name" +msgstr "Wpisz Imię" + +#: Source/DiabloUI/hero/selhero.cpp:314 +msgid "" +"Invalid name. A name cannot contain spaces, reserved characters, or reserved " +"words.\n" +msgstr "" +"Niepoprawne imię. Nazwa nie może zawierać spacji, znaków specjalnych i " +"niektórych słów.\n" + +#. TRANSLATORS: Error Message +#: Source/DiabloUI/hero/selhero.cpp:321 +msgid "Unable to create character." +msgstr "Nie udało się stworzyć postaci." + +#: Source/DiabloUI/hero/selhero.cpp:487 +msgid "Level:" +msgstr "Poziom:" + +#: Source/DiabloUI/hero/selhero.cpp:491 +msgid "Strength:" +msgstr "Siła:" + +#: Source/DiabloUI/hero/selhero.cpp:491 +msgid "Magic:" +msgstr "Magia:" + +#: Source/DiabloUI/hero/selhero.cpp:491 +msgid "Dexterity:" +msgstr "Zręczność:" + +#: Source/DiabloUI/hero/selhero.cpp:491 +msgid "Vitality:" +msgstr "Żywotność:" + +#: Source/DiabloUI/hero/selhero.cpp:493 +msgid "Savegame:" +msgstr "Zapis Gry:" + +#: Source/DiabloUI/hero/selhero.cpp:512 +msgid "Select Hero" +msgstr "Wybierz Postać" + +#: Source/DiabloUI/hero/selhero.cpp:520 +msgid "New Hero" +msgstr "Nowa Postać" + +#: Source/DiabloUI/hero/selhero.cpp:531 +msgid "Delete" +msgstr "Usuń" + +#: Source/DiabloUI/hero/selhero.cpp:540 +msgid "Multi Player Characters" +msgstr "Postacie - Gra Sieciowa" + +#: Source/DiabloUI/hero/selhero.cpp:591 +msgid "Delete Multi Player Hero" +msgstr "Usuń Postać - Gra Sieciowa" + +#: Source/DiabloUI/hero/selhero.cpp:593 +msgid "Delete Single Player Hero" +msgstr "Usuń Postać - Gra Jednoosobowa" + +#: Source/DiabloUI/hero/selhero.cpp:595 +#, c++-format +msgid "Are you sure you want to delete the character \"{:s}\"?" +msgstr "Czy na pewno chcesz usunąć postać \"{:s}\"?" + #: Source/DiabloUI/mainmenu.cpp:38 msgid "Single Player" msgstr "Jeden Gracz" @@ -335,7 +477,7 @@ msgstr "Jeden Gracz" msgid "Multi Player" msgstr "Wielu Graczy" -#: Source/DiabloUI/mainmenu.cpp:40 Source/DiabloUI/settingsmenu.cpp:260 +#: Source/DiabloUI/mainmenu.cpp:40 Source/DiabloUI/settingsmenu.cpp:366 msgid "Settings" msgstr "Ustawienia" @@ -355,138 +497,136 @@ msgstr "Wyjdź z Hellfire" msgid "Exit Diablo" msgstr "Wyjdź z Diablo" -#: Source/DiabloUI/mainmenu.cpp:60 +#: Source/DiabloUI/mainmenu.cpp:62 msgid "Shareware" msgstr "Demo" -#: 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 "Anuluj" - -#: Source/DiabloUI/selconn.cpp:14 +#: Source/DiabloUI/multi/selconn.cpp:14 msgid "Client-Server (TCP)" msgstr "Klient-Serwer (TCP)" -#: Source/DiabloUI/selconn.cpp:15 -#, fuzzy -msgid "Offline" -msgstr "Loopback" +# NOTES(nelchael): Pojawia się w menu "wybierz połączenie", stąd "lokalne" połączenie. +#: Source/DiabloUI/multi/selconn.cpp:15 +msgid "Offline" +msgstr "Lokalne" -#: Source/DiabloUI/selconn.cpp:54 Source/DiabloUI/selgame.cpp:604 -#: Source/DiabloUI/selgame.cpp:625 +#: Source/DiabloUI/multi/selconn.cpp:56 Source/DiabloUI/multi/selgame.cpp:649 +#: Source/DiabloUI/multi/selgame.cpp:673 msgid "Multi Player Game" msgstr "Gra Wieloosobowa" -#: Source/DiabloUI/selconn.cpp:60 +#: Source/DiabloUI/multi/selconn.cpp:62 msgid "Requirements:" msgstr "Wymagania:" -#: Source/DiabloUI/selconn.cpp:66 +#: Source/DiabloUI/multi/selconn.cpp:68 msgid "no gateway needed" -msgstr "Bez bramki" +msgstr "bez bramki" -#: Source/DiabloUI/selconn.cpp:72 +#: Source/DiabloUI/multi/selconn.cpp:74 msgid "Select Connection" msgstr "Wybierz Połączenie" -#: Source/DiabloUI/selconn.cpp:75 +#: Source/DiabloUI/multi/selconn.cpp:77 msgid "Change Gateway" msgstr "Zmień Bramkę" -#: Source/DiabloUI/selconn.cpp:108 +#: Source/DiabloUI/multi/selconn.cpp:110 msgid "All computers must be connected to a TCP-compatible network." msgstr "Wszystkie komputery muszą być połączone z siecią kompatybilną z TCP." -#: Source/DiabloUI/selconn.cpp:112 +#: Source/DiabloUI/multi/selconn.cpp:114 msgid "All computers must be connected to the internet." msgstr "Wszystkie komputery muszą być połączone z internetem." -#: Source/DiabloUI/selconn.cpp:116 +#: Source/DiabloUI/multi/selconn.cpp:118 msgid "Play by yourself with no network exposure." msgstr "Graj samemu bez łączenia się z internetem." -#: Source/DiabloUI/selconn.cpp:121 +#: Source/DiabloUI/multi/selconn.cpp:123 +#, c++-format msgid "Players Supported: {:d}" msgstr "Liczba graczy: {:d}" -#: Source/DiabloUI/selgame.cpp:83 Source/options.cpp:548 Source/options.cpp:587 -#: Source/quests.cpp:49 +#: Source/DiabloUI/multi/selgame.cpp:86 Source/options.cpp:572 +#: Source/options.cpp:611 Source/quests.cpp:57 msgid "Diablo" msgstr "Diablo" -#: Source/DiabloUI/selgame.cpp:86 +#: Source/DiabloUI/multi/selgame.cpp:89 msgid "Diablo Shareware" msgstr "Demo Diablo" -#: Source/DiabloUI/selgame.cpp:89 Source/options.cpp:550 Source/options.cpp:601 +#: Source/DiabloUI/multi/selgame.cpp:92 Source/options.cpp:574 +#: Source/options.cpp:625 msgid "Hellfire" msgstr "Hellfire" -#: Source/DiabloUI/selgame.cpp:92 +#: Source/DiabloUI/multi/selgame.cpp:95 msgid "Hellfire Shareware" msgstr "Demo Hellfire" -#: Source/DiabloUI/selgame.cpp:95 +#: Source/DiabloUI/multi/selgame.cpp:98 msgid "The host is running a different game than you." msgstr "Host używa innej gry niż ty." -#: Source/DiabloUI/selgame.cpp:97 +#: Source/DiabloUI/multi/selgame.cpp:100 +#, c++-format msgid "The host is running a different game mode ({:s}) than you." msgstr "Host używa innej gry ({:s}) niż ty." #. TRANSLATORS: Error message when somebody tries to join a game running another version. -#: Source/DiabloUI/selgame.cpp:99 +#: Source/DiabloUI/multi/selgame.cpp:102 +#, c++-format msgid "Your version {:s} does not match the host {:d}.{:d}.{:d}." msgstr "Twoja wersja {:s} nie pasuje do wersji hosta {:d}.{:d}.{:d}." -#: Source/DiabloUI/selgame.cpp:134 Source/DiabloUI/selgame.cpp:533 +#: Source/DiabloUI/multi/selgame.cpp:139 Source/DiabloUI/multi/selgame.cpp:568 msgid "Description:" msgstr "Opis:" -#: Source/DiabloUI/selgame.cpp:140 +#: Source/DiabloUI/multi/selgame.cpp:145 msgid "Select Action" msgstr "Czynność" -#: Source/DiabloUI/selgame.cpp:143 Source/DiabloUI/selgame.cpp:294 -#: Source/DiabloUI/selgame.cpp:453 +#: Source/DiabloUI/multi/selgame.cpp:148 Source/DiabloUI/multi/selgame.cpp:325 +#: Source/DiabloUI/multi/selgame.cpp:486 msgid "Create Game" msgstr "Stwórz grę" -#: Source/DiabloUI/selgame.cpp:145 +#: Source/DiabloUI/multi/selgame.cpp:150 msgid "Create Public Game" msgstr "Stwórz publiczną grę" -#: Source/DiabloUI/selgame.cpp:146 +#: Source/DiabloUI/multi/selgame.cpp:151 msgid "Join Game" msgstr "Dołącz do gry" -#: Source/DiabloUI/selgame.cpp:150 +#: Source/DiabloUI/multi/selgame.cpp:155 msgid "Public Games" msgstr "Publiczne Gry" -#: Source/DiabloUI/selgame.cpp:155 Source/error.cpp:62 +#: Source/DiabloUI/multi/selgame.cpp:160 Source/diablo_msg.cpp:71 msgid "Loading..." msgstr "Wczytywanie..." #. TRANSLATORS: type of dungeon (i.e. Cathedral, Caves) -#: Source/DiabloUI/selgame.cpp:157 Source/discord/discord.cpp:71 -#: Source/options.cpp:569 Source/panels/charpanel.cpp:137 +#: Source/DiabloUI/multi/selgame.cpp:162 Source/discord/discord.cpp:78 +#: Source/options.cpp:593 Source/panels/charpanel.cpp:138 msgid "None" msgstr "Brak" -#: 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 +#: Source/DiabloUI/multi/selgame.cpp:176 Source/DiabloUI/multi/selgame.cpp:340 +#: Source/DiabloUI/multi/selgame.cpp:366 Source/DiabloUI/multi/selgame.cpp:508 +#: Source/DiabloUI/multi/selgame.cpp:585 msgid "CANCEL" msgstr "ANULUJ" -#: Source/DiabloUI/selgame.cpp:187 +#: Source/DiabloUI/multi/selgame.cpp:216 msgid "Create a new game with a difficulty setting of your choice." msgstr "Stwórz nową grę z wybranym poziomem trudności." -#: Source/DiabloUI/selgame.cpp:190 +#: Source/DiabloUI/multi/selgame.cpp:219 msgid "" "Create a new public game that anyone can join with a difficulty setting of " "your choice." @@ -494,79 +634,81 @@ msgstr "" "Stwórz nową publiczną grę z wybranym poziomem trudności do której każdy może " "dołączyć." -#: Source/DiabloUI/selgame.cpp:194 +#: Source/DiabloUI/multi/selgame.cpp:223 msgid "Enter Game ID to join a game already in progress." msgstr "Wprowadź ID gry, aby dołączyć do trwającej już gry." -#: Source/DiabloUI/selgame.cpp:196 +#: Source/DiabloUI/multi/selgame.cpp:225 msgid "Enter an IP or a hostname to join a game already in progress." msgstr "Wprowadź adres IP lub nazwę hosta, aby dołączyć do trwającej już gry." -#: Source/DiabloUI/selgame.cpp:201 +#: Source/DiabloUI/multi/selgame.cpp:230 msgid "Join the public game already in progress." msgstr "Dołącz do trwającej już gry publicznej." -#: 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 +#: Source/DiabloUI/multi/selgame.cpp:236 Source/DiabloUI/multi/selgame.cpp:330 +#: Source/DiabloUI/multi/selgame.cpp:391 Source/DiabloUI/multi/selgame.cpp:497 +#: Source/DiabloUI/multi/selgame.cpp:517 Source/automap.cpp:1459 +#: Source/discord/discord.cpp:106 msgid "Normal" msgstr "Normalny" -#: Source/DiabloUI/selgame.cpp:210 Source/DiabloUI/selgame.cpp:300 -#: Source/DiabloUI/selgame.cpp:364 Source/automap.cpp:525 -#: Source/discord/discord.cpp:100 +#: Source/DiabloUI/multi/selgame.cpp:239 Source/DiabloUI/multi/selgame.cpp:331 +#: Source/DiabloUI/multi/selgame.cpp:395 Source/automap.cpp:1462 +#: Source/discord/discord.cpp:106 msgid "Nightmare" msgstr "Koszmar" -#: 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 +#: Source/DiabloUI/multi/selgame.cpp:242 Source/DiabloUI/multi/selgame.cpp:332 +#: Source/DiabloUI/multi/selgame.cpp:399 Source/automap.cpp:1465 +#: Source/discord/discord.cpp:73 Source/discord/discord.cpp:106 msgid "Hell" msgstr "Piekło" #. TRANSLATORS: {:s} means: Game Difficulty. -#: Source/DiabloUI/selgame.cpp:216 Source/automap.cpp:532 +#: Source/DiabloUI/multi/selgame.cpp:245 Source/automap.cpp:1469 +#, c++-format msgid "Difficulty: {:s}" msgstr "Poziom Trudności: {:s}" -#: Source/DiabloUI/selgame.cpp:220 Source/gamemenu.cpp:161 +#: Source/DiabloUI/multi/selgame.cpp:249 Source/gamemenu.cpp:170 msgid "Speed: Normal" msgstr "Tempo: Normalne" -#: Source/DiabloUI/selgame.cpp:223 Source/gamemenu.cpp:159 +#: Source/DiabloUI/multi/selgame.cpp:252 Source/gamemenu.cpp:168 msgid "Speed: Fast" msgstr "Tempo: Szybkie" -#: Source/DiabloUI/selgame.cpp:226 Source/gamemenu.cpp:157 +#: Source/DiabloUI/multi/selgame.cpp:255 Source/gamemenu.cpp:166 msgid "Speed: Faster" msgstr "Tempo: Szybsze" -#: Source/DiabloUI/selgame.cpp:229 Source/gamemenu.cpp:155 +#: Source/DiabloUI/multi/selgame.cpp:258 Source/gamemenu.cpp:164 msgid "Speed: Fastest" msgstr "Tempo: Najszybsze" -#: Source/DiabloUI/selgame.cpp:237 +#: Source/DiabloUI/multi/selgame.cpp:266 msgid "Players: " msgstr "Gracze: " -#: Source/DiabloUI/selgame.cpp:297 +#: Source/DiabloUI/multi/selgame.cpp:328 msgid "Select Difficulty" msgstr "Poziom Trudności" -#: Source/DiabloUI/selgame.cpp:315 +#: Source/DiabloUI/multi/selgame.cpp:346 +#, c++-format msgid "Join {:s} Games" msgstr "Dołącz do gry przez {:s}" -#: Source/DiabloUI/selgame.cpp:320 +#: Source/DiabloUI/multi/selgame.cpp:351 msgid "Enter Game ID" msgstr "Wpisz ID gry" -#: Source/DiabloUI/selgame.cpp:322 +#: Source/DiabloUI/multi/selgame.cpp:353 msgid "Enter address" msgstr "Wpisz adres" -#: Source/DiabloUI/selgame.cpp:361 +#: Source/DiabloUI/multi/selgame.cpp:392 msgid "" "Normal Difficulty\n" "This is where a starting character should begin the quest to defeat Diablo." @@ -574,7 +716,7 @@ msgstr "" "Normalny\n" "Poziom dla postaci, która pierwszy raz chce się zmierzyć z Diablo." -#: Source/DiabloUI/selgame.cpp:365 +#: Source/DiabloUI/multi/selgame.cpp:396 msgid "" "Nightmare Difficulty\n" "The denizens of the Labyrinth have been bolstered and will prove to be a " @@ -584,7 +726,7 @@ msgstr "" "Potwory zamieszkujące Labirynt stają się mocniejsze i stanowią większe " "wyzwanie. Poziom trudności zalecany dla doświadczonej postaci." -#: Source/DiabloUI/selgame.cpp:369 +#: Source/DiabloUI/multi/selgame.cpp:400 msgid "" "Hell Difficulty\n" "The most powerful of the underworld's creatures lurk at the gateway into " @@ -594,7 +736,7 @@ msgstr "" "Najpotężniejsze kreautry podziemi strzegą piekielnych bram. Tylko " "najbardziej doświadczone postacie mogą zapuścić się w głąb tego świata." -#: Source/DiabloUI/selgame.cpp:384 +#: Source/DiabloUI/multi/selgame.cpp:415 msgid "" "Your character must reach level 20 before you can enter a multiplayer game " "of Nightmare difficulty." @@ -602,7 +744,7 @@ msgstr "" "Twoja postać musi osiągnąć co najmniej 20 poziom żeby grać na poziomie " "trudności Koszmar w trybie sieciowym." -#: Source/DiabloUI/selgame.cpp:386 +#: Source/DiabloUI/multi/selgame.cpp:417 msgid "" "Your character must reach level 30 before you can enter a multiplayer game " "of Hell difficulty." @@ -610,23 +752,23 @@ msgstr "" "Twoja postać musi osiągnąć co najmniej 30 poziom żeby grać na poziomie " "trudności Piekło w trybie sieciowym." -#: Source/DiabloUI/selgame.cpp:462 +#: Source/DiabloUI/multi/selgame.cpp:495 msgid "Select Game Speed" msgstr "Wybierz Tempo Gry" -#: Source/DiabloUI/selgame.cpp:465 Source/DiabloUI/selgame.cpp:488 +#: Source/DiabloUI/multi/selgame.cpp:498 Source/DiabloUI/multi/selgame.cpp:521 msgid "Fast" msgstr "Szybkie" -#: Source/DiabloUI/selgame.cpp:466 Source/DiabloUI/selgame.cpp:492 +#: Source/DiabloUI/multi/selgame.cpp:499 Source/DiabloUI/multi/selgame.cpp:525 msgid "Faster" msgstr "Szybsze" -#: Source/DiabloUI/selgame.cpp:467 Source/DiabloUI/selgame.cpp:496 +#: Source/DiabloUI/multi/selgame.cpp:500 Source/DiabloUI/multi/selgame.cpp:529 msgid "Fastest" msgstr "Najszybsze" -#: Source/DiabloUI/selgame.cpp:485 +#: Source/DiabloUI/multi/selgame.cpp:518 msgid "" "Normal Speed\n" "This is where a starting character should begin the quest to defeat Diablo." @@ -634,7 +776,7 @@ msgstr "" "Normalne Tempo Gry\n" "Dla postaci, która pierwszy raz chce się zmierzyć z Diablo." -#: Source/DiabloUI/selgame.cpp:489 +#: Source/DiabloUI/multi/selgame.cpp:522 msgid "" "Fast Speed\n" "The denizens of the Labyrinth have been hastened and will prove to be a " @@ -644,7 +786,7 @@ msgstr "" "Potwory zamieszkujące Labirynt stają się szybsze. Są większym wyzwaniem. " "Prędkość zalecana dla doświadczonej postaci." -#: Source/DiabloUI/selgame.cpp:493 +#: Source/DiabloUI/multi/selgame.cpp:526 msgid "" "Faster Speed\n" "Most monsters of the dungeon will seek you out quicker than ever before. " @@ -654,7 +796,7 @@ msgstr "" "Większość potworów będzie usiłować dorwać cię szybciej niż zwykle. Tylko " "doświadczony czempion powinien spróbować swych sił na tej prędkości." -#: Source/DiabloUI/selgame.cpp:497 +#: Source/DiabloUI/multi/selgame.cpp:530 msgid "" "Fastest Speed\n" "The minions of the underworld will rush to attack without hesitation. Only a " @@ -664,181 +806,63 @@ msgstr "" "Sługi piekielne będą nieustannie się za tobą uganiać. Tylko dla prawdziwych " "demonów prędkości." -#: Source/DiabloUI/selgame.cpp:539 Source/DiabloUI/selgame.cpp:544 +#: Source/DiabloUI/multi/selgame.cpp:574 Source/DiabloUI/multi/selgame.cpp:579 msgid "Enter Password" msgstr "Wpisz Hasło" -#: Source/DiabloUI/selhero.cpp:132 -msgid "Choose Class" -msgstr "Wybierz Klasę" - -#: Source/DiabloUI/selhero.cpp:136 Source/panels/charpanel.cpp:23 -msgid "Warrior" -msgstr "Wojownik" - -#: Source/DiabloUI/selhero.cpp:137 Source/panels/charpanel.cpp:24 -msgid "Rogue" -msgstr "Łotrzyca" - -#: Source/DiabloUI/selhero.cpp:138 Source/panels/charpanel.cpp:25 -msgid "Sorcerer" -msgstr "Czarodziej" - -#: Source/DiabloUI/selhero.cpp:140 Source/panels/charpanel.cpp:26 -msgid "Monk" -msgstr "Mnich" - -#: Source/DiabloUI/selhero.cpp:143 Source/panels/charpanel.cpp:27 -msgid "Bard" -msgstr "Barda" - -#: Source/DiabloUI/selhero.cpp:146 Source/panels/charpanel.cpp:28 -msgid "Barbarian" -msgstr "Barbarzyńca" - -#: Source/DiabloUI/selhero.cpp:163 Source/DiabloUI/selhero.cpp:237 -msgid "New Multi Player Hero" -msgstr "Nowa Postać - Gra Sieciowa" - -#: Source/DiabloUI/selhero.cpp:163 Source/DiabloUI/selhero.cpp:237 -msgid "New Single Player Hero" -msgstr "Nowa Postać - Gra Jednoosobowa" - -#: Source/DiabloUI/selhero.cpp:171 -msgid "Save File Exists" -msgstr "Znaleziono Plik Zapisu" - -#: Source/DiabloUI/selhero.cpp:174 Source/gamemenu.cpp:38 -msgid "Load Game" -msgstr "Wczytaj Grę" - -#: Source/DiabloUI/selhero.cpp:175 Source/gamemenu.cpp:37 -#: Source/gamemenu.cpp:48 Source/multi.cpp:737 -msgid "New Game" -msgstr "Nowa Gra" - -#: Source/DiabloUI/selhero.cpp:185 Source/DiabloUI/selhero.cpp:507 -msgid "Single Player Characters" -msgstr "Postacie - Gra Jednoosobowa" - -#: 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 "" -"Łotrzyca i Czarodziej są dostępni tylko w pełnej wersji gry Diablo. Odwiedź " -"https://www.gog.com/game/diablo by ją zakupić." - -#: Source/DiabloUI/selhero.cpp:243 Source/DiabloUI/selhero.cpp:246 -msgid "Enter Name" -msgstr "Wpisz Imię" - -#: Source/DiabloUI/selhero.cpp:275 -msgid "" -"Invalid name. A name cannot contain spaces, reserved characters, or reserved " -"words.\n" -msgstr "" -"Niepoprawne imię. Nazwa nie może zawierać spacji, znaków specjalnych i " -"niektórych słów.\n" - -#. TRANSLATORS: Error Message -#: Source/DiabloUI/selhero.cpp:282 -msgid "Unable to create character." -msgstr "Nie udało się stworzyć postaci." - -#: Source/DiabloUI/selhero.cpp:436 Source/DiabloUI/selhero.cpp:439 -msgid "Level:" -msgstr "Poziom:" - -#: Source/DiabloUI/selhero.cpp:444 -msgid "Strength:" -msgstr "Siła:" - -#: Source/DiabloUI/selhero.cpp:449 -msgid "Magic:" -msgstr "Magia:" - -#: Source/DiabloUI/selhero.cpp:454 -msgid "Dexterity:" -msgstr "Zręczność:" - -#: Source/DiabloUI/selhero.cpp:459 -msgid "Vitality:" -msgstr "Żywotność:" - -#: Source/DiabloUI/selhero.cpp:465 -msgid "Savegame:" -msgstr "Zapis Gry:" - -#: Source/DiabloUI/selhero.cpp:477 -msgid "Select Hero" -msgstr "Wybierz Postać" - -#: Source/DiabloUI/selhero.cpp:485 -msgid "New Hero" -msgstr "Nowa Postać" - -#: Source/DiabloUI/selhero.cpp:496 -msgid "Delete" -msgstr "Usuń" - -#: Source/DiabloUI/selhero.cpp:505 -msgid "Multi Player Characters" -msgstr "Postacie - Gra Sieciowa" - -#: Source/DiabloUI/selhero.cpp:555 -msgid "Delete Multi Player Hero" -msgstr "Usuń Postać - Gra Sieciowa" - -#: Source/DiabloUI/selhero.cpp:557 -msgid "Delete Single Player Hero" -msgstr "Usuń Postać - Gra Jednoosobowa" - -#: Source/DiabloUI/selhero.cpp:559 -msgid "Are you sure you want to delete the character \"{:s}\"?" -msgstr "Czy na pewno chcesz usunąć postać \"{:s}\"?" - -#: Source/DiabloUI/selstart.cpp:43 +#: Source/DiabloUI/selstart.cpp:41 msgid "Enter Hellfire" msgstr "Wejdź do Hellfire" -#: Source/DiabloUI/selstart.cpp:44 +#: Source/DiabloUI/selstart.cpp:42 msgid "Switch to Diablo" msgstr "Przełącz na Diablo" -#: Source/DiabloUI/selyesno.cpp:55 Source/stores.cpp:998 +#: Source/DiabloUI/selyesno.cpp:57 Source/stores.cpp:961 msgid "Yes" msgstr "Tak" -#: Source/DiabloUI/selyesno.cpp:56 Source/stores.cpp:999 +#: Source/DiabloUI/selyesno.cpp:58 Source/stores.cpp:962 msgid "No" msgstr "Nie" -#: Source/DiabloUI/settingsmenu.cpp:305 +#: Source/DiabloUI/settingsmenu.cpp:146 +msgid "Press gamepad buttons to change." +msgstr "Naciśnij przycisk na kontrolerze aby zmienić." + +#: Source/DiabloUI/settingsmenu.cpp:423 msgid "Bound key:" msgstr "Powiązany klawisz:" -#: Source/DiabloUI/settingsmenu.cpp:340 +#: Source/DiabloUI/settingsmenu.cpp:459 msgid "Press any key to change." msgstr "Naciśnij dowolny klawisz by zmienić." -#: Source/DiabloUI/settingsmenu.cpp:342 +#: Source/DiabloUI/settingsmenu.cpp:461 msgid "Unbind key" msgstr "Usuń wiązanie klawisza" -#: Source/DiabloUI/settingsmenu.cpp:348 Source/gamemenu.cpp:61 +#: Source/DiabloUI/settingsmenu.cpp:465 +msgid "Bound button combo:" +msgstr "" + +#: Source/DiabloUI/settingsmenu.cpp:474 +msgid "Unbind button combo" +msgstr "" + +#: Source/DiabloUI/settingsmenu.cpp:518 Source/gamemenu.cpp:68 msgid "Previous Menu" msgstr "Powrót" #: Source/DiabloUI/support_lines.cpp:8 msgid "" -"We maintain a chat server at Discord.gg/devilutionx Follow the links to join our " -"community where we talk about things related to Diablo, and the Hellfire " +"We maintain a chat server at Discord.gg/devilutionx Follow the links to join " +"our community where we talk about things related to Diablo, and the Hellfire " "expansion." msgstr "" -"Utrzymujemy serwer czatu pod adresem Discord.gg/devilutionx Podążaj za linkami, " -"aby dołączyć do naszej społeczności, gdzie rozmawiamy o rzeczach związanych " -"z Diablo i dodatkiem Hellfire." +"Utrzymujemy serwer czatu pod adresem Discord.gg/devilutionx Podążaj za " +"linkami, aby dołączyć do naszej społeczności, gdzie rozmawiamy o rzeczach " +"związanych z Diablo i dodatkiem Hellfire." #: Source/DiabloUI/support_lines.cpp:10 msgid "" @@ -883,16 +907,17 @@ msgstr "" "SDL, który jest objęty licencją zlib-license. Więcej szczegółów można " "znaleźć w ReadMe." -#: Source/DiabloUI/title.cpp:46 +#: Source/DiabloUI/title.cpp:59 msgid "Copyright © 1996-2001 Blizzard Entertainment" msgstr "Copyright © 1996-2001 Blizzard Entertainment" -#: Source/appfat.cpp:38 +#: Source/appfat.cpp:52 msgid "Error" msgstr "Błąd" #. TRANSLATORS: Error message that displays relevant information for bug report -#: Source/appfat.cpp:100 +#: Source/appfat.cpp:67 +#, c++-format msgid "" "{:s}\n" "\n" @@ -902,7 +927,8 @@ msgstr "" "\n" "Nastąpił błąd w: {:s} linia {:d}" -#: Source/appfat.cpp:109 +#: Source/appfat.cpp:76 +#, c++-format msgid "" "Unable to open main data archive ({:s}).\n" "\n" @@ -912,12 +938,13 @@ msgstr "" "\n" "Upewnij się, że plik znajduje się w folderze." -#: Source/appfat.cpp:114 +#: Source/appfat.cpp:81 msgid "Data File Error" msgstr "Błąd Pliku z Danymi" #. TRANSLATORS: Error when Program is not allowed to write data -#: Source/appfat.cpp:120 +#: Source/appfat.cpp:87 +#, c++-format msgid "" "Unable to write to location:\n" "{:s}" @@ -925,960 +952,1441 @@ msgstr "" "Nie da się zapisać do:\n" "{:s}" -#: Source/appfat.cpp:122 +#: Source/appfat.cpp:89 msgid "Read-Only Directory Error" msgstr "Błąd Folderu Tylko do Odczytu" -#: Source/automap.cpp:484 +#: Source/automap.cpp:1414 msgid "Game: " msgstr "Gra: " -#: Source/automap.cpp:492 +# NOTES(nelchael): Wyświetlane zamiast nazwy gry gdy widoczna jest mapa. +#: Source/automap.cpp:1422 +msgid "Offline Game" +msgstr "Gra lokalna" + +#: Source/automap.cpp:1424 msgid "Password: " msgstr "Hasło: " -#: Source/automap.cpp:495 +#: Source/automap.cpp:1427 msgid "Public Game" msgstr "Publiczna Gra" -#: Source/automap.cpp:509 +#: Source/automap.cpp:1441 +#, c++-format msgid "Level: Nest {:d}" msgstr "Gniazdo - Poziom {:d}" -#: Source/automap.cpp:511 +#: Source/automap.cpp:1444 +#, c++-format msgid "Level: Crypt {:d}" msgstr "Krypta - Poziom {:d}" -#: Source/automap.cpp:513 +#: Source/automap.cpp:1447 Source/discord/discord.cpp:73 Source/objects.cpp:153 +msgid "Town" +msgstr "Miasto" + +#: Source/automap.cpp:1450 +#, c++-format msgid "Level: {:d}" msgstr "Poziom: {:d}" -#: Source/control.cpp:155 +#: Source/control.cpp:174 msgid "Tab" msgstr "Tab" -#: Source/control.cpp:155 +#: Source/control.cpp:174 msgid "Esc" msgstr "Esc" -#: Source/control.cpp:155 +#: Source/control.cpp:174 msgid "Enter" msgstr "Enter" -#: Source/control.cpp:158 +#: Source/control.cpp:177 msgid "Character Information" msgstr "Informacje o postaci" -#: Source/control.cpp:159 +#: Source/control.cpp:178 msgid "Quests log" msgstr "Dziennik Zadań" -#: Source/control.cpp:160 +#: Source/control.cpp:179 msgid "Automap" msgstr "Automapa" -#: Source/control.cpp:161 +#: Source/control.cpp:180 msgid "Main Menu" msgstr "Menu Główne" -#: Source/control.cpp:162 Source/diablo.cpp:1535 +#: Source/control.cpp:181 Source/diablo.cpp:1735 Source/diablo.cpp:2068 msgid "Inventory" msgstr "Ekwipunek" -#: Source/control.cpp:163 +#: Source/control.cpp:182 msgid "Spell book" msgstr "Księga zaklęć" -#: Source/control.cpp:164 +#: Source/control.cpp:183 msgid "Send Message" msgstr "Wyślij Wiadomość" -#: Source/control.cpp:695 +#: Source/control.cpp:369 +msgid "Available Commands:" +msgstr "Dostępne Polecenia:" + +#: Source/control.cpp:377 Source/control.cpp:567 +msgid "Command " +msgstr "Polecenie " + +#: Source/control.cpp:377 Source/control.cpp:567 +msgid " is unknown." +msgstr " jest nieznane." + +#: Source/control.cpp:380 Source/control.cpp:381 +msgid "Description: " +msgstr "Opis: " + +#: Source/control.cpp:380 +msgid "" +"\n" +"Parameters: No additional parameter needed." +msgstr "" +"\n" +"Parametry: Brak wymaganych dodatkowych parametrów." + +#: Source/control.cpp:381 +msgid "" +"\n" +"Parameters: " +msgstr "" +"\n" +"Parametry: " + +#: Source/control.cpp:401 Source/control.cpp:433 +msgid "Arenas are only supported in multiplayer." +msgstr "Areny są obsługiwane tylko w grach wieloosobowych." + +#: Source/control.cpp:406 +msgid "What arena do you want to visit?" +msgstr "Którą arenę chcesz odwiedzić?" + +#: Source/control.cpp:414 +msgid "Invalid arena-number. Valid numbers are:" +msgstr "Błędny numer areny. Poprawne numery to:" + +#: Source/control.cpp:420 +msgid "To enter a arena, you need to be in town or another arena." +msgstr "Aby wejść na arenę musisz być w mieście lub na innej arenie." + +#: Source/control.cpp:458 +msgid "Inspecting only supported in multiplayer." +msgstr "" + +#: Source/control.cpp:463 Source/control.cpp:754 +msgid "Stopped inspecting players." +msgstr "" + +#: Source/control.cpp:478 +msgid "No players found with such a name" +msgstr "Nie znaleziono graczy z tą nazwą" + +#: Source/control.cpp:484 +msgid "Inspecting player: " +msgstr "" + +#: Source/control.cpp:553 +msgid "Prints help overview or help for a specific command." +msgstr "" + +#: Source/control.cpp:553 +msgid "[command]" +msgstr "[polecenie]" + +#: Source/control.cpp:554 +msgid "Enter a PvP Arena." +msgstr "Wejdź na Arenę PvP." + +#: Source/control.cpp:554 +msgid "" +msgstr "" + +#: Source/control.cpp:555 +msgid "Gives Arena Potions." +msgstr "Daje Mikstury Areny." + +#: Source/control.cpp:555 +msgid "" +msgstr "" + +#: Source/control.cpp:556 +msgid "Inspects stats and equipment of another player." +msgstr "" + +#: Source/control.cpp:556 +msgid "" +msgstr "" + +#: Source/control.cpp:557 +msgid "Show seed infos for current level." +msgstr "" + +#: Source/control.cpp:1049 msgid "Player friendly" msgstr "Neutralność" -#: Source/control.cpp:697 +#: Source/control.cpp:1051 msgid "Player attack" msgstr "Wrogość" -#: Source/control.cpp:700 +#: Source/control.cpp:1054 +#, c++-format msgid "Hotkey: {:s}" msgstr "Klawisz: {:s}" -#: Source/control.cpp:707 +#: Source/control.cpp:1061 msgid "Select current spell button" msgstr "Wybierz zaklęcie" -#: Source/control.cpp:710 +#: Source/control.cpp:1064 msgid "Hotkey: 's'" msgstr "Klawisz: 's'" -#: Source/control.cpp:716 Source/panels/spell_list.cpp:163 +#: Source/control.cpp:1070 Source/panels/spell_list.cpp:152 +#, c++-format msgid "{:s} Skill" msgstr "Umiejętność: {:s}" -#: Source/control.cpp:719 Source/panels/spell_list.cpp:170 +#: Source/control.cpp:1073 Source/panels/spell_list.cpp:159 +#, c++-format msgid "{:s} Spell" msgstr "Czar: {:s}" -#: Source/control.cpp:721 Source/panels/spell_list.cpp:175 +#: Source/control.cpp:1075 Source/panels/spell_list.cpp:164 msgid "Spell Level 0 - Unusable" msgstr "Poziom Czaru: 0 - Bezużyteczny" -#: Source/control.cpp:721 Source/panels/spell_list.cpp:177 +#: Source/control.cpp:1075 Source/panels/spell_list.cpp:166 +#, c++-format msgid "Spell Level {:d}" msgstr "Poziom Czaru: {:d}" -#: Source/control.cpp:724 Source/panels/spell_list.cpp:184 +#: Source/control.cpp:1078 Source/panels/spell_list.cpp:173 +#, c++-format msgid "Scroll of {:s}" msgstr "Zwój: {:s}" -#: Source/control.cpp:729 Source/panels/spell_list.cpp:189 +#: Source/control.cpp:1082 Source/panels/spell_list.cpp:177 +#, c++-format msgid "{:d} Scroll" msgid_plural "{:d} Scrolls" msgstr[0] "Zwój: {:d}" msgstr[1] "Zwoje: {:d}" msgstr[2] "Zwoje: {:d}" -#: Source/control.cpp:732 Source/panels/spell_list.cpp:196 +#: Source/control.cpp:1085 Source/panels/spell_list.cpp:184 +#, c++-format msgid "Staff of {:s}" msgstr "Kostur: {:s}" -#: Source/control.cpp:733 Source/panels/spell_list.cpp:198 +#: Source/control.cpp:1086 Source/panels/spell_list.cpp:186 +#, c++-format msgid "{:d} Charge" msgid_plural "{:d} Charges" msgstr[0] "Ładunek: {:d}" msgstr[1] "Ładunki: {:d}" msgstr[2] "Ładunki: {:d}" -#: Source/control.cpp:861 Source/inv.cpp:1963 Source/items.cpp:3489 +#: Source/control.cpp:1205 Source/inv.cpp:1873 Source/items.cpp:3607 +#, c++-format msgid "{:s} gold piece" msgid_plural "{:s} gold pieces" msgstr[0] "{:s} złota" msgstr[1] "{:s} złota" msgstr[2] "{:s} złota" -#: Source/control.cpp:863 +#: Source/control.cpp:1207 msgid "Requirements not met" msgstr "Nie spełniono wymagań" -#: Source/control.cpp:897 +#: Source/control.cpp:1236 +#, c++-format msgid "{:s}, Level: {:d}" msgstr "{:s}, Poziom: {:d}" -#: Source/control.cpp:898 +#: Source/control.cpp:1237 +#, c++-format msgid "Hit Points {:d} of {:d}" msgstr "Punkty Życia {:d} / {:d}" -#: Source/control.cpp:932 +#: Source/control.cpp:1268 msgid "Level Up" msgstr "Nowy Poziom" -#. TRANSLATORS: {:d} is a number. Dialog is shown when splitting a stash of Gold. -#: Source/control.cpp:1040 +#. TRANSLATORS: {:s} is a number with separators. Dialog is shown when splitting a stash of Gold. +#: Source/control.cpp:1381 +#, 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] "Masz {:s} złota. Ile monet chcesz oddzielić?" msgstr[1] "Masz {:s} złota. Ile monet chcesz oddzielić?" msgstr[2] "Masz {:s} złota. Ile monet chcesz oddzielić?" -#: 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 "" -"Błąd podczas wczytywania danych.\n" -"\n" -"Upewnij się, że najnowsza wersja pliku devilutionx.mpq znajduje się w " -"folderze z grą." - -#: Source/cursor.cpp:222 +#: Source/cursor.cpp:641 msgid "Town Portal" msgstr "Miejski Portal" -#: Source/cursor.cpp:223 +#: Source/cursor.cpp:642 +#, c++-format msgid "from {:s}" msgstr "od: {:s}" -#: Source/cursor.cpp:237 +#: Source/cursor.cpp:655 msgid "Portal to" msgstr "Portal do" -#: Source/cursor.cpp:238 +#: Source/cursor.cpp:656 msgid "The Unholy Altar" msgstr "Przeklęty Ołtarz" -#: Source/cursor.cpp:238 +#: Source/cursor.cpp:656 msgid "level 15" msgstr "poziom 15" -#: Source/diablo.cpp:120 -msgid "I need help! Come Here!" -msgstr "Pomocy! Chodź tu!" +#. TRANSLATORS: Error message when a data file is missing or corrupt. Arguments are {file name} +#: Source/data/file.cpp:36 +#, c++-format +msgid "Unable to load data from file {0}" +msgstr "Nie można wczytać danych z pliku {0}" + +#. TRANSLATORS: Error message when a data file is empty or only contains the header row. Arguments are {file name} +#: Source/data/file.cpp:41 +#, c++-format +msgid "{0} is incomplete, please check the file contents." +msgstr "{0} jest niekompletny, proszę sprawdzić zawartość pliku." + +#. TRANSLATORS: Error message when a data file doesn't contain the expected columns. Arguments are {file name} +#: Source/data/file.cpp:46 +#, c++-format +msgid "" +"Your {0} file doesn't have the expected columns, please make sure it matches " +"the documented format." +msgstr "" +"Twój plik {0} nie ma oczekiwanych kolumn, proszę upewnić się, że pasuje do " +"udokumentowanego formatu." + +#. TRANSLATORS: Error message when parsing a data file and a text value is encountered when a number is expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} +#: Source/data/file.cpp:61 +#, c++-format +msgid "Non-numeric value {0} for {1} in {2} at row {3} and column {4}" +msgstr "" + +#. TRANSLATORS: Error message when parsing a data file and we find a number larger than expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} +#: Source/data/file.cpp:67 +#, c++-format +msgid "Out of range value {0} for {1} in {2} at row {3} and column {4}" +msgstr "" + +#. TRANSLATORS: Error message when we find an unrecognised value in a key column. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} +#: Source/data/file.cpp:73 +#, c++-format +msgid "Invalid value {0} for {1} in {2} at row {3} and column {4}" +msgstr "" + +#: Source/diablo.cpp:132 +msgid "I need help! Come here!" +msgstr "Potrzebuję pomocy! Chodź tu!" -#: Source/diablo.cpp:121 +#: Source/diablo.cpp:133 msgid "Follow me." msgstr "Idź za mną." -#: Source/diablo.cpp:122 +#: Source/diablo.cpp:134 msgid "Here's something for you." msgstr "Mam coś dla ciebie." -#: Source/diablo.cpp:123 +#: Source/diablo.cpp:135 msgid "Now you DIE!" msgstr "Teraz ZGINIESZ!" -#. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:821 -msgid "Options:" -msgstr "Opcje" +#: Source/diablo.cpp:136 +msgid "Heal yourself!" +msgstr "Uzdrów się!" + +#: Source/diablo.cpp:137 +msgid "Watch out!" +msgstr "Uważaj!" + +#: Source/diablo.cpp:138 +msgid "Thanks." +msgstr "Dziękuję." + +#: Source/diablo.cpp:139 +msgid "Retreat!" +msgstr "Wycofać się!" + +#: Source/diablo.cpp:140 +msgid "Sorry." +msgstr "Przepraszam." + +#: Source/diablo.cpp:141 +msgid "I'm waiting." +msgstr "Czekam." #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:822 +#: Source/diablo.cpp:912 msgid "Print this message and exit" msgstr "Wyświetl tę wiadomość i wyjdź" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:823 +#: Source/diablo.cpp:913 msgid "Print the version and exit" msgstr "Wyświetl numer wersji i wyjdź" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:824 +#: Source/diablo.cpp:914 msgid "Specify the folder of diabdat.mpq" msgstr "Określ folder dla diabdat.mpq" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:825 +#: Source/diablo.cpp:915 msgid "Specify the folder of save files" msgstr "Określ folder dla plików zapisu" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:826 +#: Source/diablo.cpp:916 msgid "Specify the location of diablo.ini" msgstr "Określ lokalizację diablo.ini" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:827 +#: Source/diablo.cpp:917 +msgid "Specify the language code (e.g. en or pt_BR)" +msgstr "Podaj kod języka (na przykład en lub pt_BR)" + +#. TRANSLATORS: Commandline Option +#: Source/diablo.cpp:918 msgid "Skip startup videos" msgstr "Pomiń loga i wprowadzenie" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:828 +#: Source/diablo.cpp:919 msgid "Display frames per second" msgstr "Wyświetl ilość klatek na sekundę" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:829 +#: Source/diablo.cpp:920 msgid "Enable verbose logging" msgstr "Włącz szczegółowy log" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:830 +#: Source/diablo.cpp:922 msgid "Record a demo file" msgstr "Nagraj demo" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:831 +#: Source/diablo.cpp:923 msgid "Play a demo file" msgstr "Odtwórz plik z nagranym demo" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:832 +#: Source/diablo.cpp:924 msgid "Disable all frame limiting during demo playback" msgstr "Wyłącz limit klatek podczas odtwarzania demo" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:833 +#: Source/diablo.cpp:927 msgid "Game selection:" msgstr "Wybór gry:" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:834 +#: Source/diablo.cpp:929 msgid "Force Shareware mode" msgstr "Wymuś tryb demo" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:835 +#: Source/diablo.cpp:930 msgid "Force Diablo mode" msgstr "Wymuś tryb Diablo" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:836 +#: Source/diablo.cpp:931 msgid "Force Hellfire mode" msgstr "Wymuś tryb Hellfire" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:837 +#: Source/diablo.cpp:932 msgid "Hellfire options:" -msgstr "Opcje Hellfire" +msgstr "Opcje Hellfire:" -#: Source/diablo.cpp:843 +#: Source/diablo.cpp:942 msgid "Report bugs at https://github.com/diasurgical/devilutionX/" msgstr "Zgłoś błędy na https://github.com/diasurgical/devilutionX/" -#: Source/diablo.cpp:955 -msgid "version {:s}" -msgstr "wersja {:s}" +#: Source/diablo.cpp:1113 +msgid "Please update devilutionx.mpq and fonts.mpq to the latest version" +msgstr "Proszę zaktualizować devilutionx.mpq i fonts.mpq do najnowszych wersji" + +#: Source/diablo.cpp:1115 +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 "" +"Błąd podczas wczytywania danych.\n" +"\n" +"Upewnij się, że najnowsza wersja pliku devilutionx.mpq znajduje się w " +"folderze z grą." + +#: Source/diablo.cpp:1119 +msgid "Please update fonts.mpq to the latest version" +msgstr "Proszę zaktualizować fonts.mpq do najnowszej wersji" -#: Source/diablo.cpp:1275 +#: Source/diablo.cpp:1437 msgid "-- Network timeout --" msgstr "-- Przekroczono limit czasu oczekiwania --" -#: Source/diablo.cpp:1276 +#: Source/diablo.cpp:1438 msgid "-- Waiting for players --" msgstr "-- Oczekiwanie na graczy --" -#: Source/diablo.cpp:1295 +#: Source/diablo.cpp:1461 msgid "No help available" msgstr "Pomoc niedostępna" -#: Source/diablo.cpp:1296 +#: Source/diablo.cpp:1462 msgid "while in stores" msgstr "podczas przebywania w sklepie" -#: Source/diablo.cpp:1437 +#: Source/diablo.cpp:1613 Source/diablo.cpp:1900 +#, c++-format msgid "Belt item {}" -msgstr "Element pasa {}" +msgstr "Przedmiot pasa {}" -#: Source/diablo.cpp:1438 +#: Source/diablo.cpp:1614 Source/diablo.cpp:1901 msgid "Use Belt item." msgstr "Użycie przedmiotu z pasa." -#: Source/diablo.cpp:1453 +#: Source/diablo.cpp:1629 Source/diablo.cpp:1916 +#, c++-format msgid "Quick spell {}" msgstr "Szybki czar {}" -#: Source/diablo.cpp:1454 +#: Source/diablo.cpp:1630 Source/diablo.cpp:1917 msgid "Hotkey for skill or spell." msgstr "Ustawianie skrótu klawiszowego umiejętności." -#: Source/diablo.cpp:1472 +#: Source/diablo.cpp:1648 Source/diablo.cpp:2044 +msgid "Use health potion" +msgstr "Użyj mikstury leczenia" + +#: Source/diablo.cpp:1649 Source/diablo.cpp:2045 +msgid "Use health potions from belt." +msgstr "Użyj mikstury leczenia z pasa." + +#: Source/diablo.cpp:1656 Source/diablo.cpp:2052 +msgid "Use mana potion" +msgstr "Użyj mikstury many" + +#: Source/diablo.cpp:1657 Source/diablo.cpp:2053 +msgid "Use mana potions from belt." +msgstr "Użyj mikstury many z pasa." + +#: Source/diablo.cpp:1664 Source/diablo.cpp:2098 msgid "Speedbook" msgstr "Podręczna Księga" -#: Source/diablo.cpp:1473 +#: Source/diablo.cpp:1665 Source/diablo.cpp:2099 msgid "Open Speedbook." msgstr "Wyświetla Podręczną Księgę." -#: Source/diablo.cpp:1480 +#: Source/diablo.cpp:1672 Source/diablo.cpp:2231 msgid "Quick save" msgstr "Szybki zapis" -#: Source/diablo.cpp:1481 +#: Source/diablo.cpp:1673 Source/diablo.cpp:2232 msgid "Saves the game." msgstr "Zapisuje grę." -#: Source/diablo.cpp:1488 +#: Source/diablo.cpp:1680 Source/diablo.cpp:2239 msgid "Quick load" msgstr "Szybki odczyt" -#: Source/diablo.cpp:1489 +#: Source/diablo.cpp:1681 Source/diablo.cpp:2240 msgid "Loads the game." msgstr "Wczytuje grę." -#: Source/diablo.cpp:1497 +#: Source/diablo.cpp:1689 msgid "Quit game" msgstr "Wyjdź z gry" -#: Source/diablo.cpp:1498 +#: Source/diablo.cpp:1690 msgid "Closes the game." msgstr "Zamyka grę." -#: Source/diablo.cpp:1504 +#: Source/diablo.cpp:1696 msgid "Stop hero" msgstr "Zatrzymaj bohatera" -#: Source/diablo.cpp:1505 +#: Source/diablo.cpp:1697 msgid "Stops walking and cancel pending actions." msgstr "Zatrzymuje chodzenie i anuluje oczekujące działania." -#: Source/diablo.cpp:1512 +#: Source/diablo.cpp:1704 Source/diablo.cpp:2247 msgid "Item highlighting" msgstr "Podświetlenie przedmiotów" -#: Source/diablo.cpp:1513 +#: Source/diablo.cpp:1705 Source/diablo.cpp:2248 msgid "Show/hide items on ground." msgstr "Pokaż/ukryj przedmioty na ziemi." -#: Source/diablo.cpp:1519 +#: Source/diablo.cpp:1711 Source/diablo.cpp:2254 msgid "Toggle item highlighting" msgstr "Przełączanie podświetlania przedmiotów" -#: Source/diablo.cpp:1520 +#: Source/diablo.cpp:1712 Source/diablo.cpp:2255 msgid "Permanent show/hide items on ground." msgstr "Stałe pokazywanie/ukrywanie przedmiotów na ziemi." -#: Source/diablo.cpp:1526 +#: Source/diablo.cpp:1718 Source/diablo.cpp:2108 msgid "Toggle automap" msgstr "Przełączanie automapy" -#: Source/diablo.cpp:1527 +#: Source/diablo.cpp:1719 Source/diablo.cpp:2109 msgid "Toggles if automap is displayed." msgstr "Przełącza, czy wyświetlana jest automapa." -#: Source/diablo.cpp:1536 +#: Source/diablo.cpp:1726 +msgid "Cycle map type" +msgstr "Przełącz typ mapy" + +#: Source/diablo.cpp:1727 +msgid "Opaque -> Transparent -> Minimap -> None" +msgstr "Nieprzezroczysta -> Przezroczysta -> Mini mapa -> Brak" + +#: Source/diablo.cpp:1736 Source/diablo.cpp:2069 msgid "Open Inventory screen." msgstr "Wyświetla ekran Ekwipunku." -#: Source/diablo.cpp:1543 +#: Source/diablo.cpp:1743 Source/diablo.cpp:2060 msgid "Character" msgstr "Postać" -#: Source/diablo.cpp:1544 +#: Source/diablo.cpp:1744 Source/diablo.cpp:2061 msgid "Open Character screen." msgstr "Wyświetla panel postaci." -#: Source/diablo.cpp:1551 +#: Source/diablo.cpp:1751 Source/diablo.cpp:2078 msgid "Quest log" msgstr "Dziennik Zadań" -#: Source/diablo.cpp:1552 +#: Source/diablo.cpp:1752 Source/diablo.cpp:2079 msgid "Open Quest log." msgstr "Wyświetla Dziennik Zadań." -#: Source/diablo.cpp:1559 +#: Source/diablo.cpp:1759 Source/diablo.cpp:2088 msgid "Spellbook" msgstr "Księga zaklęć" -#: Source/diablo.cpp:1560 +#: Source/diablo.cpp:1760 Source/diablo.cpp:2089 msgid "Open Spellbook." msgstr "Wyświetla księgę zaklęć." -#: Source/diablo.cpp:1568 +#: Source/diablo.cpp:1768 +#, c++-format msgid "Quick Message {}" msgstr "Szybka wiadomość {}" -#: Source/diablo.cpp:1569 +#: Source/diablo.cpp:1769 msgid "Use Quick Message in chat." msgstr "Użyj funkcji Szybka Wiadomość na czacie." -#: Source/diablo.cpp:1578 +#: Source/diablo.cpp:1778 Source/diablo.cpp:2261 msgid "Hide Info Screens" msgstr "Ukryj ekrany informacyjne" -#: Source/diablo.cpp:1579 +#: Source/diablo.cpp:1779 Source/diablo.cpp:2262 msgid "Hide all info screens." msgstr "Ukryj wszystkie ekrany informacyjne." -#: Source/diablo.cpp:1599 +#: Source/diablo.cpp:1802 Source/diablo.cpp:2285 Source/options.cpp:986 msgid "Zoom" msgstr "Powiększenie" -#: Source/diablo.cpp:1600 +#: Source/diablo.cpp:1803 Source/diablo.cpp:2286 msgid "Zoom Game Screen." msgstr "Przybliżenie ekranu." -#: Source/diablo.cpp:1610 +#: Source/diablo.cpp:1813 Source/diablo.cpp:2296 msgid "Pause Game" msgstr "Wstrzymaj grę" -#: Source/diablo.cpp:1611 +#: Source/diablo.cpp:1814 Source/diablo.cpp:1820 Source/diablo.cpp:2297 msgid "Pauses the game." msgstr "Wstrzymuje grę." -#: Source/diablo.cpp:1616 +#: Source/diablo.cpp:1819 +msgid "Pause Game (Alternate)" +msgstr "Wstrzymaj grę (alternatywnie)" + +#: Source/diablo.cpp:1825 Source/diablo.cpp:2302 msgid "Decrease Gamma" msgstr "Zmniejsz jasność" -#: Source/diablo.cpp:1617 +#: Source/diablo.cpp:1826 Source/diablo.cpp:2303 msgid "Reduce screen brightness." msgstr "Zmniejsza jasność ekranu." -#: Source/diablo.cpp:1624 +#: Source/diablo.cpp:1833 Source/diablo.cpp:2310 msgid "Increase Gamma" msgstr "Zwiększ jasność" -#: Source/diablo.cpp:1625 +#: Source/diablo.cpp:1834 Source/diablo.cpp:2311 msgid "Increase screen brightness." msgstr "Zwiększa jasność ekranu." -#: Source/diablo.cpp:1632 +#: Source/diablo.cpp:1841 Source/diablo.cpp:2318 msgid "Help" msgstr "Pomoc" -#: Source/diablo.cpp:1633 +#: Source/diablo.cpp:1842 Source/diablo.cpp:2319 msgid "Open Help Screen." msgstr "Wyświetl Okno Pomocy." -#: Source/diablo.cpp:1640 +#: Source/diablo.cpp:1849 Source/diablo.cpp:2326 msgid "Screenshot" msgstr "Zrzut ekranu" -#: Source/diablo.cpp:1641 +#: Source/diablo.cpp:1850 Source/diablo.cpp:2327 msgid "Takes a screenshot." msgstr "Wykonuje zrzut ekranu." -#: Source/diablo.cpp:1647 +#: Source/diablo.cpp:1856 Source/diablo.cpp:2333 msgid "Game info" msgstr "Informacje o grze" -#: Source/diablo.cpp:1648 +#: Source/diablo.cpp:1857 Source/diablo.cpp:2334 msgid "Displays game infos." msgstr "Wyświetla informacje o grze." #. TRANSLATORS: {:s} means: Character Name, Game Version, Game Difficulty. -#: Source/diablo.cpp:1652 +#: Source/diablo.cpp:1861 Source/diablo.cpp:2338 +#, c++-format msgid "{:s} {:s}" msgstr "{:s} {:s}" -#: Source/diablo.cpp:1661 +#: Source/diablo.cpp:1870 Source/diablo.cpp:2347 msgid "Chat Log" msgstr "Dziennik czatu" -#: Source/diablo.cpp:1662 +#: Source/diablo.cpp:1871 Source/diablo.cpp:2348 msgid "Displays chat log." msgstr "Wyświetla dziennik czatu." -#: Source/discord/discord.cpp:66 Source/objects.cpp:136 -msgid "Town" -msgstr "Okoliczna" +#: Source/diablo.cpp:1879 +msgid "Console" +msgstr "Konsola" -#: Source/discord/discord.cpp:66 -msgid "Cathedral" -msgstr "Katedra" +#: Source/diablo.cpp:1880 +msgid "Opens Lua console." +msgstr "Otwiera konsolę Lua." -#: Source/discord/discord.cpp:66 -msgid "Catacombs" -msgstr "Katakumby" +#: Source/diablo.cpp:1935 +msgid "Primary action" +msgstr "Główna akcja" -#: Source/discord/discord.cpp:66 -msgid "Caves" -msgstr "Jaskinie" +#: Source/diablo.cpp:1936 +msgid "Attack monsters, talk to towners, lift and place inventory items." +msgstr "" +"Atakuj potwory, rozmawiaj z mieszkańcami, podnoś i umieszczaj przedmioty w " +"ekwipunku." -#: Source/discord/discord.cpp:66 -msgid "Nest" -msgstr "Gniazdo" +#: Source/diablo.cpp:1950 +msgid "Secondary action" +msgstr "Drugorzędna akcja" -#: Source/discord/discord.cpp:66 -msgid "Crypt" -msgstr "Krypta" +#: Source/diablo.cpp:1951 +msgid "Open chests, interact with doors, pick up items." +msgstr "Otwieraj skrzynie, używaj drzwi, podnoś przedmioty." -#. TRANSLATORS: dungeon type and floor number i.e. "Cathedral 3" -#: Source/discord/discord.cpp:82 -msgid "{} {}" -msgstr "{} {}" +#: Source/diablo.cpp:1965 +msgid "Spell action" +msgstr "Akcja czaru" -#. TRANSLATORS: Discord character, i.e. "Lv 6 Warrior" -#: Source/discord/discord.cpp:90 -msgid "Lv {} {}" -msgstr "Poz. {} {}" +#: Source/diablo.cpp:1966 +msgid "Cast the active spell." +msgstr "Rzuć aktywny czar." -#. TRANSLATORS: Discord state i.e. "Nightmare difficulty" -#: Source/discord/discord.cpp:102 -msgid "{} difficulty" -msgstr "{} Poziom Trudności" +#: Source/diablo.cpp:1980 +msgid "Cancel action" +msgstr "Akcja anulowania" -#. TRANSLATORS: Discord activity, not in game -#: Source/discord/discord.cpp:182 -msgid "In Menu" -msgstr "W Menu" +#: Source/diablo.cpp:1981 +msgid "Close menus." +msgstr "Zamknij menu." -#: Source/dvlnet/loopback.cpp:118 -msgid "loopback" -msgstr "loopback" +#: Source/diablo.cpp:2006 +msgid "Move up" +msgstr "Ruch do góry" -#: Source/dvlnet/tcp_client.cpp:65 -msgid "Unable to connect" -msgstr "Nie udało się połączyć" +#: Source/diablo.cpp:2007 +msgid "Moves the player character up." +msgstr "Porusza postacią gracza do góry." -#: Source/dvlnet/tcp_client.cpp:91 -msgid "error: read 0 bytes from server" -msgstr "błąd: wczytano 0 bajtów z serwera" +#: Source/diablo.cpp:2012 +msgid "Move down" +msgstr "Ruch w dół" + +#: Source/diablo.cpp:2013 +msgid "Moves the player character down." +msgstr "Porusza postacią gracza w dół." + +#: Source/diablo.cpp:2018 +msgid "Move left" +msgstr "Ruch w lewo" + +#: Source/diablo.cpp:2019 +msgid "Moves the player character left." +msgstr "Porusza postacią gracza w lewo." + +#: Source/diablo.cpp:2024 +msgid "Move right" +msgstr "Ruch w prawo" + +#: Source/diablo.cpp:2025 +msgid "Moves the player character right." +msgstr "Porusza postacią gracza w prawo." + +#: Source/diablo.cpp:2030 +msgid "Stand ground" +msgstr "" + +#: Source/diablo.cpp:2031 +msgid "Hold to prevent the player from moving." +msgstr "" + +#: Source/diablo.cpp:2036 +msgid "Toggle stand ground" +msgstr "" + +#: Source/diablo.cpp:2037 +msgid "Toggle whether the player moves." +msgstr "" + +#: Source/diablo.cpp:2114 +msgid "Move mouse up" +msgstr "" + +#: Source/diablo.cpp:2115 +msgid "Simulates upward mouse movement." +msgstr "" + +#: Source/diablo.cpp:2120 +msgid "Move mouse down" +msgstr "" + +#: Source/diablo.cpp:2121 +msgid "Simulates downward mouse movement." +msgstr "" + +#: Source/diablo.cpp:2126 +msgid "Move mouse left" +msgstr "" + +#: Source/diablo.cpp:2127 +msgid "Simulates leftward mouse movement." +msgstr "" + +#: Source/diablo.cpp:2132 +msgid "Move mouse right" +msgstr "" + +#: Source/diablo.cpp:2133 +msgid "Simulates rightward mouse movement." +msgstr "" + +#: Source/diablo.cpp:2151 Source/diablo.cpp:2158 +msgid "Left mouse click" +msgstr "Kliknięcie lewym przyciskiem myszy" + +#: Source/diablo.cpp:2152 Source/diablo.cpp:2159 +msgid "Simulates the left mouse button." +msgstr "Symuluje kliknięcie lewym przyciskiem myszy." + +#: Source/diablo.cpp:2176 Source/diablo.cpp:2183 +msgid "Right mouse click" +msgstr "Kliknięcie prawym przyciskiem myszy" + +#: Source/diablo.cpp:2177 Source/diablo.cpp:2184 +msgid "Simulates the right mouse button." +msgstr "Symuluje kliknięcie prawym przyciskiem myszy." + +#: Source/diablo.cpp:2190 +msgid "Gamepad hotspell menu" +msgstr "" + +#: Source/diablo.cpp:2191 +msgid "Hold to set or use spell hotkeys." +msgstr "" + +#: Source/diablo.cpp:2197 +msgid "Gamepad menu navigator" +msgstr "" + +#: Source/diablo.cpp:2198 +msgid "Hold to access gamepad menu navigation." +msgstr "" + +#: Source/diablo.cpp:2213 Source/diablo.cpp:2222 +msgid "Toggle game menu" +msgstr "Przełącza menu gry" -#: Source/error.cpp:53 -msgid "No automap available in town" -msgstr "Mapa niedostępna w mieście" +#: Source/diablo.cpp:2214 Source/diablo.cpp:2223 +msgid "Opens the game menu." +msgstr "Otwiera menu gry." -#: Source/error.cpp:54 +#: Source/diablo_msg.cpp:62 +msgid "Game saved" +msgstr "Gra zapisana" + +#: Source/diablo_msg.cpp:63 msgid "No multiplayer functions in demo" msgstr "Brak funkcji gry sieciowej w demo" -#: Source/error.cpp:55 +#: Source/diablo_msg.cpp:64 msgid "Direct Sound Creation Failed" msgstr "Błąd Utworzenia Direct Sound" -#: Source/error.cpp:56 +#: Source/diablo_msg.cpp:65 msgid "Not available in shareware version" msgstr "Niedostępne w wersji shareware" -#: Source/error.cpp:57 +#: Source/diablo_msg.cpp:66 msgid "Not enough space to save" msgstr "Brak miejsca do zapisu" -#: Source/error.cpp:58 +#: Source/diablo_msg.cpp:67 msgid "No Pause in town" msgstr "Brak pauzy w mieście" -#: Source/error.cpp:59 +#: Source/diablo_msg.cpp:68 msgid "Copying to a hard disk is recommended" msgstr "Zalecane skopiowanie na dysk twardy" -#: Source/error.cpp:60 +#: Source/diablo_msg.cpp:69 msgid "Multiplayer sync problem" msgstr "Błąd synchronizacji gry sieciowej" -#: Source/error.cpp:61 +#: Source/diablo_msg.cpp:70 msgid "No pause in multiplayer" msgstr "Brak pauzy w rozgrywce sieciowej" -#: Source/error.cpp:63 +#: Source/diablo_msg.cpp:72 msgid "Saving..." msgstr "Zapisywanie..." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:64 +#: Source/diablo_msg.cpp:73 msgid "Some are weakened as one grows strong" msgstr "Przypływ sił również ma swą cenę" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:65 +#: Source/diablo_msg.cpp:74 msgid "New strength is forged through destruction" msgstr "Przekucie w moc wymaga zniszczenia" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:66 +#: Source/diablo_msg.cpp:75 msgid "Those who defend seldom attack" msgstr "Atak nie zawsze jest najlepszą obroną" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:67 +#: Source/diablo_msg.cpp:76 msgid "The sword of justice is swift and sharp" msgstr "Miecz sprawiedliwości jest szybki i ostry" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:68 +#: Source/diablo_msg.cpp:77 msgid "While the spirit is vigilant the body thrives" msgstr "Harmonia ducha przynosi ukojenie dla ciała" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:69 +#: Source/diablo_msg.cpp:78 msgid "The powers of mana refocused renews" msgstr "Utracona energia powraca" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:70 +#: Source/diablo_msg.cpp:79 msgid "Time cannot diminish the power of steel" msgstr "Czas nie naruszy najlepszej stali" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:71 +#: Source/diablo_msg.cpp:80 msgid "Magic is not always what it seems to be" msgstr "Magia nie zawsze jest tym, na co wygląda" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:72 +#: Source/diablo_msg.cpp:81 msgid "What once was opened now is closed" msgstr "Co raz było otwarte, znów jest zamknięte" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:73 +#: Source/diablo_msg.cpp:82 msgid "Intensity comes at the cost of wisdom" msgstr "Żywiołowość zyskana kosztem mądrości" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:74 +#: Source/diablo_msg.cpp:83 msgid "Arcane power brings destruction" msgstr "Tajemna moc przynosi zniszczenie" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:75 +#: Source/diablo_msg.cpp:84 msgid "That which cannot be held cannot be harmed" msgstr "To, czego nie można trzymać, nie może być uszkodzone" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:76 +#: Source/diablo_msg.cpp:85 msgid "Crimson and Azure become as the sun" msgstr "Szkarłat i lazur błyszczą niczym słońce" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:77 +#: Source/diablo_msg.cpp:86 msgid "Knowledge and wisdom at the cost of self" msgstr "Wiedza i mądrość poprzez zatracenie" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:78 +#: Source/diablo_msg.cpp:87 msgid "Drink and be refreshed" msgstr "Napij się i poczuj odnowienie" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:79 +#: Source/diablo_msg.cpp:88 msgid "Wherever you go, there you are" msgstr "Pójdź w nieznane" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:80 +#: Source/diablo_msg.cpp:89 msgid "Energy comes at the cost of wisdom" msgstr "Energia zyskana kosztem mądrości" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:81 +#: Source/diablo_msg.cpp:90 msgid "Riches abound when least expected" msgstr "Bogactwo przybywa nieoczekiwanie" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:82 +#: Source/diablo_msg.cpp:91 msgid "Where avarice fails, patience gains reward" msgstr "Gdzie chciwość zawodzi, tam cierpliwość popłaca" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:83 +#: Source/diablo_msg.cpp:92 msgid "Blessed by a benevolent companion!" msgstr "Błogosławieństwo od towarzysza!" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:84 +#: Source/diablo_msg.cpp:93 msgid "The hands of men may be guided by fate" msgstr "Los jest w twoich rękach" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:85 +#: Source/diablo_msg.cpp:94 msgid "Strength is bolstered by heavenly faith" msgstr "Głęboka wiara zwiększa siłę" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:86 +#: Source/diablo_msg.cpp:95 msgid "The essence of life flows from within" msgstr "Istota życia leży we wnętrzu" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:87 +#: Source/diablo_msg.cpp:96 msgid "The way is made clear when viewed from above" msgstr "Droga widziana z góry jest przejrzystsza" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:88 +#: Source/diablo_msg.cpp:97 msgid "Salvation comes at the cost of wisdom" msgstr "Zbawienie kosztem mądrości" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:89 +#: Source/diablo_msg.cpp:98 msgid "Mysteries are revealed in the light of reason" msgstr "Mądrość znajdziecie jedynie wśród uczonych" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:90 +#: Source/diablo_msg.cpp:99 msgid "Those who are last may yet be first" msgstr "Ostatni mogą być jeszcze pierwszymi" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:91 +#: Source/diablo_msg.cpp:100 msgid "Generosity brings its own rewards" msgstr "Hojność zostaje nagrodzona" -#: Source/error.cpp:92 +#: Source/diablo_msg.cpp:101 msgid "You must be at least level 8 to use this." msgstr "Dostępne od poziomu 8." -#: Source/error.cpp:93 +#: Source/diablo_msg.cpp:102 msgid "You must be at least level 13 to use this." msgstr "Dostępne od poziomu 13." -#: Source/error.cpp:94 +#: Source/diablo_msg.cpp:103 msgid "You must be at least level 17 to use this." msgstr "Dostępne od poziomu 17." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:95 +#: Source/diablo_msg.cpp:104 msgid "Arcane knowledge gained!" msgstr "Zdobyto tajemną wiedzę!" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:96 +#: Source/diablo_msg.cpp:105 msgid "That which does not kill you..." msgstr "Co cię nie zabije…" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:97 +#: Source/diablo_msg.cpp:106 msgid "Knowledge is power." msgstr "Wiedza to potęga." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:98 +#: Source/diablo_msg.cpp:107 msgid "Give and you shall receive." msgstr "Coś za coś." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:99 +#: Source/diablo_msg.cpp:108 msgid "Some experience is gained by touch." msgstr "Niektórych rzeczy trzeba doświadczyć." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:100 +#: Source/diablo_msg.cpp:109 msgid "There's no place like home." msgstr "Wszędzie dobrze, ale w domu najlepiej." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:101 +#: Source/diablo_msg.cpp:110 msgid "Spiritual energy is restored." msgstr "Energia duchowa została przywrócona." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:102 +#: Source/diablo_msg.cpp:111 msgid "You feel more agile." msgstr "Czujesz przypływ zwinności." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:103 +#: Source/diablo_msg.cpp:112 msgid "You feel stronger." msgstr "Czujesz przypływ sił." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:104 +#: Source/diablo_msg.cpp:113 msgid "You feel wiser." msgstr "Czujesz przypływ mądrości." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:105 +#: Source/diablo_msg.cpp:114 msgid "You feel refreshed." msgstr "Czujesz pobudzenie." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:106 +#: Source/diablo_msg.cpp:115 msgid "That which can break will." msgstr "Co może się zepsuć, na pewno się zepsuje." -#: Source/gamemenu.cpp:35 +#: Source/discord/discord.cpp:73 +msgid "Cathedral" +msgstr "Katedra" + +#: Source/discord/discord.cpp:73 +msgid "Catacombs" +msgstr "Katakumby" + +#: Source/discord/discord.cpp:73 +msgid "Caves" +msgstr "Jaskinie" + +#: Source/discord/discord.cpp:73 +msgid "Nest" +msgstr "Gniazdo" + +#: Source/discord/discord.cpp:73 +msgid "Crypt" +msgstr "Krypta" + +#. TRANSLATORS: dungeon type and floor number i.e. "Cathedral 3" +#: Source/discord/discord.cpp:89 +#, c++-format +msgid "{} {}" +msgstr "{} {}" + +#. TRANSLATORS: Discord character, i.e. "Lv 6 Warrior" +#: Source/discord/discord.cpp:96 +#, c++-format +msgid "Lv {} {}" +msgstr "Poz. {} {}" + +#. TRANSLATORS: Discord state i.e. "Nightmare difficulty" +#: Source/discord/discord.cpp:108 +#, c++-format +msgid "{} difficulty" +msgstr "{} Poziom Trudności" + +#. TRANSLATORS: Discord activity, not in game +#: Source/discord/discord.cpp:189 +msgid "In Menu" +msgstr "W Menu" + +#: Source/dvlnet/loopback.cpp:117 +msgid "loopback" +msgstr "loopback" + +#: Source/dvlnet/tcp_client.cpp:81 +msgid "Unable to connect" +msgstr "Nie udało się połączyć" + +#: Source/dvlnet/tcp_client.cpp:119 +msgid "error: read 0 bytes from server" +msgstr "błąd: wczytano 0 bajtów z serwera" + +#: Source/engine/demomode.cpp:179 Source/options.cpp:679 +msgid "Resolution" +msgstr "Rozdzielczość" + +#: Source/engine/demomode.cpp:181 Source/options.cpp:1045 +msgid "Run in Town" +msgstr "Bieg w mieście" + +#: Source/engine/demomode.cpp:182 Source/options.cpp:1047 +msgid "Theo Quest" +msgstr "Zadanie Theo" + +#: Source/engine/demomode.cpp:183 Source/options.cpp:1048 +msgid "Cow Quest" +msgstr "Zadanie Krowie" + +#: Source/engine/demomode.cpp:184 Source/options.cpp:1058 +msgid "Auto Gold Pickup" +msgstr "Auto podnoszenie złota" + +#: Source/engine/demomode.cpp:185 Source/options.cpp:1059 +msgid "Auto Elixir Pickup" +msgstr "Auto podnoszenie eliksirów" + +#: Source/engine/demomode.cpp:186 Source/options.cpp:1060 +msgid "Auto Oil Pickup" +msgstr "Auto podnoszenie olejów" + +#: Source/engine/demomode.cpp:187 Source/options.cpp:1061 +msgid "Auto Pickup in Town" +msgstr "Auto podnoszenie w mieście" + +#: Source/engine/demomode.cpp:188 Source/options.cpp:1062 +msgid "Adria Refills Mana" +msgstr "Adria Uzupełnia Manę" + +#: Source/engine/demomode.cpp:189 Source/options.cpp:1063 +msgid "Auto Equip Weapons" +msgstr "Automatycznie Załóż Broń" + +#: Source/engine/demomode.cpp:190 Source/options.cpp:1064 +msgid "Auto Equip Armor" +msgstr "Automatycznie Załóż Pancerz" + +#: Source/engine/demomode.cpp:191 Source/options.cpp:1065 +msgid "Auto Equip Helms" +msgstr "Automatycznie Załóż Hełmy" + +#: Source/engine/demomode.cpp:192 Source/options.cpp:1066 +msgid "Auto Equip Shields" +msgstr "Automatycznie Załóż Tarcze" + +#: Source/engine/demomode.cpp:193 Source/options.cpp:1067 +msgid "Auto Equip Jewelry" +msgstr "Automatycznie Załóż Biżuterię" + +#: Source/engine/demomode.cpp:194 Source/options.cpp:1068 +msgid "Randomize Quests" +msgstr "Losuj Zadania" + +#: Source/engine/demomode.cpp:195 Source/options.cpp:1070 +msgid "Show Item Labels" +msgstr "Pokaż etykiety przedmiotów" + +#: Source/engine/demomode.cpp:196 Source/options.cpp:1071 +msgid "Auto Refill Belt" +msgstr "Automatycznie uzupełnij pas" + +#: Source/engine/demomode.cpp:197 Source/options.cpp:1072 +msgid "Disable Crippling Shrines" +msgstr "Wyłącz kaleczące kapliczki" + +#: Source/engine/demomode.cpp:201 Source/options.cpp:1074 +msgid "Heal Potion Pickup" +msgstr "Podnoszenie mikstur Leczenia" + +#: Source/engine/demomode.cpp:202 Source/options.cpp:1075 +msgid "Full Heal Potion Pickup" +msgstr "Podnoszenie mikstur Pełn. Leczenia" + +#: Source/engine/demomode.cpp:203 Source/options.cpp:1076 +msgid "Mana Potion Pickup" +msgstr "Podnoszenie mikstur Many" + +#: Source/engine/demomode.cpp:204 Source/options.cpp:1077 +msgid "Full Mana Potion Pickup" +msgstr "Podnoszenie mikstur Pełnej Many" + +#: Source/engine/demomode.cpp:205 Source/options.cpp:1078 +msgid "Rejuvenation Potion Pickup" +msgstr "Podnoszenie mikstur Wzmocnienia" + +#: Source/engine/demomode.cpp:206 Source/options.cpp:1079 +msgid "Full Rejuvenation Potion Pickup" +msgstr "Podnoszenie mikstur Pełn. Wzmocnienia" + +#: Source/gamemenu.cpp:42 msgid "Save Game" msgstr "Zapisz Grę" -#: Source/gamemenu.cpp:36 Source/gamemenu.cpp:47 +#: Source/gamemenu.cpp:43 Source/gamemenu.cpp:54 msgid "Options" msgstr "Opcje" -#: Source/gamemenu.cpp:39 Source/gamemenu.cpp:50 +#: Source/gamemenu.cpp:46 Source/gamemenu.cpp:57 msgid "Quit Game" msgstr "Wyjdź z Gry" -#: Source/gamemenu.cpp:49 +#: Source/gamemenu.cpp:56 msgid "Restart In Town" msgstr "Kontynuuj rozgrywkę" -#: Source/gamemenu.cpp:59 +#: Source/gamemenu.cpp:66 msgid "Gamma" msgstr "Jasność" -#: Source/gamemenu.cpp:60 Source/gamemenu.cpp:167 +#: Source/gamemenu.cpp:67 Source/gamemenu.cpp:176 msgid "Speed" msgstr "Tempo" -#: Source/gamemenu.cpp:68 +#: Source/gamemenu.cpp:75 msgid "Music Disabled" msgstr "Muzyka Wyłączona" -#: Source/gamemenu.cpp:72 +#: Source/gamemenu.cpp:79 msgid "Sound" msgstr "Dźwięk" -#: Source/gamemenu.cpp:73 +#: Source/gamemenu.cpp:80 msgid "Sound Disabled" msgstr "Dźwięk Wyłączony" -#: Source/gmenu.cpp:168 +#: Source/gmenu.cpp:178 msgid "Pause" msgstr "Pauza" -#: Source/help.cpp:27 +#: Source/help.cpp:28 msgid "$Keyboard Shortcuts:" msgstr "$Skróty Klawiszowe:" -#: Source/help.cpp:28 +#: Source/help.cpp:29 msgid "F1: Open Help Screen" msgstr "F1: Wyświetl okno pomocy" -#: Source/help.cpp:29 +#: Source/help.cpp:30 msgid "Esc: Display Main Menu" msgstr "Esc: Wyświetl menu główne" -#: Source/help.cpp:30 +#: Source/help.cpp:31 msgid "Tab: Display Auto-map" msgstr "Tab: Wyświetl mapę" -#: Source/help.cpp:31 +#: Source/help.cpp:32 msgid "Space: Hide all info screens" msgstr "Space: Ukryj wszystkie okna" -#: Source/help.cpp:32 +#: Source/help.cpp:33 msgid "S: Open Speedbook" msgstr "S: Wyświetla podręczną księgę" -#: Source/help.cpp:33 +#: Source/help.cpp:34 msgid "B: Open Spellbook" msgstr "B: Wyświetla księgę zaklęć" -#: Source/help.cpp:34 +#: Source/help.cpp:35 msgid "I: Open Inventory screen" msgstr "I: Wyświetla ekwipunek" -#: Source/help.cpp:35 +#: Source/help.cpp:36 msgid "C: Open Character screen" msgstr "C: Wyświetla panel postaci" -#: Source/help.cpp:36 +#: Source/help.cpp:37 msgid "Q: Open Quest log" msgstr "Q: Wyświetla dziennik zadań" -#: Source/help.cpp:37 +#: Source/help.cpp:38 msgid "F: Reduce screen brightness" msgstr "F: Zmniejsza jasność ekranu" -#: Source/help.cpp:38 +#: Source/help.cpp:39 msgid "G: Increase screen brightness" msgstr "G: Zwiększa jasność ekranu" -#: Source/help.cpp:39 +#: Source/help.cpp:40 msgid "Z: Zoom Game Screen" msgstr "Z: Przybliżenie ekranu" -#: Source/help.cpp:40 +#: Source/help.cpp:41 msgid "+ / -: Zoom Automap" msgstr "+ / -: Skalowanie mapy" -#: Source/help.cpp:41 +#: Source/help.cpp:42 msgid "1 - 8: Use Belt item" msgstr "1 - 8: Sięgnięcie po przedmiot z pasa" -#: Source/help.cpp:42 +#: Source/help.cpp:43 msgid "F5, F6, F7, F8: Set hotkey for skill or spell" msgstr "F5, F6, F7, F8: Ustawianie skrótu klawiszowego umiejętności" -#: Source/help.cpp:43 +#: Source/help.cpp:44 msgid "Shift + Left Mouse Button: Attack without moving" msgstr "Shift + LPM: Atak w miejscu" -#: Source/help.cpp:44 +#: Source/help.cpp:45 msgid "Shift + Left Mouse Button (on character screen): Assign all stat points" msgstr "Shift + LPM (na panelu postaci): Przydziel wszystkie punkty" -#: Source/help.cpp:45 +#: Source/help.cpp:46 msgid "" "Shift + Left Mouse Button (on inventory): Move item to belt or equip/unequip " "item" msgstr "" "Shift + LPM (w ekwipunku): Przenieś na pas lub przygotuj/schowaj przedmiot" -#: Source/help.cpp:46 +#: Source/help.cpp:47 msgid "Shift + Left Mouse Button (on belt): Move item to inventory" msgstr "Shift + LPM (na pasie): Przenieś przedmiot do ekwipunku" -#: Source/help.cpp:48 +#: Source/help.cpp:49 msgid "$Movement:" msgstr "$Poruszanie się:" -#: Source/help.cpp:49 +#: Source/help.cpp:50 msgid "" "If you hold the mouse button down while moving, the character will continue " "to move in that direction." msgstr "" "Wciśnięty Lewy Przycisk Myszy wskazuje kierunek, w który ma podążać postać." -#: Source/help.cpp:52 +#: Source/help.cpp:53 msgid "$Combat:" msgstr "$Walka:" -#: Source/help.cpp:53 +#: Source/help.cpp:54 msgid "" "Holding down the shift key and then left-clicking allows the character to " "attack without moving." @@ -1886,11 +2394,11 @@ msgstr "" "Wciśnięcie Shift powoduje, że po kliknięciu postać atakuje bez poruszania " "się." -#: Source/help.cpp:56 +#: Source/help.cpp:57 msgid "$Auto-map:" msgstr "$Mapa:" -#: Source/help.cpp:57 +#: Source/help.cpp:58 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 " @@ -1900,11 +2408,11 @@ msgstr "" "przycisk 'TAB' na klawiaturze. Przyciski + oraz - umożliwiają jej " "powiększanie i oddalanie. Klawiszami strzałek można ją również przemieszczać." -#: Source/help.cpp:62 +#: Source/help.cpp:63 msgid "$Picking up Objects:" msgstr "$Podnoszenie Obiektów:" -#: Source/help.cpp:63 +#: Source/help.cpp:64 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 " @@ -1917,21 +2425,21 @@ msgstr "" "wciskając na klawiaturze numer, który się przy nich pojawia lub klikając na " "nie Prawym Przyciskiem Myszy." -#: Source/help.cpp:69 +#: Source/help.cpp:70 msgid "$Gold:" msgstr "$Złoto:" -#: Source/help.cpp:70 +#: Source/help.cpp:71 msgid "" "You can select a specific amount of gold to drop by right-clicking on a pile " "of gold in your inventory." msgstr "Kliknięcie PPM na złoto w ekwipunku umożliwia jego rozdzielenie." -#: Source/help.cpp:73 +#: Source/help.cpp:74 msgid "$Skills & Spells:" msgstr "$Umiejętności i Czary:" -#: Source/help.cpp:74 +#: Source/help.cpp:75 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 " @@ -1944,11 +2452,11 @@ msgstr "" "ono aktywne i gotowe do użycia. Wtedy, kliknięcie PPM na danym terenie, " "umożliwia rzucenie go." -#: Source/help.cpp:80 +#: Source/help.cpp:81 msgid "$Using the Speedbook for Spells:" msgstr "$Użycie podręcznej księgi czarów:" -#: Source/help.cpp:81 +#: Source/help.cpp:82 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 " @@ -1957,7 +2465,7 @@ msgstr "" "Wciśnięcie LPM na 'przygotowany czar' otworzy 'podręczną księgę' pozwalającą " "wybrać umiejętność. By ją użyć wciśnij PPM na główne okno rozgrywki." -#: Source/help.cpp:85 +#: Source/help.cpp:86 msgid "" "Shift + Left-clicking on the 'select current spell' button will clear the " "readied spell." @@ -1965,11 +2473,11 @@ msgstr "" "Shift + kliknięcie lewym przyciskiem myszy na przycisku \"wybierz bieżące " "zaklęcie\" spowoduje usunięcie przygotowanego zaklęcia." -#: Source/help.cpp:87 +#: Source/help.cpp:88 msgid "$Setting Spell Hotkeys:" msgstr "$Ustawianie skrótów klawiszowych umiejętności:" -#: Source/help.cpp:88 +#: Source/help.cpp:89 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, " @@ -1979,11 +2487,11 @@ msgstr "" "klawiszem S najedź myszką na wybrany czar. Następnie naciśnij klawisz F5, " "F6, F7 albo F8, aby przypisać go do wybranej umiejętności." -#: Source/help.cpp:93 +#: Source/help.cpp:94 msgid "$Spell Books:" msgstr "$Księgi czarów:" -#: Source/help.cpp:94 +#: Source/help.cpp:95 msgid "" "Reading more than one book increases your knowledge of that spell, allowing " "you to cast the spell more effectively." @@ -1991,35 +2499,35 @@ msgstr "" "Czytanie wielu ksiąg zwiększa twą wiedzę o czarze, dzięki czemu może być ono " "jeszcze skuteczniejsze." -#: Source/help.cpp:177 +#: Source/help.cpp:200 msgid "Shareware Hellfire Help" msgstr "Pomoc Wersji Shareware Hellfire" -#: Source/help.cpp:177 +#: Source/help.cpp:200 msgid "Hellfire Help" msgstr "Pomoc Hellfire" -#: Source/help.cpp:179 +#: Source/help.cpp:202 msgid "Shareware Diablo Help" msgstr "Pomoc Wersji Shareware Diablo" -#: Source/help.cpp:179 +#: Source/help.cpp:202 msgid "Diablo Help" msgstr "Pomoc Diablo" -#: Source/help.cpp:209 Source/qol/chatlog.cpp:180 +#: Source/help.cpp:234 Source/qol/chatlog.cpp:200 msgid "Press ESC to end or the arrow keys to scroll." msgstr "ESC - wyjście, Strzałki - przewijanie" -#: Source/init.cpp:194 +#: Source/init.cpp:298 Source/init.cpp:338 msgid "diabdat.mpq or spawn.mpq" msgstr "diabdat.mpq lub spawn.mpq" -#: Source/init.cpp:215 +#: Source/init.cpp:319 Source/init.cpp:359 msgid "Some Hellfire MPQs are missing" msgstr "Brakuje części plików MPQ do Hellfire" -#: Source/init.cpp:215 +#: Source/init.cpp:319 Source/init.cpp:359 msgid "" "Not all Hellfire MPQs were found.\n" "Please copy all the hf*.mpq files." @@ -2027,9479 +2535,9531 @@ msgstr "" "Nie znaleziono wszystkich plików MPQ do Hellfire.\n" "Skopiuj wszystkie pliki hf*.mpq do folderu." -#: Source/init.cpp:223 +#: Source/init.cpp:368 msgid "Unable to create main window" msgstr "Nie udało się utworzyć głównego okna" -#: Source/itemdat.cpp:53 Source/itemdat.cpp:235 Source/panels/charpanel.cpp:164 -msgid "Gold" -msgstr "Złoto" +#: Source/inv.cpp:2118 +msgid "No room for item" +msgstr "Brak miejsca na przedmiot" -#: Source/itemdat.cpp:54 Source/itemdat.cpp:172 -msgid "Short Sword" -msgstr "Krótki Miecz" +#: Source/items.cpp:174 Source/translation_dummy.cpp:360 +msgid "Oil of Accuracy" +msgstr "Olej Precyzji" -#: Source/itemdat.cpp:55 Source/itemdat.cpp:124 -msgid "Buckler" -msgstr "Puklerz" +#: Source/items.cpp:175 +msgid "Oil of Mastery" +msgstr "Olej Opanowania" -#: Source/itemdat.cpp:56 Source/itemdat.cpp:192 Source/itemdat.cpp:193 -msgid "Club" -msgstr "Maczuga" +#: Source/items.cpp:176 Source/translation_dummy.cpp:361 +msgid "Oil of Sharpness" +msgstr "Olej Wyostrzenia" -#: Source/itemdat.cpp:57 Source/itemdat.cpp:196 -msgid "Short Bow" -msgstr "Krótki Łuk" +#: Source/items.cpp:177 +msgid "Oil of Death" +msgstr "Olej Śmierci" -#: Source/itemdat.cpp:58 -msgid "Short Staff of Mana" -msgstr "Kostur Many" +#: Source/items.cpp:178 +msgid "Oil of Skill" +msgstr "Olej Umiejętności" -#: Source/itemdat.cpp:59 -msgid "Cleaver" -msgstr "Tasak" +#: Source/items.cpp:179 Source/translation_dummy.cpp:282 +#: Source/translation_dummy.cpp:359 +msgid "Blacksmith Oil" +msgstr "Olej Rzemieślnika" -#: Source/itemdat.cpp:60 Source/itemdat.cpp:434 -msgid "The Undead Crown" -msgstr "Korona Nieumarłych" +#: Source/items.cpp:180 +msgid "Oil of Fortitude" +msgstr "Olej Wytrzymałości" -#: Source/itemdat.cpp:61 Source/itemdat.cpp:435 -msgid "Empyrean Band" -msgstr "Pierścień Niebios" +#: Source/items.cpp:181 +msgid "Oil of Permanence" +msgstr "Olej Niezniszczalności" -#: Source/itemdat.cpp:62 -msgid "Magic Rock" -msgstr "Magiczny Kamień" +#: Source/items.cpp:182 +msgid "Oil of Hardening" +msgstr "Olej Wzmocnienia" -#: Source/itemdat.cpp:63 Source/itemdat.cpp:436 -msgid "Optic Amulet" -msgstr "Optyczny Amulet" +#: Source/items.cpp:183 +msgid "Oil of Imperviousness" +msgstr "Olej Nieprzenikalności" -#: Source/itemdat.cpp:64 Source/itemdat.cpp:437 -msgid "Ring of Truth" -msgstr "Pierścień Prawdy" +#. TRANSLATORS: Constructs item names. Format: {Item} of {Spell}. Example: War Staff of Firewall +#: Source/items.cpp:1112 +#, c++-format +msgctxt "spell" +msgid "{0} of {1}" +msgstr "{0} {1}" -#: Source/itemdat.cpp:65 -msgid "Tavern Sign" -msgstr "Szyld Gospody" +#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Spell}. Example: King's War Staff of Firewall +#: Source/items.cpp:1124 +#, c++-format +msgctxt "spell" +msgid "{0} {1} of {2}" +msgstr "{0} {1} {2}" -#: Source/itemdat.cpp:66 Source/itemdat.cpp:438 -msgid "Harlequin Crest" -msgstr "Przyodziewek Arlekina" +#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Suffix}. Example: King's Long Sword of the Whale +#: Source/items.cpp:1162 +#, c++-format +msgid "{0} {1} of {2}" +msgstr "{0} {1} {2}" -#: Source/itemdat.cpp:67 Source/itemdat.cpp:439 -msgid "Veil of Steel" -msgstr "Stalowy Woal" +#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item}. Example: King's Long Sword +#: Source/items.cpp:1165 +#, c++-format +msgid "{0} {1}" +msgstr "{0} {1}" -#: Source/itemdat.cpp:68 -msgid "Golden Elixir" -msgstr "Złoty Eliksir" +#. TRANSLATORS: Constructs item names. Format: {Item} of {Suffix}. Example: Long Sword of the Whale +#: Source/items.cpp:1168 +#, c++-format +msgid "{0} of {1}" +msgstr "{0} {1}" -#: Source/itemdat.cpp:69 Source/quests.cpp:54 -msgid "Anvil of Fury" -msgstr "Kowadło Gniewu" +#: Source/items.cpp:1699 Source/items.cpp:1707 +msgid "increases a weapon's" +msgstr "znacznie zwiększa" -#: Source/itemdat.cpp:70 Source/quests.cpp:45 -msgid "Black Mushroom" -msgstr "Czarny Grzyb" +#: Source/items.cpp:1700 +msgid "chance to hit" +msgstr "celność broni" -#: Source/itemdat.cpp:71 -msgid "Brain" -msgstr "Mózg" +#: Source/items.cpp:1703 +msgid "greatly increases a" +msgstr "znacznie zwiększa" -#: Source/itemdat.cpp:72 -msgid "Fungal Tome" -msgstr "Atlas Grzybów" +#: Source/items.cpp:1704 +msgid "weapon's chance to hit" +msgstr "celność broni" -#: Source/itemdat.cpp:73 -msgid "Spectral Elixir" -msgstr "Widmowy Eliksir" +#: Source/items.cpp:1708 +msgid "damage potential" +msgstr "możliwe obrażenia" -#: Source/itemdat.cpp:74 -msgid "Blood Stone" -msgstr "Kamień Krwi" +#: Source/items.cpp:1711 +msgid "greatly increases a weapon's" +msgstr "znacznie zwiększa" -#: Source/itemdat.cpp:75 -msgid "Cathedral Map" -msgstr "Mapa Katedry" +#: Source/items.cpp:1712 +msgid "damage potential - not bows" +msgstr "możliwe obrażenia (bez łuków)" -#: Source/itemdat.cpp:76 -msgid "Heart" -msgstr "Serce" +#: Source/items.cpp:1715 +msgid "reduces attributes needed" +msgstr "zmniejsza wymagane atrybuty" -#: Source/itemdat.cpp:77 Source/itemdat.cpp:130 -msgid "Potion of Healing" -msgstr "Mikstura Leczenia" +#: Source/items.cpp:1716 +msgid "to use armor or weapons" +msgstr "i pozwala używać przedmioty" -#: Source/itemdat.cpp:78 Source/itemdat.cpp:132 -msgid "Potion of Mana" -msgstr "Mikstura Many" +#: Source/items.cpp:1719 +#, no-c-format +msgid "restores 20% of an" +msgstr "przywraca 20%" -#: Source/itemdat.cpp:79 Source/itemdat.cpp:147 -msgid "Scroll of Identify" -msgstr "Zwój Identyfikacji" +#: Source/items.cpp:1720 +msgid "item's durability" +msgstr "wytrzymałości przedmiotu" -#: Source/itemdat.cpp:80 Source/itemdat.cpp:151 -msgid "Scroll of Town Portal" -msgstr "Zwój Miejskiego Portalu" +#: Source/items.cpp:1723 +msgid "increases an item's" +msgstr "zwiększa" -#: Source/itemdat.cpp:81 Source/itemdat.cpp:440 -msgid "Arkaine's Valor" -msgstr "Odwaga Arkaina" +#: Source/items.cpp:1724 +msgid "current and max durability" +msgstr "bieżącą i maks. wytrzymałość" -#: Source/itemdat.cpp:82 Source/itemdat.cpp:131 -msgid "Potion of Full Healing" -msgstr "Mikstura Pełnego Leczenia" +#: Source/items.cpp:1727 +msgid "makes an item indestructible" +msgstr "czyni rzecz niezniszczalną" -#: Source/itemdat.cpp:83 Source/itemdat.cpp:133 -msgid "Potion of Full Mana" -msgstr "Mikstura Pełnej Many" +#: Source/items.cpp:1730 +msgid "increases the armor class" +msgstr "zwiększa klasę pancerza" -#: Source/itemdat.cpp:84 Source/itemdat.cpp:441 -msgid "Griswold's Edge" -msgstr "Ostrze Griswolda" +#: Source/items.cpp:1731 +msgid "of armor and shields" +msgstr "tarcz i zbroi" -#: Source/itemdat.cpp:85 Source/itemdat.cpp:442 -msgid "Bovine Plate" -msgstr "Byczy Pancerz" +#: Source/items.cpp:1734 +msgid "greatly increases the armor" +msgstr "znacznie zwiększa pancerz" -#: Source/itemdat.cpp:86 -msgid "Staff of Lazarus" -msgstr "Kostur Lazarusa" +#: Source/items.cpp:1735 +msgid "class of armor and shields" +msgstr "klasę tarcz i zbroi" -#: Source/itemdat.cpp:87 Source/itemdat.cpp:148 -msgid "Scroll of Resurrect" -msgstr "Zwój Wskrzeszenia" +#: Source/items.cpp:1738 Source/items.cpp:1745 +msgid "sets fire trap" +msgstr "ustawia ognistą pułapkę" -#: Source/itemdat.cpp:88 Source/itemdat.cpp:136 Source/items.cpp:172 -msgid "Blacksmith Oil" -msgstr "Olej Rzemieślnika" +#: Source/items.cpp:1742 +msgid "sets lightning trap" +msgstr "ustawia pułapkę z błyskawic" -#: Source/itemdat.cpp:89 Source/itemdat.cpp:204 -msgid "Short Staff" -msgstr "Krótki Kostur" +#: Source/items.cpp:1748 +msgid "sets petrification trap" +msgstr "ustawia pułapkę Petryfikacji" -#: 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 "Miecz" +#: Source/items.cpp:1751 +msgid "restore all life" +msgstr "odnawia całe życie" -#: Source/itemdat.cpp:91 Source/itemdat.cpp:171 -msgid "Dagger" -msgstr "Sztylet" +#: Source/items.cpp:1754 +msgid "restore some life" +msgstr "odnawia część życia" -#: Source/itemdat.cpp:92 -msgid "Rune Bomb" -msgstr "Magiczna Bomba" +#: Source/items.cpp:1757 +msgid "restore some mana" +msgstr "odnawia część many" -#: Source/itemdat.cpp:93 -msgid "Theodore" -msgstr "Teodor" +#: Source/items.cpp:1760 +msgid "restore all mana" +msgstr "odnawia całą manę" -#: Source/itemdat.cpp:94 -msgid "Auric Amulet" -msgstr "Amulet Dobrobytu" +#: Source/items.cpp:1763 +msgid "increase strength" +msgstr "zwiększa siłę" -#: Source/itemdat.cpp:95 -msgid "Torn Note 1" -msgstr "Podarta Notatka, cz. 1" +#: Source/items.cpp:1766 +msgid "increase magic" +msgstr "zwiększa magię" -#: Source/itemdat.cpp:96 -msgid "Torn Note 2" -msgstr "Podarta Notatka, cz. 2" +#: Source/items.cpp:1769 +msgid "increase dexterity" +msgstr "zwiększa zręczność" -#: Source/itemdat.cpp:97 -msgid "Torn Note 3" -msgstr "Podarta Notatka, cz. 3" +#: Source/items.cpp:1772 +msgid "increase vitality" +msgstr "zwiększa żywotność" -#: Source/itemdat.cpp:98 -msgid "Reconstructed Note" -msgstr "Odtworzona Notatka" +#: Source/items.cpp:1775 +msgid "restore some life and mana" +msgstr "odnawia część życia i many" -#: Source/itemdat.cpp:99 -msgid "Brown Suit" -msgstr "Brązowy Strój" +#: Source/items.cpp:1778 Source/items.cpp:1781 +msgid "restore all life and mana" +msgstr "odnawia całe życie i manę" -#: Source/itemdat.cpp:100 -msgid "Grey Suit" -msgstr "Szary Strój" +#: Source/items.cpp:1782 +msgid "(works only in arenas)" +msgstr "(działa tylko na arenach)" -#: Source/itemdat.cpp:101 Source/itemdat.cpp:102 -msgid "Cap" -msgstr "Kaptur" +#: Source/items.cpp:1796 +msgid "Right-click to view" +msgstr "PPM by wyświetlić" -#: Source/itemdat.cpp:102 -msgid "Skull Cap" -msgstr "Szyszak" +#: Source/items.cpp:1799 +msgid "Right-click to use" +msgstr "PPM by użyć" -#: Source/itemdat.cpp:103 Source/itemdat.cpp:104 Source/itemdat.cpp:106 -msgid "Helm" -msgstr "Hełm" +#: Source/items.cpp:1801 +msgid "" +"Right-click to read, then\n" +"left-click to target" +msgstr "" +"PPM by odczytać, następnie\n" +"LPM na cel" -#: Source/itemdat.cpp:104 -msgid "Full Helm" -msgstr "Zamknięty Hełm" +#: Source/items.cpp:1803 +msgid "Right-click to read" +msgstr "PPM by odczytać" -#: Source/itemdat.cpp:105 -msgid "Crown" -msgstr "Korona" +#: Source/items.cpp:1810 +msgid "Activate to view" +msgstr "Aktywuj, aby wyświetlić" -#: Source/itemdat.cpp:106 -msgid "Great Helm" -msgstr "Wielki Hełm" +#: Source/items.cpp:1814 Source/items.cpp:1852 +msgid "Open inventory to use" +msgstr "Otwórz ekwipunek, aby użyć" -#: Source/itemdat.cpp:107 -msgid "Cape" -msgstr "Peleryna" +#: Source/items.cpp:1816 +msgid "Activate to use" +msgstr "Aktywuj, aby użyć" -#: Source/itemdat.cpp:108 -msgid "Rags" -msgstr "Łach" +#: Source/items.cpp:1819 +msgid "" +"Select from spell book, then\n" +"cast spell to read" +msgstr "" +"Wybierz z księgi zaklęć, a następnie\n" +"rzuć zaklęcie, aby przeczytać" -#: Source/itemdat.cpp:109 -msgid "Cloak" -msgstr "Szata" +#: Source/items.cpp:1821 +msgid "Activate to read" +msgstr "Aktywuj, aby przeczytać" -#: Source/itemdat.cpp:110 -msgid "Robe" -msgstr "Habit" +#: Source/items.cpp:1848 +#, c++-format +msgid "{} to view" +msgstr "{} aby wyświetlić" -#: Source/itemdat.cpp:111 -msgid "Quilted Armor" -msgstr "Pikowana Zbroja" +#: Source/items.cpp:1854 +#, c++-format +msgid "{} to use" +msgstr "{} aby użyć" -#: Source/itemdat.cpp:111 Source/itemdat.cpp:112 Source/itemdat.cpp:113 -#: Source/itemdat.cpp:114 Source/objects.cpp:5476 -msgid "Armor" -msgstr "Zbroja" +#: Source/items.cpp:1857 +#, c++-format +msgid "" +"Select from spell book,\n" +"then {} to read" +msgstr "" +"Wybierz z księgi zaklęć,\n" +"a następnie {} aby przeczytać" -#: Source/itemdat.cpp:112 -msgid "Leather Armor" -msgstr "Skórzana Zbroja" +#: Source/items.cpp:1859 +#, c++-format +msgid "{} to read" +msgstr "{} aby przeczytać" -#: Source/itemdat.cpp:113 -msgid "Hard Leather Armor" -msgstr "Ciężka Skórzana Zbroja" +#: Source/items.cpp:1866 +#, c++-format +msgctxt "player" +msgid "Level: {:d}" +msgstr "Poziom: {:d}" -#: Source/itemdat.cpp:114 -msgid "Studded Leather Armor" -msgstr "Ćwiekowana Zbroja" +#: Source/items.cpp:1870 +msgid "Doubles gold capacity" +msgstr "Podwaja objętość sakwy" -#: Source/itemdat.cpp:115 -msgid "Ring Mail" -msgstr "Pierścieniowa Zbroja" +#: Source/items.cpp:1902 Source/stores.cpp:325 +msgid "Required:" +msgstr "Wymagania:" -#: Source/itemdat.cpp:115 Source/itemdat.cpp:116 Source/itemdat.cpp:117 -#: Source/itemdat.cpp:119 -msgid "Mail" -msgstr "Zbroja" +#: Source/items.cpp:1904 Source/stores.cpp:327 +#, c++-format +msgid " {:d} Str" +msgstr " {:d} Siły" -#: Source/itemdat.cpp:116 -msgid "Chain Mail" -msgstr "Kolczuga" +#: Source/items.cpp:1906 Source/stores.cpp:329 +#, c++-format +msgid " {:d} Mag" +msgstr " {:d} Magii" -#: Source/itemdat.cpp:117 -msgid "Scale Mail" -msgstr "Łuskowa Zbroja" +#: Source/items.cpp:1908 Source/stores.cpp:331 +#, c++-format +msgid " {:d} Dex" +msgstr " {:d} Zręcz." -#: Source/itemdat.cpp:118 -msgid "Breast Plate" -msgstr "Napierśnik" +#. TRANSLATORS: {:s} will be a Character Name +#: Source/items.cpp:2283 +#, c++-format +msgid "Ear of {:s}" +msgstr "Ucho ({:s})" -#: Source/itemdat.cpp:118 Source/itemdat.cpp:120 Source/itemdat.cpp:121 -#: Source/itemdat.cpp:122 Source/itemdat.cpp:123 -msgid "Plate" -msgstr "Zbroja" +#: Source/items.cpp:3673 +#, c++-format +msgid "chance to hit: {:+d}%" +msgstr "celność: {:+d}%" -#: Source/itemdat.cpp:119 -msgid "Splint Mail" -msgstr "Karacenowa Zbroja" +#: Source/items.cpp:3676 +#, no-c-format, c++-format +msgid "{:+d}% damage" +msgstr "{:+d}% do obrażeń" -#: Source/itemdat.cpp:120 -msgid "Plate Mail" -msgstr "Płytowa Zbroja" +#: Source/items.cpp:3679 Source/items.cpp:3863 +#, c++-format +msgid "to hit: {:+d}%, {:+d}% damage" +msgstr "celność: {:+d}%, obrażenia: {:+d}%" -#: Source/itemdat.cpp:121 -msgid "Field Plate" -msgstr "Turniejowa Zbroja" +#: Source/items.cpp:3682 +#, no-c-format, c++-format +msgid "{:+d}% armor" +msgstr "{:+d}% do obrony" -#: Source/itemdat.cpp:122 -msgid "Gothic Plate" -msgstr "Gotycka Płytowa Zbroja" +#: Source/items.cpp:3685 +#, c++-format +msgid "armor class: {:d}" +msgstr "klasa pancerza: {:d}" -#: Source/itemdat.cpp:123 -msgid "Full Plate Mail" -msgstr "Pełna Płytowa Zbroja" +#: Source/items.cpp:3689 +#, c++-format +msgid "Resist Fire: {:+d}%" +msgstr "Odporność na Ogień {:+d}%" -#: 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 "Tarcza" +#: Source/items.cpp:3691 +#, c++-format +msgid "Resist Fire: {:+d}% MAX" +msgstr "Odporność na Ogień {:+d}% MAX" -#: Source/itemdat.cpp:125 -msgid "Small Shield" -msgstr "Mała Tarcza" +#: Source/items.cpp:3695 +#, c++-format +msgid "Resist Lightning: {:+d}%" +msgstr "Odp. na Błyskawice {:+d}%" -#: Source/itemdat.cpp:126 -msgid "Large Shield" -msgstr "Duża Tarcza" +#: Source/items.cpp:3697 +#, c++-format +msgid "Resist Lightning: {:+d}% MAX" +msgstr "Odp. na Błyskawice {:+d}% MAX" -#: Source/itemdat.cpp:127 -msgid "Kite Shield" -msgstr "Trójkątna Tarcza" +#: Source/items.cpp:3701 +#, c++-format +msgid "Resist Magic: {:+d}%" +msgstr "Odporność na Magię {:+d}%" -#: Source/itemdat.cpp:128 -msgid "Tower Shield" -msgstr "Ciężka Tarcza" +#: Source/items.cpp:3703 +#, c++-format +msgid "Resist Magic: {:+d}% MAX" +msgstr "Odporność na Magię {:+d}% MAX" -#: Source/itemdat.cpp:129 -msgid "Gothic Shield" -msgstr "Gotycka Tarcza" +#: Source/items.cpp:3706 +#, c++-format +msgid "Resist All: {:+d}%" +msgstr "Odporność na wszystko: {:+d}%" -#: Source/itemdat.cpp:134 -msgid "Potion of Rejuvenation" -msgstr "Mikstura Wzmocnienia" +#: Source/items.cpp:3708 +#, c++-format +msgid "Resist All: {:+d}% MAX" +msgstr "Odporność na wszystko {:+d}% MAX" -#: Source/itemdat.cpp:135 -msgid "Potion of Full Rejuvenation" -msgstr "Mikstura Pełnego Wzmocnienia" +#: Source/items.cpp:3711 +#, c++-format +msgid "spells are increased {:d} level" +msgid_plural "spells are increased {:d} levels" +msgstr[0] "poziom czarów: {:d}" +msgstr[1] "poziom czarów: {:d}" +msgstr[2] "poziom czarów: {:d}" -#: Source/itemdat.cpp:137 Source/items.cpp:167 -msgid "Oil of Accuracy" -msgstr "Olej Precyzji" +#: Source/items.cpp:3713 +#, c++-format +msgid "spells are decreased {:d} level" +msgid_plural "spells are decreased {:d} levels" +msgstr[0] "poziom czarów: {:d}" +msgstr[1] "poziom czarów: {:d}" +msgstr[2] "poziom czarów: {:d}" -#: Source/itemdat.cpp:138 Source/items.cpp:169 -msgid "Oil of Sharpness" -msgstr "Olej Wyostrzenia" +#: Source/items.cpp:3715 +msgid "spell levels unchanged (?)" +msgstr "poziom czarów bez zmian (?)" -#: Source/itemdat.cpp:139 -msgid "Oil" -msgstr "Olej" +#: Source/items.cpp:3717 +msgid "Extra charges" +msgstr "Dodatkowe ładunki" -#: Source/itemdat.cpp:140 -msgid "Elixir of Strength" -msgstr "Eliksir Siły" +#: Source/items.cpp:3719 +#, c++-format +msgid "{:d} {:s} charge" +msgid_plural "{:d} {:s} charges" +msgstr[0] "Ładunki: {:d} {:s}" +msgstr[1] "Ładunki: {:d} {:s}" +msgstr[2] "Ładunki: {:d} {:s}" -#: Source/itemdat.cpp:141 -msgid "Elixir of Magic" -msgstr "Eliksir Magii" +#: Source/items.cpp:3722 +#, c++-format +msgid "Fire hit damage: {:d}" +msgstr "Obr. od ognia: {:d}" -#: Source/itemdat.cpp:142 -msgid "Elixir of Dexterity" -msgstr "Eliksir Zręczności" +#: Source/items.cpp:3724 +#, c++-format +msgid "Fire hit damage: {:d}-{:d}" +msgstr "Obr. od ognia: {:d}-{:d}" -#: Source/itemdat.cpp:143 -msgid "Elixir of Vitality" -msgstr "Eliksir Żywotności" +#: Source/items.cpp:3727 +#, c++-format +msgid "Lightning hit damage: {:d}" +msgstr "Obr. od błyskawic: {:d}" -#: Source/itemdat.cpp:144 -msgid "Scroll of Healing" -msgstr "Zwój Uzdrowienia" +#: Source/items.cpp:3729 +#, c++-format +msgid "Lightning hit damage: {:d}-{:d}" +msgstr "Obr. od błyskawic: {:d}-{:d}" -#: Source/itemdat.cpp:145 -msgid "Scroll of Search" -msgstr "Zwój Przeszukiwania" +#: Source/items.cpp:3732 +#, c++-format +msgid "{:+d} to strength" +msgstr "{:+d} do siły" -#: Source/itemdat.cpp:146 -msgid "Scroll of Lightning" -msgstr "Zwój Błyskawic" +#: Source/items.cpp:3735 +#, c++-format +msgid "{:+d} to magic" +msgstr "{:+d} do magii" -#: Source/itemdat.cpp:149 -msgid "Scroll of Fire Wall" -msgstr "Zwój Ściany Ognia" +#: Source/items.cpp:3738 +#, c++-format +msgid "{:+d} to dexterity" +msgstr "{:+d} do zręczności" -#: Source/itemdat.cpp:150 -msgid "Scroll of Inferno" -msgstr "Zwój Inferno" +#: Source/items.cpp:3741 +#, c++-format +msgid "{:+d} to vitality" +msgstr "{:+d} do żywotności" -#: Source/itemdat.cpp:152 -msgid "Scroll of Flash" -msgstr "Zwój Rozbłysku" +#: Source/items.cpp:3744 +#, c++-format +msgid "{:+d} to all attributes" +msgstr "{:+d} do wszystkich atrybutów" -#: Source/itemdat.cpp:153 -msgid "Scroll of Infravision" -msgstr "Zwój Infrawizji" +#: Source/items.cpp:3747 +#, c++-format +msgid "{:+d} damage from enemies" +msgstr "{:+d} obrażeń od wrogów" -#: Source/itemdat.cpp:154 -msgid "Scroll of Phasing" -msgstr "Zwój Przenikania" +#: Source/items.cpp:3750 +#, c++-format +msgid "Hit Points: {:+d}" +msgstr "Punkty życia: {:+d}" -#: Source/itemdat.cpp:155 -msgid "Scroll of Mana Shield" -msgstr "Zwój Tarczy Many" +#: Source/items.cpp:3753 +#, c++-format +msgid "Mana: {:+d}" +msgstr "Mana: {:+d}" -#: Source/itemdat.cpp:156 -msgid "Scroll of Flame Wave" -msgstr "Zwój Fali Płomieni" +#: Source/items.cpp:3755 +msgid "high durability" +msgstr "wysoka wytrzymałość" -#: Source/itemdat.cpp:157 -msgid "Scroll of Fireball" -msgstr "Zwój Ognistej Kuli" +#: Source/items.cpp:3757 +msgid "decreased durability" +msgstr "zmniejszona wytrzymałość" -#: Source/itemdat.cpp:158 -msgid "Scroll of Stone Curse" -msgstr "Zwój Petryfikacji" +#: Source/items.cpp:3759 +msgid "indestructible" +msgstr "niezniszczalność" -#: Source/itemdat.cpp:159 -msgid "Scroll of Chain Lightning" -msgstr "Zwój Eksplozji Błyskawic" +#: Source/items.cpp:3761 +#, no-c-format, c++-format +msgid "+{:d}% light radius" +msgstr "+{:d}% do promienia światła" -#: Source/itemdat.cpp:160 -msgid "Scroll of Guardian" -msgstr "Zwój Strażnika" +#: Source/items.cpp:3763 +#, no-c-format, c++-format +msgid "-{:d}% light radius" +msgstr "-{:d}% do promienia światła" -#: Source/itemdat.cpp:162 -msgid "Scroll of Nova" -msgstr "Zwój Novy" +#: Source/items.cpp:3765 +msgid "multiple arrows per shot" +msgstr "wielostrzał" -#: Source/itemdat.cpp:163 -msgid "Scroll of Golem" -msgstr "Zwój Golema" +#: Source/items.cpp:3768 +#, c++-format +msgid "fire arrows damage: {:d}" +msgstr "obr. płonących strzał {:d}" -#: Source/itemdat.cpp:165 -msgid "Scroll of Teleport" -msgstr "Zwój Teleportacji" +#: Source/items.cpp:3770 +#, c++-format +msgid "fire arrows damage: {:d}-{:d}" +msgstr "obr. płonących strzał: {:d}-{:d}" -#: Source/itemdat.cpp:166 -msgid "Scroll of Apocalypse" -msgstr "Zwój Apokalipsy" +#: Source/items.cpp:3773 +#, c++-format +msgid "lightning arrows damage {:d}" +msgstr "obr. strzał błyskawic {:d}" -#: Source/itemdat.cpp:167 Source/itemdat.cpp:168 Source/itemdat.cpp:169 -#: Source/itemdat.cpp:170 -msgid "Book of " -msgstr "Księga czaru: " +#: Source/items.cpp:3775 +#, c++-format +msgid "lightning arrows damage {:d}-{:d}" +msgstr "obr. strzał błyskawic {:d}-{:d}" -#: Source/itemdat.cpp:173 -msgid "Falchion" -msgstr "Tasak" +#: Source/items.cpp:3778 +#, c++-format +msgid "fireball damage: {:d}" +msgstr "obr. ognistej kuli {:d}" -#: Source/itemdat.cpp:174 -msgid "Scimitar" -msgstr "Sejmitar" +#: Source/items.cpp:3780 +#, c++-format +msgid "fireball damage: {:d}-{:d}" +msgstr "obr. ognistej kuli {:d}-{:d}" -#: Source/itemdat.cpp:175 -msgid "Claymore" -msgstr "Claymore" +#: Source/items.cpp:3782 +msgid "attacker takes 1-3 damage" +msgstr "atakujący otrzymuje 1-3 obrażeń" -#: Source/itemdat.cpp:176 -msgid "Blade" -msgstr "Klinga" +#: Source/items.cpp:3784 +msgid "user loses all mana" +msgstr "gracz traci całą manę" -#: Source/itemdat.cpp:177 -msgid "Sabre" -msgstr "Szabla" +#: Source/items.cpp:3786 +msgid "absorbs half of trap damage" +msgstr "2x mniej obrażeń od pułapek" -#: Source/itemdat.cpp:178 -msgid "Long Sword" -msgstr "Długi Miecz" +#: Source/items.cpp:3788 +msgid "knocks target back" +msgstr "odrzuca przeciwnika" -#: Source/itemdat.cpp:179 -msgid "Broad Sword" -msgstr "Pałasz" +#: Source/items.cpp:3790 +#, no-c-format +msgid "+200% damage vs. demons" +msgstr "+200% obrażeń wobec demonów" -#: Source/itemdat.cpp:180 -msgid "Bastard Sword" -msgstr "Bękarcki Miecz" +#: Source/items.cpp:3792 +msgid "All Resistance equals 0" +msgstr "Odporności spadają do 0" -#: Source/itemdat.cpp:181 -msgid "Two-Handed Sword" -msgstr "Dwuręczny Miecz" +#: Source/items.cpp:3795 +#, no-c-format +msgid "hit steals 3% mana" +msgstr "uderzenie kradnie 3% many" -#: Source/itemdat.cpp:182 -msgid "Great Sword" -msgstr "Wielki Miecz" +#: Source/items.cpp:3797 +#, no-c-format +msgid "hit steals 5% mana" +msgstr "uderzenie kradnie 5% many" -#: Source/itemdat.cpp:183 -msgid "Small Axe" -msgstr "Ręczny Topór" +#: Source/items.cpp:3801 +#, no-c-format +msgid "hit steals 3% life" +msgstr "uderzenie kradnie 3% życia" -#: 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 "Topór" +#: Source/items.cpp:3803 +#, no-c-format +msgid "hit steals 5% life" +msgstr "uderzenie kradnie 5% życia" -#: Source/itemdat.cpp:185 -msgid "Large Axe" -msgstr "Duży Topór" +#: Source/items.cpp:3806 +msgid "penetrates target's armor" +msgstr "przebija pancerz wroga" -#: Source/itemdat.cpp:186 -msgid "Broad Axe" -msgstr "Szeroki Topór" +#: Source/items.cpp:3809 +msgid "quick attack" +msgstr "szybki atak" -#: Source/itemdat.cpp:187 -msgid "Battle Axe" -msgstr "Bitewny Topór" +#: Source/items.cpp:3811 +msgid "fast attack" +msgstr "szybki atak" -#: Source/itemdat.cpp:188 -msgid "Great Axe" -msgstr "Wielki Topór" +#: Source/items.cpp:3813 +msgid "faster attack" +msgstr "szybszy atak" -#: Source/itemdat.cpp:189 Source/itemdat.cpp:190 -msgid "Mace" -msgstr "Buława" +#: Source/items.cpp:3815 +msgid "fastest attack" +msgstr "najszybszy atak" -#: Source/itemdat.cpp:190 -msgid "Morning Star" -msgstr "Wekiera" +#: Source/items.cpp:3816 Source/items.cpp:3824 Source/items.cpp:3873 +msgid "Another ability (NW)" +msgstr "Inna zdolność" -#: Source/itemdat.cpp:191 -msgid "War Hammer" -msgstr "Bojowy Młot" +#: Source/items.cpp:3819 +msgid "fast hit recovery" +msgstr "szybko wznawia atak" -#: Source/itemdat.cpp:191 -msgid "Hammer" -msgstr "Młot" +#: Source/items.cpp:3821 +msgid "faster hit recovery" +msgstr "szybsze wznowienie ataku" -#: Source/itemdat.cpp:192 -msgid "Spiked Club" -msgstr "Kolczasta Maczuga" +#: Source/items.cpp:3823 +msgid "fastest hit recovery" +msgstr "najszybsze wznowienie ataku" -#: Source/itemdat.cpp:194 -msgid "Flail" -msgstr "Korbacz" +#: Source/items.cpp:3826 +msgid "fast block" +msgstr "szybki blok" -#: Source/itemdat.cpp:195 -msgid "Maul" -msgstr "Kafar" +#: Source/items.cpp:3828 +#, c++-format +msgid "adds {:d} point to damage" +msgid_plural "adds {:d} points to damage" +msgstr[0] "+{:d} pkt. obrażeń" +msgstr[1] "+{:d} pkt. obrażeń" +msgstr[2] "+{:d} pkt. obrażeń" -#: 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 "Łuk" +#: Source/items.cpp:3830 +msgid "fires random speed arrows" +msgstr "losowe szybkie strzały" -#: Source/itemdat.cpp:197 -msgid "Hunter's Bow" -msgstr "Myśliwski Łuk" +#: Source/items.cpp:3832 +msgid "unusual item damage" +msgstr "nietypowe obrażenia" -#: Source/itemdat.cpp:198 -msgid "Long Bow" -msgstr "Długi Łuk" +#: Source/items.cpp:3834 +msgid "altered durability" +msgstr "zmieniona wytrzymałość" -#: Source/itemdat.cpp:199 -msgid "Composite Bow" -msgstr "Refleksyjny Łuk" +#: Source/items.cpp:3836 +msgid "one handed sword" +msgstr "miecz jednoręczny" -#: Source/itemdat.cpp:200 -msgid "Short Battle Bow" -msgstr "Krótki Bitewny Łuk" +#: Source/items.cpp:3838 +msgid "constantly lose hit points" +msgstr "stale tracisz punkty życia" -#: Source/itemdat.cpp:201 -msgid "Long Battle Bow" -msgstr "Długi Bitewny Łuk" +#: Source/items.cpp:3840 +msgid "life stealing" +msgstr "kradzież życia" -#: Source/itemdat.cpp:202 -msgid "Short War Bow" -msgstr "Krótki Bojowy Łuk" +#: Source/items.cpp:3842 +msgid "no strength requirement" +msgstr "brak wymogu siły" -#: Source/itemdat.cpp:203 -msgid "Long War Bow" -msgstr "Długi Bojowy Łuk" +#: Source/items.cpp:3847 +#, c++-format +msgid "lightning damage: {:d}" +msgstr "obrażenia błyskawic: {:d}" -#: 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 "Kostur" +#: Source/items.cpp:3849 +#, c++-format +msgid "lightning damage: {:d}-{:d}" +msgstr "obrażenia błyskawic: {:d}-{:d}" -#: Source/itemdat.cpp:205 -msgid "Long Staff" -msgstr "Długi Kostur" +#: Source/items.cpp:3851 +msgid "charged bolts on hits" +msgstr "rzuca Wiązkę Błyskawic" -#: Source/itemdat.cpp:206 -msgid "Composite Staff" -msgstr "Wygięty Kostur" +#: Source/items.cpp:3853 +msgid "occasional triple damage" +msgstr "okazyjnie potraja obrażenia" -#: Source/itemdat.cpp:207 -msgid "Quarter Staff" -msgstr "Okuty Kostur" +#: Source/items.cpp:3855 +#, no-c-format, c++-format +msgid "decaying {:+d}% damage" +msgstr "rozkład {:+d}% obrażeń" -#: Source/itemdat.cpp:208 -msgid "War Staff" -msgstr "Bojowy Kostur" +#: Source/items.cpp:3857 +msgid "2x dmg to monst, 1x to you" +msgstr "2x obr. dla potw, 1x dla cb" -#: Source/itemdat.cpp:209 Source/itemdat.cpp:210 Source/itemdat.cpp:211 -msgid "Ring" -msgstr "Pierścień" +#: Source/items.cpp:3859 +#, no-c-format +msgid "Random 0 - 600% damage" +msgstr "Losowo 0 - 600% obrażeń" -#: Source/itemdat.cpp:212 Source/itemdat.cpp:213 -msgid "Amulet" -msgstr "Amulet" +#: Source/items.cpp:3861 +#, no-c-format, c++-format +msgid "low dur, {:+d}% damage" +msgstr "mała wyt, {:+d}% obrażeń" -#: Source/itemdat.cpp:214 -msgid "Rune of Fire" -msgstr "Runa Ognia" +#: Source/items.cpp:3865 +msgid "extra AC vs demons" +msgstr "ekstra pancerz przeciwko demonom" -#: Source/itemdat.cpp:214 Source/itemdat.cpp:215 Source/itemdat.cpp:216 -#: Source/itemdat.cpp:217 Source/itemdat.cpp:218 -msgid "Rune" -msgstr "Runa" +#: Source/items.cpp:3867 +msgid "extra AC vs undead" +msgstr "ekstra pancerz przeciwko nieumarłym" -#: Source/itemdat.cpp:215 -msgid "Rune of Lightning" -msgstr "Runa Błyskawic" +#: Source/items.cpp:3869 +msgid "50% Mana moved to Health" +msgstr "50% Many przechodzi do Życia" -#: Source/itemdat.cpp:216 -msgid "Greater Rune of Fire" -msgstr "Większa Runa Ognia" +#: Source/items.cpp:3871 +msgid "40% Health moved to Mana" +msgstr "40% Życia przechodzi do Many" -#: Source/itemdat.cpp:217 -msgid "Greater Rune of Lightning" -msgstr "Większa Runa Błyskawic" +#: Source/items.cpp:3912 Source/items.cpp:3953 +#, c++-format +msgid "damage: {:d} Indestructible" +msgstr "obr: {:d} Niezniszczalność" -#: Source/itemdat.cpp:218 -msgid "Rune of Stone" -msgstr "Runa Kamienia" +#. TRANSLATORS: Dur: is durability +#: Source/items.cpp:3914 Source/items.cpp:3955 +#, c++-format +msgid "damage: {:d} Dur: {:d}/{:d}" +msgstr "obr: {:d} Wyt: {:d}/{:d}" -#: Source/itemdat.cpp:219 -msgid "Short Staff of Charged Bolt" -msgstr "Kostur Wiązki Błyskawic" +#: Source/items.cpp:3917 Source/items.cpp:3958 +#, c++-format +msgid "damage: {:d}-{:d} Indestructible" +msgstr "obr: {:d}-{:d} Niezniszczalność" -#. TRANSLATORS: Item prefix section. -#: Source/itemdat.cpp:229 -msgid "Tin" -msgstr "Blaszany" +#. TRANSLATORS: Dur: is durability +#: Source/items.cpp:3919 Source/items.cpp:3960 +#, c++-format +msgid "damage: {:d}-{:d} Dur: {:d}/{:d}" +msgstr "obr: {:d}-{:d} Wyt: {:d}/{:d}" -#: Source/itemdat.cpp:230 -msgid "Brass" -msgstr "Mosiężny" +#: Source/items.cpp:3924 Source/items.cpp:3970 +#, c++-format +msgid "armor: {:d} Indestructible" +msgstr "pancerz: {:d} Niezniszczalność" -#: Source/itemdat.cpp:231 -msgid "Bronze" -msgstr "Brązowy" +#. TRANSLATORS: Dur: is durability +#: Source/items.cpp:3926 Source/items.cpp:3972 +#, c++-format +msgid "armor: {:d} Dur: {:d}/{:d}" +msgstr "pancerz: {:d} Wyt: {:d}/{:d}" -#: Source/itemdat.cpp:232 -msgid "Iron" -msgstr "Żelazny" +#: Source/items.cpp:3929 Source/items.cpp:3963 Source/items.cpp:3976 +#: Source/stores.cpp:299 +#, c++-format +msgid "Charges: {:d}/{:d}" +msgstr "Ładunki: {:d}/{:d}" -#: Source/itemdat.cpp:233 -msgid "Steel" -msgstr "Stalowy" +#: Source/items.cpp:3938 +msgid "unique item" +msgstr "unikat" -#: Source/itemdat.cpp:234 -msgid "Silver" -msgstr "Srebrny" +#: Source/items.cpp:3966 Source/items.cpp:3974 Source/items.cpp:3980 +msgid "Not Identified" +msgstr "Nie zidentyfikowano" -#: Source/itemdat.cpp:236 -msgid "Platinum" -msgstr "Platynowy" +#: Source/levels/setmaps.cpp:27 +msgid "Skeleton King's Lair" +msgstr "Siedziba Króla Szkieletów" -#: Source/itemdat.cpp:237 -msgid "Mithril" -msgstr "Mithrilowy" +#: Source/levels/setmaps.cpp:28 +msgid "Chamber of Bone" +msgstr "Komnata Kości" -#: Source/itemdat.cpp:238 -msgid "Meteoric" -msgstr "Meteorytowy" +#. TRANSLATORS: Quest Map +#: Source/levels/setmaps.cpp:29 Source/quests.cpp:103 +msgid "Maze" +msgstr "Labirynt" -#: Source/itemdat.cpp:239 Source/objects.cpp:109 -msgid "Weird" -msgstr "Dziwny" +#: Source/levels/setmaps.cpp:30 Source/quests.cpp:65 +msgid "Poisoned Water Supply" +msgstr "Zatrute Źródło Wody" -#: Source/itemdat.cpp:240 -msgid "Strange" -msgstr "Nietypowy" +#: Source/levels/setmaps.cpp:31 +msgid "Archbishop Lazarus' Lair" +msgstr "Siedziba Arcybiskupa Lazarusa" -#: Source/itemdat.cpp:241 -msgid "Useless" -msgstr "Zbędny" +#: Source/levels/setmaps.cpp:32 +msgid "Church Arena" +msgstr "Arena Kościoła" -#: Source/itemdat.cpp:242 -msgid "Bent" -msgstr "Wygięty" +#: Source/levels/setmaps.cpp:33 +msgid "Hell Arena" +msgstr "Arena Piekła" -#: Source/itemdat.cpp:243 -msgid "Weak" -msgstr "Słaby" +#: Source/levels/setmaps.cpp:34 +msgid "Circle of Life Arena" +msgstr "Arena Kręgu Życia" -#: Source/itemdat.cpp:244 -msgid "Jagged" -msgstr "Pęknięty" +#: Source/levels/trigs.cpp:352 +msgid "Down to dungeon" +msgstr "Zejdź do lochu" -#: Source/itemdat.cpp:245 -msgid "Deadly" -msgstr "Zabójczy" +#: Source/levels/trigs.cpp:361 +msgid "Down to catacombs" +msgstr "Zejdź do katakumb" -#: Source/itemdat.cpp:246 -msgid "Heavy" -msgstr "Ciężki" +#: Source/levels/trigs.cpp:371 +msgid "Down to caves" +msgstr "Zejdź do jaskiń" -#: Source/itemdat.cpp:247 -msgid "Vicious" -msgstr "Wściekły" +#: Source/levels/trigs.cpp:381 +msgid "Down to hell" +msgstr "Zejdź do piekła" -#: Source/itemdat.cpp:248 -msgid "Brutal" -msgstr "Okrutny" +#: Source/levels/trigs.cpp:391 +msgid "Down to Hive" +msgstr "Zejdź do Gniazda" -#: Source/itemdat.cpp:249 -msgid "Massive" -msgstr "Ogromny" +#: Source/levels/trigs.cpp:401 +msgid "Down to Crypt" +msgstr "Zejdź do Krypty" -#: Source/itemdat.cpp:250 -msgid "Savage" -msgstr "Dziki" +#: Source/levels/trigs.cpp:416 Source/levels/trigs.cpp:451 +#: Source/levels/trigs.cpp:497 Source/levels/trigs.cpp:549 +#, c++-format +msgid "Up to level {:d}" +msgstr "Wejdź na poziom {:d}" -#: Source/itemdat.cpp:251 -msgid "Ruthless" -msgstr "Bezwzględny" +#: Source/levels/trigs.cpp:418 Source/levels/trigs.cpp:480 +#: Source/levels/trigs.cpp:532 Source/levels/trigs.cpp:579 +#: Source/levels/trigs.cpp:641 Source/levels/trigs.cpp:690 +#: Source/levels/trigs.cpp:797 +msgid "Up to town" +msgstr "Wejdź do miasta" -#: Source/itemdat.cpp:252 -msgid "Merciless" -msgstr "Bezlitosny" +#: Source/levels/trigs.cpp:429 Source/levels/trigs.cpp:462 +#: Source/levels/trigs.cpp:514 Source/levels/trigs.cpp:561 +#: Source/levels/trigs.cpp:623 +#, c++-format +msgid "Down to level {:d}" +msgstr "Zejdź na poziom {:d}" -#: Source/itemdat.cpp:253 -msgid "Clumsy" -msgstr "Nieudany" +#: Source/levels/trigs.cpp:592 +msgid "Down to Diablo" +msgstr "Staw czoła Diablo" -#: Source/itemdat.cpp:254 -msgid "Dull" -msgstr "Tępy" +#: Source/levels/trigs.cpp:610 +#, c++-format +msgid "Up to Nest level {:d}" +msgstr "Do Gniazda - poziom {:d}" -#: Source/itemdat.cpp:255 -msgid "Sharp" -msgstr "Ostry" +#: Source/levels/trigs.cpp:658 +#, c++-format +msgid "Up to Crypt level {:d}" +msgstr "Do Krypty - poziom {:d}" -#: Source/itemdat.cpp:256 Source/itemdat.cpp:266 -msgid "Fine" -msgstr "Świetny" +#: Source/levels/trigs.cpp:668 Source/quests.cpp:74 +msgid "Cornerstone of the World" +msgstr "Fundament Świata" -#: Source/itemdat.cpp:257 -msgid "Warrior's" -msgstr "Wojowniczy" +#: Source/levels/trigs.cpp:673 +#, c++-format +msgid "Down to Crypt level {:d}" +msgstr "Do Krypty - poziom {:d}" -#: Source/itemdat.cpp:258 -msgid "Soldier's" -msgstr "Giermkowski" +#: Source/levels/trigs.cpp:721 Source/levels/trigs.cpp:735 +#: Source/levels/trigs.cpp:749 +#, c++-format +msgid "Back to Level {:d}" +msgstr "Wróć na poziom {:d}" -#: Source/itemdat.cpp:259 -msgid "Lord's" -msgstr "Lordowski" +#: Source/loadsave.cpp:2093 Source/loadsave.cpp:2625 +msgid "Unable to open save file archive" +msgstr "Nie można otworzyć pliku zapisu" -#: Source/itemdat.cpp:260 -msgid "Knight's" -msgstr "Rycerski" +#: Source/loadsave.cpp:2096 +msgid "Invalid save file" +msgstr "Nieprawidłowy plik zapisu" -#: Source/itemdat.cpp:261 -msgid "Master's" -msgstr "Wyborny" +#: Source/loadsave.cpp:2127 +msgid "Player is on a Hellfire only level" +msgstr "Gracz przebywa na poziomie z dodatku Hellfire" -#: Source/itemdat.cpp:262 -msgid "Champion's" -msgstr "Przewyborny" +#: Source/loadsave.cpp:2383 +msgid "Invalid game state" +msgstr "Nieprawidłowy stan gry" -#: Source/itemdat.cpp:263 -msgid "King's" -msgstr "Królewski" +#: Source/menu.cpp:155 +msgid "Unable to display mainmenu" +msgstr "Nie można wyświetlić głównego menu" -#: Source/itemdat.cpp:264 -msgid "Vulnerable" -msgstr "Delikatny" +#: Source/monster.cpp:2992 +msgid "Animal" +msgstr "Zwierzę" -#: Source/itemdat.cpp:265 -msgid "Rusted" -msgstr "Stary" +#: Source/monster.cpp:2994 +msgid "Demon" +msgstr "Demon" -#: Source/itemdat.cpp:267 -msgid "Strong" -msgstr "Silny" +#: Source/monster.cpp:2996 +msgid "Undead" +msgstr "Nieumarły" -#: Source/itemdat.cpp:268 -msgid "Grand" -msgstr "Wielki" +#: Source/monster.cpp:4299 +#, c++-format +msgid "Type: {:s} Kills: {:d}" +msgstr "Rodzaj: {:s} Zabitych: {:d}" -#: Source/itemdat.cpp:269 -msgid "Valiant" -msgstr "Bitny" +#: Source/monster.cpp:4301 +#, c++-format +msgid "Total kills: {:d}" +msgstr "Suma zabitych: {:d}" -#: Source/itemdat.cpp:270 -msgid "Glorious" -msgstr "Chwalebny" +#: Source/monster.cpp:4333 +#, c++-format +msgid "Hit Points: {:d}-{:d}" +msgstr "Punkty życia: {:d}-{:d}" -#: Source/itemdat.cpp:271 -msgid "Blessed" -msgstr "Zbawienny" +#: Source/monster.cpp:4338 +msgid "No magic resistance" +msgstr "Brak odporności na magię" -#: Source/itemdat.cpp:272 -msgid "Saintly" -msgstr "Uświęcony" +#: Source/monster.cpp:4341 +msgid "Resists:" +msgstr "Odporności:" -#: Source/itemdat.cpp:273 -msgid "Awesome" -msgstr "Cudowny" +#: Source/monster.cpp:4343 Source/monster.cpp:4353 +msgid " Magic" +msgstr " Magię" -#: Source/itemdat.cpp:274 Source/objects.cpp:121 -msgid "Holy" -msgstr "Święty" +#: Source/monster.cpp:4345 Source/monster.cpp:4355 +msgid " Fire" +msgstr " Ogień" -#: Source/itemdat.cpp:275 -msgid "Godly" -msgstr "Boski" +#: Source/monster.cpp:4347 Source/monster.cpp:4357 +msgid " Lightning" +msgstr " Błyskawice" -#: Source/itemdat.cpp:276 -msgid "Red" -msgstr "Czerwony" +#: Source/monster.cpp:4351 +msgid "Immune:" +msgstr "Niewrażliwość na:" -#: Source/itemdat.cpp:277 Source/itemdat.cpp:278 -msgid "Crimson" -msgstr "Szkarłatny" +#: Source/monster.cpp:4368 +#, c++-format +msgid "Type: {:s}" +msgstr "Rodzaj: {:s}" -#: Source/itemdat.cpp:279 -msgid "Garnet" -msgstr "Bordowy" +#: Source/monster.cpp:4373 Source/monster.cpp:4379 +msgid "No resistances" +msgstr "Brak odporności" -#: Source/itemdat.cpp:280 -msgid "Ruby" -msgstr "Rubinowy" +#: Source/monster.cpp:4374 Source/monster.cpp:4383 +msgid "No Immunities" +msgstr "Brak niewrażliwości" -#: Source/itemdat.cpp:281 -msgid "Blue" -msgstr "Niebieski" +#: Source/monster.cpp:4377 +msgid "Some Magic Resistances" +msgstr "Lekka odporność na magię" -#: Source/itemdat.cpp:282 -msgid "Azure" -msgstr "Lazurowy" +#: Source/monster.cpp:4381 +msgid "Some Magic Immunities" +msgstr "Częściowa niewraż. na magię" -#: Source/itemdat.cpp:283 -msgid "Lapis" -msgstr "Lapisowy" +#: Source/mpq/mpq_writer.cpp:161 +msgid "Failed to open archive for writing." +msgstr "Błąd otwarcia archiwum do zapisu." -#: Source/itemdat.cpp:284 -msgid "Cobalt" -msgstr "Kobaltowy" +#: Source/msg.cpp:774 +msgid "Trying to drop a floor item?" +msgstr "Próbujesz upuścić przedmiot leżący na ziemi?" -#: Source/itemdat.cpp:285 -msgid "Sapphire" -msgstr "Szafirowy" +#: Source/msg.cpp:1376 +#, c++-format +msgid "{:s} has cast an invalid spell." +msgstr "{:s} rzucił niewłaściwe zaklęcie." -#: Source/itemdat.cpp:286 -msgid "White" -msgstr "Biały" +#: Source/msg.cpp:1380 +#, c++-format +msgid "{:s} has cast an illegal spell." +msgstr "{:s} rzucił nielegalne zaklęcie." -#: Source/itemdat.cpp:287 -msgid "Pearl" -msgstr "Perłowy" +#: Source/msg.cpp:2011 Source/multi.cpp:793 Source/multi.cpp:843 +#, c++-format +msgid "Player '{:s}' (level {:d}) just joined the game" +msgstr "Gracz '{:s}' (poziom {:d}) dołączył do gry" -#: Source/itemdat.cpp:288 -msgid "Ivory" -msgstr "Kościany" +#: Source/msg.cpp:2372 +msgid "The game ended" +msgstr "Gra została zakończona" -#: Source/itemdat.cpp:289 -msgid "Crystal" -msgstr "Brylantowy" +#: Source/msg.cpp:2378 +msgid "Unable to get level data" +msgstr "Nie można wczytać danych poziomu" -#: Source/itemdat.cpp:290 -msgid "Diamond" -msgstr "Diamentowy" +#: Source/multi.cpp:260 +#, c++-format +msgid "Player '{:s}' just left the game" +msgstr "Gracz '{:s}' opuścił grę" -#: Source/itemdat.cpp:291 -msgid "Topaz" -msgstr "Topazowy" +#: Source/multi.cpp:263 +#, c++-format +msgid "Player '{:s}' killed Diablo and left the game!" +msgstr "Gracz '{:s}' pokonał Diablo i opuścił grę!" -#: Source/itemdat.cpp:292 -msgid "Amber" -msgstr "Cytrynowy" +#: Source/multi.cpp:267 +#, c++-format +msgid "Player '{:s}' dropped due to timeout" +msgstr "Gracz '{:s}' opuścił grę z powodu limitu czasu" -#: Source/itemdat.cpp:293 -msgid "Jade" -msgstr "Agatowy" +#: Source/multi.cpp:845 +#, c++-format +msgid "Player '{:s}' (level {:d}) is already in the game" +msgstr "Gracz '{:s}' (poziom {:d}) jest już w grze" -#: Source/itemdat.cpp:294 -msgid "Obsidian" -msgstr "Obsydianowy" +#. TRANSLATORS: Shrine Name Block +#: Source/objects.cpp:123 +msgid "Mysterious" +msgstr "Tajemnicza" -#: Source/itemdat.cpp:295 -msgid "Emerald" -msgstr "Szmaragdowy" +#: Source/objects.cpp:124 +msgid "Hidden" +msgstr "Ukryty" -#: Source/itemdat.cpp:296 -msgid "Hyena's" -msgstr "Wilczy" +#: Source/objects.cpp:125 +msgid "Gloomy" +msgstr "Ponura" -#: Source/itemdat.cpp:297 -msgid "Frog's" -msgstr "Żabi" +#: Source/objects.cpp:126 Source/translation_dummy.cpp:610 +msgid "Weird" +msgstr "Dziwny" -#: Source/itemdat.cpp:298 -msgid "Spider's" -msgstr "Pajęczy" +#: Source/objects.cpp:127 Source/objects.cpp:134 +msgid "Magical" +msgstr "Magiczna" -#: Source/itemdat.cpp:299 -msgid "Raven's" -msgstr "Kruczy" +#: Source/objects.cpp:128 +msgid "Stone" +msgstr "Kamienna" -#: Source/itemdat.cpp:300 -msgid "Snake's" -msgstr "Wężowy" +#: Source/objects.cpp:129 +msgid "Religious" +msgstr "Zakonna" -#: Source/itemdat.cpp:301 -msgid "Serpent's" -msgstr "Gadzi" +#: Source/objects.cpp:130 +msgid "Enchanted" +msgstr "Zaczarowana" -#: Source/itemdat.cpp:302 -msgid "Drake's" -msgstr "Sokoli" +#: Source/objects.cpp:131 +msgid "Thaumaturgic" +msgstr "Taumaturgiczna" -#: Source/itemdat.cpp:303 -msgid "Dragon's" -msgstr "Smoczy" +#: Source/objects.cpp:132 +msgid "Fascinating" +msgstr "Czarująca" -#: Source/itemdat.cpp:304 -msgid "Wyrm's" -msgstr "Żmijowy" +#: Source/objects.cpp:133 +msgid "Cryptic" +msgstr "Zagadkowa" -#: Source/itemdat.cpp:305 -msgid "Hydra's" -msgstr "Mityczny" +#: Source/objects.cpp:135 +msgid "Eldritch" +msgstr "Koszmarna" -#: Source/itemdat.cpp:306 -msgid "Angel's" -msgstr "Anielski" +#: Source/objects.cpp:136 +msgid "Eerie" +msgstr "Niesamowita" -#: Source/itemdat.cpp:307 -msgid "Arch-Angel's" -msgstr "Archanielski" +#: Source/objects.cpp:137 +msgid "Divine" +msgstr "Boska" -#: Source/itemdat.cpp:308 -msgid "Plentiful" -msgstr "Obfity" +#: Source/objects.cpp:138 Source/translation_dummy.cpp:645 +msgid "Holy" +msgstr "Święty" -#: Source/itemdat.cpp:309 -msgid "Bountiful" -msgstr "Dorodny" +#: Source/objects.cpp:139 +msgid "Sacred" +msgstr "Święta" -#: Source/itemdat.cpp:310 -msgid "Flaming" -msgstr "Płonący" +#: Source/objects.cpp:140 +msgid "Spiritual" +msgstr "Duchowa" -#: Source/itemdat.cpp:311 -msgid "Lightning" -msgstr "Piorunowy" +#: Source/objects.cpp:141 +msgid "Spooky" +msgstr "Upiorna" -#: Source/itemdat.cpp:312 -msgid "Jester's" -msgstr "Błazeński" +#: Source/objects.cpp:142 +msgid "Abandoned" +msgstr "Porzucona" -#: Source/itemdat.cpp:313 -msgid "Crystalline" -msgstr "Kryształowy" +#: Source/objects.cpp:143 +msgid "Creepy" +msgstr "Straszna" -#. TRANSLATORS: Item prefix section end. -#: Source/itemdat.cpp:315 -msgid "Doppelganger's" -msgstr "Rozdwojony" +#: Source/objects.cpp:144 +msgid "Quiet" +msgstr "Cicha" -#. 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 "jakości" +#: Source/objects.cpp:145 +msgid "Secluded" +msgstr "Odosobniona" -#: Source/itemdat.cpp:326 -msgid "maiming" -msgstr "ran" +#: Source/objects.cpp:146 +msgid "Ornate" +msgstr "Ozdobna" -#: Source/itemdat.cpp:327 -msgid "slaying" -msgstr "zagłady" +#: Source/objects.cpp:147 +msgid "Glimmering" +msgstr "Migocząca" -#: Source/itemdat.cpp:328 -msgid "gore" -msgstr "sadyzmu" +#: Source/objects.cpp:148 +msgid "Tainted" +msgstr "Splugawiona" -#: Source/itemdat.cpp:329 -msgid "carnage" -msgstr "zbrodni" +#: Source/objects.cpp:149 +msgid "Oily" +msgstr "Wzmacniająca" -#: Source/itemdat.cpp:330 -msgid "slaughter" -msgstr "rzezi" +#: Source/objects.cpp:150 +msgid "Glowing" +msgstr "Świecąca" -#: Source/itemdat.cpp:331 -msgid "pain" -msgstr "udręki" +#: Source/objects.cpp:151 +msgid "Mendicant's" +msgstr "Matczyna" -#: Source/itemdat.cpp:332 -msgid "tears" -msgstr "smutku" +#: Source/objects.cpp:152 +msgid "Sparkling" +msgstr "Błyszcząca" -#: Source/itemdat.cpp:333 -msgid "health" -msgstr "zdrowia" +#: Source/objects.cpp:154 +msgid "Shimmering" +msgstr "Lśniąca" -#: Source/itemdat.cpp:334 -msgid "protection" -msgstr "opieki" +#: Source/objects.cpp:155 +msgid "Solar" +msgstr "Promienista" -#: Source/itemdat.cpp:335 -msgid "absorption" -msgstr "absorpcji" +#. TRANSLATORS: Shrine Name Block end +#: Source/objects.cpp:157 +msgid "Murphy's" +msgstr "Czarnowidząca" -#: Source/itemdat.cpp:336 -msgid "deflection" -msgstr "unikania" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:210 +msgid "The Great Conflict" +msgstr "Wielki Konflikt" -#: Source/itemdat.cpp:337 -msgid "osmosis" -msgstr "izolacji" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:211 +msgid "The Wages of Sin are War" +msgstr "Wojna Karą za Grzechy" -#: Source/itemdat.cpp:338 -msgid "frailty" -msgstr "marności" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:212 +msgid "The Tale of the Horadrim" +msgstr "Historia Horadrimów" -#: Source/itemdat.cpp:339 -msgid "weakness" -msgstr "słabości" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:213 +msgid "The Dark Exile" +msgstr "Mroczne Wygnanie" -#: Source/itemdat.cpp:340 -msgid "strength" -msgstr "siły" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:214 +msgid "The Sin War" +msgstr "Wojna Grzechu" -#: Source/itemdat.cpp:341 -msgid "might" -msgstr "mocy" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:215 +msgid "The Binding of the Three" +msgstr "Schwytanie Trójcy" -#: Source/itemdat.cpp:342 -msgid "power" -msgstr "władzy" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:216 +msgid "The Realms Beyond" +msgstr "Nieznane Krainy" -#: Source/itemdat.cpp:343 -msgid "giants" -msgstr "giganta" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:217 +msgid "Tale of the Three" +msgstr "Historia Trójcy" -#: Source/itemdat.cpp:344 -msgid "titans" -msgstr "tytana" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:218 +msgid "The Black King" +msgstr "Czarny Król" -#: Source/itemdat.cpp:345 -msgid "paralysis" -msgstr "paraliżu" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:219 +msgid "Journal: The Ensorcellment" +msgstr "Dziennik: Wpływ Magii" -#: Source/itemdat.cpp:346 -msgid "atrophy" -msgstr "atrofii" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:220 +msgid "Journal: The Meeting" +msgstr "Dziennik: Konfrontacja" -#: Source/itemdat.cpp:347 -msgid "dexterity" -msgstr "zręczności" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:221 +msgid "Journal: The Tirade" +msgstr "Dziennik: Tyrada" -#: Source/itemdat.cpp:348 -msgid "skill" -msgstr "talentu" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:222 +msgid "Journal: His Power Grows" +msgstr "Dziennik: Jego Siła Wzrasta" -#: Source/itemdat.cpp:349 -msgid "accuracy" -msgstr "celności" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:223 +msgid "Journal: NA-KRUL" +msgstr "Dziennik: NA-KRUL" -#: Source/itemdat.cpp:350 -msgid "precision" -msgstr "precyzji" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:224 +msgid "Journal: The End" +msgstr "Dziennik: Zakończenie" -#: Source/itemdat.cpp:351 -msgid "perfection" -msgstr "perfekcji" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:225 +msgid "A Spellbook" +msgstr "Księga Zaklęć" -#: Source/itemdat.cpp:352 -msgid "the fool" -msgstr "głupca" +#: Source/objects.cpp:4769 +msgid "Crucified Skeleton" +msgstr "Ukrzyżowany Szkielet" -#: Source/itemdat.cpp:353 -msgid "dyslexia" -msgstr "dysfunkcji" +#: Source/objects.cpp:4773 +msgid "Lever" +msgstr "Dźwignia" -#: Source/itemdat.cpp:354 -msgid "magic" -msgstr "magii" +#: Source/objects.cpp:4783 +msgid "Open Door" +msgstr "Otwarte Drzwi" -#: Source/itemdat.cpp:355 -msgid "the mind" -msgstr "myśliciela" +#: Source/objects.cpp:4785 +msgid "Closed Door" +msgstr "Zamknięte Drzwi" -#: Source/itemdat.cpp:356 -msgid "brilliance" -msgstr "rozumu" +#: Source/objects.cpp:4787 +msgid "Blocked Door" +msgstr "Zablokowane Drzwi" -#: Source/itemdat.cpp:357 -msgid "sorcery" -msgstr "czarów" +#: Source/objects.cpp:4792 +msgid "Ancient Tome" +msgstr "Starożytna Księga" -#: Source/itemdat.cpp:358 -msgid "wizardry" -msgstr "iluzji" +#: Source/objects.cpp:4794 +msgid "Book of Vileness" +msgstr "Księga Obłudy" -#: Source/itemdat.cpp:359 -msgid "illness" -msgstr "zarazy" +#: Source/objects.cpp:4799 +msgid "Skull Lever" +msgstr "Kościana dźwignia" -#: Source/itemdat.cpp:360 -msgid "disease" -msgstr "choroby" +#: Source/objects.cpp:4801 +msgid "Mythical Book" +msgstr "Mityczna Księga" -#: Source/itemdat.cpp:361 -msgid "vitality" -msgstr "żywotności" +#: Source/objects.cpp:4804 +msgid "Small Chest" +msgstr "Mała Skrzynia" -#: Source/itemdat.cpp:362 -msgid "zest" -msgstr "witalności" +#: Source/objects.cpp:4807 +msgid "Chest" +msgstr "Skrzynia" -#: Source/itemdat.cpp:363 -msgid "vim" -msgstr "werwy" +#: Source/objects.cpp:4811 +msgid "Large Chest" +msgstr "Duża Skrzynia" -#: Source/itemdat.cpp:364 -msgid "vigor" -msgstr "wigoru" +#: Source/objects.cpp:4814 +msgid "Sarcophagus" +msgstr "Sarkofag" -#: Source/itemdat.cpp:365 -msgid "life" -msgstr "życia" +#: Source/objects.cpp:4816 +msgid "Bookshelf" +msgstr "Regał" -#: Source/itemdat.cpp:366 -msgid "trouble" -msgstr "utrapienia" +#: Source/objects.cpp:4819 +msgid "Bookcase" +msgstr "Biblioteczka" -#: Source/itemdat.cpp:367 -msgid "the pit" -msgstr "otchłani" +#: Source/objects.cpp:4822 +msgid "Barrel" +msgstr "Beczka" -#: Source/itemdat.cpp:368 -msgid "the sky" -msgstr "łaski" +#: Source/objects.cpp:4825 +msgid "Pod" +msgstr "Kokon" -#: Source/itemdat.cpp:369 -msgid "the moon" -msgstr "księżyca" +#: Source/objects.cpp:4828 +msgid "Urn" +msgstr "Urna" -#: Source/itemdat.cpp:370 -msgid "the stars" -msgstr "gwiazd" +#. TRANSLATORS: {:s} will be a name from the Shrine block above +#: Source/objects.cpp:4831 +#, c++-format +msgid "{:s} Shrine" +msgstr "{:s} Kapliczka" -#: Source/itemdat.cpp:371 -msgid "the heavens" -msgstr "niebios" +#: Source/objects.cpp:4833 +msgid "Skeleton Tome" +msgstr "Kościana Księga" -#: Source/itemdat.cpp:372 -msgid "the zodiac" -msgstr "zodiaku" +#: Source/objects.cpp:4835 +msgid "Library Book" +msgstr "Księga Biblioteczna" -#: Source/itemdat.cpp:373 -msgid "the vulture" -msgstr "sępa" +#: Source/objects.cpp:4837 +msgid "Blood Fountain" +msgstr "Fontanna Krwi" -#: Source/itemdat.cpp:374 -msgid "the jackal" -msgstr "szakala" +#: Source/objects.cpp:4839 +msgid "Decapitated Body" +msgstr "Okaleczone Ciało" -#: Source/itemdat.cpp:375 -msgid "the fox" -msgstr "lisa" +#: Source/objects.cpp:4841 +msgid "Book of the Blind" +msgstr "Księga Ślepców" -#: Source/itemdat.cpp:376 -msgid "the jaguar" -msgstr "jaguara" +#: Source/objects.cpp:4843 +msgid "Book of Blood" +msgstr "Księga Krwi" -#: Source/itemdat.cpp:377 -msgid "the eagle" -msgstr "orła" +#: Source/objects.cpp:4845 +msgid "Purifying Spring" +msgstr "Źródło Oczyszczenia" -#: Source/itemdat.cpp:378 -msgid "the wolf" -msgstr "hieny" +#: Source/objects.cpp:4848 Source/translation_dummy.cpp:316 +#: Source/translation_dummy.cpp:318 Source/translation_dummy.cpp:320 +#: Source/translation_dummy.cpp:322 +msgid "Armor" +msgstr "Zbroja" -#: Source/itemdat.cpp:379 -msgid "the tiger" -msgstr "tygrysa" +#: Source/objects.cpp:4850 Source/objects.cpp:4867 +msgid "Weapon Rack" +msgstr "Stojak na Broń" -#: Source/itemdat.cpp:380 -msgid "the lion" -msgstr "lwa" +#: Source/objects.cpp:4852 +msgid "Goat Shrine" +msgstr "Koźla Kapliczka" -#: Source/itemdat.cpp:381 -msgid "the mammoth" -msgstr "mamuta" +#: Source/objects.cpp:4854 +msgid "Cauldron" +msgstr "Kocioł" -#: Source/itemdat.cpp:382 -msgid "the whale" -msgstr "wieloryba" +#: Source/objects.cpp:4856 +msgid "Murky Pool" +msgstr "Mętna Sadzawka" -#: Source/itemdat.cpp:383 -msgid "fragility" -msgstr "wątłości" +#: Source/objects.cpp:4858 +msgid "Fountain of Tears" +msgstr "Fontanna Łez" -#: Source/itemdat.cpp:384 -msgid "brittleness" -msgstr "kruchości" +#: Source/objects.cpp:4860 +msgid "Steel Tome" +msgstr "Żelazna Księga" -#: Source/itemdat.cpp:385 -msgid "sturdiness" -msgstr "krzepy" +#: Source/objects.cpp:4862 +msgid "Pedestal of Blood" +msgstr "Piedestał Krwi" -#: Source/itemdat.cpp:386 -msgid "craftsmanship" -msgstr "kunsztu" +#: Source/objects.cpp:4869 +msgid "Mushroom Patch" +msgstr "Skupisko grzybów" -#: Source/itemdat.cpp:387 -msgid "structure" -msgstr "zwięzłości" +#: Source/objects.cpp:4871 +msgid "Vile Stand" +msgstr "Stojak Nikczemnego" -#: Source/itemdat.cpp:388 -msgid "the ages" -msgstr "wieków" +#: Source/objects.cpp:4873 +msgid "Slain Hero" +msgstr "Zabity Bohater" -#: Source/itemdat.cpp:389 -msgid "the dark" -msgstr "mroku" +#. TRANSLATORS: {:s} will either be a chest or a door +#: Source/objects.cpp:4885 +#, c++-format +msgid "Trapped {:s}" +msgstr "{:s} (pułapka)" -#: Source/itemdat.cpp:390 -msgid "the night" -msgstr "zmierzchu" +#. TRANSLATORS: If user enabled diablo.ini setting "Disable Crippling Shrines" is set to 1; also used for Na-Kruls lever +#: Source/objects.cpp:4890 +#, c++-format +msgid "{:s} (disabled)" +msgstr "{:s} (wył.)" -#: Source/itemdat.cpp:391 -msgid "light" -msgstr "blasku" +#: Source/options.cpp:457 Source/options.cpp:581 Source/options.cpp:587 +msgid "ON" +msgstr "TAK" -#: Source/itemdat.cpp:392 -msgid "radiance" -msgstr "jasności" +#: Source/options.cpp:457 Source/options.cpp:579 Source/options.cpp:585 +msgid "OFF" +msgstr "NIE" -#: Source/itemdat.cpp:393 -msgid "flame" -msgstr "płomienia" +#: Source/options.cpp:569 +msgid "Start Up" +msgstr "Uruchomienie" -#: Source/itemdat.cpp:394 -msgid "fire" -msgstr "ognia" +#: Source/options.cpp:569 +msgid "Start Up Settings" +msgstr "Ustawienia uruchamiania" -#: Source/itemdat.cpp:395 -msgid "burning" -msgstr "spopielenia" +#: Source/options.cpp:570 +msgid "Game Mode" +msgstr "Tryb gry" -#: Source/itemdat.cpp:396 -msgid "shock" -msgstr "wstrząsu" +#: Source/options.cpp:570 +msgid "Play Diablo or Hellfire." +msgstr "Zagraj w Diablo lub Hellfire." -#: Source/itemdat.cpp:397 -msgid "lightning" -msgstr "błyskawic" +#: Source/options.cpp:576 +msgid "Restrict to Shareware" +msgstr "Ogranicz do wersji Demo" -#: Source/itemdat.cpp:398 -msgid "thunder" -msgstr "gromu" +#: Source/options.cpp:576 +msgid "" +"Makes the game compatible with the demo. Enables multiplayer with friends " +"who don't own a full copy of Diablo." +msgstr "" +"Sprawia, że gra jest kompatybilna z wersją demonstracyjną. Umożliwia grę " +"wieloosobową z przyjaciółmi, którzy nie posiadają pełnej kopii Diablo." -#: Source/itemdat.cpp:399 -msgid "many" -msgstr "pokusy" +#: Source/options.cpp:577 Source/options.cpp:583 +msgid "Intro" +msgstr "Intro" -#: Source/itemdat.cpp:400 -msgid "plenty" -msgstr "obfitości" +#: Source/options.cpp:577 Source/options.cpp:583 +msgid "Shown Intro cinematic." +msgstr "Pokaż intro." -#: Source/itemdat.cpp:401 -msgid "thorns" -msgstr "iglicy" +#: Source/options.cpp:589 +msgid "Splash" +msgstr "Powitanie" -#: Source/itemdat.cpp:402 -msgid "corruption" -msgstr "skazy" +#: Source/options.cpp:589 +msgid "Shown splash screen." +msgstr "Pokaż ekran powitalny." -#: Source/itemdat.cpp:403 -msgid "thieves" -msgstr "złodzieja" +#: Source/options.cpp:591 +msgid "Logo and Title Screen" +msgstr "Logo i ekran tytułowy" -#: Source/itemdat.cpp:404 -msgid "the bear" -msgstr "bizona" +#: Source/options.cpp:592 +msgid "Title Screen" +msgstr "Ekran Tytułowy" -#: Source/itemdat.cpp:405 -msgid "the bat" -msgstr "nietoperza" +#: Source/options.cpp:611 +msgid "Diablo specific Settings" +msgstr "Ustawienia specyficzne dla Diablo" -#: Source/itemdat.cpp:406 -msgid "vampires" -msgstr "wampira" +#: Source/options.cpp:625 +msgid "Hellfire specific Settings" +msgstr "Opcje Hellfire" -#: Source/itemdat.cpp:407 -msgid "the leech" -msgstr "pijawki" +#: Source/options.cpp:639 +msgid "Audio" +msgstr "Audio" -#: Source/itemdat.cpp:408 -msgid "blood" -msgstr "krwi" +#: Source/options.cpp:639 +msgid "Audio Settings" +msgstr "Ustawienia Audio" -#: Source/itemdat.cpp:409 -msgid "piercing" -msgstr "rozdarcia" +#: Source/options.cpp:642 +msgid "Walking Sound" +msgstr "Dźwięk chodzenia" -#: Source/itemdat.cpp:410 -msgid "puncturing" -msgstr "przebicia" +#: Source/options.cpp:642 +msgid "Player emits sound when walking." +msgstr "Gracz wydaje dźwięki podczas chodzenia." -#: Source/itemdat.cpp:411 -msgid "bashing" -msgstr "natarcia" +#: Source/options.cpp:643 +msgid "Auto Equip Sound" +msgstr "Auto Wyposażenie Dźwięk" -#: Source/itemdat.cpp:412 -msgid "readiness" -msgstr "gotowości" +#: Source/options.cpp:643 +msgid "Automatically equipping items on pickup emits the equipment sound." +msgstr "" +"Automatyczne zakładanie przedmiotów przy podniesieniu powoduje emisję " +"dźwięku." -#: Source/itemdat.cpp:413 -msgid "swiftness" -msgstr "zwinności" +#: Source/options.cpp:644 +msgid "Item Pickup Sound" +msgstr "Dźwięk podnoszenia przedmiotu" -#: Source/itemdat.cpp:414 -msgid "speed" -msgstr "szybkości" +#: Source/options.cpp:644 +msgid "Picking up items emits the items pickup sound." +msgstr "" +"Podnoszenie przedmiotów powoduje emisję dźwięku podnoszenia przedmiotów." -#: Source/itemdat.cpp:415 -msgid "haste" -msgstr "pośpiechu" +#: Source/options.cpp:645 +msgid "Sample Rate" +msgstr "Częstotliwość próbkowania" -#: Source/itemdat.cpp:416 -msgid "balance" -msgstr "równowagi" +#: Source/options.cpp:645 +msgid "Output sample rate (Hz)." +msgstr "Częstotliwość próbkowania wyjścia (Hz)." -#: Source/itemdat.cpp:417 -msgid "stability" -msgstr "zaufania" +#: Source/options.cpp:646 +msgid "Channels" +msgstr "Kanały" -#: Source/itemdat.cpp:418 -msgid "harmony" -msgstr "harmonii" +#: Source/options.cpp:646 +msgid "Number of output channels." +msgstr "Liczba kanałów wyjścia." -#: Source/itemdat.cpp:419 -msgid "blocking" -msgstr "blokowania" +#: Source/options.cpp:647 +msgid "Buffer Size" +msgstr "Rozmiar Bufora" -#: Source/itemdat.cpp:420 -msgid "devastation" -msgstr "destrukcji" +#: Source/options.cpp:647 +msgid "Buffer size (number of frames per channel)." +msgstr "Rozmiar bufora (liczba ramek na kanał)." -#: Source/itemdat.cpp:421 -msgid "decay" -msgstr "rozkładu" +#: Source/options.cpp:648 +msgid "Resampling Quality" +msgstr "Jakość powtórnego próbkowania" -#. TRANSLATORS: Item suffix section end. -#: Source/itemdat.cpp:423 -msgid "peril" -msgstr "zagrożenia" +#: Source/options.cpp:648 +msgid "Quality of the resampler, from 0 (lowest) to 10 (highest)." +msgstr "Jakość resamplera, od 0 (najniższa) do 10 (najwyższa)." -#. TRANSLATORS: Unique Item section -#: Source/itemdat.cpp:433 -msgid "The Butcher's Cleaver" -msgstr "Tasak Rzeźnika" +#: Source/options.cpp:679 +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 "" +"Wpływa na wew. rozdzielczość gry i określa obszar widzenia użytkownika. " +"Uwaga: Rozdzielczość ta może się różnić od roz. ekranu, dla funkcji - " +"Zwiększanie, Skalowanie Całkowite, Dopasuj do Ekranu." -#: Source/itemdat.cpp:443 -msgid "The Rift Bow" -msgstr "Łuk Szybkości" +#: Source/options.cpp:825 +msgid "Resampler" +msgstr "" -#: Source/itemdat.cpp:444 -msgid "The Needler" -msgstr "Iglica" +#: Source/options.cpp:825 +msgid "Audio resampler" +msgstr "" -#: Source/itemdat.cpp:445 -msgid "The Celestial Bow" -msgstr "Niebiański Łuk" +#: Source/options.cpp:882 +msgid "Device" +msgstr "Urządzenie" -#: Source/itemdat.cpp:446 -msgid "Deadly Hunter" -msgstr "Mroczny Łowca" +#: Source/options.cpp:882 +msgid "Audio device" +msgstr "Urządzenie audio" -#: Source/itemdat.cpp:447 -msgid "Bow of the Dead" -msgstr "Śmiercionośny Łuk" +#: Source/options.cpp:950 +msgid "Graphics" +msgstr "Grafika" -#: Source/itemdat.cpp:448 -msgid "The Blackoak Bow" -msgstr "Łuk z Mrocznego Dębu" +#: Source/options.cpp:950 +msgid "Graphics Settings" +msgstr "Ustawienia Graficzne" -#: Source/itemdat.cpp:449 -msgid "Flamedart" -msgstr "Płomienny Łuk" +#: Source/options.cpp:951 +msgid "Fullscreen" +msgstr "Pełny Ekran" -#: Source/itemdat.cpp:450 -msgid "Fleshstinger" -msgstr "Przebijacz" +#: Source/options.cpp:951 +msgid "Display the game in windowed or fullscreen mode." +msgstr "Wyświetlanie gry w trybie okienkowym lub pełnoekranowym." -#: Source/itemdat.cpp:451 -msgid "Windforce" -msgstr "Siła Wiatru" +#: Source/options.cpp:953 +msgid "Fit to Screen" +msgstr "Dopasuj do ekranu" -#: Source/itemdat.cpp:452 -msgid "Eaglehorn" -msgstr "Orli Róg" +#: Source/options.cpp:953 +msgid "" +"Automatically adjust the game window to your current desktop screen aspect " +"ratio and resolution." +msgstr "" +"Automatycznie dostosuj okno gry do bieżących proporcji i rozdzielczości " +"ekranu pulpitu." -#: Source/itemdat.cpp:453 -msgid "Gonnagal's Dirk" -msgstr "Sztylet Gonnagala" +#: Source/options.cpp:956 +msgid "Upscale" +msgstr "Zwiększanie Rozdzielczości" -#: Source/itemdat.cpp:454 -msgid "The Defender" -msgstr "Obrońca" +#: Source/options.cpp:956 +msgid "" +"Enables image scaling from the game resolution to your monitor resolution. " +"Prevents changing the monitor resolution and allows window resizing." +msgstr "" +"Umożliwia skalowanie obrazu z rozdzielczości gry do rozdzielczości monitora. " +"Zapobiega zmianie rozdzielczości monitora i umożliwia zmianę rozmiaru okna." -#: Source/itemdat.cpp:455 -msgid "Gryphon's Claw" -msgstr "Pazur Gryfa" +#: Source/options.cpp:963 +msgid "Scaling Quality" +msgstr "Rodzaj skalowania" -#: Source/itemdat.cpp:456 -msgid "Black Razor" -msgstr "Czarna Brzytwa" +#: Source/options.cpp:963 +msgid "Enables optional filters to the output image when upscaling." +msgstr "" +"Włącza opcjonalne filtry dla obrazu wyjściowego podczas skalowania w górę." -#: Source/itemdat.cpp:457 -msgid "Gibbous Moon" -msgstr "Zaćmienie Księżyca" +#: Source/options.cpp:965 +msgid "Nearest Pixel" +msgstr "Najbliższy piksel" -#: Source/itemdat.cpp:458 -msgid "Ice Shank" -msgstr "Ostrze Mrozu" +#: Source/options.cpp:966 +msgid "Bilinear" +msgstr "Bilinearne" -#: Source/itemdat.cpp:459 -msgid "The Executioner's Blade" -msgstr "Miecz Kata" +#: Source/options.cpp:967 +msgid "Anisotropic" +msgstr "Anizotropowe" -#: Source/itemdat.cpp:460 -msgid "The Bonesaw" -msgstr "Przecinacz Kości" +#: Source/options.cpp:969 +msgid "Integer Scaling" +msgstr "Skalowanie Całkowite" -#: Source/itemdat.cpp:461 -msgid "Shadowhawk" -msgstr "Orła Cień" +#: Source/options.cpp:969 +msgid "Scales the image using whole number pixel ratio." +msgstr "Skaluje obraz przy użyciu współczynnika liczby całkowitej pikseli." -#: Source/itemdat.cpp:462 -msgid "Wizardspike" -msgstr "Kolec Czarownika" +#: Source/options.cpp:976 +msgid "Vertical Sync" +msgstr "Synchronizacja pionowa" -#: Source/itemdat.cpp:463 -msgid "Lightsabre" -msgstr "Miecz Światłości" +#: Source/options.cpp:977 +msgid "" +"Forces waiting for Vertical Sync. Prevents tearing effect when drawing a " +"frame. Disabling it can help with mouse lag on some systems." +msgstr "" +"Wymusza oczekiwanie na synchronizację pionową. Zapobiega efektowi rozrywania " +"podczas rysowania klatki. Wyłączenie tej funkcji może pomóc w eliminacji " +"opóźnień myszy w niektórych systemach." -#: Source/itemdat.cpp:464 -msgid "The Falcon's Talon" -msgstr "Szpon Sokoła" - -#: Source/itemdat.cpp:465 -msgid "Inferno" -msgstr "Inferno" +#: Source/options.cpp:986 +msgid "Zoom on when enabled." +msgstr "" -#: Source/itemdat.cpp:466 -msgid "Doombringer" -msgstr "Herold Zagłady" +#: Source/options.cpp:987 +msgid "Color Cycling" +msgstr "Przełączanie kolorów" -#: Source/itemdat.cpp:467 -msgid "The Grizzly" -msgstr "Niedźwiedź" +#: Source/options.cpp:987 +msgid "Color cycling effect used for water, lava, and acid animation." +msgstr "" +"Efekt cyklicznych zmian kolorów stosowany w animacjach wody, lawy i kwasu." -#: Source/itemdat.cpp:468 -msgid "The Grandfather" -msgstr "Pradziad" +#: Source/options.cpp:988 +msgid "Alternate nest art" +msgstr "Użyj alternatywnej grafiki gniazda" -#: Source/itemdat.cpp:469 -msgid "The Mangler" -msgstr "Okulawiacz" +#: Source/options.cpp:988 +msgid "The game will use an alternative palette for Hellfire’s nest tileset." +msgstr "" +"W grze zostanie wykorzystana alternatywna paleta kafelków gniazda Hellfire." -#: Source/itemdat.cpp:470 -msgid "Sharp Beak" -msgstr "Zakrzywiony Dziób" +#: Source/options.cpp:990 +msgid "Hardware Cursor" +msgstr "Kursor sprzętowy" -#: Source/itemdat.cpp:471 -msgid "BloodSlayer" -msgstr "Krwawy Pogromca" +#: Source/options.cpp:990 +msgid "Use a hardware cursor" +msgstr "Użyj kursora sprzętowego" -#: Source/itemdat.cpp:472 -msgid "The Celestial Axe" -msgstr "Niebiański Topór" +#: Source/options.cpp:991 +msgid "Hardware Cursor For Items" +msgstr "Kursor sprzęt. dla przedmiotów" -#: Source/itemdat.cpp:473 -msgid "Wicked Axe" -msgstr "Niegodziwy Topór" +#: Source/options.cpp:991 +msgid "Use a hardware cursor for items." +msgstr "Użyj kursora sprzętowego dla przedmiotów." -#: Source/itemdat.cpp:474 -msgid "Stonecleaver" -msgstr "Kamienny Tasak" +#: Source/options.cpp:992 +msgid "Hardware Cursor Maximum Size" +msgstr "Maks. rozmiar kursora sprzętowego" -#: Source/itemdat.cpp:475 -msgid "Aguinara's Hatchet" -msgstr "Topór Aguinary" +#: Source/options.cpp:992 +msgid "" +"Maximum width / height for the hardware cursor. Larger cursors fall back to " +"software." +msgstr "" +"Maksymalna szerokość / wysokość kursora sprzętowego. Większe kursory są " +"przełączane z powrotem do programowego." -#: Source/itemdat.cpp:476 -msgid "Hellslayer" -msgstr "Pogromca Piekieł" +#: Source/options.cpp:994 +msgid "FPS Limiter" +msgstr "Ogranicznik FPS" -#: Source/itemdat.cpp:477 -msgid "Messerschmidt's Reaver" -msgstr "Rozpruwacz Messerschmidta" +#: Source/options.cpp:994 +msgid "FPS is limited to avoid high CPU load. Limit considers refresh rate." +msgstr "" +"Liczba FPS jest ograniczona, aby uniknąć dużego obciążenia procesora. " +"Ograniczenie uwzględnia częstotliwość odświeżania." -#: Source/itemdat.cpp:478 -msgid "Crackrust" -msgstr "Rdzawy Łomot" +#: Source/options.cpp:995 +msgid "Show FPS" +msgstr "Pokaż FPS" -#: Source/itemdat.cpp:479 -msgid "Hammer of Jholm" -msgstr "Młot Jholma" +#: Source/options.cpp:995 +msgid "Displays the FPS in the upper left corner of the screen." +msgstr "Wyświetlanie liczby FPS w lewym górnym rogu ekranu." -#: Source/itemdat.cpp:480 -msgid "Civerb's Cudgel" -msgstr "Pałka Civerba" +#: Source/options.cpp:1043 +msgid "Gameplay" +msgstr "Rozgrywka" -#: Source/itemdat.cpp:481 -msgid "The Celestial Star" -msgstr "Niebiańska Gwiazda" +#: Source/options.cpp:1043 +msgid "Gameplay Settings" +msgstr "Ustawienia Rozgrywki" -#: Source/itemdat.cpp:482 -msgid "Baranar's Star" -msgstr "Gwiazda Baranara" +#: Source/options.cpp:1045 +msgid "" +"Enable jogging/fast walking in town for Diablo and Hellfire. This option was " +"introduced in the expansion." +msgstr "" +"Włączenie biegania/szybkiego chodzenia w mieście dla Diablo i Hellfire. " +"Opcja ta została wprowadzona w dodatku." -#: Source/itemdat.cpp:483 -msgid "Gnarled Root" -msgstr "Spaczony Korzeń" +#: Source/options.cpp:1046 +msgid "Grab Input" +msgstr "Chwytanie Wejścia" -#: Source/itemdat.cpp:484 -msgid "The Cranium Basher" -msgstr "Łamiczerep" +#: Source/options.cpp:1046 +msgid "When enabled mouse is locked to the game window." +msgstr "Po włączeniu tej funkcji mysz jest zablokowana w oknie gry." -#: Source/itemdat.cpp:485 -msgid "Schaefer's Hammer" -msgstr "Młot Schaefera" +#: Source/options.cpp:1047 +msgid "Enable Little Girl quest." +msgstr "Włącz zadanie Mała Dziewczynka." -#: Source/itemdat.cpp:486 -msgid "Dreamflange" -msgstr "Poskramiacz Snów" +#: Source/options.cpp:1048 +msgid "" +"Enable Jersey's quest. Lester the farmer is replaced by the Complete Nut." +msgstr "" +"Włącz zadanie Jersey. Rolnik Lester zostaje zastąpiony przez Kompletnego " +"Wariata." -#: Source/itemdat.cpp:487 -msgid "Staff of Shadows" -msgstr "Kostur Cieni" +#: Source/options.cpp:1049 +msgid "Friendly Fire" +msgstr "Przyjacielski ogień" -#: Source/itemdat.cpp:488 -msgid "Immolator" -msgstr "Kostur Ofiarny" +#: Source/options.cpp:1049 +msgid "" +"Allow arrow/spell damage between players in multiplayer even when the " +"friendly mode is on." +msgstr "" +"Umożliwienie zadawania obrażeń od strzał/czarów między graczami w trybie " +"wieloosobowym, nawet jeśli włączony jest tryb przyjazny." -#: Source/itemdat.cpp:489 -msgid "Storm Spire" -msgstr "Szturmowa Iglica" +#: Source/options.cpp:1050 +msgid "Full quests in Multiplayer" +msgstr "Pełne zadania w grach wieloosobowych" -#: Source/itemdat.cpp:490 -msgid "Gleamsong" -msgstr "Złudna Pieśń" +#: Source/options.cpp:1050 +msgid "Enables the full/uncut singleplayer version of quests." +msgstr "Włącza pełne wersje zadań z gry jednoosobowej." -#: Source/itemdat.cpp:491 -msgid "Thundercall" -msgstr "Zew Burzy" +#: Source/options.cpp:1051 +msgid "Test Bard" +msgstr "Testuj Barda" -#: Source/itemdat.cpp:492 -msgid "The Protector" -msgstr "Orędownik" +#: Source/options.cpp:1051 +msgid "Force the Bard character type to appear in the hero selection menu." +msgstr "Wymuś wyświetlanie typu postaci Bard w menu wyboru bohatera." -#: Source/itemdat.cpp:493 -msgid "Naj's Puzzler" -msgstr "Zagadka Naja" +#: Source/options.cpp:1052 +msgid "Test Barbarian" +msgstr "Testuj Barbarzyńcę" -#: Source/itemdat.cpp:494 -msgid "Mindcry" -msgstr "Płacz Umysłu" +#: Source/options.cpp:1052 +msgid "" +"Force the Barbarian character type to appear in the hero selection menu." +msgstr "" +"Wymuszenie wyświetlania typu postaci Barbarzyńca w menu wyboru bohatera." -#: Source/itemdat.cpp:495 -msgid "Rod of Onan" -msgstr "Drąg Onana" +#: Source/options.cpp:1053 +msgid "Experience Bar" +msgstr "Pasek Doświadczenia" -#: Source/itemdat.cpp:496 -msgid "Helm of Spirits" -msgstr "Korona Dusz" +#: Source/options.cpp:1053 +msgid "Experience Bar is added to the UI at the bottom of the screen." +msgstr "" +"Pasek doświadczenia zostanie dodany do interfejsu użytkownika w dolnej " +"części ekranu." -#: Source/itemdat.cpp:497 -msgid "Thinking Cap" -msgstr "Kaptur Umysłu" +#: Source/options.cpp:1054 +msgid "Show Item Graphics in Stores" +msgstr "Pokaż grafiki przedmiotów w sklepach" -#: Source/itemdat.cpp:498 -msgid "OverLord's Helm" -msgstr "Hełm Nadzorcy" +#: Source/options.cpp:1054 +msgid "Show item graphics to the left of item descriptions in store menus." +msgstr "Pokaż grafiki przedmiotów na lewo od opisu w menu sklepów." -#: Source/itemdat.cpp:499 -msgid "Fool's Crest" -msgstr "Korona Błazna" +#: Source/options.cpp:1055 +msgid "Show health values" +msgstr "Pokaż punkty życia" -#: Source/itemdat.cpp:500 -msgid "Gotterdamerung" -msgstr "Zmierzch Bogów" +#: Source/options.cpp:1055 +msgid "Displays current / max health value on health globe." +msgstr "Wyświetla aktualną / maksymalną wartość stanu zdrowia na kuli zdrowia." -#: Source/itemdat.cpp:501 -msgid "Royal Circlet" -msgstr "Królewski Diadem" +#: Source/options.cpp:1056 +msgid "Show mana values" +msgstr "Pokaż punkty many" -#: Source/itemdat.cpp:502 -msgid "Torn Flesh of Souls" -msgstr "Strzępek Duszy" +#: Source/options.cpp:1056 +msgid "Displays current / max mana value on mana globe." +msgstr "Wyświetla aktualną / maksymalną wartość many na kuli many." -#: Source/itemdat.cpp:503 -msgid "The Gladiator's Bane" -msgstr "Zguba Gladiatora" +#: Source/options.cpp:1057 +msgid "Enemy Health Bar" +msgstr "Pasek zdrowia wroga" -#: Source/itemdat.cpp:504 -msgid "The Rainbow Cloak" -msgstr "Szata Tęczy" +#: Source/options.cpp:1057 +msgid "Enemy Health Bar is displayed at the top of the screen." +msgstr "Pasek zdrowia wroga zostanie wyświetlony w górnej części ekranu." -#: Source/itemdat.cpp:505 -msgid "Leather of Aut" -msgstr "Zbroja Giermka" +#: Source/options.cpp:1058 +msgid "Gold is automatically collected when in close proximity to the player." +msgstr "Złoto jest automatycznie zbierane, gdy znajduje się w pobliżu gracza." -#: Source/itemdat.cpp:506 -msgid "Wisdom's Wrap" -msgstr "Szata Mądrości" +#: Source/options.cpp:1059 +msgid "" +"Elixirs are automatically collected when in close proximity to the player." +msgstr "Eliksiry są automatycznie zbierane, gdy znajdują się w pobliżu gracza." -#: Source/itemdat.cpp:507 -msgid "Sparking Mail" -msgstr "Iskrząca Kolczuga" +#: Source/options.cpp:1060 +msgid "Oils are automatically collected when in close proximity to the player." +msgstr "Oleje są automatycznie zbierane, gdy znajdują się w pobliżu gracza." -#: Source/itemdat.cpp:508 -msgid "Scavenger Carapace" -msgstr "Pancerz Ścierwojada" +#: Source/options.cpp:1061 +msgid "Automatically pickup items in town." +msgstr "Automatycznie podnoś przedmioty w mieście." -#: Source/itemdat.cpp:509 -msgid "Nightscape" -msgstr "Mroczny Uciekinier" +#: Source/options.cpp:1062 +msgid "Adria will refill your mana when you visit her shop." +msgstr "Adria uzupełni twoją manę, gdy odwiedzisz jej sklep." -#: Source/itemdat.cpp:510 -msgid "Naj's Light Plate" -msgstr "Lekki Pancerz Naja" +#: Source/options.cpp:1063 +msgid "" +"Weapons will be automatically equipped on pickup or purchase if enabled." +msgstr "" +"Broń zostanie automatycznie założona przy podnoszeniu lub zakupie, jeśli " +"dana opcja jest włączona." -#: Source/itemdat.cpp:511 -msgid "Demonspike Coat" -msgstr "Demoniczny Pancerz" +#: Source/options.cpp:1064 +msgid "Armor will be automatically equipped on pickup or purchase if enabled." +msgstr "" +"Pancerze będą automatycznie zakładane przy podnoszeniu lub zakupie, jeśli " +"dana opcja jest włączona." -#: Source/itemdat.cpp:512 -msgid "The Deflector" -msgstr "Bariera" +#: Source/options.cpp:1065 +msgid "Helms will be automatically equipped on pickup or purchase if enabled." +msgstr "" +"Hełmy będą automatycznie zakładane przy podnoszeniu lub zakupie, jeśli dana " +"opcja jest włączona." -#: Source/itemdat.cpp:513 -msgid "Split Skull Shield" -msgstr "Tarcza Czaszki" +#: Source/options.cpp:1066 +msgid "" +"Shields will be automatically equipped on pickup or purchase if enabled." +msgstr "" +"Tarcze będą automatycznie zakładane przy podnoszeniu lub zakupie, jeśli dana " +"opcja jest włączona." -#: Source/itemdat.cpp:514 -msgid "Dragon's Breach" -msgstr "Smocza Osłona" +#: Source/options.cpp:1067 +msgid "" +"Jewelry will be automatically equipped on pickup or purchase if enabled." +msgstr "" +"Biżuteria zostanie automatycznie założona przy podnoszeniu lub zakupie, " +"jeśli dana opcja jest włączona." -#: Source/itemdat.cpp:515 -msgid "Blackoak Shield" -msgstr "Tarcza z Mrocznego Dębu" +#: Source/options.cpp:1068 +msgid "Randomly selecting available quests for new games." +msgstr "Losowe wybieranie dostępnych zadań dla nowych gier." -#: Source/itemdat.cpp:516 -msgid "Holy Defender" -msgstr "Święty Obrońca" +#: Source/options.cpp:1069 +msgid "Show Monster Type" +msgstr "Pokaż typ potwora" -#: Source/itemdat.cpp:517 -msgid "Stormshield" -msgstr "Tarcza Burzy" +#: Source/options.cpp:1069 +msgid "" +"Hovering over a monster will display the type of monster in the description " +"box in the UI." +msgstr "" +"Najechanie kursorem myszy na potwora spowoduje wyświetlenie jego typu w polu " +"opisu w interfejsie użytkownika." -#: Source/itemdat.cpp:518 -msgid "Bramble" -msgstr "Cierń" +#: Source/options.cpp:1070 +msgid "Show labels for items on the ground when enabled." +msgstr "" +"Pokazuje etykiety przedmiotów znajdujących się na ziemi kiedy włączone." -#: Source/itemdat.cpp:519 -msgid "Ring of Regha" -msgstr "Pierścień Regha" +#: Source/options.cpp:1071 +msgid "Refill belt from inventory when belt item is consumed." +msgstr "Uzupełnij pasek z ekwipunku, gdy przedmiot w nim zostanie zużyty." -#: Source/itemdat.cpp:520 -msgid "The Bleeder" -msgstr "Krwotok" - -#: Source/itemdat.cpp:521 -msgid "Constricting Ring" -msgstr "Pierścień Uciśnienia" - -#: Source/itemdat.cpp:522 -msgid "Ring of Engagement" -msgstr "Pierścień Zobowiązania" +#: Source/options.cpp:1072 +msgid "" +"When enabled Cauldrons, Fascinating Shrines, Goat Shrines, Ornate Shrines, " +"Sacred Shrines and Murphy's Shrines are not able to be clicked on and " +"labeled as disabled." +msgstr "" +"Po włączeniu opcji Kociołki, Fascynujące Kapliczki, Kozie Kapliczki, " +"Zdobione Kapliczki, Święte Kapliczki i Kapliczki Murphy'ego nie mogą zostać " +"kliknięte i są one oznaczane jako wyłączone." -#: Source/itemdat.cpp:523 -msgid "Giant's Knuckle" -msgstr "Pierścień Olbrzyma" +#: Source/options.cpp:1073 +msgid "Quick Cast" +msgstr "Szybki Czar" -#: Source/itemdat.cpp:524 -msgid "Mercurial Ring" -msgstr "Pierścień Merkurego" +#: Source/options.cpp:1073 +msgid "" +"Spell hotkeys instantly cast the spell, rather than switching the readied " +"spell." +msgstr "" +"Naciśnięcie klawisza skrótu zaklęcia powoduje natychmiastowe rzucenie " +"zaklęcia, zamiast przełączania gotowego zaklęcia." -#: Source/itemdat.cpp:525 -msgid "Xorine's Ring" -msgstr "Pierścień Xorina" +#: Source/options.cpp:1074 +msgid "Number of Healing potions to pick up automatically." +msgstr "Liczba mikstur Leczenia, które są podnoszone automatycznie." -#: Source/itemdat.cpp:526 -msgid "Karik's Ring" -msgstr "Pierścień Karika" +#: Source/options.cpp:1075 +msgid "Number of Full Healing potions to pick up automatically." +msgstr "Liczba mikstur Pełnego Leczenia, które są podnoszone automatycznie." -#: Source/itemdat.cpp:527 -msgid "Ring of Magma" -msgstr "Pierścień Magmy" +#: Source/options.cpp:1076 +msgid "Number of Mana potions to pick up automatically." +msgstr "Liczba mikstur Many, które będą podnoszone automatycznie." -#: Source/itemdat.cpp:528 -msgid "Ring of the Mystics" -msgstr "Pierścień Mistyków" +#: Source/options.cpp:1077 +msgid "Number of Full Mana potions to pick up automatically." +msgstr "Liczba mikstur Pełnej Many, które będą podnoszone automatycznie." -#: Source/itemdat.cpp:529 -msgid "Ring of Thunder" -msgstr "Pierścień Burzy" +#: Source/options.cpp:1078 +msgid "Number of Rejuvenation potions to pick up automatically." +msgstr "Liczba mikstur Wzmocnienia, które będą podnoszone automatycznie." -#: Source/itemdat.cpp:530 -msgid "Amulet of Warding" -msgstr "Amulet Ochrony" +#: Source/options.cpp:1079 +msgid "Number of Full Rejuvenation potions to pick up automatically." +msgstr "" +"Liczba mikstur Pełnego Wzmocnienia, które będą podnoszone automatycznie." -#: Source/itemdat.cpp:531 -msgid "Gnat Sting" -msgstr "Ukąszenie Komara" +#: Source/options.cpp:1080 +msgid "Enable floating numbers" +msgstr "Pokazuj liczbowe wartości" -#: Source/itemdat.cpp:532 -msgid "Flambeau" -msgstr "Żarliwiec" +#: Source/options.cpp:1080 +msgid "Enables floating numbers on gaining XP / dealing damage etc." +msgstr "" +"Pokazuje liczbowe wartości przy zdobywaniu doświadczenia / zadawaniu obrażeń " +"etc." -#: Source/itemdat.cpp:533 -msgid "Armor of Gloom" -msgstr "Zbroja Posępności" +#: Source/options.cpp:1082 +msgid "Off" +msgstr "Wyłączone" -#: Source/itemdat.cpp:534 -msgid "Blitzen" -msgstr "Przebłysk" +#: Source/options.cpp:1083 +msgid "Random Angles" +msgstr "Losowy kierunek" -#: Source/itemdat.cpp:535 -msgid "Thunderclap" -msgstr "Uderzenie Pioruna" +#: Source/options.cpp:1084 +msgid "Vertical Only" +msgstr "Tylko w pionie" -#: Source/itemdat.cpp:536 -msgid "Shirotachi" -msgstr "Shirotachi" +#: Source/options.cpp:1135 +msgid "Controller" +msgstr "Kontroler" -#: Source/itemdat.cpp:537 -msgid "Eater of Souls" -msgstr "Pożeracz Dusz" +#: Source/options.cpp:1135 +msgid "Controller Settings" +msgstr "Ustawienia Kontrolera" -#: Source/itemdat.cpp:538 -msgid "Diamondedge" -msgstr "Diamentowe Ostrze" +#: Source/options.cpp:1144 +msgid "Network" +msgstr "Sieć" -#: Source/itemdat.cpp:539 -msgid "Bone Chain Armor" -msgstr "Kościana Kolczuga" +#: Source/options.cpp:1144 +msgid "Network Settings" +msgstr "Ustawienia Sieciowe" -#: Source/itemdat.cpp:540 -msgid "Demon Plate Armor" -msgstr "Łuska Demona" +#: Source/options.cpp:1156 +msgid "Chat" +msgstr "Czat" -#: Source/itemdat.cpp:541 -msgid "Acolyte's Amulet" -msgstr "Amulet Akolity" +#: Source/options.cpp:1156 +msgid "Chat Settings" +msgstr "Ustawienia czatu" -#. TRANSLATORS: Unique Item section end. -#: Source/itemdat.cpp:543 -msgid "Gladiator's Ring" -msgstr "Pierścień Gladiatora" +#: Source/options.cpp:1165 Source/options.cpp:1281 +msgid "Language" +msgstr "Język" -#: Source/items.cpp:168 -msgid "Oil of Mastery" -msgstr "Olej Opanowania" +#: Source/options.cpp:1165 +msgid "Define what language to use in game." +msgstr "Określ, jakiego języka należy używać w grze." -#: Source/items.cpp:170 -msgid "Oil of Death" -msgstr "Olej Śmierci" +#: Source/options.cpp:1281 +msgid "Language Settings" +msgstr "Ustawienia Języka" -#: Source/items.cpp:171 -msgid "Oil of Skill" -msgstr "Olej Umiejętności" +#: Source/options.cpp:1293 +msgid "Keymapping" +msgstr "Mapowanie Klawiszy" -#: Source/items.cpp:173 -msgid "Oil of Fortitude" -msgstr "Olej Wytrzymałości" +#: Source/options.cpp:1293 +msgid "Keymapping Settings" +msgstr "Ustawienia mapowania klawiszy" -#: Source/items.cpp:174 -msgid "Oil of Permanence" -msgstr "Olej Niezniszczalności" +#: Source/options.cpp:1551 +msgid "Padmapping" +msgstr "Mapowanie kontrolera" -#: Source/items.cpp:175 -msgid "Oil of Hardening" -msgstr "Olej Wzmocnienia" +#: Source/options.cpp:1551 +msgid "Padmapping Settings" +msgstr "Ustawienia mapowania kontrolera" -#: Source/items.cpp:176 -msgid "Oil of Imperviousness" -msgstr "Olej Nieprzenikalności" +#: Source/panels/charpanel.cpp:128 +msgid "Level" +msgstr "Poziom" -#. 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}" +#: Source/panels/charpanel.cpp:130 +msgid "Experience" +msgstr "Doświadczenie" -#. 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}" +#: Source/panels/charpanel.cpp:135 +msgid "Next level" +msgstr "Następny poziom" -#. 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}" +#: Source/panels/charpanel.cpp:145 +msgid "Base" +msgstr "Baza" -#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item}. Example: King's Long Sword -#: Source/items.cpp:1178 -msgid "{0} {1}" -msgstr "{0} {1}" +#: Source/panels/charpanel.cpp:146 +msgid "Now" +msgstr "Teraz" -#. 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/panels/charpanel.cpp:147 +msgid "Strength" +msgstr "Siła" -#: Source/items.cpp:1716 Source/items.cpp:1724 -msgid "increases a weapon's" -msgstr "znacznie zwiększa" +#: Source/panels/charpanel.cpp:151 +msgid "Magic" +msgstr "Magia" -#: Source/items.cpp:1717 -msgid "chance to hit" -msgstr "celność broni" +#: Source/panels/charpanel.cpp:155 +msgid "Dexterity" +msgstr "Zręczność" -#: Source/items.cpp:1720 -msgid "greatly increases a" -msgstr "znacznie zwiększa" +#: Source/panels/charpanel.cpp:158 +msgid "Vitality" +msgstr "Żywotność" -#: Source/items.cpp:1721 -msgid "weapon's chance to hit" -msgstr "celność broni" +#: Source/panels/charpanel.cpp:161 +msgid "Points to distribute" +msgstr "Punkty do rozdania" -#: Source/items.cpp:1725 -msgid "damage potential" -msgstr "możliwe obrażenia" +#: Source/panels/charpanel.cpp:167 Source/translation_dummy.cpp:247 +#: Source/translation_dummy.cpp:606 +msgid "Gold" +msgstr "Złoto" -#: Source/items.cpp:1728 -msgid "greatly increases a weapon's" -msgstr "znacznie zwiększa" +#: Source/panels/charpanel.cpp:171 +msgid "Armor class" +msgstr "Obrona" -#: Source/items.cpp:1729 -msgid "damage potential - not bows" -msgstr "możliwe obrażenia (bez łuków)" +#: Source/panels/charpanel.cpp:173 +msgid "To hit" +msgstr "" +"Celność\n" +"ataku" -#: Source/items.cpp:1732 -msgid "reduces attributes needed" -msgstr "zmniejsza wymagane atrybuty" +#: Source/panels/charpanel.cpp:175 +msgid "Damage" +msgstr "Obrażenia" -#: Source/items.cpp:1733 -msgid "to use armor or weapons" -msgstr "i pozwala używać przedmioty" +#: Source/panels/charpanel.cpp:182 +msgid "Life" +msgstr "Życie" -#: Source/items.cpp:1736 -#, no-c-format -msgid "restores 20% of an" -msgstr "przywraca 20%" +#: Source/panels/charpanel.cpp:186 +msgid "Mana" +msgstr "Mana" -#: Source/items.cpp:1737 -msgid "item's durability" -msgstr "wytrzymałości przedmiotu" +#: Source/panels/charpanel.cpp:191 +msgid "Resist magic" +msgstr "" +"Odporności:\n" +"Magia\n" +" " -#: Source/items.cpp:1740 -msgid "increases an item's" -msgstr "zwiększa" +#: Source/panels/charpanel.cpp:193 +msgid "Resist fire" +msgstr "Ogień" -#: Source/items.cpp:1741 -msgid "current and max durability" -msgstr "bieżącą i maks. wytrzymałość" +#: Source/panels/charpanel.cpp:195 +msgid "Resist lightning" +msgstr "Błyskawice" -#: Source/items.cpp:1744 -msgid "makes an item indestructible" -msgstr "czyni rzecz niezniszczalną" +#: Source/panels/mainpanel.cpp:87 +msgid "char" +msgstr "postać" -#: Source/items.cpp:1747 -msgid "increases the armor class" -msgstr "zwiększa klasę pancerza" +#: Source/panels/mainpanel.cpp:88 +msgid "quests" +msgstr "zadania" -#: Source/items.cpp:1748 -msgid "of armor and shields" -msgstr "tarcz i zbroi" +#: Source/panels/mainpanel.cpp:89 +msgid "map" +msgstr "mapa" -#: Source/items.cpp:1751 -msgid "greatly increases the armor" -msgstr "znacznie zwiększa pancerz" +#: Source/panels/mainpanel.cpp:90 +msgid "menu" +msgstr "menu" -#: Source/items.cpp:1752 -msgid "class of armor and shields" -msgstr "klasę tarcz i zbroi" +#: Source/panels/mainpanel.cpp:91 +msgid "inv" +msgstr "ekwipunek" -#: Source/items.cpp:1755 Source/items.cpp:1762 -msgid "sets fire trap" -msgstr "ustawia ognistą pułapkę" +#: Source/panels/mainpanel.cpp:92 +msgid "spells" +msgstr "czary" -#: Source/items.cpp:1759 -msgid "sets lightning trap" -msgstr "ustawia pułapkę z błyskawic" +#: Source/panels/mainpanel.cpp:102 Source/panels/mainpanel.cpp:128 +#: Source/panels/mainpanel.cpp:130 +msgid "voice" +msgstr "czat" -#: Source/items.cpp:1765 -msgid "sets petrification trap" -msgstr "ustawia pułapkę Petryfikacji" +#: Source/panels/mainpanel.cpp:123 Source/panels/mainpanel.cpp:125 +#: Source/panels/mainpanel.cpp:127 +msgid "mute" +msgstr "cisza" -#: Source/items.cpp:1768 -msgid "restore all life" -msgstr "odnawia całe życie" +#: Source/panels/spell_book.cpp:116 +msgid "Unusable" +msgstr "Nieprzydatny" -#: Source/items.cpp:1771 -msgid "restore some life" -msgstr "odnawia część życia" +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:119 +msgid "Dmg: 1/3 target hp" +msgstr "Obr: 1/3 PŻ celu" -#: Source/items.cpp:1774 -msgid "recover life" -msgstr "przywraca życie" +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:126 +#, c++-format +msgid "Heals: {:d} - {:d}" +msgstr "Uzdrawia: {:d} - {:d}" -#: Source/items.cpp:1777 -msgid "deadly heal" -msgstr "śmiertelne leczenie" +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:128 +#, c++-format +msgid "Damage: {:d} - {:d}" +msgstr "Obrażenia: {:d} - {:d}" -#: Source/items.cpp:1780 -msgid "restore some mana" -msgstr "odnawia część many" +#: Source/panels/spell_book.cpp:183 Source/panels/spell_list.cpp:151 +msgid "Skill" +msgstr "Umiejętność" -#: Source/items.cpp:1783 -msgid "restore all mana" -msgstr "odnawia całą manę" - -#: Source/items.cpp:1786 -msgid "increase strength" -msgstr "zwiększa siłę" +#: Source/panels/spell_book.cpp:187 +#, c++-format +msgid "Staff ({:d} charge)" +msgid_plural "Staff ({:d} charges)" +msgstr[0] "Kostur ({:d} ładunek)" +msgstr[1] "Kostur ({:d} ładunki)" +msgstr[2] "Kostur ({:d} ładunków)" -#: Source/items.cpp:1789 -msgid "increase magic" -msgstr "zwiększa magię" +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:192 +#, c++-format +msgctxt "spellbook" +msgid "Level {:d}" +msgstr "Poziom {:d}" -#: Source/items.cpp:1792 -msgid "increase dexterity" -msgstr "zwiększa zręczność" +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:196 +#, c++-format +msgctxt "spellbook" +msgid "Mana: {:d}" +msgstr "Mana: {:d}" -#: Source/items.cpp:1795 -msgid "increase vitality" -msgstr "zwiększa żywotność" +#: Source/panels/spell_list.cpp:158 +msgid "Spell" +msgstr "Czar" -#: Source/items.cpp:1799 -msgid "decrease strength" -msgstr "zmniejsza siłę" +#: Source/panels/spell_list.cpp:161 +msgid "Damages undead only" +msgstr "Rani tylko nieumarłych" -#: Source/items.cpp:1802 -msgid "decrease dexterity" -msgstr "zmniejsza zręczność" +#: Source/panels/spell_list.cpp:172 +msgid "Scroll" +msgstr "Zwój" -#: Source/items.cpp:1805 -msgid "decrease vitality" -msgstr "zmniejsza żywotność" +#: Source/panels/spell_list.cpp:183 Source/translation_dummy.cpp:459 +#: Source/translation_dummy.cpp:461 Source/translation_dummy.cpp:463 +#: Source/translation_dummy.cpp:465 Source/translation_dummy.cpp:467 +msgid "Staff" +msgstr "Kostur" -#: Source/items.cpp:1808 -msgid "restore some life and mana" -msgstr "odnawia część życia i many" +#: Source/panels/spell_list.cpp:193 +#, c++-format +msgid "Spell Hotkey {:s}" +msgstr "Klawisz Czaru: {:s}" -#: Source/items.cpp:1811 -msgid "restore all life and mana" -msgstr "odnawia całe życie i manę" +#: Source/pfile.cpp:745 +msgid "Unable to open archive" +msgstr "Nie udało się wczytać zawartości" -#: Source/items.cpp:1826 Source/items.cpp:1866 -msgid "Right-click to read" -msgstr "PPM by odczytać" +#: Source/pfile.cpp:747 +msgid "Unable to load character" +msgstr "Nie udało się wczytać postaci" -#: Source/items.cpp:1829 Source/items.cpp:1844 Source/items.cpp:1858 -msgid "Open inventory to use" -msgstr "Otwórz ekwipunek, aby użyć" +#: Source/plrmsg.cpp:85 Source/qol/chatlog.cpp:129 +#, c++-format +msgid "{:s} (lvl {:d}): " +msgstr "{:s} (lvl {:d}): " -#: Source/items.cpp:1831 Source/items.cpp:1846 Source/items.cpp:1868 -msgid "Activate to read" -msgstr "Aktywuj, aby przeczytać" +#: Source/qol/chatlog.cpp:169 +#, c++-format +msgid "Chat History (Messages: {:d})" +msgstr "Historia czatu (Wiadomości: {:d})" -#: Source/items.cpp:1837 -msgid "Right-click to read, then\nleft-click to target" -msgstr "PPM by odczytać, następnie\nLPM na cel" +#: Source/qol/itemlabels.cpp:107 +#, c++-format +msgid "{:s} gold" +msgstr "{:s} złota" -#: Source/items.cpp:1841 -msgid "Select from spell book, then\ncast spell to read" -msgstr "Wybierz z księgi zaklęć, a następnie\nrzuć zaklęcie, aby przeczytać" +#: Source/qol/stash.cpp:650 +msgid "How many gold pieces do you want to withdraw?" +msgstr "Ile sztuk złota chcesz wypłacić?" -#: Source/items.cpp:1855 -msgid "Right-click to use" -msgstr "PPM by użyć" +#: Source/qol/xpbar.cpp:125 +#, c++-format +msgid "Level {:d}" +msgstr "Poziom {:d}" -#: Source/items.cpp:1860 -msgid "Activate to use" -msgstr "Aktywuj, aby użyć" +#: Source/qol/xpbar.cpp:131 Source/qol/xpbar.cpp:139 +#, c++-format +msgid "Experience: {:s}" +msgstr "Doświadczenie: {:s}" -#: Source/items.cpp:1873 -msgid "Right-click to view" -msgstr "PPM by wyświetlić" +#: Source/qol/xpbar.cpp:132 +msgid "Maximum Level" +msgstr "Maksymalny Poziom" -#: Source/items.cpp:1875 -msgid "Activate to view" -msgstr "Aktywuj, aby wyświetlić" +#: Source/qol/xpbar.cpp:141 +#, c++-format +msgid "Next Level: {:s}" +msgstr "Następny poziom: {:s}" -#: Source/items.cpp:1879 -msgctxt "player" -msgid "Level: {:d}" -msgstr "Poziom: {:d}" +#: Source/qol/xpbar.cpp:142 +#, c++-format +msgid "{:s} to Level {:d}" +msgstr "{:s} do Poziomu {:d}" -#: Source/items.cpp:1882 -msgid "Doubles gold capacity" -msgstr "Podwaja objętość sakwy" +#. TRANSLATORS: Quest Name Block +#: Source/quests.cpp:52 +msgid "The Magic Rock" +msgstr "Magiczny Kamień" -#: Source/items.cpp:1893 Source/stores.cpp:285 -msgid "Required:" -msgstr "Wymagania:" +#: Source/quests.cpp:53 Source/translation_dummy.cpp:264 +msgid "Black Mushroom" +msgstr "Czarny Grzyb" -#: Source/items.cpp:1895 Source/stores.cpp:287 -msgid " {:d} Str" -msgstr " {:d} Siły" +#: Source/quests.cpp:54 +msgid "Gharbad The Weak" +msgstr "Garbad Słaby" -#: Source/items.cpp:1897 Source/stores.cpp:289 -msgid " {:d} Mag" -msgstr " {:d} Magii" +#: Source/quests.cpp:55 +msgid "Zhar the Mad" +msgstr "Zhar Szalony" -#: Source/items.cpp:1899 Source/stores.cpp:291 -msgid " {:d} Dex" -msgstr " {:d} Zręcz." +#: Source/quests.cpp:56 +msgid "Lachdanan" +msgstr "Lachdanan" -#. TRANSLATORS: {:s} will be a Character Name -#: Source/items.cpp:3259 Source/player.cpp:3150 -msgid "Ear of {:s}" -msgstr "Ucho ({:s})" +#: Source/quests.cpp:58 +msgid "The Butcher" +msgstr "Rzeźnik" -#: Source/items.cpp:3555 -msgid "chance to hit: {:+d}%" -msgstr "celność: {:+d}%" +#: Source/quests.cpp:59 +msgid "Ogden's Sign" +msgstr "Szyld Ogdena" -#: Source/items.cpp:3558 -#, no-c-format -msgid "{:+d}% damage" -msgstr "{:+d}% do obrażeń" +#: Source/quests.cpp:60 +msgid "Halls of the Blind" +msgstr "Sale Ślepców" -#: Source/items.cpp:3561 Source/items.cpp:3759 -msgid "to hit: {:+d}%, {:+d}% damage" -msgstr "celność: {:+d}%, obrażenia: {:+d}%" +#: Source/quests.cpp:61 +msgid "Valor" +msgstr "Odwaga" -#: Source/items.cpp:3564 -#, no-c-format -msgid "{:+d}% armor" -msgstr "{:+d}% do obrony" +#: Source/quests.cpp:62 Source/translation_dummy.cpp:263 +msgid "Anvil of Fury" +msgstr "Kowadło Gniewu" -#: Source/items.cpp:3567 -msgid "armor class: {:d}" -msgstr "klasa pancerza: {:d}" +#: Source/quests.cpp:63 +msgid "Warlord of Blood" +msgstr "Marszałek Krwi" -#: Source/items.cpp:3571 Source/items.cpp:3745 -msgid "Resist Fire: {:+d}%" -msgstr "Odporność na Ogień {:+d}%" +#: Source/quests.cpp:64 +msgid "The Curse of King Leoric" +msgstr "Klątwa Króla Leoryka" -#: Source/items.cpp:3573 -msgid "Resist Fire: {:+d}% MAX" -msgstr "Odporność na Ogień {:+d}% MAX" +#. TRANSLATORS: Quest Map +#: Source/quests.cpp:66 Source/quests.cpp:102 +msgid "The Chamber of Bone" +msgstr "Komnata Kości" -#: Source/items.cpp:3577 -msgid "Resist Lightning: {:+d}%" -msgstr "Odp. na Błyskawice {:+d}%" +#: Source/quests.cpp:67 +msgid "Archbishop Lazarus" +msgstr "Arcybiskup Lazarus" -#: Source/items.cpp:3579 -msgid "Resist Lightning: {:+d}% MAX" -msgstr "Odp. na Błyskawice {:+d}% MAX" +#: Source/quests.cpp:68 +msgid "Grave Matters" +msgstr "Sprawa Grobowca" -#: Source/items.cpp:3583 -msgid "Resist Magic: {:+d}%" -msgstr "Odporność na Magię {:+d}%" +#: Source/quests.cpp:69 +msgid "Farmer's Orchard" +msgstr "Sad Farmera" -#: Source/items.cpp:3585 -msgid "Resist Magic: {:+d}% MAX" -msgstr "Odporność na Magię {:+d}% MAX" +#: Source/quests.cpp:70 +msgid "Little Girl" +msgstr "Mała Dziewczynka" -#: Source/items.cpp:3589 -msgid "Resist All: {:+d}%" -msgstr "Odporność na wszystko: {:+d}%" +#: Source/quests.cpp:71 +msgid "Wandering Trader" +msgstr "Wędrujący Handlarz" -#: Source/items.cpp:3591 -msgid "Resist All: {:+d}% MAX" -msgstr "Odporność na wszystko {:+d}% MAX" +#: Source/quests.cpp:72 +msgid "The Defiler" +msgstr "Profanator" -#: Source/items.cpp:3594 -msgid "spells are increased {:d} level" -msgid_plural "spells are increased {:d} levels" -msgstr[0] "poziom czarów: {:d}" -msgstr[1] "poziom czarów: {:d}" -msgstr[2] "poziom czarów: {:d}" +#: Source/quests.cpp:73 +msgid "Na-Krul" +msgstr "Na-Krul" -#: Source/items.cpp:3596 -msgid "spells are decreased {:d} level" -msgid_plural "spells are decreased {:d} levels" -msgstr[0] "poziom czarów: {:d}" -msgstr[1] "poziom czarów: {:d}" -msgstr[2] "poziom czarów: {:d}" +#. TRANSLATORS: Quest Name Block end +#: Source/quests.cpp:75 +msgid "The Jersey's Jersey" +msgstr "Krowia Krowa" -#: Source/items.cpp:3598 -msgid "spell levels unchanged (?)" -msgstr "poziom czarów bez zmian (?)" +#. TRANSLATORS: Quest Map +#: Source/quests.cpp:101 +msgid "King Leoric's Tomb" +msgstr "Grobowiec Króla Leoryka" -#: Source/items.cpp:3600 -msgid "Extra charges" -msgstr "Dodatkowe ładunki" +#. TRANSLATORS: Quest Map +#: Source/quests.cpp:104 +msgid "A Dark Passage" +msgstr "Ciemne Przejście" -#: Source/items.cpp:3602 -msgid "{:d} {:s} charge" -msgid_plural "{:d} {:s} charges" -msgstr[0] "Ładunki: {:d} {:s}" -msgstr[1] "Ładunki: {:d} {:s}" -msgstr[2] "Ładunki: {:d} {:s}" +#. TRANSLATORS: Quest Map +#: Source/quests.cpp:105 +msgid "Unholy Altar" +msgstr "Przeklęty Ołtarz" -#: Source/items.cpp:3605 -msgid "Fire hit damage: {:d}" -msgstr "Obr. od ognia: {:d}" +#. TRANSLATORS: Used for Quest Portals. {:s} is a Map Name +#: Source/quests.cpp:406 +#, c++-format +msgid "To {:s}" +msgstr "{:s}" -#: Source/items.cpp:3607 -msgid "Fire hit damage: {:d}-{:d}" -msgstr "Obr. od ognia: {:d}-{:d}" +#: Source/spelldat.cpp:29 +msgctxt "spell" +msgid "Firebolt" +msgstr "Ognisty Pocisk" -#: Source/items.cpp:3610 -msgid "Lightning hit damage: {:d}" -msgstr "Obr. od błyskawic: {:d}" +#: Source/spelldat.cpp:30 +msgctxt "spell" +msgid "Healing" +msgstr "Uzdrowienie" -#: Source/items.cpp:3612 -msgid "Lightning hit damage: {:d}-{:d}" -msgstr "Obr. od błyskawic: {:d}-{:d}" +#: Source/spelldat.cpp:31 +msgctxt "spell" +msgid "Lightning" +msgstr "Błyskawice" -#: Source/items.cpp:3615 -msgid "{:+d} to strength" -msgstr "{:+d} do siły" +#: Source/spelldat.cpp:32 +msgctxt "spell" +msgid "Flash" +msgstr "Rozbłysk" -#: Source/items.cpp:3618 -msgid "{:+d} to magic" -msgstr "{:+d} do magii" +#: Source/spelldat.cpp:33 +msgctxt "spell" +msgid "Identify" +msgstr "Identyfikacja" -#: Source/items.cpp:3621 -msgid "{:+d} to dexterity" -msgstr "{:+d} do zręczności" +#: Source/spelldat.cpp:34 +msgctxt "spell" +msgid "Fire Wall" +msgstr "Ściana Ognia" -#: Source/items.cpp:3624 -msgid "{:+d} to vitality" -msgstr "{:+d} do żywotności" +#: Source/spelldat.cpp:35 +msgctxt "spell" +msgid "Town Portal" +msgstr "Miejski Portal" -#: Source/items.cpp:3627 -msgid "{:+d} to all attributes" -msgstr "{:+d} do wszystkich atrybutów" +#: Source/spelldat.cpp:36 +msgctxt "spell" +msgid "Stone Curse" +msgstr "Petryfikacja" -#: Source/items.cpp:3630 -msgid "{:+d} damage from enemies" -msgstr "{:+d} obrażeń od wrogów" +#: Source/spelldat.cpp:37 +msgctxt "spell" +msgid "Infravision" +msgstr "Infrawizja" -#: Source/items.cpp:3633 -msgid "Hit Points: {:+d}" -msgstr "Punkty życia: {:+d}" +#: Source/spelldat.cpp:38 +msgctxt "spell" +msgid "Phasing" +msgstr "Przenikanie" -#: Source/items.cpp:3636 -msgid "Mana: {:+d}" -msgstr "Mana: {:+d}" +#: Source/spelldat.cpp:39 +msgctxt "spell" +msgid "Mana Shield" +msgstr "Tarcza Many" -#: Source/items.cpp:3638 -msgid "high durability" -msgstr "wysoka wytrzymałość" +#: Source/spelldat.cpp:40 +msgctxt "spell" +msgid "Fireball" +msgstr "Ognista Kula" -#: Source/items.cpp:3640 -msgid "decreased durability" -msgstr "zmniejszona wytrzymałość" +#: Source/spelldat.cpp:41 +msgctxt "spell" +msgid "Guardian" +msgstr "Strażnik" -#: Source/items.cpp:3642 -msgid "indestructible" -msgstr "niezniszczalność" +#: Source/spelldat.cpp:42 +msgctxt "spell" +msgid "Chain Lightning" +msgstr "Eksplozja Błyskawic" -#: Source/items.cpp:3644 -#, no-c-format -msgid "+{:d}% light radius" -msgstr "+{:d}% do promienia światła" +#: Source/spelldat.cpp:43 +msgctxt "spell" +msgid "Flame Wave" +msgstr "Fala Płomieni" -#: Source/items.cpp:3646 -#, no-c-format -msgid "-{:d}% light radius" -msgstr "-{:d}% do promienia światła" +#: Source/spelldat.cpp:44 +msgctxt "spell" +msgid "Doom Serpents" +msgstr "Hydry" -#: Source/items.cpp:3648 -msgid "multiple arrows per shot" -msgstr "wielostrzał" +#: Source/spelldat.cpp:45 +msgctxt "spell" +msgid "Blood Ritual" +msgstr "Krwawy Rytuał" -#: Source/items.cpp:3651 -msgid "fire arrows damage: {:d}" -msgstr "obr. płonących strzał {:d}" +#: Source/spelldat.cpp:46 +msgctxt "spell" +msgid "Nova" +msgstr "Nova" -#: Source/items.cpp:3653 -msgid "fire arrows damage: {:d}-{:d}" -msgstr "obr. płonących strzał: {:d}-{:d}" +#: Source/spelldat.cpp:47 +msgctxt "spell" +msgid "Invisibility" +msgstr "Niewidzialność" -#: Source/items.cpp:3656 -msgid "lightning arrows damage {:d}" -msgstr "obr. strzał błyskawic {:d}" +#: Source/spelldat.cpp:48 +msgctxt "spell" +msgid "Inferno" +msgstr "Inferno" -#: Source/items.cpp:3658 -msgid "lightning arrows damage {:d}-{:d}" -msgstr "obr. strzał błyskawic {:d}-{:d}" +#: Source/spelldat.cpp:49 +msgctxt "spell" +msgid "Golem" +msgstr "Golem" -#: Source/items.cpp:3661 -msgid "fireball damage: {:d}" -msgstr "obr. ognistej kuli {:d}" +#: Source/spelldat.cpp:50 +msgctxt "spell" +msgid "Rage" +msgstr "Furia" -#: Source/items.cpp:3663 -msgid "fireball damage: {:d}-{:d}" -msgstr "obr. ognistej kuli {:d}-{:d}" +#: Source/spelldat.cpp:51 +msgctxt "spell" +msgid "Teleport" +msgstr "Teleportacja" -#: Source/items.cpp:3665 -msgid "attacker takes 1-3 damage" -msgstr "atakujący otrzymuje 1-3 obrażeń" +#: Source/spelldat.cpp:52 +msgctxt "spell" +msgid "Apocalypse" +msgstr "Apokalipsa" -#: Source/items.cpp:3667 -msgid "user loses all mana" -msgstr "gracz traci całą manę" +#: Source/spelldat.cpp:53 +msgctxt "spell" +msgid "Etherealize" +msgstr "Znieczulenie" -#: Source/items.cpp:3669 -msgid "you can't heal" -msgstr "nie możesz się leczyć" +#: Source/spelldat.cpp:54 +msgctxt "spell" +msgid "Item Repair" +msgstr "Naprawa Przedmiotów" -#: Source/items.cpp:3671 -msgid "absorbs half of trap damage" -msgstr "2x mniej obrażeń od pułapek" +#: Source/spelldat.cpp:55 +msgctxt "spell" +msgid "Staff Recharge" +msgstr "Naładowanie Kostura" -#: Source/items.cpp:3673 -msgid "knocks target back" -msgstr "odrzuca przeciwnika" +#: Source/spelldat.cpp:56 +msgctxt "spell" +msgid "Trap Disarm" +msgstr "Rozbrajanie Pułapek" -#: Source/items.cpp:3675 -#, no-c-format -msgid "+200% damage vs. demons" -msgstr "+200% obrażeń wobec demonów" +#: Source/spelldat.cpp:57 +msgctxt "spell" +msgid "Elemental" +msgstr "Żywiołak" -#: Source/items.cpp:3677 -msgid "All Resistance equals 0" -msgstr "Odporności spadają do 0" +#: Source/spelldat.cpp:58 +msgctxt "spell" +msgid "Charged Bolt" +msgstr "Wiązka Błyskawic" -#: Source/items.cpp:3679 -msgid "hit monster doesn't heal" -msgstr "ranny potwór się nie ulecza" +#: Source/spelldat.cpp:59 +msgctxt "spell" +msgid "Holy Bolt" +msgstr "Święty Pocisk" -#: Source/items.cpp:3682 -#, no-c-format -msgid "hit steals 3% mana" -msgstr "uderzenie kradnie 3% many" +#: Source/spelldat.cpp:60 +msgctxt "spell" +msgid "Resurrect" +msgstr "Wskrzeszenie" -#: Source/items.cpp:3684 -#, no-c-format -msgid "hit steals 5% mana" -msgstr "uderzenie kradnie 5% many" +#: Source/spelldat.cpp:61 +msgctxt "spell" +msgid "Telekinesis" +msgstr "Telekineza" -#: Source/items.cpp:3688 -#, no-c-format -msgid "hit steals 3% life" -msgstr "uderzenie kradnie 3% życia" +#: Source/spelldat.cpp:62 +msgctxt "spell" +msgid "Heal Other" +msgstr "Uzdrowienie Innych" -#: Source/items.cpp:3690 -#, no-c-format -msgid "hit steals 5% life" -msgstr "uderzenie kradnie 5% życia" +#: Source/spelldat.cpp:63 +msgctxt "spell" +msgid "Blood Star" +msgstr "Krwawa Gwiazda" -#: Source/items.cpp:3693 -msgid "penetrates target's armor" -msgstr "przebija pancerz wroga" +#: Source/spelldat.cpp:64 +msgctxt "spell" +msgid "Bone Spirit" +msgstr "Kościane Widmo" -#: Source/items.cpp:3696 -msgid "quick attack" -msgstr "szybki atak" +#: Source/spelldat.cpp:65 +msgctxt "spell" +msgid "Mana" +msgstr "Mana" -#: Source/items.cpp:3698 -msgid "fast attack" -msgstr "szybki atak" +#: Source/spelldat.cpp:66 +msgctxt "spell" +msgid "the Magi" +msgstr "Mądrości" -#: Source/items.cpp:3700 -msgid "faster attack" -msgstr "szybszy atak" +#: Source/spelldat.cpp:67 +msgctxt "spell" +msgid "the Jester" +msgstr "Ironii" -#: Source/items.cpp:3702 -msgid "fastest attack" -msgstr "najszybszy atak" +#: Source/spelldat.cpp:68 +msgctxt "spell" +msgid "Lightning Wall" +msgstr "Ściana Błyskawic" -#: Source/items.cpp:3703 Source/items.cpp:3711 Source/items.cpp:3769 -msgid "Another ability (NW)" -msgstr "Inna zdolność" +#: Source/spelldat.cpp:69 +msgctxt "spell" +msgid "Immolation" +msgstr "Podpalenie" -#: Source/items.cpp:3706 -msgid "fast hit recovery" -msgstr "szybko wznawia atak" +#: Source/spelldat.cpp:70 +msgctxt "spell" +msgid "Warp" +msgstr "Przejście" -#: Source/items.cpp:3708 -msgid "faster hit recovery" -msgstr "szybsze wznowienie ataku" +#: Source/spelldat.cpp:71 +msgctxt "spell" +msgid "Reflect" +msgstr "Odbicie" -#: Source/items.cpp:3710 -msgid "fastest hit recovery" -msgstr "najszybsze wznowienie ataku" +#: Source/spelldat.cpp:72 +msgctxt "spell" +msgid "Berserk" +msgstr "Szał Bojowy" -#: Source/items.cpp:3713 -msgid "fast block" -msgstr "szybki blok" +#: Source/spelldat.cpp:73 +msgctxt "spell" +msgid "Ring of Fire" +msgstr "Pierścień Ognia" -#: Source/items.cpp:3715 -msgid "adds {:d} point to damage" -msgid_plural "adds {:d} points to damage" -msgstr[0] "+{:d} pkt. obrażeń" -msgstr[1] "+{:d} pkt. obrażeń" -msgstr[2] "+{:d} pkt. obrażeń" +#: Source/spelldat.cpp:74 +msgctxt "spell" +msgid "Search" +msgstr "Przeszukiwanie" -#: Source/items.cpp:3717 -msgid "fires random speed arrows" -msgstr "losowe szybkie strzały" +#: Source/spelldat.cpp:75 +msgctxt "spell" +msgid "Rune of Fire" +msgstr "Runa Ognia" -#: Source/items.cpp:3719 -msgid "unusual item damage" -msgstr "nietypowe obrażenia" +#: Source/spelldat.cpp:76 +msgctxt "spell" +msgid "Rune of Light" +msgstr "Runa Światła" -#: Source/items.cpp:3721 -msgid "altered durability" -msgstr "zmieniona wytrzymałość" +#: Source/spelldat.cpp:77 +msgctxt "spell" +msgid "Rune of Nova" +msgstr "Runa Novy" -#: Source/items.cpp:3723 -msgid "Faster attack swing" -msgstr "Szybkie wykonanie ataku" +#: Source/spelldat.cpp:78 +msgctxt "spell" +msgid "Rune of Immolation" +msgstr "Runa Podpalenia" -#: Source/items.cpp:3725 -msgid "one handed sword" -msgstr "miecz jednoręczny" +#: Source/spelldat.cpp:79 +msgctxt "spell" +msgid "Rune of Stone" +msgstr "Runa Kamienia" -#: Source/items.cpp:3727 -msgid "constantly lose hit points" -msgstr "stale tracisz punkty życia" +#: Source/stores.cpp:129 +msgid "Griswold" +msgstr "Griswold" -#: Source/items.cpp:3729 -msgid "life stealing" -msgstr "kradzież życia" +#: Source/stores.cpp:130 +msgid "Pepin" +msgstr "Pepin" -#: Source/items.cpp:3731 -msgid "no strength requirement" -msgstr "brak wymogu siły" +#: Source/stores.cpp:132 +msgid "Ogden" +msgstr "Ogden" -#: Source/items.cpp:3733 -msgid "see with infravision" -msgstr "widzenie z infrawizją" +#: Source/stores.cpp:133 +msgid "Cain" +msgstr "Cain" -#: Source/items.cpp:3738 -msgid "lightning damage: {:d}" -msgstr "obrażenia błyskawic: {:d}" +#: Source/stores.cpp:134 +msgid "Farnham" +msgstr "Farnham" -#: Source/items.cpp:3740 -msgid "lightning damage: {:d}-{:d}" -msgstr "obrażenia błyskawic: {:d}-{:d}" +#: Source/stores.cpp:135 +msgid "Adria" +msgstr "Adria" -#: Source/items.cpp:3742 -msgid "charged bolts on hits" -msgstr "rzuca Wiązkę Błyskawic" +#: Source/stores.cpp:136 Source/stores.cpp:1261 +msgid "Gillian" +msgstr "Gillian" -#: Source/items.cpp:3749 -msgid "occasional triple damage" -msgstr "okazyjnie potraja obrażenia" +#: Source/stores.cpp:137 +msgid "Wirt" +msgstr "Wirt" -#: Source/items.cpp:3751 -#, no-c-format -msgid "decaying {:+d}% damage" -msgstr "rozkład {:+d}% obrażeń" +#: Source/stores.cpp:263 Source/stores.cpp:270 +msgid "Back" +msgstr "Powrót" -#: Source/items.cpp:3753 -msgid "2x dmg to monst, 1x to you" -msgstr "2x obr. dla potw, 1x dla cb" +#: Source/stores.cpp:292 Source/stores.cpp:298 +msgid ", " +msgstr ", " -#: Source/items.cpp:3755 -#, no-c-format -msgid "Random 0 - 600% damage" -msgstr "Losowo 0 - 600% obrażeń" +#: Source/stores.cpp:309 +#, c++-format +msgid "Damage: {:d}-{:d} " +msgstr "Obrażenia: {:d}-{:d} " -#: Source/items.cpp:3757 -#, no-c-format -msgid "low dur, {:+d}% damage" -msgstr "mała wyt, {:+d}% obrażeń" +#: Source/stores.cpp:311 +#, c++-format +msgid "Armor: {:d} " +msgstr "Obrona: {:d} " -#: Source/items.cpp:3761 -msgid "extra AC vs demons" -msgstr "ekstra pancerz przeciwko demonom" +#: Source/stores.cpp:313 +#, c++-format +msgid "Dur: {:d}/{:d}, " +msgstr "Wyt: {:d}/{:d}, " -#: Source/items.cpp:3763 -msgid "extra AC vs undead" -msgstr "ekstra pancerz przeciwko nieumarłym" +#: Source/stores.cpp:315 +msgid "Indestructible, " +msgstr "Niezniszczalność, " -#: Source/items.cpp:3765 -msgid "50% Mana moved to Health" -msgstr "50% Many przechodzi do Życia" +#: Source/stores.cpp:323 +msgid "No required attributes" +msgstr "Brak wymagań atrybutów" -#: Source/items.cpp:3767 -msgid "40% Health moved to Mana" -msgstr "40% Życia przechodzi do Many" +#: Source/stores.cpp:381 Source/stores.cpp:1029 Source/stores.cpp:1248 +msgid "Welcome to the" +msgstr "Witaj" -#: Source/items.cpp:3804 Source/items.cpp:3842 -msgid "damage: {:d} Indestructible" -msgstr "obr: {:d} Niezniszczalność" +#: Source/stores.cpp:382 +msgid "Blacksmith's shop" +msgstr "Warsztat Kowala" -#. TRANSLATORS: Dur: is durability -#: Source/items.cpp:3806 Source/items.cpp:3844 -msgid "damage: {:d} Dur: {:d}/{:d}" -msgstr "obr: {:d} Wyt: {:d}/{:d}" +#: Source/stores.cpp:383 Source/stores.cpp:680 Source/stores.cpp:1031 +#: Source/stores.cpp:1074 Source/stores.cpp:1250 Source/stores.cpp:1262 +#: Source/stores.cpp:1275 +msgid "Would you like to:" +msgstr "Czynność:" -#: Source/items.cpp:3809 Source/items.cpp:3847 -msgid "damage: {:d}-{:d} Indestructible" -msgstr "obr: {:d}-{:d} Niezniszczalność" +#: Source/stores.cpp:384 +msgid "Talk to Griswold" +msgstr "Rozmowa" -#. TRANSLATORS: Dur: is durability -#: Source/items.cpp:3811 Source/items.cpp:3849 -msgid "damage: {:d}-{:d} Dur: {:d}/{:d}" -msgstr "obr: {:d}-{:d} Wyt: {:d}/{:d}" +#: Source/stores.cpp:385 +msgid "Buy basic items" +msgstr "Kup zwykłe przedmioty" -#: Source/items.cpp:3816 Source/items.cpp:3859 -msgid "armor: {:d} Indestructible" -msgstr "pancerz: {:d} Niezniszczalność" +#: Source/stores.cpp:386 +msgid "Buy premium items" +msgstr "Kup wyjątkowe przedmioty" -#. TRANSLATORS: Dur: is durability -#: Source/items.cpp:3818 Source/items.cpp:3861 -msgid "armor: {:d} Dur: {:d}/{:d}" -msgstr "pancerz: {:d} Wyt: {:d}/{:d}" +#: Source/stores.cpp:387 Source/stores.cpp:683 +msgid "Sell items" +msgstr "Sprzedaż" -#: Source/items.cpp:3821 Source/items.cpp:3852 Source/items.cpp:3865 -#: Source/stores.cpp:259 -msgid "Charges: {:d}/{:d}" -msgstr "Ładunki: {:d}/{:d}" +#: Source/stores.cpp:388 +msgid "Repair items" +msgstr "Naprawa" -#: Source/items.cpp:3830 -msgid "unique item" -msgstr "unikat" +#: Source/stores.cpp:389 +msgid "Leave the shop" +msgstr "Wyjdź" -#: Source/items.cpp:3855 Source/items.cpp:3863 Source/items.cpp:3869 -msgid "Not Identified" -msgstr "Nie zidentyfikowano" +#: Source/stores.cpp:417 Source/stores.cpp:719 Source/stores.cpp:1051 +msgid "I have these items for sale:" +msgstr "Podstawowe przedmioty na sprzedaż:" -#: Source/loadsave.cpp:1870 Source/loadsave.cpp:2388 -msgid "Unable to open save file archive" -msgstr "Nie można otworzyć pliku zapisu" +#: Source/stores.cpp:466 +msgid "I have these premium items for sale:" +msgstr "Wyjątkowe przedmioty na sprzedaż:" -#: Source/loadsave.cpp:1873 -msgid "Invalid save file" -msgstr "Nieprawidłowy plik zapisu" +#: Source/stores.cpp:562 Source/stores.cpp:812 +msgid "You have nothing I want." +msgstr "Nie masz rzeczy, których potrzebuję." -#: Source/loadsave.cpp:1904 -msgid "Player is on a Hellfire only level" -msgstr "Gracz przebywa na poziomie z dodatku Hellfire" +#: Source/stores.cpp:573 Source/stores.cpp:824 +msgid "Which item is for sale?" +msgstr "Który przedmiot chcesz sprzedać?" -#: Source/loadsave.cpp:2150 -msgid "Invalid game state" -msgstr "Nieprawidłowy stan gry" +#: Source/stores.cpp:641 +msgid "You have nothing to repair." +msgstr "Nie masz nic do naprawy." -#: Source/menu.cpp:149 -msgid "Unable to display mainmenu" -msgstr "Nie można wyświetlić głównego menu" +#: Source/stores.cpp:652 +msgid "Repair which item?" +msgstr "Który przedmiot naprawić?" -#. TRANSLATORS: Monster Block start -#. MT_NZOMBIE -#: Source/monstdat.cpp:20 -msgctxt "monster" -msgid "Zombie" -msgstr "Zombie" +#: Source/stores.cpp:679 +msgid "Witch's shack" +msgstr "Chata Wiedźmy" -#: Source/monstdat.cpp:21 -msgctxt "monster" -msgid "Ghoul" -msgstr "Ghul" +#: Source/stores.cpp:681 +msgid "Talk to Adria" +msgstr "Rozmowa" -#: Source/monstdat.cpp:22 -msgctxt "monster" -msgid "Rotting Carcass" -msgstr "Gnijące Truchło" +#: Source/stores.cpp:682 Source/stores.cpp:1033 +msgid "Buy items" +msgstr "Kup przedmioty" -#: Source/monstdat.cpp:23 -msgctxt "monster" -msgid "Black Death" -msgstr "Czarna Śmierć" +#: Source/stores.cpp:684 +msgid "Recharge staves" +msgstr "Naładowanie kosturów" -#: Source/monstdat.cpp:24 Source/monstdat.cpp:32 -msgctxt "monster" -msgid "Fallen One" -msgstr "Upadły" +#: Source/stores.cpp:685 +msgid "Leave the shack" +msgstr "Wyjdź" -#: Source/monstdat.cpp:25 Source/monstdat.cpp:33 -msgctxt "monster" -msgid "Carver" -msgstr "Podrzynacz" +#: Source/stores.cpp:886 +msgid "You have nothing to recharge." +msgstr "Nie masz niczego do regeneracji." -#: Source/monstdat.cpp:26 Source/monstdat.cpp:34 -msgctxt "monster" -msgid "Devil Kin" -msgstr "Diablik" +#: Source/stores.cpp:897 +msgid "Recharge which item?" +msgstr "Który przedmiot zregenerować?" -#: Source/monstdat.cpp:27 Source/monstdat.cpp:35 -msgctxt "monster" -msgid "Dark One" -msgstr "Mroczny" +#: Source/stores.cpp:910 +msgid "You do not have enough gold" +msgstr "Brakuje ci złota" -#: Source/monstdat.cpp:28 Source/monstdat.cpp:40 -msgctxt "monster" -msgid "Skeleton" -msgstr "Szkielet" +#: Source/stores.cpp:918 +msgid "You do not have enough room in inventory" +msgstr "Nie masz miejsca w ekwipunku" -#: Source/monstdat.cpp:29 -msgctxt "monster" -msgid "Corpse Axe" -msgstr "Trupi Topornik" +#: Source/stores.cpp:936 +msgid "Do we have a deal?" +msgstr "Umowa stoi?" -#: Source/monstdat.cpp:30 Source/monstdat.cpp:42 -msgctxt "monster" -msgid "Burning Dead" -msgstr "Płonący Trup" +#: Source/stores.cpp:939 +msgid "Are you sure you want to identify this item?" +msgstr "Czy na pewno chcesz zidentyfikować ten przedmiot?" -#: Source/monstdat.cpp:31 Source/monstdat.cpp:43 -msgctxt "monster" -msgid "Horror" -msgstr "Kościej" +#: Source/stores.cpp:945 +msgid "Are you sure you want to buy this item?" +msgstr "Czy na pewno chcesz kupić ten przedmiot?" -#: Source/monstdat.cpp:36 -msgctxt "monster" -msgid "Scavenger" -msgstr "Ścierwojad" +#: Source/stores.cpp:948 +msgid "Are you sure you want to recharge this item?" +msgstr "Czy na pewno chcesz zregenerować ten przedmiot?" -#: Source/monstdat.cpp:37 -msgctxt "monster" -msgid "Plague Eater" -msgstr "Pożeracz Zarazy" +#: Source/stores.cpp:952 +msgid "Are you sure you want to sell this item?" +msgstr "Czy na pewno chcesz sprzedać ten przedmiot?" -#: Source/monstdat.cpp:38 -msgctxt "monster" -msgid "Shadow Beast" -msgstr "Bestia Cienia" +#: Source/stores.cpp:955 +msgid "Are you sure you want to repair this item?" +msgstr "Czy na pewno chcesz naprawić ten przedmiot?" -#: Source/monstdat.cpp:39 -msgctxt "monster" -msgid "Bone Gasher" -msgstr "Miażdżyciel Kości" +#: Source/stores.cpp:969 Source/towners.cpp:152 +msgid "Wirt the Peg-legged boy" +msgstr "Wirt - kulejący chłopiec" -#: Source/monstdat.cpp:41 -msgctxt "monster" -msgid "Corpse Bow" -msgstr "Trupi Łucznik" +#: Source/stores.cpp:972 Source/stores.cpp:979 +msgid "Talk to Wirt" +msgstr "Rozmowa" -#: Source/monstdat.cpp:44 -msgctxt "monster" -msgid "Skeleton Captain" -msgstr "Kapitan Szkieletów" +#: Source/stores.cpp:973 +msgid "I have something for sale," +msgstr "Pokażę ci co oferuję," -#: Source/monstdat.cpp:45 -msgctxt "monster" -msgid "Corpse Captain" -msgstr "Kapitan Zwłok" +#: Source/stores.cpp:974 +msgid "but it will cost 50 gold" +msgstr "jeśli dasz mi zaliczkę:" -#: Source/monstdat.cpp:46 -msgctxt "monster" -msgid "Burning Dead Captain" -msgstr "Kapitan Płonących Trupów" +#: Source/stores.cpp:975 +msgid "just to take a look. " +msgstr "50 szt. złota. " -#: Source/monstdat.cpp:47 -msgctxt "monster" -msgid "Horror Captain" -msgstr "Kapitan Kościejów" +#: Source/stores.cpp:976 +msgid "What have you got?" +msgstr "Zgoda, co tam masz?" -#: Source/monstdat.cpp:48 -msgctxt "monster" -msgid "Invisible Lord" -msgstr "Niewidzialny Władca" +#: Source/stores.cpp:977 Source/stores.cpp:980 Source/stores.cpp:1077 +#: Source/stores.cpp:1265 +msgid "Say goodbye" +msgstr "Wyjdź" -#: Source/monstdat.cpp:49 -msgctxt "monster" -msgid "Hidden" -msgstr "Ukryty" +#: Source/stores.cpp:990 +msgid "I have this item for sale:" +msgstr "Mogę ci to sprzedać:" -#: Source/monstdat.cpp:50 -msgctxt "monster" -msgid "Stalker" -msgstr "Prześladowca" +#: Source/stores.cpp:1007 +msgid "Leave" +msgstr "Wyjdź" -#: Source/monstdat.cpp:51 -msgctxt "monster" -msgid "Unseen" -msgstr "Niewidoczny" +#: Source/stores.cpp:1030 +msgid "Healer's home" +msgstr "Dom Uzdrowiciela" -#: Source/monstdat.cpp:52 -msgctxt "monster" -msgid "Illusion Weaver" -msgstr "Tkacz Iluzji" +#: Source/stores.cpp:1032 +msgid "Talk to Pepin" +msgstr "Rozmowa" -#: Source/monstdat.cpp:53 -msgctxt "monster" -msgid "Satyr Lord" -msgstr "Władca Satyrów" +#: Source/stores.cpp:1034 +msgid "Leave Healer's home" +msgstr "Wyjdź" -#: Source/monstdat.cpp:54 Source/monstdat.cpp:62 -msgctxt "monster" -msgid "Flesh Clan" -msgstr "Klan Rozpruwaczy" +#: Source/stores.cpp:1073 +msgid "The Town Elder" +msgstr "Miejski Kronikarz" -#: Source/monstdat.cpp:55 Source/monstdat.cpp:63 -msgctxt "monster" -msgid "Stone Clan" -msgstr "Klan Kamienia" +#: Source/stores.cpp:1075 +msgid "Talk to Cain" +msgstr "Rozmowa" -#: Source/monstdat.cpp:56 Source/monstdat.cpp:64 -msgctxt "monster" -msgid "Fire Clan" -msgstr "Klan Ognia" +#: Source/stores.cpp:1076 +msgid "Identify an item" +msgstr "Identyfikacja" -#: Source/monstdat.cpp:57 Source/monstdat.cpp:65 -msgctxt "monster" -msgid "Night Clan" -msgstr "Klan Nocy" +#: Source/stores.cpp:1169 +msgid "You have nothing to identify." +msgstr "Nie masz nic do identyfikacji." -#: Source/monstdat.cpp:58 -msgctxt "monster" -msgid "Fiend" -msgstr "Bies" +#: Source/stores.cpp:1180 +msgid "Identify which item?" +msgstr "Który przedmiot zidentyfikować?" -#: Source/monstdat.cpp:59 -msgctxt "monster" -msgid "Blink" -msgstr "Migon" +#: Source/stores.cpp:1195 +msgid "This item is:" +msgstr "Ten przedmiot to:" -#: Source/monstdat.cpp:60 -msgctxt "monster" -msgid "Gloom" -msgstr "Ponurzec" +#: Source/stores.cpp:1198 +msgid "Done" +msgstr "Akceptuj" -#: Source/monstdat.cpp:61 -msgctxt "monster" -msgid "Familiar" -msgstr "Chowaniec" +#: Source/stores.cpp:1207 +#, c++-format +msgid "Talk to {:s}" +msgstr "Rozmawiaj z {:s}" -#: Source/monstdat.cpp:66 -msgctxt "monster" -msgid "Acid Beast" -msgstr "Żrąca Bestia" +#: Source/stores.cpp:1210 +#, c++-format +msgid "Talking to {:s}" +msgstr "Rozmowa z {:s}" -#: Source/monstdat.cpp:67 -msgctxt "monster" -msgid "Poison Spitter" -msgstr "Trujopluj" +#: Source/stores.cpp:1211 +msgid "is not available" +msgstr "jest niedostępna" -#: Source/monstdat.cpp:68 -msgctxt "monster" -msgid "Pit Beast" -msgstr "Wżerobestia" +#: Source/stores.cpp:1212 +msgid "in the shareware" +msgstr "w wersji" -#: Source/monstdat.cpp:69 -msgctxt "monster" -msgid "Lava Maw" -msgstr "Ogniopaszczy" +#: Source/stores.cpp:1213 +msgid "version" +msgstr "shareware" -#: Source/monstdat.cpp:70 Source/monstdat.cpp:474 -msgctxt "monster" -msgid "Skeleton King" -msgstr "Król Szkieletów" +#: Source/stores.cpp:1240 +msgid "Gossip" +msgstr "Plotki" -#: Source/monstdat.cpp:71 Source/monstdat.cpp:482 -msgctxt "monster" -msgid "The Butcher" -msgstr "Rzeźnik" +#: Source/stores.cpp:1249 +msgid "Rising Sun" +msgstr "Wschodzące Słońce" -#: Source/monstdat.cpp:72 -msgctxt "monster" -msgid "Overlord" -msgstr "Nadzorca" +#: Source/stores.cpp:1251 +msgid "Talk to Ogden" +msgstr "Rozmowa" -#: Source/monstdat.cpp:73 -msgctxt "monster" -msgid "Mud Man" -msgstr "Błotoczłek" +#: Source/stores.cpp:1252 +msgid "Leave the tavern" +msgstr "Wyjdź" -#: Source/monstdat.cpp:74 -msgctxt "monster" -msgid "Toad Demon" -msgstr "Ropuszy Demon" +#: Source/stores.cpp:1263 +msgid "Talk to Gillian" +msgstr "Rozmowa" -#: Source/monstdat.cpp:75 -msgctxt "monster" -msgid "Flayed One" -msgstr "Obdzieracz Skór" +#: Source/stores.cpp:1264 +msgid "Access Storage" +msgstr "Otwórz Skrytkę" -#: Source/monstdat.cpp:76 -msgctxt "monster" -msgid "Wyrm" -msgstr "Żmijowiec" +#: Source/stores.cpp:1274 Source/towners.cpp:207 +msgid "Farnham the Drunk" +msgstr "Pijak Farnham" -#: Source/monstdat.cpp:77 -msgctxt "monster" -msgid "Cave Slug" -msgstr "Podziemny Czerw" +#: Source/stores.cpp:1276 +msgid "Talk to Farnham" +msgstr "Rozmowa" -#: Source/monstdat.cpp:78 -msgctxt "monster" -msgid "Devil Wyrm" -msgstr "Diabelski Żmijowiec" +#: Source/stores.cpp:1277 +msgid "Say Goodbye" +msgstr "Wyjdź" -#: Source/monstdat.cpp:79 -msgctxt "monster" -msgid "Devourer" -msgstr "Pożeracz" +#: Source/stores.cpp:2410 +#, c++-format +msgid "Your gold: {:s}" +msgstr "Twoje złoto: {:s}" -#: Source/monstdat.cpp:80 -msgctxt "monster" -msgid "Magma Demon" -msgstr "Demon Magmy" +#. 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 "" +" Ach, chcesz poznać historię naszego Króla, tak? Tragiczny upadek Leoryka " +"był bolesnym ciosem dla tej krainy. Ludzie od zawsze go wielbili, a teraz " +"panicznie się go boją. Do dzisiaj zastanawia mnie to, jak Król mógł upaść " +"tak nisko. Przecież był tak wielkodusznym władcą. Tylko najgorsze moce " +"piekieł mogły tak doszczętnie zniszczyć jego duszę..." -#: Source/monstdat.cpp:81 -msgctxt "monster" -msgid "Blood Stone" -msgstr "Kamień Krwi" +#. 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 "" +"Wioska potrzebuje twojej pomocy! Kilka miesięcy temu porwano syna króla " +"Leoryka, księcia Albrechta. Nasz władca wpadł w szał i przeczesał całą " +"wioskę w poszukiwaniu zaginionego dziecka. Z każdym kolejnym dniem, Leoryk " +"coraz głębiej popadał w obłęd. Doszukiwał się winy u niewinnych mieszczan, " +"których kazał potem brutalnie stracić. Mniej niż połowa z nas przeżyła jego " +"furię...\n" +" \n" +"Królewscy rycerze i kapłani próbowali go uspokoić. Wtedy Król zwrócił się " +"przeciwko nim. Nie mając innego wyjścia, byli zmuszeni go zabić. Wraz ze " +"swoim ostatnim tchnieniem, Leoryk nałożył straszliwą klątwę na swą byłą " +"świtę. Poprzysiągł, że będą służyć mu w ciemnościach, na wieczność...\n" +" \n" +"W tym momencie sprawy przybrały gorszy obrót, niż sobie wyobrażałem! Nasz " +"były król obudził się z wiecznego snu i przewodzi teraz legionami " +"nieumarłych sług w labiryncie. Jego ciało zostało pochowane w komnacie, trzy " +"poziomy pod katedrą. Błagam, zniszcz jego przeklętą formę i pozwól mu " +"spocząć w pokoju..." -#: Source/monstdat.cpp:82 -msgctxt "monster" -msgid "Hell Stone" -msgstr "Piekielny Kamień" +#. 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 "" +"Już mówiłem, Król został pochowany trzy poziomy pod katedrą. Oczekuje w " +"mrokach podziemi na okazję, by nas zniszczyć..." -#: Source/monstdat.cpp:83 -msgctxt "monster" -msgid "Lava Lord" -msgstr "Władca Lawy" +#. 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 "" +"Klątwa już minęła, ale obawiam się, że to był dopiero początek większych " +"problemów. Na szczęście, możemy jeszcze zostać uratowani przed nadchodzącym " +"niebezpieczeństwem. Twoje zwycięstwo dobrze nam wróży. Niech prowadzi cię " +"światłość." -#: Source/monstdat.cpp:84 -msgctxt "monster" -msgid "Horned Demon" -msgstr "Rogaty Demon" +#. 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 "" +"Utrata syna przerosła Króla. Próbowałem złagodzić jego obłęd, niestety " +"bezskutecznie. Tamtego dnia, nad królestwem zawisła czarna klątwa. Jeżeli " +"jednak uda ci się oswobodzić jego duszę, to może ta klątwa przeminie..." -#: Source/monstdat.cpp:85 -msgctxt "monster" -msgid "Mud Runner" -msgstr "Błotowiec" +#. 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 "" +"Nie chcę myśleć o tym, jak zginął nasz Król. Wolę zapamiętać go jako " +"życzliwego i prawego władcę. Jego śmierć była bardzo smutnym wydarzeniem." -#: Source/monstdat.cpp:86 -msgctxt "monster" -msgid "Frost Charger" -msgstr "Mroźny Szarżownik" +#. 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 "" +"Wykułem wiele broni i pancerzy dla świty Króla Leoryka. Sam władca dostał " +"ode mnie ogromny, dwuręczny miecz z najlepszego mithrilu, z koroną do " +"kompletu. Wciąż nie mogę uwierzyć w to, jak zginął. Musiał zostać opętany " +"przez naprawdę potężne i złowrogie siły!" -#: Source/monstdat.cpp:87 -msgctxt "monster" -msgid "Obsidian Lord" -msgstr "Obsydianowy Władca" - -#: Source/monstdat.cpp:88 -msgctxt "monster" -msgid "oldboned" -msgstr "staruszek" - -#: Source/monstdat.cpp:89 -msgctxt "monster" -msgid "Red Death" -msgstr "Czerwona Zaraza" - -#: Source/monstdat.cpp:90 -msgctxt "monster" -msgid "Litch Demon" -msgstr "Diabelski Mag" - -#: Source/monstdat.cpp:91 -msgctxt "monster" -msgid "Undead Balrog" -msgstr "Nieumarły Balrog" - -#: Source/monstdat.cpp:92 -msgctxt "monster" -msgid "Incinerator" -msgstr "Kremator" - -#: Source/monstdat.cpp:93 -msgctxt "monster" -msgid "Flame Lord" -msgstr "Władca Płomieni" - -#: Source/monstdat.cpp:94 -msgctxt "monster" -msgid "Doom Fire" -msgstr "Płonąca Zagłada" - -#: Source/monstdat.cpp:95 -msgctxt "monster" -msgid "Hell Burner" -msgstr "Piekielny Pomiot" +#. 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 "" +"Nie obchodzi mnie to. Słuchaj, żaden kościotrup nie będzie MOIM królem. " +"Leoryk jest królem. Słyszysz? Królem! WIWAT KRÓL!" -#: Source/monstdat.cpp:96 -msgctxt "monster" -msgid "Red Storm" -msgstr "Czerwony Burzowiec" +#. 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 "" +"Martwi kroczący pomiędzy żywymi podążyli za przeklętym Królem. Posiadł moc, " +"dzięki której może pozyskiwać jeszcze większą ilość wojowników. Jego armia " +"nieumarłych wciąż się rozrasta. Jeśli go nie powstrzymasz, z pewnością " +"zaatakuje tę krainę i wybije wszystkich jej mieszkańców." -#: Source/monstdat.cpp:97 -msgctxt "monster" -msgid "Storm Rider" -msgstr "Burzowy Jeździec" +#. 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 "" +"Słuchaj, ja tu prowadzę interesy. Nie sprzedaję informacji i nie obchodzi " +"mnie jakiś Król, który jest martwy dłużej, niż ja żyję. No, chyba, że " +"potrzebujesz wyposażenia przeciwko niemu. Wtedy pomogę." -#: Source/monstdat.cpp:98 -msgctxt "monster" -msgid "Storm Lord" -msgstr "Władca Burz" +#. 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 "" +"Ciepło życia wpełzło do mego grobowca. Za chwilę cię stłamszę i staniesz się " +"wiecznym sługą mego Pana!" -#: Source/monstdat.cpp:99 -msgctxt "monster" -msgid "Maelstrom" -msgstr "Cyklon" +#. 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 "" +"Widzę, że ta dziwna sytuacja zastanawia również ciebie. Demony bojące się " +"światła słonecznego prawdopodobnie wierzą, że drzemie w nim wielka moc. " +"Przypuszczam, że gdy zobaczyły słońce, zobrazowane na wspomnianym przez " +"ciebie szyldzie, pomyślały, że skrywa w sobie tajemniczą energię. Hmm, być " +"może nie są tak bystre, jak się obawialiśmy..." -#: Source/monstdat.cpp:100 -msgctxt "monster" -msgid "Devil Kin Brute" -msgstr "Oprych Diablików" +#. 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 "" +"Jeśli mogę... chciałbym opowiedzieć ci dziwną historię. Wiem, że masz " +"ogromną wiedzę o tych potwornościach zamieszkałych w labiryncie, a " +"przydarzyło się coś czego za nic nie potrafię zrozumieć... Pewnej nocy " +"obudziło mnie jakieś dziwne skrzypienie. Kiedy wyjrzałem z sypialni, na " +"dziedzińcu zobaczyłem cienie małych stworzeń, wyglądających jak demony. Po " +"chwili uciekły, ale zdążyły ukraść szyld mojej gospody. Nie pojmuję, czemu " +"zabrały szyld, a nas zostawiły w spokoju... dziwne, nie?" -#: Source/monstdat.cpp:101 -msgctxt "monster" -msgid "Winged-Demon" -msgstr "Skrzydlaty Demon" +#. 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 "" +"Och, nie trzeba było zwracać mi szyldu, choć dzięki tobie nie muszę kupować " +"nowego. Dobrze, zobaczmy, jak mogę ci to wynagrodzić? Hmmm, co my tu mamy... " +"o, jest! Tę czapkę zostawił po sobie pewien czarodziej, który zatrzymał się " +"tu jakiś czas temu. Może ci się do czegoś przyda." -#: Source/monstdat.cpp:102 -msgctxt "monster" -msgid "Gargoyle" -msgstr "Gargulec" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Wielkie Nieba! Demony biegają nocami po wiosce i grabią nasze domy - czy nie " +"ma już żadnych świętości? Mam nadzieję, że Ogden i Garda są cali. Chyba " +"przyszliby do mnie, gdyby coś im się stało..." -#: Source/monstdat.cpp:103 -msgctxt "monster" -msgid "Blood Claw" -msgstr "Krwawoszpon" +#. 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 "" +"O rety! Zabrały szyld? Musiałyśmy przespać z babcią całe to zamieszanie. " +"Dzięki światłości, że stwory nie zaatakowały gospody." -#: Source/monstdat.cpp:104 -msgctxt "monster" -msgid "Death Wing" -msgstr "Śmiercioskrzydły" +#. 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 "" +"Że co? Demony ukradły szyld Ogdena? Znam z plotek i widzenia większe " +"potworności, których się dopuszczały. \n" +" \n" +"Z reguły wolą wyrywać serca z piersi niż szyldy z fasad." -#: Source/monstdat.cpp:105 -msgctxt "monster" -msgid "Slayer" -msgstr "Pogromca" +#. 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 "" +"Wiesz co? Myślę, że ktoś ukradł ten szyld i będzie chciał za niego miliony " +"monet. Gdybym był Ogdenem... choć nie jestem, no ale gdybym był... to " +"kupiłbym nowy szyld, z jakimś ładniejszym malunkiem. Może kuflem piwa, albo " +"kiszonym ogórkiem..." -#: Source/monstdat.cpp:106 -msgctxt "monster" -msgid "Guardian" -msgstr "Strażnik" +#. 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 "" +"Żaden śmiertelnik nie jest w stanie w pełni zrozumieć demonicznego umysłu. \n" +" \n" +"Ich dziwne zachowania mogą być częścią większego planu." -#: Source/monstdat.cpp:107 -msgctxt "monster" -msgid "Vortex Lord" -msgstr "Władca Wiru" +#. 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 "" +"I co - jeszcze sugeruje, że to moja sprawka? Pewnie Griswold też trzyma jego " +"stronę. \n" +" \n" +"Słuchaj, szyldy przestałem kraść dawno temu. Nie dostaniesz wiele za kawałek " +"drewna." -#: Source/monstdat.cpp:108 -msgctxt "monster" -msgid "Balrog" -msgstr "Balrog" +#. 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 "" +"Hej - Ty co zabijać wszystkich! Ty zdobyć mi Czarodziejski Znak albo my " +"atakować! Ty nie odejść żywo! \n" +" \n" +"Ty zabić dużych brzydali i przynieść nam Magia. Iść za róg i drzwi, znaleźć " +"brzydali. \n" +" \n" +"Ty dać, ty odejść!" -#: Source/monstdat.cpp:109 -msgctxt "monster" -msgid "Cave Viper" -msgstr "Jaskiniowy Żmij" +#. TRANSLATORS: Quest dialog spoken by Snotspill (Hostile) +#: Source/textdat.cpp:57 +msgid "You kill uglies, get banner. You bring to me, or else..." +msgstr "" +"Ty zabić brzydali, zdobyć znak. \n" +" \n" +"Ty mi go przynieść, bo inaczej..." -#: Source/monstdat.cpp:110 -msgctxt "monster" -msgid "Fire Drake" -msgstr "Ognisty Jaszczur" +#. 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 "" +"Oddać! O taak! \n" +" \n" +"Już odejść, my potężni. \n" +" \n" +"My zniszczyć wszystkich! \n" +" \n" +"Duża magia!" -#: Source/monstdat.cpp:111 -msgctxt "monster" -msgid "Gold Viper" -msgstr "Złoty Żmij" - -#: Source/monstdat.cpp:112 -msgctxt "monster" -msgid "Azure Drake" -msgstr "Lazurowy Jaszczur" - -#: Source/monstdat.cpp:113 -msgctxt "monster" -msgid "Black Knight" -msgstr "Czarny Rycerz" +#. 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 "" +"To nie wróży nam dobrze. Potwierdzają się moje najgorsze obawy. Nie chciałem " +"wierzyć starożytnym przepowiedniom, jednak właśnie zaczynają się " +"urzeczywistniać. Chyba powinienem powiedzieć ci, kim dokładnie jestem. \n" +" \n" +"Nazywam się Deckard Cain Starszy, jestem ostatnim potomkiem prastarego " +"Bractwa, chroniącego ludzkość przed odwiecznym złem. Złem, które właśnie się " +"przebudziło. \n" +" \n" +"Arcybiskup Lazarus, kiedyś najbardziej zaufany doradca Króla Leoryka, " +"zachęcił grupę prostych mieszczan do zejścia w głąb Labiryntu. Mieli " +"odnaleźć zaginionego księcia, Albrechta. Po długim czasie, z całej grupy, " +"żywych wróciło tylko kilku. \n" +" \n" +"Jestem głupcem! Powinienem domyślić się, że coś było nie tak. To pewnie sam " +"Lazarus porwał Albrechta i ukrył go we wnętrzu Labiryntu. Nie rozumiem tylko " +"dlaczego Arcybiskup poddał się mocom ciemności, i czemu wybrał akurat " +"dziecko... No chyba, że chciał złożyć je w ofierze swym mrocznym panom! \n" +" \n" +"Tak, to musiał być jego plan od samego początku! Ludzie z 'grupy " +"ratowniczej', którym udało się uwolnić, mówili o tym, że Lazarus na sam " +"koniec zniknął w najdalszych zakątkach Labiryntu. Musisz się pośpieszyć i " +"ocalić księcia przed ofiarnym ostrzem tego obłąkanego potwora!" -#: Source/monstdat.cpp:114 -msgctxt "monster" -msgid "Doom Guard" -msgstr "Strażnik Zagłady" +#. 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 "" +"Pośpiesz się! Albrecht wciąż jest w rękach Lazarusa. Książę i mieszkańcy " +"tego królestwa liczą na ciebie!" -#: Source/monstdat.cpp:115 -msgctxt "monster" -msgid "Steel Lord" -msgstr "Stalowy Władca" +#. 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 "" +"To naprawdę ponura historia. Lazarus bez wątpienia spłonie w piekle za ten " +"potworny czyn. Jednakże chłopiec, którego opisujesz, nie jest naszym " +"księciem. Albrecht nadal może być w niebezpieczeństwie. A ten wielki symbol, " +"o którym mówisz, musi być portalem do samego serca Labiryntu. \n" +"Posłuchaj uważnie - zło z którym teraz się spotkasz, to mroczny Pan Grozy. " +"śmiertelnicy nazywają go Diablo. To właśnie jego uwięziono wewnątrz " +"Labiryntu wiele wieków temu. Obawiam się, że znowu zacznie siać grozę w " +"krainie śmiertelników. Musisz przejść przez portal i zniszczyć Diablo, nim " +"będzie za późno!" -#: Source/monstdat.cpp:116 -msgctxt "monster" -msgid "Blood Knight" -msgstr "Rycerz Krwi" +#. 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 "" +"Lazarus był arcybiskupem, który zaprowadził wielu spośród mieszkańców do " +"Labiryntu. Tego dnia straciłem mnóstwo dobrych przyjaciół, a Lazarus nigdy " +"już nie powrócił. Przypuszczam, że został po drodze zabity, jak zresztą " +"większość. Mam też do ciebie jedną prośbę - nie rozmawiaj o tym dniu z " +"Farnhamem." -#: Source/monstdat.cpp:117 -msgctxt "monster" -msgid "The Shredded" -msgstr "Związany" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Byłem zszokowany kiedy tamtej nocy usłyszałem o planie mieszkańców. " +"Myślałem, że z tych wszystkich ludzi, to Lazarus ma najwięcej zdrowego " +"rozsądku. W końcu był Arcybiskupem i zawsze troszczył się o tutejszą " +"ludność. Wróciło tylu rannych, że... nie dałem rady wszystkich uratować." -#: Source/monstdat.cpp:118 -msgctxt "monster" -msgid "Hollow One" -msgstr "Pusta Istota" +#. 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 "" +"Pamiętam Lazarusa jako bardzo ciepłego i życzliwego człowieka. Przemawiał na " +"pogrzebie mojej mamy i w tym trudnym czasie wspierał mnie i babcię. " +"Codziennie modlę się o to, żeby jakimś cudem wrócił cały i zdrowy." -#: Source/monstdat.cpp:119 -msgctxt "monster" -msgid "Pain Master" -msgstr "Mistrz Bólu" +#. 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 "" +"Byłem członkiem grupy sprowadzonej do Labiryntu przez Lazarusa. Cały czas " +"mówił coś o świętej zemście, ale gdy pojawiły się demony, to nawet nie " +"wyciągnął broni zza pasa. Zamiast tego uciekł wprost w korytarze wypełnione " +"sługusami zła!" -#: Source/monstdat.cpp:120 -msgctxt "monster" -msgid "Reality Weaver" -msgstr "Tkacz Rzeczywistości" +#. 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 "" +"Dźgały, potem gryzły, były wszędzie. Kłamca! KłAMCA! Wszyscy zginęli! Nie " +"żyją! Słyszysz? Po prostu padali, jeden po drugim... ich krew tryskała po " +"całej podłodze... wszystko przez niego..." -#: Source/monstdat.cpp:121 -msgctxt "monster" -msgid "Succubus" -msgstr "Sukkub" +#. 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 "" +"Choć nie poznałam Lazarusa, wyczuwam w nim głęboki konflikt. Stanowi ogromne " +"niebezpieczeństwo. Nic nie powstrzyma go przed służbą mocom ciemności, które " +"nad nim zapanowały." -#: Source/monstdat.cpp:122 -msgctxt "monster" -msgid "Snow Witch" -msgstr "Śnieżna Wiedźma" +#. 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 "" +"Och, szlachetny Lazarus, ten któremu taaak dobrze szło odstraszanie demonów, " +"że straciłem nogę? Wiesz, dam ci bezpłatną radę. Porozmawiaj z Farnhamem, on " +"wszystko widział." -#: Source/monstdat.cpp:123 -msgctxt "monster" -msgid "Hell Spawn" -msgstr "Córa Piekieł" +#. 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 "" +"To koniec twej drogi. Czeka cię już tylko gniew mego Pana! Nie udało ci się " +"uratować dziecka, ale zaraz spotkasz je w piekle!" -#: Source/monstdat.cpp:124 -msgctxt "monster" -msgid "Soul Burner" -msgstr "Wypalaczka Dusz" +#. 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 "" +"Hmm, nie mam zbyt wielu informacji na ten temat. Słyszałem niegdyś o tunelu, " +"który prowadzi do podziemnego jeziora. Prawdopodobnie jest ono źródłem " +"wypełniającym nasze studnie. Niestety, nie wiem co dokładnie mogło " +"doprowadzić do skażenia naszej wody." -#: Source/monstdat.cpp:125 -msgctxt "monster" -msgid "Counselor" -msgstr "Kanclerz" +#. 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 "" +"Od zawsze staram się utrzymywać duże zapasy żywności i napitków w naszej " +"piwnicy. Niestety, odkąd miasto nie ma dostępu do świeżej wody, rezerwy " +"szybko się wyczerpują.\n" +" \n" +"Błagam, pomóż nam, jesteś naszą jedyną nadzieją." -#: Source/monstdat.cpp:126 -msgctxt "monster" -msgid "Magistrate" -msgstr "Mediator" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Właśnie miałem cię wołać! Woda w naszych studniach stała się brudna i " +"cuchnąca. Kilku mieszkańców wioski miało po jej wypiciu objawy zatrucia, a " +"nasze rezerwy świeżej wody błyskawicznie się wyczerpują. Jestem przekonany, " +"że gdzieś w pobliżu jest przejście prowadzące do źródeł zaopatrujących nasze " +"miasto. Proszę, znajdź przyczynę tego problemu, inaczej źle się to dla nas " +"skończy." -#: Source/monstdat.cpp:127 -msgctxt "monster" -msgid "Cabalist" -msgstr "Kabalista" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Proszę, pośpiesz się. Z każdą godziną mamy coraz mniej wody zdatnej do " +"picia. \n" +" \n" +"Nie przetrwamy długo bez twojej pomocy." -#: Source/monstdat.cpp:128 -msgctxt "monster" -msgid "Advocate" -msgstr "Adwokat" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Twierdzisz, że sama obecność demonów skaziła nasze wody? Och, podziemia " +"Tristram skrywają naprawdę wielkie zło, lecz twoja wytrwałość i odwaga dają " +"nam jeszcze nadzieję. Proszę, trzymaj ten pierścień - powinien pomóc ci w " +"pokonywaniu tych nikczemnych stworzeń." -#: Source/monstdat.cpp:129 -msgctxt "monster" -msgid "Golem" -msgstr "Golem" - -#: Source/monstdat.cpp:130 -msgctxt "monster" -msgid "The Dark Lord" -msgstr "Mroczny Władca" - -#: Source/monstdat.cpp:131 -msgctxt "monster" -msgid "The Arch-Litch Malignus" -msgstr "Arcy-Lisz Złośliwiec" +#. 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 "" +"Moja babcia źle się czuje, a Garda powiedziała, że woda ze studni nie nadaje " +"się do picia. Proszę, możesz nam jakoś pomóc?" -#: Source/monstdat.cpp:132 -msgctxt "monster" -msgid "Hellboar" -msgstr "Piekielny Dzik" +#. 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 "" +"Pepin ma rację, skażona woda to ogromny problem. Próbowałem oczyścić jedną z " +"pomniejszych studni, ale wciąż niesamowicie z niej cuchnie. Problem musi " +"leżeć gdzieś u źródeł." -#: Source/monstdat.cpp:133 -msgctxt "monster" -msgid "Stinger" -msgstr "Kąsacz" +#. TRANSLATORS: Quest dialog spoken by Farnham +#: Source/textdat.cpp:100 +msgid "You drink water?" +msgstr "No co ty? Wodę pijesz?!" -#: Source/monstdat.cpp:134 -msgctxt "monster" -msgid "Psychorb" -msgstr "Wizjoner" +#. 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 "" +"Mieszkańców Tristram czeka śmierć, jeśli w studni z powrotem nie pojawi się " +"czysta woda. \n" +" \n" +"Miej na uwadze, że istotą tego problemu są demony, choć nie są tego świadome." -#: Source/monstdat.cpp:135 -msgctxt "monster" -msgid "Arachnon" -msgstr "Pajęczysko" +#. 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 "" +"Tym razem trzymam za ciebie kciuki. że tak powiem: biznes nie może kwitnąć, " +"kiedy nie ma wody. Lepiej szybko zbadaj sytuację!" -#: Source/monstdat.cpp:136 -msgctxt "monster" -msgid "Felltwin" -msgstr "Wtórnie Upadły" +#. 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 "" +"Komnata z ludzkich kości... Tak, wspomniano o niej w kilku pradawnych " +"tekstach, które studiowałem w bibliotekach Wschodu. Napomknięto w nich, że " +"gdy Władcy Podziemi chcieli chronić swoje wielkie skarby, tworzyli specjalne " +"sfery. Każdy kto zginął w takiej sferze próbując zdobyć skarb, stawał się na " +"wieczność jego strażnikiem. Czyż to nie potworna, a zarazem dość rozsądna " +"metoda?" -#: Source/monstdat.cpp:137 -msgctxt "monster" -msgid "Hork Spawn" -msgstr "Pomiot Przeżartucha" +#. 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 "" +"Niestety, słyszę o niej pierwszy raz. Cain ma wiele ksiąg i może w którejś z " +"nich znajdzie coś na jej temat." -#: Source/monstdat.cpp:138 -msgctxt "monster" -msgid "Venomtail" -msgstr "Ogon Jadu" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: Source/textdat.cpp:109 +msgid "" +"This sounds like a very dangerous place. If you venture there, please take " +"great care." +msgstr "" +"Brzmi niebezpiecznie. Jeżeli tam wejdziesz, to proszę, zachowaj ostrożność." -#: Source/monstdat.cpp:139 -msgctxt "monster" -msgid "Necromorb" -msgstr "Nekrokula" +#. 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 "" +"Chyba nigdy nie słyszałam o takiej Komnacie. Nasz Kronikarz Cain może coś o " +"niej wiedzieć." -#: Source/monstdat.cpp:140 -msgctxt "monster" -msgid "Spider Lord" -msgstr "Pajęczy Władca" +#. 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 "" +"Nie słyszałem o tym miejscu. Zapytaj Caina. On gada o tak wielu rzeczach, że " +"pewnie ma już kilka odpowiedzi na to pytanie." -#: Source/monstdat.cpp:141 -msgctxt "monster" -msgid "Lashworm" -msgstr "Okaleczony Robak" +#. 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 "" +"No dobra, słuchaj. Jest sobie taka komnata z drewna. I jego żona, wiesz, ona " +"mówi, na drzewo... bo musiszz zaczekać. To ja mówię, że tak się nie mówi, a " +"jeśli myślisz, że za to ZAPłACĘ... to... nieważnee." -#: Source/monstdat.cpp:142 -msgctxt "monster" -msgid "Torchant" -msgstr "Zapaleniec" +#. 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 "" +"Jeśli umrzesz w tym przeklętym miejscu, rozpocznie się twa wieczna służba " +"dla władców ciemności.\n" +" \n" +"Do Komnaty Kości wchodź na własną odpowiedzialność." -#: Source/monstdat.cpp:143 Source/monstdat.cpp:483 -msgctxt "monster" -msgid "Hork Demon" -msgstr "Przeżartuch" +#. 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 "" +"Ogromny i tajemniczy skarb, powiadasz? Może nawet coś bym od ciebie kupił... " +"a nie potrzebujesz może jakiegoś unikalnego i drogiego wyposażenia żeby go " +"zdobyć?" -#: Source/monstdat.cpp:144 -msgctxt "monster" -msgid "Hell Bug" -msgstr "Piekielny Robak" +#. 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 "" +"Wygląda na to, że Lazarus zwabił do Labiryntu wielu mieszkańców. Powiedział " +"im, że idą tam, aby odnaleźć zaginionego księcia. Udało mu się wszystkich " +"podburzyć i zmienić w rozszalały tłum, przez co nikt nie myślał o " +"czyhających zagrożeniach... Kiedy cała grupa została osaczona przez " +"przeraźliwe kreatury, Arcybiskup zniknął, pozostawiając swoich ludzi na " +"pewną i potworną śmierć." -#: Source/monstdat.cpp:145 -msgctxt "monster" -msgid "Gravedigger" -msgstr "Grabarz" +#. 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 "" +"Tak, Farnham mamrotał coś o potężnej bestii, władającej straszliwą bronią. Z " +"tego co pamiętam, nazywał ją... rzeźnikiem." -#: Source/monstdat.cpp:146 -msgctxt "monster" -msgid "Tomb Rat" -msgstr "Grobowy Szczur" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Na światłość, słyszałem o tym podłym demonie. Wielu ze śmiałków, którzy " +"przeżyli wyprawę do katedry, nosiło znamiona jego straszliwego gniewu. Nie " +"mam pojęcia czego używa do ćwiartowania swych ofiar, ale... to nie może być " +"broń z tego świata. Zostawia ropiejące rany, których nawet ja nie jestem w " +"stanie wyleczyć. Miej się na baczności, jeżeli staniesz z nim do walki..." -#: Source/monstdat.cpp:147 -msgctxt "monster" -msgid "Firebat" -msgstr "Ognisty Nietoperz" +#. 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 "" +"Kiedy Farnham opowiadał coś o rzeźniku zabijającym ludzi, zlekceważyłam to. " +"Jednak skoro teraz ty przychodzisz z tym do mnie... to już sama nie wiem." -#: Source/monstdat.cpp:148 -msgctxt "monster" -msgid "Skullwing" -msgstr "Skrzydlata Czaszka" +#. 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 "" +"Razem z Farnhamem na własne oczy widzieliśmy Rzeźnika. Machał tasakiem " +"wielkim jak topór, rozcinając jednym uderzeniem ciała mych przyjaciół. Od " +"tego przerażającego widoku oddzielała mnie jedynie gromadka małych, " +"skrzeczących stworów. Na szczęście, jakimś cudem, udało mi się odnaleźć " +"drogę ucieczki. Nigdy więcej nie ujrzałem tego ohydnego potwora, ale jego " +"zakrwawione oblicze nawiedza mnie do dzisiaj." -#: Source/monstdat.cpp:149 -msgctxt "monster" -msgid "Lich" -msgstr "Lisz" +#. 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 "" +"Wielki! Wielki tasak przecinający moich przyjaciół. Nie dało się zatrzymać. " +"Musiałem uciekać... zostawić ich... zamkniętych w komnacie z tyloma " +"ciałami... tylu bliskich... NIEEEEEEEE!" -#: Source/monstdat.cpp:150 -msgctxt "monster" -msgid "Crypt Demon" -msgstr "Demon Krypty" +#. 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 "" +"Rzeźnik to brutalny potwór, któremu ból i cierpienie innych sprawiają " +"ogromną przyjemność. Spójrz do czego doprowadził Farnhama. Unicestwienie " +"tego demona na pewno uspokoiłoby mieszkańców wioski." -#: Source/monstdat.cpp:151 -msgctxt "monster" -msgid "Hellbat" -msgstr "Piekielny Nietoperz" +#. 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 "" +"Wiem o tym potworze więcej, niż ci się wydaje. Jego mali towarzysze złapali " +"mnie i pozbawili nogi, zanim Griswold zdążył mnie stamtąd wydostać. \n" +" \n" +"Mówiąc wprost - zabij go, zanim on zabije ciebie i doda twoje zwłoki do " +"swojej kolekcji." -#: Source/monstdat.cpp:152 -msgctxt "monster" -msgid "Bone Demon" -msgstr "Kościany Demon" - -#: Source/monstdat.cpp:153 -msgctxt "monster" -msgid "Arch Lich" -msgstr "Arcylisz" - -#: Source/monstdat.cpp:154 -msgctxt "monster" -msgid "Biclops" -msgstr "Dwugłowiec" +#. 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 "" +"Proszę... wysłuchaj mnie. Arcybiskup Lazarus... to on sprowadził nas na dół, " +"żeby odnaleźć zaginionego księcia. Ten drań wprowadził nas w pułapkę! Teraz " +"wszyscy nie żyją... zabił ich demon nazywany Rzeźnikiem. Pomścij nas! Zgładź " +"Rzeźnika, by nasze dusze mogły zaznać spokoju." -#: Source/monstdat.cpp:155 -msgctxt "monster" -msgid "Flesh Thing" -msgstr "Padliniec" +#. 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 "" +"Recytujesz bardzo ciekawy wiersz, napisany w stylu, który przypomina mi " +"jeden utwór. Poczekaj chwilę - jak on brzmiał?\n" +" \n" +"... Ciemność otaczająca ukrytych. Błysk oczu niewidoczny, słyszalny jedynie " +"dźwięk ostrych szponów dręczących biedne dusze, które oślepionona " +"wieczność. \n" +" \n" +"Więzienie dla potępionych zwane... Salami ślepców..." -#: Source/monstdat.cpp:156 -msgctxt "monster" -msgid "Reaper" -msgstr "Żniwiarz" +#. 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 "" +"Nigdy nie interesowałem się poezją. Zatrudniałem bardów, kiedy gospoda " +"jeszcze dobrze prosperowała, ale to dawne czasy.\n" +" \n" +"Co? Och, tak... emm, wiesz, lepiej będzie jak zapytasz kogoś innego." -#. TRANSLATORS: Monster Block end -#. MT_NAKRUL -#: Source/monstdat.cpp:158 Source/monstdat.cpp:485 -msgctxt "monster" -msgid "Na-Krul" -msgstr "Na-Krul" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Brzmi znajomo. Jeśli mnie pamięć nie myli, to natknąłem się na podobny tekst " +"podczas zapoznawania się z historiami chorób powodowanych przez demony. " +"Opisywano tam straszne miejsce, które... czekaj - chyba nie zamierzasz tam " +"iść, co?" -#. TRANSLATORS: Unique Monster Block start -#: Source/monstdat.cpp:473 -msgctxt "monster" -msgid "Gharbad the Weak" -msgstr "Garbad Słaby" +#. 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 "" +"Jeśli masz pytania dotyczące ślepoty, zagadaj do Pepina. Pamiętam, że dał " +"mojej babci miksturę, dzięki której zaczęła lepiej widzieć. Może tobie też " +"będzie w stanie jakoś pomóc." -#: Source/monstdat.cpp:475 -msgctxt "monster" -msgid "Zhar the Mad" -msgstr "Zhar Szalony" +#. 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 "" +"Niestety, nie słyszałem, ani nie widziałem miejsca, które mogłoby pasować do " +"twojego opisu. Porozmawiaj na ten temat z Cainem." -#: Source/monstdat.cpp:476 -msgctxt "monster" -msgid "Snotspill" -msgstr "Śluzoglut" +#. TRANSLATORS: Quest dialog spoken by Farnham +#: Source/textdat.cpp:150 +msgid "Look here... that's pretty funny, huh? Get it? Blind - look here?" +msgstr "Patrzaj na to... ale jaja, nie? No łapiesz? ślepy - i patrzaj!" -#: Source/monstdat.cpp:477 -msgctxt "monster" -msgid "Arch-Bishop Lazarus" -msgstr "Arcybiskup Lazarus" +#. 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 "" +"Jest to miejsce wielkiej udręki i grozy. Poruszaj się po nim ostrożnie albo " +"zostaniesz tam dłużej niż myślisz." -#: Source/monstdat.cpp:478 -msgctxt "monster" -msgid "Red Vex" -msgstr "Czerwona Dręczycielka" +#. 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 "" +"Zobaczmy, czy dużo ode mnie kupujesz? Nie. Czy płacisz za informacje? Nie. " +"Czy właśnie wynosisz się stąd, żeby porozmawiać z kronikarzem, który żyje z " +"gadania? Owszem." -#: Source/monstdat.cpp:479 -msgctxt "monster" -msgid "Black Jade" -msgstr "Nefrytoczerń" +#. 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 "" +"Twierdzisz, że przemówił do ciebie Lachdanan? Za życia był wielkim \n" +"i szanowanym rycerzem. Przez lata wiernie służył swojemu królowi, ale to już " +"pewnie wiesz. \n" +" \n" +"Lachdanan na pewno nie poddałby się tak łatwo Klątwie rzuconej przez Króla, " +"dlatego ta historia może być prawdziwa. Na twoim miejscu, spróbowałbym mu " +"jakoś pomóc." -#: Source/monstdat.cpp:480 -msgctxt "monster" -msgid "Lachdanan" -msgstr "Lachdanan" +#. 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 "" +"Ten dzielny wojownik, o którym mówisz, od dawna nie żyje! Nie chcę rozmawiać " +"o zmarłych przed moją karczmą, dziękuję." -#: Source/monstdat.cpp:481 -msgctxt "monster" -msgid "Warlord of Blood" -msgstr "Marszałek Krwi" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Złoty eliksir, hm. Nigdy wcześniej nie przyrządzałem mikstury w takim " +"kolorze. Trudno mi przewidzieć, jak mógłby na ciebie zadziałać, więc lepiej " +"go NIE PRÓBUJ. Najlepiej będzie jak po prostu spełnisz ostatnią wolę " +"Lachdanana." -#: Source/monstdat.cpp:484 -msgctxt "monster" -msgid "The Defiler" -msgstr "Profanator" +#. 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 "" +"Nigdy wcześniej nie słyszałam o tym Lachdananie. Przykro mi, tym razem nie " +"jestem w stanie pomóc." -#: Source/monstdat.cpp:486 -msgctxt "monster" -msgid "Bonehead Keenaxe" -msgstr "Bezmózgi Topornik" +#. 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 "" +"Jeśli to faktycznie jest Lachdanan, myślę, że warto mu pomóc. Miałem " +"przyjemność go poznać. Wyglądał na osobę z natury lojalną i uczciwą. Klątwa " +"Króla Leoryka prawdopodobnie najdotkliwiej uderzyła więc właśnie w niego." -#: Source/monstdat.cpp:487 -msgctxt "monster" -msgid "Bladeskin the Slasher" -msgstr "Twardoskóry Zabójca" +#. 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 "" +" Lachdanan nie żyje. Nie ze mną te numery, wszyscy już o tym wiedzą. A " +"zmarli nie mówią. Sprawdzałem!" -#: Source/monstdat.cpp:488 -msgctxt "monster" -msgid "Soulpus" -msgstr "Ropiejąca Dusza" +#. 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 "" +"W Labiryncie może być uwięzionych więcej ludzi pokroju Lachdanana. \n" +" \n" +"Wyczuwam w nim ogromne wyrzuty sumienia i honor. Pomagając mu, pomożesz " +"Tristram." -#: Source/monstdat.cpp:489 -msgctxt "monster" -msgid "Pukerat the Unclean" -msgstr "Plugawy Szujożerca" +#. 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 "" +"Czekaj, niech zgadnę. Pod Cainem pojawiła się gigantyczna szczelina, " +"pochłonęła go, a potem spłonął w ogniu piekielnym i nie odpowie już więcej " +"na twoje pytania. Och, nie trafiłem? Dobra, to zgaduję, że albo coś kupujesz " +"albo stąd idziesz." -#: Source/monstdat.cpp:490 -msgctxt "monster" -msgid "Boneripper" -msgstr "Kościorwij" +#. 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 "" +"Proszę, nie krzywdź mnie. Wszystko ci wyjaśnię. Byłem niegdyś kapitanem " +"rycerzy króla Leoryka. Strzegliśmy sprawiedliwości i honoru tutejszych ziem. " +"Niestety, przez udział w jego tragicznej śmierci, spadła na nas mroczna " +"Klątwa. Chociaż moi towarzysze zaakceptowali swój koszmarny los, ja, chcąc " +"uwolnić się od przekleństwa, uciekłem z królewskiej krypty. Nie potrafiłem " +"jednak sobie pomóc...\n" +" \n" +"Słyszałem o Złotym Eliksirze, który mógłby zdjąć ze mnie klątwę. Moja dusza " +"zaznałaby w końcu spokoju. Niestety nie udało mi się go znaleźć. Wciąż " +"słabnę i zanikają we mnie resztki człowieczeństwa. Proszę, pomóż mi odnaleźć " +"Eliksir. Odwdzięczę ci się za twój wysiłek, przysięgam na swój honor." -#: Source/monstdat.cpp:491 -msgctxt "monster" -msgid "Rotfeast the Hungry" -msgstr "Pleśniożer" +#. 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 "" +"Nie masz jeszcze Złotego Eliksiru. Boję się, że pozostanę przeklęty na " +"wieczność. Proszę, znajdź go..." -#: Source/monstdat.cpp:492 -msgctxt "monster" -msgid "Gutshank the Quick" -msgstr "Zwinny Patroszyciel" - -#: Source/monstdat.cpp:493 -msgctxt "monster" -msgid "Brokenhead Bangshield" -msgstr "Czaszkołup Tarczownik" - -#: Source/monstdat.cpp:494 -msgctxt "monster" -msgid "Bongo" -msgstr "Bongo" +#. 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 "" +"Ma dusza została uratowana przed potępieniem, nawet nie wyobrażasz sobie ile " +"ci zawdzięczam. Dołożę wszelkich starań, aby pomóc ci z zaświatów, a " +"tymczasem - weź mój hełm. W drodze, która mnie czeka, na niewiele mi się " +"przyda. Ciebie może za to chronić przed siłami zła, które spotkasz. Niech " +"światłość zostanie twym przewodnikiem..." -#: Source/monstdat.cpp:495 -msgctxt "monster" -msgid "Rotcarnage" -msgstr "Rzezimieszek" +#. 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 "" +"Griswold mówił o legendarnym Kowadle Gniewu, którego nikomu nie udało się " +"jeszcze odnaleźć. To narzędzie rzemieślnicze zostało wytopione z metalowych " +"kości brzytwodemonów, pośród czaszek pięciu najsilniejszych magów podziemi, " +"a następnie pokryte runami mocy i chaosu. Broń i pancerz wykute na tym " +"Kowadle przesiąkają więc potęgą Chaosu, przez co tak naprawdę nigdy nie " +"wiadomo jakie magiczne właściwości otrzymają..." -#: Source/monstdat.cpp:496 -msgctxt "monster" -msgid "Shadowbite" -msgstr "Kąsacz Cieni" +#. 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 "" +"Nie sądzisz, że lepiej byłoby zapytać o to Griswolda? Ma większą wiedzę na " +"ten temat." -#: Source/monstdat.cpp:497 -msgctxt "monster" -msgid "Deadeye" -msgstr "Martwooki" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Mógłbym opowiedzieć coś na temat Misy Uzdrowienia albo Srebrnego Kielicha " +"Oczyszczenia, ale w tym wypadku, lepiej będzie jak udasz się do Griswolda " +"albo Caina." -#: Source/monstdat.cpp:498 -msgctxt "monster" -msgid "Madeye the Dead" -msgstr "Trup Szalonooki" +#. 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 "" +"Kiedy byliśmy mali, ojciec Griswolda opowiadał nam o wielkim kowadle, " +"którego używano do wykuwania potężnych broni. Mówił, że po uderzeniu w nie " +"młotem, ziemia zaczynała drżeć tak, jakby wpadała w gniew. Ilekroć czuję, że " +"ziemia się trzęsie, przypominam sobie tę historię." -#: Source/monstdat.cpp:499 -msgctxt "monster" -msgid "El Chupacabras" -msgstr "El Chupacabras" +#. 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 "" +"Witaj! Cieszę się, gdy odwiedzają mnie ulubieni klienci! Podobno schodzisz " +"na coraz niższe poziomy Labiryntu, więc warto byłoby ci opowiedzieć pewną " +"historię. \n" +" \n" +"Jeden z mieszkańców, któremu udało się wrócić z Katedry, opowiedział mi o " +"niezwykłym kowadle, które zobaczył w trakcie ucieczki. Opis, który " +"przedstawił, przypomniał mi o legendzie z dzieciństwa. Opowiadała ona o " +"Piekielnej Kuźni, w której wykuwano potężne bronie nasycone magią. Mówiła " +"także o tym, że gdzieś na jej terenie znajduje się Kowadło Gniewu! Podobno " +"zawiera w sobie esencję świata demonów. \n" +" \n" +"Mówi się, że broń wykuta na płonącym Kowadle nasycona jest ogromną mocą. " +"Jeśli rzeczywiście było ono tym z legendy, mógłbym za jego pomocą wykuć broń " +"zdolną do zniszczenia nawet najpotężniejszego władcy Piekieł. \n" +" \n" +"Przynieś mi to kowadło i od razu biorę się do roboty!" -#: Source/monstdat.cpp:500 -msgctxt "monster" -msgid "Skullfire" -msgstr "Ognistogłowy" +#. 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 "" +"Na razie nic? Nie poddawaj się. Broń wykuta na tym kowadle znacznie zwiększy " +"twoje szanse. Wykonam ją najlepiej jak potrafię, zaufaj mi." -#: Source/monstdat.cpp:501 -msgctxt "monster" -msgid "Warpskull" -msgstr "Wypaczona Czaszka" +#. 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 "" +"Nie mogę w to uwierzyć! Udało ci się, to jest Kowadło Gniewu. Doskonała " +"robota. Teraz pokażemy tym bydlakom, że broń zrobiona przez człowieka " +"potrafi spuścić dużo większy łomot niż najlepsza broń Piekieł. Weź to. Niech " +"światłość ma cię w swojej opiece." -#: Source/monstdat.cpp:502 -msgctxt "monster" -msgid "Goretongue" -msgstr "Krwiopijca" +#. 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 "" +"Griswold nie może sprzedać swojego kowadła. No bo bez niego chyba nie będzie " +"słyszał. Ja to nigdy nie oddałbym mojego kowadełka!" -#: Source/monstdat.cpp:503 -msgctxt "monster" -msgid "Pulsecrawler" -msgstr "Rozdrażniony Pełzacz" +#. 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 "" +"Wewnątrz Labiryntu znajduje się wiele artefaktów o mocach przekraczających " +"pojęcie śmiertelników. Część z nich może być używana zarówno przez siły " +"Cienia jak i światłości. Zabranie tego Kowadła z podziemi może przechylić " +"szalę Wojny Grzechu w stronę światła." -#: Source/monstdat.cpp:504 -msgctxt "monster" -msgid "Moonbender" -msgstr "Zaklinacz Księżyca" +#. 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 "" +"Jeśli znajdziesz artefakt dla Griswolda, poważnie zaszkodzisz moim " +"interesom. Ach, nie uda ci się to." -#: Source/monstdat.cpp:505 -msgctxt "monster" -msgid "Wrathraven" -msgstr "Kruk Gniewu" +#. 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 "" +"Brama Krwi i Komnata Ognia to obiekty o mistycznym pochodzeniu. Gdziekolwiek " +"znajduje się miejsce, wspomniane w tej księdze, na pewno emanuje tam wielka " +"moc. \n" +" \n" +"Legendy mówią o obsydianowym piedestale ozdobionym kośćmi i zwieńczonym " +"źródłem wrzącej krwi. Mówi się również o Kamieniach Krwi, które umożliwiają " +"otwarcie komnaty strzegącej starożytnego skarbu... \n" +" \n" +"Powiadają, że starożytny bohater Arkain, umieścił tam święty pancerz Odwagi. " +"Arkain był pierwszym śmiertelnikiem, któremu udało się przechylić szalę " +"Wojny Grzechu i przegonić legiony ciemności z powrotem do płonących " +"piekieł. \n" +" \n" +"Tuż przed jego śmiercią, zbroję ukryto w tajnej krypcie. Mówi się, że gdy " +"ten święty pancerz będzie znowu potrzebny, nadejdzie bohater i ponownie " +"przywdzieje... Odwagę. Być może to będziesz ty..." -#: Source/monstdat.cpp:506 -msgctxt "monster" -msgid "Spineeater" -msgstr "Kręgojad" +#. 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 "" +"Każde dziecko słyszało historię o wojowniku Arkainie i jego niezwykłym " +"pancerzu Odwagi. Odnalezienie go zapewni ci dobrą ochronę przed złem z " +"Labiryntu." -#: Source/monstdat.cpp:507 -msgctxt "monster" -msgid "Blackash the Burning" -msgstr "Spopielacz" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Hmm... to brzmi znajomo, ale jestem teraz tak pochłonięty nauką tworzenia " +"nowych leków i eliksirów, że za nic nie mogę sobie tego przypomnieć. " +"Wybacz..." -#: Source/monstdat.cpp:508 -msgctxt "monster" -msgid "Shadowcrow" -msgstr "Cieniowron" +#. 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 "" +"Historia o magicznym pancerzu, zwanym Odwagą? Brzmi jak coś z opowieści " +"chłopców. Może zapytaj jednego z nich?" -#: Source/monstdat.cpp:509 -msgctxt "monster" -msgid "Blightstone the Weak" -msgstr "Osłabiony Niszczyciel" +#. 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 "" +"Pancerz Odwagi może przechylić szalę na twoją stronę. Wielu go już szukało - " +"w tym ja - niestety bezskutecznie. Arkain dobrze go ukrył i żeby go odnaleźć " +"potrzeba będzie raczej czegoś więcej, niż odrobiny szczęścia." -#: Source/monstdat.cpp:510 -msgctxt "monster" -msgid "Bilefroth the Pit Master" -msgstr "Rządca Czeluści" +#. TRANSLATORS: Quest dialog "spoken" by Farnham +#: Source/textdat.cpp:208 +msgid "Zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz..." +msgstr "Chrrrrrrrrrrrrrrrrrrrrrrrrrrrr..." -#: Source/monstdat.cpp:511 -msgctxt "monster" -msgid "Bloodskin Darkbow" -msgstr "Krwawoskóry Łucznik" +#. 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 "" +"Jeśli podczas wędrówki, natrafisz na Kamienie Krwi, korzystaj z nich " +"ostrożnie. \n" +" \n" +"Droga pełna jest niebezpieczeństw, a ocalić może cię jedynie wiara we własne " +"siły." -#: Source/monstdat.cpp:512 -msgctxt "monster" -msgid "Foulwing" -msgstr "Plugawoskrzydły" +#. 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 "" +"Zamierzasz znaleźć pancerz, zwany Odwagą?\n" +" \n" +"Arkaine ukrył go tak dobrze, że nikomu się to jeszcze nie udało. Skoro moje " +"znajomości nic nie zdziałały, to wątpię, by tobie miało się powieść." -#: Source/monstdat.cpp:513 -msgctxt "monster" -msgid "Shadowdrinker" -msgstr "Spijacz Cieni" +#. 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 "" +"Znam tylko jedną legendę, która opowiada o takim wojowniku. Historia o nim " +"znajduje się wewnątrz starożytnych kronik Wojny Grzechu... \n" +"Splamiony krwią, śmiercią i tysiącami lat wojen, Marszałek Krwi wchodzi na " +"stos swych wypatroszonych ofiar. Za pomocą swego demonicznego ostrza, rzuca " +"na ludzkość tajemniczą klątwę, nawołując do stawienia się przed oblicze " +"Piekielnego Kata.\n" +" \n" +"Napisano również, że kiedyś był śmiertelnikiem walczącym w Wojnie Grzechu u " +"boku Legionów Ciemności. Stracił człowieczeństwo przez swą wiecznie " +"niezaspokojoną żądzę krwi." -#: Source/monstdat.cpp:514 -msgctxt "monster" -msgid "Hazeshifter" -msgstr "Dręczyciel" +#. 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 "" +"Obawiam się, że nic nie słyszałem o tak okrutnym wojowniku. Mam nadzieję, że " +"nie spotkasz go na swojej drodze, bo... wydaje się być wyjątkowo " +"niebezpieczny." -#: Source/monstdat.cpp:515 -msgctxt "monster" -msgid "Deathspit" -msgstr "Śmierciopluj" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Cain będzie mógł powiedzieć na ten temat więcej niż ja kiedykolwiek " +"chciałbym usłyszeć." -#: Source/monstdat.cpp:516 -msgctxt "monster" -msgid "Bloodgutter" -msgstr "Krwiopijca" +#. 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 "" +"Jeśli staniesz do walki z tak groźnym przeciwnikiem, niech czuwa nad tobą " +"światłość. Wierzę, że ci się uda." -#: Source/monstdat.cpp:517 -msgctxt "monster" -msgid "Deathshade Fleshmaul" -msgstr "Cień Rozpruwacza" +#. 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 "" +"Postać Marszałka Krwi otaczają potworne legendy. Dobrze przygotuj się do " +"starcia z nim, gdyż jest bezwzględny i bezlitosny." -#: Source/monstdat.cpp:518 -msgctxt "monster" -msgid "Warmaggot the Mad" -msgstr "Wojenny Czerw" +#. 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 "" +"Dlaczego ty chcesz ciągle rozmawiać o Krwi? A co z kwiatkami, słoneczkiem... " +"no i na przykład tą piękną panienką przy barze. Słuchaj no - ty masz już " +"chyba obsesję!" -#: Source/monstdat.cpp:519 -msgctxt "monster" -msgid "Glasskull the Jagged" -msgstr "Kościany Okruch" +#. 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 "" +"Jego umiejętność posługiwania się ostrzem jest godna podziwu, w końcu od " +"tysięcy lat poruszał się wyłącznie na polu bitwy. Przykro mi, ale nie " +"potrafię przewidzieć czy wygrasz ten pojedynek." -#: Source/monstdat.cpp:520 -msgctxt "monster" -msgid "Blightfire" -msgstr "Płomień Zniszczenia" +#. 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 "" +"Nigdy nie widziałem się z tym Marszałkiem, ale ma chyba spory przerób " +"mieczy. Chętnie zaopatrzyłbym jego wojska..." -#: Source/monstdat.cpp:521 -msgctxt "monster" -msgid "Nightwing the Cold" -msgstr "Mroźny Nocoskrzydły" +#. 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 "Lata wojen sprawiły, że pragnę już tylko jednego. Twojej krwi!" -#: Source/monstdat.cpp:522 -msgctxt "monster" -msgid "Gorestone" -msgstr "Rzezikamień" +#. 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 "" +"Griswold mówi o Kamieniu Niebios, który miał zostać zawieziony do wschodniej " +"enklawy i poddany tam dalszym badaniom. Emanował energią, która wyzwalała " +"wizje niedostępne zwykłemu człowiekowi. Nie wiem, jakie jeszcze skrywa " +"tajemnice, ale bez wątpienia warto byłoby go odnaleźć." -#: Source/monstdat.cpp:523 -msgctxt "monster" -msgid "Bronzefist Firestone" -msgstr "Ognisty Pięściarz" +#. 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 "" +"Karawana zatrzymała się tu, żeby uzupełnić zapasy przed dalszą podróżą na " +"wschód. Sprzedałem im prawie całą skrzynię świeżych owoców i znakomite " +"mięso, przygotowane przez Gardę. Szkoda,że tak skończyli..." -#: Source/monstdat.cpp:524 -msgctxt "monster" -msgid "Wrathfire the Doomed" -msgstr "Zgładzony Furiat" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Nie wiem po co im był ten kamień, ale skoro spadł z nieba, to lepiej na " +"siebie uważaj!" -#: Source/monstdat.cpp:525 -msgctxt "monster" -msgid "Firewound the Grim" -msgstr "Poparzony Pustelnik" +#. 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 "" +"Tak, kojarzę. Kiedyś zatrzymała się tu karawana z grupą bardzo ważnych " +"ludzi. Pamiętam, że mieli dość specyficzne akcenty i przygotowywali się " +"właśnie do dalekiej podróży. \n" +" \n" +"Myślę, że odnalezienie ich rzeczy może być teraz naprawdę trudne." -#: Source/monstdat.cpp:526 -msgctxt "monster" -msgid "Baron Sludge" -msgstr "Błotnisty Magnat" +#. 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 "" +"Zaczekaj chwilę - opowiem ci historię, która może cię zainteresować. Jakiś " +"czas temu przejeżdżała tu karawana zmierzająca w kierunku wschodnich " +"królestw. Rzekomo przewoziła skrawek nieba, który spadł na ziemię! Niestety " +"karawana została okradziona przez zamaskowanych jeźdźców niedaleko północnej " +"granicy naszej wioski. Przeszukałem jej pozostałości, chcąc znaleźć " +"niebiański kamień, lecz nie było już po nim śladu. Znajdź go i przynieś, a " +"wykonam dla ciebie coś naprawdę niesamowitego." -#: Source/monstdat.cpp:527 -msgctxt "monster" -msgid "Blighthorn Steelmace" -msgstr "Cień Zniszczenia" +#. 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 "" +"Mam nadzieję, że uda ci się znaleźć ten kamień. Jeśli mi go przyniesiesz, " +"wykonam z niego coś potężnego." -#: Source/monstdat.cpp:528 -msgctxt "monster" -msgid "Chaoshowler" -msgstr "Skowytnik" +#. 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 "" +"Pokaż mi to - och... tak go sobie wyobrażałem. Daj mi chwilę...\n" +" \n" +"Ach, proszę bardzo. Umieściłem kawałki tego kamienia w srebrnym pierścieniu, " +"który odziedziczyłem po ojcu. Mam nadzieję, że będzie ci dobrze służyć." -#: Source/monstdat.cpp:529 -msgctxt "monster" -msgid "Doomgrin the Rotting" -msgstr "Gnijący Chichot" +#. 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 "" +"Miałem kiedyś piękny pierścień; bardzo drogi, był trochę niebieskawy, trochę " +"zieloniutki, czerwoniutki... i srebrny też był. Nie pamiętam już co się z " +"nim stało, ale bardzo mi go brakuje..." -#: Source/monstdat.cpp:530 -msgctxt "monster" -msgid "Madburner" -msgstr "Wściekły Podpalacz" +#. 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 "" +"W Kamieniu Niebios drzemie ogromna moc, a Griswold będzie potrafił " +"wykorzystać ją dla naszego wspólnego dobra. Wiedz, że gdyby ktoś inny zlecił " +"ci znalezienie tego kamienia, powstrzymałabym cię." -#: Source/monstdat.cpp:531 -msgctxt "monster" -msgid "Bonesaw the Litch" -msgstr "Lisz Piłognat" +#. 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 "" +"Jeżeli ktokolwiek mógłby zrobić coś sensownego z tego kamienia, to tylko " +"Griswold. On zna się na swojej robocie. Mimo, że czasami podkradam mu " +"klientów, to szanuję jakość jego towarów." -#: Source/monstdat.cpp:532 -msgctxt "monster" -msgid "Breakspine" -msgstr "Łamikark" +#. 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 "" +"Wiedźma Adria szuka czarnego grzyba? Wiem o nim tyle samo co o uzbrojonych " +"krowach. W moich księgach nic o tym nie wspominano. Może Pepin będzie " +"wiedział coś na jego temat." -#: Source/monstdat.cpp:533 -msgctxt "monster" -msgid "Devilskull Sharpbone" -msgstr "Pożeracz Kości" +#. 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 "" +"Powiem tylko, że razem z Gardą, nigdy, PRZENIGDY, nie podalibyśmy naszym " +"dostojnym gościom czarnych grzybów. Jeśli Adria chce ich w swoim kotle, to " +"jej sprawa, ja nawet nie wiem gdzie ich szukać. Czarne grzyby... ohyda!" -#: Source/monstdat.cpp:534 -msgctxt "monster" -msgid "Brokenstorm" -msgstr "Burzowiec" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Wiedźma powiedziała, że przyniesiesz mi mózg demona. Potrzebuję go do " +"opracowania antidotum. Dla ludzi, którzy zostali zranieni przez te plugawe " +"bestie, moje lekarstwo będzie na wagę złota. Muszę tylko rozpracować " +"działanie trucizny, którą zostali zakażeni. Wystarczy więc, że wyjmiesz mózg " +"z demona którego zabijesz i mi go dostarczysz. Będę ci za to naprawdę " +"wdzięczny." -#: Source/monstdat.cpp:535 -msgctxt "monster" -msgid "Stormbane" -msgstr "Szturmująca Zguba" +#. 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 "" +"Wspaniale, dokładnie taki miałem na myśli. Nie potrzebowałem go do " +"skończenia eliksiru, ale zostanie do badań. Mógłbym cię prosić o zaniesienie " +"tego wiedźmie? Chyba czeka na tę miksturę." -#: Source/monstdat.cpp:536 -msgctxt "monster" -msgid "Oozedrool" -msgstr "Szlamożer" +#. 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 "" +"Ogden zapewne ma kilka grzybów w swojej spiżarni. Może więc jego zapytaj?" -#: Source/monstdat.cpp:537 -msgctxt "monster" -msgid "Goldblight of the Flame" -msgstr "Płomienny Złotognij" +#. 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 "" +"Skoro Adria go jeszcze nie ma, na pewno jest wyjątkowy. Nic o nim nie wiem, " +"ale to musi być... ogromny, nabrzmiały, gigantyczny i nadęty grzyb! No, " +"życzę udanego grzybobrania." -#: Source/monstdat.cpp:538 -msgctxt "monster" -msgid "Blackstorm" -msgstr "Czarny Sztorm" +#. 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 "" +"Ogden przyrządza takie czarne grzyby, że jak wypiję, to od razu mną miota. " +"Słuchaj no, słuchaj... jest na to sposób - trzeba pić z umiarem!" -#: Source/monstdat.cpp:539 -msgctxt "monster" -msgid "Plaguewrath" -msgstr "Plagoupiór" +#. 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 "" +"Co my tu mamy? Ciekawe, to wygląda na księgę alchemicznych składników. Jest " +"i czarny grzyb. Powinien być ogromny, na pewno trudno go będzie przeoczyć. " +"Jeśli takowy znajdziesz, przynieś mi go, dobrze?" -#: Source/monstdat.cpp:540 -msgctxt "monster" -msgid "The Flayer" -msgstr "Obdzieracz" +#. 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 "" +"To duży i czarny grzyb. Idź go szukać, jest mi potrzebny do specjalnej " +"mikstury." -#: Source/monstdat.cpp:541 -msgctxt "monster" -msgid "Bluehorn" -msgstr "Błękitnorogi" +#. 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 "" +"Tak, ten będzie idealny do mojego wywaru. Nawiasem mówiąc, uzdrowiciel " +"potrzebuje mózgu jednego z demonów, żeby móc wyleczyć ludzi zatrutych jadem. " +"Pewnie zamierza zrobić z niego antidotum. Jeśli pomożesz mu znaleźć to czego " +"szuka, poproś go o odrobinę tego eliksiru dla mnie." -#: Source/monstdat.cpp:542 -msgctxt "monster" -msgid "Warpfire Hellspawn" -msgstr "Piekielne Plugastwo" +#. 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 "" +"Po co mi to przynosisz? Nie potrzebuję mózgu demona. Chcę trochę eliksiru " +"nad którym pracuje uzdrowiciel. To on potrzebuje tego cudacznego organu, " +"który właśnie trzymasz, do przyrządzenia mikstury. Wystarczy trochę " +"pomyśleć, prawda?" -#: Source/monstdat.cpp:543 -msgctxt "monster" -msgid "Fangspeir" -msgstr "Ostrokieł" +#. 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 "" +"Co? Dopiero teraz przynosisz mi eliksir od uzdrowiciela? Jednak nie był mi " +"potrzebny do wywaru. Zatrzymaj go dla siebie..." -#: Source/monstdat.cpp:544 -msgctxt "monster" -msgid "Festerskull" -msgstr "Pęknięta Czaszka" +#. 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 "Nie mam żadnych grzybów. Może chcesz coś bardziej użytecznego?" -#: Source/monstdat.cpp:545 -msgctxt "monster" -msgid "Lionskull the Bent" -msgstr "Krętacz Lwia Czaszka" +#. 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 "Usunięty quest!" -#: Source/monstdat.cpp:546 -msgctxt "monster" -msgid "Blacktongue" -msgstr "Czarny Jęzorak" +#. 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 "Usunięty quest." -#: Source/monstdat.cpp:547 -msgctxt "monster" -msgid "Viletouch" -msgstr "Plugawion" +#. 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 "Usunięty quest." -#: Source/monstdat.cpp:548 -msgctxt "monster" -msgid "Viperflame" -msgstr "Ogniożmij" +#. 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 "Usunięty quest." -#: Source/monstdat.cpp:549 -msgctxt "monster" -msgid "Fangskin" -msgstr "Zęboskóry" +#. TRANSLATORS: Quest dialog spoken by Pepin (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 "Usunięty quest." -#: Source/monstdat.cpp:550 -msgctxt "monster" -msgid "Witchfire the Unholy" -msgstr "Wiedźma Profanacji" +#. 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 "Usunięty quest." -#: Source/monstdat.cpp:551 -msgctxt "monster" -msgid "Blackskull" -msgstr "Czarnokostny" - -#: Source/monstdat.cpp:552 -msgctxt "monster" -msgid "Soulslash" -msgstr "Rozpruwacz Dusz" +#. 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 "Usunięty quest." -#: Source/monstdat.cpp:553 -msgctxt "monster" -msgid "Windspawn" -msgstr "Porywisty Niegodziwiec" +#. 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 "" +"Cho... Chono tu. Słuchaj. Nie wiem czy ty to widzisz, ale tu jest coś " +"narysowane. To mapa." -#: Source/monstdat.cpp:554 -msgctxt "monster" -msgid "Lord of the Pit" -msgstr "Pan Czeluści" +#. 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 "Usunięty quest." -#: Source/monstdat.cpp:555 -msgctxt "monster" -msgid "Rustweaver" -msgstr "Tkacz Rdzy" +#. 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 "Usunięty quest." -#: Source/monstdat.cpp:556 -msgctxt "monster" -msgid "Howlingire the Shade" -msgstr "Wyjący Cień" +#. 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 "Proooosi nie bij. Nie zabijaj. Daruj życie, a dobro cię spotka." -#: Source/monstdat.cpp:557 -msgctxt "monster" -msgid "Doomcloud" -msgstr "Powiew Zagłady" +#. 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 "" +"Dla ciebie coś robi. Prosi zostaw Garbada. On żyje i daje dobro.\n" +" \n" +"A ty weź to jako dowód obietnicy..." -#: Source/monstdat.cpp:558 -msgctxt "monster" -msgid "Bloodmoon Soulfire" -msgstr "Księżycowa Dusza" +#. 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 "" +"Jeszcze nic! Kończy! \n" +" \n" +"Wielka moc i siła. Żyć! Żyć! \n" +" \n" +"Nie męczył, więc spełni obietnicę!" -#: Source/monstdat.cpp:559 -msgctxt "monster" -msgid "Witchmoon" -msgstr "Księżycowa Wiedźma" +#. 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 "To za dobre! Zbyt silne! Nie odda tak łatwo!" -#: Source/monstdat.cpp:560 -msgctxt "monster" -msgid "Gorefeast" -msgstr "Żywiciel Krwi" +#. 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 "" +"Co?! Co ty tutaj robisz? Przez takich jak ty nie mogę się skoncentrować. " +"Masz, weź to i daj mi pracować w spokoju. Więcej się tu nie pałętaj!" -#: Source/monstdat.cpp:561 -msgctxt "monster" -msgid "Graywar the Slayer" -msgstr "Pogromiciel" +#. TRANSLATORS: Quest dialog spoken by Zhar the Mad (Hostile) +#: Source/textdat.cpp:307 +msgid "Arrrrgh! Your curiosity will be the death of you!!!" +msgstr "Arrrrgh! Ciekawość prowadzi do piekła!!!" -#: Source/monstdat.cpp:562 -msgctxt "monster" -msgid "Dreadjudge" -msgstr "Sędzia Grozy" +#. TRANSLATORS: Neutral dialog spoken by Cain +#: Source/textdat.cpp:308 +msgid "Hello, my friend. Stay awhile and listen..." +msgstr "Zostań na chwilę i posłuchaj..." -#: Source/monstdat.cpp:563 -msgctxt "monster" -msgid "Stareye the Witch" -msgstr "Gwiezdnooka Wiedźma" +#. 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 "" +"Kiedy zaczniesz schodzić w głąb Labiryntu, prawdopodobnie natrafisz na " +"ukryte tam księgi o wielkiej wiedzy. \n" +" \n" +"Czytaj je uważnie, mogą powiedzieć ci o rzeczach, o których nawet ja nie " +"słyszałem." -#: Source/monstdat.cpp:564 -msgctxt "monster" -msgid "Steelskull the Hunter" -msgstr "Łowca Stalowa Czaszka" +#. 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 "" +"Znam wiele mitów i legend, które mogą zawierać odpowiedzi na nurtujące cię " +"pytania. Jeżeli podczas podróży obudzą się w tobie wątpliwości, przyjdź z " +"nimi do mnie. Pomogę ci w miarę możliwości." -#: Source/monstdat.cpp:565 -msgctxt "monster" -msgid "Sir Gorash" -msgstr "Generał Gorasz" +#. 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 "" +"Griswold to człowiek wielkich czynów. Pewnie nie mówił ci o swojej wyprawie " +"w głąb labiryntu. Uratował wtedy Wirta. Odczuł też na własnej skórze jak tam " +"jest niebezpiecznie. Poza tym jest niezwykle uzdolnionym rzemieślnikiem. " +"Jeżeli twierdzi, że zrobi wszystko, żeby ci pomóc, zaufaj mu." -#: Source/monstdat.cpp:566 -msgctxt "monster" -msgid "The Vizier" -msgstr "Wezyr" +#. 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 "" +"Ogden jest właścicielem Gospody pod Wschodzącym Słońcem już od prawie " +"czterech lat. Kupił ją kilka miesięcy przed tym jak wszystko poszło w " +"diabły. Wraz z żoną, Gardą, nie mają pieniędzy na wyjazd. Wszystkie " +"oszczędności zainwestowali w ułożenie sobie życia tutaj. Ogden jest dobrym i " +"bardzo odpowiedzialnym człowiekiem." -#: Source/monstdat.cpp:567 -msgctxt "monster" -msgid "Zamphir" -msgstr "Zamphir" +#. 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 "" +"Biedny Farnham. Jako jeden z nielicznych przetrwał zagładę grupy, która " +"tamtego strasznego dnia wkroczyła do Katedry z Lazarusem. Przeżył, ale jego " +"odwaga i rozsądek pozostały w mrokach podziemi. Teraz spokój odnajduje " +"jedynie na dnie swojego kufla. Słuchaj go uważnie, ponieważ czasami z jego " +"chaotycznego bełkotu można wyciągnąć skrawek prawdy." -#: Source/monstdat.cpp:568 -msgctxt "monster" -msgid "Bloodlust" -msgstr "Żądna Krwi" +#. 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 "" +"Adria wzbudza we mnie niepokój. Przybyła krótko po zbezczeszczeniu katedry, " +"w czasie gdy wszyscy inni uciekali. Zamieszkała w małej chatce na obrzeżach " +"miasta, zbudowanej ponoć z dnia na dzień. Ponadto, wiedźma ta posiada dostęp " +"do wielu dziwnych i tajemniczych artefaktów oraz ksiąg, których nigdy " +"wcześniej nie widziałem." -#: Source/monstdat.cpp:569 -msgctxt "monster" -msgid "Webwidow" -msgstr "Wdowa Sieci" +#. 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 "" +"Historia Wirta jest przerażająca. Został wyrwany z rąk swej matki i " +"zaciągnięty do labiryntu przez małe, plugawe, dzierżące włócznie pomioty. " +"Musisz wiedzieć, że tamtego dnia porwano też wiele innych dzieci, w tym syna " +"Króla Leoryka. Rycerze z pałacu ruszyli im na ratunek, lecz od tamtej pory " +"nie dali już znaku życia. Wirta odnalazł kowal, niestety chłopiec padł już " +"ofiarą potwornych tortur tych plugawych bestii." -#: Source/monstdat.cpp:570 -msgctxt "monster" -msgid "Fleshdancer" -msgstr "Pożeracz Zwłok" +#. 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 "" +"Ach, Pepin. Uważam go za prawdziwego przyjaciela - chyba najbliższego, " +"jakiego mam. Czasami wydaje się być trochę zagubiony, ale nigdy jeszcze nie " +"spotkałem cieplejszej duszy. Niewielu posiada takie zasoby wiedzy czy " +"umiejętności i chce się nimi dzielić." -#: Source/monstdat.cpp:571 -msgctxt "monster" -msgid "Grimspike" -msgstr "Ostrze Grozy" +#. 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 "" +"Gillian jest wspaniałą kobietą. Zajmuje w moim sercu szczególne miejsce. Ma " +"pogodną duszę, a jej uśmiech niejednego już oczarował. Mieszka w gospodzie i " +"pomaga swojej babci, która jest zbyt chora, żeby się swobodnie poruszać. " +"Choć wiem, że każdy mężczyzna w wiosce oddałby za nią życie, to i tak " +"czasami boję się o jej bezpieczeństwo." -#. TRANSLATORS: Unique Monster Block end -#: Source/monstdat.cpp:573 -msgctxt "monster" -msgid "Doomlock" -msgstr "Zaklęta Śmierć" +#. TRANSLATORS: Neutral dialog spoken by Ogden +#: Source/textdat.cpp:327 +msgid "Greetings, good master. Welcome to the Tavern of the Rising Sun!" +msgstr "Witaj w mojej Gospodzie!" -#: Source/monster.cpp:3386 -msgid "Animal" -msgstr "Zwierzę" - -#: Source/monster.cpp:3388 -msgid "Demon" -msgstr "Demon" +#. 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 "" +"Wielu śmiałków zasiadało przy stołach mej gospody i opowiedziano tutaj " +"mnóstwo historii zapijając je także mnóstwem piwa. Jedyną rzeczą, z którą " +"prawie każdy się zgadzał, była stara maksyma. Może ci się przyda. Ciało " +"możesz rozciąć, ale kości musisz zmiażdżyć." -#: Source/monster.cpp:3390 -msgid "Undead" -msgstr "Nieumarły" +#. 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 "" +"Kowal Griswold posiada ogromną wiedzę na temat broni i pancerzy. Koniecznie " +"go odwiedź, jeśli twój ekwipunek będzie wymagał naprawy." -#: Source/monster.cpp:4643 -msgid "Type: {:s} Kills: {:d}" -msgstr "Rodzaj: {:s} Zabitych: {:d}" +#. 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 "" +"Farnham spędza tu stanowczo za dużo czasu. Nieustannie zapija smutki tanim " +"piwem. Nie mam serca, żeby go wyprosić, bo domyślam się ile wycierpiał w " +"Labiryncie." -#: Source/monster.cpp:4645 -msgid "Total kills: {:d}" -msgstr "Suma zabitych: {:d}" +#. 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 "" +"Adria jest zbyt mądra jak na swój wiek i to mnie troszeczkę przeraża. \n" +" \n" +"W sumie, to bez znaczenia. Posiada niepokojąco wiele magicznych przedmiotów. " +"Znajdziesz ją w chacie, po drugiej stronie rzeki." -#: Source/monster.cpp:4677 -msgid "Hit Points: {:d}-{:d}" -msgstr "Punkty życia: {:d}-{:d}" +#. 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 "" +"Jeśli chcesz dowiedzieć się więcej o historii naszej wioski, porozmawiaj z " +"naszym kronikarzem, Cainem. On naprawdę dobrze zna jej przeszłość." -#: Source/monster.cpp:4682 -msgid "No magic resistance" -msgstr "Brak odporności na magię" +#. 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 "" +"Wirt? Ach, to mały urwis. Od zawsze wpadał w tarapaty, więc nie dziwi mnie " +"co go w końcu spotkało. \n" +" \n" +"Prawdopodobnie kręcił się w miejscu, w którym nie powinno go być.Szkoda mi " +"tego chłopca, wpadł w całkowicie nieodpowiednie towarzystwo." -#: Source/monster.cpp:4685 -msgid "Resists:" -msgstr "Odporności:" +#. 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 "" +"Pepin jest dobrym człowiekiem. Z pewnością najbardziej wielkodusznym w " +"wiosce. Zawsze stara się pomagać ludziom w potrzebie, przez co ciężko jest " +"mu znaleźć nawet chwilę wytchnienia..." -#: Source/monster.cpp:4687 Source/monster.cpp:4697 -msgid " Magic" -msgstr " Magię" +#. 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 "" +"Gillian, moja barmanka? Chyba tylko miłość do babci powstrzymuje ją przed " +"ucieczką.\n" +" \n" +"Nie wiem ile już razy przekonywałem ją do wyjazdu i obiecywałem zająć się " +"staruszką. Niestety, ta młoda dziewczyna ma zbyt czułe serce, żeby ją " +"opuścić." -#: Source/monster.cpp:4689 Source/monster.cpp:4699 -msgid " Fire" -msgstr " Ogień" +#. TRANSLATORS: Neutral dialog spoken by Pepin +#: Source/textdat.cpp:345 +msgid "What ails you, my friend?" +msgstr "Jak się czujesz?" -#: Source/monster.cpp:4691 Source/monster.cpp:4701 -msgid " Lightning" -msgstr " Błyskawice" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Dokonałem ostatnio bardzo interesującego odkrycia. W przeciwieństwie do nas, " +"potwory z labiryntu mogą się leczyć bez używania mikstur czy magii. Jeśli " +"zaczniesz walczyć z jednym takim stworem upewnij się, że go zabiłeś, inaczej " +"szybko się zregeneruje." -#: Source/monster.cpp:4695 -msgid "Immune:" -msgstr "Niewrażliwość na:" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Zanim Katedra została opanowana przez te szkaradztwa, była wielką skarbnicą " +"wiedzy. Można było w niej znaleźć ogromne zbiory wartościowych ksiąg. Jeżeli " +"jeszcze jakieś znajdziesz, koniecznie je przeczytaj. Niektóre tomy mogą " +"wyjaśniać sekrety labiryntu." -#: Source/monster.cpp:4712 -msgid "Type: {:s}" -msgstr "Rodzaj: {:s}" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Griswold wie tak dużo o sztuce wojny, ile ja o sztuce leczenia. Jest " +"sprytnym sprzedawcą, a jego dzieła nie mają sobie równych. Och, to " +"prawdopodobnie dlatego, że jest teraz jedynym kowalem pozostałym w wiosce." -#: Source/monster.cpp:4717 Source/monster.cpp:4723 -msgid "No resistances" -msgstr "Brak odporności" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Cain jest prawdziwym mędrcem i przyjacielem. Przechowuje ogromną bibliotekę " +"i ma wrodzoną zdolność dostrzegania prawdziwej natury problemów. Jeżeli " +"kiedykolwiek będą cię jakieś nurtować, nie wahaj się do niego zagadać." -#: Source/monster.cpp:4718 Source/monster.cpp:4727 -msgid "No Immunities" -msgstr "Brak niewrażliwości" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Nawet moje umiejętności nie wystarczyły by w pełni uzdrowić Farnhama. Och, " +"potrafiłem wyleczyć jego ciało, ale nie jestem w stanie naprawić jego umysłu " +"i ducha." -#: Source/monster.cpp:4721 -msgid "Some Magic Resistances" -msgstr "Lekka odporność na magię" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Podczas gdy ja używam magii jedynie do tworzenia mikstur i eliksirów, które " +"tu sprzedaję, Adria zajmuje się prawdziwymi czarami. Wygląda na osobę " +"niezaznającą snu i zawsze ma dostęp do wielu mistycznych ksiąg oraz " +"artefaktów. Podejrzewam, że jej chata kryje więcej niż wydaje się na " +"pierwszy rzut oka, ale nigdy nie miałem okazji wejść do środka." -#: Source/monster.cpp:4725 -msgid "Some Magic Immunities" -msgstr "Częściowa niewraż. na magię" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Biedny Wirt. Zrobiłem dla tego dziecka wszystko co było w mojej mocy. Miał " +"paskudne rany. Wiem, że nie znosi drewnianego kołka, który zastępuje mu " +"nogę. Nikt nie powinien tak cierpieć, a już na pewno nie taki młody chłopiec." -#: Source/msg.cpp:486 -msgid "Trying to drop a floor item?" -msgstr "Próbujesz upuścić przedmiot leżący na ziemi?" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Naprawdę nie rozumiem dlaczego Ogden został w Tristram. Ma słabe nerwy, ale " +"jest inteligentnym i pracowitym człowiekiem, który poradziłby sobie " +"wszędzie. Możliwe, że bał się wyjechać po usłyszeniu wieści o morderstwach w " +"okolicy. Mógł też pozostać tu za namową żony, która nie chciała wyjeżdżać." -#: 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} aktywował nielegalne zaklęcie." +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Barmanka, z gospody Ogdena, to urocza dziewczyna. Jej babcia jest bardzo " +"chora i cierpi na halucynacje. \n" +" \n" +"Uważa je za wizje, ale nie jestem w stanie ocenić czy mówi prawdę." -#: Source/msg.cpp:1684 Source/multi.cpp:738 Source/multi.cpp:787 -msgid "Player '{:s}' (level {:d}) just joined the game" -msgstr "Gracz '{:s}' (poziom {:d}) dołączył do gry" +#. TRANSLATORS: Neutral dialog spoken by Gillian +#: Source/textdat.cpp:364 +msgid "Good day! How may I serve you?" +msgstr "Dobrego dnia! W czym mogę służyć?" -#: Source/msg.cpp:1994 -msgid "The game ended" -msgstr "Gra została zakończona" +#. 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 "" +"Moja babcia miała sen o tym, że przyjdziesz ze mną porozmawiać. Miewa takie " +"różne wizje i potrafi spojrzeć w przyszłość." -#: Source/msg.cpp:2000 -msgid "Unable to get level data" -msgstr "Nie można wczytać danych poziomu" +#. 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 "" +"Kobieta na skraju wioski to wiedźma! Wydaje się być miła, a jej imię, Adria, " +"brzmi dość przyjaźnie. Mimo to i tak się jej bardzo boję. \n" +"\n" +"Trzeba zdobyć się na wiele odwagi, żeby ją odwiedzić." -#: Source/multi.cpp:198 -msgid "Player '{:s}' just left the game" -msgstr "Gracz '{:s}' opuścił grę" +#. 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 "" +"Nasz kowal to duma mieszkańców Tristram. Jest niezwykłym mistrzem rzemiosła, " +"wygrywającym wiele konkursów w swojej gildii. Otrzymał nawet osobistą " +"pochwałę od naszego Króla Leoryka, niech mu ziemia lekką będzie. Griswold " +"jest również wielkim bohaterem; wystarczy zapytać Caina." -#: Source/multi.cpp:201 -msgid "Player '{:s}' killed Diablo and left the game!" -msgstr "Gracz '{:s}' pokonał Diablo i opuścił grę!" +#. 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 "" +"Cain jest tutejszym kronikarzem odkąd tylko pamiętam. Posiada przeogromną " +"wiedzę i możesz z nim porozmawiać praktycznie o wszystkim." -#: Source/multi.cpp:205 -msgid "Player '{:s}' dropped due to timeout" -msgstr "Gracz '{:s}' opuścił grę z powodu limitu czasu" - -#: Source/multi.cpp:789 -msgid "Player '{:s}' (level {:d}) is already in the game" -msgstr "Gracz '{:s}' (poziom {:d}) jest już w grze" - -#. TRANSLATORS: Shrine Name Block -#: Source/objects.cpp:106 -msgid "Mysterious" -msgstr "Tajemnicza" +#. 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 "" +"Farnham jest pijakiem, który swój brzuch wypełnia piwem, a uszy innych " +"bełkotem. \n" +" \n" +"Wiem, że Pepin i Ogden go wspierają. Jest mi jednak bardzo przykro, kiedy " +"widzę, że i tak z każdym dniem stacza się coraz bardziej." -#: Source/objects.cpp:107 -msgid "Hidden" -msgstr "Ukryty" +#. 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 "" +"Pepin uratował życie mojej babci i chyba nigdy nie będę w stanie mu się za " +"to odwdzięczyć. Jego umiejętność leczenia każdej choroby jest potężniejsza " +"od najmocniejszego miecza i bardziej zagadkowa niż jakikolwiek czar, który " +"potrafisz nazwać. Jeśli kiedykolwiek odniesiesz obrażenia, Pepin na pewno " +"będzie w stanie ci pomóc." -#: Source/objects.cpp:108 -msgid "Gloomy" -msgstr "Ponura" +#. 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 "" +"Dorastałam z matką Wirta. Nazywała się Kanasa. Choć potwory wyrywając jej " +"syna zadały jej tylko powierzchowne rany, to tak naprawdę nigdy już nie " +"wydobrzała. Myślę, że umarła przez złamane serce. Wirt stał się podłym " +"dzieckiem, patrzącym tylko na zysk. Wiem, że dużo wycierpiał i widział " +"rzeczy, które trudno jest mi sobie nawet wyobrazić, jednak mam wrażenie, że " +"nadal spowija go jakiś mrok." -#: Source/objects.cpp:110 Source/objects.cpp:117 -msgid "Magical" -msgstr "Magiczna" +#. 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 "" +"Ogden wraz z żoną przygarnęli mnie i moją babcię do swojego domu. Pozwolili " +"nawet zarobić trochę złota, dając mi pracę w gospodzie. Wiele im " +"zawdzięczam. Mam nadzieję, że kiedyś opuszczę to miejsce i pomogę im założyć " +"wspaniały hotel na wschodzie." -#: Source/objects.cpp:111 -msgid "Stone" -msgstr "Kamienna" +#. TRANSLATORS: Neutral dialog spoken by Griswold +#: Source/textdat.cpp:381 +msgid "Well, what can I do for ya?" +msgstr "Co mogę dla ciebie zrobić?" -#: Source/objects.cpp:112 -msgid "Religious" -msgstr "Zakonna" +#. 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 "" +"Szukasz czegoś do walki? Spójrz na broń podstawową, taką jak ta stępiona " +"maczuga. Potrafi zdziałać cuda w walce przeciwko nieumarłym stworom tam na " +"dole. Nie ma nic lepszego do roztrzaskiwania tych spróchniałych szkieletów!" -#: Source/objects.cpp:113 -msgid "Enchanted" -msgstr "Zaczarowana" +#. 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 "" +"Topór? Aaachh, to dobra broń, nadaje się do walki z każdym przeciwnikiem. " +"Spójrz, jak przecina powietrze, a potem wyobraź sobie, że spotyka łeb " +"tłustego demona na swojej drodze. Pamiętaj jednak, że ciężko się nim macha - " +"ale mówimy przecież o potężnych ciosach!" -#: Source/objects.cpp:114 -msgid "Thaumaturgic" -msgstr "Taumaturgiczna" +#. 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 "" +"Spójrz na to wykończenie, na te proporcje. Miecz we właściwych rękach jest " +"najlepszą bronią. Jego błyszczące ostrze z łatwością przebija i rozcina " +"nieumarłych. Bez problemu rani także żywych przeciwników!" -#: Source/objects.cpp:115 -msgid "Fascinating" -msgstr "Czarująca" +#. 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 "" +"Na twojej broni i pancerzu na pewno nie raz zostaną ślady po walce ze złem. " +"Przynieś je wtedy do mnie, a dzięki odrobinie pracy i gorącej kuźni sprawię, " +"że będą jak nowe!" -#: Source/objects.cpp:116 -msgid "Cryptic" -msgstr "Zagadkowa" +#. 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 "" +"Ta wiedźma, Adria, zawsze ma wszystko czego tylko zapragnie. Za to ja muszę " +"przemycać metale i narzędzia z karawan, które zatrzymują się na uboczach " +"tego nieszczęsnego miasta. Gdybym poznał choć trochę jej zaklęć... Mógłbym " +"wykonywać naprawdę nieprawdopodobne rzeczy." -#: Source/objects.cpp:118 -msgid "Eldritch" -msgstr "Koszmarna" +#. 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 "" +"Gillian to miła dziewuszka. Szkoda, że jej babulka jest tak schorowana. " +"Gdyby nie to, zorganizowałbym im wyjazd na jednej z kupieckich karawan." -#: Source/objects.cpp:119 -msgid "Eerie" -msgstr "Niesamowita" +#. 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 "" +"Czasami odnoszę wrażenie, że Cain jest zbyt gadatliwy. Choć takie już pewnie " +"jego powołanie. Gdybym tylko potrafił kuć stal z takim zacięciem, jak on " +"potrafi paplać... Mógłbym wtedy wykonać zbroję płytową, wystarczająco dobrą " +"dla samego Cesarza!" -#: Source/objects.cpp:120 -msgid "Divine" -msgstr "Boska" +#. 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 "" +"Tej nocy, kiedy Lazarus sprowadził nas do labiryntu, trzymałem się blisko " +"Farnhama. To był ostatni raz kiedy widziałem Arcybiskupa. Gdyby Farnham nie " +"stał wtedy u mojego boku, mógłbym nie przeżyć. Obawiam się, że te " +"wydarzenia, okaleczyły jego duszę, tak jak mnie okulawiły. Chciałbym mu " +"pomóc, ale nie jestem w stanie stoczyć tej walki za niego." -#: Source/objects.cpp:122 -msgid "Sacred" -msgstr "Święta" +#. 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 "" +"Ten wspaniały człowiek stawia potrzeby innych ponad swoje. Nie znajdziesz " +"nigdzie osoby, która mogłaby powiedzieć coś złego o naszym uzdrowicielu." -#: Source/objects.cpp:123 -msgid "Spiritual" -msgstr "Duchowa" +#. 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 "" +"Ten mały wpędzi się kiedyś w jakieś poważne tarapaty... znowu. Próbowałem " +"zaciekawić go swoim zawodem - no wiesz, nauczyć go uczciwej pracy. On jednak " +"woli czerpać zyski z handlu towarami nieznanego pochodzenia. Nie mogę mieć " +"mu tego za złe, po tym co przeszedł, ale chciałbym, żeby bardziej na siebie " +"uważał." -#: Source/objects.cpp:124 -msgid "Spooky" -msgstr "Upiorna" +#. 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 "" +"Karczmarz prowadzi tu swój mały interes, ale ciężko mu cokolwiek zarobić. " +"Ledwo wiąże koniec z końcem. Dostarcza jedzenie i oferuje noclegi " +"przyjezdnym. A ci niestety często wymykają się potajemnie i nie płacą mu " +"należności. Gdyby nie zapasy zboża i suszonego mięsa w jego piwnicy, " +"większość z nas już dawno pomarłaby z głodu od czasu najazdu demonów." -#: Source/objects.cpp:125 -msgid "Abandoned" -msgstr "Porzucona" +#. TRANSLATORS: Neutral dialog spoken by Farnham +#: Source/textdat.cpp:404 +msgid "Can't a fella drink in peace?" +msgstr "Napić się nie mogę w spokoju?" -#: Source/objects.cpp:126 -msgid "Creepy" -msgstr "Straszna" +#. 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 "Dziewuszka przy barze? Och, to dopiero ślicznotka. Do tego taka miła." -#: Source/objects.cpp:127 -msgid "Quiet" -msgstr "Cicha" +#. 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 "" +"Dlaczego ta starucha nie zrobi dla odmiany czegoś ciekawszego. Wiem, wiem, " +"nie ma na to czasu, ale posłuchaj... Ona jest jakaś nienormalna. Nigdy nie " +"widziałem, żeby coś jadła albo piła - a nie możesz ufać komuś, kto nie pije. " +"Nawet troszeczkę." -#: Source/objects.cpp:128 -msgid "Secluded" -msgstr "Odosobniona" +#. 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 "" +"Cain nie jest tym za kogo się podaje. Wiem, wiem, opowiada ciekawe " +"historyjki... parę z nich jest naprawdę strasznych. Albo śmiesznych... Wiesz " +"co? On wie więcej niż wie, że wie." -#: Source/objects.cpp:129 -msgid "Ornate" -msgstr "Ozdobna" +#. 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 "" +"Griswold? Stary, dobry Griswold. Kocham go jak brata! Walczyliśmy razem, " +"wiesz? Dawniej, kiedy... my... Lazarus... Lazarus... Lazarus!!!" -#: Source/objects.cpp:130 -msgid "Glimmering" -msgstr "Migocząca" +#. 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 "" +"Hehehe, lubię Pepina. Wiesz, on naprawdę robi co może. Słuchaj, musisz go " +"poznać. To miły gość, który zawsze pomaga ludziom w potrzebie. Hej, to chyba " +"tak jak ty, nie? Dawniej też taki byłem..." -#: Source/objects.cpp:131 -msgid "Tainted" -msgstr "Splugawiona" +#. 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 "" +"Wirt jest dzieckiem, które ma więcej problemów ode mnie, a ja wiem wszystko " +"o problemach. Posłuchaj - ten dzieciak robi świetne interesy, ale on też tam " +"był, wiesz? Stracił nogę! Musi teraz chodzić na jakimś kołku. To przykre, " +"naprawdę przykre..." -#: Source/objects.cpp:132 -msgid "Oily" -msgstr "Wzmacniająca" +#. 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 "" +"Ogden to najlepszy człowiek w mieście. Jego żona chyba za mną nie przepada, " +"ale dopóki uzupełnia beczki, to da się ją jakoś znieść. Wygląda na to, że to " +"ja spędzam najwięcej czasu z Ogdenem, ale on jest dla mnie taki dobry..." -#: Source/objects.cpp:133 -msgid "Glowing" -msgstr "Świecąca" +#. 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 "" +"Heeejj chcę zzi coś powiedzieć, wiem o nich wszystko. Są moją specjalnością. " +"Te tutaj są najlepsze... naaaajleeeepsze! Inne piwa są nic warte odkąd te " +"głupie kundle..." -#: Source/objects.cpp:134 -msgid "Mendicant's" -msgstr "Matczyna" +#. 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 "" +"Nikt mnie nie chce słu... słuchać. Gdzieś - nie jestem pewien - ale gdzieś " +"pod kościołem leżą całe stosy złota. Błyszczą i brzęczą. I tylko czekają, " +"coby ktoś je zebrał." -#: Source/objects.cpp:135 -msgid "Sparkling" -msgstr "Błyszcząca" +#. 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 "" +"Wiem, że masz własne zdanie i wiem, że i tak mi nie uwierzysz, ale ta broń, " +"którą walczysz - ona jest za słaba na tych wielkich bydlaków! Nie obchodzi " +"mnie co mówi Griswold, ale już się nie robi takich rzeczy, co za dawnych " +"czasów..." -#: Source/objects.cpp:137 -msgid "Shimmering" -msgstr "Lśniąca" +#. 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 "" +"Gdybym był tobą... no ale nie jestem... no ale gdybym był, to wtedy bym " +"wszystko sprzedał i stąd wyjechał... Ten chłopak, o tam... On zawsze ma " +"dobry towar, ale jak nie masz złota... to ci nawet tego nie pokaże." -#: Source/objects.cpp:138 -msgid "Solar" -msgstr "Promienista" +#. TRANSLATORS: Neutral dialog spoken by Adria +#: Source/textdat.cpp:427 +msgid "I sense a soul in search of answers..." +msgstr "Wyczuwam duszę szukającą odpowiedzi..." -#. TRANSLATORS: Shrine Name Block end -#: Source/objects.cpp:140 -msgid "Murphy's" -msgstr "Czarnowidząca" +#. 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 "" +"Mądrość jest nabyta, nie nadana. Dlatego pochłaniaj wiedzę z każdej " +"odnalezionej księgi. Jeśli będzie to wiedza, którą już posiadasz to " +"pamiętaj, że można ją zawsze poszerzyć." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:270 -msgid "The Great Conflict" -msgstr "Wielki Konflikt" +#. 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 "" +"Najpotężniejsza moc często trwa najkrócej. Zapewne natkniesz się na " +"starożytne formuły, o wielkiej sile, zapisane na zwojach pergaminów. Moc " +"tych zwojów pozwala na wykorzystanie ich zarówno przez ucznia, jak i " +"mistrza, z identycznym skutkiem. Wiedz, że trzeba je głośno wyrecytować, a " +"zapisane na nich słowa nie mogą być zachowane w umyśle. Pamiętaj... użyć " +"można ich tylko raz, dlatego wykorzystuj je z rozwagą." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:271 -msgid "The Wages of Sin are War" -msgstr "Wojna Karą za Grzechy" +#. 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 "" +"Choć żar słońca jest niezmierzony, niewinny płomień świecy stwarza większe " +"zagrożenie. żadnej energii, nie ważne jak wielkiej, nie wykorzystasz bez " +"odpowiedniego jej skupienia. Magiczna moc zaczarowanych kosturów może być " +"wielokrotnie uzupełniana, co umożliwi dalsze rzucanie zaklęć. Posiadam " +"umiejętność przywracania im energii - lecz wiedz, że wszystko ma swoją cenę." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:272 -msgid "The Tale of the Horadrim" -msgstr "Historia Horadrimów" +#. 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 "" +"Granicę mądrości wyznacza społeczeństwo. Jeśli nie uda ci się rozszyfrować " +"znalezionych ksiąg lub pergaminów, nie wahaj się przynieść ich do mnie. " +"Spróbuję je odczytać i podzielę się odkryciami." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:273 -msgid "The Dark Exile" -msgstr "Mroczne Wygnanie" +#. 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 "" +"Dla człowieka znającego jedynie żelazo, nie ma większej magii niż stal. " +"Kowal Griswold jest większym czarodziejem, niż mu się wydaje. Jego " +"umiejętność spajania ognia z metalem jest zaprawdę niezrównana." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:274 -msgid "The Sin War" -msgstr "Wojna Grzechu" +#. 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 "" +"Tak jak zepsucie korzysta z zalet obłudy, tak cnotliwość korzysta z mocy " +"lojalności. Młoda Gillian ma dobre serce. Potrzeby jej babci są dla niej " +"ważniejsze od własnych. Boi się mnie, ale tylko dlatego, że mnie nie rozumie." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:275 -msgid "The Binding of the Three" -msgstr "Schwytanie Trójcy" +#. 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 "" +"Skrzynia otwarta w ciemnościach nie skrywa większych bogactw niż otwarta w " +"blasku światła. Kronikarz Cain jest tajemnicą, ale tylko dla zaślepionych. " +"Jego wiedza o tym, co spoczywa pod katedrą jest dużo większa, niż mu się " +"wydaje." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:276 -msgid "The Realms Beyond" -msgstr "Nieznane Krainy" +#. 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 "" +"Im głębszą wiarę pokładasz w jednym człowieku, tym niżej możesz upaść. " +"Farnham zatracił swą duszę, ale nie przez demony. Stało się to po zdradzie " +"Arcybiskupa Lazarusa, gdy zobaczył cierpienie swoich najbliższych " +"przyjaciół. Pamiętaj, że Farnham posiada ogromną wiedzę, musisz tylko " +"oddzielić ziarno od plew." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:277 -msgid "Tale of the Three" -msgstr "Historia Trójcy" +#. 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 "" +"Dłoń, serce i rozum, tworząc idealną harmonię, potrafią dokonywać cudów. " +"Uzdrowiciel Pepin dostrzega w ciele to, czego nawet ja nie potrafię. Jego " +"zdolność leczenia chorych i rannych wzbogacona jest o umiejętność tworzenia " +"mikstur i eliksirów. Będzie dla ciebie wspaniałym sojusznikiem." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:278 -msgid "The Black King" -msgstr "Czarny Król" +#. 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 "" +"Nie potrafimy wejrzeć w przyszłość, lecz kiedy nadejdzie, dzierżyć ją będą " +"dzieci. W duszy Wirta można dostrzec mrok, jednak nie stanowi on zagrożenia " +"dla miasta ani mieszkańców. Jego kontakty z łotrami i sekretnymi gildiami z " +"pobliskich wiosek zapewniają mu dostęp do wielu przedmiotów, które są rzadko " +"spotykane w Tristram. Możesz potępiać jego metody, ale usługi, które " +"świadczy, mogą okazać się pomocne w walce z nadchodzącą ciemnością." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:279 -msgid "Journal: The Ensorcellment" -msgstr "Dziennik: Wpływ Magii" +#. 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 "" +"Domu nie tworzą gliniane ściany i dach pokryty strzechą. Karczmarz Ogden " +"spełnia więcej potrzeb tutejszych mieszkańców, niż są tego świadomi. " +"Zapewnia schronienie Gillian i jej babci, podtrzymuje na duchu Farnhama i " +"przypomina wszystkim o czasach świetności Tristram. Jego gospoda i dostępne " +"tam proste przyjemności są dla wielu ludzi przebłyskiem dawnych i spokojnych " +"czasów. Te wspomnienia rozbudzają w nich nadzieję na twój sukces." -#. TRANSLATORS: Book Title -#: Source/objects.cpp:280 -msgid "Journal: The Meeting" -msgstr "Dziennik: Konfrontacja" - -#. TRANSLATORS: Book Title -#: Source/objects.cpp:281 -msgid "Journal: The Tirade" -msgstr "Dziennik: Tyrada" - -#. TRANSLATORS: Book Title -#: Source/objects.cpp:282 -msgid "Journal: His Power Grows" -msgstr "Dziennik: Jego Siła Wzrasta" - -#. TRANSLATORS: Book Title -#: Source/objects.cpp:283 -msgid "Journal: NA-KRUL" -msgstr "Dziennik: NA-KRUL" - -#. TRANSLATORS: Book Title -#: Source/objects.cpp:284 -msgid "Journal: The End" -msgstr "Dziennik: Zakończenie" - -#. TRANSLATORS: Book Title -#: Source/objects.cpp:285 -msgid "A Spellbook" -msgstr "Księga Zaklęć" - -#: Source/objects.cpp:5383 -msgid "Crucified Skeleton" -msgstr "Ukrzyżowany Szkielet" - -#: Source/objects.cpp:5387 -msgid "Lever" -msgstr "Dźwignia" +#. TRANSLATORS: Neutral dialog spoken by Wirt +#: Source/textdat.cpp:450 +msgid "Pssst... over here..." +msgstr "Hej... Tu jestem..." -#: Source/objects.cpp:5396 -msgid "Open Door" -msgstr "Otwarte Drzwi" +#. 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 "" +"Nie każdy w Tristram potrzebuje rzeczy z Labiryntu. Ja też ich nie chcę, " +"choć pewnie trudno w to uwierzyć. \n" +" \n" +"Często tylko ty będziesz w stanie poznać ich prawdziwą naturę." -#: Source/objects.cpp:5398 -msgid "Closed Door" -msgstr "Zamknięte Drzwi" +#. 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 "" +"Nie wierz we wszystko, co mówi ten pijak. Nadmiar alkoholu zdążył już " +"przyćmić jego wzrok i zdrowy rozsądek." -#: Source/objects.cpp:5400 -msgid "Blocked Door" -msgstr "Zablokowane Drzwi" +#. 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 "" +"Moje towary nie pochodzą z Tristram. Jestem importerem jakościowych dóbr. " +"Jeżeli chcesz handlować szmelcem, udaj się do Griswolda, Pepina albo tej " +"wiedźmy, Adrii. Założę się, że wezmą wszystko, co im przyniesiesz..." -#: Source/objects.cpp:5405 -msgid "Ancient Tome" -msgstr "Starożytna Księga" +#. 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 "" +"Przypuszczam, że zawdzięczam swoje życie kowalowi, o ile można to jeszcze " +"nazywać... życiem. Jasne, Griswold zaproponował mi naukę rzemiosła w kuźni. " +"Mógłbym się od niego wiele nauczyć, ale w ten sposób nie zdobędę " +"wystarczającej ilości pieniędzy do... dobrze, powiedzmy, że moje plany " +"wymagają większych nakładów finansowych." -#: Source/objects.cpp:5407 -msgid "Book of Vileness" -msgstr "Księga Obłudy" +#. 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 "" +"Gdybym był trochę starszy, obsypałbym ją wszystkimi skarbami świata. Mogę " +"cię zapewnić, że w moje ręce wpadają naprawdę piękne rzeczy. Gillian jest " +"prześliczną dziewczyną, która powinna opuścić Tristram póki jeszcze jest " +"bezpiecznie. Hmmm... może zabiorę ją ze sobą, kiedy będę odjeżdżał..." -#: Source/objects.cpp:5412 -msgid "Skull Lever" -msgstr "Kościana dźwignia" +#. 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 "" +"Cain wie zbyt dużo. Przeraża mnie nawet bardziej niż ta kobieta za rzeką. " +"Wciąż mówi jakie to mam szczęście, że przeżyłem oraz że w legendzie " +"przepowiedziano moją historię. Chyba postradał zmysły." -#: Source/objects.cpp:5415 -msgid "Mythical Book" -msgstr "Mityczna Księga" +#. 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 "" +"Farnham - no, to jest facet z poważnymi problemami, i uwierz mi, wiem co " +"oznaczają poważne problemy. Za bardzo komuś zaufał. Lazarus sprowadził go do " +"samej krainy śmierci. Och, wiem co tam jest, więc nie próbuj nawet mówić mi " +"o swoich planach zniszczenia zła, które miota się po labiryncie. Lepiej " +"uważaj na nogi..." -#: Source/objects.cpp:5419 -msgid "Small Chest" -msgstr "Mała Skrzynia" +#. 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 "" +"Stary, dobry Pepin... może okazać się pomocny... dopóki nie będzie trzeba ci " +"z powrotem czegoś przyszywać. \n" +" \n" +"Gdybym tylko miał wtedy trochę jego eliksirów, to może nie straciłbym nogi..." -#: Source/objects.cpp:5423 -msgid "Chest" -msgstr "Skrzynia" +#. 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 "" +"Adria naprawdę mnie niepokoi. No jasne, Cain też potrafi przerazić tym co " +"mówi o przeszłości, ale ta wiedźma - potrafi w nią wejrzeć. Zawsze znajduje " +"sposób na zdobycie wszystkiego, czego tylko zapragnie. Adria dostaje w ręce " +"takie rzeczy, których nie widziałem nawet w czasie trwania Największych " +"Targów!" -#: Source/objects.cpp:5428 -msgid "Large Chest" -msgstr "Duża Skrzynia" +#. 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 "" +"Ogden zachowuje się jak wariat. Mógłbym wywieść go z miasta za rozsądną " +"cenę, lecz on uparcie próbuje uratować swoją głupią gospodę. Przynajmniej " +"daje Gillian miejsce pracy, a jego żona Garda piecze wyśmienite ciasta..." -#: Source/objects.cpp:5431 -msgid "Sarcophagus" -msgstr "Sarkofag" +#. TRANSLATORS: Quest text spoken aloud from a book by player +#: Source/textdat.cpp:471 Source/textdat.cpp:479 Source/textdat.cpp:487 +#: Source/textdat.cpp:517 Source/textdat.cpp:525 +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 "" +"Za Salą Bohaterów znajduje się Komnata Kości. Wieczne potępienie czeka na " +"każdego, kto odważy się wykraść złożone w niej skarby. Tak oznajmił Pan " +"Grozy, tak też zostało zapisane." -#: Source/objects.cpp:5434 -msgid "Bookshelf" -msgstr "Regał" +#. 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:519 Source/textdat.cpp:527 +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 "" +"... I tak, zamknięta za Bramą Krwi i Komnatą Ognia, Odwaga oczekuje na " +"przebudzenie przez Bohatera światłości..." -#: Source/objects.cpp:5438 -msgid "Bookcase" -msgstr "Biblioteczka" +#. 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:521 Source/textdat.cpp:529 +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 "" +"Nie pojmujesz mych wizji,\n" +"Nie rozróżniasz w nich fikcji.\n" +"Odwróć wzrok, już zniknęły,\n" +"Blask światła zniknął naraz.\n" +"Lecz swą pieśnią cię tknęły,\n" +"Widzisz już baśni obraz.\n" +"Kłębi się w sennych marach,\n" +"W Sal ślepców korytarzach." -#: Source/objects.cpp:5443 -msgid "Pod" -msgstr "Kokon" +#. TRANSLATORS: Quest text spoken aloud from a book by player +#: Source/textdat.cpp:477 Source/textdat.cpp:485 Source/textdat.cpp:493 +#: Source/textdat.cpp:523 Source/textdat.cpp:531 +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 "" +"Zbrojownie Piekła stały się domem Marszałka Krwi. Gdziekolwiek przejdzie, " +"pozostawia za sobą tysiące okaleczonych ciał. Mordował zarówno aniołów, jak " +"i ludzi. Składa niekończące się ofiary Panom Ciemności, pragnącym tylko " +"jednego. Krwi..." -#: Source/objects.cpp:5445 -msgid "Urn" -msgstr "Urna" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:497 +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 "" +"Daj świadectwo prawdom tu zapisanym, gdyż są one ostatnim dziedzictwem " +"horadrimów. Poza naszym ojczystym światem trwa wojna pomiędzy utopijnym " +"Królestwem Niebios i pogrążonymi w chaosie Płonącymi Piekłami. Wojnę tę " +"nazywa się Wielkim Konfliktem. Zaczęła się jeszcze przed powstaniem gwiazd " +"na niebie. Siły światła i ciemności nieustannie walczą w niej o przejęcie " +"kontroli nad wszelkim stworzeniem, lecz żadna ze stron nigdy nie uzyskała " +"znaczącej przewagi." -#: Source/objects.cpp:5447 -msgid "Barrel" -msgstr "Beczka" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:499 +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 "" +"Daj świadectwo prawdom tu zapisanym, gdyż są one ostatnim dziedzictwem " +"horadrimów. Kiedy Odwieczny Konflikt pomiędzy Królestwem Niebios i Płonącymi " +"Piekłami przeniósł się do świata śmiertelników, nazwano go Wojną Grzechu. " +"Anioły i demony zaczęły krążyć pomiędzy ludźmi w ukryciu, walcząc pod inną " +"postacią. Kilku potężniejszym śmiałkom udało się sprzymierzyć ze stronami " +"konfliktu i wpłynąć na przebieg Wojny Grzechu." -#. TRANSLATORS: {:s} will be a name from the Shrine block above -#: Source/objects.cpp:5451 -msgid "{:s} Shrine" -msgstr "{:s} Kapliczka" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:501 +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 "" +"Daj świadectwo prawdom tu zapisanym, gdyż są one ostatnim dziedzictwem " +"horadrimów. Około trzystu lat temu, dowiedzieliśmy się, że nasz świat " +"nawiedziła Mroczna Trójca z Płonących Piekieł.Trzej bracia zapanowali na " +"dekady nad ziemiami Wschodu, a miejscowa ludność żyła tam w panicznym " +"strachu. Nasz zakon - horadrimowie - został stworzony przez tajemną grupę " +"magów, by odnaleźć i raz na zawsze pojmać Trójcę Zła.\n" +" \n" +"Dawni Horadrimowie schwytali dwóch \n" +"z Trójcy za pomocą potężnych artefaktów znanych jako Kamienie Dusz\n" +"i zakopali je głęboko pod piaskami wschodnich pustkowi.\n" +"Trzeci Zły zdołał uciec na zachód. Wtedy podążył za nim nasz zakon. \n" +"Trzeci Zły - znany jako Diablo, Pan Grozy - został ostatecznie schwytany, a " +"jego esencja została umieszczona w Kamieniu Dusz i ukryta w tym Labiryncie.\n" +"\n" +"Strzeż się, Kamień Dusz musi być trzymany z dala od niepowołanych. Gdyby " +"Diablo zdołał się z niego wydostać, próbowałby przejąć ciało, którym mógłby " +"z łatwością zawładnąć. Najlepszym celem dla osłabionego demona byłoby " +"dziecko lub starzec." -#: Source/objects.cpp:5454 -msgid "Skeleton Tome" -msgstr "Kościana Księga" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:503 +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 "" +"Dokonało się. W Płonących Piekłach nastał czas wielkiej rewolucji, nazywanej " +"Mrocznym Wygnaniem. Pomniejsze Zła obaliły Wielką Trójcę i wygnały ich dusze " +"do krainy śmiertelników. Podczas nieobecności trzech braci, demony Belial, " +"Pan Kłamstwa, i Azmodan, Pan Grzechu, zaczęły walczyć o władzę. Cała kraina " +"podzieliła się na wyznawców Beliala i Azmodana, a siły Niebios nieustannie " +"próbowały rozbić Wrota Piekieł." -#: Source/objects.cpp:5457 -msgid "Library Book" -msgstr "Księga Biblioteczna" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:505 +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 "" +"Wiele demonów przybyło do krainy śmiertelników w poszukiwaniu Trzech Braci. " +"Ich poczynania na ziemi śledziły anioły, które rozpoczęły polowania na nie w " +"wielkich miastach Wschodu. Anioły sprzymierzyły się ze śmiertelnikami i " +"zawarły pakt z tajemniczym zakonem horadrimów. Jego członkowie szybko stali " +"się znakomitymi łowcami zła i błyskawicznie zdobyli w krainie podziemi wielu " +"wrogów." -#: Source/objects.cpp:5460 -msgid "Blood Fountain" -msgstr "Fontanna Krwi" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:507 +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 "" +"Dokonało się. Duchy Wielkiej Trójcy zostały wygnane do świata śmiertelników. " +"Po trwającym dekady sianiu spustoszenia na Wschodzie, zostali wytropieni " +"przez zakon śmiertelników. Horadrimowie użyli artefaktów nazywanych " +"Kamieniami Dusz i umieścili w nich esencje Mefisto, Pana Nienawiści i jego " +"brata Baala, Pana Zniszczenia. Najmłodszy brat, Diablo - Pan Grozy, uciekł " +"na zachód.\n" +" \n" +"Ostatecznie Horadrimowie schwytali do Kamienia Dusz również Diablo, a " +"następnie ukryli go pod starożytną, zapomnianą katedrą. Tam oto Pan Grozy " +"pozostaje uśpiony i oczekuje na przebudzenie. Wiedzcie o tym, że potrzebuje " +"młodego i energicznego ciała, naiwnej osoby, którą łatwo będzie opętać. " +"Odrodzi się wtedy, by uwolnić swych Braci i ponownie wzniecić ogień Wojny " +"Grzechu..." -#: Source/objects.cpp:5463 -msgid "Decapitated Body" -msgstr "Okaleczone Ciało" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:509 +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 "" +"Chwała Diablo - Panu Grozy, który przetrwał Mroczne Wygnanie. Mój Pan i " +"Mistrz przebudził się wreszcie z głębokiego snu. Wyjawił mi sekrety znane " +"niewielu śmiertelnikom. Opowiedział mi o Królestwie Niebios i Płonących " +"Piekłach, ścierających się w odwiecznej wojnie. Pokazał moce, które " +"sprowadziły ten konflikt do krainy ludzi, a samą walkę za ten świat i każde " +"jego istnienie nazwał Wojną Grzechu." -#: Source/objects.cpp:5466 -msgid "Book of the Blind" -msgstr "Księga Ślepców" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:511 +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 "" +"Chwała i uwielbienie dla Diablo - Pana Grozy, który przewodzi całej Trójcy. " +"Mój Pan opowiadał mi o swych dwóch braciach, Mefisto i Baalu, z którymi " +"dawno temu został wygnany do naszego świata. Czeka teraz na odpowiedni " +"moment, by wykorzystać swą niesamowitą moc do uwolnienia pojmanych braci, " +"znajdujących się w grobowcach pod piaskami Wschodu. Kiedy mój Pan oswobodzi " +"swych braci, Wojna Grzechu jeszcze raz zazna gniewu Trójcy." -#: Source/objects.cpp:5469 -msgid "Book of Blood" -msgstr "Księga Krwi" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:513 +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 "" +"Oddajcie cześć i pokłon Diablo - Panu Grozy i niszczycielowi dusz. Kiedy " +"przebudziłem mego mistrza, spróbował opętać śmiertelnika. Diablo chciał " +"posiąść ciało Króla Leoryka, jednak był jeszcze zbyt słaby. Potrzebował " +"ofiary słabszej i naiwniejszej, by powrócić w pełni do tego świata. Uznał " +"chłopca, Albrechta, za idealny cel. Podczas gdy Król Leoryk tracił zmysły po " +"nieudanej próbie opętania, ja porwałem jego syna Albrechta i zaprowadziłem " +"go przed mojego Mistrza. Teraz oczekuję rozkazu od Diablo i modlę się o to, " +"by zostać nagrodzonym kiedy zostanie Panem tego świata." -#: Source/objects.cpp:5472 -msgid "Purifying Spring" -msgstr "Źródło Oczyszczenia" +#. TRANSLATORS: Neutral Text spoken by Ogden +#: Source/textdat.cpp:515 +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 "" +"Dobrze znów cię widzieć!\n" +"Dużo się zmieniło od czasu twojej ostatniej wizyty. Żyliśmy tu spokojnie, do " +"momentu, w którym przybyły te diabelskie stworzenia i zniszczyły naszą " +"wioskę. Wielu zginęło na miejscu. Tych co chwycili za broń zabito albo " +"porwano na niewolników - jak nie gorzej. Kościół na skraju miasta " +"zbezczeszczono i służy teraz mrocznym rytuałom. Nocami po osadzie roznoszą " +"się nieludzkie krzyki. Mimo to wciąż mamy nadzieję, że część naszych ludzi " +"przeżyła. Udaj się ścieżką wiodącą między gospodą a kuźnią, tam znajdziesz " +"katedrę. Uratuj kogo tylko zdołasz. \n" +" \n" +"Więcej opowiem ci przy następnym spotkaniu. Powodzenia." -#: Source/objects.cpp:5479 Source/objects.cpp:5503 -msgid "Weapon Rack" -msgstr "Stojak na Broń" +#. TRANSLATORS: Quest text spoken by Adria +#: Source/textdat.cpp:533 +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 "" +"Pamiętaj o tym zadaniu. Znalezienie utraconego skarbu nie jest proste. Tym " +"bardziej znalezienie skarbu, który został ukryty. Nie pozwól, aby " +"upływający czas wpłynął na twoje poszukiwania." -#: Source/objects.cpp:5482 -msgid "Goat Shrine" -msgstr "Koźla Kapliczka" +#. TRANSLATORS: Quest text spoken by Griswold +#: Source/textdat.cpp:535 +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 "" +"Co? To bzdura. Pod Tristram nie ma ukrytych skarbów. Pokaż mi to! Ach, " +"spójrz, te rysunki nie pasują do naszego miasta. Na twoim miejscu skupiłbym " +"się nad tym co jest pod katedrą, a nie na tej mapie." -#: Source/objects.cpp:5485 -msgid "Cauldron" -msgstr "Kocioł" +#. TRANSLATORS: Quest text spoken by Pepin +#: Source/textdat.cpp:537 +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 "" +"Nie mam czasu na przeglądanie map. Wielu ludzi potrzebuje teraz mojej " +"pomocy, twojej zresztą też." -#: Source/objects.cpp:5488 -msgid "Murky Pool" -msgstr "Mętna Sadzawka" +#. TRANSLATORS: Quest text spoken by Adria +#: Source/textdat.cpp:539 Source/textdat.cpp:551 +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 "Usunięty quest." -#: Source/objects.cpp:5491 -msgid "Fountain of Tears" -msgstr "Fontanna Łez" +#. TRANSLATORS: Quest text spoken by Ogden +#: Source/textdat.cpp:541 +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 "" +"Założę się, że to Wirt robi sobie z ciebie żarty. Pewnie pęknie ze śmiechu, " +"gdy się dowie, jak pytasz wszystkich o te bazgroły, które ci podrzucił. Ja " +"bym sobie dał z tym spokój." -#: Source/objects.cpp:5494 -msgid "Steel Tome" -msgstr "Żelazna Księga" +#. TRANSLATORS: Quest text spoken by Cain +#: Source/textdat.cpp:543 +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 "" +"Kiedyś często zatrzymywali się tu podróżni z dalekich stron, jednak wiele " +"się od tego czasu zmieniło. Ale dzieci często fantazjują o ukrytych " +"jaskiniach i zakopanych skarbach. Choć Wirt rzadko daje się ponieść " +"młodzieńczym zabawom, to nadal możliwe, że to tylko jego wymysł." -#: Source/objects.cpp:5497 -msgid "Pedestal of Blood" -msgstr "Piedestał Krwi" +#. TRANSLATORS: Quest text spoken by Farnham +#: Source/textdat.cpp:545 +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 "" +"Cho... Chono tu. Słuchaj. Nie wiem czy ty to widzisz, ale tu jest coś " +"narysowane. To mapa." -#: Source/objects.cpp:5506 -msgid "Mushroom Patch" -msgstr "Skupisko grzybów" +#. TRANSLATORS: Quest text spoken by Gillian +#: Source/textdat.cpp:547 +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 "" +"Moja babcia często opowiadała mi historie o dziwnych mocach zamieszkujących " +"cmentarz przed kościołem. Opowiem ci jedną z nich. Mówiła, że jeżeli na " +"cmentarzu złoży się właściwą ofiarę, pójdzie do katedry, pomodli się za " +"zmarłego i wróci, złożona ofiara zostanie w tajemniczy sposób przyjęta. Nie " +"wiem czy to tylko gadanie starej schorowanej kobiety, ale teraz wszystko " +"jest możliwe." -#: Source/objects.cpp:5509 -msgid "Vile Stand" -msgstr "Stojak Nikczemnego" +#. TRANSLATORS: Quest text spoken by Wirt +#: Source/textdat.cpp:549 +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 "" +"Ogromny i tajemniczy skarb, powiadasz? Może nawet coś bym od ciebie " +"kupił... a nie potrzebujesz może jakiegoś unikalnego i drogiego wyposażenia " +"żeby go zdobyć?" -#: Source/objects.cpp:5512 -msgid "Slain Hero" -msgstr "Zabity Bohater" - -#. TRANSLATORS: {:s} will either be a chest or a door -#: Source/objects.cpp:5519 -msgid "Trapped {:s}" -msgstr "{:s} (pułapka)" - -#. 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} (wył.)" - -#: Source/options.cpp:426 Source/options.cpp:557 Source/options.cpp:563 -msgid "ON" -msgstr "WŁ." - -#: Source/options.cpp:426 Source/options.cpp:555 Source/options.cpp:561 -msgid "OFF" -msgstr "WYŁ." - -#: Source/options.cpp:545 -msgid "Start Up" -msgstr "Uruchomienie" - -#: Source/options.cpp:545 -msgid "Start Up Settings" -msgstr "Ustawienia uruchamiania" - -#: Source/options.cpp:546 -msgid "Game Mode" -msgstr "Tryb gry" - -#: Source/options.cpp:546 -msgid "Play Diablo or Hellfire." -msgstr "Zagraj w Diablo lub Hellfire." - -#: Source/options.cpp:552 -msgid "Restrict to Shareware" -msgstr "Ogranicz do wersji Demo" - -#: Source/options.cpp:552 +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:553 msgid "" -"Makes the game compatible with the demo. Enables multiplayer with friends " -"who don't own a full copy of Diablo." +"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 "" -"Sprawia, że gra jest kompatybilna z wersją demonstracyjną. Umożliwia grę " -"wieloosobową z przyjaciółmi, którzy nie posiadają pełnej kopii Diablo." - -#: Source/options.cpp:553 Source/options.cpp:559 -msgid "Intro" -msgstr "Intro" - -#: Source/options.cpp:553 Source/options.cpp:559 -msgid "Shown Intro cinematic." -msgstr "Pokaż intro." - -#: Source/options.cpp:565 -msgid "Splash" -msgstr "Powitanie" - -#: Source/options.cpp:565 -msgid "Shown splash screen." -msgstr "Pokaż ekran powitalny." - -#: Source/options.cpp:567 -msgid "Logo and Title Screen" -msgstr "Logo i ekran tytułowy" - -#: Source/options.cpp:568 -msgid "Title Screen" -msgstr "Ekran Tytułowy" - -#: Source/options.cpp:587 -msgid "Diablo specific Settings" -msgstr "Ustawienia specyficzne dla Diablo" - -#: Source/options.cpp:601 -msgid "Hellfire specific Settings" -msgstr "Opcje Hellfire" - -#: Source/options.cpp:615 -msgid "Audio" -msgstr "Audio" - -#: Source/options.cpp:615 -msgid "Audio Settings" -msgstr "Ustawienia Audio" - -#: Source/options.cpp:618 -msgid "Walking Sound" -msgstr "Dźwięk chodzenia" - -#: Source/options.cpp:618 -msgid "Player emits sound when walking." -msgstr "Gracz wydaje dźwięki podczas chodzenia." - -#: Source/options.cpp:619 -msgid "Auto Equip Sound" -msgstr "Auto Wyposażenie Dźwięk" +"Hej, to chyba o tobie wszyscy wkoło gadają. Pomożesz biednemu, prostemu " +"farmerowi? Na skraju mojego gospodarstwa, na południe stąd, z ziemi " +"wypełzło jakieś paskudztwo! Blokuje mi drogę do upraw i stogów siana, przez " +"co moje krowy zaczynają głodować. Wiedźma dała mi to coś, żeby wysadzić " +"tamto szkaradztwo w powietrze. Jeśli mnie wyręczysz, będę ci dozgonnie " +"wdzięczny. Sam nie dam rady, bo wiesz, ktoś musi pilnować krów..." -#: Source/options.cpp:619 -msgid "Automatically equipping items on pickup emits the equipment sound." +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:555 +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 "" -"Automatyczne zakładanie przedmiotów przy podniesieniu powoduje emisję " -"dźwięku." - -#: Source/options.cpp:620 -msgid "Item Pickup Sound" -msgstr "Dźwięk podnoszenia przedmiotu" +"Wiedziałem, że to nie może być takie proste, jak mówiła wiedźma. To smutne, " +"że w dzisiejszych czasach nie można ufać już nawet własnym sąsiadom." -#: Source/options.cpp:620 -msgid "Picking up items emits the items pickup sound." +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:557 +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 "" -"Podnoszenie przedmiotów powoduje emisję dźwięku podnoszenia przedmiotów." - -#: Source/options.cpp:621 -msgid "Sample Rate" -msgstr "Częstotliwość próbkowania" - -#: Source/options.cpp:621 -msgid "Output sample rate (Hz)." -msgstr "Częstotliwość próbkowania wyjścia (Hz)." - -#: Source/options.cpp:622 -msgid "Channels" -msgstr "Kanały" - -#: Source/options.cpp:622 -msgid "Number of output channels." -msgstr "Liczba kanałów wyjścia." - -#: Source/options.cpp:623 -msgid "Buffer Size" -msgstr "Rozmiar Bufora" - -#: Source/options.cpp:623 -msgid "Buffer size (number of frames per channel)." -msgstr "Rozmiar bufora (liczba ramek na kanał)." - -#: Source/options.cpp:631 -msgid "Resampling Quality" -msgstr "Jakość powtórnego próbkowania" - -#: Source/options.cpp:631 -msgid "Quality of the resampler, from 0 (lowest) to 10 (highest)." -msgstr "Jakość resamplera, od 0 (najniższa) do 10 (najwyższa)." - -#: Source/options.cpp:654 -msgid "Resolution" -msgstr "Rozdzielczość" +"Już po wszystkim? Czy to coś wróciło już do ciemnych otchłani Hadesu, z " +"których przyszło? Co?! No nie mów mi, że gdzieś ci się zgubił ładunek! To " +"nie była tania rzecz. Musisz go teraz znaleźć. Inaczej nie pozbędziesz się " +"tego paskudztwa z naszego miasta." -#: Source/options.cpp:654 +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:559 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." +"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 "" -"Wpływa na wew. rozdzielczość gry i określa obszar widzenia użytkownika. " -"Uwaga: Rozdzielczość ta może się różnić od roz. ekranu, dla funkcji - " -"Zwiększanie, Skalowanie Całkowite, Dopasuj do Ekranu." - -#: Source/options.cpp:750 -msgid "Graphics" -msgstr "Grafika" - -#: Source/options.cpp:750 -msgid "Graphics Settings" -msgstr "Ustawienia Graficzne" - -#: Source/options.cpp:751 -msgid "Fullscreen" -msgstr "Pełny Ekran" - -#: Source/options.cpp:751 -msgid "Display the game in windowed or fullscreen mode." -msgstr "Wyświetlanie gry w trybie okienkowym lub pełnoekranowym." - -#: Source/options.cpp:753 -msgid "Fit to Screen" -msgstr "Dopasuj do ekranu" +"Eksplozję było słychać aż tutaj! Wielkie dzięki. Nastały trudne czasy - " +"najpierw te potwory w katedrze, teraz to coś wypełzające z ziemi. Wiesz, " +"może i jestem biednym farmerem, ale potrafię docenić czyjś trud - weź to w " +"podzięce." -#: Source/options.cpp:753 +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:561 msgid "" -"Automatically adjust the game window to your current desktop screen aspect " -"ratio and resolution." +"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 "" -"Automatycznie dostosuj okno gry do bieżących proporcji i rozdzielczości " -"ekranu pulpitu." +"Och, mam kłopot... może... Nie, nie, nie... Masz wystarczająco dużo " +"problemów. Ale jeśli możesz to odwiedź mnie, kiedy już oczyścisz kościół z " +"tych kreatur... Znajdziesz wtedy trochę czasu dla biednego farmera, co?" -#: Source/options.cpp:756 -msgid "Upscale" -msgstr "Zwiększanie Rozdzielczości" +#. TRANSLATORS: Quest text spoken by Little Girl +#: Source/textdat.cpp:563 +msgid "Waaaah! (sniff) Waaaah! (sniff)" +msgstr "Buuu! Buuu!" -#: Source/options.cpp:756 +#. TRANSLATORS: Quest text spoken by Little Girl +#: Source/textdat.cpp:564 msgid "" -"Enables image scaling from the game resolution to your monitor resolution. " -"Prevents changing the monitor resolution and allows window resizing." +"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 "" -"Umożliwia skalowanie obrazu z rozdzielczości gry do rozdzielczości monitora. " -"Zapobiega zmianie rozdzielczości monitora i umożliwia zmianę rozmiaru okna." - -#: Source/options.cpp:757 -msgid "Scaling Quality" -msgstr "Rodzaj skalowania" +"Zgubiłam Teosia! To mój najlepszy przyjaciel! Bawiliśmy się przy rzece, i " +"Teoś powiedział, że idziemy zobaczyć to duże zielone coś. Mówiłam, że " +"lepiej nie, ale był uparty i poszliśmy. Nagle wypełzł wielki ROBAL! " +"Zaczęliśmy uciekać, ale Teoś upadł i to coś go PORWAłO!" -#: Source/options.cpp:757 -msgid "Enables optional filters to the output image when upscaling." +#. TRANSLATORS: Quest text spoken by Little Girl +#: Source/textdat.cpp:566 +msgid "" +"Didja find him? You gotta find Theodore, please! He's just little. He " +"can't take care of himself! Please!" msgstr "" -"Włącza opcjonalne filtry dla obrazu wyjściowego podczas skalowania w górę." - -#: Source/options.cpp:759 -msgid "Nearest Pixel" -msgstr "Najbliższy piksel" - -#: Source/options.cpp:760 -msgid "Bilinear" -msgstr "Bilinearne" - -#: Source/options.cpp:761 -msgid "Anisotropic" -msgstr "Anizotropowe" +"Masz go? Znajdź Teodora, proszę! Jest taki malutki. Nie poradzi sobie " +"sam! Proszę!" -#: Source/options.cpp:763 -msgid "Integer Scaling" -msgstr "Skalowanie Całkowite" +#. TRANSLATORS: Quest text spoken by Little Girl (Quest End) +#: Source/textdat.cpp:568 +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 "" +"Masz go! Jest bezpieczny! Dziękuję ci! Och Teosiu, czy te paskudne robale " +"cię wystraszyły? Co to? Blee! Coś przyczepiło ci się do futerka! Fuj! " +"Chodź Teosiu, wracamy do domu! A tobie jeszcze raz dziękuję!" -#: Source/options.cpp:763 -msgid "Scales the image using whole number pixel ratio." -msgstr "Skaluje obraz przy użyciu współczynnika liczby całkowitej pikseli." +#. TRANSLATORS: Quest text spoken by Defiler (Hostile) +#: Source/textdat.cpp:570 +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 "" +"Przez lata byliśmy pogrążeni w głębokim śnie, ale nadszedł moment " +"przebudzenia. Po tak długim spoczynku czujemy potworny głód. Już wkrótce " +"ruszymy na łowy..." -#: Source/options.cpp:764 -msgid "Vertical Sync" -msgstr "Synchronizacja pionowa" +#. TRANSLATORS: Quest text spoken by Defiler (Hostile) +#: Source/textdat.cpp:572 +msgid "" +"Have you been enjoying yourself, little mammal? How pathetic. Your little " +"world will be no challenge at all." +msgstr "Usunięta kwestia." -#: Source/options.cpp:764 +#. TRANSLATORS: Quest text spoken by Defiler (Hostile) +#: Source/textdat.cpp:574 msgid "" -"Forces waiting for Vertical Sync. Prevents tearing effect when drawing a " -"frame. Disabling it can help with mouse lag on some systems." +"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 "" -"Wymusza oczekiwanie na synchronizację pionową. Zapobiega efektowi rozrywania " -"podczas rysowania klatki. Wyłączenie tej funkcji może pomóc w eliminacji " -"opóźnień myszy w niektórych systemach." +"Wasza kraina zostanie sprofanowana, a nasza rasa opanuje ziemię, która jest " +"waszym domem. Nasze macki obejmą cały świat i będziemy ucztować na zwłokach " +"jego mieszkańców. Ludzie staną się naszymi orędownikami i pożywieniem." -#: Source/options.cpp:767 -msgid "Color Cycling" -msgstr "Przełączanie kolorów" - -#: Source/options.cpp:767 -msgid "Color cycling effect used for water, lava, and acid animation." -msgstr "" -"Efekt cyklicznych zmian kolorów stosowany w animacjach wody, lawy i kwasu." - -#: Source/options.cpp:768 -msgid "Alternate nest art" -msgstr "Użyj alternatywnej grafiki gniazda" +#. TRANSLATORS: Quest text spoken by Defiler (Hostile) +#: Source/textdat.cpp:576 +msgid "" +"Ah, I can smell you...you are close! Close! Ssss...the scent of blood and " +"fear...how enticing..." +msgstr "Usunięta kwestia." -#: Source/options.cpp:768 -msgid "The game will use an alternative palette for Hellfire’s nest tileset." +#. TRANSLATORS: Quest text spoken by Narrator +#: Source/textdat.cpp:584 +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 "" -"W grze zostanie wykorzystana alternatywna paleta kafelków gniazda Hellfire." - -#: Source/options.cpp:770 -msgid "Hardware Cursor" -msgstr "Kursor sprzętowy" - -#: Source/options.cpp:770 -msgid "Use a hardware cursor" -msgstr "Użyj kursora sprzętowego" +"I tak, w roku Złotej światłości, podjęto decyzję o wzniesieniu wielkiej " +"Katedry. Kamieniem węgielnym tego świętego miejsca była półprzezroczysta " +"skała, Antyrael, nazwana tak na cześć Anioła, który podzielił się swą wiedzą " +"z horadrimami. \n" +" \n" +"W roku Zwodniczych Cieni nastąpiło trzęsienie ziemi, przez które Katedra " +"popadła w ruinę. Gdy budowano katakumby i zamki, a ludzkość walczyła ze " +"spustoszeniem wywołanym przez Wojnę Grzechu, z ruin wygrzebano cenną skałę. " +"Tak właśnie zniknął kamień węgielny. \n" +" \n" +"Kamień był jednocześnie z tego świata i ze wszystkich światów. Tak jak " +"światłość jest jednocześnie wewnątrz wszystkiego i ponad wszystkim. " +"Światłość i jedność są świętą podstawą: jedność celu i jedność dziedzictwa." -#: Source/options.cpp:771 -msgid "Hardware Cursor For Items" -msgstr "Kursor sprzęt. dla przedmiotów" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:586 +msgid "Moo." +msgstr "Muu." -#: Source/options.cpp:771 -msgid "Use a hardware cursor for items." -msgstr "Użyj kursora sprzętowego dla przedmiotów." +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:587 +msgid "I said, Moo." +msgstr "No, muu." -#: Source/options.cpp:772 -msgid "Hardware Cursor Maximum Size" -msgstr "Maks. rozmiar kursora sprzętowego" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:588 +msgid "Look I'm just a cow, OK?" +msgstr "Słuchaj, jestem krową, kapujesz?" -#: Source/options.cpp:772 +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:589 msgid "" -"Maximum width / height for the hardware cursor. Larger cursors fall back to " -"software." +"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 "" -"Maksymalna szerokość / wysokość kursora sprzętowego. Większe kursory są " -"przełączane z powrotem do programowego." - -#: Source/options.cpp:774 -msgid "FPS Limiter" -msgstr "Ogranicznik FPS" +"Dobra, dobra. To tylko przebranie. Normalnie się tak nie ubieram, ale " +"siedziałem w domu i rozmyślałem nad swoim życiem, gdy nagle te robale, " +"pnącza i inne świństwa zaczęły wypełzać spod podłogi... to było okropne! " +"Szkoda, że nie miałem pod ręką innego ubrania, nie wyglądałbym jak skończony " +"wariat. Hej! Może wpadniesz do mnie do domu i przyniesiesz mój garniak? Ten " +"brązowy, bo szary jest na wyjścia wieczorowe. Zrobiłbym to sam, ale nie " +"chcę, żeby ktoś widział mnie w tym przebraniu. Weź to, może się przydać " +"do... przedarcia się przez te wszystkie przerosty. Ciężko nie zauważyć " +"mojego domu, jest na południu, przy rozwidleniu rzeki... wiesz... taki z " +"przerośniętym ogródkiem." -#: Source/options.cpp:774 -msgid "FPS is limited to avoid high CPU load. Limit considers refresh rate." +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:591 +msgid "" +"What are you wasting time for? Go get my suit! And hurry! That Holstein " +"over there keeps winking at me!" msgstr "" -"Liczba FPS jest ograniczona, aby uniknąć dużego obciążenia procesora. " -"Ograniczenie uwzględnia częstotliwość odświeżania." - -#: Source/options.cpp:775 -msgid "Show FPS" -msgstr "Pokaż FPS" - -#: Source/options.cpp:775 -msgid "Displays the FPS in the upper left corner of the screen." -msgstr "Wyświetlanie liczby FPS w lewym górnym rogu ekranu." - -#: Source/options.cpp:776 -msgid "Show health values" -msgstr "Pokaż punkty życia" - -#: Source/options.cpp:776 -msgid "Displays current / max health value on health globe." -msgstr "Wyświetla aktualną / maksymalną wartość stanu zdrowia na kuli zdrowia." - -#: Source/options.cpp:777 -msgid "Show mana values" -msgstr "Pokaż punkty many" - -#: Source/options.cpp:777 -msgid "Displays current / max mana value on mana globe." -msgstr "Wyświetla aktualną / maksymalną wartość many na kuli many." - -#: Source/options.cpp:826 -msgid "Gameplay" -msgstr "Rozgrywka" - -#: Source/options.cpp:826 -msgid "Gameplay Settings" -msgstr "Ustawienia Rozgrywki" - -#: Source/options.cpp:828 -msgid "Run in Town" -msgstr "Bieg w mieście" +"Na co ty czekasz? Leć po mój garniak! Szybko! Tamten byczek dziwnie się " +"już na mnie patrzy!" -#: Source/options.cpp:828 +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:593 msgid "" -"Enable jogging/fast walking in town for Diablo and Hellfire. This option was " -"introduced in the expansion." +"Hey, have you got my suit there? Quick, pass it over! These ears itch like " +"you wouldn't believe!" msgstr "" -"Włączenie biegania/szybkiego chodzenia w mieście dla Diablo i Hellfire. " -"Opcja ta została wprowadzona w dodatku." - -#: Source/options.cpp:829 -msgid "Grab Input" -msgstr "Chwytanie Wejścia" - -#: Source/options.cpp:829 -msgid "When enabled mouse is locked to the game window." -msgstr "Po włączeniu tej funkcji mysz jest zablokowana w oknie gry." - -#: Source/options.cpp:830 -msgid "Theo Quest" -msgstr "Zadanie Theo" +"Hej, masz moje ubranie? Streszczaj się! Te uszy tak swędzą, że już nie " +"wytrzymie!" -#: Source/options.cpp:830 -msgid "Enable Little Girl quest." -msgstr "Włącz zadanie Mała Dziewczynka." +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:595 +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 "" +"Nie, nie, nie, nie! Ten strój jest SZARY! Jest na wyjścia wieczorowe! Na " +"ważne spotkania! Nie mogę GO teraz założyć. Padło ci na mózg? Przecież " +"mówiłem, że ma być BRĄZOWY." -#: Source/options.cpp:831 -msgid "Cow Quest" -msgstr "Zadanie Krowie" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:597 +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 "" +"Aaach, o wiele lepiej. Wreszcie mogę poczuć się jak poważny człowiek! Czy " +"rogi są prosto? świetnie. No, dzięki za pomoc. Mam dla ciebie prezent... " +"łap; dam ci też małą radę dotyczącą ubioru, no wiesz... twojej stylówki... " +"możesz założyć coś trochę... albo kupić nowe... wiesz o co chodzi? Ten cały " +"motyw poszukiwacza przygód jest trochę... zbyt retro. Taka mała rada, hm? " +"Ciao." -#: Source/options.cpp:831 +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:599 msgid "" -"Enable Jersey's quest. Lester the farmer is replaced by the Complete Nut." +"Look. I'm a cow. And you, you're monster bait. Get some experience under " +"your belt! We'll talk..." msgstr "" -"Włącz zadanie Jersey. Rolnik Lester zostaje zastąpiony przez Kompletnego " -"Wariata." +"Słuchaj, jestem krową, a ty zwykłą przynętą na potwory. Nabierz trochę " +"doświadczenia, to pogadamy..." -#: Source/options.cpp:832 -msgid "Friendly Fire" -msgstr "Przyjacielski ogień" +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:602 +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 "" +"Zadanie, które ci powierzyłem, nie jest łatwe. Jeśli mógłbym jakoś... co " +"powiesz na dzbanek pysznego, świeżutkiego mleka?" -#: Source/options.cpp:832 +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:604 msgid "" -"Allow arrow/spell damage between players in multiplayer even when the " -"friendly mode is on." +"Oh, I could use your help, but perhaps after you've saved the catacombs from " +"the desecration of those beasts." msgstr "" -"Umożliwienie zadawania obrażeń od strzał/czarów między graczami w trybie " -"wieloosobowym, nawet jeśli włączony jest tryb przyjazny." +"Och, skorzystałbym z twojej pomocy, ale najpierw musisz oczyścić katakumby z " +"bestii, które je splugawiły." -#: Source/options.cpp:833 -msgid "Test Bard" -msgstr "Testuj Barda" +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:606 +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 "" +"Mam kłopot, ale nie mogę polegać na kimś zupełnie obcym. Odwiedź mnie, jak " +"nabierzesz doświadczenia." -#: Source/options.cpp:833 -msgid "Force the Bard character type to appear in the hero selection menu." -msgstr "Wymuś wyświetlanie typu postaci Bard w menu wyboru bohatera." +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:608 +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 "" +"Widzę w tobie ogromny potencjał. Mógłbym poprosić cię o przysługę, kiedy " +"już wypełnisz swoje przeznaczenie?" -#: Source/options.cpp:834 -msgid "Test Barbarian" -msgstr "Testuj Barbarzyńcę" +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:610 +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 "" +"Musisz mieć więcej siły. Nie chcę, żeby ci się stała krzywda, bo nikt poza " +"tobą nie będzie w stanie oczyścić katedry!" -#: Source/options.cpp:834 +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:612 msgid "" -"Force the Barbarian character type to appear in the hero selection menu." +"Me, I'm a self-made cow. Make something of yourself, and... then we'll talk." msgstr "" -"Wymuszenie wyświetlania typu postaci Barbarzyńca w menu wyboru bohatera." +"Jestem samozwańczą krową. Ogarnij się, bo póki co nie dorastasz mi do... " +"kopyt." -#: Source/options.cpp:835 -msgid "Experience Bar" -msgstr "Pasek Doświadczenia" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:614 +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 "" +"Nie będę tłumaczył się wszystkim przyjezdnym! Nie masz przypadkiem jakichś " +"potworów do zabicia? Wyjaśnię ci to później. O ile przeżyjesz..." -#: Source/options.cpp:835 -msgid "Experience Bar is added to the UI at the bottom of the screen." +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:616 +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 "" -"Pasek doświadczenia zostanie dodany do interfejsu użytkownika w dolnej " -"części ekranu." +"Przestań mnie dręczyć. Szukam kogoś naprawdę walecznego. Ty nie dasz " +"rady. Nie powierzę ci zadania, bo te potwory w moment cię zeżrą... Musisz " +"nabrać doświadczenia." -#: Source/options.cpp:836 -msgid "Enemy Health Bar" -msgstr "Pasek zdrowia wroga" - -#: Source/options.cpp:836 -msgid "Enemy Health Bar is displayed at the top of the screen." -msgstr "Pasek zdrowia wroga zostanie wyświetlony w górnej części ekranu." - -#: Source/options.cpp:837 -msgid "Auto Gold Pickup" -msgstr "Auto podnoszenie złota" - -#: Source/options.cpp:837 -msgid "Gold is automatically collected when in close proximity to the player." -msgstr "Złoto jest automatycznie zbierane, gdy znajduje się w pobliżu gracza." - -#: Source/options.cpp:838 -msgid "Auto Elixir Pickup" -msgstr "Auto podnoszenie eliksirów" - -#: Source/options.cpp:838 +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:618 msgid "" -"Elixirs are automatically collected when in close proximity to the player." -msgstr "Eliksiry są automatycznie zbierane, gdy znajdują się w pobliżu gracza." - -#: Source/options.cpp:839 -msgid "Auto Pickup in Town" -msgstr "Auto podnoszenie w mieście" - -#: Source/options.cpp:839 -msgid "Automatically pickup items in town." -msgstr "Automatycznie podnoś przedmioty w mieście." - -#: Source/options.cpp:840 -msgid "Adria Refills Mana" -msgstr "Adria Uzupełnia Manę" - -#: Source/options.cpp:840 -msgid "Adria will refill your mana when you visit her shop." -msgstr "Adria uzupełni twoją manę, gdy odwiedzisz jej sklep." - -#: Source/options.cpp:841 -msgid "Auto Equip Weapons" -msgstr "Automatycznie Załóż Broń" +"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 "" +"Dobra, dobra. To tylko przebranie. Normalnie się tak nie ubieram, ale " +"siedziałem w domu i rozmyślałem nad swoim życiem, gdy nagle te robale, " +"pnącza i inne świństwa zaczęły wypełzać spod podłogi... to było okropne! " +"Szkoda, że nie miałem pod ręką innego ubrania, nie wyglądałbym jak skończony " +"wariat. Hej! Może wpadniesz do mnie do domu i przyniesiesz mój garniak? Ten " +"brązowy, bo szary jest na wyjścia wieczorowe. Zrobiłbym to sam, ale nie " +"chcę, żeby ktoś widział mnie w tym przebraniu. Weź to, może się przydać " +"do... przedarcia się przez te wszystkie przerosty. Ciężko nie zauważyć " +"mojego domu, jest na południu, przy rozwidleniu rzeki... wiesz... taki z " +"przerośniętym ogródkiem." -#: Source/options.cpp:841 +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:621 msgid "" -"Weapons will be automatically equipped on pickup or purchase if enabled." +"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 "" -"Broń zostanie automatycznie założona przy podnoszeniu lub zakupie, jeśli " -"dana opcja jest włączona." - -#: Source/options.cpp:842 -msgid "Auto Equip Armor" -msgstr "Automatycznie Załóż Pancerz" +"Zaklęcia, groźby czy negocjacje zdają się nie mieć sensu. Wszystkie moje " +"sposoby zniewalania słabszych demonów nie działają na tę przerażającą bestię." -#: Source/options.cpp:842 -msgid "Armor will be automatically equipped on pickup or purchase if enabled." +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:623 +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 "" -"Pancerze będą automatycznie zakładane przy podnoszeniu lub zakupie, jeśli " -"dana opcja jest włączona." - -#: Source/options.cpp:843 -msgid "Auto Equip Helms" -msgstr "Automatycznie Załóż Hełmy" +"Mój dom ogarnia skażenie spowodowane zepsuciem niechcianego więźnia. Krypty " +"pełne są cieni czających się na krawędzi wzroku. Piskliwy dźwięk drapania " +"małych kreatur doprowadza mnie już do szaleństwa. Prawdopodobnie szukają " +"mego dziennika." -#: Source/options.cpp:843 -msgid "Helms will be automatically equipped on pickup or purchase if enabled." +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:625 +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 "" -"Hełmy będą automatycznie zakładane przy podnoszeniu lub zakupie, jeśli dana " -"opcja jest włączona." - -#: Source/options.cpp:844 -msgid "Auto Equip Shields" -msgstr "Automatycznie Załóż Tarcze" +"W swych tyradach potwór zdradził swe imię... Na-Krul. Chciałem znaleźć " +"informacje na jego temat, ale demony zniszczyły moją bibliotekę. Na-Krul... " +"sam dźwięk tego imienia przeszywa mnie zimnym dreszczem. Nawet w myślach " +"boję się je powtarzać. Wolę mówić na niego... Potwór." -#: Source/options.cpp:844 +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:627 msgid "" -"Shields will be automatically equipped on pickup or purchase if enabled." +"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 "" -"Tarcze będą automatycznie zakładane przy podnoszeniu lub zakupie, jeśli dana " -"opcja jest włączona." - -#: Source/options.cpp:845 -msgid "Auto Equip Jewelry" -msgstr "Automatycznie Załóż Biżuterię" +"Uwięziony potwór jest tak rozwścieczony, że przez jego wycie nie mogę zaznać " +"niezbędnego mi teraz snu. Przeklina mnie za zesłanie go do Otchłani. Jego " +"słowa wypełniają moje serce strachem, lecz ani na chwilę nie potrafię " +"zagłuszyć tego dźwięku." -#: Source/options.cpp:845 +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:629 msgid "" -"Jewelry will be automatically equipped on pickup or purchase if enabled." +"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 "" -"Biżuteria zostanie automatycznie założona przy podnoszeniu lub zakupie, " -"jeśli dana opcja jest włączona." - -#: Source/options.cpp:846 -msgid "Randomize Quests" -msgstr "Losuj Zadania" - -#: Source/options.cpp:846 -msgid "Randomly selecting available quests for new games." -msgstr "Losowe wybieranie dostępnych zadań dla nowych gier." - -#: Source/options.cpp:847 -msgid "Show Monster Type" -msgstr "Pokaż typ potwora" +"Mój czas już się kończy. Muszę spisać sposoby na osłabienie demona i ukryć " +"te teksty przed sługami, które mogą chcieć uwolnić swego Pana. Mam " +"nadzieję, że ten kto odnajdzie dziennik, wykorzysta mą wiedzę." -#: Source/options.cpp:847 +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:631 msgid "" -"Hovering over a monster will display the type of monster in the description " -"box in the UI." +"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 "" -"Najechanie kursorem myszy na potwora spowoduje wyświetlenie jego typu w polu " -"opisu w interfejsie użytkownika." +"Ktokolwiek czyta ten zwój, musi powstrzymać demoniczną kreaturę znajdującą " +"się za tamtymi murami. Mój czas się skończył. Nawet teraz jego upiorne " +"sługi próbują przebić spróchniałe drzwi, za którymi się skryłem. \n" +" \n" +"Dzięki tajemnej magii unieruchomiłem, a następnie uwięziłem demona za " +"grubymi murami. Jednak boję się, że to za mało. \n" +" \n" +"Zaklęcia spisane w mych trzech księgach zapewnią ci bezpieczne przejście do " +"jego komnaty, ale tylko rzucone w odpowiedniej kolejności. Dźwignie w " +"przedsionku zniosą bariery i uwolnią demona; nie dotykaj ich! Wystarczy, że " +"użyjesz tamtych trzech zaklęć. W przeciwnym razie jego moc może okazać się " +"zbyt potężna, by pozwolić ci na zwycięstwo." -#: Source/options.cpp:848 -msgid "Auto Refill Belt" -msgstr "Automatycznie uzupełnij pas" +#. TRANSLATORS: Quest text read aloud from book by player +#: Source/textdat.cpp:633 Source/textdat.cpp:636 Source/textdat.cpp:639 +#: Source/textdat.cpp:642 Source/textdat.cpp:645 +msgid "In Spiritu Sanctum." +msgstr "In Spiritu Sanctum." -#: Source/options.cpp:848 -msgid "Refill belt from inventory when belt item is consumed." -msgstr "Uzupełnij pasek z ekwipunku, gdy przedmiot w nim zostanie zużyty." +#. TRANSLATORS: Quest text read aloud from book by player +#: Source/textdat.cpp:634 Source/textdat.cpp:637 Source/textdat.cpp:640 +#: Source/textdat.cpp:643 Source/textdat.cpp:646 +msgid "Praedictum Otium." +msgstr "Praedictum Otium." -#: Source/options.cpp:849 -msgid "Disable Crippling Shrines" -msgstr "Wyłącz kaleczące kapliczki" +#. TRANSLATORS: Quest text read aloud from book by player +#: Source/textdat.cpp:635 Source/textdat.cpp:638 Source/textdat.cpp:641 +#: Source/textdat.cpp:644 Source/textdat.cpp:647 +msgid "Efficio Obitus Ut Inimicus." +msgstr "Efficio Obitus Ut Inimicus." -#: Source/options.cpp:849 -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 "" -"Po włączeniu opcji Kociołki, Fascynujące Kapliczki, Kozie Kapliczki, " -"Zdobione Kapliczki i Święte Kapliczki nie mogą zostać kliknięte i są one " -"oznaczane jako wyłączone." +#: Source/towners.cpp:82 +msgid "Griswold the Blacksmith" +msgstr "Kowal Griswold" -#: Source/options.cpp:850 -msgid "Quick Cast" -msgstr "Szybki Czar" +#: Source/towners.cpp:104 +msgid "Ogden the Tavern owner" +msgstr "Ogden - Właściciel Gospody" -#: Source/options.cpp:850 -msgid "" -"Spell hotkeys instantly cast the spell, rather than switching the readied " -"spell." -msgstr "" -"Naciśnięcie klawisza skrótu zaklęcia powoduje natychmiastowe rzucenie " -"zaklęcia, zamiast przełączania gotowego zaklęcia." +#: Source/towners.cpp:113 +msgid "Wounded Townsman" +msgstr "Ranny Mieszkaniec" -#: Source/options.cpp:851 -msgid "Heal Potion Pickup" -msgstr "Podnoszenie mikstur Leczenia" +#: Source/towners.cpp:134 +msgid "Adria the Witch" +msgstr "Wiedźma Adria" -#: Source/options.cpp:851 -msgid "Number of Healing potions to pick up automatically." -msgstr "Liczba mikstur Leczenia, które są podnoszone automatycznie." +#: Source/towners.cpp:143 +msgid "Gillian the Barmaid" +msgstr "Barmanka Gillian" -#: Source/options.cpp:852 -msgid "Full Heal Potion Pickup" -msgstr "Podnoszenie mikstur Pełn. Leczenia" +#: Source/towners.cpp:174 +msgid "Pepin the Healer" +msgstr "Uzdrowiciel Pepin" -#: Source/options.cpp:852 -msgid "Number of Full Healing potions to pick up automatically." -msgstr "Liczba mikstur Pełnego Leczenia, które są podnoszone automatycznie." +#: Source/towners.cpp:191 +msgid "Cain the Elder" +msgstr "Mędrzec Cain" -#: Source/options.cpp:853 -msgid "Mana Potion Pickup" -msgstr "Podnoszenie mikstur Many" +#: Source/towners.cpp:218 +msgid "Cow" +msgstr "Krowa" -#: Source/options.cpp:853 -msgid "Number of Mana potions to pick up automatically." -msgstr "Liczba mikstur Many, które będą podnoszone automatycznie." +#: Source/towners.cpp:241 +msgid "Lester the farmer" +msgstr "Farmer Lester" -#: Source/options.cpp:854 -msgid "Full Mana Potion Pickup" -msgstr "Podnoszenie mikstur Pełnej Many" +#: Source/towners.cpp:253 +msgid "Complete Nut" +msgstr "Skończony Wariat" -#: Source/options.cpp:854 -msgid "Number of Full Mana potions to pick up automatically." -msgstr "Liczba mikstur Pełnej Many, które będą podnoszone automatycznie." +#: Source/towners.cpp:261 +msgid "Celia" +msgstr "Celia" -#: Source/options.cpp:855 -msgid "Rejuvenation Potion Pickup" -msgstr "Podnoszenie mikstur Wzmocnienia" +#: Source/towners.cpp:274 +msgid "Slain Townsman" +msgstr "Zabity Mieszkaniec" -#: Source/options.cpp:855 -msgid "Number of Rejuvenation potions to pick up automatically." -msgstr "Liczba mikstur Wzmocnienia, które będą podnoszone automatycznie." +#: Source/translation_dummy.cpp:9 +msgctxt "monster" +msgid "Zombie" +msgstr "Zombie" -#: Source/options.cpp:856 -msgid "Full Rejuvenation Potion Pickup" -msgstr "Podnoszenie mikstur Pełn. Wzmocnienia" +#: Source/translation_dummy.cpp:10 +msgctxt "monster" +msgid "Ghoul" +msgstr "Ghul" -#: Source/options.cpp:856 -msgid "Number of Full Rejuvenation potions to pick up automatically." -msgstr "" -"Liczba mikstur Pełnego Wzmocnienia, które będą podnoszone automatycznie." +#: Source/translation_dummy.cpp:11 +msgctxt "monster" +msgid "Rotting Carcass" +msgstr "Gnijące Truchło" -#: Source/options.cpp:899 -msgid "Controller" -msgstr "Kontroler" +#: Source/translation_dummy.cpp:12 +msgctxt "monster" +msgid "Black Death" +msgstr "Czarna Śmierć" -#: Source/options.cpp:899 -msgid "Controller Settings" -msgstr "Ustawienia Kontrolera" +#: Source/translation_dummy.cpp:13 Source/translation_dummy.cpp:21 +msgctxt "monster" +msgid "Fallen One" +msgstr "Upadły" -#: Source/options.cpp:908 -msgid "Network" -msgstr "Sieć" +#: Source/translation_dummy.cpp:14 Source/translation_dummy.cpp:22 +msgctxt "monster" +msgid "Carver" +msgstr "Podrzynacz" -#: Source/options.cpp:908 -msgid "Network Settings" -msgstr "Ustawienia Sieciowe" +#: Source/translation_dummy.cpp:15 Source/translation_dummy.cpp:23 +msgctxt "monster" +msgid "Devil Kin" +msgstr "Diablik" -#: Source/options.cpp:920 -msgid "Chat" -msgstr "Czat" +#: Source/translation_dummy.cpp:16 Source/translation_dummy.cpp:24 +msgctxt "monster" +msgid "Dark One" +msgstr "Mroczny" -#: Source/options.cpp:920 -msgid "Chat Settings" -msgstr "Ustawienia czatu" +#: Source/translation_dummy.cpp:17 Source/translation_dummy.cpp:29 +msgctxt "monster" +msgid "Skeleton" +msgstr "Szkielet" -#: Source/options.cpp:929 Source/options.cpp:1045 -msgid "Language" -msgstr "Język" +#: Source/translation_dummy.cpp:18 +msgctxt "monster" +msgid "Corpse Axe" +msgstr "Trupi Topornik" -#: Source/options.cpp:929 -msgid "Define what language to use in game." -msgstr "Określ, jakiego języka należy używać w grze." +#: Source/translation_dummy.cpp:19 Source/translation_dummy.cpp:31 +msgctxt "monster" +msgid "Burning Dead" +msgstr "Płonący Trup" -#: Source/options.cpp:1045 -msgid "Language Settings" -msgstr "Ustawienia Języka" +#: Source/translation_dummy.cpp:20 Source/translation_dummy.cpp:32 +msgctxt "monster" +msgid "Horror" +msgstr "Kościej" -#: Source/options.cpp:1057 -msgid "Keymapping" -msgstr "Mapowanie Klawiszy" +#: Source/translation_dummy.cpp:25 +msgctxt "monster" +msgid "Scavenger" +msgstr "Ścierwojad" -#: Source/options.cpp:1057 -msgid "Keymapping Settings" -msgstr "Ustawienia mapowania klawiszy" +#: Source/translation_dummy.cpp:26 +msgctxt "monster" +msgid "Plague Eater" +msgstr "Pożeracz Zarazy" -#: Source/panels/charpanel.cpp:130 -msgid "Level" -msgstr "Poziom" +#: Source/translation_dummy.cpp:27 +msgctxt "monster" +msgid "Shadow Beast" +msgstr "Bestia Cienia" -#: Source/panels/charpanel.cpp:132 -msgid "Experience" -msgstr "Doświadczenie" +#: Source/translation_dummy.cpp:28 +msgctxt "monster" +msgid "Bone Gasher" +msgstr "Miażdżyciel Kości" -#: Source/panels/charpanel.cpp:134 -msgid "Next level" -msgstr "Następny poziom" +#: Source/translation_dummy.cpp:30 +msgctxt "monster" +msgid "Corpse Bow" +msgstr "Trupi Łucznik" -#: Source/panels/charpanel.cpp:142 -msgid "Base" -msgstr "Baza" +#: Source/translation_dummy.cpp:33 +msgctxt "monster" +msgid "Skeleton Captain" +msgstr "Kapitan Szkieletów" -#: Source/panels/charpanel.cpp:143 -msgid "Now" -msgstr "Teraz" +#: Source/translation_dummy.cpp:34 +msgctxt "monster" +msgid "Corpse Captain" +msgstr "Kapitan Zwłok" -#: Source/panels/charpanel.cpp:144 -msgid "Strength" -msgstr "Siła" +#: Source/translation_dummy.cpp:35 +msgctxt "monster" +msgid "Burning Dead Captain" +msgstr "Kapitan Płonących Trupów" -#: Source/panels/charpanel.cpp:148 -msgid "Magic" -msgstr "Magia" +#: Source/translation_dummy.cpp:36 +msgctxt "monster" +msgid "Horror Captain" +msgstr "Kapitan Kościejów" -#: Source/panels/charpanel.cpp:152 -msgid "Dexterity" -msgstr "Zręczność" +#: Source/translation_dummy.cpp:37 +msgctxt "monster" +msgid "Invisible Lord" +msgstr "Niewidzialny Władca" -#: Source/panels/charpanel.cpp:155 -msgid "Vitality" -msgstr "Żywotność" +#: Source/translation_dummy.cpp:38 +msgctxt "monster" +msgid "Hidden" +msgstr "Ukryty" -#: Source/panels/charpanel.cpp:158 -msgid "Points to distribute" -msgstr "Punkty do rozdania" +#: Source/translation_dummy.cpp:39 +msgctxt "monster" +msgid "Stalker" +msgstr "Prześladowca" -#: Source/panels/charpanel.cpp:168 -msgid "Armor class" -msgstr "Obrona" +#: Source/translation_dummy.cpp:40 +msgctxt "monster" +msgid "Unseen" +msgstr "Niewidoczny" -#: Source/panels/charpanel.cpp:170 -msgid "To hit" -msgstr "" -"Celność\n" -"ataku" +#: Source/translation_dummy.cpp:41 +msgctxt "monster" +msgid "Illusion Weaver" +msgstr "Tkacz Iluzji" -#: Source/panels/charpanel.cpp:172 -msgid "Damage" -msgstr "Obrażenia" +#: Source/translation_dummy.cpp:42 +msgctxt "monster" +msgid "Satyr Lord" +msgstr "Władca Satyrów" -#: Source/panels/charpanel.cpp:179 -msgid "Life" -msgstr "Życie" +#: Source/translation_dummy.cpp:43 Source/translation_dummy.cpp:51 +msgctxt "monster" +msgid "Flesh Clan" +msgstr "Klan Rozpruwaczy" -#: Source/panels/charpanel.cpp:183 -msgid "Mana" -msgstr "Mana" +#: Source/translation_dummy.cpp:44 Source/translation_dummy.cpp:52 +msgctxt "monster" +msgid "Stone Clan" +msgstr "Klan Kamienia" -#: Source/panels/charpanel.cpp:188 -msgid "Resist magic" -msgstr "" -"Odporności:\n" -"Magia\n" -" " +#: Source/translation_dummy.cpp:45 Source/translation_dummy.cpp:53 +msgctxt "monster" +msgid "Fire Clan" +msgstr "Klan Ognia" -#: Source/panels/charpanel.cpp:190 -msgid "Resist fire" -msgstr "Ogień" +#: Source/translation_dummy.cpp:46 Source/translation_dummy.cpp:54 +msgctxt "monster" +msgid "Night Clan" +msgstr "Klan Nocy" -#: Source/panels/charpanel.cpp:192 -msgid "Resist lightning" -msgstr "Błyskawice" +#: Source/translation_dummy.cpp:47 +msgctxt "monster" +msgid "Fiend" +msgstr "Bies" -#: Source/panels/mainpanel.cpp:62 Source/panels/mainpanel.cpp:106 -#: Source/panels/mainpanel.cpp:108 -msgid "voice" -msgstr "czat" +#: Source/translation_dummy.cpp:48 +msgctxt "monster" +msgid "Blink" +msgstr "Migon" -#: Source/panels/mainpanel.cpp:84 -msgid "char" -msgstr "postać" +#: Source/translation_dummy.cpp:49 +msgctxt "monster" +msgid "Gloom" +msgstr "Ponurzec" -#: Source/panels/mainpanel.cpp:85 -msgid "quests" -msgstr "zadania" +#: Source/translation_dummy.cpp:50 +msgctxt "monster" +msgid "Familiar" +msgstr "Chowaniec" -#: Source/panels/mainpanel.cpp:86 -msgid "map" -msgstr "mapa" +#: Source/translation_dummy.cpp:55 +msgctxt "monster" +msgid "Acid Beast" +msgstr "Żrąca Bestia" -#: Source/panels/mainpanel.cpp:87 -msgid "menu" -msgstr "menu" +#: Source/translation_dummy.cpp:56 +msgctxt "monster" +msgid "Poison Spitter" +msgstr "Trujopluj" -#: Source/panels/mainpanel.cpp:88 -msgid "inv" -msgstr "ekwipunek" +#: Source/translation_dummy.cpp:57 +msgctxt "monster" +msgid "Pit Beast" +msgstr "Wżerobestia" -#: Source/panels/mainpanel.cpp:89 -msgid "spells" -msgstr "czary" +#: Source/translation_dummy.cpp:58 +msgctxt "monster" +msgid "Lava Maw" +msgstr "Ogniopaszczy" -#: Source/panels/mainpanel.cpp:101 Source/panels/mainpanel.cpp:103 -#: Source/panels/mainpanel.cpp:105 -msgid "mute" -msgstr "cisza" +#: Source/translation_dummy.cpp:59 Source/translation_dummy.cpp:148 +msgctxt "monster" +msgid "Skeleton King" +msgstr "Król Szkieletów" -#: Source/panels/spell_book.cpp:155 Source/panels/spell_list.cpp:162 -msgid "Skill" -msgstr "Umiejętność" +#: Source/translation_dummy.cpp:60 Source/translation_dummy.cpp:156 +msgctxt "monster" +msgid "The Butcher" +msgstr "Rzeźnik" -#: Source/panels/spell_book.cpp:159 -msgid "Staff ({:d} charge)" -msgid_plural "Staff ({:d} charges)" -msgstr[0] "Kostur ({:d} ładunek)" -msgstr[1] "Kostur ({:d} ładunki)" -msgstr[2] "Kostur ({:d} ładunków)" +#: Source/translation_dummy.cpp:61 +msgctxt "monster" +msgid "Overlord" +msgstr "Nadzorca" -#. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:164 -msgctxt "spellbook" -msgid "Level {:d}" -msgstr "Poziom {:d}" +#: Source/translation_dummy.cpp:62 +msgctxt "monster" +msgid "Mud Man" +msgstr "Błotoczłek" -#: Source/panels/spell_book.cpp:166 -msgid "Unusable" -msgstr "Nieprzydatny" +#: Source/translation_dummy.cpp:63 +msgctxt "monster" +msgid "Toad Demon" +msgstr "Ropuszy Demon" -#. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:174 -msgid "Heals: {:d} - {:d}" -msgstr "Uzdrawia: {:d} - {:d}" +#: Source/translation_dummy.cpp:64 +msgctxt "monster" +msgid "Flayed One" +msgstr "Obdzieracz Skór" -#. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:176 -msgid "Damage: {:d} - {:d}" -msgstr "Obrażenia: {:d} - {:d}" +#: Source/translation_dummy.cpp:65 +msgctxt "monster" +msgid "Wyrm" +msgstr "Żmijowiec" -#. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:180 -msgid "Dmg: 1/3 target hp" -msgstr "Obr: 1/3 PŻ celu" +#: Source/translation_dummy.cpp:66 +msgctxt "monster" +msgid "Cave Slug" +msgstr "Podziemny Czerw" -#. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:182 -msgctxt "spellbook" -msgid "Mana: {:d}" -msgstr "Mana: {:d}" +#: Source/translation_dummy.cpp:67 +msgctxt "monster" +msgid "Devil Wyrm" +msgstr "Diabelski Żmijowiec" -#: Source/panels/spell_list.cpp:169 -msgid "Spell" -msgstr "Czar" +#: Source/translation_dummy.cpp:68 +msgctxt "monster" +msgid "Devourer" +msgstr "Pożeracz" -#: Source/panels/spell_list.cpp:172 -msgid "Damages undead only" -msgstr "Rani tylko nieumarłych" +#: Source/translation_dummy.cpp:69 +msgctxt "monster" +msgid "Magma Demon" +msgstr "Demon Magmy" -#: Source/panels/spell_list.cpp:183 -msgid "Scroll" -msgstr "Zwój" +#: Source/translation_dummy.cpp:70 +msgctxt "monster" +msgid "Blood Stone" +msgstr "Kamień Krwi" -#: Source/panels/spell_list.cpp:204 -msgid "Spell Hotkey {:s}" -msgstr "Klawisz Czaru: {:s}" +#: Source/translation_dummy.cpp:71 +msgctxt "monster" +msgid "Hell Stone" +msgstr "Piekielny Kamień" -#: Source/pfile.cpp:266 -msgid "Failed to open player archive for writing." -msgstr "Błąd podczas wczytywania danych postaci." +#: Source/translation_dummy.cpp:72 +msgctxt "monster" +msgid "Lava Lord" +msgstr "Władca Lawy" -#: Source/pfile.cpp:308 -msgid "Failed to open stash archive for writing." -msgstr "Błąd podczas wczytywania danych skrytki." +#: Source/translation_dummy.cpp:73 +msgctxt "monster" +msgid "Horned Demon" +msgstr "Rogaty Demon" -#: Source/pfile.cpp:419 -msgid "Unable to open archive" -msgstr "Nie udało się wczytać zawartości" - -#: Source/pfile.cpp:421 -msgid "Unable to load character" -msgstr "Nie udało się wczytać postaci" +#: Source/translation_dummy.cpp:74 +msgctxt "monster" +msgid "Mud Runner" +msgstr "Błotowiec" -#: Source/pfile.cpp:445 Source/pfile.cpp:465 -msgid "Unable to read to save file archive" -msgstr "Błąd podczas wczytywania pliku zapisu" +#: Source/translation_dummy.cpp:75 +msgctxt "monster" +msgid "Frost Charger" +msgstr "Mroźny Szarżownik" -#: Source/pfile.cpp:484 -msgid "Unable to write to save file archive" -msgstr "Błąd podczas tworzenia pliku zapisu" +#: Source/translation_dummy.cpp:76 +msgctxt "monster" +msgid "Obsidian Lord" +msgstr "Obsydianowy Władca" -#: Source/plrmsg.cpp:83 Source/qol/chatlog.cpp:126 -msgid "{:s} (lvl {:d}): " -msgstr "{:s} (lvl {:d}): " +#: Source/translation_dummy.cpp:77 +msgctxt "monster" +msgid "oldboned" +msgstr "staruszek" -#: Source/qol/chatlog.cpp:154 -msgid "Chat History (Messages: {:d})" -msgstr "Historia czatu (Wiadomości: {:d})" +#: Source/translation_dummy.cpp:78 +msgctxt "monster" +msgid "Red Death" +msgstr "Czerwona Zaraza" -#: Source/qol/itemlabels.cpp:72 -msgid "{:s} gold" -msgstr "{:s} złota" +#: Source/translation_dummy.cpp:79 +msgctxt "monster" +msgid "Litch Demon" +msgstr "Diabelski Mag" -#: Source/qol/stash.cpp:618 -msgid "How many gold pieces do you want to withdraw?" -msgstr "Ile sztuk złota chcesz wypłacić?" +#: Source/translation_dummy.cpp:80 +msgctxt "monster" +msgid "Undead Balrog" +msgstr "Nieumarły Balrog" -#. TRANSLATORS: Thousands separator -#: Source/qol/xpbar.cpp:61 -msgid "," -msgstr "," +#: Source/translation_dummy.cpp:81 +msgctxt "monster" +msgid "Incinerator" +msgstr "Kremator" -#: Source/qol/xpbar.cpp:146 -msgid "Level {:d}" -msgstr "Poziom {:d}" +#: Source/translation_dummy.cpp:82 +msgctxt "monster" +msgid "Flame Lord" +msgstr "Władca Płomieni" -#: Source/qol/xpbar.cpp:152 Source/qol/xpbar.cpp:160 -msgid "Experience: {:s}" -msgstr "Doświadczenie: {:s}" +#: Source/translation_dummy.cpp:83 +msgctxt "monster" +msgid "Doom Fire" +msgstr "Płonąca Zagłada" -#: Source/qol/xpbar.cpp:153 -msgid "Maximum Level" -msgstr "Maksymalny Poziom" +#: Source/translation_dummy.cpp:84 +msgctxt "monster" +msgid "Hell Burner" +msgstr "Piekielny Pomiot" -#: Source/qol/xpbar.cpp:161 -msgid "Next Level: {:s}" -msgstr "Następny poziom: {:s}" +#: Source/translation_dummy.cpp:85 +msgctxt "monster" +msgid "Red Storm" +msgstr "Czerwony Burzowiec" -#: Source/qol/xpbar.cpp:162 -msgid "{:s} to Level {:d}" -msgstr "{:s} do Poziomu {:d}" +#: Source/translation_dummy.cpp:86 +msgctxt "monster" +msgid "Storm Rider" +msgstr "Burzowy Jeździec" -#. TRANSLATORS: Quest Name Block -#: Source/quests.cpp:44 -msgid "The Magic Rock" -msgstr "Magiczny Kamień" +#: Source/translation_dummy.cpp:87 +msgctxt "monster" +msgid "Storm Lord" +msgstr "Władca Burz" -#: Source/quests.cpp:46 -msgid "Gharbad The Weak" -msgstr "Garbad Słaby" +#: Source/translation_dummy.cpp:88 +msgctxt "monster" +msgid "Maelstrom" +msgstr "Cyklon" -#: Source/quests.cpp:47 -msgid "Zhar the Mad" -msgstr "Zhar Szalony" +#: Source/translation_dummy.cpp:89 +msgctxt "monster" +msgid "Devil Kin Brute" +msgstr "Oprych Diablików" -#: Source/quests.cpp:48 -msgid "Lachdanan" -msgstr "Lachdanan" +#: Source/translation_dummy.cpp:90 +msgctxt "monster" +msgid "Winged-Demon" +msgstr "Skrzydlaty Demon" -#: Source/quests.cpp:50 -msgid "The Butcher" -msgstr "Rzeźnik" +#: Source/translation_dummy.cpp:91 +msgctxt "monster" +msgid "Gargoyle" +msgstr "Gargulec" -#: Source/quests.cpp:51 -msgid "Ogden's Sign" -msgstr "Szyld Ogdena" +#: Source/translation_dummy.cpp:92 +msgctxt "monster" +msgid "Blood Claw" +msgstr "Krwawoszpon" -#: Source/quests.cpp:52 -msgid "Halls of the Blind" -msgstr "Sale Ślepców" +#: Source/translation_dummy.cpp:93 +msgctxt "monster" +msgid "Death Wing" +msgstr "Śmiercioskrzydły" -#: Source/quests.cpp:53 -msgid "Valor" -msgstr "Odwaga" +#: Source/translation_dummy.cpp:94 +msgctxt "monster" +msgid "Slayer" +msgstr "Pogromca" -#: Source/quests.cpp:55 -msgid "Warlord of Blood" -msgstr "Marszałek Krwi" +#: Source/translation_dummy.cpp:95 +msgctxt "monster" +msgid "Guardian" +msgstr "Strażnik" -#: Source/quests.cpp:56 -msgid "The Curse of King Leoric" -msgstr "Klątwa Króla Leoryka" +#: Source/translation_dummy.cpp:96 +msgctxt "monster" +msgid "Vortex Lord" +msgstr "Władca Wiru" -#: Source/quests.cpp:57 Source/setmaps.cpp:27 -msgid "Poisoned Water Supply" -msgstr "Zatrute Źródło Wody" +#: Source/translation_dummy.cpp:97 +msgctxt "monster" +msgid "Balrog" +msgstr "Balrog" -#. TRANSLATORS: Quest Map -#: Source/quests.cpp:58 Source/quests.cpp:94 -msgid "The Chamber of Bone" -msgstr "Komnata Kości" +#: Source/translation_dummy.cpp:98 +msgctxt "monster" +msgid "Cave Viper" +msgstr "Jaskiniowy Żmij" -#: Source/quests.cpp:59 -msgid "Archbishop Lazarus" -msgstr "Arcybiskup Lazarus" +#: Source/translation_dummy.cpp:99 +msgctxt "monster" +msgid "Fire Drake" +msgstr "Ognisty Jaszczur" -#: Source/quests.cpp:60 -msgid "Grave Matters" -msgstr "Sprawa Grobowca" +#: Source/translation_dummy.cpp:100 +msgctxt "monster" +msgid "Gold Viper" +msgstr "Złoty Żmij" -#: Source/quests.cpp:61 -msgid "Farmer's Orchard" -msgstr "Sad Farmera" +#: Source/translation_dummy.cpp:101 +msgctxt "monster" +msgid "Azure Drake" +msgstr "Lazurowy Jaszczur" -#: Source/quests.cpp:62 -msgid "Little Girl" -msgstr "Mała Dziewczynka" +#: Source/translation_dummy.cpp:102 +msgctxt "monster" +msgid "Black Knight" +msgstr "Czarny Rycerz" -#: Source/quests.cpp:63 -msgid "Wandering Trader" -msgstr "Wędrujący Handlarz" +#: Source/translation_dummy.cpp:103 +msgctxt "monster" +msgid "Doom Guard" +msgstr "Strażnik Zagłady" -#: Source/quests.cpp:64 -msgid "The Defiler" -msgstr "Profanator" +#: Source/translation_dummy.cpp:104 +msgctxt "monster" +msgid "Steel Lord" +msgstr "Stalowy Władca" -#: Source/quests.cpp:65 -msgid "Na-Krul" -msgstr "Na-Krul" +#: Source/translation_dummy.cpp:105 +msgctxt "monster" +msgid "Blood Knight" +msgstr "Rycerz Krwi" -#: Source/quests.cpp:66 Source/trigs.cpp:449 -msgid "Cornerstone of the World" -msgstr "Fundament Świata" +#: Source/translation_dummy.cpp:106 +msgctxt "monster" +msgid "The Shredded" +msgstr "Związany" -#. TRANSLATORS: Quest Name Block end -#: Source/quests.cpp:67 -msgid "The Jersey's Jersey" -msgstr "Krowia Krowa" +#: Source/translation_dummy.cpp:107 +msgctxt "monster" +msgid "Hollow One" +msgstr "Pusta Istota" -#. TRANSLATORS: Quest Map -#: Source/quests.cpp:93 -msgid "King Leoric's Tomb" -msgstr "Grobowiec Króla Leoryka" +#: Source/translation_dummy.cpp:108 +msgctxt "monster" +msgid "Pain Master" +msgstr "Mistrz Bólu" -#. TRANSLATORS: Quest Map -#: Source/quests.cpp:95 Source/setmaps.cpp:26 -msgid "Maze" -msgstr "Labirynt" +#: Source/translation_dummy.cpp:109 +msgctxt "monster" +msgid "Reality Weaver" +msgstr "Tkacz Rzeczywistości" -#. TRANSLATORS: Quest Map -#: Source/quests.cpp:96 -msgid "A Dark Passage" -msgstr "Ciemne Przejście" +#: Source/translation_dummy.cpp:110 +msgctxt "monster" +msgid "Succubus" +msgstr "Sukkub" -#. TRANSLATORS: Quest Map -#: Source/quests.cpp:97 -msgid "Unholy Altar" -msgstr "Przeklęty Ołtarz" +#: Source/translation_dummy.cpp:111 +msgctxt "monster" +msgid "Snow Witch" +msgstr "Śnieżna Wiedźma" -#. TRANSLATORS: Used for Quest Portals. {:s} is a Map Name -#: Source/quests.cpp:451 -msgid "To {:s}" -msgstr "{:s}" +#: Source/translation_dummy.cpp:112 +msgctxt "monster" +msgid "Hell Spawn" +msgstr "Córa Piekieł" -#: Source/setmaps.cpp:24 -msgid "Skeleton King's Lair" -msgstr "Siedziba Króla Szkieletów" +#: Source/translation_dummy.cpp:113 +msgctxt "monster" +msgid "Soul Burner" +msgstr "Wypalaczka Dusz" -#: Source/setmaps.cpp:25 -msgid "Chamber of Bone" -msgstr "Komnata Kości" +#: Source/translation_dummy.cpp:114 +msgctxt "monster" +msgid "Counselor" +msgstr "Kanclerz" -#: Source/setmaps.cpp:28 -msgid "Archbishop Lazarus' Lair" -msgstr "Siedziba Arcybiskupa Lazarusa" +#: Source/translation_dummy.cpp:115 +msgctxt "monster" +msgid "Magistrate" +msgstr "Mediator" -#: Source/spelldat.cpp:16 -msgctxt "spell" -msgid "Firebolt" -msgstr "Ognisty Pocisk" +#: Source/translation_dummy.cpp:116 +msgctxt "monster" +msgid "Cabalist" +msgstr "Kabalista" -#: Source/spelldat.cpp:17 -msgctxt "spell" -msgid "Healing" -msgstr "Uzdrowienie" +#: Source/translation_dummy.cpp:117 +msgctxt "monster" +msgid "Advocate" +msgstr "Adwokat" -#: Source/spelldat.cpp:18 -msgctxt "spell" -msgid "Lightning" -msgstr "Błyskawice" +#: Source/translation_dummy.cpp:118 +msgctxt "monster" +msgid "Golem" +msgstr "Golem" -#: Source/spelldat.cpp:19 -msgctxt "spell" -msgid "Flash" -msgstr "Rozbłysk" +#: Source/translation_dummy.cpp:119 +msgctxt "monster" +msgid "The Dark Lord" +msgstr "Mroczny Władca" -#: Source/spelldat.cpp:20 -msgctxt "spell" -msgid "Identify" -msgstr "Identyfikacja" +#: Source/translation_dummy.cpp:120 +msgctxt "monster" +msgid "The Arch-Litch Malignus" +msgstr "Arcy-Lisz Złośliwiec" -#: Source/spelldat.cpp:21 -msgctxt "spell" -msgid "Fire Wall" -msgstr "Ściana Ognia" +#: Source/translation_dummy.cpp:121 +msgctxt "monster" +msgid "Hellboar" +msgstr "Piekielny Dzik" -#: Source/spelldat.cpp:22 -msgctxt "spell" -msgid "Town Portal" -msgstr "Miejski Portal" +#: Source/translation_dummy.cpp:122 +msgctxt "monster" +msgid "Stinger" +msgstr "Kąsacz" -#: Source/spelldat.cpp:23 -msgctxt "spell" -msgid "Stone Curse" -msgstr "Petryfikacja" +#: Source/translation_dummy.cpp:123 +msgctxt "monster" +msgid "Psychorb" +msgstr "Wizjoner" -#: Source/spelldat.cpp:24 -msgctxt "spell" -msgid "Infravision" -msgstr "Infrawizja" +#: Source/translation_dummy.cpp:124 +msgctxt "monster" +msgid "Arachnon" +msgstr "Pajęczysko" -#: Source/spelldat.cpp:25 -msgctxt "spell" -msgid "Phasing" -msgstr "Przenikanie" +#: Source/translation_dummy.cpp:125 +msgctxt "monster" +msgid "Felltwin" +msgstr "Wtórnie Upadły" -#: Source/spelldat.cpp:26 -msgctxt "spell" -msgid "Mana Shield" -msgstr "Tarcza Many" +#: Source/translation_dummy.cpp:126 +msgctxt "monster" +msgid "Hork Spawn" +msgstr "Pomiot Przeżartucha" -#: Source/spelldat.cpp:27 -msgctxt "spell" -msgid "Fireball" -msgstr "Ognista Kula" +#: Source/translation_dummy.cpp:127 +msgctxt "monster" +msgid "Venomtail" +msgstr "Ogon Jadu" -#: Source/spelldat.cpp:28 -msgctxt "spell" -msgid "Guardian" -msgstr "Strażnik" +#: Source/translation_dummy.cpp:128 +msgctxt "monster" +msgid "Necromorb" +msgstr "Nekrokula" -#: Source/spelldat.cpp:29 -msgctxt "spell" -msgid "Chain Lightning" -msgstr "Eksplozja Błyskawic" +#: Source/translation_dummy.cpp:129 +msgctxt "monster" +msgid "Spider Lord" +msgstr "Pajęczy Władca" -#: Source/spelldat.cpp:30 -msgctxt "spell" -msgid "Flame Wave" -msgstr "Fala Płomieni" +#: Source/translation_dummy.cpp:130 +msgctxt "monster" +msgid "Lashworm" +msgstr "Okaleczony Robak" -#: Source/spelldat.cpp:31 -msgctxt "spell" -msgid "Doom Serpents" -msgstr "Hydry" +#: Source/translation_dummy.cpp:131 +msgctxt "monster" +msgid "Torchant" +msgstr "Zapaleniec" -#: Source/spelldat.cpp:32 -msgctxt "spell" -msgid "Blood Ritual" -msgstr "Krwawy Rytuał" +#: Source/translation_dummy.cpp:132 Source/translation_dummy.cpp:157 +msgctxt "monster" +msgid "Hork Demon" +msgstr "Przeżartuch" -#: Source/spelldat.cpp:33 -msgctxt "spell" -msgid "Nova" -msgstr "Nova" +#: Source/translation_dummy.cpp:133 +msgctxt "monster" +msgid "Hell Bug" +msgstr "Piekielny Robak" -#: Source/spelldat.cpp:34 -msgctxt "spell" -msgid "Invisibility" -msgstr "Niewidzialność" +#: Source/translation_dummy.cpp:134 +msgctxt "monster" +msgid "Gravedigger" +msgstr "Grabarz" -#: Source/spelldat.cpp:35 -msgctxt "spell" -msgid "Inferno" -msgstr "Inferno" +#: Source/translation_dummy.cpp:135 +msgctxt "monster" +msgid "Tomb Rat" +msgstr "Grobowy Szczur" -#: Source/spelldat.cpp:36 -msgctxt "spell" -msgid "Golem" -msgstr "Golem" +#: Source/translation_dummy.cpp:136 +msgctxt "monster" +msgid "Firebat" +msgstr "Ognisty Nietoperz" -#: Source/spelldat.cpp:37 -msgctxt "spell" -msgid "Rage" -msgstr "Furia" +#: Source/translation_dummy.cpp:137 +msgctxt "monster" +msgid "Skullwing" +msgstr "Skrzydlata Czaszka" -#: Source/spelldat.cpp:38 -msgctxt "spell" -msgid "Teleport" -msgstr "Teleportacja" +#: Source/translation_dummy.cpp:138 +msgctxt "monster" +msgid "Lich" +msgstr "Lisz" -#: Source/spelldat.cpp:39 -msgctxt "spell" -msgid "Apocalypse" -msgstr "Apokalipsa" +#: Source/translation_dummy.cpp:139 +msgctxt "monster" +msgid "Crypt Demon" +msgstr "Demon Krypty" -#: Source/spelldat.cpp:40 -msgctxt "spell" -msgid "Etherealize" -msgstr "Znieczulenie" +#: Source/translation_dummy.cpp:140 +msgctxt "monster" +msgid "Hellbat" +msgstr "Piekielny Nietoperz" -#: Source/spelldat.cpp:41 -msgctxt "spell" -msgid "Item Repair" -msgstr "Naprawa przedmiotów" +#: Source/translation_dummy.cpp:141 +msgctxt "monster" +msgid "Bone Demon" +msgstr "Kościany Demon" -#: Source/spelldat.cpp:42 -msgctxt "spell" -msgid "Staff Recharge" -msgstr "Naładowanie Kostura" +#: Source/translation_dummy.cpp:142 +msgctxt "monster" +msgid "Arch Lich" +msgstr "Arcylisz" -#: Source/spelldat.cpp:43 -msgctxt "spell" -msgid "Trap Disarm" -msgstr "Rozbrajanie Pułapek" +#: Source/translation_dummy.cpp:143 +msgctxt "monster" +msgid "Biclops" +msgstr "Dwugłowiec" -#: Source/spelldat.cpp:44 -msgctxt "spell" -msgid "Elemental" -msgstr "Żywiołak" +#: Source/translation_dummy.cpp:144 +msgctxt "monster" +msgid "Flesh Thing" +msgstr "Padliniec" -#: Source/spelldat.cpp:45 -msgctxt "spell" -msgid "Charged Bolt" -msgstr "Wiązka Błyskawic" +#: Source/translation_dummy.cpp:145 +msgctxt "monster" +msgid "Reaper" +msgstr "Żniwiarz" -#: Source/spelldat.cpp:46 -msgctxt "spell" -msgid "Holy Bolt" -msgstr "Święty Pocisk" +#: Source/translation_dummy.cpp:146 Source/translation_dummy.cpp:159 +msgctxt "monster" +msgid "Na-Krul" +msgstr "Na-Krul" -#: Source/spelldat.cpp:47 -msgctxt "spell" -msgid "Resurrect" -msgstr "Wskrzeszenie" +#: Source/translation_dummy.cpp:147 +msgctxt "monster" +msgid "Gharbad the Weak" +msgstr "Garbad Słaby" -#: Source/spelldat.cpp:48 -msgctxt "spell" -msgid "Telekinesis" -msgstr "Telekineza" +#: Source/translation_dummy.cpp:149 +msgctxt "monster" +msgid "Zhar the Mad" +msgstr "Zhar Szalony" -#: Source/spelldat.cpp:49 -msgctxt "spell" -msgid "Heal Other" -msgstr "Uzdrowienie Innych" +#: Source/translation_dummy.cpp:150 +msgctxt "monster" +msgid "Snotspill" +msgstr "Śluzoglut" -#: Source/spelldat.cpp:50 -msgctxt "spell" -msgid "Blood Star" -msgstr "Krwawa Gwiazda" +#: Source/translation_dummy.cpp:151 +msgctxt "monster" +msgid "Arch-Bishop Lazarus" +msgstr "Arcybiskup Lazarus" -#: Source/spelldat.cpp:51 -msgctxt "spell" -msgid "Bone Spirit" -msgstr "Kościane Widmo" +#: Source/translation_dummy.cpp:152 +msgctxt "monster" +msgid "Red Vex" +msgstr "Czerwona Dręczycielka" -#: Source/spelldat.cpp:52 -msgctxt "spell" -msgid "Mana" -msgstr "Mana" +#: Source/translation_dummy.cpp:153 +msgctxt "monster" +msgid "Black Jade" +msgstr "Nefrytoczerń" -#: Source/spelldat.cpp:53 -msgctxt "spell" -msgid "the Magi" -msgstr "Mądrości" +#: Source/translation_dummy.cpp:154 +msgctxt "monster" +msgid "Lachdanan" +msgstr "Lachdanan" -#: Source/spelldat.cpp:54 -msgctxt "spell" -msgid "the Jester" -msgstr "Ironii" +#: Source/translation_dummy.cpp:155 +msgctxt "monster" +msgid "Warlord of Blood" +msgstr "Marszałek Krwi" -#: Source/spelldat.cpp:55 -msgctxt "spell" -msgid "Lightning Wall" -msgstr "Ściana Błyskawic" +#: Source/translation_dummy.cpp:158 +msgctxt "monster" +msgid "The Defiler" +msgstr "Profanator" -#: Source/spelldat.cpp:56 -msgctxt "spell" -msgid "Immolation" -msgstr "Podpalenie" +#: Source/translation_dummy.cpp:160 +msgctxt "monster" +msgid "Bonehead Keenaxe" +msgstr "Bezmózgi Topornik" -#: Source/spelldat.cpp:57 -msgctxt "spell" -msgid "Warp" -msgstr "Przejście" +#: Source/translation_dummy.cpp:161 +msgctxt "monster" +msgid "Bladeskin the Slasher" +msgstr "Twardoskóry Zabójca" -#: Source/spelldat.cpp:58 -msgctxt "spell" -msgid "Reflect" -msgstr "Odbicie" +#: Source/translation_dummy.cpp:162 +msgctxt "monster" +msgid "Soulpus" +msgstr "Ropiejąca Dusza" -#: Source/spelldat.cpp:59 -msgctxt "spell" -msgid "Berserk" -msgstr "Szał Bojowy" +#: Source/translation_dummy.cpp:163 +msgctxt "monster" +msgid "Pukerat the Unclean" +msgstr "Plugawy Szujożerca" -#: Source/spelldat.cpp:60 -msgctxt "spell" -msgid "Ring of Fire" -msgstr "Pierścień Ognia" +#: Source/translation_dummy.cpp:164 +msgctxt "monster" +msgid "Boneripper" +msgstr "Kościorwij" -#: Source/spelldat.cpp:61 -msgctxt "spell" -msgid "Search" -msgstr "Przeszukiwanie" +#: Source/translation_dummy.cpp:165 +msgctxt "monster" +msgid "Rotfeast the Hungry" +msgstr "Pleśniożer" -#: Source/spelldat.cpp:62 -msgctxt "spell" -msgid "Rune of Fire" -msgstr "Runa Ognia" +#: Source/translation_dummy.cpp:166 +msgctxt "monster" +msgid "Gutshank the Quick" +msgstr "Zwinny Patroszyciel" -#: Source/spelldat.cpp:63 -msgctxt "spell" -msgid "Rune of Light" -msgstr "Runa Światła" +#: Source/translation_dummy.cpp:167 +msgctxt "monster" +msgid "Brokenhead Bangshield" +msgstr "Czaszkołup Tarczownik" -#: Source/spelldat.cpp:64 -msgctxt "spell" -msgid "Rune of Nova" -msgstr "Runa Novy" +#: Source/translation_dummy.cpp:168 +msgctxt "monster" +msgid "Bongo" +msgstr "Bongo" -#: Source/spelldat.cpp:65 -msgctxt "spell" -msgid "Rune of Immolation" -msgstr "Runa Podpalenia" +#: Source/translation_dummy.cpp:169 +msgctxt "monster" +msgid "Rotcarnage" +msgstr "Rzezimieszek" -#: Source/spelldat.cpp:66 -msgctxt "spell" -msgid "Rune of Stone" -msgstr "Runa Kamienia" +#: Source/translation_dummy.cpp:170 +msgctxt "monster" +msgid "Shadowbite" +msgstr "Kąsacz Cieni" -#: Source/stores.cpp:96 -msgid "Griswold" -msgstr "Griswold" +#: Source/translation_dummy.cpp:171 +msgctxt "monster" +msgid "Deadeye" +msgstr "Martwooki" -#: Source/stores.cpp:97 -msgid "Pepin" -msgstr "Pepin" +#: Source/translation_dummy.cpp:172 +msgctxt "monster" +msgid "Madeye the Dead" +msgstr "Trup Szalonooki" -#: Source/stores.cpp:99 -msgid "Ogden" -msgstr "Ogden" +#: Source/translation_dummy.cpp:173 +msgctxt "monster" +msgid "El Chupacabras" +msgstr "El Chupacabras" -#: Source/stores.cpp:100 -msgid "Cain" -msgstr "Cain" +#: Source/translation_dummy.cpp:174 +msgctxt "monster" +msgid "Skullfire" +msgstr "Ognistogłowy" -#: Source/stores.cpp:101 -msgid "Farnham" -msgstr "Farnham" +#: Source/translation_dummy.cpp:175 +msgctxt "monster" +msgid "Warpskull" +msgstr "Wypaczona Czaszka" -#: Source/stores.cpp:102 -msgid "Adria" -msgstr "Adria" +#: Source/translation_dummy.cpp:176 +msgctxt "monster" +msgid "Goretongue" +msgstr "Krwiopijca" -#: Source/stores.cpp:103 Source/stores.cpp:1318 -msgid "Gillian" -msgstr "Gillian" +#: Source/translation_dummy.cpp:177 +msgctxt "monster" +msgid "Pulsecrawler" +msgstr "Rozdrażniony Pełzacz" -#: Source/stores.cpp:104 -msgid "Wirt" -msgstr "Wirt" +#: Source/translation_dummy.cpp:178 +msgctxt "monster" +msgid "Moonbender" +msgstr "Zaklinacz Księżyca" -#: Source/stores.cpp:223 Source/stores.cpp:230 -msgid "Back" -msgstr "Powrót" +#: Source/translation_dummy.cpp:179 +msgctxt "monster" +msgid "Wrathraven" +msgstr "Kruk Gniewu" -#: Source/stores.cpp:252 Source/stores.cpp:258 -msgid ", " -msgstr ", " +#: Source/translation_dummy.cpp:180 +msgctxt "monster" +msgid "Spineeater" +msgstr "Kręgojad" -#: Source/stores.cpp:269 -msgid "Damage: {:d}-{:d} " -msgstr "Obrażenia: {:d}-{:d} " +#: Source/translation_dummy.cpp:181 +msgctxt "monster" +msgid "Blackash the Burning" +msgstr "Spopielacz" -#: Source/stores.cpp:271 -msgid "Armor: {:d} " -msgstr "Obrona: {:d} " +#: Source/translation_dummy.cpp:182 +msgctxt "monster" +msgid "Shadowcrow" +msgstr "Cieniowron" -#: Source/stores.cpp:273 -msgid "Dur: {:d}/{:d}, " -msgstr "Wyt: {:d}/{:d}, " +#: Source/translation_dummy.cpp:183 +msgctxt "monster" +msgid "Blightstone the Weak" +msgstr "Osłabiony Niszczyciel" -#: Source/stores.cpp:275 -msgid "Indestructible, " -msgstr "Niezniszczalność, " +#: Source/translation_dummy.cpp:184 +msgctxt "monster" +msgid "Bilefroth the Pit Master" +msgstr "Rządca Czeluści" -#: Source/stores.cpp:283 -msgid "No required attributes" -msgstr "Brak wymagań atrybutów" +#: Source/translation_dummy.cpp:185 +msgctxt "monster" +msgid "Bloodskin Darkbow" +msgstr "Krwawoskóry Łucznik" -#: Source/stores.cpp:315 Source/stores.cpp:1071 Source/stores.cpp:1305 -msgid "Welcome to the" -msgstr "Witaj" +#: Source/translation_dummy.cpp:186 +msgctxt "monster" +msgid "Foulwing" +msgstr "Plugawoskrzydły" -#: Source/stores.cpp:316 -msgid "Blacksmith's shop" -msgstr "Warsztat Kowala" +#: Source/translation_dummy.cpp:187 +msgctxt "monster" +msgid "Shadowdrinker" +msgstr "Spijacz Cieni" -#: Source/stores.cpp:317 Source/stores.cpp:677 Source/stores.cpp:1073 -#: Source/stores.cpp:1131 Source/stores.cpp:1307 Source/stores.cpp:1319 -#: Source/stores.cpp:1332 -msgid "Would you like to:" -msgstr "Czynność:" +#: Source/translation_dummy.cpp:188 +msgctxt "monster" +msgid "Hazeshifter" +msgstr "Dręczyciel" -#: Source/stores.cpp:318 -msgid "Talk to Griswold" -msgstr "Rozmowa" +#: Source/translation_dummy.cpp:189 +msgctxt "monster" +msgid "Deathspit" +msgstr "Śmierciopluj" -#: Source/stores.cpp:319 -msgid "Buy basic items" -msgstr "Kup zwykłe przedmioty" +#: Source/translation_dummy.cpp:190 +msgctxt "monster" +msgid "Bloodgutter" +msgstr "Krwiopijca" -#: Source/stores.cpp:320 -msgid "Buy premium items" -msgstr "Kup wyjątkowe przedmioty" +#: Source/translation_dummy.cpp:191 +msgctxt "monster" +msgid "Deathshade Fleshmaul" +msgstr "Cień Rozpruwacza" -#: Source/stores.cpp:321 Source/stores.cpp:680 -msgid "Sell items" -msgstr "Sprzedaż" +#: Source/translation_dummy.cpp:192 +msgctxt "monster" +msgid "Warmaggot the Mad" +msgstr "Wojenny Czerw" -#: Source/stores.cpp:322 -msgid "Repair items" -msgstr "Naprawa" +#: Source/translation_dummy.cpp:193 +msgctxt "monster" +msgid "Glasskull the Jagged" +msgstr "Kościany Okruch" -#: Source/stores.cpp:323 -msgid "Leave the shop" -msgstr "Wyjdź" +#: Source/translation_dummy.cpp:194 +msgctxt "monster" +msgid "Blightfire" +msgstr "Płomień Zniszczenia" -#: Source/stores.cpp:372 Source/stores.cpp:737 Source/stores.cpp:1108 -msgid "I have these items for sale:" -msgstr "Podstawowe przedmioty na sprzedaż:" +#: Source/translation_dummy.cpp:195 +msgctxt "monster" +msgid "Nightwing the Cold" +msgstr "Mroźny Nocoskrzydły" -#: Source/stores.cpp:437 -msgid "I have these premium items for sale:" -msgstr "Wyjątkowe przedmioty na sprzedaż:" +#: Source/translation_dummy.cpp:196 +msgctxt "monster" +msgid "Gorestone" +msgstr "Rzezikamień" -#: Source/stores.cpp:556 Source/stores.cpp:830 -msgid "You have nothing I want." -msgstr "Nie masz rzeczy, których potrzebuję." +#: Source/translation_dummy.cpp:197 +msgctxt "monster" +msgid "Bronzefist Firestone" +msgstr "Ognisty Pięściarz" -#: Source/stores.cpp:567 Source/stores.cpp:842 -msgid "Which item is for sale?" -msgstr "Który przedmiot chcesz sprzedać?" +#: Source/translation_dummy.cpp:198 +msgctxt "monster" +msgid "Wrathfire the Doomed" +msgstr "Zgładzony Furiat" -#: Source/stores.cpp:638 -msgid "You have nothing to repair." -msgstr "Nie masz nic do naprawy." +#: Source/translation_dummy.cpp:199 +msgctxt "monster" +msgid "Firewound the Grim" +msgstr "Poparzony Pustelnik" -#: Source/stores.cpp:649 -msgid "Repair which item?" -msgstr "Który przedmiot naprawić?" +#: Source/translation_dummy.cpp:200 +msgctxt "monster" +msgid "Baron Sludge" +msgstr "Błotnisty Magnat" -#: Source/stores.cpp:676 -msgid "Witch's shack" -msgstr "Chata Wiedźmy" +#: Source/translation_dummy.cpp:201 +msgctxt "monster" +msgid "Blighthorn Steelmace" +msgstr "Cień Zniszczenia" -#: Source/stores.cpp:678 -msgid "Talk to Adria" -msgstr "Rozmowa" +#: Source/translation_dummy.cpp:202 +msgctxt "monster" +msgid "Chaoshowler" +msgstr "Skowytnik" -#: Source/stores.cpp:679 Source/stores.cpp:1075 -msgid "Buy items" -msgstr "Kup przedmioty" +#: Source/translation_dummy.cpp:203 +msgctxt "monster" +msgid "Doomgrin the Rotting" +msgstr "Gnijący Chichot" -#: Source/stores.cpp:681 -msgid "Recharge staves" -msgstr "Naładowanie kosturów" +#: Source/translation_dummy.cpp:204 +msgctxt "monster" +msgid "Madburner" +msgstr "Wściekły Podpalacz" -#: Source/stores.cpp:682 -msgid "Leave the shack" -msgstr "Wyjdź" +#: Source/translation_dummy.cpp:205 +msgctxt "monster" +msgid "Bonesaw the Litch" +msgstr "Lisz Piłognat" -#: Source/stores.cpp:904 -msgid "You have nothing to recharge." -msgstr "Nie masz niczego do regeneracji." +#: Source/translation_dummy.cpp:206 +msgctxt "monster" +msgid "Breakspine" +msgstr "Łamikark" -#: Source/stores.cpp:915 -msgid "Recharge which item?" -msgstr "Który przedmiot zregenerować?" +#: Source/translation_dummy.cpp:207 +msgctxt "monster" +msgid "Devilskull Sharpbone" +msgstr "Pożeracz Kości" -#: Source/stores.cpp:928 -msgid "You do not have enough gold" -msgstr "Brakuje ci złota" +#: Source/translation_dummy.cpp:208 +msgctxt "monster" +msgid "Brokenstorm" +msgstr "Burzowiec" -#: Source/stores.cpp:936 -msgid "You do not have enough room in inventory" -msgstr "Nie masz miejsca w ekwipunku" +#: Source/translation_dummy.cpp:209 +msgctxt "monster" +msgid "Stormbane" +msgstr "Szturmująca Zguba" -#: Source/stores.cpp:973 -msgid "Do we have a deal?" -msgstr "Umowa stoi?" +#: Source/translation_dummy.cpp:210 +msgctxt "monster" +msgid "Oozedrool" +msgstr "Szlamożer" -#: Source/stores.cpp:976 -msgid "Are you sure you want to identify this item?" -msgstr "Czy na pewno chcesz zidentyfikować ten przedmiot?" +#: Source/translation_dummy.cpp:211 +msgctxt "monster" +msgid "Goldblight of the Flame" +msgstr "Płomienny Złotognij" -#: Source/stores.cpp:982 -msgid "Are you sure you want to buy this item?" -msgstr "Czy na pewno chcesz kupić ten przedmiot?" +#: Source/translation_dummy.cpp:212 +msgctxt "monster" +msgid "Blackstorm" +msgstr "Czarny Sztorm" -#: Source/stores.cpp:985 -msgid "Are you sure you want to recharge this item?" -msgstr "Czy na pewno chcesz zregenerować ten przedmiot?" +#: Source/translation_dummy.cpp:213 +msgctxt "monster" +msgid "Plaguewrath" +msgstr "Plagoupiór" -#: Source/stores.cpp:989 -msgid "Are you sure you want to sell this item?" -msgstr "Czy na pewno chcesz sprzedać ten przedmiot?" +#: Source/translation_dummy.cpp:214 +msgctxt "monster" +msgid "The Flayer" +msgstr "Obdzieracz" -#: Source/stores.cpp:992 -msgid "Are you sure you want to repair this item?" -msgstr "Czy na pewno chcesz naprawić ten przedmiot?" +#: Source/translation_dummy.cpp:215 +msgctxt "monster" +msgid "Bluehorn" +msgstr "Błękitnorogi" -#: Source/stores.cpp:1006 Source/towners.cpp:150 -msgid "Wirt the Peg-legged boy" -msgstr "Wirt - kulejący chłopiec" +#: Source/translation_dummy.cpp:216 +msgctxt "monster" +msgid "Warpfire Hellspawn" +msgstr "Piekielne Plugastwo" -#: Source/stores.cpp:1009 Source/stores.cpp:1016 -msgid "Talk to Wirt" -msgstr "Rozmowa" +#: Source/translation_dummy.cpp:217 +msgctxt "monster" +msgid "Fangspeir" +msgstr "Ostrokieł" -#: Source/stores.cpp:1010 -msgid "I have something for sale," -msgstr "Pokażę ci co oferuję," +#: Source/translation_dummy.cpp:218 +msgctxt "monster" +msgid "Festerskull" +msgstr "Pęknięta Czaszka" -#: Source/stores.cpp:1011 -msgid "but it will cost 50 gold" -msgstr "jeśli dasz mi zaliczkę:" +#: Source/translation_dummy.cpp:219 +msgctxt "monster" +msgid "Lionskull the Bent" +msgstr "Krętacz Lwia Czaszka" -#: Source/stores.cpp:1012 -msgid "just to take a look. " -msgstr "50 szt. złota. " +#: Source/translation_dummy.cpp:220 +msgctxt "monster" +msgid "Blacktongue" +msgstr "Czarny Jęzorak" -#: Source/stores.cpp:1013 -msgid "What have you got?" -msgstr "Zgoda, co tam masz?" +#: Source/translation_dummy.cpp:221 +msgctxt "monster" +msgid "Viletouch" +msgstr "Plugawion" -#: Source/stores.cpp:1014 Source/stores.cpp:1017 Source/stores.cpp:1134 -#: Source/stores.cpp:1322 -msgid "Say goodbye" -msgstr "Wyjdź" +#: Source/translation_dummy.cpp:222 +msgctxt "monster" +msgid "Viperflame" +msgstr "Ogniożmij" -#: Source/stores.cpp:1027 -msgid "I have this item for sale:" -msgstr "Mogę ci to sprzedać:" +#: Source/translation_dummy.cpp:223 +msgctxt "monster" +msgid "Fangskin" +msgstr "Zęboskóry" -#: Source/stores.cpp:1049 -msgid "Leave" -msgstr "Wyjdź" +#: Source/translation_dummy.cpp:224 +msgctxt "monster" +msgid "Witchfire the Unholy" +msgstr "Wiedźma Profanacji" -#: Source/stores.cpp:1072 -msgid "Healer's home" -msgstr "Dom Uzdrowiciela" +#: Source/translation_dummy.cpp:225 +msgctxt "monster" +msgid "Blackskull" +msgstr "Czarnokostny" -#: Source/stores.cpp:1074 -msgid "Talk to Pepin" -msgstr "Rozmowa" +#: Source/translation_dummy.cpp:226 +msgctxt "monster" +msgid "Soulslash" +msgstr "Rozpruwacz Dusz" -#: Source/stores.cpp:1076 -msgid "Leave Healer's home" -msgstr "Wyjdź" +#: Source/translation_dummy.cpp:227 +msgctxt "monster" +msgid "Windspawn" +msgstr "Porywisty Niegodziwiec" -#: Source/stores.cpp:1130 -msgid "The Town Elder" -msgstr "Miejski Kronikarz" +#: Source/translation_dummy.cpp:228 +msgctxt "monster" +msgid "Lord of the Pit" +msgstr "Pan Czeluści" -#: Source/stores.cpp:1132 -msgid "Talk to Cain" -msgstr "Rozmowa" +#: Source/translation_dummy.cpp:229 +msgctxt "monster" +msgid "Rustweaver" +msgstr "Tkacz Rdzy" -#: Source/stores.cpp:1133 -msgid "Identify an item" -msgstr "Identyfikacja" +#: Source/translation_dummy.cpp:230 +msgctxt "monster" +msgid "Howlingire the Shade" +msgstr "Wyjący Cień" -#: Source/stores.cpp:1226 -msgid "You have nothing to identify." -msgstr "Nie masz nic do identyfikacji." +#: Source/translation_dummy.cpp:231 +msgctxt "monster" +msgid "Doomcloud" +msgstr "Powiew Zagłady" -#: Source/stores.cpp:1237 -msgid "Identify which item?" -msgstr "Który przedmiot zidentyfikować?" +#: Source/translation_dummy.cpp:232 +msgctxt "monster" +msgid "Bloodmoon Soulfire" +msgstr "Księżycowa Dusza" -#: Source/stores.cpp:1252 -msgid "This item is:" -msgstr "Ten przedmiot to:" +#: Source/translation_dummy.cpp:233 +msgctxt "monster" +msgid "Witchmoon" +msgstr "Księżycowa Wiedźma" -#: Source/stores.cpp:1255 -msgid "Done" -msgstr "Akceptuj" +#: Source/translation_dummy.cpp:234 +msgctxt "monster" +msgid "Gorefeast" +msgstr "Żywiciel Krwi" -#: Source/stores.cpp:1264 -msgid "Talk to {:s}" -msgstr "Rozmawiaj z {:s}" +#: Source/translation_dummy.cpp:235 +msgctxt "monster" +msgid "Graywar the Slayer" +msgstr "Pogromiciel" -#: Source/stores.cpp:1267 -msgid "Talking to {:s}" -msgstr "Rozmowa z {:s}" +#: Source/translation_dummy.cpp:236 +msgctxt "monster" +msgid "Dreadjudge" +msgstr "Sędzia Grozy" -#: Source/stores.cpp:1268 -msgid "is not available" -msgstr "jest niedostępna" +#: Source/translation_dummy.cpp:237 +msgctxt "monster" +msgid "Stareye the Witch" +msgstr "Gwiezdnooka Wiedźma" -#: Source/stores.cpp:1269 -msgid "in the shareware" -msgstr "w wersji" +#: Source/translation_dummy.cpp:238 +msgctxt "monster" +msgid "Steelskull the Hunter" +msgstr "Łowca Stalowa Czaszka" -#: Source/stores.cpp:1270 -msgid "version" -msgstr "shareware" +#: Source/translation_dummy.cpp:239 +msgctxt "monster" +msgid "Sir Gorash" +msgstr "Generał Gorasz" -#: Source/stores.cpp:1297 -msgid "Gossip" -msgstr "Plotki" +#: Source/translation_dummy.cpp:240 +msgctxt "monster" +msgid "The Vizier" +msgstr "Wezyr" -#: Source/stores.cpp:1306 -msgid "Rising Sun" -msgstr "Wschodzące Słońce" +#: Source/translation_dummy.cpp:241 +msgctxt "monster" +msgid "Zamphir" +msgstr "Zamphir" -#: Source/stores.cpp:1308 -msgid "Talk to Ogden" -msgstr "Rozmowa" +#: Source/translation_dummy.cpp:242 +msgctxt "monster" +msgid "Bloodlust" +msgstr "Żądna Krwi" -#: Source/stores.cpp:1309 -msgid "Leave the tavern" -msgstr "Wyjdź" +#: Source/translation_dummy.cpp:243 +msgctxt "monster" +msgid "Webwidow" +msgstr "Wdowa Sieci" -#: Source/stores.cpp:1320 -msgid "Talk to Gillian" -msgstr "Rozmowa" +#: Source/translation_dummy.cpp:244 +msgctxt "monster" +msgid "Fleshdancer" +msgstr "Pożeracz Zwłok" -#: Source/stores.cpp:1321 -msgid "Access Storage" -msgstr "Otwórz Skrytkę" +#: Source/translation_dummy.cpp:245 +msgctxt "monster" +msgid "Grimspike" +msgstr "Ostrze Grozy" -#: Source/stores.cpp:1331 Source/towners.cpp:205 -msgid "Farnham the Drunk" -msgstr "Pijak Farnham" +#: Source/translation_dummy.cpp:246 +msgctxt "monster" +msgid "Doomlock" +msgstr "Zaklęta Śmierć" -#: Source/stores.cpp:1333 -msgid "Talk to Farnham" -msgstr "Rozmowa" +#: Source/translation_dummy.cpp:248 Source/translation_dummy.cpp:394 +msgid "Short Sword" +msgstr "Krótki Miecz" -#: Source/stores.cpp:1334 -msgid "Say Goodbye" -msgstr "Wyjdź" +#: Source/translation_dummy.cpp:249 Source/translation_dummy.cpp:341 +msgid "Buckler" +msgstr "Puklerz" -#: Source/stores.cpp:2439 -msgid "Your gold: {:s}" -msgstr "Twoje złoto: {:s}" +#: Source/translation_dummy.cpp:250 Source/translation_dummy.cpp:435 +#: Source/translation_dummy.cpp:436 Source/translation_dummy.cpp:437 +msgid "Club" +msgstr "Maczuga" -#. 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 "" -" Ach, chcesz poznać historię naszego Króla, tak? Tragiczny upadek Leoryka " -"był bolesnym ciosem dla tej krainy. Ludzie od zawsze go wielbili, a teraz " -"panicznie się go boją. Do dzisiaj zastanawia mnie to, jak Król mógł upaść " -"tak nisko. Przecież był tak wielkodusznym władcą. Tylko najgorsze moce " -"piekieł mogły tak doszczętnie zniszczyć jego duszę..." +#: Source/translation_dummy.cpp:251 Source/translation_dummy.cpp:442 +msgid "Short Bow" +msgstr "Krótki Łuk" -#. 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 "" -"Wioska potrzebuje twojej pomocy! Kilka miesięcy temu porwano syna króla " -"Leoryka, księcia Albrechta. Nasz władca wpadł w szał i przeczesał całą " -"wioskę w poszukiwaniu zaginionego dziecka. Z każdym kolejnym dniem, Leoryk " -"coraz głębiej popadał w obłęd. Doszukiwał się winy u niewinnych mieszczan, " -"których kazał potem brutalnie stracić. Mniej niż połowa z nas przeżyła " -"jego furię...\n" -" \n" -"Królewscy rycerze i kapłani próbowali go uspokoić. Wtedy Król zwrócił się " -"przeciwko nim. Nie mając innego wyjścia, byli zmuszeni go zabić. Wraz ze " -"swoim ostatnim tchnieniem, Leoryk nałożył straszliwą klątwę na swą byłą " -"świtę. Poprzysiągł, że będą służyć mu w ciemnościach, na wieczność...\n" -" \n" -"W tym momencie sprawy przybrały gorszy obrót, niż sobie wyobrażałem! Nasz " -"były król obudził się z wiecznego snu i przewodzi teraz legionami " -"nieumarłych sług w labiryncie. Jego ciało zostało pochowane w komnacie, trzy " -"poziomy pod katedrą. Błagam, zniszcz jego przeklętą formę i pozwól mu " -"spocząć w pokoju..." +#: Source/translation_dummy.cpp:252 +msgid "Short Staff of Mana" +msgstr "Kostur Many" -#. 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 "" -"Już mówiłem, Król został pochowany trzy poziomy pod katedrą. Oczekuje w " -"mrokach podziemi na okazję, by nas zniszczyć..." +#: Source/translation_dummy.cpp:253 +msgid "Cleaver" +msgstr "Tasak" -#. 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 "" -"Klątwa już minęła, ale obawiam się, że to był dopiero początek większych " -"problemów. Na szczęście, możemy jeszcze zostać uratowani przed nadchodzącym " -"niebezpieczeństwem. Twoje zwycięstwo dobrze nam wróży. Niech prowadzi cię " -"światłość." +#: Source/translation_dummy.cpp:254 Source/translation_dummy.cpp:491 +msgid "The Undead Crown" +msgstr "Korona Nieumarłych" -#. 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 "" -"Utrata syna przerosła Króla. Próbowałem złagodzić jego obłęd, niestety " -"bezskutecznie. Tamtego dnia, nad królestwem zawisła czarna klątwa. Jeżeli " -"jednak uda ci się oswobodzić jego duszę, to może ta klątwa przeminie..." +#: Source/translation_dummy.cpp:255 Source/translation_dummy.cpp:492 +msgid "Empyrean Band" +msgstr "Pierścień Niebios" -#. 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 "" -"Nie chcę myśleć o tym, jak zginął nasz Król. Wolę zapamiętać go jako " -"życzliwego i prawego władcę. Jego śmierć była bardzo smutnym wydarzeniem." +#: Source/translation_dummy.cpp:256 +msgid "Magic Rock" +msgstr "Magiczny Kamień" -#. 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 "" -"Wykułem wiele broni i pancerzy dla świty Króla Leoryka. Sam władca dostał " -"ode mnie ogromny, dwuręczny miecz z najlepszego mithrilu, z koroną do " -"kompletu. Wciąż nie mogę uwierzyć w to, jak zginął. Musiał zostać opętany " -"przez naprawdę potężne i złowrogie siły!" +#: Source/translation_dummy.cpp:257 Source/translation_dummy.cpp:493 +msgid "Optic Amulet" +msgstr "Optyczny Amulet" -#. 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 "" -"Nie obchodzi mnie to. Słuchaj, żaden kościotrup nie będzie MOIM królem. " -"Leoryk jest królem. Słyszysz? Królem! WIWAT KRÓL!" +#: Source/translation_dummy.cpp:258 Source/translation_dummy.cpp:494 +msgid "Ring of Truth" +msgstr "Pierścień Prawdy" -#. 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 "" -"Martwi kroczący pomiędzy żywymi podążyli za przeklętym Królem. Posiadł moc, " -"dzięki której może pozyskiwać jeszcze większą ilość wojowników. Jego armia " -"nieumarłych wciąż się rozrasta. Jeśli go nie powstrzymasz, z pewnością " -"zaatakuje tę krainę i wybije wszystkich jej mieszkańców." +#: Source/translation_dummy.cpp:259 +msgid "Tavern Sign" +msgstr "Szyld Gospody" -#. 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 "" -"Słuchaj, ja tu prowadzę interesy. Nie sprzedaję informacji i nie obchodzi " -"mnie jakiś Król, który jest martwy dłużej, niż ja żyję. No, chyba, że " -"potrzebujesz wyposażenia przeciwko niemu. Wtedy pomogę." +#: Source/translation_dummy.cpp:260 Source/translation_dummy.cpp:495 +msgid "Harlequin Crest" +msgstr "Przyodziewek Arlekina" -#. 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 "" -"Ciepło życia wpełzło do mego grobowca. Za chwilę cię stłamszę i staniesz " -"się wiecznym sługą mego Pana!" +#: Source/translation_dummy.cpp:261 Source/translation_dummy.cpp:496 +msgid "Veil of Steel" +msgstr "Stalowy Woal" -#. 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 "" -"Widzę, że ta dziwna sytuacja zastanawia również ciebie. Demony bojące się " -"światła słonecznego prawdopodobnie wierzą, że drzemie w nim wielka moc. " -"Przypuszczam, że gdy zobaczyły słońce, zobrazowane na wspomnianym przez " -"ciebie szyldzie, pomyślały, że skrywa w sobie tajemniczą energię. Hmm, być " -"może nie są tak bystre, jak się obawialiśmy..." +#: Source/translation_dummy.cpp:262 +msgid "Golden Elixir" +msgstr "Złoty Eliksir" -#. 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 "" -"Jeśli mogę... chciałbym opowiedzieć ci dziwną historię. Wiem, że masz " -"ogromną wiedzę o tych potwornościach zamieszkałych w labiryncie, a " -"przydarzyło się coś czego za nic nie potrafię zrozumieć... Pewnej nocy " -"obudziło mnie jakieś dziwne skrzypienie. Kiedy wyjrzałem z sypialni, na " -"dziedzińcu zobaczyłem cienie małych stworzeń, wyglądających jak demony. Po " -"chwili uciekły, ale zdążyły ukraść szyld mojej gospody. Nie pojmuję, czemu " -"zabrały szyld, a nas zostawiły w spokoju... dziwne, nie?" +#: Source/translation_dummy.cpp:265 +msgid "Brain" +msgstr "Mózg" -#. 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 "" -"Och, nie trzeba było zwracać mi szyldu, choć dzięki tobie nie muszę kupować " -"nowego. Dobrze, zobaczmy, jak mogę ci to wynagrodzić? Hmmm, co my tu " -"mamy... o, jest! Tę czapkę zostawił po sobie pewien czarodziej, który " -"zatrzymał się tu jakiś czas temu. Może ci się do czegoś przyda." +#: Source/translation_dummy.cpp:266 +msgid "Fungal Tome" +msgstr "Atlas Grzybów" -#. 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 "" -"Wielkie Nieba! Demony biegają nocami po wiosce i grabią nasze domy - czy " -"nie ma już żadnych świętości? Mam nadzieję, że Ogden i Garda są cali. Chyba " -"przyszliby do mnie, gdyby coś im się stało..." +#: Source/translation_dummy.cpp:267 +msgid "Spectral Elixir" +msgstr "Widmowy Eliksir" -#. 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 "" -"O rety! Zabrały szyld? Musiałyśmy przespać z babcią całe to zamieszanie. " -"Dzięki światłości, że stwory nie zaatakowały gospody." +#: Source/translation_dummy.cpp:268 +msgid "Blood Stone" +msgstr "Kamień Krwi" -#. 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 "" -"Że co? Demony ukradły szyld Ogdena? Znam z plotek i widzenia większe " -"potworności, których się dopuszczały. \n" -" \n" -"Z reguły wolą wyrywać serca z piersi niż szyldy z fasad." +#: Source/translation_dummy.cpp:269 +msgid "Cathedral Map" +msgstr "Mapa Katedry" -#. 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 "" -"Wiesz co? Myślę, że ktoś ukradł ten szyld i będzie chciał za niego miliony " -"monet. Gdybym był Ogdenem... choć nie jestem, no ale gdybym był... to " -"kupiłbym nowy szyld, z jakimś ładniejszym malunkiem. Może kuflem piwa, albo " -"kiszonym ogórkiem..." +#: Source/translation_dummy.cpp:270 +msgid "Heart" +msgstr "Serce" -#. 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 "" -"Żaden śmiertelnik nie jest w stanie w pełni zrozumieć demonicznego umysłu. \n" -" \n" -"Ich dziwne zachowania mogą być częścią większego planu." +#: Source/translation_dummy.cpp:271 Source/translation_dummy.cpp:353 +msgid "Potion of Healing" +msgstr "Mikstura Leczenia" -#. 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 "" -"I co - jeszcze sugeruje, że to moja sprawka? Pewnie Griswold też trzyma " -"jego stronę. \n" -" \n" -"Słuchaj, szyldy przestałem kraść dawno temu. Nie dostaniesz wiele za kawałek " -"drewna." +#: Source/translation_dummy.cpp:272 Source/translation_dummy.cpp:355 +msgid "Potion of Mana" +msgstr "Mikstura Many" -#. 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 "" -"Hej - Ty co zabijać wszystkich! Ty zdobyć mi Czarodziejski Znak albo my " -"atakować! Ty nie odejść żywo! \n" -" \n" -"Ty zabić dużych brzydali i przynieść nam Magia. Iść za róg i drzwi, znaleźć " -"brzydali. \n" -" \n" -"Ty dać, ty odejść!" +#: Source/translation_dummy.cpp:273 Source/translation_dummy.cpp:370 +msgid "Scroll of Identify" +msgstr "Zwój Identyfikacji" -#. TRANSLATORS: Quest dialog spoken by Snotspill (Hostile) -#: Source/textdat.cpp:57 -msgid "You kill uglies, get banner. You bring to me, or else..." -msgstr "" -"Ty zabić brzydali, zdobyć znak. \n" -" \n" -"Ty mi go przynieść, bo inaczej..." +#: Source/translation_dummy.cpp:274 Source/translation_dummy.cpp:374 +msgid "Scroll of Town Portal" +msgstr "Zwój Miejskiego Portalu" -#. 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 "" -"Oddać! O taak! \n" -" \n" -"Już odejść, my potężni. \n" -" \n" -"My zniszczyć wszystkich! \n" -" \n" -"Duża magia!" +#: Source/translation_dummy.cpp:275 Source/translation_dummy.cpp:497 +msgid "Arkaine's Valor" +msgstr "Odwaga Arkaina" -#. 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 "" -"To nie wróży nam dobrze. Potwierdzają się moje najgorsze obawy. Nie chciałem " -"wierzyć starożytnym przepowiedniom, jednak właśnie zaczynają się " -"urzeczywistniać. Chyba powinienem powiedzieć ci, kim dokładnie jestem. \n" -" \n" -"Nazywam się Deckard Cain Starszy, jestem ostatnim potomkiem prastarego " -"Bractwa, chroniącego ludzkość przed odwiecznym złem. Złem, które właśnie się " -"przebudziło. \n" -" \n" -"Arcybiskup Lazarus, kiedyś najbardziej zaufany doradca Króla Leoryka, " -"zachęcił grupę prostych mieszczan do zejścia w głąb Labiryntu. Mieli " -"odnaleźć zaginionego księcia, Albrechta. Po długim czasie, z całej " -"grupy, żywych wróciło tylko kilku. \n" -" \n" -"Jestem głupcem! Powinienem domyślić się, że coś było nie tak. To pewnie sam " -"Lazarus porwał Albrechta i ukrył go we wnętrzu Labiryntu. Nie rozumiem tylko " -"dlaczego Arcybiskup poddał się mocom ciemności, i czemu wybrał akurat " -"dziecko... No chyba, że chciał złożyć je w ofierze swym mrocznym panom! \n" -" \n" -"Tak, to musiał być jego plan od samego początku! Ludzie z 'grupy " -"ratowniczej', którym udało się uwolnić, mówili o tym, że Lazarus na sam " -"koniec zniknął w najdalszych zakątkach Labiryntu. Musisz się pośpieszyć " -"i ocalić księcia przed ofiarnym ostrzem tego obłąkanego potwora!" +#: Source/translation_dummy.cpp:276 Source/translation_dummy.cpp:354 +msgid "Potion of Full Healing" +msgstr "Mikstura Pełnego Leczenia" -#. 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 "" -"Pośpiesz się! Albrecht wciąż jest w rękach Lazarusa. Książę i mieszkańcy " -"tego królestwa liczą na ciebie!" +#: Source/translation_dummy.cpp:277 Source/translation_dummy.cpp:356 +msgid "Potion of Full Mana" +msgstr "Mikstura Pełnej Many" -#. 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 "" -"To naprawdę ponura historia. Lazarus bez wątpienia spłonie w piekle za ten " -"potworny czyn. Jednakże chłopiec, którego opisujesz, nie jest naszym " -"księciem. Albrecht nadal może być w niebezpieczeństwie. A ten wielki " -"symbol, o którym mówisz, musi być portalem do samego serca Labiryntu." -" \n" -"Posłuchaj uważnie - zło z którym teraz się spotkasz, to mroczny Pan Grozy. " -"śmiertelnicy nazywają go Diablo. To właśnie jego uwięziono wewnątrz " -"Labiryntu wiele wieków temu. Obawiam się, że znowu zacznie siać grozę " -"w krainie śmiertelników. Musisz przejść przez portal i zniszczyć " -"Diablo, nim będzie za późno!" +#: Source/translation_dummy.cpp:278 Source/translation_dummy.cpp:498 +msgid "Griswold's Edge" +msgstr "Ostrze Griswolda" -#. 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 "" -"Lazarus był arcybiskupem, który zaprowadził wielu spośród mieszkańców do " -"Labiryntu. Tego dnia straciłem mnóstwo dobrych przyjaciół, a Lazarus nigdy " -"już nie powrócił. Przypuszczam, że został po drodze zabity, jak zresztą " -"większość. Mam też do ciebie jedną prośbę - nie rozmawiaj o tym dniu z " -"Farnhamem." +#: Source/translation_dummy.cpp:279 Source/translation_dummy.cpp:499 +msgid "Bovine Plate" +msgstr "Byczy Pancerz" -#. 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 "" -"Byłem zszokowany kiedy tamtej nocy usłyszałem o planie mieszkańców. " -"Myślałem, że z tych wszystkich ludzi, to Lazarus ma najwięcej zdrowego " -"rozsądku. W końcu był Arcybiskupem i zawsze troszczył się o tutejszą " -"ludność. Wróciło tylu rannych, że... nie dałem rady wszystkich uratować." +#: Source/translation_dummy.cpp:280 +msgid "Staff of Lazarus" +msgstr "Kostur Lazarusa" -#. 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 "" -"Pamiętam Lazarusa jako bardzo ciepłego i życzliwego człowieka. Przemawiał " -"na pogrzebie mojej mamy i w tym trudnym czasie wspierał mnie i babcię. " -"Codziennie modlę się o to, żeby jakimś cudem wrócił cały i zdrowy." +#: Source/translation_dummy.cpp:281 Source/translation_dummy.cpp:371 +msgid "Scroll of Resurrect" +msgstr "Zwój Wskrzeszenia" -#. 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 "" -"Byłem członkiem grupy sprowadzonej do Labiryntu przez Lazarusa. Cały czas " -"mówił coś o świętej zemście, ale gdy pojawiły się demony, to nawet nie " -"wyciągnął broni zza pasa. Zamiast tego uciekł wprost w korytarze wypełnione " -"sługusami zła!" +#: Source/translation_dummy.cpp:283 Source/translation_dummy.cpp:458 +msgid "Short Staff" +msgstr "Krótki Kostur" -#. 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 "" -"Dźgały, potem gryzły, były wszędzie. Kłamca! KłAMCA! Wszyscy zginęli! Nie " -"żyją! Słyszysz? Po prostu padali, jeden po drugim... ich krew tryskała po " -"całej podłodze... wszystko przez niego..." +#: Source/translation_dummy.cpp:284 Source/translation_dummy.cpp:395 +#: Source/translation_dummy.cpp:397 Source/translation_dummy.cpp:399 +#: Source/translation_dummy.cpp:401 Source/translation_dummy.cpp:407 +#: Source/translation_dummy.cpp:409 Source/translation_dummy.cpp:411 +#: Source/translation_dummy.cpp:413 Source/translation_dummy.cpp:415 +msgid "Sword" +msgstr "Miecz" -#. 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 "" -"Choć nie poznałam Lazarusa, wyczuwam w nim głęboki konflikt. Stanowi " -"ogromne niebezpieczeństwo. Nic nie powstrzyma go przed służbą mocom " -"ciemności, które nad nim zapanowały." +#: Source/translation_dummy.cpp:285 Source/translation_dummy.cpp:392 +#: Source/translation_dummy.cpp:393 +msgid "Dagger" +msgstr "Sztylet" -#. 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 "" -"Och, szlachetny Lazarus, ten któremu taaak dobrze szło odstraszanie demonów, " -"że straciłem nogę? Wiesz, dam ci bezpłatną radę. Porozmawiaj z Farnhamem, " -"on wszystko widział." +#: Source/translation_dummy.cpp:286 +msgid "Rune Bomb" +msgstr "Magiczna Bomba" -#. 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 "" -"To koniec twej drogi. Czeka cię już tylko gniew mego Pana! Nie udało ci " -"się uratować dziecka, ale zaraz spotkasz je w piekle!" +#: Source/translation_dummy.cpp:287 +msgid "Theodore" +msgstr "Teodor" -#. 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 "" -"Hmm, nie mam zbyt wielu informacji na ten temat. Słyszałem niegdyś o " -"tunelu, który prowadzi do podziemnego jeziora. Prawdopodobnie jest ono " -"źródłem wypełniającym nasze studnie. Niestety, nie wiem co dokładnie " -"mogło doprowadzić do skażenia naszej wody." +#: Source/translation_dummy.cpp:288 +msgid "Auric Amulet" +msgstr "Amulet Dobrobytu" -#. 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 "" -"Od zawsze staram się utrzymywać duże zapasy żywności i napitków w naszej " -"piwnicy. Niestety, odkąd miasto nie ma dostępu do świeżej wody, rezerwy " -"szybko się wyczerpują.\n" -" \n" -"Błagam, pomóż nam, jesteś naszą jedyną nadzieją." +#: Source/translation_dummy.cpp:289 +msgid "Torn Note 1" +msgstr "Podarta Notatka, cz. 1" -#. 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 "" -"Właśnie miałem cię wołać! Woda w naszych studniach stała się brudna " -"i cuchnąca. Kilku mieszkańców wioski miało po jej wypiciu objawy zatrucia, " -"a nasze rezerwy świeżej wody błyskawicznie się wyczerpują. Jestem " -"przekonany, że gdzieś w pobliżu jest przejście prowadzące do źródeł " -"zaopatrujących nasze miasto. Proszę, znajdź przyczynę tego problemu, inaczej " -"źle się to dla nas skończy." +#: Source/translation_dummy.cpp:290 +msgid "Torn Note 2" +msgstr "Podarta Notatka, cz. 2" -#. 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 "" -"Proszę, pośpiesz się. Z każdą godziną mamy coraz mniej wody zdatnej do " -"picia. \n" -" \n" -"Nie przetrwamy długo bez twojej pomocy." +#: Source/translation_dummy.cpp:291 +msgid "Torn Note 3" +msgstr "Podarta Notatka, cz. 3" -#. 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 "" -"Twierdzisz, że sama obecność demonów skaziła nasze wody? Och, podziemia " -"Tristram skrywają naprawdę wielkie zło, lecz twoja wytrwałość i odwaga dają " -"nam jeszcze nadzieję. Proszę, trzymaj ten pierścień - powinien pomóc ci " -"w pokonywaniu tych nikczemnych stworzeń." +#: Source/translation_dummy.cpp:292 +msgid "Reconstructed Note" +msgstr "Odtworzona Notatka" -#. 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 "" -"Moja babcia źle się czuje, a Garda powiedziała, że woda ze studni nie nadaje " -"się do picia. Proszę, możesz nam jakoś pomóc?" +#: Source/translation_dummy.cpp:293 +msgid "Brown Suit" +msgstr "Brązowy Strój" -#. 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 "" -"Pepin ma rację, skażona woda to ogromny problem. Próbowałem oczyścić " -"jedną z pomniejszych studni, ale wciąż niesamowicie z niej cuchnie. " -"Problem musi leżeć gdzieś u źródeł." - -#. TRANSLATORS: Quest dialog spoken by Farnham -#: Source/textdat.cpp:100 -msgid "You drink water?" -msgstr "No co ty? Wodę pijesz?!" +#: Source/translation_dummy.cpp:294 +msgid "Grey Suit" +msgstr "Szary Strój" -#. 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 "" -"Mieszkańców Tristram czeka śmierć, jeśli w studni z powrotem nie pojawi się " -"czysta woda. \n" -" \n" -"Miej na uwadze, że istotą tego problemu są demony, choć nie są " -"tego świadome." +#: Source/translation_dummy.cpp:295 Source/translation_dummy.cpp:296 +#: Source/translation_dummy.cpp:298 +msgid "Cap" +msgstr "Kaptur" -#. 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 "" -"Tym razem trzymam za ciebie kciuki. że tak powiem: biznes nie może kwitnąć, " -"kiedy nie ma wody. Lepiej szybko zbadaj sytuację!" +#: Source/translation_dummy.cpp:297 +msgid "Skull Cap" +msgstr "Szyszak" -#. 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 "" -"Komnata z ludzkich kości... Tak, wspomniano o niej w kilku pradawnych " -"tekstach, które studiowałem w bibliotekach Wschodu. Napomknięto w nich, że " -"gdy Władcy Podziemi chcieli chronić swoje wielkie skarby, tworzyli " -"specjalne sfery. Każdy kto zginął w takiej sferze próbując zdobyć skarb, " -"stawał się na wieczność jego strażnikiem. Czyż to nie potworna, a zarazem " -"dość rozsądna metoda?" +#: Source/translation_dummy.cpp:299 Source/translation_dummy.cpp:300 +#: Source/translation_dummy.cpp:302 Source/translation_dummy.cpp:306 +msgid "Helm" +msgstr "Hełm" -#. 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 "" -"Niestety, słyszę o niej pierwszy raz. Cain ma wiele ksiąg i może w którejś " -"z nich znajdzie coś na jej temat." +#: Source/translation_dummy.cpp:301 +msgid "Full Helm" +msgstr "Zamknięty Hełm" -#. 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 "" -"Brzmi niebezpiecznie. Jeżeli tam wejdziesz, to proszę, zachowaj ostrożność." +#: Source/translation_dummy.cpp:303 Source/translation_dummy.cpp:304 +msgid "Crown" +msgstr "Korona" -#. 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 "" -"Chyba nigdy nie słyszałam o takiej Komnacie. Nasz Kronikarz Cain może coś " -"o niej wiedzieć." +#: Source/translation_dummy.cpp:305 +msgid "Great Helm" +msgstr "Wielki Hełm" -#. 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 "" -"Nie słyszałem o tym miejscu. Zapytaj Caina. On gada o tak wielu rzeczach, " -"że pewnie ma już kilka odpowiedzi na to pytanie." +#: Source/translation_dummy.cpp:307 Source/translation_dummy.cpp:308 +msgid "Cape" +msgstr "Peleryna" -#. 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 "" -"No dobra, słuchaj. Jest sobie taka komnata z drewna. I jego żona, wiesz, " -"ona mówi, na drzewo... bo musiszz zaczekać. To ja mówię, że tak się nie " -"mówi, a jeśli myślisz, że za to ZAPłACĘ... to... nieważnee." +#: Source/translation_dummy.cpp:309 Source/translation_dummy.cpp:310 +msgid "Rags" +msgstr "Łach" -#. 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 "" -"Jeśli umrzesz w tym przeklętym miejscu, rozpocznie się twa wieczna służba " -"dla władców ciemności.\n" -" \n" -"Do Komnaty Kości wchodź na własną odpowiedzialność." +#: Source/translation_dummy.cpp:311 Source/translation_dummy.cpp:312 +msgid "Cloak" +msgstr "Szata" -#. 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 "" -"Ogromny i tajemniczy skarb, powiadasz? Może nawet coś bym od ciebie " -"kupił... a nie potrzebujesz może jakiegoś unikalnego i drogiego wyposażenia " -"żeby go zdobyć?" +#: Source/translation_dummy.cpp:313 Source/translation_dummy.cpp:314 +msgid "Robe" +msgstr "Habit" -#. 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 "" -"Wygląda na to, że Lazarus zwabił do Labiryntu wielu mieszkańców. " -"Powiedział im, że idą tam, aby odnaleźć zaginionego księcia. Udało mu się " -"wszystkich podburzyć i zmienić w rozszalały tłum, przez co nikt nie myślał " -"o czyhających zagrożeniach... Kiedy cała grupa została osaczona przez " -"przeraźliwe kreatury, Arcybiskup zniknął, pozostawiając swoich ludzi na " -"pewną i potworną śmierć." +#: Source/translation_dummy.cpp:315 +msgid "Quilted Armor" +msgstr "Pikowana Zbroja" -#. 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 "" -"Tak, Farnham mamrotał coś o potężnej bestii, władającej straszliwą " -"bronią. Z tego co pamiętam, nazywał ją... rzeźnikiem." +#: Source/translation_dummy.cpp:317 +msgid "Leather Armor" +msgstr "Skórzana Zbroja" -#. 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 "" -"Na światłość, słyszałem o tym podłym demonie. Wielu ze śmiałków, którzy " -"przeżyli wyprawę do katedry, nosiło znamiona jego straszliwego gniewu. " -"Nie mam pojęcia czego używa do ćwiartowania swych ofiar, ale... " -"to nie może być broń z tego świata. Zostawia ropiejące rany, których nawet " -"ja nie jestem w stanie wyleczyć. Miej się na baczności, jeżeli staniesz z " -"nim do walki..." +#: Source/translation_dummy.cpp:319 +msgid "Hard Leather Armor" +msgstr "Ciężka Skórzana Zbroja" -#. 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 "" -"Kiedy Farnham opowiadał coś o rzeźniku zabijającym ludzi, zlekceważyłam " -"to. Jednak skoro teraz ty przychodzisz z tym do mnie... to już sama nie wiem." +#: Source/translation_dummy.cpp:321 +msgid "Studded Leather Armor" +msgstr "Ćwiekowana Zbroja" -#. 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 "" -"Razem z Farnhamem na własne oczy widzieliśmy Rzeźnika. Machał tasakiem " -"wielkim jak topór, rozcinając jednym uderzeniem ciała mych przyjaciół. " -"Od tego przerażającego widoku oddzielała mnie jedynie gromadka małych, " -"skrzeczących stworów. Na szczęście, jakimś cudem, udało mi się odnaleźć " -"drogę ucieczki. Nigdy więcej nie ujrzałem tego ohydnego potwora, ale jego " -"zakrwawione oblicze nawiedza mnie do dzisiaj." +#: Source/translation_dummy.cpp:323 +msgid "Ring Mail" +msgstr "Pierścieniowa Zbroja" -#. 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 "" -"Wielki! Wielki tasak przecinający moich przyjaciół. Nie dało się zatrzymać. " -"Musiałem uciekać... zostawić ich... zamkniętych w komnacie z tyloma " -"ciałami... tylu bliskich... NIEEEEEEEE!" +#: Source/translation_dummy.cpp:324 Source/translation_dummy.cpp:326 +#: Source/translation_dummy.cpp:328 Source/translation_dummy.cpp:332 +msgid "Mail" +msgstr "Zbroja" -#. 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 "" -"Rzeźnik to brutalny potwór, któremu ból i cierpienie innych sprawiają " -"ogromną przyjemność. Spójrz do czego doprowadził Farnhama. Unicestwienie " -"tego demona na pewno uspokoiłoby mieszkańców wioski." +#: Source/translation_dummy.cpp:325 +msgid "Chain Mail" +msgstr "Kolczuga" -#. 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 "" -"Wiem o tym potworze więcej, niż ci się wydaje. Jego mali towarzysze złapali " -"mnie i pozbawili nogi, zanim Griswold zdążył mnie stamtąd wydostać. \n" -" \n" -"Mówiąc wprost - zabij go, zanim on zabije ciebie i doda twoje zwłoki do " -"swojej kolekcji." +#: Source/translation_dummy.cpp:327 +msgid "Scale Mail" +msgstr "Łuskowa Zbroja" -#. 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 "" -"Proszę... wysłuchaj mnie. Arcybiskup Lazarus... to on sprowadził nas na " -"dół, żeby odnaleźć zaginionego księcia. Ten drań wprowadził nas w pułapkę! " -"Teraz wszyscy nie żyją... zabił ich demon nazywany Rzeźnikiem. Pomścij nas! " -"Zgładź Rzeźnika, by nasze dusze mogły zaznać spokoju." +#: Source/translation_dummy.cpp:329 +msgid "Breast Plate" +msgstr "Napierśnik" -#. 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 "" -"Recytujesz bardzo ciekawy wiersz, napisany w stylu, który przypomina mi " -"jeden utwór. Poczekaj chwilę - jak on brzmiał?\n" -" \n" -"... Ciemność otaczająca ukrytych. Błysk oczu niewidoczny, słyszalny jedynie " -"dźwięk ostrych szponów dręczących biedne dusze, które oślepiono" -"na wieczność. \n" -" \n" -"Więzienie dla potępionych zwane... Salami ślepców..." +#: Source/translation_dummy.cpp:330 Source/translation_dummy.cpp:334 +#: Source/translation_dummy.cpp:336 Source/translation_dummy.cpp:338 +#: Source/translation_dummy.cpp:340 +msgid "Plate" +msgstr "Zbroja" -#. 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 "" -"Nigdy nie interesowałem się poezją. Zatrudniałem bardów, kiedy gospoda " -"jeszcze dobrze prosperowała, ale to dawne czasy.\n" -" \n" -"Co? Och, tak... emm, wiesz, lepiej będzie jak zapytasz kogoś innego." +#: Source/translation_dummy.cpp:331 +msgid "Splint Mail" +msgstr "Karacenowa Zbroja" -#. 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 "" -"Brzmi znajomo. Jeśli mnie pamięć nie myli, to natknąłem się na podobny " -"tekst podczas zapoznawania się z historiami chorób powodowanych przez " -"demony. Opisywano tam straszne miejsce, które... czekaj - chyba nie " -"zamierzasz tam iść, co?" +#: Source/translation_dummy.cpp:333 +msgid "Plate Mail" +msgstr "Płytowa Zbroja" -#. 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 "" -"Jeśli masz pytania dotyczące ślepoty, zagadaj do Pepina. Pamiętam, że dał " -"mojej babci miksturę, dzięki której zaczęła lepiej widzieć. Może tobie też " -"będzie w stanie jakoś pomóc." +#: Source/translation_dummy.cpp:335 +msgid "Field Plate" +msgstr "Turniejowa Zbroja" -#. 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 "" -"Niestety, nie słyszałem, ani nie widziałem miejsca, które mogłoby pasować do " -"twojego opisu. Porozmawiaj na ten temat z Cainem." +#: Source/translation_dummy.cpp:337 +msgid "Gothic Plate" +msgstr "Gotycka Płytowa Zbroja" -#. TRANSLATORS: Quest dialog spoken by Farnham -#: Source/textdat.cpp:150 -msgid "Look here... that's pretty funny, huh? Get it? Blind - look here?" -msgstr "Patrzaj na to... ale jaja, nie? No łapiesz? ślepy - i patrzaj!" +#: Source/translation_dummy.cpp:339 +msgid "Full Plate Mail" +msgstr "Pełna Płytowa Zbroja" -#. 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 "" -"Jest to miejsce wielkiej udręki i grozy. Poruszaj się po nim ostrożnie " -"albo zostaniesz tam dłużej niż myślisz." +#: Source/translation_dummy.cpp:342 Source/translation_dummy.cpp:344 +#: Source/translation_dummy.cpp:346 Source/translation_dummy.cpp:348 +#: Source/translation_dummy.cpp:350 Source/translation_dummy.cpp:352 +msgid "Shield" +msgstr "Tarcza" -#. 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 "" -"Zobaczmy, czy dużo ode mnie kupujesz? Nie. Czy płacisz za informacje? Nie. " -"Czy właśnie wynosisz się stąd, żeby porozmawiać z kronikarzem, który żyje " -"z gadania? Owszem." +#: Source/translation_dummy.cpp:343 +msgid "Small Shield" +msgstr "Mała Tarcza" -#. 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 "" -"Twierdzisz, że przemówił do ciebie Lachdanan? Za życia był wielkim \n" -"i szanowanym rycerzem. Przez lata wiernie służył swojemu królowi, " -"ale to już pewnie wiesz. \n" -" \n" -"Lachdanan na pewno nie poddałby się tak łatwo Klątwie rzuconej przez Króla, " -"dlatego ta historia może być prawdziwa. Na twoim miejscu, spróbowałbym mu " -"jakoś pomóc." +#: Source/translation_dummy.cpp:345 +msgid "Large Shield" +msgstr "Duża Tarcza" -#. 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 "" -"Ten dzielny wojownik, o którym mówisz, od dawna nie żyje! Nie chcę " -"rozmawiać o zmarłych przed moją karczmą, dziękuję." +#: Source/translation_dummy.cpp:347 +msgid "Kite Shield" +msgstr "Trójkątna Tarcza" -#. 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 "" -"Złoty eliksir, hm. Nigdy wcześniej nie przyrządzałem mikstury w takim " -"kolorze. Trudno mi przewidzieć, jak mógłby na ciebie zadziałać, więc lepiej " -"go NIE PRÓBUJ. Najlepiej będzie jak po prostu spełnisz ostatnią wolę " -"Lachdanana." +#: Source/translation_dummy.cpp:349 +msgid "Tower Shield" +msgstr "Ciężka Tarcza" -#. 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 "" -"Nigdy wcześniej nie słyszałam o tym Lachdananie. Przykro mi, tym razem nie " -"jestem w stanie pomóc." +#: Source/translation_dummy.cpp:351 +msgid "Gothic Shield" +msgstr "Gotycka Tarcza" -#. 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 "" -"Jeśli to faktycznie jest Lachdanan, myślę, że warto mu pomóc. Miałem " -"przyjemność go poznać. Wyglądał na osobę z natury lojalną i uczciwą. " -"Klątwa Króla Leoryka prawdopodobnie najdotkliwiej uderzyła więc właśnie w " -"niego." +#: Source/translation_dummy.cpp:357 +msgid "Potion of Rejuvenation" +msgstr "Mikstura Wzmocnienia" -#. 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 "" -" Lachdanan nie żyje. Nie ze mną te numery, wszyscy już o tym wiedzą. A " -"zmarli nie mówią. Sprawdzałem!" +#: Source/translation_dummy.cpp:358 +msgid "Potion of Full Rejuvenation" +msgstr "Mikstura Pełnego Wzmocnienia" -#. 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 "" -"W Labiryncie może być uwięzionych więcej ludzi pokroju Lachdanana. \n" -" \n" -"Wyczuwam w nim ogromne wyrzuty sumienia i honor. Pomagając mu, pomożesz Tristram." +#: Source/translation_dummy.cpp:362 +msgid "Oil" +msgstr "Olej" -#. 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 "" -"Czekaj, niech zgadnę. Pod Cainem pojawiła się gigantyczna szczelina, " -"pochłonęła go, a potem spłonął w ogniu piekielnym i nie odpowie już więcej " -"na twoje pytania. Och, nie trafiłem? Dobra, to zgaduję, że albo coś " -"kupujesz albo stąd idziesz." +#: Source/translation_dummy.cpp:363 +msgid "Elixir of Strength" +msgstr "Eliksir Siły" -#. 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 "" -"Proszę, nie krzywdź mnie. Wszystko ci wyjaśnię. Byłem niegdyś kapitanem " -"rycerzy króla Leoryka. Strzegliśmy sprawiedliwości i honoru tutejszych " -"ziem. Niestety, przez udział w jego tragicznej śmierci, spadła na nas " -"mroczna Klątwa. Chociaż moi towarzysze zaakceptowali swój koszmarny los, " -"ja, chcąc uwolnić się od przekleństwa, uciekłem z królewskiej krypty. Nie " -"potrafiłem jednak sobie pomóc...\n" -" \n" -"Słyszałem o Złotym Eliksirze, który mógłby zdjąć ze mnie klątwę. Moja dusza " -"zaznałaby w końcu spokoju. Niestety nie udało mi się go znaleźć. " -"Wciąż słabnę i zanikają we mnie resztki człowieczeństwa. Proszę, pomóż mi " -"odnaleźć Eliksir. Odwdzięczę ci się za twój wysiłek, przysięgam na swój " -"honor." +#: Source/translation_dummy.cpp:364 +msgid "Elixir of Magic" +msgstr "Eliksir Magii" -#. 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 "" -"Nie masz jeszcze Złotego Eliksiru. Boję się, że pozostanę przeklęty na " -"wieczność. Proszę, znajdź go..." +#: Source/translation_dummy.cpp:365 +msgid "Elixir of Dexterity" +msgstr "Eliksir Zręczności" -#. 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 "" -"Ma dusza została uratowana przed potępieniem, nawet nie wyobrażasz sobie ile " -"ci zawdzięczam. Dołożę wszelkich starań, aby pomóc ci z zaświatów, " -"a tymczasem - weź mój hełm. W drodze, która mnie czeka, na niewiele mi się " -"przyda. Ciebie może za to chronić przed siłami zła, które spotkasz. Niech " -"światłość zostanie twym przewodnikiem..." +#: Source/translation_dummy.cpp:366 +msgid "Elixir of Vitality" +msgstr "Eliksir Żywotności" -#. 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 "" -"Griswold mówił o legendarnym Kowadle Gniewu, którego nikomu nie udało się " -"jeszcze odnaleźć. To narzędzie rzemieślnicze zostało wytopione z " -"metalowych kości brzytwodemonów, pośród czaszek pięciu najsilniejszych " -"magów podziemi, a następnie pokryte runami mocy i chaosu. Broń i pancerz " -"wykute na tym Kowadle przesiąkają więc potęgą Chaosu, przez co tak naprawdę " -"nigdy nie wiadomo jakie magiczne właściwości otrzymają..." +#: Source/translation_dummy.cpp:367 +msgid "Scroll of Healing" +msgstr "Zwój Uzdrowienia" -#. 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 "" -"Nie sądzisz, że lepiej byłoby zapytać o to Griswolda? Ma większą wiedzę na " -"ten temat." +#: Source/translation_dummy.cpp:368 +msgid "Scroll of Search" +msgstr "Zwój Przeszukiwania" -#. 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 "" -"Mógłbym opowiedzieć coś na temat Misy Uzdrowienia albo Srebrnego Kielicha " -"Oczyszczenia, ale w tym wypadku, lepiej będzie jak udasz się do Griswolda " -"albo Caina." +#: Source/translation_dummy.cpp:369 +msgid "Scroll of Lightning" +msgstr "Zwój Błyskawic" -#. 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 "" -"Kiedy byliśmy mali, ojciec Griswolda opowiadał nam o wielkim kowadle, " -"którego używano do wykuwania potężnych broni. Mówił, że po uderzeniu " -"w nie młotem, ziemia zaczynała drżeć tak, jakby wpadała w gniew. Ilekroć " -"czuję, że ziemia się trzęsie, przypominam sobie tę historię." +#: Source/translation_dummy.cpp:372 +msgid "Scroll of Fire Wall" +msgstr "Zwój Ściany Ognia" -#. 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 "" -"Witaj! Cieszę się, gdy odwiedzają mnie ulubieni klienci! Podobno schodzisz " -"na coraz niższe poziomy Labiryntu, więc warto byłoby ci opowiedzieć pewną " -"historię. \n" -" \n" -"Jeden z mieszkańców, któremu udało się wrócić z Katedry, opowiedział mi " -"o niezwykłym kowadle, które zobaczył w trakcie ucieczki. Opis, który " -"przedstawił, przypomniał mi o legendzie z dzieciństwa. Opowiadała ona o " -"Piekielnej Kuźni, w której wykuwano potężne bronie nasycone magią. " -"Mówiła także o tym, że gdzieś na jej terenie znajduje się Kowadło Gniewu! " -"Podobno zawiera w sobie esencję świata demonów. \n" -" \n" -"Mówi się, że broń wykuta na płonącym Kowadle nasycona jest ogromną mocą. " -"Jeśli rzeczywiście było ono tym z legendy, mógłbym za jego pomocą wykuć broń " -"zdolną do zniszczenia nawet najpotężniejszego władcy Piekieł. \n" -" \n" -"Przynieś mi to kowadło i od razu biorę się do roboty!" +#: Source/translation_dummy.cpp:373 +msgid "Scroll of Inferno" +msgstr "Zwój Inferno" -#. 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 "" -"Na razie nic? Nie poddawaj się. Broń wykuta na tym kowadle znacznie " -"zwiększy twoje szanse. Wykonam ją najlepiej jak potrafię, zaufaj mi." +#: Source/translation_dummy.cpp:375 +msgid "Scroll of Flash" +msgstr "Zwój Rozbłysku" -#. 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 "" -"Nie mogę w to uwierzyć! Udało ci się, to jest Kowadło Gniewu. Doskonała " -"robota. Teraz pokażemy tym bydlakom, że broń zrobiona przez człowieka " -"potrafi spuścić dużo większy łomot niż najlepsza broń Piekieł. Weź to. " -"Niech światłość ma cię w swojej opiece." +#: Source/translation_dummy.cpp:376 +msgid "Scroll of Infravision" +msgstr "Zwój Infrawizji" -#. 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 "" -"Griswold nie może sprzedać swojego kowadła. No bo bez niego chyba nie " -"będzie słyszał. Ja to nigdy nie oddałbym mojego kowadełka!" +#: Source/translation_dummy.cpp:377 +msgid "Scroll of Phasing" +msgstr "Zwój Przenikania" -#. 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 "" -"Wewnątrz Labiryntu znajduje się wiele artefaktów o mocach przekraczających " -"pojęcie śmiertelników. Część z nich może być używana zarówno przez siły " -"Cienia jak i światłości. Zabranie tego Kowadła z podziemi może przechylić " -"szalę Wojny Grzechu w stronę światła." +#: Source/translation_dummy.cpp:378 +msgid "Scroll of Mana Shield" +msgstr "Zwój Tarczy Many" -#. 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 "" -"Jeśli znajdziesz artefakt dla Griswolda, poważnie zaszkodzisz moim " -"interesom. Ach, nie uda ci się to." +#: Source/translation_dummy.cpp:379 +msgid "Scroll of Flame Wave" +msgstr "Zwój Fali Płomieni" -#. 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 "" -"Brama Krwi i Komnata Ognia to obiekty o mistycznym pochodzeniu. " -"Gdziekolwiek znajduje się miejsce, wspomniane w tej księdze, " -"na pewno emanuje tam wielka moc. \n" -" \n" -"Legendy mówią o obsydianowym piedestale ozdobionym kośćmi i zwieńczonym " -"źródłem wrzącej krwi. Mówi się również o Kamieniach Krwi, które umożliwiają " -"otwarcie komnaty strzegącej starożytnego skarbu... \n" -" \n" -"Powiadają, że starożytny bohater Arkain, umieścił tam święty pancerz " -"Odwagi. Arkain był pierwszym śmiertelnikiem, któremu udało się przechylić " -"szalę Wojny Grzechu i przegonić legiony ciemności z powrotem do płonących " -"piekieł. \n" -" \n" -"Tuż przed jego śmiercią, zbroję ukryto w tajnej krypcie. Mówi się, że gdy " -"ten święty pancerz będzie znowu potrzebny, nadejdzie bohater i ponownie " -"przywdzieje... Odwagę. Być może to będziesz ty..." +#: Source/translation_dummy.cpp:380 +msgid "Scroll of Fireball" +msgstr "Zwój Ognistej Kuli" -#. 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 "" -"Każde dziecko słyszało historię o wojowniku Arkainie i jego niezwykłym " -"pancerzu Odwagi. Odnalezienie go zapewni ci dobrą ochronę przed złem z " -"Labiryntu." +#: Source/translation_dummy.cpp:381 +msgid "Scroll of Stone Curse" +msgstr "Zwój Petryfikacji" -#. 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 "" -"Hmm... to brzmi znajomo, ale jestem teraz tak pochłonięty nauką tworzenia " -"nowych leków i eliksirów, że za nic nie mogę sobie tego przypomnieć. " -"Wybacz..." +#: Source/translation_dummy.cpp:382 +msgid "Scroll of Chain Lightning" +msgstr "Zwój Eksplozji Błyskawic" -#. 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 "" -"Historia o magicznym pancerzu, zwanym Odwagą? Brzmi jak coś z opowieści " -"chłopców. Może zapytaj jednego z nich?" +#: Source/translation_dummy.cpp:383 +msgid "Scroll of Guardian" +msgstr "Zwój Strażnika" -#. 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 "" -"Pancerz Odwagi może przechylić szalę na twoją stronę. Wielu go już " -"szukało - w tym ja - niestety bezskutecznie. Arkain dobrze go ukrył i żeby " -"go odnaleźć potrzeba będzie raczej czegoś więcej, niż odrobiny szczęścia." +#: Source/translation_dummy.cpp:384 +msgid "Scroll of Nova" +msgstr "Zwój Novy" -#. TRANSLATORS: Quest dialog "spoken" by Farnham -#: Source/textdat.cpp:208 -msgid "Zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz..." -msgstr "Chrrrrrrrrrrrrrrrrrrrrrrrrrrrr..." +#: Source/translation_dummy.cpp:385 +msgid "Scroll of Golem" +msgstr "Zwój Golema" -#. 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 "" -"Jeśli podczas wędrówki, natrafisz na Kamienie Krwi, korzystaj z nich " -"ostrożnie. \n" -" \n" -"Droga pełna jest niebezpieczeństw, a ocalić może cię jedynie wiara we " -"własne siły." +#: Source/translation_dummy.cpp:386 +msgid "Scroll of Teleport" +msgstr "Zwój Teleportacji" -#. 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 "" -"Zamierzasz znaleźć pancerz, zwany Odwagą?\n" -" \n" -"Arkaine ukrył go tak dobrze, że nikomu się to jeszcze nie udało. Skoro moje " -"znajomości nic nie zdziałały, to wątpię, by tobie miało się powieść." +#: Source/translation_dummy.cpp:387 +msgid "Scroll of Apocalypse" +msgstr "Zwój Apokalipsy" -#. 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 "" -"Znam tylko jedną legendę, która opowiada o takim wojowniku. Historia " -"o nim znajduje się wewnątrz starożytnych kronik Wojny Grzechu..." -" \n" -"Splamiony krwią, śmiercią i tysiącami lat wojen, Marszałek Krwi wchodzi na " -"stos swych wypatroszonych ofiar. Za pomocą swego demonicznego ostrza, rzuca " -"na ludzkość tajemniczą klątwę, nawołując do stawienia się przed oblicze " -"Piekielnego Kata.\n" -" \n" -"Napisano również, że kiedyś był śmiertelnikiem walczącym w Wojnie Grzechu u " -"boku Legionów Ciemności. Stracił człowieczeństwo przez swą wiecznie " -"niezaspokojoną żądzę krwi." +#: Source/translation_dummy.cpp:388 Source/translation_dummy.cpp:389 +#: Source/translation_dummy.cpp:390 Source/translation_dummy.cpp:391 +msgid "Book of " +msgstr "Księga czaru: " -#. 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 "" -"Obawiam się, że nic nie słyszałem o tak okrutnym wojowniku. " -"Mam nadzieję, że nie spotkasz go na swojej drodze, bo... wydaje się być " -"wyjątkowo niebezpieczny." +#: Source/translation_dummy.cpp:396 +msgid "Falchion" +msgstr "Tasak" -#. 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 "" -"Cain będzie mógł powiedzieć na ten temat więcej niż ja kiedykolwiek " -"chciałbym usłyszeć." +#: Source/translation_dummy.cpp:398 +msgid "Scimitar" +msgstr "Sejmitar" -#. 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 "" -"Jeśli staniesz do walki z tak groźnym przeciwnikiem, niech czuwa nad tobą " -"światłość. Wierzę, że ci się uda." +#: Source/translation_dummy.cpp:400 +msgid "Claymore" +msgstr "Claymore" -#. 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 "" -"Postać Marszałka Krwi otaczają potworne legendy. Dobrze przygotuj się do " -"starcia z nim, gdyż jest bezwzględny i bezlitosny." +#: Source/translation_dummy.cpp:402 Source/translation_dummy.cpp:403 +msgid "Blade" +msgstr "Klinga" -#. 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 "" -"Dlaczego ty chcesz ciągle rozmawiać o Krwi? A co z kwiatkami, słoneczkiem... " -"no i na przykład tą piękną panienką przy barze. Słuchaj no - ty masz już " -"chyba obsesję!" +#: Source/translation_dummy.cpp:404 Source/translation_dummy.cpp:405 +msgid "Sabre" +msgstr "Szabla" -#. 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 "" -"Jego umiejętność posługiwania się ostrzem jest godna podziwu, w końcu od " -"tysięcy lat poruszał się wyłącznie na polu bitwy. Przykro mi, ale nie " -"potrafię przewidzieć czy wygrasz ten pojedynek." +#: Source/translation_dummy.cpp:406 +msgid "Long Sword" +msgstr "Długi Miecz" -#. 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 "" -"Nigdy nie widziałem się z tym Marszałkiem, ale ma chyba spory przerób " -"mieczy. Chętnie zaopatrzyłbym jego wojska..." +#: Source/translation_dummy.cpp:408 +msgid "Broad Sword" +msgstr "Pałasz" -#. 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 "Lata wojen sprawiły, że pragnę już tylko jednego. Twojej krwi!" +#: Source/translation_dummy.cpp:410 +msgid "Bastard Sword" +msgstr "Bękarcki Miecz" -#. 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 "" -"Griswold mówi o Kamieniu Niebios, który miał zostać zawieziony do wschodniej " -"enklawy i poddany tam dalszym badaniom. Emanował energią, która wyzwalała " -"wizje niedostępne zwykłemu człowiekowi. Nie wiem, jakie jeszcze skrywa " -"tajemnice, ale bez wątpienia warto byłoby go odnaleźć." +#: Source/translation_dummy.cpp:412 +msgid "Two-Handed Sword" +msgstr "Dwuręczny Miecz" -#. 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 "" -"Karawana zatrzymała się tu, żeby uzupełnić zapasy przed dalszą podróżą " -"na wschód. Sprzedałem im prawie całą skrzynię świeżych owoców i znakomite " -"mięso, przygotowane przez Gardę. Szkoda,że tak skończyli..." +#: Source/translation_dummy.cpp:414 +msgid "Great Sword" +msgstr "Wielki Miecz" -#. 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 "" -"Nie wiem po co im był ten kamień, ale skoro spadł z nieba, to lepiej na " -"siebie uważaj!" +#: Source/translation_dummy.cpp:416 +msgid "Small Axe" +msgstr "Ręczny Topór" -#. 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 "" -"Tak, kojarzę. Kiedyś zatrzymała się tu karawana z grupą bardzo ważnych " -"ludzi. Pamiętam, że mieli dość specyficzne akcenty i przygotowywali się " -"właśnie do dalekiej podróży. \n" -" \n" -"Myślę, że odnalezienie ich rzeczy może być teraz naprawdę trudne." +#: Source/translation_dummy.cpp:417 Source/translation_dummy.cpp:418 +#: Source/translation_dummy.cpp:419 Source/translation_dummy.cpp:421 +#: Source/translation_dummy.cpp:423 Source/translation_dummy.cpp:425 +#: Source/translation_dummy.cpp:427 +msgid "Axe" +msgstr "Topór" -#. 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 "" -"Zaczekaj chwilę - opowiem ci historię, która może cię zainteresować. Jakiś " -"czas temu przejeżdżała tu karawana zmierzająca w kierunku wschodnich " -"królestw. Rzekomo przewoziła skrawek nieba, który spadł na ziemię! " -"Niestety karawana została okradziona przez zamaskowanych jeźdźców niedaleko " -"północnej granicy naszej wioski. Przeszukałem jej pozostałości, chcąc " -"znaleźć niebiański kamień, lecz nie było już po nim śladu. Znajdź go i " -"przynieś, a wykonam dla ciebie coś naprawdę niesamowitego." +#: Source/translation_dummy.cpp:420 +msgid "Large Axe" +msgstr "Duży Topór" -#. 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 "" -"Mam nadzieję, że uda ci się znaleźć ten kamień. Jeśli mi go przyniesiesz, " -"wykonam z niego coś potężnego." +#: Source/translation_dummy.cpp:422 +msgid "Broad Axe" +msgstr "Szeroki Topór" -#. 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 "" -"Pokaż mi to - och... tak go sobie wyobrażałem. Daj mi chwilę...\n" -" \n" -"Ach, proszę bardzo. Umieściłem kawałki tego kamienia w srebrnym " -"pierścieniu, który odziedziczyłem po ojcu. Mam nadzieję, że będzie ci " -"dobrze służyć." +#: Source/translation_dummy.cpp:424 +msgid "Battle Axe" +msgstr "Bitewny Topór" -#. 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 "" -"Miałem kiedyś piękny pierścień; bardzo drogi, był trochę niebieskawy, trochę " -"zieloniutki, czerwoniutki... i srebrny też był. Nie pamiętam już co się z " -"nim stało, ale bardzo mi go brakuje..." +#: Source/translation_dummy.cpp:426 +msgid "Great Axe" +msgstr "Wielki Topór" -#. 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 "" -"W Kamieniu Niebios drzemie ogromna moc, a Griswold będzie potrafił " -"wykorzystać ją dla naszego wspólnego dobra. Wiedz, że gdyby ktoś inny " -"zlecił ci znalezienie tego kamienia, powstrzymałabym cię." +#: Source/translation_dummy.cpp:428 Source/translation_dummy.cpp:429 +#: Source/translation_dummy.cpp:431 +msgid "Mace" +msgstr "Buława" -#. 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 "" -"Jeżeli ktokolwiek mógłby zrobić coś sensownego z tego kamienia, to tylko " -"Griswold. On zna się na swojej robocie. Mimo, że czasami podkradam mu " -"klientów, to szanuję jakość jego towarów." +#: Source/translation_dummy.cpp:430 +msgid "Morning Star" +msgstr "Wekiera" -#. 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 "" -"Wiedźma Adria szuka czarnego grzyba? Wiem o nim tyle samo co o uzbrojonych " -"krowach. W moich księgach nic o tym nie wspominano. Może Pepin będzie " -"wiedział coś na jego temat." +#: Source/translation_dummy.cpp:432 +msgid "War Hammer" +msgstr "Bojowy Młot" -#. 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 "" -"Powiem tylko, że razem z Gardą, nigdy, PRZENIGDY, nie podalibyśmy naszym " -"dostojnym gościom czarnych grzybów. Jeśli Adria chce ich w swoim kotle, to " -"jej sprawa, ja nawet nie wiem gdzie ich szukać. Czarne grzyby... ohyda!" +#: Source/translation_dummy.cpp:433 +msgid "Hammer" +msgstr "Młot" -#. 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 "" -"Wiedźma powiedziała, że przyniesiesz mi mózg demona. Potrzebuję go do " -"opracowania antidotum. Dla ludzi, którzy zostali zranieni przez te plugawe " -"bestie, moje lekarstwo będzie na wagę złota. Muszę tylko rozpracować " -"działanie trucizny, którą zostali zakażeni. Wystarczy więc, że wyjmiesz " -"mózg z demona którego zabijesz i mi go dostarczysz. Będę ci za to naprawdę " -"wdzięczny." +#: Source/translation_dummy.cpp:434 +msgid "Spiked Club" +msgstr "Kolczasta Maczuga" -#. 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 "" -"Wspaniale, dokładnie taki miałem na myśli. Nie potrzebowałem go do " -"skończenia eliksiru, ale zostanie do badań. Mógłbym cię prosić o " -"zaniesienie tego wiedźmie? Chyba czeka na tę miksturę." +#: Source/translation_dummy.cpp:438 Source/translation_dummy.cpp:439 +msgid "Flail" +msgstr "Korbacz" -#. 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 "" -"Ogden zapewne ma kilka grzybów w swojej spiżarni. Może więc jego zapytaj?" +#: Source/translation_dummy.cpp:440 Source/translation_dummy.cpp:441 +msgid "Maul" +msgstr "Kafar" -#. 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 "" -"Skoro Adria go jeszcze nie ma, na pewno jest wyjątkowy. Nic o nim nie wiem, " -"ale to musi być... ogromny, nabrzmiały, gigantyczny i nadęty grzyb! No, " -"życzę udanego grzybobrania." +#: Source/translation_dummy.cpp:443 Source/translation_dummy.cpp:445 +#: Source/translation_dummy.cpp:447 Source/translation_dummy.cpp:449 +#: Source/translation_dummy.cpp:451 Source/translation_dummy.cpp:453 +#: Source/translation_dummy.cpp:455 Source/translation_dummy.cpp:457 +msgid "Bow" +msgstr "Łuk" -#. 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 "" -"Ogden przyrządza takie czarne grzyby, że jak wypiję, to od razu mną miota. " -"Słuchaj no, słuchaj... jest na to sposób - trzeba pić z umiarem!" +#: Source/translation_dummy.cpp:444 +msgid "Hunter's Bow" +msgstr "Myśliwski Łuk" -#. 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 "" -"Co my tu mamy? Ciekawe, to wygląda na księgę alchemicznych składników. " -"Jest i czarny grzyb. Powinien być ogromny, na pewno trudno go będzie " -"przeoczyć. Jeśli takowy znajdziesz, przynieś mi go, dobrze?" +#: Source/translation_dummy.cpp:446 +msgid "Long Bow" +msgstr "Długi Łuk" -#. 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 "" -"To duży i czarny grzyb. Idź go szukać, jest mi potrzebny do specjalnej " -"mikstury." +#: Source/translation_dummy.cpp:448 +msgid "Composite Bow" +msgstr "Refleksyjny Łuk" -#. 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 "" -"Tak, ten będzie idealny do mojego wywaru. Nawiasem mówiąc, uzdrowiciel " -"potrzebuje mózgu jednego z demonów, żeby móc wyleczyć ludzi zatrutych " -"jadem. Pewnie zamierza zrobić z niego antidotum. Jeśli pomożesz mu znaleźć " -"to czego szuka, poproś go o odrobinę tego eliksiru dla mnie." +#: Source/translation_dummy.cpp:450 +msgid "Short Battle Bow" +msgstr "Krótki Bitewny Łuk" -#. 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 "" -"Po co mi to przynosisz? Nie potrzebuję mózgu demona. Chcę trochę eliksiru " -"nad którym pracuje uzdrowiciel. To on potrzebuje tego cudacznego organu, " -"który właśnie trzymasz, do przyrządzenia mikstury. Wystarczy trochę " -"pomyśleć, prawda?" +#: Source/translation_dummy.cpp:452 +msgid "Long Battle Bow" +msgstr "Długi Bitewny Łuk" -#. 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 "" -"Co? Dopiero teraz przynosisz mi eliksir od uzdrowiciela? Jednak nie był " -"mi potrzebny do wywaru. Zatrzymaj go dla siebie..." +#: Source/translation_dummy.cpp:454 +msgid "Short War Bow" +msgstr "Krótki Bojowy Łuk" -#. 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 "Nie mam żadnych grzybów. Może chcesz coś bardziej użytecznego?" +#: Source/translation_dummy.cpp:456 +msgid "Long War Bow" +msgstr "Długi Bojowy Łuk" -#. 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 "Usunięty quest!" +#: Source/translation_dummy.cpp:460 +msgid "Long Staff" +msgstr "Długi Kostur" -#. 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 "Usunięty quest." +#: Source/translation_dummy.cpp:462 +msgid "Composite Staff" +msgstr "Wygięty Kostur" -#. 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 "Usunięty quest." +#: Source/translation_dummy.cpp:464 +msgid "Quarter Staff" +msgstr "Okuty Kostur" -#. 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 "Usunięty quest." +#: Source/translation_dummy.cpp:466 +msgid "War Staff" +msgstr "Bojowy Kostur" -#. 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 "Usunięty quest." +#: Source/translation_dummy.cpp:468 Source/translation_dummy.cpp:469 +#: Source/translation_dummy.cpp:470 Source/translation_dummy.cpp:471 +#: Source/translation_dummy.cpp:472 Source/translation_dummy.cpp:473 +msgid "Ring" +msgstr "Pierścień" -#. 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 "Usunięty quest." +#: Source/translation_dummy.cpp:474 Source/translation_dummy.cpp:475 +#: Source/translation_dummy.cpp:476 Source/translation_dummy.cpp:477 +msgid "Amulet" +msgstr "Amulet" -#. 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 "Usunięty quest." +#: Source/translation_dummy.cpp:478 +msgid "Rune of Fire" +msgstr "Runa Ognia" -#. 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 "" -"Cho... Chono tu. Słuchaj. Nie wiem czy ty to widzisz, ale tu jest coś " -"narysowane. To mapa." +#: Source/translation_dummy.cpp:479 Source/translation_dummy.cpp:481 +#: Source/translation_dummy.cpp:483 Source/translation_dummy.cpp:485 +#: Source/translation_dummy.cpp:487 +msgid "Rune" +msgstr "Runa" -#. 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 "Usunięty quest." +#: Source/translation_dummy.cpp:480 +msgid "Rune of Lightning" +msgstr "Runa Błyskawic" -#. 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 "Usunięty quest." +#: Source/translation_dummy.cpp:482 +msgid "Greater Rune of Fire" +msgstr "Większa Runa Ognia" -#. 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 "" -"Proooosi nie bij. Nie zabijaj. Daruj życie, a dobro cię spotka." +#: Source/translation_dummy.cpp:484 +msgid "Greater Rune of Lightning" +msgstr "Większa Runa Błyskawic" -#. 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 "" -"Dla ciebie coś robi. Prosi zostaw Garbada. On żyje i daje dobro.\n" -" \n" -"A ty weź to jako dowód obietnicy..." +#: Source/translation_dummy.cpp:486 +msgid "Rune of Stone" +msgstr "Runa Kamienia" -#. 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 "" -"Jeszcze nic! Kończy! \n" -" \n" -"Wielka moc i siła. Żyć! Żyć! \n" -" \n" -"Nie męczył, więc spełni obietnicę!" +#: Source/translation_dummy.cpp:488 +msgid "Short Staff of Charged Bolt" +msgstr "Kostur Wiązki Błyskawic" -#. 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 "To za dobre! Zbyt silne! Nie odda tak łatwo!" +#: Source/translation_dummy.cpp:489 +msgid "Arena Potion" +msgstr "Mikstura Areny" -#. 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 "" -"Co?! Co ty tutaj robisz? Przez takich jak ty nie mogę się skoncentrować. " -"Masz, weź to i daj mi pracować w spokoju. Więcej się tu nie pałętaj!" +#: Source/translation_dummy.cpp:490 +msgid "The Butcher's Cleaver" +msgstr "Tasak Rzeźnika" -#. TRANSLATORS: Quest dialog spoken by Zhar the Mad (Hostile) -#: Source/textdat.cpp:307 -msgid "Arrrrgh! Your curiosity will be the death of you!!!" -msgstr "" -"Arrrrgh! Ciekawość prowadzi do piekła!!!" +#: Source/translation_dummy.cpp:500 +msgid "The Rift Bow" +msgstr "Łuk Szybkości" -#. TRANSLATORS: Neutral dialog spoken by Cain -#: Source/textdat.cpp:308 -msgid "Hello, my friend. Stay awhile and listen..." -msgstr "Zostań na chwilę i posłuchaj..." +#: Source/translation_dummy.cpp:501 +msgid "The Needler" +msgstr "Iglica" -#. 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 "" -"Kiedy zaczniesz schodzić w głąb Labiryntu, prawdopodobnie natrafisz na " -"ukryte tam księgi o wielkiej wiedzy. \n" -" \n" -"Czytaj je uważnie, mogą powiedzieć ci o rzeczach, o których nawet ja nie słyszałem." +#: Source/translation_dummy.cpp:502 +msgid "The Celestial Bow" +msgstr "Niebiański Łuk" -#. 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 "" -"Znam wiele mitów i legend, które mogą zawierać odpowiedzi na nurtujące cię " -"pytania. Jeżeli podczas podróży obudzą się w tobie wątpliwości, przyjdź z " -"nimi do mnie. Pomogę ci w miarę możliwości." +#: Source/translation_dummy.cpp:503 +msgid "Deadly Hunter" +msgstr "Mroczny Łowca" -#. 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 "" -"Griswold to człowiek wielkich czynów. Pewnie nie mówił ci o swojej wyprawie " -"w głąb labiryntu. Uratował wtedy Wirta. Odczuł też na własnej skórze jak " -"tam jest niebezpiecznie. Poza tym jest niezwykle uzdolnionym " -"rzemieślnikiem. Jeżeli twierdzi, że zrobi wszystko, żeby ci pomóc, zaufaj mu." +#: Source/translation_dummy.cpp:504 +msgid "Bow of the Dead" +msgstr "Śmiercionośny Łuk" -#. 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 "" -"Ogden jest właścicielem Gospody pod Wschodzącym Słońcem już od prawie " -"czterech lat. Kupił ją kilka miesięcy przed tym jak wszystko poszło w " -"diabły. Wraz z żoną, Gardą, nie mają pieniędzy na wyjazd. Wszystkie " -"oszczędności zainwestowali w ułożenie sobie życia tutaj. Ogden jest dobrym " -"i bardzo odpowiedzialnym człowiekiem." +#: Source/translation_dummy.cpp:505 +msgid "The Blackoak Bow" +msgstr "Łuk z Mrocznego Dębu" -#. 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 "" -"Biedny Farnham. Jako jeden z nielicznych przetrwał zagładę grupy, która " -"tamtego strasznego dnia wkroczyła do Katedry z Lazarusem. Przeżył, ale jego " -"odwaga i rozsądek pozostały w mrokach podziemi. Teraz spokój odnajduje " -"jedynie na dnie swojego kufla. Słuchaj go uważnie, ponieważ czasami z jego " -"chaotycznego bełkotu można wyciągnąć skrawek prawdy." +#: Source/translation_dummy.cpp:506 +msgid "Flamedart" +msgstr "Płomienny Łuk" -#. 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 "" -"Adria wzbudza we mnie niepokój. Przybyła krótko po zbezczeszczeniu katedry, " -"w czasie gdy wszyscy inni uciekali. Zamieszkała w małej chatce na obrzeżach " -"miasta, zbudowanej ponoć z dnia na dzień. Ponadto, wiedźma ta posiada dostęp " -"do wielu dziwnych i tajemniczych artefaktów oraz ksiąg, których nigdy " -"wcześniej nie widziałem." +#: Source/translation_dummy.cpp:507 +msgid "Fleshstinger" +msgstr "Przebijacz" -#. 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 "" -"Historia Wirta jest przerażająca. Został wyrwany z rąk swej matki i " -"zaciągnięty do labiryntu przez małe, plugawe, dzierżące włócznie pomioty. " -"Musisz wiedzieć, że tamtego dnia porwano też wiele innych dzieci, w tym syna " -"Króla Leoryka. Rycerze z pałacu ruszyli im na ratunek, lecz od tamtej pory " -"nie dali już znaku życia. Wirta odnalazł kowal, niestety chłopiec padł już " -"ofiarą potwornych tortur tych plugawych bestii." +#: Source/translation_dummy.cpp:508 +msgid "Windforce" +msgstr "Siła Wiatru" -#. 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 "" -"Ach, Pepin. Uważam go za prawdziwego przyjaciela - chyba najbliższego, " -"jakiego mam. Czasami wydaje się być trochę zagubiony, ale nigdy jeszcze nie " -"spotkałem cieplejszej duszy. Niewielu posiada takie zasoby wiedzy czy " -"umiejętności i chce się nimi dzielić." +#: Source/translation_dummy.cpp:509 +msgid "Eaglehorn" +msgstr "Orli Róg" -#. 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 "" -"Gillian jest wspaniałą kobietą. Zajmuje w moim sercu szczególne miejsce. " -"Ma pogodną duszę, a jej uśmiech niejednego już oczarował. Mieszka w " -"gospodzie i pomaga swojej babci, która jest zbyt chora, żeby się swobodnie " -"poruszać. Choć wiem, że każdy mężczyzna w wiosce oddałby za nią życie, " -"to i tak czasami boję się o jej bezpieczeństwo." +#: Source/translation_dummy.cpp:510 +msgid "Gonnagal's Dirk" +msgstr "Sztylet Gonnagala" -#. TRANSLATORS: Neutral dialog spoken by Ogden -#: Source/textdat.cpp:327 -msgid "Greetings, good master. Welcome to the Tavern of the Rising Sun!" -msgstr "Witaj w mojej Gospodzie!" +#: Source/translation_dummy.cpp:511 +msgid "The Defender" +msgstr "Obrońca" -#. 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 "" -"Wielu śmiałków zasiadało przy stołach mej gospody i opowiedziano tutaj " -"mnóstwo historii zapijając je także mnóstwem piwa. Jedyną rzeczą, z którą " -"prawie każdy się zgadzał, była stara maksyma. Może ci się przyda. Ciało " -"możesz rozciąć, ale kości musisz zmiażdżyć." +#: Source/translation_dummy.cpp:512 +msgid "Gryphon's Claw" +msgstr "Pazur Gryfa" -#. 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 "" -"Kowal Griswold posiada ogromną wiedzę na temat broni i pancerzy. Koniecznie " -"go odwiedź, jeśli twój ekwipunek będzie wymagał naprawy." +#: Source/translation_dummy.cpp:513 +msgid "Black Razor" +msgstr "Czarna Brzytwa" -#. 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 "" -"Farnham spędza tu stanowczo za dużo czasu. Nieustannie zapija smutki tanim " -"piwem. Nie mam serca, żeby go wyprosić, bo domyślam się ile wycierpiał w " -"Labiryncie." +#: Source/translation_dummy.cpp:514 +msgid "Gibbous Moon" +msgstr "Zaćmienie Księżyca" -#. 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 "" -"Adria jest zbyt mądra jak na swój wiek i to mnie troszeczkę przeraża. \n" -" \n" -"W sumie, to bez znaczenia. Posiada niepokojąco wiele magicznych " -"przedmiotów. Znajdziesz ją w chacie, po drugiej stronie rzeki." +#: Source/translation_dummy.cpp:515 +msgid "Ice Shank" +msgstr "Ostrze Mrozu" -#. 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 "" -"Jeśli chcesz dowiedzieć się więcej o historii naszej wioski, porozmawiaj z " -"naszym kronikarzem, Cainem. On naprawdę dobrze zna jej przeszłość." +#: Source/translation_dummy.cpp:516 +msgid "The Executioner's Blade" +msgstr "Miecz Kata" -#. 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 "" -"Wirt? Ach, to mały urwis. Od zawsze wpadał w tarapaty, więc nie dziwi mnie " -"co go w końcu spotkało. \n" -" \n" -"Prawdopodobnie kręcił się w miejscu, w którym nie powinno go być." -"Szkoda mi tego chłopca, wpadł w całkowicie nieodpowiednie towarzystwo." +#: Source/translation_dummy.cpp:517 +msgid "The Bonesaw" +msgstr "Przecinacz Kości" -#. 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 "" -"Pepin jest dobrym człowiekiem. Z pewnością najbardziej wielkodusznym w " -"wiosce. Zawsze stara się pomagać ludziom w potrzebie, przez co ciężko jest " -"mu znaleźć nawet chwilę wytchnienia..." +#: Source/translation_dummy.cpp:518 +msgid "Shadowhawk" +msgstr "Orła Cień" -#. 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 "" -"Gillian, moja barmanka? Chyba tylko miłość do babci powstrzymuje ją przed " -"ucieczką.\n" -" \n" -"Nie wiem ile już razy przekonywałem ją do wyjazdu i obiecywałem zająć się " -"staruszką. Niestety, ta młoda dziewczyna ma zbyt czułe serce, żeby ją " -"opuścić." +#: Source/translation_dummy.cpp:519 +msgid "Wizardspike" +msgstr "Kolec Czarownika" -#. TRANSLATORS: Neutral dialog spoken by Pipin -#: Source/textdat.cpp:345 -msgid "What ails you, my friend?" -msgstr "Jak się czujesz?" +#: Source/translation_dummy.cpp:520 +msgid "Lightsabre" +msgstr "Miecz Światłości" -#. 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 "" -"Dokonałem ostatnio bardzo interesującego odkrycia. W przeciwieństwie do nas, " -"potwory z labiryntu mogą się leczyć bez używania mikstur czy magii. Jeśli " -"zaczniesz walczyć z jednym takim stworem upewnij się, że go zabiłeś, inaczej " -"szybko się zregeneruje." +#: Source/translation_dummy.cpp:521 +msgid "The Falcon's Talon" +msgstr "Szpon Sokoła" -#. 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 "" -"Zanim Katedra została opanowana przez te szkaradztwa, była wielką skarbnicą " -"wiedzy. Można było w niej znaleźć ogromne zbiory wartościowych ksiąg. " -"Jeżeli jeszcze jakieś znajdziesz, koniecznie je przeczytaj. Niektóre tomy " -"mogą wyjaśniać sekrety labiryntu." +#: Source/translation_dummy.cpp:522 +msgid "Inferno" +msgstr "Inferno" -#. 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 "" -"Griswold wie tak dużo o sztuce wojny, ile ja o sztuce leczenia. Jest " -"sprytnym sprzedawcą, a jego dzieła nie mają sobie równych. Och, to " -"prawdopodobnie dlatego, że jest teraz jedynym kowalem pozostałym w wiosce." +#: Source/translation_dummy.cpp:523 +msgid "Doombringer" +msgstr "Herold Zagłady" -#. 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 "" -"Cain jest prawdziwym mędrcem i przyjacielem. Przechowuje ogromną bibliotekę " -"i ma wrodzoną zdolność dostrzegania prawdziwej natury problemów. " -"Jeżeli kiedykolwiek będą cię jakieś nurtować, nie wahaj się do niego zagadać." +#: Source/translation_dummy.cpp:524 +msgid "The Grizzly" +msgstr "Niedźwiedź" -#. 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 "" -"Nawet moje umiejętności nie wystarczyły by w pełni uzdrowić Farnhama. " -"Och, potrafiłem wyleczyć jego ciało, ale nie jestem w stanie naprawić jego " -"umysłu i ducha." +#: Source/translation_dummy.cpp:525 +msgid "The Grandfather" +msgstr "Pradziad" -#. 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 "" -"Podczas gdy ja używam magii jedynie do tworzenia mikstur i eliksirów, które " -"tu sprzedaję, Adria zajmuje się prawdziwymi czarami. Wygląda na osobę " -"niezaznającą snu i zawsze ma dostęp do wielu mistycznych ksiąg oraz " -"artefaktów. Podejrzewam, że jej chata kryje więcej niż wydaje się na " -"pierwszy rzut oka, ale nigdy nie miałem okazji wejść do środka." +#: Source/translation_dummy.cpp:526 +msgid "The Mangler" +msgstr "Okulawiacz" -#. 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 "" -"Biedny Wirt. Zrobiłem dla tego dziecka wszystko co było w mojej mocy. Miał " -"paskudne rany. Wiem, że nie znosi drewnianego kołka, który zastępuje mu " -"nogę. Nikt nie powinien tak cierpieć, a już na pewno nie taki młody " -"chłopiec." +#: Source/translation_dummy.cpp:527 +msgid "Sharp Beak" +msgstr "Zakrzywiony Dziób" -#. 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 "" -"Naprawdę nie rozumiem dlaczego Ogden został w Tristram. Ma słabe nerwy, " -"ale jest inteligentnym i pracowitym człowiekiem, który poradziłby sobie " -"wszędzie. Możliwe, że bał się wyjechać po usłyszeniu wieści o morderstwach " -"w okolicy. Mógł też pozostać tu za namową żony, która nie chciała wyjeżdżać." +#: Source/translation_dummy.cpp:528 +msgid "BloodSlayer" +msgstr "Krwawy Pogromca" -#. 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 "" -"Barmanka, z gospody Ogdena, to urocza dziewczyna. Jej babcia jest bardzo " -"chora i cierpi na halucynacje. \n" -" \n" -"Uważa je za wizje, ale nie jestem w stanie ocenić czy mówi prawdę." +#: Source/translation_dummy.cpp:529 +msgid "The Celestial Axe" +msgstr "Niebiański Topór" -#. TRANSLATORS: Neutral dialog spoken by Gillian -#: Source/textdat.cpp:364 -msgid "Good day! How may I serve you?" -msgstr "Dobrego dnia! W czym mogę służyć?" +#: Source/translation_dummy.cpp:530 +msgid "Wicked Axe" +msgstr "Niegodziwy Topór" -#. 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 "" -"Moja babcia miała sen o tym, że przyjdziesz ze mną porozmawiać. " -"Miewa takie różne wizje i potrafi spojrzeć w przyszłość." +#: Source/translation_dummy.cpp:531 +msgid "Stonecleaver" +msgstr "Kamienny Tasak" -#. 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 "" -"Kobieta na skraju wioski to wiedźma! Wydaje się być miła, a jej imię, " -"Adria, brzmi dość przyjaźnie. Mimo to i tak się jej bardzo boję. \n" -"\n" -"Trzeba zdobyć się na wiele odwagi, żeby ją odwiedzić." +#: Source/translation_dummy.cpp:532 +msgid "Aguinara's Hatchet" +msgstr "Topór Aguinary" -#. 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 "" -"Nasz kowal to duma mieszkańców Tristram. Jest niezwykłym mistrzem " -"rzemiosła, wygrywającym wiele konkursów w swojej gildii. Otrzymał nawet " -"osobistą pochwałę od naszego Króla Leoryka, niech mu ziemia lekką będzie. " -"Griswold jest również wielkim bohaterem; wystarczy zapytać Caina." +#: Source/translation_dummy.cpp:533 +msgid "Hellslayer" +msgstr "Pogromca Piekieł" -#. 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 "" -"Cain jest tutejszym kronikarzem odkąd tylko pamiętam. Posiada przeogromną " -"wiedzę i możesz z nim porozmawiać praktycznie o wszystkim." +#: Source/translation_dummy.cpp:534 +msgid "Messerschmidt's Reaver" +msgstr "Rozpruwacz Messerschmidta" -#. 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 "" -"Farnham jest pijakiem, który swój brzuch wypełnia piwem, a uszy innych " -"bełkotem. \n" -" \n" -"Wiem, że Pepin i Ogden go wspierają. Jest mi jednak bardzo " -"przykro, kiedy widzę, że i tak z każdym dniem stacza się coraz bardziej." +#: Source/translation_dummy.cpp:535 +msgid "Crackrust" +msgstr "Rdzawy Łomot" -#. 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 "" -"Pepin uratował życie mojej babci i chyba nigdy nie będę w stanie mu się za " -"to odwdzięczyć. Jego umiejętność leczenia każdej choroby jest potężniejsza " -"od najmocniejszego miecza i bardziej zagadkowa niż jakikolwiek czar, który " -"potrafisz nazwać. Jeśli kiedykolwiek odniesiesz obrażenia, Pepin na pewno " -"będzie w stanie ci pomóc." +#: Source/translation_dummy.cpp:536 +msgid "Hammer of Jholm" +msgstr "Młot Jholma" -#. 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 "" -"Dorastałam z matką Wirta. Nazywała się Kanasa. Choć potwory wyrywając jej " -"syna zadały jej tylko powierzchowne rany, to tak naprawdę nigdy już nie " -"wydobrzała. Myślę, że umarła przez złamane serce. Wirt stał się podłym " -"dzieckiem, patrzącym tylko na zysk. Wiem, że dużo wycierpiał i widział " -"rzeczy, które trudno jest mi sobie nawet wyobrazić, jednak mam wrażenie, że " -"nadal spowija go jakiś mrok." +#: Source/translation_dummy.cpp:537 +msgid "Civerb's Cudgel" +msgstr "Pałka Civerba" -#. 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 "" -"Ogden wraz z żoną przygarnęli mnie i moją babcię do swojego domu. Pozwolili " -"nawet zarobić trochę złota, dając mi pracę w gospodzie. Wiele im " -"zawdzięczam. Mam nadzieję, że kiedyś opuszczę to miejsce i pomogę im " -"założyć wspaniały hotel na wschodzie." +#: Source/translation_dummy.cpp:538 +msgid "The Celestial Star" +msgstr "Niebiańska Gwiazda" -#. TRANSLATORS: Neutral dialog spoken by Griswold -#: Source/textdat.cpp:381 -msgid "Well, what can I do for ya?" -msgstr "Co mogę dla ciebie zrobić?" +#: Source/translation_dummy.cpp:539 +msgid "Baranar's Star" +msgstr "Gwiazda Baranara" -#. 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 "" -"Szukasz czegoś do walki? Spójrz na broń podstawową, taką jak ta stępiona " -"maczuga. Potrafi zdziałać cuda w walce przeciwko nieumarłym stworom tam na " -"dole. Nie ma nic lepszego do roztrzaskiwania tych spróchniałych szkieletów!" +#: Source/translation_dummy.cpp:540 +msgid "Gnarled Root" +msgstr "Spaczony Korzeń" -#. 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 "" -"Topór? Aaachh, to dobra broń, nadaje się do walki z każdym przeciwnikiem. " -"Spójrz, jak przecina powietrze, a potem wyobraź sobie, że spotyka łeb " -"tłustego demona na swojej drodze. Pamiętaj jednak, że ciężko się nim macha " -"- ale mówimy przecież o potężnych ciosach!" +#: Source/translation_dummy.cpp:541 +msgid "The Cranium Basher" +msgstr "Łamiczerep" -#. 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 "" -"Spójrz na to wykończenie, na te proporcje. Miecz we właściwych rękach jest " -"najlepszą bronią. Jego błyszczące ostrze z łatwością przebija i rozcina " -"nieumarłych. Bez problemu rani także żywych przeciwników!" +#: Source/translation_dummy.cpp:542 +msgid "Schaefer's Hammer" +msgstr "Młot Schaefera" -#. 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 "" -"Na twojej broni i pancerzu na pewno nie raz zostaną ślady po walce ze złem. " -"Przynieś je wtedy do mnie, a dzięki odrobinie pracy i gorącej kuźni sprawię, " -"że będą jak nowe!" +#: Source/translation_dummy.cpp:543 +msgid "Dreamflange" +msgstr "Poskramiacz Snów" -#. 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 "" -"Ta wiedźma, Adria, zawsze ma wszystko czego tylko zapragnie. Za to ja muszę " -"przemycać metale i narzędzia z karawan, które zatrzymują się na uboczach " -"tego nieszczęsnego miasta. Gdybym poznał choć trochę jej zaklęć... Mógłbym " -"wykonywać naprawdę nieprawdopodobne rzeczy." +#: Source/translation_dummy.cpp:544 +msgid "Staff of Shadows" +msgstr "Kostur Cieni" -#. 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 "" -"Gillian to miła dziewuszka. Szkoda, że jej babulka jest tak schorowana. " -"Gdyby nie to, zorganizowałbym im wyjazd na jednej z kupieckich karawan." +#: Source/translation_dummy.cpp:545 +msgid "Immolator" +msgstr "Kostur Ofiarny" -#. 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 "" -"Czasami odnoszę wrażenie, że Cain jest zbyt gadatliwy. Choć takie już " -"pewnie jego powołanie. Gdybym tylko potrafił kuć stal z takim zacięciem, " -"jak on potrafi paplać... Mógłbym wtedy wykonać zbroję płytową, wystarczająco " -"dobrą dla samego Cesarza!" +#: Source/translation_dummy.cpp:546 +msgid "Storm Spire" +msgstr "Szturmowa Iglica" -#. 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 "" -"Tej nocy, kiedy Lazarus sprowadził nas do labiryntu, trzymałem się blisko " -"Farnhama. To był ostatni raz kiedy widziałem Arcybiskupa. Gdyby Farnham " -"nie stał wtedy u mojego boku, mógłbym nie przeżyć. Obawiam się, że te " -"wydarzenia, okaleczyły jego duszę, tak jak mnie okulawiły. Chciałbym mu " -"pomóc, ale nie jestem w stanie stoczyć tej walki za niego." +#: Source/translation_dummy.cpp:547 +msgid "Gleamsong" +msgstr "Złudna Pieśń" -#. 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 "" -"Ten wspaniały człowiek stawia potrzeby innych ponad swoje. Nie znajdziesz " -"nigdzie osoby, która mogłaby powiedzieć coś złego o naszym uzdrowicielu." +#: Source/translation_dummy.cpp:548 +msgid "Thundercall" +msgstr "Zew Burzy" -#. 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 "" -"Ten mały wpędzi się kiedyś w jakieś poważne tarapaty... znowu. Próbowałem " -"zaciekawić go swoim zawodem - no wiesz, nauczyć go uczciwej pracy. On " -"jednak woli czerpać zyski z handlu towarami nieznanego pochodzenia. Nie " -"mogę mieć mu tego za złe, po tym co przeszedł, ale chciałbym, żeby bardziej " -"na siebie uważał." +#: Source/translation_dummy.cpp:549 +msgid "The Protector" +msgstr "Orędownik" -#. 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 "" -"Karczmarz prowadzi tu swój mały interes, ale ciężko mu cokolwiek zarobić. " -"Ledwo wiąże koniec z końcem. Dostarcza jedzenie i oferuje noclegi " -"przyjezdnym. A ci niestety często wymykają się potajemnie i nie płacą mu " -"należności. Gdyby nie zapasy zboża i suszonego mięsa w jego piwnicy, " -"większość z nas już dawno pomarłaby z głodu od czasu najazdu demonów." +#: Source/translation_dummy.cpp:550 +msgid "Naj's Puzzler" +msgstr "Zagadka Naja" -#. TRANSLATORS: Neutral dialog spoken by Farnham -#: Source/textdat.cpp:404 -msgid "Can't a fella drink in peace?" -msgstr "Napić się nie mogę w spokoju?" +#: Source/translation_dummy.cpp:551 +msgid "Mindcry" +msgstr "Płacz Umysłu" -#. 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 "" -"Dziewuszka przy barze? Och, to dopiero ślicznotka. Do tego taka miła." +#: Source/translation_dummy.cpp:552 +msgid "Rod of Onan" +msgstr "Drąg Onana" -#. 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 "" -"Dlaczego ta starucha nie zrobi dla odmiany czegoś ciekawszego. Wiem, wiem, " -"nie ma na to czasu, ale posłuchaj... Ona jest jakaś nienormalna. " -"Nigdy nie widziałem, żeby coś jadła albo piła - a nie możesz ufać komuś, " -"kto nie pije. Nawet troszeczkę." +#: Source/translation_dummy.cpp:553 +msgid "Helm of Spirits" +msgstr "Korona Dusz" -#. 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 "" -"Cain nie jest tym za kogo się podaje. Wiem, wiem, opowiada ciekawe " -"historyjki... parę z nich jest naprawdę strasznych. Albo śmiesznych... " -"Wiesz co? On wie więcej niż wie, że wie." +#: Source/translation_dummy.cpp:554 +msgid "Thinking Cap" +msgstr "Kaptur Umysłu" -#. 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 "" -"Griswold? Stary, dobry Griswold. Kocham go jak brata! Walczyliśmy razem, " -"wiesz? Dawniej, kiedy... my... Lazarus... Lazarus... Lazarus!!!" +#: Source/translation_dummy.cpp:555 +msgid "OverLord's Helm" +msgstr "Hełm Nadzorcy" -#. 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 "" -"Hehehe, lubię Pepina. Wiesz, on naprawdę robi co może. Słuchaj, musisz go " -"poznać. To miły gość, który zawsze pomaga ludziom w potrzebie. " -"Hej, to chyba tak jak ty, nie? Dawniej też taki byłem..." +#: Source/translation_dummy.cpp:556 +msgid "Fool's Crest" +msgstr "Korona Błazna" -#. 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 "" -"Wirt jest dzieckiem, które ma więcej problemów ode mnie, a ja wiem " -"wszystko o problemach. Posłuchaj - ten dzieciak robi świetne interesy, " -"ale on też tam był, wiesz? Stracił nogę! Musi teraz chodzić na jakimś kołku. " -"To przykre, naprawdę przykre..." +#: Source/translation_dummy.cpp:557 +msgid "Gotterdamerung" +msgstr "Zmierzch Bogów" -#. 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 "" -"Ogden to najlepszy człowiek w mieście. Jego żona chyba za mną nie przepada, " -"ale dopóki uzupełnia beczki, to da się ją jakoś znieść. Wygląda na to, że " -"to ja spędzam najwięcej czasu z Ogdenem, ale on jest dla mnie taki dobry..." +#: Source/translation_dummy.cpp:558 +msgid "Royal Circlet" +msgstr "Królewski Diadem" -#. 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 "" -"Heeejj chcę zzi coś powiedzieć, wiem o nich wszystko. Są moją " -"specjalnością. Te tutaj są najlepsze... naaaajleeeepsze! Inne piwa są nic " -"warte odkąd te głupie kundle..." +#: Source/translation_dummy.cpp:559 +msgid "Torn Flesh of Souls" +msgstr "Strzępek Duszy" -#. 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 "" -"Nikt mnie nie chce słu... słuchać. Gdzieś - nie jestem pewien - ale gdzieś " -"pod kościołem leżą całe stosy złota. Błyszczą i brzęczą. I tylko czekają, " -"coby ktoś je zebrał." +#: Source/translation_dummy.cpp:560 +msgid "The Gladiator's Bane" +msgstr "Zguba Gladiatora" -#. 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 "" -"Wiem, że masz własne zdanie i wiem, że i tak mi nie uwierzysz, ale ta broń, " -"którą walczysz - ona jest za słaba na tych wielkich bydlaków! Nie obchodzi " -"mnie co mówi Griswold, ale już się nie robi takich rzeczy, co za dawnych " -"czasów..." +#: Source/translation_dummy.cpp:561 +msgid "The Rainbow Cloak" +msgstr "Szata Tęczy" -#. 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 "" -"Gdybym był tobą... no ale nie jestem... no ale gdybym był, to wtedy bym " -"wszystko sprzedał i stąd wyjechał... Ten chłopak, o tam... On zawsze ma " -"dobry towar, ale jak nie masz złota... to ci nawet tego nie pokaże." +#: Source/translation_dummy.cpp:562 +msgid "Leather of Aut" +msgstr "Zbroja Giermka" -#. TRANSLATORS: Neutral dialog spoken by Adria -#: Source/textdat.cpp:427 -msgid "I sense a soul in search of answers..." -msgstr "Wyczuwam duszę szukającą odpowiedzi..." +#: Source/translation_dummy.cpp:563 +msgid "Wisdom's Wrap" +msgstr "Szata Mądrości" -#. 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 "" -"Mądrość jest nabyta, nie nadana. Dlatego pochłaniaj wiedzę z każdej " -"odnalezionej księgi. Jeśli będzie to wiedza, którą już posiadasz to " -"pamiętaj, że można ją zawsze poszerzyć." +#: Source/translation_dummy.cpp:564 +msgid "Sparking Mail" +msgstr "Iskrząca Kolczuga" -#. 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 "" -"Najpotężniejsza moc często trwa najkrócej. Zapewne natkniesz się na " -"starożytne formuły, o wielkiej sile, zapisane na zwojach pergaminów. " -"Moc tych zwojów pozwala na wykorzystanie ich zarówno przez ucznia, jak i " -"mistrza, z identycznym skutkiem. Wiedz, że trzeba je głośno wyrecytować, a " -"zapisane na nich słowa nie mogą być zachowane w umyśle. Pamiętaj... " -"użyć można ich tylko raz, dlatego wykorzystuj je z rozwagą." +#: Source/translation_dummy.cpp:565 +msgid "Scavenger Carapace" +msgstr "Pancerz Ścierwojada" -#. 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 "" -"Choć żar słońca jest niezmierzony, niewinny płomień świecy stwarza większe " -"zagrożenie. żadnej energii, nie ważne jak wielkiej, nie wykorzystasz bez " -"odpowiedniego jej skupienia. Magiczna moc zaczarowanych kosturów może być " -"wielokrotnie uzupełniana, co umożliwi dalsze rzucanie zaklęć. Posiadam " -"umiejętność przywracania im energii - lecz wiedz, że wszystko ma swoją cenę." +#: Source/translation_dummy.cpp:566 +msgid "Nightscape" +msgstr "Mroczny Uciekinier" -#. 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 "" -"Granicę mądrości wyznacza społeczeństwo. Jeśli nie uda ci się rozszyfrować " -"znalezionych ksiąg lub pergaminów, nie wahaj się przynieść ich do mnie. " -"Spróbuję je odczytać i podzielę się odkryciami." +#: Source/translation_dummy.cpp:567 +msgid "Naj's Light Plate" +msgstr "Lekki Pancerz Naja" -#. 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 "" -"Dla człowieka znającego jedynie żelazo, nie ma większej magii niż stal. " -"Kowal Griswold jest większym czarodziejem, niż mu się wydaje. Jego " -"umiejętność spajania ognia z metalem jest zaprawdę niezrównana." +#: Source/translation_dummy.cpp:568 +msgid "Demonspike Coat" +msgstr "Demoniczny Pancerz" -#. 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 "" -"Tak jak zepsucie korzysta z zalet obłudy, tak cnotliwość korzysta z mocy " -"lojalności. Młoda Gillian ma dobre serce. Potrzeby jej babci są dla niej " -"ważniejsze od własnych. Boi się mnie, ale tylko dlatego, że mnie nie rozumie." +#: Source/translation_dummy.cpp:569 +msgid "The Deflector" +msgstr "Bariera" -#. 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 "" -"Skrzynia otwarta w ciemnościach nie skrywa większych bogactw niż otwarta w " -"blasku światła. Kronikarz Cain jest tajemnicą, ale tylko dla zaślepionych. " -"Jego wiedza o tym, co spoczywa pod katedrą jest dużo większa, niż mu się " -"wydaje." +#: Source/translation_dummy.cpp:570 +msgid "Split Skull Shield" +msgstr "Tarcza Czaszki" -#. 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 "" -"Im głębszą wiarę pokładasz w jednym człowieku, tym niżej możesz upaść. " -"Farnham zatracił swą duszę, ale nie przez demony. Stało się to po zdradzie " -"Arcybiskupa Lazarusa, gdy zobaczył cierpienie swoich najbliższych " -"przyjaciół. Pamiętaj, że Farnham posiada ogromną wiedzę, musisz tylko " -"oddzielić ziarno od plew." +#: Source/translation_dummy.cpp:571 +msgid "Dragon's Breach" +msgstr "Smocza Osłona" -#. 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 "" -"Dłoń, serce i rozum, tworząc idealną harmonię, potrafią dokonywać cudów. " -"Uzdrowiciel Pepin dostrzega w ciele to, czego nawet ja nie potrafię. Jego " -"zdolność leczenia chorych i rannych wzbogacona jest o umiejętność tworzenia " -"mikstur i eliksirów. Będzie dla ciebie wspaniałym sojusznikiem." +#: Source/translation_dummy.cpp:572 +msgid "Blackoak Shield" +msgstr "Tarcza z Mrocznego Dębu" -#. 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 "" -"Nie potrafimy wejrzeć w przyszłość, lecz kiedy nadejdzie, dzierżyć ją będą " -"dzieci. W duszy Wirta można dostrzec mrok, jednak nie stanowi on zagrożenia " -"dla miasta ani mieszkańców. Jego kontakty z łotrami i sekretnymi gildiami z " -"pobliskich wiosek zapewniają mu dostęp do wielu przedmiotów, które są rzadko " -"spotykane w Tristram. Możesz potępiać jego metody, ale usługi, które " -"świadczy, mogą okazać się pomocne w walce z nadchodzącą ciemnością." +#: Source/translation_dummy.cpp:573 +msgid "Holy Defender" +msgstr "Święty Obrońca" -#. 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 "" -"Domu nie tworzą gliniane ściany i dach pokryty strzechą. " -"Karczmarz Ogden spełnia więcej potrzeb tutejszych mieszkańców, niż są tego " -"świadomi. Zapewnia schronienie Gillian i jej babci, podtrzymuje na duchu " -"Farnhama i przypomina wszystkim o czasach świetności Tristram. Jego gospoda " -"i dostępne tam proste przyjemności są dla wielu ludzi przebłyskiem dawnych i " -"spokojnych czasów. Te wspomnienia rozbudzają w nich nadzieję na twój sukces." +#: Source/translation_dummy.cpp:574 +msgid "Stormshield" +msgstr "Tarcza Burzy" -#. TRANSLATORS: Neutral dialog spoken by Wirt -#: Source/textdat.cpp:450 -msgid "Pssst... over here..." -msgstr "Hej... Tu jestem..." +#: Source/translation_dummy.cpp:575 +msgid "Bramble" +msgstr "Cierń" -#. 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 "" -"Nie każdy w Tristram potrzebuje rzeczy z Labiryntu. Ja też ich nie chcę, " -"choć pewnie trudno w to uwierzyć. \n" -" \n" -"Często tylko ty będziesz w stanie poznać ich prawdziwą naturę." +#: Source/translation_dummy.cpp:576 +msgid "Ring of Regha" +msgstr "Pierścień Regha" -#. 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 "" -"Nie wierz we wszystko, co mówi ten pijak. " -"Nadmiar alkoholu zdążył już przyćmić jego wzrok i zdrowy rozsądek." +#: Source/translation_dummy.cpp:577 +msgid "The Bleeder" +msgstr "Krwotok" -#. 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 "" -"Moje towary nie pochodzą z Tristram. Jestem importerem jakościowych dóbr. " -"Jeżeli chcesz handlować szmelcem, udaj się do Griswolda, Pepina albo tej " -"wiedźmy, Adrii. Założę się, że wezmą wszystko, co im przyniesiesz..." +#: Source/translation_dummy.cpp:578 +msgid "Constricting Ring" +msgstr "Pierścień Uciśnienia" -#. 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 "" -"Przypuszczam, że zawdzięczam swoje życie kowalowi, o ile można to jeszcze " -"nazywać... życiem. Jasne, Griswold zaproponował mi naukę rzemiosła w " -"kuźni. Mógłbym się od niego wiele nauczyć, ale w ten sposób nie zdobędę " -"wystarczającej ilości pieniędzy do... dobrze, powiedzmy, że moje plany " -"wymagają większych nakładów finansowych." +#: Source/translation_dummy.cpp:579 +msgid "Ring of Engagement" +msgstr "Pierścień Zobowiązania" -#. 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 "" -"Gdybym był trochę starszy, obsypałbym ją wszystkimi skarbami świata. Mogę " -"cię zapewnić, że w moje ręce wpadają naprawdę piękne rzeczy. Gillian jest " -"prześliczną dziewczyną, która powinna opuścić Tristram póki jeszcze jest " -"bezpiecznie. Hmmm... może zabiorę ją ze sobą, kiedy będę odjeżdżał..." +#: Source/translation_dummy.cpp:580 +msgid "Giant's Knuckle" +msgstr "Pierścień Olbrzyma" -#. 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 "" -"Cain wie zbyt dużo. Przeraża mnie nawet bardziej niż ta kobieta za rzeką. " -"Wciąż mówi jakie to mam szczęście, że przeżyłem oraz że w legendzie " -"przepowiedziano moją historię. Chyba postradał zmysły." +#: Source/translation_dummy.cpp:581 +msgid "Mercurial Ring" +msgstr "Pierścień Merkurego" -#. 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 "" -"Farnham - no, to jest facet z poważnymi problemami, i uwierz mi, wiem co " -"oznaczają poważne problemy. Za bardzo komuś zaufał. Lazarus sprowadził go " -"do samej krainy śmierci. Och, wiem co tam jest, więc nie próbuj nawet mówić " -"mi o swoich planach zniszczenia zła, które miota się po labiryncie. Lepiej " -"uważaj na nogi..." +#: Source/translation_dummy.cpp:582 +msgid "Xorine's Ring" +msgstr "Pierścień Xorina" -#. 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 "" -"Stary, dobry Pepin... może okazać się pomocny... dopóki nie będzie trzeba " -"ci z powrotem czegoś przyszywać. \n" -" \n" -"Gdybym tylko miał wtedy trochę jego eliksirów, to może nie straciłbym nogi..." +#: Source/translation_dummy.cpp:583 +msgid "Karik's Ring" +msgstr "Pierścień Karika" -#. 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 "" -"Adria naprawdę mnie niepokoi. No jasne, Cain też potrafi przerazić tym co " -"mówi o przeszłości, ale ta wiedźma - potrafi w nią wejrzeć. Zawsze znajduje " -"sposób na zdobycie wszystkiego, czego tylko zapragnie. Adria dostaje w ręce " -"takie rzeczy, których nie widziałem nawet w czasie trwania Największych " -"Targów!" +#: Source/translation_dummy.cpp:584 +msgid "Ring of Magma" +msgstr "Pierścień Magmy" -#. 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 "" -"Ogden zachowuje się jak wariat. Mógłbym wywieść go z miasta za rozsądną " -"cenę, lecz on uparcie próbuje uratować swoją głupią gospodę. Przynajmniej " -"daje Gillian miejsce pracy, a jego żona Garda piecze wyśmienite ciasta..." +#: Source/translation_dummy.cpp:585 +msgid "Ring of the Mystics" +msgstr "Pierścień Mistyków" -#. 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 "" -"Za Salą Bohaterów znajduje się Komnata Kości. Wieczne potępienie czeka na " -"każdego, kto odważy się wykraść złożone w niej skarby. Tak oznajmił Pan " -"Grozy, tak też zostało zapisane." +#: Source/translation_dummy.cpp:586 +msgid "Ring of Thunder" +msgstr "Pierścień Burzy" -#. 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 "" -"... I tak, zamknięta za Bramą Krwi i Komnatą Ognia, Odwaga oczekuje na " -"przebudzenie przez Bohatera światłości..." +#: Source/translation_dummy.cpp:587 +msgid "Amulet of Warding" +msgstr "Amulet Ochrony" -#. 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 "" -"Nie pojmujesz mych wizji,\n" -"Nie rozróżniasz w nich fikcji.\n" -"Odwróć wzrok, już zniknęły,\n" -"Blask światła zniknął naraz.\n" -"Lecz swą pieśnią cię tknęły,\n" -"Widzisz już baśni obraz.\n" -"Kłębi się w sennych marach,\n" -"W Sal ślepców korytarzach." +#: Source/translation_dummy.cpp:588 +msgid "Gnat Sting" +msgstr "Ukąszenie Komara" -#. 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 "" -"Zbrojownie Piekła stały się domem Marszałka Krwi. Gdziekolwiek przejdzie, " -"pozostawia za sobą tysiące okaleczonych ciał. Mordował zarówno aniołów, jak " -"i ludzi. Składa niekończące się ofiary Panom Ciemności, pragnącym tylko " -"jednego. Krwi..." +#: Source/translation_dummy.cpp:589 +msgid "Flambeau" +msgstr "Żarliwiec" -#. 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 "" -"Daj świadectwo prawdom tu zapisanym, gdyż są one ostatnim dziedzictwem " -"horadrimów. Poza naszym ojczystym światem trwa wojna pomiędzy utopijnym " -"Królestwem Niebios i pogrążonymi w chaosie Płonącymi Piekłami. Wojnę tę " -"nazywa się Wielkim Konfliktem. Zaczęła się jeszcze przed powstaniem gwiazd " -"na niebie. Siły światła i ciemności nieustannie walczą w niej o przejęcie " -"kontroli nad wszelkim stworzeniem, lecz żadna ze stron nigdy nie uzyskała " -"znaczącej przewagi." +#: Source/translation_dummy.cpp:590 +msgid "Armor of Gloom" +msgstr "Zbroja Posępności" -#. 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 "" -"Daj świadectwo prawdom tu zapisanym, gdyż są one ostatnim dziedzictwem " -"horadrimów. Kiedy Odwieczny Konflikt pomiędzy Królestwem Niebios i Płonącymi " -"Piekłami przeniósł się do świata śmiertelników, nazwano go Wojną Grzechu. " -"Anioły i demony zaczęły krążyć pomiędzy ludźmi w ukryciu, walcząc pod inną " -"postacią. Kilku potężniejszym śmiałkom udało się sprzymierzyć ze stronami " -"konfliktu i wpłynąć na przebieg Wojny Grzechu." +#: Source/translation_dummy.cpp:591 +msgid "Blitzen" +msgstr "Przebłysk" -#. 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 "" -"Daj świadectwo prawdom tu zapisanym, gdyż są one ostatnim dziedzictwem " -"horadrimów. Około trzystu lat temu, dowiedzieliśmy się, że nasz świat " -"nawiedziła Mroczna Trójca z Płonących Piekieł." -"Trzej bracia zapanowali na dekady nad ziemiami Wschodu, a miejscowa ludność " -"żyła tam w panicznym strachu. Nasz zakon - horadrimowie - został stworzony " -"przez tajemną grupę magów, by odnaleźć i raz na zawsze pojmać Trójcę Zła.\n" -" \n" -"Dawni Horadrimowie schwytali dwóch \n" -"z Trójcy za pomocą potężnych artefaktów znanych jako Kamienie Dusz\n" -"i zakopali je głęboko pod piaskami wschodnich pustkowi.\n" -"Trzeci Zły zdołał uciec na zachód. Wtedy podążył za nim nasz zakon. \n" -"Trzeci Zły - znany jako Diablo, Pan Grozy - został ostatecznie schwytany, a " -"jego esencja została umieszczona w Kamieniu Dusz i ukryta w tym Labiryncie.\n" -"\n" -"Strzeż się, Kamień Dusz musi być trzymany z dala od niepowołanych. " -"Gdyby Diablo zdołał się z niego wydostać, próbowałby przejąć ciało, którym " -"mógłby z łatwością zawładnąć. Najlepszym celem dla osłabionego demona byłoby " -"dziecko lub starzec." +#: Source/translation_dummy.cpp:592 +msgid "Thunderclap" +msgstr "Uderzenie Pioruna" -#. 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 "" -"Dokonało się. W Płonących Piekłach nastał czas wielkiej rewolucji, nazywanej " -"Mrocznym Wygnaniem. Pomniejsze Zła obaliły Wielką Trójcę i wygnały ich dusze " -"do krainy śmiertelników. Podczas nieobecności trzech braci, demony Belial, " -"Pan Kłamstwa, i Azmodan, Pan Grzechu, zaczęły walczyć o władzę. Cała kraina " -"podzieliła się na wyznawców Beliala i Azmodana, a siły Niebios nieustannie " -"próbowały rozbić Wrota Piekieł." +#: Source/translation_dummy.cpp:593 +msgid "Shirotachi" +msgstr "Shirotachi" -#. 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 "" -"Wiele demonów przybyło do krainy śmiertelników w poszukiwaniu Trzech Braci. " -"Ich poczynania na ziemi śledziły anioły, które rozpoczęły polowania na nie w " -"wielkich miastach Wschodu. Anioły sprzymierzyły się ze śmiertelnikami i " -"zawarły pakt z tajemniczym zakonem horadrimów. Jego członkowie szybko " -"stali się znakomitymi łowcami zła i błyskawicznie zdobyli w krainie podziemi " -"wielu wrogów." +#: Source/translation_dummy.cpp:594 +msgid "Eater of Souls" +msgstr "Pożeracz Dusz" -#. 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 "" -"Dokonało się. Duchy Wielkiej Trójcy zostały wygnane do świata " -"śmiertelników. Po trwającym dekady sianiu spustoszenia na Wschodzie, " -"zostali wytropieni przez zakon śmiertelników. Horadrimowie użyli artefaktów " -"nazywanych Kamieniami Dusz i umieścili w nich esencje Mefisto, Pana " -"Nienawiści i jego brata Baala, Pana Zniszczenia. Najmłodszy brat, Diablo - " -"Pan Grozy, uciekł na zachód.\n" -" \n" -"Ostatecznie Horadrimowie schwytali do Kamienia Dusz również Diablo, a " -"następnie ukryli go pod starożytną, zapomnianą katedrą. Tam oto Pan Grozy " -"pozostaje uśpiony i oczekuje na przebudzenie. Wiedzcie o tym, że potrzebuje " -"młodego i energicznego ciała, naiwnej osoby, którą łatwo będzie opętać. " -"Odrodzi się wtedy, by uwolnić swych Braci i ponownie wzniecić ogień Wojny " -"Grzechu..." +#: Source/translation_dummy.cpp:595 +msgid "Diamondedge" +msgstr "Diamentowe Ostrze" + +#: Source/translation_dummy.cpp:596 +msgid "Bone Chain Armor" +msgstr "Kościana Kolczuga" + +#: Source/translation_dummy.cpp:597 +msgid "Demon Plate Armor" +msgstr "Łuska Demona" + +#: Source/translation_dummy.cpp:598 +msgid "Acolyte's Amulet" +msgstr "Amulet Akolity" + +#: Source/translation_dummy.cpp:599 +msgid "Gladiator's Ring" +msgstr "Pierścień Gladiatora" + +#: Source/translation_dummy.cpp:600 +msgid "Tin" +msgstr "Blaszany" + +#: Source/translation_dummy.cpp:601 +msgid "Brass" +msgstr "Mosiężny" + +#: Source/translation_dummy.cpp:602 +msgid "Bronze" +msgstr "Brązowy" + +#: Source/translation_dummy.cpp:603 +msgid "Iron" +msgstr "Żelazny" + +#: Source/translation_dummy.cpp:604 +msgid "Steel" +msgstr "Stalowy" + +#: Source/translation_dummy.cpp:605 +msgid "Silver" +msgstr "Srebrny" + +#: Source/translation_dummy.cpp:607 +msgid "Platinum" +msgstr "Platynowy" + +#: Source/translation_dummy.cpp:608 +msgid "Mithril" +msgstr "Mithrilowy" + +#: Source/translation_dummy.cpp:609 +msgid "Meteoric" +msgstr "Meteorytowy" + +#: Source/translation_dummy.cpp:611 +msgid "Strange" +msgstr "Nietypowy" + +#: Source/translation_dummy.cpp:612 +msgid "Useless" +msgstr "Zbędny" + +#: Source/translation_dummy.cpp:613 +msgid "Bent" +msgstr "Wygięty" + +#: Source/translation_dummy.cpp:614 +msgid "Weak" +msgstr "Słaby" + +#: Source/translation_dummy.cpp:615 +msgid "Jagged" +msgstr "Pęknięty" + +#: Source/translation_dummy.cpp:616 +msgid "Deadly" +msgstr "Zabójczy" + +#: Source/translation_dummy.cpp:617 +msgid "Heavy" +msgstr "Ciężki" + +#: Source/translation_dummy.cpp:618 +msgid "Vicious" +msgstr "Wściekły" + +#: Source/translation_dummy.cpp:619 +msgid "Brutal" +msgstr "Okrutny" + +#: Source/translation_dummy.cpp:620 +msgid "Massive" +msgstr "Ogromny" + +#: Source/translation_dummy.cpp:621 +msgid "Savage" +msgstr "Dziki" + +#: Source/translation_dummy.cpp:622 +msgid "Ruthless" +msgstr "Bezwzględny" + +#: Source/translation_dummy.cpp:623 +msgid "Merciless" +msgstr "Bezlitosny" + +#: Source/translation_dummy.cpp:624 +msgid "Clumsy" +msgstr "Nieudany" + +#: Source/translation_dummy.cpp:625 +msgid "Dull" +msgstr "Tępy" + +#: Source/translation_dummy.cpp:626 +msgid "Sharp" +msgstr "Ostry" + +#: Source/translation_dummy.cpp:627 Source/translation_dummy.cpp:637 +msgid "Fine" +msgstr "Świetny" + +#: Source/translation_dummy.cpp:628 +msgid "Warrior's" +msgstr "Wojowniczy" + +#: Source/translation_dummy.cpp:629 +msgid "Soldier's" +msgstr "Giermkowski" + +#: Source/translation_dummy.cpp:630 +msgid "Lord's" +msgstr "Lordowski" + +#: Source/translation_dummy.cpp:631 +msgid "Knight's" +msgstr "Rycerski" + +#: Source/translation_dummy.cpp:632 +msgid "Master's" +msgstr "Wyborny" + +#: Source/translation_dummy.cpp:633 +msgid "Champion's" +msgstr "Przewyborny" + +#: Source/translation_dummy.cpp:634 +msgid "King's" +msgstr "Królewski" + +#: Source/translation_dummy.cpp:635 +msgid "Vulnerable" +msgstr "Delikatny" + +#: Source/translation_dummy.cpp:636 +msgid "Rusted" +msgstr "Stary" + +#: Source/translation_dummy.cpp:638 +msgid "Strong" +msgstr "Silny" + +#: Source/translation_dummy.cpp:639 +msgid "Grand" +msgstr "Wielki" + +#: Source/translation_dummy.cpp:640 +msgid "Valiant" +msgstr "Bitny" + +#: Source/translation_dummy.cpp:641 +msgid "Glorious" +msgstr "Chwalebny" + +#: Source/translation_dummy.cpp:642 +msgid "Blessed" +msgstr "Zbawienny" + +#: Source/translation_dummy.cpp:643 +msgid "Saintly" +msgstr "Uświęcony" + +#: Source/translation_dummy.cpp:644 +msgid "Awesome" +msgstr "Cudowny" + +#: Source/translation_dummy.cpp:646 +msgid "Godly" +msgstr "Boski" + +#: Source/translation_dummy.cpp:647 +msgid "Red" +msgstr "Czerwony" + +#: Source/translation_dummy.cpp:648 Source/translation_dummy.cpp:649 +msgid "Crimson" +msgstr "Szkarłatny" + +#: Source/translation_dummy.cpp:650 +msgid "Garnet" +msgstr "Bordowy" + +#: Source/translation_dummy.cpp:651 +msgid "Ruby" +msgstr "Rubinowy" + +#: Source/translation_dummy.cpp:652 +msgid "Blue" +msgstr "Niebieski" + +#: Source/translation_dummy.cpp:653 +msgid "Azure" +msgstr "Lazurowy" + +#: Source/translation_dummy.cpp:654 +msgid "Lapis" +msgstr "Lapisowy" + +#: Source/translation_dummy.cpp:655 +msgid "Cobalt" +msgstr "Kobaltowy" + +#: Source/translation_dummy.cpp:656 +msgid "Sapphire" +msgstr "Szafirowy" + +#: Source/translation_dummy.cpp:657 +msgid "White" +msgstr "Biały" + +#: Source/translation_dummy.cpp:658 +msgid "Pearl" +msgstr "Perłowy" + +#: Source/translation_dummy.cpp:659 +msgid "Ivory" +msgstr "Kościany" + +#: Source/translation_dummy.cpp:660 +msgid "Crystal" +msgstr "Brylantowy" + +#: Source/translation_dummy.cpp:661 +msgid "Diamond" +msgstr "Diamentowy" + +#: Source/translation_dummy.cpp:662 +msgid "Topaz" +msgstr "Topazowy" + +#: Source/translation_dummy.cpp:663 +msgid "Amber" +msgstr "Cytrynowy" + +#: Source/translation_dummy.cpp:664 +msgid "Jade" +msgstr "Agatowy" + +#: Source/translation_dummy.cpp:665 +msgid "Obsidian" +msgstr "Obsydianowy" + +#: Source/translation_dummy.cpp:666 +msgid "Emerald" +msgstr "Szmaragdowy" + +#: Source/translation_dummy.cpp:667 +msgid "Hyena's" +msgstr "Wilczy" + +#: Source/translation_dummy.cpp:668 +msgid "Frog's" +msgstr "Żabi" + +#: Source/translation_dummy.cpp:669 +msgid "Spider's" +msgstr "Pajęczy" + +#: Source/translation_dummy.cpp:670 +msgid "Raven's" +msgstr "Kruczy" + +#: Source/translation_dummy.cpp:671 +msgid "Snake's" +msgstr "Wężowy" + +#: Source/translation_dummy.cpp:672 +msgid "Serpent's" +msgstr "Gadzi" + +#: Source/translation_dummy.cpp:673 +msgid "Drake's" +msgstr "Sokoli" + +#: Source/translation_dummy.cpp:674 +msgid "Dragon's" +msgstr "Smoczy" + +#: Source/translation_dummy.cpp:675 +msgid "Wyrm's" +msgstr "Żmijowy" + +#: Source/translation_dummy.cpp:676 +msgid "Hydra's" +msgstr "Mityczny" + +#: Source/translation_dummy.cpp:677 +msgid "Angel's" +msgstr "Anielski" + +#: Source/translation_dummy.cpp:678 +msgid "Arch-Angel's" +msgstr "Archanielski" + +#: Source/translation_dummy.cpp:679 +msgid "Plentiful" +msgstr "Obfity" + +#: Source/translation_dummy.cpp:680 +msgid "Bountiful" +msgstr "Dorodny" + +#: Source/translation_dummy.cpp:681 +msgid "Flaming" +msgstr "Płonący" + +#: Source/translation_dummy.cpp:682 +msgid "Lightning" +msgstr "Piorunowy" + +#: Source/translation_dummy.cpp:683 +msgid "Jester's" +msgstr "Błazeński" + +#: Source/translation_dummy.cpp:684 +msgid "Crystalline" +msgstr "Kryształowy" + +#: Source/translation_dummy.cpp:685 +msgid "Doppelganger's" +msgstr "Rozdwojony" + +#: Source/translation_dummy.cpp:686 +msgid "quality" +msgstr "jakości" + +#: Source/translation_dummy.cpp:687 +msgid "maiming" +msgstr "ran" + +#: Source/translation_dummy.cpp:688 +msgid "slaying" +msgstr "zagłady" + +#: Source/translation_dummy.cpp:689 +msgid "gore" +msgstr "sadyzmu" + +#: Source/translation_dummy.cpp:690 +msgid "carnage" +msgstr "zbrodni" + +#: Source/translation_dummy.cpp:691 +msgid "slaughter" +msgstr "rzezi" -#. 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 "" -"Chwała Diablo - Panu Grozy, który przetrwał Mroczne Wygnanie. Mój Pan i " -"Mistrz przebudził się wreszcie z głębokiego snu. Wyjawił mi sekrety znane " -"niewielu śmiertelnikom. Opowiedział mi o Królestwie Niebios i Płonących " -"Piekłach, ścierających się w odwiecznej wojnie. Pokazał moce, które " -"sprowadziły ten konflikt do krainy ludzi, a samą walkę za ten świat i każde " -"jego istnienie nazwał Wojną Grzechu." +#: Source/translation_dummy.cpp:692 +msgid "pain" +msgstr "udręki" -#. 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 "" -"Chwała i uwielbienie dla Diablo - Pana Grozy, który przewodzi całej " -"Trójcy. Mój Pan opowiadał mi o swych dwóch braciach, Mefisto i Baalu, z " -"którymi dawno temu został wygnany do naszego świata. Czeka teraz na " -"odpowiedni moment, by wykorzystać swą niesamowitą moc do uwolnienia " -"pojmanych braci, znajdujących się w grobowcach pod piaskami Wschodu. " -"Kiedy mój Pan oswobodzi swych braci, Wojna Grzechu jeszcze raz zazna gniewu " -"Trójcy." +#: Source/translation_dummy.cpp:693 +msgid "tears" +msgstr "smutku" -#. 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 "" -"Oddajcie cześć i pokłon Diablo - Panu Grozy i niszczycielowi dusz. " -"Kiedy przebudziłem mego mistrza, spróbował opętać śmiertelnika. Diablo " -"chciał posiąść ciało Króla Leoryka, jednak był jeszcze zbyt słaby. " -"Potrzebował ofiary słabszej i naiwniejszej, by powrócić w pełni do tego " -"świata. Uznał chłopca, Albrechta, za idealny cel. Podczas gdy Król Leoryk " -"tracił zmysły po nieudanej próbie opętania, ja porwałem jego syna Albrechta " -"i zaprowadziłem go przed mojego Mistrza. Teraz oczekuję rozkazu od Diablo " -"i modlę się o to, by zostać nagrodzonym kiedy zostanie Panem tego świata." +#: Source/translation_dummy.cpp:694 +msgid "health" +msgstr "zdrowia" -#. 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 "" -"Dobrze znów cię widzieć!\n" -"Dużo się zmieniło od czasu twojej ostatniej wizyty. Żyliśmy tu spokojnie, " -"do momentu, w którym przybyły te diabelskie stworzenia i zniszczyły naszą " -"wioskę. Wielu zginęło na miejscu. Tych co chwycili za broń zabito albo " -"porwano na niewolników - jak nie gorzej. Kościół na skraju miasta " -"zbezczeszczono i służy teraz mrocznym rytuałom. Nocami po osadzie roznoszą " -"się nieludzkie krzyki. Mimo to wciąż mamy nadzieję, że część naszych ludzi " -"przeżyła. Udaj się ścieżką wiodącą między gospodą a kuźnią, tam znajdziesz " -"katedrę. Uratuj kogo tylko zdołasz. \n" -" \n" -"Więcej opowiem ci przy następnym spotkaniu. Powodzenia." +#: Source/translation_dummy.cpp:695 +msgid "protection" +msgstr "opieki" -#. 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 "" -"Za Salą Bohaterów znajduje się Komnata Kości. Wieczne potępienie czeka na " -"każdego, kto odważy się wykraść złożone w niej skarby. Tak oznajmił Pan " -"Grozy, tak też zostało zapisane." +#: Source/translation_dummy.cpp:696 +msgid "absorption" +msgstr "absorpcji" -#. 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 "" -"Zbrojownie Piekła stały się domem Marszałka Krwi. Gdziekolwiek przejdzie, " -"pozostawia za sobą tysiące okaleczonych ciał. Mordował zarówno aniołów, jak " -"i ludzi. Składa niekończące się ofiary Panom Ciemności, pragnącym tylko " -"jednego. Krwi..." +#: Source/translation_dummy.cpp:697 +msgid "deflection" +msgstr "unikania" -#. 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 "" -"Pamiętaj o tym zadaniu. Znalezienie utraconego skarbu nie jest proste. Tym " -"bardziej znalezienie skarbu, który został ukryty. Nie pozwól, aby " -"upływający czas wpłynął na twoje poszukiwania." +#: Source/translation_dummy.cpp:698 +msgid "osmosis" +msgstr "izolacji" -#. 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 "" -"Co? To bzdura. Pod Tristram nie ma ukrytych skarbów. Pokaż mi to! " -"Ach, spójrz, te rysunki nie pasują do naszego miasta. Na twoim miejscu " -"skupiłbym się nad tym co jest pod katedrą, a nie na tej mapie." +#: Source/translation_dummy.cpp:699 +msgid "frailty" +msgstr "marności" -#. 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 "" -"Nie mam czasu na przeglądanie map. Wielu ludzi potrzebuje teraz mojej " -"pomocy, twojej zresztą też." +#: Source/translation_dummy.cpp:700 +msgid "weakness" +msgstr "słabości" -#. 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 "Usunięty quest." +#: Source/translation_dummy.cpp:701 +msgid "strength" +msgstr "siły" -#. 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 "" -"Założę się, że to Wirt robi sobie z ciebie żarty. Pewnie pęknie ze śmiechu, " -"gdy się dowie, jak pytasz wszystkich o te bazgroły, które ci podrzucił. " -"Ja bym sobie dał z tym spokój." +#: Source/translation_dummy.cpp:702 +msgid "might" +msgstr "mocy" -#. 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 "" -"Kiedyś często zatrzymywali się tu podróżni z dalekich stron, jednak wiele " -"się od tego czasu zmieniło. Ale dzieci często fantazjują o ukrytych " -"jaskiniach i zakopanych skarbach. Choć Wirt rzadko daje się ponieść " -"młodzieńczym zabawom, to nadal możliwe, że to tylko jego wymysł." +#: Source/translation_dummy.cpp:703 +msgid "power" +msgstr "władzy" -#. 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 "" -"Cho... Chono tu. Słuchaj. Nie wiem czy ty to widzisz, ale tu jest coś " -"narysowane. To mapa." +#: Source/translation_dummy.cpp:704 +msgid "giants" +msgstr "giganta" -#. 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 "" -"Moja babcia często opowiadała mi historie o dziwnych mocach zamieszkujących " -"cmentarz przed kościołem. Opowiem ci jedną z nich. " -"Mówiła, że jeżeli na cmentarzu złoży się właściwą ofiarę, pójdzie do " -"katedry, pomodli się za zmarłego i wróci, złożona ofiara zostanie w " -"tajemniczy sposób przyjęta. " -"Nie wiem czy to tylko gadanie starej schorowanej kobiety, ale teraz wszystko " -"jest możliwe." +#: Source/translation_dummy.cpp:705 +msgid "titans" +msgstr "tytana" -#. 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 "" -"Ogromny i tajemniczy skarb, powiadasz? Może nawet coś bym od ciebie " -"kupił... a nie potrzebujesz może jakiegoś unikalnego i drogiego " -"wyposażenia żeby go zdobyć?" +#: Source/translation_dummy.cpp:706 +msgid "paralysis" +msgstr "paraliżu" -#. 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 "" -"Hej, to chyba o tobie wszyscy wkoło gadają. Pomożesz biednemu, prostemu " -"farmerowi? Na skraju mojego gospodarstwa, na południe stąd, z ziemi " -"wypełzło jakieś paskudztwo! Blokuje mi drogę do upraw i stogów siana, przez " -"co moje krowy zaczynają głodować. Wiedźma dała mi to coś, żeby wysadzić " -"tamto szkaradztwo w powietrze. Jeśli mnie wyręczysz, będę ci dozgonnie " -"wdzięczny. Sam nie dam rady, bo wiesz, ktoś musi pilnować krów..." +#: Source/translation_dummy.cpp:707 +msgid "atrophy" +msgstr "atrofii" -#. 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 "" -"Wiedziałem, że to nie może być takie proste, jak mówiła wiedźma. To smutne, " -"że w dzisiejszych czasach nie można ufać już nawet własnym sąsiadom." +#: Source/translation_dummy.cpp:708 +msgid "dexterity" +msgstr "zręczności" -#. 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 "" -"Już po wszystkim? Czy to coś wróciło już do ciemnych otchłani Hadesu, z " -"których przyszło? Co?! No nie mów mi, że gdzieś ci się zgubił ładunek! To " -"nie była tania rzecz. Musisz go teraz znaleźć. Inaczej nie pozbędziesz " -"się tego paskudztwa z naszego miasta." +#: Source/translation_dummy.cpp:709 +msgid "skill" +msgstr "talentu" -#. 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 "" -"Eksplozję było słychać aż tutaj! Wielkie dzięki. Nastały trudne czasy - " -"najpierw te potwory w katedrze, teraz to coś wypełzające z ziemi. Wiesz, " -"może i jestem biednym farmerem, ale potrafię docenić czyjś trud - weź to " -"w podzięce." +#: Source/translation_dummy.cpp:710 +msgid "accuracy" +msgstr "celności" -#. 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 "" -"Och, mam kłopot... może... Nie, nie, nie... Masz wystarczająco dużo " -"problemów. Ale jeśli możesz to odwiedź mnie, kiedy już oczyścisz kościół z " -"tych kreatur... Znajdziesz wtedy trochę czasu dla biednego farmera, co?" +#: Source/translation_dummy.cpp:711 +msgid "precision" +msgstr "precyzji" + +#: Source/translation_dummy.cpp:712 +msgid "perfection" +msgstr "perfekcji" + +#: Source/translation_dummy.cpp:713 +msgid "the fool" +msgstr "głupca" + +#: Source/translation_dummy.cpp:714 +msgid "dyslexia" +msgstr "dysfunkcji" + +#: Source/translation_dummy.cpp:715 +msgid "magic" +msgstr "magii" + +#: Source/translation_dummy.cpp:716 +msgid "the mind" +msgstr "myśliciela" + +#: Source/translation_dummy.cpp:717 +msgid "brilliance" +msgstr "rozumu" -#. TRANSLATORS: Quest text spoken by Little Girl -#: Source/textdat.cpp:571 -msgid "Waaaah! (sniff) Waaaah! (sniff)" -msgstr "Buuu! Buuu!" +#: Source/translation_dummy.cpp:718 +msgid "sorcery" +msgstr "czarów" -#. 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 "" -"Zgubiłam Teosia! To mój najlepszy przyjaciel! Bawiliśmy się przy rzece, i " -"Teoś powiedział, że idziemy zobaczyć to duże zielone coś. Mówiłam, że " -"lepiej nie, ale był uparty i poszliśmy. Nagle wypełzł wielki ROBAL! " -"Zaczęliśmy uciekać, ale Teoś upadł i to coś go PORWAłO!" +#: Source/translation_dummy.cpp:719 +msgid "wizardry" +msgstr "iluzji" -#. 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 "" -"Masz go? Znajdź Teodora, proszę! Jest taki malutki. Nie poradzi sobie " -"sam! Proszę!" +#: Source/translation_dummy.cpp:720 +msgid "illness" +msgstr "zarazy" -#. 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 "" -"Masz go! Jest bezpieczny! Dziękuję ci! Och Teosiu, czy te paskudne robale " -"cię wystraszyły? Co to? Blee! Coś przyczepiło ci się do futerka! Fuj! " -"Chodź Teosiu, wracamy do domu! A tobie jeszcze raz dziękuję!" +#: Source/translation_dummy.cpp:721 +msgid "disease" +msgstr "choroby" -#. 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 "" -"Przez lata byliśmy pogrążeni w głębokim śnie, ale nadszedł moment " -"przebudzenia. Po tak długim spoczynku czujemy potworny głód. Już wkrótce " -"ruszymy na łowy..." +#: Source/translation_dummy.cpp:722 +msgid "vitality" +msgstr "żywotności" -#. 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 "Usunięta kwestia." +#: Source/translation_dummy.cpp:723 +msgid "zest" +msgstr "witalności" -#. 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 "" -"Wasza kraina zostanie sprofanowana, " -"a nasza rasa opanuje ziemię, która jest waszym domem. Nasze macki obejmą " -"cały świat i będziemy ucztować na zwłokach jego mieszkańców. Ludzie staną " -"się naszymi orędownikami i pożywieniem." +#: Source/translation_dummy.cpp:724 +msgid "vim" +msgstr "werwy" -#. 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 "Usunięta kwestia." +#: Source/translation_dummy.cpp:725 +msgid "vigor" +msgstr "wigoru" -#. 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 "" -"I tak, w roku Złotej światłości, podjęto decyzję o wzniesieniu wielkiej " -"Katedry. Kamieniem węgielnym tego świętego miejsca była półprzezroczysta " -"skała, Antyrael, nazwana tak na cześć Anioła, który podzielił się swą wiedzą " -"z horadrimami. \n" -" \n" -"W roku Zwodniczych Cieni nastąpiło trzęsienie ziemi, przez które Katedra " -"popadła w ruinę. Gdy budowano katakumby i zamki, a ludzkość walczyła ze " -"spustoszeniem wywołanym przez Wojnę Grzechu, z ruin wygrzebano cenną skałę. " -"Tak właśnie zniknął kamień węgielny. \n" -" \n" -"Kamień był jednocześnie z tego świata i ze wszystkich światów. Tak jak " -"światłość jest jednocześnie wewnątrz wszystkiego i ponad wszystkim. " -"Światłość i jedność są świętą podstawą: jedność celu i jedność dziedzictwa." +#: Source/translation_dummy.cpp:726 +msgid "life" +msgstr "życia" -#. TRANSLATORS: Quest text spoken by Complete Nut -#: Source/textdat.cpp:594 -msgid "Moo." -msgstr "Muu." +#: Source/translation_dummy.cpp:727 +msgid "trouble" +msgstr "utrapienia" -#. TRANSLATORS: Quest text spoken by Complete Nut -#: Source/textdat.cpp:595 -msgid "I said, Moo." -msgstr "No, muu." +#: Source/translation_dummy.cpp:728 +msgid "the pit" +msgstr "otchłani" -#. TRANSLATORS: Quest text spoken by Complete Nut -#: Source/textdat.cpp:596 -msgid "Look I'm just a cow, OK?" -msgstr "Słuchaj, jestem krową, kapujesz?" +#: Source/translation_dummy.cpp:729 +msgid "the sky" +msgstr "łaski" -#. 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 "" -"Dobra, dobra. To tylko przebranie. Normalnie się tak nie ubieram, ale " -"siedziałem w domu i rozmyślałem nad swoim życiem, gdy nagle te robale, " -"pnącza i inne świństwa zaczęły wypełzać spod podłogi... to było okropne! " -"Szkoda, że nie miałem pod ręką innego ubrania, nie wyglądałbym jak skończony " -"wariat. Hej! Może wpadniesz do mnie do domu i przyniesiesz mój garniak? " -"Ten brązowy, bo szary jest na wyjścia wieczorowe. Zrobiłbym to sam, ale " -"nie chcę, żeby ktoś widział mnie w tym przebraniu. " -"Weź to, może się przydać do... przedarcia się przez te wszystkie przerosty. " -"Ciężko nie zauważyć mojego domu, jest na południu, przy rozwidleniu rzeki... " -"wiesz... taki z przerośniętym ogródkiem." +#: Source/translation_dummy.cpp:730 +msgid "the moon" +msgstr "księżyca" -#. 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 "" -"Na co ty czekasz? Leć po mój garniak! Szybko! Tamten byczek dziwnie się " -"już na mnie patrzy!" +#: Source/translation_dummy.cpp:731 +msgid "the stars" +msgstr "gwiazd" -#. 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 "" -"Hej, masz moje ubranie? Streszczaj się! Te uszy tak swędzą, że już nie " -"wytrzymie!" +#: Source/translation_dummy.cpp:732 +msgid "the heavens" +msgstr "niebios" -#. 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 "" -"Nie, nie, nie, nie! Ten strój jest SZARY! Jest na wyjścia wieczorowe! Na " -"ważne spotkania! Nie mogę GO teraz założyć. Padło ci na mózg? Przecież " -"mówiłem, że ma być BRĄZOWY." +#: Source/translation_dummy.cpp:733 +msgid "the zodiac" +msgstr "zodiaku" -#. 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 "" -"Aaach, o wiele lepiej. Wreszcie mogę poczuć się jak poważny człowiek! Czy " -"rogi są prosto? świetnie. No, dzięki za pomoc. Mam dla ciebie prezent... " -"łap; dam ci też małą radę dotyczącą ubioru, no wiesz... twojej stylówki... " -"możesz założyć coś trochę... albo kupić nowe... wiesz o co chodzi? " -"Ten cały motyw poszukiwacza przygód jest trochę... zbyt retro. " -"Taka mała rada, hm? Ciao." +#: Source/translation_dummy.cpp:734 +msgid "the vulture" +msgstr "sępa" -#. 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 "" -"Słuchaj, jestem krową, a ty zwykłą przynętą na potwory. Nabierz trochę " -"doświadczenia, to pogadamy..." +#: Source/translation_dummy.cpp:735 +msgid "the jackal" +msgstr "szakala" -#. 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 "" -"Zadanie, które ci powierzyłem, nie jest łatwe. Jeśli mógłbym jakoś... co " -"powiesz na dzbanek pysznego, świeżutkiego mleka?" +#: Source/translation_dummy.cpp:736 +msgid "the fox" +msgstr "lisa" -#. 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 "" -"Och, skorzystałbym z twojej pomocy, ale najpierw musisz oczyścić katakumby z " -"bestii, które je splugawiły." +#: Source/translation_dummy.cpp:737 +msgid "the jaguar" +msgstr "jaguara" -#. 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 "" -"Mam kłopot, ale nie mogę polegać na kimś zupełnie obcym. Odwiedź mnie, jak " -"nabierzesz doświadczenia." +#: Source/translation_dummy.cpp:738 +msgid "the eagle" +msgstr "orła" -#. 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 "" -"Widzę w tobie ogromny potencjał. Mógłbym poprosić cię o przysługę, kiedy " -"już wypełnisz swoje przeznaczenie?" +#: Source/translation_dummy.cpp:739 +msgid "the wolf" +msgstr "hieny" -#. 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 "" -"Musisz mieć więcej siły. Nie chcę, żeby ci się stała krzywda, bo nikt poza " -"tobą nie będzie w stanie oczyścić katedry!" +#: Source/translation_dummy.cpp:740 +msgid "the tiger" +msgstr "tygrysa" -#. 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 "" -"Jestem samozwańczą krową. Ogarnij się, bo póki co nie dorastasz mi do... " -"kopyt." +#: Source/translation_dummy.cpp:741 +msgid "the lion" +msgstr "lwa" + +#: Source/translation_dummy.cpp:742 +msgid "the mammoth" +msgstr "mamuta" -#. 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 "" -"Nie będę tłumaczył się wszystkim przyjezdnym! Nie masz przypadkiem jakichś " -"potworów do zabicia? Wyjaśnię ci to później. O ile przeżyjesz..." +#: Source/translation_dummy.cpp:743 +msgid "the whale" +msgstr "wieloryba" -#. 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 "" -"Przestań mnie dręczyć. Szukam kogoś naprawdę walecznego. Ty nie dasz " -"rady. Nie powierzę ci zadania, bo te potwory w moment cię zeżrą... Musisz " -"nabrać doświadczenia." +#: Source/translation_dummy.cpp:744 +msgid "fragility" +msgstr "wątłości" -#. 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 "" -"Dobra, dobra. To tylko przebranie. Normalnie się tak nie ubieram, ale " -"siedziałem w domu i rozmyślałem nad swoim życiem, gdy nagle te robale, " -"pnącza i inne świństwa zaczęły wypełzać spod podłogi... to było okropne! " -"Szkoda, że nie miałem pod ręką innego ubrania, nie wyglądałbym jak skończony " -"wariat. Hej! Może wpadniesz do mnie do domu i przyniesiesz mój garniak? " -"Ten brązowy, bo szary jest na wyjścia wieczorowe. Zrobiłbym to sam, ale " -"nie chcę, żeby ktoś widział mnie w tym przebraniu. Weź to, może się przydać " -"do... przedarcia się przez te wszystkie przerosty. Ciężko nie zauważyć " -"mojego domu, jest na południu, przy rozwidleniu rzeki... wiesz... taki " -"z przerośniętym ogródkiem." +#: Source/translation_dummy.cpp:745 +msgid "brittleness" +msgstr "kruchości" -#. 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 "Usunięta kwestia." +#: Source/translation_dummy.cpp:746 +msgid "sturdiness" +msgstr "krzepy" -#. 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 "" -"Zaklęcia, groźby czy negocjacje zdają się nie mieć sensu. Wszystkie moje " -"sposoby zniewalania słabszych demonów nie działają na tę przerażającą bestię." +#: Source/translation_dummy.cpp:747 +msgid "craftsmanship" +msgstr "kunsztu" -#. 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 "" -"Mój dom ogarnia skażenie spowodowane zepsuciem niechcianego więźnia. Krypty " -"pełne są cieni czających się na krawędzi wzroku. Piskliwy dźwięk drapania małych " -"kreatur doprowadza mnie już do szaleństwa. Prawdopodobnie szukają mego dziennika." +#: Source/translation_dummy.cpp:748 +msgid "structure" +msgstr "zwięzłości" -#. 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 "" -"W swych tyradach potwór zdradził swe imię... Na-Krul. Chciałem znaleźć " -"informacje na jego temat, ale demony zniszczyły moją bibliotekę. " -"Na-Krul... sam dźwięk tego imienia przeszywa mnie zimnym dreszczem. Nawet w " -"myślach boję się je powtarzać. Wolę mówić na niego... Potwór." +#: Source/translation_dummy.cpp:749 +msgid "the ages" +msgstr "wieków" -#. 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 "" -"Uwięziony potwór jest tak rozwścieczony, że przez jego wycie nie mogę zaznać " -"niezbędnego mi teraz snu. Przeklina mnie za zesłanie go do Otchłani. " -"Jego słowa wypełniają moje serce strachem, lecz ani na chwilę nie potrafię " -"zagłuszyć tego dźwięku." +#: Source/translation_dummy.cpp:750 +msgid "the dark" +msgstr "mroku" -#. 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 "" -"Mój czas już się kończy. Muszę spisać sposoby na osłabienie demona i ukryć " -"te teksty przed sługami, które mogą chcieć uwolnić swego Pana. Mam " -"nadzieję, że ten kto odnajdzie dziennik, wykorzysta mą wiedzę." +#: Source/translation_dummy.cpp:751 +msgid "the night" +msgstr "zmierzchu" -#. 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 "" -"Ktokolwiek czyta ten zwój, musi powstrzymać demoniczną kreaturę znajdującą " -"się za tamtymi murami. Mój czas się skończył. Nawet teraz jego upiorne " -"sługi próbują przebić spróchniałe drzwi, za którymi się skryłem. \n" -" \n" -"Dzięki tajemnej magii unieruchomiłem, a następnie uwięziłem demona za " -"grubymi murami. Jednak boję się, że to za mało. \n" -" \n" -"Zaklęcia spisane w mych trzech księgach zapewnią ci bezpieczne przejście do " -"jego komnaty, ale tylko rzucone w odpowiedniej kolejności. Dźwignie w " -"przedsionku zniosą bariery i uwolnią demona; nie dotykaj ich! Wystarczy, że " -"użyjesz tamtych trzech zaklęć. W przeciwnym razie jego moc może okazać się " -"zbyt potężna, by pozwolić ci na zwycięstwo." +#: Source/translation_dummy.cpp:752 +msgid "light" +msgstr "blasku" -#. 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." +#: Source/translation_dummy.cpp:753 +msgid "radiance" +msgstr "jasności" -#. 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." +#: Source/translation_dummy.cpp:754 +msgid "flame" +msgstr "płomienia" -#. 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/translation_dummy.cpp:755 +msgid "fire" +msgstr "ognia" -#: Source/towners.cpp:79 -msgid "Griswold the Blacksmith" -msgstr "Kowal Griswold" +#: Source/translation_dummy.cpp:756 +msgid "burning" +msgstr "spopielenia" -#: Source/towners.cpp:101 -msgid "Ogden the Tavern owner" -msgstr "Ogden - Właściciel Gospody" +#: Source/translation_dummy.cpp:757 +msgid "shock" +msgstr "wstrząsu" -#: Source/towners.cpp:110 -msgid "Wounded Townsman" -msgstr "Ranny Mieszkaniec" +#: Source/translation_dummy.cpp:758 +msgid "lightning" +msgstr "błyskawic" -#: Source/towners.cpp:132 -msgid "Adria the Witch" -msgstr "Wiedźma Adria" +#: Source/translation_dummy.cpp:759 +msgid "thunder" +msgstr "gromu" -#: Source/towners.cpp:141 -msgid "Gillian the Barmaid" -msgstr "Barmanka Gillian" +#: Source/translation_dummy.cpp:760 +msgid "many" +msgstr "pokusy" -#: Source/towners.cpp:172 -msgid "Pepin the Healer" -msgstr "Uzdrowiciel Pepin" +#: Source/translation_dummy.cpp:761 +msgid "plenty" +msgstr "obfitości" -#: Source/towners.cpp:189 -msgid "Cain the Elder" -msgstr "Mędrzec Cain" +#: Source/translation_dummy.cpp:762 +msgid "thorns" +msgstr "iglicy" -#: Source/towners.cpp:216 -msgid "Cow" -msgstr "Krowa" +#: Source/translation_dummy.cpp:763 +msgid "corruption" +msgstr "skazy" -#: Source/towners.cpp:240 -msgid "Lester the farmer" -msgstr "Farmer Lester" +#: Source/translation_dummy.cpp:764 +msgid "thieves" +msgstr "złodzieja" -#: Source/towners.cpp:253 -msgid "Complete Nut" -msgstr "Skończony Wariat" +#: Source/translation_dummy.cpp:765 +msgid "the bear" +msgstr "bizona" -#: Source/towners.cpp:262 -msgid "Celia" -msgstr "Celia" +#: Source/translation_dummy.cpp:766 +msgid "the bat" +msgstr "nietoperza" -#: Source/towners.cpp:275 -msgid "Slain Townsman" -msgstr "Zabity Mieszkaniec" +#: Source/translation_dummy.cpp:767 +msgid "vampires" +msgstr "wampira" -#: Source/trigs.cpp:343 -msgid "Down to dungeon" -msgstr "Zejdź do lochu" +#: Source/translation_dummy.cpp:768 +msgid "the leech" +msgstr "pijawki" -#: Source/trigs.cpp:354 -msgid "Down to catacombs" -msgstr "Zejdź do katakumb" +#: Source/translation_dummy.cpp:769 +msgid "blood" +msgstr "krwi" -#: Source/trigs.cpp:364 -msgid "Down to caves" -msgstr "Zejdź do jaskiń" +#: Source/translation_dummy.cpp:770 +msgid "piercing" +msgstr "rozdarcia" -#: Source/trigs.cpp:374 -msgid "Down to hell" -msgstr "Zejdź do piekła" +#: Source/translation_dummy.cpp:771 +msgid "puncturing" +msgstr "przebicia" -#: Source/trigs.cpp:386 -msgid "Down to Hive" -msgstr "Zejdź do Gniazda" +#: Source/translation_dummy.cpp:772 +msgid "bashing" +msgstr "natarcia" -#: Source/trigs.cpp:398 -msgid "Down to Crypt" -msgstr "Zejdź do Krypty" +#: Source/translation_dummy.cpp:773 +msgid "readiness" +msgstr "gotowości" -#: Source/trigs.cpp:414 Source/trigs.cpp:494 Source/trigs.cpp:541 -#: Source/trigs.cpp:636 -msgid "Up to level {:d}" -msgstr "Wejdź na poziom {:d}" +#: Source/translation_dummy.cpp:774 +msgid "swiftness" +msgstr "zwinności" -#: 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 "Wejdź do miasta" +#: Source/translation_dummy.cpp:775 +msgid "speed" +msgstr "szybkości" -#: 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 "Zejdź na poziom {:d}" +#: Source/translation_dummy.cpp:776 +msgid "haste" +msgstr "pośpiechu" -#: Source/trigs.cpp:439 -msgid "Up to Crypt level {:d}" -msgstr "Do Krypty - poziom {:d}" +#: Source/translation_dummy.cpp:777 +msgid "balance" +msgstr "równowagi" -#: Source/trigs.cpp:454 -msgid "Down to Crypt level {:d}" -msgstr "Do Krypty - poziom {:d}" +#: Source/translation_dummy.cpp:778 +msgid "stability" +msgstr "zaufania" -#: Source/trigs.cpp:570 -msgid "Up to Nest level {:d}" -msgstr "Do Gniazda - poziom {:d}" +#: Source/translation_dummy.cpp:779 +msgid "harmony" +msgstr "harmonii" -#: Source/trigs.cpp:679 -msgid "Down to Diablo" -msgstr "Staw czoła Diablo" +#: Source/translation_dummy.cpp:780 +msgid "blocking" +msgstr "blokowania" -#: Source/trigs.cpp:712 Source/trigs.cpp:726 Source/trigs.cpp:740 -msgid "Back to Level {:d}" -msgstr "Wróć na poziom {:d}" +#: Source/translation_dummy.cpp:781 +msgid "devastation" +msgstr "destrukcji" -#~ msgid "Right click to use" -#~ msgstr "PPM by użyć" +#: Source/translation_dummy.cpp:782 +msgid "decay" +msgstr "rozkładu" + +#: Source/translation_dummy.cpp:783 +msgid "peril" +msgstr "zagrożenia" -#~ msgid "Right click to read" -#~ msgstr "PPM by odczytać" +#. TRANSLATORS: Thousands separator +#: Source/utils/format_int.cpp:27 +msgid "," +msgstr "," diff --git a/Translations/sv.po b/Translations/sv.po index 58b3088911b..9f93f1a85df 100644 --- a/Translations/sv.po +++ b/Translations/sv.po @@ -1087,7 +1087,7 @@ msgstr "" #: Source/cursor.cpp:220 msgid "Town Portal" -msgstr "Stadsportal" +msgstr "Byportal" #: Source/cursor.cpp:221 msgid "from {:s}" @@ -1481,7 +1481,7 @@ msgstr "fel: läste 0 bytes från servern" #: Source/error.cpp:55 msgid "No automap available in town" -msgstr "Ingen autokarta tillgänglig i staden" +msgstr "Ingen autokarta tillgänglig i byn" #: Source/error.cpp:56 msgid "No multiplayer functions in demo" @@ -1501,7 +1501,7 @@ msgstr "Inte tillräckligt med utrymme för att spara" #: Source/error.cpp:60 msgid "No Pause in town" -msgstr "Ingen Paus i staden" +msgstr "Ingen Paus i byn" #: Source/error.cpp:61 msgid "Copying to a hard disk is recommended" @@ -1745,7 +1745,7 @@ msgstr "Avsluta Spel" #: Source/gamemenu.cpp:51 msgid "Restart In Town" -msgstr "Starta Om i Staden" +msgstr "Starta Om i Byn" #: Source/gamemenu.cpp:63 msgid "Gamma" @@ -2063,7 +2063,7 @@ msgstr "Köttyxa" #: Source/itemdat.cpp:60 Source/itemdat.cpp:434 msgid "The Undead Crown" -msgstr "De Vandödas Krona" +msgstr "De Odödas Krona" #: Source/itemdat.cpp:61 Source/itemdat.cpp:435 msgid "Empyrean Band" @@ -2143,7 +2143,7 @@ msgstr "Identifiera-Skriftrulle" #: Source/itemdat.cpp:80 Source/itemdat.cpp:151 msgid "Scroll of Town Portal" -msgstr "Stadsportal-Skriftrulle" +msgstr "Byportal-Skriftrulle" #: Source/itemdat.cpp:81 Source/itemdat.cpp:440 msgid "Arkaine's Valor" @@ -2483,7 +2483,7 @@ msgstr "Huggare" #: Source/itemdat.cpp:175 msgid "Claymore" -msgstr "Höglandssvärd" +msgstr "Slagsvärd" #: Source/itemdat.cpp:176 msgid "Blade" @@ -2560,7 +2560,7 @@ msgstr "Spikklubba" #: Source/itemdat.cpp:194 msgid "Flail" -msgstr "Slaga" +msgstr "Stridsgissel" #: Source/itemdat.cpp:195 msgid "Maul" @@ -4413,7 +4413,7 @@ msgstr "extra PK mot demoner" #: Source/items.cpp:3754 msgid "extra AC vs undead" -msgstr "extra PK mot vandöda" +msgstr "extra PK mot ödöda" #: Source/items.cpp:3756 msgid "50% Mana moved to Health" @@ -4788,7 +4788,7 @@ msgstr "Likdemon" #: Source/monstdat.cpp:91 msgctxt "monster" msgid "Undead Balrog" -msgstr "Vandöd Balrog" +msgstr "Odöd Balrog" #: Source/monstdat.cpp:92 msgctxt "monster" @@ -5614,7 +5614,7 @@ msgstr "Demon" #: Source/monster.cpp:3386 msgid "Undead" -msgstr "Vandöd" +msgstr "Odöd" #: Source/monster.cpp:4648 msgid "Type: {:s} Kills: {:d}" @@ -6848,7 +6848,7 @@ msgstr "Magi" #: Source/panels/spell_list.cpp:172 msgid "Damages undead only" -msgstr "Skadar endast vandöda" +msgstr "Skadar endast odöda" #: Source/panels/spell_list.cpp:183 msgid "Scroll" @@ -7080,7 +7080,7 @@ msgstr "Eldmur" #: Source/spelldat.cpp:22 msgctxt "spell" msgid "Town Portal" -msgstr "Stadsportal" +msgstr "Byportal" #: Source/spelldat.cpp:23 msgctxt "spell" @@ -7683,7 +7683,7 @@ msgstr "" "\n" "Här tar historien en ännu mörkare vändning än vad jag trodde var möjligt! " "Vår forne kung har stigit upp från sin eviga sömn och styr nu en legion av " -"vandöda tjänare inne i Labyrinten. Hans kropp begravdes i en grift tre " +"odöda tjänare inne i Labyrinten. Hans kropp begravdes i en grift tre " "våningar under Katedralen. Snälla, kära mästare, släpp hans själ fri genom " "att förgöra hans förbannade nuvarande form..." @@ -7768,7 +7768,7 @@ msgid "" "all who still live here." msgstr "" "De döda som går bland de levande följer den förbannade Kungen. Han har " -"makten att väcka upp ännu fler krigare för de vandödas evigt växande armé. " +"makten att väcka upp ännu fler krigare för de odödas evigt växande armé. " "Om du inte stoppar hans styre kommer han säkerligen tåga över detta land och " "slå ned alla som ännu bor här." @@ -7782,7 +7782,7 @@ msgid "" msgstr "" "Lyssna, jag har affärer att sköta här. Jag säljer inte information, och jag " "bryr mig inte om någon Kung som varit död längre än jag levat. Men om du " -"behöver något att använda mot den här vandöda Kungen, så kanske jag kan " +"behöver något att använda mot den här odöda Kungen, så kanske jag kan " "hjälpa dig..." #. TRANSLATORS: Quest dialog spoken by The Skeleton King (Hostile) @@ -8142,7 +8142,7 @@ msgid "" "Please, do what you can or I don't know what we will do." msgstr "" "Jag har alltid försökt hålla stora förråd av mat och dryck i vår källare, " -"men om hela staden saknar färskvatten kommer till och med våra förråd sina " +"men om hela byn saknar färskvatten kommer till och med våra förråd sina " "snart.\n" "\n" "Snälla, gör vad du kan, annars vet jag inte vad vi ska ta oss till." @@ -10005,7 +10005,7 @@ msgid "" "little skeletons!" msgstr "" "Om du är ute efter ett bra vapen så se här. Ta ett vanligt trubbigt vapen, " -"som en stridsklubba. Funkar utmärkt mot de där vandöda fasorna där nere, och " +"som en stridsklubba. Funkar utmärkt mot de där odöda fasorna där nere, och " "det finns inget bättre för att spräcka smala små skelett!" #. TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) @@ -10030,7 +10030,7 @@ msgid "" msgstr "" "Titta på den här klingan, balansen. Ett svärd i rätt händer och mot rätt " "fiende, är alla vapens mästare. Dess skarpa egg har inte mycket att skära " -"eller hugga igenom mot de vandöda, men mot levande fiender kan ett svärd på " +"eller hugga igenom mot de odöda, men mot levande fiender kan ett svärd på " "riktigt skära deras kött!" #. TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) @@ -10945,7 +10945,7 @@ msgid "" "the talk of an old sick woman, but anything seems possible these days." msgstr "" "Min mormor berättar ofta historier om mystiska krafter som bebor kyrkogården " -"utanför stade. Och du kanske vill höra en av dem. Hon sade att om man lämnar " +"utanför byn. Och du kanske vill höra en av dem. Hon sade att om man lämnar " "en lämplig gåva i kyrkogården, går in i katedralen för att be för de döda, " "och sedan kommer tillbaka, så skulle gåvan ändrats på något konstigt sätt. " "Jag vet inte om det bara är en sjuk gammal kvinnas prat, men allt verkar " @@ -11463,7 +11463,7 @@ msgstr "Krögaren Ogden" #: Source/towners.cpp:110 msgid "Wounded Townsman" -msgstr "Skadad Stadsbo" +msgstr "Skadad Bybo" #: Source/towners.cpp:132 msgid "Adria the Witch" @@ -11499,7 +11499,7 @@ msgstr "Celia" #: Source/towners.cpp:277 msgid "Slain Townsman" -msgstr "Dräpt Stadsbo" +msgstr "Dräpt Bybo" #: Source/trigs.cpp:343 msgid "Down to dungeon" @@ -11533,7 +11533,7 @@ msgstr "Upp till nivå {: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 "Upp till staden" +msgstr "Upp till byn" #: Source/trigs.cpp:427 Source/trigs.cpp:505 Source/trigs.cpp:558 #: Source/trigs.cpp:583 Source/trigs.cpp:648 diff --git a/Translations/uk.po b/Translations/uk.po index 4ce72ea919e..758b860fdd3 100644 --- a/Translations/uk.po +++ b/Translations/uk.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: DevilutionX\n" -"POT-Creation-Date: 2023-07-30 15:40+0300\n" +"POT-Creation-Date: 2023-11-15 16:12+0200\n" "PO-Revision-Date: \n" "Last-Translator: Oleksandr Kalko (tsunami_state) \n" "Language-Team: \n" @@ -15,7 +15,7 @@ msgstr "" "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.3.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" @@ -107,7 +107,7 @@ msgstr "Помічник Режисера" msgid "Diablo Strike Team" msgstr "Ударна Команда Diablo" -#: Source/DiabloUI/credits_lines.cpp:77 Source/gamemenu.cpp:71 +#: Source/DiabloUI/credits_lines.cpp:77 Source/gamemenu.cpp:74 msgid "Music" msgstr "Музика" @@ -323,9 +323,9 @@ msgstr "\tПід час створення цієї гри не було про #: Source/DiabloUI/dialogs.cpp:84 Source/DiabloUI/dialogs.cpp:96 #: Source/DiabloUI/hero/selhero.cpp:177 Source/DiabloUI/hero/selhero.cpp:203 #: Source/DiabloUI/hero/selhero.cpp:288 Source/DiabloUI/hero/selhero.cpp:528 -#: Source/DiabloUI/multi/selconn.cpp:82 Source/DiabloUI/multi/selgame.cpp:171 -#: Source/DiabloUI/multi/selgame.cpp:335 Source/DiabloUI/multi/selgame.cpp:361 -#: Source/DiabloUI/multi/selgame.cpp:503 Source/DiabloUI/multi/selgame.cpp:580 +#: Source/DiabloUI/multi/selconn.cpp:82 Source/DiabloUI/multi/selgame.cpp:173 +#: Source/DiabloUI/multi/selgame.cpp:337 Source/DiabloUI/multi/selgame.cpp:363 +#: Source/DiabloUI/multi/selgame.cpp:505 Source/DiabloUI/multi/selgame.cpp:582 #: Source/DiabloUI/selok.cpp:71 msgid "OK" msgstr "ОК" @@ -336,27 +336,29 @@ msgstr "Виберіть Клас" #. TRANSLATORS: Player Block start #. HeroClass::Warrior -#: Source/DiabloUI/hero/selhero.cpp:159 Source/playerdat.cpp:89 +#: Source/DiabloUI/hero/selhero.cpp:159 Source/playerdat.cpp:254 msgid "Warrior" msgstr "Воїн" -#: Source/DiabloUI/hero/selhero.cpp:160 Source/playerdat.cpp:90 +#: Source/DiabloUI/hero/selhero.cpp:160 Source/playerdat.cpp:255 msgid "Rogue" msgstr "Пройдисвітка" -#: Source/DiabloUI/hero/selhero.cpp:161 Source/playerdat.cpp:91 +#: Source/DiabloUI/hero/selhero.cpp:161 Source/playerdat.cpp:256 msgid "Sorcerer" msgstr "Чаклун" -#: Source/DiabloUI/hero/selhero.cpp:163 Source/playerdat.cpp:92 +#: Source/DiabloUI/hero/selhero.cpp:163 Source/playerdat.cpp:257 msgid "Monk" msgstr "Монах" -#: Source/DiabloUI/hero/selhero.cpp:166 Source/playerdat.cpp:93 +#: Source/DiabloUI/hero/selhero.cpp:165 Source/playerdat.cpp:258 msgid "Bard" msgstr "Бард" -#: Source/DiabloUI/hero/selhero.cpp:169 Source/playerdat.cpp:94 +#. TRANSLATORS: Player Block end +#. HeroClass::Barbarian +#: Source/DiabloUI/hero/selhero.cpp:168 Source/playerdat.cpp:260 msgid "Barbarian" msgstr "Варвар" @@ -378,12 +380,12 @@ msgstr "Новий Герой" msgid "Save File Exists" msgstr "Збереження Існує" -#: Source/DiabloUI/hero/selhero.cpp:198 Source/gamemenu.cpp:42 +#: Source/DiabloUI/hero/selhero.cpp:198 Source/gamemenu.cpp:45 msgid "Load Game" msgstr "Завантажити Гру" -#: Source/DiabloUI/hero/selhero.cpp:199 Source/gamemenu.cpp:41 -#: Source/gamemenu.cpp:52 Source/multi.cpp:796 +#: Source/DiabloUI/hero/selhero.cpp:199 Source/gamemenu.cpp:44 +#: Source/gamemenu.cpp:55 Source/multi.cpp:792 msgid "New Game" msgstr "Нова Гра" @@ -465,6 +467,7 @@ msgid "Delete Single Player Hero" msgstr "Стерти Героя" #: Source/DiabloUI/hero/selhero.cpp:595 +#, c++-format msgid "Are you sure you want to delete the character \"{:s}\"?" msgstr "Ви впевнені, що хочете стерти героя \"{:s}\"?" @@ -508,8 +511,8 @@ msgstr "Клієнт-Сервер (TCP)" msgid "Offline" msgstr "Офлайн" -#: Source/DiabloUI/multi/selconn.cpp:56 Source/DiabloUI/multi/selgame.cpp:647 -#: Source/DiabloUI/multi/selgame.cpp:671 +#: Source/DiabloUI/multi/selconn.cpp:56 Source/DiabloUI/multi/selgame.cpp:649 +#: Source/DiabloUI/multi/selgame.cpp:673 msgid "Multi Player Game" msgstr "Гра в Мережі" @@ -542,11 +545,12 @@ msgid "Play by yourself with no network exposure." msgstr "Грайте самостійно без доступу до мереж." #: Source/DiabloUI/multi/selconn.cpp:123 +#, c++-format msgid "Players Supported: {:d}" msgstr "Підтримується Гравців: {:d}" -#: Source/DiabloUI/multi/selgame.cpp:86 Source/options.cpp:571 -#: Source/options.cpp:610 Source/quests.cpp:53 +#: Source/DiabloUI/multi/selgame.cpp:86 Source/options.cpp:572 +#: Source/options.cpp:611 Source/quests.cpp:57 msgid "Diablo" msgstr "Diablo" @@ -554,8 +558,8 @@ msgstr "Diablo" msgid "Diablo Shareware" msgstr "Демо-версія Diablo" -#: Source/DiabloUI/multi/selgame.cpp:92 Source/options.cpp:573 -#: Source/options.cpp:624 +#: Source/DiabloUI/multi/selgame.cpp:92 Source/options.cpp:574 +#: Source/options.cpp:625 msgid "Hellfire" msgstr "Hellfire" @@ -568,139 +572,143 @@ msgid "The host is running a different game than you." msgstr "На сервері запущена інша гра." #: Source/DiabloUI/multi/selgame.cpp:100 +#, 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:102 +#, c++-format msgid "Your version {:s} does not match the host {:d}.{:d}.{:d}." msgstr "" "Ваша версія {:s} несумісна з версією {:d}.{:d}.{:d} що присутня на сервері." -#: Source/DiabloUI/multi/selgame.cpp:137 Source/DiabloUI/multi/selgame.cpp:566 +#: Source/DiabloUI/multi/selgame.cpp:139 Source/DiabloUI/multi/selgame.cpp:568 msgid "Description:" msgstr "Описання:" -#: Source/DiabloUI/multi/selgame.cpp:143 +#: Source/DiabloUI/multi/selgame.cpp:145 msgid "Select Action" msgstr "Виберіть" -#: Source/DiabloUI/multi/selgame.cpp:146 Source/DiabloUI/multi/selgame.cpp:323 -#: Source/DiabloUI/multi/selgame.cpp:484 +#: Source/DiabloUI/multi/selgame.cpp:148 Source/DiabloUI/multi/selgame.cpp:325 +#: Source/DiabloUI/multi/selgame.cpp:486 msgid "Create Game" msgstr "Створити Гру" -#: Source/DiabloUI/multi/selgame.cpp:148 +#: Source/DiabloUI/multi/selgame.cpp:150 msgid "Create Public Game" msgstr "Відкрита Гра" -#: Source/DiabloUI/multi/selgame.cpp:149 +#: Source/DiabloUI/multi/selgame.cpp:151 msgid "Join Game" msgstr "Приєднатися" -#: Source/DiabloUI/multi/selgame.cpp:153 +#: Source/DiabloUI/multi/selgame.cpp:155 msgid "Public Games" msgstr "Відкриті Ігри" -#: Source/DiabloUI/multi/selgame.cpp:158 Source/error.cpp:63 +#: Source/DiabloUI/multi/selgame.cpp:160 Source/diablo_msg.cpp:71 msgid "Loading..." msgstr "Завантажується…" #. TRANSLATORS: type of dungeon (i.e. Cathedral, Caves) -#: Source/DiabloUI/multi/selgame.cpp:160 Source/discord/discord.cpp:73 -#: Source/options.cpp:592 Source/panels/charpanel.cpp:137 +#: Source/DiabloUI/multi/selgame.cpp:162 Source/discord/discord.cpp:78 +#: Source/options.cpp:593 Source/panels/charpanel.cpp:138 msgid "None" msgstr "Немає" -#: Source/DiabloUI/multi/selgame.cpp:174 Source/DiabloUI/multi/selgame.cpp:338 -#: Source/DiabloUI/multi/selgame.cpp:364 Source/DiabloUI/multi/selgame.cpp:506 -#: Source/DiabloUI/multi/selgame.cpp:583 +#: Source/DiabloUI/multi/selgame.cpp:176 Source/DiabloUI/multi/selgame.cpp:340 +#: Source/DiabloUI/multi/selgame.cpp:366 Source/DiabloUI/multi/selgame.cpp:508 +#: Source/DiabloUI/multi/selgame.cpp:585 msgid "CANCEL" msgstr "СКАСУВАТИ" -#: Source/DiabloUI/multi/selgame.cpp:214 +#: Source/DiabloUI/multi/selgame.cpp:216 msgid "Create a new game with a difficulty setting of your choice." msgstr "Створити нову гру з вибором рівня важкості." -#: Source/DiabloUI/multi/selgame.cpp:217 +#: Source/DiabloUI/multi/selgame.cpp:219 msgid "" "Create a new public game that anyone can join with a difficulty setting of " "your choice." msgstr "Створити нову відкриту гру без паролю з вибором рівня важкості." -#: Source/DiabloUI/multi/selgame.cpp:221 +#: Source/DiabloUI/multi/selgame.cpp:223 msgid "Enter Game ID to join a game already in progress." msgstr "Введіть ID Гри і приєднайтесь до вже запущеної гри." -#: Source/DiabloUI/multi/selgame.cpp:223 +#: Source/DiabloUI/multi/selgame.cpp:225 msgid "Enter an IP or a hostname to join a game already in progress." msgstr "Введіть IP адресу або ім'я домену і приєднайтесь до вже запущеної гри." -#: Source/DiabloUI/multi/selgame.cpp:228 +#: Source/DiabloUI/multi/selgame.cpp:230 msgid "Join the public game already in progress." msgstr "Приєднайтесь до відкритої гри, яка вже запущена." -#: Source/DiabloUI/multi/selgame.cpp:234 Source/DiabloUI/multi/selgame.cpp:328 -#: Source/DiabloUI/multi/selgame.cpp:389 Source/DiabloUI/multi/selgame.cpp:495 -#: Source/DiabloUI/multi/selgame.cpp:515 Source/automap.cpp:768 -#: Source/discord/discord.cpp:102 +#: Source/DiabloUI/multi/selgame.cpp:236 Source/DiabloUI/multi/selgame.cpp:330 +#: Source/DiabloUI/multi/selgame.cpp:391 Source/DiabloUI/multi/selgame.cpp:497 +#: Source/DiabloUI/multi/selgame.cpp:517 Source/automap.cpp:1459 +#: Source/discord/discord.cpp:106 msgid "Normal" msgstr "Нормальна" -#: Source/DiabloUI/multi/selgame.cpp:237 Source/DiabloUI/multi/selgame.cpp:329 -#: Source/DiabloUI/multi/selgame.cpp:393 Source/automap.cpp:771 -#: Source/discord/discord.cpp:102 +#: Source/DiabloUI/multi/selgame.cpp:239 Source/DiabloUI/multi/selgame.cpp:331 +#: Source/DiabloUI/multi/selgame.cpp:395 Source/automap.cpp:1462 +#: Source/discord/discord.cpp:106 msgid "Nightmare" msgstr "Кошмарна" -#: Source/DiabloUI/multi/selgame.cpp:240 Source/DiabloUI/multi/selgame.cpp:330 -#: Source/DiabloUI/multi/selgame.cpp:397 Source/automap.cpp:774 -#: Source/discord/discord.cpp:68 Source/discord/discord.cpp:102 +#: Source/DiabloUI/multi/selgame.cpp:242 Source/DiabloUI/multi/selgame.cpp:332 +#: Source/DiabloUI/multi/selgame.cpp:399 Source/automap.cpp:1465 +#: Source/discord/discord.cpp:73 Source/discord/discord.cpp:106 msgid "Hell" msgstr "Пекельна" #. TRANSLATORS: {:s} means: Game Difficulty. -#: Source/DiabloUI/multi/selgame.cpp:243 Source/automap.cpp:778 +#: Source/DiabloUI/multi/selgame.cpp:245 Source/automap.cpp:1469 +#, c++-format msgid "Difficulty: {:s}" msgstr "Складність: {:s}" -#: Source/DiabloUI/multi/selgame.cpp:247 Source/gamemenu.cpp:167 +#: Source/DiabloUI/multi/selgame.cpp:249 Source/gamemenu.cpp:170 msgid "Speed: Normal" msgstr "Швикість: Нормальна" -#: Source/DiabloUI/multi/selgame.cpp:250 Source/gamemenu.cpp:165 +#: Source/DiabloUI/multi/selgame.cpp:252 Source/gamemenu.cpp:168 msgid "Speed: Fast" msgstr "Швидкість: Швидка" -#: Source/DiabloUI/multi/selgame.cpp:253 Source/gamemenu.cpp:163 +#: Source/DiabloUI/multi/selgame.cpp:255 Source/gamemenu.cpp:166 msgid "Speed: Faster" msgstr "Швидкість: Дуже Швидка" -#: Source/DiabloUI/multi/selgame.cpp:256 Source/gamemenu.cpp:161 +#: Source/DiabloUI/multi/selgame.cpp:258 Source/gamemenu.cpp:164 msgid "Speed: Fastest" msgstr "Швидкість: Найшвидша" -#: Source/DiabloUI/multi/selgame.cpp:264 +#: Source/DiabloUI/multi/selgame.cpp:266 msgid "Players: " msgstr "Гравці: " -#: Source/DiabloUI/multi/selgame.cpp:326 +#: Source/DiabloUI/multi/selgame.cpp:328 msgid "Select Difficulty" msgstr "Виберіть Складність" -#: Source/DiabloUI/multi/selgame.cpp:344 +#: Source/DiabloUI/multi/selgame.cpp:346 +#, c++-format msgid "Join {:s} Games" msgstr "Приєднатися до {:s} Гри" -#: Source/DiabloUI/multi/selgame.cpp:349 +#: Source/DiabloUI/multi/selgame.cpp:351 msgid "Enter Game ID" msgstr "Введіть ID Гри" -#: Source/DiabloUI/multi/selgame.cpp:351 +#: Source/DiabloUI/multi/selgame.cpp:353 msgid "Enter address" msgstr "Введіть адресу" -#: Source/DiabloUI/multi/selgame.cpp:390 +#: Source/DiabloUI/multi/selgame.cpp:392 msgid "" "Normal Difficulty\n" "This is where a starting character should begin the quest to defeat Diablo." @@ -708,7 +716,7 @@ msgstr "" "Нормальна Складність\n" "Рекомендується для нових героїв, що хочуть перемогти Діабло." -#: Source/DiabloUI/multi/selgame.cpp:394 +#: Source/DiabloUI/multi/selgame.cpp:396 msgid "" "Nightmare Difficulty\n" "The denizens of the Labyrinth have been bolstered and will prove to be a " @@ -718,7 +726,7 @@ msgstr "" "Мешканці Лабіринту стали сильніші і їх буде важче перемогти. Рекомендується " "тільки для досвідчених героїв." -#: Source/DiabloUI/multi/selgame.cpp:398 +#: Source/DiabloUI/multi/selgame.cpp:400 msgid "" "Hell Difficulty\n" "The most powerful of the underworld's creatures lurk at the gateway into " @@ -728,7 +736,7 @@ msgstr "" "Наймогутніші пекельні створіння підстерігають Вас біля воріт в Пекло. Тільки " "найбільш досвідчені герої відважаться підти туди." -#: Source/DiabloUI/multi/selgame.cpp:413 +#: Source/DiabloUI/multi/selgame.cpp:415 msgid "" "Your character must reach level 20 before you can enter a multiplayer game " "of Nightmare difficulty." @@ -736,7 +744,7 @@ msgstr "" "Ваш герой має бути 20 рівня щоб приєднатися до мережевої гри Кошмарної " "складності." -#: Source/DiabloUI/multi/selgame.cpp:415 +#: Source/DiabloUI/multi/selgame.cpp:417 msgid "" "Your character must reach level 30 before you can enter a multiplayer game " "of Hell difficulty." @@ -744,23 +752,23 @@ msgstr "" "Ваш герой має бути 30 рівня щоб приєднатися до мережевої гри Пекельної " "складності." -#: Source/DiabloUI/multi/selgame.cpp:493 +#: Source/DiabloUI/multi/selgame.cpp:495 msgid "Select Game Speed" msgstr "Виберіть Швидкість Гри" -#: Source/DiabloUI/multi/selgame.cpp:496 Source/DiabloUI/multi/selgame.cpp:519 +#: Source/DiabloUI/multi/selgame.cpp:498 Source/DiabloUI/multi/selgame.cpp:521 msgid "Fast" msgstr "Швидка" -#: Source/DiabloUI/multi/selgame.cpp:497 Source/DiabloUI/multi/selgame.cpp:523 +#: Source/DiabloUI/multi/selgame.cpp:499 Source/DiabloUI/multi/selgame.cpp:525 msgid "Faster" msgstr "Дуже Швидка" -#: Source/DiabloUI/multi/selgame.cpp:498 Source/DiabloUI/multi/selgame.cpp:527 +#: Source/DiabloUI/multi/selgame.cpp:500 Source/DiabloUI/multi/selgame.cpp:529 msgid "Fastest" msgstr "Найшвидша" -#: Source/DiabloUI/multi/selgame.cpp:516 +#: Source/DiabloUI/multi/selgame.cpp:518 msgid "" "Normal Speed\n" "This is where a starting character should begin the quest to defeat Diablo." @@ -768,7 +776,7 @@ msgstr "" "Нормальна Швидкість\n" "Рекомендується для нових героїв, що хочуть перемогти Діабло." -#: Source/DiabloUI/multi/selgame.cpp:520 +#: Source/DiabloUI/multi/selgame.cpp:522 msgid "" "Fast Speed\n" "The denizens of the Labyrinth have been hastened and will prove to be a " @@ -777,7 +785,7 @@ msgstr "" "Мешканці Лабіринту стали швидше і їх буде важче перемогти. Рекомендується " "тільки для досвідчених героїв." -#: Source/DiabloUI/multi/selgame.cpp:524 +#: Source/DiabloUI/multi/selgame.cpp:526 msgid "" "Faster Speed\n" "Most monsters of the dungeon will seek you out quicker than ever before. " @@ -787,7 +795,7 @@ msgstr "" "Монстри підземелля вишукують тебе набагато швидше. Тільки досвідчені " "чемпіони спробують удачу на цій швидкості." -#: Source/DiabloUI/multi/selgame.cpp:528 +#: Source/DiabloUI/multi/selgame.cpp:530 msgid "" "Fastest Speed\n" "The minions of the underworld will rush to attack without hesitation. Only a " @@ -797,7 +805,7 @@ msgstr "" "Служники Пекла відразу атакують без будь-яких вагань. Тільки справжні " "безумці будуть встигати за ними." -#: Source/DiabloUI/multi/selgame.cpp:572 Source/DiabloUI/multi/selgame.cpp:577 +#: Source/DiabloUI/multi/selgame.cpp:574 Source/DiabloUI/multi/selgame.cpp:579 msgid "Enter Password" msgstr "Введіть Пароль" @@ -809,11 +817,11 @@ msgstr "Зайти в Hellfire" msgid "Switch to Diablo" msgstr "Перейти в Diablo" -#: Source/DiabloUI/selyesno.cpp:57 Source/stores.cpp:1001 +#: Source/DiabloUI/selyesno.cpp:57 Source/stores.cpp:961 msgid "Yes" msgstr "Так" -#: Source/DiabloUI/selyesno.cpp:58 Source/stores.cpp:1002 +#: Source/DiabloUI/selyesno.cpp:58 Source/stores.cpp:962 msgid "No" msgstr "Ні" @@ -821,27 +829,27 @@ msgstr "Ні" msgid "Press gamepad buttons to change." msgstr "Натисніть на кнопку геймпада щоб змінити." -#: Source/DiabloUI/settingsmenu.cpp:424 +#: Source/DiabloUI/settingsmenu.cpp:423 msgid "Bound key:" msgstr "Прив'язана клавіша:" -#: Source/DiabloUI/settingsmenu.cpp:460 +#: Source/DiabloUI/settingsmenu.cpp:459 msgid "Press any key to change." msgstr "Натисніть на бажану клавішу." -#: Source/DiabloUI/settingsmenu.cpp:462 +#: Source/DiabloUI/settingsmenu.cpp:461 msgid "Unbind key" msgstr "Прив'язати клавішу" -#: Source/DiabloUI/settingsmenu.cpp:466 +#: Source/DiabloUI/settingsmenu.cpp:465 msgid "Bound button combo:" msgstr "Прив'язана комбінація кнопок:" -#: Source/DiabloUI/settingsmenu.cpp:475 +#: Source/DiabloUI/settingsmenu.cpp:474 msgid "Unbind button combo" msgstr "Відв'язати комбінацію кнопок" -#: Source/DiabloUI/settingsmenu.cpp:519 Source/gamemenu.cpp:65 +#: Source/DiabloUI/settingsmenu.cpp:518 Source/gamemenu.cpp:68 msgid "Previous Menu" msgstr "Попереднє Меню" @@ -896,7 +904,7 @@ msgstr "" "ліцензований під CC-BY 4.0. Порт також використовує SDL що ліцензований під " "zlib-ліцензією. Дивіться файл ReadMe щоб дізнатися більше." -#: Source/DiabloUI/title.cpp:57 +#: Source/DiabloUI/title.cpp:59 msgid "Copyright © 1996-2001 Blizzard Entertainment" msgstr "Авторські права © 1996-2001 Blizzard Entertainment" @@ -906,6 +914,7 @@ msgstr "Помилка" #. TRANSLATORS: Error message that displays relevant information for bug report #: Source/appfat.cpp:67 +#, c++-format msgid "" "{:s}\n" "\n" @@ -916,6 +925,7 @@ msgstr "" "Сталася помилка в файлі: {:s}, лінія {:d}" #: Source/appfat.cpp:76 +#, c++-format msgid "" "Unable to open main data archive ({:s}).\n" "\n" @@ -931,6 +941,7 @@ msgstr "Помилка Файлу Даних" #. TRANSLATORS: Error when Program is not allowed to write data #: Source/appfat.cpp:87 +#, c++-format msgid "" "Unable to write to location:\n" "{:s}" @@ -942,95 +953,98 @@ msgstr "" msgid "Read-Only Directory Error" msgstr "Папка доступна тільки для читання" -#: Source/automap.cpp:723 +#: Source/automap.cpp:1414 msgid "Game: " msgstr "Гра: " -#: Source/automap.cpp:731 +#: Source/automap.cpp:1422 msgid "Offline Game" msgstr "Офлайн Гра" -#: Source/automap.cpp:733 +#: Source/automap.cpp:1424 msgid "Password: " msgstr "Пароль: " -#: Source/automap.cpp:736 +#: Source/automap.cpp:1427 msgid "Public Game" msgstr "Відкрита Гра" -#: Source/automap.cpp:750 +#: Source/automap.cpp:1441 +#, c++-format msgid "Level: Nest {:d}" msgstr "Рівень: Гніздо {:d}" -#: Source/automap.cpp:753 +#: Source/automap.cpp:1444 +#, c++-format msgid "Level: Crypt {:d}" msgstr "Рівень: Склеп {:d}" -#: Source/automap.cpp:756 Source/discord/discord.cpp:68 Source/objects.cpp:151 +#: Source/automap.cpp:1447 Source/discord/discord.cpp:73 Source/objects.cpp:153 msgid "Town" msgstr "Місто" -#: Source/automap.cpp:759 +#: Source/automap.cpp:1450 +#, c++-format msgid "Level: {:d}" msgstr "Рівень: {:d}" -#: Source/control.cpp:160 +#: Source/control.cpp:174 msgid "Tab" msgstr "Tab" -#: Source/control.cpp:160 +#: Source/control.cpp:174 msgid "Esc" msgstr "Esc" -#: Source/control.cpp:160 +#: Source/control.cpp:174 msgid "Enter" msgstr "Enter" -#: Source/control.cpp:163 +#: Source/control.cpp:177 msgid "Character Information" msgstr "Інформація про Героя" -#: Source/control.cpp:164 +#: Source/control.cpp:178 msgid "Quests log" msgstr "Журнал" -#: Source/control.cpp:165 +#: Source/control.cpp:179 msgid "Automap" msgstr "Автокарта" -#: Source/control.cpp:166 +#: Source/control.cpp:180 msgid "Main Menu" msgstr "Головне Меню" -#: Source/control.cpp:167 Source/diablo.cpp:1701 Source/diablo.cpp:2019 +#: Source/control.cpp:181 Source/diablo.cpp:1735 Source/diablo.cpp:2068 msgid "Inventory" msgstr "Інвентар" -#: Source/control.cpp:168 +#: Source/control.cpp:182 msgid "Spell book" msgstr "Книга Магії" -#: Source/control.cpp:169 +#: Source/control.cpp:183 msgid "Send Message" msgstr "Відправити Повідомлення" -#: Source/control.cpp:348 +#: Source/control.cpp:369 msgid "Available Commands:" msgstr "Доступні Команди:" -#: Source/control.cpp:356 +#: Source/control.cpp:377 Source/control.cpp:567 msgid "Command " msgstr "Команда " -#: Source/control.cpp:356 -msgid " is unkown." +#: Source/control.cpp:377 Source/control.cpp:567 +msgid " is unknown." msgstr " невідома." -#: Source/control.cpp:359 Source/control.cpp:360 +#: Source/control.cpp:380 Source/control.cpp:381 msgid "Description: " msgstr "Опис: " -#: Source/control.cpp:359 +#: Source/control.cpp:380 msgid "" "\n" "Parameters: No additional parameter needed." @@ -1038,7 +1052,7 @@ msgstr "" "\n" "Параметри: Додаткові параметри не потрібні." -#: Source/control.cpp:360 +#: Source/control.cpp:381 msgid "" "\n" "Parameters: " @@ -1046,317 +1060,371 @@ msgstr "" "\n" "Параметри: " -#: Source/control.cpp:380 Source/control.cpp:412 +#: Source/control.cpp:401 Source/control.cpp:433 msgid "Arenas are only supported in multiplayer." msgstr "Арени підтримуються тільки в мережевій грі." -#: Source/control.cpp:385 +#: Source/control.cpp:406 msgid "What arena do you want to visit?" msgstr "Яку арену ви хочете відвідати?" -#: Source/control.cpp:393 +#: Source/control.cpp:414 msgid "Invalid arena-number. Valid numbers are:" msgstr "Невірний номер арени. Допустимі номери:" -#: Source/control.cpp:399 +#: Source/control.cpp:420 msgid "To enter a arena, you need to be in town or another arena." msgstr "Щоб потрапити на арену, треба бути в місті або іншій арені." -#: Source/control.cpp:436 +#: Source/control.cpp:458 msgid "Inspecting only supported in multiplayer." msgstr "Огляд гравців підтримується тільки в мережевій грі." -#: Source/control.cpp:441 Source/control.cpp:730 +#: Source/control.cpp:463 Source/control.cpp:754 msgid "Stopped inspecting players." msgstr "Припинив огляд гравців." -#: Source/control.cpp:451 -msgid "Inspecting player: " -msgstr "Огляд гравця: " - -#: Source/control.cpp:460 +#: Source/control.cpp:478 msgid "No players found with such a name" msgstr "Гравців з таким ім'ям не знайдено" -#: Source/control.cpp:524 -msgid "/help" -msgstr "/help" +#: Source/control.cpp:484 +msgid "Inspecting player: " +msgstr "Огляд гравця: " -#: Source/control.cpp:524 +#: Source/control.cpp:553 msgid "Prints help overview or help for a specific command." msgstr "Друкує огляд довідки або довідку для конкретної команди." -#: Source/control.cpp:524 +#: Source/control.cpp:553 msgid "[command]" msgstr "[команда]" -#: Source/control.cpp:525 -msgid "/arena" -msgstr "/arena" - -#: Source/control.cpp:525 +#: Source/control.cpp:554 msgid "Enter a PvP Arena." msgstr "Зайти на PvP-арену." -#: Source/control.cpp:525 +#: Source/control.cpp:554 msgid "" msgstr "<номер-арени>" -#: Source/control.cpp:526 -msgid "/arenapot" -msgstr "/arenapot" - -#: Source/control.cpp:526 +#: Source/control.cpp:555 msgid "Gives Arena Potions." msgstr "Дає Зілля Арени." -#: Source/control.cpp:526 +#: Source/control.cpp:555 msgid "" msgstr "<номер>" -#: Source/control.cpp:527 -msgid "/inspect" -msgstr "/inspect" - -#: Source/control.cpp:527 +#: Source/control.cpp:556 msgid "Inspects stats and equipment of another player." msgstr "Оглядає статистику та спорядження іншого гравця." -#: Source/control.cpp:527 +#: Source/control.cpp:556 msgid "" msgstr "<ім'я гравця>" -#: Source/control.cpp:528 -msgid "/seedinfo" -msgstr "/seedinfo" - -#: Source/control.cpp:528 +#: Source/control.cpp:557 msgid "Show seed infos for current level." msgstr "Показати інформацію про початкові значення ГВЧ для поточного рівня." -#: Source/control.cpp:538 -msgid "Command \"" -msgstr "Команда \"" - -#: Source/control.cpp:1012 +#: Source/control.cpp:1049 msgid "Player friendly" msgstr "Дружній гравець" -#: Source/control.cpp:1014 +#: Source/control.cpp:1051 msgid "Player attack" msgstr "Гравець атакує" -#: Source/control.cpp:1017 +#: Source/control.cpp:1054 +#, c++-format msgid "Hotkey: {:s}" msgstr "Гаряча клавіша: {:s}" -#: Source/control.cpp:1024 +#: Source/control.cpp:1061 msgid "Select current spell button" msgstr "Виберіть кнопку для чар" -#: Source/control.cpp:1027 +#: Source/control.cpp:1064 msgid "Hotkey: 's'" msgstr "Гаряча клавіша: 's'" -#: Source/control.cpp:1033 Source/panels/spell_list.cpp:151 +#: Source/control.cpp:1070 Source/panels/spell_list.cpp:152 +#, c++-format msgid "{:s} Skill" msgstr "{:s} Талант" -#: Source/control.cpp:1036 Source/panels/spell_list.cpp:158 +#: Source/control.cpp:1073 Source/panels/spell_list.cpp:159 +#, c++-format msgid "{:s} Spell" msgstr "Чари {:s}" -#: Source/control.cpp:1038 Source/panels/spell_list.cpp:163 +#: Source/control.cpp:1075 Source/panels/spell_list.cpp:164 msgid "Spell Level 0 - Unusable" msgstr "0-овий Рівень Чар - Невикористовні" -#: Source/control.cpp:1038 Source/panels/spell_list.cpp:165 +#: Source/control.cpp:1075 Source/panels/spell_list.cpp:166 +#, c++-format msgid "Spell Level {:d}" msgstr "Рівень Чар {:d}" -#: Source/control.cpp:1041 Source/panels/spell_list.cpp:172 +#: Source/control.cpp:1078 Source/panels/spell_list.cpp:173 +#, c++-format msgid "Scroll of {:s}" msgstr "Сувій {:s}" -#: Source/control.cpp:1046 Source/panels/spell_list.cpp:177 +#: Source/control.cpp:1082 Source/panels/spell_list.cpp:177 +#, c++-format msgid "{:d} Scroll" msgid_plural "{:d} Scrolls" msgstr[0] "{:d} Сувій" msgstr[1] "{:d} Сувія" msgstr[2] "{:d} Сувоїв" -#: Source/control.cpp:1049 Source/panels/spell_list.cpp:184 +#: Source/control.cpp:1085 Source/panels/spell_list.cpp:184 +#, c++-format msgid "Staff of {:s}" msgstr "Посох {:s}" -#: Source/control.cpp:1050 Source/panels/spell_list.cpp:186 +#: Source/control.cpp:1086 Source/panels/spell_list.cpp:186 +#, c++-format msgid "{:d} Charge" msgid_plural "{:d} Charges" msgstr[0] "{:d} Заряд" msgstr[1] "{:d} Заряда" msgstr[2] "{:d} Зарядів" -#: Source/control.cpp:1175 Source/inv.cpp:1878 Source/items.cpp:3643 +#: Source/control.cpp:1205 Source/inv.cpp:1873 Source/items.cpp:3607 +#, c++-format msgid "{:s} gold piece" msgid_plural "{:s} gold pieces" msgstr[0] "{:s} золота монета" msgstr[1] "{:s} золоті монети" msgstr[2] "{:s} золотих монет" -#: Source/control.cpp:1177 +#: Source/control.cpp:1207 msgid "Requirements not met" msgstr "Вимоги не виконані" -#: Source/control.cpp:1206 +#: Source/control.cpp:1236 +#, c++-format msgid "{:s}, Level: {:d}" msgstr "{:s}, Рівень {:d}" -#: Source/control.cpp:1207 +#: Source/control.cpp:1237 +#, c++-format msgid "Hit Points {:d} of {:d}" msgstr "Здоров'я {:d} з {:d}" -#: Source/control.cpp:1238 +#: Source/control.cpp:1268 msgid "Level Up" msgstr "Новий Рівень" #. TRANSLATORS: {:s} is a number with separators. Dialog is shown when splitting a stash of Gold. -#: Source/control.cpp:1346 +#: Source/control.cpp:1381 +#, 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[1] "У тебе {:s} золоті монети. Скільки ти хочеш забрати?" msgstr[2] "У тебе {:s} золотих монет. Скільки ти хочеш забрати?" -#: Source/cursor.cpp:326 +#: Source/cursor.cpp:641 msgid "Town Portal" msgstr "Портал в Місто" -#: Source/cursor.cpp:327 +#: Source/cursor.cpp:642 +#, c++-format msgid "from {:s}" msgstr "з {:s}" -#: Source/cursor.cpp:340 +#: Source/cursor.cpp:655 msgid "Portal to" msgstr "Портал в" -#: Source/cursor.cpp:341 +#: Source/cursor.cpp:656 msgid "The Unholy Altar" msgstr "Нечестивий Вівтар" -#: Source/cursor.cpp:341 +#: Source/cursor.cpp:656 msgid "level 15" msgstr "рівень 15" -#: Source/diablo.cpp:125 -msgid "I need help! Come Here!" +#. TRANSLATORS: Error message when a data file is missing or corrupt. Arguments are {file name} +#: Source/data/file.cpp:36 +#, c++-format +msgid "Unable to load data from file {0}" +msgstr "Не вдалося завантажити дані з файлу {0}" + +#. TRANSLATORS: Error message when a data file is empty or only contains the header row. Arguments are {file name} +#: Source/data/file.cpp:41 +#, c++-format +msgid "{0} is incomplete, please check the file contents." +msgstr "{0} є неповним, будь ласка, перевірте вміст файлу." + +#. TRANSLATORS: Error message when a data file doesn't contain the expected columns. Arguments are {file name} +#: Source/data/file.cpp:46 +#, c++-format +msgid "" +"Your {0} file doesn't have the expected columns, please make sure it matches " +"the documented format." +msgstr "" +"Ваш файл {0} не містить очікуваних стовпців, будь ласка, переконайтеся, що " +"він відповідає задокументованому формату." + +#. TRANSLATORS: Error message when parsing a data file and a text value is encountered when a number is expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} +#: Source/data/file.cpp:61 +#, c++-format +msgid "Non-numeric value {0} for {1} in {2} at row {3} and column {4}" +msgstr "Нечислове значення {0} для {1} у {2} у рядку {3} та стовпчику {4}" + +#. TRANSLATORS: Error message when parsing a data file and we find a number larger than expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} +#: Source/data/file.cpp:67 +#, c++-format +msgid "Out of range value {0} for {1} in {2} at row {3} and column {4}" +msgstr "" +"Значення поза діапазоном {0} для {1} в {2} в рядку {3} та стовпчику {4}" + +#. TRANSLATORS: Error message when we find an unrecognised value in a key column. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} +#: Source/data/file.cpp:73 +#, c++-format +msgid "Invalid value {0} for {1} in {2} at row {3} and column {4}" +msgstr "Невірне значення {0} для {1} в {2} в рядку {3} та стовпчику {4}" + +#: Source/diablo.cpp:132 +msgid "I need help! Come here!" msgstr "Мені потрібна допомога! Ідіть сюди!" -#: Source/diablo.cpp:126 +#: Source/diablo.cpp:133 msgid "Follow me." msgstr "За мною." -#: Source/diablo.cpp:127 +#: Source/diablo.cpp:134 msgid "Here's something for you." msgstr "Ось щось для тебе." -#: Source/diablo.cpp:128 +#: Source/diablo.cpp:135 msgid "Now you DIE!" msgstr "А тепер ПОМРИ!" +#: Source/diablo.cpp:136 +msgid "Heal yourself!" +msgstr "Зціли себе!" + +#: Source/diablo.cpp:137 +msgid "Watch out!" +msgstr "Обережно!" + +#: Source/diablo.cpp:138 +msgid "Thanks." +msgstr "Дякую." + +#: Source/diablo.cpp:139 +msgid "Retreat!" +msgstr "Відступаємо!" + +#: Source/diablo.cpp:140 +msgid "Sorry." +msgstr "Вибач." + +#: Source/diablo.cpp:141 +msgid "I'm waiting." +msgstr "Я чекаю." + #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:917 +#: Source/diablo.cpp:912 msgid "Print this message and exit" msgstr "Показати це повідомлення і вийти" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:918 +#: Source/diablo.cpp:913 msgid "Print the version and exit" msgstr "Показати версію і вийти" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:919 +#: Source/diablo.cpp:914 msgid "Specify the folder of diabdat.mpq" msgstr "Визначити папку з diabdat.mpq" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:920 +#: Source/diablo.cpp:915 msgid "Specify the folder of save files" msgstr "Визначити папку з збереженнями" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:921 +#: Source/diablo.cpp:916 msgid "Specify the location of diablo.ini" msgstr "Визначити папку з diablo.ini" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:922 +#: Source/diablo.cpp:917 msgid "Specify the language code (e.g. en or pt_BR)" msgstr "Вкажіть код мови (наприклад, en або pt_BR)" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:923 +#: Source/diablo.cpp:918 msgid "Skip startup videos" msgstr "Пропустити заставки" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:924 +#: Source/diablo.cpp:919 msgid "Display frames per second" msgstr "Показати кадри в секунду" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:925 +#: Source/diablo.cpp:920 msgid "Enable verbose logging" msgstr "Ввімкнути велемовне логування" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:927 +#: Source/diablo.cpp:922 msgid "Record a demo file" msgstr "Записати демо" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:928 +#: Source/diablo.cpp:923 msgid "Play a demo file" msgstr "Зіграти демо" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:929 +#: Source/diablo.cpp:924 msgid "Disable all frame limiting during demo playback" msgstr "Віключити ліміт кадрів під час програшу демо" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:932 +#: Source/diablo.cpp:927 msgid "Game selection:" msgstr "Вибір гри:" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:934 +#: Source/diablo.cpp:929 msgid "Force Shareware mode" msgstr "Примусити режим демо-версії" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:935 +#: Source/diablo.cpp:930 msgid "Force Diablo mode" msgstr "Примусити режим Diablo" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:936 +#: Source/diablo.cpp:931 msgid "Force Hellfire mode" msgstr "Примусити режим Hellfire" #. TRANSLATORS: Commandline Option -#: Source/diablo.cpp:937 +#: Source/diablo.cpp:932 msgid "Hellfire options:" msgstr "Опції Hellfire:" -#: Source/diablo.cpp:947 +#: Source/diablo.cpp:942 msgid "Report bugs at https://github.com/diasurgical/devilutionX/" msgstr "Пишіть про помилки на https://github.com/diasurgical/devilutionX/" -#: Source/diablo.cpp:1103 +#: Source/diablo.cpp:1113 msgid "Please update devilutionx.mpq and fonts.mpq to the latest version" msgstr "Будь ласка, оновіть devilutionx.mpq та fonts.mpq до останньої версії" -#: Source/diablo.cpp:1105 +#: Source/diablo.cpp:1115 msgid "" "Failed to load UI resources.\n" "\n" @@ -1366,730 +1434,853 @@ msgstr "" "\n" "Впевніться що devilutionx.mpq в папці гри і актуальної версії." -#: Source/diablo.cpp:1109 +#: Source/diablo.cpp:1119 msgid "Please update fonts.mpq to the latest version" msgstr "Будь ласка, оновіть fonts.mpq до останньої версії" -#: Source/diablo.cpp:1423 +#: Source/diablo.cpp:1437 msgid "-- Network timeout --" msgstr "-- Тайм-аут мережі --" -#: Source/diablo.cpp:1424 +#: Source/diablo.cpp:1438 msgid "-- Waiting for players --" msgstr "-- Чекаємо гравців --" -#: Source/diablo.cpp:1447 +#: Source/diablo.cpp:1461 msgid "No help available" msgstr "Допомога недоступна" -#: Source/diablo.cpp:1448 +#: Source/diablo.cpp:1462 msgid "while in stores" msgstr "в магазинах" -#: Source/diablo.cpp:1587 Source/diablo.cpp:1851 +#: Source/diablo.cpp:1613 Source/diablo.cpp:1900 +#, c++-format msgid "Belt item {}" msgstr "Предмет з Поясу {}" -#: Source/diablo.cpp:1588 Source/diablo.cpp:1852 +#: Source/diablo.cpp:1614 Source/diablo.cpp:1901 msgid "Use Belt item." msgstr "Використати предмет з Поясу." -#: Source/diablo.cpp:1603 Source/diablo.cpp:1867 +#: Source/diablo.cpp:1629 Source/diablo.cpp:1916 +#, c++-format msgid "Quick spell {}" msgstr "Швидкі Чари {}" -#: Source/diablo.cpp:1604 Source/diablo.cpp:1868 +#: Source/diablo.cpp:1630 Source/diablo.cpp:1917 msgid "Hotkey for skill or spell." msgstr "Гаряча клавіша для чар або таланту." -#: Source/diablo.cpp:1622 Source/diablo.cpp:1995 +#: Source/diablo.cpp:1648 Source/diablo.cpp:2044 msgid "Use health potion" msgstr "Використати зілля Зцілення" -#: Source/diablo.cpp:1623 Source/diablo.cpp:1996 +#: Source/diablo.cpp:1649 Source/diablo.cpp:2045 msgid "Use health potions from belt." msgstr "Використати зілля Зцілення з пояса." -#: Source/diablo.cpp:1630 Source/diablo.cpp:2003 +#: Source/diablo.cpp:1656 Source/diablo.cpp:2052 msgid "Use mana potion" msgstr "Використати зілля Мани" -#: Source/diablo.cpp:1631 Source/diablo.cpp:2004 +#: Source/diablo.cpp:1657 Source/diablo.cpp:2053 msgid "Use mana potions from belt." msgstr "Використати зілля Мани з пояса." -#: Source/diablo.cpp:1638 Source/diablo.cpp:2049 +#: Source/diablo.cpp:1664 Source/diablo.cpp:2098 msgid "Speedbook" msgstr "Швидка Книга Чар" -#: Source/diablo.cpp:1639 Source/diablo.cpp:2050 +#: Source/diablo.cpp:1665 Source/diablo.cpp:2099 msgid "Open Speedbook." msgstr "Відкрити Швидку Книгу Чар." -#: Source/diablo.cpp:1646 Source/diablo.cpp:2182 +#: Source/diablo.cpp:1672 Source/diablo.cpp:2231 msgid "Quick save" msgstr "Швидке Збереження" -#: Source/diablo.cpp:1647 Source/diablo.cpp:2183 +#: Source/diablo.cpp:1673 Source/diablo.cpp:2232 msgid "Saves the game." msgstr "Зберігає гру." -#: Source/diablo.cpp:1654 Source/diablo.cpp:2190 +#: Source/diablo.cpp:1680 Source/diablo.cpp:2239 msgid "Quick load" msgstr "Швидке Завантаження" -#: Source/diablo.cpp:1655 Source/diablo.cpp:2191 +#: Source/diablo.cpp:1681 Source/diablo.cpp:2240 msgid "Loads the game." msgstr "Завантажує гру." -#: Source/diablo.cpp:1663 +#: Source/diablo.cpp:1689 msgid "Quit game" msgstr "Вийти з Гри" -#: Source/diablo.cpp:1664 +#: Source/diablo.cpp:1690 msgid "Closes the game." msgstr "Закриває гру." -#: Source/diablo.cpp:1670 +#: Source/diablo.cpp:1696 msgid "Stop hero" msgstr "Зупинити героя" -#: Source/diablo.cpp:1671 +#: Source/diablo.cpp:1697 msgid "Stops walking and cancel pending actions." msgstr "Зупиняє ходьбу і незакінчені дії." -#: Source/diablo.cpp:1678 Source/diablo.cpp:2198 +#: Source/diablo.cpp:1704 Source/diablo.cpp:2247 msgid "Item highlighting" msgstr "Підствітлення Предметів" -#: Source/diablo.cpp:1679 Source/diablo.cpp:2199 +#: Source/diablo.cpp:1705 Source/diablo.cpp:2248 msgid "Show/hide items on ground." msgstr "Показати/Сховати предмети на землі." -#: Source/diablo.cpp:1685 Source/diablo.cpp:2205 +#: Source/diablo.cpp:1711 Source/diablo.cpp:2254 msgid "Toggle item highlighting" msgstr "Постійне підсвітлення предметів" -#: Source/diablo.cpp:1686 Source/diablo.cpp:2206 +#: Source/diablo.cpp:1712 Source/diablo.cpp:2255 msgid "Permanent show/hide items on ground." msgstr "Постійно показувати/ховати предмети на землі." -#: Source/diablo.cpp:1692 Source/diablo.cpp:2059 +#: Source/diablo.cpp:1718 Source/diablo.cpp:2108 msgid "Toggle automap" msgstr "Автокарта" -#: Source/diablo.cpp:1693 Source/diablo.cpp:2060 +#: Source/diablo.cpp:1719 Source/diablo.cpp:2109 msgid "Toggles if automap is displayed." msgstr "Перемикає відображення автокарти." -#: Source/diablo.cpp:1702 Source/diablo.cpp:2020 +#: Source/diablo.cpp:1726 +msgid "Cycle map type" +msgstr "Циклювати тип карти" + +#: Source/diablo.cpp:1727 +msgid "Opaque -> Transparent -> Minimap -> None" +msgstr "Непрозора -> Прозора -> Мінікарта -> Немає" + +#: Source/diablo.cpp:1736 Source/diablo.cpp:2069 msgid "Open Inventory screen." msgstr "Відкрити вікно Інвентаря." -#: Source/diablo.cpp:1709 Source/diablo.cpp:2011 +#: Source/diablo.cpp:1743 Source/diablo.cpp:2060 msgid "Character" msgstr "Герой" -#: Source/diablo.cpp:1710 Source/diablo.cpp:2012 +#: Source/diablo.cpp:1744 Source/diablo.cpp:2061 msgid "Open Character screen." msgstr "Відкрити вікно Героя." -#: Source/diablo.cpp:1717 Source/diablo.cpp:2029 +#: Source/diablo.cpp:1751 Source/diablo.cpp:2078 msgid "Quest log" msgstr "Журнал" -#: Source/diablo.cpp:1718 Source/diablo.cpp:2030 +#: Source/diablo.cpp:1752 Source/diablo.cpp:2079 msgid "Open Quest log." msgstr "Відкрити Журнал квестів." -#: Source/diablo.cpp:1725 Source/diablo.cpp:2039 +#: Source/diablo.cpp:1759 Source/diablo.cpp:2088 msgid "Spellbook" msgstr "Книга Магії" -#: Source/diablo.cpp:1726 Source/diablo.cpp:2040 +#: Source/diablo.cpp:1760 Source/diablo.cpp:2089 msgid "Open Spellbook." msgstr "Відкрити Книгу Чар." -#: Source/diablo.cpp:1734 +#: Source/diablo.cpp:1768 +#, c++-format msgid "Quick Message {}" msgstr "Швидке Повідомлення {}" -#: Source/diablo.cpp:1735 +#: Source/diablo.cpp:1769 msgid "Use Quick Message in chat." msgstr "Відправити Швидке Повідомлення в чат." -#: Source/diablo.cpp:1744 Source/diablo.cpp:2212 +#: Source/diablo.cpp:1778 Source/diablo.cpp:2261 msgid "Hide Info Screens" msgstr "Сховати Вікна" -#: Source/diablo.cpp:1745 Source/diablo.cpp:2213 +#: Source/diablo.cpp:1779 Source/diablo.cpp:2262 msgid "Hide all info screens." msgstr "Ховає всі інформаційні вікна." -#: Source/diablo.cpp:1765 Source/diablo.cpp:2233 Source/options.cpp:986 +#: Source/diablo.cpp:1802 Source/diablo.cpp:2285 Source/options.cpp:986 msgid "Zoom" msgstr "Збільшення" -#: Source/diablo.cpp:1766 Source/diablo.cpp:2234 +#: Source/diablo.cpp:1803 Source/diablo.cpp:2286 msgid "Zoom Game Screen." msgstr "Збільшити/зменшити екран гри." -#: Source/diablo.cpp:1776 Source/diablo.cpp:2244 +#: Source/diablo.cpp:1813 Source/diablo.cpp:2296 msgid "Pause Game" msgstr "Зупинити Гру" -#: Source/diablo.cpp:1777 Source/diablo.cpp:2245 +#: Source/diablo.cpp:1814 Source/diablo.cpp:1820 Source/diablo.cpp:2297 msgid "Pauses the game." msgstr "Ставить гру на паузу." -#: Source/diablo.cpp:1782 Source/diablo.cpp:2250 +#: Source/diablo.cpp:1819 +msgid "Pause Game (Alternate)" +msgstr "Зупинити гру (Альтернатива)" + +#: Source/diablo.cpp:1825 Source/diablo.cpp:2302 msgid "Decrease Gamma" msgstr "Зменшити Гамму" -#: Source/diablo.cpp:1783 Source/diablo.cpp:2251 +#: Source/diablo.cpp:1826 Source/diablo.cpp:2303 msgid "Reduce screen brightness." msgstr "Зменшити яскравість екрану." -#: Source/diablo.cpp:1790 Source/diablo.cpp:2258 +#: Source/diablo.cpp:1833 Source/diablo.cpp:2310 msgid "Increase Gamma" msgstr "Збільшити Гамму" -#: Source/diablo.cpp:1791 Source/diablo.cpp:2259 +#: Source/diablo.cpp:1834 Source/diablo.cpp:2311 msgid "Increase screen brightness." msgstr "Збільшити яскравість екрану." -#: Source/diablo.cpp:1798 Source/diablo.cpp:2266 +#: Source/diablo.cpp:1841 Source/diablo.cpp:2318 msgid "Help" msgstr "Допомога" -#: Source/diablo.cpp:1799 Source/diablo.cpp:2267 +#: Source/diablo.cpp:1842 Source/diablo.cpp:2319 msgid "Open Help Screen." msgstr "Відкрити вікно Допомоги." -#: Source/diablo.cpp:1806 Source/diablo.cpp:2274 +#: Source/diablo.cpp:1849 Source/diablo.cpp:2326 msgid "Screenshot" msgstr "Знімок екрана" -#: Source/diablo.cpp:1807 Source/diablo.cpp:2275 +#: Source/diablo.cpp:1850 Source/diablo.cpp:2327 msgid "Takes a screenshot." msgstr "Робить знімок екрана." -#: Source/diablo.cpp:1813 Source/diablo.cpp:2281 +#: Source/diablo.cpp:1856 Source/diablo.cpp:2333 msgid "Game info" msgstr "Інформація про гру" -#: Source/diablo.cpp:1814 Source/diablo.cpp:2282 +#: Source/diablo.cpp:1857 Source/diablo.cpp:2334 msgid "Displays game infos." msgstr "Показує інформацію про гру." #. TRANSLATORS: {:s} means: Character Name, Game Version, Game Difficulty. -#: Source/diablo.cpp:1818 Source/diablo.cpp:2286 +#: Source/diablo.cpp:1861 Source/diablo.cpp:2338 +#, c++-format msgid "{:s} {:s}" msgstr "{:s} {:s}" -#: Source/diablo.cpp:1827 Source/diablo.cpp:2295 +#: Source/diablo.cpp:1870 Source/diablo.cpp:2347 msgid "Chat Log" msgstr "Лог Чату" -#: Source/diablo.cpp:1828 Source/diablo.cpp:2296 +#: Source/diablo.cpp:1871 Source/diablo.cpp:2348 msgid "Displays chat log." msgstr "Показує лог чату." -#: Source/diablo.cpp:1886 +#: Source/diablo.cpp:1879 +msgid "Console" +msgstr "Консоль" + +#: Source/diablo.cpp:1880 +msgid "Opens Lua console." +msgstr "Відкриває консоль Lua." + +#: Source/diablo.cpp:1935 msgid "Primary action" msgstr "Основна дія" -#: Source/diablo.cpp:1887 +#: Source/diablo.cpp:1936 msgid "Attack monsters, talk to towners, lift and place inventory items." msgstr "" "Атака монстрів, розмови з городянами, підняття та розміщення предметів " "інвентарю." -#: Source/diablo.cpp:1901 +#: Source/diablo.cpp:1950 msgid "Secondary action" msgstr "Вторинна дія" -#: Source/diablo.cpp:1902 +#: Source/diablo.cpp:1951 msgid "Open chests, interact with doors, pick up items." msgstr "Відкриття скринь, взаємодія з дверима, підбір предметів." -#: Source/diablo.cpp:1916 +#: Source/diablo.cpp:1965 msgid "Spell action" msgstr "Дія чарування" -#: Source/diablo.cpp:1917 +#: Source/diablo.cpp:1966 msgid "Cast the active spell." msgstr "Чарує активне чарування." -#: Source/diablo.cpp:1931 +#: Source/diablo.cpp:1980 msgid "Cancel action" msgstr "Відміна" -#: Source/diablo.cpp:1932 +#: Source/diablo.cpp:1981 msgid "Close menus." msgstr "Закриває меню." -#: Source/diablo.cpp:1957 +#: Source/diablo.cpp:2006 msgid "Move up" msgstr "Ідти нагору" -#: Source/diablo.cpp:1958 +#: Source/diablo.cpp:2007 msgid "Moves the player character up." msgstr "Переміщує персонажа гравця вгору." -#: Source/diablo.cpp:1963 +#: Source/diablo.cpp:2012 msgid "Move down" msgstr "Ідти вниз" -#: Source/diablo.cpp:1964 +#: Source/diablo.cpp:2013 msgid "Moves the player character down." msgstr "Переміщує персонажа гравця вниз." -#: Source/diablo.cpp:1969 +#: Source/diablo.cpp:2018 msgid "Move left" msgstr "Ідти вліво" -#: Source/diablo.cpp:1970 +#: Source/diablo.cpp:2019 msgid "Moves the player character left." msgstr "Переміщує персонажа гравця вліво." -#: Source/diablo.cpp:1975 +#: Source/diablo.cpp:2024 msgid "Move right" msgstr "Ідти вправо" -#: Source/diablo.cpp:1976 +#: Source/diablo.cpp:2025 msgid "Moves the player character right." msgstr "Переміщує персонажа гравця вправо." -#: Source/diablo.cpp:1981 +#: Source/diablo.cpp:2030 msgid "Stand ground" msgstr "Стояти на місці" -#: Source/diablo.cpp:1982 +#: Source/diablo.cpp:2031 msgid "Hold to prevent the player from moving." msgstr "Утримуйте, щоб не дати гравцеві рухатися." -#: Source/diablo.cpp:1987 +#: Source/diablo.cpp:2036 msgid "Toggle stand ground" msgstr "Перемкнути стояння на місці" -#: Source/diablo.cpp:1988 +#: Source/diablo.cpp:2037 msgid "Toggle whether the player moves." msgstr "Перемикає, чи може гравець рухатись." -#: Source/diablo.cpp:2065 +#: Source/diablo.cpp:2114 msgid "Move mouse up" msgstr "Перемішення мишу вгору" -#: Source/diablo.cpp:2066 +#: Source/diablo.cpp:2115 msgid "Simulates upward mouse movement." msgstr "Імітує рух миші вгору." -#: Source/diablo.cpp:2071 +#: Source/diablo.cpp:2120 msgid "Move mouse down" msgstr "Рухати миш вниз" -#: Source/diablo.cpp:2072 +#: Source/diablo.cpp:2121 msgid "Simulates downward mouse movement." msgstr "Імітує рух миші вниз." -#: Source/diablo.cpp:2077 +#: Source/diablo.cpp:2126 msgid "Move mouse left" msgstr "Рухати миш вліво" -#: Source/diablo.cpp:2078 +#: Source/diablo.cpp:2127 msgid "Simulates leftward mouse movement." msgstr "Імітує рух миші вліво." -#: Source/diablo.cpp:2083 +#: Source/diablo.cpp:2132 msgid "Move mouse right" msgstr "Рухати миш вправо" -#: Source/diablo.cpp:2084 +#: Source/diablo.cpp:2133 msgid "Simulates rightward mouse movement." msgstr "Імітує рух миші вправо." -#: Source/diablo.cpp:2102 Source/diablo.cpp:2109 +#: Source/diablo.cpp:2151 Source/diablo.cpp:2158 msgid "Left mouse click" msgstr "Клік лівою кнопкою миші" -#: Source/diablo.cpp:2103 Source/diablo.cpp:2110 +#: Source/diablo.cpp:2152 Source/diablo.cpp:2159 msgid "Simulates the left mouse button." msgstr "Імітує ліву кнопку миші." -#: Source/diablo.cpp:2127 Source/diablo.cpp:2134 +#: Source/diablo.cpp:2176 Source/diablo.cpp:2183 msgid "Right mouse click" msgstr "Клік правою кнопкою миші" -#: Source/diablo.cpp:2128 Source/diablo.cpp:2135 +#: Source/diablo.cpp:2177 Source/diablo.cpp:2184 msgid "Simulates the right mouse button." msgstr "Імітує праву кнопку миші." -#: Source/diablo.cpp:2141 +#: Source/diablo.cpp:2190 msgid "Gamepad hotspell menu" msgstr "Меню гарячих чарувань геймпаду" -#: Source/diablo.cpp:2142 +#: Source/diablo.cpp:2191 msgid "Hold to set or use spell hotkeys." msgstr "Утримуйте, щоб встановити або використати гарячі клавіші чарування." -#: Source/diablo.cpp:2148 +#: Source/diablo.cpp:2197 msgid "Gamepad menu navigator" msgstr "Навігація по меню геймпада" -#: Source/diablo.cpp:2149 +#: Source/diablo.cpp:2198 msgid "Hold to access gamepad menu navigation." msgstr "Утримуйте для навігації по меню геймпада." -#: Source/diablo.cpp:2164 Source/diablo.cpp:2173 +#: Source/diablo.cpp:2213 Source/diablo.cpp:2222 msgid "Toggle game menu" msgstr "Перемкнути ігрове меню" -#: Source/diablo.cpp:2165 Source/diablo.cpp:2174 +#: Source/diablo.cpp:2214 Source/diablo.cpp:2223 msgid "Opens the game menu." msgstr "Відкриває ігрове меню." -#: Source/discord/discord.cpp:68 -msgid "Cathedral" -msgstr "Собор" - -#: Source/discord/discord.cpp:68 -msgid "Catacombs" -msgstr "Катакомби" - -#: Source/discord/discord.cpp:68 -msgid "Caves" -msgstr "Печери" - -#: Source/discord/discord.cpp:68 -msgid "Nest" -msgstr "Гніздо" - -#: Source/discord/discord.cpp:68 -msgid "Crypt" -msgstr "Склеп" - -#. TRANSLATORS: dungeon type and floor number i.e. "Cathedral 3" -#: Source/discord/discord.cpp:84 -msgid "{} {}" -msgstr "{} {}" - -#. TRANSLATORS: Discord character, i.e. "Lv 6 Warrior" -#: Source/discord/discord.cpp:92 -msgid "Lv {} {}" -msgstr "{} {} Рів" - -#. TRANSLATORS: Discord state i.e. "Nightmare difficulty" -#: Source/discord/discord.cpp:104 -msgid "{} difficulty" -msgstr "{} Складність" - -#. TRANSLATORS: Discord activity, not in game -#: Source/discord/discord.cpp:185 -msgid "In Menu" -msgstr "В Меню" - -#: Source/dvlnet/loopback.cpp:118 -msgid "loopback" -msgstr "петльова адреса" - -#: Source/dvlnet/tcp_client.cpp:67 -msgid "Unable to connect" -msgstr "Неможливо з'єднатися" - -#: Source/dvlnet/tcp_client.cpp:93 -msgid "error: read 0 bytes from server" -msgstr "помилка: прочитано 0 байт від сервера" +#: Source/diablo_msg.cpp:62 +msgid "Game saved" +msgstr "Гру збережено" -#: Source/error.cpp:54 -msgid "No automap available in town" -msgstr "Автокарта недоступна в Місті" - -#: Source/error.cpp:55 +#: Source/diablo_msg.cpp:63 msgid "No multiplayer functions in demo" msgstr "Мережева гра недоступна в демо" -#: Source/error.cpp:56 +#: Source/diablo_msg.cpp:64 msgid "Direct Sound Creation Failed" msgstr "Помилка створення Direct Sound" -#: Source/error.cpp:57 +#: Source/diablo_msg.cpp:65 msgid "Not available in shareware version" msgstr "Недоступно в демо-версії" -#: Source/error.cpp:58 +#: Source/diablo_msg.cpp:66 msgid "Not enough space to save" msgstr "Недостатньо місця для збереження" -#: Source/error.cpp:59 +#: Source/diablo_msg.cpp:67 msgid "No Pause in town" msgstr "Пауза недоступна в Місті" -#: Source/error.cpp:60 +#: Source/diablo_msg.cpp:68 msgid "Copying to a hard disk is recommended" msgstr "Рекомендуємо копіювати на жорсткий диск" -#: Source/error.cpp:61 +#: Source/diablo_msg.cpp:69 msgid "Multiplayer sync problem" msgstr "Проблема синхронізації мережі" -#: Source/error.cpp:62 +#: Source/diablo_msg.cpp:70 msgid "No pause in multiplayer" msgstr "Пауза недоступна в мережевій грі" -#: Source/error.cpp:64 +#: Source/diablo_msg.cpp:72 msgid "Saving..." msgstr "Зберігається…" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:65 +#: Source/diablo_msg.cpp:73 msgid "Some are weakened as one grows strong" msgstr "Деякі слабшають, коли один стає сильним" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:66 +#: Source/diablo_msg.cpp:74 msgid "New strength is forged through destruction" msgstr "Через руйнування формується нова сила" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:67 +#: Source/diablo_msg.cpp:75 msgid "Those who defend seldom attack" msgstr "Ті, хто захищаються, рідко нападають" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:68 +#: Source/diablo_msg.cpp:76 msgid "The sword of justice is swift and sharp" msgstr "Меч справедливості швидкий і гострий" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:69 +#: Source/diablo_msg.cpp:77 msgid "While the spirit is vigilant the body thrives" msgstr "Поки дух пильний, тіло процвітає" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:70 +#: Source/diablo_msg.cpp:78 msgid "The powers of mana refocused renews" msgstr "Сили мани поновлюються" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:71 +#: Source/diablo_msg.cpp:79 msgid "Time cannot diminish the power of steel" msgstr "Час не може зменшити силу сталі" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:72 +#: Source/diablo_msg.cpp:80 msgid "Magic is not always what it seems to be" msgstr "Магія не завжди така, якою здається" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:73 +#: Source/diablo_msg.cpp:81 msgid "What once was opened now is closed" msgstr "Те, що було відкрито, зараз закрито" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:74 +#: Source/diablo_msg.cpp:82 msgid "Intensity comes at the cost of wisdom" msgstr "Сила приходить ціною мудрості" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:75 +#: Source/diablo_msg.cpp:83 msgid "Arcane power brings destruction" msgstr "Таємна сила приносить руйнування" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:76 +#: Source/diablo_msg.cpp:84 msgid "That which cannot be held cannot be harmed" msgstr "Те, що не утримаєш — не зламаєш" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:77 +#: Source/diablo_msg.cpp:85 msgid "Crimson and Azure become as the sun" msgstr "Багряний та Лазурний стають, як сонце" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:78 +#: Source/diablo_msg.cpp:86 msgid "Knowledge and wisdom at the cost of self" msgstr "Знання і мудрість ціною себе" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:79 +#: Source/diablo_msg.cpp:87 msgid "Drink and be refreshed" msgstr "Пий і освіжайся" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:80 +#: Source/diablo_msg.cpp:88 msgid "Wherever you go, there you are" msgstr "Куди б ти не пішов, ти там" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:81 +#: Source/diablo_msg.cpp:89 msgid "Energy comes at the cost of wisdom" msgstr "Енергія приходить ціною мудрості" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:82 +#: Source/diablo_msg.cpp:90 msgid "Riches abound when least expected" msgstr "Рясні багатства коли менше всього їх очікуєш" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:83 +#: Source/diablo_msg.cpp:91 msgid "Where avarice fails, patience gains reward" msgstr "Коли скупість зазнає невдачі, терпіння отримує винагороду" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:84 +#: Source/diablo_msg.cpp:92 msgid "Blessed by a benevolent companion!" msgstr "Освячений доброзичливим товаришем!" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:85 +#: Source/diablo_msg.cpp:93 msgid "The hands of men may be guided by fate" msgstr "Доля може керувати руками людей" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:86 +#: Source/diablo_msg.cpp:94 msgid "Strength is bolstered by heavenly faith" msgstr "Сила підкріпляється небесною вірою" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:87 +#: Source/diablo_msg.cpp:95 msgid "The essence of life flows from within" msgstr "Суть життя випливає зсередини" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:88 +#: Source/diablo_msg.cpp:96 msgid "The way is made clear when viewed from above" msgstr "Коли дивишся зверху, шлях стає зрозумілим" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:89 +#: Source/diablo_msg.cpp:97 msgid "Salvation comes at the cost of wisdom" msgstr "Спасіння приходить ціною мудрості" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:90 +#: Source/diablo_msg.cpp:98 msgid "Mysteries are revealed in the light of reason" msgstr "Таємниці розкриваються у світлі розуму" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:91 +#: Source/diablo_msg.cpp:99 msgid "Those who are last may yet be first" msgstr "Ті, хто останні, можуть бути першими" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:92 +#: Source/diablo_msg.cpp:100 msgid "Generosity brings its own rewards" msgstr "Щедрість приносить свою винагороду" -#: Source/error.cpp:93 +#: Source/diablo_msg.cpp:101 msgid "You must be at least level 8 to use this." msgstr "Ти маєш бути принаймні 8 рівня, щоб скористатися цим." -#: Source/error.cpp:94 +#: Source/diablo_msg.cpp:102 msgid "You must be at least level 13 to use this." msgstr "Ти маєш бути принаймні 13 рівня, щоб скористатися цим." -#: Source/error.cpp:95 +#: Source/diablo_msg.cpp:103 msgid "You must be at least level 17 to use this." msgstr "Ти маєш бути принаймні 17 рівня, щоб скористатися цим." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:96 +#: Source/diablo_msg.cpp:104 msgid "Arcane knowledge gained!" msgstr "Отримані таємні знання!" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:97 +#: Source/diablo_msg.cpp:105 msgid "That which does not kill you..." msgstr "Те, що тебе не вбиває…" #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:98 +#: Source/diablo_msg.cpp:106 msgid "Knowledge is power." msgstr "Знання це сила." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:99 +#: Source/diablo_msg.cpp:107 msgid "Give and you shall receive." msgstr "Давай і отримаєш." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:100 +#: Source/diablo_msg.cpp:108 msgid "Some experience is gained by touch." msgstr "Певний досвід набувається на дотик." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:101 +#: Source/diablo_msg.cpp:109 msgid "There's no place like home." msgstr "Усюди добре, а дома найкраще." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:102 +#: Source/diablo_msg.cpp:110 msgid "Spiritual energy is restored." msgstr "Духовна енергія відновлена." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:103 +#: Source/diablo_msg.cpp:111 msgid "You feel more agile." msgstr "Ти відчуваєш спритнішим." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:104 +#: Source/diablo_msg.cpp:112 msgid "You feel stronger." msgstr "Ти відчуваєш сильнішим." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:105 +#: Source/diablo_msg.cpp:113 msgid "You feel wiser." msgstr "Ти відчуваєш мудрішим." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:106 +#: Source/diablo_msg.cpp:114 msgid "You feel refreshed." msgstr "Ти відчуваєш себе оновленим." #. TRANSLATORS: Shrine Text. Keep atmospheric. :) -#: Source/error.cpp:107 +#: Source/diablo_msg.cpp:115 msgid "That which can break will." msgstr "Те, що не ламається — зламається." -#: Source/gamemenu.cpp:39 +#: Source/discord/discord.cpp:73 +msgid "Cathedral" +msgstr "Собор" + +#: Source/discord/discord.cpp:73 +msgid "Catacombs" +msgstr "Катакомби" + +#: Source/discord/discord.cpp:73 +msgid "Caves" +msgstr "Печери" + +#: Source/discord/discord.cpp:73 +msgid "Nest" +msgstr "Гніздо" + +#: Source/discord/discord.cpp:73 +msgid "Crypt" +msgstr "Склеп" + +#. TRANSLATORS: dungeon type and floor number i.e. "Cathedral 3" +#: Source/discord/discord.cpp:89 +#, c++-format +msgid "{} {}" +msgstr "{} {}" + +#. TRANSLATORS: Discord character, i.e. "Lv 6 Warrior" +#: Source/discord/discord.cpp:96 +#, c++-format +msgid "Lv {} {}" +msgstr "{} {} Рів" + +#. TRANSLATORS: Discord state i.e. "Nightmare difficulty" +#: Source/discord/discord.cpp:108 +#, c++-format +msgid "{} difficulty" +msgstr "{} Складність" + +#. TRANSLATORS: Discord activity, not in game +#: Source/discord/discord.cpp:189 +msgid "In Menu" +msgstr "В Меню" + +#: Source/dvlnet/loopback.cpp:117 +msgid "loopback" +msgstr "петльова адреса" + +#: Source/dvlnet/tcp_client.cpp:81 +msgid "Unable to connect" +msgstr "Неможливо з'єднатися" + +#: Source/dvlnet/tcp_client.cpp:119 +msgid "error: read 0 bytes from server" +msgstr "помилка: прочитано 0 байт від сервера" + +#: Source/engine/demomode.cpp:179 Source/options.cpp:679 +msgid "Resolution" +msgstr "Роздільна Здатність" + +#: Source/engine/demomode.cpp:181 Source/options.cpp:1045 +msgid "Run in Town" +msgstr "Біг в Місті" + +#: Source/engine/demomode.cpp:182 Source/options.cpp:1047 +msgid "Theo Quest" +msgstr "Квест Тео" + +#: Source/engine/demomode.cpp:183 Source/options.cpp:1048 +msgid "Cow Quest" +msgstr "Квест Корови" + +#: Source/engine/demomode.cpp:184 Source/options.cpp:1058 +msgid "Auto Gold Pickup" +msgstr "Автопідбір Золота" + +#: Source/engine/demomode.cpp:185 Source/options.cpp:1059 +msgid "Auto Elixir Pickup" +msgstr "Автопідбір Еліксирів" + +#: Source/engine/demomode.cpp:186 Source/options.cpp:1060 +msgid "Auto Oil Pickup" +msgstr "Автопідбір Масел" + +#: Source/engine/demomode.cpp:187 Source/options.cpp:1061 +msgid "Auto Pickup in Town" +msgstr "Автопідбір в Місті" + +#: Source/engine/demomode.cpp:188 Source/options.cpp:1062 +msgid "Adria Refills Mana" +msgstr "Адрія Поповнює Ману" + +#: Source/engine/demomode.cpp:189 Source/options.cpp:1063 +msgid "Auto Equip Weapons" +msgstr "Автоспорядження Зброї" + +#: Source/engine/demomode.cpp:190 Source/options.cpp:1064 +msgid "Auto Equip Armor" +msgstr "Автоспорядження Броні" + +#: Source/engine/demomode.cpp:191 Source/options.cpp:1065 +msgid "Auto Equip Helms" +msgstr "Автоспорядження Шоломів" + +#: Source/engine/demomode.cpp:192 Source/options.cpp:1066 +msgid "Auto Equip Shields" +msgstr "Автоспорядження Щитів" + +#: Source/engine/demomode.cpp:193 Source/options.cpp:1067 +msgid "Auto Equip Jewelry" +msgstr "Автоспорядження Коштовностей" + +#: Source/engine/demomode.cpp:194 Source/options.cpp:1068 +msgid "Randomize Quests" +msgstr "Рандомізація Квестів" + +#: Source/engine/demomode.cpp:195 Source/options.cpp:1070 +msgid "Show Item Labels" +msgstr "Показати Назви Предметів" + +#: Source/engine/demomode.cpp:196 Source/options.cpp:1071 +msgid "Auto Refill Belt" +msgstr "Автозаповнення Пояса" + +#: Source/engine/demomode.cpp:197 Source/options.cpp:1072 +msgid "Disable Crippling Shrines" +msgstr "Вимкнути Псуючі Вівтарі" + +#: Source/engine/demomode.cpp:201 Source/options.cpp:1074 +msgid "Heal Potion Pickup" +msgstr "Підбір Зілля Зцілення" + +#: Source/engine/demomode.cpp:202 Source/options.cpp:1075 +msgid "Full Heal Potion Pickup" +msgstr "Підбір Зілля Повного Зцілення" + +#: Source/engine/demomode.cpp:203 Source/options.cpp:1076 +msgid "Mana Potion Pickup" +msgstr "Підбір Зілля Мани" + +#: Source/engine/demomode.cpp:204 Source/options.cpp:1077 +msgid "Full Mana Potion Pickup" +msgstr "Підбір Зілля Повної Мани" + +#: Source/engine/demomode.cpp:205 Source/options.cpp:1078 +msgid "Rejuvenation Potion Pickup" +msgstr "Підбір Зілля Омолодження" + +#: Source/engine/demomode.cpp:206 Source/options.cpp:1079 +msgid "Full Rejuvenation Potion Pickup" +msgstr "Підбір Зілля Повного Омолодження" + +#: Source/gamemenu.cpp:42 msgid "Save Game" msgstr "Зберегти Гру" -#: Source/gamemenu.cpp:40 Source/gamemenu.cpp:51 +#: Source/gamemenu.cpp:43 Source/gamemenu.cpp:54 msgid "Options" msgstr "Опції" -#: Source/gamemenu.cpp:43 Source/gamemenu.cpp:54 +#: Source/gamemenu.cpp:46 Source/gamemenu.cpp:57 msgid "Quit Game" msgstr "Вийти з Гри" -#: Source/gamemenu.cpp:53 +#: Source/gamemenu.cpp:56 msgid "Restart In Town" msgstr "Почати в Місті" -#: Source/gamemenu.cpp:63 +#: Source/gamemenu.cpp:66 msgid "Gamma" msgstr "Гамма" -#: Source/gamemenu.cpp:64 Source/gamemenu.cpp:173 +#: Source/gamemenu.cpp:67 Source/gamemenu.cpp:176 msgid "Speed" msgstr "Швид-ть" -#: Source/gamemenu.cpp:72 +#: Source/gamemenu.cpp:75 msgid "Music Disabled" msgstr "Музика Вимкнена" -#: Source/gamemenu.cpp:76 +#: Source/gamemenu.cpp:79 msgid "Sound" msgstr "Звук" -#: Source/gamemenu.cpp:77 +#: Source/gamemenu.cpp:80 msgid "Sound Disabled" msgstr "Звук Вимкнено" -#: Source/gmenu.cpp:177 +#: Source/gmenu.cpp:178 msgid "Pause" msgstr "Пауза" @@ -2325,19 +2516,19 @@ msgstr "Допомога Демо-версії Diablo" msgid "Diablo Help" msgstr "Допомога Diablo" -#: Source/help.cpp:233 Source/qol/chatlog.cpp:189 +#: Source/help.cpp:234 Source/qol/chatlog.cpp:200 msgid "Press ESC to end or the arrow keys to scroll." msgstr "Натисніть ESC щоб закрити або клавіші зі стрілками для прокрутки." -#: Source/init.cpp:295 Source/init.cpp:335 +#: Source/init.cpp:298 Source/init.cpp:338 msgid "diabdat.mpq or spawn.mpq" msgstr "diabdat.mpq або spawn.mpq" -#: Source/init.cpp:316 Source/init.cpp:356 +#: Source/init.cpp:319 Source/init.cpp:359 msgid "Some Hellfire MPQs are missing" msgstr "Деякі файли MPQ для Hellfire відсутні" -#: Source/init.cpp:316 Source/init.cpp:356 +#: Source/init.cpp:319 Source/init.cpp:359 msgid "" "Not all Hellfire MPQs were found.\n" "Please copy all the hf*.mpq files." @@ -2345,9539 +2536,9533 @@ msgstr "" "Не всі файли MPQ для Hellfire були знайдені.\n" "Будь ласка скопіюйте всі файли hf*.mpq." -#: Source/init.cpp:365 +#: Source/init.cpp:368 msgid "Unable to create main window" msgstr "Неможливо створити головне вікно" -#: Source/inv.cpp:2126 +#: Source/inv.cpp:2118 msgid "No room for item" msgstr "Немає місця для предмету" -#: Source/itemdat.cpp:53 Source/itemdat.cpp:236 Source/panels/charpanel.cpp:165 -msgid "Gold" -msgstr "Золотий" +#: Source/items.cpp:174 Source/translation_dummy.cpp:360 +msgid "Oil of Accuracy" +msgstr "Масло Точності" -#: Source/itemdat.cpp:54 Source/itemdat.cpp:172 -msgid "Short Sword" -msgstr "Короткий Меч" +#: Source/items.cpp:175 +msgid "Oil of Mastery" +msgstr "Масло Майстерності" -#: Source/itemdat.cpp:55 Source/itemdat.cpp:124 -msgid "Buckler" -msgstr "Круглий Щит" +#: Source/items.cpp:176 Source/translation_dummy.cpp:361 +msgid "Oil of Sharpness" +msgstr "Масло Гостроти" -#: Source/itemdat.cpp:56 Source/itemdat.cpp:192 Source/itemdat.cpp:193 -msgid "Club" -msgstr "Палиця" +#: Source/items.cpp:177 +msgid "Oil of Death" +msgstr "Масло Смерті" -#: Source/itemdat.cpp:57 Source/itemdat.cpp:196 -msgid "Short Bow" -msgstr "Короткий Лук" +#: Source/items.cpp:178 +msgid "Oil of Skill" +msgstr "Масло Таланту" -#: Source/itemdat.cpp:58 -msgid "Short Staff of Mana" -msgstr "Короткий Посох Мани" +#: Source/items.cpp:179 Source/translation_dummy.cpp:282 +#: Source/translation_dummy.cpp:359 +msgid "Blacksmith Oil" +msgstr "Ковальське Масло" -#: Source/itemdat.cpp:59 -msgid "Cleaver" -msgstr "Сікач" +#: Source/items.cpp:180 +msgid "Oil of Fortitude" +msgstr "Масло Стійкості" -#: Source/itemdat.cpp:60 Source/itemdat.cpp:435 -msgid "The Undead Crown" -msgstr "Нечестива Корона" +#: Source/items.cpp:181 +msgid "Oil of Permanence" +msgstr "Масло Міцності" -#: Source/itemdat.cpp:61 Source/itemdat.cpp:436 -msgid "Empyrean Band" -msgstr "Обруч Емпірею" +#: Source/items.cpp:182 +msgid "Oil of Hardening" +msgstr "Масло Гартування" -#: Source/itemdat.cpp:62 -msgid "Magic Rock" -msgstr "Магічний Камінь" +#: Source/items.cpp:183 +msgid "Oil of Imperviousness" +msgstr "Масло Непроникності" -#: Source/itemdat.cpp:63 Source/itemdat.cpp:437 -msgid "Optic Amulet" -msgstr "Оптичний Амулет" +#. TRANSLATORS: Constructs item names. Format: {Item} of {Spell}. Example: War Staff of Firewall +#: Source/items.cpp:1112 +#, c++-format +msgctxt "spell" +msgid "{0} of {1}" +msgstr "{0} {1}" -#: Source/itemdat.cpp:64 Source/itemdat.cpp:438 -msgid "Ring of Truth" -msgstr "Перстень Правди" +#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Spell}. Example: King's War Staff of Firewall +#: Source/items.cpp:1124 +#, c++-format +msgctxt "spell" +msgid "{0} {1} of {2}" +msgstr "{0} {1} {2}" -#: Source/itemdat.cpp:65 -msgid "Tavern Sign" -msgstr "Знак Таверни" +#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Suffix}. Example: King's Long Sword of the Whale +#: Source/items.cpp:1162 +#, c++-format +msgid "{0} {1} of {2}" +msgstr "{0} {1} {2}" -#: Source/itemdat.cpp:66 Source/itemdat.cpp:439 -msgid "Harlequin Crest" -msgstr "Гребінь Арлекіна" +#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item}. Example: King's Long Sword +#: Source/items.cpp:1165 +#, c++-format +msgid "{0} {1}" +msgstr "{0} {1}" -#: Source/itemdat.cpp:67 Source/itemdat.cpp:440 -msgid "Veil of Steel" -msgstr "Стальна Чадра" +#. TRANSLATORS: Constructs item names. Format: {Item} of {Suffix}. Example: Long Sword of the Whale +#: Source/items.cpp:1168 +#, c++-format +msgid "{0} of {1}" +msgstr "{0} {1}" -#: Source/itemdat.cpp:68 -msgid "Golden Elixir" -msgstr "Золотий Еліксир" +#: Source/items.cpp:1699 Source/items.cpp:1707 +msgid "increases a weapon's" +msgstr "підвищує у зброї" -#: Source/itemdat.cpp:69 Source/quests.cpp:58 -msgid "Anvil of Fury" -msgstr "Ковадло Люті" +#: Source/items.cpp:1700 +msgid "chance to hit" +msgstr "шанс удару" -#: Source/itemdat.cpp:70 Source/quests.cpp:49 -msgid "Black Mushroom" -msgstr "Чорний Гриб" +#: Source/items.cpp:1703 +msgid "greatly increases a" +msgstr "набагато підвищує" -#: Source/itemdat.cpp:71 -msgid "Brain" -msgstr "Мозок" +#: Source/items.cpp:1704 +msgid "weapon's chance to hit" +msgstr "шанс удару зброї" -#: Source/itemdat.cpp:72 -msgid "Fungal Tome" -msgstr "Грибковий Том" +#: Source/items.cpp:1708 +msgid "damage potential" +msgstr "потенціал шкоди" -#: Source/itemdat.cpp:73 -msgid "Spectral Elixir" -msgstr "Примарний Еліксир" +#: Source/items.cpp:1711 +msgid "greatly increases a weapon's" +msgstr "набагато підвищує у зброї" -#: Source/itemdat.cpp:74 -msgid "Blood Stone" -msgstr "Кров'яний Камінь" +#: Source/items.cpp:1712 +msgid "damage potential - not bows" +msgstr "потенціал шкоди — крім луків" -#: Source/itemdat.cpp:75 -msgid "Cathedral Map" -msgstr "Карта Собору" +#: Source/items.cpp:1715 +msgid "reduces attributes needed" +msgstr "знижує вимоги атрибутів" -#: Source/itemdat.cpp:76 -msgid "Heart" -msgstr "Сердце" +#: Source/items.cpp:1716 +msgid "to use armor or weapons" +msgstr "шоб використати броню чи зброю" -#: Source/itemdat.cpp:77 Source/itemdat.cpp:130 -msgid "Potion of Healing" -msgstr "Зілля Зцілення" +#: Source/items.cpp:1719 +#, no-c-format +msgid "restores 20% of an" +msgstr "відновлює 20%" -#: Source/itemdat.cpp:78 Source/itemdat.cpp:132 -msgid "Potion of Mana" -msgstr "Зілля Мани" +#: Source/items.cpp:1720 +msgid "item's durability" +msgstr "міцність предмету" -#: Source/itemdat.cpp:79 Source/itemdat.cpp:147 -msgid "Scroll of Identify" -msgstr "Сувій Розпізнавання" +#: Source/items.cpp:1723 +msgid "increases an item's" +msgstr "підвищує у предмета" -#: Source/itemdat.cpp:80 Source/itemdat.cpp:151 -msgid "Scroll of Town Portal" -msgstr "Сувій Порталу в Місто" +#: Source/items.cpp:1724 +msgid "current and max durability" +msgstr "поточну і макс міцність" -#: Source/itemdat.cpp:81 Source/itemdat.cpp:441 -msgid "Arkaine's Valor" -msgstr "Доблесть Аркейну" +#: Source/items.cpp:1727 +msgid "makes an item indestructible" +msgstr "робить предмет неруйновним" -#: Source/itemdat.cpp:82 Source/itemdat.cpp:131 -msgid "Potion of Full Healing" -msgstr "Зілля Повного Зцілення" +#: Source/items.cpp:1730 +msgid "increases the armor class" +msgstr "підвищує клас броні" -#: Source/itemdat.cpp:83 Source/itemdat.cpp:133 -msgid "Potion of Full Mana" -msgstr "Зілля Повної Мани" +#: Source/items.cpp:1731 +msgid "of armor and shields" +msgstr "броні і щитів" -#: Source/itemdat.cpp:84 Source/itemdat.cpp:442 -msgid "Griswold's Edge" -msgstr "Лезо Грізволда" +#: Source/items.cpp:1734 +msgid "greatly increases the armor" +msgstr "набагато підвищує у броні" -#: Source/itemdat.cpp:85 Source/itemdat.cpp:443 -msgid "Bovine Plate" -msgstr "Бичачі Лати" +#: Source/items.cpp:1735 +msgid "class of armor and shields" +msgstr "клас броні і щитів" -#: Source/itemdat.cpp:86 -msgid "Staff of Lazarus" -msgstr "Посох Лазаря" +#: Source/items.cpp:1738 Source/items.cpp:1745 +msgid "sets fire trap" +msgstr "ставить пастку вогню" -#: Source/itemdat.cpp:87 Source/itemdat.cpp:148 -msgid "Scroll of Resurrect" -msgstr "Сувій Воскрешення" +#: Source/items.cpp:1742 +msgid "sets lightning trap" +msgstr "ставить пастку блискавки" -#: Source/itemdat.cpp:88 Source/itemdat.cpp:136 Source/items.cpp:180 -msgid "Blacksmith Oil" -msgstr "Ковальське Масло" +#: Source/items.cpp:1748 +msgid "sets petrification trap" +msgstr "ставить пастку окам'яніння" -#: Source/itemdat.cpp:89 Source/itemdat.cpp:204 -msgid "Short Staff" -msgstr "Короткий Посох" +#: Source/items.cpp:1751 +msgid "restore all life" +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/items.cpp:1754 +msgid "restore some life" +msgstr "відновлює трохи життя" -#: Source/itemdat.cpp:91 Source/itemdat.cpp:171 -msgid "Dagger" -msgstr "Кинджал" +#: Source/items.cpp:1757 +msgid "restore some mana" +msgstr "відновлює трохи мани" -#: Source/itemdat.cpp:92 -msgid "Rune Bomb" -msgstr "Рунна Бомба" +#: Source/items.cpp:1760 +msgid "restore all mana" +msgstr "відновлює всю ману" -#: Source/itemdat.cpp:93 -msgid "Theodore" -msgstr "Теодор" +#: Source/items.cpp:1763 +msgid "increase strength" +msgstr "підвищує силу" -#: Source/itemdat.cpp:94 -msgid "Auric Amulet" -msgstr "Золотоносний Амулет" +#: Source/items.cpp:1766 +msgid "increase magic" +msgstr "підвищує магію" -#: Source/itemdat.cpp:95 -msgid "Torn Note 1" -msgstr "Розірвана Записка 1" +#: Source/items.cpp:1769 +msgid "increase dexterity" +msgstr "підвищує спритність" -#: Source/itemdat.cpp:96 -msgid "Torn Note 2" -msgstr "Розірвана Записка 2" +#: Source/items.cpp:1772 +msgid "increase vitality" +msgstr "підвищує живучість" -#: Source/itemdat.cpp:97 -msgid "Torn Note 3" -msgstr "Розірвана Записка 3" +#: Source/items.cpp:1775 +msgid "restore some life and mana" +msgstr "відновлює трохи життя і мани" -#: Source/itemdat.cpp:98 -msgid "Reconstructed Note" -msgstr "Відновлена Записка" +#: Source/items.cpp:1778 Source/items.cpp:1781 +msgid "restore all life and mana" +msgstr "відновлює все життя і ману" -#: Source/itemdat.cpp:99 -msgid "Brown Suit" -msgstr "Коричневий Костюм" +#: Source/items.cpp:1782 +msgid "(works only in arenas)" +msgstr "(працює тільки на аренах)" -#: Source/itemdat.cpp:100 -msgid "Grey Suit" -msgstr "Сірий Костюм" +#: Source/items.cpp:1796 +msgid "Right-click to view" +msgstr "Правий клік щоб оглянути" -#: Source/itemdat.cpp:101 Source/itemdat.cpp:102 -msgid "Cap" -msgstr "Шапка" +#: Source/items.cpp:1799 +msgid "Right-click to use" +msgstr "Правий клік щоб використати" -#: Source/itemdat.cpp:102 -msgid "Skull Cap" -msgstr "Черепна Шапка" +#: Source/items.cpp:1801 +msgid "" +"Right-click to read, then\n" +"left-click to target" +msgstr "" +"Правий клік, щоб прочитати,\n" +"лівий клік, щоб вибрати ціль" -#: Source/itemdat.cpp:103 Source/itemdat.cpp:104 Source/itemdat.cpp:106 -msgid "Helm" -msgstr "Шолом" +#: Source/items.cpp:1803 +msgid "Right-click to read" +msgstr "Правий клік щоб прочитати" -#: Source/itemdat.cpp:104 -msgid "Full Helm" -msgstr "Повний Шолом" +#: Source/items.cpp:1810 +msgid "Activate to view" +msgstr "Активуйте щоб оглянути" -#: Source/itemdat.cpp:105 -msgid "Crown" -msgstr "Корона" +#: Source/items.cpp:1814 Source/items.cpp:1852 +msgid "Open inventory to use" +msgstr "Відкрийте інвентар щоб використати" -#: Source/itemdat.cpp:106 -msgid "Great Helm" -msgstr "Величезний Шолом" +#: Source/items.cpp:1816 +msgid "Activate to use" +msgstr "Активуйте щоб використати" -#: Source/itemdat.cpp:107 -msgid "Cape" -msgstr "Накидка" +#: Source/items.cpp:1819 +msgid "" +"Select from spell book, then\n" +"cast spell to read" +msgstr "" +"Виберіть з книги чарувань, а потім\n" +"чаруйте, щоб прочитати" -#: Source/itemdat.cpp:108 -msgid "Rags" -msgstr "Ганчірки" +#: Source/items.cpp:1821 +msgid "Activate to read" +msgstr "Активуйте щоб прочитати" -#: Source/itemdat.cpp:109 -msgid "Cloak" -msgstr "Мантія" +#: Source/items.cpp:1848 +#, c++-format +msgid "{} to view" +msgstr "{} щоб оглянути" -#: Source/itemdat.cpp:110 -msgid "Robe" -msgstr "Ряса" +#: Source/items.cpp:1854 +#, c++-format +msgid "{} to use" +msgstr "{} щоб використати" -#: Source/itemdat.cpp:111 -msgid "Quilted Armor" -msgstr "Стьобана Броня" +#: Source/items.cpp:1857 +#, c++-format +msgid "" +"Select from spell book,\n" +"then {} to read" +msgstr "" +"Виберіть з книги чар,\n" +"потім {}, щоб прочитати" -#: Source/itemdat.cpp:111 Source/itemdat.cpp:112 Source/itemdat.cpp:113 -#: Source/itemdat.cpp:114 Source/objects.cpp:4964 -msgid "Armor" -msgstr "Броня" +#: Source/items.cpp:1859 +#, c++-format +msgid "{} to read" +msgstr "{} щоб прочитати" -#: Source/itemdat.cpp:112 -msgid "Leather Armor" -msgstr "Шкіряна Броня" +#: Source/items.cpp:1866 +#, c++-format +msgctxt "player" +msgid "Level: {:d}" +msgstr "Рівень: {:d}" -#: Source/itemdat.cpp:113 -msgid "Hard Leather Armor" -msgstr "Броня з Твердої Шкіри" +#: Source/items.cpp:1870 +msgid "Doubles gold capacity" +msgstr "Подвоює ємність золота" -#: Source/itemdat.cpp:114 -msgid "Studded Leather Armor" -msgstr "Броня з Клепаної Шкіри" +#: Source/items.cpp:1902 Source/stores.cpp:325 +msgid "Required:" +msgstr "Вимоги:" -#: Source/itemdat.cpp:115 -msgid "Ring Mail" -msgstr "Кільцева Кольчуга" +#: Source/items.cpp:1904 Source/stores.cpp:327 +#, c++-format +msgid " {:d} Str" +msgstr " {:d} Сил" -#: Source/itemdat.cpp:115 Source/itemdat.cpp:116 Source/itemdat.cpp:117 -#: Source/itemdat.cpp:119 -msgid "Mail" -msgstr "Кольчуга" +#: Source/items.cpp:1906 Source/stores.cpp:329 +#, c++-format +msgid " {:d} Mag" +msgstr " {:d} Маг" -#: Source/itemdat.cpp:116 -msgid "Chain Mail" -msgstr "Кільчаста Кольчуга" +#: Source/items.cpp:1908 Source/stores.cpp:331 +#, c++-format +msgid " {:d} Dex" +msgstr " {:d} Спр" -#: Source/itemdat.cpp:117 -msgid "Scale Mail" -msgstr "Лускова Кольчуга" +#. TRANSLATORS: {:s} will be a Character Name +#: Source/items.cpp:2283 +#, c++-format +msgid "Ear of {:s}" +msgstr "Вухо {:s}" -#: Source/itemdat.cpp:118 -msgid "Breast Plate" -msgstr "Нагрудник" +#: Source/items.cpp:3673 +#, c++-format +msgid "chance to hit: {:+d}%" +msgstr "шанс удару: {:+d}%" -#: 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/items.cpp:3676 +#, no-c-format, c++-format +msgid "{:+d}% damage" +msgstr "{:+d}% шкоди" -#: Source/itemdat.cpp:120 -msgid "Plate Mail" -msgstr "Пластинчасті Лати" +#: Source/items.cpp:3679 Source/items.cpp:3863 +#, c++-format +msgid "to hit: {:+d}%, {:+d}% damage" +msgstr "шанс: {:+d}%, {:+d}% шкоди" -#: Source/itemdat.cpp:121 -msgid "Field Plate" -msgstr "Польові Лати" +#: Source/items.cpp:3682 +#, no-c-format, c++-format +msgid "{:+d}% armor" +msgstr "{:+d}% броні" -#: Source/itemdat.cpp:122 -msgid "Gothic Plate" -msgstr "Готичні Лати" +#: Source/items.cpp:3685 +#, c++-format +msgid "armor class: {:d}" +msgstr "клас броні: {:d}" -#: Source/itemdat.cpp:123 -msgid "Full Plate Mail" -msgstr "Повні Лати" +#: Source/items.cpp:3689 +#, c++-format +msgid "Resist Fire: {:+d}%" +msgstr "Спротив Вогню: {:+d}%" -#: 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/items.cpp:3691 +#, c++-format +msgid "Resist Fire: {:+d}% MAX" +msgstr "Спротив Вогню: {:+d}% МАКС" -#: Source/itemdat.cpp:125 -msgid "Small Shield" -msgstr "Малий Щит" +#: Source/items.cpp:3695 +#, c++-format +msgid "Resist Lightning: {:+d}%" +msgstr "Спротив Блискавці: {:+d}%" -#: Source/itemdat.cpp:126 -msgid "Large Shield" -msgstr "Великий Щит" +#: Source/items.cpp:3697 +#, c++-format +msgid "Resist Lightning: {:+d}% MAX" +msgstr "Спротив Блискавці: {:+d}% МАКС" -#: Source/itemdat.cpp:127 -msgid "Kite Shield" -msgstr "Краплеподібний Щит" +#: Source/items.cpp:3701 +#, c++-format +msgid "Resist Magic: {:+d}%" +msgstr "Спротив Магії: {:+d}%" -#: Source/itemdat.cpp:128 -msgid "Tower Shield" -msgstr "Баштовий Щит" +#: Source/items.cpp:3703 +#, c++-format +msgid "Resist Magic: {:+d}% MAX" +msgstr "Спротив Магії: {:+d}% МАКС" -#: Source/itemdat.cpp:129 -msgid "Gothic Shield" -msgstr "Готичний Щит" +#: Source/items.cpp:3706 +#, c++-format +msgid "Resist All: {:+d}%" +msgstr "Спротив Всьому: {:+d}%" -#: Source/itemdat.cpp:134 -msgid "Potion of Rejuvenation" -msgstr "Зілля Омолодження" +#: Source/items.cpp:3708 +#, c++-format +msgid "Resist All: {:+d}% MAX" +msgstr "Спротив Всьому: {:+d}% МАКС" -#: Source/itemdat.cpp:135 -msgid "Potion of Full Rejuvenation" -msgstr "Зілля Повного Омолодження" +#: Source/items.cpp:3711 +#, c++-format +msgid "spells are increased {:d} level" +msgid_plural "spells are increased {:d} levels" +msgstr[0] "чари покращені на {:d} рівень" +msgstr[1] "чари покращені на {:d} рівня" +msgstr[2] "чари покращені на {:d} рівнів" -#: Source/itemdat.cpp:137 Source/items.cpp:175 -msgid "Oil of Accuracy" -msgstr "Масло Точності" +#: Source/items.cpp:3713 +#, c++-format +msgid "spells are decreased {:d} level" +msgid_plural "spells are decreased {:d} levels" +msgstr[0] "чари погіршені на {:d} рівень" +msgstr[1] "чари погіршені на {:d} рівня" +msgstr[2] "чари погіршені на {:d} рівнів" -#: Source/itemdat.cpp:138 Source/items.cpp:177 -msgid "Oil of Sharpness" -msgstr "Масло Гостроти" +#: Source/items.cpp:3715 +msgid "spell levels unchanged (?)" +msgstr "рівень чар не змінюється (?)" -#: Source/itemdat.cpp:139 -msgid "Oil" -msgstr "Масло" +#: Source/items.cpp:3717 +msgid "Extra charges" +msgstr "Додаткові заряди" -#: Source/itemdat.cpp:140 -msgid "Elixir of Strength" -msgstr "Еліксир Сили" +#: Source/items.cpp:3719 +#, c++-format +msgid "{:d} {:s} charge" +msgid_plural "{:d} {:s} charges" +msgstr[0] "{:d} {:s} заряд" +msgstr[1] "{:d} {:s} заряди" +msgstr[2] "{:d} {:s} зарядів" -#: Source/itemdat.cpp:141 -msgid "Elixir of Magic" -msgstr "Еліксир Магії" +#: Source/items.cpp:3722 +#, c++-format +msgid "Fire hit damage: {:d}" +msgstr "Шкода вогнем: {:d}" -#: Source/itemdat.cpp:142 -msgid "Elixir of Dexterity" -msgstr "Еліксир Спритності" +#: Source/items.cpp:3724 +#, c++-format +msgid "Fire hit damage: {:d}-{:d}" +msgstr "Шкода вогнем: {:d}-{:d}" -#: Source/itemdat.cpp:143 -msgid "Elixir of Vitality" -msgstr "Еліксир Живучості" +#: Source/items.cpp:3727 +#, c++-format +msgid "Lightning hit damage: {:d}" +msgstr "Шкода блискавкою: {:d}" -#: Source/itemdat.cpp:144 -msgid "Scroll of Healing" -msgstr "Сувій Зцілення" +#: Source/items.cpp:3729 +#, c++-format +msgid "Lightning hit damage: {:d}-{:d}" +msgstr "Шкода блискавкою: {:d}-{:d}" -#: Source/itemdat.cpp:145 -msgid "Scroll of Search" -msgstr "Сувій Пошуку" +#: Source/items.cpp:3732 +#, c++-format +msgid "{:+d} to strength" +msgstr "{:+d} до сили" -#: Source/itemdat.cpp:146 -msgid "Scroll of Lightning" -msgstr "Сувій Блискавки" +#: Source/items.cpp:3735 +#, c++-format +msgid "{:+d} to magic" +msgstr "{:+d} до магії" -#: Source/itemdat.cpp:149 -msgid "Scroll of Fire Wall" -msgstr "Сувій Стіни Вогню" +#: Source/items.cpp:3738 +#, c++-format +msgid "{:+d} to dexterity" +msgstr "{:+d} до спритності" -#: Source/itemdat.cpp:150 -msgid "Scroll of Inferno" -msgstr "Сувій Пекла" +#: Source/items.cpp:3741 +#, c++-format +msgid "{:+d} to vitality" +msgstr "{:+d} до живучості" -#: Source/itemdat.cpp:152 -msgid "Scroll of Flash" -msgstr "Сувій Спалаху" +#: Source/items.cpp:3744 +#, c++-format +msgid "{:+d} to all attributes" +msgstr "{:+d} до всіх атрибутів" -#: Source/itemdat.cpp:153 -msgid "Scroll of Infravision" -msgstr "Сувій Інфрабачення" +#: Source/items.cpp:3747 +#, c++-format +msgid "{:+d} damage from enemies" +msgstr "{:+d} шкоди від ворогів" -#: Source/itemdat.cpp:154 -msgid "Scroll of Phasing" -msgstr "Сувій Фазування" +#: Source/items.cpp:3750 +#, c++-format +msgid "Hit Points: {:+d}" +msgstr "Здоров'я {:+d}" -#: Source/itemdat.cpp:155 -msgid "Scroll of Mana Shield" -msgstr "Сувій Щита Мани" +#: Source/items.cpp:3753 +#, c++-format +msgid "Mana: {:+d}" +msgstr "Мана: {:+d}" -#: Source/itemdat.cpp:156 -msgid "Scroll of Flame Wave" -msgstr "Сувій Хвилі Вогню" +#: Source/items.cpp:3755 +msgid "high durability" +msgstr "висока міцність" -#: Source/itemdat.cpp:157 -msgid "Scroll of Fireball" -msgstr "Сувій Кулі Вогню" +#: Source/items.cpp:3757 +msgid "decreased durability" +msgstr "зменшена міцність" -#: Source/itemdat.cpp:158 -msgid "Scroll of Stone Curse" -msgstr "Сувій Прокляття Каменю" +#: Source/items.cpp:3759 +msgid "indestructible" +msgstr "неруйновний" -#: Source/itemdat.cpp:159 -msgid "Scroll of Chain Lightning" -msgstr "Сувій Ланцюгової Блискавки" +#: Source/items.cpp:3761 +#, no-c-format, c++-format +msgid "+{:d}% light radius" +msgstr "радіус світла +{:d}%" -#: Source/itemdat.cpp:160 -msgid "Scroll of Guardian" -msgstr "Сувій Охоронця" +#: Source/items.cpp:3763 +#, no-c-format, c++-format +msgid "-{:d}% light radius" +msgstr "радіус світла -{:d}%" -#: Source/itemdat.cpp:162 -msgid "Scroll of Nova" -msgstr "Сувій Нови" +#: Source/items.cpp:3765 +msgid "multiple arrows per shot" +msgstr "стріляє декілька стріл відразу" -#: Source/itemdat.cpp:163 -msgid "Scroll of Golem" -msgstr "Сувій Голему" +#: Source/items.cpp:3768 +#, c++-format +msgid "fire arrows damage: {:d}" +msgstr "шкода вогняних стріл: {:d}" -#: Source/itemdat.cpp:165 -msgid "Scroll of Teleport" -msgstr "Сувій Телепорту" +#: Source/items.cpp:3770 +#, c++-format +msgid "fire arrows damage: {:d}-{:d}" +msgstr "шкода вогняних стріл: {:d}-{:d}" -#: Source/itemdat.cpp:166 -msgid "Scroll of Apocalypse" -msgstr "Сувій Апокаліпсису" +#: Source/items.cpp:3773 +#, c++-format +msgid "lightning arrows damage {:d}" +msgstr "шкода стріл блискавки: {:d}" -#: Source/itemdat.cpp:167 Source/itemdat.cpp:168 Source/itemdat.cpp:169 -#: Source/itemdat.cpp:170 -msgid "Book of " -msgstr "Книга " +#: Source/items.cpp:3775 +#, c++-format +msgid "lightning arrows damage {:d}-{:d}" +msgstr "шкода стріл блискавки: {:d}-{:d}" -#: Source/itemdat.cpp:173 -msgid "Falchion" -msgstr "Фальшіон" +#: Source/items.cpp:3778 +#, c++-format +msgid "fireball damage: {:d}" +msgstr "шкода кулі вогню: {:d}" -#: Source/itemdat.cpp:174 -msgid "Scimitar" -msgstr "Ятаган" +#: Source/items.cpp:3780 +#, c++-format +msgid "fireball damage: {:d}-{:d}" +msgstr "шкода кулі вогню: {:d}-{:d}" -#: Source/itemdat.cpp:175 -msgid "Claymore" -msgstr "Клеймор" +#: Source/items.cpp:3782 +msgid "attacker takes 1-3 damage" +msgstr "нападник отримує 1-3 шкоди" -#: Source/itemdat.cpp:176 -msgid "Blade" -msgstr "Клинок" +#: Source/items.cpp:3784 +msgid "user loses all mana" +msgstr "поглинає всю ману" -#: Source/itemdat.cpp:177 -msgid "Sabre" -msgstr "Шабля" +#: Source/items.cpp:3786 +msgid "absorbs half of trap damage" +msgstr "поглинає 50% шкоди пасток" -#: Source/itemdat.cpp:178 -msgid "Long Sword" -msgstr "Довгий Меч" +#: Source/items.cpp:3788 +msgid "knocks target back" +msgstr "відбиває ворога назад" -#: Source/itemdat.cpp:179 -msgid "Broad Sword" -msgstr "Широкий Меч" +#: Source/items.cpp:3790 +#, no-c-format +msgid "+200% damage vs. demons" +msgstr "+200% шкоди проти демонів" -#: Source/itemdat.cpp:180 -msgid "Bastard Sword" -msgstr "Меч-Бастард" +#: Source/items.cpp:3792 +msgid "All Resistance equals 0" +msgstr "Всі Спротиви рівні 0" -#: Source/itemdat.cpp:181 -msgid "Two-Handed Sword" -msgstr "Дворучний Меч" +#: Source/items.cpp:3795 +#, no-c-format +msgid "hit steals 3% mana" +msgstr "удар краде 3% мани" -#: Source/itemdat.cpp:182 -msgid "Great Sword" -msgstr "Величезний Меч" +#: Source/items.cpp:3797 +#, no-c-format +msgid "hit steals 5% mana" +msgstr "удар краде 5% мани" -#: Source/itemdat.cpp:183 -msgid "Small Axe" -msgstr "Мала Сокира" +#: Source/items.cpp:3801 +#, no-c-format +msgid "hit steals 3% life" +msgstr "удар краде 3% життя" -#: 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/items.cpp:3803 +#, no-c-format +msgid "hit steals 5% life" +msgstr "удар краде 5% життя" -#: Source/itemdat.cpp:185 -msgid "Large Axe" -msgstr "Велика Сокира" +#: Source/items.cpp:3806 +msgid "penetrates target's armor" +msgstr "пробиває броню" -#: Source/itemdat.cpp:186 -msgid "Broad Axe" -msgstr "Широка Сокира" +#: Source/items.cpp:3809 +msgid "quick attack" +msgstr "моторна атака" -#: Source/itemdat.cpp:187 -msgid "Battle Axe" -msgstr "Бойова Сокира" +#: Source/items.cpp:3811 +msgid "fast attack" +msgstr "швидка атака" -#: Source/itemdat.cpp:188 -msgid "Great Axe" -msgstr "Величезна Сокира" +#: Source/items.cpp:3813 +msgid "faster attack" +msgstr "швидша атака" -#: Source/itemdat.cpp:189 Source/itemdat.cpp:190 -msgid "Mace" -msgstr "Булава" +#: Source/items.cpp:3815 +msgid "fastest attack" +msgstr "найшвидша атака" -#: Source/itemdat.cpp:190 -msgid "Morning Star" -msgstr "Моргенштерн" +# Що за NW? +#: Source/items.cpp:3816 Source/items.cpp:3824 Source/items.cpp:3873 +msgid "Another ability (NW)" +msgstr "Інше уміння (NW)" -#: Source/itemdat.cpp:191 -msgid "War Hammer" -msgstr "Бойовий Молот" +#: Source/items.cpp:3819 +msgid "fast hit recovery" +msgstr "швидке відновлення після удару" -#: Source/itemdat.cpp:191 -msgid "Hammer" -msgstr "Молот" +#: Source/items.cpp:3821 +msgid "faster hit recovery" +msgstr "швидше відновлення після удару" -#: Source/itemdat.cpp:192 -msgid "Spiked Club" -msgstr "Палиця з Гвіздками" +#: Source/items.cpp:3823 +msgid "fastest hit recovery" +msgstr "найшвидше відновлення після удару" -#: Source/itemdat.cpp:194 -msgid "Flail" -msgstr "Ціп" +#: Source/items.cpp:3826 +msgid "fast block" +msgstr "швидкий блок" -#: Source/itemdat.cpp:195 -msgid "Maul" -msgstr "Клепач" +#: Source/items.cpp:3828 +#, c++-format +msgid "adds {:d} point to damage" +msgid_plural "adds {:d} points to damage" +msgstr[0] "додає {:d} очко до шкоди" +msgstr[1] "додає {:d} очка до шкоди" +msgstr[2] "додає {:d} очків до шкоди" -#: 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/items.cpp:3830 +msgid "fires random speed arrows" +msgstr "стріляє кілька швидких стріл" -#: Source/itemdat.cpp:197 -msgid "Hunter's Bow" -msgstr "Мисливський Лук" +#: Source/items.cpp:3832 +msgid "unusual item damage" +msgstr "у предмету незвичайна шкода" -#: Source/itemdat.cpp:198 -msgid "Long Bow" -msgstr "Довгий Лук" +#: Source/items.cpp:3834 +msgid "altered durability" +msgstr "змінена міцність" -#: Source/itemdat.cpp:199 -msgid "Composite Bow" -msgstr "Композитний Лук" +#: Source/items.cpp:3836 +msgid "one handed sword" +msgstr "одноручний меч" -#: Source/itemdat.cpp:200 -msgid "Short Battle Bow" -msgstr "Короткий Бойовий Лук" +#: Source/items.cpp:3838 +msgid "constantly lose hit points" +msgstr "постійно втрачаєш очки життя" -#: Source/itemdat.cpp:201 -msgid "Long Battle Bow" -msgstr "Довгий Бойовий Лук" +#: Source/items.cpp:3840 +msgid "life stealing" +msgstr "краде життя" -#: Source/itemdat.cpp:202 -msgid "Short War Bow" -msgstr "Короткий Воєнний Лук" +#: Source/items.cpp:3842 +msgid "no strength requirement" +msgstr "немає вимог по силі" -#: Source/itemdat.cpp:203 -msgid "Long War Bow" -msgstr "Довгий Воєнний Лук" +#: Source/items.cpp:3847 +#, c++-format +msgid "lightning damage: {:d}" +msgstr "шкода блискавкою: {:d}" -#: 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:183 -msgid "Staff" -msgstr "Посох" +#: Source/items.cpp:3849 +#, c++-format +msgid "lightning damage: {:d}-{:d}" +msgstr "шкода блискавкою: {:d}-{:d}" -#: Source/itemdat.cpp:205 -msgid "Long Staff" -msgstr "Довгий Посох" +#: Source/items.cpp:3851 +msgid "charged bolts on hits" +msgstr "при ударі стріляє зарядженою стрілою" -#: Source/itemdat.cpp:206 -msgid "Composite Staff" -msgstr "Композитний Посох" +#: Source/items.cpp:3853 +msgid "occasional triple damage" +msgstr "випадкова потрійна шкода" -#: Source/itemdat.cpp:207 -msgid "Quarter Staff" -msgstr "Четвертковий Посох" +#: Source/items.cpp:3855 +#, no-c-format, c++-format +msgid "decaying {:+d}% damage" +msgstr "шкода згасає {:+d}%" -#: Source/itemdat.cpp:208 -msgid "War Staff" -msgstr "Бойовий Посох" +#: Source/items.cpp:3857 +msgid "2x dmg to monst, 1x to you" +msgstr "2х шкд монстрам, 1х шкд тобі" -#: Source/itemdat.cpp:209 Source/itemdat.cpp:210 Source/itemdat.cpp:211 -msgid "Ring" -msgstr "Перстень" +#: Source/items.cpp:3859 +#, no-c-format +msgid "Random 0 - 600% damage" +msgstr "Випадкова шкода 0 - 600%" -#: Source/itemdat.cpp:212 Source/itemdat.cpp:213 -msgid "Amulet" -msgstr "Амулет" +#: Source/items.cpp:3861 +#, no-c-format, c++-format +msgid "low dur, {:+d}% damage" +msgstr "низька міц, {:+d}% шкоди" -#: Source/itemdat.cpp:214 -msgid "Rune of Fire" -msgstr "Руна Вогню" +# ШУ- шанс удару +#: Source/items.cpp:3865 +msgid "extra AC vs demons" +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/items.cpp:3867 +msgid "extra AC vs undead" +msgstr "додат. ШУ проти нечисті" -#: Source/itemdat.cpp:215 -msgid "Rune of Lightning" -msgstr "Руна Блискавки" +#: Source/items.cpp:3869 +msgid "50% Mana moved to Health" +msgstr "50% Мани перенесене в Здоров'я" -#: Source/itemdat.cpp:216 -msgid "Greater Rune of Fire" -msgstr "Більша Руна Вогню" +#: Source/items.cpp:3871 +msgid "40% Health moved to Mana" +msgstr "40% Здоров'я перенесене в Ману" -#: Source/itemdat.cpp:217 -msgid "Greater Rune of Lightning" -msgstr "Більша Руна Блискавки" +#: Source/items.cpp:3912 Source/items.cpp:3953 +#, c++-format +msgid "damage: {:d} Indestructible" +msgstr "шкода: {:d} Неруйновний" -#: Source/itemdat.cpp:218 -msgid "Rune of Stone" -msgstr "Руна Каменю" +#. TRANSLATORS: Dur: is durability +#: Source/items.cpp:3914 Source/items.cpp:3955 +#, c++-format +msgid "damage: {:d} Dur: {:d}/{:d}" +msgstr "шкода: {:d} Міц: {:d}/{:d}" -#: Source/itemdat.cpp:219 -msgid "Short Staff of Charged Bolt" -msgstr "Короткий Посох Зарядженої Стріли" +#: Source/items.cpp:3917 Source/items.cpp:3958 +#, c++-format +msgid "damage: {:d}-{:d} Indestructible" +msgstr "шкода: {:d}-{:d} Неруйновний" -#: Source/itemdat.cpp:220 -msgid "Arena Potion" -msgstr "Зілля Арени" +#. TRANSLATORS: Dur: is durability +#: Source/items.cpp:3919 Source/items.cpp:3960 +#, c++-format +msgid "damage: {:d}-{:d} Dur: {:d}/{:d}" +msgstr "шкода: {:d}-{:d} Міц: {:d}/{:d}" -#. TRANSLATORS: Item prefix section. -#: Source/itemdat.cpp:230 -msgid "Tin" -msgstr "Олов'яний" +#: Source/items.cpp:3924 Source/items.cpp:3970 +#, c++-format +msgid "armor: {:d} Indestructible" +msgstr "броня: {:d} Неруйновний" -#: Source/itemdat.cpp:231 -msgid "Brass" -msgstr "Латунний" +#. TRANSLATORS: Dur: is durability +#: Source/items.cpp:3926 Source/items.cpp:3972 +#, c++-format +msgid "armor: {:d} Dur: {:d}/{:d}" +msgstr "броня: {:d} Міц: {:d}/{:d}" -#: Source/itemdat.cpp:232 -msgid "Bronze" -msgstr "Бронзовий" +#: Source/items.cpp:3929 Source/items.cpp:3963 Source/items.cpp:3976 +#: Source/stores.cpp:299 +#, c++-format +msgid "Charges: {:d}/{:d}" +msgstr "Заряди: {:d}/{:d}" -#: Source/itemdat.cpp:233 -msgid "Iron" -msgstr "Залізний" +#: Source/items.cpp:3938 +msgid "unique item" +msgstr "унікальний предмет" -#: Source/itemdat.cpp:234 -msgid "Steel" -msgstr "Стальний" +#: Source/items.cpp:3966 Source/items.cpp:3974 Source/items.cpp:3980 +msgid "Not Identified" +msgstr "Не Розпізнано" -#: Source/itemdat.cpp:235 -msgid "Silver" -msgstr "Срібний" +#: Source/levels/setmaps.cpp:27 +msgid "Skeleton King's Lair" +msgstr "Лігво Короля-Скелета" -#: Source/itemdat.cpp:237 -msgid "Platinum" -msgstr "Платиновий" +#: Source/levels/setmaps.cpp:28 +msgid "Chamber of Bone" +msgstr "Палата Кості" -#: Source/itemdat.cpp:238 -msgid "Mithril" -msgstr "Міфріловий" +#. TRANSLATORS: Quest Map +#: Source/levels/setmaps.cpp:29 Source/quests.cpp:103 +msgid "Maze" +msgstr "Лабіринт" -#: Source/itemdat.cpp:239 -msgid "Meteoric" -msgstr "Метеоритний" +#: Source/levels/setmaps.cpp:30 Source/quests.cpp:65 +msgid "Poisoned Water Supply" +msgstr "Отруєне Джерело" -#: Source/itemdat.cpp:240 Source/objects.cpp:124 -msgid "Weird" -msgstr "Чудний" +#: Source/levels/setmaps.cpp:31 +msgid "Archbishop Lazarus' Lair" +msgstr "Лігво Архієпископа Лазаря" -#: Source/itemdat.cpp:241 -msgid "Strange" -msgstr "Дивний" +#: Source/levels/setmaps.cpp:32 +msgid "Church Arena" +msgstr "Арена \"Церква\"" -#: Source/itemdat.cpp:242 -msgid "Useless" -msgstr "Непотрібний" +#: Source/levels/setmaps.cpp:33 +msgid "Hell Arena" +msgstr "Арена \"Пекло\"" -#: Source/itemdat.cpp:243 -msgid "Bent" -msgstr "Погнутий" +#: Source/levels/setmaps.cpp:34 +msgid "Circle of Life Arena" +msgstr "Арена \"Коло життя\"" -#: Source/itemdat.cpp:244 -msgid "Weak" -msgstr "Слабий" +#: Source/levels/trigs.cpp:352 +msgid "Down to dungeon" +msgstr "Вниз в підземелля" -#: Source/itemdat.cpp:245 -msgid "Jagged" -msgstr "Зазублений" +#: Source/levels/trigs.cpp:361 +msgid "Down to catacombs" +msgstr "Вниз в катакомби" -#: Source/itemdat.cpp:246 -msgid "Deadly" -msgstr "Смертельний" +#: Source/levels/trigs.cpp:371 +msgid "Down to caves" +msgstr "Вниз в печери" -#: Source/itemdat.cpp:247 -msgid "Heavy" -msgstr "Важкий" +#: Source/levels/trigs.cpp:381 +msgid "Down to hell" +msgstr "Вниз в пекло" -#: Source/itemdat.cpp:248 -msgid "Vicious" -msgstr "Розпутний" +#: Source/levels/trigs.cpp:391 +msgid "Down to Hive" +msgstr "Вниз в Гніздо" -#: Source/itemdat.cpp:249 -msgid "Brutal" -msgstr "Брутальний" +#: Source/levels/trigs.cpp:401 +msgid "Down to Crypt" +msgstr "Вниз до Склепу" -#: Source/itemdat.cpp:250 -msgid "Massive" -msgstr "Масивний" +#: Source/levels/trigs.cpp:416 Source/levels/trigs.cpp:451 +#: Source/levels/trigs.cpp:497 Source/levels/trigs.cpp:549 +#, c++-format +msgid "Up to level {:d}" +msgstr "Вверх на {:d} рівень" -#: Source/itemdat.cpp:251 -msgid "Savage" -msgstr "Дикий" +#: Source/levels/trigs.cpp:418 Source/levels/trigs.cpp:480 +#: Source/levels/trigs.cpp:532 Source/levels/trigs.cpp:579 +#: Source/levels/trigs.cpp:641 Source/levels/trigs.cpp:690 +#: Source/levels/trigs.cpp:797 +msgid "Up to town" +msgstr "Вверх у Місто" -#: Source/itemdat.cpp:252 -msgid "Ruthless" -msgstr "Жорстокий" +#: Source/levels/trigs.cpp:429 Source/levels/trigs.cpp:462 +#: Source/levels/trigs.cpp:514 Source/levels/trigs.cpp:561 +#: Source/levels/trigs.cpp:623 +#, c++-format +msgid "Down to level {:d}" +msgstr "Вниз на {:d} рівень" -#: Source/itemdat.cpp:253 -msgid "Merciless" -msgstr "Безжалісний" +#: Source/levels/trigs.cpp:592 +msgid "Down to Diablo" +msgstr "Вниз до Діабло" -#: Source/itemdat.cpp:254 -msgid "Clumsy" -msgstr "Незграбний" +#: Source/levels/trigs.cpp:610 +#, c++-format +msgid "Up to Nest level {:d}" +msgstr "Вверх на {:d} рівень Гнізда" -#: Source/itemdat.cpp:255 -msgid "Dull" -msgstr "Тупий" +#: Source/levels/trigs.cpp:658 +#, c++-format +msgid "Up to Crypt level {:d}" +msgstr "Вверх на {:d} рівень Склепу" -#: Source/itemdat.cpp:256 -msgid "Sharp" -msgstr "Гострий" +#: Source/levels/trigs.cpp:668 Source/quests.cpp:74 +msgid "Cornerstone of the World" +msgstr "Наріжний Камінь Світу" -#: Source/itemdat.cpp:257 Source/itemdat.cpp:267 -msgid "Fine" -msgstr "Гарний" +#: Source/levels/trigs.cpp:673 +#, c++-format +msgid "Down to Crypt level {:d}" +msgstr "Вниз на {:d} рівень Склепу" -#: Source/itemdat.cpp:258 -msgid "Warrior's" -msgstr "Воїнський" +#: Source/levels/trigs.cpp:721 Source/levels/trigs.cpp:735 +#: Source/levels/trigs.cpp:749 +#, c++-format +msgid "Back to Level {:d}" +msgstr "Назад на {:d} рівень" -#: Source/itemdat.cpp:259 -msgid "Soldier's" -msgstr "Солдатський" +#: Source/loadsave.cpp:2093 Source/loadsave.cpp:2625 +msgid "Unable to open save file archive" +msgstr "Неможливо відкрити архів збереження" -#: Source/itemdat.cpp:260 -msgid "Lord's" -msgstr "Лордський" +#: Source/loadsave.cpp:2096 +msgid "Invalid save file" +msgstr "Файл збереження зламаний" -#: Source/itemdat.cpp:261 -msgid "Knight's" -msgstr "Лицарський" +#: Source/loadsave.cpp:2127 +msgid "Player is on a Hellfire only level" +msgstr "Гравець на рівні, доступному тільки в Hellfire" -#: Source/itemdat.cpp:262 -msgid "Master's" -msgstr "Паньский" +#: Source/loadsave.cpp:2383 +msgid "Invalid game state" +msgstr "Невірний стан гри" -#: Source/itemdat.cpp:263 -msgid "Champion's" -msgstr "Чемпіонський" +#: Source/menu.cpp:155 +msgid "Unable to display mainmenu" +msgstr "Неможливо показати головне меню" -#: Source/itemdat.cpp:264 -msgid "King's" -msgstr "Королівський" +#: Source/monster.cpp:2992 +msgid "Animal" +msgstr "Тварина" -#: Source/itemdat.cpp:265 -msgid "Vulnerable" -msgstr "Уразливий" +#: Source/monster.cpp:2994 +msgid "Demon" +msgstr "Демон" -#: Source/itemdat.cpp:266 -msgid "Rusted" -msgstr "Іржавий" +#: Source/monster.cpp:2996 +msgid "Undead" +msgstr "Нечисть" -#: Source/itemdat.cpp:268 -msgid "Strong" -msgstr "Сильний" +#: Source/monster.cpp:4299 +#, c++-format +msgid "Type: {:s} Kills: {:d}" +msgstr "Тип: {:s} Вбито: {:d}" -#: Source/itemdat.cpp:269 -msgid "Grand" -msgstr "Величний" +#: Source/monster.cpp:4301 +#, c++-format +msgid "Total kills: {:d}" +msgstr "Всього вбито: {:d}" -#: Source/itemdat.cpp:270 -msgid "Valiant" -msgstr "Доблесний" +#: Source/monster.cpp:4333 +#, c++-format +msgid "Hit Points: {:d}-{:d}" +msgstr "Життя {:d} з {:d}" -#: Source/itemdat.cpp:271 -msgid "Glorious" -msgstr "Славетний" +#: Source/monster.cpp:4338 +msgid "No magic resistance" +msgstr "Не спротивлюється магії" -#: Source/itemdat.cpp:272 -msgid "Blessed" -msgstr "Благословенний" +#: Source/monster.cpp:4341 +msgid "Resists:" +msgstr "Спротив:" -#: Source/itemdat.cpp:273 -msgid "Saintly" -msgstr "Праведний" +#: Source/monster.cpp:4343 Source/monster.cpp:4353 +msgid " Magic" +msgstr " Магія" -#: Source/itemdat.cpp:274 -msgid "Awesome" -msgstr "Фантастичний" +#: Source/monster.cpp:4345 Source/monster.cpp:4355 +msgid " Fire" +msgstr " Вогонь" -#: Source/itemdat.cpp:275 Source/objects.cpp:136 -msgid "Holy" -msgstr "Святий" +#: Source/monster.cpp:4347 Source/monster.cpp:4357 +msgid " Lightning" +msgstr " Блискавка" -#: Source/itemdat.cpp:276 -msgid "Godly" -msgstr "Божий" +#: Source/monster.cpp:4351 +msgid "Immune:" +msgstr "Імунітет:" -#: Source/itemdat.cpp:277 -msgid "Red" -msgstr "Червоний" +#: Source/monster.cpp:4368 +#, c++-format +msgid "Type: {:s}" +msgstr "Тип: {:s}" -#: Source/itemdat.cpp:278 Source/itemdat.cpp:279 -msgid "Crimson" -msgstr "Багряний" +#: Source/monster.cpp:4373 Source/monster.cpp:4379 +msgid "No resistances" +msgstr "Немає спротиву" -#: Source/itemdat.cpp:280 -msgid "Garnet" -msgstr "Гранатовий" +#: Source/monster.cpp:4374 Source/monster.cpp:4383 +msgid "No Immunities" +msgstr "Немає Імунітету" -#: Source/itemdat.cpp:281 -msgid "Ruby" -msgstr "Рубіновий" +#: Source/monster.cpp:4377 +msgid "Some Magic Resistances" +msgstr "Декілька Магічних Спротивів" -#: Source/itemdat.cpp:282 -msgid "Blue" -msgstr "Синій" +#: Source/monster.cpp:4381 +msgid "Some Magic Immunities" +msgstr "Декілька Магічних Імунітетів" -#: Source/itemdat.cpp:283 -msgid "Azure" -msgstr "Блакитний" +#: Source/mpq/mpq_writer.cpp:161 +msgid "Failed to open archive for writing." +msgstr "Неможливо відкрити архів для читання." -#: Source/itemdat.cpp:284 -msgid "Lapis" -msgstr "Лазурний" +#: Source/msg.cpp:774 +msgid "Trying to drop a floor item?" +msgstr "Пробуєте кинути предмет що на підлозі?" -#: Source/itemdat.cpp:285 -msgid "Cobalt" -msgstr "Кобальтовий" +#: Source/msg.cpp:1376 +#, c++-format +msgid "{:s} has cast an invalid spell." +msgstr "{:s} зворожував заборонені чари." -#: Source/itemdat.cpp:286 -msgid "Sapphire" -msgstr "Сапфірний" +#: Source/msg.cpp:1380 +#, c++-format +msgid "{:s} has cast an illegal spell." +msgstr "{:s} зворожував заборонені чари." -#: Source/itemdat.cpp:287 -msgid "White" -msgstr "Білий" +#: Source/msg.cpp:2011 Source/multi.cpp:793 Source/multi.cpp:843 +#, c++-format +msgid "Player '{:s}' (level {:d}) just joined the game" +msgstr "Гравець '{:s}' (рівень {:d}) приєднався до гри" -#: Source/itemdat.cpp:288 -msgid "Pearl" -msgstr "Перламутровий" +#: Source/msg.cpp:2372 +msgid "The game ended" +msgstr "Гра закінчилась" -#: Source/itemdat.cpp:289 -msgid "Ivory" -msgstr "Слонової кістки" +#: Source/msg.cpp:2378 +msgid "Unable to get level data" +msgstr "Неможливо дістати дані рівня" -#: Source/itemdat.cpp:290 -msgid "Crystal" -msgstr "Кришталевий" +#: Source/multi.cpp:260 +#, c++-format +msgid "Player '{:s}' just left the game" +msgstr "Гравець '{:s}' покинув гру" -#: Source/itemdat.cpp:291 -msgid "Diamond" -msgstr "Діамантовий" +#: Source/multi.cpp:263 +#, c++-format +msgid "Player '{:s}' killed Diablo and left the game!" +msgstr "Гравець '{:s}' вбив Діабло і покинув гру!" -#: Source/itemdat.cpp:292 -msgid "Topaz" -msgstr "Топазовий" +#: Source/multi.cpp:267 +#, c++-format +msgid "Player '{:s}' dropped due to timeout" +msgstr "Гравець '{:s}' відключився через таймаут" -#: Source/itemdat.cpp:293 -msgid "Amber" -msgstr "Бурштинний" +#: Source/multi.cpp:845 +#, c++-format +msgid "Player '{:s}' (level {:d}) is already in the game" +msgstr "Гравець '{:s}' (рівень {:d}) вже в грі" -#: Source/itemdat.cpp:294 -msgid "Jade" -msgstr "Нефритовий" +#. TRANSLATORS: Shrine Name Block +#: Source/objects.cpp:123 +msgid "Mysterious" +msgstr "Таємний" -#: Source/itemdat.cpp:295 -msgid "Obsidian" -msgstr "Обсидіяновий" +#: Source/objects.cpp:124 +msgid "Hidden" +msgstr "Схований" -#: Source/itemdat.cpp:296 -msgid "Emerald" -msgstr "Смарагдовий" +#: Source/objects.cpp:125 +msgid "Gloomy" +msgstr "Похмурий" -#: Source/itemdat.cpp:297 -msgid "Hyena's" -msgstr "Гієнний" +#: Source/objects.cpp:126 Source/translation_dummy.cpp:610 +msgid "Weird" +msgstr "Чудний" -#: Source/itemdat.cpp:298 -msgid "Frog's" -msgstr "Жаб'ячий" +#: Source/objects.cpp:127 Source/objects.cpp:134 +msgid "Magical" +msgstr "Магічний" -#: Source/itemdat.cpp:299 -msgid "Spider's" -msgstr "Павучий" +#: Source/objects.cpp:128 +msgid "Stone" +msgstr "Кам'яний" -#: Source/itemdat.cpp:300 -msgid "Raven's" -msgstr "Воронний" +#: Source/objects.cpp:129 +msgid "Religious" +msgstr "Релігійний" -#: Source/itemdat.cpp:301 -msgid "Snake's" -msgstr "Зміїний" +#: Source/objects.cpp:130 +msgid "Enchanted" +msgstr "Зачарований" -#: Source/itemdat.cpp:302 -msgid "Serpent's" -msgstr "Гадючий" +#: Source/objects.cpp:131 +msgid "Thaumaturgic" +msgstr "Чудотворний" -#: Source/itemdat.cpp:303 -msgid "Drake's" -msgstr "Левіятановий" +#: Source/objects.cpp:132 +msgid "Fascinating" +msgstr "Чарівливий" -#: Source/itemdat.cpp:304 -msgid "Dragon's" -msgstr "Драконовий" +#: Source/objects.cpp:133 +msgid "Cryptic" +msgstr "Загадковий" -#: Source/itemdat.cpp:305 -msgid "Wyrm's" -msgstr "Супостатний" +#: Source/objects.cpp:135 +msgid "Eldritch" +msgstr "Лиховісний" -#: Source/itemdat.cpp:306 -msgid "Hydra's" -msgstr "Гідровий" +#: Source/objects.cpp:136 +msgid "Eerie" +msgstr "Моторошний" -#: Source/itemdat.cpp:307 -msgid "Angel's" -msgstr "Ангела" +#: Source/objects.cpp:137 +msgid "Divine" +msgstr "Божественний" -#: Source/itemdat.cpp:308 -msgid "Arch-Angel's" -msgstr "Архангела" +#: Source/objects.cpp:138 Source/translation_dummy.cpp:645 +msgid "Holy" +msgstr "Святий" -#: Source/itemdat.cpp:309 -msgid "Plentiful" -msgstr "Рясний" +#: Source/objects.cpp:139 +msgid "Sacred" +msgstr "Святий" -#: Source/itemdat.cpp:310 -msgid "Bountiful" -msgstr "Щедрий" +#: Source/objects.cpp:140 +msgid "Spiritual" +msgstr "Духовний" -#: Source/itemdat.cpp:311 -msgid "Flaming" -msgstr "Полум'яний" +#: Source/objects.cpp:141 +msgid "Spooky" +msgstr "Лячний" -#: Source/itemdat.cpp:312 -msgid "Lightning" -msgstr "Блискавичий" +#: Source/objects.cpp:142 +msgid "Abandoned" +msgstr "Покинутий" -#: Source/itemdat.cpp:313 -msgid "Jester's" -msgstr "Блазневий" +#: Source/objects.cpp:143 +msgid "Creepy" +msgstr "Страховитий" -#: Source/itemdat.cpp:314 -msgid "Crystalline" -msgstr "Кристалічний" +#: Source/objects.cpp:144 +msgid "Quiet" +msgstr "Тихий" -#. TRANSLATORS: Item prefix section end. -#: Source/itemdat.cpp:316 -msgid "Doppelganger's" -msgstr "Двійниковий" +#: Source/objects.cpp:145 +msgid "Secluded" +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:326 -msgid "quality" -msgstr "якості" +#: Source/objects.cpp:146 +msgid "Ornate" +msgstr "Барвистий" -#: Source/itemdat.cpp:327 -msgid "maiming" -msgstr "знівечення" +#: Source/objects.cpp:147 +msgid "Glimmering" +msgstr "Блимаючий" -#: Source/itemdat.cpp:328 -msgid "slaying" -msgstr "убивання" +#: Source/objects.cpp:148 +msgid "Tainted" +msgstr "Засмерділий" -#: Source/itemdat.cpp:329 -msgid "gore" -msgstr "крові" +#: Source/objects.cpp:149 +msgid "Oily" +msgstr "Масляний" -#: Source/itemdat.cpp:330 -msgid "carnage" -msgstr "різанини" - -#: Source/itemdat.cpp:331 -msgid "slaughter" -msgstr "забою" +#: Source/objects.cpp:150 +msgid "Glowing" +msgstr "Світний" -#: Source/itemdat.cpp:332 -msgid "pain" -msgstr "болі" +#: Source/objects.cpp:151 +msgid "Mendicant's" +msgstr "Жебрака" -#: Source/itemdat.cpp:333 -msgid "tears" -msgstr "сліз" +#: Source/objects.cpp:152 +msgid "Sparkling" +msgstr "Іскристий" -#: Source/itemdat.cpp:334 -msgid "health" -msgstr "здоров'я" +#: Source/objects.cpp:154 +msgid "Shimmering" +msgstr "Мерехтливий" -#: Source/itemdat.cpp:335 -msgid "protection" -msgstr "захисту" +#: Source/objects.cpp:155 +msgid "Solar" +msgstr "Сонячний" -#: Source/itemdat.cpp:336 -msgid "absorption" -msgstr "поглинання" +#. TRANSLATORS: Shrine Name Block end +#: Source/objects.cpp:157 +msgid "Murphy's" +msgstr "Мерфі" -#: Source/itemdat.cpp:337 -msgid "deflection" -msgstr "відхилення" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:210 +msgid "The Great Conflict" +msgstr "Великий Конфлікт" -#: Source/itemdat.cpp:338 -msgid "osmosis" -msgstr "осмосу" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:211 +msgid "The Wages of Sin are War" +msgstr "Розплата за Гріхи — це Війна" -#: Source/itemdat.cpp:339 -msgid "frailty" -msgstr "субтильності" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:212 +msgid "The Tale of the Horadrim" +msgstr "Розповідь про Хорадріма" -#: Source/itemdat.cpp:340 -msgid "weakness" -msgstr "слабкості" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:213 +msgid "The Dark Exile" +msgstr "Темне Заслання" -#: Source/itemdat.cpp:341 -msgid "strength" -msgstr "сили" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:214 +msgid "The Sin War" +msgstr "Війна Гріхів" -#: Source/itemdat.cpp:342 -msgid "might" -msgstr "могутності" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:215 +msgid "The Binding of the Three" +msgstr "Зв'язування Трьох" -#: Source/itemdat.cpp:343 -msgid "power" -msgstr "міці" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:216 +msgid "The Realms Beyond" +msgstr "Виміри за Межами" -#: Source/itemdat.cpp:344 -msgid "giants" -msgstr "гіганта" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:217 +msgid "Tale of the Three" +msgstr "Оповідь про Трьох" -#: Source/itemdat.cpp:345 -msgid "titans" -msgstr "титана" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:218 +msgid "The Black King" +msgstr "Чорний Король" -#: Source/itemdat.cpp:346 -msgid "paralysis" -msgstr "паралізу" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:219 +msgid "Journal: The Ensorcellment" +msgstr "Щоденник: Чаклунство" -#: Source/itemdat.cpp:347 -msgid "atrophy" -msgstr "атрофії" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:220 +msgid "Journal: The Meeting" +msgstr "Щоденник: Зустріч" -#: Source/itemdat.cpp:348 -msgid "dexterity" -msgstr "спритності" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:221 +msgid "Journal: The Tirade" +msgstr "Щоденник: Тирада" -#: Source/itemdat.cpp:349 -msgid "skill" -msgstr "таланту" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:222 +msgid "Journal: His Power Grows" +msgstr "Щоденник: Його Сила Росте" -#: Source/itemdat.cpp:350 -msgid "accuracy" -msgstr "точності" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:223 +msgid "Journal: NA-KRUL" +msgstr "Щоденник: НА-КРУЛ" -#: Source/itemdat.cpp:351 -msgid "precision" -msgstr "влучності" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:224 +msgid "Journal: The End" +msgstr "Щоденник: Кінець" -#: Source/itemdat.cpp:352 -msgid "perfection" -msgstr "досконалості" +#. TRANSLATORS: Book Title +#: Source/objects.cpp:225 +msgid "A Spellbook" +msgstr "Книга Чар" -#: Source/itemdat.cpp:353 -msgid "the fool" -msgstr "дурня" +#: Source/objects.cpp:4769 +msgid "Crucified Skeleton" +msgstr "Розп'ятий Скелет" -#: Source/itemdat.cpp:354 -msgid "dyslexia" -msgstr "дислексії" +#: Source/objects.cpp:4773 +msgid "Lever" +msgstr "Важіль" -#: Source/itemdat.cpp:355 -msgid "magic" -msgstr "магії" +#: Source/objects.cpp:4783 +msgid "Open Door" +msgstr "Відкриті Двері" -#: Source/itemdat.cpp:356 -msgid "the mind" -msgstr "розуму" +#: Source/objects.cpp:4785 +msgid "Closed Door" +msgstr "Закриті Двері" -#: Source/itemdat.cpp:357 -msgid "brilliance" -msgstr "здібності" +#: Source/objects.cpp:4787 +msgid "Blocked Door" +msgstr "Заблоковані Двері" -#: Source/itemdat.cpp:358 -msgid "sorcery" -msgstr "чарівництва" +#: Source/objects.cpp:4792 +msgid "Ancient Tome" +msgstr "Стародавній Том" -#: Source/itemdat.cpp:359 -msgid "wizardry" -msgstr "чаклунства" +#: Source/objects.cpp:4794 +msgid "Book of Vileness" +msgstr "Кинга Підлості" -#: Source/itemdat.cpp:360 -msgid "illness" -msgstr "недуги" +#: Source/objects.cpp:4799 +msgid "Skull Lever" +msgstr "Черепний Важіль" -#: Source/itemdat.cpp:361 -msgid "disease" -msgstr "хвороби" +#: Source/objects.cpp:4801 +msgid "Mythical Book" +msgstr "Міфічна Книга" -#: Source/itemdat.cpp:362 -msgid "vitality" -msgstr "жувочості" +#: Source/objects.cpp:4804 +msgid "Small Chest" +msgstr "Маленька Скриня" -#: Source/itemdat.cpp:363 -msgid "zest" -msgstr "запалу" +#: Source/objects.cpp:4807 +msgid "Chest" +msgstr "Скриня" -#: Source/itemdat.cpp:364 -msgid "vim" -msgstr "енергії" +#: Source/objects.cpp:4811 +msgid "Large Chest" +msgstr "Велика Скриня" -#: Source/itemdat.cpp:365 -msgid "vigor" -msgstr "снаги" +#: Source/objects.cpp:4814 +msgid "Sarcophagus" +msgstr "Саркофаг" -#: Source/itemdat.cpp:366 -msgid "life" -msgstr "життя" +#: Source/objects.cpp:4816 +msgid "Bookshelf" +msgstr "Полиця з Книгами" -#: Source/itemdat.cpp:367 -msgid "trouble" -msgstr "лиха" +#: Source/objects.cpp:4819 +msgid "Bookcase" +msgstr "Шкаф з Книгами" -#: Source/itemdat.cpp:368 -msgid "the pit" -msgstr "ями" +#: Source/objects.cpp:4822 +msgid "Barrel" +msgstr "Бочка" -#: Source/itemdat.cpp:369 -msgid "the sky" -msgstr "неба" +# How does the pod look like? +#: Source/objects.cpp:4825 +msgid "Pod" +msgstr "Банка" -#: Source/itemdat.cpp:370 -msgid "the moon" -msgstr "місяця" +#: Source/objects.cpp:4828 +msgid "Urn" +msgstr "Урна" -#: Source/itemdat.cpp:371 -msgid "the stars" -msgstr "зорей" +#. TRANSLATORS: {:s} will be a name from the Shrine block above +#: Source/objects.cpp:4831 +#, c++-format +msgid "{:s} Shrine" +msgstr "{:s} Вівтар" -#: Source/itemdat.cpp:372 -msgid "the heavens" -msgstr "небес" +#: Source/objects.cpp:4833 +msgid "Skeleton Tome" +msgstr "Скелетний Том" -#: Source/itemdat.cpp:373 -msgid "the zodiac" -msgstr "зодіаку" +#: Source/objects.cpp:4835 +msgid "Library Book" +msgstr "Бібліотечна Книга" -#: Source/itemdat.cpp:374 -msgid "the vulture" -msgstr "стерв'ятника" +#: Source/objects.cpp:4837 +msgid "Blood Fountain" +msgstr "Кривавий Фонтан" -#: Source/itemdat.cpp:375 -msgid "the jackal" -msgstr "шакала" +#: Source/objects.cpp:4839 +msgid "Decapitated Body" +msgstr "Обезглавлений Труп" -#: Source/itemdat.cpp:376 -msgid "the fox" -msgstr "лисиці" +#: Source/objects.cpp:4841 +msgid "Book of the Blind" +msgstr "Книга Сліпих" -#: Source/itemdat.cpp:377 -msgid "the jaguar" -msgstr "ягуара" +#: Source/objects.cpp:4843 +msgid "Book of Blood" +msgstr "Книга Крові" -#: Source/itemdat.cpp:378 -msgid "the eagle" -msgstr "орла" +#: Source/objects.cpp:4845 +msgid "Purifying Spring" +msgstr "Очищальне Джерело" -#: Source/itemdat.cpp:379 -msgid "the wolf" -msgstr "вовка" +#: Source/objects.cpp:4848 Source/translation_dummy.cpp:316 +#: Source/translation_dummy.cpp:318 Source/translation_dummy.cpp:320 +#: Source/translation_dummy.cpp:322 +msgid "Armor" +msgstr "Броня" -#: Source/itemdat.cpp:380 -msgid "the tiger" -msgstr "тигра" +#: Source/objects.cpp:4850 Source/objects.cpp:4867 +msgid "Weapon Rack" +msgstr "Стелаж зі Зброєю" -#: Source/itemdat.cpp:381 -msgid "the lion" -msgstr "лева" +#: Source/objects.cpp:4852 +msgid "Goat Shrine" +msgstr "Вівтар Кози" -#: Source/itemdat.cpp:382 -msgid "the mammoth" -msgstr "мамонта" +#: Source/objects.cpp:4854 +msgid "Cauldron" +msgstr "Казан" -#: Source/itemdat.cpp:383 -msgid "the whale" -msgstr "кита" +#: Source/objects.cpp:4856 +msgid "Murky Pool" +msgstr "Мутний Ставок" -#: Source/itemdat.cpp:384 -msgid "fragility" -msgstr "крихкості" +#: Source/objects.cpp:4858 +msgid "Fountain of Tears" +msgstr "Фонтан Сліз" -#: Source/itemdat.cpp:385 -msgid "brittleness" -msgstr "ламкості" +#: Source/objects.cpp:4860 +msgid "Steel Tome" +msgstr "Сталевий Том" -#: Source/itemdat.cpp:386 -msgid "sturdiness" -msgstr "міцності" +#: Source/objects.cpp:4862 +msgid "Pedestal of Blood" +msgstr "П'єдестал Крові" -#: Source/itemdat.cpp:387 -msgid "craftsmanship" -msgstr "майстерності" +#: Source/objects.cpp:4869 +msgid "Mushroom Patch" +msgstr "Грибна Галявина" -#: Source/itemdat.cpp:388 -msgid "structure" -msgstr "структури" +#: Source/objects.cpp:4871 +msgid "Vile Stand" +msgstr "Огидний Стелаж" -#: Source/itemdat.cpp:389 -msgid "the ages" -msgstr "віків" +#: Source/objects.cpp:4873 +msgid "Slain Hero" +msgstr "Вбитий Герой" -#: Source/itemdat.cpp:390 -msgid "the dark" -msgstr "темряви" +#. TRANSLATORS: {:s} will either be a chest or a door +#: Source/objects.cpp:4885 +#, c++-format +msgid "Trapped {:s}" +msgstr "{:s} з пасткою" -#: Source/itemdat.cpp:391 -msgid "the night" -msgstr "ночі" +#. TRANSLATORS: If user enabled diablo.ini setting "Disable Crippling Shrines" is set to 1; also used for Na-Kruls lever +#: Source/objects.cpp:4890 +#, c++-format +msgid "{:s} (disabled)" +msgstr "{:s} (не активно)" -#: Source/itemdat.cpp:392 -msgid "light" -msgstr "світла" +#: Source/options.cpp:457 Source/options.cpp:581 Source/options.cpp:587 +msgid "ON" +msgstr "УВІМК" -#: Source/itemdat.cpp:393 -msgid "radiance" -msgstr "сяяння" +#: Source/options.cpp:457 Source/options.cpp:579 Source/options.cpp:585 +msgid "OFF" +msgstr "ВИМК" -#: Source/itemdat.cpp:394 -msgid "flame" -msgstr "полум'я" +#: Source/options.cpp:569 +msgid "Start Up" +msgstr "Запуск" -#: Source/itemdat.cpp:395 -msgid "fire" -msgstr "вогню" +#: Source/options.cpp:569 +msgid "Start Up Settings" +msgstr "Налаштування Запуску" -#: Source/itemdat.cpp:396 -msgid "burning" -msgstr "горіння" +#: Source/options.cpp:570 +msgid "Game Mode" +msgstr "Режим Гри" -#: Source/itemdat.cpp:397 -msgid "shock" -msgstr "шоку" +#: Source/options.cpp:570 +msgid "Play Diablo or Hellfire." +msgstr "Грати в Diablo або в Hellfire." -#: Source/itemdat.cpp:398 -msgid "lightning" -msgstr "блискавки" +#: Source/options.cpp:576 +msgid "Restrict to Shareware" +msgstr "Обмежити гру до Демо" -#: Source/itemdat.cpp:399 -msgid "thunder" -msgstr "грому" +#: Source/options.cpp:576 +msgid "" +"Makes the game compatible with the demo. Enables multiplayer with friends " +"who don't own a full copy of Diablo." +msgstr "" +"Робить гру сумісною з демо-версією. Дозволяє гру по мережі з друзями у яких " +"немає повної копії Diablo." -#: Source/itemdat.cpp:400 -msgid "many" -msgstr "багатьох" +#: Source/options.cpp:577 Source/options.cpp:583 +msgid "Intro" +msgstr "Ролик" -#: Source/itemdat.cpp:401 -msgid "plenty" -msgstr "безлічі" +#: Source/options.cpp:577 Source/options.cpp:583 +msgid "Shown Intro cinematic." +msgstr "Показати початковий ролик." -#: Source/itemdat.cpp:402 -msgid "thorns" -msgstr "шипів" +#: Source/options.cpp:589 +msgid "Splash" +msgstr "Заставка" -#: Source/itemdat.cpp:403 -msgid "corruption" -msgstr "псування" +#: Source/options.cpp:589 +msgid "Shown splash screen." +msgstr "Показана заставка." -#: Source/itemdat.cpp:404 -msgid "thieves" -msgstr "крадіїв" +#: Source/options.cpp:591 +msgid "Logo and Title Screen" +msgstr "Логотип і Початковий Екран" -#: Source/itemdat.cpp:405 -msgid "the bear" -msgstr "ведмеда" +#: Source/options.cpp:592 +msgid "Title Screen" +msgstr "Початковий Екран" -#: Source/itemdat.cpp:406 -msgid "the bat" -msgstr "літучої миші" +#: Source/options.cpp:611 +msgid "Diablo specific Settings" +msgstr "Налаштування Diablo" -#: Source/itemdat.cpp:407 -msgid "vampires" -msgstr "вампірів" +#: Source/options.cpp:625 +msgid "Hellfire specific Settings" +msgstr "Настройки Hellfire" -#: Source/itemdat.cpp:408 -msgid "the leech" -msgstr "п'явки" +#: Source/options.cpp:639 +msgid "Audio" +msgstr "Аудіо" -#: Source/itemdat.cpp:409 -msgid "blood" -msgstr "крові" +#: Source/options.cpp:639 +msgid "Audio Settings" +msgstr "Налаштування Аудіо" -#: Source/itemdat.cpp:410 -msgid "piercing" -msgstr "пробивання" +#: Source/options.cpp:642 +msgid "Walking Sound" +msgstr "Звук Ходьби" -#: Source/itemdat.cpp:411 -msgid "puncturing" -msgstr "проколювання" +#: Source/options.cpp:642 +msgid "Player emits sound when walking." +msgstr "При ходьбі гравець видає звук." -#: Source/itemdat.cpp:412 -msgid "bashing" -msgstr "побиття" +#: Source/options.cpp:643 +msgid "Auto Equip Sound" +msgstr "Звук Автоспорядження" -#: Source/itemdat.cpp:413 -msgid "readiness" -msgstr "готовності" +#: Source/options.cpp:643 +msgid "Automatically equipping items on pickup emits the equipment sound." +msgstr "Автоматичне спорядження предметів видає звук спорядження." -#: Source/itemdat.cpp:414 -msgid "swiftness" -msgstr "хуткості" +#: Source/options.cpp:644 +msgid "Item Pickup Sound" +msgstr "Звук Підбору Предмету" -#: Source/itemdat.cpp:415 -msgid "speed" -msgstr "швидкості" +#: Source/options.cpp:644 +msgid "Picking up items emits the items pickup sound." +msgstr "Підбір предметів видає звук підбору." -#: Source/itemdat.cpp:416 -msgid "haste" -msgstr "поспішності" +#: Source/options.cpp:645 +msgid "Sample Rate" +msgstr "Частота Дискретизації" -#: Source/itemdat.cpp:417 -msgid "balance" -msgstr "балансу" +#: Source/options.cpp:645 +msgid "Output sample rate (Hz)." +msgstr "Частота дискретизації звуку (в Гц)" -#: Source/itemdat.cpp:418 -msgid "stability" -msgstr "стабільності" +#: Source/options.cpp:646 +msgid "Channels" +msgstr "Канали" -#: Source/itemdat.cpp:419 -msgid "harmony" -msgstr "гармонії" +#: Source/options.cpp:646 +msgid "Number of output channels." +msgstr "Кількість каналів звуку." -#: Source/itemdat.cpp:420 -msgid "blocking" -msgstr "блокування" +#: Source/options.cpp:647 +msgid "Buffer Size" +msgstr "Розмір Буферу" -#: Source/itemdat.cpp:421 -msgid "devastation" -msgstr "спустошення" +#: Source/options.cpp:647 +msgid "Buffer size (number of frames per channel)." +msgstr "Розмір буферу (кількість кадрів на канал)." -#: Source/itemdat.cpp:422 -msgid "decay" -msgstr "занепаду" +#: Source/options.cpp:648 +msgid "Resampling Quality" +msgstr "Якість Семплування" -#. TRANSLATORS: Item suffix section end. -#: Source/itemdat.cpp:424 -msgid "peril" -msgstr "небезпеки" +#: Source/options.cpp:648 +msgid "Quality of the resampler, from 0 (lowest) to 10 (highest)." +msgstr "Якість семлеру, від 0 (найнижча) до 10 (найвища)." -#. TRANSLATORS: Unique Item section -#: Source/itemdat.cpp:434 -msgid "The Butcher's Cleaver" -msgstr "Сікач М'ясника" +#: Source/options.cpp:679 +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/itemdat.cpp:444 -msgid "The Rift Bow" -msgstr "Лук Розриву" +#: Source/options.cpp:825 +msgid "Resampler" +msgstr "Cемплер" -#: Source/itemdat.cpp:445 -msgid "The Needler" -msgstr "Голкостріл" +#: Source/options.cpp:825 +msgid "Audio resampler" +msgstr "Аудіо cемплер" -#: Source/itemdat.cpp:446 -msgid "The Celestial Bow" -msgstr "Небесний Лук" +#: Source/options.cpp:882 +msgid "Device" +msgstr "Пристрій" -#: Source/itemdat.cpp:447 -msgid "Deadly Hunter" -msgstr "Смертельний Мисливець" +#: Source/options.cpp:882 +msgid "Audio device" +msgstr "Пристрій аудіо" -#: Source/itemdat.cpp:448 -msgid "Bow of the Dead" -msgstr "Лук Мертвих" +#: Source/options.cpp:950 +msgid "Graphics" +msgstr "Графіка" -#: Source/itemdat.cpp:449 -msgid "The Blackoak Bow" -msgstr "Лук з Чорного Дуба" +#: Source/options.cpp:950 +msgid "Graphics Settings" +msgstr "Налаштування Графіки" -#: Source/itemdat.cpp:450 -msgid "Flamedart" -msgstr "Вогнедротик" +#: Source/options.cpp:951 +msgid "Fullscreen" +msgstr "Повноекранний Режим" -#: Source/itemdat.cpp:451 -msgid "Fleshstinger" -msgstr "Жало Плоті" +#: Source/options.cpp:951 +msgid "Display the game in windowed or fullscreen mode." +msgstr "Показує гру в режимі повного екрану чи вікна." -#: Source/itemdat.cpp:452 -msgid "Windforce" -msgstr "Сила Вітру" +#: Source/options.cpp:953 +msgid "Fit to Screen" +msgstr "Налаштувати по Екрану" -#: Source/itemdat.cpp:453 -msgid "Eaglehorn" -msgstr "Орлиний Ріг" +#: Source/options.cpp:953 +msgid "" +"Automatically adjust the game window to your current desktop screen aspect " +"ratio and resolution." +msgstr "" +"Автоматично налаштовує вікно гри згідно співвідношення сторін і роздільної " +"здатності екрану." -#: Source/itemdat.cpp:454 -msgid "Gonnagal's Dirk" -msgstr "Кинджал Гоннагала" +#: Source/options.cpp:956 +msgid "Upscale" +msgstr "Збільшення Зображення" -#: Source/itemdat.cpp:455 -msgid "The Defender" -msgstr "Захисник" +#: Source/options.cpp:956 +msgid "" +"Enables image scaling from the game resolution to your monitor resolution. " +"Prevents changing the monitor resolution and allows window resizing." +msgstr "" +"Дозволяє збільшення зображення гри. Забороняє зміну роздільної здатності " +"монітора. Дозволяє міняти розмір вікна." -#: Source/itemdat.cpp:456 -msgid "Gryphon's Claw" -msgstr "Кіготь Грифона" +#: Source/options.cpp:963 +msgid "Scaling Quality" +msgstr "Якість Масштабування" -#: Source/itemdat.cpp:457 -msgid "Black Razor" -msgstr "Чорна Бритва" +#: Source/options.cpp:963 +msgid "Enables optional filters to the output image when upscaling." +msgstr "Накладає опційні фільтри до збільшеної картинки." -#: Source/itemdat.cpp:458 -msgid "Gibbous Moon" -msgstr "Пів-місяць" +#: Source/options.cpp:965 +msgid "Nearest Pixel" +msgstr "Найближчий Піксель" -#: Source/itemdat.cpp:459 -msgid "Ice Shank" -msgstr "Крижана Заточка" +#: Source/options.cpp:966 +msgid "Bilinear" +msgstr "Білінійний" -#: Source/itemdat.cpp:460 -msgid "The Executioner's Blade" -msgstr "Клинок Ката" +#: Source/options.cpp:967 +msgid "Anisotropic" +msgstr "Анізотропний" -#: Source/itemdat.cpp:461 -msgid "The Bonesaw" -msgstr "Кісткопил" +#: Source/options.cpp:969 +msgid "Integer Scaling" +msgstr "Коефіцієнт Масштабування" -#: Source/itemdat.cpp:462 -msgid "Shadowhawk" -msgstr "Тіньовий Яструб" +#: Source/options.cpp:969 +msgid "Scales the image using whole number pixel ratio." +msgstr "Збільшує зображення гри по коефіцієнту." -#: Source/itemdat.cpp:463 -msgid "Wizardspike" -msgstr "Вістря Мага" +#: Source/options.cpp:976 +msgid "Vertical Sync" +msgstr "Вертикальна Синхронізація" -#: Source/itemdat.cpp:464 -msgid "Lightsabre" -msgstr "Світловий Меч" +#: Source/options.cpp:977 +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/itemdat.cpp:465 -msgid "The Falcon's Talon" -msgstr "Кіготь Сокола" +#: Source/options.cpp:986 +msgid "Zoom on when enabled." +msgstr "Коли ввімкнено, наближує екран гри." -#: Source/itemdat.cpp:466 -msgid "Inferno" -msgstr "Інферно" +#: Source/options.cpp:987 +msgid "Color Cycling" +msgstr "Циклування Кольорів" -#: Source/itemdat.cpp:467 -msgid "Doombringer" -msgstr "Погибельник" +#: Source/options.cpp:987 +msgid "Color cycling effect used for water, lava, and acid animation." +msgstr "" +"Ефект циклування кольорів, що використовується для анімації води, лави та " +"кислоти." -#: Source/itemdat.cpp:468 -msgid "The Grizzly" -msgstr "Грізлі" +#: Source/options.cpp:988 +msgid "Alternate nest art" +msgstr "Альтернативна палітра для Гнізда" -#: Source/itemdat.cpp:469 -msgid "The Grandfather" -msgstr "Прадід" +#: Source/options.cpp:988 +msgid "The game will use an alternative palette for Hellfire’s nest tileset." +msgstr "" +"У грі буде використовуватися альтернативна палітра для плиток Гнізда з " +"Hellfire." -#: Source/itemdat.cpp:470 -msgid "The Mangler" -msgstr "Калічитель" +#: Source/options.cpp:990 +msgid "Hardware Cursor" +msgstr "Апаратний Курсор" -#: Source/itemdat.cpp:471 -msgid "Sharp Beak" -msgstr "Гострий Дзьоб" +#: Source/options.cpp:990 +msgid "Use a hardware cursor" +msgstr "Використовувати апаратно-прискорений курсор" -#: Source/itemdat.cpp:472 -msgid "BloodSlayer" -msgstr "Крововбивця" +#: Source/options.cpp:991 +msgid "Hardware Cursor For Items" +msgstr "Апаратний Курсор для Предметів" -#: Source/itemdat.cpp:473 -msgid "The Celestial Axe" -msgstr "Небесна Сокира" +#: Source/options.cpp:991 +msgid "Use a hardware cursor for items." +msgstr "Використовувати апаратно-прискорений курсор для предметів." -#: Source/itemdat.cpp:474 -msgid "Wicked Axe" -msgstr "Зла Сокира" +#: Source/options.cpp:992 +msgid "Hardware Cursor Maximum Size" +msgstr "Макс Розмір Аппаратного Курсору" -#: Source/itemdat.cpp:475 -msgid "Stonecleaver" -msgstr "Каменокол" +#: Source/options.cpp:992 +msgid "" +"Maximum width / height for the hardware cursor. Larger cursors fall back to " +"software." +msgstr "" +"Максимальна широта / висота для апаратно-прискореного курсору. Більші " +"курсори використовують програмний спосіб." -#: Source/itemdat.cpp:476 -msgid "Aguinara's Hatchet" -msgstr "Томагавк Агінари" +#: Source/options.cpp:994 +msgid "FPS Limiter" +msgstr "Ліміт Кадрів в Секунду" -#: Source/itemdat.cpp:477 -msgid "Hellslayer" -msgstr "Пекельний Вбивця" +#: Source/options.cpp:994 +msgid "FPS is limited to avoid high CPU load. Limit considers refresh rate." +msgstr "" +"Кадри в секунду лімітовані, щоб запобігти високе використання процессора. " +"Ліміт враховує частоту оновлення монітора." -#: Source/itemdat.cpp:478 -msgid "Messerschmidt's Reaver" -msgstr "Грабіжник Мессершмідта" +#: Source/options.cpp:995 +msgid "Show FPS" +msgstr "Показати Кадри в Секунду" -#: Source/itemdat.cpp:479 -msgid "Crackrust" -msgstr "Іржа" +#: Source/options.cpp:995 +msgid "Displays the FPS in the upper left corner of the screen." +msgstr "Показує лічильник кадрів в секунду в верхньому лівому кутку екрана." -#: Source/itemdat.cpp:480 -msgid "Hammer of Jholm" -msgstr "Молот Йольма" +#: Source/options.cpp:1043 +msgid "Gameplay" +msgstr "Гра" -#: Source/itemdat.cpp:481 -msgid "Civerb's Cudgel" -msgstr "Дубина Циверба" +#: Source/options.cpp:1043 +msgid "Gameplay Settings" +msgstr "Налаштування Гри" -#: Source/itemdat.cpp:482 -msgid "The Celestial Star" -msgstr "Небесна Зоря" +#: Source/options.cpp:1045 +msgid "" +"Enable jogging/fast walking in town for Diablo and Hellfire. This option was " +"introduced in the expansion." +msgstr "" +"Вмикає біг/швидку ходьбу в Місті для Diablo та Hellfire. Цю опцію вперше " +"додали в Hellfire." -#: Source/itemdat.cpp:483 -msgid "Baranar's Star" -msgstr "Зірка Баранара" +#: Source/options.cpp:1046 +msgid "Grab Input" +msgstr "Захопити Мишу" -#: Source/itemdat.cpp:484 -msgid "Gnarled Root" -msgstr "Сучкуватий Корінь" +#: Source/options.cpp:1046 +msgid "When enabled mouse is locked to the game window." +msgstr "Коли це налаштування ввімкнене, курсор буде прив'язаний до вікна гри ." -#: Source/itemdat.cpp:485 -msgid "The Cranium Basher" -msgstr "Черепний Розбійник" +#: Source/options.cpp:1047 +msgid "Enable Little Girl quest." +msgstr "Дозволяє квест Маленької Дівчинки." -#: Source/itemdat.cpp:486 -msgid "Schaefer's Hammer" -msgstr "Молот Шефера" +#: Source/options.cpp:1048 +msgid "" +"Enable Jersey's quest. Lester the farmer is replaced by the Complete Nut." +msgstr "Дозволяє квест Джерсі. Замість Фермера Лестера буде Повний Псих." -#: Source/itemdat.cpp:487 -msgid "Dreamflange" -msgstr "Край Сну" +#: Source/options.cpp:1049 +msgid "Friendly Fire" +msgstr "Дружній Вогонь" -#: Source/itemdat.cpp:488 -msgid "Staff of Shadows" -msgstr "Посох Тіней" +#: Source/options.cpp:1049 +msgid "" +"Allow arrow/spell damage between players in multiplayer even when the " +"friendly mode is on." +msgstr "" +"Дозволяє шкоду від чар/стріл гравцям в мережевій грі, навіть якщо ввімкнений " +"дружній режим." -#: Source/itemdat.cpp:489 -msgid "Immolator" -msgstr "Спалювач" +#: Source/options.cpp:1050 +msgid "Full quests in Multiplayer" +msgstr "Повні квести в Мережевій Грі" -#: Source/itemdat.cpp:490 -msgid "Storm Spire" -msgstr "Штормовий Шпиль" +#: Source/options.cpp:1050 +msgid "Enables the full/uncut singleplayer version of quests." +msgstr "Вмикає повні/неурізані версії квестів з одиночної гри." -#: Source/itemdat.cpp:491 -msgid "Gleamsong" -msgstr "Мерехтюча Пісня" +#: Source/options.cpp:1051 +msgid "Test Bard" +msgstr "Тестовий Бард" -#: Source/itemdat.cpp:492 -msgid "Thundercall" -msgstr "Поклик Грому" +#: Source/options.cpp:1051 +msgid "Force the Bard character type to appear in the hero selection menu." +msgstr "Примусити \"Бард\" з'явитися в меню вибору типу героя." -#: Source/itemdat.cpp:493 -msgid "The Protector" -msgstr "Захисник" +#: Source/options.cpp:1052 +msgid "Test Barbarian" +msgstr "Тестовий Варвар" -#: Source/itemdat.cpp:494 -msgid "Naj's Puzzler" -msgstr "Головоломка Наджа" +#: Source/options.cpp:1052 +msgid "" +"Force the Barbarian character type to appear in the hero selection menu." +msgstr "Примусити \"Варвар\" з'явитися в меню вибору типу героя." -#: Source/itemdat.cpp:495 -msgid "Mindcry" -msgstr "Крик Розуму" +#: Source/options.cpp:1053 +msgid "Experience Bar" +msgstr "Шкала Досвіду" -#: Source/itemdat.cpp:496 -msgid "Rod of Onan" -msgstr "Жезл Онана" +#: Source/options.cpp:1053 +msgid "Experience Bar is added to the UI at the bottom of the screen." +msgstr "Додає внизу екрана Шкалу Досвіду." -#: Source/itemdat.cpp:497 -msgid "Helm of Spirits" -msgstr "Шлем Духів" +#: Source/options.cpp:1054 +msgid "Show Item Graphics in Stores" +msgstr "Показувати Іконку Предмету в Магазині" -#: Source/itemdat.cpp:498 -msgid "Thinking Cap" -msgstr "Думаюча Шапка" +#: Source/options.cpp:1054 +msgid "Show item graphics to the left of item descriptions in store menus." +msgstr "Показувати іконку предмету зліва від його опису в меню магазину." -#: Source/itemdat.cpp:499 -msgid "OverLord's Helm" -msgstr "Шлем Повелителя" +#: Source/options.cpp:1055 +msgid "Show health values" +msgstr "Показати значення здоров'я" -#: Source/itemdat.cpp:500 -msgid "Fool's Crest" -msgstr "Герб Дурня" +#: Source/options.cpp:1055 +msgid "Displays current / max health value on health globe." +msgstr "Показує значення поточного/максимального здоров'я на кулі здоров'я." -#: Source/itemdat.cpp:501 -msgid "Gotterdamerung" -msgstr "Загибель Богів" +#: Source/options.cpp:1056 +msgid "Show mana values" +msgstr "Показати значення мани" -#: Source/itemdat.cpp:502 -msgid "Royal Circlet" -msgstr "Королівський Вінець" +#: Source/options.cpp:1056 +msgid "Displays current / max mana value on mana globe." +msgstr "Показує значення поточної/максимальної мани на кулі мани." -#: Source/itemdat.cpp:503 -msgid "Torn Flesh of Souls" -msgstr "Розірвана Плоть Душ" +#: Source/options.cpp:1057 +msgid "Enemy Health Bar" +msgstr "Шкала Ворожого Життя" -#: Source/itemdat.cpp:504 -msgid "The Gladiator's Bane" -msgstr "Прокляття Гладіатора" +#: Source/options.cpp:1057 +msgid "Enemy Health Bar is displayed at the top of the screen." +msgstr "Додає внизу екрана Шкалу Ворожого Життя." -#: Source/itemdat.cpp:505 -msgid "The Rainbow Cloak" -msgstr "Веселковий Плащ" - -#: Source/itemdat.cpp:506 -msgid "Leather of Aut" -msgstr "Шкіра Аута" +#: Source/options.cpp:1058 +msgid "Gold is automatically collected when in close proximity to the player." +msgstr "" +"Золото автоматично збирається коли гравець знаходиться близько до нього." -#: Source/itemdat.cpp:507 -msgid "Wisdom's Wrap" -msgstr "Шаль Мудрості" +#: Source/options.cpp:1059 +msgid "" +"Elixirs are automatically collected when in close proximity to the player." +msgstr "" +"Еліксири автоматично збираються коли гравець знаходиться близько до них." -#: Source/itemdat.cpp:508 -msgid "Sparking Mail" -msgstr "Іскряча Броня" +#: Source/options.cpp:1060 +msgid "Oils are automatically collected when in close proximity to the player." +msgstr "Масла автоматично збираються коли гравець знаходиться близько до них." -#: Source/itemdat.cpp:509 -msgid "Scavenger Carapace" -msgstr "Панцир Cміттяра" +#: Source/options.cpp:1061 +msgid "Automatically pickup items in town." +msgstr "Предмети в Місті автоматично збираються." -#: Source/itemdat.cpp:510 -msgid "Nightscape" -msgstr "Нічний Покров" +#: Source/options.cpp:1062 +msgid "Adria will refill your mana when you visit her shop." +msgstr "Адрія поповнить ману коли гравець відвідує її магазин." -#: Source/itemdat.cpp:511 -msgid "Naj's Light Plate" -msgstr "Легка Пластина Наджа" +#: Source/options.cpp:1063 +msgid "" +"Weapons will be automatically equipped on pickup or purchase if enabled." +msgstr "Зброя буде автоматично споряджена після її підбору або покупки." -#: Source/itemdat.cpp:512 -msgid "Demonspike Coat" -msgstr "Мундир з Демоношипів" +#: Source/options.cpp:1064 +msgid "Armor will be automatically equipped on pickup or purchase if enabled." +msgstr "Броня буде автоматично споряджена після її підбору або покупки." -#: Source/itemdat.cpp:513 -msgid "The Deflector" -msgstr "Дефлектор" +#: Source/options.cpp:1065 +msgid "Helms will be automatically equipped on pickup or purchase if enabled." +msgstr "Шоломи будуть автоматично споряджені після їх підбору або покупки." -#: Source/itemdat.cpp:514 -msgid "Split Skull Shield" -msgstr "Щит-Череполом" +#: Source/options.cpp:1066 +msgid "" +"Shields will be automatically equipped on pickup or purchase if enabled." +msgstr "Щити будуть автоматично споряджені після їх підбору або покупки." -#: Source/itemdat.cpp:515 -msgid "Dragon's Breach" -msgstr "Пролом Дракона" +#: Source/options.cpp:1067 +msgid "" +"Jewelry will be automatically equipped on pickup or purchase if enabled." +msgstr "" +"Коштовності будуть автоматично споряджені після їх підбору або покупки." -#: Source/itemdat.cpp:516 -msgid "Blackoak Shield" -msgstr "Щит з Чорного Дуба" +#: Source/options.cpp:1068 +msgid "Randomly selecting available quests for new games." +msgstr "Квести випадково вибираються для нової гри." -#: Source/itemdat.cpp:517 -msgid "Holy Defender" -msgstr "Святий Захисник" +#: Source/options.cpp:1069 +msgid "Show Monster Type" +msgstr "Показати Тип Монстра" -#: Source/itemdat.cpp:518 -msgid "Stormshield" -msgstr "Штормовий Щит" +#: Source/options.cpp:1069 +msgid "" +"Hovering over a monster will display the type of monster in the description " +"box in the UI." +msgstr "" +"Наведення курсора миші над монстром покаже його тип в Панелі Інформації." -#: Source/itemdat.cpp:519 -msgid "Bramble" -msgstr "Терен" +#: Source/options.cpp:1070 +msgid "Show labels for items on the ground when enabled." +msgstr "Показати назви предметів на землі, коли це налаштування ввімкнене." -#: Source/itemdat.cpp:520 -msgid "Ring of Regha" -msgstr "Кільце Реги" +#: Source/options.cpp:1071 +msgid "Refill belt from inventory when belt item is consumed." +msgstr "" +"Коли предмет з Поясу використовується, Пояс автоматично поповнюється з " +"інвентаря." -#: Source/itemdat.cpp:521 -msgid "The Bleeder" -msgstr "Кровопроливач" +#: Source/options.cpp:1072 +msgid "" +"When enabled Cauldrons, Fascinating Shrines, Goat Shrines, Ornate Shrines, " +"Sacred Shrines and Murphy's Shrines are not able to be clicked on and " +"labeled as disabled." +msgstr "" +"Коли це налаштування ввімкнене, гравець не зможе клікнути на Казани, " +"Чарівливі, Барвисті, Святі Вівтарі і Вівтарі Кози, які будуть показані як " +"неактивні." -#: Source/itemdat.cpp:522 -msgid "Constricting Ring" -msgstr "Тісний Перстень" +#: Source/options.cpp:1073 +msgid "Quick Cast" +msgstr "Швидке Чаклування" -#: Source/itemdat.cpp:523 -msgid "Ring of Engagement" -msgstr "Перстень Заручення" +#: Source/options.cpp:1073 +msgid "" +"Spell hotkeys instantly cast the spell, rather than switching the readied " +"spell." +msgstr "Гаряча клавішу чар буде відразу чаклувати попередньо вибрані чари." -#: Source/itemdat.cpp:524 -msgid "Giant's Knuckle" -msgstr "Кулак Гіганта" +#: Source/options.cpp:1074 +msgid "Number of Healing potions to pick up automatically." +msgstr "Кількість Зілля Зцілення щоб автоматично підібрати." -#: Source/itemdat.cpp:525 -msgid "Mercurial Ring" -msgstr "Ртутний Перстень" +#: Source/options.cpp:1075 +msgid "Number of Full Healing potions to pick up automatically." +msgstr "Кількість Зілля Повного Зцілення щоб автоматично підібрати." -#: Source/itemdat.cpp:526 -msgid "Xorine's Ring" -msgstr "Перстень Ксорини" +#: Source/options.cpp:1076 +msgid "Number of Mana potions to pick up automatically." +msgstr "Кількість Зілля Мани щоб автоматично підібрати." -#: Source/itemdat.cpp:527 -msgid "Karik's Ring" -msgstr "Перстень Карика" +#: Source/options.cpp:1077 +msgid "Number of Full Mana potions to pick up automatically." +msgstr "Кількість Зілля Повної щоб автоматично підібрати." -#: Source/itemdat.cpp:528 -msgid "Ring of Magma" -msgstr "Перстень Магми" +#: Source/options.cpp:1078 +msgid "Number of Rejuvenation potions to pick up automatically." +msgstr "Кількість Зілля Омолодження щоб автоматично підібрати." -#: Source/itemdat.cpp:529 -msgid "Ring of the Mystics" -msgstr "Перстень Містиків" +#: Source/options.cpp:1079 +msgid "Number of Full Rejuvenation potions to pick up automatically." +msgstr "Кількість Зілля Повного Омолодження щоб автоматично підібрати." -#: Source/itemdat.cpp:530 -msgid "Ring of Thunder" -msgstr "Перстень Грому" +#: Source/options.cpp:1080 +msgid "Enable floating numbers" +msgstr "Ввімкнути Плаваючі Числа" -#: Source/itemdat.cpp:531 -msgid "Amulet of Warding" -msgstr "Амулет Охорони" +#: Source/options.cpp:1080 +msgid "Enables floating numbers on gaining XP / dealing damage etc." +msgstr "Вмикає плаваючі числа при отриманні XP / нанесенні шкоди тощо." -#: Source/itemdat.cpp:532 -msgid "Gnat Sting" -msgstr "Укус Комара" +#: Source/options.cpp:1082 +msgid "Off" +msgstr "Вимк" -#: Source/itemdat.cpp:533 -msgid "Flambeau" -msgstr "Фламбо" +#: Source/options.cpp:1083 +msgid "Random Angles" +msgstr "Випадкові Кути" -#: Source/itemdat.cpp:534 -msgid "Armor of Gloom" -msgstr "Броня Мороку" +#: Source/options.cpp:1084 +msgid "Vertical Only" +msgstr "Тільки Вертикально" -#: Source/itemdat.cpp:535 -msgid "Blitzen" -msgstr "Блітцен" +#: Source/options.cpp:1135 +msgid "Controller" +msgstr "Контроллер" -#: Source/itemdat.cpp:536 -msgid "Thunderclap" -msgstr "Грім" +#: Source/options.cpp:1135 +msgid "Controller Settings" +msgstr "Налаштування Контроллеру" -#: Source/itemdat.cpp:537 -msgid "Shirotachi" -msgstr "Широтачі" +#: Source/options.cpp:1144 +msgid "Network" +msgstr "Мережа" -#: Source/itemdat.cpp:538 -msgid "Eater of Souls" -msgstr "Пожирач Душ" +#: Source/options.cpp:1144 +msgid "Network Settings" +msgstr "Налаштування Мережі" -#: Source/itemdat.cpp:539 -msgid "Diamondedge" -msgstr "Діамантове Лезо" +#: Source/options.cpp:1156 +msgid "Chat" +msgstr "Чат" -#: Source/itemdat.cpp:540 -msgid "Bone Chain Armor" -msgstr "Кістяна Кільчаста Броня" +#: Source/options.cpp:1156 +msgid "Chat Settings" +msgstr "Налаштування Чату" -#: Source/itemdat.cpp:541 -msgid "Demon Plate Armor" -msgstr "Демонська Латна Броня" +#: Source/options.cpp:1165 Source/options.cpp:1281 +msgid "Language" +msgstr "Мова" -#: Source/itemdat.cpp:542 -msgid "Acolyte's Amulet" -msgstr "Амулет Прислужника" +#: Source/options.cpp:1165 +msgid "Define what language to use in game." +msgstr "Визначте мову гри." -#. TRANSLATORS: Unique Item section end. -#: Source/itemdat.cpp:544 -msgid "Gladiator's Ring" -msgstr "Перстень Гладіатора" +#: Source/options.cpp:1281 +msgid "Language Settings" +msgstr "Налаштування Мови" -#: Source/items.cpp:176 -msgid "Oil of Mastery" -msgstr "Масло Майстерності" +#: Source/options.cpp:1293 +msgid "Keymapping" +msgstr "Назначення Клавіш" -#: Source/items.cpp:178 -msgid "Oil of Death" -msgstr "Масло Смерті" +#: Source/options.cpp:1293 +msgid "Keymapping Settings" +msgstr "Налаштування Назначення" -#: Source/items.cpp:179 -msgid "Oil of Skill" -msgstr "Масло Таланту" +#: Source/options.cpp:1551 +msgid "Padmapping" +msgstr "Падмапінг" -#: Source/items.cpp:181 -msgid "Oil of Fortitude" -msgstr "Масло Стійкості" +#: Source/options.cpp:1551 +msgid "Padmapping Settings" +msgstr "Налаштування Падмапінгу" -#: Source/items.cpp:182 -msgid "Oil of Permanence" -msgstr "Масло Міцності" +#: Source/panels/charpanel.cpp:128 +msgid "Level" +msgstr "Рівень" -#: Source/items.cpp:183 -msgid "Oil of Hardening" -msgstr "Масло Гартування" +#: Source/panels/charpanel.cpp:130 +msgid "Experience" +msgstr "Досвід" -#: Source/items.cpp:184 -msgid "Oil of Imperviousness" -msgstr "Масло Непроникності" +#: Source/panels/charpanel.cpp:135 +msgid "Next level" +msgstr "Наступний рівень" -#. TRANSLATORS: Constructs item names. Format: {Item} of {Spell}. Example: War Staff of Firewall -#: Source/items.cpp:1113 -msgctxt "spell" -msgid "{0} of {1}" -msgstr "{0} {1}" +#: Source/panels/charpanel.cpp:145 +msgid "Base" +msgstr "Базовий" -#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Spell}. Example: King's War Staff of Firewall -#: Source/items.cpp:1125 -msgctxt "spell" -msgid "{0} {1} of {2}" -msgstr "{0} {1} {2}" +#: Source/panels/charpanel.cpp:146 +msgid "Now" +msgstr "Зараз" -#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Suffix}. Example: King's Long Sword of the Whale -#: Source/items.cpp:1163 -msgid "{0} {1} of {2}" -msgstr "{0} {1} {2}" +#: Source/panels/charpanel.cpp:147 +msgid "Strength" +msgstr "Сила" -#. TRANSLATORS: Constructs item names. Format: {Prefix} {Item}. Example: King's Long Sword -#: Source/items.cpp:1166 -msgid "{0} {1}" -msgstr "{0} {1}" +#: Source/panels/charpanel.cpp:151 +msgid "Magic" +msgstr "Магія" -#. TRANSLATORS: Constructs item names. Format: {Item} of {Suffix}. Example: Long Sword of the Whale -#: Source/items.cpp:1169 -msgid "{0} of {1}" -msgstr "{0} {1}" +#: Source/panels/charpanel.cpp:155 +msgid "Dexterity" +msgstr "Спринтість" -#: Source/items.cpp:1700 Source/items.cpp:1708 -msgid "increases a weapon's" -msgstr "підвищує у зброї" +#: Source/panels/charpanel.cpp:158 +msgid "Vitality" +msgstr "Живучість" -#: Source/items.cpp:1701 -msgid "chance to hit" -msgstr "шанс удару" +#: Source/panels/charpanel.cpp:161 +msgid "Points to distribute" +msgstr "Розподілити очків" -#: Source/items.cpp:1704 -msgid "greatly increases a" -msgstr "набагато підвищує" +#: Source/panels/charpanel.cpp:167 Source/translation_dummy.cpp:247 +#: Source/translation_dummy.cpp:606 +msgid "Gold" +msgstr "Золотий" -#: Source/items.cpp:1705 -msgid "weapon's chance to hit" -msgstr "шанс удару зброї" +#: Source/panels/charpanel.cpp:171 +msgid "Armor class" +msgstr "Клас броні" -#: Source/items.cpp:1709 -msgid "damage potential" -msgstr "потенціал шкоди" +#: Source/panels/charpanel.cpp:173 +msgid "To hit" +msgstr "Шанс" -#: Source/items.cpp:1712 -msgid "greatly increases a weapon's" -msgstr "набагато підвищує у зброї" +#: Source/panels/charpanel.cpp:175 +msgid "Damage" +msgstr "Шкода" -#: Source/items.cpp:1713 -msgid "damage potential - not bows" -msgstr "потенціал шкоди — крім луків" +#: Source/panels/charpanel.cpp:182 +msgid "Life" +msgstr "Життя" -#: Source/items.cpp:1716 -msgid "reduces attributes needed" -msgstr "знижує вимоги атрибутів" +#: Source/panels/charpanel.cpp:186 +msgid "Mana" +msgstr "Мана" -#: Source/items.cpp:1717 -msgid "to use armor or weapons" -msgstr "шоб використати броню чи зброю" +#: Source/panels/charpanel.cpp:191 +msgid "Resist magic" +msgstr "Спротив магії" -#: Source/items.cpp:1720 -#, no-c-format -msgid "restores 20% of an" -msgstr "відновлює 20%" +#: Source/panels/charpanel.cpp:193 +msgid "Resist fire" +msgstr "Спротив вогню" -#: Source/items.cpp:1721 -msgid "item's durability" -msgstr "міцність предмету" +#: Source/panels/charpanel.cpp:195 +msgid "Resist lightning" +msgstr "Спротив блискавці" -#: Source/items.cpp:1724 -msgid "increases an item's" -msgstr "підвищує у предмета" +#: Source/panels/mainpanel.cpp:87 +msgid "char" +msgstr "герой" -#: Source/items.cpp:1725 -msgid "current and max durability" -msgstr "поточну і макс міцність" +#: Source/panels/mainpanel.cpp:88 +msgid "quests" +msgstr "журнал" -#: Source/items.cpp:1728 -msgid "makes an item indestructible" -msgstr "робить предмет неруйновним" +#: Source/panels/mainpanel.cpp:89 +msgid "map" +msgstr "карта" -#: Source/items.cpp:1731 -msgid "increases the armor class" -msgstr "підвищує клас броні" +#: Source/panels/mainpanel.cpp:90 +msgid "menu" +msgstr "меню" -#: Source/items.cpp:1732 -msgid "of armor and shields" -msgstr "броні і щитів" +#: Source/panels/mainpanel.cpp:91 +msgid "inv" +msgstr "інвен" -#: Source/items.cpp:1735 -msgid "greatly increases the armor" -msgstr "набагато підвищує у броні" +#: Source/panels/mainpanel.cpp:92 +msgid "spells" +msgstr "чари" -#: Source/items.cpp:1736 -msgid "class of armor and shields" -msgstr "клас броні і щитів" +#: Source/panels/mainpanel.cpp:102 Source/panels/mainpanel.cpp:128 +#: Source/panels/mainpanel.cpp:130 +msgid "voice" +msgstr "чат" -#: Source/items.cpp:1739 Source/items.cpp:1746 -msgid "sets fire trap" -msgstr "ставить пастку вогню" +#: Source/panels/mainpanel.cpp:123 Source/panels/mainpanel.cpp:125 +#: Source/panels/mainpanel.cpp:127 +msgid "mute" +msgstr "заблок" -#: Source/items.cpp:1743 -msgid "sets lightning trap" -msgstr "ставить пастку блискавки" +#: Source/panels/spell_book.cpp:116 +msgid "Unusable" +msgstr "Непридатні" -#: Source/items.cpp:1749 -msgid "sets petrification trap" -msgstr "ставить пастку окам'яніння" +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:119 +msgid "Dmg: 1/3 target hp" +msgstr "Шкд: 1/3 життя цілі" -#: Source/items.cpp:1752 -msgid "restore all life" -msgstr "відновлює все життя" +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:126 +#, c++-format +msgid "Heals: {:d} - {:d}" +msgstr "Зцілює: {:d} - {:d}" -#: Source/items.cpp:1755 -msgid "restore some life" -msgstr "відновлює трохи життя" +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:128 +#, c++-format +msgid "Damage: {:d} - {:d}" +msgstr "Шкода: {:d} - {:d}" -#: Source/items.cpp:1758 -msgid "restore some mana" -msgstr "відновлює трохи мани" +#: Source/panels/spell_book.cpp:183 Source/panels/spell_list.cpp:151 +msgid "Skill" +msgstr "Талант" -#: Source/items.cpp:1761 -msgid "restore all mana" -msgstr "відновлює всю ману" +#: Source/panels/spell_book.cpp:187 +#, c++-format +msgid "Staff ({:d} charge)" +msgid_plural "Staff ({:d} charges)" +msgstr[0] "Посох ({:d} заряд)" +msgstr[1] "Посох ({:d} заряда)" +msgstr[2] "Посох ({:d} зарядів)" -#: Source/items.cpp:1764 -msgid "increase strength" -msgstr "підвищує силу" +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:192 +#, c++-format +msgctxt "spellbook" +msgid "Level {:d}" +msgstr "Рівень: {:d}" -#: Source/items.cpp:1767 -msgid "increase magic" -msgstr "підвищує магію" +#. TRANSLATORS: UI constraints, keep short please. +#: Source/panels/spell_book.cpp:196 +#, c++-format +msgctxt "spellbook" +msgid "Mana: {:d}" +msgstr "Мана: {:d}" -#: Source/items.cpp:1770 -msgid "increase dexterity" -msgstr "підвищує спритність" +#: Source/panels/spell_list.cpp:158 +msgid "Spell" +msgstr "Чари" -#: Source/items.cpp:1773 -msgid "increase vitality" -msgstr "підвищує живучість" +#: Source/panels/spell_list.cpp:161 +msgid "Damages undead only" +msgstr "Б'є тільки нечисть" -#: Source/items.cpp:1776 -msgid "restore some life and mana" -msgstr "відновлює трохи життя і мани" +#: Source/panels/spell_list.cpp:172 +msgid "Scroll" +msgstr "Сувій" -#: Source/items.cpp:1779 Source/items.cpp:1782 -msgid "restore all life and mana" -msgstr "відновлює все життя і ману" +#: Source/panels/spell_list.cpp:183 Source/translation_dummy.cpp:459 +#: Source/translation_dummy.cpp:461 Source/translation_dummy.cpp:463 +#: Source/translation_dummy.cpp:465 Source/translation_dummy.cpp:467 +msgid "Staff" +msgstr "Посох" -#: Source/items.cpp:1783 -msgid "(works only in arenas)" -msgstr "(працює тільки на аренах)" +#: Source/panels/spell_list.cpp:193 +#, c++-format +msgid "Spell Hotkey {:s}" +msgstr "Гаряча клавіша Чар {:s}" -#: Source/items.cpp:1797 -msgid "Right-click to view" -msgstr "Правий клік щоб оглянути" +#: Source/pfile.cpp:745 +msgid "Unable to open archive" +msgstr "Неможливо відкрити архів" -#: Source/items.cpp:1800 -msgid "Right-click to use" -msgstr "Правий клік щоб використати" +#: Source/pfile.cpp:747 +msgid "Unable to load character" +msgstr "Неможливо завантажити героя" -#: Source/items.cpp:1802 -msgid "" -"Right-click to read, then\n" -"left-click to target" -msgstr "" -"Правий клік, щоб прочитати,\n" -"лівий клік, щоб вибрати ціль" +#: Source/plrmsg.cpp:85 Source/qol/chatlog.cpp:129 +#, c++-format +msgid "{:s} (lvl {:d}): " +msgstr "{:s} (рів {:d}): " -#: Source/items.cpp:1804 -msgid "Right-click to read" -msgstr "Правий клік щоб прочитати" +#: Source/qol/chatlog.cpp:169 +#, c++-format +msgid "Chat History (Messages: {:d})" +msgstr "Історія Чату (Повідомлень: {:d})" -#: Source/items.cpp:1811 -msgid "Activate to view" -msgstr "Активуйте щоб оглянути" +#: Source/qol/itemlabels.cpp:107 +#, c++-format +msgid "{:s} gold" +msgstr "{:s} золота" -#: Source/items.cpp:1815 Source/items.cpp:1853 -msgid "Open inventory to use" -msgstr "Відкрийте інвентар щоб використати" +#: Source/qol/stash.cpp:650 +msgid "How many gold pieces do you want to withdraw?" +msgstr "Скільки золотих монет ти хочеш забрати?" -#: Source/items.cpp:1817 -msgid "Activate to use" -msgstr "Активуйте щоб використати" +#: Source/qol/xpbar.cpp:125 +#, c++-format +msgid "Level {:d}" +msgstr "Рівень {:d}" -#: Source/items.cpp:1820 -msgid "" -"Select from spell book, then\n" -"cast spell to read" -msgstr "" -"Виберіть з книги чарувань, а потім\n" -"чаруйте, щоб прочитати" +#: Source/qol/xpbar.cpp:131 Source/qol/xpbar.cpp:139 +#, c++-format +msgid "Experience: {:s}" +msgstr "Досвід: {:s}" -#: Source/items.cpp:1822 -msgid "Activate to read" -msgstr "Активуйте щоб прочитати" +#: Source/qol/xpbar.cpp:132 +msgid "Maximum Level" +msgstr "Максимальний Рівень" -#: Source/items.cpp:1849 -msgid "{} to view" -msgstr "{} щоб оглянути" +#: Source/qol/xpbar.cpp:141 +#, c++-format +msgid "Next Level: {:s}" +msgstr "Наступний рівень: {:s}" -#: Source/items.cpp:1855 -msgid "{} to use" -msgstr "{} щоб використати" +#: Source/qol/xpbar.cpp:142 +#, c++-format +msgid "{:s} to Level {:d}" +msgstr "{:s} до {:d} Рівня" -#: Source/items.cpp:1858 -msgid "" -"Select from spell book,\n" -"then {} to read" -msgstr "" -"Виберіть з книги чар,\n" -"потім {}, щоб прочитати" +#. TRANSLATORS: Quest Name Block +#: Source/quests.cpp:52 +msgid "The Magic Rock" +msgstr "Магічний Камінь" -#: Source/items.cpp:1860 -msgid "{} to read" -msgstr "{} щоб прочитати" +#: Source/quests.cpp:53 Source/translation_dummy.cpp:264 +msgid "Black Mushroom" +msgstr "Чорний Гриб" -#: Source/items.cpp:1867 -msgctxt "player" -msgid "Level: {:d}" -msgstr "Рівень: {:d}" +#: Source/quests.cpp:54 +msgid "Gharbad The Weak" +msgstr "Слабак Габард" -#: Source/items.cpp:1871 -msgid "Doubles gold capacity" -msgstr "Подвоює ємність золота" +#: Source/quests.cpp:55 +msgid "Zhar the Mad" +msgstr "Схиблений Жар" -#: Source/items.cpp:1903 Source/stores.cpp:325 -msgid "Required:" -msgstr "Вимоги:" +#: Source/quests.cpp:56 +msgid "Lachdanan" +msgstr "Лахданан" -#: Source/items.cpp:1905 Source/stores.cpp:327 -msgid " {:d} Str" -msgstr " {:d} Сил" +#: Source/quests.cpp:58 +msgid "The Butcher" +msgstr "М'ясник" -#: Source/items.cpp:1907 Source/stores.cpp:329 -msgid " {:d} Mag" -msgstr " {:d} Маг" +#: Source/quests.cpp:59 +msgid "Ogden's Sign" +msgstr "Знак Огдена" -#: Source/items.cpp:1909 Source/stores.cpp:331 -msgid " {:d} Dex" -msgstr " {:d} Спр" +#: Source/quests.cpp:60 +msgid "Halls of the Blind" +msgstr "Зали Сліпих" -#. TRANSLATORS: {:s} will be a Character Name -#: Source/items.cpp:2284 -msgid "Ear of {:s}" -msgstr "Вухо {:s}" +#: Source/quests.cpp:61 +msgid "Valor" +msgstr "Доблесть" -#: Source/items.cpp:3709 -msgid "chance to hit: {:+d}%" -msgstr "шанс удару: {:+d}%" +#: Source/quests.cpp:62 Source/translation_dummy.cpp:263 +msgid "Anvil of Fury" +msgstr "Ковадло Люті" -#: Source/items.cpp:3712 -#, no-c-format -msgid "{:+d}% damage" -msgstr "{:+d}% шкоди" +#: Source/quests.cpp:63 +msgid "Warlord of Blood" +msgstr "Воєвода Крові" -#: Source/items.cpp:3715 Source/items.cpp:3899 -msgid "to hit: {:+d}%, {:+d}% damage" -msgstr "шанс: {:+d}%, {:+d}% шкоди" +#: Source/quests.cpp:64 +msgid "The Curse of King Leoric" +msgstr "Прокляття Короля Леоріка" -#: Source/items.cpp:3718 -#, no-c-format -msgid "{:+d}% armor" -msgstr "{:+d}% броні" +#. TRANSLATORS: Quest Map +#: Source/quests.cpp:66 Source/quests.cpp:102 +msgid "The Chamber of Bone" +msgstr "Палата Кості" -#: Source/items.cpp:3721 -msgid "armor class: {:d}" -msgstr "клас броні: {:d}" +#: Source/quests.cpp:67 +msgid "Archbishop Lazarus" +msgstr "Архієпископ Лазарь" -#: Source/items.cpp:3725 -msgid "Resist Fire: {:+d}%" -msgstr "Спротив Вогню: {:+d}%" +#: Source/quests.cpp:68 +msgid "Grave Matters" +msgstr "Могильні Справи" -#: Source/items.cpp:3727 -msgid "Resist Fire: {:+d}% MAX" -msgstr "Спротив Вогню: {:+d}% МАКС" +#: Source/quests.cpp:69 +msgid "Farmer's Orchard" +msgstr "Сад Фермера" -#: Source/items.cpp:3731 -msgid "Resist Lightning: {:+d}%" -msgstr "Спротив Блискавці: {:+d}%" +#: Source/quests.cpp:70 +msgid "Little Girl" +msgstr "Маленька Дівчинка" -#: Source/items.cpp:3733 -msgid "Resist Lightning: {:+d}% MAX" -msgstr "Спротив Блискавці: {:+d}% МАКС" +#: Source/quests.cpp:71 +msgid "Wandering Trader" +msgstr "Мандруючий Торговець" -#: Source/items.cpp:3737 -msgid "Resist Magic: {:+d}%" -msgstr "Спротив Магії: {:+d}%" +#: Source/quests.cpp:72 +msgid "The Defiler" +msgstr "Паплюжник" -#: Source/items.cpp:3739 -msgid "Resist Magic: {:+d}% MAX" -msgstr "Спротив Магії: {:+d}% МАКС" +#: Source/quests.cpp:73 +msgid "Na-Krul" +msgstr "На-Крул" -#: Source/items.cpp:3742 -msgid "Resist All: {:+d}%" -msgstr "Спротив Всьому: {:+d}%" +#. TRANSLATORS: Quest Name Block end +#: Source/quests.cpp:75 +msgid "The Jersey's Jersey" +msgstr "Джерсі Джерсі" -#: Source/items.cpp:3744 -msgid "Resist All: {:+d}% MAX" -msgstr "Спротив Всьому: {:+d}% МАКС" +#. TRANSLATORS: Quest Map +#: Source/quests.cpp:101 +msgid "King Leoric's Tomb" +msgstr "Гробниця Короля Леоріка" -#: Source/items.cpp:3747 -msgid "spells are increased {:d} level" -msgid_plural "spells are increased {:d} levels" -msgstr[0] "чари покращені на {:d} рівень" -msgstr[1] "чари покращені на {:d} рівня" -msgstr[2] "чари покращені на {:d} рівнів" +#. TRANSLATORS: Quest Map +#: Source/quests.cpp:104 +msgid "A Dark Passage" +msgstr "Темний Прохід" -#: Source/items.cpp:3749 -msgid "spells are decreased {:d} level" -msgid_plural "spells are decreased {:d} levels" -msgstr[0] "чари погіршені на {:d} рівень" -msgstr[1] "чари погіршені на {:d} рівня" -msgstr[2] "чари погіршені на {:d} рівнів" +#. TRANSLATORS: Quest Map +#: Source/quests.cpp:105 +msgid "Unholy Altar" +msgstr "Нечестивий Вівтар" -#: Source/items.cpp:3751 -msgid "spell levels unchanged (?)" -msgstr "рівень чар не змінюється (?)" +#. TRANSLATORS: Used for Quest Portals. {:s} is a Map Name +#: Source/quests.cpp:406 +#, c++-format +msgid "To {:s}" +msgstr "До {:s}" -#: Source/items.cpp:3753 -msgid "Extra charges" -msgstr "Додаткові заряди" +#: Source/spelldat.cpp:29 +msgctxt "spell" +msgid "Firebolt" +msgstr "Стріла Вогню" -#: Source/items.cpp:3755 -msgid "{:d} {:s} charge" -msgid_plural "{:d} {:s} charges" -msgstr[0] "{:d} {:s} заряд" -msgstr[1] "{:d} {:s} заряди" -msgstr[2] "{:d} {:s} зарядів" +#: Source/spelldat.cpp:30 +msgctxt "spell" +msgid "Healing" +msgstr "Зцілення" -#: Source/items.cpp:3758 -msgid "Fire hit damage: {:d}" -msgstr "Шкода вогнем: {:d}" +#: Source/spelldat.cpp:31 +msgctxt "spell" +msgid "Lightning" +msgstr "Блискавка" -#: Source/items.cpp:3760 -msgid "Fire hit damage: {:d}-{:d}" -msgstr "Шкода вогнем: {:d}-{:d}" +#: Source/spelldat.cpp:32 +msgctxt "spell" +msgid "Flash" +msgstr "Спалах" -#: Source/items.cpp:3763 -msgid "Lightning hit damage: {:d}" -msgstr "Шкода блискавкою: {:d}" +#: Source/spelldat.cpp:33 +msgctxt "spell" +msgid "Identify" +msgstr "Розпізнавання" -#: Source/items.cpp:3765 -msgid "Lightning hit damage: {:d}-{:d}" -msgstr "Шкода блискавкою: {:d}-{:d}" +#: Source/spelldat.cpp:34 +msgctxt "spell" +msgid "Fire Wall" +msgstr "Стіна Вогню" -#: Source/items.cpp:3768 -msgid "{:+d} to strength" -msgstr "{:+d} до сили" +#: Source/spelldat.cpp:35 +msgctxt "spell" +msgid "Town Portal" +msgstr "Портал в Місто" -#: Source/items.cpp:3771 -msgid "{:+d} to magic" -msgstr "{:+d} до магії" +#: Source/spelldat.cpp:36 +msgctxt "spell" +msgid "Stone Curse" +msgstr "Кам'яне Прокляття" -#: Source/items.cpp:3774 -msgid "{:+d} to dexterity" -msgstr "{:+d} до спритності" +#: Source/spelldat.cpp:37 +msgctxt "spell" +msgid "Infravision" +msgstr "Інфрабачення" -#: Source/items.cpp:3777 -msgid "{:+d} to vitality" -msgstr "{:+d} до живучості" +#: Source/spelldat.cpp:38 +msgctxt "spell" +msgid "Phasing" +msgstr "Фазування" -#: Source/items.cpp:3780 -msgid "{:+d} to all attributes" -msgstr "{:+d} до всіх атрибутів" +#: Source/spelldat.cpp:39 +msgctxt "spell" +msgid "Mana Shield" +msgstr "Щит Мани" -#: Source/items.cpp:3783 -msgid "{:+d} damage from enemies" -msgstr "{:+d} шкоди від ворогів" +#: Source/spelldat.cpp:40 +msgctxt "spell" +msgid "Fireball" +msgstr "Куля Вогню" -#: Source/items.cpp:3786 -msgid "Hit Points: {:+d}" -msgstr "Здоров'я {:+d}" +#: Source/spelldat.cpp:41 +msgctxt "spell" +msgid "Guardian" +msgstr "Охоронець" -#: Source/items.cpp:3789 -msgid "Mana: {:+d}" -msgstr "Мана: {:+d}" +#: Source/spelldat.cpp:42 +msgctxt "spell" +msgid "Chain Lightning" +msgstr "Ланцюгова Блискавка" -#: Source/items.cpp:3791 -msgid "high durability" -msgstr "висока міцність" +#: Source/spelldat.cpp:43 +msgctxt "spell" +msgid "Flame Wave" +msgstr "Хвиля Вогню" -#: Source/items.cpp:3793 -msgid "decreased durability" -msgstr "зменшена міцність" +#: Source/spelldat.cpp:44 +msgctxt "spell" +msgid "Doom Serpents" +msgstr "Змії Долі" -#: Source/items.cpp:3795 -msgid "indestructible" -msgstr "неруйновний" +#: Source/spelldat.cpp:45 +msgctxt "spell" +msgid "Blood Ritual" +msgstr "Кривавий Ритуал" -#: Source/items.cpp:3797 -#, no-c-format -msgid "+{:d}% light radius" -msgstr "радіус світла +{:d}%" +#: Source/spelldat.cpp:46 +msgctxt "spell" +msgid "Nova" +msgstr "Нова" -#: Source/items.cpp:3799 -#, no-c-format -msgid "-{:d}% light radius" -msgstr "радіус світла -{:d}%" +#: Source/spelldat.cpp:47 +msgctxt "spell" +msgid "Invisibility" +msgstr "Невидимість" -#: Source/items.cpp:3801 -msgid "multiple arrows per shot" -msgstr "стріляє декілька стріл відразу" +#: Source/spelldat.cpp:48 +msgctxt "spell" +msgid "Inferno" +msgstr "Пекло" -#: Source/items.cpp:3804 -msgid "fire arrows damage: {:d}" -msgstr "шкода вогняних стріл: {:d}" +#: Source/spelldat.cpp:49 +msgctxt "spell" +msgid "Golem" +msgstr "Голем" -#: Source/items.cpp:3806 -msgid "fire arrows damage: {:d}-{:d}" -msgstr "шкода вогняних стріл: {:d}-{:d}" +#: Source/spelldat.cpp:50 +msgctxt "spell" +msgid "Rage" +msgstr "Лють" -#: Source/items.cpp:3809 -msgid "lightning arrows damage {:d}" -msgstr "шкода стріл блискавки: {:d}" - -#: Source/items.cpp:3811 -msgid "lightning arrows damage {:d}-{:d}" -msgstr "шкода стріл блискавки: {:d}-{:d}" +#: Source/spelldat.cpp:51 +msgctxt "spell" +msgid "Teleport" +msgstr "Телепорт" -#: Source/items.cpp:3814 -msgid "fireball damage: {:d}" -msgstr "шкода кулі вогню: {:d}" +#: Source/spelldat.cpp:52 +msgctxt "spell" +msgid "Apocalypse" +msgstr "Апокаліпсис" -#: Source/items.cpp:3816 -msgid "fireball damage: {:d}-{:d}" -msgstr "шкода кулі вогню: {:d}-{:d}" +#: Source/spelldat.cpp:53 +msgctxt "spell" +msgid "Etherealize" +msgstr "Етеризація" -#: Source/items.cpp:3818 -msgid "attacker takes 1-3 damage" -msgstr "нападник отримує 1-3 шкоди" +#: Source/spelldat.cpp:54 +msgctxt "spell" +msgid "Item Repair" +msgstr "Ремонт Предмету" -#: Source/items.cpp:3820 -msgid "user loses all mana" -msgstr "поглинає всю ману" +#: Source/spelldat.cpp:55 +msgctxt "spell" +msgid "Staff Recharge" +msgstr "Перезарядка Посоху" -#: Source/items.cpp:3822 -msgid "absorbs half of trap damage" -msgstr "поглинає 50% шкоди пасток" +#: Source/spelldat.cpp:56 +msgctxt "spell" +msgid "Trap Disarm" +msgstr "Обеззброєння Пастки" -#: Source/items.cpp:3824 -msgid "knocks target back" -msgstr "відбиває ворога назад" +#: Source/spelldat.cpp:57 +msgctxt "spell" +msgid "Elemental" +msgstr "Елементаль" -#: Source/items.cpp:3826 -#, no-c-format -msgid "+200% damage vs. demons" -msgstr "+200% шкоди проти демонів" +#: Source/spelldat.cpp:58 +msgctxt "spell" +msgid "Charged Bolt" +msgstr "Заряджена Стріла" -#: Source/items.cpp:3828 -msgid "All Resistance equals 0" -msgstr "Всі Спротиви рівні 0" +#: Source/spelldat.cpp:59 +msgctxt "spell" +msgid "Holy Bolt" +msgstr "Свята Стріла" -#: Source/items.cpp:3831 -#, no-c-format -msgid "hit steals 3% mana" -msgstr "удар краде 3% мани" +#: Source/spelldat.cpp:60 +msgctxt "spell" +msgid "Resurrect" +msgstr "Воскрешення" -#: Source/items.cpp:3833 -#, no-c-format -msgid "hit steals 5% mana" -msgstr "удар краде 5% мани" +#: Source/spelldat.cpp:61 +msgctxt "spell" +msgid "Telekinesis" +msgstr "Телекінез" -#: Source/items.cpp:3837 -#, no-c-format -msgid "hit steals 3% life" -msgstr "удар краде 3% життя" +#: Source/spelldat.cpp:62 +msgctxt "spell" +msgid "Heal Other" +msgstr "Зцілити Іншого" -#: Source/items.cpp:3839 -#, no-c-format -msgid "hit steals 5% life" -msgstr "удар краде 5% життя" +#: Source/spelldat.cpp:63 +msgctxt "spell" +msgid "Blood Star" +msgstr "Кривава Зоря" -#: Source/items.cpp:3842 -msgid "penetrates target's armor" -msgstr "пробиває броню" +#: Source/spelldat.cpp:64 +msgctxt "spell" +msgid "Bone Spirit" +msgstr "Кістлявий Дух" -#: Source/items.cpp:3845 -msgid "quick attack" -msgstr "моторна атака" +#: Source/spelldat.cpp:65 +msgctxt "spell" +msgid "Mana" +msgstr "Мана" -#: Source/items.cpp:3847 -msgid "fast attack" -msgstr "швидка атака" +#: Source/spelldat.cpp:66 +msgctxt "spell" +msgid "the Magi" +msgstr "Волхви" -#: Source/items.cpp:3849 -msgid "faster attack" -msgstr "швидша атака" +#: Source/spelldat.cpp:67 +msgctxt "spell" +msgid "the Jester" +msgstr "Блазень" -#: Source/items.cpp:3851 -msgid "fastest attack" -msgstr "найшвидша атака" +#: Source/spelldat.cpp:68 +msgctxt "spell" +msgid "Lightning Wall" +msgstr "Стіна Блискавки" -# Що за NW? -#: Source/items.cpp:3852 Source/items.cpp:3860 Source/items.cpp:3909 -msgid "Another ability (NW)" -msgstr "Інше уміння (NW)" +#: Source/spelldat.cpp:69 +msgctxt "spell" +msgid "Immolation" +msgstr "Горіння" -#: Source/items.cpp:3855 -msgid "fast hit recovery" -msgstr "швидке відновлення після удару" +#: Source/spelldat.cpp:70 +msgctxt "spell" +msgid "Warp" +msgstr "Викривлення" -#: Source/items.cpp:3857 -msgid "faster hit recovery" -msgstr "швидше відновлення після удару" +#: Source/spelldat.cpp:71 +msgctxt "spell" +msgid "Reflect" +msgstr "Відбиття" -#: Source/items.cpp:3859 -msgid "fastest hit recovery" -msgstr "найшвидше відновлення після удару" +#: Source/spelldat.cpp:72 +msgctxt "spell" +msgid "Berserk" +msgstr "Берсерк" -#: Source/items.cpp:3862 -msgid "fast block" -msgstr "швидкий блок" +#: Source/spelldat.cpp:73 +msgctxt "spell" +msgid "Ring of Fire" +msgstr "Кільце Вогню" -#: Source/items.cpp:3864 -msgid "adds {:d} point to damage" -msgid_plural "adds {:d} points to damage" -msgstr[0] "додає {:d} очко до шкоди" -msgstr[1] "додає {:d} очка до шкоди" -msgstr[2] "додає {:d} очків до шкоди" +#: Source/spelldat.cpp:74 +msgctxt "spell" +msgid "Search" +msgstr "Пошук" -#: Source/items.cpp:3866 -msgid "fires random speed arrows" -msgstr "стріляє кілька швидких стріл" +#: Source/spelldat.cpp:75 +msgctxt "spell" +msgid "Rune of Fire" +msgstr "Руна Вогню" -#: Source/items.cpp:3868 -msgid "unusual item damage" -msgstr "у предмету незвичайна шкода" +#: Source/spelldat.cpp:76 +msgctxt "spell" +msgid "Rune of Light" +msgstr "Руна Світла" -#: Source/items.cpp:3870 -msgid "altered durability" -msgstr "змінена міцність" +#: Source/spelldat.cpp:77 +msgctxt "spell" +msgid "Rune of Nova" +msgstr "Руна Нови" -#: Source/items.cpp:3872 -msgid "one handed sword" -msgstr "одноручний меч" +#: Source/spelldat.cpp:78 +msgctxt "spell" +msgid "Rune of Immolation" +msgstr "Руна Горіння" -#: Source/items.cpp:3874 -msgid "constantly lose hit points" -msgstr "постійно втрачаєш очки життя" +#: Source/spelldat.cpp:79 +msgctxt "spell" +msgid "Rune of Stone" +msgstr "Руна Каменю" -#: Source/items.cpp:3876 -msgid "life stealing" -msgstr "краде життя" +#: Source/stores.cpp:129 +msgid "Griswold" +msgstr "Грізволд" -#: Source/items.cpp:3878 -msgid "no strength requirement" -msgstr "немає вимог по силі" +#: Source/stores.cpp:130 +msgid "Pepin" +msgstr "Пепін" -#: Source/items.cpp:3883 -msgid "lightning damage: {:d}" -msgstr "шкода блискавкою: {:d}" +#: Source/stores.cpp:132 +msgid "Ogden" +msgstr "Одген" -#: Source/items.cpp:3885 -msgid "lightning damage: {:d}-{:d}" -msgstr "шкода блискавкою: {:d}-{:d}" +#: Source/stores.cpp:133 +msgid "Cain" +msgstr "Каїн" -#: Source/items.cpp:3887 -msgid "charged bolts on hits" -msgstr "при ударі стріляє зарядженою стрілою" +#: Source/stores.cpp:134 +msgid "Farnham" +msgstr "Фарнхем" -#: Source/items.cpp:3889 -msgid "occasional triple damage" -msgstr "випадкова потрійна шкода" +#: Source/stores.cpp:135 +msgid "Adria" +msgstr "Адрія" -#: Source/items.cpp:3891 -#, no-c-format -msgid "decaying {:+d}% damage" -msgstr "шкода згасає {:+d}%" +#: Source/stores.cpp:136 Source/stores.cpp:1261 +msgid "Gillian" +msgstr "Гілліан" -#: Source/items.cpp:3893 -msgid "2x dmg to monst, 1x to you" -msgstr "2х шкд монстрам, 1х шкд тобі" +#: Source/stores.cpp:137 +msgid "Wirt" +msgstr "Вірт" -#: Source/items.cpp:3895 -#, no-c-format -msgid "Random 0 - 600% damage" -msgstr "Випадкова шкода 0 - 600%" +#: Source/stores.cpp:263 Source/stores.cpp:270 +msgid "Back" +msgstr "Назад" -#: Source/items.cpp:3897 -#, no-c-format -msgid "low dur, {:+d}% damage" -msgstr "низька міц, {:+d}% шкоди" +#: Source/stores.cpp:292 Source/stores.cpp:298 +msgid ", " +msgstr ", " -# ШУ- шанс удару -#: Source/items.cpp:3901 -msgid "extra AC vs demons" -msgstr "додат. ШУ проти демонів" +#: Source/stores.cpp:309 +#, c++-format +msgid "Damage: {:d}-{:d} " +msgstr "Шкода: {:d}-{:d} " -# ШУ - шанс удару -#: Source/items.cpp:3903 -msgid "extra AC vs undead" -msgstr "додат. ШУ проти нечисті" +#: Source/stores.cpp:311 +#, c++-format +msgid "Armor: {:d} " +msgstr "Броня: {:d} " -#: Source/items.cpp:3905 -msgid "50% Mana moved to Health" -msgstr "50% Мани перенесене в Здоров'я" - -#: Source/items.cpp:3907 -msgid "40% Health moved to Mana" -msgstr "40% Здоров'я перенесене в Ману" +#: Source/stores.cpp:313 +#, c++-format +msgid "Dur: {:d}/{:d}, " +msgstr "Міц: {:d}/{:d}, " -#: Source/items.cpp:3947 Source/items.cpp:3988 -msgid "damage: {:d} Indestructible" -msgstr "шкода: {:d} Неруйновний" +#: Source/stores.cpp:315 +msgid "Indestructible, " +msgstr "Неруйновний, " -#. TRANSLATORS: Dur: is durability -#: Source/items.cpp:3949 Source/items.cpp:3990 -msgid "damage: {:d} Dur: {:d}/{:d}" -msgstr "шкода: {:d} Міц: {:d}/{:d}" +#: Source/stores.cpp:323 +msgid "No required attributes" +msgstr "Немає вимог" -#: Source/items.cpp:3952 Source/items.cpp:3993 -msgid "damage: {:d}-{:d} Indestructible" -msgstr "шкода: {:d}-{:d} Неруйновний" +#: Source/stores.cpp:381 Source/stores.cpp:1029 Source/stores.cpp:1248 +msgid "Welcome to the" +msgstr "Ласкаво просимо" -#. TRANSLATORS: Dur: is durability -#: Source/items.cpp:3954 Source/items.cpp:3995 -msgid "damage: {:d}-{:d} Dur: {:d}/{:d}" -msgstr "шкода: {:d}-{:d} Міц: {:d}/{:d}" +#: Source/stores.cpp:382 +msgid "Blacksmith's shop" +msgstr "Ковальська Майстерня" -#: Source/items.cpp:3959 Source/items.cpp:4005 -msgid "armor: {:d} Indestructible" -msgstr "броня: {:d} Неруйновний" +#: Source/stores.cpp:383 Source/stores.cpp:680 Source/stores.cpp:1031 +#: Source/stores.cpp:1074 Source/stores.cpp:1250 Source/stores.cpp:1262 +#: Source/stores.cpp:1275 +msgid "Would you like to:" +msgstr "Ви хочете:" -#. TRANSLATORS: Dur: is durability -#: Source/items.cpp:3961 Source/items.cpp:4007 -msgid "armor: {:d} Dur: {:d}/{:d}" -msgstr "броня: {:d} Міц: {:d}/{:d}" +#: Source/stores.cpp:384 +msgid "Talk to Griswold" +msgstr "Поговорити з Грізвольдом" -#: Source/items.cpp:3964 Source/items.cpp:3998 Source/items.cpp:4011 -#: Source/stores.cpp:299 -msgid "Charges: {:d}/{:d}" -msgstr "Заряди: {:d}/{:d}" +#: Source/stores.cpp:385 +msgid "Buy basic items" +msgstr "Купити прості предмети" -#: Source/items.cpp:3973 -msgid "unique item" -msgstr "унікальний предмет" +#: Source/stores.cpp:386 +msgid "Buy premium items" +msgstr "Купити преміальні предмети" -#: Source/items.cpp:4001 Source/items.cpp:4009 Source/items.cpp:4015 -msgid "Not Identified" -msgstr "Не Розпізнано" +#: Source/stores.cpp:387 Source/stores.cpp:683 +msgid "Sell items" +msgstr "Продати предмети" -#: Source/levels/setmaps.cpp:27 -msgid "Skeleton King's Lair" -msgstr "Лігво Короля-Скелета" +#: Source/stores.cpp:388 +msgid "Repair items" +msgstr "Відремонтувати предмети" -#: Source/levels/setmaps.cpp:28 -msgid "Chamber of Bone" -msgstr "Палата Кості" +#: Source/stores.cpp:389 +msgid "Leave the shop" +msgstr "Покинути магазин" -#. TRANSLATORS: Quest Map -#: Source/levels/setmaps.cpp:29 Source/quests.cpp:99 -msgid "Maze" -msgstr "Лабіринт" +#: Source/stores.cpp:417 Source/stores.cpp:719 Source/stores.cpp:1051 +msgid "I have these items for sale:" +msgstr "В мене продаються такі предмети:" -#: Source/levels/setmaps.cpp:30 Source/quests.cpp:61 -msgid "Poisoned Water Supply" -msgstr "Отруєне Джерело" +#: Source/stores.cpp:466 +msgid "I have these premium items for sale:" +msgstr "Є такі преміальні предмети:" -#: Source/levels/setmaps.cpp:31 -msgid "Archbishop Lazarus' Lair" -msgstr "Лігво Архієпископа Лазаря" +#: Source/stores.cpp:562 Source/stores.cpp:812 +msgid "You have nothing I want." +msgstr "Вам немає що продати." -#: Source/levels/setmaps.cpp:32 -msgid "Church Arena" -msgstr "Арена \"Церква\"" +#: Source/stores.cpp:573 Source/stores.cpp:824 +msgid "Which item is for sale?" +msgstr "Який предмет продати?" -#: Source/levels/setmaps.cpp:33 -msgid "Hell Arena" -msgstr "Арена \"Пекло\"" +#: Source/stores.cpp:641 +msgid "You have nothing to repair." +msgstr "Немає чого ремонтувати." -#: Source/levels/setmaps.cpp:34 -msgid "Circle of Life Arena" -msgstr "Арена \"Коло життя\"" +#: Source/stores.cpp:652 +msgid "Repair which item?" +msgstr "Який предмет ремонтувати?" -#: Source/levels/trigs.cpp:350 -msgid "Down to dungeon" -msgstr "Вниз в підземелля" +#: Source/stores.cpp:679 +msgid "Witch's shack" +msgstr "Хатина відьми" -#: Source/levels/trigs.cpp:359 -msgid "Down to catacombs" -msgstr "Вниз в катакомби" +#: Source/stores.cpp:681 +msgid "Talk to Adria" +msgstr "Поговорити з Адрією" -#: Source/levels/trigs.cpp:369 -msgid "Down to caves" -msgstr "Вниз в печери" +#: Source/stores.cpp:682 Source/stores.cpp:1033 +msgid "Buy items" +msgstr "Купити предмети" -#: Source/levels/trigs.cpp:379 -msgid "Down to hell" -msgstr "Вниз в пекло" +#: Source/stores.cpp:684 +msgid "Recharge staves" +msgstr "Перезарядити посохи" -#: Source/levels/trigs.cpp:389 -msgid "Down to Hive" -msgstr "Вниз в Гніздо" +#: Source/stores.cpp:685 +msgid "Leave the shack" +msgstr "Покинути хатину" -#: Source/levels/trigs.cpp:399 -msgid "Down to Crypt" -msgstr "Вниз до Склепу" +#: Source/stores.cpp:886 +msgid "You have nothing to recharge." +msgstr "Немає чого заряджати." -#: Source/levels/trigs.cpp:414 Source/levels/trigs.cpp:449 -#: Source/levels/trigs.cpp:495 Source/levels/trigs.cpp:547 -msgid "Up to level {:d}" -msgstr "Вверх на {:d} рівень" +#: Source/stores.cpp:897 +msgid "Recharge which item?" +msgstr "Який предмет зарядити?" -#: Source/levels/trigs.cpp:416 Source/levels/trigs.cpp:478 -#: Source/levels/trigs.cpp:530 Source/levels/trigs.cpp:577 -#: Source/levels/trigs.cpp:639 Source/levels/trigs.cpp:688 -#: Source/levels/trigs.cpp:795 -msgid "Up to town" -msgstr "Вверх у Місто" +#: Source/stores.cpp:910 +msgid "You do not have enough gold" +msgstr "У Вас недостатньо золота" -#: Source/levels/trigs.cpp:427 Source/levels/trigs.cpp:460 -#: Source/levels/trigs.cpp:512 Source/levels/trigs.cpp:559 -#: Source/levels/trigs.cpp:621 -msgid "Down to level {:d}" -msgstr "Вниз на {:d} рівень" +#: Source/stores.cpp:918 +msgid "You do not have enough room in inventory" +msgstr "У Вас недостатньо місця в інвентарі" -#: Source/levels/trigs.cpp:590 -msgid "Down to Diablo" -msgstr "Вниз до Діабло" +#: Source/stores.cpp:936 +msgid "Do we have a deal?" +msgstr "Домовились?" -#: Source/levels/trigs.cpp:608 -msgid "Up to Nest level {:d}" -msgstr "Вверх на {:d} рівень Гнізда" +#: Source/stores.cpp:939 +msgid "Are you sure you want to identify this item?" +msgstr "Ви впевнені, що хочете розпізнати цей предмет?" -#: Source/levels/trigs.cpp:656 -msgid "Up to Crypt level {:d}" -msgstr "Вверх на {:d} рівень Склепу" +#: Source/stores.cpp:945 +msgid "Are you sure you want to buy this item?" +msgstr "Ви впевнені, що хочете купити цей предмет?" -#: Source/levels/trigs.cpp:666 Source/quests.cpp:70 -msgid "Cornerstone of the World" -msgstr "Наріжний Камінь Світу" +#: Source/stores.cpp:948 +msgid "Are you sure you want to recharge this item?" +msgstr "Ви впевнені, що хочете зарядити цей предмет?" -#: Source/levels/trigs.cpp:671 -msgid "Down to Crypt level {:d}" -msgstr "Вниз на {:d} рівень Склепу" +#: Source/stores.cpp:952 +msgid "Are you sure you want to sell this item?" +msgstr "Ви впевнені, що хочете продати цей предмет?" -#: Source/levels/trigs.cpp:719 Source/levels/trigs.cpp:733 -#: Source/levels/trigs.cpp:747 -msgid "Back to Level {:d}" -msgstr "Назад на {:d} рівень" +#: Source/stores.cpp:955 +msgid "Are you sure you want to repair this item?" +msgstr "Ви впевнені, що хочете відремонтувати цей предмет?" -#: Source/loadsave.cpp:2094 Source/loadsave.cpp:2626 -msgid "Unable to open save file archive" -msgstr "Неможливо відкрити архів збереження" +#: Source/stores.cpp:969 Source/towners.cpp:152 +msgid "Wirt the Peg-legged boy" +msgstr "Одноногий хлопчик Вірт" -#: Source/loadsave.cpp:2097 -msgid "Invalid save file" -msgstr "Файл збереження зламаний" +#: Source/stores.cpp:972 Source/stores.cpp:979 +msgid "Talk to Wirt" +msgstr "Поговорити з Віртом" -#: Source/loadsave.cpp:2128 -msgid "Player is on a Hellfire only level" -msgstr "Гравець на рівні, доступному тільки в Hellfire" +#: Source/stores.cpp:973 +msgid "I have something for sale," +msgstr "В мене є щось на продаж," -#: Source/loadsave.cpp:2384 -msgid "Invalid game state" -msgstr "Невірний стан гри" +#: Source/stores.cpp:974 +msgid "but it will cost 50 gold" +msgstr "але воно коштує 50 золота" -#: Source/menu.cpp:156 -msgid "Unable to display mainmenu" -msgstr "Неможливо показати головне меню" +#: Source/stores.cpp:975 +msgid "just to take a look. " +msgstr "подивись. " -#. TRANSLATORS: Monster Block start -#. MT_NZOMBIE -#: Source/monstdat.cpp:33 -msgctxt "monster" -msgid "Zombie" -msgstr "Зомбі" +#: Source/stores.cpp:976 +msgid "What have you got?" +msgstr "Що в тебе є?" -#: Source/monstdat.cpp:34 -msgctxt "monster" -msgid "Ghoul" -msgstr "Гуль" +#: Source/stores.cpp:977 Source/stores.cpp:980 Source/stores.cpp:1077 +#: Source/stores.cpp:1265 +msgid "Say goodbye" +msgstr "Попрощатися" -#: Source/monstdat.cpp:35 -msgctxt "monster" -msgid "Rotting Carcass" -msgstr "Гниюча Туша" +#: Source/stores.cpp:990 +msgid "I have this item for sale:" +msgstr "В мене продаються такі предмети:" -#: Source/monstdat.cpp:36 -msgctxt "monster" -msgid "Black Death" -msgstr "Чорна Смерть" +#: Source/stores.cpp:1007 +msgid "Leave" +msgstr "Покинути" -#: Source/monstdat.cpp:37 Source/monstdat.cpp:45 -msgctxt "monster" -msgid "Fallen One" -msgstr "Впалий" +#: Source/stores.cpp:1030 +msgid "Healer's home" +msgstr "Дім цілителя" -#: Source/monstdat.cpp:38 Source/monstdat.cpp:46 -msgctxt "monster" -msgid "Carver" -msgstr "Різьбяр" +#: Source/stores.cpp:1032 +msgid "Talk to Pepin" +msgstr "Поговорити з Пепіном" -#: Source/monstdat.cpp:39 Source/monstdat.cpp:47 -msgctxt "monster" -msgid "Devil Kin" -msgstr "Бісовий Рід" +#: Source/stores.cpp:1034 +msgid "Leave Healer's home" +msgstr "Покинути дім цілителя" -#: Source/monstdat.cpp:40 Source/monstdat.cpp:48 -msgctxt "monster" -msgid "Dark One" -msgstr "Темний" +#: Source/stores.cpp:1073 +msgid "The Town Elder" +msgstr "Старець Міста" -#: Source/monstdat.cpp:41 Source/monstdat.cpp:53 -msgctxt "monster" -msgid "Skeleton" -msgstr "Скелет" +#: Source/stores.cpp:1075 +msgid "Talk to Cain" +msgstr "Поговорити з Каїном" -#: Source/monstdat.cpp:42 -msgctxt "monster" -msgid "Corpse Axe" -msgstr "Труп з Сокирою" +#: Source/stores.cpp:1076 +msgid "Identify an item" +msgstr "Розпізнати предмет" -#: Source/monstdat.cpp:43 Source/monstdat.cpp:55 -msgctxt "monster" -msgid "Burning Dead" -msgstr "Палаючий Скелет" +#: Source/stores.cpp:1169 +msgid "You have nothing to identify." +msgstr "Немає чого розпізнавати." -#: Source/monstdat.cpp:44 Source/monstdat.cpp:56 -msgctxt "monster" -msgid "Horror" -msgstr "Кошмар" +#: Source/stores.cpp:1180 +msgid "Identify which item?" +msgstr "Який предмет розпізнати?" -#: Source/monstdat.cpp:49 -msgctxt "monster" -msgid "Scavenger" -msgstr "Сміттяр" +#: Source/stores.cpp:1195 +msgid "This item is:" +msgstr "Предмет:" -#: Source/monstdat.cpp:50 -msgctxt "monster" -msgid "Plague Eater" -msgstr "Пожирач Чуми" +#: Source/stores.cpp:1198 +msgid "Done" +msgstr "Готово" -#: Source/monstdat.cpp:51 -msgctxt "monster" -msgid "Shadow Beast" -msgstr "Звір Тіні" +#: Source/stores.cpp:1207 +#, c++-format +msgid "Talk to {:s}" +msgstr "Поговорити з {:s}" -#: Source/monstdat.cpp:52 -msgctxt "monster" -msgid "Bone Gasher" -msgstr "Костогриз" +#: Source/stores.cpp:1210 +#, c++-format +msgid "Talking to {:s}" +msgstr "Говоримо з {:s}" -#: Source/monstdat.cpp:54 -msgctxt "monster" -msgid "Corpse Bow" -msgstr "Труп з Луком" +#: Source/stores.cpp:1211 +msgid "is not available" +msgstr "недоступне" -#: Source/monstdat.cpp:57 -msgctxt "monster" -msgid "Skeleton Captain" -msgstr "Капітан Скелетів" - -#: Source/monstdat.cpp:58 -msgctxt "monster" -msgid "Corpse Captain" -msgstr "Капітан Трупів" - -#: Source/monstdat.cpp:59 -msgctxt "monster" -msgid "Burning Dead Captain" -msgstr "Капітан Палаючих Скелетів" - -#: Source/monstdat.cpp:60 -msgctxt "monster" -msgid "Horror Captain" -msgstr "Капітан Кошмарів" +#: Source/stores.cpp:1212 +msgid "in the shareware" +msgstr "в демо-версії" -#: Source/monstdat.cpp:61 -msgctxt "monster" -msgid "Invisible Lord" -msgstr "Невидимий Лорд" +#: Source/stores.cpp:1213 +msgid "version" +msgstr "версії" -#: Source/monstdat.cpp:62 -msgctxt "monster" -msgid "Hidden" -msgstr "Прихований" +#: Source/stores.cpp:1240 +msgid "Gossip" +msgstr "Плітки" -#: Source/monstdat.cpp:63 -msgctxt "monster" -msgid "Stalker" -msgstr "Сталкер" +#: Source/stores.cpp:1249 +msgid "Rising Sun" +msgstr "Світанкове Сонце" -#: Source/monstdat.cpp:64 -msgctxt "monster" -msgid "Unseen" -msgstr "Невидимий" +#: Source/stores.cpp:1251 +msgid "Talk to Ogden" +msgstr "Поговорити з Огденом" -#: Source/monstdat.cpp:65 -msgctxt "monster" -msgid "Illusion Weaver" -msgstr "Ткач Іллюзій" +#: Source/stores.cpp:1252 +msgid "Leave the tavern" +msgstr "Покинути таверну" -#: Source/monstdat.cpp:66 -msgctxt "monster" -msgid "Satyr Lord" -msgstr "Лорд Сатирів" +#: Source/stores.cpp:1263 +msgid "Talk to Gillian" +msgstr "Поговорити з Гілліан" -#: Source/monstdat.cpp:67 Source/monstdat.cpp:75 -msgctxt "monster" -msgid "Flesh Clan" -msgstr "Клан Плоті" +#: Source/stores.cpp:1264 +msgid "Access Storage" +msgstr "Зайти до схованки" -#: Source/monstdat.cpp:68 Source/monstdat.cpp:76 -msgctxt "monster" -msgid "Stone Clan" -msgstr "Клан Каменю" +#: Source/stores.cpp:1274 Source/towners.cpp:207 +msgid "Farnham the Drunk" +msgstr "П'яниця Фарнхем" -#: Source/monstdat.cpp:69 Source/monstdat.cpp:77 -msgctxt "monster" -msgid "Fire Clan" -msgstr "Клан Вогню" +#: Source/stores.cpp:1276 +msgid "Talk to Farnham" +msgstr "Поговорити з Фарнхемом" -#: Source/monstdat.cpp:70 Source/monstdat.cpp:78 -msgctxt "monster" -msgid "Night Clan" -msgstr "Клан Ночі" +#: Source/stores.cpp:1277 +msgid "Say Goodbye" +msgstr "Попрощатися" -#: Source/monstdat.cpp:71 -msgctxt "monster" -msgid "Fiend" -msgstr "Біс" +#: Source/stores.cpp:2410 +#, c++-format +msgid "Your gold: {:s}" +msgstr "Ваше золото: {:s}" -#: Source/monstdat.cpp:72 -msgctxt "monster" -msgid "Blink" -msgstr "Блимач" +#. 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 "" +" А, історія нашого Короля… Трагічне падіння Леоріка стало великою бідою для " +"наших земель. Люди завжди любили Короля, а тепер живуть в смертельному " +"страху до нього. Я часто сам себе питаю — як так могло статися? Адже Леорік " +"був найбільш праведним серед нас. Тільки найпотворніші та найчорніші сили " +"Пекла могли спотворити сердце Короля…" -#: Source/monstdat.cpp:73 -msgctxt "monster" -msgid "Gloom" -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" +"Лицарі і Священники Короля намагалися умилостивити його, тоді він обрушив " +"всю свою лють на них. Лицарі були змушені його вбити. На останніх хвилинах " +"життя, Король прокляв свою колишню свиту. Він поклявся, що ті хто " +"воспротивилися йому будуть завжди підчинятися Королю в темряві…\n" +"\n" +"Після цього все стало ще гірше ніж було! Мертвий Король встав зі свого " +"вічного сну і тепер керує військом нечисті в Лабіринті. Його тіло поховано у " +"склепі на третьому підземному рівні під Собором. Прошу тебе, Майстре, " +"звільни його заблудшу душу та зруйнуй його осквернене тіло…" -#: Source/monstdat.cpp:74 -msgctxt "monster" -msgid "Familiar" -msgstr "Фамільяр" +#. 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 "" +"Мастре, як я вам казав, Король був похований трьома рівнями нижче. Він там, " +"внизу, чекає свого шансу знищити цю землю в гнилій темряві…" -#: Source/monstdat.cpp:79 -msgctxt "monster" -msgid "Acid Beast" -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 "" +"Прокляття нашого Короля минуло, але я боюся, що тут діє більше зло. Проте " +"твоя перемога — добрий знак, що ми ще можемо бути врятовані від темряви, що " +"поглинає нашу землю. Хай Світло веде тебе на твоєму шляху, майстре." -#: Source/monstdat.cpp:80 -msgctxt "monster" -msgid "Poison Spitter" -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 "" +"Втрата сина була занадто великою трагедією для Короля Леорика. Я зробив усе, " +"що міг, щоб полегшити його безумство, але врешті-решт воно його подолало. З " +"того дня над цим королівством нависло чорне прокляття, але, можливо, якби ти " +"звільнив його дух із земної в’язниці, прокляття було б знято…" -#: Source/monstdat.cpp:81 -msgctxt "monster" -msgid "Pit Beast" -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 "" +"Я не хочу думати про те, як помер Король. Я хочу запам'ятати його як добрим " +"і справедливим правителем. Його смерть була такою сумною і якось дуже " +"неправильною." -#: Source/monstdat.cpp:82 -msgctxt "monster" -msgid "Lava Maw" -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 "" +"Я зробив багато зброї та більшість обладунків, які Король Леорік " +"використовував для спорядження своїх лицарів. Я навіть зробив для нього " +"величезний дворучний меч з найкращого міфрилу, а також польову корону в " +"придачу. Я досі не можу повірити, як він помер, але, мабуть, якась зловісна " +"сила звела його з розуму!" -#: Source/monstdat.cpp:83 Source/monstdat.cpp:344 -msgctxt "monster" -msgid "Skeleton King" -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 "" +"Мене це не хвилює. Слухай, ніякий скелет не буде МОЇМ королем. Леорік — " +"Король. Король, ти мене чуєш? СЛАВА КОРОЛЮ!" -#: Source/monstdat.cpp:84 Source/monstdat.cpp:352 -msgctxt "monster" -msgid "The Butcher" -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 "" +"Мертві, що ходять серед живих, слідують за проклятим Королем. Він володіє " +"силою підняти ще більше воїнів для постійно зростаючої армії нежиті. Якщо ти " +"не зупиниш його правління, він неодмінно пройде по цій землі і вб’є всіх, " +"хто ще тут живе." -#: Source/monstdat.cpp:85 -msgctxt "monster" -msgid "Overlord" -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 "" +"Дивись, я веду тут бізнес. Я не продаю інформацію, і мені байдуже до якогось " +"Короля, який був мертвий довше, ніж я живу. Якщо тобі потрібна якась зброя " +"проти цього Короля нежиті, то я можу тобі допомогти…" -#: Source/monstdat.cpp:86 -msgctxt "monster" -msgid "Mud Man" -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 "" +"Тепло життя увійшло в мою могилу. Приготуйся вічно служити моєму Господарю, " +"смертний!" -#: Source/monstdat.cpp:87 -msgctxt "monster" -msgid "Toad Demon" -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 "" +"Я бачу, ця дивна поведінка і тебе морочить. Я припускаю, що оскільки демони " +"бояться світла сонця і вірять, що воно володіє великою силою, то можливо, " +"зображення на знаку сонця, що сходить, змусило їх повірити, що знак теж " +"володіє таємними силами. Хм, можливо, не всі вони такі розумні, як ми " +"боялися…" -#: Source/monstdat.cpp:88 -msgctxt "monster" -msgid "Flayed One" -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 "" +"Учителю, у мене стався дивний випадок. Я знаю, що ти багато знаєш про тих " +"чудовиськ, які мешкають у лабіринті, але це те, чого я не можу зрозуміти… " +"Уночі мене розбудив шкряпаючий звук біля моєї таверни. Коли я виглянув зі " +"своєї спальні, я побачив тіні маленьких демоноподібних створінь у дворі " +"корчми. Через деякий час вони втекли, але не раніше, ніж вкрали табличку до " +"моєї корчми. Я не знаю, чому демони крадуть мій знак, але залишають мою " +"родину в спокої… дивно, правда?" -#: Source/monstdat.cpp:89 -msgctxt "monster" -msgid "Wyrm" -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 "" +"О, тобі не було потрібно повертати мій знак, але виходить, що це мені " +"заощадить витрати на виготовлення нового. Що ж, зараз подивлюсь, що б я міг " +"би дати тобі в винагороду? Хм, що в нас тут є… ах, ось! Колись чарівник " +"залишив цю шапку в кімнаті. Можливо, це тобі пригодиться." -#: Source/monstdat.cpp:90 -msgctxt "monster" -msgid "Cave Slug" -msgstr "Печерний Слизняк" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Боже мій, демони бігають вночі по селу, грабують наші домівки — хіба немає " +"нічого святого? Сподіваюся, що з Огденом і Гардою все гаразд. Хіба що, вони " +"б прийшли до мене, якби їх поранили…" -#: Source/monstdat.cpp:91 -msgctxt "monster" -msgid "Devil Wyrm" -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 "" +"О Боже! Так от куди дівся знак? Напевно, ми з бабусею все це проспали. Слава " +"Світлу, що ті чудовиська не напали на таверну." -#: Source/monstdat.cpp:92 -msgctxt "monster" -msgid "Devourer" -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" +"Демони хочуть вирвати твоє серце, а не якийсь знак таверни." -#: Source/monstdat.cpp:93 -msgctxt "monster" -msgid "Magma Demon" -msgstr "Демон Магми" +#. 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 "" +"Знаєш, що я думаю? Хтось взяв цей знак, і вони хочуть за нього багато " +"грошей. Якби я був Огденом… А я не він, але якби був… Я б просто купив новий " +"знак із гарним малюнком. Щоб там був гарний кухоль елю або шматочок сиру…" -#: Source/monstdat.cpp:94 -msgctxt "monster" -msgid "Blood Stone" -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" +"Ніколи не давай їм заплутати тебе своїми непомірними вчинками, оскільки це " +"теж може бути їх планом." -#: Source/monstdat.cpp:95 -msgctxt "monster" -msgid "Hell Stone" -msgstr "Пекельний Камінь" +#. 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" +"Я вже кілька місяців тому подолав бажання касти знаки. На шматку дерева " +"прибуток не отримаєш." -#: Source/monstdat.cpp:96 -msgctxt "monster" -msgid "Lava Lord" -msgstr "Лорд Лави" +#. 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 "" +"Гей — Ти той, що всіх вбиваєш! Ти даєш мені Магічний Прапор, або ми " +"атакуємо! Ти не підеш з життям! Ти вбиваєш великих потвор і повертаєш Магію. " +"Пройди повз кут і двері, знайди потвор. Ти даєш, ти йдеш!" -#: Source/monstdat.cpp:97 -msgctxt "monster" -msgid "Horned Demon" -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 "Ти вбиваєш потвор, береш прапор. Ти принеси мені, а то…" -#: Source/monstdat.cpp:98 -msgctxt "monster" -msgid "Mud Runner" -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 "" +"Ти даєш! Так, добре! Іди зараз, ми сильні. Ми вб'ємо всіх великою Магією!" -#: Source/monstdat.cpp:99 -msgctxt "monster" -msgid "Frost Charger" -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" +"Це, мабуть і є те, що він запланував! Ті, хто залишився в живих із його " +"«рятівної групи», кажуть, що Лазаря востаннє бачили втікаючим у найглибші " +"надра лабіринту. Ти маєш поквапитися і врятувати принца від жертовного леза " +"цього божевільного негідника!" -#: Source/monstdat.cpp:100 -msgctxt "monster" -msgid "Obsidian Lord" -msgstr "Лорд Обсидіяну" +#. 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 "" +"Ти повинен поспішити і врятувати Альбрехта з рук Лазаря. Принц і жителі " +"королівства розраховують на тебе!" -#: Source/monstdat.cpp:101 -msgctxt "monster" -msgid "oldboned" -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" +" \n" +"Знай це, друже мій — Зло, проти якого ти борешся, це темний Володар Жахів. " +"Він відомий смертним як Діабло. Саме він був ув’язнений у Лабіринті багато " +"століть тому, і я боюся, що він знову прагне посіяти хаос у виміру людства. " +"Ти маєш пройти через портал і знищити Діабло поки не пізно!" -#: Source/monstdat.cpp:102 -msgctxt "monster" -msgid "Red Death" -msgstr "Червона Смерть" - -#: Source/monstdat.cpp:103 -msgctxt "monster" -msgid "Litch Demon" -msgstr "Демон-Ліч" - -#: Source/monstdat.cpp:104 -msgctxt "monster" -msgid "Undead Balrog" -msgstr "Балрог-Нежить" - -#: Source/monstdat.cpp:105 -msgctxt "monster" -msgid "Incinerator" -msgstr "Крематор" +#. 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 "" +"Лазар був Архієпископом, який ввів городян у лабіринт. Того дня я втратив " +"багато хороших друзів, а Лазар так і не повернувся. Думаю, що його вбили " +"разом з іншими. Майстре, якщо ти зробиш мені послугу, будь ласка, не говори " +"про той день з Фарнхемом." -#: Source/monstdat.cpp:106 -msgctxt "monster" -msgid "Flame Lord" -msgstr "Лорд Вогню" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Я був шокований, коли почув, що жителі міста планували робити тієї ночі. Я " +"думав, що Лазар мав більше глузду, з усіх людей. Він був Архієпископом і, " +"здавалося, завжди дбав про жителів Трістрама. Було так багато поранених, я " +"не міг їх усіх врятувати…" -#: Source/monstdat.cpp:107 -msgctxt "monster" -msgid "Doom Fire" -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 "" +"Я пам’ятаю Лазаря як дуже доброго і доброзичливого чоловіка. Він виступав на " +"похороні моєї матері і підтримував мене і мою бабусю в дуже скрутний час. Я " +"молюся щовечора, щоб він як-небудь був живий і в безпеці." -#: Source/monstdat.cpp:108 -msgctxt "monster" -msgid "Hell Burner" -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 "" +"Я був там, коли Лазар ввів нас у лабіринт. Він говорив про святу відплату, " +"але коли ми почали боротися з тими пекельними виродками, він їх пальцем не " +"торкнувся. Він просто побіг глибше в ті темні безкінечні палати, що " +"наповнені слугами темряви!" -#: Source/monstdat.cpp:109 -msgctxt "monster" -msgid "Red Storm" -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 "" +"Вони колють, потім кусають, потім вони всюди, навколо вас. Брехун! БРЕХУН! " +"Вони всі мертві! Мертві! Ти мене чуєш? Вони падають і падають… їх кров " +"розливається по підлозі… це все його вина…" -#: Source/monstdat.cpp:110 -msgctxt "monster" -msgid "Storm Rider" -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 "" +"Я не знала Лазаря, про якого ти говориш, але відчуваю великий конфлікт " +"всередині його душі. Він представляє велику небезпеку і ні перед чим не " +"зупиниться, щоб служити силам темряви, які визнали його своїм." -#: Source/monstdat.cpp:111 -msgctxt "monster" -msgid "Storm Lord" -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 "" +"А, праведний Лазар, який був таким корисним проти тих чудовиськ внизу. Мою " +"ногу не врятував, так же? Дивись, дам тобі пораду просто так. Спитай " +"Фарнхема, він там був." -#: Source/monstdat.cpp:112 -msgctxt "monster" -msgid "Maelstrom" -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 "" +"Відмовся від своїх дурних пошуків. Все, що тебе чекає — лють мого Господаря! " +"Рятувати дитину вже пізно. Тепер ти приєднаєшся до нього в пеклі!" -#: Source/monstdat.cpp:113 -msgctxt "monster" -msgid "Devil Kin Brute" -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 "" +"Хм, не знаю що тобі сказати і що може стати в нагоді. Вода, яка наповнює " +"наші криниці надходить із підземного джерела. Я чув про тунель, що веде до " +"великого озера, можливо вони — одне й те саме. На жаль, я не знаю, що могло " +"б призвести до забруднення нашого джерела." -#: Source/monstdat.cpp:114 -msgctxt "monster" -msgid "Winged-Demon" -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" +" \n" +"Будь ласка, зроби все, що можеш, або я не знаю, що ми будемо робити далі." -#: Source/monstdat.cpp:115 -msgctxt "monster" -msgid "Gargoyle" -msgstr "Ґаргулья" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Я радий, що вчасно тебе наздогнав ! Вода в наших колодязях стала солонуватою " +"і застояною, і деякі городяни захворіли через неї. Наші запаси прісної води " +"швидко вичерпуються. Я вірю, що є прохід, який веде до джерел, які " +"постачають наше місто. Будь ласка, знайди що причинило це нещастя, інакше ми " +"всі неодмінно загинемо." -#: Source/monstdat.cpp:116 -msgctxt "monster" -msgid "Blood Claw" -msgstr "Кривавий Кіготь" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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" +" \n" +"Довго прожити без твоєї допомоги ми не зможемо." -#: Source/monstdat.cpp:117 -msgctxt "monster" -msgid "Death Wing" -msgstr "Крило Смерті" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"То ти кажеш, що сама присутність демонів спричинила забруднення води? О, під " +"нашим містом ховається справді велике зло, але твоя наполегливість і " +"мужність дає нам надію. Візьми цей перстень, будь ласка — можливо, він " +"допоможе тобі в знищенні цих підлих істот." -#: Source/monstdat.cpp:118 -msgctxt "monster" -msgid "Slayer" -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 "" +"Моя бабуся дуже слабка, а Гарда каже, що ми не можемо пити воду з криниці. " +"Прошу, ти можеш нам чимось допомогти?" -#: Source/monstdat.cpp:119 -msgctxt "monster" -msgid "Guardian" -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 "" +"Пепін сказав тобі правду. Прісна вода нам дуже скоро знадобиться. Я пробував " +"очистити один з колодязів поменше, але там пахне застійним брудом. Можливо, " +"засмічення іде з джерела." -#: Source/monstdat.cpp:120 -msgctxt "monster" -msgid "Vortex Lord" -msgstr "Лорд Вихру" +#. TRANSLATORS: Quest dialog spoken by Farnham +#: Source/textdat.cpp:100 +msgid "You drink water?" +msgstr "Ти п'єш воду?" -#: Source/monstdat.cpp:121 -msgctxt "monster" -msgid "Balrog" -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" +"Знай таке — демони знаходяться в центрі цієї справи, але вони не знають, що " +"вони наробили." -#: Source/monstdat.cpp:122 -msgctxt "monster" -msgid "Cave Viper" -msgstr "Печерна Гадюка" +#. 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 "" +"В цей раз, я з тобою. Мій бізнес, так би мовити, закінчиться, якщо немає " +"кому продавати. Краще дізнайся, що відбувається, і як найшвидше!" -#: Source/monstdat.cpp:123 -msgctxt "monster" -msgid "Fire Drake" -msgstr "Вогняний Левіятан" - -#: Source/monstdat.cpp:124 -msgctxt "monster" -msgid "Gold Viper" -msgstr "Золота Гадюка" - -#: Source/monstdat.cpp:125 -msgctxt "monster" -msgid "Azure Drake" -msgstr "Лазурний Левіятан" - -#: Source/monstdat.cpp:126 -msgctxt "monster" -msgid "Black Knight" -msgstr "Чорний Лицар" - -#: Source/monstdat.cpp:127 -msgctxt "monster" -msgid "Doom Guard" -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 "" +"Книга, що говорить про палату з людських кісток? Ну, Палата Кості згадується " +"в деяких архаїчних творах, які я вивчав у бібліотеках Сходу. У цих томах " +"йшлося про те, що, коли Володарі підземного світу хотіли захистити великі " +"скарби, вони створили ділянки, де ті, хто загинули спробувавши вкрасти " +"скарби, будуть зобов’язані його захищати назавжди. Заплутаний, але на диво " +"відповідний кінець?" -#: Source/monstdat.cpp:128 -msgctxt "monster" -msgid "Steel Lord" -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 "" +"Боюся, що я нічого про це не знаю, Майстре. У Каїна є багато книг, які " +"можуть стати в нагоді." -#: Source/monstdat.cpp:129 -msgctxt "monster" -msgid "Blood Knight" -msgstr "Лицар Крові" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: Source/textdat.cpp:109 +msgid "" +"This sounds like a very dangerous place. If you venture there, please take " +"great care." +msgstr "" +"Звучить як дуже небезпечне місце. Якщо ти ризикнеш підти туди, будь " +"обережним." -#: Source/monstdat.cpp:130 -msgctxt "monster" -msgid "The Shredded" -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 "" +"Боюся, що я нічого про це не чув. Можливо, Каїн Розповідач міг би допомогти." -#: Source/monstdat.cpp:131 -msgctxt "monster" -msgid "Hollow One" -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 "" +"Я нічого не знаю про те місце, але ти можеш спитати у Каїна. Він говорить " +"про багато речей, і я б не здивувався, якби він мав відповіді на твої " +"питання." -#: Source/monstdat.cpp:132 -msgctxt "monster" -msgid "Pain Master" -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 "" +"Гаразд, слухай. Є ось ця дерев'яна палата. А його дружина, ну знаєш, вона, " +"говорить дереву… що треба почекати. Тоді я кажу, що це може спрацювати проти " +"нього, але якщо ти думаєш, що я ЗАПЛАЧУ за це… ти… ем… так." -#: Source/monstdat.cpp:133 -msgctxt "monster" -msgid "Reality Weaver" -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" +" \n" +"Іди в Палату Кості на свій страх і ризик." -#: Source/monstdat.cpp:134 -msgctxt "monster" -msgid "Succubus" -msgstr "Суккуб" +#. 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 "" +"То ти кажеш, величезний і таємничий скарб? Мені було б цікаво взяти у тебе " +"пару речей… або, ще краще, хіба тобі не потрібні рідкісні та дорогі припаси, " +"щоб пережити це випробування?" -#: Source/monstdat.cpp:135 -msgctxt "monster" -msgid "Snow Witch" -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 "" +"Схоже, архієпископ Лазар підбурив багатьох городян піти в Лабіринт, щоб " +"знайти зниклого сина Короля. Він зіграв на їхніх страхах і збив їх у шалену " +"юрбу. Жоден з них не був готовий до того, що лежало під землею… Лазар " +"покинув їх там, унизу — залишив у лапах невимовних жахів на смерть." -#: Source/monstdat.cpp:136 -msgctxt "monster" -msgid "Hell Spawn" -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 "" +"Так, Фарнхем бурмотів щось про незграбного звіра, який володів лютою зброєю. " +"Схоже, він назвав його м’ясником." -#: Source/monstdat.cpp:137 -msgctxt "monster" -msgid "Soul Burner" -msgstr "Пальник Душ" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Заради Світла, я знаю цього мерзенного демона. Коли ті, хто пережив атаку, " +"яку вів Лазарь, виповзли з собору — було багато тих, хто носив шрами його " +"гніву на своїх тілах. Я не знаю, чим він розрізав своїх жертв, але це не " +"могло бути з цього світу. Воно лишало рани, нагноєні хворобою, і я бачив, що " +"їх майже неможливо вилікувати. Будь обережним, якщо плануєш битися з цим " +"негідником…" -#: Source/monstdat.cpp:138 -msgctxt "monster" -msgid "Counselor" -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 "" +"Коли Фарнхем сказав щось про м’ясника, який вбиває людей, я не сприйняла це " +"всерйоз. Але оскільки ти про це кажеш, то можливо, це правда." -#: Source/monstdat.cpp:139 -msgctxt "monster" -msgid "Magistrate" -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 "" +"Я бачив те, що Фарнхем називає М’ясником, коли воно прорізало шлях крізь " +"моїх друзів. Воно махнуло тесаком, завбільшки з сокиру, рубало кінцівки й " +"вирізало хоробрих людей там, де вони стояли. Я був відділений від бою " +"безліччю маленьких верескливих демонів і якось знайшов сходи, що ведуть " +"наверх. Я більше ніколи не бачив цього жахливого звіра, але його " +"закривавлене обличчя переслідує мене і донині." -#: Source/monstdat.cpp:140 -msgctxt "monster" -msgid "Cabalist" -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 "" +"Великий! Великий тесак вбиває всіх моїх друзів. Не міг зупинити його, " +"довелося тікати, не міг їх врятувати. У пастці, так багато трупів… так " +"багато друзів… НІІІІІІІІІІ!" -#: Source/monstdat.cpp:141 -msgctxt "monster" -msgid "Advocate" -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 "" +"М’ясник — садистська істота, яка насолоджується тортурами та болем інших. Ти " +"бачив його роботу в п’яниці Фарнхемі. Його знищення зробить багато щоб " +"забезпечити безпеку цього села." -#: Source/monstdat.cpp:142 -msgctxt "monster" -msgid "Golem" -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" +"Скажу прямо – вбий його, перш ніж він уб’є тебе і додасть твій труп до своєї " +"колекції." -#: Source/monstdat.cpp:143 -msgctxt "monster" -msgid "The Dark Lord" -msgstr "Темний Лорд" +#. 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 "" +"Прошу тебе, вислухай. Архієпископ Лазар, він привів нас сюди, щоб знайти " +"загубленого принца. Мерзотник завів нас у пастку! Тепер усі мертві… убиті " +"демоном, якого він назвав М’ясником. Помстись за нас! Знайди цього М'ясника " +"і вбий його, щоб наші душі нарешті упокоїлись…" -#: Source/monstdat.cpp:144 -msgctxt "monster" -msgid "The Arch-Litch Malignus" -msgstr "Архі-Ліч Малігнус" - -#: Source/monstdat.cpp:145 -msgctxt "monster" -msgid "Hellboar" -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" +"…Тьма огортає Прихованих. Очі, невидимо світяться, лише звуки гострих " +"кігтів, які шкрябають, щоб мучити ті бідні душі, що втратили зір навіки. " +"В'язниця для тих проклятих називається Залами Сліпих…" -#: Source/monstdat.cpp:146 -msgctxt "monster" -msgid "Stinger" -msgstr "Жало" +#. 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" +"Що? О, так… ну, ти міг би спитати, що знають інші." -#: Source/monstdat.cpp:147 -msgctxt "monster" -msgid "Psychorb" -msgstr "Псих-Око" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Він здається дуже знайомим. Я пам’ятаю, як читав щось дуже схоже на цей " +"вірш, досліджуючи історію демонічних страждань. Там говорилося про місце " +"великого зла, що… почекай, ти ж туди не підеш?" -#: Source/monstdat.cpp:148 -msgctxt "monster" -msgid "Arachnon" -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 "" +"Якщо у тебе є питання про сліпоту, поговори з Пепіном. Він давав моїй бабусі " +"зілля, яке допомогло прояснити її зір, тож, можливо, він і тобі допоможе." -#: Source/monstdat.cpp:149 -msgctxt "monster" -msgid "Felltwin" -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 "" +"Боюся, що я не чув і не бачив місце, що б відповідало твоєму яскравому " +"опису, друже. Можливо, Каїн Розповідач міг би допомогти." -#: Source/monstdat.cpp:150 -msgctxt "monster" -msgid "Hork Spawn" -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 "Глянь сюди… Смішно, правда? Зрозумів? Сліпий — глянь сюди?" -#: Source/monstdat.cpp:151 -msgctxt "monster" -msgid "Venomtail" -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" +"Будь обережним, інакше ти сам надовго там залишишся." -#: Source/monstdat.cpp:152 -msgctxt "monster" -msgid "Necromorb" -msgstr "Некро-Око" +#. 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 "" +"Подивимося, я тобі щось продаю? Ні. Ти даєш мені гроші, щоб я тобі " +"розказував? Ні. Ти зараз підеш і будеш говорити з оповідачем, який живе " +"заради таких речей? Так." -#: Source/monstdat.cpp:153 -msgctxt "monster" -msgid "Spider Lord" -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" +"З тих, кого охопило прокляття короля, Лахданан найменше би підкорився " +"темряві без бою, тому схоже, що твоя історія правдива. Якби я був на твоєму " +"місці, друже, я б знайшов спосіб, як звільнити його від тортур." -#: Source/monstdat.cpp:154 -msgctxt "monster" -msgid "Lashworm" -msgstr "Хльостохробак" +#. 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 "" +"Ти говориш про відважного воїна, що давно помер! Ну ні, у моїй корчмі таких " +"балачок про бесіди з померлими не буде." -#: Source/monstdat.cpp:155 -msgctxt "monster" -msgid "Torchant" -msgstr "Торчант" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Значить, золотий еліксир. Я ніколи раніше не готував зілля такого кольору, " +"тому не можу сказати що станеться, якщо ти його вип'єш. Як твій цілитель, я " +"настійно раджу, що якщо ти знайдеш такий еліксир, роби так, як просить " +"Лахданан, і НЕ пробуй його на собі." -#: Source/monstdat.cpp:156 Source/monstdat.cpp:353 -msgctxt "monster" -msgid "Hork Demon" -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 "" +"Я ніколи раніше не чула про Лахданана. Пробач, але я не зможу тут тобі " +"допомогти." -#: Source/monstdat.cpp:157 -msgctxt "monster" -msgid "Hell Bug" -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 "" +"Якщо ти насправді зустрів Лахданана, я б порадив допомогти йому. Я кілька " +"разів мав з ним справу вважаю його чесним і відданим за своєю природою. " +"Прокляття, яке впало на послідовників Короля Леоріка, особливо тяжко " +"обрушилося на нього." -#: Source/monstdat.cpp:158 -msgctxt "monster" -msgid "Gravedigger" -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 "" +" Лахданан мертвий. Усі це знають, і ти не зможеш обдурити мене, щоб я " +"передумав. З мертвими не можна говорити. Я знаю!" -#: Source/monstdat.cpp:159 -msgctxt "monster" -msgid "Tomb Rat" -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" +"Я відчуваю в ньому честь і велику провину. Допоможи йому, і ти допоможеш " +"всьому Трістраму." -#: Source/monstdat.cpp:160 -msgctxt "monster" -msgid "Firebat" -msgstr "Вогняний Кажан" +#. 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 "" +"Зачекай, дай вгадати. Каїна поглинула гігантська тріщина, що відкрилася під " +"ним. Його спалило кулею пекельного вогню, і він більше не зможе відповідати " +"на твої запитання. О, цього не сталося? Тоді, або щось купуй, або іди звідси." -#: Source/monstdat.cpp:161 -msgctxt "monster" -msgid "Skullwing" -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" +"Я чув про Золотий еліксир, який міг би зняти прокляття і дати моїй душі " +"спокій, але я не зміг його знайти. Моя сила слабшає, а разом з цим і моя " +"остання людяність. Будь ласка, допоможи мені і знайди еліксир. Твої зусилля " +"я відплачу — клянусь своєю честю." -#: Source/monstdat.cpp:162 -msgctxt "monster" -msgid "Lich" -msgstr "Ліч" +#. 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 "" +"Ти не знайшов Золотий еліксир. Я боюся, що навічно приречений. Прошу, не " +"здавайся…" -#: Source/monstdat.cpp:163 -msgctxt "monster" -msgid "Crypt Demon" -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 "" +"Ти врятував мою душу від прокляття, і за це в тебе в боргу. Якщо колись буде " +"спосіб відплатити тобі з-за могили, я знайду його, але поки — візьми мій " +"шолом. Він не знадобиться меня в подорожі, в яку я піду. Нехай він захистить " +"тебе від темних сил внизу. Іди зі Світлом, друже…" -#: Source/monstdat.cpp:164 -msgctxt "monster" -msgid "Hellbat" -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 "" +"Грізволд говорить про Ковадло Люті — легендарний артефакт, який довго " +"шукали, але так і не знайшли. Створене з металевих кісток демонів з Лезової " +"Темниці, Ковадло Люті було виплавлене навколо черепів п’яти наймогутніших " +"магів підземного світу. На нім вирізані руни сили та хаосу, і будь-яка зброя " +"або броня, виковане на цьому ковадлі, буде занурене в вимір Хаосу, " +"наповнюючи її магічними властивостями. Кажуть, що через непередбачуваність " +"Хаосу важко знати, яким буде результат…" -#: Source/monstdat.cpp:165 -msgctxt "monster" -msgid "Bone Demon" -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 "" +"Хіба не було б краще запитати про це Грізволда? Знаєш, він досить умілий." -#: Source/monstdat.cpp:166 -msgctxt "monster" -msgid "Arch Lich" -msgstr "Архі-Ліч" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Якби ти шукав інформацію про Товкачик Зцілення або Срібну Чашу Очищення, я " +"міг би тобі допомогти, друже. Однак у цій справі тобі краще поговорити з " +"Грізволдом або з Каїном." -#: Source/monstdat.cpp:167 -msgctxt "monster" -msgid "Biclops" -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 "" +"Коли ми росли, батько Грізволда розповідав нам про гігантське ковадло, на " +"якому виготовляли могутню зброю. Він сказав, що коли по цьому ковадлу " +"вдарять молотком, земля трясеться від великої люті. Я завжди згадую цю " +"історію коли земля трясеться." -#: Source/monstdat.cpp:168 -msgctxt "monster" -msgid "Flesh Thing" -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" +"Знайди мені це Ковадло, і я візьмуся за роботу!" -#: Source/monstdat.cpp:169 -msgctxt "monster" -msgid "Reaper" -msgstr "Жнець" +#. 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: Monster Block end -#. MT_NAKRUL -#: Source/monstdat.cpp:171 Source/monstdat.cpp:355 -msgctxt "monster" -msgid "Na-Krul" -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: Unique Monster Block start -#: Source/monstdat.cpp:343 -msgctxt "monster" -msgid "Gharbad the Weak" -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 "" +"Грізволд не може продати своє ковадло. Що він тоді робитиме? І я б теж " +"розсердився, якби хтось узяв моє ковадло!" -#: Source/monstdat.cpp:345 -msgctxt "monster" -msgid "Zhar the Mad" -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 "" +"У Лабіринті є багато артефактів, які володіють силами, що незбагненні для " +"смертних. Деякі з них мають фантастичну силу, яку може використати Світло, " +"або Темрява. Здобуття Ковадла з Лабіринту може зрушити хід Війни Гріхів у " +"бік Світла." -#: Source/monstdat.cpp:346 -msgctxt "monster" -msgid "Snotspill" -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 "" +"Якби ти знайшли цей артефакт для Грізволда, це могло б серйозно зашкодити " +"моєму бізнесу. Оооо, ти ніколи його не знайдеш." -#: Source/monstdat.cpp:347 -msgctxt "monster" -msgid "Arch-Bishop Lazarus" -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" +"Перед смертю Аркейна, його обладунки були заховані в таємному сховищі. " +"Кажуть, що коли цей святий обладунок знову знадобиться, постане герой, що " +"надягне Доблесть. Можливо, ти і є той герой…" -#: Source/monstdat.cpp:348 -msgctxt "monster" -msgid "Red Vex" -msgstr "Червоний Векс" +#. 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 "" +"Кожна дитина чує історію воїна Аркейна та його містичної броні, відомої як " +"Доблесть. Якби ти зміг знайти його могилу, ти був би добре захищений від зла " +"в Лабіринті." -#: Source/monstdat.cpp:349 -msgctxt "monster" -msgid "Black Jade" -msgstr "Чорний Нефрит" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Хм… звучить як щось, що я повинен пам’ятати, але я був настільки зайнятий " +"вивченням нових ліків і створенням кращих еліксирів, що, мабуть, забув. " +"Вибач…" -#: Source/monstdat.cpp:350 -msgctxt "monster" -msgid "Lachdanan" -msgstr "Лахданан" - -#: Source/monstdat.cpp:351 -msgctxt "monster" -msgid "Warlord of Blood" -msgstr "Воєвода Крові" - -#: Source/monstdat.cpp:354 -msgctxt "monster" -msgid "The Defiler" -msgstr "Паплюжник" - -#: Source/monstdat.cpp:356 -msgctxt "monster" -msgid "Bonehead Keenaxe" -msgstr "Гостросокирний Кістяк" - -#: Source/monstdat.cpp:357 -msgctxt "monster" -msgid "Bladeskin the Slasher" -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 "" +"Я часто чула, як хлопці розповідали про чарівну броню під назвою Доблесть. " +"Краще запитай в когось із чоловіків у селі." -#: Source/monstdat.cpp:358 -msgctxt "monster" -msgid "Soulpus" -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 "" +"Броня, відома як Доблесть, може стати тим, що схилить ваги на твою користь. " +"Скажу тобі, що багато хто її шукав, в тому числі і я. Аркейн добре сховав " +"її, друже, і знадобиться більше ніж трохи удачі, щоб розкрити таємниці що " +"ховали її, ось так багато років." -#: Source/monstdat.cpp:359 -msgctxt "monster" -msgid "Pukerat the Unclean" -msgstr "Нечистий Блювопацюк" +#. TRANSLATORS: Quest dialog "spoken" by Farnham +#: Source/textdat.cpp:208 +msgid "Zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz..." +msgstr "Хрррррррррррррррррррррррррррр…" -#: Source/monstdat.cpp:360 -msgctxt "monster" -msgid "Boneripper" -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" +"Шлях повен небезпеки, і твоя єдина надія полягає у вірі в себе." -#: Source/monstdat.cpp:361 -msgctxt "monster" -msgid "Rotfeast the Hungry" -msgstr "Голодний Гнилеїд" +#. 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" +"Ніхто ніколи не дізнався, де Аркейн сховав речі, і якщо навіть мої контакти " +"не змогли їх знайти, я сумніваюся, що і ти коли-небудь знайдеш." -#: Source/monstdat.cpp:362 -msgctxt "monster" -msgid "Gutshank the Quick" -msgstr "Швидкий Животоріз" +#. 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" +"Також написано, що, хоча він колись був смертним, що воював поруч із " +"Легіоном Темряви під час Війни гріхів, він втратив свою людяність через " +"ненаситну кровожерність." -#: Source/monstdat.cpp:363 -msgctxt "monster" -msgid "Brokenhead Bangshield" -msgstr "Ломочереп зі Щитом" +#. 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 "" +"Боюся, що я нічого не чув про такого злісного воїна, добрий майстре. " +"Сподіваюся, вам не доведеться з ним битися, бо він здається дуже небезпечним." -#: Source/monstdat.cpp:364 -msgctxt "monster" -msgid "Bongo" -msgstr "Бонго" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Каїн розповість тобі набагато більше про щось подібне, ніж я хотів би знати." -#: Source/monstdat.cpp:365 -msgctxt "monster" -msgid "Rotcarnage" -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 "" +"Якщо тобі треба битися з таким запеклим супротивником, то нехай Світло буде " +"твоїм провідником і захисником. Я буду думати про тебе." -#: Source/monstdat.cpp:366 -msgctxt "monster" -msgid "Shadowbite" -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 "" +"Темні та злі легенди оточують Воєводу Крові. Будь готовим, мій друже, бо він " +"не покаже милосердя і не почує поблажок." -#: Source/monstdat.cpp:367 -msgctxt "monster" -msgid "Deadeye" -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 "" +"Завжди треба говорити про Кров? А як щодо квітів, сонця та тієї гарної " +"дівчини, що приносить випивку. Слухай сюди, друже - ти настирний, ти це " +"знаєш?" -#: Source/monstdat.cpp:368 -msgctxt "monster" -msgid "Madeye the Dead" -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 "" +"Його майстерність меча вражає, і він прожив тисячі років, знаючи лише війну. " +"Мені жаль… Я не бачу, чи зможеш ти перемогти його." -#: Source/monstdat.cpp:369 -msgctxt "monster" -msgid "El Chupacabras" -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 "" +"Я ніколи не мав справи з цим Воєводою, що ти говориш, але здається, що в " +"нього часто ламаються мечі. Я не проти, щоб постачати його армію…" -#: Source/monstdat.cpp:370 -msgctxt "monster" -msgid "Skullfire" -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 "" +"Мій клинок співає по твоїй крові, смертний, і моїм темнім господарям не буде " +"відмови." -#: Source/monstdat.cpp:371 -msgctxt "monster" -msgid "Warpskull" -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 "" +"Грізволд говорить про Небесний Камінь, що був призначений для анклаву, " +"розташованому на сході. Його везли туди для подальшого дослідження. Цей " +"камінь світився енергією, яка якимось чином надавала бачення, що виходить за " +"межі того, чим могла володіти звичайна людина. Я не знаю, які секрети він " +"приховує, друже, але знайти цей камінь, безумовно, виявилося б найціннішим." -#: Source/monstdat.cpp:372 -msgctxt "monster" -msgid "Goretongue" -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 "" +"Караван зупинився тут, щоб взяти припаси для їх подорожі на схід. Я продав " +"їм чимало свіжих фруктів і чудового солодкового м'яса, яке Гарда тільки " +"закінчила випікати. Прикро що з ними сталося…" -#: Source/monstdat.cpp:373 -msgctxt "monster" -msgid "Pulsecrawler" -msgstr "Пульсоповз" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Я не знаю, що вони думали, що вони могли побачити з тим каменем, але скажу " +"так. Якщо з неба падає каміння, будь обережним!" -#: Source/monstdat.cpp:374 -msgctxt "monster" -msgid "Moonbender" -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" +"Я не знаю, як ти сподіваєшся знайти те, що вони несли." -#: Source/monstdat.cpp:375 -msgctxt "monster" -msgid "Wrathraven" -msgstr "Ворон Кари" +#. 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 "" +"Залишся на хвилинку — у мене є історія, яка може тебе зацікавити. Якийсь час " +"потому, тут проходив караван, який прямував до східних королівств. Він " +"нібито ніс шматочок неба, що впав на землю! Вершники в плащах напали на " +"караван з засідки на північ звідси, біля дороги. Я обшукав уламки в пошуках " +"цієї небесної скелі, але її ніде не було. Якщо ти знайдеш її, то думаю, що " +"зможу виготовити з неї щось корисне." -#: Source/monstdat.cpp:376 -msgctxt "monster" -msgid "Spineeater" -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 "" +"Я все ще чекаю, коли ти принесеш мені той камінь з небес. Я знаю, що можу " +"зробити з нього щось могутнє." -#: Source/monstdat.cpp:377 -msgctxt "monster" -msgid "Blackash the Burning" -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" +"Ах, ось. Я розташував шматки каменю в срібному перстні, який залишив мені " +"батько. Сподіваюся, він добре тобі послужить." -#: Source/monstdat.cpp:378 -msgctxt "monster" -msgid "Shadowcrow" -msgstr "Тіньоворона" +#. 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 "" +"Колись у мене був гарний перстень; він був дуже дорогий, синій і зелений, і " +"червоний, і срібний. Не пам’ятаю, що з цим сталося. Я дуже сумую за цим " +"перстнем…" -#: Source/monstdat.cpp:379 -msgctxt "monster" -msgid "Blightstone the Weak" -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 "" +"Небесний Камінь дуже могутній, і якби хтось, окрім Грізволда, запропонував " +"тобі його знайти, я б запобігла цьому. Він приборкає його силу, і ця сила " +"буде на благо всім нам." -#: Source/monstdat.cpp:380 -msgctxt "monster" -msgid "Bilefroth the Pit Master" -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 "" +"Якщо хтось зможе щось зробити з того каменю, то це Грізволд. Він знає, що " +"робить, і я поважаю його майстерність, хай би я і намагаюся вкрасти його " +"клієнтів." -#: Source/monstdat.cpp:381 -msgctxt "monster" -msgid "Bloodskin Darkbow" -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 "" +"Відьма Адрія шукає чорний гриб? Я знаю про Чорні Гриби стільки ж, скільки " +"про Червоних Оселедців. Можливо, Цілитель Пепін розкаже більше, але такого " +"немає в жодній з моїх оповідань чи книжок." -#: Source/monstdat.cpp:382 -msgctxt "monster" -msgid "Foulwing" -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 "" +"Я ось що скажу. Ні Гарда, ні я, НІКОЛИ б не подали чорні гриби нашим " +"почесним гостям. Якщо Адрія хоче гриби у своєму рагу, то це її справа, але я " +"не можу допомогти в їх пошуку. Чорні гриби… Гидота!" -#: Source/monstdat.cpp:383 -msgctxt "monster" -msgid "Shadowdrinker" -msgstr "Тінопитій" +#. TRANSLATORS: Quest dialog spoken by Pepin +#: 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 "" +"Відьма сказала мені, що ти шукаєш мозок демона, щоб допомогти мені створити " +"еліксир. Він має бути дуже цінним для тих, хто постраждав від тих мерзенних " +"звірів, якщо я зможу розкрити таємниці їхньої алхімії. Якщо ти зможеш " +"видалити мозок демона, коли його вб'єш, я був би вдячний, якби ти приніс " +"його мені." -#: Source/monstdat.cpp:384 -msgctxt "monster" -msgid "Hazeshifter" -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 "" +"Чудово, це саме те, що мені потрібно. Я зміг закінчити еліксир без нього, " +"але не завадить мати його для вивчення. Не міг б ти віднести це до відьми? Я " +"думаю, що вона чекає на це." -#: Source/monstdat.cpp:385 -msgctxt "monster" -msgid "Deathspit" -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 "" +"Думаю, в погребі в Огдена можуть бути гриби. Чому б тобі не запитати його?" -#: Source/monstdat.cpp:386 -msgctxt "monster" -msgid "Bloodgutter" -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 "" +"Якщо його немає у Адрії, то можеш бути певним, що це справді рідкість. Я не " +"зможу допомогти тобі більше, але це звучить як… величезний, гігантський, " +"набряклий, роздутий гриб! Ну що ж, гарного полювання." -#: Source/monstdat.cpp:387 -msgctxt "monster" -msgid "Deathshade Fleshmaul" -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 "" +"Огден мішає ДОБРИЙ чорний гриб, але якщо я його вип’ю, мені стає погано. " +"Слухай, слухай… ось секрет - головне — помірність!" -#: Source/monstdat.cpp:388 -msgctxt "monster" -msgid "Warmaggot the Mad" -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 "" +"Що ми тут маємо? Цікаво, схоже на книгу реактивів. Добре гляди, щоб знайти " +"чорний гриб. Його легко впізнати, він повинен бути досить великим. Якщо " +"знайдеш, його принеси мені, будь ласка?" -#: Source/monstdat.cpp:389 -msgctxt "monster" -msgid "Glasskull the Jagged" -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 "" +"Мені потрібен великий чорний гриб. Тепер біжи і принеси його мені, щоб я " +"могла використати його для спеціального варева, над яким я працюю." -#: Source/monstdat.cpp:390 -msgctxt "monster" -msgid "Blightfire" -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 "" +"Так, це ідеально підійде для варева, що я роблю. До речі, цілитель шукає " +"мозок якогось демона, щоб лікувати тих, кого вразила його згубна отрута. " +"Вважаю, що він хоче зробити з нього еліксир. Якщо ти допоможеш йому знайти " +"те, що потрібно, то будь ласка, подивись, чи можеш отримати для мене зразок " +"еліксиру." -#: Source/monstdat.cpp:391 -msgctxt "monster" -msgid "Nightwing the Cold" -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 "" +"Чому ти приніс це сюди? Наразі мені не потрібен мозок демона. Мені потрібен " +"еліксир, над яким працює Цілитель. Йому потрібен той гротескний орган, який " +"ти тримаєш, а потім принеси мені еліксир. Доволі просто, якщо подумаєш, чи " +"не так?" -#: Source/monstdat.cpp:392 -msgctxt "monster" -msgid "Gorestone" -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 "" +"Що? Тепер ти приніс мені той еліксир від цілителя? Я змогла доробити варево " +"без нього. Чому б тобі не залишити його собі…" -#: Source/monstdat.cpp:393 -msgctxt "monster" -msgid "Bronzefist Firestone" -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 "" +"Я не продаю гриби будь-якого розміру чи кольору. Може тобі потрібне щось " +"більш корисне?" -#: Source/monstdat.cpp:394 -msgctxt "monster" -msgid "Wrathfire the Doomed" -msgstr "Приречений Гнівовогонь" - -#: Source/monstdat.cpp:395 -msgctxt "monster" -msgid "Firewound the Grim" -msgstr "Нещадний Вогнеран" - -#: Source/monstdat.cpp:396 -msgctxt "monster" -msgid "Baron Sludge" -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" +"Зло, якому ви протидієш, це темний Лордом Жаху, відомим смертним людям як " +"Діабло. Саме він був ув’язнений у Лабіринті багато століть тому. Карта, яку " +"ти зараз тримаєш, була створена багато років тому, щоб відзначити час, коли " +"Діабло знову повстане з ув’язнення. Коли дві зірки на цій карті зрівняються, " +"Діабло буде на піку своєї сили. Він буде майже непереможним...\n" +" \n" +"Тепер ти в гонці з часом, друже! Знайди Діабло та знищ його до того, як " +"зірки зійдуться, бо можливо у нас ніколи більше не буде шансу позбавити світ " +"від його зла!" -#: Source/monstdat.cpp:397 -msgctxt "monster" -msgid "Blighthorn Steelmace" -msgstr "Сталебулавовий Іржекіготь" +#. 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 "" +"В нас немає часу! Я відчуваю його темну силу, і тільки ти можеш перешкодити " +"йому досягти повної сили." -#: Source/monstdat.cpp:398 -msgctxt "monster" -msgid "Chaoshowler" -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 "" +"Я впевнений, що ти старався як міг, але боюся, що навіть твоєї сили і волі " +"не вистачить. Діабло зараз на піку своєї земної могутності, і тобі " +"знадобиться вся твоя мужність і сила, щоб перемогти його. Нехай Світло " +"оберігає і веде тебе, друже. Я допоможу всім, чим зможу." -#: Source/monstdat.cpp:399 -msgctxt "monster" -msgid "Doomgrin the Rotting" -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 "" +"Якщо відьма не може тобі допомогти і пропонує звернутися Каїна, чому ти " +"думаєш, що я щось знаю? Схоже, справа дуже серйозна. Поспіши і зустрінься з " +"розповідачем, як каже Адрія." -#: Source/monstdat.cpp:400 -msgctxt "monster" -msgid "Madburner" -msgstr "Безумний Пальник" +#. TRANSLATORS: Quest dialog spoken by Pepin (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" +"Я бачу, що це карта зірок на нашому небі, але більше за це я не вмію." -#: Source/monstdat.cpp:401 -msgctxt "monster" -msgid "Bonesaw the Litch" -msgstr "Ліч-Кістопил" +#. 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" +"Каїн дуже добре обізнаний у стародавніх писаннях, а це, безсумнівно, " +"найстаріший папірець, який я коли-небудь бачила." -#: Source/monstdat.cpp:402 -msgctxt "monster" -msgid "Breakspine" -msgstr "Спинолом" +#. 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 "" +"Я ніколи не бачив такої карти. Де ти її взяв? Хоча я поняття не маю, як її " +"читати, можливо Каїн чи Адрія зможуть дати відповіді на твої питання." -#: Source/monstdat.cpp:403 -msgctxt "monster" -msgid "Devilskull Sharpbone" -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 "" +"Слухай сюди, підійди ближче. Не знаю, чи ти знаєш те, що я знаю, але у тебе " +"тут справді щось є. Це карта." -#: Source/monstdat.cpp:404 -msgctxt "monster" -msgid "Brokenstorm" -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 "" +"О, боюся, це не передвіщає нічого хорошого. Ця карта зірок віщує велике " +"лихо, але не мені розказувати її секрети. Настав твій час для дуже серйозної " +"розмови з Розповідачем…" -#: Source/monstdat.cpp:405 -msgctxt "monster" -msgid "Stormbane" -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 "" +"Я шукав карту, але це точно не вона. Ти повинні показати її Адрії — вона, " +"напевно, розповість тобі, що це таке. Я скажу одне; вона виглядає старою, а " +"стара карта зазвичай означає що вона цінна." -#: Source/monstdat.cpp:406 -msgctxt "monster" -msgid "Oozedrool" -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 "" +"Прошууу, не бий. Не вбивай. Залиш живим і наступного разу принесу добро тобі." -#: Source/monstdat.cpp:407 -msgctxt "monster" -msgid "Goldblight of the Flame" -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" +"Візьми це як доказ, що я тримаю слово…" -#: Source/monstdat.cpp:408 -msgctxt "monster" -msgid "Blackstorm" -msgstr "Чорнобуря" +#. 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" +"Без болю і обіцянки я дотримаюся!" -#: Source/monstdat.cpp:409 -msgctxt "monster" -msgid "Plaguewrath" -msgstr "Чумогнів" +#. 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 "Занадто добре для тебе. Дуже могутнє! Хочеш — забери!" -#: Source/monstdat.cpp:410 -msgctxt "monster" -msgid "The Flayer" -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 "" +"Що?! Чому ти тут? Усі цих переривання зведуть мене з розуму. Візьми ось це і " +"дай мені працювати. Не турбуй мене більше!" -#: Source/monstdat.cpp:411 -msgctxt "monster" -msgid "Bluehorn" -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 "Аааа! Товя цікавість обійтеться тобі смертю!!!" -#: Source/monstdat.cpp:412 -msgctxt "monster" -msgid "Warpfire Hellspawn" -msgstr "Виродок Кривовогню" +#. TRANSLATORS: Neutral dialog spoken by Cain +#: Source/textdat.cpp:308 +msgid "Hello, my friend. Stay awhile and listen..." +msgstr "Привіт, мій друже. Залишся і послухай…" -#: Source/monstdat.cpp:413 -msgctxt "monster" -msgid "Fangspeir" -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" +"Читай їх уважно, бо вони розкажуть те, чого навіть я не знаю." -#: Source/monstdat.cpp:414 -msgctxt "monster" -msgid "Festerskull" -msgstr "Гноєчереп" +#. 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 "" +"Я знаю багато міфів і легенд, які можуть мати відповіді на питання, що " +"виникнуть під час твоїх подорожей у Лабіринт. Якщо ти зіткнешся з задачами " +"чи запитаннями, які не знаєш, знайди мене, і я розскажу, що можу." -#: Source/monstdat.cpp:415 -msgctxt "monster" -msgid "Lionskull the Bent" -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 "" +"Грізволд — людина великих дій і великої мужності. Б’юся об заклад, він " +"ніколи не розповідав тобі про те, як пішов у Лабіринт, щоб врятувати Вірта, " +"чи не так? Він знає свою долю небезпек, які там знаходяться, а тепер — і ти. " +"Він вправний майстер, і якщо він стверджує, що може чимось тобі допомогти, " +"то можеш розраховувати на його чесність і майстерність." -#: Source/monstdat.cpp:416 -msgctxt "monster" -msgid "Blacktongue" -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 "" +"Огден володіє та керує Таверною і Корчмою Висхідного Сонця вже майже чотири " +"роки. Він придбав його всього за кілька місяців, перш ніж все тут пійшло до " +"біса. У нього та його дружини Гарди немає грошей, щоб виїхати, оскільки вони " +"вклали все, що мали, щоб створити собі тут життя. Він хороша людина з " +"глибоким почуттям відповідальності." -#: Source/monstdat.cpp:417 -msgctxt "monster" -msgid "Viletouch" -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 "" +"Бідний Фарнхем. Він є тривожним нагадуванням про приречене зборище, яке " +"увійшло до собору з Лазарем того темного дня. Він утік живим, але його " +"хоробрість і глузд залишилися в тій темній ямі. Нині він знаходить втіху " +"лише на дні кухля, але час від часу в його нісенітницях є частинки правди." -#: Source/monstdat.cpp:418 -msgctxt "monster" -msgid "Viperflame" -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 "" +"Відьма Адрія — аномалія тут, у Трістрамі. Вона прибула невдовзі після того, " +"як собор було осквернено, поки більшість інших тікали. Вона здавалося б, за " +"ніч побудувала невелику хатину на околиці міста, і має при собі багато " +"дивних та загадкових артефактів і томів знань, яких навіть я ніколи раніше " +"не бачив." -#: Source/monstdat.cpp:419 -msgctxt "monster" -msgid "Fangskin" -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 "" +"Історія Вірта страшна і трагічна. Маленькі мерзенні демони що мали гострі " +"списи забрали його з рук матері і потягли в лабіринт. Того дня забрали " +"багато інших дітей, разом з сином Короля Леоріка. Лицарі палацу пішли вниз, " +"але так і не повернулися. Коваль знайшов хлопчика, але не раніше, як " +"мерзенні звірі почали катувати його для своїх садистських втіх." -#: Source/monstdat.cpp:420 -msgctxt "monster" -msgid "Witchfire the Unholy" -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 "" +"А, Пепін. Я вважаю його справжнім другом — можливо, найближчим з усіх. Він " +"трохи заплутаний часом, але ніколи не існувало більш турботливої чи уважної " +"душі. Його знання та навички мало кому зрівняються, і його двері завжди " +"відкриті." -#: Source/monstdat.cpp:421 -msgctxt "monster" -msgid "Blackskull" -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 "" +"Гілліан — хороша жінка. Її обожнюють за піднесений настрій і легкий сміх, " +"вона займає особливе місце в моєму серці. Вона залишається в таверні, щоб " +"підтримати свою літню бабусю, яка занадто хвора, щоб подорожувати. Я іноді " +"боюся за її безпеку, але знаю, що будь-який чоловік у селі воліє померти, " +"ніж побачити, як їй нашкодять." -#: Source/monstdat.cpp:422 -msgctxt "monster" -msgid "Soulslash" -msgstr "Душеріз" +#. TRANSLATORS: Neutral dialog spoken by Ogden +#: Source/textdat.cpp:327 +msgid "Greetings, good master. Welcome to the Tavern of the Rising Sun!" +msgstr "Вітаю, добрий майстре. Ласкаво просимо до Таверни Висхідного Сонця!" -#: Source/monstdat.cpp:423 -msgctxt "monster" -msgid "Windspawn" -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 "" +"Багато шукачів пригод сідали за столи моєї таверни, і в десять разів більше " +"історій було розказано, скільки елю було випито . Єдине, що я коли-небудь " +"чув, щоб хтось із них погодився, це стара аксіома. Можливо, вона допоможе " +"тобі. Плоть можна розрізати, але кістки треба трощити." -#: Source/monstdat.cpp:424 -msgctxt "monster" -msgid "Lord of the Pit" -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 "" +"Коваль Грізволд дуже добре обізнаний зі зброєю та обладунками. Якщо тобі " +"коли-небудь знадобиться робота зі спорядженням, тоді звісно, треба ідти до " +"нього." -#: Source/monstdat.cpp:425 -msgctxt "monster" -msgid "Rustweaver" -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 "" +"Фарнхем проводить тут занадто багато часу, топить свої печалі в дешевому " +"елі. Я б змусив його піти, але він все-таки так страждав в Лабіринті." -#: Source/monstdat.cpp:426 -msgctxt "monster" -msgid "Howlingire the Shade" -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" +"Ну, неважливо. Якщо тобі коли-небудь доведеться торгувати зачарованими " +"предметами, вона тримає добре укомплектовану хатину через річку." -#: Source/monstdat.cpp:427 -msgctxt "monster" -msgid "Doomcloud" -msgstr "Хмара Загибелі" +#. 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 "" +"Якщо ти хочеш дізнатися більше про історію села, Розповідач Каїн чимало знає " +"про наше минуле." -#: Source/monstdat.cpp:428 -msgctxt "monster" -msgid "Bloodmoon Soulfire" -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" +"Напевно, він дурів десь, де йому не слід було бути. Мені шкода хлопця, але " +"мені не подобається його компанія." -#: Source/monstdat.cpp:429 -msgctxt "monster" -msgid "Witchmoon" -msgstr "Відьмомісяць" +#. 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 "" +"Пепін — добра людина — і, безперечно, найщедріша в селі. Він завжди " +"піклується про потреби інших, але здається, якісь неприємності супроводжують " +"його, куди б він не пішов…" -#: Source/monstdat.cpp:430 -msgctxt "monster" -msgid "Gorefeast" -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" +"Бог знає, що я благав її піти, сказавши, що буду стежити за старою, але вона " +"занадто мила й турботлива, щоб це зробити." -#: Source/monstdat.cpp:431 -msgctxt "monster" -msgid "Graywar the Slayer" -msgstr "Вбивця Сіробій" +#. TRANSLATORS: Neutral dialog spoken by Pepin +#: Source/textdat.cpp:345 +msgid "What ails you, my friend?" +msgstr "Що турбує тебе, друже?" -#: Source/monstdat.cpp:432 -msgctxt "monster" -msgid "Dreadjudge" -msgstr "Суддя" - -#: Source/monstdat.cpp:433 -msgctxt "monster" -msgid "Stareye the Witch" -msgstr "Відьма Зоряоко" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Я зробив дуже цікаве відкриття. На відміну від нас, істоти в Лабіринті " +"можуть зцілювати себе без допомоги зілля або магії. Якщо ти пораниш монстра, " +"переконайся, що він мертвий, інакше він може відновитися." -#: Source/monstdat.cpp:434 -msgctxt "monster" -msgid "Steelskull the Hunter" -msgstr "Мисливець Сталечереп" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"До того, як Собором заволоділо те, що ховається внизу, він був місцем " +"великого вивчання. Там можна знайти багато книг. Якщо ти знайдеш їх, " +"прочитай їх усі, оскільки деякі з них можуть берегти секрети Лабіринту." -#: Source/monstdat.cpp:435 -msgctxt "monster" -msgid "Sir Gorash" -msgstr "Сір Гораш" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Грізволд знає про мистецтво війни стільки ж, скільки я про мистецтво " +"зцілення. Він хитрий купець, але його робота не має собі рівних. О, я " +"припускаю, що це може тому, що він єдиний коваль, що тут залишився." -#: Source/monstdat.cpp:436 -msgctxt "monster" -msgid "The Vizier" -msgstr "Візир" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Каїн — справжній друг і великий мудрець. У нього велика бібліотека і він має " +"вроджену здатність розпізнати справжню природу речей. Якщо у тебе коли-" +"небудь виникнуть запитання, звертайся до нього." -#: Source/monstdat.cpp:437 -msgctxt "monster" -msgid "Zamphir" -msgstr "Замфір" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Навіть мій досвід не допоміг повністю вилікувати Фарнхема. О, я зміг зцілити " +"його тіло, але його розум і дух за межами моїх знань." -#: Source/monstdat.cpp:438 -msgctxt "monster" -msgid "Bloodlust" -msgstr "Кровожад" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Хоча я використовую деякі обмежені форми магії для створення зілль та " +"еліксирів, Адрія — справжня чарівниця. Здається, вона ніколи не спить, і " +"вона завжди має багато містичних томів і артефактів. Я вважаю, що її хатина " +"набагато більша всередині, ніж ззовні, хоч я ніколи там і не був." -#: Source/monstdat.cpp:439 -msgctxt "monster" -msgid "Webwidow" -msgstr "Павуковдова" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Бідний Вірт. Я зробив для нього все можливе, але знаю, що він зневажає той " +"протез, який я змушений був прикріпити до його ноги. Його рани були " +"жахливими. Ніхто, а особливо такий молодий хлопець, не повинен страждати як " +"він." -#: Source/monstdat.cpp:440 -msgctxt "monster" -msgid "Fleshdancer" -msgstr "Плотетанцівниця" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" +"Я справді не розумію, чому Огден залишається тут, у Трістрамі. Він страждає " +"від легкого нервового збудження, але він розумний і працьовитий чоловік, " +"скрізь досяг би успіху, куди б не пішов. Я припускаю, що це може бути страх " +"перед тими жахіттями, що відбуваються в навколо нашого села, або, можливо, " +"його дружина, не бажає нікуди йти." -#: Source/monstdat.cpp:441 -msgctxt "monster" -msgid "Grimspike" -msgstr "Незламовістря" +#. TRANSLATORS: Neutral dialog spoken by Pepin (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: Unique Monster Block end -#: Source/monstdat.cpp:443 -msgctxt "monster" -msgid "Doomlock" -msgstr "Замок Загибіелі" +#. TRANSLATORS: Neutral dialog spoken by Gillian +#: Source/textdat.cpp:364 +msgid "Good day! How may I serve you?" +msgstr "Добрий день! Чим я можу тобі услужити?" -#: Source/monster.cpp:2965 -msgid "Animal" -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 "" +"Моїй бабусі наснилося, що ти прийдеш і поговориш зі мною. Знаєш, у неї " +"видіння, і вона бачить майбутнє." -#: Source/monster.cpp:2967 -msgid "Demon" -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" +"Потрібна була б така смілива людина, як ти, щоб побачити, що вона там робить." -#: Source/monster.cpp:2969 -msgid "Undead" -msgstr "Нечисть" +#. 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 "" +"Жителі Трістраму гордяться своїм Ковалем. Він не тільки умілий майстер, що " +"переміг у багатьох змаганнях своєї гільдії, але й отримав похвалу від самого " +"нашого Короля Леоріка — нехай його душа спочиває з миром. Грізволд також " +"великий герой; можеш спитати Каїна." -#: Source/monster.cpp:4241 -msgid "Type: {:s} Kills: {:d}" -msgstr "Тип: {:s} Вбито: {:d}" +#. 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 "" +"Каїн був розповідачем Трістраму, скільки я себе пам’ятаю. Він дуже багато " +"знає і може розповісти тобі майже про все." -#: Source/monster.cpp:4243 -msgid "Total kills: {:d}" -msgstr "Всього вбито: {:d}" +#. 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" +"Я знаю, що Пепін, і Огден симпатизують йому, але мене так засмучує, що я " +"бачу, як він щоночі все далі і далі впадає в п'яний дурман." -#: Source/monster.cpp:4275 -msgid "Hit Points: {:d}-{:d}" -msgstr "Життя {:d} з {:d}" +#. 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 "" +"Пепін врятував життя моїй бабусі, і я знаю, що ніколи не зможу йому за це " +"віддячити. Його здатність зцілювати будь-яку хворобу потужніша, ніж " +"наймогутніший меч, і загадковіша, ніж будь-яке заклинання, яке ти можете " +"назвати. Якщо тобі коли-небудь знадобиться зцілення, Пепін тобі допоможе." -#: Source/monster.cpp:4280 -msgid "No magic resistance" -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 "" +"Я виросла з матір'ю Вірта, Кенас. Хоча її лише трохи поранили, коли ті " +"огидні створіння вкрали його, вона так і не одужала. Я думаю, що вона " +"померла через розбите серце. Вірт став підлим юнаком, який прагне лише " +"наживитися на чужому поті. Я знаю, що він страждав і бачив жахи, які навіть " +"я уявити не можу, але частина цієї темряви нависає над ним досі." -#: Source/monster.cpp:4283 -msgid "Resists:" -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 "" +"Огден та його дружина взяли мене та мою бабусю в свій дім і навіть дозволили " +"мені заробити кілька золотих, працюючи в корчмі. Я дуже багато завдячую їм і " +"сподіваюся, що колись покину це місце і допоможу їм відкрити грандіозний " +"готель на сході." -#: Source/monster.cpp:4285 Source/monster.cpp:4295 -msgid " Magic" -msgstr " Магія" +#. TRANSLATORS: Neutral dialog spoken by Griswold +#: Source/textdat.cpp:381 +msgid "Well, what can I do for ya?" +msgstr "Ну, що я можу тобі зробити?" -#: Source/monster.cpp:4287 Source/monster.cpp:4297 -msgid " Fire" -msgstr " Вогонь" - -#: Source/monster.cpp:4289 Source/monster.cpp:4299 -msgid " Lightning" -msgstr " Блискавка" - -#: Source/monster.cpp:4293 -msgid "Immune:" -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 "" +"Якщо ти шукаєш добру зброю, дозволь мені показати тобі це. Наприклад, візьми " +"якусь тупу булаву. Проти більшості тих невмирущих жахів піде як по маслу, " +"немає нічого кращого, щоб розбивати сухі скелети!" -#: Source/monster.cpp:4310 -msgid "Type: {:s}" -msgstr "Тип: {:s}" +#. 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 "" +"Сокира? Так, це хороша зброя, збалансована проти будь-якого ворога. " +"Подивись, як вона розсікає повітря, а потім уяви собі гарну товстенну голову " +"демона на її шляху. Май на увазі, що вона повільно розмахується — але який " +"удар!" -#: Source/monster.cpp:4315 Source/monster.cpp:4321 -msgid "No resistances" -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 "" +"Подивись на це лезо, на рівновагу. У правильних руках і проти правильного " +"ворога, меч — майстер усієї зброї. Гострому лезу мало чого рубати чи " +"прорізати в нежиті, але проти живого, дихаючого ворога, немає нічого краще, " +"ніж меч!" -#: Source/monster.cpp:4316 Source/monster.cpp:4325 -msgid "No Immunities" -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 "" +"Твоя зброя та броня показуватимуть ознаки боротьби з Темрявою. Якщо ти " +"принесеш їх мені, трохи попрацювавши в гарячій кузні, я зроблю їх як " +"новенькі." -#: Source/monster.cpp:4319 -msgid "Some Magic Resistances" -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 "" +"Мені доводиться практично провозити контрабандою потрібні мені метали та " +"інструменти з караванів, що огинають околиці нашого проклятого міста, а ця " +"відьма Адрія завжди отримує все, що їй потрібно. Якби я б хоч трохи знав як " +"приборкати магію, як вона, я б зробив справді неймовірні речі." -#: Source/monster.cpp:4323 -msgid "Some Magic Immunities" -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 "" +"Гілліан — гарна дівчина. Шкода, що в її бабці таке погане здоров’я, інакше я " +"б влаштував, щоб вивезти їх звідси на торговому каравані." -#: Source/mpq/mpq_writer.cpp:161 -msgid "Failed to open archive for writing." -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 "" +"Іноді мені здається, що Каїн забагато говорить, але схоже, що це його " +"покликання в житті. Якби я міг гнути сталь так само, як він може гнути твоє " +"вухо, я б зробив лати, достойні Імператора!" -#: Source/msg.cpp:770 -msgid "Trying to drop a floor item?" -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 "" +"Я був з Фарнхемом тоді, коли Лазар повів нас у Лабіринт. Я більше ніколи не " +"бачив Архієпископа, і, можливо б не вижив, якби Фарнхем не був поруч зі " +"мною. Я боюся, що напад залишив його душу так само покаліченою, як і мою " +"ногу. Я бився б за нього в цій битві якби міг, але не можу." -#: Source/msg.cpp:1374 -msgid "{:s} has cast an invalid spell." -msgstr "{:s} зворожував заборонені чари." +#. 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 "" +"Хороший чоловік, що ставить потреби інших вище своїх. Ти не знайдеш нікого в " +"Трістрамі — тай й де завгодно, якщо на те пішло — хто б сказав погане про " +"цілителя." -#: Source/msg.cpp:1378 -msgid "{:s} has cast an illegal spell." -msgstr "{:s} зворожував заборонені чари." +#. 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 "" +"Цей хлопець потрапить в серйозні неприємності… або, я мушу сказати, знову " +"потрапить. Я намагався зацікавити його роботою тут, в кузні, навчити чесному " +"ремеслу, але він віддає перевагу високим прибуткам від торгівлі сумнівними " +"товарами. Я не можу злитися на нього після того, що з ним сталося, але я " +"хотів, щоб він хоча б був обережний." -#: Source/msg.cpp:2028 Source/multi.cpp:797 Source/multi.cpp:848 -msgid "Player '{:s}' (level {:d}) just joined the game" -msgstr "Гравець '{:s}' (рівень {:d}) приєднався до гри" +#. 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 "" +"У корчмаря мало бізнесу і немає реального способу заробляти. Йому вдається " +"звести кінці з кінцями, забезпечуючи їжею та житлом тих, хто час від часу " +"ходить через село, але вони можуть втекти, не плативши. Якби не запаси зерна " +"та в’яленого м’яса, які він тримав у своєму погребі, то чому б більшість із " +"нас померли б від голоду в той перший рік, коли всю сільську місцевість " +"заполонили демони." -#: Source/msg.cpp:2395 -msgid "The game ended" -msgstr "Гра закінчилась" +#. TRANSLATORS: Neutral dialog spoken by Farnham +#: Source/textdat.cpp:404 +msgid "Can't a fella drink in peace?" +msgstr "Хіба я не можу спокійно випити?" -#: Source/msg.cpp:2401 -msgid "Unable to get level data" -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 "Дівчина, що приносить напої? О, так, яка гарна жінка. І добра теж." -#: Source/multi.cpp:262 -msgid "Player '{:s}' just left the game" -msgstr "Гравець '{:s}' покинув гру" +#. 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 "" +"Чому б та стара карга щось не зробила. Звісно, звісно, у неї є речі, але " +"послухай мене… вона неприродня. Я ніколи не бачив, щоб вона їла чи пила — і " +"не можна вірити тому, хто хоч трохи не п’є." -#: Source/multi.cpp:265 -msgid "Player '{:s}' killed Diablo and left the game!" -msgstr "Гравець '{:s}' вбив Діабло і покинув гру!" +#. 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 "" +"Каїн не той, ким себе видає. Звичайно, звичайно, він розказує хороші " +"історії… деякі з них справді страшні або смішні… але я думаю, що він знає " +"більше, ніж знає, що знає." -#: Source/multi.cpp:269 -msgid "Player '{:s}' dropped due to timeout" -msgstr "Гравець '{:s}' відключився через таймаут" +#. 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 "" +"Грізволд? Старий добрий Грізволд. Я люблю його як брата! Ми билися разом, " +"знаєш, коли… ми… Лазар… Лазар… Лазар!!!" -#: Source/multi.cpp:850 -msgid "Player '{:s}' (level {:d}) is already in the game" -msgstr "Гравець '{:s}' (рівень {:d}) вже в грі" +#. 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: Shrine Name Block -#: Source/objects.cpp:121 -msgid "Mysterious" -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 "" +"У Вірта більше проблем, ніж у мене, а я все знаю про проблеми. Слухай сюди — " +"у того пацана добра угода, але він там був, розумієш? Втратив ногу! Треба " +"ходити на шматку дерева. Так сумно, так сумно…" -#: Source/objects.cpp:122 -msgid "Hidden" -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 "" +"Огден — найкращий чоловік у селі. Не думаю, що я дуже подобаюся його " +"дружині, але поки вона буде відкривати бочки, вона нормальна. Я проводжу з " +"Огденом більше часу, ніж всі, але він такий добрий до мене…" -#: Source/objects.cpp:123 -msgid "Gloomy" -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 "" +"Хочу тобі дещо сказати, бо я знаю все про ці штуки. Я в цьому знаюсь. Цей " +"ель ось тут найкращий…наааааайкращий! Той інший не годиться з тих пір, як " +"дурні собаки…" -#: Source/objects.cpp:125 Source/objects.cpp:132 -msgid "Magical" -msgstr "Магічний" - -#: Source/objects.cpp:126 -msgid "Stone" -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 "" +"Мене ніхто ніколи не слу… слухає. Десь – я не знаю де, – але десь під " +"церквою є ціла купа золота. Блищить і сяє, і я просто чекаю, щоб хтось її " +"забрав." -#: Source/objects.cpp:127 -msgid "Religious" -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 "" +"Я знаю, що у тебе свої ідеї, і я знаю, що ти в це не повіриш, але та зброя, " +"що в тебе просто не годиться проти тих нелюдів! О, мені байдуже, що говорить " +"Грізволд, вони нічого не можуть зробити, як у старі часи…" -#: Source/objects.cpp:128 -msgid "Enchanted" -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 "" +"Якби я був тобою… а я не ти… але якби був, я б продав усе, що в тебе є, і " +"пішов звідси. Той хлопець ось там… У нього завжди є щось хороше, але тобі " +"треба дати йому трохи золота, інакше він навіть не покаже, що у нього є." -#: Source/objects.cpp:129 -msgid "Thaumaturgic" -msgstr "Чудотворний" +#. TRANSLATORS: Neutral dialog spoken by Adria +#: Source/textdat.cpp:427 +msgid "I sense a soul in search of answers..." +msgstr "Я відчуваю душу в пошуках відповідей…" -#: Source/objects.cpp:130 -msgid "Fascinating" -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 "" +"Мудрість заробляється, а не дається. Якщо ти знайдеш том знань, поглинай " +"його слова. Якщо ти вже знаєш про таємні секрети, записані в книзі, то " +"пам’ятай — рівень майстерності завжди може підвищитися." -#: Source/objects.cpp:131 -msgid "Cryptic" -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 "" +"Найбільша сила часто триває найкоротше. Ти можеш знайти стародавні слова " +"сили, написані на пергаментних сувоях. Сила цих сувоїв полягає тому, що і " +"учень, і адепт чарує їх з однаковим умінням. Їх слабкість полягає в тому, що " +"їх треба прочитати вголос, і неможливо втримати в голові. Також знай, що " +"такі сувої можна прочитати лише раз, тому використовуй їх обережно." -#: Source/objects.cpp:133 -msgid "Eldritch" -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 "" +"Хоча сонячна спека безмірна, просте полум’я свічки становить більшу " +"небезпеку. Жодна енергія, якою б великою вона не була, не може бути " +"використана без належного зосередження. Для багатьох заклинань зачаровані " +"Посохи можна заряджати магічною енергією багато раз. Я можу відновити їхню " +"силу - але знай, що нічого не робиться безкоштовно." -#: Source/objects.cpp:134 -msgid "Eerie" -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 "" +"Сума наших знань — у сумі її людей. Якщо ти знайдеш книгу чи сувій, що ти не " +"можете розшифрувати, не соромся принести його мені. Я поділюся тим, що " +"знайшла, якщо я можу зрозуміти їх." -#: Source/objects.cpp:135 -msgid "Divine" -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 "" +"Для людини, що знає лише Залізо, немає більшої магії, ніж Сталь. Коваль " +"Грізволд більше чаклує, ніж знає. Його здатності об'єднувати вогонь і метал " +"не має рівних на цій землі." -#: Source/objects.cpp:137 -msgid "Sacred" -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 "" +"Псування має силу обману, але невинність володіє могутністю чистоти. Молода " +"жінка Гілліан має чисте серце, ставить потреби свого матріарха над своїми. " +"Вона боїться мене, але тільки тому, що не розуміє мене." -#: Source/objects.cpp:138 -msgid "Spiritual" -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 "" +"Скриня, відкрита в темряві, не має більше скарбу, ніж коли її відкрили на " +"світлі. Розповідач Каїн — загадкова людина, але тільки для тих, хто не " +"дивиться. Його знання про те, що лежить під собором, набагато більше, ніж " +"навіть він собі уявляє." -#: Source/objects.cpp:139 -msgid "Spooky" -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 "" +"Чим більше ти довіряєш одній людині, тим гірше розчарування. Фарнхем втратив " +"свою душу, але не від якогось демона. Вона була втрачена, коли він побачив " +"своїх односельчан, зраджених Архієпископом Лазарем. У нього є цінні знання, " +"але ти маєш відокремити факти від вигадок." -#: Source/objects.cpp:140 -msgid "Abandoned" -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 "" +"Рука, серце і розум можуть творити чудеса, коли вони в повній гармонії. " +"Цілитель Пепін бачить тіло так, як навіть я не можу. Його здатність цілити " +"хворих і поранених посилюється його розумінням варіння еліксирів і зілля. " +"Він твій найкращий союзник в Трістрамі." -#: Source/objects.cpp:141 -msgid "Creepy" -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 "" +"Є багато, чого ми не можемо побачити в майбутньому, але коли воно настане, " +"ним володітимуть діти. У хлопчика Вірта темнота на душі, але він не " +"становить загрози ні місту, ні його жителям. Його таємні стосунки з " +"шибениками та негласними гільдіями сусідніх міст дають йому доступ до " +"багатьох пристроїв, що нелегко знайти в Трістрамі. Хоча його методи можуть " +"бути докірливими, Вірт може допомогти тобі в боротьбі з Темрявою, що " +"вторгається." -#: Source/objects.cpp:142 -msgid "Quiet" -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 "" +"Земляні стіни та солом'яний навіс не зроблять дому. Корчмар Огден грає " +"більшу роль у місті, ніж багато хто розуміє. Він надає притулок для Гілліан " +"і її матріарха, підтримує те життя, що залишилось у Фарнхема, і є якорем для " +"всіх, хто залишився в місті, яким колись був Трістрам. Його таверна та " +"прості задоволення, які ще можна знайти там, дають хоч на мить побачити " +"життя, що було раніше. Саме ця пам’ять продовжує живити їхні надії на твій " +"успіх." -#: Source/objects.cpp:143 -msgid "Secluded" -msgstr "Відлюдний" +#. TRANSLATORS: Neutral dialog spoken by Wirt +#: Source/textdat.cpp:450 +msgid "Pssst... over here..." +msgstr "Псс… сюди…" -#: Source/objects.cpp:144 -msgid "Ornate" -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" +"Іноді тільки ти зможеш знайти ціль для деяких речей." -#: Source/objects.cpp:145 -msgid "Glimmering" -msgstr "Блимаючий" +#. 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 "" +"Не вір всьому, що говорить п'яниця. Забагато елю затуманило його зір і " +"здоровий глузд." -#: Source/objects.cpp:146 -msgid "Tainted" -msgstr "Засмерділий" - -#: Source/objects.cpp:147 -msgid "Oily" -msgstr "Масляний" - -#: Source/objects.cpp:148 -msgid "Glowing" -msgstr "Світний" - -#: Source/objects.cpp:149 -msgid "Mendicant's" -msgstr "Жебрака" - -#: Source/objects.cpp:150 -msgid "Sparkling" -msgstr "Іскристий" - -#: Source/objects.cpp:152 -msgid "Shimmering" -msgstr "Мерехтливий" - -#: Source/objects.cpp:153 -msgid "Solar" -msgstr "Сонячний" - -#. TRANSLATORS: Shrine Name Block end -#: Source/objects.cpp:155 -msgid "Murphy's" -msgstr "Мерфі" - -#. TRANSLATORS: Book Title -#: Source/objects.cpp:285 -msgid "The Great Conflict" -msgstr "Великий Конфлікт" - -#. TRANSLATORS: Book Title -#: Source/objects.cpp:286 -msgid "The Wages of Sin are War" -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: Book Title -#: Source/objects.cpp:287 -msgid "The Tale of the Horadrim" -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: Book Title -#: Source/objects.cpp:288 -msgid "The Dark Exile" -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: Book Title -#: Source/objects.cpp:289 -msgid "The Sin War" -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: Book Title -#: Source/objects.cpp:290 -msgid "The Binding of the Three" -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: Book Title -#: Source/objects.cpp:291 -msgid "The Realms Beyond" -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: Book Title -#: Source/objects.cpp:292 -msgid "Tale of the Three" -msgstr "Оповідь про Трьох" +#. 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: Book Title -#: Source/objects.cpp:293 -msgid "The Black King" -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: Book Title -#: Source/objects.cpp:294 -msgid "Journal: The Ensorcellment" -msgstr "Щоденник: Чаклунство" +#. TRANSLATORS: Quest text spoken aloud from a book by player +#: Source/textdat.cpp:471 Source/textdat.cpp:479 Source/textdat.cpp:487 +#: Source/textdat.cpp:517 Source/textdat.cpp:525 +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: Book Title -#: Source/objects.cpp:295 -msgid "Journal: The Meeting" -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:519 Source/textdat.cpp:527 +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: Book Title -#: Source/objects.cpp:296 -msgid "Journal: The Tirade" -msgstr "Щоденник: Тирада" +# TBD: Make this rhyme +#. 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:521 Source/textdat.cpp:529 +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: Book Title -#: Source/objects.cpp:297 -msgid "Journal: His Power Grows" -msgstr "Щоденник: Його Сила Росте" +#. TRANSLATORS: Quest text spoken aloud from a book by player +#: Source/textdat.cpp:477 Source/textdat.cpp:485 Source/textdat.cpp:493 +#: Source/textdat.cpp:523 Source/textdat.cpp:531 +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 Title -#: Source/objects.cpp:298 -msgid "Journal: NA-KRUL" -msgstr "Щоденник: НА-КРУЛ" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:497 +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 Title -#: Source/objects.cpp:299 -msgid "Journal: The End" -msgstr "Щоденник: Кінець" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:499 +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 Title -#: Source/objects.cpp:300 -msgid "A Spellbook" -msgstr "Книга Чар" - -#: Source/objects.cpp:4885 -msgid "Crucified Skeleton" -msgstr "Розп'ятий Скелет" - -#: Source/objects.cpp:4889 -msgid "Lever" -msgstr "Важіль" - -#: Source/objects.cpp:4899 -msgid "Open Door" -msgstr "Відкриті Двері" - -#: Source/objects.cpp:4901 -msgid "Closed Door" -msgstr "Закриті Двері" - -#: Source/objects.cpp:4903 -msgid "Blocked Door" -msgstr "Заблоковані Двері" - -#: Source/objects.cpp:4908 -msgid "Ancient Tome" -msgstr "Стародавній Том" - -#: Source/objects.cpp:4910 -msgid "Book of Vileness" -msgstr "Кинга Підлості" - -#: Source/objects.cpp:4915 -msgid "Skull Lever" -msgstr "Черепний Важіль" - -#: Source/objects.cpp:4917 -msgid "Mythical Book" -msgstr "Міфічна Книга" - -#: Source/objects.cpp:4920 -msgid "Small Chest" -msgstr "Маленька Скриня" - -#: Source/objects.cpp:4923 -msgid "Chest" -msgstr "Скриня" - -#: Source/objects.cpp:4927 -msgid "Large Chest" -msgstr "Велика Скриня" - -#: Source/objects.cpp:4930 -msgid "Sarcophagus" -msgstr "Саркофаг" - -#: Source/objects.cpp:4932 -msgid "Bookshelf" -msgstr "Полиця з Книгами" - -#: Source/objects.cpp:4935 -msgid "Bookcase" -msgstr "Шкаф з Книгами" - -#: Source/objects.cpp:4938 -msgid "Barrel" -msgstr "Бочка" - -# How does the pod look like? -#: Source/objects.cpp:4941 -msgid "Pod" -msgstr "Банка" - -#: Source/objects.cpp:4944 -msgid "Urn" -msgstr "Урна" - -#. TRANSLATORS: {:s} will be a name from the Shrine block above -#: Source/objects.cpp:4947 -msgid "{:s} Shrine" -msgstr "{:s} Вівтар" - -#: Source/objects.cpp:4949 -msgid "Skeleton Tome" -msgstr "Скелетний Том" - -#: Source/objects.cpp:4951 -msgid "Library Book" -msgstr "Бібліотечна Книга" - -#: Source/objects.cpp:4953 -msgid "Blood Fountain" -msgstr "Кривавий Фонтан" - -#: Source/objects.cpp:4955 -msgid "Decapitated Body" -msgstr "Обезглавлений Труп" - -#: Source/objects.cpp:4957 -msgid "Book of the Blind" -msgstr "Книга Сліпих" - -#: Source/objects.cpp:4959 -msgid "Book of Blood" -msgstr "Книга Крові" - -#: Source/objects.cpp:4961 -msgid "Purifying Spring" -msgstr "Очищальне Джерело" - -#: Source/objects.cpp:4966 Source/objects.cpp:4983 -msgid "Weapon Rack" -msgstr "Стелаж зі Зброєю" - -#: Source/objects.cpp:4968 -msgid "Goat Shrine" -msgstr "Вівтар Кози" - -#: Source/objects.cpp:4970 -msgid "Cauldron" -msgstr "Казан" - -#: Source/objects.cpp:4972 -msgid "Murky Pool" -msgstr "Мутний Ставок" - -#: Source/objects.cpp:4974 -msgid "Fountain of Tears" -msgstr "Фонтан Сліз" - -#: Source/objects.cpp:4976 -msgid "Steel Tome" -msgstr "Сталевий Том" - -#: Source/objects.cpp:4978 -msgid "Pedestal of Blood" -msgstr "П'єдестал Крові" - -#: Source/objects.cpp:4985 -msgid "Mushroom Patch" -msgstr "Грибна Галявина" - -#: Source/objects.cpp:4987 -msgid "Vile Stand" -msgstr "Огидний Стелаж" - -#: Source/objects.cpp:4989 -msgid "Slain Hero" -msgstr "Вбитий Герой" - -#. TRANSLATORS: {:s} will either be a chest or a door -#: Source/objects.cpp:5001 -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:5006 -msgid "{:s} (disabled)" -msgstr "{:s} (не активно)" - -#: Source/options.cpp:455 Source/options.cpp:580 Source/options.cpp:586 -msgid "ON" -msgstr "УВІМК" - -#: Source/options.cpp:455 Source/options.cpp:578 Source/options.cpp:584 -msgid "OFF" -msgstr "ВИМК" - -#: Source/options.cpp:568 -msgid "Start Up" -msgstr "Запуск" - -#: Source/options.cpp:568 -msgid "Start Up Settings" -msgstr "Налаштування Запуску" - -#: Source/options.cpp:569 -msgid "Game Mode" -msgstr "Режим Гри" - -#: Source/options.cpp:569 -msgid "Play Diablo or Hellfire." -msgstr "Грати в Diablo або в Hellfire." - -#: Source/options.cpp:575 -msgid "Restrict to Shareware" -msgstr "Обмежити гру до Демо" - -#: Source/options.cpp:575 +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:501 msgid "" -"Makes the game compatible with the demo. Enables multiplayer with friends " -"who don't own a full copy of Diablo." +"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 "" -"Робить гру сумісною з демо-версією. Дозволяє гру по мережі з друзями у яких " -"немає повної копії Diablo." - -#: Source/options.cpp:576 Source/options.cpp:582 -msgid "Intro" -msgstr "Ролик" - -#: Source/options.cpp:576 Source/options.cpp:582 -msgid "Shown Intro cinematic." -msgstr "Показати початковий ролик." - -#: Source/options.cpp:588 -msgid "Splash" -msgstr "Заставка" - -#: Source/options.cpp:588 -msgid "Shown splash screen." -msgstr "Показана заставка." - -#: Source/options.cpp:590 -msgid "Logo and Title Screen" -msgstr "Логотип і Початковий Екран" - -#: Source/options.cpp:591 -msgid "Title Screen" -msgstr "Початковий Екран" - -#: Source/options.cpp:610 -msgid "Diablo specific Settings" -msgstr "Налаштування Diablo" - -#: Source/options.cpp:624 -msgid "Hellfire specific Settings" -msgstr "Настройки Hellfire" +"Стережись і свідчи про істини, які тут лежать, бо вони є останньою спадщиною " +"Хорадріма. Майже триста років тому стало відомо, що три головні зла " +"палаючого пекла таємничим чином прийшли в наш світ. Три брати десятиліттями " +"спустошували землі сходу, а людство тремтіло від них. Наш Орден – Хорадрім – " +"був заснований групою потайливих магів, щоб вистежити та захопити Три Зла " +"раз і назавжди.\n" +" \n" +"Оригінальний Хорадрім захопив два з Трьох Зол у потужних артефактах, відомих " +"як Камені душі, і поховав їх глибоко під пустельними східними пісками. Третє " +"Зло уникнуло полону і втекло на захід разом із багатьма Хорадрімами в " +"погоні. Третє Зло - відоме як Діабло, Володар Жаху - врешті-решт був " +"схоплений, його сутність поміщена в Камінь душі та похований в цьому " +"Лабіринті.\n" +" \n" +"Будьте попереджені, що камінь душі слід утримувати від відкриття тими, хто " +"не вірить. Якщо Діабло звільниться, він буде шукати тіло, яким легко " +"керувати, оскільки він дуже слабкий — можливо, тіло старого або дитини." -#: Source/options.cpp:638 -msgid "Audio" -msgstr "Аудіо" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:503 +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 "" +"Так сталося, що в Палаючих Пеклах відбулася велика революція, відома як " +"Темне Заслання. Менші Зла повалили Три Перші Зла і вигнали їхні духовні " +"форми в вимір смертних. Демони Беліал (Володар Брехні) і Азмодан (Володар " +"Гріха) боролися за правління Пекла під час відсутності Трьох братів. Усе " +"Пекло поділилося між фракціями Беліала та Азмодана, в той час як сили " +"Високих Небес безперервно били по самих Воротах Пекла." -#: Source/options.cpp:638 -msgid "Audio Settings" -msgstr "Налаштування Аудіо" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:505 +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 "" +"Багато демонів подорожували в вимір смертних у пошуках Трьох Братів. За ними " +"слідували ангели, що полювали на них у великих містах Сходу. Ангели вступили " +"в союз з таємним орденом смертних магів, що зветься Хорадрім, які швидко " +"стали досвідченими в полюванні на демонів. Також вони нажили собі багато " +"темних ворогів у підземних світах." -#: Source/options.cpp:641 -msgid "Walking Sound" -msgstr "Звук Ходьби" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:507 +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" +"Врешті-решт Хорадрім також захопив Діабло в Камені Душі і поховав його під " +"стародавнім, забутим собором. Там Володар Жаху спить і чекає часу свого " +"відродження. Знайте, що він буде шукати молоде і сильне тіло, яким можна " +"володіти, — тіло, що є невинним і яким легко керувати. Тоді він встане, щоб " +"звільнити своїх братів і ще раз розпалити полум'я Війни Гріхів…" -#: Source/options.cpp:641 -msgid "Player emits sound when walking." -msgstr "При ходьбі гравець видає звук." +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:509 +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 "" +"Всі похвали Діабло - Лорду Жаху і Тому, Хто Вижив Темне Заслання. Коли він " +"прокинувся від довгого сну, мій Володар і Вчитель розповів мені про " +"таємниці, які небагато смертних знають. Він сказав мені, що царства Високих " +"Небес і палаючі ями Пекла, воюють у вічній війні. Він розкрив сили, які " +"принесли цей розбрат у царства людей. Мій повелитель назвав битву за цей " +"світ і всіх, хто в ньому існує, Війною Гріхів." -#: Source/options.cpp:642 -msgid "Auto Equip Sound" -msgstr "Звук Автоспорядження" +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:511 +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 "" +"Слава і Хвала Діабло - Повелителю Жаху і Лідеру Трьох. Мій Повелитель " +"говорив мені про своїх братів, Мефісто і Ваала, які давно були вигнані в цей " +"світ. Мій Володар хоче дочекатися і використати свою дивовижну силу, щоб він " +"міг звільнити своїх полонених братів з їх гробниць під пісками сходу. Як " +"тільки мій Повелитель звільнить своїх братів, Війна гріхів знову пізнає лють " +"Трьох." -#: Source/options.cpp:642 -msgid "Automatically equipping items on pickup emits the equipment sound." -msgstr "Автоматичне спорядження предметів видає звук спорядження." +#. TRANSLATORS: Book read aloud +#: Source/textdat.cpp:513 +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 "" +"Радуйся і принеси жертву Діабло - Повелителю Жаху і Винищувачу Душ. Коли я " +"розбудив свого Вчителя від сну, він спробував заволодіти смертним тілом. " +"Діабло намагався заволодіти тілом Короля Леоріка, але мій господар був " +"занадто слабкий від ув’язнення. Моєму Повелителю був потрібен простий і " +"невинний якір у цьому світі, і він знайшов, що хлопчик Альбрехт ідеально " +"підходить для цього. У той час як добрий Король Леорік збожеволів від " +"невдалої спроби вселитись в нього, я викрав його сина Альбрехта і привів " +"його до свого господаря. Тепер я чекаю поклику Діабло і молюся, щоб мене " +"винагородили, коли він нарешті стане Володарем цього світу." -#: Source/options.cpp:643 -msgid "Item Pickup Sound" -msgstr "Звук Підбору Предмету" +#. TRANSLATORS: Neutral Text spoken by Ogden +#: Source/textdat.cpp:515 +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" +"Багато чого змінилося з тих пір, як ти тут жив, друже. Усе було мирно, поки " +"не прийшли темні вершники і не знищили наше село. Багатьох вирубали там, де " +"вони стояли, а тих, хто брався за зброю, вбивали або тялги на работоргівлю — " +"а то і гірше. Церква на околиці міста була осквернена і використовується для " +"темних ритуалів. Уночі лунають нелюдські крики, але дехто з городян ще може " +"вижили. Йди стежкою, що лежить між моєю таверною і ковальською майcтернею, " +"щоб знайти церкву і врятуй кого зможеш.\n" +" \n" +"Я розповім більше, коли ми знову поговоримо. Удачі." -#: Source/options.cpp:643 -msgid "Picking up items emits the items pickup sound." -msgstr "Підбір предметів видає звук підбору." +#. TRANSLATORS: Quest text spoken by Adria +#: Source/textdat.cpp:533 +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 "" +"Продовжуй свої звідини. Знайти втрачений скарб непросто. Знайти схований " +"скарб ще важче. Дам тобі ось таку пораду. Не дозволяй піскам часу " +"заплутати твої пошуки." -#: Source/options.cpp:644 -msgid "Sample Rate" -msgstr "Частота Дискретизації" +#. TRANSLATORS: Quest text spoken by Griswold +#: Source/textdat.cpp:535 +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 "" +"Що?! Це дурість. Тут, у Трістрамі, немає скарбів. Дай мені подивитись!! " +"О, подивись, малюнки тут неточні. Вони зовсім не відповідають нашому " +"місту. Я б мав на увазі те, що під собором, а не те, що лежить під нашим " +"ґрунтом." -#: Source/options.cpp:644 -msgid "Output sample rate (Hz)." -msgstr "Частота дискретизації звуку (в Гц)" +#. TRANSLATORS: Quest text spoken by Pepin +#: Source/textdat.cpp:537 +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 "" +"У мене немає часу говорити про якусь карту, що ти шукаєш. У мене багато " +"хворих, яким потрібна моя і твоя допомога." -#: Source/options.cpp:645 -msgid "Channels" -msgstr "Канали" +#. TRANSLATORS: Quest text spoken by Adria +#: Source/textdat.cpp:539 Source/textdat.cpp:551 +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 "" +"Колись гордий Ісволл піймався в пастку глибоко під землею. Його позбавили " +"честі і змінили обличчя. Його захопили в пастку безкінечних мук. " +"Призначений ховати саме те, що могло б його звільнити." -#: Source/options.cpp:645 -msgid "Number of output channels." -msgstr "Кількість каналів звуку." +#. TRANSLATORS: Quest text spoken by Ogden +#: Source/textdat.cpp:541 +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 "" +"Б’юся об заклад, що Вірт побачив, як ти ідеш, і прикинувся лише для того, " +"щоб потім сміятися з тебе, як ти бігаєш по місту носом в грунт. Я б його " +"проігнорував." -#: Source/options.cpp:646 -msgid "Buffer Size" -msgstr "Розмір Буферу" +#. TRANSLATORS: Quest text spoken by Cain +#: Source/textdat.cpp:543 +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 "" +"Був час, коли це місто було частою зупинкою для мандрівників звідусіль. З " +"тих пір багато чого змінилося. Але приховані печери та закопані скарби – " +"звичайні фантазії будь-якої дитини. Вірт іноді вдається до юнацьких ігор. " +"Так що це може бути просто його уява." -#: Source/options.cpp:646 -msgid "Buffer size (number of frames per channel)." -msgstr "Розмір буферу (кількість кадрів на канал)." +#. TRANSLATORS: Quest text spoken by Farnham +#: Source/textdat.cpp:545 +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 "" +"Слухай сюди. Підійди ближче. Не знаю, чи ти знаєш те, що я знаю, але у " +"тебе тут справді щось є. Це карта." -#: Source/options.cpp:647 -msgid "Resampling Quality" -msgstr "Якість Семплування" +#. TRANSLATORS: Quest text spoken by Gillian +#: Source/textdat.cpp:547 +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 "" +"Моя бабуся часто розповідає мені історії про дивні сили, які населяють " +"цвинтар біля церкви. Може тобі було б цікаво почути одну з них. Вона " +"сказала, що якби ти залишиш належне подання на цвинтарі, увійдеш до собору, " +"щоб помолитися за померлих, а потім повернешся, то подання якимось дивним " +"чином зміниться. Я не знаю, чи це лише маразм старої хворої жінки, але " +"сьогодні все здається можливим." -#: Source/options.cpp:647 -msgid "Quality of the resampler, from 0 (lowest) to 10 (highest)." -msgstr "Якість семлеру, від 0 (найнижча) до 10 (найвища)." +#. TRANSLATORS: Quest text spoken by Wirt +#: Source/textdat.cpp:549 +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 "" +"Хммм. Значить, величезний і таємничий скарб. Мммм. Можливо, мені було б " +"цікаво взяти в тебе кілька речей. Або ще краще, хіба тобі не потрібні " +"рідкісні та дорогі припаси, щоб пройти це випробування?" -#: Source/options.cpp:678 -msgid "Resolution" -msgstr "Роздільна Здатність" +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:553 +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 "" +"Отже, ти герой, про якого всі говорили. Можливо, ти міг би допомогти " +"бідному, простому фермеру вийти з жахливої ситуації? На півдні звідси, на " +"краю мого саду, з землі надулася якась жахлива річ! Я не можу дістатися до " +"своїх посівів чи тюків сіна, і мої бідні корови будуть голодувати. Відьма " +"дала ось це мені і сказала, що це підірве ту штуку з мого поля. Якби ти міг " +"її знищити, я був би навіки вдячний. Я б це зробив сам, але хтось має " +"залишитися тут з коровами…" -#: Source/options.cpp:678 +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:555 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." +"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 "" -"Діє на роздільну здатність гри і визначає видиму область. Примітка: " -"Налаштування може відрізнятись від роздільної здатності монітору коли " -"використовуються \"Збільшення Зображення\", \"Коефіцієнт Масштабування\" або " -"\"Налаштувати по Екрану\"." +"Я знав, що все не може бути так просто, як казала та відьма. Це сумний світ, " +"коли ти не можеш довіряти навіть своїм сусідам." -#: Source/options.cpp:825 -msgid "Resampler" -msgstr "Cемплер" +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:557 +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 "" +"Вона зникло? Ти відправив її назад до темних закутків Аїда, що породили " +"його? Що? О, тільки не кажи мені, що ти її не знайшов! Такі речі коштують " +"недешево. Ти маєш знайти її, а потім підірвати ту жахливу річ." -#: Source/options.cpp:825 -msgid "Audio resampler" -msgstr "Аудіо cемплер" +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:559 +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 "" +"Я почув вибух звідти! Велике тобі спасибі, добрий незнайомець. Що з усіма " +"цими речами, що виходять із землі, монстрами, що захоплюють церкву, і так " +"далі, це важкі часи. Я лише бідний фермер, але ось — візьми це, і мою велику " +"подяку." -#: Source/options.cpp:882 -msgid "Device" -msgstr "Пристрій" +#. TRANSLATORS: Neutral text spoken by Farmer (Gossip) +#: Source/textdat.cpp:561 +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 "" +"Ой, така в мене біда... може... Ні, я не можу нав'язуватись тобі, коли у " +"тебе інші неприємності. Може, після того, як ти очистиш церкву від тих " +"створінь, ти повернешся... і виділиш трохи часу, щоб допомогти бідному " +"фермеру?" -#: Source/options.cpp:882 -msgid "Audio device" -msgstr "Пристрій аудіо" +# I you find a better onomatopeia of crying in Ukrainian, I'm all ears +#. TRANSLATORS: Quest text spoken by Little Girl +#: Source/textdat.cpp:563 +msgid "Waaaah! (sniff) Waaaah! (sniff)" +msgstr "Гуууу! (хлип) Гуууу! (хлип)" -#: Source/options.cpp:950 -msgid "Graphics" -msgstr "Графіка" +#. TRANSLATORS: Quest text spoken by Little Girl +#: Source/textdat.cpp:564 +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 "" +"Я загубила Тео! Я загубила свого найкращого друга! Ми гралися біля річки, " +"і Тео сказав, що хоче подивитися на велику зелену штуку. Я сказала, що не " +"треба, але ми прокрались туди, а потім раптом вийшов цей ЖУК! Ми втекли, " +"але Тео впав, і жук СХОПИВ і забрав його !" -#: Source/options.cpp:950 -msgid "Graphics Settings" -msgstr "Налаштування Графіки" +#. TRANSLATORS: Quest text spoken by Little Girl +#: Source/textdat.cpp:566 +msgid "" +"Didja find him? You gotta find Theodore, please! He's just little. He " +"can't take care of himself! Please!" +msgstr "" +"Ти його знайшов? Будь ласка, знайди Теодора! Він просто маленький. Він не " +"може подбати про себе! Прошу!" -#: Source/options.cpp:951 -msgid "Fullscreen" -msgstr "Повноекранний Режим" +#. TRANSLATORS: Quest text spoken by Little Girl (Quest End) +#: Source/textdat.cpp:568 +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 "" +"Ти знайшов його! Ти знайшов його! Дякую! О, Тео, тебе налякали ті " +"неприємні жучки? Фе! Тьфу! Щось прилипло до твого хутра! Фу! Давай, " +"Тео, ходімо додому! Ще раз дякую, герой!" -#: Source/options.cpp:951 -msgid "Display the game in windowed or fullscreen mode." -msgstr "Показує гру в режимі повного екрану чи вікна." +#. TRANSLATORS: Quest text spoken by Defiler (Hostile) +#: Source/textdat.cpp:570 +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 "" +"Ми довго дрімали, і настав час пробудження. Після довгого сну ми сповнені " +"сильний голодом. Вже скоро, ми поживимося…" -#: Source/options.cpp:953 -msgid "Fit to Screen" -msgstr "Налаштувати по Екрану" +#. TRANSLATORS: Quest text spoken by Defiler (Hostile) +#: Source/textdat.cpp:572 +msgid "" +"Have you been enjoying yourself, little mammal? How pathetic. Your little " +"world will be no challenge at all." +msgstr "" +"Тобі сподобалось, ссавцю? Як жалюгідно. Твій маленький світ не буде для нас " +"проблемою." -#: Source/options.cpp:953 +#. TRANSLATORS: Quest text spoken by Defiler (Hostile) +#: Source/textdat.cpp:574 msgid "" -"Automatically adjust the game window to your current desktop screen aspect " -"ratio and resolution." +"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 "" -"Автоматично налаштовує вікно гри згідно співвідношення сторін і роздільної " -"здатності екрану." +"Ці землі будуть осквернені, і наш виводок заполонить поля, які люди " +"називають домом. Наші вусики огорнуть цей світ, і ми будемо ласувати плоттю " +"його мешканців. Людина стане нашим майном і їжею." -#: Source/options.cpp:956 -msgid "Upscale" -msgstr "Збільшення Зображення" +#. TRANSLATORS: Quest text spoken by Defiler (Hostile) +#: Source/textdat.cpp:576 +msgid "" +"Ah, I can smell you...you are close! Close! Ssss...the scent of blood and " +"fear...how enticing..." +msgstr "" +"Ах, я чую тебе…ти близько! Близько! Шшшш…запах крові та страху…як привабливо…" -#: Source/options.cpp:956 +#. TRANSLATORS: Quest text spoken by Narrator +#: Source/textdat.cpp:584 msgid "" -"Enables image scaling from the game resolution to your monitor resolution. " -"Prevents changing the monitor resolution and allows window resizing." +"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" +"Камінь був від цього світу – і від усіх світів – оскільки Світло є у всіх " +"речах, і поза всіма речами. Світло і єдність є наслідками цієї святої " +"основи, єдність мети і єдність володіння." -#: Source/options.cpp:963 -msgid "Scaling Quality" -msgstr "Якість Масштабування" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:586 +msgid "Moo." +msgstr "Му." -#: Source/options.cpp:963 -msgid "Enables optional filters to the output image when upscaling." -msgstr "Накладає опційні фільтри до збільшеної картинки." +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:587 +msgid "I said, Moo." +msgstr "Я сказав Му." -#: Source/options.cpp:965 -msgid "Nearest Pixel" -msgstr "Найближчий Піксель" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:588 +msgid "Look I'm just a cow, OK?" +msgstr "Я проста корова, ясно?" -#: Source/options.cpp:966 -msgid "Bilinear" -msgstr "Білінійний" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:589 +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 "" +"Ну добре, добре. Насправді я не корова. Зазвичай я так не ходжу; але я " +"сидів вдома і займався власними справами, і раптом з підлоги почали вилазити " +"жуки, лози, цибулини... це було жахливо! Якби в мене був час нормально " +"одягнутися, я б виглядав не так погано. Гей! Ти б не міг піти до моєї хати " +"і забрати костюм? Коричневий, а не сірий, той для вечору. Я б зробив це " +"сам, але я не хочу, щоб мене бачили в цьому убранні. Ось візьми це, може " +"знадобиться... щоб вбити ті речі, що обросли на моїй хаті. Її не пропустиш, " +"вона на півдні від розвилки річки... ну знаєш... та, де город заріс ." -#: Source/options.cpp:967 -msgid "Anisotropic" -msgstr "Анізотропний" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:591 +msgid "" +"What are you wasting time for? Go get my suit! And hurry! That Holstein " +"over there keeps winking at me!" +msgstr "" +"Що ти тут стоїш? Іди і знайди мій костюм! І поспіши! Той Гольштейн мені " +"весь час підморгує!" -#: Source/options.cpp:969 -msgid "Integer Scaling" -msgstr "Коефіцієнт Масштабування" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:593 +msgid "" +"Hey, have you got my suit there? Quick, pass it over! These ears itch like " +"you wouldn't believe!" +msgstr "" +"Гей, у тебе є мій костюм? Швидко, давай його сюди! Ти не повіриш як ці " +"вуха сверблять!" -#: Source/options.cpp:969 -msgid "Scales the image using whole number pixel ratio." -msgstr "Збільшує зображення гри по коефіцієнту." +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:595 +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 "" +"Ні ні ні ні! Це мій СІРИЙ костюм! Це для вечора! На офіційні випадки! Я " +"не можу ось ЦЕ носити. Ти що, дивак якийсь? Мені потрібен КОРИЧНЕВИЙ " +"костюм." -#: Source/options.cpp:976 -msgid "Vertical Sync" -msgstr "Вертикальна Синхронізація" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:597 +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 "" +"Ах, так НАБАГАТО краще. Хух! Нарешті трохи гідності! Мої роги прямі? " +"Добре. Дивись. Дуже дякую за допомогу. Ось візьми ось це в подарунок; і, " +"знаєш… невеличка порада по моді… тобі б знадобилось трохи… тобі б краще " +"замінити… нутизнаєшпрощояговорю? Цей мотив шукача пригод такий… ретро. " +"Невеличка порада, га? Чао." -#: Source/options.cpp:977 +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:599 msgid "" -"Forces waiting for Vertical Sync. Prevents tearing effect when drawing a " -"frame. Disabling it can help with mouse lag on some systems." +"Look. I'm a cow. And you, you're monster bait. Get some experience under " +"your belt! We'll talk..." msgstr "" -"Примушує очікування кінця Вертикальної Синхронізації. Запобігає ефекту " -"розриву зображення. Якщо налаштування вимкнене, то це може зменшити лаг на " -"деяких системах." +"Подивись. Я корова. А ти, ти приманка. Наберись досвіду! Тоді і " +"поговоримо…" -#: Source/options.cpp:986 -msgid "Zoom on when enabled." -msgstr "Коли ввімкнено, наближує екран гри." +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:602 +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 "" +"Мабуть, я дав тобі насправді страшне завдання. Якби я міг якось… Тобі б " +"допоміг глечик смачного свіжого молока?" -#: Source/options.cpp:987 -msgid "Color Cycling" -msgstr "Циклування Кольорів" +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:604 +msgid "" +"Oh, I could use your help, but perhaps after you've saved the catacombs from " +"the desecration of those beasts." +msgstr "" +"О, мені б знадобилася твоя допомога, але, мабуть, після того, як ти очистиш " +"катакомби від осквернення тих звірів." -#: Source/options.cpp:987 -msgid "Color cycling effect used for water, lava, and acid animation." +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:606 +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 "" -"Ефект циклування кольорів, що використовується для анімації води, лави та " -"кислоти." +"Мені потрібно щось зробити, але я не буду зобов'язувати незнайомця. Можливо " +"після того, як ти пробув тут деякий час, мені буде зручніше просити про " +"послугу." -#: Source/options.cpp:988 -msgid "Alternate nest art" -msgstr "Альтернативна палітра для Гнізда" +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:608 +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 "" +"Я бачу у тобі потенціал для величі. Можливо, колись, поки ти виконуєш свою " +"долю, ти зможеш зайти і зробити мені невеличку послугу?" -#: Source/options.cpp:988 -msgid "The game will use an alternative palette for Hellfire’s nest tileset." +#. TRANSLATORS: Quest text spoken by Farmer +#: Source/textdat.cpp:610 +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 "" -"У грі буде використовуватися альтернативна палітра для плиток Гнізда з " -"Hellfire." +"Я думаю, що ти міг би мені допомогти, але, хіба що після того, як станете " +"трохи сильнішим. Я не хотів би зашкодити єдиному шансу села знищити загрозу " +"в церкві!" -#: Source/options.cpp:990 -msgid "Hardware Cursor" -msgstr "Апаратний Курсор" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:612 +msgid "" +"Me, I'm a self-made cow. Make something of yourself, and... then we'll talk." +msgstr "" +"Я — корова, що сама всього досягла. Досягни чогось, і… от тоді і поговоримо." -#: Source/options.cpp:990 -msgid "Use a hardware cursor" -msgstr "Використовувати апаратно-прискорений курсор" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:614 +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 "" +"Мені не потрібно пояснюватися кожному туристу, що проходить повз! Хіба тобі " +"не треба вбивати монстрів? Поговоримо пізніше. Якщо ти виживеш…" -#: Source/options.cpp:991 -msgid "Hardware Cursor For Items" -msgstr "Апаратний Курсор для Предметів" +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:616 +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 "" +"Перестань мене турбувати. Я шукаю когось справді героїчного. І ти не " +"підходиш. Я не можу тобі довіряти, тебе будь-коли з’їдять монстри… Мені " +"потрібен досвідчений герой." -#: Source/options.cpp:991 -msgid "Use a hardware cursor for items." -msgstr "Використовувати апаратно-прискорений курсор для предметів." +# Puns are lost in translation :( +# I'm all ears if you happen to know how to restore them +#. TRANSLATORS: Quest text spoken by Complete Nut +#: Source/textdat.cpp:618 +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 "" +"Добре, я перестану з маячнею. Я не хотів заплутати тебе. Я сидів вдома в " +"поганому настрої, коли все стало зовсім погано; з підлоги виліз цілий натовп " +"монстрів! Я злякався. Просто так сталося, що я був одягнений у цю Джерсі, " +"коли вибіг за двері, і тепер я виглядаю вкрай смішно. Якби в мене був " +"якийсь нормальний одяг, то було б не так погано. Гей! Ти б не міг піти до " +"моєї хати і забрати костюм? Коричневий, а не сірий, той для вечору. Я б " +"зробив це сам, але я не хочу, щоб хтось бачив мене таким. Я б зробив це " +"сам, але я не хочу, щоб мене бачили в цьому убранні. Ось візьми це, може " +"знадобиться... щоб вбити ті речі, що обросли на моїй хаті. Її не пропустиш, " +"вона на півдні від розвилки річки... ну знаєш... та, де город заріс." -#: Source/options.cpp:992 -msgid "Hardware Cursor Maximum Size" -msgstr "Макс Розмір Аппаратного Курсору" +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:621 +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 "" +"Я пробував заклинання, погрози, відречення та торг із цим негідним " +"створінням — безрезультатно. Мої методи поневолення менших демонів, " +"здається, не впливають на цього страшного звіра." -#: Source/options.cpp:992 +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:623 msgid "" -"Maximum width / height for the hardware cursor. Larger cursors fall back to " -"software." +"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 "" -"Максимальна широта / висота для апаратно-прискореного курсору. Більші " -"курсори використовують програмний спосіб." +"Мій дім повільно псується підлістю цього ненависного в’язня. Склепи повні " +"тіней, що рухаються відразу за кутами зору. Краєм вуха чується ледве " +"помітне дряпання кігтів. Я думаю, що вони шукають цей щоденник." -#: Source/options.cpp:994 -msgid "FPS Limiter" -msgstr "Ліміт Кадрів в Секунду" +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:625 +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 "" +"У своїй тираді істота видала своє ім'я — На-Крул. Я намагався дослідити це " +"ім'я, але менші демони якимось чином знищили мою бібліотеку. На-Крул… Ім’я " +"наповнює мене холодним жахом. Я вважаю за краще думати про нього лише як про " +"Істоту, а не розмірковувати про його справжнє ім’я." -#: Source/options.cpp:994 -msgid "FPS is limited to avoid high CPU load. Limit considers refresh rate." +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:627 +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 "" -"Кадри в секунду лімітовані, щоб запобігти високе використання процессора. " -"Ліміт враховує частоту оновлення монітора." +"Гнівне виття істоти, що в пастці, не дає мені виспатись. Він лютує проти " +"того, хто послав його в Порожнечу, і кличе на мене огидні прокляття за те, " +"що я зловив його. Його слова наповнюють моє серце жахом, але я не можу " +"заглушити його голос." -#: Source/options.cpp:995 -msgid "Show FPS" -msgstr "Показати Кадри в Секунду" +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:629 +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 "" +"В мене майже немає часу. Я повинен записати способи як ослабити демона, а " +"потім сховати цей текст, щоб його слуги не використали мої знання, щоб " +"звільнити свого володаря. Я сподіваюся, що той, хто знайде цей журнал, буде " +"шукати знання." -#: Source/options.cpp:995 -msgid "Displays the FPS in the upper left corner of the screen." -msgstr "Показує лічильник кадрів в секунду в верхньому лівому кутку екрана." +#. TRANSLATORS: Quest text read aloud from book +#: Source/textdat.cpp:631 +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" +"Заклинання, знайдені в трьох моїх гримуарах, забезпечать тобі захищений вхід " +"до його виміру, але лише якщо їх використати в належній послідовності. " +"Важелі біля входу знімуть бар'єри і звільнять демона; не торкайся їх! " +"Використовуй лише ці заклинання, щоб ввійти, інакше його сила може виявитися " +"занадто великою, щоб перемогти його." -#: Source/options.cpp:1043 -msgid "Gameplay" -msgstr "Гра" +#. TRANSLATORS: Quest text read aloud from book by player +#: Source/textdat.cpp:633 Source/textdat.cpp:636 Source/textdat.cpp:639 +#: Source/textdat.cpp:642 Source/textdat.cpp:645 +msgid "In Spiritu Sanctum." +msgstr "In Spiritu Sanctum." -#: Source/options.cpp:1043 -msgid "Gameplay Settings" -msgstr "Налаштування Гри" +#. TRANSLATORS: Quest text read aloud from book by player +#: Source/textdat.cpp:634 Source/textdat.cpp:637 Source/textdat.cpp:640 +#: Source/textdat.cpp:643 Source/textdat.cpp:646 +msgid "Praedictum Otium." +msgstr "Praedictum Otium." -#: Source/options.cpp:1045 -msgid "Run in Town" -msgstr "Біг в Місті" +#. TRANSLATORS: Quest text read aloud from book by player +#: Source/textdat.cpp:635 Source/textdat.cpp:638 Source/textdat.cpp:641 +#: Source/textdat.cpp:644 Source/textdat.cpp:647 +msgid "Efficio Obitus Ut Inimicus." +msgstr "Efficio Obitus Ut Inimicus." -#: Source/options.cpp:1045 -msgid "" -"Enable jogging/fast walking in town for Diablo and Hellfire. This option was " -"introduced in the expansion." -msgstr "" -"Вмикає біг/швидку ходьбу в Місті для Diablo та Hellfire. Цю опцію вперше " -"додали в Hellfire." +#: Source/towners.cpp:82 +msgid "Griswold the Blacksmith" +msgstr "Коваль Грізвольд" -#: Source/options.cpp:1046 -msgid "Grab Input" -msgstr "Захопити Мишу" +#: Source/towners.cpp:104 +msgid "Ogden the Tavern owner" +msgstr "Хазяїн таверни Огден" -#: Source/options.cpp:1046 -msgid "When enabled mouse is locked to the game window." -msgstr "Коли це налаштування ввімкнене, курсор буде прив'язаний до вікна гри ." +#: Source/towners.cpp:113 +msgid "Wounded Townsman" +msgstr "Поранений Житель" -#: Source/options.cpp:1047 -msgid "Theo Quest" -msgstr "Квест Тео" +#: Source/towners.cpp:134 +msgid "Adria the Witch" +msgstr "Відьма Адрія" -#: Source/options.cpp:1047 -msgid "Enable Little Girl quest." -msgstr "Дозволяє квест Маленької Дівчинки." +#: Source/towners.cpp:143 +msgid "Gillian the Barmaid" +msgstr "Барменша Гілліан" -#: Source/options.cpp:1048 -msgid "Cow Quest" -msgstr "Квест Корови" +#: Source/towners.cpp:174 +msgid "Pepin the Healer" +msgstr "Цілитель Пепін" -#: Source/options.cpp:1048 -msgid "" -"Enable Jersey's quest. Lester the farmer is replaced by the Complete Nut." -msgstr "Дозволяє квест Джерсі. Замість Фермера Лестера буде Повний Псих." +#: Source/towners.cpp:191 +msgid "Cain the Elder" +msgstr "Старець Каїн" -#: Source/options.cpp:1049 -msgid "Friendly Fire" -msgstr "Дружній Вогонь" +#: Source/towners.cpp:218 +msgid "Cow" +msgstr "Корова" -#: Source/options.cpp:1049 -msgid "" -"Allow arrow/spell damage between players in multiplayer even when the " -"friendly mode is on." -msgstr "" -"Дозволяє шкоду від чар/стріл гравцям в мережевій грі, навіть якщо ввімкнений " -"дружній режим." +#: Source/towners.cpp:241 +msgid "Lester the farmer" +msgstr "Фермер Лестер" -#: Source/options.cpp:1050 -msgid "Full quests in Multiplayer" -msgstr "Повні квести в Мережевій Грі" +#: Source/towners.cpp:253 +msgid "Complete Nut" +msgstr "Повний Псих" -#: Source/options.cpp:1050 -msgid "Enables the full/uncut singleplayer version of quests." -msgstr "Вмикає повні/неурізані версії квестів з одиночної гри." +#: Source/towners.cpp:261 +msgid "Celia" +msgstr "Селія" -#: Source/options.cpp:1051 -msgid "Test Bard" -msgstr "Тестовий Бард" +#: Source/towners.cpp:274 +msgid "Slain Townsman" +msgstr "Вбитий Житель" -#: Source/options.cpp:1051 -msgid "Force the Bard character type to appear in the hero selection menu." -msgstr "Примусити \"Бард\" з'явитися в меню вибору типу героя." +#: Source/translation_dummy.cpp:9 +msgctxt "monster" +msgid "Zombie" +msgstr "Зомбі" -#: Source/options.cpp:1052 -msgid "Test Barbarian" -msgstr "Тестовий Варвар" +#: Source/translation_dummy.cpp:10 +msgctxt "monster" +msgid "Ghoul" +msgstr "Гуль" -#: Source/options.cpp:1052 -msgid "" -"Force the Barbarian character type to appear in the hero selection menu." -msgstr "Примусити \"Варвар\" з'явитися в меню вибору типу героя." +#: Source/translation_dummy.cpp:11 +msgctxt "monster" +msgid "Rotting Carcass" +msgstr "Гниюча Туша" -#: Source/options.cpp:1053 -msgid "Experience Bar" -msgstr "Шкала Досвіду" +#: Source/translation_dummy.cpp:12 +msgctxt "monster" +msgid "Black Death" +msgstr "Чорна Смерть" -#: Source/options.cpp:1053 -msgid "Experience Bar is added to the UI at the bottom of the screen." -msgstr "Додає внизу екрана Шкалу Досвіду." +#: Source/translation_dummy.cpp:13 Source/translation_dummy.cpp:21 +msgctxt "monster" +msgid "Fallen One" +msgstr "Впалий" -#: Source/options.cpp:1054 -msgid "Show Item Graphics in Stores" -msgstr "Показувати Іконку Предмету в Магазині" +#: Source/translation_dummy.cpp:14 Source/translation_dummy.cpp:22 +msgctxt "monster" +msgid "Carver" +msgstr "Різьбяр" -#: Source/options.cpp:1054 -msgid "Show item graphics to the left of item descriptions in store menus." -msgstr "Показувати іконку предмету зліва від його опису в меню магазину." +#: Source/translation_dummy.cpp:15 Source/translation_dummy.cpp:23 +msgctxt "monster" +msgid "Devil Kin" +msgstr "Бісовий Рід" -#: Source/options.cpp:1055 -msgid "Show health values" -msgstr "Показати значення здоров'я" +#: Source/translation_dummy.cpp:16 Source/translation_dummy.cpp:24 +msgctxt "monster" +msgid "Dark One" +msgstr "Темний" -#: Source/options.cpp:1055 -msgid "Displays current / max health value on health globe." -msgstr "Показує значення поточного/максимального здоров'я на кулі здоров'я." +#: Source/translation_dummy.cpp:17 Source/translation_dummy.cpp:29 +msgctxt "monster" +msgid "Skeleton" +msgstr "Скелет" -#: Source/options.cpp:1056 -msgid "Show mana values" -msgstr "Показати значення мани" +#: Source/translation_dummy.cpp:18 +msgctxt "monster" +msgid "Corpse Axe" +msgstr "Труп з Сокирою" -#: Source/options.cpp:1056 -msgid "Displays current / max mana value on mana globe." -msgstr "Показує значення поточної/максимальної мани на кулі мани." +#: Source/translation_dummy.cpp:19 Source/translation_dummy.cpp:31 +msgctxt "monster" +msgid "Burning Dead" +msgstr "Палаючий Скелет" -#: Source/options.cpp:1057 -msgid "Enemy Health Bar" -msgstr "Шкала Ворожого Життя" +#: Source/translation_dummy.cpp:20 Source/translation_dummy.cpp:32 +msgctxt "monster" +msgid "Horror" +msgstr "Кошмар" -#: Source/options.cpp:1057 -msgid "Enemy Health Bar is displayed at the top of the screen." -msgstr "Додає внизу екрана Шкалу Ворожого Життя." +#: Source/translation_dummy.cpp:25 +msgctxt "monster" +msgid "Scavenger" +msgstr "Сміттяр" -#: Source/options.cpp:1058 -msgid "Auto Gold Pickup" -msgstr "Автопідбір Золота" +#: Source/translation_dummy.cpp:26 +msgctxt "monster" +msgid "Plague Eater" +msgstr "Пожирач Чуми" -#: Source/options.cpp:1058 -msgid "Gold is automatically collected when in close proximity to the player." -msgstr "" -"Золото автоматично збирається коли гравець знаходиться близько до нього." +#: Source/translation_dummy.cpp:27 +msgctxt "monster" +msgid "Shadow Beast" +msgstr "Звір Тіні" -#: Source/options.cpp:1059 -msgid "Auto Elixir Pickup" -msgstr "Автопідбір Еліксирів" +#: Source/translation_dummy.cpp:28 +msgctxt "monster" +msgid "Bone Gasher" +msgstr "Костогриз" -#: Source/options.cpp:1059 -msgid "" -"Elixirs are automatically collected when in close proximity to the player." -msgstr "" -"Еліксири автоматично збираються коли гравець знаходиться близько до них." +#: Source/translation_dummy.cpp:30 +msgctxt "monster" +msgid "Corpse Bow" +msgstr "Труп з Луком" -#: Source/options.cpp:1060 -msgid "Auto Oil Pickup" -msgstr "Автопідбір Масел" +#: Source/translation_dummy.cpp:33 +msgctxt "monster" +msgid "Skeleton Captain" +msgstr "Капітан Скелетів" -#: Source/options.cpp:1060 -msgid "Oils are automatically collected when in close proximity to the player." -msgstr "Масла автоматично збираються коли гравець знаходиться близько до них." +#: Source/translation_dummy.cpp:34 +msgctxt "monster" +msgid "Corpse Captain" +msgstr "Капітан Трупів" -#: Source/options.cpp:1061 -msgid "Auto Pickup in Town" -msgstr "Автопідбір в Місті" +#: Source/translation_dummy.cpp:35 +msgctxt "monster" +msgid "Burning Dead Captain" +msgstr "Капітан Палаючих Скелетів" -#: Source/options.cpp:1061 -msgid "Automatically pickup items in town." -msgstr "Предмети в Місті автоматично збираються." +#: Source/translation_dummy.cpp:36 +msgctxt "monster" +msgid "Horror Captain" +msgstr "Капітан Кошмарів" -#: Source/options.cpp:1062 -msgid "Adria Refills Mana" -msgstr "Адрія Поповнює Ману" +#: Source/translation_dummy.cpp:37 +msgctxt "monster" +msgid "Invisible Lord" +msgstr "Невидимий Лорд" -#: Source/options.cpp:1062 -msgid "Adria will refill your mana when you visit her shop." -msgstr "Адрія поповнить ману коли гравець відвідує її магазин." +#: Source/translation_dummy.cpp:38 +msgctxt "monster" +msgid "Hidden" +msgstr "Прихований" -#: Source/options.cpp:1063 -msgid "Auto Equip Weapons" -msgstr "Автоспорядження Зброї" +#: Source/translation_dummy.cpp:39 +msgctxt "monster" +msgid "Stalker" +msgstr "Сталкер" -#: Source/options.cpp:1063 -msgid "" -"Weapons will be automatically equipped on pickup or purchase if enabled." -msgstr "Зброя буде автоматично споряджена після її підбору або покупки." +#: Source/translation_dummy.cpp:40 +msgctxt "monster" +msgid "Unseen" +msgstr "Невидимий" -#: Source/options.cpp:1064 -msgid "Auto Equip Armor" -msgstr "Автоспорядження Броні" +#: Source/translation_dummy.cpp:41 +msgctxt "monster" +msgid "Illusion Weaver" +msgstr "Ткач Іллюзій" -#: Source/options.cpp:1064 -msgid "Armor will be automatically equipped on pickup or purchase if enabled." -msgstr "Броня буде автоматично споряджена після її підбору або покупки." +#: Source/translation_dummy.cpp:42 +msgctxt "monster" +msgid "Satyr Lord" +msgstr "Лорд Сатирів" -#: Source/options.cpp:1065 -msgid "Auto Equip Helms" -msgstr "Автоспорядження Шоломів" +#: Source/translation_dummy.cpp:43 Source/translation_dummy.cpp:51 +msgctxt "monster" +msgid "Flesh Clan" +msgstr "Клан Плоті" + +#: Source/translation_dummy.cpp:44 Source/translation_dummy.cpp:52 +msgctxt "monster" +msgid "Stone Clan" +msgstr "Клан Каменю" -#: Source/options.cpp:1065 -msgid "Helms will be automatically equipped on pickup or purchase if enabled." -msgstr "Шоломи будуть автоматично споряджені після їх підбору або покупки." +#: Source/translation_dummy.cpp:45 Source/translation_dummy.cpp:53 +msgctxt "monster" +msgid "Fire Clan" +msgstr "Клан Вогню" -#: Source/options.cpp:1066 -msgid "Auto Equip Shields" -msgstr "Автоспорядження Щитів" +#: Source/translation_dummy.cpp:46 Source/translation_dummy.cpp:54 +msgctxt "monster" +msgid "Night Clan" +msgstr "Клан Ночі" -#: Source/options.cpp:1066 -msgid "" -"Shields will be automatically equipped on pickup or purchase if enabled." -msgstr "Щити будуть автоматично споряджені після їх підбору або покупки." +#: Source/translation_dummy.cpp:47 +msgctxt "monster" +msgid "Fiend" +msgstr "Біс" -#: Source/options.cpp:1067 -msgid "Auto Equip Jewelry" -msgstr "Автоспорядження Коштовностей" +#: Source/translation_dummy.cpp:48 +msgctxt "monster" +msgid "Blink" +msgstr "Блимач" -#: Source/options.cpp:1067 -msgid "" -"Jewelry will be automatically equipped on pickup or purchase if enabled." -msgstr "" -"Коштовності будуть автоматично споряджені після їх підбору або покупки." +#: Source/translation_dummy.cpp:49 +msgctxt "monster" +msgid "Gloom" +msgstr "Похмурень" -#: Source/options.cpp:1068 -msgid "Randomize Quests" -msgstr "Рандомізація Квестів" +#: Source/translation_dummy.cpp:50 +msgctxt "monster" +msgid "Familiar" +msgstr "Фамільяр" -#: Source/options.cpp:1068 -msgid "Randomly selecting available quests for new games." -msgstr "Квести випадково вибираються для нової гри." +#: Source/translation_dummy.cpp:55 +msgctxt "monster" +msgid "Acid Beast" +msgstr "Кислотний Звір" -#: Source/options.cpp:1069 -msgid "Show Monster Type" -msgstr "Показати Тип Монстра" +#: Source/translation_dummy.cpp:56 +msgctxt "monster" +msgid "Poison Spitter" +msgstr "Отрутоплюй" -#: Source/options.cpp:1069 -msgid "" -"Hovering over a monster will display the type of monster in the description " -"box in the UI." -msgstr "" -"Наведення курсора миші над монстром покаже його тип в Панелі Інформації." +#: Source/translation_dummy.cpp:57 +msgctxt "monster" +msgid "Pit Beast" +msgstr "Ямний Звір" -#: Source/options.cpp:1070 -msgid "Show Item Labels" -msgstr "Показати Назви Предметів" +#: Source/translation_dummy.cpp:58 +msgctxt "monster" +msgid "Lava Maw" +msgstr "Лавова Паща" -#: Source/options.cpp:1070 -msgid "Show labels for items on the ground when enabled." -msgstr "Показати назви предметів на землі, коли це налаштування ввімкнене." +#: Source/translation_dummy.cpp:59 Source/translation_dummy.cpp:148 +msgctxt "monster" +msgid "Skeleton King" +msgstr "Король-Скелет" -#: Source/options.cpp:1071 -msgid "Auto Refill Belt" -msgstr "Автозаповнення Пояса" +#: Source/translation_dummy.cpp:60 Source/translation_dummy.cpp:156 +msgctxt "monster" +msgid "The Butcher" +msgstr "М'ясник" -#: Source/options.cpp:1071 -msgid "Refill belt from inventory when belt item is consumed." -msgstr "" -"Коли предмет з Поясу використовується, Пояс автоматично поповнюється з " -"інвентаря." +#: Source/translation_dummy.cpp:61 +msgctxt "monster" +msgid "Overlord" +msgstr "Повелитель" -#: Source/options.cpp:1072 -msgid "Disable Crippling Shrines" -msgstr "Вимкнути Псуючі Вівтарі" +#: Source/translation_dummy.cpp:62 +msgctxt "monster" +msgid "Mud Man" +msgstr "Брудолюдина" -#: Source/options.cpp:1072 -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/translation_dummy.cpp:63 +msgctxt "monster" +msgid "Toad Demon" +msgstr "Жабодемон" -#: Source/options.cpp:1073 -msgid "Quick Cast" -msgstr "Швидке Чаклування" +#: Source/translation_dummy.cpp:64 +msgctxt "monster" +msgid "Flayed One" +msgstr "Облуплений" -#: Source/options.cpp:1073 -msgid "" -"Spell hotkeys instantly cast the spell, rather than switching the readied " -"spell." -msgstr "Гаряча клавішу чар буде відразу чаклувати попередньо вибрані чари." +#: Source/translation_dummy.cpp:65 +msgctxt "monster" +msgid "Wyrm" +msgstr "Супостат" -#: Source/options.cpp:1074 -msgid "Heal Potion Pickup" -msgstr "Підбір Зілля Зцілення" +#: Source/translation_dummy.cpp:66 +msgctxt "monster" +msgid "Cave Slug" +msgstr "Печерний Слизняк" -#: Source/options.cpp:1074 -msgid "Number of Healing potions to pick up automatically." -msgstr "Кількість Зілля Зцілення щоб автоматично підібрати." +#: Source/translation_dummy.cpp:67 +msgctxt "monster" +msgid "Devil Wyrm" +msgstr "Диявол-Супостат" -#: Source/options.cpp:1075 -msgid "Full Heal Potion Pickup" -msgstr "Підбір Зілля Повного Зцілення" +#: Source/translation_dummy.cpp:68 +msgctxt "monster" +msgid "Devourer" +msgstr "Пожирач" -#: Source/options.cpp:1075 -msgid "Number of Full Healing potions to pick up automatically." -msgstr "Кількість Зілля Повного Зцілення щоб автоматично підібрати." +#: Source/translation_dummy.cpp:69 +msgctxt "monster" +msgid "Magma Demon" +msgstr "Демон Магми" -#: Source/options.cpp:1076 -msgid "Mana Potion Pickup" -msgstr "Підбір Зілля Мани" +#: Source/translation_dummy.cpp:70 +msgctxt "monster" +msgid "Blood Stone" +msgstr "Кривавий Камінь" -#: Source/options.cpp:1076 -msgid "Number of Mana potions to pick up automatically." -msgstr "Кількість Зілля Мани щоб автоматично підібрати." +#: Source/translation_dummy.cpp:71 +msgctxt "monster" +msgid "Hell Stone" +msgstr "Пекельний Камінь" -#: Source/options.cpp:1077 -msgid "Full Mana Potion Pickup" -msgstr "Підбір Зілля Повної Мани" +#: Source/translation_dummy.cpp:72 +msgctxt "monster" +msgid "Lava Lord" +msgstr "Лорд Лави" -#: Source/options.cpp:1077 -msgid "Number of Full Mana potions to pick up automatically." -msgstr "Кількість Зілля Повної щоб автоматично підібрати." +#: Source/translation_dummy.cpp:73 +msgctxt "monster" +msgid "Horned Demon" +msgstr "Рогатий Демон" -#: Source/options.cpp:1078 -msgid "Rejuvenation Potion Pickup" -msgstr "Підбір Зілля Омолодження" +#: Source/translation_dummy.cpp:74 +msgctxt "monster" +msgid "Mud Runner" +msgstr "Брудобігун" -#: Source/options.cpp:1078 -msgid "Number of Rejuvenation potions to pick up automatically." -msgstr "Кількість Зілля Омолодження щоб автоматично підібрати." +#: Source/translation_dummy.cpp:75 +msgctxt "monster" +msgid "Frost Charger" +msgstr "Морозний Бігун" -#: Source/options.cpp:1079 -msgid "Full Rejuvenation Potion Pickup" -msgstr "Підбір Зілля Повного Омолодження" +#: Source/translation_dummy.cpp:76 +msgctxt "monster" +msgid "Obsidian Lord" +msgstr "Лорд Обсидіяну" -#: Source/options.cpp:1079 -msgid "Number of Full Rejuvenation potions to pick up automatically." -msgstr "Кількість Зілля Повного Омолодження щоб автоматично підібрати." +#: Source/translation_dummy.cpp:77 +msgctxt "monster" +msgid "oldboned" +msgstr "старокостяний" -#: Source/options.cpp:1080 -msgid "Enable floating numbers" -msgstr "Ввімкнути Плаваючі Числа" +#: Source/translation_dummy.cpp:78 +msgctxt "monster" +msgid "Red Death" +msgstr "Червона Смерть" -#: Source/options.cpp:1080 -msgid "Enables floating numbers on gaining XP / dealing damage etc." -msgstr "Вмикає плаваючі числа при отриманні XP / нанесенні шкоди тощо." +#: Source/translation_dummy.cpp:79 +msgctxt "monster" +msgid "Litch Demon" +msgstr "Демон-Ліч" -#: Source/options.cpp:1082 -msgid "Off" -msgstr "Вимк" +#: Source/translation_dummy.cpp:80 +msgctxt "monster" +msgid "Undead Balrog" +msgstr "Балрог-Нежить" -#: Source/options.cpp:1083 -msgid "Random Angles" -msgstr "Випадкові Кути" +#: Source/translation_dummy.cpp:81 +msgctxt "monster" +msgid "Incinerator" +msgstr "Крематор" -#: Source/options.cpp:1084 -msgid "Vertical Only" -msgstr "Тільки Вертикально" +#: Source/translation_dummy.cpp:82 +msgctxt "monster" +msgid "Flame Lord" +msgstr "Лорд Вогню" -#: Source/options.cpp:1135 -msgid "Controller" -msgstr "Контроллер" +#: Source/translation_dummy.cpp:83 +msgctxt "monster" +msgid "Doom Fire" +msgstr "Вогонь Долі" -#: Source/options.cpp:1135 -msgid "Controller Settings" -msgstr "Налаштування Контроллеру" +#: Source/translation_dummy.cpp:84 +msgctxt "monster" +msgid "Hell Burner" +msgstr "Пальник Пекла" -#: Source/options.cpp:1144 -msgid "Network" -msgstr "Мережа" +#: Source/translation_dummy.cpp:85 +msgctxt "monster" +msgid "Red Storm" +msgstr "Червоний Шторм" -#: Source/options.cpp:1144 -msgid "Network Settings" -msgstr "Налаштування Мережі" +#: Source/translation_dummy.cpp:86 +msgctxt "monster" +msgid "Storm Rider" +msgstr "Штормовий Вершник" -#: Source/options.cpp:1156 -msgid "Chat" -msgstr "Чат" +#: Source/translation_dummy.cpp:87 +msgctxt "monster" +msgid "Storm Lord" +msgstr "Лорд Шторму" -#: Source/options.cpp:1156 -msgid "Chat Settings" -msgstr "Налаштування Чату" +#: Source/translation_dummy.cpp:88 +msgctxt "monster" +msgid "Maelstrom" +msgstr "Вихор" -#: Source/options.cpp:1165 Source/options.cpp:1281 -msgid "Language" -msgstr "Мова" +#: Source/translation_dummy.cpp:89 +msgctxt "monster" +msgid "Devil Kin Brute" +msgstr "Бісовий Рід-Нелюд" -#: Source/options.cpp:1165 -msgid "Define what language to use in game." -msgstr "Визначте мову гри." +#: Source/translation_dummy.cpp:90 +msgctxt "monster" +msgid "Winged-Demon" +msgstr "Крилатий Демон" -#: Source/options.cpp:1281 -msgid "Language Settings" -msgstr "Налаштування Мови" +#: Source/translation_dummy.cpp:91 +msgctxt "monster" +msgid "Gargoyle" +msgstr "Ґаргулья" -#: Source/options.cpp:1293 -msgid "Keymapping" -msgstr "Назначення Клавіш" +#: Source/translation_dummy.cpp:92 +msgctxt "monster" +msgid "Blood Claw" +msgstr "Кривавий Кіготь" -#: Source/options.cpp:1293 -msgid "Keymapping Settings" -msgstr "Налаштування Назначення" +#: Source/translation_dummy.cpp:93 +msgctxt "monster" +msgid "Death Wing" +msgstr "Крило Смерті" -#: Source/options.cpp:1516 -msgid "Padmapping" -msgstr "Падмапінг" +#: Source/translation_dummy.cpp:94 +msgctxt "monster" +msgid "Slayer" +msgstr "Вбивця" -#: Source/options.cpp:1516 -msgid "Padmapping Settings" -msgstr "Налаштування Падмапінгу" +#: Source/translation_dummy.cpp:95 +msgctxt "monster" +msgid "Guardian" +msgstr "Заступник" -#: Source/panels/charpanel.cpp:127 -msgid "Level" -msgstr "Рівень" +#: Source/translation_dummy.cpp:96 +msgctxt "monster" +msgid "Vortex Lord" +msgstr "Лорд Вихру" -#: Source/panels/charpanel.cpp:129 -msgid "Experience" -msgstr "Досвід" +#: Source/translation_dummy.cpp:97 +msgctxt "monster" +msgid "Balrog" +msgstr "Балрог" -#: Source/panels/charpanel.cpp:134 -msgid "Next level" -msgstr "Наступний рівень" +#: Source/translation_dummy.cpp:98 +msgctxt "monster" +msgid "Cave Viper" +msgstr "Печерна Гадюка" -#: Source/panels/charpanel.cpp:143 -msgid "Base" -msgstr "Базовий" +#: Source/translation_dummy.cpp:99 +msgctxt "monster" +msgid "Fire Drake" +msgstr "Вогняний Левіятан" -#: Source/panels/charpanel.cpp:144 -msgid "Now" -msgstr "Зараз" +#: Source/translation_dummy.cpp:100 +msgctxt "monster" +msgid "Gold Viper" +msgstr "Золота Гадюка" -#: Source/panels/charpanel.cpp:145 -msgid "Strength" -msgstr "Сила" +#: Source/translation_dummy.cpp:101 +msgctxt "monster" +msgid "Azure Drake" +msgstr "Лазурний Левіятан" -#: Source/panels/charpanel.cpp:149 -msgid "Magic" -msgstr "Магія" +#: Source/translation_dummy.cpp:102 +msgctxt "monster" +msgid "Black Knight" +msgstr "Чорний Лицар" -#: Source/panels/charpanel.cpp:153 -msgid "Dexterity" -msgstr "Спринтість" +#: Source/translation_dummy.cpp:103 +msgctxt "monster" +msgid "Doom Guard" +msgstr "Страж Долі" -#: Source/panels/charpanel.cpp:156 -msgid "Vitality" -msgstr "Живучість" +#: Source/translation_dummy.cpp:104 +msgctxt "monster" +msgid "Steel Lord" +msgstr "Лорд Сталі" -#: Source/panels/charpanel.cpp:159 -msgid "Points to distribute" -msgstr "Розподілити очків" +#: Source/translation_dummy.cpp:105 +msgctxt "monster" +msgid "Blood Knight" +msgstr "Лицар Крові" -#: Source/panels/charpanel.cpp:169 -msgid "Armor class" -msgstr "Клас броні" +#: Source/translation_dummy.cpp:106 +msgctxt "monster" +msgid "The Shredded" +msgstr "Січений" -#: Source/panels/charpanel.cpp:171 -msgid "To hit" -msgstr "Шанс" +#: Source/translation_dummy.cpp:107 +msgctxt "monster" +msgid "Hollow One" +msgstr "Пустий" -#: Source/panels/charpanel.cpp:173 -msgid "Damage" -msgstr "Шкода" +#: Source/translation_dummy.cpp:108 +msgctxt "monster" +msgid "Pain Master" +msgstr "Майстер Болю" -#: Source/panels/charpanel.cpp:180 -msgid "Life" -msgstr "Життя" +#: Source/translation_dummy.cpp:109 +msgctxt "monster" +msgid "Reality Weaver" +msgstr "Ткач Реальності" -#: Source/panels/charpanel.cpp:184 -msgid "Mana" -msgstr "Мана" +#: Source/translation_dummy.cpp:110 +msgctxt "monster" +msgid "Succubus" +msgstr "Суккуб" -#: Source/panels/charpanel.cpp:189 -msgid "Resist magic" -msgstr "Спротив магії" +#: Source/translation_dummy.cpp:111 +msgctxt "monster" +msgid "Snow Witch" +msgstr "Сніжна Відьма" -#: Source/panels/charpanel.cpp:191 -msgid "Resist fire" -msgstr "Спротив вогню" +#: Source/translation_dummy.cpp:112 +msgctxt "monster" +msgid "Hell Spawn" +msgstr "Пекельний Виродок" -#: Source/panels/charpanel.cpp:193 -msgid "Resist lightning" -msgstr "Спротив блискавці" +#: Source/translation_dummy.cpp:113 +msgctxt "monster" +msgid "Soul Burner" +msgstr "Пальник Душ" -#: Source/panels/mainpanel.cpp:85 -msgid "char" -msgstr "герой" +#: Source/translation_dummy.cpp:114 +msgctxt "monster" +msgid "Counselor" +msgstr "Радник" -#: Source/panels/mainpanel.cpp:86 -msgid "quests" -msgstr "журнал" +#: Source/translation_dummy.cpp:115 +msgctxt "monster" +msgid "Magistrate" +msgstr "Магістрат" -#: Source/panels/mainpanel.cpp:87 -msgid "map" -msgstr "карта" +#: Source/translation_dummy.cpp:116 +msgctxt "monster" +msgid "Cabalist" +msgstr "Кабаліст" -#: Source/panels/mainpanel.cpp:88 -msgid "menu" -msgstr "меню" +#: Source/translation_dummy.cpp:117 +msgctxt "monster" +msgid "Advocate" +msgstr "Прибічник" -#: Source/panels/mainpanel.cpp:89 -msgid "inv" -msgstr "інвен" +#: Source/translation_dummy.cpp:118 +msgctxt "monster" +msgid "Golem" +msgstr "Голем" -#: Source/panels/mainpanel.cpp:90 -msgid "spells" -msgstr "чари" +#: Source/translation_dummy.cpp:119 +msgctxt "monster" +msgid "The Dark Lord" +msgstr "Темний Лорд" -#: Source/panels/mainpanel.cpp:100 Source/panels/mainpanel.cpp:126 -#: Source/panels/mainpanel.cpp:128 -msgid "voice" -msgstr "чат" +#: Source/translation_dummy.cpp:120 +msgctxt "monster" +msgid "The Arch-Litch Malignus" +msgstr "Архі-Ліч Малігнус" -#: Source/panels/mainpanel.cpp:121 Source/panels/mainpanel.cpp:123 -#: Source/panels/mainpanel.cpp:125 -msgid "mute" -msgstr "заблок" +#: Source/translation_dummy.cpp:121 +msgctxt "monster" +msgid "Hellboar" +msgstr "Пекельний Кабан" -#: Source/panels/spell_book.cpp:158 Source/panels/spell_list.cpp:150 -msgid "Skill" -msgstr "Талант" +#: Source/translation_dummy.cpp:122 +msgctxt "monster" +msgid "Stinger" +msgstr "Жало" -#: Source/panels/spell_book.cpp:162 -msgid "Staff ({:d} charge)" -msgid_plural "Staff ({:d} charges)" -msgstr[0] "Посох ({:d} заряд)" -msgstr[1] "Посох ({:d} заряда)" -msgstr[2] "Посох ({:d} зарядів)" +#: Source/translation_dummy.cpp:123 +msgctxt "monster" +msgid "Psychorb" +msgstr "Псих-Око" -#. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:167 -msgctxt "spellbook" -msgid "Level {:d}" -msgstr "Рівень: {:d}" +#: Source/translation_dummy.cpp:124 +msgctxt "monster" +msgid "Arachnon" +msgstr "Арахнон" -#: Source/panels/spell_book.cpp:169 -msgid "Unusable" -msgstr "Непридатні" +#: Source/translation_dummy.cpp:125 +msgctxt "monster" +msgid "Felltwin" +msgstr "Фелтвін" -#. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:177 -msgid "Heals: {:d} - {:d}" -msgstr "Зцілює: {:d} - {:d}" +#: Source/translation_dummy.cpp:126 +msgctxt "monster" +msgid "Hork Spawn" +msgstr "Блювотний Виродок" -#. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:179 -msgid "Damage: {:d} - {:d}" -msgstr "Шкода: {:d} - {:d}" +#: Source/translation_dummy.cpp:127 +msgctxt "monster" +msgid "Venomtail" +msgstr "Отруто-хвіст" -#. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:183 -msgid "Dmg: 1/3 target hp" -msgstr "Шкд: 1/3 життя цілі" +#: Source/translation_dummy.cpp:128 +msgctxt "monster" +msgid "Necromorb" +msgstr "Некро-Око" -#. TRANSLATORS: UI constraints, keep short please. -#: Source/panels/spell_book.cpp:185 -msgctxt "spellbook" -msgid "Mana: {:d}" -msgstr "Мана: {:d}" +#: Source/translation_dummy.cpp:129 +msgctxt "monster" +msgid "Spider Lord" +msgstr "Лорд-Павук" -#: Source/panels/spell_list.cpp:157 -msgid "Spell" -msgstr "Чари" +#: Source/translation_dummy.cpp:130 +msgctxt "monster" +msgid "Lashworm" +msgstr "Хльостохробак" -#: Source/panels/spell_list.cpp:160 -msgid "Damages undead only" -msgstr "Б'є тільки нечисть" +#: Source/translation_dummy.cpp:131 +msgctxt "monster" +msgid "Torchant" +msgstr "Торчант" -#: Source/panels/spell_list.cpp:171 -msgid "Scroll" -msgstr "Сувій" +#: Source/translation_dummy.cpp:132 Source/translation_dummy.cpp:157 +msgctxt "monster" +msgid "Hork Demon" +msgstr "Блювотний Демон" -#: Source/panels/spell_list.cpp:193 -msgid "Spell Hotkey {:s}" -msgstr "Гаряча клавіша Чар {:s}" +#: Source/translation_dummy.cpp:133 +msgctxt "monster" +msgid "Hell Bug" +msgstr "Пекельний Жук" -#: Source/pfile.cpp:723 -msgid "Unable to open archive" -msgstr "Неможливо відкрити архів" +#: Source/translation_dummy.cpp:134 +msgctxt "monster" +msgid "Gravedigger" +msgstr "Могильник" + +#: Source/translation_dummy.cpp:135 +msgctxt "monster" +msgid "Tomb Rat" +msgstr "Могильний Щур" -#: Source/pfile.cpp:725 -msgid "Unable to load character" -msgstr "Неможливо завантажити героя" +#: Source/translation_dummy.cpp:136 +msgctxt "monster" +msgid "Firebat" +msgstr "Вогняний Кажан" -#: Source/plrmsg.cpp:84 Source/qol/chatlog.cpp:131 -msgid "{:s} (lvl {:d}): " -msgstr "{:s} (рів {:d}): " +#: Source/translation_dummy.cpp:137 +msgctxt "monster" +msgid "Skullwing" +msgstr "Черепокрило" -#: Source/qol/chatlog.cpp:160 -msgid "Chat History (Messages: {:d})" -msgstr "Історія Чату (Повідомлень: {:d})" +#: Source/translation_dummy.cpp:138 +msgctxt "monster" +msgid "Lich" +msgstr "Ліч" -#: Source/qol/itemlabels.cpp:105 -msgid "{:s} gold" -msgstr "{:s} золота" +#: Source/translation_dummy.cpp:139 +msgctxt "monster" +msgid "Crypt Demon" +msgstr "Демон Склепу" -#: Source/qol/stash.cpp:640 -msgid "How many gold pieces do you want to withdraw?" -msgstr "Скільки золотих монет ти хочеш забрати?" +#: Source/translation_dummy.cpp:140 +msgctxt "monster" +msgid "Hellbat" +msgstr "Пекельний Кажан" -#: Source/qol/xpbar.cpp:125 -msgid "Level {:d}" -msgstr "Рівень {:d}" +#: Source/translation_dummy.cpp:141 +msgctxt "monster" +msgid "Bone Demon" +msgstr "Кістяний Демон" -#: Source/qol/xpbar.cpp:131 Source/qol/xpbar.cpp:139 -msgid "Experience: {:s}" -msgstr "Досвід: {:s}" +#: Source/translation_dummy.cpp:142 +msgctxt "monster" +msgid "Arch Lich" +msgstr "Архі-Ліч" -#: Source/qol/xpbar.cpp:132 -msgid "Maximum Level" -msgstr "Максимальний Рівень" +#: Source/translation_dummy.cpp:143 +msgctxt "monster" +msgid "Biclops" +msgstr "Біклоп" -#: Source/qol/xpbar.cpp:140 -msgid "Next Level: {:s}" -msgstr "Наступний рівень: {:s}" +#: Source/translation_dummy.cpp:144 +msgctxt "monster" +msgid "Flesh Thing" +msgstr "Річ із Плоті" -#: Source/qol/xpbar.cpp:141 -msgid "{:s} to Level {:d}" -msgstr "{:s} до {:d} Рівня" +#: Source/translation_dummy.cpp:145 +msgctxt "monster" +msgid "Reaper" +msgstr "Жнець" -#. TRANSLATORS: Quest Name Block -#: Source/quests.cpp:48 -msgid "The Magic Rock" -msgstr "Магічний Камінь" +#: Source/translation_dummy.cpp:146 Source/translation_dummy.cpp:159 +msgctxt "monster" +msgid "Na-Krul" +msgstr "На-Крул" -#: Source/quests.cpp:50 -msgid "Gharbad The Weak" +#: Source/translation_dummy.cpp:147 +msgctxt "monster" +msgid "Gharbad the Weak" msgstr "Слабак Габард" -#: Source/quests.cpp:51 +#: Source/translation_dummy.cpp:149 +msgctxt "monster" msgid "Zhar the Mad" -msgstr "Схиблений Жар" +msgstr "Безумний Жар" -#: Source/quests.cpp:52 -msgid "Lachdanan" -msgstr "Лахданан" +#: Source/translation_dummy.cpp:150 +msgctxt "monster" +msgid "Snotspill" +msgstr "Шмарклі" -#: Source/quests.cpp:54 -msgid "The Butcher" -msgstr "М'ясник" +#: Source/translation_dummy.cpp:151 +msgctxt "monster" +msgid "Arch-Bishop Lazarus" +msgstr "Архієпископ Лазар" -#: Source/quests.cpp:55 -msgid "Ogden's Sign" -msgstr "Знак Огдена" +#: Source/translation_dummy.cpp:152 +msgctxt "monster" +msgid "Red Vex" +msgstr "Червоний Векс" -#: Source/quests.cpp:56 -msgid "Halls of the Blind" -msgstr "Зали Сліпих" +#: Source/translation_dummy.cpp:153 +msgctxt "monster" +msgid "Black Jade" +msgstr "Чорний Нефрит" -#: Source/quests.cpp:57 -msgid "Valor" -msgstr "Доблесть" +#: Source/translation_dummy.cpp:154 +msgctxt "monster" +msgid "Lachdanan" +msgstr "Лахданан" -#: Source/quests.cpp:59 +#: Source/translation_dummy.cpp:155 +msgctxt "monster" msgid "Warlord of Blood" msgstr "Воєвода Крові" -#: Source/quests.cpp:60 -msgid "The Curse of King Leoric" -msgstr "Прокляття Короля Леоріка" +#: Source/translation_dummy.cpp:158 +msgctxt "monster" +msgid "The Defiler" +msgstr "Паплюжник" -#. TRANSLATORS: Quest Map -#: Source/quests.cpp:62 Source/quests.cpp:98 -msgid "The Chamber of Bone" -msgstr "Палата Кості" +#: Source/translation_dummy.cpp:160 +msgctxt "monster" +msgid "Bonehead Keenaxe" +msgstr "Гостросокирний Кістяк" -#: Source/quests.cpp:63 -msgid "Archbishop Lazarus" -msgstr "Архієпископ Лазарь" +#: Source/translation_dummy.cpp:161 +msgctxt "monster" +msgid "Bladeskin the Slasher" +msgstr "Рубака Мечешкура" -#: Source/quests.cpp:64 -msgid "Grave Matters" -msgstr "Могильні Справи" +#: Source/translation_dummy.cpp:162 +msgctxt "monster" +msgid "Soulpus" +msgstr "Душогній" -#: Source/quests.cpp:65 -msgid "Farmer's Orchard" -msgstr "Сад Фермера" +#: Source/translation_dummy.cpp:163 +msgctxt "monster" +msgid "Pukerat the Unclean" +msgstr "Нечистий Блювопацюк" -#: Source/quests.cpp:66 -msgid "Little Girl" -msgstr "Маленька Дівчинка" +#: Source/translation_dummy.cpp:164 +msgctxt "monster" +msgid "Boneripper" +msgstr "Костелом" -#: Source/quests.cpp:67 -msgid "Wandering Trader" -msgstr "Мандруючий Торговець" +#: Source/translation_dummy.cpp:165 +msgctxt "monster" +msgid "Rotfeast the Hungry" +msgstr "Голодний Гнилеїд" -#: Source/quests.cpp:68 -msgid "The Defiler" -msgstr "Паплюжник" +#: Source/translation_dummy.cpp:166 +msgctxt "monster" +msgid "Gutshank the Quick" +msgstr "Швидкий Животоріз" -#: Source/quests.cpp:69 -msgid "Na-Krul" -msgstr "На-Крул" +#: Source/translation_dummy.cpp:167 +msgctxt "monster" +msgid "Brokenhead Bangshield" +msgstr "Ломочереп зі Щитом" -#. TRANSLATORS: Quest Name Block end -#: Source/quests.cpp:71 -msgid "The Jersey's Jersey" -msgstr "Джерсі Джерсі" +#: Source/translation_dummy.cpp:168 +msgctxt "monster" +msgid "Bongo" +msgstr "Бонго" -#. TRANSLATORS: Quest Map -#: Source/quests.cpp:97 -msgid "King Leoric's Tomb" -msgstr "Гробниця Короля Леоріка" +#: Source/translation_dummy.cpp:169 +msgctxt "monster" +msgid "Rotcarnage" +msgstr "Гнилеріз" -#. TRANSLATORS: Quest Map -#: Source/quests.cpp:100 -msgid "A Dark Passage" -msgstr "Темний Прохід" +#: Source/translation_dummy.cpp:170 +msgctxt "monster" +msgid "Shadowbite" +msgstr "Тінекус" -#. TRANSLATORS: Quest Map -#: Source/quests.cpp:101 -msgid "Unholy Altar" -msgstr "Нечестивий Вівтар" +#: Source/translation_dummy.cpp:171 +msgctxt "monster" +msgid "Deadeye" +msgstr "Снайпер" -#. TRANSLATORS: Used for Quest Portals. {:s} is a Map Name -#: Source/quests.cpp:401 -msgid "To {:s}" -msgstr "До {:s}" +#: Source/translation_dummy.cpp:172 +msgctxt "monster" +msgid "Madeye the Dead" +msgstr "Мертвий Влучник" -#: Source/spelldat.cpp:24 -msgctxt "spell" -msgid "Firebolt" -msgstr "Стріла Вогню" +#: Source/translation_dummy.cpp:173 +msgctxt "monster" +msgid "El Chupacabras" +msgstr "Чупакабри" -#: Source/spelldat.cpp:25 -msgctxt "spell" -msgid "Healing" -msgstr "Зцілення" +#: Source/translation_dummy.cpp:174 +msgctxt "monster" +msgid "Skullfire" +msgstr "Череповогонь" -#: Source/spelldat.cpp:26 -msgctxt "spell" -msgid "Lightning" -msgstr "Блискавка" +#: Source/translation_dummy.cpp:175 +msgctxt "monster" +msgid "Warpskull" +msgstr "Кривочереп" -#: Source/spelldat.cpp:27 -msgctxt "spell" -msgid "Flash" -msgstr "Спалах" +#: Source/translation_dummy.cpp:176 +msgctxt "monster" +msgid "Goretongue" +msgstr "Кровоязик" -#: Source/spelldat.cpp:28 -msgctxt "spell" -msgid "Identify" -msgstr "Розпізнавання" +#: Source/translation_dummy.cpp:177 +msgctxt "monster" +msgid "Pulsecrawler" +msgstr "Пульсоповз" -#: Source/spelldat.cpp:29 -msgctxt "spell" -msgid "Fire Wall" -msgstr "Стіна Вогню" +#: Source/translation_dummy.cpp:178 +msgctxt "monster" +msgid "Moonbender" +msgstr "Місячний Маг" -#: Source/spelldat.cpp:30 -msgctxt "spell" -msgid "Town Portal" -msgstr "Портал в Місто" +#: Source/translation_dummy.cpp:179 +msgctxt "monster" +msgid "Wrathraven" +msgstr "Ворон Кари" -#: Source/spelldat.cpp:31 -msgctxt "spell" -msgid "Stone Curse" -msgstr "Кам'яне Прокляття" +#: Source/translation_dummy.cpp:180 +msgctxt "monster" +msgid "Spineeater" +msgstr "Хребетоїд" -#: Source/spelldat.cpp:32 -msgctxt "spell" -msgid "Infravision" -msgstr "Інфрабачення" +#: Source/translation_dummy.cpp:181 +msgctxt "monster" +msgid "Blackash the Burning" +msgstr "Пекучий Чорнопопіл" -#: Source/spelldat.cpp:33 -msgctxt "spell" -msgid "Phasing" -msgstr "Фазування" +#: Source/translation_dummy.cpp:182 +msgctxt "monster" +msgid "Shadowcrow" +msgstr "Тіньоворона" -#: Source/spelldat.cpp:34 -msgctxt "spell" -msgid "Mana Shield" -msgstr "Щит Мани" +#: Source/translation_dummy.cpp:183 +msgctxt "monster" +msgid "Blightstone the Weak" +msgstr "Слабак Іржекамінь" -#: Source/spelldat.cpp:35 -msgctxt "spell" -msgid "Fireball" -msgstr "Куля Вогню" +#: Source/translation_dummy.cpp:184 +msgctxt "monster" +msgid "Bilefroth the Pit Master" +msgstr "Господар Ями Жовчепінь" -#: Source/spelldat.cpp:36 -msgctxt "spell" -msgid "Guardian" -msgstr "Охоронець" +#: Source/translation_dummy.cpp:185 +msgctxt "monster" +msgid "Bloodskin Darkbow" +msgstr "Темнолучник Кровошкур" -#: Source/spelldat.cpp:37 -msgctxt "spell" -msgid "Chain Lightning" -msgstr "Ланцюгова Блискавка" +#: Source/translation_dummy.cpp:186 +msgctxt "monster" +msgid "Foulwing" +msgstr "Огиднокрил" -#: Source/spelldat.cpp:38 -msgctxt "spell" -msgid "Flame Wave" -msgstr "Хвиля Вогню" +#: Source/translation_dummy.cpp:187 +msgctxt "monster" +msgid "Shadowdrinker" +msgstr "Тінопитій" -#: Source/spelldat.cpp:39 -msgctxt "spell" -msgid "Doom Serpents" -msgstr "Змії Долі" +#: Source/translation_dummy.cpp:188 +msgctxt "monster" +msgid "Hazeshifter" +msgstr "Імлозмін" -#: Source/spelldat.cpp:40 -msgctxt "spell" -msgid "Blood Ritual" -msgstr "Кривавий Ритуал" +#: Source/translation_dummy.cpp:189 +msgctxt "monster" +msgid "Deathspit" +msgstr "Смертеплюй" -#: Source/spelldat.cpp:41 -msgctxt "spell" -msgid "Nova" -msgstr "Нова" +#: Source/translation_dummy.cpp:190 +msgctxt "monster" +msgid "Bloodgutter" +msgstr "Кровотрощ" -#: Source/spelldat.cpp:42 -msgctxt "spell" -msgid "Invisibility" -msgstr "Невидимість" +#: Source/translation_dummy.cpp:191 +msgctxt "monster" +msgid "Deathshade Fleshmaul" +msgstr "Смертетінь Плотеріз" -#: Source/spelldat.cpp:43 -msgctxt "spell" -msgid "Inferno" -msgstr "Пекло" +#: Source/translation_dummy.cpp:192 +msgctxt "monster" +msgid "Warmaggot the Mad" +msgstr "Безумний Військохробак" -#: Source/spelldat.cpp:44 -msgctxt "spell" -msgid "Golem" -msgstr "Голем" +#: Source/translation_dummy.cpp:193 +msgctxt "monster" +msgid "Glasskull the Jagged" +msgstr "Зазублений Скляночереп" -#: Source/spelldat.cpp:45 -msgctxt "spell" -msgid "Rage" -msgstr "Лють" +#: Source/translation_dummy.cpp:194 +msgctxt "monster" +msgid "Blightfire" +msgstr "Іржевогонь" -#: Source/spelldat.cpp:46 -msgctxt "spell" -msgid "Teleport" -msgstr "Телепорт" +#: Source/translation_dummy.cpp:195 +msgctxt "monster" +msgid "Nightwing the Cold" +msgstr "Холодний Нічнокрил" -#: Source/spelldat.cpp:47 -msgctxt "spell" -msgid "Apocalypse" -msgstr "Апокаліпсис" +#: Source/translation_dummy.cpp:196 +msgctxt "monster" +msgid "Gorestone" +msgstr "Кровокамінь" -#: Source/spelldat.cpp:48 -msgctxt "spell" -msgid "Etherealize" -msgstr "Етеризація" +#: Source/translation_dummy.cpp:197 +msgctxt "monster" +msgid "Bronzefist Firestone" +msgstr "Бронзокулачний Вогнекамінь" -#: Source/spelldat.cpp:49 -msgctxt "spell" -msgid "Item Repair" -msgstr "Ремонт Предмету" +#: Source/translation_dummy.cpp:198 +msgctxt "monster" +msgid "Wrathfire the Doomed" +msgstr "Приречений Гнівовогонь" -#: Source/spelldat.cpp:50 -msgctxt "spell" -msgid "Staff Recharge" -msgstr "Перезарядка Посоху" +#: Source/translation_dummy.cpp:199 +msgctxt "monster" +msgid "Firewound the Grim" +msgstr "Нещадний Вогнеран" -#: Source/spelldat.cpp:51 -msgctxt "spell" -msgid "Trap Disarm" -msgstr "Обеззброєння Пастки" +#: Source/translation_dummy.cpp:200 +msgctxt "monster" +msgid "Baron Sludge" +msgstr "Барон Багна" -#: Source/spelldat.cpp:52 -msgctxt "spell" -msgid "Elemental" -msgstr "Елементаль" +#: Source/translation_dummy.cpp:201 +msgctxt "monster" +msgid "Blighthorn Steelmace" +msgstr "Сталебулавовий Іржекіготь" -#: Source/spelldat.cpp:53 -msgctxt "spell" -msgid "Charged Bolt" -msgstr "Заряджена Стріла" +#: Source/translation_dummy.cpp:202 +msgctxt "monster" +msgid "Chaoshowler" +msgstr "Ревун Хаосу" -#: Source/spelldat.cpp:54 -msgctxt "spell" -msgid "Holy Bolt" -msgstr "Свята Стріла" +#: Source/translation_dummy.cpp:203 +msgctxt "monster" +msgid "Doomgrin the Rotting" +msgstr "Гниючий Оскал Долі" -#: Source/spelldat.cpp:55 -msgctxt "spell" -msgid "Resurrect" -msgstr "Воскрешення" +#: Source/translation_dummy.cpp:204 +msgctxt "monster" +msgid "Madburner" +msgstr "Безумний Пальник" -#: Source/spelldat.cpp:56 -msgctxt "spell" -msgid "Telekinesis" -msgstr "Телекінез" +#: Source/translation_dummy.cpp:205 +msgctxt "monster" +msgid "Bonesaw the Litch" +msgstr "Ліч-Кістопил" -#: Source/spelldat.cpp:57 -msgctxt "spell" -msgid "Heal Other" -msgstr "Зцілити Іншого" +#: Source/translation_dummy.cpp:206 +msgctxt "monster" +msgid "Breakspine" +msgstr "Спинолом" -#: Source/spelldat.cpp:58 -msgctxt "spell" -msgid "Blood Star" -msgstr "Кривава Зоря" +#: Source/translation_dummy.cpp:207 +msgctxt "monster" +msgid "Devilskull Sharpbone" +msgstr "Гострокіст Чорточереп" -#: Source/spelldat.cpp:59 -msgctxt "spell" -msgid "Bone Spirit" -msgstr "Кістлявий Дух" +#: Source/translation_dummy.cpp:208 +msgctxt "monster" +msgid "Brokenstorm" +msgstr "Ломошторм" -#: Source/spelldat.cpp:60 -msgctxt "spell" -msgid "Mana" -msgstr "Мана" +#: Source/translation_dummy.cpp:209 +msgctxt "monster" +msgid "Stormbane" +msgstr "Прокльоношторм" -#: Source/spelldat.cpp:61 -msgctxt "spell" -msgid "the Magi" -msgstr "Волхви" +#: Source/translation_dummy.cpp:210 +msgctxt "monster" +msgid "Oozedrool" +msgstr "Липкослина" -#: Source/spelldat.cpp:62 -msgctxt "spell" -msgid "the Jester" -msgstr "Блазень" +#: Source/translation_dummy.cpp:211 +msgctxt "monster" +msgid "Goldblight of the Flame" +msgstr "Полум'яна Золотоіржа" -#: Source/spelldat.cpp:63 -msgctxt "spell" -msgid "Lightning Wall" -msgstr "Стіна Блискавки" +#: Source/translation_dummy.cpp:212 +msgctxt "monster" +msgid "Blackstorm" +msgstr "Чорнобуря" -#: Source/spelldat.cpp:64 -msgctxt "spell" -msgid "Immolation" -msgstr "Горіння" +#: Source/translation_dummy.cpp:213 +msgctxt "monster" +msgid "Plaguewrath" +msgstr "Чумогнів" -#: Source/spelldat.cpp:65 -msgctxt "spell" -msgid "Warp" -msgstr "Викривлення" +#: Source/translation_dummy.cpp:214 +msgctxt "monster" +msgid "The Flayer" +msgstr "Шкуродер" -#: Source/spelldat.cpp:66 -msgctxt "spell" -msgid "Reflect" -msgstr "Відбиття" +#: Source/translation_dummy.cpp:215 +msgctxt "monster" +msgid "Bluehorn" +msgstr "Синеріг" -#: Source/spelldat.cpp:67 -msgctxt "spell" -msgid "Berserk" -msgstr "Берсерк" +#: Source/translation_dummy.cpp:216 +msgctxt "monster" +msgid "Warpfire Hellspawn" +msgstr "Виродок Кривовогню" -#: Source/spelldat.cpp:68 -msgctxt "spell" -msgid "Ring of Fire" -msgstr "Кільце Вогню" +#: Source/translation_dummy.cpp:217 +msgctxt "monster" +msgid "Fangspeir" +msgstr "Кігтяник" -#: Source/spelldat.cpp:69 -msgctxt "spell" -msgid "Search" -msgstr "Пошук" +#: Source/translation_dummy.cpp:218 +msgctxt "monster" +msgid "Festerskull" +msgstr "Гноєчереп" -#: Source/spelldat.cpp:70 -msgctxt "spell" -msgid "Rune of Fire" -msgstr "Руна Вогню" +#: Source/translation_dummy.cpp:219 +msgctxt "monster" +msgid "Lionskull the Bent" +msgstr "Зігнутий Левочереп" + +#: Source/translation_dummy.cpp:220 +msgctxt "monster" +msgid "Blacktongue" +msgstr "Чорноязик" -#: Source/spelldat.cpp:71 -msgctxt "spell" -msgid "Rune of Light" -msgstr "Руна Світла" +#: Source/translation_dummy.cpp:221 +msgctxt "monster" +msgid "Viletouch" +msgstr "Огиднодотик" -#: Source/spelldat.cpp:72 -msgctxt "spell" -msgid "Rune of Nova" -msgstr "Руна Нови" +#: Source/translation_dummy.cpp:222 +msgctxt "monster" +msgid "Viperflame" +msgstr "Вогняна Змія" -#: Source/spelldat.cpp:73 -msgctxt "spell" -msgid "Rune of Immolation" -msgstr "Руна Горіння" +#: Source/translation_dummy.cpp:223 +msgctxt "monster" +msgid "Fangskin" +msgstr "Іклошкур" -#: Source/spelldat.cpp:74 -msgctxt "spell" -msgid "Rune of Stone" -msgstr "Руна Каменю" +#: Source/translation_dummy.cpp:224 +msgctxt "monster" +msgid "Witchfire the Unholy" +msgstr "Нечестивий Відьмовогонь" -#: Source/stores.cpp:129 -msgid "Griswold" -msgstr "Грізволд" +#: Source/translation_dummy.cpp:225 +msgctxt "monster" +msgid "Blackskull" +msgstr "Чорночереп" -#: Source/stores.cpp:130 -msgid "Pepin" -msgstr "Пепін" +#: Source/translation_dummy.cpp:226 +msgctxt "monster" +msgid "Soulslash" +msgstr "Душеріз" -#: Source/stores.cpp:132 -msgid "Ogden" -msgstr "Одген" +#: Source/translation_dummy.cpp:227 +msgctxt "monster" +msgid "Windspawn" +msgstr "Виродок Вітру" -#: Source/stores.cpp:133 -msgid "Cain" -msgstr "Каїн" +#: Source/translation_dummy.cpp:228 +msgctxt "monster" +msgid "Lord of the Pit" +msgstr "Лорд Ями" -#: Source/stores.cpp:134 -msgid "Farnham" -msgstr "Фарнхем" +#: Source/translation_dummy.cpp:229 +msgctxt "monster" +msgid "Rustweaver" +msgstr "Іржеткач" -#: Source/stores.cpp:135 -msgid "Adria" -msgstr "Адрія" +#: Source/translation_dummy.cpp:230 +msgctxt "monster" +msgid "Howlingire the Shade" +msgstr "Виюча Тінь" -#: Source/stores.cpp:136 Source/stores.cpp:1315 -msgid "Gillian" -msgstr "Гілліан" +#: Source/translation_dummy.cpp:231 +msgctxt "monster" +msgid "Doomcloud" +msgstr "Хмара Загибелі" -#: Source/stores.cpp:137 -msgid "Wirt" -msgstr "Вірт" +#: Source/translation_dummy.cpp:232 +msgctxt "monster" +msgid "Bloodmoon Soulfire" +msgstr "Кровомісяцевий Душевогонь" -#: Source/stores.cpp:263 Source/stores.cpp:270 -msgid "Back" -msgstr "Назад" +#: Source/translation_dummy.cpp:233 +msgctxt "monster" +msgid "Witchmoon" +msgstr "Відьмомісяць" -#: Source/stores.cpp:292 Source/stores.cpp:298 -msgid ", " -msgstr ", " +#: Source/translation_dummy.cpp:234 +msgctxt "monster" +msgid "Gorefeast" +msgstr "Кровобенкет" -#: Source/stores.cpp:309 -msgid "Damage: {:d}-{:d} " -msgstr "Шкода: {:d}-{:d} " +#: Source/translation_dummy.cpp:235 +msgctxt "monster" +msgid "Graywar the Slayer" +msgstr "Вбивця Сіробій" -#: Source/stores.cpp:311 -msgid "Armor: {:d} " -msgstr "Броня: {:d} " +#: Source/translation_dummy.cpp:236 +msgctxt "monster" +msgid "Dreadjudge" +msgstr "Суддя" -#: Source/stores.cpp:313 -msgid "Dur: {:d}/{:d}, " -msgstr "Міц: {:d}/{:d}, " +#: Source/translation_dummy.cpp:237 +msgctxt "monster" +msgid "Stareye the Witch" +msgstr "Відьма Зоряоко" -#: Source/stores.cpp:315 -msgid "Indestructible, " -msgstr "Неруйновний, " +#: Source/translation_dummy.cpp:238 +msgctxt "monster" +msgid "Steelskull the Hunter" +msgstr "Мисливець Сталечереп" -#: Source/stores.cpp:323 -msgid "No required attributes" -msgstr "Немає вимог" +#: Source/translation_dummy.cpp:239 +msgctxt "monster" +msgid "Sir Gorash" +msgstr "Сір Гораш" -#: Source/stores.cpp:355 Source/stores.cpp:1069 Source/stores.cpp:1302 -msgid "Welcome to the" -msgstr "Ласкаво просимо" +#: Source/translation_dummy.cpp:240 +msgctxt "monster" +msgid "The Vizier" +msgstr "Візир" -#: Source/stores.cpp:356 -msgid "Blacksmith's shop" -msgstr "Ковальська Майстерня" +#: Source/translation_dummy.cpp:241 +msgctxt "monster" +msgid "Zamphir" +msgstr "Замфір" -#: Source/stores.cpp:357 Source/stores.cpp:705 Source/stores.cpp:1071 -#: Source/stores.cpp:1128 Source/stores.cpp:1304 Source/stores.cpp:1316 -#: Source/stores.cpp:1329 -msgid "Would you like to:" -msgstr "Ви хочете:" +#: Source/translation_dummy.cpp:242 +msgctxt "monster" +msgid "Bloodlust" +msgstr "Кровожад" -#: Source/stores.cpp:358 -msgid "Talk to Griswold" -msgstr "Поговорити з Грізвольдом" +#: Source/translation_dummy.cpp:243 +msgctxt "monster" +msgid "Webwidow" +msgstr "Павуковдова" -#: Source/stores.cpp:359 -msgid "Buy basic items" -msgstr "Купити прості предмети" +#: Source/translation_dummy.cpp:244 +msgctxt "monster" +msgid "Fleshdancer" +msgstr "Плотетанцівниця" -#: Source/stores.cpp:360 -msgid "Buy premium items" -msgstr "Купити преміальні предмети" +#: Source/translation_dummy.cpp:245 +msgctxt "monster" +msgid "Grimspike" +msgstr "Незламовістря" -#: Source/stores.cpp:361 Source/stores.cpp:708 -msgid "Sell items" -msgstr "Продати предмети" +#: Source/translation_dummy.cpp:246 +msgctxt "monster" +msgid "Doomlock" +msgstr "Замок Загибіелі" -#: Source/stores.cpp:362 -msgid "Repair items" -msgstr "Відремонтувати предмети" +#: Source/translation_dummy.cpp:248 Source/translation_dummy.cpp:394 +msgid "Short Sword" +msgstr "Короткий Меч" -#: Source/stores.cpp:363 -msgid "Leave the shop" -msgstr "Покинути магазин" +#: Source/translation_dummy.cpp:249 Source/translation_dummy.cpp:341 +msgid "Buckler" +msgstr "Круглий Щит" -#: Source/stores.cpp:406 Source/stores.cpp:759 Source/stores.cpp:1105 -msgid "I have these items for sale:" -msgstr "В мене продаються такі предмети:" +#: Source/translation_dummy.cpp:250 Source/translation_dummy.cpp:435 +#: Source/translation_dummy.cpp:436 Source/translation_dummy.cpp:437 +msgid "Club" +msgstr "Палиця" -#: Source/stores.cpp:471 -msgid "I have these premium items for sale:" -msgstr "Є такі преміальні предмети:" +#: Source/translation_dummy.cpp:251 Source/translation_dummy.cpp:442 +msgid "Short Bow" +msgstr "Короткий Лук" -#: Source/stores.cpp:590 Source/stores.cpp:852 -msgid "You have nothing I want." -msgstr "Вам немає що продати." +#: Source/translation_dummy.cpp:252 +msgid "Short Staff of Mana" +msgstr "Короткий Посох Мани" -#: Source/stores.cpp:601 Source/stores.cpp:864 -msgid "Which item is for sale?" -msgstr "Який предмет продати?" +#: Source/translation_dummy.cpp:253 +msgid "Cleaver" +msgstr "Сікач" -#: Source/stores.cpp:666 -msgid "You have nothing to repair." -msgstr "Немає чого ремонтувати." +#: Source/translation_dummy.cpp:254 Source/translation_dummy.cpp:491 +msgid "The Undead Crown" +msgstr "Нечестива Корона" -#: Source/stores.cpp:677 -msgid "Repair which item?" -msgstr "Який предмет ремонтувати?" +#: Source/translation_dummy.cpp:255 Source/translation_dummy.cpp:492 +msgid "Empyrean Band" +msgstr "Обруч Емпірею" -#: Source/stores.cpp:704 -msgid "Witch's shack" -msgstr "Хатина відьми" +#: Source/translation_dummy.cpp:256 +msgid "Magic Rock" +msgstr "Магічний Камінь" -#: Source/stores.cpp:706 -msgid "Talk to Adria" -msgstr "Поговорити з Адрією" +#: Source/translation_dummy.cpp:257 Source/translation_dummy.cpp:493 +msgid "Optic Amulet" +msgstr "Оптичний Амулет" -#: Source/stores.cpp:707 Source/stores.cpp:1073 -msgid "Buy items" -msgstr "Купити предмети" +#: Source/translation_dummy.cpp:258 Source/translation_dummy.cpp:494 +msgid "Ring of Truth" +msgstr "Перстень Правди" -#: Source/stores.cpp:709 -msgid "Recharge staves" -msgstr "Перезарядити посохи" +#: Source/translation_dummy.cpp:259 +msgid "Tavern Sign" +msgstr "Знак Таверни" -#: Source/stores.cpp:710 -msgid "Leave the shack" -msgstr "Покинути хатину" +#: Source/translation_dummy.cpp:260 Source/translation_dummy.cpp:495 +msgid "Harlequin Crest" +msgstr "Гребінь Арлекіна" -#: Source/stores.cpp:926 -msgid "You have nothing to recharge." -msgstr "Немає чого заряджати." +#: Source/translation_dummy.cpp:261 Source/translation_dummy.cpp:496 +msgid "Veil of Steel" +msgstr "Стальна Чадра" -#: Source/stores.cpp:937 -msgid "Recharge which item?" -msgstr "Який предмет зарядити?" +#: Source/translation_dummy.cpp:262 +msgid "Golden Elixir" +msgstr "Золотий Еліксир" -#: Source/stores.cpp:950 -msgid "You do not have enough gold" -msgstr "У Вас недостатньо золота" +#: Source/translation_dummy.cpp:265 +msgid "Brain" +msgstr "Мозок" -#: Source/stores.cpp:958 -msgid "You do not have enough room in inventory" -msgstr "У Вас недостатньо місця в інвентарі" +#: Source/translation_dummy.cpp:266 +msgid "Fungal Tome" +msgstr "Грибковий Том" -#: Source/stores.cpp:976 -msgid "Do we have a deal?" -msgstr "Домовились?" +#: Source/translation_dummy.cpp:267 +msgid "Spectral Elixir" +msgstr "Примарний Еліксир" -#: Source/stores.cpp:979 -msgid "Are you sure you want to identify this item?" -msgstr "Ви впевнені, що хочете розпізнати цей предмет?" +#: Source/translation_dummy.cpp:268 +msgid "Blood Stone" +msgstr "Кров'яний Камінь" -#: Source/stores.cpp:985 -msgid "Are you sure you want to buy this item?" -msgstr "Ви впевнені, що хочете купити цей предмет?" +#: Source/translation_dummy.cpp:269 +msgid "Cathedral Map" +msgstr "Карта Собору" -#: Source/stores.cpp:988 -msgid "Are you sure you want to recharge this item?" -msgstr "Ви впевнені, що хочете зарядити цей предмет?" +#: Source/translation_dummy.cpp:270 +msgid "Heart" +msgstr "Сердце" -#: Source/stores.cpp:992 -msgid "Are you sure you want to sell this item?" -msgstr "Ви впевнені, що хочете продати цей предмет?" +#: Source/translation_dummy.cpp:271 Source/translation_dummy.cpp:353 +msgid "Potion of Healing" +msgstr "Зілля Зцілення" -#: Source/stores.cpp:995 -msgid "Are you sure you want to repair this item?" -msgstr "Ви впевнені, що хочете відремонтувати цей предмет?" +#: Source/translation_dummy.cpp:272 Source/translation_dummy.cpp:355 +msgid "Potion of Mana" +msgstr "Зілля Мани" -#: Source/stores.cpp:1009 Source/towners.cpp:158 -msgid "Wirt the Peg-legged boy" -msgstr "Одноногий хлопчик Вірт" +#: Source/translation_dummy.cpp:273 Source/translation_dummy.cpp:370 +msgid "Scroll of Identify" +msgstr "Сувій Розпізнавання" -#: Source/stores.cpp:1012 Source/stores.cpp:1019 -msgid "Talk to Wirt" -msgstr "Поговорити з Віртом" +#: Source/translation_dummy.cpp:274 Source/translation_dummy.cpp:374 +msgid "Scroll of Town Portal" +msgstr "Сувій Порталу в Місто" + +#: Source/translation_dummy.cpp:275 Source/translation_dummy.cpp:497 +msgid "Arkaine's Valor" +msgstr "Доблесть Аркейну" -#: Source/stores.cpp:1013 -msgid "I have something for sale," -msgstr "В мене є щось на продаж," +#: Source/translation_dummy.cpp:276 Source/translation_dummy.cpp:354 +msgid "Potion of Full Healing" +msgstr "Зілля Повного Зцілення" -#: Source/stores.cpp:1014 -msgid "but it will cost 50 gold" -msgstr "але воно коштує 50 золота" +#: Source/translation_dummy.cpp:277 Source/translation_dummy.cpp:356 +msgid "Potion of Full Mana" +msgstr "Зілля Повної Мани" -#: Source/stores.cpp:1015 -msgid "just to take a look. " -msgstr "подивись. " +#: Source/translation_dummy.cpp:278 Source/translation_dummy.cpp:498 +msgid "Griswold's Edge" +msgstr "Лезо Грізволда" -#: Source/stores.cpp:1016 -msgid "What have you got?" -msgstr "Що в тебе є?" +#: Source/translation_dummy.cpp:279 Source/translation_dummy.cpp:499 +msgid "Bovine Plate" +msgstr "Бичачі Лати" -#: Source/stores.cpp:1017 Source/stores.cpp:1020 Source/stores.cpp:1131 -#: Source/stores.cpp:1319 -msgid "Say goodbye" -msgstr "Попрощатися" +#: Source/translation_dummy.cpp:280 +msgid "Staff of Lazarus" +msgstr "Посох Лазаря" -#: Source/stores.cpp:1030 -msgid "I have this item for sale:" -msgstr "В мене продаються такі предмети:" +#: Source/translation_dummy.cpp:281 Source/translation_dummy.cpp:371 +msgid "Scroll of Resurrect" +msgstr "Сувій Воскрешення" -#: Source/stores.cpp:1047 -msgid "Leave" -msgstr "Покинути" +#: Source/translation_dummy.cpp:283 Source/translation_dummy.cpp:458 +msgid "Short Staff" +msgstr "Короткий Посох" -#: Source/stores.cpp:1070 -msgid "Healer's home" -msgstr "Дім цілителя" +#: Source/translation_dummy.cpp:284 Source/translation_dummy.cpp:395 +#: Source/translation_dummy.cpp:397 Source/translation_dummy.cpp:399 +#: Source/translation_dummy.cpp:401 Source/translation_dummy.cpp:407 +#: Source/translation_dummy.cpp:409 Source/translation_dummy.cpp:411 +#: Source/translation_dummy.cpp:413 Source/translation_dummy.cpp:415 +msgid "Sword" +msgstr "Меч" -#: Source/stores.cpp:1072 -msgid "Talk to Pepin" -msgstr "Поговорити з Пепіном" +#: Source/translation_dummy.cpp:285 Source/translation_dummy.cpp:392 +#: Source/translation_dummy.cpp:393 +msgid "Dagger" +msgstr "Кинджал" -#: Source/stores.cpp:1074 -msgid "Leave Healer's home" -msgstr "Покинути дім цілителя" +#: Source/translation_dummy.cpp:286 +msgid "Rune Bomb" +msgstr "Рунна Бомба" -#: Source/stores.cpp:1127 -msgid "The Town Elder" -msgstr "Старець Міста" +#: Source/translation_dummy.cpp:287 +msgid "Theodore" +msgstr "Теодор" -#: Source/stores.cpp:1129 -msgid "Talk to Cain" -msgstr "Поговорити з Каїном" +#: Source/translation_dummy.cpp:288 +msgid "Auric Amulet" +msgstr "Золотоносний Амулет" -#: Source/stores.cpp:1130 -msgid "Identify an item" -msgstr "Розпізнати предмет" +#: Source/translation_dummy.cpp:289 +msgid "Torn Note 1" +msgstr "Розірвана Записка 1" -#: Source/stores.cpp:1223 -msgid "You have nothing to identify." -msgstr "Немає чого розпізнавати." +#: Source/translation_dummy.cpp:290 +msgid "Torn Note 2" +msgstr "Розірвана Записка 2" -#: Source/stores.cpp:1234 -msgid "Identify which item?" -msgstr "Який предмет розпізнати?" +#: Source/translation_dummy.cpp:291 +msgid "Torn Note 3" +msgstr "Розірвана Записка 3" -#: Source/stores.cpp:1249 -msgid "This item is:" -msgstr "Предмет:" +#: Source/translation_dummy.cpp:292 +msgid "Reconstructed Note" +msgstr "Відновлена Записка" -#: Source/stores.cpp:1252 -msgid "Done" -msgstr "Готово" +#: Source/translation_dummy.cpp:293 +msgid "Brown Suit" +msgstr "Коричневий Костюм" -#: Source/stores.cpp:1261 -msgid "Talk to {:s}" -msgstr "Поговорити з {:s}" +#: Source/translation_dummy.cpp:294 +msgid "Grey Suit" +msgstr "Сірий Костюм" -#: Source/stores.cpp:1264 -msgid "Talking to {:s}" -msgstr "Говоримо з {:s}" +#: Source/translation_dummy.cpp:295 Source/translation_dummy.cpp:296 +#: Source/translation_dummy.cpp:298 +msgid "Cap" +msgstr "Шапка" -#: Source/stores.cpp:1265 -msgid "is not available" -msgstr "недоступне" +#: Source/translation_dummy.cpp:297 +msgid "Skull Cap" +msgstr "Черепна Шапка" -#: Source/stores.cpp:1266 -msgid "in the shareware" -msgstr "в демо-версії" +#: Source/translation_dummy.cpp:299 Source/translation_dummy.cpp:300 +#: Source/translation_dummy.cpp:302 Source/translation_dummy.cpp:306 +msgid "Helm" +msgstr "Шолом" -#: Source/stores.cpp:1267 -msgid "version" -msgstr "версії" +#: Source/translation_dummy.cpp:301 +msgid "Full Helm" +msgstr "Повний Шолом" -#: Source/stores.cpp:1294 -msgid "Gossip" -msgstr "Плітки" +#: Source/translation_dummy.cpp:303 Source/translation_dummy.cpp:304 +msgid "Crown" +msgstr "Корона" -#: Source/stores.cpp:1303 -msgid "Rising Sun" -msgstr "Світанкове Сонце" +#: Source/translation_dummy.cpp:305 +msgid "Great Helm" +msgstr "Величезний Шолом" -#: Source/stores.cpp:1305 -msgid "Talk to Ogden" -msgstr "Поговорити з Огденом" +#: Source/translation_dummy.cpp:307 Source/translation_dummy.cpp:308 +msgid "Cape" +msgstr "Накидка" -#: Source/stores.cpp:1306 -msgid "Leave the tavern" -msgstr "Покинути таверну" +#: Source/translation_dummy.cpp:309 Source/translation_dummy.cpp:310 +msgid "Rags" +msgstr "Ганчірки" -#: Source/stores.cpp:1317 -msgid "Talk to Gillian" -msgstr "Поговорити з Гілліан" +#: Source/translation_dummy.cpp:311 Source/translation_dummy.cpp:312 +msgid "Cloak" +msgstr "Мантія" -#: Source/stores.cpp:1318 -msgid "Access Storage" -msgstr "Зайти до схованки" +#: Source/translation_dummy.cpp:313 Source/translation_dummy.cpp:314 +msgid "Robe" +msgstr "Ряса" -#: Source/stores.cpp:1328 Source/towners.cpp:216 -msgid "Farnham the Drunk" -msgstr "П'яниця Фарнхем" +#: Source/translation_dummy.cpp:315 +msgid "Quilted Armor" +msgstr "Стьобана Броня" -#: Source/stores.cpp:1330 -msgid "Talk to Farnham" -msgstr "Поговорити з Фарнхемом" +#: Source/translation_dummy.cpp:317 +msgid "Leather Armor" +msgstr "Шкіряна Броня" -#: Source/stores.cpp:1331 -msgid "Say Goodbye" -msgstr "Попрощатися" +#: Source/translation_dummy.cpp:319 +msgid "Hard Leather Armor" +msgstr "Броня з Твердої Шкіри" -#: Source/stores.cpp:2464 -msgid "Your gold: {:s}" -msgstr "Ваше золото: {:s}" +#: Source/translation_dummy.cpp:321 +msgid "Studded Leather Armor" +msgstr "Броня з Клепаної Шкіри" -#. 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 "" -" А, історія нашого Короля… Трагічне падіння Леоріка стало великою бідою для " -"наших земель. Люди завжди любили Короля, а тепер живуть в смертельному " -"страху до нього. Я часто сам себе питаю — як так могло статися? Адже Леорік " -"був найбільш праведним серед нас. Тільки найпотворніші та найчорніші сили " -"Пекла могли спотворити сердце Короля…" +#: Source/translation_dummy.cpp:323 +msgid "Ring Mail" +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" -"Лицарі і Священники Короля намагалися умилостивити його, тоді він обрушив " -"всю свою лють на них. Лицарі були змушені його вбити. На останніх хвилинах " -"життя, Король прокляв свою колишню свиту. Він поклявся, що ті хто " -"воспротивилися йому будуть завжди підчинятися Королю в темряві…\n" -"\n" -"Після цього все стало ще гірше ніж було! Мертвий Король встав зі свого " -"вічного сну і тепер керує військом нечисті в Лабіринті. Його тіло поховано у " -"склепі на третьому підземному рівні під Собором. Прошу тебе, Майстре, " -"звільни його заблудшу душу та зруйнуй його осквернене тіло…" +#: Source/translation_dummy.cpp:324 Source/translation_dummy.cpp:326 +#: Source/translation_dummy.cpp:328 Source/translation_dummy.cpp:332 +msgid "Mail" +msgstr "Кольчуга" -#. 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 "" -"Мастре, як я вам казав, Король був похований трьома рівнями нижче. Він там, " -"внизу, чекає свого шансу знищити цю землю в гнилій темряві…" +#: Source/translation_dummy.cpp:325 +msgid "Chain Mail" +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 "" -"Прокляття нашого Короля минуло, але я боюся, що тут діє більше зло. Проте " -"твоя перемога — добрий знак, що ми ще можемо бути врятовані від темряви, що " -"поглинає нашу землю. Хай Світло веде тебе на твоєму шляху, майстре." +#: Source/translation_dummy.cpp:327 +msgid "Scale Mail" +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 "" -"Втрата сина була занадто великою трагедією для Короля Леорика. Я зробив усе, " -"що міг, щоб полегшити його безумство, але врешті-решт воно його подолало. З " -"того дня над цим королівством нависло чорне прокляття, але, можливо, якби ти " -"звільнив його дух із земної в’язниці, прокляття було б знято…" +#: Source/translation_dummy.cpp:329 +msgid "Breast Plate" +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 "" -"Я не хочу думати про те, як помер Король. Я хочу запам'ятати його як добрим " -"і справедливим правителем. Його смерть була такою сумною і якось дуже " -"неправильною." +#: Source/translation_dummy.cpp:330 Source/translation_dummy.cpp:334 +#: Source/translation_dummy.cpp:336 Source/translation_dummy.cpp:338 +#: Source/translation_dummy.cpp:340 +msgid "Plate" +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 "" -"Я зробив багато зброї та більшість обладунків, які Король Леорік " -"використовував для спорядження своїх лицарів. Я навіть зробив для нього " -"величезний дворучний меч з найкращого міфрилу, а також польову корону в " -"придачу. Я досі не можу повірити, як він помер, але, мабуть, якась зловісна " -"сила звела його з розуму!" +#: Source/translation_dummy.cpp:331 +msgid "Splint Mail" +msgstr "Шинні Лати" + +#: Source/translation_dummy.cpp:333 +msgid "Plate Mail" +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 "" -"Мене це не хвилює. Слухай, ніякий скелет не буде МОЇМ королем. Леорік — " -"Король. Король, ти мене чуєш? СЛАВА КОРОЛЮ!" +#: Source/translation_dummy.cpp:335 +msgid "Field Plate" +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 "" -"Мертві, що ходять серед живих, слідують за проклятим Королем. Він володіє " -"силою підняти ще більше воїнів для постійно зростаючої армії нежиті. Якщо ти " -"не зупиниш його правління, він неодмінно пройде по цій землі і вб’є всіх, " -"хто ще тут живе." +#: Source/translation_dummy.cpp:337 +msgid "Gothic Plate" +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 "" -"Дивись, я веду тут бізнес. Я не продаю інформацію, і мені байдуже до якогось " -"Короля, який був мертвий довше, ніж я живу. Якщо тобі потрібна якась зброя " -"проти цього Короля нежиті, то я можу тобі допомогти…" +#: Source/translation_dummy.cpp:339 +msgid "Full Plate Mail" +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 "" -"Тепло життя увійшло в мою могилу. Приготуйся вічно служити моєму Господарю, " -"смертний!" +#: Source/translation_dummy.cpp:342 Source/translation_dummy.cpp:344 +#: Source/translation_dummy.cpp:346 Source/translation_dummy.cpp:348 +#: Source/translation_dummy.cpp:350 Source/translation_dummy.cpp:352 +msgid "Shield" +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 "" -"Я бачу, ця дивна поведінка і тебе морочить. Я припускаю, що оскільки демони " -"бояться світла сонця і вірять, що воно володіє великою силою, то можливо, " -"зображення на знаку сонця, що сходить, змусило їх повірити, що знак теж " -"володіє таємними силами. Хм, можливо, не всі вони такі розумні, як ми " -"боялися…" +#: Source/translation_dummy.cpp:343 +msgid "Small Shield" +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 "" -"Учителю, у мене стався дивний випадок. Я знаю, що ти багато знаєш про тих " -"чудовиськ, які мешкають у лабіринті, але це те, чого я не можу зрозуміти… " -"Уночі мене розбудив шкряпаючий звук біля моєї таверни. Коли я виглянув зі " -"своєї спальні, я побачив тіні маленьких демоноподібних створінь у дворі " -"корчми. Через деякий час вони втекли, але не раніше, ніж вкрали табличку до " -"моєї корчми. Я не знаю, чому демони крадуть мій знак, але залишають мою " -"родину в спокої… дивно, правда?" +#: Source/translation_dummy.cpp:345 +msgid "Large Shield" +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 "" -"О, тобі не було потрібно повертати мій знак, але виходить, що це мені " -"заощадить витрати на виготовлення нового. Що ж, зараз подивлюсь, що б я міг " -"би дати тобі в винагороду? Хм, що в нас тут є… ах, ось! Колись чарівник " -"залишив цю шапку в кімнаті. Можливо, це тобі пригодиться." +#: Source/translation_dummy.cpp:347 +msgid "Kite Shield" +msgstr "Краплеподібний Щит" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Боже мій, демони бігають вночі по селу, грабують наші домівки — хіба немає " -"нічого святого? Сподіваюся, що з Огденом і Гардою все гаразд. Хіба що, вони " -"б прийшли до мене, якби їх поранили…" +#: Source/translation_dummy.cpp:349 +msgid "Tower Shield" +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 "" -"О Боже! Так от куди дівся знак? Напевно, ми з бабусею все це проспали. Слава " -"Світлу, що ті чудовиська не напали на таверну." +#: Source/translation_dummy.cpp:351 +msgid "Gothic Shield" +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" -"Демони хочуть вирвати твоє серце, а не якийсь знак таверни." +#: Source/translation_dummy.cpp:357 +msgid "Potion of Rejuvenation" +msgstr "Зілля Омолодження" -#. 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 "" -"Знаєш, що я думаю? Хтось взяв цей знак, і вони хочуть за нього багато " -"грошей. Якби я був Огденом… А я не він, але якби був… Я б просто купив новий " -"знак із гарним малюнком. Щоб там був гарний кухоль елю або шматочок сиру…" +#: Source/translation_dummy.cpp:358 +msgid "Potion of Full Rejuvenation" +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" -"Ніколи не давай їм заплутати тебе своїми непомірними вчинками, оскільки це " -"теж може бути їх планом." +#: Source/translation_dummy.cpp:362 +msgid "Oil" +msgstr "Масло" -#. 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" -"Я вже кілька місяців тому подолав бажання касти знаки. На шматку дерева " -"прибуток не отримаєш." +#: Source/translation_dummy.cpp:363 +msgid "Elixir of Strength" +msgstr "Еліксир Сили" -#. 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 "" -"Гей — Ти той, що всіх вбиваєш! Ти даєш мені Магічний Прапор, або ми " -"атакуємо! Ти не підеш з життям! Ти вбиваєш великих потвор і повертаєш Магію. " -"Пройди повз кут і двері, знайди потвор. Ти даєш, ти йдеш!" +#: Source/translation_dummy.cpp:364 +msgid "Elixir of Magic" +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 "Ти вбиваєш потвор, береш прапор. Ти принеси мені, а то…" +#: Source/translation_dummy.cpp:365 +msgid "Elixir of Dexterity" +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 "" -"Ти даєш! Так, добре! Іди зараз, ми сильні. Ми вб'ємо всіх великою Магією!" +#: Source/translation_dummy.cpp:366 +msgid "Elixir of Vitality" +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" -"Це, мабуть і є те, що він запланував! Ті, хто залишився в живих із його " -"«рятівної групи», кажуть, що Лазаря востаннє бачили втікаючим у найглибші " -"надра лабіринту. Ти маєш поквапитися і врятувати принца від жертовного леза " -"цього божевільного негідника!" +#: Source/translation_dummy.cpp:367 +msgid "Scroll of Healing" +msgstr "Сувій Зцілення" + +#: Source/translation_dummy.cpp:368 +msgid "Scroll of Search" +msgstr "Сувій Пошуку" -#. 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 "" -"Ти повинен поспішити і врятувати Альбрехта з рук Лазаря. Принц і жителі " -"королівства розраховують на тебе!" +#: Source/translation_dummy.cpp:369 +msgid "Scroll of Lightning" +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" -" \n" -"Знай це, друже мій — Зло, проти якого ти борешся, це темний Володар Жахів. " -"Він відомий смертним як Діабло. Саме він був ув’язнений у Лабіринті багато " -"століть тому, і я боюся, що він знову прагне посіяти хаос у виміру людства. " -"Ти маєш пройти через портал і знищити Діабло поки не пізно!" +#: Source/translation_dummy.cpp:372 +msgid "Scroll of Fire Wall" +msgstr "Сувій Стіни Вогню" -#. 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 "" -"Лазар був Архієпископом, який ввів городян у лабіринт. Того дня я втратив " -"багато хороших друзів, а Лазар так і не повернувся. Думаю, що його вбили " -"разом з іншими. Майстре, якщо ти зробиш мені послугу, будь ласка, не говори " -"про той день з Фарнхемом." +#: Source/translation_dummy.cpp:373 +msgid "Scroll of Inferno" +msgstr "Сувій Пекла" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Я був шокований, коли почув, що жителі міста планували робити тієї ночі. Я " -"думав, що Лазар мав більше глузду, з усіх людей. Він був Архієпископом і, " -"здавалося, завжди дбав про жителів Трістрама. Було так багато поранених, я " -"не міг їх усіх врятувати…" +#: Source/translation_dummy.cpp:375 +msgid "Scroll of Flash" +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 "" -"Я пам’ятаю Лазаря як дуже доброго і доброзичливого чоловіка. Він виступав на " -"похороні моєї матері і підтримував мене і мою бабусю в дуже скрутний час. Я " -"молюся щовечора, щоб він як-небудь був живий і в безпеці." +#: Source/translation_dummy.cpp:376 +msgid "Scroll of Infravision" +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 "" -"Я був там, коли Лазар ввів нас у лабіринт. Він говорив про святу відплату, " -"але коли ми почали боротися з тими пекельними виродками, він їх пальцем не " -"торкнувся. Він просто побіг глибше в ті темні безкінечні палати, що " -"наповнені слугами темряви!" +#: Source/translation_dummy.cpp:377 +msgid "Scroll of Phasing" +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 "" -"Вони колють, потім кусають, потім вони всюди, навколо вас. Брехун! БРЕХУН! " -"Вони всі мертві! Мертві! Ти мене чуєш? Вони падають і падають… їх кров " -"розливається по підлозі… це все його вина…" +#: Source/translation_dummy.cpp:378 +msgid "Scroll of Mana Shield" +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 "" -"Я не знала Лазаря, про якого ти говориш, але відчуваю великий конфлікт " -"всередині його душі. Він представляє велику небезпеку і ні перед чим не " -"зупиниться, щоб служити силам темряви, які визнали його своїм." +#: Source/translation_dummy.cpp:379 +msgid "Scroll of Flame Wave" +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 "" -"А, праведний Лазар, який був таким корисним проти тих чудовиськ внизу. Мою " -"ногу не врятував, так же? Дивись, дам тобі пораду просто так. Спитай " -"Фарнхема, він там був." +#: Source/translation_dummy.cpp:380 +msgid "Scroll of Fireball" +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 "" -"Відмовся від своїх дурних пошуків. Все, що тебе чекає — лють мого Господаря! " -"Рятувати дитину вже пізно. Тепер ти приєднаєшся до нього в пеклі!" +#: Source/translation_dummy.cpp:381 +msgid "Scroll of Stone Curse" +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 "" -"Хм, не знаю що тобі сказати і що може стати в нагоді. Вода, яка наповнює " -"наші криниці надходить із підземного джерела. Я чув про тунель, що веде до " -"великого озера, можливо вони — одне й те саме. На жаль, я не знаю, що могло " -"б призвести до забруднення нашого джерела." +#: Source/translation_dummy.cpp:382 +msgid "Scroll of Chain Lightning" +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" -" \n" -"Будь ласка, зроби все, що можеш, або я не знаю, що ми будемо робити далі." +#: Source/translation_dummy.cpp:383 +msgid "Scroll of Guardian" +msgstr "Сувій Охоронця" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Я радий, що вчасно тебе наздогнав ! Вода в наших колодязях стала солонуватою " -"і застояною, і деякі городяни захворіли через неї. Наші запаси прісної води " -"швидко вичерпуються. Я вірю, що є прохід, який веде до джерел, які " -"постачають наше місто. Будь ласка, знайди що причинило це нещастя, інакше ми " -"всі неодмінно загинемо." +#: Source/translation_dummy.cpp:384 +msgid "Scroll of Nova" +msgstr "Сувій Нови" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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" -" \n" -"Довго прожити без твоєї допомоги ми не зможемо." +#: Source/translation_dummy.cpp:385 +msgid "Scroll of Golem" +msgstr "Сувій Голему" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"То ти кажеш, що сама присутність демонів спричинила забруднення води? О, під " -"нашим містом ховається справді велике зло, але твоя наполегливість і " -"мужність дає нам надію. Візьми цей перстень, будь ласка — можливо, він " -"допоможе тобі в знищенні цих підлих істот." +#: Source/translation_dummy.cpp:386 +msgid "Scroll of Teleport" +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 "" -"Моя бабуся дуже слабка, а Гарда каже, що ми не можемо пити воду з криниці. " -"Прошу, ти можеш нам чимось допомогти?" +#: Source/translation_dummy.cpp:387 +msgid "Scroll of Apocalypse" +msgstr "Сувій Апокаліпсису" + +#: Source/translation_dummy.cpp:388 Source/translation_dummy.cpp:389 +#: Source/translation_dummy.cpp:390 Source/translation_dummy.cpp:391 +msgid "Book of " +msgstr "Книга " + +#: Source/translation_dummy.cpp:396 +msgid "Falchion" +msgstr "Фальшіон" + +#: Source/translation_dummy.cpp:398 +msgid "Scimitar" +msgstr "Ятаган" + +#: Source/translation_dummy.cpp:400 +msgid "Claymore" +msgstr "Клеймор" + +#: Source/translation_dummy.cpp:402 Source/translation_dummy.cpp:403 +msgid "Blade" +msgstr "Клинок" + +#: Source/translation_dummy.cpp:404 Source/translation_dummy.cpp:405 +msgid "Sabre" +msgstr "Шабля" + +#: Source/translation_dummy.cpp:406 +msgid "Long Sword" +msgstr "Довгий Меч" + +#: Source/translation_dummy.cpp:408 +msgid "Broad Sword" +msgstr "Широкий Меч" + +#: Source/translation_dummy.cpp:410 +msgid "Bastard Sword" +msgstr "Меч-Бастард" + +#: Source/translation_dummy.cpp:412 +msgid "Two-Handed Sword" +msgstr "Дворучний Меч" + +#: Source/translation_dummy.cpp:414 +msgid "Great Sword" +msgstr "Величезний Меч" + +#: Source/translation_dummy.cpp:416 +msgid "Small Axe" +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 "" -"Пепін сказав тобі правду. Прісна вода нам дуже скоро знадобиться. Я пробував " -"очистити один з колодязів поменше, але там пахне застійним брудом. Можливо, " -"засмічення іде з джерела." +#: Source/translation_dummy.cpp:417 Source/translation_dummy.cpp:418 +#: Source/translation_dummy.cpp:419 Source/translation_dummy.cpp:421 +#: Source/translation_dummy.cpp:423 Source/translation_dummy.cpp:425 +#: Source/translation_dummy.cpp:427 +msgid "Axe" +msgstr "Сокира" -#. TRANSLATORS: Quest dialog spoken by Farnham -#: Source/textdat.cpp:100 -msgid "You drink water?" -msgstr "Ти п'єш воду?" +#: Source/translation_dummy.cpp:420 +msgid "Large Axe" +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" -"Знай таке — демони знаходяться в центрі цієї справи, але вони не знають, що " -"вони наробили." +#: Source/translation_dummy.cpp:422 +msgid "Broad Axe" +msgstr "Широка Сокира" -#. 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 "" -"В цей раз, я з тобою. Мій бізнес, так би мовити, закінчиться, якщо немає " -"кому продавати. Краще дізнайся, що відбувається, і як найшвидше!" +#: Source/translation_dummy.cpp:424 +msgid "Battle Axe" +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 "" -"Книга, що говорить про палату з людських кісток? Ну, Палата Кості згадується " -"в деяких архаїчних творах, які я вивчав у бібліотеках Сходу. У цих томах " -"йшлося про те, що, коли Володарі підземного світу хотіли захистити великі " -"скарби, вони створили ділянки, де ті, хто загинули спробувавши вкрасти " -"скарби, будуть зобов’язані його захищати назавжди. Заплутаний, але на диво " -"відповідний кінець?" +#: Source/translation_dummy.cpp:426 +msgid "Great Axe" +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 "" -"Боюся, що я нічого про це не знаю, Майстре. У Каїна є багато книг, які " -"можуть стати в нагоді." +#: Source/translation_dummy.cpp:428 Source/translation_dummy.cpp:429 +#: Source/translation_dummy.cpp:431 +msgid "Mace" +msgstr "Булава" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: Source/textdat.cpp:109 -msgid "" -"This sounds like a very dangerous place. If you venture there, please take " -"great care." -msgstr "" -"Звучить як дуже небезпечне місце. Якщо ти ризикнеш підти туди, будь " -"обережним." +#: Source/translation_dummy.cpp:430 +msgid "Morning Star" +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 "" -"Боюся, що я нічого про це не чув. Можливо, Каїн Розповідач міг би допомогти." +#: Source/translation_dummy.cpp:432 +msgid "War Hammer" +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 "" -"Я нічого не знаю про те місце, але ти можеш спитати у Каїна. Він говорить " -"про багато речей, і я б не здивувався, якби він мав відповіді на твої " -"питання." +#: Source/translation_dummy.cpp:433 +msgid "Hammer" +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 "" -"Гаразд, слухай. Є ось ця дерев'яна палата. А його дружина, ну знаєш, вона, " -"говорить дереву… що треба почекати. Тоді я кажу, що це може спрацювати проти " -"нього, але якщо ти думаєш, що я ЗАПЛАЧУ за це… ти… ем… так." +#: Source/translation_dummy.cpp:434 +msgid "Spiked Club" +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" -" \n" -"Іди в Палату Кості на свій страх і ризик." +#: Source/translation_dummy.cpp:438 Source/translation_dummy.cpp:439 +msgid "Flail" +msgstr "Ціп" -#. 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 "" -"То ти кажеш, величезний і таємничий скарб? Мені було б цікаво взяти у тебе " -"пару речей… або, ще краще, хіба тобі не потрібні рідкісні та дорогі припаси, " -"щоб пережити це випробування?" +#: Source/translation_dummy.cpp:440 Source/translation_dummy.cpp:441 +msgid "Maul" +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 "" -"Схоже, архієпископ Лазар підбурив багатьох городян піти в Лабіринт, щоб " -"знайти зниклого сина Короля. Він зіграв на їхніх страхах і збив їх у шалену " -"юрбу. Жоден з них не був готовий до того, що лежало під землею… Лазар " -"покинув їх там, унизу — залишив у лапах невимовних жахів на смерть." +#: Source/translation_dummy.cpp:443 Source/translation_dummy.cpp:445 +#: Source/translation_dummy.cpp:447 Source/translation_dummy.cpp:449 +#: Source/translation_dummy.cpp:451 Source/translation_dummy.cpp:453 +#: Source/translation_dummy.cpp:455 Source/translation_dummy.cpp:457 +msgid "Bow" +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 "" -"Так, Фарнхем бурмотів щось про незграбного звіра, який володів лютою зброєю. " -"Схоже, він назвав його м’ясником." +#: Source/translation_dummy.cpp:444 +msgid "Hunter's Bow" +msgstr "Мисливський Лук" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Заради Світла, я знаю цього мерзенного демона. Коли ті, хто пережив атаку, " -"яку вів Лазарь, виповзли з собору — було багато тих, хто носив шрами його " -"гніву на своїх тілах. Я не знаю, чим він розрізав своїх жертв, але це не " -"могло бути з цього світу. Воно лишало рани, нагноєні хворобою, і я бачив, що " -"їх майже неможливо вилікувати. Будь обережним, якщо плануєш битися з цим " -"негідником…" +#: Source/translation_dummy.cpp:446 +msgid "Long Bow" +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 "" -"Коли Фарнхем сказав щось про м’ясника, який вбиває людей, я не сприйняла це " -"всерйоз. Але оскільки ти про це кажеш, то можливо, це правда." +#: Source/translation_dummy.cpp:448 +msgid "Composite Bow" +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 "" -"Я бачив те, що Фарнхем називає М’ясником, коли воно прорізало шлях крізь " -"моїх друзів. Воно махнуло тесаком, завбільшки з сокиру, рубало кінцівки й " -"вирізало хоробрих людей там, де вони стояли. Я був відділений від бою " -"безліччю маленьких верескливих демонів і якось знайшов сходи, що ведуть " -"наверх. Я більше ніколи не бачив цього жахливого звіра, але його " -"закривавлене обличчя переслідує мене і донині." +#: Source/translation_dummy.cpp:450 +msgid "Short Battle Bow" +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 "" -"Великий! Великий тесак вбиває всіх моїх друзів. Не міг зупинити його, " -"довелося тікати, не міг їх врятувати. У пастці, так багато трупів… так " -"багато друзів… НІІІІІІІІІІ!" +#: Source/translation_dummy.cpp:452 +msgid "Long Battle Bow" +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 "" -"М’ясник — садистська істота, яка насолоджується тортурами та болем інших. Ти " -"бачив його роботу в п’яниці Фарнхемі. Його знищення зробить багато щоб " -"забезпечити безпеку цього села." +#: Source/translation_dummy.cpp:454 +msgid "Short War Bow" +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" -"Скажу прямо – вбий його, перш ніж він уб’є тебе і додасть твій труп до своєї " -"колекції." +#: Source/translation_dummy.cpp:456 +msgid "Long War Bow" +msgstr "Довгий Воєнний Лук" + +#: Source/translation_dummy.cpp:460 +msgid "Long Staff" +msgstr "Довгий Посох" -#. 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 "" -"Прошу тебе, вислухай. Архієпископ Лазар, він привів нас сюди, щоб знайти " -"загубленого принца. Мерзотник завів нас у пастку! Тепер усі мертві… убиті " -"демоном, якого він назвав М’ясником. Помстись за нас! Знайди цього М'ясника " -"і вбий його, щоб наші душі нарешті упокоїлись…" +#: Source/translation_dummy.cpp:462 +msgid "Composite Staff" +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" -"…Тьма огортає Прихованих. Очі, невидимо світяться, лише звуки гострих " -"кігтів, які шкрябають, щоб мучити ті бідні душі, що втратили зір навіки. " -"В'язниця для тих проклятих називається Залами Сліпих…" +#: Source/translation_dummy.cpp:464 +msgid "Quarter Staff" +msgstr "Четвертковий Посох" -#. 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" -"Що? О, так… ну, ти міг би спитати, що знають інші." +#: Source/translation_dummy.cpp:466 +msgid "War Staff" +msgstr "Бойовий Посох" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Він здається дуже знайомим. Я пам’ятаю, як читав щось дуже схоже на цей " -"вірш, досліджуючи історію демонічних страждань. Там говорилося про місце " -"великого зла, що… почекай, ти ж туди не підеш?" +#: Source/translation_dummy.cpp:468 Source/translation_dummy.cpp:469 +#: Source/translation_dummy.cpp:470 Source/translation_dummy.cpp:471 +#: Source/translation_dummy.cpp:472 Source/translation_dummy.cpp:473 +msgid "Ring" +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 "" -"Якщо у тебе є питання про сліпоту, поговори з Пепіном. Він давав моїй бабусі " -"зілля, яке допомогло прояснити її зір, тож, можливо, він і тобі допоможе." +#: Source/translation_dummy.cpp:474 Source/translation_dummy.cpp:475 +#: Source/translation_dummy.cpp:476 Source/translation_dummy.cpp:477 +msgid "Amulet" +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 "" -"Боюся, що я не чув і не бачив місце, що б відповідало твоєму яскравому " -"опису, друже. Можливо, Каїн Розповідач міг би допомогти." +#: Source/translation_dummy.cpp:478 +msgid "Rune of Fire" +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 "Глянь сюди… Смішно, правда? Зрозумів? Сліпий — глянь сюди?" +#: Source/translation_dummy.cpp:479 Source/translation_dummy.cpp:481 +#: Source/translation_dummy.cpp:483 Source/translation_dummy.cpp:485 +#: Source/translation_dummy.cpp:487 +msgid "Rune" +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" -"Будь обережним, інакше ти сам надовго там залишишся." +#: Source/translation_dummy.cpp:480 +msgid "Rune of Lightning" +msgstr "Руна Блискавки" -#. 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 "" -"Подивимося, я тобі щось продаю? Ні. Ти даєш мені гроші, щоб я тобі " -"розказував? Ні. Ти зараз підеш і будеш говорити з оповідачем, який живе " -"заради таких речей? Так." +#: Source/translation_dummy.cpp:482 +msgid "Greater Rune of Fire" +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" -"З тих, кого охопило прокляття короля, Лахданан найменше би підкорився " -"темряві без бою, тому схоже, що твоя історія правдива. Якби я був на твоєму " -"місці, друже, я б знайшов спосіб, як звільнити його від тортур." +#: Source/translation_dummy.cpp:484 +msgid "Greater Rune of Lightning" +msgstr "Більша Руна Блискавки" -#. 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 "" -"Ти говориш про відважного воїна, що давно помер! Ну ні, у моїй корчмі таких " -"балачок про бесіди з померлими не буде." +#: Source/translation_dummy.cpp:486 +msgid "Rune of Stone" +msgstr "Руна Каменю" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Значить, золотий еліксир. Я ніколи раніше не готував зілля такого кольору, " -"тому не можу сказати що станеться, якщо ти його вип'єш. Як твій цілитель, я " -"настійно раджу, що якщо ти знайдеш такий еліксир, роби так, як просить " -"Лахданан, і НЕ пробуй його на собі." +#: Source/translation_dummy.cpp:488 +msgid "Short Staff of Charged Bolt" +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 "" -"Я ніколи раніше не чула про Лахданана. Пробач, але я не зможу тут тобі " -"допомогти." +#: Source/translation_dummy.cpp:489 +msgid "Arena Potion" +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 "" -"Якщо ти насправді зустрів Лахданана, я б порадив допомогти йому. Я кілька " -"разів мав з ним справу вважаю його чесним і відданим за своєю природою. " -"Прокляття, яке впало на послідовників Короля Леоріка, особливо тяжко " -"обрушилося на нього." +#: Source/translation_dummy.cpp:490 +msgid "The Butcher's Cleaver" +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 "" -" Лахданан мертвий. Усі це знають, і ти не зможеш обдурити мене, щоб я " -"передумав. З мертвими не можна говорити. Я знаю!" +#: Source/translation_dummy.cpp:500 +msgid "The Rift Bow" +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" -"Я відчуваю в ньому честь і велику провину. Допоможи йому, і ти допоможеш " -"всьому Трістраму." +#: Source/translation_dummy.cpp:501 +msgid "The Needler" +msgstr "Голкостріл" -#. 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 "" -"Зачекай, дай вгадати. Каїна поглинула гігантська тріщина, що відкрилася під " -"ним. Його спалило кулею пекельного вогню, і він більше не зможе відповідати " -"на твої запитання. О, цього не сталося? Тоді, або щось купуй, або іди звідси." +#: Source/translation_dummy.cpp:502 +msgid "The Celestial Bow" +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" -"Я чув про Золотий еліксир, який міг би зняти прокляття і дати моїй душі " -"спокій, але я не зміг його знайти. Моя сила слабшає, а разом з цим і моя " -"остання людяність. Будь ласка, допоможи мені і знайди еліксир. Твої зусилля " -"я відплачу — клянусь своєю честю." +#: Source/translation_dummy.cpp:503 +msgid "Deadly Hunter" +msgstr "Смертельний Мисливець" + +#: Source/translation_dummy.cpp:504 +msgid "Bow of the Dead" +msgstr "Лук Мертвих" + +#: Source/translation_dummy.cpp:505 +msgid "The Blackoak Bow" +msgstr "Лук з Чорного Дуба" + +#: Source/translation_dummy.cpp:506 +msgid "Flamedart" +msgstr "Вогнедротик" -#. 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 "" -"Ти не знайшов Золотий еліксир. Я боюся, що навічно приречений. Прошу, не " -"здавайся…" +#: Source/translation_dummy.cpp:507 +msgid "Fleshstinger" +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 "" -"Ти врятував мою душу від прокляття, і за це в тебе в боргу. Якщо колись буде " -"спосіб відплатити тобі з-за могили, я знайду його, але поки — візьми мій " -"шолом. Він не знадобиться меня в подорожі, в яку я піду. Нехай він захистить " -"тебе від темних сил внизу. Іди зі Світлом, друже…" +#: Source/translation_dummy.cpp:508 +msgid "Windforce" +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 "" -"Грізволд говорить про Ковадло Люті — легендарний артефакт, який довго " -"шукали, але так і не знайшли. Створене з металевих кісток демонів з Лезової " -"Темниці, Ковадло Люті було виплавлене навколо черепів п’яти наймогутніших " -"магів підземного світу. На нім вирізані руни сили та хаосу, і будь-яка зброя " -"або броня, виковане на цьому ковадлі, буде занурене в вимір Хаосу, " -"наповнюючи її магічними властивостями. Кажуть, що через непередбачуваність " -"Хаосу важко знати, яким буде результат…" +#: Source/translation_dummy.cpp:509 +msgid "Eaglehorn" +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 "" -"Хіба не було б краще запитати про це Грізволда? Знаєш, він досить умілий." +#: Source/translation_dummy.cpp:510 +msgid "Gonnagal's Dirk" +msgstr "Кинджал Гоннагала" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Якби ти шукав інформацію про Товкачик Зцілення або Срібну Чашу Очищення, я " -"міг би тобі допомогти, друже. Однак у цій справі тобі краще поговорити з " -"Грізволдом або з Каїном." +#: Source/translation_dummy.cpp:511 +msgid "The Defender" +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 "" -"Коли ми росли, батько Грізволда розповідав нам про гігантське ковадло, на " -"якому виготовляли могутню зброю. Він сказав, що коли по цьому ковадлу " -"вдарять молотком, земля трясеться від великої люті. Я завжди згадую цю " -"історію коли земля трясеться." +#: Source/translation_dummy.cpp:512 +msgid "Gryphon's Claw" +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" -"Знайди мені це Ковадло, і я візьмуся за роботу!" +#: Source/translation_dummy.cpp:513 +msgid "Black Razor" +msgstr "Чорна Бритва" -#. 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 "" -"Ще нічого, а? Ну, продовжуй шукати. Зброя, викована на ковадлі, може бути " -"твоєю найбільшою надією, і я впевнений, що зможу зробити тобі легендарну " -"зброю." +#: Source/translation_dummy.cpp:514 +msgid "Gibbous Moon" +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 "" -"Я не можу в це повірити! Це Ковадло Люті - молодець, друже. Зараз ми " -"покажемо тим виродкам, що в пеклі немає зброї більш смертоносної, ніж та, " -"яку створили люди! Візьми це і нехай Світло захистить тебе." +#: Source/translation_dummy.cpp:515 +msgid "Ice Shank" +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 "" -"Грізволд не може продати своє ковадло. Що він тоді робитиме? І я б теж " -"розсердився, якби хтось узяв моє ковадло!" +#: Source/translation_dummy.cpp:516 +msgid "The Executioner's Blade" +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 "" -"У Лабіринті є багато артефактів, які володіють силами, що незбагненні для " -"смертних. Деякі з них мають фантастичну силу, яку може використати Світло, " -"або Темрява. Здобуття Ковадла з Лабіринту може зрушити хід Війни Гріхів у " -"бік Світла." +#: Source/translation_dummy.cpp:517 +msgid "The Bonesaw" +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 "" -"Якби ти знайшли цей артефакт для Грізволда, це могло б серйозно зашкодити " -"моєму бізнесу. Оооо, ти ніколи його не знайдеш." +#: Source/translation_dummy.cpp:518 +msgid "Shadowhawk" +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" -"Перед смертю Аркейна, його обладунки були заховані в таємному сховищі. " -"Кажуть, що коли цей святий обладунок знову знадобиться, постане герой, що " -"надягне Доблесть. Можливо, ти і є той герой…" +#: Source/translation_dummy.cpp:519 +msgid "Wizardspike" +msgstr "Вістря Мага" -#. 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 "" -"Кожна дитина чує історію воїна Аркейна та його містичної броні, відомої як " -"Доблесть. Якби ти зміг знайти його могилу, ти був би добре захищений від зла " -"в Лабіринті." +#: Source/translation_dummy.cpp:520 +msgid "Lightsabre" +msgstr "Світловий Меч" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Хм… звучить як щось, що я повинен пам’ятати, але я був настільки зайнятий " -"вивченням нових ліків і створенням кращих еліксирів, що, мабуть, забув. " -"Вибач…" +#: Source/translation_dummy.cpp:521 +msgid "The Falcon's Talon" +msgstr "Кіготь Сокола" + +#: Source/translation_dummy.cpp:522 +msgid "Inferno" +msgstr "Інферно" + +#: Source/translation_dummy.cpp:523 +msgid "Doombringer" +msgstr "Погибельник" + +#: Source/translation_dummy.cpp:524 +msgid "The Grizzly" +msgstr "Грізлі" + +#: Source/translation_dummy.cpp:525 +msgid "The Grandfather" +msgstr "Прадід" + +#: Source/translation_dummy.cpp:526 +msgid "The Mangler" +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 "" -"Я часто чула, як хлопці розповідали про чарівну броню під назвою Доблесть. " -"Краще запитай в когось із чоловіків у селі." +#: Source/translation_dummy.cpp:527 +msgid "Sharp Beak" +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 "" -"Броня, відома як Доблесть, може стати тим, що схилить ваги на твою користь. " -"Скажу тобі, що багато хто її шукав, в тому числі і я. Аркейн добре сховав " -"її, друже, і знадобиться більше ніж трохи удачі, щоб розкрити таємниці що " -"ховали її, ось так багато років." +#: Source/translation_dummy.cpp:528 +msgid "BloodSlayer" +msgstr "Крововбивця" -#. TRANSLATORS: Quest dialog "spoken" by Farnham -#: Source/textdat.cpp:208 -msgid "Zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz..." -msgstr "Хрррррррррррррррррррррррррррр…" +#: Source/translation_dummy.cpp:529 +msgid "The Celestial Axe" +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" -"Шлях повен небезпеки, і твоя єдина надія полягає у вірі в себе." +#: Source/translation_dummy.cpp:530 +msgid "Wicked Axe" +msgstr "Зла Сокира" -#. 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" -"Ніхто ніколи не дізнався, де Аркейн сховав речі, і якщо навіть мої контакти " -"не змогли їх знайти, я сумніваюся, що і ти коли-небудь знайдеш." +#: Source/translation_dummy.cpp:531 +msgid "Stonecleaver" +msgstr "Каменокол" -#. 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" -"Також написано, що, хоча він колись був смертним, що воював поруч із " -"Легіоном Темряви під час Війни гріхів, він втратив свою людяність через " -"ненаситну кровожерність." +#: Source/translation_dummy.cpp:532 +msgid "Aguinara's Hatchet" +msgstr "Томагавк Агінари" -#. 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 "" -"Боюся, що я нічого не чув про такого злісного воїна, добрий майстре. " -"Сподіваюся, вам не доведеться з ним битися, бо він здається дуже небезпечним." +#: Source/translation_dummy.cpp:533 +msgid "Hellslayer" +msgstr "Пекельний Вбивця" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Каїн розповість тобі набагато більше про щось подібне, ніж я хотів би знати." +#: Source/translation_dummy.cpp:534 +msgid "Messerschmidt's Reaver" +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 "" -"Якщо тобі треба битися з таким запеклим супротивником, то нехай Світло буде " -"твоїм провідником і захисником. Я буду думати про тебе." +#: Source/translation_dummy.cpp:535 +msgid "Crackrust" +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 "" -"Темні та злі легенди оточують Воєводу Крові. Будь готовим, мій друже, бо він " -"не покаже милосердя і не почує поблажок." +#: Source/translation_dummy.cpp:536 +msgid "Hammer of Jholm" +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 "" -"Завжди треба говорити про Кров? А як щодо квітів, сонця та тієї гарної " -"дівчини, що приносить випивку. Слухай сюди, друже - ти настирний, ти це " -"знаєш?" +#: Source/translation_dummy.cpp:537 +msgid "Civerb's Cudgel" +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 "" -"Його майстерність меча вражає, і він прожив тисячі років, знаючи лише війну. " -"Мені жаль… Я не бачу, чи зможеш ти перемогти його." +#: Source/translation_dummy.cpp:538 +msgid "The Celestial Star" +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 "" -"Я ніколи не мав справи з цим Воєводою, що ти говориш, але здається, що в " -"нього часто ламаються мечі. Я не проти, щоб постачати його армію…" +#: Source/translation_dummy.cpp:539 +msgid "Baranar's Star" +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 "" -"Мій клинок співає по твоїй крові, смертний, і моїм темнім господарям не буде " -"відмови." +#: Source/translation_dummy.cpp:540 +msgid "Gnarled Root" +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 "" -"Грізволд говорить про Небесний Камінь, що був призначений для анклаву, " -"розташованому на сході. Його везли туди для подальшого дослідження. Цей " -"камінь світився енергією, яка якимось чином надавала бачення, що виходить за " -"межі того, чим могла володіти звичайна людина. Я не знаю, які секрети він " -"приховує, друже, але знайти цей камінь, безумовно, виявилося б найціннішим." +#: Source/translation_dummy.cpp:541 +msgid "The Cranium Basher" +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 "" -"Караван зупинився тут, щоб взяти припаси для їх подорожі на схід. Я продав " -"їм чимало свіжих фруктів і чудового солодкового м'яса, яке Гарда тільки " -"закінчила випікати. Прикро що з ними сталося…" +#: Source/translation_dummy.cpp:542 +msgid "Schaefer's Hammer" +msgstr "Молот Шефера" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Я не знаю, що вони думали, що вони могли побачити з тим каменем, але скажу " -"так. Якщо з неба падає каміння, будь обережним!" +#: Source/translation_dummy.cpp:543 +msgid "Dreamflange" +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" -"Я не знаю, як ти сподіваєшся знайти те, що вони несли." +#: Source/translation_dummy.cpp:544 +msgid "Staff of Shadows" +msgstr "Посох Тіней" -#. 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 "" -"Залишся на хвилинку — у мене є історія, яка може тебе зацікавити. Якийсь час " -"потому, тут проходив караван, який прямував до східних королівств. Він " -"нібито ніс шматочок неба, що впав на землю! Вершники в плащах напали на " -"караван з засідки на північ звідси, біля дороги. Я обшукав уламки в пошуках " -"цієї небесної скелі, але її ніде не було. Якщо ти знайдеш її, то думаю, що " -"зможу виготовити з неї щось корисне." +#: Source/translation_dummy.cpp:545 +msgid "Immolator" +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 "" -"Я все ще чекаю, коли ти принесеш мені той камінь з небес. Я знаю, що можу " -"зробити з нього щось могутнє." +#: Source/translation_dummy.cpp:546 +msgid "Storm Spire" +msgstr "Штормовий Шпиль" + +#: Source/translation_dummy.cpp:547 +msgid "Gleamsong" +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" -"Ах, ось. Я розташував шматки каменю в срібному перстні, який залишив мені " -"батько. Сподіваюся, він добре тобі послужить." +#: Source/translation_dummy.cpp:548 +msgid "Thundercall" +msgstr "Поклик Грому" -#. 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 "" -"Колись у мене був гарний перстень; він був дуже дорогий, синій і зелений, і " -"червоний, і срібний. Не пам’ятаю, що з цим сталося. Я дуже сумую за цим " -"перстнем…" +#: Source/translation_dummy.cpp:549 +msgid "The Protector" +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 "" -"Небесний Камінь дуже могутній, і якби хтось, окрім Грізволда, запропонував " -"тобі його знайти, я б запобігла цьому. Він приборкає його силу, і ця сила " -"буде на благо всім нам." +#: Source/translation_dummy.cpp:550 +msgid "Naj's Puzzler" +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 "" -"Якщо хтось зможе щось зробити з того каменю, то це Грізволд. Він знає, що " -"робить, і я поважаю його майстерність, хай би я і намагаюся вкрасти його " -"клієнтів." +#: Source/translation_dummy.cpp:551 +msgid "Mindcry" +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 "" -"Відьма Адрія шукає чорний гриб? Я знаю про Чорні Гриби стільки ж, скільки " -"про Червоних Оселедців. Можливо, Цілитель Пепін розкаже більше, але такого " -"немає в жодній з моїх оповідань чи книжок." +#: Source/translation_dummy.cpp:552 +msgid "Rod of Onan" +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 "" -"Я ось що скажу. Ні Гарда, ні я, НІКОЛИ б не подали чорні гриби нашим " -"почесним гостям. Якщо Адрія хоче гриби у своєму рагу, то це її справа, але я " -"не можу допомогти в їх пошуку. Чорні гриби… Гидота!" +#: Source/translation_dummy.cpp:553 +msgid "Helm of Spirits" +msgstr "Шлем Духів" -#. TRANSLATORS: Quest dialog spoken by Pepin -#: 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 "" -"Відьма сказала мені, що ти шукаєш мозок демона, щоб допомогти мені створити " -"еліксир. Він має бути дуже цінним для тих, хто постраждав від тих мерзенних " -"звірів, якщо я зможу розкрити таємниці їхньої алхімії. Якщо ти зможеш " -"видалити мозок демона, коли його вб'єш, я був би вдячний, якби ти приніс " -"його мені." +#: Source/translation_dummy.cpp:554 +msgid "Thinking Cap" +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 "" -"Чудово, це саме те, що мені потрібно. Я зміг закінчити еліксир без нього, " -"але не завадить мати його для вивчення. Не міг б ти віднести це до відьми? Я " -"думаю, що вона чекає на це." +#: Source/translation_dummy.cpp:555 +msgid "OverLord's Helm" +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 "" -"Думаю, в погребі в Огдена можуть бути гриби. Чому б тобі не запитати його?" +#: Source/translation_dummy.cpp:556 +msgid "Fool's Crest" +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 "" -"Якщо його немає у Адрії, то можеш бути певним, що це справді рідкість. Я не " -"зможу допомогти тобі більше, але це звучить як… величезний, гігантський, " -"набряклий, роздутий гриб! Ну що ж, гарного полювання." +#: Source/translation_dummy.cpp:557 +msgid "Gotterdamerung" +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 "" -"Огден мішає ДОБРИЙ чорний гриб, але якщо я його вип’ю, мені стає погано. " -"Слухай, слухай… ось секрет - головне — помірність!" +#: Source/translation_dummy.cpp:558 +msgid "Royal Circlet" +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 "" -"Що ми тут маємо? Цікаво, схоже на книгу реактивів. Добре гляди, щоб знайти " -"чорний гриб. Його легко впізнати, він повинен бути досить великим. Якщо " -"знайдеш, його принеси мені, будь ласка?" +#: Source/translation_dummy.cpp:559 +msgid "Torn Flesh of Souls" +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 "" -"Мені потрібен великий чорний гриб. Тепер біжи і принеси його мені, щоб я " -"могла використати його для спеціального варева, над яким я працюю." +#: Source/translation_dummy.cpp:560 +msgid "The Gladiator's Bane" +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 "" -"Так, це ідеально підійде для варева, що я роблю. До речі, цілитель шукає " -"мозок якогось демона, щоб лікувати тих, кого вразила його згубна отрута. " -"Вважаю, що він хоче зробити з нього еліксир. Якщо ти допоможеш йому знайти " -"те, що потрібно, то будь ласка, подивись, чи можеш отримати для мене зразок " -"еліксиру." +#: Source/translation_dummy.cpp:561 +msgid "The Rainbow Cloak" +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 "" -"Чому ти приніс це сюди? Наразі мені не потрібен мозок демона. Мені потрібен " -"еліксир, над яким працює Цілитель. Йому потрібен той гротескний орган, який " -"ти тримаєш, а потім принеси мені еліксир. Доволі просто, якщо подумаєш, чи " -"не так?" +#: Source/translation_dummy.cpp:562 +msgid "Leather of Aut" +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 "" -"Що? Тепер ти приніс мені той еліксир від цілителя? Я змогла доробити варево " -"без нього. Чому б тобі не залишити його собі…" +#: Source/translation_dummy.cpp:563 +msgid "Wisdom's Wrap" +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 "" -"Я не продаю гриби будь-якого розміру чи кольору. Може тобі потрібне щось " -"більш корисне?" +#: Source/translation_dummy.cpp:564 +msgid "Sparking Mail" +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" -"Зло, якому ви протидієш, це темний Лордом Жаху, відомим смертним людям як " -"Діабло. Саме він був ув’язнений у Лабіринті багато століть тому. Карта, яку " -"ти зараз тримаєш, була створена багато років тому, щоб відзначити час, коли " -"Діабло знову повстане з ув’язнення. Коли дві зірки на цій карті зрівняються, " -"Діабло буде на піку своєї сили. Він буде майже непереможним...\n" -" \n" -"Тепер ти в гонці з часом, друже! Знайди Діабло та знищ його до того, як " -"зірки зійдуться, бо можливо у нас ніколи більше не буде шансу позбавити світ " -"від його зла!" +#: Source/translation_dummy.cpp:565 +msgid "Scavenger Carapace" +msgstr "Панцир Cміттяра" + +#: Source/translation_dummy.cpp:566 +msgid "Nightscape" +msgstr "Нічний Покров" + +#: Source/translation_dummy.cpp:567 +msgid "Naj's Light Plate" +msgstr "Легка Пластина Наджа" + +#: Source/translation_dummy.cpp:568 +msgid "Demonspike Coat" +msgstr "Мундир з Демоношипів" -#. 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 "" -"В нас немає часу! Я відчуваю його темну силу, і тільки ти можеш перешкодити " -"йому досягти повної сили." +#: Source/translation_dummy.cpp:569 +msgid "The Deflector" +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 "" -"Я впевнений, що ти старався як міг, але боюся, що навіть твоєї сили і волі " -"не вистачить. Діабло зараз на піку своєї земної могутності, і тобі " -"знадобиться вся твоя мужність і сила, щоб перемогти його. Нехай Світло " -"оберігає і веде тебе, друже. Я допоможу всім, чим зможу." +#: Source/translation_dummy.cpp:570 +msgid "Split Skull Shield" +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 "" -"Якщо відьма не може тобі допомогти і пропонує звернутися Каїна, чому ти " -"думаєш, що я щось знаю? Схоже, справа дуже серйозна. Поспіши і зустрінься з " -"розповідачем, як каже Адрія." +#: Source/translation_dummy.cpp:571 +msgid "Dragon's Breach" +msgstr "Пролом Дракона" -#. TRANSLATORS: Quest dialog spoken by Pepin (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" -"Я бачу, що це карта зірок на нашому небі, але більше за це я не вмію." +#: Source/translation_dummy.cpp:572 +msgid "Blackoak Shield" +msgstr "Щит з Чорного Дуба" -#. 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" -"Каїн дуже добре обізнаний у стародавніх писаннях, а це, безсумнівно, " -"найстаріший папірець, який я коли-небудь бачила." +#: Source/translation_dummy.cpp:573 +msgid "Holy Defender" +msgstr "Святий Захисник" -#. 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 "" -"Я ніколи не бачив такої карти. Де ти її взяв? Хоча я поняття не маю, як її " -"читати, можливо Каїн чи Адрія зможуть дати відповіді на твої питання." +#: Source/translation_dummy.cpp:574 +msgid "Stormshield" +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 "" -"Слухай сюди, підійди ближче. Не знаю, чи ти знаєш те, що я знаю, але у тебе " -"тут справді щось є. Це карта." +#: Source/translation_dummy.cpp:575 +msgid "Bramble" +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 "" -"О, боюся, це не передвіщає нічого хорошого. Ця карта зірок віщує велике " -"лихо, але не мені розказувати її секрети. Настав твій час для дуже серйозної " -"розмови з Розповідачем…" +#: Source/translation_dummy.cpp:576 +msgid "Ring of Regha" +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 "" -"Я шукав карту, але це точно не вона. Ти повинні показати її Адрії — вона, " -"напевно, розповість тобі, що це таке. Я скажу одне; вона виглядає старою, а " -"стара карта зазвичай означає що вона цінна." +#: Source/translation_dummy.cpp:577 +msgid "The Bleeder" +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 "" -"Прошууу, не бий. Не вбивай. Залиш живим і наступного разу принесу добро тобі." +#: Source/translation_dummy.cpp:578 +msgid "Constricting Ring" +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" -"Візьми це як доказ, що я тримаю слово…" +#: Source/translation_dummy.cpp:579 +msgid "Ring of Engagement" +msgstr "Перстень Заручення" -#. 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" -"Без болю і обіцянки я дотримаюся!" +#: Source/translation_dummy.cpp:580 +msgid "Giant's Knuckle" +msgstr "Кулак Гіганта" -#. 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 "Занадто добре для тебе. Дуже могутнє! Хочеш — забери!" +#: Source/translation_dummy.cpp:581 +msgid "Mercurial Ring" +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 "" -"Що?! Чому ти тут? Усі цих переривання зведуть мене з розуму. Візьми ось це і " -"дай мені працювати. Не турбуй мене більше!" +#: Source/translation_dummy.cpp:582 +msgid "Xorine's Ring" +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 "Аааа! Товя цікавість обійтеться тобі смертю!!!" +#: Source/translation_dummy.cpp:583 +msgid "Karik's Ring" +msgstr "Перстень Карика" -#. TRANSLATORS: Neutral dialog spoken by Cain -#: Source/textdat.cpp:308 -msgid "Hello, my friend. Stay awhile and listen..." -msgstr "Привіт, мій друже. Залишся і послухай…" +#: Source/translation_dummy.cpp:584 +msgid "Ring of Magma" +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" -"Читай їх уважно, бо вони розкажуть те, чого навіть я не знаю." +#: Source/translation_dummy.cpp:585 +msgid "Ring of the Mystics" +msgstr "Перстень Містиків" -#. 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 "" -"Я знаю багато міфів і легенд, які можуть мати відповіді на питання, що " -"виникнуть під час твоїх подорожей у Лабіринт. Якщо ти зіткнешся з задачами " -"чи запитаннями, які не знаєш, знайди мене, і я розскажу, що можу." +#: Source/translation_dummy.cpp:586 +msgid "Ring of Thunder" +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 "" -"Грізволд — людина великих дій і великої мужності. Б’юся об заклад, він " -"ніколи не розповідав тобі про те, як пішов у Лабіринт, щоб врятувати Вірта, " -"чи не так? Він знає свою долю небезпек, які там знаходяться, а тепер — і ти. " -"Він вправний майстер, і якщо він стверджує, що може чимось тобі допомогти, " -"то можеш розраховувати на його чесність і майстерність." +#: Source/translation_dummy.cpp:587 +msgid "Amulet of Warding" +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 "" -"Огден володіє та керує Таверною і Корчмою Висхідного Сонця вже майже чотири " -"роки. Він придбав його всього за кілька місяців, перш ніж все тут пійшло до " -"біса. У нього та його дружини Гарди немає грошей, щоб виїхати, оскільки вони " -"вклали все, що мали, щоб створити собі тут життя. Він хороша людина з " -"глибоким почуттям відповідальності." +#: Source/translation_dummy.cpp:588 +msgid "Gnat Sting" +msgstr "Укус Комара" + +#: Source/translation_dummy.cpp:589 +msgid "Flambeau" +msgstr "Фламбо" + +#: Source/translation_dummy.cpp:590 +msgid "Armor of Gloom" +msgstr "Броня Мороку" + +#: Source/translation_dummy.cpp:591 +msgid "Blitzen" +msgstr "Блітцен" + +#: Source/translation_dummy.cpp:592 +msgid "Thunderclap" +msgstr "Грім" + +#: Source/translation_dummy.cpp:593 +msgid "Shirotachi" +msgstr "Широтачі" + +#: Source/translation_dummy.cpp:594 +msgid "Eater of Souls" +msgstr "Пожирач Душ" + +#: Source/translation_dummy.cpp:595 +msgid "Diamondedge" +msgstr "Діамантове Лезо" + +#: Source/translation_dummy.cpp:596 +msgid "Bone Chain Armor" +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 "" -"Бідний Фарнхем. Він є тривожним нагадуванням про приречене зборище, яке " -"увійшло до собору з Лазарем того темного дня. Він утік живим, але його " -"хоробрість і глузд залишилися в тій темній ямі. Нині він знаходить втіху " -"лише на дні кухля, але час від часу в його нісенітницях є частинки правди." +#: Source/translation_dummy.cpp:597 +msgid "Demon Plate Armor" +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 "" -"Відьма Адрія — аномалія тут, у Трістрамі. Вона прибула невдовзі після того, " -"як собор було осквернено, поки більшість інших тікали. Вона здавалося б, за " -"ніч побудувала невелику хатину на околиці міста, і має при собі багато " -"дивних та загадкових артефактів і томів знань, яких навіть я ніколи раніше " -"не бачив." +#: Source/translation_dummy.cpp:598 +msgid "Acolyte's Amulet" +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 "" -"Історія Вірта страшна і трагічна. Маленькі мерзенні демони що мали гострі " -"списи забрали його з рук матері і потягли в лабіринт. Того дня забрали " -"багато інших дітей, разом з сином Короля Леоріка. Лицарі палацу пішли вниз, " -"але так і не повернулися. Коваль знайшов хлопчика, але не раніше, як " -"мерзенні звірі почали катувати його для своїх садистських втіх." +#: Source/translation_dummy.cpp:599 +msgid "Gladiator's Ring" +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 "" -"А, Пепін. Я вважаю його справжнім другом — можливо, найближчим з усіх. Він " -"трохи заплутаний часом, але ніколи не існувало більш турботливої чи уважної " -"душі. Його знання та навички мало кому зрівняються, і його двері завжди " -"відкриті." +#: Source/translation_dummy.cpp:600 +msgid "Tin" +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 "" -"Гілліан — хороша жінка. Її обожнюють за піднесений настрій і легкий сміх, " -"вона займає особливе місце в моєму серці. Вона залишається в таверні, щоб " -"підтримати свою літню бабусю, яка занадто хвора, щоб подорожувати. Я іноді " -"боюся за її безпеку, але знаю, що будь-який чоловік у селі воліє померти, " -"ніж побачити, як їй нашкодять." +#: Source/translation_dummy.cpp:601 +msgid "Brass" +msgstr "Латунний" -#. TRANSLATORS: Neutral dialog spoken by Ogden -#: Source/textdat.cpp:327 -msgid "Greetings, good master. Welcome to the Tavern of the Rising Sun!" -msgstr "Вітаю, добрий майстре. Ласкаво просимо до Таверни Висхідного Сонця!" +#: Source/translation_dummy.cpp:602 +msgid "Bronze" +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 "" -"Багато шукачів пригод сідали за столи моєї таверни, і в десять разів більше " -"історій було розказано, скільки елю було випито . Єдине, що я коли-небудь " -"чув, щоб хтось із них погодився, це стара аксіома. Можливо, вона допоможе " -"тобі. Плоть можна розрізати, але кістки треба трощити." +#: Source/translation_dummy.cpp:603 +msgid "Iron" +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 "" -"Коваль Грізволд дуже добре обізнаний зі зброєю та обладунками. Якщо тобі " -"коли-небудь знадобиться робота зі спорядженням, тоді звісно, треба ідти до " -"нього." +#: Source/translation_dummy.cpp:604 +msgid "Steel" +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 "" -"Фарнхем проводить тут занадто багато часу, топить свої печалі в дешевому " -"елі. Я б змусив його піти, але він все-таки так страждав в Лабіринті." +#: Source/translation_dummy.cpp:605 +msgid "Silver" +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" -"Ну, неважливо. Якщо тобі коли-небудь доведеться торгувати зачарованими " -"предметами, вона тримає добре укомплектовану хатину через річку." +#: Source/translation_dummy.cpp:607 +msgid "Platinum" +msgstr "Платиновий" -#. 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 "" -"Якщо ти хочеш дізнатися більше про історію села, Розповідач Каїн чимало знає " -"про наше минуле." +#: Source/translation_dummy.cpp:608 +msgid "Mithril" +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" -"Напевно, він дурів десь, де йому не слід було бути. Мені шкода хлопця, але " -"мені не подобається його компанія." +#: Source/translation_dummy.cpp:609 +msgid "Meteoric" +msgstr "Метеоритний" -#. 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 "" -"Пепін — добра людина — і, безперечно, найщедріша в селі. Він завжди " -"піклується про потреби інших, але здається, якісь неприємності супроводжують " -"його, куди б він не пішов…" +#: Source/translation_dummy.cpp:611 +msgid "Strange" +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" -"Бог знає, що я благав її піти, сказавши, що буду стежити за старою, але вона " -"занадто мила й турботлива, щоб це зробити." +#: Source/translation_dummy.cpp:612 +msgid "Useless" +msgstr "Непотрібний" -#. TRANSLATORS: Neutral dialog spoken by Pepin -#: Source/textdat.cpp:345 -msgid "What ails you, my friend?" -msgstr "Що турбує тебе, друже?" +#: Source/translation_dummy.cpp:613 +msgid "Bent" +msgstr "Погнутий" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" -"Я зробив дуже цікаве відкриття. На відміну від нас, істоти в Лабіринті " -"можуть зцілювати себе без допомоги зілля або магії. Якщо ти пораниш монстра, " -"переконайся, що він мертвий, інакше він може відновитися." +#: Source/translation_dummy.cpp:614 +msgid "Weak" +msgstr "Слабий" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" -"До того, як Собором заволоділо те, що ховається внизу, він був місцем " -"великого вивчання. Там можна знайти багато книг. Якщо ти знайдеш їх, " -"прочитай їх усі, оскільки деякі з них можуть берегти секрети Лабіринту." +#: Source/translation_dummy.cpp:615 +msgid "Jagged" +msgstr "Зазублений" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" -"Грізволд знає про мистецтво війни стільки ж, скільки я про мистецтво " -"зцілення. Він хитрий купець, але його робота не має собі рівних. О, я " -"припускаю, що це може тому, що він єдиний коваль, що тут залишився." +#: Source/translation_dummy.cpp:616 +msgid "Deadly" +msgstr "Смертельний" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" -"Каїн — справжній друг і великий мудрець. У нього велика бібліотека і він має " -"вроджену здатність розпізнати справжню природу речей. Якщо у тебе коли-" -"небудь виникнуть запитання, звертайся до нього." +#: Source/translation_dummy.cpp:617 +msgid "Heavy" +msgstr "Важкий" + +#: Source/translation_dummy.cpp:618 +msgid "Vicious" +msgstr "Розпутний" + +#: Source/translation_dummy.cpp:619 +msgid "Brutal" +msgstr "Брутальний" + +#: Source/translation_dummy.cpp:620 +msgid "Massive" +msgstr "Масивний" + +#: Source/translation_dummy.cpp:621 +msgid "Savage" +msgstr "Дикий" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" -"Навіть мій досвід не допоміг повністю вилікувати Фарнхема. О, я зміг зцілити " -"його тіло, але його розум і дух за межами моїх знань." +#: Source/translation_dummy.cpp:622 +msgid "Ruthless" +msgstr "Жорстокий" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" -"Хоча я використовую деякі обмежені форми магії для створення зілль та " -"еліксирів, Адрія — справжня чарівниця. Здається, вона ніколи не спить, і " -"вона завжди має багато містичних томів і артефактів. Я вважаю, що її хатина " -"набагато більша всередині, ніж ззовні, хоч я ніколи там і не був." +#: Source/translation_dummy.cpp:623 +msgid "Merciless" +msgstr "Безжалісний" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" -"Бідний Вірт. Я зробив для нього все можливе, але знаю, що він зневажає той " -"протез, який я змушений був прикріпити до його ноги. Його рани були " -"жахливими. Ніхто, а особливо такий молодий хлопець, не повинен страждати як " -"він." +#: Source/translation_dummy.cpp:624 +msgid "Clumsy" +msgstr "Незграбний" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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 "" -"Я справді не розумію, чому Огден залишається тут, у Трістрамі. Він страждає " -"від легкого нервового збудження, але він розумний і працьовитий чоловік, " -"скрізь досяг би успіху, куди б не пішов. Я припускаю, що це може бути страх " -"перед тими жахіттями, що відбуваються в навколо нашого села, або, можливо, " -"його дружина, не бажає нікуди йти." +#: Source/translation_dummy.cpp:625 +msgid "Dull" +msgstr "Тупий" -#. TRANSLATORS: Neutral dialog spoken by Pepin (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" -"Вона стверджує, що це видіння, але я не маю цьому жодних доказів." +#: Source/translation_dummy.cpp:626 +msgid "Sharp" +msgstr "Гострий" -#. TRANSLATORS: Neutral dialog spoken by Gillian -#: Source/textdat.cpp:364 -msgid "Good day! How may I serve you?" -msgstr "Добрий день! Чим я можу тобі услужити?" +#: Source/translation_dummy.cpp:627 Source/translation_dummy.cpp:637 +msgid "Fine" +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 "" -"Моїй бабусі наснилося, що ти прийдеш і поговориш зі мною. Знаєш, у неї " -"видіння, і вона бачить майбутнє." +#: Source/translation_dummy.cpp:628 +msgid "Warrior's" +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" -"Потрібна була б така смілива людина, як ти, щоб побачити, що вона там робить." +#: Source/translation_dummy.cpp:629 +msgid "Soldier's" +msgstr "Солдатський" -#. 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 "" -"Жителі Трістраму гордяться своїм Ковалем. Він не тільки умілий майстер, що " -"переміг у багатьох змаганнях своєї гільдії, але й отримав похвалу від самого " -"нашого Короля Леоріка — нехай його душа спочиває з миром. Грізволд також " -"великий герой; можеш спитати Каїна." +#: Source/translation_dummy.cpp:630 +msgid "Lord's" +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 "" -"Каїн був розповідачем Трістраму, скільки я себе пам’ятаю. Він дуже багато " -"знає і може розповісти тобі майже про все." +#: Source/translation_dummy.cpp:631 +msgid "Knight's" +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" -"Я знаю, що Пепін, і Огден симпатизують йому, але мене так засмучує, що я " -"бачу, як він щоночі все далі і далі впадає в п'яний дурман." +#: Source/translation_dummy.cpp:632 +msgid "Master's" +msgstr "Паньский" -#. 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 "" -"Пепін врятував життя моїй бабусі, і я знаю, що ніколи не зможу йому за це " -"віддячити. Його здатність зцілювати будь-яку хворобу потужніша, ніж " -"наймогутніший меч, і загадковіша, ніж будь-яке заклинання, яке ти можете " -"назвати. Якщо тобі коли-небудь знадобиться зцілення, Пепін тобі допоможе." +#: Source/translation_dummy.cpp:633 +msgid "Champion's" +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 "" -"Я виросла з матір'ю Вірта, Кенас. Хоча її лише трохи поранили, коли ті " -"огидні створіння вкрали його, вона так і не одужала. Я думаю, що вона " -"померла через розбите серце. Вірт став підлим юнаком, який прагне лише " -"наживитися на чужому поті. Я знаю, що він страждав і бачив жахи, які навіть " -"я уявити не можу, але частина цієї темряви нависає над ним досі." +#: Source/translation_dummy.cpp:634 +msgid "King's" +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 "" -"Огден та його дружина взяли мене та мою бабусю в свій дім і навіть дозволили " -"мені заробити кілька золотих, працюючи в корчмі. Я дуже багато завдячую їм і " -"сподіваюся, що колись покину це місце і допоможу їм відкрити грандіозний " -"готель на сході." +#: Source/translation_dummy.cpp:635 +msgid "Vulnerable" +msgstr "Уразливий" -#. TRANSLATORS: Neutral dialog spoken by Griswold -#: Source/textdat.cpp:381 -msgid "Well, what can I do for ya?" -msgstr "Ну, що я можу тобі зробити?" +#: Source/translation_dummy.cpp:636 +msgid "Rusted" +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 "" -"Якщо ти шукаєш добру зброю, дозволь мені показати тобі це. Наприклад, візьми " -"якусь тупу булаву. Проти більшості тих невмирущих жахів піде як по маслу, " -"немає нічого кращого, щоб розбивати сухі скелети!" +#: Source/translation_dummy.cpp:638 +msgid "Strong" +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 "" -"Сокира? Так, це хороша зброя, збалансована проти будь-якого ворога. " -"Подивись, як вона розсікає повітря, а потім уяви собі гарну товстенну голову " -"демона на її шляху. Май на увазі, що вона повільно розмахується — але який " -"удар!" +#: Source/translation_dummy.cpp:639 +msgid "Grand" +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 "" -"Подивись на це лезо, на рівновагу. У правильних руках і проти правильного " -"ворога, меч — майстер усієї зброї. Гострому лезу мало чого рубати чи " -"прорізати в нежиті, але проти живого, дихаючого ворога, немає нічого краще, " -"ніж меч!" +#: Source/translation_dummy.cpp:640 +msgid "Valiant" +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 "" -"Твоя зброя та броня показуватимуть ознаки боротьби з Темрявою. Якщо ти " -"принесеш їх мені, трохи попрацювавши в гарячій кузні, я зроблю їх як " -"новенькі." +#: Source/translation_dummy.cpp:641 +msgid "Glorious" +msgstr "Славетний" + +#: Source/translation_dummy.cpp:642 +msgid "Blessed" +msgstr "Благословенний" + +#: Source/translation_dummy.cpp:643 +msgid "Saintly" +msgstr "Праведний" + +#: Source/translation_dummy.cpp:644 +msgid "Awesome" +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 "" -"Мені доводиться практично провозити контрабандою потрібні мені метали та " -"інструменти з караванів, що огинають околиці нашого проклятого міста, а ця " -"відьма Адрія завжди отримує все, що їй потрібно. Якби я б хоч трохи знав як " -"приборкати магію, як вона, я б зробив справді неймовірні речі." +#: Source/translation_dummy.cpp:646 +msgid "Godly" +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 "" -"Гілліан — гарна дівчина. Шкода, що в її бабці таке погане здоров’я, інакше я " -"б влаштував, щоб вивезти їх звідси на торговому каравані." +#: Source/translation_dummy.cpp:647 +msgid "Red" +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 "" -"Іноді мені здається, що Каїн забагато говорить, але схоже, що це його " -"покликання в житті. Якби я міг гнути сталь так само, як він може гнути твоє " -"вухо, я б зробив лати, достойні Імператора!" +#: Source/translation_dummy.cpp:648 Source/translation_dummy.cpp:649 +msgid "Crimson" +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 "" -"Я був з Фарнхемом тоді, коли Лазар повів нас у Лабіринт. Я більше ніколи не " -"бачив Архієпископа, і, можливо б не вижив, якби Фарнхем не був поруч зі " -"мною. Я боюся, що напад залишив його душу так само покаліченою, як і мою " -"ногу. Я бився б за нього в цій битві якби міг, але не можу." +#: Source/translation_dummy.cpp:650 +msgid "Garnet" +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 "" -"Хороший чоловік, що ставить потреби інших вище своїх. Ти не знайдеш нікого в " -"Трістрамі — тай й де завгодно, якщо на те пішло — хто б сказав погане про " -"цілителя." +#: Source/translation_dummy.cpp:651 +msgid "Ruby" +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 "" -"Цей хлопець потрапить в серйозні неприємності… або, я мушу сказати, знову " -"потрапить. Я намагався зацікавити його роботою тут, в кузні, навчити чесному " -"ремеслу, але він віддає перевагу високим прибуткам від торгівлі сумнівними " -"товарами. Я не можу злитися на нього після того, що з ним сталося, але я " -"хотів, щоб він хоча б був обережний." +#: Source/translation_dummy.cpp:652 +msgid "Blue" +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 "" -"У корчмаря мало бізнесу і немає реального способу заробляти. Йому вдається " -"звести кінці з кінцями, забезпечуючи їжею та житлом тих, хто час від часу " -"ходить через село, але вони можуть втекти, не плативши. Якби не запаси зерна " -"та в’яленого м’яса, які він тримав у своєму погребі, то чому б більшість із " -"нас померли б від голоду в той перший рік, коли всю сільську місцевість " -"заполонили демони." +#: Source/translation_dummy.cpp:653 +msgid "Azure" +msgstr "Блакитний" -#. TRANSLATORS: Neutral dialog spoken by Farnham -#: Source/textdat.cpp:404 -msgid "Can't a fella drink in peace?" -msgstr "Хіба я не можу спокійно випити?" +#: Source/translation_dummy.cpp:654 +msgid "Lapis" +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 "Дівчина, що приносить напої? О, так, яка гарна жінка. І добра теж." +#: Source/translation_dummy.cpp:655 +msgid "Cobalt" +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 "" -"Чому б та стара карга щось не зробила. Звісно, звісно, у неї є речі, але " -"послухай мене… вона неприродня. Я ніколи не бачив, щоб вона їла чи пила — і " -"не можна вірити тому, хто хоч трохи не п’є." +#: Source/translation_dummy.cpp:656 +msgid "Sapphire" +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 "" -"Каїн не той, ким себе видає. Звичайно, звичайно, він розказує хороші " -"історії… деякі з них справді страшні або смішні… але я думаю, що він знає " -"більше, ніж знає, що знає." +#: Source/translation_dummy.cpp:657 +msgid "White" +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 "" -"Грізволд? Старий добрий Грізволд. Я люблю його як брата! Ми билися разом, " -"знаєш, коли… ми… Лазар… Лазар… Лазар!!!" +#: Source/translation_dummy.cpp:658 +msgid "Pearl" +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 "" -"Хе-хе, мені подобається Пепін. Знаєш, він справді старається. Слухай сюди, " -"ти маєш познайомитися з ним. Такий добрий хлопець з людьми, які завжди " -"хочуть допомоги. Гей, я думаю, це було б схоже на тебе, а герой? Я теж був " -"героєм…" +#: Source/translation_dummy.cpp:659 +msgid "Ivory" +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 "" -"У Вірта більше проблем, ніж у мене, а я все знаю про проблеми. Слухай сюди — " -"у того пацана добра угода, але він там був, розумієш? Втратив ногу! Треба " -"ходити на шматку дерева. Так сумно, так сумно…" +#: Source/translation_dummy.cpp:660 +msgid "Crystal" +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 "" -"Огден — найкращий чоловік у селі. Не думаю, що я дуже подобаюся його " -"дружині, але поки вона буде відкривати бочки, вона нормальна. Я проводжу з " -"Огденом більше часу, ніж всі, але він такий добрий до мене…" +#: Source/translation_dummy.cpp:661 +msgid "Diamond" +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 "" -"Хочу тобі дещо сказати, бо я знаю все про ці штуки. Я в цьому знаюсь. Цей " -"ель ось тут найкращий…наааааайкращий! Той інший не годиться з тих пір, як " -"дурні собаки…" +#: Source/translation_dummy.cpp:662 +msgid "Topaz" +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 "" -"Мене ніхто ніколи не слу… слухає. Десь – я не знаю де, – але десь під " -"церквою є ціла купа золота. Блищить і сяє, і я просто чекаю, щоб хтось її " -"забрав." +#: Source/translation_dummy.cpp:663 +msgid "Amber" +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 "" -"Я знаю, що у тебе свої ідеї, і я знаю, що ти в це не повіриш, але та зброя, " -"що в тебе просто не годиться проти тих нелюдів! О, мені байдуже, що говорить " -"Грізволд, вони нічого не можуть зробити, як у старі часи…" +#: Source/translation_dummy.cpp:664 +msgid "Jade" +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 "" -"Якби я був тобою… а я не ти… але якби був, я б продав усе, що в тебе є, і " -"пішов звідси. Той хлопець ось там… У нього завжди є щось хороше, але тобі " -"треба дати йому трохи золота, інакше він навіть не покаже, що у нього є." +#: Source/translation_dummy.cpp:665 +msgid "Obsidian" +msgstr "Обсидіяновий" -#. TRANSLATORS: Neutral dialog spoken by Adria -#: Source/textdat.cpp:427 -msgid "I sense a soul in search of answers..." -msgstr "Я відчуваю душу в пошуках відповідей…" +#: Source/translation_dummy.cpp:666 +msgid "Emerald" +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 "" -"Мудрість заробляється, а не дається. Якщо ти знайдеш том знань, поглинай " -"його слова. Якщо ти вже знаєш про таємні секрети, записані в книзі, то " -"пам’ятай — рівень майстерності завжди може підвищитися." +#: Source/translation_dummy.cpp:667 +msgid "Hyena's" +msgstr "Гієнний" + +#: Source/translation_dummy.cpp:668 +msgid "Frog's" +msgstr "Жаб'ячий" + +#: Source/translation_dummy.cpp:669 +msgid "Spider's" +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 "" -"Найбільша сила часто триває найкоротше. Ти можеш знайти стародавні слова " -"сили, написані на пергаментних сувоях. Сила цих сувоїв полягає тому, що і " -"учень, і адепт чарує їх з однаковим умінням. Їх слабкість полягає в тому, що " -"їх треба прочитати вголос, і неможливо втримати в голові. Також знай, що " -"такі сувої можна прочитати лише раз, тому використовуй їх обережно." +#: Source/translation_dummy.cpp:670 +msgid "Raven's" +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 "" -"Хоча сонячна спека безмірна, просте полум’я свічки становить більшу " -"небезпеку. Жодна енергія, якою б великою вона не була, не може бути " -"використана без належного зосередження. Для багатьох заклинань зачаровані " -"Посохи можна заряджати магічною енергією багато раз. Я можу відновити їхню " -"силу - але знай, що нічого не робиться безкоштовно." +#: Source/translation_dummy.cpp:671 +msgid "Snake's" +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 "" -"Сума наших знань — у сумі її людей. Якщо ти знайдеш книгу чи сувій, що ти не " -"можете розшифрувати, не соромся принести його мені. Я поділюся тим, що " -"знайшла, якщо я можу зрозуміти їх." +#: Source/translation_dummy.cpp:672 +msgid "Serpent's" +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 "" -"Для людини, що знає лише Залізо, немає більшої магії, ніж Сталь. Коваль " -"Грізволд більше чаклує, ніж знає. Його здатності об'єднувати вогонь і метал " -"не має рівних на цій землі." +#: Source/translation_dummy.cpp:673 +msgid "Drake's" +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 "" -"Псування має силу обману, але невинність володіє могутністю чистоти. Молода " -"жінка Гілліан має чисте серце, ставить потреби свого матріарха над своїми. " -"Вона боїться мене, але тільки тому, що не розуміє мене." +#: Source/translation_dummy.cpp:674 +msgid "Dragon's" +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 "" -"Скриня, відкрита в темряві, не має більше скарбу, ніж коли її відкрили на " -"світлі. Розповідач Каїн — загадкова людина, але тільки для тих, хто не " -"дивиться. Його знання про те, що лежить під собором, набагато більше, ніж " -"навіть він собі уявляє." +#: Source/translation_dummy.cpp:675 +msgid "Wyrm's" +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 "" -"Чим більше ти довіряєш одній людині, тим гірше розчарування. Фарнхем втратив " -"свою душу, але не від якогось демона. Вона була втрачена, коли він побачив " -"своїх односельчан, зраджених Архієпископом Лазарем. У нього є цінні знання, " -"але ти маєш відокремити факти від вигадок." +#: Source/translation_dummy.cpp:676 +msgid "Hydra's" +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 "" -"Рука, серце і розум можуть творити чудеса, коли вони в повній гармонії. " -"Цілитель Пепін бачить тіло так, як навіть я не можу. Його здатність цілити " -"хворих і поранених посилюється його розумінням варіння еліксирів і зілля. " -"Він твій найкращий союзник в Трістрамі." +#: Source/translation_dummy.cpp:677 +msgid "Angel's" +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 "" -"Є багато, чого ми не можемо побачити в майбутньому, але коли воно настане, " -"ним володітимуть діти. У хлопчика Вірта темнота на душі, але він не " -"становить загрози ні місту, ні його жителям. Його таємні стосунки з " -"шибениками та негласними гільдіями сусідніх міст дають йому доступ до " -"багатьох пристроїв, що нелегко знайти в Трістрамі. Хоча його методи можуть " -"бути докірливими, Вірт може допомогти тобі в боротьбі з Темрявою, що " -"вторгається." +#: Source/translation_dummy.cpp:678 +msgid "Arch-Angel's" +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 "" -"Земляні стіни та солом'яний навіс не зроблять дому. Корчмар Огден грає " -"більшу роль у місті, ніж багато хто розуміє. Він надає притулок для Гілліан " -"і її матріарха, підтримує те життя, що залишилось у Фарнхема, і є якорем для " -"всіх, хто залишився в місті, яким колись був Трістрам. Його таверна та " -"прості задоволення, які ще можна знайти там, дають хоч на мить побачити " -"життя, що було раніше. Саме ця пам’ять продовжує живити їхні надії на твій " -"успіх." +#: Source/translation_dummy.cpp:679 +msgid "Plentiful" +msgstr "Рясний" -#. TRANSLATORS: Neutral dialog spoken by Wirt -#: Source/textdat.cpp:450 -msgid "Pssst... over here..." -msgstr "Псс… сюди…" +#: Source/translation_dummy.cpp:680 +msgid "Bountiful" +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" -"Іноді тільки ти зможеш знайти ціль для деяких речей." +#: Source/translation_dummy.cpp:681 +msgid "Flaming" +msgstr "Полум'яний" -#. 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 "" -"Не вір всьому, що говорить п'яниця. Забагато елю затуманило його зір і " -"здоровий глузд." +#: Source/translation_dummy.cpp:682 +msgid "Lightning" +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 "" -"Якщо ти не помітив, я не купую у Трістрамі все підряд. Я імпортер якісних " -"товарів. Якщо ти хочеш торгувати мотлохом, то іди до Грізволда, Пепіна або " -"до тієї відьми, Адрії. Я впевнений, що вони розкуплять все, що ти їм " -"принесеш…" +#: Source/translation_dummy.cpp:683 +msgid "Jester's" +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 "" -"Що ж, я зобов'язаний ковалю своїм життям - тим що залишилось. Звісно, " -"Грізволд запропонував мені навчання в кузні, і він доволі добрий, але я " -"ніколи не отримаю достатньо грошей, щоб… ну, скажімо, у мене є певні плани, " -"які потребують багато золота." +#: Source/translation_dummy.cpp:684 +msgid "Crystalline" +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 "" -"Якби я був на пару років старшим, я б засипав її всіма багатствами, що " -"зібрав, і дозволь мені тебе запевнити, що в мене є дуже хороші речі. " -"Джилліан — красива дівчина, і вона повинна вибратися з Трістрама якомога " -"скоріше. Хм… можливо, я візьму її з собою, коли піду…" +#: Source/translation_dummy.cpp:685 +msgid "Doppelganger's" +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 "" -"Каїн занадто багато знає. Він до смерті мене лякає — навіть більше, ніж та " -"жінка за річкою. Він постійно каже мені про те, як мені пощастило, що вижив, " -"і як моя історія передрікається в легендах. Я думаю, що він зійшов з глузду." +#: Source/translation_dummy.cpp:686 +msgid "quality" +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 "" -"Фарнхем — оце чоловік з серйозними проблемами, і я знаю наскільки вони " -"серйозні. Він занадто повірив у непорочність однієї людини, а Лазар ввів " -"його в самі щелепи смерті. О, я знаю, як там унизу, тож навіть не починай " -"мені розказувати про свої плани знищити зло, що живе в тому Лабіринті. " -"Просто стеж за ногами…" +#: Source/translation_dummy.cpp:687 +msgid "maiming" +msgstr "знівечення" + +#: Source/translation_dummy.cpp:688 +msgid "slaying" +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" -"Якби у мене були хоч одне з тих зіллів, що він варить, у мене все ще була б " -"нога…" +#: Source/translation_dummy.cpp:689 +msgid "gore" +msgstr "крові" -#. 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 "" -"Адрія справді турбує мене. Звісно, Каїн кидає в мурашки тим, що може " -"розповісти тобі про минуле, але ця відьма може зазирнути у твоє минуле. Вона " -"завжди якось отримує все, що їй потрібно. Адрія отримує в свої руки більше " -"товарів, ніж я бачив, що проходять через ворота Королівського Базару під час " -"Великого Фестивалю." +#: Source/translation_dummy.cpp:690 +msgid "carnage" +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 "" -"Огден — дурень, що залишився тут. Я міг би вивезти його з міста за дуже " -"розумну ціну, але він наполягає на тому, щоб заробити за допомогою тієї " -"дурної таверни. Принаймні, він дає Гілліан місце для роботи, а його дружина " -"Гарда робить чудовий Пастуховий пиріг…" +#: Source/translation_dummy.cpp:691 +msgid "slaughter" +msgstr "забою" -#. TRANSLATORS: Quest text spoken aloud from a book by player -#: Source/textdat.cpp:471 Source/textdat.cpp:479 Source/textdat.cpp:487 -#: 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 "" -"За Залом Героїв стоїть Палата Кості. Вічна смерть чекає кожного, хто захоче " -"вкрасти скарби, що в цій кімнаті. Так говорить Володар Жахів, і так написано." +#: Source/translation_dummy.cpp:692 +msgid "pain" +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 "" -"…і тому, замкнена за Воротами Крові та за Залами Вогню, Доблесть чекає, коли " -"Герой Світла прокинеться..." +#: Source/translation_dummy.cpp:693 +msgid "tears" +msgstr "сліз" -# TBD: Make this rhyme -#. 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" -"Падай в Зали Сліпих." +#: Source/translation_dummy.cpp:694 +msgid "health" +msgstr "здоров'я" -#. TRANSLATORS: Quest text spoken aloud from a book by player -#: Source/textdat.cpp:477 Source/textdat.cpp:485 Source/textdat.cpp:493 -#: 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 men alike have been cut down to " -"fulfill his endless sacrifices to the Dark ones who scream for one thing - " -"blood." -msgstr "" -"Збройниці Пекла — дім Воєводи Крові. За ним лежать тисячі понівечених тіл. " -"Ангелів і людей вирізали, щоб принести його нескінченні жертви Темним, які " -"вимагають лише одне – кров." +#: Source/translation_dummy.cpp:695 +msgid "protection" +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 "" -"Стережись і свідчи про істини, що тут лежать, бо вони є останньою спадщиною " -"Хорадріма. Є війна, яка вирує навіть зараз, за межами відомих нам полів — " -"між утопічними королівствами Високих Небес і хаотичними ямами Палаючого " -"Пекла. Ця війна відома як Великий конфлікт, і вона лютувала і горіла довше, " -"ніж будь-яка з зірок на небі. Жодна зі сторін ніколи не отримує владу " -"надовго, оскільки сили Світла і Темряви постійно змагаються за контроль над " -"усім творінням." +#: Source/translation_dummy.cpp:696 +msgid "absorption" +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 "" -"Стережись і свідчи про істини, які тут лежать, бо вони є останньою спадщиною " -"Хорадріма. Коли Вічний конфлікт між Високими Небесами і Палаючим Пеклом " -"випаде на землю смертних, це називається Війною Гріха. Ангели і демони " -"ходять замасковані серед людей, воюючи таємно, подалі від допитливих очей " -"смертних. Деякі сміливі, могутні смертні навіть об’єдналися з будь-якою " -"стороною і допомогли вирішити хід Війни Гріхів." +#: Source/translation_dummy.cpp:697 +msgid "deflection" +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" -"Будьте попереджені, що камінь душі слід утримувати від відкриття тими, хто " -"не вірить. Якщо Діабло звільниться, він буде шукати тіло, яким легко " -"керувати, оскільки він дуже слабкий — можливо, тіло старого або дитини." +#: Source/translation_dummy.cpp:698 +msgid "osmosis" +msgstr "осмосу" -#. 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 "" -"Так сталося, що в Палаючих Пеклах відбулася велика революція, відома як " -"Темне Заслання. Менші Зла повалили Три Перші Зла і вигнали їхні духовні " -"форми в вимір смертних. Демони Беліал (Володар Брехні) і Азмодан (Володар " -"Гріха) боролися за правління Пекла під час відсутності Трьох братів. Усе " -"Пекло поділилося між фракціями Беліала та Азмодана, в той час як сили " -"Високих Небес безперервно били по самих Воротах Пекла." +#: Source/translation_dummy.cpp:699 +msgid "frailty" +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 "" -"Багато демонів подорожували в вимір смертних у пошуках Трьох Братів. За ними " -"слідували ангели, що полювали на них у великих містах Сходу. Ангели вступили " -"в союз з таємним орденом смертних магів, що зветься Хорадрім, які швидко " -"стали досвідченими в полюванні на демонів. Також вони нажили собі багато " -"темних ворогів у підземних світах." +#: Source/translation_dummy.cpp:700 +msgid "weakness" +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" -"Врешті-решт Хорадрім також захопив Діабло в Камені Душі і поховав його під " -"стародавнім, забутим собором. Там Володар Жаху спить і чекає часу свого " -"відродження. Знайте, що він буде шукати молоде і сильне тіло, яким можна " -"володіти, — тіло, що є невинним і яким легко керувати. Тоді він встане, щоб " -"звільнити своїх братів і ще раз розпалити полум'я Війни Гріхів…" +#: Source/translation_dummy.cpp:701 +msgid "strength" +msgstr "сили" + +#: Source/translation_dummy.cpp:702 +msgid "might" +msgstr "могутності" + +#: Source/translation_dummy.cpp:703 +msgid "power" +msgstr "міці" + +#: Source/translation_dummy.cpp:704 +msgid "giants" +msgstr "гіганта" + +#: Source/translation_dummy.cpp:705 +msgid "titans" +msgstr "титана" -#. 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 "" -"Всі похвали Діабло - Лорду Жаху і Тому, Хто Вижив Темне Заслання. Коли він " -"прокинувся від довгого сну, мій Володар і Вчитель розповів мені про " -"таємниці, які небагато смертних знають. Він сказав мені, що царства Високих " -"Небес і палаючі ями Пекла, воюють у вічній війні. Він розкрив сили, які " -"принесли цей розбрат у царства людей. Мій повелитель назвав битву за цей " -"світ і всіх, хто в ньому існує, Війною Гріхів." +#: Source/translation_dummy.cpp:706 +msgid "paralysis" +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 "" -"Слава і Хвала Діабло - Повелителю Жаху і Лідеру Трьох. Мій Повелитель " -"говорив мені про своїх братів, Мефісто і Ваала, які давно були вигнані в цей " -"світ. Мій Володар хоче дочекатися і використати свою дивовижну силу, щоб він " -"міг звільнити своїх полонених братів з їх гробниць під пісками сходу. Як " -"тільки мій Повелитель звільнить своїх братів, Війна гріхів знову пізнає лють " -"Трьох." +#: Source/translation_dummy.cpp:707 +msgid "atrophy" +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 "" -"Радуйся і принеси жертву Діабло - Повелителю Жаху і Винищувачу Душ. Коли я " -"розбудив свого Вчителя від сну, він спробував заволодіти смертним тілом. " -"Діабло намагався заволодіти тілом Короля Леоріка, але мій господар був " -"занадто слабкий від ув’язнення. Моєму Повелителю був потрібен простий і " -"невинний якір у цьому світі, і він знайшов, що хлопчик Альбрехт ідеально " -"підходить для цього. У той час як добрий Король Леорік збожеволів від " -"невдалої спроби вселитись в нього, я викрав його сина Альбрехта і привів " -"його до свого господаря. Тепер я чекаю поклику Діабло і молюся, щоб мене " -"винагородили, коли він нарешті стане Володарем цього світу." +#: Source/translation_dummy.cpp:708 +msgid "dexterity" +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" -"Багато чого змінилося з тих пір, як ти тут жив, друже. Усе було мирно, поки " -"не прийшли темні вершники і не знищили наше село. Багатьох вирубали там, де " -"вони стояли, а тих, хто брався за зброю, вбивали або тялги на работоргівлю — " -"а то і гірше. Церква на околиці міста була осквернена і використовується для " -"темних ритуалів. Уночі лунають нелюдські крики, але дехто з городян ще може " -"вижили. Йди стежкою, що лежить між моєю таверною і ковальською майcтернею, " -"щоб знайти церкву і врятуй кого зможеш.\n" -" \n" -"Я розповім більше, коли ми знову поговоримо. Удачі." +#: Source/translation_dummy.cpp:709 +msgid "skill" +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 "" -"Продовжуй свої звідини. Знайти втрачений скарб непросто. Знайти схований " -"скарб ще важче. Дам тобі ось таку пораду. Не дозволяй піскам часу " -"заплутати твої пошуки." +#: Source/translation_dummy.cpp:710 +msgid "accuracy" +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 "" -"Що?! Це дурість. Тут, у Трістрамі, немає скарбів. Дай мені подивитись!! " -"О, подивись, малюнки тут неточні. Вони зовсім не відповідають нашому " -"місту. Я б мав на увазі те, що під собором, а не те, що лежить під нашим " -"ґрунтом." +#: Source/translation_dummy.cpp:711 +msgid "precision" +msgstr "влучності" -#. TRANSLATORS: Quest text spoken by Pepin -#: 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 "" -"У мене немає часу говорити про якусь карту, що ти шукаєш. У мене багато " -"хворих, яким потрібна моя і твоя допомога." +#: Source/translation_dummy.cpp:712 +msgid "perfection" +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 "" -"Колись гордий Ісволл піймався в пастку глибоко під землею. Його позбавили " -"честі і змінили обличчя. Його захопили в пастку безкінечних мук. " -"Призначений ховати саме те, що могло б його звільнити." +#: Source/translation_dummy.cpp:713 +msgid "the fool" +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 "" -"Б’юся об заклад, що Вірт побачив, як ти ідеш, і прикинувся лише для того, " -"щоб потім сміятися з тебе, як ти бігаєш по місту носом в грунт. Я б його " -"проігнорував." +#: Source/translation_dummy.cpp:714 +msgid "dyslexia" +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 "" -"Був час, коли це місто було частою зупинкою для мандрівників звідусіль. З " -"тих пір багато чого змінилося. Але приховані печери та закопані скарби – " -"звичайні фантазії будь-якої дитини. Вірт іноді вдається до юнацьких ігор. " -"Так що це може бути просто його уява." +#: Source/translation_dummy.cpp:715 +msgid "magic" +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 "" -"Слухай сюди. Підійди ближче. Не знаю, чи ти знаєш те, що я знаю, але у " -"тебе тут справді щось є. Це карта." +#: Source/translation_dummy.cpp:716 +msgid "the mind" +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 "" -"Моя бабуся часто розповідає мені історії про дивні сили, які населяють " -"цвинтар біля церкви. Може тобі було б цікаво почути одну з них. Вона " -"сказала, що якби ти залишиш належне подання на цвинтарі, увійдеш до собору, " -"щоб помолитися за померлих, а потім повернешся, то подання якимось дивним " -"чином зміниться. Я не знаю, чи це лише маразм старої хворої жінки, але " -"сьогодні все здається можливим." +#: Source/translation_dummy.cpp:717 +msgid "brilliance" +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 "" -"Хммм. Значить, величезний і таємничий скарб. Мммм. Можливо, мені було б " -"цікаво взяти в тебе кілька речей. Або ще краще, хіба тобі не потрібні " -"рідкісні та дорогі припаси, щоб пройти це випробування?" +#: Source/translation_dummy.cpp:718 +msgid "sorcery" +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 "" -"Отже, ти герой, про якого всі говорили. Можливо, ти міг би допомогти " -"бідному, простому фермеру вийти з жахливої ситуації? На півдні звідси, на " -"краю мого саду, з землі надулася якась жахлива річ! Я не можу дістатися до " -"своїх посівів чи тюків сіна, і мої бідні корови будуть голодувати. Відьма " -"дала ось це мені і сказала, що це підірве ту штуку з мого поля. Якби ти міг " -"її знищити, я був би навіки вдячний. Я б це зробив сам, але хтось має " -"залишитися тут з коровами…" +#: Source/translation_dummy.cpp:719 +msgid "wizardry" +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 "" -"Я знав, що все не може бути так просто, як казала та відьма. Це сумний світ, " -"коли ти не можеш довіряти навіть своїм сусідам." +#: Source/translation_dummy.cpp:720 +msgid "illness" +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 "" -"Вона зникло? Ти відправив її назад до темних закутків Аїда, що породили " -"його? Що? О, тільки не кажи мені, що ти її не знайшов! Такі речі коштують " -"недешево. Ти маєш знайти її, а потім підірвати ту жахливу річ." +#: Source/translation_dummy.cpp:721 +msgid "disease" +msgstr "хвороби" + +#: Source/translation_dummy.cpp:722 +msgid "vitality" +msgstr "жувочості" + +#: Source/translation_dummy.cpp:723 +msgid "zest" +msgstr "запалу" + +#: Source/translation_dummy.cpp:724 +msgid "vim" +msgstr "енергії" + +#: Source/translation_dummy.cpp:725 +msgid "vigor" +msgstr "снаги" + +#: Source/translation_dummy.cpp:726 +msgid "life" +msgstr "життя" + +#: Source/translation_dummy.cpp:727 +msgid "trouble" +msgstr "лиха" + +#: Source/translation_dummy.cpp:728 +msgid "the pit" +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 "" -"Я почув вибух звідти! Велике тобі спасибі, добрий незнайомець. Що з усіма " -"цими речами, що виходять із землі, монстрами, що захоплюють церкву, і так " -"далі, це важкі часи. Я лише бідний фермер, але ось — візьми це, і мою велику " -"подяку." +#: Source/translation_dummy.cpp:729 +msgid "the sky" +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 "" -"Ой, така в мене біда... може... Ні, я не можу нав'язуватись тобі, коли у " -"тебе інші неприємності. Може, після того, як ти очистиш церкву від тих " -"створінь, ти повернешся... і виділиш трохи часу, щоб допомогти бідному " -"фермеру?" +#: Source/translation_dummy.cpp:730 +msgid "the moon" +msgstr "місяця" -# I you find a better onomatopeia of crying in Ukrainian, I'm all ears -#. TRANSLATORS: Quest text spoken by Little Girl -#: Source/textdat.cpp:571 -msgid "Waaaah! (sniff) Waaaah! (sniff)" -msgstr "Гуууу! (хлип) Гуууу! (хлип)" +#: Source/translation_dummy.cpp:731 +msgid "the stars" +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 "" -"Я загубила Тео! Я загубила свого найкращого друга! Ми гралися біля річки, " -"і Тео сказав, що хоче подивитися на велику зелену штуку. Я сказала, що не " -"треба, але ми прокрались туди, а потім раптом вийшов цей ЖУК! Ми втекли, " -"але Тео впав, і жук СХОПИВ і забрав його !" +#: Source/translation_dummy.cpp:732 +msgid "the heavens" +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 "" -"Ти його знайшов? Будь ласка, знайди Теодора! Він просто маленький. Він не " -"може подбати про себе! Прошу!" +#: Source/translation_dummy.cpp:733 +msgid "the zodiac" +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 "" -"Ти знайшов його! Ти знайшов його! Дякую! О, Тео, тебе налякали ті " -"неприємні жучки? Фе! Тьфу! Щось прилипло до твого хутра! Фу! Давай, " -"Тео, ходімо додому! Ще раз дякую, герой!" +#: Source/translation_dummy.cpp:734 +msgid "the vulture" +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 "" -"Ми довго дрімали, і настав час пробудження. Після довгого сну ми сповнені " -"сильний голодом. Вже скоро, ми поживимося…" +#: Source/translation_dummy.cpp:735 +msgid "the jackal" +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 "" -"Тобі сподобалось, ссавцю? Як жалюгідно. Твій маленький світ не буде для нас " -"проблемою." +#: Source/translation_dummy.cpp:736 +msgid "the fox" +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 "" -"Ці землі будуть осквернені, і наш виводок заполонить поля, які люди " -"називають домом. Наші вусики огорнуть цей світ, і ми будемо ласувати плоттю " -"його мешканців. Людина стане нашим майном і їжею." +#: Source/translation_dummy.cpp:737 +msgid "the jaguar" +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 "" -"Ах, я чую тебе…ти близько! Близько! Шшшш…запах крові та страху…як привабливо…" +#: Source/translation_dummy.cpp:738 +msgid "the eagle" +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" -"Камінь був від цього світу – і від усіх світів – оскільки Світло є у всіх " -"речах, і поза всіма речами. Світло і єдність є наслідками цієї святої " -"основи, єдність мети і єдність володіння." +#: Source/translation_dummy.cpp:739 +msgid "the wolf" +msgstr "вовка" -#. TRANSLATORS: Quest text spoken by Complete Nut -#: Source/textdat.cpp:594 -msgid "Moo." -msgstr "Му." +#: Source/translation_dummy.cpp:740 +msgid "the tiger" +msgstr "тигра" -#. TRANSLATORS: Quest text spoken by Complete Nut -#: Source/textdat.cpp:595 -msgid "I said, Moo." -msgstr "Я сказав Му." +#: Source/translation_dummy.cpp:741 +msgid "the lion" +msgstr "лева" -#. TRANSLATORS: Quest text spoken by Complete Nut -#: Source/textdat.cpp:596 -msgid "Look I'm just a cow, OK?" -msgstr "Я проста корова, ясно?" +#: Source/translation_dummy.cpp:742 +msgid "the mammoth" +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 "" -"Ну добре, добре. Насправді я не корова. Зазвичай я так не ходжу; але я " -"сидів вдома і займався власними справами, і раптом з підлоги почали вилазити " -"жуки, лози, цибулини... це було жахливо! Якби в мене був час нормально " -"одягнутися, я б виглядав не так погано. Гей! Ти б не міг піти до моєї хати " -"і забрати костюм? Коричневий, а не сірий, той для вечору. Я б зробив це " -"сам, але я не хочу, щоб мене бачили в цьому убранні. Ось візьми це, може " -"знадобиться... щоб вбити ті речі, що обросли на моїй хаті. Її не пропустиш, " -"вона на півдні від розвилки річки... ну знаєш... та, де город заріс ." +#: Source/translation_dummy.cpp:743 +msgid "the whale" +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 "" -"Що ти тут стоїш? Іди і знайди мій костюм! І поспіши! Той Гольштейн мені " -"весь час підморгує!" +#: Source/translation_dummy.cpp:744 +msgid "fragility" +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 "" -"Гей, у тебе є мій костюм? Швидко, давай його сюди! Ти не повіриш як ці " -"вуха сверблять!" +#: Source/translation_dummy.cpp:745 +msgid "brittleness" +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 "" -"Ні ні ні ні! Це мій СІРИЙ костюм! Це для вечора! На офіційні випадки! Я " -"не можу ось ЦЕ носити. Ти що, дивак якийсь? Мені потрібен КОРИЧНЕВИЙ " -"костюм." +#: Source/translation_dummy.cpp:746 +msgid "sturdiness" +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 "" -"Ах, так НАБАГАТО краще. Хух! Нарешті трохи гідності! Мої роги прямі? " -"Добре. Дивись. Дуже дякую за допомогу. Ось візьми ось це в подарунок; і, " -"знаєш… невеличка порада по моді… тобі б знадобилось трохи… тобі б краще " -"замінити… нутизнаєшпрощояговорю? Цей мотив шукача пригод такий… ретро. " -"Невеличка порада, га? Чао." +#: Source/translation_dummy.cpp:747 +msgid "craftsmanship" +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 "" -"Подивись. Я корова. А ти, ти приманка. Наберись досвіду! Тоді і " -"поговоримо…" +#: Source/translation_dummy.cpp:748 +msgid "structure" +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 "" -"Мабуть, я дав тобі насправді страшне завдання. Якби я міг якось… Тобі б " -"допоміг глечик смачного свіжого молока?" +#: Source/translation_dummy.cpp:749 +msgid "the ages" +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 "" -"О, мені б знадобилася твоя допомога, але, мабуть, після того, як ти очистиш " -"катакомби від осквернення тих звірів." +#: Source/translation_dummy.cpp:750 +msgid "the dark" +msgstr "темряви" + +#: Source/translation_dummy.cpp:751 +msgid "the night" +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 "" -"Мені потрібно щось зробити, але я не буду зобов'язувати незнайомця. Можливо " -"після того, як ти пробув тут деякий час, мені буде зручніше просити про " -"послугу." +#: Source/translation_dummy.cpp:752 +msgid "light" +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 "" -"Я бачу у тобі потенціал для величі. Можливо, колись, поки ти виконуєш свою " -"долю, ти зможеш зайти і зробити мені невеличку послугу?" +#: Source/translation_dummy.cpp:753 +msgid "radiance" +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 "" -"Я думаю, що ти міг би мені допомогти, але, хіба що після того, як станете " -"трохи сильнішим. Я не хотів би зашкодити єдиному шансу села знищити загрозу " -"в церкві!" +#: Source/translation_dummy.cpp:754 +msgid "flame" +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 "" -"Я — корова, що сама всього досягла. Досягни чогось, і… от тоді і поговоримо." +#: Source/translation_dummy.cpp:755 +msgid "fire" +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 "" -"Мені не потрібно пояснюватися кожному туристу, що проходить повз! Хіба тобі " -"не треба вбивати монстрів? Поговоримо пізніше. Якщо ти виживеш…" +#: Source/translation_dummy.cpp:756 +msgid "burning" +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 "" -"Перестань мене турбувати. Я шукаю когось справді героїчного. І ти не " -"підходиш. Я не можу тобі довіряти, тебе будь-коли з’їдять монстри… Мені " -"потрібен досвідчений герой." +#: Source/translation_dummy.cpp:757 +msgid "shock" +msgstr "шоку" -# Puns are lost in translation :( -# I'm all ears if you happen to know how to restore them -#. 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 "" -"Добре, я перестану з маячнею. Я не хотів заплутати тебе. Я сидів вдома в " -"поганому настрої, коли все стало зовсім погано; з підлоги виліз цілий натовп " -"монстрів! Я злякався. Просто так сталося, що я був одягнений у цю Джерсі, " -"коли вибіг за двері, і тепер я виглядаю вкрай смішно. Якби в мене був " -"якийсь нормальний одяг, то було б не так погано. Гей! Ти б не міг піти до " -"моєї хати і забрати костюм? Коричневий, а не сірий, той для вечору. Я б " -"зробив це сам, але я не хочу, щоб хтось бачив мене таким. Я б зробив це " -"сам, але я не хочу, щоб мене бачили в цьому убранні. Ось візьми це, може " -"знадобиться... щоб вбити ті речі, що обросли на моїй хаті. Її не пропустиш, " -"вона на півдні від розвилки річки... ну знаєш... та, де город заріс." +#: Source/translation_dummy.cpp:758 +msgid "lightning" +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 "" -"Сьогодні хмарно та прохолодно. Закидаючи сітки некромантії через порожнечу, " -"впіймав два нових підвиди літаючих кошмарів. Гарний робочий день. Треба не " -"забути замовити ще трохи кажанового гуано та чорних свічок від Адрії; В мене " -"вони закінчуються." +#: Source/translation_dummy.cpp:759 +msgid "thunder" +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 "" -"Я пробував заклинання, погрози, відречення та торг із цим негідним " -"створінням — безрезультатно. Мої методи поневолення менших демонів, " -"здається, не впливають на цього страшного звіра." +#: Source/translation_dummy.cpp:760 +msgid "many" +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 "" -"Мій дім повільно псується підлістю цього ненависного в’язня. Склепи повні " -"тіней, що рухаються відразу за кутами зору. Краєм вуха чується ледве " -"помітне дряпання кігтів. Я думаю, що вони шукають цей щоденник." +#: Source/translation_dummy.cpp:761 +msgid "plenty" +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 "" -"У своїй тираді істота видала своє ім'я — На-Крул. Я намагався дослідити це " -"ім'я, але менші демони якимось чином знищили мою бібліотеку. На-Крул… Ім’я " -"наповнює мене холодним жахом. Я вважаю за краще думати про нього лише як про " -"Істоту, а не розмірковувати про його справжнє ім’я." +#: Source/translation_dummy.cpp:762 +msgid "thorns" +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 "" -"Гнівне виття істоти, що в пастці, не дає мені виспатись. Він лютує проти " -"того, хто послав його в Порожнечу, і кличе на мене огидні прокляття за те, " -"що я зловив його. Його слова наповнюють моє серце жахом, але я не можу " -"заглушити його голос." +#: Source/translation_dummy.cpp:763 +msgid "corruption" +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 "" -"В мене майже немає часу. Я повинен записати способи як ослабити демона, а " -"потім сховати цей текст, щоб його слуги не використали мої знання, щоб " -"звільнити свого володаря. Я сподіваюся, що той, хто знайде цей журнал, буде " -"шукати знання." +#: Source/translation_dummy.cpp:764 +msgid "thieves" +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" -"Заклинання, знайдені в трьох моїх гримуарах, забезпечать тобі захищений вхід " -"до його виміру, але лише якщо їх використати в належній послідовності. " -"Важелі біля входу знімуть бар'єри і звільнять демона; не торкайся їх! " -"Використовуй лише ці заклинання, щоб ввійти, інакше його сила може виявитися " -"занадто великою, щоб перемогти його." +#: Source/translation_dummy.cpp:765 +msgid "the bear" +msgstr "ведмеда" -#. 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." +#: Source/translation_dummy.cpp:766 +msgid "the bat" +msgstr "літучої миші" -#. 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." +#: Source/translation_dummy.cpp:767 +msgid "vampires" +msgstr "вампірів" -#. 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/translation_dummy.cpp:768 +msgid "the leech" +msgstr "п'явки" -#: Source/towners.cpp:83 -msgid "Griswold the Blacksmith" -msgstr "Коваль Грізвольд" +#: Source/translation_dummy.cpp:769 +msgid "blood" +msgstr "крові" -#: Source/towners.cpp:106 -msgid "Ogden the Tavern owner" -msgstr "Хазяїн таверни Огден" +#: Source/translation_dummy.cpp:770 +msgid "piercing" +msgstr "пробивання" -#: Source/towners.cpp:116 -msgid "Wounded Townsman" -msgstr "Поранений Житель" +#: Source/translation_dummy.cpp:771 +msgid "puncturing" +msgstr "проколювання" -#: Source/towners.cpp:138 -msgid "Adria the Witch" -msgstr "Відьма Адрія" +#: Source/translation_dummy.cpp:772 +msgid "bashing" +msgstr "побиття" -#: Source/towners.cpp:148 -msgid "Gillian the Barmaid" -msgstr "Барменша Гілліан" +#: Source/translation_dummy.cpp:773 +msgid "readiness" +msgstr "готовності" -#: Source/towners.cpp:181 -msgid "Pepin the Healer" -msgstr "Цілитель Пепін" +#: Source/translation_dummy.cpp:774 +msgid "swiftness" +msgstr "хуткості" -#: Source/towners.cpp:199 -msgid "Cain the Elder" -msgstr "Старець Каїн" +#: Source/translation_dummy.cpp:775 +msgid "speed" +msgstr "швидкості" -#: Source/towners.cpp:228 -msgid "Cow" -msgstr "Корова" +#: Source/translation_dummy.cpp:776 +msgid "haste" +msgstr "поспішності" -#: Source/towners.cpp:252 -msgid "Lester the farmer" -msgstr "Фермер Лестер" +#: Source/translation_dummy.cpp:777 +msgid "balance" +msgstr "балансу" -#: Source/towners.cpp:265 -msgid "Complete Nut" -msgstr "Повний Псих" +#: Source/translation_dummy.cpp:778 +msgid "stability" +msgstr "стабільності" -#: Source/towners.cpp:274 -msgid "Celia" -msgstr "Селія" +#: Source/translation_dummy.cpp:779 +msgid "harmony" +msgstr "гармонії" -#: Source/towners.cpp:287 -msgid "Slain Townsman" -msgstr "Вбитий Житель" +#: Source/translation_dummy.cpp:780 +msgid "blocking" +msgstr "блокування" + +#: Source/translation_dummy.cpp:781 +msgid "devastation" +msgstr "спустошення" + +#: Source/translation_dummy.cpp:782 +msgid "decay" +msgstr "занепаду" + +#: Source/translation_dummy.cpp:783 +msgid "peril" +msgstr "небезпеки" #. TRANSLATORS: Thousands separator -#: Source/utils/format_int.cpp:26 +#: Source/utils/format_int.cpp:27 msgid "," msgstr "," diff --git a/VERSION b/VERSION index 9fa7b61f40f..82ede4d5109 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.1-dev +1.6.0-dev diff --git a/android-project/app/build.gradle b/android-project/app/build.gradle index 2dba407ec6e..2a6508c621a 100644 --- a/android-project/app/build.gradle +++ b/android-project/app/build.gradle @@ -7,15 +7,15 @@ if (buildAsApplication) { } android { - ndkVersion '25.2.9519653' + ndkVersion '26.1.10909125' compileSdk 33 defaultConfig { if (buildAsApplication) { applicationId "org.diasurgical.devilutionx" } - minSdkVersion 18 + minSdkVersion 21 targetSdkVersion 33 - versionCode 26 + versionCode 28 versionName project.file('../../VERSION').text.trim() externalNativeBuild { cmake { @@ -31,6 +31,9 @@ android { } } namespace 'org.diasurgical.devilutionx' + buildFeatures { + buildConfig true + } applicationVariants.all { variant -> tasks["merge${variant.name.capitalize()}Assets"] .dependsOn("externalNativeBuild${variant.name.capitalize()}") diff --git a/android-project/app/src/main/java/org/diasurgical/devilutionx/DataActivity.java b/android-project/app/src/main/java/org/diasurgical/devilutionx/DataActivity.java index 6b4cbb3743d..d0704544131 100644 --- a/android-project/app/src/main/java/org/diasurgical/devilutionx/DataActivity.java +++ b/android-project/app/src/main/java/org/diasurgical/devilutionx/DataActivity.java @@ -6,6 +6,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; @@ -33,6 +34,12 @@ protected void onCreate(Bundle savedInstanceState) { ((TextView) findViewById(R.id.full_guide)).setMovementMethod(LinkMovementMethod.getInstance()); ((TextView) findViewById(R.id.online_guide)).setMovementMethod(LinkMovementMethod.getInstance()); + + boolean isTelevision = getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); + if (isTelevision) { + findViewById(R.id.gamepad_text).setVisibility(View.VISIBLE); + findViewById(R.id.gamepad_icon).setVisibility(View.VISIBLE); + } } protected void onResume() { diff --git a/android-project/app/src/main/java/org/diasurgical/devilutionx/ImportActivity.java b/android-project/app/src/main/java/org/diasurgical/devilutionx/ImportActivity.java index 2948d994cef..ba9c8b75c8e 100644 --- a/android-project/app/src/main/java/org/diasurgical/devilutionx/ImportActivity.java +++ b/android-project/app/src/main/java/org/diasurgical/devilutionx/ImportActivity.java @@ -1,6 +1,7 @@ package org.diasurgical.devilutionx; import android.app.Activity; +import android.app.AlertDialog; import android.content.ClipData; import android.content.ContentResolver; import android.content.Intent; @@ -17,6 +18,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; import java.util.Objects; public class ImportActivity extends Activity { @@ -27,11 +29,22 @@ public class ImportActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); - intent.setType("*/*"); - startActivityForResult(intent, IMPORT_REQUEST_CODE); + + ExternalFilesManager fileManager = new ExternalFilesManager(this); + String externalFilesDir = fileManager.getExternalFilesDirectory(); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(getString(R.string.import_data_info, externalFilesDir)); + builder.setPositiveButton(R.string.ok_button, (dialog, which) -> { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + intent.setType("*/*"); + startActivityForResult(intent, IMPORT_REQUEST_CODE); + }); + + AlertDialog dialog = builder.create(); + dialog.show(); } @Override @@ -40,31 +53,88 @@ protected void onActivityResult(int requestCode, int resultCode, @Nullable Inten return; if (resultCode == Activity.RESULT_OK && data != null) { - importFile(data.getData()); - handleClipData(data.getClipData()); - } + ArrayList uriList = getItemUris(data.getClipData()); + + Uri dataUri = data.getData(); + if (dataUri != null) + uriList.add(dataUri); + + ArrayList fileNames = getFileNames(uriList); + ArrayList overwrittenFiles = getOverwrittenFiles(fileNames); + if (overwrittenFiles.isEmpty()) { + importFiles(uriList); + finish(); + return; + } + + String overwrittenFileList = String.join("\n", overwrittenFiles); + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(getString(R.string.overwrite_query, overwrittenFileList)); + builder.setPositiveButton(R.string.continue_button, (dialog, which) -> { importFiles(uriList); }); + builder.setNegativeButton(R.string.cancel_button, null); + builder.setOnDismissListener(dialog -> finish()); - finish(); + AlertDialog dialog = builder.create(); + dialog.show(); + } } - private void handleClipData(ClipData clipData) { + private ArrayList getItemUris(ClipData clipData) { + ArrayList uriList = new ArrayList<>(); if (clipData == null) - return; + return uriList; for (int i = 0; i < clipData.getItemCount(); i++) { ClipData.Item item = clipData.getItemAt(i); if (item == null) continue; - importFile(item.getUri()); + Uri itemUri = item.getUri(); + if (itemUri == null) + continue; + + uriList.add(itemUri); } + + return uriList; + } + + private ArrayList getFileNames(ArrayList uriList) { + ArrayList fileNames = new ArrayList<>(); + for (Uri uri : uriList) { + DocumentFile file = DocumentFile.fromSingleUri(getApplicationContext(), uri); + if (file == null) + continue; + + String fileName = file.getName(); + fileNames.add(fileName); + } + return fileNames; + } + + private ArrayList getOverwrittenFiles(ArrayList fileNames) { + ArrayList overwrittenFiles = new ArrayList<>(); + ExternalFilesManager fileManager = new ExternalFilesManager(this); + for (String fileName : fileNames) { + if (fileManager.hasFile(fileName)) + overwrittenFiles.add(fileName); + } + return overwrittenFiles; + } + + private void importFiles(ArrayList uriList) { + for (Uri uri : uriList) + importFile(uri); } private void importFile(Uri fileUri) { if (fileUri == null) return; - DocumentFile file = Objects.requireNonNull(DocumentFile.fromSingleUri(getApplicationContext(), fileUri)); + DocumentFile file = DocumentFile.fromSingleUri(getApplicationContext(), fileUri); + if (file == null) + return; + String fileName = file.getName(); ExternalFilesManager fileManager = new ExternalFilesManager(this); File externalFile = fileManager.getFile(fileName); diff --git a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java index 65c5a42370f..ee5521fd5e3 100644 --- a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java +++ b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java @@ -186,7 +186,7 @@ public BluetoothGatt getGatt() { // Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead // of TRANSPORT_LE. Let's force ourselves to connect low energy. private BluetoothGatt connectGatt(boolean managed) { - if (Build.VERSION.SDK_INT >= 23) { + if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) { try { return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE); } catch (Exception e) { @@ -429,7 +429,7 @@ public void run() { } }); } - } + } else if (newState == 0) { mIsConnected = false; } diff --git a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java index 30e9416a439..5310d6016ad 100644 --- a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java +++ b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java @@ -252,6 +252,7 @@ private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterfa 0x24c6, // PowerA 0x2c22, // Qanba 0x2dc8, // 8BitDo + 0x9886, // ASTRO Gaming }; if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && @@ -272,6 +273,7 @@ private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterfa final int XB1_IFACE_SUBCLASS = 71; final int XB1_IFACE_PROTOCOL = 208; final int[] SUPPORTED_VENDORS = { + 0x03f0, // HP 0x044f, // Thrustmaster 0x045e, // Microsoft 0x0738, // Mad Catz @@ -356,13 +358,13 @@ private void connectHIDDeviceUSB(UsbDevice usbDevice) { private void initializeBluetooth() { Log.d(TAG, "Initializing Bluetooth"); - if (Build.VERSION.SDK_INT <= 30 && + if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ && mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH"); return; } - if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18)) { + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) { Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE"); return; } @@ -576,7 +578,7 @@ public boolean openDevice(int deviceID) { try { final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31 int flags; - if (Build.VERSION.SDK_INT >= 31) { + if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) { flags = FLAG_MUTABLE; } else { flags = 0; diff --git a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java index d20fe80bc69..bfe0cf954d9 100644 --- a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java +++ b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java @@ -52,7 +52,7 @@ public int getProductId() { @Override public String getSerialNumber() { String result = null; - if (Build.VERSION.SDK_INT >= 21) { + if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) { try { result = mDevice.getSerialNumber(); } @@ -74,7 +74,7 @@ public int getVersion() { @Override public String getManufacturerName() { String result = null; - if (Build.VERSION.SDK_INT >= 21) { + if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) { result = mDevice.getManufacturerName(); } if (result == null) { @@ -86,7 +86,7 @@ public String getManufacturerName() { @Override public String getProductName() { String result = null; - if (Build.VERSION.SDK_INT >= 21) { + if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) { result = mDevice.getProductName(); } if (result == null) { diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java index fde9b0a3eb0..a2b5ba873bd 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -60,8 +60,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener { private static final String TAG = "SDL"; private static final int SDL_MAJOR_VERSION = 2; - private static final int SDL_MINOR_VERSION = 27; - private static final int SDL_MICRO_VERSION = 0; + private static final int SDL_MINOR_VERSION = 28; + private static final int SDL_MICRO_VERSION = 5; /* // Display InputType.SOURCE/CLASS of events and devices // @@ -93,7 +93,7 @@ public static void debugSource(int sources, String prefix) { s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (Build.VERSION.SDK_INT >= 23) { tst = InputDevice.SOURCE_BLUETOOTH_STYLUS; if ((s & tst) == tst) src += " BLUETOOTH_STYLUS"; s2 &= ~tst; @@ -107,7 +107,7 @@ public static void debugSource(int sources, String prefix) { if ((s & tst) == tst) src += " GAMEPAD"; s2 &= ~tst; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (Build.VERSION.SDK_INT >= 21) { tst = InputDevice.SOURCE_HDMI; if ((s & tst) == tst) src += " HDMI"; s2 &= ~tst; @@ -146,7 +146,7 @@ public static void debugSource(int sources, String prefix) { if ((s & tst) == tst) src += " TOUCHSCREEN"; s2 &= ~tst; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + if (Build.VERSION.SDK_INT >= 18) { tst = InputDevice.SOURCE_TOUCH_NAVIGATION; if ((s & tst) == tst) src += " TOUCH_NAVIGATION"; s2 &= ~tst; @@ -170,7 +170,7 @@ public static void debugSource(int sources, String prefix) { */ public static boolean mIsResumedCalled, mHasFocus; - public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24); + public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */); // Cursor types // private static final int SDL_SYSTEM_CURSOR_NONE = -1; @@ -224,9 +224,9 @@ public enum NativeState { protected static SDLGenericMotionListener_API12 getMotionListener() { if (mMotionListener == null) { - if (Build.VERSION.SDK_INT >= 26) { + if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) { mMotionListener = new SDLGenericMotionListener_API26(); - } else if (Build.VERSION.SDK_INT >= 24) { + } else if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { mMotionListener = new SDLGenericMotionListener_API24(); } else { mMotionListener = new SDLGenericMotionListener_API12(); @@ -404,7 +404,7 @@ public void onClick(DialogInterface dialog,int id) { SDLActivity.onNativeOrientationChanged(mCurrentOrientation); try { - if (Build.VERSION.SDK_INT < 24) { + if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) { mCurrentLocale = getContext().getResources().getConfiguration().locale; } else { mCurrentLocale = getContext().getResources().getConfiguration().getLocales().get(0); @@ -768,7 +768,7 @@ public void handleMessage(Message msg) { } break; case COMMAND_CHANGE_WINDOW_STYLE: - if (Build.VERSION.SDK_INT >= 19) { + if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) { if (context instanceof Activity) { Window window = ((Activity) context).getWindow(); if (window != null) { @@ -843,7 +843,7 @@ boolean sendCommand(int command, Object data) { msg.obj = data; boolean result = commandHandler.sendMessage(msg); - if (Build.VERSION.SDK_INT >= 19) { + if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) { if (command == COMMAND_CHANGE_WINDOW_STYLE) { // Ensure we don't return until the resize has actually happened, // or 500ms have passed. @@ -1095,7 +1095,7 @@ public static boolean supportsRelativeMouse() // thus SDK version 27. If we are in DeX mode and not API 27 or higher, as a result, // we should stick to relative mode. // - if ((Build.VERSION.SDK_INT < 27) && isDeXMode()) { + if (Build.VERSION.SDK_INT < 27 /* Android 8.1 (O_MR1) */ && isDeXMode()) { return false; } @@ -1185,7 +1185,7 @@ public static boolean isChromebook() { * This method is called by SDL using JNI. */ public static boolean isDeXMode() { - if (Build.VERSION.SDK_INT < 24) { + if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) { return false; } try { @@ -1345,23 +1345,6 @@ public static boolean handleKeyEvent(View v, int keyCode, KeyEvent event, InputC } } - if ((source & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (isTextInputEvent(event)) { - if (ic != null) { - ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); - } else { - SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1); - } - } - onNativeKeyDown(keyCode); - return true; - } else if (event.getAction() == KeyEvent.ACTION_UP) { - onNativeKeyUp(keyCode); - return true; - } - } - if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses // they are ignored here because sending them as mouse input to SDL is messy @@ -1376,6 +1359,21 @@ public static boolean handleKeyEvent(View v, int keyCode, KeyEvent event, InputC } } + if (event.getAction() == KeyEvent.ACTION_DOWN) { + if (isTextInputEvent(event)) { + if (ic != null) { + ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); + } else { + SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1); + } + } + onNativeKeyDown(keyCode); + return true; + } else if (event.getAction() == KeyEvent.ACTION_UP) { + onNativeKeyUp(keyCode); + return true; + } + return false; } @@ -1622,7 +1620,7 @@ public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) { private final Runnable rehideSystemUi = new Runnable() { @Override public void run() { - if (Build.VERSION.SDK_INT >= 19) { + if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) { int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | @@ -1675,7 +1673,7 @@ public static int createCustomCursor(int[] colors, int width, int height, int ho Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888); ++mLastCursorID; - if (Build.VERSION.SDK_INT >= 24) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { try { mCursors.put(mLastCursorID, PointerIcon.create(bitmap, hotSpotX, hotSpotY)); } catch (Exception e) { @@ -1691,7 +1689,7 @@ public static int createCustomCursor(int[] colors, int width, int height, int ho * This method is called by SDL using JNI. */ public static void destroyCustomCursor(int cursorID) { - if (Build.VERSION.SDK_INT >= 24) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { try { mCursors.remove(cursorID); } catch (Exception e) { @@ -1705,7 +1703,7 @@ public static void destroyCustomCursor(int cursorID) { */ public static boolean setCustomCursor(int cursorID) { - if (Build.VERSION.SDK_INT >= 24) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { try { mSurface.setPointerIcon(mCursors.get(cursorID)); } catch (Exception e) { @@ -1760,7 +1758,7 @@ public static boolean setSystemCursor(int cursorID) { cursor_type = 1002; //PointerIcon.TYPE_HAND; break; } - if (Build.VERSION.SDK_INT >= 24) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { try { mSurface.setPointerIcon(PointerIcon.getSystemIcon(SDL.getContext(), cursor_type)); } catch (Exception e) { @@ -1774,7 +1772,7 @@ public static boolean setSystemCursor(int cursorID) { * This method is called by SDL using JNI. */ public static void requestPermission(String permission, int requestCode) { - if (Build.VERSION.SDK_INT < 23) { + if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) { nativePermissionResult(requestCode, true); return; } @@ -1803,7 +1801,7 @@ public static int openURL(String url) i.setData(Uri.parse(url)); int flags = Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_MULTIPLE_TASK; - if (Build.VERSION.SDK_INT >= 21) { + if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) { flags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT; } else { flags |= Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET; @@ -2007,6 +2005,18 @@ public boolean setComposingText(CharSequence text, int newCursorPosition) { @Override public boolean deleteSurroundingText(int beforeLength, int afterLength) { + if (Build.VERSION.SDK_INT <= 29 /* Android 10.0 (Q) */) { + // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection + // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265 + if (beforeLength > 0 && afterLength == 0) { + // backspace(s) + while (beforeLength-- > 0) { + nativeGenerateScancodeForUnichar('\b'); + } + return true; + } + } + if (!super.deleteSurroundingText(beforeLength, afterLength)) { return false; } diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLAudioManager.java b/android-project/app/src/main/java/org/libsdl/app/SDLAudioManager.java index 2a74fb02686..7c821a4097b 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDLAudioManager.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDLAudioManager.java @@ -29,7 +29,7 @@ public static void initialize() { mAudioRecord = null; mAudioDeviceCallback = null; - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { mAudioDeviceCallback = new AudioDeviceCallback() { @Override @@ -79,14 +79,14 @@ protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz"); /* On older devices let's use known good settings */ - if (Build.VERSION.SDK_INT < 21) { + if (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) { if (desiredChannels > 2) { desiredChannels = 2; } } /* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */ - if (Build.VERSION.SDK_INT < 22) { + if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) { if (sampleRate < 8000) { sampleRate = 8000; } else if (sampleRate > 48000) { @@ -95,7 +95,7 @@ protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, } if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) { - int minSDKVersion = (isCapture ? 23 : 21); + int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */); if (Build.VERSION.SDK_INT < minSDKVersion) { audioFormat = AudioFormat.ENCODING_PCM_16BIT; } @@ -156,7 +156,7 @@ protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER; break; case 8: - if (Build.VERSION.SDK_INT >= 23) { + if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) { channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND; } else { Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround"); @@ -237,7 +237,7 @@ protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, return null; } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && deviceId != 0) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) { mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId)); } @@ -264,7 +264,7 @@ protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, return null; } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && deviceId != 0) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) { mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId)); } @@ -283,7 +283,7 @@ protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, } private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) .filter(deviceInfo -> deviceInfo.getId() == deviceId) @@ -295,7 +295,7 @@ private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) { } private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) .filter(deviceInfo -> deviceInfo.getId() == deviceId) @@ -307,14 +307,14 @@ private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) { } private static void registerAudioDeviceCallback() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null); } } private static void unregisterAudioDeviceCallback(Context context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback); } @@ -324,7 +324,7 @@ private static void unregisterAudioDeviceCallback(Context context) { * This method is called by SDL using JNI. */ public static int[] getAudioOutputDevices() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).mapToInt(AudioDeviceInfo::getId).toArray(); } else { @@ -336,7 +336,7 @@ public static int[] getAudioOutputDevices() { * This method is called by SDL using JNI. */ public static int[] getAudioInputDevices() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).mapToInt(AudioDeviceInfo::getId).toArray(); } else { @@ -360,7 +360,7 @@ public static void audioWriteFloatBuffer(float[] buffer) { return; } - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { + if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) { Log.e(TAG, "Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)"); return; } @@ -443,7 +443,7 @@ public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChan /** This method is called by SDL using JNI. */ public static int captureReadFloatBuffer(float[] buffer, boolean blocking) { - if (Build.VERSION.SDK_INT < 23) { + if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) { return 0; } else { return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); @@ -452,7 +452,7 @@ public static int captureReadFloatBuffer(float[] buffer, boolean blocking) { /** This method is called by SDL using JNI. */ public static int captureReadShortBuffer(short[] buffer, boolean blocking) { - if (Build.VERSION.SDK_INT < 23) { + if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) { return mAudioRecord.read(buffer, 0, buffer.length); } else { return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); @@ -461,7 +461,7 @@ public static int captureReadShortBuffer(short[] buffer, boolean blocking) { /** This method is called by SDL using JNI. */ public static int captureReadByteBuffer(byte[] buffer, boolean blocking) { - if (Build.VERSION.SDK_INT < 23) { + if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) { return mAudioRecord.read(buffer, 0, buffer.length); } else { return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java b/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java index 85ecbded42a..d6913f1571f 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java @@ -42,7 +42,7 @@ public static native void onNativeHat(int device_id, int hat_id, public static void initialize() { if (mJoystickHandler == null) { - if (Build.VERSION.SDK_INT >= 19) { + if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) { mJoystickHandler = new SDLJoystickHandler_API19(); } else { mJoystickHandler = new SDLJoystickHandler_API16(); @@ -50,7 +50,7 @@ public static void initialize() { } if (mHapticHandler == null) { - if (Build.VERSION.SDK_INT >= 26) { + if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) { mHapticHandler = new SDLHapticHandler_API26(); } else { mHapticHandler = new SDLHapticHandler(); @@ -809,7 +809,7 @@ public boolean onGenericMotion(View v, MotionEvent event) { @Override public boolean supportsRelativeMouse() { - return (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)); + return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */); } @Override @@ -819,7 +819,7 @@ public boolean inRelativeMode() { @Override public boolean setRelativeMouseEnabled(boolean enabled) { - if (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)) { + if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) { if (enabled) { SDLActivity.getContentView().requestPointerCapture(); } else { diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java b/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java index dcd26d495c8..0857e4b6f3d 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java @@ -116,7 +116,7 @@ public void surfaceChanged(SurfaceHolder holder, int nDeviceHeight = height; try { - if (Build.VERSION.SDK_INT >= 17) { + if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) { DisplayMetrics realMetrics = new DisplayMetrics(); mDisplay.getRealMetrics( realMetrics ); nDeviceWidth = realMetrics.widthPixels; @@ -163,7 +163,7 @@ public void surfaceChanged(SurfaceHolder holder, // Don't skip in MultiWindow. if (skip) { - if (Build.VERSION.SDK_INT >= 24) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { if (SDLActivity.mSingleton.isInMultiWindowMode()) { Log.v("SDL", "Don't skip in Multi-Window"); skip = false; diff --git a/android-project/app/src/main/res/drawable/gamepad.xml b/android-project/app/src/main/res/drawable/gamepad.xml new file mode 100644 index 00000000000..7770ec7d400 --- /dev/null +++ b/android-project/app/src/main/res/drawable/gamepad.xml @@ -0,0 +1,271 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android-project/app/src/main/res/layout/activity_data.xml b/android-project/app/src/main/res/layout/activity_data.xml index 6d87a057851..0c6c3828bb0 100644 --- a/android-project/app/src/main/res/layout/activity_data.xml +++ b/android-project/app/src/main/res/layout/activity_data.xml @@ -75,4 +75,29 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/full_guide" /> + + + + + diff --git a/android-project/app/src/main/res/values/strings.xml b/android-project/app/src/main/res/values/strings.xml index b4ad488bf00..1f0054ea4ae 100644 --- a/android-project/app/src/main/res/values/strings.xml +++ b/android-project/app/src/main/res/values/strings.xml @@ -12,4 +12,10 @@ Download started Diablo Demo Data Import Data + This game requires a gamepad to play + On the next screen, select files that will be imported into:\n\n%1$s + The following files will be overwritten. Is this okay?\n\n%1$s + OK + Continue + Cancel diff --git a/android-project/build.gradle b/android-project/build.gradle index fff0f39e299..250375c438c 100644 --- a/android-project/build.gradle +++ b/android-project/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:8.1.0' + classpath 'com.android.tools.build:gradle:8.2.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/android-project/gradle.properties b/android-project/gradle.properties index 3f60deff987..9f958a0d111 100644 --- a/android-project/gradle.properties +++ b/android-project/gradle.properties @@ -1,3 +1,2 @@ -android.defaults.buildfeatures.buildconfig=true android.nonFinalResIds=false android.nonTransitiveRClass=false diff --git a/android-project/gradle/wrapper/gradle-wrapper.properties b/android-project/gradle/wrapper/gradle-wrapper.properties index ec908fc3398..6af0ac65e47 100644 --- a/android-project/gradle/wrapper/gradle-wrapper.properties +++ b/android-project/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Aug 19 20:48:57 CEST 2023 +#Wed Dec 13 13:49:58 EET 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/Packaging/resources/assets/arena/church.dun b/assets/arena/church.dun similarity index 100% rename from Packaging/resources/assets/arena/church.dun rename to assets/arena/church.dun diff --git a/Packaging/resources/assets/arena/circle_of_death.dun b/assets/arena/circle_of_death.dun similarity index 100% rename from Packaging/resources/assets/arena/circle_of_death.dun rename to assets/arena/circle_of_death.dun diff --git a/Packaging/resources/assets/arena/hell.dun b/assets/arena/hell.dun similarity index 100% rename from Packaging/resources/assets/arena/hell.dun rename to assets/arena/hell.dun diff --git a/Packaging/resources/assets/data/boxleftend.clx b/assets/data/boxleftend.clx similarity index 100% rename from Packaging/resources/assets/data/boxleftend.clx rename to assets/data/boxleftend.clx diff --git a/Packaging/resources/assets/data/boxmiddle.clx b/assets/data/boxmiddle.clx similarity index 100% rename from Packaging/resources/assets/data/boxmiddle.clx rename to assets/data/boxmiddle.clx diff --git a/Packaging/resources/assets/data/boxrightend.clx b/assets/data/boxrightend.clx similarity index 100% rename from Packaging/resources/assets/data/boxrightend.clx rename to assets/data/boxrightend.clx diff --git a/Packaging/resources/assets/data/charbg.clx b/assets/data/charbg.clx similarity index 100% rename from Packaging/resources/assets/data/charbg.clx rename to assets/data/charbg.clx diff --git a/Packaging/resources/assets/data/dirtybuc.clx b/assets/data/dirtybuc.clx similarity index 100% rename from Packaging/resources/assets/data/dirtybuc.clx rename to assets/data/dirtybuc.clx diff --git a/Packaging/resources/assets/data/dirtybucp.clx b/assets/data/dirtybucp.clx similarity index 100% rename from Packaging/resources/assets/data/dirtybucp.clx rename to assets/data/dirtybucp.clx diff --git a/Packaging/resources/assets/data/health.clx b/assets/data/health.clx similarity index 100% rename from Packaging/resources/assets/data/health.clx rename to assets/data/health.clx diff --git a/Packaging/resources/assets/data/healthbox.clx b/assets/data/healthbox.clx similarity index 100% rename from Packaging/resources/assets/data/healthbox.clx rename to assets/data/healthbox.clx diff --git a/Packaging/resources/assets/data/hintbox.clx b/assets/data/hintbox.clx similarity index 100% rename from Packaging/resources/assets/data/hintbox.clx rename to assets/data/hintbox.clx diff --git a/Packaging/resources/assets/data/hintboxbackground.clx b/assets/data/hintboxbackground.clx similarity index 100% rename from Packaging/resources/assets/data/hintboxbackground.clx rename to assets/data/hintboxbackground.clx diff --git a/Packaging/resources/assets/data/hinticons.clx b/assets/data/hinticons.clx similarity index 100% rename from Packaging/resources/assets/data/hinticons.clx rename to assets/data/hinticons.clx diff --git a/Packaging/resources/assets/data/monstertags.clx b/assets/data/monstertags.clx similarity index 100% rename from Packaging/resources/assets/data/monstertags.clx rename to assets/data/monstertags.clx diff --git a/Packaging/resources/assets/data/panel8buc.clx b/assets/data/panel8buc.clx similarity index 100% rename from Packaging/resources/assets/data/panel8buc.clx rename to assets/data/panel8buc.clx diff --git a/Packaging/resources/assets/data/panel8bucp.clx b/assets/data/panel8bucp.clx similarity index 100% rename from Packaging/resources/assets/data/panel8bucp.clx rename to assets/data/panel8bucp.clx diff --git a/Packaging/resources/assets/data/resistance.clx b/assets/data/resistance.clx similarity index 100% rename from Packaging/resources/assets/data/resistance.clx rename to assets/data/resistance.clx diff --git a/Packaging/resources/assets/data/stash.clx b/assets/data/stash.clx similarity index 100% rename from Packaging/resources/assets/data/stash.clx rename to assets/data/stash.clx diff --git a/Packaging/resources/assets/data/stashnavbtns.clx b/assets/data/stashnavbtns.clx similarity index 100% rename from Packaging/resources/assets/data/stashnavbtns.clx rename to assets/data/stashnavbtns.clx diff --git a/Packaging/resources/assets/data/talkbutton.clx b/assets/data/talkbutton.clx similarity index 100% rename from Packaging/resources/assets/data/talkbutton.clx rename to assets/data/talkbutton.clx diff --git a/Packaging/resources/assets/data/xpbar.clx b/assets/data/xpbar.clx similarity index 100% rename from Packaging/resources/assets/data/xpbar.clx rename to assets/data/xpbar.clx diff --git a/Packaging/resources/assets/fonts/12-00.clx b/assets/fonts/12-00.clx similarity index 100% rename from Packaging/resources/assets/fonts/12-00.clx rename to assets/fonts/12-00.clx diff --git a/Packaging/resources/assets/fonts/12-01.clx b/assets/fonts/12-01.clx similarity index 100% rename from Packaging/resources/assets/fonts/12-01.clx rename to assets/fonts/12-01.clx diff --git a/Packaging/resources/assets/fonts/12-02.clx b/assets/fonts/12-02.clx similarity index 100% rename from Packaging/resources/assets/fonts/12-02.clx rename to assets/fonts/12-02.clx diff --git a/Packaging/resources/assets/fonts/12-03.clx b/assets/fonts/12-03.clx similarity index 100% rename from Packaging/resources/assets/fonts/12-03.clx rename to assets/fonts/12-03.clx diff --git a/Packaging/resources/assets/fonts/12-04.clx b/assets/fonts/12-04.clx similarity index 100% rename from Packaging/resources/assets/fonts/12-04.clx rename to assets/fonts/12-04.clx diff --git a/Packaging/resources/assets/fonts/12-1f1.clx b/assets/fonts/12-1f1.clx similarity index 100% rename from Packaging/resources/assets/fonts/12-1f1.clx rename to assets/fonts/12-1f1.clx diff --git a/Packaging/resources/assets/fonts/12-1f3.clx b/assets/fonts/12-1f3.clx similarity index 100% rename from Packaging/resources/assets/fonts/12-1f3.clx rename to assets/fonts/12-1f3.clx diff --git a/Packaging/resources/assets/fonts/12-1f4.clx b/assets/fonts/12-1f4.clx similarity index 100% rename from Packaging/resources/assets/fonts/12-1f4.clx rename to assets/fonts/12-1f4.clx diff --git a/Packaging/resources/assets/fonts/12-1f5.clx b/assets/fonts/12-1f5.clx similarity index 100% rename from Packaging/resources/assets/fonts/12-1f5.clx rename to assets/fonts/12-1f5.clx diff --git a/Packaging/resources/assets/fonts/12-1f6.clx b/assets/fonts/12-1f6.clx similarity index 100% rename from Packaging/resources/assets/fonts/12-1f6.clx rename to assets/fonts/12-1f6.clx diff --git a/Packaging/resources/assets/fonts/12-1f9.clx b/assets/fonts/12-1f9.clx similarity index 100% rename from Packaging/resources/assets/fonts/12-1f9.clx rename to assets/fonts/12-1f9.clx diff --git a/Packaging/resources/assets/fonts/12-20.clx b/assets/fonts/12-20.clx similarity index 100% rename from Packaging/resources/assets/fonts/12-20.clx rename to assets/fonts/12-20.clx diff --git a/Packaging/resources/assets/fonts/12-26.clx b/assets/fonts/12-26.clx similarity index 100% rename from Packaging/resources/assets/fonts/12-26.clx rename to assets/fonts/12-26.clx diff --git a/Packaging/resources/assets/fonts/12-e0.clx b/assets/fonts/12-e0.clx similarity index 100% rename from Packaging/resources/assets/fonts/12-e0.clx rename to assets/fonts/12-e0.clx diff --git a/Packaging/resources/assets/fonts/22-00.clx b/assets/fonts/22-00.clx similarity index 100% rename from Packaging/resources/assets/fonts/22-00.clx rename to assets/fonts/22-00.clx diff --git a/Packaging/resources/assets/fonts/22-01.clx b/assets/fonts/22-01.clx similarity index 100% rename from Packaging/resources/assets/fonts/22-01.clx rename to assets/fonts/22-01.clx diff --git a/Packaging/resources/assets/fonts/22-02.clx b/assets/fonts/22-02.clx similarity index 100% rename from Packaging/resources/assets/fonts/22-02.clx rename to assets/fonts/22-02.clx diff --git a/Packaging/resources/assets/fonts/22-03.clx b/assets/fonts/22-03.clx similarity index 100% rename from Packaging/resources/assets/fonts/22-03.clx rename to assets/fonts/22-03.clx diff --git a/Packaging/resources/assets/fonts/22-04.clx b/assets/fonts/22-04.clx similarity index 100% rename from Packaging/resources/assets/fonts/22-04.clx rename to assets/fonts/22-04.clx diff --git a/Packaging/resources/assets/fonts/22-05.clx b/assets/fonts/22-05.clx similarity index 100% rename from Packaging/resources/assets/fonts/22-05.clx rename to assets/fonts/22-05.clx diff --git a/Packaging/resources/assets/fonts/22-20.clx b/assets/fonts/22-20.clx similarity index 100% rename from Packaging/resources/assets/fonts/22-20.clx rename to assets/fonts/22-20.clx diff --git a/Packaging/resources/assets/fonts/24-00.clx b/assets/fonts/24-00.clx similarity index 100% rename from Packaging/resources/assets/fonts/24-00.clx rename to assets/fonts/24-00.clx diff --git a/Packaging/resources/assets/fonts/24-01.clx b/assets/fonts/24-01.clx similarity index 100% rename from Packaging/resources/assets/fonts/24-01.clx rename to assets/fonts/24-01.clx diff --git a/Packaging/resources/assets/fonts/24-02.clx b/assets/fonts/24-02.clx similarity index 100% rename from Packaging/resources/assets/fonts/24-02.clx rename to assets/fonts/24-02.clx diff --git a/Packaging/resources/assets/fonts/24-03.clx b/assets/fonts/24-03.clx similarity index 100% rename from Packaging/resources/assets/fonts/24-03.clx rename to assets/fonts/24-03.clx diff --git a/Packaging/resources/assets/fonts/24-04.clx b/assets/fonts/24-04.clx similarity index 100% rename from Packaging/resources/assets/fonts/24-04.clx rename to assets/fonts/24-04.clx diff --git a/Packaging/resources/assets/fonts/24-1f1.clx b/assets/fonts/24-1f1.clx similarity index 100% rename from Packaging/resources/assets/fonts/24-1f1.clx rename to assets/fonts/24-1f1.clx diff --git a/Packaging/resources/assets/fonts/24-1f3.clx b/assets/fonts/24-1f3.clx similarity index 100% rename from Packaging/resources/assets/fonts/24-1f3.clx rename to assets/fonts/24-1f3.clx diff --git a/Packaging/resources/assets/fonts/24-1f4.clx b/assets/fonts/24-1f4.clx similarity index 100% rename from Packaging/resources/assets/fonts/24-1f4.clx rename to assets/fonts/24-1f4.clx diff --git a/Packaging/resources/assets/fonts/24-1f5.clx b/assets/fonts/24-1f5.clx similarity index 100% rename from Packaging/resources/assets/fonts/24-1f5.clx rename to assets/fonts/24-1f5.clx diff --git a/Packaging/resources/assets/fonts/24-1f6.clx b/assets/fonts/24-1f6.clx similarity index 100% rename from Packaging/resources/assets/fonts/24-1f6.clx rename to assets/fonts/24-1f6.clx diff --git a/Packaging/resources/assets/fonts/24-1f9.clx b/assets/fonts/24-1f9.clx similarity index 100% rename from Packaging/resources/assets/fonts/24-1f9.clx rename to assets/fonts/24-1f9.clx diff --git a/Packaging/resources/assets/fonts/24-20.clx b/assets/fonts/24-20.clx similarity index 100% rename from Packaging/resources/assets/fonts/24-20.clx rename to assets/fonts/24-20.clx diff --git a/Packaging/resources/assets/fonts/24-26.clx b/assets/fonts/24-26.clx similarity index 100% rename from Packaging/resources/assets/fonts/24-26.clx rename to assets/fonts/24-26.clx diff --git a/Packaging/resources/assets/fonts/24-e0.clx b/assets/fonts/24-e0.clx similarity index 100% rename from Packaging/resources/assets/fonts/24-e0.clx rename to assets/fonts/24-e0.clx diff --git a/Packaging/resources/assets/fonts/30-00.clx b/assets/fonts/30-00.clx similarity index 100% rename from Packaging/resources/assets/fonts/30-00.clx rename to assets/fonts/30-00.clx diff --git a/Packaging/resources/assets/fonts/30-01.clx b/assets/fonts/30-01.clx similarity index 100% rename from Packaging/resources/assets/fonts/30-01.clx rename to assets/fonts/30-01.clx diff --git a/Packaging/resources/assets/fonts/30-02.clx b/assets/fonts/30-02.clx similarity index 100% rename from Packaging/resources/assets/fonts/30-02.clx rename to assets/fonts/30-02.clx diff --git a/Packaging/resources/assets/fonts/30-03.clx b/assets/fonts/30-03.clx similarity index 100% rename from Packaging/resources/assets/fonts/30-03.clx rename to assets/fonts/30-03.clx diff --git a/Packaging/resources/assets/fonts/30-04.clx b/assets/fonts/30-04.clx similarity index 100% rename from Packaging/resources/assets/fonts/30-04.clx rename to assets/fonts/30-04.clx diff --git a/Packaging/resources/assets/fonts/30-20.clx b/assets/fonts/30-20.clx similarity index 100% rename from Packaging/resources/assets/fonts/30-20.clx rename to assets/fonts/30-20.clx diff --git a/Packaging/resources/assets/fonts/42-00.clx b/assets/fonts/42-00.clx similarity index 100% rename from Packaging/resources/assets/fonts/42-00.clx rename to assets/fonts/42-00.clx diff --git a/Packaging/resources/assets/fonts/42-01.clx b/assets/fonts/42-01.clx similarity index 100% rename from Packaging/resources/assets/fonts/42-01.clx rename to assets/fonts/42-01.clx diff --git a/Packaging/resources/assets/fonts/42-02.clx b/assets/fonts/42-02.clx similarity index 100% rename from Packaging/resources/assets/fonts/42-02.clx rename to assets/fonts/42-02.clx diff --git a/Packaging/resources/assets/fonts/42-03.clx b/assets/fonts/42-03.clx similarity index 100% rename from Packaging/resources/assets/fonts/42-03.clx rename to assets/fonts/42-03.clx diff --git a/Packaging/resources/assets/fonts/42-04.clx b/assets/fonts/42-04.clx similarity index 100% rename from Packaging/resources/assets/fonts/42-04.clx rename to assets/fonts/42-04.clx diff --git a/Packaging/resources/assets/fonts/42-20.clx b/assets/fonts/42-20.clx similarity index 100% rename from Packaging/resources/assets/fonts/42-20.clx rename to assets/fonts/42-20.clx diff --git a/Packaging/resources/assets/fonts/46-00.clx b/assets/fonts/46-00.clx similarity index 100% rename from Packaging/resources/assets/fonts/46-00.clx rename to assets/fonts/46-00.clx diff --git a/Packaging/resources/assets/fonts/46-01.clx b/assets/fonts/46-01.clx similarity index 100% rename from Packaging/resources/assets/fonts/46-01.clx rename to assets/fonts/46-01.clx diff --git a/Packaging/resources/assets/fonts/46-02.clx b/assets/fonts/46-02.clx similarity index 100% rename from Packaging/resources/assets/fonts/46-02.clx rename to assets/fonts/46-02.clx diff --git a/Packaging/resources/assets/fonts/46-03.clx b/assets/fonts/46-03.clx similarity index 100% rename from Packaging/resources/assets/fonts/46-03.clx rename to assets/fonts/46-03.clx diff --git a/Packaging/resources/assets/fonts/46-04.clx b/assets/fonts/46-04.clx similarity index 100% rename from Packaging/resources/assets/fonts/46-04.clx rename to assets/fonts/46-04.clx diff --git a/Packaging/resources/assets/fonts/46-20.clx b/assets/fonts/46-20.clx similarity index 100% rename from Packaging/resources/assets/fonts/46-20.clx rename to assets/fonts/46-20.clx diff --git a/Packaging/resources/assets/fonts/black.trn b/assets/fonts/black.trn similarity index 100% rename from Packaging/resources/assets/fonts/black.trn rename to assets/fonts/black.trn diff --git a/Packaging/resources/assets/fonts/blue.trn b/assets/fonts/blue.trn similarity index 100% rename from Packaging/resources/assets/fonts/blue.trn rename to assets/fonts/blue.trn diff --git a/Packaging/resources/assets/fonts/buttonface.trn b/assets/fonts/buttonface.trn similarity index 100% rename from Packaging/resources/assets/fonts/buttonface.trn rename to assets/fonts/buttonface.trn diff --git a/Packaging/resources/assets/fonts/buttonpushed.trn b/assets/fonts/buttonpushed.trn similarity index 100% rename from Packaging/resources/assets/fonts/buttonpushed.trn rename to assets/fonts/buttonpushed.trn diff --git a/assets/fonts/gamedialogred.trn b/assets/fonts/gamedialogred.trn new file mode 100644 index 00000000000..5676b4e78c8 Binary files /dev/null and b/assets/fonts/gamedialogred.trn differ diff --git a/assets/fonts/gamedialogwhite.trn b/assets/fonts/gamedialogwhite.trn new file mode 100644 index 00000000000..3e0f42ed410 Binary files /dev/null and b/assets/fonts/gamedialogwhite.trn differ diff --git a/assets/fonts/gamedialogyellow.trn b/assets/fonts/gamedialogyellow.trn new file mode 100644 index 00000000000..23d06dd3088 Binary files /dev/null and b/assets/fonts/gamedialogyellow.trn differ diff --git a/Packaging/resources/assets/fonts/goldui.trn b/assets/fonts/goldui.trn similarity index 100% rename from Packaging/resources/assets/fonts/goldui.trn rename to assets/fonts/goldui.trn diff --git a/Packaging/resources/assets/fonts/golduis.trn b/assets/fonts/golduis.trn similarity index 100% rename from Packaging/resources/assets/fonts/golduis.trn rename to assets/fonts/golduis.trn diff --git a/Packaging/resources/assets/fonts/grayui.trn b/assets/fonts/grayui.trn similarity index 100% rename from Packaging/resources/assets/fonts/grayui.trn rename to assets/fonts/grayui.trn diff --git a/Packaging/resources/assets/fonts/grayuis.trn b/assets/fonts/grayuis.trn similarity index 100% rename from Packaging/resources/assets/fonts/grayuis.trn rename to assets/fonts/grayuis.trn diff --git a/Packaging/resources/assets/fonts/orange.trn b/assets/fonts/orange.trn similarity index 100% rename from Packaging/resources/assets/fonts/orange.trn rename to assets/fonts/orange.trn diff --git a/Packaging/resources/assets/fonts/red.trn b/assets/fonts/red.trn similarity index 100% rename from Packaging/resources/assets/fonts/red.trn rename to assets/fonts/red.trn diff --git a/Packaging/resources/assets/fonts/white.trn b/assets/fonts/white.trn similarity index 100% rename from Packaging/resources/assets/fonts/white.trn rename to assets/fonts/white.trn diff --git a/Packaging/resources/assets/fonts/whitegold.trn b/assets/fonts/whitegold.trn similarity index 100% rename from Packaging/resources/assets/fonts/whitegold.trn rename to assets/fonts/whitegold.trn diff --git a/Packaging/resources/assets/fonts/yellow.trn b/assets/fonts/yellow.trn similarity index 100% rename from Packaging/resources/assets/fonts/yellow.trn rename to assets/fonts/yellow.trn diff --git a/Packaging/resources/assets/gendata/cut2w.clx b/assets/gendata/cut2w.clx similarity index 100% rename from Packaging/resources/assets/gendata/cut2w.clx rename to assets/gendata/cut2w.clx diff --git a/Packaging/resources/assets/gendata/cut3w.clx b/assets/gendata/cut3w.clx similarity index 100% rename from Packaging/resources/assets/gendata/cut3w.clx rename to assets/gendata/cut3w.clx diff --git a/Packaging/resources/assets/gendata/cut4w.clx b/assets/gendata/cut4w.clx similarity index 100% rename from Packaging/resources/assets/gendata/cut4w.clx rename to assets/gendata/cut4w.clx diff --git a/Packaging/resources/assets/gendata/cutgatew.clx b/assets/gendata/cutgatew.clx similarity index 100% rename from Packaging/resources/assets/gendata/cutgatew.clx rename to assets/gendata/cutgatew.clx diff --git a/Packaging/resources/assets/gendata/cutl1dw.clx b/assets/gendata/cutl1dw.clx similarity index 100% rename from Packaging/resources/assets/gendata/cutl1dw.clx rename to assets/gendata/cutl1dw.clx diff --git a/Packaging/resources/assets/gendata/cutportlw.clx b/assets/gendata/cutportlw.clx similarity index 100% rename from Packaging/resources/assets/gendata/cutportlw.clx rename to assets/gendata/cutportlw.clx diff --git a/Packaging/resources/assets/gendata/cutportrw.clx b/assets/gendata/cutportrw.clx similarity index 100% rename from Packaging/resources/assets/gendata/cutportrw.clx rename to assets/gendata/cutportrw.clx diff --git a/Packaging/resources/assets/gendata/cutstartw.clx b/assets/gendata/cutstartw.clx similarity index 100% rename from Packaging/resources/assets/gendata/cutstartw.clx rename to assets/gendata/cutstartw.clx diff --git a/Packaging/resources/assets/gendata/cutttw.clx b/assets/gendata/cutttw.clx similarity index 100% rename from Packaging/resources/assets/gendata/cutttw.clx rename to assets/gendata/cutttw.clx diff --git a/Packaging/resources/assets/gendata/pause.trn b/assets/gendata/pause.trn similarity index 100% rename from Packaging/resources/assets/gendata/pause.trn rename to assets/gendata/pause.trn diff --git a/Packaging/resources/assets/levels/l1data/sklkngt.dun b/assets/levels/l1data/sklkngt.dun similarity index 100% rename from Packaging/resources/assets/levels/l1data/sklkngt.dun rename to assets/levels/l1data/sklkngt.dun diff --git a/Packaging/resources/assets/levels/l2data/bonechat.dun b/assets/levels/l2data/bonechat.dun similarity index 100% rename from Packaging/resources/assets/levels/l2data/bonechat.dun rename to assets/levels/l2data/bonechat.dun diff --git a/Packaging/resources/assets/levels/towndata/automap.amp b/assets/levels/towndata/automap.amp similarity index 100% rename from Packaging/resources/assets/levels/towndata/automap.amp rename to assets/levels/towndata/automap.amp diff --git a/Packaging/resources/assets/levels/towndata/automap.dun b/assets/levels/towndata/automap.dun similarity index 100% rename from Packaging/resources/assets/levels/towndata/automap.dun rename to assets/levels/towndata/automap.dun diff --git a/assets/lua/devilutionx/events.lua b/assets/lua/devilutionx/events.lua new file mode 100644 index 00000000000..fd79130072b --- /dev/null +++ b/assets/lua/devilutionx/events.lua @@ -0,0 +1,65 @@ +local function CreateEvent() + local functions = {} + return { + ---Adds an event handler. + --- + ---The handler called every time an event is triggered. + ---@param func function + add = function(func) + table.insert(functions, func) + end, + + ---Removes the event handler. + ---@param func function + remove = function(func) + for i, f in ipairs(functions) do + if f == func then + table.remove(functions, i) + break + end + end + end, + + ---Triggers an event. + --- + ---The arguments are forwarded to handlers. + ---@param ... any + trigger = function(...) + if arg ~= nil then + for _, func in ipairs(functions) do + func(table.unpack(arg)) + end + else + for _, func in ipairs(functions) do + func() + end + end + end, + __sig_trigger = "(...)", + } +end + +local events = { + ---Called early on game boot. + GameBoot = CreateEvent(), + __doc_GameBoot = "Called early on game boot.", + + ---Called every time a new game is started. + GameStart = CreateEvent(), + __doc_GameStart = "Called every time a new game is started.", + + ---Called every frame at the end. + GameDrawComplete = CreateEvent(), + __doc_GameDrawComplete = "Called every frame at the end.", +} + +---Registers a custom event type with the given name. +---@param name string +function events.registerCustom(name) + events[name] = CreateEvent() +end + +events.__sig_registerCustom = "(name: string)" +events.__doc_registerCustom = "Register a custom event type." + +return events diff --git a/assets/lua/inspect.lua b/assets/lua/inspect.lua new file mode 100644 index 00000000000..9900a0b81b9 --- /dev/null +++ b/assets/lua/inspect.lua @@ -0,0 +1,371 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local math = _tl_compat and _tl_compat.math or math; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table +local inspect = {Options = {}, } + + + + + + + + + + + + + + + + + +inspect._VERSION = 'inspect.lua 3.1.0' +inspect._URL = 'http://github.com/kikito/inspect.lua' +inspect._DESCRIPTION = 'human-readable representations of tables' +inspect._LICENSE = [[ + MIT LICENSE + + Copyright (c) 2022 Enrique García Cota + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +]] +inspect.KEY = setmetatable({}, { __tostring = function() return 'inspect.KEY' end }) +inspect.METATABLE = setmetatable({}, { __tostring = function() return 'inspect.METATABLE' end }) + +local tostring = tostring +local rep = string.rep +local match = string.match +local char = string.char +local gsub = string.gsub +local fmt = string.format + +local _rawget +if rawget then + _rawget = rawget +else + _rawget = function(t, k) return t[k] end +end + +local function rawpairs(t) + return next, t, nil +end + + + +local function smartQuote(str) + if match(str, '"') and not match(str, "'") then + return "'" .. str .. "'" + end + return '"' .. gsub(str, '"', '\\"') .. '"' +end + + +local shortControlCharEscapes = { + ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", + ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\127"] = "\\127", +} +local longControlCharEscapes = { ["\127"] = "\127" } +for i = 0, 31 do + local ch = char(i) + if not shortControlCharEscapes[ch] then + shortControlCharEscapes[ch] = "\\" .. i + longControlCharEscapes[ch] = fmt("\\%03d", i) + end +end + +local function escape(str) + return (gsub(gsub(gsub(str, "\\", "\\\\"), + "(%c)%f[0-9]", longControlCharEscapes), + "%c", shortControlCharEscapes)) +end + +local luaKeywords = { + ['and'] = true, + ['break'] = true, + ['do'] = true, + ['else'] = true, + ['elseif'] = true, + ['end'] = true, + ['false'] = true, + ['for'] = true, + ['function'] = true, + ['goto'] = true, + ['if'] = true, + ['in'] = true, + ['local'] = true, + ['nil'] = true, + ['not'] = true, + ['or'] = true, + ['repeat'] = true, + ['return'] = true, + ['then'] = true, + ['true'] = true, + ['until'] = true, + ['while'] = true, +} + +local function isIdentifier(str) + return type(str) == "string" and + not not str:match("^[_%a][_%a%d]*$") and + not luaKeywords[str] +end + +local flr = math.floor +local function isSequenceKey(k, sequenceLength) + return type(k) == "number" and + flr(k) == k and + 1 <= (k) and + k <= sequenceLength +end + +local defaultTypeOrders = { + ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, + ['function'] = 5, ['userdata'] = 6, ['thread'] = 7, +} + +local function sortKeys(a, b) + local ta, tb = type(a), type(b) + + + if ta == tb and (ta == 'string' or ta == 'number') then + return (a) < (b) + end + + local dta = defaultTypeOrders[ta] or 100 + local dtb = defaultTypeOrders[tb] or 100 + + + return dta == dtb and ta < tb or dta < dtb +end + +local function getKeys(t) + + local seqLen = 1 + while _rawget(t, seqLen) ~= nil do + seqLen = seqLen + 1 + end + seqLen = seqLen - 1 + + local keys, keysLen = {}, 0 + for k in rawpairs(t) do + if not isSequenceKey(k, seqLen) then + keysLen = keysLen + 1 + keys[keysLen] = k + end + end + table.sort(keys, sortKeys) + return keys, keysLen, seqLen +end + +local function countCycles(x, cycles) + if type(x) == "table" then + if cycles[x] then + cycles[x] = cycles[x] + 1 + else + cycles[x] = 1 + for k, v in rawpairs(x) do + countCycles(k, cycles) + countCycles(v, cycles) + end + countCycles(getmetatable(x), cycles) + end + end +end + +local function makePath(path, a, b) + local newPath = {} + local len = #path + for i = 1, len do newPath[i] = path[i] end + + newPath[len + 1] = a + newPath[len + 2] = b + + return newPath +end + + +local function processRecursive(process, + item, + path, + visited) + if item == nil then return nil end + if visited[item] then return visited[item] end + + local processed = process(item, path) + if type(processed) == "table" then + local processedCopy = {} + visited[item] = processedCopy + local processedKey + + for k, v in rawpairs(processed) do + processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) + if processedKey ~= nil then + processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) + end + end + + local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) + if type(mt) ~= 'table' then mt = nil end + setmetatable(processedCopy, mt) + processed = processedCopy + end + return processed +end + +local function puts(buf, str) + buf.n = buf.n + 1 + buf[buf.n] = str +end + + + +local Inspector = {} + + + + + + + + + + +local Inspector_mt = { __index = Inspector } + +local function tabify(inspector) + puts(inspector.buf, inspector.newline .. rep(inspector.indent, inspector.level)) +end + +function Inspector:getId(v) + local id = self.ids[v] + local ids = self.ids + if not id then + local tv = type(v) + id = (ids[tv] or 0) + 1 + ids[v], ids[tv] = id, id + end + return tostring(id) +end + +function Inspector:putValue(v) + local buf = self.buf + local tv = type(v) + if tv == 'string' then + puts(buf, smartQuote(escape(v))) + elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or + tv == 'cdata' or tv == 'ctype' then + puts(buf, tostring(v)) + elseif tv == 'table' and not self.ids[v] then + local t = v + + if t == inspect.KEY or t == inspect.METATABLE then + puts(buf, tostring(t)) + elseif self.level >= self.depth then + puts(buf, '{...}') + else + if self.cycles[t] > 1 then puts(buf, fmt('<%d>', self:getId(t))) end + + local keys, keysLen, seqLen = getKeys(t) + + puts(buf, '{') + self.level = self.level + 1 + + for i = 1, seqLen + keysLen do + if i > 1 then puts(buf, ',') end + if i <= seqLen then + puts(buf, ' ') + self:putValue(t[i]) + else + local k = keys[i - seqLen] + tabify(self) + if isIdentifier(k) then + puts(buf, k) + else + puts(buf, "[") + self:putValue(k) + puts(buf, "]") + end + puts(buf, ' = ') + self:putValue(t[k]) + end + end + + local mt = getmetatable(t) + if type(mt) == 'table' then + if seqLen + keysLen > 0 then puts(buf, ',') end + tabify(self) + puts(buf, ' = ') + self:putValue(mt) + end + + self.level = self.level - 1 + + if keysLen > 0 or type(mt) == 'table' then + tabify(self) + elseif seqLen > 0 then + puts(buf, ' ') + end + + puts(buf, '}') + end + + else + puts(buf, fmt('<%s %d>', tv, self:getId(v))) + end +end + + + + +function inspect.inspect(root, options) + options = options or {} + + local depth = options.depth or (math.huge) + local newline = options.newline or '\n' + local indent = options.indent or ' ' + local process = options.process + + if process then + root = processRecursive(process, root, {}, {}) + end + + local cycles = {} + countCycles(root, cycles) + + local inspector = setmetatable({ + buf = { n = 0 }, + ids = {}, + cycles = cycles, + depth = depth, + level = 0, + newline = newline, + indent = indent, + }, Inspector_mt) + + inspector:putValue(root) + + return table.concat(inspector.buf) +end + +setmetatable(inspect, { + __call = function(_, root, options) + return inspect.inspect(root, options) + end, +}) + +return inspect diff --git a/assets/lua/repl_prelude.lua b/assets/lua/repl_prelude.lua new file mode 100644 index 00000000000..72e61bc2252 --- /dev/null +++ b/assets/lua/repl_prelude.lua @@ -0,0 +1,7 @@ +events = require('devilutionx.events') +log = require('devilutionx.log') +audio = require('devilutionx.audio') +render = require('devilutionx.render') +message = require('devilutionx.message') +if _DEBUG then dev = require('devilutionx.dev') end +inspect = require('inspect') diff --git a/assets/lua_internal/get_lua_function_signature.lua b/assets/lua_internal/get_lua_function_signature.lua new file mode 100644 index 00000000000..8cd57fcf38a --- /dev/null +++ b/assets/lua_internal/get_lua_function_signature.lua @@ -0,0 +1,41 @@ +-- Gets the signature of native (non-C) Lua function +-- Based on https://stackoverflow.com/a/24216007/181228 + +local function getlocals(l) + local i = 0 + local direction = 1 + return function() + i = i + direction + local k, v = debug.getlocal(l, i) + if (direction == 1 and (k == nil or k.sub(k, 1, 1) == '(')) then + i = -1 + direction = -1 + k, v = debug.getlocal(l, i) + end + return k, v + end +end + +local function dumpsig(f) + assert(type(f) == 'function', "bad argument #1 to 'dumpsig' (function expected)") + local p = {} + pcall(function() + local oldhook + local hook = function(event, line) + for k, v in getlocals(3) do + if k == "(*vararg)" then + table.insert(p, "...") + break + end + table.insert(p, k) + end + debug.sethook(oldhook) + error('aborting the call') + end + oldhook = debug.sethook(hook, "c") + f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) + end) + return "(" .. table.concat(p, ", ") .. ")" +end + +return dumpsig diff --git a/Packaging/resources/assets/nlevels/cutl5w.clx b/assets/nlevels/cutl5w.clx similarity index 100% rename from Packaging/resources/assets/nlevels/cutl5w.clx rename to assets/nlevels/cutl5w.clx diff --git a/Packaging/resources/assets/nlevels/cutl6w.clx b/assets/nlevels/cutl6w.clx similarity index 100% rename from Packaging/resources/assets/nlevels/cutl6w.clx rename to assets/nlevels/cutl6w.clx diff --git a/Packaging/resources/assets/nlevels/l5data/cornerstone.dun b/assets/nlevels/l5data/cornerstone.dun similarity index 100% rename from Packaging/resources/assets/nlevels/l5data/cornerstone.dun rename to assets/nlevels/l5data/cornerstone.dun diff --git a/Packaging/resources/assets/nlevels/l5data/uberroom.dun b/assets/nlevels/l5data/uberroom.dun similarity index 100% rename from Packaging/resources/assets/nlevels/l5data/uberroom.dun rename to assets/nlevels/l5data/uberroom.dun diff --git a/assets/txtdata/Experience.tsv b/assets/txtdata/Experience.tsv new file mode 100644 index 00000000000..62ce5e443fe --- /dev/null +++ b/assets/txtdata/Experience.tsv @@ -0,0 +1,51 @@ +Level Experience +1 2000 +2 4620 +3 8040 +4 12489 +5 18258 +6 25712 +7 35309 +8 47622 +9 63364 +10 83419 +11 108879 +12 141086 +13 181683 +14 231075 +15 313656 +16 424067 +17 571190 +18 766569 +19 1025154 +20 1366227 +21 1814568 +22 2401895 +23 3168651 +24 4166200 +25 5459523 +26 7130496 +27 9281874 +28 12042092 +29 15571031 +30 20066900 +31 25774405 +32 32994399 +33 42095202 +34 53525811 +35 67831218 +36 85670061 +37 107834823 +38 135274799 +39 169122009 +40 210720231 +41 261657253 +42 323800420 +43 399335440 +44 490808349 +45 601170414 +46 733825617 +47 892680222 +48 1082908612 +49 1310707109 +50 1583495809 diff --git a/assets/txtdata/Readme.md b/assets/txtdata/Readme.md new file mode 100644 index 00000000000..dd1c8cb66d3 --- /dev/null +++ b/assets/txtdata/Readme.md @@ -0,0 +1,144 @@ +Data files are based on the format used by Diablo 2. This is a format very +similar to [IANA TSV][iana-tsv] but with fewer restrictions. Existing tools +such as AFJ Sheet, Diablo 2 Excel File Editor, [D2ExcelPlus][d2-excel-plus], +or spreadsheet programs such as Excel and [LibreOffice][libreoffice] Calc can +be used to read/modify these files. If using a program like Excel or +LibreOffice you will need to check that the output matches the expected format +(tabs as delimiters, field values saved with leading spaces and quote +characters preserved, tabs and newlines in fields stripped or transformed to +spaces). + +## Format Specification +For modders, the important thing to keep in mind is that values cannot contain +tab characters or line breaks. If you make sure the file looks roughly the +same after changing your values you shouldn't get any errors. + +A formal description of the format using +[W3C's EBNF for XML notation][w3-xml-ebnf]: +``` +/* + Files MAY start with a UTF8 BOM (mainly to play nicer with Excel). + The first record in a file SHOULD be used as a header + Implementations are free to treat records however they want, using + multiple records as headers or none at all. + All files contain at least one record. The last record in a file SHOULD end + with a trailing newline, however some spreadsheet applications do not + output trailing newlines so we allow flexibility here. This means that an + empty file (zero-length or containing only a UTF8 BOM sequence) is valid + and is typically interpreted as a header for a single unnamed column with + no records. + Records SHOULD contain the same number of fields. +*/ +DXTxtFile ::= utf8bom? ( singleRecord | record+ finalRecord? ) + +utf8bom ::= #xEF #xBB #xBF + +/* + if parsing reaches EOF and the file has no record terminators then the file + MUST be treated as containing a single record with no terminator +*/ +singleRecord ::= fields + +/* + if for some reason you want to end a file with a record containing a single + empty field then the record MUST end with a valid terminator +*/ +record ::= fields recordterm + +/* + this means that the terminator is only truly optional for files where the + final record contains a single field with at least one character, or at least + two fields (as there will then be at least one field separator even if both + fields are zero-length) +*/ +finalRecord ::= nonEmptyField | field fieldsep fields + +/* an empty line is treated as a single empty field */ +fields ::= field ( fieldsep field )* + +/* fields MAY be zero length */ +field ::= character* + +/* + unless the final record only consists of a single field, in which case it + MUST contain at least one character +*/ +nonEmptyField ::= character+ + +/* + Any Char (see https://www.w3.org/TR/xml/#NT-Char) except Tab ("\t", 0x09), + Line Feed ("\n", 0x0A), or Carriage Return ("\r", 0x0D) is allowed as a + field value. For maximum portability characters in the discouraged ranges + described in the linked section SHOULD NOT be used +*/ +character ::= [^#x9#xA#xD] + +/* fields MUST be separated by tabs */ +fieldsep ::= #x9 + +/* + records (other than the final record or the only record in a single-record + file) MUST be terminated by line feeds, a cr/lf pair MAY be used +*/ +recordterm ::= #xD? #xA +``` + +## File Descriptions +The following documentation describes how these files are used in DevilutionX. +Diablo and Hellfire do not use external text files, you cannot use these files +to change the behaviour in the original games or the GoG versions. Diablo 2 +uses a similar but distinct format, [ThePhrozenKeep][d2mods-info] provide a +good reference for modding that game. Diablo 2 Resurrected uses a different +format again, refer to the help files provided alongside the game data +(`Data/Global/Excel`) (also available online at +[D2:R Modding][d2rmodding-utilities]). + +### Experience.tsv +Experience contains the experience value thresholds before a character +advances to the next level. All numeric values in this file MUST be written in +base 10 with no decimal or thousands separators. The first row of this file is +used as a header and requires the following column names: + +#### Level +A numeric value used to set the order for experience thresholds. The header +line MUST be the first line in the file. Levels SHOULD proceed in ascending +order after that starting from level 1. Levels up to 255 are supported, the +highest value will be used as the maximum character level. If you leave any +gaps then characters will not be able to advance past that level and experience +caps will not apply. + +If you're familiar with Diablo 2 text files you might expect to use a MaxLevel +row to set character level limits and to have a level 0 line, we ignore these +lines in DevilutionX. As all characters start at level 1 we use the threshold +for level 1 to determine when characters advance past level 1. The highest +level defined in the file is the maximum level. + +#### Experience +This column determines the experience points required for characters to +advance past that level. For example a file like: +```tsv +Level Experience +1 2000 +2 4000 +3 6000 +4 8000 +5 10000 +``` +Could be used to have characters level up to a max of 5 every 2000 experience +points. They would start at level 1, level up to 2 at 2000 exp, level 3 at +4000 exp, level 4 at 6000 exp, then reach the maxium level of 5 at 8000 exp. +Characters would continue gaining experience until they hit 10000 experience +points and will not level up any further. + +You should provide a value for every row up to (and including) the maximum +level you intend players to be able to reach. If you have an empty cell for an +experience value at a given level then characters will not be able to advance +past that level. They will continue to gain experience without a cap (up to the +hard limit of `2^32-1`, 4,294,967,295). + +[d2-excel-plus]: https://github.com/Cjreek/D2ExcelPlus +[d2mods-info]: https://www.d2mods.info/forum/viewtopic.php?t=34455 +[d2rmodding-utilities]: https://www.d2rmodding.com/utilities +[iana-tsv]: https://www.iana.org/assignments/media-types/text/tab-separated-values +[libreoffice]: https://www.libreoffice.org +[w3-xml-ebnf]: https://www.w3.org/TR/xml/#sec-notation diff --git a/assets/txtdata/classes/README.md b/assets/txtdata/classes/README.md new file mode 100644 index 00000000000..7d1b9199154 --- /dev/null +++ b/assets/txtdata/classes/README.md @@ -0,0 +1,28 @@ +# Player class data + +There is one folder per class. + +### attributes.tsv + + Attribute | Description +------------------:|-------------------------------------- + `baseStr` | Class Starting Strength Stat, uint8_t + `baseMag` | Class Starting Magic Stat, uint8_t + `baseDex` | Class Starting Dexterity Stat, uint8_t + `baseVit` | Class Starting Vitality Stat, uint8_t + `maxStr` | Class Maximum Strength Stat, uint8_t + `maxMag` | Class Maximum Magic Stat, uint8_t + `maxDex` | Class Maximum Dexterity Stat, uint8_t + `maxVit` | Class Maximum Vitality Stat, uint8_t + `blockBonus` | Class Block Bonus, % + `adjLife` | Class Life Adjustment, decimal + `adjMana` | Class Mana Adjustment, decimal + `lvlLife` | Life gained on level up, decimal + `lvlMana` | Mana gained on level up, decimal + `chrLife` | Life from base Vitality, decimal + `chrMana` | Mana from base Magic, decimal + `itmLife` | Life from item bonus Vitality, decimal + `itmMana` | Mana from item bonus Magic, decimal + `baseMagicToHit` | Starting chance to hit with spells/scrolls, % + `baseMeleeToHit` | Starting chance to hit with melee weapons/fists, % + `baseRangedToHit` | Starting chance to hit with ranged weapons, % diff --git a/assets/txtdata/classes/barbarian/attributes.tsv b/assets/txtdata/classes/barbarian/attributes.tsv new file mode 100644 index 00000000000..2da44432de7 --- /dev/null +++ b/assets/txtdata/classes/barbarian/attributes.tsv @@ -0,0 +1,21 @@ +Attribute Value +baseStr 40 +baseMag 0 +baseDex 20 +baseVit 25 +maxStr 255 +maxMag 0 +maxDex 55 +maxVit 150 +blockBonus 30 +adjLife 18 +adjMana 0 +lvlLife 2 +lvlMana 0 +chrLife 2 +chrMana 1 +itmLife 2.5 +itmMana 1 +baseMagicToHit 50 +baseMeleeToHit 50 +baseRangedToHit 50 diff --git a/assets/txtdata/classes/bard/attributes.tsv b/assets/txtdata/classes/bard/attributes.tsv new file mode 100644 index 00000000000..4548b016a57 --- /dev/null +++ b/assets/txtdata/classes/bard/attributes.tsv @@ -0,0 +1,21 @@ +Attribute Value +baseStr 20 +baseMag 20 +baseDex 25 +baseVit 20 +maxStr 120 +maxMag 120 +maxDex 120 +maxVit 100 +blockBonus 25 +adjLife 23 +adjMana 3 +lvlLife 2 +lvlMana 2 +chrLife 1 +chrMana 1.5 +itmLife 1.5 +itmMana 1.75 +baseMagicToHit 60 +baseMeleeToHit 50 +baseRangedToHit 60 diff --git a/assets/txtdata/classes/monk/attributes.tsv b/assets/txtdata/classes/monk/attributes.tsv new file mode 100644 index 00000000000..4d27f9ad851 --- /dev/null +++ b/assets/txtdata/classes/monk/attributes.tsv @@ -0,0 +1,21 @@ +Attribute Value +baseStr 25 +baseMag 15 +baseDex 25 +baseVit 20 +maxStr 150 +maxMag 80 +maxDex 150 +maxVit 80 +blockBonus 25 +adjLife 23 +adjMana 5.5 +lvlLife 2 +lvlMana 2 +chrLife 1 +chrMana 1 +itmLife 1.5 +itmMana 1.5 +baseMagicToHit 50 +baseMeleeToHit 50 +baseRangedToHit 50 diff --git a/assets/txtdata/classes/rogue/attributes.tsv b/assets/txtdata/classes/rogue/attributes.tsv new file mode 100644 index 00000000000..8731a9a3e7a --- /dev/null +++ b/assets/txtdata/classes/rogue/attributes.tsv @@ -0,0 +1,21 @@ +Attribute Value +baseStr 20 +baseMag 15 +baseDex 30 +baseVit 20 +maxStr 55 +maxMag 70 +maxDex 250 +maxVit 80 +blockBonus 20 +adjLife 23 +adjMana 5.5 +lvlLife 2 +lvlMana 2 +chrLife 1 +chrMana 1 +itmLife 1.5 +itmMana 1.5 +baseMagicToHit 50 +baseMeleeToHit 50 +baseRangedToHit 70 diff --git a/assets/txtdata/classes/sorcerer/attributes.tsv b/assets/txtdata/classes/sorcerer/attributes.tsv new file mode 100644 index 00000000000..4cc0445e15c --- /dev/null +++ b/assets/txtdata/classes/sorcerer/attributes.tsv @@ -0,0 +1,21 @@ +Attribute Value +baseStr 15 +baseMag 35 +baseDex 15 +baseVit 20 +maxStr 45 +maxMag 250 +maxDex 85 +maxVit 80 +blockBonus 10 +adjLife 9 +adjMana -2 +lvlLife 1 +lvlMana 2 +chrLife 1 +chrMana 2 +itmLife 1 +itmMana 2 +baseMagicToHit 70 +baseMeleeToHit 50 +baseRangedToHit 50 diff --git a/assets/txtdata/classes/warrior/attributes.tsv b/assets/txtdata/classes/warrior/attributes.tsv new file mode 100644 index 00000000000..447298fb652 --- /dev/null +++ b/assets/txtdata/classes/warrior/attributes.tsv @@ -0,0 +1,21 @@ +Attribute Value +baseStr 30 +baseMag 10 +baseDex 20 +baseVit 25 +maxStr 250 +maxMag 50 +maxDex 60 +maxVit 100 +blockBonus 30 +adjLife 18 +adjMana -1 +lvlLife 2 +lvlMana 1 +chrLife 2 +chrMana 1 +itmLife 2 +itmMana 1 +baseMagicToHit 50 +baseMeleeToHit 70 +baseRangedToHit 60 diff --git a/assets/txtdata/items/item_prefixes.tsv b/assets/txtdata/items/item_prefixes.tsv new file mode 100644 index 00000000000..fbc7cb4cedf --- /dev/null +++ b/assets/txtdata/items/item_prefixes.tsv @@ -0,0 +1,87 @@ +name power power.value1 power.value2 minLevel itemTypes alignment doubleChance useful minVal maxVal multVal +Tin TOHIT_CURSE 6 10 3 Weapon,Bow,Misc Any true false 0 0 -3 +Brass TOHIT_CURSE 1 5 1 Weapon,Bow,Misc Any true false 0 0 -2 +Bronze TOHIT 1 5 1 Weapon,Bow,Misc Any true true 100 500 2 +Iron TOHIT 6 10 4 Weapon,Bow,Misc Any true true 600 1000 3 +Steel TOHIT 11 15 6 Weapon,Bow,Misc Any true true 1100 1500 5 +Silver TOHIT 16 20 9 Weapon,Bow,Misc Good true true 1600 2000 7 +Gold TOHIT 21 30 12 Weapon,Bow,Misc Good true true 2100 3000 9 +Platinum TOHIT 31 40 16 Weapon,Bow Good true true 3100 4000 11 +Mithril TOHIT 41 60 20 Weapon,Bow Good true true 4100 6000 13 +Meteoric TOHIT 61 80 23 Weapon,Bow Any true true 6100 10000 15 +Weird TOHIT 81 100 35 Weapon,Bow Any true true 10100 14000 17 +Strange TOHIT 101 150 60 Weapon,Bow Any true true 14100 20000 20 +Useless DAMP_CURSE 100 100 5 Weapon,Staff,Bow Any true false 0 0 -8 +Bent DAMP_CURSE 50 75 3 Weapon,Staff,Bow Any true false 0 0 -4 +Weak DAMP_CURSE 25 45 1 Weapon,Staff,Bow Any true false 0 0 -3 +Jagged DAMP 20 35 4 Weapon,Staff,Bow Any true true 250 450 3 +Deadly DAMP 36 50 6 Weapon,Staff,Bow Any true true 500 700 4 +Heavy DAMP 51 65 9 Weapon,Staff,Bow Any true true 750 950 5 +Vicious DAMP 66 80 12 Weapon,Staff,Bow Evil true true 1000 1450 8 +Brutal DAMP 81 95 16 Weapon,Staff,Bow Any true true 1500 1950 10 +Massive DAMP 96 110 20 Weapon,Staff,Bow Any true true 2000 2450 13 +Savage DAMP 111 125 23 Weapon,Bow Any true true 2500 3000 15 +Ruthless DAMP 126 150 35 Weapon,Bow Any true true 10100 15000 17 +Merciless DAMP 151 175 60 Weapon,Bow Any true true 15000 20000 20 +Clumsy TOHIT_DAMP_CURSE 50 75 5 Weapon,Staff,Bow Any true false 0 0 -7 +Dull TOHIT_DAMP_CURSE 25 45 1 Weapon,Staff,Bow Any true false 0 0 -5 +Sharp TOHIT_DAMP 20 35 1 Weapon,Staff,Bow Any true false 350 950 5 +Fine TOHIT_DAMP 36 50 6 Weapon,Staff,Bow Any true true 1100 1700 7 +Warrior's TOHIT_DAMP 51 65 10 Weapon,Staff,Bow Any true true 1850 2450 13 +Soldier's TOHIT_DAMP 66 80 15 Weapon,Staff Any true true 2600 3950 17 +Lord's TOHIT_DAMP 81 95 19 Weapon,Staff Any true true 4100 5950 21 +Knight's TOHIT_DAMP 96 110 23 Weapon,Staff Any true true 6100 8450 26 +Master's TOHIT_DAMP 111 125 28 Weapon,Staff Any true true 8600 13000 30 +Champion's TOHIT_DAMP 126 150 40 Weapon,Staff Any true true 15200 24000 33 +King's TOHIT_DAMP 151 175 28 Weapon,Staff Any true true 24100 35000 38 +Vulnerable ACP_CURSE 51 100 3 Armor,Shield Any true false 0 0 -3 +Rusted ACP_CURSE 25 50 1 Armor,Shield Any true false 0 0 -2 +Fine ACP 20 30 1 Armor,Shield Any true true 20 100 2 +Strong ACP 31 40 3 Armor,Shield Any true true 120 200 3 +Grand ACP 41 55 6 Armor,Shield Any true true 220 300 5 +Valiant ACP 56 70 10 Armor,Shield Any true true 320 400 7 +Glorious ACP 71 90 14 Armor,Shield Good true true 420 600 9 +Blessed ACP 91 110 19 Armor,Shield Good true true 620 800 11 +Saintly ACP 111 130 24 Armor,Shield Good true true 820 1200 13 +Awesome ACP 131 150 28 Armor,Shield Good true true 1220 2000 15 +Holy ACP 151 170 35 Armor,Shield Good true true 5200 6000 17 +Godly ACP 171 200 60 Armor,Shield Good true true 6200 7000 20 +Red FIRERES 10 20 4 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 500 1500 2 +Crimson FIRERES 21 30 10 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 2100 3000 2 +Crimson FIRERES 31 40 16 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 3100 4000 2 +Garnet FIRERES 41 50 20 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 8200 12000 3 +Ruby FIRERES 51 60 26 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 17100 20000 5 +Blue LIGHTRES 10 20 4 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 500 1500 2 +Azure LIGHTRES 21 30 10 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 2100 3000 2 +Lapis LIGHTRES 31 40 16 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 3100 4000 2 +Cobalt LIGHTRES 41 50 20 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 8200 12000 3 +Sapphire LIGHTRES 51 60 26 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 17100 20000 5 +White MAGICRES 10 20 4 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 500 1500 2 +Pearl MAGICRES 21 30 10 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 2100 3000 2 +Ivory MAGICRES 31 40 16 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 3100 4000 2 +Crystal MAGICRES 41 50 20 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 8200 12000 3 +Diamond MAGICRES 51 60 26 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 17100 20000 5 +Topaz ALLRES 10 15 8 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 2000 5000 3 +Amber ALLRES 16 20 12 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 7400 10000 3 +Jade ALLRES 21 30 18 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 11000 15000 3 +Obsidian ALLRES 31 40 24 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 24000 40000 4 +Emerald ALLRES 41 50 31 Shield,Weapon,Staff,Bow Any false true 61000 75000 7 +Hyena's MANA_CURSE 11 25 4 Staff,Misc Any false false 100 1000 -2 +Frog's MANA_CURSE 1 10 1 Staff,Misc Evil false false 0 0 -2 +Spider's MANA 10 15 1 Staff,Misc Evil false true 500 1000 2 +Raven's MANA 15 20 5 Staff,Misc Any false true 1100 2000 3 +Snake's MANA 21 30 9 Staff,Misc Any false true 2100 4000 5 +Serpent's MANA 30 40 15 Staff,Misc Any false true 4100 6000 7 +Drake's MANA 41 50 21 Staff,Misc Any false true 6100 10000 9 +Dragon's MANA 51 60 27 Staff,Misc Any false true 10100 15000 11 +Wyrm's MANA 61 80 35 Staff Any false true 15100 19000 12 +Hydra's MANA 81 100 60 Staff Any false true 19100 30000 13 +Angel's SPLLVLADD 1 1 15 Staff Good false true 25000 25000 2 +Arch-Angel's SPLLVLADD 2 2 25 Staff Good false true 50000 50000 3 +Plentiful CHARGES 2 2 4 Staff Any false true 2000 2000 2 +Bountiful CHARGES 3 3 9 Staff Any false true 3000 3000 3 +Flaming FIREDAM 1 10 7 Weapon,Staff Any false true 5000 5000 2 +Lightning LIGHTDAM 2 20 18 Weapon,Staff Any false true 10000 10000 2 +Jester's JESTERS 1 1 7 Weapon Any false true 1200 1200 3 +Crystalline CRYSTALLINE 30 70 5 Weapon Any false true 1000 3000 3 +Doppelganger's DOPPELGANGER 81 95 11 Weapon,Staff Any false true 2000 2400 10 diff --git a/assets/txtdata/items/item_suffixes.tsv b/assets/txtdata/items/item_suffixes.tsv new file mode 100644 index 00000000000..16f905dea1f --- /dev/null +++ b/assets/txtdata/items/item_suffixes.tsv @@ -0,0 +1,99 @@ +name power power.value1 power.value2 minLevel itemTypes alignment doubleChance useful minVal maxVal multVal +quality DAMMOD 1 2 2 Weapon,Staff,Bow Any false true 100 200 2 +maiming DAMMOD 3 5 7 Weapon,Staff,Bow Any false true 1300 1500 3 +slaying DAMMOD 6 8 15 Weapon Any false true 2600 3000 5 +gore DAMMOD 9 12 25 Weapon Any false true 4100 5000 8 +carnage DAMMOD 13 16 35 Weapon Any false true 5100 10000 10 +slaughter DAMMOD 17 20 60 Weapon Any false true 10100 15000 13 +pain GETHIT_CURSE 2 4 4 Armor,Shield,Misc Evil false false 0 0 -4 +tears GETHIT_CURSE 1 1 2 Armor,Shield,Misc Evil false false 0 0 -2 +health GETHIT 1 1 2 Armor,Shield,Misc Good false true 200 200 2 +protection GETHIT 2 2 6 Armor,Shield Good false true 400 800 4 +absorption GETHIT 3 3 12 Armor,Shield Good false true 1001 2500 10 +deflection GETHIT 4 4 20 Armor Good false true 2500 6500 15 +osmosis GETHIT 5 6 50 Armor Good false true 7500 10000 20 +frailty STR_CURSE 6 10 3 Armor,Shield,Weapon,Bow,Misc Evil false false 0 0 -3 +weakness STR_CURSE 1 5 1 Armor,Shield,Weapon,Staff,Bow,Misc Evil false false 0 0 -2 +strength STR 1 5 1 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 200 1000 2 +might STR 6 10 5 Armor,Shield,Weapon,Bow,Misc Any false true 1200 2000 3 +power STR 11 15 11 Armor,Shield,Weapon,Bow,Misc Any false true 2200 3000 4 +giants STR 16 20 17 Armor,Weapon,Bow,Misc Any false true 3200 5000 7 +titans STR 21 30 23 Weapon,Misc Any false true 5200 10000 10 +paralysis DEX_CURSE 6 10 3 Armor,Shield,Weapon,Bow,Misc Evil false false 0 0 -3 +atrophy DEX_CURSE 1 5 1 Armor,Shield,Weapon,Staff,Bow,Misc Evil false false 0 0 -2 +dexterity DEX 1 5 1 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 200 1000 2 +skill DEX 6 10 5 Armor,Shield,Weapon,Bow,Misc Any false true 1200 2000 3 +accuracy DEX 11 15 11 Armor,Shield,Weapon,Bow,Misc Any false true 2200 3000 4 +precision DEX 16 20 17 Armor,Weapon,Bow,Misc Any false true 3200 5000 7 +perfection DEX 21 30 23 Bow,Misc Any false true 5200 10000 10 +the fool MAG_CURSE 6 10 3 Armor,Shield,Weapon,Staff,Bow,Misc Evil false false 0 0 -3 +dyslexia MAG_CURSE 1 5 1 Armor,Shield,Weapon,Staff,Bow,Misc Evil false false 0 0 -2 +magic MAG 1 5 1 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 200 1000 2 +the mind MAG 6 10 5 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 1200 2000 3 +brilliance MAG 11 15 11 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 2200 3000 4 +sorcery MAG 16 20 17 Armor,Weapon,Staff,Bow,Misc Any false true 3200 5000 7 +wizardry MAG 21 30 23 Staff,Misc Any false true 5200 10000 10 +illness VIT_CURSE 6 10 3 Armor,Shield,Weapon,Staff,Bow,Misc Evil false false 0 0 -3 +disease VIT_CURSE 1 5 1 Armor,Shield,Weapon,Staff,Bow,Misc Evil false false 0 0 -2 +vitality VIT 1 5 1 Armor,Shield,Weapon,Staff,Bow,Misc Good false true 200 1000 2 +zest VIT 6 10 5 Armor,Shield,Weapon,Bow,Misc Good false true 1200 2000 3 +vim VIT 11 15 11 Armor,Shield,Weapon,Bow,Misc Good false true 2200 3000 4 +vigor VIT 16 20 17 Armor,Weapon,Bow,Misc Good false true 3200 5000 7 +life VIT 21 30 23 Misc Good false true 5200 10000 10 +trouble ATTRIBS_CURSE 6 10 12 Armor,Shield,Weapon,Staff,Bow,Misc Evil false false 0 0 -10 +the pit ATTRIBS_CURSE 1 5 5 Armor,Shield,Weapon,Staff,Bow,Misc Evil false false 0 0 -5 +the sky ATTRIBS 1 3 5 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 800 4000 5 +the moon ATTRIBS 4 7 11 Armor,Shield,Weapon,Staff,Bow,Misc Any false true 4800 8000 10 +the stars ATTRIBS 8 11 17 Armor,Weapon,Bow,Misc Any false true 8800 12000 15 +the heavens ATTRIBS 12 15 25 Weapon,Bow,Misc Any false true 12800 20000 20 +the zodiac ATTRIBS 16 20 30 Misc Any false true 20800 40000 30 +the vulture LIFE_CURSE 11 25 4 Armor,Shield,Misc Evil false false 0 0 -4 +the jackal LIFE_CURSE 1 10 1 Armor,Shield,Misc Evil false false 0 0 -2 +the fox LIFE 10 15 1 Armor,Shield,Misc Any false true 100 1000 2 +the jaguar LIFE 16 20 5 Armor,Shield,Misc Any false true 1100 2000 3 +the eagle LIFE 21 30 9 Armor,Shield,Misc Any false true 2100 4000 5 +the wolf LIFE 30 40 15 Armor,Shield,Misc Any false true 4100 6000 7 +the tiger LIFE 41 50 21 Armor,Shield,Misc Any false true 6100 10000 9 +the lion LIFE 51 60 27 Armor,Misc Any false true 10100 15000 11 +the mammoth LIFE 61 80 35 Armor Any false true 15100 19000 12 +the whale LIFE 81 100 60 Armor Any false true 19100 30000 13 +fragility DUR_CURSE 100 100 3 Armor,Shield,Weapon Evil false false 0 0 -4 +brittleness DUR_CURSE 26 75 1 Armor,Shield,Weapon Evil false false 0 0 -2 +sturdiness DUR 26 75 1 Armor,Shield,Weapon,Staff Any false true 100 100 2 +craftsmanship DUR 51 100 6 Armor,Shield,Weapon,Staff Any false true 200 200 2 +structure DUR 101 200 12 Armor,Shield,Weapon,Staff Any false true 300 300 2 +the ages INDESTRUCTIBLE 25 Armor,Shield,Weapon,Staff Any false true 600 600 5 +the dark LIGHT_CURSE 4 4 6 Armor,Weapon,Misc Evil false false 0 0 -3 +the night LIGHT_CURSE 2 2 3 Armor,Weapon,Misc Evil false false 0 0 -2 +light LIGHT 2 2 4 Armor,Weapon,Misc Good false true 750 750 2 +radiance LIGHT 4 4 8 Armor,Weapon,Misc Good false true 1500 1500 3 +flame FIRE_ARROWS 1 3 1 Bow Any false true 2000 2000 2 +fire FIRE_ARROWS 1 6 11 Bow Any false true 4000 4000 4 +burning FIRE_ARROWS 1 16 35 Bow Any false true 6000 6000 6 +shock LIGHT_ARROWS 1 6 13 Bow Any false true 6000 6000 2 +lightning LIGHT_ARROWS 1 10 21 Bow Any false true 8000 8000 4 +thunder LIGHT_ARROWS 1 20 60 Bow Any false true 12000 12000 6 +many DUR 100 100 3 Bow Any false true 750 750 2 +plenty DUR 200 200 7 Bow Any false true 1500 1500 3 +thorns THORNS 1 3 1 Armor,Shield Any false true 500 500 2 +corruption NOMANA 5 Armor,Shield,Weapon Evil false false -1000 -1000 2 +thieves ABSHALFTRAP 11 Armor,Shield,Misc Any false true 1500 1500 2 +the bear KNOCKBACK 5 Weapon,Staff,Bow Evil false true 750 750 2 +the bat STEALMANA 3 3 8 Weapon Any false true 7500 7500 3 +vampires STEALMANA 5 5 19 Weapon Any false true 15000 15000 3 +the leech STEALLIFE 3 3 8 Weapon Any false true 7500 7500 3 +blood STEALLIFE 5 5 19 Weapon Any false true 15000 15000 3 +piercing TARGAC 1 1 1 Weapon,Bow Any false true 1000 1000 3 +puncturing TARGAC 2 2 9 Weapon,Bow Any false true 2000 2000 6 +bashing TARGAC 3 3 17 Weapon Any false true 4000 4000 12 +readiness FASTATTACK 1 1 1 Weapon,Staff,Bow Any false true 2000 2000 2 +swiftness FASTATTACK 2 2 10 Weapon,Staff,Bow Any false true 4000 4000 4 +speed FASTATTACK 3 3 19 Weapon,Staff Any false true 8000 8000 8 +haste FASTATTACK 4 4 27 Weapon,Staff Any false true 16000 16000 16 +balance FASTRECOVER 1 1 1 Armor,Misc Any false true 2000 2000 2 +stability FASTRECOVER 2 2 10 Armor,Misc Any false true 4000 4000 4 +harmony FASTRECOVER 3 3 20 Armor,Misc Any false true 8000 8000 8 +blocking FASTBLOCK 1 1 5 Shield Any false true 4000 4000 4 +devastation DEVASTATION 1 1 1 Weapon,Staff,Bow Any false true 1200 1200 3 +decay DECAY 150 250 1 Weapon,Staff,Bow Any false true 200 200 2 +peril PERIL 1 1 5 Weapon,Staff,Bow Any false true 500 500 1 diff --git a/assets/txtdata/items/itemdat.tsv b/assets/txtdata/items/itemdat.tsv new file mode 100644 index 00000000000..5496869379e --- /dev/null +++ b/assets/txtdata/items/itemdat.tsv @@ -0,0 +1,169 @@ +id dropRate class equipType cursorGraphic itemType uniqueBaseItem name shortName minMonsterLevel durability minDamage maxDamage minArmor maxArmor minStrength minMagic minDexterity specialEffects miscId spell usable value +IDI_GOLD Regular Gold Unequippable GOLD Gold NONE Gold 1 0 0 0 0 0 0 0 0 NONE Null true 0 +IDI_WARRIOR Never Weapon One-handed SHORT_SWORD Sword NONE Short Sword 2 20 2 6 0 0 18 0 0 NONE Null false 50 +IDI_WARRSHLD Never Armor One-handed BUCKLER Shield NONE Buckler 2 10 0 0 3 3 0 0 0 NONE Null false 50 +IDI_WARRCLUB Never Weapon One-handed CLUB Mace SPIKCLUB Club 1 20 1 6 0 0 0 0 0 NONE Null false 20 +IDI_ROGUE Never Weapon Two-handed SHORT_BOW Bow NONE Short Bow 1 30 1 4 0 0 0 0 0 NONE Null false 100 +IDI_SORCERER Never Weapon Two-handed SHORT_STAFF Staff NONE Short Staff of Mana 1 25 2 4 0 0 0 20 0 STAFF Mana false 520 +IDI_CLEAVER Never Weapon Two-handed CLEAVER Axe CLEAVER Cleaver 10 10 4 24 0 0 0 0 0 UNIQUE Null false 2000 +IDI_SKCROWN Never Armor Helm THE_UNDEAD_CROWN Helm SKCROWN The Undead Crown 0 50 0 0 15 15 0 0 0 RandomStealLife UNIQUE Null false 10000 +IDI_INFRARING Never Misc Ring EMPYREAN_BAND Ring INFRARING Empyrean Band 0 0 0 0 0 0 0 0 0 UNIQUE Null false 8000 +IDI_ROCK Never Quest Unequippable MAGIC_ROCK Misc NONE Magic Rock 0 0 0 0 0 0 0 0 0 NONE Null false 0 +IDI_OPTAMULET Never Misc Amulet OPTIC_AMULET Amulet OPTAMULET Optic Amulet 0 0 0 0 0 0 0 0 0 UNIQUE Null false 5000 +IDI_TRING Never Misc Ring RING_OF_TRUTH Ring TRING Ring of Truth 0 0 0 0 0 0 0 0 0 UNIQUE Null false 1000 +IDI_BANNER Never Quest Unequippable TAVERN_SIGN Misc NONE Tavern Sign 0 0 0 0 0 0 0 0 0 NONE Null false 0 +IDI_HARCREST Never Armor Helm HARLEQUIN_CREST Helm HARCREST Harlequin Crest 0 15 0 0 0 0 0 0 0 UNIQUE Null false 15 +IDI_STEELVEIL Never Armor Helm VIEL_OF_STEEL Helm STEELVEIL Veil of Steel 0 60 0 0 18 18 0 0 0 UNIQUE Null false 0 +IDI_GLDNELIX Never Misc Unequippable GOLDEN_ELIXIR Misc ELIXIR Golden Elixir 15 0 0 0 0 0 0 0 0 NONE Null false 0 +IDI_ANVIL Never Quest Unequippable ANVIL_OF_FURY Misc NONE Anvil of Fury 0 0 0 0 0 0 0 0 0 NONE Null false 0 +IDI_MUSHROOM Never Quest Unequippable BLACK_MUSHROOM Misc NONE Black Mushroom 0 0 0 0 0 0 0 0 0 NONE Null false 0 +IDI_BRAIN Never Quest Unequippable BRAIN Misc NONE Brain 0 0 0 0 0 0 0 0 0 NONE Null false 0 +IDI_FUNGALTM Never Quest Unequippable FUNGAL_TOME Misc NONE Fungal Tome 0 0 0 0 0 0 0 0 0 NONE Null false 0 +IDI_SPECELIX Never Misc Unequippable SPECTRAL_ELIXIR Misc ELIXIR Spectral Elixir 15 0 0 0 0 0 0 0 0 SPECELIX Null true 0 +IDI_BLDSTONE Never Quest Unequippable BLOOD_STONE Misc NONE Blood Stone 0 0 0 0 0 0 0 0 0 NONE Null false 0 +IDI_MAPOFDOOM Never Quest Unequippable MAP_OF_THE_STARS Misc MAPOFDOOM Cathedral Map 0 0 0 0 0 0 0 0 0 MAPOFDOOM Null true 0 +IDI_EAR Never Quest Unequippable EAR_SORCERER Misc NONE Heart 0 0 0 0 0 0 0 0 0 EAR Null false 0 +IDI_HEAL Never Misc Unequippable POTION_OF_HEALING Misc NONE Potion of Healing 0 0 0 0 0 0 0 0 0 HEAL Null true 50 +IDI_MANA Never Misc Unequippable POTION_OF_MANA Misc NONE Potion of Mana 0 0 0 0 0 0 0 0 0 MANA Null true 50 +IDI_IDENTIFY Never Misc Unequippable SCROLL_OF Misc NONE Scroll of Identify 1 0 0 0 0 0 0 0 0 SCROLL Identify true 200 +IDI_PORTAL Never Misc Unequippable SCROLL_OF Misc NONE Scroll of Town Portal 4 0 0 0 0 0 0 0 0 SCROLL TownPortal true 200 +IDI_ARMOFVAL Never Armor Armor ARKAINES_VALOR MediumArmor ARMOFVAL Arkaine's Valor 0 40 0 0 0 0 0 0 0 UNIQUE Null false 0 +IDI_FULLHEAL Never Misc Unequippable POTION_OF_FULL_HEALING Misc NONE Potion of Full Healing 1 0 0 0 0 0 0 0 0 FULLHEAL Null true 150 +IDI_FULLMANA Never Misc Unequippable POTION_OF_FULL_MANA Misc NONE Potion of Full Mana 1 0 0 0 0 0 0 0 0 FULLMANA Null true 150 +IDI_GRISWOLD Never Weapon One-handed BROAD_SWORD Sword GRISWOLD Griswold's Edge 8 50 4 12 0 0 40 0 0 UNIQUE Null false 750 +IDI_LGTFORGE Never Armor Armor BOVINE HeavyArmor BOVINE Bovine Plate 0 40 0 0 0 0 50 0 0 UNIQUE Null false 0 +IDI_LAZSTAFF Never Misc Unequippable STAFF_OF_LAZARUS Misc LAZSTAFF Staff of Lazarus 0 0 0 0 0 0 0 0 0 NONE Null false 0 +IDI_RESURRECT Never Misc Unequippable SCROLL_OF Misc NONE Scroll of Resurrect 1 0 0 0 0 0 0 0 0 SCROLLT Resurrect true 250 +IDI_OIL Never Misc Unequippable OIL Misc NONE Blacksmith Oil 1 0 0 0 0 0 0 0 0 OILBSMTH Null true 100 +IDI_SHORTSTAFF Never Weapon Two-handed SHORT_STAFF Staff NONE Short Staff 1 25 2 4 0 0 0 0 0 NONE Null false 20 +IDI_BARDSWORD Never Weapon One-handed SHORT_SWORD Sword NONE Sword 2 8 1 5 0 0 15 0 20 NONE Null false 20 +IDI_BARDDAGGER Never Weapon One-handed DAGGER Sword NONE Dagger 1 16 1 4 0 0 0 0 0 NONE Null false 20 +IDI_RUNEBOMB Never Quest Unequippable RUNE_BOMB Misc NONE Rune Bomb 0 0 0 0 0 0 0 0 0 NONE Null false 0 +IDI_THEODORE Never Quest Unequippable THEODORE Misc NONE Theodore 0 0 0 0 0 0 0 0 0 NONE Null false 0 +IDI_AURIC Never Misc Amulet AURIC_AMULET Misc NONE Auric Amulet 0 0 0 0 0 0 0 0 0 AURIC Null false 100 +IDI_NOTE1 Never Quest Unequippable TORN_NOTE_1 Misc NONE Torn Note 1 0 0 0 0 0 0 0 0 0 NONE Null false 0 +IDI_NOTE2 Never Quest Unequippable TORN_NOTE_2 Misc NONE Torn Note 2 0 0 0 0 0 0 0 0 0 NONE Null false 0 +IDI_NOTE3 Never Quest Unequippable TORN_NOTE_3 Misc NONE Torn Note 3 0 0 0 0 0 0 0 0 0 NONE Null false 0 +IDI_FULLNOTE Never Quest Unequippable RECONSTRUCTED_NOTE Misc NONE Reconstructed Note 0 0 0 0 0 0 0 0 0 NOTE Null true 0 +IDI_BROWNSUIT Never Quest Unequippable BROWN_SUIT Misc NONE Brown Suit 0 0 0 0 0 0 0 0 0 NONE Null false 0 +IDI_GREYSUIT Never Quest Unequippable GREY_SUIT Misc NONE Grey Suit 0 0 0 0 0 0 0 0 0 NONE Null false 0 + Regular Armor Helm CAP Helm NONE Cap Cap 1 15 0 0 1 3 0 0 0 NONE Null false 15 + Regular Armor Helm SKULL_CAP Helm SKULLCAP Skull Cap Cap 4 20 0 0 2 4 0 0 0 NONE Null false 25 + Regular Armor Helm HELM Helm HELM Helm Helm 8 30 0 0 4 6 25 0 0 NONE Null false 40 + Regular Armor Helm FULL_HELM Helm NONE Full Helm Helm 12 35 0 0 6 8 35 0 0 NONE Null false 90 + Regular Armor Helm CROWN Helm CROWN Crown Crown 16 40 0 0 8 12 0 0 0 NONE Null false 200 + Regular Armor Helm GREAT_HELM Helm GREATHELM Great Helm Helm 20 60 0 0 10 15 50 0 0 NONE Null false 400 + Regular Armor Armor CAPE LightArmor CAPE Cape Cape 1 12 0 0 1 5 0 0 0 NONE Null false 10 + Regular Armor Armor RAGS LightArmor RAGS Rags Rags 1 6 0 0 2 6 0 0 0 NONE Null false 5 + Regular Armor Armor CLOAK LightArmor CLOAK Cloak Cloak 2 18 0 0 3 7 0 0 0 NONE Null false 40 + Regular Armor Armor ROBE LightArmor ROBE Robe Robe 3 24 0 0 4 7 0 0 0 NONE Null false 75 + Regular Armor Armor QUILTED_ARMOR LightArmor NONE Quilted Armor Armor 4 30 0 0 7 10 0 0 0 NONE Null false 200 + Regular Armor Armor LEATHER_ARMOR LightArmor LEATHARMOR Leather Armor Armor 6 35 0 0 10 13 0 0 0 NONE Null false 300 + Regular Armor Armor HARD_LEATHER_ARMOR LightArmor NONE Hard Leather Armor Armor 7 40 0 0 11 14 0 0 0 NONE Null false 450 + Regular Armor Armor STUDDED_LEATHER_ARMOR LightArmor STUDARMOR Studded Leather Armor Armor 9 45 0 0 15 17 20 0 0 NONE Null false 700 + Regular Armor Armor RING_MAIL MediumArmor NONE Ring Mail Mail 11 50 0 0 17 20 25 0 0 NONE Null false 900 + Regular Armor Armor CHAIN_MAIL MediumArmor CHAINMAIL Chain Mail Mail 13 55 0 0 18 22 30 0 0 NONE Null false 1250 + Regular Armor Armor SCALE_MAIL MediumArmor NONE Scale Mail Mail 15 60 0 0 23 28 35 0 0 NONE Null false 2300 + Regular Armor Armor BREAST_PLATE HeavyArmor BREASTPLATE Breast Plate Plate 16 80 0 0 20 24 40 0 0 NONE Null false 2800 + Regular Armor Armor SPLINT_MAIL MediumArmor NONE Splint Mail Mail 17 65 0 0 30 35 40 0 0 NONE Null false 3250 + Regular Armor Armor FIELD_PLATE HeavyArmor PLATEMAIL Plate Mail Plate 19 75 0 0 42 50 60 0 0 NONE Null false 4600 + Regular Armor Armor FIELD_PLATE HeavyArmor NONE Field Plate Plate 21 80 0 0 40 45 65 0 0 NONE Null false 5800 + Regular Armor Armor GOTHIC_PLATE HeavyArmor NONE Gothic Plate Plate 23 100 0 0 50 60 80 0 0 NONE Null false 8000 + Regular Armor Armor FULL_PLATE_MAIL HeavyArmor FULLPLATE Full Plate Mail Plate 25 90 0 0 60 75 90 0 0 NONE Null false 6500 + Regular Armor One-handed BUCKLER Shield BUCKLER Buckler Shield 1 16 0 0 1 5 0 0 0 NONE Null false 30 + Regular Armor One-handed SMALL_SHIELD Shield SMALLSHIELD Small Shield Shield 5 24 0 0 3 8 25 0 0 NONE Null false 90 + Regular Armor One-handed LARGE_SHIELD Shield LARGESHIELD Large Shield Shield 9 32 0 0 5 10 40 0 0 NONE Null false 200 + Regular Armor One-handed KITE_SHIELD Shield KITESHIELD Kite Shield Shield 14 40 0 0 8 15 50 0 0 NONE Null false 400 + Regular Armor One-handed TOWER_SHIELD Shield GOTHSHIELD Tower Shield Shield 20 50 0 0 12 20 60 0 0 NONE Null false 850 + Regular Armor One-handed GOTHIC_SHIELD Shield GOTHSHIELD Gothic Shield Shield 23 60 0 0 14 18 80 0 0 NONE Null false 2300 + Regular Misc Unequippable POTION_OF_HEALING Misc NONE Potion of Healing 1 0 0 0 0 0 0 0 0 HEAL Null true 50 + Regular Misc Unequippable POTION_OF_FULL_HEALING Misc NONE Potion of Full Healing 1 0 0 0 0 0 0 0 0 FULLHEAL Null true 150 + Regular Misc Unequippable POTION_OF_MANA Misc NONE Potion of Mana 1 0 0 0 0 0 0 0 0 MANA Null true 50 + Regular Misc Unequippable POTION_OF_FULL_MANA Misc NONE Potion of Full Mana 1 0 0 0 0 0 0 0 0 FULLMANA Null true 150 + Regular Misc Unequippable POTION_OF_REJUVENATION Misc NONE Potion of Rejuvenation 3 0 0 0 0 0 0 0 0 REJUV Null true 120 + Regular Misc Unequippable POTION_OF_FULL_REJUVENATION Misc NONE Potion of Full Rejuvenation 7 0 0 0 0 0 0 0 0 FULLREJUV Null true 600 + Regular Misc Unequippable OIL Misc NONE Blacksmith Oil 1 0 0 0 0 0 0 0 0 OILBSMTH Null true 100 + Regular Misc Unequippable OIL Misc NONE Oil of Accuracy 1 0 0 0 0 0 0 0 0 OILACC Null true 500 + Regular Misc Unequippable OIL Misc NONE Oil of Sharpness 1 0 0 0 0 0 0 0 0 OILSHARP Null true 500 + Regular Misc Unequippable OIL Misc NONE Oil 10 0 0 0 0 0 0 0 0 OILOF Null true 0 + Regular Misc Unequippable ELIXIR_OF_STRENGTH Misc NONE Elixir of Strength 15 0 0 0 0 0 0 0 0 ELIXSTR Null true 5000 + Regular Misc Unequippable ELIXIR_OF_MAGIC Misc NONE Elixir of Magic 15 0 0 0 0 0 0 0 0 ELIXMAG Null true 5000 + Regular Misc Unequippable ELIXIR_OF_DEXTERITY Misc NONE Elixir of Dexterity 15 0 0 0 0 0 0 0 0 ELIXDEX Null true 5000 + Regular Misc Unequippable ELIXIR_OF_VITALITY Misc NONE Elixir of Vitality 20 0 0 0 0 0 0 0 0 ELIXVIT Null true 5000 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Healing 1 0 0 0 0 0 0 0 0 SCROLL Healing true 50 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Search 1 0 0 0 0 0 0 0 0 SCROLL Search true 50 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Lightning 4 0 0 0 0 0 0 0 0 SCROLLT Lightning true 150 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Identify 1 0 0 0 0 0 0 0 0 SCROLL Identify true 100 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Resurrect 1 0 0 0 0 0 0 0 0 SCROLLT Resurrect true 250 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Fire Wall 4 0 0 0 0 0 0 17 0 SCROLLT FireWall true 400 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Inferno 1 0 0 0 0 0 0 19 0 SCROLLT Inferno true 100 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Town Portal 4 0 0 0 0 0 0 0 0 SCROLL TownPortal true 200 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Flash 6 0 0 0 0 0 0 21 0 SCROLLT Flash true 500 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Infravision 8 0 0 0 0 0 0 23 0 SCROLL Infravision true 600 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Phasing 6 0 0 0 0 0 0 25 0 SCROLL Phasing true 200 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Mana Shield 8 0 0 0 0 0 0 0 0 SCROLL ManaShield true 1200 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Flame Wave 10 0 0 0 0 0 0 29 0 SCROLLT FlameWave true 650 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Fireball 8 0 0 0 0 0 0 31 0 SCROLLT Fireball true 300 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Stone Curse 6 0 0 0 0 0 0 33 0 SCROLLT StoneCurse true 800 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Chain Lightning 10 0 0 0 0 0 0 35 0 SCROLLT ChainLightning true 750 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Guardian 12 0 0 0 0 0 0 47 0 SCROLLT Guardian true 950 + Never Misc Unequippable SCROLL_OF Misc NONE Non Item 0 0 0 0 0 0 0 0 0 NONE Null false 0 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Nova 14 0 0 0 0 0 0 57 0 SCROLL Nova true 1300 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Golem 10 0 0 0 0 0 0 51 0 SCROLLT Golem true 1100 + Never Misc Unequippable SCROLL_OF Misc NONE Scroll of None 99 0 0 0 0 0 0 61 0 SCROLLT Null true 1000 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Teleport 14 0 0 0 0 0 0 81 0 SCROLL Teleport true 3000 + Regular Misc Unequippable SCROLL_OF Misc NONE Scroll of Apocalypse 22 0 0 0 0 0 0 117 0 SCROLL Apocalypse true 2000 + Regular Misc Unequippable BOOK_BLUE Misc NONE Book of 2 0 0 0 0 0 0 0 0 BOOK Null true 0 + Regular Misc Unequippable BOOK_BLUE Misc NONE Book of 8 0 0 0 0 0 0 0 0 BOOK Null true 0 + Regular Misc Unequippable BOOK_BLUE Misc NONE Book of 14 0 0 0 0 0 0 0 0 BOOK Null true 0 + Regular Misc Unequippable BOOK_BLUE Misc NONE Book of 20 0 0 0 0 0 0 0 0 BOOK Null true 0 + Regular Weapon One-handed DAGGER Sword DAGGER Dagger Dagger 1 16 1 4 0 0 0 0 0 NONE Null false 60 + Regular Weapon One-handed SHORT_SWORD Sword NONE Short Sword Sword 1 24 2 6 0 0 18 0 0 NONE Null false 120 + Regular Weapon One-handed FALCHION Sword FALCHION Falchion Sword 2 20 4 8 0 0 30 0 0 NONE Null false 250 + Regular Weapon One-handed SCIMITAR Sword SCIMITAR Scimitar Sword 4 28 3 7 0 0 23 0 23 NONE Null false 200 + Regular Weapon One-handed CLAYMORE Sword CLAYMORE Claymore Sword 5 36 1 12 0 0 35 0 0 NONE Null false 450 + Regular Weapon One-handed BLADE Sword NONE Blade Blade 4 30 3 8 0 0 25 0 30 NONE Null false 280 + Regular Weapon One-handed SABRE Sword SABRE Sabre Sabre 1 45 1 8 0 0 17 0 0 NONE Null false 170 + Regular Weapon One-handed LONG_SWORD Sword LONGSWR Long Sword Sword 6 40 2 10 0 0 30 0 30 NONE Null false 350 + Regular Weapon One-handed BROAD_SWORD Sword BROADSWR Broad Sword Sword 8 50 4 12 0 0 40 0 0 NONE Null false 750 + Regular Weapon One-handed BASTARD_SWORD Sword BASTARDSWR Bastard Sword Sword 10 60 6 15 0 0 50 0 0 NONE Null false 1000 + Regular Weapon Two-handed TWO_HANDED_SWORD Sword TWOHANDSWR Two-Handed Sword Sword 14 75 8 16 0 0 65 0 0 NONE Null false 1800 + Regular Weapon Two-handed GREAT_SWORD Sword GREATSWR Great Sword Sword 17 100 10 20 0 0 75 0 0 NONE Null false 3000 + Regular Weapon Two-handed SMALL_AXE Axe SMALLAXE Small Axe Axe 2 24 2 10 0 0 0 0 0 NONE Null false 150 + Regular Weapon Two-handed AXE Axe NONE Axe Axe 4 32 4 12 0 0 22 0 0 NONE Null false 450 + Regular Weapon Two-handed LARGE_AXE Axe LARGEAXE Large Axe Axe 6 40 6 16 0 0 30 0 0 NONE Null false 750 + Regular Weapon Two-handed BROAD_AXE Axe BROADAXE Broad Axe Axe 8 50 8 20 0 0 50 0 0 NONE Null false 1000 + Regular Weapon Two-handed BATTLE_AXE Axe BATTLEAXE Battle Axe Axe 10 60 10 25 0 0 65 0 0 NONE Null false 1500 + Regular Weapon Two-handed GREAT_AXE Axe GREATAXE Great Axe Axe 12 75 12 30 0 0 80 0 0 NONE Null false 2500 + Regular Weapon One-handed MACE Mace MACE Mace Mace 2 32 1 8 0 0 16 0 0 NONE Null false 200 + Regular Weapon One-handed MORNING_STAR Mace MORNSTAR Morning Star Mace 3 40 1 10 0 0 26 0 0 NONE Null false 300 + Regular Weapon One-handed WAR_HAMMER Mace WARHAMMER War Hammer Hammer 5 50 5 9 0 0 40 0 0 NONE Null false 600 + Regular Weapon One-handed SPIKED_CLUB Mace SPIKCLUB Spiked Club Club 4 20 3 6 0 0 18 0 0 NONE Null false 225 + Regular Weapon One-handed CLUB Mace SPIKCLUB Club Club 1 20 1 6 0 0 0 0 0 NONE Null false 20 + Regular Weapon One-handed FLAIL Mace FLAIL Flail Flail 7 36 2 12 0 0 30 0 0 NONE Null false 500 + Regular Weapon Two-handed MAUL Mace MAUL Maul Maul 10 50 6 20 0 0 55 0 0 NONE Null false 900 + Double Weapon Two-handed SHORT_BOW Bow SHORTBOW Short Bow Bow 1 30 1 4 0 0 0 0 0 NONE Null false 100 + Double Weapon Two-handed HUNTERS_BOW Bow HUNTBOW Hunter's Bow Bow 3 40 2 5 0 0 20 0 35 NONE Null false 350 + Double Weapon Two-handed HUNTERS_BOW Bow LONGBOW Long Bow Bow 5 35 1 6 0 0 25 0 30 NONE Null false 250 + Double Weapon Two-handed COMPOSITE_BOW Bow COMPBOW Composite Bow Bow 7 45 3 6 0 0 25 0 40 NONE Null false 600 + Double Weapon Two-handed SHORT_BATTLE_BOW Bow NONE Short Battle Bow Bow 9 45 3 7 0 0 30 0 50 NONE Null false 750 + Double Weapon Two-handed LONG_BATTLE_BOW Bow BATTLEBOW Long Battle Bow Bow 11 50 1 10 0 0 30 0 60 NONE Null false 1000 + Double Weapon Two-handed SHORT_WAR_BOW Bow NONE Short War Bow Bow 15 55 4 8 0 0 35 0 70 NONE Null false 1500 + Double Weapon Two-handed LONG_WAR_BOW Bow WARBOW Long War Bow Bow 19 60 1 14 0 0 45 0 80 NONE Null false 2000 + Regular Weapon Two-handed SHORT_STAFF Staff SHORTSTAFF Short Staff Staff 1 25 2 4 0 0 0 0 0 STAFF Null false 30 + Regular Weapon Two-handed LONG_STAFF Staff LONGSTAFF Long Staff Staff 4 35 4 8 0 0 0 0 0 STAFF Null false 100 + Regular Weapon Two-handed COMPOSITE_STAFF Staff COMPSTAFF Composite Staff Staff 6 45 5 10 0 0 0 0 0 STAFF Null false 500 + Regular Weapon Two-handed SHORT_STAFF Staff QUARSTAFF Quarter Staff Staff 9 55 6 12 0 0 20 0 0 STAFF Null false 1000 + Regular Weapon Two-handed WAR_STAFF Staff WARSTAFF War Staff Staff 12 75 8 16 0 0 30 0 0 STAFF Null false 1500 + Regular Misc Ring RING Ring RING Ring Ring 5 0 0 0 0 0 0 0 0 RING Null false 1000 + Regular Misc Ring RING Ring RING Ring Ring 10 0 0 0 0 0 0 0 0 RING Null false 1000 + Regular Misc Ring RING Ring RING Ring Ring 15 0 0 0 0 0 0 0 0 RING Null false 1000 + Regular Misc Amulet AMULET Amulet AMULET Amulet Amulet 8 0 0 0 0 0 0 0 0 AMULET Null false 1200 + Regular Misc Amulet AMULET Amulet AMULET Amulet Amulet 16 0 0 0 0 0 0 0 0 AMULET Null false 1200 + Regular Misc Unequippable RUNE_OF_FIRE Misc NONE Rune of Fire Rune 1 0 0 0 0 0 0 0 0 RUNEF Null true 100 + Regular Misc Unequippable RUNE_OF_LIGHTNING Misc NONE Rune of Lightning Rune 3 0 0 0 0 0 0 13 0 RUNEL Null true 200 + Regular Misc Unequippable GREATER_RUNE_OF_FIRE Misc NONE Greater Rune of Fire Rune 7 0 0 0 0 0 0 42 0 GR_RUNEF Null true 400 + Regular Misc Unequippable GREATER_RUNE_OF_LIGHTNING Misc NONE Greater Rune of Lightning Rune 7 0 0 0 0 0 0 42 0 GR_RUNEL Null true 500 + Regular Misc Unequippable RUNE_OF_STONE Misc NONE Rune of Stone Rune 7 0 0 0 0 0 0 25 0 RUNES Null true 300 +IDI_SORCERER Never Weapon Two-handed SHORT_STAFF Staff NONE Short Staff of Charged Bolt 1 25 2 4 0 0 0 20 0 STAFF ChargedBolt false 520 +IDI_ARENAPOT Never Misc Unequippable ARENA_POTION Misc NONE Arena Potion 7 0 0 0 0 0 0 0 0 ARENAPOT Null true 0 diff --git a/assets/txtdata/items/unique_itemdat.tsv b/assets/txtdata/items/unique_itemdat.tsv new file mode 100644 index 00000000000..84c1c8070d5 --- /dev/null +++ b/assets/txtdata/items/unique_itemdat.tsv @@ -0,0 +1,111 @@ +name uniqueBaseItem minLevel value power0 power0.value1 power0.value2 power1 power1.value1 power1.value2 power2 power2.value1 power2.value2 power3 power3.value1 power3.value2 power4 power4.value1 power4.value2 power5 power5.value1 power5.value2 +The Butcher's Cleaver CLEAVER 1 3650 STR 10 10 SETDAM 4 24 SETDUR 10 10 +The Undead Crown SKCROWN 1 16650 RNDSTEALLIFE SETAC 8 8 INVCURS 77 +Empyrean Band INFRARING 1 8000 ATTRIBS 2 2 LIGHT 2 2 FASTRECOVER 1 1 ABSHALFTRAP +Optic Amulet OPTAMULET 1 9750 LIGHT 2 2 LIGHTRES 20 20 GETHIT 1 1 MAG 5 5 INVCURS 44 +Ring of Truth TRING 1 9100 LIFE 10 10 GETHIT 1 1 ALLRES 10 10 INVCURS 10 +Harlequin Crest HARCREST 1 4000 AC_CURSE 3 3 GETHIT 1 1 ATTRIBS 2 2 LIFE 7 7 MANA 7 7 INVCURS 81 +Veil of Steel STEELVEIL 1 63800 ALLRES 50 50 LIGHT_CURSE 2 2 ACP 60 60 MANA_CURSE 30 30 STR 15 15 VIT 15 15 +Arkaine's Valor ARMOFVAL 1 42000 SETAC 25 25 VIT 10 10 GETHIT 3 3 FASTRECOVER 3 3 +Griswold's Edge GRISWOLD 1 42000 FIREDAM 1 10 TOHIT 25 25 FASTATTACK 2 2 KNOCKBACK MANA 20 20 LIFE_CURSE 20 20 +Bovine Plate BOVINE 1 400 SETAC 150 150 INDESTRUCTIBLE LIGHT 5 5 ALLRES 30 30 MANA_CURSE 50 50 SPLLVLADD -2 -2 +The Rift Bow SHORTBOW 1 1800 RNDARROWVEL DAMMOD 2 2 DEX_CURSE 3 3 +The Needler SHORTBOW 2 8900 TOHIT 50 50 SETDAM 1 3 FASTATTACK 2 2 INVCURS 158 +The Celestial Bow LONGBOW 2 1200 NOMINSTR DAMMOD 2 2 SETAC 5 5 INVCURS 133 +Deadly Hunter COMPBOW 3 8750 3XDAMVDEM 10 10 TOHIT 20 20 MAG_CURSE 5 5 INVCURS 108 +Bow of the Dead COMPBOW 5 2500 TOHIT 10 10 DEX 4 4 VIT_CURSE 3 3 LIGHT_CURSE 2 2 SETDUR 30 30 INVCURS 108 +The Blackoak Bow LONGBOW 5 2500 DEX 10 10 VIT_CURSE 10 10 DAMP 50 50 LIGHT_CURSE 1 1 +Flamedart HUNTBOW 10 14250 FIRE_ARROWS 0 0 FIREDAM 1 6 TOHIT 20 20 FIRERES 40 40 +Fleshstinger LONGBOW 13 16500 DEX 15 15 TOHIT 40 40 DAMP 80 80 DUR 6 6 +Windforce WARBOW 17 37750 STR 5 5 DAMP 200 200 KNOCKBACK INVCURS 164 +Eaglehorn BATTLEBOW 26 42500 DEX 20 20 TOHIT 50 50 DAMP 100 100 INDESTRUCTIBLE INVCURS 108 +Gonnagal's Dirk DAGGER 1 7040 DEX_CURSE 5 5 DAMMOD 4 4 FASTATTACK 2 2 FIRERES 25 25 INVCURS 54 +The Defender SABRE 1 2000 SETAC 5 5 VIT 5 5 TOHIT_CURSE 5 5 +Gryphon's Claw FALCHION 1 1000 DAMP 100 100 MAG_CURSE 2 2 DEX_CURSE 5 5 INVCURS 68 +Black Razor DAGGER 1 2000 DAMP 150 150 VIT 2 2 SETDUR 5 5 INVCURS 53 +Gibbous Moon BROADSWR 2 6660 ATTRIBS 2 2 DAMP 25 25 MANA 15 15 LIGHT_CURSE 3 3 +Ice Shank LONGSWR 3 5250 FIRERES 40 40 SETDUR 15 15 STR 5 10 +The Executioner's Blade FALCHION 3 7080 DAMP 150 150 LIFE_CURSE 10 10 LIGHT_CURSE 1 1 DUR 200 200 INVCURS 58 +The Bonesaw CLAYMORE 6 4400 DAMMOD 10 10 STR 10 10 MAG_CURSE 5 5 DEX_CURSE 5 5 LIFE 10 10 MANA_CURSE 10 10 +Shadowhawk BROADSWR 8 13750 LIGHT_CURSE 2 2 STEALLIFE 5 5 TOHIT 15 15 ALLRES 5 5 +Wizardspike DAGGER 11 12920 MAG 15 15 MANA 35 35 TOHIT 25 25 ALLRES 15 15 INVCURS 50 +Lightsabre SABRE 13 19150 LIGHT 2 2 LIGHTDAM 1 10 TOHIT 20 20 LIGHTRES 50 50 +The Falcon's Talon SCIMITAR 15 7867 FASTATTACK 4 4 TOHIT 20 20 DAMP_CURSE 33 33 DEX 10 10 INVCURS 68 +Inferno LONGSWR 17 34600 FIREDAM 2 12 LIGHT 3 3 MANA 20 20 FIRERES 80 80 +Doombringer BASTARDSWR 19 18250 TOHIT 25 25 DAMP 250 250 ATTRIBS_CURSE 5 5 LIFE_CURSE 25 25 LIGHT_CURSE 2 2 +The Grizzly TWOHANDSWR 23 50000 STR 20 20 VIT_CURSE 5 5 DAMP 200 200 KNOCKBACK DUR 100 100 INVCURS 160 +The Grandfather GREATSWR 27 119800 ONEHAND ATTRIBS 5 5 TOHIT 20 20 DAMP 70 70 LIFE 20 20 INVCURS 161 +The Mangler LARGEAXE 2 2850 DAMP 200 200 DEX_CURSE 5 5 MAG_CURSE 5 5 MANA_CURSE 10 10 INVCURS 144 +Sharp Beak LARGEAXE 2 2850 LIFE 20 20 MAG_CURSE 10 10 MANA_CURSE 10 10 INVCURS 143 +BloodSlayer BROADAXE 3 2500 DAMP 100 100 3XDAMVDEM 50 50 ATTRIBS_CURSE 5 5 SPLLVLADD -1 -1 INVCURS 144 +The Celestial Axe BATTLEAXE 4 14100 NOMINSTR TOHIT 15 15 LIFE 15 15 STR_CURSE 15 15 +Wicked Axe LARGEAXE 5 31150 TOHIT 30 30 DEX 10 10 VIT_CURSE 10 10 GETHIT 1 6 INDESTRUCTIBLE INVCURS 143 +Stonecleaver BROADAXE 7 23900 LIFE 30 30 TOHIT 20 20 DAMP 50 50 LIGHTRES 40 40 INVCURS 104 +Aguinara's Hatchet SMALLAXE 12 24800 SPLLVLADD 1 1 MAG 10 10 MAGICRES 80 80 +Hellslayer BATTLEAXE 15 26200 STR 8 8 VIT 8 8 DAMP 100 100 LIFE 25 25 MANA_CURSE 25 25 +Messerschmidt's Reaver GREATAXE 25 58000 DAMP 200 200 DAMMOD 15 15 ATTRIBS 5 5 LIFE_CURSE 50 50 FIREDAM 2 12 INVCURS 163 +Crackrust MACE 1 11375 ATTRIBS 2 2 INDESTRUCTIBLE ALLRES 15 15 DAMP 50 50 SPLLVLADD -1 -1 +Hammer of Jholm MAUL 1 8700 DAMP 4 10 INDESTRUCTIBLE STR 3 3 TOHIT 15 15 +Civerb's Cudgel MACE 1 2000 3XDAMVDEM 35 35 DEX_CURSE 5 5 MAG_CURSE 2 2 +The Celestial Star FLAIL 2 7810 NOMINSTR LIGHT 2 2 DAMMOD 10 10 AC_CURSE 8 8 INVCURS 131 +Baranar's Star MORNSTAR 5 6850 TOHIT 12 12 DAMP 80 80 FASTATTACK 1 1 VIT 4 4 DEX_CURSE 4 4 SETDUR 60 60 +Gnarled Root SPIKCLUB 9 9820 TOHIT 20 20 DAMP 300 300 DEX 10 10 MAG 5 5 ALLRES 10 10 AC_CURSE 10 10 +The Cranium Basher MAUL 12 36500 DAMMOD 20 20 STR 15 15 INDESTRUCTIBLE MANA_CURSE 150 150 ALLRES 5 5 INVCURS 122 +Schaefer's Hammer WARHAMMER 16 56125 DAMP_CURSE 100 100 LIGHTDAM 1 50 LIFE 50 50 TOHIT 30 30 LIGHTRES 80 80 LIGHT 1 1 +Dreamflange MACE 26 26450 MAG 30 30 MANA 50 50 MAGICRES 50 50 LIGHT 2 2 SPLLVLADD 1 1 +Staff of Shadows LONGSTAFF 2 1250 MAG_CURSE 10 10 TOHIT 10 10 DAMP 60 60 LIGHT_CURSE 2 2 FASTATTACK 1 1 +Immolator LONGSTAFF 4 3900 FIRERES 20 20 FIREDAM 4 4 MANA 10 10 VIT_CURSE 5 5 +Storm Spire WARSTAFF 8 22500 LIGHTRES 50 50 LIGHTDAM 2 8 STR 10 10 MAG_CURSE 10 10 +Gleamsong SHORTSTAFF 8 6520 MANA 25 25 STR_CURSE 3 3 VIT_CURSE 3 3 SPELL 10 76 +Thundercall COMPSTAFF 14 22250 TOHIT 35 35 LIGHTDAM 1 10 SPELL 3 76 LIGHTRES 30 30 LIGHT 2 2 +The Protector SHORTSTAFF 16 17240 VIT 5 5 GETHIT 5 5 SETAC 40 40 SPELL 2 86 THORNS 1 3 INVCURS 162 +Naj's Puzzler LONGSTAFF 18 34000 MAG 20 20 DEX 10 10 ALLRES 20 20 SPELL 23 57 LIFE_CURSE 25 25 +Mindcry QUARSTAFF 20 41500 MAG 15 15 SPELL 13 69 ALLRES 15 15 SPLLVLADD 1 1 +Rod of Onan WARSTAFF 22 44167 SPELL 21 50 DAMP 100 100 ATTRIBS 5 5 +Helm of Spirits HELM 1 7525 STEALLIFE 5 5 INVCURS 77 +Thinking Cap SKULLCAP 6 2020 MANA 30 30 SPLLVLADD 2 2 ALLRES 20 20 SETDUR 1 1 INVCURS 93 +OverLord's Helm HELM 7 12500 STR 20 20 DEX 15 15 VIT 5 5 MAG_CURSE 20 20 SETDUR 15 15 INVCURS 99 +Fool's Crest HELM 12 10150 ATTRIBS_CURSE 4 4 LIFE 100 100 GETHIT_CURSE 1 6 THORNS 1 3 INVCURS 80 +Gotterdamerung GREATHELM 21 54900 ATTRIBS 20 20 SETAC 60 60 GETHIT 4 4 ALLRESZERO LIGHT_CURSE 4 4 INVCURS 85 +Royal Circlet CROWN 27 24875 ATTRIBS 10 10 MANA 40 40 SETAC 40 40 LIGHT 1 1 INVCURS 79 +Torn Flesh of Souls RAGS 2 4825 SETAC 8 8 VIT 10 10 GETHIT 1 1 INDESTRUCTIBLE INVCURS 92 +The Gladiator's Bane STUDARMOR 6 3450 SETAC 25 25 GETHIT 2 2 DUR 200 200 ATTRIBS_CURSE 3 3 +The Rainbow Cloak CLOAK 2 4900 SETAC 10 10 ATTRIBS 1 1 ALLRES 10 10 LIFE 5 5 DUR 50 50 INVCURS 138 +Leather of Aut LEATHARMOR 4 10550 SETAC 15 15 STR 5 5 MAG_CURSE 5 5 DEX 5 5 INDESTRUCTIBLE +Wisdom's Wrap ROBE 5 6200 MAG 5 5 MANA 10 10 LIGHTRES 25 25 SETAC 15 15 GETHIT 1 1 INVCURS 138 +Sparking Mail CHAINMAIL 9 15750 SETAC 30 30 LIGHTDAM 1 10 +Scavenger Carapace BREASTPLATE 13 14000 GETHIT 15 15 AC_CURSE 30 30 DEX 5 5 LIGHTRES 40 40 +Nightscape CAPE 16 11600 FASTRECOVER 2 2 LIGHT_CURSE 4 4 SETAC 15 15 DEX 3 3 ALLRES 20 20 INVCURS 138 +Naj's Light Plate PLATEMAIL 19 78700 NOMINSTR MAG 5 5 MANA 20 20 ALLRES 20 20 SPLLVLADD 1 1 INVCURS 159 +Demonspike Coat FULLPLATE 25 251175 SETAC 100 100 GETHIT 6 6 STR 10 10 INDESTRUCTIBLE FIRERES 50 50 +The Deflector BUCKLER 1 1500 SETAC 7 7 ALLRES 10 10 DAMP_CURSE 20 20 TOHIT_CURSE 5 5 INVCURS 83 +Split Skull Shield BUCKLER 1 2025 SETAC 10 10 LIFE 10 10 STR 2 2 LIGHT_CURSE 1 1 SETDUR 15 15 INVCURS 116 +Dragon's Breach KITESHIELD 2 19200 FIRERES 25 25 STR 5 5 SETAC 20 20 MAG_CURSE 5 5 INDESTRUCTIBLE INVCURS 117 +Blackoak Shield SMALLSHIELD 4 5725 DEX 10 10 VIT_CURSE 10 10 SETAC 18 18 LIGHT_CURSE 1 1 DUR 150 150 INVCURS 146 +Holy Defender LARGESHIELD 10 13800 SETAC 15 15 GETHIT 2 2 FIRERES 20 20 DUR 200 200 FASTBLOCK 1 1 INVCURS 146 +Stormshield GOTHSHIELD 24 49000 SETAC 40 40 GETHIT_CURSE 4 4 STR 10 10 INDESTRUCTIBLE FASTBLOCK 1 1 INVCURS 148 +Bramble RING 1 1000 ATTRIBS_CURSE 2 2 DAMMOD 3 3 MANA 10 10 INVCURS 9 +Ring of Regha RING 1 4175 MAG 10 10 MAGICRES 10 10 LIGHT 1 1 STR_CURSE 3 3 DEX_CURSE 3 3 INVCURS 11 +The Bleeder RING 2 8500 MAGICRES 20 20 MANA 30 30 LIFE_CURSE 10 10 INVCURS 8 +Constricting Ring RING 5 62000 ALLRES 75 75 DRAINLIFE INVCURS 14 +Ring of Engagement RING 11 12476 GETHIT 1 2 THORNS 1 3 SETAC 5 5 TARGAC 2 2 INVCURS 13 +Giant's Knuckle RING 8 8000 STR 60 60 DEX_CURSE 30 30 INVCURS 179 +Mercurial Ring RING 8 8000 DEX 60 60 STR_CURSE 30 30 INVCURS 176 +Xorine's Ring RING 8 8000 MAG 60 60 STR_CURSE 30 30 INVCURS 168 +Karik's Ring RING 8 8000 VIT 60 60 MAG_CURSE 30 30 INVCURS 173 +Ring of Magma RING 8 8000 FIRERES 60 60 LIGHTRES_CURSE 30 30 MAGICRES_CURSE 30 30 INVCURS 184 +Ring of the Mystics RING 8 8000 MAGICRES 60 60 FIRERES_CURSE 30 30 LIGHTRES_CURSE 30 30 INVCURS 181 +Ring of Thunder RING 8 8000 LIGHTRES 60 60 FIRERES_CURSE 30 30 MAGICRES_CURSE 30 30 INVCURS 177 +Amulet of Warding AMULET 12 30000 ALLRES 40 40 LIFE_CURSE 100 100 INVCURS 170 +Gnat Sting HUNTBOW 15 30000 MULT_ARROWS 3 3 SETDAM 1 2 FASTATTACK 1 1 INDESTRUCTIBLE INVCURS 210 +Flambeau COMPBOW 11 30000 FIREBALL 15 20 SETDAM 0 0 INDESTRUCTIBLE INVCURS 209 +Armor of Gloom FULLPLATE 25 200000 NOMINSTR SETAC 225 225 ALLRESZERO LIGHT_CURSE 2 2 INVCURS 203 +Blitzen COMPBOW 13 30000 ADDACLIFE 10 15 SETDAM 0 0 INDESTRUCTIBLE INVCURS 219 +Thunderclap WARHAMMER 13 30000 ADDMANAAC 3 6 STR 20 20 LIGHTRES 30 30 LIGHT 2 2 INDESTRUCTIBLE INVCURS 205 +Shirotachi GREATSWR 21 36000 ONEHAND FASTATTACK 4 4 TARGAC 2 2 LIGHTDAM 6 6 +Eater of Souls TWOHANDSWR 23 42000 INDESTRUCTIBLE LIFE 50 50 STEALLIFE 5 5 STEALMANA 5 5 DRAINLIFE INVCURS 200 +Diamondedge LONGSWR 17 42000 SETDUR 10 10 TOHIT 50 50 DAMP 100 100 LIGHTRES 50 50 SETAC 10 10 INVCURS 206 +Bone Chain Armor CHAINMAIL 13 36000 SETAC 40 40 ACUNDEAD INVCURS 204 +Demon Plate Armor FULLPLATE 25 80000 SETAC 80 80 ACDEMON INVCURS 225 +Acolyte's Amulet AMULET 10 10000 MANATOLIFE 50 50 INVCURS 183 +Gladiator's Ring RING 10 10000 LIFETOMANA 40 40 INVCURS 186 diff --git a/assets/txtdata/missiles/missile_sprites.tsv b/assets/txtdata/missiles/missile_sprites.tsv new file mode 100644 index 00000000000..571609e6ffb --- /dev/null +++ b/assets/txtdata/missiles/missile_sprites.tsv @@ -0,0 +1,60 @@ +id width width2 name numFrames flags frameDelay frameLength +Arrow 96 16 arrows 1 NotAnimated 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16 +Fireball 96 16 fireba 16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14 +Guardian 96 16 guard 3 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 15,14,3,0,0,0,0,0,0,0,0,0,0,0,0,0 +Lightning 96 16 lghning 1 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 +FireWall 128 32 firewal 2 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 13,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +MagmaBallExplosion 128 32 magblos 1 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10 +TownPortal 96 16 portal 2 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16 +FlashBottom 160 48 bluexfr 1 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19 +FlashTop 160 48 bluexbk 1 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19 +ManaShield 96 16 manashld 1 NotAnimated 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +BloodHit 96 16 4 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15 +BoneHit 128 32 3 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 +MetalHit 96 16 3 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10 +FireArrow 96 16 farrow 16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 +DoomSerpents 96 16 doom 9 MonsterOwned 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15 +Golem 0 0 1 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +Spurt 128 32 2 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 +ApocalypseBoom 96 16 newexp 1 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15 +StoneCurseShatter 128 32 shatter1 1 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12 +BigExplosion 160 48 bigexp 1 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15 +Inferno 96 16 inferno 1 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20 +ThinLightning 96 16 thinlght 1 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 +BloodStar 128 32 flare 1 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16 +BloodStarExplosion 128 32 flareexp 1 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 +MagmaBall 128 32 magball 8 MonsterOwned 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16 +Krull 96 16 krull 1 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14 +ChargedBolt 64 0 miniltng 1 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 +HolyBolt 96 16 holy 16 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14 +HolyBoltExplosion 160 48 holyexpl 1 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 +LightningArrow 96 16 larrow 16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 +FireArrowExplosion 64 0 1 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6 +Acid 96 16 acidbf 16 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 +AcidSplat 96 16 acidspla 1 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 +AcidPuddle 96 16 acidpud 2 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 9,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +Etherealize 96 16 1 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +Elemental 96 16 firerun 8 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12 +Resurrect 96 16 ressur1 1 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16 +BoneSpirit 96 16 sklball 9 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 16,16,16,16,16,16,16,16,8,0,0,0,0,0,0,0 +RedPortal 96 16 rportal 2 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16 +DiabloApocalypseBoom 160 48 fireplar 1 MonsterOwned 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17 +BloodStarBlue 96 16 scubmisb 1 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16 +BloodStarBlueExplosion 128 32 scbsexpb 1 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6 +BloodStarYellow 96 16 scubmisc 1 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16 +BloodStarYellowExplosion 128 32 scbsexpc 1 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6 +BloodStarRed 96 16 scubmisd 1 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16 +BloodStarRedExplosion 128 32 scbsexpd 1 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6 +HorkSpawn 96 16 spawns 8 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9 +Reflect 160 64 reflect 1 NotAnimated 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +OrangeFlare 96 8 ms_ora 16 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15 +BlueFlare 96 8 ms_bla 16 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15 +RedFlare 96 8 ms_reb 16 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15 +YellowFlare 96 8 ms_yeb 16 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15 +Rune 96 8 rglows1 1 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10 +YellowFlareExplosion 220 78 ex_yel2 1 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10 +BlueFlareExplosion 212 86 ex_blu2 1 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10 +RedFlareExplosion 292 114 ex_red3 1 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 +BlueFlare2 96 8 ms_blb 16 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15 +OrangeFlareExplosion 96 -12 ex_ora1 1 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13 +BlueFlareExplosion2 292 114 ex_blu3 1 MonsterOwned 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 diff --git a/assets/txtdata/monsters/monstdat.tsv b/assets/txtdata/monsters/monstdat.tsv new file mode 100644 index 00000000000..2eb413075ea --- /dev/null +++ b/assets/txtdata/monsters/monstdat.tsv @@ -0,0 +1,139 @@ +_monster_id name assetsSuffix soundSuffix trnFile availability width image hasSpecial hasSpecialSound frames[6] rate[6] minDunLvl maxDunLvl level hitPointsMinimum hitPointsMaximum ai abilityFlags intelligence toHit animFrameNum minDamage maxDamage toHitSpecial animFrameNumSpecial minDamageSpecial maxDamageSpecial armorClass monsterClass resistance resistanceHell selectionType treasure exp +MT_NZOMBIE Zombie zombie\zombie Always 128 799 false false 11,24,12,6,16,0 4,1,1,1,1,1 1 2 1 4 7 Zombie 0 10 8 2 5 0 0 0 0 5 Undead IMMUNE_MAGIC IMMUNE_MAGIC 3 54 +MT_BZOMBIE Ghoul zombie\zombie zombie\bluered Always 128 799 false false 11,24,12,6,16,0 4,1,1,1,1,1 2 3 2 7 11 Zombie 1 10 8 3 10 0 0 0 0 10 Undead IMMUNE_MAGIC IMMUNE_MAGIC 3 58 +MT_GZOMBIE Rotting Carcass zombie\zombie zombie\grey Always 128 799 false false 11,24,12,6,16,0 4,1,1,1,1,1 2 4 4 15 25 Zombie 2 25 8 5 15 0 0 0 0 15 Undead IMMUNE_MAGIC IMMUNE_MAGIC,RESIST_FIRE 3 136 +MT_YZOMBIE Black Death zombie\zombie zombie\yellow Always 128 799 false false 11,24,12,6,16,0 4,1,1,1,1,1 3 5 6 25 40 Zombie 3 30 8 6 22 0 0 0 0 20 Undead IMMUNE_MAGIC IMMUNE_MAGIC,RESIST_LIGHTNING 3 240 +MT_RFALLSP Fallen One falspear\phall falspear\fallent Always 128 543 true true 11,11,13,11,18,13 3,1,1,1,1,1 1 2 1 1 4 Fallen 0 15 7 1 3 0 5 0 0 0 Animal 3 46 +MT_DFALLSP Carver falspear\phall falspear\dark Always 128 543 true true 11,11,13,11,18,13 3,1,1,1,1,1 2 3 3 4 8 Fallen 2 20 7 2 5 0 5 0 0 5 Animal 3 80 +MT_YFALLSP Devil Kin falspear\phall Always 128 543 true true 11,11,13,11,18,13 3,1,1,1,1,1 2 4 5 12 24 Fallen 2 25 7 3 7 0 5 0 0 10 Animal RESIST_FIRE 3 155 +MT_BFALLSP Dark One falspear\phall falspear\blue Always 128 543 true true 11,11,13,11,18,13 3,1,1,1,1,1 3 5 7 20 36 Fallen 3 30 7 4 8 0 5 0 0 15 Animal RESIST_LIGHTNING 3 255 +MT_WSKELAX Skeleton skelaxe\sklax skelaxe\white Always 128 553 true false 12,8,13,6,17,16 5,1,1,1,1,1 1 2 1 2 4 SkeletonMelee 0 20 8 1 4 0 0 0 0 0 Undead IMMUNE_MAGIC IMMUNE_MAGIC 3 64 +MT_TSKELAX Corpse Axe skelaxe\sklax skelaxe\skelt Always 128 553 true false 12,8,13,6,17,16 4,1,1,1,1,1 2 3 2 4 7 SkeletonMelee 1 25 8 3 5 0 0 0 0 0 Undead IMMUNE_MAGIC IMMUNE_MAGIC 3 68 +MT_RSKELAX Burning Dead skelaxe\sklax Always 128 553 true false 12,8,13,6,17,16 2,1,1,1,1,1 2 4 4 8 12 SkeletonMelee 2 30 8 3 7 0 0 0 0 5 Undead IMMUNE_MAGIC,RESIST_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 3 154 +MT_XSKELAX Horror skelaxe\sklax skelaxe\black Always 128 553 true false 12,8,13,6,17,16 3,1,1,1,1,1 3 5 6 12 20 SkeletonMelee 3 35 8 4 9 0 0 0 0 15 Undead IMMUNE_MAGIC,RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_LIGHTNING 3 264 +MT_RFALLSD Fallen One falsword\fall falsword\fallent Always 128 623 true true 12,12,13,11,14,15 3,1,1,1,1,1 1 2 1 2 5 Fallen 0 15 8 1 4 0 5 0 0 10 Animal 3 52 +MT_DFALLSD Carver falsword\fall falsword\dark Always 128 623 true true 12,12,13,11,14,15 3,1,1,1,1,1 2 3 3 5 9 Fallen 1 20 8 2 7 0 5 0 0 15 Animal 3 90 +MT_YFALLSD Devil Kin falsword\fall Always 128 623 true true 12,12,13,11,14,15 3,1,1,1,1,1 2 4 5 16 24 Fallen 2 25 8 4 10 0 5 0 0 20 Animal RESIST_FIRE 3 180 +MT_BFALLSD Dark One falsword\fall falsword\blue Always 128 623 true true 12,12,13,11,14,15 3,1,1,1,1,1 3 5 7 24 36 Fallen 3 30 8 4 12 0 5 0 0 25 Animal RESIST_LIGHTNING 3 280 +MT_NSCAV Scavenger scav\scav Always 128 410 true false 12,8,12,6,20,11 2,1,1,1,1,1 1 3 2 3 6 Scavenger 0 20 7 1 5 0 0 0 0 10 Animal RESIST_FIRE 3 80 +MT_BSCAV Plague Eater scav\scav scav\scavbr Always 128 410 true false 12,8,12,6,20,11 2,1,1,1,1,1 2 4 4 12 24 Scavenger 1 30 7 1 8 0 0 0 0 20 Animal RESIST_LIGHTNING 3 188 +MT_WSCAV Shadow Beast scav\scav scav\scavbe Always 128 410 true false 12,8,12,6,20,11 2,1,1,1,1,1 3 5 6 24 36 Scavenger 2 35 7 3 12 0 0 0 0 25 Animal RESIST_FIRE 3 375 +MT_YSCAV Bone Gasher scav\scav scav\scavw Always 128 410 true false 12,8,12,6,20,11 2,1,1,1,1,1 4 6 8 28 40 Scavenger 3 35 7 5 15 0 0 0 0 30 Animal RESIST_MAGIC RESIST_LIGHTNING 3 552 +MT_WSKELBW Skeleton skelbow\sklbw skelbow\white Always 128 567 true false 9,8,16,5,16,16 4,1,1,1,1,1 2 3 3 2 4 SkeletonRanged 0 15 12 1 2 0 0 0 0 0 Undead IMMUNE_MAGIC IMMUNE_MAGIC 3 110 +MT_TSKELBW Corpse Bow skelbow\sklbw skelbow\skelt Always 128 567 true false 9,8,16,5,16,16 4,1,1,1,1,1 2 4 5 8 16 SkeletonRanged 1 25 12 1 4 0 0 0 0 0 Undead IMMUNE_MAGIC IMMUNE_MAGIC 3 210 +MT_RSKELBW Burning Dead skelbow\sklbw Always 128 567 true false 9,8,16,5,16,16 2,1,1,1,1,1 3 5 7 10 24 SkeletonRanged 2 30 12 1 6 0 0 0 0 5 Undead IMMUNE_MAGIC,RESIST_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 3 364 +MT_XSKELBW Horror skelbow\sklbw skelbow\black Always 128 567 true false 9,8,16,5,16,16 3,1,1,1,1,1 4 6 9 15 45 SkeletonRanged 3 35 12 2 9 0 0 0 0 15 Undead IMMUNE_MAGIC,RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_LIGHTNING 3 594 +MT_WSKELSD Skeleton Captain skelsd\sklsr skelsd\white Always 128 575 true true 13,8,12,7,15,16 4,1,1,1,1,1 1 3 2 3 6 SkeletonMelee 0 20 8 2 7 0 0 0 0 10 Undead IMMUNE_MAGIC IMMUNE_MAGIC 3 90 +MT_TSKELSD Corpse Captain skelsd\sklsr skelsd\skelt Always 128 575 true false 13,8,12,7,15,16 4,1,1,1,1,1 2 4 4 12 20 SkeletonMelee 1 30 8 3 9 0 0 0 0 5 Undead IMMUNE_MAGIC IMMUNE_MAGIC 3 200 +MT_RSKELSD Burning Dead Captain skelsd\sklsr Always 128 575 true false 13,8,12,7,15,16 4,1,1,1,1,1 3 5 6 16 30 SkeletonMelee 2 35 8 4 10 0 0 0 0 15 Undead IMMUNE_MAGIC,RESIST_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 3 393 +MT_XSKELSD Horror Captain skelsd\sklsr skelsd\black Always 128 575 true false 13,8,12,7,15,16 4,1,1,1,1,1 4 6 8 35 50 SkeletonMelee SEARCH 3 40 8 5 14 0 0 0 0 30 Undead IMMUNE_MAGIC,RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_LIGHTNING 3 604 +MT_INVILORD Invisible Lord tsneak\tsneak Never 128 800 false false 13,13,15,11,16,0 2,1,1,1,1,1 19 20 14 278 278 SkeletonMelee SEARCH,CAN_OPEN_DOOR 3 65 8 16 30 0 0 0 0 60 Demon RESIST_MAGIC,RESIST_FIRE,RESIST_LIGHTNING RESIST_MAGIC,RESIST_FIRE,RESIST_LIGHTNING 3 2000 +MT_SNEAK Hidden sneak\sneak Retail 128 992 true false 16,8,12,8,24,15 2,1,1,1,1,1 2 5 5 8 24 Sneak HIDDEN 0 35 8 3 6 0 0 0 0 25 Demon 3 278 +MT_STALKER Stalker sneak\sneak sneak\sneakv2 Retail 128 992 true false 16,8,12,8,24,15 2,1,1,1,1,1 5 7 9 30 45 Sneak HIDDEN,SEARCH 1 40 8 8 16 0 0 0 0 30 Demon 3 630 +MT_UNSEEN Unseen sneak\sneak sneak\sneakv3 Retail 128 992 true false 16,8,12,8,24,15 2,1,1,1,1,1 6 8 11 35 50 Sneak HIDDEN,SEARCH 2 45 8 12 20 0 0 0 0 30 Demon RESIST_MAGIC IMMUNE_MAGIC 3 935 +MT_ILLWEAV Illusion Weaver sneak\sneak sneak\sneakv1 Retail 128 992 true false 16,8,12,8,24,15 2,1,1,1,1,1 8 10 13 40 60 Sneak HIDDEN,SEARCH 3 60 8 16 24 0 0 0 0 30 Demon RESIST_MAGIC,RESIST_FIRE IMMUNE_MAGIC,RESIST_FIRE 3 1500 +MT_LRDSAYTR Satyr Lord goatlord\goatl newsfx\satyr Retail 160 800 false false 13,13,14,9,16,0 2,1,1,1,1,1 21 22 28 160 200 SkeletonMelee SEARCH 3 90 8 20 30 0 0 0 0 70 Animal RESIST_FIRE,RESIST_LIGHTNING RESIST_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 3 2800 +MT_NGOATMC Flesh Clan goatmace\goat Retail 128 1030 true false 12,8,12,6,20,12 2,1,1,1,1,1 4 6 8 30 45 GoatMelee SEARCH,CAN_OPEN_DOOR 0 50 8 4 10 0 0 0 0 40 Demon 3 460 +MT_BGOATMC Stone Clan goatmace\goat goatmace\beige Retail 128 1030 true false 12,8,12,6,20,12 2,1,1,1,1,1 5 7 10 40 55 GoatMelee SEARCH,CAN_OPEN_DOOR 1 60 8 6 12 0 0 0 0 40 Demon RESIST_MAGIC IMMUNE_MAGIC 3 685 +MT_RGOATMC Fire Clan goatmace\goat goatmace\red Retail 128 1030 true false 12,8,12,6,20,12 2,1,1,1,1,1 6 8 12 50 65 GoatMelee SEARCH,CAN_OPEN_DOOR 2 70 8 8 16 0 0 0 0 45 Demon RESIST_FIRE IMMUNE_FIRE 3 906 +MT_GGOATMC Night Clan goatmace\goat goatmace\gray Retail 128 1030 true false 12,8,12,6,20,12 2,1,1,1,1,1 7 9 14 55 70 GoatMelee SEARCH,CAN_OPEN_DOOR 3 80 8 10 20 15 0 30 30 50 Demon RESIST_MAGIC IMMUNE_MAGIC 3 1190 +MT_FIEND Fiend bat\bat bat\red Always 96 364 false false 9,13,10,9,13,0 1,1,1,1,1,1 2 3 3 3 6 Bat 0 35 5 1 6 0 0 0 0 0 Animal 6 None 102 +MT_BLINK Blink bat\bat Always 96 364 false false 9,13,10,9,13,0 1,1,1,1,1,1 3 5 7 12 28 Bat 1 45 5 1 8 0 0 0 0 15 Animal 6 None 340 +MT_GLOOM Gloom bat\bat bat\grey Always 96 364 false false 9,13,10,9,13,0 1,1,1,1,1,1 4 6 9 28 36 Bat SEARCH 2 70 5 4 12 0 0 0 0 35 Animal RESIST_MAGIC RESIST_MAGIC 6 None 509 +MT_FAMILIAR Familiar bat\bat bat\orange Always 96 364 false false 9,13,10,9,13,0 1,1,1,1,1,1 6 8 13 20 35 Bat SEARCH 3 50 5 4 16 0 0 0 0 35 Demon RESIST_MAGIC,IMMUNE_LIGHTNING RESIST_MAGIC,IMMUNE_LIGHTNING 6 None 448 +MT_NGOATBW Flesh Clan goatbow\goatb Retail 128 1040 false false 12,8,16,6,20,0 3,1,1,1,1,1 4 6 8 20 35 GoatRanged CAN_OPEN_DOOR 0 35 13 1 7 0 0 0 0 35 Demon 3 448 +MT_BGOATBW Stone Clan goatbow\goatb goatbow\beige Retail 128 1040 false false 12,8,16,6,20,0 3,1,1,1,1,1 5 7 10 30 40 GoatRanged CAN_OPEN_DOOR 1 40 13 2 9 0 0 0 0 35 Demon RESIST_MAGIC IMMUNE_MAGIC 3 645 +MT_RGOATBW Fire Clan goatbow\goatb goatbow\red Retail 128 1040 false false 12,8,16,6,20,0 3,1,1,1,1,1 6 8 12 40 50 GoatRanged SEARCH,CAN_OPEN_DOOR 2 45 13 3 11 0 0 0 0 35 Demon RESIST_FIRE IMMUNE_FIRE 3 822 +MT_GGOATBW Night Clan goatbow\goatb goatbow\gray Retail 128 1040 false false 12,8,16,6,20,0 3,1,1,1,1,1 7 9 14 50 65 GoatRanged SEARCH,CAN_OPEN_DOOR 3 50 13 4 13 15 0 0 0 40 Demon RESIST_MAGIC IMMUNE_MAGIC 3 1092 +MT_NACID Acid Beast acid\acid Retail 128 716 true true 13,8,12,8,16,12 1,1,1,1,1,1 6 8 11 40 66 Acid 0 40 8 4 12 25 8 0 0 30 Animal IMMUNE_ACID IMMUNE_MAGIC,IMMUNE_ACID 3 846 +MT_RACID Poison Spitter acid\acid acid\acidblk Retail 128 716 true true 13,8,12,8,16,12 1,1,1,1,1,1 8 10 15 60 85 Acid 1 45 8 4 16 25 8 0 0 30 Animal IMMUNE_ACID IMMUNE_MAGIC,IMMUNE_ACID 3 1248 +MT_BACID Pit Beast acid\acid acid\acidb Retail 128 716 true true 13,8,12,8,16,12 1,1,1,1,1,1 10 12 21 80 110 Acid 2 55 8 8 18 35 8 0 0 35 Animal RESIST_MAGIC,IMMUNE_ACID IMMUNE_MAGIC,RESIST_LIGHTNING,IMMUNE_ACID 3 2060 +MT_XACID Lava Maw acid\acid acid\acidr Retail 128 716 true true 13,8,12,8,16,12 1,1,1,1,1,1 12 14 25 100 150 Acid 3 65 8 10 20 40 8 0 0 35 Animal RESIST_MAGIC,IMMUNE_FIRE,IMMUNE_ACID IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_ACID 3 2940 +MT_SKING Skeleton King sking\sking skelaxe\white Never 160 1010 true true 8,6,16,6,16,6 2,1,1,1,1,2 4 4 9 140 140 SkeletonKing SEARCH,CAN_OPEN_DOOR 3 60 8 6 16 0 0 0 0 70 Undead IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 7 Uniq(SKCROWN) 570 +MT_CLEAVER The Butcher fatc\fatc Never 128 980 false false 10,8,12,6,16,0 1,1,1,1,1,1 1 1 1 320 320 Butcher 3 50 8 6 12 0 0 0 0 50 Demon RESIST_FIRE,RESIST_LIGHTNING RESIST_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 3 Uniq(CLEAVER) 710 +MT_FAT Overlord fat\fat Retail 128 1130 true false 8,10,15,6,16,10 4,1,1,1,1,1 5 7 10 60 80 Fat 0 55 8 6 12 0 0 0 0 55 Demon RESIST_FIRE 3 635 +MT_MUDMAN Mud Man fat\fat fat\blue Retail 128 1130 true false 8,10,15,6,16,10 4,1,1,1,1,1 7 9 14 100 125 Fat SEARCH 1 60 8 8 16 0 0 0 0 60 Demon IMMUNE_LIGHTNING 3 1165 +MT_TOAD Toad Demon fat\fat fat\fatb Retail 128 1130 true false 8,10,15,6,16,10 4,1,1,1,1,1 8 10 16 135 160 Fat SEARCH 2 70 8 8 16 40 0 8 20 65 Demon IMMUNE_MAGIC IMMUNE_MAGIC,RESIST_LIGHTNING 3 1380 +MT_FLAYED Flayed One fat\fat fat\fatf Retail 128 1130 true false 8,10,15,6,16,10 4,1,1,1,1,1 10 12 20 160 200 Fat SEARCH 3 85 8 10 20 0 0 0 0 70 Demon RESIST_MAGIC,IMMUNE_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 3 2058 +MT_WYRM Wyrm worm\worm Never 160 2420 false false 13,13,13,11,19,0 1,1,1,1,1,1 5 7 11 60 90 SkeletonMelee 0 40 8 4 10 0 0 0 0 25 Animal RESIST_MAGIC RESIST_MAGIC 3 660 +MT_CAVSLUG Cave Slug worm\worm Never 160 2420 false false 13,13,13,11,19,0 1,1,1,1,1,1 6 8 13 75 110 SkeletonMelee 1 50 8 6 13 0 0 0 0 30 Animal RESIST_MAGIC RESIST_MAGIC 3 994 +MT_DVLWYRM Devil Wyrm worm\worm Never 160 2420 false false 13,13,13,11,19,0 1,1,1,1,1,1 7 9 15 100 140 SkeletonMelee 2 55 8 8 16 0 0 0 0 30 Animal RESIST_MAGIC,RESIST_FIRE RESIST_MAGIC,RESIST_FIRE 3 1320 +MT_DEVOUR Devourer worm\worm Never 160 2420 false false 13,13,13,11,19,0 1,1,1,1,1,1 8 10 17 125 200 SkeletonMelee 3 60 8 10 20 0 0 0 0 35 Animal RESIST_MAGIC,RESIST_FIRE RESIST_MAGIC,RESIST_FIRE 3 1827 +MT_NMAGMA Magma Demon magma\magma Retail 128 1680 true true 8,10,14,7,18,18 2,1,1,1,1,1 8 9 13 50 70 Magma SEARCH,CAN_OPEN_DOOR 0 45 4 2 10 50 13 0 0 45 Demon IMMUNE_MAGIC,RESIST_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 7 1076 +MT_YMAGMA Blood Stone magma\magma magma\yellow Retail 128 1680 true true 8,10,14,7,18,18 2,1,1,1,1,1 8 10 14 55 75 Magma SEARCH,CAN_OPEN_DOOR 1 50 4 2 12 50 14 0 0 45 Demon IMMUNE_MAGIC,IMMUNE_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 7 1309 +MT_BMAGMA Hell Stone magma\magma magma\blue Retail 128 1680 true true 8,10,14,7,18,18 2,1,1,1,1,1 9 11 16 60 80 Magma SEARCH,CAN_OPEN_DOOR 2 60 4 2 20 60 14 0 0 50 Demon IMMUNE_MAGIC,IMMUNE_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 7 1680 +MT_WMAGMA Lava Lord magma\magma magma\wierd Retail 128 1680 true true 8,10,14,7,18,18 2,1,1,1,1,1 9 11 18 70 85 Magma SEARCH,CAN_OPEN_DOOR 3 75 4 4 24 60 14 0 0 60 Demon IMMUNE_MAGIC,IMMUNE_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 7 2124 +MT_HORNED Horned Demon rhino\rhino Retail 160 1630 true true 8,8,14,6,16,6 2,1,1,1,1,1 7 9 13 40 80 Rhino SEARCH,CAN_OPEN_DOOR 0 60 7 2 16 100 0 5 32 40 Animal RESIST_FIRE 7 1172 +MT_MUDRUN Mud Runner rhino\rhino rhino\orange Retail 160 1630 true true 8,8,14,6,16,6 2,1,1,1,1,1 8 10 15 50 90 Rhino SEARCH,CAN_OPEN_DOOR 1 70 7 6 18 100 0 12 36 45 Animal RESIST_FIRE 7 1404 +MT_FROSTC Frost Charger rhino\rhino rhino\blue Retail 160 1630 true true 8,8,14,6,16,6 2,1,1,1,1,1 9 11 17 60 100 Rhino SEARCH,CAN_OPEN_DOOR 2 80 7 8 20 100 0 20 40 50 Animal IMMUNE_MAGIC,RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_LIGHTNING 7 1720 +MT_OBLORD Obsidian Lord rhino\rhino rhino\rhinob Retail 160 1630 true true 8,8,14,6,16,6 2,1,1,1,1,1 10 12 19 70 110 Rhino SEARCH,CAN_OPEN_DOOR 3 90 7 10 22 100 0 20 50 55 Animal IMMUNE_MAGIC,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 7 1809 +MT_BONEDMN oldboned demskel\demskl Never 128 1740 true true 10,8,20,6,24,16 3,1,1,1,1,1 24 24 12 70 70 Storm 0 60 8 6 14 12 0 0 0 50 Demon IMMUNE_MAGIC IMMUNE_MAGIC 7 1344 +MT_REDDTH Red Death thin\thin thin\thinv3 Never 160 1740 true true 8,8,18,4,17,14 3,1,1,1,1,1 8 10 16 96 96 Storm 1 75 5 10 20 0 0 0 0 60 Demon IMMUNE_MAGIC,IMMUNE_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 7 2168 +MT_LTCHDMN Litch Demon thin\thin thin\thinv3 Never 160 1740 true true 8,8,18,4,17,14 3,1,1,1,1,1 9 11 18 110 110 Storm 2 80 5 10 24 0 0 0 0 45 Demon IMMUNE_MAGIC,IMMUNE_LIGHTNING IMMUNE_MAGIC,IMMUNE_LIGHTNING 7 2736 +MT_UDEDBLRG Undead Balrog thin\thin thin\thinv3 Never 160 1740 true true 8,8,18,4,17,14 3,1,1,1,1,1 11 13 22 130 130 Storm 3 85 5 12 30 0 0 0 0 65 Demon IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING 7 3575 +MT_INCIN Incinerator fireman\firem Never 128 1460 true false 14,19,20,8,14,23 1,1,1,1,1,1 21 22 16 30 45 FireMan 0 75 8 8 16 0 0 0 0 25 Demon IMMUNE_MAGIC,IMMUNE_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 3 1888 +MT_FLAMLRD Flame Lord fireman\firem Never 128 1460 true false 14,19,20,8,14,23 1,1,1,1,1,1 22 23 18 40 55 FireMan 1 75 8 10 20 0 0 0 0 25 Demon IMMUNE_MAGIC,IMMUNE_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 3 2250 +MT_DOOMFIRE Doom Fire fireman\firem Never 128 1460 true false 14,19,20,8,14,23 1,1,1,1,1,1 23 24 20 50 65 FireMan 2 80 8 12 24 0 0 0 0 30 Demon IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 3 2740 +MT_HELLBURN Hell Burner fireman\firem Never 128 1460 true false 14,19,20,8,14,23 1,1,1,1,1,1 24 24 22 60 80 FireMan 3 85 8 15 30 0 0 0 0 30 Demon IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 3 3355 +MT_STORM Red Storm thin\thin thin\thinv3 Retail 160 1740 true true 8,8,18,4,17,14 3,1,1,1,1,1 9 11 18 55 110 Storm SEARCH,CAN_OPEN_DOOR 0 80 5 8 18 75 8 4 16 30 Demon IMMUNE_MAGIC,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_LIGHTNING 7 2160 +MT_RSTORM Storm Rider thin\thin Retail 160 1740 true true 8,8,18,4,17,14 3,1,1,1,1,1 10 12 20 60 120 Storm SEARCH,CAN_OPEN_DOOR 1 80 5 8 18 80 8 4 16 30 Demon RESIST_MAGIC,IMMUNE_LIGHTNING IMMUNE_MAGIC,IMMUNE_LIGHTNING 7 2391 +MT_STORML Storm Lord thin\thin thin\thinv2 Retail 160 1740 true true 8,8,18,4,17,14 3,1,1,1,1,1 11 13 22 75 135 Storm SEARCH,CAN_OPEN_DOOR 2 85 5 12 24 75 8 4 16 35 Demon RESIST_MAGIC,IMMUNE_LIGHTNING IMMUNE_MAGIC,IMMUNE_LIGHTNING 7 2775 +MT_MAEL Maelstrom thin\thin thin\thinv1 Retail 160 1740 true true 8,8,18,4,17,14 3,1,1,1,1,1 12 14 24 90 150 Storm SEARCH,CAN_OPEN_DOOR 3 90 5 12 28 75 8 4 16 40 Demon RESIST_MAGIC,IMMUNE_LIGHTNING IMMUNE_MAGIC,IMMUNE_LIGHTNING 7 3177 +MT_BIGFALL Devil Kin Brute bigfall\fallg newsfx\kbrute Retail 128 800 true false 10,8,11,8,17,0 1,1,1,1,2,2 21 22 27 120 160 SkeletonMelee SEARCH,CAN_OPEN_DOOR 3 100 6 18 24 0 0 0 0 70 Animal RESIST_FIRE,RESIST_LIGHTNING RESIST_MAGIC,RESIST_FIRE,RESIST_LIGHTNING 3 2400 +MT_WINGED Winged-Demon gargoyle\gargo Retail 160 1650 true false 14,14,14,10,18,14 1,1,1,1,1,2 5 7 9 45 60 Gargoyle CAN_OPEN_DOOR 0 50 7 10 16 0 0 0 0 45 Demon IMMUNE_MAGIC,RESIST_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 6 662 +MT_GARGOYLE Gargoyle gargoyle\gargo gargoyle\gare Retail 160 1650 true false 14,14,14,10,18,14 1,1,1,1,1,2 7 9 13 60 90 Gargoyle CAN_OPEN_DOOR 1 65 7 10 16 0 0 0 0 45 Demon IMMUNE_MAGIC,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_LIGHTNING 6 1205 +MT_BLOODCLW Blood Claw gargoyle\gargo gargoyle\gargbr Retail 160 1650 true false 14,14,14,10,18,14 1,1,1,1,1,1 9 11 19 75 125 Gargoyle CAN_OPEN_DOOR 2 80 7 14 22 0 0 0 0 50 Demon IMMUNE_MAGIC,IMMUNE_FIRE IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 6 1873 +MT_DEATHW Death Wing gargoyle\gargo gargoyle\gargb Retail 160 1650 true false 14,14,14,10,18,14 1,1,1,1,1,1 10 12 23 90 150 Gargoyle CAN_OPEN_DOOR 3 95 7 16 28 0 0 0 0 60 Demon IMMUNE_MAGIC,IMMUNE_LIGHTNING IMMUNE_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING 6 2278 +MT_MEGA Slayer mega\mega Retail 160 2220 true true 6,7,14,1,24,5 3,1,1,1,2,1 10 12 20 120 140 Mega SEARCH,CAN_OPEN_DOOR 0 100 8 12 20 0 3 0 0 60 Demon RESIST_MAGIC,IMMUNE_FIRE RESIST_MAGIC,IMMUNE_FIRE 7 2300 +MT_GUARD Guardian mega\mega mega\guard Retail 160 2220 true true 6,7,14,1,24,5 3,1,1,1,2,1 11 13 22 140 160 Mega SEARCH,CAN_OPEN_DOOR 1 110 8 14 22 0 3 0 0 65 Demon RESIST_MAGIC,IMMUNE_FIRE RESIST_MAGIC,IMMUNE_FIRE 7 2714 +MT_VTEXLRD Vortex Lord mega\mega mega\vtexl Retail 160 2220 true true 6,7,14,1,24,5 3,1,1,1,2,1 12 14 24 160 180 Mega SEARCH,CAN_OPEN_DOOR 2 120 8 18 24 0 3 0 0 70 Demon RESIST_MAGIC,IMMUNE_FIRE RESIST_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 7 3252 +MT_BALROG Balrog mega\mega mega\balr Retail 160 2220 true true 6,7,14,1,24,5 3,1,1,1,2,1 13 15 26 180 200 Mega SEARCH,CAN_OPEN_DOOR 3 130 8 22 30 0 3 0 0 75 Demon RESIST_MAGIC,IMMUNE_FIRE RESIST_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 7 3643 +MT_NSNAKE Cave Viper snake\snake Retail 160 1270 false false 12,11,13,5,18,0 2,1,1,1,1,1 11 13 21 100 150 Snake SEARCH 0 90 8 8 20 0 0 0 0 60 Demon IMMUNE_MAGIC IMMUNE_MAGIC 7 2725 +MT_RSNAKE Fire Drake snake\snake snake\snakr Retail 160 1270 false false 12,11,13,5,18,0 2,1,1,1,1,1 12 14 23 120 170 Snake SEARCH 1 105 8 12 24 0 0 0 0 65 Demon IMMUNE_MAGIC,RESIST_FIRE IMMUNE_MAGIC,IMMUNE_FIRE 7 3139 +MT_BSNAKE Gold Viper snake\snake snake\snakg Retail 160 1270 false false 12,11,13,5,18,0 2,1,1,1,1,1 13 14 25 140 180 Snake SEARCH 2 120 8 15 26 0 0 0 0 70 Demon IMMUNE_MAGIC,RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_LIGHTNING 7 3540 +MT_GSNAKE Azure Drake snake\snake snake\snakb Retail 160 1270 false false 12,11,13,5,18,0 2,1,1,1,1,1 15 16 27 160 200 Snake SEARCH 3 130 8 18 30 0 0 0 0 75 Demon RESIST_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING 7 3791 +MT_NBLACK Black Knight black\black Retail 160 2120 false false 8,8,16,4,24,0 2,1,1,1,1,1 12 14 24 150 150 SkeletonMelee SEARCH 0 110 8 15 20 0 0 0 0 75 Demon RESIST_MAGIC,RESIST_LIGHTNING RESIST_MAGIC,IMMUNE_LIGHTNING 7 3360 +MT_RTBLACK Doom Guard black\black black\blkkntrt Retail 160 2120 false false 8,8,16,4,24,0 2,1,1,1,1,1 13 15 26 165 165 SkeletonMelee SEARCH 0 130 8 18 25 0 0 0 0 75 Demon RESIST_MAGIC,RESIST_FIRE RESIST_MAGIC,IMMUNE_FIRE 7 3650 +MT_BTBLACK Steel Lord black\black black\blkkntbt Retail 160 2120 false false 8,8,16,4,24,0 2,1,1,1,1,1 14 16 28 180 180 SkeletonMelee SEARCH 1 120 8 20 30 0 0 0 0 80 Demon RESIST_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 7 4252 +MT_RBLACK Blood Knight black\black black\blkkntbe Retail 160 2120 false false 8,8,16,4,24,0 2,1,1,1,1,1 13 14 30 200 200 SkeletonMelee SEARCH 1 130 8 25 35 0 0 0 0 85 Demon IMMUNE_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING IMMUNE_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING 7 5130 +MT_UNRAV The Shredded unrav\unrav newsfx\shred Retail 96 484 false false 10,10,12,5,16,0 1,1,1,1,1,1 17 18 23 70 90 SkeletonMelee 0 75 7 4 12 0 0 0 0 65 Undead RESIST_FIRE,RESIST_LIGHTNING RESIST_FIRE,RESIST_LIGHTNING 3 900 +MT_HOLOWONE Hollow One unrav\unrav acid\acid Never 96 484 false false 10,10,12,5,16,0 1,1,1,1,1,1 18 19 27 135 240 SkeletonMelee 1 75 7 12 24 0 0 0 0 75 Undead IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 3 4374 +MT_PAINMSTR Pain Master unrav\unrav acid\acid Never 96 484 false false 10,10,12,5,16,0 1,1,1,1,1,1 19 20 29 110 200 SkeletonMelee 2 80 7 16 30 0 0 0 0 80 Undead IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 3 5147 +MT_REALWEAV Reality Weaver unrav\unrav acid\acid Never 96 484 false false 10,10,12,5,16,0 1,1,1,1,1,1 20 20 30 135 240 SkeletonMelee 3 85 7 20 35 0 0 0 0 85 Undead RESIST_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING RESIST_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 3 5925 +MT_SUCCUBUS Succubus succ\scbs Retail 128 980 false false 14,8,16,7,24,0 1,1,1,1,1,1 12 14 24 120 150 Succubus CAN_OPEN_DOOR 0 100 10 1 20 0 0 0 0 60 Demon RESIST_MAGIC IMMUNE_MAGIC,RESIST_FIRE 3 3696 +MT_SNOWWICH Snow Witch succ\scbs succ\succb Retail 128 980 false false 14,8,16,7,24,0 1,1,1,1,1,1 13 15 26 135 175 Succubus CAN_OPEN_DOOR 1 110 10 1 24 0 0 0 0 65 Demon RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_LIGHTNING 3 4084 +MT_HLSPWN Hell Spawn succ\scbs succ\succrw Retail 128 980 false false 14,8,16,7,24,0 1,1,1,1,1,1 14 16 28 150 200 Succubus SEARCH,CAN_OPEN_DOOR 2 115 10 1 30 0 0 0 0 75 Demon RESIST_MAGIC,IMMUNE_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 3 4480 +MT_SOLBRNR Soul Burner succ\scbs succ\succbw Retail 128 980 false false 14,8,16,7,24,0 1,1,1,1,1,1 15 16 30 140 225 Succubus SEARCH,CAN_OPEN_DOOR 3 120 10 1 35 0 0 0 0 85 Demon RESIST_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 3 4644 +MT_COUNSLR Counselor mage\mage Retail 128 2000 true false 12,1,20,8,28,20 1,1,1,1,1,1 13 14 25 70 70 Counselor CAN_OPEN_DOOR 0 90 8 8 20 0 0 0 0 0 Demon RESIST_MAGIC,RESIST_FIRE,RESIST_LIGHTNING RESIST_MAGIC,RESIST_FIRE,RESIST_LIGHTNING 7 4070 +MT_MAGISTR Magistrate mage\mage mage\cnselg Retail 128 2000 true false 12,1,20,8,28,20 1,1,1,1,1,1 14 15 27 85 85 Counselor CAN_OPEN_DOOR 1 100 8 10 24 0 0 0 0 0 Demon RESIST_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 7 4478 +MT_CABALIST Cabalist mage\mage mage\cnselgd Retail 128 2000 true false 12,1,20,8,28,20 1,1,1,1,1,1 15 16 29 120 120 Counselor CAN_OPEN_DOOR 2 110 8 14 30 0 0 0 0 0 Demon RESIST_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING IMMUNE_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING 7 4929 +MT_ADVOCATE Advocate mage\mage mage\cnselbk Retail 128 2000 true false 12,1,20,8,28,20 1,1,1,1,1,1 16 16 30 145 145 Counselor CAN_OPEN_DOOR 3 120 8 15 25 0 0 0 0 0 Demon IMMUNE_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 7 4968 +MT_GOLEM Golem golem\golem golem\golm Never 96 386 true false 0,16,12,0,12,20 1,1,1,1,1,1 1 1 12 1 1 Golem CAN_OPEN_DOOR 0 0 7 1 1 0 0 0 0 1 Demon 0 0 +MT_DIABLO The Dark Lord diablo\diablo Never 160 2000 true true 16,6,16,6,16,16 1,1,1,1,1,1 26 26 45 3333 3333 Diablo KNOCKBACK,SEARCH,CAN_OPEN_DOOR 3 220 4 30 60 0 11 0 0 90 Demon IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING 7 31666 +MT_DARKMAGE The Arch-Litch Malignus darkmage\dmage darkmage\dmag Never 128 1060 true false 6,1,21,6,23,18 1,1,1,1,1,1 21 21 30 160 160 Counselor CAN_OPEN_DOOR 3 120 8 20 40 0 0 0 0 70 Demon RESIST_MAGIC,RESIST_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 7 4968 +MT_HELLBOAR Hellboar fork\fork newsfx\hboar Retail 188 800 false false 10,10,15,6,16,0 2,1,1,1,1,1 17 18 23 80 100 SkeletonMelee KNOCKBACK,SEARCH 2 70 7 16 24 0 0 0 0 60 Demon RESIST_FIRE,RESIST_LIGHTNING 3 750 +MT_STINGER Stinger scorp\scorp newsfx\stingr Retail 64 305 false false 10,10,12,6,15,0 2,1,1,1,1,1 17 18 22 30 40 SkeletonMelee 3 85 8 1 20 0 0 0 0 50 Animal RESIST_LIGHTNING 1 500 +MT_PSYCHORB Psychorb eye\eye newsfx\psyco Retail 156 800 false false 12,13,13,7,21,0 2,1,1,1,1,1 17 18 22 20 30 Psychorb 3 80 8 10 10 0 0 0 0 40 Animal RESIST_FIRE 6 450 +MT_ARACHNON Arachnon spider\spider newsfx\slord Retail 148 800 false false 12,10,15,6,20,0 2,1,1,1,1,1 17 18 22 60 80 SkeletonMelee SEARCH 3 50 8 5 15 0 0 0 0 50 Animal RESIST_LIGHTNING 7 500 +MT_FELLTWIN Felltwin tsneak\tsneak newsfx\ftwin Retail 128 800 false false 13,13,15,11,16,0 2,1,1,1,1,1 17 18 22 50 70 SkeletonMelee SEARCH,CAN_OPEN_DOOR 3 70 8 10 18 0 0 0 0 50 Demon RESIST_FIRE,RESIST_LIGHTNING 3 600 +MT_HORKSPWN Hork Spawn spawn\spawn newsfx\hspawn Retail 164 520 false true 15,12,14,11,14,0 1,1,1,1,1,1 18 19 22 30 30 SkeletonMelee 3 60 8 10 25 0 0 0 0 25 Demon RESIST_MAGIC RESIST_MAGIC 3 250 +MT_VENMTAIL Venomtail wscorp\wscorp newsfx\stingr Retail 86 305 false false 10,10,12,6,15,0 2,1,1,1,1,1 19 20 24 40 50 SkeletonMelee 3 85 8 1 30 0 0 0 0 60 Animal RESIST_LIGHTNING IMMUNE_LIGHTNING 1 1000 +MT_NECRMORB Necromorb eye2\eye2 newsfx\psyco Retail 140 800 false false 12,13,13,7,21,0 2,1,1,1,1,1 19 20 24 30 40 Necromorb 3 80 8 20 20 0 0 0 0 50 Animal RESIST_FIRE IMMUNE_FIRE,RESIST_LIGHTNING 6 1100 +MT_SPIDLORD Spider Lord bspidr\bspidr newsfx\slord Retail 148 800 true true 12,10,15,6,20,10 2,1,1,1,1,1 19 20 24 80 100 Acid SEARCH 3 60 8 8 20 75 8 10 10 60 Animal RESIST_LIGHTNING RESIST_FIRE,IMMUNE_LIGHTNING 7 1250 +MT_LASHWORM Lashworm clasp\clasp newsfx\lworm Retail 176 800 false false 10,12,15,6,16,0 1,1,1,1,1,1 19 20 20 30 30 SkeletonMelee 3 90 8 12 20 0 0 0 0 50 Animal RESIST_FIRE 3 600 +MT_TORCHANT Torchant antworm\worm newsfx\tchant Retail 192 800 false false 14,12,12,6,20,0 2,1,1,1,1,1 19 20 22 60 80 Torchant 3 75 8 20 30 0 0 0 0 70 Animal IMMUNE_FIRE RESIST_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 7 1250 +MT_HORKDMN Hork Demon horkd\horkd newsfx\hdemon Never 138 800 true true 15,8,16,6,16,9 2,1,1,1,1,2 19 19 27 120 160 SkeletonMelee 3 60 8 20 35 80 8 0 0 80 Demon RESIST_LIGHTNING RESIST_MAGIC,IMMUNE_LIGHTNING 7 2000 +MT_DEFILER Hell Bug hellbug\hellbg newsfx\defile Never 198 800 true true 8,8,14,6,14,12 1,1,1,1,1,1 20 20 30 240 240 SkeletonMelee SEARCH 3 110 8 20 30 90 8 50 60 80 Demon RESIST_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING RESIST_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 7 5000 +MT_GRAVEDIG Gravedigger gravdg\gravdg newsfx\gdiggr Retail 124 800 true true 24,24,12,6,16,16 2,1,1,1,1,1 21 21 26 120 240 Scavenger CAN_OPEN_DOOR 3 80 6 2 12 0 0 0 0 20 Undead IMMUNE_LIGHTNING RESIST_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING 3 2000 +MT_TOMBRAT Tomb Rat rat\rat newsfx\tmbrat Retail 104 550 false false 11,8,12,6,20,0 2,1,1,1,1,1 21 22 24 80 120 SkeletonMelee 3 120 8 12 25 0 0 0 0 30 Animal RESIST_FIRE,RESIST_LIGHTNING 3 1800 +MT_FIREBAT Firebat hellbat\helbat newsfx\helbat Retail 96 550 false false 18,16,14,6,18,11 2,1,1,1,1,1 21 22 24 60 80 FireBat 3 100 8 15 20 0 0 0 0 70 Animal IMMUNE_FIRE RESIST_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 7 2400 +MT_SKLWING Skullwing demskel\demskl newsfx\swing skelaxe\skelt Retail 128 1740 true false 10,8,20,6,24,16 3,1,1,1,1,1 21 22 27 70 70 SkeletonMelee 0 75 7 15 20 75 9 15 20 80 Undead RESIST_FIRE,RESIST_LIGHTNING RESIST_FIRE,RESIST_LIGHTNING 7 3000 +MT_LICH Lich lich\lich newsfx\lich Retail 96 800 false true 12,10,10,7,21,0 2,1,1,1,2,1 21 22 25 80 100 Lich 3 100 8 15 20 0 0 0 0 60 Undead RESIST_LIGHTNING RESIST_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING 3 3000 +MT_CRYPTDMN Crypt Demon bubba\bubba newsfx\crypt Retail 154 800 false true 8,18,12,8,21,0 3,1,1,1,1,1 22 23 28 200 240 SkeletonMelee 3 100 8 20 40 0 0 0 0 85 Demon IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING 3 3200 +MT_HELLBAT Hellbat hellbat2\bhelbt newsfx\helbat Retail 96 550 true false 18,16,14,6,18,11 2,1,1,1,1,1 23 24 29 100 140 Torchant 3 110 8 30 30 0 0 0 0 80 Demon RESIST_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING RESIST_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 7 3600 +MT_BONEDEMN Bone Demon demskel\demskl newsfx\swing Retail 128 1740 true true 10,8,20,6,24,16 3,1,1,1,1,1 23 24 30 240 280 BoneDemon 0 100 8 40 50 160 12 50 50 50 Undead IMMUNE_FIRE,IMMUNE_LIGHTNING IMMUNE_FIRE,IMMUNE_LIGHTNING 7 5000 +MT_ARCHLICH Arch Lich lich2\lich2 newsfx\lich Retail 136 800 false true 12,10,10,7,21,0 2,1,1,1,2,1 23 24 30 180 200 ArchLich 3 120 8 30 30 0 0 0 0 75 Undead RESIST_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 3 4000 +MT_BICLOPS Biclops byclps\byclps newsfx\biclop Retail 180 800 false false 10,11,16,6,16,0 2,1,1,1,2,1 23 24 30 200 240 SkeletonMelee KNOCKBACK,CAN_OPEN_DOOR 3 90 8 40 50 0 0 0 0 80 Demon RESIST_LIGHTNING RESIST_FIRE,RESIST_LIGHTNING 3 4000 +MT_FLESTHNG Flesh Thing flesh\flesh newsfx\flesht Retail 164 800 false true 15,24,15,6,16,0 1,1,1,1,1,1 23 24 28 300 400 SkeletonMelee 3 150 8 12 18 0 0 0 0 70 Demon RESIST_MAGIC,RESIST_FIRE,RESIST_LIGHTNING RESIST_MAGIC,RESIST_FIRE,RESIST_LIGHTNING 3 4000 +MT_REAPER Reaper reaper\reap newsfx\reaper Retail 180 800 false false 12,10,14,6,16,0 2,1,1,1,1,1 23 24 30 260 300 SkeletonMelee 3 120 8 30 35 0 0 0 0 90 Demon IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 3 6000 +MT_NAKRUL Na-Krul nkr\nkr newsfx\nakrul Never 226 1200 true true 2,6,16,3,16,16 1,1,1,1,1,1 31 31 40 1332 1332 SkeletonMelee KNOCKBACK,SEARCH,CAN_OPEN_DOOR 3 150 7 40 50 150 10 40 50 125 Demon IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING 7 13333 diff --git a/assets/txtdata/monsters/unique_monstdat.tsv b/assets/txtdata/monsters/unique_monstdat.tsv new file mode 100644 index 00000000000..841b86097d5 --- /dev/null +++ b/assets/txtdata/monsters/unique_monstdat.tsv @@ -0,0 +1,101 @@ +type name trn level maxHp ai intelligence minDamage maxDamage resistance monsterPack customToHit customArmorClass talkMessage +MT_NGOATMC Gharbad the Weak bsdb 4 120 Gharbad 3 8 16 IMMUNE_LIGHTNING None 0 0 TEXT_GARBUD1 +MT_SKING Skeleton King genrl 0 240 SkeletonKing 3 6 16 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Independent 0 0 +MT_COUNSLR Zhar the Mad general 8 360 Zhar 3 16 40 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING None 0 0 TEXT_ZHAR1 +MT_BFALLSP Snotspill bng 4 220 Snotspill 3 10 18 RESIST_LIGHTNING None 0 0 TEXT_BANNER10 +MT_ADVOCATE Arch-Bishop Lazarus general 0 600 Lazarus 3 30 50 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING None 0 0 TEXT_VILE13 +MT_HLSPWN Red Vex redv 0 400 LazarusSuccubus 3 30 50 IMMUNE_MAGIC,RESIST_FIRE None 0 0 TEXT_VILE13 +MT_HLSPWN Black Jade blkjd 0 400 LazarusSuccubus 3 30 50 IMMUNE_MAGIC,RESIST_LIGHTNING None 0 0 TEXT_VILE13 +MT_RBLACK Lachdanan bhka 14 500 Lachdanan 3 0 0 None 0 0 TEXT_VEIL9 +MT_BTBLACK Warlord of Blood general 13 850 Warlord 3 35 50 IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING None 0 0 TEXT_WARLRD9 +MT_CLEAVER The Butcher genrl 0 220 Butcher 3 6 12 RESIST_FIRE,RESIST_LIGHTNING None 0 0 +MT_HORKDMN Hork Demon genrl 19 300 HorkDemon 3 20 35 RESIST_LIGHTNING None 0 0 +MT_DEFILER The Defiler genrl 20 480 SkeletonMelee 3 30 40 RESIST_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING None 0 0 +MT_NAKRUL Na-Krul genrl 0 1332 SkeletonMelee 3 40 50 IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING Leashed 0 0 +MT_TSKELAX Bonehead Keenaxe bhka 2 91 SkeletonMelee 2 4 10 IMMUNE_MAGIC Leashed 100 0 +MT_RFALLSD Bladeskin the Slasher bsts 2 51 Fallen 0 6 18 RESIST_FIRE Leashed 0 45 +MT_NZOMBIE Soulpus general 2 133 Zombie 0 4 8 RESIST_FIRE,RESIST_LIGHTNING None 0 0 +MT_RFALLSP Pukerat the Unclean ptu 2 77 Fallen 3 1 5 RESIST_FIRE None 0 0 +MT_WSKELAX Boneripper br 2 54 Bat 0 6 15 IMMUNE_MAGIC,IMMUNE_FIRE Leashed 0 0 +MT_NZOMBIE Rotfeast the Hungry eth 2 85 SkeletonMelee 3 4 12 IMMUNE_MAGIC Leashed 0 0 +MT_DFALLSD Gutshank the Quick gtq 3 66 Bat 2 6 16 RESIST_FIRE Leashed 0 0 +MT_TSKELSD Brokenhead Bangshield bhbs 3 108 SkeletonMelee 3 12 20 IMMUNE_MAGIC,RESIST_LIGHTNING Leashed 0 0 +MT_YFALLSP Bongo bng 3 178 Fallen 3 9 21 Leashed 0 0 +MT_BZOMBIE Rotcarnage rcrn 3 102 Zombie 3 9 24 IMMUNE_MAGIC,RESIST_LIGHTNING Leashed 0 45 +MT_NSCAV Shadowbite shbt 2 60 SkeletonMelee 3 3 20 IMMUNE_FIRE Leashed 0 0 +MT_WSKELBW Deadeye de 2 49 GoatRanged 0 6 9 IMMUNE_MAGIC,RESIST_FIRE None 0 0 +MT_RSKELAX Madeye the Dead mtd 4 75 Bat 0 9 21 IMMUNE_MAGIC,IMMUNE_FIRE Leashed 0 30 +MT_BSCAV El Chupacabras general 3 120 GoatMelee 0 10 18 RESIST_FIRE Leashed 0 0 +MT_TSKELBW Skullfire skfr 3 125 GoatRanged 1 6 10 IMMUNE_FIRE None 0 0 +MT_SNEAK Warpskull tspo 3 117 Sneak 2 6 18 RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_GZOMBIE Goretongue pmr 3 156 SkeletonMelee 1 15 30 IMMUNE_MAGIC None 0 0 +MT_WSCAV Pulsecrawler bhka 4 150 Scavenger 0 16 20 IMMUNE_FIRE,RESIST_LIGHTNING Leashed 0 45 +MT_BLINK Moonbender general 4 135 Bat 0 9 27 IMMUNE_FIRE Leashed 0 0 +MT_BLINK Wrathraven general 5 135 Bat 2 9 22 IMMUNE_FIRE Leashed 0 0 +MT_YSCAV Spineeater general 4 180 Scavenger 1 18 25 IMMUNE_LIGHTNING Leashed 0 0 +MT_RSKELBW Blackash the Burning bashtb 4 120 GoatRanged 0 6 16 IMMUNE_MAGIC,IMMUNE_FIRE Leashed 0 0 +MT_BFALLSD Shadowcrow general 5 270 Sneak 2 12 25 Leashed 0 0 +MT_LRDSAYTR Blightstone the Weak bhka 4 360 SkeletonMelee 0 4 12 IMMUNE_MAGIC,RESIST_LIGHTNING Leashed 70 0 +MT_FAT Bilefroth the Pit Master bftp 6 210 Bat 1 16 23 IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_NGOATBW Bloodskin Darkbow bsdb 5 207 GoatRanged 0 3 16 RESIST_FIRE,RESIST_LIGHTNING Leashed 0 55 +MT_GLOOM Foulwing db 5 246 Rhino 3 12 28 RESIST_FIRE Leashed 0 0 +MT_XSKELSD Shadowdrinker shdr 5 300 Sneak 1 18 26 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING None 0 45 +MT_UNSEEN Hazeshifter bhka 5 285 Sneak 3 18 30 IMMUNE_LIGHTNING Leashed 0 0 +MT_NACID Deathspit bfds 6 303 AcidUnique 0 12 32 RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_RGOATMC Bloodgutter bgbl 6 315 Bat 1 24 34 IMMUNE_FIRE Leashed 0 0 +MT_BGOATMC Deathshade Fleshmaul dsfm 6 276 Rhino 0 12 24 IMMUNE_MAGIC,RESIST_FIRE None 0 65 +MT_WYRM Warmaggot the Mad general 6 246 Bat 3 15 30 RESIST_LIGHTNING Leashed 0 0 +MT_STORM Glasskull the Jagged bhka 7 354 Storm 0 18 30 IMMUNE_MAGIC,IMMUNE_FIRE Leashed 0 0 +MT_RGOATBW Blightfire blf 7 321 Succubus 2 13 21 IMMUNE_FIRE Leashed 0 0 +MT_GARGOYLE Nightwing the Cold general 7 342 Bat 1 18 26 IMMUNE_MAGIC,RESIST_LIGHTNING Leashed 0 0 +MT_GGOATBW Gorestone general 7 303 GoatRanged 1 15 28 RESIST_LIGHTNING Leashed 70 0 +MT_BMAGMA Bronzefist Firestone general 8 360 Magma 0 30 36 IMMUNE_MAGIC,RESIST_FIRE Leashed 0 0 +MT_INCIN Wrathfire the Doomed wftd 8 270 SkeletonMelee 2 20 30 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_NMAGMA Firewound the Grim bhka 8 303 Magma 0 18 22 IMMUNE_MAGIC,RESIST_FIRE Leashed 0 0 +MT_MUDMAN Baron Sludge bsm 8 315 Sneak 3 25 34 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Leashed 0 75 +MT_GGOATMC Blighthorn Steelmace bhsm 7 250 Rhino 0 20 28 RESIST_LIGHTNING Leashed 0 45 +MT_RACID Chaoshowler general 8 240 AcidUnique 0 12 20 Leashed 0 0 +MT_REDDTH Doomgrin the Rotting general 8 405 Storm 3 25 50 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_FLAMLRD Madburner general 9 270 Storm 0 20 40 IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING Leashed 0 0 +MT_LTCHDMN Bonesaw the Litch general 9 495 Storm 2 30 55 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_MUDRUN Breakspine general 9 351 Rhino 0 25 34 RESIST_FIRE Leashed 0 0 +MT_REDDTH Devilskull Sharpbone general 9 444 Storm 1 25 40 IMMUNE_FIRE Leashed 0 0 +MT_STORM Brokenstorm general 9 411 Storm 2 25 36 IMMUNE_LIGHTNING Leashed 0 0 +MT_RSTORM Stormbane general 9 555 Storm 3 30 30 IMMUNE_LIGHTNING Leashed 0 0 +MT_TOAD Oozedrool general 9 483 Fat 3 25 30 RESIST_LIGHTNING Leashed 0 0 +MT_BLOODCLW Goldblight of the Flame general 10 405 Gargoyle 0 15 35 IMMUNE_MAGIC,IMMUNE_FIRE Leashed 0 80 +MT_OBLORD Blackstorm general 10 525 Rhino 3 20 40 IMMUNE_MAGIC,IMMUNE_LIGHTNING Leashed 0 90 +MT_RACID Plaguewrath general 10 450 AcidUnique 2 20 30 IMMUNE_MAGIC,RESIST_FIRE Leashed 0 0 +MT_RSTORM The Flayer general 10 501 Storm 1 20 35 RESIST_MAGIC,RESIST_FIRE,IMMUNE_LIGHTNING Leashed 0 0 +MT_FROSTC Bluehorn general 11 477 Rhino 1 25 30 IMMUNE_MAGIC,RESIST_FIRE Leashed 0 90 +MT_HELLBURN Warpfire Hellspawn general 11 525 FireMan 3 10 40 RESIST_MAGIC,IMMUNE_FIRE Leashed 0 0 +MT_NSNAKE Fangspeir general 11 444 SkeletonMelee 1 15 32 IMMUNE_FIRE Leashed 0 0 +MT_UDEDBLRG Festerskull general 11 600 Storm 2 15 30 IMMUNE_MAGIC Leashed 0 0 +MT_NBLACK Lionskull the Bent general 12 525 SkeletonMelee 2 25 25 IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING Leashed 0 0 +MT_COUNSLR Blacktongue general 12 360 Counselor 3 15 30 RESIST_FIRE Leashed 0 0 +MT_DEATHW Viletouch general 12 525 Gargoyle 3 20 40 IMMUNE_LIGHTNING Leashed 0 0 +MT_RSNAKE Viperflame general 12 570 SkeletonMelee 1 25 35 IMMUNE_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_BSNAKE Fangskin bhka 14 681 SkeletonMelee 2 15 50 IMMUNE_MAGIC,RESIST_LIGHTNING Leashed 0 0 +MT_SUCCUBUS Witchfire the Unholy general 12 444 Succubus 3 10 20 IMMUNE_MAGIC,IMMUNE_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_BALROG Blackskull bhka 13 750 SkeletonMelee 3 25 40 IMMUNE_MAGIC,RESIST_LIGHTNING Leashed 0 0 +MT_UNRAV Soulslash general 12 450 SkeletonMelee 0 25 25 IMMUNE_MAGIC Leashed 0 0 +MT_VTEXLRD Windspawn general 12 711 SkeletonMelee 1 35 40 IMMUNE_MAGIC,IMMUNE_FIRE Leashed 0 0 +MT_GSNAKE Lord of the Pit general 13 762 SkeletonMelee 2 25 42 RESIST_FIRE Leashed 0 0 +MT_RTBLACK Rustweaver general 13 400 SkeletonMelee 3 1 60 IMMUNE_MAGIC,IMMUNE_FIRE,IMMUNE_LIGHTNING None 0 0 +MT_HOLOWONE Howlingire the Shade general 13 450 SkeletonMelee 2 40 75 RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_MAEL Doomcloud general 13 612 Storm 1 1 60 RESIST_FIRE,IMMUNE_LIGHTNING None 0 0 +MT_PAINMSTR Bloodmoon Soulfire general 13 684 SkeletonMelee 1 15 40 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_SNOWWICH Witchmoon general 13 310 Succubus 3 30 40 RESIST_LIGHTNING None 0 0 +MT_VTEXLRD Gorefeast general 13 771 SkeletonMelee 3 20 55 RESIST_FIRE None 0 0 +MT_RTBLACK Graywar the Slayer general 14 672 SkeletonMelee 1 30 50 RESIST_LIGHTNING None 0 0 +MT_MAGISTR Dreadjudge general 14 540 Counselor 1 30 40 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_HLSPWN Stareye the Witch general 14 726 Succubus 2 30 50 IMMUNE_FIRE None 0 0 +MT_BTBLACK Steelskull the Hunter general 14 831 SkeletonMelee 3 40 50 RESIST_LIGHTNING None 0 0 +MT_RBLACK Sir Gorash general 16 1050 SkeletonMelee 1 20 60 None 0 0 +MT_CABALIST The Vizier general 15 850 Counselor 2 25 40 IMMUNE_FIRE Leashed 0 0 +MT_REALWEAV Zamphir general 15 891 SkeletonMelee 2 30 50 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 +MT_HLSPWN Bloodlust general 15 825 Succubus 1 20 55 IMMUNE_MAGIC,IMMUNE_LIGHTNING None 0 0 +MT_HLSPWN Webwidow general 16 774 Succubus 1 20 50 IMMUNE_MAGIC,IMMUNE_FIRE None 0 0 +MT_SOLBRNR Fleshdancer general 16 999 Succubus 3 30 50 IMMUNE_MAGIC,RESIST_FIRE None 0 0 +MT_OBLORD Grimspike general 19 534 Sneak 1 25 40 IMMUNE_MAGIC,RESIST_FIRE Leashed 0 0 +MT_STORML Doomlock general 28 534 Sneak 1 35 55 IMMUNE_MAGIC,RESIST_FIRE,RESIST_LIGHTNING Leashed 0 0 diff --git a/assets/txtdata/sound/effects.tsv b/assets/txtdata/sound/effects.tsv new file mode 100644 index 00000000000..8a9e5921bbc --- /dev/null +++ b/assets/txtdata/sound/effects.tsv @@ -0,0 +1,924 @@ +id flags path +Walk Misc sfx\misc\walk1.wav +ShootBow Misc sfx\misc\bfire.wav +CastSpell Misc sfx\misc\tmag.wav +Swing Misc sfx\misc\swing.wav +Swing2 Misc sfx\misc\swing2.wav +WarriorDeath Misc sfx\misc\dead.wav +ShootBow2 Misc,Hellfire sfx\misc\sting1.wav +ShootFireballBow Misc,Hellfire sfx\misc\fballbow.wav +QuestDone Stream sfx\misc\questdon.wav +BarrelExpload Misc sfx\items\barlfire.wav +BarrelBreak Misc sfx\items\barrel.wav +PodExpload Misc,Hellfire sfx\items\podpop8.wav +PodPop Misc,Hellfire sfx\items\podpop5.wav +UrnExpload Misc,Hellfire sfx\items\urnpop3.wav +UrnBreak Misc,Hellfire sfx\items\urnpop2.wav +BrutalHit Misc sfx\items\bhit.wav +BrutalHit1 Misc sfx\items\bhit1.wav +ChestOpen Misc sfx\items\chest.wav +DoorClose Misc sfx\items\doorclos.wav +DoorOpen Misc sfx\items\dooropen.wav +ItemAnvilFlip Misc sfx\items\flipanvl.wav +ItemAxeFlip Misc sfx\items\flipaxe.wav +ItemBloodStoneFlip Misc sfx\items\flipblst.wav +ItemBodyPartFlip Misc sfx\items\flipbody.wav +ItemBookFlip Misc sfx\items\flipbook.wav +ItemBowFlip Misc sfx\items\flipbow.wav +ItemCapFlip Misc sfx\items\flipcap.wav +ItemArmorFlip Misc sfx\items\flipharm.wav +ItemLeatherFlip Misc sfx\items\fliplarm.wav +ItemMushroomFlip Misc sfx\items\flipmush.wav +ItemPotionFlip Misc sfx\items\flippot.wav +ItemRingFlip Misc sfx\items\flipring.wav +ItemRockFlip Misc sfx\items\fliprock.wav +ItemScrollFlip Misc sfx\items\flipscrl.wav +ItemShieldFlip Misc sfx\items\flipshld.wav +ItemSignFlip Misc sfx\items\flipsign.wav +ItemStaffFlip Misc sfx\items\flipstaf.wav +ItemSwordFlip Misc sfx\items\flipswor.wav +ItemGold Misc sfx\items\gold.wav +ItemAnvil Misc sfx\items\invanvl.wav +ItemAxe Misc sfx\items\invaxe.wav +ItemBloodStone Misc sfx\items\invblst.wav +ItemBodyPart Misc sfx\items\invbody.wav +ItemBook Misc sfx\items\invbook.wav +ItemBow Misc sfx\items\invbow.wav +ItemCap Misc sfx\items\invcap.wav +GrabItem Misc sfx\items\invgrab.wav +ItemArmor Misc sfx\items\invharm.wav +ItemLeather Misc sfx\items\invlarm.wav +ItemMushroom Misc sfx\items\invmush.wav +ItemPotion Misc sfx\items\invpot.wav +ItemRing Misc sfx\items\invring.wav +ItemRock Misc sfx\items\invrock.wav +ItemScroll Misc sfx\items\invscrol.wav +ItemShield Misc sfx\items\invshiel.wav +ItemSign Misc sfx\items\invsign.wav +ItemStaff Misc sfx\items\invstaf.wav +ItemSword Misc sfx\items\invsword.wav +OperateLever Misc sfx\items\lever.wav +OperateShrine Misc sfx\items\magic.wav +OperateShrine1 Misc sfx\items\magic1.wav +ReadBook Misc sfx\items\readbook.wav +Sarcophagus Misc sfx\items\sarc.wav +MenuMove Ui sfx\items\titlemov.wav +MenuSelect Ui sfx\items\titlslct.wav +TriggerTrap Misc sfx\items\trap.wav +CastFire Misc sfx\misc\cast2.wav +CastLightning Misc sfx\misc\cast4.wav +CastSkill Misc sfx\misc\cast6.wav +SpellEnd Misc sfx\misc\cast7.wav +CastHealing Misc sfx\misc\cast8.wav +SpellRepair Misc sfx\misc\repair.wav +SpellAcid Misc sfx\misc\acids1.wav +SpellAcid1 Misc sfx\misc\acids2.wav +SpellApocalypse Misc sfx\misc\apoc.wav +SpellBloodStar Misc sfx\misc\blodstar.wav +SpellBloodStarHit Misc sfx\misc\blsimpt.wav +SpellBoneSpirit Misc sfx\misc\bonesp.wav +SpellBoneSpiritHit Misc sfx\misc\bsimpct.wav +OperateCaldron Misc sfx\misc\caldron.wav +SpellChargedBolt Misc sfx\misc\cbolt.wav +SpellDoomSerpents Misc sfx\misc\dserp.wav +SpellLightningHit Misc sfx\misc\elecimp1.wav +SpellElemental Misc sfx\misc\elementl.wav +SpellEtherealize Misc sfx\misc\ethereal.wav +SpellFirebolt Misc sfx\misc\fbolt1.wav +SpellFireHit Misc sfx\misc\firimp2.wav +SpellFlameWave Misc sfx\misc\flamwave.wav +OperateFountain Misc sfx\misc\fountain.wav +SpellGolem Misc sfx\misc\golum.wav +OperateGoatShrine Misc sfx\misc\gshrine.wav +SpellGuardian Misc sfx\misc\guard.wav +SpellGuardianHit Misc sfx\misc\grdlanch.wav +SpellHolyBolt Misc sfx\misc\holybolt.wav +SpellInfravision Misc sfx\misc\infravis.wav +SpellInvisibility Misc sfx\misc\invisibl.wav +SpellLightning Misc sfx\misc\lning1.wav +SpellManaShield Misc sfx\misc\mshield.wav +BigExplosion Misc,Hellfire sfx\misc\nestxpld.wav +SpellNova Misc sfx\misc\nova.wav +SpellPuddle Misc sfx\misc\puddle.wav +SpellResurrect Misc sfx\misc\resur.wav +SpellStoneCurse Misc sfx\misc\scurimp.wav +SpellPortal Misc sfx\misc\sentinel.wav +SpellInferno Misc sfx\misc\spoutstr.wav +SpellTrapDisarm Misc sfx\misc\trapdis.wav +SpellTeleport Misc sfx\misc\teleport.wav +SpellFireWall Misc sfx\misc\wallloop.wav +SpellLightningWall Misc,Hellfire sfx\misc\lmag.wav +Gillian1 Stream sfx\towners\bmaid01.wav +Gillian2 Stream sfx\towners\bmaid02.wav +Gillian3 Stream sfx\towners\bmaid03.wav +Gillian4 Stream sfx\towners\bmaid04.wav +Gillian5 Stream sfx\towners\bmaid05.wav +Gillian6 Stream sfx\towners\bmaid06.wav +Gillian7 Stream sfx\towners\bmaid07.wav +Gillian8 Stream sfx\towners\bmaid08.wav +Gillian9 Stream sfx\towners\bmaid09.wav +Gillian10 Stream sfx\towners\bmaid10.wav +Gillian11 Stream sfx\towners\bmaid11.wav +Gillian12 Stream sfx\towners\bmaid12.wav +Gillian13 Stream sfx\towners\bmaid13.wav +Gillian14 Stream sfx\towners\bmaid14.wav +Gillian15 Stream sfx\towners\bmaid15.wav +Gillian16 Stream sfx\towners\bmaid16.wav +Gillian17 Stream sfx\towners\bmaid17.wav +Gillian18 Stream sfx\towners\bmaid18.wav +Gillian19 Stream sfx\towners\bmaid19.wav +Gillian20 Stream sfx\towners\bmaid20.wav +Gillian21 Stream sfx\towners\bmaid21.wav +Gillian22 Stream sfx\towners\bmaid22.wav +Gillian23 Stream sfx\towners\bmaid23.wav +Gillian24 Stream sfx\towners\bmaid24.wav +Gillian25 Stream sfx\towners\bmaid25.wav +Gillian26 Stream sfx\towners\bmaid26.wav +Gillian27 Stream sfx\towners\bmaid27.wav +Gillian28 Stream sfx\towners\bmaid28.wav +Gillian29 Stream sfx\towners\bmaid29.wav +Gillian30 Stream sfx\towners\bmaid30.wav +Gillian31 Stream sfx\towners\bmaid31.wav +Gillian32 Stream sfx\towners\bmaid32.wav +Gillian33 Stream sfx\towners\bmaid33.wav +Gillian34 Stream sfx\towners\bmaid34.wav +Gillian35 Stream sfx\towners\bmaid35.wav +Gillian36 Stream sfx\towners\bmaid36.wav +Gillian37 Stream sfx\towners\bmaid37.wav +Gillian38 Stream sfx\towners\bmaid38.wav +Gillian39 Stream sfx\towners\bmaid39.wav +Gillian40 Stream sfx\towners\bmaid40.wav +Griswold1 Stream sfx\towners\bsmith01.wav +Griswold2 Stream sfx\towners\bsmith02.wav +Griswold3 Stream sfx\towners\bsmith03.wav +Griswold4 Stream sfx\towners\bsmith04.wav +Griswold5 Stream sfx\towners\bsmith05.wav +Griswold6 Stream sfx\towners\bsmith06.wav +Griswold7 Stream sfx\towners\bsmith07.wav +Griswold8 Stream sfx\towners\bsmith08.wav +Griswold9 Stream sfx\towners\bsmith09.wav +Griswold10 Stream sfx\towners\bsmith10.wav +Griswold11 Stream sfx\towners\bsmith11.wav +Griswold12 Stream sfx\towners\bsmith12.wav +Griswold13 Stream sfx\towners\bsmith13.wav +Griswold14 Stream sfx\towners\bsmith14.wav +Griswold15 Stream sfx\towners\bsmith15.wav +Griswold16 Stream sfx\towners\bsmith16.wav +Griswold17 Stream sfx\towners\bsmith17.wav +Griswold18 Stream sfx\towners\bsmith18.wav +Griswold19 Stream sfx\towners\bsmith19.wav +Griswold20 Stream sfx\towners\bsmith20.wav +Griswold21 Stream sfx\towners\bsmith21.wav +Griswold22 Stream sfx\towners\bsmith22.wav +Griswold23 Stream sfx\towners\bsmith23.wav +Griswold24 Stream sfx\towners\bsmith24.wav +Griswold25 Stream sfx\towners\bsmith25.wav +Griswold26 Stream sfx\towners\bsmith26.wav +Griswold27 Stream sfx\towners\bsmith27.wav +Griswold28 Stream sfx\towners\bsmith28.wav +Griswold29 Stream sfx\towners\bsmith29.wav +Griswold30 Stream sfx\towners\bsmith30.wav +Griswold31 Stream sfx\towners\bsmith31.wav +Griswold32 Stream sfx\towners\bsmith32.wav +Griswold33 Stream sfx\towners\bsmith33.wav +Griswold34 Stream sfx\towners\bsmith34.wav +Griswold35 Stream sfx\towners\bsmith35.wav +Griswold36 Stream sfx\towners\bsmith36.wav +Griswold37 Stream sfx\towners\bsmith37.wav +Griswold38 Stream sfx\towners\bsmith38.wav +Griswold39 Stream sfx\towners\bsmith39.wav +Griswold40 Stream sfx\towners\bsmith40.wav +Griswold41 Stream sfx\towners\bsmith41.wav +Griswold42 Stream sfx\towners\bsmith42.wav +Griswold43 Stream sfx\towners\bsmith43.wav +Griswold44 Stream sfx\towners\bsmith44.wav +Griswold45 Stream sfx\towners\bsmith45.wav +Griswold46 Stream sfx\towners\bsmith46.wav +Griswold47 Stream sfx\towners\bsmith47.wav +Griswold48 Stream sfx\towners\bsmith48.wav +Griswold49 Stream sfx\towners\bsmith49.wav +Griswold50 Stream sfx\towners\bsmith50.wav +Griswold51 Stream sfx\towners\bsmith51.wav +Griswold52 Stream sfx\towners\bsmith52.wav +Griswold53 Stream sfx\towners\bsmith53.wav +Griswold54 Stream sfx\towners\bsmith54.wav +Griswold55 Stream sfx\towners\bsmith55.wav +Griswold56 Stream sfx\towners\bsmith56.wav +Cow1 Misc sfx\towners\cow1.wav +Cow2 Misc sfx\towners\cow2.wav +Pig Misc,Hellfire sfx\towners\cow7.wav +Duck Misc,Hellfire sfx\towners\cow8.wav +WoundedTownsmanOld Stream sfx\towners\deadguy2.wav +Farnham1 Stream sfx\towners\drunk01.wav +Farnham2 Stream sfx\towners\drunk02.wav +Farnham3 Stream sfx\towners\drunk03.wav +Farnham4 Stream sfx\towners\drunk04.wav +Farnham5 Stream sfx\towners\drunk05.wav +Farnham6 Stream sfx\towners\drunk06.wav +Farnham7 Stream sfx\towners\drunk07.wav +Farnham8 Stream sfx\towners\drunk08.wav +Farnham9 Stream sfx\towners\drunk09.wav +Farnham10 Stream sfx\towners\drunk10.wav +Farnham11 Stream sfx\towners\drunk11.wav +Farnham12 Stream sfx\towners\drunk12.wav +Farnham13 Stream sfx\towners\drunk13.wav +Farnham14 Stream sfx\towners\drunk14.wav +Farnham15 Stream sfx\towners\drunk15.wav +Farnham16 Stream sfx\towners\drunk16.wav +Farnham17 Stream sfx\towners\drunk17.wav +Farnham18 Stream sfx\towners\drunk18.wav +Farnham19 Stream sfx\towners\drunk19.wav +Farnham20 Stream sfx\towners\drunk20.wav +Farnham21 Stream sfx\towners\drunk21.wav +Farnham22 Stream sfx\towners\drunk22.wav +Farnham23 Stream sfx\towners\drunk23.wav +Farnham24 Stream sfx\towners\drunk24.wav +Farnham25 Stream sfx\towners\drunk25.wav +Farnham26 Stream sfx\towners\drunk26.wav +Farnham27 Stream sfx\towners\drunk27.wav +Farnham28 Stream sfx\towners\drunk28.wav +Farnham29 Stream sfx\towners\drunk29.wav +Farnham30 Stream sfx\towners\drunk30.wav +Farnham31 Stream sfx\towners\drunk31.wav +Farnham32 Stream sfx\towners\drunk32.wav +Farnham33 Stream sfx\towners\drunk33.wav +Farnham34 Stream sfx\towners\drunk34.wav +Farnham35 Stream sfx\towners\drunk35.wav +Pepin1 Stream sfx\towners\healer01.wav +Pepin2 Stream sfx\towners\healer02.wav +Pepin3 Stream sfx\towners\healer03.wav +Pepin4 Stream sfx\towners\healer04.wav +Pepin5 Stream sfx\towners\healer05.wav +Pepin6 Stream sfx\towners\healer06.wav +Pepin7 Stream sfx\towners\healer07.wav +Pepin8 Stream sfx\towners\healer08.wav +Pepin9 Stream sfx\towners\healer09.wav +Pepin10 Stream sfx\towners\healer10.wav +Pepin11 Stream sfx\towners\healer11.wav +Pepin12 Stream sfx\towners\healer12.wav +Pepin13 Stream sfx\towners\healer13.wav +Pepin14 Stream sfx\towners\healer14.wav +Pepin15 Stream sfx\towners\healer15.wav +Pepin16 Stream sfx\towners\healer16.wav +Pepin17 Stream sfx\towners\healer17.wav +Pepin18 Stream sfx\towners\healer18.wav +Pepin19 Stream sfx\towners\healer19.wav +Pepin20 Stream sfx\towners\healer20.wav +Pepin21 Stream sfx\towners\healer21.wav +Pepin22 Stream sfx\towners\healer22.wav +Pepin23 Stream sfx\towners\healer23.wav +Pepin24 Stream sfx\towners\healer24.wav +Pepin25 Stream sfx\towners\healer25.wav +Pepin26 Stream sfx\towners\healer26.wav +Pepin27 Stream sfx\towners\healer27.wav +Pepin28 Stream sfx\towners\healer28.wav +Pepin29 Stream sfx\towners\healer29.wav +Pepin30 Stream sfx\towners\healer30.wav +Pepin31 Stream sfx\towners\healer31.wav +Pepin32 Stream sfx\towners\healer32.wav +Pepin33 Stream sfx\towners\healer33.wav +Pepin34 Stream sfx\towners\healer34.wav +Pepin35 Stream sfx\towners\healer35.wav +Pepin36 Stream sfx\towners\healer36.wav +Pepin37 Stream sfx\towners\healer37.wav +Pepin38 Stream sfx\towners\healer38.wav +Pepin39 Stream sfx\towners\healer39.wav +Pepin40 Stream sfx\towners\healer40.wav +Pepin41 Stream sfx\towners\healer41.wav +Pepin42 Stream sfx\towners\healer42.wav +Pepin43 Stream sfx\towners\healer43.wav +Pepin44 Stream sfx\towners\healer44.wav +Pepin45 Stream sfx\towners\healer45.wav +Pepin46 Stream sfx\towners\healer46.wav +Pepin47 Stream sfx\towners\healer47.wav +Wirt1 Stream sfx\towners\pegboy01.wav +Wirt2 Stream sfx\towners\pegboy02.wav +Wirt3 Stream sfx\towners\pegboy03.wav +Wirt4 Stream sfx\towners\pegboy04.wav +Wirt5 Stream sfx\towners\pegboy05.wav +Wirt6 Stream sfx\towners\pegboy06.wav +Wirt7 Stream sfx\towners\pegboy07.wav +Wirt8 Stream sfx\towners\pegboy08.wav +Wirt9 Stream sfx\towners\pegboy09.wav +Wirt10 Stream sfx\towners\pegboy10.wav +Wirt11 Stream sfx\towners\pegboy11.wav +Wirt12 Stream sfx\towners\pegboy12.wav +Wirt13 Stream sfx\towners\pegboy13.wav +Wirt14 Stream sfx\towners\pegboy14.wav +Wirt15 Stream sfx\towners\pegboy15.wav +Wirt16 Stream sfx\towners\pegboy16.wav +Wirt17 Stream sfx\towners\pegboy17.wav +Wirt18 Stream sfx\towners\pegboy18.wav +Wirt19 Stream sfx\towners\pegboy19.wav +Wirt20 Stream sfx\towners\pegboy20.wav +Wirt21 Stream sfx\towners\pegboy21.wav +Wirt22 Stream sfx\towners\pegboy22.wav +Wirt23 Stream sfx\towners\pegboy23.wav +Wirt24 Stream sfx\towners\pegboy24.wav +Wirt25 Stream sfx\towners\pegboy25.wav +Wirt26 Stream sfx\towners\pegboy26.wav +Wirt27 Stream sfx\towners\pegboy27.wav +Wirt28 Stream sfx\towners\pegboy28.wav +Wirt29 Stream sfx\towners\pegboy29.wav +Wirt30 Stream sfx\towners\pegboy30.wav +Wirt31 Stream sfx\towners\pegboy31.wav +Wirt32 Stream sfx\towners\pegboy32.wav +Wirt33 Stream sfx\towners\pegboy33.wav +Wirt34 Stream sfx\towners\pegboy34.wav +Wirt35 Stream sfx\towners\pegboy35.wav +Wirt36 Stream sfx\towners\pegboy36.wav +Wirt37 Stream sfx\towners\pegboy37.wav +Wirt38 Stream sfx\towners\pegboy38.wav +Wirt39 Stream sfx\towners\pegboy39.wav +Wirt40 Stream sfx\towners\pegboy40.wav +Wirt41 Stream sfx\towners\pegboy41.wav +Wirt42 Stream sfx\towners\pegboy42.wav +Wirt43 Stream sfx\towners\pegboy43.wav +Tremain0 Stream sfx\towners\priest00.wav +Tremain1 Stream sfx\towners\priest01.wav +Tremain2 Stream sfx\towners\priest02.wav +Tremain3 Stream sfx\towners\priest03.wav +Tremain4 Stream sfx\towners\priest04.wav +Tremain5 Stream sfx\towners\priest05.wav +Tremain6 Stream sfx\towners\priest06.wav +Tremain7 Stream sfx\towners\priest07.wav +Cain0 Stream sfx\towners\storyt00.wav +Cain1 Stream sfx\towners\storyt01.wav +Cain2 Stream sfx\towners\storyt02.wav +Cain3 Stream sfx\towners\storyt03.wav +Cain4 Stream sfx\towners\storyt04.wav +Cain5 Stream sfx\towners\storyt05.wav +Cain6 Stream sfx\towners\storyt06.wav +Cain7 Stream sfx\towners\storyt07.wav +Cain8 Stream sfx\towners\storyt08.wav +Cain9 Stream sfx\towners\storyt09.wav +Cain10 Stream sfx\towners\storyt10.wav +Cain11 Stream sfx\towners\storyt11.wav +Cain12 Stream sfx\towners\storyt12.wav +Cain13 Stream sfx\towners\storyt13.wav +Cain14 Stream sfx\towners\storyt14.wav +Cain15 Stream sfx\towners\storyt15.wav +Cain16 Stream sfx\towners\storyt16.wav +Cain17 Stream sfx\towners\storyt17.wav +Cain18 Stream sfx\towners\storyt18.wav +Cain19 Stream sfx\towners\storyt19.wav +Cain20 Stream sfx\towners\storyt20.wav +Cain21 Stream sfx\towners\storyt21.wav +Cain22 Stream sfx\towners\storyt22.wav +Cain23 Stream sfx\towners\storyt23.wav +Cain24 Stream sfx\towners\storyt24.wav +Cain25 Stream sfx\towners\storyt25.wav +Cain26 Stream sfx\towners\storyt26.wav +Cain27 Stream sfx\towners\storyt27.wav +Cain28 Stream sfx\towners\storyt28.wav +Cain29 Stream sfx\towners\storyt29.wav +Cain30 Stream sfx\towners\storyt30.wav +Cain31 Stream sfx\towners\storyt31.wav +Cain32 Stream sfx\towners\storyt32.wav +Cain33 Stream sfx\towners\storyt33.wav +Cain34 Stream sfx\towners\storyt34.wav +Cain35 Stream sfx\towners\storyt35.wav +Cain36 Stream sfx\towners\storyt36.wav +Cain37 Stream sfx\towners\storyt37.wav +Cain38 Stream sfx\towners\storyt38.wav +Ogden0 Stream sfx\towners\tavown00.wav +Ogden1 Stream sfx\towners\tavown01.wav +Ogden2 Stream sfx\towners\tavown02.wav +Ogden3 Stream sfx\towners\tavown03.wav +Ogden4 Stream sfx\towners\tavown04.wav +Ogden5 Stream sfx\towners\tavown05.wav +Ogden6 Stream sfx\towners\tavown06.wav +Ogden7 Stream sfx\towners\tavown07.wav +Ogden8 Stream sfx\towners\tavown08.wav +Ogden9 Stream sfx\towners\tavown09.wav +Ogden10 Stream sfx\towners\tavown10.wav +Ogden11 Stream sfx\towners\tavown11.wav +Ogden12 Stream sfx\towners\tavown12.wav +Ogden13 Stream sfx\towners\tavown13.wav +Ogden14 Stream sfx\towners\tavown14.wav +Ogden15 Stream sfx\towners\tavown15.wav +Ogden16 Stream sfx\towners\tavown16.wav +Ogden17 Stream sfx\towners\tavown17.wav +Ogden18 Stream sfx\towners\tavown18.wav +Ogden19 Stream sfx\towners\tavown19.wav +Ogden20 Stream sfx\towners\tavown20.wav +Ogden21 Stream sfx\towners\tavown21.wav +Ogden22 Stream sfx\towners\tavown22.wav +Ogden23 Stream sfx\towners\tavown23.wav +Ogden24 Stream sfx\towners\tavown24.wav +Ogden25 Stream sfx\towners\tavown25.wav +Ogden26 Stream sfx\towners\tavown26.wav +Ogden27 Stream sfx\towners\tavown27.wav +Ogden28 Stream sfx\towners\tavown28.wav +Ogden29 Stream sfx\towners\tavown29.wav +Ogden30 Stream sfx\towners\tavown30.wav +Ogden31 Stream sfx\towners\tavown31.wav +Ogden32 Stream sfx\towners\tavown32.wav +Ogden33 Stream sfx\towners\tavown33.wav +Ogden34 Stream sfx\towners\tavown34.wav +Ogden35 Stream sfx\towners\tavown35.wav +Ogden36 Stream sfx\towners\tavown36.wav +Ogden37 Stream sfx\towners\tavown37.wav +Ogden38 Stream sfx\towners\tavown38.wav +Ogden39 Stream sfx\towners\tavown39.wav +Ogden40 Stream sfx\towners\tavown40.wav +Ogden41 Stream sfx\towners\tavown41.wav +Ogden42 Stream sfx\towners\tavown42.wav +Ogden43 Stream sfx\towners\tavown43.wav +Ogden44 Stream sfx\towners\tavown44.wav +Ogden45 Stream sfx\towners\tavown45.wav +Adria1 Stream sfx\towners\witch01.wav +Adria2 Stream sfx\towners\witch02.wav +Adria3 Stream sfx\towners\witch03.wav +Adria4 Stream sfx\towners\witch04.wav +Adria5 Stream sfx\towners\witch05.wav +Adria6 Stream sfx\towners\witch06.wav +Adria7 Stream sfx\towners\witch07.wav +Adria8 Stream sfx\towners\witch08.wav +Adria9 Stream sfx\towners\witch09.wav +Adria10 Stream sfx\towners\witch10.wav +Adria11 Stream sfx\towners\witch11.wav +Adria12 Stream sfx\towners\witch12.wav +Adria13 Stream sfx\towners\witch13.wav +Adria14 Stream sfx\towners\witch14.wav +Adria15 Stream sfx\towners\witch15.wav +Adria16 Stream sfx\towners\witch16.wav +Adria17 Stream sfx\towners\witch17.wav +Adria18 Stream sfx\towners\witch18.wav +Adria19 Stream sfx\towners\witch19.wav +Adria20 Stream sfx\towners\witch20.wav +Adria21 Stream sfx\towners\witch21.wav +Adria22 Stream sfx\towners\witch22.wav +Adria23 Stream sfx\towners\witch23.wav +Adria24 Stream sfx\towners\witch24.wav +Adria25 Stream sfx\towners\witch25.wav +Adria26 Stream sfx\towners\witch26.wav +Adria27 Stream sfx\towners\witch27.wav +Adria28 Stream sfx\towners\witch28.wav +Adria29 Stream sfx\towners\witch29.wav +Adria30 Stream sfx\towners\witch30.wav +Adria31 Stream sfx\towners\witch31.wav +Adria32 Stream sfx\towners\witch32.wav +Adria33 Stream sfx\towners\witch33.wav +Adria34 Stream sfx\towners\witch34.wav +Adria35 Stream sfx\towners\witch35.wav +Adria36 Stream sfx\towners\witch36.wav +Adria37 Stream sfx\towners\witch37.wav +Adria38 Stream sfx\towners\witch38.wav +Adria39 Stream sfx\towners\witch39.wav +Adria40 Stream sfx\towners\witch40.wav +Adria41 Stream sfx\towners\witch41.wav +Adria42 Stream sfx\towners\witch42.wav +Adria43 Stream sfx\towners\witch43.wav +Adria44 Stream sfx\towners\witch44.wav +Adria45 Stream sfx\towners\witch45.wav +Adria46 Stream sfx\towners\witch46.wav +Adria47 Stream sfx\towners\witch47.wav +Adria48 Stream sfx\towners\witch48.wav +Adria49 Stream sfx\towners\witch49.wav +Adria50 Stream sfx\towners\witch50.wav +WoundedTownsman Stream sfx\towners\wound01.wav +Sorceror1 Stream,Sorcerer sfx\sorceror\mage01.wav +Sorceror2 Stream,Sorcerer sfx\sorceror\mage02.wav +Sorceror3 Stream,Sorcerer sfx\sorceror\mage03.wav +Sorceror4 Stream,Sorcerer sfx\sorceror\mage04.wav +Sorceror5 Stream,Sorcerer sfx\sorceror\mage05.wav +Sorceror6 Stream,Sorcerer sfx\sorceror\mage06.wav +Sorceror7 Stream,Sorcerer sfx\sorceror\mage07.wav +Sorceror8 Stream,Sorcerer sfx\sorceror\mage08.wav +Sorceror9 Stream,Sorcerer sfx\sorceror\mage09.wav +Sorceror10 Stream,Sorcerer sfx\sorceror\mage10.wav +Sorceror11 Stream,Sorcerer sfx\sorceror\mage11.wav +Sorceror12 Stream,Sorcerer sfx\sorceror\mage12.wav +Sorceror13 Sorcerer sfx\sorceror\mage13.wav +Sorceror14 Sorcerer sfx\sorceror\mage14.wav +Sorceror15 Sorcerer sfx\sorceror\mage15.wav +Sorceror16 Sorcerer sfx\sorceror\mage16.wav +Sorceror17 Sorcerer sfx\sorceror\mage17.wav +Sorceror18 Sorcerer sfx\sorceror\mage18.wav +Sorceror19 Sorcerer sfx\sorceror\mage19.wav +Sorceror20 Sorcerer sfx\sorceror\mage20.wav +Sorceror21 Sorcerer sfx\sorceror\mage21.wav +Sorceror22 Sorcerer sfx\sorceror\mage22.wav +Sorceror23 Sorcerer sfx\sorceror\mage23.wav +Sorceror24 Sorcerer sfx\sorceror\mage24.wav +Sorceror25 Sorcerer sfx\sorceror\mage25.wav +Sorceror26 Sorcerer sfx\sorceror\mage26.wav +Sorceror27 Sorcerer sfx\sorceror\mage27.wav +Sorceror28 Sorcerer sfx\sorceror\mage28.wav +Sorceror29 Sorcerer sfx\sorceror\mage29.wav +Sorceror30 Sorcerer sfx\sorceror\mage30.wav +Sorceror31 Sorcerer sfx\sorceror\mage31.wav +Sorceror32 Sorcerer sfx\sorceror\mage32.wav +Sorceror33 Sorcerer sfx\sorceror\mage33.wav +Sorceror34 Sorcerer sfx\sorceror\mage34.wav +Sorceror35 Sorcerer sfx\sorceror\mage35.wav +Sorceror36 Sorcerer sfx\sorceror\mage36.wav +Sorceror37 Sorcerer sfx\sorceror\mage37.wav +Sorceror38 Sorcerer sfx\sorceror\mage38.wav +Sorceror39 Sorcerer sfx\sorceror\mage39.wav +Sorceror40 Sorcerer sfx\sorceror\mage40.wav +Sorceror41 Sorcerer sfx\sorceror\mage41.wav +Sorceror42 Sorcerer sfx\sorceror\mage42.wav +Sorceror43 Sorcerer sfx\sorceror\mage43.wav +Sorceror44 Sorcerer sfx\sorceror\mage44.wav +Sorceror45 Sorcerer sfx\sorceror\mage45.wav +Sorceror46 Sorcerer sfx\sorceror\mage46.wav +Sorceror47 Sorcerer sfx\sorceror\mage47.wav +Sorceror48 Sorcerer sfx\sorceror\mage48.wav +Sorceror49 Sorcerer sfx\sorceror\mage49.wav +Sorceror50 Sorcerer sfx\sorceror\mage50.wav +Sorceror51 Stream,Sorcerer sfx\sorceror\mage51.wav +Sorceror52 Stream,Sorcerer sfx\sorceror\mage52.wav +Sorceror53 Stream,Sorcerer sfx\sorceror\mage53.wav +Sorceror54 Stream,Sorcerer sfx\sorceror\mage54.wav +Sorceror55 Stream,Sorcerer sfx\sorceror\mage55.wav +Sorceror56 Stream,Sorcerer sfx\sorceror\mage56.wav +Sorceror57 Sorcerer sfx\sorceror\mage57.wav +Sorceror58 Stream,Sorcerer sfx\sorceror\mage58.wav +Sorceror59 Stream,Sorcerer sfx\sorceror\mage59.wav +Sorceror60 Stream,Sorcerer sfx\sorceror\mage60.wav +Sorceror61 Stream,Sorcerer sfx\sorceror\mage61.wav +Sorceror62 Stream,Sorcerer sfx\sorceror\mage62.wav +Sorceror63 Stream,Sorcerer sfx\sorceror\mage63.wav +Sorceror64 Sorcerer sfx\sorceror\mage64.wav +Sorceror65 Sorcerer sfx\sorceror\mage65.wav +Sorceror66 Sorcerer sfx\sorceror\mage66.wav +Sorceror67 Sorcerer sfx\sorceror\mage67.wav +Sorceror68 Sorcerer sfx\sorceror\mage68.wav +Sorceror69 Sorcerer sfx\sorceror\mage69.wav +Sorceror69b Sorcerer sfx\sorceror\mage69b.wav +Sorceror70 Sorcerer sfx\sorceror\mage70.wav +Sorceror71 Sorcerer sfx\sorceror\mage71.wav +Sorceror72 Sorcerer sfx\sorceror\mage72.wav +Sorceror73 Sorcerer sfx\sorceror\mage73.wav +Sorceror74 Sorcerer sfx\sorceror\mage74.wav +Sorceror75 Sorcerer sfx\sorceror\mage75.wav +Sorceror76 Sorcerer sfx\sorceror\mage76.wav +Sorceror77 Sorcerer sfx\sorceror\mage77.wav +Sorceror78 Sorcerer sfx\sorceror\mage78.wav +Sorceror79 Sorcerer sfx\sorceror\mage79.wav +Sorceror80 Stream,Sorcerer sfx\sorceror\mage80.wav +Sorceror81 Stream,Sorcerer sfx\sorceror\mage81.wav +Sorceror82 Stream,Sorcerer sfx\sorceror\mage82.wav +Sorceror83 Stream,Sorcerer sfx\sorceror\mage83.wav +Sorceror84 Stream,Sorcerer sfx\sorceror\mage84.wav +Sorceror85 Stream,Sorcerer sfx\sorceror\mage85.wav +Sorceror86 Stream,Sorcerer sfx\sorceror\mage86.wav +Sorceror87 Stream,Sorcerer sfx\sorceror\mage87.wav +Sorceror88 Stream,Sorcerer sfx\sorceror\mage88.wav +Sorceror89 Stream,Sorcerer sfx\sorceror\mage89.wav +Sorceror90 Stream,Sorcerer sfx\sorceror\mage90.wav +Sorceror91 Stream,Sorcerer sfx\sorceror\mage91.wav +Sorceror92 Stream,Sorcerer sfx\sorceror\mage92.wav +Sorceror93 Stream,Sorcerer sfx\sorceror\mage93.wav +Sorceror94 Stream,Sorcerer sfx\sorceror\mage94.wav +Sorceror95 Stream,Sorcerer sfx\sorceror\mage95.wav +Sorceror96 Stream,Sorcerer sfx\sorceror\mage96.wav +Sorceror97 Stream,Sorcerer sfx\sorceror\mage97.wav +Sorceror98 Stream,Sorcerer sfx\sorceror\mage98.wav +Sorceror99 Stream,Sorcerer sfx\sorceror\mage99.wav +Sorceror100 Stream,Sorcerer sfx\sorceror\mage100.wav +Sorceror101 Stream,Sorcerer sfx\sorceror\mage101.wav +Sorceror102 Stream,Sorcerer sfx\sorceror\mage102.wav +Rogue1 Stream,Rogue sfx\rogue\rogue01.wav +Rogue2 Stream,Rogue sfx\rogue\rogue02.wav +Rogue3 Stream,Rogue sfx\rogue\rogue03.wav +Rogue4 Stream,Rogue sfx\rogue\rogue04.wav +Rogue5 Stream,Rogue sfx\rogue\rogue05.wav +Rogue6 Stream,Rogue sfx\rogue\rogue06.wav +Rogue7 Stream,Rogue sfx\rogue\rogue07.wav +Rogue8 Stream,Rogue sfx\rogue\rogue08.wav +Rogue9 Stream,Rogue sfx\rogue\rogue09.wav +Rogue10 Stream,Rogue sfx\rogue\rogue10.wav +Rogue11 Stream,Rogue sfx\rogue\rogue11.wav +Rogue12 Stream,Rogue sfx\rogue\rogue12.wav +Rogue13 Rogue sfx\rogue\rogue13.wav +Rogue14 Rogue sfx\rogue\rogue14.wav +Rogue15 Rogue sfx\rogue\rogue15.wav +Rogue16 Rogue sfx\rogue\rogue16.wav +Rogue17 Rogue sfx\rogue\rogue17.wav +Rogue18 Rogue sfx\rogue\rogue18.wav +Rogue19 Rogue sfx\rogue\rogue19.wav +Rogue20 Rogue sfx\rogue\rogue20.wav +Rogue21 Rogue sfx\rogue\rogue21.wav +Rogue22 Rogue sfx\rogue\rogue22.wav +Rogue23 Rogue sfx\rogue\rogue23.wav +Rogue24 Rogue sfx\rogue\rogue24.wav +Rogue25 Rogue sfx\rogue\rogue25.wav +Rogue26 Rogue sfx\rogue\rogue26.wav +Rogue27 Rogue sfx\rogue\rogue27.wav +Rogue28 Rogue sfx\rogue\rogue28.wav +Rogue29 Rogue sfx\rogue\rogue29.wav +Rogue30 Rogue sfx\rogue\rogue30.wav +Rogue31 Rogue sfx\rogue\rogue31.wav +Rogue32 Rogue sfx\rogue\rogue32.wav +Rogue33 Rogue sfx\rogue\rogue33.wav +Rogue34 Rogue sfx\rogue\rogue34.wav +Rogue35 Rogue sfx\rogue\rogue35.wav +Rogue36 Rogue sfx\rogue\rogue36.wav +Rogue37 Rogue sfx\rogue\rogue37.wav +Rogue38 Rogue sfx\rogue\rogue38.wav +Rogue39 Rogue sfx\rogue\rogue39.wav +Rogue40 Rogue sfx\rogue\rogue40.wav +Rogue41 Rogue sfx\rogue\rogue41.wav +Rogue42 Rogue sfx\rogue\rogue42.wav +Rogue43 Rogue sfx\rogue\rogue43.wav +Rogue44 Rogue sfx\rogue\rogue44.wav +Rogue45 Rogue sfx\rogue\rogue45.wav +Rogue46 Rogue sfx\rogue\rogue46.wav +Rogue47 Rogue sfx\rogue\rogue47.wav +Rogue48 Rogue sfx\rogue\rogue48.wav +Rogue49 Rogue sfx\rogue\rogue49.wav +Rogue50 Rogue sfx\rogue\rogue50.wav +Rogue51 Stream,Rogue sfx\rogue\rogue51.wav +Rogue52 Stream,Rogue sfx\rogue\rogue52.wav +Rogue53 Stream,Rogue sfx\rogue\rogue53.wav +Rogue54 Stream,Rogue sfx\rogue\rogue54.wav +Rogue55 Stream,Rogue sfx\rogue\rogue55.wav +Rogue56 Stream,Rogue sfx\rogue\rogue56.wav +Rogue57 Rogue sfx\rogue\rogue57.wav +Rogue58 Stream,Rogue sfx\rogue\rogue58.wav +Rogue59 Stream,Rogue sfx\rogue\rogue59.wav +Rogue60 Stream,Rogue sfx\rogue\rogue60.wav +Rogue61 Stream,Rogue sfx\rogue\rogue61.wav +Rogue62 Stream,Rogue sfx\rogue\rogue62.wav +Rogue63 Stream,Rogue sfx\rogue\rogue63.wav +Rogue64 Rogue sfx\rogue\rogue64.wav +Rogue65 Rogue sfx\rogue\rogue65.wav +Rogue66 Rogue sfx\rogue\rogue66.wav +Rogue67 Rogue sfx\rogue\rogue67.wav +Rogue68 Rogue sfx\rogue\rogue68.wav +Rogue69 Rogue sfx\rogue\rogue69.wav +Rogue69b Rogue sfx\rogue\rogue69b.wav +Rogue70 Rogue sfx\rogue\rogue70.wav +Rogue71 Rogue sfx\rogue\rogue71.wav +Rogue72 Rogue sfx\rogue\rogue72.wav +Rogue73 Rogue sfx\rogue\rogue73.wav +Rogue74 Rogue sfx\rogue\rogue74.wav +Rogue75 Rogue sfx\rogue\rogue75.wav +Rogue76 Rogue sfx\rogue\rogue76.wav +Rogue77 Rogue sfx\rogue\rogue77.wav +Rogue78 Rogue sfx\rogue\rogue78.wav +Rogue79 Rogue sfx\rogue\rogue79.wav +Rogue80 Stream,Rogue sfx\rogue\rogue80.wav +Rogue81 Stream,Rogue sfx\rogue\rogue81.wav +Rogue82 Stream,Rogue sfx\rogue\rogue82.wav +Rogue83 Stream,Rogue sfx\rogue\rogue83.wav +Rogue84 Stream,Rogue sfx\rogue\rogue84.wav +Rogue85 Stream,Rogue sfx\rogue\rogue85.wav +Rogue86 Stream,Rogue sfx\rogue\rogue86.wav +Rogue87 Stream,Rogue sfx\rogue\rogue87.wav +Rogue88 Stream,Rogue sfx\rogue\rogue88.wav +Rogue89 Stream,Rogue sfx\rogue\rogue89.wav +Rogue90 Stream,Rogue sfx\rogue\rogue90.wav +Rogue91 Stream,Rogue sfx\rogue\rogue91.wav +Rogue92 Stream,Rogue sfx\rogue\rogue92.wav +Rogue93 Stream,Rogue sfx\rogue\rogue93.wav +Rogue94 Stream,Rogue sfx\rogue\rogue94.wav +Rogue95 Stream,Rogue sfx\rogue\rogue95.wav +Rogue96 Stream,Rogue sfx\rogue\rogue96.wav +Rogue97 Stream,Rogue sfx\rogue\rogue97.wav +Rogue98 Stream,Rogue sfx\rogue\rogue98.wav +Rogue99 Stream,Rogue sfx\rogue\rogue99.wav +Rogue100 Stream,Rogue sfx\rogue\rogue100.wav +Rogue101 Stream,Rogue sfx\rogue\rogue101.wav +Rogue102 Stream,Rogue sfx\rogue\rogue102.wav +Warrior1 Stream,Warrior sfx\warrior\warior01.wav +Warrior2 Stream,Warrior sfx\warrior\warior02.wav +Warrior3 Stream,Warrior sfx\warrior\warior03.wav +Warrior4 Stream,Warrior sfx\warrior\warior04.wav +Warrior5 Stream,Warrior sfx\warrior\warior05.wav +Warrior6 Stream,Warrior sfx\warrior\warior06.wav +Warrior7 Stream,Warrior sfx\warrior\warior07.wav +Warrior8 Stream,Warrior sfx\warrior\warior08.wav +Warrior9 Stream,Warrior sfx\warrior\warior09.wav +Warrior10 Stream,Warrior sfx\warrior\warior10.wav +Warrior11 Stream,Warrior sfx\warrior\warior11.wav +Warrior12 Stream,Warrior sfx\warrior\warior12.wav +Warrior13 Warrior sfx\warrior\warior13.wav +Warrior14 Warrior sfx\warrior\warior14.wav +Warrior14b Warrior sfx\warrior\wario14b.wav +Warrior14c Warrior sfx\warrior\wario14c.wav +Warrior15 Warrior sfx\warrior\warior15.wav +Warrior15b Warrior sfx\warrior\wario15b.wav +Warrior15c Warrior sfx\warrior\wario15c.wav +Warrior16 Warrior sfx\warrior\warior16.wav +Warrior16b Warrior sfx\warrior\wario16b.wav +Warrior16c Warrior sfx\warrior\wario16c.wav +Warrior17 Warrior sfx\warrior\warior17.wav +Warrior18 Warrior sfx\warrior\warior18.wav +Warrior19 Warrior sfx\warrior\warior19.wav +Warrior20 Warrior sfx\warrior\warior20.wav +Warrior21 Warrior sfx\warrior\warior21.wav +Warrior22 Warrior sfx\warrior\warior22.wav +Warrior23 Warrior sfx\warrior\warior23.wav +Warrior24 Warrior sfx\warrior\warior24.wav +Warrior25 Warrior sfx\warrior\warior25.wav +Warrior26 Warrior sfx\warrior\warior26.wav +Warrior27 Warrior sfx\warrior\warior27.wav +Warrior28 Warrior sfx\warrior\warior28.wav +Warrior29 Warrior sfx\warrior\warior29.wav +Warrior30 Warrior sfx\warrior\warior30.wav +Warrior31 Warrior sfx\warrior\warior31.wav +Warrior32 Warrior sfx\warrior\warior32.wav +Warrior33 Warrior sfx\warrior\warior33.wav +Warrior34 Warrior sfx\warrior\warior34.wav +Warrior35 Warrior sfx\warrior\warior35.wav +Warrior36 Warrior sfx\warrior\warior36.wav +Warrior37 Warrior sfx\warrior\warior37.wav +Warrior38 Warrior sfx\warrior\warior38.wav +Warrior39 Warrior sfx\warrior\warior39.wav +Warrior40 Warrior sfx\warrior\warior40.wav +Warrior41 Warrior sfx\warrior\warior41.wav +Warrior42 Warrior sfx\warrior\warior42.wav +Warrior43 Warrior sfx\warrior\warior43.wav +Warrior44 Warrior sfx\warrior\warior44.wav +Warrior45 Warrior sfx\warrior\warior45.wav +Warrior46 Warrior sfx\warrior\warior46.wav +Warrior47 Warrior sfx\warrior\warior47.wav +Warrior48 Warrior sfx\warrior\warior48.wav +Warrior49 Warrior sfx\warrior\warior49.wav +Warrior50 Warrior sfx\warrior\warior50.wav +Warrior51 Stream,Warrior sfx\warrior\warior51.wav +Warrior52 Stream,Warrior sfx\warrior\warior52.wav +Warrior53 Stream,Warrior sfx\warrior\warior53.wav +Warrior54 Stream,Warrior sfx\warrior\warior54.wav +Warrior55 Stream,Warrior sfx\warrior\warior55.wav +Warrior56 Stream,Warrior sfx\warrior\warior56.wav +Warrior57 Warrior sfx\warrior\warior57.wav +Warrior58 Stream,Warrior sfx\warrior\warior58.wav +Warrior59 Stream,Warrior sfx\warrior\warior59.wav +Warrior60 Stream,Warrior sfx\warrior\warior60.wav +Warrior61 Stream,Warrior sfx\warrior\warior61.wav +Warrior62 Stream,Warrior sfx\warrior\warior62.wav +Warrior63 Stream,Warrior sfx\warrior\warior63.wav +Warrior64 Warrior sfx\warrior\warior64.wav +Warrior65 Warrior sfx\warrior\warior65.wav +Warrior66 Warrior sfx\warrior\warior66.wav +Warrior67 Warrior sfx\warrior\warior67.wav +Warrior68 Warrior sfx\warrior\warior68.wav +Warrior69 Warrior sfx\warrior\warior69.wav +Warrior69b Warrior sfx\warrior\wario69b.wav +Warrior70 Warrior sfx\warrior\warior70.wav +Warrior71 Warrior sfx\warrior\warior71.wav +Warrior72 Warrior sfx\warrior\warior72.wav +Warrior73 Warrior sfx\warrior\warior73.wav +Warrior74 Warrior sfx\warrior\warior74.wav +Warrior75 Warrior sfx\warrior\warior75.wav +Warrior76 Warrior sfx\warrior\warior76.wav +Warrior77 Warrior sfx\warrior\warior77.wav +Warrior78 Warrior sfx\warrior\warior78.wav +Warrior79 Warrior sfx\warrior\warior79.wav +Warrior80 Stream,Warrior sfx\warrior\warior80.wav +Warrior81 Stream,Warrior sfx\warrior\warior81.wav +Warrior82 Stream,Warrior sfx\warrior\warior82.wav +Warrior83 Stream,Warrior sfx\warrior\warior83.wav +Warrior84 Stream,Warrior sfx\warrior\warior84.wav +Warrior85 Stream,Warrior sfx\warrior\warior85.wav +Warrior86 Stream,Warrior sfx\warrior\warior86.wav +Warrior87 Stream,Warrior sfx\warrior\warior87.wav +Warrior88 Stream,Warrior sfx\warrior\warior88.wav +Warrior89 Stream,Warrior sfx\warrior\warior89.wav +Warrior90 Stream,Warrior sfx\warrior\warior90.wav +Warrior91 Stream,Warrior sfx\warrior\warior91.wav +Warrior92 Stream,Warrior sfx\warrior\warior92.wav +Warrior93 Stream,Warrior sfx\warrior\warior93.wav +Warrior94 Stream,Warrior sfx\warrior\warior94.wav +Warrior95 Stream,Warrior sfx\warrior\warior95.wav +Warrior95b Stream,Warrior sfx\warrior\wario95b.wav +Warrior95c Stream,Warrior sfx\warrior\wario95c.wav +Warrior95d Stream,Warrior sfx\warrior\wario95d.wav +Warrior95e Stream,Warrior sfx\warrior\wario95e.wav +Warrior95f Stream,Warrior sfx\warrior\wario95f.wav +Warrior96b Stream,Warrior sfx\warrior\wario96b.wav +Warrior97 Stream,Warrior sfx\warrior\wario97.wav +Warrior98 Stream,Warrior sfx\warrior\wario98.wav +Warrior99 Stream,Warrior sfx\warrior\warior99.wav +Warrior100 Stream,Warrior sfx\warrior\wario100.wav +Warrior101 Stream,Warrior sfx\warrior\wario101.wav +Warrior102 Stream,Warrior sfx\warrior\wario102.wav +Monk1 Stream,Monk sfx\monk\monk01.wav +Monk8 Stream,Monk sfx\monk\monk08.wav +Monk9 Stream,Monk sfx\monk\monk09.wav +Monk10 Stream,Monk sfx\monk\monk10.wav +Monk11 Stream,Monk sfx\monk\monk11.wav +Monk12 Stream,Monk sfx\monk\monk12.wav +Monk13 Monk sfx\monk\monk13.wav +Monk14 Monk sfx\monk\monk14.wav +Monk15 Monk sfx\monk\monk15.wav +Monk16 Monk sfx\monk\monk16.wav +Monk24 Monk sfx\monk\monk24.wav +Monk27 Monk sfx\monk\monk27.wav +Monk29 Monk sfx\monk\monk29.wav +Monk34 Monk sfx\monk\monk34.wav +Monk35 Monk sfx\monk\monk35.wav +Monk43 Monk sfx\monk\monk43.wav +Monk46 Monk sfx\monk\monk46.wav +Monk49 Monk sfx\monk\monk49.wav +Monk50 Monk sfx\monk\monk50.wav +Monk52 Stream,Monk sfx\monk\monk52.wav +Monk54 Stream,Monk sfx\monk\monk54.wav +Monk55 Stream,Monk sfx\monk\monk55.wav +Monk56 Stream,Monk sfx\monk\monk56.wav +Monk61 Stream,Monk sfx\monk\monk61.wav +Monk62 Stream,Monk sfx\monk\monk62.wav +Monk68 Monk sfx\monk\monk68.wav +Monk69 Monk sfx\monk\monk69.wav +Monk69b Monk sfx\monk\monk69b.wav +Monk70 Monk sfx\monk\monk70.wav +Monk71 Monk sfx\monk\monk71.wav +Monk79 Monk sfx\monk\monk79.wav +Monk80 Stream,Monk sfx\monk\monk80.wav +Monk82 Stream,Monk sfx\monk\monk82.wav +Monk83 Stream,Monk sfx\monk\monk83.wav +Monk87 Stream,Monk sfx\monk\monk87.wav +Monk88 Stream,Monk sfx\monk\monk88.wav +Monk89 Stream,Monk sfx\monk\monk89.wav +Monk91 Stream,Monk sfx\monk\monk91.wav +Monk92 Stream,Monk sfx\monk\monk92.wav +Monk94 Stream,Monk sfx\monk\monk94.wav +Monk95 Stream,Monk sfx\monk\monk95.wav +Monk96 Stream,Monk sfx\monk\monk96.wav +Monk97 Stream,Monk sfx\monk\monk97.wav +Monk98 Stream,Monk sfx\monk\monk98.wav +Monk99 Stream,Monk sfx\monk\monk99.wav +Narrator1 Stream sfx\narrator\nar01.wav +Narrator2 Stream sfx\narrator\nar02.wav +Narrator3 Stream sfx\narrator\nar03.wav +Narrator4 Stream sfx\narrator\nar04.wav +Narrator5 Stream sfx\narrator\nar05.wav +Narrator6 Stream sfx\narrator\nar06.wav +Narrator7 Stream sfx\narrator\nar07.wav +Narrator8 Stream sfx\narrator\nar08.wav +Narrator9 Stream sfx\narrator\nar09.wav +DiabloGreeting Stream sfx\misc\lvl16int.wav +ButcherGreeting Stream sfx\monsters\butcher.wav +Gharbad1 Stream sfx\monsters\garbud01.wav +Gharbad2 Stream sfx\monsters\garbud02.wav +Gharbad3 Stream sfx\monsters\garbud03.wav +Gharbad4 Stream sfx\monsters\garbud04.wav +Izual Stream sfx\monsters\izual01.wav +Lachdanan1 Stream sfx\monsters\lach01.wav +Lachdanan2 Stream sfx\monsters\lach02.wav +Lachdanan3 Stream sfx\monsters\lach03.wav +LazarusGreeting Stream sfx\monsters\laz01.wav +LazarusGreetingShort Stream sfx\monsters\laz02.wav +LeoricGreeting Stream sfx\monsters\sking01.wav +Snotspill1 Stream sfx\monsters\snot01.wav +Snotspill2 Stream sfx\monsters\snot02.wav +Snotspill3 Stream sfx\monsters\snot03.wav +Warlord Stream sfx\monsters\warlrd01.wav +Warlock Stream sfx\monsters\wlock01.wav +Zhar1 Stream sfx\monsters\zhar01.wav +Zhar2 Stream sfx\monsters\zhar02.wav +DiabloDeath Stream sfx\monsters\diablod.wav +Farmer1 Stream sfx\hellfire\farmer1.wav +Farmer2 Stream sfx\hellfire\farmer2.wav +Farmer2a Stream sfx\hellfire\Farmer2a.wav +Farmer3 Stream sfx\hellfire\farmer3.wav +Farmer4 Stream sfx\hellfire\farmer4.wav +Farmer5 Stream sfx\hellfire\farmer5.wav +Farmer6 Stream sfx\hellfire\farmer6.wav +Farmer7 Stream sfx\hellfire\farmer7.wav +Farmer8 Stream sfx\hellfire\farmer8.wav +Farmer9 Stream sfx\hellfire\farmer9.wav +Celia1 Stream sfx\hellfire\teddybr1.wav +Celia2 Stream sfx\hellfire\teddybr2.wav +Celia3 Stream sfx\hellfire\teddybr3.wav +Celia4 Stream sfx\hellfire\teddybr4.wav +Defiler1 Stream sfx\hellfire\defiler1.wav +Defiler2 Stream sfx\hellfire\defiler2.wav +Defiler3 Stream sfx\hellfire\defiler3.wav +Defiler4 Stream sfx\hellfire\defiler4.wav +Defiler8 Stream sfx\hellfire\defiler8.wav +Defiler6 Stream sfx\hellfire\defiler6.wav +Defiler7 Stream sfx\hellfire\defiler7.wav +NaKrul1 Stream sfx\hellfire\nakrul1.wav +NaKrul2 Stream sfx\hellfire\nakrul2.wav +NaKrul3 Stream sfx\hellfire\nakrul3.wav +NaKrul4 Stream sfx\hellfire\nakrul4.wav +NaKrul5 Stream sfx\hellfire\nakrul5.wav +NaKrul6 Stream sfx\hellfire\nakrul6.wav +NarratorHF3 Stream sfx\hellfire\naratr3.wav +CompleteNut1 Stream sfx\hellfire\cowsut1.wav +CompleteNut2 Stream sfx\hellfire\cowsut2.wav +CompleteNut3 Stream sfx\hellfire\cowsut3.wav +CompleteNut4 Stream sfx\hellfire\cowsut4.wav +CompleteNut4a Stream sfx\hellfire\cowsut4a.wav +CompleteNut5 Stream sfx\hellfire\cowsut5.wav +CompleteNut6 Stream sfx\hellfire\cowsut6.wav +CompleteNut7 Stream sfx\hellfire\cowsut7.wav +CompleteNut8 Stream sfx\hellfire\cowsut8.wav +CompleteNut9 Stream sfx\hellfire\cowsut9.wav +CompleteNut10 Stream sfx\hellfire\cowsut10.wav +CompleteNut11 Stream sfx\hellfire\cowsut11.wav +CompleteNut12 Stream sfx\hellfire\cowsut12.wav +NarratorHF6 Stream sfx\hellfire\naratr6.wav +NarratorHF7 Stream sfx\hellfire\naratr7.wav +NarratorHF8 Stream sfx\hellfire\naratr8.wav +NarratorHF5 Stream sfx\hellfire\naratr5.wav +NarratorHF9 Stream sfx\hellfire\naratr9.wav +NarratorHF4 Stream sfx\hellfire\naratr4.wav +CryptDoorOpen Misc,Hellfire sfx\items\cropen.wav +CryptDoorClose Misc,Hellfire sfx\items\crclos.wav diff --git a/assets/txtdata/spells/spelldat.tsv b/assets/txtdata/spells/spelldat.tsv new file mode 100644 index 00000000000..5f135d211ac --- /dev/null +++ b/assets/txtdata/spells/spelldat.tsv @@ -0,0 +1,52 @@ +id name soundId bookCost10 staffCost10 manaCost flags bookLevel staffLevel minIntelligence missiles manaMultiplier minMana staffMin staffMax +Firebolt Firebolt CastFire 100 5 6 Fire,Targeted 1 1 15 Firebolt 1 3 40 80 +Healing Healing CastHealing 100 5 5 Magic,AllowedInTown 1 1 17 Healing 3 1 20 40 +Lightning Lightning CastLightning 300 15 10 Lightning,Targeted 4 3 20 LightningControl 1 6 20 60 +Flash Flash CastLightning 750 50 30 Lightning 5 4 33 FlashBottom,FlashTop 2 16 20 40 +Identify Identify CastSkill 0 10 13 Magic,AllowedInTown -1 -1 23 Identify 2 1 8 12 +FireWall Fire Wall CastFire 600 40 28 Fire,Targeted 3 2 27 FireWallControl 2 16 8 16 +TownPortal Town Portal CastSkill 300 20 35 Magic,Targeted 3 3 20 TownPortal 3 18 8 12 +StoneCurse Stone Curse CastFire 1200 80 60 Magic,Targeted 6 5 51 StoneCurse 3 40 8 16 +Infravision Infravision CastHealing 0 60 40 Magic -1 -1 36 Infravision 5 20 0 0 +Phasing Phasing CastFire 350 20 12 Magic 7 6 39 Phasing 2 4 40 80 +ManaShield Mana Shield CastFire 1600 120 33 Magic 6 5 25 ManaShield 0 33 4 10 +Fireball Fireball CastFire 800 30 16 Fire,Targeted 8 7 48 Fireball 1 10 40 80 +Guardian Guardian CastFire 1400 95 50 Fire,Targeted 9 8 61 Guardian 2 30 16 32 +ChainLightning Chain Lightning CastFire 1100 75 30 Lightning 8 7 54 ChainLightning 1 18 20 60 +FlameWave Flame Wave CastFire 1000 65 35 Fire,Targeted 9 8 54 FlameWaveControl 3 20 20 40 +DoomSerpents Doom Serpents CastFire 0 0 0 Lightning -1 -1 0 0 0 40 80 +BloodRitual Blood Ritual CastFire 0 0 0 Magic -1 -1 0 0 0 40 80 +Nova Nova CastLightning 2100 130 60 Magic 14 10 87 Nova 3 35 16 32 +Invisibility Invisibility CastFire 0 0 0 Magic -1 -1 0 0 0 40 80 +Inferno Inferno CastFire 200 10 11 Fire,Targeted 3 2 20 InfernoControl 1 6 20 40 +Golem Golem CastFire 1800 110 100 Fire,Targeted 11 9 81 Golem 6 60 16 32 +Rage Rage CastHealing 0 0 15 Magic -1 -1 0 Rage 1 1 0 0 +Teleport Teleport CastSkill 2000 125 35 Magic,Targeted 14 12 105 Teleport 3 15 16 32 +Apocalypse Apocalypse CastFire 3000 200 150 Fire 19 15 149 Apocalypse 6 90 8 12 +Etherealize Etherealize CastFire 2600 160 100 Magic -1 -1 93 Etherealize 0 100 2 6 +ItemRepair Item Repair CastSkill 0 0 0 Magic,AllowedInTown -1 -1 255 ItemRepair 0 0 40 80 +StaffRecharge Staff Recharge CastSkill 0 0 0 Magic,AllowedInTown -1 -1 255 StaffRecharge 0 0 40 80 +TrapDisarm Trap Disarm CastSkill 0 0 0 Magic -1 -1 255 TrapDisarm 0 0 40 80 +Elemental Elemental CastFire 1050 70 35 Fire 8 6 68 Elemental 2 20 20 60 +ChargedBolt Charged Bolt CastFire 100 5 6 Lightning,Targeted 1 1 25 ChargedBolt 1 6 40 80 +HolyBolt Holy Bolt CastFire 100 5 7 Magic,Targeted 1 1 20 HolyBolt 1 3 40 80 +Resurrect Resurrect CastHealing 400 25 20 Magic,AllowedInTown -1 5 30 Resurrect 0 20 4 10 +Telekinesis Telekinesis CastFire 250 20 15 Magic 2 2 33 Telekinesis 2 8 20 40 +HealOther Heal Other CastHealing 100 5 5 Magic,AllowedInTown 1 1 17 HealOther 3 1 20 40 +BloodStar Blood Star CastFire 2750 180 25 Magic 14 13 70 BloodStar 2 14 20 60 +BoneSpirit Bone Spirit CastFire 1150 80 24 Magic 9 7 34 BoneSpirit 1 12 20 60 +Mana Mana CastHealing 100 5 255 Magic,AllowedInTown -1 5 17 Mana 3 1 12 24 +Magi the Magi CastHealing 10000 20 255 Magic,AllowedInTown -1 20 45 Magi 3 1 15 30 +Jester the Jester CastHealing 10000 20 255 Magic,Targeted -1 4 30 Jester 3 1 15 30 +LightningWall Lightning Wall CastLightning 600 40 28 Lightning,Targeted 3 2 27 LightningWallControl 2 16 8 16 +Immolation Immolation CastFire 2100 130 60 Fire 14 10 87 Immolation 3 35 16 32 +Warp Warp CastSkill 300 20 35 Magic 3 3 25 Warp 3 18 8 12 +Reflect Reflect CastSkill 300 20 35 Magic 3 3 25 Reflect 3 15 8 12 +Berserk Berserk CastSkill 300 20 35 Magic,Targeted 3 3 35 Berserk 3 15 8 12 +RingOfFire Ring of Fire CastFire 600 40 28 Fire 5 5 27 RingOfFire 2 16 8 16 +Search Search CastSkill 300 20 15 Magic 1 3 25 Search 1 1 8 12 +RuneOfFire Rune of Fire CastHealing 800 30 255 Magic,Targeted -1 -1 48 RuneOfFire 1 10 40 80 +RuneOfLight Rune of Light CastHealing 800 30 255 Magic,Targeted -1 -1 48 RuneOfLight 1 10 40 80 +RuneOfNova Rune of Nova CastHealing 800 30 255 Magic,Targeted -1 -1 48 RuneOfNova 1 10 40 80 +RuneOfImmolation Rune of Immolation CastHealing 800 30 255 Magic,Targeted -1 -1 48 RuneOfImmolation 1 10 40 80 +RuneOfStone Rune of Stone CastHealing 800 30 255 Magic,Targeted -1 -1 48 RuneOfStone 1 10 40 80 diff --git a/Packaging/resources/assets/ui_art/button.png b/assets/ui_art/button.png similarity index 100% rename from Packaging/resources/assets/ui_art/button.png rename to assets/ui_art/button.png diff --git a/Packaging/resources/assets/ui_art/creditsw.clx b/assets/ui_art/creditsw.clx similarity index 100% rename from Packaging/resources/assets/ui_art/creditsw.clx rename to assets/ui_art/creditsw.clx diff --git a/Packaging/resources/assets/ui_art/diablo.pal b/assets/ui_art/diablo.pal similarity index 100% rename from Packaging/resources/assets/ui_art/diablo.pal rename to assets/ui_art/diablo.pal diff --git a/Packaging/resources/assets/ui_art/directions.png b/assets/ui_art/directions.png similarity index 100% rename from Packaging/resources/assets/ui_art/directions.png rename to assets/ui_art/directions.png diff --git a/Packaging/resources/assets/ui_art/directions2.png b/assets/ui_art/directions2.png similarity index 100% rename from Packaging/resources/assets/ui_art/directions2.png rename to assets/ui_art/directions2.png diff --git a/Packaging/resources/assets/ui_art/dvl_but_sml.clx b/assets/ui_art/dvl_but_sml.clx similarity index 100% rename from Packaging/resources/assets/ui_art/dvl_but_sml.clx rename to assets/ui_art/dvl_but_sml.clx diff --git a/Packaging/resources/assets/ui_art/dvl_lrpopup.clx b/assets/ui_art/dvl_lrpopup.clx similarity index 100% rename from Packaging/resources/assets/ui_art/dvl_lrpopup.clx rename to assets/ui_art/dvl_lrpopup.clx diff --git a/Packaging/resources/assets/ui_art/hellfire.pal b/assets/ui_art/hellfire.pal similarity index 100% rename from Packaging/resources/assets/ui_art/hellfire.pal rename to assets/ui_art/hellfire.pal diff --git a/Packaging/resources/assets/ui_art/hf_titlew.clx b/assets/ui_art/hf_titlew.clx similarity index 100% rename from Packaging/resources/assets/ui_art/hf_titlew.clx rename to assets/ui_art/hf_titlew.clx diff --git a/Packaging/resources/assets/ui_art/mainmenuw.clx b/assets/ui_art/mainmenuw.clx similarity index 100% rename from Packaging/resources/assets/ui_art/mainmenuw.clx rename to assets/ui_art/mainmenuw.clx diff --git a/Packaging/resources/assets/ui_art/menu-levelup.png b/assets/ui_art/menu-levelup.png similarity index 100% rename from Packaging/resources/assets/ui_art/menu-levelup.png rename to assets/ui_art/menu-levelup.png diff --git a/Packaging/resources/assets/ui_art/menu.png b/assets/ui_art/menu.png similarity index 100% rename from Packaging/resources/assets/ui_art/menu.png rename to assets/ui_art/menu.png diff --git a/Packaging/resources/assets/ui_art/supportw.clx b/assets/ui_art/supportw.clx similarity index 100% rename from Packaging/resources/assets/ui_art/supportw.clx rename to assets/ui_art/supportw.clx diff --git a/docs/BACKGROUND.md b/docs/BACKGROUND.md index 5976d980495..1d0e68657c7 100644 --- a/docs/BACKGROUND.md +++ b/docs/BACKGROUND.md @@ -48,7 +48,7 @@ Id Build Count Name Description Primarily "/O1" was used, but there are also peculiarities such as the use of both Microsoft Visual Studio 6 and Microsoft Visual Code 5 for linking the game. -5. The heartfelt dedication of a team of people. GalaXyHaXz did the initial heavy lifting and succeeded in the tremendous task of getting the decompiled source code of Diablo 1 compiling with the original toolchain. Later on she released the project open source and a community of open source collaborators formed. Most of us have never met in real life prior to joining the project, which stands to show that there is strength in online collaboration that transcend both culture and borders. +5. The heartfelt dedication of a team of people. GalaXyHaXz did the initial heavy lifting and succeeded in the tremendous task of getting the decompiled source code of Diablo 1 compiling with the original toolchain. Later on she released the project open source and a community of open source collaborators formed. Most of us have never met in real life prior to joining the project, which stands to show that there is strength in online collaboration that transcends both culture and borders. 6. The Beta release and the Alpha4 release of Diablo 1 has also proved invaluable resources for cross-validation as the compiler optimization level was not set to release mode for these binaries. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1c63c2e1916..31e189f2dca 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## DevilutionX 1.5.1 + ### Features #### Multiplayer @@ -15,41 +17,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update the pvp arenas - Rename "Loopback" to "Offline" +#### Stability / Performance / System + +- Move hp/mana display and item graphics to gameplay options +- Validate properties when reloading items +- Demomode: Improve replay stability +- Update [Discord link](https://discord.gg/devilutionx) +- Display save game confirmation +- Reduce ram usage + #### Translations +- Update Simplified Chinese translation - Update French translation - Update German translation - Update Greek translation - Update Japanese translation - Update Korean translation - Update Portuguese translation +- Update Spanish translation +- Update Swedish translation - Update Ukrainian translation -#### Platforms - -- Android TV: Update banner to include app name - -#### Stability / Performance / System - -- Move hp/mana display and item graphics to gameplay options -- Validate properties when reloading items -- Demomode: Improve replay stability -- Update [Discord link](https://discord.gg/devilutionx) -- Reduce ram usage - ### Bugfixes #### Gameplay - Being able to enter Lazarus' chamber before opening the portal - Book requirements not updating -- Diablo: Incorrect level 4 layout when Magic Banner quest is active -- Halls of the Blind not being compleated by picking up the amulet +- Some monsters not walking +- Missiles not traveling the full distance at some angles +- Diablo: Incorrect level 4 layout when the Magic Banner quest is active +- Halls of the Blind not being completed by picking up the amulet +- Shareware: Bucklers not dropping +- Player animation stuttering + +#### Multiplayer + +- Potions dropped by Divine shrines not being synced #### Platforms -- Linux: Add sdl-image dependency for deb package +- Linux: Add sdl-image dependency for the deb package - Linux: Include discord dependency +- Xbox One: Missing assets #### Graphics / Audio @@ -57,14 +68,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Incorrect outlines at the right edge of the screen - NPC speech continuing after starting a new game - Correct various font rendering issues +- Hide the hit indicator when only one player is in the game +- Issues with flashing lights +- Floating number still appearing after death +- Misaligned automap #### Controls +- Inconsistencies with placing items in to the stash - Gamepad: Being stuck in dialogs - Gamepad: Unable to use some scrolls directly #### Stability / Performance / System +- Unable to playback new demo files - Various crashes ### Bugfixes for original Diablo bugs @@ -72,11 +89,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### Gameplay - Durability overflowing when reloading items +- Teleporting onto an occupied tile +- Right-click during dialogs casts spells #### Graphics / Audio +- Cursor jitter when interacting with the inventory - Broken lava tiles +#### Controls + +- Inconsistencies with placing items in to the inventory + +### Bugfixes for original Hellfire bugs + +#### Gameplay + +- Warping onto a solid tile + ## DevilutionX 1.5.0 ### Features @@ -214,7 +244,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### Gameplay -- Several issues that would cause missiles to miss when they shoudn't +- Several issues that would cause missiles to miss when they shouldn't - Some wall tiles not blocking missiles and vision - The player can spawn in an incorrect location on some levels - Missing the extra stats at level 50 @@ -230,7 +260,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The Deadly Hunter bow not dealing the correct damage - Spell remaining unavailable after using a stone shrine until reequipping the staff - Fast and faster hit recovery stacking -- Incorrect calculation for max chages lost with when using the recharge skill +- Incorrect calculation for max charges lost with when using the recharge skill - Not getting XP after damaging a monster if it dies from a trap - Fire Arrows causing monsters to stop healing @@ -397,9 +427,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improve Korean localization - Improve Portuguese localization - Improve Romanian localization -- Improve Russian localization ([optional dub](https://github.com/diasurgical/devilutionx-assets/releases/download/v2/ru.mpq) by Stream) +- Improve Russian localization ([optional dub](https://github.com/diasurgical/devilutionx-assets/releases/latest/download/ru.mpq) by Stream) - Improve Spanish localization - + #### Gameplay - Added a stash at Gillian's house @@ -577,7 +607,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - PS4: The games fail to launch without a default ini, please see https://github.com/diasurgical/devilutionX/issues/4443 - Clicking an empty hero slot will crash the game -- Xbo/s: Missing translation files (download and add [devilutionx.mpq](https://github.com/diasurgical/devilutionx-assets/releases/download/v2/devilutionx.mpq)) +- Xbo/s: Missing translation files (download and add [devilutionx.mpq](https://github.com/diasurgical/devilutionx-assets/releases/latest/download/devilutionx.mpq)) ## DevilutionX 1.3.0 @@ -604,7 +634,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added option for pickup sound - Shrine reveals map in a different color than your own exploration - Automap has drop shadow for better contrast in some levels -- Added font with support for Extended Latin, Greek, Coptic, Cyrillic, [Chinese, Japanese, and Korean](https://github.com/diasurgical/devilutionx-assets/releases/download/v1/fonts.mpq) +- Added font with support for Extended Latin, Greek, Coptic, Cyrillic, [Chinese, Japanese, and Korean](https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq) - Item outline color now matches rarity - Use gold color to indicate unique items in stores - Improved XP bar visuals @@ -664,12 +694,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added French - Added German - Added Italian -- Added Korean (requires the [additional fonts](https://github.com/diasurgical/devilutionx-assets/releases/download/v1/fonts.mpq)) -- Added Polish ([optional dub](https://github.com/diasurgical/devilutionx-assets/releases/download/v1/pl.mpq) by professional voice actors) +- Added Korean (requires the [additional fonts](https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq)) +- Added Polish ([optional dub](https://github.com/diasurgical/devilutionx-assets/releases/latest/download/pl.mpq) by professional voice actors) - Added Russian -- Added Simplified Chinese (requires the [additional fonts](https://github.com/diasurgical/devilutionx-assets/releases/download/v1/fonts.mpq)) +- Added Simplified Chinese (requires the [additional fonts](https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq)) - Added Spanish -- Added Traditional Chinese (requires the [additional fonts](https://github.com/diasurgical/devilutionx-assets/releases/download/v1/fonts.mpq)) +- Added Traditional Chinese (requires the [additional fonts](https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq)) ### Bugfixes @@ -724,7 +754,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix name filtering - Correct minor memory leaks - Further reduced memory usage -- Performance improvments +- Performance improvements - Windows: Only show network errors once ### Bugfixes for original Diablo bugs @@ -1352,7 +1382,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### January 14, 2019 - [50% of functions are now binary identical](https://github.com/diasurgical/devilution/milestone/3) to the 1.09b version -- [#456](https://github.com/diasurgical/devilution/pull/456) Assets can now be loaded directly form disk (no need for MPQ-files when modding) +- [#456](https://github.com/diasurgical/devilution/pull/456) Assets can now be loaded directly from disk (no need for MPQ-files when modding) - [#528](https://github.com/diasurgical/devilution/pull/528) Code ported to C (can still be compiled as C++) - [#111](https://github.com/diasurgical/devilution/pull/111) Rich Header no longer contains incorrect sections - [#182](https://github.com/diasurgical/devilution/pull/182) defined a [Code Style](https://github.com/diasurgical/devilution/wiki/Code-style-guide) with accompanying clang-format definition diff --git a/docs/building.md b/docs/building.md index 7ba245fbeed..5800f1ec25d 100644 --- a/docs/building.md +++ b/docs/building.md @@ -496,7 +496,7 @@ emrun index.html * Windows 10 * CMake * Git -* Visual Studio 2022 with the foloowing packages installed: +* Visual Studio 2022 with the following packages installed: * C++ (v143) Universal Windows Platform tools * Windows 11 SDK (10.0.22000.0) * Windows 10 SDK (10.0.18362.0) diff --git a/docs/gh-codespaces-ports-browser.png b/docs/gh-codespaces-ports-browser.png new file mode 100644 index 00000000000..6778f5a287a Binary files /dev/null and b/docs/gh-codespaces-ports-browser.png differ diff --git a/docs/gh-continue-in-codespaces.png b/docs/gh-continue-in-codespaces.png new file mode 100644 index 00000000000..d9594b61f01 Binary files /dev/null and b/docs/gh-continue-in-codespaces.png differ diff --git a/docs/gh-focus-on-terminal.png b/docs/gh-focus-on-terminal.png new file mode 100644 index 00000000000..af4fd0a3a89 Binary files /dev/null and b/docs/gh-focus-on-terminal.png differ diff --git a/docs/gh-open-codespace.png b/docs/gh-open-codespace.png new file mode 100644 index 00000000000..b75f960023c Binary files /dev/null and b/docs/gh-open-codespace.png differ diff --git a/docs/github-codespaces.md b/docs/github-codespaces.md new file mode 100644 index 00000000000..985290724f1 --- /dev/null +++ b/docs/github-codespaces.md @@ -0,0 +1,30 @@ +# Developing with GitHub Codespaces + +A GitHub codespace container with all dependencies and several useful tools preinstalled is configured in the `.devcontainer` directory. + +To develop in a codespace, open the in-browser VS Code editor by either pressing the `.` or going to https://github.dev/diasurgical/devilutionX/. + +Then, focus on the terminal window from the Command Palette menu (F1): + +![screenshot](gh-focus-on-terminal.png) + +Then, click "Continue Working in GitHub Codespaces": + +![screenshot](gh-continue-in-codespaces.png) + +It will take a few minutes to build the container. + +Once installed, the page will load back into the in-browser VS Code editor but now with a +container shell. + +To view the graphical desktop of the container, click Ports > Browser. + +The password is `vscode`. + +![screenshot](gh-codespaces-ports-browser.png) + +For more information about the desktop environment, see https://github.com/devcontainers/features/tree/main/src/desktop-lite + +Once you have a codespace set up, you can reuse it in the future by going to "Your codespaces" on GitHub (https://github.com/codespaces): + +![screenshot](gh-open-codespace.png) diff --git a/docs/installing.md b/docs/installing.md index 4b43037700d..8d1ab1f578c 100644 --- a/docs/installing.md +++ b/docs/installing.md @@ -4,10 +4,10 @@ First, you will need access to the game's MPQ files. - Locate `DIABDAT.MPQ` on your CD, or in the [GoG](https://www.gog.com/game/diablo) installation (or [extract it from the GoG installer](https://github.com/diasurgical/devilutionX/wiki/Extracting-the-.MPQs-from-the-GoG-installer)). - For the Diablo: Hellfire expansion you will also need `hellfire.mpq`, `hfmonk.mpq`, `hfmusic.mpq`, `hfvoice.mpq`. -- DevilutionX comes with [devilutionx.mpq](https://github.com/diasurgical/devilutionx-assets/releases/download/v3/devilutionx.mpq) which is required to run the game properly. -- Chinese, Korean, and Japanese users will also need [fonts.mpq](https://github.com/diasurgical/devilutionx-assets/releases/download/v3/fonts.mpq) or the text will be missing. -- For Polish voice support you need [pl.mpq](https://github.com/diasurgical/devilutionx-assets/releases/download/v2/pl.mpq) -- For Russian voice support you need [ru.mpq](https://github.com/diasurgical/devilutionx-assets/releases/download/v2/ru.mpq) +- DevilutionX comes with [devilutionx.mpq](https://github.com/diasurgical/devilutionx-assets/releases/latest/download/devilutionx.mpq) which is required to run the game properly. +- Chinese, Korean, and Japanese users will also need [fonts.mpq](https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq) or the text will be missing. +- For Polish voice support you need [pl.mpq](https://github.com/diasurgical/devilutionx-assets/releases/latest/download/pl.mpq) +- For Russian voice support you need [ru.mpq](https://github.com/diasurgical/devilutionx-assets/releases/latest/download/ru.mpq) Download the latest [DevilutionX release](https://github.com/diasurgical/devilutionX/releases) for your system (if available) and extract the contents to a location of your choosing, or [build from source](building.md). Then follow the system-specific instructions below. @@ -152,7 +152,7 @@ If you'd like to use this option, scan the QR code below. - Press `View` on DevilutionX and select `Manage game and add-ons` - Go to `File info` and note `FullName` - Copy the MPQ files to `/LOCALFOLDER/*FullName*/LocalState/diasurgical/devilution` using a FTP-client on your PC - + ![image](https://user-images.githubusercontent.com/204594/187104388-fc5648da-5629-4335-ae8b-403600721e2a.png) diff --git a/docs/manual/platforms/3ds.md b/docs/manual/platforms/3ds.md index ac81c20a89f..ff53a7d5db1 100644 --- a/docs/manual/platforms/3ds.md +++ b/docs/manual/platforms/3ds.md @@ -3,7 +3,7 @@ ## Installation Look for the latest release on the -[Releases](https://github.com/diasurgical/devilutionX/releases) page. +[Releases](https://github.com/diasurgical/devilutionX/releases/latest) page. Installation instructions can be found on the [Installing](/docs/installing.md) page. @@ -52,7 +52,7 @@ on another platform with more capable hardware. The 3DS version of the game will attempt to detect the appropriate language based on your 3DS console's language setting. Chinese, Korean, and Japanese users will need to download -[fonts.mpq](https://github.com/diasurgical/devilutionx-assets/releases/download/v1/fonts.mpq) +[fonts.mpq](https://github.com/diasurgical/devilutionx-assets/releases/latest/download/fonts.mpq) or the text will be missing. It is currently not recommended to use the Chinese, Korean, or Japanese translations on old 3DS models. diff --git a/test/.clang-format b/test/.clang-format index 2403752f3a8..024bdf68edc 100644 --- a/test/.clang-format +++ b/test/.clang-format @@ -1,7 +1,9 @@ BasedOnStyle: webkit AlignTrailingComments: true AllowShortBlocksOnASingleLine: true -AllowShortFunctionsOnASingleLine: None +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse PointerAlignment: Right TabWidth: 4 UseTab: ForIndentation diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b84cb2e2222..cf5266c0f04 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -15,6 +15,7 @@ set(tests automap_test codec_test cursor_test + data_file_test dead_test diablo_test drlg_common_test @@ -31,6 +32,7 @@ set(tests missiles_test pack_test path_test + parse_int_test player_test quests_test random_test diff --git a/test/Fixtures.cmake b/test/Fixtures.cmake index 98af1f5a8ab..0fcae3e162a 100644 --- a/test/Fixtures.cmake +++ b/test/Fixtures.cmake @@ -84,6 +84,14 @@ set(devilutionx_fixtures timedemo/WarriorLevel1to2/demo_0.dmo timedemo/WarriorLevel1to2/demo_0_reference_spawn_0.sv timedemo/WarriorLevel1to2/spawn_0.sv + txtdata/cr.tsv + txtdata/crlf.tsv + txtdata/empty.tsv + txtdata/empty_with_utf8_bom.tsv + txtdata/lf.tsv + txtdata/lf_no_trail.tsv + txtdata/sample.tsv + txtdata/utf8_bom.tsv ) foreach(fixture ${devilutionx_fixtures}) diff --git a/test/automap_test.cpp b/test/automap_test.cpp index 2a8c7b2a06b..5c1cebf097f 100644 --- a/test/automap_test.cpp +++ b/test/automap_test.cpp @@ -9,11 +9,11 @@ TEST(Automap, InitAutomap) InitAutomapOnce(); EXPECT_EQ(AutomapActive, false); EXPECT_EQ(AutoMapScale, 50); - EXPECT_EQ(AmLine(64), 32); - EXPECT_EQ(AmLine(32), 16); - EXPECT_EQ(AmLine(16), 8); - EXPECT_EQ(AmLine(8), 4); - EXPECT_EQ(AmLine(4), 2); + EXPECT_EQ(AmLine(AmLineLength::DoubleTile), static_cast(AmLineLength::FullTile)); + EXPECT_EQ(AmLine(AmLineLength::FullAndHalfTile), 6); + EXPECT_EQ(AmLine(AmLineLength::FullTile), static_cast(AmLineLength::HalfTile)); + EXPECT_EQ(AmLine(AmLineLength::HalfTile), static_cast(AmLineLength::QuarterTile)); + EXPECT_EQ(AmLine(AmLineLength::QuarterTile), 1); } TEST(Automap, StartAutomap) @@ -64,50 +64,47 @@ TEST(Automap, AutomapZoomIn) { AutoMapScale = 50; AutomapZoomIn(); - EXPECT_EQ(AutoMapScale, 55); - EXPECT_EQ(AmLine(64), 35); - EXPECT_EQ(AmLine(32), 17); - EXPECT_EQ(AmLine(16), 8); - EXPECT_EQ(AmLine(8), 4); - EXPECT_EQ(AmLine(4), 2); + EXPECT_EQ(AutoMapScale, 75); + EXPECT_EQ(AmLine(AmLineLength::DoubleTile), static_cast(AmLineLength::FullAndHalfTile)); + EXPECT_EQ(AmLine(AmLineLength::FullTile), 6); + EXPECT_EQ(AmLine(AmLineLength::HalfTile), 3); + EXPECT_EQ(AmLine(AmLineLength::QuarterTile), 1); } TEST(Automap, AutomapZoomIn_Max) { - AutoMapScale = 195; + AutoMapScale = 175; + AutoMapScale = 175; AutomapZoomIn(); AutomapZoomIn(); EXPECT_EQ(AutoMapScale, 200); - EXPECT_EQ(AmLine(64), 128); - EXPECT_EQ(AmLine(32), 64); - EXPECT_EQ(AmLine(16), 32); - EXPECT_EQ(AmLine(8), 16); - EXPECT_EQ(AmLine(4), 8); + EXPECT_EQ(AmLine(AmLineLength::DoubleTile), 32); + EXPECT_EQ(AmLine(AmLineLength::FullTile), static_cast(AmLineLength::DoubleTile)); + EXPECT_EQ(AmLine(AmLineLength::HalfTile), static_cast(AmLineLength::FullTile)); + EXPECT_EQ(AmLine(AmLineLength::QuarterTile), static_cast(AmLineLength::HalfTile)); } TEST(Automap, AutomapZoomOut) { AutoMapScale = 200; AutomapZoomOut(); - EXPECT_EQ(AutoMapScale, 195); - EXPECT_EQ(AmLine(64), 124); - EXPECT_EQ(AmLine(32), 62); - EXPECT_EQ(AmLine(16), 31); - EXPECT_EQ(AmLine(8), 15); - EXPECT_EQ(AmLine(4), 7); + EXPECT_EQ(AutoMapScale, 175); + EXPECT_EQ(AmLine(AmLineLength::DoubleTile), 28); + EXPECT_EQ(AmLine(AmLineLength::FullTile), 14); + EXPECT_EQ(AmLine(AmLineLength::HalfTile), 7); + EXPECT_EQ(AmLine(AmLineLength::QuarterTile), 3); } TEST(Automap, AutomapZoomOut_Min) { - AutoMapScale = 55; + AutoMapScale = 50; AutomapZoomOut(); AutomapZoomOut(); - EXPECT_EQ(AutoMapScale, 50); - EXPECT_EQ(AmLine(64), 32); - EXPECT_EQ(AmLine(32), 16); - EXPECT_EQ(AmLine(16), 8); - EXPECT_EQ(AmLine(8), 4); - EXPECT_EQ(AmLine(4), 2); + EXPECT_EQ(AutoMapScale, 25); + EXPECT_EQ(AmLine(AmLineLength::DoubleTile), static_cast(AmLineLength::HalfTile)); + EXPECT_EQ(AmLine(AmLineLength::FullTile), static_cast(AmLineLength::QuarterTile)); + EXPECT_EQ(AmLine(AmLineLength::HalfTile), 1); + EXPECT_EQ(AmLine(AmLineLength::QuarterTile), 0); } TEST(Automap, AutomapZoomReset) @@ -119,9 +116,8 @@ TEST(Automap, AutomapZoomReset) EXPECT_EQ(AutomapOffset.deltaX, 0); EXPECT_EQ(AutomapOffset.deltaY, 0); EXPECT_EQ(AutoMapScale, 50); - EXPECT_EQ(AmLine(64), 32); - EXPECT_EQ(AmLine(32), 16); - EXPECT_EQ(AmLine(16), 8); - EXPECT_EQ(AmLine(8), 4); - EXPECT_EQ(AmLine(4), 2); + EXPECT_EQ(AmLine(AmLineLength::DoubleTile), static_cast(AmLineLength::FullTile)); + EXPECT_EQ(AmLine(AmLineLength::FullTile), static_cast(AmLineLength::HalfTile)); + EXPECT_EQ(AmLine(AmLineLength::HalfTile), static_cast(AmLineLength::QuarterTile)); + EXPECT_EQ(AmLine(AmLineLength::QuarterTile), 1); } diff --git a/test/data_file_test.cpp b/test/data_file_test.cpp new file mode 100644 index 00000000000..38740022246 --- /dev/null +++ b/test/data_file_test.cpp @@ -0,0 +1,532 @@ +#include + +#include "data/file.hpp" +#include "data/parser.hpp" + +#include +#include + +#include "utils/paths.h" + +namespace devilution { +auto LoadDataFile(std::string_view file) +{ + std::string unitTestFolderCompletePath = paths::BasePath() + "/test/fixtures/"; + paths::SetAssetsPath(unitTestFolderCompletePath); + return DataFile::load(file); +} + +void TestFileContents( + const DataFile &dataFile, + std::vector> expectedContent, + GetFieldResult::Status expectedEndOfRecordStatus = GetFieldResult::Status::EndOfRecord, + GetFieldResult::Status expectedEndOfFileStatus = GetFieldResult::Status::EndOfFile) +{ + GetFieldResult result { dataFile.data() }; + const char *end = dataFile.data() + dataFile.size(); + unsigned row = 0; + do { + ASSERT_LT(row, expectedContent.size()) << "Too many records"; + unsigned col = 0; + do { + ASSERT_LT(col, expectedContent[row].size()) << "Too many fields in record " << row; + result = GetNextField(result.next, end); + EXPECT_EQ(result.value, expectedContent[row][col]) << "Unexpected value at record " << row << " and field " << col; + col++; + } while (!result.endOfRecord()); + if (!result.endOfFile()) { + EXPECT_EQ(result.status, expectedEndOfRecordStatus) << "Unexpected status when parsing the end of record " << row; + } + + EXPECT_EQ(col, expectedContent[row].size()) << "Parsing returned fewer fields than expected in record " << row; + row++; + } while (!result.endOfFile()); + EXPECT_EQ(result.status, expectedEndOfFileStatus) << "Unexpected status when parsing the end of the file"; + + EXPECT_EQ(row, expectedContent.size()) << "Parsing returned fewer records than expected"; +} + +TEST(DataFileTest, TryLoadMissingFile) +{ + auto result = LoadDataFile("txtdata\\not_found.tsv"); + EXPECT_FALSE(result.has_value()) << "Trying to load a non-existent file should return an unexpected/error response"; + + EXPECT_EQ(result.error(), DataFile::Error::NotFound) << "The error code should indicate the file was not found"; +} + +TEST(DataFileTest, LoadCRFile) +{ + auto result = LoadDataFile("txtdata\\cr.tsv"); + ASSERT_TRUE(result.has_value()) << "Unable to load cr.tsv"; + + DataFile &dataFile = result.value(); + EXPECT_EQ(dataFile.size(), 33) << "File size should be reported in code units"; + + std::vector> expectedFields { + { "Test", "Empty", "Values" }, + { "", "2", "3" }, + { "1", "2", "" }, + { "1", "", "3" } + }; + + TestFileContents(dataFile, expectedFields, GetFieldResult::Status::BadRecordTerminator, GetFieldResult::Status::FileTruncated); +} + +TEST(DataFileTest, LoadWindowsFile) +{ + auto result = LoadDataFile("txtdata\\crlf.tsv"); + ASSERT_TRUE(result.has_value()) << "Unable to load crlf.tsv"; + + DataFile &dataFile = result.value(); + EXPECT_EQ(dataFile.size(), 37) << "File size should be reported in code units"; + + std::vector> expectedFields { + { "Test", "Empty", "Values" }, + { "", "2", "3" }, + { "1", "2", "" }, + { "1", "", "3" }, + }; + + TestFileContents(dataFile, expectedFields); +} + +TEST(DataFileTest, LoadTypicalFile) +{ + auto result = LoadDataFile("txtdata\\lf.tsv"); + ASSERT_TRUE(result.has_value()) << "Unable to load lf.tsv"; + + DataFile &dataFile = result.value(); + EXPECT_EQ(dataFile.size(), 33) << "File size should be reported in code units"; + + std::vector> expectedFields { + { "Test", "Empty", "Values" }, + { "", "2", "3" }, + { "1", "2", "" }, + { "1", "", "3" }, + }; + + TestFileContents(dataFile, expectedFields); +} + +TEST(DataFileTest, LoadFileWithNoTrailingNewline) +{ + auto result = LoadDataFile("txtdata\\lf_no_trail.tsv"); + ASSERT_TRUE(result.has_value()) << "Unable to load lf_no_trail.tsv"; + + DataFile &dataFile = result.value(); + EXPECT_EQ(dataFile.size(), 32) << "File size should be reported in code units"; + + std::vector> expectedFields { + { "Test", "Empty", "Values" }, + { "", "2", "3" }, + { "1", "2", "" }, + { "1", "", "3" }, + }; + + TestFileContents(dataFile, expectedFields, GetFieldResult::Status::EndOfRecord, GetFieldResult::Status::NoFinalTerminator); +} + +std::string_view mapError(DataFile::Error error) +{ + switch (error) { + case DataFile::Error::NotFound: + return "not found"; + case DataFile::Error::OpenFailed: + return "cannot open"; + case DataFile::Error::BadRead: + return "cannot read contents"; + default: + return "unexpected error"; + } +} + +TEST(DataFileTest, LoadEmptyFile) +{ + auto result = LoadDataFile("txtdata\\empty.tsv"); + if (!result.has_value()) { + FAIL() << "Unable to load empty.tsv, error: " << mapError(result.error()); + } + + DataFile &dataFile = result.value(); + EXPECT_EQ(dataFile.size(), 0) << "File size should be reported in code units"; + + std::vector> expectedFields { + { "" }, + }; + + TestFileContents(dataFile, expectedFields, GetFieldResult::Status::NoFinalTerminator, GetFieldResult::Status::NoFinalTerminator); +} + +TEST(DataFileTest, LoadEmptyFileWithBOM) +{ + auto result = LoadDataFile("txtdata\\empty_with_utf8_bom.tsv"); + if (!result.has_value()) { + FAIL() << "Unable to load empty_with_utf8_bom.tsv, error: " << mapError(result.error()); + } + + DataFile &dataFile = result.value(); + EXPECT_EQ(dataFile.size(), 0) << "Loading a file containing a UTF8 byte order marker should strip that prefix"; + + std::vector> expectedFields { + { "" }, + }; + + TestFileContents(dataFile, expectedFields, GetFieldResult::Status::NoFinalTerminator, GetFieldResult::Status::NoFinalTerminator); +} + +TEST(DataFileTest, LoadUtf8WithBOM) +{ + auto result = LoadDataFile("txtdata\\utf8_bom.tsv"); + ASSERT_TRUE(result.has_value()) << "Unable to load utf8_bom.tsv"; + + DataFile &dataFile = result.value(); + EXPECT_EQ(dataFile.size(), 33) << "Loading a file containing a UTF8 byte order marker should strip that prefix"; + + std::vector> expectedFields { + { "Test", "Empty", "Values" }, + { "", "2", "3" }, + { "1", "2", "" }, + { "1", "", "3" }, + }; + + TestFileContents(dataFile, expectedFields); +} + +TEST(DataFileTest, ParseInt) +{ + auto result = LoadDataFile("txtdata\\sample.tsv"); + if (!result.has_value()) { + FAIL() << "Unable to load sample.tsv, error: " << mapError(result.error()); + } + + DataFile &dataFile = result.value(); + auto unused = dataFile.parseHeader(nullptr, nullptr, [](std::string_view) -> tl::expected { return tl::unexpected { ColumnDefinition::Error::UnknownColumn }; }); + + EXPECT_TRUE(unused.has_value()) << "Should be able to parse and discard the header from the sample.tsv file"; + + for (DataFileRecord record : dataFile) { + auto fieldIt = record.begin(); + auto end = record.end(); + + ASSERT_NE(fieldIt, end) << "sample.tsv must contain at least one field to use as a test value for strings"; + + // First field is a string that doesn't start with a digit or - character + DataFileField field = *fieldIt; + uint8_t shortVal = 5; + auto parseIntResult = field.parseInt(shortVal); + if (parseIntResult.has_value()) { + ADD_FAILURE() << "Parsing a string as an int should not succeed"; + } else { + EXPECT_EQ(parseIntResult.error(), DataFileField::Error::NotANumber) << "Strings are not uint8_t values"; + } + EXPECT_EQ(shortVal, 5) << "Value is not modified when parsing as uint8_t fails due to non-numeric fields"; + EXPECT_EQ(*field, "Sample") << "Should be able to access the field value as a string after failure"; + ++fieldIt; + + ASSERT_NE(fieldIt, end) << "sample.tsv must contain a second field to use as a test value for small ints"; + + // Second field is a number that fits into an uint8_t value + field = *fieldIt; + shortVal = 5; + parseIntResult = field.parseInt(shortVal); + EXPECT_TRUE(parseIntResult.has_value()) << "Expected " << field << " to fit into a uint8_t variable"; + EXPECT_EQ(shortVal, 145) << "Parsing should give the expected base 10 value"; + EXPECT_EQ(*field, "145") << "Should be able to access the field value as a string even after parsing as an int"; + + int longVal = 1; + auto parseFixedResult = field.parseFixed6(longVal); + EXPECT_TRUE(parseFixedResult.has_value()) << "Expected " << field << " to be parsed as a fixed point integer with only the integer part"; + EXPECT_EQ(longVal, 145 << 6) << "Parsing should give the expected fixed point base 10 value"; + + ++fieldIt; + + ASSERT_NE(fieldIt, end) << "sample.tsv must contain a third field to use as a test value for large ints"; + + // Third field is a number too large for a uint8_t but that fits into an int value + field = *fieldIt; + parseIntResult = field.parseInt(shortVal); + if (parseIntResult.has_value()) { + ADD_FAILURE() << "Parsing an int into a short variable should not succeed"; + } else { + EXPECT_EQ(parseIntResult.error(), DataFileField::Error::OutOfRange) << "A value too large to fit into a uint8_t variable should report an error"; + } + EXPECT_EQ(shortVal, 145) << "Value is not modified when parsing as uint8_t fails due to out of range value"; + longVal = 42; + parseIntResult = field.parseInt(longVal); + EXPECT_TRUE(parseIntResult.has_value()) << "Expected " << field << " to fit into an int variable"; + EXPECT_EQ(longVal, 70322) << "Value is expected to be parsed into a larger type after an out of range failure"; + EXPECT_EQ(*field, "70322") << "Should be able to access the field value as a string after parsing as an int"; + ++fieldIt; + + ASSERT_NE(fieldIt, end) << "sample.tsv must contain a fourth field to use as a test value for fields that look like ints"; + + // Fourth field is not an integer, but a value that starts with one or more digits that fit into an uint8_t value + field = *fieldIt; + parseIntResult = field.parseInt(shortVal); + EXPECT_TRUE(parseIntResult.has_value()) << "Expected " << field << " to fit into a uint8_t variable (even though it's not really an int)"; + EXPECT_EQ(shortVal, 6) << "Value is loaded as expected until the first non-digit character"; + EXPECT_EQ(*field, "6.34") << "Should be able to access the field value as a string after parsing as an int"; + int fixedVal = 64; + parseFixedResult = field.parseFixed6(fixedVal); + EXPECT_TRUE(parseFixedResult.has_value()) << "Expected " << field << " to be parsed as a fixed point value"; + // 6.34 is parsed as 384 (6<<6) + 22 (0.34 rounds to 0.34375, 22/64) + EXPECT_EQ(fixedVal, 406) << "Value is loaded as a fixed point number"; + + uint8_t shortFixedVal = 32; + parseFixedResult = field.parseFixed6(shortFixedVal); + EXPECT_FALSE(parseFixedResult.has_value()) << "Expected " << field << " to fail to parse into a 2.6 fixed point variable"; + EXPECT_EQ(parseFixedResult.error(), DataFileField::Error::OutOfRange) << "A value too large to fit into a 2 bit integer part should report an error"; + EXPECT_EQ(shortFixedVal, 32) << "The variable should not be modified when parsing fails"; + + ++fieldIt; + + ASSERT_NE(fieldIt, end) << "sample.tsv must contain a fifth field to use as a test value for fixed point overflow"; + + field = *fieldIt; + parseFixedResult = field.parseFixed6(shortFixedVal); + EXPECT_FALSE(parseFixedResult.has_value()) << "Expected " << field << " to fail to parse into a 2.6 fixed point variable"; + EXPECT_EQ(parseFixedResult.error(), DataFileField::Error::OutOfRange) << "A value that after rounding is too large to fit into a 2 bit integer part should report an error"; + EXPECT_EQ(shortFixedVal, 32) << "The variable should not be modified when parsing fails"; + } +} + +TEST(DataFileTest, IterateOverRecords) +{ + auto result = LoadDataFile("txtdata\\lf.tsv"); + ASSERT_TRUE(result.has_value()) << "Unable to load lf.tsv"; + + DataFile &dataFile = result.value(); + + std::vector> expectedFields { + { "Test", "Empty", "Values" }, + { "", "2", "3" }, + { "1", "2", "" }, + { "1", "", "3" }, + }; + + unsigned row = 0; + for (DataFileRecord record : dataFile) { + ASSERT_LT(row, expectedFields.size()) << "Too many records"; + unsigned col = 0; + for (DataFileField field : record) { + if (col < expectedFields[row].size()) + EXPECT_EQ(*field, expectedFields[row][col]) << "Unexpected value at record " << row << " and field " << col; + else + ADD_FAILURE() << "Extra value '" << field << "' in record " << row << " at field " << col; + col++; + } + EXPECT_GE(col, expectedFields[row].size()) << "Parsing returned fewer fields than expected in record " << row; + row++; + } + EXPECT_EQ(row, expectedFields.size()) << "Parsing returned fewer records than expected"; +} + +TEST(DataFileTest, ParseHeaderThenIterateOverRecords) +{ + auto result = LoadDataFile("txtdata\\lf.tsv"); + ASSERT_TRUE(result.has_value()) << "Unable to load lf.tsv"; + + DataFile &dataFile = result.value(); + + std::vector> expectedFields { + { "", "2", "3" }, + { "1", "2", "" }, + { "1", "", "3" }, + }; + + auto parseHeaderResult = dataFile.parseHeader(nullptr, nullptr, [](std::string_view) -> tl::expected { return tl::unexpected { ColumnDefinition::Error::UnknownColumn }; }); + EXPECT_TRUE(parseHeaderResult.has_value()) << "Expected to be able to parse and discard the header record"; + + unsigned row = 0; + for (DataFileRecord record : dataFile) { + ASSERT_LT(row, expectedFields.size()) << "Too many records"; + EXPECT_EQ(row + 1, record.row()) << "DataFileRecord (through iterator) should report a 1-indexed row after parsing the header record"; + unsigned col = 0; + for (DataFileField field : record) { + EXPECT_EQ(record.row(), field.row()) << "Field should report the same row as the current DataFileRecord"; + EXPECT_EQ(col, field.column()) << "Field (through iterator) should report a 0-indexed column"; + if (col < expectedFields[row].size()) + EXPECT_EQ(*field, expectedFields[row][col]) << "Unexpected value at record " << row << " and field " << col; + else + ADD_FAILURE() << "Extra value '" << field << "' in record " << row << " at field " << col; + col++; + } + EXPECT_GE(col, expectedFields[row].size()) << "Parsing returned fewer fields than expected in record " << row; + row++; + } + EXPECT_EQ(row, expectedFields.size()) << "Parsing returned fewer records than expected"; +} + +TEST(DataFileTest, DiscardAllAfterFirstField) +{ + auto loadDataResult = LoadDataFile("txtdata\\lf.tsv"); + ASSERT_TRUE(loadDataResult.has_value()) << "Unable to load lf.tsv"; + + DataFile &dataFile = loadDataResult.value(); + + std::vector> expectedFields { + { "Test", "Empty", "Values" }, + { "", "2", "3" }, + { "1", "2", "" }, + { "1", "", "3" }, + }; + + GetFieldResult result { dataFile.data() }; + const char *end = dataFile.data() + dataFile.size(); + unsigned row = 0; + do { + ASSERT_LT(row, expectedFields.size()) << "Too many records"; + result = GetNextField(result.next, end); + EXPECT_EQ(result.value, expectedFields[row][0]) << "Unexpected first value at record " << row; + + if (result.endOfRecord() && !result.endOfFile()) + ADD_FAILURE() << "Parsing returned fewer fields than expected in record " << row; + else + result = DiscardRemainingFields(result.next, end); + row++; + } while (!result.endOfFile()); + + EXPECT_EQ(row, expectedFields.size()) << "Parsing returned fewer records than expected"; +} + +TEST(DataFileTest, DiscardAllAfterFirstFieldIterator) +{ + auto result = LoadDataFile("txtdata\\lf.tsv"); + ASSERT_TRUE(result.has_value()) << "Unable to load lf.tsv"; + + DataFile &dataFile = result.value(); + + std::vector> expectedFields { + { "Test", "Empty", "Values" }, + { "", "2", "3" }, + { "1", "2", "" }, + { "1", "", "3" }, + }; + + unsigned row = 0; + for (DataFileRecord record : dataFile) { + ASSERT_LT(row, expectedFields.size()) << "Too many records"; + for (DataFileField field : record) { + EXPECT_EQ(*field, expectedFields[row][0]) << "Field with value " << field << " does not match the expected value for record " << row; + break; + } + row++; + } +} + +TEST(DataFileTest, DiscardAllUpToLastField) +{ + auto loadDataResult = LoadDataFile("txtdata\\lf.tsv"); + ASSERT_TRUE(loadDataResult.has_value()) << "Unable to load lf.tsv"; + + DataFile &dataFile = loadDataResult.value(); + + std::vector> expectedFields { + { "Test", "Empty", "Values" }, + { "", "2", "3" }, + { "1", "2", "" }, + { "1", "", "3" }, + }; + + GetFieldResult result { dataFile.data() }; + const char *const end = dataFile.data() + dataFile.size(); + unsigned row = 0; + do { + ASSERT_LT(row, expectedFields.size()) << "Too many records"; + result = DiscardMultipleFields(result.next, end, 2); + if (row < expectedFields.size()) { + EXPECT_FALSE(result.endOfRecord()) << "Parsing returned fewer fields than expected in record " << row; + if (!result.endOfRecord()) + result = GetNextField(result.next, end); + } + EXPECT_EQ(result.value, expectedFields[row][2]) << "Unexpected last value at record " << row; + + if (!result.endOfRecord()) { + ADD_FAILURE() << "Parsing returned fewer fields than expected in record " << row; + result = DiscardRemainingFields(result.next, end); + } + row++; + } while (!result.endOfFile()); + + EXPECT_EQ(row, expectedFields.size()) << "Parsing returned fewer records than expected"; +} + +TEST(DataFileTest, SkipFieldIterator) +{ + auto loadDataResult = LoadDataFile("txtdata\\lf.tsv"); + ASSERT_TRUE(loadDataResult.has_value()) << "Unable to load lf.tsv"; + + DataFile &dataFile = loadDataResult.value(); + + std::vector> expectedFields { + { "Test", "Empty", "Values" }, + { "", "2", "3" }, + { "1", "2", "" }, + { "1", "", "3" }, + }; + + unsigned row = 0; + for (DataFileRecord record : dataFile) { + ASSERT_LT(row, expectedFields.size()) << "Too many records"; + auto fieldIt = record.begin(); + auto endField = record.end(); + fieldIt += 0; + EXPECT_EQ((*fieldIt).value(), expectedFields[row][0]) << "Advancing a field iterator by 0 should not discard any values"; + + fieldIt += 2; + if (row < expectedFields.size()) { + if (fieldIt == endField) { + ADD_FAILURE() << "Parsing returned fewer fields than expected in record " << row; + } else { + EXPECT_EQ((*fieldIt).value(), expectedFields[row][2]) << "Unexpected last value at record " << row; + ++fieldIt; + } + } + EXPECT_EQ(fieldIt, endField) << "Parsing returned more fields than expected in record " << row; + EXPECT_EQ(fieldIt.column(), expectedFields[row].size() - 1) << "Field iterator should report the index of the last column"; + + ++row; + } + + EXPECT_EQ(row, expectedFields.size()) << "Parsing returned fewer records than expected"; +} + +TEST(DataFileTest, SkipRowIterator) +{ + auto loadDataResult = LoadDataFile("txtdata\\lf.tsv"); + ASSERT_TRUE(loadDataResult.has_value()) << "Unable to load lf.tsv"; + + DataFile &dataFile = loadDataResult.value(); + + std::vector> expectedFields { + { "Test", "Empty", "Values" }, + { "", "2", "3" }, + { "1", "2", "" }, + { "1", "", "3" }, + }; + + auto recordIt = dataFile.begin(); + auto endRecord = dataFile.end(); + recordIt += 0; + EXPECT_EQ((*(*recordIt).begin()).value(), expectedFields[0][0]) << "Advancing a record iterator by 0 should not discard any values"; + recordIt += 2; + unsigned row = 2; + while (recordIt != endRecord) { + ASSERT_LT(row, expectedFields.size()) << "Too many records"; + unsigned col = 0; + for (DataFileField field : *recordIt) { + if (col < expectedFields[row].size()) + EXPECT_EQ(*field, expectedFields[row][col]) << "Unexpected value at record " << row << " and field " << col; + else + ADD_FAILURE() << "Extra value '" << field << "' in record " << row << " at field " << col; + col++; + } + EXPECT_GE(col, expectedFields[row].size()) << "Parsing returned fewer fields than expected in record " << row; + ++row; + ++recordIt; + } + + EXPECT_EQ(row, expectedFields.size()) << "Parsing returned fewer records than expected"; +} + +} // namespace devilution diff --git a/test/drlg_test.hpp b/test/drlg_test.hpp index 1612c86673f..9906116c4ff 100644 --- a/test/drlg_test.hpp +++ b/test/drlg_test.hpp @@ -48,7 +48,7 @@ void LoadExpectedLevelData(const char *fixture) dunPath.append(fixture); DunData = LoadFileInMem(dunPath.c_str()); ASSERT_NE(DunData, nullptr) << "Unable to load test fixture " << dunPath; - ASSERT_EQ(Size(DMAXX, DMAXY), Size(SDL_SwapLE16(DunData[0]), SDL_SwapLE16(DunData[1]))); + ASSERT_EQ(WorldTileSize(DMAXX, DMAXY), GetDunSize(DunData.get())); } void TestInitGame(bool fullQuests = true, bool originalCathedral = true) diff --git a/test/fixtures/timedemo/WarriorLevel1to2/demo_0.dmo b/test/fixtures/timedemo/WarriorLevel1to2/demo_0.dmo index a7c10bc76ed..a269c07d945 100644 Binary files a/test/fixtures/timedemo/WarriorLevel1to2/demo_0.dmo and b/test/fixtures/timedemo/WarriorLevel1to2/demo_0.dmo differ diff --git a/test/fixtures/timedemo/WarriorLevel1to2/demo_0_reference_spawn_0.sv b/test/fixtures/timedemo/WarriorLevel1to2/demo_0_reference_spawn_0.sv index 2d3029cf557..ab92dc324ab 100644 Binary files a/test/fixtures/timedemo/WarriorLevel1to2/demo_0_reference_spawn_0.sv and b/test/fixtures/timedemo/WarriorLevel1to2/demo_0_reference_spawn_0.sv differ diff --git a/test/fixtures/txtdata/cr.tsv b/test/fixtures/txtdata/cr.tsv new file mode 100644 index 00000000000..8d21d964edb --- /dev/null +++ b/test/fixtures/txtdata/cr.tsv @@ -0,0 +1 @@ +Test Empty Values 2 3 1 2 1 3 \ No newline at end of file diff --git a/test/fixtures/txtdata/crlf.tsv b/test/fixtures/txtdata/crlf.tsv new file mode 100644 index 00000000000..3b844d7d208 --- /dev/null +++ b/test/fixtures/txtdata/crlf.tsv @@ -0,0 +1,4 @@ +Test Empty Values + 2 3 +1 2 +1 3 diff --git a/test/fixtures/txtdata/empty.tsv b/test/fixtures/txtdata/empty.tsv new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/fixtures/txtdata/empty_with_utf8_bom.tsv b/test/fixtures/txtdata/empty_with_utf8_bom.tsv new file mode 100644 index 00000000000..5f282702bb0 --- /dev/null +++ b/test/fixtures/txtdata/empty_with_utf8_bom.tsv @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/fixtures/txtdata/lf.tsv b/test/fixtures/txtdata/lf.tsv new file mode 100644 index 00000000000..fea9ce8b508 --- /dev/null +++ b/test/fixtures/txtdata/lf.tsv @@ -0,0 +1,4 @@ +Test Empty Values + 2 3 +1 2 +1 3 diff --git a/test/fixtures/txtdata/lf_no_trail.tsv b/test/fixtures/txtdata/lf_no_trail.tsv new file mode 100644 index 00000000000..303e16a0cd5 --- /dev/null +++ b/test/fixtures/txtdata/lf_no_trail.tsv @@ -0,0 +1,4 @@ +Test Empty Values + 2 3 +1 2 +1 3 \ No newline at end of file diff --git a/test/fixtures/txtdata/sample.tsv b/test/fixtures/txtdata/sample.tsv new file mode 100644 index 00000000000..a56e0e15741 --- /dev/null +++ b/test/fixtures/txtdata/sample.tsv @@ -0,0 +1,2 @@ +String Byte Int Float FloatOverflow +Sample 145 70322 6.34 3.999 diff --git a/test/fixtures/txtdata/utf8_bom.tsv b/test/fixtures/txtdata/utf8_bom.tsv new file mode 100644 index 00000000000..f8b5b137407 --- /dev/null +++ b/test/fixtures/txtdata/utf8_bom.tsv @@ -0,0 +1,4 @@ +Test Empty Values + 2 3 +1 2 +1 3 diff --git a/test/inv_test.cpp b/test/inv_test.cpp index cca25eae544..29a9ba28b85 100644 --- a/test/inv_test.cpp +++ b/test/inv_test.cpp @@ -15,6 +15,12 @@ class InvTest : public ::testing::Test { Players.resize(1); MyPlayer = &Players[0]; } + + static void SetUpTestSuite() + { + LoadSpellData(); + LoadItemData(); + } }; /* Set up a given item as a spell scroll, allowing for its usage. */ diff --git a/test/lighting_test.cpp b/test/lighting_test.cpp index a3b5ddaae4d..e3995be6221 100644 --- a/test/lighting_test.cpp +++ b/test/lighting_test.cpp @@ -1,3 +1,5 @@ +#include + #include #include "control.h" @@ -25,7 +27,7 @@ TEST(Lighting, CrawlTables) for (int j = -MaxCrawlRadius; j <= MaxCrawlRadius; j++) { if (added[i + 20][j + 20]) continue; - if (abs(i) == MaxCrawlRadius && abs(j) == MaxCrawlRadius) + if (std::abs(i) == MaxCrawlRadius && std::abs(j) == MaxCrawlRadius) continue; // Limit of the crawl table rage EXPECT_EQ(false, true) << "while checking location " << i << ":" << j; } diff --git a/test/missiles_test.cpp b/test/missiles_test.cpp index e4ef2ac35fb..d518e11fc5c 100644 --- a/test/missiles_test.cpp +++ b/test/missiles_test.cpp @@ -20,7 +20,7 @@ void TestArrowRotatesUniformly(Missile &missile, int startingFrame, int leftFram observed[missile._miAnimFrame]++; } - EXPECT_THAT(observed, UnorderedElementsAre(Pair(leftFrame, AllOf(Gt(30), Lt(70))), Pair(rightFrame, AllOf(Gt(30), Lt(70))))) << "Arrows should rotate either direction roughly 50% of the time"; + EXPECT_THAT(observed, UnorderedElementsAre(Pair(leftFrame, AllOf(Gt(30U), Lt(70U))), Pair(rightFrame, AllOf(Gt(30U), Lt(70U))))) << "Arrows should rotate either direction roughly 50% of the time"; } void TestAnimatedMissileRotatesUniformly(Missile &missile, int startingDir, int leftDir, int rightDir) @@ -32,7 +32,7 @@ void TestAnimatedMissileRotatesUniformly(Missile &missile, int startingDir, int observed[missile._mimfnum]++; } - EXPECT_THAT(observed, UnorderedElementsAre(Pair(leftDir, AllOf(Gt(30), Lt(70))), Pair(rightDir, AllOf(Gt(30), Lt(70))))) << "Animated missiles should rotate either direction roughly 50% of the time"; + EXPECT_THAT(observed, UnorderedElementsAre(Pair(leftDir, AllOf(Gt(30U), Lt(70U))), Pair(rightDir, AllOf(Gt(30U), Lt(70U))))) << "Animated missiles should rotate either direction roughly 50% of the time"; } TEST(Missiles, RotateBlockedMissileArrow) @@ -41,10 +41,11 @@ TEST(Missiles, RotateBlockedMissileArrow) MyPlayerId = 0; MyPlayer = &Players[MyPlayerId]; *MyPlayer = {}; + LoadMissileData(); Player &player = Players[0]; // missile can be a copy or a reference, there's no nullptr check and the functions that use it don't expect the instance to be part of a global structure so it doesn't really matter for this use. - Missile missile = *AddMissile({ 0, 0 }, { 0, 0 }, Direction::South, MissileID::Arrow, TARGET_MONSTERS, player.getId(), 0, 0); + Missile missile = *AddMissile({ 0, 0 }, { 0, 0 }, Direction::South, MissileID::Arrow, TARGET_MONSTERS, player, 0, 0); // Arrows have a hardcoded frame count and use 1-indexed sprites EXPECT_EQ(missile._miAnimFrame, 1); @@ -54,7 +55,7 @@ TEST(Missiles, RotateBlockedMissileArrow) TestArrowRotatesUniformly(missile, 16, 15, 1); // All other missiles use the number of 0-indexed sprites defined in MissileSpriteData - missile = *AddMissile({ 0, 0 }, { 0, 0 }, Direction::South, MissileID::Firebolt, TARGET_MONSTERS, player.getId(), 0, 0); + missile = *AddMissile({ 0, 0 }, { 0, 0 }, Direction::South, MissileID::Firebolt, TARGET_MONSTERS, player, 0, 0); EXPECT_EQ(missile._mimfnum, 0); TestAnimatedMissileRotatesUniformly(missile, 5, 4, 6); TestAnimatedMissileRotatesUniformly(missile, 0, 15, 1); diff --git a/test/pack_test.cpp b/test/pack_test.cpp index 31fa8d52269..1601fcb92a2 100644 --- a/test/pack_test.cpp +++ b/test/pack_test.cpp @@ -2,7 +2,9 @@ #include +#include "monstdat.h" #include "pack.h" +#include "playerdat.hpp" #include "utils/paths.h" namespace devilution { @@ -409,6 +411,12 @@ class PackTest : public ::testing::Test { Players.resize(1); MyPlayer = &Players[0]; } + + static void SetUpTestSuite() + { + LoadSpellData(); + LoadItemData(); + } }; TEST_F(PackTest, UnPackItem_diablo) @@ -870,6 +878,7 @@ class NetPackTest : public ::testing::Test { { Players.resize(2); MyPlayer = &Players[0]; + gbIsMultiplayer = true; PlayerPack testPack { 0, 0, -1, 9, 0, 2, 61, 24, 0, 0, "MP-Warrior", 0, 120, 25, 60, 60, 37, 0, 85670061, 3921, 13568, 13568, 3904, 3904, @@ -942,6 +951,14 @@ class NetPackTest : public ::testing::Test { SwapLE(testPack); UnPackPlayer(testPack, *MyPlayer); } + + static void SetUpTestSuite() + { + LoadSpellData(); + LoadPlayerDataFiles(); + LoadMonsterData(); + LoadItemData(); + } }; bool TestNetPackValidation() @@ -958,8 +975,10 @@ TEST_F(NetPackTest, UnPackNetPlayer_valid) TEST_F(NetPackTest, UnPackNetPlayer_invalid_class) { - MyPlayer->_pClass = static_cast(-1); - ASSERT_FALSE(TestNetPackValidation()); + PlayerNetPack packed; + PackNetPlayer(packed, *MyPlayer); + packed.pClass = std::numeric_limits::max(); + ASSERT_FALSE(UnPackNetPlayer(packed, Players[1])); } TEST_F(NetPackTest, UnPackNetPlayer_invalid_oob) @@ -980,15 +999,6 @@ TEST_F(NetPackTest, UnPackNetPlayer_invalid_plrlevel) ASSERT_FALSE(TestNetPackValidation()); } -TEST_F(NetPackTest, UnPackNetPlayer_invalid_pLevel) -{ - MyPlayer->_pLevel = 0; - ASSERT_FALSE(TestNetPackValidation()); - - MyPlayer->_pLevel = MaxCharacterLevel + 1; - ASSERT_FALSE(TestNetPackValidation()); -} - TEST_F(NetPackTest, UnPackNetPlayer_invalid_hpBase) { MyPlayer->_pHPBase = -64; @@ -1030,7 +1040,7 @@ TEST_F(NetPackTest, UnPackNetPlayer_invalid_baseVit) TEST_F(NetPackTest, UnPackNetPlayer_invalid_numInv) { - MyPlayer->_pNumInv = InventoryGridCells; + MyPlayer->_pNumInv = InventoryGridCells + 1; ASSERT_FALSE(TestNetPackValidation()); } @@ -1090,8 +1100,10 @@ TEST_F(NetPackTest, UnPackNetPlayer_invalid_damageMod) TEST_F(NetPackTest, UnPackNetPlayer_invalid_baseToBlk) { - MyPlayer->_pBaseToBlk++; - ASSERT_FALSE(TestNetPackValidation()); + PlayerNetPack packed; + PackNetPlayer(packed, *MyPlayer); + packed.pBaseToBlk++; + ASSERT_FALSE(UnPackNetPlayer(packed, Players[1])); } TEST_F(NetPackTest, UnPackNetPlayer_invalid_iMinDam) @@ -1281,7 +1293,7 @@ TEST_F(NetPackTest, UnPackNetPlayer_invalid_pregenItemFlags) for (Item &item : MyPlayer->InvList) { if (item.isEmpty()) continue; - if (item.IDidx == IDI_EAR) + if (IsAnyOf(item.IDidx, IDI_GOLD, IDI_EAR)) continue; uint16_t createInfo = item._iCreateInfo; item._iCreateInfo |= CF_PREGEN; @@ -1299,7 +1311,7 @@ TEST_F(NetPackTest, UnPackNetPlayer_invalid_usefulItemFlags) for (Item &item : MyPlayer->InvList) { if (item.isEmpty()) continue; - if (item.IDidx == IDI_EAR) + if (IsAnyOf(item.IDidx, IDI_GOLD, IDI_EAR)) continue; if ((item._iCreateInfo & CF_USEFUL) != CF_USEFUL) continue; @@ -1319,7 +1331,7 @@ TEST_F(NetPackTest, UnPackNetPlayer_invalid_townItemFlags) for (Item &item : MyPlayer->InvList) { if (item.isEmpty()) continue; - if (item.IDidx == IDI_EAR) + if (IsAnyOf(item.IDidx, IDI_GOLD, IDI_EAR)) continue; if ((item._iCreateInfo & CF_TOWN) == 0) continue; @@ -1340,14 +1352,14 @@ TEST_F(NetPackTest, UnPackNetPlayer_invalid_townItemLevel) for (Item &item : MyPlayer->InvBody) { if (item.isEmpty()) continue; - if (item.IDidx == IDI_EAR) + if (IsAnyOf(item.IDidx, IDI_GOLD, IDI_EAR)) continue; if ((item._iCreateInfo & CF_TOWN) == 0) continue; uint16_t createInfo = item._iCreateInfo; bool boyItem = (item._iCreateInfo & CF_BOY) != 0; item._iCreateInfo &= ~CF_LEVEL; - item._iCreateInfo |= boyItem ? MaxCharacterLevel + 1 : 31; + item._iCreateInfo |= boyItem ? MyPlayer->getMaxCharacterLevel() + 1 : 31; ASSERT_FALSE(TestNetPackValidation()); item._iCreateInfo = createInfo; @@ -1365,7 +1377,7 @@ TEST_F(NetPackTest, UnPackNetPlayer_invalid_uniqueMonsterItemLevel) for (Item &item : MyPlayer->InvList) { if (item.isEmpty()) continue; - if (item.IDidx == IDI_EAR) + if (IsAnyOf(item.IDidx, IDI_GOLD, IDI_EAR)) continue; if ((item._iCreateInfo & CF_USEFUL) != CF_UPER15) continue; @@ -1386,7 +1398,7 @@ TEST_F(NetPackTest, UnPackNetPlayer_invalid_monsterItemLevel) for (Item &item : MyPlayer->InvBody) { if (item.isEmpty()) continue; - if (item.IDidx == IDI_EAR) + if (IsAnyOf(item.IDidx, IDI_GOLD, IDI_EAR)) continue; if ((item._iCreateInfo & CF_TOWN) != 0) continue; diff --git a/test/parse_int_test.cpp b/test/parse_int_test.cpp new file mode 100644 index 00000000000..1a49b13c670 --- /dev/null +++ b/test/parse_int_test.cpp @@ -0,0 +1,151 @@ +#include + +#include "utils/parse_int.hpp" + +namespace devilution { +TEST(ParseIntTest, ParseInt) +{ + ParseIntResult result = ParseInt(""); + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ParseIntError::ParseError); + + result = ParseInt("abcd"); + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ParseIntError::ParseError); + + result = ParseInt("12"); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result.value(), 12); + + result = ParseInt(("99999999"), -5, 100); + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ParseIntError::OutOfRange); + + ParseIntResult shortResult = ParseInt(("99999999")); + ASSERT_FALSE(shortResult.has_value()); + EXPECT_EQ(shortResult.error(), ParseIntError::OutOfRange); +} + +TEST(ParseIntTest, ParseFixed6Fraction) +{ + EXPECT_EQ(ParseFixed6Fraction(""), 0); + EXPECT_EQ(ParseFixed6Fraction("0"), 0); + EXPECT_EQ(ParseFixed6Fraction("00781249"), 0); + EXPECT_EQ(ParseFixed6Fraction("0078125"), 1); + EXPECT_EQ(ParseFixed6Fraction("015625"), 1); + EXPECT_EQ(ParseFixed6Fraction("03125"), 2); + EXPECT_EQ(ParseFixed6Fraction("046875"), 3); + EXPECT_EQ(ParseFixed6Fraction("0625"), 4); + EXPECT_EQ(ParseFixed6Fraction("078125"), 5); + EXPECT_EQ(ParseFixed6Fraction("09375"), 6); + EXPECT_EQ(ParseFixed6Fraction("109375"), 7); + EXPECT_EQ(ParseFixed6Fraction("125"), 8); + EXPECT_EQ(ParseFixed6Fraction("140625"), 9); + EXPECT_EQ(ParseFixed6Fraction("15625"), 10); + EXPECT_EQ(ParseFixed6Fraction("171875"), 11); + EXPECT_EQ(ParseFixed6Fraction("1875"), 12); + EXPECT_EQ(ParseFixed6Fraction("203125"), 13); + EXPECT_EQ(ParseFixed6Fraction("21875"), 14); + EXPECT_EQ(ParseFixed6Fraction("234375"), 15); + EXPECT_EQ(ParseFixed6Fraction("25"), 16); + EXPECT_EQ(ParseFixed6Fraction("265625"), 17); + EXPECT_EQ(ParseFixed6Fraction("28125"), 18); + EXPECT_EQ(ParseFixed6Fraction("296875"), 19); + EXPECT_EQ(ParseFixed6Fraction("3125"), 20); + EXPECT_EQ(ParseFixed6Fraction("328125"), 21); + EXPECT_EQ(ParseFixed6Fraction("34375"), 22); + EXPECT_EQ(ParseFixed6Fraction("359375"), 23); + EXPECT_EQ(ParseFixed6Fraction("375"), 24); + EXPECT_EQ(ParseFixed6Fraction("390625"), 25); + EXPECT_EQ(ParseFixed6Fraction("40625"), 26); + EXPECT_EQ(ParseFixed6Fraction("421875"), 27); + EXPECT_EQ(ParseFixed6Fraction("4375"), 28); + EXPECT_EQ(ParseFixed6Fraction("453125"), 29); + EXPECT_EQ(ParseFixed6Fraction("46875"), 30); + EXPECT_EQ(ParseFixed6Fraction("484375"), 31); + EXPECT_EQ(ParseFixed6Fraction("5"), 32); + EXPECT_EQ(ParseFixed6Fraction("515625"), 33); + EXPECT_EQ(ParseFixed6Fraction("53125"), 34); + EXPECT_EQ(ParseFixed6Fraction("546875"), 35); + EXPECT_EQ(ParseFixed6Fraction("5625"), 36); + EXPECT_EQ(ParseFixed6Fraction("578125"), 37); + EXPECT_EQ(ParseFixed6Fraction("59375"), 38); + EXPECT_EQ(ParseFixed6Fraction("609375"), 39); + EXPECT_EQ(ParseFixed6Fraction("625"), 40); + EXPECT_EQ(ParseFixed6Fraction("640625"), 41); + EXPECT_EQ(ParseFixed6Fraction("65625"), 42); + EXPECT_EQ(ParseFixed6Fraction("671875"), 43); + EXPECT_EQ(ParseFixed6Fraction("6875"), 44); + EXPECT_EQ(ParseFixed6Fraction("703125"), 45); + EXPECT_EQ(ParseFixed6Fraction("71875"), 46); + EXPECT_EQ(ParseFixed6Fraction("734375"), 47); + EXPECT_EQ(ParseFixed6Fraction("75"), 48); + EXPECT_EQ(ParseFixed6Fraction("765625"), 49); + EXPECT_EQ(ParseFixed6Fraction("78125"), 50); + EXPECT_EQ(ParseFixed6Fraction("796875"), 51); + EXPECT_EQ(ParseFixed6Fraction("8125"), 52); + EXPECT_EQ(ParseFixed6Fraction("828125"), 53); + EXPECT_EQ(ParseFixed6Fraction("84375"), 54); + EXPECT_EQ(ParseFixed6Fraction("859375"), 55); + EXPECT_EQ(ParseFixed6Fraction("875"), 56); + EXPECT_EQ(ParseFixed6Fraction("890625"), 57); + EXPECT_EQ(ParseFixed6Fraction("90625"), 58); + EXPECT_EQ(ParseFixed6Fraction("921875"), 59); + EXPECT_EQ(ParseFixed6Fraction("9375"), 60); + EXPECT_EQ(ParseFixed6Fraction("953125"), 61); + EXPECT_EQ(ParseFixed6Fraction("96875"), 62); + EXPECT_EQ(ParseFixed6Fraction("984375"), 63); + EXPECT_EQ(ParseFixed6Fraction("99218749"), 63); + EXPECT_EQ(ParseFixed6Fraction("9921875"), 64); +} + +TEST(ParseInt, ParseFixed6) +{ + ParseIntResult result = ParseFixed6(""); + ASSERT_FALSE(result.has_value()) << "Empty strings are not valid fixed point values."; + EXPECT_EQ(result.error(), ParseIntError::ParseError) << "ParseFixed6 should give a ParseError code when parsing an empty string."; + + result = ParseFixed6("abcd"); + ASSERT_FALSE(result.has_value()) << "Non-numeric strings should not be parsed as a fixed-point value."; + EXPECT_EQ(result.error(), ParseIntError::ParseError) << "ParseFixed6 should give a ParseError code when parsing a non-numeric string."; + + result = ParseFixed6("."); + ASSERT_FALSE(result.has_value()) << "To match std::from_chars ParseFixed6 should fail to parse a decimal string with no digits."; + EXPECT_EQ(result.error(), ParseIntError::ParseError) << "Decimal strings with no digits are reported as ParseError codes."; + + result = ParseFixed6("1."); + ASSERT_TRUE(result.has_value()) << "A trailing decimal point is permitted for fixed point values"; + EXPECT_EQ(result.value(), 1 << 6); + + result = ParseFixed6(".5"); + ASSERT_TRUE(result.has_value()) << "A fixed point value with no integer part is accepted"; + EXPECT_EQ(result.value(), 32); + + std::string_view badString { "-." }; + const char *endOfParse = nullptr; + result = ParseFixed6(badString, &endOfParse); + ASSERT_FALSE(result.has_value()) << "To match std::from_chars ParseFixed6 should fail to parse a decimal string with no digits, even if it starts with a minus sign."; + EXPECT_EQ(result.error(), ParseIntError::ParseError) << "Decimal strings with no digits are reported as ParseError codes."; + EXPECT_EQ(endOfParse, badString.data()) << "Failed fixed point parsing should set the end pointer to match the start of the string even though it read multiple characters"; + + result = ParseFixed6("-1."); + ASSERT_TRUE(result.has_value()) << "negative fixed point values are handled when reading into signed types"; + EXPECT_EQ(result.value(), -1 << 6); + + result = ParseFixed6("-1.25"); + ASSERT_TRUE(result.has_value()) << "negative fixed point values are handled when reading into signed types"; + EXPECT_EQ(result.value(), -((1 << 6) + 16)) << "and the fraction part is combined with the integer part respecting the sign"; + + result = ParseFixed6("-.25"); + ASSERT_TRUE(result.has_value()) << "negative fixed point values with no integer digits are handled when reading into signed types"; + EXPECT_EQ(result.value(), -16) << "and the fraction part is used respecting the sign"; + + result = ParseFixed6("-0.25"); + ASSERT_TRUE(result.has_value()) << "negative fixed point values with an explicit -0 integer part are handled when reading into signed types"; + EXPECT_EQ(result.value(), -16) << "and the fraction part is used respecting the sign"; + + ParseIntResult unsignedResult = ParseFixed6("-1."); + ASSERT_FALSE(unsignedResult.has_value()) << "negative fixed point values are not permitted when reading into unsigned types"; + EXPECT_EQ(unsignedResult.error(), ParseIntError::ParseError) << "Attempting to parse a negative value into an unsigned type is a ParseError, not an OutOfRange value"; +} +} // namespace devilution diff --git a/test/player_test.cpp b/test/player_test.cpp index a92baa7291d..a423b96988f 100644 --- a/test/player_test.cpp +++ b/test/player_test.cpp @@ -2,6 +2,8 @@ #include +#include "playerdat.hpp" + using namespace devilution; namespace devilution { @@ -14,7 +16,9 @@ int RunBlockTest(int frames, ItemSpecialEffect flags) player._pHFrames = frames; player._pIFlags = flags; - StartPlrHit(player, 5, false); + // StartPlrHit compares damage (a 6 bit fixed point value) to character level to determine if the player shrugs off the hit. + // We don't initialise player so this comparison can't be relied on, instead we use forcehit to ensure the player enters hit mode + StartPlrHit(player, 0, true); int i = 1; for (; i < 100; i++) { @@ -109,13 +113,13 @@ static void AssertPlayer(Player &player) ASSERT_EQ(player._pDexterity, 30); ASSERT_EQ(player._pBaseVit, 20); ASSERT_EQ(player._pVitality, 20); - ASSERT_EQ(player._pLevel, 1); + ASSERT_EQ(player.getCharacterLevel(), 1); ASSERT_EQ(player._pStatPts, 0); ASSERT_EQ(player._pExperience, 0); ASSERT_EQ(player._pGold, 100); ASSERT_EQ(player._pMaxHPBase, 2880); ASSERT_EQ(player._pHPBase, 2880); - ASSERT_EQ(player._pBaseToBlk, 20); + ASSERT_EQ(player.getBaseToBlock(), 20); ASSERT_EQ(player._pMaxManaBase, 1440); ASSERT_EQ(player._pManaBase, 1440); ASSERT_EQ(player._pMemSpells, 0); @@ -148,14 +152,15 @@ static void AssertPlayer(Player &player) ASSERT_EQ(player._pMaxHP, 2880); ASSERT_EQ(player._pMana, 1440); ASSERT_EQ(player._pMaxMana, 1440); - ASSERT_EQ(player._pNextExper, 2000); + ASSERT_EQ(player.getNextExperienceThreshold(), 2000); ASSERT_EQ(player._pMagResist, 0); ASSERT_EQ(player._pFireResist, 0); ASSERT_EQ(player._pLghtResist, 0); ASSERT_EQ(CountBool(player._pLvlVisited, NUMLEVELS), 0); ASSERT_EQ(CountBool(player._pSLvlVisited, NUMLEVELS), 0); + // This test case uses a Rogue, starting loadout is a short bow with damage 1-4 ASSERT_EQ(player._pIMinDam, 1); - ASSERT_EQ(player._pIMaxDam, 1); + ASSERT_EQ(player._pIMaxDam, 4); ASSERT_EQ(player._pIAC, 0); ASSERT_EQ(player._pIBonusDam, 0); ASSERT_EQ(player._pIBonusToHit, 0); @@ -174,6 +179,8 @@ static void AssertPlayer(Player &player) TEST(Player, CreatePlayer) { + LoadPlayerDataFiles(); + LoadItemData(); Players.resize(1); CreatePlayer(Players[0], HeroClass::Rogue); AssertPlayer(Players[0]); diff --git a/test/player_test.h b/test/player_test.h index 955d8b25b59..539010ee75d 100644 --- a/test/player_test.h +++ b/test/player_test.h @@ -10,22 +10,22 @@ using namespace devilution; -static int CountItems(Item *items, int n) +static size_t CountItems(Item *items, int n) { return std::count_if(items, items + n, [](Item x) { return !x.isEmpty(); }); } -static int Count8(int8_t *ints, int n) +static size_t Count8(int8_t *ints, int n) { return std::count_if(ints, ints + n, [](int8_t x) { return x != 0; }); } -static int CountU8(uint8_t *ints, int n) +static size_t CountU8(uint8_t *ints, int n) { return std::count_if(ints, ints + n, [](uint8_t x) { return x != 0; }); } -static int CountBool(bool *bools, int n) +static size_t CountBool(bool *bools, int n) { return std::count_if(bools, bools + n, [](bool x) { return x; }); } diff --git a/test/random_test.cpp b/test/random_test.cpp index f57b74e1063..ed8a8bdc6b5 100644 --- a/test/random_test.cpp +++ b/test/random_test.cpp @@ -279,6 +279,31 @@ TEST(RandomTest, NegativeReturnValues) 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 "; diff --git a/test/str_cat_test.cpp b/test/str_cat_test.cpp index ee6675c9723..3aa6280ca53 100644 --- a/test/str_cat_test.cpp +++ b/test/str_cat_test.cpp @@ -14,7 +14,7 @@ TEST(StrCatTest, BufCopyBasicTest) { char buf[5]; char *end = BufCopy(buf, "a", "b", "c", 5); - EXPECT_EQ(string_view(buf, end - buf), "abc5"); + EXPECT_EQ(std::string_view(buf, end - buf), "abc5"); } } // namespace diff --git a/test/timedemo_test.cpp b/test/timedemo_test.cpp index 7d808069399..28fd3915508 100644 --- a/test/timedemo_test.cpp +++ b/test/timedemo_test.cpp @@ -4,8 +4,11 @@ #include "diablo.h" #include "engine/demomode.h" +#include "lua/lua.hpp" +#include "monstdat.h" #include "options.h" #include "pfile.h" +#include "playerdat.hpp" #include "utils/display.h" #include "utils/paths.h" @@ -32,6 +35,7 @@ void RunTimedemo(std::string timedemoFolderName) InitKeymapActions(); LoadOptions(); + LuaInitialize(); const int demoNumber = 0; @@ -48,6 +52,11 @@ void RunTimedemo(std::string timedemoFolderName) HeadlessMode = true; demo::InitPlayBack(demoNumber, true); + LoadSpellData(); + LoadPlayerDataFiles(); + LoadMissileData(); + LoadMonsterData(); + LoadItemData(); pfile_ui_set_hero_infos(Dummy_GetHeroInfo); gbLoadGame = true; diff --git a/test/writehero_test.cpp b/test/writehero_test.cpp index 1fc7368a17a..88e6004418d 100644 --- a/test/writehero_test.cpp +++ b/test/writehero_test.cpp @@ -11,6 +11,7 @@ #include "loadsave.h" #include "pack.h" #include "pfile.h" +#include "playerdat.hpp" #include "utils/file_util.h" #include "utils/paths.h" @@ -278,13 +279,13 @@ void AssertPlayer(Player &player) ASSERT_EQ(player._pDexterity, 281); ASSERT_EQ(player._pBaseVit, 80); ASSERT_EQ(player._pVitality, 90); - ASSERT_EQ(player._pLevel, 50); + ASSERT_EQ(player.getCharacterLevel(), 50); ASSERT_EQ(player._pStatPts, 0); ASSERT_EQ(player._pExperience, 1583495809); ASSERT_EQ(player._pGold, 0); ASSERT_EQ(player._pMaxHPBase, 12864); ASSERT_EQ(player._pHPBase, 12864); - ASSERT_EQ(player._pBaseToBlk, 20); + ASSERT_EQ(player.getBaseToBlock(), 20); ASSERT_EQ(player._pMaxManaBase, 11104); ASSERT_EQ(player._pManaBase, 11104); ASSERT_EQ(player._pMemSpells, 66309357295); @@ -323,7 +324,7 @@ void AssertPlayer(Player &player) ASSERT_EQ(player._pMaxHP, 16640); ASSERT_EQ(player._pMana, 14624); ASSERT_EQ(player._pMaxMana, 14624); - ASSERT_EQ(player._pNextExper, 1310707109); + ASSERT_EQ(player.getNextExperienceThreshold(), 1583495809); ASSERT_EQ(player._pMagResist, 75); ASSERT_EQ(player._pFireResist, 16); ASSERT_EQ(player._pLghtResist, 75); @@ -373,6 +374,9 @@ TEST(Writehero, pfile_write_hero) MyPlayerId = 0; MyPlayer = &Players[MyPlayerId]; + LoadSpellData(); + LoadPlayerDataFiles(); + LoadItemData(); _uiheroinfo info {}; info.heroclass = HeroClass::Rogue; pfile_ui_save_create(&info); @@ -383,8 +387,9 @@ TEST(Writehero, pfile_write_hero) pfile_write_hero(); const char *path = "multi_0.sv"; - uintmax_t size; - ASSERT_TRUE(GetFileSize(path, &size)); + uintmax_t fileSize; + ASSERT_TRUE(GetFileSize(path, &fileSize)); + size_t size = static_cast(fileSize); FILE *f = std::fopen(path, "rb"); ASSERT_TRUE(f != nullptr); std::unique_ptr data { new char[size] }; diff --git a/tools/extract_translation_data.py b/tools/extract_translation_data.py new file mode 100755 index 00000000000..35c101f45f6 --- /dev/null +++ b/tools/extract_translation_data.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +import csv +import pathlib + +root = pathlib.Path(__file__).resolve().parent.parent +translation_dummy_path = root.joinpath("Source/translation_dummy.cpp") +monstdat_path = root.joinpath("assets/txtdata/monsters/monstdat.tsv") +unique_monstdat_path = root.joinpath("assets/txtdata/monsters/unique_monstdat.tsv") +itemdat_path = root.joinpath("assets/txtdata/items/itemdat.tsv") +unique_itemdat_path = root.joinpath("assets/txtdata/items/unique_itemdat.tsv") +item_prefixes_path = root.joinpath("assets/txtdata/items/item_prefixes.tsv") +item_suffixes_path = root.joinpath("assets/txtdata/items/item_suffixes.tsv") +spelldat_path = root.joinpath("assets/txtdata/spells/spelldat.tsv") + +with open(translation_dummy_path, 'w') as temp_source: + temp_source.write(f'/**\n') + temp_source.write(f' * @file translation_dummy.cpp\n') + temp_source.write(f' *\n') + temp_source.write(f' * Do not edit this file manually, it is automatically generated\n') + temp_source.write(f' * and updated by the extract_translation_data.py script.\n') + temp_source.write(f' */\n') + temp_source.write(f'#include "utils/language.h"\n') + temp_source.write(f'\n') + with open(monstdat_path, 'r') as tsv: + reader = csv.DictReader(tsv, delimiter='\t') + for row in reader: + name = row['name'] + var_name = row['_monster_id'] + "_NAME" + temp_source.write(f'const char *{var_name} = P_("monster", "{name}");\n') + with open(unique_monstdat_path, 'r') as tsv: + reader = csv.DictReader(tsv, delimiter='\t') + for row in reader: + name = row['name'] + var_name = name.upper().replace(' ', '_').replace('-', '_') + "_NAME" + temp_source.write(f'const char *{var_name} = P_("monster", "{name}");\n') + with open(itemdat_path, 'r') as tsv: + reader = csv.DictReader(tsv, delimiter='\t') + for i, row in enumerate(reader): + id = row['id'] + name = row['name'] + if name == 'Scroll of None' or name == 'Non Item': + continue + shortName = row['shortName'] + var_name = id if id else f'ITEM_{i}' + temp_source.write(f'const char *{var_name}_NAME = N_("{name}");\n') + if shortName: + temp_source.write(f'const char *{var_name}_SHORT_NAME = N_("{shortName}");\n') + with open(unique_itemdat_path, 'r') as tsv: + reader = csv.DictReader(tsv, delimiter='\t') + for i, row in enumerate(reader): + name = row['name'] + var_name = f'UNIQUE_ITEM_{i}' + temp_source.write(f'const char *{var_name}_NAME = N_("{name}");\n') + with open(item_prefixes_path, 'r') as tsv: + reader = csv.DictReader(tsv, delimiter='\t') + for i, row in enumerate(reader): + name = row['name'] + var_name = f'ITEM_PREFIX_{i}' + temp_source.write(f'const char *{var_name}_NAME = N_("{name}");\n') + with open(item_suffixes_path, 'r') as tsv: + reader = csv.DictReader(tsv, delimiter='\t') + for i, row in enumerate(reader): + name = row['name'] + var_name = f'ITEM_SUFFIX_{i}' + temp_source.write(f'const char *{var_name}_NAME = N_("{name}");\n') + with open(spelldat_path, 'r') as tsv: + reader = csv.DictReader(tsv, delimiter='\t') + for i, row in enumerate(reader): + name = row['name'] + var_name = 'SPELL_' + name.upper().replace(' ', '_').replace('-', '_') + temp_source.write(f'const char *{var_name}_NAME = P_("spell", "{name}");\n') diff --git a/tools/run_big_endian_tests.sh b/tools/run_big_endian_tests.sh index 7c50654e881..3458a6cf893 100755 --- a/tools/run_big_endian_tests.sh +++ b/tools/run_big_endian_tests.sh @@ -12,7 +12,7 @@ fi # We disable ASAN and UBSAN for now because of: # https://gitlab.alpinelinux.org/alpine/aports/-/issues/14435 -docker run -u "$(id -u "$USER"):$(id -g "$USER")" --rm --mount "type=bind,source=${PWD},target=/host" devilutionx-s390x-test sh -c "cd /host && \ +docker run --platform linux/s390x -u "$(id -u "$USER"):$(id -g "$USER")" --rm --mount "type=bind,source=${PWD},target=/host" devilutionx-s390x-test sh -c "cd /host && \ export CCACHE_DIR=/host/.s390x-ccache && \ cmake -S. -Bbuild-s390x-test -G Ninja -DASAN=OFF -DUBSAN=OFF -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ \ -DNONET=ON -DNOSOUND=ON && \ diff --git a/tools/update_bundled_assets.sh b/tools/update_bundled_assets.sh index 1c8d766dc50..86ba48ecdd2 100755 --- a/tools/update_bundled_assets.sh +++ b/tools/update_bundled_assets.sh @@ -7,7 +7,7 @@ if [[ $# -eq 0 ]]; then fi ASSETS_REPO_DIR="$1" -OUTPUT_DIR="${PWD}/Packaging/resources/assets" +OUTPUT_DIR="${PWD}/assets" set -x cd "${ASSETS_REPO_DIR}/bundled-assets" diff --git a/uwp-project/devilutionx.vcxproj b/uwp-project/devilutionx.vcxproj index 8d0d6f43a63..81689119674 100644 --- a/uwp-project/devilutionx.vcxproj +++ b/uwp-project/devilutionx.vcxproj @@ -73,8 +73,8 @@ - sdl_image.lib;libpng16_staticd.lib;pkware.lib;fmtd.lib;zlibstatic.lib;bzip2.lib;libsmackerdec.lib;libmpq.lib;libdevilutionx.lib;sdl2.lib;sdl_audiolib.lib;sodium.lib;zt.lib;lwip_pic.lib;miniupnpc_pic.lib;natpmp_pic.lib;zt_pic.lib;zto_pic.lib;shlwapi.lib;shell32.lib;%(AdditionalDependencies) - ..\build\SDL\VisualC-WinRT\x64\Debug\SDL-UWP;..\build\3rdParty\SDL_image\Debug;..\build\_deps\zlib-build\Debug;..\build\3rdParty\PKWare\Debug;..\build\3rdParty\bzip2\Debug;..\build\3rdParty\libsmackerdec\Debug;..\build\3rdParty\libmpq\Debug;..\build\_deps\sdl_audiolib-build\Debug;..\build\_deps\libsodium-build\Debug;..\build\_deps\libzt-build\lib\Debug;..\build\_deps\libfmt-build\Debug;..\build\_deps\libpng-build\Debug;..\build\Source\libdevilutionx.dir\Debug;%(AdditionalLibraryDirectories) + sdl_image.lib;libpng16_staticd.lib;pkware.lib;fmtd.lib;zlibstatic.lib;bzip2.lib;libsmackerdec.lib;libmpq.lib;libdevilutionx.lib;lua_static.lib;sdl2.lib;sdl_audiolib.lib;asio.lib;sodium.lib;zt.lib;lwip_pic.lib;miniupnpc_pic.lib;natpmp_pic.lib;zt_pic.lib;zto_pic.lib;shlwapi.lib;shell32.lib;%(AdditionalDependencies) + ..\build\SDL\VisualC-WinRT\x64\Debug\SDL-UWP;..\build\3rdParty\SDL_image\Debug;..\build\_deps\zlib-build\Debug;..\build\3rdParty\PKWare\Debug;..\build\3rdParty\bzip2\Debug;..\build\3rdParty\libsmackerdec\Debug;..\build\3rdParty\libmpq\Debug;..\build\_deps\lua-build\lua-5.4.6\Debug;..\build\_deps\sdl_audiolib-build\Debug;..\build\3rdParty\asio\Debug;..\build\_deps\libsodium-build\Debug;..\build\_deps\libzt-build\lib\Debug;..\build\_deps\libfmt-build\Debug;..\build\_deps\libpng-build\Debug;..\build\Source\libdevilutionx.dir\Debug;%(AdditionalLibraryDirectories) pch.h @@ -92,8 +92,8 @@ - sdl_image.lib;libpng16_static.lib;pkware.lib;fmt.lib;zlibstatic.lib;bzip2.lib;libsmackerdec.lib;libmpq.lib;libdevilutionx.lib;sdl2.lib;sdl_audiolib.lib;sodium.lib;zt.lib;lwip_pic.lib;miniupnpc_pic.lib;natpmp_pic.lib;zt_pic.lib;zto_pic.lib;shlwapi.lib;shell32.lib;%(AdditionalDependencies) - ..\build\SDL\VisualC-WinRT\x64\Release\SDL-UWP;..\build\3rdParty\SDL_image\Release;..\build\_deps\zlib-build\Release;..\build\3rdParty\PKWare\Release;..\build\3rdParty\bzip2\Release;..\build\3rdParty\libsmackerdec\Release;..\build\3rdParty\libmpq\Release;..\build\_deps\sdl_audiolib-build\Release;..\build\_deps\libsodium-build\Release;..\build\_deps\libzt-build\lib\Release;..\build\_deps\libfmt-build\Release;..\build\_deps\libpng-build\Release;..\build\Source\libdevilutionx.dir\Release;%(AdditionalLibraryDirectories) + sdl_image.lib;libpng16_static.lib;pkware.lib;fmt.lib;zlibstatic.lib;bzip2.lib;libsmackerdec.lib;libmpq.lib;libdevilutionx.lib;lua_static.lib;sdl2.lib;sdl_audiolib.lib;asio.lib;sodium.lib;zt.lib;lwip_pic.lib;miniupnpc_pic.lib;natpmp_pic.lib;zt_pic.lib;zto_pic.lib;shlwapi.lib;shell32.lib;%(AdditionalDependencies) + ..\build\SDL\VisualC-WinRT\x64\Release\SDL-UWP;..\build\3rdParty\SDL_image\Release;..\build\_deps\zlib-build\Release;..\build\3rdParty\PKWare\Release;..\build\3rdParty\bzip2\Release;..\build\3rdParty\libsmackerdec\Release;..\build\3rdParty\libmpq\Release;..\build\_deps\lua-build\lua-5.4.6\Release;..\build\_deps\sdl_audiolib-build\Release;..\build\3rdParty\asio\Release;..\build\_deps\libsodium-build\Release;..\build\_deps\libzt-build\lib\Release;..\build\_deps\libfmt-build\Release;..\build\_deps\libpng-build\Release;..\build\Source\libdevilutionx.dir\Release;%(AdditionalLibraryDirectories) pch.h diff --git a/vcpkg.json b/vcpkg.json index 4eeef04bea0..97d07ce8d76 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,37 +1,38 @@ { - "name": "devilutionx", - "version-string": "1.4.0", - "dependencies": [ - "fmt", - "bzip2", - "simpleini" - ], - "builtin-baseline": "78b61582c9e093fda56a01ebb654be15a0033897", - "features": { - "sdl1": { - "description": "Use SDL1.2 instead of SDL2", - "dependencies": [ "sdl1", "libpng" ] - }, - "sdl2": { - "description": "Use SDL2", - "dependencies": [ "sdl2", "sdl2-image" ] - }, - "encryption": { - "description": "Build libsodium for packet encryption", - "dependencies": [ "libsodium" ] - }, - "translations": { - "description": "Build translation files", - "dependencies": [ - { - "name": "gettext", - "features": [ "tools" ] - } - ] - }, - "tests": { - "description": "Build tests", - "dependencies": [ "gtest" ] - } - } + "name": "devilutionx", + "version-string": "1.4.0", + "dependencies": [ + "fmt", + "bzip2", + "simpleini", + "lua" + ], + "builtin-baseline": "16ee2ecb31788c336ace8bb14c21801efb6836e4", + "features": { + "sdl1": { + "description": "Use SDL1.2 instead of SDL2", + "dependencies": [ "sdl1", "libpng" ] + }, + "sdl2": { + "description": "Use SDL2", + "dependencies": [ "sdl2", "sdl2-image" ] + }, + "encryption": { + "description": "Build libsodium for packet encryption", + "dependencies": [ "libsodium" ] + }, + "translations": { + "description": "Build translation files", + "dependencies": [ + { + "name": "gettext", + "features": [ "tools" ] + } + ] + }, + "tests": { + "description": "Build tests", + "dependencies": [ "gtest" ] + } + } }