Compare commits

..

7 commits

Author SHA1 Message Date
Jordan Irwin
17516e8cfc
Disable encrypted DependencyInfoBlock in APK build
Some checks failed
Android Build Test / build (push) Has been cancelled
Java CI / build (push) Has been cancelled
https://gitlab.com/fdroid/fdroiddata/-/issues/3330
2024-12-20 19:57:51 -08:00
Jordan Irwin
83133d02c7
Update Android release notes 2024-12-20 15:07:51 -08:00
Hendrik Brummermann
3a89bd576c updated android versionCode 2024-12-18 21:34:35 +01:00
Jordan Irwin
b91361d963
Remove unreachable code block 2024-12-17 20:02:56 -08:00
Jordan Irwin
bdfd0a1929
Fix random selecting between patrolling & wander idle behavior 2024-12-17 18:45:53 -08:00
Jordan Irwin
d19868ce10
Touch up scaling artifacts in necrosophia overlay 2024-12-15 22:29:04 -08:00
Hendrik Brummermann
08aad10188 updated version number 2024-12-15 08:14:24 +01:00
260 changed files with 8410 additions and 8114 deletions

View file

@ -7,56 +7,16 @@ on:
pull_request:
paths:
- 'app/android/**'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- id: job_info
uses: daleyjem/gh-job-id@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
job_name: 'build'
- uses: actions/checkout@v3
- name: Checkout Repository
uses: actions/checkout@v3
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- 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
- name: Build Android Packages
run: cd app/android/client && gradle assemble

View file

@ -29,15 +29,15 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v2
# 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@v3
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -46,7 +46,9 @@ jobs:
# and modify them (or add more) to build your code if your project
# uses a compiled language
- run: ant -noinput -f build.xml clean compile
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v2

View file

@ -20,15 +20,4 @@
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
</natures>
<filteredResources>
<filter>
<id>1771748271774</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

View file

