Compare commits

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

199 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
Hendrik Brummermann
ccee5a42b3 added experiment to .gitignore 2025-12-21 22:11:50 +01:00
Jordan Irwin
f2d519a29f
Revert "Ensure cargo-license installed for nativehelper build"
This reverts commit d516c738bf.
2025-11-03 03:43:05 -08:00
Jordan Irwin
d516c738bf
Ensure cargo-license installed for nativehelper build 2025-11-03 03:29:09 -08:00
Jordan Irwin
ad92b7f2a9
make sure extensions/binary directory exists when building Neutralinojos app 2025-11-03 00:14:27 -08:00
Hendrik Brummermann
2ee0aaa5e7 created stendhal-client.zip file and renamed executables 2025-11-02 12:12:50 +01:00
Jordan Irwin
6592f5c245
Fix Gourmet achievement
Coal for Haunchy quest slot state was changed to use "done" instead of "waiting".

Closes: https://codeberg.org/arianne/stendhal/issues/28
2025-11-01 00:18:03 -07:00
Jordan Irwin
8206631665
Fix Unit test for Gwen's updated shop prices 2025-10-31 18:21:40 -07:00
Jordan Irwin
bdb34e8628
Reduce outfit prices in Gwen's shop by 50% 2025-10-31 18:07:20 -07:00
Jordan Irwin
0fba46d89b
Minor change to script text output 2025-10-31 17:56:18 -07:00
Jordan Irwin
4a55c4a706
Trim large music files to decrease size to 2MB or less
Should help to mitigate issue with Java client failing to load large music data on systems with low resources.

https://github.com/arianne/stendhal/issues/750
2025-10-31 17:43:09 -07:00
Jordan Irwin
4253e1e73b
Python script to check audio files sizes
Prints warning if file size exceeds 2MB.

https://github.com/arianne/stendhal/issues/750
2025-10-31 17:42:55 -07:00
Jordan Irwin
4e8fe0592e
Add license boilerplates 2025-10-15 20:15:19 -07:00
Jordan Irwin
5709a85824
Set Java client compilation to 8
Support for 7 removed in newer Java versions.
2025-09-25 22:03:44 -07:00
Jordan Irwin
700e3ee730
Replace calls to deprecated URL constructors 2025-09-25 21:58:36 -07:00
Jordan Irwin
0b2cb9df08
Set User-Agent string for Wikipedia API requests
Closes: https://codeberg.org/arianne/stendhal/issues/27
2025-09-25 21:58:27 -07:00
Jordan Irwin
63d9667264
Fix typo in quest dialogue 2025-09-25 19:48:53 -07:00
Jordan Irwin
920ed5a3a2
Add start screen screenshot for F-Droid 2025-09-24 13:15:29 -07:00
Jordan Irwin
f02fc6b04f
Add phone screenshots for F-Droid
Closes https://github.com/arianne/stendhal/issues/754
2025-09-24 13:02:57 -07:00
Jordan Irwin
807a8bb40a
Print authentication status when nativehelper connection closes 2025-09-12 10:40:28 -07:00
Jordan Irwin
e6180e9ff5
Update changes 2025-09-08 23:05:15 -07:00
Jordan Irwin
3de1141898
Close nativehelper extension when clientDisconnect event triggered
Closes https://github.com/neutralinojs/neutralinojs/issues/1458
Closes https://github.com/arianne/stendhal/issues/736
2025-09-08 22:46:57 -07:00
Jordan Irwin
3e31e6b788
Update changes 2025-09-07 13:08:15 -07:00
Jordan Irwin
ff9fb76ab3
Update Neutralino.js to 6.2.0
Closes: https://github.com/arianne/stendhal/issues/696

