Compare commits

...
Sign in to create a new pull request.

158 commits

Author SHA1 Message Date
Hendrik Brummermann
fcd8330db0
Merge pull request #769 from arianne/dependabot/npm_and_yarn/npm_and_yarn-53cbaf2a5b
Some checks failed
CodeQL / Analyze (push) Has been cancelled
Java CI / build (push) Has been cancelled
Bump esbuild from 0.27.2 to 0.28.1 in the npm_and_yarn group across 1 directory
2026-07-12 06:51:38 +02:00
dependabot[bot]
98864f3802
Bump esbuild in the npm_and_yarn group across 1 directory
Some checks failed
Java CI / build (push) Has been cancelled
Bumps the npm_and_yarn group with 1 update in the / directory: [esbuild](https://github.com/evanw/esbuild).


Updates `esbuild` from 0.27.2 to 0.28.1
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2025.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.2...v0.28.1)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.28.1
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-14 11:54:18 +00:00
dimitris
0dff60f98e Guard external link launch against ActivityNotFoundException 2026-06-04 15:04:24 -07:00
Hendrik Brummermann
246d9ce288 converted BarehandAttackSprite and RangedAttackSprite to ImageBitmap 2026-03-01 23:04:12 +01:00
Hendrik Brummermann
31d417e31f code cleanup 2026-03-01 23:04:12 +01:00
Hendrik Brummermann
60d20f593b use ImageBitmap for SkillEffect 2026-03-01 23:04:11 +01:00
Hendrik Brummermann
f44e519104
Merge pull request #762 from arianne/dependabot/npm_and_yarn/npm_and_yarn-fb4fae9948
Bump minimatch from 9.0.5 to 9.0.9 in the npm_and_yarn group across 1 directory
2026-03-01 12:54:22 +01:00
dependabot[bot]
70dc2ded31
Bump minimatch in the npm_and_yarn group across 1 directory
Some checks failed
Java CI / build (push) Has been cancelled
Bumps the npm_and_yarn group with 1 update in the / directory: [minimatch](https://github.com/isaacs/minimatch).


Updates `minimatch` from 9.0.5 to 9.0.9
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v9.0.5...v9.0.9)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-version: 9.0.9
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-01 10:34:11 +00:00
Hendrik Brummermann
542f4524db converted StatusIconDrawer to ImageBitmap 2026-02-28 18:55:14 +01:00
Hendrik Brummermann
0d25beefa8 refactored singleton class StatusIconDrawer out of RPEntity 2026-02-28 03:14:24 +01:00
Hendrik Brummermann
3a478f341d use HeldObjectManager.get() 2026-02-28 02:30:16 +01:00
Hendrik Brummermann
c9f865a894 slightly reduce size of drag icon to so players focus cursor position 2026-02-23 22:26:32 +01:00
Hendrik Brummermann
afd5329fcc Item uses ImageBitmap instead of HTMLImageElement 2026-02-23 22:08:35 +01:00
Hendrik Brummermann
b8edcfb252 code cleanup 2026-02-22 19:48:41 +01:00
Hendrik Brummermann
bebce66517 use ImageBitmap in DomesticAnimal 2026-02-22 19:32:51 +01:00
Hendrik Brummermann
6f58eef4f5 updated marauroa.jar to set _rpclass before init() 2026-02-22 19:29:26 +01:00
Hendrik Brummermann
4d2925aa4b cleaned up HTMLImageStorage usage 2026-02-22 10:53:57 +01:00
Hendrik Brummermann
fb2ae76fc5 renamed SpriteStore to HTMLImageStore and access it directly 2026-02-22 09:59:37 +01:00
Hendrik Brummermann
aadec09b5d clean up factory class 2026-02-22 09:16:33 +01:00
Hendrik Brummermann
9fd4316d0f removed stendhal.data.sprites variable 2026-02-22 09:09:49 +01:00
Hendrik Brummermann
e1d01dea79 replaced stendhal.ui.gamewindow and stendhal.ui.viewport 2026-02-12 23:52:30 +01:00
Hendrik Brummermann
dd500a36e5 replaced stendhal.zone with Zone.get() 2026-02-12 22:29:45 +01:00
Hendrik Brummermann
8b2727f392 converted stendhal.ui.html into static class HTMLUtil 2026-02-12 21:59:12 +01:00
Hendrik Brummermann
d350c2b494 invoke singleton get-methods instead of SingletonRepo 2026-02-12 21:27:15 +01:00
Hendrik Brummermann
2022b28b76 access OutfitStore singleton directly 2026-02-12 01:42:32 +01:00
Hendrik Brummermann
1f9242ae6f converted to image bitmap 2026-02-10 23:24:15 +01:00
Hendrik Brummermann
03654e4459 prevent path traversal 2026-02-09 22:32:04 +01:00
Hendrik Brummermann
fcadfcdbd3 use ImageBitmap 2026-02-09 22:28:39 +01:00
Hendrik Brummermann
581ffc886a use ImageBitmap and ImageManager 2026-02-09 00:32:54 +01:00
Hendrik Brummermann
e2146721d1 use ImageBitmap in ActivityIndicatorSprite 2026-02-08 22:02:29 +01:00
Hendrik Brummermann
c6a51ffcf3 converted Blood from SpriteStore to ImageManager 2026-02-08 18:45:22 +01:00
Hendrik Brummermann
d846fa5818 created an ImageSprite class for ImageManager ImageRefs and use it 2026-02-08 18:45:05 +01:00
Hendrik Brummermann
13fc7f6b1a deleted unused classes EmptySprite, ImageSprite, Sprite, Tileset 2026-02-08 16:45:36 +01:00
Hendrik Brummermann
86cf9c0959 deleted class ImageSprite 2026-02-08 15:19:16 +01:00
Hendrik Brummermann
7c5cef1181 fixed ctrl direction change 2026-02-08 00:27:36 +01:00
Hendrik Brummermann
72f23a7bc1 call super.destroy() 2026-02-07 22:24:24 +01:00
Hendrik Brummermann
404263a1ef code cleanup 2026-02-07 22:23:33 +01:00
Hendrik Brummermann
b479ec3d52 use ImageManager instead of SpriteStore for weather images 2026-02-07 02:04:40 +01:00
Hendrik Brummermann
b796ba36b0 use ImageManager instead of SpriteStore 2026-02-07 02:01:55 +01:00
Hendrik Brummermann
07a41fddfe cache tileset-images to speed up zone changes 2026-02-07 02:01:21 +01:00
Hendrik Brummermann
97ea5ab442 use ImageManager 2026-02-05 22:09:59 +01:00
Hendrik Brummermann
659c155a36 apply preferences to own outfit 2026-02-05 22:09:39 +01:00
Hendrik Brummermann
0f3ecbd3dd code cleanup 2026-02-05 22:08:12 +01:00
Hendrik Brummermann
535e7f5727 use ImageManager images for preview of outfit parts into Outfit Dialog 2026-02-04 22:57:48 +01:00
Hendrik Brummermann
1bf34316fb support ImageRef.free() without going through the ImageManager 2026-02-04 22:57:01 +01:00
Hendrik Brummermann
c7befd2fe1 support cleanup of ImageManager 2026-02-04 22:15:01 +01:00
Hendrik Brummermann
230b25e5fa added support for filtered images 2026-02-02 22:10:18 +01:00
Hendrik Brummermann
3b6d652aa8 dont return the image in waitFor because it might have expired 2026-02-02 22:04:07 +01:00
Hendrik Brummermann
41604ca00e code cleanup 2026-02-02 21:59:34 +01:00
Hendrik Brummermann
7e0fbe0f16 split ImageRef and ImageRefImpl to hide management methods 2026-02-02 20:54:03 +01:00
Hendrik Brummermann
2bd16f0307 implemented ImageRef.waitFor() 2026-02-01 22:43:01 +01:00
Hendrik Brummermann
afb06b8f97 started to work on ImageManager and ImageRef 2026-02-01 22:25:24 +01:00
Hendrik Brummermann
95f373bd1c removed reference to global stendhal object 2026-02-01 22:25:06 +01:00
Hendrik Brummermann
e58e6a54cb do not use failsave sprite for drag image 2026-01-31 23:11:37 +01:00
Hendrik Brummermann
f109b551f5 optimized image creation 2026-01-31 23:10:42 +01:00
Hendrik Brummermann
5f4d9d9460 moved SpriteStore.getAreaOf() to HTMLImageElementUtil 2026-01-31 21:28:43 +01:00
Hendrik Brummermann
61f047a955 prevent negative positions of dialogs 2026-01-31 17:18:58 +01:00
Hendrik Brummermann
3534bf384f close outfit dialog preview ImageBitmap 2026-01-31 17:06:49 +01:00
Hendrik Brummermann
6b1296e059 renamed MultilayerImage to MultilayerImageLoader 2026-01-31 15:50:39 +01:00
Hendrik Brummermann
c19e4b0627 added filter support to MultilayerImage 2026-01-31 15:50:19 +01:00
Hendrik Brummermann
72d569343a implemented MultilayerImage loader 2026-01-31 14:55:56 +01:00
Hendrik Brummermann
a6708ee0c2 removed references to stendhal.data.map and SingletonRepo.getMap() 2026-01-25 23:43:17 +01:00
Hendrik Brummermann
e0dce1a242 access TileMap directly without referencing stendhal.data.map 2026-01-25 23:33:25 +01:00
Hendrik Brummermann
55e802b988 changed module and moduleResolution for better compatibility 2026-01-25 23:09:08 +01:00
Hendrik Brummermann
d24e452f59 inlined LandscapeRender.ts into CombinedTilesetRenderingStrategy.ts 2026-01-25 23:08:26 +01:00
Hendrik Brummermann
8fc173d923 replaced tileset loader "ImagePreloader" as ImageCache using ImageBitmap 2026-01-25 20:01:31 +01:00
Hendrik Brummermann
1484f872da code cleanup 2026-01-25 19:59:25 +01:00
Hendrik Brummermann
93a99c9c10 moved CombinedTilesetRenderingStrategy out of LandscapeRenderingStrategy 2026-01-25 01:53:13 +01:00
Hendrik Brummermann
79ba39a030 fixed moduleResolution setting 2026-01-25 01:41:35 +01:00
Hendrik Brummermann
1c50a2a454 moved sprite/image/ImageFilter out of data/SpriteStore 2026-01-25 01:40:28 +01:00
Hendrik Brummermann
4feb1e628a use OffscreenCanvas to create outfit sprite 2026-01-24 23:13:03 +01:00
Hendrik Brummermann
662aab6ded use ImageBitmap for minimap static collisions 2026-01-24 18:39:35 +01:00
Hendrik Brummermann
f9fc0230f5 use OffscreenCanvas 2026-01-24 15:13:40 +01:00
Hendrik Brummermann
ba6d507924 inlined DrawingStage into Outfit 2026-01-23 23:00:04 +01:00
Hendrik Brummermann
e121d44356 added TODOs and fixed compiler warnings 2026-01-23 22:47:34 +01:00
Hendrik Brummermann
8b61de02e6 converted Singleton DrawingStage to instance and use Offscreen 2026-01-23 22:47:17 +01:00
Hendrik Brummermann
1ca1f0e47e fixed undefined _hsMap() error 2026-01-23 21:31:57 +01:00
Jordan Irwin
3026afe931
Manually build code in CodeQL workflow as default target has changed 2026-01-20 21:52:22 -08:00
Jordan Irwin
f9a37f122e
Update instances of CodeQL Action to v3 as v2 is now deprecated 2026-01-20 21:39:33 -08:00
Jordan Irwin
fd2cff8618
Clean up README 2026-01-20 20:27:21 -08:00
Jordan Irwin
30494af57c
Fix running server with 'ant server_run' 2026-01-20 19:41:59 -08:00
Jordan Irwin
2ed431fd6a
Add Spanish translations for lemon, banana, & corn 2026-01-20 01:27:31 -08:00
Jordan Irwin
cc4918a5ed
Add Spanish translation for map description 2026-01-20 01:26:38 -08:00
Jordan Irwin
6608eb2b02
Add Spanish translations for some newer items 2026-01-20 01:26:09 -08:00
Jordan Irwin
79622ee9cf
Update Spanish translations for some renamed items 2026-01-20 01:12:57 -08:00
Jordan Irwin
c6f2718aec
Remove trailing periods from translated names 2026-01-20 01:12:06 -08:00
Jordan Irwin
1fd97009eb
Update Spanish translations of item & entity descriptions 2026-01-20 00:19:55 -08:00
Jordan Irwin
517b62c5b1
Update Spanash locale file header 2026-01-20 00:19:29 -08:00
Jordan Irwin
de36580350
Run 'ant locale_update' 2026-01-20 00:14:16 -08:00
Jordan Irwin
3d971f37b5
Update locale template 2026-01-20 00:13:41 -08:00
Jordan Irwin
4666911be6
Don't need English language file 2026-01-20 00:13:30 -08:00
AntumDeluge
c4723aba93 Merge pull request 'Clean Up Ant Build' (#32) from AntumDeluge/stendhal:build_cleanup into master
Reviewed-on: https://codeberg.org/arianne/stendhal/pulls/32
2026-01-20 03:47:18 +01:00
Jordan Irwin
8c93122d1e
Rename target locale_build_template to locale_template 2026-01-19 18:07:18 -08:00
Jordan Irwin
2bec3a151b
Remove unneeded aliases 2026-01-19 18:03:56 -08:00
Jordan Irwin
38f5c0124d
Remove build target descriptions
Descriptions now available in `ant help` target.
2026-01-19 18:03:56 -08:00
Jordan Irwin
2df3243f2d
Make 'help' default target 2026-01-19 18:03:55 -08:00
Jordan Irwin
08873546ba
Add help info to ant build 2026-01-19 18:03:47 -08:00
Jordan Irwin
26c4723749
Move build properties to sub-module 2026-01-19 17:38:39 -08:00
Jordan Irwin
6eeff4766d
Make compile_stendhaltools target alias of compile_tools 2026-01-19 17:38:39 -08:00
Jordan Irwin
737cac9495
Make compile_stendhaltextclient build target alias of compile_textclient 2026-01-19 17:38:33 -08:00
Hendrik Brummermann
5ae5d33438 updated marauroa.jar
Some checks failed
Java CI / build (push) Has been cancelled
2026-01-18 21:41:30 +01:00
Hendrik Brummermann
621a43579a fixed import 2026-01-18 21:05:29 +01:00
Hendrik Brummermann
45cd2ac30e moved onPerceptionEnd method from Client to StendhalPerceptionListener 2026-01-18 14:13:13 +01:00
Hendrik Brummermann
b328b1aedf moved RPObject, RPEvent and RPSlot from Stendhal to Marauroa 2026-01-17 23:50:58 +01:00
Hendrik Brummermann
d633ebf947 use instance of Marauroa class instead of global marauroa object 2026-01-17 15:15:33 +01:00
Hendrik Brummermann
f4935f4928 updated marauroa.jar 2026-01-16 22:07:55 +01:00
Hendrik Brummermann
b71a29f883 user register for RPObjects 2026-01-16 22:04:56 +01:00
Hendrik Brummermann
3424b898b8 created dedicated CorpseSlot class 2026-01-16 22:04:20 +01:00
Hendrik Brummermann
1f3abca39c use register for events 2026-01-16 22:03:57 +01:00
Hendrik Brummermann
d6ca574a20 fixed references to wrong "ui" variable 2026-01-15 21:42:09 +01:00
Hendrik Brummermann
612b4a6b88 code cleanup 2026-01-14 23:09:37 +01:00
Hendrik Brummermann
a13ee36fe8 converted all events to TypeScript 2026-01-14 22:56:50 +01:00
Jordan Irwin
2abc8df666
Run 'ant cleanwhitespace' 2026-01-13 19:12:13 -08:00
Jordan Irwin
14d7e046be
Process ant build files in 'cleanwhitespace' target 2026-01-13 19:09:24 -08:00
Jordan Irwin
6c64621496
Don't try to clean removed 'tiled' directory 2026-01-13 19:09:23 -08:00
Hendrik Brummermann
6a4009c790 fixed build 2026-01-13 23:07:55 +01:00
Hendrik Brummermann
38934a8c09 fixed local builds after esbuild migration 2026-01-13 09:13:54 +01:00
Hendrik Brummermann
738d3cd830 explicitly define marauroa global variable 2026-01-10 00:24:12 +01:00
Hendrik Brummermann
8cb1a7b050 converted stendhal object into a static class 2026-01-09 00:06:37 +01:00
Hendrik Brummermann
d1fc6947b7 use esbuild 2026-01-06 21:45:58 +01:00
Hendrik Brummermann
402bb84c43 fixed this-reference and redirect to character selection on disconnect 2026-01-06 21:44:41 +01:00
Hendrik Brummermann
819b80913f added esbuild as devDependencies 2026-01-06 21:43:57 +01:00
Hendrik Brummermann
da4378d30c updated marauroa 2026-01-06 21:43:35 +01:00
Hendrik Brummermann
37d3e1c84d fixed init order and wrong "this". 2026-01-02 04:11:34 +01:00
Hendrik Brummermann
f1d98fff56 access SpriteStore via singletons instead of stendhal-variable 2026-01-02 04:11:33 +01:00
Jordan Irwin
8f642ac446
Increment version to 1.49.5 2026-01-01 18:33:01 -08:00
Jordan Irwin
6224a0638a
Run 'ant init' to update copyright year 2026-01-01 18:31:27 -08:00
Jordan Irwin
c38740eaab
Use 'versionName' instead of 'versionCode' in Android output filename 2026-01-01 18:24:56 -08:00
Hendrik Brummermann
cbf971e632 replace stendhal.paths with Paths to ensure correct init order 2026-01-02 01:37:39 +01:00
Hendrik Brummermann
7546fbba80 removed unused library 2025-12-30 00:24:38 +01:00
Hendrik Brummermann
face9f3a67 use OffscreenCanvas and ImageBitmap instead of img- and canvas-elements 2025-12-28 18:59:53 +01:00
Hendrik Brummermann
703fa61329 cleaned up references to animations 2025-12-28 18:13:15 +01:00
Hendrik Brummermann
4dbe1f73fe implemented light rendering (to test "/debug light" and change zone) 2025-12-28 17:51:51 +01:00
Hendrik Brummermann
09371bff47 restore transparency after blending 2025-12-28 14:41:39 +01:00
Hendrik Brummermann
6ff59a0040 refactored drawCombinedTileAtIndex and drawTile in preparation for blend 2025-12-28 12:40:28 +01:00
Hendrik Brummermann
063d03c45f used Canvas and RenderingContext2D to allow OffscreenCanvas 2025-12-28 12:32:20 +01:00
Hendrik Brummermann
d191c14232 updated typescript and typedoc 2025-12-28 12:31:20 +01:00
Hendrik Brummermann
d9b51f0d15 disabled code to draw lights (alpha is broken) 2025-12-28 10:04:14 +01:00
Hendrik Brummermann
44f6a4a9fe marked event listener as passive to prevent scroll delay 2025-12-27 13:10:16 +01:00
Hendrik Brummermann
d1d4eb5c49 removed setting alpha in applyHSLFilter method 2025-12-27 13:00:05 +01:00
Hendrik Brummermann
0d56d2b63b renamed Map to TileMap and use type definition 2025-12-26 14:57:00 +01:00
Hendrik Brummermann
a000e5f788 made blend layer available 2025-12-26 14:41:21 +01:00
Jordan Irwin
cc5414894f
Use JDK version 17 in Android Build Test workflow
Current bundled Gradle Wrapper version is 7.5.1, which isn't
compatible with more recent Java releases.
2025-12-24 22:10:38 -08:00
Jordan Irwin
2e5dbc12c6
Validate Gradle Wrapper .jar in Android Build Test workflow 2025-12-24 22:01:09 -08:00
Jordan Irwin
f756534846
Validate Gradle Wrapper distribution checksum not .jar checksum 2025-12-24 21:59:21 -08:00
Jordan Irwin
7174c32521
Send Android Build error report to Discord dev channel 2025-12-24 18:21:32 -08:00
Jordan Irwin
049b91e98b
Don't send details to Discord 2025-12-24 18:21:01 -08:00
Jordan Irwin
a5db37305e
Fix 'run' line 2025-12-24 18:13:57 -08:00
Jordan Irwin
179bc4d5e7
Fix code alignment in Android Build Test GitHub workflow 2025-12-24 18:10:25 -08:00
Jordan Irwin
31c4f9cfb4
Discord webhook for Android Build Test failure 2025-12-24 18:07:00 -08:00
Jordan Irwin
c4491004d2
Parse job ID in Android Build Test GitHub workflow 2025-12-24 18:05:36 -08:00
Jordan Irwin
e910892a16
Build .apk packages using Ant in Android Build Test GitHub workflow 2025-12-24 17:31:39 -08:00
Jordan Irwin
7beb123117
Allow dispatching Android build workflow manually 2025-12-24 17:11:58 -08:00
Hendrik Brummermann
a141cc187d properly format item names in description text of shop windows 2025-12-22 19:21:51 +01:00
Hendrik Brummermann
c68f0a59d5 show outfits in shop sign dialog 2025-12-22 18:06:00 +01:00
Hendrik Brummermann
a3bd4b328a code cleanup 2025-12-22 18:05:49 +01:00
Hendrik Brummermann
9e56945faf use proper table for item shop signs 2025-12-22 17:31:24 +01:00
Hendrik Brummermann
6f783d3ef6 moved ShowItemListEvent and ShowOutfitListEvent out of EventRegistry 2025-12-22 17:02:31 +01:00
222 changed files with 7758 additions and 8226 deletions

View file

@ -7,16 +7,56 @@ on:
pull_request:
paths:
- 'app/android/**'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- id: job_info
uses: daleyjem/gh-job-id@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
job_name: 'build'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Checkout Repository
uses: actions/checkout@v3
- name: Build Android Packages
run: cd app/android/client && gradle assemble
- name: Set Up JDK
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'adopt'
- name: Validate Gradle Wrapper .jar
run: |
cd app/android/gradle/wrapper
curl --location --output gradle-wrapper.jar.sha256 \
https://services.gradle.org/distributions/gradle-7.5.1-wrapper.jar.sha256
echo " gradle-wrapper.jar" >> gradle-wrapper.jar.sha256
sha256sum --check gradle-wrapper.jar.sha256
- name: Build APKs with Ant
uses: cedx/setup-ant@v6
with:
version: '1.10'
- run: ant -noinput -buildfile build.xml android -Dgradle.params="assembleDebug assembleRelease"
- name: Failure Report
uses: sarisia/actions-status-discord@v1
if: failure()
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_DEVELOPMENT }}
with:
title: 'Android Build Failed'
description: '${{ steps.job_info.outputs.html_url }}'
nodetail: true
- uses: sarisia/actions-status-discord@v1
if: failure()
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_DEVLOG }}
with:
title: 'Android Build Failed'
description: '${{ steps.job_info.outputs.html_url }}'
nodetail: true