@ -30,28 +30,29 @@ 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
[stendhalgame.org](https://stendhalgame.org/) in your mobile browser and click "install" when prompted.
https://stendhalgame.org/ in your mobile browser and click "install" when prompted.
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="75">](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 [stendhalgame.org](https://stendhalgame.org/download.html).
[F-Droid App Store](https://f-droid.org/) or download it from 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
[java.com](https://www.java.com/en/download) on Microsoft Windows. Java is included in
[https://www.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 [stendhalgame.org](https://stendhalgame.org/download.html).<br>
You can double click on `stendhal-starter.jar` and the client will run.<br>
If you prefer to run from command line, then just execute this command in the stendhal folder:
Please download Stendhal from [https://stendhalgame.org](https://stendhalgame.org)<br>
You can double click on stendhal-starter.jar and the client will run.<br>
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
@ -66,7 +67,7 @@ There is a [manual](https://stendhalgame.org/wiki/Stendhal_Manual), and there is
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 Software Foundation
- The **server**, **java client**, and **Android client**: *GNU General Public License* 2 or later as published by the Free
- 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
@ -75,6 +76,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<br>
Stendhal(c) is copyright of the Arianne Project, 2006-2026, arianne-general at lists dot sourceforge dot net
Stendhal(c) is copyright of the Arianne Project, 2006-2024, arianne-general at lists dot sourceforge dot net
Please have a look at the list of [contributors](doc/contributors.md#contributors).

1
app/.gitignore vendored
View file

@ -1 +0,0 @@
/godot/

View file

@ -10,8 +10,8 @@ android {
applicationId namespace
minSdk 21
targetSdk 32
versionCode 1049005
versionName "1.49.5"
versionCode 1048000
versionName "1.48"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
targetSdkVersion 32
minSdkVersion 21
@ -26,7 +26,7 @@ android {
}
// build output
project.setProperty("archivesBaseName", "${namespace}-${defaultConfig.versionName}")
project.setProperty("archivesBaseName", "${namespace}-${defaultConfig.versionCode}")
project.setProperty("buildDir", "${projectDir}/../../../build/build_android_client")
/* A keystore.properties file can be placed in the root Android project directory and must

View file

@ -14,7 +14,6 @@ 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;
@ -190,11 +189,7 @@ public class ClientView extends WebView {
if (!UrlHelper.isInternalUri(uri)) {
// open external links in default browser/app
// FIXME: should we ask for confirmation?
try {
MainActivity.get().startActivity(new Intent(Intent.ACTION_VIEW, uri));
} catch (final ActivityNotFoundException e) {
Notifier.toast("No app found to open this link");
}
MainActivity.get().startActivity(new Intent(Intent.ACTION_VIEW, uri));
return true;
}
view.loadUrl(UrlHelper.checkClientUrl(uri.toString()));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 647 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 597 KiB

View file

@ -1,3 +1,7 @@
PUT RELEASE ANNOUNCEMENT LINES HERE
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.
Please see the complete change log for further details: https://arianne-project.org/game/stendhal.html#changes

View file

@ -5,6 +5,6 @@
<string name="app_name">Stendhal</string>
<string name="intent_url_scheme">stendhalprod8gps5y99pu</string>
<string name="build_type">prod</string>
<string name="build_version">1.49.5</string>
<string name="build_version">1.48</string>
</resources>

View file

@ -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 {

View file

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4
distributionSha256Sum=91a239400bb638f36a1795d8fdf7939d532cdc7d794d1119b7261aac158b1e60
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -18,7 +18,6 @@ panic = 'abort'
strip = true
[dependencies]
lazy_static = "1.5.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
steamworks = "0.10.0"

View file

@ -1,12 +1,9 @@
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;
@ -19,10 +16,6 @@ use websocket::client::ClientBuilder;
use websocket::{Message, OwnedMessage};
lazy_static! {
static ref AUTHENTICATED: Mutex<bool> = Mutex::new(false);
}
fn connect() -> (Sender<OwnedMessage>, JoinHandle<()>) {
println!("Read connection parameters");
let mut json = String::new();
@ -126,14 +119,6 @@ fn connect() -> (Sender<OwnedMessage>, JoinHandle<()>) {
fn handle_message(nl_token: &String, tx: Sender<OwnedMessage>, 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);
}
}
@ -147,10 +132,6 @@ fn authenticate(tx: Sender<OwnedMessage>, 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);

View file

@ -11,7 +11,6 @@ neutralino.js
# Neutralinojs related files
.storage
*.log
extensions/binary/steam_appid.txt
extensions/binary/*.so
extensions/binary/*.dll
extensions/binary/nativehelper*

View file

@ -0,0 +1 @@
2267890

View file

@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/neutralinojs/neutralinojs/main/schemas/neutralino.config.schema.json",
"applicationId": "org.stendhalgame.client",
"version": "1.49.5",
"version": "1.48",
"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": "6.2.0",
"clientVersion": "6.2.0"
"binaryVersion": "5.0.0",
"clientVersion": "5.0.1"
}
}

View file

@ -1,90 +0,0 @@
#!/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())

View file

@ -1,6 +1,6 @@
@echo off
set STENDHAL_VERSION=1.49.5
set STENDHAL_VERSION=1.48
set SERVER_JAR=stendhal-server-%STENDHAL_VERSION%.jar
:: change to server directory

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
STENDHAL_VERSION="1.49.5"
STENDHAL_VERSION="1.48"
SERVER_JAR="stendhal-server-${STENDHAL_VERSION}.jar"
# change to server directory

View file

@ -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.49
version = 1.49.5
version.old = 1.47
version = 1.48
version.android = 1048000
# FIXME: dynamic method to set this property?
version.android = 1049005
# javac options
javac.deprecation = true

View file

@ -1,32 +1,81 @@
<?xml version='1.0'?>
<project name="stendhal" default="help" basedir=".">
<description>
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.
</description>
<project name="stendhal" default="compile" basedir=".">
<!--
Sets a property (by name and value), or set of properties (from file or resource) in the project. Properties are case sensitive.
Properties are immutable: whoever sets a property first freezes it for the rest of the build; they are most definitely not variables.
-->
<property file="build.ant-private.properties"/>
<property file="build.ant.properties"/>
<property name="build_tests" value="${buildroot}/build_tests"/>
<property name="build_tests_report" value="${buildroot}/build_test_report"/>
<property name="build_client" value="${buildroot}/build_client"/>
<property name="build_client_data" value="${buildroot}/build_client_data"/>
<property name="build_client_sound_data" value="${buildroot}/build_client_sound_data"/>
<property name="build_client_music_data" value="${buildroot}/build_client_music_data"/>
<property name="build_webclient" value="${buildroot}/build_webclient"/>
<property name="build_docker" value="${buildroot}/build_docker"/>
<property name="buildtools_docker" value="buildtools/docker"/>
<property name="build_postman" value="${buildroot}/buildbot/"/>
<property name="build_server" value="${buildroot}/build_server"/>
<property name="build_server_maps" value="${buildroot}/build_server_maps"/>
<property name="build_server_script" value="${buildroot}/build_server_script"/>
<property name="build_server_mods" value="${buildroot}/build_server_mods"/>
<property name="build_server_xmlconf" value="${buildroot}/build_server_xmlconf"/>
<property name="build_stendhaltools" value="${buildroot}/build_stendhaltools"/>
<property name="build_stendhaltextclient" value="${buildroot}/build_stendhaltextclient"/>
<property name="client_data" value="stendhal-data-${version}.jar"/>
<property name="client_jarname" value="stendhal-${version}.jar"/>
<property name="client_sound_data" value="stendhal-sound-data-${version}.jar"/>
<property name="client_music_data" value="stendhal-music-data-${version}.jar"/>
<property name="client_starter_jarname" value="stendhal-starter-${version}.jar"/>
<property name="dist_server_binary" value="${buildroot}/stendhal-server-${version}.zip" />
<property name="maps_jarname" value="stendhal-maps-${version}.jar"/>
<property name="server_jarname" value="stendhal-server-${version}.jar"/>
<property name="xmlconf_jarname" value="stendhal-xmlconf-${version}.jar"/>
<property name="config_dir" value="data/conf"/>
<property name="schema_location" value="${config_dir}"/>
<property name="cobertura.dir" value="${libdir}/cobertura"/>
<condition property="exclude.signing" value="true">
<not>
<available file="keystore.ks"/>
</not>
</condition>
<condition property="exclude.jardiff" value="true">
<not>
<available file="${build-archive}/stendhal-${version.old}.zip"/>
</not>
</condition>
<!-- time & date -->
<tstamp>
<format property="year" pattern="yyyy"/>
<format property="date" pattern="yyyy-MM-dd"/>
<format property="time" pattern="HH:mm:ss"/>
</tstamp>
<property name="datetime" value="${date} ${time}"/>
<!-- Ant sub-modules -->
<import file="build-private.xml" optional="true"/>
<import file="${ant_modules}/properties.xml"/>
<import file="${ant_modules}/info.xml"/>
<import file="${ant_modules}/system.xml"/>
<import file="${ant_modules}/apps.xml"/>
<import file="${ant_modules}/main.xml"/>

View file

@ -37,7 +37,6 @@
<arg value="--target" />
<arg value="x86_64-pc-windows-gnu" />
</exec>
<mkdir dir="app/neutralinojs/extensions/binary" />
<exec executable="cargo" dir="app/nativehelper" failonerror="true" output="app/neutralinojs/extensions/binary/LICENSE.txt">
<arg value="license" />
<arg value="-ad" />
@ -78,33 +77,23 @@
<include name="*.log" />
</fileset>
</delete>
<move
file="app/neutralinojs/dist/stendhal-client/stendhal-client-linux_x64"
tofile="app/neutralinojs/dist/stendhal-client/stendhal-client-linux" />
<move
file="app/neutralinojs/dist/stendhal-client/stendhal-client-win_x64.exe"
tofile="app/neutralinojs/dist/stendhal-client/stendhal-client-windows.exe" />
<chmod dir="app/neutralinojs/dist/stendhal-client/" type="file" perm="ugo+rx">
<include name="stendhal-client-linux" />
<include name="stendhal*" />
<include name="**/nativehelper*" />
</chmod>
<!--
<copy todir="dist/stendhal-client" flatten="true">
<fileset dir="app/neutralinoks">
<include name="LICENSE.txt" />
</fileset>
</copy>
-->
<copy file="app/neutralinojs/LICENSE.txt" tofile="app/neutralinojs/dist/stendhal-client/LICENSE.txt">
<filterchain>
<concatfilter append="app/neutralinojs/extensions/binary/LICENSE.txt" />
</filterchain>
</copy>
<echo file="app/neutralinojs/dist/stendhal-client/extensions/binary/steam_appid.txt">${steam-appid}</echo>
<delete file="${buildroot}/stendhal-client-${version}.zip"/>
<zip destfile="${buildroot}/stendhal-client-${version}.zip">
<zipfileset dir="app/neutralinojs/dist/stendhal-client" prefix="stendhal-client" filemode="755">
<include name="stendhal-client-linux" />
<include name="**/nativehelper*" />
</zipfileset>
<zipfileset dir="app/neutralinojs/dist/stendhal-client" prefix="stendhal-client">
<exclude name="stendhal-client-linux" />
<exclude name="**/nativehelper*" />
</zipfileset>
</zip>
</target>
@ -161,8 +150,7 @@
</target> <!-- client_android_build -->
<!-- Create Android client package for release and distribution. -->
<target name="android">
<target name="android" description="Create Android client package for release and distribution.">
<echo
if:set="exclude.webview"
message="ANDROID_SDK_ROOT environment variable not set. Skipping Android WebView client build."/>

View file

@ -2,8 +2,7 @@
<project xmlns:if="ant:if" xmlns:unless="ant:unless">
<!-- Delete all build files and directories. -->
<target name="clean">
<target name="clean" description="Delete all build files and directories.">
<delete includeEmptyDirs="true" quiet="true">
<fileset dir="${buildroot}" excludes="eclipse/" defaultExcludes="false"/>
</delete>
@ -19,16 +18,14 @@
</target>
<!-- Delete client data build files and directories. -->
<target name="clean_client_data">
<target name="clean_client_data" description="Delete client data build files and directories.">
<delete dir="${build_client_data}"/>
<delete dir="${build_client_music_data}"/>
<delete dir="${build_client_sound_data}"/>
</target>
<!-- Delete web client build files and directories. -->
<target name="cleanjs">
<target name="cleanjs" description="Delete web client build files and directories.">
<delete dir="${buildroot}/js"/>
<delete dir="${buildroot}/ts"/>
</target>

View file

@ -19,15 +19,13 @@
</condition>
<!-- Build server and clients. -->
<target name="jar" depends="client_build,server_build">
<target name="jar" depends="client_build,server_build" description="Build server and clients.">
<!-- XXX: client & server builds are not exclusive to NetBeans, remove this output? -->
<echo>Built client and server for NetBeans.</echo>
</target>
<!-- Sign client distribution. -->
<target name="signjar" unless="exclude.signing">
<target name="signjar" unless="exclude.signing" description="Sign client distribution.">
<signjar
alias="${keystore.alias}"
keystore="keystore.ks"
@ -36,23 +34,20 @@
</target>
<!-- Generate JAR files for both client and server. -->
<!-- XXX: why does this depend on "server_build" twice? -->
<target name="compile" depends="server_build,client_build,server_build" />
<target name="compile" depends="server_build,client_build,server_build" description="Generate JAR files for both client and server."/>
<!-- Create all packages for Stendhal release. -->
<target name="dist" depends="clean,dist_binary,dist_source,signupdates" />
<target name="dist" depends="clean,dist_binary,dist_source,signupdates" description="Create all packages for Stendhal release."/>
<!-- Create all binary packages for Stendhal release. -->
<target name="dist_binary" depends="clean,dist_server_binary,dist_client_binary,dist_client_starter,dist_webclient_binary" />
<target name="dist_binary" depends="clean,dist_server_binary,dist_client_binary,dist_client_starter,dist_webclient_binary"
description="Create all binary packages for Stendhal release."/>
<!-- START: source -->
<!-- Create all source packages for Stendhal release. -->
<target name="dist_source">
<target name="dist_source" description="Create all source packages for Stendhal release.">
<mkdir dir="${buildroot}/stendhal-${version}-src"/>
<copy todir="${buildroot}/stendhal-${version}-src/src">
@ -109,8 +104,7 @@
<!-- START: client data -->
<!-- Build client data files. -->
<target name="build_client_data">
<target name="build_client_data" description="Build client data files.">
<!-- base data -->
<mkdir dir="${build_client_data}"/>
<copy todir="${build_client_data}/data/sprites">
@ -185,8 +179,7 @@
<!-- START: server -->
<!-- Compile JAR file for Stendhal server. -->
<target name="server_build" depends="init,checkxml">
<target name="server_build" depends="init,checkxml" description="Compile JAR file for Stendhal server.">
<mkdir dir="${build_server}"/>
<copy todir="${build_server}/data/conf" file="data/conf/stendhalcreateaccount.properties"/>
@ -347,8 +340,7 @@
</target> <!-- server_build -->
<!-- Create server package for release and distribution. -->
<target name="dist_server_binary" depends="server_build">
<target name="dist_server_binary" depends="server_build" description="Create server package for release and distribution.">
<mkdir dir="${buildroot}/stendhal-server-${version}"/>
<copy todir="${buildroot}/stendhal-server-${version}/data/script">
@ -400,12 +392,10 @@
</target> <!-- dist_server_binary -->
<!-- Build and push container image. -->
<target name="dist_docker" depends="docker_build,docker_push" />
<target name="dist_docker" depends="docker_build,docker_push" description="Build and push container image."/>
<!-- Prepare container image build. -->
<target name="docker_build_prepare" depends="dist_server_binary">
<target name="docker_build_prepare" depends="dist_server_binary" description="Prepare container image build.">
<mkdir dir="${build_docker}" />
<copy file="${buildtools_docker}/Dockerfile" todir="${build_docker}" />
<copy file="${buildtools_docker}/.dockerignore" todir="${build_docker}" />
@ -414,8 +404,7 @@
</target> <!-- docker_build_prepare -->
<!-- Build container image. -->
<target name="docker_build" depends="docker_build_prepare">
<target name="docker_build" depends="docker_build_prepare" description="Build container image.">
<exec dir="${build_docker}" executable="docker">
<arg value="build" />
<arg value="-t" />
@ -425,57 +414,28 @@
</target> <!-- docker_build -->
<!-- Push container image. -->
<target name="docker_push" />
<target name="docker_push" description="Push container image."/>
<!-- Run server. -->
<target name="server_run" depends="server_build,compilejs">
<mkdir dir="run_srvr/libs"/>
<mkdir dir="run_srvr/js"/>
<target name="server_run" depends="server_build,compilejs" description="Run server.">
<mkdir dir="run_srvr"/>
<copy todir="run_srvr">
<fileset file="build/lib/${server_jarname}"/>
<fileset file="build/lib/${xmlconf_jarname}"/>
<fileset file="build/lib/${maps_jarname}"/>
</copy>
<copy todir="run_srvr/libs">
<fileset file="${marauroa_jar}" />
<fileset file="${groovy_jar}" />
<fileset file="${guava_jar}" />
<fileset file="${log4j_jar}" />
<fileset file="${mysqldriver_jar}" />
<fileset file="${h2_jar}" />
<fileset file="${hamcrest_jar}" />
<fileset file="${tomcatcore_jar}" />
<fileset file="${tomcatwebsocket_jar}" />
<fileset file="${jakartaannotation_jar}" />
<fileset file="${luaj_jar}" />
<fileset file="${jsonsimple_jar}" />
</copy>
<copy todir="run_srvr/js">
<fileset dir="${srcjs}">
<exclude name="stendhal/" />
<exclude name="stendhal-compiled.js.map" />
<exclude name=".gitignore" />
</fileset>
<fileset file="libs/marauroa.jar"/>
<fileset file="libs/groovy.jar"/>
<fileset file="libs/guava.jar"/>
<fileset file="libs/log4j.jar"/>
<fileset file="libs/${mysqldriver_jarname}"/>
</copy>
<chmod file="run_srvr/${server_jarname}" perm="+x"/>
<java jar="run_srvr/${server_jarname}"
fork="true"
failonerror="true"
maxmemory="256m">
<arg value="-c" />
<arg value="server.ini" />
<arg value="-l" />
<classpath>
<pathelement location="${basedir}" />
</classpath>
</java>
<exec dir="." executable="java">
<arg line="-Xmx256m -cp run_srvr -jar run_srvr/${server_jarname} -c server.ini -l"/>
</exec>
<!-- we've run the program, so delete the jars -->
<delete dir="run_srvr"/>
@ -486,8 +446,7 @@
<!-- START: java client -->
<!-- Compile client JAR file. -->
<target name="client_build" depends="init,build_client_data">
<target name="client_build" depends="init,build_client_data" description="Compile client JAR file.">
<mkdir dir="${build_client}"/>
<copy todir="${build_client}/games/stendhal/client">
@ -535,7 +494,7 @@
<!-- compile Starter.java for Java 1.7. So we will be able to
display a dialogbox if running on an old Java Version -->
<javac srcdir="${src}" destdir="${build_client}" debug="${javac.debug}" debuglevel="${javac.debuglevel}" source="1.8" target="1.8" deprecation="${javac.deprecation}" includeantruntime="false">
<javac srcdir="${src}" destdir="${build_client}" debug="${javac.debug}" debuglevel="${javac.debuglevel}" source="1.7" target="1.7" deprecation="${javac.deprecation}" includeantruntime="false">
<include name="games/stendhal/client/update/Starter.java"/>
<compilerarg value="-encoding"/>
@ -580,8 +539,7 @@
</target> <!-- client_build -->
<!-- Compile text client. -->
<target name="compile_textclient">
<target name="compile_stendhaltextclient" description="Compile text client.">
<mkdir dir="${build_stendhaltextclient}"/>
<javac srcdir="${src}" destdir="${build_stendhaltextclient}" source="1.8" target="1.8" debug="${javac.debug}" debuglevel="${javac.debuglevel}" deprecation="${javac.deprecation}" includeantruntime="false">
@ -602,12 +560,8 @@
</javac>
</target> <!-- compile_stendhaltextclient -->
<!-- Alias of `compile_textclient`. -->
<target name="compile_stendhaltextclient" depends="compile_textclient" />
<!-- Create Java client package for release and distribution. -->
<target name="dist_client_binary" depends="clean_client_data,client_build">
<target name="dist_client_binary" depends="clean_client_data,client_build" description="Create Java client package for release and distribution.">
<mkdir dir="${buildroot}/stendhal-${version}"/>
<mkdir dir="${buildroot}/stendhal-${version}/lib"/>
<mkdir dir="${buildroot}/stendhal-${version}/log"/>
@ -643,8 +597,8 @@
</target> <!-- dist_client_binary -->
<!-- Create Java client starter package (obsolete). -->
<target name="dist_client_starter" depends="client_build">
<!-- XXX: is this still used? -->
<target name="dist_client_starter" depends="client_build" description="Create Java client starter package.">
<mkdir dir="${buildroot}/stendhal-${version}"/>
<copy todir="${buildroot}/stendhal-${version}" file="${lib}/${client_starter_jarname}"/>
<copy todir="${buildroot}/stendhal-${version}/lib">
@ -666,8 +620,7 @@
</target> <!-- dist_client_starter -->
<!-- Run Java client. -->
<target name="client_run" depends="client_build">
<target name="client_run" depends="client_build" description="Run Java client.">
<mkdir dir="run_clnt"/>
<mkdir dir="run_clnt/lib"/>
@ -703,9 +656,8 @@
</target> <!-- client_run -->
<!-- Run client for NetBeans trick. -->
<!-- Netbeans won't let you choose ant options, so this does it for us-->
<target name="run" depends="client_run" />
<target name="run" depends="client_run" description="Run client for NetBeans trick."/>
<!-- END: java client -->
@ -713,42 +665,30 @@
<!-- START: web client -->
<target name="js-prepare-marauroa">
<mkdir dir="node_modules/marauroa"/>
<unzip dest="node_modules/marauroa" src="libs/marauroa.jar">
<mapper type="flatten"/>
<mkdir dir="build/js"/>
<unzip dest="build" src="libs/marauroa.jar">
<patternset>
<include name="js/**"/>
</patternset>
</unzip>
</target>
<target name="js-prepare-build">
<mkdir dir="node_modules/marauroa"/>
<echo file="node_modules/marauroa/build.js">
export function version(marauroa, stendhal) {
stendhal.data = stendhal.data || {};
<echo file="build/js/build.js">
var stendhal = window.stendhal = window.stendhal || {};
stendhal.data = stendhal.data || {};
stendhal.data.build = {
version: "${version}",
build: "${datetime}",
dist: "webclient"
}
//CI
}
stendhal.data.build = {
version: "${version}",
build: "${datetime}",
dist: "webclient"
}
</echo>
<antcall target="js-prepare-build-ci" />
</target>
<target name="js-prepare-build-ci" if="ci.build.info">
<loadfile property="cibuildinfo" srcFile="${ci.build.info}" failonerror="false" quiet="true"/>
<replace file="node_modules/marauroa/build.js" token="//CI" value="${cibuildinfo}" />
</target>
<extension-point name="js-prepare" depends="js-prepare-node,js-prepare-contribs,js-prepare-marauroa,js-prepare-build"/>
<extension-point name="js-prepare" depends="js-prepare-marauroa,js-prepare-build,js-prepare-node,js-prepare-contribs"/>
<target name="js-prepare-node">
@ -773,19 +713,72 @@ export function version(marauroa, stendhal) {
<!-- TODO: Do not write stendhal-compiled.js to the source directory tree but directly to the compiled directory tree -->
<target name="js-invoke-compiler">
<exec executable="${executable.npx}" dir="${srcjs}/stendhal" failonerror="true">
<arg value="tsc"/>
</exec>
<exec executable="${executable.npx}" dir="${srcjs}/stendhal" failonerror="true">
<arg value="esbuild"/>
<arg value="--bundle"/>
<arg value="--outfile=../stendhal-compiled.js"/>
<arg value="--platform=browser"/>
<!-- <arg value="- -minify"/> -->
<arg value="main.ts"/>
</exec>
<property name="js-debug" value="--debug --formatting=PRETTY_PRINT --create_source_map ${srcjs}/stendhal-compiled.js.map --source_map_include_content"/>
<property name="js-files-license" value="${srcjs}/stendhal/license.js"/>
<property name="js-files-marauroa" value="build/js/marauroa.js build/js/client-framework.js build/js/message-factory.js build/js/perception.js build/js/rpfactory.js build/js/inflate.js build/js/deserializer.js"/>
<property name="js-files-config" value="build/js/build.js"/>
<property name="js-files-lib1" value="${srcjs}/jsxgraph-util.js"/>
<property name="js-files-lib2" value=""/>
<property name="js-files-data1" value="${srcjs}/stendhal/data/sha3.js"/>
<property name="js-files-data2" value=""/>
<property name="js-files-entity5" value=""/>
<property name="js-files-entity6" value=""/>
<property name="js-files-ui1" value=""/>
<property name="js-files-ui2" value=""/>
<property name="js-files-ui3" value=""/>
<property name="js-files-main1" value=""/>
<property name="js-files-ui4" value=""/>
<property name="js-files-main2" value=""/>
<property name="js-files-main3" value=""/>
<property name="js-files-ts" value="build/ts/*.js build/ts/**/*.js"/>
<property name="js-files-ts2" value=""/>
<java jar="buildtools/closure-compiler/compiler.jar"
fork="true"
failonerror="true"
maxmemory="128m">
<arg value="--language_in=ECMASCRIPT_2020"/>
<arg value="--language_out=STABLE"/>
<arg value="--compilation_level"/>
<arg value="ADVANCED"/>
<arg line="${js-debug}"/>
<arg value="--process_common_js_modules"/>
<arg value="--module_resolution=node"/>
<arg value="--jscomp_off=checkVars"/>
<!-- DEBUG: -->
<!--<arg value="-\-jscomp_off=globalThis"/>-->
<arg value="--js_output_file=${srcjs}/stendhal-compiled.js"/>
<arg line="${js-files-license}"/>
<arg line="${js-files-marauroa}"/>
<arg line="${js-files-config}"/>
<arg line="${js-files-lib1}"/>
<arg line="${js-files-lib2}"/>
<arg line="${js-files-data1}"/>
<arg line="${js-files-data2}"/>
<arg line="${js-files-entity5}"/>
<arg line="${js-files-entity6}"/>
<arg line="${js-files-ui1}"/>
<arg line="${js-files-ui2}"/>
<arg line="${js-files-ui3}"/>
<arg line="${js-files-main1}"/>
<arg line="${js-files-ui4}"/>
<arg line="${js-files-main2}"/>
<arg line="${js-files-main3}"/>
<arg line="${js-files-ts}"/>
<arg line="${js-files-ts2}"/>
</java>
<echo file="${srcjs}/stendhal-compiled.js" append="true">
//# sourceMappingURL=stendhal-compiled.js.map
</echo>
<antcall target="compilejs-hook" />
@ -802,8 +795,7 @@ export function version(marauroa, stendhal) {
</target>
<!-- Compile web client. -->
<target name="compilejs" depends="cleanjs">
<target name="compilejs" depends="cleanjs" description="Compile web client.">
<antcall target="js-prepare" unless:set="exclude.webclient"/>
<antcall target="js-invoke-compiler" unless:set="exclude.webclient"/>
@ -812,8 +804,7 @@ export function version(marauroa, stendhal) {
</target> <!-- compilejs -->
<!-- Compile and stage web client files. -->
<target name="webclient_build" depends="compilejs,clean_client_data,build_client_data">
<target name="webclient_build" depends="compilejs,clean_client_data,build_client_data" description="Compile and stage web client files.">
<mkdir dir="${build_webclient}"/>
<copy todir="${build_webclient}">
@ -835,8 +826,10 @@ export function version(marauroa, stendhal) {
</target> <!-- webclient_build -->
<!-- Create web client package for release and distribution. -->
<target name="dist_webclient_binary" depends="webclient_build">
<target name="build_webclient" depends="webclient_build" description="Alias of webclient_build."/>
<target name="dist_webclient_binary" depends="webclient_build" description="Create web client package for release and distribution.">
<zip destfile="${buildroot}/stendhal-webclient-${version}.zip">
<zipfileset dir="${build_client_data}"/>
<zipfileset dir="${build_client_music_data}"/>

View file

@ -12,8 +12,7 @@
<delete dir="${buildroot}/build_docs"/>
</target>
<!-- Generate HTML documentation for Java, TypeScript, and Lua APIs. -->
<target name="docs" depends="luadocs,javadocs,tsdocs"/>
<target name="docs" description="Generate HTML documentation for Java, TypeScript, and Lua APIs." depends="luadocs,javadocs,tsdocs"/>
<!-- Java documentation -->

View file

@ -1,86 +0,0 @@
<?xml version='1.0'?>
<project>
<target name="info" description="Print project build information.">
<echo level="info" message="Project:" />
<echo level="info" message=" Name: .......... ${game_name}" />
<echo level="info" message=" Version: ....... ${version}" />
<echo level="info" message=" Root: .......... ${basedir}" />
<echo level="info" />
<echo level="info" message="Build system:" />
<echo level="info" message=" Ant version: ... ${ant.version.number}" />
<echo level="info" message=" Ant home: ...... ${ant.location}" />
<echo level="info" message=" Java version: .. ${java.version}" />
<echo level="info" message=" Java home: ..... ${java.home}" />
</target>
<target name="help" description="Print project build and help information." depends="info">
<echo level="info" message="Usage:" />
<echo level="info" message=" ant &lt;target&gt; [&lt;target&gt;...]" />
<echo level="info" />
<echo level="info" />
<echo level="info" message="=== TARGETS ===" />
<echo level="info" />
<echo level="info" message="General:" />
<echo level="info" message=" info: ........................ Print project build information." />
<echo level="info" message=" help: ........................ Print project build and help information." />
<echo level="info" message=" init: ........................ Update version and resource info." />
<echo level="info" message=" checkxml: .................... Verify XML files are well-formed." />
<echo level="info" />
<echo level="info" message="Build:" />
<echo level="info" message=" server_build: ................ Compile JAR file for Stendhal server." />
<echo level="info" message=" client_build: ................ Compile client JAR file." />
<echo level="info" message=" webclient_build: ............. Compile and stage web client files." />
<echo level="info" message=" compile_textclient: .......... Compile text client." />
<echo level="info" message=" compile: ..................... Generate JAR files for both client and server." />
<echo level="info" message=" compilejs: ................... Compile web client." />
<echo level="info" message=" compile_tools: ............... Compile Stendhal build tools." />
<echo level="info" message=" server_run: .................. Run server." />
<echo level="info" message=" client_run: .................. Run Java client." />
<echo level="info" message=" postman: ..................... Build postman bot." />
<echo level="info" />
<echo level="info" message="Testing:" />
<echo level="info" message=" compile_tests: ............... Compile all tests." />
<echo level="info" message=" run_tests: ................... Run all tests." />
<echo level="info" message=" test: ........................ Compile and run all tests." />
<echo level="info" />
<echo level="info" message="Build cleaning:" />
<echo level="info" message=" clean: ....................... Delete all build files and directories." />
<echo level="info" message=" clean_client_data: ........... Delete client data build files and directories." />
<echo level="info" message=" cleanjs: ..................... Delete web client build files and directories." />
<echo level="info" />
<echo level="info" message="Source maintanence:" />
<echo level="info" message=" clean_whitespace: ............ Clean whitespace in source and other specified file types." />
<echo level="info" />
<echo level="info" message="Distribution:" />
<echo level="info" message=" dist_source: ................. Create all source packages for Stendhal release." />
<echo level="info" message=" dist_binary: ................. Create all binary packages for Stendhal release." />
<echo level="info" message=" dist_server_binary: .......... Create server package for release and distribution." />
<echo level="info" message=" dist_client_binary: .......... Create Java client package for release and distribution." />
<echo level="info" message=" dist_webclient_binary: ....... Create web client package for release and distribution." />
<echo level="info" message=" dist_client_starter: ......... Create Java client starter package (obsolete)." />
<echo level="info" message=" dist: ........................ Create all packages for Stendhal release." />
<echo level="info" message=" android: ..................... Create Android client package for release and distribution." />
<echo level="info" />
<echo level="info" message="Resources:" />
<echo level="info" message=" rendermaps: .................. Convert TMX maps to PNG images scaled 1:16." />
<echo level="info" message=" renderatlas: ................. Generate files for the Stendhal Atlas." />
<echo level="info" message=" renderworldmap: .............. Export world.tmx map to PNG image." />
<echo level="info" message=" updatemaps: .................. Load and save TMX maps to fix tilesets changes." />
<echo level="info" />
<echo level="info" message="Documentation:" />
<echo level="info" message=" docs: ........................ Generate HTML documentation for Java, TypeScript, and Lua APIs." />
<echo level="info" />
<echo level="info" message="Localization:" />
<echo level="info" message=" locale_template: ............. Creates a template for translating." />
<echo level="info" message=" locale_update: ............... Update translation files from template." />
<echo level="info" />
<echo level="info" message="Aliases:" />
<echo level="info" message=" compile_stendhaltextclient: .. Alias of `compile_textclient`." />
<echo level="info" message=" compile_stendhaltools: ....... Alias of `compile_tools`." />
<echo level="info" />
<echo level="info" message="Execute `ant -p -v` for a full list of build targets." />
</target>
</project>

View file

@ -2,8 +2,7 @@
<project xmlns:if="ant:if" xmlns:unless="ant:unless">
<!-- Creates a template for translating. -->
<target name="locale_template">
<target name="locale_build_template" description="Creates a template for translating.">
<echo unless:true="${python.present}" message="Python unavailable, cannot build locale template"/>
<exec executable="${executable.python}" if:true="${python.present}" failonerror="true">
@ -12,8 +11,7 @@
</exec>
</target>
<!-- Update translation files from template. -->
<target name="locale_update">
<target name="locale_update" description="Update translation files from template.">
<echo unless:true="${python.present}" message="Python unavailable, cannot update translations"/>
<exec executable="${executable.python}" if:true="${python.present}" failonerror="true">

View file

@ -2,8 +2,7 @@
<project xmlns:if="ant:if" xmlns:unless="ant:unless">
<!-- Update version and resource info. -->
<target name="init">
<target name="init" description="Update version and resource info.">
<mkdir dir="${lib}"/>
<!-- update version -->
@ -43,10 +42,6 @@
match="&quot;version&quot;: &quot;.*&quot;"
replace="&quot;version&quot;: &quot;${version}&quot;"
byline="true"/>
<replaceregexp file="${src}/games/stendhal/client/update/HttpClient.java"
match="StendhalClient/.* \("
replace="StendhalClient/${version} \("
byline="true"/>
<!-- update location of resources -->
<replaceregexp file="${src}/games/stendhal/client/update/game-default.properties"
@ -90,8 +85,7 @@
</target> <!-- init -->
<!-- Verify XML files are well-formed. -->
<target name="checkxml">
<target name="checkxml" description="Verify XML files are well-formed.">
<schemavalidate failonerror="yes" lenient="yes" warn="yes">
<schema namespace="stendhal" file="${config_dir}/zones.xsd"/>
<fileset dir="${config_dir}/zones">
@ -169,8 +163,7 @@
</target> <!-- checkxml -->
<!-- Add date to pre-release test client. -->
<target name="internalUpdatePrereleaseVersion" if="PRE_RELEASE">
<target name="internalUpdatePrereleaseVersion" if="PRE_RELEASE" description="Add date to pre-release test client.">
<tstamp prefix="date">
<format property="iso" pattern="yyyy-MM-dd"/>
</tstamp>

View file

@ -4,8 +4,7 @@
<import file="tools.xml" />
<!-- Convert TMX maps to PNG images scaled 1:16. -->
<target name="rendermaps" depends="compile_stendhaltools">
<target name="rendermaps" description="Convert TMX maps to PNG images scaled 1:16." depends="compile_stendhaltools">
<taskdef name="maprenderer" classname="games.stendhal.tools.MapRenderer">
<classpath>
<pathelement path="${build_stendhaltools}"/>
@ -24,8 +23,7 @@
</target> <!-- rendermaps -->
<!-- Generate files for the Stendhal Atlas. -->
<target name="renderatlas" depends="compile_stendhaltools">
<target name="renderatlas" description="Generate files for the Stendhal Atlas." depends="compile_stendhaltools">
<taskdef name="maprenderer" classname="games.stendhal.tools.MapRenderer">
<classpath>
<pathelement path="${build_stendhaltools}"/>
@ -60,8 +58,7 @@
</target> <!-- renderatlas -->
<!-- Export world.tmx map to PNG image. -->
<target name="renderworldmap" depends="rendermaps">
<target name="renderworldmap" description="Export world.tmx map to PNG image." depends="rendermaps">
<!-- embed area images with area name -->
<!-- FIXME: need to detect the Python "PIL" module to ensure script can run or
write Java class that can do this -->
@ -88,8 +85,7 @@
</target> <!-- renderworldmap -->
<!-- Load and save TMX maps to fix tilesets changes. -->
<target name="updatemaps" depends="compile_stendhaltools">
<target name="updatemaps" description="Load and save TMX maps to fix tilesets changes." depends="compile_stendhaltools">
<taskdef name="mapupdater" classname="games.stendhal.tools.MapUpdater">
<classpath>
<pathelement path="${build_stendhaltools}"/>

View file

@ -16,8 +16,7 @@
<delete file="${build}/package/client/stendhal-starter.exe"/>
</target>
<!-- Creates MSI installer package from current build. -->
<target name="msiPackage" depends="stagePackage">
<target name="msiPackage" description="Creates MSI installer package from current build" depends="stagePackage">
<delete dir="${build}/package/msi-app-image"/>
<exec executable="jpackage" dir="." failonerror="true">
<arg line="--type app-image"/>
@ -47,8 +46,7 @@
</exec>
</target> <!-- msiPackage -->
<!-- Creates DMG package from current build. -->
<target name="dmgPackage" depends="stagePackage">
<target name="dmgPackage" description="Creates DMG package from current build" depends="stagePackage">
<delete dir="${build}/package/dmg-app-image"/>
<exec executable="jpackage" dir="." failonerror="true">
<arg line="--type app-image"/>
@ -76,8 +74,7 @@
</exec>
</target> <!-- dmgPackage -->
<!-- Creates RPM package from current build. -->
<target name="rpmPackage" depends="stagePackage">
<target name="rpmPackage" description="Creates RPM package from current build" depends="stagePackage">
<delete dir="${build}/package/rpm-app-image"/>
<exec executable="jpackage" dir="." failonerror="true">
<arg line="--type app-image"/>
@ -110,8 +107,7 @@
</exec>
</target> <!-- rpmPackage -->
<!-- Creates DEB package from current build. -->
<target name="debPackage" depends="stagePackage">
<target name="debPackage" description="Creates DEB package from current build" depends="stagePackage">
<delete dir="${build}/package/deb-app-image"/>
<exec executable="jpackage" dir="." failonerror="true">
<arg line="--type app-image"/>

View file

@ -1,77 +0,0 @@
<?xml version='1.0'?>
<project>
<!--
Sets a property (by name and value), or set of properties (from file or resource) in the project. Properties are case sensitive.
Properties are immutable: whoever sets a property first freezes it for the rest of the build; they are most definitely not variables.
-->
<!-- store ant version number without extra details -->
<antversion property="ant.version.number" />
<!-- store ant home directory with system delimiter -->
<property name="ant.location" location="${ant.home}" />
<property name="build_tests" value="${buildroot}/build_tests"/>
<property name="build_tests_report" value="${buildroot}/build_test_report"/>
<property name="build_client" value="${buildroot}/build_client"/>
<property name="build_client_data" value="${buildroot}/build_client_data"/>
<property name="build_client_sound_data" value="${buildroot}/build_client_sound_data"/>
<property name="build_client_music_data" value="${buildroot}/build_client_music_data"/>
<property name="build_webclient" value="${buildroot}/build_webclient"/>
<property name="build_docker" value="${buildroot}/build_docker"/>
<property name="buildtools_docker" value="buildtools/docker"/>
<property name="build_postman" value="${buildroot}/buildbot/"/>
<property name="build_server" value="${buildroot}/build_server"/>
<property name="build_server_maps" value="${buildroot}/build_server_maps"/>
<property name="build_server_script" value="${buildroot}/build_server_script"/>
<property name="build_server_mods" value="${buildroot}/build_server_mods"/>
<property name="build_server_xmlconf" value="${buildroot}/build_server_xmlconf"/>
<property name="build_stendhaltools" value="${buildroot}/build_stendhaltools"/>
<property name="build_stendhaltextclient" value="${buildroot}/build_stendhaltextclient"/>
<property name="client_data" value="stendhal-data-${version}.jar"/>
<property name="client_jarname" value="stendhal-${version}.jar"/>
<property name="client_sound_data" value="stendhal-sound-data-${version}.jar"/>
<property name="client_music_data" value="stendhal-music-data-${version}.jar"/>
<property name="client_starter_jarname" value="stendhal-starter-${version}.jar"/>
<property name="dist_server_binary" value="${buildroot}/stendhal-server-${version}.zip" />
<property name="maps_jarname" value="stendhal-maps-${version}.jar"/>
<property name="server_jarname" value="stendhal-server-${version}.jar"/>
<property name="xmlconf_jarname" value="stendhal-xmlconf-${version}.jar"/>
<property name="config_dir" value="data/conf"/>
<property name="schema_location" value="${config_dir}"/>
<property name="cobertura.dir" value="${libdir}/cobertura"/>
<condition property="exclude.signing" value="true">
<not>
<available file="keystore.ks"/>
</not>
</condition>
<condition property="exclude.jardiff" value="true">
<not>
<available file="${build-archive}/stendhal-${version.old}.zip"/>
</not>
</condition>
<!-- time & date -->
<tstamp>
<format property="year" pattern="yyyy"/>
<format property="date" pattern="yyyy-MM-dd"/>
<format property="time" pattern="HH:mm:ss"/>
</tstamp>
<property name="datetime" value="${date} ${time}"/>
</project>

View file

@ -7,8 +7,7 @@
<import file="dist.xml" />
<import file="tools.xml" />
<!-- Compile and run all tests. -->
<target name="test" depends="compile_tests,prepare_serverini_for_tests,run_tests,report_test">
<target name="test" description="Compile and run all tests." depends="compile_tests,prepare_serverini_for_tests,run_tests,report_test">
<exec executable="curl">
<arg value="-q"/>
<arg value="-F"/>
@ -22,8 +21,7 @@
</target> <!-- test -->
<!-- Compile all tests. -->
<target name="compile_tests" depends="server_build,client_build,compile_stendhaltools,compile_stendhaltextclient,postman">
<target name="compile_tests" description="Compile all tests." depends="server_build,client_build,compile_stendhaltools,compile_stendhaltextclient,postman">
<mkdir dir="${build_tests}"/>
<copy todir="${build_tests}">
@ -65,8 +63,7 @@
</target> <!-- compile_tests -->
<!-- Generate server.ini for testing. -->
<target name="prepare_serverini_for_tests" unless="server.ini">
<target name="prepare_serverini_for_tests" unless="server.ini" description="Generate server.ini for testing.">
<echo file="server.ini">
database_implementation=games.stendhal.server.core.engine.StendhalPlayerDatabase
factory_implementation=games.stendhal.server.core.engine.StendhalRPObjectFactory
@ -88,8 +85,7 @@ d = 9017093486007323270605414717455737376618092805104733236083857811315606869372
</target> <!-- prepare_serverini_for_tests -->
<!-- Run all tests. -->
<target name="run_tests">
<target name="run_tests" description="Run all tests.">
<mkdir dir="${build_tests_report}"/>
<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
<classpath path="buildtools/jacoco/jacocoant.jar"/>
@ -151,8 +147,7 @@ d = 9017093486007323270605414717455737376618092805104733236083857811315606869372
</target> <!-- run_tests -->
<!-- Aggregate test results. -->
<target name="report_test">
<target name="report_test" description="Aggregate test results.">
<mkdir dir="${build_tests_report}"/>
<junitreport todir="${build_tests_report}" tofile="testresults.xml">
<fileset dir="${build_tests_report}">

View file

@ -4,8 +4,7 @@
<import file="dist.xml" />
<!-- Compile Stendhal build tools. -->
<target name="compile_tools">
<target name="compile_stendhaltools" description="Compile Stendhal build tools.">
<mkdir dir="${build_stendhaltools}"/>
<javac srcdir="${src}" destdir="${build_stendhaltools}" source="1.8" target="1.8" debug="${javac.debug}" debuglevel="${javac.debuglevel}" deprecation="${javac.deprecation}" includeantruntime="true">
@ -29,11 +28,7 @@
</javac>
</target> <!-- compile_stendhaltools -->
<!-- Alias of `compile_tools`. -->
<target name="compile_stendhaltools" depends="compile_tools" />
<!-- Clean whitespace in source and other specified file types. -->
<target name="clean_whitespace" depends="compile_stendhaltools">
<target name="clean_whitespace" description="Clean whitespace in source and other specified file types." depends="compile_stendhaltools">
<!-- process main Java source, config, & scripting files -->
<java classname="games.stendhal.tools.CleanWhitespace">
<classpath>
@ -41,7 +36,7 @@
</classpath>
<arg value="src/"/>
<arg value="tests/"/>
<arg value="app/android/client/src"/>
<arg value="android/app/src/"/>
<arg value="java,xml,lua"/>
</java>
@ -60,22 +55,16 @@
<pathelement path="${build_stendhaltools}"/>
</classpath>
<arg value="data/"/>
<arg value="tiled/"/>
<arg value="xml,xsd,lua,py"/>
</java>
<!-- process Ant build configuration files -->
<java classname="games.stendhal.tools.CleanWhitespace">
<classpath>
<pathelement path="${build_stendhaltools}"/>
</classpath>
<arg value="${ant_modules}" />
<arg value="xml" />
</java>
</target> <!-- clean_whitespace -->
<!-- Build postman bot. -->
<target name="postman">
<target name="cleanwhitespace" depends="clean_whitespace" description="Alias of clean_whitespace."/>
<target name="postman" description="Build postman bot.">
<mkdir dir="${buildroot}/buildbot"/>
<javac srcdir="${src}" destdir="${buildroot}/buildbot" debug="${javac.debug}" debuglevel="${javac.debuglevel}" source="1.8" target="1.8" deprecation="${javac.deprecation}" includeantruntime="false">
<include name="games/stendhal/bot/postman/*.java"/>
@ -111,8 +100,7 @@
</target> <!-- postman -->
<!-- Sign updates. -->
<target name="signupdates" depends="compile_stendhaltools,jardiff" unless="exclude.signing">
<target name="signupdates" description="Sign updates." depends="compile_stendhaltools,jardiff" unless="exclude.signing">
<taskdef name="updatesigner" classname="games.stendhal.tools.updateprop.UpdateSigner">
<classpath>
<pathelement path="."/>
@ -128,8 +116,7 @@
</target> <!-- signupdates -->
<!-- Generate release changes info. -->
<target name="jardiff" depends="client_build" unless="exclude.jardiff">
<target name="jardiff" depends="client_build" unless="exclude.jardiff" description="Generate release changes info.">
<echo>Creating update from ${version.old} to ${version}</echo>
<delete>
<fileset dir="${build-archive}">
@ -173,9 +160,8 @@
</target> <!-- jardiff -->
<!-- Run enemy balancer. -->
<!-- USAGE: ant balance -Dcreature="<creature_name>" -->
<target name="balance" depends="compile_stendhaltools,server_build">
<target name="balance" description="Run enemy balancer." depends="compile_stendhaltools,server_build">
<java classname="games.stendhal.tools.BalanceRPGame">
<classpath>
<pathelement path="${build_stendhaltools}"/>

View file

@ -1,6 +1,6 @@
site_name: Stendhal 1.49.5 Reference Documentation
site_name: Stendhal 1.48 Reference Documentation
site_description: A fun, friendly, & free multiplayer online adventure game.
copyright: Copyright © 2003-2026 Stendhal
copyright: Copyright © 2003-2024 Stendhal
site_url: https://stendhalgame.org/
nav:
- Index: /reference/

View file

@ -16,18 +16,18 @@
-->
<shop name="deniran_accessories" type="outfit">
<outfit name="white balloon" layers="detail=2" price="25000"/>
<outfit name="black balloon" layers="detail=3" price="25000"/>
<outfit name="bone" layers="detail=4" price="50000"/>
<outfit name="umbrella" layers="detail=7" price="25000"/>
<outfit name="sword" layers="detail=8" price="125000"/>
<outfit name="knife" layers="detail=9" price="62500"/>
<outfit name="spear" layers="detail=10" price="75000"/>
<outfit name="bow" layers="detail=11" price="87500"/>
<outfit name="axe1" layers="detail=12" price="125000"/>
<outfit name="axe2" layers="detail=13" price="125000"/>
<outfit name="kite shield" layers="detail=14" price="112500"/>
<outfit name="shield" layers="detail=15" price="112500"/>
<outfit name="white balloon" layers="detail=2" price="50000"/>
<outfit name="black balloon" layers="detail=3" price="50000"/>
<outfit name="bone" layers="detail=4" price="100000"/>
<outfit name="umbrella" layers="detail=7" price="50000"/>
<outfit name="sword" layers="detail=8" price="250000"/>
<outfit name="knife" layers="detail=9" price="125000"/>
<outfit name="spear" layers="detail=10" price="150000"/>
<outfit name="bow" layers="detail=11" price="175000"/>
<outfit name="axe1" layers="detail=12" price="250000"/>
<outfit name="axe2" layers="detail=13" price="250000"/>
<outfit name="kite shield" layers="detail=14" price="225000"/>
<outfit name="shield" layers="detail=15" price="225000"/>
<merchant name="Gwen" flags="configure,noOffer,removeDetailColor,confirmTemp,confirmBalloon,resetBeforeChange"
note="do not expire"/>
</shop>

1779
data/languages/en.txt Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1097,7 +1097,6 @@ 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.=
@ -1266,9 +1265,6 @@ 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
@ -1411,9 +1407,6 @@ 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
@ -1642,7 +1635,6 @@ 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.=
@ -1710,7 +1702,6 @@ 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
@ -2277,7 +2268,6 @@ 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.=

View file

@ -1,112 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.8" tiledversion="1.8.4" orientation="orthogonal" renderorder="right-down" width="128" height="128" tilewidth="32" tileheight="32" infinite="0" nextlayerid="9" nextobjectid="1">
<tileset firstgid="1" name="ground/brown" tilewidth="32" tileheight="32" tilecount="5" columns="5">
<image source="../../tileset/ground/brown.png" trans="ff00ff" width="160" height="32"/>
</tileset>
<tileset firstgid="6" name="logic/collision" tilewidth="32" tileheight="32" tilecount="1" columns="1">
<image source="../../tileset/logic/collision.png" width="32" height="32"/>
</tileset>
<tileset firstgid="7" name="logic/creature/chaos" tilewidth="32" tileheight="32" tilecount="7" columns="7">
<image source="../../tileset/logic/creature/chaos.png" width="224" height="32"/>
</tileset>
<tileset firstgid="14" name="ground/ground" tilewidth="32" tileheight="32" tilecount="33" columns="3">
<image source="../../tileset/ground/ground.png" width="96" height="352"/>
</tileset>
<tileset firstgid="47" name="ground/water/pool" tilewidth="32" tileheight="32" tilecount="72" columns="3">
<image source="../../tileset/ground/water/pool.png" width="96" height="768"/>
</tileset>
<tileset firstgid="119" name="ground/brown_edges" tilewidth="32" tileheight="32" tilecount="18" columns="6">
<image source="../../tileset/ground/brown_edges.png" width="192" height="96"/>
</tileset>
<tileset firstgid="137" name="ground/brown_corners" tilewidth="32" tileheight="32" tilecount="8" columns="4">
<image source="../../tileset/ground/brown_corners.png" width="128" height="64"/>
</tileset>
<tileset firstgid="145" name="ground/earth_edges" tilewidth="32" tileheight="32" tilecount="18" columns="6">
<image source="../../tileset/ground/earth_edges.png" width="192" height="96"/>
</tileset>
<tileset firstgid="163" name="ground/grass_corners" tilewidth="32" tileheight="32" tilecount="24" columns="6">
<image source="../../tileset/ground/grass_corners.png" width="192" height="128"/>
</tileset>
<tileset firstgid="187" name="ground/rock/green_crystal2" tilewidth="32" tileheight="32" tilecount="20" columns="5">
<image source="../../tileset/ground/rock/green_crystal2.png" width="160" height="128"/>
</tileset>
<tileset firstgid="207" name="ground/rock/pebbles" tilewidth="32" tileheight="32" tilecount="3" columns="1">
<image source="../../tileset/ground/rock/pebbles.png" width="32" height="96"/>
</tileset>
<tileset firstgid="210" name="ground/rock/blue_crystal" tilewidth="32" tileheight="32" tilecount="20" columns="5">
<image source="../../tileset/ground/rock/blue_crystal.png" width="160" height="128"/>
</tileset>
<tileset firstgid="230" name="ground/rock/rocks_3" tilewidth="32" tileheight="32" tilecount="16" columns="4">
<image source="../../tileset/ground/rock/rocks_3.png" width="128" height="128"/>
</tileset>
<tileset firstgid="246" name="plant/bush/various_small" tilewidth="32" tileheight="32" tilecount="9" columns="3">
<image source="../../tileset/plant/bush/various_small.png" width="96" height="96"/>
</tileset>
<tileset firstgid="255" name="plant/mosses" tilewidth="32" tileheight="32" tilecount="16" columns="4">
<image source="../../tileset/plant/mosses.png" width="128" height="128"/>
</tileset>
<tileset firstgid="271" name="logic/creature/hybrid" tilewidth="32" tileheight="32" tilecount="16" columns="8">
<image source="../../tileset/logic/creature/hybrid.png" trans="ff00ff" width="256" height="64"/>
</tileset>
<tileset firstgid="287" name="ground/grass_2" tilewidth="32" tileheight="32" tilecount="21" columns="3">
<image source="../../tileset/ground/grass_2.png" width="96" height="224"/>
</tileset>
<tileset firstgid="308" name="ground/water/whirlpool" tilewidth="32" tileheight="32" tilecount="8" columns="1">
<image source="../../tileset/ground/water/whirlpool.png" width="32" height="256"/>
</tileset>
<tileset firstgid="316" name="logic/creature/mythical_being" tilewidth="32" tileheight="32" tilecount="24" columns="8">
<image source="../../tileset/logic/creature/mythical_being.png" width="256" height="96"/>
</tileset>
<tileset firstgid="340" name="building/wall/int_sandstone" tilewidth="32" tileheight="32" tilecount="15" columns="3">
<image source="../../tileset/building/wall/int_sandstone.png" width="96" height="160"/>
</tileset>
<tileset firstgid="355" name="building/entrance/shadow" tilewidth="32" tileheight="32" tilecount="3" columns="1">
<image source="../../tileset/building/entrance/shadow.png" width="32" height="96"/>
</tileset>
<tileset firstgid="358" name="plant/flower/hothouse_flowers" tilewidth="32" tileheight="32" tilecount="72" columns="6">
<image source="../../tileset/plant/flower/hothouse_flowers.png" width="192" height="384"/>
</tileset>
<tileset firstgid="430" name="logic/creature/insect" tilewidth="32" tileheight="32" tilecount="5" columns="5">
<image source="../../tileset/logic/creature/insect.png" width="160" height="32"/>
</tileset>
<layer id="1" name="0_floor" width="128" height="128">
<data encoding="base64" compression="gzip">
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
</data>
</layer>
<layer id="2" name="1_terrain" width="128" height="128">
<data encoding="base64" compression="gzip">
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
</data>
</layer>
<layer id="3" name="2_object" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3bMQrAIAwFUHOrnqU6tPe/RCkOHUUQrPjelDGQ5ROSlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2dcbsDpjpMn8AoEORHQAAaMhRc6O9437eXeMdXw0AAAAAAAAAAACMcbjLAwAW5b8A+sn/AAAAAAAAAAAAAPAvD8b9VLMAAAEA
</data>
</layer>
<layer id="4" name="3_roof" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3c20rDQBAG4Lz/O1lBL6q3HloVD/sceuMUWppeGKOwmbL7ffBDSgM7sJDdzEKGAQAAAAAAAAAAAIBaPiNf2UUAAAAAAAAAAAAAQEdKdgEAAFRVRtebrCJY1F3kPrsI0mwjTxP/l4XqIMdr5C3yuP/tud+f58hLdhGkeY98ZBcB/MvDcFy/6Y/1u2/Wb8b0dNq3e0//6Rtbv/V0aNuhpwMAQJt27wIluwigqpJdAGnKjHv0/fqm79e+qbNefb/2Oevtm7NeDuz3+ma/1zf7PQAAAAAAAAAAMlxEVtlFcGI8J7eVx7qKXFceg79Zck7WkZuFxmKeqTm5rDhu7WcNAHA+rPsA0Laa/QMAAAAAAAAAAAAAAADOxzeEbpmpAAABAA==
</data>
</layer>
<layer id="5" name="4_roof_add" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3OAQ0AAAgDoPfPZyBj3E1IQAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwy7QDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPLYaZVTEAAAEA
</data>
</layer>
<layer id="6" name="objects" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3cQQqDMBAF0Liytr1C73/NIrgqoVA1nZh5b+PGxQdlNF9MKQAAAAAAAABAhHt0AAAAAAAAgIE8ogMAAAAAX83b0Roe8nhGBwAAAICij8pqTy/xmk6PcViPma7MPBjf5zWeq2cBtOeZA0BrvscDe9XmR6v3V91Wv3QmwEp/AQAAwC9u0QEAAAAAACAx/xBAbvbzBQAAAKBGXwQAAFyNven+w3oRODIHzBDO4l4a0xIdAOiOeQ8AAAAAAAAAAAAAAJDXG8ZnpKUAAAEA
</data>
</layer>
<layer id="7" name="collision" width="128" height="128" visible="0" opacity="0.58">
<data encoding="base64" compression="gzip">
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=
</data>
</layer>
<layer id="8" name="protection" width="128" height="128" visible="0">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3BAQEAAACAkP6v7ggKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAauuOl9cAAAEA
</data>
</layer>
</map>
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.8" tiledversion="1.8.4" orientation="orthogonal" renderorder="right-down" width="128" height="128" tilewidth="32" tileheight="32" infinite="0" nextlayerid="9" nextobjectid="1">
<tileset firstgid="1" name="ground/brown" tilewidth="32" tileheight="32" tilecount="5" columns="5">
<image source="../../tileset/ground/brown.png" trans="ff00ff" width="160" height="32"/>
</tileset>
<tileset firstgid="6" name="logic/collision" tilewidth="32" tileheight="32" tilecount="1" columns="1">
<image source="../../tileset/logic/collision.png" width="32" height="32"/>
</tileset>
<tileset firstgid="7" name="logic/creature/chaos" tilewidth="32" tileheight="32" tilecount="7" columns="7">
<image source="../../tileset/logic/creature/chaos.png" width="224" height="32"/>
</tileset>
<tileset firstgid="14" name="ground/ground" tilewidth="32" tileheight="32" tilecount="33" columns="3">
<image source="../../tileset/ground/ground.png" width="96" height="352"/>
</tileset>
<tileset firstgid="47" name="ground/water/pool" tilewidth="32" tileheight="32" tilecount="72" columns="3">
<image source="../../tileset/ground/water/pool.png" width="96" height="768"/>
</tileset>
<tileset firstgid="119" name="ground/brown_edges" tilewidth="32" tileheight="32" tilecount="18" columns="6">
<image source="../../tileset/ground/brown_edges.png" width="192" height="96"/>
</tileset>
<tileset firstgid="137" name="ground/brown_corners" tilewidth="32" tileheight="32" tilecount="8" columns="4">
<image source="../../tileset/ground/brown_corners.png" width="128" height="64"/>
</tileset>
<tileset firstgid="145" name="ground/earth_edges" tilewidth="32" tileheight="32" tilecount="18" columns="6">
<image source="../../tileset/ground/earth_edges.png" width="192" height="96"/>
</tileset>
<tileset firstgid="163" name="ground/grass_corners" tilewidth="32" tileheight="32" tilecount="24" columns="6">
<image source="../../tileset/ground/grass_corners.png" width="192" height="128"/>
</tileset>
<tileset firstgid="187" name="ground/rock/green_crystal2" tilewidth="32" tileheight="32" tilecount="20" columns="5">
<image source="../../tileset/ground/rock/green_crystal2.png" width="160" height="128"/>
</tileset>
<tileset firstgid="207" name="ground/rock/pebbles" tilewidth="32" tileheight="32" tilecount="3" columns="1">
<image source="../../tileset/ground/rock/pebbles.png" width="32" height="96"/>
</tileset>
<tileset firstgid="210" name="ground/rock/blue_crystal" tilewidth="32" tileheight="32" tilecount="20" columns="5">
<image source="../../tileset/ground/rock/blue_crystal.png" width="160" height="128"/>
</tileset>
<tileset firstgid="230" name="ground/rock/rocks_3" tilewidth="32" tileheight="32" tilecount="16" columns="4">
<image source="../../tileset/ground/rock/rocks_3.png" width="128" height="128"/>
</tileset>
<tileset firstgid="246" name="plant/bush/various_small" tilewidth="32" tileheight="32" tilecount="9" columns="3">
<image source="../../tileset/plant/bush/various_small.png" width="96" height="96"/>
</tileset>
<tileset firstgid="255" name="plant/mosses" tilewidth="32" tileheight="32" tilecount="16" columns="4">
<image source="../../tileset/plant/mosses.png" width="128" height="128"/>
</tileset>
<tileset firstgid="271" name="logic/creature/hybrid" tilewidth="32" tileheight="32" tilecount="16" columns="8">
<image source="../../tileset/logic/creature/hybrid.png" trans="ff00ff" width="256" height="64"/>
</tileset>
<tileset firstgid="287" name="ground/grass_2" tilewidth="32" tileheight="32" tilecount="21" columns="3">
<image source="../../tileset/ground/grass_2.png" width="96" height="224"/>
</tileset>
<tileset firstgid="308" name="ground/water/whirlpool" tilewidth="32" tileheight="32" tilecount="8" columns="1">
<image source="../../tileset/ground/water/whirlpool.png" width="32" height="256"/>
</tileset>
<tileset firstgid="316" name="logic/creature/mythical_being" tilewidth="32" tileheight="32" tilecount="24" columns="8">
<image source="../../tileset/logic/creature/mythical_being.png" width="256" height="96"/>
</tileset>
<tileset firstgid="340" name="building/wall/int_sandstone" tilewidth="32" tileheight="32" tilecount="15" columns="3">
<image source="../../tileset/building/wall/int_sandstone.png" width="96" height="160"/>
</tileset>
<tileset firstgid="355" name="building/entrance/shadow" tilewidth="32" tileheight="32" tilecount="3" columns="1">
<image source="../../tileset/building/entrance/shadow.png" width="32" height="96"/>
</tileset>
<tileset firstgid="358" name="plant/flower/hothouse_flowers" tilewidth="32" tileheight="32" tilecount="72" columns="6">
<image source="../../tileset/plant/flower/hothouse_flowers.png" width="192" height="384"/>
</tileset>
<tileset firstgid="430" name="logic/creature/insect" tilewidth="32" tileheight="32" tilecount="5" columns="5">
<image source="../../tileset/logic/creature/insect.png" width="160" height="32"/>
</tileset>
<layer id="1" name="0_floor" width="128" height="128">
<data encoding="base64" compression="gzip">
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
</data>
</layer>
<layer id="2" name="1_terrain" width="128" height="128">
<data encoding="base64" compression="gzip">
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
</data>
</layer>
<layer id="3" name="2_object" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3bMQrAIAwFUHOrnqU6tPe/RCkOHUUQrPjelDGQ5ROSlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2dcbsDpjpMn8AoEORHQAAaMhRc6O9437eXeMdXw0AAAAAAAAAAACMcbjLAwAW5b8A+sn/AAAAAAAAAAAAAPAvD8b9VLMAAAEA
</data>
</layer>
<layer id="4" name="3_roof" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3c20rDQBAG4Lz/O1lBL6q3HloVD/sceuMUWppeGKOwmbL7ffBDSgM7sJDdzEKGAQAAAAAAAAAAAIBaPiNf2UUAAAAAAAAAAAAAQEdKdgEAAFRVRtebrCJY1F3kPrsI0mwjTxP/l4XqIMdr5C3yuP/tud+f58hLdhGkeY98ZBcB/MvDcFy/6Y/1u2/Wb8b0dNq3e0//6Rtbv/V0aNuhpwMAQJt27wIluwigqpJdAGnKjHv0/fqm79e+qbNefb/2Oevtm7NeDuz3+ma/1zf7PQAAAAAAAAAAMlxEVtlFcGI8J7eVx7qKXFceg79Zck7WkZuFxmKeqTm5rDhu7WcNAHA+rPsA0Laa/QMAAAAAAAAAAAAAAADOxzeEbpmpAAABAA==
</data>
</layer>
<layer id="5" name="4_roof_add" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3OAQ0AAAgDoPfPZyBj3E1IQAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwy7QDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPLYaZVTEAAAEA
</data>
</layer>
<layer id="6" name="objects" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3cQQqDMBAF0Liytr1C73/NIrgqoVA1nZh5b+PGxQdlNF9MKQAAAAAAAABAhHt0AAAAAAAAgIE8ogMAAAAAX83b0Roe8nhGBwAAAICij8pqTy/xmk6PcViPma7MPBjf5zWeq2cBtOeZA0BrvscDe9XmR6v3V91Wv3QmwEp/AQAAwC9u0QEAAAAAACAx/xBAbvbzBQAAAKBGXwQAAFyNven+w3oRODIHzBDO4l4a0xIdAOiOeQ8AAAAAAAAAAAAAAJDXG8ZnpKUAAAEA
</data>
</layer>
<layer id="7" name="collision" width="128" height="128" visible="0" opacity="0.58">
<data encoding="base64" compression="gzip">
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=
</data>
</layer>
<layer id="8" name="protection" width="128" height="128" visible="0">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3BAQEAAACAkP6v7ggKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAauuOl9cAAAEA
</data>
</layer>
</map>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -2,24 +2,6 @@ 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*
@ -706,7 +688,7 @@ Changelog
*other*
- started to work on a list of all contributors, please help to add to it:
https://codeberg.org/arianne/stendhal/src/commit/master/doc/contributors.md
https://github.com/arianne/stendhal/blob/master/doc/contributors.md
1.34
@ -1414,7 +1396,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 Codeberg, Github and Sourceforge in both directions
- Git repository is mirrored instantaneously between 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
@ -2577,7 +2559,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://mastodon.social/@stendhaltrade
- Harold tweets trades, the official stendhal server uses https://twitter.com/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

Binary file not shown.

755
package-lock.json generated
View file

@ -1,736 +1,21 @@
{
"name": "stendhal",
"version": "1.0.0",
"lockfileVersion": 3,
"lockfileVersion": 2,
"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": {
"@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"
"typescript": "^5.1.0"
}
},
"node_modules/typescript": {
"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",
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -738,29 +23,13 @@
"engines": {
"node": ">=14.17"
}
},
"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"
}
}
},
"dependencies": {
"typescript": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w=="
}
}
}

View file

@ -28,10 +28,11 @@
},
"homepage": "https://arianne-project.org",
"private": true,
"dependencies": {
"typescript": "^5.2.0"
},
"devDependencies": {
"esbuild": "0.28.1",
"showdown": "^2.1.0",
"typedoc": "^0.28.15",
"typescript": "^5.9.3"
"typedoc": "^0.25.12"
}
}

View file

@ -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|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)(/.*)?";
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)(/.*)?";
final Pattern whitelist = Pattern.compile(p);

View file

@ -1,6 +1,6 @@
/* $Id$ */
/***************************************************************************
* (C) Copyright 2003-2025 - Stendhal *
* (C) Copyright 2003-2010 - Stendhal *
***************************************************************************
***************************************************************************
* *
@ -12,7 +12,6 @@
***************************************************************************/
package games.stendhal.client.gui.imageviewer;
import java.net.URI;
import java.net.URL;
import org.apache.log4j.Logger;
@ -54,7 +53,7 @@ public final class RPEventImageViewer {
try {
URL url = null;
if (path.startsWith("http://") || path.startsWith("https://")) {
url = new URI(path).toURL();
url = new URL(path);
} else {
url = DataLoader.getResource(path);
}

View file

@ -1,5 +1,5 @@
/***************************************************************************
* (C) Copyright 2012-2025 Faiumoni e. V. *
* (C) Copyright 2012-2022 Faiumoni e. V. *
***************************************************************************
***************************************************************************
* *
@ -15,8 +15,6 @@ 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;
@ -62,8 +60,8 @@ public class DataLoader {
File file = contentFilenameMapping.get(slashlessName);
if (file != null) {
try {
return new URI("jar:" + file.toURI().toASCIIString() + "!/" + slashlessName).toURL();
} catch (MalformedURLException | URISyntaxException e) {
return new URL("jar:" + file.toURI().toASCIIString() + "!/" + slashlessName);
} catch (MalformedURLException e) {
logger.error(e, e);
}
}

View file

@ -1,6 +1,6 @@
/* $Id$ */
/***************************************************************************
* (C) Copyright 2003-2025 - Stendhal *
* (C) Copyright 2003-2010 - Stendhal *
***************************************************************************
***************************************************************************
* *
@ -22,8 +22,6 @@ 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;
@ -355,7 +353,7 @@ public class SpriteStore {
URL url;
if (ref.startsWith("http://") || ref.startsWith("https://")) {
logger.info("Loading sprite from a URL...");
url = new URI(ref).toURL();
url = new URL(ref);
} else {
url = DataLoader.getResource(ref);
}
@ -374,7 +372,7 @@ public class SpriteStore {
// use ImageIO to read the image in
sourceImage = ImageIO.read(url);
} catch (final IOException | URISyntaxException e) {
} catch (final IOException e) {
logger.error("Failed to read: " + ref, e);
if (ref.equals(FAILSAFE_ICON_REF)) {

View file

@ -1,5 +1,5 @@
/***************************************************************************
* (C) Copyright 2003-2025 - Stendhal *
* (C) Copyright 2003-2016 - Stendhal *
***************************************************************************
***************************************************************************
* *
@ -20,7 +20,6 @@ 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;
@ -110,15 +109,13 @@ public class HttpClient {
// sometimes problems with the webservers being slow or not responding
// at all.
try {
final URL url = new URI(urlString).toURL();
final URL url = new URL(urlString);
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);
@ -137,7 +134,7 @@ public class HttpClient {
if(!passedRedirects.contains(newUrl)) {
// open the new connnection again
passedRedirects.add(newUrl);
connection = (HttpURLConnection) new URI(newUrl).toURL().openConnection();
connection = (HttpURLConnection) new URL(newUrl).openConnection();
redirect = isRedirect(connection.getResponseCode());
} else {
System.err.println(String.format("The URL '%s' leads into a circular redirect.", url));

View file

@ -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.49.5";
public static final String VERSION = "1.48";
/** 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.

View file

@ -1,6 +1,6 @@
/* $Id$ */
/***************************************************************************
* (C) Copyright 2003-2025 - Stendhal *
* (C) Copyright 2003-2024 - Stendhal *
***************************************************************************
***************************************************************************
* *
@ -16,7 +16,7 @@ import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
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 URI uri = new URI(xmlFile);
is = uri.toURL().openStream();
final URL url = new URL(xmlFile);
is = url.openStream();
}
// Wrap with GZIP decoder for .tmx.gz files

View file

@ -1,5 +1,5 @@
/***************************************************************************
* (C) Copyright 2003-2025 - Marauroa *
* (C) Copyright 2003-2024 - Marauroa *
***************************************************************************
***************************************************************************
* *
@ -16,8 +16,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
@ -377,7 +376,8 @@ public class StendhalRPRuleProcessor implements IRPRuleProcessor {
protected void executeZonePreLogic() {
for (final IRPZone zoneI : SingletonRepository.getRPWorld()) {
((StendhalRPZone) zoneI).preLogic();
final StendhalRPZone zone = (StendhalRPZone) zoneI;
zone.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 URI uri = new URI(msg);
final URL url = new URL(msg);
HttpURLConnection.setFollowRedirects(false);
final HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();
final HttpURLConnection connection = (HttpURLConnection) url.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 | URISyntaxException e) {
} catch (final IOException e) {
if (firstWelcomeException) {
logger.warn("Can't read server_welcome from marauroa.ini", e);
firstWelcomeException = false;

View file

@ -1,5 +1,5 @@
/***************************************************************************
* (C) Copyright 2003-2025 - Stendhal *
* (C) Copyright 2003-2023 - Stendhal *
***************************************************************************
***************************************************************************
* *
@ -26,6 +26,7 @@ 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;
@ -63,7 +64,7 @@ public class ProductionAchievementFactory extends AbstractAchievementFactory {
Achievement.MEDIUM_BASE_SCORE, true,
new AndCondition(
new PlayerProducedNumberOfItemsCondition(1, foods),
new QuestCompletedCondition("coal_for_haunchy"))));
new QuestStateStartsWithCondition("coal_for_haunchy","waiting;"))));
final List<String> drinklist = producerRegister.getProducedItemNames("drink");

View file

@ -1,5 +1,5 @@
/***************************************************************************
* Copyright © 2003-2025 - Arianne *
* Copyright © 2003-2024 - Arianne *
***************************************************************************
***************************************************************************
* *
@ -530,7 +530,7 @@ public abstract class GuidedEntity extends ActiveEntity {
*/
@Deprecated
public final boolean atMovementRadius() {
return movementRadius > 0 && Math.sqrt(getDistanceFromOrigin()) >= movementRadius;
return Math.sqrt(getDistanceFromOrigin()) >= movementRadius;
}
protected final Direction getDirectionFromOrigin() {

View file

@ -1,6 +1,6 @@
/* $Id$ */
/***************************************************************************
* (C) Copyright 2003-2025 - Marauroa *
* (C) Copyright 2003-2024 - 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();

View file

@ -1 +1 @@
package games.stendhal.server.entity.npc.behaviour.impl.idle;
package games.stendhal.server.entity.npc.behaviour.impl.idle;

View file

@ -1,6 +1,6 @@
/* $Id$ */
/***************************************************************************
* (C) Copyright 2003-2024 - Stendhal *
* (C) Copyright 2003-2010 - Stendhal *
***************************************************************************
***************************************************************************
* *
@ -75,7 +75,7 @@ public class FatherNPC implements ZoneConfigurator {
};
npc.setOutfit(1, 34, 0, null, 0, null, 27, null, 0);
npc.setOutfit(1, 34, 7, null, 0, null, 27, null, 0);
npc.setPosition(12, 7);
npc.setDirection(Direction.DOWN);
npc.initHP(100);

View file

@ -1,5 +1,5 @@
/***************************************************************************
* Copyright © 2020-2025 - Faiumoni e. V. *
* Copyright © 2020-2024 - 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 your help. Now I have all the information I need to complete my #bestiary."
npc.say("Thanks so much for you 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);
}}));

View file

@ -64,7 +64,7 @@ public class AsynchronousProgramExecutor extends Thread {
}
/**
* sends the message to the social media account
* sends the message to the twitter account
*
* @param cmd command
*/

View file

@ -1,5 +1,5 @@
/***************************************************************************
* (C) Copyright 2003-2025 - Stendhal *
* (C) Copyright 2003-2023 - Stendhal *
***************************************************************************
***************************************************************************
* *
@ -18,8 +18,7 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
@ -136,7 +135,7 @@ public class SourceForgeContributorsGenerator {
@SuppressWarnings("unchecked")
private void fetchProfiles(Map<String, Map<String, Object>> contributors) throws IOException {
for (Map.Entry<String, Map<String, Object>> entry : contributors.entrySet()) {
try (Reader reader = new InputStreamReader(new URI("https://sourceforge.net/rest/u/" + entry.getKey() +"/profile").toURL().openStream(), "UTF-8")) {
try (Reader reader = new InputStreamReader(new URL("https://sourceforge.net/rest/u/" + entry.getKey() +"/profile").openStream(), "UTF-8")) {
Map<String, Object> contributor = entry.getValue();
Map<String, Object> profile = (Map<String, Object>) JSONValue.parse(reader);
String fullname = (String) profile.get("name");
@ -150,7 +149,7 @@ public class SourceForgeContributorsGenerator {
if (country != null && !country.trim().equals("")) {
contributor.put("country", country);
}
} catch (FileNotFoundException | URISyntaxException e) {
} catch (FileNotFoundException e) {
System.err.println("Profile " + entry.getKey() + " does not exist.");
}
}

View file

@ -1,6 +1,6 @@
/* $Id$ */
/***************************************************************************
* (C) Copyright 2003-2025 - Stendhal *
* (C) Copyright 2003-2011 - Stendhal *
***************************************************************************
***************************************************************************
* *
@ -18,8 +18,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@ -81,14 +80,13 @@ 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, URISyntaxException {
private void loadOldUpdateProperties() throws IOException {
prop = new Properties();
InputStream is;
if (oldFile.indexOf(":") > 2) {
URI uri = new URI(oldFile);
is = uri.toURL().openStream();
URL url = new URL(oldFile);
is = url.openStream();
} else {
is = new FileInputStream(oldFile);
}

View file

@ -335,7 +335,7 @@ div.logcolR {
background: url(/data/gui/danger.png) repeat;
}
.qm-button, canvas#held-object {
.qm-button, img#held-object {
position: absolute;
display: none;
opacity: 0.5;
@ -708,16 +708,3 @@ 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;
}

1169
src/js/jsxgraph-util.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -486,11 +486,14 @@
<div class="shortcuts-dialog"></div>
</template>
<canvas id="held-object" class="unselectable notarget" draggable="false"></canvas>
<img id="held-object" class="hidden unselectable notarget" draggable="false">
<!-- place at end of document so drawn above all other elements -->
<img id="click-indicator" class="unclickable notarget">
<!-- hidden canvas for manipulating images before being displayed -->
<canvas id="drawing-stage" class="hidden"></canvas>
<script type="text/javascript" src="stendhal-compiled.js"></script>
</body>

View file

@ -1,5 +1,5 @@
/***************************************************************************
* Copyright © 2023-2026 - Faiumoni e. V. *
* Copyright © 2023-2024 - Faiumoni e. V. *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
@ -9,21 +9,25 @@
* *
***************************************************************************/
import { marauroa, Deserializer, RPObject } from "marauroa"
import { stendhal } from "./stendhal";
declare var marauroa: any;
declare var stendhal: any;
import { StendhalPerceptionListener } from "./PerceptionListener";
import { PerceptionListener } from "./PerceptionListener";
import { singletons } from "./SingletonRepo";
import { Paths } from "./data/Paths";
import { Color } from "./data/color/Color";
import { EntityRegistry } from "./entity/EntityRegistry";
import { Ground } from "./entity/Ground";
import { RPObject } from "./entity/RPObject";
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";
@ -36,12 +40,6 @@ 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";
/**
@ -64,7 +62,6 @@ export class Client {
/** Singleton instance. */
private static instance: Client;
public loaded = false;
/**
@ -81,7 +78,7 @@ export class Client {
* Hidden singleton constructor.
*/
private constructor() {
// empty
// do nothing
}
/**
@ -94,18 +91,19 @@ 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();
// 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);
this.initZone();
}
/**
@ -119,6 +117,9 @@ 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 = [];
}
@ -140,9 +141,22 @@ 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.
@ -163,11 +177,11 @@ export class Client {
singletons.getEventRegistry().init();
// initialize tileset animation data
TileStore.get().init();
singletons.getTileStore().init();
// initialize emoji data
singletons.getEmojiStore().init();
// initialize outfit data
OutfitStore.get().init();
stendhal.data.outfit.init();
new DesktopUserInterfaceFactory().create();
@ -190,11 +204,11 @@ export class Client {
stendhal.ui.globalInternalWindow = new DialogHandler();
// pre-cache images & sounds
htmlImageStore.startupCache();
stendhal.data.sprites.startupCache();
stendhal.sound.startupCache();
if (document.getElementById("viewport")) {
ViewPort.get().draw();
stendhal.ui.gamewindow.draw.apply(stendhal.ui.gamewindow, arguments);
}
}
@ -245,14 +259,11 @@ export class Client {
* Registers Marauroa event handlers.
*/
registerMarauroaEventHandlers() {
marauroa.clientFramework.onDisconnect = function(_reason: string, _code: number, _wasClean: boolean) {
marauroa.clientFramework.onDisconnect = function(_reason: string, _error: string) {
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<string, string>) {
if (config["client_login_url"]) {
@ -270,25 +281,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 = (characters: {[key: string]: RPObject}) => {
marauroa.clientFramework.onAvailableCharacterDetails = function(characters: {[key: string]: RPObject}) {
SingletonFloatingWindow.closeAll();
if (!Object.keys(characters).length && this.username) {
marauroa.clientFramework.createCharacter(this.username, {});
@ -320,9 +331,9 @@ export class Client {
}
items[i]["ack"] = true;
}
}.bind(this);
};
marauroa.clientFramework.onTransfer = (items: any) => {
marauroa.clientFramework.onTransfer = function(items: any) {
var data = {} as any;
var zoneName = ""
for (var i in items) {
@ -334,13 +345,34 @@ export class Client {
this.onDataMap(items[i]["data"]);
}
}
TileMap.get().onTransfer(zoneName, data);
stendhal.data.map.onTransfer(zoneName, data);
};
// update user interface on perceptions
if (document.getElementById("viewport")) {
// override perception listener
marauroa.perceptionListener = new StendhalPerceptionListener();
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);
}
}
}
}
@ -388,19 +420,18 @@ export class Client {
document.getElementById("body")!.addEventListener("mouseenter", stendhal.main.onMouseEnter);
let gamewindow = document.getElementById("viewport")!;
let viewPort = ViewPort.get();
var gamewindow = document.getElementById("viewport")!;
gamewindow.setAttribute("draggable", "true");
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});
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);
singletons.getJoystickController().registerGlobalEventHandlers();
@ -428,7 +459,7 @@ export class Client {
document.addEventListener("click", Client.handleClickIndicator);
document.addEventListener("touchend", Client.handleClickIndicator);
};
click_indicator.src = Paths.gui + "/click_indicator.png";
click_indicator.src = stendhal.paths.gui + "/click_indicator.png";
}
/**
@ -438,10 +469,8 @@ 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 = Deserializer.fromBase64(data);
var deserializer = marauroa.Deserializer.fromBase64(data);
deserializer.readAttributes(zoneinfo);
(ui.get(UIComponentEnum.ZoneInfo) as ZoneInfoComponent).zoneChange(zoneinfo);
@ -452,29 +481,29 @@ export class Client {
// parallax background
if (stendhal.config.getBoolean("effect.parallax")) {
map.setParallax(zoneinfo["parallax"]);
map.setIgnoredTiles(zoneinfo["parallax_ignore_tiles"]);
stendhal.data.map.setParallax(zoneinfo["parallax"]);
stendhal.data.map.setIgnoredTiles(zoneinfo["parallax_ignore_tiles"]);
}
// coloring information
if (zoneinfo["color"] && stendhal.config.getBoolean("effect.lighting")) {
if (zoneinfo["color_method"]) {
viewPort.setColorMethod(zoneinfo["color_method"]);
stendhal.ui.gamewindow.setColorMethod(zoneinfo["color_method"]);
}
if (zoneinfo["blend_method"]) {
viewPort.setBlendMethod(zoneinfo["blend_method"]);
stendhal.ui.gamewindow.setBlendMethod(zoneinfo["blend_method"]);
}
const hsl = Color.numToHSL(Number(zoneinfo["color"]));
viewPort.HSLFilter = hsl.toString();
stendhal.ui.gamewindow.HSLFilter = hsl.toString();
// deprecated
viewPort.filter = "hue-rotate(" + hsl.H + "deg) saturate(" + hsl.S
stendhal.ui.gamewindow.filter = "hue-rotate(" + hsl.H + "deg) saturate(" + hsl.S
+ ") brightness(" + hsl.L + ")";
} else {
viewPort.HSLFilter = undefined;
viewPort.filter = undefined;
stendhal.ui.gamewindow.HSLFilter = undefined;
stendhal.ui.gamewindow.filter = undefined;
}
WeatherRenderer.get().update(zoneinfo["weather"]);
singletons.getWeatherRenderer().update(zoneinfo["weather"]);
}
/**
@ -489,7 +518,7 @@ export class Client {
*/
onMouseEnter(e: MouseEvent) {
// use Stendhal's built-in cursor for entire page
(e.target as HTMLElement).style.cursor = "url(" + Paths.sprites + "/cursor/normal.png) 1 3, auto";
(e.target as HTMLElement).style.cursor = "url(" + stendhal.paths.sprites + "/cursor/normal.png) 1 3, auto";
}
/**

View file

@ -0,0 +1,310 @@
/***************************************************************************
* (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);
}
}
}

View file

@ -9,24 +9,29 @@
* *
***************************************************************************/
import { ui } from "ui/UI";
import { stendhal } from "./stendhal";
declare var stendhal: any;
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";
import { RPObject } from "./entity/RPObject";
/**
* Class to override `marauroa.perceptionListener` & handle creating
* User object.
*/
export class StendhalPerceptionListener extends PerceptionListener {
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];
}
}
}
/**
* Action when an object is added to the world.
@ -37,7 +42,7 @@ export class StendhalPerceptionListener extends PerceptionListener {
* `true` if object was added.
* @see marauroa.perceptionListener.onAdded
*/
override onAdded(obj: RPObject): boolean {
onAdded(obj: RPObject): boolean {
if (obj["c"] === "player") {
if (obj.hasOwnProperty("a")) {
if (obj["a"]["name"] === stendhal.session.getCharName()) {
@ -46,28 +51,6 @@ export class StendhalPerceptionListener extends PerceptionListener {
}
}
}
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);
}
return this._super.onAdded(obj);
}
}

View file

@ -1,5 +1,5 @@
/***************************************************************************
* Copyright © 2003-2026 - Faiumoni e. V. *
* Copyright © 2003-2024 - Faiumoni e. V. *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
@ -10,35 +10,45 @@
***************************************************************************/
import { Client } from "./Client";
import { EventRegistry } from "./event/EventRegistry";
import { EventRegistry } from "./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 { CacheManager } from "./data/CacheManager";
import { CStatus } from "./data/CStatus";
import { CacheManager } from "./data/CacheManager";
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 { Inventory } from "./ui/Inventory";
import { SoftwareJoystickController } from "./ui/SoftwareJoystickController";
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 { 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 {
@ -65,6 +75,10 @@ export class SingletonRepo {
return DownloadUtil;
}
static getDrawingStage(): DrawingStage {
return DrawingStage.get();
}
static getEmojiStore(): EmojiStore {
return EmojiStore.get();
}
@ -81,6 +95,14 @@ export class SingletonRepo {
return GroupManager.get();
}
static getHeldObjectManager(): HeldObjectManager {
return HeldObjectManager.get();
}
static getHTMLManager(): HTMLManager {
return HTMLManager.get();
}
static getInventory(): Inventory {
return Inventory.get();
}
@ -97,6 +119,14 @@ export class SingletonRepo {
return LoopedSoundSourceManager.get();
}
static getMap(): Map {
return Map.get();
}
static getOutfitStore(): OutfitStore {
return OutfitStore.get();
}
static getPaths(): typeof Paths {
return Paths;
}
@ -113,10 +143,33 @@ 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;

View file

@ -9,8 +9,8 @@
* *
***************************************************************************/
import { marauroa } from "marauroa"
import { stendhal } from "./stendhal";
declare var marauroa: any;
declare var stendhal: any;
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);
ViewPort.get().updateClickMode();
stendhal.ui.gamewindow.updateClickMode();
if (newMode) {
Chat.log("info", "Click mode is now set to double click.");

View file

@ -10,6 +10,7 @@
* *
***************************************************************************/
declare var stendhal: any;
import { ParamList } from "./ParamList";
import { SlashAction } from "./SlashAction";
@ -19,7 +20,6 @@ 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 = ui.getPageOffset();
const offset = stendhal.ui.getPageOffset();
const content = new AboutDialog();
const dialog = ui.createSingletonFloatingWindow(

View file

@ -1,5 +1,5 @@
/***************************************************************************
* (C) Copyright 2022-2026 - Stendhal *
* (C) Copyright 2022-2024 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
@ -9,20 +9,21 @@
* *
***************************************************************************/
import { singletons } from "../SingletonRepo";
import { ui } from "../ui/UI";
import { UIComponentEnum } from "../ui/UIComponentEnum";
import { FloatingWindow } from "../ui/toolkit/FloatingWindow";
import { Panel } from "ui/toolkit/Panel";
import { ShowFloatingWindowComponent } from "../ui/component/ShowFloatingWindowComponent";
import { SlashAction } from "./SlashAction";
import { ShowFloatingWindowComponent } from "../ui/component/ShowFloatingWindowComponent";
import { Panel } from "ui/toolkit/Panel";
import { Chat } from "../util/Chat";
import { Debug } from "../util/Debug";
import { marauroa } from "marauroa";
import { WeatherRenderer } from "util/WeatherRenderer";
import { stendhal } from "../stendhal";
declare var marauroa: any;
declare var stendhal: any;
/**
* performances debugging actions
@ -54,8 +55,6 @@ 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;
}
@ -67,8 +66,7 @@ export class DebugAction extends SlashAction {
" /debug ui [pop]",
" /debug weather [<name>]",
" /debug settings",
" /debug touch",
" /debug light"
" /debug touch"
];
Chat.log("client", usage);
}
@ -137,9 +135,14 @@ export class DebugAction extends SlashAction {
if (weather) {
weather = weather.replace(/ /g, "_");
WeatherRenderer.get().update(weather);
const wfilename = stendhal.paths.weather + "/" + weather + ".png";
if (!stendhal.data.sprites.getCached(wfilename)) {
Chat.logH("error", "unknown weather: " + wfilename);
return;
}
}
}
singletons.getWeatherRenderer().update(weather);
}
};

View file

@ -14,8 +14,7 @@ import { SlashAction } from "./SlashAction";
import { ScreenCapture } from "../util/ScreenCapture";
import { stendhal } from "../stendhal";
import { ViewPort } from "ui/ViewPort";
declare var stendhal: any;
export class ScreenCaptureAction extends SlashAction {
@ -36,7 +35,7 @@ export class ScreenCaptureAction extends SlashAction {
return true;
}
this.recorder = new ScreenCapture();
this.recorder.start(ViewPort.get().getElement());
this.recorder.start(stendhal.ui.viewport.getElement() as HTMLCanvasElement);
return true;
}
}

View file

@ -10,7 +10,7 @@
* *
***************************************************************************/
import { stendhal } from "../stendhal";
declare var stendhal: any;
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 = ui.getPageOffset();
const offset = stendhal.ui.getPageOffset();
const content = new SettingsDialog();
const dialog = ui.createSingletonFloatingWindow(

View file

@ -9,7 +9,7 @@
* *
***************************************************************************/
import { marauroa } from "marauroa"
declare var marauroa: any;
import { ParamList } from "./ParamList";
import { Pair } from "../util/Pair";

View file

@ -9,8 +9,8 @@
* *
***************************************************************************/
import { marauroa } from "marauroa"
import { stendhal } from "../stendhal";
declare var marauroa: any;
declare var stendhal: any;
export class CStatus {

View file

@ -9,7 +9,7 @@
* *
***************************************************************************/
import { stendhal } from "../stendhal";
declare var stendhal: any;
/**

View file

@ -1,5 +1,5 @@
/***************************************************************************
* Copyright © 2003-2026 - Stendhal *
* Copyright © 2003-2023 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
@ -9,9 +9,10 @@
* *
***************************************************************************/
import { JSONLoader } from "../util/JSONLoader";
import { htmlImageStore } from "./HTMLImageStore";
import { Paths } from "./Paths";
import { JSONLoader } from "../util/JSONLoader";
declare var stendhal: any;
export class EmojiStore {
@ -70,7 +71,7 @@ export class EmojiStore {
if (!filename) {
return;
}
return htmlImageStore.get(filename);
return stendhal.data.sprites.get(filename);
}
/**

View file

@ -10,7 +10,7 @@
* *
***************************************************************************/
import { stendhal } from "../stendhal";
declare var stendhal: any;
import { Entity } from "../entity/Entity";

View file

@ -9,7 +9,7 @@
* *
***************************************************************************/
import { stendhal } from "../stendhal";
declare var stendhal: any;
import { SlashActionRepo } from "../SlashActionRepo";

View file

@ -1,290 +0,0 @@
/***************************************************************************
* (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 <code>undefined</code>.
*/
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
* <code>true</code> 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();

View file

@ -0,0 +1,174 @@
/***************************************************************************
* (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");
}
}

View file

@ -9,56 +9,54 @@
* *
***************************************************************************/
import { Deserializer } from "marauroa"
import { stendhal } from "../stendhal";
declare var marauroa: any;
declare var stendhal: any;
import { Paths } from "./Paths";
import { LandscapeRenderingStrategy } from "../landscape/LandscapeRenderingStrategy";
import { CombinedTilesetRenderingStrategy } from "../landscape/CombinedTilesetRenderingStrategy";
import { LandscapeRenderingStrategy, CombinedTilesetRenderingStrategy } from "../landscape/LandscapeRenderingStrategy";
import { IndividualTilesetRenderingStrategy } from "../landscape/IndividualTilesetRenderingStrategy";
import { ParallaxBackground } from "../landscape/ParallaxBackground";
export class TileMap {
export class Map {
public currentZoneName = "";
private currentZoneName = "";
public offsetX = 0;
public offsetY = 0;
public zoneSizeX = -1;
public zoneSizeY = -1;
public sizeX = 20;
public sizeY = 15;
private offsetX = 0;
private offsetY = 0;
private zoneSizeX = -1;
private zoneSizeY = -1;
private sizeX = 20;
private sizeY = 15;
public tileWidth = 32;
public tileHeight = 32;
public zoom = 100;
private tileWidth = 32;
private tileHeight = 32;
private zoom = 100;
public tilesetFilenames: string[] = [];
public aImages = -1;
public layerNames: any = -1;
public layers: any = -1;
public firstgids: any = -1;
public gidsindex: any = [];
private tilesetFilenames: string[] = [];
private aImages = -1;
private layerNames: any = -1;
private layers: any = -1;
private firstgids: any = -1;
private gidsindex: any = [];
public drawingError = false;
public targetTileWidth = 0;
public targetTileHeight = 0;
private drawingError = false;
private targetTileWidth = 0;
private targetTileHeight = 0;
public readonly layerGroups = [
private readonly layerGroups: any = [
["0_floor", "1_terrain", "2_object"],
["3_roof", "4_roof_add"]
];
public readonly blendLayers = ["blend_ground", "blend_roof"]
public layerGroupIndexes: any;
private layerGroupIndexes: any;
public strategy: LandscapeRenderingStrategy;
public protection: any;
public collisionData: any;
private strategy: LandscapeRenderingStrategy;
private protection: any;
private collisionData: any;
// alternatives for known images that may be considered violent or mature
public knownSafeTilesets: string[] = [
private knownSafeTilesets: string[] = [
Paths.tileset + "/item/armor/bloodied_small_axe",
Paths.tileset + "/item/blood/floor_stain",
Paths.tileset + "/item/blood/floor_stains_2",
@ -66,22 +64,22 @@ export class TileMap {
Paths.tileset + "/item/blood/small_stains"
];
public parallax: ParallaxBackground;
public parallaxImage?: string;
public ignoredTiles: string[];
private parallax: ParallaxBackground;
private parallaxImage?: string;
private ignoredTiles: string[];
/** Singleton instance. */
private static instance: TileMap;
private static instance: Map;
/**
* Retrieves singleton instance.
*/
static get(): TileMap {
if (!TileMap.instance) {
TileMap.instance = new TileMap();
static get(): Map {
if (!Map.instance) {
Map.instance = new Map();
}
return TileMap.instance;
return Map.instance;
}
/**
@ -126,8 +124,6 @@ export class TileMap {
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();
@ -142,7 +138,7 @@ export class TileMap {
decodeTileset(content: any, name: string) {
var layerData = content[name];
var deserializer = Deserializer.fromBase64(layerData);
var deserializer = marauroa.Deserializer.fromBase64(layerData);
var amount = deserializer.readInt() as number;
this.tilesetFilenames = [];
@ -196,7 +192,7 @@ export class TileMap {
if (!layerData) {
return;
}
var deserializer = Deserializer.fromDeflatedBase64(layerData);
var deserializer = marauroa.Deserializer.fromDeflatedBase64(layerData);
deserializer.readString(); // zone name
this.zoneSizeX = deserializer.readInt();
this.zoneSizeY = deserializer.readInt();

View file

@ -10,14 +10,11 @@
* *
***************************************************************************/
import { DrawingStage } from "../util/DrawingStage";
import { Pair } from "../util/Pair";
import { StringUtil } from "../util/StringUtil";
import { Paths } from "./Paths";
import { stendhal } from "../stendhal";
import { ImageMetadata } from "sprite/image/ImageMetadata";
import { MultilayerImageLoader } from "sprite/image/MultilayerImageLoader";
import { OutfitStore } from "./OutfitStore";
declare var stendhal: any;
/**
@ -116,7 +113,7 @@ export class Outfit {
public getLayers(): Pair<string, number>[] {
const layers: Pair<string, number>[] = [];
// only include valid layers
for (const name of OutfitStore.get().getLayerNames()) {
for (const name of stendhal.data.outfit.getLayerNames()) {
let index = this.layers[name];
if (index != undefined) {
layers.push(new Pair(name, index));
@ -154,7 +151,7 @@ export class Outfit {
* Color value or `undefined`.
*/
public getLayerColor(name: string): number|undefined {
if (OutfitStore.get().isSkinLayer(name)) {
if (stendhal.data.outfit.isSkinLayer(name)) {
name = "skin";
}
return this.coloring && name in this.coloring ? this.coloring[name] : undefined;
@ -196,14 +193,40 @@ export class Outfit {
*
* TODO:
* - use dress layer based on body type
*
* @param {Function} callback
* Function to pass image to when ready.
*/
async toImage(): Promise<ImageBitmap> {
let parts: ImageMetadata[] = [];
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);
let outfitLayers = this.getLayers();
const detailIndex = this.getLayerIndex("detail") || 0;
if (OutfitStore.get().detailHasRearLayer(detailIndex)) {
if (stendhal.data.outfit.detailHasRearLayer(detailIndex)) {
outfitLayers = [new Pair("detail-rear", detailIndex), ...outfitLayers];
}
const layers: HTMLImageElement[] = [];
for (const l of outfitLayers) {
let layerName = l.first;
let suffix = "";
@ -212,21 +235,58 @@ export class Outfit {
layerName = layerName.replace(/-rear$/, "");
}
const layerIndex = l.second;
let layerPath = Paths.sprites + "/outfit/" + layerName + "/"
let layerPath = stendhal.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";
let filter = undefined;
let coloring = this.getLayerColor(layerName);
if (coloring) {
filter = "trueColor";
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));
}
parts.push(new ImageMetadata(layerPath, filter, coloring));
}
return await new MultilayerImageLoader(parts).load();
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);
}
}
}
}

View file

@ -25,7 +25,7 @@ export class OutfitStore {
// player pickable layers
public readonly count: {[key: string]: number} = {
private count: {[key: string]: number} = {
"hat": 19,
"hair": 57,
"mask": 9,

View file

@ -0,0 +1,618 @@
/***************************************************************************
* (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 = <HTMLCanvasElement> 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 <em>a copy</em> 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<Number>}
*/
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<Number>} rgb
* @return {Array<Number>}
*/
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<Number>} hsl
* @return {Array<Number>}
*/
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 <code>undefined</code>.
*/
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
* <code>true</code> 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];
}
};

View file

@ -12,6 +12,9 @@
import { Paths } from "./Paths";
import { JSONLoader } from "../util/JSONLoader";
declare var stendhal: any;
type AnimationMap = {
[key: string]: any;
}
@ -67,7 +70,7 @@ export class TileStore {
this.landscapeMap = this.loadAnimations(
loader.data["landscape"], Paths.tileset + "/");
this.weatherMap = this.loadAnimations(
loader.data["weather"], Paths.weather + "/");
loader.data["weather"], stendhal.paths.weather + "/");
};
loader.load(Paths.tileset + "/animation.json");
}

View file

@ -1,5 +1,5 @@
/***************************************************************************
* Copyright © 2003-2026 - Faiumoni e. V. *
* Copyright © 2003-2024 - Faiumoni e. V. *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
@ -9,11 +9,10 @@
* *
***************************************************************************/
import { marauroa } from "marauroa"
import { stendhal } from "../../stendhal";
declare var marauroa: any;
declare var stendhal: any;
import { LoopedSoundSource } from "../../entity/LoopedSoundSource";
import { Zone } from "entity/Zone";
// server doesn't distinguish between music & looped sound effects so we
@ -180,8 +179,8 @@ export class LoopedSoundSourceManager {
*/
private getZoneEntities(): LoopedSoundSource[] {
const ents: LoopedSoundSource[] = [];
if (Zone.get().entities) {
for (const ent of Zone.get().entities) {
if (stendhal.zone.entities) {
for (const ent of stendhal.zone.entities) {
if (ent instanceof LoopedSoundSource) {
ents.push(ent);
}

View file

@ -16,7 +16,7 @@ import { AbstractEnum } from "../enum/AbstractEnum";
/**
* Available layers of sound.
*
* string names should match `games.stendhal.common.constants.SoundLayer`
* NOTE: string names should match `games.stendhal.common.constants.SoundLayer`
*/
export class SoundLayer extends AbstractEnum<string> {
@ -29,6 +29,9 @@ export class SoundLayer extends AbstractEnum<string> {
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);
@ -44,7 +47,7 @@ export class SoundLayer extends AbstractEnum<string> {
static names(): string[] {
const names: string[] = [];
for (const layer of SoundLayer.layers) {
names.push(layer.value!);
names.push(layer.value);
}
return names;
}

View file

@ -9,15 +9,16 @@
* *
***************************************************************************/
import { marauroa } from "marauroa"
import { stendhal } from "../../stendhal";
declare var marauroa: any;
declare var stendhal: any;
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";
/**
@ -243,7 +244,7 @@ export class SoundManager {
let snd = this.cache[soundName] || this.cacheGlobal[soundName];
if (!snd) {
// add new sound to cache
snd = this.load(soundName, Paths.sounds + "/" + soundName + ".ogg");
snd = this.load(soundName, stendhal.paths.sounds + "/" + soundName + ".ogg");
}
if (!this.cache[soundName]) {
@ -405,7 +406,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, Paths.music + "/" + musicName + ".ogg");
this.load(musicName, stendhal.paths.music + "/" + musicName + ".ogg");
}
return this.playLocalizedLoop(x, y, radius, layer, musicName, volume);
}
@ -423,7 +424,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, Paths.music + "/" + musicName + ".ogg");
this.load(musicName, stendhal.paths.music + "/" + musicName + ".ogg");
}
return this.playGlobalizedLoop(musicName, "music", volume);
}
@ -795,6 +796,6 @@ export class SoundManager {
startupCache() {
// login sound
this.load("ui/login",
Paths.sounds + "/ui/login.ogg", true);
stendhal.paths.sounds + "/ui/login.ogg", true);
}
}

View file

@ -13,9 +13,7 @@ import { Entity } from "./Entity";
import { Direction } from "../util/Direction";
import { MathUtil } from "../util/MathUtil";
import { stendhal } from "../stendhal";
import { TileMap } from "data/TileMap";
import { Zone } from "./Zone";
declare var stendhal: any;
export class ActiveEntity extends Entity {
@ -232,10 +230,9 @@ 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 (map.collision(x, y)) {
if (stendhal.data.map.collision(x, y)) {
return true;
}
}
@ -261,8 +258,8 @@ export class ActiveEntity extends Entity {
var thisEndY = Math.ceil(this["_y"] + this["height"]);
var i;
for (i in Zone.get().entities) {
var other = Zone.get().entities[i];
for (i in stendhal.zone.entities) {
var other = stendhal.zone.entities[i];
if (!this.isObstacle(other)) {
continue;
}

View file

@ -1,5 +1,5 @@
/***************************************************************************
* (C) Copyright 2003-2026 - Stendhal *
* (C) Copyright 2003-2024 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
@ -9,13 +9,9 @@
* *
***************************************************************************/
import { RenderingContext2D } from "util/Types";
import { Entity } from "./Entity";
import { Paths } from "../data/Paths";
import { stendhal } from "../stendhal";
import { ImageSprite } from "sprite/image/ImageSprite";
import { images } from "sprite/image/ImageManager";
declare var stendhal: any;
export class Blood extends Entity {
@ -24,30 +20,27 @@ export class Blood extends Entity {
constructor() {
super();
this.imageSprite = new ImageSprite(
images.load(Paths.sprites + "/combat/blood_red.png"),
0, 0, 32, 32
);
this.sprite = {
height: 32,
width: 32,
filename: stendhal.paths.sprites + "/combat/blood_red.png"
};
}
override set(key: string, value: any) {
super.set(key, value);
if (key === "amount") {
this.imageSprite!.offsetY = parseInt(value, 10) * 32;
this.sprite.offsetY = parseInt(value, 10) * 32;
} else if (key === "class") {
this.imageSprite?.free();
this.imageSprite = new ImageSprite(
images.load(Paths.sprites + "/combat/blood_" + value + ".png"),
0, parseInt(this["amount"], 10) * 32, 32, 32
);
this.sprite.filename = stendhal.paths.sprites + "/combat/blood_" + value + ".png";
}
}
override getCursor(_x: number, _y: number) {
return "url(" + Paths.sprites + "/cursor/walk.png) 1 3, auto";
override getCursor(x: number, y: number) {
return "url(" + stendhal.paths.sprites + "/cursor/walk.png) 1 3, auto";
}
override drawSpriteAt(ctx: RenderingContext2D, x: number, y: number) {
override drawSpriteAt(ctx: CanvasRenderingContext2D, x: number, y: number) {
if (!stendhal.config.getBoolean("effect.blood")) {
return;
}

View file

@ -19,13 +19,23 @@ import { FloatingWindow } from "../ui/toolkit/FloatingWindow";
import { ItemInventoryComponent } from "../ui/component/ItemInventoryComponent";
import { Chat } from "../util/Chat";
import { Paths } from "../data/Paths";
import { marauroa } from "marauroa"
import { stendhal } from "../stendhal";
import { Entity } from "./Entity";
import { ImageSprite } from "sprite/image/ImageSprite";
import { images } from "sprite/image/ImageManager";
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
};
export class Chest extends PopupInventory {
@ -33,22 +43,16 @@ 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.imageSprite!.offsetY = 32;
this.sprite = OPEN_SPRITE;
this.open = true;
}
if (this.isNextTo(marauroa.me as Entity)) {
if (this.isNextTo(marauroa.me)) {
this.openInventoryWindow();
}
}
@ -56,7 +60,7 @@ export class Chest extends PopupInventory {
override unset(key: string) {
super.unset(key);
if (key === "open") {
this.imageSprite!.offsetY = 0;
this.sprite = CLOSED_SPRITE;
this.open = false;
if (this.inventory && this.inventory.isOpen()) {
this.inventory.close();
@ -147,7 +151,7 @@ export class Chest extends PopupInventory {
}
override getCursor(_x: number, _y: number) {
return "url(" + Paths.sprites + "/cursor/bag.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/bag.png) 1 3, auto";
}
}

Some files were not shown because too many files have changed in this diff Show more