Compare commits

..

7 commits

Author SHA1 Message Date
Hendrik Brummermann
cbe0825e9b updated release notes
Some checks failed
Android Build Test / build (push) Has been cancelled
Java CI / build (push) Has been cancelled
2025-12-23 08:49:29 +01:00
Hendrik Brummermann
05d1fb34af properly format item names in description text of shop windows 2025-12-22 19:22:11 +01:00
Hendrik Brummermann
39edb92f1c show outfits in shop sign dialog 2025-12-22 18:08:52 +01:00
Hendrik Brummermann
17003c457a code cleanup 2025-12-22 18:08:45 +01:00
Hendrik Brummermann
ef6c92bd74 use proper table for item shop signs 2025-12-22 18:08:37 +01:00
Hendrik Brummermann
59f2ba9fbd moved ShowItemListEvent and ShowOutfitListEvent out of EventRegistry 2025-12-22 18:08:17 +01:00
Hendrik Brummermann
e00e6b9b4c updated version number 2025-12-21 22:19:28 +01:00
223 changed files with 7988 additions and 7348 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

1
.gitignore vendored
View file

@ -50,3 +50,4 @@ build_server_xmlconf
/node_modules/
/log/
/build-archive/
/bin/

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-2025, arianne-general at lists dot sourceforge dot net
Please have a look at the list of [contributors](doc/contributors.md#contributors).

View file

@ -10,8 +10,8 @@ android {
applicationId namespace
minSdk 21
targetSdk 32
versionCode 1049005
versionName "1.49.5"
versionCode 1049000
versionName "1.49"
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()));

View file

@ -1,3 +1,5 @@
PUT RELEASE ANNOUNCEMENT LINES HERE
The outfit merchants display images of their products now. In order not to fall behind, item sellers have polished their shop signs, to highlight item names.
This in turn prompted Gwen at Deniarn Accessories to cut her prices in half. All in all, it is a good time for some late Christmas shopping.
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.49</string>
</resources>

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

@ -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.49",
"defaultMode": "window",
"port": 0,
"documentRoot": "/resources/",

View file

@ -1,6 +1,6 @@
@echo off
set STENDHAL_VERSION=1.49.5
set STENDHAL_VERSION=1.49
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.49"
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.48
version = 1.49
# FIXME: dynamic method to set this property?
version.android = 1049005
version.android = 1049000
# 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

@ -161,8 +161,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">
@ -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 -->
@ -90,8 +89,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 +167,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>
@ -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.49 Reference Documentation
site_description: A fun, friendly, & free multiplayer online adventure game.
copyright: Copyright © 2003-2026 Stendhal
copyright: Copyright © 2003-2025 Stendhal
site_url: https://stendhalgame.org/
nav:
- Index: /reference/

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.=

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