View file

@ -29,15 +29,15 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
#- name: Autobuild
# uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -46,9 +46,7 @@ jobs:
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- run: ant -noinput -f build.xml clean compile
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3

View file

@ -20,4 +20,15 @@
<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,29 +30,28 @@ button to create or log in to your account.
### Mobile
Play using the [progressive web app](https://en.wikipedia.org/wiki/Progressive_web_app). Just visit
https://stendhalgame.org/ in your mobile browser and click "install" when prompted.
[stendhalgame.org](https://stendhalgame.org/) in your mobile browser and click "install" when prompted.
[<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 https://stendhalgame.org/download.html.
[F-Droid App Store](https://f-droid.org/) or download it from [stendhalgame.org](https://stendhalgame.org/download.html).
### Classic Desktop
[![Download](https://stendhalgame.org/images/downloadbutton.png)](https://arianne-project.org/download/stendhal.zip)
Play using the classic desktop client. You will need Java which you can download from
[https://www.java.com](https://www.java.com/en/download) on Microsoft Windows. Java is included in
[java.com](https://www.java.com/en/download) on Microsoft Windows. Java is included in
most Linux distributions. Both Oracle Java and OpenJDK are supported.
Please download Stendhal from [https://stendhalgame.org](https://stendhalgame.org)<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 [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:
`java -jar stendhal-starter.jar`
There is a [manual](https://stendhalgame.org/wiki/Stendhal_Manual), and there is Help from the client menu icons in the upper right hand side of the game screen.
There is a [manual](https://stendhalgame.org/wiki/Stendhal_Manual), and there is Help from the client menu icons in the upper right hand side of the game screen.
## Development environment
@ -67,7 +66,7 @@ There is a [manual](https://stendhalgame.org/wiki/Stendhal_Manual), and there i
Stendhal is free software. You can redistribute it and/or modify under the following terms:
- The **server**, **java client**, and **Android client**: *GNU General Public License* 2 or later as published by the Free
- The **server**, **java client**, and **Android client**: *GNU General Public License* 2 or later as published by the Free Software Foundation
- The **web client** and **native client**: *GNU Affero General Public License* 3 or later as published by the Free Software Foundation with the definition of "System libraries" being extended to include authentication providers and software distribution platforms.
- The **nativehelper** extensions: *GNU Lesser General Public License* 3 or later as published by the Free Software Foundation
@ -76,6 +75,6 @@ even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE
[LICENSE.txt](LICENSE.txt) and [src/js/LICENSE.html](src/js/LICENSE.html) file for more details.
Stendhal(c) is copyright of Miguel Angel Blanch Lardin, 2005-2008, arianne_rpg at users dot sourceforge dot net and others<br>
Stendhal(c) is copyright of the Arianne Project, 2006-2025, arianne-general at lists dot sourceforge dot net
Stendhal(c) is copyright of the Arianne Project, 2006-2026, arianne-general at lists dot sourceforge dot net
Please have a look at the list of [contributors](doc/contributors.md#contributors).

View file

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

View file

@ -14,6 +14,7 @@ package org.stendhalgame.client;
import java.util.Random;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@ -189,7 +190,11 @@ public class ClientView extends WebView {
if (!UrlHelper.isInternalUri(uri)) {
// open external links in default browser/app
// FIXME: should we ask for confirmation?
MainActivity.get().startActivity(new Intent(Intent.ACTION_VIEW, uri));
try {
MainActivity.get().startActivity(new Intent(Intent.ACTION_VIEW, uri));
} catch (final ActivityNotFoundException e) {
Notifier.toast("No app found to open this link");
}
return true;
}
view.loadUrl(UrlHelper.checkClientUrl(uri.toString()));

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.48.5</string>
<string name="build_version">1.49.5</string>
</resources>

View file

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

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.48.5",
"version": "1.49.5",
"defaultMode": "window",
"port": 0,
"documentRoot": "/resources/",

View file

@ -1,6 +1,6 @@
@echo off
set STENDHAL_VERSION=1.48.5
set STENDHAL_VERSION=1.49.5
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.48.5"
STENDHAL_VERSION="1.49.5"
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.48
version = 1.48.5
version.old = 1.49
version = 1.49.5
# FIXME: dynamic method to set this property?
version.android = 1048005
version.android = 1049005
# javac options
javac.deprecation = true

View file

@ -1,81 +1,32 @@
<?xml version='1.0'?>
<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.
-->
<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>
<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,7 +161,8 @@
</target> <!-- client_android_build -->
<target name="android" description="Create Android client package for release and distribution.">
<!-- Create Android client package for release and distribution. -->
<target name="android">
<echo
if:set="exclude.webview"
message="ANDROID_SDK_ROOT environment variable not set. Skipping Android WebView client build."/>

View file

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

View file

@ -19,13 +19,15 @@
</condition>
<target name="jar" depends="client_build,server_build" description="Build server and clients.">
<!-- Build server and clients. -->
<target name="jar" depends="client_build,server_build">
<!-- XXX: client & server builds are not exclusive to NetBeans, remove this output? -->
<echo>Built client and server for NetBeans.</echo>
</target>
<target name="signjar" unless="exclude.signing" description="Sign client distribution.">
<!-- Sign client distribution. -->
<target name="signjar" unless="exclude.signing">
<signjar
alias="${keystore.alias}"
keystore="keystore.ks"
@ -34,20 +36,23 @@
</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" description="Generate JAR files for both client and server."/>
<target name="compile" depends="server_build,client_build,server_build" />
<target name="dist" depends="clean,dist_binary,dist_source,signupdates" description="Create all packages for Stendhal release."/>
<!-- Create all packages for Stendhal release. -->
<target name="dist" depends="clean,dist_binary,dist_source,signupdates" />
<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."/>
<!-- 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" />
<!-- START: source -->
<target name="dist_source" description="Create all source packages for Stendhal release.">
<!-- Create all source packages for Stendhal release. -->
<target name="dist_source">
<mkdir dir="${buildroot}/stendhal-${version}-src"/>
<copy todir="${buildroot}/stendhal-${version}-src/src">
@ -104,7 +109,8 @@
<!-- START: client data -->
<target name="build_client_data" description="Build client data files.">
<!-- Build client data files. -->
<target name="build_client_data">
<!-- base data -->
<mkdir dir="${build_client_data}"/>
<copy todir="${build_client_data}/data/sprites">
@ -179,7 +185,8 @@
<!-- START: server -->
<target name="server_build" depends="init,checkxml" description="Compile JAR file for Stendhal server.">
<!-- Compile JAR file for Stendhal server. -->
<target name="server_build" depends="init,checkxml">
<mkdir dir="${build_server}"/>
<copy todir="${build_server}/data/conf" file="data/conf/stendhalcreateaccount.properties"/>
@ -340,7 +347,8 @@
</target> <!-- server_build -->
<target name="dist_server_binary" depends="server_build" description="Create server package for release and distribution.">
<!-- Create server package for release and distribution. -->
<target name="dist_server_binary" depends="server_build">
<mkdir dir="${buildroot}/stendhal-server-${version}"/>
<copy todir="${buildroot}/stendhal-server-${version}/data/script">
@ -392,10 +400,12 @@
</target> <!-- dist_server_binary -->
<target name="dist_docker" depends="docker_build,docker_push" description="Build and push container image."/>
<!-- Build and push container image. -->
<target name="dist_docker" depends="docker_build,docker_push" />
<target name="docker_build_prepare" depends="dist_server_binary" description="Prepare container image build.">
<!-- Prepare container image build. -->
<target name="docker_build_prepare" depends="dist_server_binary">
<mkdir dir="${build_docker}" />
<copy file="${buildtools_docker}/Dockerfile" todir="${build_docker}" />
<copy file="${buildtools_docker}/.dockerignore" todir="${build_docker}" />
@ -404,7 +414,8 @@
</target> <!-- docker_build_prepare -->
<target name="docker_build" depends="docker_build_prepare" description="Build container image.">
<!-- Build container image. -->
<target name="docker_build" depends="docker_build_prepare">
<exec dir="${build_docker}" executable="docker">
<arg value="build" />
<arg value="-t" />
@ -414,28 +425,57 @@
</target> <!-- docker_build -->
<target name="docker_push" description="Push container image."/>
<!-- Push container image. -->
<target name="docker_push" />
<target name="server_run" depends="server_build,compilejs" description="Run server.">
<mkdir dir="run_srvr"/>
<!-- Run server. -->
<target name="server_run" depends="server_build,compilejs">
<mkdir dir="run_srvr/libs"/>
<mkdir dir="run_srvr/js"/>
<copy todir="run_srvr">
<fileset file="build/lib/${server_jarname}"/>
<fileset file="build/lib/${xmlconf_jarname}"/>
<fileset file="build/lib/${maps_jarname}"/>
<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>
<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>
</copy>
<chmod file="run_srvr/${server_jarname}" perm="+x"/>
<exec dir="." executable="java">
<arg line="-Xmx256m -cp run_srvr -jar run_srvr/${server_jarname} -c server.ini -l"/>
</exec>
<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>
<!-- we've run the program, so delete the jars -->
<delete dir="run_srvr"/>
@ -446,7 +486,8 @@
<!-- START: java client -->
<target name="client_build" depends="init,build_client_data" description="Compile client JAR file.">
<!-- Compile client JAR file. -->
<target name="client_build" depends="init,build_client_data">
<mkdir dir="${build_client}"/>
<copy todir="${build_client}/games/stendhal/client">
@ -539,7 +580,8 @@
</target> <!-- client_build -->
<target name="compile_stendhaltextclient" description="Compile text client.">
<!-- Compile text client. -->
<target name="compile_textclient">
<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">
@ -560,8 +602,12 @@
</javac>
</target> <!-- compile_stendhaltextclient -->
<!-- Alias of `compile_textclient`. -->
<target name="compile_stendhaltextclient" depends="compile_textclient" />
<target name="dist_client_binary" depends="clean_client_data,client_build" description="Create Java client package for release and distribution.">
<!-- Create Java client package for release and distribution. -->
<target name="dist_client_binary" depends="clean_client_data,client_build">
<mkdir dir="${buildroot}/stendhal-${version}"/>
<mkdir dir="${buildroot}/stendhal-${version}/lib"/>
<mkdir dir="${buildroot}/stendhal-${version}/log"/>
@ -597,8 +643,8 @@
</target> <!-- dist_client_binary -->
<!-- XXX: is this still used? -->
<target name="dist_client_starter" depends="client_build" description="Create Java client starter package.">
<!-- Create Java client starter package (obsolete). -->
<target name="dist_client_starter" depends="client_build">
<mkdir dir="${buildroot}/stendhal-${version}"/>
<copy todir="${buildroot}/stendhal-${version}" file="${lib}/${client_starter_jarname}"/>
<copy todir="${buildroot}/stendhal-${version}/lib">
@ -620,7 +666,8 @@
</target> <!-- dist_client_starter -->
<target name="client_run" depends="client_build" description="Run Java client.">
<!-- Run Java client. -->
<target name="client_run" depends="client_build">
<mkdir dir="run_clnt"/>
<mkdir dir="run_clnt/lib"/>
@ -656,8 +703,9 @@
</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" description="Run client for NetBeans trick."/>
<target name="run" depends="client_run" />
<!-- END: java client -->
@ -665,30 +713,42 @@
<!-- START: web client -->
<target name="js-prepare-marauroa">
<mkdir dir="build/js"/>
<unzip dest="build" src="libs/marauroa.jar">
<mkdir dir="node_modules/marauroa"/>
<unzip dest="node_modules/marauroa" src="libs/marauroa.jar">
<mapper type="flatten"/>
<patternset>
<include name="js/**"/>
</patternset>
</unzip>
</target>
<target name="js-prepare-build">
<echo file="build/js/build.js">
var stendhal = window.stendhal = window.stendhal || {};
stendhal.data = stendhal.data || {};
<mkdir dir="node_modules/marauroa"/>
<echo file="node_modules/marauroa/build.js">
export function version(marauroa, stendhal) {
stendhal.data = stendhal.data || {};
stendhal.data.build = {
version: "${version}",
build: "${datetime}",
dist: "webclient"
}
stendhal.data.build = {
version: "${version}",
build: "${datetime}",
dist: "webclient"
}
//CI
}
</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-marauroa,js-prepare-build,js-prepare-node,js-prepare-contribs"/>
<extension-point name="js-prepare" depends="js-prepare-node,js-prepare-contribs,js-prepare-marauroa,js-prepare-build"/>
<target name="js-prepare-node">
@ -713,72 +773,19 @@
<!-- 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>
<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>
<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>
<antcall target="compilejs-hook" />
@ -795,7 +802,8 @@
</target>
<target name="compilejs" depends="cleanjs" description="Compile web client.">
<!-- Compile web client. -->
<target name="compilejs" depends="cleanjs">
<antcall target="js-prepare" unless:set="exclude.webclient"/>
<antcall target="js-invoke-compiler" unless:set="exclude.webclient"/>
@ -804,7 +812,8 @@
</target> <!-- compilejs -->
<target name="webclient_build" depends="compilejs,clean_client_data,build_client_data" description="Compile and stage web client files.">
<!-- Compile and stage web client files. -->
<target name="webclient_build" depends="compilejs,clean_client_data,build_client_data">
<mkdir dir="${build_webclient}"/>
<copy todir="${build_webclient}">
@ -826,10 +835,8 @@
</target> <!-- 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.">
<!-- Create web client package for release and distribution. -->
<target name="dist_webclient_binary" depends="webclient_build">
<zip destfile="${buildroot}/stendhal-webclient-${version}.zip">
<zipfileset dir="${build_client_data}"/>
<zipfileset dir="${build_client_music_data}"/>

View file

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

View file

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

View file

@ -2,7 +2,8 @@
<project xmlns:if="ant:if" xmlns:unless="ant:unless">
<target name="init" description="Update version and resource info.">
<!-- Update version and resource info. -->
<target name="init">
<mkdir dir="${lib}"/>
<!-- update version -->
@ -89,7 +90,8 @@
</target> <!-- init -->
<target name="checkxml" description="Verify XML files are well-formed.">
<!-- Verify XML files are well-formed. -->
<target name="checkxml">
<schemavalidate failonerror="yes" lenient="yes" warn="yes">
<schema namespace="stendhal" file="${config_dir}/zones.xsd"/>
<fileset dir="${config_dir}/zones">
@ -167,7 +169,8 @@
</target> <!-- checkxml -->
<target name="internalUpdatePrereleaseVersion" if="PRE_RELEASE" description="Add date to pre-release test client.">
<!-- Add date to pre-release test client. -->
<target name="internalUpdatePrereleaseVersion" if="PRE_RELEASE">
<tstamp prefix="date">
<format property="iso" pattern="yyyy-MM-dd"/>
</tstamp>

View file

@ -4,7 +4,8 @@
<import file="tools.xml" />
<target name="rendermaps" description="Convert TMX maps to PNG images scaled 1:16." depends="compile_stendhaltools">
<!-- Convert TMX maps to PNG images scaled 1:16. -->
<target name="rendermaps" depends="compile_stendhaltools">
<taskdef name="maprenderer" classname="games.stendhal.tools.MapRenderer">
<classpath>
<pathelement path="${build_stendhaltools}"/>
@ -23,7 +24,8 @@
</target> <!-- rendermaps -->
<target name="renderatlas" description="Generate files for the Stendhal Atlas." depends="compile_stendhaltools">
<!-- Generate files for the Stendhal Atlas. -->
<target name="renderatlas" depends="compile_stendhaltools">
<taskdef name="maprenderer" classname="games.stendhal.tools.MapRenderer">
<classpath>
<pathelement path="${build_stendhaltools}"/>
@ -58,7 +60,8 @@
</target> <!-- renderatlas -->
<target name="renderworldmap" description="Export world.tmx map to PNG image." depends="rendermaps">
<!-- Export world.tmx map to PNG image. -->
<target name="renderworldmap" 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 -->
@ -85,7 +88,8 @@
</target> <!-- renderworldmap -->
<target name="updatemaps" description="Load and save TMX maps to fix tilesets changes." depends="compile_stendhaltools">
<!-- Load and save TMX maps to fix tilesets changes. -->
<target name="updatemaps" depends="compile_stendhaltools">
<taskdef name="mapupdater" classname="games.stendhal.tools.MapUpdater">
<classpath>
<pathelement path="${build_stendhaltools}"/>

View file

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

View file

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

View file

@ -4,7 +4,8 @@
<import file="dist.xml" />
<target name="compile_stendhaltools" description="Compile Stendhal build tools.">
<!-- Compile Stendhal build tools. -->
<target name="compile_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">
@ -28,7 +29,11 @@
</javac>
</target> <!-- compile_stendhaltools -->
<target name="clean_whitespace" description="Clean whitespace in source and other specified file types." depends="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">
<!-- process main Java source, config, & scripting files -->
<java classname="games.stendhal.tools.CleanWhitespace">
<classpath>
@ -55,16 +60,22 @@
<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 -->
<target name="cleanwhitespace" depends="clean_whitespace" description="Alias of clean_whitespace."/>
<target name="postman" description="Build postman bot.">
<!-- Build postman bot. -->
<target name="postman">
<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"/>
@ -100,7 +111,8 @@
</target> <!-- postman -->
<target name="signupdates" description="Sign updates." depends="compile_stendhaltools,jardiff" unless="exclude.signing">
<!-- Sign updates. -->
<target name="signupdates" depends="compile_stendhaltools,jardiff" unless="exclude.signing">
<taskdef name="updatesigner" classname="games.stendhal.tools.updateprop.UpdateSigner">
<classpath>
<pathelement path="."/>
@ -116,7 +128,8 @@
</target> <!-- signupdates -->
<target name="jardiff" depends="client_build" unless="exclude.jardiff" description="Generate release changes info.">
<!-- Generate release changes info. -->
<target name="jardiff" depends="client_build" unless="exclude.jardiff">
<echo>Creating update from ${version.old} to ${version}</echo>
<delete>
<fileset dir="${build-archive}">
@ -160,8 +173,9 @@
</target> <!-- jardiff -->
<!-- Run enemy balancer. -->
<!-- USAGE: ant balance -Dcreature="<creature_name>" -->
<target name="balance" description="Run enemy balancer." depends="compile_stendhaltools,server_build">
<target name="balance" 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.48.5 Reference Documentation
site_name: Stendhal 1.49.5 Reference Documentation
site_description: A fun, friendly, & free multiplayer online adventure game.
copyright: Copyright © 2003-2025 Stendhal
copyright: Copyright © 2003-2026 Stendhal
site_url: https://stendhalgame.org/
nav:
- Index: /reference/

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1097,6 +1097,7 @@ venom gland=
You see the venom gland of a giant king cobra. It is filled with deadly toxin.=
pelt=
You see a pelt of animal skin. It's used to make clothes and leather armor.=
leather thread=
You see a strand of sturdy leather thread.=
@ -1265,6 +1266,9 @@ You see an imperial ring. It is imbued with the strength of a king.=
enhanced imperial ring=
You see an enhanced imperial ring. It eminates the auras of strength and defense.=
ring of clarity=
You see a ring of clarity. You feel more aware while wearing it.=
## scrolls
@ -1407,6 +1411,9 @@ You see a silvery gm token. Does it bring you pride?=
bronze gm token=
You see a bronze gm token. Does it bring you pride?=
four-leaf clover=
You see a lucky four-leaf clover. What are the chances of finding one!?=
## swords
@ -1635,6 +1642,7 @@ You see a killer bat which will suck your blood!=
giant bat=
giant killer bat=
You see a giant killer bat. As if small wasn't pesky enough, these giant ones are capable of taking down anyone who dares come before them.=
piglet=
You see a piglet, a bit dirty.=
@ -1702,6 +1710,7 @@ black bear=
You see a strong black bear which hibernates in winter.=
polar bear=
You see a polar bear which lives in the icy desert. Beware! It can take you down with its powerful claws!=
## animal_aquatic
@ -2268,6 +2277,7 @@ cannibal=
You see a cannibal. He hunts you for cooking and eating your fresh meat! It's useless to tell him that your flesh is not good, better fight him or run away!=
elder cannibal=
You see an elder cannibal. For years it has eaten humans. It will not stop until its hunger is satisfied.=
cannibal warrior=
You see a cannibal warrior.=

View file

@ -12,7 +12,7 @@ Changelog
*bug fixes*
- fixed invalid head outfit index of Mr Ross
- fixed silent NPC fixed paths
- fixed silent NPC paths
- fixed desktop webview client window freezing on Windows when closed
- fixed nativehelper extension process not exiting cleanly on Windows
- set User-Agent string for Wikipedia API requests

Binary file not shown.

755
package-lock.json generated
View file

@ -1,21 +1,736 @@
{
"name": "stendhal",
"version": "1.0.0",
"lockfileVersion": 2,
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "stendhal",
"version": "1.0.0",
"license": "AGPL-3.0-or-later",
"devDependencies": {
"esbuild": "0.28.1",
"showdown": "^2.1.0",
"typedoc": "^0.28.15",
"typescript": "^5.9.3"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
"integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
"integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
"integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
"integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
"integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
"integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
"integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
"integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
"integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
"integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
"integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
"integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
"integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
"integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
"integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
"integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
"integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
"integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
"integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
"integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
"integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
"integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
"integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
"integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
"integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
"integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@gerrit0/mini-shiki": {
"version": "3.20.0",
"resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.20.0.tgz",
"integrity": "sha512-Wa57i+bMpK6PGJZ1f2myxo3iO+K/kZikcyvH8NIqNNZhQUbDav7V9LQmWOXhf946mz5c1NZ19WMsGYiDKTryzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"typescript": "^5.1.0"
"@shikijs/engine-oniguruma": "^3.20.0",
"@shikijs/langs": "^3.20.0",
"@shikijs/themes": "^3.20.0",
"@shikijs/types": "^3.20.0",
"@shikijs/vscode-textmate": "^10.0.2"
}
},
"node_modules/@shikijs/engine-oniguruma": {
"version": "3.20.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.20.0.tgz",
"integrity": "sha512-Yx3gy7xLzM0ZOjqoxciHjA7dAt5tyzJE3L4uQoM83agahy+PlW244XJSrmJRSBvGYELDhYXPacD4R/cauV5bzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.20.0",
"@shikijs/vscode-textmate": "^10.0.2"
}
},
"node_modules/@shikijs/langs": {
"version": "3.20.0",
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.20.0.tgz",
"integrity": "sha512-le+bssCxcSHrygCWuOrYJHvjus6zhQ2K7q/0mgjiffRbkhM4o1EWu2m+29l0yEsHDbWaWPNnDUTRVVBvBBeKaA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.20.0"
}
},
"node_modules/@shikijs/themes": {
"version": "3.20.0",
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.20.0.tgz",
"integrity": "sha512-U1NSU7Sl26Q7ErRvJUouArxfM2euWqq1xaSrbqMu2iqa+tSp0D1Yah8216sDYbdDHw4C8b75UpE65eWorm2erQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.20.0"
}
},
"node_modules/@shikijs/types": {
"version": "3.20.0",
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.20.0.tgz",
"integrity": "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
}
},
"node_modules/@shikijs/vscode-textmate": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
"integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/hast": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/unist": "*"
}
},
"node_modules/@types/unist": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
"dev": true,
"license": "MIT"
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"node_modules/balanced-match": {
"version": "1.0.2",
"dev": true,
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/commander": {
"version": "9.5.0",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || >=14"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/esbuild": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
"integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.28.1",
"@esbuild/android-arm": "0.28.1",
"@esbuild/android-arm64": "0.28.1",
"@esbuild/android-x64": "0.28.1",
"@esbuild/darwin-arm64": "0.28.1",
"@esbuild/darwin-x64": "0.28.1",
"@esbuild/freebsd-arm64": "0.28.1",
"@esbuild/freebsd-x64": "0.28.1",
"@esbuild/linux-arm": "0.28.1",
"@esbuild/linux-arm64": "0.28.1",
"@esbuild/linux-ia32": "0.28.1",
"@esbuild/linux-loong64": "0.28.1",
"@esbuild/linux-mips64el": "0.28.1",
"@esbuild/linux-ppc64": "0.28.1",
"@esbuild/linux-riscv64": "0.28.1",
"@esbuild/linux-s390x": "0.28.1",
"@esbuild/linux-x64": "0.28.1",
"@esbuild/netbsd-arm64": "0.28.1",
"@esbuild/netbsd-x64": "0.28.1",
"@esbuild/openbsd-arm64": "0.28.1",
"@esbuild/openbsd-x64": "0.28.1",
"@esbuild/openharmony-arm64": "0.28.1",
"@esbuild/sunos-x64": "0.28.1",
"@esbuild/win32-arm64": "0.28.1",
"@esbuild/win32-ia32": "0.28.1",
"@esbuild/win32-x64": "0.28.1"
}
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/lunr": {
"version": "2.3.9",
"dev": true,
"license": "MIT"
},
"node_modules/markdown-it": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"dev": true,
"license": "MIT"
},
"node_modules/minimatch": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/showdown": {
"version": "2.1.0",
"dev": true,
"license": "MIT",
"dependencies": {
"commander": "^9.0.0"
},
"bin": {
"showdown": "bin/showdown.js"
},
"funding": {
"type": "individual",
"url": "https://www.paypal.me/tiviesantos"
}
},
"node_modules/typedoc": {
"version": "0.28.15",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.15.tgz",
"integrity": "sha512-mw2/2vTL7MlT+BVo43lOsufkkd2CJO4zeOSuWQQsiXoV2VuEn7f6IZp2jsUDPmBMABpgR0R5jlcJ2OGEFYmkyg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@gerrit0/mini-shiki": "^3.17.0",
"lunr": "^2.3.9",
"markdown-it": "^14.1.0",
"minimatch": "^9.0.5",
"yaml": "^2.8.1"
},
"bin": {
"typedoc": "bin/typedoc"
},
"engines": {
"node": ">= 18",
"pnpm": ">= 10"
},
"peerDependencies": {
"typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x"
}
},
"node_modules/typescript": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -23,13 +738,29 @@
"engines": {
"node": ">=14.17"
}
}
},
"dependencies": {
"typescript": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w=="
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"dev": true,
"license": "MIT"
},
"node_modules/yaml": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
"dev": true,
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
},
"funding": {
"url": "https://github.com/sponsors/eemeli"
}
}
}
}

