diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index c550b69f66..710be77781 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -7,16 +7,56 @@ on: pull_request: paths: - 'app/android/**' + workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - id: job_info + uses: daleyjem/gh-job-id@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + job_name: 'build' - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3 + - name: Checkout Repository + uses: actions/checkout@v3 - - name: Build Android Packages - run: cd app/android/client && gradle assemble + - name: Set Up JDK + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'adopt' + + - name: Validate Gradle Wrapper .jar + run: | + cd app/android/gradle/wrapper + curl --location --output gradle-wrapper.jar.sha256 \ + https://services.gradle.org/distributions/gradle-7.5.1-wrapper.jar.sha256 + echo " gradle-wrapper.jar" >> gradle-wrapper.jar.sha256 + sha256sum --check gradle-wrapper.jar.sha256 + + - name: Build APKs with Ant + uses: cedx/setup-ant@v6 + with: + version: '1.10' + - run: ant -noinput -buildfile build.xml android -Dgradle.params="assembleDebug assembleRelease" + + - name: Failure Report + uses: sarisia/actions-status-discord@v1 + if: failure() + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_DEVELOPMENT }} + with: + title: 'Android Build Failed' + description: '${{ steps.job_info.outputs.html_url }}' + nodetail: true + - uses: sarisia/actions-status-discord@v1 + if: failure() + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_DEVLOG }} + with: + title: 'Android Build Failed' + description: '${{ steps.job_info.outputs.html_url }}' + nodetail: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1aaaeb4e51..3c5094235c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -29,15 +29,15 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 # Override language selection by uncommenting this and choosing your languages # with: # languages: go, javascript, csharp, python, cpp, java # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 + #- name: Autobuild + # uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -46,9 +46,7 @@ jobs: # and modify them (or add more) to build your code if your project # uses a compiled language - #- run: | - # make bootstrap - # make release + - run: ant -noinput -f build.xml clean compile - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.project b/.project index 8aa95068ea..a3d0298ad9 100644 --- a/.project +++ b/.project @@ -20,4 +20,15 @@ org.eclipse.jdt.core.javanature org.eclipse.wst.common.project.facet.core.nature + + + 1771748271774 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/README.md b/README.md index 1f1a30a95d..8b25a90318 100644 --- a/README.md +++ b/README.md @@ -30,29 +30,28 @@ button to create or log in to your account. ### Mobile Play using the [progressive web app](https://en.wikipedia.org/wiki/Progressive_web_app). Just visit -https://stendhalgame.org/ in your mobile browser and click "install" when prompted. +[stendhalgame.org](https://stendhalgame.org/) in your mobile browser and click "install" when prompted. [](https://f-droid.org/packages/org.stendhalgame.client/) Play on your Android device using the dedicated app. You can get it from the -[F-Droid App Store](https://f-droid.org/) or download it from https://stendhalgame.org/download.html. +[F-Droid App Store](https://f-droid.org/) or download it from [stendhalgame.org](https://stendhalgame.org/download.html). ### Classic Desktop [![Download](https://stendhalgame.org/images/downloadbutton.png)](https://arianne-project.org/download/stendhal.zip) Play using the classic desktop client. You will need Java which you can download from -[https://www.java.com](https://www.java.com/en/download) on Microsoft Windows. Java is included in +[java.com](https://www.java.com/en/download) on Microsoft Windows. Java is included in most Linux distributions. Both Oracle Java and OpenJDK are supported. -Please download Stendhal from [https://stendhalgame.org](https://stendhalgame.org)
-You can double click on stendhal-starter.jar and the client will run.
-If you prefer to run from command line, then just execute this command -in the stendhal folder: +Please download Stendhal from [stendhalgame.org](https://stendhalgame.org/download.html).
+You can double click on `stendhal-starter.jar` and the client will run.
+If you prefer to run from command line, then just execute this command in the stendhal folder: `java -jar stendhal-starter.jar` -There is a [manual](https://stendhalgame.org/wiki/Stendhal_Manual), and there is Help from the client menu icons in the upper right hand side of the game screen. +There is a [manual](https://stendhalgame.org/wiki/Stendhal_Manual), and there is Help from the client menu icons in the upper right hand side of the game screen. ## Development environment @@ -67,7 +66,7 @@ There is a [manual](https://stendhalgame.org/wiki/Stendhal_Manual), and there i Stendhal is free software. You can redistribute it and/or modify under the following terms: -- The **server**, **java client**, and **Android client**: *GNU General Public License* 2 or later as published by the Free +- The **server**, **java client**, and **Android client**: *GNU General Public License* 2 or later as published by the Free Software Foundation - The **web client** and **native client**: *GNU Affero General Public License* 3 or later as published by the Free Software Foundation with the definition of "System libraries" being extended to include authentication providers and software distribution platforms. - The **nativehelper** extensions: *GNU Lesser General Public License* 3 or later as published by the Free Software Foundation @@ -76,6 +75,6 @@ even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE [LICENSE.txt](LICENSE.txt) and [src/js/LICENSE.html](src/js/LICENSE.html) file for more details. Stendhal(c) is copyright of Miguel Angel Blanch Lardin, 2005-2008, arianne_rpg at users dot sourceforge dot net and others
-Stendhal(c) is copyright of the Arianne Project, 2006-2024, arianne-general at lists dot sourceforge dot net +Stendhal(c) is copyright of the Arianne Project, 2006-2026, arianne-general at lists dot sourceforge dot net Please have a look at the list of [contributors](doc/contributors.md#contributors). diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000000..a82f7921f3 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/godot/ diff --git a/app/android/client/build.gradle b/app/android/client/build.gradle index 67a570bc87..d5f56cb075 100644 --- a/app/android/client/build.gradle +++ b/app/android/client/build.gradle @@ -10,8 +10,8 @@ android { applicationId namespace minSdk 21 targetSdk 32 - versionCode 1048000 - versionName "1.48" + versionCode 1049005 + versionName "1.49.5" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" targetSdkVersion 32 minSdkVersion 21 @@ -26,7 +26,7 @@ android { } // build output - project.setProperty("archivesBaseName", "${namespace}-${defaultConfig.versionCode}") + project.setProperty("archivesBaseName", "${namespace}-${defaultConfig.versionName}") project.setProperty("buildDir", "${projectDir}/../../../build/build_android_client") /* A keystore.properties file can be placed in the root Android project directory and must diff --git a/app/android/client/src/main/java/org/stendhalgame/client/ClientView.java b/app/android/client/src/main/java/org/stendhalgame/client/ClientView.java index 6bc105179b..d3bb03015a 100644 --- a/app/android/client/src/main/java/org/stendhalgame/client/ClientView.java +++ b/app/android/client/src/main/java/org/stendhalgame/client/ClientView.java @@ -14,6 +14,7 @@ package org.stendhalgame.client; import java.util.Random; import android.app.AlertDialog; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -189,7 +190,11 @@ public class ClientView extends WebView { if (!UrlHelper.isInternalUri(uri)) { // open external links in default browser/app // FIXME: should we ask for confirmation? - MainActivity.get().startActivity(new Intent(Intent.ACTION_VIEW, uri)); + try { + MainActivity.get().startActivity(new Intent(Intent.ACTION_VIEW, uri)); + } catch (final ActivityNotFoundException e) { + Notifier.toast("No app found to open this link"); + } return true; } view.loadUrl(UrlHelper.checkClientUrl(uri.toString())); diff --git a/app/android/client/src/main/play/listings/en-US/graphics/phone-screenshots/01-start.jpg b/app/android/client/src/main/play/listings/en-US/graphics/phone-screenshots/01-start.jpg new file mode 100644 index 0000000000..3dbef90e03 Binary files /dev/null and b/app/android/client/src/main/play/listings/en-US/graphics/phone-screenshots/01-start.jpg differ diff --git a/app/android/client/src/main/play/listings/en-US/graphics/phone-screenshots/02-npc_interaction.jpg b/app/android/client/src/main/play/listings/en-US/graphics/phone-screenshots/02-npc_interaction.jpg new file mode 100644 index 0000000000..5cb65ec5e9 Binary files /dev/null and b/app/android/client/src/main/play/listings/en-US/graphics/phone-screenshots/02-npc_interaction.jpg differ diff --git a/app/android/client/src/main/play/listings/en-US/graphics/phone-screenshots/03-melee_combat.jpg b/app/android/client/src/main/play/listings/en-US/graphics/phone-screenshots/03-melee_combat.jpg new file mode 100644 index 0000000000..86fe43ded9 Binary files /dev/null and b/app/android/client/src/main/play/listings/en-US/graphics/phone-screenshots/03-melee_combat.jpg differ diff --git a/app/android/client/src/main/play/release-notes/en-US/default.txt b/app/android/client/src/main/play/release-notes/en-US/default.txt index 6484572ca3..70b8142595 100644 --- a/app/android/client/src/main/play/release-notes/en-US/default.txt +++ b/app/android/client/src/main/play/release-notes/en-US/default.txt @@ -1,7 +1,3 @@ -The world has just gotten a little fancier. You may notice that the clouds have cleared in Kikareukin, revealing a beautiful view of Semos below. - -If luck has it, you may find a rare four-leaf clover. Keep your eyes peeled, they pop up in random locations that receive sunshine. Clover hunters will reward generously for such valuable trinkets. - -Jef is still eager to hear from his mother, Amber. But don't expect to find her on her usual route as she has taken to wandering off the beaten path. Speaking of wandering, some creatures have become more unpredictable in their behavior. Be careful, as you may encounter them in unexpected places. +PUT RELEASE ANNOUNCEMENT LINES HERE Please see the complete change log for further details: https://arianne-project.org/game/stendhal.html#changes diff --git a/app/android/client/src/main/res/values/strings.xml b/app/android/client/src/main/res/values/strings.xml index c6c482d0de..f60bf90f42 100644 --- a/app/android/client/src/main/res/values/strings.xml +++ b/app/android/client/src/main/res/values/strings.xml @@ -5,6 +5,6 @@ Stendhal stendhalprod8gps5y99pu prod - 1.48 + 1.49.5 diff --git a/app/android/client/src/shared/java/org/stendhalgame/client/Logger.java b/app/android/client/src/shared/java/org/stendhalgame/client/Logger.java index c4aab87415..cb14799ca6 100644 --- a/app/android/client/src/shared/java/org/stendhalgame/client/Logger.java +++ b/app/android/client/src/shared/java/org/stendhalgame/client/Logger.java @@ -19,7 +19,7 @@ import android.content.DialogInterface; /** * Semi-functional logger for release builds. - * + * * Displays notification dialogs but does not write to logs directory. */ public class Logger { diff --git a/app/android/gradle/wrapper/gradle-wrapper.properties b/app/android/gradle/wrapper/gradle-wrapper.properties index 0176d15822..b916c04dbb 100644 --- a/app/android/gradle/wrapper/gradle-wrapper.properties +++ b/app/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=91a239400bb638f36a1795d8fdf7939d532cdc7d794d1119b7261aac158b1e60 +distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4 distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/app/nativehelper/Cargo.toml b/app/nativehelper/Cargo.toml index bdf01b24a2..4df05921bc 100644 --- a/app/nativehelper/Cargo.toml +++ b/app/nativehelper/Cargo.toml @@ -18,6 +18,7 @@ panic = 'abort' strip = true [dependencies] +lazy_static = "1.5.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" steamworks = "0.10.0" diff --git a/app/nativehelper/src/main.rs b/app/nativehelper/src/main.rs index c30249e96a..75186ee688 100644 --- a/app/nativehelper/src/main.rs +++ b/app/nativehelper/src/main.rs @@ -1,9 +1,12 @@ +use lazy_static::lazy_static; use serde_json::Value; use std::io; use std::io::Read; +use std::process; use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; +use std::sync::Mutex; use std::sync::mpsc::Sender; use std::sync::mpsc::channel; use std::thread; @@ -16,6 +19,10 @@ use websocket::client::ClientBuilder; use websocket::{Message, OwnedMessage}; +lazy_static! { + static ref AUTHENTICATED: Mutex = Mutex::new(false); +} + fn connect() -> (Sender, JoinHandle<()>) { println!("Read connection parameters"); let mut json = String::new(); @@ -119,6 +126,14 @@ fn connect() -> (Sender, JoinHandle<()>) { fn handle_message(nl_token: &String, tx: Sender, message: String) { if message.contains("event\":\"request_authentication\"") { authenticate(tx, nl_token.clone()); + } else if message.contains("event\":\"clientDisconnect\"") { + let authenticated = AUTHENTICATED.lock().unwrap(); + if *authenticated { + println!("Authenticated, exiting extension ..."); + } else { + println!("Unauthenticated, exiting extension ..."); + } + process::exit(0); } } @@ -132,6 +147,10 @@ fn authenticate(tx: Sender, nl_token: String) { let _callback = client.register_callback(move |session_ticket_response: AuthSessionTicketResponse| { //if session_ticket_response.ticket == ticket_handle { + if session_ticket_response.result.is_ok() { + let mut authenticated = AUTHENTICATED.lock().unwrap(); + *authenticated = true; + } println!("Ticket Response Result: {:?}", session_ticket_response.result); callback_called.store(true, Ordering::Relaxed); // println!("Ticket : {:?}", session_ticket_response.ticket); diff --git a/app/neutralinojs/.gitignore b/app/neutralinojs/.gitignore index 87d1a9794d..3bdc203be2 100644 --- a/app/neutralinojs/.gitignore +++ b/app/neutralinojs/.gitignore @@ -11,6 +11,7 @@ neutralino.js # Neutralinojs related files .storage *.log +extensions/binary/steam_appid.txt extensions/binary/*.so extensions/binary/*.dll extensions/binary/nativehelper* diff --git a/app/neutralinojs/extensions/binary/steam_appid.txt b/app/neutralinojs/extensions/binary/steam_appid.txt deleted file mode 100644 index d689143772..0000000000 --- a/app/neutralinojs/extensions/binary/steam_appid.txt +++ /dev/null @@ -1 +0,0 @@ -2267890 diff --git a/app/neutralinojs/neutralino.config.json b/app/neutralinojs/neutralino.config.json index b31a531ff7..7d4ac4d423 100644 --- a/app/neutralinojs/neutralino.config.json +++ b/app/neutralinojs/neutralino.config.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/neutralinojs/neutralinojs/main/schemas/neutralino.config.schema.json", "applicationId": "org.stendhalgame.client", - "version": "1.48", + "version": "1.49.5", "defaultMode": "window", "port": 0, "documentRoot": "/resources/", @@ -11,9 +11,9 @@ "enableExtensions": true, "extensions": [ { - "id": "nativehelper", - "commandLinux": "${NL_PATH}/extensions/binary/nativehelper", - "commandWindows": "${NL_PATH}/extensions/binary/nativehelper.exe" + "id": "nativehelper", + "commandLinux": "${NL_PATH}/extensions/binary/nativehelper", + "commandWindows": "${NL_PATH}/extensions/binary/nativehelper.exe" } ], "tokenSecurity": "one-time", @@ -55,7 +55,7 @@ "resourcesPath": "/resources/", "extensionsPath": "/extensions/", "clientLibrary": "/resources/js/neutralino.js", - "binaryVersion": "5.0.0", - "clientVersion": "5.0.1" + "binaryVersion": "6.2.0", + "clientVersion": "6.2.0" } } diff --git a/bin/check-file-size.py b/bin/check-file-size.py new file mode 100755 index 0000000000..8ea91e307e --- /dev/null +++ b/bin/check-file-size.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +''' +*************************************************************************** +* Copyright © 2025 - Faiumoni e. V. * +*************************************************************************** +*************************************************************************** +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +*************************************************************************** +''' + +import errno +import os +import sys + +from pathlib import Path + + +dir_root = Path(__file__).parent +if "bin" == dir_root.name: + dir_root = Path(dir_root.parent) +dir_music = Path(dir_root, "data/music") +dir_sounds = Path(dir_root, "data/sounds") + + +## Checks if a file is larger than 2MB. +# +# On systems with low resources, the Java client struggles to load large audio files. +# +# @param path +# File path. +# @return +# `EFBIG` if file is larger than 2MB, 0 otherwise. +def checkAudioFileSize(path): + if not path.is_file(): + # ignore if audio file doesn't exist + print("WARNING: Ignoring file not found: {}".format(path)) + return 0 + + if path.stat().st_size > 1048576 * 2: + # warn if file size > 2MB + return errno.EFBIG + return 0 + +## Checks directory for large audio files. +# +# @param path +# Directory path to be walked. +# @return +# `EFBIG` if any file larger than 2MB found, 0 otherwise. +def checkAudioDir(path): + if not path.is_dir(): + # ignore if audio directory doesn't exist + print("WARNING: Ignoring directory not found: {}".format(path)) + return 0 + + res = 0 + for ROOT, DIRS, FILES in path.walk(): + for filename in FILES: + filepath = Path(ROOT, filename) + if filepath.suffix.lower().lstrip(".") not in ("ogg", "oga", "mid", "midi"): + # only check supported file types + continue + + code = checkAudioFileSize(filepath) + if code == errno.EFBIG: + res = code + sys.stderr.write("WARNING: Audio file size larger than 2MB: {}\n".format(str(filepath)[len(str(dir_root)):].lstrip("/\\"))) + + return res + +def main(): + res = 0 + for audio_dir in dir_music, dir_sounds: + code = checkAudioDir(audio_dir) + if code != 0: + res = code + if res == 0: + print("No large audio data files detected") + return res + +if __name__ == "__main__": + # work from source tree root + os.chdir(dir_root) + sys.exit(main()) diff --git a/bin/runserver.bat b/bin/runserver.bat index f79b805565..91d06c7e74 100644 --- a/bin/runserver.bat +++ b/bin/runserver.bat @@ -1,6 +1,6 @@ @echo off -set STENDHAL_VERSION=1.48 +set STENDHAL_VERSION=1.49.5 set SERVER_JAR=stendhal-server-%STENDHAL_VERSION%.jar :: change to server directory diff --git a/bin/runserver.sh b/bin/runserver.sh index 1edba29428..6dbe08452d 100755 --- a/bin/runserver.sh +++ b/bin/runserver.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -STENDHAL_VERSION="1.48" +STENDHAL_VERSION="1.49.5" SERVER_JAR="stendhal-server-${STENDHAL_VERSION}.jar" # change to server directory diff --git a/build.ant.properties b/build.ant.properties index 5397b84baf..fb5e66018b 100644 --- a/build.ant.properties +++ b/build.ant.properties @@ -91,10 +91,10 @@ updates_server_fallback = http://arianne.sourceforge.net/stendhal/updates version_server = http://arianne.sourceforge.net/stendhal.version # current version of stendhal -version.old = 1.47 -version = 1.48 -version.android = 1048000 +version.old = 1.49 +version = 1.49.5 # FIXME: dynamic method to set this property? +version.android = 1049005 # javac options javac.deprecation = true diff --git a/build.xml b/build.xml index aeb0e308fb..f438a3c6ea 100644 --- a/build.xml +++ b/build.xml @@ -1,81 +1,32 @@ - - + + +Project: + Name: .......... ${game_name} + Version: ....... ${version} + Root: .......... ${basedir} + +Build system: + Ant version: ... ${ant.version.number} + Ant home: ...... ${ant.location} + Java version: .. ${java.version} + Java home: ..... ${java.home} + +Execute `ant help` for more information. + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/buildtools/ant_modules/apps.xml b/buildtools/ant_modules/apps.xml index d7e035b242..632566cf60 100644 --- a/buildtools/ant_modules/apps.xml +++ b/buildtools/ant_modules/apps.xml @@ -37,6 +37,7 @@ + @@ -77,23 +78,33 @@ + + - + - - + ${steam-appid} + + + + + + + + + + + @@ -150,7 +161,8 @@ - + + diff --git a/buildtools/ant_modules/clean.xml b/buildtools/ant_modules/clean.xml index f4d61adb72..eb73fe81aa 100644 --- a/buildtools/ant_modules/clean.xml +++ b/buildtools/ant_modules/clean.xml @@ -2,7 +2,8 @@ - + + @@ -18,14 +19,16 @@ - + + - + + diff --git a/buildtools/ant_modules/dist.xml b/buildtools/ant_modules/dist.xml index 57c605fddb..69e17d1822 100644 --- a/buildtools/ant_modules/dist.xml +++ b/buildtools/ant_modules/dist.xml @@ -19,13 +19,15 @@ - + + Built client and server for NetBeans. - + + + - + - + + - + + - + + @@ -104,7 +109,8 @@ - + + @@ -179,7 +185,8 @@ - + + @@ -340,7 +347,8 @@ - + + @@ -392,10 +400,12 @@ - + + - + + @@ -404,7 +414,8 @@ - + + @@ -414,28 +425,57 @@ - + + - - + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + @@ -446,7 +486,8 @@ - + + @@ -494,7 +535,7 @@ - + @@ -539,7 +580,8 @@ - + + @@ -560,8 +602,12 @@ + + - + + + @@ -597,8 +643,8 @@ - - + + @@ -620,7 +666,8 @@ - + + @@ -656,8 +703,9 @@ + - + @@ -665,30 +713,42 @@ - - + + + + - - var stendhal = window.stendhal = window.stendhal || {}; - stendhal.data = stendhal.data || {}; + + +export function version(marauroa, stendhal) { + stendhal.data = stendhal.data || {}; - stendhal.data.build = { - version: "${version}", - build: "${datetime}", - dist: "webclient" - } + stendhal.data.build = { + version: "${version}", + build: "${datetime}", + dist: "webclient" + } + + //CI +} + + + + + + - + @@ -713,72 +773,19 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -//# sourceMappingURL=stendhal-compiled.js.map - + + + + + + + + @@ -795,7 +802,8 @@ - + + @@ -804,7 +812,8 @@ - + + @@ -826,10 +835,8 @@ - - - - + + diff --git a/buildtools/ant_modules/docs.xml b/buildtools/ant_modules/docs.xml index b4ed151203..4e6cd05dfb 100644 --- a/buildtools/ant_modules/docs.xml +++ b/buildtools/ant_modules/docs.xml @@ -12,7 +12,8 @@ - + + diff --git a/buildtools/ant_modules/info.xml b/buildtools/ant_modules/info.xml new file mode 100644 index 0000000000..68c7ef2400 --- /dev/null +++ b/buildtools/ant_modules/info.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/buildtools/ant_modules/locale.xml b/buildtools/ant_modules/locale.xml index 452774ca2e..66f25bb768 100644 --- a/buildtools/ant_modules/locale.xml +++ b/buildtools/ant_modules/locale.xml @@ -2,7 +2,8 @@ - + + @@ -11,7 +12,8 @@ - + + diff --git a/buildtools/ant_modules/main.xml b/buildtools/ant_modules/main.xml index 2902dede0a..9891b0664c 100644 --- a/buildtools/ant_modules/main.xml +++ b/buildtools/ant_modules/main.xml @@ -2,7 +2,8 @@ - + + @@ -42,6 +43,10 @@ match=""version": ".*"" replace=""version": "${version}"" byline="true"/> + - + + @@ -163,7 +169,8 @@ - + + diff --git a/buildtools/ant_modules/maps.xml b/buildtools/ant_modules/maps.xml index 8402027f26..1f12acb6fa 100644 --- a/buildtools/ant_modules/maps.xml +++ b/buildtools/ant_modules/maps.xml @@ -4,7 +4,8 @@ - + + @@ -23,7 +24,8 @@ - + + @@ -58,7 +60,8 @@ - + + @@ -85,7 +88,8 @@ - + + diff --git a/buildtools/ant_modules/package.xml b/buildtools/ant_modules/package.xml index 7b0356c940..1d260be037 100644 --- a/buildtools/ant_modules/package.xml +++ b/buildtools/ant_modules/package.xml @@ -16,7 +16,8 @@ - + + @@ -46,7 +47,8 @@ - + + @@ -74,7 +76,8 @@ - + + @@ -107,7 +110,8 @@ - + + diff --git a/buildtools/ant_modules/properties.xml b/buildtools/ant_modules/properties.xml new file mode 100644 index 0000000000..76fdfc3ae4 --- /dev/null +++ b/buildtools/ant_modules/properties.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/buildtools/ant_modules/testing.xml b/buildtools/ant_modules/testing.xml index bcb48f75ca..a0bfbea6cd 100644 --- a/buildtools/ant_modules/testing.xml +++ b/buildtools/ant_modules/testing.xml @@ -7,7 +7,8 @@ - + + @@ -21,7 +22,8 @@ - + + @@ -63,7 +65,8 @@ - + + database_implementation=games.stendhal.server.core.engine.StendhalPlayerDatabase factory_implementation=games.stendhal.server.core.engine.StendhalRPObjectFactory @@ -85,7 +88,8 @@ d = 9017093486007323270605414717455737376618092805104733236083857811315606869372 - + + @@ -147,7 +151,8 @@ d = 9017093486007323270605414717455737376618092805104733236083857811315606869372 - + + diff --git a/buildtools/ant_modules/tools.xml b/buildtools/ant_modules/tools.xml index b6dc403527..67bf2c0136 100644 --- a/buildtools/ant_modules/tools.xml +++ b/buildtools/ant_modules/tools.xml @@ -4,7 +4,8 @@ - + + @@ -28,7 +29,11 @@ - + + + + + @@ -36,7 +41,7 @@ - + @@ -55,16 +60,22 @@ - + + + + + + + + + - - - - + + @@ -100,7 +111,8 @@ - + + @@ -116,7 +128,8 @@ - + + Creating update from ${version.old} to ${version} @@ -160,8 +173,9 @@ + - + diff --git a/buildtools/mkdocs/mkdocs.yml b/buildtools/mkdocs/mkdocs.yml index d20b234f14..b73584a76f 100644 --- a/buildtools/mkdocs/mkdocs.yml +++ b/buildtools/mkdocs/mkdocs.yml @@ -1,6 +1,6 @@ -site_name: Stendhal 1.48 Reference Documentation +site_name: Stendhal 1.49.5 Reference Documentation site_description: A fun, friendly, & free multiplayer online adventure game. -copyright: Copyright © 2003-2024 Stendhal +copyright: Copyright © 2003-2026 Stendhal site_url: https://stendhalgame.org/ nav: - Index: /reference/ diff --git a/data/conf/shops/sell_outfits.xml b/data/conf/shops/sell_outfits.xml index 14e7e77b71..ca2e0b0f40 100644 --- a/data/conf/shops/sell_outfits.xml +++ b/data/conf/shops/sell_outfits.xml @@ -16,18 +16,18 @@ --> - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/data/languages/en.txt b/data/languages/en.txt deleted file mode 100644 index 2368fd02bd..0000000000 --- a/data/languages/en.txt +++ /dev/null @@ -1,1779 +0,0 @@ -wooden arrow=wooden arrow -You see a wooden arrow. With a supply of arrows in one hand and a bow in the other you can attack from a distance.=You see a wooden arrow. With a supply of arrows in one hand and a bow in the other you can attack from a distance. -steel arrow=steel arrow -You see a shining steel arrow. With plenty of these, and a good bow, you'll be a formidable archer.=You see a shining steel arrow. With plenty of these, and a good bow, you'll be a formidable archer. -golden arrow=golden arrow -You see a golden arrow. With a fist full of these and a good bow, you will surely take down your enemies quicker.=You see a golden arrow. With a fist full of these and a good bow, you will surely take down your enemies quicker. -power arrow=power arrow -You see a power arrow. The sting of this arrow will surely defeat your enemies, when equipped with a bow.=You see a power arrow. The sting of this arrow will surely defeat your enemies, when equipped with a bow. -barbarian armor=barbarian armor -You see a suit of barbarian armor. It's dirty and smelly, but still, rather strong.=You see a suit of barbarian armor. It's dirty and smelly, but still, rather strong. -black armor=black armor -You see a suit of black armor. Does it put you in a dark mood?=You see a suit of black armor. Does it put you in a dark mood? -blue armor=blue armor -You see a suit of blue armor. The color is stunning azure.=You see a suit of blue armor. The color is stunning azure. -chain armor=chain armor -You see a suit of chain armor. Perhaps even if you don't need it, you can sell it.=You see a suit of chain armor. Perhaps even if you don't need it, you can sell it. -chaos armor=chaos armor -You see a suit of chaos armor. The reflecting contrasting colors make no sense to you.=You see a suit of chaos armor. The reflecting contrasting colors make no sense to you. -dress=dress -You see a dress, which provides practically no defense. It is barely worth picking up.=You see a dress, which provides practically no defense. It is barely worth picking up. -dwarvish armor=dwarvish armor -You see a dwarvish armor. It's made with ingenuity and skill.=You see a dwarvish armor. It's made with ingenuity and skill. -elvish armor=elvish armor -You see a suit of elvish armor. It's made from tree leaves! But somehow constructed to give strong defense.=You see a suit of elvish armor. It's made from tree leaves! But somehow constructed to give strong defense. -enhanced chainmail=enhanced chainmail -You see a robust suit of enhanced chainmail. The links are closely spaced so even the sharpest point should not pierce it.=You see a robust suit of enhanced chainmail. The links are closely spaced so even the sharpest point should not pierce it. -golden armor=golden armor -You see a suit of golden armor. It's made from the best materials with great attention to detail.=You see a suit of golden armor. It's made from the best materials with great attention to detail. -golden chainmail=golden chainmail -You see a valuable suit of golden chainmail. It is crafted from fine links of expensive metal.=You see a valuable suit of golden chainmail. It is crafted from fine links of expensive metal. -ice armor=ice armor -You see a suit of ice armor, to protect even the coldest of hearts.=You see a suit of ice armor, to protect even the coldest of hearts. -iron scale armor=iron scale armor -You see a suit of iron scale armor, whose rounded scales will protect you from many blows.=You see a suit of iron scale armor, whose rounded scales will protect you from many blows. -leather armor=leather armor -You see a worn suit of leather armor. It will provide only minimal protection.=You see a worn suit of leather armor. It will provide only minimal protection. -leather cuirass=leather cuirass -You see a leather cuirass, well made but still able to be pierced by sharp swords.=You see a leather cuirass, well made but still able to be pierced by sharp swords. -leather scale armor=leather scale armor -You see a suit of leather scale armor. The leather is toughened somehow for better protection.=You see a suit of leather scale armor. The leather is toughened somehow for better protection. -magic plate armor=magic plate armor -You see a suit of magic plate armor. The steel is well tempered and the suit is very strong.=You see a suit of magic plate armor. The steel is well tempered and the suit is very strong. -mainio armor=mainio armor -You see a strange strange suit of mainio armor. What it lacks in subtlety it makes up for with strength.=You see a strange strange suit of mainio armor. What it lacks in subtlety it makes up for with strength. -mithril armor=mithril armor -You see a suit of mithril armor. Its tightly woven links are truly amazing.=You see a suit of mithril armor. Its tightly woven links are truly amazing. -pauldroned iron cuirass=pauldroned iron cuirass -You see a formidable pauldroned iron cuirass. The pauldrons (shoulder pieces) will protect your upper half.=You see a formidable pauldroned iron cuirass. The pauldrons (shoulder pieces) will protect your upper half. -pauldroned leather cuirass=pauldroned leather cuirass -You see a pauldroned leather cuirass. It was crafted to withstand minor attacks.=You see a pauldroned leather cuirass. It was crafted to withstand minor attacks. -plate armor=plate armor -You see a suit of plate armor. You imagine it would be heavy but the steel is well tempered and the suit is very strong.=You see a suit of plate armor. You imagine it would be heavy but the steel is well tempered and the suit is very strong. -red armor=red armor -You see a suit of red armor. The color is a striking crimson.=You see a suit of red armor. The color is a striking crimson. -scale armor=scale armor -You see a suit of scale armor. It is not flexible like chain, but will be a better defense.=You see a suit of scale armor. It is not flexible like chain, but will be a better defense. -shadow armor=shadow armor -You see a suit of shadow armor. It's hard to decide with what it is made, it is so dark.=You see a suit of shadow armor. It's hard to decide with what it is made, it is so dark. -stone armor=stone armor -You see a suit of stone armor. Swords will crumble if they try to pierce it.=You see a suit of stone armor. Swords will crumble if they try to pierce it. -studded armor=studded armor -You see a suit of studded armor. Some studs are loose or rusty but it is still fit for purpose.=You see a suit of studded armor. Some studs are loose or rusty but it is still fit for purpose. -xeno armor=xeno armor -You see a suit of xeno armor. The colors remind you of the xenocium hybrid creature.=You see a suit of xeno armor. The colors remind you of the xenocium hybrid creature. -axe=axe -You see a rusty but heavy axe. What it lacks in sharpness it makes up for in weight.=You see a rusty but heavy axe. What it lacks in sharpness it makes up for in weight. -bardiche=bardiche -You see a rare and powerful bardiche.=You see a rare and powerful bardiche. -battle axe=battle axe -You see a battle axe. It is well crafted and sharp despite its age.=You see a battle axe. It is well crafted and sharp despite its age. -black halberd=black halberd -You see a black halberd. It's immensely heavy so will make huge blows but you can only swing it at a very slow rate.=You see a black halberd. It's immensely heavy so will make huge blows but you can only swing it at a very slow rate. -black scythe=black scythe -You see a black scythe. Its attack is awesome for a farming tool.=You see a black scythe. Its attack is awesome for a farming tool. -chaos axe=chaos axe -You see a chaos axe. The handle is some alloy you can't recognize and the blade is golden colored, but thinking too long about it makes you confused.=You see a chaos axe. The handle is some alloy you can't recognize and the blade is golden colored, but thinking too long about it makes you confused. -durin axe=durin axe -You see a durin axe, a great prize from the dwarves.=You see a durin axe, a great prize from the dwarves. -golden twoside axe=golden twoside axe -You see a golden twoside axe. This axe is a rare find.=You see a golden twoside axe. This axe is a rare find. -halberd=halberd -You see a rare halberd, with an axe's blade fused onto a shaft. It is both heavy and keen-edged.=You see a rare halberd, with an axe's blade fused onto a shaft. It is both heavy and keen-edged. -hand axe=hand axe -You see a hand axe, well worn from years of use but may prove useful.=You see a hand axe, well worn from years of use but may prove useful. -magic twoside axe=magic twoside axe -You see a magic twoside axe. The nature of the magic isn't clear to you.=You see a magic twoside axe. The nature of the magic isn't clear to you. -old scythe=old scythe -You see an old scythe, it used to be weapon of death. But now it is only a useful tool for farming.=You see an old scythe, it used to be weapon of death. But now it is only a useful tool for farming. -scythe=scythe -You see the weapon of death - a scythe. It looks dangerous but perhaps also a useful tool for farming.=You see the weapon of death - a scythe. It looks dangerous but perhaps also a useful tool for farming. -sickle=sickle -You see a sickle, it is a useful tool for farming.=You see a sickle, it is a useful tool for farming. -small axe=small axe -You see a small axe. It appears well made but you suspect it is too light to do much damage.=You see a small axe. It appears well made but you suspect it is too light to do much damage. -twoside axe=twoside axe -You see a well crafted twoside axe.=You see a well crafted twoside axe. -black book=black book -You see the black book that Jynath kept out for so long.=You see the black book that Jynath kept out for so long. -blue book=blue book -You see a book with the words GEMS AND MINERALS on the front cover.=You see a book with the words GEMS AND MINERALS on the front cover. -black boots=black boots -You stub your toe on this pair of black boots. Perhaps they bring you bad luck?=You stub your toe on this pair of black boots. Perhaps they bring you bad luck? -blue boots=blue boots -You see a pair of blue boots.=You see a pair of blue boots. -chain boots=chain boots -You see a pair of chain boots, quite rusty but still strong.=You see a pair of chain boots, quite rusty but still strong. -chaos boots=chaos boots -You see a pair of chaos boots. Your steps are unsteady in them, but the extra defense they give is worth it.=You see a pair of chaos boots. Your steps are unsteady in them, but the extra defense they give is worth it. -elvish boots=elvish boots -You see a pair of elvish boots.=You see a pair of elvish boots. -golden boots=golden boots -You see a pair of golden boots. They will be heavy to wear but well worth their weight.=You see a pair of golden boots. They will be heavy to wear but well worth their weight. -green thing boots=green thing boots -You see a pair of green thing boots. They're slimy inside, ewwww.=You see a pair of green thing boots. They're slimy inside, ewwww. -killer boots=killer boots -You see a high heeled pair of killer boots. Are you brave enough to wear them?=You see a high heeled pair of killer boots. Are you brave enough to wear them? -leather boots=leather boots -You see a simple pair of leather boots to give your feet some protection.=You see a simple pair of leather boots to give your feet some protection. -mainio boots=mainio boots -You see a pair of mainio boots. It's hard to believe that boots looking like this would protect your feet so well as they do.=You see a pair of mainio boots. It's hard to believe that boots looking like this would protect your feet so well as they do. -mithril boots=mithril boots -You see a sparkling set of mithril boots.=You see a sparkling set of mithril boots. -red boots=red boots -You see a pair of red boots, the color reminds you of fresh blood.=You see a pair of red boots, the color reminds you of fresh blood. -shadow boots=shadow boots -You see a pair of shadow boots. You can't fathom what they are made of.=You see a pair of shadow boots. You can't fathom what they are made of. -steel boots=steel boots -You see a pair of steel boots, fit for any soldier.=You see a pair of steel boots, fit for any soldier. -stone boots=stone boots -You see a pair of stone boots. They may be heavy, but they protect your feet well.=You see a pair of stone boots. They may be heavy, but they protect your feet well. -studded boots=studded boots -You see a pair of worn studded boots with moderate protection for your feet.=You see a pair of worn studded boots with moderate protection for your feet. -xeno boots=xeno boots -You see a fine specimen of craftmanship, a pair of xeno boots.=You see a fine specimen of craftmanship, a pair of xeno boots. -present=present -You see a present. Open it.=You see a present. Open it. -basket=basket -You see an easter basket. Look inside.=You see an easter basket. Look inside. -stocking=stocking -You see a christmas stocking. Look inside.=You see a christmas stocking. Look inside. -black cloak=black cloak -You see a black cloak. The last owner had black thoughts. Will you?=You see a black cloak. The last owner had black thoughts. Will you? -black dragon cloak=black dragon cloak -You see a torn section from an enormous black dragon cloak. You're very lucky: the piece is just the right size for a human to wear.=You see a torn section from an enormous black dragon cloak. You're very lucky: the piece is just the right size for a human to wear. -blue dragon cloak=blue dragon cloak -You see a blue dragon cloak. The weave is tight and strong, though you wish the color drew less attention.=You see a blue dragon cloak. The weave is tight and strong, though you wish the color drew less attention. -blue elf cloak=blue elf cloak -You see a blue elf cloak. The material is thick and warm.=You see a blue elf cloak. The material is thick and warm. -blue striped cloak=blue striped cloak -You see a fashionable blue striped cloak.=You see a fashionable blue striped cloak. -bone dragon cloak=bone dragon cloak -You see a bone dragon cloak. It's incredibly old, like its last owner.=You see a bone dragon cloak. It's incredibly old, like its last owner. -chaos cloak=chaos cloak -You see a chaos cloak. The colors look like oil shimmering on a puddle.=You see a chaos cloak. The colors look like oil shimmering on a puddle. -cloak=cloak -You see a simple cloth cloak.=You see a simple cloth cloak. -dwarf cloak=dwarf cloak -You see a dwarf cloak. Apparently this passes for fashion in some parts, but to you it looks more functional than fashionable.=You see a dwarf cloak. Apparently this passes for fashion in some parts, but to you it looks more functional than fashionable. -elf cloak=elf cloak -You see a white elf cloak. It doesn't look very warm.=You see a white elf cloak. It doesn't look very warm. -elvish cloak=elvish cloak -You see an elvish cloak. The material is dark, but thick and warm.=You see an elvish cloak. The material is dark, but thick and warm. -golden cloak=golden cloak -You see a golden cloak. See how the light glistens off the gold, it is a prized possession.=You see a golden cloak. See how the light glistens off the gold, it is a prized possession. -green dragon cloak=green dragon cloak -You see a huge green dragon cloak. It's probably too big for you but, even dragging on the floor it will provide excellent protection.=You see a huge green dragon cloak. It's probably too big for you but, even dragging on the floor it will provide excellent protection. -lich cloak=lich cloak -You see a purple lich cloak. It's stained with blood - yours, from the previous undead owner, or perhaps the blood of his enemies...=You see a purple lich cloak. It's stained with blood - yours, from the previous undead owner, or perhaps the blood of his enemies... -mainio cloak=mainio cloak -You see a mainio cloak. Its flashing colors are absurd! But you wear it because it's very well made.=You see a mainio cloak. Its flashing colors are absurd! But you wear it because it's very well made. -mithril cloak=mithril cloak -You see a mithril cloak. Its weave is so fine and its looks are breathtaking.=You see a mithril cloak. Its weave is so fine and its looks are breathtaking. -red cloak=red cloak -You see a red cloak covered in polka dots. It's terribly snazzy.=You see a red cloak covered in polka dots. It's terribly snazzy. -red dragon cloak=red dragon cloak -You see a red dragon cloak. He didn't have one for a long time but finally caved in to peer pressure and got one.=You see a red dragon cloak. He didn't have one for a long time but finally caved in to peer pressure and got one. -rift cloak=rift cloak -You see a rift cloak. Almost useless in the battle, but saves your life when you enter a magical rift.=You see a rift cloak. Almost useless in the battle, but saves your life when you enter a magical rift. -shadow cloak=shadow cloak -You see a shadow cloak, in colors of the shadow armor.=You see a shadow cloak, in colors of the shadow armor. -stone cloak=stone cloak -You see a stone cloak. It's hard to tell how it was made, as the fibres look like narrow threads of mineral.=You see a stone cloak. It's hard to tell how it was made, as the fibres look like narrow threads of mineral. -vampire cloak=vampire cloak -You see a vibrant red vampire cloak. Wearing it sends a shiver down your spine.=You see a vibrant red vampire cloak. Wearing it sends a shiver down your spine. -xeno cloak=xeno cloak -You see a xeno cloak. It's heavy and thick, giving you a good protection.=You see a xeno cloak. It's heavy and thick, giving you a good protection. -chaos hammer=chaos hammer -You see a chaos hammer. It is a fearsome weapon.=You see a chaos hammer. It is a fearsome weapon. -club=club -You see a wooden club. It won't kill much but is better than no weapon.=You see a wooden club. It won't kill much but is better than no weapon. -club of thorns=club of thorns -You see the club of thorns. The thorns are full with blood of tortured humans. It is hugely powerful but will drain your life to use it.=You see the club of thorns. The thorns are full with blood of tortured humans. It is hugely powerful but will drain your life to use it. -enhanced mace=enhanced mace -You see an enhanced mace. The heavy attacking end makes it hard to balance.=You see an enhanced mace. The heavy attacking end makes it hard to balance. -flail=flail -You see a flail. Swinging it in circles will tire you but it will be an effective weapon.=You see a flail. Swinging it in circles will tire you but it will be an effective weapon. -golden hammer=golden hammer -You see a valuable golden hammer. This golden weapon may be worth a lot to someone. It has a slow rate of attack.=You see a valuable golden hammer. This golden weapon may be worth a lot to someone. It has a slow rate of attack. -golden mace=golden mace -You see a shining golden mace. Despite looking ornamental, it is sharp enough to do some damage.=You see a shining golden mace. Despite looking ornamental, it is sharp enough to do some damage. -hammer=hammer -You see a common hammer. It is heavy enough that you should be able to bludgeon your enemies with it, but only slowly=You see a common hammer. It is heavy enough that you should be able to bludgeon your enemies with it, but only slowly -ice war hammer=ice war hammer -You see a ice war hammer. The last owner must have been huge, to wield it.=You see a ice war hammer. The last owner must have been huge, to wield it. -mace=mace -You see a mace, much neglected in years of use.=You see a mace, much neglected in years of use. -morning star=morning star -You see a morning star. This is a very rare item indeed.=You see a morning star. This is a very rare item indeed. -necromancers staff=necromancers staff -You see a cunningly beautiful staff. If you listen carefully you can hear its previous owners' souls screaming from within it.=You see a cunningly beautiful staff. If you listen carefully you can hear its previous owners' souls screaming from within it. -rod of the gm=rod of the gm -You see a rod of the GM. This rod is reserved for those with special powers, be careful not to let it fall into the wrong hands.=You see a rod of the GM. This rod is reserved for those with special powers, be careful not to let it fall into the wrong hands. -skull staff=skull staff -You see a rare skull staff. It appears mystical but will work like any other staff.=You see a rare skull staff. It appears mystical but will work like any other staff. -staff=staff -You see a staff. This is a seldom seen or used weapon.=You see a staff. This is a seldom seen or used weapon. -ugmash=ugmash -You see a huge ugmash. It can attack with a faster rate than many other clubs.=You see a huge ugmash. It can attack with a faster rate than many other clubs. -vulcano hammer=vulcano hammer -You see a vulcano hammer. It's said to be a weapon of a God, but attacks with a snail like pace.=You see a vulcano hammer. It's said to be a weapon of a God, but attacks with a snail like pace. -war hammer=war hammer -You see a huge war hammer. It seems strong but is so heavy that you can only lift it to attack every so often..=You see a huge war hammer. It seems strong but is so heavy that you can only lift it to attack every so often.. -empty sack=empty sack -You see an empty sack. It will not stand up, unless you fill it first.=You see an empty sack. It will not stand up, unless you fill it first. -flask=flask -You see a flask. This flask is good for storing medicine.=You see a flask. This flask is good for storing medicine. -eared bottle=eared bottle -You see an eared bottle. It is empty, someone drank the contents.=You see an eared bottle. It is empty, someone drank the contents. -fat bottle=fat bottle -You see a fat bottle. It is empty, someone drank the contents.=You see a fat bottle. It is empty, someone drank the contents. -slim bottle=slim bottle -You see a slim bottle. It is empty, someone drank the contents.=You see a slim bottle. It is empty, someone drank the contents. -empty goblet=empty goblet -You see an empty goblet. You wonder what could it hold.=You see an empty goblet. You wonder what could it hold. -goblet=goblet -You see a goblet. You look at its contents, and shiver at the blood inside.=You see a goblet. You look at its contents, and shiver at the blood inside. -keyring=keyring -You see a keyring.=You see a keyring. -red emotion crystal=red emotion crystal -You see a red emotion crystal. It feels hot to the touch.=You see a red emotion crystal. It feels hot to the touch. -purple emotion crystal=purple emotion crystal -You see a purple emotion crystal. It feels cold to the touch.=You see a purple emotion crystal. It feels cold to the touch. -yellow emotion crystal=yellow emotion crystal -You see a yellow emotion crystal. It energizes you.=You see a yellow emotion crystal. It energizes you. -pink emotion crystal=pink emotion crystal -You see a pink emotion crystal. It almost feels soft.=You see a pink emotion crystal. It almost feels soft. -blue emotion crystal=blue emotion crystal -You see a blue emotion crystal. While holding it your worries seem to disappear.=You see a blue emotion crystal. While holding it your worries seem to disappear. -note=note -You see a note.=You see a note. -coupon=coupon -You see a coupon.=You see a coupon. -closed envelope=closed envelope -You see a closed envelope.=You see a closed envelope. -opened envelope=opened envelope -You see an opened envelope.=You see an opened envelope. -sealed envelope=sealed envelope -You see a sealed envelope.=You see a sealed envelope. -unsealed envelope=unsealed envelope -You see an unsealed envelope.=You see an unsealed envelope. -assassins id=assassins id -You see an assassin's ID. Perhaps this can be put to good use.=You see an assassin's ID. Perhaps this can be put to good use. -map=map -You see a map.=You see a map. -blank scroll=blank scroll -You see a rolled up blank scroll.=You see a rolled up blank scroll. -ice scroll=ice scroll -You see a scroll, circled by ice crystals. You can feel its cold aura.=You see a scroll, circled by ice crystals. You can feel its cold aura. -fierywater=fierywater -You see a bottle of fierywater. Do not drink it pure, you would evaporate!=You see a bottle of fierywater. Do not drink it pure, you would evaporate! -mild koboldish torcibud=mild koboldish torcibud -You see a bottle of mild koboldish torcibud. It will help you recover when you're all bruised and battered.=You see a bottle of mild koboldish torcibud. It will help you recover when you're all bruised and battered. -strong koboldish torcibud=strong koboldish torcibud -You see a bottle of strong koboldish torcibud. It will help you recover when you're all bruised and battered.=You see a bottle of strong koboldish torcibud. It will help you recover when you're all bruised and battered. -vsop koboldish torcibud=vsop koboldish torcibud -You see a bottle of VSOP koboldish torcibud, specially aged in oak casks wrapped by the darkness of a dungeon cellar. It will help you recover when you're all bruised and battered.=You see a bottle of VSOP koboldish torcibud, specially aged in oak casks wrapped by the darkness of a dungeon cellar. It will help you recover when you're all bruised and battered. -apple juice=apple juice -You see apple juice. On a hot summer day, it would taste great.=You see apple juice. On a hot summer day, it would taste great. -beer=beer -You see a beer. On a hot summer day, it would taste great.=You see a beer. On a hot summer day, it would taste great. -wine=wine -You see a glass of red wine. It will quench your thirst.=You see a glass of red wine. It will quench your thirst. -water=water -You see a bottle of fresh spring water. It's really tasty and fresh.=You see a bottle of fresh spring water. It's really tasty and fresh. -pina colada=pina colada -You see a pina colada. It looks delicious but tastes dangerously strong!=You see a pina colada. It looks delicious but tastes dangerously strong! -tea=tea -You see a lovely cup of tea.=You see a lovely cup of tea. -milk=milk -You see a small bottle of fresh milk.=You see a small bottle of fresh milk. -antidote=antidote -You see an antidote, to cure you of poison and protect you for a while.=You see an antidote, to cure you of poison and protect you for a while. -greater antidote=greater antidote -You see a greater antidote, to cure you of poison and protect you for a long while.=You see a greater antidote, to cure you of poison and protect you for a long while. -mana=mana -You see an bottle of mana, to increase your mana rapidly.=You see an bottle of mana, to increase your mana rapidly. -poison=poison -You see a bottle of poison. Don't drink it!=You see a bottle of poison. Don't drink it! -greater poison=greater poison -You see a greater poison. Don't drink it!=You see a greater poison. Don't drink it! -deadly poison=deadly poison -You see a deadly poison. Don't drink it!=You see a deadly poison. Don't drink it! -disease poison=disease poison -You see a disease poison. Be careful, it's wicked.=You see a disease poison. Be careful, it's wicked. -mega poison=mega poison -You see a mega poison. This is some wicked poison.=You see a mega poison. This is some wicked poison. -twilight elixir=twilight elixir -You see a bottle of medicinal elixir.=You see a bottle of medicinal elixir. -minor potion=minor potion -You see a minor potion. It's not much but may save you in a tight spot.=You see a minor potion. It's not much but may save you in a tight spot. -soup=soup -You see a bowl of soup. Its contents fill you up.=You see a bowl of soup. Its contents fill you up. -fish soup=fish soup -You see a bowl of fish soup. Its contents fill you up.=You see a bowl of fish soup. Its contents fill you up. -potion=potion -You see a potion. Take it to battle and you may survive longer.=You see a potion. Take it to battle and you may survive longer. -greater potion=greater potion -You see a greater potion, invaluable in battle.=You see a greater potion, invaluable in battle. -mega potion=mega potion -You see a mega potion. It strengthens greatly.=You see a mega potion. It strengthens greatly. -love potion=love potion -You see a love potion. It looks powerful so take care to use it carefully!=You see a love potion. It looks powerful so take care to use it carefully! -rhosyd=rhosyd -You see a beautiful and rare rhosyd flower.=You see a beautiful and rare rhosyd flower. -rose=rose -You see a single red rose. It smells heavenly.=You see a single red rose. It smells heavenly. -lilia=lilia -You see a lilia, a single precious bloom.=You see a lilia, a single precious bloom. -daisies=daisies -You see a bunch of daisies. They are cheap and cheerful!=You see a bunch of daisies. They are cheap and cheerful! -pansy=pansy -You see a pansy, so colorful and beautiful.=You see a pansy, so colorful and beautiful. -zantedeschia=zantedeschia -You see a zantedeschia, a delicate bloom with the sweetest fragrance.=You see a zantedeschia, a delicate bloom with the sweetest fragrance. -apple=apple -You see a nice shiny apple.=You see a nice shiny apple. -black apple=black apple -You see a black apple. It must be a gift from lil Johnnnny. He's the guy with black items.=You see a black apple. It must be a gift from lil Johnnnny. He's the guy with black items. -purple apple=purple apple -You see a purple apple. It is loaded with memories= -banana=banana -You see a banana. Take care of monkeys when you have them in your bag!=You see a banana. Take care of monkeys when you have them in your bag! -cherry=cherry -You see a cherry. Its tangy taste revives you a little.=You see a cherry. Its tangy taste revives you a little. -coconut=coconut -You see a coconut. Luckily it's already cracked and you can drink from it.=You see a coconut. Luckily it's already cracked and you can drink from it. -grapes=grapes -You see a bunch of grapes. They look quite juicy and tasty.=You see a bunch of grapes. They look quite juicy and tasty. -olive=olive -You see a small ripe olive. It can be eaten or used to create olive oil.=You see a small ripe olive. It can be eaten or used to create olive oil. -pear=pear -You see a pear. Its lovely smell convinces you of taking it.=You see a pear. Its lovely smell convinces you of taking it. -pineapple=pineapple -You see a tropical pineapple.=You see a tropical pineapple. -pomegranate=pomegranate -You see a pomegranate. If you eat it, you will be in a great shape soon.=You see a pomegranate. If you eat it, you will be in a great shape soon. -watermelon=watermelon -You see a watermelon. It freshes your throat during hot summer days.=You see a watermelon. It freshes your throat during hot summer days. -artichoke=artichoke -You see an artichoke. It can be an ingredient, or you can eat it.=You see an artichoke. It can be an ingredient, or you can eat it. -broccoli=broccoli -You see a bunch of broccoli.=You see a bunch of broccoli. -carrot=carrot -You see a carrot. It can be an ingredient, or you can eat it.=You see a carrot. It can be an ingredient, or you can eat it. -cauliflower=cauliflower -You see a fresh head of cauliflower.=You see a fresh head of cauliflower. -courgette=courgette -You see a courgette. It can be an ingredient, or you can eat it.=You see a courgette. It can be an ingredient, or you can eat it. -collard=collard -You see a collard. It's rare and strangely magical.=You see a collard. It's rare and strangely magical. -garlic=garlic -You see some fresh garlic.=You see some fresh garlic. -leek=leek -You see a fresh leek.=You see a fresh leek. -onion=onion -You see an onion.=You see an onion. -salad=salad -You see a salad.=You see a salad. -spinach=spinach -You see spinach. It is enriched with vitamins.=You see spinach. It is enriched with vitamins. -tomato=tomato -You see a tomato, put it in a salad or eat it.=You see a tomato, put it in a salad or eat it. -egg=egg -You see an egg.=You see an egg. -button mushroom=button mushroom -You see a button mushroom. It can be an ingredient, or you can eat it.=You see a button mushroom. It can be an ingredient, or you can eat it. -porcini=porcini -You see a porcini. It can be an ingredient, or you can eat it.=You see a porcini. It can be an ingredient, or you can eat it. -toadstool=toadstool -You see a toadstool. Beware!=You see a toadstool. Beware! -char=char -You see a char, with a fleshy pink belly.=You see a char, with a fleshy pink belly. -cod=cod -You see a cod.=You see a cod. -mackerel=mackerel -You see a mackerel.=You see a mackerel. -perch=perch -You see a perch.=You see a perch. -roach=roach -You see a roach.=You see a roach. -clownfish=clownfish -You see a striped clownfish.=You see a striped clownfish. -red lionfish=red lionfish -You see a venomous coral reef red lionfish.=You see a venomous coral reef red lionfish. -surgeonfish=surgeonfish -You see a bright blue surgeonfish.=You see a bright blue surgeonfish. -trout=trout -You see a trout.=You see a trout. -bread=bread -You see a loaf of bread. It is freshly baked and ready to eat.=You see a loaf of bread. It is freshly baked and ready to eat. -crepes suzette=crepes suzette -You see a crepes suzette topped with hot chocolate sauce.=You see a crepes suzette topped with hot chocolate sauce. -apple pie=apple pie -You see a delicious apple pie, made with only genuine ingredients.=You see a delicious apple pie, made with only genuine ingredients. -cherry pie=cherry pie -You see a delicious cherry pie, made with only genuine ingredients.=You see a delicious cherry pie, made with only genuine ingredients. -pie=pie -You see a meat and vegetable pie.=You see a meat and vegetable pie. -fish pie=fish pie -You see a fish and leek pie. It's not too stodgy so you can eat it faster than a meat pie.=You see a fish and leek pie. It's not too stodgy so you can eat it faster than a meat pie. -sandwich=sandwich -You see a ham and cheese sandwich.=You see a ham and cheese sandwich. -smoked ham=smoked ham -You see a smoked ham.=You see a smoked ham. -smoked meat=smoked meat -You see a piece of smoked meat.=You see a piece of smoked meat. -smoked cod=smoked cod -You see a smoked cod.=You see a smoked cod. -smoked trout=smoked trout -You see a smoked trout.=You see a smoked trout. -sugar=sugar -You see a sack of raw sugar.=You see a sack of raw sugar. -butter=butter -You see some butter. It is an ingredient or can be eaten if you like.=You see some butter. It is an ingredient or can be eaten if you like. -honey=honey -You see a jar of sticky sweet honey. It's the clear runny kind.=You see a jar of sticky sweet honey. It's the clear runny kind. -icecream=icecream -You see a wafer cone with vanilla ice cream and a chocolate flake.=You see a wafer cone with vanilla ice cream and a chocolate flake. -licorice=licorice -You see some delicious licorice. Your mouth waters.=You see some delicious licorice. Your mouth waters. -hotdog=hotdog -You see what looks like a tasty hotdog on a bun.=You see what looks like a tasty hotdog on a bun. -cheeseydog=cheeseydog -You see what looks like a tasty cheeseydog on a bun.=You see what looks like a tasty cheeseydog on a bun. -cheese sausage=cheese sausage -You see what looks like a tasty cheese sausage.=You see what looks like a tasty cheese sausage. -sausage=sausage -You see what looks like a tasty sausage.=You see what looks like a tasty sausage. -tuna sandwich=tuna sandwich -You see what looks like tuna sandwich.=You see what looks like tuna sandwich. -chocolate shake=chocolate shake -You see a scrumptious chocolate shake. Absolutely NO calories.=You see a scrumptious chocolate shake. Absolutely NO calories. -vanilla shake=vanilla shake -You see a yummy vanilla shake. Absolutely NO calories.=You see a yummy vanilla shake. Absolutely NO calories. -cheese=cheese -You see some cheese. Eat it and see what happens.=You see some cheese. Eat it and see what happens. -chicken=chicken -You see a tasty leg of chicken.=You see a tasty leg of chicken. -chocolate bar=chocolate bar -You see an absolutely yummy chocolate bar.=You see an absolutely yummy chocolate bar. -fairy cake=fairy cake -You see a luscious fairy cake with icing and sprinkles on top.=You see a luscious fairy cake with icing and sprinkles on top. -ham=ham -You see a juicy ham.=You see a juicy ham. -meat=meat -You see a piece of meat.=You see a piece of meat. -easter egg=easter egg -You see a lovely chocolate easter egg. It's huge!=You see a lovely chocolate easter egg. It's huge! -small easter egg=small easter egg -You see a small chocolate easter egg. Yummy!=You see a small chocolate easter egg. Yummy! -grilled steak=grilled steak -You see a fresh grilled steak. It smells awesome and is really juicy.=You see a fresh grilled steak. It smells awesome and is really juicy. -pizza=pizza -You see a tasty pizza, and hope they put extra cheese on it.=You see a tasty pizza, and hope they put extra cheese on it. -leather helmet=leather helmet -You see a leather helmet, it seems more like a hat than a defensive item.=You see a leather helmet, it seems more like a hat than a defensive item. -trophy helmet=trophy helmet -You see a Trophy Helmet. Those who were brave and finished the death match, win this helmet.=You see a Trophy Helmet. Those who were brave and finished the death match, win this helmet. -robins hat=robins hat -You see a robins hat. It's cute but that's about it.=You see a robins hat. It's cute but that's about it. -studded helmet=studded helmet -You see a studded helmet, it should protect your head against cuts but perhaps not hard blows.=You see a studded helmet, it should protect your head against cuts but perhaps not hard blows. -elvish hat=elvish hat -You see an elvish hat. The elf craftmanship has made the leafy material stronger than one would think.=You see an elvish hat. The elf craftmanship has made the leafy material stronger than one would think. -chain helmet=chain helmet -You see a solid chain helmet, it will be excellent protection for your head.=You see a solid chain helmet, it will be excellent protection for your head. -viking helmet=viking helmet -You see a viking helmet. The previous owner wore it to many battles.=You see a viking helmet. The previous owner wore it to many battles. -aventail=aventail -You see a aventail. It's a clever design.=You see a aventail. It's a clever design. -blue helmet=blue helmet -You see a brand new blue helmet.=You see a brand new blue helmet. -red helmet=red helmet -You see a red helmet, it reminds you of a jester's hat but you can be sure it has better protection than that.=You see a red helmet, it reminds you of a jester's hat but you can be sure it has better protection than that. -golden helmet=golden helmet -You see a heavy golden helmet. The thick metal should protect you against almost anything.=You see a heavy golden helmet. The thick metal should protect you against almost anything. -horned golden helmet=horned golden helmet -You see a horned golden helmet.=You see a horned golden helmet. -shadow helmet=shadow helmet -You see a heavy shadow helmet. Instead of shining, it darkly gleams.=You see a heavy shadow helmet. Instead of shining, it darkly gleams. -xeno helmet=xeno helmet -You see a xeno helmet.=You see a xeno helmet. -chaos helmet=chaos helmet -You see a chaos helmet.=You see a chaos helmet. -mainio helmet=mainio helmet -You see a strange mainio helmet. You can barely see when wearing it, but it protects your head very well.=You see a strange mainio helmet. You can barely see when wearing it, but it protects your head very well. -magic chain helmet=magic chain helmet -You see a magic chain helmet.=You see a magic chain helmet. -liberty helmet=liberty helmet -You see the liberty helmet. It's light and strong, you feel free wearing it.=You see the liberty helmet. It's light and strong, you feel free wearing it. -black helmet=black helmet -You see a black helmet.=You see a black helmet. -mithril helmet=mithril helmet -You see a mithril helmet.=You see a mithril helmet. -arandula=arandula -You see a arandula. It has medicinal properties, if the right person uses it.=You see a arandula. It has medicinal properties, if the right person uses it. -kokuda=kokuda -You see a kokuda. It has medicinal properties, if the right person uses it.=You see a kokuda. It has medicinal properties, if the right person uses it. -kekik=kekik -You see a kekik. It is a herb which can have healing properties.=You see a kekik. It is a herb which can have healing properties. -mandragora=mandragora -You see a root of mandragora. It has medicinal properties, if the right person uses it.=You see a root of mandragora. It has medicinal properties, if the right person uses it. -sclaria=sclaria -You see the herb sclaria. The smell is calming.=You see the herb sclaria. The smell is calming. -reindeer moss=reindeer moss -You see reindeer moss, something that grows only around Semos. It is quite a delicacy to reindeer, but extremely bitter to humans.=You see reindeer moss, something that grows only around Semos. It is quite a delicacy to reindeer, but extremely bitter to humans. -emerald=emerald -You see an emerald, a precious gemstone.=You see an emerald, a precious gemstone. -sapphire=sapphire -You see a sapphire, a precious gemstone.=You see a sapphire, a precious gemstone. -carbuncle=carbuncle -You see a carbuncle, a precious gemstone related to the garnet.=You see a carbuncle, a precious gemstone related to the garnet. -obsidian=obsidian -You see a piece of obsidian, it's known as volcanic glass because it forms when lava cools rapidly. When used as a gemstone it may look jet black or silvery grey, depending on how it is cut.=You see a piece of obsidian, it's known as volcanic glass because it forms when lava cools rapidly. When used as a gemstone it may look jet black or silvery grey, depending on how it is cut. -diamond=diamond -You see a diamond, not just a beautiful gem but also the hardest naturally occurring material .=You see a diamond, not just a beautiful gem but also the hardest naturally occurring material . -black pearl=black pearl -You see a black pearl, so dark and shiny.=You see a black pearl, so dark and shiny. -dungeon silver key=dungeon silver key -You see a silver dungeon key. Use it only if you are brave.=You see a silver dungeon key. Use it only if you are brave. -lich gold key=lich gold key -You see a golden lich key. It will open the door to terrible things.=You see a golden lich key. It will open the door to terrible things. -minotaur key=minotaur key -You see a fancy bronze key. It smells faintly of minotaur, perhaps one dropped it?=You see a fancy bronze key. It smells faintly of minotaur, perhaps one dropped it? -nalwor bank key=nalwor bank key -You see a key to the customer room of Nalwor Bank.=You see a key to the customer room of Nalwor Bank. -kotoch prison key=kotoch prison key -You see an ancient copper key, enscribed with something in Orcish.=You see an ancient copper key, enscribed with something in Orcish. -kanmararn prison key=kanmararn prison key -You see a key which matches the locks in the Kanmararn prison.=You see a key which matches the locks in the Kanmararn prison. -sedah gate key=sedah gate key -You see a key for the gate to the imperial city of Sedah.=You see a key for the gate to the imperial city of Sedah. -pet sanctuary key=pet sanctuary key -You see a key for the pet sanctuary in Ados.=You see a key for the pet sanctuary in Ados. -house key=house key -You should not see this description=You should not see this description -gate key=gate key -You should not see this description=You should not see this description -master key=master key -You see a master key.=You see a master key. -small key=small key -You see the key for Zara's row house in Ados.=You see the key for Zara's row house in Ados. -black legs=black legs -You see a pair of black legs.=You see a pair of black legs. -blue legs=blue legs -You see a pair of shiny new blue legs.=You see a pair of shiny new blue legs. -chain legs=chain legs -You see a pair of well made chain legs. They should protect you against any attacks aimed low.=You see a pair of well made chain legs. They should protect you against any attacks aimed low. -chaos legs=chaos legs -You see a pair of chaos legs. The strange colors confuse you.=You see a pair of chaos legs. The strange colors confuse you. -dwarvish legs=dwarvish legs -You see a pair of dwarvish legs, designed by master craftsmen.=You see a pair of dwarvish legs, designed by master craftsmen. -elvish legs=elvish legs -You see a pair of elvish legs.=You see a pair of elvish legs. -golden legs=golden legs -You see a pair of shining, strong golden legs.=You see a pair of shining, strong golden legs. -jewelled legs=jewelled legs -You see a beautiful pair of jewelled legs. A large red gem glints at you and they're encrusted all over with sapphires.=You see a beautiful pair of jewelled legs. A large red gem glints at you and they're encrusted all over with sapphires. -leather legs=leather legs -You see a pair of leather legs. They look new but they're poor quality.=You see a pair of leather legs. They look new but they're poor quality. -mainio legs=mainio legs -You see a pair of odd looking mainio legs. They are somewhat startling but they give you a good protection.=You see a pair of odd looking mainio legs. They are somewhat startling but they give you a good protection. -mithril legs=mithril legs -You see a pair of mithril legs. They will serve you well.=You see a pair of mithril legs. They will serve you well. -red legs=red legs -A pair of red legs are in your grasp.=A pair of red legs are in your grasp. -shadow legs=shadow legs -You see a pair of shadow legs. The weight of them is incredible.=You see a pair of shadow legs. The weight of them is incredible. -stone legs=stone legs -You see a pair of stone legs, heavy and durable.=You see a pair of stone legs, heavy and durable. -studded legs=studded legs -You see a pair of blood stained studded legs. You're not sure if the blood came from their last owner ... or his enemies.=You see a pair of blood stained studded legs. You're not sure if the blood came from their last owner ... or his enemies. -xeno legs=xeno legs -You see a pair of xeno legs. They're very strong and look brand new.=You see a pair of xeno legs. They're very strong and look brand new. -bulb=bulb -You see a bulb. It can be planted anywhere, but it will only thrive on fertile ground.=You see a bulb. It can be planted anywhere, but it will only thrive on fertile ground. -seed=seed -You see a seed. It can be planted anywhere, but it will only thrive on fertile ground.=You see a seed. It can be planted anywhere, but it will only thrive on fertile ground. -giant heart=giant heart -You see a giant heart. This proves that a giant was defeated, and has magical properties that you are not sure of.=You see a giant heart. This proves that a giant was defeated, and has magical properties that you are not sure of. -dice=dice -You see a set of dice. May lady luck be with you.=You see a set of dice. May lady luck be with you. -cards=cards -You see a deck of cards. Shuffle them well, to get a winning hand.=You see a deck of cards. Shuffle them well, to get a winning hand. -teddy=teddy -You see a teddy bear. Its soft and cuddly, and children love them.=You see a teddy bear. Its soft and cuddly, and children love them. -rodent trap=rodent trap -You see a rodent trap.=You see a rodent trap. -vampirette entrails=vampirette entrails -You see some vampirette entrails. These guts proves that a vampirette was slain.=You see some vampirette entrails. These guts proves that a vampirette was slain. -bat entrails=bat entrails -You see some bat entrails, full of the blood of a living creature.=You see some bat entrails, full of the blood of a living creature. -suntan cream=suntan cream -You see a suntan cream.=You see a suntan cream. -marbles=marbles -You see some marbles. Boys will be boys.=You see some marbles. Boys will be boys. -canned tuna=canned tuna -You see what appears to be canned tuna. Too bad you don't have a can opener.=You see what appears to be canned tuna. Too bad you don't have a can opener. -snowglobe=snowglobe -You see a snowglobe. It's so festive, it reminds you of Christmas!=You see a snowglobe. It's so festive, it reminds you of Christmas! -bobbin=bobbin -You see a bobbin for a sewing machine.=You see a bobbin for a sewing machine. -venom gland=venom gland -You see the venom gland of a giant cobra. It is filled with deadly toxin.=You see the venom gland of a giant cobra. It is filled with deadly toxin. -snowball=snowball -You see snowballs. You can throw them at your friends or put it in your bag to keep your fish fresh. But be careful, there could be little stones in it.=You see snowballs. You can throw them at your friends or put it in your bag to keep your fish fresh. But be careful, there could be little stones in it. -vomit=vomit -You see a pile of vomit. Gross!=You see a pile of vomit. Gross! -shuriken=shuriken -You see a shuriken. You'll need to be close before you can use it as a missile. Stand square on, not diagonally, for the best results.=You see a shuriken. You'll need to be close before you can use it as a missile. Stand square on, not diagonally, for the best results. -fire shuriken=fire shuriken -You see a fire shuriken. Throwing this flaming missile unleashes fiendish power.=You see a fire shuriken. Throwing this flaming missile unleashes fiendish power. -wooden spear=wooden spear -You see a wooden spear. You can attack at a distance with this primitive weapon. Stand square on, not diagonally, for the best results.=You see a wooden spear. You can attack at a distance with this primitive weapon. Stand square on, not diagonally, for the best results. -money=money -You see a money.=You see a money. -wooden bow=wooden bow -You see a wooden bow. With this in one hand, and wooden arrows in the other, you may be able to attack from a distance.=You see a wooden bow. With this in one hand, and wooden arrows in the other, you may be able to attack from a distance. -longbow=longbow -You see a longbow. Equipped with this, and a supply of arrows, you will have an effective ranged attack.=You see a longbow. Equipped with this, and a supply of arrows, you will have an effective ranged attack. -composite bow=composite bow -You see a composite bow. It's better strung than most bows, for excellent ranged attack.=You see a composite bow. It's better strung than most bows, for excellent ranged attack. -crossbow=crossbow -You see a crossbow. The attack is high but it has a very short range, what do you prefer?=You see a crossbow. The attack is high but it has a very short range, what do you prefer? -hunter crossbow=hunter crossbow -You see a hunter crossbow. You must decide if the high attack is worth the short range it has.=You see a hunter crossbow. You must decide if the high attack is worth the short range it has. -mithril bow=mithril bow -You see a light and strong mithril bow. Its impeccable workmanship affords incredible accuracy.=You see a light and strong mithril bow. Its impeccable workmanship affords incredible accuracy. -wood=wood -You see some wood, fallen from a nearby tree.=You see some wood, fallen from a nearby tree. -sugar cane=sugar cane -You see a sugar cane. You can grind it for raw sugar.=You see a sugar cane. You can grind it for raw sugar. -grain=grain -You see a sheaf of grain.=You see a sheaf of grain. -flour=flour -You see a sack of flour.=You see a sack of flour. -iron ore=iron ore -You see a nugget of iron ore.=You see a nugget of iron ore. -coal=coal -You see a black and shiny piece of stone coal. It stayed in the ground for million of years.=You see a black and shiny piece of stone coal. It stayed in the ground for million of years. -wool=wool -You see wool. It was recently shorn.=You see wool. It was recently shorn. -yarn=yarn -You see a ball of yarn. It was spun with care.=You see a ball of yarn. It was spun with care. -cloth=cloth -You see a roll of cloth. It is of first-class quality.=You see a roll of cloth. It is of first-class quality. -gold nugget=gold nugget -You see a gold nugget. Its rich golden color shines, and it may be useful.=You see a gold nugget. Its rich golden color shines, and it may be useful. -mithril nugget=mithril nugget -You see a mithril nugget. Its pale silvery color shines, and it may be useful.=You see a mithril nugget. Its pale silvery color shines, and it may be useful. -iron=iron -You see a piece of iron.=You see a piece of iron. -gold bar=gold bar -You see a gold bar. It will make beautiful jewelry, or fetch a fine price.=You see a gold bar. It will make beautiful jewelry, or fetch a fine price. -mithril bar=mithril bar -You see a mithril bar. It can be made into excellent armor, or fetch a fine price.=You see a mithril bar. It can be made into excellent armor, or fetch a fine price. -golden egg=golden egg -You see a valuable golden egg.=You see a valuable golden egg. -spotted egg=spotted egg -You see a spotted egg. They are said to be delicious.=You see a spotted egg. They are said to be delicious. -reptile egg=reptile egg -You see a valuable reptile egg.=You see a valuable reptile egg. -silk gland=silk gland -You see a silk gland from a giant spider. Its contents are extremely valuable.=You see a silk gland from a giant spider. Its contents are extremely valuable. -silk thread=silk thread -You see a spool of silk thread, spun from the glands of a giant spider..=You see a spool of silk thread, spun from the glands of a giant spider.. -oil=oil -You see a small can of oil.=You see a small can of oil. -mithril thread=mithril thread -You see a spool of mithril thread, made from silk fused with mithril.=You see a spool of mithril thread, made from silk fused with mithril. -mithril fabric=mithril fabric -You see a couple yards of mithril fabric. It's very sparkly!=You see a couple yards of mithril fabric. It's very sparkly! -horse hair=horse hair -You see some horse hairs.=You see some horse hairs. -beeswax=beeswax -You see a piece of beeswax.=You see a piece of beeswax. -candle=candle -You see a candle in a candle stick.=You see a candle in a candle stick. -angel figurine=angel figurine -You see a sparkling angel figurine.=You see a sparkling angel figurine. -skull ring=skull ring -You see a skull ring.=You see a skull ring. -amulet=amulet -You see an amulet.=You see an amulet. -ankh=ankh -You see a mysterious ankh. You don't know what it is for.=You see a mysterious ankh. You don't know what it is for. -wedding ring=wedding ring -You see a golden wedding ring, a symbol of love.=You see a golden wedding ring, a symbol of love. -engagement ring=engagement ring -You see a diamond engagement ring, a symbol of love (or a symbol of wealth?)=You see a diamond engagement ring, a symbol of love (or a symbol of wealth?) -emerald ring=emerald ring -You see an emerald ring, known as ring of life. Wear it, and you risk less from death.=You see an emerald ring, known as ring of life. Wear it, and you risk less from death. -summon scroll=summon scroll -You see a summon scroll.=You see a summon scroll. -nalwor city scroll=nalwor city scroll -You see a nalwor city scroll, it will take you back to nalwor city.=You see a nalwor city scroll, it will take you back to nalwor city. -ados city scroll=ados city scroll -You see a ados city scroll, it will take you back to ados city.=You see a ados city scroll, it will take you back to ados city. -fado city scroll=fado city scroll -You see a fado city scroll, it will take you back to fado city.=You see a fado city scroll, it will take you back to fado city. -kirdneh city scroll=kirdneh city scroll -You see a kirdneh city scroll, it will take you back to kirdneh city.=You see a kirdneh city scroll, it will take you back to kirdneh city. -kalavan city scroll=kalavan city scroll -You see a kalavan city scroll, it will take you back to kalavan city.=You see a kalavan city scroll, it will take you back to kalavan city. -marked scroll=marked scroll -You see a marked scroll.=You see a marked scroll. -invitation scroll=invitation scroll -You see an invitation scroll.=You see an invitation scroll. -home scroll=home scroll -You see a home scroll, it will take you back to semos city.=You see a home scroll, it will take you back to semos city. -empty scroll=empty scroll -You see an empty scroll. If used correctly it will return you to where you marked it at.=You see an empty scroll. If used correctly it will return you to where you marked it at. -rainbow beans=rainbow beans -You see a small pile of rainbow beans. One says something odd.=You see a small pile of rainbow beans. One says something odd. -balloon=balloon -You see a bright red balloon. You can use it to float high up into the clouds.=You see a bright red balloon. You can use it to float high up into the clouds. -twilight moss=twilight moss -You see a strange looking piece of moss. It looks like it could have hallucinogenic properties.=You see a strange looking piece of moss. It looks like it could have hallucinogenic properties. -lucky charm=lucky charm -You see a lucky charm. Some believe that it helps their karma in some way, but others think that is superstition.=You see a lucky charm. Some believe that it helps their karma in some way, but others think that is superstition. -mithril clasp=mithril clasp -You see a clasp made from mithril, it would provide the fastening for a cloak.=You see a clasp made from mithril, it would provide the fastening for a cloak. -mythical egg=mythical egg -You see a mythical egg, but you do not know, what it is for.=You see a mythical egg, but you do not know, what it is for. -magical eggshells=magical eggshells -You see shards from a mythical egg. They have magical properties.=You see shards from a mythical egg. They have magical properties. -golden gm token=golden gm token -You see a golden gm token.=You see a golden gm token. -silvery gm token=silvery gm token -You see a silvery gm token.=You see a silvery gm token. -bronze gm token=bronze gm token -You see a bronze gm token.=You see a bronze gm token. -black shield=black shield -You see a black shield. Its darkness will surely protect you.=You see a black shield. Its darkness will surely protect you. -blue shield=blue shield -You see a blue shield. It feels light in your hand but is strong enough for excellent protection.=You see a blue shield. It feels light in your hand but is strong enough for excellent protection. -buckler=buckler -You see a small buckler. It may protect you against swords and maces but against stronger weapons you do not trust it.=You see a small buckler. It may protect you against swords and maces but against stronger weapons you do not trust it. -chaos shield=chaos shield -You see a chaos shield. Its charming appearance will fool your enemies.=You see a chaos shield. Its charming appearance will fool your enemies. -crown shield=crown shield -You see a mighty crown shield. It is in perfect condition.=You see a mighty crown shield. It is in perfect condition. -elvish shield=elvish shield -You see an elvish shield. It looks like one big tree leaf!=You see an elvish shield. It looks like one big tree leaf! -enhanced lion shield=enhanced lion shield -An enhanced lion shield glints at you. It will defend you well.=An enhanced lion shield glints at you. It will defend you well. -golden shield=golden shield -You see a golden shield, could it be the ultimate in defense?=You see a golden shield, could it be the ultimate in defense? -green dragon shield=green dragon shield -You see an green dragon shield, scaly and strong.=You see an green dragon shield, scaly and strong. -lion shield=lion shield -You see a valuable lion shield. It is heavy enough to defend you against strong attacks.=You see a valuable lion shield. It is heavy enough to defend you against strong attacks. -magic plate shield=magic plate shield -You see a magic plate shield. It has a magical quality surrounding it.=You see a magic plate shield. It has a magical quality surrounding it. -mainio shield=mainio shield -You see a strange looking mainio shield. You have no idea what it's made of, but it seems to protect you well by distracting your enemies.=You see a strange looking mainio shield. You have no idea what it's made of, but it seems to protect you well by distracting your enemies. -mithril shield=mithril shield -You see a mithril shield. You will most surely be protected.=You see a mithril shield. You will most surely be protected. -plate shield=plate shield -You see a solid plate shield. It is in good condition, and might be worth a little.=You see a solid plate shield. It is in good condition, and might be worth a little. -red shield=red shield -You see a red shield. It is a stunning shade of scarlet.=You see a red shield. It is a stunning shade of scarlet. -shadow shield=shadow shield -You see a shadow shield. Rather than reflecting light, it seems to absorb it.=You see a shadow shield. Rather than reflecting light, it seems to absorb it. -skull shield=skull shield -The face of a skull scowls at you from this fearsome skull shield.=The face of a skull scowls at you from this fearsome skull shield. -studded shield=studded shield -You see a studded shield. It's a little dented but seems robust enough.=You see a studded shield. It's a little dented but seems robust enough. -unicorn shield=unicorn shield -You see a unicorn shield. It's beautifully crafted and will give you excellent protection.=You see a unicorn shield. It's beautifully crafted and will give you excellent protection. -wooden shield=wooden shield -You see a wooden shield. It is made of cheap wood and heavily used. However, it still seems to offer you some kind of basic protection.=You see a wooden shield. It is made of cheap wood and heavily used. However, it still seems to offer you some kind of basic protection. -xeno shield=xeno shield -You see a lightweight but strong xeno shield, in the colors of the creature it was looted from.=You see a lightweight but strong xeno shield, in the colors of the creature it was looted from. -assassin dagger=assassin dagger -You see an assassin dagger, easy to pull out and use when needed.=You see an assassin dagger, easy to pull out and use when needed. -biting sword=biting sword -The point of this biting sword is chiseled to a lethal point. It will serve you well.=The point of this biting sword is chiseled to a lethal point. It will serve you well. -black sword=black sword -You see a black sword. Its darkness will surely protect you.=You see a black sword. Its darkness will surely protect you. -broadsword=broadsword -You see a mighty broadsword. Enemies will see their death.=You see a mighty broadsword. Enemies will see their death. -buster=buster -You see a buster, with its distinctive, oversized blade.=You see a buster, with its distinctive, oversized blade. -chaos dagger=chaos dagger -You see a chaos dagger, it is very lightweight and sharp. It will heal you while hurting them.=You see a chaos dagger, it is very lightweight and sharp. It will heal you while hurting them. -chaos sword=chaos sword -The chaos sword. It hurts them but it heals you.=The chaos sword. It hurts them but it heals you. -claymore=claymore -This claymore is very hard to lift as it is so heavy. It will be a good weapon if you can swing it.=This claymore is very hard to lift as it is so heavy. It will be a good weapon if you can swing it. -dagger=dagger -You see a dagger, it is little more than decorative but you can jab pretty fast with it.=You see a dagger, it is little more than decorative but you can jab pretty fast with it. -dark dagger=dark dagger -You see a dark dagger. It's designed to be used with a fast motion.=You see a dark dagger. It's designed to be used with a fast motion. -demon fire sword=demon fire sword -The demon fire sword, a horrible sword taken from hell's deepest dungeons.=The demon fire sword, a horrible sword taken from hell's deepest dungeons. -demon sword=demon sword -Demon skeletons return from hell with this demon sword.=Demon skeletons return from hell with this demon sword. -drow sword=drow sword -This is a genuine drow sword.=This is a genuine drow sword. -elvish sword=elvish sword -This is an elvish sword. It looks like it is made from tree leaves!=This is an elvish sword. It looks like it is made from tree leaves! -fire sword=fire sword -Flames race along this fire sword as you wield it.=Flames race along this fire sword as you wield it. -golden blade=golden blade -You see the rare and precious golden blade. It can steal life from those you stab with it.=You see the rare and precious golden blade. It can steal life from those you stab with it. -golden orc sword=golden orc sword -You see a golden orc sword, with its heavy and deadly appearance it seems also valuable.=You see a golden orc sword, with its heavy and deadly appearance it seems also valuable. -great sword=great sword -This rare great sword is heavy and of good craftsmanship. It can only attack incredibly slowly.=This rare great sword is heavy and of good craftsmanship. It can only attack incredibly slowly. -hell dagger=hell dagger -You see a hell dagger. Is the color that of the burning fires of Hell?=You see a hell dagger. Is the color that of the burning fires of Hell? -ice sword=ice sword -You see an ice sword, the blade is lethally cold to the touch, and you know it is one of the best weapons you'll find.=You see an ice sword, the blade is lethally cold to the touch, and you know it is one of the best weapons you'll find. -imperator sword=imperator sword -You see a an imperator sword, crafted from you know not what.=You see a an imperator sword, crafted from you know not what. -immortal sword=immortal sword -This immortal sword is a gift of gods to the chosen one. It is heavy and of exquisite craftsmanship.=This immortal sword is a gift of gods to the chosen one. It is heavy and of exquisite craftsmanship. -katana=katana -You see a valuable katana, with a lethal looking blade.=You see a valuable katana, with a lethal looking blade. -knife=knife -You see a blunt knife which looks capable of little damage.=You see a blunt knife which looks capable of little damage. -l hand sword=l hand sword -You see l hand sword. It must be used with right hand sword.=You see l hand sword. It must be used with right hand sword. -night dagger=night dagger -You see a night dagger, to be used with stealth.=You see a night dagger, to be used with stealth. -nihonto=nihonto -You see a samurai's nihonto sword with a razor sharp blade.=You see a samurai's nihonto sword with a razor sharp blade. -obsidian knife=obsidian knife -You see a knife whose blade is crafted to a lethal edge from finest obsidian.=You see a knife whose blade is crafted to a lethal edge from finest obsidian. -orc sword=orc sword -You see an orc sword which has a heavy and deadly appearance.=You see an orc sword which has a heavy and deadly appearance. -orcish sword=orcish sword -This orcish sword is badly done and weighs a lot.=This orcish sword is badly done and weighs a lot. -r hand sword=r hand sword -You see r hand sword. It must be used with a left hand sword.=You see r hand sword. It must be used with a left hand sword. -scimitar=scimitar -This scimitar catches the light as you look at it, it will be a powerful weapon.=This scimitar catches the light as you look at it, it will be a powerful weapon. -short sword=short sword -You see a well kept short sword which will be easy to carry as you travel.=You see a well kept short sword which will be easy to carry as you travel. -soul dagger=soul dagger -You see a soul dagger. It's so light and sharp that you can stab at a very fast rate.=You see a soul dagger. It's so light and sharp that you can stab at a very fast rate. -sword=sword -You see a sharp sword which might even be worth a little.=You see a sharp sword which might even be worth a little. -vampire sword=vampire sword -You see the vampire sword, which will suck life from those you attack with it.=You see the vampire sword, which will suck life from those you attack with it. -xeno sword=xeno sword -You wield a xeno sword. Despite the great strength it is strangely light.=You wield a xeno sword. Despite the great strength it is strangely light. -arrow game token=arrow game token -You see a token, that when placed in the right spot could point you in the right direction.=You see a token, that when placed in the right spot could point you in the right direction. -o board token=o board token -You see an O token.=You see an O token. -x board token=x board token -You see an X token.=You see an X token. -food mill=food mill -You see a food mill. It is the traditional tool to mill food.=You see a food mill. It is the traditional tool to mill food. -sugar mill=sugar mill -You see a sugar mill. You can obtain sugar if you grind canes with it.=You see a sugar mill. You can obtain sugar if you grind canes with it. -scroll eraser=scroll eraser -You see a scroll eraser. Put a marked scroll in the opposite hand to clean the scroll.=You see a scroll eraser. Put a marked scroll in the opposite hand to clean the scroll. -pestle and mortar=pestle and mortar -You see a pestle and mortar. This is the traditional tool to pound herbs, food and other substances.=You see a pestle and mortar. This is the traditional tool to pound herbs, food and other substances. -gold pan=gold pan -You see a gold pan. It is the traditional tool to prospect for gold.=You see a gold pan. It is the traditional tool to prospect for gold. -fishing rod=fishing rod -You see a fishing rod. If you find a fishing spot you can catch fish with it.=You see a fishing rod. If you find a fishing spot you can catch fish with it. -magical scissors=magical scissors -You see a sparkling pair of magical scissors. Just think what they could cut!=You see a sparkling pair of magical scissors. Just think what they could cut! -magical needle=magical needle -You see a magical needle with a very sharp point.=You see a magical needle with a very sharp point. -pick=pick -You see a pick. It is a tool which helps you to get some coal.=You see a pick. It is a tool which helps you to get some coal. -shovel=shovel -You see a shovel, a tool for digging.=You see a shovel, a tool for digging. -dark angel=dark angel -You see a dark angel. He is an strong evil angel, and for now he is under cover of darkness.=You see a dark angel. He is an strong evil angel, and for now he is under cover of darkness. -My will be done over land and sky!=My will be done over land and sky! -Mine is the Power and the Glory!=Mine is the Power and the Glory! -dark archangel=dark archangel -You see a dark archangel. After she fell from heaven her soul became as dark as she is now.=You see a dark archangel. After she fell from heaven her soul became as dark as she is now. -I will strike you down with more power than you can imagine!!=I will strike you down with more power than you can imagine!! -My will be done!=My will be done! -fallen angel=fallen angel -You see a fallen angel. When he turned away from good, he fell down from heaven.=You see a fallen angel. When he turned away from good, he fell down from heaven. -You are DOOMED.=You are DOOMED. -I am here to destroy all people.=I am here to destroy all people. -baby angel=baby angel -You see a baby angel. Will it ever get a chance to grow up?=You see a baby angel. Will it ever get a chance to grow up? -I want to grow to be mighty!=I want to grow to be mighty! -angel=angel -You see an angel. With his flame sword he guards the way and doesn't let you pass.=You see an angel. With his flame sword he guards the way and doesn't let you pass. -Meet thy maker!=Meet thy maker! -Mere mortals cannot challenge me!=Mere mortals cannot challenge me! -archangel=archangel -You see an archangel. She wants to kill you because she doesn't want you in the clouds.=You see an archangel. She wants to kill you because she doesn't want you in the clouds. -Neither the earth nor the sky has a place for you...=Neither the earth nor the sky has a place for you... -Leave now, before it is too late for you.=Leave now, before it is too late for you. -Your destiny is not here.=Your destiny is not here. -spider=spider -You see a spider. It makes a giant spider web to trap you!=You see a spider. It makes a giant spider web to trap you! -poisonous spider=poisonous spider -You see a poisonous spider. Be careful! Its poison is very strong! It will make you very weak and can probably kill you!=You see a poisonous spider. Be careful! Its poison is very strong! It will make you very weak and can probably kill you! -giant spider=giant spider -You see a giant spider. It has very long legs which are quite dangerous.=You see a giant spider. It has very long legs which are quite dangerous. -crab=crab -You see a little crab that comes from the beach, be careful of its pincers.=You see a little crab that comes from the beach, be careful of its pincers. -click-clack-click=click-clack-click -chick=chick -You see a little chick, it doesn't like to be away from its mother.=You see a little chick, it doesn't like to be away from its mother. -peep peep=peep peep -peep peep peep=peep peep peep -chicken=chicken -You see a chicken, isn't it nice?=You see a chicken, isn't it nice? -cluck cluck=cluck cluck -cluck cluck cluck=cluck cluck cluck -mother hen=mother hen -You see a mother hen, she will probably hurt you if you touch her sons.=You see a mother hen, she will probably hurt you if you touch her sons. -peep peep=peep peep -peep peep peep=peep peep peep -penguin=penguin -You see a penguin, smart and silly.=You see a penguin, smart and silly. -Oou! Oou!=Oou! Oou! -*flap* *flap*=*flap* *flap* -pigeon=pigeon -You see a pigeon, it loves the little seeds.=You see a pigeon, it loves the little seeds. -killer bee=killer bee -You see a killer bee which will sting you to death!=You see a killer bee which will sting you to death! -BbzzZZzzz!=BbzzZZzzz! -deer=deer -You see a defenseless deer, do not have fear of it.=You see a defenseless deer, do not have fear of it. -mouse=mouse -You see a small mouse that seems to be very hungry.=You see a small mouse that seems to be very hungry. -squeak ... squeak!=squeak ... squeak! -rat=rat -You see a small rat that seems to be extremely hungry.=You see a small rat that seems to be extremely hungry. -Squeak! Squeak!=Squeak! Squeak! -bat=bat -You see a bat, it will seek your blood, don't be distracted...=You see a bat, it will seek your blood, don't be distracted... -caverat=caverat -You see a caverat which is dirty and loves the dungeons.=You see a caverat which is dirty and loves the dungeons. -piglet=piglet -You see a piglet, a bit dirty.=You see a piglet, a bit dirty. -shnuffle=shnuffle -oink oink=oink oink -oink=oink -babybear=babybear -You see a babybear, isn't it sweet?=You see a babybear, isn't it sweet? -Roar!=Roar! -beaver=beaver -You see a beaver. It likes to build dams in the river.=You see a beaver. It likes to build dams in the river. -big bad wolf=big bad wolf -You see a big bad wolf. It is really wild, hungry and really dangerous!=You see a big bad wolf. It is really wild, hungry and really dangerous! -Aooooooouuhhh!!=Aooooooouuhhh!! -boar=boar -You see a boar, they are pretty wild!=You see a boar, they are pretty wild! -Oink!!=Oink!! -bull=bull -You see a bull, run away!!!=You see a bull, run away!!! -MOOOOO!!!=MOOOOO!!! -cow=cow -You see a cow, taste her delicious milk.=You see a cow, taste her delicious milk. -mooooo=mooooo -Moooo moooo mooooooo=Moooo moooo mooooooo -fox=fox -You see a fox, it becomes extinct!=You see a fox, it becomes extinct! -goat=goat -You see a goat, which is wild and lives in the mountain.=You see a goat, which is wild and lives in the mountain. -horse=horse -You see a horse, don't slap it or it will run away!=You see a horse, don't slap it or it will run away! -neigghhh neigghhh=neigghhh neigghhh -monkey=monkey -You see a monkey, would you like to offer it a banana?=You see a monkey, would you like to offer it a banana? -Aooooooouuhhh!!=Aooooooouuhhh!! -panda=panda -You see a panda which comes from an exotic land.=You see a panda which comes from an exotic land. -Roar!=Roar! -ram=ram -You see a ram, maybe you are able to shear it?=You see a ram, maybe you are able to shear it? -Baaaaaa=Baaaaaa -white horse=white horse -You see a white horse, they love to stay with their brown friends.=You see a white horse, they love to stay with their brown friends. -naayyy=naayyy -wolf=wolf -You see a wolf. It is wild and very hungry!=You see a wolf. It is wild and very hungry! -Aooooooouuhhh!!=Aooooooouuhhh!! -bear=bear -You see a bear. You are lucky and found it awake.=You see a bear. You are lucky and found it awake. -Roar!=Roar! -lion=lion -You see a lion, dangerous and hungry...=You see a lion, dangerous and hungry... -Grooooaaaarrrr!=Grooooaaaarrrr! -elephant=elephant -You see an elephant, a huge animal with lots of ham and meat. Are you sure that you want to attack it?=You see an elephant, a huge animal with lots of ham and meat. Are you sure that you want to attack it? -WHOOOOEOEOEZZZZZZ!!!=WHOOOOEOEOEZZZZZZ!!! -tiger=tiger -You see a tiger, which likes deer and human meat.=You see a tiger, which likes deer and human meat. -Miiiiaaauuu!=Miiiiaaauuu! -venomrat=venomrat -You see a venomrat, extremely poisonous.=You see a venomrat, extremely poisonous. -killer bat=killer bat -You see a killer bat which will suck your blood!=You see a killer bat which will suck your blood! -black bear=black bear -You see a strong black bear which hibernates in winter.=You see a strong black bear which hibernates in winter. -ROOOOAAAAR!=ROOOOAAAAR! -razorrat=razorrat -You see a razorrat, its noise will make you crazy!=You see a razorrat, its noise will make you crazy! -giantrat=giantrat -You see a giant rat. It seems like it survived from an experiment which went wrong.=You see a giant rat. It seems like it survived from an experiment which went wrong. -unicorn=unicorn -You see a rare unicorn. It is widely believed to be only a myth.=You see a rare unicorn. It is widely believed to be only a myth. -centaur=centaur -You see a noble centaur, half man, half horse. He looks capable of great damage but thankfully seems to mean you no harm.=You see a noble centaur, half man, half horse. He looks capable of great damage but thankfully seems to mean you no harm. -pegasus=pegasus -You see Pegasus, the winged horse. His name means Lightning and he carries bolts of Thunder.=You see Pegasus, the winged horse. His name means Lightning and he carries bolts of Thunder. -caiman=caiman -You see a caiman, which is hidden in the water.=You see a caiman, which is hidden in the water. -Grrr Grrr!=Grrr Grrr! -grass snake=grass snake -You see a grass snake which is quite dangerous.=You see a grass snake which is quite dangerous. -sssSSSsssss...=sssSSSsssss... -snake=snake -You see a snake, it came from the forest to poison you!=You see a snake, it came from the forest to poison you! -SsSssSsssssss!=SsSssSsssssss! -crocodile=crocodile -You see a crocodile, it arises from the deep waters in the rivers.=You see a crocodile, it arises from the deep waters in the rivers. -Rrrrrrrrr=Rrrrrrrrr -cobra=cobra -You see a cobra.=You see a cobra. -king cobra=king cobra -You see a king cobra. It is extremely venomous.=You see a king cobra. It is extremely venomous. -young beholder=young beholder -You see a young beholder. His huge eye will find you everywhere!=You see a young beholder. His huge eye will find you everywhere! -Defend yourself! I can see you!=Defend yourself! I can see you! -beholder=beholder -You see a beholder which can see you very well with his huge eye...=You see a beholder which can see you very well with his huge eye... -elder beholder=elder beholder -You see an elder beholder, his nasty eye will scare you.=You see an elder beholder, his nasty eye will scare you. -gargoyle=gargoyle -You see a gargoyle, You should think that it is a statue, but no, it is alive!=You see a gargoyle, You should think that it is a statue, but no, it is alive! -elder gargoyle=elder gargoyle -You see an elder gargoyle, it is wise...=You see an elder gargoyle, it is wise... -dark gargoyle=dark gargoyle -You see a dark gargoyle, do you feel the fear?=You see a dark gargoyle, do you feel the fear? -venom gargoyle=venom gargoyle -You see a venom gargoyle, a hideous and poisonous creature.=You see a venom gargoyle, a hideous and poisonous creature. -mimic=mimic -You see a mimic. Its content might be different from what you expect.=You see a mimic. Its content might be different from what you expect. -snarfkin=snarfkin -You see a snarfkin, the favorite pet of wizards. Be careful, its claws are poisonous!=You see a snarfkin, the favorite pet of wizards. Be careful, its claws are poisonous! -glow monster=glow monster -You see a glow monster.=You see a glow monster. -dark mutant=dark mutant -You see a dark mutant.=You see a dark mutant. -googon=googon -You see a googon that comes from the worst place in this world...Be careful this creature is a deadly killer.=You see a googon that comes from the worst place in this world...Be careful this creature is a deadly killer. -grobble monster=grobble monster -You see a grobble monster.=You see a grobble monster. -grobble grubble grobble=grobble grubble grobble -gribble grobble grubble=gribble grobble grubble -winged blurble=winged blurble -You see a winged blurble.=You see a winged blurble. -imperial experiment=imperial experiment -You see an imperial experiment which mirrors the fate of many of science departments test subjects. It's skin glows and standing before it taxes your body.=You see an imperial experiment which mirrors the fate of many of science departments test subjects. It's skin glows and standing before it taxes your body. -imperial mutant=imperial mutant -You see an imperial mutant.=You see an imperial mutant. -Ouurgghh!=Ouurgghh! -thing=thing -You see a thing. It looks like a very mutated human and is so scary that you think it has to come from another world.=You see a thing. It looks like a very mutated human and is so scary that you think it has to come from another world. -green slime=green slime -You see a green slime, sticky and shiny.=You see a green slime, sticky and shiny. -brown slime=brown slime -You see a brown slime, sticky and hungry.=You see a brown slime, sticky and hungry. -black slime=black slime -You see a black slime, with a sticky trail behind it.=You see a black slime, with a sticky trail behind it. -twilight slime=twilight slime -You see a twilight slime, so strange and beautiful. Why is it coming towards you? Runnnnnnnnn!=You see a twilight slime, so strange and beautiful. Why is it coming towards you? Runnnnnnnnn! -violet avenger=violet avenger -You see a violet avenger.=You see a violet avenger. -devil queen=devil queen -You see a devil queen.=You see a devil queen. -Who wakes me from my meditation?=Who wakes me from my meditation? -You will regret this!=You will regret this! -demon=demon -You see a demon. He personifies evil.=You see a demon. He personifies evil. -imperial demon servant=imperial demon servant -You see an imperial demon servant who carries out whims of his demon lords.=You see an imperial demon servant who carries out whims of his demon lords. -imperial demon lord=imperial demon lord -You see an imperial demon lord. He holds the rank of General among demons, and very few out rank it in hell.=You see an imperial demon lord. He holds the rank of General among demons, and very few out rank it in hell. -balrog=balrog -You see a huge balrog. He is slow hitting, but terribly strong as he stands towering above you.=You see a huge balrog. He is slow hitting, but terribly strong as he stands towering above you. -Arrrrllllggggghhhh=Arrrrllllggggghhhh -Growwwwwwwrrrrrrr!=Growwwwwwwrrrrrrr! -imp=imp -You see an imp. He may look a little bit cute, but don't forget he is one of the devil's servants!=You see an imp. He may look a little bit cute, but don't forget he is one of the devil's servants! -winged blue imp=winged blue imp -You see a winged blue imp. It's endowed with the power of evil. His trident is able to poison even the bravest warriors.=You see a winged blue imp. It's endowed with the power of evil. His trident is able to poison even the bravest warriors. -chaos green dragonrider=chaos green dragonrider -You see a chaos green dragonrider. Don't misjudge him as to believe it's a normal green dragon, as it is controlled by a mysterious sorcerer seated on his back.=You see a chaos green dragonrider. Don't misjudge him as to believe it's a normal green dragon, as it is controlled by a mysterious sorcerer seated on his back. -Wheee!!=Wheee!! -It's fun up here!=It's fun up here! -chaos red dragonrider=chaos red dragonrider -You see a chaos red dragonrider. He might look like a red dragon, but the sorcerer seated on his back guides him to give even more strength out.=You see a chaos red dragonrider. He might look like a red dragon, but the sorcerer seated on his back guides him to give even more strength out. -Charrrrrrrrge!=Charrrrrrrrge! -green dragon=green dragon -You see a green dragon. His wings may be too small for him to fly with them but nevertheless he looks very strong and his thick green skin seems to protect him quite well.=You see a green dragon. His wings may be too small for him to fly with them but nevertheless he looks very strong and his thick green skin seems to protect him quite well. -bone dragon=bone dragon -You see a bone dragon. It's been said that there are creatures with enough will to live that their souls still cling to life even as their flesh and blood has failed them. This is living proof.=You see a bone dragon. It's been said that there are creatures with enough will to live that their souls still cling to life even as their flesh and blood has failed them. This is living proof. -blue dragon=blue dragon -You see a blue dragon. It has a very beautiful blue skin which shimmers mysteriously in the light. But don't forget that it also has claws and teeth!=You see a blue dragon. It has a very beautiful blue skin which shimmers mysteriously in the light. But don't forget that it also has claws and teeth! -red dragon=red dragon -You see into the emerald eyes of a red dragon. With these it can spot prey in the darkness. It's red scales serve it well, reflecting any light that shines on them blinding enemies. The scales are prized for their beauty, and difficult to obtain from such a ferocious beast.=You see into the emerald eyes of a red dragon. With these it can spot prey in the darkness. It's red scales serve it well, reflecting any light that shines on them blinding enemies. The scales are prized for their beauty, and difficult to obtain from such a ferocious beast. -black dragon=black dragon -You see a black dragon. As you see this huge black creature you almost faint! If you concentrate you can feel the deep growling of it in your bones.=You see a black dragon. As you see this huge black creature you almost faint! If you concentrate you can feel the deep growling of it in your bones. -flying golden dragon=flying golden dragon -You see a flying golden dragon. As it spreads its mighty wings you shiver... this mystical creature seems to have much power in it!=You see a flying golden dragon. As it spreads its mighty wings you shiver... this mystical creature seems to have much power in it! -twin headed dragon=twin headed dragon -You see a twin headed dragon. The two heads are staring at you with their four eyes!=You see a twin headed dragon. The two heads are staring at you with their four eyes! -duergar=duergar -You see a duergar. It is a close relative to dwarves but with a diabolically tainted blood.=You see a duergar. It is a close relative to dwarves but with a diabolically tainted blood. -duergar axeman=duergar axeman -You see a duergar axeman.=You see a duergar axeman. -elder duergar=elder duergar -You see an elder duergar.=You see an elder duergar. -duergar elite=duergar elite -You see a duergar elite.=You see a duergar elite. -duergar hero=duergar hero -You see a duergar hero.=You see a duergar hero. -We mighty duergars will crush the dwarves!=We mighty duergars will crush the dwarves! -duergar wizard=duergar wizard -You see a duergar wizard.=You see a duergar wizard. -black duergar=black duergar -You see a black duergar.=You see a black duergar. -Dwarves are the enemy ... but we kill all who cross our path!=Dwarves are the enemy ... but we kill all who cross our path! -duergar king=duergar king -You see a duergar king.=You see a duergar king. -dwarf=dwarf -You see a dwarf who works in the mine to find some iron for us.=You see a dwarf who works in the mine to find some iron for us. -dwarf guardian=dwarf guardian -You see a dwarf guardian who fights against the humans.=You see a dwarf guardian who fights against the humans. -elder dwarf=elder dwarf -You see an elder dwarf, a mighty warrior tired of fighting.=You see an elder dwarf, a mighty warrior tired of fighting. -hero dwarf=hero dwarf -You see a hero dwarf. He is an old dwarf warrior who became a hero for fighting in many dwarf wars and he is very famous for his strength and brutality.=You see a hero dwarf. He is an old dwarf warrior who became a hero for fighting in many dwarf wars and he is very famous for his strength and brutality. -leader dwarf=leader dwarf -You see a leader dwarf. He is the most mighty dwarf of his tribe so he enjoys much popularity there.=You see a leader dwarf. He is the most mighty dwarf of his tribe so he enjoys much popularity there. -giant dwarf=giant dwarf -You see a giant dwarf. It is very hard for him to be a giant dwarf because some of the other dwarves don't really see him as one of them because of his height... But in his big heart he is a dwarf and in a battle he will fight against each enemy!=You see a giant dwarf. It is very hard for him to be a giant dwarf because some of the other dwarves don't really see him as one of them because of his height... But in his big heart he is a dwarf and in a battle he will fight against each enemy! -Dhohr Nuggetcutter=Dhohr Nuggetcutter -You see a dhohr nuggetcutter.=You see a dhohr nuggetcutter. -Lord Durin=Lord Durin -You see the mighty Lord Durin, an ancient dwarf who has led his race through countless battles.=You see the mighty Lord Durin, an ancient dwarf who has led his race through countless battles. -Don't call me Friend.=Don't call me Friend. -mountain dwarf=mountain dwarf -You see a mountain dwarf. He likes earth, stones and rocks. Deep within the mountain, where no animal and no human has ever been, you can hear the sound of his pickaxe as he searches for gold and ore.=You see a mountain dwarf. He likes earth, stones and rocks. Deep within the mountain, where no animal and no human has ever been, you can hear the sound of his pickaxe as he searches for gold and ore. -mountain dwarf guardian=mountain dwarf guardian -You see a mountain dwarf guardian. He has to watch out for enemies and guards the mountain dwarfs while they are doing their work.=You see a mountain dwarf guardian. He has to watch out for enemies and guards the mountain dwarfs while they are doing their work. -mountain elder dwarf=mountain elder dwarf -You see a mountain elder dwarf. For many years he worked deep in the mountain and found much gold and ore in his life. Soon he will be too old to do this work but at the moment he is still a strong dwarf.=You see a mountain elder dwarf. For many years he worked deep in the mountain and found much gold and ore in his life. Soon he will be too old to do this work but at the moment he is still a strong dwarf. -mountain hero dwarf=mountain hero dwarf -You see a mountain hero dwarf. Many, many dwarves admire him! He was a member of a dwarvish group which discovered a very big gold deposit!=You see a mountain hero dwarf. Many, many dwarves admire him! He was a member of a dwarvish group which discovered a very big gold deposit! -mountain leader dwarf=mountain leader dwarf -You see a mountain leader dwarf. He is responsible for the mountain dwarves and their work, and also for the organization of the guardian troops. But in serious cases he will also fight on his own!=You see a mountain leader dwarf. He is responsible for the mountain dwarves and their work, and also for the organization of the guardian troops. But in serious cases he will also fight on his own! -air elemental=air elemental -You see an air elemental and feel a fresh breeze past you.=You see an air elemental and feel a fresh breeze past you. -djinn=djinn -You see a djinn. He isn't caught in a bottle or lamp so someone must have let him free and gave him the order to kill you.=You see a djinn. He isn't caught in a bottle or lamp so someone must have let him free and gave him the order to kill you. -nymph=nymph -You see a nymph. She loves nature very much! As you take a look at her she feels disturbed in sun-bathing and talking to the birds, perhaps she was best left alone.=You see a nymph. She loves nature very much! As you take a look at her she feels disturbed in sun-bathing and talking to the birds, perhaps she was best left alone. -earth elemental=earth elemental -You see an earth elemental. The earth is quaking as it moves over the surface.=You see an earth elemental. The earth is quaking as it moves over the surface. -fire elemental=fire elemental -You see a fire elemental and hardly can stand the heat.=You see a fire elemental and hardly can stand the heat. -ice elemental=ice elemental -You see an ice elemental and sense a shiver on your spine.=You see an ice elemental and sense a shiver on your spine. -ice golem=ice golem -You see an Ice golem. Ice Golems are elementals made out of ice. You should watch out for its icy fists or you might get hurt.=You see an Ice golem. Ice Golems are elementals made out of ice. You should watch out for its icy fists or you might get hurt. -ice giant=ice giant -You see an ice giant. His skin is covered with frozen hoarfrost and his breath feels like the cold north wind.=You see an ice giant. His skin is covered with frozen hoarfrost and his breath feels like the cold north wind. -stone golem=stone golem -You see a stone golem, it only exists to protect. What was it crafted for to protect, and what would one do if it lost its purpose?=You see a stone golem, it only exists to protect. What was it crafted for to protect, and what would one do if it lost its purpose? -dwarf golem=dwarf golem -You see a dwarf golem. A long time ago a dwarf got turned into a big rock by some mystical forces. To save their friend the other dwarves created a golem out of this rock! From this day the dwarf golem lives together with them.=You see a dwarf golem. A long time ago a dwarf got turned into a big rock by some mystical forces. To save their friend the other dwarves created a golem out of this rock! From this day the dwarf golem lives together with them. -water elemental=water elemental -You see a Water elemental which is made of water and various other liquids.=You see a Water elemental which is made of water and various other liquids. -wooden golem=wooden golem -You see a wooden golem. The scroll with ancient words in his head keeps it walking.=You see a wooden golem. The scroll with ancient words in his head keeps it walking. -ent=ent -You see an ent. First you think it is a tree but you will get a surprise when it starts to walk!=You see an ent. First you think it is a tree but you will get a surprise when it starts to walk! -old ent=old ent -You see an old ent. It has seen many years passing by and survived a lot of storms. You can see that it is very old because it has already lost all of its leaves and most of its bark is broken...=You see an old ent. It has seen many years passing by and survived a lot of storms. You can see that it is very old because it has already lost all of its leaves and most of its bark is broken... -entwife=entwife -You see an entwife. It is stronger than an ent and lives in a group in an ancient forest.=You see an entwife. It is stronger than an ent and lives in a group in an ancient forest. -albino elf swordsman=albino elf swordsman -You see an albino elf swordsman who is a professional in using swords. He can attack you from behind in a blink of an eye.=You see an albino elf swordsman who is a professional in using swords. He can attack you from behind in a blink of an eye. -albino elf archer=albino elf archer -You see an albino elf archer. He is so sharp-eyed that he could hit a bee from a distance of 100 meters.=You see an albino elf archer. He is so sharp-eyed that he could hit a bee from a distance of 100 meters. -albino elf magician=albino elf magician -You see an albino elf magician. He knows all the secrets of magic. You'd be better to not let him unleash his full power!=You see an albino elf magician. He knows all the secrets of magic. You'd be better to not let him unleash his full power! -albino elf queen=albino elf queen -You see an albino elf queen. She is the matriarch of the albino elf race and she will do everything to protect it.=You see an albino elf queen. She is the matriarch of the albino elf race and she will do everything to protect it. -albino elf king=albino elf king -You see an albino elf king. He is the patriarch of albino elves and won countless of battles with his powerful albino elf army.=You see an albino elf king. He is the patriarch of albino elves and won countless of battles with his powerful albino elf army. -child dark elf=child dark elf -You see a child dark elf. You should know that dark elves are already quite strong and intelligent when they are children. Don't think you can fight him so easily!=You see a child dark elf. You should know that dark elves are already quite strong and intelligent when they are children. Don't think you can fight him so easily! -dark elf archer=dark elf archer -You see a dark elf archer. Dark elf archer's flawless aim can be fatal for those who dare to oppose them.=You see a dark elf archer. Dark elf archer's flawless aim can be fatal for those who dare to oppose them. -dark elf=dark elf -You see a dark elf.=You see a dark elf. -dark elf elite archer=dark elf elite archer -You see a dark elf elite archer.=You see a dark elf elite archer. -dark elf captain=dark elf captain -You see a dark elf captain.=You see a dark elf captain. -Think not to defy the dark elves!=Think not to defy the dark elves! -dark elf knight=dark elf knight -You see a dark elf knight.=You see a dark elf knight. -dark elf general=dark elf general -You see a Dark Elf General. It is commanding the evil troops of Nalwor Caves against intruders.=You see a Dark Elf General. It is commanding the evil troops of Nalwor Caves against intruders. -We will never return to the light!=We will never return to the light! -dark elf wizard=dark elf wizard -Dark elf wizards are very powerful and dangerous. They are specialised in dark magic.=Dark elf wizards are very powerful and dangerous. They are specialised in dark magic. -The Spider Queen will avenge me!=The Spider Queen will avenge me! -dark elf viceroy=dark elf viceroy -You see a dark elf viceroy.=You see a dark elf viceroy. -Keep away from me, sunlight dweller!=Keep away from me, sunlight dweller! -dark elf ranger=dark elf ranger -You see a dark elf ranger.=You see a dark elf ranger. -Keep away from me, sunlight dweller!=Keep away from me, sunlight dweller! -dark elf sacerdotist=dark elf sacerdotist -You see a dark elf sacerdotist.=You see a dark elf sacerdotist. -The Queen of Spiders will take her revenge on you!=The Queen of Spiders will take her revenge on you! -dark elf admiral=dark elf admiral -You see a dark elf admiral.=You see a dark elf admiral. -We are the darkness you fear!=We are the darkness you fear! -Fear the dark elf armies!=Fear the dark elf armies! -dark elf master=dark elf master -You see a dark elf master. Beware of his high skills on magic and swordsmanship!=You see a dark elf master. Beware of his high skills on magic and swordsmanship! -Darkness will fall upon you!=Darkness will fall upon you! -You will never see the light again!=You will never see the light again! -dark elf matronmother=dark elf matronmother -You see a dark elf matronmother.=You see a dark elf matronmother. -Now, you will pay.=Now, you will pay. -You disturbed my rest!=You disturbed my rest! -elf=elf -You see an elf which loves the forest and lives in peace.=You see an elf which loves the forest and lives in peace. -militia elf=militia elf -You see a militia elf which trains hard to beat you!=You see a militia elf which trains hard to beat you! -archer elf=archer elf -You see an archer elf which can do much damage with its wooden arrows.=You see an archer elf which can do much damage with its wooden arrows. -soldier elf=soldier elf -You see a soldier elf, a good partner of the militia elves.=You see a soldier elf, a good partner of the militia elves. -mage elf=mage elf -You see a mage elf. She can feel pure magic when she stands outside in the forest and just frees her mind.=You see a mage elf. She can feel pure magic when she stands outside in the forest and just frees her mind. -commander elf=commander elf -You see a busy commander elf. He has control over the army of elves.=You see a busy commander elf. He has control over the army of elves. -archmage elf=archmage elf -You see an archmage elf. She is pretty.=You see an archmage elf. She is pretty. -elf sacerdotist=elf sacerdotist -You see an elf sacerdotist.=You see an elf sacerdotist. -elf wizard=elf wizard -You see an elf wizard. He is a very wise elf who knows almost every kind of magic. He likes to use nature spells to make every plant and creature fertile, but he is also able to defend himself when he has to do.=You see an elf wizard. He is a very wise elf who knows almost every kind of magic. He likes to use nature spells to make every plant and creature fertile, but he is also able to defend himself when he has to do. -elf bodyguard=elf bodyguard -You see an elf bodyguard.=You see an elf bodyguard. -amazoness giant=amazoness giant -You see an amazoness giant. She looks very beautiful but don't be deceived by that!=You see an amazoness giant. She looks very beautiful but don't be deceived by that! -giant=giant -You look up into the face of a huge giant. His physical proportions and extraordinary strength should give you goosebumps.=You look up into the face of a huge giant. His physical proportions and extraordinary strength should give you goosebumps. -I seek the blood of a Semos Man!=I seek the blood of a Semos Man! -Fee Fie Foe Fum!!=Fee Fie Foe Fum!! -elder giant=elder giant -You see an elder giant. For many years he lived alone and had to hide because he was hunted by humans! Now he is old and has decided not to hide any longer because he wants to see something else in his life.=You see an elder giant. For many years he lived alone and had to hide because he was hunted by humans! Now he is old and has decided not to hide any longer because he wants to see something else in his life. -Gnomes! Run away!=Gnomes! Run away! -You may steal my money but you'll never take my heart!=You may steal my money but you'll never take my heart! -master giant=master giant -You see a master giant with dark black eyes. His blood pumps loudly into his heart.=You see a master giant with dark black eyes. His blood pumps loudly into his heart. -Thou shalt die!=Thou shalt die! -Worms will feast on your corpse!=Worms will feast on your corpse! -black giant=black giant -You see a tall black giant. His armor and sword will hit you hard. Be prepared!=You see a tall black giant. His armor and sword will hit you hard. Be prepared! -Leave me now, before it is too late!=Leave me now, before it is too late! -Who dare challenge me?=Who dare challenge me? -imperial general giant=imperial general giant -You see an imperial general giant.=You see an imperial general giant. -giant kobold=giant kobold -You see a giant kobold. Though generally playful it doesn't quite know it's own strength. When it asks for a hug, it's best to decline that offer.=You see a giant kobold. Though generally playful it doesn't quite know it's own strength. When it asks for a hug, it's best to decline that offer. -Hug?=Hug? -kasarkutominubat=kasarkutominubat -You see a kasarkutominubat.=You see a kasarkutominubat. -gnome=gnome -You see a gnome who lives in the mountains.=You see a gnome who lives in the mountains. -Giiiiiaaants! Run away!=Giiiiiaaants! Run away! -mage gnome=mage gnome -You see a mage gnome, she cleans the dirty mountain.=You see a mage gnome, she cleans the dirty mountain. -Giiiiiaaants! Run away!=Giiiiiaaants! Run away! -infantry gnome=infantry gnome -You see an infantry gnome who will protect his little town against the humans!=You see an infantry gnome who will protect his little town against the humans! -Ready! Aim! Runnnnnnnnnnn=Ready! Aim! Runnnnnnnnnnn -cavalryman gnome=cavalryman gnome -You see a cavalryman gnome who does long travels to search for gold.=You see a cavalryman gnome who does long travels to search for gold. -Ready! Aim! Runnnnnnnnnnn=Ready! Aim! Runnnnnnnnnnn -goblin=goblin -You see a goblin, it is an evil and mischievous creature.=You see a goblin, it is an evil and mischievous creature. -Umm! Tasty meat.=Umm! Tasty meat. -veteran goblin=veteran goblin -You see a veteran goblin, an old warrior...=You see a veteran goblin, an old warrior... -Breakfast time!=Breakfast time! -soldier goblin=soldier goblin -You see a soldier goblin, a young warrior.=You see a soldier goblin, a young warrior. -amazoness archer=amazoness archer -You see an amazoness archer. She handles her bow quite well and her arrows are very dangerous when she shoots them at you out of the forest!=You see an amazoness archer. She handles her bow quite well and her arrows are very dangerous when she shoots them at you out of the forest! -amazoness archer commander=amazoness archer commander -You see an amazoness archer commander. She is the leader of amazoness archers. Her bow skills are incredible and her arrows pierce everything.=You see an amazoness archer commander. She is the leader of amazoness archers. Her bow skills are incredible and her arrows pierce everything. -amazoness bodyguard=amazoness bodyguard -You see an amazoness bodyguard who will guarantee the safety to the people of Amazon island.=You see an amazoness bodyguard who will guarantee the safety to the people of Amazon island. -amazoness coastguard=amazoness coastguard -You see an amazoness coastguard. She protects the coasts of Amazon Island from intruders. She is not too happy to see you running in her island.=You see an amazoness coastguard. She protects the coasts of Amazon Island from intruders. She is not too happy to see you running in her island. -amazoness coastguard mistress=amazoness coastguard mistress -You see an amazoness coastguard mistress. Even if they are women, these blue haired mistresses should not be underestimated.=You see an amazoness coastguard mistress. Even if they are women, these blue haired mistresses should not be underestimated. -amazoness commander=amazoness commander -You see an amazoness commander who is specialised in eliminating any strangers who dare to take a step to her island.=You see an amazoness commander who is specialised in eliminating any strangers who dare to take a step to her island. -amazoness elite coastguard=amazoness elite coastguard -You see an amazoness elite coastguard. When it is her shift to protect the island, you would just wish to have stayed at home.=You see an amazoness elite coastguard. When it is her shift to protect the island, you would just wish to have stayed at home. -amazoness hunter=amazoness hunter -You see an amazoness hunter. She creeps through the forest to hunt animals. But she doesn't like that you are in her hunting area!=You see an amazoness hunter. She creeps through the forest to hunt animals. But she doesn't like that you are in her hunting area! -amazoness vigilance=amazoness vigilance -You see an amazoness vigilance. Her senses are well tuned, so you have to be very silent when you don't want her to hear you!=You see an amazoness vigilance. Her senses are well tuned, so you have to be very silent when you don't want her to hear you! -amazoness imperator=amazoness imperator -You see an amazoness imperator. She is the emperor of Amazon island and has ruled it for decades and won't let you get past her very easily.=You see an amazoness imperator. She is the emperor of Amazon island and has ruled it for decades and won't let you get past her very easily. -bandit=bandit -You see a lean and mean bandit. He's out to get your life and purse!=You see a lean and mean bandit. He's out to get your life and purse! -disciple assassin=disciple assassin -You see a disciple assassin. He is quite young but he has learned to fight and kill already.=You see a disciple assassin. He is quite young but he has learned to fight and kill already. -My dad can beat your dad.=My dad can beat your dad. -I'm gonna tell my mommy.=I'm gonna tell my mommy. -Teacher, make him stop.=Teacher, make him stop. -apprentice assassin=apprentice assassin -You see an apprentice assassin. He has been taught some very awesome moves and methods, so he is already able to kill an enemy almost perfectly!=You see an apprentice assassin. He has been taught some very awesome moves and methods, so he is already able to kill an enemy almost perfectly! -Bring it on!=Bring it on! -Any time you're ready.=Any time you're ready. -You want a piece of me?=You want a piece of me? -Come on, i dare you.=Come on, i dare you. -hunter=hunter -You see a hunter.=You see a hunter. -I always get my prey!=I always get my prey! -assassin=assassin -You see an assassin. He is very good at killing his victims silently without being seen by anyone.=You see an assassin. He is very good at killing his victims silently without being seen by anyone. -Time to die.=Time to die. -ninja=ninja -Your keen eyes made you able to see this fast ninja. Prepare for a fight!=Your keen eyes made you able to see this fast ninja. Prepare for a fight! -samurai=samurai -You see a samurai.=You see a samurai. -shadows assassin=shadows assassin -You see a shadows assassin. He knows how to become one with the shadows and to move in the dark without being seen or making any sound.=You see a shadows assassin. He knows how to become one with the shadows and to move in the dark without being seen or making any sound. -master assassin=master assassin -You see a master assassin. He is a master of assassination! He is able to kill an enemy without letting him know that he is already there!=You see a master assassin. He is a master of assassination! He is able to kill an enemy without letting him know that he is already there! -Why do you return?=Why do you return? -You've been warned before.=You've been warned before. -Have you lost your marbles?=Have you lost your marbles? -barbarian=barbarian -You see a barbarian.=You see a barbarian. -barbarian wolf=barbarian wolf -You see a barbarian wolf.=You see a barbarian wolf. -barbarian elite=barbarian elite -You see a barbarian elite.=You see a barbarian elite. -barbarian priest=barbarian priest -You see a barbarian priest.=You see a barbarian priest. -barbarian chaman=barbarian chaman -You see a barbarian chaman.=You see a barbarian chaman. -King he say kill!=King he say kill! -barbarian leader=barbarian leader -You see a barbarian leader.=You see a barbarian leader. -Me destroy you!=Me destroy you! -barbarian king=barbarian king -You see a barbarian king.=You see a barbarian king. -blordrough quartermaster=blordrough quartermaster -You see a man who gave up his life as a puppet of the Blordrough army, all for the love of dragons. He is wonderfully happy.=You see a man who gave up his life as a puppet of the Blordrough army, all for the love of dragons. He is wonderfully happy. -We might be in hiding, but we are STILL mighty.=We might be in hiding, but we are STILL mighty. -blordrough corporal=blordrough corporal -You see a man who feels his past indicates how he should be treated now but desparately wishes to exercise his intelect in a game of chess.=You see a man who feels his past indicates how he should be treated now but desparately wishes to exercise his intelect in a game of chess. -You WILL show us the respect we deserve.=You WILL show us the respect we deserve. -Care for a game of chess?=Care for a game of chess? -blordrough storm trooper=blordrough storm trooper -You see a man who is but a fraction of his former glory as one of the most feared foes of Faiumoni. Now, his main concern is keeping his house clean and tidy.=You see a man who is but a fraction of his former glory as one of the most feared foes of Faiumoni. Now, his main concern is keeping his house clean and tidy. -Wipe your feet before entering our domain.=Wipe your feet before entering our domain. -I'll teach you to defile our sanctuary.=I'll teach you to defile our sanctuary. -cannibal woman=cannibal woman -You see a cannibal woman. She wants to make a soup out of your bones!=You see a cannibal woman. She wants to make a soup out of your bones! -Zag-Zag!=Zag-Zag! -cannibal=cannibal -You see a cannibal. He hunts you for cooking and eating your fresh meat! It's useless to tell him that your flesh is not good, better fight him or run away!=You see a cannibal. He hunts you for cooking and eating your fresh meat! It's useless to tell him that your flesh is not good, better fight him or run away! -Ou-Garrrr!=Ou-Garrrr! -elder cannibal=elder cannibal -You see an elder cannibal.=You see an elder cannibal. -Yummie!=Yummie! -chaos soldier=chaos soldier -You see a chaos soldier. Chaos soldiers are servants of the evil. Killing you is one of their evil plans.=You see a chaos soldier. Chaos soldiers are servants of the evil. Killing you is one of their evil plans. -chaos warrior=chaos warrior -You see a chaos warrior. This chaotic creature is fighting with a big blade to slay his enemies.=You see a chaos warrior. This chaotic creature is fighting with a big blade to slay his enemies. -Defy order! Seek chaos!=Defy order! Seek chaos! -chaos commander=chaos commander -You see a chaos commander who is working on an evil plan. You should take care of him before he fulfills his duty.=You see a chaos commander who is working on an evil plan. You should take care of him before he fulfills his duty. -Chaos in the wrath!=Chaos in the wrath! -chaos sorcerer=chaos sorcerer -You see a chaos sorcerer. Chaos sorcerers will cast some evil spells on you if you don't obey them.=You see a chaos sorcerer. Chaos sorcerers will cast some evil spells on you if you don't obey them. -I will summon chaos and fury upon you!=I will summon chaos and fury upon you! -chaos dragonrider=chaos dragonrider -You see a chaos dragonrider. He is very well trained in dragon riding, but he can also fight well on the ground.=You see a chaos dragonrider. He is very well trained in dragon riding, but he can also fight well on the ground. -chaos lord=chaos lord -You see a chaos lord who is one of the strongest chaos creatures in Faiumoni. You should prepare well before attacking him.=You see a chaos lord who is one of the strongest chaos creatures in Faiumoni. You should prepare well before attacking him. -Chaos will rule!=Chaos will rule! -chaos overlord=chaos overlord -You see a chaos overlord. It is him who directs the creation of evil plans and gives orders to all chaotic creatures which exist.=You see a chaos overlord. It is him who directs the creation of evil plans and gives orders to all chaotic creatures which exist. -I am become death, destroyer of worlds.=I am become death, destroyer of worlds. -chaos pawn=chaos pawn -You see a pawn for a game of chess. He will drop nothing if you kill him.=You see a pawn for a game of chess. He will drop nothing if you kill him. -chaos rook=chaos rook -You see a rook for a game of chess. He will drop nothing if you kill him.=You see a rook for a game of chess. He will drop nothing if you kill him. -chaos knight=chaos knight -You see a knight for a game of chess. He will drop nothing if you kill him.=You see a knight for a game of chess. He will drop nothing if you kill him. -chaos bishop=chaos bishop -You see a bishop for a game of chess. He will not reward you if you kill him.=You see a bishop for a game of chess. He will not reward you if you kill him. -chaos king=chaos king -You see a king of a game of chess. He will reward nothing if you kill him.=You see a king of a game of chess. He will reward nothing if you kill him. -chaos queen=chaos queen -You see a queen piece for a game of chess. She will reward nothing if you kill her.=You see a queen piece for a game of chess. She will reward nothing if you kill her. -imperial defender=imperial defender -You see an imperial defender.=You see an imperial defender. -imperial veteran=imperial veteran -You see an imperial veteran. He fought in many battles and wars. Now he is old, but over the years he always looked for a new enemy to fight against.=You see an imperial veteran. He fought in many battles and wars. Now he is old, but over the years he always looked for a new enemy to fight against. -imperial archer=imperial archer -You see an imperial archer.=You see an imperial archer. -imperial priest=imperial priest -You see an imperial priest. The church has fallen out of favor with the citizens as of late, but this imperial priest still clings to the faith.=You see an imperial priest. The church has fallen out of favor with the citizens as of late, but this imperial priest still clings to the faith. -imperial elite guardian=imperial elite guardian -You see an Imperial Elite Guardian. He is a very well trained guardian and he will protect his environment until the end.=You see an Imperial Elite Guardian. He is a very well trained guardian and he will protect his environment until the end. -imperial scientist=imperial scientist -You see an imperial scientist. He is quite clever but also a little bit crazy and unfortunately he uses his intelligence for evil experiments.=You see an imperial scientist. He is quite clever but also a little bit crazy and unfortunately he uses his intelligence for evil experiments. -imperial high priest=imperial high priest -You see an imperial high priest who's troubled by the actions of the imperial science department. Be careful, he might take those frustrations out on you!=You see an imperial high priest who's troubled by the actions of the imperial science department. Be careful, he might take those frustrations out on you! -imperial archer leader=imperial archer leader -You see an imperial archer leader.=You see an imperial archer leader. -imperial elite archer=imperial elite archer -You see an imperial elite archer.=You see an imperial elite archer. -imperial leader=imperial leader -You see a well dressed imperial leader. It's troops will try to dash you.=You see a well dressed imperial leader. It's troops will try to dash you. -imperial knight=imperial knight -You see an imperial knight.=You see an imperial knight. -imperial chief=imperial chief -You see an imperial chief.=You see an imperial chief. -imperial commander=imperial commander -You see an imperial commander.=You see an imperial commander. -Our troops will crush you!=Our troops will crush you! -imperial general=imperial general -You see an imperial general with a huge scar above his eye. He is still strong, don't think that he will not see you!=You see an imperial general with a huge scar above his eye. He is still strong, don't think that he will not see you! -Our troops will crush you!=Our troops will crush you! -emperor dalmung=emperor dalmung -You see an emperor dalmung who stands before you. Once a powerful warlock trained by a mighty magician, but he disappeared decades ago. Now it seems he's in league with Ivan Abe!=You see an emperor dalmung who stands before you. Once a powerful warlock trained by a mighty magician, but he disappeared decades ago. Now it seems he's in league with Ivan Abe! -My empire is undefeatable!=My empire is undefeatable! -kalavan housewife=kalavan housewife -You see a kalavan housewife. She lives in Kalavan city and she very angry with rude people who want to steal her food out of the kitchen and destroy her beautiful flower garden!=You see a kalavan housewife. She lives in Kalavan city and she very angry with rude people who want to steal her food out of the kitchen and destroy her beautiful flower garden! -kalavan peasant=kalavan peasant -You see a kalavan peasant. He works in the fields around Kalavan city. As he heard there were strangers in the city he came to help the kalavan citizens in fighting the enemies!=You see a kalavan peasant. He works in the fields around Kalavan city. As he heard there were strangers in the city he came to help the kalavan citizens in fighting the enemies! -kalavan wisewoman=kalavan wisewoman -You see a kalavan wisewoman. Normally she sits in her small room and does some handworking but now she wants to have some action!=You see a kalavan wisewoman. Normally she sits in her small room and does some handworking but now she wants to have some action! -kalavan citizen=kalavan citizen -You see a kalavan citizen. He lives in kalavan city and tries to protect it from invaders!=You see a kalavan citizen. He lives in kalavan city and tries to protect it from invaders! -madaram archer=madaram archer -You see a madaram archer.=You see a madaram archer. -madaram peasant=madaram peasant -You see a madaram peasant.=You see a madaram peasant. -madaram trooper=madaram trooper -You see a madaram trooper.=You see a madaram trooper. -madaram soldier=madaram soldier -You see a madaram soldier.=You see a madaram soldier. -madaram healer=madaram healer -You see a madaram healer.=You see a madaram healer. -madaram axeman=madaram axeman -You see a madaram axeman.=You see a madaram axeman. -madaram queen=madaram queen -You see a madaram queen.=You see a madaram queen. -madaram hero=madaram hero -You see a madaram hero.=You see a madaram hero. -madaram windwalker=madaram windwalker -You see a madaram windwalker.=You see a madaram windwalker. -madaram cavalry=madaram cavalry -You see a madaram cavalry.=You see a madaram cavalry. -madaram stalker=madaram stalker -You see a madaram stalker.=You see a madaram stalker. -madaram buster blader=madaram buster blader -You see a madaram buster blader.=You see a madaram buster blader. -madaram pawn=madaram pawn -You see a pawn for a game of chess. It will reward nothing if killed.=You see a pawn for a game of chess. It will reward nothing if killed. -madaram rook=madaram rook -You see a rook for a game of chess. It will scarcely reward you to kill it.=You see a rook for a game of chess. It will scarcely reward you to kill it. -madaram knight=madaram knight -You see a knight, a chess piece. He won't reward you if you kill him.=You see a knight, a chess piece. He won't reward you if you kill him. -madaram bishop=madaram bishop -You see a bishop for a game of chess. It will reward you nothing if you slay it.=You see a bishop for a game of chess. It will reward you nothing if you slay it. -madaram king=madaram king -You see a king to play chess with, he will reward you nothing if you kill him.=You see a king to play chess with, he will reward you nothing if you kill him. -madaram queen =madaram queen -You see a queen piece for a game of chess. She will reward nothing if you kill her.=You see a queen piece for a game of chess. She will reward nothing if you kill her. -mithrilbourgh soldier=mithrilbourgh soldier -You see a mithrilbourgh soldier.=You see a mithrilbourgh soldier. -mithrilbourgh archer=mithrilbourgh archer -You see a mithrilbourgh archer.=You see a mithrilbourgh archer. -mithrilbourgh knight=mithrilbourgh knight -You see a mithrilbourgh knight.=You see a mithrilbourgh knight. -mithrilbourgh elite archer=mithrilbourgh elite archer -You see a mithrilbourgh elite archer.=You see a mithrilbourgh elite archer. -mithrilbourgh sergeant=mithrilbourgh sergeant -You see a mithrilbourgh sergeant.=You see a mithrilbourgh sergeant. -mithrilbourgh lieutenant=mithrilbourgh lieutenant -You see a mithrilbourgh lieutenant.=You see a mithrilbourgh lieutenant. -mithrilbourgh colonel=mithrilbourgh colonel -You see a mithrilbourgh colonel.=You see a mithrilbourgh colonel. -mithrilbourgh wizard=mithrilbourgh wizard -You see a mithrilbourgh wizard.=You see a mithrilbourgh wizard. -mithrilbourgh general=mithrilbourgh general -You see a mithrilbourgh general. Some call him Hawkeyes. He cast his gaze over the city walls and saw you coming from miles away.=You see a mithrilbourgh general. Some call him Hawkeyes. He cast his gaze over the city walls and saw you coming from miles away. -monk=monk -You see a monk. Monks are very calm and peaceful. They live alone among themselves, but they don't want to be interrupted by anyone.=You see a monk. Monks are very calm and peaceful. They live alone among themselves, but they don't want to be interrupted by anyone. -Meet you after Cataclysm.=Meet you after Cataclysm. -The end of the world is nigh...=The end of the world is nigh... -Yield to the Power!=Yield to the Power! -I see you, heretic!=I see you, heretic! -darkmonk=darkmonk -You see a Darkmonk. Darkmonks can control dark powers, and they will do anything to get you out of their way!=You see a Darkmonk. Darkmonks can control dark powers, and they will do anything to get you out of their way! -Hail to the greater Power!=Hail to the greater Power! -The Cataclysm cometh!=The Cataclysm cometh! -devil monk=devil monk -You see a devil monk. He is a member of a devil cult which practises black rituals.=You see a devil monk. He is a member of a devil cult which practises black rituals. -risecia thug=risecia thug -You see a risecia thug, stay away from him!=You see a risecia thug, stay away from him! -risecia archer=risecia archer -You see a risecia archer, just one of the warriors who wants to protect his chief.=You see a risecia archer, just one of the warriors who wants to protect his chief. -risecia swordsman=risecia swordsman -You see a risecia swordsman, the blade of his sword will cut your head!=You see a risecia swordsman, the blade of his sword will cut your head! -Chief Falatheen of the Risecia=Chief Falatheen of the Risecia -You see the Chief Falatheen of the Risecia, he doesn't want to die! His little warriors protect him against invaders.=You see the Chief Falatheen of the Risecia, he doesn't want to die! His little warriors protect him against invaders. -littlewitch=littlewitch -You see a little witch. Witches are are evil little girls who are very dangerous.=You see a little witch. Witches are are evil little girls who are very dangerous. -littlewizard=littlewizard -You see a little wizard. Although he is a child, he is already able to cast powerful spells.=You see a little wizard. Although he is a child, he is already able to cast powerful spells. -robot aruthon=robot aruthon -You see a robot aruthon.=You see a robot aruthon. -dark aruthon=dark aruthon -You see a dark aruthon.=You see a dark aruthon. -leprechaun=leprechaun -You see a leprechaun, you would be lucky if you found his treasure.=You see a leprechaun, you would be lucky if you found his treasure. -They're always after my Lucky Charms!!=They're always after my Lucky Charms!! -Clovers and rainbows... stars and horseshoes...=Clovers and rainbows... stars and horseshoes... -littlefairy=littlefairy -You see a little fairy, which is magical and nice.=You see a little fairy, which is magical and nice. -clurichaun=clurichaun -You see a clurichaun. He is rich, but you don't know if you are able to find his gold!=You see a clurichaun. He is rich, but you don't know if you are able to find his gold! -Floating blue balloons...=Floating blue balloons... -Join me for a drink, lassie?=Join me for a drink, lassie? -brain ahouga=brain ahouga -You see a brain ahouga, a very strange creature only living in your dreams!=You see a brain ahouga, a very strange creature only living in your dreams! -blue mohiko=blue mohiko -You see a blue mohiko.=You see a blue mohiko. -red roohako=red roohako -You see a red roohako. It is coming for you in your deepest dreams.=You see a red roohako. It is coming for you in your deepest dreams. -arachne=arachne -You see Arachne, a horrid half woman half spider. With so many legs she can hit you very fast, and her poisonous bite is fierce.=You see Arachne, a horrid half woman half spider. With so many legs she can hit you very fast, and her poisonous bite is fierce. -cat woman=cat woman -You see a cat woman. She's agile, stealthy, fast and sharp, so stay focused and don't get cut!=You see a cat woman. She's agile, stealthy, fast and sharp, so stay focused and don't get cut! -frogman=frogman -You see a frogman which lives in a lake which isn't very deep.=You see a frogman which lives in a lake which isn't very deep. -elite frogman=elite frogman -You see an elite frogman. His huge eyes let you shiver!=You see an elite frogman. His huge eyes let you shiver! -wizard frogman=wizard frogman -You see a mighty wizard frogman. His wand and magical cloak show his wisdom and power.=You see a mighty wizard frogman. His wand and magical cloak show his wisdom and power. -kobold=kobold -You see a kobold. People say that it is a mythical spirit.=You see a kobold. People say that it is a mythical spirit. -Grrrr! Wooof!=Grrrr! Wooof! -soldier kobold=soldier kobold -You see a soldier kobold, he's protecting his king.=You see a soldier kobold, he's protecting his king. -Woorf wer warf!=Woorf wer warf! -archer kobold=archer kobold -You see an archer kobold. It runs away from humans.=You see an archer kobold. It runs away from humans. -Wssss! Warf wit!=Wssss! Warf wit! -veteran kobold=veteran kobold -You see a veteran kobold, it is mighty!=You see a veteran kobold, it is mighty! -leader kobold=leader kobold -You see a leader kobold, one of the mighty warriors.=You see a leader kobold, one of the mighty warriors. -minotaur=minotaur -You see a minotaur. He escaped from his labyrinth and is very angry now!=You see a minotaur. He escaped from his labyrinth and is very angry now! -You will never find the way out!=You will never find the way out! -minotaur king=minotaur king -You see a Minotaur King. He doesn't have to escape from his labyrinth to be very angry...=You see a Minotaur King. He doesn't have to escape from his labyrinth to be very angry... -I am the king of this area!=I am the king of this area! -naga=naga -You see a naga. She awaits unwary victims to whom she may give her deadly kiss.=You see a naga. She awaits unwary victims to whom she may give her deadly kiss. -high naga=high naga -You see a high naga.=You see a high naga. -armed naga=armed naga -You see an armed naga.=You see an armed naga. -lamia=lamia -You see a lamia.=You see a lamia. -ratman=ratman -You see a ratman. He is a mutation that comes from old times.=You see a ratman. He is a mutation that comes from old times. -You, humans, never leave us alone!=You, humans, never leave us alone! -ratwoman=ratwoman -You see a ratwoman. She is a mutation of women with the rats in the century XVIII.=You see a ratwoman. She is a mutation of women with the rats in the century XVIII. -You humans never leave us alone!=You humans never leave us alone! -archrat=archrat -You see an archrat. You will be distracted with its aura, be careful...=You see an archrat. You will be distracted with its aura, be careful... -Feel my wrath!=Feel my wrath! -You're messing with the rats?!=You're messing with the rats?! -Your death has come!=Your death has come! -sheepman=sheepman -You see a sheepman.=You see a sheepman. -I'm no fluffy lamb!=I'm no fluffy lamb! -armored sheepman=armored sheepman -You see an armored sheepman.=You see an armored sheepman. -You're baaaarmy to attack me!=You're baaaarmy to attack me! -elder sheepman=elder sheepman -You see an elder sheepman.=You see an elder sheepman. -Bah! Stay away!=Bah! Stay away! -elite sheepman=elite sheepman -You see an elite sheepman.=You see an elite sheepman. -Baaaaack away!=Baaaaack away! -werewolf=werewolf -You see a werewolf. Normally he is a human but at fullmoon nights he turns into a dangerous beast! His poison seems to last forever.=You see a werewolf. Normally he is a human but at fullmoon nights he turns into a dangerous beast! His poison seems to last forever. -xenocium=xenocium -You see a xenocium.=You see a xenocium. -oni warrior=oni warrior -You see an oni warrior. His purple skin color should not lull you into a false sense of security!=You see an oni warrior. His purple skin color should not lull you into a false sense of security! -oni archer=oni archer -You see an oni archer. His arrows will follow you everywhere for protecting his queen!=You see an oni archer. His arrows will follow you everywhere for protecting his queen! -oni priest=oni priest -You see a mysterious oni priest, uttering strange incantations.=You see a mysterious oni priest, uttering strange incantations. -oni king=oni king -You see the oni king. His power leads brave oni troops around his kingdom.=You see the oni king. His power leads brave oni troops around his kingdom. -Revere me, the Oni King!=Revere me, the Oni King! -oni queen=oni queen -You see the oni queen. She supports her husband and takes an eye onto their kingdom while he conquers new land and fights on battlefields.=You see the oni queen. She supports her husband and takes an eye onto their kingdom while he conquers new land and fights on battlefields. -You must bow before the Oni Queen.=You must bow before the Oni Queen. -orc=orc -You see an orc which is noisy and silly.=You see an orc which is noisy and silly. -orc spearman=orc spearman -You see an orc spearman. Run away fast before a spear pierces you.=You see an orc spearman. Run away fast before a spear pierces you. -orc hunter=orc hunter -You see an orc hunter. It it seeking for you!=You see an orc hunter. It it seeking for you! -orc warrior=orc warrior -You see an orc warrior which doesn't like you searching in the mountain.=You see an orc warrior which doesn't like you searching in the mountain. -orc chief=orc chief -You see an orc chief which is too busy to attend to you!=You see an orc chief which is too busy to attend to you! -mountain orc=mountain orc -You see a mountain orc.=You see a mountain orc. -mountain orc warrior=mountain orc warrior -You see a mountain orc warrior.=You see a mountain orc warrior. -mountain orc hunter=mountain orc hunter -You see a mountain orc hunter.=You see a mountain orc hunter. -mountain orc chief=mountain orc chief -You see a mountain orc chief.=You see a mountain orc chief. -cyclops=cyclops -You see a cyclops. It just has one eye.=You see a cyclops. It just has one eye. -ogre=ogre -You see an ogre, it is nude and rude!=You see an ogre, it is nude and rude! -elder ogre=elder ogre -You see an elder ogre, he's in the last moments of his life, take pity on him.=You see an elder ogre, he's in the last moments of his life, take pity on him. -ogre soldier=ogre soldier -You see an ogre soldier, smelling like it didn't have had a bath for years. Ewww.=You see an ogre soldier, smelling like it didn't have had a bath for years. Ewww. -superogre=superogre -You see a superogre which belongs to the family of very material creatures, the Ogres. Ever since this one happened upon a peculiar helmet and has been revered among its peers. Take care, it throws a mean punch!=You see a superogre which belongs to the family of very material creatures, the Ogres. Ever since this one happened upon a peculiar helmet and has been revered among its peers. Take care, it throws a mean punch! -troll=troll -You see a troll, be careful in his terrain.=You see a troll, be careful in his terrain. -red troll=red troll -You see a red troll, he's burning with anger.=You see a red troll, he's burning with anger. -cave troll=cave troll -You see a cave troll.=You see a cave troll. -Uggga yggga arghhhhh=Uggga yggga arghhhhh -Ooggga UGGga arhhh=Ooggga UGGga arhhh -zombie rat=zombie rat -You see a hungry zombie rat and it doesn't like to see you.=You see a hungry zombie rat and it doesn't like to see you. -SHAAAAAAA!!=SHAAAAAAA!! -SHREEAK!!=SHREEAK!! -ghost hound=ghost hound -As you catch sight of its shaggy shape and gleaming evil eyes, you suddenly remember all the horror stories around the dreaded ghost hound!=As you catch sight of its shaggy shape and gleaming evil eyes, you suddenly remember all the horror stories around the dreaded ghost hound! -Ghhrr!=Ghhrr! -WooOouuuUuh!=WooOouuuUuh! -fallen warrior=fallen warrior -You see a fallen warrior.=You see a fallen warrior. -fallen priest=fallen priest -You see a fallen priest. He was once just a regular human priest, until the dark powers corrupted him and turned him to the dark side.=You see a fallen priest. He was once just a regular human priest, until the dark powers corrupted him and turned him to the dark side. -fallen high priest=fallen high priest -You see a fallen high priest.=You see a fallen high priest. -lich=lich -You see a lich, a dark desolate creature which has been doomed to wander in endless turmoil, feeding on the souls of the righteous.=You see a lich, a dark desolate creature which has been doomed to wander in endless turmoil, feeding on the souls of the righteous. -dead lich=dead lich -You see a dead lich with his huge green and heavy cloak. Although he is dead and bony, he'll seek revenge for his too early passing and will always return fast and ruthless!=You see a dead lich with his huge green and heavy cloak. Although he is dead and bony, he'll seek revenge for his too early passing and will always return fast and ruthless! -high lich=high lich -The terrible grinning skull of a high lich leers at you as it prepares to slaughter you in a myriad of magical ways.=The terrible grinning skull of a high lich leers at you as it prepares to slaughter you in a myriad of magical ways. -death knight=death knight -You see a death knight. Some time ago he fought in tournaments where his only aim was to kill as many enemies as possible.=You see a death knight. Some time ago he fought in tournaments where his only aim was to kill as many enemies as possible. -necrosophia=necrosophia -You see Necrosophia. She is a mighty beast, she will burn you with fire while you are disoriented by the rainbow beans.=You see Necrosophia. She is a mighty beast, she will burn you with fire while you are disoriented by the rainbow beans. -mummy=mummy -You see a mummy which usually sleeps in a coffin.=You see a mummy which usually sleeps in a coffin. -royal mummy=royal mummy -You see a royal mummy. You wonder what keeps this king's soul from finding peace.=You see a royal mummy. You wonder what keeps this king's soul from finding peace. -skeleton=skeleton -You see a skeleton which survives to get revenge.=You see a skeleton which survives to get revenge. -Thou shall not pass!=Thou shall not pass! -tiny skelly=tiny skelly -You see a tiny skelly. It is silly, little and evil, be careful with its poison...=You see a tiny skelly. It is silly, little and evil, be careful with its poison... -Thou shall not maim!=Thou shall not maim! -warrior skeleton=warrior skeleton -You see a warrior skeleton, it's just a normal skeleton with a shield!=You see a warrior skeleton, it's just a normal skeleton with a shield! -Thou shall not kill!=Thou shall not kill! -elder skeleton=elder skeleton -You see the bones of an old man. He lived to a ripe old age, and is sure to know a thing or two that separates him from all the vengeful skeletons that died young.=You see the bones of an old man. He lived to a ripe old age, and is sure to know a thing or two that separates him from all the vengeful skeletons that died young. -Thou shall not survive!=Thou shall not survive! -demon skeleton=demon skeleton -You see a demon skeleton. Demon skeletons have ascended from the underworld and they are going to take over this world. You really should slay them before it is too late!=You see a demon skeleton. Demon skeletons have ascended from the underworld and they are going to take over this world. You really should slay them before it is too late! -Thou shall not rob!=Thou shall not rob! -gashadokuro=gashadokuro -A giant figure made of bone, known only as gashadokuro, towers before you. He is created from the bones of starving humans.=A giant figure made of bone, known only as gashadokuro, towers before you. He is created from the bones of starving humans. -ghost=ghost -You see a ghost, don't be afraid! It doesn't seem to be evil.=You see a ghost, don't be afraid! It doesn't seem to be evil. -Ooooouuuuuh!=Ooooouuuuuh! -death=death -You see Death. Do you have the will to stare him in the face? Today he's not in the mood to bargain with your soul.=You see Death. Do you have the will to stare him in the face? Today he's not in the mood to bargain with your soul. -Judgement is coming!=Judgement is coming! -Uuuuhhhhh!, i'm gonna get you!=Uuuuhhhhh!, i'm gonna get you! -invisible man=invisible man -You see nothing but the landscape. As you stare at the point where you think you have seen something, you suddenly see the slight contour of a man!=You see nothing but the landscape. As you stare at the point where you think you have seen something, you suddenly see the slight contour of a man! -My unnatural state should evoke fear in you ...=My unnatural state should evoke fear in you ... -green phantom=green phantom -You see a green phantom.=You see a green phantom. -incorporeal armor=incorporeal armor -You see an incorporeal armor. You are quite surprised as it walks towards you because there is nobody in it.=You see an incorporeal armor. You are quite surprised as it walks towards you because there is nobody in it. -Thou shall not pillage!=Thou shall not pillage! -black death=black death -You see a black death. With his scythe he reaps your soul and then he takes it over the river where it has to stay until judgement day arrives.=You see a black death. With his scythe he reaps your soul and then he takes it over the river where it has to stay until judgement day arrives. -Your time to die has come!=Your time to die has come! -Say goodbye to this world!=Say goodbye to this world! -golden death=golden death -You see the golden death. He came to take your soul away to pay for all your sins.=You see the golden death. He came to take your soul away to pay for all your sins. -You will dream of me tonight...=You will dream of me tonight... -You will pay for your greed!=You will pay for your greed! -vampire bride=vampire bride -You see a vampire bride. She was resurrected from the dead by a wicked vampire lord.=You see a vampire bride. She was resurrected from the dead by a wicked vampire lord. -vampirette=vampirette -You see a Vampirette. Beware of its sharp teeth! She wants to drink your blood and turn you into a vampire!=You see a Vampirette. Beware of its sharp teeth! She wants to drink your blood and turn you into a vampire! -vampire lord=vampire lord -You see a vampire lord. Be careful, he is after your blood!=You see a vampire lord. Be careful, he is after your blood! -bloody zombie=bloody zombie -You see a bloody zombie. Can you see the drops of blood which fall from his body?=You see a bloody zombie. Can you see the drops of blood which fall from his body? -brrraaaaaaaaaains=brrraaaaaaaaaains -zombie=zombie -You see a zombie. It woke up just to take revenge, be silent.=You see a zombie. It woke up just to take revenge, be silent. -headless monster=headless monster -You see a headless monster, how can he hear that we come to him?=You see a headless monster, how can he hear that we come to him? -rotten zombie=rotten zombie -You see a rotten zombie. You are disgusted as you see its rotten skin that hangs down in pieces from his half rotten bones.=You see a rotten zombie. You are disgusted as you see its rotten skin that hangs down in pieces from his half rotten bones. -bluuergghhhoooorrghhhhh=bluuergghhhoooorrghhhhh diff --git a/data/languages/es.txt b/data/languages/es.txt index 0d4abc6e4e..24a41b8c52 100644 --- a/data/languages/es.txt +++ b/data/languages/es.txt @@ -1,529 +1,2886 @@ -dress=vestido -You see a dress, which provides practically no defense. It is barely worth picking up.=Ves un vestido, que no proporciona prácticamente defensa alguna. Apenas vale la pena recogerlo. -leather armor=armadura de cuero -You see a worn suit of leather armor. It will provide only minimal protection.=Ves un traje de armadura de cuero desgastado. Sólo te proporcionará una mínima protección. -leather cuirass=coraza de cuero -You see a leather cuirass, well made but still able to be pierced by sharp swords.=Ves una coraza de cuero, bien hecha pero que todavía puede ser traspasada por espadas afiladas. -leather scale armor=armadura de escamas de cuero -You see a suit of leather scale armor. The leather is toughened somehow for better protection.=Ves un traje de armadura de escamas de cuero. El cuero ha sido endurecido de alguna forma para una mejor protección. -pauldroned leather cuirass=coraza de cuero con hombreras. -You see a pauldroned leather cuirass. It was crafted to withstand minor attacks.=Ves una coraza de cuero con hombreras. Fue diseñada para soportar ataques menores. -studded armor=armadura tachonada -You see a suit of studded armor. Some studs are loose or rusty but it is still fit for purpose.=Ves un traje de armadura tachonada. Algunos tachones están sueltos u oxidados pero todavía es adecuada para su finalidad. -chain armor=cota de malla -You see a suit of chain armor. Perhaps even if you don't need it, you can sell it.=Ves un traje de armadura de cota de malla. Quizá incluso si no la necesitas, puedas venderla. -enhanced chainmail=cota de malla mejorada -You see a robust suit of enhanced chainmail. The links are closely spaced so even the sharpest point should not pierce it.=Ves un traje robusto de cota de malla. Los eslabones están muy próximos entre sí por lo que incluso la punta más afilada no debería de poder traspasarla. -scale armor=armadura de escamas -You see a suit of scale armor. It is not flexible like chain, but will be a better defense.=Ves un traje de armadura de escamas. No es flexible como la cota de malla, pero te proporcionará una mejor defensa. -iron scale armor=armadura de escamas de hierro -You see a suit of iron scale armor, whose rounded scales will protect you from many blows.=Ves un traje de armadura de escamas de hierro, cuyas escamas redondeadas te protegerán de muchos golpes. -golden chainmail=cota de malla de oro -You see a valuable suit of golden chainmail. It is crafted from fine links of expensive metal.=Ves un valioso traje de cota de malla de oro. Se ha elaborado a partir de finos eslabones de caro metal. -pauldroned iron cuirass=coraza de hierro con hombreras -You see a formidable pauldroned iron cuirass. The pauldrons (shoulder pieces) will protect your upper half.=Ves una formidable coraza de hierro con hombreras. Las hombreras (las piezas de los hombros) protegerán tu mitad superior. -plate armor=armadura de placas de acero -You see a suit of plate armor. You imagine it would be heavy but the steel is well tempered and the suit is very strong.=Ves un traje de armadura de placas de acero. Te imaginas que será muy pesada pero el acero está bien templado y el traje es muy resistente. -red armor=armadura roja -You see a suit of red armor. The color is a striking crimson.=Ves un traje de armadura roja. Su color es de un llamativo tono carmesí. -blue armor=armadura azul -You see a suit of blue armor. The color is stunning azure.=Ves un traje de armadura azul. Su color es un impresionante azul. -elvish armor=armadura élfica -You see a suit of elvish armor. It's made from tree leaves! But somehow constructed to give strong defense.=Ves un traje de armadura élfica ¡Está elaborada a partir de hojas de árbol! Pero construido de alguna forma para proporcionar una resistente defensa. + +## +## Language: Spanish (es) +## Translators: unknown, AntumDeluge +## + + +# items names and descriptions + +## armors + barbarian armor=armadura de bárbaro You see a suit of barbarian armor. It's dirty and smelly, but still, rather strong.=Ves un traje de armadura de bárbaro. Está sucia y huele mal, pero aún así, es bastante resistente. -golden armor=armadura de oro -You see a suit of golden armor. It's made from the best materials with great attention to detail.=Ves un traje de armadura de oro. Está elaborada a partir de los mejores materiales con gran atención al detalle. -shadow armor=armadura de sombra -You see a suit of shadow armor. It's hard to decide with what it is made, it is so dark.=Ves un traje dearmadura de sombra. Es difícil decidir de qué está hecha, es tan oscura. -dwarvish armor=armadura enana -You see a dwarvish armor. It's made with ingenuity and skill.=Ves una armadura enana. Se ha elaborado con ingenio y habilidad. -stone armor=armadura de piedra -You see a suit of stone armor. Swords will crumble if they try to pierce it.=Ves un traje de armadura de piedra. Las espadas se quebrarán si intentan traspasarla. -ice armor=armadura de hielo -You see a suit of ice armor, to protect even the coldest of hearts.=Ves un traje de armadura de hielo, para proteger incluso al mas frío de los corazones. -chaos armor=armadura del caos -You see a suit of chaos armor. The reflecting contrasting colors make no sense to you.=Ves un traje de armadura del caos. Los contrastantes colores que se reflejan en ella para ti no tienen ningún sentido. -xeno armor=armadura de xeno -You see a suit of xeno armor. The colors remind you of the xenocium hybrid creature.=Ves un traje de armadura de xeno. Sus colores te recuerdan a la criatura híbrida xenocium. -mainio armor=armadura de mainio -You see a strange strange suit of mainio armor. What it lacks in subtlety it makes up for with strength.=Ves un extraño traje de armadura de mainio. Lo que le falta en sutileza lo suple con fortaleza. -magic plate armor=armadura de placas de acero mágica -You see a suit of magic plate armor. The steel is well tempered and the suit is very strong.=Ves un traje de armadura de placas de acero mágica. El acero está bien templado y el traje es muy resistente. + black armor=armadura negra You see a suit of black armor. Does it put you in a dark mood?=Ves un traje de armadura negra ¿Te pone de negro humor? + +blue armor=armadura azul +You see a suit of blue armor. The color is stunning azure.=Ves un traje de armadura azul. Su color es un impresionante azul. + +chain armor=cota de malla +You see a suit of chain armor. Perhaps even if you don't need it, you can sell it.=Ves un traje de armadura de cota de malla. Quizá incluso si no la necesitas, puedas venderla. + +chaos armor=armadura del caos +You see a suit of chaos armor. The reflecting contrasting colors make no sense to you.=Ves un traje de armadura del caos. Los contrastantes colores que se reflejan en ella para ti no tienen ningún sentido. + +dress=vestido +You see a dress, which provides practically no defense. It is barely worth picking up.=Ves un vestido, que no proporciona prácticamente defensa alguna. Apenas vale la pena recogerlo. + +dwarvish armor=armadura enana +You see a dwarvish armor. It's made with ingenuity and skill.=Ves una armadura enana. Se ha elaborado con ingenio y habilidad. + +elvish armor=armadura élfica +You see a suit of elvish armor. It's made from tree leaves! But somehow constructed to give strong defense.=Ves un traje de armadura élfica ¡Está elaborada a partir de hojas de árbol! Pero construido de alguna forma para proporcionar una resistente defensa. + +enhanced chainmail=cota de malla mejorada +You see a robust suit of enhanced chainmail. The links are closely spaced so even the sharpest point should not pierce it.=Ves un traje robusto de cota de malla. Los eslabones están muy próximos entre sí por lo que incluso la punta más afilada no debería de poder traspasarla. + +golden armor=armadura de oro +You see a suit of golden armor. It's made from the best materials with great attention to detail.=Ves un traje de armadura de oro. Está elaborada a partir de los mejores materiales con gran atención al detalle. + +golden chainmail=cota de malla de oro +You see a valuable suit of golden chainmail. It is crafted from fine links of expensive metal.=Ves un valioso traje de cota de malla de oro. Se ha elaborado a partir de finos eslabones de caro metal. + +ice armor=armadura de hielo +You see a suit of ice armor, to protect even the coldest of hearts.=Ves un traje de armadura de hielo, para proteger incluso al mas frío de los corazones. + +iron scale armor=armadura de escamas de hierro +You see a suit of iron scale armor, whose rounded scales will protect you from many blows.=Ves un traje de armadura de escamas de hierro, cuyas escamas redondeadas te protegerán de muchos golpes. + +leather armor=armadura de cuero +You see a worn suit of leather armor. It will provide only minimal protection.=Ves un traje de armadura de cuero desgastado. Sólo te proporcionará una mínima protección. + +leather cuirass=coraza de cuero +You see a leather cuirass, well made but still able to be pierced by sharp swords.=Ves una coraza de cuero, bien hecha pero que todavía puede ser traspasada por espadas afiladas. + +leather scale armor=armadura de escamas de cuero +You see a suit of leather scale armor. The leather is toughened somehow for better protection.=Ves un traje de armadura de escamas de cuero. El cuero ha sido endurecido de alguna forma para una mejor protección. + +magic plate armor=armadura de placas de acero mágica +You see a suit of magic plate armor. The steel is well tempered and the suit is very strong.=Ves un traje de armadura de placas de acero mágica. El acero está bien templado y el traje es muy resistente. + +mainio armor=armadura de mainio +You see a strange suit of mainio armor. What it lacks in subtlety it makes up for with strength.=Ves un extraño traje de armadura de mainio. Lo que le falta en sutileza lo suple con fortaleza. + mithril armor=armadura de mithril You see a suit of mithril armor. Its tightly woven links are truly amazing.=Ves un traje de armadura de mithril. Sus eslabones apretadamente tejidos son verdaderamante asombrosos. + +pauldroned iron cuirass=coraza de hierro con hombreras +You see a formidable pauldroned iron cuirass. The pauldrons (shoulder pieces) will protect your upper half.=Ves una formidable coraza de hierro con hombreras. Las hombreras (las piezas de los hombros) protegerán tu mitad superior. + +pauldroned leather cuirass=coraza de cuero con hombreras +You see a pauldroned leather cuirass. It was crafted to withstand minor attacks.=Ves una coraza de cuero con hombreras. Fue diseñada para soportar ataques menores. + +plate armor=armadura de placas de acero +You see a suit of plate armor. You imagine it would be heavy but the steel is well tempered and the suit is very strong.=Ves un traje de armadura de placas de acero. Te imaginas que será muy pesada pero el acero está bien templado y el traje es muy resistente. + +red armor=armadura roja +You see a suit of red armor. The color is a striking crimson.=Ves un traje de armadura roja. Su color es de un llamativo tono carmesí. + +royal armor= +You see a suit of royal armor. This armor is fit for a king.= + +scale armor=armadura de escamas +You see a suit of scale armor. It is not flexible like chain, but will be a better defense.=Ves un traje de armadura de escamas. No es flexible como la cota de malla, pero te proporcionará una mejor defensa. + +scuba gear= +You see a scuba suit with a heavy helmet attached. It is dense enough to offer protection under the waves but of little use on land.= + +shadow armor=armadura de sombra +You see a suit of shadow armor. It's hard to decide with what it is made, it is so dark.=Ves un traje dearmadura de sombra. Es difícil decidir de qué está hecha, es tan oscura. + +stone armor=armadura de piedra +You see a suit of stone armor. Swords will crumble if they try to pierce it.=Ves un traje de armadura de piedra. Las espadas se quebrarán si intentan traspasarla. + +studded armor=armadura tachonada +You see a suit of studded armor. Some studs are loose or rusty but it is still fit for purpose.=Ves un traje de armadura tachonada. Algunos tachones están sueltos u oxidados pero todavía es adecuada para su finalidad. + +xeno armor=armadura de xeno +You see a suit of xeno armor. The colors remind you of the xenocium hybrid creature.=Ves un traje de armadura de xeno. Sus colores te recuerdan a la criatura híbrida xenocium. + + +## arrows + wooden arrow=flecha de madera You see a wooden arrow. With a supply of arrows in one hand and a bow in the other you can attack from a distance.=Ves una flecha de madera. Con un surtido de flechas en una mano y un arco en la otra puedes atacar a distancia. + steel arrow=flecha de acero You see a shining steel arrow. With plenty of these, and a good bow, you'll be a formidable archer.=Ves una flecha de reluciente acero. Con muchas de éstas, y un buen arco, serás un arquero formidable. + golden arrow=flecha de oro -You see a golden arrow. With a fist full of these and a good bow, you will surely take down your enemies quicker.=Ves una flecha de oro. Con un puñado de éstas y un buen arco, derribarás a tus enemigos más rápido. +You see a golden arrow. With a fist full of these and a good bow, you will surely take down your enemies quicker.=Ves una flecha de oro. Con un puñado de éstas y un buen arco, derribarás a tus enemigos más rápido. + +fire arrow=flecha de fuego +You see a fire arrow. The tip burns brightly.=Ves una flecha de fuego. La punta arde brillantemente. + +ice arrow=flecha de hielo +You see an ice arrow. It can deliver a bitter sting.=Ves una flecha de hielo. Puede infligir una picadura penosa. + +light arrow=flecha de luz +You see a light arrow. It shines with a holy radiance.=Ves una flecha de luz. Brilla con un resplandor santo. + +dark arrow=flecha de oscuridad +You see a dark arrow. It's hard to describe.=Ves una flecha de oscuridad. Es difícil de describir. + power arrow=flecha de potencia -You see a power arrow. The sting of this arrow will surely defeat your enemies, when equipped with a bow.=Ves una flecha de potencia. La punzada de esta flecha seguramente derrotará a tus enemigos, cuando vaya equipada con un arco. -sickle=hoz -You see a sickle, it is a useful tool for farming.=Ves una hoz, es una herramienta útil para la labranza. -old scythe=guadaña vieja -You see an old scythe, it used to be weapon of death. But now it is only a useful tool for farming.=Ves una vieja guadaña, solía ser el arma de la muerte. Pero ahora sólo es una útil herramienta de labranza. -small axe=hacha pequeña -You see a small axe. It appears well made but you suspect it is too light to do much damage.=Ves un hacha pequeña. Parece bien elaborada pero sospechas que es demasiado ligera para hacer mucho daño. -hand axe=hacha de mano -You see a hand axe, well worn from years of use but may prove useful.=Ves un hacha de mano, muy desgastada por años de uso pero podría resultar útil. +You see a power arrow. The sting of this arrow will surely defeat your enemies, when equipped with a bow.=Ves una flecha de potencia. La punzada de esta flecha seguramente derrotará a tus enemigos, cuando vaya equipada con un arco. + + +## axes + axe=hacha You see a rusty but heavy axe. What it lacks in sharpness it makes up for in weight.=Ves un hacha oxidada pero pesada. Lo que le falta en filo lo suple con peso -battle axe=hacha de batalla -You see a battle axe. It is well crafted and sharp despite its age.=Ves un hacha de batalla. Está bien diseñada y afilada a pesar de su antigüedad. -scythe=guadaña -You see the weapon of death - a scythe. It looks dangerous but perhaps also a useful tool for farming.=Ves el arma de la muerte - una guadaña. Parece peligrosa pero quizá también sea una herramienta útil para la labranza. -twoside axe=hacha de doble hoja -You see a well crafted twoside axe.=Ves un hacha de doble hoja bien elaborada. + bardiche=bardiche You see a rare and powerful bardiche.=Ves una rara y poderosa bardiche. -golden twoside axe=hacha de doble hoja de oro -You see a golden twoside axe. This axe is a rare find.=Ves un hacha de doble hoja de oro. Este hacha es un raro hallazgo. -halberd=alabarda -You see a rare halberd, with an axe's blade fused onto a shaft. It is both heavy and keen-edged.=Ves una rara alabarda, con una hoja de hacha enastada en un astil. Es a la vez pesada y de borde afilado. -magic twoside axe=hacha de doble hoja mágica -You see a magic twoside axe. The nature of the magic isn't clear to you.=Ves un hacha de doble hoja mágica. La naturaleza de la magia no está clara para ti. -durin axe=hacha de durin -You see a durin axe, a great prize from the dwarves.=Ves un hacha de durin, un gran premio de los enanos. -black scythe=guadaña negra -You see a black scythe. Its attack is awesome for a farming tool.=Ves una guadaña negra. Su ataque es asombroso para tratarse de una herramienta de labranza. -chaos axe=hacha del caos -You see a chaos axe. The handle is some alloy you can't recognize and the blade is golden colored, but thinking too long about it makes you confused.=Ves un hacha del caos. El astil es de una aleación que no sabes reconocer y la hoja es de color dorado, pero pensar mucho en ello te causa confusión. + +battle axe=hacha de batalla +You see a battle axe. It is well crafted and sharp despite its age.=Ves un hacha de batalla. Está bien diseñada y afilada a pesar de su antigüedad. + black halberd=alabarda negra You see a black halberd. It's immensely heavy so will make huge blows but you can only swing it at a very slow rate.=Ves una alabarda negra. Es inmensamente pesada asi que producirá enormes golpes pero sólo la puedes mover a un ritmo muy lento. + +black scythe=guadaña negra +You see a black scythe. Its attack is awesome for a farming tool.=Ves una guadaña negra. Su ataque es asombroso para tratarse de una herramienta de labranza. + +chaos axe=hacha del caos +You see a chaos axe. The handle is some alloy you can't recognize and the blade is golden colored, but thinking too long about it makes you confused.=Ves un hacha del caos. El astil es de una aleación que no sabes reconocer y la hoja es de color dorado, pero pensar mucho en ello te causa confusión. + +durin axe=hacha de durin +You see a durin axe, a great prize from the dwarves.=Ves un hacha de durin, un gran premio de los enanos. + +golden twoside axe=hacha de doble hoja de oro +You see a golden twoside axe. This axe is a rare find.=Ves un hacha de doble hoja de oro. Este hacha es un raro hallazgo. + +halberd=alabarda +You see a rare halberd, with an axe's blade fused onto a shaft. It is both heavy and keen-edged.=Ves una rara alabarda, con una hoja de hacha enastada en un astil. Es a la vez pesada y de borde afilado. + +hand axe=hacha de mano +You see a hand axe, well worn from years of use but may prove useful.=Ves un hacha de mano, muy desgastada por años de uso pero podría resultar útil. + +magic twoside axe=hacha de doble hoja mágica +You see a magic twoside axe. The nature of the magic isn't clear to you.=Ves un hacha de doble hoja mágica. La naturaleza de la magia no está clara para ti. + +old scythe=guadaña vieja +You see an old scythe, it used to be a weapon of death. But now it is only a useful tool for farming.=Ves una vieja guadaña, solía ser el arma de la muerte. Pero ahora sólo es una útil herramienta de labranza. + +scythe=guadaña +You see the weapon of death - a scythe. It looks dangerous but perhaps also a useful tool for farming.=Ves el arma de la muerte - una guadaña. Parece peligrosa pero quizá también sea una herramienta útil para la labranza. + +sickle=hoz +You see a sickle, it is a useful tool for farming.=Ves una hoz, es una herramienta útil para la labranza. + +small axe=hacha pequeña +You see a small axe. It appears well made but you suspect it is too light to do much damage.=Ves un hacha pequeña. Parece bien elaborada pero sospechas que es demasiado ligera para hacer mucho daño. + +twoside axe=hacha de doble hoja +You see a well crafted twoside axe.=Ves un hacha de doble hoja bien elaborada. + + +## books + black book=libro negro You see the black book that Jynath kept out for so long.=Ves el libro negro que Jynath mantuvo oculto durante tanto tiempo. + blue book=libro azul You see a book with the words GEMS AND MINERALS on the front cover.=Ves un libro azul con las palabras GEMAS Y MINERALES en la cubierta frontal. -leather boots=botas de cuero -You see a simple pair of leather boots to give your feet some protection.=Ves un simple par de botas de cuero para dar algo de protección a tus pies. -studded boots=botas tachonadas -You see a pair of worn studded boots with moderate protection for your feet.=Ves un par de botas tachonadas desgastadas que proporcionan una protección moderada a tus pies. -chain boots=botas de cota de malla -You see a pair of chain boots, quite rusty but still strong.=Ves un par de botas de cota de malla, bastante oxidadas pero todavía resistentes. -red boots=botas rojas. -You see a pair of red boots, the color reminds you of fresh blood.=Ves un par de botas rojas, su color te recuerda a la sangre fresca. -steel boots=botas de acero -You see a pair of steel boots, fit for any soldier.=Ves un par de botas de acero, adecuadas para cualquier soldado. -blue boots=botas azules -You see a pair of blue boots.=Ves un par de botas azules. -elvish boots=botas élficas -You see a pair of elvish boots.=Ves un par de botas élficas. -golden boots=botas de oro -You see a pair of golden boots. They will be heavy to wear but well worth their weight.=Ves un par de botas de oro. Serán pesadas de llevar pero valen su peso. -shadow boots=botas de sombra -You see a pair of shadow boots. You can't fathom what they are made of.=Ves un par de botas de sombra. No puedes entender de qué están hechas. -stone boots=botas de piedra -You see a pair of stone boots. They may be heavy, but they protect your feet well.=Ves un par de botas de piedra. Puede que sean pesadas, pero te protegen bien los pies. -chaos boots=botas del caos -You see a pair of chaos boots. Your steps are unsteady in them, but the extra defense they give is worth it.=Ves un par de botas del caos. Tus pasos son inestables con ellas, pero la defensa extra que proporcionan lo vale. -mainio boots=botas de mainio -You see a pair of mainio boots. It's hard to believe that boots looking like this would protect your feet so well as they do.=Ves un par de botas de mainio. Cuesta creer que unas botas con esa apariencia te pudiesen proteger los pies tan bien como lo hacen. -green thing boots=botas de la cosa verde -You see a pair of green thing boots. They're slimy inside, ewwww.=Ves un par de botas de la cosa verde. Estan pegajosas por dentro, puajjj. -xeno boots=botas de xeno -You see a fine specimen of craftmanship, a pair of xeno boots.=Ves una excelente obra de artesanía, un par de botas de xeno. + +bestiary=bestiario +You see a bestiary, containing information about enemies found in Faiumoni.=Ves un bestiario que contiene información sobre los enemigos encontrados en Faiumoni. + + +## boots + black boots=botas negras You stub your toe on this pair of black boots. Perhaps they bring you bad luck?=te diste en el dedo del pie con algo usando estas botas ¿Quizá te dan mala suerte? -mithril boots=botas de mithril -You see a sparkling set of mithril boots.=Ves un brillante conjunto de botas de mithril. + +blue boots=botas azules +You see a pair of blue boots.=Ves un par de botas azules. + +chain boots=botas de cota de malla +You see a pair of chain boots, quite rusty but still strong.=Ves un par de botas de cota de malla, bastante oxidadas pero todavía resistentes. + +chaos boots=botas del caos +You see a pair of chaos boots. Your steps are unsteady in them, but the extra defense they give is worth it.=Ves un par de botas del caos. Tus pasos son inestables con ellas, pero la defensa extra que proporcionan lo vale. + +elvish boots=botas élficas +You see a pair of elvish boots.=Ves un par de botas élficas. + +golden boots=botas de oro +You see a pair of golden boots. They will be heavy to wear but well worth their weight.=Ves un par de botas de oro. Serán pesadas de llevar pero valen su peso. + +green thing boots=botas de la cosa verde +You see a pair of green thing boots. They're slimy inside, ewwww.=Ves un par de botas de la cosa verde. Estan pegajosas por dentro, puajjj. + killer boots=botas asesinas You see a high heeled pair of killer boots. Are you brave enough to wear them?=Ves un par de botas asesinas de tacón alto ¿Eres lo bastante valiente como para llevarlas? + +leather boots=botas de cuero +You see a simple pair of leather boots to give your feet some protection.=Ves un simple par de botas de cuero para dar algo de protección a tus pies. + +magic plate boots= + +mainio boots=botas de mainio +You see a pair of mainio boots. It's hard to believe that boots looking like this would protect your feet so well as they do.=Ves un par de botas de mainio. Cuesta creer que unas botas con esa apariencia te pudiesen proteger los pies tan bien como lo hacen. + +mithril boots=botas de mithril +You see a sparkling set of mithril boots.=Ves un brillante conjunto de botas de mithril. + +red boots=botas rojas +You see a pair of red boots, the color reminds you of fresh blood.=Ves un par de botas rojas, su color te recuerda a la sangre fresca. + +royal boots= +You see a pair of royal boots. They have magical properties= + +shadow boots=botas de sombra +You see a pair of shadow boots. You can't fathom what they are made of.=Ves un par de botas de sombra. No puedes entender de qué están hechas. + +steel boots=botas de acero +You see a pair of steel boots, fit for any soldier.=Ves un par de botas de acero, adecuadas para cualquier soldado. + +stone boots=botas de piedra +You see a pair of stone boots. They may be heavy, but they protect your feet well.=Ves un par de botas de piedra. Puede que sean pesadas, pero te protegen bien los pies. + +studded boots=botas tachonadas +You see a pair of worn studded boots with moderate protection for your feet.=Ves un par de botas tachonadas desgastadas que proporcionan una protección moderada a tus pies. + +xeno boots=botas de xeno +You see a fine specimen of craftmanship, a pair of xeno boots.=Ves una excelente obra de artesanía, un par de botas de xeno. + + +## boxes + present=regalo You see a present. Open it.=Ves un regalo. Abrelo. + basket=cesta You see an easter basket. Look inside.=Ves una cesta de Pascua. Mira dentro de ella. + stocking=calcetín You see a christmas stocking. Look inside.=Ves un calcetín de Navidad. Mira dentro de él. -cloak=capa -You see a simple cloth cloak.=Ves una simple capa de tela. -elf cloak=capa de elfo -You see a white elf cloak. It doesn't look very warm.=Ves una capa de elfo blanca. No parece muy cálida. -dwarf cloak=capa enana -You see a dwarf cloak. Apparently this passes for fashion in some parts, but to you it looks more functional than fashionable.=Ves una capa enana. Aparentemente esto es lo que se entiende por moda en algunos lugares, pero a ti te resulta más funcional que de moda. -red cloak=capa roja -You see a red cloak covered in polka dots. It's terribly snazzy.=Ves una capa roja cubierta de lunares. Es terriblemente elegante. -elvish cloak=capa élfica -You see an elvish cloak. The material is dark, but thick and warm.=Ves una capa élfica. Su material es oscuro, pero es grueso y cálido. -blue striped cloak=capa de rayas azules -You see a fashionable blue striped cloak.=Ves una capa de rayas azules de moda. -blue elf cloak=capa de elfo azul -You see a blue elf cloak. The material is thick and warm.=Ves una capa de elfo azul. El material es grueso y cálido. -stone cloak=capa de piedra -You see a stone cloak. It's hard to tell how it was made, as the fibres look like narrow threads of mineral.=Ves una capa de piedra. Es difícil decir como se elaboró, ya que las fibras parecen delgados hilos de mineral. -green dragon cloak=capa de dragón verde. -You see a huge green dragon cloak. It's probably too big for you but, even dragging on the floor it will provide excellent protection.=Ves una enorme capa de dragón verde. Es probablemente demasiado grande para ti pero, aunque arrastre por el suelo te proporcionará una protección excelente. -bone dragon cloak=capa de dragón de hueso -You see a bone dragon cloak. It's incredibly old, like its last owner.=Ves una capa de dragón de hueso. Es increíblemente antigua, como su último dueño. -lich cloak=capa de lich -You see a purple lich cloak. It's stained with blood - yours, from the previous undead owner, or perhaps the blood of his enemies...=Ves una capa púrpura de lich. Está manchada de sangre - la tuya, la de su anterior dueño, o quizá la sangre de sus enemigos... -vampire cloak=capa de vampiro -You see a vibrant red vampire cloak. Wearing it sends a shiver down your spine.=Ves una capa de vampiro de vibrante color rojo. Llevarla puesta te produce un escalofrío por tu columna vertebral. -shadow cloak=capa de sombra -You see a shadow cloak, in colors of the shadow armor.=Ves una capa de sombra, en los mismos colores que la armadura de sombra. -blue dragon cloak=capa de dragón azul -You see a blue dragon cloak. The weave is tight and strong, though you wish the color drew less attention.=Ves una capa de dragón azul. La trama es apretada y resistente, aunque desearías que su color llamase menos la atención. -red dragon cloak=capa de dragón rojo -You see a red dragon cloak. He didn't have one for a long time but finally caved in to peer pressure and got one.=Ves una capa de dragón rojo. Él no había tenido ninguna durante mucho tiempo pero finalmente cedió a la presión de sus compañeros y se buscó una. -black dragon cloak=capa de dragón negro -You see a torn section from an enormous black dragon cloak. You're very lucky: the piece is just the right size for a human to wear.=Ves un retal arrancado de una enorme capa de dragón negro. Tienes mucha suerte: la pieza es justo del tamaño adecuado para que lo lleve un humano. -golden cloak=capa de oro -You see a golden cloak. See how the light glistens off the gold, it is a prized possession.=Ves una capa de oro. Ves cómo la luz brilla saliendo del oro, es una preciada posesión. -chaos cloak=capa del caos -You see a chaos cloak. The colors look like oil shimmering on a puddle.=Ves una capa del caos. Sus colores parecen aceite brillando en un charco. -mainio cloak=capa de mainio -You see a mainio cloak. It's flashing colors are absurd! But you wear it because it's very well made.=Ves una capa de mainio ¡Sus colores parpadeantes son absurdos! Pero la llevas puesta porque está muy bien elaborada. -xeno cloak=capa de xeno -You see a xeno cloak. It's heavy and thick, giving you a good protection.=Ves una capa de xeno. Es pesada y gruesa, proporcionandote una buena protección. + + +## cloaks + black cloak=capa negra You see a black cloak. The last owner had black thoughts. Will you?=Ves una capa negra. Su último dueño tuvo negros pensamientos ¿Los tendrás tú? + +black dragon cloak=capa de dragón negro +You see a torn section from an enormous black dragon cloak. You're very lucky: the piece is just the right size for a human to wear.=Ves un retal arrancado de una enorme capa de dragón negro. Tienes mucha suerte: la pieza es justo del tamaño adecuado para que lo lleve un humano. + +blue dragon cloak=capa de dragón azul +You see a blue dragon cloak. The weave is tight and strong, though you wish the color drew less attention.=Ves una capa de dragón azul. La trama es apretada y resistente, aunque desearías que su color llamase menos la atención. + +blue elf cloak=capa de elfo azul +You see a blue elf cloak. The material is thick and warm.=Ves una capa de elfo azul. El material es grueso y cálido. + +blue striped cloak=capa de rayas azules +You see a fashionable blue striped cloak.=Ves una capa de rayas azules de moda. + +bone dragon cloak=capa de dragón de hueso +You see a bone dragon cloak. It's incredibly old, like its last owner.=Ves una capa de dragón de hueso. Es increíblemente antigua, como su último dueño. + +chaos cloak=capa del caos +You see a chaos cloak. The colors look like oil shimmering on a puddle.=Ves una capa del caos. Sus colores parecen aceite brillando en un charco. + +cloak=capa +You see a simple cloth cloak.=Ves una simple capa de tela. + +dwarf cloak=capa enana +You see a dwarf cloak. Apparently this passes for fashion in some parts, but to you it looks more functional than fashionable.=Ves una capa enana. Aparentemente esto es lo que se entiende por moda en algunos lugares, pero a ti te resulta más funcional que de moda. + +elf cloak=capa de elfo +You see a white elf cloak. It doesn't look very warm.=Ves una capa de elfo blanca. No parece muy cálida. + +elvish cloak=capa élfica +You see an elvish cloak. The material is dark, but thick and warm.=Ves una capa élfica. Su material es oscuro, pero es grueso y cálido. + +golden cloak=capa de oro +You see a golden cloak. See how the light glistens off the gold, it is a prized possession.=Ves una capa de oro. Ves cómo la luz brilla saliendo del oro, es una preciada posesión. + +green dragon cloak=capa de dragón verde +You see a huge green dragon cloak. It's probably too big for you but, even dragging on the floor it will provide excellent protection.=Ves una enorme capa de dragón verde. Es probablemente demasiado grande para ti pero, aunque arrastre por el suelo te proporcionará una protección excelente. + +lich cloak=capa de lich +You see a purple lich cloak. It's stained with blood - yours, from the previous undead owner, or perhaps the blood of his enemies...=Ves una capa púrpura de lich. Está manchada de sangre - la tuya, la de su anterior dueño, o quizá la sangre de sus enemigos... + +magic cloak= +You see a magic cloak.= + +mainio cloak=capa de mainio +You see a mainio cloak. Its flashing colors are absurd! But you wear it because it's very well made.=Ves una capa de mainio ¡Sus colores parpadeantes son absurdos! Pero la llevas puesta porque está muy bien elaborada. + mithril cloak=capa de mithril You see a mithril cloak. Its weave is so fine and its looks are breathtaking.=Ves una capa de mithril. Su trama es tan fina y su apariencia quita la respiración. -rift cloak=capa de rift -You see a rift cloak. Almost useless in the battle, but saves your life when you enter a magical rift.=Ves una capa de rift. Casi inútil en la batalla, pero te salva la vida cuando entras en un rift mágico. -club=garrote -You see a wooden club. It won't kill much but is better than no weapon.=Ves un garrote de madera. No matará mucho pero es mejor que no tener ningún arma. -staff=bastón -You see a staff. This is a seldom seen or used weapon.=Ves un bastón. Ésta es un arma que rara vez se ve o se usa. -mace=maza -You see a mace, much neglected in years of use.=Ves una maza, muy descuidada en los años de uso. -flail=mangual -You see a military flail. Swinging it in circles will tire you but it will be an effective weapon.=Ves un mangual militar. Moverlo en círculos te cansará pero será un arma efectiva. -enhanced mace=maza mejorada -You see an enhanced mace. The heavy attacking end makes it hard to balance.=Ves una maza mejorada. Su pesado extremo de ataque la hace difícil de equilibrar. -golden mace=maza de oro -You see a shining golden mace. Despite looking ornamental, it is sharp enough to do some damage.=Ves una maza de oro brillante. A pesar de parece ornamental, está lo bastante afilada para hacer algún daño. -skull staff=bastón de calavera -You see a rare skull staff. It appears mystical but will work like any other staff.=Ves un bastón de calavera. Parece místico pero funcionará como cualquier otro bastón. -morning star=lucero del alba -You see a morning star. This is a very rare item indeed.=Ves un lucero del alba. Éste es realmente un artículo raro. -hammer=martillo -You see a common hammer. It is heavy enough that you should be able to bludgeon your enemies with it, but only slowly=Ves un martillo común. Es bastante pesado por lo que deberías de ser capaz de aporrear a tus enemigos con él, pero sólo con lentitud. -golden hammer=martillo de oro -You see a valuable golden hammer. This golden weapon may be worth a lot to someone. It has a slow rate of attack.=Ves un valioso martillo de oro. Este arma de oro puede valer muchísimo para alguien. Tiene un ritmo de ataque lento. -war hammer=martillo de guerra -You see a huge war hammer. It seems strong but is so heavy that you can only lift it to attack every so often..=Ves un enorme martillo de guerra. Parece potente pero es tan pesado que sólo lo puedes levantar de vez en cuando para atacar.. -ice war hammer=martillo de guerra de hielo -You see a ice war hammer. The last owner must have been huge, to wield it.=Ves un martillo de guerra de hielo. Su último dueño debe de haber sido enorme, para manejarlo. + +red cloak=capa roja +You see a red cloak covered in polka dots. It's terribly snazzy.=Ves una capa roja cubierta de lunares. Es terriblemente elegante. + +red dragon cloak=capa de dragón rojo +You see a red dragon cloak. He didn't have one for a long time but finally caved in to peer pressure and got one.=Ves una capa de dragón rojo. Él no había tenido ninguna durante mucho tiempo pero finalmente cedió a la presión de sus compañeros y se buscó una. + +royal cloak=capa real +You see a royal cloak. It will protect you against mighty foes.=Ves una capa real. Te protegará contra enemigos poderosos. + +shadow cloak=capa de sombra +You see a shadow cloak, in colors of the shadow armor.=Ves una capa de sombra, en los mismos colores que la armadura de sombra. + +stone cloak=capa de piedra +You see a stone cloak. It's hard to tell how it was made, as the fibres look like narrow threads of mineral.=Ves una capa de piedra. Es difícil decir como se elaboró, ya que las fibras parecen delgados hilos de mineral. + +vampire cloak=capa de vampiro +You see a vibrant red vampire cloak. Wearing it sends a shiver down your spine.=Ves una capa de vampiro de vibrante color rojo. Llevarla puesta te produce un escalofrío por tu columna vertebral. + +xeno cloak=capa de xeno +You see a xeno cloak. It's heavy and thick, giving you good protection.=Ves una capa de xeno. Es pesada y gruesa, proporcionandote una buena protección. + + +## clubs + chaos hammer=martillo del caos You see a chaos hammer. It is a fearsome weapon.=Ves un martillo del caos. Es un arma temible. -ugmash=ugmash -You see a huge ugmash. It can attack with a faster rate than many other clubs.=Ves un enorme ugmash. Puede atacar a un ritmo más rápido que muchos otros garrotes. -vulcano hammer=martillo de vulcano -You see a vulcano hammer. It's said to be a weapon of a God, but attacks with a snail like pace.=Ves un martillo de vulcano. Se dice que es el arma de un dios, pero ataca al ritmo de un caracol. + +club=garrote +You see a wooden club. It won't kill much but is better than no weapon.=Ves un garrote de madera. No matará mucho pero es mejor que no tener ningún arma. + club of thorns=garrote de espinas You see the club of thorns. The thorns are full with blood of tortured humans. It is hugely powerful but will drain your life to use it.=Ves el garrote de espinas. Las espinas estan llenas de sangre de humanos torturados. Es enormemente poderosa pero el usarla agotará tu vida. -necromancers staff=baston de nigromante -You see a cunningly beautiful staff. If you listen carefully you can hear its previous owners' souls screaming from within it.=Ves un bastón engañosamente hermoso. Si escuchas atentamente puedes oir las almas de sus anteriores dueños gritar desde dentro. + +enhanced mace=maza mejorada +You see an enhanced mace. The heavy attacking end makes it hard to balance.=Ves una maza mejorada. Su pesado extremo de ataque la hace difícil de equilibrar. + +flail=mangual +You see a flail. Swinging it in circles will tire you but it will be an effective weapon.=Ves un mangual. Moverlo en círculos te cansará pero será un arma efectiva. + +golden hammer=martillo de oro +You see a valuable golden hammer. This golden weapon may be worth a lot to someone. It has a slow rate of attack.=Ves un valioso martillo de oro. Este arma de oro puede valer muchísimo para alguien. Tiene un ritmo de ataque lento. + +golden mace=maza de oro +You see a shining golden mace. Despite looking ornamental, it is sharp enough to do some damage.=Ves una maza de oro brillante. A pesar de parece ornamental, está lo bastante afilada para hacer algún daño. + +hammer=martillo +You see a common hammer. It is heavy enough that you should be able to bludgeon your enemies with it, but only slowly=Ves un martillo común. Es bastante pesado por lo que deberías de ser capaz de aporrear a tus enemigos con él, pero sólo con lentitud. + +ice war hammer=martillo de guerra de hielo +You see an ice war hammer. The last owner must have been huge to wield it.=Ves un martillo de guerra de hielo. Su último dueño debe de haber sido enorme para manejarlo. + +mace=maza +You see a mace, much neglected in years of use.=Ves una maza, muy descuidada en los años de uso. + +morning star=lucero del alba +You see a morning star. This is a very rare item indeed.=Ves un lucero del alba. Éste es realmente un artículo raro. + rod of the gm=vara del DJ -You see a rod of the GM. This rod is reserved for those with special powers, be careful not to let it fall into the wrong hands.=Ves una vara del DJ. Esta vara se reserva a aquellos con poderes especiales, ten cuidado de no dejar que caiga en las manos equivocadas. +You see a rod of the GM. This rod is reserved for those with special powers, be careful not to let it fall into the wrong hands.=Ves una vara del DJ. Esta vara se reserva a aquellos con poderes especiales, ten cuidado de no dejar que caiga en las manos equivocadas. + +skull staff=bastón de calavera +You see a rare skull staff. It appears mystical but will work like any other staff.=Ves un bastón de calavera. Parece místico pero funcionará como cualquier otro bastón. + +staff=bastón +You see a staff. This is a seldom seen or used weapon.=Ves un bastón. Ésta es un arma que rara vez se ve o se usa. + +ugmash=ugmash +You see a huge ugmash. It can attack with a faster rate than many other clubs.=Ves un enorme ugmash. Puede atacar a un ritmo más rápido que muchos otros garrotes. + +vulcano hammer=martillo de vulcano +You see a vulcano hammer. It's said to be a weapon of a God, but attacks with a snail like pace.=Ves un martillo de vulcano. Se dice que es el arma de un dios, pero ataca al ritmo de un caracol. + +war hammer=martillo de guerra +You see a huge war hammer. It seems strong but is so heavy that you can only lift it to attack every so often.=Ves un enorme martillo de guerra. Parece potente pero es tan pesado que sólo lo puedes levantar de vez en cuando para atacar. + + +## containers + +keyring=llavero +You see a keyring.=Ves un llavero. + empty sack=saco vacío You see an empty sack. It will not stand up, unless you fill it first.=Ves un saco vacío. No se mantendrá en pie, a menos que lo llenes primero. + flask=frasco -You see a flask. This flask is good for storing medicine.=Ves un frasco. Este frasco es bueno para almacenar medicina. +You see a flask. It's good for storing medicine.=Ves un frasco. Es bueno para almacenar medicina. + +eared bottle=botella con asas +You see an eared bottle. It is empty, someone drank the contents.=Ves una botella con asas. Está vacía, alguien se bebió su contenido. + +slim bottle=botella estrecha +You see a slim bottle. It is empty, someone drank the contents.=Ves una botella estrecha. Está vacía, alguien se bebió su contenido. + +fat bottle=botella gruesa +You see a fat bottle. It is empty, someone drank the contents.=Ves una botella gruesa. Está vacía, alguien se bebió su contenido. + +empty goblet=copa vacía +You see an empty goblet. You wonder what could it hold.=Ves una copa vacía. Te preguntas que podría contener. + +goblet=copa +You see a goblet. You look at its contents, and shiver at the blood inside.=Ves una copa. Miras su contenido, y sientes escalofríos al ver sangre dentro. + +vial=ampolleta +You see a vial used in chemestry.=Ves una ampolleta utilizada en química. + + +## crystals + +red emotion crystal=cristal rojo de emoción +You see a red emotion crystal. It feels hot to the touch.=Ves un cristal rojo de emoción. Se siente caliente al tacto. + +purple emotion crystal=cristal morado de emoción +You see a purple emotion crystal. It feels cold to the touch.=Ves un cristal morado de emoción. Se siente frío al tacto. + +yellow emotion crystal=cristal amarillo de emoción +You see a yellow emotion crystal. It energizes you.=Ves un cristal amarillo de emoción. Te da energía. + +pink emotion crystal=cristal rosa de emoción +You see a pink emotion crystal. It almost feels soft.=Ves un cristal rosa de emoción. Casi se siente suave. + +blue emotion crystal=cristal azul de emoción +You see a blue emotion crystal. While holding it your worries seem to disappear.=Ves un cristal azul de emoción. Mientras al sujetarlo tus preocupaciones parecen desaparecer. + + +## documents + +note=nota +You see a note.=Ves una nota. + +coupon=cupón +You see a coupon.=Ves un cupón. + +closed envelope=sobre cerrado + +opened envelope=sobre abierto + +sealed envelope=sobre sellado + +unsealed envelope=sobre sellado abierto + +assassins id=identificación de asesino +You see an assassin's ID. Perhaps this can be put to good use.=Ves una identificación de asesino. Quizá puedas hacer un buen uso de ella. + +map=mapa +You see a hand drawn map, but no matter how you look at it, nothing on it looks familiar.=Ves un mapa dibujado a mano. Pero por mucho que lo mires, no hay nada en él que parece familiar. + + +## drinks + +olive oil=aceite de oliva +You see a bottle of olive oil. Do not drink it pure, it will make you feel sick!=Ves una botella de aceite de oliva. No lo bebas puro, ¡te hará sentir mal! + +vinegar=vinagre +You see a bottle of vinegar. Do not drink it pure, it will make you feel sick!=Ves una botella de vinagre. No lo bebas puro, ¡te hará sentir mal! + +fierywater=aguardiente +You see a bottle of fierywater. Do not drink it pure, you would evaporate!=Ves una botella de aguardiente. No te la bebas pura, ¡Te evaporarías! + +mild koboldish torcibud=suave torcibud koboldiano +You see a bottle of mild koboldish torcibud. It will help you recover when you're all bruised and battered.=Ves una botella de suave torcibud koboldiano. Te ayudará a recuperarte cuando estés todo magullado. + +strong koboldish torcibud=fuerte torcibud koboldiano +You see a bottle of strong koboldish torcibud. It will help you recover when you're all bruised and battered.=Ves una botella de fuerte torcibud koboldiano. Te ayudará a recuperarte cuando estés todo magullado. + +vsop koboldish torcibud=torcibud koboldiano de estaca vieja muy especial +You see a bottle of VSOP koboldish torcibud, specially aged in oak casks wrapped by the darkness of a dungeon cellar. It will help you recover when you're all bruised and battered.=Ves un torcibud koboldiano de estaca vieja muy especial, especialmente envejecida en barricas de roble envueltas por la oscuridad de la bodega de una mazmorra. Te ayudará a recuperarte cuando estés todo magullado. + +apple juice=zumo de manzana +You see apple juice. On a hot summer day, it would taste great.=Ves zumo de manzana. En un día de verano caluroso, sabría estupendo. + +beer=cerveza +You see a beer. On a hot summer day, it would taste great.=Ves una cerveza. En un día de verano caluroso, sabría estupendo. + +wine=vino +You see a glass of red wine. It will quench your thirst.=Ves un vaso de vino tinto. Saciará tu sed. + +water=agua +You see a bottle of fresh spring water. It's really tasty and fresh.=Ves una botella de agua fresca de manantial. Es realmente sabrosa y fresca. + +pina colada=piña colada +You see a pina colada. It looks delicious but tastes dangerously strong!=Ves una piña colada. ¡Parece deliciosa pero sabe peligrosamente fuerte! + +tea=té +You see a lovely cup of tea.=Ves una encantadora taza de té. + +milk=leche +You see a small bottle of fresh milk.=Ves una pequeña botella de leche fresca. + +antidote=antídoto +You see an antidote, to cure you of poison and protect you for a while.=Ves un antídoto, para curarte de venenos y protegerte durante un rato. + +greater antidote=antidoto mayor +You see a greater antidote, to cure you of poison and protect you for a long while.=Ves un antidoto mayor, para curarte de venenos y protegerte durante un largo rato. + +mana=maná +You see an bottle of mana, to increase your mana rapidly.=Ves una botella de maná, para rellenar tu maná rápidamente. + +poison=veneno +You see a bottle of poison. Don't drink it!=Ves una botella de veneno ¡No te la tomes! + +greater poison=veneno mayor +You see a greater poison. Don't drink it!=Ves un veneno mayor ¡No te lo tomes! + +deadly poison=veneno mortal +You see a deadly poison. Don't drink it!=Ves un veneno mortal ¡No te lo tomes! + +disease poison=veneno de enfermar +You see a disease poison. Be careful, it's wicked.=Ves un veneno de enfermar. Ten cuidado, es malvado. + +mega poison=mega veneno +You see a mega poison. This is some wicked poison.=Ves un mega veneno. Éste es un veneno malvado. + +twilight elixir=elixir del crepúsculo +You see a bottle of medicinal elixir.=Ves una botella de elixir medicinal. + +minor potion=poción menor +You see a minor potion. It's not much but may save you in a tight spot.=Ves una poción menor. No es mucho pero puede salvarte en una situación difícil. + +soup=sopa +You see a bowl of soup. Its contents fill you up.=Ves un plato de sopa. Su contenido te llena. + +fish soup=sopa de pescado +You see a bowl of fish soup. Its contents fill you up.=Ves un plato de sopa de pescado. Su contenido te llena. + +potion=poción +You see a potion. Take it to battle and you may survive longer.=Ves una poción. Llevatela a la batalla y puedes sobrevivir por más tiempo. + +greater potion=poción mayor +You see a greater potion, invaluable in battle.=Ves una poción mayor, inestimable en la batalla. + +mega potion=mega poción +You see a mega potion. It strengthens greatly.=Ves una mega poción. Te fortalece en gran medida. + +love potion=poción de amor +You see a love potion. It looks powerful so take care to use it carefully!=Ves una poción de amor ¡Parece potente así que ten cuidado de usarla con precaución! + +sedative=sedante +You see a sedative. It's effects can protect you from a shocked state.=Ves un sedante. Sus efectos pueden protegerte de un estado de shock. + +ashen holy water=agua santa cenizada +A bottle of ashen holy water.=Ves una botella de agua santa cenizada. + +cobra venom=veneno de cobra +You see a vial filled with cobra venom. Even in this small amount, it could be deadly if ingested.=Ves una ampolleta llena de veneno de cobra. Aun en esta pequeña cantidad, podría ser letal si se ingiere. + +antivenom=antiveneno +You see a vial filled with antivenom.=Ves una ampolleta llena de antiveneno. + + +## flowers + +rhosyd=rhosyd +You see a beautiful and rare rhosyd flower.=Ves una bella y rara flor de rhosyd. + +rose=rosa +You see a single red rose. It smells heavenly.=Ves una sola rosa roja. + +lilia=lilia +You see a lilia, a single precious bloom.=Ves una lilia, una sola flor preciosa. + +daisies=margaritas +You see a bunch of daisies. They are cheap and cheerful!=Ves un ramo de margaritas ¡Son baratas y alegran! + +pansy=pensamiento +You see a pansy, so colorful and beautiful.=Ves un pensamiento, tan colorido y hermoso. + +zantedeschia=zantedeschia +You see a zantedeschia, a delicate bloom with the sweetest fragrance.=Ves una zantedeschia, una flor delicada con la más dulce de las fragancias. + + +## food + +apple=manzana +You see a nice shiny apple.=Ves una bonita y brillante manzana. + +purple apple=manzana morada +You see a purple apple. It is loaded with memories=Ves una manzana morada. Está cargada de recuerdos. + +mauve apple=manzana malva +You see a mauve apple. It is loaded with memories=Ves una manzana malva. Está cargada de recuerdos. + +black apple=manzana negra +You see a black apple. It must be a gift from lil Johnnnny. He's the guy with black items.=Ves una manzana negra. Debe de ser un regalo del pequeño Johnnny + +lemon=limón +You see a lemon. Could be an ingredient for the right chef...=Ves un limón. Podría ser un ingrediente para el chef adecuado. + +banana=banana +You see a banana. Be careful of monkeys when you have them in your bag!=Ves una banana. ¡Ten cuidado con los monos cuando las llevas en tu bolso! + +cherry=cereza +You see a cherry. Its tangy taste revives you a little.=Ves una cereza. Su sabor amargo te revive un poco. + +coconut=coco +You see a coconut. Luckily it's already cracked and you can drink from it.=Ves un coco. Afortunadamente ya está abierto y puedes beber de él. + +corn=maíz +You see an ear of corn. It could be eaten raw, grilled or turned into flour... Your choice.=Ves una oreja de maíz. Se puede comer cruda, asada o convertirla en harina... Tú decides. + +grapes= +You see a bunch of grapes. They look quite juicy and tasty.= + +olive= +You see a small ripe olive. It can be eaten or used to create olive oil.= + +pear= +You see a pear. Its lovely smell convinces you of taking it.= + +pineapple=piña +You see a tropical pineapple.=Ves una piña tropical. + +pomegranate= +You see a pomegranate. If you eat it, you will be in great shape soon.= + +watermelon= +You see a watermelon. It refreshes your throat during hot summer days.= + +artichoke=alcachofa +You see an artichoke. It can be an ingredient, or you can eat it.=Ves una alcachofa. Puede ser un ingrediente, o te la puedes comer. + +broccoli=brocoli +You see a bunch of broccoli.=Ves un manojo de brocoli. + +carrot=zanahoria +You see a carrot. It can be an ingredient, or you can eat it.=Ves una zanahoria. Puede ser un ingrediente, o te la puedes comer. + +cauliflower=coliflor +You see a fresh head of cauliflower.=Ves la cabeza de una fresca coliflor. + +zucchini=calabacín +You see a zucchini. It can be an ingredient, or you can eat it.=Ves un calabacín. Puede ser un ingrediente, o te lo puedes comer. + +collard=col +You see a collard. It's rare and strangely magical.=Ves una col. Es rara y extrañamente mágica. + +garlic=ajo +You see some fresh garlic.=Ves un poco de ajo fresco. + +leek=puerro +You see a fresh leek.=Ves un puerro fresco. + +onion=cebolla +You see an onion.=Ves una cebolla. + +lettuce=lechuga + +spinach=espinacas +You see spinach. It is enriched with vitamins.=Ves espinacas. Está enriquecida con vitaminas. + +tomato=tomate +You see a tomato, put it in a salad or eat it.=Ves un tomate, ponlo en la ensalada o cometelo. + +egg=huevo +You see an egg.=Ves un huevo. + +button mushroom=champiñón común +You see a button mushroom. It can be an ingredient, or you can eat it.=Ves un champiñón común. Puede ser un ingrediente, o te lo puedes comer. + +porcini=seta calabaza +You see a porcini. It can be an ingredient, or you can eat it.=Ves una seta calabaza. Puede ser un ingrediente, o te la puedes comer. + +toadstool=seta venenosa +You see a toadstool. Beware!=Ves una seta venenosa ¡Ten cuidado! + +char=salvelinus +You see a char, with a fleshy pink belly.=Ves un salvelinus, con un vientre carnoso de color rosa. + +cod=bacalao +You see a cod.=Ves un bacalao. + +smoked cod=bacalao ahumado +You see a smoked cod.=Ves un bacalao ahumado. + +mackerel=caballa +You see a mackerel.=Ves una caballa. + +perch=perca +You see a perch.=Ves una perca. + +roach=rutilo +You see a roach.=Ves un rutilo + +clownfish=pez payaso +You see a striped clownfish.=Ves un pez payaso a rayas. + +red lionfish=pterois rojo +You see a venomous coral reef red lionfish.=Ves un pterois rojo venenoso de los arrecifes de coral + +surgeonfish=pez cirujano +You see a bright blue surgeonfish.=Ves un pez cirujano de color azul brillante. + +trout=trucha +You see a trout.=Ves una trucha. + +smoked trout=trucha ahumado +You see a smoked trout.=Ves una trucha ahumada. + +bread=pan +You see a loaf of bread. It is freshly baked and ready to eat.=Ves una barra de pan. Está recién horneada y lista para comer. + +pie=pastel +You see a meat and vegetable pie.=Ves un pastel de carne y verduras. + +apple pie=tarta de manzana +You see a delicious apple pie, made with only genuine ingredients.=Ves una deliciosa tarta de manzana, elaborada sólo con los ingredientes genuinos. + +cherry pie=tarta de cereza +You see a delicious cherry pie, made with only genuine ingredients.=Ves una tarta de cereza deliciosa, elaborada sólo con los ingredientes genuinos. + +fish pie=empanada de pescado +You see a fish and leek pie. It's not too stodgy so you can eat it faster than a meat pie.=Ves una empanada de pescado y puerros. No es demasiado pesada así que te la puedes comer más rápido que un pastel de carne. + +crepes suzette=crepe suzette +You see a crepes suzette topped with hot chocolate sauce.=Ves una crepe suzette cubierta con salsa de chocolate caliente. + +sandwich=sandwich +You see a ham and cheese sandwich.=Ves un sandwich de jamón y queso. + +smoked ham=jamón ahumado +You see a smoked ham.=Ves un jamón ahumado. + +sugar=azúcar +You see a sack of raw sugar.=Ves un saco de azúcar no refinado. + +butter=mantequilla +You see some butter. It is an ingredient or can be eaten if you like.=Ves un poco de mantequilla. Es un ingrediente o se puede comer si quieres. + +honey=miel +You see a jar of sticky sweet honey. It's the clear runny kind.=Ves una jarra de miel dulce y pegajosa. Es de tipo fluído y claro. + +icecream=helado +You see a wafer cone with vanilla ice cream and chocolate flakes.=Ves un cucurucho de helado de vainilla y virutas de chocolate. + +licorice=regaliz +You see some delicious licorice. Your mouth waters.=Ves un poco de delicioso regaliz. Se te hace la boca agua. + +hotdog=perrito caliente +You see what looks like a tasty hotdog on a bun.=Ves lo que parece ser un sabroso perrito caliente en un bollo. + +cheeseydog=perrito caliente con queso +You see what looks like a tasty cheeseydog on a bun.=Ves lo que parece ser un sabroso perrito caliente con queso en un bollo. + +cheese sausage=salchicha de queso +You see what looks like a tasty cheese sausage.=Ves lo que parece ser una sabrosa salchicha de queso. + +sausage=salchicha +You see what looks like a tasty sausage.=Ves lo que parece ser una sabrosa salchicha. + +tuna sandwich=sandwich de atún +You see what looks like a tuna sandwich.=Ves lo que parece ser un sandwich de atún. + +chocolate shake=batido de chocolate +You see a scrumptious chocolate shake. Absolutely NO calories.=Ves un batido de chocolate para chuparse los dedos. Absolutamente SIN calorías. + +vanilla shake=batido de vainilla +You see a yummy vanilla shake. Absolutely NO calories.=Ves un sabroso batido de vainilla. Absolutamente SIN calorías. + +potato=papa +You see a potato.=Ves una papa. + +habanero pepper= +You see a habanero pepper. Could be a very HOT ingredient for a decent meal...= + +pinto beans= +You see a bunch of pinto beans. An ingredient a chef will surely appreciate...= + +cheese=queso +You see some cheese. Eat it and see what happens.=Ves un poco de queso. Cometelo y verás lo que pasa. + +chicken=pollo +You see a tasty leg of chicken.=Ves un sabroso muslo de pollo. + +chocolate bar=barrita de chocolate +You see an absolutely yummy chocolate bar.=Ves una barrita de chocolate absolutamente deliciosa. + +candycane= +You see a delicious candy cane.= + +greenmint= +You see a green mint.= + +gumdrops= +You see a mix of gum drops.= + +lemondrop= +You see a sweet and tangy lemon drop.= + +lollipop= +You see a lollipop.= + +peppermint= +You see a sweet, little peppermint.= + +taffy= +You see some ooey gooey taffy.= + +fairy cake=fairy cake +You see a luscious fairy cake with icing and sprinkles on top.=Ves un delicioso fairy cake con cobertura y fideos de chocolate por encima. + +ham=jamón +You see a juicy ham.=Ves un jugoso jamón. + +meat=carne +You see a piece of meat.=Ves un pedazo de carne. + +smoked meat=carne ahumado +You see a piece of smoked meat.=Ves un trozo de carne ahumada. + +easter egg=huevo de pascua +You see a lovely chocolate easter egg. It's huge!=Ves un encantador huevo de pascua ¡Es enorme! + +small easter egg=huevo de pascua pequeño +You see a small chocolate easter egg. Yummy!=Ves un pequeño huevo de pascua de chocolate ¡Delicioso! + +grilled steak=filete a la parrilla +You see a fresh grilled steak. It smells awesome and is really juicy.=Ves un filete a la parrilla recién hecho. Huele fenomenal y está realmente jugoso. + +pizza=pizza +You see a tasty pizza, and hope they put extra cheese on it.=Ves una sabrosa pizza, y esperas que le hayan puesto extra de queso por encima. + + +## furniture + +picture in wooden frame= +You see a picture in a wooden frame= + + +## helmets + +leather helmet=casco de cuero +You see a leather helmet, it seems more like a hat than a defensive item.=Ves un casco de cuero, parece más un sombrero que un elemento defensivo. + +trophy helmet=casco trofeo +You see a Trophy Helmet. Those who were brave and finished the death match, win this helmet.= + +robins hat=sombrero de robin +You see a robins hat. It's cute but that's about it.=Ves un sombrero de robin. Es gracioso pero eso es todo. + +studded helmet=casco tachonado +You see a studded helmet, it should protect your head against cuts but perhaps not hard blows.=Ves un casco tachonado, debería de protegerte la cabeza de cortes pero quizá no de golpes fuertes. + +elvish hat= +You see an elvish hat. The elf craftmanship has made the leafy material stronger than one would think.= + +chain helmet=casco de cota de malla +You see a solid chain helmet, it will be excellent protection for your head.=Ves un sólido casco de cota de malla, será una excelente protección para la cabeza. + +viking helmet=casco de vikingo +You see a viking helmet. The previous owner wore it to many battles.=Ves un casco de vikingo. Su anterior dueño lo llevó consigo a muchas batallas. + +aventail=almófar +You see an aventail. It's a clever design.=Ves un almófar. Es un diseño ingenioso. + +blue helmet=casco azul +You see a brand new blue helmet.=Ves un casco azul nuevecito. + +red helmet=casco rojo +You see a red helmet, it reminds you of a jester's hat but you can be sure it has better protection than that.=Ves un casco rojo, te recuerda al sombrero de un bufón pero puedes estar seguro de que te proporcionará una protección mejor que esa. + +golden helmet=casco de oro +You see a heavy golden helmet. The thick metal should protect you against almost anything.=Ves un pesado casco de oro. El grueso metal debería de protegerte contra casi todo. + +horned golden helmet=casco de oro con cuernos +You see a horned golden helmet.=Ves un casco de oro con cuernos. + +shadow helmet=casco de sombra +You see a heavy shadow helmet. Instead of shining, it darkly gleams.=Ves un pesado casco de sombra. En lugar de brillar, emite destellos oscuros. + +xeno helmet= +You see a xeno helmet.= + +chaos helmet=casco del caos +You see a chaos helmet.=Ves un casco del caos. + +mainio helmet=casco de mainio +You see a strange mainio helmet. You can barely see when wearing it, but it protects your head very well.=Ves un extraño casco de mainio. Apenas puedes ver cuando lo llevas puesto, pero te protege muy bien la cabeza. + +magic chain helmet=casco de cota de malla mágico +You see a magic chain helmet.=Ves un casco de cota de malla mágico. + +liberty helmet=casco de la libertad +You see the liberty helmet. It's light and strong, you feel free wearing it.=Ves el casco de la libertad. Es ligero y resistente, te sientes libre al llevarlo puesto. + +black helmet=casco negro +You see a black helmet.=Ves un casco negro + +mithril helmet=casco de mithril +You see a mithril helmet.=Ves un casco de mithril. + +royal helmet= +You see a royal helmet.= + + +## herbs + +arandula=arandula +You see an arandula. It has medicinal properties, if the right person uses it.=Ves una arandula. Tiene propiedades medicinales, si es usada por la persona adecuada. + +kokuda=kokuda +You see a kokuda. It has medicinal properties, if the right person uses it.=Ves una kokuda. Tiene propiedades medicinales, si es usada por la persona adecuada. + +kekik=kekik +You see a kekik. It is a herb which can have healing properties.=Ves una kekik. Es una hierba que puede tener propiedades curativas. + +mandragora=mandrágora +You see a root of mandragora. It has medicinal properties, if the right person uses it.=Ves una raíz de mandrágora. Tiene propiedades medicinales, si es usada por la persona adecuada. + +sclaria=sclaria +You see the herb sclaria. The smell is calming.=Ves la hierba sclaria. Su olor es calmante. + +reindeer moss= +You see reindeer moss, something that grows only around Semos. It is quite a delicacy to reindeer, but extremely bitter to humans.= + + +## jewellery + +emerald=esmeralda +You see an emerald, a precious gemstone.=Ves una esmeralda, una gema preciosa. + +sapphire=zafiro +You see a sapphire, a precious gemstone.=Ves un zafiro, una gema preciosa. + +carbuncle=carbúnculo +You see a carbuncle, a precious gemstone related to the garnet.=Ves un carbúnculo, una gema preciosa relacionada con el granate. + +obsidian=obsidiana +You see a piece of obsidian, it's known as volcanic glass because it forms when lava cools rapidly. When used as a gemstone it may look jet black or silvery grey, depending on how it is cut.=Ves una pieza de obsidiana, es conocida como vidrio volcánico porque se forma cuando la lava se enfría rápidamente. Cuando se usa como gema puede parecer negro azabache o gris plateado, dependiendo de cómo se corte. + +amethyst= +You see an amethyst, a precious gemstone.= + +diamond=diamante +You see a diamond, not just a beautiful gem but also the hardest naturally occurring material.=Ves un diamante, no es sólo una hermosa gema sino también el material más duro de origen natural. + +black pearl=perla negra +You see a black pearl, so dark and shiny.=Ves una perla negra, tan oscura y brillante. + + +## keys + +dungeon silver key=llave de plata de la mazmorra +You see a silver dungeon key. Use it only if you are brave.=Ves una llave de plata de la mazmorra. Usala sólo si eres valiente. + +lich gold key=llave de oro del lich +You see a golden lich key. It will open the door to terrible things.=Ves una llave de oro del lick. Abrirá la puerta a cosas terribles. + +minotaur key=llave del minotauro +You see a fancy bronze key. It smells faintly of minotaur. Perhaps one dropped it?=Ves una lujosa llave de bronce. Huele ligeramente a minotauro. ¿Quizá se le cayó a uno? + +nalwor bank key=llave del banco de nalwor +You see a key to the customer room of Nalwor Bank.=Ves una llave de la sala de clientes del Banco de Nalwork. + +kotoch prison key=llave de la prisión de kotoch +You see an ancient copper key, enscribed with something in Orcish.=Ves una antigua llave de cobre, grabada con algo en Orco. + +kanmararn prison key=llave de la prisión de kanmararn +You see a key which matches the locks in the Kanmararn prison.=Ves una llave que coincide con las cerraduras de la prisión de Kanmararn. + +sedah gate key=llave de la puerta de sedah +You see a key for the gate to the imperial city of Sedah.=Ves una llave que abre la puerta que lleva a la ciudad imperial de Sedah. + +pet sanctuary key=llave del santuario de mascotas +You see a key for the pet sanctuary in Ados.=Ves una llave que abre el santuario de mascotas en Ados. + +house key=llave de la casa +You should not see this description=No deberías de poder ver esta descripción + +gate key= +You should not see this description=No deberías de poder ver esta descripción + +master key=llave maestra + +small key=llave pequeña +You see the key for Zara's row house in Ados.=Ves la llave de la row house de Zara en Ados. + + +## legs + +black legs=quijotes negros +You see a pair of black legs.=Ves un par de quijotes negros. + +blue legs=quijotes azules +You see a pair of shiny new blue legs.=Ves un par de quijotes azules nuevos y brillantes. + +chain legs=quijotes de cota de malla +You see a pair of well made chain legs. They should protect you against any attacks aimed low.=Ves un par de quijotes de cota de malla bien elaborados. Deberían de protegerte frente a cualquier ataque bajo. + +chaos legs=quijotes del caos +You see a pair of chaos legs. The strange colors confuse you.=Ves un par de quijotes del caos. Sus extraños colores te confunden. + +dwarvish legs= +You see a pair of dwarvish legs, designed by master craftsmen.= + +elvish legs=quijotes élficos +You see a pair of elvish legs.=Ves un par de quijotes élficos. + +golden legs=quijotes de oro +You see a pair of shining, strong golden legs.=Ves un par de quijotes de oro, resistentes y relucientes. + +jewelled legs=quijotes enjoyados +You see a beautiful pair of jewelled legs. A large red gem glints at you and they're encrusted all over with sapphires.=Ves un hermoso par de quijotes enjoyados. Una gema roja grande emite reflejos hacia ti y tienen incrustaciones de zafiro por todas partes. + +leather legs=quijotes de cuero +You see a pair of leather legs. They look new but they're poor quality.=Ves un par de quijotes de cuero. Parecen nuevos pero son de mala calidad. + +magic plate legs= + +mainio legs=quijotes de mainio +You see a pair of odd looking mainio legs. They are somewhat startling but they give you good protection.=Ves un par de quijotes de mainio de extraño aspecto. Son de algún modo sorprendentes pero te proporcionan una buena proteccion. + +mithril legs=quijotes de mithril +You see a pair of mithril legs. They will serve you well.=Ves un par de quijotes de mithril. Te servirán de mucho. + +red legs=quijotes rojos +A pair of red legs are in your grasp.=Un par de quijotes rojos están a tu alcance. + +royal legs= +You see a pair of royal legs.= + +shadow legs=quijotes de sombra +You see a pair of shadow legs. The weight of them is incredible.=Ves un par de quijotes de sombra. Su peso es increíble. + +stone legs= +You see a pair of stone legs, heavy and durable.= + +studded legs=quijotes tachonados +You see a pair of blood stained studded legs. You're not sure if the blood came from their last owner... or his enemies.=Ves un par de quijotes tachonados manchados de sangre. No estás seguro de si la sangre es de su último dueño... o de sus enemigos. + +xeno legs=quijotes de xeno +You see a pair of xeno legs. They're very strong and look brand new.=Ves un par de quijotes de xeno. Son muy resistentes y parecen nuevecitos. + + +## miscs + +decent meal= +You see a decent meal for Groongo Rahnnt. Deliver it to him quickly!= + +bulb=bulbo +You see a bulb. It can be planted in fertile ground where it will thrive.=Ves un bulbo. Puede ser plantado en terreno fértil donde se prosperará. + +seed=semilla +You see a seed. It can be planted in fertile ground where it will thrive.=Ves un semilla. Puede ser plantado en terreno fértil donde se prosperará. + +giant heart=corazón gigante +You see a giant heart. This proves that a giant was defeated, and has magical properties that you are not sure of.=Ves un corazón gigante. Ésto demuestra que un gigante fue derrotado, y tiene propiedades mágicas de las que no estás seguro. + +dice=dados +You see a set of dice. May lady luck be with you.=Ves un grupo de dados. Que la suerte te acompañe. + +cards=cartas +You see a deck of cards. Shuffle them well, to get a winning hand.=Ves una baraja de cartas. Barajealas bien, para ganar la mano. + +teddy=peluche +You see a teddy bear. It's soft and cuddly, and children love them.=Ves un oso de peluche. Es suave y tierno, y a los niños les encanta. + +rodent trap= +You see a rodent trap.= + +vampirette entrails=entrañas de vampiresa +You see some vampirette entrails. These guts prove that a vampirette was slain.=Ves unas entrañas de vampiresa. Estas vísceras demuestran que una vampiresa fue asesinada. + +bat entrails=entrañas de murciégalo +You see some bat entrails, full of the blood of a living creature.=Ves unas entrañas de murciégalo, llenas de sangre de algún ser vivo. + +suntan cream=crema bronceadora +You see a tube of suntan cream and notice that the outside is slippery. A nice scent of coconut mixed with bannana wafts into the air from it.= + +marbles=canicas +You see some marbles. Boys will be boys.=Ves unas canicas. Son cosas de niños. + +canned tuna=atún en lata +You see what appears to be canned tuna. Too bad you don't have a can opener.=Ves lo que parece ser atún enlatado. Lástima que no tengas un abrelatas. + +snowglobe=esfera de nieve +You see a snowglobe. It's so festive, it reminds you of Christmas!=Ves una esfera de nieve. ¡Es tan festiva, te recuerda a la Navidad! + +bobbin=canilla +You see a bobbin for a sewing machine.=Ves una canilla para una máquina de coser. + +venom gland= +You see the venom gland of a giant king cobra. It is filled with deadly toxin.= + +pelt= +You see a pelt of animal skin. It's used to make clothes and leather armor.= + +leather thread= +You see a strand of sturdy leather thread.= + +unicorn horn= +You see a spiral unicorn horn. It is highly valued by traders and used in the creation of magical items.= + + +## missiles + +snowball=bola de nieve +You see a snowball. You can throw it at your friends or put it in your bag to keep your fish fresh. But be careful, there could be little stones in it.=Ves una bola de nieve. Puedes tirarselas a tus amigos o ponerlas en tu bolsa para conservar el pescado fresco. Pero ten cuidado, podría haber piedrecitas dentro de ella. + +vomit=vómito +You see a pile of vomit. Gross!=Ves un montón de vómito ¡Qué desagradable! + +shuriken=shuriken +You see a shuriken. You'll need to be close before you can use it as a missile. Stand square on, not diagonally, for the best results.=Ves un shuriken. Necesitarás estar cerca antes de que lo puedas usar como proyectil. Permanece recto, no inclinado, para obtener los mejores resultados. + +fire shuriken=shuriken de fuego +You see a fire shuriken. Throwing this flaming missile unleashes fiendish power.=Ves un shuriken de fuego. El lanzamiento de este proyectil en llamas desata un poder diabólico. + +wooden spear=lanza de madera +You see a wooden spear. You can attack at a distance with this primitive weapon. Stand square on, not diagonally, for the best results.=Ves una lanza de madera. Puedes atacar a distancia con este arma primitiva. Permanece recto, no inclinado, para obtener los mejores resultados. + + +## money + +money=dinero + + +## ranged + +wooden bow=arco de madera +You see a wooden bow. With this in one hand, and wooden arrows in the other, you may be able to attack from a distance.=Ves un arco de madera. Con esto en una mano, y flechas de madera en la otra, puedes ser capaz de atacar a distancia. + +longbow=arco largo +You see a longbow. Equipped with this, and a supply of arrows, you will have an effective ranged attack.=Ves un arco largo. Equipado con ésto, y con un surtido de flechas, conseguirás un efectivo ataque a distancia. + +composite bow=arco compuesto +You see a composite bow. It's better strung than most bows, for excellent ranged attack.=Ves un arco compuesto. Está mejor tensado que la mayoría de arcos, para proporcionar un excelente ataque a distancia. + +crossbow=ballesta +You see a crossbow. The attack is high but it has a very short range. What do you prefer?=Ves una ballesta. Su ataque es elevado pero cubre una distancia muy corta. ¿Qué prefieres? + +hunter crossbow=ballesta de cazador +You see a hunter crossbow. You must decide if the high attack is worth the short range it has.=Ves una ballesta de cazador. Debes decidir si su elevado ataque compensa la corta distancia que cubre. + +mithril bow=arco de mithril +You see a light and strong mithril bow. Its impeccable workmanship affords incredible accuracy.=Ves un arco de mithril ligero y resistente. Su impecable elaboración permite una precisión increíble. + + +## relics + +skull ring=anillo de calavera +You see a skull ring.=Ves un anillo de calavera. + +amulet=amuleto + + +## resources + +wood=madera +You see some wood, fallen from a nearby tree.=Ves un poco de madera, caída de algún árbol cercano. + +sugar cane=caña de azúcar +You see a sugar cane. You can grind it for raw sugar.=Ves una caña de azúcar. Puedes molerla para obtener azúcar no refinado. + +grain=trigo +You see a sheaf of grain.=Ves una gavilla de trigo. + +flour=harina +You see a sack of flour. It can be used in recipes for pizzas and sandwiches.= + +iron ore=mineral de hierro + +coal=carbón +You see a black and shiny piece of stone coal. It stayed in the ground for million of years.=Ves un pedazo de piedra de carbón negro y brillante. Permaneció enterrado durante millones de años. + +wool=lana +You see wool. It was recently shorn.=Ves lana. Fue esquilada recientemente. + +yarn=hilo de lana +You see a ball of yarn. It was spun with care.=Ves un ovillo de lana. Fue hilado con sumo cuidado. + +cloth=tela +You see a roll of cloth. It is of first-class quality.=Ves un rollo de tela. Es de primera calidad. + +gold nugget=pepita de oro +You see a gold nugget. Its rich golden color shines, and it may be useful.=Ves una pepita de oro. Su vivo color dorado brilla,y puede resultar útil. + +mithril nugget=pepita de mithril +You see a mithril nugget. Its pale silvery color shines, and it may be useful.=Ves una pepita de mithril. Su pálido color plateado brilla, y puede resultar útil. + +iron bar= + +gold bar=barra de oro +You see a gold bar. It will make beautiful jewelry, or fetch a fine price.=Ves una barra de oro. Servirá para hacer hermosas joyas, o se pagará un buen precio por ella. + +mithril bar=barra de mithril +You see a mithril bar. It can be made into excellent armor, or fetch a fine price.=Ves una barra de mithril. Se puede elaborar una armadura excelente con ella, o se puede pagar un buen precio por ella. + +golden egg=huevo de oro +You see a valuable golden egg.=Ves un valioso huevo de oro. + +spotted egg=huevo con manchas +You see a spotted egg. They are said to be delicious.=Ves un huevo con manchas. Se dice que son deliciosos. + +silk gland=glándula de seda +You see a silk gland from a giant spider. Its contents are extremely valuable.=Ves una glándula de seda de una araña gigante. Su contenido es extremadamente valioso. + +silk thread=hilo de seda +You see a spool of silk thread, spun from the glands of a giant spider.=Ves un carrete de hilo de seda, hilado de las glándulas de una araña gigante. + +oil=aceite +You see a small can of oil.=Ves una pequeña lata de aceite. + +mithril thread=hilo de mithril +You see a spool of mithril thread, made from silk fused with mithril.=Ves un carrete de hilo de mithril, elaborado a partir de seda fundida con mithril. + +mithril fabric=tela de mithril +You see a couple yards of mithril fabric. It's very sparkly!=Ves un par de metros de tela de mithril ¡Emite muchos destellos! + +horse hair=pelo de caballo +You see some horse hairs.=Ves un poco de pelos de caballo. + +beeswax=cera de abeja +You see a piece of beeswax.=Ves un pedazo de cera de abeja. + +candle=vela +You see a candle in a candle stick.=Ves una vela en un candelero. + +charcoal= +You see charcoal, a component of carbon steel.= + + +## rings + +medicinal ring= +You see a medicinal ring. It may protect you from being poisoned when worn on the finger.= + +antivenom ring= +You see an antivenom ring. It has a good chance to protect you from poisoning when worn on the finger.= + +placid ring= +You see a placid ring. It may reduce the effects of being shocked when worn on the finger.= + +insulated ring= +You see an insulated ring that may protect from being shocked. It must be worn on the finger.= + +wedding ring=anillo de boda +You see a golden wedding ring, a symbol of love.= + +engagement ring=anillo de compromiso +You see a diamond engagement ring, a symbol of love (or a symbol of wealth?)=Ves un anillo de compromiso de diamante, un símbolo de amor (¿o un símbolo de riqueza?) + +emerald ring=anillo de esmeralda +You see an emerald ring, known as ring of life. Wear it, and you risk less from death.=Ves un anillo de esmeralda, conocido como el anillo de la vida. Llevalo, y arriesgarás menos al morir. + +turtle shell ring= +You see a turtle shell ring.= + +imperial ring= +You see an imperial ring. It is imbued with the strength of a king.= + +enhanced imperial ring= +You see an enhanced imperial ring. It eminates the auras of strength and defense.= + +ring of clarity= +You see a ring of clarity. You feel more aware while wearing it.= + + +## scrolls + +summon scroll=pergamino de invocación +You see a summon scroll. It summons a creature to aid in your adventures.= + +summon pet scroll= + +blank pet scroll= + +home scroll=pergamino del hogar +You see a home scroll, it will take you back to semos city.=Ves un pergamino del hogar, te llevará de regreso a la ciudad de semos. + +nalwor city scroll=pergamino de la ciudad de nalwor +You see a nalwor city scroll, it will take you back to nalwor city.=Ves un pergamino de la ciudad de nalwor, te llevará de regreso a la ciudad de nalwor. + +ados city scroll=pergamino de la ciudad de ados +You see an ados city scroll, it will take you back to ados city.=Ves un pergamino de la ciudad de ados, te llevará de regreso a la ciudad de ados. + +fado city scroll=pergamino de la ciudad de fado +You see a fado city scroll, it will take you back to fado city.=Ves un pergamino de la ciudad de fado, te llevará de regreso a la ciudad de fado. + +kirdneh city scroll=pergamino de la ciudad de kirdneh +You see a kirdneh city scroll, it will take you back to kirdneh city.=Ves un pergamino de la ciudad de kirdneh, te llevará de regreso a la ciudad de kirdneh. + +kalavan city scroll=pergamino de la ciudad de kalavan +You see a kalavan city scroll, it will take you back to kalavan city.=Ves un pergamino de la ciudad de kalavan, te llevará de regreso a la ciudad de kalavan. + +deniran city scroll= +You see a deniran city scroll, it will take you back to deniran city.= + +marked scroll=pergamino marcado + +invitation scroll=pergamino de invitación + +empty scroll=pergamino en blanco +You see an empty scroll. If used correctly it will return you to where you marked it at.=Ves un pergamino en blanco. Usado correctamente te llevará de regreso a donde lo marcaste. + +rainbow beans=haba del arco iris +You see a small pile of rainbow beans. One says something odd.=Ves un pequeño montón de habas del arco iris. Una dice algo raro. + +balloon=globo +You see a bright red balloon. You can use it to float high up into the clouds.=Ves un globo de color rojo brillante. Puedes usarlo para flotar en lo alto de las nubes. + +twilight moss=musgo del crepúsculo +You see a strange looking piece of moss. It looks like it could have hallucinogenic properties.=Ves un pedazo de musgo de aspecto extraño. Parece que podría tener propiedades alucinógenas. + + +## shields + +black shield=escudo negro +You see a black shield. Its darkness will surely protect you.=Ves un escudo negro. Su oscuridad seguro que te protegerá. + +blue shield=escudo azul +You see a blue shield. It feels light in your hand but is strong enough for excellent protection.=Ves un escudo azul. Notas en tu mano que es ligero pero es lo bastante resistente como para proporcionar una excelente protección. + +buckler=broquel +You see a small buckler. It may protect you against swords and maces but against stronger weapons you do not trust it.=Ves un pequeño broquel. Puede protegerte frente a espadas y mazas pero frente a armas más potentes no te confíes. + +chaos shield=escudo del caos +You see a chaos shield. Its charming appearance will fool your enemies.=Ves un escudo del caos. Su encantadora apariencia engañará a tus enemigos. + +crown shield=escudo de la corona +You see a mighty crown shield. It is in perfect condition.=Ves un poderoso escudo de la corona. Esta en perfectas condiciones. + +elvish shield=escudo élfico +You see an elvish shield. It looks like one big tree leaf!=Ves un escudo élfico ¡Se parece a una gran hoja de árbol! + +enhanced lion shield=escudo de león mejorado +An enhanced lion shield glints at you. It will defend you well.=Un escudo de león mejorado emite destellos hacia ti. Te defenderá bien. + +golden shield=escudo de oro +You see a golden shield, could it be the ultimate in defense?=Ves un escudo de oro, ¿podría ser lo último en defensa? + +green dragon shield=escudo de dragón verde +You see a green dragon shield, scaly and strong.=Ves un escudo de dragón verde, escamoso y resistente. + +lion shield=escudo de león +You see a valuable lion shield. It is heavy enough to defend you against strong attacks.=Ves un valioso escudo de león. Es lo bastante pesado como para defenderte frente a ataques potentes. + +magic plate shield=escudo mágico de placas de acero +You see a magic plate shield. It has a magical quality surrounding it.=Ves un escudo mágico de placas de acero. Tiene una cualidad mágica que lo envuelve. + +mainio shield=escudo de mainio +You see a strange looking mainio shield. You have no idea what it's made of, but it seems to protect you well by distracting your enemies.=Ves un escudo de mainio de extraña apariencia. No tienes ni idea de lo que está hecho, pero parece que te protege bien al distraer a tus enemigos. + +mithril shield=escudo de mithril +You see a mithril shield. You will most surely be protected.=Ves un escudo de mithril. Casi con toda seguridad estarás protegido. + +plate shield=escudo de placas de acero +You see a solid plate shield. It is in good condition, and might be worth a little.=Ves un sólido escudo de placas de acero. Está en buenas condiciones, y podría valer bastante. + +red shield=escudo rojo +You see a red shield. It is a stunning shade of scarlet.=Ves un escudo rojo. Es de un imponente tono escarlata. + +royal shield= +You see a royal shield. It has a magical quality surrounding it.= + +shadow shield=escudo de sombra +You see a shadow shield. Rather than reflecting light, it seems to absorb it.=Ves un escudo de sombra. En lugar de reflejar la luz, parece absorberla. + +skull shield=escudo de calavera +The face of a skull scowls at you from this fearsome skull shield.=La cara de una calavera te frunce el ceño desde este temible escudo de calavera + +studded shield=escudo tachonado +You see a studded shield. It's a little dented but seems robust enough.=Ves un escudo tachonado. Está un poco abollado peroparece bastante robusto. + +unicorn shield=escudo del unicornio +You see a unicorn shield. It's beautifully crafted and will give you excellent protection.=Ves un escudo del unicornio. Está bellamente elaborado y te proporcionará una protección excelente. + +wooden shield=escudo de madera +You see a wooden shield. It is made of cheap wood and heavily used. However, it still seems to offer you some kind of basic protection.=Ves un escudo de madera. Está elaborado a partir de madera barata y ha sido muy utilizado. Sin embargo, todavía parece proporcionar algún tipo de protección básica. + +xeno shield=escudo de xeno +You see a lightweight but strong xeno shield, in the colors of the creature it was looted from.=Ves un escudo de xeno ligero pero resistente, de los mismos tonos que la criatura a la que se le saqueó. + +ice shield= + + +## special + +lucky charm=amuleto de la suerte +You see a lucky charm. Some believe that it helps their karma in some way, but others think that is superstition.=Ves un amuleto de la suerte. Algunos creen que ayuda a su karma de algún modo, pero otros piensan que es una superstición. + +mithril clasp=cierre de mithril +You see a clasp made from mithril, it would provide the fastening for a cloak.=Ves un cierre elaborado a partir de mithril, te proporcionaría la fijación para una capa. + +mythical egg=huevo mítico +You see a mythical egg, but you do not know what it is for.=Ves un huevo mítico, pero no sabes para qué es. + +magical eggshells=cáscaras de huevo mágicas +You see shards from a mythical egg. They have magical properties.=Ves fragmentos de un huevo mítico. Tienen propiedades mágicas. + +golden gm token=ficha de dj de oro +You see a golden gm token. Does it bring you pride?= + +silvery gm token=ficha de dj de plata +You see a silvery gm token. Does it bring you pride?= + +bronze gm token=ficha de dj de bronce +You see a bronze gm token. Does it bring you pride?= + +four-leaf clover= +You see a lucky four-leaf clover. What are the chances of finding one!?= + + +## swords + +assassin dagger=daga de asesino +You see an assassin dagger, easy to pull out and use when needed.=Ves una daga de asesino, fácil de sacar y de usar cuando se necesite. + +biting sword=espada mordaz +The tip of this biting sword is chiseled to a lethal point. It will serve you well.=La punta de esta espada mordaz está cincelada hasta un punto letal. + +black sword=espada negra +You see a black sword. Its darkness will surely protect you.=Ves una espada negra. Su oscuridad seguro que te protegerá. + +broadsword=espada ancha +You see a mighty broadsword. Enemies will see their death.=Ves una poderosa espada ancha. Los enemigos verán su muerte. + +buster=buster +You see a buster, with its distinctive, oversized blade.=Ves una espada buster, con su distintiva hoja sobredimensionada. + +chaos dagger=daga del caos +You see a chaos dagger, it is very lightweight and sharp. It will heal you while hurting them.=Ves una daga del caos, es muy ligera y afilada. + +chaos sword=espada del caos +You see a chaos sword. Wielding it allows to hurt your foes and to heal you at the same time.=Ves una espada del caos. Empuñarla te permite hacer daño a tus enemigos y curarte al mismo tiempo. + +claymore=claymore +This claymore is very hard to lift as it is so heavy. It will be a good weapon if you can swing it.=Esta claymore es muy difícil de levantar ya que es muy pesada. + +dagger=daga +You see a dagger, it is little more than decorative but you can jab pretty fast with it.=Ves una daga, es poco más que decorativa pero puedes punzar bastante rápido con ella. + +dark dagger=daga oscura +You see a dark dagger. It's designed to be used with a fast motion.=Ves una daga oscura. Se ha diseñado para ser utilizada con un movimiento rápido. + +demon fire sword=espada de fuego de demonio +The demon fire sword, a horrible sword taken from hell's deepest dungeons.=La espada de fuego de demonio, una espada horrible sacada de las más profundas mazmorras del infierno. + +demon sword=espada de demonio +Demon skeletons return from hell with this demon sword.=Los esqueletos demonio regresan del infierno con esta espada de demonio. + +drow sword=espada drow +This is a genuine drow sword.=Esta es una genuina espada drow. + +elvish sword=espada élfica +This is an elvish sword. It is finely crafted.=Esta es una espada élfica. Está finamente elaborada. + +fire sword=espada de fuego +Flames race along this fire sword as you wield it.=Las llamas corren a lo largo de esta espada de fuego mientras la esgrimes. + +golden blade=cuchilla de oro +You see the rare and precious golden blade. It can steal life from those you stab with it.=Ves la rara y preciosa cuchilla de oro. Puede robar la vida a aquellos que apuñales con ella. + +golden orc sword=espada de oro de orco +You see a golden orc sword, with its heavy and deadly appearance it seems also valuable.=Ves una espada de oro de orco, con su pesada y mortal apariencia parece tan valiosa. + +great sword=gran espada +This rare great sword is heavy and of good craftsmanship. It can only attack incredibly slowly.=Esta rara gran espada es pesada y de buena fabricación. + +hell dagger=daga del infierno +You see a hell dagger. Is the color that of the burning fires of Hell?=Ves una daga del infierno ¿Es ése el color de las llamas del infierno? + +ice sword=espada de hielo +You see an ice sword, the blade is lethally cold to the touch, and you know it is one of the best weapons you'll find.=Ves una espada de hielo, la hoja es letalmente fría al tacto, y sabes que es una de las mejores armas que vas a encontrar. + +imperator sword=espada imperator +You see a an imperator sword, crafted from you know not what.=Ves una espada imperator, elaborada a partir de ya sabes qué. + +immortal sword=espada inmortal +This immortal sword is a gift of gods to the chosen one. It is heavy and of exquisite craftsmanship.=Esta espada inmortal es un regalo de los dioses al elegido. Es pesada y de una elaboración exquisita. + +katana=katana +You see a valuable katana, with a lethal looking blade.=Ves una valiosa katana, con una hoja de apariencia letal. + +knife=cuchillo +You see a blunt knife which looks capable of little damage.=Ves un cuchillo desafilado que parece capaz de hacer poco daño. + +l hand sword=espada de mano i +You see l hand sword. It must be used with right hand sword.=Ves una espada de mano i. Debe usarse con una espada de mano derecha. + +night dagger=daga de la noche +You see a night dagger, to be used with stealth.=Ves una daga de la noche, para ser utilizada con sigilo. + +nihonto=nihonto +You see a samurai's nihonto sword with a razor sharp blade.=Ves una espada nihonto de samurai con una hoja tan afilada como la de una cuchilla. + +obsidian knife=cuchillo de obsidiana +You see a knife whose blade is crafted to a lethal edge from the finest obsidian.=Ves un cuchillo cuya hoja se ha trabajado hasta conseguir un filo letal a partir de la obsidiana más pura. + +orc sword=espada de orco +You see an orc sword which has a heavy and deadly appearance.=Ves una espada de orco que tiene una apariencia pesada y mortal. + +orcish sword=espada orca +This orcish sword is badly done and weighs a lot.=Esta espada orca está mal hecha y pesa un montón. + +r hand sword=espada de mano d +You see r hand sword. It must be used with a left hand sword.=Ves una espada de mano d. Debe usarse con una espada de mano izquierda. + +royal dagger= +You see the rare and precious royal dagger. It can steal life from those you stab with it.= + +scimitar=cimitarra +This scimitar catches the light as you look at it, it will be a powerful weapon.=Esta cimitarra atrapa la luz mientras la miras, sera un arma poderosa. + +short sword=espada corta +You see a well kept short sword which will be easy to carry as you travel.=Ves una espada corta bien conservada que será fácil de llevar cuando viajes. + +soul dagger=daga del alma +You see a soul dagger. It's so light and sharp that you can stab at a very fast rate.=Ves una daga del alma. Es tan ligera y afilada que puedes apuñalar a un ritmo muy rápido. + +sword=espada +You see a sharp sword which might even be worth a little.=Ves una espada afilada que podría incluso valer un poco. + +vampire sword=espada de vampiro +You see the vampire sword, which will suck life from those you attack with it.=Ves la espada de vampiro, que absorberá la vida de aquellos a los que ataques con ella. + +xeno sword=espada de xeno +You wield a xeno sword. Despite the great strength it is strangely light.=Tu esgrimes una espada de xeno. A pesar de su gran potencia es extrañamente ligera. + +training sword= +You see a wooden training sword not even useful for cutting butter. But it is light and easy to swing.= + + +## tokens + +arrow game token=ficha del juego de la flecha +You see a token, that when placed in the right spot could point you in the right direction.=Ves una ficha, que cuando se coloque en el lugar correcto podría señalarte la dirección correcta. + +o board token=ficha del tablero o +You see an O token.=Ves una ficha O + +x board token=ficha del tablero x +You see an X token.=Ves una ficha X + + +## tools + +food mill=molino de alimentos +You see a food mill. It is the traditional tool to mill food.=Ves un molino de alimentos. Es la herramienta tradicional para moler alimentos. + +sugar mill=molino de azúcar +You see a sugar mill. You can obtain sugar if you grind canes with it.=Ves un molino de azúcar. Puedes obtener azúcar si mueles cañas con él. + +scroll eraser=pergamino borrador +You see a scroll eraser. Put a marked scroll in the opposite hand to clean the scroll.=Ves un pergamino borrador. Pon un pergamino marcado en la mano opuesta para limpiar el pergamino. + +pestle and mortar=maza y mortero +You see a pestle and mortar. This is the traditional tool to pound herbs, food and other substances.=Ves una maza y un mortero. Esta es la herramienta tradicional para machacar hierbas, alimentos y otras sustancias. + +gold pan=sartén del oro +You see a gold pan. It is the traditional tool to prospect for gold.=Ves una sartén del oro. Es la herramienta tradicional para buscar oro. + +fishing rod=caña de pescar +You see a fishing rod. If you find a fishing spot you can catch fish with it.=Ves una caña de pescar. Si encuentra un lugar para pescar puedes capturar peces con ella. + +lamp= +You see a lamp.= + +magical scissors=tijeras mágicas +You see a sparkling pair of magical scissors. Just think what they could cut!=Ves un par de tijeras mágicas centelleantes ¡Piensa sólo en lo que podrían cortar! + +magical needle=aguja mágica +You see a magical needle with a very sharp point.=Ves una aguja mágica con una punta muy afilada. + +pick=pico +You see a pick. It is a tool for excavating coal.=Ves un pico. Es una herramienta para la extracción de carbón. + +shovel= +You see a shovel, a tool for digging.= + +leather needle= +You see a heavy leather needle for working thick hide.= + +rotary cutter= +You see a rotary cutter. It can be used to cut up pizza. But perhaps it can be used on other things as well.= + +metal detector= +You may find buried treasures with this metal detector.= + +rope= +You see a sturdy rope.= + + +## whips + +venom whip= +You see a venom whip. Its thong has been saturated with cobra venom.= + + +# creatures names and descriptions + +## angel + +dark angel=ángel oscuro +You see a dark angel. He is an strong evil angel, and for now he is under cover of darkness.=Ves a un ángel oscuro. Es un ángel poderoso y malvado, y por ahora está al amparo de la oscuridad. + +dark archangel=arcángel oscuro +You see a dark archangel. After she fell from heaven her soul became as dark as she is now.=Ves a una arcángel oscura. Después de que cayese del cielo su alma se volvió tan oscura como ella lo es ahora. + +fallen angel=ángel caído +You see a fallen angel. When he turned away from good, he fell down from heaven.=Ves a un ángel caído. Cuando se apartó del bien, se cayó del cielo. + +baby angel=bebé angel +You see a baby angel. Will it ever get a chance to grow up?=Ves a un bebé angel ¿Tendrá alguna vez la oportunidad de hacerse mayor? + +angel=ángel +You see an angel. With his flame sword he guards the way and doesn't let you pass.=Ves a un ángel. Con su espada flamígera protege el camino y no te deja pasar. + +archangel=arcángel +You see an archangel. It is Heaven's most terrifying weapon, a fierce and absolute force of primordial creation.= + + +## animal + +crab=cangrejo +You see a little crab that comes from the beach, be careful of its pincers.=Ves un pequeño cangrejo que viene de la playa, ten cuidado con sus pinzas. + +deer=ciervo +You see a defenseless deer, do not have fear of it.=Ves un ciervo indefenso, no le tengas miedo. +You see a reindeer running freely, and wonder if he is one of Santa's special helpers.= + +bat=murciégalo +You see a bat, it will seek your blood, don't be distracted...=Ves un murciégalo, buscará tu sangre, no te distraigas... + +killer bat=murciégalo asesino +You see a killer bat which will suck your blood!=¡Ves un murciégalo asesino que te chupará la sangre! + +giant bat= + +giant killer bat= +You see a giant killer bat. As if small wasn't pesky enough, these giant ones are capable of taking down anyone who dares come before them.= + +piglet=cerdito +You see a piglet, a bit dirty.=Ves un cerdito, un poco sucio. + +boar=verraco +You see a boar, they are pretty wild!=Ves un verraco, ¡son bastante salvajes! + +bull=toro +You see a bull, run away!!!=Ves un toro, ¡sal corriendo! + +cow=vaca +You see a cow, taste her delicious milk.=Ves una vaca, prueba su deliciosa leche. + +goat=cabra +You see a goat, which is wild and lives in the mountain.=Ves una cabra, que es salvaje y vive en la montaña. + +ram=carnero +You see a ram, maybe you are able to shear it?=Ves un carnero, ¿quizá seas capaz trasquilarlo? + +horse=caballo +You see a horse, don't slap it or it will run away!=Ves un caballo, ¡no les des una palmada o saldrá corriendo! + +white horse=caballo blanco +You see a white horse, they love to stay with their brown friends.=Ves un caballo blanco, les gusta estar con sus amigos marrones. + +monkey=mono +You see a monkey, would you like to offer it a banana?=Ves un mono, ¿Te gustaría ofrecerle un plátano? + +fox=zorro +You see a fox, it becomes extinct!=Ves un zorro, ¡se esta extinguiendo! + +wolf=lobo +You see a wolf. It is wild and very hungry!=Ves un lobo ¡Es salvaje y está muy hambriento! + +big bad wolf=lobo grande y malo +You see a big bad wolf. It is really wild, hungry and really dangerous!=Ves un lobo grande y malo ¡Es verdaderamente salvaje, está hambriento y es realmente peligroso! + +lion=león +You see a lion, dangerous and hungry...=Ves un león, peligroso y hambriento... + +tiger=tigre +You see a tiger, which likes deer and human meat.=Ves un tigre, al que le gustan los ciervos y la carne humana. + +elephant=elephant +You see an elephant, a huge animal with lots of ham and meat. Are you sure that you want to attack it?=Ves un elefante, un animal enorme con un montón de jamón y de carne. + +bull elephant= +You see an aggressive bull elephant.= + +musth elephant= +You see a musth elephant. It is highly aggressive and unpredictable.= + +woolly mammoth= + +babybear=cachorro de oso +You see a babybear, isn't it sweet?=Ves un cachorro de oso, ¿a que es dulce? + +panda=panda +You see a panda which comes from an exotic land.=ves un panda que viene de una tierra exótica. + +bear=oso +You see a bear. You are lucky and found it awake.=Ves un oso. Has tenido suerte y lo has encontrado despierto. + +black bear=oso negro +You see a strong black bear which hibernates in winter.=Ves un fuerte oso negro que hiberna en invierno. + +polar bear= +You see a polar bear which lives in the icy desert. Beware! It can take you down with its powerful claws!= + + +## animal_aquatic + +kraken= +From the depths of the sea rises a Kraken!= + +neo kraken= +From the horrible darkness of the deep sea rises a giant NEO kraken!= + +shark= +A shark! Best move cautiously.= + + +## animal_fowl + +chick=pollito +You see a little chick, it doesn't like to be away from its mother.=Ves un pequeño pollito, no le gusta estar lejos de su madre. + +chicken=pollo +You see a chicken, isn't it nice?=Ves un pollo, ¿a que es bonito? + +dodo= +You see a dodo bird, aren't they supposed to be extinct?= + +mother hen=madre gallina +You see a mother hen, she will probably hurt you if you touch her sons.=Ves una madre gallina, probablemente te haga daño si tocas a sus hijos + +penguin=pingüino +You see a penguin, smart and silly.=Ves un pingüino, bobo y elegante + +pigeon=paloma +You see a pigeon, it loves the little seeds.=Ves una paloma, le encantan las semillas pequeñas. + + +## beast + +young beholder=joven beholder +You see a young beholder. His huge eye will find you everywhere!=Ves un joven beholder ¡Su ojo gigante te encontrará en cualquier lugar! + +beholder=beholder +You see a beholder which can see you very well with his huge eye...=Ves un beholder que puede verte muy bien con su enorme ojo... + +elder beholder=beholder anciano +You see an elder beholder, his nasty eye will scare you.=Ves un beholder anciano, su desagradable ojo te asustará. + +gargoyle=gárgola +You see a gargoyle, You should think that it is a statue, but no, it is alive!=Ves a una gárgola, Pensarías que es una estatua, pero no, ¡está viva! + +elder gargoyle=gárgola anciana +You see an elder gargoyle, it is wise...=Ves a una gárgola anciana, es sabia... + +dark gargoyle=gárgola oscura +You see a dark gargoyle, do you feel the fear?=Ves a una gárgola oscura, ¿sientes el miedo? + +venom gargoyle=gárgola de veneno +You see a venom gargoyle, a hideous and poisonous creature.=Ves a una gárgola de veneno, una criatura horrible y venenosa. + +mimic= +You see a mimic. Its content might be different from what you expect.= + +snarfkin=snarfkin +You see a snarfkin, the favorite pet of wizards. Be careful, its claws are poisonous!=Ves a un snarfkin, la mascota favorita de los magos. Ten cuidado, ¡sus garras son venenosas! + +googon=googon +You see a googon that comes from the worst place in this world...Be careful this creature is a deadly killer.=Ves a un googon que viene del peor lugar de este mundo... Ten cuidado esta criatura es una asesina mortal. + +grobble monster=monstruo grobble +You see a grobble monster. You might find this under your bed, inside your closet, or within your darkest nightmares.= + +winged blurble=blurble alado + +thing=cosa +You see a thing. It looks like a very mutated human and is so scary that you think it has to come from another world.=Ves a una cosa. Parece un humano muy mutado y es tan espantoso que crees que ha venido de otro mundo. + + +## chess + +chaos pawn=peón del caos +You see a pawn for a game of chess. He will drop nothing if you kill him.=Ves un peón para una partida de ajedrez. No dejará caer nada si lo matas. + +chaos rook=torre del caos +You see a rook for a game of chess. He will drop nothing if you kill him.=Ves una torre para una partida de ajedrez. No dejará caer nada si la matas. + +chaos knight=caballo del caos +You see a knight for a game of chess. He will drop nothing if you kill him.=Ves un caballo para una partida de ajedrez. No dejará caer nada si lo matas. + +chaos bishop=alfil del caos +You see a bishop for a game of chess. He will not reward you if you kill him.=Ves un alfil para una partida de ajedrez. No te recompensará con nada si lo matas. + +chaos king=rey del caos +You see a king of a game of chess. He will reward nothing if you kill him.=Ves un rey para una partida de ajedrez. No te recompensará con nada si lo matas. + +chaos queen=reina del caos +You see a queen piece for a game of chess. She will reward nothing if you kill her.=Ves una pieza reina para una partida de ajedrez. No te recompensará con nada si la matas. + +madaram pawn=peón madaram +You see a pawn for a game of chess. It will reward nothing if killed.=Ves un peón para una partida de ajedrez. No te recompensará con nada si lo matas. + +madaram rook=torre madaram +You see a rook for a game of chess. It will scarcely reward you to kill it.=Ves una torre para una partida de ajedrez. Apenas te recompensará con algo si la matas. + +madaram knight=caballo madaram +You see a knight, a chess piece. He won't reward you if you kill him.=Ves un caballo, una pieza de ajedrez. No te recompensará con nada si lo matas. + +madaram bishop=alfil madaram +You see a bishop for a game of chess. It will reward you nothing if you slay it.=Ves un alfil para una partida de ajedrez. No te recompensará con nada si lo matas. + +madaram king=rey madaram +You see a king to play chess with, he will reward you nothing if you kill him.=Ves un rey para jugar al ajedrez con él, no te recompensará con nada si lo matas. + +madaram queen=reina madaram +You see a queen piece for a game of chess. She will reward nothing if you kill her.=Ves una pieza reina para una partida de ajedrez. No te recompensará con nada si la matas. + + +## demon + +violet avenger=vengador violeta + +devil queen=reina del diablo + +demon=demonio +You see a demon. He personifies evil.=Ves un demonio. Personifica el mal. + +imperial demon servant=sirviente demonio imperial +You see an imperial demon servant who carries out whims of his demon lords.= + +imperial demon lord=señor demonio imperial +You see an imperial demon lord. He holds the rank of General among demons, and very few out rank it in hell.= + +balrog=balrog +You see a huge balrog. He is slow hitting, but terribly strong as he stands towering above you.=Ves a un enorme balrog. Es lento en golpear, pero terriblemente potente mientras permanece de pie imponiendose por encima de ti. + +imp=diablillo +You see an imp. He may look a little bit cute, but don't forget he is one of the devil's servants!=Ves a un diablillo¡ Puede parecer un poquito lindo, pero no olvides que es uno de los siervos del demonio! + +winged blue imp=diablillo azul alado +You see a winged blue imp. It's endowed with the power of evil. His trident is able to poison even the bravest warriors.= + +water demon= + + +## dragon + +chaos green dragonrider=jinete de dragón verde del caos +You see a chaos green dragonrider. Don't misjudge him as to believe it's a normal green dragon, as it is controlled by a mysterious sorcerer seated on his back.= + +chaos red dragonrider=jinete de dragón rojo del caos +You see a chaos red dragonrider. He might look like a red dragon, but the sorcerer seated on his back guides him to give even more strength out.= + +green dragon=dragón verde +You see a green dragon. He looks very tall and strong.= + +bone dragon=dragon de hueso +You see a bone dragon. It's been said that there are creatures with enough will to live that their souls still cling to life even as their flesh and blood has failed them. This is living proof.=Ves a un dragón de hueso. Se dice que hay criaturas con la suficiente voluntad de vivir como para que sus almas todavía se aferren a la vida aún cuando su carne y su sangre les han abandonado. + +blue dragon=dragón azul +You see a blue dragon. It has a very beautiful blue skin which shimmers mysteriously in the light. But don't forget that it also has claws and teeth!=Ves a un dragón azul. Tiene un tono de piel azul muy bonito que brilla misteriosamente en la luz¡Pero no olvides que también tiene garras y dientes! + +red dragon=dragón rojo +You see into the emerald eyes of a red dragon. With these it can spot prey in the darkness. Its red scales serve it well, reflecting any light that shines on them blinding enemies. The scales are prized for their beauty, and difficult to obtain from such a ferocious beast.= + +black dragon=dragón negro +You see a black dragon. As you see this huge black creature you almost faint! If you concentrate you can feel the deep growling of it in your bones.=Ves un dragón negro ¡Al ver a esta enorme criatura negra casi te desmayas! + +flying golden dragon=dragón de oro volador +You see a flying golden dragon. As it spreads its mighty wings you shiver... this mystical creature seems to have much power in it!=Ves a un dragón de oro volador. A medida que extiende sus poderosas alas te estremeces... ¡esta criatura mística parece tener mucho poder en su interior! + +twin headed dragon=dragón de dos cabezas +You see a twin headed dragon. The two heads are staring at you with their four eyes!=Ves a un dragón de dos cabezas ¡Las dos cabezas te están mirando con sus cuatro ojos! + +rysin dragon= + + +## dwarf + +duergar=duérgar +You see a duergar. It is a close relative to dwarves but with a diabolically tainted blood.= + +duergar axeman=hachero duérgar + +elder duergar=duérgar anciano + +duergar elite=duérgar de élite + +duergar hero=héroe duérgar + +duergar wizard=mago duérgar + +black duergar=duérgar negro + +duergar king=rey duérgar + +dwarf=enano +You see a dwarf who works in the mine to find some iron for us.=Ves a un enano que trabaja en la mina para encontrar un poco de hierro para nosotros. + +dwarf guardian=guardián enano +You see a dwarf guardian who fights against the humans.=Ves a un guardián enano que lucha contra los humanos. + +elder dwarf=enano anciano +You see an elder dwarf, a mighty warrior tired of fighting.=Ves a un enano anciano, un poderoso guerrero cansado de luchar. + +hero dwarf=héroe enano +You see a hero dwarf. He is an old dwarf warrior who became a hero for fighting in many dwarf wars and he is very famous for his strength and brutality.=Ves a un héroe enano. Es un viejo guerrero enano que se convirtión en héroe por luchar en muchas guerras de enanos y es muy famoso por su fuerza y brutalidad. + +leader dwarf=líder de los enanos +You see a leader dwarf. He is the most mighty dwarf of his tribe so he enjoys much popularity there.=Ves a un líder de los enanos. Es el enano más poderoso de su tribu así que disfruta de una gran popularidad allí. + +giant dwarf=enano gigante +You see a giant dwarf. It is very hard for him to be a giant dwarf because some of the other dwarves don't really see him as one of them because of his height... But in his big heart he is a dwarf and in a battle he will fight against each enemy!=Ves a un enano gigante. Es muy duro para él ser un gigante por algunos de los otros enanos no le ven realmente como uno de los suyos por su estatura... Pero en su gran corazón se siente un enano y en la batalla luchará contra cada enemigo! + +Dhohr Nuggetcutter=Dhohr Nuggetcutter + +Lord Durin=Lord Durin +You see the mighty Lord Durin, an ancient dwarf who has led his race through countless battles.=Ves al poderoso Lord Durin, un antiguo enano que ha guiado a su raza a en incontables batallas. + +mountain dwarf=enano de montaña +You see a mountain dwarf. He likes earth, stones and rocks. Deep within the mountain, where no animal and no human has ever been, you can hear the sound of his pickaxe as he searches for gold and ore.=Ves a un enano de montaña. Le gusta la tierra, las piedras y las rocas. En lo profundo de la montaña, donde ningún animal ni ser humano ha estado nunca, puedes oir el sonido de su piqueta mientras busca mineral de oro y de hierro. + +mountain dwarf guardian=guardián enano de montaña +You see a mountain dwarf guardian. He has to watch out for enemies and guards the mountain dwarfs while they are doing their work.=Ves a un guardián enano de montaña. Tiene que estar alerta de los enemigos y protege las montañas enanas mientras ellos hacen su trabajo. + +mountain elder dwarf=enano de montaña anciano +You see a mountain elder dwarf. For many years he worked deep in the mountain and found much gold and ore in his life. Soon he will be too old to do this work but at the moment he is still a strong dwarf.=Ves a un enano de montaña anciano. Durante muchos años trabajó en lo profundo de la montaña y encontró mucho mineral de oro y de hierro en su vida. Pronto será demasiado viejo para hacer este trabajo pero por el momento todavía es un enano fuerte. + +mountain hero dwarf=héroe enano de la montaña +You see a mountain hero dwarf. Many, many dwarves admire him! He was a member of a dwarvish group which discovered a very big gold deposit!=Ves a un héroe enano de la montaña ¡Muchos, muchísimos enanos le admiran! ¡Fue miembro de un grupo de enanos que descubrió a depósito de oro muy grande! + +mountain leader dwarf=líder de los enanos de montaña +You see a mountain leader dwarf. He is responsible for the mountain dwarves and their work, and also for the organization of the guardian troops. But in serious cases he will also fight on his own!=Ves a un líder de los enanos de montaña. Es responsable de las montañas de los enanos y de sus trabajos, y también de la organización de las tropas guardianas ¡Pero en situaciones graves también luchará por sí sólo! + + +## elemental + +air elemental=elemental de aire +You see an air elemental and feel a fresh breeze past you.=Ves a un elemental de aire y sientes una fresca brisa que te atraviesa. + +djinn=djinn +You see a djinn. He isn't caught in a bottle or lamp so someone must have let him free and gave him the order to kill you.=Ves a un djinn. No está atrapado en una botella o lámpara así que alguien debe de haberle soltado y le dió la orden de matarte. + +nymph=ninfa +You see a nymph. She loves nature very much! As you take a look at her she feels disturbed in sun-bathing and talking to the birds, perhaps she was best left alone.=Ves a una ninfa ¡Le gusta muchísimo la naturaleza! Al echarle un vistazo se siente molestada en su tomar el sol y en su hablar con los pájaros, quizá habría sido mejor dejarla a solas. + +earth elemental=elemental de tierra +You see an earth elemental. The earth is quaking as it moves over the surface.=Ves a un elemental de tierra. La tierra tiembla mientras se mueve sobre su superficie. + +fire elemental=elemental de fuego +You see a fire elemental and hardly can stand the heat.=Ves a un elemental de fuego y apenas puedes soportar el calor. + +legendary ice elemental= +You see a Legendary Elemental of Ice...= + +ice elemental=elemental de hielo +You see an ice elemental and sense a shiver on your spine.=Ves a un elemental de hielo y sientes un escalofrío en tu columna vertebral. + +ice giant=gigante de hielo +You see an ice giant. His skin is covered with frozen hoarfrost and his breath feels like the cold north wind.=Ves a un gigante de hielo. Su piel está cubierta de escarcha helada y su aliento se siente como el frío viento del norte. + +water elemental=elemental de agua +You see a Water elemental which is made of water and various other liquids.= + +ent=ent +You see an ent. First you think it is a tree but you will get a surprise when it starts to walk!=Ves a un ent !Primero piensas que es un árbol pero te llevas una sorpresa cuando empieza a caminar! + +old ent=ent viejo +You see an old ent. It has seen many years passing by and survived a lot of storms. You can see that it is very old because it has already lost all of its leaves and most of its bark is broken...=Ves a un viejo ent. Ha visto muchos años pasar y ha sobrevivido a muchas tormentas. Puedes ver que es muy viejo porque ya ha perdido todas sus hojas y la mayor parte de su corteza está rota... + +entwife=esposaent +You see an entwife. It is stronger than an ent and lives in a group in an ancient forest.=Ves a una esposaent. Es más fuerte que un ent y vive en grupo en un bosque antiguo. + + +## elf + +albino elf swordsman=espadachín elfo albino +You see an albino elf swordsman who is a professional in using swords. He can attack you from behind in a blink of an eye.= + +albino elf archer=arquero elfo albino +You see an albino elf archer. He is so sharp-eyed that he could hit a bee from a distance of 100 meters.= + +albino elf magician=mago elfo albino +You see an albino elf magician. He knows all the secrets of magic. You'd be better to not let him unleash his full power!= + +albino elf queen=reina elfa albina +You see an albino elf queen. She is the matriarch of the albino elf race and she will do everything to protect it.= + +albino elf king=rey elfo albino +You see an albino elf king. He is the patriarch of albino elves and won countless of battles with his powerful albino elf army.= + +child dark elf=niño elfo oscuro +You see a child dark elf. You should know that dark elves are already quite strong and intelligent when they are children. Don't think you can fight him so easily!=Ves a un niño elfo oscuro. Deberías saber que los elfos oscuros son ya bastante fuertes e inteligentes cuando son niños ¡No creas que puedes enfrentarte a él tan fácilmente! + +dark elf archer=arquero elfo oscuro +You see a dark elf archer. Dark elf archer's flawless aim can be fatal for those who dare to oppose them.= + +dark elf=elfo oscuro + +dark elf elite archer=arquero de élite elfo oscuro + +dark elf captain=capitán elfo oscuro + +dark elf knight=caballero elfo oscuro + +dark elf general=general elfo oscuro +You see a Dark Elf General. It is commanding the evil troops of Nalwor Caves against intruders.= + +dark elf wizard=mago elfo oscuro +Dark elf wizards are very powerful and dangerous. They are specialised in dark magic.= + +dark elf viceroy=virrey elfo oscuro + +dark elf ranger=explorador elfo oscuro + +dark elf sacerdotist=sacerdotisa elfa oscura + +dark elf admiral=almirante elfo oscuro + +dark elf master=Maestre elfo oscuro +You see a dark elf master. Beware of his high skills on magic and swordsmanship!= + +dark elf matronmother=matrona madre elfa oscura +Dusky and shrouded, this matriarch rules her clan with a soft touch and sharp fangs. An expert in venoms and poisons, you wouldn't ever want to turn your back on her.= + +elf=elfo +You see an elf which loves the forest and lives in peace.=Ves a un elfo al que le encanta el bosque y que vive en paz. + +militia elf=elfo de la milicia +You see a militia elf which trains hard to beat you!=¡Ves a un elfo de la milicia que se entrena duramente para vencerte! + +archer elf=arquero elfo +You see an archer elf which can do much damage with its wooden arrows.=Ves a un arquero elfo que puede hacer mucho daño con sus flechas de madera. + +soldier elf=soldado elfo +You see a soldier elf, a good partner of the militia elves.=Ves a un soldado elfo, un buen compañero de la milicia de los elfos. + +mage elf=maga elfa +You see a mage elf. She can feel pure magic when she stands outside in the forest and just frees her mind.=Ves a una maga elfa. Se puede sentir magia pura cuando se encuentra fuera en el bosque y simplemente libera su mente. + +commander elf=comandante elfo +You see a busy commander elf. He has control over the army of elves.=Ves a un ocupado comandante elfo. Tiene el control del ejército de los elfos. + +archmage elf=archimaga elfa +You see an archmage elf. She is pretty.=Ves a una archimaga elfa. Es guapa. + +elf sacerdotist=sacerdotisa elfa + +elf wizard=mago elfo +You see an elf wizard. He is a very wise elf who knows almost every kind of magic. He likes to use nature spells to make every plant and creature fertile, but he is also able to defend himself when he has to do.=Ves a un mago elfo. Es un elfo muy sabio que sabe casi cualquier tipo de magia. Le gusta usar hechizos de la naturaleza para hacer a cualquier planta o animal fértil, pero también es capaz de defenderse cuando tiene que hacerlo. + +elf bodyguard=guardaespaldas elfo + + +## giant + +amazoness giant=giganta amazona +You see an amazoness giant. She looks very beautiful but don't be deceived by that!=Ves a una giganta amazona ¡Parece muy hermosa pero no te dejes engañar por eso! + +giant=gigante +You look up into the face of a huge giant. His physical proportions and extraordinary strength should give you goosebumps.= + +elder giant=gigante anciano +You see an elder giant. For many years he lived alone and had to hide because he was hunted by humans! Now he is old and has decided not to hide any longer because he wants to see something else in his life.=Ves a un gigante anciano. Durante muchos años vivió sólo y tuvo que esconderse ¡porque era cazado por los humanos! Ahora es viejo y ha decidido no esconderse por más tiempo porque quiere ver otra cosa más en su vida. + +master giant= Maestre gigante +You see a master giant with dark black eyes. His blood pumps loudly into his heart.= + +black giant=gigante negro +You see a tall black giant. His armor and sword will hit you hard. Be prepared!= + +imperial general giant=general imperial gigante +You see an imperial general giant. He lurks in caves with his comrades to take you down.= + +kasarkutominubat=kasarkutominubat +You see kasarkutominubat, a creature not known to many, told it was a human champion long ago, now it resembles nothing but that, with a mask bearing thousands of souls on it and hands running with nothing but blood.= + + +## gnome + +gnome=gnomo +You see a gnome who lives in the mountains.=Ves a un gnomo que vive en las montañas. + +mage gnome=mago gnomo +You see a mage gnome, she cleans the dirty mountain.=Ves a una maga gnoma, ella limpia la sucia montaña. + +infantry gnome=gnomo de infantería +You see an infantry gnome who will protect his little town against the humans!=Ves a un gnomo de infantería que protegerá su pequeña aldea frente a ¡los humanos! + +cavalryman gnome=soldado de caballería gnomo +You see a cavalryman gnome who does long travels to search for gold.=Ves a un soldado de caballería gnomo que realiza largos viajes en busca de oro. + +giant gnome= + +giant gnomess= + + +## goblin + +goblin=goblin +You see a goblin, it is an evil and mischievous creature.=Ves a un goblin, es una criatura malvada y maliciosa. + +veteran goblin=veterano goblin +You see a veteran goblin, an old warrior...=Ves a un goblin veterano, un viejo guerrero... + +soldier goblin=soldado goblin +You see a soldier goblin, a young warrior.=Ves a un soldado goblin, un joven guerrero. + + +## golem + +dwarf golem=golem enano +You see a dwarf golem. A long time ago a dwarf got turned into a big rock by some mystical forces. To save their friend the other dwarves created a golem out of this rock! From this day the dwarf golem lives together with them.=Ves a un golem enano. Hace mucho tiempo un enano fue convertido en una gran roca por unas fuerzas místicas. Para salvar a su amigo los otros enanos crearon un golem ¡a partir de esta roca! Desde este día el golem enano convive con ellos. + +ice golem=golem de hielo +You see an Ice golem. Ice Golems are elementals made out of ice. You should watch out for its icy fists or you might get hurt.= + +stone golem=golem de piedra +You see a stone golem, it only exists to protect. What was it crafted for to protect, and what would one do if it lost its purpose?=Ves a un golem de piedra, existe solamente para proteger. ¿Qué fue diseñado para proteger, y qué haría si perdiese su propósito? + +wooden golem=golem de madera +You see a wooden golem. The scroll with ancient words in his head keeps it walking.=Ves a un golem de madera. El pergamino con las antiguas palabras en su cabeza lo mantiene caminando. + + +## human + +kalavan housewife=ama de casa kalavan +You see a kalavan housewife. She lives in Kalavan city and she is very angry with rude people who want to steal her food out of the kitchen and destroy her beautiful flower garden!=Ves a una ama de casa kalavan. Vive en la ciudad de Kalavan y ¡se enfada mucho con la gente ruda que quiere robar comida de su cocina y destruir su precioso jardín! + +kalavan peasant=campesino kalavan +You see a kalavan peasant. He works in the fields around Kalavan city. As he heard there were strangers in the city he came to help the kalavan citizens in fighting the enemies!=Ves un campesino kalavan. Trabaja en los campos de alrededor de la ciudad de Kalavan. Cuando oyó que había extranjeros en la ciudad vino a ayudar a los ciudadanos de kalavan a luchar contra los enemigos! + +kalavan wisewoman=mujer sabia de kalavan +You see a kalavan wisewoman. Normally she sits in her small room and does some handworking but now she wants to have some action!=Ves a una mujer sabia de kalavan. Normalmente se sienta en su pequeña habitación y hace labores manuales pero ahora quiere tener algo de acción! + +kalavan citizen=ciudadano de kalavan +You see a kalavan citizen. He lives in kalavan city and tries to protect it from invaders!=Ves a un ciudadano de kalavan. Vive en la ciudad de kalavan ¡e intenta protegerla frente a los invasores! + +monk=monje +You see a monk. Monks are very calm and peaceful. They live alone among themselves, but they don't want to be interrupted by anyone.= + +darkmonk=monje oscuro +You see a Darkmonk. Darkmonks can control dark powers, and they will do anything to get you out of their way!= + +devil monk=monje del diablo +You see a devil monk. He is a member of a devil cult which practises black rituals.=Ves a un monje del diablo. Es miembro de un culto al diablo que practica negros rituales. + +littlewitch=brujita +You see a little witch. These little girls are witches and that makes them very dangerous.=Ves a una brujita. Estas niñitas son brujas y eso las hace muy peligrosas. + +littlewizard=pequeño mago +You see a little wizard. Although he is a child, he is already able to cast powerful spells.=Ves a un pequeño mago. Aunque es un crío, ya es capaz de lanzar poderosos hechizos. + + +## human_amazon + +amazoness archer=arquera amazona +You see an amazoness archer. She handles her bow quite well and her arrows are very dangerous when she shoots them at you out of the forest!=Ves a una arquera amazona ¡Maneja su arco bastante bien y sus flechas son muy peligrosas cuando las dispara hacia ti desde el bosque! + +amazoness archer commander=arquera comandante amazona +You see an amazoness archer commander. She is the leader of amazoness archers. Her bow skills are incredible and her arrows pierce everything.= + +amazoness bodyguard=guardaespaldas amazona +You see an amazoness bodyguard who will guarantee the safety to the people of Amazon island.= + +amazoness coastguard=guardacostas amazona +You see an amazoness coastguard. She protects the coasts of Amazon Island from intruders. She is not too happy to see you running in her island.= + +amazoness coastguard mistress=guardacostas Maestre amazona +You see an amazoness coastguard mistress. Even if they are women, these blue haired mistresses should not be underestimated.= + +amazoness commander=comandante amazona +You see an amazoness commander who is specialised in eliminating any strangers who dare to take a step to her island.= + +amazoness elite coastguard=guardacostas de élite amazona +You see an amazoness elite coastguard. When it is her shift to protect the island, you would just wish to have stayed at home.= + +amazoness hunter=cazadora amazona +You see an amazoness hunter. She creeps through the forest to hunt animals. But she doesn't like that you are in her hunting area!=Ves a una cazadora amazona ¡Se arrastra por el bosque para cazar animales ¡Pero no le gusta que estés en su zona de caza! + +amazoness vigilance=vigilante amazona +You see an amazoness vigilance. Her senses are well tuned, so you have to be very silent when you don't want her to hear you!=Ves a una vigilante amazona ¡Sus sentidos están muy desarrollados, así que tienes que ser muy silencioso cuando no quieras que te oiga! + +amazoness imperator=imperator amazona +You see an amazoness imperator. She is the emperor of Amazon island and has ruled it for decades and won't let you get past her very easily.= + + +## human_assassin + +bandit=bandido +You see a lean and mean bandit. He's out to get your life and purse!=Ves a un bandido preparado y listo para actuar ¡Hará lo que sea necesario para llevarse tu vida y tu dinero! + +disciple assassin=discípulo de asesino +You see a disciple assassin. He is quite young but he has learned to fight and kill already.=Ves a un discípulo de asesino. Es bastante joven pero ya ha aprendido a luchar y a matar. + +apprentice assassin=aprendiz de asesino +You see an apprentice assassin. He has been taught some very awesome moves and methods, so he is already able to kill an enemy almost perfectly!=Ves a un aprendiz de asesino. Le han enseñado algunos movimientos y métodos muy impresionantes, así que ya es capaz de matar a un enemigo casi perfectamente! + +hunter=cazador + +assassin=asesino +You see an assassin. He is very good at killing his victims silently without being seen by anyone.=Ves a un asesino. Es muy bueno matando a sus victimas silenciosamente sin ser visto por nadie. + +ninja=ninja +Your keen eyes made you able to see this fast ninja. Prepare for a fight!= + +samurai=samurai +If this samurai failed to uphold his honor, he could only regain it by performing seppuku.= + +shadows assassin=asesino de las sombras +You see a shadows assassin. He knows how to become one with the shadows and to move in the dark without being seen or making any sound.=Ves a un asesino de las sombras. Sabe como hacerse uno con las sombras y moverse en la oscuridad sin ser visto o hacer ningún sonido. + +master assassin=Maestre asesino +You see a master assassin. He is a master of assassination! He is able to kill an enemy without letting him know that he is already there!=Ves a un Maestre asesino ¡Es un maestro del asesinato! ¡Es capaz de matar a un enemigo sin dejar que sepa que ya está allí! + + +## human_barbarian + +barbarian=bárbaro + +barbarian wolf=lobo bárbaro + +barbarian elite=bárbaro de élite + +barbarian priest=sacerdote bárbaro + +barbarian chaman=chamán bárbaro + +barbarian leader=líder bárbaro + +barbarian king=rey bárbaro + + +## human_blordrough + +blordrough quartermaster=intendente de blordrough +You see a man who gave up his life as a puppet of the Blordrough army, all for the love of dragons. He is wonderfully happy.= + +blordrough corporal=cabo de blordrough +You see a man who feels his past indicates how he should be treated now but desperately wishes to exercise his intelect in a game of chess.= + +blordrough storm trooper=soldado de asalto de blordrough +You see a man who is but a fraction of his former glory as one of the most feared foes of Faiumoni. Now, his main concern is keeping his house clean and tidy.= + +blordrough soldier= +You see a man who is fast and strong.= + +blordrough elite= +You see a man who is aging, but he knows how to fight.= + +blordrough infantry= +You see a man who has flaming red hair and red beady eyes.= + +blordrough captain= +You see a seasoned warrior.= + +blordrough general= +You see a lean, mean, fighting machine.= + + +## human_cannibal + +cannibal woman=mujer caníbal +You see a cannibal woman. She wants to make a soup out of your bones!=Ves a una mujer caníbal ¡Quiere hacer una sopa con tus huesos! + +cannibal=caníbal +You see a cannibal. He hunts you for cooking and eating your fresh meat! It's useless to tell him that your flesh is not good, better fight him or run away!=Ves a un caníbal. Te caza para cocinar y comerse tu carne fresca! + +elder cannibal=caníbal anciano +You see an elder cannibal. For years it has eaten humans. It will not stop until its hunger is satisfied.= + +cannibal warrior= +You see a cannibal warrior.= + + +## human_chaos + +chaos soldier=soldado del caos +You see a chaos soldier. Chaos soldiers are servants of the evil. Killing you is one of their evil plans.= + +chaos warrior=guerrero del caos +You see a chaos warrior. This chaotic creature is fighting with a big blade to slay his enemies.= + +chaos commander=comandante del caos +You see a chaos commander who is working on an evil plan. You should take care of him before he fulfills his duty.= + +chaos sorcerer=hechicero del caos +You see a chaos sorcerer. Chaos sorcerers will cast some evil spells on you if you don't obey them.= + +chaos dragonrider=jinete de dragón del caos +You see a chaos dragonrider. He is very well trained in dragon riding, but he can also fight well on the ground.=Ves a un jinete de dragón del caos. Está muy bien entrenado en montar a dragón, pero también puede luchar bien en el suelo. + +chaos lord=señor del caos +You see a chaos lord who is one of the strongest chaos creatures in Faiumoni. You should prepare well before attacking him.= + +chaos overlord=gran señor del caos +You see a chaos overlord. It is him who directs the creation of evil plans and gives orders to all chaotic creatures which exist.=Ves a un gran señor del caos. Es él quien dirige la creación de los planes malvados y da las órdenes a todas las criaturas que existen. + + +## human_imperial + +imperial defender=defensor imperial +You see an imperial defender. He and his comrades won't let you walk into their territory easily.= + +imperial veteran=veterano imperial +You see an imperial veteran. He fought in many battles and wars. Now he is old, but over the years he always looked for a new enemy to fight against.=Ves a un veterano imperial. Luchó en muchas batallas y guerras. Ahora es viejo, pero a lo largo de los años siempre ha buscado un nuevo enemigo contra el que luchar. + +imperial archer=arquero imperial + +imperial priest=sacerdote imperial +You see an imperial priest. The church has fallen out of favor with the citizens as of late, but this imperial priest still clings to the faith.= + +imperial elite guardian=guardián de élite imperial +You see an Imperial Elite Guardian. He is a very well trained guardian and he will protect his environment until the end.= + +imperial scientist=científico imperial +You see an imperial scientist. He is quite clever but also a little bit crazy and unfortunately he uses his intelligence for evil experiments.=Ves a un científico imperial. Es bastante listo pero también un poquito loco y desafortunadamente usa su inteligencia para experimentos malvados. + +imperial high priest=sumo sacerdote imperial +You see an imperial high priest who's troubled by the actions of the imperial science department. Be careful, he might take those frustrations out on you!= + +imperial archer leader=líder arquero imperial + +imperial elite archer=arquero de élite imperial + +imperial leader=líder imperial +You see a well dressed imperial leader. Its troops will try to dash you.= + +imperial knight=caballero imperial + +imperial chief=jefe imperial + +imperial commander=comandante imperial + +imperial general=general imperial +You see an imperial general with a huge scar above his eye. He is still strong, don't think that he will not see you!= + +emperor dalmung=emperador dalmung +You see emperor dalmung who stands before you. Once a powerful warlock trained by a mighty magician, but he disappeared decades ago. Now it seems he's in league with Ivan Abe!= + + +## human_madaram + +madaram archer=arquero madaram + +madaram peasant=campesino madaram + +madaram trooper=soldado de asalto madaram + +madaram soldier=soldado madaram + +madaram healer=sanador madaram + +madaram axeman=hachero madaram + +madaram queen=reina madaram + +madaram hero=héroe madaram + +madaram windwalker=windwalker madaram + +madaram cavalry=caballería madaram + +madaram stalker=acechador madaram + +madaram buster blader=buster blader madaram + + +## human_mithrilbourgh + +mithrilbourgh soldier=soldado de mithrilbourgh + +mithrilbourgh archer=arquero de mithrilbourgh + +mithrilbourgh knight=caballero de mithrilbourgh + +mithrilbourgh elite archer=arquero de élite de mithrilbourgh + +mithrilbourgh sergeant=sargento de mithrilbourgh + +mithrilbourgh lieutenant=lugarteniente de mithrilbourgh + +mithrilbourgh colonel=coronel de mithrilbourgh + +mithrilbourgh wizard=mago de mithrilbourgh + +mithrilbourgh general=general de mithrilbourgh +You see a mithrilbourgh general. Some call him Hawkeyes. He cast his gaze over the city walls and saw you coming from miles away.= + + +## human_risecia + +risecia thug=matón risecia +You see a risecia thug, stay away from him!=Ves a un matón risecia, ¡mantente alejado de él! + +risecia archer=arquero risecia +You see a risecia archer, just one of the warriors who wants to protect his chief.=Ves a un arquero risecia, sólo uno más de los guerreros que quieren proteger a su jefe. + +risecia swordsman=espadachín risecia +You see a risecia swordsman, the blade of his sword will cut your head!=Ves a un espadachín risecia, ¡la hoja de su espada te cortará la cabeza! + +Chief Falatheen of the Risecia=Jefe Falatheen de los Risecia +You see the Chief Falatheen of the Risecia, he doesn't want to die! His little warriors protect him against invaders.=Ves al Jefe Falatheen de los Risecia, ¡no quiere morir! Sus pequeños guerreros le protegen frente a los invasores. + + +## humanoid + +robot aruthon=robot aruthon + +dark aruthon=aruthon oscuro + +leprechaun=leprechaun +You see a leprechaun, you would be lucky if you found his treasure.=Ves a un leprechaun, tendrías suerte si encontrases su tesoro. + +littlefairy=hadita +You see a little fairy, which is magical and nice.=Ves a una hadita, que es mágica y preciosa. + +clurichaun=clurichaun +You see a clurichaun. He is rich, but you don't know if you are able to find his gold!=Ves a un clurichaun. Es rico, pero no sabes si serás capaz de encontrar su oro! + +brain ahouga=cerebro ahouga +You see a brain ahouga, a very strange creature only living in your dreams!=Ves a un cerebro ahouga, ¡una criatura muy extraña que sólo vive en tus sueños! + +blue mohiko=mohiko azul +You see a blue mohiko. A brown alien with a large blue mohawk.= + +red roohako=roohako rojo +You see a red roohako. It is coming for you in your deepest dreams.= + + +## hybrid + +arachne=arachne +You see Arachne, a horrid half woman half spider. With so many legs she can hit you very fast, and her poisonous bite is fierce.=Ves a Arachne, un ser horrible mitad mujer mitad araña. Con tantas piernas puede golpearte muy rápido, y su mordedura venenosa es feroz. + +frogman=hombre-rana +You see a frogman which lives in a lake which isn't very deep.=Ves a un hombre-rana que vive en un lago no muy profundo. + +elite frogman=hombre-rana de élite +You see an elite frogman. His huge eyes let you shiver!= + +wizard frogman=mago hombre-rana +You see a mighty wizard frogman. His wand and magical cloak show his wisdom and power.= + +minotaur=minotauro +You see a minotaur. He escaped from his labyrinth and is very angry now!=Ves a un minotauro ¡Ha escapado de su laberinto y ahora está muy enfadado! + +minotaur king=rey minotauro +You see a Minotaur King. He doesn't have to escape from his labyrinth to be very angry...= + +naga=naga +You see a naga. She awaits unwary victims to whom she may give her deadly kiss.= + +high naga=naga suprema + +armed naga=naga con brazos + +lamia= + +ratman=hombre rata +You see a ratman. He is a mutation that comes from old times.=Ves a un hombre rata. Es una mutación que procede de tiempos remotos. + +ratwoman=mujer rata +You see a ratwoman. She is a mutation of women with the rats in the century XVIII.=Ves a una mujer rata. Es una mutación de mujeres con ratas en el siglo XVIII. + +archrat=archirrata +You see an archrat. You will be distracted with its aura, be careful...=Ves una archirrata. Te distraerás con su aura, ten cuidado... + +sheepman=hombre oveja + +armored sheepman=hombre oveja blindado + +elder sheepman=hombre oveja anciano + +elite sheepman=hombre oveja de élite + +xenocium=xenocium + + +## insect + +killer bee= +You see a killer bee which will sting you to death!= + +tiny scarab= +You see a tiny scarab. It is an insect familiar to desert ecologies that will eat almost anything, including dung. Its hard carapace provides protection from the sun and being crushed, so don't think a little stomp will stop it.= + +water scarab= + + +## kobold + +kobold=kobold +You see a kobold. People say that it is a mythical spirit.=Ves a un kobold. La gente dice que es un espíritu mítico. + +soldier kobold=soldado kobold +You see a soldier kobold, he's protecting his king.=Ves a un soldado kobold, está protegiendo a su rey. + +archer kobold=arquero kobold +You see an archer kobold. It runs away from humans.=Ves a un arquero kobold. Sale corriendo de los humanos. + +veteran kobold=veterano kobold +You see a veteran kobold, it is mighty!=Ves a un veterano kobold, ¡es poderoso! + +leader kobold=líder kobold +You see a leader kobold, one of the mighty warriors.=Ves a un líder kobold, uno de los poderosos guerreros. + +giant kobold=kobold gigante +You see a giant kobold. Though generally playful it doesn't quite know its own strength. When it asks for a hug, it's best to decline that offer.=Ves a un kobold gigante. Aunque por lo general es juguetón no conoce su propia fuerza. Cuando pide un abrazo, es mejor rechazar la oferta. + + +## mutant + +cat woman=mujer gata +You see a cat woman. She's agile, stealthy, fast and sharp, so stay focused and don't get cut!= + +dark mutant=mutante oscuro + +glow monster=monstruo del resplandor +You see a glow monster. Its gleam in the dark sewers fightens you.= + +imperial experiment=experimento imperial +You see an imperial experiment which mirrors the fate of many of science departments test subjects. Its skin glows and standing before it taxes your body.= + +imperial mutant=mutante imperial + +poison shroom= +You see a toadstool... spitting poison at you?= + +werewolf=hombre lobo +You see a werewolf. Normally he is a human but at fullmoon nights he turns into a dangerous beast! His poison seems to last forever.=Ves a un hombre lobo. Normalmente es un humano pero ¡en las noches de luna llena se convierte en una bestia peligrosa! Su veneno parece durar para siempre. + + +## mythical_being + +mermaid= +You see a mermaid. They perfer their own company and are not too happy to see you here.= + +emerald mermaid= +You see an emerald mermaid.= + +amethyst mermaid= +You see an amethyst mermaid.= + +ruby mermaid= +You see a ruby mermaid.= + +sapphire mermaid= +You see a sapphire mermaid.= + +unicorn=unicornio +You see a rare unicorn. It is widely believed to be only a myth.=Ves un raro unicornio. En general se cree que es sólo un mito. + +white unicorn= + +black unicorn= + +golden unicorn= + +pegasus=pegaso +You see Pegasus, the winged horse. His name means Lightning and he carries bolts of Thunder.=Ves a Pegasus, el caballo alado. Su nombre significa Relámpago y lleva los rayos del Trueno. + +baby pegasus= + +bronze pegasus= + +ivory pegasus= + +ebony pegasus= + +centaur=centauro +You see a noble centaur, half man, half horse. He looks capable of great damage but thankfully seems to mean you no harm.=Ves a un noble centauro, mitad hombre, mitad caballo. Parece capaz de causar gran daño pero afortunadamente no parece tener intención de herirte. + +solar centaur= + +glacier centaur= + +centaur king= + + +## ogre + +ogre=ogro +You see an ogre, it is nude and rude!=Ves a un ogro, ¡está desnudo y es rudo! + +elder ogre=ogro anciano +You see an elder ogre, he's in the last moments of his life, take pity on him.=Ves a un ogro anciano, está en los últimos momentos de su vida, ten compasión de él. + +ogre soldier=soldado ogro +You see an ogre soldier, smelling like it didn't have had a bath for years. Ewww.=Ves a un soldado ogro, huele como si no se hubiese bañado durante años. Puaj! + +superogre=superogro +You see a superogre which belongs to the family of very material creatures, the Ogres. Ever since this one happened upon a peculiar helmet and has been revered among its peers. Take care, it throws a mean punch!= + + +## oni + +oni warrior=guerrero oni +You see an oni warrior. His purple skin color should not lull you into a false sense of security!= + +oni archer=arquero oni +You see an oni archer. His arrows will follow you everywhere for protecting his queen!= + +oni priest=sacerdote oni +You see a mysterious oni priest, uttering strange incantations.=Ves a un misterioso sacerdote oni, pronunciando extraños encantamientos. + +oni king=rey oni +You see the oni king. His power leads brave oni troops around his kingdom.= + +oni queen=reina oni +You see the oni queen. She supports her husband and takes an eye onto their kingdom while he conquers new land and fights on battlefields.= + + +## orc + +orc=orco +You see an orc which is noisy and silly.=Ves a un orco que es ruidoso y tonto. + +orc spearman=lancero orco +You see an orc spearman. Run away fast before a spear pierces you.=Ves a un lancero orco. Sal corriendo rápido antes de que una lanza te atraviese. + +orc hunter=cazador orco +You see an orc hunter. It it seeking for you!=Ves a un cazador orco ¡Te está buscando a ti! + +orc warrior=guerrero orco +You see an orc warrior which doesn't like you searching in the mountain.=Ves a un guerrero orco al que no le gusta que andes buscando en la montaña. + +orc chief=jefe orco +You see an orc chief which is too busy to attend to you!=Ves a un jefe orco ¡que está demasiado ocupado para atenderte! + +mountain orc=orco de montaña +You see a mountain orc. Not very cunning but his robustness, stench and loudness could still overwhelm you!= + +mountain orc warrior=guerrero orco de montaña +You see a mountain orc warrior. Not very cunning but you should not underestimate his robustness and stench!= + +mountain orc hunter=cazador orco de montaña +You see a mountain orc hunter. He's in search for raw meat... You could be his next lunch!= + +mountain orc chief=jefe orco de montaña +You see a mountain orc chief. He's proud, loud and reeks like a thousand orcs together...= + +golden orc= + + +## reptile + +grass snake=serpiente de hierba +You see a grass snake which is quite dangerous.=Ves una serpiente de hierba que es bastante peligrosa. + +snake=serpiente +You see a snake, it came from the forest to poison you!=Ves una serpiente, ¡vino desde el bosque a envenenarte! + +cobra= + +king cobra= +You see a king cobra. It is extremely venomous.= + +caiman=caimán +You see a caiman, which is hidden in the water.=Ves un caimán, que está escondido en el agua. + +crocodile=crocodilo +You see a crocodile, it arises from the deep waters in the rivers.=Ves un crocodilo, surge de las aguas profundas de los ríos. + +turtle= +You see a turtle swimming quietly in the water. It doesn't want to be disturbed.= + + +## rodent + +mouse=ratón +You see a small mouse that seems to be very hungry.=Ves un pequeño ratón que parece estar muy hambriento. + +rat=rata +You see a small rat that seems to be extremely hungry.=Ves una pequeña rata que parece estar extremadamente hambrienta. + +caverat=rata de cueva +You see a caverat which is dirty and loves the dungeons.=Ves una rata de cueva que es sucia y le encantan las mazmorras. + +venomrat=rata de veneno +You see a venomrat, extremely poisonous.=Ves una rata de veneno, extremadamente venenosa. + +zombie rat=rata zombi +You see a hungry zombie rat and it doesn't like to see you.=Ves una hambrienta rata zombi y a ella no le gusta verte a ti. + +razorrat=rata de cuchilla +You see a razorrat, its noise will make you crazy!=Ves una rata de cuchilla, ¡su ruído te hará perder la cabeza! + +giantrat=ratagigante +You see a giant rat. It seems like it survived from an experiment which went wrong.=Ves una rata gigante. Parece que haya sobrevivido a un experimento que salió mal. + +beaver=castor +You see a beaver. It likes to build dams in the river.=Ves un castor. Le gusta construir presas en el río. + + +## slime + +black slime=baba negra +You see a black slime, with a sticky trail behind it.=Ves una baba negra, que va dejando un rastro pegajoso. + +brown slime=baba marrón +You see a brown slime, sticky and hungry.=Ves una baba marrón, pegajosa y hambrienta. + +green slime=baba verde +You see a green slime, sticky and shiny.=Ves una baba verde, pegajosa y brillante. + +twilight slime=baba del crepúsculo +You see a twilight slime, so strange and beautiful. Why is it coming towards you? Runnnnnnnnn!=Ves una baba del crepúsculo, tan extraña y bella ¿Por qué se está acercando hacia ti? ¡Correeeeeeeee! + + +## spider + +spider=araña +You see a spider. It makes a giant spider web to trap you!=Ves una araña ¡Está haciendo una telaraña gigante para atraparte! + +poisonous spider=araña venenosa +You see a poisonous spider. Be careful! Its poison is very strong! It will make you very weak and can probably kill you!=Ves una araña venenosa ¡Ten cuidado! ¡Su veneno es muy fuerte! ¡Te debilitará y probablemente puede matarte! + +giant spider=araña gigante +You see a giant spider. It has very long legs which are quite dangerous.=Ves a una araña gigante. Tiene patas muy largas que son bastante peligrosas. + + +## troll + +cyclops=cíclope +You see a cyclops. It just has one eye.=Ves a un cíclope. Sólo tiene un ojo. + +troll=trol +You see a troll, be careful in his terrain.=Ves a un trol, ten cuidado en su terreno. + +red troll=trol rojo +You see a red troll, he's burning with anger.=Ves a un trol rojo, está quemando de ira. + +cave troll=trol de cueva + + +## undead + +ghost hound=perro fantasma +As you catch sight of its shaggy shape and gleaming evil eyes, you suddenly remember all the horror stories around the dreaded ghost hound!=Al avistar su forma peluda y sus malvados ojos brillantes, de repente recuerdas todas las historias de terror acerca del temido perro fantasma! + +death knight=caballero de la muerte +You see a death knight. Some time ago he fought in tournaments where his only aim was to kill as many enemies as possible.=Ves a un caballero de la muerte. Hace algún tiempo luchó en torneos donde su único objetivo era matar tantos enemigos como fuese posible. + +necrosophia=necrosophia +You see Necrosophia. She is a mighty beast, she will burn you with fire while you are disoriented by the rainbow beans.= + +green phantom=fantasma verde + + +## undead_lich + +lich=lich +You see a lich, a dark desolate creature which has been doomed to wander in endless turmoil, feeding on the souls of the righteous.=Ves a un lich, una criatura oscura solitaria que ha sido condenada a vagar en confusión sin fin, alimentándose de las almas de los justos. + +dead lich=lich muerto +You see a dead lich with his huge green and heavy cloak. Although he is dead and bony, he'll seek revenge for his too early passing and will always return fast and ruthless!= + +high lich=lich supremo +The terrible grinning skull of a high lich leers at you as it prepares to slaughter you in a myriad of magical ways.=La terrible sonrisa de la calavera de un lich supremo te mira de reojo al prepararse para matarte de una miríada de mágicas maneras. + + +## undead_mummy + +mummy=momia +You see a mummy which usually sleeps in a coffin.=Ves a una momia que suele dormir en un sarcófago. + +royal mummy=momia real +You see a royal mummy. You wonder what keeps this king's soul from finding peace.=Ves a una momia real. Te preguntas qué le impide al alma de este rey encontrar la paz. + + +## undead_skeleton + +skeleton=esqueleto +You see a skeleton which survives to get revenge.=Ves a un esqueleto que sobrevive para vengarse. + +tiny skelly=pequeño esqueletito +You see a tiny skelly. It is silly, little and evil, be careful with its poison...=Ves a un pequeño esqueletito. Es tonto, pequeño y malvado, ten cuidado con su veneno... + +warrior skeleton=guerrero esqueleto +You see a warrior skeleton, it's just a normal skeleton with a shield!=Ves a un guerrero esqueleto, ¡es sólo un esqueleto normal con un escudo! + +elder skeleton=esqueleto anciano +You see the bones of an old man. He lived to a ripe old age, and is sure to know a thing or two that separates him from all the vengeful skeletons that died young.= + +demon skeleton=esqueleto demonio +You see a demon skeleton. Demon skeletons have ascended from the underworld and they are going to take over this world. You really should slay them before it is too late!= + +gashadokuro=gashadokuro +A giant figure made of bone, known only as gashadokuro, towers before you. He is created from the bones of starving humans.=Una figura gigante hecha de hueso, conocida sólo como gashadokuro, se impone ante ti. Se ha creado a partir de los huesos de humanos muertos de hambre. + +fallen warrior=guerrero caído +You see a fallen warrior. Once he fought in great battles but now he has fallen to the depths of hell only to come back up to get his revenge.= + +fallen priest=sacerdote caído +You see a fallen priest. He was once just a regular human priest, until the dark powers corrupted him and turned him to the dark side.= + +fallen high priest=sumo sacerdote caído +You see a fallen high priest. It is manifested through the dark forces seized of this world.= + + +## undead_specter + +ghost=fantasma +You see a ghost, don't be afraid! It doesn't seem to be evil.=Ves un fantasma, ¡no tengas miedo! No parece ser malvado. + +death=muerte +You see Death. Do you have the will to stare him in the face? Today he's not in the mood to bargain with your soul.=Ves a la Muerte ¿Tendrás el valor de mirarla a la cara? Hoy no está de humor para negociar con tu alma. + +black death=muerte negra +You see a black death. With his scythe he reaps your soul and then he takes it over the river where it has to stay until judgement day arrives.=Ves una muerte negra. Con su guadaña siega tu alma y después se la lleva al río donde tiene que permanecer hasta que llegue el día del juicio. + +golden death=muerte de oro +You see the golden death. He came to take your soul away to pay for all your sins.=Ves a la muerte de oro. Vino a llevarse tu alma para que pague por todos tus pecados. + +invisible man=hombre invisible +You see nothing but the landscape. As you stare at the point where you think you have seen something, you suddenly see the slight contour of a man!=No ves nada excepto el paisaje. Al mirar fijamente al punto donde crees haber visto algo, de repente ¡ves el ligero contorno de un hombre! + +incorporeal armor=armadura incorpórea +You see an incorporeal armor. You are quite surprised as it walks towards you because there is nobody in it.=Ves una armadura incorpórea. Te sorprende bastante al caminar hacia ti porque no hay nadie dentro de ella. + + +## undead_vampire + +vampire bride=novia vampira +You see a vampire bride. She was resurrected from the dead by a wicked vampire lord.=Ves a una novia vampira. Fue resucitada de entre los muertos por un malvado señor vampiro. + +vampirette=vampiresa +You see a Vampirette. Beware of its sharp teeth! She wants to drink your blood and turn you into a vampire!= + +vampire lord=señor vampiro +You see a vampire lord. Be careful, he is after your blood!=Ves a un señor vampiro. Ten cuidado, ¡va tras tu sangre! + + +## undead_zombie + +bloody zombie=zombi sangriento +You see a bloody zombie. Can you see the drops of blood which fall from his body?=Ves a un zombi sangriento. ¿Puedes ver las gotas de sangre que caen de su cuerpo? + +zombie=zombi +You see a zombie. It woke up just to take revenge, be silent.=Ves a un zombi. Se despertó solo para vengarse, estate en silencio. + +headless monster=monstruo sin cabeza +You see a headless monster, how can he hear that we come to him?=Ves a un monstruo sin cabeza, ¿Cómo ha podido oír que nos acercábamos a él? + +rotten zombie=zombi podrido +You see a rotten zombie. You are disgusted as you see its rotten skin that hangs down in pieces from his half rotten bones.=Ves a un zombi podrido. Estás asqueado al ver su piel podrida que cuelga hecha jirones de sus huesos medio podridos. + + +# strings not found in template + +rift cloak=capa de rift +You see a rift cloak. Almost useless in the battle, but saves your life when you enter a magical rift.=Ves una capa de rift. Casi inútil en la batalla, pero te salva la vida cuando entras en un rift mágico. +necromancers staff=baston de nigromante +You see a cunningly beautiful staff. If you listen carefully you can hear its previous owners' souls screaming from within it.=Ves un bastón engañosamente hermoso. Si escuchas atentamente puedes oir las almas de sus anteriores dueños gritar desde dentro. bottle=botella You see a bottle. It is empty, someone drank the contents.=Ves una botella. Está vacía, alguien se bebió su contenido. big bottle=botella grande You see a big bottle.=Ves una botella grande. -eared bottle=botella con asas -You see an eared bottle. It is empty, someone drank the contents.=Ves una botella con asas. Está vacía, alguien se bebió su contenido. -fat bottle=botella gruesa -You see a fat bottle. It is empty, someone drank the contents.=Ves una botella gruesa. Está vacía, alguien se bebió su contenido. short rounded bottle=botella redondeada baja You see a short rounded bottle. It is empty, someone drank the contents.=Ves una botella redondeada baja. Está vacía, alguien se bebió su contenido. short triangular bottle=botella triangular baja You see a short triangular bottle. It is empty, someone drank the contents.=Ves una botella triangular baja. Está vacía, alguien se bebió su contenido. -slim bottle=botella estrecha -You see a slim bottle. It is empty, someone drank the contents.=Ves una botella estrecha. Está vacía, alguien se bebió su contenido. squared bottle=botella cuadrada You see a squared bottle. It is empty, someone drank the contents.=Ves una botella cuadrada. Está vacía, alguien se bebió su contenido. tall bottle=botella alta. You see a tall bottle. It is empty, someone drank the contents.=Ves una botella alta. Está vacía, alguien se bebió su contenido. triangular bottle=botella triangular You see a triangular bottle. It is empty, someone drank the contents.=Ves una botella triangular. Está vacía, alguien se bebió su contenido. - -empty goblet=copa vacía -You see an empty goblet. You wonder what could it hold.=Ves una copa vacía. Te preguntas que podría contener. -goblet=copa -You see a goblet. You look at its contents, and shiver at the blood inside.=Ves una copa. Miras su contenido, y sientes escalofríos al ver sangre dentro. -keyring=llavero -You see a keyring.=Ves un llavero. -note=nota -You see a note.=Ves una nota. -coupon=cupón -You see a coupon.=Ves un cupón. -closed envelope=sobre cerrado -opened envelope=sobre abierto -sealed envelope=sobre sellado -unsealed envelope=sobre sellado abierto -assassins id=identificación de asesino -You see an assassin's ID. Perhaps this can be put to good use.=Ves una identificación de asesino. Quizá puedas hacer un buen uso de ella. -map=mapa blank scroll=pergamino en blanco You see a rolled up blank scroll.=Ves un pergamino enrollado y en blanco ice scroll=pergamino de hielo You see a scroll, circled by ice crystals. You can feel its cold aura.=Ves un pergamino, rodeado por cristales de hielo. Puedes sentir su fría aura. -fierywater=aguardiente -You see a bottle of fierywater. Do not drink it pure, you would evaporate!=Ves una botella de aguardiente. No te la bebas pura, ¡Te evaporarías! -mild koboldish torcibud=suave torcibud koboldiano -You see a bottle of mild koboldish torcibud. It will help you recover when you're all bruised and battered.=Ves una botella de suave torcibud koboldiano. Te ayudará a recuperarte cuando estés todo magullado. -strong koboldish torcibud=fuerte torcibud koboldiano -You see a bottle of strong koboldish torcibud. It will help you recover when you're all bruised and battered.=Ves una botella de fuerte torcibud koboldiano. Te ayudará a recuperarte cuando estés todo magullado. -vsop koboldish torcibud=torcibud koboldiano de estaca vieja muy especial -You see a bottle of VSOP koboldish torcibud, specially aged in oak casks wrapped by the darkness of a dungeon cellar. It will help you recover when you're all bruised and battered.=Ves un torcibud koboldiano de estaca vieja muy especial, especialmente envejecida en barricas de roble envueltas por la oscuridad de la bodega de una mazmorra. Te ayudará a recuperarte cuando estés todo magullado. -apple juice=zumo de manzana -You see apple juice. On a hot summer day, it would taste great.=Ves zumo de manzana. En un día de verano caluroso, sabría estupendo. -beer=cerveza -You see a beer. On a hot summer day, it would taste great.=Ves una cerveza. En un día de verano caluroso, sabría estupendo. -wine=vino -You see a glass of red wine. It will quench your thirst.=Ves un vaso de vino tinto. Saciará tu sed. -water=agua -You see a bottle of fresh spring water. It's really tasty and fresh.=Ves una botella de agua fresca de manantial. Es realmente sabrosa y fresca. -pina colada=piña colada -You see a pina colada. It looks delicious but tastes dangerously strong!.=Ves una piña colada ¡Parece deliciosa pero sabe peligrosamente fuerte! -tea=té -You see a lovely cup of tea.=Ves una encantadora taza de té. -milk=leche -You see a small bottle of fresh milk.=Ves una pequeña botella de leche fresca. -antidote=antídoto -You see an antidote, to cure you of poison and protect you for a while.=Ves un antídoto, para curarte de venenos y protegerte durante un rato. -greater antidote=antidoto mayor -You see a greater antidote, to cure you of poison and protect you for a long while.=Ves un antidoto mayor, para curarte de venenos y protegerte durante un largo rato. -poison=veneno -You see a bottle of poison. Don't drink it!=Ves una botella de veneno ¡No te la tomes! -greater poison=veneno mayor -You see a greater poison. Don't drink it!=Ves un veneno mayor ¡No te lo tomes! -deadly poison=veneno mortal -You see a deadly poison. Don't drink it!=Ves un veneno mortal ¡No te lo tomes! -disease poison=veneno de enfermar -You see a disease poison. Be careful, it's wicked.=Ves un veneno de enfermar. Ten cuidado, es malvado. -mega poison=mega veneno -You see a mega poison. This is some wicked poison.=Ves un mega veneno. Éste es un veneno malvado. -twilight elixir=elixir del crepúsculo -You see a bottle of medicinal elixir.=Ves una botella de elixir medicinal. -minor potion=poción menor -You see a minor potion. It's not much but may save you in a tight spot.=Ves una poción menor. No es mucho pero puede salvarte en una situación difícil. -soup=sopa -You see a bowl of soup. Its contents fill you up.=Ves un plato de sopa. Su contenido te llena. -fish soup=sopa de pescado -You see a bowl of fish soup. Its contents fill you up.=Ves un plato de sopa de pescado. Su contenido te llena. -potion=poción -You see a potion. Take it to battle and you may survive longer.=Ves una poción. Llevatela a la batalla y puedes sobrevivir por más tiempo. -greater potion=poción mayor -You see a greater potion, invaluable in battle.=Ves una poción mayor, inestimable en la batalla. -mega potion=mega poción -You see a mega potion. It strengthens greatly.=Ves una mega poción. Te fortalece en gran medida. -love potion=poción de amor -You see a love potion. It looks powerful so take care to use it carefully!=Ves una poción de amor ¡Parece potente así que ten cuidado de usarla con precaución! -rhosyd=rhosyd -You see a beautiful and rare rhosyd flower.=Ves una bella y rara flor de rhosyd. -rose=rosa -You see a single red rose. It smells heavenly.=Ves una sola rosa roja. -lilia=lilia -You see a lilia, a single precious bloom.=Ves una lilia, una sola flor preciosa. -daisies=margaritas -You see a bunch of daisies. They are cheap and cheerful!=Ves un ramo de margaritas ¡Son baratas y alegran! -pansy=pensamiento -You see a pansy, so colorful and beautiful.=Ves un pensamiento, tan colorido y hermoso. -zantedeschia=zantedeschia -You see a zantedeschia, a delicate bloom with the sweetest fragrance.=Ves una zantedeschia, una flor delicada con la más dulce de las fragancias. -cherry=cereza -You see a cherry. Its tangy taste revives you a little.=Ves una cereza. Su sabor amargo te revive un poco. -butter=mantequilla -You see some butter. It is an ingredient or can be eaten if you like.=Ves un poco de mantequilla. Es un ingrediente o se puede comer si quieres. -cheese=queso -You see some cheese. Eat it and see what happens.=Ves un poco de queso. Cometelo y verás lo que pasa. -coconut=coco -You see a coconut. Luckily it's already cracked and you can drink from it.=Ves un coco. Afortunadamente ya está abierto y puedes beber de él. -icecream=helado -You see a wafer cone with vanilla ice cream and a chocolate flake.=Ves un cucurucho de helado de vainilla y virutas de chocolate. -pineapple=piña -You see a tropical pineapple.=Ves una piña tropical. -artichoke=alcachofa -You see an artichoke. It can be an ingredient, or you can eat it.=Ves una alcachofa. Puede ser un ingrediente, o te la puedes comer. -carrot=zanahoria -You see a carrot. It can be an ingredient, or you can eat it.=Ves una zanahoria. Puede ser un ingrediente, o te la puedes comer. -salad=ensalada -You see a salad.=Ves una ensalada. -tomato=tomate -You see a tomato, put it in a salad or eat it.=Ves un tomate, ponlo en la ensalada o cometelo. -apple=manzana -You see a nice shiny apple.=Ves una bonita y brillante manzana. -black apple=manzana negra -You see a black apple. It must be a gift from lil Johnnnny. He's the guy with black items.=Ves una manzana negra. Debe de ser un regalo del pequeño Johnnny -bread=pan -You see a loaf of bread. It is freshly baked and ready to eat.=Ves una barra de pan. Está recién horneada y lista para comer. -button mushroom=champiñón común -You see a button mushroom. It can be an ingredient, or you can eat it.=Ves un champiñón común. Puede ser un ingrediente, o te lo puedes comer. -porcini=seta calabaza -You see a porcini. It can be an ingredient, or you can eat it.=Ves una seta calabaza. Puede ser un ingrediente, o te la puedes comer. -spinach=espinacas -You see spinach. It is enriched with vitamins.=Ves espinacas. Está enriquecida con vitaminas. -courgette=calabacín -You see a courgette. It can be an ingredient, or you can eat it.=Ves un calabacín. Puede ser un ingrediente, o te lo puedes comer. -collard=col -You see a collard. It's rare and strangely magical.=Ves una col. Es rara y extrañamente mágica. -onion=cebolla -You see an onion.=Ves una cebolla. -garlic=ajo -You see some fresh garlic.=Ves un poco de ajo fresco. -cauliflower=coliflor -You see a fresh head of cauliflower.=Ves la cabeza de una fresca coliflor. -broccoli=brocoli -You see a bunch of broccoli.=Ves un manojo de brocoli. -leek=puerro -You see a fresh leek.=Ves un puerro fresco. -perch=perca -You see a perch.=Ves una perca. -mackerel=caballa -You see a mackerel.=Ves una caballa. -roach=rutilo -You see a roach.=Ves un rutilo -char=salvelinus -You see a char, with a fleshy pink belly.=Ves un salvelinus, con un vientre carnoso de color rosa. -clownfish=pez payaso -You see a striped clownfish.=Ves un pez payaso a rayas. -surgeonfish=pez cirujano -You see a bright blue surgeonfish.=Ves un pez cirujano de color azul brillante. -red lionfish=pterois rojo -You see a venomous coral reef red lionfish.=Ves un pterois rojo venenoso de los arrecifes de coral -egg=huevo -You see an egg.=Ves un huevo. -chicken=pollo -You see a tasty leg of chicken.=Ves un sabroso muslo de pollo. -meat=carne -You see a piece of meat.=Ves un pedazo de carne. -trout=trucha -You see a trout.=Ves una trucha. -cod=bacalao -You see a cod.=Ves un bacalao. -ham=jamón -You see a juicy ham.=Ves un jugoso jamón. -toadstool=seta venenosa -You see a toadstool. Beware!=Ves una seta venenosa ¡Ten cuidado! -fairy cake=fairy cake -You see a luscious fairy cake with icing and sprinkles on top.=Ves un delicioso fairy cake con cobertura y fideos de chocolate por encima. -crepes suzette=crepe suzette -You see a crepes suzette topped with hot chocolate sauce.=Ves una crepe suzette cubierta con salsa de chocolate caliente. -sandwich=sandwich -You see a ham and cheese sandwich.=Ves un sandwich de jamón y queso. -pie=pastel -You see a meat and vegetable pie.=Ves un pastel de carne y verduras. -fish pie=empanada de pescado -You see a fish and leek pie. It's not too stodgy so you can eat it faster than a meat pie.=Ves una empanada de pescado y puerros. No es demasiado pesada así que te la puedes comer más rápido que un pastel de carne. -apple pie=tarta de manzana -You see a delicious apple pie, made with only genuine ingredients.=Ves una deliciosa tarta de manzana, elaborada sólo con los ingredientes genuinos. -cherry pie=tarta de cereza -You see a delicious cherry pie, made with only genuine ingredients.=Ves una tarta de cereza deliciosa, elaborada sólo con los ingredientes genuinos. -pizza=pizza -You see a tasty pizza, and hope they put extra cheese on it.=Ves una sabrosa pizza, y esperas que le hayan puesto extra de queso por encima. -easter egg=huevo de pascua -You see a lovely chocolate easter egg. It's huge!=Ves un encantador huevo de pascua ¡Es enorme! -small easter egg=huevo de pascua pequeño -You see a small chocolate easter egg. Yummy!=Ves un pequeño huevo de pascua de chocolate ¡Delicioso! -chocolate bar=barrita de chocolate -You see an absolutely yummy chocolate bar.=Ves una barrita de chocolate absolutamente deliciosa. -tuna sandwich=sandwich de atún -You see what looks like tuna sandwich.=Ves lo que parece ser un sandwich de atún. -sausage=salchicha -You see what looks like a tasty sausage.=Ves lo que parece ser una sabrosa salchicha. -cheese sausage=salchicha de queso -You see what looks like a tasty cheese sausage.=Ves lo que parece ser una sabrosa salchicha de queso. -hotdog=perrito caliente -You see what looks like a tasty hotdog on a bun.=Ves lo que parece ser un sabroso perrito caliente en un bollo. -cheeseydog=perrito caliente con queso -You see what looks like a tasty cheeseydog on a bun.=Ves lo que parece ser un sabroso perrito caliente con queso en un bollo. -vanilla shake=batido de vainilla -You see a yummy vanilla shake. Absolutely NO calories.=Ves un sabroso batido de vainilla. Absolutamente SIN calorías. -chocolate shake=batido de chocolate -You see a scrumptious chocolate shake. Absolutely NO calories.=Ves un batido de chocolate para chuparse los dedos. Absolutamente SIN calorías. -honey=miel -You see a jar of sticky sweet honey. It's the clear runny kind.=Ves una jarra de miel dulce y pegajosa. Es de tipo fluído y claro. -sugar=azúcar -You see a sack of raw sugar.=Ves un saco de azúcar no refinado. -licorice=regaliz -You see some delicious licorice. Your mouth waters.=Ves un poco de delicioso regaliz. Se te hace la boca agua. -grilled steak=filete a la parrilla -You see a fresh grilled steak. It smells awesome and is really juicy.=Ves un filete a la parrilla recién hecho. Huele fenomenal y está realmente jugoso. -leather helmet=casco de cuero -You see a leather helmet, it seems more like a hat than a defensive item.=Ves un casco de cuero, parece más un sombrero que un elemento defensivo. -trophy helmet=casco trofeo -robins hat=sombrero de robin -You see a robins hat. It's cute but that's about it.=Ves un sombrero de robin. Es gracioso pero eso es todo. -studded helmet=casco tachonado -You see a studded helmet, it should protect your head against cuts but perhaps not hard blows.=Ves un casco tachonado, debería de protegerte la cabeza de cortes pero quizá no de golpes fuertes. -chain helmet=casco de cota de malla -You see a solid chain helmet, it will be excellent protection for your head.=Ves un sólido casco de cota de malla, será una excelente protección para la cabeza. -viking helmet=casco de vikingo -You see a viking helmet. The previous owner wore it to many battles.=Ves un casco de vikingo. Su anterior dueño lo llevó consigo a muchas batallas. -aventail=almófar -You see a aventail. It's a clever design.=Ves un almófar. Es un diseño ingenioso. -red helmet=casco rojo -You see a red helmet, it reminds you of a jester's hat but you can be sure it has better protection than that.=Ves un casco rojo, te recuerda al sombrero de un bufón pero puedes estar seguro de que te proporcionará una protección mejor que esa. -blue helmet=casco azul -You see a brand new blue helmet.=Ves un casco azul nuevecito. -golden helmet=casco de oro -You see a heavy golden helmet. The thick metal should protect you against almost anything.=Ves un pesado casco de oro. El grueso metal debería de protegerte contra casi todo. -shadow helmet=casco de sombra -You see a heavy shadow helmet. Instead of shining, it darkly gleams.=Ves un pesado casco de sombra. En lugar de brillar, emite destellos oscuros. -horned golden helmet=casco de oro con cuernos -You see a horned golden helmet.=Ves un casco de oro con cuernos. -chaos helmet=casco del caos -You see a chaos helmet.=Ves un casco del caos. -mainio helmet=casco de mainio -You see a strange mainio helmet. You can barely see when wearing it, but it protects your head very well.=Ves un extraño casco de mainio. Apenas puedes ver cuando lo llevas puesto, pero te protege muy bien la cabeza. -magic chain helmet=casco de cota de malla mágico -You see a magic chain helmet.=Ves un casco de cota de malla mágico. -liberty helmet=casco de la libertad -You see the liberty helmet. It's light and strong, you feel free wearing it.=Ves el casco de la libertad. Es ligero y resistente, te sientes libre al llevarlo puesto. -black helmet=casco negro -You see a black helmet.=Ves un casco negro -mithril helmet=casco de mithril -You see a mithril helmet.=Ves un casco de mithril. -arandula=arandula -You see a arandula. It has medicinal properties, if the right person uses it.=Ves una arandula. Tiene propiedades medicinales, si es usada por la persona adecuada. -kokuda=kokuda -You see a kokuda. It has medicinal properties, if the right person uses it.=Ves una kokuda. Tiene propiedades medicinales, si es usada por la persona adecuada. -kekik=kekik -You see a kekik. It is a herb which can have healing properties.=Ves una kekik. Es una hierba que puede tener propiedades curativas. -mandragora=mandrágora -You see a root of mandragora. It has medicinal properties, if the right person uses it.=Ves una raíz de mandrágora. Tiene propiedades medicinales, si es usada por la persona adecuada. -sclaria=sclaria -You see the herb sclaria. The smell is calming.=Ves la hierba sclaria. Su olor es calmante. -emerald=esmeralda -You see an emerald, a precious gemstone.=Ves una esmeralda, una gema preciosa. -sapphire=zafiro -You see a sapphire, a precious gemstone.=Ves un zafiro, una gema preciosa. -carbuncle=carbúnculo -You see a carbuncle, a precious gemstone related to the garnet.=Ves un carbúnculo, una gema preciosa relacionada con el granate. -obsidian=obsidiana -You see a piece of obsidian, it's known as volcanic glass because it forms when lava cools rapidly. When used as a gemstone it may look jet black or silvery grey, depending on how it is cut.=Ves una pieza de obsidiana, es conocida como vidrio volcánico porque se forma cuando la lava se enfría rápidamente. Cuando se usa como gema puede parecer negro azabache o gris plateado, dependiendo de cómo se corte. -diamond=diamante -You see a diamond, not just a beautiful gem but also the hardest naturally occurring material .=Ves un diamante, no es sólo una hermosa gema sino también el material más duro de origen natural. -black pearl=perla negra -You see a black pearl, so dark and shiny.=Ves una perla negra, tan oscura y brillante. -dungeon silver key=llave de plata de la mazmorra -You see a silver dungeon key. Use it only if you are brave.=Ves una llave de plata de la mazmorra. Usala sólo si eres valiente. -lich gold key=llave de oro del lich -You see a golden lich key. It will open the door to terrible things.=Ves una llave de oro del lick. Abrirá la puerta a cosas terribles. -minotaur key=llave del minotauro -You see a fancy bronze key. It smells faintly of minotaur, perhaps one dropped it?=Ves una lujosa llave de bronce. Huele ligeramente a minotauro, ¿quizá se le cayó a uno? -nalwor bank key=llave del banco de nalwor -You see a key to the customer room of Nalwor Bank.=Ves una llave de la sala de clientes del Banco de Nalwork. -kotoch prison key=llave de la prisión de kotoch -You see an ancient copper key, enscribed with something in Orcish.=Ves una antigua llave de cobre, grabada con algo en Orco. -kanmararn prison key=llave de la prisión de kanmararn. -You see a key which matches the locks in the Kanmararn prison.=Ves una llave que coincide con las cerraduras de la prisión de Kanmararn. -sedah gate key=llave de la puerta de sedah -You see a key for the gate to the imperial city of Sedah.=Ves una llave que abre la puerta que lleva a la ciudad imperial de Sedah. -pet sanctuary key=llave del santuario de mascotas -You see a key for the pet sanctuary in Ados.=Ves una llave que abre el santuario de mascotas en Ados. -house key=llave de la casa -You should not see this description=No deberías de poder ver esta descripción -master key=llave maestra fado hotel key 001=llave 001 del hotel de fado You see a key to room #001 in the Fado Hotel.=Ves una llave de la habitación #001 del hotel de Fado. fado hotel key 002=llave 002 del hotel de fado @@ -540,446 +2897,45 @@ fado hotel key 007=llave 007 del hotel de fado You see a key to room #007 in the Fado Hotel.=Ves una llave de la habitación #007 del hotel de Fado. fado hotel key 008=llave 008 del hotel de fado You see a key to room #008 in the Fado Hotel.=Ves una llave de la habitación #008 del hotel de Fado. -small key=llave pequeña -You see the key for Zara's row house in Ados.=Ves la llave de la row house de Zara en Ados. -leather legs=quijotes de cuero -You see a pair of leather legs. They look new but they're poor quality.=Ves un par de quijotes de cuero. Parecen nuevos pero son de mala calidad. -studded legs=quijotes tachonados -You see a pair of blood stained studded legs. You're not sure if the blood came from their last owner ... or his enemies.=Ves un par de quijotes tachonados manchados de sangre. No estás seguro de si la sangre es de su último dueño ... o de sus enemigos. -chain legs=quijotes de cota de malla -You see a pair of well made chain legs. They should protect you against any attacks aimed low.=Ves un par de quijotes de cota de malla bien elaborados. Deberían de protegerte frente a cualquier ataque bajo. -red legs=quijotes rojos -A pair of red legs are in your grasp.=Un par de quijotes rojos están a tu alcance. -elvish legs=quijotes élficos -You see a pair of elvish legs.=Ves un par de quijotes élficos. -blue legs=quijotes azules -You see a pair of shiny new blue legs.=Ves un par de quijotes azules nuevos y brillantes. -golden legs=quijotes de oro -You see a pair of shining, strong golden legs.=Ves un par de quijotes de oro, resistentes y relucientes. -shadow legs=quijotes de sombra -You see a pair of shadow legs. The weight of them is incredible.=Ves un par de quijotes de sombra. Su peso es increíble. -mainio legs=quijotes de mainio -You see a pair of odd looking mainio legs. They are somewhat startling but they give you a good protection.=Ves un par de quijotes de mainio de extraño aspecto. Son de algún modo sorprendentes pero te proporcionan una buena proteccion. -chaos legs=quijotes del caos -You see a pair of chaos legs. The strange colors confuse you.=Ves un par de quijotes del caos. Sus extraños colores te confunden. -xeno legs=quijotes de xeno -You see a pair of xeno legs. They're very strong and look brand new.=Ves un par de quijotes de xeno. Son muy resistentes y parecen nuevecitos. -jewelled legs=quijotes enjoyados -You see a beautiful pair of jewelled legs. A large red gem glints at you and they're encrusted all over with sapphires.=Ves un hermoso par de quijotes enjoyados. Una gema roja grande emite reflejos hacia ti y tienen incrustaciones de zafiro por todas partes. -black legs=quijotes negros -You see a pair of black legs.=Ves un par de quijotes negros. -mithril legs=quijotes de mithril -You see a pair of mithril legs. They will serve you well.=Ves un par de quijotes de mithril. Te servirán de mucho. -bulb=bulbo -You see a bulb. It can be planted anywhere, but it will only thrive on fertile ground.=Ves un bulbo. Puede ser plantado en cualquier sitio, pero sólo prosperará en un terreno fértil. -seed=semilla -You see a seed. It can be planted anywhere, but it will only thrive on fertile ground.=Ves una semilla. Puede ser plantada en cualquier sitio, pero sólo prosperará en un terreno fértil. -giant heart=corazón gigante -You see a giant heart. This proves that a giant was defeated, and has magical properties that you are not sure of.=Ves un corazón gigante. Ésto demuestra que un gigante fue derrotado, y tiene propiedades mágicas de las que no estás seguro. -dice=dados -You see a set of dice. May lady luck be with you.=Ves un grupo de dados. Que la suerte te acompañe. -cards=cartas -You see a deck of cards. Shuffle them well, to get a winning hand.=Ves una baraja de cartas. Barajealas bien, para ganar la mano. -teddy=peluche -You see a teddy bear. Its soft and cuddly, and children love them.=Ves un oso de peluche. Es suave y tierno, y a los niños les encanta. -vampirette entrails=entrañas de vampiresa -You see some vampirette entrails. These guts proves that a vampirette was slain.=Ves unas entrañas de vampiresa. Estas vísceras demuestran que una vampiresa fue asesinada. -bat entrails=entrañas de murciégalo -You see some bat entrails, full of the blood of a living creature.=Ves unas entrañas de murciégalo, llenas de sangre de algún ser vivo. -suntan cream=crema bronceadora -marbles=canicas -You see some marbles. Boys will be boys.=Ves unas canicas. Son cosas de niños. -canned tuna=atún en lata -You see tuna in a can. Too bad you don't have a can opener.=Ves atún en una lata. Lástima que no tengas un abrelatas. -snowglobe=esfera de nieve -You see a snowglobe. You are SUCH a tourist!=Ves una esfera de nieve ¡Estás hecho un turista! -bobbin=canilla -You see a bobbin for a sewing machine.=Ves una canilla para una máquina de coser. -snowball=bola de nieve -You see snowballs. You can throw them at your friends or put it in your bag to keep your fish fresh. But be careful, there could be little stones in it.=Ves bolas de nieve. Puedes tirarselas a tus amigos o ponerlas en tu bolsa para conservar el pescado fresco. Pero ten cuidado, podría haber piedrecitas dentro de ella. -vomit=vómito -You see a pile of vomit. Gross!=Ves un montón de vómito ¡Qué desagradable! dart=dardo You see a dart. You'll need to be close before you can use it as a missile. Stand square on, not diagonally, for the best results.=Ves un dardo. Necesitarás estar cerca antes de que lo puedas usar como proyectil. Permanece recto, no inclinado, para obtener los mejores resultados. power dart=dardo de potencia You see a power dart. You'll need to be close before you can use it as a missile. Stand square on, not diagonally, for the best results.=Ves un dardo de potencia. Necesitarás estar cerca antes de que lo puedas usar como proyectil. Permanece recto, no inclinado, para obtener los mejores resultados. -shuriken=shuriken -You see a shuriken. You'll need to be close before you can use it as a missile. Stand square on, not diagonally, for the best results.=Ves un shuriken. Necesitarás estar cerca antes de que lo puedas usar como proyectil. Permanece recto, no inclinado, para obtener los mejores resultados. -fire shuriken=shuriken de fuego -You see a fire shuriken. Throwing this flaming missile unleashes fiendish power.=Ves un shuriken de fuego. El lanzamiento de este proyectil en llamas desata un poder diabólico. -wooden spear=lanza de madera -You see a wooden spear. You can attack at a distance with this primitive weapon. Stand square on, not diagonally, for the best results.=Ves una lanza de madera. Puedes atacar a distancia con este arma primitiva. Permanece recto, no inclinado, para obtener los mejores resultados. -money=dinero -wooden bow=arco de madera -You see a wooden bow. With this in one hand, and wooden arrows in the other, you may be able to attack from a distance.=Ves un arco de madera. Con esto en una mano, y flechas de madera en la otra, puedes ser capaz de atacar a distancia. -longbow=arco largo -You see a longbow. Equipped with this, and a supply of arrows, you will have an effective ranged attack.=Ves un arco largo. Equipado con ésto, y con un surtido de flechas, conseguirás un efectivo ataque a distancia. -composite bow=arco compuesto -You see a composite bow. It's better strung than most bows, for excellent ranged attack.=Ves un arco compuesto. Está mejor tensado que la mayoría de arcos, para proporcionar un excelente ataque a distancia. -crossbow=ballesta -You see a crossbow. The attack is high but it has a very short range, what do you prefer?=Ves una ballesta. Su ataque es elevado pero cubre una distancia muy corta, ¿qué prefieres? -hunter crossbow=ballesta de cazador -You see a hunter crossbow. You must decide if the high attack is worth the short range it has.=Ves una ballesta de cazador. Debes decidir si su elevado ataque compensa la corta distancia que cubre. -mithril bow=arco de mithril -You see a light and strong mithril bow. Its impeccable workmanship affords incredible accuracy.=Ves un arco de mithril ligero y resistente. Su impecable elaboración permite una precisión increíble. angel figurine=figurilla de ángel You see a sparkling angel figurine.=Ves una figurilla de ángel resplandeciente. -skull ring=anillo de calavera -You see a skull ring.=Ves un anillo de calavera. -amulet=amuleto ankh=anj You see a mysterious ankh. You don't know what it is for.=Ves un misterioso anj. No sabes para qué sirve. -wood=madera -You see some wood, fallen from a nearby tree.=Ves un poco de madera, caída de algún árbol cercano. -sugar cane=caña de azúcar -You see a sugar cane. You can grind it for raw sugar.=Ves una caña de azúcar. Puedes molerla para obtener azúcar no refinado. -grain=trigo -You see a sheaf of grain.=Ves una gavilla de trigo. -flour=harina -iron ore=mineral de hierro -coal=carbón -You see a black and shiny piece of stone coal. It stayed in the ground for million of years.=Ves un pedazo de piedra de carbón negro y brillante. Permaneció enterrado durante millones de años. -wool=lana -You see wool. It was recently shorn.=Ves lana. Fue esquilada recientemente. -yarn=hilo de lana -You see a ball of yarn. It was spun with care.=Ves un ovillo de lana. Fue hilado con sumo cuidado. -cloth=tela -You see a roll of cloth. It is of first-class quality.=Ves un rollo de tela. Es de primera calidad. -gold nugget=pepita de oro -You see a gold nugget. Its rich golden color shines, and it may be useful.=Ves una pepita de oro. Su vivo color dorado brilla,y puede resultar útil. -mithril nugget=pepita de mithril -You see a mithril nugget. Its pale silvery color shines, and it may be useful.=Ves una pepita de mithril. Su pálido color plateado brilla, y puede resultar útil. iron=hierro -gold bar=barra de oro -You see a gold bar. It will make beautiful jewelry, or fetch a fine price.=Ves una barra de oro. Servirá para hacer hermosas joyas, o se pagará un buen precio por ella. -mithril bar=barra de mithril -You see a mithril bar. It can be made into excellent armor, or fetch a fine price.=Ves una barra de mithril. Se puede elaborar una armadura excelente con ella, o se puede pagar un buen precio por ella. -golden egg=huevo de oro -You see a valuable golden egg.=Ves un valioso huevo de oro. -spotted egg=huevo con manchas -You see a spotted egg. They are said to be delicious.=Ves un huevo con manchas. Se dice que son deliciosos. reptile egg=huevo de reptil You see a valuable reptile egg.=Ves un valioso huevo de reptil. -silk gland=glándula de seda -You see a silk gland from a giant spider. Its contents are extremely valuable.=Ves una glándula de seda de una araña gigante. Su contenido es extremadamente valioso. -silk thread=hilo de seda -You see a spool of silk thread, spun from the glands of a giant spider..=Ves un carrete de hilo de seda, hilado de las glándulas de una araña gigante.. -oil=aceite -You see a small can of oil.=Ves una pequeña lata de aceite. -mithril thread=hilo de mithril -You see a spool of mithril thread, made from silk fused with mithril.=Ves un carrete de hilo de mithril, elaborado a partir de seda fundida con mithril. -mithril fabric=tela de mithril -You see a couple yards of mithril fabric. It's very sparkly!=Ves un par de metros de tela de mithril ¡Emite muchos destellos! -horse hair=pelo de caballo -You see some horse hairs.=Ves un poco de pelos de caballo. -beeswax=cera de abeja -You see a piece of beeswax.=Ves un pedazo de cera de abeja. -candle=vela -You see a candle in a candle stick.=Ves una vela en un candelero. -wedding ring=anillo de boda -engagement ring=anillo de compromiso -You see a diamond engagement ring, a symbol of love (or a symbol of wealth?)=Ves un anillo de compromiso de diamante, un símbolo de amor (¿o un símbolo de riqueza?) -emerald ring=anillo de esmeralda -You see an emerald ring, known as ring of life. Wear it, and you risk less from death.=Ves un anillo de esmeralda, conocido como el anillo de la vida. Llevalo, y arriesgarás menos al morir. -summon scroll=pergamino de invocación -nalwor city scroll=pergamino de la ciudad de nalwor -You see a nalwor city scroll, it will take you back to nalwor city.=Ves un pergamino de la ciudad de nalwor, te llevará de regreso a la ciudad de nalwor. -ados city scroll=pergamino de la ciudad de ados -You see a ados city scroll, it will take you back to ados city.=Ves un pergamino de la ciudad de ados, te llevará de regreso a la ciudad de ados. -fado city scroll=pergamino de la ciudad de fado -You see a fado city scroll, it will take you back to fado city.=Ves un pergamino de la ciudad de fado, te llevará de regreso a la ciudad de fado. -kirdneh city scroll=pergamino de la ciudad de kirdneh -You see a kirdneh city scroll, it will take you back to kirdneh city.=Ves un pergamino de la ciudad de kirdneh, te llevará de regreso a la ciudad de kirdneh. -kalavan city scroll=pergamino de la ciudad de kalavan -You see a kalavan city scroll, it will take you back to kalavan city.=Ves un pergamino de la ciudad de kalavan, te llevará de regreso a la ciudad de kalavan. -marked scroll=pergamino marcado -invitation scroll=pergamino de invitación -home scroll=pergamino del hogar -You see a home scroll, it will take you back to semos city.=Ves un pergamino del hogar, te llevará de regreso a la ciudad de semos. -empty scroll=pergamino en blanco -You see an empty scroll. If used correctly it will return you to where you marked it at.=Ves un pergamino en blanco. Usado correctamente te llevará de regreso a donde lo marcaste. -rainbow beans=haba del arco iris -You see a small pile of rainbow beans. One says something odd.=Ves un pequeño montón de habas del arco iris. Una dice algo raro. -balloon=globo -You see a bright red balloon. You can use it to float high up into the clouds.=Ves un globo de color rojo brillante. Puedes usarlo para flotar en lo alto de las nubes. -twilight moss=musgo del crepúsculo -You see a strange looking piece of moss. It looks like it could have hallucinogenic properties.=Ves un pedazo de musgo de aspecto extraño. Parece que podría tener propiedades alucinógenas. -buckler=broquel -You see a small buckler. It may protect you against swords and maces but against stronger weapons you do not trust it.=Ves un pequeño broquel. Puede protegerte frente a espadas y mazas pero frente a armas más potentes no te confíes. -wooden shield=escudo de madera -You see a wooden shield. It is made of cheap wood and heavily used. However, it still seems to offer you some kind of basic protection.=Ves un escudo de madera. Está elaborado a partir de madera barata y ha sido muy utilizado. Sin embargo, todavía parece proporcionar algún tipo de protección básica. -studded shield=escudo tachonado. -You see a studded shield. It's a little dented but seems robust enough.=Ves un escudo tachonado. Está un poco abollado peroparece bastante robusto. -plate shield=escudo de placas de acero -You see a solid plate shield. It is in good condition, and might be worth a little.=Ves un sólido escudo de placas de acero. Está en buenas condiciones, y podría valer bastante. -lion shield=escudo de león -You see a valuable lion shield. It is heavy enough to defend you against strong attacks.=Ves un valioso escudo de león. Es lo bastante pesado como para defenderte frente a ataques potentes. -unicorn shield=escudo del unicornio -You see a unicorn shield. It's beautifully crafted and will give you excellent protection.=Ves un escudo del unicornio. Está bellamente elaborado y te proporcionará una protección excelente. -enhanced lion shield=escudo de león mejorado -An enhanced lion shield glints at you. It will defend you well.=Un escudo de león mejorado emite destellos hacia ti. Te defenderá bien. -skull shield=escudo de calavera -The face of a skull scowls at you from this fearsome skull shield.=La cara de una calavera te frunce el ceño desde este temible escudo de calavera -crown shield=escudo de la corona -You see a mighty crown shield. It is in perfect condition.=Ves un poderoso escudo de la corona. Esta en perfectas condiciones. -red shield=escudo rojo -You see a red shield. It is a stunning shade of scarlet.=Ves un escudo rojo. Es de un imponente tono escarlata. -blue shield=escudo azul -You see a blue shield. It feels light in your hand but is strong enough for excellent protection.=Ves un escudo azul. Notas en tu mano que es ligero pero es lo bastante resistente como para proporcionar una excelente protección. -elvish shield=escudo élfico -You see an elvish shield. It looks like one big tree leaf!=Ves un escudo élfico ¡Se parece a una gran hoja de árbol! -golden shield=escudo de oro -You see a golden shield, could it be the ultimate in defense?=Ves un escudo de oro, ¿podría ser lo último en defensa? -green dragon shield=escudo de dragón verde -You see an green dragon shield, scaly and strong.=Ves un escudo de dragón verde, escamoso y resistente. -shadow shield=escudo de sombra -You see a shadow shield. Rather than reflecting light, it seems to absorb it.=Ves un escudo de sombra. En lugar de reflejar la luz, parece absorberla. -mainio shield=escudo de mainio -You see a strange looking mainio shield. You have no idea what it's made of, but it seems to protect you well by distracting your enemies.=Ves un escudo de mainio de extraña apariencia. No tienes ni idea de lo que está hecho, pero parece que te protege bien al distraer a tus enemigos. -chaos shield=escudo del caos -You see a chaos shield. Its charming appearance will fool your enemies.=Ves un escudo del caos. Su encantadora apariencia engañará a tus enemigos. -magic plate shield=escudo mágico de placas de acero -You see a magic plate shield. Its has a magical quality surrounding it.=Ves un escudo mágico de placas de acero. Tiene una cualidad mágica que lo envuelve. -xeno shield=escudo de xeno -You see a lightweight but strong xeno shield, in the colors of the creature it was looted from.=Ves un escudo de xeno ligero pero resistente, de los mismos tonos que la criatura a la que se le saqueó. -black shield=escudo negro. -You see a black shield. Its darkness will surely protect you.=Ves un escudo negro. Su oscuridad seguro que te protegerá. -mithril shield=escudo de mithril -You see a mithril shield. You will most surely be protected.=Ves un escudo de mithril. Casi con toda seguridad estarás protegido. -lucky charm=amuleto de la suerte -You see a lucky charm. Some believe that it helps their karma in some way, but others think that is superstition.=Ves un amuleto de la suerte. Algunos creen que ayuda a su karma de algún modo, pero otros piensan que es una superstición. -mithril clasp=cierre de mithril -You see a clasp made from mithril, it would provide the fastening for a cloak.=Ves un cierre elaborado a partir de mithril, te proporcionaría la fijación para una capa. -mythical egg=huevo mítico -You see a mythical egg, but you do not know, what it is for.=Ves un huevo mítico, pero no sabes, para qué es. -magical eggshells=cáscaras de huevo mágicas -You see shards from a mythical egg. They have magical properties.=Ves fragmentos de un huevo mítico. Tienen propiedades mágicas. -golden gm token=ficha de dj de oro -silvery gm token=ficha de dj de plata -bronze gm token=ficha de dj de bronce -assassin dagger=daga de asesino -You see an assassin dagger, easy to pull out and use when needed.=Ves una daga de asesino, fácil de sacar y de usar cuando se necesite. -knife=cuchillo -You see a blunt knife which looks capable of little damage.=Ves un cuchillo desafilado que parece capaz de hacer poco daño. -night dagger=daga de la noche -You see a night dagger, to be used with stealth.=Ves una daga de la noche, para ser utilizada con sigilo. -obsidian knife=cuchillo de obsidiana -You see a knife whose blade is crafted to a lethal edge from finest obsidian.=Ves un cuchillo cuya hoja se ha trabajado hasta conseguir un filo letal a partir de la obsidiana más pura. -soul dagger=daga del alma -You see a soul dagger. It's so light and sharp that you can stab at a very fast rate.=Ves una daga del alma. Es tan ligera y afilada que puedes apuñalar a un ritmo muy rápido. -golden blade=cuchilla de oro -You see the rare and precious golden blade. It can steal life from those you stab with it.=Ves la rara y preciosa cuchilla de oro. Puede robar la vida a aquellos que apuñales con ella. -dagger=daga -You see a dagger, it is little more than decorative but you can jab pretty fast with it.=Ves una daga, es poco más que decorativa pero puedes punzar bastante rápido con ella. -dark dagger=daga oscura -You see a dark dagger. It's designed to be used with a fast motion.=Ves una daga oscura. Se ha diseñado para ser utilizada con un movimiento rápido. -hell dagger=daga del infierno -You see a hell dagger. Is the color that of the burning fires of Hell?=Ves una daga del infierno ¿Es ése el color de las llamas del infierno? -chaos dagger=daga del caos -You see a chaos dagger, it is very lightweight and sharp. It will heal you while hurting them.=Ves una daga del caos, es muy ligera y afilada. -short sword=espada corta -You see a well kept short sword which will be easy to carry as you travel.=Ves una espada corta bien conservada que será fácil de llevar cuando viajes. -r hand sword=espada de mano d -You see r hand sword. It must be used with a left hand sword.=Ves una espada de mano d. Debe usarse con una espada de mano izquierda. -l hand sword=espada de mano i -You see l hand sword. It must be used with right hand sword.=Ves una espada de mano i. Debe usarse con una espada de mano derecha. -sword=espada -You see a sharp sword which might even be worth a little.=Ves una espada afilada que podría incluso valer un poco. -imperator sword=espada imperator -You see a an imperator sword, crafted from you know not what.=Ves una espada imperator, elaborada a partir de ya sabes qué. -scimitar=cimitarra -This scimitar catches the light as you look at it, it will be a powerful weapon.=Esta cimitarra atrapa la luz mientras la miras, sera un arma poderosa. -katana=katana -You see a valuable katana, with a lethal looking blade.=Ves una valiosa katana, con una hoja de apariencia letal. -nihonto=nihonto -You see a samurai's nihonto sword with a razor sharp blade.=Ves una espada nihonto de samurai con una hoja tan afilada como la de una cuchilla. -buster=buster -You see a buster, with its distinctive, oversized blade.=Ves una espada buster, con su distintiva hoja sobredimensionada. -orc sword=espada de orco -You see an orc sword which has a heavy and deadly appearance.=Ves una espada de orco que tiene una apariencia pesada y mortal. -demon sword=espada de demonio -Demon skeletons return from hell with this demon sword.=Los esqueletos demonio regresan del infierno con esta espada de demonio. -vampire sword=espada de vampiro -You see the vampire sword, which will suck life from those you attack with it.=Ves la espada de vampiro, que absorberá la vida de aquellos a los que ataques con ella. -claymore=claymore -This claymore is very hard to lift as it is so heavy. It will be a good weapon if you can swing it.=Esta claymore es muy difícil de levantar ya que es muy pesada. -biting sword=espada mordaz -The point of this biting sword is chiseled to a lethal point. It will serve you well.=La punta de esta espada mordaz está cincelada hasta un punto letal. -fire sword=espada de fuego -Flames race along this fire sword as you wield it.=Las llamas corren a lo largo de esta espada de fuego mientras la esgrimes. -ice sword=espada de hielo -You see an ice sword, the blade is lethally cold to the touch, and you know it is one of the best weapons you'll find.=Ves una espada de hielo, la hoja es letalmente fría al tacto, y sabes que es una de las mejores armas que vas a encontrar. -chaos sword=espada del caos -The chaos sword. It hurts them but it heals you.=La espada del caos. Les hace daño a ellos pero a ti te cura. -broadsword=espada ancha -You see a mighty broadsword. Enemies will see their death.=Ves una poderosa espada ancha. Los enemigos verán su muerte. -golden orc sword=espada de oro de orco -You see a golden orc sword, with its heavy and deadly appearance it seems also valuable.=Ves una espada de oro de orco, con su pesada y mortal apariencia parece tan valiosa. -elvish sword=espada élfica -This an elvish sword. It looks like it is made from tree leaves!=Esta es una espada élfica ¡Parece que está hecha de hojas de árbol! -drow sword=espada drow -This is a genuine drow sword.=Esta es una genuina espada drow. -great sword=gran espada -This rare great sword is heavy and of good craftsmanship. It can only attack incredibly slowly.=Esta rara gran espada es pesada y de buena fabricación. -orcish sword=espada orca -This orcish sword is badly done and weighs a lot.=Esta espada orca está mal hecha y pesa un montón. -immortal sword=espada inmortal -This immortal sword is a gift of gods to the chosen one. It is heavy and of exquisite craftsmanship.=Esta espada inmortal es un regalo de los dioses al elegido. Es pesada y de una elaboración exquisita. -xeno sword=espada de xeno -You wield a xeno sword. Despite the great strength it is strangely light.=Tu esgrimes una espada de xeno. A pesar de su gran potencia es extrañamente ligera. -black sword=espada negra -You see a black sword. Its darkness will surely protect you.=Ves una espada negra. Su oscuridad seguro que te protegerá. -demon fire sword=espada de fuego de demonio -The demon fire sword, a horrible sword taken from hell's deepest dungeons.=La espada de fuego de demonio, una espada horrible sacada de las más profundas mazmorras del infierno. -arrow game token=ficha del juego de la flecha -You see a token, that when placed in the right spot could point you in the right direction.=Ves una ficha, que cuando se coloque en el lugar correcto podría señalarte la dirección correcta. -o board token=ficha del tablero o -You see an O token.=Ves una ficha O -x board token=ficha del tablero x -You see an X token.=Ves una ficha X -food mill=molino de alimentos -You see a food mill. It is the traditional tool to mill food.=Ves un molino de alimentos. Es la herramienta tradicional para moler alimentos. -sugar mill=molino de azúcar -You see a sugar mill. You can obtain sugar if you grind canes with it.=Ves un molino de azúcar. Puedes obtener azúcar si mueles cañas con él. -scroll eraser=pergamino borrador -You see a scroll eraser. Put a marked scroll in the opposite hand to clean the scroll.=Ves un pergamino borrador. Pon un pergamino marcado en la mano opuesta para limpiar el pergamino. -pestle and mortar=maza y mortero -You see a pestle and mortar. This is the traditional tool to pound herbs, food and other substances.=Ves una maza y un mortero. Esta es la herramienta tradicional para machacar hierbas, alimentos y otras sustancias. -gold pan=sartén del oro -You see a gold pan. It is the traditional tool to prospect for gold.=Ves una sartén del oro. Es la herramienta tradicional para buscar oro. -fishing rod=caña de pescar -You see a fishing rod. If you find a fishing spot you can catch fish with it.=Ves una caña de pescar. Si encuentra un lugar para pescar puedes capturar peces con ella. -magical scissors=tijeras mágicas. -You see a sparkling pair of magical scissors. Just think what they could cut!=Ves un par de tijeras mágicas centelleantes ¡Piensa sólo en lo que podrían cortar! -magical needle=aguja mágica -You see a magical needle with a very sharp point.=Ves una aguja mágica con una punta muy afilada. -pick=pico -You see a pick. It is a tool which helps you to get some coal.=Ves un pico. Es una herramienta que te ayuda a obtener algo de carbón. -albino elf swordsman=espadachín elfo albino -albino elf archer=arquero elfo albino -albino elf magician=mago elfo albino -albino elf queen=reina elfa albina -albino elf king=rey elfo albino -amazoness archer=arquera amazona -You see an amazoness archer. She handles her bow quite well and her arrows are very dangerous when she shoots them at you out of the forest!=Ves a una arquera amazona ¡Maneja su arco bastante bien y sus flechas son muy peligrosas cuando las dispara hacia ti desde el bosque! -amazoness hunter=cazadora amazona -You see an amazoness hunter. She creeps through the forest to hunt animals. But she doesn't like that you are in her hunting area!=Ves a una cazadora amazona ¡Se arrastra por el bosque para cazar animales ¡Pero no le gusta que estés en su zona de caza! -amazoness coastguard=guardacostas amazona -amazoness archer commander=arquera comandante amazona -amazoness elite coastguard=guardacostas de élite amazona -amazoness bodyguard=guardaespaldas amazona -amazoness coastguard mistress=guardacostas Maestre amazona -amazoness commander=comandante amazona -amazoness vigilance=vigilante amazona -You see an amazoness vigilance. Her senses are well tuned, so you have to be very silent when you don't want her to hear you!=Ves a una vigilante amazona ¡Sus sentidos están muy desarrollados, así que tienes que ser muy silencioso cuando no quieras que te oiga! -amazoness imperator=imperator amazona -deer=ciervo -You see a defenseless deer, do not have fear of it.=Ves un ciervo indefenso, no le tengas miedo. -crab=cangrejo -You see a little crab that comes from the beach, be careful of its pincers.=Ves un pequeño cangrejo que viene de la playa, ten cuidado con sus pinzas. + +# creature speach not yet supported by parser + click-clack-click=clic-clac-clic -bat=murciégalo -You see a bat, it will seek your blood, don't be distracted...=Ves un murciégalo, buscará tu sangre, no te distraigas... -chicken=pollo -You see a chicken, isn't it nice?.=Ves un pollo, ¿a que es bonito? cluck cluck cluck=cloc cloc cloc cluck cluck=cloc cloc -penguin=pingüino -You see a penguin, smart and silly.=Ves un pingüino, bobo y elegante Oou! Oou!=Oou! Oou! *flap* *flap*=*flap* *flap* -pigeon=paloma -You see a pigeon, it loves the little seeds.=Ves una paloma, le encantan las semillas pequeñas. -monkey=mono -You see a monkey, would you like to offer it a banana?=Ves un mono, ¿Te gustaría ofrecerle un plátano? Aooooooouuhhh!!=Aooooooouuhhh!! -boar=verraco -You see a boar, they are pretty wild!=Ves un verraco, ¡son bastante salvajes! Oink!!=Oink!! -wolf=lobo -You see a wolf. It is wild and very hungry!=Ves un lobo ¡Es salvaje y está muy hambriento! Aooooooouuhhh!!=Aooooooouuhhh!! -big bad wolf=lobo grande y malo -You see a big bad wolf. It is really wild, hungry and really dangerous!=Ves un lobo grande y malo ¡Es verdaderamente salvaje, está hambriento y es realmente peligroso! Aooooooouuhhh!!=Aooooooouuhhh!! -fox=zorro -You see a fox, it becomes extinct!=Ves un zorro, ¡se esta extinguiendo! -caiman=caimán -You see a caiman, which is hidden in the water.=Ves un caimán, que está escondido en el agua. Grrr Grrr!=Grrr Grrr! -babybear=cachorro de oso -You see a babybear, isn't it sweet?=Ves un cachorro de oso, ¿a que es dulce? Roar!=Roar! -panda=panda -You see a panda which comes from an exotic land.=ves un panda que viene de una tierra exótica. Roar!=Roar! -snake=serpiente -You see a snake, it came from the forest to poison you!=Ves una serpiente, ¡vino desde el bosque a envenenarte! SsSssSsssssss!=SsSssSsssssss! -bear=oso -You see a bear. You are lucky and found it awake.=Ves un oso. Has tenido suerte y lo has encontrado despierto. Roar!=Roar! -lion=león -You see a lion, dangerous and hungry...=Ves un león, peligroso y hambriento... Grooooaaaarrrr!=Grooooaaaarrrr! -elephant=elephant -You see an elephant, a huge animal with lots of ham and meat. Are you sure that you want to attack it?=Ves un elefante, un animal enorme con un montón de jamón y de carne. WHOOOOEOEOEZZZZZZ!!!=WHOOOOEOEOEZZZZZZ!!! -tiger=tigre -You see a tiger, which likes deer and human meat.=Ves un tigre, al que le gustan los ciervos y la carne humana. Miiiiaaauuu!=Miiiiaaauuu! -killer bat=murciégalo asesino -You see a killer bat which will suck your blood!=¡Ves un murciégalo asesino que te chupará la sangre! -green slime=baba verde -You see a green slime, sticky and shiny.=Ves una baba verde, pegajosa y brillante. -black bear=oso negro -You see a strong black bear which hibernates in winter.=Ves un fuerte oso negro que hiberna en invierno. ROOOOAAAAR!=ROOOOAAAAR! -brown slime=baba marrón -You see a brown slime, sticky and hungry.=Ves una baba marrón, pegajosa y hambrienta. -spider=araña -You see a spider. It makes a giant spider web to trap you!=Ves una araña ¡Está haciendo una telaraña gigante para atraparte! -black slime=baba negra -You see a black slime, with a sticky trail behind it.=Ves una baba negra, que va dejando un rastro pegajoso. -poisonous spider=araña venenosa -You see a poisonous spider. Be careful! Its poison is very strong! It will make you very weak and can probably kill you!=Ves una araña venenosa ¡Ten cuidado! ¡Su veneno es muy fuerte! ¡Te debilitará y probablemente puede matarte! -twilight slime=baba del crepúsculo -You see a twilight slime, so strange and beautiful. Why is it coming towards you? Runnnnnnnnn!=Ves una baba del crepúsculo, tan extraña y bella ¿Por qué se está acercando hacia ti? ¡Correeeeeeeee! -young beholder=joven beholder -You see a young beholder. His huge eye will find you everywhere!=Ves un joven beholder ¡Su ojo gigante te encontrará en cualquier lugar! Defend yourself! I can see you!=¡Defiendete! ¡Puedo verte! -beholder=beholder -You see a beholder which can see you very well with its huge eye...=Ves un beholder que puede verte muy bien con su enorme ojo... -elder beholder=beholder anciano -You see an elder beholder, his nasty eye will scare you.=Ves un beholder anciano, su desagradable ojo te asustará. -chaos soldier=soldado del caos -chaos warrior=guerrero del caos Defy order! Seek chaos!=¡Desafía al orden! ¡Busca el caos! -chaos commander=comandante del caos Chaos in the wrath!=¡El caos en la ira! -chaos sorcerer=hechicero del caos I will summon chaos and fury upon you!=¡Invocaré el caos y la furia sobre ti! -chaos dragonrider=jinete de dragón del caos -You see a chaos dragonrider. He is very well trained in dragon riding, but he can also fight well on the ground.=Ves a un jinete de dragón del caos. Está muy bien entrenado en montar a dragón, pero también puede luchar bien en el suelo. -chaos lord=señor del caos Chaos will rule!=¡El caos reinará! -chaos overlord=gran señor del caos -You see a chaos overlord. It is he who directs the creation of evil plans and gives orders to all chaotic creatures which exist.=Ves a un gran señor del caos. Es él quien dirige la creación de los planes malvados y da las órdenes a todas las criaturas que existen. I am become death, destroyer of worlds.=Me he convertido en la muerte, en el destructor de mundos. -chaos pawn=peón del caos -You see a pawn for a game of chess. He will drop nothing if you kill him.=Ves un peón para una partida de ajedrez. No dejará caer nada si lo matas. -chaos rook=torre del caos -You see a rook for a game of chess. He will drop nothing if you kill him.=Ves una torre para una partida de ajedrez. No dejará caer nada si la matas. -chaos knight=caballo del caos -You see a knight for a game of chess. He will drop nothing if you kill him.=Ves un caballo para una partida de ajedrez. No dejará caer nada si lo matas. -chaos bishop=alfil del caos -You see a bishop for a game of chess. He will not reward you if you kill him.=Ves un alfil para una partida de ajedrez. No te recompensará con nada si lo matas. -chaos king=rey del caos -You see a king of a game of chess. He will reward nothing if you kill him.=Ves un rey para una partida de ajedrez. No te recompensará con nada si lo matas. -chaos queen=reina del caos -You see a queen piece for a game of chess. She will reward nothing if you kill her.=Ves una pieza reina para una partida de ajedrez. No te recompensará con nada si la matas. pirate=pirata -You see a bandit. Together with his partners he commits many different crimes to get much money or some rare and valuable items.=Ves a un bandido. Junto a sus compañeros comete muy diversos crímenes para conseguir mucho dinero o algunos objetos raros y valiosos. Aye, my hearties!=¡Sí, mis valientes! Yohoho and a boddle of rum!=¡Yohoho y una bodella de rón! pirate wench=chica pirata @@ -996,606 +2952,146 @@ pirate leader=líder de los piratas Be quiet or I will lock you on my ship, just like I locked up the girl Susi!=¡Estate quieto o te encerraré en mi barco, como encerré a esa niña llamada Susi! You remind me of the Ados girl we stole to clean our ship!=¡Me recuerdas a la niña de Ados que robamos para que limpie nuestro barco! You'd make a nice playmate for the Susi girl on our ship...=Serías un buen compañero de juegos para la niña llamada Susi en nuestro barco... -child dark elf=niño elfo oscuro -You see a child dark elf. You should know that dark elves are already quite strong and intelligent when they are children. Don't think you can fight him so easily!=Ves a un niño elfo oscuro. Deberías saber que los elfos oscuros son ya bastante fuertes e inteligentes cuando son niños ¡No creas que puedes enfrentarte a él tan fácilmente! -dark elf archer=arquero elfo oscuro -dark elf=elfo oscuro -dark elf elite archer=arquero de élite elfo oscuro -dark elf captain=capitán elfo oscuro Think not to defy the dark elves!=¡No pienses en desafiar a los elfos oscuros! -dark elf knight=caballero elfo oscuro -dark elf general=general elfo oscuro We will never return to the light!=¡Nunca regresaremos a la luz! -dark elf wizard=mago elfo oscuro The Spider Queen will avenge me!=¡La Reina Araña me vengará! -dark elf viceroy=virrey elfo oscuro Keep away from me, sunlight dweller!=¡Alejate de mí, morador de la luz! -dark elf ranger=explorador elfo oscuro Keep away from me, sunlight dweller!=¡Alejate de mí, morador de la luz! -dark elf sacerdotist=sacerdotisa elfa oscura The Queen of Spiders will take her revenge on you!=¡La Reina de las Arañas consumará su venganza sobre ti! -dark elf admiral=almirante elfo oscuro Fear the dark elf armies!=¡Temed a los ejércitos de los elfos oscuros! We are the darkness you fear!=¡Somos la oscuridad que temes! -dark elf master=Maestre elfo oscuro You will never see the light again!=¡No volverás a ver la luz! Darkness will fall upon you!=¡La oscuridad caerá sobre ti! -dark elf matronmother=matrona madre elfa oscura You disturbed my rest!=¡Has perturbado mi descanso! Now, you will pay.=Ahora, lo pagarás. -imp=diablillo -You see an imp. He may look a little bit cute, but don't forget he is one of the devil's servants!=Ves a un diablillo¡ Puede parecer un poquito lindo, pero no olvides que es uno de los siervos del demonio! -winged blue imp=diablillo azul alado -demon=demonio -You see a demon. He personifies evil.=Ves un demonio. Personifica el mal. -imperial demon servant=sirviente demonio imperial -imperial demon lord=señor demonio imperial -dwarf=enano -You see a dwarf who works in the mine to find some iron for us.=Ves a un enano que trabaja en la mina para encontrar un poco de hierro para nosotros. -dwarf guardian=guardián enano -You see a dwarf guardian who fights against the humans.=Ves a un guardián enano que lucha contra los humanos. -elder dwarf=enano anciano -You see an elder dwarf, a mighty warrior tired of fighting.=Ves a un enano anciano, un poderoso guerrero cansado de luchar. -hero dwarf=héroe enano -You see a hero dwarf. He is an old dwarf warrior who became a hero for fighting in many dwarf wars and he is very famous for his strength and brutality.=Ves a un héroe enano. Es un viejo guerrero enano que se convirtión en héroe por luchar en muchas guerras de enanos y es muy famoso por su fuerza y brutalidad. -duergar=duérgar -duergar axeman=hachero duérgar -leader dwarf=líder de los enanos -You see a leader dwarf. He is the most mighty dwarf of his tribe so he enjoys much popularity there.=Ves a un líder de los enanos. Es el enano más poderoso de su tribu así que disfruta de una gran popularidad allí. -elder duergar=duérgar anciano -duergar elite=duérgar de élite -duergar hero=héroe duérgar We mighty duergars will crush the dwarves!=¡Nosotros los poderosos duérgars aplastaremos a los enanos! -duergar wizard=mago duérgar -mountain dwarf=enano de montaña -You see a mountain dwarf. He likes earth, stones and rocks. Deep within the mountain, where no animal and no human has ever been, you can hear the sound of his pickaxe as he searches for gold and ore.=Ves a un enano de montaña. Le gusta la tierra, las piedras y las rocas. En lo profundo de la montaña, donde ningún animal ni ser humano ha estado nunca, puedes oir el sonido de su piqueta mientras busca mineral de oro y de hierro. -mountain dwarf guardian=guardián enano de montaña -You see a mountain dwarf guardian. He has to watch out for enemies and guards the mountain dwarfs while they are doing their work.=Ves a un guardián enano de montaña. Tiene que estar alerta de los enemigos y protege las montañas enanas mientras ellos hacen su trabajo. -black duergar=duérgar negro Dwarves are the enemy ... but we kill all who cross our path!=Los enanos son el enemigo... ¡pero matamos a quien se cruza en nuestro camino! -mountain elder dwarf=enano de montaña anciano -You see a mountain elder dwarf. For many years he worked deep in the mountain and found much gold and ore in his life. Soon he will be too old to do this work but at the moment he is still a strong dwarf.=Ves a un enano de montaña anciano. Durante muchos años trabajó en lo profundo de la montaña y encontró mucho mineral de oro y de hierro en su vida. Pronto será demasiado viejo para hacer este trabajo pero por el momento todavía es un enano fuerte. -mountain hero dwarf=héroe enano de la montaña -You see a mountain hero dwarf. Many, many dwarves admire him! He was a member of a dwarvish group which discovered a very big gold deposit!=Ves a un héroe enano de la montaña ¡Muchos, muchísimos enanos le admiran! ¡Fue miembro de un grupo de enanos que descubrió a depósito de oro muy grande! -mountain leader dwarf=líder de los enanos de montaña -You see a mountain leader dwarf. He is responsible for the mountain dwarves and their work, and also for the organization of the guardian troops. But in serious cases he will also fight on his own!=Ves a un líder de los enanos de montaña. Es responsable de las montañas de los enanos y de sus trabajos, y también de la organización de las tropas guardianas ¡Pero en situaciones graves también luchará por sí sólo! -duergar king=rey duérgar -giant dwarf=enano gigante -You see a giant dwarf. It is very hard for him to be a giant dwarf because some of the other dwarves don't really see him as one of them because of his height... But in his big heart he is a dwarf and in a battle he will fight against each enemy!=Ves a un enano gigante. Es muy duro para él ser un gigante por algunos de los otros enanos no le ven realmente como uno de los suyos por su estatura... Pero en su gran corazón se siente un enano y en la batalla luchará contra cada enemigo! -Dhohr Nuggetcutter=Dhohr Nuggetcutter -Lord Durin=Lord Durin -You see the mighty Lord Durin, an ancient dwarf who has led his race through countless battles.=Ves al poderoso Lord Durin, un antiguo enano que ha guiado a su raza a en incontables batallas. Don't call me Friend.=No me llames Amigo. -nymph=ninfa -You see a nymph. She loves nature very much! As you take a look at her she feels disturbed in sun-bathing and talking to the birds, perhaps she was best left alone.=Ves a una ninfa ¡Le gusta muchísimo la naturaleza! Al echarle un vistazo se siente molestada en su tomar el sol y en su hablar con los pájaros, quizá habría sido mejor dejarla a solas. -earth elemental=elemental de tierra -You see an earth elemental. The earth is quaking as it moves over the surface.=Ves a un elemental de tierra. La tierra tiembla mientras se mueve sobre su superficie. -fire elemental=elemental de fuego -You see a fire elemental and hardly can stand the heat.=Ves a un elemental de fuego y apenas puedes soportar el calor. -water elemental=elemental de agua -air elemental=elemental de aire -You see an air elemental and feel a fresh breeze past you.=Ves a un elemental de aire y sientes una fresca brisa que te atraviesa. -ice elemental=elemental de hielo -You see an ice elemental and sense a shiver on your spine.=Ves a un elemental de hielo y sientes un escalofrío en tu columna vertebral. -djinn=djinn -You see a djinn. He isn't caught in a bottle or lamp so someone must have let him free and gave him the order to kill you.=Ves a un djinn. No está atrapado en una botella o lámpara así que alguien debe de haberle soltado y le dió la orden de matarte. -elf=elfo -You see an elf which loves the forest and lives in peace.=Ves a un elfo al que le encanta el bosque y que vive en paz. -militia elf=elfo de la milicia -You see a militia elf which trains hard to beat you!=¡Ves a un elfo de la milicia que se entrena duramente para vencerte! -archer elf=arquero elfo -You see an archer elf which can do much damage with its wooden arrows.=Ves a un arquero elfo que puede hacer mucho daño con sus flechas de madera. -soldier elf=soldado elfo -You see a soldier elf, a good partner of the militia elves.=Ves a un soldado elfo, un buen compañero de la milicia de los elfos. -mage elf=maga elfa -You see a mage elf. She can feel pure magic when she stands outside in the forest and just frees her mind.=Ves a una maga elfa. Se puede sentir magia pura cuando se encuentra fuera en el bosque y simplemente libera su mente. -commander elf=comandante elfo -You see a busy commander elf. He has control over the army of elves.=Ves a un ocupado comandante elfo. Tiene el control del ejército de los elfos. -archmage elf=archimaga elfa -You see an archmage elf. She is pretty.=Ves a una archimaga elfa. Es guapa. -elf sacerdotist=sacerdotisa elfa -elf wizard=mago elfo -You see an elf wizard. He is a very wise elf who knows almost every kind of magic. He likes to use nature spells to make every plant and creature fertile, but he is also able to defend himself when he has to do.=Ves a un mago elfo. Es un elfo muy sabio que sabe casi cualquier tipo de magia. Le gusta usar hechizos de la naturaleza para hacer a cualquier planta o animal fértil, pero también es capaz de defenderse cuando tiene que hacerlo. -elf bodyguard=guardaespaldas elfo -ent=ent -You see an ent. First you think it is a tree but you will get a surprise when it starts to walk!=Ves a un ent !Primero piensas que es un árbol pero te llevas una sorpresa cuando empieza a caminar! -old ent=ent viejo. -You see an old ent. It has seen many years passing by and survived a lot of storms. You can see that it is very old because it has already lost all of its leaves and most of its bark is broken...=Ves a un viejo ent. Ha visto muchos años pasar y ha sobrevivido a muchas tormentas. Puedes ver que es muy viejo porque ya ha perdido todas sus hojas y la mayor parte de su corteza está rota... -entwife=esposaent -You see an entwife. It is stronger than an ent and lives in a group in an ancient forest.=Ves a una esposaent. Es más fuerte que un ent y vive en grupo en un bosque antiguo. -leprechaun=leprechaun -You see a leprechaun, you would be lucky if you found his treasure.=Ves a un leprechaun, tendrías suerte si encontrases su tesoro. They're always after my Lucky Charms!!=¡¡Siempre van en busca de mis Amuletos de la Suerte!! Clovers and rainbows... stars and horseshoes...=Tréboles y arco iris... estrellas y herraduras... -littlefairy=hadita -You see a little fairy, which is magical and nice.=Ves a una hadita, que es mágica y preciosa. -clurichaun=clurichaun -You see a clurichaun. He is rich, but you don't know if you are able to find his gold!=Ves a un clurichaun. Es rico, pero no sabes si serás capaz de encontrar su oro! Join me for a drink, lassie?=¿Te tomas una copa conmigo, muchacha? Floating blue balloons...=Globos azules flotando... -baby angel=bebé angel -You see a baby angel. Will it ever get a chance to grow up?=Ves a un bebé angel ¿Tendrá alguna vez la oportunidad de hacerse mayor? I want to grow to be mighty!=¡Quiero crecer para ser poderoso! -chick=pollito -You see a little chick, it doesn't like to be away from its mother.=Ves un pequeño pollito, no le gusta estar lejos de su madre. peep peep=pip pip peep peep peep=pip pip pip -piglet=cerdito -You see a piglet, a bit dirty.=Ves un cerdito, un poco sucio. oink=oink oink oink=oink oink shnuffle=shnuffle -mother hen=madre gallina -You see a mother hen, she will probably hurt you if you touch her sons.=Ves una madre gallina, probablemente te haga daño si tocas a sus hijos peep peep=pip pip peep peep peep=pip pip pip -ram=carnero -You see a ram, maybe you are able to shear it?=Ves un carnero, ¿quizá seas capaz trasquilarlo? Baaaaaa=Baaaaaa -beaver=castor -You see a beaver. It likes to build dams in the river.=Ves un castor. Le gusta construir presas en el río. -cow=vaca -You see a cow, taste her delicious milk.=Ves una vaca, prueba su deliciosa leche. Moooo moooo mooooooo=Moooo moooo mooooooo mooooo=mooooo -goat=cabra -You see a goat, which is wild and lives in the mountain.=Ves una cabra, que es salvaje y vive en la montaña. -horse=caballo -You see a horse, don't slap it or it will run away!=Ves un caballo, ¡no les des una palmada o saldrá corriendo! neigghhh neigghhh=neigghhh neigghhh -white horse=caballo blanco -You see a white horse, they love to stay with their brown friends.=Ves un caballo blanco, les gusta estar con sus amigos marrones. naayyy=naayyy -bull=toro -You see a bull, run away!!!=Ves un toro, ¡sal corriendo! MOOOOO!!!=MOOOOO!!! -grass snake=serpiente de hierba -You see a grass snake which is quite dangerous.=Ves una serpiente de hierba que es bastante peligrosa. sssSSSsssss...=sssSSSsssss... -frogman=hombre-rana -You see a frogman which lives in a lake which isn't very deep.=Ves a un hombre-rana que vive en un lago no muy profundo. -elite frogman=hombre-rana de élite -wizard frogman=mago hombre-rana -gargoyle=gárgola -You see a gargoyle, You should think that it is a statue, but no, it is alive!=Ves a una gárgola, Pensarías que es una estatua, pero no, ¡está viva! -elder gargoyle=gárgola anciana -You see an elder gargoyle, it is wise...=Ves a una gárgola anciana, es sabia... -dark gargoyle=gárgola oscura -You see a dark gargoyle, do you feel the fear?=Ves a una gárgola oscura, ¿sientes el miedo? -venom gargoyle=gárgola de veneno -You see a venom gargoyle, a hideous and poisonous creature.=Ves a una gárgola de veneno, una criatura horrible y venenosa. -cyclops=cíclope -You see a cyclops. It just has one eye.=Ves a un cíclope. Sólo tiene un ojo. -ogre=ogro -You see an ogre, it is nude and rude!=Ves a un ogro, ¡está desnudo y es rudo! -elder ogre=ogro anciano -You see an elder ogre, he's in the last moments of his life, take pity on him.=Ves a un ogro anciano, está en los últimos momentos de su vida, ten compasión de él. -ogre soldier=soldado ogro -You see an ogre soldier, smelling like it didn't have had a bath for years. Ewww.=Ves a un soldado ogro, huele como si no se hubiese bañado durante años. Puaj! -superogre=superogro -giantrat=ratagigante -You see a giant rat. It seems like it survived from an experiment which went wrong.=Ves una rata gigante. Parece que haya sobrevivido a un experimento que salió mal. -crocodile=crocodilo -You see a crocodile, it arises from the deep waters in the rivers.=Ves un crocodilo, surge de las aguas profundas de los ríos. Rrrrrrrrr=Rrrrrrrrr -dwarf golem=golem enano -You see a dwarf golem. A long time ago a dwarf got turned into a big rock by some mystical forces. To save their friend the other dwarves created a golem out of this rock! From this day the dwarf golem lives together with them.=Ves a un golem enano. Hace mucho tiempo un enano fue convertido en una gran roca por unas fuerzas místicas. Para salvar a su amigo los otros enanos crearon un golem ¡a partir de esta roca! Desde este día el golem enano convive con ellos. -giant=gigante Fee Fie Foe Fum!!= ¡¡Fi Fa Fo Fum!! I seek the blood of a Semos Man!=¡Busco la sangre de un Hombre de Semos! -elder giant=gigante anciano -You see an elder giant. For many years he lived alone and had to hide because he was hunted by humans! Now he is old and has decided not to hide any longer because he wants to see something else in his life.=Ves a un gigante anciano. Durante muchos años vivió sólo y tuvo que esconderse ¡porque era cazado por los humanos! Ahora es viejo y ha decidido no esconderse por más tiempo porque quiere ver otra cosa más en su vida. Gnomes! Run away!=¡Gnomos! ¡Huyamos! You may steal my money but you'll never take my heart!=¡Podéis robarme mi dinero pero nunca me robaréis mi corazón! -master giant= Maestre gigante Thou shalt die!= ¡Morirás! Worms will feast on your corpse!= ¡Los gusanos se darán un festín con tu cadáver! -ice giant=gigante de hielo -You see an ice giant. His skin is covered with frozen hoarfrost and his breath feels like the cold north wind.=Ves a un gigante de hielo. Su piel está cubierta de escarcha helada y su aliento se siente como el frío viento del norte. -amazoness giant=giganta amazona -You see an amazoness giant. She looks very beautiful but don't be deceived by that!=Ves a una giganta amazona ¡Parece muy hermosa pero no te dejes engañar por eso! -fallen angel=ángel caído -You see a fallen angel. When he turned away from good, he fell down from heaven.=Ves a un ángel caído. Cuando se apartó del bien, se cayó del cielo. You are DOOMED.=Estás CONDENADO I am here to destroy all people.=Estoy aquí para destruir a toda la gente. -black giant=gigante negro Who dare challenge me?=¿Quién se atreve a desafiarme? Leave me now, before it is too late!=¡Marchaos ahora, antes de que sea demasiado tarde! -angel=ángel -You see an angel. With his flame sword he guards the way and doesn't let you pass.=Ves a un ángel. Con su espada flamígera protege el camino y no te deja pasar. Meet thy maker!=¡Conoce a tu creador! Mere mortals cannot challenge me!=¡Los simples mortales no pueden desafiarme! -archangel=arcángel -You see an archangel. She wants to kill you because she doesn't want you in the clouds.=Ves a una arcángel. Quiere matarte porque no quiere que estés en las nubes. Leave now, before it is too late for you.=Marchate ahora, antes de que sea demasiado tarde para ti. Your destiny is not here.=Tu destino no está aquí. Neither the earth nor the sky has a place for you...=Ni la tierra ni el cielo tienen un lugar para ti... -dark angel=ángel oscuro -You see a dark angel. He is an strong evil angel, and for now he is under cover of darkness.=Ves a un ángel oscuro. Es un ángel poderoso y malvado, y por ahora está al amparo de la oscuridad. My will be done over land and sky!=¡Mi voluntad se hará en la tierra y en el cielo! Mine is the Power and the Glory!=¡Mío es el poder y la gloria! -dark archangel=arcángel oscuro -You see a dark archangel, after she fell from heaven her soul became as dark as she is now.=Ves a una arcángel oscura, después de que cayese del cielo su alma se volvió tan oscura como ella lo es ahora. I will strike you down with more power than you can imagine!!= ¡¡Yo te derribaré con más poder del que puedas imaginar!! My will be done!=¡Mi voluntad se hará! -gnome=gnomo -You see a gnome who lives in the mountains.=Ves a un gnomo que vive en las montañas. Giiiiiaaants! Run away!=¡Giganteeeeees! ¡Huyamos! -mage gnome=mago gnomo -You see a mage gnome, she cleans the dirty mountain.=Ves a una maga gnoma, ella limpia la sucia montaña. Giiiiiaaants! Run away!=¡Giganteeeeees! ¡Huyamos! -infantry gnome=gnomo de infantería -You see an infantry gnome who will protect his little town against the humans!=Ves a un gnomo de infantería que protegerá su pequeña aldea frente a ¡los humanos! Ready! Aim! Runnnnnnnnnnn=¡Preparados! ¡Apunten! ¡Corraaaaaaaaaaaan! -cavalryman gnome=soldado de caballería gnomo -You see a cavalryman gnome who does long travels to search for gold.=Ves a un soldado de caballería gnomo que realiza largos viajes en busca de oro. Ready! Aim! Runnnnnnnnnnn=¡Preparados! ¡Apunten! ¡Corraaaaaaaaaaaan! -goblin=goblin -You see a goblin, it is an evil and mischievous creature.=Ves a un goblin, es una criatura malvada y maliciosa. Umm! Tasty meat.=¡Umm! Carne sabrosa. -veteran goblin=veterano goblin -You see a veteran goblin, an old warrior...=Ves a un goblin veterano, un viejo guerrero... Breakfast time!=¡Hora de desayunar! -soldier goblin=soldado goblin -You see a soldier goblin, a young warrior.=Ves a un soldado goblin, un joven guerrero. -wooden golem=golem de madera -You see a wooden golem. The scroll with ancient words in his head keeps it walking.=Ves a un golem de madera. El pergamino con las antiguas palabras en su cabeza lo mantiene caminando. -stone golem=golem de piedra -You see a stone golem, it exists to protect and nothing else. What was it crafted to protect, and what would one do if it lost it's purpose?=Ves a un golem de piedra, existe para proteger y nada más ¿Qué fue diseñado para proteger, y qué haría si perdiese su propósito? -ice golem=golem de hielo -green dragon=dragón verde -giant kobold=kobold gigante -You see a giant kobold. Though generally playful it doesn't quite know it's own strength. When it asks for a hug, it's best to decline that offer.=Ves a un kobold gigante. Aunque por lo general es juguetón no conoce su propia fuerza. Cuando pide un abrazo, es mejor rechazar la oferta. Hug?=¿Un abrazo? -bone dragon=dragon de hueso -You see a bone dragon. It's been said that there are creatures with enough will to live that their souls still cling to life even as their flesh and blood has failed them. This is living proof.=Ves a un dragón de hueso. Se dice que hay criaturas con la suficiente voluntad de vivir como para que sus almas todavía se aferren a la vida aún cuando su carne y su sangre les han abandonado. -blue dragon=dragón azul -You see a blue dragon. It has a very beautiful blue skin which shimmers mysteriously in the light. But don't forget that it also has claws and teeth!=Ves a un dragón azul. Tiene un tono de piel azul muy bonito que brilla misteriosamente en la luz¡Pero no olvides que también tiene garras y dientes! -giant spider=araña gigante -You see a giant spider. It has very long legs which are quite dangerous.=Ves a una araña gigante. Tiene patas muy largas que son bastante peligrosas. -red dragon=dragón rojo -flying golden dragon=dragón de oro volador -You see a flying golden dragon. As it spreads its mighty wings you shiver... this mystical creature seems to have much power in it!=Ves a un dragón de oro volador. A medida que extiende sus poderosas alas te estremeces... ¡esta criatura mística parece tener mucho poder en su interior! -twin headed dragon=dragón de dos cabezas -You see a twin headed dragon. The two heads are staring at you with their four eyes!=Ves a un dragón de dos cabezas ¡Las dos cabezas te están mirando con sus cuatro ojos! -chaos green dragonrider=jinete de dragón verde del caos Wheee!!=¡¡Uiií!! It's fun up here!=¡Es divertido estar aquí arriba! -thing=cosa -You see a thing. It looks like a very mutated human and is so scary that you think it has to come from another world.=Ves a una cosa. Parece un humano muy mutado y es tan espantoso que crees que ha venido de otro mundo. -chaos red dragonrider=jinete de dragón rojo del caos Charrrrrrrrge!=¡A la cargaaaaaaaa! -black death=muerte negra -You see black death. With his scythe he reaps your soul and then he takes it over the river where it has to stay until judgement day.=Ves a la muerte negra. Con su guadaña siega tu alma y después se la lleva al río donde tiene que permanecer hasta el día del juicio. Your time to die has come!=¡Ha llegado tu hora de morir! Say goodbye to this world!=¡Di adiós a este mundo! -golden death=muerte de oro -You see the golden death. He came to take your soul away to pay for all your sins.=Ves a la muerte de oro. Vino a llevarse tu alma para que pague por todos tus pecados. You will dream of me tonight...=Soñarás conmigo esta noche... You will pay for your greed!=¡Pagarás por tu codicia! -risecia thug=matón risecia -You see a risecia thug, stay away from him!=Ves a un matón risecia, ¡mantente alejado de él! -risecia archer=arquero risecia -You see a risecia archer, just one of the warriors who wants to protect his chief.=Ves a un arquero risecia, sólo uno más de los guerreros que quieren proteger a su jefe. -risecia swordsman=espadachín risecia -You see a risecia swordsman, the blade of his sword will cut your head!=Ves a un espadachín risecia, ¡la hoja de su espada te cortará la cabeza! -Chief Falatheen of the Risecia=Jefe Falatheen de los Risecia -You see the Chief Falatheen of the Risecia, he doesn't want to die! His little warriors protect him against invaders.=Ves al Jefe Falatheen de los Risecia, ¡no quiere morir! Sus pequeños guerreros le protegen frente a los invasores. -cannibal woman=mujer caníbal -You see a cannibal woman. She wants to make a soup out of your bones!=Ves a una mujer caníbal ¡Quiere hacer una sopa con tus huesos! Zag-Zag!=Zag-Zag! -littlewitch=brujita -You see a little witch. Witches are are evil little girls who are very dangerous.=Ves a una brujita. Las brujas son niñitas malvadas que son muy peligrosas. -monk=monje The end of the world is nigh...=El fin del mundo está cerca... Yield to the Power!=¡sometete al poder! Meet you after Cataclysm.=Nos veremos después del Cataclismo. I see you, heretic!=¡Te estoy viendo, hereje! -littlewizard=pequeño mago -You see a little wizard. Although he is a child, he is already able to cast powerful spells.=Ves a un pequeño mago. Aunque es un crío, ya es capaz de lanzar poderosos hechizos. -kalavan housewife=ama de casa kalavan -You see a kalavan housewife. She lives in Kalavan city and she very angry with rude people who want to steal her food out of the kitchen and destroy her beautiful flower garden!=Ves a una ama de casa kalavan. Vive en la ciudad de Kalavan y ¡se enfada mucho con la gente ruda que quiere robar comida de su cocina y destruir su precioso jardín! -cannibal=caníbal -You see a cannibal. He hunts you for cooking and eating your fresh meat! It's useless to tell him that your flesh is not good, better fight him or run away!=Ves a un caníbal. Te caza para cocinar y comerse tu carne fresca! Ou-Garrrr!=Ou-Garrrr! -kalavan peasant=campesino kalavan -You see a kalavan peasant. He works in the fields around Kalavan city. As he heard there were strangers in the city he came to help the kalavan citizens in fighting the enemies!=Ves un campesino kalavan. Trabaja en los campos de alrededor de la ciudad de Kalavan. Cuando oyó que había extranjeros en la ciudad vino a ayudar a los ciudadanos de kalavan a luchar contra los enemigos! -kalavan wisewoman=mujer sabia de kalavan -You see a kalavan wisewoman. Normally she sits in her small room and does some handworking but now she wants to have some action!=Ves a una mujer sabia de kalavan. Normalmente se sienta en su pequeña habitación y hace labores manuales pero ahora quiere tener algo de acción! -kalavan citizen=ciudadano de kalavan -You see a kalavan citizen. He lives in kalavan city and tries to protect it from invaders!=Ves a un ciudadano de kalavan. Vive en la ciudad de kalavan ¡e intenta protegerla frente a los invasores! -elder cannibal=caníbal anciano Yummie!=mmmmm! -imperial defender=defensor imperial -bandit=bandido -You see a lean and mean bandit. He's out to get your life and purse!=Ves a un bandido preparado y listo para actuar ¡Hará lo que sea necesario para llevarse tu vida y tu dinero! -imperial veteran=veterano imperial -You see an imperial veteran. He fought in many battles and wars. Now he is old, but over the years he always looked for a new enemy to fight against.=Ves a un veterano imperial. Luchó en muchas batallas y guerras. Ahora es viejo, pero a lo largo de los años siempre ha buscado un nuevo enemigo contra el que luchar. -darkmonk=monje oscuro The Cataclysm cometh!=¡El cometa del Cataclismo! Hail to the greater Power!=¡Salve al Poder supremo! -barbarian=bárbaro -imperial archer=arquero imperial -disciple assassin=discípulo de asesino -You see a disciple assassin. He is quite young but he has learned to fight and kill already.=Ves a un discípulo de asesino. Es bastante joven pero ya ha aprendido a luchar y a matar. I'm gonna tell my mommy.=Se lo voy a decir a mi mamá. Teacher, make him stop.=Seño, digale que pare. My dad can beat your dad.=Mi papá puede ganar a tu papá. -imperial priest=sacerdote imperial -imperial elite guardian=guardián de élite imperial -barbarian wolf=lobo bárbaro -apprentice assassin=aprendiz de asesino -You see an apprentice assassin. He has been taught some very awesome moves and methods, so he is already able to kill an enemy almost perfectly!=Ves a un aprendiz de asesino. Le han enseñado algunos movimientos y métodos muy impresionantes, así que ya es capaz de matar a un enemigo casi perfectamente! Come on, i dare you.=Venga, a ver si te atreves. You want a piece of me?=¿Quieres un pedazo de mí? Any time you're ready.=Cuando estés listo. Bring it on!=¡Adelante! -hunter=cazador I always get my prey!=¡Siempre consigo a mi presa! -barbarian elite=bárbaro de élite -imperial scientist=científico imperial -You see an imperial scientist. He is quite clever but also a little bit crazy and unfortunately he uses his intelligence for evil experiments.=Ves a un científico imperial. Es bastante listo pero también un poquito loco y desafortunadamente usa su inteligencia para experimentos malvados. -barbarian priest=sacerdote bárbaro -imperial high priest=sumo sacerdote imperial -assassin=asesino -You see an assassin. He is very good at killing his victims silently without being seen by anyone.=Ves a un asesino. Es muy bueno matando a sus victimas silenciosamente sin ser visto por nadie. Time to die.=Es hora de que mueras. -barbarian chaman=chamán bárbaro King he say kill!=¡El rey ha dicho que te mate! -imperial archer leader=líder arquero imperial -barbarian leader=líder bárbaro Me destroy you!=¡Yo destruyo tú! -mithrilbourgh soldier=soldado de mithrilbourgh -imperial elite archer=arquero de élite imperial -ninja=ninja -imperial leader=líder imperial -mithrilbourgh archer=arquero de mithrilbourgh -mithrilbourgh knight=caballero de mithrilbourgh -imperial knight=caballero imperial -imperial chief=jefe imperial -mithrilbourgh elite archer=arquero de élite de mithrilbourgh -barbarian king=rey bárbaro -mithrilbourgh sergeant=sargento de mithrilbourgh -samurai=samurai -imperial commander=comandante imperial Our troops will crush you!=¡Nuestras tropas os aplastarán! -mithrilbourgh lieutenant=lugarteniente de mithrilbourgh -mithrilbourgh colonel=coronel de mithrilbourgh -mithrilbourgh wizard=mago de mithrilbourgh -shadows assassin=asesino de las sombras -You see a shadows assassin. He knows how to become one with the shadows and to move in the dark without being seen or making any sound.=Ves a un asesino de las sombras. Sabe como hacerse uno con las sombras y moverse en la oscuridad sin ser visto o hacer ningún sonido. -imperial general=general imperial Our troops will crush you!=¡Nuestras tropas os aplastarán! -mithrilbourgh general=general de mithrilbourgh -emperor dalmung=emperador dalmung My empire is undefeatable!=¡Mi imperio es inderrotable! -blordrough quartermaster=intendente de blordrough We might be in hiding, but we are STILL mighty.=Puede que nos escondamos, pero TODAVÍA somos poderosos. -imperial general giant=general imperial gigante -master assassin=Maestre asesino -You see a master assassin. He is a master of assassination! He is able to kill an enemy without letting him know that he is already there!=Ves a un Maestre asesino ¡Es un maestro del asesinato! ¡Es capaz de matar a un enemigo sin dejar que sepa que ya está allí! You've been warned before.=Ya te había advertido. Why do you return?=¿Por qué has vuelto? Have you lost your marbles?=¿Has perdido tus canicas? -blordrough corporal=cabo de blordrough You WILL show us the respect we deserve.=Tú nos MOSTRARÁS el respeto que merecemos. Care for a game of chess?=¿Te apetece una partida de ajedrez? -blordrough storm trooper=soldado de asalto de blordrough Wipe your feet before entering our domain.=Limpiate los pies antes de entrar en nuestro dominio. I'll teach you to defile our sanctuary.=Te voy a enseñar a profanar nuestro santuario. -tiny skelly=pequeño esqueletito -You see a tiny skelly. It is silly, little and evil, be careful with its poison...=Ves a un pequeño esqueletito. Es tonto, pequeño y malvado, ten cuidado con su veneno... Thou shall not maim!=¡Tú no mutilarás! -brain ahouga=cerebro ahouga -You see a brain ahouga, a very strange creature only living in your dreams!=Ves a un cerebro ahouga, ¡una criatura muy extraña que sólo vive en tus sueños! -red roohako=roohako rojo -robot aruthon=robot aruthon -violet avenger=vengador violeta -googon=googon -You see a googon that comes from the worst place in this world...Be careful this creature is a deadly killer.=Ves a un googon que viene del peor lugar de este mundo... Ten cuidado esta criatura es una asesina mortal. -blue mohiko=mohiko azul -snarfkin=snarfkin -You see a snarfkin, the favorite pet of wizards. Be careful, its claws are poisonous!=Ves a un snarfkin, la mascota favorita de los magos. Ten cuidado, ¡sus garras son venenosas! -dark aruthon=aruthon oscuro -grobble monster=monstruo grobble grobble grubble grobble=grobble grubble grobble gribble grobble grubble=gribble grobble grubble -winged blurble=blurble alado -xenocium=xenocium -necrosophia=necrosophia -arachne=arachne -You see Arachne, a horrid half woman half spider. With so many legs she can hit you very fast, and her poisonous bite is fierce.=Ves a Arachne, un ser horrible mitad mujer mitad araña. Con tantas piernas puede golpearte muy rápido, y su mordedura venenosa es feroz. -kobold=kobold -You see a kobold. People say that it is a mythical spirit.=Ves a un kobold. La gente dice que es un espíritu mítico. Grrrr! Wooof!=Grrrr! Wooof! -soldier kobold=soldado kobold -You see a soldier kobold, he's protecting his king.=Ves a un soldado kobold, está protegiendo a su rey. Woorf wer warf!=Woorf wer warf! -archer kobold=arquero kobold -You see an archer kobold. It runs away from humans.=Ves a un arquero kobold. Sale corriendo de los humanos. Wssss! Warf wit!=Wssss! Warf wit! -veteran kobold=veterano kobold -You see a veteran kobold, it is mighty!=Ves a un veterano kobold, ¡es poderoso! -leader kobold=líder kobold -You see a leader kobold, one of the mighty warriors.=Ves a un líder kobold, uno de los poderosos guerreros. -madaram archer=arquero madaram -madaram peasant=campesino madaram -madaram trooper=soldado de asalto madaram -madaram soldier=soldado madaram -madaram healer=sanador madaram -madaram axeman=hachero madaram -madaram queen=reina madaram -madaram hero=héroe madaram -madaram windwalker=windwalker madaram -madaram cavalry=caballería madaram -madaram stalker=acechador madaram -madaram buster blader=buster blader madaram -madaram pawn=peón madaram -You see a pawn for a game of chess. It will reward nothing if killed.=Ves un peón para una partida de ajedrez. No te recompensará con nada si lo matas. -madaram rook=torre madaram -You see a rook for a game of chess. It will scarcely reward you to kill it.=Ves una torre para una partida de ajedrez. Apenas te recompensará con algo si la matas. -madaram knight=caballo madaram -You see a knight, a chess piece. He won't reward you if you kill him.=Ves un caballo, una pieza de ajedrez. No te recompensará con nada si lo matas. -madaram bishop=alfil madaram -You see a bishop for a game of chess. It will reward you nothing if you slay it.=Ves un alfil para una partida de ajedrez. No te recompensará con nada si lo matas. -madaram king=rey madaram -You see a king to play chess with, he will reward you nothing if you kill him.=Ves un rey para jugar al ajedrez con él, no te recompensará con nada si lo matas. madaram queen =reina madaram -You see a queen piece for a game of chess. She will reward nothing if you kill her.=Ves una pieza reina para una partida de ajedrez. No te recompensará con nada si la matas. -kasarkutominubat=kasarkutominubat -minotaur=minotauro -You see a minotaur. He escaped from his labyrinth and is very angry now!=Ves a un minotauro ¡Ha escapado de su laberinto y ahora está muy enfadado! You will never find the way out!=¡Nunca encontrarás la salida! -minotaur king=rey minotauro I am the king of this area!=¡Soy el rey de esta área! -mummy=momia -You see a mummy which usually sleeps in a coffin.=Ves a una momia que suele dormir en un sarcófago. -royal mummy=momia real -You see a royal mummy. You wonder what keeps this king's soul from finding peace.=Ves a una momia real. Te preguntas qué le impide al alma de este rey encontrar la paz. -glow monster=monstruo del resplandor -dark mutant=mutante oscuro -cat woman=mujer gata -naga=naga -werewolf=hombre lobo -You see a werewolf. Normally he is a human but at fullmoon nights he turns into a dangerous beast! His poison seems to last forever.=Ves a un hombre lobo. Normalmente es un humano pero ¡en las noches de luna llena se convierte en una bestia peligrosa! Su veneno parece durar para siempre. -high naga=naga suprema -imperial experiment=experimento imperial -imperial mutant=mutante imperial Ouurgghh!=Ouurgghh! -armed naga=naga con brazos -black dragon=dragón negro -You see a black dragon. As you see this huge black creature you almost faint! If you concentrate you can feel the deep growling of it in your bones.=Ves un dragón negro ¡Al ver a esta enorme criatura negra casi te desmayas! -balrog=balrog -You see a huge balrog. He is slow hitting, but terribly strong as he stands towering above you.=Ves a un enorme balrog. Es lento en golpear, pero terriblemente potente mientras permanece de pie imponiendose por encima de ti. Growwwwwwwrrrrrrr!=Growwwwwwwrrrrrrr! Arrrrllllggggghhhh=Arrrrllllggggghhhh -gashadokuro=gashadokuro -A giant figure made of bone, known only as gashadokuro, towers before you. He is created from the bones of starving humans.=Una figura gigante hecha de hueso, conocida sólo como gashadokuro, se impone ante ti. Se ha creado a partir de los huesos de humanos muertos de hambre. -unicorn=unicornio -You see a rare unicorn. It is widely believed to be only a myth.=Ves un raro unicornio. En general se cree que es sólo un mito. -centaur=centauro - You see a noble centaur, half man, half horse. He looks capable of great damage but thankfully seems to mean you no harm.=Ves a un noble centauro, mitad hombre, mitad caballo. Parece capaz de causar gran daño pero afortunadamente no parece tener intención de herirte. -pegasus=pegaso -You see Pegasus, the winged horse. His name means Lightning and he carries bolts of Thunder.=Ves a Pegasus, el caballo alado. Su nombre significa Relámpago y lleva los rayos del Trueno. -oni warrior=guerrero oni -oni archer=arquero oni -oni priest=sacerdote oni -You see a mysterious oni priest, uttering strange incantations.=Ves a un misterioso sacerdote oni, pronunciando extraños encantamientos. -oni king=rey oni Revere me, the Oni King!=¡Veneradme, al Rey Oni! -oni queen=reina oni You must bow before the Oni Queen.=Debes inclinarte ante la Reina Oni. -orc=orco -You see an orc which is noisy and silly.=Ves a un orco que es ruidoso y tonto. -orc spearman=lancero orco -You see an orc spearman. Run away fast before a spear pierces you.=Ves a un lancero orco. Sal corriendo rápido antes de que una lanza te atraviese. -orc warrior=guerrero orco -You see an orc warrior which doesn't like you searching in the mountain.=Ves a un guerrero orco al que no le gusta que andes buscando en la montaña. -orc hunter=cazador orco -You see an orc hunter. It it seeking for you!=Ves a un cazador orco ¡Te está buscando a ti! -orc chief=jefe orco -You see an orc chief which is too busy to attend to you!=Ves a un jefe orco ¡que está demasiado ocupado para atenderte! -mountain orc=orco de montaña -mountain orc warrior=guerrero orco de montaña -mountain orc hunter=cazador orco de montaña -mountain orc chief=jefe orco de montaña -ratman=hombre rata -You see a ratman. He is a mutation that comes from old times.=Ves a un hombre rata. Es una mutación que procede de tiempos remotos. You, humans, never leave us alone!=¡Vosotros, los humanos, nunca nos dejáis en paz! -ratwoman=mujer rata -You see a ratwoman. She is a mutation of women with the rats in the century XVIII.=Ves a una mujer rata. Es una mutación de mujeres con ratas en el siglo XVIII. You humans never leave us alone!=¡Vosotros los humanos nunca nos dejáis en paz! -archrat=archirrata -You see an archrat. You will be distracted with its aura, be careful...=Ves una archirrata. Te distraerás con su aura, ten cuidado... You're messing with the rats?!=¡¿Te estás metiendo con las ratas?! Your death has come!=¡Tu muerte ha llegado! Feel my wrath!=¡Sufre mi ira! -sheepman=hombre oveja I'm no fluffy lamb!=¡No soy un esponjoso cordero! -armored sheepman=hombre oveja blindado You're baaaarmy to attack me!=¡Estás chiflado al atacarme! -elder sheepman=hombre oveja anciano Bah! Stay away!=¡Bah! ¡Mantente alejado! -elite sheepman=hombre oveja de élite Baaaaack away!= ¡Retrocede! -rat=rata -You see a small rat that seems to be extremely hungry.=Ves una pequeña rata que parece estar extremadamente hambrienta. Squeak! Squeak!=¡Cuic!¡Cuic! -mouse=ratón -You see a small mouse that seems to be very hungry.=Ves un pequeño ratón que parece estar muy hambriento. squeak ... squeak!=¡cuic ... cuic! -caverat=rata de cueva -You see a caverat which is dirty and loves the dungeons.=Ves una rata de cueva que es sucia y le encantan las mazmorras. -razorrat=rata de cuchilla -You see a razorrat, its noise will make you crazy!=Ves una rata de cuchilla, ¡su ruído te hará perder la cabeza! -venomrat=rata de veneno -You see a venomrat, extremely poisonous.=Ves una rata de veneno, extremadamente venenosa. -zombie rat=rata zombi -You see a hungry zombie rat and it doesn't like to see you.=Ves una hambrienta rata zombi y a ella no le gusta verte a ti. SHREEAK!!=SHREEAK!! SHAAAAAAA!!=SHAAAAAAA!! -troll=trol -You see a troll, be careful in his terrain.=Ves a un trol, ten cuidado en su terreno. -red troll=trol rojo -You see a red troll, he's burning with anger.=Ves a un trol rojo, está quemando de ira. -cave troll=trol de cueva Uggga yggga arghhhhh=Uggga yggga arghhhhh Ooggga UGGga arhhh=Ooggga UGGga arhhh -skeleton=esqueleto -You see a skeleton which survives to get revenge.=Ves a un esqueleto que sobrevive para vengarse. Thou shall not pass!=¡No pasarás! -ghost=fantasma -You see a ghost, don't be afraid! It doesn't seem to be evil.=Ves un fantasma, ¡no tengas miedo! No parece ser malvado. Ooooouuuuuh!=Ooooouuuuuh! -ghost hound=perro fantasma -As you catch sight of its shaggy shape and gleaming evil eyes, you suddenly remember all the horror stories around the dreaded ghost hound!=Al avistar su forma peluda y sus malvados ojos brillantes, de repente recuerdas todas las historias de terror acerca del temido perro fantasma! WooOouuuUuh!=WooOouuuUuh! Ghhrr!=Ghhrr! -bloody zombie=zombi sangriento -You see a bloody zombie. Can you see the drops of blood that fall from his body?=Ves a un zombi sangriento ¿Puedes ver las gotas de sangre que caen de su cuerpo? brrraaaaaaaaaains=cereeeeeeeebrooooos -zombie=zombi -You see a zombie. It woke up just to take revenge, be silent.=Ves a un zombi. Se despertó solo para vengarse, estate en silencio. -headless monster=monstruo sin cabeza -You see a headless monster, how can he hear that we come to him?=Ves a un monstruo sin cabeza, ¿Cómo ha podido oír que nos acercábamos a él? -warrior skeleton=guerrero esqueleto -You see a warrior skeleton, it's just a normal skeleton with a shield!=Ves a un guerrero esqueleto, ¡es sólo un esqueleto normal con un escudo! Thou shall not kill!=¡No matarás! -elder skeleton=esqueleto anciano Thou shall not survive!=¡No sobrevivirás! -rotten zombie=zombi podrido -You see a rotten zombie. You are disgusted as you see its rotten skin that hangs down in pieces from his half rotten bones.=Ves a un zombi podrido. Estás asqueado al ver su piel podrida que cuelga hecha jirones de sus huesos medio podridos. bluuergghhhoooorrghhhhh=bluuergghhhoooorrghhhhh -death=muerte -You see Death. Do you have the will to stare him in the face? Today he's not in the mood to bargain with your soul.=Ves a la Muerte ¿Tendrás el valor de mirarla a la cara? Hoy no está de humor para negociar con tu alma. Judgement is coming!=¡El juicio se acerca! Uuuuhhhhh!, i'm gonna get you!=¡Uuuuhhhhh!, te voy a coger! -demon skeleton=esqueleto demonio Thou shall not rob!=¡No robarás! -invisible man=hombre invisible -You see nothing but the landscape. As you stare at the point where you think you have seen something, you suddenly see the slight contour of a man!=No ves nada excepto el paisaje. Al mirar fijamente al punto donde crees haber visto algo, de repente ¡ves el ligero contorno de un hombre! My unnatural state should evoke fear in you ...=Mi estado no natural debería evocar el miedo en ti... -death knight=caballero de la muerte -You see a death knight. Some time ago he fought in tournaments where his only aim was to kill as many enemies as possible.=Ves a un caballero de la muerte. Hace algún tiempo luchó en torneos donde su único objetivo era matar tantos enemigos como fuese posible. -devil queen=reina del diablo Who wakes me from my meditation?=¿Quien me ha despertado de mi meditación? You will regret this!=¡Lo lamentarás! -fallen warrior=guerrero caído -green phantom=fantasma verde -lich=lich -You see a lich, a dark desolate creature which has been doomed to wander in endless turmoil, feeding on the souls of the righteous.=Ves a un lich, una criatura oscura solitaria que ha sido condenada a vagar en confusión sin fin, alimentándose de las almas de los justos. -fallen priest=sacerdote caído -incorporeal armor=armadura incorpórea -You see an incorporeal armor. You are quite surprised as it walks towards you because there is nobody in it.=Ves una armadura incorpórea. Te sorprende bastante al caminar hacia ti porque no hay nadie dentro de ella. Thou shall not pillage!=¡No saquearás! -dead lich=lich muerto -fallen high priest=sumo sacerdote caído -high lich=lich supremo -The terrible grinning skull of a high lich leers at you as it prepares to slaughter you in a myriad of magical ways.=La terrible sonrisa de la calavera de un lich supremo te mira de reojo al prepararse para matarte de una miríada de mágicas maneras. -devil monk=monje del diablo -You see a devil monk. He is a member of a devil cult which practises black rituals.=Ves a un monje del diablo. Es miembro de un culto al diablo que practica negros rituales. -vampire bride=novia vampira -You see a vampire bride. She was resurrected from the dead by a wicked vampire lord.=Ves a una novia vampira. Fue resucitada de entre los muertos por un malvado señor vampiro. -vampirette=vampiresa -vampire lord=señor vampiro -You see a vampire lord. Be careful, he is after your blood!=Ves a un señor vampiro. Ten cuidado, ¡va tras tu sangre! diff --git a/data/languages/template.txt b/data/languages/template.txt index 301920f472..3d244004c6 100644 --- a/data/languages/template.txt +++ b/data/languages/template.txt @@ -1097,6 +1097,7 @@ venom gland= You see the venom gland of a giant king cobra. It is filled with deadly toxin.= pelt= +You see a pelt of animal skin. It's used to make clothes and leather armor.= leather thread= You see a strand of sturdy leather thread.= @@ -1265,6 +1266,9 @@ You see an imperial ring. It is imbued with the strength of a king.= enhanced imperial ring= You see an enhanced imperial ring. It eminates the auras of strength and defense.= +ring of clarity= +You see a ring of clarity. You feel more aware while wearing it.= + ## scrolls @@ -1407,6 +1411,9 @@ You see a silvery gm token. Does it bring you pride?= bronze gm token= You see a bronze gm token. Does it bring you pride?= +four-leaf clover= +You see a lucky four-leaf clover. What are the chances of finding one!?= + ## swords @@ -1635,6 +1642,7 @@ You see a killer bat which will suck your blood!= giant bat= giant killer bat= +You see a giant killer bat. As if small wasn't pesky enough, these giant ones are capable of taking down anyone who dares come before them.= piglet= You see a piglet, a bit dirty.= @@ -1702,6 +1710,7 @@ black bear= You see a strong black bear which hibernates in winter.= polar bear= +You see a polar bear which lives in the icy desert. Beware! It can take you down with its powerful claws!= ## animal_aquatic @@ -2268,6 +2277,7 @@ cannibal= You see a cannibal. He hunts you for cooking and eating your fresh meat! It's useless to tell him that your flesh is not good, better fight him or run away!= elder cannibal= +You see an elder cannibal. For years it has eaten humans. It will not stop until its hunger is satisfied.= cannibal warrior= You see a cannibal warrior.= diff --git a/data/maps/Level -1/semos/mine_n2_e2.tmx b/data/maps/Level -1/semos/mine_n2_e2.tmx index 8ad8805e2f..d425eecce5 100644 --- a/data/maps/Level -1/semos/mine_n2_e2.tmx +++ b/data/maps/Level -1/semos/mine_n2_e2.tmx @@ -1,112 +1,112 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - H4sIAAAAAAAACu2cy4rbQBBFFfIHWSZg8JcMhsAku0wW8x2B+X9IDDHRKGqpH/W4VX0Xd+lRVZ2jatmD/fphWV4ZhmEYhmEYhmEYhoHPx6Uc79oYH+7knzc13Mk/Z1rYk3/stLIm/zwh93nD+36+SDAn/7gh+zkifZ+TPX60mZM9Zqy4kz1OLJmTPU48uJP/nLzJPjZ7ib/lPYMZo3Gvkjl+NPcz2WPHYjeTO154Hs8bsp83fBafM5rPeAxuRt/T0YO4kWRP/rEizZ4exMgIT3oQN1LcuBfiRYMRPcCPNhc+J+DGkgMdwIn37OkBPnereSPWlDHoM6YHGLNlrXkScZZ8Lph7fnw2nHtmvb0g98Q52fXl0aNVTZFmgsBfqnfPehD6j8hacg5edWbjjTTTSLVm4o4yz2j1RmeONs9o9ZI/+WdxIFoP3lyzuRCtZm+W2TyIUmdGp8gSLz38vF2IuHfQ08rMYy9EPncipGe2Vh54nDPWfiNkpB9JF0b2DQLzzA54zlFj1si1oTrQ2gvajCP5icof7V5DqCELf00HtDhEYJ/VgdGeZuMfyYGWuXl4R/44DpB/Xv418yP/3PzPZkj+svV+Xv7Fm3vtDMlfh//XP/nyN8gOkL88/9uK/V6Q+Es4QP7v+a/v/ZrM5IC2VxH5Wzigzb+WF/nXRYqJtQNn15mB/7MQf2kHpO/Pnmtp1IzGX2IHaPGx4l+6FvnXs9biZOmA5i5BYr/lX3MGeNTYMk9v7pH5n+0ArxrpgD9/lJq1HcvigCR/tJrJX67nTwH504Gc/Fvr15xLRA96ajrjb82+tY9oDmi4MFIHIv/W3qI6MOKC1LUz8O+dpTd3hCCe/6OMou8D8h/nRAf6+O99BuzNvZeR1b6JnDv/KDugt0c6MDd/etDG/wcofwku3jvHm3eJP/r7QE8Heq5ptVtGr1HLP5sDvR5I/23N2rPzl7qPNK7pPZdZ+Hs5cHZd75nMxN/TgVIN3vMY4X/bYY/OH8kDlPTyv9/7P5f/Pw/07oceyPde2v17Z4F3X9b8I3rQ2tuW/X33v5F/OA96e9q79zPw1/QAyYfRHlr40wE8D6T5P5/wj+jAuvarsg+R2D/47+2AX0n5r6O9F7T8kKynxP9tycP/yAFvH7yzx399Btx25uTNUtsFbybe/Gu+C+zNkB7o8rf+LrB3ZvWg9P+f2t+D8OZGD/T49/wmkDc/eiDHv9eBbE5k9uCMPx3IvQ/W/I8ckPIgqwveHC3404P3/C8JPOjhX/veIKsfR/yjObDl37IDXjYZ5X05mCmKI+s615mNf8mJpwpmT4WUZluas4YXNde+LPu1zs7/aDc88r3jddv9IOHH3t5pzfa1Ec+BPf7SPmj59K0QK/4ZzoEa/mg+1PpS8mObK/l3p4cPoldXIQ9m49/qBvpueeyDrQu1bkR7BtDkL+2P5Rm1PhvOEpl/FBckd1Drs8SZBxnYZ/ZD4lw48iDD54D0oN+DyOx/A/Jq0JkAAAEA - - - - - H4sIAAAAAAAACu2dS48cNRDHPUoUKQp8JT5FVgQWVpsD3wEOUQ6QA5G4ceLUIHGCj8GR55lTeIblERFIwkoBMcVOhdpaP6r86LZ76ic5k52Z7umZf7lctstu5wzDMAzDMAzDMAzDGIHDjXOveMrLm6WvzGgJ6n5zW566s4LA/181/VcJ1R34i7xG7cD0Xx+HO91P2fNPSfndWf0fnVCbfpNoChpTOzjdPXd9+56XtuVF039IDpnOPnj9p1i9Hxeq/Z/ufEznPH9j20/twfQfj1hMF+KUPJr+Y3MU0ezL3SOv+1x3bPut3e+XVFxHNZb4AHqMad8nhx6dJdqiro8E7zXt+4O36SG4LfwqPP+JM5/fG766XhMeA5j2fSCt65xfCj4T/IbF+svA4zicg5GA43NQDnb6feF53wN3/px/b8s/7BxW9+eHjtFINYe66tOM9vuekPdyqM2Y7svA51uRk8gx1xOaxfr9FPPzyxHS3Qedf5HU0yNyTl9sZ/V9WSRzMSW++QY71nz88vA+XKjOW997fWB95zEYz61CrF1eDxJfT7Ecm/XA+3SSMVjTf2ykY7W+/vjD3ePxxnKtR+TQE9v54rzfPM/z/pqNw45HatyFao79u4PAMfBe8wNjkBrL4X152g8Hm3niOQYxO+iX1PycpC9Px2oOEuMCaAe2Hmt5pGN42nG7UHsA+ObzzDfMi2ROHuflcsbwbiT8AJ6fY3bQnlSdrzW3wtsEDcdmA9VJ1fmS+p6Cz+XE7AH9gvmCOlDdQ7m1c+fM3BD6BehTmC/Ip0V8V5NUrIiYL9AjbeeXnqOV2gD2Hc0G4khi+97m5qW5XoD5gouk5mtwnVwvdZ4D+n+vPGbffYE0Fwfprc5TjiKxqQ/8rvtmAzlraHqt8xT0/39kHMvHk+eyh9B61dpj21xzTT3puc5TQu2/b04ptr8HcFzpt0/pKl3Xor0e3/oZKSOshfDVm5L1gHytP8DHkFr5hVhd9OVJ+ea+cu2KMoKPB7BP+tidxXv4Pb/OPF/s9+e+Y+k8lNj8eIyvAs+PtgZCm0Ma4pQ9Ivg7hXKSSvX/sezw6ixtz1okfXzeB4zlmp86v+8PMdrvlWK07wP639s+fkOeA7/vyytqUVr6yFzf/iDzONj/bLS+r6/+994/0fS1ahynZam+bw6o/7fb8p3rX/va/gfmOCA3Psc2Qsfw53seG+c5AD1r3wJpjiQnlWPto7Vf8PXhe7S5XtHYAsZ50nlSH7XGQfjc4g/urC9Pr9PQkdKV/q7SvJkYObZAdcf2O3adhg5fbIzjh744Pze3MidekI7b1NRfOrfSeu5lTmI6xvp6mtzK2Pl9vxlqD/10uscWwOfqSvVvsT/gSH3kVDsg/S6l9nCs1OGeOxvTydG/9Z6QQM99Io7EBrTfRRozPIy/HEQz91Qyt1nKKPGJZJw8x6/lxAy+uSe+L6f0d601/5PLKG2BtJ9XYs8xW9CMTz1RXIcmx7MVI9kA16fVWF9pvDCS/sAo7QAS+91a5MTD5+GYjm9uFfoCdJ7W9G+LpD2o+Z18OsXaBNO/PZoxwlJQJ6zjqfwK038eYjZQsw0IrcWguTa0X7BW/Wk/tRdiv2Etu/Z9RizPaq36c3oYUw75AGifa/kA0Ck0FnSf/F877je6/sjSOTex37GGDUjHH7Q552vRnzO3Pfj0oXsq1fh++Bl0rpffT1n7OWvVnzPHGFNrHwCE5g2wL2j6h6E+4barbw+15gpzPwvGh0x/GaB/izlIen8UH7XHBDDeA04yzl+Sv1aTlvq/vS1vsXKHvF5zPeKcY0K+e7GU7Au4FK1yzlH3O6k3EmrYwlxjQjVZsh1oUfdBe43uPkpsIRUL9jBuQVnSB9TWv4b2HLzG14TXqtlDbd9tQKP/R4nXW2jvQ5rzN9o+eku0Axr9Jxe3gduF1yJFes2aOtWDL5hb/5zY773Ia73pD3AbCM3b97CXYsn+0BpK9kYJ6T+X7we0PlvrB3poD1rYQumeOJc3Yf3nqvuUEj+QYmlfQClZ41Z7T5ye9Nf25bR5nT3ZABC7/+9ce3z0pD8lpx8rua9XL+1BL4T0f2db3pjzQhg5+ktjQ8DswLkrkfYfWNIH5I7rjRwXLMHkwmMAS7cBSM48nCbW3mcbgH22em0DkNJ1YD4b4HuX7mt78FzHbQBSOqeh6W/tkx1c3n3HyfXdBtTSJJVPAuTmflFG3Duq134gpbSd1sQFJTmvvB/6uWvjV0J7yGg+A/f87T0GQFrnftb4XK4/rCXC3OIadkD3qoI8xU+35TN35tugaH1XbBwY6MkHALXXgtX+XDgnakJBG/jEyfbJj+2fHxrbAF+T03aNpD9Q0w/E1hogJ+RzU/VXO+9Ltcy5PwyAcQ2cax/0B2q3Bdr2INTmYpypvcdFDrgvN123ptEf+n+wR/eI+gO14ypNXEDrLd9T7Kc6l5N1nxTtHNFo7b+PJe3AR+7+6Ln34IB2Pzf3+91t+TDy+gj6I7XzX2vsR0uRtguhe+Lwc5XkA1zdpLUHRtK/1Vh+KEb4WXke2Fsu1zfQNeu17ocV8/vISPoDrcdwS31Cjbiwlp+T6N/bGJCU1vN6kj3sWgDjB7X0n1x6HQAwqg3ccvOMw6dyudA+HsdP8x8+G4LnWt0PT+IDgNHaAeDNbblL/l5qfi8n12+u+3utWX8AbADXKqMtLL3HTU+sXX8K2sJd9vw+zfsj2J+Q6j9qDODD5xOAfbSDycliQGANPoDj8wn7ZgcaH/C6W48foOyzHUj1R9AOsKzJHmLx4lrtQKs/h9vDWmyil/5jK67uvsfk5DGAlLXEi/viD0p9gA/qF9ZgC7fY32taO9JCf8oaYkeuPzC6L8A5zMnVbwN8+GKFUeIGn/7IqPddoLT2ARJS9rGUjVxyK9SfXXMP+ksI2UiJXVwSlNXpz5i25X1S5mgPaiLxHSUllis1YhxA8w0fwT/s2ic3tj0swWh+4HLgevkaaWBy+bbwsTtvS2u1q9H0v4Dw+icn1xO0/yBwngduvr4H54UG51zTmMAzyPe5H37XMyZ33h5C2oeOobYQ8xu1fEltOxjBB9DcNFjfdqWja56cznYeeY5d0g5G0B+R5i5quMqP8ZxDu9bxQjwSua5rm/I2JdcWWt2DYSku5Dlv/tdX0hbkcE1gP8H3EiZXHl9q7WCkuq+lRh5rjRLySc/vnue+osa4VsoOaq3JmYt/ATb8NvgAAAEA - - - - - H4sIAAAAAAAACu3bMQrAIAwFUHOrnqU6tPe/RCkOHUUQrPjelDGQ5ROSlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2dcbsDpjpMn8AoEORHQAAaMhRc6O9437eXeMdXw0AAAAAAAAAAACMcbjLAwAW5b8A+sn/AAAAAAAAAAAAAPAvD8b9VLMAAAEA - - - - - H4sIAAAAAAAACu3c20rDQBAG4Lz/O1lBL6q3HloVD/sceuMUWppeGKOwmbL7ffBDSgM7sJDdzEKGAQAAAAAAAAAAAIBaPiNf2UUAAAAAAAAAAAAAQEdKdgEAAFRVRtebrCJY1F3kPrsI0mwjTxP/l4XqIMdr5C3yuP/tud+f58hLdhGkeY98ZBcB/MvDcFy/6Y/1u2/Wb8b0dNq3e0//6Rtbv/V0aNuhpwMAQJt27wIluwigqpJdAGnKjHv0/fqm79e+qbNefb/2Oevtm7NeDuz3+ma/1zf7PQAAAAAAAAAAMlxEVtlFcGI8J7eVx7qKXFceg79Zck7WkZuFxmKeqTm5rDhu7WcNAHA+rPsA0Laa/QMAAAAAAAAAAAAAAADOxzeEbpmpAAABAA== - - - - - H4sIAAAAAAAACu3OAQ0AAAgDoPfPZyBj3E1IQAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwy7QDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPLYaZVTEAAAEA - - - - - H4sIAAAAAAAACu3cQQqDMBAF0Liytr1C73/NIrgqoVA1nZh5b+PGxQdlNF9MKQAAAAAAAABAhHt0AAAAAAAAgIE8ogMAAAAAX83b0Roe8nhGBwAAAICij8pqTy/xmk6PcViPma7MPBjf5zWeq2cBtOeZA0BrvscDe9XmR6v3V91Wv3QmwEp/AQAAwC9u0QEAAAAAACAx/xBAbvbzBQAAAKBGXwQAAFyNven+w3oRODIHzBDO4l4a0xIdAOiOeQ8AAAAAAAAAAAAAAJDXG8ZnpKUAAAEA - - - - - H4sIAAAAAAAACu2T22prQQxD91P//5NLHgohNMlcbC95tgSihXP2WNZyf67r+rFt27Zt27Zt27Zt27bl/Z/oTDbD/U90Npvj/vfTPs/fROezazi/E53b5thfAtltjv0lkN9muF8CO9gcfzq/ncN55Bt6JzuW+Yzo3WyG+yWwn73HfPdG6F3tee6j373+2/P/p3e157XzDr2rvcY+6i16XzuPVdQ7NsOdzmabvV3H/Fl0TptjfwlktTn2l0Bem2N/CWS2c5mP3gW9hx3P/k8j39C72DH8d2+G3sleZxj1zspbNsf9SnozaoatyX7lBiLm2ftsqudVZrAZ9iv3QHd0ohW7/ia6sxOs3u830fk6u0unI6IzdnOnHkdF5+zgrt3NiM6q5u59rYjOTPukjlZF51buic5awZ/cuSqP4u6q/J/VNU/Wuyq+nn6+/p4lBf6Rohkq/V2NqEvOUdEcM9hn9m7+Ou6wQ4V25tD9RHRK56H574ruaKVTOgO9f6bo/ez4W9j9xtb3J63ezac3ojI/56E77O53WrmZT8rKS/fX3e+08+03ReRbecuOv4GdeyAyRu9QkanTDax2OfutGvPMfJ1vQLXfKtFMIzupmPFp9k4WSjRTxRuIYNKB/Z9oppF8yNtbzaEgmmU0D2reynwF0Sw73MCMzJ+/gYw5o7zMn7+Bylvbma0gml8WF4L97FwFrWSlmY/0WHVjO/MUNJuVZj3LJ3vGziwFncC/6gY+zTJ/8zf/+96A+fOO2HN1ZkbmCq3kpDtb6ZO+TcUbWM2YsVt2nzTju/GvyjUzm+asdgM7+ap2yeqT5q1wAxn8szLO5u14A9WK5l+hjE5p7gT/3Wxd+He7gewuo/J04t/1BiJ7jc5AsP80P7JHmnfUHWTO7sh/JjPNWs3f+iGV9fdEd67kkW4oRe4V/f4pHumEUvRuWXM6e6QLBWXfAs1Bhf+7Lmhl7Ro9o5tHe6CVvXPWLHWP7k+rkn/GTFWP7q6iqv0jZmXmruZ/8g2M7pbZ7W7215kn83+I4L86Nyt/RMaZN9SkfgOk7sD/oegbmNlVjf/Py+934P9Q1g3M7qzS2e5+Kvc8K5U7oBS1V+cOHrrbHUTu0233/5TJP6KPqC4zdjiB/0NVN0D2Us2/2w08VHkHlf1k5VXaMUrVN5DZXUUe87+PV/rqKLpnVa901V1050pe7egE0d0reLWf00Rz6Mbfd3CGo7s4RTQXBf5RPXQXzYjkH7V/d9GcSP7RHXQXzYziH71/V9HMaP5VHaiKZqbQMZ2bvBF6Z9X+6F2q7oLeTb2n1Zl0F3f0fwzURHd0sk8U3Wknq4jMQjPoyF/pdqJEs6DdXVE70BxUfUfRnav6taPTRfdtf7/DCtH7RvsXoMjHOgAAAQA= - - - - - H4sIAAAAAAAACu3BAQEAAACAkP6v7ggKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAauuOl9cAAAEA - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + H4sIAAAAAAAACu2cy4rbQBBFFfIHWSZg8JcMhsAku0wW8x2B+X9IDDHRKGqpH/W4VX0Xd+lRVZ2jatmD/fphWV4ZhmEYhmEYhmEYhoHPx6Uc79oYH+7knzc13Mk/Z1rYk3/stLIm/zwh93nD+36+SDAn/7gh+zkifZ+TPX60mZM9Zqy4kz1OLJmTPU48uJP/nLzJPjZ7ib/lPYMZo3Gvkjl+NPcz2WPHYjeTO154Hs8bsp83fBafM5rPeAxuRt/T0YO4kWRP/rEizZ4exMgIT3oQN1LcuBfiRYMRPcCPNhc+J+DGkgMdwIn37OkBPnereSPWlDHoM6YHGLNlrXkScZZ8Lph7fnw2nHtmvb0g98Q52fXl0aNVTZFmgsBfqnfPehD6j8hacg5edWbjjTTTSLVm4o4yz2j1RmeONs9o9ZI/+WdxIFoP3lyzuRCtZm+W2TyIUmdGp8gSLz38vF2IuHfQ08rMYy9EPncipGe2Vh54nDPWfiNkpB9JF0b2DQLzzA54zlFj1si1oTrQ2gvajCP5icof7V5DqCELf00HtDhEYJ/VgdGeZuMfyYGWuXl4R/44DpB/Xv418yP/3PzPZkj+svV+Xv7Fm3vtDMlfh//XP/nyN8gOkL88/9uK/V6Q+Es4QP7v+a/v/ZrM5IC2VxH5Wzigzb+WF/nXRYqJtQNn15mB/7MQf2kHpO/Pnmtp1IzGX2IHaPGx4l+6FvnXs9biZOmA5i5BYr/lX3MGeNTYMk9v7pH5n+0ArxrpgD9/lJq1HcvigCR/tJrJX67nTwH504Gc/Fvr15xLRA96ajrjb82+tY9oDmi4MFIHIv/W3qI6MOKC1LUz8O+dpTd3hCCe/6OMou8D8h/nRAf6+O99BuzNvZeR1b6JnDv/KDugt0c6MDd/etDG/wcofwku3jvHm3eJP/r7QE8Heq5ptVtGr1HLP5sDvR5I/23N2rPzl7qPNK7pPZdZ+Hs5cHZd75nMxN/TgVIN3vMY4X/bYY/OH8kDlPTyv9/7P5f/Pw/07oceyPde2v17Z4F3X9b8I3rQ2tuW/X33v5F/OA96e9q79zPw1/QAyYfRHlr40wE8D6T5P5/wj+jAuvarsg+R2D/47+2AX0n5r6O9F7T8kKynxP9tycP/yAFvH7yzx399Btx25uTNUtsFbybe/Gu+C+zNkB7o8rf+LrB3ZvWg9P+f2t+D8OZGD/T49/wmkDc/eiDHv9eBbE5k9uCMPx3IvQ/W/I8ckPIgqwveHC3404P3/C8JPOjhX/veIKsfR/yjObDl37IDXjYZ5X05mCmKI+s615mNf8mJpwpmT4WUZluas4YXNde+LPu1zs7/aDc88r3jddv9IOHH3t5pzfa1Ec+BPf7SPmj59K0QK/4ZzoEa/mg+1PpS8mObK/l3p4cPoldXIQ9m49/qBvpueeyDrQu1bkR7BtDkL+2P5Rm1PhvOEpl/FBckd1Drs8SZBxnYZ/ZD4lw48iDD54D0oN+DyOx/A/Jq0JkAAAEA + + + + + H4sIAAAAAAAACu2dS48cNRDHPUoUKQp8JT5FVgQWVpsD3wEOUQ6QA5G4ceLUIHGCj8GR55lTeIblERFIwkoBMcVOhdpaP6r86LZ76ic5k52Z7umZf7lctstu5wzDMAzDMAzDMAzDGIHDjXOveMrLm6WvzGgJ6n5zW566s4LA/181/VcJ1R34i7xG7cD0Xx+HO91P2fNPSfndWf0fnVCbfpNoChpTOzjdPXd9+56XtuVF039IDpnOPnj9p1i9Hxeq/Z/ufEznPH9j20/twfQfj1hMF+KUPJr+Y3MU0ezL3SOv+1x3bPut3e+XVFxHNZb4AHqMad8nhx6dJdqiro8E7zXt+4O36SG4LfwqPP+JM5/fG766XhMeA5j2fSCt65xfCj4T/IbF+svA4zicg5GA43NQDnb6feF53wN3/px/b8s/7BxW9+eHjtFINYe66tOM9vuekPdyqM2Y7svA51uRk8gx1xOaxfr9FPPzyxHS3Qedf5HU0yNyTl9sZ/V9WSRzMSW++QY71nz88vA+XKjOW997fWB95zEYz61CrF1eDxJfT7Ecm/XA+3SSMVjTf2ykY7W+/vjD3ePxxnKtR+TQE9v54rzfPM/z/pqNw45HatyFao79u4PAMfBe8wNjkBrL4X152g8Hm3niOQYxO+iX1PycpC9Px2oOEuMCaAe2Hmt5pGN42nG7UHsA+ObzzDfMi2ROHuflcsbwbiT8AJ6fY3bQnlSdrzW3wtsEDcdmA9VJ1fmS+p6Cz+XE7AH9gvmCOlDdQ7m1c+fM3BD6BehTmC/Ip0V8V5NUrIiYL9AjbeeXnqOV2gD2Hc0G4khi+97m5qW5XoD5gouk5mtwnVwvdZ4D+n+vPGbffYE0Fwfprc5TjiKxqQ/8rvtmAzlraHqt8xT0/39kHMvHk+eyh9B61dpj21xzTT3puc5TQu2/b04ptr8HcFzpt0/pKl3Xor0e3/oZKSOshfDVm5L1gHytP8DHkFr5hVhd9OVJ+ea+cu2KMoKPB7BP+tidxXv4Pb/OPF/s9+e+Y+k8lNj8eIyvAs+PtgZCm0Ma4pQ9Ivg7hXKSSvX/sezw6ixtz1okfXzeB4zlmp86v+8PMdrvlWK07wP639s+fkOeA7/vyytqUVr6yFzf/iDzONj/bLS+r6/+994/0fS1ahynZam+bw6o/7fb8p3rX/va/gfmOCA3Psc2Qsfw53seG+c5AD1r3wJpjiQnlWPto7Vf8PXhe7S5XtHYAsZ50nlSH7XGQfjc4g/urC9Pr9PQkdKV/q7SvJkYObZAdcf2O3adhg5fbIzjh744Pze3MidekI7b1NRfOrfSeu5lTmI6xvp6mtzK2Pl9vxlqD/10uscWwOfqSvVvsT/gSH3kVDsg/S6l9nCs1OGeOxvTydG/9Z6QQM99Io7EBrTfRRozPIy/HEQz91Qyt1nKKPGJZJw8x6/lxAy+uSe+L6f0d601/5PLKG2BtJ9XYs8xW9CMTz1RXIcmx7MVI9kA16fVWF9pvDCS/sAo7QAS+91a5MTD5+GYjm9uFfoCdJ7W9G+LpD2o+Z18OsXaBNO/PZoxwlJQJ6zjqfwK038eYjZQsw0IrcWguTa0X7BW/Wk/tRdiv2Etu/Z9RizPaq36c3oYUw75AGifa/kA0Ck0FnSf/F877je6/sjSOTex37GGDUjHH7Q552vRnzO3Pfj0oXsq1fh++Bl0rpffT1n7OWvVnzPHGFNrHwCE5g2wL2j6h6E+4barbw+15gpzPwvGh0x/GaB/izlIen8UH7XHBDDeA04yzl+Sv1aTlvq/vS1vsXKHvF5zPeKcY0K+e7GU7Au4FK1yzlH3O6k3EmrYwlxjQjVZsh1oUfdBe43uPkpsIRUL9jBuQVnSB9TWv4b2HLzG14TXqtlDbd9tQKP/R4nXW2jvQ5rzN9o+eku0Axr9Jxe3gduF1yJFes2aOtWDL5hb/5zY773Ia73pD3AbCM3b97CXYsn+0BpK9kYJ6T+X7we0PlvrB3poD1rYQumeOJc3Yf3nqvuUEj+QYmlfQClZ41Z7T5ye9Nf25bR5nT3ZABC7/+9ce3z0pD8lpx8rua9XL+1BL4T0f2db3pjzQhg5+ktjQ8DswLkrkfYfWNIH5I7rjRwXLMHkwmMAS7cBSM48nCbW3mcbgH22em0DkNJ1YD4b4HuX7mt78FzHbQBSOqeh6W/tkx1c3n3HyfXdBtTSJJVPAuTmflFG3Duq134gpbSd1sQFJTmvvB/6uWvjV0J7yGg+A/f87T0GQFrnftb4XK4/rCXC3OIadkD3qoI8xU+35TN35tugaH1XbBwY6MkHALXXgtX+XDgnakJBG/jEyfbJj+2fHxrbAF+T03aNpD9Q0w/E1hogJ+RzU/VXO+9Ltcy5PwyAcQ2cax/0B2q3Bdr2INTmYpypvcdFDrgvN123ptEf+n+wR/eI+gO14ypNXEDrLd9T7Kc6l5N1nxTtHNFo7b+PJe3AR+7+6Ln34IB2Pzf3+91t+TDy+gj6I7XzX2vsR0uRtguhe+Lwc5XkA1zdpLUHRtK/1Vh+KEb4WXke2Fsu1zfQNeu17ocV8/vISPoDrcdwS31Cjbiwlp+T6N/bGJCU1vN6kj3sWgDjB7X0n1x6HQAwqg3ccvOMw6dyudA+HsdP8x8+G4LnWt0PT+IDgNHaAeDNbblL/l5qfi8n12+u+3utWX8AbADXKqMtLL3HTU+sXX8K2sJd9vw+zfsj2J+Q6j9qDODD5xOAfbSDycliQGANPoDj8wn7ZgcaH/C6W48foOyzHUj1R9AOsKzJHmLx4lrtQKs/h9vDWmyil/5jK67uvsfk5DGAlLXEi/viD0p9gA/qF9ZgC7fY32taO9JCf8oaYkeuPzC6L8A5zMnVbwN8+GKFUeIGn/7IqPddoLT2ARJS9rGUjVxyK9SfXXMP+ksI2UiJXVwSlNXpz5i25X1S5mgPaiLxHSUllis1YhxA8w0fwT/s2ic3tj0swWh+4HLgevkaaWBy+bbwsTtvS2u1q9H0v4Dw+icn1xO0/yBwngduvr4H54UG51zTmMAzyPe5H37XMyZ33h5C2oeOobYQ8xu1fEltOxjBB9DcNFjfdqWja56cznYeeY5d0g5G0B+R5i5quMqP8ZxDu9bxQjwSua5rm/I2JdcWWt2DYSku5Dlv/tdX0hbkcE1gP8H3EiZXHl9q7WCkuq+lRh5rjRLySc/vnue+osa4VsoOaq3JmYt/ATb8NvgAAAEA + + + + + H4sIAAAAAAAACu3bMQrAIAwFUHOrnqU6tPe/RCkOHUUQrPjelDGQ5ROSlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2dcbsDpjpMn8AoEORHQAAaMhRc6O9437eXeMdXw0AAAAAAAAAAACMcbjLAwAW5b8A+sn/AAAAAAAAAAAAAPAvD8b9VLMAAAEA + + + + + H4sIAAAAAAAACu3c20rDQBAG4Lz/O1lBL6q3HloVD/sceuMUWppeGKOwmbL7ffBDSgM7sJDdzEKGAQAAAAAAAAAAAIBaPiNf2UUAAAAAAAAAAAAAQEdKdgEAAFRVRtebrCJY1F3kPrsI0mwjTxP/l4XqIMdr5C3yuP/tud+f58hLdhGkeY98ZBcB/MvDcFy/6Y/1u2/Wb8b0dNq3e0//6Rtbv/V0aNuhpwMAQJt27wIluwigqpJdAGnKjHv0/fqm79e+qbNefb/2Oevtm7NeDuz3+ma/1zf7PQAAAAAAAAAAMlxEVtlFcGI8J7eVx7qKXFceg79Zck7WkZuFxmKeqTm5rDhu7WcNAHA+rPsA0Laa/QMAAAAAAAAAAAAAAADOxzeEbpmpAAABAA== + + + + + H4sIAAAAAAAACu3OAQ0AAAgDoPfPZyBj3E1IQAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwy7QDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPLYaZVTEAAAEA + + + + + H4sIAAAAAAAACu3cQQqDMBAF0Liytr1C73/NIrgqoVA1nZh5b+PGxQdlNF9MKQAAAAAAAABAhHt0AAAAAAAAgIE8ogMAAAAAX83b0Roe8nhGBwAAAICij8pqTy/xmk6PcViPma7MPBjf5zWeq2cBtOeZA0BrvscDe9XmR6v3V91Wv3QmwEp/AQAAwC9u0QEAAAAAACAx/xBAbvbzBQAAAKBGXwQAAFyNven+w3oRODIHzBDO4l4a0xIdAOiOeQ8AAAAAAAAAAAAAAJDXG8ZnpKUAAAEA + + + + + H4sIAAAAAAAACu2T22prQQxD91P//5NLHgohNMlcbC95tgSihXP2WNZyf67r+rFt27Zt27Zt27Zt27bl/Z/oTDbD/U90Npvj/vfTPs/fROezazi/E53b5thfAtltjv0lkN9muF8CO9gcfzq/ncN55Bt6JzuW+Yzo3WyG+yWwn73HfPdG6F3tee6j373+2/P/p3e157XzDr2rvcY+6i16XzuPVdQ7NsOdzmabvV3H/Fl0TptjfwlktTn2l0Bem2N/CWS2c5mP3gW9hx3P/k8j39C72DH8d2+G3sleZxj1zspbNsf9SnozaoatyX7lBiLm2ftsqudVZrAZ9iv3QHd0ohW7/ia6sxOs3u830fk6u0unI6IzdnOnHkdF5+zgrt3NiM6q5u59rYjOTPukjlZF51buic5awZ/cuSqP4u6q/J/VNU/Wuyq+nn6+/p4lBf6Rohkq/V2NqEvOUdEcM9hn9m7+Ou6wQ4V25tD9RHRK56H574ruaKVTOgO9f6bo/ez4W9j9xtb3J63ezac3ojI/56E77O53WrmZT8rKS/fX3e+08+03ReRbecuOv4GdeyAyRu9QkanTDax2OfutGvPMfJ1vQLXfKtFMIzupmPFp9k4WSjRTxRuIYNKB/Z9oppF8yNtbzaEgmmU0D2reynwF0Sw73MCMzJ+/gYw5o7zMn7+Bylvbma0gml8WF4L97FwFrWSlmY/0WHVjO/MUNJuVZj3LJ3vGziwFncC/6gY+zTJ/8zf/+96A+fOO2HN1ZkbmCq3kpDtb6ZO+TcUbWM2YsVt2nzTju/GvyjUzm+asdgM7+ap2yeqT5q1wAxn8szLO5u14A9WK5l+hjE5p7gT/3Wxd+He7gewuo/J04t/1BiJ7jc5AsP80P7JHmnfUHWTO7sh/JjPNWs3f+iGV9fdEd67kkW4oRe4V/f4pHumEUvRuWXM6e6QLBWXfAs1Bhf+7Lmhl7Ro9o5tHe6CVvXPWLHWP7k+rkn/GTFWP7q6iqv0jZmXmruZ/8g2M7pbZ7W7215kn83+I4L86Nyt/RMaZN9SkfgOk7sD/oegbmNlVjf/Py+934P9Q1g3M7qzS2e5+Kvc8K5U7oBS1V+cOHrrbHUTu0233/5TJP6KPqC4zdjiB/0NVN0D2Us2/2w08VHkHlf1k5VXaMUrVN5DZXUUe87+PV/rqKLpnVa901V1050pe7egE0d0reLWf00Rz6Mbfd3CGo7s4RTQXBf5RPXQXzYjkH7V/d9GcSP7RHXQXzYziH71/V9HMaP5VHaiKZqbQMZ2bvBF6Z9X+6F2q7oLeTb2n1Zl0F3f0fwzURHd0sk8U3Wknq4jMQjPoyF/pdqJEs6DdXVE70BxUfUfRnav6taPTRfdtf7/DCtH7RvsXoMjHOgAAAQA= + + + + + H4sIAAAAAAAACu3BAQEAAACAkP6v7ggKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAauuOl9cAAAEA + + + diff --git a/data/music/dark_quest.flac b/data/music/dark_quest.flac index ede999f738..a74e320eac 100644 Binary files a/data/music/dark_quest.flac and b/data/music/dark_quest.flac differ diff --git a/data/music/dark_quest.ogg b/data/music/dark_quest.ogg index 09bfdbe1c4..4f46b750c4 100644 Binary files a/data/music/dark_quest.ogg and b/data/music/dark_quest.ogg differ diff --git a/data/music/night_town.flac b/data/music/night_town.flac index d6e6a52ba4..14ecb76a52 100644 Binary files a/data/music/night_town.flac and b/data/music/night_town.flac differ diff --git a/data/music/night_town.ogg b/data/music/night_town.ogg index f1bf179f74..fa4ee09746 100644 Binary files a/data/music/night_town.ogg and b/data/music/night_town.ogg differ diff --git a/data/music/settlement_of_the_frontier.ogg b/data/music/settlement_of_the_frontier.ogg index f36eb8184f..2fb8b50061 100644 Binary files a/data/music/settlement_of_the_frontier.ogg and b/data/music/settlement_of_the_frontier.ogg differ diff --git a/data/music/spinning_tavern.ogg b/data/music/spinning_tavern.ogg index c013bd0cbb..858026343f 100644 Binary files a/data/music/spinning_tavern.ogg and b/data/music/spinning_tavern.ogg differ diff --git a/doc/CHANGES.txt b/doc/CHANGES.txt index 6370a256ef..20be3f04f1 100644 --- a/doc/CHANGES.txt +++ b/doc/CHANGES.txt @@ -2,6 +2,24 @@ Changelog --------- --------- +1.49 + +*world* +- outfit prices in Gwen's shop reduced + +*web client* +- improved lighting effects + +*bug fixes* +- fixed invalid head outfit index of Mr Ross +- fixed silent NPC paths +- fixed desktop webview client window freezing on Windows when closed +- fixed nativehelper extension process not exiting cleanly on Windows +- set User-Agent string for Wikipedia API requests +- trimmed large music files to decrease size to 2MB or less +- fixed Gourmet achievement + + 1.48 *world* @@ -688,7 +706,7 @@ Changelog *other* - started to work on a list of all contributors, please help to add to it: - https://github.com/arianne/stendhal/blob/master/doc/contributors.md + https://codeberg.org/arianne/stendhal/src/commit/master/doc/contributors.md 1.34 @@ -1396,7 +1414,7 @@ the huge task of creating & replacing sprite & tileset images. - Support Postgres databases in addition to H2 (recommended) and MySQL - Setup Travis for continuous integration tests and fixed some order dependent tests - Migrated test coverage analysis from Cobertura to Jacoco -- Git repository is mirrored instantaneously between Github and Sourceforge in both directions +- Git repository is mirrored instantaneously between Codeberg, Github and Sourceforge in both directions - Yearly special event quests now pick up the current year as part of their quest slot automatically - Backend for puzzles @@ -2559,7 +2577,7 @@ read https://stendhalgame.org/wiki/Automatic_Software_Updater - Tab completion for online nick names works now without /who called before - orc spearman creature - /atlas /faq and /manual commands to open useful pages in browser -- Harold tweets trades, the official stendhal server uses https://twitter.com/stendhaltrade +- Harold tweets trades, the official stendhal server uses https://mastodon.social/@stendhaltrade - magical damage types of weapons (fire, ice ...) - resistance/vulnerability of creatures to magical weapons (fire, ice, ...) - attack sweeps look different depending on the attack type diff --git a/libs/marauroa.jar b/libs/marauroa.jar index 2ab331f0b5..746bc22386 100644 Binary files a/libs/marauroa.jar and b/libs/marauroa.jar differ diff --git a/package-lock.json b/package-lock.json index 32126ed830..0c823a1bb8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,736 @@ { "name": "stendhal", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "stendhal", "version": "1.0.0", "license": "AGPL-3.0-or-later", + "devDependencies": { + "esbuild": "0.28.1", + "showdown": "^2.1.0", + "typedoc": "^0.28.15", + "typescript": "^5.9.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@gerrit0/mini-shiki": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.20.0.tgz", + "integrity": "sha512-Wa57i+bMpK6PGJZ1f2myxo3iO+K/kZikcyvH8NIqNNZhQUbDav7V9LQmWOXhf946mz5c1NZ19WMsGYiDKTryzQ==", + "dev": true, + "license": "MIT", "dependencies": { - "typescript": "^5.1.0" + "@shikijs/engine-oniguruma": "^3.20.0", + "@shikijs/langs": "^3.20.0", + "@shikijs/themes": "^3.20.0", + "@shikijs/types": "^3.20.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.20.0.tgz", + "integrity": "sha512-Yx3gy7xLzM0ZOjqoxciHjA7dAt5tyzJE3L4uQoM83agahy+PlW244XJSrmJRSBvGYELDhYXPacD4R/cauV5bzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.20.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.20.0.tgz", + "integrity": "sha512-le+bssCxcSHrygCWuOrYJHvjus6zhQ2K7q/0mgjiffRbkhM4o1EWu2m+29l0yEsHDbWaWPNnDUTRVVBvBBeKaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.20.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.20.0.tgz", + "integrity": "sha512-U1NSU7Sl26Q7ErRvJUouArxfM2euWqq1xaSrbqMu2iqa+tSp0D1Yah8216sDYbdDHw4C8b75UpE65eWorm2erQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.20.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.20.0.tgz", + "integrity": "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/commander": { + "version": "9.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/lunr": { + "version": "2.3.9", + "dev": true, + "license": "MIT" + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/showdown": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^9.0.0" + }, + "bin": { + "showdown": "bin/showdown.js" + }, + "funding": { + "type": "individual", + "url": "https://www.paypal.me/tiviesantos" + } + }, + "node_modules/typedoc": { + "version": "0.28.15", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.15.tgz", + "integrity": "sha512-mw2/2vTL7MlT+BVo43lOsufkkd2CJO4zeOSuWQQsiXoV2VuEn7f6IZp2jsUDPmBMABpgR0R5jlcJ2OGEFYmkyg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gerrit0/mini-shiki": "^3.17.0", + "lunr": "^2.3.9", + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "yaml": "^2.8.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18", + "pnpm": ">= 10" + }, + "peerDependencies": { + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x" } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -23,13 +738,29 @@ "engines": { "node": ">=14.17" } - } - }, - "dependencies": { - "typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==" + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } } } } diff --git a/package.json b/package.json index 6619d1b7a2..9b000be13f 100644 --- a/package.json +++ b/package.json @@ -28,11 +28,10 @@ }, "homepage": "https://arianne-project.org", "private": true, - "dependencies": { - "typescript": "^5.2.0" - }, "devDependencies": { + "esbuild": "0.28.1", "showdown": "^2.1.0", - "typedoc": "^0.25.12" + "typedoc": "^0.28.15", + "typescript": "^5.9.3" } } diff --git a/src/games/stendhal/client/gui/KTextEdit.java b/src/games/stendhal/client/gui/KTextEdit.java index 67a3ebe3fa..5c0448eeef 100644 --- a/src/games/stendhal/client/gui/KTextEdit.java +++ b/src/games/stendhal/client/gui/KTextEdit.java @@ -569,7 +569,7 @@ class KTextEdit extends JComponent { */ class LinkListener { /** Allowed patterns for links to be opened in a browser. */ - String p = "^(http|https)://(|www\\.)(faiumoni.de|stendhalgame.org|arianne.sf.net|arianne-project.org|arianne.sourceforge.net|sourceforge.net|sf.net|download.oracle.com|libregamewiki.org|freesound.org|opengameart.org|openclipart.org|github.io|github.com|arianne.github.io|postsai.github.io)(/.*)?"; + String p = "^(http|https)://(|www\\.)(faiumoni.de|stendhalgame.org|arianne.sf.net|arianne-project.org|codeberg.org|arianne.sourceforge.net|sourceforge.net|sf.net|download.oracle.com|libregamewiki.org|freesound.org|opengameart.org|openclipart.org|github.io|github.com|arianne.github.io|postsai.github.io)(/.*)?"; final Pattern whitelist = Pattern.compile(p); diff --git a/src/games/stendhal/client/gui/imageviewer/RPEventImageViewer.java b/src/games/stendhal/client/gui/imageviewer/RPEventImageViewer.java index 45d5d413de..7b14d5dcdc 100644 --- a/src/games/stendhal/client/gui/imageviewer/RPEventImageViewer.java +++ b/src/games/stendhal/client/gui/imageviewer/RPEventImageViewer.java @@ -1,6 +1,6 @@ /* $Id$ */ /*************************************************************************** - * (C) Copyright 2003-2010 - Stendhal * + * (C) Copyright 2003-2025 - Stendhal * *************************************************************************** *************************************************************************** * * @@ -12,6 +12,7 @@ ***************************************************************************/ package games.stendhal.client.gui.imageviewer; +import java.net.URI; import java.net.URL; import org.apache.log4j.Logger; @@ -53,7 +54,7 @@ public final class RPEventImageViewer { try { URL url = null; if (path.startsWith("http://") || path.startsWith("https://")) { - url = new URL(path); + url = new URI(path).toURL(); } else { url = DataLoader.getResource(path); } diff --git a/src/games/stendhal/client/sprite/DataLoader.java b/src/games/stendhal/client/sprite/DataLoader.java index ba7a037418..9b3e3b8c5d 100644 --- a/src/games/stendhal/client/sprite/DataLoader.java +++ b/src/games/stendhal/client/sprite/DataLoader.java @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2012-2022 Faiumoni e. V. * + * (C) Copyright 2012-2025 Faiumoni e. V. * *************************************************************************** *************************************************************************** * * @@ -15,6 +15,8 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Paths; import java.util.Enumeration; @@ -60,8 +62,8 @@ public class DataLoader { File file = contentFilenameMapping.get(slashlessName); if (file != null) { try { - return new URL("jar:" + file.toURI().toASCIIString() + "!/" + slashlessName); - } catch (MalformedURLException e) { + return new URI("jar:" + file.toURI().toASCIIString() + "!/" + slashlessName).toURL(); + } catch (MalformedURLException | URISyntaxException e) { logger.error(e, e); } } diff --git a/src/games/stendhal/client/sprite/SpriteStore.java b/src/games/stendhal/client/sprite/SpriteStore.java index 7ad4600047..cdc00ebd3c 100644 --- a/src/games/stendhal/client/sprite/SpriteStore.java +++ b/src/games/stendhal/client/sprite/SpriteStore.java @@ -1,6 +1,6 @@ /* $Id$ */ /*************************************************************************** - * (C) Copyright 2003-2010 - Stendhal * + * (C) Copyright 2003-2025 - Stendhal * *************************************************************************** *************************************************************************** * * @@ -22,6 +22,8 @@ import java.awt.HeadlessException; import java.awt.Image; import java.awt.image.BufferedImage; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import javax.imageio.ImageIO; @@ -353,7 +355,7 @@ public class SpriteStore { URL url; if (ref.startsWith("http://") || ref.startsWith("https://")) { logger.info("Loading sprite from a URL..."); - url = new URL(ref); + url = new URI(ref).toURL(); } else { url = DataLoader.getResource(ref); } @@ -372,7 +374,7 @@ public class SpriteStore { // use ImageIO to read the image in sourceImage = ImageIO.read(url); - } catch (final IOException e) { + } catch (final IOException | URISyntaxException e) { logger.error("Failed to read: " + ref, e); if (ref.equals(FAILSAFE_ICON_REF)) { diff --git a/src/games/stendhal/client/update/HttpClient.java b/src/games/stendhal/client/update/HttpClient.java index 50d83d12cc..5cb0b66690 100644 --- a/src/games/stendhal/client/update/HttpClient.java +++ b/src/games/stendhal/client/update/HttpClient.java @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2016 - Stendhal * + * (C) Copyright 2003-2025 - Stendhal * *************************************************************************** *************************************************************************** * * @@ -20,6 +20,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.SocketTimeoutException; +import java.net.URI; import java.net.URL; import java.util.HashSet; import java.util.Properties; @@ -109,13 +110,15 @@ public class HttpClient { // sometimes problems with the webservers being slow or not responding // at all. try { - final URL url = new URL(urlString); + final URL url = new URI(urlString).toURL(); int retryCount = 0; int myTimeout = timeout; while (is == null) { retryCount++; try { connection = (HttpURLConnection) url.openConnection(); + // Wikipedia API now requires User-Agent string + connection.setRequestProperty("User-Agent", "StendhalClient/1.49.5 (https://arianne-project.org/contact.html)"); connection.setConnectTimeout(myTimeout); connection.setInstanceFollowRedirects(true); connection.setUseCaches(false); @@ -134,7 +137,7 @@ public class HttpClient { if(!passedRedirects.contains(newUrl)) { // open the new connnection again passedRedirects.add(newUrl); - connection = (HttpURLConnection) new URL(newUrl).openConnection(); + connection = (HttpURLConnection) new URI(newUrl).toURL().openConnection(); redirect = isRedirect(connection.getResponseCode()); } else { System.err.println(String.format("The URL '%s' leads into a circular redirect.", url)); diff --git a/src/games/stendhal/common/Debug.java b/src/games/stendhal/common/Debug.java index 69197224c7..02e0f91f7b 100644 --- a/src/games/stendhal/common/Debug.java +++ b/src/games/stendhal/common/Debug.java @@ -26,7 +26,7 @@ public class Debug { /** version. */ // Note: This line is updated by build.xml using a regexp so be sure to adjust it in case you modify this line. - public static final String VERSION = "1.48"; + public static final String VERSION = "1.49.5"; /** pre release suffix */ // Note: This line is updated by build.xml using a regexp so be sure to adjust it in case you modify this line. diff --git a/src/games/stendhal/server/core/config/zone/TMXLoader.java b/src/games/stendhal/server/core/config/zone/TMXLoader.java index 56dbcb40e8..1b55092d6d 100644 --- a/src/games/stendhal/server/core/config/zone/TMXLoader.java +++ b/src/games/stendhal/server/core/config/zone/TMXLoader.java @@ -1,6 +1,6 @@ /* $Id$ */ /*************************************************************************** - * (C) Copyright 2003-2024 - Stendhal * + * (C) Copyright 2003-2025 - Stendhal * *************************************************************************** *************************************************************************** * * @@ -16,7 +16,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import java.net.MalformedURLException; -import java.net.URL; +import java.net.URI; import java.util.List; import java.util.Properties; import java.util.zip.GZIPInputStream; @@ -302,8 +302,8 @@ public class TMXLoader { final String xmlFile = makeUrl(filename); // xmlPath = makeUrl(xmlPath); - final URL url = new URL(xmlFile); - is = url.openStream(); + final URI uri = new URI(xmlFile); + is = uri.toURL().openStream(); } // Wrap with GZIP decoder for .tmx.gz files diff --git a/src/games/stendhal/server/core/engine/StendhalRPRuleProcessor.java b/src/games/stendhal/server/core/engine/StendhalRPRuleProcessor.java index 5e51bf5ce9..b24ad8beb3 100644 --- a/src/games/stendhal/server/core/engine/StendhalRPRuleProcessor.java +++ b/src/games/stendhal/server/core/engine/StendhalRPRuleProcessor.java @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2024 - Marauroa * + * (C) Copyright 2003-2025 - Marauroa * *************************************************************************** *************************************************************************** * * @@ -16,7 +16,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; -import java.net.URL; +import java.net.URI; +import java.net.URISyntaxException; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; @@ -376,8 +377,7 @@ public class StendhalRPRuleProcessor implements IRPRuleProcessor { protected void executeZonePreLogic() { for (final IRPZone zoneI : SingletonRepository.getRPWorld()) { - final StendhalRPZone zone = (StendhalRPZone) zoneI; - zone.preLogic(); + ((StendhalRPZone) zoneI).preLogic(); } } @@ -544,9 +544,9 @@ public class StendhalRPRuleProcessor implements IRPRuleProcessor { if (config.has("server_welcome")) { msg = config.get("server_welcome"); if (msg.startsWith("http://") || msg.startsWith("https://")) { - final URL url = new URL(msg); + final URI uri = new URI(msg); HttpURLConnection.setFollowRedirects(false); - final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + final HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection(); try { final BufferedReader br = new BufferedReader( new InputStreamReader(connection.getInputStream(), "UTF-8")); @@ -560,7 +560,7 @@ public class StendhalRPRuleProcessor implements IRPRuleProcessor { } } } - } catch (final IOException e) { + } catch (final IOException | URISyntaxException e) { if (firstWelcomeException) { logger.warn("Can't read server_welcome from marauroa.ini", e); firstWelcomeException = false; diff --git a/src/games/stendhal/server/core/rp/achievement/factory/ProductionAchievementFactory.java b/src/games/stendhal/server/core/rp/achievement/factory/ProductionAchievementFactory.java index 5acc7f1bea..d4da39bc6f 100644 --- a/src/games/stendhal/server/core/rp/achievement/factory/ProductionAchievementFactory.java +++ b/src/games/stendhal/server/core/rp/achievement/factory/ProductionAchievementFactory.java @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2023 - Stendhal * + * (C) Copyright 2003-2025 - Stendhal * *************************************************************************** *************************************************************************** * * @@ -26,7 +26,6 @@ import games.stendhal.server.entity.npc.behaviour.journal.ProducerRegister; import games.stendhal.server.entity.npc.condition.AndCondition; import games.stendhal.server.entity.npc.condition.PlayerProducedNumberOfItemsCondition; import games.stendhal.server.entity.npc.condition.QuestCompletedCondition; -import games.stendhal.server.entity.npc.condition.QuestStateStartsWithCondition; import games.stendhal.server.entity.player.Player; @@ -64,7 +63,7 @@ public class ProductionAchievementFactory extends AbstractAchievementFactory { Achievement.MEDIUM_BASE_SCORE, true, new AndCondition( new PlayerProducedNumberOfItemsCondition(1, foods), - new QuestStateStartsWithCondition("coal_for_haunchy","waiting;")))); + new QuestCompletedCondition("coal_for_haunchy")))); final List drinklist = producerRegister.getProducedItemNames("drink"); diff --git a/src/games/stendhal/server/entity/GuidedEntity.java b/src/games/stendhal/server/entity/GuidedEntity.java index 3b6b197c30..67026fc719 100644 --- a/src/games/stendhal/server/entity/GuidedEntity.java +++ b/src/games/stendhal/server/entity/GuidedEntity.java @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright © 2003-2024 - Arianne * + * Copyright © 2003-2025 - Arianne * *************************************************************************** *************************************************************************** * * @@ -530,7 +530,7 @@ public abstract class GuidedEntity extends ActiveEntity { */ @Deprecated public final boolean atMovementRadius() { - return Math.sqrt(getDistanceFromOrigin()) >= movementRadius; + return movementRadius > 0 && Math.sqrt(getDistanceFromOrigin()) >= movementRadius; } protected final Direction getDirectionFromOrigin() { diff --git a/src/games/stendhal/server/entity/npc/NPC.java b/src/games/stendhal/server/entity/npc/NPC.java index 434d3d3e7c..8b0a755d1e 100644 --- a/src/games/stendhal/server/entity/npc/NPC.java +++ b/src/games/stendhal/server/entity/npc/NPC.java @@ -1,6 +1,6 @@ /* $Id$ */ /*************************************************************************** - * (C) Copyright 2003-2024 - Marauroa * + * (C) Copyright 2003-2025 - Marauroa * *************************************************************************** *************************************************************************** * * @@ -326,10 +326,10 @@ public abstract class NPC extends DressedEntity { // handled in pre-logic return; } else { - if (atMovementRadius()) { - onOutsideMovementRadius(); - } if (!hasPath()) { + if (atMovementRadius()) { + onOutsideMovementRadius(); + } if (logger.isDebugEnabled()) { String title = getTitle(); String zone = getZone().getName(); diff --git a/src/games/stendhal/server/entity/npc/behaviour/impl/idle/package-info.java b/src/games/stendhal/server/entity/npc/behaviour/impl/idle/package-info.java index e2698479a6..05807f7b61 100644 --- a/src/games/stendhal/server/entity/npc/behaviour/impl/idle/package-info.java +++ b/src/games/stendhal/server/entity/npc/behaviour/impl/idle/package-info.java @@ -1 +1 @@ -package games.stendhal.server.entity.npc.behaviour.impl.idle; \ No newline at end of file +package games.stendhal.server.entity.npc.behaviour.impl.idle; diff --git a/src/games/stendhal/server/maps/ados/rosshouse/FatherNPC.java b/src/games/stendhal/server/maps/ados/rosshouse/FatherNPC.java index 7c33fcc042..5bbdc40927 100644 --- a/src/games/stendhal/server/maps/ados/rosshouse/FatherNPC.java +++ b/src/games/stendhal/server/maps/ados/rosshouse/FatherNPC.java @@ -1,6 +1,6 @@ /* $Id$ */ /*************************************************************************** - * (C) Copyright 2003-2010 - Stendhal * + * (C) Copyright 2003-2024 - Stendhal * *************************************************************************** *************************************************************************** * * @@ -75,7 +75,7 @@ public class FatherNPC implements ZoneConfigurator { }; - npc.setOutfit(1, 34, 7, null, 0, null, 27, null, 0); + npc.setOutfit(1, 34, 0, null, 0, null, 27, null, 0); npc.setPosition(12, 7); npc.setDirection(Direction.DOWN); npc.initHP(100); diff --git a/src/games/stendhal/server/maps/quests/CollectEnemyData.java b/src/games/stendhal/server/maps/quests/CollectEnemyData.java index 6ec4444c7d..9cf7392d62 100644 --- a/src/games/stendhal/server/maps/quests/CollectEnemyData.java +++ b/src/games/stendhal/server/maps/quests/CollectEnemyData.java @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright © 2020-2024 - Faiumoni e. V. * + * Copyright © 2020-2025 - Faiumoni e. V. * *************************************************************************** *************************************************************************** * * @@ -376,7 +376,7 @@ public class CollectEnemyData extends AbstractQuest { new ChatAction() { @Override public void fire(final Player player, final Sentence sentence, final EventRaiser raiser) { - npc.say("Thanks so much for you help. Now I have all the information I need to complete my #bestiary." + npc.say("Thanks so much for your help. Now I have all the information I need to complete my #bestiary." + " If you would like one of your own, I can sell you one."); player.addKarma(200.0); }})); diff --git a/src/games/stendhal/server/util/AsynchronousProgramExecutor.java b/src/games/stendhal/server/util/AsynchronousProgramExecutor.java index 6ad5355559..f80939b764 100644 --- a/src/games/stendhal/server/util/AsynchronousProgramExecutor.java +++ b/src/games/stendhal/server/util/AsynchronousProgramExecutor.java @@ -64,7 +64,7 @@ public class AsynchronousProgramExecutor extends Thread { } /** - * sends the message to the twitter account + * sends the message to the social media account * * @param cmd command */ diff --git a/src/games/stendhal/tools/contributors/SourceForgeContributorsGenerator.java b/src/games/stendhal/tools/contributors/SourceForgeContributorsGenerator.java index b3e23a99c8..b02a4de591 100644 --- a/src/games/stendhal/tools/contributors/SourceForgeContributorsGenerator.java +++ b/src/games/stendhal/tools/contributors/SourceForgeContributorsGenerator.java @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2023 - Stendhal * + * (C) Copyright 2003-2025 - Stendhal * *************************************************************************** *************************************************************************** * * @@ -18,7 +18,8 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.io.Reader; -import java.net.URL; +import java.net.URI; +import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; @@ -135,7 +136,7 @@ public class SourceForgeContributorsGenerator { @SuppressWarnings("unchecked") private void fetchProfiles(Map> contributors) throws IOException { for (Map.Entry> entry : contributors.entrySet()) { - try (Reader reader = new InputStreamReader(new URL("https://sourceforge.net/rest/u/" + entry.getKey() +"/profile").openStream(), "UTF-8")) { + try (Reader reader = new InputStreamReader(new URI("https://sourceforge.net/rest/u/" + entry.getKey() +"/profile").toURL().openStream(), "UTF-8")) { Map contributor = entry.getValue(); Map profile = (Map) JSONValue.parse(reader); String fullname = (String) profile.get("name"); @@ -149,7 +150,7 @@ public class SourceForgeContributorsGenerator { if (country != null && !country.trim().equals("")) { contributor.put("country", country); } - } catch (FileNotFoundException e) { + } catch (FileNotFoundException | URISyntaxException e) { System.err.println("Profile " + entry.getKey() + " does not exist."); } } diff --git a/src/games/stendhal/tools/updateprop/UpdatePropUpdater.java b/src/games/stendhal/tools/updateprop/UpdatePropUpdater.java index d58abb8b56..adf676f581 100644 --- a/src/games/stendhal/tools/updateprop/UpdatePropUpdater.java +++ b/src/games/stendhal/tools/updateprop/UpdatePropUpdater.java @@ -1,6 +1,6 @@ /* $Id$ */ /*************************************************************************** - * (C) Copyright 2003-2011 - Stendhal * + * (C) Copyright 2003-2025 - Stendhal * *************************************************************************** *************************************************************************** * * @@ -18,7 +18,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; -import java.net.URL; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -80,13 +81,14 @@ public class UpdatePropUpdater { * loads the current version of the update.properties * * @throws IOException in case of an input/output error + * @throws URISyntaxException */ - private void loadOldUpdateProperties() throws IOException { + private void loadOldUpdateProperties() throws IOException, URISyntaxException { prop = new Properties(); InputStream is; if (oldFile.indexOf(":") > 2) { - URL url = new URL(oldFile); - is = url.openStream(); + URI uri = new URI(oldFile); + is = uri.toURL().openStream(); } else { is = new FileInputStream(oldFile); } diff --git a/src/js/css/main.css b/src/js/css/main.css index 86d9b999e4..76765f1468 100644 --- a/src/js/css/main.css +++ b/src/js/css/main.css @@ -335,7 +335,7 @@ div.logcolR { background: url(/data/gui/danger.png) repeat; } -.qm-button, img#held-object { +.qm-button, canvas#held-object { position: absolute; display: none; opacity: 0.5; @@ -708,3 +708,16 @@ img#click-indicator { .pad-contents { padding: 0.5em; } + +table.shoptable { + border-collapse: collapse; +} + +table.shoptable th, table.shoptable td { + border: 1px solid var(--text-color); + padding: 2px; +} + +td.number { + text-align: right; +} diff --git a/src/js/jsxgraph-util.js b/src/js/jsxgraph-util.js deleted file mode 100644 index c2ab4938ab..0000000000 --- a/src/js/jsxgraph-util.js +++ /dev/null @@ -1,1169 +0,0 @@ -/* - From: http://jsxgraph.uni-bayreuth.de/wp/tag/gunzip/ - From: https://jsxgraph.org/wp/ - - Copyright 2008,2009 - Matthias Ehmann, - Michael Gerhaeuser, - Carsten Miller, - Bianca Valentin, - Alfred Wassermann, - Peter Wilfahrt - - This file is part of JSXGraph. - - JSXGraph is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - JSXGraph is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with JSXGraph. If not, see . -*/ - -/** - * @fileoverview Utilities for uncompressing and base64 decoding - */ - -/** - * @class Util class - * Class for gunzipping, unzipping and base64 decoding of files. - * It is used for reading GEONExT, Geogebra and Intergeo files. - * - * Only Huffman codes are decoded in gunzip. - * The code is based on the source code for gunzip.c by Pasi Ojala - * @see http://www.cs.tut.fi/~albert/Dev/gunzip/gunzip.c - * @see http://www.cs.tut.fi/~albert - */ -JXG = {}; -JXG.Util = {}; - -/** - * Unzip zip files - */ -JXG.Util.Unzip = function (barray){ - var outputArr = [], - output = "", - debug = false, - gpflags, - files = 0, - unzipped = [], - crc, - buf32k = new Array(32768), - bIdx = 0, - modeZIP=false, - - CRC, SIZE, - - bitReverse = [ - 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, - 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, - 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, - 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, - 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, - 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, - 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, - 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, - 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, - 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, - 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, - 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, - 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, - 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, - 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, - 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, - 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, - 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, - 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, - 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, - 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, - 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, - 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, - 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, - 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, - 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, - 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, - 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, - 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, - 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, - 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, - 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff - ], - - cplens = [ - 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, - 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 - ], - - cplext = [ - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, - 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99 - ], /* 99==invalid */ - - cpdist = [ - 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0007, 0x0009, 0x000d, - 0x0011, 0x0019, 0x0021, 0x0031, 0x0041, 0x0061, 0x0081, 0x00c1, - 0x0101, 0x0181, 0x0201, 0x0301, 0x0401, 0x0601, 0x0801, 0x0c01, - 0x1001, 0x1801, 0x2001, 0x3001, 0x4001, 0x6001 - ], - - cpdext = [ - 0, 0, 0, 0, 1, 1, 2, 2, - 3, 3, 4, 4, 5, 5, 6, 6, - 7, 7, 8, 8, 9, 9, 10, 10, - 11, 11, 12, 12, 13, 13 - ], - - border = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15], - - bA = barray, - - bytepos=0, - bitpos=0, - bb = 1, - bits=0, - - NAMEMAX = 256, - - nameBuf = [], - - fileout; - - function readByte(){ - bits+=8; - if (bytepos"); - return bA[bytepos++]; - } else - return -1; - }; - - function byteAlign(){ - bb = 1; - }; - - function readBit(){ - var carry; - bits++; - carry = (bb & 1); - bb >>= 1; - if (bb==0){ - bb = readByte(); - carry = (bb & 1); - bb = (bb>>1) | 0x80; - } - return carry; - }; - - function readBits(a) { - var res = 0, - i = a; - - while(i--) { - res = (res<<1) | readBit(); - } - if(a) { - res = bitReverse[res]>>(8-a); - } - return res; - }; - - function flushBuffer(){ - //document.write('FLUSHBUFFER:'+buf32k); - bIdx = 0; - }; - function addBuffer(a){ - SIZE++; - //CRC=updcrc(a,crc); - buf32k[bIdx++] = a; - outputArr.push(String.fromCharCode(a)); - //output+=String.fromCharCode(a); - if(bIdx==0x8000){ - //document.write('ADDBUFFER:'+buf32k); - bIdx=0; - } - }; - - function HufNode() { - this.b0=0; - this.b1=0; - this.jump = null; - this.jumppos = -1; - }; - - var LITERALS = 288; - - var literalTree = new Array(LITERALS); - var distanceTree = new Array(32); - var treepos=0; - var Places = null; - var Places2 = null; - - var impDistanceTree = new Array(64); - var impLengthTree = new Array(64); - - var len = 0; - var fpos = new Array(17); - fpos[0]=0; - var flens; - var fmax; - - function IsPat() { - while (1) { - if (fpos[len] >= fmax) - return -1; - if (flens[fpos[len]] == len) - return fpos[len]++; - fpos[len]++; - } - }; - - function Rec() { - var curplace = Places[treepos]; - var tmp; - if (debug) - document.write("
len:"+len+" treepos:"+treepos); - if(len==17) { //war 17 - return -1; - } - treepos++; - len++; - - tmp = IsPat(); - if (debug) - document.write("
IsPat "+tmp); - if(tmp >= 0) { - curplace.b0 = tmp; /* leaf cell for 0-bit */ - if (debug) - document.write("
b0 "+curplace.b0); - } else { - /* Not a Leaf cell */ - curplace.b0 = 0x8000; - if (debug) - document.write("
b0 "+curplace.b0); - if(Rec()) - return -1; - } - tmp = IsPat(); - if(tmp >= 0) { - curplace.b1 = tmp; /* leaf cell for 1-bit */ - if (debug) - document.write("
b1 "+curplace.b1); - curplace.jump = null; /* Just for the display routine */ - } else { - /* Not a Leaf cell */ - curplace.b1 = 0x8000; - if (debug) - document.write("
b1 "+curplace.b1); - curplace.jump = Places[treepos]; - curplace.jumppos = treepos; - if(Rec()) - return -1; - } - len--; - return 0; - }; - - function CreateTree(currentTree, numval, lengths, show) { - var i; - /* Create the Huffman decode tree/table */ - //document.write("
createtree
"); - if (debug) - document.write("currentTree "+currentTree+" numval "+numval+" lengths "+lengths+" show "+show); - Places = currentTree; - treepos=0; - flens = lengths; - fmax = numval; - for (i=0;i<17;i++) - fpos[i] = 0; - len = 0; - if(Rec()) { - //fprintf(stderr, "invalid huffman tree\n"); - if (debug) - alert("invalid huffman tree\n"); - return -1; - } - if (debug){ - document.write('
Tree: '+Places.length); - for (var a=0;a<32;a++){ - document.write("Places["+a+"].b0="+Places[a].b0+"
"); - document.write("Places["+a+"].b1="+Places[a].b1+"
"); - } - } - - /*if(show) { - var tmp; - for(tmp=currentTree;tmpjump?tmp->jump-currentTree:0,(tmp->jump?tmp->jump-currentTree:0)*6+0xcf0); - if(!(tmp.b0 & 0x8000)) { - //fprintf(stdout, " 0x%03x (%c)", tmp->b0,(tmp->b0<256 && isprint(tmp->b0))?tmp->b0:'�'); - } - if(!(tmp.b1 & 0x8000)) { - if((tmp.b0 & 0x8000)) - fprintf(stdout, " "); - fprintf(stdout, " 0x%03x (%c)", tmp->b1,(tmp->b1<256 && isprint(tmp->b1))?tmp->b1:'�'); - } - fprintf(stdout, "\n"); - } - }*/ - return 0; - }; - - function DecodeValue(currentTree) { - var len, i, - xtreepos=0, - X = currentTree[xtreepos], - b; - - /* decode one symbol of the data */ - while(1) { - b=readBit(); - if (debug) - document.write("b="+b); - if(b) { - if(!(X.b1 & 0x8000)){ - if (debug) - document.write("ret1"); - return X.b1; /* If leaf node, return data */ - } - X = X.jump; - len = currentTree.length; - for (i=0;i>1); - if(j > 23) { - j = (j<<1) | readBit(); /* 48..255 */ - - if(j > 199) { /* 200..255 */ - j -= 128; /* 72..127 */ - j = (j<<1) | readBit(); /* 144..255 << */ - } else { /* 48..199 */ - j -= 48; /* 0..151 */ - if(j > 143) { - j = j+136; /* 280..287 << */ - /* 0..143 << */ - } - } - } else { /* 0..23 */ - j += 256; /* 256..279 << */ - } - if(j < 256) { - addBuffer(j); - //document.write("out:"+String.fromCharCode(j)); - /*fprintf(errfp, "@%d %02x\n", SIZE, j);*/ - } else if(j == 256) { - /* EOF */ - break; - } else { - var len, dist; - - j -= 256 + 1; /* bytes + EOF */ - len = readBits(cplext[j]) + cplens[j]; - - j = bitReverse[readBits(5)]>>3; - if(cpdext[j] > 8) { - dist = readBits(8); - dist |= (readBits(cpdext[j]-8)<<8); - } else { - dist = readBits(cpdext[j]); - } - dist += cpdist[j]; - - /*fprintf(errfp, "@%d (l%02x,d%04x)\n", SIZE, len, dist);*/ - for(j=0;jparam: "+literalCodes+" "+distCodes+" "+lenCodes+"
"); - for(j=0; j<19; j++) { - ll[j] = 0; - } - - // Get the decode tree code lengths - - //document.write("
"); - for(j=0; jll:'+ll); - len = distanceTree.length; - for (i=0; idistanceTree"); - for(var a=0;a"+distanceTree[a].b0+" "+distanceTree[a].b1+" "+distanceTree[a].jump+" "+distanceTree[a].jumppos); - /*if (distanceTree[a].jumppos!=-1) - document.write(" "+distanceTree[a].jump.b0+" "+distanceTree[a].jump.b1); - */ - } - } - //document.write('
tree created'); - - //read in literal and distance code lengths - n = literalCodes + distCodes; - i = 0; - var z=-1; - if (debug) - document.write("
n="+n+" bits: "+bits+"
"); - while(i < n) { - z++; - j = DecodeValue(distanceTree); - if (debug) - document.write("
"+z+" i:"+i+" decode: "+j+" bits "+bits+"
"); - if(j<16) { // length of code in bits (0..15) - ll[i++] = j; - } else if(j==16) { // repeat last length 3 to 6 times - var l; - j = 3 + readBits(2); - if(i+j > n) { - flushBuffer(); - return 1; - } - l = i ? ll[i-1] : 0; - while(j--) { - ll[i++] = l; - } - } else { - if(j==17) { // 3 to 10 zero length codes - j = 3 + readBits(3); - } else { // j == 18: 11 to 138 zero length codes - j = 11 + readBits(7); - } - if(i+j > n) { - flushBuffer(); - return 1; - } - while(j--) { - ll[i++] = 0; - } - } - } - /*for(j=0; jliteralTree"); - while(1) { - j = DecodeValue(literalTree); - if(j >= 256) { // In C64: if carry set - var len, dist; - j -= 256; - if(j == 0) { - // EOF - break; - } - j--; - len = readBits(cplext[j]) + cplens[j]; - - j = DecodeValue(distanceTree); - if(cpdext[j] > 8) { - dist = readBits(8); - dist |= (readBits(cpdext[j]-8)<<8); - } else { - dist = readBits(cpdext[j]); - } - dist += cpdist[j]; - while(len--) { - var c = buf32k[(bIdx - dist) & 0x7fff]; - addBuffer(c); - } - } else { - addBuffer(j); - } - } - } - } while(!last); - flushBuffer(); - - byteAlign(); - return 0; - }; - - JXG.Util.Unzip.prototype.unzipFile = function(name) { - var i; - this.unzip(); - //alert(unzipped[0][1]); - for (i=0;i"); - } - */ - //alert(bA); - nextFile(); - return unzipped; - }; - - function nextFile(){ - if (debug) - alert("NEXTFILE"); - outputArr = []; - var tmp = []; - modeZIP = false; - tmp[0] = readByte(); - tmp[1] = readByte(); - if (debug) - alert("type: "+tmp[0]+" "+tmp[1]); - if (tmp[0] == parseInt("78",16) && tmp[1] == parseInt("da",16)){ //GZIP - if (debug) - alert("GEONExT-GZIP"); - DeflateLoop(); - if (debug) - alert(outputArr.join('')); - unzipped[files] = new Array(2); - unzipped[files][0] = outputArr.join(''); - unzipped[files][1] = "geonext.gxt"; - files++; - } - if (tmp[0] == parseInt("1f",16) && tmp[1] == parseInt("8b",16)){ //GZIP - if (debug) - alert("GZIP"); - //DeflateLoop(); - skipdir(); - if (debug) - alert(outputArr.join('')); - unzipped[files] = new Array(2); - unzipped[files][0] = outputArr.join(''); - unzipped[files][1] = "file"; - files++; - } - if (tmp[0] == parseInt("50",16) && tmp[1] == parseInt("4b",16)){ //ZIP - modeZIP = true; - tmp[2] = readByte(); - tmp[3] = readByte(); - if (tmp[2] == parseInt("3",16) && tmp[3] == parseInt("4",16)){ - //MODE_ZIP - tmp[0] = readByte(); - tmp[1] = readByte(); - if (debug) - alert("ZIP-Version: "+tmp[1]+" "+tmp[0]/10+"."+tmp[0]%10); - - gpflags = readByte(); - gpflags |= (readByte()<<8); - if (debug) - alert("gpflags: "+gpflags); - - var method = readByte(); - method |= (readByte()<<8); - if (debug) - alert("method: "+method); - - readByte(); - readByte(); - readByte(); - readByte(); - - var crc = readByte(); - crc |= (readByte()<<8); - crc |= (readByte()<<16); - crc |= (readByte()<<24); - - var compSize = readByte(); - compSize |= (readByte()<<8); - compSize |= (readByte()<<16); - compSize |= (readByte()<<24); - - var size = readByte(); - size |= (readByte()<<8); - size |= (readByte()<<16); - size |= (readByte()<<24); - - if (debug) - alert("local CRC: "+crc+"\nlocal Size: "+size+"\nlocal CompSize: "+compSize); - - var filelen = readByte(); - filelen |= (readByte()<<8); - - var extralen = readByte(); - extralen |= (readByte()<<8); - - if (debug) - alert("filelen "+filelen); - i = 0; - nameBuf = []; - while (filelen--){ - var c = readByte(); - if (c == "/" | c ==":"){ - i = 0; - } else if (i < NAMEMAX-1) - nameBuf[i++] = String.fromCharCode(c); - } - if (debug) - alert("nameBuf: "+nameBuf); - - //nameBuf[i] = "\0"; - if (!fileout) - fileout = nameBuf; - - var i = 0; - while (i < extralen){ - c = readByte(); - i++; - } - - CRC = 0xffffffff; - SIZE = 0; - - if (size = 0 && fileOut.charAt(fileout.length-1)=="/"){ - //skipdir - if (debug) - alert("skipdir"); - } - if (method == 8){ - DeflateLoop(); - if (debug) - alert(outputArr.join('')); - unzipped[files] = new Array(2); - unzipped[files][0] = outputArr.join(''); - unzipped[files][1] = nameBuf.join(''); - files++; - //return outputArr.join(''); - } - skipdir(); - } - } - }; - - function skipdir(){ - var crc, - tmp = [], - compSize, size, os, i, c; - - if ((gpflags & 8)) { - tmp[0] = readByte(); - tmp[1] = readByte(); - tmp[2] = readByte(); - tmp[3] = readByte(); - - if (tmp[0] == parseInt("50",16) && - tmp[1] == parseInt("4b",16) && - tmp[2] == parseInt("07",16) && - tmp[3] == parseInt("08",16)) - { - crc = readByte(); - crc |= (readByte()<<8); - crc |= (readByte()<<16); - crc |= (readByte()<<24); - } else { - crc = tmp[0] | (tmp[1]<<8) | (tmp[2]<<16) | (tmp[3]<<24); - } - - compSize = readByte(); - compSize |= (readByte()<<8); - compSize |= (readByte()<<16); - compSize |= (readByte()<<24); - - size = readByte(); - size |= (readByte()<<8); - size |= (readByte()<<16); - size |= (readByte()<<24); - - if (debug) - alert("CRC:"); - } - - if (modeZIP) - nextFile(); - - tmp[0] = readByte(); - if (tmp[0] != 8) { - if (debug) - alert("Unknown compression method!"); - return 0; - } - - gpflags = readByte(); - if (debug){ - if ((gpflags & ~(parseInt("1f",16)))) - alert("Unknown flags set!"); - } - - readByte(); - readByte(); - readByte(); - readByte(); - - readByte(); - os = readByte(); - - if ((gpflags & 4)){ - tmp[0] = readByte(); - tmp[2] = readByte(); - len = tmp[0] + 256*tmp[1]; - if (debug) - alert("Extra field size: "+len); - for (i=0;ihttp://www.webtoolkit.info/ -*/ -JXG.Util.Base64 = { - - // private property - _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", - - // public method for encoding - encode : function (input) { - var output = [], - chr1, chr2, chr3, enc1, enc2, enc3, enc4, - i = 0; - - input = JXG.Util.Base64._utf8_encode(input); - - while (i < input.length) { - - chr1 = input.charCodeAt(i++); - chr2 = input.charCodeAt(i++); - chr3 = input.charCodeAt(i++); - - enc1 = chr1 >> 2; - enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); - enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); - enc4 = chr3 & 63; - - if (isNaN(chr2)) { - enc3 = enc4 = 64; - } else if (isNaN(chr3)) { - enc4 = 64; - } - - output.push([this._keyStr.charAt(enc1), - this._keyStr.charAt(enc2), - this._keyStr.charAt(enc3), - this._keyStr.charAt(enc4)].join('')); - } - - return output.join(''); - }, - - // public method for decoding - decode : function (input, utf8) { - var output = [], - chr1, chr2, chr3, - enc1, enc2, enc3, enc4, - i = 0; - - input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); - - while (i < input.length) { - - enc1 = this._keyStr.indexOf(input.charAt(i++)); - enc2 = this._keyStr.indexOf(input.charAt(i++)); - enc3 = this._keyStr.indexOf(input.charAt(i++)); - enc4 = this._keyStr.indexOf(input.charAt(i++)); - - chr1 = (enc1 << 2) | (enc2 >> 4); - chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); - chr3 = ((enc3 & 3) << 6) | enc4; - - output.push(String.fromCharCode(chr1)); - - if (enc3 != 64) { - output.push(String.fromCharCode(chr2)); - } - if (enc4 != 64) { - output.push(String.fromCharCode(chr3)); - } - } - - output = output.join(''); - - if (utf8) { - output = JXG.Util.Base64._utf8_decode(output); - } - return output; - - }, - - // private method for UTF-8 encoding - _utf8_encode : function (string) { - string = string.replace(/\r\n/g,"\n"); - var utftext = ""; - - for (var n = 0; n < string.length; n++) { - - var c = string.charCodeAt(n); - - if (c < 128) { - utftext += String.fromCharCode(c); - } - else if((c > 127) && (c < 2048)) { - utftext += String.fromCharCode((c >> 6) | 192); - utftext += String.fromCharCode((c & 63) | 128); - } - else { - utftext += String.fromCharCode((c >> 12) | 224); - utftext += String.fromCharCode(((c >> 6) & 63) | 128); - utftext += String.fromCharCode((c & 63) | 128); - } - - } - - return utftext; - }, - - // private method for UTF-8 decoding - _utf8_decode : function (utftext) { - var string = [], - i = 0, - c = 0, c2 = 0, c3 = 0; - - while ( i < utftext.length ) { - c = utftext.charCodeAt(i); - if (c < 128) { - string.push(String.fromCharCode(c)); - i++; - } - else if((c > 191) && (c < 224)) { - c2 = utftext.charCodeAt(i+1); - string.push(String.fromCharCode(((c & 31) << 6) | (c2 & 63))); - i += 2; - } - else { - c2 = utftext.charCodeAt(i+1); - c3 = utftext.charCodeAt(i+2); - string.push(String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63))); - i += 3; - } - } - return string.join(''); - }, - - _destrip: function (stripped, wrap){ - var lines = [], lineno, i, - destripped = []; - - if (wrap==null) - wrap = 76; - - stripped.replace(/ /g, ""); - lineno = stripped.length / wrap; - for (i = 0; i < lineno; i++) - lines[i]=stripped.substr(i * wrap, wrap); - if (lineno != stripped.length / wrap) - lines[lines.length]=stripped.substr(lineno * wrap, stripped.length-(lineno * wrap)); - - for (i = 0; i < lines.length; i++) - destripped.push(lines[i]); - return destripped.join('\n'); - }, - - decodeAsArray: function (input){ - var dec = this.decode(input), - ar = [], i; - for (i=0;i255){ - switch (c) { - case 8364: c=128; - break; - case 8218: c=130; - break; - case 402: c=131; - break; - case 8222: c=132; - break; - case 8230: c=133; - break; - case 8224: c=134; - break; - case 8225: c=135; - break; - case 710: c=136; - break; - case 8240: c=137; - break; - case 352: c=138; - break; - case 8249: c=139; - break; - case 338: c=140; - break; - case 381: c=142; - break; - case 8216: c=145; - break; - case 8217: c=146; - break; - case 8220: c=147; - break; - case 8221: c=148; - break; - case 8226: c=149; - break; - case 8211: c=150; - break; - case 8212: c=151; - break; - case 732: c=152; - break; - case 8482: c=153; - break; - case 353: c=154; - break; - case 8250: c=155; - break; - case 339: c=156; - break; - case 382: c=158; - break; - case 376: c=159; - break; - default: - break; - } - } - return c; -}; - -/** - * Decoding string into utf-8 - * @param {String} string to decode - * @return {String} utf8 decoded string - */ -JXG.Util.utf8Decode = function(utftext) { - var string = []; - var i = 0; - var c = 0, c1 = 0, c2 = 0; - - while ( i < utftext.length ) { - c = utftext.charCodeAt(i); - - if (c < 128) { - string.push(String.fromCharCode(c)); - i++; - } else if((c > 191) && (c < 224)) { - c2 = utftext.charCodeAt(i+1); - string.push(String.fromCharCode(((c & 31) << 6) | (c2 & 63))); - i += 2; - } else { - c2 = utftext.charCodeAt(i+1); - c3 = utftext.charCodeAt(i+2); - string.push(String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63))); - i += 3; - } - }; - return string.join(''); -}; diff --git a/src/js/stendhal.html b/src/js/stendhal.html index 784045833f..c294d10012 100644 --- a/src/js/stendhal.html +++ b/src/js/stendhal.html @@ -486,14 +486,11 @@
- + - - - diff --git a/src/js/stendhal/Client.ts b/src/js/stendhal/Client.ts index 9e0298af15..aa0e7885a8 100644 --- a/src/js/stendhal/Client.ts +++ b/src/js/stendhal/Client.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright © 2023-2024 - Faiumoni e. V. * + * Copyright © 2023-2026 - Faiumoni e. V. * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,25 +9,21 @@ * * ***************************************************************************/ -declare var marauroa: any; -declare var stendhal: any; +import { marauroa, Deserializer, RPObject } from "marauroa" +import { stendhal } from "./stendhal"; -import { PerceptionListener } from "./PerceptionListener"; +import { StendhalPerceptionListener } from "./PerceptionListener"; import { singletons } from "./SingletonRepo"; import { Paths } from "./data/Paths"; import { Color } from "./data/color/Color"; -import { Ground } from "./entity/Ground"; -import { RPObject } from "./entity/RPObject"; +import { EntityRegistry } from "./entity/EntityRegistry"; import { ui } from "./ui/UI"; import { UIComponentEnum } from "./ui/UIComponentEnum"; -import { BuddyListComponent } from "./ui/component/BuddyListComponent"; -import { MiniMapComponent } from "./ui/component/MiniMapComponent"; -import { PlayerEquipmentComponent } from "./ui/component/PlayerEquipmentComponent"; import { ZoneInfoComponent } from "./ui/component/ZoneInfoComponent"; import { ChooseCharacterDialog } from "./ui/dialog/ChooseCharacterDialog"; @@ -40,6 +36,12 @@ import { SingletonFloatingWindow } from "./ui/toolkit/SingletonFloatingWindow"; import { Chat } from "./util/Chat"; import { DialogHandler } from "./util/DialogHandler"; import { Globals } from "./util/Globals"; +import { TileMap } from "data/TileMap"; +import { OutfitStore } from "data/OutfitStore"; +import { TileStore } from "data/TileStore"; +import { WeatherRenderer } from "util/WeatherRenderer"; +import { ViewPort } from "ui/ViewPort"; +import { htmlImageStore } from "data/HTMLImageStore"; /** @@ -62,6 +64,7 @@ export class Client { /** Singleton instance. */ private static instance: Client; + public loaded = false; /** @@ -78,7 +81,7 @@ export class Client { * Hidden singleton constructor. */ private constructor() { - // do nothing + // empty } /** @@ -91,19 +94,18 @@ export class Client { } this.initialized = true; - // add version & build info to DOM for retrieval by browser - document.documentElement.setAttribute("data-build-version", stendhal.data.build.version); - document.documentElement.setAttribute("data-build-build", stendhal.data.build.build); - - stendhal.paths = singletons.getPaths(); stendhal.config = singletons.getConfigManager(); stendhal.session = singletons.getSessionManager(); stendhal.actions = singletons.getSlashActionRepo(); + new EntityRegistry().init(); this.initData(); this.initSound(); this.initUI(); - this.initZone(); + + // add version & build info to DOM for retrieval by browser + document.documentElement.setAttribute("data-build-version", stendhal.data.build?.version); + document.documentElement.setAttribute("data-build-build", stendhal.data.build?.build); } /** @@ -117,9 +119,6 @@ export class Client { stendhal.data.cstatus = singletons.getCStatus(); stendhal.data.cstatus.init(); stendhal.data.group = singletons.getGroupManager(); - stendhal.data.outfit = singletons.getOutfitStore(); - stendhal.data.sprites = singletons.getSpriteStore(); - stendhal.data.map = singletons.getMap(); // online players stendhal.players = []; } @@ -141,22 +140,9 @@ export class Client { private initUI() { stendhal.ui = stendhal.ui || {}; stendhal.ui.equip = singletons.getInventory(); - stendhal.ui.html = singletons.getHTMLManager(); - stendhal.ui.touch = singletons.getTouchHandler(); - stendhal.ui.viewport = singletons.getViewPort(); - // alias for backward-compat until changed in all source - stendhal.ui.gamewindow = stendhal.ui.viewport; - stendhal.ui.getMenuStyle = Globals.getMenuStyle; } - /** - * Builds initial zone for user to enter world. - */ - private initZone() { - stendhal.zone = singletons.getZone(); - stendhal.zone.ground = new Ground(); - } /** * Main startup routines. @@ -177,11 +163,11 @@ export class Client { singletons.getEventRegistry().init(); // initialize tileset animation data - singletons.getTileStore().init(); + TileStore.get().init(); // initialize emoji data singletons.getEmojiStore().init(); // initialize outfit data - stendhal.data.outfit.init(); + OutfitStore.get().init(); new DesktopUserInterfaceFactory().create(); @@ -204,11 +190,11 @@ export class Client { stendhal.ui.globalInternalWindow = new DialogHandler(); // pre-cache images & sounds - stendhal.data.sprites.startupCache(); + htmlImageStore.startupCache(); stendhal.sound.startupCache(); if (document.getElementById("viewport")) { - stendhal.ui.gamewindow.draw.apply(stendhal.ui.gamewindow, arguments); + ViewPort.get().draw(); } } @@ -259,11 +245,14 @@ export class Client { * Registers Marauroa event handlers. */ registerMarauroaEventHandlers() { - marauroa.clientFramework.onDisconnect = function(_reason: string, _error: string) { + marauroa.clientFramework.onDisconnect = function(_reason: string, _code: number, _wasClean: boolean) { if (!Client.instance.unloading) { Chat.logH("error", "Disconnected from server."); + if (window.location.hostname !== "localhost" && window.location.hostname !== "127.0.0.1" && window.location.hostname !== "::1") { + window.location.href = "/account/mycharacters.html"; + } } - }; + }.bind(this); marauroa.clientFramework.onLoginRequired = function(config: Record) { if (config["client_login_url"]) { @@ -281,25 +270,25 @@ export class Client { "Login", new LoginDialog(), 100, 50).enableCloseButton(false); - }; + }.bind(this); marauroa.clientFramework.onCreateAccountAck = function(username: string) { // TODO: We should login automatically alert("Account succesfully created, please login."); window.location.reload(); - }; + }.bind(this); marauroa.clientFramework.onCreateCharacterAck = function(charname: string, _template: any) { // Client.get().chooseCharacter(charname); - }; + }.bind(this); marauroa.clientFramework.onLoginFailed = function(_reason: string, _text: string) { alert("Login failed. " + _text); // TODO: Server closes the connection, so we need to open a new one window.location.reload(); - }; + }.bind(this); - marauroa.clientFramework.onAvailableCharacterDetails = function(characters: {[key: string]: RPObject}) { + marauroa.clientFramework.onAvailableCharacterDetails = (characters: {[key: string]: RPObject}) => { SingletonFloatingWindow.closeAll(); if (!Object.keys(characters).length && this.username) { marauroa.clientFramework.createCharacter(this.username, {}); @@ -331,9 +320,9 @@ export class Client { } items[i]["ack"] = true; } - }; + }.bind(this); - marauroa.clientFramework.onTransfer = function(items: any) { + marauroa.clientFramework.onTransfer = (items: any) => { var data = {} as any; var zoneName = "" for (var i in items) { @@ -345,34 +334,13 @@ export class Client { this.onDataMap(items[i]["data"]); } } - stendhal.data.map.onTransfer(zoneName, data); + TileMap.get().onTransfer(zoneName, data); }; // update user interface on perceptions if (document.getElementById("viewport")) { // override perception listener - marauroa.perceptionListener = new PerceptionListener(marauroa.perceptionListener); - marauroa.perceptionListener.onPerceptionEnd = function(_type: Int8Array, _timestamp: number) { - stendhal.zone.sortEntities(); - (ui.get(UIComponentEnum.MiniMap) as MiniMapComponent).draw(); - (ui.get(UIComponentEnum.BuddyList) as BuddyListComponent).update(); - stendhal.ui.equip.update(); - (ui.get(UIComponentEnum.PlayerEquipment) as PlayerEquipmentComponent).update(); - if (!this.loaded) { - this.loaded = true; - // delay visibile change of client a little to allow for initialisation in the background for a smoother experience - window.setTimeout(function() { - let body = document.getElementById("body")!; - body.style.cursor = "auto"; - document.getElementById("client")!.style.display = "block"; - document.getElementById("loginpopup")!.style.display = "none"; - - // initialize observer after UI is ready - singletons.getUIUpdateObserver().init(); - ui.onDisplayReady(); - }, 300); - } - } + marauroa.perceptionListener = new StendhalPerceptionListener(); } } @@ -420,18 +388,19 @@ export class Client { document.getElementById("body")!.addEventListener("mouseenter", stendhal.main.onMouseEnter); - var gamewindow = document.getElementById("viewport")!; + let gamewindow = document.getElementById("viewport")!; + let viewPort = ViewPort.get(); gamewindow.setAttribute("draggable", "true"); - gamewindow.addEventListener("mousedown", stendhal.ui.gamewindow.onMouseDown); - gamewindow.addEventListener("dblclick", stendhal.ui.gamewindow.onMouseDown); - gamewindow.addEventListener("dragstart", stendhal.ui.gamewindow.onDragStart); - gamewindow.addEventListener("mousemove", stendhal.ui.gamewindow.onMouseMove); - gamewindow.addEventListener("touchstart", stendhal.ui.gamewindow.onMouseDown); - gamewindow.addEventListener("touchend", stendhal.ui.gamewindow.onTouchEnd); - gamewindow.addEventListener("dragover", stendhal.ui.gamewindow.onDragOver); - gamewindow.addEventListener("drop", stendhal.ui.gamewindow.onDrop); - gamewindow.addEventListener("contextmenu", stendhal.ui.gamewindow.onContentMenu); - gamewindow.addEventListener("wheel", stendhal.ui.gamewindow.onMouseWheel); + gamewindow.addEventListener("mousedown", viewPort.onMouseDown); + gamewindow.addEventListener("dblclick", viewPort.onMouseDown); + gamewindow.addEventListener("dragstart", viewPort.onDragStart); + gamewindow.addEventListener("mousemove", viewPort.onMouseMove); + gamewindow.addEventListener("touchstart", viewPort.onMouseDown, {passive: true}); + gamewindow.addEventListener("touchend", viewPort.onTouchEnd); + gamewindow.addEventListener("dragover", viewPort.onDragOver); + gamewindow.addEventListener("drop", viewPort.onDrop); + gamewindow.addEventListener("contextmenu", viewPort.onContentMenu); + gamewindow.addEventListener("wheel", viewPort.onMouseWheel, {passive: true}); singletons.getJoystickController().registerGlobalEventHandlers(); @@ -459,7 +428,7 @@ export class Client { document.addEventListener("click", Client.handleClickIndicator); document.addEventListener("touchend", Client.handleClickIndicator); }; - click_indicator.src = stendhal.paths.gui + "/click_indicator.png"; + click_indicator.src = Paths.gui + "/click_indicator.png"; } /** @@ -469,8 +438,10 @@ export class Client { * Information about map. */ onDataMap(data: any) { + let map = TileMap.get(); + let viewPort = ViewPort.get(); var zoneinfo = {} as {[key: string]: string}; - var deserializer = marauroa.Deserializer.fromBase64(data); + var deserializer = Deserializer.fromBase64(data); deserializer.readAttributes(zoneinfo); (ui.get(UIComponentEnum.ZoneInfo) as ZoneInfoComponent).zoneChange(zoneinfo); @@ -481,29 +452,29 @@ export class Client { // parallax background if (stendhal.config.getBoolean("effect.parallax")) { - stendhal.data.map.setParallax(zoneinfo["parallax"]); - stendhal.data.map.setIgnoredTiles(zoneinfo["parallax_ignore_tiles"]); + map.setParallax(zoneinfo["parallax"]); + map.setIgnoredTiles(zoneinfo["parallax_ignore_tiles"]); } // coloring information if (zoneinfo["color"] && stendhal.config.getBoolean("effect.lighting")) { if (zoneinfo["color_method"]) { - stendhal.ui.gamewindow.setColorMethod(zoneinfo["color_method"]); + viewPort.setColorMethod(zoneinfo["color_method"]); } if (zoneinfo["blend_method"]) { - stendhal.ui.gamewindow.setBlendMethod(zoneinfo["blend_method"]); + viewPort.setBlendMethod(zoneinfo["blend_method"]); } const hsl = Color.numToHSL(Number(zoneinfo["color"])); - stendhal.ui.gamewindow.HSLFilter = hsl.toString(); + viewPort.HSLFilter = hsl.toString(); // deprecated - stendhal.ui.gamewindow.filter = "hue-rotate(" + hsl.H + "deg) saturate(" + hsl.S + viewPort.filter = "hue-rotate(" + hsl.H + "deg) saturate(" + hsl.S + ") brightness(" + hsl.L + ")"; } else { - stendhal.ui.gamewindow.HSLFilter = undefined; - stendhal.ui.gamewindow.filter = undefined; + viewPort.HSLFilter = undefined; + viewPort.filter = undefined; } - singletons.getWeatherRenderer().update(zoneinfo["weather"]); + WeatherRenderer.get().update(zoneinfo["weather"]); } /** @@ -518,7 +489,7 @@ export class Client { */ onMouseEnter(e: MouseEvent) { // use Stendhal's built-in cursor for entire page - (e.target as HTMLElement).style.cursor = "url(" + stendhal.paths.sprites + "/cursor/normal.png) 1 3, auto"; + (e.target as HTMLElement).style.cursor = "url(" + Paths.sprites + "/cursor/normal.png) 1 3, auto"; } /** diff --git a/src/js/stendhal/EventRegistry.ts b/src/js/stendhal/EventRegistry.ts deleted file mode 100644 index f4b2448c7b..0000000000 --- a/src/js/stendhal/EventRegistry.ts +++ /dev/null @@ -1,310 +0,0 @@ -/*************************************************************************** - * (C) Copyright 2003-2024 - Stendhal * - *************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU Affero General Public License as * - * published by the Free Software Foundation; either version 3 of the * - * License, or (at your option) any later version. * - * * - ***************************************************************************/ - -declare var marauroa: any; -declare var stendhal: any; - -import { RPEntity } from "./entity/RPEntity"; -import { RPObject } from "./entity/RPObject"; - -import { BestiaryEvent } from "./event/BestiaryEvent"; -import { ChatOptionsEvent } from "./event/ChatOptionsEvent"; -import { ExamineEvent } from "./event/ExamineEvent"; -import { GroupChangeEvent } from "./event/GroupChangeEvent"; -import { GroupInviteEvent } from "./event/GroupInviteEvent"; -import { PlayerLoggedOnEvent } from "./event/PlayerLoggedOnEvent"; -import { PlayerLoggedOutEvent } from "./event/PlayerLoggedOutEvent"; -import { ProgressStatusEvent } from "./event/ProgressStatusEvent"; -import { RPEvent } from "./event/RPEvent"; -import { SoundEvent } from "./event/SoundEvent"; -import { TradeEvent } from "./event/TradeEvent"; -import { ViewChangeEvent } from "./event/ViewChangeEvent"; - -import { ui } from "./ui/UI"; - -import { DialogContentComponent } from "./ui/toolkit/DialogContentComponent"; - -import { Chat } from "./util/Chat"; - - -export class EventRegistry { - - private initialized = false; - - /** Singleton instance. */ - private static instance: EventRegistry; - - - /** - * Retrieves singleton instance. - */ - static get(): EventRegistry { - if (!EventRegistry.instance) { - EventRegistry.instance = new EventRegistry(); - } - return EventRegistry.instance; - } - - /** - * Hidden singleton constructor. - */ - private constructor() { - // do nothing - } - - /** - * Registers standard events. - */ - init() { - if (this.initialized) { - console.warn("tried to re-initialize EventRegistry"); - return; - } - this.initialized = true; - - this.register("chat_options", new ChatOptionsEvent()); - this.register("examine", new ExamineEvent()); - this.register("group_change_event", new GroupChangeEvent()); - this.register("group_invite_event", new GroupInviteEvent()); - this.register("progress_status_event", new ProgressStatusEvent()); - this.register("trade_state_change_event", new TradeEvent()); - - this.register("attack", { - execute: function(entity: RPEntity) { - var target = entity.getAttackTarget(); - if (!target) { - return; - } - if (this.hasOwnProperty("hit")) { - var damage = parseInt(this["damage"], 10); - if (damage !== 0) { - target.onDamaged(entity, damage); - } else { - target.onBlocked(entity); - } - } else { - target.onMissed(entity); - } - entity.onAttackPerformed(parseInt(this["type"], 10), this.hasOwnProperty("ranged"), this["weapon"]); - } - }); // attack - - this.register("bestiary", new BestiaryEvent()); - - this.register("global_visual_effect", { - execute: function(rpobject: RPObject) { - // TODO: new GlobalVisualEffectEvent(); - } - }); // global_visual_effect - - this.register("image_event", { - execute: function(rpobject: RPObject) { - // TODO: new ImageEffectEvent(); - console.log("image_event", this, rpobject); - } - }); // image_event - - this.register("player_logged_on", new PlayerLoggedOnEvent()); - this.register("player_logged_out", new PlayerLoggedOutEvent()); - - this.register("private_text", { - soundTextEvents: { - "privmsg": true, - "support": true, - "tutorial": true - }, - - execute: function(rpobject: RPObject) { - const ttype = this["texttype"].toLowerCase(); - const msg = this["text"].replace(/\\r\\n/g, "\n").replace(/\\r/g, "\n"); - - let profile; - if (this.hasOwnProperty("profile")) { - profile = this["profile"]; - } else if (ttype === "tutorial") { - profile = "floattingladynpc"; - } - - if (ttype === "server" && msg.includes("\n")) { - Chat.log(ttype, msg.split("\n"), undefined, profile); - } else { - // scene settings messages should not disturb playing, just create some atmosphere - const headed = ttype !== "scene_setting"; - Chat.log(ttype, msg, undefined, profile, headed); - } - - // play notification sound - const notif = stendhal.config.get("chat.private.sound"); - if (notif && this.soundTextEvents[ttype]) { - stendhal.sound.playGlobalizedEffect(notif); - } - } - }); // private_text - - this.register("reached_achievement", { - execute: function(rpobject: RPObject) { - stendhal.ui.gamewindow.addAchievementNotif(this["category"], this["title"], this["description"]); - } - }); // reached_achievement - - this.register("show_item_list", { - execute: function(rpobject: RPObject) { - let title = "Items"; - let caption = ""; - let items = []; - - if (this.hasOwnProperty("title")) { - title = this["title"]; - } - if (this.hasOwnProperty("caption")) { - caption = this["caption"]; - } - if (this.hasOwnProperty("content")) { - for (var obj in this["content"]) { - if (this["content"].hasOwnProperty(obj)) { - var slotObj = this["content"][obj]; - var data = this["content"][obj]["a"]; - const i = { - clazz: data["class"], - subclass: data["subclass"], - img: data["class"] + "/" + data["subclass"] + ".png", - price: data["price"], - desc: data["description_info"] - } - - // seller shops prefix prices with "-" - if (i.price.startsWith("-")) { - i.price = i.price.substr(1); - } - items.push(i); - } - } - } - - const content = new class extends DialogContentComponent {} ("empty-div-template"); - content.componentElement.classList.add("shopsign"); - const captionElement = document.createElement("div"); - captionElement.className = "horizontalgroup shopcaption"; - captionElement.textContent = caption + "\nItem\t-\tPrice\t-\tDescription"; - content.componentElement.appendChild(captionElement); - const itemList = document.createElement("div"); - itemList.className = "shoplist"; - content.componentElement.appendChild(itemList); - - // TODO: organize in columns & show item sprites - for (const i of items) { - const row = document.createElement("div"); - row.className = "horizontalgroup shoprow"; - const img = document.createElement("div"); - img.className = "shopcol"; - img.appendChild(stendhal.data.sprites.get(stendhal.paths.sprites + "/items/" + i.img)); - row.appendChild(img); - const price = document.createElement("div"); - price.className = "shopcol"; - price.textContent = i.price; - row.appendChild(price); - const desc = document.createElement("div"); - desc.className = "shopcol shopcolr"; - desc.textContent = i.desc; - row.appendChild(desc); - itemList.appendChild(row); - } - - stendhal.ui.globalInternalWindow.set( - ui.createSingletonFloatingWindow(title, content, 20, 20)); - } - }); // show_item_list - - this.register("show_outfit_list", { - execute: function(rpobject: RPObject) { - let title = "Outfits"; - let caption = ""; - let outfits = []; - - if (this.hasOwnProperty("title")) { - title = this["title"]; - } - if (this.hasOwnProperty("caption")) { - caption = this["caption"]; - } - if (this.hasOwnProperty("outfits")) { - for (let o of this["outfits"].split(":")) { - o = o.split(";"); - if (o.length > 2) { - outfits.push([o[0], o[1], o[2]]); - } - } - } - if (this.hasOwnProperty("show_base")) { - //Chat.log("normal", this["show_base"]); - } - - const content = new class extends DialogContentComponent {} ("empty-div-template"); - content.componentElement.classList.add("shopsign"); - const captionElement = document.createElement("div"); - captionElement.className = "horizontalgroup shopcaption"; - captionElement.textContent = caption; - content.componentElement.appendChild(captionElement); - const itemList = document.createElement("div"); - itemList.className = "shoplist"; - content.componentElement.appendChild(itemList); - - // TODO: organize in columns & show outfit sprites - for (const o of outfits) { - const row = document.createElement("div"); - row.className = "horizontalgroup shoprow"; - row.textContent = o[0] + ": " + o[2]; - itemList.appendChild(row); - } - - stendhal.ui.globalInternalWindow.set( - ui.createSingletonFloatingWindow(title, content, 20, 20)); - } - }); // show_outfit_list - - this.register("sound_event", new SoundEvent()); - - this.register("text", { - execute: function(entity: RPEntity) { - if (this.hasOwnProperty("range")) { - entity.say(this["text"], this["range"]); - } else { - entity.say(this["text"]); - } - } - }); // text - - this.register("transition_graph", { - execute: function(rpobject: RPObject) { - // TODO: new TransitionGraphEvent(); - } - }); // transition_graph - - this.register("view_change", new ViewChangeEvent()); - } - - /** - * Registers a new event. - * - * @param _type - * String identifier. - * @param _event - * Event to be registered. - */ - register(_type: string, _event: any) { - if (_event instanceof RPEvent) { - marauroa.rpeventFactory[_type] = _event; - } else { - marauroa.rpeventFactory[_type] = - marauroa.util.fromProto(marauroa.rpeventFactory["_default"], _event); - } - } -} diff --git a/src/js/stendhal/PerceptionListener.ts b/src/js/stendhal/PerceptionListener.ts index f2bccf6fdf..3e0ea4601a 100644 --- a/src/js/stendhal/PerceptionListener.ts +++ b/src/js/stendhal/PerceptionListener.ts @@ -9,29 +9,24 @@ * * ***************************************************************************/ -declare var stendhal: any; +import { ui } from "ui/UI"; +import { stendhal } from "./stendhal"; -import { RPObject } from "./entity/RPObject"; +import { RPObject, PerceptionListener } from "marauroa"; +import { UIComponentEnum } from "ui/UIComponentEnum"; +import { MiniMapComponent } from "ui/component/MiniMapComponent"; +import { BuddyListComponent } from "ui/component/BuddyListComponent"; +import { PlayerEquipmentComponent } from "ui/component/PlayerEquipmentComponent"; +import { singletons } from "SingletonRepo"; +import { Client } from "Client"; +import { Zone } from "entity/Zone"; /** * Class to override `marauroa.perceptionListener` & handle creating * User object. */ -export class PerceptionListener { - [key: string]: any; - private readonly _super: any; - - - constructor(perceptionListener: any) { - this._super = perceptionListener; - for (const prop of Object.getOwnPropertyNames(perceptionListener)) { - if (typeof(this[prop]) === "undefined") { - // members & methods that are not overridden by this class - this[prop] = perceptionListener[prop]; - } - } - } +export class StendhalPerceptionListener extends PerceptionListener { /** * Action when an object is added to the world. @@ -42,7 +37,7 @@ export class PerceptionListener { * `true` if object was added. * @see marauroa.perceptionListener.onAdded */ - onAdded(obj: RPObject): boolean { + override onAdded(obj: RPObject): boolean { if (obj["c"] === "player") { if (obj.hasOwnProperty("a")) { if (obj["a"]["name"] === stendhal.session.getCharName()) { @@ -51,6 +46,28 @@ export class PerceptionListener { } } } - return this._super.onAdded(obj); + return super.onAdded(obj); + } + + override onPerceptionEnd(_type: Int8Array, _timestamp: number) { + Zone.get().sortEntities(); + (ui.get(UIComponentEnum.MiniMap) as MiniMapComponent).draw(); + (ui.get(UIComponentEnum.BuddyList) as BuddyListComponent).update(); + stendhal.ui.equip.update(); + (ui.get(UIComponentEnum.PlayerEquipment) as PlayerEquipmentComponent).update(); + if (!Client.get().loaded) { + Client.get().loaded = true; + // delay visibile change of client a little to allow for initialisation in the background for a smoother experience + window.setTimeout(function() { + let body = document.getElementById("body")!; + body.style.cursor = "auto"; + document.getElementById("client")!.style.display = "block"; + document.getElementById("loginpopup")!.style.display = "none"; + + // initialize observer after UI is ready + singletons.getUIUpdateObserver().init(); + ui.onDisplayReady(); + }, 300); + } } } diff --git a/src/js/stendhal/SingletonRepo.ts b/src/js/stendhal/SingletonRepo.ts index 4e7827b3d8..229007b928 100644 --- a/src/js/stendhal/SingletonRepo.ts +++ b/src/js/stendhal/SingletonRepo.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright © 2003-2024 - Faiumoni e. V. * + * Copyright © 2003-2026 - Faiumoni e. V. * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -10,45 +10,35 @@ ***************************************************************************/ import { Client } from "./Client"; -import { EventRegistry } from "./EventRegistry"; +import { EventRegistry } from "./event/EventRegistry"; import { SlashActionRepo } from "./SlashActionRepo"; -import { Zone } from "./entity/Zone"; - import { ConfigManager } from "./util/ConfigManager"; import { DownloadUtil } from "./util/DownloadUtil"; -import { DrawingStage } from "./util/DrawingStage"; import { FacingHandler } from "./util/FacingHandler"; import { KeyHandler } from "./util/KeyHandler"; import { SessionManager } from "./util/SessionManager"; -import { WeatherRenderer } from "./util/WeatherRenderer"; -import { CStatus } from "./data/CStatus"; import { CacheManager } from "./data/CacheManager"; +import { CStatus } from "./data/CStatus"; import { EmojiStore } from "./data/EmojiStore"; import { GroupManager } from "./data/GroupManager"; -import { Map } from "./data/Map"; -import { OutfitStore } from "./data/OutfitStore"; import { Paths } from "./data/Paths"; -import { SpriteStore, store } from "./data/SpriteStore"; -import { TileStore } from "./data/TileStore"; import { LoopedSoundSourceManager } from "./data/sound/LoopedSoundSourceManager"; import { SoundManager } from "./data/sound/SoundManager"; -import { ui } from "./ui/UI"; -import { UIComponentEnum } from "./ui/UIComponentEnum"; -import { HeldObjectManager } from "./ui/HeldObject"; -import { HTMLManager } from "./ui/HTMLManager"; import { Inventory } from "./ui/Inventory"; import { SoftwareJoystickController } from "./ui/SoftwareJoystickController"; -import { TouchHandler } from "./ui/TouchHandler"; +import { ui } from "./ui/UI"; +import { UIComponentEnum } from "./ui/UIComponentEnum"; import { UIUpdateObserver } from "./ui/UIUpdateObserver"; -import { ViewPort } from "./ui/ViewPort"; import { ChatInputComponent } from "./ui/component/ChatInputComponent"; - +/** + * @Deprecated use Class.get() directly to prevent dependency chain + */ export class SingletonRepo { static getCStatus(): CStatus { @@ -75,10 +65,6 @@ export class SingletonRepo { return DownloadUtil; } - static getDrawingStage(): DrawingStage { - return DrawingStage.get(); - } - static getEmojiStore(): EmojiStore { return EmojiStore.get(); } @@ -95,14 +81,6 @@ export class SingletonRepo { return GroupManager.get(); } - static getHeldObjectManager(): HeldObjectManager { - return HeldObjectManager.get(); - } - - static getHTMLManager(): HTMLManager { - return HTMLManager.get(); - } - static getInventory(): Inventory { return Inventory.get(); } @@ -119,14 +97,6 @@ export class SingletonRepo { return LoopedSoundSourceManager.get(); } - static getMap(): Map { - return Map.get(); - } - - static getOutfitStore(): OutfitStore { - return OutfitStore.get(); - } - static getPaths(): typeof Paths { return Paths; } @@ -143,33 +113,10 @@ export class SingletonRepo { return SoundManager.get(); } - static getSpriteStore(): SpriteStore { - return store; - } - - static getTileStore(): TileStore { - return TileStore.get(); - } - - static getTouchHandler(): TouchHandler { - return TouchHandler.get(); - } - static getUIUpdateObserver(): UIUpdateObserver { return UIUpdateObserver.get(); } - static getViewPort(): ViewPort { - return ViewPort.get(); - } - - static getWeatherRenderer(): WeatherRenderer { - return WeatherRenderer.get(); - } - - static getZone(): Zone { - return Zone.get(); - } } export const singletons = SingletonRepo; diff --git a/src/js/stendhal/SlashActionRepo.ts b/src/js/stendhal/SlashActionRepo.ts index 97b9ffd77b..b528f5addd 100644 --- a/src/js/stendhal/SlashActionRepo.ts +++ b/src/js/stendhal/SlashActionRepo.ts @@ -9,8 +9,8 @@ * * ***************************************************************************/ -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { stendhal } from "./stendhal"; import { singletons } from "./SingletonRepo"; @@ -766,7 +766,7 @@ export class SlashActionRepo { execute: (type: string, params: string[], remainder: string): boolean => { const newMode = !stendhal.config.getBoolean("input.doubleclick"); stendhal.config.set("input.doubleclick", newMode); - stendhal.ui.gamewindow.updateClickMode(); + ViewPort.get().updateClickMode(); if (newMode) { Chat.log("info", "Click mode is now set to double click."); diff --git a/src/js/stendhal/action/AboutAction.ts b/src/js/stendhal/action/AboutAction.ts index bce90a0ea3..e5ba9ee862 100644 --- a/src/js/stendhal/action/AboutAction.ts +++ b/src/js/stendhal/action/AboutAction.ts @@ -10,7 +10,6 @@ * * ***************************************************************************/ -declare var stendhal: any; import { ParamList } from "./ParamList"; import { SlashAction } from "./SlashAction"; @@ -20,6 +19,7 @@ import { ui } from "../ui/UI"; import { AboutDialog } from "../ui/dialog/AboutDialog"; import { Pair } from "../util/Pair"; +import { stendhal } from "../stendhal"; export class AboutAction extends SlashAction { @@ -28,7 +28,7 @@ export class AboutAction extends SlashAction { execute(_type: string, _params: string[], _remainder: string): boolean { const wstate = stendhal.config.getWindowState("about"); - const offset = stendhal.ui.getPageOffset(); + const offset = ui.getPageOffset(); const content = new AboutDialog(); const dialog = ui.createSingletonFloatingWindow( diff --git a/src/js/stendhal/action/DebugAction.ts b/src/js/stendhal/action/DebugAction.ts index 148a303d82..7b92d9fda7 100644 --- a/src/js/stendhal/action/DebugAction.ts +++ b/src/js/stendhal/action/DebugAction.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2022-2024 - Stendhal * + * (C) Copyright 2022-2026 - Stendhal * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,21 +9,20 @@ * * ***************************************************************************/ -import { singletons } from "../SingletonRepo"; - import { ui } from "../ui/UI"; import { UIComponentEnum } from "../ui/UIComponentEnum"; import { FloatingWindow } from "../ui/toolkit/FloatingWindow"; -import { SlashAction } from "./SlashAction"; -import { ShowFloatingWindowComponent } from "../ui/component/ShowFloatingWindowComponent"; import { Panel } from "ui/toolkit/Panel"; +import { ShowFloatingWindowComponent } from "../ui/component/ShowFloatingWindowComponent"; +import { SlashAction } from "./SlashAction"; import { Chat } from "../util/Chat"; import { Debug } from "../util/Debug"; -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa"; +import { WeatherRenderer } from "util/WeatherRenderer"; +import { stendhal } from "../stendhal"; /** * performances debugging actions @@ -55,6 +54,8 @@ export class DebugAction extends SlashAction { Debug.setActive("screencap", !Debug.isActive("screencap")); Chat.log("client", "Screen capture debugging " + (Debug.isActive("screencap") ? "enabled" : "disabled")); + } else if(params[0] === "light") { + Debug.toggle("light"); } return true; } @@ -66,7 +67,8 @@ export class DebugAction extends SlashAction { " /debug ui [pop]", " /debug weather []", " /debug settings", - " /debug touch" + " /debug touch", + " /debug light" ]; Chat.log("client", usage); } @@ -135,14 +137,9 @@ export class DebugAction extends SlashAction { if (weather) { weather = weather.replace(/ /g, "_"); - const wfilename = stendhal.paths.weather + "/" + weather + ".png"; - if (!stendhal.data.sprites.getCached(wfilename)) { - Chat.logH("error", "unknown weather: " + wfilename); - return; - } + WeatherRenderer.get().update(weather); } - singletons.getWeatherRenderer().update(weather); - } + } }; diff --git a/src/js/stendhal/action/ScreenCaptureAction.ts b/src/js/stendhal/action/ScreenCaptureAction.ts index 7c8fc9d3c2..378fda38bb 100644 --- a/src/js/stendhal/action/ScreenCaptureAction.ts +++ b/src/js/stendhal/action/ScreenCaptureAction.ts @@ -14,7 +14,8 @@ import { SlashAction } from "./SlashAction"; import { ScreenCapture } from "../util/ScreenCapture"; -declare var stendhal: any; +import { stendhal } from "../stendhal"; +import { ViewPort } from "ui/ViewPort"; export class ScreenCaptureAction extends SlashAction { @@ -35,7 +36,7 @@ export class ScreenCaptureAction extends SlashAction { return true; } this.recorder = new ScreenCapture(); - this.recorder.start(stendhal.ui.viewport.getElement() as HTMLCanvasElement); + this.recorder.start(ViewPort.get().getElement()); return true; } } diff --git a/src/js/stendhal/action/SettingsAction.ts b/src/js/stendhal/action/SettingsAction.ts index 13dc33f351..61e00d60d0 100644 --- a/src/js/stendhal/action/SettingsAction.ts +++ b/src/js/stendhal/action/SettingsAction.ts @@ -10,7 +10,7 @@ * * ***************************************************************************/ -declare var stendhal: any; +import { stendhal } from "../stendhal"; import { SlashAction } from "./SlashAction"; @@ -28,7 +28,7 @@ export class SettingsAction extends SlashAction { execute(_type: string, _params: string[], _remainder: string): boolean { const wstate = stendhal.config.getWindowState("menu"); - const offset = stendhal.ui.getPageOffset(); + const offset = ui.getPageOffset(); const content = new SettingsDialog(); const dialog = ui.createSingletonFloatingWindow( diff --git a/src/js/stendhal/action/SlashAction.ts b/src/js/stendhal/action/SlashAction.ts index 41c86e4126..0e0632a3d1 100644 --- a/src/js/stendhal/action/SlashAction.ts +++ b/src/js/stendhal/action/SlashAction.ts @@ -9,7 +9,7 @@ * * ***************************************************************************/ -declare var marauroa: any; +import { marauroa } from "marauroa" import { ParamList } from "./ParamList"; import { Pair } from "../util/Pair"; diff --git a/src/js/stendhal/data/CStatus.ts b/src/js/stendhal/data/CStatus.ts index feb9b2497b..a0e77f6992 100644 --- a/src/js/stendhal/data/CStatus.ts +++ b/src/js/stendhal/data/CStatus.ts @@ -9,8 +9,8 @@ * * ***************************************************************************/ -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { stendhal } from "../stendhal"; export class CStatus { diff --git a/src/js/stendhal/data/CacheManager.ts b/src/js/stendhal/data/CacheManager.ts index a65ff5c1c5..e3b66bb1bb 100644 --- a/src/js/stendhal/data/CacheManager.ts +++ b/src/js/stendhal/data/CacheManager.ts @@ -9,7 +9,7 @@ * * ***************************************************************************/ -declare var stendhal: any; +import { stendhal } from "../stendhal"; /** diff --git a/src/js/stendhal/data/EmojiStore.ts b/src/js/stendhal/data/EmojiStore.ts index 78a72e5e85..574ecce14c 100644 --- a/src/js/stendhal/data/EmojiStore.ts +++ b/src/js/stendhal/data/EmojiStore.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright © 2003-2023 - Stendhal * + * Copyright © 2003-2026 - Stendhal * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,10 +9,9 @@ * * ***************************************************************************/ -import { Paths } from "./Paths"; import { JSONLoader } from "../util/JSONLoader"; - -declare var stendhal: any; +import { htmlImageStore } from "./HTMLImageStore"; +import { Paths } from "./Paths"; export class EmojiStore { @@ -71,7 +70,7 @@ export class EmojiStore { if (!filename) { return; } - return stendhal.data.sprites.get(filename); + return htmlImageStore.get(filename); } /** diff --git a/src/js/stendhal/data/EntityOverlayRegistry.ts b/src/js/stendhal/data/EntityOverlayRegistry.ts index c651e405cd..3be6716b9b 100644 --- a/src/js/stendhal/data/EntityOverlayRegistry.ts +++ b/src/js/stendhal/data/EntityOverlayRegistry.ts @@ -10,7 +10,7 @@ * * ***************************************************************************/ -declare var stendhal: any; +import { stendhal } from "../stendhal"; import { Entity } from "../entity/Entity"; diff --git a/src/js/stendhal/data/GroupManager.ts b/src/js/stendhal/data/GroupManager.ts index 9423ba2f6a..3c2b6f9b49 100644 --- a/src/js/stendhal/data/GroupManager.ts +++ b/src/js/stendhal/data/GroupManager.ts @@ -9,7 +9,7 @@ * * ***************************************************************************/ -declare var stendhal: any; +import { stendhal } from "../stendhal"; import { SlashActionRepo } from "../SlashActionRepo"; diff --git a/src/js/stendhal/data/HTMLImageStore.ts b/src/js/stendhal/data/HTMLImageStore.ts new file mode 100644 index 0000000000..3ac5b9c9f4 --- /dev/null +++ b/src/js/stendhal/data/HTMLImageStore.ts @@ -0,0 +1,290 @@ +/*************************************************************************** + * (C) Copyright 2003-2026 - Stendhal * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { Paths } from "./Paths"; +import { ImageFilter } from "../sprite/image/ImageFilter"; + +export class HTMLImageStore { + + private knownBrokenUrls: {[url: string]: boolean} = {}; + private images: {[filename: string]: CanvasImageSource} = {}; + + private knownShadows: {[key: string]: boolean} = { + "24x32": true, + "32x32": true, + "32x48": true, + "32x48_long": true, + "48x64": true, + "48x64_float": true, + "64x48": true, + "64x64": true, + "64x85": true, + "64x96": true, + "76x64": true, + "81x96": true, + "96x96": true, + "96x128": true, + "128x96": true, + "128x170": true, + "144x128": true, + "168x224": true, + "192x192": true, + "192x192_float": true, + "192x256": true, + "320x440": true, + "ent": true + }; + + // alternatives for known images that may be considered violent or mature + private knownSafeSprites: {[filename: string]: boolean} = { + [Paths.sprites + "/monsters/huge_animal/thing"]: true, + [Paths.sprites + "/monsters/mutant/imperial_mutant"]: true, + [Paths.sprites + "/monsters/undead/bloody_zombie"]: true, + [Paths.sprites + "/npc/deadmannpc"]: true + }; + + // TODO: move to animation.json + animations: {[key: string]: any} = { + idea: { + "love": {delay: 100, offsetX: 24, offsetY: -8} + } + }; + + + get(filename: string): any { + if (!filename) { + return {}; + } + if (filename.indexOf("undefined") > -1) { + if (!this.knownBrokenUrls[filename]) { + console.log("Broken image path: ", filename, new Error()); + } + this.knownBrokenUrls[filename] = true; + return {}; + } + if (this.images[filename]) { + return this.images[filename]; + } + var temp = new Image(); + temp.onerror = (function(t: HTMLImageElement, store: HTMLImageStore) { + return function() { + if (t.src && !store.knownBrokenUrls[t.src]) { + console.log("Broken image path:", t.src, new Error()); + store.knownBrokenUrls[t.src] = true; + } + const failsafe = store.getFailsafe(); + if (failsafe.src && t.src !== failsafe.src) { + t.src = failsafe.src; + } + }; + })(temp, this); + temp.src = filename; + this.images[filename] = temp; + return temp; + } + + /** + * Rotates an image. + * + * @param img + * Image to be rotated. + * @param angle + * Angle of rotation. + */ + private rotate(img: HTMLImageElement, angle: number) { + const canvas = document.getElementById("drawing-stage")! as HTMLCanvasElement; + const ctx = canvas.getContext("2d")!; + // make sure working with blank canvas + ctx.clearRect(0, 0, canvas.width, canvas.height); + canvas.width = img.width; + canvas.height = img.height; + + ctx.translate(canvas.width / 2, canvas.height / 2); + ctx.rotate(angle * Math.PI / 180); + ctx.translate(-canvas.width / 2, -canvas.height / 2); + ctx.drawImage(img, 0, 0); + + img.src = canvas.toDataURL("image/png"); + } + + /** + * Retrieves a rotated image. + * + * @param filename + * Path to target image file. + * @param angle + * Angle of rotation. + * @return + * HTMLImageElement. + */ + getRotated(filename: string, angle: number): any { + if (angle == 0) { + return this.get(filename); + } + const id = filename + "-rot" + angle; + if (this.images[id]) { + return this.get(id); + } + // NOTE: cannot use HTMLImageElement.cloneNode here, must get base image then transfer + // `src` property when ready + const img = new Image(); + img.onload = () => { + img.onload = null; + this.rotate(img, angle); + } + const baseImg = this.get(filename); + if (baseImg.complete) { + img.src = baseImg.src; + } else { + baseImg.onload = () => { + baseImg.onload = null; + img.src = baseImg.src; + } + } + this.images[id] = img; + return img; + } + + /** + * Adds an image to cache. + * + * @param {string} id + * Cache identifier. + * @param {SpriteImage} + * Image to be cached. + */ + cache(id: string, image: CanvasImageSource) { + this.images[id] = image; + } + + /** + * Used when we only want an image if it was previously cached. + * + * @param filename + * Full file path. + * @return + * HTMLImageElement or undefined. + */ + getCached(filename: string): any { + return this.images[filename]; + } + + /** + * Retrieves the failsafe sprite. + * + * @return + * HTMLImageElement with failsafe image data. + */ + getFailsafe(): HTMLImageElement { + const filename = Paths.sprites + "/failsafe.png"; + let failsafe = this.images[filename]; + if (failsafe) { + } else { + failsafe = new Image(); + failsafe.src = filename; + this.images[filename] = failsafe; + } + return failsafe as HTMLImageElement; + } + + /** + * Checks cached images for a valid filename. + * + * @param filename + * Image filename to be checked. + * @return + * Path to image or failsafe image file. + */ + checkPath(filename: string): string { + return this.get(filename).src; + } + + + /** + * @param {string} fileName + * @param {string} filter + * @param {number=} param + */ + getFiltered(fileName: string, filter: string, param?: number) { + const img = this.get(fileName); + if (!img.complete || img.width === 0 || img.height === 0) { + return img; + } + const filteredName = fileName + " " + filter + " " + param; + let filtered = this.images[filteredName]; + if (typeof(filtered) === "undefined") { + const canvas = document.createElement("canvas") as any; + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext("2d")!; + ctx.drawImage(img, 0, 0); + const imgData = ctx.getImageData(0, 0, img.width, img.height); + new ImageFilter().filter(imgData, filter, param); + ctx.putImageData(imgData, 0, 0); + canvas.complete = true; + this.images[filteredName] = filtered = canvas; + } + + return filtered; + } + + + /** + * Retrieves a shadow sprite if the style is available. + * + * @param shadowStyle + * Style of shadow to get from cache. + * @return + * Image sprite or undefined. + */ + getShadow(shadowStyle: string): any { + if (this.knownShadows[shadowStyle]) { + const img = new Image(); + img.src = Paths.sprites + "/shadow/" + shadowStyle + ".png"; + return img; + } + return undefined; + } + + /** + * Checks if there is a "safe" image available for sprite. + * + * @param filename + * The sprite image base file path. + * @return + * true if a known safe image is available. + */ + hasSafeImage(filename: string): boolean { + return this.knownSafeSprites[filename] == true; + } + + /** + * Called at startup to pre-cache certain images. + */ + startupCache() { + // failsafe image + this.getFailsafe(); + // tutorial profile + this.get(Paths.sprites + "/npc/floattingladynpc.png"); + // achievement assets + this.get(Paths.gui + "/banner_background.png"); + for (const cat of ["commerce", "deathmatch", "experience", "fighting", "friend", + "interior_zone", "item", "obtain", "outside_zone", "production", "quest", + "quest_ados_items", "quest_kill_blordroughs", "quest_kirdneh_item", + "quest_mithrilbourgh_enemy_army", "quest_semos_monster", "special", + "underground_zone"]) { + this.get(Paths.achievements + "/" + cat + ".png"); + } + } +} + +// SpriteStore singleton instance +export const htmlImageStore = /* @__PURE__ */ new HTMLImageStore(); diff --git a/src/js/stendhal/data/ImagePreloader.ts b/src/js/stendhal/data/ImagePreloader.ts deleted file mode 100644 index cb83480709..0000000000 --- a/src/js/stendhal/data/ImagePreloader.ts +++ /dev/null @@ -1,174 +0,0 @@ -/*************************************************************************** - * (C) Copyright 2003-2023 - Stendhal * - *************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU Affero General Public License as * - * published by the Free Software Foundation; either version 3 of the * - * License, or (at your option) any later version. * - * * - ***************************************************************************/ - -declare var stendhal: any; - - -/* - * Start: http://www.webreference.com/programming/javascript/gr/column3/ - * ( https://web.archive.org/web/20110910064905/http://www.webreference.com:80/programming/javascript/gr/column3/index.html ) - * -function ImagePreloader(images, callback) { - // store the call-back - this.callback = callback; - - // initialize internal state. - this.nLoaded = 0; - this.nProcessed = 0; - stendhal.data.map.aImages = new Array; - - // record the number of images. - this.nImages = images.length; - - // for each image, call preload() - for ( var i = 0; i < images.length; i++) { - this.preload(images[i]); - } -} - -ImagePreloader.prototype.preload = function(image) { - // create new Image object and add to array - var oImage = new Image; - stendhal.data.map.aImages.push(oImage); - - // set up event handlers for the Image object - oImage.onload = ImagePreloader.prototype.onload; - oImage.onerror = ImagePreloader.prototype.onerror; - oImage.onabort = ImagePreloader.prototype.onabort; - - // assign pointer back to this. - oImage.oImagePreloader = this; - oImage.bLoaded = false; - - // assign the .src property of the Image object - oImage.src = image; -}; - -ImagePreloader.prototype.onComplete = function() { - this.nProcessed++; - if (this.nProcessed == this.nImages) { - this.callback(); - } -}; - -ImagePreloader.prototype.onload = function() { - this.bLoaded = true; - this.oImagePreloader.nLoaded++; - this.oImagePreloader.onComplete(); -}; - -ImagePreloader.prototype.onerror = function() { - this.bError = true; - this.oImagePreloader.onComplete(); - console.error("Error loading " + this.src); -}; - -ImagePreloader.prototype.onabort = function() { - this.bAbort = true; - this.oImagePreloader.onComplete(); - console.error("Loading " + this.src + " was aborted"); -}; - * - * End: http://www.webreference.com/programming/javascript/gr/column3/ - */ - - -/** - * preloads images - * - * @param images image url to load - * @param callback callback to invoke - * @constructor - */ -export class ImagePreloader extends Function { - - oImagePreloader: ImagePreloader; - src?: string; - callback: Function; - - // internal state - bLoaded: boolean; - bError: boolean; - bAbort: boolean; - nLoaded: number; - nProcessed: number; - - // number of images - nImages: number; - - - constructor(images: string[], callback: Function) { - super(); - - this.oImagePreloader = {} as ImagePreloader; - // store the call-back - this.callback = callback; - - // initialize internal state. - this.bLoaded = false; - this.bError = false; - this.bAbort = false; - this.nLoaded = 0; - this.nProcessed = 0; - stendhal.data.map.aImages = new Array(); - - // record the number of images. - this.nImages = images.length; - - // for each image, call preload() - for (let i = 0; i < images.length; i++) { - this.preload(images[i]); - } - } - - preload(image: string) { - // create new Image object and add to array - const oImage = new Image() as any; - stendhal.data.map.aImages.push(oImage); - - // set up event handlers for the Image object - oImage.onload = ImagePreloader.prototype.onload; - oImage.onerror = ImagePreloader.prototype.onerror; - oImage.onabort = ImagePreloader.prototype.onabort; - - // assign pointer back to this - oImage.oImagePreloader = this; - oImage.bLoaded = false; - - // assign the .src property of the Image object - oImage.src = image; - } - - onComplete() { - this.nProcessed++; - if (this.nProcessed == this.nImages) { - this.callback(); - } - } - - onload() { - this.bLoaded = true; - this.oImagePreloader.nLoaded++; - this.oImagePreloader.onComplete(); - } - - onerror() { - this.bError = true; - this.oImagePreloader.onComplete(); - console.error("Error loading " + this.src); - } - - onabort() { - this.bAbort = true; - this.oImagePreloader.onComplete(); - console.error("Loading " + this.src + " was aborted"); - } -} diff --git a/src/js/stendhal/data/Outfit.ts b/src/js/stendhal/data/Outfit.ts index 41aad65b05..6052266214 100644 --- a/src/js/stendhal/data/Outfit.ts +++ b/src/js/stendhal/data/Outfit.ts @@ -10,11 +10,14 @@ * * ***************************************************************************/ -import { DrawingStage } from "../util/DrawingStage"; import { Pair } from "../util/Pair"; import { StringUtil } from "../util/StringUtil"; +import { Paths } from "./Paths"; -declare var stendhal: any; +import { stendhal } from "../stendhal"; +import { ImageMetadata } from "sprite/image/ImageMetadata"; +import { MultilayerImageLoader } from "sprite/image/MultilayerImageLoader"; +import { OutfitStore } from "./OutfitStore"; /** @@ -113,7 +116,7 @@ export class Outfit { public getLayers(): Pair[] { const layers: Pair[] = []; // only include valid layers - for (const name of stendhal.data.outfit.getLayerNames()) { + for (const name of OutfitStore.get().getLayerNames()) { let index = this.layers[name]; if (index != undefined) { layers.push(new Pair(name, index)); @@ -151,7 +154,7 @@ export class Outfit { * Color value or `undefined`. */ public getLayerColor(name: string): number|undefined { - if (stendhal.data.outfit.isSkinLayer(name)) { + if (OutfitStore.get().isSkinLayer(name)) { name = "skin"; } return this.coloring && name in this.coloring ? this.coloring[name] : undefined; @@ -193,40 +196,14 @@ export class Outfit { * * TODO: * - use dress layer based on body type - * - * @param {Function} callback - * Function to pass image to when ready. */ - toImage(callback: Function) { - const sig = this.getSignature(); - // get directly from cache since we don't want to return failsafe image - let image: HTMLImageElement = stendhal.data.sprites.getCached(sig); - - const onReady = function(e?: Event) { - image.removeEventListener("load", onReady); - callback(image); - }; - - if (image instanceof HTMLImageElement) { - if (image.height > 0) { - onReady(); - } else { - image.addEventListener("load", onReady); - } - return; - } - - const stage = DrawingStage.get(); - stage.reset(); - stage.setSize(48 * 3, 64 * 4); - + async toImage(): Promise { + let parts: ImageMetadata[] = []; let outfitLayers = this.getLayers(); const detailIndex = this.getLayerIndex("detail") || 0; - if (stendhal.data.outfit.detailHasRearLayer(detailIndex)) { + if (OutfitStore.get().detailHasRearLayer(detailIndex)) { outfitLayers = [new Pair("detail-rear", detailIndex), ...outfitLayers]; } - - const layers: HTMLImageElement[] = []; for (const l of outfitLayers) { let layerName = l.first; let suffix = ""; @@ -235,58 +212,21 @@ export class Outfit { layerName = layerName.replace(/-rear$/, ""); } const layerIndex = l.second; - let layerPath = stendhal.paths.sprites + "/outfit/" + layerName + "/" + let layerPath = Paths.sprites + "/outfit/" + layerName + "/" + StringUtil.padLeft(""+layerIndex, "0", 3) + suffix; if (layerName === "body" && stendhal.config.getBoolean("effect.no-nude")) { // FIXME: some non-player pickable bodies don't have "no-nude" variant layerPath += "-nonude"; } layerPath += ".png"; - const coloring = this.getLayerColor(layerName); - if (typeof(coloring) === "undefined") { - layers.push(stendhal.data.sprites.get(layerPath)); - } else { - layers.push(stendhal.data.sprites.getFiltered(layerPath, "trueColor", coloring)); + let filter = undefined; + let coloring = this.getLayerColor(layerName); + if (coloring) { + filter = "trueColor"; } + parts.push(new ImageMetadata(layerPath, filter, coloring)); } - if (layers.length == 0) { - image = stendhal.data.sprites.getFailsafe(); - callback(image); - return; - } - - let onAllLayersReady: Function; - const onLayerReady = function(e?: Event) { - for (const layer of layers) { - if (!layer.complete || layer.height === 0) { - return; - } - } - onAllLayersReady(); - }; - - onAllLayersReady = function() { - for (const layer of layers) { - layer.removeEventListener("load", onLayerReady); - stage.drawImage(layer); - } - image = stage.toImage(); - stage.reset(); - stendhal.data.sprites.cache(sig, image); - if (image.height > 0) { - onReady(); - } else { - image.addEventListener("load", onReady); - } - }; - - for (const layer of layers) { - if (layer.height > 0) { - onLayerReady(); - } else { - layer.addEventListener("load", onLayerReady); - } - } + return await new MultilayerImageLoader(parts).load(); } } diff --git a/src/js/stendhal/data/OutfitStore.ts b/src/js/stendhal/data/OutfitStore.ts index 8c4bb5eb8b..aaa22018f6 100644 --- a/src/js/stendhal/data/OutfitStore.ts +++ b/src/js/stendhal/data/OutfitStore.ts @@ -25,7 +25,7 @@ export class OutfitStore { // player pickable layers - private count: {[key: string]: number} = { + public readonly count: {[key: string]: number} = { "hat": 19, "hair": 57, "mask": 9, diff --git a/src/js/stendhal/data/SpriteStore.ts b/src/js/stendhal/data/SpriteStore.ts deleted file mode 100644 index ad165fca12..0000000000 --- a/src/js/stendhal/data/SpriteStore.ts +++ /dev/null @@ -1,618 +0,0 @@ -/*************************************************************************** - * (C) Copyright 2003-2024 - Stendhal * - *************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU Affero General Public License as * - * published by the Free Software Foundation; either version 3 of the * - * License, or (at your option) any later version. * - * * - ***************************************************************************/ - -declare var stendhal: any; - -import { Paths } from "./Paths"; - - -class SpriteImage extends Image { - // number of times the image has been accessed after initial creation - counter = 0; -} - -export class SpriteStore { - - private knownBrokenUrls: {[url: string]: boolean} = {}; - private images: {[filename: string]: SpriteImage} = {}; - - private knownShadows: {[key: string]: boolean} = { - "24x32": true, - "32x32": true, - "32x48": true, - "32x48_long": true, - "48x64": true, - "48x64_float": true, - "64x48": true, - "64x64": true, - "64x85": true, - "64x96": true, - "76x64": true, - "81x96": true, - "96x96": true, - "96x128": true, - "128x96": true, - "128x170": true, - "144x128": true, - "168x224": true, - "192x192": true, - "192x192_float": true, - "192x256": true, - "320x440": true, - "ent": true - }; - - // alternatives for known images that may be considered violent or mature - private knownSafeSprites: {[filename: string]: boolean} = { - [Paths.sprites + "/monsters/huge_animal/thing"]: true, - [Paths.sprites + "/monsters/mutant/imperial_mutant"]: true, - [Paths.sprites + "/monsters/undead/bloody_zombie"]: true, - [Paths.sprites + "/npc/deadmannpc"]: true - }; - - // TODO: move to animation.json - private animations: {[key: string]: any} = { - idea: { - "love": {delay: 100, offsetX: 24, offsetY: -8} - } - }; - - - /** - * Hidden singleton constructor. - */ - protected constructor() { - // do nothing - } - - get(filename: string): any { - if (!filename) { - return {}; - } - if (filename.indexOf("undefined") > -1) { - if (!this.knownBrokenUrls[filename]) { - console.log("Broken image path: ", filename, new Error()); - } - this.knownBrokenUrls[filename] = true; - return {}; - } - if (this.images[filename]) { - this.images[filename].counter++; - return this.images[filename]; - } - var temp = new Image() as SpriteImage; - // TypeError: Image constructor: 'new' is required - //~ var temp = new SpriteImage(); - temp.counter = 0; - temp.onerror = (function(t: SpriteImage, store: SpriteStore) { - return function() { - if (t.src && !store.knownBrokenUrls[t.src]) { - console.log("Broken image path:", t.src, new Error()); - store.knownBrokenUrls[t.src] = true; - } - const failsafe = store.getFailsafe(); - if (failsafe.src && t.src !== failsafe.src) { - t.src = failsafe.src; - } - }; - })(temp, this); - temp.src = filename; - this.images[filename] = temp; - return temp; - } - - getWithPromise(filename: string): any { - return new Promise((resolve) => { - if (typeof(this.images[filename]) != "undefined") { - this.images[filename].counter++; - resolve(this.images[filename]); - } - - const image = new Image() as SpriteImage; - // TypeError: Image constructor: 'new' is required - //~ const image = new SpriteImage(); - image.counter = 0; - this.images[filename] = image; - image.onload = () => resolve(image); - image.src = filename; - }); - } - - /** - * Rotates an image. - * - * @param img - * Image to be rotated. - * @param angle - * Angle of rotation. - */ - private rotate(img: HTMLImageElement, angle: number) { - const canvas = document.getElementById("drawing-stage")!; - const ctx = canvas.getContext("2d")!; - // make sure working with blank canvas - ctx.clearRect(0, 0, canvas.width, canvas.height); - canvas.width = img.width; - canvas.height = img.height; - - ctx.translate(canvas.width / 2, canvas.height / 2); - ctx.rotate(angle * Math.PI / 180); - ctx.translate(-canvas.width / 2, -canvas.height / 2); - ctx.drawImage(img, 0, 0); - - img.src = canvas.toDataURL("image/png"); - } - - /** - * Retrieves a rotated image. - * - * @param filename - * Path to target image file. - * @param angle - * Angle of rotation. - * @return - * HTMLImageElement. - */ - getRotated(filename: string, angle: number): any { - if (angle == 0) { - return this.get(filename); - } - const id = filename + "-rot" + angle; - if (this.images[id]) { - return this.get(id); - } - // NOTE: cannot use HTMLImageElement.cloneNode here, must get base image then transfer - // `src` property when ready - const img = new Image() as SpriteImage; - img.counter = 0; - img.onload = () => { - img.onload = null; - this.rotate(img, angle); - } - const baseImg = this.get(filename); - if (baseImg.complete) { - img.src = baseImg.src; - } else { - baseImg.onload = () => { - baseImg.onload = null; - img.src = baseImg.src; - } - } - this.images[id] = img; - return img; - } - - /** - * Adds an image to cache. - * - * @param {string} id - * Cache identifier. - * @param {SpriteImage} - * Image to be cached. - */ - cache(id: string, image: SpriteImage) { - image.counter = 0; - this.images[id] = image; - } - - /** - * Used when we only want an image if it was previously cached. - * - * @param filename - * Full file path. - * @return - * HTMLImageElement or undefined. - */ - getCached(filename: string): any { - return this.images[filename]; - } - - /** - * Retrieves the failsafe sprite. - * - * @return - * HTMLImageElement with failsafe image data. - */ - getFailsafe(): HTMLImageElement { - const filename = Paths.sprites + "/failsafe.png"; - let failsafe = this.images[filename]; - if (failsafe) { - failsafe.counter++; - } else { - failsafe = new Image() as SpriteImage; - // TypeError: Image constructor: 'new' is required - //~ failsafe = new SpriteImage(); - failsafe.counter = 0; - failsafe.src = filename; - this.images[filename] = failsafe; - } - return failsafe; - } - - /** - * Checks cached images for a valid filename. - * - * @param filename - * Image filename to be checked. - * @return - * Path to image or failsafe image file. - */ - checkPath(filename: string): string { - return this.get(filename).src; - } - - /** deletes all objects that have not been accessed since this method was called last time */ - // TODO: call clean on map change - clean() { - for (var i in this.images) { - console.log(typeof(this.images[i])); - if (this.images[i] instanceof SpriteImage) { - if (this.images[i].counter > 0) { - this.images[i].counter--; - } else { - delete(this.images[i]); - } - } - } - } - - /** - * Get an image element whose image data is an area of a specified image. - * If the area matches the original image, the image itself is returned. - * Otherwise a copy of the image data is returned. This is meant - * to be used for obtaining the drag image for drag and drop. - * - * @param image original image - * @param width width of the area - * @param height height of the area - * @param {number=} offsetX optional. left x coordinate of the area - * @param {number=} offsetY optional. top y coordinate of the area - */ - getAreaOf(image: HTMLImageElement, width: number, height: number, - offsetX?: number, offsetY?: number): any { - try { - offsetX = offsetX || 0; - offsetY = offsetY || 0; - if ((image.width === width) && (image.height === height) - && (offsetX === 0) && (offsetY === 0)) { - return image; - } - var canvas = document.createElement("canvas") as HTMLCanvasElement; - canvas.width = width; - canvas.height = height; - var ctx = canvas.getContext("2d")!; - ctx.drawImage(image, offsetX, offsetY, width, height, 0, 0, width, height); - // Firefox would be able to use the canvas directly as a drag image, but - // Chrome does not. This should work in any standards compliant browser. - var newImage = new Image(); - newImage.src = canvas.toDataURL("image/png"); - return newImage; - } catch (err) { - if (err instanceof DOMException) { - return this.getFailsafe(); - } else { - // don't ignore other errors - throw err; - } - } - - return {}; - } - - /** - * @param {string} fileName - * @param {string} filter - * @param {number=} param - */ - getFiltered(fileName: string, filter: string, param?: number) { - const img = this.get(fileName); - let filterFn; - if (typeof(filter) === "undefined" - || !(filterFn = this.filter[filter]) - || !img.complete || img.width === 0 || img.height === 0) { - return img; - } - const filteredName = fileName + " " + filter + " " + param; - let filtered: SpriteImage = this.images[filteredName]; - if (typeof(filtered) === "undefined") { - const canvas = document.createElement("canvas") as any; - canvas.width = img.width; - canvas.height = img.height; - const ctx = canvas.getContext("2d")!; - ctx.drawImage(img, 0, 0); - const imgData = ctx.getImageData(0, 0, img.width, img.height); - const data = imgData.data; - filterFn(data, param); - ctx.putImageData(imgData, 0, 0); - canvas.complete = true; - this.images[filteredName] = filtered = canvas as SpriteImage; - } - - return filtered; - } - - /** - * @param {string} fileName - * @param {string} filter - * @param {number=} param - */ - getFilteredWithPromise(fileName: string, filter: string, param?: number) { - const imgPromise = this.getWithPromise(fileName); - return imgPromise.then(function (img: HTMLImageElement) { - let filterFn: Function; - if (typeof(filter) === "undefined" - || !(filterFn = stendhal.data.sprites.filter[filter]) - || !img.complete || img.width === 0 || img.height === 0) { - return img; - } - const filteredName = fileName + " " + filter + " " + param; - let filtered: SpriteImage = stendhal.data.sprites.images[filteredName]; - if (typeof(filtered) === "undefined") { - const canvas = document.createElement("canvas") as any; - canvas.width = img.width; - canvas.height = img.height; - const ctx = canvas.getContext("2d")!; - ctx.drawImage(img, 0, 0); - const imgData = ctx.getImageData(0, 0, img.width, img.height); - const data = imgData.data; - filterFn(data, param); - ctx.putImageData(imgData, 0, 0); - canvas.complete = true; - stendhal.data.sprites.images[filteredName] = filtered = canvas as SpriteImage; - } - - return filtered; - }); - } - - - /** Image filters */ - filter: {[key: string]: Function} = { - // Helper functions - /** - * @param {Number} rgb - * @return {Array} - */ - splitrgb: function(rgb: number): number[] { - rgb &= 0xffffff; - var b = rgb & 0xff; - rgb >>>= 8; - var g = rgb & 0xff; - rgb >>>= 8; - return [rgb, g, b]; - }, - - mergergb: function(rgbArray: number[]): number { - const r = rgbArray[0] << 16; - const g = rgbArray[1] << 8; - return 0xffffff & (r | g | rgbArray[2]); - }, - - /** - * @param {Array} rgb - * @return {Array} - */ - rgb2hsl: function(rgb: number[]): number[] { - var r = rgb[0] / 255; - var g = rgb[1] / 255; - var b = rgb[2] / 255; - - var max, min, maxVar; - // Find the max and minimum colors, and remember which one it was - if (r > g) { - max = r; - min = g; - maxVar = 0; - } else { - max = g; - min = r; - maxVar = 1; - } - if (b > max) { - max = b; - maxVar = 2; - } else if (b < min) { - min = b; - } - - // lightness - var l = (max + min) / 2; - var s, h; - - // saturation - var diff = max - min; - if (diff < 0.000001) { - s = 0; - // hue not really defined, but set it to something reasonable - h = 0; - } else { - if (l < 0.5) { - s = diff / (max + min); - } else { - s = diff / (2 - max - min); - } - - // hue - if (maxVar === 0) { - h = (g - b) / diff; - } else if (maxVar === 1) { - h = 2 + (b - r) / diff; - } else { - h = 4 + (r - g) / diff; - } - // Normalize to range [0, 1]. It's more useful than the usual 360 - h /= 6; - } - - return [h, s, l]; - }, - - /** - * @param {Array} hsl - * @return {Array} - */ - hsl2rgb: function(hsl: number[]): number[] { - var r, g, b; - var h = hsl[0]; - var s = hsl[1]; - var l = hsl[2]; - - if (s < 0.0000001) { - r = g = b = Math.floor(255 * l); - } else { - var tmp1, tmp2; - if (l < 0.5) { - tmp1 = l * (1 + s); - } else { - tmp1 = l + s - l * s; - } - tmp2 = 2 * l - tmp1; - - var rf = stendhal.data.sprites.filter.hue2rgb(this.limitHue(h + 1/3), tmp2, tmp1); - var gf = stendhal.data.sprites.filter.hue2rgb(this.limitHue(h), tmp2, tmp1); - var bf = stendhal.data.sprites.filter.hue2rgb(this.limitHue(h - 1/3), tmp2, tmp1); - - r = Math.floor(255 * rf) & 0xff; - g = Math.floor(255 * gf) & 0xff; - b = Math.floor(255 * bf) & 0xff; - } - - return [r, g, b]; - }, - - /** - * @param {Number} hue - * @param {Number} val1 - * @param {Number} val2 - */ - hue2rgb: function(hue: number, val1: number, val2: number): number { - var res = hue; - if (6 * hue < 1) { - res = val1 + (val2 - val1) * 6 * hue; - } else if (2 * hue < 1) { - res = val2; - } else if (3 * hue < 2) { - res = val1 + (val2 - val1) * (2/3 - hue) * 6; - } else { - res = val1; - } - - return res; - }, - - /** - * @param {Number} hue - */ - limitHue: function(hue: number): number { - var res = hue; - if (res < 0) { - res += 1; - } else if (res > 1) { - res -= 1; - } - return res; - } - } - - /** - * Retrieves a shadow sprite if the style is available. - * - * @param shadowStyle - * Style of shadow to get from cache. - * @return - * Image sprite or undefined. - */ - getShadow(shadowStyle: string): any { - if (this.knownShadows[shadowStyle]) { - const img = new Image(); - img.src = Paths.sprites + "/shadow/" + shadowStyle + ".png"; - return img; - } - return undefined; - } - - /** - * Checks if there is a "safe" image available for sprite. - * - * @param filename - * The sprite image base file path. - * @return - * true if a known safe image is available. - */ - hasSafeImage(filename: string): boolean { - return this.knownSafeSprites[filename] == true; - } - - /** - * Called at startup to pre-cache certain images. - */ - startupCache() { - // failsafe image - this.getFailsafe(); - // tutorial profile - this.get(Paths.sprites + "/npc/floattingladynpc.png"); - // achievement assets - this.get(Paths.gui + "/banner_background.png"); - for (const cat of ["commerce", "deathmatch", "experience", "fighting", "friend", - "interior_zone", "item", "obtain", "outside_zone", "production", "quest", - "quest_ados_items", "quest_kill_blordroughs", "quest_kirdneh_item", - "quest_mithrilbourgh_enemy_army", "quest_semos_monster", "special", - "underground_zone"]) { - this.get(Paths.achievements + "/" + cat + ".png"); - } - // weather - for (const weather of ["clouds", "fog", "fog_heavy", "rain", "rain_heavy", - "rain_light", "snow", "snow_heavy", "snow_light", "wave"]) { - this.get(Paths.weather + "/" + weather + ".png"); - } - } -} - -/** - * Hidden class to create the singleton instance internally. - */ -class SpriteStoreInternal extends SpriteStore { - - /** Singleton instance. */ - private static instance: SpriteStore; - - /** - * Retrieves singleton instance. - */ - static get(): SpriteStore { - if (!SpriteStoreInternal.instance) { - SpriteStoreInternal.instance = new SpriteStore(); - } - return SpriteStoreInternal.instance; - } -} - -// SpriteStore singleton instance -export const store = SpriteStoreInternal.get(); -// *** Image filters. Prevent the closure compiler from mangling the names. *** -store.filter['trueColor'] = function(data: any, color: number) { - var hslColor = stendhal.data.sprites.filter.rgb2hsl(stendhal.data.sprites.filter.splitrgb(color)); - var end = data.length; - for (var i = 0; i < end; i += 4) { - var rgb = [data[i], data[i + 1], data[i + 2]]; - var hsl = stendhal.data.sprites.filter.rgb2hsl(rgb); - // Adjust the brightness - var adj = hslColor[2] - 0.5; // [-0.5, 0.5] - var tmp = hsl[2] - 0.5; // [-0.5, 0.5] - // tweaks the middle lights either upward or downward, depending - // on if source lightness is high or low - var l = hsl[2] - 2.0 * adj * ((tmp * tmp) - 0.25); - var resultHsl = [hslColor[0], hslColor[1], l]; - var resultRgb = stendhal.data.sprites.filter.hsl2rgb(resultHsl); - data[i] = resultRgb[0]; - data[i+1] = resultRgb[1]; - data[i+2] = resultRgb[2]; - } -}; diff --git a/src/js/stendhal/data/Map.ts b/src/js/stendhal/data/TileMap.ts similarity index 81% rename from src/js/stendhal/data/Map.ts rename to src/js/stendhal/data/TileMap.ts index b3a8c79364..ebd62a6154 100644 --- a/src/js/stendhal/data/Map.ts +++ b/src/js/stendhal/data/TileMap.ts @@ -9,54 +9,56 @@ * * ***************************************************************************/ -declare var marauroa: any; -declare var stendhal: any; +import { Deserializer } from "marauroa" +import { stendhal } from "../stendhal"; import { Paths } from "./Paths"; -import { LandscapeRenderingStrategy, CombinedTilesetRenderingStrategy } from "../landscape/LandscapeRenderingStrategy"; +import { LandscapeRenderingStrategy } from "../landscape/LandscapeRenderingStrategy"; +import { CombinedTilesetRenderingStrategy } from "../landscape/CombinedTilesetRenderingStrategy"; import { IndividualTilesetRenderingStrategy } from "../landscape/IndividualTilesetRenderingStrategy"; import { ParallaxBackground } from "../landscape/ParallaxBackground"; -export class Map { +export class TileMap { - private currentZoneName = ""; + public currentZoneName = ""; - private offsetX = 0; - private offsetY = 0; - private zoneSizeX = -1; - private zoneSizeY = -1; - private sizeX = 20; - private sizeY = 15; + public offsetX = 0; + public offsetY = 0; + public zoneSizeX = -1; + public zoneSizeY = -1; + public sizeX = 20; + public sizeY = 15; - private tileWidth = 32; - private tileHeight = 32; - private zoom = 100; + public tileWidth = 32; + public tileHeight = 32; + public zoom = 100; - private tilesetFilenames: string[] = []; - private aImages = -1; - private layerNames: any = -1; - private layers: any = -1; - private firstgids: any = -1; - private gidsindex: any = []; + public tilesetFilenames: string[] = []; + public aImages = -1; + public layerNames: any = -1; + public layers: any = -1; + public firstgids: any = -1; + public gidsindex: any = []; - private drawingError = false; - private targetTileWidth = 0; - private targetTileHeight = 0; + public drawingError = false; + public targetTileWidth = 0; + public targetTileHeight = 0; - private readonly layerGroups: any = [ + public readonly layerGroups = [ ["0_floor", "1_terrain", "2_object"], ["3_roof", "4_roof_add"] ]; - private layerGroupIndexes: any; + public readonly blendLayers = ["blend_ground", "blend_roof"] + public layerGroupIndexes: any; - private strategy: LandscapeRenderingStrategy; - private protection: any; - private collisionData: any; + public strategy: LandscapeRenderingStrategy; + public protection: any; + public collisionData: any; // alternatives for known images that may be considered violent or mature - private knownSafeTilesets: string[] = [ + public knownSafeTilesets: string[] = [ Paths.tileset + "/item/armor/bloodied_small_axe", Paths.tileset + "/item/blood/floor_stain", Paths.tileset + "/item/blood/floor_stains_2", @@ -64,22 +66,22 @@ export class Map { Paths.tileset + "/item/blood/small_stains" ]; - private parallax: ParallaxBackground; - private parallaxImage?: string; - private ignoredTiles: string[]; + public parallax: ParallaxBackground; + public parallaxImage?: string; + public ignoredTiles: string[]; /** Singleton instance. */ - private static instance: Map; + private static instance: TileMap; /** * Retrieves singleton instance. */ - static get(): Map { - if (!Map.instance) { - Map.instance = new Map(); + static get(): TileMap { + if (!TileMap.instance) { + TileMap.instance = new TileMap(); } - return Map.instance; + return TileMap.instance; } /** @@ -124,6 +126,8 @@ export class Map { this.decodeMapLayer(content, "2_object"); this.decodeMapLayer(content, "3_roof"); this.decodeMapLayer(content, "4_roof_add"); + this.decodeMapLayer(content, "blend_ground"); + this.decodeMapLayer(content, "blend_roof"); this.protection = this.decodeMapLayer(content, "protection"); this.collisionData = this.decodeMapLayer(content, "collision"); this.layerGroupIndexes = this.mapLayerGroup(); @@ -138,7 +142,7 @@ export class Map { decodeTileset(content: any, name: string) { var layerData = content[name]; - var deserializer = marauroa.Deserializer.fromBase64(layerData); + var deserializer = Deserializer.fromBase64(layerData); var amount = deserializer.readInt() as number; this.tilesetFilenames = []; @@ -192,7 +196,7 @@ export class Map { if (!layerData) { return; } - var deserializer = marauroa.Deserializer.fromDeflatedBase64(layerData); + var deserializer = Deserializer.fromDeflatedBase64(layerData); deserializer.readString(); // zone name this.zoneSizeX = deserializer.readInt(); this.zoneSizeY = deserializer.readInt(); diff --git a/src/js/stendhal/data/TileStore.ts b/src/js/stendhal/data/TileStore.ts index 17e20db3e9..1a3c2f2e73 100644 --- a/src/js/stendhal/data/TileStore.ts +++ b/src/js/stendhal/data/TileStore.ts @@ -12,9 +12,6 @@ import { Paths } from "./Paths"; import { JSONLoader } from "../util/JSONLoader"; -declare var stendhal: any; - - type AnimationMap = { [key: string]: any; } @@ -70,7 +67,7 @@ export class TileStore { this.landscapeMap = this.loadAnimations( loader.data["landscape"], Paths.tileset + "/"); this.weatherMap = this.loadAnimations( - loader.data["weather"], stendhal.paths.weather + "/"); + loader.data["weather"], Paths.weather + "/"); }; loader.load(Paths.tileset + "/animation.json"); } diff --git a/src/js/stendhal/data/sound/LoopedSoundSourceManager.ts b/src/js/stendhal/data/sound/LoopedSoundSourceManager.ts index bce6834e88..3eb6fe7791 100644 --- a/src/js/stendhal/data/sound/LoopedSoundSourceManager.ts +++ b/src/js/stendhal/data/sound/LoopedSoundSourceManager.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright © 2003-2024 - Faiumoni e. V. * + * Copyright © 2003-2026 - Faiumoni e. V. * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,10 +9,11 @@ * * ***************************************************************************/ -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { stendhal } from "../../stendhal"; import { LoopedSoundSource } from "../../entity/LoopedSoundSource"; +import { Zone } from "entity/Zone"; // server doesn't distinguish between music & looped sound effects so we @@ -179,8 +180,8 @@ export class LoopedSoundSourceManager { */ private getZoneEntities(): LoopedSoundSource[] { const ents: LoopedSoundSource[] = []; - if (stendhal.zone.entities) { - for (const ent of stendhal.zone.entities) { + if (Zone.get().entities) { + for (const ent of Zone.get().entities) { if (ent instanceof LoopedSoundSource) { ents.push(ent); } diff --git a/src/js/stendhal/data/sound/SoundLayer.ts b/src/js/stendhal/data/sound/SoundLayer.ts index 138db35491..667b704ce3 100644 --- a/src/js/stendhal/data/sound/SoundLayer.ts +++ b/src/js/stendhal/data/sound/SoundLayer.ts @@ -16,7 +16,7 @@ import { AbstractEnum } from "../enum/AbstractEnum"; /** * Available layers of sound. * - * NOTE: string names should match `games.stendhal.common.constants.SoundLayer` + * string names should match `games.stendhal.common.constants.SoundLayer` */ export class SoundLayer extends AbstractEnum { @@ -29,9 +29,6 @@ export class SoundLayer extends AbstractEnum { static readonly SFX = new SoundLayer("sfx"); static readonly GUI = new SoundLayer("gui"); - /** Value is required. */ - override readonly value!: string; - private constructor(value: string) { super(value); @@ -47,7 +44,7 @@ export class SoundLayer extends AbstractEnum { static names(): string[] { const names: string[] = []; for (const layer of SoundLayer.layers) { - names.push(layer.value); + names.push(layer.value!); } return names; } diff --git a/src/js/stendhal/data/sound/SoundManager.ts b/src/js/stendhal/data/sound/SoundManager.ts index 27c8194081..100899de53 100644 --- a/src/js/stendhal/data/sound/SoundManager.ts +++ b/src/js/stendhal/data/sound/SoundManager.ts @@ -9,16 +9,15 @@ * * ***************************************************************************/ -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { stendhal } from "../../stendhal"; import { SoundFactory } from "./SoundFactory"; import { SoundObject } from "./SoundFactory"; import { SoundLayer } from "./SoundLayer"; -import { singletons } from "../../SingletonRepo"; - import { ui } from "../../ui/UI"; +import { Paths } from "../Paths"; /** @@ -244,7 +243,7 @@ export class SoundManager { let snd = this.cache[soundName] || this.cacheGlobal[soundName]; if (!snd) { // add new sound to cache - snd = this.load(soundName, stendhal.paths.sounds + "/" + soundName + ".ogg"); + snd = this.load(soundName, Paths.sounds + "/" + soundName + ".ogg"); } if (!this.cache[soundName]) { @@ -406,7 +405,7 @@ export class SoundManager { volume=1.0): SoundObject|undefined { // load into cache so playEffect doesn't look in "data/sounds" if (!this.cache[musicName]) { - this.load(musicName, stendhal.paths.music + "/" + musicName + ".ogg"); + this.load(musicName, Paths.music + "/" + musicName + ".ogg"); } return this.playLocalizedLoop(x, y, radius, layer, musicName, volume); } @@ -424,7 +423,7 @@ export class SoundManager { playGlobalizedMusic(musicName: string, volume=1.0): SoundObject|undefined { // load into cache so playEffect doesn't look in "data/sounds" if (!this.cache[musicName]) { - this.load(musicName, stendhal.paths.music + "/" + musicName + ".ogg"); + this.load(musicName, Paths.music + "/" + musicName + ".ogg"); } return this.playGlobalizedLoop(musicName, "music", volume); } @@ -796,6 +795,6 @@ export class SoundManager { startupCache() { // login sound this.load("ui/login", - stendhal.paths.sounds + "/ui/login.ogg", true); + Paths.sounds + "/ui/login.ogg", true); } } diff --git a/src/js/stendhal/entity/ActiveEntity.ts b/src/js/stendhal/entity/ActiveEntity.ts index 0807fb1808..80152deba7 100644 --- a/src/js/stendhal/entity/ActiveEntity.ts +++ b/src/js/stendhal/entity/ActiveEntity.ts @@ -13,7 +13,9 @@ import { Entity } from "./Entity"; import { Direction } from "../util/Direction"; import { MathUtil } from "../util/MathUtil"; -declare var stendhal: any; +import { stendhal } from "../stendhal"; +import { TileMap } from "data/TileMap"; +import { Zone } from "./Zone"; export class ActiveEntity extends Entity { @@ -230,9 +232,10 @@ export class ActiveEntity extends Entity { var startY = Math.floor(this["_y"]); var endX = Math.ceil(this["_x"] + this["width"]); var endY = Math.ceil(this["_y"] + this["height"]); + let map = TileMap.get(); for (var y = startY; y < endY; y++) { for (var x = startX; x < endX; x++) { - if (stendhal.data.map.collision(x, y)) { + if (map.collision(x, y)) { return true; } } @@ -258,8 +261,8 @@ export class ActiveEntity extends Entity { var thisEndY = Math.ceil(this["_y"] + this["height"]); var i; - for (i in stendhal.zone.entities) { - var other = stendhal.zone.entities[i]; + for (i in Zone.get().entities) { + var other = Zone.get().entities[i]; if (!this.isObstacle(other)) { continue; } diff --git a/src/js/stendhal/entity/Blood.ts b/src/js/stendhal/entity/Blood.ts index 680b8a26c5..6fed890f72 100644 --- a/src/js/stendhal/entity/Blood.ts +++ b/src/js/stendhal/entity/Blood.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2024 - Stendhal * + * (C) Copyright 2003-2026 - Stendhal * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,9 +9,13 @@ * * ***************************************************************************/ +import { RenderingContext2D } from "util/Types"; import { Entity } from "./Entity"; +import { Paths } from "../data/Paths"; -declare var stendhal: any; +import { stendhal } from "../stendhal"; +import { ImageSprite } from "sprite/image/ImageSprite"; +import { images } from "sprite/image/ImageManager"; export class Blood extends Entity { @@ -20,27 +24,30 @@ export class Blood extends Entity { constructor() { super(); - this.sprite = { - height: 32, - width: 32, - filename: stendhal.paths.sprites + "/combat/blood_red.png" - }; + this.imageSprite = new ImageSprite( + images.load(Paths.sprites + "/combat/blood_red.png"), + 0, 0, 32, 32 + ); } override set(key: string, value: any) { super.set(key, value); if (key === "amount") { - this.sprite.offsetY = parseInt(value, 10) * 32; + this.imageSprite!.offsetY = parseInt(value, 10) * 32; } else if (key === "class") { - this.sprite.filename = stendhal.paths.sprites + "/combat/blood_" + value + ".png"; + this.imageSprite?.free(); + this.imageSprite = new ImageSprite( + images.load(Paths.sprites + "/combat/blood_" + value + ".png"), + 0, parseInt(this["amount"], 10) * 32, 32, 32 + ); } } - override getCursor(x: number, y: number) { - return "url(" + stendhal.paths.sprites + "/cursor/walk.png) 1 3, auto"; + override getCursor(_x: number, _y: number) { + return "url(" + Paths.sprites + "/cursor/walk.png) 1 3, auto"; } - override drawSpriteAt(ctx: CanvasRenderingContext2D, x: number, y: number) { + override drawSpriteAt(ctx: RenderingContext2D, x: number, y: number) { if (!stendhal.config.getBoolean("effect.blood")) { return; } diff --git a/src/js/stendhal/entity/Chest.ts b/src/js/stendhal/entity/Chest.ts index f6a9b7f63b..59b4438682 100644 --- a/src/js/stendhal/entity/Chest.ts +++ b/src/js/stendhal/entity/Chest.ts @@ -19,23 +19,13 @@ import { FloatingWindow } from "../ui/toolkit/FloatingWindow"; import { ItemInventoryComponent } from "../ui/component/ItemInventoryComponent"; import { Chat } from "../util/Chat"; +import { Paths } from "../data/Paths"; -declare var marauroa: any; -declare var stendhal: any; - - -let OPEN_SPRITE = { - filename: stendhal.paths.sprites + "/chest.png", - height: 32, - width: 32, - offsetY: 32 -}; - -let CLOSED_SPRITE = { - filename: stendhal.paths.sprites + "/chest.png", - height: 32, - width: 32 -}; +import { marauroa } from "marauroa" +import { stendhal } from "../stendhal"; +import { Entity } from "./Entity"; +import { ImageSprite } from "sprite/image/ImageSprite"; +import { images } from "sprite/image/ImageManager"; export class Chest extends PopupInventory { @@ -43,16 +33,22 @@ export class Chest extends PopupInventory { override minimapStyle = Color.CHEST; override zIndex = 5000; - sprite = CLOSED_SPRITE; open = false; + constructor() { + super(); + this.imageSprite = new ImageSprite( + images.load(Paths.sprites + "/chest.png"), + 0, 0, 32, 32); + } + override set(key: string, value: any) { super.set(key, value); if (key === "open") { - this.sprite = OPEN_SPRITE; + this.imageSprite!.offsetY = 32; this.open = true; } - if (this.isNextTo(marauroa.me)) { + if (this.isNextTo(marauroa.me as Entity)) { this.openInventoryWindow(); } } @@ -60,7 +56,7 @@ export class Chest extends PopupInventory { override unset(key: string) { super.unset(key); if (key === "open") { - this.sprite = CLOSED_SPRITE; + this.imageSprite!.offsetY = 0; this.open = false; if (this.inventory && this.inventory.isOpen()) { this.inventory.close(); @@ -151,7 +147,7 @@ export class Chest extends PopupInventory { } override getCursor(_x: number, _y: number) { - return "url(" + stendhal.paths.sprites + "/cursor/bag.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/bag.png) 1 3, auto"; } } diff --git a/src/js/stendhal/entity/Corpse.ts b/src/js/stendhal/entity/Corpse.ts index 16ccd30caa..30d44aa9f0 100644 --- a/src/js/stendhal/entity/Corpse.ts +++ b/src/js/stendhal/entity/Corpse.ts @@ -9,6 +9,8 @@ * * ***************************************************************************/ +import { CorpseSlot } from "./CorpseSlot"; +import { RenderingContext2D } from "util/Types"; import { ActivityIndicatorSprite } from "../sprite/ActivityIndicatorSprite"; import { ItemInventoryComponent } from "../ui/component/ItemInventoryComponent"; @@ -17,9 +19,14 @@ import { FloatingWindow } from "../ui/toolkit/FloatingWindow"; import { Chat } from "../util/Chat"; import { PopupInventory } from "./PopupInventory"; +import { Paths } from "../data/Paths"; +import { singletons } from "../SingletonRepo"; -declare var marauroa: any; -declare var stendhal: any; +import { marauroa, RPObject, RPZone } from "marauroa" +import { stendhal } from "../stendhal"; +import { images } from "sprite/image/ImageManager"; +import { ImageSprite } from "sprite/image/ImageSprite"; +import { ViewPort } from "ui/ViewPort"; export class Corpse extends PopupInventory { @@ -40,61 +47,42 @@ export class Corpse extends PopupInventory { override set(key: string, value: any) { super.set(key, value); - this.sprite = this.sprite || {}; - const bloodEnabled = stendhal.config.getBoolean("effect.blood"); - + let bloodEnabled = stendhal.config.getBoolean("effect.blood"); if (bloodEnabled && (key === "image")) { - this.sprite.filename = stendhal.paths.sprites + "/corpse/" + value + ".png"; + this.imageSprite?.free(); + this.imageSprite = new ImageSprite( + images.load(Paths.sprites + "/corpse/" + value + ".png")); } else if (!bloodEnabled && (key === "harmless_image")) { - this.sprite.filename = stendhal.paths.sprites + "/corpse/" + value + ".png"; + this.imageSprite?.free(); + this.imageSprite = new ImageSprite( + images.load(Paths.sprites + "/corpse/" + value + ".png")); } } - override draw(ctx: CanvasRenderingContext2D) { - if (!this.sprite) { + override draw(ctx: RenderingContext2D) { + let image = this.imageSprite?.imageRef?.image; + if (!image) { return; } super.draw(ctx); if (this.indicator && !this.isEmpty()) { - const tileW = stendhal.ui.gamewindow.targetTileWidth; - const tileH = stendhal.ui.gamewindow.targetTileHeight; - if (this.sprite.width == undefined || this.sprite.height == undefined) { - const image = stendhal.data.sprites.get(this.sprite.filename); - if (image.complete) { - this.sprite.width = image.width < tileW ? tileW : image.width; - this.sprite.height = image.height < tileH ? tileH : image.height; - } - return; - } - - const offsetX = Math.floor((this["width"] * tileW - this.sprite.width) / 2); - const offsetY = Math.floor((this["height"] * tileH - this.sprite.height) / 2); - const dx = this["x"] * tileW + offsetX; - const dy = this["y"] * tileH + offsetY; - - this.indicator.draw(ctx, dx, dy, this.sprite.width); + let viewPort = ViewPort.get(); + const tileW = viewPort.targetTileWidth; + const tileH = viewPort.targetTileHeight; + let width = image.width < tileW ? tileW : image.width; + let height = image.height < tileH ? tileH : image.height; + let offsetX = Math.floor((this["width"] * tileW - width) / 2); + let offsetY = Math.floor((this["height"] * tileH - height) / 2); + let dx = this["x"] * tileW + offsetX; + let dy = this["y"] * tileH + offsetY; + this.indicator.draw(ctx, dx, dy, width); } } override createSlot(name: string) { - var slot = marauroa.util.fromProto(marauroa.rpslotFactory["_default"], { - add: function(object: any) { - marauroa.rpslotFactory["_default"].add.apply(this, arguments); - if (this._objects.length > 0) { - this._parent.autoOpenIfDesired(); - } - }, - - del: function(key: any) { - marauroa.rpslotFactory["_default"].del.apply(this, arguments); - if (this._objects.length == 0) { - this._parent.closeCorpseInventory(); - } - } - }); + let slot = new CorpseSlot(); slot._name = name; - slot._objects = []; slot._parent = this; return slot; } @@ -168,18 +156,18 @@ export class Corpse extends PopupInventory { override getCursor(_x: number, _y: number) { if (this.isEmpty()) { - return "url(" + stendhal.paths.sprites + "/cursor/emptybag.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/emptybag.png) 1 3, auto"; } // owner if (!this["corpse_owner"] || (this["corpse_owner"] == marauroa.me["_name"])) { - return "url(" + stendhal.paths.sprites + "/cursor/bag.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/bag.png) 1 3, auto"; } if ((stendhal.data.group.lootmode === "shared") && (stendhal.data.group.members[this["corpse_owner"]])) { - return "url(" + stendhal.paths.sprites + "/cursor/bag.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/bag.png) 1 3, auto"; } - return "url(" + stendhal.paths.sprites + "/cursor/lockedbag.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/lockedbag.png) 1 3, auto"; } public override isDraggable(): boolean { @@ -189,4 +177,9 @@ export class Corpse extends PopupInventory { private isEmpty() { return !this["content"] || this["content"]._objects.length === 0; } + + override destroy(parent: RPObject|RPZone): void { + this.indicator?.free(); + super.destroy(parent); + } } diff --git a/src/js/stendhal/entity/CorpseSlot.ts b/src/js/stendhal/entity/CorpseSlot.ts new file mode 100644 index 0000000000..23a1f91df8 --- /dev/null +++ b/src/js/stendhal/entity/CorpseSlot.ts @@ -0,0 +1,34 @@ +/*************************************************************************** + * (C) Copyright 2003-2026 - Stendhal * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { Corpse } from "./Corpse"; +import { RPSlot } from "marauroa"; + +export class CorpseSlot extends RPSlot { + + public _name?: string; + public _parent?: Corpse; + + override add(object: any) { + super.add(object); + if (this._objects.length > 0) { + this._parent?.autoOpenIfDesired(); + } + } + + override del(key: any) { + super.del(key); + if (this._objects.length == 0) { + this._parent?.closeCorpseInventory(); + } + } + +} diff --git a/src/js/stendhal/entity/Creature.ts b/src/js/stendhal/entity/Creature.ts index 7b58ebfbe3..9eee4b1661 100644 --- a/src/js/stendhal/entity/Creature.ts +++ b/src/js/stendhal/entity/Creature.ts @@ -16,9 +16,10 @@ import { EntityOverlayRegistry } from "../data/EntityOverlayRegistry"; import { Color } from "../data/color/Color"; import { SkillEffect } from "../sprite/action/SkillEffect"; +import { Paths } from "../data/Paths"; -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { stendhal } from "../stendhal"; export class Creature extends RPEntity { @@ -27,7 +28,7 @@ export class Creature extends RPEntity { override titleStyle = "#ffc8c8"; - override set(key: string, value: string) { + override set(key: string, value: any) { super.set(key, value); if (key === "name") { @@ -52,7 +53,7 @@ export class Creature extends RPEntity { } override getCursor(_x: number, _y: number) { - return "url(" + stendhal.paths.sprites + "/cursor/attack.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/attack.png) 1 3, auto"; } /** diff --git a/src/js/stendhal/entity/DomesticAnimal.ts b/src/js/stendhal/entity/DomesticAnimal.ts index 137bdc52fe..cecb7e3a3e 100644 --- a/src/js/stendhal/entity/DomesticAnimal.ts +++ b/src/js/stendhal/entity/DomesticAnimal.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2024 - Stendhal * + * (C) Copyright 2003-2026 - Stendhal * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,53 +9,62 @@ * * ***************************************************************************/ +import { MenuItem } from "../action/MenuItem"; import { RPEntity } from "./RPEntity"; -import { MenuItem} from "../action/MenuItem"; +import { RenderingContext2D } from "util/Types"; import { Color } from "../data/color/Color"; +import { Paths } from "../data/Paths"; -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa"; +import { images } from "sprite/image/ImageManager"; +import { ImageSprite } from "sprite/image/ImageSprite"; export class DomesticAnimal extends RPEntity { override minimapStyle = Color.DOMESTICANIMAL; - override drawMain(ctx: CanvasRenderingContext2D) { - if (!this.imagePath && this["_rpclass"]) { - this["largeWeight"] = this["largeWeight"] | 20; - if (this["_rpclass"] == "sheep") { - this["largeWeight"] = 60; - } - this.imagePath = stendhal.paths.sprites + "/" + this["_rpclass"] + ".png"; - } - - var localX = this["_x"] * 32; - var localY = this["_y"] * 32; - var image = stendhal.data.sprites.get(this.imagePath); - if (image.height) { - var nFrames = 3; - var nDirections = 4; - var yRow = this["dir"] - 1; - if (this["weight"] >= this["largeWeight"]) { - yRow += 4; - } - this["drawHeight"] = image.height / nDirections / 2; - this["drawWidth"] = image.width / nFrames; - var drawX = ((this["width"] * 32) - this["drawWidth"]) / 2; - var frame = 0; - if (this["speed"] > 0) { - // % Works normally with *floats* (just whose bright idea was - // that?), so use floor() as a workaround - frame = Math.floor(Date.now() / 100) % nFrames; - } - var drawY = (this["height"] * 32) - this["drawHeight"]; - ctx.drawImage(image, frame * this["drawWidth"], yRow * this["drawHeight"], this["drawWidth"], this["drawHeight"], localX + drawX, localY + drawY, this["drawWidth"], this["drawHeight"]); + override init() { + this.imageSprite = new ImageSprite( + images.load(Paths.sprites + "/" + this["_rpclass"] + ".png")); + this["largeWeight"] = this["largeWeight"] || 20; + if (this["_rpclass"] == "sheep") { + this["largeWeight"] = 60; } } + + override drawMain(ctx: RenderingContext2D) { + + let image = this.imageSprite?.imageRef?.image + if (!image) { + return; + } + + let localX = this["_x"] * 32; + let localY = this["_y"] * 32; + + let nFrames = 3; + let nDirections = 4; + let yRow = this["dir"] - 1; + if (this["weight"] >= this["largeWeight"]) { + yRow += 4; + } + this["drawHeight"] = image.height / nDirections / 2; + this["drawWidth"] = image.width / nFrames; + let drawX = ((this["width"] * 32) - this["drawWidth"]) / 2; + let frame = 0; + if (this["speed"] > 0) { + // % Works normally with *floats* (just whose bright idea was + // that?), so use floor() as a workaround + frame = Math.floor(Date.now() / 100) % nFrames; + } + let drawY = (this["height"] * 32) - this["drawHeight"]; + ctx.drawImage(image, frame * this["drawWidth"], yRow * this["drawHeight"], this["drawWidth"], this["drawHeight"], localX + drawX, localY + drawY, this["drawWidth"], this["drawHeight"]); + } + override getCursor(_x: number, _y: number) { - return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto"; } override buildActions(list: MenuItem[]) { diff --git a/src/js/stendhal/entity/Door.ts b/src/js/stendhal/entity/Door.ts index 3258d3aa41..90f98337d9 100644 --- a/src/js/stendhal/entity/Door.ts +++ b/src/js/stendhal/entity/Door.ts @@ -9,32 +9,45 @@ * * ***************************************************************************/ +import { RenderingContext2D } from "util/Types"; import { MenuItem } from "../action/MenuItem"; import { Portal } from "./Portal"; - -declare var marauroa: any; -declare var stendhal: any; +import { Paths } from "../data/Paths"; +import { ImageSprite } from "sprite/image/ImageSprite"; +import { images } from "sprite/image/ImageManager"; export class Door extends Portal { override zIndex = 5000; - override draw(ctx: CanvasRenderingContext2D) { - let imagePath = stendhal.paths.sprites + "/doors/" + this["class"] + ".png"; - let image = stendhal.data.sprites.get(imagePath); - if (image.height) { - let height = image.height / 2; - let x = (this["x"] * 32) - ((image.width - 32) / 2); - let y = (this["y"] * 32) - ((height - 32) / 2); - - var offsetY = height; - if (this["open"] === "") { - offsetY = 0; + override set(key: string, value: any) { + super.set(key, value); + if (key === "class") { + if (this.imageSprite) { + this.imageSprite?.free(); + this.imageSprite = new ImageSprite( + images.load(Paths.sprites + "/doors/" + value + ".png"), + 0, 0, 0, 0); } - ctx.drawImage(image, 0, offsetY, image.width, height, x, y, image.width, height); } } + override draw(ctx: RenderingContext2D) { + let image = this.imageSprite?.imageRef?.image; + if (!image) { + return; + } + let height = image.height / 2; + let x = (this["x"] * 32) - ((image.width - 32) / 2); + let y = (this["y"] * 32) - ((height - 32) / 2); + + let offsetY = height; + if (this["open"] === "") { + offsetY = 0; + } + ctx.drawImage(image, 0, offsetY, image.width, height, x, y, image.width, height); + } + override buildActions(list: MenuItem[]) { list.push({ title: "Look", @@ -51,7 +64,7 @@ export class Door extends Portal { } override getCursor(_x: number, _y: number) { - return "url(" + stendhal.paths.sprites + "/cursor/portal.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/portal.png) 1 3, auto"; } } diff --git a/src/js/stendhal/entity/Entity.ts b/src/js/stendhal/entity/Entity.ts index ee52a43a29..1e32a0b2d8 100644 --- a/src/js/stendhal/entity/Entity.ts +++ b/src/js/stendhal/entity/Entity.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2024 - Stendhal * + * (C) Copyright 2003-2026 - Stendhal * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,12 +9,17 @@ * * ***************************************************************************/ +import { RPObject, RPZone } from "marauroa"; +import { RenderingContext2D } from "util/Types"; import { MenuItem } from "../action/MenuItem"; +import { Paths } from "../data/Paths"; import { Chat } from "../util/Chat"; -import { RPObject } from "./RPObject"; -declare var marauroa: any; -declare var stendhal: any; +import { htmlImageStore } from "data/HTMLImageStore"; +import { marauroa } from "marauroa"; +import { HTMLUtil } from "ui/HTMLUtil"; +import { ViewPort } from "ui/ViewPort"; +import { ImageSprite } from "../sprite/image/ImageSprite"; /** * General entity @@ -25,6 +30,7 @@ export class Entity extends RPObject { minimapShow = false; minimapStyle = "rgb(200,255,200)"; zIndex = 10000; + imageSprite?: ImageSprite; override set(key: string, value: any) { super.set(key, value); @@ -137,10 +143,10 @@ export class Entity extends RPObject { /** * is this entity visible to a specific action * - * @param filter 0: short left click + * @param _filter 0: short left click * @return true, if the entity is visible, false otherwise */ - isVisibleToAction(filter: boolean) { + isVisibleToAction(_filter?: boolean) { return false; } @@ -179,7 +185,7 @@ export class Entity extends RPObject { // action replaces "Look" command (e. g. "look closely" on signs) if (this["action"]) { list.push({ - title: stendhal.ui.html.niceName(this["action"]), + title: HTMLUtil.niceName(this["action"]), type: this.actionAliasToAction(this["action"]) }); } else { @@ -204,8 +210,8 @@ export class Entity extends RPObject { this["_x"] = this["x"]; } - draw(ctx: CanvasRenderingContext2D) { - if (this.sprite) { + draw(ctx: RenderingContext2D) { + if (this.sprite || this.imageSprite) { this.drawSprite(ctx); } } @@ -213,23 +219,27 @@ export class Entity extends RPObject { /** * draws a standard sprite */ - drawSprite(ctx: CanvasRenderingContext2D) { + drawSprite(ctx: RenderingContext2D) { this.drawSpriteAt(ctx, this["x"] * 32, this["y"] * 32); } - drawSpriteAt(ctx: CanvasRenderingContext2D, x: number, y: number) { - var image = stendhal.data.sprites.get(this.sprite.filename); - if (image.height) { - var offsetX = this.sprite.offsetX || 0; - var offsetY = this.sprite.offsetY || 0; - var width = this.sprite.width || image.width; - var height = this.sprite.height || image.height; + drawSpriteAt(ctx: RenderingContext2D, x: number, y: number) { + if (this.imageSprite) { + this.imageSprite.drawOnto(ctx, x, y, this.getWidth() * 32, this.getHeight() * 32); + } else { + let image = htmlImageStore.get(this.sprite.filename); + if (image.height) { + let offsetX = this.sprite.offsetX || 0; + let offsetY = this.sprite.offsetY || 0; + let width = this.sprite.width || image.width; + let height = this.sprite.height || image.height; - // use entity dimensions to center sprite - x += Math.floor((this.getWidth() * 32 - width) / 2); - y += Math.floor((this.getHeight() * 32 - height) / 2); - - ctx.drawImage(image, offsetX, offsetY, width, height, x, y, width, height); + // use entity dimensions to center sprite + x += Math.floor((this.getWidth() * 32 - width) / 2); + y += Math.floor((this.getHeight() * 32 - height) / 2); + + ctx.drawImage(image, offsetX, offsetY, width, height, x, y, width, height); + } } } @@ -274,7 +284,7 @@ export class Entity extends RPObject { cursor = this["cursor"]; } - return "url(" + stendhal.paths.sprites + "/cursor/" + cursor.toLowerCase().replace("_", "") + ".png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/" + cursor.toLowerCase().replace("_", "") + ".png) 1 3, auto"; } /** @@ -320,16 +330,17 @@ export class Entity extends RPObject { * Checks if within vieport area. */ public inView(): boolean { + let viewPort = ViewPort.get(); const view_rect: any = {}; - view_rect.left = stendhal.ui.gamewindow.offsetX; - view_rect.right = view_rect.left + stendhal.ui.gamewindow.width; - view_rect.top = stendhal.ui.gamewindow.offsetY; - view_rect.bottom = view_rect.top + stendhal.ui.gamewindow.height; + view_rect.left = viewPort.offsetX; + view_rect.right = view_rect.left + viewPort.width; + view_rect.top = viewPort.offsetY; + view_rect.bottom = view_rect.top + viewPort.height; - const pixelX = this["x"] * stendhal.ui.gamewindow.targetTileWidth - + Math.floor(stendhal.ui.gamewindow.targetTileWidth / 2); - const pixelY = this["y"] * stendhal.ui.gamewindow.targetTileHeight - + Math.floor(stendhal.ui.gamewindow.targetTileHeight / 2); + const pixelX = this["x"] * viewPort.targetTileWidth + + Math.floor(viewPort.targetTileWidth / 2); + const pixelY = this["y"] * viewPort.targetTileHeight + + Math.floor(viewPort.targetTileHeight / 2); const ent_rect: any = {}; // horizontal orientation is centered @@ -342,4 +353,8 @@ export class Entity extends RPObject { return ent_rect.right > view_rect.left && ent_rect.left < view_rect.right && ent_rect.bottom > view_rect.top && ent_rect.top < view_rect.bottom } + + override destroy(_parent: RPObject|RPZone) { + this.imageSprite?.free(); + } } diff --git a/src/js/stendhal/entity/EntityRegistry.ts b/src/js/stendhal/entity/EntityRegistry.ts index 535792b188..aef519c5bd 100644 --- a/src/js/stendhal/entity/EntityRegistry.ts +++ b/src/js/stendhal/entity/EntityRegistry.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2023 - Stendhal * + * (C) Copyright 2003-2026 - Stendhal * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,7 +9,7 @@ * * ***************************************************************************/ -declare var marauroa: any; +import { marauroa } from "marauroa" import { Blood } from "./Blood"; import { Chest} from "./Chest"; @@ -35,41 +35,46 @@ import { User } from "./User"; import { VisibleEntity } from "./VisibleEntity"; import { WalkBlocker } from "./WalkBlocker"; -marauroa.rpobjectFactory["area"] = InvisibleEntity; -marauroa.rpobjectFactory["baby_dragon"] = DomesticAnimal; -marauroa.rpobjectFactory["blackboard"] = Sign; -marauroa.rpobjectFactory["blocktarget"] = InvisibleEntity; -marauroa.rpobjectFactory["block"] = VisibleEntity; -marauroa.rpobjectFactory["blood"] = Blood; -marauroa.rpobjectFactory["cat"] = DomesticAnimal; -marauroa.rpobjectFactory["chest"] = Chest; -marauroa.rpobjectFactory["corpse"] = Corpse; -marauroa.rpobjectFactory["creature"] = Creature; -marauroa.rpobjectFactory["_default"] = UnknownEntity; -marauroa.rpobjectFactory["domesticanimal"] = DomesticAnimal; -marauroa.rpobjectFactory["door"] = Door; -marauroa.rpobjectFactory["flyover"] = InvisibleEntity; -marauroa.rpobjectFactory["food"] = Food; -marauroa.rpobjectFactory["game_board"] = GameBoard; -marauroa.rpobjectFactory["gate"] = Gate -marauroa.rpobjectFactory["growing_entity_spawner"] = GrowingEntitySpawner; -marauroa.rpobjectFactory["house_portal"] = Portal; -marauroa.rpobjectFactory["invisible_entity"] = InvisibleEntity; -marauroa.rpobjectFactory["item"] = Item; -marauroa.rpobjectFactory["looped_sound_source"] = LoopedSoundSource; -marauroa.rpobjectFactory["npc"] = NPC; -marauroa.rpobjectFactory["plant_grower"] = VisibleEntity; -marauroa.rpobjectFactory["player"] = Player; -marauroa.rpobjectFactory["portal"] = Portal; -marauroa.rpobjectFactory["rented_sign"] = Sign; -marauroa.rpobjectFactory["sheep"] = DomesticAnimal; -marauroa.rpobjectFactory["sign"] = Sign; -marauroa.rpobjectFactory["tiled_entity"] = InvisibleEntity; -marauroa.rpobjectFactory["training_dummy"] = TrainingDummy; -marauroa.rpobjectFactory["unknown"] = UnknownEntity; -marauroa.rpobjectFactory["useable_entity"] = UseableEntity; -marauroa.rpobjectFactory["user"] = User; -marauroa.rpobjectFactory["visible_entity"] = VisibleEntity; -marauroa.rpobjectFactory["walkblocker"] = WalkBlocker; -marauroa.rpobjectFactory["wall"] = InvisibleEntity; -marauroa.rpobjectFactory["weather_entity"] = InvisibleEntity; +export class EntityRegistry { + + public init() { + marauroa.rpobjectFactory.register("area", InvisibleEntity); + marauroa.rpobjectFactory.register("baby_dragon", DomesticAnimal); + marauroa.rpobjectFactory.register("blackboard", Sign); + marauroa.rpobjectFactory.register("blocktarget", InvisibleEntity); + marauroa.rpobjectFactory.register("block", VisibleEntity); + marauroa.rpobjectFactory.register("blood", Blood); + marauroa.rpobjectFactory.register("cat", DomesticAnimal); + marauroa.rpobjectFactory.register("chest", Chest); + marauroa.rpobjectFactory.register("corpse", Corpse); + marauroa.rpobjectFactory.register("creature", Creature); + marauroa.rpobjectFactory.register("_default", UnknownEntity); + marauroa.rpobjectFactory.register("domesticanimal", DomesticAnimal); + marauroa.rpobjectFactory.register("door", Door); + marauroa.rpobjectFactory.register("flyover", InvisibleEntity); + marauroa.rpobjectFactory.register("food", Food); + marauroa.rpobjectFactory.register("game_board", GameBoard); + marauroa.rpobjectFactory.register("gate", Gate); + marauroa.rpobjectFactory.register("growing_entity_spawner", GrowingEntitySpawner); + marauroa.rpobjectFactory.register("house_portal", Portal); + marauroa.rpobjectFactory.register("invisible_entity", InvisibleEntity); + marauroa.rpobjectFactory.register("item", Item); + marauroa.rpobjectFactory.register("looped_sound_source", LoopedSoundSource); + marauroa.rpobjectFactory.register("npc", NPC); + marauroa.rpobjectFactory.register("plant_grower", VisibleEntity); + marauroa.rpobjectFactory.register("player", Player); + marauroa.rpobjectFactory.register("portal", Portal); + marauroa.rpobjectFactory.register("rented_sign", Sign); + marauroa.rpobjectFactory.register("sheep", DomesticAnimal); + marauroa.rpobjectFactory.register("sign", Sign); + marauroa.rpobjectFactory.register("tiled_entity", InvisibleEntity); + marauroa.rpobjectFactory.register("training_dummy", TrainingDummy); + marauroa.rpobjectFactory.register("unknown", UnknownEntity); + marauroa.rpobjectFactory.register("useable_entity", UseableEntity); + marauroa.rpobjectFactory.register("user", User); + marauroa.rpobjectFactory.register("visible_entity", VisibleEntity); + marauroa.rpobjectFactory.register("walkblocker", WalkBlocker); + marauroa.rpobjectFactory.register("wall", InvisibleEntity); + marauroa.rpobjectFactory.register("weather_entity", InvisibleEntity); + } +} diff --git a/src/js/stendhal/entity/Food.ts b/src/js/stendhal/entity/Food.ts index 31effe663c..0936841396 100644 --- a/src/js/stendhal/entity/Food.ts +++ b/src/js/stendhal/entity/Food.ts @@ -10,33 +10,33 @@ ***************************************************************************/ import { Entity } from "./Entity"; +import { Paths } from "../data/Paths"; -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { ImageSprite } from "sprite/image/ImageSprite"; +import { images } from "sprite/image/ImageManager"; export class Food extends Entity { override zIndex = 5000; + override init() { + this.imageSprite = new ImageSprite( + images.load(Paths.sprites + "/food.png"), + 0, this["amount"] * 32, 32, 32); + } + override set(key: string, value: any) { super.set(key, value); if (key === "amount") { - this._amount = parseInt(value, 10); + if (this.imageSprite) { + this.imageSprite.offsetY = this["amount"] * 32; + } } // TODO: play sound effect } - override draw(ctx: CanvasRenderingContext2D) { - var image = stendhal.data.sprites.get(stendhal.paths.sprites + "/food.png"); - if (image.height) { - var localX = this["x"] * 32; - var localY = this["y"] * 32; - var offset = this._amount * 32; - ctx.drawImage(image, 0, offset, 32, 32, localX, localY, 32, 32); - } - } - override onclick(_x: number, _y: number) { var action = { "type": "look", @@ -50,7 +50,7 @@ export class Food extends Entity { } override getCursor(_x: number, _y: number) { - return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto"; } } diff --git a/src/js/stendhal/entity/GameBoard.ts b/src/js/stendhal/entity/GameBoard.ts index 412e11fbbb..487678f1e3 100644 --- a/src/js/stendhal/entity/GameBoard.ts +++ b/src/js/stendhal/entity/GameBoard.ts @@ -9,28 +9,24 @@ * * ***************************************************************************/ +import { Paths } from "../data/Paths"; import { Entity } from "./Entity"; -declare var stendhal: any; +import { stendhal } from "../stendhal"; +import { ImageSprite } from "sprite/image/ImageSprite"; +import { images } from "sprite/image/ImageManager"; export class GameBoard extends Entity { override minimapShow = false; override zIndex = 100; - constructor() { - super(); - this.sprite = { - height: 32 * 3, - width: 32 * 3 - }; - } - override set(key: string, value: any) { super.set(key, value); if (key === "class") { - this.sprite.filename = stendhal.paths.sprites + "/gameboard/" - + this["class"] + ".png"; + let filename = Paths.sprites + "/gameboard/" + this["class"] + ".png"; + this.imageSprite?.free(); + this.imageSprite = new ImageSprite(images.load(filename), 0, 0, 3*32, 3*32); } } @@ -39,7 +35,7 @@ export class GameBoard extends Entity { } override getCursor(_x: number, _y: number) { - return "url(" + stendhal.paths.sprites + "/cursor/walk.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/walk.png) 1 3, auto"; } } diff --git a/src/js/stendhal/entity/Gate.ts b/src/js/stendhal/entity/Gate.ts index 881e9659e5..e151c10c6a 100644 --- a/src/js/stendhal/entity/Gate.ts +++ b/src/js/stendhal/entity/Gate.ts @@ -9,30 +9,45 @@ * * ***************************************************************************/ +import { RenderingContext2D } from "util/Types"; import { MenuItem } from "../action/MenuItem"; import { Entity } from "./Entity"; +import { Paths } from "../data/Paths"; -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { images } from "sprite/image/ImageManager"; +import { ImageSprite } from "sprite/image/ImageSprite"; export class Gate extends Entity { override zIndex = 5000; + private locked = false; + + override init() { + this.imageSprite?.free(); + this.imageSprite = new ImageSprite( + images.load(Paths.sprites + "/doors/" + this["image"] + "_" + this["orientation"] + ".png"), + 0, 0, 0, 0); + } override set(key: string, value: any) { super.set(key, value); if (key === "resistance") { - this["locked"] = parseInt(value, 10) !== 0; + this.locked = parseInt(value, 10) !== 0; } else if (key === "image" || key === "orientation") { - // Force re-evaluation of the sprite - delete this["_image"]; + if (this.imageSprite) { + this.imageSprite?.free(); + this.imageSprite = new ImageSprite( + images.load(Paths.sprites + "/doors/" + this["image"] + "_" + this["orientation"] + ".png"), + 0, 0, 0, 0); + } } } override buildActions(list: MenuItem[]) { var id = this["id"]; list.push({ - title: (this["locked"]) ? "Open" : "Close", + title: (this.locked) ? "Open" : "Close", action: function(_entity: Entity) { var action = { "type": "use", @@ -44,20 +59,18 @@ export class Gate extends Entity { }); } - override draw(ctx: CanvasRenderingContext2D) { - if (this._image == undefined) { - var filename = stendhal.paths.sprites + "/doors/" + this["image"] + "_" + this["orientation"] + ".png"; - this._image = stendhal.data.sprites.get(filename); - } - if (this._image.height) { - var xOffset = -32 * Math.floor(this._image.width / 32 / 2); - var height = this._image.height / 2; - var yOffset = -32 * Math.floor(height / 32 / 2); - var localX = this["_x"] * 32 + xOffset; - var localY = this["_y"] * 32 + yOffset; - var yStart = (this["locked"]) ? height : 0; - ctx.drawImage(this._image, 0, yStart, this._image.width, height, localX, localY, this._image.width, height); + override draw(ctx: RenderingContext2D) { + let image = this.imageSprite?.imageRef?.image; + if (!image) { + return; } + let xOffset = -32 * Math.floor(image.width / 32 / 2); + let height = image.height / 2; + let yOffset = -32 * Math.floor(height / 32 / 2); + let localX = this["_x"] * 32 + xOffset; + let localY = this["_y"] * 32 + yOffset; + let yStart = (this.locked) ? height : 0; + ctx.drawImage(image, 0, yStart, image.width, height, localX, localY, image.width, height); } override isVisibleToAction(_filter: boolean) { @@ -74,7 +87,7 @@ export class Gate extends Entity { } override getCursor(_x: number, _y: number) { - return "url(" + stendhal.paths.sprites + "/cursor/activity.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/activity.png) 1 3, auto"; } } diff --git a/src/js/stendhal/entity/Ground.ts b/src/js/stendhal/entity/Ground.ts index 68c69f360e..14159c5455 100644 --- a/src/js/stendhal/entity/Ground.ts +++ b/src/js/stendhal/entity/Ground.ts @@ -9,8 +9,12 @@ * * ***************************************************************************/ -declare var marauroa: any; -declare var stendhal: any; +import { Paths } from "../data/Paths"; + +import { marauroa } from "marauroa" +import { stendhal } from "../stendhal"; +import { TileMap } from "data/TileMap"; +import { ViewPort } from "ui/ViewPort"; /** @@ -31,39 +35,42 @@ export class Ground { * @return Direction of the zone to change to, null if no zone change should happen */ private calculateZoneChangeDirection(x: number, y: number): string|null { + let map = TileMap.get(); if (x < 15) { return "4"; // LEFT } - if (x > stendhal.data.map.zoneSizeX * 32 - 15) { + if (x > map.zoneSizeX * 32 - 15) { return "2"; // RIGHT } if (y < 15) { return "1"; // UP } - if (y > stendhal.data.map.zoneSizeY * 32 - 15) { + if (y > map.zoneSizeY * 32 - 15) { return "3"; // DOWN } return null; } getCursor(x: number, y: number): string { - if ((x < 15) || (y < 15) || (x > stendhal.data.map.zoneSizeX * 32 - 15) || (y > stendhal.data.map.zoneSizeY * 32 - 15)) { - return "url(" + stendhal.paths.sprites + "/cursor/walkborder.png) 1 3, auto" + let map = TileMap.get(); + if ((x < 15) || (y < 15) || (x > map.zoneSizeX * 32 - 15) || (y > map.zoneSizeY * 32 - 15)) { + return "url(" + Paths.sprites + "/cursor/walkborder.png) 1 3, auto" } var worldX = Math.floor(x / 32); var worldY = Math.floor(y / 32); - if (stendhal.data.map.collision(worldX, worldY)) { - return "url(" + stendhal.paths.sprites + "/cursor/stop.png) 1 3, auto"; + if (map.collision(worldX, worldY)) { + return "url(" + Paths.sprites + "/cursor/stop.png) 1 3, auto"; } - return "url(" + stendhal.paths.sprites + "/cursor/walk.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/walk.png) 1 3, auto"; } onclick(x: number, y: number, dblclick: boolean) { if (!stendhal.config.getBoolean("pathfinding")) { return; } - var gameX = x + stendhal.ui.gamewindow.offsetX; - var gameY = y + stendhal.ui.gamewindow.offsetY; + let viewPort = ViewPort.get(); + var gameX = x + viewPort.offsetX; + var gameY = y + viewPort.offsetY; var action = { "type": "moveto", "x": "" + Math.floor(gameX / 32), diff --git a/src/js/stendhal/entity/GrowingEntitySpawner.ts b/src/js/stendhal/entity/GrowingEntitySpawner.ts index 3aa64e390a..4f5acf2ce4 100644 --- a/src/js/stendhal/entity/GrowingEntitySpawner.ts +++ b/src/js/stendhal/entity/GrowingEntitySpawner.ts @@ -9,16 +9,33 @@ * * ***************************************************************************/ +import { RenderingContext2D } from "util/Types"; import { MenuItem } from "../action/MenuItem"; import { Entity } from "./Entity"; +import { Paths } from "../data/Paths"; +import { singletons } from "../SingletonRepo"; -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { ImageSprite } from "sprite/image/ImageSprite"; +import { images } from "sprite/image/ImageManager"; export class GrowingEntitySpawner extends Entity { override zIndex = 3000; + + override set(key: string, value: any) { + super.set(key, value); + if (key === "class") { + this.imageSprite?.free(); + let className = value.replace(" ", "_"); + this.imageSprite = new ImageSprite( + images.load(Paths.sprites + "/" + className + ".png"), + 0, 0, 0, 0); + } + } + + /** * is this entity visible to a specific action * @@ -48,34 +65,23 @@ export class GrowingEntitySpawner extends Entity { marauroa.clientFramework.sendAction(action); } - /** - * draw RPEntities - */ - override draw(ctx: CanvasRenderingContext2D) { - var localX = this["x"] * 32; - var localY = this["y"] * 32; - - // FIXME: - // temporary fix, problem lies higher up - // appears to only affect button_mushroom_grower - // could be issue in marauroa - let class_name = this["class"]; - if (class_name.includes(" ")) { - class_name = class_name.replace(" ", "_"); + override draw(ctx: RenderingContext2D) { + let localX = this["x"] * 32; + let localY = this["y"] * 32; + let image = this.imageSprite?.imageRef?.image; + if (!image) { + return; } - var image = stendhal.data.sprites.get(stendhal.paths.sprites + "/" + class_name + ".png"); - if (image.height) { // image.complete is true on missing image files - var count = parseInt(this["max_ripeness"], 10) + 1; - var drawHeight = image.height / count; - var yRow = this["ripeness"]; - ctx.drawImage(image, 0, yRow * drawHeight, image.width, drawHeight, + let count = parseInt(this["max_ripeness"], 10) + 1; + let drawHeight = image.height / count; + let yRow = this["ripeness"]; + ctx.drawImage(image, 0, yRow * drawHeight, image.width, drawHeight, localX, localY - drawHeight + 32, image.width, drawHeight); - } } override getCursor(_x: number, _y: number) { - return "url(" + stendhal.paths.sprites + "/cursor/harvest.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/harvest.png) 1 3, auto"; } } diff --git a/src/js/stendhal/entity/InvisibleEntity.ts b/src/js/stendhal/entity/InvisibleEntity.ts index 36afdfe64c..9b5b27d361 100644 --- a/src/js/stendhal/entity/InvisibleEntity.ts +++ b/src/js/stendhal/entity/InvisibleEntity.ts @@ -9,10 +9,9 @@ * * ***************************************************************************/ +import { Paths } from "../data/Paths"; import { Entity } from "./Entity"; -declare var stendhal: any; - export class InvisibleEntity extends Entity { override isVisibleToAction(_filter: boolean) { @@ -20,7 +19,7 @@ export class InvisibleEntity extends Entity { } override getCursor(_x: number, _y: number) { - return "url(" + stendhal.paths.sprites + "/cursor/walk.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/walk.png) 1 3, auto"; } } diff --git a/src/js/stendhal/entity/Item.ts b/src/js/stendhal/entity/Item.ts index 791bc533db..87116c2bea 100644 --- a/src/js/stendhal/entity/Item.ts +++ b/src/js/stendhal/entity/Item.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2023 - Stendhal * + * (C) Copyright 2003-2026 - Stendhal * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,14 +9,16 @@ * * ***************************************************************************/ -import { ItemMap } from "./ItemMap"; +import { RenderingContext2D } from "util/Types"; import { MenuItem } from "../action/MenuItem"; -import { Entity } from "./Entity"; +import { Paths } from "../data/Paths"; import { TextSprite } from "../sprite/TextSprite"; +import { Entity } from "./Entity"; +import { ItemMap } from "./ItemMap"; -declare var marauroa: any; -declare var stendhal: any; - +import { marauroa } from "marauroa"; +import { images } from "sprite/image/ImageManager"; +import { ImageSprite } from "sprite/image/ImageSprite"; export class Item extends Entity { @@ -28,15 +30,10 @@ export class Item extends Entity { // animation private frameTimeStamp = 0; private animated: boolean|null = null; - private xFrames: number|null = null; - private yFrames: number|null = null; + constructor() { super(); - this.sprite = { - height: 32, - width: 32 - }; this.quantityTextSprite = new TextSprite("", "white", "10px sans-serif"); } @@ -72,36 +69,50 @@ export class Item extends Entity { override set(key: string, value: any) { super.set(key, value); if (key === "class" || key === "subclass") { - this.sprite.filename = stendhal.paths.sprites + "/items/" - + this["class"] + "/" + this["subclass"] + ".png"; - } - if (key === "quantity") { + if (this["class"] && this["subclass"]) { + this.imageSprite?.free(); + this.imageSprite = new ImageSprite( + images.load(Paths.sprites + "/items/" + this["class"] + "/" + this["subclass"] + ".png"), + 0, (this["state"] || 0) * 32, 32, 32); + } + } else if (key === "state") { + if (this.imageSprite) { + this.imageSprite.offsetY = (this["state"] || 0) * 32 + } + } else if (key === "quantity") { this.quantityTextSprite = new TextSprite(this.formatQuantity(), "white", "10px sans-serif"); } } - override draw(ctx: CanvasRenderingContext2D) { - this.sprite.offsetY = (this["state"] || 0) * 32 + override draw(ctx: RenderingContext2D) { this.stepAnimation(); - this.drawAt(ctx, this["x"] * 32, this["y"] * 32); } - drawAt(ctx: CanvasRenderingContext2D, x: number, y: number) { - if (this.sprite) { - this.drawSpriteAt(ctx, x, y); - let textMetrics = this.quantityTextSprite.getTextMetrics(ctx); - this.quantityTextSprite.draw(ctx, x + (32 - textMetrics.width), y + 6); + drawAt(ctx: RenderingContext2D, x: number, y: number) { + let image = this.imageSprite?.imageRef?.image; + if (!image) { + return; } + this.drawSpriteAt(ctx, x, y); + let textMetrics = this.quantityTextSprite.getTextMetrics(ctx); + if (!textMetrics) { + throw new Error("textMetrics is undefined"); + } + this.quantityTextSprite.draw(ctx, x + (32 - textMetrics.width), y + 6); } public stepAnimation() { + let image = this.imageSprite?.imageRef?.image; + if (!image) { + return; + } const currentTimeStamp = +new Date(); - if (this.frameTimeStamp == 0) { - this.frameTimeStamp = currentTimeStamp; - this.sprite.offsetX = 0; - this.sprite.offsetY = 0; - } else if (currentTimeStamp - this.frameTimeStamp >= 100) { +// if (this.frameTimeStamp == 0) { +// this.frameTimeStamp = currentTimeStamp; +// this.imageSprite?.offsetX = 0; +// } + if (currentTimeStamp - this.frameTimeStamp >= 100) { // FIXME: need proper FPS limit this.setXFrameIndex(this.getXFrameIndex() + 1); this.frameTimeStamp = currentTimeStamp; @@ -128,7 +139,7 @@ export class Item extends Entity { } else { cursor = ItemMap.getCursor(this["class"], this["name"]); } - return "url(" + stendhal.paths.sprites + "/cursor/" + cursor + ".png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/" + cursor + ".png) 1 3, auto"; } public getToolTip(): string { @@ -142,51 +153,31 @@ export class Item extends Entity { } public isAnimated(): boolean { - if (!stendhal.data.sprites.get(this.sprite.filename).height) { - return false; + if (this.animated === null) { + let image = this.imageSprite?.imageRef?.image + if (image) { + this.animated = (image.width / 32) > 1; + } } - if (this.animated == null) { - // store animation state - this.animated = (stendhal.data.sprites.get(this.sprite.filename).width / 32) > 1; - } - - return this.animated; + return this.animated || false; } private setXFrameIndex(idx: number) { - if (this.xFrames == null) { - const img = stendhal.data.sprites.get(this.sprite.filename); - this.xFrames = img.width / 32; + let image = this.imageSprite?.imageRef.image + if (!image) { + return; } - - if (idx >= this.xFrames) { + let frames = image.width / 32; + if (idx >= frames) { // restart idx = 0; } - this.sprite.offsetX = idx * 32; - } - - private setYFrameIndex(idx: number) { - if (this.yFrames == null) { - const img = stendhal.data.sprites.get(this.sprite.filename); - this.yFrames = img.height / 32; - } - - if (idx >= this.yFrames) { - // restart - idx = 0; - } - - this.sprite.offsetY = idx * 32; + this.imageSprite!.offsetX = idx * 32; } public getXFrameIndex(): number { - return (this.sprite.offsetX || 0) / 32; - } - - public getYFrameIndex(): number { - return (this.sprite.offsetY || 0) / 32; + return (this.imageSprite?.offsetX || 0) / 32; } public override isDraggable(): boolean { diff --git a/src/js/stendhal/entity/ItemMap.ts b/src/js/stendhal/entity/ItemMap.ts index 23f49b9988..b352055586 100644 --- a/src/js/stendhal/entity/ItemMap.ts +++ b/src/js/stendhal/entity/ItemMap.ts @@ -15,7 +15,7 @@ import { MenuItem } from "../action/MenuItem"; const config = singletons.getConfigManager(); -declare var marauroa: any; +import { marauroa } from "marauroa" const defaultUse = { title: "Use", diff --git a/src/js/stendhal/entity/NPC.ts b/src/js/stendhal/entity/NPC.ts index ee9d8ed438..d4c1b42cb8 100644 --- a/src/js/stendhal/entity/NPC.ts +++ b/src/js/stendhal/entity/NPC.ts @@ -14,8 +14,8 @@ import { RPEntity } from "./RPEntity"; import { EntityOverlayRegistry } from "../data/EntityOverlayRegistry"; import { Color } from "../data/color/Color"; - -declare var stendhal: any; +import { RenderingContext2D } from "util/Types"; +import { Paths } from "../data/Paths"; export class NPC extends RPEntity { override minimapStyle = Color.NPC; @@ -28,7 +28,7 @@ export class NPC extends RPEntity { this["base_hp"] = 100; } - override set(key: string, value: string) { + override set(key: string, value: any) { super.set(key, value); if (key === "name") { @@ -42,7 +42,7 @@ export class NPC extends RPEntity { } } - override drawTop(ctx: CanvasRenderingContext2D) { + override drawTop(ctx: RenderingContext2D) { var localX = this["_x"] * 32; var localY = this["_y"] * 32; if (typeof(this["no_hpbar"]) == "undefined") { @@ -54,7 +54,7 @@ export class NPC extends RPEntity { } override getCursor(_x: number, _y: number) { - return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto"; } } diff --git a/src/js/stendhal/entity/Player.ts b/src/js/stendhal/entity/Player.ts index 2a07d86520..80df9f55f9 100644 --- a/src/js/stendhal/entity/Player.ts +++ b/src/js/stendhal/entity/Player.ts @@ -9,8 +9,8 @@ * * ***************************************************************************/ -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { stendhal } from "../stendhal"; import { RPEntity } from "./RPEntity"; @@ -22,6 +22,8 @@ import { UIComponentEnum } from "../ui/UIComponentEnum"; import { GroupPanelComponent } from "../ui/component/GroupPanelComponent"; import { Color } from "../data/color/Color"; +import { RenderingContext2D } from "util/Types"; +import { Paths } from "../data/Paths"; export class Player extends RPEntity { @@ -169,7 +171,7 @@ export class Player extends RPEntity { } } - override draw(ctx: CanvasRenderingContext2D) { + override draw(ctx: RenderingContext2D) { if (this.isIgnored()) { return; } @@ -236,8 +238,8 @@ export class Player extends RPEntity { override getCursor(_x: number, _y: number) { if (this.isVisibleToAction(false)) { - return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto"; } - return "url(" + stendhal.paths.sprites + "/cursor/walk.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/walk.png) 1 3, auto"; } } diff --git a/src/js/stendhal/entity/PopupInventory.ts b/src/js/stendhal/entity/PopupInventory.ts index fb995da53e..5af5364106 100644 --- a/src/js/stendhal/entity/PopupInventory.ts +++ b/src/js/stendhal/entity/PopupInventory.ts @@ -9,10 +9,10 @@ * * ***************************************************************************/ -import { Entity } from "./Entity"; -import { RPObject } from "./RPObject"; +import { marauroa, RPObject, RPZone } from "marauroa"; -declare var marauroa: any; +import { RenderingContext2D } from "util/Types"; +import { Entity } from "./Entity"; export class PopupInventory extends Entity { @@ -20,7 +20,7 @@ export class PopupInventory extends Entity { protected maxDistToView = 4; - override draw(ctx: CanvasRenderingContext2D) { + override draw(ctx: RenderingContext2D) { super.draw(ctx); if (this.inventory && this.inventory.isOpen() && !this.canViewContents()) { // player has moved too far away @@ -32,9 +32,9 @@ export class PopupInventory extends Entity { // inheriting classes can override } - override destroy(_parent: RPObject) { + override destroy(parent: RPObject|RPZone) { this.closeInventoryWindow(); - super.destroy(_parent); + super.destroy(parent); } /** diff --git a/src/js/stendhal/entity/Portal.ts b/src/js/stendhal/entity/Portal.ts index 27a62d3ea7..de6e562436 100644 --- a/src/js/stendhal/entity/Portal.ts +++ b/src/js/stendhal/entity/Portal.ts @@ -13,9 +13,10 @@ import { Entity } from "./Entity"; import { MenuItem } from "../action/MenuItem"; import { Color } from "../data/color/Color"; +import { Paths } from "../data/Paths"; -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { TileMap } from "data/TileMap"; export class Portal extends Entity { @@ -59,7 +60,8 @@ export class Portal extends Entity { */ override getDefaultAction() { // don't try to walk to if on collision tile - if (stendhal.data.map.collision(this["x"], this["y"])) { + let map = TileMap.get(); + if (map.collision(this["x"], this["y"])) { return { "type": "use", "target": "#" + this["id"], @@ -75,7 +77,7 @@ export class Portal extends Entity { } override getCursor(_x: number, _y: number) { - return "url(" + stendhal.paths.sprites + "/cursor/portal.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/portal.png) 1 3, auto"; } } diff --git a/src/js/stendhal/entity/RPEntity.ts b/src/js/stendhal/entity/RPEntity.ts index e49c6789a7..7ccc19d564 100644 --- a/src/js/stendhal/entity/RPEntity.ts +++ b/src/js/stendhal/entity/RPEntity.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2024 - Stendhal * + * (C) Copyright 2003-2026 - Stendhal * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,30 +9,35 @@ * * ***************************************************************************/ -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa"; +import { stendhal } from "../stendhal"; -import { ActiveEntity } from "./ActiveEntity"; -import { Entity } from "./Entity"; import { singletons } from "../SingletonRepo"; import { MenuItem } from "../action/MenuItem"; +import { ActiveEntity } from "./ActiveEntity"; +import { Entity } from "./Entity"; import { Color } from "../data/color/Color"; import { Chat } from "../util/Chat"; import { Nature } from "../util/Nature"; -import { Floater } from "../sprite/Floater"; import { EmojiSprite } from "../sprite/EmojiSprite"; +import { Floater } from "../sprite/Floater"; import { OverlaySpriteImpl } from "../sprite/OverlaySpriteImpl"; import { SpeechBubble } from "../sprite/SpeechBubble"; import { TextSprite } from "../sprite/TextSprite"; import { BarehandAttackSprite } from "../sprite/action/BarehandAttackSprite"; -//import { MeleeAttackSprite } from "../sprite/action/MeleeAttackSprite"; import { RangedAttackSprite } from "../sprite/action/RangedAttackSprite"; +import { htmlImageStore } from "data/HTMLImageStore"; import { ImageWithDimensions } from "data/ImageWithDimensions"; +import { OutfitStore } from "data/OutfitStore"; +import { ViewPort } from "ui/ViewPort"; +import { RenderingContext2D } from "util/Types"; +import { Paths } from "../data/Paths"; +import { statusIconDrawer } from "sprite/indicator/StatusIconDrawer"; var HEALTH_BAR_HEIGHT = 6; @@ -54,7 +59,7 @@ export class RPEntity extends ActiveEntity { // for adjusting entity hp bar & title vertical position protected titleDrawYOffset: number = 0; // canvas for merging outfit layers to be drawn - private octx?: CanvasRenderingContext2D; + private octx?: RenderingContext2D; private attackers: {[key: string]: any} = { size: 0 }; @@ -204,11 +209,11 @@ export class RPEntity extends ActiveEntity { * Text to display. */ addSpeechBubble(text: string) { - stendhal.ui.gamewindow.addTextSprite(new SpeechBubble(text, this)); + ViewPort.get().addTextSprite(new SpeechBubble(text, this)); } addEmoji(emoji: HTMLImageElement) { - stendhal.ui.gamewindow.addEmojiSprite(new EmojiSprite(emoji, this)); + ViewPort.get().addEmojiSprite(new EmojiSprite(emoji, this)); } /** @@ -218,8 +223,8 @@ export class RPEntity extends ActiveEntity { return typeof(this["no_shadow"]) === "undefined"; } - drawMultipartOutfit(ctx: CanvasRenderingContext2D) { - const store = singletons.getOutfitStore(); + drawMultipartOutfit(ctx: RenderingContext2D) { + const store = OutfitStore.get(); // layers in draw order var layers = store.getLayerNames(); @@ -244,7 +249,7 @@ export class RPEntity extends ActiveEntity { if (stendhal.config.getBoolean("effect.shadows") && this.castsShadow()) { // dressed entities should use 48x64 sprites // FIXME: this will not display correctly for horse outfit - const shadow = stendhal.data.sprites.getShadow("48x64"); + const shadow = htmlImageStore.getShadow("48x64"); if (shadow && shadow.complete && shadow.height) { // draw shadow below other layers @@ -255,23 +260,21 @@ export class RPEntity extends ActiveEntity { if (this.octx) { this.octx.clearRect(0, 0, this.octx.canvas.width, this.octx.canvas.height); } - if (stendhal.data.outfit.detailHasRearLayer(outfit["detail"])) { + if (store.detailHasRearLayer(outfit["detail"])) { layers.splice(0, 0, "detail-rear"); outfit["detail-rear"] = outfit["detail"]; } for (const layer of layers) { // hair is not drawn under certain hats/helmets - if (layer == "hair" && !stendhal.data.outfit.drawHair(outfit["hat"])) { + if (layer == "hair" && !store.drawHair(outfit["hat"])) { continue; } const lsprite = this.getOutfitPart(layer, outfit[layer], outfit["body"]); if (lsprite && lsprite.complete && lsprite.height) { if (!this.octx) { - let ocanvas = document.createElement("canvas"); + let ocanvas = new OffscreenCanvas(lsprite.width, lsprite.height); this.octx = ocanvas.getContext("2d")!; - ocanvas.width = lsprite.width; - ocanvas.height = lsprite.height; } this.octx!.drawImage(lsprite, 0, 0); } @@ -302,27 +305,28 @@ export class RPEntity extends ActiveEntity { n = "0" + index; } + let store = OutfitStore.get(); if (part === "body" && index < 3 && stendhal.config.getBoolean("effect.no-nude")) { n += "-nonude"; - } else if (part === "dress" && stendhal.data.outfit.drawBustyDress(index, body)) { + } else if (part === "dress" && store.drawBustyDress(index, body)) { n += "b"; } else if (part.endsWith("-rear")) { n += "-rear"; part = part.replace(/-rear$/, ""); } - const filename = stendhal.paths.sprites + "/outfit/" + part + "/" + n + ".png"; + const filename = Paths.sprites + "/outfit/" + part + "/" + n + ".png"; const colors = this["outfit_colors"]; let colorname; - if (stendhal.data.outfit.isSkinLayer(part)) { + if (store.isSkinLayer(part)) { colorname = "skin"; } else { colorname = part; } if (typeof(colors) !== "undefined" && (typeof(colors[colorname]) !== "undefined")) { - return stendhal.data.sprites.getFiltered(filename, "trueColor", colors[colorname]); + return htmlImageStore.getFiltered(filename, "trueColor", colors[colorname]); } else { - return stendhal.data.sprites.get(filename); + return htmlImageStore.get(filename); } } @@ -331,7 +335,7 @@ export class RPEntity extends ActiveEntity { * extends past top edge. */ public setStatusBarOffset() { - const screenOffsetY = stendhal.ui.gamewindow.offsetY; + const screenOffsetY = ViewPort.get().offsetY; const entityBottom = (this["_y"] * 32) + (this["height"] * 32); // FIXME: how to get text height dynamically? const entityTop = entityBottom - this["drawHeight"] @@ -347,138 +351,70 @@ export class RPEntity extends ActiveEntity { /** * draw RPEntities */ - override draw(ctx: CanvasRenderingContext2D) { + override draw(ctx: RenderingContext2D) { if (typeof(this["ghostmode"]) != "undefined" && marauroa.me && !marauroa.me.isAdmin()) { return; } this.drawCombat(ctx); this.drawMain(ctx); this.drawAttack(ctx); - this.drawStatusIcons(ctx); + statusIconDrawer.drawStatusIcons(this, ctx); } - drawMain(ctx: CanvasRenderingContext2D) { + drawMain(ctx: RenderingContext2D) { if (typeof(this["outfit"]) != "undefined" || typeof(this["outfit_ext"]) != "undefined") { this.drawMultipartOutfit(ctx); } else { - let filename = stendhal.paths.sprites + "/" + this.spritePath + "/" + this["class"]; - if (typeof(this["subclass"]) != "undefined") { - filename = filename + "/" + this["subclass"]; - } - - // check for safe image - if (!stendhal.config.getBoolean("effect.blood") && stendhal.data.sprites.hasSafeImage(filename)) { - filename = filename + "-safe.png"; - } else { - filename = filename + ".png"; - } - - let image = stendhal.data.sprites.get(filename); - - if (stendhal.config.getBoolean("effect.shadows") && this.castsShadow()) { - // check for configured shadow style - let shadow_style = this["shadow_style"]; - if (typeof(shadow_style) === "undefined") { - // default to sprite dimensions - shadow_style = (image.width / 3) + "x" + (image.height / 4); - } - - const shadow = stendhal.data.sprites.getShadow(shadow_style); - - // draw shadow first - if (typeof(shadow) !== "undefined") { - this.drawSpriteImage(ctx, shadow); - } - } - - this.drawSpriteImage(ctx, image); - this.drawOverlayAnimation(ctx); + this.drawSingleOutfit(ctx); } } - drawStatusIcons(ctx: CanvasRenderingContext2D) { - - function _drawAnimatedIcon(icon: CanvasImageSource, delay: number, nFrames: number, xdim: number, ydim: number, x: number, y: number) { - var frame = Math.floor(Date.now() / delay) % nFrames; - ctx.drawImage(icon, frame * xdim, 0, xdim, ydim, x, y, xdim, ydim); - } - function drawAnimatedIcon(iconPath: string, delay: number, x: number, y: number, fWidth?: number) { - var icon = stendhal.data.sprites.get(iconPath); - var dimH = icon.height; - var dimW = typeof(fWidth) !== "undefined" ? fWidth : dimH; - var nFrames = icon.width / dimW; - _drawAnimatedIcon(icon, delay, nFrames, dimW, dimH, x, y); - } - function drawAnimatedIconWithFrames(iconPath: string, nFrames: number, delay: number, x: number, y: number) { - var icon = stendhal.data.sprites.get(iconPath); - var ydim = icon.height; - var xdim = icon.width / nFrames; - _drawAnimatedIcon(icon, delay, nFrames, xdim, ydim, x, y); + drawSingleOutfit(ctx: RenderingContext2D) { + let filename = Paths.sprites + "/" + this.spritePath + "/" + this["class"]; + if (typeof(this["subclass"]) != "undefined") { + filename = filename + "/" + this["subclass"]; } - var x = this["_x"] * 32 - 10; - var y = (this["_y"] + 1) * 32; - if (this.hasOwnProperty("choking")) { - ctx.drawImage(stendhal.data.sprites.get(stendhal.paths.sprites + "/ideas/choking.png"), x, y - 10); - } else if (this.hasOwnProperty("eating")) { - ctx.drawImage(stendhal.data.sprites.get(stendhal.paths.sprites + "/ideas/eat.png"), x, y - 10); + // check for safe image + if (!stendhal.config.getBoolean("effect.blood") && htmlImageStore.hasSafeImage(filename)) { + filename = filename + "-safe.png"; + } else { + filename = filename + ".png"; } - // NPC and pet idea icons - if (this.hasOwnProperty("idea")) { - const idea = stendhal.paths.sprites + "/ideas/" + this["idea"] + ".png"; - const ani = stendhal.data.sprites.animations.idea[this["idea"]]; - if (ani) { - drawAnimatedIcon(idea, ani.delay, x + ani.offsetX * this["width"], - y - this["drawHeight"] + ani.offsetY); - } else { - ctx.drawImage(stendhal.data.sprites.get(idea), x + 32 * this["width"], - y - this["drawHeight"]); + + let image = htmlImageStore.get(filename); + + if (stendhal.config.getBoolean("effect.shadows") && this.castsShadow()) { + // check for configured shadow style + let shadow_style = this["shadow_style"]; + if (typeof(shadow_style) === "undefined") { + // default to sprite dimensions + shadow_style = (image.width / 3) + "x" + (image.height / 4); + } + + const shadow = htmlImageStore.getShadow(shadow_style); + + // draw shadow first + if (typeof(shadow) !== "undefined") { + this.drawSpriteImage(ctx, shadow); } } - if (this.hasOwnProperty("away")) { - drawAnimatedIcon(stendhal.paths.sprites + "/ideas/away.png", 1500, x + 32 * this["width"], y - this["drawHeight"]); - } - if (this.hasOwnProperty("grumpy")) { - drawAnimatedIcon(stendhal.paths.sprites + "/ideas/grumpy.png", 1000, x + 5, y - this["drawHeight"]); - } - if (this.hasOwnProperty("last_player_kill_time")) { - drawAnimatedIconWithFrames(stendhal.paths.sprites + "/ideas/pk.png", 12, 300, x, y - this["drawHeight"]); - } - // status affects - if (this.hasOwnProperty("poisoned")) { - drawAnimatedIcon(stendhal.paths.sprites + "/status/poison.png", 100, x + 32 * this["width"] - 10, y - this["drawHeight"]); - } - if (this.hasOwnProperty("status_confuse")) { - drawAnimatedIcon(stendhal.paths.sprites + "/status/confuse.png", 200, x + 32 * this["width"] - 14, y - this["drawHeight"] + 16); - } - if (this.hasOwnProperty("status_shock")) { - drawAnimatedIcon(stendhal.paths.sprites + "/status/shock.png", 200, x + 32 * this["width"] - 25, y - 32, 38); - } - // NPC job icons - let nextX = x; - if (this.hasOwnProperty("job_healer")) { - ctx.drawImage(stendhal.data.sprites.get(stendhal.paths.sprites + "/status/healer.png"), nextX, y - 10); - nextX += 12; - } - if (this.hasOwnProperty("job_merchant")) { - ctx.drawImage(stendhal.data.sprites.get(stendhal.paths.sprites + "/status/merchant.png"), nextX, y - 10); - nextX += 12; - } - if (this.hasOwnProperty("job_producer")) { - ctx.drawImage(stendhal.data.sprites.get(stendhal.paths.sprites + "/status/producer.png"), nextX, y - 16); - nextX += 16; - } + + this.drawSpriteImage(ctx, image); + this.drawOverlayAnimation(ctx); } + + /** * Draw colored ellipses (or rectangles on browsers that do not support * ellipses) when the entity is being attacked, or is attacking the user. */ - drawCombat(ctx: CanvasRenderingContext2D) { + drawCombat(ctx: RenderingContext2D) { if (this.attackers.size > 0) { ctx.lineWidth = 1; /* - * As of 2015-9-15 CanvasRenderingContext2D.ellipse() is not + * As of 2015-9-15 RenderingContext2D.ellipse() is not * supported in most browsers. Fall back to rectangles on these. * Also on Chrome 45.0.2454.85 ellipse() does not seem to support * the begin angle parameter correctly, nor does the stroke @@ -539,7 +475,7 @@ export class RPEntity extends ActiveEntity { * the coordinates where to start floating. The method should return true * when the floater should be removed. */ - drawFloaters(ctx: CanvasRenderingContext2D) { + drawFloaters(ctx: RenderingContext2D) { var centerX = (this["_x"] + this["width"] / 2) * 32; var topY = (this["_y"] + 1) * 32 - this["drawHeight"]; // Grab an unchanging copy @@ -555,10 +491,10 @@ export class RPEntity extends ActiveEntity { } /** - * @param {CanvasRenderingContext2D} ctx + * @param {RenderingContext2D} ctx * @param {Image} image */ - drawSpriteImage(ctx: CanvasRenderingContext2D, image: CanvasImageSource & ImageWithDimensions) { + drawSpriteImage(ctx: RenderingContext2D, image: CanvasImageSource & ImageWithDimensions) { var localX = this["_x"] * 32; var localY = this["_y"] * 32; if (image.height) { // image.complete is true on missing image files @@ -610,7 +546,7 @@ export class RPEntity extends ActiveEntity { } } - drawTop(ctx: CanvasRenderingContext2D) { + drawTop(ctx: RenderingContext2D) { var localX = this["_x"] * 32; var localY = this["_y"] * 32; this.drawFloaters(ctx); @@ -618,7 +554,7 @@ export class RPEntity extends ActiveEntity { this.drawTitle(ctx, localX, localY + this.statusBarYOffset); } - drawHealthBar(ctx: CanvasRenderingContext2D, x: number, y: number) { + drawHealthBar(ctx: RenderingContext2D, x: number, y: number) { var drawX = x + ((this["width"] * 32) - this["drawWidth"]) / 2; var drawY = y + (this["height"] * 32) - this["drawHeight"] - HEALTH_BAR_HEIGHT + this.titleDrawYOffset; @@ -655,42 +591,48 @@ export class RPEntity extends ActiveEntity { } } - drawTitle(ctx: CanvasRenderingContext2D, x: number, y: number) { + drawTitle(ctx: RenderingContext2D, x: number, y: number) { if (this.titleTextSprite) { let textMetrics = this.titleTextSprite.getTextMetrics(ctx); + if (!textMetrics) { + throw new Error("textMetrics is undefined"); + } var drawY = y + (this["height"] * 32) - this["drawHeight"] - HEALTH_BAR_HEIGHT + this.titleDrawYOffset; this.titleTextSprite.draw(ctx, x + (this["width"] * 32 - textMetrics.width) / 2, drawY - 5 - HEALTH_BAR_HEIGHT); } } - drawAttack(ctx: CanvasRenderingContext2D) { + drawAttack(ctx: RenderingContext2D) { if (!this.attackSprite) { return; } if (this.attackSprite.expired()) { // sprite expired & should be removed // NOTE: if this is done after `this.attackSprite.draw` animation doesn't look right + this.attackSprite.free(); this.attackSprite = undefined; return; } + let viewPort = ViewPort.get(); var localX = this["_x"] * 32; var localY = this["_y"] * 32; - var localW = this["width"] * stendhal.ui.gamewindow.targetTileWidth; - var localH = this["height"] * stendhal.ui.gamewindow.targetTileHeight; + var localW = this["width"] * viewPort.targetTileWidth; + var localH = this["height"] * viewPort.targetTileHeight; this.attackSprite.draw(ctx, localX, localY, localW, localH); } /** * Draws an animation over entity sprite. * - * @param {CanvasRenderingContext2D} ctx + * @param {RenderingContext2D} ctx * Canvas context to draw on. */ - private drawOverlayAnimation(ctx: CanvasRenderingContext2D) { + private drawOverlayAnimation(ctx: RenderingContext2D) { if (this.overlay && this.overlay.draw(ctx, this["drawOffsetX"], this["drawOffsetY"], this["drawWidth"], this["drawHeight"])) { // overlay sprite expired + this.overlay.free(); this.overlay = undefined; } } @@ -710,21 +652,21 @@ export class RPEntity extends ActiveEntity { } onDamaged(_source: Entity, damage: number) { - this.attackResult = this.createResultIcon(stendhal.paths.sprites + "/combat/hitted.png"); + this.attackResult = this.createResultIcon(Paths.sprites + "/combat/hitted.png"); var sounds = ["attack-melee-01", "attack-melee-02", "attack-melee-03", "attack-melee-04", "attack-melee-05", "attack-melee-06", "attack-melee-07"]; var index = Math.floor(Math.random() * Math.floor(sounds.length)); stendhal.sound.playLocalizedEffect(this["_x"], this["_y"], 20, 3, sounds[index], 1); } onBlocked(_source: Entity) { - this.attackResult = this.createResultIcon(stendhal.paths.sprites + "/combat/blocked.png"); + this.attackResult = this.createResultIcon(Paths.sprites + "/combat/blocked.png"); var sounds = ["clang-metallic-1", "clang-dull-1"]; var index = Math.floor(Math.random() * Math.floor(sounds.length)); stendhal.sound.playLocalizedEffect(this["_x"], this["_y"], 20, 3, sounds[index], 1); } onMissed(_source: Entity) { - this.attackResult = this.createResultIcon(stendhal.paths.sprites + "/combat/missed.png"); + this.attackResult = this.createResultIcon(Paths.sprites + "/combat/missed.png"); } protected onHPChanged(change: number) { @@ -779,8 +721,8 @@ export class RPEntity extends ActiveEntity { createResultIcon(imagePath: string) { return { initTime: Date.now(), - image: stendhal.data.sprites.get(imagePath), - draw: function(ctx: CanvasRenderingContext2D, x: number, y: number) { + image: htmlImageStore.get(imagePath), + draw: function(ctx: RenderingContext2D, x: number, y: number) { ctx.drawImage(this.image, x, y); return (Date.now() - this.initTime > 1200); } @@ -793,6 +735,7 @@ export class RPEntity extends ActiveEntity { weapon = undefined; } + this.attackSprite?.free(); if (ranged) { this.attackSprite = new RangedAttackSprite(this, this.getAttackTarget()!, Nature.VALUES[nature].color, weapon); @@ -810,12 +753,12 @@ export class RPEntity extends ActiveEntity { const imagePath = Nature.VALUES[nature].getWeaponPath(weapon); /* if (weapon.startsWith("blade_strike")) { - this.attackSprite = new BarehandAttackSprite(this, stendhal.data.sprites.get(imagePath)); + this.attackSprite = new BarehandAttackSprite(this, imagePath); } else { this.attackSprite = new MeleeAttackSprite(this, imagePath); } */ - this.attackSprite = new BarehandAttackSprite(this, stendhal.data.sprites.get(imagePath)); + this.attackSprite = new BarehandAttackSprite(this, imagePath); } } @@ -844,10 +787,10 @@ export class RPEntity extends ActiveEntity { } } - override destroy(_obj: Entity) { - if (this._target) { - this._target.onAttackStopped(this); - } + override destroy(obj: Entity) { + this._target?.onAttackStopped(this); + this.overlay?.free(); + super.destroy(obj); } /** diff --git a/src/js/stendhal/entity/RPObject.ts b/src/js/stendhal/entity/RPObject.ts deleted file mode 100644 index bac2ac041d..0000000000 --- a/src/js/stendhal/entity/RPObject.ts +++ /dev/null @@ -1,96 +0,0 @@ -/*************************************************************************** - * (C) Copyright 2003-2023 - Stendhal * - *************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU Affero General Public License as * - * published by the Free Software Foundation; either version 3 of the * - * License, or (at your option) any later version. * - * * - ***************************************************************************/ - -declare var marauroa: any; - - -/** - * base class for RPObjects - */ -export class RPObject { - [key: string]: any; - - onEvent(e: any) { - var event = marauroa.rpeventFactory.create(e["c"]); - for (var i in e["a"]) { - if (e["a"].hasOwnProperty(i)) { - event[i] = e["a"][i]; - } - event["_rpclass"] = e["c"]; - } - - // Event slots - for (var slot in e["s"]) { - if (e["s"].hasOwnProperty(slot)) { - event[slot] = e["s"][slot]; - } - } - event.execute(this); - } - - set(key: string, value: object) { - this[key] = value; - } - - setMapEntry(map: string, key: string, value: object) { - this[map][key] = value; - } - - unset(key: string) { - delete this[key]; - } - - unsetMapEntry(map: string, key: string) { - delete this[map][key]; - } - - destroy(_parent: RPObject) { - // do nothing - } - - createSlot(name: string) { - var slot = marauroa.rpslotFactory.create(name); - slot._parent = this; - return slot; - } - - init() { - // do nothing - } - - /** - * Retrieves object width. - */ - public getWidth(): number { - return this["width"] || 1; - } - - /** - * Retrieves object height. - */ - public getHeight(): number { - return this["height"] || 1; - } - - /** - * Checks if this object is nested. - */ - public isContained(): boolean { - return this._parent !== undefined; - } - - /** - * Retrieves the parent object. - */ - public getContainer(): RPObject { - return this._parent; - } -} diff --git a/src/js/stendhal/entity/Sign.ts b/src/js/stendhal/entity/Sign.ts index 512c2c0f07..f9429c12ba 100644 --- a/src/js/stendhal/entity/Sign.ts +++ b/src/js/stendhal/entity/Sign.ts @@ -9,37 +9,49 @@ * * ***************************************************************************/ +import { RPObject, RPZone} from "marauroa"; import { Entity } from "./Entity"; import { ActivityIndicatorSprite } from "../sprite/ActivityIndicatorSprite"; +import { RenderingContext2D } from "util/Types"; +import { Paths } from "../data/Paths"; +import { singletons } from "../SingletonRepo"; -declare var stendhal: any; +import { stendhal } from "../stendhal"; +import { images } from "sprite/image/ImageManager"; +import { ImageSprite } from "sprite/image/ImageSprite"; export class Sign extends Entity { override zIndex = 5000; private indicator?: ActivityIndicatorSprite; - - constructor() { - super(); - this["class"] = "default"; + override init() { + super.init(); + if (!this.imageSprite) { + this.imageSprite = new ImageSprite( + images.load(Paths.sprites + "/signs/" + "default" + ".png"), + 0, 0, 32, 32 + ); + } } override set(key: string, value: object) { super.set(key, value); if (key === "activity-indicator" && stendhal.config.getBoolean("activity-indicator")) { this.indicator = new ActivityIndicatorSprite(); + } else if (key === "class") { + this.imageSprite = new ImageSprite( + images.load(Paths.sprites + "/signs/" + this["class"] + ".png"), + 0, 0, 32, 32 + ); } } - override draw(ctx: CanvasRenderingContext2D) { - if (!this.imagePath) { - this.imagePath = stendhal.paths.sprites + "/signs/" + this["class"] + ".png"; - } - var image = stendhal.data.sprites.get(this.imagePath); - if (image.height) { - var localX = this["x"] * 32; - var localY = this["y"] * 32; + override draw(ctx: RenderingContext2D) { + let image = this.imageSprite?.imageRef?.image; + let localX = this["x"] * 32; + let localY = this["y"] * 32; + if (image) { ctx.drawImage(image, localX, localY); if (this.indicator) { this.indicator.draw(ctx, localX, localY, image.width); @@ -52,7 +64,12 @@ export class Sign extends Entity { } override getCursor(_x: number, _y: number) { - return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto"; + } + + override destroy(parent: RPObject|RPZone): void { + this.indicator?.free(); + super.destroy(parent); } } diff --git a/src/js/stendhal/entity/TrainingDummy.ts b/src/js/stendhal/entity/TrainingDummy.ts index d2984622ce..776227e83d 100644 --- a/src/js/stendhal/entity/TrainingDummy.ts +++ b/src/js/stendhal/entity/TrainingDummy.ts @@ -9,10 +9,10 @@ * * ***************************************************************************/ +import { Paths } from "../data/Paths"; import { NPC } from "./NPC"; -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" export class TrainingDummy extends NPC { @@ -29,6 +29,6 @@ export class TrainingDummy extends NPC { } override getCursor(_x: number, _y: number) { - return "url(" + stendhal.paths.sprites + "/cursor/attack.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/attack.png) 1 3, auto"; } } diff --git a/src/js/stendhal/entity/UnknownEntity.ts b/src/js/stendhal/entity/UnknownEntity.ts index df40496262..1a9381ff08 100644 --- a/src/js/stendhal/entity/UnknownEntity.ts +++ b/src/js/stendhal/entity/UnknownEntity.ts @@ -11,7 +11,7 @@ import { Entity } from "./Entity"; -declare var marauroa: any; +import { marauroa } from "marauroa" export class UnknownEntity extends Entity { diff --git a/src/js/stendhal/entity/UseableEntity.ts b/src/js/stendhal/entity/UseableEntity.ts index 4ec2317aa8..96883af230 100644 --- a/src/js/stendhal/entity/UseableEntity.ts +++ b/src/js/stendhal/entity/UseableEntity.ts @@ -9,32 +9,26 @@ * * ***************************************************************************/ -declare var stendhal: any; - import { Entity } from "./Entity"; import { MenuItem } from "../action/MenuItem"; +import { Paths } from "../data/Paths"; +import { ImageSprite } from "sprite/image/ImageSprite"; +import { images } from "sprite/image/ImageManager"; export class UseableEntity extends Entity { override zIndex = 3000 action = "use"; - constructor() { - super(); - this.sprite = { - height: 32, - width: 32 - }; - } - override set(key: string, value: any) { super.set(key, value); if (key === "class" || key === "name") { - this.sprite.filename = stendhal.paths.sprites + "/" - + this["class"] + "/" + this["_name"] + ".png"; - } - if (key === "state") { - this.sprite.offsetY = this["state"] * 32; + let filename = Paths.sprites + "/" + this["class"] + "/" + this["_name"] + ".png"; + let state = this["state"] * 32 || 0; + this.imageSprite?.free(); + this.imageSprite = new ImageSprite(images.load(filename), 0, state, 32, 32); + } else if (key === "state" && this.imageSprite) { + this.imageSprite.offsetY = this["state"] * 32; } } diff --git a/src/js/stendhal/entity/User.ts b/src/js/stendhal/entity/User.ts index 353ee34152..c2c55844c7 100644 --- a/src/js/stendhal/entity/User.ts +++ b/src/js/stendhal/entity/User.ts @@ -9,8 +9,8 @@ * * ***************************************************************************/ -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { stendhal } from "../stendhal"; import { Entity } from "./Entity"; import { Player } from "./Player"; @@ -33,6 +33,7 @@ import { OutfitDialog } from "../ui/dialog/outfit/OutfitDialog"; import { FloatingWindow } from "../ui/toolkit/FloatingWindow"; import { Direction } from "../util/Direction"; +import { ViewPort } from "ui/ViewPort"; /** @@ -163,7 +164,7 @@ export class User extends Player { override onExitZone() { super.onExitZone(); // speech bubbles & emojis from viewport - stendhal.ui.gamewindow.onExitZone(); + ViewPort.get().onExitZone(); // stop sounds & clear map sounds cache on zone change const msgs: string[] = []; const lssm = singletons.getLoopedSoundSourceManager(); @@ -225,7 +226,7 @@ export class User extends Player { * @param dir {util.Direction.Direction} * New direction to face. */ - public setFacing(dir: Direction) { + public faceTo(dir: Direction) { marauroa.clientFramework.sendAction({type: "face", dir: ""+dir.val}); } @@ -244,7 +245,7 @@ export class User extends Player { return; } // in case viewport frozen from view change event - stendhal.ui.viewport.freeze = false; + ViewPort.get().freeze = false; if (cancelAutoWalk && this.autoWalkEnabled() && this.getWalkDirection() == dir) { // cancel auto-walk if enabled & new direction is same as current direction of movement marauroa.clientFramework.sendAction({type: "walk"}); @@ -280,7 +281,7 @@ export class User extends Player { public moveTo(x: number, y: number, zone?: string): void; public moveTo(p1: object|number, p2?: number, p3?:string) { // in case viewport frozen from view change event - stendhal.ui.viewport.freeze = false; + ViewPort.get().freeze = false; let action: any = {}; if (typeof(p1) === "object") { action = p1; diff --git a/src/js/stendhal/entity/VisibleEntity.ts b/src/js/stendhal/entity/VisibleEntity.ts index 12cea77ae8..dff542145a 100644 --- a/src/js/stendhal/entity/VisibleEntity.ts +++ b/src/js/stendhal/entity/VisibleEntity.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2023 - Stendhal * + * (C) Copyright 2003-2026 - Stendhal * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,9 +9,10 @@ * * ***************************************************************************/ +import { images } from "sprite/image/ImageManager"; +import { Paths } from "../data/Paths"; import { Entity } from "./Entity"; - -declare var stendhal: any; +import { ImageSprite } from "sprite/image/ImageSprite"; export class VisibleEntity extends Entity { @@ -19,21 +20,29 @@ export class VisibleEntity extends Entity { constructor() { super(); - this.sprite = { - height: 32, - width: 32 - }; + } + + override init(): void { + let filename = Paths.sprites + "/" + + (this["class"] || "") + "/" + + (this["subclass"] || "") + "/" + + (this["_name"] || "") + ".png"; + let state = this["state"] * 32 || 0; + this.imageSprite = new ImageSprite(images.load(filename), 0, state, 32, 32); } override set(key: string, value: any) { super.set(key, value); if (key === "class" || key === "subclass" || key === "_name") { - this.sprite.filename = stendhal.paths.sprites + "/" - + (this["class"] || "") + "/" - + (this["subclass"] || "") + "/" - + (this["_name"] || "") + ".png"; + // if this was already initialized, create a new ImageSprite + if (this.imageSprite) { + this.imageSprite.free(); + this.init(); + } } else if (key === "state") { - this.sprite.offsetY = value * 32; + if (this.imageSprite) { + this.imageSprite.offsetY = value * 32; + } } } @@ -42,7 +51,7 @@ export class VisibleEntity extends Entity { } override getCursor(_x: number, _y: number) { - return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto"; } } diff --git a/src/js/stendhal/entity/WalkBlocker.ts b/src/js/stendhal/entity/WalkBlocker.ts index 7d8ccd650d..72654ecc64 100644 --- a/src/js/stendhal/entity/WalkBlocker.ts +++ b/src/js/stendhal/entity/WalkBlocker.ts @@ -9,10 +9,9 @@ * * ***************************************************************************/ +import { Paths } from "../data/Paths"; import { Entity } from "./Entity"; -declare var stendhal: any; - export class WalkBlocker extends Entity { override zIndex = 3000; @@ -22,7 +21,7 @@ export class WalkBlocker extends Entity { } override getCursor(_x: number, _y: number) { - return "url(" + stendhal.paths.sprites + "/cursor/stop.png) 1 3, auto"; + return "url(" + Paths.sprites + "/cursor/stop.png) 1 3, auto"; } } diff --git a/src/js/stendhal/entity/Zone.ts b/src/js/stendhal/entity/Zone.ts index 6afb996d9b..0bcdbcefa3 100644 --- a/src/js/stendhal/entity/Zone.ts +++ b/src/js/stendhal/entity/Zone.ts @@ -9,11 +9,12 @@ * * ***************************************************************************/ -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { stendhal } from "../stendhal"; import { Entity } from "./Entity"; import { Player } from "./Player"; +import { Ground } from "./Ground"; /** @@ -22,11 +23,12 @@ import { Player } from "./Player"; export class Zone { /** Entities found in this zone. */ - private entities: Entity[] = []; + public entities: Entity[] = []; /** Singleton instance. */ private static instance: Zone; + public ground = new Ground(); /** * Retrieves singleton instance. @@ -45,7 +47,7 @@ export class Zone { // do nothing } - getEntitiesAt(x: number, y: number, filter: boolean): Entity[] { + getEntitiesAt(x: number, y: number, filter?: boolean): Entity[] { const xGrid = x / 32; const yGrid = y / 32; const entities = [] as Entity[]; @@ -63,14 +65,14 @@ export class Zone { return entities; } - entityAt(x: number, y: number, filter: boolean): Entity { - let res = stendhal.zone.ground; + entityAt(x: number, y: number, filter?: boolean): Entity { + let res = Zone.get().ground as any; for (const obj of this.getEntitiesAt(x, y, filter)) { res = obj; } // If we found an entity, return it - if (res != stendhal.zone.ground) { + if (res != Zone.get().ground) { return res; } diff --git a/src/js/stendhal/event/AttackEvent.ts b/src/js/stendhal/event/AttackEvent.ts new file mode 100644 index 0000000000..4d01c198e3 --- /dev/null +++ b/src/js/stendhal/event/AttackEvent.ts @@ -0,0 +1,38 @@ +/*************************************************************************** + * (C) Copyright 2005-2026 - Stendhal * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { RPEvent } from "marauroa" + +export class AttackEvent extends RPEvent { + + public damage!: string; + public type!: string; + public weapon!: string; + + public execute(entity: any): void { + let target = entity.getAttackTarget(); + if (!target) { + return; + } + if (this.hasOwnProperty("hit")) { + var damage = parseInt(this["damage"], 10); + if (damage !== 0) { + target.onDamaged(entity, damage); + } else { + target.onBlocked(entity); + } + } else { + target.onMissed(entity); + } + entity.onAttackPerformed(parseInt(this["type"], 10), this.hasOwnProperty("ranged"), this["weapon"]); + } + +}; diff --git a/src/js/stendhal/event/BestiaryEvent.ts b/src/js/stendhal/event/BestiaryEvent.ts index b6c3d1d47c..df1eea3ca4 100644 --- a/src/js/stendhal/event/BestiaryEvent.ts +++ b/src/js/stendhal/event/BestiaryEvent.ts @@ -10,13 +10,13 @@ * * ***************************************************************************/ -import { RPEvent } from "./RPEvent"; +import { RPEvent } from "marauroa" import { ui } from "../ui/UI"; import { DialogContentComponent } from "../ui/toolkit/DialogContentComponent"; -declare var stendhal: any; +import { stendhal } from "../stendhal"; export class BestiaryEvent extends RPEvent { @@ -24,7 +24,7 @@ export class BestiaryEvent extends RPEvent { enemies!: string; - execute(entity: any) { + execute(_entity: any) { if (!this.hasOwnProperty("enemies")) { console.error("Event does not have \"enemies\" attribute"); return; diff --git a/src/js/stendhal/event/ChatOptionsEvent.ts b/src/js/stendhal/event/ChatOptionsEvent.ts index f16cc023a4..919e02a38e 100644 --- a/src/js/stendhal/event/ChatOptionsEvent.ts +++ b/src/js/stendhal/event/ChatOptionsEvent.ts @@ -11,9 +11,9 @@ import { Chat } from "../util/Chat"; import { ChatOptionsDialog } from "../ui/dialog/ChatOptionsDialog"; -import { RPEvent } from "./RPEvent"; +import { RPEvent } from "marauroa" -declare var marauroa: any +import { marauroa } from "marauroa" /** * shows an image for a detail view diff --git a/src/js/stendhal/event/EventRegistry.ts b/src/js/stendhal/event/EventRegistry.ts new file mode 100644 index 0000000000..76da3507c5 --- /dev/null +++ b/src/js/stendhal/event/EventRegistry.ts @@ -0,0 +1,93 @@ +/*************************************************************************** + * (C) Copyright 2003-2026 - Stendhal * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { marauroa } from "marauroa" + +import { AttackEvent } from "event/AttackEvent"; +import { BestiaryEvent } from "./BestiaryEvent"; +import { ChatOptionsEvent } from "./ChatOptionsEvent"; +import { ExamineEvent } from "./ExamineEvent"; +import { GlobalVisualEffectEvent } from "event/GlobalVisualEffectEvent"; +import { GroupChangeEvent } from "./GroupChangeEvent"; +import { GroupInviteEvent } from "./GroupInviteEvent"; +import { ImageEvent } from "event/ImageEvent"; +import { PlayerLoggedOnEvent } from "./PlayerLoggedOnEvent"; +import { PlayerLoggedOutEvent } from "./PlayerLoggedOutEvent"; +import { PrivateTextEvent } from "event/PrivateTextEvent"; +import { ProgressStatusEvent } from "./ProgressStatusEvent"; +import { ReachedAchievementEvent } from "event/ReachedAchievementEvent"; +import { ShowItemListEvent } from "./ShowItemListEvent"; +import { ShowOutfitListEvent } from "./ShowOutfitListEvent"; +import { SoundEvent } from "./SoundEvent"; +import { TextEvent } from "event/TextEvent"; +import { TradeEvent } from "./TradeEvent"; +import { TransitionGraphEvent } from "event/TransitionGraphEvent"; +import { ViewChangeEvent } from "./ViewChangeEvent"; + + +export class EventRegistry { + + private initialized = false; + + /** Singleton instance. */ + private static instance: EventRegistry; + + + /** + * Retrieves singleton instance. + */ + static get(): EventRegistry { + if (!EventRegistry.instance) { + EventRegistry.instance = new EventRegistry(); + } + return EventRegistry.instance; + } + + /** + * Hidden singleton constructor. + */ + private constructor() { + // do nothing + } + + /** + * Registers standard events. + */ + init() { + if (this.initialized) { + console.warn("tried to re-initialize EventRegistry"); + return; + } + this.initialized = true; + + marauroa.rpeventFactory.register("attack", AttackEvent); + marauroa.rpeventFactory.register("bestiary", BestiaryEvent); + marauroa.rpeventFactory.register("chat_options", ChatOptionsEvent); + marauroa.rpeventFactory.register("examine", ExamineEvent); + marauroa.rpeventFactory.register("global_visual_effect", GlobalVisualEffectEvent); + marauroa.rpeventFactory.register("group_change_event", GroupChangeEvent); + marauroa.rpeventFactory.register("group_invite_event", GroupInviteEvent); + marauroa.rpeventFactory.register("image_event", ImageEvent); + marauroa.rpeventFactory.register("player_logged_on", PlayerLoggedOnEvent); + marauroa.rpeventFactory.register("player_logged_out", PlayerLoggedOutEvent); + marauroa.rpeventFactory.register("private_text", PrivateTextEvent); + marauroa.rpeventFactory.register("progress_status_event", ProgressStatusEvent); + marauroa.rpeventFactory.register("reached_achievement", ReachedAchievementEvent); + marauroa.rpeventFactory.register("show_item_list", ShowItemListEvent); + marauroa.rpeventFactory.register("show_outfit_list", ShowOutfitListEvent); + marauroa.rpeventFactory.register("sound_event", SoundEvent); + marauroa.rpeventFactory.register("text", TextEvent); + marauroa.rpeventFactory.register("trade_state_change_event", TradeEvent); + marauroa.rpeventFactory.register("transition_graph", TransitionGraphEvent); + marauroa.rpeventFactory.register("view_change", ViewChangeEvent); + } + +} diff --git a/src/js/stendhal/event/ExamineEvent.ts b/src/js/stendhal/event/ExamineEvent.ts index 3a79d3f649..108fbc7911 100644 --- a/src/js/stendhal/event/ExamineEvent.ts +++ b/src/js/stendhal/event/ExamineEvent.ts @@ -11,9 +11,9 @@ import { ImageViewerDialog } from "../ui/dialog/ImageViewerDialog"; import { ui } from "../ui/UI"; -import { RPEvent } from "./RPEvent"; +import { RPEvent } from "marauroa" -declare var marauroa: any +import { marauroa } from "marauroa" /** * shows an image for a detail view diff --git a/src/js/stendhal/event/GlobalVisualEffectEvent.ts b/src/js/stendhal/event/GlobalVisualEffectEvent.ts new file mode 100644 index 0000000000..989ceb2cb9 --- /dev/null +++ b/src/js/stendhal/event/GlobalVisualEffectEvent.ts @@ -0,0 +1,20 @@ +/*************************************************************************** + * (C) Copyright 2005-2026 - Stendhal * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { RPEvent } from "marauroa" + +export class GlobalVisualEffectEvent extends RPEvent { + + public execute(_entity: any): void { + // not implemented + } + +}; diff --git a/src/js/stendhal/event/GroupChangeEvent.ts b/src/js/stendhal/event/GroupChangeEvent.ts index 713a26f7b8..372edb65fe 100644 --- a/src/js/stendhal/event/GroupChangeEvent.ts +++ b/src/js/stendhal/event/GroupChangeEvent.ts @@ -9,13 +9,13 @@ * * ***************************************************************************/ -import { RPEvent } from "./RPEvent"; +import { RPEvent } from "marauroa" import { ui } from "../ui/UI"; import { UIComponentEnum } from "../ui/UIComponentEnum"; import { GroupPanelComponent } from "../ui/component/GroupPanelComponent"; -declare var stendhal: any; -declare var marauroa: any; +import { stendhal } from "../stendhal"; +import { marauroa } from "marauroa" /** * handles player trade events diff --git a/src/js/stendhal/event/GroupInviteEvent.ts b/src/js/stendhal/event/GroupInviteEvent.ts index dda3f7af48..455f457a41 100644 --- a/src/js/stendhal/event/GroupInviteEvent.ts +++ b/src/js/stendhal/event/GroupInviteEvent.ts @@ -10,12 +10,12 @@ ***************************************************************************/ -import { RPEvent } from "./RPEvent"; +import { RPEvent } from "marauroa" import { ui } from "../ui/UI"; import { UIComponentEnum } from "../ui/UIComponentEnum"; import { GroupPanelComponent } from "../ui/component/GroupPanelComponent"; -declare var marauroa: any; +import { marauroa } from "marauroa" /** * handles player trade events diff --git a/src/js/stendhal/event/ImageEvent.ts b/src/js/stendhal/event/ImageEvent.ts new file mode 100644 index 0000000000..8c744e5305 --- /dev/null +++ b/src/js/stendhal/event/ImageEvent.ts @@ -0,0 +1,20 @@ +/*************************************************************************** + * (C) Copyright 2005-2026 - Stendhal * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { RPEvent } from "marauroa" + +export class ImageEvent extends RPEvent { + + public execute(_entity: any): void { + // not implemented + } + +}; diff --git a/src/js/stendhal/event/PlayerLoggedOnEvent.ts b/src/js/stendhal/event/PlayerLoggedOnEvent.ts index d24042385d..ca409baa91 100644 --- a/src/js/stendhal/event/PlayerLoggedOnEvent.ts +++ b/src/js/stendhal/event/PlayerLoggedOnEvent.ts @@ -10,9 +10,9 @@ * * ***************************************************************************/ -declare var stendhal: any; +import { stendhal } from "../stendhal"; -import { RPEvent } from "./RPEvent"; +import { RPEvent } from "marauroa" /** diff --git a/src/js/stendhal/event/PlayerLoggedOutEvent.ts b/src/js/stendhal/event/PlayerLoggedOutEvent.ts index f976a8cd64..df741f4973 100644 --- a/src/js/stendhal/event/PlayerLoggedOutEvent.ts +++ b/src/js/stendhal/event/PlayerLoggedOutEvent.ts @@ -10,9 +10,9 @@ * * ***************************************************************************/ -declare var stendhal: any; +import { stendhal } from "../stendhal"; -import { RPEvent } from "./RPEvent"; +import { RPEvent } from "marauroa" export class PlayerLoggedOutEvent extends RPEvent { diff --git a/src/js/stendhal/event/PrivateTextEvent.ts b/src/js/stendhal/event/PrivateTextEvent.ts new file mode 100644 index 0000000000..5066b5d65d --- /dev/null +++ b/src/js/stendhal/event/PrivateTextEvent.ts @@ -0,0 +1,53 @@ +/*************************************************************************** + * (C) Copyright 2005-2023 - Stendhal * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { Chat } from "util/Chat"; +import { RPEvent } from "marauroa" +import { stendhal } from "stendhal"; + +/** + * handles private text messages + */ +export class PrivateTextEvent extends RPEvent { + + public texttype!: string; + public text!: string; + public profile!: string; + + private soundTextEvents = ["privmsg", "support", "tutorial"]; + + public execute(_entity: any): void { + const ttype = this["texttype"].toLowerCase(); + const msg = this["text"].replace(/\\r\\n/g, "\n").replace(/\\r/g, "\n"); + + let profile; + if (this.hasOwnProperty("profile")) { + profile = this["profile"]; + } else if (ttype === "tutorial") { + profile = "floattingladynpc"; + } + + if (ttype === "server" && msg.includes("\n")) { + Chat.log(ttype, msg.split("\n"), undefined, profile); + } else { + // scene settings messages should not disturb playing, just create some atmosphere + const headed = ttype !== "scene_setting"; + Chat.log(ttype, msg, undefined, profile, headed); + } + + // play notification sound + const notif = stendhal.config.get("chat.private.sound"); + if (notif && this.soundTextEvents.indexOf(ttype) > -1) { + stendhal.sound.playGlobalizedEffect(notif); + } + } + +}; diff --git a/src/js/stendhal/event/ProgressStatusEvent.ts b/src/js/stendhal/event/ProgressStatusEvent.ts index 16b36e78f8..8e09564274 100644 --- a/src/js/stendhal/event/ProgressStatusEvent.ts +++ b/src/js/stendhal/event/ProgressStatusEvent.ts @@ -13,9 +13,8 @@ import { FloatingWindow } from "../ui/toolkit/FloatingWindow"; import { TravelLogDialog } from "../ui/dialog/TravelLogDialog"; import { ui } from "../ui/UI"; import { UIComponentEnum } from "../ui/UIComponentEnum"; -import { RPEvent } from "./RPEvent"; - -declare let stendhal: any; +import { RPEvent } from "marauroa" +import { stendhal } from "stendhal"; /** diff --git a/src/js/stendhal/event/ReachedAchievementEvent.ts b/src/js/stendhal/event/ReachedAchievementEvent.ts new file mode 100644 index 0000000000..c47a431275 --- /dev/null +++ b/src/js/stendhal/event/ReachedAchievementEvent.ts @@ -0,0 +1,27 @@ +/*************************************************************************** + * (C) Copyright 2005-2026 - Stendhal * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { RPEvent } from "marauroa" +import { ViewPort } from "ui/ViewPort"; + +/** + * displays an achievement banner + */ +export class ReachedAchievementEvent extends RPEvent { + + public category!: string; + public title!: string; + public description!: string; + + public execute(_entity: any): void { + ViewPort.get().addAchievementNotif(this["category"], this["title"], this["description"]); + } +}; diff --git a/src/js/stendhal/event/ShowItemListEvent.ts b/src/js/stendhal/event/ShowItemListEvent.ts new file mode 100644 index 0000000000..345a5f7620 --- /dev/null +++ b/src/js/stendhal/event/ShowItemListEvent.ts @@ -0,0 +1,124 @@ +/*************************************************************************** + * Copyright © 2024 - 2026 Faiumoni e. V. * + *************************************************************************** + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { RPEvent } from "marauroa"; + +import { Paths } from "../data/Paths"; +import { DialogContentComponent } from "../ui/toolkit/DialogContentComponent"; +import { ui } from "../ui/UI"; +import { Chat } from "../util/Chat"; + +import { htmlImageStore } from "data/HTMLImageStore"; +import { stendhal } from "../stendhal"; + + +export class ShowItemListEvent extends RPEvent { + + title!: string; + caption!: string; + content!: any; + + execute(_entity: any) { + let title = "Items"; + let caption = ""; + let items = []; + + if (this.hasOwnProperty("title")) { + title = this["title"]; + } + if (this.hasOwnProperty("caption")) { + caption = this["caption"]; + } + if (this.hasOwnProperty("content")) { + for (let obj in this["content"]) { + if (this["content"].hasOwnProperty(obj)) { + let data = this["content"][obj]["a"]; + let item = { + clazz: data["class"], + subclass: data["subclass"], + img: data["class"] + "/" + data["subclass"] + ".png", + price: data["price"], + desc: data["description_info"] + } + + // seller shops prefix prices with "-" + if (item.price.startsWith("-")) { + item.price = item.price.substr(1); + } + items.push(item); + } + } + } + + const content = new class extends DialogContentComponent { }("empty-div-template"); + content.componentElement.classList.add("shopsign"); + // Create table + const table = document.createElement("table"); + table.className = "shoptable"; + content.componentElement.appendChild(table); + + // Caption + const tableCaption = document.createElement("caption"); + tableCaption.className = "shopcaption"; + tableCaption.textContent = caption; + table.appendChild(tableCaption); + + // Header + const thead = document.createElement("thead"); + const headerRow = document.createElement("tr"); + + const headers = ["Item", "Price", "Description"]; + for (const text of headers) { + const th = document.createElement("th"); + th.textContent = text; + headerRow.appendChild(th); + } + + thead.appendChild(headerRow); + table.appendChild(thead); + + // Body + const tbody = document.createElement("tbody"); + + for (const i of items) { + const row = document.createElement("tr"); + + // Item (image) + const itemCell = document.createElement("td"); + itemCell.appendChild( + htmlImageStore.get( + Paths.sprites + "/items/" + i.img + ) + ); + row.appendChild(itemCell); + + // Price + const priceCell = document.createElement("td"); + priceCell.className = "number"; + priceCell.textContent = i.price; + row.appendChild(priceCell); + + // Description + const descCell = document.createElement("td"); + descCell.innerHTML = Chat.formatLogEntry(i.desc); + row.appendChild(descCell); + + tbody.appendChild(row); + } + + table.appendChild(tbody); + + stendhal.ui.globalInternalWindow.set( + ui.createSingletonFloatingWindow(title, content, 20, 20)); + } + +} diff --git a/src/js/stendhal/event/ShowOutfitListEvent.ts b/src/js/stendhal/event/ShowOutfitListEvent.ts new file mode 100644 index 0000000000..a0c8dc5a4c --- /dev/null +++ b/src/js/stendhal/event/ShowOutfitListEvent.ts @@ -0,0 +1,146 @@ +/*************************************************************************** + * Copyright © 2024 - 2025 Faiumoni e. V. * + *************************************************************************** + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { RPEvent } from "marauroa" + +import { ui } from "../ui/UI"; +import { DialogContentComponent } from "../ui/toolkit/DialogContentComponent"; + +import { stendhal } from "../stendhal"; + + +export class ShowOutfitListEvent extends RPEvent { + + title!: string; + caption!: string; + outfits!: string; + + execute(_entity: any) { + let title = "Outfits"; + let caption = ""; + let outfits = []; + + if (this.hasOwnProperty("title")) { + title = this["title"]; + } + if (this.hasOwnProperty("caption")) { + caption = this["caption"]; + } + if (this.hasOwnProperty("outfits")) { + for (let o of this["outfits"].split(":")) { + let parts = o.split(";"); + if (parts.length > 2) { + outfits.push({ + name: parts[0], + outfit: parts[1], + price: parts[2] + }); + } + } + } + if (this.hasOwnProperty("show_base")) { + //Chat.log("normal", this["show_base"]); + } + + const content = new class extends DialogContentComponent { }("empty-div-template"); + content.componentElement.classList.add("shopsign"); + + const table = document.createElement("table"); + table.className = "shoptable"; + content.componentElement.appendChild(table); + + // Caption + const tableCaption = document.createElement("caption"); + tableCaption.className = "shopcaption"; + tableCaption.textContent = caption; + table.appendChild(tableCaption); + + // Header + const thead = document.createElement("thead"); + const headerRow = document.createElement("tr"); + + const headers = ["Name", "Outfit", "Price"]; + for (const text of headers) { + const th = document.createElement("th"); + th.textContent = text; + headerRow.appendChild(th); + } + + thead.appendChild(headerRow); + table.appendChild(thead); + + // Body + const tbody = document.createElement("tbody"); + + for (const outfit of outfits) { + const row = document.createElement("tr"); + + // Name + const nameCell = document.createElement("td"); + nameCell.textContent = outfit.name; + row.appendChild(nameCell); + + // Image + const imageCell = document.createElement("td"); + let url = ShowOutfitListEvent.outfitToUrl(outfit.outfit); + const img1 = document.createElement("img"); + const img2 = document.createElement("img"); + img1.src = url; + img2.src = url + "&offset=0"; + imageCell.append(img1); + imageCell.append(img2); + row.appendChild(imageCell); + + // Price + const priceCell = document.createElement("td"); + priceCell.className = "number"; + priceCell.textContent = outfit.price; + row.appendChild(priceCell); + + tbody.appendChild(row); + } + + table.appendChild(tbody); + + stendhal.ui.globalInternalWindow.set( + ui.createSingletonFloatingWindow(title, content, 20, 20)); + } + + private static outfitToUrl(outfit?: string): string { + if (!outfit) { + return ""; + } + + // Required order + const order = ["body", "dress", "head", "mouth", "eyes", "mask", "hair", "hat", "detail"]; + + // Parse input into a map + let values: Record = {}; + + for (let part of outfit.split(",")) { + let [key, value] = part.split("="); + values[key] = Number(value); + } + + // Build path + let parts = order.map(key => { + let value = values[key] ?? 0; + return `${key}-${value}-0`; + }); + + let url = + "https://stendhalgame.org/createoutfit.php?rewritten=true&dialog=shop&outfit=" + + parts.join("_"); + + return url; + } +} diff --git a/src/js/stendhal/event/SoundEvent.ts b/src/js/stendhal/event/SoundEvent.ts index 7a0654847b..55382bad48 100644 --- a/src/js/stendhal/event/SoundEvent.ts +++ b/src/js/stendhal/event/SoundEvent.ts @@ -10,13 +10,13 @@ * * ***************************************************************************/ -import { RPEvent } from "./RPEvent"; +import { RPEvent } from "marauroa" import { SoundID } from "../data/sound/SoundID"; import { SoundManager } from "../data/sound/SoundManager"; -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { stendhal } from "../stendhal"; export class SoundEvent extends RPEvent { diff --git a/src/js/stendhal/event/TextEvent.ts b/src/js/stendhal/event/TextEvent.ts new file mode 100644 index 0000000000..5436fe1cc2 --- /dev/null +++ b/src/js/stendhal/event/TextEvent.ts @@ -0,0 +1,31 @@ +/*************************************************************************** + * (C) Copyright 2005-2023 - Stendhal * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { Entity } from "entity/Entity"; +import { RPEvent } from "marauroa" + +/** + * text events + */ +export class TextEvent extends RPEvent { + + public range!: string; + public text!: string; + + public execute(entity: Entity): void { + if (this["range"]) { + entity.say(this["text"], parseInt(this["range"], 10)); + } else { + entity.say(this["text"]); + } + } + +}; diff --git a/src/js/stendhal/event/TradeEvent.ts b/src/js/stendhal/event/TradeEvent.ts index 76eac52690..09bf9bac94 100644 --- a/src/js/stendhal/event/TradeEvent.ts +++ b/src/js/stendhal/event/TradeEvent.ts @@ -15,10 +15,11 @@ import { TradeDialog } from "../ui/dialog/TradeDialog"; import { UIComponentEnum } from "../ui/UIComponentEnum"; import { ui } from "../ui/UI"; -import { RPEvent } from "./RPEvent"; +import { RPEvent } from "marauroa" -declare var stendhal: any; -declare var marauroa: any; +import { stendhal } from "../stendhal"; +import { marauroa } from "marauroa" +import { Player } from "../entity/Player"; /** * handles player trade events @@ -39,7 +40,7 @@ export class TradeEvent extends RPEvent { if (dialog) { let partner = marauroa.currentZone[this["partner_id"]]; - dialog.updateTradeState(marauroa.me, partner, this["user_trade_state"], this["partner_trade_state"]); + dialog.updateTradeState(marauroa.me as Player, partner, this["user_trade_state"], this["partner_trade_state"]); } } diff --git a/src/js/stendhal/event/RPEvent.ts b/src/js/stendhal/event/TransitionGraphEvent.ts similarity index 83% rename from src/js/stendhal/event/RPEvent.ts rename to src/js/stendhal/event/TransitionGraphEvent.ts index 209ee6e5bb..fc4807c532 100644 --- a/src/js/stendhal/event/RPEvent.ts +++ b/src/js/stendhal/event/TransitionGraphEvent.ts @@ -9,11 +9,13 @@ * * ***************************************************************************/ -/** - * abstract base class for Events from the server - */ -export abstract class RPEvent { +import { RPEvent } from "marauroa" - abstract execute(entity: any): void; + +export class TransitionGraphEvent extends RPEvent { + + public execute(_entity: any): void { + // not implemented + } }; diff --git a/src/js/stendhal/event/ViewChangeEvent.ts b/src/js/stendhal/event/ViewChangeEvent.ts index f86330fd4b..8d08276148 100644 --- a/src/js/stendhal/event/ViewChangeEvent.ts +++ b/src/js/stendhal/event/ViewChangeEvent.ts @@ -10,9 +10,11 @@ * * ***************************************************************************/ -declare var stendhal: any; +import { stendhal } from "../stendhal"; -import { RPEvent } from "./RPEvent"; +import { Canvas } from "util/Types"; +import { RPEvent } from "marauroa" +import { ViewPort } from "ui/ViewPort"; /** @@ -24,13 +26,14 @@ export class ViewChangeEvent extends RPEvent { public y!: number; - execute(entity: any) { - const canvas = stendhal.ui.viewport.getElement() as HTMLCanvasElement; + execute(_entity: any) { + let viewport = ViewPort.get(); + const canvas = viewport.getElement(); queueMicrotask(() => { - stendhal.ui.viewport.freeze = true; - stendhal.ui.viewport.offsetX = this.x * stendhal.ui.viewport.targetTileWidth + viewport.freeze = true; + viewport.offsetX = this.x * viewport.targetTileWidth - Math.round(canvas.width / 2); - stendhal.ui.viewport.offsetY = this.y * stendhal.ui.viewport.targetTileHeight + viewport.offsetY = this.y * viewport.targetTileHeight - Math.round(canvas.height / 2); }); } diff --git a/src/js/stendhal/landscape/CombinedTileset.ts b/src/js/stendhal/landscape/CombinedTileset.ts index 2731927447..1689e586fd 100644 --- a/src/js/stendhal/landscape/CombinedTileset.ts +++ b/src/js/stendhal/landscape/CombinedTileset.ts @@ -9,7 +9,7 @@ * * ***************************************************************************/ -declare var stendhal: any; +import { Canvas, RenderingContext2D } from "util/Types"; /** * a combined tileset with group layers using one single tileset image. @@ -21,20 +21,19 @@ declare var stendhal: any; * We use one large tileset for the combined tiles in order to be compatible with WebGL. */ export class CombinedTileset { - public readonly canvas: HTMLCanvasElement; - public readonly ctx: CanvasRenderingContext2D; + public readonly canvas: Canvas; + public readonly ctx: RenderingContext2D; public readonly tilesPerRow: number; - constructor(numberOfTiles: number, public readonly combinedLayers: number[][]) { + constructor(numberOfTiles: number, public readonly combinedLayers: number[][], tileWidth: number, tileHeight: number) { // The original approach used an a very wide image of 32 pixel height. // But both Firefox and Chrome are limit the dimension of an image to 2^15 pixels. this.tilesPerRow = Math.ceil(Math.sqrt(numberOfTiles)); - this.canvas = document.createElement("canvas"); - this.canvas.width = stendhal.data.map.tileWidth * this.tilesPerRow; - this.canvas.height = stendhal.data.map.tileHeight * this.tilesPerRow; + this.canvas = new OffscreenCanvas( + tileWidth * this.tilesPerRow, + tileHeight * this.tilesPerRow); this.ctx = this.canvas.getContext("2d")!; } - } diff --git a/src/js/stendhal/landscape/CombinedTilesetFactory.ts b/src/js/stendhal/landscape/CombinedTilesetFactory.ts index 245266fdf0..9060aa6862 100644 --- a/src/js/stendhal/landscape/CombinedTilesetFactory.ts +++ b/src/js/stendhal/landscape/CombinedTilesetFactory.ts @@ -9,12 +9,13 @@ * * ***************************************************************************/ +import { TileMap } from "../data/TileMap"; import { CombinedTileset } from "./CombinedTileset"; import { CombinedTilesetImageLoader } from "./CombinedTilesetImageLoader"; export class CombinedTilesetFactory { - constructor(private map: any) {} + constructor(private map: TileMap) {} combine(): CombinedTileset { let combinedTilesToIndex: Map = new Map(); @@ -33,7 +34,15 @@ export class CombinedTilesetFactory { for (let group = 0; group < this.map.layerGroupIndexes.length; group++) { let layers = this.map.layerGroupIndexes[group]; + let blendLayer = this.map.layerNames.indexOf(this.map.blendLayers[group]); let combinedTile = [] + + if (blendLayer && this.map.layers[blendLayer]) { + combinedTile.push(this.map.layers[blendLayer][y * this.map.zoneSizeX + x]); + } else { + combinedTile.push(0); + } + for (let layer of layers) { let gid = this.map.layers[layer][y * this.map.zoneSizeX + x]; if (gid > 0) { @@ -43,7 +52,8 @@ export class CombinedTilesetFactory { // most of the tiles on the roof layer are empty, so we add a special case here // to skip drawing completely transparent tiles later. - if (combinedTile.length === 0) { + // In this case, we ignore the entry on the blend layer + if (combinedTile.length <= 1) { combinedLayers[group][y * this.map.zoneSizeX + x] = -1 continue; } @@ -61,7 +71,7 @@ export class CombinedTilesetFactory { } } } - let combinedTileset = new CombinedTileset(combinedTilesToIndex.size, combinedLayers); + let combinedTileset = new CombinedTileset(combinedTilesToIndex.size, combinedLayers, this.map.tileWidth, this.map.tileHeight); // console.log(this.map, combinedTilesToIndex, combinedLayers); // document.getElementsByTagName("body")[0].append(combinedTileset.canvas); diff --git a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts index 8c792338bc..548ace3331 100644 --- a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts +++ b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2022-2023 - Faiumoni e. V. * + * (C) Copyright 2022-2025 - Faiumoni e. V. * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,25 +9,42 @@ * * ***************************************************************************/ -import { TileStore } from "../data/TileStore"; +import { Canvas, RenderingContext2D } from "util/Types"; +import { TileMap } from "../data/TileMap"; import { MapOfSets } from "../util/MapOfSets"; import { CombinedTileset } from "./CombinedTileset"; +import { Debug } from "../util/Debug"; -declare var stendhal: any; +import { stendhal } from "../stendhal"; +import { images } from "sprite/image/ImageManager"; +import { ImageRef } from "sprite/image/ImageRef"; +import { ViewPort } from "ui/ViewPort"; export class CombinedTilesetImageLoader { private tileUsedAtIndex!: MapOfSets - private tilesetImages: HTMLImageElement[] = []; - private animations: any = {}; - private landscapeAnimationMap: any; + private tilesetImages: ImageRef[] = []; + private drawCanvas: Canvas; + private drawCtx: RenderingContext2D; + private blendCanvas: Canvas; + private blendCtx: RenderingContext2D; + private blendFixCanvas: Canvas; + private blendFixCtx: RenderingContext2D; + private loadedTilesetCount = 0; + private usedTilesetCount = 0; constructor( - private map: any, + private map: TileMap, private indexToCombinedTiles: Map, private combinedTileset: CombinedTileset) { - this.landscapeAnimationMap = TileStore.get().getLandscapeMap(); - } + + this.drawCanvas = new OffscreenCanvas(this.map.tileWidth, this.map.tileHeight); + this.blendCanvas = new OffscreenCanvas(this.map.tileWidth, this.map.tileHeight); + this.blendFixCanvas = new OffscreenCanvas(this.map.tileWidth, this.map.tileHeight); + this.drawCtx = this.drawCanvas.getContext("2d")!; + this.blendCtx = this.blendCanvas.getContext("2d")!; + this.blendFixCtx = this.blendFixCanvas.getContext("2d")!; + } private calculateTileUsedAtIndex(): void { @@ -50,29 +67,34 @@ export class CombinedTilesetImageLoader { } - private loadTileset(tileset: number) { - let img = document.createElement("img"); - img.onload = () => { - this.drawTileset(tileset); - } - + private async loadTileset(tileset: number) { const tsname = this.map.tilesetFilenames[tileset]; - img.src = tsname + "?v=" + stendhal.data.build.version; - - if (this.landscapeAnimationMap) { - const animation = this.landscapeAnimationMap[tsname]; - if (animation) { - this.animations[tileset] = animation; - } + if (!tsname) { + return; } + let imageRef = images.load(tsname); + await imageRef.waitFor(); + this.tilesetImages[tileset] = imageRef; + this.loadedTilesetCount++; + this.drawTileset(tileset); + if (this.loadedTilesetCount >= this.usedTilesetCount) { + this.finish(); + } + } - this.tilesetImages[tileset] = img; + private finish() { + for (let imageRef of this.tilesetImages) { + imageRef.free(); + } } private drawTileset(tileset: number): void { let firstGid = this.map.firstgids[tileset]; - let image = this.tilesetImages[tileset]; + let image = this.tilesetImages[tileset].image; + if (!image) { + return; + } let numberOfTiles = image.width / this.map.tileWidth * image.height / this.map.tileHeight; let lastGid = firstGid + numberOfTiles; @@ -96,36 +118,85 @@ export class CombinedTilesetImageLoader { const pixelX = x * this.map.tileWidth; const pixelY = y * this.map.tileHeight; - this.combinedTileset.ctx.clearRect(pixelX, pixelY, this.map.tileWidth, this.map.tileHeight); - for (let tile of tiles) { - let flip = tile & 0xE0000000; - let gid = tile & 0x1FFFFFFF; - let tileset = this.map.getTilesetForGid(gid); - let image = this.tilesetImages[tileset]; - if (!image || !image.height) { - continue; - } - - let base = this.map.firstgids[tileset]; - let tileIndexInTileset = gid - base; - this.drawTile(pixelX, pixelY, image, tileIndexInTileset, flip); - + if (Debug.isActive("light") && ViewPort.get().HSLFilter) { + this.drawCombinedTileWithBlend(tiles, pixelX, pixelY); + } else { + this.drawCombinedTileWithoutBlend(this.combinedTileset.ctx, tiles, pixelX, pixelY); } } - private drawTile(pixelX: number, pixelY: number, tilesetImage: HTMLImageElement, tileIndexInTileset: number, flip: number) { + private drawCombinedTileWithoutBlend(ctx: RenderingContext2D, tiles: number[], pixelX: number, pixelY: number) { + ctx.clearRect(pixelX, pixelY, this.map.tileWidth, this.map.tileHeight); + for (let [i, tile] of tiles.entries()) { + if (i === 0) { + continue; // skip blend layer + } + this.drawTile(ctx, tile, pixelX, pixelY); + } + } + + private drawCombinedTileWithBlend(tiles: number[], pixelX: number, pixelY: number) { + + this.drawCombinedTileWithoutBlend(this.drawCtx, tiles, 0, 0); + + // copy the image to the blend canvas + this.blendCtx.globalCompositeOperation = "source-over"; + this.blendCtx.clearRect(0, 0, this.map.tileWidth, this.map.tileHeight); + this.blendCtx.drawImage(this.drawCanvas, 0, 0); + + this.blendCtx.globalCompositeOperation = "multiply"; + this.blendCtx.fillStyle = ViewPort.get().HSLFilter!; + this.blendCtx.fillRect(0, 0, this.map.tileWidth, this.map.tileHeight); + + let tile = tiles[0]; + this.blendCtx.globalCompositeOperation = "soft-light"; + if (tile > 0) { + // turn transparency into black for the blend layer + this.blendFixCtx.fillStyle = "black"; + this.blendFixCtx.fillRect(0, 0, this.map.tileWidth, this.map.tileHeight); + this.drawTile(this.blendFixCtx, tile, 0, 0); + + // draw the blend layer onto the blend canvas + this.blendCtx.drawImage(this.blendFixCanvas, 0, 0); + } else { + this.blendCtx.fillStyle = "black"; + this.blendCtx.fillRect(0, 0, this.map.tileWidth, this.map.tileHeight); + } + + // restore transparency which was lost during multiply and soft-light composition + this.blendCtx.globalCompositeOperation = "destination-in"; + this.blendCtx.drawImage(this.drawCanvas, 0, 0); + + // draw the blend canvas at the correct position onto the combined tileset + this.combinedTileset.ctx.clearRect(pixelX, pixelY, this.map.tileWidth, this.map.tileHeight); + this.combinedTileset.ctx.drawImage(this.blendCanvas, pixelX, pixelY); + } + + private drawTile(ctx: RenderingContext2D, tile: number, pixelX: number, pixelY: number) { + let flip = tile & 0xE0000000; + let gid = tile & 0x1FFFFFFF; + let tileset = this.map.getTilesetForGid(gid); + let image = this.tilesetImages[tileset]?.image; + if (!image) { + return; + } + + let base = this.map.firstgids[tileset]; + let tileIndexInTileset = gid - base; + this.drawImageTile(ctx, pixelX, pixelY, image, tileIndexInTileset, flip); + } + + private drawImageTile(ctx: RenderingContext2D, pixelX: number, pixelY: number, tilesetImage: ImageBitmap, tileIndexInTileset: number, flip: number) { const tilesetWidth = tilesetImage.width; const tilesPerRow = Math.floor(tilesetWidth / this.map.tileWidth); - const ctx = this.combinedTileset.ctx; - if (flip === 0) { ctx.drawImage(tilesetImage, - (tileIndexInTileset % tilesPerRow) * this.map.tileWidth, - Math.floor(tileIndexInTileset / tilesPerRow) * this.map.tileHeight, - this.map.tileWidth, this.map.tileHeight, - pixelX, pixelY, - this.map.tileWidth, this.map.tileHeight); + (tileIndexInTileset % tilesPerRow) * this.map.tileWidth, + Math.floor(tileIndexInTileset / tilesPerRow) * this.map.tileHeight, + this.map.tileWidth, this.map.tileHeight, + pixelX, pixelY, + this.map.tileWidth, this.map.tileHeight); } else { ctx.translate(pixelX, pixelY); @@ -154,7 +225,12 @@ export class CombinedTilesetImageLoader { restore.push([0, 1, 1, 0, 0, 0]); } - this.drawTile(0, 0, tilesetImage, tileIndexInTileset, 0); + ctx.drawImage(tilesetImage, + (tileIndexInTileset % tilesPerRow) * this.map.tileWidth, + Math.floor(tileIndexInTileset / tilesPerRow) * this.map.tileHeight, + this.map.tileWidth, this.map.tileHeight, + 0, 0, + this.map.tileWidth, this.map.tileHeight); restore.reverse(); for (const args of restore) { @@ -168,9 +244,9 @@ export class CombinedTilesetImageLoader { console.log("CombinedTilesetImageLoader.load()"); this.calculateTileUsedAtIndex(); let usedTilesets = this.calculateUsedTilesets(this.tileUsedAtIndex.keys()); + this.usedTilesetCount = usedTilesets.size; for (let tileset of usedTilesets) { this.loadTileset(tileset); } - } } diff --git a/src/js/stendhal/landscape/CombinedTilesetRenderingStrategy.ts b/src/js/stendhal/landscape/CombinedTilesetRenderingStrategy.ts new file mode 100644 index 0000000000..0ccf32b68b --- /dev/null +++ b/src/js/stendhal/landscape/CombinedTilesetRenderingStrategy.ts @@ -0,0 +1,90 @@ +/*************************************************************************** + * (C) Copyright 2022-2026 - Faiumoni e. V. * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { LandscapeRenderingStrategy } from "./LandscapeRenderingStrategy"; +import { Canvas, RenderingContext2D } from "util/Types"; +import { TileMap } from "../data/TileMap"; +import { CombinedTilesetFactory } from "./CombinedTilesetFactory"; +import { CombinedTileset } from "./CombinedTileset"; + +export class CombinedTilesetRenderingStrategy extends LandscapeRenderingStrategy { + private combinedTileset?: CombinedTileset; + private map!: TileMap; + + public onMapLoaded(map: TileMap): void { + this.map = map; + let combinedTilesetFactory = new CombinedTilesetFactory(map); + this.combinedTileset = combinedTilesetFactory.combine(); + } + + public onTilesetLoaded(): void { + let body = document.getElementById("body")!; + body.style.cursor = "auto"; + } + + public render( + canvas: Canvas, gamewindow: any, + tileOffsetX: number, tileOffsetY: number, targetTileWidth: number, targetTileHeight: number): void { + + this.drawLayer( + canvas, + this.combinedTileset, + 0, + tileOffsetX, tileOffsetY, targetTileWidth, targetTileHeight); + + gamewindow.drawEntities(); + + this.drawLayer( + canvas, + this.combinedTileset, + 1, + tileOffsetX, tileOffsetY, targetTileWidth, targetTileHeight); + } + + drawLayer( + canvas: Canvas, + combinedTileset: CombinedTileset|undefined, layerNo: number, + tileOffsetX: number, tileOffsetY: number, targetTileWidth: number, targetTileHeight: number): void { + if (!combinedTileset) { + return; + } + let ctx = canvas.getContext("2d")! as RenderingContext2D; + + const layer = combinedTileset.combinedLayers[layerNo]; + const yMax = Math.min(tileOffsetY + canvas.height / targetTileHeight + 1, this.map.zoneSizeY); + const xMax = Math.min(tileOffsetX + canvas.width / targetTileWidth + 1, this.map.zoneSizeX); + + for (let y = tileOffsetY; y < yMax; y++) { + for (let x = tileOffsetX; x < xMax; x++) { + let index = layer[y * this.map.zoneSizeX + x]; + if (index > -1) { + + try { + const pixelX = x * targetTileWidth; + const pixelY = y * targetTileHeight; + + ctx.drawImage(combinedTileset.canvas, + + (index % combinedTileset.tilesPerRow) * this.map.tileWidth, + Math.floor(index / combinedTileset.tilesPerRow) * this.map.tileHeight, + + this.map.tileWidth, this.map.tileHeight, + pixelX, pixelY, + targetTileWidth, targetTileHeight); + } catch (e) { + console.error(e); + } + } + } + } + } + +} diff --git a/src/js/stendhal/landscape/IndividualTilesetRenderingStrategy.ts b/src/js/stendhal/landscape/IndividualTilesetRenderingStrategy.ts index 60ed83c047..8de9384643 100644 --- a/src/js/stendhal/landscape/IndividualTilesetRenderingStrategy.ts +++ b/src/js/stendhal/landscape/IndividualTilesetRenderingStrategy.ts @@ -9,17 +9,21 @@ * * ***************************************************************************/ -declare var stendhal: any; +import { stendhal } from "../stendhal"; import { LandscapeRenderingStrategy } from "./LandscapeRenderingStrategy"; -import { ImagePreloader } from "../data/ImagePreloader"; +import { ImageCache } from "../sprite/image/ImageCache"; import { Chat } from "../util/Chat"; +import { TileMap } from "../data/TileMap"; +import { Canvas, RenderingContext2D } from "util/Types"; export class IndividualTilesetRenderingStrategy extends LandscapeRenderingStrategy { private targetTileWidth = 32; private targetTileHeight = 32; + private imageCache = new ImageCache(); + private map!: TileMap; constructor() { super(); @@ -28,27 +32,29 @@ export class IndividualTilesetRenderingStrategy extends LandscapeRenderingStrate }, 1000); } - public onMapLoaded(_map: any): void { + public onMapLoaded(map: TileMap): void { // do nothing console.log("Using IndividualTilesetRenderingStrategy.") + this.map = map; + this.imageCache.close(); + this.imageCache = new ImageCache(); + this.imageCache.load(map.tilesetFilenames); } public onTilesetLoaded(): void { - new ImagePreloader(stendhal.data.map.tilesetFilenames, function() { - let body = document.getElementById("body")!; - body.style.cursor = "auto"; - }); + let body = document.getElementById("body")!; + body.style.cursor = "auto"; } public render( - canvas: HTMLCanvasElement, gamewindow: any, + canvas: Canvas, gamewindow: any, tileOffsetX: number, tileOffsetY: number, targetTileWidth: number, targetTileHeight: number): void { this.targetTileWidth = targetTileWidth; this.targetTileHeight = targetTileHeight; - for (var drawingLayer=0; drawingLayer < stendhal.data.map.layers.length; drawingLayer++) { - var name = stendhal.data.map.layerNames[drawingLayer]; + for (var drawingLayer=0; drawingLayer < this.map.layers.length; drawingLayer++) { + var name = this.map.layerNames[drawingLayer]; if (name !== "protection" && name !== "collision" && name !== "objects" && name !== "blend_ground" && name !== "blend_roof") { this.paintLayer(canvas, drawingLayer, tileOffsetX, tileOffsetY); @@ -59,27 +65,28 @@ export class IndividualTilesetRenderingStrategy extends LandscapeRenderingStrate } } - private paintLayer(canvas: HTMLCanvasElement, drawingLayer: number, + private paintLayer(canvas: Canvas, drawingLayer: number, tileOffsetX: number, tileOffsetY: number) { - const layer = stendhal.data.map.layers[drawingLayer]; - const yMax = Math.min(tileOffsetY + canvas.height / this.targetTileHeight + 1, stendhal.data.map.zoneSizeY); - const xMax = Math.min(tileOffsetX + canvas.width / this.targetTileWidth + 1, stendhal.data.map.zoneSizeX); - let ctx = canvas.getContext("2d")!; + const layer = this.map.layers[drawingLayer]; + const yMax = Math.min(tileOffsetY + canvas.height / this.targetTileHeight + 1, this.map.zoneSizeY); + const xMax = Math.min(tileOffsetX + canvas.width / this.targetTileWidth + 1, this.map.zoneSizeX); + let ctx = canvas.getContext("2d")! as RenderingContext2D; for (let y = tileOffsetY; y < yMax; y++) { for (let x = tileOffsetX; x < xMax; x++) { - let gid = layer[y * stendhal.data.map.zoneSizeX + x]; + let gid = layer[y * this.map.zoneSizeX + x]; const flip = gid & 0xE0000000; gid &= 0x1FFFFFFF; if (gid > 0) { - const tileset = stendhal.data.map.getTilesetForGid(gid); - const base = stendhal.data.map.firstgids[tileset]; + const tileset = this.map.getTilesetForGid(gid); + const base = this.map.firstgids[tileset]; const idx = gid - base; try { - if (stendhal.data.map.aImages[tileset].height > 0) { - this.drawTile(ctx, stendhal.data.map.aImages[tileset], idx, x, y, flip); + let image = this.imageCache.images[this.map.tilesetFilenames[tileset]]; + if (image) { + this.drawTile(ctx, image, idx, x, y, flip); } } catch (e) { console.error(e); @@ -89,17 +96,17 @@ export class IndividualTilesetRenderingStrategy extends LandscapeRenderingStrate } } - private drawTile(ctx: CanvasRenderingContext2D, tileset: HTMLImageElement, idx: number, x: number, y: number, flip = 0) { + private drawTile(ctx: RenderingContext2D, tileset: ImageBitmap, idx: number, x: number, y: number, flip = 0) { const tilesetWidth = tileset.width; - const tilesPerRow = Math.floor(tilesetWidth / stendhal.data.map.tileWidth); + const tilesPerRow = Math.floor(tilesetWidth / this.map.tileWidth); const pixelX = x * this.targetTileWidth; const pixelY = y * this.targetTileHeight; if (flip === 0) { ctx.drawImage(tileset, - (idx % tilesPerRow) * stendhal.data.map.tileWidth, - Math.floor(idx / tilesPerRow) * stendhal.data.map.tileHeight, - stendhal.data.map.tileWidth, stendhal.data.map.tileHeight, + (idx % tilesPerRow) * this.map.tileWidth, + Math.floor(idx / tilesPerRow) * this.map.tileHeight, + this.map.tileWidth, this.map.tileHeight, pixelX, pixelY, this.targetTileWidth, this.targetTileHeight); } else { diff --git a/src/js/stendhal/landscape/LandscapeRenderer.ts b/src/js/stendhal/landscape/LandscapeRenderer.ts deleted file mode 100644 index be00690247..0000000000 --- a/src/js/stendhal/landscape/LandscapeRenderer.ts +++ /dev/null @@ -1,56 +0,0 @@ -/*************************************************************************** - * (C) Copyright 2022-2023 - Faiumoni e. V. * - *************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU Affero General Public License as * - * published by the Free Software Foundation; either version 3 of the * - * License, or (at your option) any later version. * - * * - ***************************************************************************/ - -import { CombinedTileset } from "./CombinedTileset"; - -declare var stendhal: any; - -export class LandscapeRenderer { - - drawLayer( - canvas: HTMLCanvasElement, - combinedTileset: CombinedTileset, layerNo: number, - tileOffsetX: number, tileOffsetY: number, targetTileWidth: number, targetTileHeight: number): void { - if (!combinedTileset) { - return; - } - let ctx = canvas.getContext("2d")!; - - const layer = combinedTileset.combinedLayers[layerNo]; - const yMax = Math.min(tileOffsetY + canvas.height / targetTileHeight + 1, stendhal.data.map.zoneSizeY); - const xMax = Math.min(tileOffsetX + canvas.width / targetTileWidth + 1, stendhal.data.map.zoneSizeX); - - for (let y = tileOffsetY; y < yMax; y++) { - for (let x = tileOffsetX; x < xMax; x++) { - let index = layer[y * stendhal.data.map.zoneSizeX + x]; - if (index > -1) { - - try { - const pixelX = x * targetTileWidth; - const pixelY = y * targetTileHeight; - - ctx.drawImage(combinedTileset.canvas, - - (index % combinedTileset.tilesPerRow) * stendhal.data.map.tileWidth, - Math.floor(index / combinedTileset.tilesPerRow) * stendhal.data.map.tileHeight, - - stendhal.data.map.tileWidth, stendhal.data.map.tileHeight, - pixelX, pixelY, - targetTileWidth, targetTileHeight); - } catch (e) { - console.error(e); - } - } - } - } - } - -} diff --git a/src/js/stendhal/landscape/LandscapeRenderingStrategy.ts b/src/js/stendhal/landscape/LandscapeRenderingStrategy.ts index 61db62bf4c..2d6502edbd 100644 --- a/src/js/stendhal/landscape/LandscapeRenderingStrategy.ts +++ b/src/js/stendhal/landscape/LandscapeRenderingStrategy.ts @@ -9,54 +9,21 @@ * * ***************************************************************************/ +import { Canvas } from "util/Types"; +import { TileMap } from "../data/TileMap"; import { CombinedTilesetFactory } from "./CombinedTilesetFactory"; -import { LandscapeRenderer } from "./LandscapeRenderer"; -declare var stendhal: any; +import { stendhal } from "../stendhal"; export abstract class LandscapeRenderingStrategy { - public abstract onMapLoaded(map: any): void; + public abstract onMapLoaded(map: TileMap): void; public abstract onTilesetLoaded(): void; public abstract render( - canvas: HTMLCanvasElement, gamewindow: any, + canvas: Canvas, gamewindow: any, tileOffsetX: number, tileOffsetY: number, targetTileWidth: number, targetTileHeight: number): void; } - -export class CombinedTilesetRenderingStrategy extends LandscapeRenderingStrategy { - - public onMapLoaded(map: any): void { - let combinedTilesetFactory = new CombinedTilesetFactory(map); - stendhal.data.map.combinedTileset = combinedTilesetFactory.combine(); - } - - public onTilesetLoaded(): void { - let body = document.getElementById("body")!; - body.style.cursor = "auto"; - } - - public render( - canvas: HTMLCanvasElement, gamewindow: any, - tileOffsetX: number, tileOffsetY: number, targetTileWidth: number, targetTileHeight: number): void { - - let landscapeRenderder = new LandscapeRenderer(); - landscapeRenderder.drawLayer( - canvas, - stendhal.data.map.combinedTileset, - 0, - tileOffsetX, tileOffsetY, targetTileWidth, targetTileHeight); - - gamewindow.drawEntities(); - - landscapeRenderder.drawLayer( - canvas, - stendhal.data.map.combinedTileset, - 1, - tileOffsetX, tileOffsetY, targetTileWidth, targetTileHeight); - } - -} diff --git a/src/js/stendhal/landscape/ParallaxBackground.ts b/src/js/stendhal/landscape/ParallaxBackground.ts index f32472d943..574132be25 100644 --- a/src/js/stendhal/landscape/ParallaxBackground.ts +++ b/src/js/stendhal/landscape/ParallaxBackground.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright © 2024 - Faiumoni e. V. * + * Copyright © 2024-2026 - Faiumoni e. V. * *************************************************************************** *************************************************************************** * * @@ -10,8 +10,10 @@ * * ***************************************************************************/ +import { RenderingContext2D } from "util/Types"; import { singletons } from "../SingletonRepo"; - +import { ImageRef } from "sprite/image/ImageRef"; +import { images } from "sprite/image/ImageManager"; export class ParallaxBackground { @@ -19,7 +21,7 @@ export class ParallaxBackground { public static readonly SCROLL = 0.25; /** Tiled image to be drawn. */ - private image?: HTMLImageElement; + private imageRef?: ImageRef; /** Singleton instance. */ private static instance: ParallaxBackground; @@ -54,8 +56,8 @@ export class ParallaxBackground { * Map pixel height. */ setImage(name: string, width: number, height: number) { - const fullPath = singletons.getPaths().parallax + "/" + name + ".png"; - this.image = singletons.getSpriteStore().get(fullPath); + let fullPath = singletons.getPaths().parallax + "/" + name + ".png"; + this.imageRef = images.load(fullPath); /* FIXME: //this.image = singletons.getTileStore().getParallax(name, ParallaxBackground.SCROLL, width, height); @@ -72,20 +74,22 @@ export class ParallaxBackground { * Unsets parallax background image. */ reset() { - this.image = undefined; + this.imageRef?.free(); + this.imageRef = undefined; } - draw(ctx: CanvasRenderingContext2D, offsetX: number, offsetY: number) { - if (!this.image || !this.image.height) { + draw(ctx: RenderingContext2D, offsetX: number, offsetY: number) { + if (!this.imageRef?.image) { return; } + let image = this.imageRef.image; // FIXME: seams are visible when walking - let dy = offsetY - ((offsetY / 4) % this.image.height); - for (dy; dy < this.image.height * 100; dy += this.image.height) { - let dx = offsetX - ((offsetX / 4) % this.image.width); - for (dx; dx < this.image.width * 100; dx += this.image.width) { - ctx.drawImage(this.image, dx, dy); + let dy = offsetY - ((offsetY / 4) % image.height); + for (dy; dy < image.height * 100; dy += image.height) { + let dx = offsetX - ((offsetX / 4) % image.width); + for (dx; dx < image.width * 100; dx += image.width) { + ctx.drawImage(image, dx, dy); } } diff --git a/src/js/stendhal/license.js b/src/js/stendhal/license.js index 998fb61401..86e2af4ccb 100644 --- a/src/js/stendhal/license.js +++ b/src/js/stendhal/license.js @@ -1,7 +1,7 @@ /** * @license *************************************************************************** - * (C) Copyright 2003-2023 - Stendhal * + * (C) Copyright 2003-2026 - Stendhal * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * diff --git a/src/js/stendhal/main.ts b/src/js/stendhal/main.ts index ac2046f89e..55aa254ebf 100644 --- a/src/js/stendhal/main.ts +++ b/src/js/stendhal/main.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2024 - Stendhal * + * (C) Copyright 2003-2026 - Stendhal * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,18 +9,22 @@ * * ***************************************************************************/ +import { marauroa } from "marauroa"; +import { stendhal } from "./stendhal"; import { Client } from "./Client"; -declare var stendhal: any; +declare var require: any; +(window as any)["Zlib"] = require("marauroa/inflate").Zlib; +let build = require("marauroa/build"); +require("./data/sha3"); + /** * Initializes "stendhal" object. */ function initGlobals() { - const win = window as any; - //win.marauroa = win.marauroa || {}; // marauroa object should already be intialized - win.stendhal = win.stendhal || {}; + build.version(marauroa, stendhal); stendhal.main = Client.get(); } @@ -30,6 +34,6 @@ function initGlobals() { stendhal.main.init(); - document.addEventListener('DOMContentLoaded', stendhal.main.startup); - window.addEventListener('error', stendhal.main.onerror); + document.addEventListener('DOMContentLoaded', () => stendhal.main.startup()); + window.addEventListener('error', (error: ErrorEvent) => stendhal.main.onError(error)); })(); diff --git a/src/js/stendhal/sprite/AchievementBanner.ts b/src/js/stendhal/sprite/AchievementBanner.ts index 3ba28b0d5c..55d8f85b41 100644 --- a/src/js/stendhal/sprite/AchievementBanner.ts +++ b/src/js/stendhal/sprite/AchievementBanner.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright © 2003-2024 - Faiumoni e. V. * + * Copyright © 2003-2026 - Faiumoni e. V. * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,11 +9,13 @@ * * ***************************************************************************/ -import { TextBubble } from "./TextBubble"; +import { htmlImageStore } from "data/HTMLImageStore"; +import { ViewPort } from "ui/ViewPort"; +import { RenderingContext2D } from "util/Types"; +import { Paths } from "../data/Paths"; +import { stendhal } from "../stendhal"; import { BackgroundPainter } from "../util/BackgroundPainter"; - -declare const stendhal: any; - +import { TextBubble } from "./TextBubble"; export class AchievementBanner extends TextBubble { @@ -32,9 +34,9 @@ export class AchievementBanner extends TextBubble { constructor(cat: string, title: string, desc: string) { super(desc); this.title = title; - const bg = stendhal.data.sprites.get(stendhal.paths.gui + "/banner_background.png"); + const bg = htmlImageStore.get(Paths.gui + "/banner_background.png"); this.banner = new BackgroundPainter(bg); - this.icon = stendhal.data.sprites.get(stendhal.paths.achievements + this.icon = htmlImageStore.get(Paths.achievements + "/" + cat.toLowerCase() + ".png"); /* keep achievements on the screen a bit longer since they @@ -42,8 +44,7 @@ export class AchievementBanner extends TextBubble { */ this.duration = TextBubble.STANDARD_DUR * 4; - const gamewindow = - document.getElementById("viewport")!; + const gamewindow = document.getElementById("viewport")! as HTMLCanvasElement; const td = this.getTextDimensions(gamewindow.getContext("2d")!); this.innerWidth = td.width + this.padding; // add padding between icon & text @@ -55,9 +56,10 @@ export class AchievementBanner extends TextBubble { this.y = gamewindow.height - this.height; } - override draw(ctx: CanvasRenderingContext2D): boolean { - const targetX = stendhal.ui.gamewindow.offsetX + this.x; - const targetY = stendhal.ui.gamewindow.offsetY + this.y; + override draw(ctx: RenderingContext2D): boolean { + let viewPort = ViewPort.get(); + const targetX = viewPort.offsetX + this.x; + const targetY = viewPort.offsetY + this.y; const iconX = targetX + (this.width / 2) - (this.innerWidth / 2); const iconY = targetY + (this.height / 2) - (this.icon.height * 0.75); @@ -81,7 +83,7 @@ export class AchievementBanner extends TextBubble { return this.expired(); } - private getTextDimensions(ctx: CanvasRenderingContext2D): any { + private getTextDimensions(ctx: RenderingContext2D): any { const ret = {} as any; ctx.font = this.font; let m = ctx.measureText(this.text); @@ -95,10 +97,10 @@ export class AchievementBanner extends TextBubble { } override getX(): number { - return stendhal.ui.gamewindow.offsetX + this.x; + return ViewPort.get().offsetX + this.x; } override getY(): number { - return stendhal.ui.gamewindow.offsetY + this.y; + return ViewPort.get().offsetY + this.y; } } diff --git a/src/js/stendhal/sprite/ActivityIndicatorSprite.ts b/src/js/stendhal/sprite/ActivityIndicatorSprite.ts index 02ac50813b..0f1a2bf67f 100644 --- a/src/js/stendhal/sprite/ActivityIndicatorSprite.ts +++ b/src/js/stendhal/sprite/ActivityIndicatorSprite.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright © 2024 - Faiumoni e. V. * + * Copyright © 2024-2026 - Faiumoni e. V. * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,9 +9,12 @@ * * ***************************************************************************/ +import { RenderingContext2D } from "util/Types"; import { Paths } from "../data/Paths"; -declare var stendhal: any; +import { stendhal } from "../stendhal"; +import { ImageRef } from "./image/ImageRef"; +import { images } from "./image/ImageManager"; /** @@ -19,7 +22,7 @@ declare var stendhal: any; */ export class ActivityIndicatorSprite { - private static readonly img = stendhal.data.sprites.get(Paths.sprites + "/ideas/activity.png"); + private imageRef: ImageRef; private frameIdx = 0; private lastFrameUpdate = 0; @@ -28,13 +31,12 @@ export class ActivityIndicatorSprite { constructor() { this.animate = stendhal.config.getBoolean("activity-indicator.animate"); + this.imageRef = images.load(Paths.sprites + "/ideas/activity.png"); } /** * Draws an indicator. * - * TODO: add option enable/disable animation - * * @param dx * Pixel position where parent object is drawn on X axis. * @param dy @@ -42,23 +44,24 @@ export class ActivityIndicatorSprite { * @param width * Pixel width of parent object. */ - public draw(ctx: CanvasRenderingContext2D, dx: number, dy: number, width: number) { - if (!ActivityIndicatorSprite.img.complete) { + public draw(ctx: RenderingContext2D, dx: number, dy: number, width: number) { + let image = this.imageRef.image; + if (!image) { return; } // NOTE: indicator image should be square - const dim = ActivityIndicatorSprite.img.height; + let dim = image.height; if (this.animate) { - const cycleStart = Date.now(); + let cycleStart = Date.now(); if (this.lastFrameUpdate == 0) { this.lastFrameUpdate = cycleStart; } if (cycleStart - this.lastFrameUpdate > 150) { this.frameIdx++; - if ((this.frameIdx + 1) * dim >= ActivityIndicatorSprite.img.width) { + if ((this.frameIdx + 1) * dim >= image.width) { this.frameIdx = 0; } this.lastFrameUpdate = cycleStart; @@ -66,6 +69,10 @@ export class ActivityIndicatorSprite { } // draw in upper-right of target area - ctx.drawImage(ActivityIndicatorSprite.img, dim * this.frameIdx, 0, dim, dim, dx+width-dim, dy, dim, dim); + ctx.drawImage(image, dim * this.frameIdx, 0, dim, dim, dx+width-dim, dy, dim, dim); + } + + free() { + this.imageRef.free(); } } diff --git a/src/js/stendhal/sprite/EmojiSprite.ts b/src/js/stendhal/sprite/EmojiSprite.ts index d3abc01580..e81c22dd28 100644 --- a/src/js/stendhal/sprite/EmojiSprite.ts +++ b/src/js/stendhal/sprite/EmojiSprite.ts @@ -9,6 +9,7 @@ * * ***************************************************************************/ +import { RenderingContext2D } from "util/Types"; import { RPEntity } from "../entity/RPEntity"; @@ -24,7 +25,7 @@ export class EmojiSprite { this.sprite = sprite; } - draw(ctx: CanvasRenderingContext2D): boolean { + draw(ctx: RenderingContext2D): boolean { let x = this.owner["_x"] * 32 - 16; let y = this.owner["_y"] * 32 - 32; if (x < 0) { diff --git a/src/js/stendhal/sprite/EmptySprite.ts b/src/js/stendhal/sprite/EmptySprite.ts deleted file mode 100644 index 8ceac7cfdb..0000000000 --- a/src/js/stendhal/sprite/EmptySprite.ts +++ /dev/null @@ -1,113 +0,0 @@ -/*************************************************************************** - * (C) Copyright 2003-2023 - Stendhal * - *************************************************************************** - *************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU Affero General Public License as * - * published by the Free Software Foundation; either version 3 of the * - * License, or (at your option) any later version. * - * * - ***************************************************************************/ - -import { Sprite } from "./Sprite"; - - -/** - * An empty (non-drawing) sprite. - */ -export class EmptySprite { - - public constructor(private width: number, private height: number, private reference?: object) { - - } - - /** - * Create a sub-region of this sprite. NOTE: This does not use - * caching. - * - * @param x - * The starting X coordinate. - * @param y - * The starting Y coordinate. - * @param width - * The region width. - * @param height - * The region height. - * @param ref - * The sprite reference. - * - * @return A new sprite. - */ - createRegion(x: number, y: number, width: number, height: number, ref: object): Sprite { - return new EmptySprite(width, height, ref); - } - - /** - * Draw the sprite onto the graphics context provided. - * - * @param g - * The graphics context on which to draw the sprite - * @param x - * The x location at which to draw the sprite - * @param y - * The y location at which to draw the sprite - */ - draw(g: CanvasRenderingContext2D, x: number, y: number): void; - - /** - * Draws the image. - * - * @param g - * the graphics context where to draw to - * @param destx - * destination x - * @param desty - * destination y - * @param x - * the source x - * @param y - * the source y - * @param w - * the width - * @param h - * the height - */ - public draw(g: CanvasRenderingContext2D, destx: number, desty: number, x: number, y: number, w: number, h: number): void; - public draw(g: CanvasRenderingContext2D, destx: number, desty: number, x?: number, y?: number, w?: number, h?: number): void { - } - - /** - * Get the height of the drawn sprite. - * - * @return The height in pixels of this sprite - */ - public getHeight(): number { - return this.height; - } - - /** - * Get the sprite reference. This identifier is an externally opaque object - * that implements equals() and hashCode() to uniquely/repeatably reference - * a keyed sprite. - * - * @return The reference identifier, or null if not - * referencable. - */ - public getReference(): object | undefined { - return this.reference; - } - - /** - * Get the width of the drawn sprite. - * - * @return The width in pixels of this sprite - */ - public getWidth(): number { - return this.width;; - } - - public isConstant(): boolean { - return true; - } -} diff --git a/src/js/stendhal/sprite/Floater.ts b/src/js/stendhal/sprite/Floater.ts index f529137473..08a4f1e925 100644 --- a/src/js/stendhal/sprite/Floater.ts +++ b/src/js/stendhal/sprite/Floater.ts @@ -9,6 +9,7 @@ * * ***************************************************************************/ +import { RenderingContext2D } from "util/Types"; import { TextSprite } from "./TextSprite"; /** @@ -23,7 +24,7 @@ export class Floater { this.textSprite = new TextSprite(this.message, this.color, "12px sans-serif") } - draw(ctx: CanvasRenderingContext2D, x: number, y: number): boolean { + draw(ctx: RenderingContext2D, x: number, y: number): boolean { let textOffset = this.textSprite.getTextMetrics(ctx).width / 2; var timeDiff = Date.now() - this.initTime; this.textSprite.draw(ctx, x - textOffset, y - timeDiff / 50); diff --git a/src/js/stendhal/sprite/ImageSprite.ts b/src/js/stendhal/sprite/ImageSprite.ts deleted file mode 100644 index 3e25d125f2..0000000000 --- a/src/js/stendhal/sprite/ImageSprite.ts +++ /dev/null @@ -1,194 +0,0 @@ -/*************************************************************************** - * (C) Copyright 2003-2023 - Stendhal * - *************************************************************************** - *************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU Affero General Public License as * - * published by the Free Software Foundation; either version 3 of the * - * License, or (at your option) any later version. * - * * - ***************************************************************************/ - -import { ImageWithDimensions } from "data/ImageWithDimensions"; -import { EmptySprite } from "./EmptySprite"; -import { Sprite } from "./Sprite"; - -/** - * A sprite to be displayed on the screen. Note that a sprite contains no state - * information, i.e. its just the image and not the location. This allows us to - * use a single sprite in lots of different places without having to store - * multiple copies of the image. - * - * @author Kevin Glass - */ -export class ImageSprite implements Sprite { - - /** The image to be drawn for this sprite. */ - private image: CanvasImageSource & ImageWithDimensions; - - /** - * The identifier reference. - */ - private reference?: object; - - /** - * Create a new sprite based on an image. - * - * @param image - * The image that is this sprite. - * @param reference - * The sprite reference, or null. - */ - public constructor (image: CanvasImageSource & ImageWithDimensions | Sprite, reference?: object) { - if (image instanceof Sprite) { - this.image = ImageSprite.createCompatibleImage(image); - } else { - this.image = image; - } - this.reference = reference; - } - - private static createCompatibleImage(sprite: Sprite): HTMLCanvasElement { - const canvas = document.createElement('canvas'); - const g = canvas.getContext('2d'); - canvas.width = sprite.getWidth(); - canvas.height = sprite.getHeight(); - sprite.draw(g!, 0, 0); - return canvas; - } - - - /** - * Create a sub-region of this sprite. NOTE: This does not use - * caching. - * - * @param x - * The starting X coordinate. - * @param y - * The starting Y coordinate. - * @param width - * The region width. - * @param height - * The region height. - * @param ref - * The sprite reference. - * - * @return A new sprite. - */ - public createRegion(x: number, y: number, width: number, height: number, ref?: object): Sprite { - const iwidth = this.getWidth(); - const iheight = this.getHeight(); - - if ((x >= iwidth) || (y >= iheight)) { - // Outside of image (nothing to draw) - return new EmptySprite(width, height, ref); - } - - // Exclude regions outside the original image - width = Math.min(width, iwidth); - height = Math.min(height, iheight); - - // Full copy method (the memory hog) - const canvas = document.createElement('canvas'); - const g = canvas.getContext('2d'); - canvas.width = width; - canvas.height = height; - - this.draw(g!, 0, 0, x, y, width, height); - - return new ImageSprite(canvas, ref); - } - - /** - * Draw the sprite onto the graphics context provided. - * - * @param g - * The graphics context on which to draw the sprite - * @param x - * The x location at which to draw the sprite - * @param y - * The y location at which to draw the sprite - */ - public draw(g: CanvasRenderingContext2D, destx: number, desty: number): void; - - /** - * Draws the image. - * - * @param g - * the graphics context where to draw to - * @param destx - * destination x - * @param desty - * destination y - * @param x - * the source x - * @param y - * the source y - * @param w - * the width - * @param h - * the height - */ - public draw(g: CanvasRenderingContext2D, destx: number, desty: number, x: number, y: number, w: number, h: number): void; - public draw(g: CanvasRenderingContext2D, destx: number, desty: number, x?: number, y?: number, w?: number, h?: number): void { - if (arguments.length === 7) { - g.drawImage(this.image, destx, desty, destx + w!, desty + h!, x!, y!, x! + w!, y! + h!); - } else { - g.drawImage(this.image, destx, desty); - } - } - - /** - * Get the height of the drawn sprite. - * - * @return The height in pixels of this sprite - */ - public getHeight(): number { - return this.image.height as number; - } - - /** - * Get the sprite reference. This identifier is an externally opaque object - * that implements equals() and hashCode() to uniquely/repeatably reference - * a keyed sprite. - * - * @return The reference identifier, or null if not - * referencable. - */ - public getReference(): object | undefined { - return this.reference; - } - - /** - * Get the width of the drawn sprite. - * - * @return The width in pixels of this sprite - */ - public getWidth(): number { - return this.image.width as number; - } - - public isConstant(): boolean { - return true; - } - - /** - * Retrieves a single frame from the image. - * - * @param xIndex - * Horizontal index. - * @param yIndex - * Vertical index. - * @return - * Cropped Sprite. - */ - public getFrame(xIndex: number, yIndex: number): Sprite { - const w = this.getWidth() / 3; - const h = this.getHeight() / 4; - const x = w * xIndex; - const y = h * yIndex; - - return this.createRegion(x, y, w, h, undefined); - } -} diff --git a/src/js/stendhal/sprite/NotificationBubble.ts b/src/js/stendhal/sprite/NotificationBubble.ts index df6d04b6ca..83e1f8a2d3 100644 --- a/src/js/stendhal/sprite/NotificationBubble.ts +++ b/src/js/stendhal/sprite/NotificationBubble.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright © 2003-2024 - Faiumoni e. V. * + * Copyright © 2003-2026 - Faiumoni e. V. * *************************************************************************** *************************************************************************** * * @@ -10,14 +10,18 @@ * * ***************************************************************************/ -declare var stendhal: any; import { TextBubble } from "./TextBubble"; import { Color } from "../data/color/Color"; +import { htmlImageStore } from "data/HTMLImageStore"; +import { ViewPort } from "ui/ViewPort"; +import { RenderingContext2D } from "util/Types"; +import { Paths } from "../data/Paths"; import { NotificationType } from "../util/NotificationType"; import { Speech } from "../util/Speech"; +import { HTMLImageElementUtil } from "./image/HTMLImageElementUtil"; export class NotificationBubble extends TextBubble { @@ -70,10 +74,11 @@ export class NotificationBubble extends TextBubble { } } - override draw(ctx: CanvasRenderingContext2D): boolean { - const screenTop = stendhal.ui.gamewindow.offsetY; + override draw(ctx: RenderingContext2D): boolean { + let viewPort = ViewPort.get() + const screenTop = viewPort.offsetY; const screenBottom = screenTop + ctx.canvas.height; - const screenLeft = stendhal.ui.gamewindow.offsetX; + const screenLeft = viewPort.offsetX; const screenCenterX = screenLeft + (ctx.canvas.width / 2); @@ -132,10 +137,10 @@ export class NotificationBubble extends TextBubble { * Loads a profile image to be drawn with text. */ private loadProfileSprite() { - const img = stendhal.data.sprites.get(stendhal.paths.sprites + const img = htmlImageStore.get(Paths.sprites + "/npc/" + this.profileName + ".png"); if (img.complete && img.height) { - this.profile = stendhal.data.sprites.getAreaOf(img, 48, 48, 48, 128); + this.profile = HTMLImageElementUtil.getAreaOf(img, 48, 48, 48, 128); } } } diff --git a/src/js/stendhal/sprite/OverlaySpriteImpl.ts b/src/js/stendhal/sprite/OverlaySpriteImpl.ts index 70da92fe57..cd12104f8f 100644 --- a/src/js/stendhal/sprite/OverlaySpriteImpl.ts +++ b/src/js/stendhal/sprite/OverlaySpriteImpl.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright © 2024 - Faiumoni e. V. * + * Copyright © 20242-2026 - Faiumoni e. V. * *************************************************************************** *************************************************************************** * * @@ -10,13 +10,15 @@ * * ***************************************************************************/ +import { RenderingContext2D } from "util/Types"; + export interface OverlaySpriteImpl { /** * Instructions when sprite should be drawn on viewport. * - * @param {CanvasRenderingContext2D} ctx + * @param {RenderingContext2D} ctx * The viewport canvas drawing context. * @param {number} x * Horizonal pixel position of where to draw on canvas. @@ -29,7 +31,7 @@ export interface OverlaySpriteImpl { * @returns {boolean} * `true` to denote sprite is expired & should be removed. */ - draw(ctx: CanvasRenderingContext2D, x: number, y: number, drawWidth: number, drawHeight: number) + draw(ctx: RenderingContext2D, x: number, y: number, drawWidth: number, drawHeight: number) : boolean; /** @@ -39,4 +41,6 @@ export interface OverlaySpriteImpl { * `true` if sprite should be removed. */ expired(): boolean; + + free(): void; } diff --git a/src/js/stendhal/sprite/SpeechBubble.ts b/src/js/stendhal/sprite/SpeechBubble.ts index 2e8b71fc75..521d15eb3c 100644 --- a/src/js/stendhal/sprite/SpeechBubble.ts +++ b/src/js/stendhal/sprite/SpeechBubble.ts @@ -9,7 +9,7 @@ * * ***************************************************************************/ -declare var stendhal: any; +import { stendhal } from "../stendhal"; import { TextBubble } from "./TextBubble"; @@ -19,6 +19,8 @@ import { RPEntity } from "../entity/RPEntity"; import { Pair } from "../util/Pair"; import { Speech } from "../util/Speech"; +import { RenderingContext2D } from "util/Types"; +import { ViewPort } from "ui/ViewPort"; export class SpeechBubble extends TextBubble { @@ -31,6 +33,7 @@ export class SpeechBubble extends TextBubble { constructor(text: string, entity: RPEntity) { + let viewPort = ViewPort.get(); text = text.replace(/\\\\/g, "\\"); super((text.length > 30) ? (text.substring(0, 30) + "...") : text); this.entity = entity; @@ -41,9 +44,9 @@ export class SpeechBubble extends TextBubble { this.offsetY = 0; // find free text bubble position const x = this.getX(), y = this.getY(); - for (const bubble of stendhal.ui.gamewindow.textSprites) { + for (const bubble of viewPort.textSprites) { if (x == bubble.getX() && y + this.offsetY == bubble.getY()) { - this.offsetY += stendhal.ui.gamewindow.targetTileHeight / 2; + this.offsetY += viewPort.targetTileHeight / 2; } } @@ -52,7 +55,7 @@ export class SpeechBubble extends TextBubble { this.text.length * TextBubble.STANDARD_DUR / 50); } - override draw(ctx: CanvasRenderingContext2D): boolean { + override draw(ctx: RenderingContext2D): boolean { ctx.lineWidth = 2; ctx.font = "14px Arial"; ctx.fillStyle = Color.WHITE; @@ -82,8 +85,9 @@ export class SpeechBubble extends TextBubble { override getX(): number { let x = this.entity["_x"] * 32 + (32 * this.entity["width"]); if (this.entity.inView()) { + let viewPort = ViewPort.get(); // keep on screen while entity is visible (border is 1 pixel) - const overdraw = x + this.width - (stendhal.ui.gamewindow.offsetX + stendhal.ui.gamewindow.width) + 1; + const overdraw = x + this.width - (viewPort.offsetX + viewPort.width) + 1; x = overdraw > 0 ? x - overdraw : x; } return x; @@ -92,8 +96,9 @@ export class SpeechBubble extends TextBubble { override getY(): number { let y = this.entity["_y"] * 32 - 16 - (32 * (this.entity["height"] - 1)) + this.offsetY - TextBubble.adjustY; if (this.entity.inView()) { + let viewPort = ViewPort.get(); // keep on screen while entity is visible (border is 1 pixel) - y = y < stendhal.ui.gamewindow.offsetY + 1 ? stendhal.ui.gamewindow.offsetY + 1 : y; + y = y < viewPort.offsetY + 1 ? viewPort.offsetY + 1 : y; } return y; } diff --git a/src/js/stendhal/sprite/Sprite.ts b/src/js/stendhal/sprite/Sprite.ts deleted file mode 100644 index acfb065ba5..0000000000 --- a/src/js/stendhal/sprite/Sprite.ts +++ /dev/null @@ -1,103 +0,0 @@ -/*************************************************************************** - * (C) Copyright 2003-2023 - Stendhal * - *************************************************************************** - *************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU Affero General Public License as * - * published by the Free Software Foundation; either version 3 of the * - * License, or (at your option) any later version. * - * * - ***************************************************************************/ - - /** - * A sprite to be displayed on the screen. Note that a sprite contains no state - * information, i.e. its just the image and not the location. This allows us to - * use a single sprite in lots of different places without having to store - * multiple copies of the image. - */ -export abstract class Sprite { - - /** - * Create a sub-region of this sprite. NOTE: This does not use - * caching. - * - * @param x - * The starting X coordinate. - * @param y - * The starting Y coordinate. - * @param width - * The region width. - * @param height - * The region height. - * @param ref - * The sprite reference. - * - * @return A new sprite. - */ - abstract createRegion(x: number, y: number, width: number, height: number, ref: object): Sprite; - - /** - * Draw the sprite onto the graphics context provided. - * - * @param g - * The graphics context on which to draw the sprite - * @param x - * The x location at which to draw the sprite - * @param y - * The y location at which to draw the sprite - */ - abstract draw(g: CanvasRenderingContext2D, x: number, y: number): void; - - /** - * Draws the image. - * - * @param g - * the graphics context where to draw to - * @param destx - * destination x - * @param desty - * destination y - * @param x - * the source x - * @param y - * the source y - * @param w - * the width - * @param h - * the height - */ - abstract draw(g: CanvasRenderingContext2D, destx: number, desty: number, x: number, y: number, w: number, h: number): void; - - /** - * Get the height of the drawn sprite. - * - * @return The height in pixels of this sprite - */ - abstract getHeight(): number; - - /** - * Get the sprite reference. This identifier is an externally opaque object - * that implements equals() and hashCode() to uniquely/repeatably reference - * a keyed sprite. - * - * @return The reference identifier, or null if not - * referencable. - */ - abstract getReference(): object | undefined; - - /** - * Get the width of the drawn sprite. - * - * @return The width in pixels of this sprite - */ - abstract getWidth(): number; - - /** - * Check whether the sprite won't change between draws. - * - * @return true if different draws of the sprite always - * have the same result. - */ - abstract isConstant(): boolean; -} diff --git a/src/js/stendhal/sprite/TextBubble.ts b/src/js/stendhal/sprite/TextBubble.ts index 97f4c1cc0f..b15e7060db 100644 --- a/src/js/stendhal/sprite/TextBubble.ts +++ b/src/js/stendhal/sprite/TextBubble.ts @@ -9,10 +9,13 @@ * * ***************************************************************************/ -declare var stendhal: any; +import { stendhal } from "../stendhal"; +import { RenderingContext2D } from "util/Types"; import { Color } from "../data/color/Color"; import { Pair } from "../util/Pair"; +import { HTMLUtil } from "ui/HTMLUtil"; +import { ViewPort } from "ui/ViewPort"; export abstract class TextBubble { @@ -45,7 +48,7 @@ export abstract class TextBubble { * true if the sprites duration time has expired * & should be removed from the screen. */ - abstract draw(ctx: CanvasRenderingContext2D): boolean; + abstract draw(ctx: RenderingContext2D): boolean; getX(): number { return this.x; @@ -109,14 +112,15 @@ export abstract class TextBubble { * Mouse or touch event executed on canvas context. */ onClick(evt: MouseEvent|TouchEvent) { - const pos = stendhal.ui.html.extractPosition(evt); + let viewPort = ViewPort.get(); + const pos = HTMLUtil.extractPosition(evt); const screenRect = document.getElementById("viewport")!.getBoundingClientRect(); - const pointX = pos.clientX - screenRect.x + stendhal.ui.gamewindow.offsetX; - const pointY = pos.clientY - screenRect.y + stendhal.ui.gamewindow.offsetY + TextBubble.adjustY; + const pointX = pos.clientX - screenRect.x + viewPort.offsetX; + const pointY = pos.clientY - screenRect.y + viewPort.offsetY + TextBubble.adjustY; if (this.clipsPoint(pointX, pointY)) { evt.stopPropagation(); // remove when clicked or tapped - stendhal.ui.gamewindow.removeTextBubble(this, pointX, pointY); + viewPort.removeTextBubble(this, pointX, pointY); } } @@ -128,7 +132,13 @@ export abstract class TextBubble { * @param ctx * Canvas context on which text bubble is drawn. */ - onAdded(ctx: CanvasRenderingContext2D) { + onAdded(ctx: RenderingContext2D) { + // TODO: Reference the Viewport directly instead of deriving the canvas from the context + if (!(ctx instanceof CanvasRenderingContext2D)) { + console.error("Cannot add event listeners because HTMLCanvasElement is required."); + return; + } + // prevent multiple listeners from being added if (typeof(this.onRemovedAction) === "undefined") { // add click listener to remove chat bubble diff --git a/src/js/stendhal/sprite/TextSprite.ts b/src/js/stendhal/sprite/TextSprite.ts index 458ad09207..9ec21c69b2 100644 --- a/src/js/stendhal/sprite/TextSprite.ts +++ b/src/js/stendhal/sprite/TextSprite.ts @@ -9,6 +9,8 @@ * * ***************************************************************************/ +import { RenderingContext2D } from "util/Types"; + export class TextSprite { private textMetrics?: TextMetrics; @@ -25,7 +27,7 @@ export class TextSprite { * @param x x coordinate * @param y y coordinate */ - public draw(ctx: CanvasRenderingContext2D, x: number, y: number) { + public draw(ctx: RenderingContext2D, x: number, y: number) { ctx.font = this.font; ctx.lineWidth = 2; ctx.strokeStyle = "black"; @@ -35,7 +37,7 @@ export class TextSprite { ctx.fillText(this.text, x, y); } - getTextMetrics(ctx: CanvasRenderingContext2D) { + getTextMetrics(ctx: RenderingContext2D) { if (!this.textMetrics) { ctx.font = this.font; this.textMetrics = ctx.measureText(this.text); diff --git a/src/js/stendhal/sprite/action/AttackSprite.ts b/src/js/stendhal/sprite/action/AttackSprite.ts index 588b62aef8..6740ceaa55 100644 --- a/src/js/stendhal/sprite/action/AttackSprite.ts +++ b/src/js/stendhal/sprite/action/AttackSprite.ts @@ -10,6 +10,7 @@ * * ***************************************************************************/ +import { RenderingContext2D } from "util/Types"; import { OverlaySpriteImpl } from "../OverlaySpriteImpl"; @@ -25,10 +26,12 @@ export abstract class AttackSprite implements OverlaySpriteImpl { this.initTime = Date.now(); } - abstract draw(ctx: CanvasRenderingContext2D, x: number, y: number, entityWidth: number, + abstract draw(ctx: RenderingContext2D, x: number, y: number, entityWidth: number, entityHeight: number): boolean; expired(): boolean { return Date.now() - this.initTime > 180; } + + abstract free() :void; } diff --git a/src/js/stendhal/sprite/action/BarehandAttackSprite.ts b/src/js/stendhal/sprite/action/BarehandAttackSprite.ts index c200445a04..441191ddd1 100644 --- a/src/js/stendhal/sprite/action/BarehandAttackSprite.ts +++ b/src/js/stendhal/sprite/action/BarehandAttackSprite.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright © 2023-2024 - Faiumoni e. V. * + * Copyright © 2023-2026 - Faiumoni e. V. * *************************************************************************** *************************************************************************** * * @@ -10,34 +10,40 @@ * * ***************************************************************************/ -import { AttackSprite } from "./AttackSprite"; +import { RenderingContext2D } from "util/Types"; import { RPEntity } from "../../entity/RPEntity"; +import { AttackSprite } from "./AttackSprite"; -declare var stendhal: any; +import { images } from "sprite/image/ImageManager"; +import { ImageRef } from "sprite/image/ImageRef"; +import { ViewPort } from "ui/ViewPort"; export class BarehandAttackSprite extends AttackSprite { private readonly dir: number; - private readonly image: HTMLImageElement; + private readonly imageRef: ImageRef; - constructor(source: RPEntity, image: HTMLImageElement) { + constructor(source: RPEntity, imagePath: string) { super(); this.dir = source["dir"]; - this.image = image; + this.imageRef = images.load(imagePath); } - override draw(ctx: CanvasRenderingContext2D, x: number, y: number, entityWidth: number, - entityHeight: number): boolean { - if (!this.image || !this.image.height) { + override draw(ctx: RenderingContext2D, x: number, y: number, + entityWidth: number, entityHeight: number): boolean { + + let image = this.imageRef.image; + if (!image) { return this.expired(); } - + + let viewPort = ViewPort.get(); const dtime = Date.now() - this.initTime; const frameIndex = Math.floor(Math.min(dtime / 60, 2)); - const drawWidth = this.image.width / 3; - const drawHeight = this.image.height / 4; + const drawWidth = image.width / 3; + const drawHeight = image.height / 4; const centerX = x + (entityWidth - drawWidth) / 2; const centerY = y + (entityHeight - drawHeight) / 2; @@ -45,19 +51,19 @@ export class BarehandAttackSprite extends AttackSprite { let sx, sy; switch (this.dir+"") { case "1": // UP - sx = centerX + (stendhal.ui.gamewindow.targetTileWidth / 2); - sy = y - (stendhal.ui.gamewindow.targetTileHeight * 1.5); + sx = centerX + (viewPort.targetTileWidth / 2); + sy = y - (viewPort.targetTileHeight * 1.5); break; case "3": // DOWN sx = centerX; - sy = y + entityHeight - drawHeight + (stendhal.ui.gamewindow.targetTileHeight / 2); + sy = y + entityHeight - drawHeight + (viewPort.targetTileHeight / 2); break; case "4": // LEFT - sx = x - (stendhal.ui.gamewindow.targetTileWidth / 2); - sy = centerY - (stendhal.ui.gamewindow.targetTileHeight / 2); + sx = x - (viewPort.targetTileWidth / 2); + sy = centerY - (viewPort.targetTileHeight / 2); break; case "2": // RIGHT - sx = x + entityWidth - drawWidth + (stendhal.ui.gamewindow.targetTileWidth / 2); + sx = x + entityWidth - drawWidth + (viewPort.targetTileWidth / 2); sy = centerY; // - ICON_OFFSET; // ICON_OFFSET = 8 in Java client break; default: @@ -65,8 +71,16 @@ export class BarehandAttackSprite extends AttackSprite { sy = centerY; } - ctx.drawImage(this.image, frameIndex * drawWidth, (this.dir - 1) * drawHeight, - drawWidth, drawHeight, sx, sy, drawWidth, drawHeight); + ctx.drawImage(image, + frameIndex * drawWidth, (this.dir - 1) * drawHeight, + drawWidth, drawHeight, + sx, sy, + drawWidth, drawHeight); return this.expired(); } + + override free(): void { + this.imageRef?.free(); + } + } diff --git a/src/js/stendhal/sprite/action/DirectionalSkillEffect.ts b/src/js/stendhal/sprite/action/DirectionalSkillEffect.ts index 3b041cefba..e2a7d6880f 100644 --- a/src/js/stendhal/sprite/action/DirectionalSkillEffect.ts +++ b/src/js/stendhal/sprite/action/DirectionalSkillEffect.ts @@ -15,6 +15,7 @@ import { SkillEffect } from "./SkillEffect"; import { Entity } from "../../entity/Entity"; import { Direction } from "../../util/Direction"; +import { RenderingContext2D } from "util/Types"; /** @@ -44,14 +45,14 @@ export class DirectionalSkillEffect extends SkillEffect { this.entity = entity; } - protected override drawInternal(ctx: CanvasRenderingContext2D, colIdx: number, x: number, + protected override drawInternal(ctx: RenderingContext2D, colIdx: number, x: number, y: number, drawWidth: number, drawHeight: number) { const dir = Direction.VALUES[parseInt(this.entity["dir"], 10)]; let rowIdx = dir.val - 1; if (rowIdx < 0) { rowIdx = Direction.DOWN.val - 1; // default is third row (down) } - ctx.drawImage(this.image, colIdx*drawWidth, rowIdx*drawHeight, drawWidth, drawHeight, x, y, - drawWidth, drawHeight); + ctx.drawImage(this.imageRef.image!, colIdx*drawWidth, rowIdx*drawHeight, drawWidth, drawHeight, + x, y, drawWidth, drawHeight); } } diff --git a/src/js/stendhal/sprite/action/MeleeAttackSprite.ts b/src/js/stendhal/sprite/action/MeleeAttackSprite.ts index 4108a76b1b..9dfe8beb60 100644 --- a/src/js/stendhal/sprite/action/MeleeAttackSprite.ts +++ b/src/js/stendhal/sprite/action/MeleeAttackSprite.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright © 2023-2024 - Faiumoni e. V. * + * Copyright © 2023-2026 - Faiumoni e. V. * *************************************************************************** *************************************************************************** * * @@ -10,11 +10,11 @@ * * ***************************************************************************/ -declare var stendhal: any; - -import { AttackSprite } from "./AttackSprite"; +import { htmlImageStore } from "data/HTMLImageStore"; +import { RenderingContext2D } from "util/Types"; import { RPEntity } from "../../entity/RPEntity"; import { Direction } from "../../util/Direction"; +import { AttackSprite } from "./AttackSprite"; export class MeleeAttackSprite extends AttackSprite { @@ -45,10 +45,10 @@ export class MeleeAttackSprite extends AttackSprite { const rot = 90 * (this.dir.val - 1); imagePath = imagePath.replace(/\.png$/, ""); this.frames = [ - //~ stendhal.data.sprites.getRotated(imagePath + "_rot45.png", rot-90), + //~ singletons.getSpriteStore().getRotated(imagePath + "_rot45.png", rot-90), undefined, - stendhal.data.sprites.getRotated(imagePath + ".png", rot), - //~ stendhal.data.sprites.getRotated(imagePath + "_rot45.png", rot) + htmlImageStore.getRotated(imagePath + ".png", rot), + //~ singletons.getSpriteStore().getRotated(imagePath + "_rot45.png", rot) undefined ]; if (this.frames[1].complete) { @@ -108,7 +108,7 @@ export class MeleeAttackSprite extends AttackSprite { } } - override draw(ctx: CanvasRenderingContext2D, x: number, y: number, entityWidth: number, + override draw(ctx: RenderingContext2D, x: number, y: number, entityWidth: number, entityHeight: number): boolean { const dtime = Date.now() - this.initTime; const frameIndex = Math.floor(Math.min(dtime / 60, 2)); @@ -130,4 +130,8 @@ export class MeleeAttackSprite extends AttackSprite { ctx.drawImage(frame, 0, 0, frame.width, frame.height, dx, dy, frame.width, frame.height); return this.expired(); } + + override free(): void { + // do nothing + } } diff --git a/src/js/stendhal/sprite/action/RangedAttackSprite.ts b/src/js/stendhal/sprite/action/RangedAttackSprite.ts index c3385ff7c0..2a32346cc9 100644 --- a/src/js/stendhal/sprite/action/RangedAttackSprite.ts +++ b/src/js/stendhal/sprite/action/RangedAttackSprite.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright © 2023-2024 - Stendhal * + * Copyright © 2023-2026 - Stendhal * *************************************************************************** *************************************************************************** * * @@ -10,10 +10,14 @@ * * ***************************************************************************/ -import { AttackSprite } from "./AttackSprite"; +import { RenderingContext2D } from "util/Types"; +import { Paths } from "../../data/Paths"; import { RPEntity } from "../../entity/RPEntity"; +import { AttackSprite } from "./AttackSprite"; -declare var stendhal: any; +import { images } from "sprite/image/ImageManager"; +import { ImageRef } from "sprite/image/ImageRef"; +import { ViewPort } from "ui/ViewPort"; export class RangedAttackSprite extends AttackSprite { @@ -22,12 +26,13 @@ export class RangedAttackSprite extends AttackSprite { private readonly targetX: number; private readonly targetY: number; private readonly color: string; - private readonly image = stendhal.data.sprites.get(stendhal.paths.sprites + "/combat/ranged.png"); + private readonly imageRef: ImageRef; private readonly weapon?: string; constructor(source: RPEntity, target: RPEntity, color: string, weapon?: string) { super(); + this.imageRef = images.load(Paths.sprites + "/combat/ranged.png"); this.dir = source["dir"]; this.targetX = (target.x + target.width / 2) * 32; this.targetY = (target.y + target.height / 2) * 32; @@ -35,9 +40,10 @@ export class RangedAttackSprite extends AttackSprite { this.weapon = weapon; } - override draw(ctx: CanvasRenderingContext2D, x: number, y: number, entityWidth: number, + override draw(ctx: RenderingContext2D, x: number, y: number, entityWidth: number, entityHeight: number): boolean { // FIXME: alignment with entity is not correct + let viewPort = ViewPort.get(); var dtime = Date.now() - this.initTime; // We can use fractional "frame" for the lines. Just @@ -61,12 +67,14 @@ export class RangedAttackSprite extends AttackSprite { ctx.lineTo(endX, endY); ctx.stroke(); + let image = this.imageRef.image; + // draw bow - if (this.weapon === "ranged" && this.image.height) { + if (this.weapon === "ranged" && image) { frame = Math.floor(Math.min(dtime / 60, 3)); const yRow = this.dir - 1; - const drawWidth = this.image.width / 3; - const drawHeight = this.image.height / 4; + const drawWidth = image.width / 3; + const drawHeight = image.height / 4; const centerX = x + (entityWidth - drawWidth) / 2; const centerY = y + (entityHeight - drawHeight) / 2; @@ -75,19 +83,19 @@ export class RangedAttackSprite extends AttackSprite { let sx, sy; switch (this.dir+"") { case "1": // UP - sx = centerX + (stendhal.ui.gamewindow.targetTileWidth / 2); - sy = y - (stendhal.ui.gamewindow.targetTileHeight * 1.5); + sx = centerX + (viewPort.targetTileWidth / 2); + sy = y - (viewPort.targetTileHeight * 1.5); break; case "3": // DOWN sx = centerX; - sy = y + entityHeight - drawHeight + (stendhal.ui.gamewindow.targetTileHeight / 2); + sy = y + entityHeight - drawHeight + (viewPort.targetTileHeight / 2); break; case "4": // LEFT - sx = x - (stendhal.ui.gamewindow.targetTileWidth / 2); - sy = centerY - (stendhal.ui.gamewindow.targetTileHeight / 2); + sx = x - (viewPort.targetTileWidth / 2); + sy = centerY - (viewPort.targetTileHeight / 2); break; case "2": // RIGHT - sx = x + entityWidth - drawWidth + (stendhal.ui.gamewindow.targetTileWidth / 2); + sx = x + entityWidth - drawWidth + (viewPort.targetTileWidth / 2); sy = centerY; // - ICON_OFFSET; // ICON_OFFSET = 8 in Java client break; default: @@ -95,9 +103,16 @@ export class RangedAttackSprite extends AttackSprite { sy = centerY; } - ctx.drawImage(this.image, frame * drawWidth, yRow * drawHeight, - drawWidth, drawHeight, sx, sy, drawWidth, drawHeight); + ctx.drawImage(image, + frame * drawWidth, yRow * drawHeight, + drawWidth, drawHeight, + sx, sy, + drawWidth, drawHeight); } return this.expired(); } + + override free(): void { + this.imageRef.free(); + } } diff --git a/src/js/stendhal/sprite/action/SkillEffect.ts b/src/js/stendhal/sprite/action/SkillEffect.ts index 026d6054fb..f42be3aec6 100644 --- a/src/js/stendhal/sprite/action/SkillEffect.ts +++ b/src/js/stendhal/sprite/action/SkillEffect.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright © 2024 - Faiumoni e. V. * + * Copyright © 2024-2026 - Faiumoni e. V. * *************************************************************************** *************************************************************************** * * @@ -12,6 +12,9 @@ import { OverlaySpriteImpl } from "../OverlaySpriteImpl"; +import { images } from "sprite/image/ImageManager"; +import { ImageRef } from "sprite/image/ImageRef"; +import { RenderingContext2D } from "util/Types"; import { singletons } from "../../SingletonRepo"; @@ -21,7 +24,7 @@ import { singletons } from "../../SingletonRepo"; export class SkillEffect implements OverlaySpriteImpl { /** Image to be drawn on canvas. */ - protected readonly image: HTMLImageElement; + protected readonly imageRef: ImageRef; /** Time for which each frame should be displayed. */ private readonly delay: number; /** Timestamp at which this sprite should expire. */ @@ -45,8 +48,7 @@ export class SkillEffect implements OverlaySpriteImpl { * sprite should not expire. */ constructor(name: string, delay=100, duration=0) { - this.image = singletons.getSpriteStore().get(singletons.getPaths().sprites + "/entity/overlay/" - + name + ".png"); + this.imageRef = images.load(singletons.getPaths().sprites + "/entity/overlay/" + name + ".png"); this.cycleTime = Date.now(); this.drawTime = 0; this.delay = delay > 0 ? delay : 100; @@ -56,7 +58,7 @@ export class SkillEffect implements OverlaySpriteImpl { /** * Draws sprite to canvas. * - * @param {CanvasRenderingContext2D} ctx + * @param {RenderingContext2D} ctx * Canvas drawing context. * @param {number} colIdx * Horizontal frame index. @@ -69,20 +71,20 @@ export class SkillEffect implements OverlaySpriteImpl { * @param {number} drawHeight * Height of each frame to draw. */ - protected drawInternal(ctx: CanvasRenderingContext2D, colIdx: number, x: number, y: number, + protected drawInternal(ctx: RenderingContext2D, colIdx: number, x: number, y: number, drawWidth: number, drawHeight: number) { - ctx.drawImage(this.image, colIdx*drawWidth, 0, drawWidth, drawHeight, x, y, drawWidth, - drawHeight); + ctx.drawImage(this.imageRef.image!, colIdx*drawWidth, 0, drawWidth, drawHeight, + x, y, drawWidth, drawHeight); } - draw(ctx: CanvasRenderingContext2D, x=0, y=0, drawWidth=48, drawHeight=64): boolean { + draw(ctx: RenderingContext2D, x=0, y=0, drawWidth=48, drawHeight=64): boolean { this.drawTime = Date.now(); - if (!this.image.height) { + if (!this.imageRef.image) { return this.expired(); } const timeDiff = this.drawTime - this.cycleTime; - const fcount = Math.floor(this.image.width / drawWidth); + const fcount = Math.floor(this.imageRef.image?.width / drawWidth); let frame = Math.floor(timeDiff / this.delay); if (frame >= fcount) { // start new frame cycle @@ -97,4 +99,9 @@ export class SkillEffect implements OverlaySpriteImpl { expired(): boolean { return this.expires > 0 && this.drawTime > this.expires; } + + free(): void { + this.imageRef.free(); + } + } diff --git a/src/js/stendhal/sprite/image/FilteredImageRefImpl.ts b/src/js/stendhal/sprite/image/FilteredImageRefImpl.ts new file mode 100644 index 0000000000..806610a1c1 --- /dev/null +++ b/src/js/stendhal/sprite/image/FilteredImageRefImpl.ts @@ -0,0 +1,31 @@ +/*************************************************************************** + * (C) Copyright 2003-2026 - Stendhal * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { images } from "./ImageManager"; +import { ImageFilter } from "./ImageFilter"; +import { ImageRefImpl } from "./ImageRefImpl"; + +export class FilteredImageRefImpl extends ImageRefImpl { + + constructor(filename: string, private filter: string, private param?: number) { + super(filename); + } + + override async load() { + let originalImage = images.load(this.filename); + await originalImage.waitFor(); + if (originalImage.image) { + this.image = new ImageFilter().filterImage(originalImage.image, this.filter, this.param); + } + images.free(this.filename); + this.promiseResolve(); + } +} diff --git a/src/js/stendhal/sprite/image/HTMLImageElementUtil.ts b/src/js/stendhal/sprite/image/HTMLImageElementUtil.ts new file mode 100644 index 0000000000..4bbf76fff8 --- /dev/null +++ b/src/js/stendhal/sprite/image/HTMLImageElementUtil.ts @@ -0,0 +1,55 @@ +/*************************************************************************** + * (C) Copyright 2003-2026 - Stendhal * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { ImageWithDimensions } from "data/ImageWithDimensions"; + +export class HTMLImageElementUtil { + + /** + * Get an image element whose image data is an area of a specified image. + * If the area matches the original image, the image itself is returned. + * Otherwise a copy of the image data is returned. This is meant + * to be used for obtaining the drag image for drag and drop. + * + * This method intentionally returns an HTMLImageElement because Chrome is + * not able to use an HTMLCanvasElement as drag image. Firefox does support + * this. But neither browser is able to use an ImageBitmap. + * + * @param image original image + * @param width width of the area + * @param height height of the area + * @param offsetX optional. left x coordinate of the area + * @param offsetY optional. top y coordinate of the area + */ + static getAreaOf(image: CanvasImageSource & ImageWithDimensions, width: number, height: number, + offsetX?: number, offsetY?: number): any { + + offsetX = offsetX || 0; + offsetY = offsetY || 0; + if (image instanceof HTMLImageElement + && (image.width === width) && (image.height === height) + && (offsetX === 0) && (offsetY === 0)) { + return image; + } + let canvas = document.createElement("canvas") as HTMLCanvasElement; + canvas.width = width; + canvas.height = height; + let ctx = canvas.getContext("2d", {willReadFrequently: true})!; + try { + ctx.drawImage(image, offsetX, offsetY, width, height, 0, 0, width, height); + } catch (err) { + console.log(err); + } + let newImage = new Image(); + newImage.src = canvas.toDataURL("image/png"); + return newImage; + } +} diff --git a/src/js/stendhal/sprite/image/ImageCache.ts b/src/js/stendhal/sprite/image/ImageCache.ts new file mode 100644 index 0000000000..49e7968566 --- /dev/null +++ b/src/js/stendhal/sprite/image/ImageCache.ts @@ -0,0 +1,48 @@ +/*************************************************************************** + * (C) Copyright 2003-2026 - Stendhal * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { stendhal } from "stendhal" + +export class ImageCache { + images: Record = {}; + closed = false; + + load(filenames: string[]) { + for (let filename of filenames) { + this.loadImage(filename); + } + } + + private async loadImage(filename: string) { + let url = filename + "?v=" + stendhal.data.build.version; + let response = await fetch(url); + if (!response.ok || this.closed) { + return; + } + let blob = await response.blob(); + if (this.closed) { + return; + } + let bitmap = await createImageBitmap(blob); + if (this.closed) { + bitmap.close(); + return; + } + this.images[filename] = bitmap; + } + + close() { + this.closed = true; + for (let image of Object.values(this.images)) { + image?.close(); + } + } +} \ No newline at end of file diff --git a/src/js/stendhal/sprite/image/ImageFilter.ts b/src/js/stendhal/sprite/image/ImageFilter.ts new file mode 100644 index 0000000000..d23a3acb1f --- /dev/null +++ b/src/js/stendhal/sprite/image/ImageFilter.ts @@ -0,0 +1,199 @@ +/*************************************************************************** + * (C) Copyright 2003-2026 - Stendhal * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { ImageWithDimensions } from "data/ImageWithDimensions"; + +export class ImageFilter { + + /** + * @param rgb + */ + splitrgb(rgb: number): number[] { + rgb &= 0xffffff; + let b = rgb & 0xff; + rgb >>>= 8; + let g = rgb & 0xff; + rgb >>>= 8; + return [rgb, g, b]; + } + + mergergb(rgbArray: number[]): number { + const r = rgbArray[0] << 16; + const g = rgbArray[1] << 8; + return 0xffffff & (r | g | rgbArray[2]); + } + + /** + * @param rgb + */ + rgb2hsl(rgb: number[]): number[] { + let r = rgb[0] / 255; + let g = rgb[1] / 255; + let b = rgb[2] / 255; + + let max, min, maxVar; + // Find the max and minimum colors, and remember which one it was + if (r > g) { + max = r; + min = g; + maxVar = 0; + } else { + max = g; + min = r; + maxVar = 1; + } + if (b > max) { + max = b; + maxVar = 2; + } else if (b < min) { + min = b; + } + + // lightness + let l = (max + min) / 2; + let s, h; + + // saturation + let diff = max - min; + if (diff < 0.000001) { + s = 0; + // hue not really defined, but set it to something reasonable + h = 0; + } else { + if (l < 0.5) { + s = diff / (max + min); + } else { + s = diff / (2 - max - min); + } + + // hue + if (maxVar === 0) { + h = (g - b) / diff; + } else if (maxVar === 1) { + h = 2 + (b - r) / diff; + } else { + h = 4 + (r - g) / diff; + } + // Normalize to range [0, 1]. It's more useful than the usual 360 + h /= 6; + } + + return [h, s, l]; + } + + /** + * @param {Array} hsl + * @return {Array} + */ + hsl2rgb(hsl: number[]): number[] { + let r, g, b; + let h = hsl[0]; + let s = hsl[1]; + let l = hsl[2]; + + if (s < 0.0000001) { + r = g = b = Math.floor(255 * l); + } else { + let tmp1, tmp2; + if (l < 0.5) { + tmp1 = l * (1 + s); + } else { + tmp1 = l + s - l * s; + } + tmp2 = 2 * l - tmp1; + + let rf = this.hue2rgb(this.limitHue(h + 1 / 3), tmp2, tmp1); + let gf = this.hue2rgb(this.limitHue(h), tmp2, tmp1); + let bf = this.hue2rgb(this.limitHue(h - 1 / 3), tmp2, tmp1); + + r = Math.floor(255 * rf) & 0xff; + g = Math.floor(255 * gf) & 0xff; + b = Math.floor(255 * bf) & 0xff; + } + + return [r, g, b]; + } + + /** + * @param hue + * @param val1 + * @param val2 + */ + private hue2rgb(hue: number, val1: number, val2: number): number { + let res = hue; + if (6 * hue < 1) { + res = val1 + (val2 - val1) * 6 * hue; + } else if (2 * hue < 1) { + res = val2; + } else if (3 * hue < 2) { + res = val1 + (val2 - val1) * (2 / 3 - hue) * 6; + } else { + res = val1; + } + + return res; + } + + /** + * @param hue + */ + private limitHue(hue: number): number { + let res = hue; + if (res < 0) { + res += 1; + } else if (res > 1) { + res -= 1; + } + return res; + } + + private trueColor(data: any, color: number) { + let hslColor = this.rgb2hsl(this.splitrgb(color)); + let end = data.length; + for (let i = 0; i < end; i += 4) { + let rgb = [data[i], data[i + 1], data[i + 2]]; + let hsl = this.rgb2hsl(rgb); + // Adjust the brightness + let adj = hslColor[2] - 0.5; // [-0.5, 0.5] + let tmp = hsl[2] - 0.5; // [-0.5, 0.5] + // tweaks the middle lights either upward or downward, depending + // on if source lightness is high or low + let l = hsl[2] - 2.0 * adj * ((tmp * tmp) - 0.25); + let resultHsl = [hslColor[0], hslColor[1], l]; + let resultRgb = this.hsl2rgb(resultHsl); + data[i] = resultRgb[0]; + data[i + 1] = resultRgb[1]; + data[i + 2] = resultRgb[2]; + } + } + + + public filter(data: ImageData, filter: string, param?: number): void { + if (filter ==='trueColor') { + this.trueColor(data.data, param!); + } else { + console.log("Unknown filter name " + filter); + } + } + + + public filterImage(image: CanvasImageSource & ImageWithDimensions, filter: string, param?: number): ImageBitmap { + const canvas = new OffscreenCanvas(image.width, image.height); + const ctx = canvas.getContext("2d", {willReadFrequently: true})!; + ctx.drawImage(image, 0, 0); + let imageData = ctx.getImageData(0, 0, image.width, image.height); + new ImageFilter().filter(imageData, filter, param); + this.filter(imageData, filter, param); + ctx.putImageData(imageData, 0, 0); + return canvas.transferToImageBitmap(); + } +} + diff --git a/src/js/stendhal/sprite/image/ImageManager.ts b/src/js/stendhal/sprite/image/ImageManager.ts new file mode 100644 index 0000000000..8c8ec26348 --- /dev/null +++ b/src/js/stendhal/sprite/image/ImageManager.ts @@ -0,0 +1,60 @@ +/*************************************************************************** + * (C) Copyright 2003-2026 - Stendhal * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { FilteredImageRefImpl } from "./FilteredImageRefImpl"; +import { ImageRef } from "./ImageRef"; +import { ImageRefImpl } from "./ImageRefImpl"; + +export class ImageManager { + + images = new Map(); + + load(filename: string, filter?: string, param?: number): ImageRef { + if (filename.indexOf("..") > -1) { + throw new Error("Invalid filename: " + filename); + } + let key = filename + "!" + filter + "!" + param; + let imageRef = this.images.get(key); + if (!imageRef) { + if (filter) { + imageRef = new FilteredImageRefImpl(filename, filter, param); + } else { + imageRef = new ImageRefImpl(filename); + } + imageRef.load(); + this.images.set(key, imageRef); + } + imageRef.use(); + return imageRef; + } + + free(filename: string, filter?: string, param?: number) { + let key = filename + "!" + filter + "!" + param; + let imageRef = this.images.get(key); + if (!imageRef) { + console.error("freeing unknown image: " + key); + return; + } + imageRef.free(); + } + + cleanup(olderThan: Date) { + for (let [key, imageRef] of this.images) { + if (imageRef.shouldCleanup(olderThan)) { + imageRef.close(); + this.images.delete(key); + } + } + } + +} + +export const images = /* @__PURE__ */ new ImageManager(); diff --git a/src/js/stendhal/sprite/Tileset.ts b/src/js/stendhal/sprite/image/ImageMetadata.ts similarity index 58% rename from src/js/stendhal/sprite/Tileset.ts rename to src/js/stendhal/sprite/image/ImageMetadata.ts index aec50facb2..fd04ad9500 100644 --- a/src/js/stendhal/sprite/Tileset.ts +++ b/src/js/stendhal/sprite/image/ImageMetadata.ts @@ -1,6 +1,5 @@ -/* $Id$ */ /*************************************************************************** - * (C) Copyright 2003-2023 - Stendhal * + * Copyright © 2026-2026 - Faiumoni e. V. * *************************************************************************** *************************************************************************** * * @@ -11,27 +10,7 @@ * * ***************************************************************************/ -import { Sprite } from "./Sprite"; +export class ImageMetadata { -/** - * A tileset. - */ -export interface Tileset { - - /** - * Get the number of tiles. - * - * @return The number of tiles. - */ - getSize(): number; - - /** - * Get the sprite for an index tile of a tileset. - * - * @param index - * The index with-in the tileset. - * - * @return A sprite, or null if no mapped sprite. - */ - getSprite(index: number): Sprite; -} + constructor(public filename: string, public filter?: string, public color?: number) {} +} \ No newline at end of file diff --git a/src/js/stendhal/sprite/image/ImageRef.ts b/src/js/stendhal/sprite/image/ImageRef.ts new file mode 100644 index 0000000000..ef41b2a7fe --- /dev/null +++ b/src/js/stendhal/sprite/image/ImageRef.ts @@ -0,0 +1,28 @@ +/*************************************************************************** + * (C) Copyright 2003-2026 - Stendhal * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { RenderingContext2D } from "util/Types"; + +export abstract class ImageRef { + image?: ImageBitmap; + + async /*abstract */ waitFor(): Promise { + return new Promise(() => {}); + } + + drawOnto(ctx: RenderingContext2D, x: number, y: number) { + if (this.image) { + ctx.drawImage(this.image, x, y); + } + } + + abstract free(): void; +} diff --git a/src/js/stendhal/sprite/image/ImageRefImpl.ts b/src/js/stendhal/sprite/image/ImageRefImpl.ts new file mode 100644 index 0000000000..a39b7599ea --- /dev/null +++ b/src/js/stendhal/sprite/image/ImageRefImpl.ts @@ -0,0 +1,91 @@ +/*************************************************************************** + * (C) Copyright 2003-2026 - Stendhal * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { stendhal } from "stendhal"; +import { ImageRef } from "./ImageRef"; + +export class ImageRefImpl extends ImageRef { + private refCount = 0; + private lastFreed?: Date; + private closed = false; + private loaded: Promise; + protected promiseResolve!: Function; + + constructor(protected filename: string) { + super(); + this.loaded = new Promise((resolve) => { + this.promiseResolve = resolve; + }) + } + + async load() { + let url = this.filename + "?v=" + stendhal.data.build.version; + let response = await fetch(url); + if (!response.ok || this.closed) { + this.promiseResolve(undefined) + return; + } + let blob = await response.blob(); + if (this.closed) { + this.promiseResolve(undefined) + return; + } + let bitmap = await createImageBitmap(blob); + if (this.closed) { + bitmap.close(); + this.promiseResolve(undefined) + return; + } + this.image = bitmap; + this.promiseResolve(bitmap); + } + + /** + * called internally by ImageManager + */ + use() { + this.refCount++; + this.lastFreed = undefined; + } + + /** + * called internally by ImageManager + */ + override free() { + this.refCount--; + if (this.refCount < 0) { + console.error("Negative reference count", this); + } + if (this.refCount <= 0) { + this.lastFreed = new Date(); + } + } + + shouldCleanup(olderThan: Date) { + if (this.refCount > 0 || !this.lastFreed) { + return false; + } + return (this.lastFreed < olderThan); + } + + /** + * called internally by ImageManager + */ + close() { + this.closed = true; + this.image?.close(); + this.image = undefined; + } + + override async waitFor(): Promise { + return this.loaded + } +} diff --git a/src/js/stendhal/sprite/image/ImageSprite.ts b/src/js/stendhal/sprite/image/ImageSprite.ts new file mode 100644 index 0000000000..0b79292b26 --- /dev/null +++ b/src/js/stendhal/sprite/image/ImageSprite.ts @@ -0,0 +1,60 @@ +/*************************************************************************** + * (C) Copyright 2003-2026 - Stendhal * + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { RenderingContext2D } from "util/Types"; +import { ImageRef } from "./ImageRef"; + + +export class ImageSprite { + + constructor( + public imageRef: ImageRef, + public offsetX?: number, + public offsetY?: number, + public width?: number, + public height?: number + ) { } + + drawOnto(ctx: RenderingContext2D, x: number, y: number, entityWidth: number, entityHeight: number) { + let image = this.imageRef.image; + if (!image) { + return; + } + let offsetX = this.offsetX || 0; + let offsetY = this.offsetY || 0; + let width = this.width || image.width; + let height = this.height || image.height; + + // use entity dimensions to center sprite + x += Math.floor((entityWidth - width) / 2); + y += Math.floor((entityHeight - height) / 2); + + ctx.drawImage(image, offsetX, offsetY, width, height, x, y, width, height); + } + + drawOntoWithZoom(ctx: RenderingContext2D, targetX: number, targetY: number, targetWidth: number, targetHeight: number) { + let image = this.imageRef.image; + if (!image) { + return; + } + let offsetX = this.offsetX || 0; + let offsetY = this.offsetY || 0; + let width = this.width || image.width; + let height = this.height || image.height; + + ctx.drawImage(image, offsetX, offsetY, width, height, targetX, targetY, targetWidth, targetHeight); + } + + public free() { + this.imageRef.free(); + } + +} diff --git a/src/js/stendhal/sprite/image/MultilayerImageLoader.ts b/src/js/stendhal/sprite/image/MultilayerImageLoader.ts new file mode 100644 index 0000000000..54de7cc0fa --- /dev/null +++ b/src/js/stendhal/sprite/image/MultilayerImageLoader.ts @@ -0,0 +1,50 @@ +/*************************************************************************** + * Copyright © 2024-2026 - Faiumoni e. V. * + *************************************************************************** + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { ImageMetadata } from "./ImageMetadata"; +import { images } from "./ImageManager"; +import { ImageRef } from "./ImageRef"; + +export class MultilayerImageLoader { + private imageRefs: ImageRef[] = []; + + constructor(private imageMetadatas: ImageMetadata[]) { + } + + public async load() { + for (let imageMetadata of this.imageMetadatas) { + this.imageRefs.push(images.load(imageMetadata.filename, imageMetadata.filter, imageMetadata.color)); + } + + for (let imageRef of this.imageRefs) { + await imageRef.waitFor(); + } + + let canvas: OffscreenCanvas|undefined = undefined; + let ctx: OffscreenCanvasRenderingContext2D|undefined = undefined; + for (let imageRef of this.imageRefs) { + if (imageRef.image) { + if (!canvas) { + canvas = new OffscreenCanvas(imageRef.image.width, imageRef.image.height); + ctx = canvas.getContext("2d")!; + } + ctx?.drawImage(imageRef.image, 0, 0); + imageRef.free(); + } + } + if (!canvas) { + canvas = new OffscreenCanvas(0, 0); + } + return canvas.transferToImageBitmap(); + } + +} diff --git a/src/js/stendhal/sprite/indicator/StatusIconDrawer.ts b/src/js/stendhal/sprite/indicator/StatusIconDrawer.ts new file mode 100644 index 0000000000..03e0dee4f6 --- /dev/null +++ b/src/js/stendhal/sprite/indicator/StatusIconDrawer.ts @@ -0,0 +1,110 @@ +/*************************************************************************** + * (C) Copyright 2003-2026 - Stendhal * + *************************************************************************** + * * + * entity program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { htmlImageStore } from "data/HTMLImageStore"; +import { Paths } from "data/Paths"; +import { RPEntity } from "entity/RPEntity"; +import { images } from "sprite/image/ImageManager"; +import { ImageRef } from "sprite/image/ImageRef"; +import { RenderingContext2D } from "util/Types"; + +export class StatusIconDrawer { + + private icons: Record = {}; + + private load(filename: string): ImageRef { + let imageRef = this.icons[filename]; + if (!imageRef) { + imageRef = images.load(filename); + this.icons[filename] = imageRef; + } + return imageRef; + } + + private drawAnimatedIcon(ctx: RenderingContext2D, iconPath: string, delay: number, x: number, y: number, fWidth?: number) { + let icon = this.load(iconPath).image; + if (!icon) { + return; + } + let dimH = icon.height; + let dimW = fWidth || dimH; + let nFrames = icon.width / dimW; + let frame = Math.floor(Date.now() / delay) % nFrames; + ctx.drawImage(icon, frame * dimW, 0, dimW, dimH, x, y, dimW, dimH); + } + + private drawAnimatedIconWithFrames(ctx: RenderingContext2D, iconPath: string, nFrames: number, delay: number, x: number, y: number) { + let icon = this.load(iconPath).image; + if (!icon) { + return; + } + let ydim = icon.height; + let xdim = icon.width / nFrames; + let frame = Math.floor(Date.now() / delay) % nFrames; + ctx.drawImage(icon, frame * xdim, 0, xdim, ydim, x, y, xdim, ydim); + } + + public drawStatusIcons(entity: RPEntity, ctx: RenderingContext2D) { + let x = entity["_x"] * 32 - 10; + let y = (entity["_y"] + 1) * 32; + if (entity.hasOwnProperty("choking")) { + this.load(Paths.sprites + "/ideas/choking.png").drawOnto(ctx, x, y - 10); + } else if (entity.hasOwnProperty("eating")) { + this.load(Paths.sprites + "/ideas/eat.png").drawOnto(ctx, x, y - 10); + } + // NPC and pet idea icons + if (entity.hasOwnProperty("idea")) { + const idea = Paths.sprites + "/ideas/" + entity["idea"] + ".png"; + const ani = htmlImageStore.animations.idea[entity["idea"]]; + if (ani) { + this.drawAnimatedIcon(ctx, idea, ani.delay, x + ani.offsetX * entity["width"], + y - entity["drawHeight"] + ani.offsetY); + } else { + this.load(idea).drawOnto(ctx, x + 32 * entity["width"], y - entity["drawHeight"]); + } + } + if (entity.hasOwnProperty("away")) { + this.drawAnimatedIcon(ctx, Paths.sprites + "/ideas/away.png", 1500, x + 32 * entity["width"], y - entity["drawHeight"]); + } + if (entity.hasOwnProperty("grumpy")) { + this.drawAnimatedIcon(ctx, Paths.sprites + "/ideas/grumpy.png", 1000, x + 5, y - entity["drawHeight"]); + } + if (entity.hasOwnProperty("last_player_kill_time")) { + this.drawAnimatedIconWithFrames(ctx, Paths.sprites + "/ideas/pk.png", 12, 300, x, y - entity["drawHeight"]); + } + // status affects + if (entity.hasOwnProperty("poisoned")) { + this.drawAnimatedIcon(ctx, Paths.sprites + "/status/poison.png", 100, x + 32 * entity["width"] - 10, y - entity["drawHeight"]); + } + if (entity.hasOwnProperty("status_confuse")) { + this.drawAnimatedIcon(ctx, Paths.sprites + "/status/confuse.png", 200, x + 32 * entity["width"] - 14, y - entity["drawHeight"] + 16); + } + if (entity.hasOwnProperty("status_shock")) { + this.drawAnimatedIcon(ctx, Paths.sprites + "/status/shock.png", 200, x + 32 * entity["width"] - 25, y - 32, 38); + } + // NPC job icons + let nextX = x; + if (entity.hasOwnProperty("job_healer")) { + this.load(Paths.sprites + "/status/healer.png").drawOnto(ctx, nextX, y - 10); + nextX += 12; + } + if (entity.hasOwnProperty("job_merchant")) { + this.load(Paths.sprites + "/status/merchant.png").drawOnto(ctx, nextX, y - 10); + nextX += 12; + } + if (entity.hasOwnProperty("job_producer")) { + this.load(Paths.sprites + "/status/producer.png").drawOnto(ctx, nextX, y - 16); + nextX += 16; + } + } +} + +export const statusIconDrawer = /* @__PURE__ */ new StatusIconDrawer(); \ No newline at end of file diff --git a/src/js/stendhal/stendhal.ts b/src/js/stendhal/stendhal.ts new file mode 100644 index 0000000000..743cc4d6f3 --- /dev/null +++ b/src/js/stendhal/stendhal.ts @@ -0,0 +1,14 @@ +/** + * this class helps with refactoring from JavaScript to TypeScript. + */ +export class stendhal { + static config: any; + static ui: any; + static sound: any; + static zone: any; + static data: any; + static session: any; + static main: any; + static actions: any; + static players: any +} diff --git a/src/js/stendhal/tsconfig.json b/src/js/stendhal/tsconfig.json index 03ff0b466b..df8edfa5e4 100644 --- a/src/js/stendhal/tsconfig.json +++ b/src/js/stendhal/tsconfig.json @@ -1,14 +1,15 @@ { "compilerOptions": { "baseUrl": ".", - "lib": ["dom", "es2015"], - "module": "commonjs", + "lib": ["dom", "es2022"], + "module": "es2020", + "moduleResolution": "bundler", "noImplicitOverride": true, "outDir": "../../../build/ts", "rootDir": ".", "sourceMap": true, "strict": true, - "target": "es6" + "target": "es2022" }, "typedocOptions": { "entryPoints": ["."], diff --git a/src/js/stendhal/ui/ChatPanel.ts b/src/js/stendhal/ui/ChatPanel.ts index 147ebd3d50..1b06fb7ed7 100644 --- a/src/js/stendhal/ui/ChatPanel.ts +++ b/src/js/stendhal/ui/ChatPanel.ts @@ -16,6 +16,7 @@ import { QuickMenu } from "./quickmenu/QuickMenu"; import { QuickMenuButton } from "./quickmenu/QuickMenuButton"; import { Panel } from "./toolkit/Panel"; import { singletons } from "../SingletonRepo"; +import { ViewPort } from "./ViewPort"; /** @@ -68,7 +69,7 @@ export class ChatPanel extends Panel { public override refresh() { const floating = this.isFloating(); if (floating) { - const rect = singletons.getViewPort().getElement().getBoundingClientRect(); + const rect = ViewPort.get().getElement().getBoundingClientRect(); const halfHeight = Math.abs(rect.height / 2); this.componentElement.style["width"] = rect.width + "px"; this.componentElement.style["height"] = halfHeight + "px"; @@ -88,7 +89,7 @@ export class ChatPanel extends Panel { QuickMenu.setButtonEnabled(UIComponentEnum.QMChat, floating); // adapt viewport layout - singletons.getViewPort().onChatPanelRefresh(floating); + ViewPort.get().onChatPanelRefresh(floating); } /** diff --git a/src/js/stendhal/ui/HTMLManager.ts b/src/js/stendhal/ui/HTMLUtil.ts similarity index 86% rename from src/js/stendhal/ui/HTMLManager.ts rename to src/js/stendhal/ui/HTMLUtil.ts index 18b08acc0f..d421b00278 100644 --- a/src/js/stendhal/ui/HTMLManager.ts +++ b/src/js/stendhal/ui/HTMLUtil.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2024 - Stendhal * + * (C) Copyright 2003-2026 - Stendhal * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,27 +9,11 @@ * * ***************************************************************************/ -declare var stendhal: any; - /** * HTML code manipulation. */ -export class HTMLManager { - - /** Singleton instance. */ - private static instance: HTMLManager; - - - /** - * Retrieves singleton instance. - */ - static get(): HTMLManager { - if (!HTMLManager.instance) { - HTMLManager.instance = new HTMLManager(); - } - return HTMLManager.instance; - } +export class HTMLUtil { /** * Hidden singleton constructor. @@ -38,7 +22,7 @@ export class HTMLManager { // do nothing } - esc(msg: string, filter=[]) { + public static esc(msg: string, filter: string[]=[]) { msg = msg.replace(//g, '>').replace(/"/g, '"').replace(/\n/g, "
"); // restore filtered tags for (const tag of filter) { @@ -49,7 +33,7 @@ export class HTMLManager { return msg; } - niceName(s: string): string { + public static niceName(s: string): string { if (!s) { return ""; } @@ -65,7 +49,7 @@ export class HTMLManager { * @return {EventTarget} * Translated event target. */ - extractTarget(event: any): EventTarget { + public static extractTarget(event: any): EventTarget { if (event.changedTouches) { // FIXME: Always uses last index. Any way to detect which touch index was engaged? const tidx = event.changedTouches.length - 1; @@ -90,9 +74,9 @@ export class HTMLManager { * @return {any} * Normalized event. */ - extractPosition(event: any): any { + public static extractPosition(event: any): any { let pos = event; - const target = this.extractTarget(event); + const target = HTMLUtil.extractTarget(event); const canvas = target as HTMLCanvasElement; if (event.changedTouches) { // FIXME: Always uses last index. Any way to detect which touch index was engaged? @@ -119,7 +103,7 @@ export class HTMLManager { return pos; } - formatTallyMarks(line: string): any { + public static formatTallyMarks(line: string): any { let tmp = line.split(""); const pre = tmp[0]; tmp = tmp[1].split(""); @@ -159,7 +143,7 @@ export class HTMLManager { * @return {string} * Slot name. */ - parseSlotName(id: string): string { + public static parseSlotName(id: string): string { if (id.includes("-")) { return id.split("-")[0]; } diff --git a/src/js/stendhal/ui/HeldObject.ts b/src/js/stendhal/ui/HeldObject.ts index f2d521946d..158741b7f5 100644 --- a/src/js/stendhal/ui/HeldObject.ts +++ b/src/js/stendhal/ui/HeldObject.ts @@ -10,9 +10,12 @@ * * ***************************************************************************/ -declare var stendhal: any; +import { Entity } from "entity/Entity"; +import { stendhal } from "../stendhal"; import { Point } from "../util/Point"; +import { HTMLUtil } from "./HTMLUtil"; +import { ViewPort } from "./ViewPort"; /** @@ -34,12 +37,14 @@ export interface HeldObject { */ export class HeldObjectManager { - /** Image displayed when an object is "held". */ - private readonly image: HTMLImageElement; - /** Singleton instance. */ private static instance: HeldObjectManager; + /** Image displayed when an object is "held". */ + private image: HTMLCanvasElement|HTMLImageElement; + + public heldObject?: HeldObject; + /** * Retrieves singleton instance. @@ -58,6 +63,28 @@ export class HeldObjectManager { this.image = document.getElementById("held-object")! as HTMLImageElement; } + public prepare(draggedEntity: Entity, e: Event) { + let image = draggedEntity?.imageSprite?.imageRef.image; + if (!draggedEntity || !(draggedEntity.type === "item" || draggedEntity.type === "corpse") || !image) { + e.preventDefault(); + return false; + } + let canvas = document.getElementById("held-object") as HTMLCanvasElement; + let sprite = draggedEntity.imageSprite!; + canvas.style.display = "block"; + this.setPosition(-1000, -1000); + canvas.width = sprite.width!; + canvas.height = sprite.height!; + let ctx = canvas.getContext("2d")!; + ctx.clearRect(0, 0, sprite.width!, sprite.height!); + sprite.drawOntoWithZoom(ctx, 0, 0, 32, 32); + + if (e instanceof DragEvent) { + e.dataTransfer?.setDragImage(canvas, 0, 0); + } + return true; + } + /** * Sets the image's visibility. * @@ -80,9 +107,8 @@ export class HeldObjectManager { * @param pos {util.Point.Point} * Initial position of displayed image. */ - public set(obj: HeldObject, img: HTMLImageElement|string, pos?: Point) { - stendhal.ui.heldObject = obj; - this.setImage(img); + public set(obj: HeldObject, pos?: Point) { + this.heldObject = obj; this.onSet(pos); } @@ -96,7 +122,7 @@ export class HeldObjectManager { if (pos) { this.setPosition(pos.x, pos.y); } else { - const rect = stendhal.ui.gamewindow.getElement().getBoundingClientRect(); + const rect = ViewPort.get().getElement().getBoundingClientRect(); this.setPosition(rect.left, rect.top); } this.setVisible(true); @@ -109,26 +135,10 @@ export class HeldObjectManager { * Hides object image. */ public onRelease() { - // NOTE: should we unset `stendhal.ui.heldObject` here? + // NOTE: should we unset `heldObject` here? this.setVisible(false); } - /** - * Sets or unsets object image. - * - * @param img {HTMLImageElement|string} - * Image element or path to image. - */ - private setImage(img?: HTMLImageElement|string) { - if (!img) { - this.image.src = ""; - } else if (typeof(img) === "string") { - this.image.src = img; - } else { - this.image.src = img.src; - } - } - /** * Updates position of displayed image. * @@ -146,7 +156,7 @@ export class HeldObjectManager { * Handles event to update held object drawing position. */ private onDragWhileHeld(e: Event) { - const pos = stendhal.ui.html.extractPosition(e); + const pos = HTMLUtil.extractPosition(e); HeldObjectManager.get().setPosition(pos.pageX, pos.pageY); } diff --git a/src/js/stendhal/ui/Inventory.ts b/src/js/stendhal/ui/Inventory.ts index 90163cf7e0..4ea876aadd 100644 --- a/src/js/stendhal/ui/Inventory.ts +++ b/src/js/stendhal/ui/Inventory.ts @@ -9,7 +9,7 @@ * * ***************************************************************************/ -declare var stendhal: any; +import { stendhal } from "../stendhal"; import { ItemContainerImplementation } from "./component/ItemContainerImplementation"; diff --git a/src/js/stendhal/ui/TouchHandler.ts b/src/js/stendhal/ui/TouchHandler.ts index 6bc38bc897..87c6f3db56 100644 --- a/src/js/stendhal/ui/TouchHandler.ts +++ b/src/js/stendhal/ui/TouchHandler.ts @@ -9,9 +9,10 @@ * * ***************************************************************************/ -declare var stendhal: any; +import { stendhal } from "../stendhal"; import { Point } from "../util/Point"; +import { HTMLUtil } from "./HTMLUtil"; /** @@ -117,7 +118,7 @@ export class TouchHandler { const durationMatch = (this.timestampTouchEnd - this.timestampTouchStart > this.longTouchDuration); let positionMatch = true; if (evt && this.origin) { - const pos = stendhal.ui.html.extractPosition(evt); + const pos = HTMLUtil.extractPosition(evt); // if position has moved too much it's not a long touch positionMatch = (Math.abs(pos.pageX - this.origin.x) <= this.moveThreshold) && (Math.abs(pos.pageY - this.origin.y) <= this.moveThreshold); diff --git a/src/js/stendhal/ui/UI.ts b/src/js/stendhal/ui/UI.ts index b718905db4..a0e77d5260 100644 --- a/src/js/stendhal/ui/UI.ts +++ b/src/js/stendhal/ui/UI.ts @@ -9,8 +9,8 @@ * * ***************************************************************************/ -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { stendhal } from "../stendhal"; import { UIComponentEnum } from "./UIComponentEnum"; import { SoftwareJoystickController } from "./SoftwareJoystickController"; diff --git a/src/js/stendhal/ui/ViewPort.ts b/src/js/stendhal/ui/ViewPort.ts index 11179fc93f..fba92cd20d 100644 --- a/src/js/stendhal/ui/ViewPort.ts +++ b/src/js/stendhal/ui/ViewPort.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2024 - Stendhal * + * (C) Copyright 2003-2026 - Stendhal * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,10 +9,10 @@ * * ***************************************************************************/ -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa"; +import { stendhal } from "../stendhal"; -import { HeldObject } from "./HeldObject"; +import { HeldObject, HeldObjectManager } from "./HeldObject"; import { ui } from "./UI"; import { UIComponentEnum } from "./UIComponentEnum"; @@ -22,7 +22,6 @@ import { ActionContextMenu } from "./dialog/ActionContextMenu"; import { DropQuantitySelectorDialog } from "./dialog/DropQuantitySelectorDialog"; import { Client } from "../Client"; -import { singletons } from "../SingletonRepo"; import { AchievementBanner } from "../sprite/AchievementBanner"; import { EmojiSprite } from "../sprite/EmojiSprite"; @@ -30,7 +29,14 @@ import { NotificationBubble } from "../sprite/NotificationBubble"; import { SpeechBubble } from "../sprite/SpeechBubble"; import { TextBubble } from "../sprite/TextBubble"; +import { TileMap } from "data/TileMap"; +import { Zone } from "entity/Zone"; +import { Canvas, RenderingContext2D } from "util/Types"; +import { WeatherRenderer } from "util/WeatherRenderer"; +import { Debug } from "../util/Debug"; import { Point } from "../util/Point"; +import { HTMLUtil } from "./HTMLUtil"; +import { TouchHandler } from "./TouchHandler"; /** @@ -39,41 +45,42 @@ import { Point } from "../util/Point"; export class ViewPort { /** Horizontal screen offset in pixels. */ - private offsetX = 0; + public offsetX = 0; /** Vertical screen offset in pixels. */ - private offsetY = 0; + public offsetY = 0; /** Prevents adjusting offset based on player position. */ - private freeze = false; + public freeze = false; /** Time of most recent redraw. */ private timeStamp = Date.now(); // dimensions // TODO: remove & use CSS style instead - private readonly width: number; - private readonly height: number; + public readonly width: number; + public readonly height: number; /** Drawing context. */ - private ctx: CanvasRenderingContext2D; + private ctx: RenderingContext2D; /** Map tile pixel width. */ - private readonly targetTileWidth = 32; + public readonly targetTileWidth = 32; /** Map tile pixel height. */ - private readonly targetTileHeight = 32; + public readonly targetTileHeight = 32; private drawingError = false; /** Active speech bubbles to draw. */ - private textSprites: SpeechBubble[] = []; + public textSprites: SpeechBubble[] = []; /** Active notification bubbles/achievement banners to draw. */ private notifSprites: TextBubble[] = []; /** Active emoji sprites to draw. */ private emojiSprites: EmojiSprite[] = []; /** Handles drawing weather in viewport. */ - private weatherRenderer = singletons.getWeatherRenderer(); + private weatherRenderer = WeatherRenderer.get(); /** Coloring method of current zone. */ - private filter?: string; // deprecated, use `HSLFilter` + public filter?: string; // deprecated, use `HSLFilter` /** Coloring filter of current zone. */ - private HSLFilter?: string; + public HSLFilter?: string; private colorMethod = ""; private blendMethod = ""; // FIXME: unused + private map: TileMap; /** Styles to be applied when chat panel is not floating. */ private readonly initialStyle: {[prop: string]: string}; @@ -81,7 +88,6 @@ export class ViewPort { /** Singleton instance. */ private static instance: ViewPort; - /** * Retrieves singleton instance. */ @@ -96,6 +102,7 @@ export class ViewPort { * Hidden singleton constructor. */ private constructor() { + this.map = TileMap.get(); const element = this.getElement() as HTMLCanvasElement; this.ctx = element.getContext("2d")!; this.width = element.width; @@ -114,11 +121,11 @@ export class ViewPort { /** * Retrieves the viewport element. * - * @return {HTMLElement} + * @return {HTMLCanvasElement} * Viewport `HTMLElement`. */ - public getElement(): HTMLElement { - return document.getElementById("viewport")!; + public getElement(): HTMLCanvasElement { + return document.getElementById("viewport")! as HTMLCanvasElement; } /** @@ -128,10 +135,10 @@ export class ViewPort { var startTime = new Date().getTime(); if (marauroa.me && document.visibilityState === "visible") { - if (marauroa.currentZoneName === stendhal.data.map.currentZoneName - || stendhal.data.map.currentZoneName === "int_vault" - || stendhal.data.map.currentZoneName === "int_adventure_island" - || stendhal.data.map.currentZoneName === "tutorial_island") { + if (marauroa.currentZoneName === this.map.currentZoneName + || this.map.currentZoneName === "int_vault" + || this.map.currentZoneName === "int_adventure_island" + || this.map.currentZoneName === "tutorial_island") { this.drawingError = false; this.ctx.globalAlpha = 1.0; @@ -144,12 +151,14 @@ export class ViewPort { // FIXME: filter should not be applied to "blend" layers //this.applyFilter(); - stendhal.data.map.parallax.draw(this.ctx, this.offsetX, this.offsetY); - stendhal.data.map.strategy.render(this.ctx.canvas, this, tileOffsetX, tileOffsetY, this.targetTileWidth, this.targetTileHeight); + this.map.parallax.draw(this.ctx, this.offsetX, this.offsetY); + this.map.strategy.render(this.ctx.canvas, this, tileOffsetX, tileOffsetY, this.targetTileWidth, this.targetTileHeight); this.weatherRenderer.draw(this.ctx); //this.removeFilter(); - this.applyHSLFilter(); + if (!Debug.isActive("light")) { + this.applyHSLFilter(); + } this.drawEntitiesTop(); this.drawEmojiSprites(); this.drawTextSprites(); @@ -160,8 +169,8 @@ export class ViewPort { (ui.get(UIComponentEnum.PlayerEquipment) as PlayerEquipmentComponent).update(); } } - window.setTimeout(function() { - stendhal.ui.gamewindow.draw.apply(stendhal.ui.gamewindow, arguments); + window.setTimeout(() => { + this.draw(); }, Math.max((1000/20) - (new Date().getTime()-startTime), 1)); } @@ -198,9 +207,6 @@ export class ViewPort { return; } this.ctx.save(); - // FIXME: is this the appropriate alpha level to use? "color_method" value from server doesn't - // appear to include alpha information - this.ctx.globalAlpha = 0.5; this.ctx.globalCompositeOperation = (this.colorMethod || this.ctx.globalCompositeOperation) as GlobalCompositeOperation; this.ctx.fillStyle = this.HSLFilter; this.ctx.fillRect(this.offsetX, this.offsetY, this.ctx.canvas.width, this.ctx.canvas.height); @@ -262,11 +268,10 @@ export class ViewPort { * Draws overall entity sprites. */ drawEntities() { - var currentTime = new Date().getTime(); - var time = currentTime - this.timeStamp; + let currentTime = new Date().getTime(); + let time = currentTime - this.timeStamp; this.timeStamp = currentTime; - for (var i in stendhal.zone.entities) { - var entity = stendhal.zone.entities[i]; + for (let entity of Zone.get().entities) { if (typeof(entity.draw) != "undefined") { entity.updatePosition(time); entity.draw(this.ctx); @@ -278,9 +283,7 @@ export class ViewPort { * Draws titles & HP bars associated with entities. */ drawEntitiesTop() { - var i; - for (i in stendhal.zone.entities) { - const entity = stendhal.zone.entities[i]; + for (let entity of Zone.get().entities) { if (typeof(entity.setStatusBarOffset) !== "undefined") { entity.setStatusBarOffset(); } @@ -335,10 +338,10 @@ export class ViewPort { /** * Updates viewport drawing position of map based on player position. * - * @param canvas {HTMLCanvasElement} + * @param canvas {Canvas} * Viewport canvas element. */ - adjustView(canvas: HTMLCanvasElement) { + adjustView(canvas: Canvas) { // IE does not support ctx.resetTransform(), so use the following workaround: this.ctx.setTransform(1, 0, 0, 1, 0, 0); @@ -353,10 +356,10 @@ export class ViewPort { } // Keep the world within the screen view - centerX = Math.min(centerX, stendhal.data.map.zoneSizeX * this.targetTileWidth - canvas.width); + centerX = Math.min(centerX, this.map.zoneSizeX * this.targetTileWidth - canvas.width); centerX = Math.max(centerX, 0); - centerY = Math.min(centerY, stendhal.data.map.zoneSizeY * this.targetTileHeight - canvas.height); + centerY = Math.min(centerY, this.map.zoneSizeY * this.targetTileHeight - canvas.height); centerY = Math.max(centerY, 0); if (this.freeze) { @@ -492,13 +495,14 @@ export class ViewPort { const mHandle: any = {}; mHandle._onMouseDown = function(e: MouseEvent|TouchEvent) { - var pos = stendhal.ui.html.extractPosition(e); - if (stendhal.ui.touch.isTouchEvent(e)) { - if (stendhal.ui.touch.holding()) { + let touchHandler = TouchHandler.get(); + var pos = HTMLUtil.extractPosition(e); + if (touchHandler.isTouchEvent(e)) { + if (touchHandler.holding()) { // prevent default viewport action when item is "held" return; } - stendhal.ui.touch.onTouchStart(pos.pageX, pos.pageY); + touchHandler.onTouchStart(pos.pageX, pos.pageY); } if (stendhal.ui.globalpopup) { stendhal.ui.globalpopup.close(); @@ -506,24 +510,26 @@ export class ViewPort { startX = pos.canvasRelativeX; startY = pos.canvasRelativeY; + let viewPort = ViewPort.get(); - var x = pos.canvasRelativeX + stendhal.ui.gamewindow.offsetX; - var y = pos.canvasRelativeY + stendhal.ui.gamewindow.offsetY; + var x = pos.canvasRelativeX + viewPort.offsetX; + var y = pos.canvasRelativeY + viewPort.offsetY; // override ground/entity action if there is a text bubble - if (stendhal.ui.gamewindow.textBubbleAt(x, y+15)) { + if (viewPort.textBubbleAt(x, y+15)) { return; } - entity = stendhal.zone.entityAt(x, y); + let zone = Zone.get(); + entity = zone.entityAt(x, y); stendhal.ui.timestampMouseDown = +new Date(); if (e.type !== "dblclick" && e.target) { - e.target.addEventListener("mousemove", mHandle.onDrag); + e.target.addEventListener("mousemove", mHandle.onDrag, {passive: true}); e.target.addEventListener("mouseup", mHandle.onMouseUp); - e.target.addEventListener("touchmove", mHandle.onDrag); + e.target.addEventListener("touchmove", mHandle.onDrag, {passive: true}); e.target.addEventListener("touchend", mHandle.onMouseUp); - } else if (entity == stendhal.zone.ground) { + } else if (entity == zone.ground) { entity.onclick(pos.canvasRelativeX, pos.canvasRelativeY, true); } } @@ -540,14 +546,15 @@ export class ViewPort { } mHandle.onMouseUp = function(e: MouseEvent|TouchEvent) { - const is_touch = stendhal.ui.touch.isTouchEvent(e); + let touchHandler = TouchHandler.get(); + const is_touch = touchHandler.isTouchEvent(e); if (is_touch) { - stendhal.ui.touch.onTouchEnd(e); + touchHandler.onTouchEnd(); } - var pos = stendhal.ui.html.extractPosition(e); - const long_touch = is_touch && stendhal.ui.touch.isLongTouch(e); + var pos = HTMLUtil.extractPosition(e); + const long_touch = is_touch && touchHandler.isLongTouch(e); if ((e instanceof MouseEvent && mHandle.isRightClick(e)) || long_touch) { - if (entity != stendhal.zone.ground) { + if (entity != Zone.get().ground) { const append: any[] = []; /* if (long_touch) { @@ -566,11 +573,11 @@ export class ViewPort { } mHandle.onDrag = function(e: MouseEvent) { - if (stendhal.ui.touch.isTouchEvent(e)) { - stendhal.ui.gamewindow.onDragStart(e); + if (TouchHandler.get().isTouchEvent(e)) { + ViewPort.get().onDragStart(e); } - var pos = stendhal.ui.html.extractPosition(e); + var pos = HTMLUtil.extractPosition(e); var xDiff = startX - pos.offsetX; var yDiff = startY - pos.offsetY; // It's not really a click if the mouse has moved too much. @@ -597,11 +604,12 @@ export class ViewPort { * Updates cursor style when positioned over an element or entity. */ onMouseMove(e: MouseEvent) { - var pos = stendhal.ui.html.extractPosition(e); - var x = pos.canvasRelativeX + stendhal.ui.gamewindow.offsetX; - var y = pos.canvasRelativeY + stendhal.ui.gamewindow.offsetY; - var entity = stendhal.zone.entityAt(x, y); - stendhal.ui.gamewindow.getElement().style.cursor = entity.getCursor(x, y); + let viewPort = ViewPort.get(); + var pos = HTMLUtil.extractPosition(e); + var x = pos.canvasRelativeX + viewPort.offsetX; + var y = pos.canvasRelativeY + viewPort.offsetY; + var entity = Zone.get().entityAt(x, y); + viewPort.getElement().style.cursor = entity.getCursor(x, y); } /** @@ -640,47 +648,37 @@ export class ViewPort { /** * Handles engaging an item or corpse to be dragged. */ - onDragStart(e: DragEvent) { - var pos = stendhal.ui.html.extractPosition(e); + onDragStart(e: DragEvent|MouseEvent) { + var pos = HTMLUtil.extractPosition(e); let draggedEntity; - for (const obj of stendhal.zone.getEntitiesAt(pos.canvasRelativeX + stendhal.ui.gamewindow.offsetX, - pos.canvasRelativeY + stendhal.ui.gamewindow.offsetY)) { + let viewPort = ViewPort.get(); + let entries = Zone.get().getEntitiesAt( + pos.canvasRelativeX + viewPort.offsetX, + pos.canvasRelativeY + viewPort.offsetY); + for (const obj of entries) { if (obj.isDraggable()) { draggedEntity = obj; } } - var img = undefined; - let heldObject: HeldObject; - if (draggedEntity && draggedEntity.type === "item") { - img = stendhal.data.sprites.getAreaOf(stendhal.data.sprites.get(draggedEntity.sprite.filename), 32, 32); - heldObject = { - path: draggedEntity.getIdPath(), - zone: marauroa.currentZoneName, - quantity: draggedEntity.hasOwnProperty("quantity") ? draggedEntity["quantity"] : 1 - } - } else if (draggedEntity && draggedEntity.type === "corpse") { - img = stendhal.data.sprites.get(draggedEntity.sprite.filename); - heldObject = { - path: draggedEntity.getIdPath(), - zone: marauroa.currentZoneName, - quantity: 1 - } - } else { + if (!draggedEntity || !HeldObjectManager.get().prepare(draggedEntity, e)) { e.preventDefault(); return; } - if (stendhal.ui.touch.isTouchEvent(e)) { - singletons.getHeldObjectManager().set(heldObject, img, new Point(pos.pageX, pos.pageY)); - stendhal.ui.touch.setHolding(true); - } else { - stendhal.ui.heldObject = heldObject; + + let heldObject: HeldObject = { + path: draggedEntity.getIdPath(), + zone: marauroa.currentZoneName, + quantity: draggedEntity.hasOwnProperty("quantity") ? draggedEntity["quantity"] : 1 } - if (e.dataTransfer) { - window.event = e; // required by setDragImage polyfil - e.dataTransfer.setDragImage(img, 0, 0); + let touchHandler = TouchHandler.get(); + if (touchHandler.isTouchEvent(e)) { + HeldObjectManager.get().set(heldObject, new Point(pos.pageX, pos.pageY)); + touchHandler.setHolding(true); + } else { + HeldObjectManager.get().heldObject = heldObject; } } @@ -698,22 +696,24 @@ export class ViewPort { /** * Handles releasing an item or corpse from drag event. */ - onDrop(e: DragEvent) { - if (stendhal.ui.heldObject) { - var pos = stendhal.ui.html.extractPosition(e); - const targetSlot = stendhal.ui.html.parseSlotName((pos.target as HTMLElement).id); + onDrop(e: DragEvent|TouchEvent) { + let heldObject = HeldObjectManager.get().heldObject; + if (heldObject) { + var pos = HTMLUtil.extractPosition(e); + const targetSlot = HTMLUtil.parseSlotName((pos.target as HTMLElement).id); const action: any = { - "zone": stendhal.ui.heldObject.zone + "zone": heldObject.zone }; if (targetSlot === "viewport") { - action.x = Math.floor((pos.canvasRelativeX + stendhal.ui.gamewindow.offsetX) / 32).toString(); - action.y = Math.floor((pos.canvasRelativeY + stendhal.ui.gamewindow.offsetY) / 32).toString(); + let viewPort = ViewPort.get(); + action.x = Math.floor((pos.canvasRelativeX + viewPort.offsetX) / 32).toString(); + action.y = Math.floor((pos.canvasRelativeY + viewPort.offsetY) / 32).toString(); - var id = stendhal.ui.heldObject.path.substr(1, stendhal.ui.heldObject.path.length - 2); + var id = heldObject.path.substring(1, heldObject.path.length - 1); var drop = /\t/.test(id); if (drop) { action["type"] = "drop"; - action["source_path"] = stendhal.ui.heldObject.path; + action["source_path"] = heldObject.path; } else { action["type"] = "displace"; action["baseitem"] = id; @@ -722,28 +722,28 @@ export class ViewPort { let objectId = marauroa.me["id"]; if (e.type === "touchend" && targetSlot === "content") { // find the actual target ID for touch events - const container = stendhal.ui.equip.getByElement(stendhal.ui.html.extractTarget(event).parentElement!); + const container = stendhal.ui.equip.getByElement((HTMLUtil.extractTarget(event) as HTMLElement).parentElement!); if (container && container.object) { objectId = container.object.id; } } action["type"] = "equip"; - action["source_path"] = stendhal.ui.heldObject.path; + action["source_path"] = heldObject.path; action["target_path"] = "[" + objectId + "\t" + targetSlot + "]"; } - const quantity = stendhal.ui.heldObject.quantity; - const sourceSlot = stendhal.ui.heldObject.slot || "viewport"; + const quantity = heldObject.quantity; + const sourceSlot = heldObject.slot || "viewport"; // item was dropped - stendhal.ui.heldObject = undefined; + heldObject = undefined; - const touch_held = stendhal.ui.touch.holding() && quantity > 1; + const touch_held = TouchHandler.get().holding() && (quantity||1) > 1; // if ctrl is pressed or holding stackable item from touch event, we ask for the quantity // NOTE: don't create selector if touch source is ground if (e.ctrlKey || (touch_held && sourceSlot !== targetSlot)) { ui.createSingletonFloatingWindow("Quantity", new DropQuantitySelectorDialog(action, touch_held), pos.pageX - 50, pos.pageY - 25); } else { - singletons.getHeldObjectManager().onRelease(); + HeldObjectManager.get().onRelease(); marauroa.clientFramework.sendAction(action); } } @@ -756,11 +756,12 @@ export class ViewPort { * This is a workaround until it's figured out how to make it work using the same methods as mouse event. */ onTouchEnd(e: TouchEvent) { - stendhal.ui.touch.onTouchEnd(); - stendhal.ui.gamewindow.onDrop(e); - if (stendhal.ui.touch.holding()) { - stendhal.ui.touch.setHolding(false); - stendhal.ui.touch.unsetOrigin(); + let touchHandler = TouchHandler.get(); + touchHandler.onTouchEnd(); + ViewPort.get().onDrop(e); + if (touchHandler.holding()) { + touchHandler.setHolding(false); + touchHandler.unsetOrigin(); } // execute here because "touchend" event propagation is cancelled on the veiwport Client.handleClickIndicator(e); diff --git a/src/js/stendhal/ui/component/BagComponent.ts b/src/js/stendhal/ui/component/BagComponent.ts index fd1d9ff89d..2a42fd48d0 100644 --- a/src/js/stendhal/ui/component/BagComponent.ts +++ b/src/js/stendhal/ui/component/BagComponent.ts @@ -11,7 +11,7 @@ import { ItemInventoryComponent } from "./ItemInventoryComponent"; -declare var marauroa: any; +import { marauroa } from "marauroa" export class BagComponent extends ItemInventoryComponent { diff --git a/src/js/stendhal/ui/component/BuddyListComponent.ts b/src/js/stendhal/ui/component/BuddyListComponent.ts index 18ec0bd96e..bf6a20446b 100644 --- a/src/js/stendhal/ui/component/BuddyListComponent.ts +++ b/src/js/stendhal/ui/component/BuddyListComponent.ts @@ -16,10 +16,12 @@ import { Component } from "../toolkit/Component"; import { ActionContextMenu } from "../dialog/ActionContextMenu"; import { ChatInputComponent } from "./ChatInputComponent"; +import { Paths } from "../../data/Paths"; +import { stendhal } from "stendhal"; -declare let marauroa: any; -declare let stendhal: any; +import { marauroa } from "marauroa" +import { HTMLUtil } from "ui/HTMLUtil"; /** * displays the player stats @@ -66,11 +68,11 @@ export class BuddyListComponent extends Component { for (let i = 0; i < buddies.length; i++) { html += "
  • " + stendhal.ui.html.esc(buddies[i].name) + "
  • "; + html += "\"> " + HTMLUtil.esc(buddies[i].name) + ""; } if (this.lastHtml !== html) { diff --git a/src/js/stendhal/ui/component/ChatInputComponent.ts b/src/js/stendhal/ui/component/ChatInputComponent.ts index 22f2fd898d..83a23dbd19 100644 --- a/src/js/stendhal/ui/component/ChatInputComponent.ts +++ b/src/js/stendhal/ui/component/ChatInputComponent.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2024 - Faiumoni e. V. * + * (C) Copyright 2003-2026 - Faiumoni e. V. * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,18 +9,19 @@ * * ***************************************************************************/ -declare let marauroa: any; -declare let stendhal: any; +import { marauroa } from "marauroa"; +import { htmlImageStore } from "data/HTMLImageStore"; +import { stendhal } from "stendhal"; +import { Paths } from "../../data/Paths"; +import { ChatCompletionHelper } from "../../util/ChatCompletionHelper"; +import { KeyCode } from "../../util/KeyCode"; import { ChatPanel } from "../ChatPanel"; import { ui } from "../UI"; import { UIComponentEnum } from "../UIComponentEnum"; import { ChatOptionsDialog } from "../dialog/ChatOptionsDialog"; import { EmojiMapDialog } from "../dialog/EmojiMapDialog"; import { Component } from "../toolkit/Component"; -import { singletons } from "../../SingletonRepo"; -import { ChatCompletionHelper } from "../../util/ChatCompletionHelper"; -import { KeyCode } from "../../util/KeyCode"; /** @@ -86,7 +87,7 @@ export class ChatInputComponent extends Component { btn_emoji.innerText = "☺"; } else { // set image for emoji button - btn_emoji.appendChild(stendhal.data.sprites.get(stendhal.paths.sprites + "/emoji/smile.png").cloneNode()); + btn_emoji.appendChild(htmlImageStore.get(Paths.sprites + "/emoji/smile.png").cloneNode()); } } diff --git a/src/js/stendhal/ui/component/ChatLogComponent.ts b/src/js/stendhal/ui/component/ChatLogComponent.ts index d562354df6..94f661d1fc 100644 --- a/src/js/stendhal/ui/component/ChatLogComponent.ts +++ b/src/js/stendhal/ui/component/ChatLogComponent.ts @@ -14,8 +14,11 @@ import { Component } from "../toolkit/Component"; import { MenuItem } from "../../action/MenuItem"; import { UIComponentEnum } from "../UIComponentEnum"; import { singletons } from "../../SingletonRepo"; +import { Chat } from "../../util/Chat"; -declare var stendhal: any; +import { stendhal } from "../../stendhal"; +import { TouchHandler } from "ui/TouchHandler"; +import { HTMLUtil } from "ui/HTMLUtil"; /** @@ -33,19 +36,20 @@ export class ChatLogComponent extends Component { super("chat"); this.refresh(); this.scrollStateBottom = this.isAtBottom(); + let touchHandler = TouchHandler.get(); this.componentElement.addEventListener("mouseup", (evt: MouseEvent) => { this.onContextMenu(evt) }); this.componentElement.addEventListener("touchstart", (evt: TouchEvent) => { - const pos = stendhal.ui.html.extractPosition(evt); - stendhal.ui.touch.onTouchStart(pos.pageX, pos.pageY); - }); + const pos = HTMLUtil.extractPosition(evt); + touchHandler.onTouchStart(pos.pageX, pos.pageY); + }, {passive: true}); this.componentElement.addEventListener("touchend", (evt: TouchEvent) => { - stendhal.ui.touch.onTouchEnd(); + touchHandler.onTouchEnd(); this.onContextMenu(evt) // clean up - stendhal.ui.touch.unsetOrigin(); + touchHandler.unsetOrigin(); }); this.scrollListener = (evt: Event) => { @@ -106,13 +110,13 @@ export class ChatLogComponent extends Component { /** * Adds a line of text. * - * @param type {string} + * @param type * Message type. - * @param message {string} + * @param message * Text to be added. - * @param orator {string} + * @param orator * Name of entity making the expression (default: `undefined`). - * @param timestamp {boolean} + * @param timestamp * If `false`, suppresses prepending message with timestamp (default: `true`). */ public addLine(type: string, message: string, orator?: string, timestamp=true) { @@ -131,7 +135,7 @@ export class ChatLogComponent extends Component { const rcol = document.createElement("div"); rcol.className = "logcolR log" + type; - rcol.innerHTML += this.formatLogEntry(message); + rcol.innerHTML += Chat.formatLogEntry(message); const row = document.createElement("div"); row.className = "logrow"; @@ -192,122 +196,7 @@ export class ChatLogComponent extends Component { this.add(row); } - /** - * Formats displayed text. - * - * @param message {string} - * Text to parse for special characters. - * @return {string} - * Text formatted with keywords & item names highlighted. - */ - private formatLogEntry(message: string): string { - if (!message) { - return ""; - } - let res = ""; - let delims = [" ", ",", ".", "!", "?", ":", ";"]; - let length = message.length; - let inHighlight = false, inUnderline = false, - inHighlightQuote = false, inUnderlineQuote = false; - for (let i = 0; i < length; i++) { - let c = message[i]; - if (c === "\\") { - let n = message[i + 1]; - res += n; - i++; - - // Highlight Start? - } else if (c === "#") { - if (inHighlight) { - res += c; - continue; - } - let n = message[i + 1]; - if (n === "#") { - res += c; - i++; - continue; - } - if (n === "'") { - inHighlightQuote = true; - i++; - } - inHighlight = true; - res += ""; - - // Underline start? - } else if (c === "§") { - if (inUnderline) { - res += c; - continue; - } - let n = message[i + 1]; - if (n === "§") { - res += c; - i++; - continue; - } - if (n === "'") { - inUnderlineQuote = true; - i++; - } - inUnderline = true; - res += ""; - - // End Highlight and Underline? - } else if (c === "'") { - if (inUnderlineQuote) { - inUnderline = false; - inUnderlineQuote = false; - res += ""; - continue; - } - if (inHighlightQuote) { - inHighlight = false; - inHighlightQuote = false; - res += ""; - continue; - } - res += c; - - // HTML escape - } else if (c === "<") { - res += "<"; - - // End of word - } else if (delims.indexOf(c) > -1) { - let n = message[i + 1]; - if (c === " " || n === " " || n == undefined) { - if (inUnderline && !inUnderlineQuote && !inHighlightQuote) { - inUnderline = false; - res += "" + c; - continue; - } - if (inHighlight && !inUnderlineQuote && !inHighlightQuote) { - inHighlight = false; - res += "" + c; - continue; - } - } - res += c; - - // Normal characters - } else { - res += c; - } - } - - // Close opened formattings - if (inUnderline) { - res += ""; - } - if (inHighlight) { - res += ""; - } - - return res; - } /** * Removes all text from log. @@ -464,7 +353,7 @@ export class ChatLogComponent extends Component { if (evt.type === "mouseup" && evt.button != 2) { return; } - if (evt.type === "touchend" && !stendhal.ui.touch.isLongTouch(evt)) { + if (evt.type === "touchend" && !TouchHandler.get().isLongTouch(evt)) { evt.preventDefault(); return; } @@ -480,7 +369,7 @@ export class ChatLogComponent extends Component { options.unshift(new MenuItem("Copy", function() { log.exportContents(); })); } - const pos = stendhal.ui.html.extractPosition(evt); + const pos = HTMLUtil.extractPosition(evt); stendhal.ui.actionContextMenu.set(ui.createSingletonFloatingWindow("Action", new LogContextMenu(options), pos.pageX - 50, pos.pageY - 5)); @@ -505,7 +394,7 @@ class LogContextMenu extends Component { let content = "
    "; for (let i = 0; i < this.options.length; i++) { - content += ""; + content += ""; } content += "
    "; this.componentElement.innerHTML = content; diff --git a/src/js/stendhal/ui/component/GroupMemberComponent.ts b/src/js/stendhal/ui/component/GroupMemberComponent.ts index 6981457a2f..944f9bb4bc 100644 --- a/src/js/stendhal/ui/component/GroupMemberComponent.ts +++ b/src/js/stendhal/ui/component/GroupMemberComponent.ts @@ -9,8 +9,7 @@ * * ***************************************************************************/ -declare let marauroa: any; -declare let stendhal: any; +import { marauroa } from "marauroa" import { ChatInputComponent } from "./ChatInputComponent"; @@ -24,6 +23,8 @@ import { StatBarComponent } from "../component/StatBarComponent"; import { ActionContextMenu } from "../dialog/ActionContextMenu"; import { singletons } from "../../SingletonRepo"; +import { stendhal } from "stendhal"; +import { Zone } from "entity/Zone"; /** @@ -45,7 +46,7 @@ export class GroupMemberComponent extends Component { this.hpBar = new StatBarComponent(); this.hpBar.componentElement.classList.add("group-member-hpbar"); // check for player to initialize HP bar value - const player = singletons.getZone().findPlayer(memberName); + const player = Zone.get().findPlayer(memberName); if (player) { // component hasn't been created yet so `Player.updateGroupStatus` will fail here this.hpBar.draw(player["hp"] / player["base_hp"]); diff --git a/src/js/stendhal/ui/component/GroupPanelComponent.ts b/src/js/stendhal/ui/component/GroupPanelComponent.ts index 64cb587ff8..84d7dec43d 100644 --- a/src/js/stendhal/ui/component/GroupPanelComponent.ts +++ b/src/js/stendhal/ui/component/GroupPanelComponent.ts @@ -9,8 +9,7 @@ * * ***************************************************************************/ -declare let marauroa: any; -declare let stendhal: any; +import { marauroa } from "marauroa" import { ChatInputComponent } from "./ChatInputComponent"; import { GroupMemberComponent } from "./GroupMemberComponent"; @@ -23,6 +22,7 @@ import { Panel } from "../toolkit/Panel"; import { TabPanelComponent } from "../toolkit/TabPanelComponent"; import { Chat } from "../../util/Chat"; +import { stendhal } from "stendhal"; /** diff --git a/src/js/stendhal/ui/component/ItemContainerImplementation.ts b/src/js/stendhal/ui/component/ItemContainerImplementation.ts index 1d024dc8f8..86c02e666c 100644 --- a/src/js/stendhal/ui/component/ItemContainerImplementation.ts +++ b/src/js/stendhal/ui/component/ItemContainerImplementation.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * (C) Copyright 2003-2024 - Faiumoni e. V. * + * (C) Copyright 2003-2026 - Faiumoni e. V. * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,17 +9,23 @@ * * ***************************************************************************/ -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa"; +import { stendhal } from "../../stendhal"; import { ui } from "../UI"; +import { Item } from "../../entity/Item"; import { ActionContextMenu } from "../dialog/ActionContextMenu"; import { DropQuantitySelectorDialog } from "../dialog/DropQuantitySelectorDialog"; -import { Item } from "../../entity/Item"; import { singletons } from "../../SingletonRepo"; +import { htmlImageStore } from "data/HTMLImageStore"; +import { HeldObjectManager } from "ui/HeldObject"; +import { HTMLUtil } from "ui/HTMLUtil"; +import { TouchHandler } from "ui/TouchHandler"; +import { ViewPort } from "ui/ViewPort"; +import { Paths } from "../../data/Paths"; import { Point } from "../../util/Point"; @@ -35,6 +41,7 @@ export class ItemContainerImplementation { // marked for updating certain attributes private dirty = false; + private touchHandler = TouchHandler.get(); // TODO: replace usage of global document.getElementById() @@ -60,7 +67,7 @@ export class ItemContainerImplementation { }); e.addEventListener("touchmove", (event: TouchEvent) => { this.onTouchMove(event); - }); + }, {passive: true}); e.addEventListener("drop", (event: DragEvent) => { this.onDrop(event) }); @@ -72,7 +79,7 @@ export class ItemContainerImplementation { }); e.addEventListener("touchstart", (event: TouchEvent) => { this.onTouchStart(event) - }); + }, {passive: true}); e.addEventListener("touchend", (event: TouchEvent) => { this.onTouchEnd(event) }); @@ -131,7 +138,7 @@ export class ItemContainerImplementation { } e.style.backgroundImage = "url(" - + stendhal.data.sprites.checkPath(stendhal.paths.sprites + + htmlImageStore.checkPath(Paths.sprites + "/items/" + o["class"] + "/" + o["subclass"] + ".png") + ")"; e.style.backgroundPosition = (xOffset+1) + "px " + (yOffset+1) + "px"; @@ -148,7 +155,7 @@ export class ItemContainerImplementation { for (let i = cnt; i < this.size; i++) { let e = this.parentElement.querySelector("#" + this.slot +this. suffix + i) as HTMLElement; if (this.defaultImage) { - e.style.backgroundImage = "url(" + stendhal.paths.gui + "/" + this.defaultImage + ")"; + e.style.backgroundImage = "url(" + Paths.gui + "/" + this.defaultImage + ")"; } else { e.style.backgroundImage = "none"; } @@ -166,7 +173,7 @@ export class ItemContainerImplementation { private onDragStart(event: DragEvent|TouchEvent) { let myobject = this.object || marauroa.me; // some mobile browsers such as Chrome call "dragstart" via long touch - if (!myobject[this.slot] || (event.type === "dragstart" && stendhal.ui.touch.isTouchEngaged())) { + if (!myobject[this.slot] || (event.type === "dragstart" && this.touchHandler.isTouchEngaged())) { event.preventDefault(); return; } @@ -183,6 +190,10 @@ export class ItemContainerImplementation { const slotNumber = target.id.slice(this.slot.length + this.suffix.length); let item = myobject[this.slot].getByIndex(slotNumber); if (item) { + if (!HeldObjectManager.get().prepare(item, event)) { + return; + } + const heldObject = { path: item.getIdPath(), zone: marauroa.currentZoneName, @@ -190,15 +201,13 @@ export class ItemContainerImplementation { quantity: item.hasOwnProperty("quantity") ? item["quantity"] : 1 }; - const img = stendhal.data.sprites.getAreaOf(stendhal.data.sprites.get(item.sprite.filename), 32, 32); if (event instanceof DragEvent && event.dataTransfer) { - stendhal.ui.heldObject = heldObject; - event.dataTransfer.setDragImage(img, 0, 0); - } else if (stendhal.ui.touch.isTouchEvent(event)) { - stendhal.ui.touch.setHolding(true); + HeldObjectManager.get().heldObject = heldObject; + } else if (this.touchHandler.isTouchEvent(event)) { + this.touchHandler.setHolding(true); // TODO: move when supported by mouse events - const pos = stendhal.ui.html.extractPosition(event); - singletons.getHeldObjectManager().set(heldObject, img, new Point(pos.pageX, pos.pageY)); + const pos = HTMLUtil.extractPosition(event); + HeldObjectManager.get().set(heldObject, new Point(pos.pageX, pos.pageY)); } } else { event.preventDefault(); @@ -209,7 +218,7 @@ export class ItemContainerImplementation { * Handles displaying an icon for objects dragged with touch. */ private onTouchMove(event: TouchEvent) { - if (stendhal.ui.heldObject) { + if (HeldObjectManager.get().heldObject) { return; } this.onDragStart(event); @@ -242,13 +251,14 @@ export class ItemContainerImplementation { } private onDrop(event: DragEvent|TouchEvent) { + let heldObject = HeldObjectManager.get().heldObject; const myobject = this.object || marauroa.me; - if (stendhal.ui.heldObject) { - const pos = stendhal.ui.html.extractPosition(event); + if (heldObject) { + const pos = HTMLUtil.extractPosition(event); const id = (pos.target as HTMLElement).id; - const targetSlot = stendhal.ui.html.parseSlotName(id); + const targetSlot = HTMLUtil.parseSlotName(id); if (event.type === "touchend" && id === "viewport") { - stendhal.ui.gamewindow.onDrop(event); + ViewPort.get().onDrop(event); event.stopPropagation(); event.preventDefault(); return; @@ -258,7 +268,7 @@ export class ItemContainerImplementation { if (event.type === "touchend") { // find the actual target ID for touch events if (targetSlot === "content") { - const container = stendhal.ui.equip.getByElement(stendhal.ui.html.extractTarget(event).parentElement!); + const container = stendhal.ui.equip.getByElement((HTMLUtil.extractTarget(event) as HTMLElement).parentElement!); if (container && container.object) { objectId = container.object.id; } @@ -269,31 +279,31 @@ export class ItemContainerImplementation { } const action = { - "source_path": stendhal.ui.heldObject.path + "source_path": heldObject.path } as any; - const sameSlot = stendhal.ui.heldObject.slot === targetSlot; + const sameSlot = heldObject.slot === targetSlot; if (sameSlot) { action["type"] = "reorder"; action["new_position"] = this.parseIndex(id) || "" + (this.size - 1); } else { action["type"] = "equip"; action["target_path"] = "[" + objectId + "\t" + targetSlot + "]"; - action["zone"] = stendhal.ui.heldObject.zone; + action["zone"] = heldObject.zone; } - const quantity = stendhal.ui.heldObject.quantity; - stendhal.ui.heldObject = undefined; + const quantity = heldObject.quantity || 1; + HeldObjectManager.get().heldObject = undefined; // if ctrl is pressed or holding stackable item from touch event, we ask for the quantity - const touch_held = stendhal.ui.touch.holding() && quantity > 1; + const touch_held = this.touchHandler.holding() && quantity > 1; const split_action = !sameSlot && ((event instanceof DragEvent && event.ctrlKey) || touch_held); if (split_action) { - const pos = stendhal.ui.html.extractPosition(event); + const pos = HTMLUtil.extractPosition(event); ui.createSingletonFloatingWindow("Quantity", new DropQuantitySelectorDialog(action, touch_held), pos.pageX - 50, pos.pageY - 25); } else { - singletons.getHeldObjectManager().onRelease(); + HeldObjectManager.get().onRelease(); marauroa.clientFramework.sendAction(action); } } @@ -318,9 +328,9 @@ export class ItemContainerImplementation { if (this.timestampMouseDown - this.timestampMouseDownPrev <= this.rightClickDuration) { // reset so subsequent single clicks/taps aren't counted this.timestampMouseDown = 0; - return (stendhal.ui.html.extractTarget(evt) as HTMLElement).id === this.lastClickedId; + return (HTMLUtil.extractTarget(evt) as HTMLElement).id === this.lastClickedId; } - this.lastClickedId = (stendhal.ui.html.extractTarget(evt) as HTMLElement).id; + this.lastClickedId = (HTMLUtil.extractTarget(evt) as HTMLElement).id; return false; } @@ -340,9 +350,9 @@ export class ItemContainerImplementation { return; } - let event = stendhal.ui.html.extractPosition(evt); + let event = HTMLUtil.extractPosition(evt); if ((event.target as any).dataItem) { - const long_touch = stendhal.ui.touch.isLongTouch(evt); + const long_touch = this.touchHandler.isLongTouch(evt); const context_action = (evt instanceof MouseEvent && this.isRightClick(evt)) || long_touch; if (this.quickPickup && !context_action) { marauroa.clientFramework.sendAction({ @@ -372,7 +382,7 @@ export class ItemContainerImplementation { stendhal.ui.actionContextMenu.set(ui.createSingletonFloatingWindow("Action", new ActionContextMenu((event.target as any).dataItem, append), event.pageX - 50, event.pageY - 5)); - } else if (!stendhal.ui.heldObject) { + } else if (!HeldObjectManager.get().heldObject) { if (!stendhal.config.getBoolean("inventory.double-click") || this.isDoubleClick(event)) { marauroa.clientFramework.sendAction({ type: "use", @@ -384,7 +394,7 @@ export class ItemContainerImplementation { } // clean up item held via touch - stendhal.ui.touch.setHolding(false); + this.touchHandler.setHolding(false); document.getElementById("viewport")!.focus(); } @@ -398,22 +408,22 @@ export class ItemContainerImplementation { } private onTouchStart(evt: TouchEvent) { - const pos = stendhal.ui.html.extractPosition(evt); - stendhal.ui.touch.onTouchStart(pos.pageX, pos.pageY); + const pos = HTMLUtil.extractPosition(evt); + this.touchHandler.onTouchStart(pos.pageX, pos.pageY); } private onTouchEnd(evt: TouchEvent) { - stendhal.ui.touch.onTouchEnd(); - if (stendhal.ui.touch.isLongTouch(evt) && !stendhal.ui.touch.holding()) { + this.touchHandler.onTouchEnd(); + if (this.touchHandler.isLongTouch(evt) && !this.touchHandler.holding()) { this.onMouseUp(evt); - } else if (stendhal.ui.touch.holding()) { + } else if (this.touchHandler.holding()) { evt.preventDefault(); this.onDrop(evt); - stendhal.ui.touch.setHolding(false); + this.touchHandler.setHolding(false); } // clean up touch handler - stendhal.ui.touch.unsetOrigin(); + this.touchHandler.unsetOrigin(); } /** @@ -427,14 +437,14 @@ export class ItemContainerImplementation { private updateCursor(target: HTMLElement, item?: Item) { if (item) { if (this.slot === "content" && stendhal.config.getBoolean("inventory.quick-pickup")) { - target.style.cursor = "url(" + stendhal.paths.sprites + target.style.cursor = "url(" + Paths.sprites + "/cursor/itempickupfromslot.png) 1 3, auto"; return; } target.style.cursor = item.getCursor(0, 0); return; } - target.style.cursor = "url(" + stendhal.paths.sprites + target.style.cursor = "url(" + Paths.sprites + "/cursor/normal.png) 1 3, auto"; } diff --git a/src/js/stendhal/ui/component/ItemInventoryComponent.ts b/src/js/stendhal/ui/component/ItemInventoryComponent.ts index 6f2662879f..7ad2dea094 100644 --- a/src/js/stendhal/ui/component/ItemInventoryComponent.ts +++ b/src/js/stendhal/ui/component/ItemInventoryComponent.ts @@ -12,7 +12,7 @@ import { Component } from "../toolkit/Component"; import { ItemContainerImplementation } from "./ItemContainerImplementation"; -declare var stendhal: any; +import { stendhal } from "../../stendhal"; /** * handles an item inventory @@ -68,7 +68,6 @@ export class ItemInventoryComponent extends Component { public override onParentClose() { let idx = stendhal.ui.equip.indexOf(this.itemContainerImplementation); - console.log(stendhal.ui.equip.getInventory(), idx); if (idx < 0) { console.log("Cannot cleanup unknown itemContainerImplementation"); return; diff --git a/src/js/stendhal/ui/component/KeyringComponent.ts b/src/js/stendhal/ui/component/KeyringComponent.ts index a5da2fb9bc..7c808267e4 100644 --- a/src/js/stendhal/ui/component/KeyringComponent.ts +++ b/src/js/stendhal/ui/component/KeyringComponent.ts @@ -11,7 +11,7 @@ import { ItemInventoryComponent } from "./ItemInventoryComponent"; -declare var marauroa: any; +import { marauroa } from "marauroa" export class KeyringComponent extends ItemInventoryComponent { diff --git a/src/js/stendhal/ui/component/MiniMapComponent.ts b/src/js/stendhal/ui/component/MiniMapComponent.ts index ea0a5d464e..5019f175f9 100644 --- a/src/js/stendhal/ui/component/MiniMapComponent.ts +++ b/src/js/stendhal/ui/component/MiniMapComponent.ts @@ -9,14 +9,17 @@ * * ***************************************************************************/ -declare let marauroa: any; -declare let stendhal: any; +import { marauroa } from "marauroa" import { Component } from "../toolkit/Component"; import { Player } from "../../entity/Player"; import { Color } from "../../data/color/Color"; +import { RenderingContext2D } from "util/Types"; +import { stendhal } from "stendhal"; +import { TileMap } from "data/TileMap"; +import { HTMLUtil } from "ui/HTMLUtil"; /** @@ -24,6 +27,7 @@ import { Color } from "../../data/color/Color"; */ export class MiniMapComponent extends Component { + private map: TileMap; private width = 128; private height = 128; private minimumScale = 2; @@ -32,7 +36,7 @@ export class MiniMapComponent extends Component { private mapWidth = 1; private mapHeight = 1; private scale = 1; - private bgImage!: HTMLCanvasElement; + private bgImage?: ImageBitmap; private lastZone?: number[]; // ground/collision colors @@ -43,6 +47,7 @@ export class MiniMapComponent extends Component { constructor() { super("minimap"); + this.map = TileMap.get(); this.componentElement.addEventListener("click", (event) => { this.onClick(event); }); @@ -53,8 +58,8 @@ export class MiniMapComponent extends Component { private zoneChange() { - this.mapWidth = stendhal.data.map.zoneSizeX; - this.mapHeight = stendhal.data.map.zoneSizeY; + this.mapWidth = this.map.zoneSizeX; + this.mapHeight = this.map.zoneSizeY; this.scale = Math.max(this.minimumScale, Math.min(this.height / this.mapHeight, this.width / this.mapWidth)); this.createBackgroundImage(); }; @@ -95,10 +100,10 @@ export class MiniMapComponent extends Component { } public draw() { - if (marauroa.currentZoneName === stendhal.data.map.currentZoneName - || stendhal.data.map.currentZoneName === "int_vault" - || stendhal.data.map.currentZoneName === "int_adventure_island" - || stendhal.data.map.currentZoneName === "tutorial_island") { + if (marauroa.currentZoneName === this.map.currentZoneName + || this.map.currentZoneName === "int_vault" + || this.map.currentZoneName === "int_adventure_island" + || this.map.currentZoneName === "tutorial_island") { this.scale = 10; @@ -119,7 +124,7 @@ export class MiniMapComponent extends Component { } } - drawBackground(ctx: CanvasRenderingContext2D) { + drawBackground(ctx: RenderingContext2D) { ctx.save(); ctx.imageSmoothingEnabled = false; @@ -137,10 +142,10 @@ export class MiniMapComponent extends Component { return; } - if (stendhal.data.map.collisionData !== this.lastZone) { - this.lastZone = stendhal.data.map.collisionData; - this.bgImage = document.createElement("canvas"); - let ctx = this.bgImage.getContext("2d")!; + if (this.map.collisionData !== this.lastZone) { + this.lastZone = this.map.collisionData; + let canvas = new OffscreenCanvas(width, height); + let ctx = canvas.getContext("2d")!; let imgData = ctx.createImageData(width, height); for (let y = 0; y < height; y++) { @@ -148,10 +153,10 @@ export class MiniMapComponent extends Component { let color = MiniMapComponent.COLOR_GROUND; // RGBA array. Find the actual position let pos = 4 * (y * width + x); - if (stendhal.data.map.collision(x, y)) { + if (this.map.collision(x, y)) { // red collision color = MiniMapComponent.COLOR_COLLISION; - } else if (stendhal.data.map.isProtected(x, y)) { + } else if (this.map.isProtected(x, y)) { // light green for protection color = MiniMapComponent.COLOR_PROTECTION; } @@ -161,14 +166,15 @@ export class MiniMapComponent extends Component { imgData.data[pos + 3] = 255; // opacity } } - this.bgImage.width = width; - this.bgImage.height = height; - ctx.putImageData(imgData, 0, 0); + if (this.bgImage) { + this.bgImage.close(); + } + this.bgImage = canvas.transferToImageBitmap(); } } - drawEntities(ctx: CanvasRenderingContext2D) { + drawEntities(ctx: RenderingContext2D) { ctx.fillStyle = Color.RED; ctx.strokeStyle = Color.BLACK; let isAdmin = marauroa.me["adminlevel"] && marauroa.me["adminlevel"] >= 600; @@ -215,10 +221,10 @@ export class MiniMapComponent extends Component { if (!stendhal.config.getBoolean("pathfinding.minimap")) { return; } - let pos = stendhal.ui.html.extractPosition(event); + let pos = HTMLUtil.extractPosition(event); let x = Math.floor((pos.canvasRelativeX + this.xOffset) / this.scale); let y = Math.floor((pos.canvasRelativeY + this.yOffset) / this.scale); - if (!stendhal.data.map.collision(x, y)) { + if (!this.map.collision(x, y)) { let action: any = { type: "moveto", x: x.toString(), diff --git a/src/js/stendhal/ui/component/OutfitPreviewComponent.ts b/src/js/stendhal/ui/component/OutfitPreviewComponent.ts index 91b8ab4910..9fc10a3892 100644 --- a/src/js/stendhal/ui/component/OutfitPreviewComponent.ts +++ b/src/js/stendhal/ui/component/OutfitPreviewComponent.ts @@ -17,8 +17,6 @@ import { Outfit } from "../../data/Outfit"; import { Direction } from "../../util/Direction"; import { StringUtil } from "../../util/StringUtil"; -declare var stendhal: any; - /** * Component to preview an entity's outfit sprite. @@ -30,7 +28,7 @@ export class OutfitPreviewComponent extends Component { private dir: Direction; private index: number; - private image?: HTMLImageElement; + private image?: ImageBitmap; private bgColor?: string; @@ -50,7 +48,8 @@ export class OutfitPreviewComponent extends Component { */ setOutfit(outfit: string, coloring?: string) { const otemp = Outfit.build(outfit, coloring); - otemp.toImage((image: HTMLImageElement) => { + otemp.toImage().then((image) => { + this.image?.close(); this.image = image; this.update(); }); @@ -140,4 +139,8 @@ export class OutfitPreviewComponent extends Component { } ctx.drawImage(this.image, this.index * w, (this.dir.val - 1) * h, w, h, 0, 0, w, h); } + + close() { + this.image?.close(); + } } diff --git a/src/js/stendhal/ui/component/PlayerEquipmentComponent.ts b/src/js/stendhal/ui/component/PlayerEquipmentComponent.ts index 50bb097baa..c4e6e913e8 100644 --- a/src/js/stendhal/ui/component/PlayerEquipmentComponent.ts +++ b/src/js/stendhal/ui/component/PlayerEquipmentComponent.ts @@ -12,7 +12,7 @@ import { Component } from "../toolkit/Component"; import { ItemContainerImplementation } from "./ItemContainerImplementation"; -declare var marauroa: any; +import { marauroa } from "marauroa" /** * manage the equipment which is used by the player diff --git a/src/js/stendhal/ui/component/PlayerStatsComponent.ts b/src/js/stendhal/ui/component/PlayerStatsComponent.ts index 74e44041ec..3428f77d75 100644 --- a/src/js/stendhal/ui/component/PlayerStatsComponent.ts +++ b/src/js/stendhal/ui/component/PlayerStatsComponent.ts @@ -22,8 +22,8 @@ import { singletons } from "../../SingletonRepo"; import { Item } from "../../entity/Item"; -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { stendhal } from "../../stendhal"; /** diff --git a/src/js/stendhal/ui/component/StatBarComponent.ts b/src/js/stendhal/ui/component/StatBarComponent.ts index b25b384ca1..2f13f329a0 100644 --- a/src/js/stendhal/ui/component/StatBarComponent.ts +++ b/src/js/stendhal/ui/component/StatBarComponent.ts @@ -11,6 +11,7 @@ import { Component } from "../toolkit/Component"; import { Color } from "../../data/color/Color"; +import { RenderingContext2D } from "util/Types"; /** * TODO: move this to `ui.toolkit`. @@ -18,7 +19,7 @@ import { Color } from "../../data/color/Color"; export class StatBarComponent extends Component { readonly canvas: HTMLCanvasElement; - protected readonly ctx: CanvasRenderingContext2D; + protected readonly ctx: RenderingContext2D; constructor(id?: string) { @@ -26,7 +27,7 @@ export class StatBarComponent extends Component { id = "statbar-template"; } super(id); - this.canvas = this.componentElement; + this.canvas = this.componentElement as HTMLCanvasElement; this.ctx = this.canvas.getContext("2d")!; } diff --git a/src/js/stendhal/ui/component/StatusesListComponent.ts b/src/js/stendhal/ui/component/StatusesListComponent.ts index b0c30a985d..6428451445 100644 --- a/src/js/stendhal/ui/component/StatusesListComponent.ts +++ b/src/js/stendhal/ui/component/StatusesListComponent.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright © 2023 - Arianne * + * Copyright © 2023-2026 - Arianne * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -11,10 +11,10 @@ import { Component } from "../toolkit/Component"; +import { htmlImageStore } from "data/HTMLImageStore"; +import { Paths } from "../../data/Paths"; import { User } from "../../entity/User"; -declare var stendhal: any; - export class StatusesListComponent extends Component { @@ -65,11 +65,11 @@ export class StatusesListComponent extends Component { } let iconPath: string; if (Object.keys(StatusesListComponent.iconPaths).indexOf(id) !== -1) { - iconPath = stendhal.paths.sprites + StatusesListComponent.iconPaths[id]; + iconPath = Paths.sprites + StatusesListComponent.iconPaths[id]; } else { - iconPath = stendhal.paths.sprites + "/status/panel/" + id + ".png"; + iconPath = Paths.sprites + "/status/panel/" + id + ".png"; } - const icon = stendhal.data.sprites.get(iconPath); + const icon = htmlImageStore.get(iconPath); icon.className = "status-icon"; this.componentElement.appendChild(icon); } diff --git a/src/js/stendhal/ui/component/ZoneInfoComponent.ts b/src/js/stendhal/ui/component/ZoneInfoComponent.ts index d4490a2395..7222a7642c 100644 --- a/src/js/stendhal/ui/component/ZoneInfoComponent.ts +++ b/src/js/stendhal/ui/component/ZoneInfoComponent.ts @@ -11,7 +11,7 @@ import { Component } from "../toolkit/Component"; -declare let marauroa: any; +import { marauroa } from "marauroa" /** * zone info diff --git a/src/js/stendhal/ui/dialog/ActionContextMenu.ts b/src/js/stendhal/ui/dialog/ActionContextMenu.ts index 003f2258eb..d4465734ac 100644 --- a/src/js/stendhal/ui/dialog/ActionContextMenu.ts +++ b/src/js/stendhal/ui/dialog/ActionContextMenu.ts @@ -14,8 +14,9 @@ import { UIComponentEnum } from "../UIComponentEnum"; import { Component } from "../toolkit/Component"; import { ChatInputComponent } from "../component/ChatInputComponent"; -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { stendhal } from "../../stendhal"; +import { HTMLUtil } from "ui/HTMLUtil"; export class ActionContextMenu extends Component { private entity: any; @@ -31,7 +32,7 @@ export class ActionContextMenu extends Component { var content = "
    "; for (var i = 0; i < this.actions.length; i++) { // FIXME: the addition of "sub-actionbutton" class should no longer be needed - content += ""; + content += ""; } content += "
    "; this.componentElement.innerHTML = content; diff --git a/src/js/stendhal/ui/dialog/ApplicationMenuDialog.ts b/src/js/stendhal/ui/dialog/ApplicationMenuDialog.ts index 4cbaa12d5d..bde75e9c0d 100644 --- a/src/js/stendhal/ui/dialog/ApplicationMenuDialog.ts +++ b/src/js/stendhal/ui/dialog/ApplicationMenuDialog.ts @@ -9,13 +9,14 @@ * * ***************************************************************************/ -declare var stendhal: any; +import { stendhal } from "../../stendhal"; import { DialogContentComponent } from "../toolkit/DialogContentComponent"; import { singletons } from "../../SingletonRepo"; import { Debug } from "../../util/Debug"; import { ScreenCapture } from "../../util/ScreenCapture"; +import { HTMLUtil } from "ui/HTMLUtil"; interface MenuAction { @@ -136,14 +137,14 @@ export class ApplicationMenuDialog extends DialogContentComponent { var content = ""; for (var i = 0; i < this.actions.length; i++) { - content += "

    " + stendhal.ui.html.esc(this.actions[i].title) + "

    " + content += "

    " + HTMLUtil.esc(this.actions[i].title) + "

    " for (var j = 0; j < this.actions[i].children.length; j++) { const action = this.actions[i].children[j]; let title = action.title; if (action.alt && action.condition && action.condition()) { title = action.alt; } - content += "
    "; + content += "
    "; } content += "
    "; } diff --git a/src/js/stendhal/ui/dialog/ChatOptionsDialog.ts b/src/js/stendhal/ui/dialog/ChatOptionsDialog.ts index 81a667faf5..caa3315b6a 100644 --- a/src/js/stendhal/ui/dialog/ChatOptionsDialog.ts +++ b/src/js/stendhal/ui/dialog/ChatOptionsDialog.ts @@ -14,8 +14,8 @@ import { DialogContentComponent } from "../toolkit/DialogContentComponent"; import { Chat } from "../../util/Chat"; import { singletons } from "../../SingletonRepo"; -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { stendhal } from "../../stendhal"; /** diff --git a/src/js/stendhal/ui/dialog/CreateAccountDialog.ts b/src/js/stendhal/ui/dialog/CreateAccountDialog.ts index 0909bd725b..a81113dfe0 100644 --- a/src/js/stendhal/ui/dialog/CreateAccountDialog.ts +++ b/src/js/stendhal/ui/dialog/CreateAccountDialog.ts @@ -13,7 +13,7 @@ import { ui } from "../UI"; import { DialogContentComponent } from "../toolkit/DialogContentComponent"; import { LoginDialog } from "./LoginDialog"; -declare var marauroa: any; +import { marauroa } from "marauroa" /** * a dialog to enter username and password diff --git a/src/js/stendhal/ui/dialog/DropQuantitySelectorDialog.ts b/src/js/stendhal/ui/dialog/DropQuantitySelectorDialog.ts index 5c2da2930c..b4291550a0 100644 --- a/src/js/stendhal/ui/dialog/DropQuantitySelectorDialog.ts +++ b/src/js/stendhal/ui/dialog/DropQuantitySelectorDialog.ts @@ -9,10 +9,10 @@ * * ***************************************************************************/ -declare var marauroa: any; +import { marauroa } from "marauroa"; +import { HeldObjectManager } from "ui/HeldObject"; import { Component } from "../toolkit/Component"; -import { singletons } from "../../SingletonRepo"; export class DropQuantitySelectorDialog extends Component { @@ -49,7 +49,7 @@ export class DropQuantitySelectorDialog extends Component { * Drops selected quantity from stack. */ private onDrop(event: Event) { - singletons.getHeldObjectManager().onRelease(); + HeldObjectManager.get().onRelease(); let quantityStr = (this.child(".quantityselectorvalue") as HTMLInputElement).value; let quantity = parseInt(quantityStr); if (quantity > 0) { @@ -64,7 +64,7 @@ export class DropQuantitySelectorDialog extends Component { * Drops entire stack. */ private onDropAll(event: Event) { - singletons.getHeldObjectManager().onRelease(); + HeldObjectManager.get().onRelease(); marauroa.clientFramework.sendAction(this.action); this.componentElement.dispatchEvent(new Event("close")); event.preventDefault(); diff --git a/src/js/stendhal/ui/dialog/EmojiMapDialog.ts b/src/js/stendhal/ui/dialog/EmojiMapDialog.ts index 9fab015c4d..b3812f4302 100644 --- a/src/js/stendhal/ui/dialog/EmojiMapDialog.ts +++ b/src/js/stendhal/ui/dialog/EmojiMapDialog.ts @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright © 2023-2024 - Stendhal * + * Copyright © 2023-2026 - Stendhal * *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * @@ -9,11 +9,13 @@ * * ***************************************************************************/ -import { DialogContentComponent } from "../toolkit/DialogContentComponent"; import { singletons } from "../../SingletonRepo"; +import { Paths } from "../../data/Paths"; +import { DialogContentComponent } from "../toolkit/DialogContentComponent"; -declare var marauroa: any; -declare var stendhal: any; +import { htmlImageStore } from "data/HTMLImageStore"; +import { marauroa } from "marauroa"; +import { stendhal } from "../../stendhal"; export class EmojiMapDialog extends DialogContentComponent { @@ -83,7 +85,7 @@ export class EmojiMapDialog extends DialogContentComponent { const button = document.createElement("button"); button.classList.add("shortcut-button", "emoji-text"); if (!this.sysEmojis) { - button.appendChild(stendhal.data.sprites.get(stendhal.paths.sprites + "/emoji/" + emoji + ".png").cloneNode()); + button.appendChild(htmlImageStore.get(Paths.sprites + "/emoji/" + emoji + ".png").cloneNode()); } else { button.innerText = emoji; } diff --git a/src/js/stendhal/ui/dialog/ImageViewerDialog.ts b/src/js/stendhal/ui/dialog/ImageViewerDialog.ts index a72c518f88..6b6b9ca3f4 100644 --- a/src/js/stendhal/ui/dialog/ImageViewerDialog.ts +++ b/src/js/stendhal/ui/dialog/ImageViewerDialog.ts @@ -11,8 +11,8 @@ import { Component } from "../toolkit/Component"; -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { stendhal } from "../../stendhal"; /** * a dialog to display images diff --git a/src/js/stendhal/ui/dialog/LoginDialog.ts b/src/js/stendhal/ui/dialog/LoginDialog.ts index 4c244428c9..3412b681f4 100644 --- a/src/js/stendhal/ui/dialog/LoginDialog.ts +++ b/src/js/stendhal/ui/dialog/LoginDialog.ts @@ -14,7 +14,7 @@ import { ui } from "../UI"; import { DialogContentComponent } from "../toolkit/DialogContentComponent"; import { CreateAccountDialog } from "./CreateAccountDialog"; -declare var marauroa: any; +import { marauroa } from "marauroa" /** * a dialog to enter username and password diff --git a/src/js/stendhal/ui/dialog/SettingsDialog.ts b/src/js/stendhal/ui/dialog/SettingsDialog.ts index 49e8bd3360..783f999577 100644 --- a/src/js/stendhal/ui/dialog/SettingsDialog.ts +++ b/src/js/stendhal/ui/dialog/SettingsDialog.ts @@ -9,7 +9,7 @@ * * ***************************************************************************/ -declare var stendhal: any; +import { stendhal } from "../../stendhal"; import { GeneralTab } from "./settings/GeneralTab"; import { InputTab } from "./settings/InputTab"; diff --git a/src/js/stendhal/ui/dialog/TradeDialog.ts b/src/js/stendhal/ui/dialog/TradeDialog.ts index 691866241d..2deb045534 100644 --- a/src/js/stendhal/ui/dialog/TradeDialog.ts +++ b/src/js/stendhal/ui/dialog/TradeDialog.ts @@ -15,7 +15,7 @@ import { DialogContentComponent } from "../toolkit/DialogContentComponent"; import { ui } from "../UI"; import { UIComponentEnum } from "../UIComponentEnum"; -declare var marauroa: any; +import { marauroa } from "marauroa" /** * a dialog to display images diff --git a/src/js/stendhal/ui/dialog/TravelLogDialog.ts b/src/js/stendhal/ui/dialog/TravelLogDialog.ts index 54e035f2c4..cec17d26d6 100644 --- a/src/js/stendhal/ui/dialog/TravelLogDialog.ts +++ b/src/js/stendhal/ui/dialog/TravelLogDialog.ts @@ -9,12 +9,14 @@ * * ***************************************************************************/ +import { Paths } from "../../data/Paths"; import { DialogContentComponent } from "../toolkit/DialogContentComponent"; import { ui } from "../UI"; import { UIComponentEnum } from "../UIComponentEnum"; -declare var marauroa: any; -declare var stendhal: any; +import { marauroa } from "marauroa" +import { stendhal } from "../../stendhal"; +import { HTMLUtil } from "ui/HTMLUtil"; /** * a dialog to display images @@ -57,9 +59,9 @@ export class TravelLogDialog extends DialogContentComponent { private createHtml(dataItems: string[]) { let buttons = ""; for (var i = 0; i < dataItems.length; i++) { - buttons = buttons + ""; + + HTMLUtil.esc(dataItems[i]) + ""; } this.child(".tavellogtabpanel")!.innerHTML = buttons; @@ -135,8 +137,8 @@ export class TravelLogDialog extends DialogContentComponent { var html = ""; for (var i = 0; i < dataItems.length; i++) { const currentItem = dataItems[i]; - html += "