Commit reference: f46403677c
2025-08-26 13:49:09 -07:00
Jordan Irwin
ccb229efe9
Clean up indentation in Neutralino.js config 2025-08-26 13:48:21 -07:00
Jordan Irwin
5f3f86c6e7
Normalize line endings in Tiled maps 2025-07-26 18:44:53 -07:00
Jordan Irwin
42c30cdbda
Run 'ant clean_whitespace' 2025-07-26 16:08:05 -07:00
Jordan Irwin
01dddc103f
Update Android source location in clean_whitespace target 2025-07-26 16:07:12 -07:00
Hendrik Brummermann
70bcbc3066 perfer Codeberg over Github 2025-01-22 22:18:29 +01:00
Hendrik Brummermann
3c7b1a30ae use Mastodon instead of Twitter 2025-01-22 22:13:20 +01:00
Jordan Irwin
e82524ec2f
Adjust coloring alpha setting to better match Java client 2025-01-09 02:54:59 -08:00
Jordan Irwin
f224b79094
Don't check movement radius if NPC has path 2025-01-02 23:56:42 -08:00
Jordan Irwin
a7e66a4288
Ignore movement limit when radius set to 0
Fixes SilentNPC paths
Closes https://codeberg.org/arianne/stendhal/issues/7
2025-01-02 23:55:56 -08:00
Jordan Irwin
79e882fe5f
Code optimization 2025-01-02 21:50:19 -08:00
Jordan Irwin
8b63897d37
Update copyright year 2025-01-02 14:38:31 -08:00
Jordan Irwin
a24774cda9
Template F-Droid release announcment file for master branch 2024-12-21 15:14:49 -08:00
Jordan Irwin
2746ee1411
Increment version 2024-12-21 15:11:30 -08:00
Jordan Irwin
962bd11aec
Fix Mr Ross's head 2024-12-21 14:57:16 -08:00
Jordan Irwin
a9ae0db9a9
Disable encrypted DependencyInfoBlock in APK build
https://gitlab.com/fdroid/fdroiddata/-/issues/3330
2024-12-20 19:58:47 -08:00
Jordan Irwin
06f4e5eb78
Remove unreachable code block 2024-12-17 19:26:16 -08:00
Jordan Irwin
ade5f226a9
Fix random selecting between patrolling & wander idle behavior 2024-12-17 18:44:43 -08:00
Jordan Irwin
d2f549394d
Touch up scaling artifacts in necrosophia overlay 2024-12-15 22:27:45 -08:00
263 changed files with 8142 additions and 8433 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-2024, arianne-general at lists dot sourceforge dot net
Stendhal(c) is copyright of the Arianne Project, 2006-2026, arianne-general at lists dot sourceforge dot net
Please have a look at the list of [contributors](doc/contributors.md#contributors).

1
app/.gitignore vendored Normal file
View file

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

View file

@ -10,15 +10,23 @@ android {
applicationId namespace
minSdk 21
targetSdk 32
versionCode 1047005
versionName "1.47.5"
versionCode 1049005
versionName "1.49.5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
targetSdkVersion 32
minSdkVersion 21
}
// F-Droid doesn't allow encrypted DependencyInfoBlock in APK
dependenciesInfo {
// disables dependency metadata when building APKs
includeInApk = false
// disables dependency metadata when building Android App Bundles
includeInBundle = false
}
// 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()));

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 KiB

View file

@ -1,9 +1,3 @@
Pizza is a popular meal of choice. With growing demand, Leander is looking to hire reliable workers to transport his hot and delicious cuisine to patrons. Couriers with reputations of on-time deliveries are being recognized for their work ethic.
PUT RELEASE ANNOUNCEMENT LINES HERE
Some adventurers may be distressed and feel that their quest for the title of "Ultimate Collector" is in vain. Well fret no more, as Balduin is offering a ray of hope. Requests to bring him a different item are now accepted afer some time has passed.
Speaking of weapons, some creatures have once again taken up arms. For example the angel and dark angel have new swords.
Last but not least: We released an official Android App, which is available in the download section of our website or on F-Droid.
Please see the complete change log for further details: http://localhost/game/stendhal.html#changes
Please see the complete change log for further details: https://arianne-project.org/game/stendhal.html#changes

View file

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

View file

@ -19,7 +19,7 @@ import android.content.DialogInterface;
/**
* Semi-functional logger for release builds.
*
*
* Displays notification dialogs but does not write to logs directory.
*/
public class Logger {

View file

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

View file

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

View file

@ -1,9 +1,12 @@
use lazy_static::lazy_static;
use serde_json::Value;
use std::io;
use std::io::Read;
use std::process;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Mutex;
use std::sync::mpsc::Sender;
use std::sync::mpsc::channel;
use std::thread;
@ -16,6 +19,10 @@ use websocket::client::ClientBuilder;
use websocket::{Message, OwnedMessage};
lazy_static! {
static ref AUTHENTICATED: Mutex<bool> = Mutex::new(false);
}
fn connect() -> (Sender<OwnedMessage>, JoinHandle<()>) {
println!("Read connection parameters");
let mut json = String::new();
@ -119,6 +126,14 @@ fn connect() -> (Sender<OwnedMessage>, JoinHandle<()>) {
fn handle_message(nl_token: &String, tx: Sender<OwnedMessage>, message: String) {
if message.contains("event\":\"request_authentication\"") {
authenticate(tx, nl_token.clone());
} else if message.contains("event\":\"clientDisconnect\"") {
let authenticated = AUTHENTICATED.lock().unwrap();
if *authenticated {
println!("Authenticated, exiting extension ...");
} else {
println!("Unauthenticated, exiting extension ...");
}
process::exit(0);
}
}
@ -132,6 +147,10 @@ fn authenticate(tx: Sender<OwnedMessage>, nl_token: String) {
let _callback =
client.register_callback(move |session_ticket_response: AuthSessionTicketResponse| {
//if session_ticket_response.ticket == ticket_handle {
if session_ticket_response.result.is_ok() {
let mut authenticated = AUTHENTICATED.lock().unwrap();
*authenticated = true;
}
println!("Ticket Response Result: {:?}", session_ticket_response.result);
callback_called.store(true, Ordering::Relaxed);
// println!("Ticket : {:?}", session_ticket_response.ticket);

View file

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

View file

@ -1 +0,0 @@
2267890

View file

@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/neutralinojs/neutralinojs/main/schemas/neutralino.config.schema.json",
"applicationId": "org.stendhalgame.client",
"version": "1.47.5",
"version": "1.49.5",
"defaultMode": "window",
"port": 0,
"documentRoot": "/resources/",
@ -11,9 +11,9 @@
"enableExtensions": true,
"extensions": [
{
"id": "nativehelper",
"commandLinux": "${NL_PATH}/extensions/binary/nativehelper",
"commandWindows": "${NL_PATH}/extensions/binary/nativehelper.exe"
"id": "nativehelper",
"commandLinux": "${NL_PATH}/extensions/binary/nativehelper",
"commandWindows": "${NL_PATH}/extensions/binary/nativehelper.exe"
}
],
"tokenSecurity": "one-time",
@ -55,7 +55,7 @@
"resourcesPath": "/resources/",
"extensionsPath": "/extensions/",
"clientLibrary": "/resources/js/neutralino.js",
"binaryVersion": "5.0.0",
"clientVersion": "5.0.1"
"binaryVersion": "6.2.0",
"clientVersion": "6.2.0"
}
}

90
bin/check-file-size.py Executable file
View file

@ -0,0 +1,90 @@
#!/usr/bin/env python3
'''
***************************************************************************
* Copyright © 2025 - Faiumoni e. V. *
***************************************************************************
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
'''
import errno
import os
import sys
from pathlib import Path
dir_root = Path(__file__).parent
if "bin" == dir_root.name:
dir_root = Path(dir_root.parent)
dir_music = Path(dir_root, "data/music")
dir_sounds = Path(dir_root, "data/sounds")
## Checks if a file is larger than 2MB.
#
# On systems with low resources, the Java client struggles to load large audio files.
#
# @param path
# File path.
# @return
# `EFBIG` if file is larger than 2MB, 0 otherwise.
def checkAudioFileSize(path):
if not path.is_file():
# ignore if audio file doesn't exist
print("WARNING: Ignoring file not found: {}".format(path))
return 0
if path.stat().st_size > 1048576 * 2:
# warn if file size > 2MB
return errno.EFBIG
return 0
## Checks directory for large audio files.
#
# @param path
# Directory path to be walked.
# @return
# `EFBIG` if any file larger than 2MB found, 0 otherwise.
def checkAudioDir(path):
if not path.is_dir():
# ignore if audio directory doesn't exist
print("WARNING: Ignoring directory not found: {}".format(path))
return 0
res = 0
for ROOT, DIRS, FILES in path.walk():
for filename in FILES:
filepath = Path(ROOT, filename)
if filepath.suffix.lower().lstrip(".") not in ("ogg", "oga", "mid", "midi"):
# only check supported file types
continue
code = checkAudioFileSize(filepath)
if code == errno.EFBIG:
res = code
sys.stderr.write("WARNING: Audio file size larger than 2MB: {}\n".format(str(filepath)[len(str(dir_root)):].lstrip("/\\")))
return res
def main():
res = 0
for audio_dir in dir_music, dir_sounds:
code = checkAudioDir(audio_dir)
if code != 0:
res = code
if res == 0:
print("No large audio data files detected")
return res
if __name__ == "__main__":
# work from source tree root
os.chdir(dir_root)
sys.exit(main())

View file

@ -1,6 +1,6 @@
@echo off
set STENDHAL_VERSION=1.47.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.47.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.47
version = 1.47.5
version.old = 1.49
version = 1.49.5
# FIXME: dynamic method to set this property?
version.android = 1047005
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

@ -37,6 +37,7 @@
<arg value="--target" />
<arg value="x86_64-pc-windows-gnu" />
</exec>
<mkdir dir="app/neutralinojs/extensions/binary" />
<exec executable="cargo" dir="app/nativehelper" failonerror="true" output="app/neutralinojs/extensions/binary/LICENSE.txt">
<arg value="license" />
<arg value="-ad" />
@ -77,23 +78,33 @@
<include name="*.log" />
</fileset>
</delete>
<move
file="app/neutralinojs/dist/stendhal-client/stendhal-client-linux_x64"
tofile="app/neutralinojs/dist/stendhal-client/stendhal-client-linux" />
<move
file="app/neutralinojs/dist/stendhal-client/stendhal-client-win_x64.exe"
tofile="app/neutralinojs/dist/stendhal-client/stendhal-client-windows.exe" />
<chmod dir="app/neutralinojs/dist/stendhal-client/" type="file" perm="ugo+rx">
<include name="stendhal*" />
<include name="stendhal-client-linux" />
<include name="**/nativehelper*" />
</chmod>
<!--
<copy todir="dist/stendhal-client" flatten="true">
<fileset dir="app/neutralinoks">
<include name="LICENSE.txt" />
</fileset>
</copy>
-->
<copy file="app/neutralinojs/LICENSE.txt" tofile="app/neutralinojs/dist/stendhal-client/LICENSE.txt">
<filterchain>
<concatfilter append="app/neutralinojs/extensions/binary/LICENSE.txt" />
</filterchain>
</copy>
<echo file="app/neutralinojs/dist/stendhal-client/extensions/binary/steam_appid.txt">${steam-appid}</echo>
<delete file="${buildroot}/stendhal-client-${version}.zip"/>
<zip destfile="${buildroot}/stendhal-client-${version}.zip">
<zipfileset dir="app/neutralinojs/dist/stendhal-client" prefix="stendhal-client" filemode="755">
<include name="stendhal-client-linux" />
<include name="**/nativehelper*" />
</zipfileset>
<zipfileset dir="app/neutralinojs/dist/stendhal-client" prefix="stendhal-client">
<exclude name="stendhal-client-linux" />
<exclude name="**/nativehelper*" />
</zipfileset>
</zip>
</target>
@ -150,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">
@ -494,7 +535,7 @@
<!-- compile Starter.java for Java 1.7. So we will be able to
display a dialogbox if running on an old Java Version -->
<javac srcdir="${src}" destdir="${build_client}" debug="${javac.debug}" debuglevel="${javac.debuglevel}" source="1.7" target="1.7" deprecation="${javac.deprecation}" includeantruntime="false">
<javac srcdir="${src}" destdir="${build_client}" debug="${javac.debug}" debuglevel="${javac.debuglevel}" source="1.8" target="1.8" deprecation="${javac.deprecation}" includeantruntime="false">
<include name="games/stendhal/client/update/Starter.java"/>
<compilerarg value="-encoding"/>
@ -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 -->
@ -42,6 +43,10 @@
match="&quot;version&quot;: &quot;.*&quot;"
replace="&quot;version&quot;: &quot;${version}&quot;"
byline="true"/>
<replaceregexp file="${src}/games/stendhal/client/update/HttpClient.java"
match="StendhalClient/.* \("
replace="StendhalClient/${version} \("
byline="true"/>
<!-- update location of resources -->
<replaceregexp file="${src}/games/stendhal/client/update/game-default.properties"
@ -85,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">
@ -163,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>
@ -36,7 +41,7 @@
</classpath>
<arg value="src/"/>
<arg value="tests/"/>
<arg value="android/app/src/"/>
<arg value="app/android/client/src"/>
<arg value="java,xml,lua"/>
</java>
@ -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.47.5 Reference Documentation
site_name: Stendhal 1.49.5 Reference Documentation
site_description: A fun, friendly, & free multiplayer online adventure game.
copyright: Copyright © 2003-2024 Stendhal
copyright: Copyright © 2003-2026 Stendhal
site_url: https://stendhalgame.org/
nav:
- Index: /reference/

View file

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

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

@ -1,112 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.8" tiledversion="1.8.4" orientation="orthogonal" renderorder="right-down" width="128" height="128" tilewidth="32" tileheight="32" infinite="0" nextlayerid="9" nextobjectid="1">
<tileset firstgid="1" name="ground/brown" tilewidth="32" tileheight="32" tilecount="5" columns="5">
<image source="../../tileset/ground/brown.png" trans="ff00ff" width="160" height="32"/>
</tileset>
<tileset firstgid="6" name="logic/collision" tilewidth="32" tileheight="32" tilecount="1" columns="1">
<image source="../../tileset/logic/collision.png" width="32" height="32"/>
</tileset>
<tileset firstgid="7" name="logic/creature/chaos" tilewidth="32" tileheight="32" tilecount="7" columns="7">
<image source="../../tileset/logic/creature/chaos.png" width="224" height="32"/>
</tileset>
<tileset firstgid="14" name="ground/ground" tilewidth="32" tileheight="32" tilecount="33" columns="3">
<image source="../../tileset/ground/ground.png" width="96" height="352"/>
</tileset>
<tileset firstgid="47" name="ground/water/pool" tilewidth="32" tileheight="32" tilecount="72" columns="3">
<image source="../../tileset/ground/water/pool.png" width="96" height="768"/>
</tileset>
<tileset firstgid="119" name="ground/brown_edges" tilewidth="32" tileheight="32" tilecount="18" columns="6">
<image source="../../tileset/ground/brown_edges.png" width="192" height="96"/>
</tileset>
<tileset firstgid="137" name="ground/brown_corners" tilewidth="32" tileheight="32" tilecount="8" columns="4">
<image source="../../tileset/ground/brown_corners.png" width="128" height="64"/>
</tileset>
<tileset firstgid="145" name="ground/earth_edges" tilewidth="32" tileheight="32" tilecount="18" columns="6">
<image source="../../tileset/ground/earth_edges.png" width="192" height="96"/>
</tileset>
<tileset firstgid="163" name="ground/grass_corners" tilewidth="32" tileheight="32" tilecount="24" columns="6">
<image source="../../tileset/ground/grass_corners.png" width="192" height="128"/>
</tileset>
<tileset firstgid="187" name="ground/rock/green_crystal2" tilewidth="32" tileheight="32" tilecount="20" columns="5">
<image source="../../tileset/ground/rock/green_crystal2.png" width="160" height="128"/>
</tileset>
<tileset firstgid="207" name="ground/rock/pebbles" tilewidth="32" tileheight="32" tilecount="3" columns="1">
<image source="../../tileset/ground/rock/pebbles.png" width="32" height="96"/>
</tileset>
<tileset firstgid="210" name="ground/rock/blue_crystal" tilewidth="32" tileheight="32" tilecount="20" columns="5">
<image source="../../tileset/ground/rock/blue_crystal.png" width="160" height="128"/>
</tileset>
<tileset firstgid="230" name="ground/rock/rocks_3" tilewidth="32" tileheight="32" tilecount="16" columns="4">
<image source="../../tileset/ground/rock/rocks_3.png" width="128" height="128"/>
</tileset>
<tileset firstgid="246" name="plant/bush/various_small" tilewidth="32" tileheight="32" tilecount="9" columns="3">
<image source="../../tileset/plant/bush/various_small.png" width="96" height="96"/>
</tileset>
<tileset firstgid="255" name="plant/mosses" tilewidth="32" tileheight="32" tilecount="16" columns="4">
<image source="../../tileset/plant/mosses.png" width="128" height="128"/>
</tileset>
<tileset firstgid="271" name="logic/creature/hybrid" tilewidth="32" tileheight="32" tilecount="16" columns="8">
<image source="../../tileset/logic/creature/hybrid.png" trans="ff00ff" width="256" height="64"/>
</tileset>
<tileset firstgid="287" name="ground/grass_2" tilewidth="32" tileheight="32" tilecount="21" columns="3">
<image source="../../tileset/ground/grass_2.png" width="96" height="224"/>
</tileset>
<tileset firstgid="308" name="ground/water/whirlpool" tilewidth="32" tileheight="32" tilecount="8" columns="1">
<image source="../../tileset/ground/water/whirlpool.png" width="32" height="256"/>
</tileset>
<tileset firstgid="316" name="logic/creature/mythical_being" tilewidth="32" tileheight="32" tilecount="24" columns="8">
<image source="../../tileset/logic/creature/mythical_being.png" width="256" height="96"/>
</tileset>
<tileset firstgid="340" name="building/wall/int_sandstone" tilewidth="32" tileheight="32" tilecount="15" columns="3">
<image source="../../tileset/building/wall/int_sandstone.png" width="96" height="160"/>
</tileset>
<tileset firstgid="355" name="building/entrance/shadow" tilewidth="32" tileheight="32" tilecount="3" columns="1">
<image source="../../tileset/building/entrance/shadow.png" width="32" height="96"/>
</tileset>
<tileset firstgid="358" name="plant/flower/hothouse_flowers" tilewidth="32" tileheight="32" tilecount="72" columns="6">
<image source="../../tileset/plant/flower/hothouse_flowers.png" width="192" height="384"/>
</tileset>
<tileset firstgid="430" name="logic/creature/insect" tilewidth="32" tileheight="32" tilecount="5" columns="5">
<image source="../../tileset/logic/creature/insect.png" width="160" height="32"/>
</tileset>
<layer id="1" name="0_floor" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu2cy4rbQBBFFfIHWSZg8JcMhsAku0wW8x2B+X9IDDHRKGqpH/W4VX0Xd+lRVZ2jatmD/fphWV4ZhmEYhmEYhmEYhoHPx6Uc79oYH+7knzc13Mk/Z1rYk3/stLIm/zwh93nD+36+SDAn/7gh+zkifZ+TPX60mZM9Zqy4kz1OLJmTPU48uJP/nLzJPjZ7ib/lPYMZo3Gvkjl+NPcz2WPHYjeTO154Hs8bsp83fBafM5rPeAxuRt/T0YO4kWRP/rEizZ4exMgIT3oQN1LcuBfiRYMRPcCPNhc+J+DGkgMdwIn37OkBPnereSPWlDHoM6YHGLNlrXkScZZ8Lph7fnw2nHtmvb0g98Q52fXl0aNVTZFmgsBfqnfPehD6j8hacg5edWbjjTTTSLVm4o4yz2j1RmeONs9o9ZI/+WdxIFoP3lyzuRCtZm+W2TyIUmdGp8gSLz38vF2IuHfQ08rMYy9EPncipGe2Vh54nDPWfiNkpB9JF0b2DQLzzA54zlFj1si1oTrQ2gvajCP5icof7V5DqCELf00HtDhEYJ/VgdGeZuMfyYGWuXl4R/44DpB/Xv418yP/3PzPZkj+svV+Xv7Fm3vtDMlfh//XP/nyN8gOkL88/9uK/V6Q+Es4QP7v+a/v/ZrM5IC2VxH5Wzigzb+WF/nXRYqJtQNn15mB/7MQf2kHpO/Pnmtp1IzGX2IHaPGx4l+6FvnXs9biZOmA5i5BYr/lX3MGeNTYMk9v7pH5n+0ArxrpgD9/lJq1HcvigCR/tJrJX67nTwH504Gc/Fvr15xLRA96ajrjb82+tY9oDmi4MFIHIv/W3qI6MOKC1LUz8O+dpTd3hCCe/6OMou8D8h/nRAf6+O99BuzNvZeR1b6JnDv/KDugt0c6MDd/etDG/wcofwku3jvHm3eJP/r7QE8Heq5ptVtGr1HLP5sDvR5I/23N2rPzl7qPNK7pPZdZ+Hs5cHZd75nMxN/TgVIN3vMY4X/bYY/OH8kDlPTyv9/7P5f/Pw/07oceyPde2v17Z4F3X9b8I3rQ2tuW/X33v5F/OA96e9q79zPw1/QAyYfRHlr40wE8D6T5P5/wj+jAuvarsg+R2D/47+2AX0n5r6O9F7T8kKynxP9tycP/yAFvH7yzx399Btx25uTNUtsFbybe/Gu+C+zNkB7o8rf+LrB3ZvWg9P+f2t+D8OZGD/T49/wmkDc/eiDHv9eBbE5k9uCMPx3IvQ/W/I8ckPIgqwveHC3404P3/C8JPOjhX/veIKsfR/yjObDl37IDXjYZ5X05mCmKI+s615mNf8mJpwpmT4WUZluas4YXNde+LPu1zs7/aDc88r3jddv9IOHH3t5pzfa1Ec+BPf7SPmj59K0QK/4ZzoEa/mg+1PpS8mObK/l3p4cPoldXIQ9m49/qBvpueeyDrQu1bkR7BtDkL+2P5Rm1PhvOEpl/FBckd1Drs8SZBxnYZ/ZD4lw48iDD54D0oN+DyOx/A/Jq0JkAAAEA
</data>
</layer>
<layer id="2" name="1_terrain" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu2dS48cNRDHPUoUKQp8JT5FVgQWVpsD3wEOUQ6QA5G4ceLUIHGCj8GR55lTeIblERFIwkoBMcVOhdpaP6r86LZ76ic5k52Z7umZf7lctstu5wzDMAzDMAzDMAzDGIHDjXOveMrLm6WvzGgJ6n5zW566s4LA/181/VcJ1R34i7xG7cD0Xx+HO91P2fNPSfndWf0fnVCbfpNoChpTOzjdPXd9+56XtuVF039IDpnOPnj9p1i9Hxeq/Z/ufEznPH9j20/twfQfj1hMF+KUPJr+Y3MU0ezL3SOv+1x3bPut3e+XVFxHNZb4AHqMad8nhx6dJdqiro8E7zXt+4O36SG4LfwqPP+JM5/fG766XhMeA5j2fSCt65xfCj4T/IbF+svA4zicg5GA43NQDnb6feF53wN3/px/b8s/7BxW9+eHjtFINYe66tOM9vuekPdyqM2Y7svA51uRk8gx1xOaxfr9FPPzyxHS3Qedf5HU0yNyTl9sZ/V9WSRzMSW++QY71nz88vA+XKjOW997fWB95zEYz61CrF1eDxJfT7Ecm/XA+3SSMVjTf2ykY7W+/vjD3ePxxnKtR+TQE9v54rzfPM/z/pqNw45HatyFao79u4PAMfBe8wNjkBrL4X152g8Hm3niOQYxO+iX1PycpC9Px2oOEuMCaAe2Hmt5pGN42nG7UHsA+ObzzDfMi2ROHuflcsbwbiT8AJ6fY3bQnlSdrzW3wtsEDcdmA9VJ1fmS+p6Cz+XE7AH9gvmCOlDdQ7m1c+fM3BD6BehTmC/Ip0V8V5NUrIiYL9AjbeeXnqOV2gD2Hc0G4khi+97m5qW5XoD5gouk5mtwnVwvdZ4D+n+vPGbffYE0Fwfprc5TjiKxqQ/8rvtmAzlraHqt8xT0/39kHMvHk+eyh9B61dpj21xzTT3puc5TQu2/b04ptr8HcFzpt0/pKl3Xor0e3/oZKSOshfDVm5L1gHytP8DHkFr5hVhd9OVJ+ea+cu2KMoKPB7BP+tidxXv4Pb/OPF/s9+e+Y+k8lNj8eIyvAs+PtgZCm0Ma4pQ9Ivg7hXKSSvX/sezw6ixtz1okfXzeB4zlmp86v+8PMdrvlWK07wP639s+fkOeA7/vyytqUVr6yFzf/iDzONj/bLS+r6/+994/0fS1ahynZam+bw6o/7fb8p3rX/va/gfmOCA3Psc2Qsfw53seG+c5AD1r3wJpjiQnlWPto7Vf8PXhe7S5XtHYAsZ50nlSH7XGQfjc4g/urC9Pr9PQkdKV/q7SvJkYObZAdcf2O3adhg5fbIzjh744Pze3MidekI7b1NRfOrfSeu5lTmI6xvp6mtzK2Pl9vxlqD/10uscWwOfqSvVvsT/gSH3kVDsg/S6l9nCs1OGeOxvTydG/9Z6QQM99Io7EBrTfRRozPIy/HEQz91Qyt1nKKPGJZJw8x6/lxAy+uSe+L6f0d601/5PLKG2BtJ9XYs8xW9CMTz1RXIcmx7MVI9kA16fVWF9pvDCS/sAo7QAS+91a5MTD5+GYjm9uFfoCdJ7W9G+LpD2o+Z18OsXaBNO/PZoxwlJQJ6zjqfwK038eYjZQsw0IrcWguTa0X7BW/Wk/tRdiv2Etu/Z9RizPaq36c3oYUw75AGifa/kA0Ck0FnSf/F877je6/sjSOTex37GGDUjHH7Q552vRnzO3Pfj0oXsq1fh++Bl0rpffT1n7OWvVnzPHGFNrHwCE5g2wL2j6h6E+4barbw+15gpzPwvGh0x/GaB/izlIen8UH7XHBDDeA04yzl+Sv1aTlvq/vS1vsXKHvF5zPeKcY0K+e7GU7Au4FK1yzlH3O6k3EmrYwlxjQjVZsh1oUfdBe43uPkpsIRUL9jBuQVnSB9TWv4b2HLzG14TXqtlDbd9tQKP/R4nXW2jvQ5rzN9o+eku0Axr9Jxe3gduF1yJFes2aOtWDL5hb/5zY773Ia73pD3AbCM3b97CXYsn+0BpK9kYJ6T+X7we0PlvrB3poD1rYQumeOJc3Yf3nqvuUEj+QYmlfQClZ41Z7T5ye9Nf25bR5nT3ZABC7/+9ce3z0pD8lpx8rua9XL+1BL4T0f2db3pjzQhg5+ktjQ8DswLkrkfYfWNIH5I7rjRwXLMHkwmMAS7cBSM48nCbW3mcbgH22em0DkNJ1YD4b4HuX7mt78FzHbQBSOqeh6W/tkx1c3n3HyfXdBtTSJJVPAuTmflFG3Duq134gpbSd1sQFJTmvvB/6uWvjV0J7yGg+A/f87T0GQFrnftb4XK4/rCXC3OIadkD3qoI8xU+35TN35tugaH1XbBwY6MkHALXXgtX+XDgnakJBG/jEyfbJj+2fHxrbAF+T03aNpD9Q0w/E1hogJ+RzU/VXO+9Ltcy5PwyAcQ2cax/0B2q3Bdr2INTmYpypvcdFDrgvN123ptEf+n+wR/eI+gO14ypNXEDrLd9T7Kc6l5N1nxTtHNFo7b+PJe3AR+7+6Ln34IB2Pzf3+91t+TDy+gj6I7XzX2vsR0uRtguhe+Lwc5XkA1zdpLUHRtK/1Vh+KEb4WXke2Fsu1zfQNeu17ocV8/vISPoDrcdwS31Cjbiwlp+T6N/bGJCU1vN6kj3sWgDjB7X0n1x6HQAwqg3ccvOMw6dyudA+HsdP8x8+G4LnWt0PT+IDgNHaAeDNbblL/l5qfi8n12+u+3utWX8AbADXKqMtLL3HTU+sXX8K2sJd9vw+zfsj2J+Q6j9qDODD5xOAfbSDycliQGANPoDj8wn7ZgcaH/C6W48foOyzHUj1R9AOsKzJHmLx4lrtQKs/h9vDWmyil/5jK67uvsfk5DGAlLXEi/viD0p9gA/qF9ZgC7fY32taO9JCf8oaYkeuPzC6L8A5zMnVbwN8+GKFUeIGn/7IqPddoLT2ARJS9rGUjVxyK9SfXXMP+ksI2UiJXVwSlNXpz5i25X1S5mgPaiLxHSUllis1YhxA8w0fwT/s2ic3tj0swWh+4HLgevkaaWBy+bbwsTtvS2u1q9H0v4Dw+icn1xO0/yBwngduvr4H54UG51zTmMAzyPe5H37XMyZ33h5C2oeOobYQ8xu1fEltOxjBB9DcNFjfdqWja56cznYeeY5d0g5G0B+R5i5quMqP8ZxDu9bxQjwSua5rm/I2JdcWWt2DYSku5Dlv/tdX0hbkcE1gP8H3EiZXHl9q7WCkuq+lRh5rjRLySc/vnue+osa4VsoOaq3JmYt/ATb8NvgAAAEA
</data>
</layer>
<layer id="3" name="2_object" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3bMQrAIAwFUHOrnqU6tPe/RCkOHUUQrPjelDGQ5ROSlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2dcbsDpjpMn8AoEORHQAAaMhRc6O9437eXeMdXw0AAAAAAAAAAACMcbjLAwAW5b8A+sn/AAAAAAAAAAAAAPAvD8b9VLMAAAEA
</data>
</layer>
<layer id="4" name="3_roof" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3c20rDQBAG4Lz/O1lBL6q3HloVD/sceuMUWppeGKOwmbL7ffBDSgM7sJDdzEKGAQAAAAAAAAAAAIBaPiNf2UUAAAAAAAAAAAAAQEdKdgEAAFRVRtebrCJY1F3kPrsI0mwjTxP/l4XqIMdr5C3yuP/tud+f58hLdhGkeY98ZBcB/MvDcFy/6Y/1u2/Wb8b0dNq3e0//6Rtbv/V0aNuhpwMAQJt27wIluwigqpJdAGnKjHv0/fqm79e+qbNefb/2Oevtm7NeDuz3+ma/1zf7PQAAAAAAAAAAMlxEVtlFcGI8J7eVx7qKXFceg79Zck7WkZuFxmKeqTm5rDhu7WcNAHA+rPsA0Laa/QMAAAAAAAAAAAAAAADOxzeEbpmpAAABAA==
</data>
</layer>
<layer id="5" name="4_roof_add" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3OAQ0AAAgDoPfPZyBj3E1IQAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwy7QDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPLYaZVTEAAAEA
</data>
</layer>
<layer id="6" name="objects" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3cQQqDMBAF0Liytr1C73/NIrgqoVA1nZh5b+PGxQdlNF9MKQAAAAAAAABAhHt0AAAAAAAAgIE8ogMAAAAAX83b0Roe8nhGBwAAAICij8pqTy/xmk6PcViPma7MPBjf5zWeq2cBtOeZA0BrvscDe9XmR6v3V91Wv3QmwEp/AQAAwC9u0QEAAAAAACAx/xBAbvbzBQAAAKBGXwQAAFyNven+w3oRODIHzBDO4l4a0xIdAOiOeQ8AAAAAAAAAAAAAAJDXG8ZnpKUAAAEA
</data>
</layer>
<layer id="7" name="collision" width="128" height="128" visible="0" opacity="0.58">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu2T22prQQxD91P//5NLHgohNMlcbC95tgSihXP2WNZyf67r+rFt27Zt27Zt27Zt27bl/Z/oTDbD/U90Npvj/vfTPs/fROezazi/E53b5thfAtltjv0lkN9muF8CO9gcfzq/ncN55Bt6JzuW+Yzo3WyG+yWwn73HfPdG6F3tee6j373+2/P/p3e157XzDr2rvcY+6i16XzuPVdQ7NsOdzmabvV3H/Fl0TptjfwlktTn2l0Bem2N/CWS2c5mP3gW9hx3P/k8j39C72DH8d2+G3sleZxj1zspbNsf9SnozaoatyX7lBiLm2ftsqudVZrAZ9iv3QHd0ohW7/ia6sxOs3u830fk6u0unI6IzdnOnHkdF5+zgrt3NiM6q5u59rYjOTPukjlZF51buic5awZ/cuSqP4u6q/J/VNU/Wuyq+nn6+/p4lBf6Rohkq/V2NqEvOUdEcM9hn9m7+Ou6wQ4V25tD9RHRK56H574ruaKVTOgO9f6bo/ez4W9j9xtb3J63ezac3ojI/56E77O53WrmZT8rKS/fX3e+08+03ReRbecuOv4GdeyAyRu9QkanTDax2OfutGvPMfJ1vQLXfKtFMIzupmPFp9k4WSjRTxRuIYNKB/Z9oppF8yNtbzaEgmmU0D2reynwF0Sw73MCMzJ+/gYw5o7zMn7+Bylvbma0gml8WF4L97FwFrWSlmY/0WHVjO/MUNJuVZj3LJ3vGziwFncC/6gY+zTJ/8zf/+96A+fOO2HN1ZkbmCq3kpDtb6ZO+TcUbWM2YsVt2nzTju/GvyjUzm+asdgM7+ap2yeqT5q1wAxn8szLO5u14A9WK5l+hjE5p7gT/3Wxd+He7gewuo/J04t/1BiJ7jc5AsP80P7JHmnfUHWTO7sh/JjPNWs3f+iGV9fdEd67kkW4oRe4V/f4pHumEUvRuWXM6e6QLBWXfAs1Bhf+7Lmhl7Ro9o5tHe6CVvXPWLHWP7k+rkn/GTFWP7q6iqv0jZmXmruZ/8g2M7pbZ7W7215kn83+I4L86Nyt/RMaZN9SkfgOk7sD/oegbmNlVjf/Py+934P9Q1g3M7qzS2e5+Kvc8K5U7oBS1V+cOHrrbHUTu0233/5TJP6KPqC4zdjiB/0NVN0D2Us2/2w08VHkHlf1k5VXaMUrVN5DZXUUe87+PV/rqKLpnVa901V1050pe7egE0d0reLWf00Rz6Mbfd3CGo7s4RTQXBf5RPXQXzYjkH7V/d9GcSP7RHXQXzYziH71/V9HMaP5VHaiKZqbQMZ2bvBF6Z9X+6F2q7oLeTb2n1Zl0F3f0fwzURHd0sk8U3Wknq4jMQjPoyF/pdqJEs6DdXVE70BxUfUfRnav6taPTRfdtf7/DCtH7RvsXoMjHOgAAAQA=
</data>
</layer>
<layer id="8" name="protection" width="128" height="128" visible="0">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3BAQEAAACAkP6v7ggKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAauuOl9cAAAEA
</data>
</layer>
</map>
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.8" tiledversion="1.8.4" orientation="orthogonal" renderorder="right-down" width="128" height="128" tilewidth="32" tileheight="32" infinite="0" nextlayerid="9" nextobjectid="1">
<tileset firstgid="1" name="ground/brown" tilewidth="32" tileheight="32" tilecount="5" columns="5">
<image source="../../tileset/ground/brown.png" trans="ff00ff" width="160" height="32"/>
</tileset>
<tileset firstgid="6" name="logic/collision" tilewidth="32" tileheight="32" tilecount="1" columns="1">
<image source="../../tileset/logic/collision.png" width="32" height="32"/>
</tileset>
<tileset firstgid="7" name="logic/creature/chaos" tilewidth="32" tileheight="32" tilecount="7" columns="7">
<image source="../../tileset/logic/creature/chaos.png" width="224" height="32"/>
</tileset>
<tileset firstgid="14" name="ground/ground" tilewidth="32" tileheight="32" tilecount="33" columns="3">
<image source="../../tileset/ground/ground.png" width="96" height="352"/>
</tileset>
<tileset firstgid="47" name="ground/water/pool" tilewidth="32" tileheight="32" tilecount="72" columns="3">
<image source="../../tileset/ground/water/pool.png" width="96" height="768"/>
</tileset>
<tileset firstgid="119" name="ground/brown_edges" tilewidth="32" tileheight="32" tilecount="18" columns="6">
<image source="../../tileset/ground/brown_edges.png" width="192" height="96"/>
</tileset>
<tileset firstgid="137" name="ground/brown_corners" tilewidth="32" tileheight="32" tilecount="8" columns="4">
<image source="../../tileset/ground/brown_corners.png" width="128" height="64"/>
</tileset>
<tileset firstgid="145" name="ground/earth_edges" tilewidth="32" tileheight="32" tilecount="18" columns="6">
<image source="../../tileset/ground/earth_edges.png" width="192" height="96"/>
</tileset>
<tileset firstgid="163" name="ground/grass_corners" tilewidth="32" tileheight="32" tilecount="24" columns="6">
<image source="../../tileset/ground/grass_corners.png" width="192" height="128"/>
</tileset>
<tileset firstgid="187" name="ground/rock/green_crystal2" tilewidth="32" tileheight="32" tilecount="20" columns="5">
<image source="../../tileset/ground/rock/green_crystal2.png" width="160" height="128"/>
</tileset>
<tileset firstgid="207" name="ground/rock/pebbles" tilewidth="32" tileheight="32" tilecount="3" columns="1">
<image source="../../tileset/ground/rock/pebbles.png" width="32" height="96"/>
</tileset>
<tileset firstgid="210" name="ground/rock/blue_crystal" tilewidth="32" tileheight="32" tilecount="20" columns="5">
<image source="../../tileset/ground/rock/blue_crystal.png" width="160" height="128"/>
</tileset>
<tileset firstgid="230" name="ground/rock/rocks_3" tilewidth="32" tileheight="32" tilecount="16" columns="4">
<image source="../../tileset/ground/rock/rocks_3.png" width="128" height="128"/>
</tileset>
<tileset firstgid="246" name="plant/bush/various_small" tilewidth="32" tileheight="32" tilecount="9" columns="3">
<image source="../../tileset/plant/bush/various_small.png" width="96" height="96"/>
</tileset>
<tileset firstgid="255" name="plant/mosses" tilewidth="32" tileheight="32" tilecount="16" columns="4">
<image source="../../tileset/plant/mosses.png" width="128" height="128"/>
</tileset>
<tileset firstgid="271" name="logic/creature/hybrid" tilewidth="32" tileheight="32" tilecount="16" columns="8">
<image source="../../tileset/logic/creature/hybrid.png" trans="ff00ff" width="256" height="64"/>
</tileset>
<tileset firstgid="287" name="ground/grass_2" tilewidth="32" tileheight="32" tilecount="21" columns="3">
<image source="../../tileset/ground/grass_2.png" width="96" height="224"/>
</tileset>
<tileset firstgid="308" name="ground/water/whirlpool" tilewidth="32" tileheight="32" tilecount="8" columns="1">
<image source="../../tileset/ground/water/whirlpool.png" width="32" height="256"/>
</tileset>
<tileset firstgid="316" name="logic/creature/mythical_being" tilewidth="32" tileheight="32" tilecount="24" columns="8">
<image source="../../tileset/logic/creature/mythical_being.png" width="256" height="96"/>
</tileset>
<tileset firstgid="340" name="building/wall/int_sandstone" tilewidth="32" tileheight="32" tilecount="15" columns="3">
<image source="../../tileset/building/wall/int_sandstone.png" width="96" height="160"/>
</tileset>
<tileset firstgid="355" name="building/entrance/shadow" tilewidth="32" tileheight="32" tilecount="3" columns="1">
<image source="../../tileset/building/entrance/shadow.png" width="32" height="96"/>
</tileset>
<tileset firstgid="358" name="plant/flower/hothouse_flowers" tilewidth="32" tileheight="32" tilecount="72" columns="6">
<image source="../../tileset/plant/flower/hothouse_flowers.png" width="192" height="384"/>
</tileset>
<tileset firstgid="430" name="logic/creature/insect" tilewidth="32" tileheight="32" tilecount="5" columns="5">
<image source="../../tileset/logic/creature/insect.png" width="160" height="32"/>
</tileset>
<layer id="1" name="0_floor" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu2cy4rbQBBFFfIHWSZg8JcMhsAku0wW8x2B+X9IDDHRKGqpH/W4VX0Xd+lRVZ2jatmD/fphWV4ZhmEYhmEYhmEYhoHPx6Uc79oYH+7knzc13Mk/Z1rYk3/stLIm/zwh93nD+36+SDAn/7gh+zkifZ+TPX60mZM9Zqy4kz1OLJmTPU48uJP/nLzJPjZ7ib/lPYMZo3Gvkjl+NPcz2WPHYjeTO154Hs8bsp83fBafM5rPeAxuRt/T0YO4kWRP/rEizZ4exMgIT3oQN1LcuBfiRYMRPcCPNhc+J+DGkgMdwIn37OkBPnereSPWlDHoM6YHGLNlrXkScZZ8Lph7fnw2nHtmvb0g98Q52fXl0aNVTZFmgsBfqnfPehD6j8hacg5edWbjjTTTSLVm4o4yz2j1RmeONs9o9ZI/+WdxIFoP3lyzuRCtZm+W2TyIUmdGp8gSLz38vF2IuHfQ08rMYy9EPncipGe2Vh54nDPWfiNkpB9JF0b2DQLzzA54zlFj1si1oTrQ2gvajCP5icof7V5DqCELf00HtDhEYJ/VgdGeZuMfyYGWuXl4R/44DpB/Xv418yP/3PzPZkj+svV+Xv7Fm3vtDMlfh//XP/nyN8gOkL88/9uK/V6Q+Es4QP7v+a/v/ZrM5IC2VxH5Wzigzb+WF/nXRYqJtQNn15mB/7MQf2kHpO/Pnmtp1IzGX2IHaPGx4l+6FvnXs9biZOmA5i5BYr/lX3MGeNTYMk9v7pH5n+0ArxrpgD9/lJq1HcvigCR/tJrJX67nTwH504Gc/Fvr15xLRA96ajrjb82+tY9oDmi4MFIHIv/W3qI6MOKC1LUz8O+dpTd3hCCe/6OMou8D8h/nRAf6+O99BuzNvZeR1b6JnDv/KDugt0c6MDd/etDG/wcofwku3jvHm3eJP/r7QE8Heq5ptVtGr1HLP5sDvR5I/23N2rPzl7qPNK7pPZdZ+Hs5cHZd75nMxN/TgVIN3vMY4X/bYY/OH8kDlPTyv9/7P5f/Pw/07oceyPde2v17Z4F3X9b8I3rQ2tuW/X33v5F/OA96e9q79zPw1/QAyYfRHlr40wE8D6T5P5/wj+jAuvarsg+R2D/47+2AX0n5r6O9F7T8kKynxP9tycP/yAFvH7yzx399Btx25uTNUtsFbybe/Gu+C+zNkB7o8rf+LrB3ZvWg9P+f2t+D8OZGD/T49/wmkDc/eiDHv9eBbE5k9uCMPx3IvQ/W/I8ckPIgqwveHC3404P3/C8JPOjhX/veIKsfR/yjObDl37IDXjYZ5X05mCmKI+s615mNf8mJpwpmT4WUZluas4YXNde+LPu1zs7/aDc88r3jddv9IOHH3t5pzfa1Ec+BPf7SPmj59K0QK/4ZzoEa/mg+1PpS8mObK/l3p4cPoldXIQ9m49/qBvpueeyDrQu1bkR7BtDkL+2P5Rm1PhvOEpl/FBckd1Drs8SZBxnYZ/ZD4lw48iDD54D0oN+DyOx/A/Jq0JkAAAEA
</data>
</layer>
<layer id="2" name="1_terrain" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu2dS48cNRDHPUoUKQp8JT5FVgQWVpsD3wEOUQ6QA5G4ceLUIHGCj8GR55lTeIblERFIwkoBMcVOhdpaP6r86LZ76ic5k52Z7umZf7lctstu5wzDMAzDMAzDMAzDGIHDjXOveMrLm6WvzGgJ6n5zW566s4LA/181/VcJ1R34i7xG7cD0Xx+HO91P2fNPSfndWf0fnVCbfpNoChpTOzjdPXd9+56XtuVF039IDpnOPnj9p1i9Hxeq/Z/ufEznPH9j20/twfQfj1hMF+KUPJr+Y3MU0ezL3SOv+1x3bPut3e+XVFxHNZb4AHqMad8nhx6dJdqiro8E7zXt+4O36SG4LfwqPP+JM5/fG766XhMeA5j2fSCt65xfCj4T/IbF+svA4zicg5GA43NQDnb6feF53wN3/px/b8s/7BxW9+eHjtFINYe66tOM9vuekPdyqM2Y7svA51uRk8gx1xOaxfr9FPPzyxHS3Qedf5HU0yNyTl9sZ/V9WSRzMSW++QY71nz88vA+XKjOW997fWB95zEYz61CrF1eDxJfT7Ecm/XA+3SSMVjTf2ykY7W+/vjD3ePxxnKtR+TQE9v54rzfPM/z/pqNw45HatyFao79u4PAMfBe8wNjkBrL4X152g8Hm3niOQYxO+iX1PycpC9Px2oOEuMCaAe2Hmt5pGN42nG7UHsA+ObzzDfMi2ROHuflcsbwbiT8AJ6fY3bQnlSdrzW3wtsEDcdmA9VJ1fmS+p6Cz+XE7AH9gvmCOlDdQ7m1c+fM3BD6BehTmC/Ip0V8V5NUrIiYL9AjbeeXnqOV2gD2Hc0G4khi+97m5qW5XoD5gouk5mtwnVwvdZ4D+n+vPGbffYE0Fwfprc5TjiKxqQ/8rvtmAzlraHqt8xT0/39kHMvHk+eyh9B61dpj21xzTT3puc5TQu2/b04ptr8HcFzpt0/pKl3Xor0e3/oZKSOshfDVm5L1gHytP8DHkFr5hVhd9OVJ+ea+cu2KMoKPB7BP+tidxXv4Pb/OPF/s9+e+Y+k8lNj8eIyvAs+PtgZCm0Ma4pQ9Ivg7hXKSSvX/sezw6ixtz1okfXzeB4zlmp86v+8PMdrvlWK07wP639s+fkOeA7/vyytqUVr6yFzf/iDzONj/bLS+r6/+994/0fS1ahynZam+bw6o/7fb8p3rX/va/gfmOCA3Psc2Qsfw53seG+c5AD1r3wJpjiQnlWPto7Vf8PXhe7S5XtHYAsZ50nlSH7XGQfjc4g/urC9Pr9PQkdKV/q7SvJkYObZAdcf2O3adhg5fbIzjh744Pze3MidekI7b1NRfOrfSeu5lTmI6xvp6mtzK2Pl9vxlqD/10uscWwOfqSvVvsT/gSH3kVDsg/S6l9nCs1OGeOxvTydG/9Z6QQM99Io7EBrTfRRozPIy/HEQz91Qyt1nKKPGJZJw8x6/lxAy+uSe+L6f0d601/5PLKG2BtJ9XYs8xW9CMTz1RXIcmx7MVI9kA16fVWF9pvDCS/sAo7QAS+91a5MTD5+GYjm9uFfoCdJ7W9G+LpD2o+Z18OsXaBNO/PZoxwlJQJ6zjqfwK038eYjZQsw0IrcWguTa0X7BW/Wk/tRdiv2Etu/Z9RizPaq36c3oYUw75AGifa/kA0Ck0FnSf/F877je6/sjSOTex37GGDUjHH7Q552vRnzO3Pfj0oXsq1fh++Bl0rpffT1n7OWvVnzPHGFNrHwCE5g2wL2j6h6E+4barbw+15gpzPwvGh0x/GaB/izlIen8UH7XHBDDeA04yzl+Sv1aTlvq/vS1vsXKHvF5zPeKcY0K+e7GU7Au4FK1yzlH3O6k3EmrYwlxjQjVZsh1oUfdBe43uPkpsIRUL9jBuQVnSB9TWv4b2HLzG14TXqtlDbd9tQKP/R4nXW2jvQ5rzN9o+eku0Axr9Jxe3gduF1yJFes2aOtWDL5hb/5zY773Ia73pD3AbCM3b97CXYsn+0BpK9kYJ6T+X7we0PlvrB3poD1rYQumeOJc3Yf3nqvuUEj+QYmlfQClZ41Z7T5ye9Nf25bR5nT3ZABC7/+9ce3z0pD8lpx8rua9XL+1BL4T0f2db3pjzQhg5+ktjQ8DswLkrkfYfWNIH5I7rjRwXLMHkwmMAS7cBSM48nCbW3mcbgH22em0DkNJ1YD4b4HuX7mt78FzHbQBSOqeh6W/tkx1c3n3HyfXdBtTSJJVPAuTmflFG3Duq134gpbSd1sQFJTmvvB/6uWvjV0J7yGg+A/f87T0GQFrnftb4XK4/rCXC3OIadkD3qoI8xU+35TN35tugaH1XbBwY6MkHALXXgtX+XDgnakJBG/jEyfbJj+2fHxrbAF+T03aNpD9Q0w/E1hogJ+RzU/VXO+9Ltcy5PwyAcQ2cax/0B2q3Bdr2INTmYpypvcdFDrgvN123ptEf+n+wR/eI+gO14ypNXEDrLd9T7Kc6l5N1nxTtHNFo7b+PJe3AR+7+6Ln34IB2Pzf3+91t+TDy+gj6I7XzX2vsR0uRtguhe+Lwc5XkA1zdpLUHRtK/1Vh+KEb4WXke2Fsu1zfQNeu17ocV8/vISPoDrcdwS31Cjbiwlp+T6N/bGJCU1vN6kj3sWgDjB7X0n1x6HQAwqg3ccvOMw6dyudA+HsdP8x8+G4LnWt0PT+IDgNHaAeDNbblL/l5qfi8n12+u+3utWX8AbADXKqMtLL3HTU+sXX8K2sJd9vw+zfsj2J+Q6j9qDODD5xOAfbSDycliQGANPoDj8wn7ZgcaH/C6W48foOyzHUj1R9AOsKzJHmLx4lrtQKs/h9vDWmyil/5jK67uvsfk5DGAlLXEi/viD0p9gA/qF9ZgC7fY32taO9JCf8oaYkeuPzC6L8A5zMnVbwN8+GKFUeIGn/7IqPddoLT2ARJS9rGUjVxyK9SfXXMP+ksI2UiJXVwSlNXpz5i25X1S5mgPaiLxHSUllis1YhxA8w0fwT/s2ic3tj0swWh+4HLgevkaaWBy+bbwsTtvS2u1q9H0v4Dw+icn1xO0/yBwngduvr4H54UG51zTmMAzyPe5H37XMyZ33h5C2oeOobYQ8xu1fEltOxjBB9DcNFjfdqWja56cznYeeY5d0g5G0B+R5i5quMqP8ZxDu9bxQjwSua5rm/I2JdcWWt2DYSku5Dlv/tdX0hbkcE1gP8H3EiZXHl9q7WCkuq+lRh5rjRLySc/vnue+osa4VsoOaq3JmYt/ATb8NvgAAAEA
</data>
</layer>
<layer id="3" name="2_object" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3bMQrAIAwFUHOrnqU6tPe/RCkOHUUQrPjelDGQ5ROSlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2dcbsDpjpMn8AoEORHQAAaMhRc6O9437eXeMdXw0AAAAAAAAAAACMcbjLAwAW5b8A+sn/AAAAAAAAAAAAAPAvD8b9VLMAAAEA
</data>
</layer>
<layer id="4" name="3_roof" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3c20rDQBAG4Lz/O1lBL6q3HloVD/sceuMUWppeGKOwmbL7ffBDSgM7sJDdzEKGAQAAAAAAAAAAAIBaPiNf2UUAAAAAAAAAAAAAQEdKdgEAAFRVRtebrCJY1F3kPrsI0mwjTxP/l4XqIMdr5C3yuP/tud+f58hLdhGkeY98ZBcB/MvDcFy/6Y/1u2/Wb8b0dNq3e0//6Rtbv/V0aNuhpwMAQJt27wIluwigqpJdAGnKjHv0/fqm79e+qbNefb/2Oevtm7NeDuz3+ma/1zf7PQAAAAAAAAAAMlxEVtlFcGI8J7eVx7qKXFceg79Zck7WkZuFxmKeqTm5rDhu7WcNAHA+rPsA0Laa/QMAAAAAAAAAAAAAAADOxzeEbpmpAAABAA==
</data>
</layer>
<layer id="5" name="4_roof_add" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3OAQ0AAAgDoPfPZyBj3E1IQAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwy7QDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPLYaZVTEAAAEA
</data>
</layer>
<layer id="6" name="objects" width="128" height="128">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3cQQqDMBAF0Liytr1C73/NIrgqoVA1nZh5b+PGxQdlNF9MKQAAAAAAAABAhHt0AAAAAAAAgIE8ogMAAAAAX83b0Roe8nhGBwAAAICij8pqTy/xmk6PcViPma7MPBjf5zWeq2cBtOeZA0BrvscDe9XmR6v3V91Wv3QmwEp/AQAAwC9u0QEAAAAAACAx/xBAbvbzBQAAAKBGXwQAAFyNven+w3oRODIHzBDO4l4a0xIdAOiOeQ8AAAAAAAAAAAAAAJDXG8ZnpKUAAAEA
</data>
</layer>
<layer id="7" name="collision" width="128" height="128" visible="0" opacity="0.58">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu2T22prQQxD91P//5NLHgohNMlcbC95tgSihXP2WNZyf67r+rFt27Zt27Zt27Zt27bl/Z/oTDbD/U90Npvj/vfTPs/fROezazi/E53b5thfAtltjv0lkN9muF8CO9gcfzq/ncN55Bt6JzuW+Yzo3WyG+yWwn73HfPdG6F3tee6j373+2/P/p3e157XzDr2rvcY+6i16XzuPVdQ7NsOdzmabvV3H/Fl0TptjfwlktTn2l0Bem2N/CWS2c5mP3gW9hx3P/k8j39C72DH8d2+G3sleZxj1zspbNsf9SnozaoatyX7lBiLm2ftsqudVZrAZ9iv3QHd0ohW7/ia6sxOs3u830fk6u0unI6IzdnOnHkdF5+zgrt3NiM6q5u59rYjOTPukjlZF51buic5awZ/cuSqP4u6q/J/VNU/Wuyq+nn6+/p4lBf6Rohkq/V2NqEvOUdEcM9hn9m7+Ou6wQ4V25tD9RHRK56H574ruaKVTOgO9f6bo/ez4W9j9xtb3J63ezac3ojI/56E77O53WrmZT8rKS/fX3e+08+03ReRbecuOv4GdeyAyRu9QkanTDax2OfutGvPMfJ1vQLXfKtFMIzupmPFp9k4WSjRTxRuIYNKB/Z9oppF8yNtbzaEgmmU0D2reynwF0Sw73MCMzJ+/gYw5o7zMn7+Bylvbma0gml8WF4L97FwFrWSlmY/0WHVjO/MUNJuVZj3LJ3vGziwFncC/6gY+zTJ/8zf/+96A+fOO2HN1ZkbmCq3kpDtb6ZO+TcUbWM2YsVt2nzTju/GvyjUzm+asdgM7+ap2yeqT5q1wAxn8szLO5u14A9WK5l+hjE5p7gT/3Wxd+He7gewuo/J04t/1BiJ7jc5AsP80P7JHmnfUHWTO7sh/JjPNWs3f+iGV9fdEd67kkW4oRe4V/f4pHumEUvRuWXM6e6QLBWXfAs1Bhf+7Lmhl7Ro9o5tHe6CVvXPWLHWP7k+rkn/GTFWP7q6iqv0jZmXmruZ/8g2M7pbZ7W7215kn83+I4L86Nyt/RMaZN9SkfgOk7sD/oegbmNlVjf/Py+934P9Q1g3M7qzS2e5+Kvc8K5U7oBS1V+cOHrrbHUTu0233/5TJP6KPqC4zdjiB/0NVN0D2Us2/2w08VHkHlf1k5VXaMUrVN5DZXUUe87+PV/rqKLpnVa901V1050pe7egE0d0reLWf00Rz6Mbfd3CGo7s4RTQXBf5RPXQXzYjkH7V/d9GcSP7RHXQXzYziH71/V9HMaP5VHaiKZqbQMZ2bvBF6Z9X+6F2q7oLeTb2n1Zl0F3f0fwzURHd0sk8U3Wknq4jMQjPoyF/pdqJEs6DdXVE70BxUfUfRnav6taPTRfdtf7/DCtH7RvsXoMjHOgAAAQA=
</data>
</layer>
<layer id="8" name="protection" width="128" height="128" visible="0">
<data encoding="base64" compression="gzip">
H4sIAAAAAAAACu3BAQEAAACAkP6v7ggKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAauuOl9cAAAEA
</data>
</layer>
</map>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

Binary file not shown.

View file

@ -2,6 +2,24 @@ Changelog
---------
---------
1.49
*world*
- outfit prices in Gwen's shop reduced
*web client*
- improved lighting effects
*bug fixes*
- fixed invalid head outfit index of Mr Ross
- fixed silent NPC paths
- fixed desktop webview client window freezing on Windows when closed
- fixed nativehelper extension process not exiting cleanly on Windows
- set User-Agent string for Wikipedia API requests
- trimmed large music files to decrease size to 2MB or less
- fixed Gourmet achievement
1.48
*world*
@ -688,7 +706,7 @@ Changelog
*other*
- started to work on a list of all contributors, please help to add to it:
https://github.com/arianne/stendhal/blob/master/doc/contributors.md
https://codeberg.org/arianne/stendhal/src/commit/master/doc/contributors.md
1.34
@ -1396,7 +1414,7 @@ the huge task of creating & replacing sprite & tileset images.
- Support Postgres databases in addition to H2 (recommended) and MySQL
- Setup Travis for continuous integration tests and fixed some order dependent tests
- Migrated test coverage analysis from Cobertura to Jacoco
- Git repository is mirrored instantaneously between Github and Sourceforge in both directions
- Git repository is mirrored instantaneously between Codeberg, Github and Sourceforge in both directions
- Yearly special event quests now pick up the current year as part of their quest slot automatically
- Backend for puzzles
@ -2559,7 +2577,7 @@ read https://stendhalgame.org/wiki/Automatic_Software_Updater
- Tab completion for online nick names works now without /who called before
- orc spearman creature
- /atlas /faq and /manual commands to open useful pages in browser
- Harold tweets trades, the official stendhal server uses https://twitter.com/stendhaltrade
- Harold tweets trades, the official stendhal server uses https://mastodon.social/@stendhaltrade
- magical damage types of weapons (fire, ice ...)
- resistance/vulnerability of creatures to magical weapons (fire, ice, ...)
- attack sweeps look different depending on the attack type

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

@ -569,7 +569,7 @@ class KTextEdit extends JComponent {
*/
class LinkListener {
/** Allowed patterns for links to be opened in a browser. */
String p = "^(http|https)://(|www\\.)(faiumoni.de|stendhalgame.org|arianne.sf.net|arianne-project.org|arianne.sourceforge.net|sourceforge.net|sf.net|download.oracle.com|libregamewiki.org|freesound.org|opengameart.org|openclipart.org|github.io|github.com|arianne.github.io|postsai.github.io)(/.*)?";
String p = "^(http|https)://(|www\\.)(faiumoni.de|stendhalgame.org|arianne.sf.net|arianne-project.org|codeberg.org|arianne.sourceforge.net|sourceforge.net|sf.net|download.oracle.com|libregamewiki.org|freesound.org|opengameart.org|openclipart.org|github.io|github.com|arianne.github.io|postsai.github.io)(/.*)?";
final Pattern whitelist = Pattern.compile(p);

View file

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

View file

@ -1,5 +1,5 @@
/***************************************************************************
* (C) Copyright 2012-2022 Faiumoni e. V. *
* (C) Copyright 2012-2025 Faiumoni e. V. *
***************************************************************************
***************************************************************************
* *
@ -15,6 +15,8 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.Enumeration;
@ -60,8 +62,8 @@ public class DataLoader {
File file = contentFilenameMapping.get(slashlessName);
if (file != null) {
try {
return new URL("jar:" + file.toURI().toASCIIString() + "!/" + slashlessName);
} catch (MalformedURLException e) {
return new URI("jar:" + file.toURI().toASCIIString() + "!/" + slashlessName).toURL();
} catch (MalformedURLException | URISyntaxException e) {
logger.error(e, e);
}
}

View file

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

View file

@ -1,5 +1,5 @@
/***************************************************************************
* (C) Copyright 2003-2016 - Stendhal *
* (C) Copyright 2003-2025 - Stendhal *
***************************************************************************
***************************************************************************
* *
@ -20,6 +20,7 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URL;
import java.util.HashSet;
import java.util.Properties;
@ -109,13 +110,15 @@ public class HttpClient {
// sometimes problems with the webservers being slow or not responding
// at all.
try {
final URL url = new URL(urlString);
final URL url = new URI(urlString).toURL();
int retryCount = 0;
int myTimeout = timeout;
while (is == null) {
retryCount++;
try {
connection = (HttpURLConnection) url.openConnection();
// Wikipedia API now requires User-Agent string
connection.setRequestProperty("User-Agent", "StendhalClient/1.49.5 (https://arianne-project.org/contact.html)");
connection.setConnectTimeout(myTimeout);
connection.setInstanceFollowRedirects(true);
connection.setUseCaches(false);
@ -134,7 +137,7 @@ public class HttpClient {
if(!passedRedirects.contains(newUrl)) {
// open the new connnection again
passedRedirects.add(newUrl);
connection = (HttpURLConnection) new URL(newUrl).openConnection();
connection = (HttpURLConnection) new URI(newUrl).toURL().openConnection();
redirect = isRedirect(connection.getResponseCode());
} else {
System.err.println(String.format("The URL '%s' leads into a circular redirect.", url));

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

@ -1,6 +1,6 @@
/* $Id$ */
/***************************************************************************
* (C) Copyright 2003-2024 - Stendhal *
* (C) Copyright 2003-2025 - Stendhal *
***************************************************************************
***************************************************************************
* *
@ -16,7 +16,7 @@ import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URI;
import java.util.List;
import java.util.Properties;
import java.util.zip.GZIPInputStream;
@ -302,8 +302,8 @@ public class TMXLoader {
final String xmlFile = makeUrl(filename);
// xmlPath = makeUrl(xmlPath);
final URL url = new URL(xmlFile);
is = url.openStream();
final URI uri = new URI(xmlFile);
is = uri.toURL().openStream();
}
// Wrap with GZIP decoder for .tmx.gz files

View file

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

View file

@ -1,5 +1,5 @@
/***************************************************************************
* (C) Copyright 2003-2023 - Stendhal *
* (C) Copyright 2003-2025 - Stendhal *
***************************************************************************
***************************************************************************
* *
@ -26,7 +26,6 @@ import games.stendhal.server.entity.npc.behaviour.journal.ProducerRegister;
import games.stendhal.server.entity.npc.condition.AndCondition;
import games.stendhal.server.entity.npc.condition.PlayerProducedNumberOfItemsCondition;
import games.stendhal.server.entity.npc.condition.QuestCompletedCondition;
import games.stendhal.server.entity.npc.condition.QuestStateStartsWithCondition;
import games.stendhal.server.entity.player.Player;
@ -64,7 +63,7 @@ public class ProductionAchievementFactory extends AbstractAchievementFactory {
Achievement.MEDIUM_BASE_SCORE, true,
new AndCondition(
new PlayerProducedNumberOfItemsCondition(1, foods),
new QuestStateStartsWithCondition("coal_for_haunchy","waiting;"))));
new QuestCompletedCondition("coal_for_haunchy"))));
final List<String> drinklist = producerRegister.getProducedItemNames("drink");

View file

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

View file

@ -32,15 +32,14 @@ public class IdleBehaviourFactory {
} else {
behaviour = new WanderIdleBehaviour();
}
} else if (aiProfiles.containsKey("patrolling") && aiProfiles.containsKey("wander")) {
// randomly select between "patrolling" & "wander" for individual entity instance
behaviour = Rand.flipCoin() ? new Patroller() : behaviour;
if (aiProfiles.containsKey("patrolling")) {
// randomly select between "patrolling" & "wander" for individual entity instance
behaviour = Rand.flipCoin() ? new Patroller() : behaviour;
}
} else if (aiProfiles.containsKey("patrolling")) {
behaviour = new Patroller();
} else if (aiProfiles.containsKey("camouflage")) {
behaviour = new CamouflagedIdleBehaviour();
} else if (aiProfiles.containsKey("wander")) {
behaviour = new WanderIdleBehaviour();
}
return behaviour;
}

View file

@ -1,6 +1,6 @@
/* $Id$ */
/***************************************************************************
* (C) Copyright 2003-2024 - Marauroa *
* (C) Copyright 2003-2025 - Marauroa *
***************************************************************************
***************************************************************************
* *
@ -326,10 +326,10 @@ public abstract class NPC extends DressedEntity {
// handled in pre-logic
return;
} else {
if (atMovementRadius()) {
onOutsideMovementRadius();
}
if (!hasPath()) {
if (atMovementRadius()) {
onOutsideMovementRadius();
}
if (logger.isDebugEnabled()) {
String title = getTitle();
String zone = getZone().getName();

View file

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

View file

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

View file

@ -1,5 +1,5 @@
/***************************************************************************
* Copyright © 2020-2024 - Faiumoni e. V. *
* Copyright © 2020-2025 - Faiumoni e. V. *
***************************************************************************
***************************************************************************
* *
@ -376,7 +376,7 @@ public class CollectEnemyData extends AbstractQuest {
new ChatAction() {
@Override
public void fire(final Player player, final Sentence sentence, final EventRaiser raiser) {
npc.say("Thanks so much for you help. Now I have all the information I need to complete my #bestiary."
npc.say("Thanks so much for your help. Now I have all the information I need to complete my #bestiary."
+ " If you would like one of your own, I can sell you one.");
player.addKarma(200.0);
}}));

View file

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

View file

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

View file

@ -1,6 +1,6 @@
/* $Id$ */
/***************************************************************************
* (C) Copyright 2003-2011 - Stendhal *
* (C) Copyright 2003-2025 - Stendhal *
***************************************************************************
***************************************************************************
* *
@ -18,7 +18,8 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@ -80,13 +81,14 @@ public class UpdatePropUpdater {
* loads the current version of the update.properties
*
* @throws IOException in case of an input/output error
* @throws URISyntaxException
*/
private void loadOldUpdateProperties() throws IOException {
private void loadOldUpdateProperties() throws IOException, URISyntaxException {
prop = new Properties();
InputStream is;
if (oldFile.indexOf(":") > 2) {
URL url = new URL(oldFile);
is = url.openStream();
URI uri = new URI(oldFile);
is = uri.toURL().openStream();
} else {
is = new FileInputStream(oldFile);
}

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);
}
}

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