View file

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

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.48.5 (https://arianne-project.org/contact.html)");
connection.setRequestProperty("User-Agent", "StendhalClient/1.49.5 (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.48.5";
public static final String VERSION = "1.49.5";
/** pre release suffix */
// Note: This line is updated by build.xml using a regexp so be sure to adjust it in case you modify this line.

View file

@ -335,7 +335,7 @@ div.logcolR {
background: url(/data/gui/danger.png) repeat;
}
.qm-button, img#held-object {
.qm-button, canvas#held-object {
position: absolute;
display: none;
opacity: 0.5;
@ -708,3 +708,16 @@ img#click-indicator {
.pad-contents {
padding: 0.5em;
}
table.shoptable {
border-collapse: collapse;
}
table.shoptable th, table.shoptable td {
border: 1px solid var(--text-color);
padding: 2px;
}
td.number {
text-align: right;
}

File diff suppressed because it is too large Load diff

View file

@ -486,14 +486,11 @@
<div class="shortcuts-dialog"></div>
</template>
<img id="held-object" class="hidden unselectable notarget" draggable="false">
<canvas id="held-object" class="unselectable notarget" draggable="false"></canvas>
<!-- 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-2024 - Faiumoni e. V. *
* Copyright © 2023-2026 - Faiumoni e. V. *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
@ -9,25 +9,21 @@
* *
***************************************************************************/
declare var marauroa: any;
declare var stendhal: any;
import { marauroa, Deserializer, RPObject } from "marauroa"
import { stendhal } from "./stendhal";
import { PerceptionListener } from "./PerceptionListener";
import { StendhalPerceptionListener } from "./PerceptionListener";
import { singletons } from "./SingletonRepo";
import { Paths } from "./data/Paths";
import { Color } from "./data/color/Color";
import { Ground } from "./entity/Ground";
import { RPObject } from "./entity/RPObject";
import { EntityRegistry } from "./entity/EntityRegistry";
import { ui } from "./ui/UI";
import { UIComponentEnum } from "./ui/UIComponentEnum";
import { BuddyListComponent } from "./ui/component/BuddyListComponent";
import { MiniMapComponent } from "./ui/component/MiniMapComponent";
import { PlayerEquipmentComponent } from "./ui/component/PlayerEquipmentComponent";
import { ZoneInfoComponent } from "./ui/component/ZoneInfoComponent";
import { ChooseCharacterDialog } from "./ui/dialog/ChooseCharacterDialog";
@ -40,6 +36,12 @@ import { SingletonFloatingWindow } from "./ui/toolkit/SingletonFloatingWindow";
import { Chat } from "./util/Chat";
import { DialogHandler } from "./util/DialogHandler";
import { Globals } from "./util/Globals";
import { TileMap } from "data/TileMap";
import { OutfitStore } from "data/OutfitStore";
import { TileStore } from "data/TileStore";
import { WeatherRenderer } from "util/WeatherRenderer";
import { ViewPort } from "ui/ViewPort";
import { htmlImageStore } from "data/HTMLImageStore";
/**
@ -62,6 +64,7 @@ export class Client {
/** Singleton instance. */
private static instance: Client;
public loaded = false;
/**
@ -78,7 +81,7 @@ export class Client {
* Hidden singleton constructor.
*/
private constructor() {
// do nothing
// empty
}
/**
@ -91,19 +94,18 @@ export class Client {
}
this.initialized = true;
// add version & build info to DOM for retrieval by browser
document.documentElement.setAttribute("data-build-version", stendhal.data.build.version);
document.documentElement.setAttribute("data-build-build", stendhal.data.build.build);
stendhal.paths = singletons.getPaths();
stendhal.config = singletons.getConfigManager();
stendhal.session = singletons.getSessionManager();
stendhal.actions = singletons.getSlashActionRepo();
new EntityRegistry().init();
this.initData();
this.initSound();
this.initUI();
this.initZone();
// add version & build info to DOM for retrieval by browser
document.documentElement.setAttribute("data-build-version", stendhal.data.build?.version);
document.documentElement.setAttribute("data-build-build", stendhal.data.build?.build);
}
/**
@ -117,9 +119,6 @@ export class Client {
stendhal.data.cstatus = singletons.getCStatus();
stendhal.data.cstatus.init();
stendhal.data.group = singletons.getGroupManager();
stendhal.data.outfit = singletons.getOutfitStore();
stendhal.data.sprites = singletons.getSpriteStore();
stendhal.data.map = singletons.getMap();
// online players
stendhal.players = [];
}
@ -141,22 +140,9 @@ export class Client {
private initUI() {
stendhal.ui = stendhal.ui || {};
stendhal.ui.equip = singletons.getInventory();
stendhal.ui.html = singletons.getHTMLManager();
stendhal.ui.touch = singletons.getTouchHandler();
stendhal.ui.viewport = singletons.getViewPort();
// alias for backward-compat until changed in all source
stendhal.ui.gamewindow = stendhal.ui.viewport;
stendhal.ui.getMenuStyle = Globals.getMenuStyle;
}
/**
* Builds initial zone for user to enter world.
*/
private initZone() {
stendhal.zone = singletons.getZone();
stendhal.zone.ground = new Ground();
}
/**
* Main startup routines.
@ -177,11 +163,11 @@ export class Client {
singletons.getEventRegistry().init();
// initialize tileset animation data
singletons.getTileStore().init();
TileStore.get().init();
// initialize emoji data
singletons.getEmojiStore().init();
// initialize outfit data
stendhal.data.outfit.init();
OutfitStore.get().init();
new DesktopUserInterfaceFactory().create();
@ -204,11 +190,11 @@ export class Client {
stendhal.ui.globalInternalWindow = new DialogHandler();
// pre-cache images & sounds
stendhal.data.sprites.startupCache();
htmlImageStore.startupCache();
stendhal.sound.startupCache();
if (document.getElementById("viewport")) {
stendhal.ui.gamewindow.draw.apply(stendhal.ui.gamewindow, arguments);
ViewPort.get().draw();
}
}
@ -259,11 +245,14 @@ export class Client {
* Registers Marauroa event handlers.
*/
registerMarauroaEventHandlers() {
marauroa.clientFramework.onDisconnect = function(_reason: string, _error: string) {
marauroa.clientFramework.onDisconnect = function(_reason: string, _code: number, _wasClean: boolean) {
if (!Client.instance.unloading) {
Chat.logH("error", "Disconnected from server.");
if (window.location.hostname !== "localhost" && window.location.hostname !== "127.0.0.1" && window.location.hostname !== "::1") {
window.location.href = "/account/mycharacters.html";
}
}
};
}.bind(this);
marauroa.clientFramework.onLoginRequired = function(config: Record<string, string>) {
if (config["client_login_url"]) {
@ -281,25 +270,25 @@ export class Client {
"Login",
new LoginDialog(),
100, 50).enableCloseButton(false);
};
}.bind(this);
marauroa.clientFramework.onCreateAccountAck = function(username: string) {
// TODO: We should login automatically
alert("Account succesfully created, please login.");
window.location.reload();
};
}.bind(this);
marauroa.clientFramework.onCreateCharacterAck = function(charname: string, _template: any) {
// Client.get().chooseCharacter(charname);
};
}.bind(this);
marauroa.clientFramework.onLoginFailed = function(_reason: string, _text: string) {
alert("Login failed. " + _text);
// TODO: Server closes the connection, so we need to open a new one
window.location.reload();
};
}.bind(this);
marauroa.clientFramework.onAvailableCharacterDetails = function(characters: {[key: string]: RPObject}) {
marauroa.clientFramework.onAvailableCharacterDetails = (characters: {[key: string]: RPObject}) => {
SingletonFloatingWindow.closeAll();
if (!Object.keys(characters).length && this.username) {
marauroa.clientFramework.createCharacter(this.username, {});
@ -331,9 +320,9 @@ export class Client {
}
items[i]["ack"] = true;
}
};
}.bind(this);
marauroa.clientFramework.onTransfer = function(items: any) {
marauroa.clientFramework.onTransfer = (items: any) => {
var data = {} as any;
var zoneName = ""
for (var i in items) {
@ -345,34 +334,13 @@ export class Client {
this.onDataMap(items[i]["data"]);
}
}
stendhal.data.map.onTransfer(zoneName, data);
TileMap.get().onTransfer(zoneName, data);
};
// update user interface on perceptions
if (document.getElementById("viewport")) {
// override perception listener
marauroa.perceptionListener = new PerceptionListener(marauroa.perceptionListener);
marauroa.perceptionListener.onPerceptionEnd = function(_type: Int8Array, _timestamp: number) {
stendhal.zone.sortEntities();
(ui.get(UIComponentEnum.MiniMap) as MiniMapComponent).draw();
(ui.get(UIComponentEnum.BuddyList) as BuddyListComponent).update();
stendhal.ui.equip.update();
(ui.get(UIComponentEnum.PlayerEquipment) as PlayerEquipmentComponent).update();
if (!this.loaded) {
this.loaded = true;
// delay visibile change of client a little to allow for initialisation in the background for a smoother experience
window.setTimeout(function() {
let body = document.getElementById("body")!;
body.style.cursor = "auto";
document.getElementById("client")!.style.display = "block";
document.getElementById("loginpopup")!.style.display = "none";
// initialize observer after UI is ready
singletons.getUIUpdateObserver().init();
ui.onDisplayReady();
}, 300);
}
}
marauroa.perceptionListener = new StendhalPerceptionListener();
}
}
@ -420,18 +388,19 @@ export class Client {
document.getElementById("body")!.addEventListener("mouseenter", stendhal.main.onMouseEnter);
var gamewindow = document.getElementById("viewport")!;
let gamewindow = document.getElementById("viewport")!;
let viewPort = ViewPort.get();
gamewindow.setAttribute("draggable", "true");
gamewindow.addEventListener("mousedown", stendhal.ui.gamewindow.onMouseDown);
gamewindow.addEventListener("dblclick", stendhal.ui.gamewindow.onMouseDown);
gamewindow.addEventListener("dragstart", stendhal.ui.gamewindow.onDragStart);
gamewindow.addEventListener("mousemove", stendhal.ui.gamewindow.onMouseMove);
gamewindow.addEventListener("touchstart", stendhal.ui.gamewindow.onMouseDown);
gamewindow.addEventListener("touchend", stendhal.ui.gamewindow.onTouchEnd);
gamewindow.addEventListener("dragover", stendhal.ui.gamewindow.onDragOver);
gamewindow.addEventListener("drop", stendhal.ui.gamewindow.onDrop);
gamewindow.addEventListener("contextmenu", stendhal.ui.gamewindow.onContentMenu);
gamewindow.addEventListener("wheel", stendhal.ui.gamewindow.onMouseWheel);
gamewindow.addEventListener("mousedown", viewPort.onMouseDown);
gamewindow.addEventListener("dblclick", viewPort.onMouseDown);
gamewindow.addEventListener("dragstart", viewPort.onDragStart);
gamewindow.addEventListener("mousemove", viewPort.onMouseMove);
gamewindow.addEventListener("touchstart", viewPort.onMouseDown, {passive: true});
gamewindow.addEventListener("touchend", viewPort.onTouchEnd);
gamewindow.addEventListener("dragover", viewPort.onDragOver);
gamewindow.addEventListener("drop", viewPort.onDrop);
gamewindow.addEventListener("contextmenu", viewPort.onContentMenu);
gamewindow.addEventListener("wheel", viewPort.onMouseWheel, {passive: true});
singletons.getJoystickController().registerGlobalEventHandlers();
@ -459,7 +428,7 @@ export class Client {
document.addEventListener("click", Client.handleClickIndicator);
document.addEventListener("touchend", Client.handleClickIndicator);
};
click_indicator.src = stendhal.paths.gui + "/click_indicator.png";
click_indicator.src = Paths.gui + "/click_indicator.png";
}
/**
@ -469,8 +438,10 @@ export class Client {
* Information about map.
*/
onDataMap(data: any) {
let map = TileMap.get();
let viewPort = ViewPort.get();
var zoneinfo = {} as {[key: string]: string};
var deserializer = marauroa.Deserializer.fromBase64(data);
var deserializer = Deserializer.fromBase64(data);
deserializer.readAttributes(zoneinfo);
(ui.get(UIComponentEnum.ZoneInfo) as ZoneInfoComponent).zoneChange(zoneinfo);
@ -481,29 +452,29 @@ export class Client {
// parallax background
if (stendhal.config.getBoolean("effect.parallax")) {
stendhal.data.map.setParallax(zoneinfo["parallax"]);
stendhal.data.map.setIgnoredTiles(zoneinfo["parallax_ignore_tiles"]);
map.setParallax(zoneinfo["parallax"]);
map.setIgnoredTiles(zoneinfo["parallax_ignore_tiles"]);
}
// coloring information
if (zoneinfo["color"] && stendhal.config.getBoolean("effect.lighting")) {
if (zoneinfo["color_method"]) {
stendhal.ui.gamewindow.setColorMethod(zoneinfo["color_method"]);
viewPort.setColorMethod(zoneinfo["color_method"]);
}
if (zoneinfo["blend_method"]) {
stendhal.ui.gamewindow.setBlendMethod(zoneinfo["blend_method"]);
viewPort.setBlendMethod(zoneinfo["blend_method"]);
}
const hsl = Color.numToHSL(Number(zoneinfo["color"]));
stendhal.ui.gamewindow.HSLFilter = hsl.toString();
viewPort.HSLFilter = hsl.toString();
// deprecated
stendhal.ui.gamewindow.filter = "hue-rotate(" + hsl.H + "deg) saturate(" + hsl.S
viewPort.filter = "hue-rotate(" + hsl.H + "deg) saturate(" + hsl.S
+ ") brightness(" + hsl.L + ")";
} else {
stendhal.ui.gamewindow.HSLFilter = undefined;
stendhal.ui.gamewindow.filter = undefined;
viewPort.HSLFilter = undefined;
viewPort.filter = undefined;
}
singletons.getWeatherRenderer().update(zoneinfo["weather"]);
WeatherRenderer.get().update(zoneinfo["weather"]);
}
/**
@ -518,7 +489,7 @@ export class Client {
*/
onMouseEnter(e: MouseEvent) {
// use Stendhal's built-in cursor for entire page
(e.target as HTMLElement).style.cursor = "url(" + stendhal.paths.sprites + "/cursor/normal.png) 1 3, auto";
(e.target as HTMLElement).style.cursor = "url(" + Paths.sprites + "/cursor/normal.png) 1 3, auto";
}
/**

View file

@ -1,310 +0,0 @@
/***************************************************************************
* (C) Copyright 2003-2024 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation; either version 3 of the *
* License, or (at your option) any later version. *
* *
***************************************************************************/
declare var marauroa: any;
declare var stendhal: any;
import { RPEntity } from "./entity/RPEntity";
import { RPObject } from "./entity/RPObject";
import { BestiaryEvent } from "./event/BestiaryEvent";
import { ChatOptionsEvent } from "./event/ChatOptionsEvent";
import { ExamineEvent } from "./event/ExamineEvent";
import { GroupChangeEvent } from "./event/GroupChangeEvent";
import { GroupInviteEvent } from "./event/GroupInviteEvent";
import { PlayerLoggedOnEvent } from "./event/PlayerLoggedOnEvent";
import { PlayerLoggedOutEvent } from "./event/PlayerLoggedOutEvent";
import { ProgressStatusEvent } from "./event/ProgressStatusEvent";
import { RPEvent } from "./event/RPEvent";
import { SoundEvent } from "./event/SoundEvent";
import { TradeEvent } from "./event/TradeEvent";
import { ViewChangeEvent } from "./event/ViewChangeEvent";
import { ui } from "./ui/UI";
import { DialogContentComponent } from "./ui/toolkit/DialogContentComponent";
import { Chat } from "./util/Chat";
export class EventRegistry {
private initialized = false;
/** Singleton instance. */
private static instance: EventRegistry;
/**
* Retrieves singleton instance.
*/
static get(): EventRegistry {
if (!EventRegistry.instance) {
EventRegistry.instance = new EventRegistry();
}
return EventRegistry.instance;
}
/**
* Hidden singleton constructor.
*/
private constructor() {
// do nothing
}
/**
* Registers standard events.
*/
init() {
if (this.initialized) {
console.warn("tried to re-initialize EventRegistry");
return;
}
this.initialized = true;
this.register("chat_options", new ChatOptionsEvent());
this.register("examine", new ExamineEvent());
this.register("group_change_event", new GroupChangeEvent());
this.register("group_invite_event", new GroupInviteEvent());
this.register("progress_status_event", new ProgressStatusEvent());
this.register("trade_state_change_event", new TradeEvent());
this.register("attack", {
execute: function(entity: RPEntity) {
var target = entity.getAttackTarget();
if (!target) {
return;
}
if (this.hasOwnProperty("hit")) {
var damage = parseInt(this["damage"], 10);
if (damage !== 0) {
target.onDamaged(entity, damage);
} else {
target.onBlocked(entity);
}
} else {
target.onMissed(entity);
}
entity.onAttackPerformed(parseInt(this["type"], 10), this.hasOwnProperty("ranged"), this["weapon"]);
}
}); // attack
this.register("bestiary", new BestiaryEvent());
this.register("global_visual_effect", {
execute: function(rpobject: RPObject) {
// TODO: new GlobalVisualEffectEvent();
}
}); // global_visual_effect
this.register("image_event", {
execute: function(rpobject: RPObject) {
// TODO: new ImageEffectEvent();
console.log("image_event", this, rpobject);
}
}); // image_event
this.register("player_logged_on", new PlayerLoggedOnEvent());
this.register("player_logged_out", new PlayerLoggedOutEvent());
this.register("private_text", {
soundTextEvents: {
"privmsg": true,
"support": true,
"tutorial": true
},
execute: function(rpobject: RPObject) {
const ttype = this["texttype"].toLowerCase();
const msg = this["text"].replace(/\\r\\n/g, "\n").replace(/\\r/g, "\n");
let profile;
if (this.hasOwnProperty("profile")) {
profile = this["profile"];
} else if (ttype === "tutorial") {
profile = "floattingladynpc";
}
if (ttype === "server" && msg.includes("\n")) {
Chat.log(ttype, msg.split("\n"), undefined, profile);
} else {
// scene settings messages should not disturb playing, just create some atmosphere
const headed = ttype !== "scene_setting";
Chat.log(ttype, msg, undefined, profile, headed);
}
// play notification sound
const notif = stendhal.config.get("chat.private.sound");
if (notif && this.soundTextEvents[ttype]) {
stendhal.sound.playGlobalizedEffect(notif);
}
}
}); // private_text
this.register("reached_achievement", {
execute: function(rpobject: RPObject) {
stendhal.ui.gamewindow.addAchievementNotif(this["category"], this["title"], this["description"]);
}
}); // reached_achievement
this.register("show_item_list", {
execute: function(rpobject: RPObject) {
let title = "Items";
let caption = "";
let items = [];
if (this.hasOwnProperty("title")) {
title = this["title"];
}
if (this.hasOwnProperty("caption")) {
caption = this["caption"];
}
if (this.hasOwnProperty("content")) {
for (var obj in this["content"]) {
if (this["content"].hasOwnProperty(obj)) {
var slotObj = this["content"][obj];
var data = this["content"][obj]["a"];
const i = {
clazz: data["class"],
subclass: data["subclass"],
img: data["class"] + "/" + data["subclass"] + ".png",
price: data["price"],
desc: data["description_info"]
}
// seller shops prefix prices with "-"
if (i.price.startsWith("-")) {
i.price = i.price.substr(1);
}
items.push(i);
}
}
}
const content = new class extends DialogContentComponent {} ("empty-div-template");
content.componentElement.classList.add("shopsign");
const captionElement = document.createElement("div");
captionElement.className = "horizontalgroup shopcaption";
captionElement.textContent = caption + "\nItem\t-\tPrice\t-\tDescription";
content.componentElement.appendChild(captionElement);
const itemList = document.createElement("div");
itemList.className = "shoplist";
content.componentElement.appendChild(itemList);
// TODO: organize in columns & show item sprites
for (const i of items) {
const row = document.createElement("div");
row.className = "horizontalgroup shoprow";
const img = document.createElement("div");
img.className = "shopcol";
img.appendChild(stendhal.data.sprites.get(stendhal.paths.sprites + "/items/" + i.img));
row.appendChild(img);
const price = document.createElement("div");
price.className = "shopcol";
price.textContent = i.price;
row.appendChild(price);
const desc = document.createElement("div");
desc.className = "shopcol shopcolr";
desc.textContent = i.desc;
row.appendChild(desc);
itemList.appendChild(row);
}
stendhal.ui.globalInternalWindow.set(
ui.createSingletonFloatingWindow(title, content, 20, 20));
}
}); // show_item_list
this.register("show_outfit_list", {
execute: function(rpobject: RPObject) {
let title = "Outfits";
let caption = "";
let outfits = [];
if (this.hasOwnProperty("title")) {
title = this["title"];
}
if (this.hasOwnProperty("caption")) {
caption = this["caption"];
}
if (this.hasOwnProperty("outfits")) {
for (let o of this["outfits"].split(":")) {
o = o.split(";");
if (o.length > 2) {
outfits.push([o[0], o[1], o[2]]);
}
}
}
if (this.hasOwnProperty("show_base")) {
//Chat.log("normal", this["show_base"]);
}
const content = new class extends DialogContentComponent {} ("empty-div-template");
content.componentElement.classList.add("shopsign");
const captionElement = document.createElement("div");
captionElement.className = "horizontalgroup shopcaption";
captionElement.textContent = caption;
content.componentElement.appendChild(captionElement);
const itemList = document.createElement("div");
itemList.className = "shoplist";
content.componentElement.appendChild(itemList);
// TODO: organize in columns & show outfit sprites
for (const o of outfits) {
const row = document.createElement("div");
row.className = "horizontalgroup shoprow";
row.textContent = o[0] + ": " + o[2];
itemList.appendChild(row);
}
stendhal.ui.globalInternalWindow.set(
ui.createSingletonFloatingWindow(title, content, 20, 20));
}
}); // show_outfit_list
this.register("sound_event", new SoundEvent());
this.register("text", {
execute: function(entity: RPEntity) {
if (this.hasOwnProperty("range")) {
entity.say(this["text"], this["range"]);
} else {
entity.say(this["text"]);
}
}
}); // text
this.register("transition_graph", {
execute: function(rpobject: RPObject) {
// TODO: new TransitionGraphEvent();
}
}); // transition_graph
this.register("view_change", new ViewChangeEvent());
}
/**
* Registers a new event.
*
* @param _type
* String identifier.
* @param _event
* Event to be registered.
*/
register(_type: string, _event: any) {
if (_event instanceof RPEvent) {
marauroa.rpeventFactory[_type] = _event;
} else {
marauroa.rpeventFactory[_type] =
marauroa.util.fromProto(marauroa.rpeventFactory["_default"], _event);
}
}
}

View file

@ -9,29 +9,24 @@
* *
***************************************************************************/
declare var stendhal: any;
import { ui } from "ui/UI";
import { stendhal } from "./stendhal";
import { RPObject } from "./entity/RPObject";
import { RPObject, PerceptionListener } from "marauroa";
import { UIComponentEnum } from "ui/UIComponentEnum";
import { MiniMapComponent } from "ui/component/MiniMapComponent";
import { BuddyListComponent } from "ui/component/BuddyListComponent";
import { PlayerEquipmentComponent } from "ui/component/PlayerEquipmentComponent";
import { singletons } from "SingletonRepo";
import { Client } from "Client";
import { Zone } from "entity/Zone";
/**
* Class to override `marauroa.perceptionListener` & handle creating
* User object.
*/
export class PerceptionListener {
[key: string]: any;
private readonly _super: any;
constructor(perceptionListener: any) {
this._super = perceptionListener;
for (const prop of Object.getOwnPropertyNames(perceptionListener)) {
if (typeof(this[prop]) === "undefined") {
// members & methods that are not overridden by this class
this[prop] = perceptionListener[prop];
}
}
}
export class StendhalPerceptionListener extends PerceptionListener {
/**
* Action when an object is added to the world.
@ -42,7 +37,7 @@ export class PerceptionListener {
* `true` if object was added.
* @see marauroa.perceptionListener.onAdded
*/
onAdded(obj: RPObject): boolean {
override onAdded(obj: RPObject): boolean {
if (obj["c"] === "player") {
if (obj.hasOwnProperty("a")) {
if (obj["a"]["name"] === stendhal.session.getCharName()) {
@ -51,6 +46,28 @@ export class PerceptionListener {
}
}
}
return this._super.onAdded(obj);
return super.onAdded(obj);
}
override onPerceptionEnd(_type: Int8Array, _timestamp: number) {
Zone.get().sortEntities();
(ui.get(UIComponentEnum.MiniMap) as MiniMapComponent).draw();
(ui.get(UIComponentEnum.BuddyList) as BuddyListComponent).update();
stendhal.ui.equip.update();
(ui.get(UIComponentEnum.PlayerEquipment) as PlayerEquipmentComponent).update();
if (!Client.get().loaded) {
Client.get().loaded = true;
// delay visibile change of client a little to allow for initialisation in the background for a smoother experience
window.setTimeout(function() {
let body = document.getElementById("body")!;
body.style.cursor = "auto";
document.getElementById("client")!.style.display = "block";
document.getElementById("loginpopup")!.style.display = "none";
// initialize observer after UI is ready
singletons.getUIUpdateObserver().init();
ui.onDisplayReady();
}, 300);
}
}
}

View file

@ -1,5 +1,5 @@
/***************************************************************************
* Copyright © 2003-2024 - Faiumoni e. V. *
* Copyright © 2003-2026 - Faiumoni e. V. *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
@ -10,45 +10,35 @@
***************************************************************************/
import { Client } from "./Client";
import { EventRegistry } from "./EventRegistry";
import { EventRegistry } from "./event/EventRegistry";
import { SlashActionRepo } from "./SlashActionRepo";
import { Zone } from "./entity/Zone";
import { ConfigManager } from "./util/ConfigManager";
import { DownloadUtil } from "./util/DownloadUtil";
import { DrawingStage } from "./util/DrawingStage";
import { FacingHandler } from "./util/FacingHandler";
import { KeyHandler } from "./util/KeyHandler";
import { SessionManager } from "./util/SessionManager";
import { WeatherRenderer } from "./util/WeatherRenderer";
import { CStatus } from "./data/CStatus";
import { CacheManager } from "./data/CacheManager";
import { CStatus } from "./data/CStatus";
import { EmojiStore } from "./data/EmojiStore";
import { GroupManager } from "./data/GroupManager";
import { Map } from "./data/Map";
import { OutfitStore } from "./data/OutfitStore";
import { Paths } from "./data/Paths";
import { SpriteStore, store } from "./data/SpriteStore";
import { TileStore } from "./data/TileStore";
import { LoopedSoundSourceManager } from "./data/sound/LoopedSoundSourceManager";
import { SoundManager } from "./data/sound/SoundManager";
import { ui } from "./ui/UI";
import { UIComponentEnum } from "./ui/UIComponentEnum";
import { HeldObjectManager } from "./ui/HeldObject";
import { HTMLManager } from "./ui/HTMLManager";
import { Inventory } from "./ui/Inventory";
import { SoftwareJoystickController } from "./ui/SoftwareJoystickController";
import { TouchHandler } from "./ui/TouchHandler";
import { ui } from "./ui/UI";
import { UIComponentEnum } from "./ui/UIComponentEnum";
import { UIUpdateObserver } from "./ui/UIUpdateObserver";
import { ViewPort } from "./ui/ViewPort";
import { ChatInputComponent } from "./ui/component/ChatInputComponent";
/**
* @Deprecated use Class.get() directly to prevent dependency chain
*/
export class SingletonRepo {
static getCStatus(): CStatus {
@ -75,10 +65,6 @@ export class SingletonRepo {
return DownloadUtil;
}
static getDrawingStage(): DrawingStage {
return DrawingStage.get();
}
static getEmojiStore(): EmojiStore {
return EmojiStore.get();
}
@ -95,14 +81,6 @@ export class SingletonRepo {
return GroupManager.get();
}
static getHeldObjectManager(): HeldObjectManager {
return HeldObjectManager.get();
}
static getHTMLManager(): HTMLManager {
return HTMLManager.get();
}
static getInventory(): Inventory {
return Inventory.get();
}
@ -119,14 +97,6 @@ export class SingletonRepo {
return LoopedSoundSourceManager.get();
}
static getMap(): Map {
return Map.get();
}
static getOutfitStore(): OutfitStore {
return OutfitStore.get();
}
static getPaths(): typeof Paths {
return Paths;
}
@ -143,33 +113,10 @@ export class SingletonRepo {
return SoundManager.get();
}
static getSpriteStore(): SpriteStore {
return store;
}
static getTileStore(): TileStore {
return TileStore.get();
}
static getTouchHandler(): TouchHandler {
return TouchHandler.get();
}
static getUIUpdateObserver(): UIUpdateObserver {
return UIUpdateObserver.get();
}
static getViewPort(): ViewPort {
return ViewPort.get();
}
static getWeatherRenderer(): WeatherRenderer {
return WeatherRenderer.get();
}
static getZone(): Zone {
return Zone.get();
}
}
export const singletons = SingletonRepo;

View file

@ -9,8 +9,8 @@
* *
***************************************************************************/
declare var marauroa: any;
declare var stendhal: any;
import { marauroa } from "marauroa"
import { stendhal } from "./stendhal";
import { singletons } from "./SingletonRepo";
@ -766,7 +766,7 @@ export class SlashActionRepo {
execute: (type: string, params: string[], remainder: string): boolean => {
const newMode = !stendhal.config.getBoolean("input.doubleclick");
stendhal.config.set("input.doubleclick", newMode);
stendhal.ui.gamewindow.updateClickMode();
ViewPort.get().updateClickMode();
if (newMode) {
Chat.log("info", "Click mode is now set to double click.");

View file

@ -10,7 +10,6 @@
* *
***************************************************************************/
declare var stendhal: any;
import { ParamList } from "./ParamList";
import { SlashAction } from "./SlashAction";
@ -20,6 +19,7 @@ import { ui } from "../ui/UI";
import { AboutDialog } from "../ui/dialog/AboutDialog";
import { Pair } from "../util/Pair";
import { stendhal } from "../stendhal";
export class AboutAction extends SlashAction {
@ -28,7 +28,7 @@ export class AboutAction extends SlashAction {
execute(_type: string, _params: string[], _remainder: string): boolean {
const wstate = stendhal.config.getWindowState("about");
const offset = stendhal.ui.getPageOffset();
const offset = ui.getPageOffset();
const content = new AboutDialog();
const dialog = ui.createSingletonFloatingWindow(

View file

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

View file

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

View file

@ -10,7 +10,7 @@
* *
***************************************************************************/
declare var stendhal: any;
import { stendhal } from "../stendhal";
import { SlashAction } from "./SlashAction";
@ -28,7 +28,7 @@ export class SettingsAction extends SlashAction {
execute(_type: string, _params: string[], _remainder: string): boolean {
const wstate = stendhal.config.getWindowState("menu");
const offset = stendhal.ui.getPageOffset();
const offset = ui.getPageOffset();
const content = new SettingsDialog();
const dialog = ui.createSingletonFloatingWindow(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,290 @@
/***************************************************************************
* (C) Copyright 2003-2026 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation; either version 3 of the *
* License, or (at your option) any later version. *
* *
***************************************************************************/
import { Paths } from "./Paths";
import { ImageFilter } from "../sprite/image/ImageFilter";
export class HTMLImageStore {
private knownBrokenUrls: {[url: string]: boolean} = {};
private images: {[filename: string]: CanvasImageSource} = {};
private knownShadows: {[key: string]: boolean} = {
"24x32": true,
"32x32": true,
"32x48": true,
"32x48_long": true,
"48x64": true,
"48x64_float": true,
"64x48": true,
"64x64": true,
"64x85": true,
"64x96": true,
"76x64": true,
"81x96": true,
"96x96": true,
"96x128": true,
"128x96": true,
"128x170": true,
"144x128": true,
"168x224": true,
"192x192": true,
"192x192_float": true,
"192x256": true,
"320x440": true,
"ent": true
};
// alternatives for known images that may be considered violent or mature
private knownSafeSprites: {[filename: string]: boolean} = {
[Paths.sprites + "/monsters/huge_animal/thing"]: true,
[Paths.sprites + "/monsters/mutant/imperial_mutant"]: true,
[Paths.sprites + "/monsters/undead/bloody_zombie"]: true,
[Paths.sprites + "/npc/deadmannpc"]: true
};
// TODO: move to animation.json
animations: {[key: string]: any} = {
idea: {
"love": {delay: 100, offsetX: 24, offsetY: -8}
}
};
get(filename: string): any {
if (!filename) {
return {};
}
if (filename.indexOf("undefined") > -1) {
if (!this.knownBrokenUrls[filename]) {
console.log("Broken image path: ", filename, new Error());
}
this.knownBrokenUrls[filename] = true;
return {};
}
if (this.images[filename]) {
return this.images[filename];
}
var temp = new Image();
temp.onerror = (function(t: HTMLImageElement, store: HTMLImageStore) {
return function() {
if (t.src && !store.knownBrokenUrls[t.src]) {
console.log("Broken image path:", t.src, new Error());
store.knownBrokenUrls[t.src] = true;
}
const failsafe = store.getFailsafe();
if (failsafe.src && t.src !== failsafe.src) {
t.src = failsafe.src;
}
};
})(temp, this);
temp.src = filename;
this.images[filename] = temp;
return temp;
}
/**
* Rotates an image.
*
* @param img
* Image to be rotated.
* @param angle
* Angle of rotation.
*/
private rotate(img: HTMLImageElement, angle: number) {
const canvas = document.getElementById("drawing-stage")! as HTMLCanvasElement;
const ctx = canvas.getContext("2d")!;
// make sure working with blank canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.width = img.width;
canvas.height = img.height;
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(angle * Math.PI / 180);
ctx.translate(-canvas.width / 2, -canvas.height / 2);
ctx.drawImage(img, 0, 0);
img.src = canvas.toDataURL("image/png");
}
/**
* Retrieves a rotated image.
*
* @param filename
* Path to target image file.
* @param angle
* Angle of rotation.
* @return
* HTMLImageElement.
*/
getRotated(filename: string, angle: number): any {
if (angle == 0) {
return this.get(filename);
}
const id = filename + "-rot" + angle;
if (this.images[id]) {
return this.get(id);
}
// NOTE: cannot use HTMLImageElement.cloneNode here, must get base image then transfer
// `src` property when ready
const img = new Image();
img.onload = () => {
img.onload = null;
this.rotate(img, angle);
}
const baseImg = this.get(filename);
if (baseImg.complete) {
img.src = baseImg.src;
} else {
baseImg.onload = () => {
baseImg.onload = null;
img.src = baseImg.src;
}
}
this.images[id] = img;
return img;
}
/**
* Adds an image to cache.
*
* @param {string} id
* Cache identifier.
* @param {SpriteImage}
* Image to be cached.
*/
cache(id: string, image: CanvasImageSource) {
this.images[id] = image;
}
/**
* Used when we only want an image if it was previously cached.
*
* @param filename
* Full file path.
* @return
* HTMLImageElement or undefined.
*/
getCached(filename: string): any {
return this.images[filename];
}
/**
* Retrieves the failsafe sprite.
*
* @return
* HTMLImageElement with failsafe image data.
*/
getFailsafe(): HTMLImageElement {
const filename = Paths.sprites + "/failsafe.png";
let failsafe = this.images[filename];
if (failsafe) {
} else {
failsafe = new Image();
failsafe.src = filename;
this.images[filename] = failsafe;
}
return failsafe as HTMLImageElement;
}
/**
* Checks cached images for a valid filename.
*
* @param filename
* Image filename to be checked.
* @return
* Path to image or failsafe image file.
*/
checkPath(filename: string): string {
return this.get(filename).src;
}
/**
* @param {string} fileName
* @param {string} filter
* @param {number=} param
*/
getFiltered(fileName: string, filter: string, param?: number) {
const img = this.get(fileName);
if (!img.complete || img.width === 0 || img.height === 0) {
return img;
}
const filteredName = fileName + " " + filter + " " + param;
let filtered = this.images[filteredName];
if (typeof(filtered) === "undefined") {
const canvas = document.createElement("canvas") as any;
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d")!;
ctx.drawImage(img, 0, 0);
const imgData = ctx.getImageData(0, 0, img.width, img.height);
new ImageFilter().filter(imgData, filter, param);
ctx.putImageData(imgData, 0, 0);
canvas.complete = true;
this.images[filteredName] = filtered = canvas;
}
return filtered;
}
/**
* Retrieves a shadow sprite if the style is available.
*
* @param shadowStyle
* Style of shadow to get from cache.
* @return
* Image sprite or <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

@ -1,174 +0,0 @@
/***************************************************************************
* (C) Copyright 2003-2023 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation; either version 3 of the *
* License, or (at your option) any later version. *
* *
***************************************************************************/
declare var stendhal: any;
/*
* Start: http://www.webreference.com/programming/javascript/gr/column3/
* ( https://web.archive.org/web/20110910064905/http://www.webreference.com:80/programming/javascript/gr/column3/index.html )
*
function ImagePreloader(images, callback) {
// store the call-back
this.callback = callback;
// initialize internal state.
this.nLoaded = 0;
this.nProcessed = 0;
stendhal.data.map.aImages = new Array;
// record the number of images.
this.nImages = images.length;
// for each image, call preload()
for ( var i = 0; i < images.length; i++) {
this.preload(images[i]);
}
}
ImagePreloader.prototype.preload = function(image) {
// create new Image object and add to array
var oImage = new Image;
stendhal.data.map.aImages.push(oImage);
// set up event handlers for the Image object
oImage.onload = ImagePreloader.prototype.onload;
oImage.onerror = ImagePreloader.prototype.onerror;
oImage.onabort = ImagePreloader.prototype.onabort;
// assign pointer back to this.
oImage.oImagePreloader = this;
oImage.bLoaded = false;
// assign the .src property of the Image object
oImage.src = image;
};
ImagePreloader.prototype.onComplete = function() {
this.nProcessed++;
if (this.nProcessed == this.nImages) {
this.callback();
}
};
ImagePreloader.prototype.onload = function() {
this.bLoaded = true;
this.oImagePreloader.nLoaded++;
this.oImagePreloader.onComplete();
};
ImagePreloader.prototype.onerror = function() {
this.bError = true;
this.oImagePreloader.onComplete();
console.error("Error loading " + this.src);
};
ImagePreloader.prototype.onabort = function() {
this.bAbort = true;
this.oImagePreloader.onComplete();
console.error("Loading " + this.src + " was aborted");
};
*
* End: http://www.webreference.com/programming/javascript/gr/column3/
*/
/**
* preloads images
*
* @param images image url to load
* @param callback callback to invoke
* @constructor
*/
export class ImagePreloader extends Function {
oImagePreloader: ImagePreloader;
src?: string;
callback: Function;
// internal state
bLoaded: boolean;
bError: boolean;
bAbort: boolean;
nLoaded: number;
nProcessed: number;
// number of images
nImages: number;
constructor(images: string[], callback: Function) {
super();
this.oImagePreloader = {} as ImagePreloader;
// store the call-back
this.callback = callback;
// initialize internal state.
this.bLoaded = false;
this.bError = false;
this.bAbort = false;
this.nLoaded = 0;
this.nProcessed = 0;
stendhal.data.map.aImages = new Array();
// record the number of images.
this.nImages = images.length;
// for each image, call preload()
for (let i = 0; i < images.length; i++) {
this.preload(images[i]);
}
}
preload(image: string) {
// create new Image object and add to array
const oImage = new Image() as any;
stendhal.data.map.aImages.push(oImage);
// set up event handlers for the Image object
oImage.onload = ImagePreloader.prototype.onload;
oImage.onerror = ImagePreloader.prototype.onerror;
oImage.onabort = ImagePreloader.prototype.onabort;
// assign pointer back to this
oImage.oImagePreloader = this;
oImage.bLoaded = false;
// assign the .src property of the Image object
oImage.src = image;
}
onComplete() {
this.nProcessed++;
if (this.nProcessed == this.nImages) {
this.callback();
}
}
onload() {
this.bLoaded = true;
this.oImagePreloader.nLoaded++;
this.oImagePreloader.onComplete();
}
onerror() {
this.bError = true;
this.oImagePreloader.onComplete();
console.error("Error loading " + this.src);
}
onabort() {
this.bAbort = true;
this.oImagePreloader.onComplete();
console.error("Loading " + this.src + " was aborted");
}
}

View file

@ -10,11 +10,14 @@
* *
***************************************************************************/
import { DrawingStage } from "../util/DrawingStage";
import { Pair } from "../util/Pair";
import { StringUtil } from "../util/StringUtil";
import { Paths } from "./Paths";
declare var stendhal: any;
import { stendhal } from "../stendhal";
import { ImageMetadata } from "sprite/image/ImageMetadata";
import { MultilayerImageLoader } from "sprite/image/MultilayerImageLoader";
import { OutfitStore } from "./OutfitStore";
/**
@ -113,7 +116,7 @@ export class Outfit {
public getLayers(): Pair<string, number>[] {
const layers: Pair<string, number>[] = [];
// only include valid layers
for (const name of stendhal.data.outfit.getLayerNames()) {
for (const name of OutfitStore.get().getLayerNames()) {
let index = this.layers[name];
if (index != undefined) {
layers.push(new Pair(name, index));
@ -151,7 +154,7 @@ export class Outfit {
* Color value or `undefined`.
*/
public getLayerColor(name: string): number|undefined {
if (stendhal.data.outfit.isSkinLayer(name)) {
if (OutfitStore.get().isSkinLayer(name)) {
name = "skin";
}
return this.coloring && name in this.coloring ? this.coloring[name] : undefined;
@ -193,40 +196,14 @@ export class Outfit {
*
* TODO:
* - use dress layer based on body type
*
* @param {Function} callback
* Function to pass image to when ready.
*/
toImage(callback: Function) {
const sig = this.getSignature();
// get directly from cache since we don't want to return failsafe image
let image: HTMLImageElement = stendhal.data.sprites.getCached(sig);
const onReady = function(e?: Event) {
image.removeEventListener("load", onReady);
callback(image);
};
if (image instanceof HTMLImageElement) {
if (image.height > 0) {
onReady();
} else {
image.addEventListener("load", onReady);
}
return;
}
const stage = DrawingStage.get();
stage.reset();
stage.setSize(48 * 3, 64 * 4);
async toImage(): Promise<ImageBitmap> {
let parts: ImageMetadata[] = [];
let outfitLayers = this.getLayers();
const detailIndex = this.getLayerIndex("detail") || 0;
if (stendhal.data.outfit.detailHasRearLayer(detailIndex)) {
if (OutfitStore.get().detailHasRearLayer(detailIndex)) {
outfitLayers = [new Pair("detail-rear", detailIndex), ...outfitLayers];
}
const layers: HTMLImageElement[] = [];
for (const l of outfitLayers) {
let layerName = l.first;
let suffix = "";
@ -235,58 +212,21 @@ export class Outfit {
layerName = layerName.replace(/-rear$/, "");
}
const layerIndex = l.second;
let layerPath = stendhal.paths.sprites + "/outfit/" + layerName + "/"
let layerPath = Paths.sprites + "/outfit/" + layerName + "/"
+ StringUtil.padLeft(""+layerIndex, "0", 3) + suffix;
if (layerName === "body" && stendhal.config.getBoolean("effect.no-nude")) {
// FIXME: some non-player pickable bodies don't have "no-nude" variant
layerPath += "-nonude";
}
layerPath += ".png";
const coloring = this.getLayerColor(layerName);
if (typeof(coloring) === "undefined") {
layers.push(stendhal.data.sprites.get(layerPath));
} else {
layers.push(stendhal.data.sprites.getFiltered(layerPath, "trueColor", coloring));
let filter = undefined;
let coloring = this.getLayerColor(layerName);
if (coloring) {
filter = "trueColor";
}
parts.push(new ImageMetadata(layerPath, filter, coloring));
}
if (layers.length == 0) {
image = stendhal.data.sprites.getFailsafe();
callback(image);
return;
}
let onAllLayersReady: Function;
const onLayerReady = function(e?: Event) {
for (const layer of layers) {
if (!layer.complete || layer.height === 0) {
return;
}
}
onAllLayersReady();
};
onAllLayersReady = function() {
for (const layer of layers) {
layer.removeEventListener("load", onLayerReady);
stage.drawImage(layer);
}
image = stage.toImage();
stage.reset();
stendhal.data.sprites.cache(sig, image);
if (image.height > 0) {
onReady();
} else {
image.addEventListener("load", onReady);
}
};
for (const layer of layers) {
if (layer.height > 0) {
onLayerReady();
} else {
layer.addEventListener("load", onLayerReady);
}
}
return await new MultilayerImageLoader(parts).load();
}
}

View file

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

View file

@ -1,618 +0,0 @@
/***************************************************************************
* (C) Copyright 2003-2024 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation; either version 3 of the *
* License, or (at your option) any later version. *
* *
***************************************************************************/
declare var stendhal: any;
import { Paths } from "./Paths";
class SpriteImage extends Image {
// number of times the image has been accessed after initial creation
counter = 0;
}
export class SpriteStore {
private knownBrokenUrls: {[url: string]: boolean} = {};
private images: {[filename: string]: SpriteImage} = {};
private knownShadows: {[key: string]: boolean} = {
"24x32": true,
"32x32": true,
"32x48": true,
"32x48_long": true,
"48x64": true,
"48x64_float": true,
"64x48": true,
"64x64": true,
"64x85": true,
"64x96": true,
"76x64": true,
"81x96": true,
"96x96": true,
"96x128": true,
"128x96": true,
"128x170": true,
"144x128": true,
"168x224": true,
"192x192": true,
"192x192_float": true,
"192x256": true,
"320x440": true,
"ent": true
};
// alternatives for known images that may be considered violent or mature
private knownSafeSprites: {[filename: string]: boolean} = {
[Paths.sprites + "/monsters/huge_animal/thing"]: true,
[Paths.sprites + "/monsters/mutant/imperial_mutant"]: true,
[Paths.sprites + "/monsters/undead/bloody_zombie"]: true,
[Paths.sprites + "/npc/deadmannpc"]: true
};
// TODO: move to animation.json
private animations: {[key: string]: any} = {
idea: {
"love": {delay: 100, offsetX: 24, offsetY: -8}
}
};
/**
* Hidden singleton constructor.
*/
protected constructor() {
// do nothing
}
get(filename: string): any {
if (!filename) {
return {};
}
if (filename.indexOf("undefined") > -1) {
if (!this.knownBrokenUrls[filename]) {
console.log("Broken image path: ", filename, new Error());
}
this.knownBrokenUrls[filename] = true;
return {};
}
if (this.images[filename]) {
this.images[filename].counter++;
return this.images[filename];
}
var temp = new Image() as SpriteImage;
// TypeError: Image constructor: 'new' is required
//~ var temp = new SpriteImage();
temp.counter = 0;
temp.onerror = (function(t: SpriteImage, store: SpriteStore) {
return function() {
if (t.src && !store.knownBrokenUrls[t.src]) {
console.log("Broken image path:", t.src, new Error());
store.knownBrokenUrls[t.src] = true;
}
const failsafe = store.getFailsafe();
if (failsafe.src && t.src !== failsafe.src) {
t.src = failsafe.src;
}
};
})(temp, this);
temp.src = filename;
this.images[filename] = temp;
return temp;
}
getWithPromise(filename: string): any {
return new Promise((resolve) => {
if (typeof(this.images[filename]) != "undefined") {
this.images[filename].counter++;
resolve(this.images[filename]);
}
const image = new Image() as SpriteImage;
// TypeError: Image constructor: 'new' is required
//~ const image = new SpriteImage();
image.counter = 0;
this.images[filename] = image;
image.onload = () => resolve(image);
image.src = filename;
});
}
/**
* Rotates an image.
*
* @param img
* Image to be rotated.
* @param angle
* Angle of rotation.
*/
private rotate(img: HTMLImageElement, angle: number) {
const canvas = <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

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

View file

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

View file

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

View file

@ -16,7 +16,7 @@ import { AbstractEnum } from "../enum/AbstractEnum";
/**
* Available layers of sound.
*
* NOTE: string names should match `games.stendhal.common.constants.SoundLayer`
* string names should match `games.stendhal.common.constants.SoundLayer`
*/
export class SoundLayer extends AbstractEnum<string> {
@ -29,9 +29,6 @@ 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);
@ -47,7 +44,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,16 +9,15 @@
* *
***************************************************************************/
declare var marauroa: any;
declare var stendhal: any;
import { marauroa } from "marauroa"
import { stendhal } from "../../stendhal";
import { SoundFactory } from "./SoundFactory";
import { SoundObject } from "./SoundFactory";
import { SoundLayer } from "./SoundLayer";
import { singletons } from "../../SingletonRepo";
import { ui } from "../../ui/UI";
import { Paths } from "../Paths";
/**
@ -244,7 +243,7 @@ export class SoundManager {
let snd = this.cache[soundName] || this.cacheGlobal[soundName];
if (!snd) {
// add new sound to cache
snd = this.load(soundName, stendhal.paths.sounds + "/" + soundName + ".ogg");
snd = this.load(soundName, Paths.sounds + "/" + soundName + ".ogg");
}
if (!this.cache[soundName]) {
@ -406,7 +405,7 @@ export class SoundManager {
volume=1.0): SoundObject|undefined {
// load into cache so playEffect doesn't look in "data/sounds"
if (!this.cache[musicName]) {
this.load(musicName, stendhal.paths.music + "/" + musicName + ".ogg");
this.load(musicName, Paths.music + "/" + musicName + ".ogg");
}
return this.playLocalizedLoop(x, y, radius, layer, musicName, volume);
}
@ -424,7 +423,7 @@ export class SoundManager {
playGlobalizedMusic(musicName: string, volume=1.0): SoundObject|undefined {
// load into cache so playEffect doesn't look in "data/sounds"
if (!this.cache[musicName]) {
this.load(musicName, stendhal.paths.music + "/" + musicName + ".ogg");
this.load(musicName, Paths.music + "/" + musicName + ".ogg");
}
return this.playGlobalizedLoop(musicName, "music", volume);
}
@ -796,6 +795,6 @@ export class SoundManager {
startupCache() {
// login sound
this.load("ui/login",
stendhal.paths.sounds + "/ui/login.ogg", true);
Paths.sounds + "/ui/login.ogg", true);
}
}

View file

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

View file

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

View file

@ -19,23 +19,13 @@ import { FloatingWindow } from "../ui/toolkit/FloatingWindow";
import { ItemInventoryComponent } from "../ui/component/ItemInventoryComponent";
import { Chat } from "../util/Chat";
import { Paths } from "../data/Paths";
declare var marauroa: any;
declare var stendhal: any;
let OPEN_SPRITE = {
filename: stendhal.paths.sprites + "/chest.png",
height: 32,
width: 32,
offsetY: 32
};
let CLOSED_SPRITE = {
filename: stendhal.paths.sprites + "/chest.png",
height: 32,
width: 32
};
import { marauroa } from "marauroa"
import { stendhal } from "../stendhal";
import { Entity } from "./Entity";
import { ImageSprite } from "sprite/image/ImageSprite";
import { images } from "sprite/image/ImageManager";
export class Chest extends PopupInventory {
@ -43,16 +33,22 @@ export class Chest extends PopupInventory {
override minimapStyle = Color.CHEST;
override zIndex = 5000;
sprite = CLOSED_SPRITE;
open = false;
constructor() {
super();
this.imageSprite = new ImageSprite(
images.load(Paths.sprites + "/chest.png"),
0, 0, 32, 32);
}
override set(key: string, value: any) {
super.set(key, value);
if (key === "open") {
this.sprite = OPEN_SPRITE;
this.imageSprite!.offsetY = 32;
this.open = true;
}
if (this.isNextTo(marauroa.me)) {
if (this.isNextTo(marauroa.me as Entity)) {
this.openInventoryWindow();
}
}
@ -60,7 +56,7 @@ export class Chest extends PopupInventory {
override unset(key: string) {
super.unset(key);
if (key === "open") {
this.sprite = CLOSED_SPRITE;
this.imageSprite!.offsetY = 0;
this.open = false;
if (this.inventory && this.inventory.isOpen()) {
this.inventory.close();
@ -151,7 +147,7 @@ export class Chest extends PopupInventory {
}
override getCursor(_x: number, _y: number) {
return "url(" + stendhal.paths.sprites + "/cursor/bag.png) 1 3, auto";
return "url(" + Paths.sprites + "/cursor/bag.png) 1 3, auto";
}
}

View file

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

View file

@ -0,0 +1,34 @@
/***************************************************************************
* (C) Copyright 2003-2026 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation; either version 3 of the *
* License, or (at your option) any later version. *
* *
***************************************************************************/
import { Corpse } from "./Corpse";
import { RPSlot } from "marauroa";
export class CorpseSlot extends RPSlot {
public _name?: string;
public _parent?: Corpse;
override add(object: any) {
super.add(object);
if (this._objects.length > 0) {
this._parent?.autoOpenIfDesired();
}
}
override del(key: any) {
super.del(key);
if (this._objects.length == 0) {
this._parent?.closeCorpseInventory();
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,93 @@
/***************************************************************************
* (C) Copyright 2003-2026 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation; either version 3 of the *
* License, or (at your option) any later version. *
* *
***************************************************************************/
import { marauroa } from "marauroa"
import { AttackEvent } from "event/AttackEvent";
import { BestiaryEvent } from "./BestiaryEvent";
import { ChatOptionsEvent } from "./ChatOptionsEvent";
import { ExamineEvent } from "./ExamineEvent";
import { GlobalVisualEffectEvent } from "event/GlobalVisualEffectEvent";
import { GroupChangeEvent } from "./GroupChangeEvent";
import { GroupInviteEvent } from "./GroupInviteEvent";
import { ImageEvent } from "event/ImageEvent";
import { PlayerLoggedOnEvent } from "./PlayerLoggedOnEvent";
import { PlayerLoggedOutEvent } from "./PlayerLoggedOutEvent";
import { PrivateTextEvent } from "event/PrivateTextEvent";
import { ProgressStatusEvent } from "./ProgressStatusEvent";
import { ReachedAchievementEvent } from "event/ReachedAchievementEvent";
import { ShowItemListEvent } from "./ShowItemListEvent";
import { ShowOutfitListEvent } from "./ShowOutfitListEvent";
import { SoundEvent } from "./SoundEvent";
import { TextEvent } from "event/TextEvent";
import { TradeEvent } from "./TradeEvent";
import { TransitionGraphEvent } from "event/TransitionGraphEvent";
import { ViewChangeEvent } from "./ViewChangeEvent";
export class EventRegistry {
private initialized = false;
/** Singleton instance. */
private static instance: EventRegistry;
/**
* Retrieves singleton instance.
*/
static get(): EventRegistry {
if (!EventRegistry.instance) {
EventRegistry.instance = new EventRegistry();
}
return EventRegistry.instance;
}
/**
* Hidden singleton constructor.
*/
private constructor() {
// do nothing
}
/**
* Registers standard events.
*/
init() {
if (this.initialized) {
console.warn("tried to re-initialize EventRegistry");
return;
}
this.initialized = true;
marauroa.rpeventFactory.register("attack", AttackEvent);
marauroa.rpeventFactory.register("bestiary", BestiaryEvent);
marauroa.rpeventFactory.register("chat_options", ChatOptionsEvent);
marauroa.rpeventFactory.register("examine", ExamineEvent);
marauroa.rpeventFactory.register("global_visual_effect", GlobalVisualEffectEvent);
marauroa.rpeventFactory.register("group_change_event", GroupChangeEvent);
marauroa.rpeventFactory.register("group_invite_event", GroupInviteEvent);
marauroa.rpeventFactory.register("image_event", ImageEvent);
marauroa.rpeventFactory.register("player_logged_on", PlayerLoggedOnEvent);
marauroa.rpeventFactory.register("player_logged_out", PlayerLoggedOutEvent);
marauroa.rpeventFactory.register("private_text", PrivateTextEvent);
marauroa.rpeventFactory.register("progress_status_event", ProgressStatusEvent);
marauroa.rpeventFactory.register("reached_achievement", ReachedAchievementEvent);
marauroa.rpeventFactory.register("show_item_list", ShowItemListEvent);
marauroa.rpeventFactory.register("show_outfit_list", ShowOutfitListEvent);
marauroa.rpeventFactory.register("sound_event", SoundEvent);
marauroa.rpeventFactory.register("text", TextEvent);
marauroa.rpeventFactory.register("trade_state_change_event", TradeEvent);
marauroa.rpeventFactory.register("transition_graph", TransitionGraphEvent);
marauroa.rpeventFactory.register("view_change", ViewChangeEvent);
}
}

View file

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

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