@ -118,7 +118,7 @@ public class HttpClient {
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.setRequestProperty("User-Agent", "StendhalClient/1.49 (https://arianne-project.org/contact.html)");
connection.setConnectTimeout(myTimeout);
connection.setInstanceFollowRedirects(true);
connection.setUseCaches(false);

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.49";
/** 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

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

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,195 @@
/***************************************************************************
* (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 { ShowItemListEvent } from "./event/ShowItemListEvent";
import { ShowOutfitListEvent } from "./event/ShowOutfitListEvent";
import { SoundEvent } from "./event/SoundEvent";
import { TradeEvent } from "./event/TradeEvent";
import { ViewChangeEvent } from "./event/ViewChangeEvent";
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", new ShowItemListEvent());
this.register("show_outfit_list", new ShowOutfitListEvent());
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";
}
}

View file

@ -9,8 +9,6 @@
* *
***************************************************************************/
import { CorpseSlot } from "./CorpseSlot";
import { RenderingContext2D } from "util/Types";
import { ActivityIndicatorSprite } from "../sprite/ActivityIndicatorSprite";
import { ItemInventoryComponent } from "../ui/component/ItemInventoryComponent";
@ -19,14 +17,9 @@ import { FloatingWindow } from "../ui/toolkit/FloatingWindow";
import { Chat } from "../util/Chat";
import { PopupInventory } from "./PopupInventory";
import { Paths } from "../data/Paths";
import { singletons } from "../SingletonRepo";
import { marauroa, RPObject, RPZone } from "marauroa"
import { stendhal } from "../stendhal";
import { images } from "sprite/image/ImageManager";
import { ImageSprite } from "sprite/image/ImageSprite";
import { ViewPort } from "ui/ViewPort";
declare var marauroa: any;
declare var stendhal: any;
export class Corpse extends PopupInventory {
@ -47,42 +40,61 @@ export class Corpse extends PopupInventory {
override set(key: string, value: any) {
super.set(key, value);
let bloodEnabled = stendhal.config.getBoolean("effect.blood");
this.sprite = this.sprite || {};
const bloodEnabled = stendhal.config.getBoolean("effect.blood");
if (bloodEnabled && (key === "image")) {
this.imageSprite?.free();
this.imageSprite = new ImageSprite(
images.load(Paths.sprites + "/corpse/" + value + ".png"));
this.sprite.filename = stendhal.paths.sprites + "/corpse/" + value + ".png";
} else if (!bloodEnabled && (key === "harmless_image")) {
this.imageSprite?.free();
this.imageSprite = new ImageSprite(
images.load(Paths.sprites + "/corpse/" + value + ".png"));
this.sprite.filename = stendhal.paths.sprites + "/corpse/" + value + ".png";
}
}
override draw(ctx: RenderingContext2D) {
let image = this.imageSprite?.imageRef?.image;
if (!image) {
override draw(ctx: CanvasRenderingContext2D) {
if (!this.sprite) {
return;
}
super.draw(ctx);
if (this.indicator && !this.isEmpty()) {
let viewPort = ViewPort.get();
const tileW = viewPort.targetTileWidth;
const tileH = viewPort.targetTileHeight;
let width = image.width < tileW ? tileW : image.width;
let height = image.height < tileH ? tileH : image.height;
let offsetX = Math.floor((this["width"] * tileW - width) / 2);
let offsetY = Math.floor((this["height"] * tileH - height) / 2);
let dx = this["x"] * tileW + offsetX;
let dy = this["y"] * tileH + offsetY;
this.indicator.draw(ctx, dx, dy, width);
const tileW = stendhal.ui.gamewindow.targetTileWidth;
const tileH = stendhal.ui.gamewindow.targetTileHeight;
if (this.sprite.width == undefined || this.sprite.height == undefined) {
const image = stendhal.data.sprites.get(this.sprite.filename);
if (image.complete) {
this.sprite.width = image.width < tileW ? tileW : image.width;
this.sprite.height = image.height < tileH ? tileH : image.height;
}
return;
}
const offsetX = Math.floor((this["width"] * tileW - this.sprite.width) / 2);
const offsetY = Math.floor((this["height"] * tileH - this.sprite.height) / 2);
const dx = this["x"] * tileW + offsetX;
const dy = this["y"] * tileH + offsetY;
this.indicator.draw(ctx, dx, dy, this.sprite.width);
}
}
override createSlot(name: string) {
let slot = new CorpseSlot();
var slot = marauroa.util.fromProto(marauroa.rpslotFactory["_default"], {
add: function(object: any) {
marauroa.rpslotFactory["_default"].add.apply(this, arguments);
if (this._objects.length > 0) {
this._parent.autoOpenIfDesired();
}
},
del: function(key: any) {
marauroa.rpslotFactory["_default"].del.apply(this, arguments);
if (this._objects.length == 0) {
this._parent.closeCorpseInventory();
}
}
});
slot._name = name;
slot._objects = [];
slot._parent = this;
return slot;
}
@ -156,18 +168,18 @@ export class Corpse extends PopupInventory {
override getCursor(_x: number, _y: number) {
if (this.isEmpty()) {
return "url(" + Paths.sprites + "/cursor/emptybag.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/emptybag.png) 1 3, auto";
}
// owner
if (!this["corpse_owner"] || (this["corpse_owner"] == marauroa.me["_name"])) {
return "url(" + Paths.sprites + "/cursor/bag.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/bag.png) 1 3, auto";
}
if ((stendhal.data.group.lootmode === "shared") && (stendhal.data.group.members[this["corpse_owner"]])) {
return "url(" + Paths.sprites + "/cursor/bag.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/bag.png) 1 3, auto";
}
return "url(" + Paths.sprites + "/cursor/lockedbag.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/lockedbag.png) 1 3, auto";
}
public override isDraggable(): boolean {
@ -177,9 +189,4 @@ export class Corpse extends PopupInventory {
private isEmpty() {
return !this["content"] || this["content"]._objects.length === 0;
}
override destroy(parent: RPObject|RPZone): void {
this.indicator?.free();
super.destroy(parent);
}
}

View file

@ -1,34 +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 { Corpse } from "./Corpse";
import { RPSlot } from "marauroa";
export class CorpseSlot extends RPSlot {
public _name?: string;
public _parent?: Corpse;
override add(object: any) {
super.add(object);
if (this._objects.length > 0) {
this._parent?.autoOpenIfDesired();
}
}
override del(key: any) {
super.del(key);
if (this._objects.length == 0) {
this._parent?.closeCorpseInventory();
}
}
}

View file

@ -16,10 +16,9 @@ import { EntityOverlayRegistry } from "../data/EntityOverlayRegistry";
import { Color } from "../data/color/Color";
import { SkillEffect } from "../sprite/action/SkillEffect";
import { Paths } from "../data/Paths";
import { marauroa } from "marauroa"
import { stendhal } from "../stendhal";
declare var marauroa: any;
declare var stendhal: any;
export class Creature extends RPEntity {
@ -28,7 +27,7 @@ export class Creature extends RPEntity {
override titleStyle = "#ffc8c8";
override set(key: string, value: any) {
override set(key: string, value: string) {
super.set(key, value);
if (key === "name") {
@ -53,7 +52,7 @@ export class Creature extends RPEntity {
}
override getCursor(_x: number, _y: number) {
return "url(" + Paths.sprites + "/cursor/attack.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/attack.png) 1 3, auto";
}
/**

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,62 +9,53 @@
* *
***************************************************************************/
import { MenuItem } from "../action/MenuItem";
import { RPEntity } from "./RPEntity";
import { MenuItem} from "../action/MenuItem";
import { RenderingContext2D } from "util/Types";
import { Color } from "../data/color/Color";
import { Paths } from "../data/Paths";
import { marauroa } from "marauroa";
import { images } from "sprite/image/ImageManager";
import { ImageSprite } from "sprite/image/ImageSprite";
declare var marauroa: any;
declare var stendhal: any;
export class DomesticAnimal extends RPEntity {
override minimapStyle = Color.DOMESTICANIMAL;
override init() {
this.imageSprite = new ImageSprite(
images.load(Paths.sprites + "/" + this["_rpclass"] + ".png"));
this["largeWeight"] = this["largeWeight"] || 20;
if (this["_rpclass"] == "sheep") {
this["largeWeight"] = 60;
}
}
override drawMain(ctx: RenderingContext2D) {
let image = this.imageSprite?.imageRef?.image
if (!image) {
return;
override drawMain(ctx: CanvasRenderingContext2D) {
if (!this.imagePath && this["_rpclass"]) {
this["largeWeight"] = this["largeWeight"] | 20;
if (this["_rpclass"] == "sheep") {
this["largeWeight"] = 60;
}
this.imagePath = stendhal.paths.sprites + "/" + this["_rpclass"] + ".png";
}
let localX = this["_x"] * 32;
let localY = this["_y"] * 32;
let nFrames = 3;
let nDirections = 4;
let yRow = this["dir"] - 1;
if (this["weight"] >= this["largeWeight"]) {
yRow += 4;
var localX = this["_x"] * 32;
var localY = this["_y"] * 32;
var image = stendhal.data.sprites.get(this.imagePath);
if (image.height) {
var nFrames = 3;
var nDirections = 4;
var yRow = this["dir"] - 1;
if (this["weight"] >= this["largeWeight"]) {
yRow += 4;
}
this["drawHeight"] = image.height / nDirections / 2;
this["drawWidth"] = image.width / nFrames;
var drawX = ((this["width"] * 32) - this["drawWidth"]) / 2;
var frame = 0;
if (this["speed"] > 0) {
// % Works normally with *floats* (just whose bright idea was
// that?), so use floor() as a workaround
frame = Math.floor(Date.now() / 100) % nFrames;
}
var drawY = (this["height"] * 32) - this["drawHeight"];
ctx.drawImage(image, frame * this["drawWidth"], yRow * this["drawHeight"], this["drawWidth"], this["drawHeight"], localX + drawX, localY + drawY, this["drawWidth"], this["drawHeight"]);
}
this["drawHeight"] = image.height / nDirections / 2;
this["drawWidth"] = image.width / nFrames;
let drawX = ((this["width"] * 32) - this["drawWidth"]) / 2;
let frame = 0;
if (this["speed"] > 0) {
// % Works normally with *floats* (just whose bright idea was
// that?), so use floor() as a workaround
frame = Math.floor(Date.now() / 100) % nFrames;
}
let drawY = (this["height"] * 32) - this["drawHeight"];
ctx.drawImage(image, frame * this["drawWidth"], yRow * this["drawHeight"], this["drawWidth"], this["drawHeight"], localX + drawX, localY + drawY, this["drawWidth"], this["drawHeight"]);
}
override getCursor(_x: number, _y: number) {
return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto";
}
override buildActions(list: MenuItem[]) {

View file

@ -9,45 +9,32 @@
* *
***************************************************************************/
import { RenderingContext2D } from "util/Types";
import { MenuItem } from "../action/MenuItem";
import { Portal } from "./Portal";
import { Paths } from "../data/Paths";
import { ImageSprite } from "sprite/image/ImageSprite";
import { images } from "sprite/image/ImageManager";
declare var marauroa: any;
declare var stendhal: any;
export class Door extends Portal {
override zIndex = 5000;
override set(key: string, value: any) {
super.set(key, value);
if (key === "class") {
if (this.imageSprite) {
this.imageSprite?.free();
this.imageSprite = new ImageSprite(
images.load(Paths.sprites + "/doors/" + value + ".png"),
0, 0, 0, 0);
override draw(ctx: CanvasRenderingContext2D) {
let imagePath = stendhal.paths.sprites + "/doors/" + this["class"] + ".png";
let image = stendhal.data.sprites.get(imagePath);
if (image.height) {
let height = image.height / 2;
let x = (this["x"] * 32) - ((image.width - 32) / 2);
let y = (this["y"] * 32) - ((height - 32) / 2);
var offsetY = height;
if (this["open"] === "") {
offsetY = 0;
}
ctx.drawImage(image, 0, offsetY, image.width, height, x, y, image.width, height);
}
}
override draw(ctx: RenderingContext2D) {
let image = this.imageSprite?.imageRef?.image;
if (!image) {
return;
}
let height = image.height / 2;
let x = (this["x"] * 32) - ((image.width - 32) / 2);
let y = (this["y"] * 32) - ((height - 32) / 2);
let offsetY = height;
if (this["open"] === "") {
offsetY = 0;
}
ctx.drawImage(image, 0, offsetY, image.width, height, x, y, image.width, height);
}
override buildActions(list: MenuItem[]) {
list.push({
title: "Look",
@ -64,7 +51,7 @@ export class Door extends Portal {
}
override getCursor(_x: number, _y: number) {
return "url(" + Paths.sprites + "/cursor/portal.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/portal.png) 1 3, auto";
}
}

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,17 +9,12 @@
* *
***************************************************************************/
import { RPObject, RPZone } from "marauroa";
import { RenderingContext2D } from "util/Types";
import { MenuItem } from "../action/MenuItem";
import { Paths } from "../data/Paths";
import { Chat } from "../util/Chat";
import { RPObject } from "./RPObject";
import { htmlImageStore } from "data/HTMLImageStore";
import { marauroa } from "marauroa";
import { HTMLUtil } from "ui/HTMLUtil";
import { ViewPort } from "ui/ViewPort";
import { ImageSprite } from "../sprite/image/ImageSprite";
declare var marauroa: any;
declare var stendhal: any;
/**
* General entity
@ -30,7 +25,6 @@ export class Entity extends RPObject {
minimapShow = false;
minimapStyle = "rgb(200,255,200)";
zIndex = 10000;
imageSprite?: ImageSprite;
override set(key: string, value: any) {
super.set(key, value);
@ -143,10 +137,10 @@ export class Entity extends RPObject {
/**
* is this entity visible to a specific action
*
* @param _filter 0: short left click
* @param filter 0: short left click
* @return true, if the entity is visible, false otherwise
*/
isVisibleToAction(_filter?: boolean) {
isVisibleToAction(filter: boolean) {
return false;
}
@ -185,7 +179,7 @@ export class Entity extends RPObject {
// action replaces "Look" command (e. g. "look closely" on signs)
if (this["action"]) {
list.push({
title: HTMLUtil.niceName(this["action"]),
title: stendhal.ui.html.niceName(this["action"]),
type: this.actionAliasToAction(this["action"])
});
} else {
@ -210,8 +204,8 @@ export class Entity extends RPObject {
this["_x"] = this["x"];
}
draw(ctx: RenderingContext2D) {
if (this.sprite || this.imageSprite) {
draw(ctx: CanvasRenderingContext2D) {
if (this.sprite) {
this.drawSprite(ctx);
}
}
@ -219,27 +213,23 @@ export class Entity extends RPObject {
/**
* draws a standard sprite
*/
drawSprite(ctx: RenderingContext2D) {
drawSprite(ctx: CanvasRenderingContext2D) {
this.drawSpriteAt(ctx, this["x"] * 32, this["y"] * 32);
}
drawSpriteAt(ctx: RenderingContext2D, x: number, y: number) {
if (this.imageSprite) {
this.imageSprite.drawOnto(ctx, x, y, this.getWidth() * 32, this.getHeight() * 32);
} else {
let image = htmlImageStore.get(this.sprite.filename);
if (image.height) {
let offsetX = this.sprite.offsetX || 0;
let offsetY = this.sprite.offsetY || 0;
let width = this.sprite.width || image.width;
let height = this.sprite.height || image.height;
drawSpriteAt(ctx: CanvasRenderingContext2D, x: number, y: number) {
var image = stendhal.data.sprites.get(this.sprite.filename);
if (image.height) {
var offsetX = this.sprite.offsetX || 0;
var offsetY = this.sprite.offsetY || 0;
var width = this.sprite.width || image.width;
var height = this.sprite.height || image.height;
// use entity dimensions to center sprite
x += Math.floor((this.getWidth() * 32 - width) / 2);
y += Math.floor((this.getHeight() * 32 - height) / 2);
ctx.drawImage(image, offsetX, offsetY, width, height, x, y, width, height);
}
// use entity dimensions to center sprite
x += Math.floor((this.getWidth() * 32 - width) / 2);
y += Math.floor((this.getHeight() * 32 - height) / 2);
ctx.drawImage(image, offsetX, offsetY, width, height, x, y, width, height);
}
}
@ -284,7 +274,7 @@ export class Entity extends RPObject {
cursor = this["cursor"];
}
return "url(" + Paths.sprites + "/cursor/" + cursor.toLowerCase().replace("_", "") + ".png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/" + cursor.toLowerCase().replace("_", "") + ".png) 1 3, auto";
}
/**
@ -330,17 +320,16 @@ export class Entity extends RPObject {
* Checks if within vieport area.
*/
public inView(): boolean {
let viewPort = ViewPort.get();
const view_rect: any = {};
view_rect.left = viewPort.offsetX;
view_rect.right = view_rect.left + viewPort.width;
view_rect.top = viewPort.offsetY;
view_rect.bottom = view_rect.top + viewPort.height;
view_rect.left = stendhal.ui.gamewindow.offsetX;
view_rect.right = view_rect.left + stendhal.ui.gamewindow.width;
view_rect.top = stendhal.ui.gamewindow.offsetY;
view_rect.bottom = view_rect.top + stendhal.ui.gamewindow.height;
const pixelX = this["x"] * viewPort.targetTileWidth
+ Math.floor(viewPort.targetTileWidth / 2);
const pixelY = this["y"] * viewPort.targetTileHeight
+ Math.floor(viewPort.targetTileHeight / 2);
const pixelX = this["x"] * stendhal.ui.gamewindow.targetTileWidth
+ Math.floor(stendhal.ui.gamewindow.targetTileWidth / 2);
const pixelY = this["y"] * stendhal.ui.gamewindow.targetTileHeight
+ Math.floor(stendhal.ui.gamewindow.targetTileHeight / 2);
const ent_rect: any = {};
// horizontal orientation is centered
@ -353,8 +342,4 @@ export class Entity extends RPObject {
return ent_rect.right > view_rect.left && ent_rect.left < view_rect.right
&& ent_rect.bottom > view_rect.top && ent_rect.top < view_rect.bottom
}
override destroy(_parent: RPObject|RPZone) {
this.imageSprite?.free();
}
}

View file

@ -1,5 +1,5 @@
/***************************************************************************
* (C) Copyright 2003-2026 - Stendhal *
* (C) Copyright 2003-2023 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
@ -9,7 +9,7 @@
* *
***************************************************************************/
import { marauroa } from "marauroa"
declare var marauroa: any;
import { Blood } from "./Blood";
import { Chest} from "./Chest";
@ -35,46 +35,41 @@ import { User } from "./User";
import { VisibleEntity } from "./VisibleEntity";
import { WalkBlocker } from "./WalkBlocker";
export class EntityRegistry {
public init() {
marauroa.rpobjectFactory.register("area", InvisibleEntity);
marauroa.rpobjectFactory.register("baby_dragon", DomesticAnimal);
marauroa.rpobjectFactory.register("blackboard", Sign);
marauroa.rpobjectFactory.register("blocktarget", InvisibleEntity);
marauroa.rpobjectFactory.register("block", VisibleEntity);
marauroa.rpobjectFactory.register("blood", Blood);
marauroa.rpobjectFactory.register("cat", DomesticAnimal);
marauroa.rpobjectFactory.register("chest", Chest);
marauroa.rpobjectFactory.register("corpse", Corpse);
marauroa.rpobjectFactory.register("creature", Creature);
marauroa.rpobjectFactory.register("_default", UnknownEntity);
marauroa.rpobjectFactory.register("domesticanimal", DomesticAnimal);
marauroa.rpobjectFactory.register("door", Door);
marauroa.rpobjectFactory.register("flyover", InvisibleEntity);
marauroa.rpobjectFactory.register("food", Food);
marauroa.rpobjectFactory.register("game_board", GameBoard);
marauroa.rpobjectFactory.register("gate", Gate);
marauroa.rpobjectFactory.register("growing_entity_spawner", GrowingEntitySpawner);
marauroa.rpobjectFactory.register("house_portal", Portal);
marauroa.rpobjectFactory.register("invisible_entity", InvisibleEntity);
marauroa.rpobjectFactory.register("item", Item);
marauroa.rpobjectFactory.register("looped_sound_source", LoopedSoundSource);
marauroa.rpobjectFactory.register("npc", NPC);
marauroa.rpobjectFactory.register("plant_grower", VisibleEntity);
marauroa.rpobjectFactory.register("player", Player);
marauroa.rpobjectFactory.register("portal", Portal);
marauroa.rpobjectFactory.register("rented_sign", Sign);
marauroa.rpobjectFactory.register("sheep", DomesticAnimal);
marauroa.rpobjectFactory.register("sign", Sign);
marauroa.rpobjectFactory.register("tiled_entity", InvisibleEntity);
marauroa.rpobjectFactory.register("training_dummy", TrainingDummy);
marauroa.rpobjectFactory.register("unknown", UnknownEntity);
marauroa.rpobjectFactory.register("useable_entity", UseableEntity);
marauroa.rpobjectFactory.register("user", User);
marauroa.rpobjectFactory.register("visible_entity", VisibleEntity);
marauroa.rpobjectFactory.register("walkblocker", WalkBlocker);
marauroa.rpobjectFactory.register("wall", InvisibleEntity);
marauroa.rpobjectFactory.register("weather_entity", InvisibleEntity);
}
}
marauroa.rpobjectFactory["area"] = InvisibleEntity;
marauroa.rpobjectFactory["baby_dragon"] = DomesticAnimal;
marauroa.rpobjectFactory["blackboard"] = Sign;
marauroa.rpobjectFactory["blocktarget"] = InvisibleEntity;
marauroa.rpobjectFactory["block"] = VisibleEntity;
marauroa.rpobjectFactory["blood"] = Blood;
marauroa.rpobjectFactory["cat"] = DomesticAnimal;
marauroa.rpobjectFactory["chest"] = Chest;
marauroa.rpobjectFactory["corpse"] = Corpse;
marauroa.rpobjectFactory["creature"] = Creature;
marauroa.rpobjectFactory["_default"] = UnknownEntity;
marauroa.rpobjectFactory["domesticanimal"] = DomesticAnimal;
marauroa.rpobjectFactory["door"] = Door;
marauroa.rpobjectFactory["flyover"] = InvisibleEntity;
marauroa.rpobjectFactory["food"] = Food;
marauroa.rpobjectFactory["game_board"] = GameBoard;
marauroa.rpobjectFactory["gate"] = Gate
marauroa.rpobjectFactory["growing_entity_spawner"] = GrowingEntitySpawner;
marauroa.rpobjectFactory["house_portal"] = Portal;
marauroa.rpobjectFactory["invisible_entity"] = InvisibleEntity;
marauroa.rpobjectFactory["item"] = Item;
marauroa.rpobjectFactory["looped_sound_source"] = LoopedSoundSource;
marauroa.rpobjectFactory["npc"] = NPC;
marauroa.rpobjectFactory["plant_grower"] = VisibleEntity;
marauroa.rpobjectFactory["player"] = Player;
marauroa.rpobjectFactory["portal"] = Portal;
marauroa.rpobjectFactory["rented_sign"] = Sign;
marauroa.rpobjectFactory["sheep"] = DomesticAnimal;
marauroa.rpobjectFactory["sign"] = Sign;
marauroa.rpobjectFactory["tiled_entity"] = InvisibleEntity;
marauroa.rpobjectFactory["training_dummy"] = TrainingDummy;
marauroa.rpobjectFactory["unknown"] = UnknownEntity;
marauroa.rpobjectFactory["useable_entity"] = UseableEntity;
marauroa.rpobjectFactory["user"] = User;
marauroa.rpobjectFactory["visible_entity"] = VisibleEntity;
marauroa.rpobjectFactory["walkblocker"] = WalkBlocker;
marauroa.rpobjectFactory["wall"] = InvisibleEntity;
marauroa.rpobjectFactory["weather_entity"] = InvisibleEntity;

View file

@ -10,33 +10,33 @@
***************************************************************************/
import { Entity } from "./Entity";
import { Paths } from "../data/Paths";
import { marauroa } from "marauroa"
import { ImageSprite } from "sprite/image/ImageSprite";
import { images } from "sprite/image/ImageManager";
declare var marauroa: any;
declare var stendhal: any;
export class Food extends Entity {
override zIndex = 5000;
override init() {
this.imageSprite = new ImageSprite(
images.load(Paths.sprites + "/food.png"),
0, this["amount"] * 32, 32, 32);
}
override set(key: string, value: any) {
super.set(key, value);
if (key === "amount") {
if (this.imageSprite) {
this.imageSprite.offsetY = this["amount"] * 32;
}
this._amount = parseInt(value, 10);
}
// TODO: play sound effect
}
override draw(ctx: CanvasRenderingContext2D) {
var image = stendhal.data.sprites.get(stendhal.paths.sprites + "/food.png");
if (image.height) {
var localX = this["x"] * 32;
var localY = this["y"] * 32;
var offset = this._amount * 32;
ctx.drawImage(image, 0, offset, 32, 32, localX, localY, 32, 32);
}
}
override onclick(_x: number, _y: number) {
var action = {
"type": "look",
@ -50,7 +50,7 @@ export class Food extends Entity {
}
override getCursor(_x: number, _y: number) {
return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto";
}
}

View file

@ -9,24 +9,28 @@
* *
***************************************************************************/
import { Paths } from "../data/Paths";
import { Entity } from "./Entity";
import { stendhal } from "../stendhal";
import { ImageSprite } from "sprite/image/ImageSprite";
import { images } from "sprite/image/ImageManager";
declare var stendhal: any;
export class GameBoard extends Entity {
override minimapShow = false;
override zIndex = 100;
constructor() {
super();
this.sprite = {
height: 32 * 3,
width: 32 * 3
};
}
override set(key: string, value: any) {
super.set(key, value);
if (key === "class") {
let filename = Paths.sprites + "/gameboard/" + this["class"] + ".png";
this.imageSprite?.free();
this.imageSprite = new ImageSprite(images.load(filename), 0, 0, 3*32, 3*32);
this.sprite.filename = stendhal.paths.sprites + "/gameboard/"
+ this["class"] + ".png";
}
}
@ -35,7 +39,7 @@ export class GameBoard extends Entity {
}
override getCursor(_x: number, _y: number) {
return "url(" + Paths.sprites + "/cursor/walk.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/walk.png) 1 3, auto";
}
}

View file

@ -9,45 +9,30 @@
* *
***************************************************************************/
import { RenderingContext2D } from "util/Types";
import { MenuItem } from "../action/MenuItem";
import { Entity } from "./Entity";
import { Paths } from "../data/Paths";
import { marauroa } from "marauroa"
import { images } from "sprite/image/ImageManager";
import { ImageSprite } from "sprite/image/ImageSprite";
declare var marauroa: any;
declare var stendhal: any;
export class Gate extends Entity {
override zIndex = 5000;
private locked = false;
override init() {
this.imageSprite?.free();
this.imageSprite = new ImageSprite(
images.load(Paths.sprites + "/doors/" + this["image"] + "_" + this["orientation"] + ".png"),
0, 0, 0, 0);
}
override set(key: string, value: any) {
super.set(key, value);
if (key === "resistance") {
this.locked = parseInt(value, 10) !== 0;
this["locked"] = parseInt(value, 10) !== 0;
} else if (key === "image" || key === "orientation") {
if (this.imageSprite) {
this.imageSprite?.free();
this.imageSprite = new ImageSprite(
images.load(Paths.sprites + "/doors/" + this["image"] + "_" + this["orientation"] + ".png"),
0, 0, 0, 0);
}
// Force re-evaluation of the sprite
delete this["_image"];
}
}
override buildActions(list: MenuItem[]) {
var id = this["id"];
list.push({
title: (this.locked) ? "Open" : "Close",
title: (this["locked"]) ? "Open" : "Close",
action: function(_entity: Entity) {
var action = {
"type": "use",
@ -59,18 +44,20 @@ export class Gate extends Entity {
});
}
override draw(ctx: RenderingContext2D) {
let image = this.imageSprite?.imageRef?.image;
if (!image) {
return;
override draw(ctx: CanvasRenderingContext2D) {
if (this._image == undefined) {
var filename = stendhal.paths.sprites + "/doors/" + this["image"] + "_" + this["orientation"] + ".png";
this._image = stendhal.data.sprites.get(filename);
}
if (this._image.height) {
var xOffset = -32 * Math.floor(this._image.width / 32 / 2);
var height = this._image.height / 2;
var yOffset = -32 * Math.floor(height / 32 / 2);
var localX = this["_x"] * 32 + xOffset;
var localY = this["_y"] * 32 + yOffset;
var yStart = (this["locked"]) ? height : 0;
ctx.drawImage(this._image, 0, yStart, this._image.width, height, localX, localY, this._image.width, height);
}
let xOffset = -32 * Math.floor(image.width / 32 / 2);
let height = image.height / 2;
let yOffset = -32 * Math.floor(height / 32 / 2);
let localX = this["_x"] * 32 + xOffset;
let localY = this["_y"] * 32 + yOffset;
let yStart = (this.locked) ? height : 0;
ctx.drawImage(image, 0, yStart, image.width, height, localX, localY, image.width, height);
}
override isVisibleToAction(_filter: boolean) {
@ -87,7 +74,7 @@ export class Gate extends Entity {
}
override getCursor(_x: number, _y: number) {
return "url(" + Paths.sprites + "/cursor/activity.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/activity.png) 1 3, auto";
}
}

View file

@ -9,12 +9,8 @@
* *
***************************************************************************/
import { Paths } from "../data/Paths";
import { marauroa } from "marauroa"
import { stendhal } from "../stendhal";
import { TileMap } from "data/TileMap";
import { ViewPort } from "ui/ViewPort";
declare var marauroa: any;
declare var stendhal: any;
/**
@ -35,42 +31,39 @@ export class Ground {
* @return Direction of the zone to change to, <code>null</code> if no zone change should happen
*/
private calculateZoneChangeDirection(x: number, y: number): string|null {
let map = TileMap.get();
if (x < 15) {
return "4"; // LEFT
}
if (x > map.zoneSizeX * 32 - 15) {
if (x > stendhal.data.map.zoneSizeX * 32 - 15) {
return "2"; // RIGHT
}
if (y < 15) {
return "1"; // UP
}
if (y > map.zoneSizeY * 32 - 15) {
if (y > stendhal.data.map.zoneSizeY * 32 - 15) {
return "3"; // DOWN
}
return null;
}
getCursor(x: number, y: number): string {
let map = TileMap.get();
if ((x < 15) || (y < 15) || (x > map.zoneSizeX * 32 - 15) || (y > map.zoneSizeY * 32 - 15)) {
return "url(" + Paths.sprites + "/cursor/walkborder.png) 1 3, auto"
if ((x < 15) || (y < 15) || (x > stendhal.data.map.zoneSizeX * 32 - 15) || (y > stendhal.data.map.zoneSizeY * 32 - 15)) {
return "url(" + stendhal.paths.sprites + "/cursor/walkborder.png) 1 3, auto"
}
var worldX = Math.floor(x / 32);
var worldY = Math.floor(y / 32);
if (map.collision(worldX, worldY)) {
return "url(" + Paths.sprites + "/cursor/stop.png) 1 3, auto";
if (stendhal.data.map.collision(worldX, worldY)) {
return "url(" + stendhal.paths.sprites + "/cursor/stop.png) 1 3, auto";
}
return "url(" + Paths.sprites + "/cursor/walk.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/walk.png) 1 3, auto";
}
onclick(x: number, y: number, dblclick: boolean) {
if (!stendhal.config.getBoolean("pathfinding")) {
return;
}
let viewPort = ViewPort.get();
var gameX = x + viewPort.offsetX;
var gameY = y + viewPort.offsetY;
var gameX = x + stendhal.ui.gamewindow.offsetX;
var gameY = y + stendhal.ui.gamewindow.offsetY;
var action = {
"type": "moveto",
"x": "" + Math.floor(gameX / 32),

View file

@ -9,33 +9,16 @@
* *
***************************************************************************/
import { RenderingContext2D } from "util/Types";
import { MenuItem } from "../action/MenuItem";
import { Entity } from "./Entity";
import { Paths } from "../data/Paths";
import { singletons } from "../SingletonRepo";
import { marauroa } from "marauroa"
import { ImageSprite } from "sprite/image/ImageSprite";
import { images } from "sprite/image/ImageManager";
declare var marauroa: any;
declare var stendhal: any;
export class GrowingEntitySpawner extends Entity {
override zIndex = 3000;
override set(key: string, value: any) {
super.set(key, value);
if (key === "class") {
this.imageSprite?.free();
let className = value.replace(" ", "_");
this.imageSprite = new ImageSprite(
images.load(Paths.sprites + "/" + className + ".png"),
0, 0, 0, 0);
}
}
/**
* is this entity visible to a specific action
*
@ -65,23 +48,34 @@ export class GrowingEntitySpawner extends Entity {
marauroa.clientFramework.sendAction(action);
}
override draw(ctx: RenderingContext2D) {
let localX = this["x"] * 32;
let localY = this["y"] * 32;
let image = this.imageSprite?.imageRef?.image;
if (!image) {
return;
/**
* draw RPEntities
*/
override draw(ctx: CanvasRenderingContext2D) {
var localX = this["x"] * 32;
var localY = this["y"] * 32;
// FIXME:
// temporary fix, problem lies higher up
// appears to only affect button_mushroom_grower
// could be issue in marauroa
let class_name = this["class"];
if (class_name.includes(" ")) {
class_name = class_name.replace(" ", "_");
}
let count = parseInt(this["max_ripeness"], 10) + 1;
let drawHeight = image.height / count;
let yRow = this["ripeness"];
ctx.drawImage(image, 0, yRow * drawHeight, image.width, drawHeight,
var image = stendhal.data.sprites.get(stendhal.paths.sprites + "/" + class_name + ".png");
if (image.height) { // image.complete is true on missing image files
var count = parseInt(this["max_ripeness"], 10) + 1;
var drawHeight = image.height / count;
var yRow = this["ripeness"];
ctx.drawImage(image, 0, yRow * drawHeight, image.width, drawHeight,
localX, localY - drawHeight + 32, image.width, drawHeight);
}
}
override getCursor(_x: number, _y: number) {
return "url(" + Paths.sprites + "/cursor/harvest.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/harvest.png) 1 3, auto";
}
}

View file

@ -9,9 +9,10 @@
* *
***************************************************************************/
import { Paths } from "../data/Paths";
import { Entity } from "./Entity";
declare var stendhal: any;
export class InvisibleEntity extends Entity {
override isVisibleToAction(_filter: boolean) {
@ -19,7 +20,7 @@ export class InvisibleEntity extends Entity {
}
override getCursor(_x: number, _y: number) {
return "url(" + Paths.sprites + "/cursor/walk.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/walk.png) 1 3, auto";
}
}

View file

@ -1,5 +1,5 @@
/***************************************************************************
* (C) Copyright 2003-2026 - Stendhal *
* (C) Copyright 2003-2023 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
@ -9,16 +9,14 @@
* *
***************************************************************************/
import { RenderingContext2D } from "util/Types";
import { MenuItem } from "../action/MenuItem";
import { Paths } from "../data/Paths";
import { TextSprite } from "../sprite/TextSprite";
import { Entity } from "./Entity";
import { ItemMap } from "./ItemMap";
import { MenuItem } from "../action/MenuItem";
import { Entity } from "./Entity";
import { TextSprite } from "../sprite/TextSprite";
declare var marauroa: any;
declare var stendhal: any;
import { marauroa } from "marauroa";
import { images } from "sprite/image/ImageManager";
import { ImageSprite } from "sprite/image/ImageSprite";
export class Item extends Entity {
@ -30,10 +28,15 @@ export class Item extends Entity {
// animation
private frameTimeStamp = 0;
private animated: boolean|null = null;
private xFrames: number|null = null;
private yFrames: number|null = null;
constructor() {
super();
this.sprite = {
height: 32,
width: 32
};
this.quantityTextSprite = new TextSprite("", "white", "10px sans-serif");
}
@ -69,50 +72,36 @@ export class Item extends Entity {
override set(key: string, value: any) {
super.set(key, value);
if (key === "class" || key === "subclass") {
if (this["class"] && this["subclass"]) {
this.imageSprite?.free();
this.imageSprite = new ImageSprite(
images.load(Paths.sprites + "/items/" + this["class"] + "/" + this["subclass"] + ".png"),
0, (this["state"] || 0) * 32, 32, 32);
}
} else if (key === "state") {
if (this.imageSprite) {
this.imageSprite.offsetY = (this["state"] || 0) * 32
}
} else if (key === "quantity") {
this.sprite.filename = stendhal.paths.sprites + "/items/"
+ this["class"] + "/" + this["subclass"] + ".png";
}
if (key === "quantity") {
this.quantityTextSprite = new TextSprite(this.formatQuantity(), "white", "10px sans-serif");
}
}
override draw(ctx: RenderingContext2D) {
override draw(ctx: CanvasRenderingContext2D) {
this.sprite.offsetY = (this["state"] || 0) * 32
this.stepAnimation();
this.drawAt(ctx, this["x"] * 32, this["y"] * 32);
}
drawAt(ctx: RenderingContext2D, x: number, y: number) {
let image = this.imageSprite?.imageRef?.image;
if (!image) {
return;
drawAt(ctx: CanvasRenderingContext2D, x: number, y: number) {
if (this.sprite) {
this.drawSpriteAt(ctx, x, y);
let textMetrics = this.quantityTextSprite.getTextMetrics(ctx);
this.quantityTextSprite.draw(ctx, x + (32 - textMetrics.width), y + 6);
}
this.drawSpriteAt(ctx, x, y);
let textMetrics = this.quantityTextSprite.getTextMetrics(ctx);
if (!textMetrics) {
throw new Error("textMetrics is undefined");
}
this.quantityTextSprite.draw(ctx, x + (32 - textMetrics.width), y + 6);
}
public stepAnimation() {
let image = this.imageSprite?.imageRef?.image;
if (!image) {
return;
}
const currentTimeStamp = +new Date();
// if (this.frameTimeStamp == 0) {
// this.frameTimeStamp = currentTimeStamp;
// this.imageSprite?.offsetX = 0;
// }
if (currentTimeStamp - this.frameTimeStamp >= 100) {
if (this.frameTimeStamp == 0) {
this.frameTimeStamp = currentTimeStamp;
this.sprite.offsetX = 0;
this.sprite.offsetY = 0;
} else if (currentTimeStamp - this.frameTimeStamp >= 100) {
// FIXME: need proper FPS limit
this.setXFrameIndex(this.getXFrameIndex() + 1);
this.frameTimeStamp = currentTimeStamp;
@ -139,7 +128,7 @@ export class Item extends Entity {
} else {
cursor = ItemMap.getCursor(this["class"], this["name"]);
}
return "url(" + Paths.sprites + "/cursor/" + cursor + ".png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/" + cursor + ".png) 1 3, auto";
}
public getToolTip(): string {
@ -153,31 +142,51 @@ export class Item extends Entity {
}
public isAnimated(): boolean {
if (this.animated === null) {
let image = this.imageSprite?.imageRef?.image
if (image) {
this.animated = (image.width / 32) > 1;
}
if (!stendhal.data.sprites.get(this.sprite.filename).height) {
return false;
}
return this.animated || false;
if (this.animated == null) {
// store animation state
this.animated = (stendhal.data.sprites.get(this.sprite.filename).width / 32) > 1;
}
return this.animated;
}
private setXFrameIndex(idx: number) {
let image = this.imageSprite?.imageRef.image
if (!image) {
return;
if (this.xFrames == null) {
const img = stendhal.data.sprites.get(this.sprite.filename);
this.xFrames = img.width / 32;
}
let frames = image.width / 32;
if (idx >= frames) {
if (idx >= this.xFrames) {
// restart
idx = 0;
}
this.imageSprite!.offsetX = idx * 32;
this.sprite.offsetX = idx * 32;
}
private setYFrameIndex(idx: number) {
if (this.yFrames == null) {
const img = stendhal.data.sprites.get(this.sprite.filename);
this.yFrames = img.height / 32;
}
if (idx >= this.yFrames) {
// restart
idx = 0;
}
this.sprite.offsetY = idx * 32;
}
public getXFrameIndex(): number {
return (this.imageSprite?.offsetX || 0) / 32;
return (this.sprite.offsetX || 0) / 32;
}
public getYFrameIndex(): number {
return (this.sprite.offsetY || 0) / 32;
}
public override isDraggable(): boolean {

View file

@ -15,7 +15,7 @@ import { MenuItem } from "../action/MenuItem";
const config = singletons.getConfigManager();
import { marauroa } from "marauroa"
declare var marauroa: any;
const defaultUse = {
title: "Use",

View file

@ -14,8 +14,8 @@ import { RPEntity } from "./RPEntity";
import { EntityOverlayRegistry } from "../data/EntityOverlayRegistry";
import { Color } from "../data/color/Color";
import { RenderingContext2D } from "util/Types";
import { Paths } from "../data/Paths";
declare var stendhal: any;
export class NPC extends RPEntity {
override minimapStyle = Color.NPC;
@ -28,7 +28,7 @@ export class NPC extends RPEntity {
this["base_hp"] = 100;
}
override set(key: string, value: any) {
override set(key: string, value: string) {
super.set(key, value);
if (key === "name") {
@ -42,7 +42,7 @@ export class NPC extends RPEntity {
}
}
override drawTop(ctx: RenderingContext2D) {
override drawTop(ctx: CanvasRenderingContext2D) {
var localX = this["_x"] * 32;
var localY = this["_y"] * 32;
if (typeof(this["no_hpbar"]) == "undefined") {
@ -54,7 +54,7 @@ export class NPC extends RPEntity {
}
override getCursor(_x: number, _y: number) {
return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto";
}
}

View file

@ -9,8 +9,8 @@
* *
***************************************************************************/
import { marauroa } from "marauroa"
import { stendhal } from "../stendhal";
declare var marauroa: any;
declare var stendhal: any;
import { RPEntity } from "./RPEntity";
@ -22,8 +22,6 @@ import { UIComponentEnum } from "../ui/UIComponentEnum";
import { GroupPanelComponent } from "../ui/component/GroupPanelComponent";
import { Color } from "../data/color/Color";
import { RenderingContext2D } from "util/Types";
import { Paths } from "../data/Paths";
export class Player extends RPEntity {
@ -171,7 +169,7 @@ export class Player extends RPEntity {
}
}
override draw(ctx: RenderingContext2D) {
override draw(ctx: CanvasRenderingContext2D) {
if (this.isIgnored()) {
return;
}
@ -238,8 +236,8 @@ export class Player extends RPEntity {
override getCursor(_x: number, _y: number) {
if (this.isVisibleToAction(false)) {
return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto";
}
return "url(" + Paths.sprites + "/cursor/walk.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/walk.png) 1 3, auto";
}
}

View file

@ -9,10 +9,10 @@
* *
***************************************************************************/
import { marauroa, RPObject, RPZone } from "marauroa";
import { RenderingContext2D } from "util/Types";
import { Entity } from "./Entity";
import { RPObject } from "./RPObject";
declare var marauroa: any;
export class PopupInventory extends Entity {
@ -20,7 +20,7 @@ export class PopupInventory extends Entity {
protected maxDistToView = 4;
override draw(ctx: RenderingContext2D) {
override draw(ctx: CanvasRenderingContext2D) {
super.draw(ctx);
if (this.inventory && this.inventory.isOpen() && !this.canViewContents()) {
// player has moved too far away
@ -32,9 +32,9 @@ export class PopupInventory extends Entity {
// inheriting classes can override
}
override destroy(parent: RPObject|RPZone) {
override destroy(_parent: RPObject) {
this.closeInventoryWindow();
super.destroy(parent);
super.destroy(_parent);
}
/**

View file

@ -13,10 +13,9 @@ import { Entity } from "./Entity";
import { MenuItem } from "../action/MenuItem";
import { Color } from "../data/color/Color";
import { Paths } from "../data/Paths";
import { marauroa } from "marauroa"
import { TileMap } from "data/TileMap";
declare var marauroa: any;
declare var stendhal: any;
export class Portal extends Entity {
@ -60,8 +59,7 @@ export class Portal extends Entity {
*/
override getDefaultAction() {
// don't try to walk to if on collision tile
let map = TileMap.get();
if (map.collision(this["x"], this["y"])) {
if (stendhal.data.map.collision(this["x"], this["y"])) {
return {
"type": "use",
"target": "#" + this["id"],
@ -77,7 +75,7 @@ export class Portal extends Entity {
}
override getCursor(_x: number, _y: number) {
return "url(" + Paths.sprites + "/cursor/portal.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/portal.png) 1 3, auto";
}
}

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,35 +9,30 @@
* *
***************************************************************************/
import { marauroa } from "marauroa";
import { stendhal } from "../stendhal";
declare var marauroa: any;
declare var stendhal: any;
import { singletons } from "../SingletonRepo";
import { MenuItem } from "../action/MenuItem";
import { ActiveEntity } from "./ActiveEntity";
import { Entity } from "./Entity";
import { singletons } from "../SingletonRepo";
import { MenuItem } from "../action/MenuItem";
import { Color } from "../data/color/Color";
import { Chat } from "../util/Chat";
import { Nature } from "../util/Nature";
import { EmojiSprite } from "../sprite/EmojiSprite";
import { Floater } from "../sprite/Floater";
import { EmojiSprite } from "../sprite/EmojiSprite";
import { OverlaySpriteImpl } from "../sprite/OverlaySpriteImpl";
import { SpeechBubble } from "../sprite/SpeechBubble";
import { TextSprite } from "../sprite/TextSprite";
import { BarehandAttackSprite } from "../sprite/action/BarehandAttackSprite";
//import { MeleeAttackSprite } from "../sprite/action/MeleeAttackSprite";
import { RangedAttackSprite } from "../sprite/action/RangedAttackSprite";
import { htmlImageStore } from "data/HTMLImageStore";
import { ImageWithDimensions } from "data/ImageWithDimensions";
import { OutfitStore } from "data/OutfitStore";
import { ViewPort } from "ui/ViewPort";
import { RenderingContext2D } from "util/Types";
import { Paths } from "../data/Paths";
import { statusIconDrawer } from "sprite/indicator/StatusIconDrawer";
var HEALTH_BAR_HEIGHT = 6;
@ -59,7 +54,7 @@ export class RPEntity extends ActiveEntity {
// for adjusting entity hp bar & title vertical position
protected titleDrawYOffset: number = 0;
// canvas for merging outfit layers to be drawn
private octx?: RenderingContext2D;
private octx?: CanvasRenderingContext2D;
private attackers: {[key: string]: any} = { size: 0 };
@ -209,11 +204,11 @@ export class RPEntity extends ActiveEntity {
* Text to display.
*/
addSpeechBubble(text: string) {
ViewPort.get().addTextSprite(new SpeechBubble(text, this));
stendhal.ui.gamewindow.addTextSprite(new SpeechBubble(text, this));
}
addEmoji(emoji: HTMLImageElement) {
ViewPort.get().addEmojiSprite(new EmojiSprite(emoji, this));
stendhal.ui.gamewindow.addEmojiSprite(new EmojiSprite(emoji, this));
}
/**
@ -223,8 +218,8 @@ export class RPEntity extends ActiveEntity {
return typeof(this["no_shadow"]) === "undefined";
}
drawMultipartOutfit(ctx: RenderingContext2D) {
const store = OutfitStore.get();
drawMultipartOutfit(ctx: CanvasRenderingContext2D) {
const store = singletons.getOutfitStore();
// layers in draw order
var layers = store.getLayerNames();
@ -249,7 +244,7 @@ export class RPEntity extends ActiveEntity {
if (stendhal.config.getBoolean("effect.shadows") && this.castsShadow()) {
// dressed entities should use 48x64 sprites
// FIXME: this will not display correctly for horse outfit
const shadow = htmlImageStore.getShadow("48x64");
const shadow = stendhal.data.sprites.getShadow("48x64");
if (shadow && shadow.complete && shadow.height) {
// draw shadow below other layers
@ -260,21 +255,23 @@ export class RPEntity extends ActiveEntity {
if (this.octx) {
this.octx.clearRect(0, 0, this.octx.canvas.width, this.octx.canvas.height);
}
if (store.detailHasRearLayer(outfit["detail"])) {
if (stendhal.data.outfit.detailHasRearLayer(outfit["detail"])) {
layers.splice(0, 0, "detail-rear");
outfit["detail-rear"] = outfit["detail"];
}
for (const layer of layers) {
// hair is not drawn under certain hats/helmets
if (layer == "hair" && !store.drawHair(outfit["hat"])) {
if (layer == "hair" && !stendhal.data.outfit.drawHair(outfit["hat"])) {
continue;
}
const lsprite = this.getOutfitPart(layer, outfit[layer], outfit["body"]);
if (lsprite && lsprite.complete && lsprite.height) {
if (!this.octx) {
let ocanvas = new OffscreenCanvas(lsprite.width, lsprite.height);
let ocanvas = document.createElement("canvas");
this.octx = ocanvas.getContext("2d")!;
ocanvas.width = lsprite.width;
ocanvas.height = lsprite.height;
}
this.octx!.drawImage(lsprite, 0, 0);
}
@ -305,28 +302,27 @@ export class RPEntity extends ActiveEntity {
n = "0" + index;
}
let store = OutfitStore.get();
if (part === "body" && index < 3 && stendhal.config.getBoolean("effect.no-nude")) {
n += "-nonude";
} else if (part === "dress" && store.drawBustyDress(index, body)) {
} else if (part === "dress" && stendhal.data.outfit.drawBustyDress(index, body)) {
n += "b";
} else if (part.endsWith("-rear")) {
n += "-rear";
part = part.replace(/-rear$/, "");
}
const filename = Paths.sprites + "/outfit/" + part + "/" + n + ".png";
const filename = stendhal.paths.sprites + "/outfit/" + part + "/" + n + ".png";
const colors = this["outfit_colors"];
let colorname;
if (store.isSkinLayer(part)) {
if (stendhal.data.outfit.isSkinLayer(part)) {
colorname = "skin";
} else {
colorname = part;
}
if (typeof(colors) !== "undefined" && (typeof(colors[colorname]) !== "undefined")) {
return htmlImageStore.getFiltered(filename, "trueColor", colors[colorname]);
return stendhal.data.sprites.getFiltered(filename, "trueColor", colors[colorname]);
} else {
return htmlImageStore.get(filename);
return stendhal.data.sprites.get(filename);
}
}
@ -335,7 +331,7 @@ export class RPEntity extends ActiveEntity {
* extends past top edge.
*/
public setStatusBarOffset() {
const screenOffsetY = ViewPort.get().offsetY;
const screenOffsetY = stendhal.ui.gamewindow.offsetY;
const entityBottom = (this["_y"] * 32) + (this["height"] * 32);
// FIXME: how to get text height dynamically?
const entityTop = entityBottom - this["drawHeight"]
@ -351,70 +347,138 @@ export class RPEntity extends ActiveEntity {
/**
* draw RPEntities
*/
override draw(ctx: RenderingContext2D) {
override draw(ctx: CanvasRenderingContext2D) {
if (typeof(this["ghostmode"]) != "undefined" && marauroa.me && !marauroa.me.isAdmin()) {
return;
}
this.drawCombat(ctx);
this.drawMain(ctx);
this.drawAttack(ctx);
statusIconDrawer.drawStatusIcons(this, ctx);
this.drawStatusIcons(ctx);
}
drawMain(ctx: RenderingContext2D) {
drawMain(ctx: CanvasRenderingContext2D) {
if (typeof(this["outfit"]) != "undefined" || typeof(this["outfit_ext"]) != "undefined") {
this.drawMultipartOutfit(ctx);
} else {
this.drawSingleOutfit(ctx);
let filename = stendhal.paths.sprites + "/" + this.spritePath + "/" + this["class"];
if (typeof(this["subclass"]) != "undefined") {
filename = filename + "/" + this["subclass"];
}
// check for safe image
if (!stendhal.config.getBoolean("effect.blood") && stendhal.data.sprites.hasSafeImage(filename)) {
filename = filename + "-safe.png";
} else {
filename = filename + ".png";
}
let image = stendhal.data.sprites.get(filename);
if (stendhal.config.getBoolean("effect.shadows") && this.castsShadow()) {
// check for configured shadow style
let shadow_style = this["shadow_style"];
if (typeof(shadow_style) === "undefined") {
// default to sprite dimensions
shadow_style = (image.width / 3) + "x" + (image.height / 4);
}
const shadow = stendhal.data.sprites.getShadow(shadow_style);
// draw shadow first
if (typeof(shadow) !== "undefined") {
this.drawSpriteImage(ctx, shadow);
}
}
this.drawSpriteImage(ctx, image);
this.drawOverlayAnimation(ctx);
}
}
drawSingleOutfit(ctx: RenderingContext2D) {
let filename = Paths.sprites + "/" + this.spritePath + "/" + this["class"];
if (typeof(this["subclass"]) != "undefined") {
filename = filename + "/" + this["subclass"];
drawStatusIcons(ctx: CanvasRenderingContext2D) {
function _drawAnimatedIcon(icon: CanvasImageSource, delay: number, nFrames: number, xdim: number, ydim: number, x: number, y: number) {
var frame = Math.floor(Date.now() / delay) % nFrames;
ctx.drawImage(icon, frame * xdim, 0, xdim, ydim, x, y, xdim, ydim);
}
function drawAnimatedIcon(iconPath: string, delay: number, x: number, y: number, fWidth?: number) {
var icon = stendhal.data.sprites.get(iconPath);
var dimH = icon.height;
var dimW = typeof(fWidth) !== "undefined" ? fWidth : dimH;
var nFrames = icon.width / dimW;
_drawAnimatedIcon(icon, delay, nFrames, dimW, dimH, x, y);
}
function drawAnimatedIconWithFrames(iconPath: string, nFrames: number, delay: number, x: number, y: number) {
var icon = stendhal.data.sprites.get(iconPath);
var ydim = icon.height;
var xdim = icon.width / nFrames;
_drawAnimatedIcon(icon, delay, nFrames, xdim, ydim, x, y);
}
// check for safe image
if (!stendhal.config.getBoolean("effect.blood") && htmlImageStore.hasSafeImage(filename)) {
filename = filename + "-safe.png";
} else {
filename = filename + ".png";
var x = this["_x"] * 32 - 10;
var y = (this["_y"] + 1) * 32;
if (this.hasOwnProperty("choking")) {
ctx.drawImage(stendhal.data.sprites.get(stendhal.paths.sprites + "/ideas/choking.png"), x, y - 10);
} else if (this.hasOwnProperty("eating")) {
ctx.drawImage(stendhal.data.sprites.get(stendhal.paths.sprites + "/ideas/eat.png"), x, y - 10);
}
let image = htmlImageStore.get(filename);
if (stendhal.config.getBoolean("effect.shadows") && this.castsShadow()) {
// check for configured shadow style
let shadow_style = this["shadow_style"];
if (typeof(shadow_style) === "undefined") {
// default to sprite dimensions
shadow_style = (image.width / 3) + "x" + (image.height / 4);
}
const shadow = htmlImageStore.getShadow(shadow_style);
// draw shadow first
if (typeof(shadow) !== "undefined") {
this.drawSpriteImage(ctx, shadow);
// NPC and pet idea icons
if (this.hasOwnProperty("idea")) {
const idea = stendhal.paths.sprites + "/ideas/" + this["idea"] + ".png";
const ani = stendhal.data.sprites.animations.idea[this["idea"]];
if (ani) {
drawAnimatedIcon(idea, ani.delay, x + ani.offsetX * this["width"],
y - this["drawHeight"] + ani.offsetY);
} else {
ctx.drawImage(stendhal.data.sprites.get(idea), x + 32 * this["width"],
y - this["drawHeight"]);
}
}
this.drawSpriteImage(ctx, image);
this.drawOverlayAnimation(ctx);
if (this.hasOwnProperty("away")) {
drawAnimatedIcon(stendhal.paths.sprites + "/ideas/away.png", 1500, x + 32 * this["width"], y - this["drawHeight"]);
}
if (this.hasOwnProperty("grumpy")) {
drawAnimatedIcon(stendhal.paths.sprites + "/ideas/grumpy.png", 1000, x + 5, y - this["drawHeight"]);
}
if (this.hasOwnProperty("last_player_kill_time")) {
drawAnimatedIconWithFrames(stendhal.paths.sprites + "/ideas/pk.png", 12, 300, x, y - this["drawHeight"]);
}
// status affects
if (this.hasOwnProperty("poisoned")) {
drawAnimatedIcon(stendhal.paths.sprites + "/status/poison.png", 100, x + 32 * this["width"] - 10, y - this["drawHeight"]);
}
if (this.hasOwnProperty("status_confuse")) {
drawAnimatedIcon(stendhal.paths.sprites + "/status/confuse.png", 200, x + 32 * this["width"] - 14, y - this["drawHeight"] + 16);
}
if (this.hasOwnProperty("status_shock")) {
drawAnimatedIcon(stendhal.paths.sprites + "/status/shock.png", 200, x + 32 * this["width"] - 25, y - 32, 38);
}
// NPC job icons
let nextX = x;
if (this.hasOwnProperty("job_healer")) {
ctx.drawImage(stendhal.data.sprites.get(stendhal.paths.sprites + "/status/healer.png"), nextX, y - 10);
nextX += 12;
}
if (this.hasOwnProperty("job_merchant")) {
ctx.drawImage(stendhal.data.sprites.get(stendhal.paths.sprites + "/status/merchant.png"), nextX, y - 10);
nextX += 12;
}
if (this.hasOwnProperty("job_producer")) {
ctx.drawImage(stendhal.data.sprites.get(stendhal.paths.sprites + "/status/producer.png"), nextX, y - 16);
nextX += 16;
}
}
/**
* Draw colored ellipses (or rectangles on browsers that do not support
* ellipses) when the entity is being attacked, or is attacking the user.
*/
drawCombat(ctx: RenderingContext2D) {
drawCombat(ctx: CanvasRenderingContext2D) {
if (this.attackers.size > 0) {
ctx.lineWidth = 1;
/*
* As of 2015-9-15 RenderingContext2D.ellipse() is not
* As of 2015-9-15 CanvasRenderingContext2D.ellipse() is not
* supported in most browsers. Fall back to rectangles on these.
* Also on Chrome 45.0.2454.85 ellipse() does not seem to support
* the begin angle parameter correctly, nor does the stroke
@ -475,7 +539,7 @@ export class RPEntity extends ActiveEntity {
* the coordinates where to start floating. The method should return true
* when the floater should be removed.
*/
drawFloaters(ctx: RenderingContext2D) {
drawFloaters(ctx: CanvasRenderingContext2D) {
var centerX = (this["_x"] + this["width"] / 2) * 32;
var topY = (this["_y"] + 1) * 32 - this["drawHeight"];
// Grab an unchanging copy
@ -491,10 +555,10 @@ export class RPEntity extends ActiveEntity {
}
/**
* @param {RenderingContext2D} ctx
* @param {CanvasRenderingContext2D} ctx
* @param {Image} image
*/
drawSpriteImage(ctx: RenderingContext2D, image: CanvasImageSource & ImageWithDimensions) {
drawSpriteImage(ctx: CanvasRenderingContext2D, image: CanvasImageSource & ImageWithDimensions) {
var localX = this["_x"] * 32;
var localY = this["_y"] * 32;
if (image.height) { // image.complete is true on missing image files
@ -546,7 +610,7 @@ export class RPEntity extends ActiveEntity {
}
}
drawTop(ctx: RenderingContext2D) {
drawTop(ctx: CanvasRenderingContext2D) {
var localX = this["_x"] * 32;
var localY = this["_y"] * 32;
this.drawFloaters(ctx);
@ -554,7 +618,7 @@ export class RPEntity extends ActiveEntity {
this.drawTitle(ctx, localX, localY + this.statusBarYOffset);
}
drawHealthBar(ctx: RenderingContext2D, x: number, y: number) {
drawHealthBar(ctx: CanvasRenderingContext2D, x: number, y: number) {
var drawX = x + ((this["width"] * 32) - this["drawWidth"]) / 2;
var drawY = y + (this["height"] * 32) - this["drawHeight"]
- HEALTH_BAR_HEIGHT + this.titleDrawYOffset;
@ -591,48 +655,42 @@ export class RPEntity extends ActiveEntity {
}
}
drawTitle(ctx: RenderingContext2D, x: number, y: number) {
drawTitle(ctx: CanvasRenderingContext2D, x: number, y: number) {
if (this.titleTextSprite) {
let textMetrics = this.titleTextSprite.getTextMetrics(ctx);
if (!textMetrics) {
throw new Error("textMetrics is undefined");
}
var drawY = y + (this["height"] * 32) - this["drawHeight"]
- HEALTH_BAR_HEIGHT + this.titleDrawYOffset;
this.titleTextSprite.draw(ctx, x + (this["width"] * 32 - textMetrics.width) / 2, drawY - 5 - HEALTH_BAR_HEIGHT);
}
}
drawAttack(ctx: RenderingContext2D) {
drawAttack(ctx: CanvasRenderingContext2D) {
if (!this.attackSprite) {
return;
}
if (this.attackSprite.expired()) {
// sprite expired & should be removed
// NOTE: if this is done after `this.attackSprite.draw` animation doesn't look right
this.attackSprite.free();
this.attackSprite = undefined;
return;
}
let viewPort = ViewPort.get();
var localX = this["_x"] * 32;
var localY = this["_y"] * 32;
var localW = this["width"] * viewPort.targetTileWidth;
var localH = this["height"] * viewPort.targetTileHeight;
var localW = this["width"] * stendhal.ui.gamewindow.targetTileWidth;
var localH = this["height"] * stendhal.ui.gamewindow.targetTileHeight;
this.attackSprite.draw(ctx, localX, localY, localW, localH);
}
/**
* Draws an animation over entity sprite.
*
* @param {RenderingContext2D} ctx
* @param {CanvasRenderingContext2D} ctx
* Canvas context to draw on.
*/
private drawOverlayAnimation(ctx: RenderingContext2D) {
private drawOverlayAnimation(ctx: CanvasRenderingContext2D) {
if (this.overlay && this.overlay.draw(ctx, this["drawOffsetX"], this["drawOffsetY"],
this["drawWidth"], this["drawHeight"])) {
// overlay sprite expired
this.overlay.free();
this.overlay = undefined;
}
}
@ -652,21 +710,21 @@ export class RPEntity extends ActiveEntity {
}
onDamaged(_source: Entity, damage: number) {
this.attackResult = this.createResultIcon(Paths.sprites + "/combat/hitted.png");
this.attackResult = this.createResultIcon(stendhal.paths.sprites + "/combat/hitted.png");
var sounds = ["attack-melee-01", "attack-melee-02", "attack-melee-03", "attack-melee-04", "attack-melee-05", "attack-melee-06", "attack-melee-07"];
var index = Math.floor(Math.random() * Math.floor(sounds.length));
stendhal.sound.playLocalizedEffect(this["_x"], this["_y"], 20, 3, sounds[index], 1);
}
onBlocked(_source: Entity) {
this.attackResult = this.createResultIcon(Paths.sprites + "/combat/blocked.png");
this.attackResult = this.createResultIcon(stendhal.paths.sprites + "/combat/blocked.png");
var sounds = ["clang-metallic-1", "clang-dull-1"];
var index = Math.floor(Math.random() * Math.floor(sounds.length));
stendhal.sound.playLocalizedEffect(this["_x"], this["_y"], 20, 3, sounds[index], 1);
}
onMissed(_source: Entity) {
this.attackResult = this.createResultIcon(Paths.sprites + "/combat/missed.png");
this.attackResult = this.createResultIcon(stendhal.paths.sprites + "/combat/missed.png");
}
protected onHPChanged(change: number) {
@ -721,8 +779,8 @@ export class RPEntity extends ActiveEntity {
createResultIcon(imagePath: string) {
return {
initTime: Date.now(),
image: htmlImageStore.get(imagePath),
draw: function(ctx: RenderingContext2D, x: number, y: number) {
image: stendhal.data.sprites.get(imagePath),
draw: function(ctx: CanvasRenderingContext2D, x: number, y: number) {
ctx.drawImage(this.image, x, y);
return (Date.now() - this.initTime > 1200);
}
@ -735,7 +793,6 @@ export class RPEntity extends ActiveEntity {
weapon = undefined;
}
this.attackSprite?.free();
if (ranged) {
this.attackSprite = new RangedAttackSprite(this, this.getAttackTarget()!,
Nature.VALUES[nature].color, weapon);
@ -753,12 +810,12 @@ export class RPEntity extends ActiveEntity {
const imagePath = Nature.VALUES[nature].getWeaponPath(weapon);
/*
if (weapon.startsWith("blade_strike")) {
this.attackSprite = new BarehandAttackSprite(this, imagePath);
this.attackSprite = new BarehandAttackSprite(this, stendhal.data.sprites.get(imagePath));
} else {
this.attackSprite = new MeleeAttackSprite(this, imagePath);
}
*/
this.attackSprite = new BarehandAttackSprite(this, imagePath);
this.attackSprite = new BarehandAttackSprite(this, stendhal.data.sprites.get(imagePath));
}
}
@ -787,10 +844,10 @@ export class RPEntity extends ActiveEntity {
}
}
override destroy(obj: Entity) {
this._target?.onAttackStopped(this);
this.overlay?.free();
super.destroy(obj);
override destroy(_obj: Entity) {
if (this._target) {
this._target.onAttackStopped(this);
}
}
/**

View file

@ -0,0 +1,96 @@
/***************************************************************************
* (C) Copyright 2003-2023 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation; either version 3 of the *
* License, or (at your option) any later version. *
* *
***************************************************************************/
declare var marauroa: any;
/**
* base class for RPObjects
*/
export class RPObject {
[key: string]: any;
onEvent(e: any) {
var event = marauroa.rpeventFactory.create(e["c"]);
for (var i in e["a"]) {
if (e["a"].hasOwnProperty(i)) {
event[i] = e["a"][i];
}
event["_rpclass"] = e["c"];
}
// Event slots
for (var slot in e["s"]) {
if (e["s"].hasOwnProperty(slot)) {
event[slot] = e["s"][slot];
}
}
event.execute(this);
}
set(key: string, value: object) {
this[key] = value;
}
setMapEntry(map: string, key: string, value: object) {
this[map][key] = value;
}
unset(key: string) {
delete this[key];
}
unsetMapEntry(map: string, key: string) {
delete this[map][key];
}
destroy(_parent: RPObject) {
// do nothing
}
createSlot(name: string) {
var slot = marauroa.rpslotFactory.create(name);
slot._parent = this;
return slot;
}
init() {
// do nothing
}
/**
* Retrieves object width.
*/
public getWidth(): number {
return this["width"] || 1;
}
/**
* Retrieves object height.
*/
public getHeight(): number {
return this["height"] || 1;
}
/**
* Checks if this object is nested.
*/
public isContained(): boolean {
return this._parent !== undefined;
}
/**
* Retrieves the parent object.
*/
public getContainer(): RPObject {
return <RPObject> this._parent;
}
}

View file

@ -9,49 +9,37 @@
* *
***************************************************************************/
import { RPObject, RPZone} from "marauroa";
import { Entity } from "./Entity";
import { ActivityIndicatorSprite } from "../sprite/ActivityIndicatorSprite";
import { RenderingContext2D } from "util/Types";
import { Paths } from "../data/Paths";
import { singletons } from "../SingletonRepo";
import { stendhal } from "../stendhal";
import { images } from "sprite/image/ImageManager";
import { ImageSprite } from "sprite/image/ImageSprite";
declare var stendhal: any;
export class Sign extends Entity {
override zIndex = 5000;
private indicator?: ActivityIndicatorSprite;
override init() {
super.init();
if (!this.imageSprite) {
this.imageSprite = new ImageSprite(
images.load(Paths.sprites + "/signs/" + "default" + ".png"),
0, 0, 32, 32
);
}
constructor() {
super();
this["class"] = "default";
}
override set(key: string, value: object) {
super.set(key, value);
if (key === "activity-indicator" && stendhal.config.getBoolean("activity-indicator")) {
this.indicator = new ActivityIndicatorSprite();
} else if (key === "class") {
this.imageSprite = new ImageSprite(
images.load(Paths.sprites + "/signs/" + this["class"] + ".png"),
0, 0, 32, 32
);
}
}
override draw(ctx: RenderingContext2D) {
let image = this.imageSprite?.imageRef?.image;
let localX = this["x"] * 32;
let localY = this["y"] * 32;
if (image) {
override draw(ctx: CanvasRenderingContext2D) {
if (!this.imagePath) {
this.imagePath = stendhal.paths.sprites + "/signs/" + this["class"] + ".png";
}
var image = stendhal.data.sprites.get(this.imagePath);
if (image.height) {
var localX = this["x"] * 32;
var localY = this["y"] * 32;
ctx.drawImage(image, localX, localY);
if (this.indicator) {
this.indicator.draw(ctx, localX, localY, image.width);
@ -64,12 +52,7 @@ export class Sign extends Entity {
}
override getCursor(_x: number, _y: number) {
return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto";
}
override destroy(parent: RPObject|RPZone): void {
this.indicator?.free();
super.destroy(parent);
return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto";
}
}

View file

@ -9,10 +9,10 @@
* *
***************************************************************************/
import { Paths } from "../data/Paths";
import { NPC } from "./NPC";
import { marauroa } from "marauroa"
declare var marauroa: any;
declare var stendhal: any;
export class TrainingDummy extends NPC {
@ -29,6 +29,6 @@ export class TrainingDummy extends NPC {
}
override getCursor(_x: number, _y: number) {
return "url(" + Paths.sprites + "/cursor/attack.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/attack.png) 1 3, auto";
}
}

View file

@ -11,7 +11,7 @@
import { Entity } from "./Entity";
import { marauroa } from "marauroa"
declare var marauroa: any;
export class UnknownEntity extends Entity {

View file

@ -9,26 +9,32 @@
* *
***************************************************************************/
declare var stendhal: any;
import { Entity } from "./Entity";
import { MenuItem } from "../action/MenuItem";
import { Paths } from "../data/Paths";
import { ImageSprite } from "sprite/image/ImageSprite";
import { images } from "sprite/image/ImageManager";
export class UseableEntity extends Entity {
override zIndex = 3000
action = "use";
constructor() {
super();
this.sprite = {
height: 32,
width: 32
};
}
override set(key: string, value: any) {
super.set(key, value);
if (key === "class" || key === "name") {
let filename = Paths.sprites + "/" + this["class"] + "/" + this["_name"] + ".png";
let state = this["state"] * 32 || 0;
this.imageSprite?.free();
this.imageSprite = new ImageSprite(images.load(filename), 0, state, 32, 32);
} else if (key === "state" && this.imageSprite) {
this.imageSprite.offsetY = this["state"] * 32;
this.sprite.filename = stendhal.paths.sprites + "/"
+ this["class"] + "/" + this["_name"] + ".png";
}
if (key === "state") {
this.sprite.offsetY = this["state"] * 32;
}
}

View file

@ -9,8 +9,8 @@
* *
***************************************************************************/
import { marauroa } from "marauroa"
import { stendhal } from "../stendhal";
declare var marauroa: any;
declare var stendhal: any;
import { Entity } from "./Entity";
import { Player } from "./Player";
@ -33,7 +33,6 @@ import { OutfitDialog } from "../ui/dialog/outfit/OutfitDialog";
import { FloatingWindow } from "../ui/toolkit/FloatingWindow";
import { Direction } from "../util/Direction";
import { ViewPort } from "ui/ViewPort";
/**
@ -164,7 +163,7 @@ export class User extends Player {
override onExitZone() {
super.onExitZone();
// speech bubbles & emojis from viewport
ViewPort.get().onExitZone();
stendhal.ui.gamewindow.onExitZone();
// stop sounds & clear map sounds cache on zone change
const msgs: string[] = [];
const lssm = singletons.getLoopedSoundSourceManager();
@ -226,7 +225,7 @@ export class User extends Player {
* @param dir {util.Direction.Direction}
* New direction to face.
*/
public faceTo(dir: Direction) {
public setFacing(dir: Direction) {
marauroa.clientFramework.sendAction({type: "face", dir: ""+dir.val});
}
@ -245,7 +244,7 @@ export class User extends Player {
return;
}
// in case viewport frozen from view change event
ViewPort.get().freeze = false;
stendhal.ui.viewport.freeze = false;
if (cancelAutoWalk && this.autoWalkEnabled() && this.getWalkDirection() == dir) {
// cancel auto-walk if enabled & new direction is same as current direction of movement
marauroa.clientFramework.sendAction({type: "walk"});
@ -281,7 +280,7 @@ export class User extends Player {
public moveTo(x: number, y: number, zone?: string): void;
public moveTo(p1: object|number, p2?: number, p3?:string) {
// in case viewport frozen from view change event
ViewPort.get().freeze = false;
stendhal.ui.viewport.freeze = false;
let action: any = {};
if (typeof(p1) === "object") {
action = p1;

View file

@ -1,5 +1,5 @@
/***************************************************************************
* (C) Copyright 2003-2026 - Stendhal *
* (C) Copyright 2003-2023 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
@ -9,10 +9,9 @@
* *
***************************************************************************/
import { images } from "sprite/image/ImageManager";
import { Paths } from "../data/Paths";
import { Entity } from "./Entity";
import { ImageSprite } from "sprite/image/ImageSprite";
declare var stendhal: any;
export class VisibleEntity extends Entity {
@ -20,29 +19,21 @@ export class VisibleEntity extends Entity {
constructor() {
super();
}
override init(): void {
let filename = Paths.sprites + "/"
+ (this["class"] || "") + "/"
+ (this["subclass"] || "") + "/"
+ (this["_name"] || "") + ".png";
let state = this["state"] * 32 || 0;
this.imageSprite = new ImageSprite(images.load(filename), 0, state, 32, 32);
this.sprite = {
height: 32,
width: 32
};
}
override set(key: string, value: any) {
super.set(key, value);
if (key === "class" || key === "subclass" || key === "_name") {
// if this was already initialized, create a new ImageSprite
if (this.imageSprite) {
this.imageSprite.free();
this.init();
}
this.sprite.filename = stendhal.paths.sprites + "/"
+ (this["class"] || "") + "/"
+ (this["subclass"] || "") + "/"
+ (this["_name"] || "") + ".png";
} else if (key === "state") {
if (this.imageSprite) {
this.imageSprite.offsetY = value * 32;
}
this.sprite.offsetY = value * 32;
}
}
@ -51,7 +42,7 @@ export class VisibleEntity extends Entity {
}
override getCursor(_x: number, _y: number) {
return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto";
}
}

View file

@ -9,9 +9,10 @@
* *
***************************************************************************/
import { Paths } from "../data/Paths";
import { Entity } from "./Entity";
declare var stendhal: any;
export class WalkBlocker extends Entity {
override zIndex = 3000;
@ -21,7 +22,7 @@ export class WalkBlocker extends Entity {
}
override getCursor(_x: number, _y: number) {
return "url(" + Paths.sprites + "/cursor/stop.png) 1 3, auto";
return "url(" + stendhal.paths.sprites + "/cursor/stop.png) 1 3, auto";
}
}

View file

@ -9,12 +9,11 @@
* *
***************************************************************************/
import { marauroa } from "marauroa"
import { stendhal } from "../stendhal";
declare var marauroa: any;
declare var stendhal: any;
import { Entity } from "./Entity";
import { Player } from "./Player";
import { Ground } from "./Ground";
/**
@ -23,12 +22,11 @@ import { Ground } from "./Ground";
export class Zone {
/** Entities found in this zone. */
public entities: Entity[] = [];
private entities: Entity[] = [];
/** Singleton instance. */
private static instance: Zone;
public ground = new Ground();
/**
* Retrieves singleton instance.
@ -47,7 +45,7 @@ export class Zone {
// do nothing
}
getEntitiesAt(x: number, y: number, filter?: boolean): Entity[] {
getEntitiesAt(x: number, y: number, filter: boolean): Entity[] {
const xGrid = x / 32;
const yGrid = y / 32;
const entities = [] as Entity[];
@ -65,14 +63,14 @@ export class Zone {
return entities;
}
entityAt(x: number, y: number, filter?: boolean): Entity {
let res = Zone.get().ground as any;
entityAt(x: number, y: number, filter: boolean): Entity {
let res = stendhal.zone.ground;
for (const obj of this.getEntitiesAt(x, y, filter)) {
res = obj;
}
// If we found an entity, return it
if (res != Zone.get().ground) {
if (res != stendhal.zone.ground) {
return res;
}

View file

@ -1,38 +0,0 @@
/***************************************************************************
* (C) Copyright 2005-2026 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation; either version 3 of the *
* License, or (at your option) any later version. *
* *
***************************************************************************/
import { RPEvent } from "marauroa"
export class AttackEvent extends RPEvent {
public damage!: string;
public type!: string;
public weapon!: string;
public execute(entity: any): void {
let target = entity.getAttackTarget();
if (!target) {
return;
}
if (this.hasOwnProperty("hit")) {
var damage = parseInt(this["damage"], 10);
if (damage !== 0) {
target.onDamaged(entity, damage);
} else {
target.onBlocked(entity);
}
} else {
target.onMissed(entity);
}
entity.onAttackPerformed(parseInt(this["type"], 10), this.hasOwnProperty("ranged"), this["weapon"]);
}
};

View file

@ -10,13 +10,13 @@
* *
***************************************************************************/
import { RPEvent } from "marauroa"
import { RPEvent } from "./RPEvent";
import { ui } from "../ui/UI";
import { DialogContentComponent } from "../ui/toolkit/DialogContentComponent";
import { stendhal } from "../stendhal";
declare var stendhal: any;
export class BestiaryEvent extends RPEvent {
@ -24,7 +24,7 @@ export class BestiaryEvent extends RPEvent {
enemies!: string;
execute(_entity: any) {
execute(entity: any) {
if (!this.hasOwnProperty("enemies")) {
console.error("Event does not have \"enemies\" attribute");
return;

View file

@ -11,9 +11,9 @@
import { Chat } from "../util/Chat";
import { ChatOptionsDialog } from "../ui/dialog/ChatOptionsDialog";
import { RPEvent } from "marauroa"
import { RPEvent } from "./RPEvent";
import { marauroa } from "marauroa"
declare var marauroa: any
/**
* shows an image for a detail view

View file

@ -1,93 +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 { marauroa } from "marauroa"
import { AttackEvent } from "event/AttackEvent";
import { BestiaryEvent } from "./BestiaryEvent";
import { ChatOptionsEvent } from "./ChatOptionsEvent";
import { ExamineEvent } from "./ExamineEvent";
import { GlobalVisualEffectEvent } from "event/GlobalVisualEffectEvent";
import { GroupChangeEvent } from "./GroupChangeEvent";
import { GroupInviteEvent } from "./GroupInviteEvent";
import { ImageEvent } from "event/ImageEvent";
import { PlayerLoggedOnEvent } from "./PlayerLoggedOnEvent";
import { PlayerLoggedOutEvent } from "./PlayerLoggedOutEvent";
import { PrivateTextEvent } from "event/PrivateTextEvent";
import { ProgressStatusEvent } from "./ProgressStatusEvent";
import { ReachedAchievementEvent } from "event/ReachedAchievementEvent";
import { ShowItemListEvent } from "./ShowItemListEvent";
import { ShowOutfitListEvent } from "./ShowOutfitListEvent";
import { SoundEvent } from "./SoundEvent";
import { TextEvent } from "event/TextEvent";
import { TradeEvent } from "./TradeEvent";
import { TransitionGraphEvent } from "event/TransitionGraphEvent";
import { ViewChangeEvent } from "./ViewChangeEvent";
export class EventRegistry {
private initialized = false;
/** Singleton instance. */
private static instance: EventRegistry;
/**
* Retrieves singleton instance.
*/
static get(): EventRegistry {
if (!EventRegistry.instance) {
EventRegistry.instance = new EventRegistry();
}
return EventRegistry.instance;
}
/**
* Hidden singleton constructor.
*/
private constructor() {
// do nothing
}
/**
* Registers standard events.
*/
init() {
if (this.initialized) {
console.warn("tried to re-initialize EventRegistry");
return;
}
this.initialized = true;
marauroa.rpeventFactory.register("attack", AttackEvent);
marauroa.rpeventFactory.register("bestiary", BestiaryEvent);
marauroa.rpeventFactory.register("chat_options", ChatOptionsEvent);
marauroa.rpeventFactory.register("examine", ExamineEvent);
marauroa.rpeventFactory.register("global_visual_effect", GlobalVisualEffectEvent);
marauroa.rpeventFactory.register("group_change_event", GroupChangeEvent);
marauroa.rpeventFactory.register("group_invite_event", GroupInviteEvent);
marauroa.rpeventFactory.register("image_event", ImageEvent);
marauroa.rpeventFactory.register("player_logged_on", PlayerLoggedOnEvent);
marauroa.rpeventFactory.register("player_logged_out", PlayerLoggedOutEvent);
marauroa.rpeventFactory.register("private_text", PrivateTextEvent);
marauroa.rpeventFactory.register("progress_status_event", ProgressStatusEvent);
marauroa.rpeventFactory.register("reached_achievement", ReachedAchievementEvent);
marauroa.rpeventFactory.register("show_item_list", ShowItemListEvent);
marauroa.rpeventFactory.register("show_outfit_list", ShowOutfitListEvent);
marauroa.rpeventFactory.register("sound_event", SoundEvent);
marauroa.rpeventFactory.register("text", TextEvent);
marauroa.rpeventFactory.register("trade_state_change_event", TradeEvent);
marauroa.rpeventFactory.register("transition_graph", TransitionGraphEvent);
marauroa.rpeventFactory.register("view_change", ViewChangeEvent);
}
}

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