From e00e6b9b4c0f0b0a45e5ad3ba9591a84b401669a Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Sun, 21 Dec 2025 22:19:28 +0100
Subject: [PATCH 001/162] updated version number
---
.gitignore | 1 +
app/android/client/build.gradle | 4 ++--
app/android/client/src/main/res/values/strings.xml | 2 +-
app/neutralinojs/neutralino.config.json | 2 +-
bin/runserver.bat | 2 +-
bin/runserver.sh | 2 +-
build.ant.properties | 4 ++--
buildtools/mkdocs/mkdocs.yml | 2 +-
doc/CHANGES.txt | 2 +-
src/games/stendhal/client/update/HttpClient.java | 2 +-
src/games/stendhal/common/Debug.java | 2 +-
11 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/.gitignore b/.gitignore
index adf7a8202c..a91f40817a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,3 +50,4 @@ build_server_xmlconf
/node_modules/
/log/
/build-archive/
+/bin/
diff --git a/app/android/client/build.gradle b/app/android/client/build.gradle
index 6fa80a9a75..407855e0ba 100644
--- a/app/android/client/build.gradle
+++ b/app/android/client/build.gradle
@@ -10,8 +10,8 @@ android {
applicationId namespace
minSdk 21
targetSdk 32
- versionCode 1048005
- versionName "1.48.5"
+ versionCode 1049000
+ versionName "1.49"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
targetSdkVersion 32
minSdkVersion 21
diff --git a/app/android/client/src/main/res/values/strings.xml b/app/android/client/src/main/res/values/strings.xml
index fe759d152f..4bc5fd3b6d 100644
--- a/app/android/client/src/main/res/values/strings.xml
+++ b/app/android/client/src/main/res/values/strings.xml
@@ -5,6 +5,6 @@
Stendhal
stendhalprod8gps5y99pu
prod
- 1.48.5
+ 1.49
diff --git a/app/neutralinojs/neutralino.config.json b/app/neutralinojs/neutralino.config.json
index 5113267710..e41d306327 100644
--- a/app/neutralinojs/neutralino.config.json
+++ b/app/neutralinojs/neutralino.config.json
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/neutralinojs/neutralinojs/main/schemas/neutralino.config.schema.json",
"applicationId": "org.stendhalgame.client",
- "version": "1.48.5",
+ "version": "1.49",
"defaultMode": "window",
"port": 0,
"documentRoot": "/resources/",
diff --git a/bin/runserver.bat b/bin/runserver.bat
index a6d9623195..07f0694868 100644
--- a/bin/runserver.bat
+++ b/bin/runserver.bat
@@ -1,6 +1,6 @@
@echo off
-set STENDHAL_VERSION=1.48.5
+set STENDHAL_VERSION=1.49
set SERVER_JAR=stendhal-server-%STENDHAL_VERSION%.jar
:: change to server directory
diff --git a/bin/runserver.sh b/bin/runserver.sh
index 1a2e475093..c725321791 100755
--- a/bin/runserver.sh
+++ b/bin/runserver.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
-STENDHAL_VERSION="1.48.5"
+STENDHAL_VERSION="1.49"
SERVER_JAR="stendhal-server-${STENDHAL_VERSION}.jar"
# change to server directory
diff --git a/build.ant.properties b/build.ant.properties
index 427f93c1f9..b75035a282 100644
--- a/build.ant.properties
+++ b/build.ant.properties
@@ -92,9 +92,9 @@ version_server = http://arianne.sourceforge.net/stendhal.version
# current version of stendhal
version.old = 1.48
-version = 1.48.5
+version = 1.49
# FIXME: dynamic method to set this property?
-version.android = 1048005
+version.android = 1049000
# javac options
javac.deprecation = true
diff --git a/buildtools/mkdocs/mkdocs.yml b/buildtools/mkdocs/mkdocs.yml
index 1483f53630..c3b158d27a 100644
--- a/buildtools/mkdocs/mkdocs.yml
+++ b/buildtools/mkdocs/mkdocs.yml
@@ -1,4 +1,4 @@
-site_name: Stendhal 1.48.5 Reference Documentation
+site_name: Stendhal 1.49 Reference Documentation
site_description: A fun, friendly, & free multiplayer online adventure game.
copyright: Copyright © 2003-2025 Stendhal
site_url: https://stendhalgame.org/
diff --git a/doc/CHANGES.txt b/doc/CHANGES.txt
index e14942632c..20be3f04f1 100644
--- a/doc/CHANGES.txt
+++ b/doc/CHANGES.txt
@@ -12,7 +12,7 @@ Changelog
*bug fixes*
- fixed invalid head outfit index of Mr Ross
-- fixed silent NPC fixed paths
+- fixed silent NPC paths
- fixed desktop webview client window freezing on Windows when closed
- fixed nativehelper extension process not exiting cleanly on Windows
- set User-Agent string for Wikipedia API requests
diff --git a/src/games/stendhal/client/update/HttpClient.java b/src/games/stendhal/client/update/HttpClient.java
index f4143f3956..e065295cb6 100644
--- a/src/games/stendhal/client/update/HttpClient.java
+++ b/src/games/stendhal/client/update/HttpClient.java
@@ -118,7 +118,7 @@ public class HttpClient {
try {
connection = (HttpURLConnection) url.openConnection();
// Wikipedia API now requires User-Agent string
- connection.setRequestProperty("User-Agent", "StendhalClient/1.48.5 (https://arianne-project.org/contact.html)");
+ connection.setRequestProperty("User-Agent", "StendhalClient/1.49 (https://arianne-project.org/contact.html)");
connection.setConnectTimeout(myTimeout);
connection.setInstanceFollowRedirects(true);
connection.setUseCaches(false);
diff --git a/src/games/stendhal/common/Debug.java b/src/games/stendhal/common/Debug.java
index 9ff2b7ed8f..6b4849bb7d 100644
--- a/src/games/stendhal/common/Debug.java
+++ b/src/games/stendhal/common/Debug.java
@@ -26,7 +26,7 @@ public class Debug {
/** version. */
// Note: This line is updated by build.xml using a regexp so be sure to adjust it in case you modify this line.
- public static final String VERSION = "1.48.5";
+ public static final String VERSION = "1.49";
/** pre release suffix */
// Note: This line is updated by build.xml using a regexp so be sure to adjust it in case you modify this line.
From 6f783d3ef69bb6edc883d0f581f222e2ce80c0eb Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Mon, 22 Dec 2025 17:02:31 +0100
Subject: [PATCH 002/162] moved ShowItemListEvent and ShowOutfitListEvent out
of EventRegistry
---
doc/CHANGES.txt | 2 +-
src/js/stendhal/EventRegistry.ts | 123 +------------------
src/js/stendhal/event/ShowItemListEvent.ts | 94 ++++++++++++++
src/js/stendhal/event/ShowOutfitListEvent.ts | 71 +++++++++++
4 files changed, 170 insertions(+), 120 deletions(-)
create mode 100644 src/js/stendhal/event/ShowItemListEvent.ts
create mode 100644 src/js/stendhal/event/ShowOutfitListEvent.ts
diff --git a/doc/CHANGES.txt b/doc/CHANGES.txt
index e14942632c..20be3f04f1 100644
--- a/doc/CHANGES.txt
+++ b/doc/CHANGES.txt
@@ -12,7 +12,7 @@ Changelog
*bug fixes*
- fixed invalid head outfit index of Mr Ross
-- fixed silent NPC fixed paths
+- fixed silent NPC paths
- fixed desktop webview client window freezing on Windows when closed
- fixed nativehelper extension process not exiting cleanly on Windows
- set User-Agent string for Wikipedia API requests
diff --git a/src/js/stendhal/EventRegistry.ts b/src/js/stendhal/EventRegistry.ts
index f4b2448c7b..6a92619cc1 100644
--- a/src/js/stendhal/EventRegistry.ts
+++ b/src/js/stendhal/EventRegistry.ts
@@ -24,14 +24,11 @@ import { PlayerLoggedOnEvent } from "./event/PlayerLoggedOnEvent";
import { PlayerLoggedOutEvent } from "./event/PlayerLoggedOutEvent";
import { ProgressStatusEvent } from "./event/ProgressStatusEvent";
import { RPEvent } from "./event/RPEvent";
+import { ShowItemListEvent } from "./event/ShowItemListEvent";
+import { ShowOutfitListEvent } from "./event/ShowOutfitListEvent";
import { SoundEvent } from "./event/SoundEvent";
import { TradeEvent } from "./event/TradeEvent";
import { ViewChangeEvent } from "./event/ViewChangeEvent";
-
-import { ui } from "./ui/UI";
-
-import { DialogContentComponent } from "./ui/toolkit/DialogContentComponent";
-
import { Chat } from "./util/Chat";
@@ -155,120 +152,8 @@ export class EventRegistry {
}
}); // 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("show_item_list", new ShowItemListEvent());
+ this.register("show_outfit_list", new ShowOutfitListEvent());
this.register("sound_event", new SoundEvent());
diff --git a/src/js/stendhal/event/ShowItemListEvent.ts b/src/js/stendhal/event/ShowItemListEvent.ts
new file mode 100644
index 0000000000..e09c065679
--- /dev/null
+++ b/src/js/stendhal/event/ShowItemListEvent.ts
@@ -0,0 +1,94 @@
+/***************************************************************************
+ * Copyright © 2024 - 2025 Faiumoni e. V. *
+ ***************************************************************************
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU Affero General Public License as *
+ * published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+import { RPEvent } from "./RPEvent";
+
+import { ui } from "../ui/UI";
+import { DialogContentComponent } from "../ui/toolkit/DialogContentComponent";
+
+declare var stendhal: any;
+
+
+export class ShowItemListEvent extends RPEvent {
+
+ title!: string;
+ caption!: string;
+ content!: any;
+
+ execute(_entity: any) {
+ let title = "Items";
+ let caption = "";
+ let items = [];
+ console.log("Items", this);
+
+ 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));
+ }
+
+}
diff --git a/src/js/stendhal/event/ShowOutfitListEvent.ts b/src/js/stendhal/event/ShowOutfitListEvent.ts
new file mode 100644
index 0000000000..e5f55d865f
--- /dev/null
+++ b/src/js/stendhal/event/ShowOutfitListEvent.ts
@@ -0,0 +1,71 @@
+/***************************************************************************
+ * Copyright © 2024 - 2025 Faiumoni e. V. *
+ ***************************************************************************
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU Affero General Public License as *
+ * published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+import { RPEvent } from "./RPEvent";
+
+import { ui } from "../ui/UI";
+import { DialogContentComponent } from "../ui/toolkit/DialogContentComponent";
+
+declare var stendhal: any;
+
+
+export class ShowOutfitListEvent extends RPEvent {
+
+ title!: string;
+ caption!: string;
+ outfits!: string;
+
+ execute(_entity: any) {
+ let title = "Outfits";
+ let caption = "";
+ let outfits = [];
+
+ if (this.hasOwnProperty("title")) {
+ title = this["title"];
+ }
+ if (this.hasOwnProperty("caption")) {
+ caption = this["caption"];
+ }
+ if (this.hasOwnProperty("outfits")) {
+ for (let o of this["outfits"].split(":")) {
+ let parts = o.split(";");
+ if (parts.length > 2) {
+ outfits.push([parts[0], parts[1], parts[2]]);
+ }
+ }
+ }
+ if (this.hasOwnProperty("show_base")) {
+ //Chat.log("normal", this["show_base"]);
+ }
+
+ const content = new class extends DialogContentComponent { }("empty-div-template");
+ content.componentElement.classList.add("shopsign");
+ const 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));
+ }
+}
From 9e56945faf74401c755071d3999aaebeeba9b4e7 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Mon, 22 Dec 2025 17:31:24 +0100
Subject: [PATCH 003/162] use proper table for item shop signs
---
src/js/css/main.css | 13 ++++
src/js/stendhal/event/ShowItemListEvent.ts | 77 +++++++++++++++-------
2 files changed, 66 insertions(+), 24 deletions(-)
diff --git a/src/js/css/main.css b/src/js/css/main.css
index 86d9b999e4..91de30c7e9 100644
--- a/src/js/css/main.css
+++ b/src/js/css/main.css
@@ -708,3 +708,16 @@ img#click-indicator {
.pad-contents {
padding: 0.5em;
}
+
+table.shoptable {
+ border-collapse: collapse;
+}
+
+table.shoptable th, table.shoptable td {
+ border: 1px solid var(--text-color);
+ padding: 2px;
+}
+
+td.number {
+ text-align: right;
+}
diff --git a/src/js/stendhal/event/ShowItemListEvent.ts b/src/js/stendhal/event/ShowItemListEvent.ts
index e09c065679..8168988cc2 100644
--- a/src/js/stendhal/event/ShowItemListEvent.ts
+++ b/src/js/stendhal/event/ShowItemListEvent.ts
@@ -60,33 +60,62 @@ export class ShowItemListEvent extends RPEvent {
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);
+ // Create table
+ const table = document.createElement("table");
+ table.className = "shoptable";
+ content.componentElement.appendChild(table);
- // 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);
+ // Caption
+ const tableCaption = document.createElement("caption");
+ tableCaption.className = "shopcaption";
+ tableCaption.textContent = caption;
+ table.appendChild(tableCaption);
+
+ // Header
+ const thead = document.createElement("thead");
+ const headerRow = document.createElement("tr");
+
+ const headers = ["Item", "Price", "Description"];
+ for (const text of headers) {
+ const th = document.createElement("th");
+ th.textContent = text;
+ headerRow.appendChild(th);
}
+ thead.appendChild(headerRow);
+ table.appendChild(thead);
+
+ // Body
+ const tbody = document.createElement("tbody");
+
+ for (const i of items) {
+ const row = document.createElement("tr");
+
+ // Item (image)
+ const itemCell = document.createElement("td");
+ itemCell.appendChild(
+ stendhal.data.sprites.get(
+ stendhal.paths.sprites + "/items/" + i.img
+ )
+ );
+ row.appendChild(itemCell);
+
+ // Price
+ const priceCell = document.createElement("td");
+ priceCell.className = "number";
+ priceCell.textContent = i.price;
+ row.appendChild(priceCell);
+
+ // Description
+ const descCell = document.createElement("td");
+ descCell.textContent = i.desc;
+ row.appendChild(descCell);
+
+ tbody.appendChild(row);
+ }
+
+ table.appendChild(tbody);
+
stendhal.ui.globalInternalWindow.set(
ui.createSingletonFloatingWindow(title, content, 20, 20));
}
From a3bd4b328a3443d89ca2a998d2b67615f293ff65 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Mon, 22 Dec 2025 18:05:49 +0100
Subject: [PATCH 004/162] code cleanup
---
src/js/stendhal/event/ShowItemListEvent.ts | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/src/js/stendhal/event/ShowItemListEvent.ts b/src/js/stendhal/event/ShowItemListEvent.ts
index 8168988cc2..3857a05e31 100644
--- a/src/js/stendhal/event/ShowItemListEvent.ts
+++ b/src/js/stendhal/event/ShowItemListEvent.ts
@@ -28,7 +28,6 @@ export class ShowItemListEvent extends RPEvent {
let title = "Items";
let caption = "";
let items = [];
- console.log("Items", this);
if (this.hasOwnProperty("title")) {
title = this["title"];
@@ -37,11 +36,10 @@ export class ShowItemListEvent extends RPEvent {
caption = this["caption"];
}
if (this.hasOwnProperty("content")) {
- for (var obj in this["content"]) {
+ for (let obj in this["content"]) {
if (this["content"].hasOwnProperty(obj)) {
- var slotObj = this["content"][obj];
- var data = this["content"][obj]["a"];
- const i = {
+ let data = this["content"][obj]["a"];
+ let item = {
clazz: data["class"],
subclass: data["subclass"],
img: data["class"] + "/" + data["subclass"] + ".png",
@@ -50,10 +48,10 @@ export class ShowItemListEvent extends RPEvent {
}
// seller shops prefix prices with "-"
- if (i.price.startsWith("-")) {
- i.price = i.price.substr(1);
+ if (item.price.startsWith("-")) {
+ item.price = item.price.substr(1);
}
- items.push(i);
+ items.push(item);
}
}
}
From c68f0a59d5bbb0f74b56fc367905e06e6e7d2a65 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Mon, 22 Dec 2025 18:06:00 +0100
Subject: [PATCH 005/162] show outfits in shop sign dialog
---
src/js/stendhal/event/ShowOutfitListEvent.ts | 103 ++++++++++++++++---
1 file changed, 89 insertions(+), 14 deletions(-)
diff --git a/src/js/stendhal/event/ShowOutfitListEvent.ts b/src/js/stendhal/event/ShowOutfitListEvent.ts
index e5f55d865f..dbaf651b4f 100644
--- a/src/js/stendhal/event/ShowOutfitListEvent.ts
+++ b/src/js/stendhal/event/ShowOutfitListEvent.ts
@@ -39,7 +39,11 @@ export class ShowOutfitListEvent extends RPEvent {
for (let o of this["outfits"].split(":")) {
let parts = o.split(";");
if (parts.length > 2) {
- outfits.push([parts[0], parts[1], parts[2]]);
+ outfits.push({
+ name: parts[0],
+ outfit: parts[1],
+ price: parts[2]
+ });
}
}
}
@@ -49,23 +53,94 @@ export class ShowOutfitListEvent extends RPEvent {
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);
+
+ const table = document.createElement("table");
+ table.className = "shoptable";
+ content.componentElement.appendChild(table);
- // 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);
+ // Caption
+ const tableCaption = document.createElement("caption");
+ tableCaption.className = "shopcaption";
+ tableCaption.textContent = caption;
+ table.appendChild(tableCaption);
+
+ // Header
+ const thead = document.createElement("thead");
+ const headerRow = document.createElement("tr");
+
+ const headers = ["Name", "Outfit", "Price"];
+ for (const text of headers) {
+ const th = document.createElement("th");
+ th.textContent = text;
+ headerRow.appendChild(th);
}
+ thead.appendChild(headerRow);
+ table.appendChild(thead);
+
+ // Body
+ const tbody = document.createElement("tbody");
+
+ for (const outfit of outfits) {
+ const row = document.createElement("tr");
+
+ // Name
+ const nameCell = document.createElement("td");
+ nameCell.textContent = outfit.name;
+ row.appendChild(nameCell);
+
+ // Image
+ const imageCell = document.createElement("td");
+ let url = ShowOutfitListEvent.outfitToUrl(outfit.outfit);
+ const img1 = document.createElement("img");
+ const img2 = document.createElement("img");
+ img1.src = url;
+ img2.src = url + "&offset=0";
+ imageCell.append(img1);
+ imageCell.append(img2);
+ row.appendChild(imageCell);
+
+ // Price
+ const priceCell = document.createElement("td");
+ priceCell.className = "number";
+ priceCell.textContent = outfit.price;
+ row.appendChild(priceCell);
+
+ tbody.appendChild(row);
+ }
+
+ table.appendChild(tbody);
+
stendhal.ui.globalInternalWindow.set(
ui.createSingletonFloatingWindow(title, content, 20, 20));
}
+
+ private static outfitToUrl(outfit?: string): string {
+ if (!outfit) {
+ return "";
+ }
+
+ // Required order
+ const order = ["body", "dress", "head", "mouth", "eyes", "mask", "hair", "hat", "detail"];
+
+ // Parse input into a map
+ let values: Record = {};
+
+ for (let part of outfit.split(",")) {
+ let [key, value] = part.split("=");
+ values[key] = Number(value);
+ }
+
+ // Build path
+ let parts = order.map(key => {
+ let value = values[key] ?? 0;
+ return `${key}-${value}-0`;
+ });
+
+ let url =
+ "https://stendhalgame.org/createoutfit.php?rewritten=true&dialog=shop&outfit=" +
+ parts.join("_");
+
+ return url;
+ }
}
From 59f2ba9fbdc515c29bb813145a7b6a82dbd2174f Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Mon, 22 Dec 2025 17:02:31 +0100
Subject: [PATCH 006/162] moved ShowItemListEvent and ShowOutfitListEvent out
of EventRegistry
---
src/js/stendhal/EventRegistry.ts | 123 +------------------
src/js/stendhal/event/ShowItemListEvent.ts | 94 ++++++++++++++
src/js/stendhal/event/ShowOutfitListEvent.ts | 71 +++++++++++
3 files changed, 169 insertions(+), 119 deletions(-)
create mode 100644 src/js/stendhal/event/ShowItemListEvent.ts
create mode 100644 src/js/stendhal/event/ShowOutfitListEvent.ts
diff --git a/src/js/stendhal/EventRegistry.ts b/src/js/stendhal/EventRegistry.ts
index f4b2448c7b..6a92619cc1 100644
--- a/src/js/stendhal/EventRegistry.ts
+++ b/src/js/stendhal/EventRegistry.ts
@@ -24,14 +24,11 @@ import { PlayerLoggedOnEvent } from "./event/PlayerLoggedOnEvent";
import { PlayerLoggedOutEvent } from "./event/PlayerLoggedOutEvent";
import { ProgressStatusEvent } from "./event/ProgressStatusEvent";
import { RPEvent } from "./event/RPEvent";
+import { ShowItemListEvent } from "./event/ShowItemListEvent";
+import { ShowOutfitListEvent } from "./event/ShowOutfitListEvent";
import { SoundEvent } from "./event/SoundEvent";
import { TradeEvent } from "./event/TradeEvent";
import { ViewChangeEvent } from "./event/ViewChangeEvent";
-
-import { ui } from "./ui/UI";
-
-import { DialogContentComponent } from "./ui/toolkit/DialogContentComponent";
-
import { Chat } from "./util/Chat";
@@ -155,120 +152,8 @@ export class EventRegistry {
}
}); // 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("show_item_list", new ShowItemListEvent());
+ this.register("show_outfit_list", new ShowOutfitListEvent());
this.register("sound_event", new SoundEvent());
diff --git a/src/js/stendhal/event/ShowItemListEvent.ts b/src/js/stendhal/event/ShowItemListEvent.ts
new file mode 100644
index 0000000000..e09c065679
--- /dev/null
+++ b/src/js/stendhal/event/ShowItemListEvent.ts
@@ -0,0 +1,94 @@
+/***************************************************************************
+ * Copyright © 2024 - 2025 Faiumoni e. V. *
+ ***************************************************************************
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU Affero General Public License as *
+ * published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+import { RPEvent } from "./RPEvent";
+
+import { ui } from "../ui/UI";
+import { DialogContentComponent } from "../ui/toolkit/DialogContentComponent";
+
+declare var stendhal: any;
+
+
+export class ShowItemListEvent extends RPEvent {
+
+ title!: string;
+ caption!: string;
+ content!: any;
+
+ execute(_entity: any) {
+ let title = "Items";
+ let caption = "";
+ let items = [];
+ console.log("Items", this);
+
+ 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));
+ }
+
+}
diff --git a/src/js/stendhal/event/ShowOutfitListEvent.ts b/src/js/stendhal/event/ShowOutfitListEvent.ts
new file mode 100644
index 0000000000..e5f55d865f
--- /dev/null
+++ b/src/js/stendhal/event/ShowOutfitListEvent.ts
@@ -0,0 +1,71 @@
+/***************************************************************************
+ * Copyright © 2024 - 2025 Faiumoni e. V. *
+ ***************************************************************************
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU Affero General Public License as *
+ * published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+import { RPEvent } from "./RPEvent";
+
+import { ui } from "../ui/UI";
+import { DialogContentComponent } from "../ui/toolkit/DialogContentComponent";
+
+declare var stendhal: any;
+
+
+export class ShowOutfitListEvent extends RPEvent {
+
+ title!: string;
+ caption!: string;
+ outfits!: string;
+
+ execute(_entity: any) {
+ let title = "Outfits";
+ let caption = "";
+ let outfits = [];
+
+ if (this.hasOwnProperty("title")) {
+ title = this["title"];
+ }
+ if (this.hasOwnProperty("caption")) {
+ caption = this["caption"];
+ }
+ if (this.hasOwnProperty("outfits")) {
+ for (let o of this["outfits"].split(":")) {
+ let parts = o.split(";");
+ if (parts.length > 2) {
+ outfits.push([parts[0], parts[1], parts[2]]);
+ }
+ }
+ }
+ if (this.hasOwnProperty("show_base")) {
+ //Chat.log("normal", this["show_base"]);
+ }
+
+ const content = new class extends DialogContentComponent { }("empty-div-template");
+ content.componentElement.classList.add("shopsign");
+ const 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));
+ }
+}
From ef6c92bd74171361f4a20346ca9c06ff4a763462 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Mon, 22 Dec 2025 17:31:24 +0100
Subject: [PATCH 007/162] use proper table for item shop signs
---
src/js/css/main.css | 13 ++++
src/js/stendhal/event/ShowItemListEvent.ts | 77 +++++++++++++++-------
2 files changed, 66 insertions(+), 24 deletions(-)
diff --git a/src/js/css/main.css b/src/js/css/main.css
index 86d9b999e4..91de30c7e9 100644
--- a/src/js/css/main.css
+++ b/src/js/css/main.css
@@ -708,3 +708,16 @@ img#click-indicator {
.pad-contents {
padding: 0.5em;
}
+
+table.shoptable {
+ border-collapse: collapse;
+}
+
+table.shoptable th, table.shoptable td {
+ border: 1px solid var(--text-color);
+ padding: 2px;
+}
+
+td.number {
+ text-align: right;
+}
diff --git a/src/js/stendhal/event/ShowItemListEvent.ts b/src/js/stendhal/event/ShowItemListEvent.ts
index e09c065679..8168988cc2 100644
--- a/src/js/stendhal/event/ShowItemListEvent.ts
+++ b/src/js/stendhal/event/ShowItemListEvent.ts
@@ -60,33 +60,62 @@ export class ShowItemListEvent extends RPEvent {
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);
+ // Create table
+ const table = document.createElement("table");
+ table.className = "shoptable";
+ content.componentElement.appendChild(table);
- // 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);
+ // Caption
+ const tableCaption = document.createElement("caption");
+ tableCaption.className = "shopcaption";
+ tableCaption.textContent = caption;
+ table.appendChild(tableCaption);
+
+ // Header
+ const thead = document.createElement("thead");
+ const headerRow = document.createElement("tr");
+
+ const headers = ["Item", "Price", "Description"];
+ for (const text of headers) {
+ const th = document.createElement("th");
+ th.textContent = text;
+ headerRow.appendChild(th);
}
+ thead.appendChild(headerRow);
+ table.appendChild(thead);
+
+ // Body
+ const tbody = document.createElement("tbody");
+
+ for (const i of items) {
+ const row = document.createElement("tr");
+
+ // Item (image)
+ const itemCell = document.createElement("td");
+ itemCell.appendChild(
+ stendhal.data.sprites.get(
+ stendhal.paths.sprites + "/items/" + i.img
+ )
+ );
+ row.appendChild(itemCell);
+
+ // Price
+ const priceCell = document.createElement("td");
+ priceCell.className = "number";
+ priceCell.textContent = i.price;
+ row.appendChild(priceCell);
+
+ // Description
+ const descCell = document.createElement("td");
+ descCell.textContent = i.desc;
+ row.appendChild(descCell);
+
+ tbody.appendChild(row);
+ }
+
+ table.appendChild(tbody);
+
stendhal.ui.globalInternalWindow.set(
ui.createSingletonFloatingWindow(title, content, 20, 20));
}
From 17003c457aafebe92818bd1ccf5594c55d36e918 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Mon, 22 Dec 2025 18:05:49 +0100
Subject: [PATCH 008/162] code cleanup
---
src/js/stendhal/event/ShowItemListEvent.ts | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/src/js/stendhal/event/ShowItemListEvent.ts b/src/js/stendhal/event/ShowItemListEvent.ts
index 8168988cc2..3857a05e31 100644
--- a/src/js/stendhal/event/ShowItemListEvent.ts
+++ b/src/js/stendhal/event/ShowItemListEvent.ts
@@ -28,7 +28,6 @@ export class ShowItemListEvent extends RPEvent {
let title = "Items";
let caption = "";
let items = [];
- console.log("Items", this);
if (this.hasOwnProperty("title")) {
title = this["title"];
@@ -37,11 +36,10 @@ export class ShowItemListEvent extends RPEvent {
caption = this["caption"];
}
if (this.hasOwnProperty("content")) {
- for (var obj in this["content"]) {
+ for (let obj in this["content"]) {
if (this["content"].hasOwnProperty(obj)) {
- var slotObj = this["content"][obj];
- var data = this["content"][obj]["a"];
- const i = {
+ let data = this["content"][obj]["a"];
+ let item = {
clazz: data["class"],
subclass: data["subclass"],
img: data["class"] + "/" + data["subclass"] + ".png",
@@ -50,10 +48,10 @@ export class ShowItemListEvent extends RPEvent {
}
// seller shops prefix prices with "-"
- if (i.price.startsWith("-")) {
- i.price = i.price.substr(1);
+ if (item.price.startsWith("-")) {
+ item.price = item.price.substr(1);
}
- items.push(i);
+ items.push(item);
}
}
}
From 39edb92f1c78dbe1da1d7eb9564b4b8457a9184b Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Mon, 22 Dec 2025 18:06:00 +0100
Subject: [PATCH 009/162] show outfits in shop sign dialog
---
src/js/stendhal/event/ShowOutfitListEvent.ts | 103 ++++++++++++++++---
1 file changed, 89 insertions(+), 14 deletions(-)
diff --git a/src/js/stendhal/event/ShowOutfitListEvent.ts b/src/js/stendhal/event/ShowOutfitListEvent.ts
index e5f55d865f..dbaf651b4f 100644
--- a/src/js/stendhal/event/ShowOutfitListEvent.ts
+++ b/src/js/stendhal/event/ShowOutfitListEvent.ts
@@ -39,7 +39,11 @@ export class ShowOutfitListEvent extends RPEvent {
for (let o of this["outfits"].split(":")) {
let parts = o.split(";");
if (parts.length > 2) {
- outfits.push([parts[0], parts[1], parts[2]]);
+ outfits.push({
+ name: parts[0],
+ outfit: parts[1],
+ price: parts[2]
+ });
}
}
}
@@ -49,23 +53,94 @@ export class ShowOutfitListEvent extends RPEvent {
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);
+
+ const table = document.createElement("table");
+ table.className = "shoptable";
+ content.componentElement.appendChild(table);
- // 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);
+ // Caption
+ const tableCaption = document.createElement("caption");
+ tableCaption.className = "shopcaption";
+ tableCaption.textContent = caption;
+ table.appendChild(tableCaption);
+
+ // Header
+ const thead = document.createElement("thead");
+ const headerRow = document.createElement("tr");
+
+ const headers = ["Name", "Outfit", "Price"];
+ for (const text of headers) {
+ const th = document.createElement("th");
+ th.textContent = text;
+ headerRow.appendChild(th);
}
+ thead.appendChild(headerRow);
+ table.appendChild(thead);
+
+ // Body
+ const tbody = document.createElement("tbody");
+
+ for (const outfit of outfits) {
+ const row = document.createElement("tr");
+
+ // Name
+ const nameCell = document.createElement("td");
+ nameCell.textContent = outfit.name;
+ row.appendChild(nameCell);
+
+ // Image
+ const imageCell = document.createElement("td");
+ let url = ShowOutfitListEvent.outfitToUrl(outfit.outfit);
+ const img1 = document.createElement("img");
+ const img2 = document.createElement("img");
+ img1.src = url;
+ img2.src = url + "&offset=0";
+ imageCell.append(img1);
+ imageCell.append(img2);
+ row.appendChild(imageCell);
+
+ // Price
+ const priceCell = document.createElement("td");
+ priceCell.className = "number";
+ priceCell.textContent = outfit.price;
+ row.appendChild(priceCell);
+
+ tbody.appendChild(row);
+ }
+
+ table.appendChild(tbody);
+
stendhal.ui.globalInternalWindow.set(
ui.createSingletonFloatingWindow(title, content, 20, 20));
}
+
+ private static outfitToUrl(outfit?: string): string {
+ if (!outfit) {
+ return "";
+ }
+
+ // Required order
+ const order = ["body", "dress", "head", "mouth", "eyes", "mask", "hair", "hat", "detail"];
+
+ // Parse input into a map
+ let values: Record = {};
+
+ for (let part of outfit.split(",")) {
+ let [key, value] = part.split("=");
+ values[key] = Number(value);
+ }
+
+ // Build path
+ let parts = order.map(key => {
+ let value = values[key] ?? 0;
+ return `${key}-${value}-0`;
+ });
+
+ let url =
+ "https://stendhalgame.org/createoutfit.php?rewritten=true&dialog=shop&outfit=" +
+ parts.join("_");
+
+ return url;
+ }
}
From a141cc187d5460a66ace7e2017baf1eb5cbf6407 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Mon, 22 Dec 2025 19:21:51 +0100
Subject: [PATCH 010/162] properly format item names in description text of
shop windows
---
src/js/stendhal/event/ShowItemListEvent.ts | 3 +-
.../stendhal/ui/component/ChatLogComponent.ts | 126 +-----------------
src/js/stendhal/util/Chat.ts | 117 ++++++++++++++++
3 files changed, 125 insertions(+), 121 deletions(-)
diff --git a/src/js/stendhal/event/ShowItemListEvent.ts b/src/js/stendhal/event/ShowItemListEvent.ts
index 3857a05e31..7b3c1f26d1 100644
--- a/src/js/stendhal/event/ShowItemListEvent.ts
+++ b/src/js/stendhal/event/ShowItemListEvent.ts
@@ -14,6 +14,7 @@ import { RPEvent } from "./RPEvent";
import { ui } from "../ui/UI";
import { DialogContentComponent } from "../ui/toolkit/DialogContentComponent";
+import { Chat } from "../util/Chat";
declare var stendhal: any;
@@ -106,7 +107,7 @@ export class ShowItemListEvent extends RPEvent {
// Description
const descCell = document.createElement("td");
- descCell.textContent = i.desc;
+ descCell.innerHTML = Chat.formatLogEntry(i.desc);
row.appendChild(descCell);
tbody.appendChild(row);
diff --git a/src/js/stendhal/ui/component/ChatLogComponent.ts b/src/js/stendhal/ui/component/ChatLogComponent.ts
index d562354df6..d69e6d9640 100644
--- a/src/js/stendhal/ui/component/ChatLogComponent.ts
+++ b/src/js/stendhal/ui/component/ChatLogComponent.ts
@@ -14,6 +14,7 @@ import { Component } from "../toolkit/Component";
import { MenuItem } from "../../action/MenuItem";
import { UIComponentEnum } from "../UIComponentEnum";
import { singletons } from "../../SingletonRepo";
+import { Chat } from "../../util/Chat";
declare var stendhal: any;
@@ -106,13 +107,13 @@ export class ChatLogComponent extends Component {
/**
* Adds a line of text.
*
- * @param type {string}
+ * @param type
* Message type.
- * @param message {string}
+ * @param message
* Text to be added.
- * @param orator {string}
+ * @param orator
* Name of entity making the expression (default: `undefined`).
- * @param timestamp {boolean}
+ * @param timestamp
* If `false`, suppresses prepending message with timestamp (default: `true`).
*/
public addLine(type: string, message: string, orator?: string, timestamp=true) {
@@ -131,7 +132,7 @@ export class ChatLogComponent extends Component {
const rcol = document.createElement("div");
rcol.className = "logcolR log" + type;
- rcol.innerHTML += this.formatLogEntry(message);
+ rcol.innerHTML += Chat.formatLogEntry(message);
const row = document.createElement("div");
row.className = "logrow";
@@ -192,122 +193,7 @@ export class ChatLogComponent extends Component {
this.add(row);
}
- /**
- * Formats displayed text.
- *
- * @param message {string}
- * Text to parse for special characters.
- * @return {string}
- * Text formatted with keywords & item names highlighted.
- */
- private formatLogEntry(message: string): string {
- if (!message) {
- return "";
- }
- let res = "";
- let delims = [" ", ",", ".", "!", "?", ":", ";"];
- let length = message.length;
- let inHighlight = false, inUnderline = false,
- inHighlightQuote = false, inUnderlineQuote = false;
- for (let i = 0; i < length; i++) {
- let c = message[i];
- if (c === "\\") {
- let n = message[i + 1];
- res += n;
- i++;
-
- // Highlight Start?
- } else if (c === "#") {
- if (inHighlight) {
- res += c;
- continue;
- }
- let n = message[i + 1];
- if (n === "#") {
- res += c;
- i++;
- continue;
- }
- if (n === "'") {
- inHighlightQuote = true;
- i++;
- }
- inHighlight = true;
- res += "";
-
- // Underline start?
- } else if (c === "§") {
- if (inUnderline) {
- res += c;
- continue;
- }
- let n = message[i + 1];
- if (n === "§") {
- res += c;
- i++;
- continue;
- }
- if (n === "'") {
- inUnderlineQuote = true;
- i++;
- }
- inUnderline = true;
- res += "";
-
- // End Highlight and Underline?
- } else if (c === "'") {
- if (inUnderlineQuote) {
- inUnderline = false;
- inUnderlineQuote = false;
- res += "";
- continue;
- }
- if (inHighlightQuote) {
- inHighlight = false;
- inHighlightQuote = false;
- res += "";
- continue;
- }
- res += c;
-
- // HTML escape
- } else if (c === "<") {
- res += "<";
-
- // End of word
- } else if (delims.indexOf(c) > -1) {
- let n = message[i + 1];
- if (c === " " || n === " " || n == undefined) {
- if (inUnderline && !inUnderlineQuote && !inHighlightQuote) {
- inUnderline = false;
- res += "" + c;
- continue;
- }
- if (inHighlight && !inUnderlineQuote && !inHighlightQuote) {
- inHighlight = false;
- res += "" + c;
- continue;
- }
- }
- res += c;
-
- // Normal characters
- } else {
- res += c;
- }
- }
-
- // Close opened formattings
- if (inUnderline) {
- res += "";
- }
- if (inHighlight) {
- res += "";
- }
-
- return res;
- }
/**
* Removes all text from log.
diff --git a/src/js/stendhal/util/Chat.ts b/src/js/stendhal/util/Chat.ts
index 7542391971..8bda707e37 100644
--- a/src/js/stendhal/util/Chat.ts
+++ b/src/js/stendhal/util/Chat.ts
@@ -99,4 +99,121 @@ export class Chat {
Chat.log("client", message);
}
}
+
+ /**
+ * Formats displayed text.
+ *
+ * @param message {string}
+ * Text to parse for special characters.
+ * @return {string}
+ * Text formatted with keywords & item names highlighted.
+ */
+ public static formatLogEntry(message: string): string {
+ if (!message) {
+ return "";
+ }
+ let res = "";
+ let delims = [" ", ",", ".", "!", "?", ":", ";"];
+ let length = message.length;
+ let inHighlight = false, inUnderline = false,
+ inHighlightQuote = false, inUnderlineQuote = false;
+ for (let i = 0; i < length; i++) {
+ let c = message[i];
+
+ if (c === "\\") {
+ let n = message[i + 1];
+ res += n;
+ i++;
+
+ // Highlight Start?
+ } else if (c === "#") {
+ if (inHighlight) {
+ res += c;
+ continue;
+ }
+ let n = message[i + 1];
+ if (n === "#") {
+ res += c;
+ i++;
+ continue;
+ }
+ if (n === "'") {
+ inHighlightQuote = true;
+ i++;
+ }
+ inHighlight = true;
+ res += "";
+
+ // Underline start?
+ } else if (c === "§") {
+ if (inUnderline) {
+ res += c;
+ continue;
+ }
+ let n = message[i + 1];
+ if (n === "§") {
+ res += c;
+ i++;
+ continue;
+ }
+ if (n === "'") {
+ inUnderlineQuote = true;
+ i++;
+ }
+ inUnderline = true;
+ res += "";
+
+ // End Highlight and Underline?
+ } else if (c === "'") {
+ if (inUnderlineQuote) {
+ inUnderline = false;
+ inUnderlineQuote = false;
+ res += "";
+ continue;
+ }
+ if (inHighlightQuote) {
+ inHighlight = false;
+ inHighlightQuote = false;
+ res += "";
+ continue;
+ }
+ res += c;
+
+ // HTML escape
+ } else if (c === "<") {
+ res += "<";
+
+ // End of word
+ } else if (delims.indexOf(c) > -1) {
+ let n = message[i + 1];
+ if (c === " " || n === " " || n == undefined) {
+ if (inUnderline && !inUnderlineQuote && !inHighlightQuote) {
+ inUnderline = false;
+ res += "" + c;
+ continue;
+ }
+ if (inHighlight && !inUnderlineQuote && !inHighlightQuote) {
+ inHighlight = false;
+ res += "" + c;
+ continue;
+ }
+ }
+ res += c;
+
+ // Normal characters
+ } else {
+ res += c;
+ }
+ }
+
+ // Close opened formattings
+ if (inUnderline) {
+ res += "";
+ }
+ if (inHighlight) {
+ res += "";
+ }
+
+ return res;
+ }
}
From 05d1fb34afbebfd7c5ab8e725c5bd19f375dc6b6 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Mon, 22 Dec 2025 19:21:51 +0100
Subject: [PATCH 011/162] properly format item names in description text of
shop windows
---
src/js/stendhal/event/ShowItemListEvent.ts | 3 +-
.../stendhal/ui/component/ChatLogComponent.ts | 126 +-----------------
src/js/stendhal/util/Chat.ts | 117 ++++++++++++++++
3 files changed, 125 insertions(+), 121 deletions(-)
diff --git a/src/js/stendhal/event/ShowItemListEvent.ts b/src/js/stendhal/event/ShowItemListEvent.ts
index 3857a05e31..7b3c1f26d1 100644
--- a/src/js/stendhal/event/ShowItemListEvent.ts
+++ b/src/js/stendhal/event/ShowItemListEvent.ts
@@ -14,6 +14,7 @@ import { RPEvent } from "./RPEvent";
import { ui } from "../ui/UI";
import { DialogContentComponent } from "../ui/toolkit/DialogContentComponent";
+import { Chat } from "../util/Chat";
declare var stendhal: any;
@@ -106,7 +107,7 @@ export class ShowItemListEvent extends RPEvent {
// Description
const descCell = document.createElement("td");
- descCell.textContent = i.desc;
+ descCell.innerHTML = Chat.formatLogEntry(i.desc);
row.appendChild(descCell);
tbody.appendChild(row);
diff --git a/src/js/stendhal/ui/component/ChatLogComponent.ts b/src/js/stendhal/ui/component/ChatLogComponent.ts
index d562354df6..d69e6d9640 100644
--- a/src/js/stendhal/ui/component/ChatLogComponent.ts
+++ b/src/js/stendhal/ui/component/ChatLogComponent.ts
@@ -14,6 +14,7 @@ import { Component } from "../toolkit/Component";
import { MenuItem } from "../../action/MenuItem";
import { UIComponentEnum } from "../UIComponentEnum";
import { singletons } from "../../SingletonRepo";
+import { Chat } from "../../util/Chat";
declare var stendhal: any;
@@ -106,13 +107,13 @@ export class ChatLogComponent extends Component {
/**
* Adds a line of text.
*
- * @param type {string}
+ * @param type
* Message type.
- * @param message {string}
+ * @param message
* Text to be added.
- * @param orator {string}
+ * @param orator
* Name of entity making the expression (default: `undefined`).
- * @param timestamp {boolean}
+ * @param timestamp
* If `false`, suppresses prepending message with timestamp (default: `true`).
*/
public addLine(type: string, message: string, orator?: string, timestamp=true) {
@@ -131,7 +132,7 @@ export class ChatLogComponent extends Component {
const rcol = document.createElement("div");
rcol.className = "logcolR log" + type;
- rcol.innerHTML += this.formatLogEntry(message);
+ rcol.innerHTML += Chat.formatLogEntry(message);
const row = document.createElement("div");
row.className = "logrow";
@@ -192,122 +193,7 @@ export class ChatLogComponent extends Component {
this.add(row);
}
- /**
- * Formats displayed text.
- *
- * @param message {string}
- * Text to parse for special characters.
- * @return {string}
- * Text formatted with keywords & item names highlighted.
- */
- private formatLogEntry(message: string): string {
- if (!message) {
- return "";
- }
- let res = "";
- let delims = [" ", ",", ".", "!", "?", ":", ";"];
- let length = message.length;
- let inHighlight = false, inUnderline = false,
- inHighlightQuote = false, inUnderlineQuote = false;
- for (let i = 0; i < length; i++) {
- let c = message[i];
- if (c === "\\") {
- let n = message[i + 1];
- res += n;
- i++;
-
- // Highlight Start?
- } else if (c === "#") {
- if (inHighlight) {
- res += c;
- continue;
- }
- let n = message[i + 1];
- if (n === "#") {
- res += c;
- i++;
- continue;
- }
- if (n === "'") {
- inHighlightQuote = true;
- i++;
- }
- inHighlight = true;
- res += "";
-
- // Underline start?
- } else if (c === "§") {
- if (inUnderline) {
- res += c;
- continue;
- }
- let n = message[i + 1];
- if (n === "§") {
- res += c;
- i++;
- continue;
- }
- if (n === "'") {
- inUnderlineQuote = true;
- i++;
- }
- inUnderline = true;
- res += "";
-
- // End Highlight and Underline?
- } else if (c === "'") {
- if (inUnderlineQuote) {
- inUnderline = false;
- inUnderlineQuote = false;
- res += "";
- continue;
- }
- if (inHighlightQuote) {
- inHighlight = false;
- inHighlightQuote = false;
- res += "";
- continue;
- }
- res += c;
-
- // HTML escape
- } else if (c === "<") {
- res += "<";
-
- // End of word
- } else if (delims.indexOf(c) > -1) {
- let n = message[i + 1];
- if (c === " " || n === " " || n == undefined) {
- if (inUnderline && !inUnderlineQuote && !inHighlightQuote) {
- inUnderline = false;
- res += "" + c;
- continue;
- }
- if (inHighlight && !inUnderlineQuote && !inHighlightQuote) {
- inHighlight = false;
- res += "" + c;
- continue;
- }
- }
- res += c;
-
- // Normal characters
- } else {
- res += c;
- }
- }
-
- // Close opened formattings
- if (inUnderline) {
- res += "";
- }
- if (inHighlight) {
- res += "";
- }
-
- return res;
- }
/**
* Removes all text from log.
diff --git a/src/js/stendhal/util/Chat.ts b/src/js/stendhal/util/Chat.ts
index 7542391971..8bda707e37 100644
--- a/src/js/stendhal/util/Chat.ts
+++ b/src/js/stendhal/util/Chat.ts
@@ -99,4 +99,121 @@ export class Chat {
Chat.log("client", message);
}
}
+
+ /**
+ * Formats displayed text.
+ *
+ * @param message {string}
+ * Text to parse for special characters.
+ * @return {string}
+ * Text formatted with keywords & item names highlighted.
+ */
+ public static formatLogEntry(message: string): string {
+ if (!message) {
+ return "";
+ }
+ let res = "";
+ let delims = [" ", ",", ".", "!", "?", ":", ";"];
+ let length = message.length;
+ let inHighlight = false, inUnderline = false,
+ inHighlightQuote = false, inUnderlineQuote = false;
+ for (let i = 0; i < length; i++) {
+ let c = message[i];
+
+ if (c === "\\") {
+ let n = message[i + 1];
+ res += n;
+ i++;
+
+ // Highlight Start?
+ } else if (c === "#") {
+ if (inHighlight) {
+ res += c;
+ continue;
+ }
+ let n = message[i + 1];
+ if (n === "#") {
+ res += c;
+ i++;
+ continue;
+ }
+ if (n === "'") {
+ inHighlightQuote = true;
+ i++;
+ }
+ inHighlight = true;
+ res += "";
+
+ // Underline start?
+ } else if (c === "§") {
+ if (inUnderline) {
+ res += c;
+ continue;
+ }
+ let n = message[i + 1];
+ if (n === "§") {
+ res += c;
+ i++;
+ continue;
+ }
+ if (n === "'") {
+ inUnderlineQuote = true;
+ i++;
+ }
+ inUnderline = true;
+ res += "";
+
+ // End Highlight and Underline?
+ } else if (c === "'") {
+ if (inUnderlineQuote) {
+ inUnderline = false;
+ inUnderlineQuote = false;
+ res += "";
+ continue;
+ }
+ if (inHighlightQuote) {
+ inHighlight = false;
+ inHighlightQuote = false;
+ res += "";
+ continue;
+ }
+ res += c;
+
+ // HTML escape
+ } else if (c === "<") {
+ res += "<";
+
+ // End of word
+ } else if (delims.indexOf(c) > -1) {
+ let n = message[i + 1];
+ if (c === " " || n === " " || n == undefined) {
+ if (inUnderline && !inUnderlineQuote && !inHighlightQuote) {
+ inUnderline = false;
+ res += "" + c;
+ continue;
+ }
+ if (inHighlight && !inUnderlineQuote && !inHighlightQuote) {
+ inHighlight = false;
+ res += "" + c;
+ continue;
+ }
+ }
+ res += c;
+
+ // Normal characters
+ } else {
+ res += c;
+ }
+ }
+
+ // Close opened formattings
+ if (inUnderline) {
+ res += "";
+ }
+ if (inHighlight) {
+ res += "";
+ }
+
+ return res;
+ }
}
From cbe0825e9b46f2a8adf9adce8c5747ece13b17a0 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Tue, 23 Dec 2025 08:49:29 +0100
Subject: [PATCH 012/162] updated release notes
---
.../client/src/main/play/release-notes/en-US/default.txt | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/app/android/client/src/main/play/release-notes/en-US/default.txt b/app/android/client/src/main/play/release-notes/en-US/default.txt
index 70b8142595..0a67955601 100644
--- a/app/android/client/src/main/play/release-notes/en-US/default.txt
+++ b/app/android/client/src/main/play/release-notes/en-US/default.txt
@@ -1,3 +1,5 @@
-PUT RELEASE ANNOUNCEMENT LINES HERE
+The outfit merchants display images of their products now. In order not to fall behind, item sellers have polished their shop signs, to highlight item names.
+
+This in turn prompted Gwen at Deniarn Accessories to cut her prices in half. All in all, it is a good time for some late Christmas shopping.
Please see the complete change log for further details: https://arianne-project.org/game/stendhal.html#changes
From 7beb1231177136a60d36598f09ed961225094d1a Mon Sep 17 00:00:00 2001
From: Jordan Irwin
Date: Wed, 24 Dec 2025 17:11:58 -0800
Subject: [PATCH 013/162] Allow dispatching Android build workflow manually
---
.github/workflows/android-build.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml
index c550b69f66..856577855d 100644
--- a/.github/workflows/android-build.yml
+++ b/.github/workflows/android-build.yml
@@ -7,6 +7,7 @@ on:
pull_request:
paths:
- 'app/android/**'
+ workflow_dispatch:
jobs:
build:
From e910892a16e67bbfea715f31f6819ec8988cdd4a Mon Sep 17 00:00:00 2001
From: Jordan Irwin
Date: Wed, 24 Dec 2025 17:31:39 -0800
Subject: [PATCH 014/162] Build .apk packages using Ant in Android Build Test
GitHub workflow
---
.github/workflows/android-build.yml | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml
index 856577855d..5776473de2 100644
--- a/.github/workflows/android-build.yml
+++ b/.github/workflows/android-build.yml
@@ -14,10 +14,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - name: Checkout Repository
+ uses: actions/checkout@v3
- - name: Setup Gradle
- uses: gradle/actions/setup-gradle@v3
+ - name: Set Up JDK 21
+ uses: actions/setup-java@v3
+ with:
+ java-version: '21'
+ distribution: 'adopt'
- - name: Build Android Packages
- run: cd app/android/client && gradle assemble
+ - 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"
From c4491004d2fe04fe4fa6e7847a8636e804a0107a Mon Sep 17 00:00:00 2001
From: Jordan Irwin
Date: Wed, 24 Dec 2025 18:05:36 -0800
Subject: [PATCH 015/162] Parse job ID in Android Build Test GitHub workflow
---
.github/workflows/android-build.yml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml
index 5776473de2..7ca4d75e71 100644
--- a/.github/workflows/android-build.yml
+++ b/.github/workflows/android-build.yml
@@ -14,6 +14,12 @@ jobs:
runs-on: ubuntu-latest
steps:
+ - id: job_info
+ uses: daleyjem/gh-job-id@v1
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ job_name: 'build'
+
- name: Checkout Repository
uses: actions/checkout@v3
From 31c4f9cfb432f7fa161d147e22a2485d36589963 Mon Sep 17 00:00:00 2001
From: Jordan Irwin
Date: Wed, 24 Dec 2025 18:07:00 -0800
Subject: [PATCH 016/162] Discord webhook for Android Build Test failure
---
.github/workflows/android-build.yml | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml
index 7ca4d75e71..a4d9e0478f 100644
--- a/.github/workflows/android-build.yml
+++ b/.github/workflows/android-build.yml
@@ -34,3 +34,12 @@ jobs:
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_DEVLOG }}
+ with:
+ title: 'Android Build Failed'
+ description: '${{ steps.job_info.outputs.html_url }}'
From 179bc4d5e706bc3cea91e562cdc3516c8a3b37cb Mon Sep 17 00:00:00 2001
From: Jordan Irwin
Date: Wed, 24 Dec 2025 18:10:25 -0800
Subject: [PATCH 017/162] Fix code alignment in Android Build Test GitHub
workflow
---
.github/workflows/android-build.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml
index a4d9e0478f..9edfb5b6e4 100644
--- a/.github/workflows/android-build.yml
+++ b/.github/workflows/android-build.yml
@@ -33,7 +33,7 @@ jobs:
uses: cedx/setup-ant@v6
with:
version: '1.10'
- run: ant -noinput -buildfile build.xml android -Dgradle.params="assembleDebug assembleRelease"
+ run: ant -noinput -buildfile build.xml android -Dgradle.params="assembleDebug assembleRelease"
- name: Failure Report
uses: sarisia/actions-status-discord@v1
From a5db37305efd551d8fd309625c7551712e63d769 Mon Sep 17 00:00:00 2001
From: Jordan Irwin
Date: Wed, 24 Dec 2025 18:13:57 -0800
Subject: [PATCH 018/162] Fix 'run' line
---
.github/workflows/android-build.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml
index 9edfb5b6e4..6e479c2457 100644
--- a/.github/workflows/android-build.yml
+++ b/.github/workflows/android-build.yml
@@ -33,7 +33,7 @@ jobs:
uses: cedx/setup-ant@v6
with:
version: '1.10'
- run: ant -noinput -buildfile build.xml android -Dgradle.params="assembleDebug assembleRelease"
+ - run: ant -noinput -buildfile build.xml android -Dgradle.params="assembleDebug assembleRelease"
- name: Failure Report
uses: sarisia/actions-status-discord@v1
From 049b91e98b2f247423ffa1ce11f247f7257062a7 Mon Sep 17 00:00:00 2001
From: Jordan Irwin
Date: Wed, 24 Dec 2025 18:21:01 -0800
Subject: [PATCH 019/162] Don't send details to Discord
---
.github/workflows/android-build.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml
index 6e479c2457..85d117420a 100644
--- a/.github/workflows/android-build.yml
+++ b/.github/workflows/android-build.yml
@@ -43,3 +43,4 @@ jobs:
with:
title: 'Android Build Failed'
description: '${{ steps.job_info.outputs.html_url }}'
+ nodetail: true
From 7174c32521d78642ea96feca4d1dc0bd3b9a8290 Mon Sep 17 00:00:00 2001
From: Jordan Irwin
Date: Wed, 24 Dec 2025 18:21:32 -0800
Subject: [PATCH 020/162] Send Android Build error report to Discord dev
channel
---
.github/workflows/android-build.yml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml
index 85d117420a..62b3f0de49 100644
--- a/.github/workflows/android-build.yml
+++ b/.github/workflows/android-build.yml
@@ -37,6 +37,14 @@ jobs:
- 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 }}
From f7565348469005e44341962439bd6b5da532d7c3 Mon Sep 17 00:00:00 2001
From: Jordan Irwin
Date: Wed, 24 Dec 2025 21:59:21 -0800
Subject: [PATCH 021/162] Validate Gradle Wrapper distribution checksum not
.jar checksum
---
app/android/gradle/wrapper/gradle-wrapper.properties | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/android/gradle/wrapper/gradle-wrapper.properties b/app/android/gradle/wrapper/gradle-wrapper.properties
index 0176d15822..b916c04dbb 100644
--- a/app/android/gradle/wrapper/gradle-wrapper.properties
+++ b/app/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionSha256Sum=91a239400bb638f36a1795d8fdf7939d532cdc7d794d1119b7261aac158b1e60
+distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
From 2e5dbc12c691c9c26c2f6b284aeb0f8255b1b8f1 Mon Sep 17 00:00:00 2001
From: Jordan Irwin
Date: Wed, 24 Dec 2025 22:01:09 -0800
Subject: [PATCH 022/162] Validate Gradle Wrapper .jar in Android Build Test
workflow
---
.github/workflows/android-build.yml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml
index 62b3f0de49..bcb0a0096e 100644
--- a/.github/workflows/android-build.yml
+++ b/.github/workflows/android-build.yml
@@ -29,6 +29,14 @@ jobs:
java-version: '21'
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:
From cc5414894f39a22fe90aeb53cd054823d645aa82 Mon Sep 17 00:00:00 2001
From: Jordan Irwin
Date: Wed, 24 Dec 2025 22:10:38 -0800
Subject: [PATCH 023/162] 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.
---
.github/workflows/android-build.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml
index bcb0a0096e..710be77781 100644
--- a/.github/workflows/android-build.yml
+++ b/.github/workflows/android-build.yml
@@ -23,10 +23,10 @@ jobs:
- name: Checkout Repository
uses: actions/checkout@v3
- - name: Set Up JDK 21
+ - name: Set Up JDK
uses: actions/setup-java@v3
with:
- java-version: '21'
+ java-version: '17'
distribution: 'adopt'
- name: Validate Gradle Wrapper .jar
From a000e5f788459649eab3d23aae32a2b7e6512bef Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Fri, 26 Dec 2025 14:41:21 +0100
Subject: [PATCH 024/162] made blend layer available
---
src/js/stendhal/data/Map.ts | 7 +++++--
src/js/stendhal/landscape/CombinedTilesetFactory.ts | 11 ++++++++++-
.../stendhal/landscape/CombinedTilesetImageLoader.ts | 5 ++++-
3 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/src/js/stendhal/data/Map.ts b/src/js/stendhal/data/Map.ts
index b3a8c79364..35cad5657d 100644
--- a/src/js/stendhal/data/Map.ts
+++ b/src/js/stendhal/data/Map.ts
@@ -45,11 +45,12 @@ export class Map {
private targetTileWidth = 0;
private 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;
@@ -124,6 +125,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();
diff --git a/src/js/stendhal/landscape/CombinedTilesetFactory.ts b/src/js/stendhal/landscape/CombinedTilesetFactory.ts
index 245266fdf0..b71b5a3eaf 100644
--- a/src/js/stendhal/landscape/CombinedTilesetFactory.ts
+++ b/src/js/stendhal/landscape/CombinedTilesetFactory.ts
@@ -33,7 +33,15 @@ export class CombinedTilesetFactory {
for (let group = 0; group < this.map.layerGroupIndexes.length; group++) {
let layers = this.map.layerGroupIndexes[group];
+ let blendLayer = this.map.layerNames.indexOf(this.map.blendLayers[group]);
let combinedTile = []
+
+ if (blendLayer && this.map.layers[blendLayer]) {
+ combinedTile.push(this.map.layers[blendLayer][y * this.map.zoneSizeX + x]);
+ } else {
+ combinedTile.push(0);
+ }
+
for (let layer of layers) {
let gid = this.map.layers[layer][y * this.map.zoneSizeX + x];
if (gid > 0) {
@@ -43,7 +51,8 @@ export class CombinedTilesetFactory {
// most of the tiles on the roof layer are empty, so we add a special case here
// to skip drawing completely transparent tiles later.
- if (combinedTile.length === 0) {
+ // In this case, we ignore the entry on the blend layer
+ if (combinedTile.length <= 1) {
combinedLayers[group][y * this.map.zoneSizeX + x] = -1
continue;
}
diff --git a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
index 8c792338bc..94f586ba54 100644
--- a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
+++ b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
@@ -97,7 +97,10 @@ export class CombinedTilesetImageLoader {
const pixelY = y * this.map.tileHeight;
this.combinedTileset.ctx.clearRect(pixelX, pixelY, this.map.tileWidth, this.map.tileHeight);
- for (let tile of tiles) {
+ for (let [i, tile] of tiles.entries()) {
+ if (i === 0) {
+ continue; // skip blend layer
+ }
let flip = tile & 0xE0000000;
let gid = tile & 0x1FFFFFFF;
let tileset = this.map.getTilesetForGid(gid);
From 0d56d2b63bf8431bdcfa3d71fdcc3f93c1819666 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Fri, 26 Dec 2025 14:57:00 +0100
Subject: [PATCH 025/162] renamed Map to TileMap and use type definition
---
src/js/stendhal/Client.ts | 2 +-
src/js/stendhal/SingletonRepo.ts | 6 +-
src/js/stendhal/data/{Map.ts => TileMap.ts} | 64 +++++++++----------
.../landscape/CombinedTilesetFactory.ts | 3 +-
.../landscape/CombinedTilesetImageLoader.ts | 3 +-
.../IndividualTilesetRenderingStrategy.ts | 3 +-
.../landscape/LandscapeRenderingStrategy.ts | 5 +-
7 files changed, 45 insertions(+), 41 deletions(-)
rename src/js/stendhal/data/{Map.ts => TileMap.ts} (89%)
diff --git a/src/js/stendhal/Client.ts b/src/js/stendhal/Client.ts
index 9e0298af15..6cfb9ecdba 100644
--- a/src/js/stendhal/Client.ts
+++ b/src/js/stendhal/Client.ts
@@ -119,7 +119,7 @@ export class Client {
stendhal.data.group = singletons.getGroupManager();
stendhal.data.outfit = singletons.getOutfitStore();
stendhal.data.sprites = singletons.getSpriteStore();
- stendhal.data.map = singletons.getMap();
+ stendhal.data.map = singletons.getTileMap();
// online players
stendhal.players = [];
}
diff --git a/src/js/stendhal/SingletonRepo.ts b/src/js/stendhal/SingletonRepo.ts
index 4e7827b3d8..bdfc331d7f 100644
--- a/src/js/stendhal/SingletonRepo.ts
+++ b/src/js/stendhal/SingletonRepo.ts
@@ -27,7 +27,7 @@ import { CStatus } from "./data/CStatus";
import { CacheManager } from "./data/CacheManager";
import { EmojiStore } from "./data/EmojiStore";
import { GroupManager } from "./data/GroupManager";
-import { Map } from "./data/Map";
+import { TileMap } from "./data/TileMap";
import { OutfitStore } from "./data/OutfitStore";
import { Paths } from "./data/Paths";
import { SpriteStore, store } from "./data/SpriteStore";
@@ -119,8 +119,8 @@ export class SingletonRepo {
return LoopedSoundSourceManager.get();
}
- static getMap(): Map {
- return Map.get();
+ static getTileMap(): TileMap {
+ return TileMap.get();
}
static getOutfitStore(): OutfitStore {
diff --git a/src/js/stendhal/data/Map.ts b/src/js/stendhal/data/TileMap.ts
similarity index 89%
rename from src/js/stendhal/data/Map.ts
rename to src/js/stendhal/data/TileMap.ts
index 35cad5657d..6c5fd81167 100644
--- a/src/js/stendhal/data/Map.ts
+++ b/src/js/stendhal/data/TileMap.ts
@@ -19,31 +19,31 @@ import { IndividualTilesetRenderingStrategy } from "../landscape/IndividualTiles
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;
public readonly layerGroups = [
["0_floor", "1_terrain", "2_object"],
@@ -52,12 +52,12 @@ export class Map {
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",
@@ -65,22 +65,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;
}
/**
diff --git a/src/js/stendhal/landscape/CombinedTilesetFactory.ts b/src/js/stendhal/landscape/CombinedTilesetFactory.ts
index b71b5a3eaf..7b27381dc7 100644
--- a/src/js/stendhal/landscape/CombinedTilesetFactory.ts
+++ b/src/js/stendhal/landscape/CombinedTilesetFactory.ts
@@ -9,12 +9,13 @@
* *
***************************************************************************/
+import { TileMap } from "../data/TileMap";
import { CombinedTileset } from "./CombinedTileset";
import { CombinedTilesetImageLoader } from "./CombinedTilesetImageLoader";
export class CombinedTilesetFactory {
- constructor(private map: any) {}
+ constructor(private map: TileMap) {}
combine(): CombinedTileset {
let combinedTilesToIndex: Map = new Map();
diff --git a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
index 94f586ba54..05292b3492 100644
--- a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
+++ b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { TileMap } from "../data/TileMap";
import { TileStore } from "../data/TileStore";
import { MapOfSets } from "../util/MapOfSets";
import { CombinedTileset } from "./CombinedTileset";
@@ -23,7 +24,7 @@ export class CombinedTilesetImageLoader {
private landscapeAnimationMap: any;
constructor(
- private map: any,
+ private map: TileMap,
private indexToCombinedTiles: Map,
private combinedTileset: CombinedTileset) {
this.landscapeAnimationMap = TileStore.get().getLandscapeMap();
diff --git a/src/js/stendhal/landscape/IndividualTilesetRenderingStrategy.ts b/src/js/stendhal/landscape/IndividualTilesetRenderingStrategy.ts
index 60ed83c047..ef1f4a9e3c 100644
--- a/src/js/stendhal/landscape/IndividualTilesetRenderingStrategy.ts
+++ b/src/js/stendhal/landscape/IndividualTilesetRenderingStrategy.ts
@@ -14,6 +14,7 @@ declare var stendhal: any;
import { LandscapeRenderingStrategy } from "./LandscapeRenderingStrategy";
import { ImagePreloader } from "../data/ImagePreloader";
import { Chat } from "../util/Chat";
+import { TileMap } from "../data/TileMap";
export class IndividualTilesetRenderingStrategy extends LandscapeRenderingStrategy {
@@ -28,7 +29,7 @@ export class IndividualTilesetRenderingStrategy extends LandscapeRenderingStrate
}, 1000);
}
- public onMapLoaded(_map: any): void {
+ public onMapLoaded(_map: TileMap): void {
// do nothing
console.log("Using IndividualTilesetRenderingStrategy.")
}
diff --git a/src/js/stendhal/landscape/LandscapeRenderingStrategy.ts b/src/js/stendhal/landscape/LandscapeRenderingStrategy.ts
index 61db62bf4c..83a81281b9 100644
--- a/src/js/stendhal/landscape/LandscapeRenderingStrategy.ts
+++ b/src/js/stendhal/landscape/LandscapeRenderingStrategy.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { TileMap } from "../data/TileMap";
import { CombinedTilesetFactory } from "./CombinedTilesetFactory";
import { LandscapeRenderer } from "./LandscapeRenderer";
@@ -17,7 +18,7 @@ declare var stendhal: any;
export abstract class LandscapeRenderingStrategy {
- public abstract onMapLoaded(map: any): void;
+ public abstract onMapLoaded(map: TileMap): void;
public abstract onTilesetLoaded(): void;
@@ -29,7 +30,7 @@ export abstract class LandscapeRenderingStrategy {
export class CombinedTilesetRenderingStrategy extends LandscapeRenderingStrategy {
- public onMapLoaded(map: any): void {
+ public onMapLoaded(map: TileMap): void {
let combinedTilesetFactory = new CombinedTilesetFactory(map);
stendhal.data.map.combinedTileset = combinedTilesetFactory.combine();
}
From d1d4eb5c499dbe69bd045841596e6fee01d70f41 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Sat, 27 Dec 2025 13:00:05 +0100
Subject: [PATCH 026/162] removed setting alpha in applyHSLFilter method
---
src/js/stendhal/ui/ViewPort.ts | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/js/stendhal/ui/ViewPort.ts b/src/js/stendhal/ui/ViewPort.ts
index 51d071942c..1d9650a21d 100644
--- a/src/js/stendhal/ui/ViewPort.ts
+++ b/src/js/stendhal/ui/ViewPort.ts
@@ -198,9 +198,6 @@ export class ViewPort {
return;
}
this.ctx.save();
- // FIXME: is this the appropriate alpha level to use? "color_method" value from server doesn't
- // appear to include alpha information
- this.ctx.globalAlpha = 0.75;
this.ctx.globalCompositeOperation = (this.colorMethod || this.ctx.globalCompositeOperation) as GlobalCompositeOperation;
this.ctx.fillStyle = this.HSLFilter;
this.ctx.fillRect(this.offsetX, this.offsetY, this.ctx.canvas.width, this.ctx.canvas.height);
From 44f6a4a9fef76ccb3d013d30e866b2fe06c73709 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Sat, 27 Dec 2025 13:10:16 +0100
Subject: [PATCH 027/162] marked event listener as passive to prevent scroll
delay
---
src/js/stendhal/Client.ts | 4 ++--
src/js/stendhal/ui/ViewPort.ts | 4 ++--
src/js/stendhal/ui/component/ChatLogComponent.ts | 2 +-
src/js/stendhal/ui/component/ItemContainerImplementation.ts | 4 ++--
src/js/stendhal/util/ElementClickListener.ts | 2 +-
5 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/js/stendhal/Client.ts b/src/js/stendhal/Client.ts
index 6cfb9ecdba..f4ccdae9cd 100644
--- a/src/js/stendhal/Client.ts
+++ b/src/js/stendhal/Client.ts
@@ -426,12 +426,12 @@ export class Client {
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("touchstart", stendhal.ui.gamewindow.onMouseDown, {passive: true});
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("wheel", stendhal.ui.gamewindow.onMouseWheel, {passive: true});
singletons.getJoystickController().registerGlobalEventHandlers();
diff --git a/src/js/stendhal/ui/ViewPort.ts b/src/js/stendhal/ui/ViewPort.ts
index 1d9650a21d..bd3e3c6134 100644
--- a/src/js/stendhal/ui/ViewPort.ts
+++ b/src/js/stendhal/ui/ViewPort.ts
@@ -516,9 +516,9 @@ export class ViewPort {
stendhal.ui.timestampMouseDown = +new Date();
if (e.type !== "dblclick" && e.target) {
- e.target.addEventListener("mousemove", mHandle.onDrag);
+ e.target.addEventListener("mousemove", mHandle.onDrag, {passive: true});
e.target.addEventListener("mouseup", mHandle.onMouseUp);
- e.target.addEventListener("touchmove", mHandle.onDrag);
+ e.target.addEventListener("touchmove", mHandle.onDrag, {passive: true});
e.target.addEventListener("touchend", mHandle.onMouseUp);
} else if (entity == stendhal.zone.ground) {
entity.onclick(pos.canvasRelativeX, pos.canvasRelativeY, true);
diff --git a/src/js/stendhal/ui/component/ChatLogComponent.ts b/src/js/stendhal/ui/component/ChatLogComponent.ts
index d69e6d9640..fbdf9e0735 100644
--- a/src/js/stendhal/ui/component/ChatLogComponent.ts
+++ b/src/js/stendhal/ui/component/ChatLogComponent.ts
@@ -41,7 +41,7 @@ export class ChatLogComponent extends Component {
this.componentElement.addEventListener("touchstart", (evt: TouchEvent) => {
const pos = stendhal.ui.html.extractPosition(evt);
stendhal.ui.touch.onTouchStart(pos.pageX, pos.pageY);
- });
+ }, {passive: true});
this.componentElement.addEventListener("touchend", (evt: TouchEvent) => {
stendhal.ui.touch.onTouchEnd();
this.onContextMenu(evt)
diff --git a/src/js/stendhal/ui/component/ItemContainerImplementation.ts b/src/js/stendhal/ui/component/ItemContainerImplementation.ts
index 1d024dc8f8..8cbbed7a8a 100644
--- a/src/js/stendhal/ui/component/ItemContainerImplementation.ts
+++ b/src/js/stendhal/ui/component/ItemContainerImplementation.ts
@@ -60,7 +60,7 @@ export class ItemContainerImplementation {
});
e.addEventListener("touchmove", (event: TouchEvent) => {
this.onTouchMove(event);
- });
+ }, {passive: true});
e.addEventListener("drop", (event: DragEvent) => {
this.onDrop(event)
});
@@ -72,7 +72,7 @@ export class ItemContainerImplementation {
});
e.addEventListener("touchstart", (event: TouchEvent) => {
this.onTouchStart(event)
- });
+ }, {passive: true});
e.addEventListener("touchend", (event: TouchEvent) => {
this.onTouchEnd(event)
});
diff --git a/src/js/stendhal/util/ElementClickListener.ts b/src/js/stendhal/util/ElementClickListener.ts
index a6dc3e7f85..5d444f8770 100644
--- a/src/js/stendhal/util/ElementClickListener.ts
+++ b/src/js/stendhal/util/ElementClickListener.ts
@@ -39,7 +39,7 @@ export class ElementClickListener {
element.addEventListener("touchstart", (evt: TouchEvent) => {
evt.preventDefault();
this.touchEngaged = evt.changedTouches.length;
- });
+ }, {passive: true});
element.addEventListener("mouseup", (evt: MouseEvent) => {
evt.preventDefault();
if (this.clickEngaged && evt.button == 0 && this.onClick) {
From d9b51f0d155ccd41712da7322751ec2335675c63 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Sun, 28 Dec 2025 10:04:14 +0100
Subject: [PATCH 028/162] disabled code to draw lights (alpha is broken)
---
.../landscape/CombinedTilesetImageLoader.ts | 34 +++++++++++++++++--
1 file changed, 32 insertions(+), 2 deletions(-)
diff --git a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
index 05292b3492..a042e2a91d 100644
--- a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
+++ b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
@@ -1,5 +1,5 @@
/***************************************************************************
- * (C) Copyright 2022-2023 - Faiumoni e. V. *
+ * (C) Copyright 2022-2025 - Faiumoni e. V. *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
@@ -58,6 +58,9 @@ export class CombinedTilesetImageLoader {
}
const tsname = this.map.tilesetFilenames[tileset];
+ if (!tsname) {
+ return;
+ }
img.src = tsname + "?v=" + stendhal.data.build.version;
if (this.landscapeAnimationMap) {
@@ -113,8 +116,35 @@ export class CombinedTilesetImageLoader {
let base = this.map.firstgids[tileset];
let tileIndexInTileset = gid - base;
this.drawTile(pixelX, pixelY, image, tileIndexInTileset, flip);
-
}
+
+ /*
+ if (stendhal.ui.gamewindow.HSLFilter) {
+ this.combinedTileset.ctx.globalCompositeOperation = "multiply";
+ this.combinedTileset.ctx.fillStyle = stendhal.ui.gamewindow.HSLFilter;
+ this.combinedTileset.ctx.fillRect(pixelX, pixelY, this.map.tileWidth, this.map.tileHeight);
+
+ let tile = tiles[0];
+ this.combinedTileset.ctx.globalCompositeOperation = "soft-light";
+ if (tile > 0) {
+ let flip = tile & 0xE0000000;
+ let gid = tile & 0x1FFFFFFF;
+ let tileset = this.map.getTilesetForGid(gid);
+ let image = this.tilesetImages[tileset];
+ if (image && image.height) {
+ let base = this.map.firstgids[tileset];
+ let tileIndexInTileset = gid - base;
+ this.drawTile(pixelX, pixelY, image, tileIndexInTileset, flip);
+ }
+ } else {
+ this.combinedTileset.ctx.fillStyle = "black";
+ this.combinedTileset.ctx.fillRect(pixelX, pixelY, this.map.tileWidth, this.map.tileHeight);
+ }
+
+ this.combinedTileset.ctx.globalCompositeOperation = "source-over";
+ // document.getElementsByTagName("body")[0].append(this.tempCanvas);
+ }
+ */
}
private drawTile(pixelX: number, pixelY: number, tilesetImage: HTMLImageElement, tileIndexInTileset: number, flip: number) {
From d191c14232ec5efbdf9fcd79841dd277f59b808e Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Sun, 28 Dec 2025 12:31:20 +0100
Subject: [PATCH 029/162] updated typescript and typedoc
---
package-lock.json | 268 +++++++++++++++++++++++++++++++++++++++++++---
package.json | 4 +-
2 files changed, 258 insertions(+), 14 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 32126ed830..bf69eee3f1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,21 +1,249 @@
{
"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": {
+ "showdown": "^2.1.0",
+ "typedoc": "^0.28.15",
+ "typescript": "^5.9.3"
+ }
+ },
+ "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/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.5",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "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 +251,29 @@
"engines": {
"node": ">=14.17"
}
- }
- },
- "dependencies": {
- "typescript": {
- "version": "5.2.2",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
- "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w=="
+ },
+ "node_modules/uc.micro": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
+ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/yaml": {
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
+ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/eemeli"
+ }
}
}
}
diff --git a/package.json b/package.json
index 6619d1b7a2..6585e78162 100644
--- a/package.json
+++ b/package.json
@@ -29,10 +29,10 @@
"homepage": "https://arianne-project.org",
"private": true,
"dependencies": {
- "typescript": "^5.2.0"
},
"devDependencies": {
"showdown": "^2.1.0",
- "typedoc": "^0.25.12"
+ "typedoc": "^0.28.15",
+ "typescript": "^5.9.3"
}
}
From 063d03c45f8204d2ed8bd6fa171547759b5c3c58 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Sun, 28 Dec 2025 12:32:20 +0100
Subject: [PATCH 030/162] used Canvas and RenderingContext2D to allow
OffscreenCanvas
---
src/js/stendhal/action/ScreenCaptureAction.ts | 1 +
src/js/stendhal/data/SpriteStore.ts | 4 +-
src/js/stendhal/entity/Blood.ts | 3 +-
src/js/stendhal/entity/Corpse.ts | 3 +-
src/js/stendhal/entity/DomesticAnimal.ts | 3 +-
src/js/stendhal/entity/Door.ts | 4 +-
src/js/stendhal/entity/Entity.ts | 7 ++--
src/js/stendhal/entity/Food.ts | 3 +-
src/js/stendhal/entity/Gate.ts | 3 +-
.../stendhal/entity/GrowingEntitySpawner.ts | 3 +-
src/js/stendhal/entity/Item.ts | 8 +++-
src/js/stendhal/entity/NPC.ts | 3 +-
src/js/stendhal/entity/Player.ts | 3 +-
src/js/stendhal/entity/PopupInventory.ts | 3 +-
src/js/stendhal/entity/RPEntity.ts | 38 ++++++++++---------
src/js/stendhal/entity/Sign.ts | 3 +-
src/js/stendhal/event/ViewChangeEvent.ts | 5 ++-
src/js/stendhal/landscape/CombinedTileset.ts | 6 ++-
.../IndividualTilesetRenderingStrategy.ts | 9 +++--
.../stendhal/landscape/LandscapeRenderer.ts | 5 ++-
.../landscape/LandscapeRenderingStrategy.ts | 5 ++-
.../stendhal/landscape/ParallaxBackground.ts | 3 +-
src/js/stendhal/sprite/AchievementBanner.ts | 8 ++--
.../sprite/ActivityIndicatorSprite.ts | 3 +-
src/js/stendhal/sprite/EmojiSprite.ts | 3 +-
src/js/stendhal/sprite/EmptySprite.ts | 7 ++--
src/js/stendhal/sprite/Floater.ts | 3 +-
src/js/stendhal/sprite/ImageSprite.ts | 9 +++--
src/js/stendhal/sprite/NotificationBubble.ts | 3 +-
src/js/stendhal/sprite/OverlaySpriteImpl.ts | 6 ++-
src/js/stendhal/sprite/SpeechBubble.ts | 3 +-
src/js/stendhal/sprite/Sprite.ts | 6 ++-
src/js/stendhal/sprite/TextBubble.ts | 11 +++++-
src/js/stendhal/sprite/TextSprite.ts | 6 ++-
src/js/stendhal/sprite/action/AttackSprite.ts | 3 +-
.../sprite/action/BarehandAttackSprite.ts | 3 +-
.../sprite/action/DirectionalSkillEffect.ts | 3 +-
.../sprite/action/MeleeAttackSprite.ts | 3 +-
.../sprite/action/RangedAttackSprite.ts | 3 +-
src/js/stendhal/sprite/action/SkillEffect.ts | 7 ++--
src/js/stendhal/ui/HTMLManager.ts | 2 -
src/js/stendhal/ui/ViewPort.ts | 7 ++--
.../stendhal/ui/component/MiniMapComponent.ts | 7 ++--
.../ui/component/OutfitPreviewComponent.ts | 3 +-
.../stendhal/ui/component/StatBarComponent.ts | 5 ++-
.../ui/dialog/outfit/OutfitColorSelector.ts | 8 ++--
.../ui/dialog/outfit/OutfitPartSelector.ts | 4 +-
src/js/stendhal/util/BackgroundPainter.ts | 4 +-
src/js/stendhal/util/DownloadUtil.ts | 4 +-
src/js/stendhal/util/DrawingStage.ts | 5 ++-
src/js/stendhal/util/ScreenCapture.ts | 2 +-
src/js/stendhal/util/Speech.ts | 6 ++-
src/js/stendhal/util/Types.ts | 2 +
src/js/stendhal/util/WeatherRenderer.ts | 17 +++++----
54 files changed, 180 insertions(+), 111 deletions(-)
create mode 100644 src/js/stendhal/util/Types.ts
diff --git a/src/js/stendhal/action/ScreenCaptureAction.ts b/src/js/stendhal/action/ScreenCaptureAction.ts
index 7c8fc9d3c2..539e89a486 100644
--- a/src/js/stendhal/action/ScreenCaptureAction.ts
+++ b/src/js/stendhal/action/ScreenCaptureAction.ts
@@ -13,6 +13,7 @@
import { SlashAction } from "./SlashAction";
import { ScreenCapture } from "../util/ScreenCapture";
+import { Canvas } from "util/Types";
declare var stendhal: any;
diff --git a/src/js/stendhal/data/SpriteStore.ts b/src/js/stendhal/data/SpriteStore.ts
index ad165fca12..2f260c89b4 100644
--- a/src/js/stendhal/data/SpriteStore.ts
+++ b/src/js/stendhal/data/SpriteStore.ts
@@ -11,6 +11,7 @@
declare var stendhal: any;
+import { Canvas } from "util/Types";
import { Paths } from "./Paths";
@@ -135,7 +136,7 @@ export class SpriteStore {
* Angle of rotation.
*/
private rotate(img: HTMLImageElement, angle: number) {
- const canvas = document.getElementById("drawing-stage")!;
+ 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);
@@ -291,6 +292,7 @@ export class SpriteStore {
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.
+ // TODO: Check if that is still true
var newImage = new Image();
newImage.src = canvas.toDataURL("image/png");
return newImage;
diff --git a/src/js/stendhal/entity/Blood.ts b/src/js/stendhal/entity/Blood.ts
index 680b8a26c5..c94abc0ea3 100644
--- a/src/js/stendhal/entity/Blood.ts
+++ b/src/js/stendhal/entity/Blood.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
import { Entity } from "./Entity";
declare var stendhal: any;
@@ -40,7 +41,7 @@ export class Blood extends Entity {
return "url(" + stendhal.paths.sprites + "/cursor/walk.png) 1 3, auto";
}
- override drawSpriteAt(ctx: CanvasRenderingContext2D, x: number, y: number) {
+ override drawSpriteAt(ctx: RenderingContext2D, x: number, y: number) {
if (!stendhal.config.getBoolean("effect.blood")) {
return;
}
diff --git a/src/js/stendhal/entity/Corpse.ts b/src/js/stendhal/entity/Corpse.ts
index 16ccd30caa..40cd34db9e 100644
--- a/src/js/stendhal/entity/Corpse.ts
+++ b/src/js/stendhal/entity/Corpse.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
import { ActivityIndicatorSprite } from "../sprite/ActivityIndicatorSprite";
import { ItemInventoryComponent } from "../ui/component/ItemInventoryComponent";
@@ -50,7 +51,7 @@ export class Corpse extends PopupInventory {
}
}
- override draw(ctx: CanvasRenderingContext2D) {
+ override draw(ctx: RenderingContext2D) {
if (!this.sprite) {
return;
}
diff --git a/src/js/stendhal/entity/DomesticAnimal.ts b/src/js/stendhal/entity/DomesticAnimal.ts
index 137bdc52fe..0ffbd015d9 100644
--- a/src/js/stendhal/entity/DomesticAnimal.ts
+++ b/src/js/stendhal/entity/DomesticAnimal.ts
@@ -13,6 +13,7 @@ import { RPEntity } from "./RPEntity";
import { MenuItem} from "../action/MenuItem";
import { Color } from "../data/color/Color";
+import { RenderingContext2D } from "util/Types";
declare var marauroa: any;
declare var stendhal: any;
@@ -21,7 +22,7 @@ export class DomesticAnimal extends RPEntity {
override minimapStyle = Color.DOMESTICANIMAL;
- override drawMain(ctx: CanvasRenderingContext2D) {
+ override drawMain(ctx: RenderingContext2D) {
if (!this.imagePath && this["_rpclass"]) {
this["largeWeight"] = this["largeWeight"] | 20;
if (this["_rpclass"] == "sheep") {
diff --git a/src/js/stendhal/entity/Door.ts b/src/js/stendhal/entity/Door.ts
index 3258d3aa41..78cf9cdfc3 100644
--- a/src/js/stendhal/entity/Door.ts
+++ b/src/js/stendhal/entity/Door.ts
@@ -9,17 +9,17 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
import { MenuItem } from "../action/MenuItem";
import { Portal } from "./Portal";
-declare var marauroa: any;
declare var stendhal: any;
export class Door extends Portal {
override zIndex = 5000;
- override draw(ctx: CanvasRenderingContext2D) {
+ override draw(ctx: RenderingContext2D) {
let imagePath = stendhal.paths.sprites + "/doors/" + this["class"] + ".png";
let image = stendhal.data.sprites.get(imagePath);
if (image.height) {
diff --git a/src/js/stendhal/entity/Entity.ts b/src/js/stendhal/entity/Entity.ts
index ee52a43a29..659a191146 100644
--- a/src/js/stendhal/entity/Entity.ts
+++ b/src/js/stendhal/entity/Entity.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
import { MenuItem } from "../action/MenuItem";
import { Chat } from "../util/Chat";
import { RPObject } from "./RPObject";
@@ -204,7 +205,7 @@ export class Entity extends RPObject {
this["_x"] = this["x"];
}
- draw(ctx: CanvasRenderingContext2D) {
+ draw(ctx: RenderingContext2D) {
if (this.sprite) {
this.drawSprite(ctx);
}
@@ -213,11 +214,11 @@ export class Entity extends RPObject {
/**
* draws a standard sprite
*/
- drawSprite(ctx: CanvasRenderingContext2D) {
+ drawSprite(ctx: RenderingContext2D) {
this.drawSpriteAt(ctx, this["x"] * 32, this["y"] * 32);
}
- drawSpriteAt(ctx: CanvasRenderingContext2D, x: number, y: number) {
+ drawSpriteAt(ctx: RenderingContext2D, x: number, y: number) {
var image = stendhal.data.sprites.get(this.sprite.filename);
if (image.height) {
var offsetX = this.sprite.offsetX || 0;
diff --git a/src/js/stendhal/entity/Food.ts b/src/js/stendhal/entity/Food.ts
index 31effe663c..9927d1a5e2 100644
--- a/src/js/stendhal/entity/Food.ts
+++ b/src/js/stendhal/entity/Food.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
import { Entity } from "./Entity";
declare var marauroa: any;
@@ -27,7 +28,7 @@ export class Food extends Entity {
// TODO: play sound effect
}
- override draw(ctx: CanvasRenderingContext2D) {
+ override draw(ctx: RenderingContext2D) {
var image = stendhal.data.sprites.get(stendhal.paths.sprites + "/food.png");
if (image.height) {
var localX = this["x"] * 32;
diff --git a/src/js/stendhal/entity/Gate.ts b/src/js/stendhal/entity/Gate.ts
index 881e9659e5..c40d22eddc 100644
--- a/src/js/stendhal/entity/Gate.ts
+++ b/src/js/stendhal/entity/Gate.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
import { MenuItem } from "../action/MenuItem";
import { Entity } from "./Entity";
@@ -44,7 +45,7 @@ export class Gate extends Entity {
});
}
- override draw(ctx: CanvasRenderingContext2D) {
+ override draw(ctx: RenderingContext2D) {
if (this._image == undefined) {
var filename = stendhal.paths.sprites + "/doors/" + this["image"] + "_" + this["orientation"] + ".png";
this._image = stendhal.data.sprites.get(filename);
diff --git a/src/js/stendhal/entity/GrowingEntitySpawner.ts b/src/js/stendhal/entity/GrowingEntitySpawner.ts
index 3aa64e390a..ef78f70751 100644
--- a/src/js/stendhal/entity/GrowingEntitySpawner.ts
+++ b/src/js/stendhal/entity/GrowingEntitySpawner.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
import { MenuItem } from "../action/MenuItem";
import { Entity } from "./Entity";
@@ -51,7 +52,7 @@ export class GrowingEntitySpawner extends Entity {
/**
* draw RPEntities
*/
- override draw(ctx: CanvasRenderingContext2D) {
+ override draw(ctx: RenderingContext2D) {
var localX = this["x"] * 32;
var localY = this["y"] * 32;
diff --git a/src/js/stendhal/entity/Item.ts b/src/js/stendhal/entity/Item.ts
index 791bc533db..f33f920bb6 100644
--- a/src/js/stendhal/entity/Item.ts
+++ b/src/js/stendhal/entity/Item.ts
@@ -13,6 +13,7 @@ import { ItemMap } from "./ItemMap";
import { MenuItem } from "../action/MenuItem";
import { Entity } from "./Entity";
import { TextSprite } from "../sprite/TextSprite";
+import { RenderingContext2D } from "util/Types";
declare var marauroa: any;
declare var stendhal: any;
@@ -80,17 +81,20 @@ export class Item extends Entity {
}
}
- override draw(ctx: CanvasRenderingContext2D) {
+ override draw(ctx: RenderingContext2D) {
this.sprite.offsetY = (this["state"] || 0) * 32
this.stepAnimation();
this.drawAt(ctx, this["x"] * 32, this["y"] * 32);
}
- drawAt(ctx: CanvasRenderingContext2D, x: number, y: number) {
+ drawAt(ctx: RenderingContext2D, x: number, y: number) {
if (this.sprite) {
this.drawSpriteAt(ctx, x, y);
let textMetrics = this.quantityTextSprite.getTextMetrics(ctx);
+ if (!textMetrics) {
+ throw new Error("textMetrics is undefined");
+ }
this.quantityTextSprite.draw(ctx, x + (32 - textMetrics.width), y + 6);
}
}
diff --git a/src/js/stendhal/entity/NPC.ts b/src/js/stendhal/entity/NPC.ts
index ee9d8ed438..26071a2b0c 100644
--- a/src/js/stendhal/entity/NPC.ts
+++ b/src/js/stendhal/entity/NPC.ts
@@ -14,6 +14,7 @@ import { RPEntity } from "./RPEntity";
import { EntityOverlayRegistry } from "../data/EntityOverlayRegistry";
import { Color } from "../data/color/Color";
+import { RenderingContext2D } from "util/Types";
declare var stendhal: any;
@@ -42,7 +43,7 @@ export class NPC extends RPEntity {
}
}
- override drawTop(ctx: CanvasRenderingContext2D) {
+ override drawTop(ctx: RenderingContext2D) {
var localX = this["_x"] * 32;
var localY = this["_y"] * 32;
if (typeof(this["no_hpbar"]) == "undefined") {
diff --git a/src/js/stendhal/entity/Player.ts b/src/js/stendhal/entity/Player.ts
index 2a07d86520..d600bf3e36 100644
--- a/src/js/stendhal/entity/Player.ts
+++ b/src/js/stendhal/entity/Player.ts
@@ -22,6 +22,7 @@ import { UIComponentEnum } from "../ui/UIComponentEnum";
import { GroupPanelComponent } from "../ui/component/GroupPanelComponent";
import { Color } from "../data/color/Color";
+import { RenderingContext2D } from "util/Types";
export class Player extends RPEntity {
@@ -169,7 +170,7 @@ export class Player extends RPEntity {
}
}
- override draw(ctx: CanvasRenderingContext2D) {
+ override draw(ctx: RenderingContext2D) {
if (this.isIgnored()) {
return;
}
diff --git a/src/js/stendhal/entity/PopupInventory.ts b/src/js/stendhal/entity/PopupInventory.ts
index fb995da53e..ab51130afd 100644
--- a/src/js/stendhal/entity/PopupInventory.ts
+++ b/src/js/stendhal/entity/PopupInventory.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
import { Entity } from "./Entity";
import { RPObject } from "./RPObject";
@@ -20,7 +21,7 @@ export class PopupInventory extends Entity {
protected maxDistToView = 4;
- override draw(ctx: CanvasRenderingContext2D) {
+ override draw(ctx: RenderingContext2D) {
super.draw(ctx);
if (this.inventory && this.inventory.isOpen() && !this.canViewContents()) {
// player has moved too far away
diff --git a/src/js/stendhal/entity/RPEntity.ts b/src/js/stendhal/entity/RPEntity.ts
index e49c6789a7..d20881020f 100644
--- a/src/js/stendhal/entity/RPEntity.ts
+++ b/src/js/stendhal/entity/RPEntity.ts
@@ -33,6 +33,7 @@ import { BarehandAttackSprite } from "../sprite/action/BarehandAttackSprite";
import { RangedAttackSprite } from "../sprite/action/RangedAttackSprite";
import { ImageWithDimensions } from "data/ImageWithDimensions";
+import { RenderingContext2D } from "util/Types";
var HEALTH_BAR_HEIGHT = 6;
@@ -54,7 +55,7 @@ export class RPEntity extends ActiveEntity {
// for adjusting entity hp bar & title vertical position
protected titleDrawYOffset: number = 0;
// canvas for merging outfit layers to be drawn
- private octx?: CanvasRenderingContext2D;
+ private octx?: RenderingContext2D;
private attackers: {[key: string]: any} = { size: 0 };
@@ -218,7 +219,7 @@ export class RPEntity extends ActiveEntity {
return typeof(this["no_shadow"]) === "undefined";
}
- drawMultipartOutfit(ctx: CanvasRenderingContext2D) {
+ drawMultipartOutfit(ctx: RenderingContext2D) {
const store = singletons.getOutfitStore();
// layers in draw order
var layers = store.getLayerNames();
@@ -347,7 +348,7 @@ export class RPEntity extends ActiveEntity {
/**
* draw RPEntities
*/
- override draw(ctx: CanvasRenderingContext2D) {
+ override draw(ctx: RenderingContext2D) {
if (typeof(this["ghostmode"]) != "undefined" && marauroa.me && !marauroa.me.isAdmin()) {
return;
}
@@ -357,7 +358,7 @@ export class RPEntity extends ActiveEntity {
this.drawStatusIcons(ctx);
}
- drawMain(ctx: CanvasRenderingContext2D) {
+ drawMain(ctx: RenderingContext2D) {
if (typeof(this["outfit"]) != "undefined" || typeof(this["outfit_ext"]) != "undefined") {
this.drawMultipartOutfit(ctx);
} else {
@@ -396,7 +397,7 @@ export class RPEntity extends ActiveEntity {
}
}
- drawStatusIcons(ctx: CanvasRenderingContext2D) {
+ drawStatusIcons(ctx: RenderingContext2D) {
function _drawAnimatedIcon(icon: CanvasImageSource, delay: number, nFrames: number, xdim: number, ydim: number, x: number, y: number) {
var frame = Math.floor(Date.now() / delay) % nFrames;
@@ -474,11 +475,11 @@ export class RPEntity extends ActiveEntity {
* Draw colored ellipses (or rectangles on browsers that do not support
* ellipses) when the entity is being attacked, or is attacking the user.
*/
- drawCombat(ctx: CanvasRenderingContext2D) {
+ drawCombat(ctx: RenderingContext2D) {
if (this.attackers.size > 0) {
ctx.lineWidth = 1;
/*
- * As of 2015-9-15 CanvasRenderingContext2D.ellipse() is not
+ * As of 2015-9-15 RenderingContext2D.ellipse() is not
* supported in most browsers. Fall back to rectangles on these.
* Also on Chrome 45.0.2454.85 ellipse() does not seem to support
* the begin angle parameter correctly, nor does the stroke
@@ -539,7 +540,7 @@ export class RPEntity extends ActiveEntity {
* the coordinates where to start floating. The method should return true
* when the floater should be removed.
*/
- drawFloaters(ctx: CanvasRenderingContext2D) {
+ drawFloaters(ctx: RenderingContext2D) {
var centerX = (this["_x"] + this["width"] / 2) * 32;
var topY = (this["_y"] + 1) * 32 - this["drawHeight"];
// Grab an unchanging copy
@@ -555,10 +556,10 @@ export class RPEntity extends ActiveEntity {
}
/**
- * @param {CanvasRenderingContext2D} ctx
+ * @param {RenderingContext2D} ctx
* @param {Image} image
*/
- drawSpriteImage(ctx: CanvasRenderingContext2D, image: CanvasImageSource & ImageWithDimensions) {
+ drawSpriteImage(ctx: RenderingContext2D, image: CanvasImageSource & ImageWithDimensions) {
var localX = this["_x"] * 32;
var localY = this["_y"] * 32;
if (image.height) { // image.complete is true on missing image files
@@ -610,7 +611,7 @@ export class RPEntity extends ActiveEntity {
}
}
- drawTop(ctx: CanvasRenderingContext2D) {
+ drawTop(ctx: RenderingContext2D) {
var localX = this["_x"] * 32;
var localY = this["_y"] * 32;
this.drawFloaters(ctx);
@@ -618,7 +619,7 @@ export class RPEntity extends ActiveEntity {
this.drawTitle(ctx, localX, localY + this.statusBarYOffset);
}
- drawHealthBar(ctx: CanvasRenderingContext2D, x: number, y: number) {
+ drawHealthBar(ctx: RenderingContext2D, x: number, y: number) {
var drawX = x + ((this["width"] * 32) - this["drawWidth"]) / 2;
var drawY = y + (this["height"] * 32) - this["drawHeight"]
- HEALTH_BAR_HEIGHT + this.titleDrawYOffset;
@@ -655,16 +656,19 @@ export class RPEntity extends ActiveEntity {
}
}
- drawTitle(ctx: CanvasRenderingContext2D, x: number, y: number) {
+ drawTitle(ctx: RenderingContext2D, x: number, y: number) {
if (this.titleTextSprite) {
let textMetrics = this.titleTextSprite.getTextMetrics(ctx);
+ if (!textMetrics) {
+ throw new Error("textMetrics is undefined");
+ }
var drawY = y + (this["height"] * 32) - this["drawHeight"]
- HEALTH_BAR_HEIGHT + this.titleDrawYOffset;
this.titleTextSprite.draw(ctx, x + (this["width"] * 32 - textMetrics.width) / 2, drawY - 5 - HEALTH_BAR_HEIGHT);
}
}
- drawAttack(ctx: CanvasRenderingContext2D) {
+ drawAttack(ctx: RenderingContext2D) {
if (!this.attackSprite) {
return;
}
@@ -684,10 +688,10 @@ export class RPEntity extends ActiveEntity {
/**
* Draws an animation over entity sprite.
*
- * @param {CanvasRenderingContext2D} ctx
+ * @param {RenderingContext2D} ctx
* Canvas context to draw on.
*/
- private drawOverlayAnimation(ctx: CanvasRenderingContext2D) {
+ private drawOverlayAnimation(ctx: RenderingContext2D) {
if (this.overlay && this.overlay.draw(ctx, this["drawOffsetX"], this["drawOffsetY"],
this["drawWidth"], this["drawHeight"])) {
// overlay sprite expired
@@ -780,7 +784,7 @@ export class RPEntity extends ActiveEntity {
return {
initTime: Date.now(),
image: stendhal.data.sprites.get(imagePath),
- draw: function(ctx: CanvasRenderingContext2D, x: number, y: number) {
+ draw: function(ctx: RenderingContext2D, x: number, y: number) {
ctx.drawImage(this.image, x, y);
return (Date.now() - this.initTime > 1200);
}
diff --git a/src/js/stendhal/entity/Sign.ts b/src/js/stendhal/entity/Sign.ts
index 512c2c0f07..03debd3039 100644
--- a/src/js/stendhal/entity/Sign.ts
+++ b/src/js/stendhal/entity/Sign.ts
@@ -11,6 +11,7 @@
import { Entity } from "./Entity";
import { ActivityIndicatorSprite } from "../sprite/ActivityIndicatorSprite";
+import { RenderingContext2D } from "util/Types";
declare var stendhal: any;
@@ -32,7 +33,7 @@ export class Sign extends Entity {
}
}
- override draw(ctx: CanvasRenderingContext2D) {
+ override draw(ctx: RenderingContext2D) {
if (!this.imagePath) {
this.imagePath = stendhal.paths.sprites + "/signs/" + this["class"] + ".png";
}
diff --git a/src/js/stendhal/event/ViewChangeEvent.ts b/src/js/stendhal/event/ViewChangeEvent.ts
index f86330fd4b..022111416e 100644
--- a/src/js/stendhal/event/ViewChangeEvent.ts
+++ b/src/js/stendhal/event/ViewChangeEvent.ts
@@ -12,6 +12,7 @@
declare var stendhal: any;
+import { Canvas } from "util/Types";
import { RPEvent } from "./RPEvent";
@@ -24,8 +25,8 @@ export class ViewChangeEvent extends RPEvent {
public y!: number;
- execute(entity: any) {
- const canvas = stendhal.ui.viewport.getElement() as HTMLCanvasElement;
+ execute(_entity: any) {
+ const canvas = stendhal.ui.viewport.getElement() as Canvas;
queueMicrotask(() => {
stendhal.ui.viewport.freeze = true;
stendhal.ui.viewport.offsetX = this.x * stendhal.ui.viewport.targetTileWidth
diff --git a/src/js/stendhal/landscape/CombinedTileset.ts b/src/js/stendhal/landscape/CombinedTileset.ts
index 2731927447..9e4f45e564 100644
--- a/src/js/stendhal/landscape/CombinedTileset.ts
+++ b/src/js/stendhal/landscape/CombinedTileset.ts
@@ -9,6 +9,8 @@
* *
***************************************************************************/
+import { Canvas, RenderingContext2D } from "util/Types";
+
declare var stendhal: any;
/**
@@ -21,8 +23,8 @@ declare var stendhal: any;
* We use one large tileset for the combined tiles in order to be compatible with WebGL.
*/
export class CombinedTileset {
- public readonly canvas: HTMLCanvasElement;
- public readonly ctx: CanvasRenderingContext2D;
+ public readonly canvas: Canvas;
+ public readonly ctx: RenderingContext2D;
public readonly tilesPerRow: number;
constructor(numberOfTiles: number, public readonly combinedLayers: number[][]) {
diff --git a/src/js/stendhal/landscape/IndividualTilesetRenderingStrategy.ts b/src/js/stendhal/landscape/IndividualTilesetRenderingStrategy.ts
index ef1f4a9e3c..0920d981d1 100644
--- a/src/js/stendhal/landscape/IndividualTilesetRenderingStrategy.ts
+++ b/src/js/stendhal/landscape/IndividualTilesetRenderingStrategy.ts
@@ -15,6 +15,7 @@ import { LandscapeRenderingStrategy } from "./LandscapeRenderingStrategy";
import { ImagePreloader } from "../data/ImagePreloader";
import { Chat } from "../util/Chat";
import { TileMap } from "../data/TileMap";
+import { Canvas, RenderingContext2D } from "util/Types";
export class IndividualTilesetRenderingStrategy extends LandscapeRenderingStrategy {
@@ -42,7 +43,7 @@ export class IndividualTilesetRenderingStrategy extends LandscapeRenderingStrate
}
public render(
- canvas: HTMLCanvasElement, gamewindow: any,
+ canvas: Canvas, gamewindow: any,
tileOffsetX: number, tileOffsetY: number, targetTileWidth: number, targetTileHeight: number): void {
this.targetTileWidth = targetTileWidth;
@@ -60,12 +61,12 @@ export class IndividualTilesetRenderingStrategy extends LandscapeRenderingStrate
}
}
- private paintLayer(canvas: HTMLCanvasElement, drawingLayer: number,
+ private paintLayer(canvas: Canvas, drawingLayer: number,
tileOffsetX: number, tileOffsetY: number) {
const layer = stendhal.data.map.layers[drawingLayer];
const yMax = Math.min(tileOffsetY + canvas.height / this.targetTileHeight + 1, stendhal.data.map.zoneSizeY);
const xMax = Math.min(tileOffsetX + canvas.width / this.targetTileWidth + 1, stendhal.data.map.zoneSizeX);
- let ctx = canvas.getContext("2d")!;
+ let ctx = canvas.getContext("2d")! as RenderingContext2D;
for (let y = tileOffsetY; y < yMax; y++) {
for (let x = tileOffsetX; x < xMax; x++) {
@@ -90,7 +91,7 @@ export class IndividualTilesetRenderingStrategy extends LandscapeRenderingStrate
}
}
- private drawTile(ctx: CanvasRenderingContext2D, tileset: HTMLImageElement, idx: number, x: number, y: number, flip = 0) {
+ private drawTile(ctx: RenderingContext2D, tileset: HTMLImageElement, idx: number, x: number, y: number, flip = 0) {
const tilesetWidth = tileset.width;
const tilesPerRow = Math.floor(tilesetWidth / stendhal.data.map.tileWidth);
const pixelX = x * this.targetTileWidth;
diff --git a/src/js/stendhal/landscape/LandscapeRenderer.ts b/src/js/stendhal/landscape/LandscapeRenderer.ts
index be00690247..135b2bb6f5 100644
--- a/src/js/stendhal/landscape/LandscapeRenderer.ts
+++ b/src/js/stendhal/landscape/LandscapeRenderer.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { Canvas, RenderingContext2D } from "util/Types";
import { CombinedTileset } from "./CombinedTileset";
declare var stendhal: any;
@@ -16,13 +17,13 @@ declare var stendhal: any;
export class LandscapeRenderer {
drawLayer(
- canvas: HTMLCanvasElement,
+ canvas: Canvas,
combinedTileset: CombinedTileset, layerNo: number,
tileOffsetX: number, tileOffsetY: number, targetTileWidth: number, targetTileHeight: number): void {
if (!combinedTileset) {
return;
}
- let ctx = canvas.getContext("2d")!;
+ let ctx = canvas.getContext("2d")! as RenderingContext2D;
const layer = combinedTileset.combinedLayers[layerNo];
const yMax = Math.min(tileOffsetY + canvas.height / targetTileHeight + 1, stendhal.data.map.zoneSizeY);
diff --git a/src/js/stendhal/landscape/LandscapeRenderingStrategy.ts b/src/js/stendhal/landscape/LandscapeRenderingStrategy.ts
index 83a81281b9..7ca2156637 100644
--- a/src/js/stendhal/landscape/LandscapeRenderingStrategy.ts
+++ b/src/js/stendhal/landscape/LandscapeRenderingStrategy.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { Canvas } from "util/Types";
import { TileMap } from "../data/TileMap";
import { CombinedTilesetFactory } from "./CombinedTilesetFactory";
import { LandscapeRenderer } from "./LandscapeRenderer";
@@ -23,7 +24,7 @@ export abstract class LandscapeRenderingStrategy {
public abstract onTilesetLoaded(): void;
public abstract render(
- canvas: HTMLCanvasElement, gamewindow: any,
+ canvas: Canvas, gamewindow: any,
tileOffsetX: number, tileOffsetY: number, targetTileWidth: number, targetTileHeight: number): void;
}
@@ -41,7 +42,7 @@ export class CombinedTilesetRenderingStrategy extends LandscapeRenderingStrategy
}
public render(
- canvas: HTMLCanvasElement, gamewindow: any,
+ canvas: Canvas, gamewindow: any,
tileOffsetX: number, tileOffsetY: number, targetTileWidth: number, targetTileHeight: number): void {
let landscapeRenderder = new LandscapeRenderer();
diff --git a/src/js/stendhal/landscape/ParallaxBackground.ts b/src/js/stendhal/landscape/ParallaxBackground.ts
index f32472d943..214113f092 100644
--- a/src/js/stendhal/landscape/ParallaxBackground.ts
+++ b/src/js/stendhal/landscape/ParallaxBackground.ts
@@ -10,6 +10,7 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
import { singletons } from "../SingletonRepo";
@@ -75,7 +76,7 @@ export class ParallaxBackground {
this.image = undefined;
}
- draw(ctx: CanvasRenderingContext2D, offsetX: number, offsetY: number) {
+ draw(ctx: RenderingContext2D, offsetX: number, offsetY: number) {
if (!this.image || !this.image.height) {
return;
}
diff --git a/src/js/stendhal/sprite/AchievementBanner.ts b/src/js/stendhal/sprite/AchievementBanner.ts
index 3ba28b0d5c..5b5a8511da 100644
--- a/src/js/stendhal/sprite/AchievementBanner.ts
+++ b/src/js/stendhal/sprite/AchievementBanner.ts
@@ -11,6 +11,7 @@
import { TextBubble } from "./TextBubble";
import { BackgroundPainter } from "../util/BackgroundPainter";
+import { RenderingContext2D } from "util/Types";
declare const stendhal: any;
@@ -42,8 +43,7 @@ export class AchievementBanner extends TextBubble {
*/
this.duration = TextBubble.STANDARD_DUR * 4;
- const gamewindow =
- document.getElementById("viewport")!;
+ const gamewindow = document.getElementById("viewport")! as HTMLCanvasElement;
const td = this.getTextDimensions(gamewindow.getContext("2d")!);
this.innerWidth = td.width + this.padding; // add padding between icon & text
@@ -55,7 +55,7 @@ export class AchievementBanner extends TextBubble {
this.y = gamewindow.height - this.height;
}
- override draw(ctx: CanvasRenderingContext2D): boolean {
+ override draw(ctx: RenderingContext2D): boolean {
const targetX = stendhal.ui.gamewindow.offsetX + this.x;
const targetY = stendhal.ui.gamewindow.offsetY + this.y;
@@ -81,7 +81,7 @@ export class AchievementBanner extends TextBubble {
return this.expired();
}
- private getTextDimensions(ctx: CanvasRenderingContext2D): any {
+ private getTextDimensions(ctx: RenderingContext2D): any {
const ret = {} as any;
ctx.font = this.font;
let m = ctx.measureText(this.text);
diff --git a/src/js/stendhal/sprite/ActivityIndicatorSprite.ts b/src/js/stendhal/sprite/ActivityIndicatorSprite.ts
index 02ac50813b..14972fb703 100644
--- a/src/js/stendhal/sprite/ActivityIndicatorSprite.ts
+++ b/src/js/stendhal/sprite/ActivityIndicatorSprite.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
import { Paths } from "../data/Paths";
declare var stendhal: any;
@@ -42,7 +43,7 @@ export class ActivityIndicatorSprite {
* @param width
* Pixel width of parent object.
*/
- public draw(ctx: CanvasRenderingContext2D, dx: number, dy: number, width: number) {
+ public draw(ctx: RenderingContext2D, dx: number, dy: number, width: number) {
if (!ActivityIndicatorSprite.img.complete) {
return;
}
diff --git a/src/js/stendhal/sprite/EmojiSprite.ts b/src/js/stendhal/sprite/EmojiSprite.ts
index d3abc01580..e81c22dd28 100644
--- a/src/js/stendhal/sprite/EmojiSprite.ts
+++ b/src/js/stendhal/sprite/EmojiSprite.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
import { RPEntity } from "../entity/RPEntity";
@@ -24,7 +25,7 @@ export class EmojiSprite {
this.sprite = sprite;
}
- draw(ctx: CanvasRenderingContext2D): boolean {
+ draw(ctx: RenderingContext2D): boolean {
let x = this.owner["_x"] * 32 - 16;
let y = this.owner["_y"] * 32 - 32;
if (x < 0) {
diff --git a/src/js/stendhal/sprite/EmptySprite.ts b/src/js/stendhal/sprite/EmptySprite.ts
index 8ceac7cfdb..856d879b51 100644
--- a/src/js/stendhal/sprite/EmptySprite.ts
+++ b/src/js/stendhal/sprite/EmptySprite.ts
@@ -10,6 +10,7 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
import { Sprite } from "./Sprite";
@@ -53,7 +54,7 @@ export class EmptySprite {
* @param y
* The y location at which to draw the sprite
*/
- draw(g: CanvasRenderingContext2D, x: number, y: number): void;
+ draw(g: RenderingContext2D, x: number, y: number): void;
/**
* Draws the image.
@@ -73,8 +74,8 @@ export class EmptySprite {
* @param h
* the height
*/
- public draw(g: CanvasRenderingContext2D, destx: number, desty: number, x: number, y: number, w: number, h: number): void;
- public draw(g: CanvasRenderingContext2D, destx: number, desty: number, x?: number, y?: number, w?: number, h?: number): void {
+ public draw(g: RenderingContext2D, destx: number, desty: number, x: number, y: number, w: number, h: number): void;
+ public draw(g: RenderingContext2D, destx: number, desty: number, x?: number, y?: number, w?: number, h?: number): void {
}
/**
diff --git a/src/js/stendhal/sprite/Floater.ts b/src/js/stendhal/sprite/Floater.ts
index f529137473..08a4f1e925 100644
--- a/src/js/stendhal/sprite/Floater.ts
+++ b/src/js/stendhal/sprite/Floater.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
import { TextSprite } from "./TextSprite";
/**
@@ -23,7 +24,7 @@ export class Floater {
this.textSprite = new TextSprite(this.message, this.color, "12px sans-serif")
}
- draw(ctx: CanvasRenderingContext2D, x: number, y: number): boolean {
+ draw(ctx: RenderingContext2D, x: number, y: number): boolean {
let textOffset = this.textSprite.getTextMetrics(ctx).width / 2;
var timeDiff = Date.now() - this.initTime;
this.textSprite.draw(ctx, x - textOffset, y - timeDiff / 50);
diff --git a/src/js/stendhal/sprite/ImageSprite.ts b/src/js/stendhal/sprite/ImageSprite.ts
index 3e25d125f2..7a63c16fb4 100644
--- a/src/js/stendhal/sprite/ImageSprite.ts
+++ b/src/js/stendhal/sprite/ImageSprite.ts
@@ -13,6 +13,7 @@
import { ImageWithDimensions } from "data/ImageWithDimensions";
import { EmptySprite } from "./EmptySprite";
import { Sprite } from "./Sprite";
+import { Canvas, RenderingContext2D } from "util/Types";
/**
* A sprite to be displayed on the screen. Note that a sprite contains no state
@@ -49,7 +50,7 @@ export class ImageSprite implements Sprite {
this.reference = reference;
}
- private static createCompatibleImage(sprite: Sprite): HTMLCanvasElement {
+ private static createCompatibleImage(sprite: Sprite): Canvas {
const canvas = document.createElement('canvas');
const g = canvas.getContext('2d');
canvas.width = sprite.getWidth();
@@ -110,7 +111,7 @@ export class ImageSprite implements Sprite {
* @param y
* The y location at which to draw the sprite
*/
- public draw(g: CanvasRenderingContext2D, destx: number, desty: number): void;
+ public draw(g: RenderingContext2D, destx: number, desty: number): void;
/**
* Draws the image.
@@ -130,8 +131,8 @@ export class ImageSprite implements Sprite {
* @param h
* the height
*/
- public draw(g: CanvasRenderingContext2D, destx: number, desty: number, x: number, y: number, w: number, h: number): void;
- public draw(g: CanvasRenderingContext2D, destx: number, desty: number, x?: number, y?: number, w?: number, h?: number): void {
+ public draw(g: RenderingContext2D, destx: number, desty: number, x: number, y: number, w: number, h: number): void;
+ public draw(g: RenderingContext2D, destx: number, desty: number, x?: number, y?: number, w?: number, h?: number): void {
if (arguments.length === 7) {
g.drawImage(this.image, destx, desty, destx + w!, desty + h!, x!, y!, x! + w!, y! + h!);
} else {
diff --git a/src/js/stendhal/sprite/NotificationBubble.ts b/src/js/stendhal/sprite/NotificationBubble.ts
index df6d04b6ca..032bfa2e86 100644
--- a/src/js/stendhal/sprite/NotificationBubble.ts
+++ b/src/js/stendhal/sprite/NotificationBubble.ts
@@ -18,6 +18,7 @@ import { Color } from "../data/color/Color";
import { NotificationType } from "../util/NotificationType";
import { Speech } from "../util/Speech";
+import { RenderingContext2D } from "util/Types";
export class NotificationBubble extends TextBubble {
@@ -70,7 +71,7 @@ export class NotificationBubble extends TextBubble {
}
}
- override draw(ctx: CanvasRenderingContext2D): boolean {
+ override draw(ctx: RenderingContext2D): boolean {
const screenTop = stendhal.ui.gamewindow.offsetY;
const screenBottom = screenTop + ctx.canvas.height;
const screenLeft = stendhal.ui.gamewindow.offsetX;
diff --git a/src/js/stendhal/sprite/OverlaySpriteImpl.ts b/src/js/stendhal/sprite/OverlaySpriteImpl.ts
index 70da92fe57..47473d7bc6 100644
--- a/src/js/stendhal/sprite/OverlaySpriteImpl.ts
+++ b/src/js/stendhal/sprite/OverlaySpriteImpl.ts
@@ -10,13 +10,15 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
+
export interface OverlaySpriteImpl {
/**
* Instructions when sprite should be drawn on viewport.
*
- * @param {CanvasRenderingContext2D} ctx
+ * @param {RenderingContext2D} ctx
* The viewport canvas drawing context.
* @param {number} x
* Horizonal pixel position of where to draw on canvas.
@@ -29,7 +31,7 @@ export interface OverlaySpriteImpl {
* @returns {boolean}
* `true` to denote sprite is expired & should be removed.
*/
- draw(ctx: CanvasRenderingContext2D, x: number, y: number, drawWidth: number, drawHeight: number)
+ draw(ctx: RenderingContext2D, x: number, y: number, drawWidth: number, drawHeight: number)
: boolean;
/**
diff --git a/src/js/stendhal/sprite/SpeechBubble.ts b/src/js/stendhal/sprite/SpeechBubble.ts
index 2e8b71fc75..623e9b06cf 100644
--- a/src/js/stendhal/sprite/SpeechBubble.ts
+++ b/src/js/stendhal/sprite/SpeechBubble.ts
@@ -19,6 +19,7 @@ import { RPEntity } from "../entity/RPEntity";
import { Pair } from "../util/Pair";
import { Speech } from "../util/Speech";
+import { RenderingContext2D } from "util/Types";
export class SpeechBubble extends TextBubble {
@@ -52,7 +53,7 @@ export class SpeechBubble extends TextBubble {
this.text.length * TextBubble.STANDARD_DUR / 50);
}
- override draw(ctx: CanvasRenderingContext2D): boolean {
+ override draw(ctx: RenderingContext2D): boolean {
ctx.lineWidth = 2;
ctx.font = "14px Arial";
ctx.fillStyle = Color.WHITE;
diff --git a/src/js/stendhal/sprite/Sprite.ts b/src/js/stendhal/sprite/Sprite.ts
index acfb065ba5..e64b52a3f5 100644
--- a/src/js/stendhal/sprite/Sprite.ts
+++ b/src/js/stendhal/sprite/Sprite.ts
@@ -10,6 +10,8 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
+
/**
* A sprite to be displayed on the screen. Note that a sprite contains no state
* information, i.e. its just the image and not the location. This allows us to
@@ -47,7 +49,7 @@ export abstract class Sprite {
* @param y
* The y location at which to draw the sprite
*/
- abstract draw(g: CanvasRenderingContext2D, x: number, y: number): void;
+ abstract draw(g: RenderingContext2D, x: number, y: number): void;
/**
* Draws the image.
@@ -67,7 +69,7 @@ export abstract class Sprite {
* @param h
* the height
*/
- abstract draw(g: CanvasRenderingContext2D, destx: number, desty: number, x: number, y: number, w: number, h: number): void;
+ abstract draw(g: RenderingContext2D, destx: number, desty: number, x: number, y: number, w: number, h: number): void;
/**
* Get the height of the drawn sprite.
diff --git a/src/js/stendhal/sprite/TextBubble.ts b/src/js/stendhal/sprite/TextBubble.ts
index 97f4c1cc0f..fe522658b1 100644
--- a/src/js/stendhal/sprite/TextBubble.ts
+++ b/src/js/stendhal/sprite/TextBubble.ts
@@ -11,6 +11,7 @@
declare var stendhal: any;
+import { RenderingContext2D } from "util/Types";
import { Color } from "../data/color/Color";
import { Pair } from "../util/Pair";
@@ -45,7 +46,7 @@ export abstract class TextBubble {
* true if the sprites duration time has expired
* & should be removed from the screen.
*/
- abstract draw(ctx: CanvasRenderingContext2D): boolean;
+ abstract draw(ctx: RenderingContext2D): boolean;
getX(): number {
return this.x;
@@ -128,7 +129,13 @@ export abstract class TextBubble {
* @param ctx
* Canvas context on which text bubble is drawn.
*/
- onAdded(ctx: CanvasRenderingContext2D) {
+ onAdded(ctx: RenderingContext2D) {
+ // TODO: Reference the Viewport directly instead of deriving the canvas from the context
+ if (!(ctx instanceof CanvasRenderingContext2D)) {
+ console.error("Cannot add event listeners because HTMLCanvasElement is required.");
+ return;
+ }
+
// prevent multiple listeners from being added
if (typeof(this.onRemovedAction) === "undefined") {
// add click listener to remove chat bubble
diff --git a/src/js/stendhal/sprite/TextSprite.ts b/src/js/stendhal/sprite/TextSprite.ts
index 458ad09207..9ec21c69b2 100644
--- a/src/js/stendhal/sprite/TextSprite.ts
+++ b/src/js/stendhal/sprite/TextSprite.ts
@@ -9,6 +9,8 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
+
export class TextSprite {
private textMetrics?: TextMetrics;
@@ -25,7 +27,7 @@ export class TextSprite {
* @param x x coordinate
* @param y y coordinate
*/
- public draw(ctx: CanvasRenderingContext2D, x: number, y: number) {
+ public draw(ctx: RenderingContext2D, x: number, y: number) {
ctx.font = this.font;
ctx.lineWidth = 2;
ctx.strokeStyle = "black";
@@ -35,7 +37,7 @@ export class TextSprite {
ctx.fillText(this.text, x, y);
}
- getTextMetrics(ctx: CanvasRenderingContext2D) {
+ getTextMetrics(ctx: RenderingContext2D) {
if (!this.textMetrics) {
ctx.font = this.font;
this.textMetrics = ctx.measureText(this.text);
diff --git a/src/js/stendhal/sprite/action/AttackSprite.ts b/src/js/stendhal/sprite/action/AttackSprite.ts
index 588b62aef8..cce51d6911 100644
--- a/src/js/stendhal/sprite/action/AttackSprite.ts
+++ b/src/js/stendhal/sprite/action/AttackSprite.ts
@@ -10,6 +10,7 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
import { OverlaySpriteImpl } from "../OverlaySpriteImpl";
@@ -25,7 +26,7 @@ export abstract class AttackSprite implements OverlaySpriteImpl {
this.initTime = Date.now();
}
- abstract draw(ctx: CanvasRenderingContext2D, x: number, y: number, entityWidth: number,
+ abstract draw(ctx: RenderingContext2D, x: number, y: number, entityWidth: number,
entityHeight: number): boolean;
expired(): boolean {
diff --git a/src/js/stendhal/sprite/action/BarehandAttackSprite.ts b/src/js/stendhal/sprite/action/BarehandAttackSprite.ts
index c200445a04..a7e41a5b83 100644
--- a/src/js/stendhal/sprite/action/BarehandAttackSprite.ts
+++ b/src/js/stendhal/sprite/action/BarehandAttackSprite.ts
@@ -12,6 +12,7 @@
import { AttackSprite } from "./AttackSprite";
import { RPEntity } from "../../entity/RPEntity";
+import { RenderingContext2D } from "util/Types";
declare var stendhal: any;
@@ -28,7 +29,7 @@ export class BarehandAttackSprite extends AttackSprite {
this.image = image;
}
- override draw(ctx: CanvasRenderingContext2D, x: number, y: number, entityWidth: number,
+ override draw(ctx: RenderingContext2D, x: number, y: number, entityWidth: number,
entityHeight: number): boolean {
if (!this.image || !this.image.height) {
return this.expired();
diff --git a/src/js/stendhal/sprite/action/DirectionalSkillEffect.ts b/src/js/stendhal/sprite/action/DirectionalSkillEffect.ts
index 3b041cefba..c9e971be9c 100644
--- a/src/js/stendhal/sprite/action/DirectionalSkillEffect.ts
+++ b/src/js/stendhal/sprite/action/DirectionalSkillEffect.ts
@@ -15,6 +15,7 @@ import { SkillEffect } from "./SkillEffect";
import { Entity } from "../../entity/Entity";
import { Direction } from "../../util/Direction";
+import { RenderingContext2D } from "util/Types";
/**
@@ -44,7 +45,7 @@ export class DirectionalSkillEffect extends SkillEffect {
this.entity = entity;
}
- protected override drawInternal(ctx: CanvasRenderingContext2D, colIdx: number, x: number,
+ protected override drawInternal(ctx: RenderingContext2D, colIdx: number, x: number,
y: number, drawWidth: number, drawHeight: number) {
const dir = Direction.VALUES[parseInt(this.entity["dir"], 10)];
let rowIdx = dir.val - 1;
diff --git a/src/js/stendhal/sprite/action/MeleeAttackSprite.ts b/src/js/stendhal/sprite/action/MeleeAttackSprite.ts
index 4108a76b1b..8cdff51b67 100644
--- a/src/js/stendhal/sprite/action/MeleeAttackSprite.ts
+++ b/src/js/stendhal/sprite/action/MeleeAttackSprite.ts
@@ -15,6 +15,7 @@ declare var stendhal: any;
import { AttackSprite } from "./AttackSprite";
import { RPEntity } from "../../entity/RPEntity";
import { Direction } from "../../util/Direction";
+import { RenderingContext2D } from "util/Types";
export class MeleeAttackSprite extends AttackSprite {
@@ -108,7 +109,7 @@ export class MeleeAttackSprite extends AttackSprite {
}
}
- override draw(ctx: CanvasRenderingContext2D, x: number, y: number, entityWidth: number,
+ override draw(ctx: RenderingContext2D, x: number, y: number, entityWidth: number,
entityHeight: number): boolean {
const dtime = Date.now() - this.initTime;
const frameIndex = Math.floor(Math.min(dtime / 60, 2));
diff --git a/src/js/stendhal/sprite/action/RangedAttackSprite.ts b/src/js/stendhal/sprite/action/RangedAttackSprite.ts
index c3385ff7c0..f1dfc47207 100644
--- a/src/js/stendhal/sprite/action/RangedAttackSprite.ts
+++ b/src/js/stendhal/sprite/action/RangedAttackSprite.ts
@@ -12,6 +12,7 @@
import { AttackSprite } from "./AttackSprite";
import { RPEntity } from "../../entity/RPEntity";
+import { RenderingContext2D } from "util/Types";
declare var stendhal: any;
@@ -35,7 +36,7 @@ export class RangedAttackSprite extends AttackSprite {
this.weapon = weapon;
}
- override draw(ctx: CanvasRenderingContext2D, x: number, y: number, entityWidth: number,
+ override draw(ctx: RenderingContext2D, x: number, y: number, entityWidth: number,
entityHeight: number): boolean {
// FIXME: alignment with entity is not correct
diff --git a/src/js/stendhal/sprite/action/SkillEffect.ts b/src/js/stendhal/sprite/action/SkillEffect.ts
index 026d6054fb..5ba8b3e27e 100644
--- a/src/js/stendhal/sprite/action/SkillEffect.ts
+++ b/src/js/stendhal/sprite/action/SkillEffect.ts
@@ -13,6 +13,7 @@
import { OverlaySpriteImpl } from "../OverlaySpriteImpl";
import { singletons } from "../../SingletonRepo";
+import { RenderingContext2D } from "util/Types";
/**
@@ -56,7 +57,7 @@ export class SkillEffect implements OverlaySpriteImpl {
/**
* Draws sprite to canvas.
*
- * @param {CanvasRenderingContext2D} ctx
+ * @param {RenderingContext2D} ctx
* Canvas drawing context.
* @param {number} colIdx
* Horizontal frame index.
@@ -69,13 +70,13 @@ export class SkillEffect implements OverlaySpriteImpl {
* @param {number} drawHeight
* Height of each frame to draw.
*/
- protected drawInternal(ctx: CanvasRenderingContext2D, colIdx: number, x: number, y: number,
+ protected drawInternal(ctx: RenderingContext2D, colIdx: number, x: number, y: number,
drawWidth: number, drawHeight: number) {
ctx.drawImage(this.image, colIdx*drawWidth, 0, drawWidth, drawHeight, x, y, drawWidth,
drawHeight);
}
- draw(ctx: CanvasRenderingContext2D, x=0, y=0, drawWidth=48, drawHeight=64): boolean {
+ draw(ctx: RenderingContext2D, x=0, y=0, drawWidth=48, drawHeight=64): boolean {
this.drawTime = Date.now();
if (!this.image.height) {
return this.expired();
diff --git a/src/js/stendhal/ui/HTMLManager.ts b/src/js/stendhal/ui/HTMLManager.ts
index 18b08acc0f..490699a40d 100644
--- a/src/js/stendhal/ui/HTMLManager.ts
+++ b/src/js/stendhal/ui/HTMLManager.ts
@@ -9,8 +9,6 @@
* *
***************************************************************************/
-declare var stendhal: any;
-
/**
* HTML code manipulation.
diff --git a/src/js/stendhal/ui/ViewPort.ts b/src/js/stendhal/ui/ViewPort.ts
index bd3e3c6134..19d641e07c 100644
--- a/src/js/stendhal/ui/ViewPort.ts
+++ b/src/js/stendhal/ui/ViewPort.ts
@@ -31,6 +31,7 @@ import { SpeechBubble } from "../sprite/SpeechBubble";
import { TextBubble } from "../sprite/TextBubble";
import { Point } from "../util/Point";
+import { Canvas, RenderingContext2D } from "util/Types";
/**
@@ -53,7 +54,7 @@ export class ViewPort {
private readonly height: number;
/** Drawing context. */
- private ctx: CanvasRenderingContext2D;
+ private ctx: RenderingContext2D;
/** Map tile pixel width. */
private readonly targetTileWidth = 32;
/** Map tile pixel height. */
@@ -332,10 +333,10 @@ export class ViewPort {
/**
* Updates viewport drawing position of map based on player position.
*
- * @param canvas {HTMLCanvasElement}
+ * @param canvas {Canvas}
* Viewport canvas element.
*/
- adjustView(canvas: HTMLCanvasElement) {
+ adjustView(canvas: Canvas) {
// IE does not support ctx.resetTransform(), so use the following workaround:
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
diff --git a/src/js/stendhal/ui/component/MiniMapComponent.ts b/src/js/stendhal/ui/component/MiniMapComponent.ts
index ea0a5d464e..8f01dd678c 100644
--- a/src/js/stendhal/ui/component/MiniMapComponent.ts
+++ b/src/js/stendhal/ui/component/MiniMapComponent.ts
@@ -17,6 +17,7 @@ import { Component } from "../toolkit/Component";
import { Player } from "../../entity/Player";
import { Color } from "../../data/color/Color";
+import { Canvas, RenderingContext2D } from "util/Types";
/**
@@ -32,7 +33,7 @@ export class MiniMapComponent extends Component {
private mapWidth = 1;
private mapHeight = 1;
private scale = 1;
- private bgImage!: HTMLCanvasElement;
+ private bgImage!: Canvas;
private lastZone?: number[];
// ground/collision colors
@@ -119,7 +120,7 @@ export class MiniMapComponent extends Component {
}
}
- drawBackground(ctx: CanvasRenderingContext2D) {
+ drawBackground(ctx: RenderingContext2D) {
ctx.save();
ctx.imageSmoothingEnabled = false;
@@ -168,7 +169,7 @@ export class MiniMapComponent extends Component {
}
}
- drawEntities(ctx: CanvasRenderingContext2D) {
+ drawEntities(ctx: RenderingContext2D) {
ctx.fillStyle = Color.RED;
ctx.strokeStyle = Color.BLACK;
let isAdmin = marauroa.me["adminlevel"] && marauroa.me["adminlevel"] >= 600;
diff --git a/src/js/stendhal/ui/component/OutfitPreviewComponent.ts b/src/js/stendhal/ui/component/OutfitPreviewComponent.ts
index 91b8ab4910..4bfa862471 100644
--- a/src/js/stendhal/ui/component/OutfitPreviewComponent.ts
+++ b/src/js/stendhal/ui/component/OutfitPreviewComponent.ts
@@ -16,8 +16,7 @@ import { Outfit } from "../../data/Outfit";
import { Direction } from "../../util/Direction";
import { StringUtil } from "../../util/StringUtil";
-
-declare var stendhal: any;
+import { Canvas } from "util/Types";
/**
diff --git a/src/js/stendhal/ui/component/StatBarComponent.ts b/src/js/stendhal/ui/component/StatBarComponent.ts
index b25b384ca1..2f13f329a0 100644
--- a/src/js/stendhal/ui/component/StatBarComponent.ts
+++ b/src/js/stendhal/ui/component/StatBarComponent.ts
@@ -11,6 +11,7 @@
import { Component } from "../toolkit/Component";
import { Color } from "../../data/color/Color";
+import { RenderingContext2D } from "util/Types";
/**
* TODO: move this to `ui.toolkit`.
@@ -18,7 +19,7 @@ import { Color } from "../../data/color/Color";
export class StatBarComponent extends Component {
readonly canvas: HTMLCanvasElement;
- protected readonly ctx: CanvasRenderingContext2D;
+ protected readonly ctx: RenderingContext2D;
constructor(id?: string) {
@@ -26,7 +27,7 @@ export class StatBarComponent extends Component {
id = "statbar-template";
}
super(id);
- this.canvas = this.componentElement;
+ this.canvas = this.componentElement as HTMLCanvasElement;
this.ctx = this.canvas.getContext("2d")!;
}
diff --git a/src/js/stendhal/ui/dialog/outfit/OutfitColorSelector.ts b/src/js/stendhal/ui/dialog/outfit/OutfitColorSelector.ts
index 34de135338..200d38d204 100644
--- a/src/js/stendhal/ui/dialog/outfit/OutfitColorSelector.ts
+++ b/src/js/stendhal/ui/dialog/outfit/OutfitColorSelector.ts
@@ -9,12 +9,14 @@
* *
***************************************************************************/
+import { Canvas, RenderingContext2D } from "util/Types";
+
declare var stendhal: any;
export class OutfitColorSelector {
- protected ctx: CanvasRenderingContext2D;
- private gradCtx: CanvasRenderingContext2D;
- protected baseImage: HTMLCanvasElement;
+ protected ctx: RenderingContext2D;
+ private gradCtx: RenderingContext2D;
+ protected baseImage: Canvas;
private onColorChanged: Function;
private _enabled: boolean;
protected _x: number;
diff --git a/src/js/stendhal/ui/dialog/outfit/OutfitPartSelector.ts b/src/js/stendhal/ui/dialog/outfit/OutfitPartSelector.ts
index 041110c265..d28cee65ba 100644
--- a/src/js/stendhal/ui/dialog/outfit/OutfitPartSelector.ts
+++ b/src/js/stendhal/ui/dialog/outfit/OutfitPartSelector.ts
@@ -9,6 +9,8 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
+
declare var stendhal: any;
export class OutfitPartSelector {
@@ -16,7 +18,7 @@ export class OutfitPartSelector {
private _onPartChanged: Function;
private _index: number;
private _maxindex: number;
- private _ctx: CanvasRenderingContext2D;
+ private _ctx: RenderingContext2D;
private _width: number;
private _height: number;
private _color?: any;
diff --git a/src/js/stendhal/util/BackgroundPainter.ts b/src/js/stendhal/util/BackgroundPainter.ts
index 0681a60c2c..80b3dd0a5f 100644
--- a/src/js/stendhal/util/BackgroundPainter.ts
+++ b/src/js/stendhal/util/BackgroundPainter.ts
@@ -9,6 +9,8 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "./Types";
+
export class BackgroundPainter {
@@ -54,7 +56,7 @@ export class BackgroundPainter {
* @param height
* Number of vertical tiles to draw.
*/
- paint(ctx: CanvasRenderingContext2D, x: number, y: number,
+ paint(ctx: RenderingContext2D, x: number, y: number,
width: number, height: number) {
// FIXME: if image parts were not cached, tile width & height may be 0
if (!this.tileW || !this.tileH) {
diff --git a/src/js/stendhal/util/DownloadUtil.ts b/src/js/stendhal/util/DownloadUtil.ts
index 776ecbb415..49b6a17f46 100644
--- a/src/js/stendhal/util/DownloadUtil.ts
+++ b/src/js/stendhal/util/DownloadUtil.ts
@@ -64,7 +64,7 @@ export class DownloadUtil {
*
* @param filename {string}
* Default filename.
- * @param canvas {HTMLCanvasElement}
+ * @param canvas {Canvas}
* Canvas containing data to convert to image.
* @return {util.DownloadObject.DownloadObject}
* Object to be handled by browser for image download.
@@ -90,7 +90,7 @@ export class DownloadUtil {
/**
* Creates an image download from a canvas element.
*
- * @param canvas {HTMLCanvasElement}
+ * @param canvas {Canvas}
* Canvas containing data to convert to image.
* @return {util.DownloadObject.DownloadObject}
* Object to be handled by browser for image download.
diff --git a/src/js/stendhal/util/DrawingStage.ts b/src/js/stendhal/util/DrawingStage.ts
index 804e4f5338..b62531772c 100644
--- a/src/js/stendhal/util/DrawingStage.ts
+++ b/src/js/stendhal/util/DrawingStage.ts
@@ -10,6 +10,8 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "./Types";
+
/**
* Hidden canvas for manipulating images before they are displayed.
@@ -19,7 +21,7 @@ export class DrawingStage {
/** Hidden canvas element. */
private canvas: HTMLCanvasElement;
/** Canvas's drawing context. */
- private ctx: CanvasRenderingContext2D;
+ private ctx: RenderingContext2D;
/** Singleton instance. */
private static instance: DrawingStage;
@@ -86,6 +88,7 @@ export class DrawingStage {
* @returns {string}
* PNG data encoded string.
*/
+ // TODO: Do we really need this? We could use an offscreen canvas otherwise
toDataURL(): string {
return this.canvas.toDataURL("image/png");
}
diff --git a/src/js/stendhal/util/ScreenCapture.ts b/src/js/stendhal/util/ScreenCapture.ts
index ed100a8527..e4e2eef35c 100644
--- a/src/js/stendhal/util/ScreenCapture.ts
+++ b/src/js/stendhal/util/ScreenCapture.ts
@@ -56,7 +56,7 @@ export class ScreenCapture {
/**
* Begins recording.
*
- * @param {HTMLCanvasElement} canvas
+ * @param {Canvas} canvas
* Canvas element to record.
* @param {AudioContext=} audio
* Sound manager from which to record audio.
diff --git a/src/js/stendhal/util/Speech.ts b/src/js/stendhal/util/Speech.ts
index f548deeb63..0808b6b3a5 100644
--- a/src/js/stendhal/util/Speech.ts
+++ b/src/js/stendhal/util/Speech.ts
@@ -9,6 +9,8 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "./Types";
+
declare var stendhal: any
@@ -27,7 +29,7 @@ export class Speech {
* @param width
* @param height
*/
- static drawBubble(ctx: CanvasRenderingContext2D, x: number, y: number,
+ static drawBubble(ctx: RenderingContext2D, x: number, y: number,
width: number, height: number, tail: boolean=false) {
ctx.strokeRect(x, y - 15, width, height);
ctx.fillRect(x, y - 15, width, height);
@@ -57,7 +59,7 @@ export class Speech {
* @param width
* @param height
*/
- static drawBubbleRounded(ctx: CanvasRenderingContext2D, x: number,
+ static drawBubbleRounded(ctx: RenderingContext2D, x: number,
y: number, width: number, height: number) {
//const arc = this.arc_diameter;
const arc = 3;
diff --git a/src/js/stendhal/util/Types.ts b/src/js/stendhal/util/Types.ts
new file mode 100644
index 0000000000..49dbf3ca90
--- /dev/null
+++ b/src/js/stendhal/util/Types.ts
@@ -0,0 +1,2 @@
+export type RenderingContext2D = CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
+export type Canvas = HTMLCanvasElement | OffscreenCanvas;
\ No newline at end of file
diff --git a/src/js/stendhal/util/WeatherRenderer.ts b/src/js/stendhal/util/WeatherRenderer.ts
index 05181321b2..096144e223 100644
--- a/src/js/stendhal/util/WeatherRenderer.ts
+++ b/src/js/stendhal/util/WeatherRenderer.ts
@@ -12,6 +12,7 @@
import { TileStore } from "../data/TileStore";
import { SoundObject } from "../data/sound/SoundFactory";
+import { Canvas, RenderingContext2D } from "./Types";
declare var stendhal: any;
@@ -143,7 +144,7 @@ export class WeatherRenderer {
console.log("using failsafe sprite height: " + spriteH);
}
- const canvas = document.getElementById("viewport") as HTMLCanvasElement;
+ const canvas = document.getElementById("viewport") as Canvas;
this.tilesX = Math.ceil(canvas.width / spriteH) + 1;
this.tilesY = Math.ceil(canvas.height / spriteH) + 1;
@@ -159,7 +160,7 @@ export class WeatherRenderer {
* @param ctx
* Drawing target element.
*/
- draw(ctx: CanvasRenderingContext2D) {
+ draw(ctx: RenderingContext2D) {
if (this.enabled && this.sprite && this.sprite.frames) {
if (!this.tilesX || !this.tilesY) {
if (!this.warned.tiling) {
@@ -191,11 +192,11 @@ export class WeatherRenderer {
/**
* Draws clouds animation.
*
- * @param {CanvasRenderingContext2D) ctx
+ * @param {RenderingContext2D) ctx
* @param {number} offsetX
* @param {number} offsetY
*/
- private drawClouds(ctx: CanvasRenderingContext2D, offsetX: number, offsetY: number) {
+ private drawClouds(ctx: RenderingContext2D, offsetX: number, offsetY: number) {
const drawStart = Date.now();
const timeDiff = drawStart - this.lastUpdate;
const dim = {width: this.sprite!.width, height: this.sprite!.height};
@@ -226,11 +227,11 @@ export class WeatherRenderer {
/**
* Draws fog animation.
*
- * @param {CanvasRenderingContext2D) ctx
+ * @param {RenderingContext2D) ctx
* @param {number} offsetX
* @param {number} offsetY
*/
- private drawFog(ctx: CanvasRenderingContext2D, offsetX: number, offsetY: number) {
+ private drawFog(ctx: RenderingContext2D, offsetX: number, offsetY: number) {
ctx.save();
if (!this.heavyFog) {
// reduce opacity for light fog
@@ -243,11 +244,11 @@ export class WeatherRenderer {
/**
* Draws types of weather other than fog.
*
- * @param {CanvasRenderingContext2D) ctx
+ * @param {RenderingContext2D) ctx
* @param {number} offsetX
* @param {number} offsetY
*/
- private drawOther(ctx: CanvasRenderingContext2D, offsetX: number, offsetY: number) {
+ private drawOther(ctx: RenderingContext2D, offsetX: number, offsetY: number) {
// width & height dimensions should be the same
const dim = this.sprite!.height;
const clipLeft = offsetX % dim;
From 6ff59a0040c170080ed2820237bc50e0134a8570 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Sun, 28 Dec 2025 12:40:28 +0100
Subject: [PATCH 031/162] refactored drawCombinedTileAtIndex and drawTile in
preparation for blend
---
.../landscape/CombinedTilesetImageLoader.ts | 73 +++++++++++--------
1 file changed, 41 insertions(+), 32 deletions(-)
diff --git a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
index a042e2a91d..d0c463ea72 100644
--- a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
+++ b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { RenderingContext2D } from "util/Types";
import { TileMap } from "../data/TileMap";
import { TileStore } from "../data/TileStore";
import { MapOfSets } from "../util/MapOfSets";
@@ -24,11 +25,11 @@ export class CombinedTilesetImageLoader {
private landscapeAnimationMap: any;
constructor(
- private map: TileMap,
- private indexToCombinedTiles: Map,
- private combinedTileset: CombinedTileset) {
- this.landscapeAnimationMap = TileStore.get().getLandscapeMap();
- }
+ private map: TileMap,
+ private indexToCombinedTiles: Map,
+ private combinedTileset: CombinedTileset) {
+ this.landscapeAnimationMap = TileStore.get().getLandscapeMap();
+ }
private calculateTileUsedAtIndex(): void {
@@ -100,25 +101,26 @@ export class CombinedTilesetImageLoader {
const pixelX = x * this.map.tileWidth;
const pixelY = y * this.map.tileHeight;
+ // Disabled because alpha is broken
+ if (false && stendhal.ui.gamewindow.HSLFilter) {
+ this.drawCombinedTileWithBlend(tiles, pixelX, pixelY);
+ } else {
+ this.drawCombinedTileWithoutBlend(this.combinedTileset.ctx, tiles, pixelX, pixelY);
+ }
+ }
+
+ private drawCombinedTileWithoutBlend(ctx: RenderingContext2D, tiles: number[], pixelX: number, pixelY: number) {
this.combinedTileset.ctx.clearRect(pixelX, pixelY, this.map.tileWidth, this.map.tileHeight);
for (let [i, tile] of tiles.entries()) {
if (i === 0) {
continue; // skip blend layer
}
- let flip = tile & 0xE0000000;
- let gid = tile & 0x1FFFFFFF;
- let tileset = this.map.getTilesetForGid(gid);
- let image = this.tilesetImages[tileset];
- if (!image || !image.height) {
- continue;
- }
-
- let base = this.map.firstgids[tileset];
- let tileIndexInTileset = gid - base;
- this.drawTile(pixelX, pixelY, image, tileIndexInTileset, flip);
+ this.drawTile(ctx, tile, pixelX, pixelY);
}
+ }
- /*
+ private drawCombinedTileWithBlend(tiles: number[], pixelX: number, pixelY: number) {
+ this.drawCombinedTileWithoutBlend(this.combinedTileset.ctx, tiles, pixelX, pixelY);
if (stendhal.ui.gamewindow.HSLFilter) {
this.combinedTileset.ctx.globalCompositeOperation = "multiply";
this.combinedTileset.ctx.fillStyle = stendhal.ui.gamewindow.HSLFilter;
@@ -127,32 +129,34 @@ export class CombinedTilesetImageLoader {
let tile = tiles[0];
this.combinedTileset.ctx.globalCompositeOperation = "soft-light";
if (tile > 0) {
- let flip = tile & 0xE0000000;
- let gid = tile & 0x1FFFFFFF;
- let tileset = this.map.getTilesetForGid(gid);
- let image = this.tilesetImages[tileset];
- if (image && image.height) {
- let base = this.map.firstgids[tileset];
- let tileIndexInTileset = gid - base;
- this.drawTile(pixelX, pixelY, image, tileIndexInTileset, flip);
- }
+ this.drawTile(this.combinedTileset.ctx, tile, pixelX, pixelY);
} else {
this.combinedTileset.ctx.fillStyle = "black";
this.combinedTileset.ctx.fillRect(pixelX, pixelY, this.map.tileWidth, this.map.tileHeight);
}
this.combinedTileset.ctx.globalCompositeOperation = "source-over";
- // document.getElementsByTagName("body")[0].append(this.tempCanvas);
}
- */
}
- private drawTile(pixelX: number, pixelY: number, tilesetImage: HTMLImageElement, tileIndexInTileset: number, flip: number) {
+ private drawTile(ctx: RenderingContext2D, tile: number, pixelX: number, pixelY: number) {
+ let flip = tile & 0xE0000000;
+ let gid = tile & 0x1FFFFFFF;
+ let tileset = this.map.getTilesetForGid(gid);
+ let image = this.tilesetImages[tileset];
+ if (!image || !image.height) {
+ return;
+ }
+
+ let base = this.map.firstgids[tileset];
+ let tileIndexInTileset = gid - base;
+ this.drawImageTile(ctx, pixelX, pixelY, image, tileIndexInTileset, flip);
+ }
+
+ private drawImageTile(ctx: RenderingContext2D, pixelX: number, pixelY: number, tilesetImage: HTMLImageElement, tileIndexInTileset: number, flip: number) {
const tilesetWidth = tilesetImage.width;
const tilesPerRow = Math.floor(tilesetWidth / this.map.tileWidth);
- const ctx = this.combinedTileset.ctx;
-
if (flip === 0) {
ctx.drawImage(tilesetImage,
(tileIndexInTileset % tilesPerRow) * this.map.tileWidth,
@@ -188,7 +192,12 @@ export class CombinedTilesetImageLoader {
restore.push([0, 1, 1, 0, 0, 0]);
}
- this.drawTile(0, 0, tilesetImage, tileIndexInTileset, 0);
+ ctx.drawImage(tilesetImage,
+ (tileIndexInTileset % tilesPerRow) * this.map.tileWidth,
+ Math.floor(tileIndexInTileset / tilesPerRow) * this.map.tileHeight,
+ this.map.tileWidth, this.map.tileHeight,
+ 0, 0,
+ this.map.tileWidth, this.map.tileHeight);
restore.reverse();
for (const args of restore) {
From 09371bff4748f1bff9440cac94a17a80bf819e69 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Sun, 28 Dec 2025 14:41:39 +0100
Subject: [PATCH 032/162] restore transparency after blending
---
.../landscape/CombinedTilesetImageLoader.ts | 52 +++++++++++++------
1 file changed, 35 insertions(+), 17 deletions(-)
diff --git a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
index d0c463ea72..2c1c8f16c2 100644
--- a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
+++ b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
@@ -9,7 +9,7 @@
* *
***************************************************************************/
-import { RenderingContext2D } from "util/Types";
+import { Canvas, RenderingContext2D } from "util/Types";
import { TileMap } from "../data/TileMap";
import { TileStore } from "../data/TileStore";
import { MapOfSets } from "../util/MapOfSets";
@@ -23,12 +23,21 @@ export class CombinedTilesetImageLoader {
private tilesetImages: HTMLImageElement[] = [];
private animations: any = {};
private landscapeAnimationMap: any;
+ private drawCanvas: Canvas;
+ private drawCtx: RenderingContext2D;
+ private blendCanvas: Canvas;
+ private blendCtx: RenderingContext2D;
constructor(
private map: TileMap,
private indexToCombinedTiles: Map,
private combinedTileset: CombinedTileset) {
this.landscapeAnimationMap = TileStore.get().getLandscapeMap();
+
+ this.drawCanvas = new OffscreenCanvas(this.map.tileWidth, this.map.tileHeight);
+ this.blendCanvas = new OffscreenCanvas(this.map.tileWidth, this.map.tileHeight);
+ this.drawCtx = this.drawCanvas.getContext("2d")!;
+ this.blendCtx = this.blendCanvas.getContext("2d")!;
}
@@ -101,16 +110,16 @@ export class CombinedTilesetImageLoader {
const pixelX = x * this.map.tileWidth;
const pixelY = y * this.map.tileHeight;
- // Disabled because alpha is broken
if (false && stendhal.ui.gamewindow.HSLFilter) {
this.drawCombinedTileWithBlend(tiles, pixelX, pixelY);
} else {
this.drawCombinedTileWithoutBlend(this.combinedTileset.ctx, tiles, pixelX, pixelY);
}
+ document.getElementsByTagName("body")[0].append(this.combinedTileset.canvas as HTMLCanvasElement);
}
private drawCombinedTileWithoutBlend(ctx: RenderingContext2D, tiles: number[], pixelX: number, pixelY: number) {
- this.combinedTileset.ctx.clearRect(pixelX, pixelY, this.map.tileWidth, this.map.tileHeight);
+ ctx.clearRect(pixelX, pixelY, this.map.tileWidth, this.map.tileHeight);
for (let [i, tile] of tiles.entries()) {
if (i === 0) {
continue; // skip blend layer
@@ -120,23 +129,32 @@ export class CombinedTilesetImageLoader {
}
private drawCombinedTileWithBlend(tiles: number[], pixelX: number, pixelY: number) {
- this.drawCombinedTileWithoutBlend(this.combinedTileset.ctx, tiles, pixelX, pixelY);
- if (stendhal.ui.gamewindow.HSLFilter) {
- this.combinedTileset.ctx.globalCompositeOperation = "multiply";
- this.combinedTileset.ctx.fillStyle = stendhal.ui.gamewindow.HSLFilter;
- this.combinedTileset.ctx.fillRect(pixelX, pixelY, this.map.tileWidth, this.map.tileHeight);
- let tile = tiles[0];
- this.combinedTileset.ctx.globalCompositeOperation = "soft-light";
- if (tile > 0) {
- this.drawTile(this.combinedTileset.ctx, tile, pixelX, pixelY);
- } else {
- this.combinedTileset.ctx.fillStyle = "black";
- this.combinedTileset.ctx.fillRect(pixelX, pixelY, this.map.tileWidth, this.map.tileHeight);
- }
+ this.drawCombinedTileWithoutBlend(this.drawCtx, tiles, 0, 0);
- this.combinedTileset.ctx.globalCompositeOperation = "source-over";
+ this.blendCtx.globalCompositeOperation = "source-over";
+ this.blendCtx.clearRect(0, 0, this.map.tileWidth, this.map.tileHeight);
+ this.blendCtx.drawImage(this.drawCanvas, 0, 0);
+
+ this.blendCtx.globalCompositeOperation = "multiply";
+ this.blendCtx.fillStyle = stendhal.ui.gamewindow.HSLFilter;
+ this.blendCtx.fillRect(0, 0, this.map.tileWidth, this.map.tileHeight);
+
+ let tile = tiles[0];
+ this.blendCtx.globalCompositeOperation = "soft-light";
+ if (tile > 0) {
+ this.drawTile(this.blendCtx, tile, 0, 0);
+ } else {
+ this.blendCtx.fillStyle = "black";
+ this.blendCtx.fillRect(0, 0, this.map.tileWidth, this.map.tileHeight);
}
+
+ // restore transparency which was lost during multiply and soft-light composition
+ this.blendCtx.globalCompositeOperation = "destination-in";
+ this.blendCtx.drawImage(this.drawCanvas, 0, 0);
+
+ this.combinedTileset.ctx.clearRect(pixelX, pixelY, this.map.tileWidth, this.map.tileHeight);
+ this.combinedTileset.ctx.drawImage(this.blendCanvas, pixelX, pixelY);
}
private drawTile(ctx: RenderingContext2D, tile: number, pixelX: number, pixelY: number) {
From 4dbe1f73fe4a1c0c635db25c9ee98bcbfa2b5610 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Sun, 28 Dec 2025 17:51:51 +0100
Subject: [PATCH 033/162] implemented light rendering (to test "/debug light"
and change zone)
---
src/js/stendhal/action/DebugAction.ts | 5 ++++-
.../landscape/CombinedTilesetImageLoader.ts | 17 +++++++++++++++--
src/js/stendhal/ui/ViewPort.ts | 5 ++++-
3 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/src/js/stendhal/action/DebugAction.ts b/src/js/stendhal/action/DebugAction.ts
index 148a303d82..cbb0715d3a 100644
--- a/src/js/stendhal/action/DebugAction.ts
+++ b/src/js/stendhal/action/DebugAction.ts
@@ -55,6 +55,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 +68,8 @@ export class DebugAction extends SlashAction {
" /debug ui [pop]",
" /debug weather []",
" /debug settings",
- " /debug touch"
+ " /debug touch",
+ " /debug light"
];
Chat.log("client", usage);
}
diff --git a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
index 2c1c8f16c2..7fce2d3dc2 100644
--- a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
+++ b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
@@ -14,6 +14,7 @@ import { TileMap } from "../data/TileMap";
import { TileStore } from "../data/TileStore";
import { MapOfSets } from "../util/MapOfSets";
import { CombinedTileset } from "./CombinedTileset";
+import { Debug } from "../util/Debug";
declare var stendhal: any;
@@ -27,6 +28,8 @@ export class CombinedTilesetImageLoader {
private drawCtx: RenderingContext2D;
private blendCanvas: Canvas;
private blendCtx: RenderingContext2D;
+ private blendFixCanvas: Canvas;
+ private blendFixCtx: RenderingContext2D;
constructor(
private map: TileMap,
@@ -36,8 +39,10 @@ export class CombinedTilesetImageLoader {
this.drawCanvas = new OffscreenCanvas(this.map.tileWidth, this.map.tileHeight);
this.blendCanvas = new OffscreenCanvas(this.map.tileWidth, this.map.tileHeight);
+ this.blendFixCanvas = new OffscreenCanvas(this.map.tileWidth, this.map.tileHeight);
this.drawCtx = this.drawCanvas.getContext("2d")!;
this.blendCtx = this.blendCanvas.getContext("2d")!;
+ this.blendFixCtx = this.blendFixCanvas.getContext("2d")!;
}
@@ -110,7 +115,7 @@ export class CombinedTilesetImageLoader {
const pixelX = x * this.map.tileWidth;
const pixelY = y * this.map.tileHeight;
- if (false && stendhal.ui.gamewindow.HSLFilter) {
+ if (Debug.isActive("light") && stendhal.ui.gamewindow.HSLFilter) {
this.drawCombinedTileWithBlend(tiles, pixelX, pixelY);
} else {
this.drawCombinedTileWithoutBlend(this.combinedTileset.ctx, tiles, pixelX, pixelY);
@@ -132,6 +137,7 @@ export class CombinedTilesetImageLoader {
this.drawCombinedTileWithoutBlend(this.drawCtx, tiles, 0, 0);
+ // copy the image to the blend canvas
this.blendCtx.globalCompositeOperation = "source-over";
this.blendCtx.clearRect(0, 0, this.map.tileWidth, this.map.tileHeight);
this.blendCtx.drawImage(this.drawCanvas, 0, 0);
@@ -143,7 +149,13 @@ export class CombinedTilesetImageLoader {
let tile = tiles[0];
this.blendCtx.globalCompositeOperation = "soft-light";
if (tile > 0) {
- this.drawTile(this.blendCtx, tile, 0, 0);
+ // turn transparency into black for the blend layer
+ this.blendFixCtx.fillStyle = "black";
+ this.blendFixCtx.fillRect(0, 0, this.map.tileWidth, this.map.tileHeight);
+ this.drawTile(this.blendFixCtx, tile, 0, 0);
+
+ // draw the blend layer onto the blend canvas
+ this.blendCtx.drawImage(this.blendFixCanvas, 0, 0);
} else {
this.blendCtx.fillStyle = "black";
this.blendCtx.fillRect(0, 0, this.map.tileWidth, this.map.tileHeight);
@@ -153,6 +165,7 @@ export class CombinedTilesetImageLoader {
this.blendCtx.globalCompositeOperation = "destination-in";
this.blendCtx.drawImage(this.drawCanvas, 0, 0);
+ // draw the blend canvas at the correct position onto the combined tileset
this.combinedTileset.ctx.clearRect(pixelX, pixelY, this.map.tileWidth, this.map.tileHeight);
this.combinedTileset.ctx.drawImage(this.blendCanvas, pixelX, pixelY);
}
diff --git a/src/js/stendhal/ui/ViewPort.ts b/src/js/stendhal/ui/ViewPort.ts
index 19d641e07c..3ac22fa449 100644
--- a/src/js/stendhal/ui/ViewPort.ts
+++ b/src/js/stendhal/ui/ViewPort.ts
@@ -32,6 +32,7 @@ import { TextBubble } from "../sprite/TextBubble";
import { Point } from "../util/Point";
import { Canvas, RenderingContext2D } from "util/Types";
+import { Debug } from "../util/Debug";
/**
@@ -150,7 +151,9 @@ export class ViewPort {
this.weatherRenderer.draw(this.ctx);
//this.removeFilter();
- this.applyHSLFilter();
+ if (!Debug.isActive("light")) {
+ this.applyHSLFilter();
+ }
this.drawEntitiesTop();
this.drawEmojiSprites();
this.drawTextSprites();
From 703fa6132940a33a744705013740bb8940012280 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Sun, 28 Dec 2025 18:13:15 +0100
Subject: [PATCH 034/162] cleaned up references to animations
---
.../stendhal/landscape/CombinedTilesetImageLoader.ts | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
index 7fce2d3dc2..552ae74ff5 100644
--- a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
+++ b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
@@ -11,7 +11,6 @@
import { Canvas, RenderingContext2D } from "util/Types";
import { TileMap } from "../data/TileMap";
-import { TileStore } from "../data/TileStore";
import { MapOfSets } from "../util/MapOfSets";
import { CombinedTileset } from "./CombinedTileset";
import { Debug } from "../util/Debug";
@@ -22,8 +21,6 @@ export class CombinedTilesetImageLoader {
private tileUsedAtIndex!: MapOfSets
private tilesetImages: HTMLImageElement[] = [];
- private animations: any = {};
- private landscapeAnimationMap: any;
private drawCanvas: Canvas;
private drawCtx: RenderingContext2D;
private blendCanvas: Canvas;
@@ -35,7 +32,6 @@ export class CombinedTilesetImageLoader {
private map: TileMap,
private indexToCombinedTiles: Map,
private combinedTileset: CombinedTileset) {
- this.landscapeAnimationMap = TileStore.get().getLandscapeMap();
this.drawCanvas = new OffscreenCanvas(this.map.tileWidth, this.map.tileHeight);
this.blendCanvas = new OffscreenCanvas(this.map.tileWidth, this.map.tileHeight);
@@ -78,13 +74,6 @@ export class CombinedTilesetImageLoader {
}
img.src = tsname + "?v=" + stendhal.data.build.version;
- if (this.landscapeAnimationMap) {
- const animation = this.landscapeAnimationMap[tsname];
- if (animation) {
- this.animations[tileset] = animation;
- }
- }
-
this.tilesetImages[tileset] = img;
}
@@ -120,7 +109,6 @@ export class CombinedTilesetImageLoader {
} else {
this.drawCombinedTileWithoutBlend(this.combinedTileset.ctx, tiles, pixelX, pixelY);
}
- document.getElementsByTagName("body")[0].append(this.combinedTileset.canvas as HTMLCanvasElement);
}
private drawCombinedTileWithoutBlend(ctx: RenderingContext2D, tiles: number[], pixelX: number, pixelY: number) {
From face9f3a675ec6192163672468edb00419bebb69 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Sun, 28 Dec 2025 18:59:53 +0100
Subject: [PATCH 035/162] use OffscreenCanvas and ImageBitmap instead of img-
and canvas-elements
---
src/js/stendhal/landscape/CombinedTileset.ts | 7 ++--
.../landscape/CombinedTilesetImageLoader.ts | 35 +++++++++++++------
2 files changed, 27 insertions(+), 15 deletions(-)
diff --git a/src/js/stendhal/landscape/CombinedTileset.ts b/src/js/stendhal/landscape/CombinedTileset.ts
index 9e4f45e564..43a4b24e9b 100644
--- a/src/js/stendhal/landscape/CombinedTileset.ts
+++ b/src/js/stendhal/landscape/CombinedTileset.ts
@@ -32,11 +32,10 @@ export class CombinedTileset {
// The original approach used an a very wide image of 32 pixel height.
// But both Firefox and Chrome are limit the dimension of an image to 2^15 pixels.
this.tilesPerRow = Math.ceil(Math.sqrt(numberOfTiles));
- this.canvas = document.createElement("canvas");
- this.canvas.width = stendhal.data.map.tileWidth * this.tilesPerRow;
- this.canvas.height = stendhal.data.map.tileHeight * this.tilesPerRow;
+ this.canvas = new OffscreenCanvas(
+ stendhal.data.map.tileWidth * this.tilesPerRow,
+ stendhal.data.map.tileHeight * this.tilesPerRow);
this.ctx = this.canvas.getContext("2d")!;
}
-
}
diff --git a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
index 552ae74ff5..dc0f024bd4 100644
--- a/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
+++ b/src/js/stendhal/landscape/CombinedTilesetImageLoader.ts
@@ -20,13 +20,15 @@ declare var stendhal: any;
export class CombinedTilesetImageLoader {
private tileUsedAtIndex!: MapOfSets
- private tilesetImages: HTMLImageElement[] = [];
+ private tilesetImages: ImageBitmap[] = [];
private drawCanvas: Canvas;
private drawCtx: RenderingContext2D;
private blendCanvas: Canvas;
private blendCtx: RenderingContext2D;
private blendFixCanvas: Canvas;
private blendFixCtx: RenderingContext2D;
+ private loadedTilesetCount = 0;
+ private usedTilesetCount = 0;
constructor(
private map: TileMap,
@@ -62,19 +64,30 @@ export class CombinedTilesetImageLoader {
}
- private loadTileset(tileset: number) {
- let img = document.createElement("img");
- img.onload = () => {
- this.drawTileset(tileset);
- }
-
+ private async loadTileset(tileset: number) {
const tsname = this.map.tilesetFilenames[tileset];
if (!tsname) {
return;
}
- img.src = tsname + "?v=" + stendhal.data.build.version;
+ let url = tsname + "?v=" + stendhal.data.build.version;
+ let response = await fetch(url);
+ if (!response.ok) {
+ return;
+ }
+ let blob = await response.blob();
+ let bitmap = await createImageBitmap(blob);
+ this.tilesetImages[tileset] = bitmap;
+ this.loadedTilesetCount++;
+ this.drawTileset(tileset);
+ if (this.loadedTilesetCount >= this.usedTilesetCount) {
+ this.finish();
+ }
+ }
- this.tilesetImages[tileset] = img;
+ private finish() {
+ for (let bitmap of this.tilesetImages) {
+ bitmap.close();
+ }
}
@@ -172,7 +185,7 @@ export class CombinedTilesetImageLoader {
this.drawImageTile(ctx, pixelX, pixelY, image, tileIndexInTileset, flip);
}
- private drawImageTile(ctx: RenderingContext2D, pixelX: number, pixelY: number, tilesetImage: HTMLImageElement, tileIndexInTileset: number, flip: number) {
+ private drawImageTile(ctx: RenderingContext2D, pixelX: number, pixelY: number, tilesetImage: ImageBitmap, tileIndexInTileset: number, flip: number) {
const tilesetWidth = tilesetImage.width;
const tilesPerRow = Math.floor(tilesetWidth / this.map.tileWidth);
@@ -230,9 +243,9 @@ export class CombinedTilesetImageLoader {
console.log("CombinedTilesetImageLoader.load()");
this.calculateTileUsedAtIndex();
let usedTilesets = this.calculateUsedTilesets(this.tileUsedAtIndex.keys());
+ this.usedTilesetCount = usedTilesets.size;
for (let tileset of usedTilesets) {
this.loadTileset(tileset);
}
-
}
}
From 7546fbba80164f75f0bdc1b4d05244e0520a8db6 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Tue, 30 Dec 2025 00:24:38 +0100
Subject: [PATCH 036/162] removed unused library
---
buildtools/ant_modules/dist.xml | 2 +-
src/js/jsxgraph-util.js | 1169 -------------------------------
2 files changed, 1 insertion(+), 1170 deletions(-)
delete mode 100644 src/js/jsxgraph-util.js
diff --git a/buildtools/ant_modules/dist.xml b/buildtools/ant_modules/dist.xml
index cf2414630a..ac9cfc3542 100644
--- a/buildtools/ant_modules/dist.xml
+++ b/buildtools/ant_modules/dist.xml
@@ -722,7 +722,7 @@
-
+
diff --git a/src/js/jsxgraph-util.js b/src/js/jsxgraph-util.js
deleted file mode 100644
index c2ab4938ab..0000000000
--- a/src/js/jsxgraph-util.js
+++ /dev/null
@@ -1,1169 +0,0 @@
-/*
- From: http://jsxgraph.uni-bayreuth.de/wp/tag/gunzip/
- From: https://jsxgraph.org/wp/
-
- Copyright 2008,2009
- Matthias Ehmann,
- Michael Gerhaeuser,
- Carsten Miller,
- Bianca Valentin,
- Alfred Wassermann,
- Peter Wilfahrt
-
- This file is part of JSXGraph.
-
- JSXGraph is free software: you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- JSXGraph is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with JSXGraph. If not, see .
-*/
-
-/**
- * @fileoverview Utilities for uncompressing and base64 decoding
- */
-
-/**
- * @class Util class
- * Class for gunzipping, unzipping and base64 decoding of files.
- * It is used for reading GEONExT, Geogebra and Intergeo files.
- *
- * Only Huffman codes are decoded in gunzip.
- * The code is based on the source code for gunzip.c by Pasi Ojala
- * @see http://www.cs.tut.fi/~albert/Dev/gunzip/gunzip.c
- * @see http://www.cs.tut.fi/~albert
- */
-JXG = {};
-JXG.Util = {};
-
-/**
- * Unzip zip files
- */
-JXG.Util.Unzip = function (barray){
- var outputArr = [],
- output = "",
- debug = false,
- gpflags,
- files = 0,
- unzipped = [],
- crc,
- buf32k = new Array(32768),
- bIdx = 0,
- modeZIP=false,
-
- CRC, SIZE,
-
- bitReverse = [
- 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
- 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
- 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
- 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
- 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
- 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
- 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
- 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
- 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
- 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
- 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
- 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
- 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
- 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
- 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
- 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
- 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
- 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
- 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
- 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
- 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
- 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
- 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
- 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
- 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
- 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
- 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
- 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
- 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
- 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
- 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
- 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
- ],
-
- cplens = [
- 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
- 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
- ],
-
- cplext = [
- 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
- 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99
- ], /* 99==invalid */
-
- cpdist = [
- 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0007, 0x0009, 0x000d,
- 0x0011, 0x0019, 0x0021, 0x0031, 0x0041, 0x0061, 0x0081, 0x00c1,
- 0x0101, 0x0181, 0x0201, 0x0301, 0x0401, 0x0601, 0x0801, 0x0c01,
- 0x1001, 0x1801, 0x2001, 0x3001, 0x4001, 0x6001
- ],
-
- cpdext = [
- 0, 0, 0, 0, 1, 1, 2, 2,
- 3, 3, 4, 4, 5, 5, 6, 6,
- 7, 7, 8, 8, 9, 9, 10, 10,
- 11, 11, 12, 12, 13, 13
- ],
-
- border = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15],
-
- bA = barray,
-
- bytepos=0,
- bitpos=0,
- bb = 1,
- bits=0,
-
- NAMEMAX = 256,
-
- nameBuf = [],
-
- fileout;
-
- function readByte(){
- bits+=8;
- if (bytepos");
- return bA[bytepos++];
- } else
- return -1;
- };
-
- function byteAlign(){
- bb = 1;
- };
-
- function readBit(){
- var carry;
- bits++;
- carry = (bb & 1);
- bb >>= 1;
- if (bb==0){
- bb = readByte();
- carry = (bb & 1);
- bb = (bb>>1) | 0x80;
- }
- return carry;
- };
-
- function readBits(a) {
- var res = 0,
- i = a;
-
- while(i--) {
- res = (res<<1) | readBit();
- }
- if(a) {
- res = bitReverse[res]>>(8-a);
- }
- return res;
- };
-
- function flushBuffer(){
- //document.write('FLUSHBUFFER:'+buf32k);
- bIdx = 0;
- };
- function addBuffer(a){
- SIZE++;
- //CRC=updcrc(a,crc);
- buf32k[bIdx++] = a;
- outputArr.push(String.fromCharCode(a));
- //output+=String.fromCharCode(a);
- if(bIdx==0x8000){
- //document.write('ADDBUFFER:'+buf32k);
- bIdx=0;
- }
- };
-
- function HufNode() {
- this.b0=0;
- this.b1=0;
- this.jump = null;
- this.jumppos = -1;
- };
-
- var LITERALS = 288;
-
- var literalTree = new Array(LITERALS);
- var distanceTree = new Array(32);
- var treepos=0;
- var Places = null;
- var Places2 = null;
-
- var impDistanceTree = new Array(64);
- var impLengthTree = new Array(64);
-
- var len = 0;
- var fpos = new Array(17);
- fpos[0]=0;
- var flens;
- var fmax;
-
- function IsPat() {
- while (1) {
- if (fpos[len] >= fmax)
- return -1;
- if (flens[fpos[len]] == len)
- return fpos[len]++;
- fpos[len]++;
- }
- };
-
- function Rec() {
- var curplace = Places[treepos];
- var tmp;
- if (debug)
- document.write("
len:"+len+" treepos:"+treepos);
- if(len==17) { //war 17
- return -1;
- }
- treepos++;
- len++;
-
- tmp = IsPat();
- if (debug)
- document.write("
IsPat "+tmp);
- if(tmp >= 0) {
- curplace.b0 = tmp; /* leaf cell for 0-bit */
- if (debug)
- document.write("
b0 "+curplace.b0);
- } else {
- /* Not a Leaf cell */
- curplace.b0 = 0x8000;
- if (debug)
- document.write("
b0 "+curplace.b0);
- if(Rec())
- return -1;
- }
- tmp = IsPat();
- if(tmp >= 0) {
- curplace.b1 = tmp; /* leaf cell for 1-bit */
- if (debug)
- document.write("
b1 "+curplace.b1);
- curplace.jump = null; /* Just for the display routine */
- } else {
- /* Not a Leaf cell */
- curplace.b1 = 0x8000;
- if (debug)
- document.write("
b1 "+curplace.b1);
- curplace.jump = Places[treepos];
- curplace.jumppos = treepos;
- if(Rec())
- return -1;
- }
- len--;
- return 0;
- };
-
- function CreateTree(currentTree, numval, lengths, show) {
- var i;
- /* Create the Huffman decode tree/table */
- //document.write("
createtree
");
- if (debug)
- document.write("currentTree "+currentTree+" numval "+numval+" lengths "+lengths+" show "+show);
- Places = currentTree;
- treepos=0;
- flens = lengths;
- fmax = numval;
- for (i=0;i<17;i++)
- fpos[i] = 0;
- len = 0;
- if(Rec()) {
- //fprintf(stderr, "invalid huffman tree\n");
- if (debug)
- alert("invalid huffman tree\n");
- return -1;
- }
- if (debug){
- document.write('
Tree: '+Places.length);
- for (var a=0;a<32;a++){
- document.write("Places["+a+"].b0="+Places[a].b0+"
");
- document.write("Places["+a+"].b1="+Places[a].b1+"
");
- }
- }
-
- /*if(show) {
- var tmp;
- for(tmp=currentTree;tmpjump?tmp->jump-currentTree:0,(tmp->jump?tmp->jump-currentTree:0)*6+0xcf0);
- if(!(tmp.b0 & 0x8000)) {
- //fprintf(stdout, " 0x%03x (%c)", tmp->b0,(tmp->b0<256 && isprint(tmp->b0))?tmp->b0:'�');
- }
- if(!(tmp.b1 & 0x8000)) {
- if((tmp.b0 & 0x8000))
- fprintf(stdout, " ");
- fprintf(stdout, " 0x%03x (%c)", tmp->b1,(tmp->b1<256 && isprint(tmp->b1))?tmp->b1:'�');
- }
- fprintf(stdout, "\n");
- }
- }*/
- return 0;
- };
-
- function DecodeValue(currentTree) {
- var len, i,
- xtreepos=0,
- X = currentTree[xtreepos],
- b;
-
- /* decode one symbol of the data */
- while(1) {
- b=readBit();
- if (debug)
- document.write("b="+b);
- if(b) {
- if(!(X.b1 & 0x8000)){
- if (debug)
- document.write("ret1");
- return X.b1; /* If leaf node, return data */
- }
- X = X.jump;
- len = currentTree.length;
- for (i=0;i>1);
- if(j > 23) {
- j = (j<<1) | readBit(); /* 48..255 */
-
- if(j > 199) { /* 200..255 */
- j -= 128; /* 72..127 */
- j = (j<<1) | readBit(); /* 144..255 << */
- } else { /* 48..199 */
- j -= 48; /* 0..151 */
- if(j > 143) {
- j = j+136; /* 280..287 << */
- /* 0..143 << */
- }
- }
- } else { /* 0..23 */
- j += 256; /* 256..279 << */
- }
- if(j < 256) {
- addBuffer(j);
- //document.write("out:"+String.fromCharCode(j));
- /*fprintf(errfp, "@%d %02x\n", SIZE, j);*/
- } else if(j == 256) {
- /* EOF */
- break;
- } else {
- var len, dist;
-
- j -= 256 + 1; /* bytes + EOF */
- len = readBits(cplext[j]) + cplens[j];
-
- j = bitReverse[readBits(5)]>>3;
- if(cpdext[j] > 8) {
- dist = readBits(8);
- dist |= (readBits(cpdext[j]-8)<<8);
- } else {
- dist = readBits(cpdext[j]);
- }
- dist += cpdist[j];
-
- /*fprintf(errfp, "@%d (l%02x,d%04x)\n", SIZE, len, dist);*/
- for(j=0;jparam: "+literalCodes+" "+distCodes+" "+lenCodes+"
");
- for(j=0; j<19; j++) {
- ll[j] = 0;
- }
-
- // Get the decode tree code lengths
-
- //document.write("
");
- for(j=0; jll:'+ll);
- len = distanceTree.length;
- for (i=0; idistanceTree");
- for(var a=0;a"+distanceTree[a].b0+" "+distanceTree[a].b1+" "+distanceTree[a].jump+" "+distanceTree[a].jumppos);
- /*if (distanceTree[a].jumppos!=-1)
- document.write(" "+distanceTree[a].jump.b0+" "+distanceTree[a].jump.b1);
- */
- }
- }
- //document.write('
tree created');
-
- //read in literal and distance code lengths
- n = literalCodes + distCodes;
- i = 0;
- var z=-1;
- if (debug)
- document.write("
n="+n+" bits: "+bits+"
");
- while(i < n) {
- z++;
- j = DecodeValue(distanceTree);
- if (debug)
- document.write("
"+z+" i:"+i+" decode: "+j+" bits "+bits+"
");
- if(j<16) { // length of code in bits (0..15)
- ll[i++] = j;
- } else if(j==16) { // repeat last length 3 to 6 times
- var l;
- j = 3 + readBits(2);
- if(i+j > n) {
- flushBuffer();
- return 1;
- }
- l = i ? ll[i-1] : 0;
- while(j--) {
- ll[i++] = l;
- }
- } else {
- if(j==17) { // 3 to 10 zero length codes
- j = 3 + readBits(3);
- } else { // j == 18: 11 to 138 zero length codes
- j = 11 + readBits(7);
- }
- if(i+j > n) {
- flushBuffer();
- return 1;
- }
- while(j--) {
- ll[i++] = 0;
- }
- }
- }
- /*for(j=0; jliteralTree");
- while(1) {
- j = DecodeValue(literalTree);
- if(j >= 256) { // In C64: if carry set
- var len, dist;
- j -= 256;
- if(j == 0) {
- // EOF
- break;
- }
- j--;
- len = readBits(cplext[j]) + cplens[j];
-
- j = DecodeValue(distanceTree);
- if(cpdext[j] > 8) {
- dist = readBits(8);
- dist |= (readBits(cpdext[j]-8)<<8);
- } else {
- dist = readBits(cpdext[j]);
- }
- dist += cpdist[j];
- while(len--) {
- var c = buf32k[(bIdx - dist) & 0x7fff];
- addBuffer(c);
- }
- } else {
- addBuffer(j);
- }
- }
- }
- } while(!last);
- flushBuffer();
-
- byteAlign();
- return 0;
- };
-
- JXG.Util.Unzip.prototype.unzipFile = function(name) {
- var i;
- this.unzip();
- //alert(unzipped[0][1]);
- for (i=0;i");
- }
- */
- //alert(bA);
- nextFile();
- return unzipped;
- };
-
- function nextFile(){
- if (debug)
- alert("NEXTFILE");
- outputArr = [];
- var tmp = [];
- modeZIP = false;
- tmp[0] = readByte();
- tmp[1] = readByte();
- if (debug)
- alert("type: "+tmp[0]+" "+tmp[1]);
- if (tmp[0] == parseInt("78",16) && tmp[1] == parseInt("da",16)){ //GZIP
- if (debug)
- alert("GEONExT-GZIP");
- DeflateLoop();
- if (debug)
- alert(outputArr.join(''));
- unzipped[files] = new Array(2);
- unzipped[files][0] = outputArr.join('');
- unzipped[files][1] = "geonext.gxt";
- files++;
- }
- if (tmp[0] == parseInt("1f",16) && tmp[1] == parseInt("8b",16)){ //GZIP
- if (debug)
- alert("GZIP");
- //DeflateLoop();
- skipdir();
- if (debug)
- alert(outputArr.join(''));
- unzipped[files] = new Array(2);
- unzipped[files][0] = outputArr.join('');
- unzipped[files][1] = "file";
- files++;
- }
- if (tmp[0] == parseInt("50",16) && tmp[1] == parseInt("4b",16)){ //ZIP
- modeZIP = true;
- tmp[2] = readByte();
- tmp[3] = readByte();
- if (tmp[2] == parseInt("3",16) && tmp[3] == parseInt("4",16)){
- //MODE_ZIP
- tmp[0] = readByte();
- tmp[1] = readByte();
- if (debug)
- alert("ZIP-Version: "+tmp[1]+" "+tmp[0]/10+"."+tmp[0]%10);
-
- gpflags = readByte();
- gpflags |= (readByte()<<8);
- if (debug)
- alert("gpflags: "+gpflags);
-
- var method = readByte();
- method |= (readByte()<<8);
- if (debug)
- alert("method: "+method);
-
- readByte();
- readByte();
- readByte();
- readByte();
-
- var crc = readByte();
- crc |= (readByte()<<8);
- crc |= (readByte()<<16);
- crc |= (readByte()<<24);
-
- var compSize = readByte();
- compSize |= (readByte()<<8);
- compSize |= (readByte()<<16);
- compSize |= (readByte()<<24);
-
- var size = readByte();
- size |= (readByte()<<8);
- size |= (readByte()<<16);
- size |= (readByte()<<24);
-
- if (debug)
- alert("local CRC: "+crc+"\nlocal Size: "+size+"\nlocal CompSize: "+compSize);
-
- var filelen = readByte();
- filelen |= (readByte()<<8);
-
- var extralen = readByte();
- extralen |= (readByte()<<8);
-
- if (debug)
- alert("filelen "+filelen);
- i = 0;
- nameBuf = [];
- while (filelen--){
- var c = readByte();
- if (c == "/" | c ==":"){
- i = 0;
- } else if (i < NAMEMAX-1)
- nameBuf[i++] = String.fromCharCode(c);
- }
- if (debug)
- alert("nameBuf: "+nameBuf);
-
- //nameBuf[i] = "\0";
- if (!fileout)
- fileout = nameBuf;
-
- var i = 0;
- while (i < extralen){
- c = readByte();
- i++;
- }
-
- CRC = 0xffffffff;
- SIZE = 0;
-
- if (size = 0 && fileOut.charAt(fileout.length-1)=="/"){
- //skipdir
- if (debug)
- alert("skipdir");
- }
- if (method == 8){
- DeflateLoop();
- if (debug)
- alert(outputArr.join(''));
- unzipped[files] = new Array(2);
- unzipped[files][0] = outputArr.join('');
- unzipped[files][1] = nameBuf.join('');
- files++;
- //return outputArr.join('');
- }
- skipdir();
- }
- }
- };
-
- function skipdir(){
- var crc,
- tmp = [],
- compSize, size, os, i, c;
-
- if ((gpflags & 8)) {
- tmp[0] = readByte();
- tmp[1] = readByte();
- tmp[2] = readByte();
- tmp[3] = readByte();
-
- if (tmp[0] == parseInt("50",16) &&
- tmp[1] == parseInt("4b",16) &&
- tmp[2] == parseInt("07",16) &&
- tmp[3] == parseInt("08",16))
- {
- crc = readByte();
- crc |= (readByte()<<8);
- crc |= (readByte()<<16);
- crc |= (readByte()<<24);
- } else {
- crc = tmp[0] | (tmp[1]<<8) | (tmp[2]<<16) | (tmp[3]<<24);
- }
-
- compSize = readByte();
- compSize |= (readByte()<<8);
- compSize |= (readByte()<<16);
- compSize |= (readByte()<<24);
-
- size = readByte();
- size |= (readByte()<<8);
- size |= (readByte()<<16);
- size |= (readByte()<<24);
-
- if (debug)
- alert("CRC:");
- }
-
- if (modeZIP)
- nextFile();
-
- tmp[0] = readByte();
- if (tmp[0] != 8) {
- if (debug)
- alert("Unknown compression method!");
- return 0;
- }
-
- gpflags = readByte();
- if (debug){
- if ((gpflags & ~(parseInt("1f",16))))
- alert("Unknown flags set!");
- }
-
- readByte();
- readByte();
- readByte();
- readByte();
-
- readByte();
- os = readByte();
-
- if ((gpflags & 4)){
- tmp[0] = readByte();
- tmp[2] = readByte();
- len = tmp[0] + 256*tmp[1];
- if (debug)
- alert("Extra field size: "+len);
- for (i=0;ihttp://www.webtoolkit.info/
-*/
-JXG.Util.Base64 = {
-
- // private property
- _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
-
- // public method for encoding
- encode : function (input) {
- var output = [],
- chr1, chr2, chr3, enc1, enc2, enc3, enc4,
- i = 0;
-
- input = JXG.Util.Base64._utf8_encode(input);
-
- while (i < input.length) {
-
- chr1 = input.charCodeAt(i++);
- chr2 = input.charCodeAt(i++);
- chr3 = input.charCodeAt(i++);
-
- enc1 = chr1 >> 2;
- enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
- enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
- enc4 = chr3 & 63;
-
- if (isNaN(chr2)) {
- enc3 = enc4 = 64;
- } else if (isNaN(chr3)) {
- enc4 = 64;
- }
-
- output.push([this._keyStr.charAt(enc1),
- this._keyStr.charAt(enc2),
- this._keyStr.charAt(enc3),
- this._keyStr.charAt(enc4)].join(''));
- }
-
- return output.join('');
- },
-
- // public method for decoding
- decode : function (input, utf8) {
- var output = [],
- chr1, chr2, chr3,
- enc1, enc2, enc3, enc4,
- i = 0;
-
- input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
-
- while (i < input.length) {
-
- enc1 = this._keyStr.indexOf(input.charAt(i++));
- enc2 = this._keyStr.indexOf(input.charAt(i++));
- enc3 = this._keyStr.indexOf(input.charAt(i++));
- enc4 = this._keyStr.indexOf(input.charAt(i++));
-
- chr1 = (enc1 << 2) | (enc2 >> 4);
- chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
- chr3 = ((enc3 & 3) << 6) | enc4;
-
- output.push(String.fromCharCode(chr1));
-
- if (enc3 != 64) {
- output.push(String.fromCharCode(chr2));
- }
- if (enc4 != 64) {
- output.push(String.fromCharCode(chr3));
- }
- }
-
- output = output.join('');
-
- if (utf8) {
- output = JXG.Util.Base64._utf8_decode(output);
- }
- return output;
-
- },
-
- // private method for UTF-8 encoding
- _utf8_encode : function (string) {
- string = string.replace(/\r\n/g,"\n");
- var utftext = "";
-
- for (var n = 0; n < string.length; n++) {
-
- var c = string.charCodeAt(n);
-
- if (c < 128) {
- utftext += String.fromCharCode(c);
- }
- else if((c > 127) && (c < 2048)) {
- utftext += String.fromCharCode((c >> 6) | 192);
- utftext += String.fromCharCode((c & 63) | 128);
- }
- else {
- utftext += String.fromCharCode((c >> 12) | 224);
- utftext += String.fromCharCode(((c >> 6) & 63) | 128);
- utftext += String.fromCharCode((c & 63) | 128);
- }
-
- }
-
- return utftext;
- },
-
- // private method for UTF-8 decoding
- _utf8_decode : function (utftext) {
- var string = [],
- i = 0,
- c = 0, c2 = 0, c3 = 0;
-
- while ( i < utftext.length ) {
- c = utftext.charCodeAt(i);
- if (c < 128) {
- string.push(String.fromCharCode(c));
- i++;
- }
- else if((c > 191) && (c < 224)) {
- c2 = utftext.charCodeAt(i+1);
- string.push(String.fromCharCode(((c & 31) << 6) | (c2 & 63)));
- i += 2;
- }
- else {
- c2 = utftext.charCodeAt(i+1);
- c3 = utftext.charCodeAt(i+2);
- string.push(String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)));
- i += 3;
- }
- }
- return string.join('');
- },
-
- _destrip: function (stripped, wrap){
- var lines = [], lineno, i,
- destripped = [];
-
- if (wrap==null)
- wrap = 76;
-
- stripped.replace(/ /g, "");
- lineno = stripped.length / wrap;
- for (i = 0; i < lineno; i++)
- lines[i]=stripped.substr(i * wrap, wrap);
- if (lineno != stripped.length / wrap)
- lines[lines.length]=stripped.substr(lineno * wrap, stripped.length-(lineno * wrap));
-
- for (i = 0; i < lines.length; i++)
- destripped.push(lines[i]);
- return destripped.join('\n');
- },
-
- decodeAsArray: function (input){
- var dec = this.decode(input),
- ar = [], i;
- for (i=0;i255){
- switch (c) {
- case 8364: c=128;
- break;
- case 8218: c=130;
- break;
- case 402: c=131;
- break;
- case 8222: c=132;
- break;
- case 8230: c=133;
- break;
- case 8224: c=134;
- break;
- case 8225: c=135;
- break;
- case 710: c=136;
- break;
- case 8240: c=137;
- break;
- case 352: c=138;
- break;
- case 8249: c=139;
- break;
- case 338: c=140;
- break;
- case 381: c=142;
- break;
- case 8216: c=145;
- break;
- case 8217: c=146;
- break;
- case 8220: c=147;
- break;
- case 8221: c=148;
- break;
- case 8226: c=149;
- break;
- case 8211: c=150;
- break;
- case 8212: c=151;
- break;
- case 732: c=152;
- break;
- case 8482: c=153;
- break;
- case 353: c=154;
- break;
- case 8250: c=155;
- break;
- case 339: c=156;
- break;
- case 382: c=158;
- break;
- case 376: c=159;
- break;
- default:
- break;
- }
- }
- return c;
-};
-
-/**
- * Decoding string into utf-8
- * @param {String} string to decode
- * @return {String} utf8 decoded string
- */
-JXG.Util.utf8Decode = function(utftext) {
- var string = [];
- var i = 0;
- var c = 0, c1 = 0, c2 = 0;
-
- while ( i < utftext.length ) {
- c = utftext.charCodeAt(i);
-
- if (c < 128) {
- string.push(String.fromCharCode(c));
- i++;
- } else if((c > 191) && (c < 224)) {
- c2 = utftext.charCodeAt(i+1);
- string.push(String.fromCharCode(((c & 31) << 6) | (c2 & 63)));
- i += 2;
- } else {
- c2 = utftext.charCodeAt(i+1);
- c3 = utftext.charCodeAt(i+2);
- string.push(String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)));
- i += 3;
- }
- };
- return string.join('');
-};
From cbf971e632f66f4a0ce81e0196ceee4f9da6032b Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Fri, 2 Jan 2026 01:37:39 +0100
Subject: [PATCH 037/162] replace stendhal.paths with Paths to ensure correct
init order
---
src/js/stendhal/action/DebugAction.ts | 3 +-
src/js/stendhal/data/Outfit.ts | 3 +-
src/js/stendhal/data/TileStore.ts | 5 +--
src/js/stendhal/data/sound/SoundManager.ts | 11 +++---
src/js/stendhal/entity/Blood.ts | 7 ++--
src/js/stendhal/entity/Chest.ts | 7 ++--
src/js/stendhal/entity/Corpse.ts | 13 +++----
src/js/stendhal/entity/Creature.ts | 3 +-
src/js/stendhal/entity/DomesticAnimal.ts | 5 +--
src/js/stendhal/entity/Door.ts | 5 +--
src/js/stendhal/entity/Entity.ts | 3 +-
src/js/stendhal/entity/Food.ts | 5 +--
src/js/stendhal/entity/GameBoard.ts | 5 +--
src/js/stendhal/entity/Gate.ts | 5 +--
src/js/stendhal/entity/Ground.ts | 8 +++--
.../stendhal/entity/GrowingEntitySpawner.ts | 5 +--
src/js/stendhal/entity/InvisibleEntity.ts | 3 +-
src/js/stendhal/entity/Item.ts | 5 +--
src/js/stendhal/entity/NPC.ts | 3 +-
src/js/stendhal/entity/Player.ts | 5 +--
src/js/stendhal/entity/Portal.ts | 3 +-
src/js/stendhal/entity/RPEntity.ts | 35 ++++++++++---------
src/js/stendhal/entity/Sign.ts | 5 +--
src/js/stendhal/entity/TrainingDummy.ts | 3 +-
src/js/stendhal/entity/UseableEntity.ts | 3 +-
src/js/stendhal/entity/VisibleEntity.ts | 5 +--
src/js/stendhal/entity/WalkBlocker.ts | 3 +-
src/js/stendhal/event/ShowItemListEvent.ts | 3 +-
src/js/stendhal/sprite/AchievementBanner.ts | 5 +--
src/js/stendhal/sprite/NotificationBubble.ts | 3 +-
.../sprite/action/RangedAttackSprite.ts | 3 +-
.../ui/component/BuddyListComponent.ts | 5 +--
.../ui/component/ChatInputComponent.ts | 3 +-
.../component/ItemContainerImplementation.ts | 9 ++---
.../ui/component/StatusesListComponent.ts | 5 +--
src/js/stendhal/ui/dialog/EmojiMapDialog.ts | 3 +-
src/js/stendhal/ui/dialog/TravelLogDialog.ts | 3 +-
.../ui/dialog/outfit/OutfitPartSelector.ts | 3 +-
src/js/stendhal/ui/joystick/JoystickImpl.ts | 3 +-
src/js/stendhal/util/ConfigManager.ts | 5 +--
src/js/stendhal/util/Nature.ts | 4 +--
src/js/stendhal/util/SessionManager.ts | 4 ++-
src/js/stendhal/util/WeatherRenderer.ts | 3 +-
43 files changed, 134 insertions(+), 96 deletions(-)
diff --git a/src/js/stendhal/action/DebugAction.ts b/src/js/stendhal/action/DebugAction.ts
index cbb0715d3a..648664b972 100644
--- a/src/js/stendhal/action/DebugAction.ts
+++ b/src/js/stendhal/action/DebugAction.ts
@@ -21,6 +21,7 @@ import { Panel } from "ui/toolkit/Panel";
import { Chat } from "../util/Chat";
import { Debug } from "../util/Debug";
+import { Paths } from "../data/Paths";
declare var marauroa: any;
declare var stendhal: any;
@@ -138,7 +139,7 @@ export class DebugAction extends SlashAction {
if (weather) {
weather = weather.replace(/ /g, "_");
- const wfilename = stendhal.paths.weather + "/" + weather + ".png";
+ const wfilename = Paths.weather + "/" + weather + ".png";
if (!stendhal.data.sprites.getCached(wfilename)) {
Chat.logH("error", "unknown weather: " + wfilename);
return;
diff --git a/src/js/stendhal/data/Outfit.ts b/src/js/stendhal/data/Outfit.ts
index 41aad65b05..15010fd80a 100644
--- a/src/js/stendhal/data/Outfit.ts
+++ b/src/js/stendhal/data/Outfit.ts
@@ -13,6 +13,7 @@
import { DrawingStage } from "../util/DrawingStage";
import { Pair } from "../util/Pair";
import { StringUtil } from "../util/StringUtil";
+import { Paths } from "./Paths";
declare var stendhal: any;
@@ -235,7 +236,7 @@ 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
diff --git a/src/js/stendhal/data/TileStore.ts b/src/js/stendhal/data/TileStore.ts
index 17e20db3e9..1a3c2f2e73 100644
--- a/src/js/stendhal/data/TileStore.ts
+++ b/src/js/stendhal/data/TileStore.ts
@@ -12,9 +12,6 @@
import { Paths } from "./Paths";
import { JSONLoader } from "../util/JSONLoader";
-declare var stendhal: any;
-
-
type AnimationMap = {
[key: string]: any;
}
@@ -70,7 +67,7 @@ export class TileStore {
this.landscapeMap = this.loadAnimations(
loader.data["landscape"], Paths.tileset + "/");
this.weatherMap = this.loadAnimations(
- loader.data["weather"], stendhal.paths.weather + "/");
+ loader.data["weather"], Paths.weather + "/");
};
loader.load(Paths.tileset + "/animation.json");
}
diff --git a/src/js/stendhal/data/sound/SoundManager.ts b/src/js/stendhal/data/sound/SoundManager.ts
index 27c8194081..743948db63 100644
--- a/src/js/stendhal/data/sound/SoundManager.ts
+++ b/src/js/stendhal/data/sound/SoundManager.ts
@@ -16,9 +16,8 @@ import { SoundFactory } from "./SoundFactory";
import { SoundObject } from "./SoundFactory";
import { SoundLayer } from "./SoundLayer";
-import { singletons } from "../../SingletonRepo";
-
import { ui } from "../../ui/UI";
+import { Paths } from "../Paths";
/**
@@ -244,7 +243,7 @@ export class SoundManager {
let snd = this.cache[soundName] || this.cacheGlobal[soundName];
if (!snd) {
// add new sound to cache
- snd = this.load(soundName, stendhal.paths.sounds + "/" + soundName + ".ogg");
+ snd = this.load(soundName, Paths.sounds + "/" + soundName + ".ogg");
}
if (!this.cache[soundName]) {
@@ -406,7 +405,7 @@ export class SoundManager {
volume=1.0): SoundObject|undefined {
// load into cache so playEffect doesn't look in "data/sounds"
if (!this.cache[musicName]) {
- this.load(musicName, stendhal.paths.music + "/" + musicName + ".ogg");
+ this.load(musicName, Paths.music + "/" + musicName + ".ogg");
}
return this.playLocalizedLoop(x, y, radius, layer, musicName, volume);
}
@@ -424,7 +423,7 @@ export class SoundManager {
playGlobalizedMusic(musicName: string, volume=1.0): SoundObject|undefined {
// load into cache so playEffect doesn't look in "data/sounds"
if (!this.cache[musicName]) {
- this.load(musicName, stendhal.paths.music + "/" + musicName + ".ogg");
+ this.load(musicName, Paths.music + "/" + musicName + ".ogg");
}
return this.playGlobalizedLoop(musicName, "music", volume);
}
@@ -796,6 +795,6 @@ export class SoundManager {
startupCache() {
// login sound
this.load("ui/login",
- stendhal.paths.sounds + "/ui/login.ogg", true);
+ Paths.sounds + "/ui/login.ogg", true);
}
}
diff --git a/src/js/stendhal/entity/Blood.ts b/src/js/stendhal/entity/Blood.ts
index c94abc0ea3..ea30c4df77 100644
--- a/src/js/stendhal/entity/Blood.ts
+++ b/src/js/stendhal/entity/Blood.ts
@@ -11,6 +11,7 @@
import { RenderingContext2D } from "util/Types";
import { Entity } from "./Entity";
+import { Paths } from "../data/Paths";
declare var stendhal: any;
@@ -24,7 +25,7 @@ export class Blood extends Entity {
this.sprite = {
height: 32,
width: 32,
- filename: stendhal.paths.sprites + "/combat/blood_red.png"
+ filename: Paths.sprites + "/combat/blood_red.png"
};
}
@@ -33,12 +34,12 @@ export class Blood extends Entity {
if (key === "amount") {
this.sprite.offsetY = parseInt(value, 10) * 32;
} else if (key === "class") {
- this.sprite.filename = stendhal.paths.sprites + "/combat/blood_" + value + ".png";
+ this.sprite.filename = Paths.sprites + "/combat/blood_" + value + ".png";
}
}
override getCursor(x: number, y: number) {
- return "url(" + stendhal.paths.sprites + "/cursor/walk.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/walk.png) 1 3, auto";
}
override drawSpriteAt(ctx: RenderingContext2D, x: number, y: number) {
diff --git a/src/js/stendhal/entity/Chest.ts b/src/js/stendhal/entity/Chest.ts
index f6a9b7f63b..9a38e51d4e 100644
--- a/src/js/stendhal/entity/Chest.ts
+++ b/src/js/stendhal/entity/Chest.ts
@@ -19,20 +19,21 @@ import { FloatingWindow } from "../ui/toolkit/FloatingWindow";
import { ItemInventoryComponent } from "../ui/component/ItemInventoryComponent";
import { Chat } from "../util/Chat";
+import { Paths } from "../data/Paths";
declare var marauroa: any;
declare var stendhal: any;
let OPEN_SPRITE = {
- filename: stendhal.paths.sprites + "/chest.png",
+ filename: Paths.sprites + "/chest.png",
height: 32,
width: 32,
offsetY: 32
};
let CLOSED_SPRITE = {
- filename: stendhal.paths.sprites + "/chest.png",
+ filename: Paths.sprites + "/chest.png",
height: 32,
width: 32
};
@@ -151,7 +152,7 @@ export class Chest extends PopupInventory {
}
override getCursor(_x: number, _y: number) {
- return "url(" + stendhal.paths.sprites + "/cursor/bag.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/bag.png) 1 3, auto";
}
}
diff --git a/src/js/stendhal/entity/Corpse.ts b/src/js/stendhal/entity/Corpse.ts
index 40cd34db9e..18b16292aa 100644
--- a/src/js/stendhal/entity/Corpse.ts
+++ b/src/js/stendhal/entity/Corpse.ts
@@ -18,6 +18,7 @@ import { FloatingWindow } from "../ui/toolkit/FloatingWindow";
import { Chat } from "../util/Chat";
import { PopupInventory } from "./PopupInventory";
+import { Paths } from "../data/Paths";
declare var marauroa: any;
declare var stendhal: any;
@@ -45,9 +46,9 @@ export class Corpse extends PopupInventory {
const bloodEnabled = stendhal.config.getBoolean("effect.blood");
if (bloodEnabled && (key === "image")) {
- this.sprite.filename = stendhal.paths.sprites + "/corpse/" + value + ".png";
+ this.sprite.filename = Paths.sprites + "/corpse/" + value + ".png";
} else if (!bloodEnabled && (key === "harmless_image")) {
- this.sprite.filename = stendhal.paths.sprites + "/corpse/" + value + ".png";
+ this.sprite.filename = Paths.sprites + "/corpse/" + value + ".png";
}
}
@@ -169,18 +170,18 @@ export class Corpse extends PopupInventory {
override getCursor(_x: number, _y: number) {
if (this.isEmpty()) {
- return "url(" + stendhal.paths.sprites + "/cursor/emptybag.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/emptybag.png) 1 3, auto";
}
// owner
if (!this["corpse_owner"] || (this["corpse_owner"] == marauroa.me["_name"])) {
- return "url(" + stendhal.paths.sprites + "/cursor/bag.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/bag.png) 1 3, auto";
}
if ((stendhal.data.group.lootmode === "shared") && (stendhal.data.group.members[this["corpse_owner"]])) {
- return "url(" + stendhal.paths.sprites + "/cursor/bag.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/bag.png) 1 3, auto";
}
- return "url(" + stendhal.paths.sprites + "/cursor/lockedbag.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/lockedbag.png) 1 3, auto";
}
public override isDraggable(): boolean {
diff --git a/src/js/stendhal/entity/Creature.ts b/src/js/stendhal/entity/Creature.ts
index 7b58ebfbe3..97523a4d46 100644
--- a/src/js/stendhal/entity/Creature.ts
+++ b/src/js/stendhal/entity/Creature.ts
@@ -16,6 +16,7 @@ import { EntityOverlayRegistry } from "../data/EntityOverlayRegistry";
import { Color } from "../data/color/Color";
import { SkillEffect } from "../sprite/action/SkillEffect";
+import { Paths } from "../data/Paths";
declare var marauroa: any;
declare var stendhal: any;
@@ -52,7 +53,7 @@ export class Creature extends RPEntity {
}
override getCursor(_x: number, _y: number) {
- return "url(" + stendhal.paths.sprites + "/cursor/attack.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/attack.png) 1 3, auto";
}
/**
diff --git a/src/js/stendhal/entity/DomesticAnimal.ts b/src/js/stendhal/entity/DomesticAnimal.ts
index 0ffbd015d9..9de58f8730 100644
--- a/src/js/stendhal/entity/DomesticAnimal.ts
+++ b/src/js/stendhal/entity/DomesticAnimal.ts
@@ -14,6 +14,7 @@ import { MenuItem} from "../action/MenuItem";
import { Color } from "../data/color/Color";
import { RenderingContext2D } from "util/Types";
+import { Paths } from "../data/Paths";
declare var marauroa: any;
declare var stendhal: any;
@@ -28,7 +29,7 @@ export class DomesticAnimal extends RPEntity {
if (this["_rpclass"] == "sheep") {
this["largeWeight"] = 60;
}
- this.imagePath = stendhal.paths.sprites + "/" + this["_rpclass"] + ".png";
+ this.imagePath = Paths.sprites + "/" + this["_rpclass"] + ".png";
}
var localX = this["_x"] * 32;
@@ -56,7 +57,7 @@ export class DomesticAnimal extends RPEntity {
}
override getCursor(_x: number, _y: number) {
- return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto";
}
override buildActions(list: MenuItem[]) {
diff --git a/src/js/stendhal/entity/Door.ts b/src/js/stendhal/entity/Door.ts
index 78cf9cdfc3..b7577b5b8f 100644
--- a/src/js/stendhal/entity/Door.ts
+++ b/src/js/stendhal/entity/Door.ts
@@ -12,6 +12,7 @@
import { RenderingContext2D } from "util/Types";
import { MenuItem } from "../action/MenuItem";
import { Portal } from "./Portal";
+import { Paths } from "../data/Paths";
declare var stendhal: any;
@@ -20,7 +21,7 @@ export class Door extends Portal {
override zIndex = 5000;
override draw(ctx: RenderingContext2D) {
- let imagePath = stendhal.paths.sprites + "/doors/" + this["class"] + ".png";
+ let imagePath = Paths.sprites + "/doors/" + this["class"] + ".png";
let image = stendhal.data.sprites.get(imagePath);
if (image.height) {
let height = image.height / 2;
@@ -51,7 +52,7 @@ export class Door extends Portal {
}
override getCursor(_x: number, _y: number) {
- return "url(" + stendhal.paths.sprites + "/cursor/portal.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/portal.png) 1 3, auto";
}
}
diff --git a/src/js/stendhal/entity/Entity.ts b/src/js/stendhal/entity/Entity.ts
index 659a191146..9f02254fd4 100644
--- a/src/js/stendhal/entity/Entity.ts
+++ b/src/js/stendhal/entity/Entity.ts
@@ -13,6 +13,7 @@ import { RenderingContext2D } from "util/Types";
import { MenuItem } from "../action/MenuItem";
import { Chat } from "../util/Chat";
import { RPObject } from "./RPObject";
+import { Paths } from "../data/Paths";
declare var marauroa: any;
declare var stendhal: any;
@@ -275,7 +276,7 @@ export class Entity extends RPObject {
cursor = this["cursor"];
}
- return "url(" + stendhal.paths.sprites + "/cursor/" + cursor.toLowerCase().replace("_", "") + ".png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/" + cursor.toLowerCase().replace("_", "") + ".png) 1 3, auto";
}
/**
diff --git a/src/js/stendhal/entity/Food.ts b/src/js/stendhal/entity/Food.ts
index 9927d1a5e2..5e862a5acd 100644
--- a/src/js/stendhal/entity/Food.ts
+++ b/src/js/stendhal/entity/Food.ts
@@ -11,6 +11,7 @@
import { RenderingContext2D } from "util/Types";
import { Entity } from "./Entity";
+import { Paths } from "../data/Paths";
declare var marauroa: any;
declare var stendhal: any;
@@ -29,7 +30,7 @@ export class Food extends Entity {
}
override draw(ctx: RenderingContext2D) {
- var image = stendhal.data.sprites.get(stendhal.paths.sprites + "/food.png");
+ var image = stendhal.data.sprites.get(Paths.sprites + "/food.png");
if (image.height) {
var localX = this["x"] * 32;
var localY = this["y"] * 32;
@@ -51,7 +52,7 @@ export class Food extends Entity {
}
override getCursor(_x: number, _y: number) {
- return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto";
}
}
diff --git a/src/js/stendhal/entity/GameBoard.ts b/src/js/stendhal/entity/GameBoard.ts
index 412e11fbbb..915cad8eac 100644
--- a/src/js/stendhal/entity/GameBoard.ts
+++ b/src/js/stendhal/entity/GameBoard.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { Paths } from "../data/Paths";
import { Entity } from "./Entity";
declare var stendhal: any;
@@ -29,7 +30,7 @@ export class GameBoard extends Entity {
override set(key: string, value: any) {
super.set(key, value);
if (key === "class") {
- this.sprite.filename = stendhal.paths.sprites + "/gameboard/"
+ this.sprite.filename = Paths.sprites + "/gameboard/"
+ this["class"] + ".png";
}
}
@@ -39,7 +40,7 @@ export class GameBoard extends Entity {
}
override getCursor(_x: number, _y: number) {
- return "url(" + stendhal.paths.sprites + "/cursor/walk.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/walk.png) 1 3, auto";
}
}
diff --git a/src/js/stendhal/entity/Gate.ts b/src/js/stendhal/entity/Gate.ts
index c40d22eddc..c01efc68f0 100644
--- a/src/js/stendhal/entity/Gate.ts
+++ b/src/js/stendhal/entity/Gate.ts
@@ -12,6 +12,7 @@
import { RenderingContext2D } from "util/Types";
import { MenuItem } from "../action/MenuItem";
import { Entity } from "./Entity";
+import { Paths } from "../data/Paths";
declare var marauroa: any;
declare var stendhal: any;
@@ -47,7 +48,7 @@ export class Gate extends Entity {
override draw(ctx: RenderingContext2D) {
if (this._image == undefined) {
- var filename = stendhal.paths.sprites + "/doors/" + this["image"] + "_" + this["orientation"] + ".png";
+ var filename = Paths.sprites + "/doors/" + this["image"] + "_" + this["orientation"] + ".png";
this._image = stendhal.data.sprites.get(filename);
}
if (this._image.height) {
@@ -75,7 +76,7 @@ export class Gate extends Entity {
}
override getCursor(_x: number, _y: number) {
- return "url(" + stendhal.paths.sprites + "/cursor/activity.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/activity.png) 1 3, auto";
}
}
diff --git a/src/js/stendhal/entity/Ground.ts b/src/js/stendhal/entity/Ground.ts
index 68c69f360e..a8d9adc7cf 100644
--- a/src/js/stendhal/entity/Ground.ts
+++ b/src/js/stendhal/entity/Ground.ts
@@ -9,6 +9,8 @@
* *
***************************************************************************/
+import { Paths } from "../data/Paths";
+
declare var marauroa: any;
declare var stendhal: any;
@@ -48,14 +50,14 @@ export class Ground {
getCursor(x: number, y: number): string {
if ((x < 15) || (y < 15) || (x > stendhal.data.map.zoneSizeX * 32 - 15) || (y > stendhal.data.map.zoneSizeY * 32 - 15)) {
- return "url(" + stendhal.paths.sprites + "/cursor/walkborder.png) 1 3, auto"
+ return "url(" + Paths.sprites + "/cursor/walkborder.png) 1 3, auto"
}
var worldX = Math.floor(x / 32);
var worldY = Math.floor(y / 32);
if (stendhal.data.map.collision(worldX, worldY)) {
- return "url(" + stendhal.paths.sprites + "/cursor/stop.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/stop.png) 1 3, auto";
}
- return "url(" + stendhal.paths.sprites + "/cursor/walk.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/walk.png) 1 3, auto";
}
onclick(x: number, y: number, dblclick: boolean) {
diff --git a/src/js/stendhal/entity/GrowingEntitySpawner.ts b/src/js/stendhal/entity/GrowingEntitySpawner.ts
index ef78f70751..e05edc09c4 100644
--- a/src/js/stendhal/entity/GrowingEntitySpawner.ts
+++ b/src/js/stendhal/entity/GrowingEntitySpawner.ts
@@ -12,6 +12,7 @@
import { RenderingContext2D } from "util/Types";
import { MenuItem } from "../action/MenuItem";
import { Entity } from "./Entity";
+import { Paths } from "../data/Paths";
declare var marauroa: any;
declare var stendhal: any;
@@ -65,7 +66,7 @@ export class GrowingEntitySpawner extends Entity {
class_name = class_name.replace(" ", "_");
}
- var image = stendhal.data.sprites.get(stendhal.paths.sprites + "/" + class_name + ".png");
+ var image = stendhal.data.sprites.get(Paths.sprites + "/" + class_name + ".png");
if (image.height) { // image.complete is true on missing image files
var count = parseInt(this["max_ripeness"], 10) + 1;
var drawHeight = image.height / count;
@@ -76,7 +77,7 @@ export class GrowingEntitySpawner extends Entity {
}
override getCursor(_x: number, _y: number) {
- return "url(" + stendhal.paths.sprites + "/cursor/harvest.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/harvest.png) 1 3, auto";
}
}
diff --git a/src/js/stendhal/entity/InvisibleEntity.ts b/src/js/stendhal/entity/InvisibleEntity.ts
index 36afdfe64c..2d862a24b7 100644
--- a/src/js/stendhal/entity/InvisibleEntity.ts
+++ b/src/js/stendhal/entity/InvisibleEntity.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { Paths } from "../data/Paths";
import { Entity } from "./Entity";
declare var stendhal: any;
@@ -20,7 +21,7 @@ export class InvisibleEntity extends Entity {
}
override getCursor(_x: number, _y: number) {
- return "url(" + stendhal.paths.sprites + "/cursor/walk.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/walk.png) 1 3, auto";
}
}
diff --git a/src/js/stendhal/entity/Item.ts b/src/js/stendhal/entity/Item.ts
index f33f920bb6..0dd58ac5f6 100644
--- a/src/js/stendhal/entity/Item.ts
+++ b/src/js/stendhal/entity/Item.ts
@@ -14,6 +14,7 @@ import { MenuItem } from "../action/MenuItem";
import { Entity } from "./Entity";
import { TextSprite } from "../sprite/TextSprite";
import { RenderingContext2D } from "util/Types";
+import { Paths } from "../data/Paths";
declare var marauroa: any;
declare var stendhal: any;
@@ -73,7 +74,7 @@ export class Item extends Entity {
override set(key: string, value: any) {
super.set(key, value);
if (key === "class" || key === "subclass") {
- this.sprite.filename = stendhal.paths.sprites + "/items/"
+ this.sprite.filename = Paths.sprites + "/items/"
+ this["class"] + "/" + this["subclass"] + ".png";
}
if (key === "quantity") {
@@ -132,7 +133,7 @@ export class Item extends Entity {
} else {
cursor = ItemMap.getCursor(this["class"], this["name"]);
}
- return "url(" + stendhal.paths.sprites + "/cursor/" + cursor + ".png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/" + cursor + ".png) 1 3, auto";
}
public getToolTip(): string {
diff --git a/src/js/stendhal/entity/NPC.ts b/src/js/stendhal/entity/NPC.ts
index 26071a2b0c..0acb1a1223 100644
--- a/src/js/stendhal/entity/NPC.ts
+++ b/src/js/stendhal/entity/NPC.ts
@@ -15,6 +15,7 @@ import { EntityOverlayRegistry } from "../data/EntityOverlayRegistry";
import { Color } from "../data/color/Color";
import { RenderingContext2D } from "util/Types";
+import { Paths } from "../data/Paths";
declare var stendhal: any;
@@ -55,7 +56,7 @@ export class NPC extends RPEntity {
}
override getCursor(_x: number, _y: number) {
- return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto";
}
}
diff --git a/src/js/stendhal/entity/Player.ts b/src/js/stendhal/entity/Player.ts
index d600bf3e36..98e71bac07 100644
--- a/src/js/stendhal/entity/Player.ts
+++ b/src/js/stendhal/entity/Player.ts
@@ -23,6 +23,7 @@ import { GroupPanelComponent } from "../ui/component/GroupPanelComponent";
import { Color } from "../data/color/Color";
import { RenderingContext2D } from "util/Types";
+import { Paths } from "../data/Paths";
export class Player extends RPEntity {
@@ -237,8 +238,8 @@ export class Player extends RPEntity {
override getCursor(_x: number, _y: number) {
if (this.isVisibleToAction(false)) {
- return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto";
}
- return "url(" + stendhal.paths.sprites + "/cursor/walk.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/walk.png) 1 3, auto";
}
}
diff --git a/src/js/stendhal/entity/Portal.ts b/src/js/stendhal/entity/Portal.ts
index 27a62d3ea7..2410693c7f 100644
--- a/src/js/stendhal/entity/Portal.ts
+++ b/src/js/stendhal/entity/Portal.ts
@@ -13,6 +13,7 @@ import { Entity } from "./Entity";
import { MenuItem } from "../action/MenuItem";
import { Color } from "../data/color/Color";
+import { Paths } from "../data/Paths";
declare var marauroa: any;
declare var stendhal: any;
@@ -75,7 +76,7 @@ export class Portal extends Entity {
}
override getCursor(_x: number, _y: number) {
- return "url(" + stendhal.paths.sprites + "/cursor/portal.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/portal.png) 1 3, auto";
}
}
diff --git a/src/js/stendhal/entity/RPEntity.ts b/src/js/stendhal/entity/RPEntity.ts
index d20881020f..9c9b7f5f40 100644
--- a/src/js/stendhal/entity/RPEntity.ts
+++ b/src/js/stendhal/entity/RPEntity.ts
@@ -34,6 +34,7 @@ import { RangedAttackSprite } from "../sprite/action/RangedAttackSprite";
import { ImageWithDimensions } from "data/ImageWithDimensions";
import { RenderingContext2D } from "util/Types";
+import { Paths } from "../data/Paths";
var HEALTH_BAR_HEIGHT = 6;
@@ -312,7 +313,7 @@ export class RPEntity extends ActiveEntity {
part = part.replace(/-rear$/, "");
}
- const filename = stendhal.paths.sprites + "/outfit/" + part + "/" + n + ".png";
+ const filename = Paths.sprites + "/outfit/" + part + "/" + n + ".png";
const colors = this["outfit_colors"];
let colorname;
if (stendhal.data.outfit.isSkinLayer(part)) {
@@ -362,7 +363,7 @@ export class RPEntity extends ActiveEntity {
if (typeof(this["outfit"]) != "undefined" || typeof(this["outfit_ext"]) != "undefined") {
this.drawMultipartOutfit(ctx);
} else {
- let filename = stendhal.paths.sprites + "/" + this.spritePath + "/" + this["class"];
+ let filename = Paths.sprites + "/" + this.spritePath + "/" + this["class"];
if (typeof(this["subclass"]) != "undefined") {
filename = filename + "/" + this["subclass"];
}
@@ -420,13 +421,13 @@ export class RPEntity extends ActiveEntity {
var x = this["_x"] * 32 - 10;
var y = (this["_y"] + 1) * 32;
if (this.hasOwnProperty("choking")) {
- ctx.drawImage(stendhal.data.sprites.get(stendhal.paths.sprites + "/ideas/choking.png"), x, y - 10);
+ ctx.drawImage(stendhal.data.sprites.get(Paths.sprites + "/ideas/choking.png"), x, y - 10);
} else if (this.hasOwnProperty("eating")) {
- ctx.drawImage(stendhal.data.sprites.get(stendhal.paths.sprites + "/ideas/eat.png"), x, y - 10);
+ ctx.drawImage(stendhal.data.sprites.get(Paths.sprites + "/ideas/eat.png"), x, y - 10);
}
// NPC and pet idea icons
if (this.hasOwnProperty("idea")) {
- const idea = stendhal.paths.sprites + "/ideas/" + this["idea"] + ".png";
+ const idea = Paths.sprites + "/ideas/" + this["idea"] + ".png";
const ani = stendhal.data.sprites.animations.idea[this["idea"]];
if (ani) {
drawAnimatedIcon(idea, ani.delay, x + ani.offsetX * this["width"],
@@ -437,36 +438,36 @@ export class RPEntity extends ActiveEntity {
}
}
if (this.hasOwnProperty("away")) {
- drawAnimatedIcon(stendhal.paths.sprites + "/ideas/away.png", 1500, x + 32 * this["width"], y - this["drawHeight"]);
+ drawAnimatedIcon(Paths.sprites + "/ideas/away.png", 1500, x + 32 * this["width"], y - this["drawHeight"]);
}
if (this.hasOwnProperty("grumpy")) {
- drawAnimatedIcon(stendhal.paths.sprites + "/ideas/grumpy.png", 1000, x + 5, y - this["drawHeight"]);
+ drawAnimatedIcon(Paths.sprites + "/ideas/grumpy.png", 1000, x + 5, y - this["drawHeight"]);
}
if (this.hasOwnProperty("last_player_kill_time")) {
- drawAnimatedIconWithFrames(stendhal.paths.sprites + "/ideas/pk.png", 12, 300, x, y - this["drawHeight"]);
+ drawAnimatedIconWithFrames(Paths.sprites + "/ideas/pk.png", 12, 300, x, y - this["drawHeight"]);
}
// status affects
if (this.hasOwnProperty("poisoned")) {
- drawAnimatedIcon(stendhal.paths.sprites + "/status/poison.png", 100, x + 32 * this["width"] - 10, y - this["drawHeight"]);
+ drawAnimatedIcon(Paths.sprites + "/status/poison.png", 100, x + 32 * this["width"] - 10, y - this["drawHeight"]);
}
if (this.hasOwnProperty("status_confuse")) {
- drawAnimatedIcon(stendhal.paths.sprites + "/status/confuse.png", 200, x + 32 * this["width"] - 14, y - this["drawHeight"] + 16);
+ drawAnimatedIcon(Paths.sprites + "/status/confuse.png", 200, x + 32 * this["width"] - 14, y - this["drawHeight"] + 16);
}
if (this.hasOwnProperty("status_shock")) {
- drawAnimatedIcon(stendhal.paths.sprites + "/status/shock.png", 200, x + 32 * this["width"] - 25, y - 32, 38);
+ drawAnimatedIcon(Paths.sprites + "/status/shock.png", 200, x + 32 * this["width"] - 25, y - 32, 38);
}
// NPC job icons
let nextX = x;
if (this.hasOwnProperty("job_healer")) {
- ctx.drawImage(stendhal.data.sprites.get(stendhal.paths.sprites + "/status/healer.png"), nextX, y - 10);
+ ctx.drawImage(stendhal.data.sprites.get(Paths.sprites + "/status/healer.png"), nextX, y - 10);
nextX += 12;
}
if (this.hasOwnProperty("job_merchant")) {
- ctx.drawImage(stendhal.data.sprites.get(stendhal.paths.sprites + "/status/merchant.png"), nextX, y - 10);
+ ctx.drawImage(stendhal.data.sprites.get(Paths.sprites + "/status/merchant.png"), nextX, y - 10);
nextX += 12;
}
if (this.hasOwnProperty("job_producer")) {
- ctx.drawImage(stendhal.data.sprites.get(stendhal.paths.sprites + "/status/producer.png"), nextX, y - 16);
+ ctx.drawImage(stendhal.data.sprites.get(Paths.sprites + "/status/producer.png"), nextX, y - 16);
nextX += 16;
}
}
@@ -714,21 +715,21 @@ export class RPEntity extends ActiveEntity {
}
onDamaged(_source: Entity, damage: number) {
- this.attackResult = this.createResultIcon(stendhal.paths.sprites + "/combat/hitted.png");
+ this.attackResult = this.createResultIcon(Paths.sprites + "/combat/hitted.png");
var sounds = ["attack-melee-01", "attack-melee-02", "attack-melee-03", "attack-melee-04", "attack-melee-05", "attack-melee-06", "attack-melee-07"];
var index = Math.floor(Math.random() * Math.floor(sounds.length));
stendhal.sound.playLocalizedEffect(this["_x"], this["_y"], 20, 3, sounds[index], 1);
}
onBlocked(_source: Entity) {
- this.attackResult = this.createResultIcon(stendhal.paths.sprites + "/combat/blocked.png");
+ this.attackResult = this.createResultIcon(Paths.sprites + "/combat/blocked.png");
var sounds = ["clang-metallic-1", "clang-dull-1"];
var index = Math.floor(Math.random() * Math.floor(sounds.length));
stendhal.sound.playLocalizedEffect(this["_x"], this["_y"], 20, 3, sounds[index], 1);
}
onMissed(_source: Entity) {
- this.attackResult = this.createResultIcon(stendhal.paths.sprites + "/combat/missed.png");
+ this.attackResult = this.createResultIcon(Paths.sprites + "/combat/missed.png");
}
protected onHPChanged(change: number) {
diff --git a/src/js/stendhal/entity/Sign.ts b/src/js/stendhal/entity/Sign.ts
index 03debd3039..cde383aee3 100644
--- a/src/js/stendhal/entity/Sign.ts
+++ b/src/js/stendhal/entity/Sign.ts
@@ -12,6 +12,7 @@
import { Entity } from "./Entity";
import { ActivityIndicatorSprite } from "../sprite/ActivityIndicatorSprite";
import { RenderingContext2D } from "util/Types";
+import { Paths } from "../data/Paths";
declare var stendhal: any;
@@ -35,7 +36,7 @@ export class Sign extends Entity {
override draw(ctx: RenderingContext2D) {
if (!this.imagePath) {
- this.imagePath = stendhal.paths.sprites + "/signs/" + this["class"] + ".png";
+ this.imagePath = Paths.sprites + "/signs/" + this["class"] + ".png";
}
var image = stendhal.data.sprites.get(this.imagePath);
if (image.height) {
@@ -53,7 +54,7 @@ export class Sign extends Entity {
}
override getCursor(_x: number, _y: number) {
- return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto";
}
}
diff --git a/src/js/stendhal/entity/TrainingDummy.ts b/src/js/stendhal/entity/TrainingDummy.ts
index d2984622ce..9f4460fc21 100644
--- a/src/js/stendhal/entity/TrainingDummy.ts
+++ b/src/js/stendhal/entity/TrainingDummy.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { Paths } from "../data/Paths";
import { NPC } from "./NPC";
declare var marauroa: any;
@@ -29,6 +30,6 @@ export class TrainingDummy extends NPC {
}
override getCursor(_x: number, _y: number) {
- return "url(" + stendhal.paths.sprites + "/cursor/attack.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/attack.png) 1 3, auto";
}
}
diff --git a/src/js/stendhal/entity/UseableEntity.ts b/src/js/stendhal/entity/UseableEntity.ts
index 4ec2317aa8..d9bdf003c4 100644
--- a/src/js/stendhal/entity/UseableEntity.ts
+++ b/src/js/stendhal/entity/UseableEntity.ts
@@ -13,6 +13,7 @@ declare var stendhal: any;
import { Entity } from "./Entity";
import { MenuItem } from "../action/MenuItem";
+import { Paths } from "../data/Paths";
export class UseableEntity extends Entity {
@@ -30,7 +31,7 @@ export class UseableEntity extends Entity {
override set(key: string, value: any) {
super.set(key, value);
if (key === "class" || key === "name") {
- this.sprite.filename = stendhal.paths.sprites + "/"
+ this.sprite.filename = Paths.sprites + "/"
+ this["class"] + "/" + this["_name"] + ".png";
}
if (key === "state") {
diff --git a/src/js/stendhal/entity/VisibleEntity.ts b/src/js/stendhal/entity/VisibleEntity.ts
index 12cea77ae8..854a29fc22 100644
--- a/src/js/stendhal/entity/VisibleEntity.ts
+++ b/src/js/stendhal/entity/VisibleEntity.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { Paths } from "../data/Paths";
import { Entity } from "./Entity";
declare var stendhal: any;
@@ -28,7 +29,7 @@ export class VisibleEntity extends Entity {
override set(key: string, value: any) {
super.set(key, value);
if (key === "class" || key === "subclass" || key === "_name") {
- this.sprite.filename = stendhal.paths.sprites + "/"
+ this.sprite.filename = Paths.sprites + "/"
+ (this["class"] || "") + "/"
+ (this["subclass"] || "") + "/"
+ (this["_name"] || "") + ".png";
@@ -42,7 +43,7 @@ export class VisibleEntity extends Entity {
}
override getCursor(_x: number, _y: number) {
- return "url(" + stendhal.paths.sprites + "/cursor/look.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/look.png) 1 3, auto";
}
}
diff --git a/src/js/stendhal/entity/WalkBlocker.ts b/src/js/stendhal/entity/WalkBlocker.ts
index 7d8ccd650d..ddaed24826 100644
--- a/src/js/stendhal/entity/WalkBlocker.ts
+++ b/src/js/stendhal/entity/WalkBlocker.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { Paths } from "../data/Paths";
import { Entity } from "./Entity";
declare var stendhal: any;
@@ -22,7 +23,7 @@ export class WalkBlocker extends Entity {
}
override getCursor(_x: number, _y: number) {
- return "url(" + stendhal.paths.sprites + "/cursor/stop.png) 1 3, auto";
+ return "url(" + Paths.sprites + "/cursor/stop.png) 1 3, auto";
}
}
diff --git a/src/js/stendhal/event/ShowItemListEvent.ts b/src/js/stendhal/event/ShowItemListEvent.ts
index 7b3c1f26d1..9bd77e7089 100644
--- a/src/js/stendhal/event/ShowItemListEvent.ts
+++ b/src/js/stendhal/event/ShowItemListEvent.ts
@@ -15,6 +15,7 @@ import { RPEvent } from "./RPEvent";
import { ui } from "../ui/UI";
import { DialogContentComponent } from "../ui/toolkit/DialogContentComponent";
import { Chat } from "../util/Chat";
+import { Paths } from "../data/Paths";
declare var stendhal: any;
@@ -94,7 +95,7 @@ export class ShowItemListEvent extends RPEvent {
const itemCell = document.createElement("td");
itemCell.appendChild(
stendhal.data.sprites.get(
- stendhal.paths.sprites + "/items/" + i.img
+ Paths.sprites + "/items/" + i.img
)
);
row.appendChild(itemCell);
diff --git a/src/js/stendhal/sprite/AchievementBanner.ts b/src/js/stendhal/sprite/AchievementBanner.ts
index 5b5a8511da..12d2427b8f 100644
--- a/src/js/stendhal/sprite/AchievementBanner.ts
+++ b/src/js/stendhal/sprite/AchievementBanner.ts
@@ -12,6 +12,7 @@
import { TextBubble } from "./TextBubble";
import { BackgroundPainter } from "../util/BackgroundPainter";
import { RenderingContext2D } from "util/Types";
+import { Paths } from "../data/Paths";
declare const stendhal: any;
@@ -33,9 +34,9 @@ export class AchievementBanner extends TextBubble {
constructor(cat: string, title: string, desc: string) {
super(desc);
this.title = title;
- const bg = stendhal.data.sprites.get(stendhal.paths.gui + "/banner_background.png");
+ const bg = stendhal.data.sprites.get(Paths.gui + "/banner_background.png");
this.banner = new BackgroundPainter(bg);
- this.icon = stendhal.data.sprites.get(stendhal.paths.achievements
+ this.icon = stendhal.data.sprites.get(Paths.achievements
+ "/" + cat.toLowerCase() + ".png");
/* keep achievements on the screen a bit longer since they
diff --git a/src/js/stendhal/sprite/NotificationBubble.ts b/src/js/stendhal/sprite/NotificationBubble.ts
index 032bfa2e86..7c4e6d7d83 100644
--- a/src/js/stendhal/sprite/NotificationBubble.ts
+++ b/src/js/stendhal/sprite/NotificationBubble.ts
@@ -19,6 +19,7 @@ import { Color } from "../data/color/Color";
import { NotificationType } from "../util/NotificationType";
import { Speech } from "../util/Speech";
import { RenderingContext2D } from "util/Types";
+import { Paths } from "../data/Paths";
export class NotificationBubble extends TextBubble {
@@ -133,7 +134,7 @@ export class NotificationBubble extends TextBubble {
* Loads a profile image to be drawn with text.
*/
private loadProfileSprite() {
- const img = stendhal.data.sprites.get(stendhal.paths.sprites
+ const img = stendhal.data.sprites.get(Paths.sprites
+ "/npc/" + this.profileName + ".png");
if (img.complete && img.height) {
this.profile = stendhal.data.sprites.getAreaOf(img, 48, 48, 48, 128);
diff --git a/src/js/stendhal/sprite/action/RangedAttackSprite.ts b/src/js/stendhal/sprite/action/RangedAttackSprite.ts
index f1dfc47207..4323a90384 100644
--- a/src/js/stendhal/sprite/action/RangedAttackSprite.ts
+++ b/src/js/stendhal/sprite/action/RangedAttackSprite.ts
@@ -13,6 +13,7 @@
import { AttackSprite } from "./AttackSprite";
import { RPEntity } from "../../entity/RPEntity";
import { RenderingContext2D } from "util/Types";
+import { Paths } from "../../data/Paths";
declare var stendhal: any;
@@ -23,7 +24,7 @@ export class RangedAttackSprite extends AttackSprite {
private readonly targetX: number;
private readonly targetY: number;
private readonly color: string;
- private readonly image = stendhal.data.sprites.get(stendhal.paths.sprites + "/combat/ranged.png");
+ private readonly image = stendhal.data.sprites.get(Paths.sprites + "/combat/ranged.png");
private readonly weapon?: string;
diff --git a/src/js/stendhal/ui/component/BuddyListComponent.ts b/src/js/stendhal/ui/component/BuddyListComponent.ts
index 18ec0bd96e..9c2d0c7254 100644
--- a/src/js/stendhal/ui/component/BuddyListComponent.ts
+++ b/src/js/stendhal/ui/component/BuddyListComponent.ts
@@ -16,6 +16,7 @@ import { Component } from "../toolkit/Component";
import { ActionContextMenu } from "../dialog/ActionContextMenu";
import { ChatInputComponent } from "./ChatInputComponent";
+import { Paths } from "../../data/Paths";
declare let marauroa: any;
@@ -66,9 +67,9 @@ export class BuddyListComponent extends Component {
for (let i = 0; i < buddies.length; i++) {
html += "
" + stendhal.ui.html.esc(buddies[i].name) + "";
}
diff --git a/src/js/stendhal/ui/component/ChatInputComponent.ts b/src/js/stendhal/ui/component/ChatInputComponent.ts
index 22f2fd898d..df8c7254ae 100644
--- a/src/js/stendhal/ui/component/ChatInputComponent.ts
+++ b/src/js/stendhal/ui/component/ChatInputComponent.ts
@@ -21,6 +21,7 @@ import { Component } from "../toolkit/Component";
import { singletons } from "../../SingletonRepo";
import { ChatCompletionHelper } from "../../util/ChatCompletionHelper";
import { KeyCode } from "../../util/KeyCode";
+import { Paths } from "../../data/Paths";
/**
@@ -86,7 +87,7 @@ export class ChatInputComponent extends Component {
btn_emoji.innerText = "☺";
} else {
// set image for emoji button
- btn_emoji.appendChild(stendhal.data.sprites.get(stendhal.paths.sprites + "/emoji/smile.png").cloneNode());
+ btn_emoji.appendChild(stendhal.data.sprites.get(Paths.sprites + "/emoji/smile.png").cloneNode());
}
}
diff --git a/src/js/stendhal/ui/component/ItemContainerImplementation.ts b/src/js/stendhal/ui/component/ItemContainerImplementation.ts
index 8cbbed7a8a..5939106f3b 100644
--- a/src/js/stendhal/ui/component/ItemContainerImplementation.ts
+++ b/src/js/stendhal/ui/component/ItemContainerImplementation.ts
@@ -21,6 +21,7 @@ import { Item } from "../../entity/Item";
import { singletons } from "../../SingletonRepo";
import { Point } from "../../util/Point";
+import { Paths } from "../../data/Paths";
/**
@@ -131,7 +132,7 @@ export class ItemContainerImplementation {
}
e.style.backgroundImage = "url("
- + stendhal.data.sprites.checkPath(stendhal.paths.sprites
+ + stendhal.data.sprites.checkPath(Paths.sprites
+ "/items/" + o["class"] + "/" + o["subclass"] + ".png")
+ ")";
e.style.backgroundPosition = (xOffset+1) + "px " + (yOffset+1) + "px";
@@ -148,7 +149,7 @@ export class ItemContainerImplementation {
for (let i = cnt; i < this.size; i++) {
let e = this.parentElement.querySelector("#" + this.slot +this. suffix + i) as HTMLElement;
if (this.defaultImage) {
- e.style.backgroundImage = "url(" + stendhal.paths.gui + "/" + this.defaultImage + ")";
+ e.style.backgroundImage = "url(" + Paths.gui + "/" + this.defaultImage + ")";
} else {
e.style.backgroundImage = "none";
}
@@ -427,14 +428,14 @@ export class ItemContainerImplementation {
private updateCursor(target: HTMLElement, item?: Item) {
if (item) {
if (this.slot === "content" && stendhal.config.getBoolean("inventory.quick-pickup")) {
- target.style.cursor = "url(" + stendhal.paths.sprites
+ target.style.cursor = "url(" + Paths.sprites
+ "/cursor/itempickupfromslot.png) 1 3, auto";
return;
}
target.style.cursor = item.getCursor(0, 0);
return;
}
- target.style.cursor = "url(" + stendhal.paths.sprites
+ target.style.cursor = "url(" + Paths.sprites
+ "/cursor/normal.png) 1 3, auto";
}
diff --git a/src/js/stendhal/ui/component/StatusesListComponent.ts b/src/js/stendhal/ui/component/StatusesListComponent.ts
index b0c30a985d..0f97733fbe 100644
--- a/src/js/stendhal/ui/component/StatusesListComponent.ts
+++ b/src/js/stendhal/ui/component/StatusesListComponent.ts
@@ -12,6 +12,7 @@
import { Component } from "../toolkit/Component";
import { User } from "../../entity/User";
+import { Paths } from "../../data/Paths";
declare var stendhal: any;
@@ -65,9 +66,9 @@ export class StatusesListComponent extends Component {
}
let iconPath: string;
if (Object.keys(StatusesListComponent.iconPaths).indexOf(id) !== -1) {
- iconPath = stendhal.paths.sprites + StatusesListComponent.iconPaths[id];
+ iconPath = Paths.sprites + StatusesListComponent.iconPaths[id];
} else {
- iconPath = stendhal.paths.sprites + "/status/panel/" + id + ".png";
+ iconPath = Paths.sprites + "/status/panel/" + id + ".png";
}
const icon = stendhal.data.sprites.get(iconPath);
icon.className = "status-icon";
diff --git a/src/js/stendhal/ui/dialog/EmojiMapDialog.ts b/src/js/stendhal/ui/dialog/EmojiMapDialog.ts
index 9fab015c4d..37044a9c77 100644
--- a/src/js/stendhal/ui/dialog/EmojiMapDialog.ts
+++ b/src/js/stendhal/ui/dialog/EmojiMapDialog.ts
@@ -11,6 +11,7 @@
import { DialogContentComponent } from "../toolkit/DialogContentComponent";
import { singletons } from "../../SingletonRepo";
+import { Paths } from "../../data/Paths";
declare var marauroa: any;
declare var stendhal: any;
@@ -83,7 +84,7 @@ export class EmojiMapDialog extends DialogContentComponent {
const button = document.createElement("button");
button.classList.add("shortcut-button", "emoji-text");
if (!this.sysEmojis) {
- button.appendChild(stendhal.data.sprites.get(stendhal.paths.sprites + "/emoji/" + emoji + ".png").cloneNode());
+ button.appendChild(stendhal.data.sprites.get(Paths.sprites + "/emoji/" + emoji + ".png").cloneNode());
} else {
button.innerText = emoji;
}
diff --git a/src/js/stendhal/ui/dialog/TravelLogDialog.ts b/src/js/stendhal/ui/dialog/TravelLogDialog.ts
index 54e035f2c4..a273fce417 100644
--- a/src/js/stendhal/ui/dialog/TravelLogDialog.ts
+++ b/src/js/stendhal/ui/dialog/TravelLogDialog.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { Paths } from "../../data/Paths";
import { DialogContentComponent } from "../toolkit/DialogContentComponent";
import { ui } from "../UI";
import { UIComponentEnum } from "../UIComponentEnum";
@@ -167,7 +168,7 @@ export class TravelLogDialog extends DialogContentComponent {
detailsSpan.innerHTML = "" + stendhal.ui.html.esc(selectedItem) + "
";
if (this.repeatable[selectedItem]) {
detailsSpan.innerHTML += ""
- + "
I can do this quest again.
";
+ + "
I can do this quest again.
";
}
detailsSpan.innerHTML += ""
diff --git a/src/js/stendhal/ui/dialog/outfit/OutfitPartSelector.ts b/src/js/stendhal/ui/dialog/outfit/OutfitPartSelector.ts
index d28cee65ba..5c403739e2 100644
--- a/src/js/stendhal/ui/dialog/outfit/OutfitPartSelector.ts
+++ b/src/js/stendhal/ui/dialog/outfit/OutfitPartSelector.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { Paths } from "../../../data/Paths";
import { RenderingContext2D } from "util/Types";
declare var stendhal: any;
@@ -106,7 +107,7 @@ export class OutfitPartSelector {
// FIXME: need to draw "busty" dress variants for body index 1
- const fname = stendhal.paths.sprites + "/outfit/" + part + "/" + this.indexString(index) + suffix;
+ const fname = Paths.sprites + "/outfit/" + part + "/" + this.indexString(index) + suffix;
if (color != null) {
return stendhal.data.sprites.getFilteredWithPromise(fname, "trueColor", color);
}
diff --git a/src/js/stendhal/ui/joystick/JoystickImpl.ts b/src/js/stendhal/ui/joystick/JoystickImpl.ts
index 4eb84aea71..6fb079f445 100644
--- a/src/js/stendhal/ui/joystick/JoystickImpl.ts
+++ b/src/js/stendhal/ui/joystick/JoystickImpl.ts
@@ -15,6 +15,7 @@ declare var stendhal: any;
import { ui } from "../UI";
import { SoftwareJoystickController } from "../SoftwareJoystickController";
import { Direction } from "../../util/Direction";
+import { Paths } from "../../data/Paths";
/**
@@ -62,7 +63,7 @@ export abstract class JoystickImpl {
* Path to image.
*/
public static getResource(basename: string): string {
- return stendhal.paths.gui + "/joystick/" + basename + ".png";
+ return Paths.gui + "/joystick/" + basename + ".png";
}
/**
diff --git a/src/js/stendhal/util/ConfigManager.ts b/src/js/stendhal/util/ConfigManager.ts
index 8b86302618..18caf0eb24 100644
--- a/src/js/stendhal/util/ConfigManager.ts
+++ b/src/js/stendhal/util/ConfigManager.ts
@@ -17,6 +17,7 @@ import { ui } from "../ui/UI";
import { UIComponentEnum } from "../ui/UIComponentEnum";
import { BuddyListComponent } from "../ui/component/BuddyListComponent";
+import { Paths } from "../data/Paths";
/**
@@ -525,7 +526,7 @@ export class ConfigManager {
applyTheme(element: HTMLElement, children=false, recurse=false, updateBG=false) {
const current = this.getTheme();
element.style.setProperty("background",
- "url(" + stendhal.paths.gui + "/" + this.themes.map[current] + ")");
+ "url(" + Paths.gui + "/" + this.themes.map[current] + ")");
// make texts readable with dark & light themes
let color = "#000000";
@@ -564,7 +565,7 @@ export class ConfigManager {
let rootStyle = document.documentElement.style;
rootStyle.setProperty("--background-url",
- "url(" + stendhal.paths.gui + "/" + this.themes.map[current] + ")");
+ "url(" + Paths.gui + "/" + this.themes.map[current] + ")");
if (this.usingDarkTheme()) {
rootStyle.setProperty("--text-color", "#fff");
rootStyle.setProperty("--text-color-inactive", "#aaa");
diff --git a/src/js/stendhal/util/Nature.ts b/src/js/stendhal/util/Nature.ts
index 01be2b1e22..a0a2c944db 100644
--- a/src/js/stendhal/util/Nature.ts
+++ b/src/js/stendhal/util/Nature.ts
@@ -9,7 +9,7 @@
* *
***************************************************************************/
-declare var stendhal: any;
+import { Paths } from "../data/Paths";
/**
@@ -47,7 +47,7 @@ export class Nature {
* Sprite path.
*/
getWeaponPath(weapon: string): string {
- let path = stendhal.paths.sprites + "/combat/" + weapon;
+ let path = Paths.sprites + "/combat/" + weapon;
if (typeof(this.elem) !== "undefined") {
path += "_" + this.elem;
}
diff --git a/src/js/stendhal/util/SessionManager.ts b/src/js/stendhal/util/SessionManager.ts
index 24d36cb455..456e2bbf63 100644
--- a/src/js/stendhal/util/SessionManager.ts
+++ b/src/js/stendhal/util/SessionManager.ts
@@ -9,6 +9,8 @@
* *
***************************************************************************/
+import { Paths } from "../data/Paths";
+
declare var stendhal: any;
@@ -197,7 +199,7 @@ export class SessionManager {
* `true` if data path root is set to "/testdata".
*/
isTestClient(): boolean {
- return stendhal.paths.data === "/testdata";
+ return Paths.data === "/testdata";
}
/**
diff --git a/src/js/stendhal/util/WeatherRenderer.ts b/src/js/stendhal/util/WeatherRenderer.ts
index 096144e223..a35a3dd137 100644
--- a/src/js/stendhal/util/WeatherRenderer.ts
+++ b/src/js/stendhal/util/WeatherRenderer.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { Paths } from "../data/Paths";
import { TileStore } from "../data/TileStore";
import { SoundObject } from "../data/sound/SoundFactory";
@@ -108,7 +109,7 @@ export class WeatherRenderer {
if (!weather) {
this.sprite = undefined;
} else {
- const img = stendhal.paths.weather + "/" + weather + ".png";
+ const img = Paths.weather + "/" + weather + ".png";
this.sprite = stendhal.data.sprites.get(img);
/* FIXME:
* "TypeError: $stendhal$$.data.$tileset$.$weatherAnimationMap$
From c38740eaabbfe038caa24e7b9520f68ca9fadcae Mon Sep 17 00:00:00 2001
From: Jordan Irwin
Date: Thu, 1 Jan 2026 18:24:56 -0800
Subject: [PATCH 038/162] Use 'versionName' instead of 'versionCode' in Android
output filename
---
app/android/client/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/android/client/build.gradle b/app/android/client/build.gradle
index 6fa80a9a75..dcff5114b2 100644
--- a/app/android/client/build.gradle
+++ b/app/android/client/build.gradle
@@ -26,7 +26,7 @@ android {
}
// build output
- project.setProperty("archivesBaseName", "${namespace}-${defaultConfig.versionCode}")
+ project.setProperty("archivesBaseName", "${namespace}-${defaultConfig.versionName}")
project.setProperty("buildDir", "${projectDir}/../../../build/build_android_client")
/* A keystore.properties file can be placed in the root Android project directory and must
From 6224a0638ad9f70285a3ee4493d2e27a945565d6 Mon Sep 17 00:00:00 2001
From: Jordan Irwin
Date: Thu, 1 Jan 2026 18:31:27 -0800
Subject: [PATCH 039/162] Run 'ant init' to update copyright year
---
README.md | 2 +-
buildtools/mkdocs/mkdocs.yml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 63698cdffc..9655013187 100644
--- a/README.md
+++ b/README.md
@@ -76,6 +76,6 @@ even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE
[LICENSE.txt](LICENSE.txt) and [src/js/LICENSE.html](src/js/LICENSE.html) file for more details.
Stendhal(c) is copyright of Miguel Angel Blanch Lardin, 2005-2008, arianne_rpg at users dot sourceforge dot net and others
-Stendhal(c) is copyright of the Arianne Project, 2006-2025, arianne-general at lists dot sourceforge dot net
+Stendhal(c) is copyright of the Arianne Project, 2006-2026, arianne-general at lists dot sourceforge dot net
Please have a look at the list of [contributors](doc/contributors.md#contributors).
diff --git a/buildtools/mkdocs/mkdocs.yml b/buildtools/mkdocs/mkdocs.yml
index 1483f53630..205e9d758c 100644
--- a/buildtools/mkdocs/mkdocs.yml
+++ b/buildtools/mkdocs/mkdocs.yml
@@ -1,6 +1,6 @@
site_name: Stendhal 1.48.5 Reference Documentation
site_description: A fun, friendly, & free multiplayer online adventure game.
-copyright: Copyright © 2003-2025 Stendhal
+copyright: Copyright © 2003-2026 Stendhal
site_url: https://stendhalgame.org/
nav:
- Index: /reference/
From 8f642ac4461886a4f3e2f9bea533f8d0ecdc2edb Mon Sep 17 00:00:00 2001
From: Jordan Irwin
Date: Thu, 1 Jan 2026 18:33:01 -0800
Subject: [PATCH 040/162] Increment version to 1.49.5
---
app/android/client/build.gradle | 4 ++--
app/android/client/src/main/res/values/strings.xml | 2 +-
app/neutralinojs/neutralino.config.json | 2 +-
bin/runserver.bat | 2 +-
bin/runserver.sh | 2 +-
build.ant.properties | 6 +++---
buildtools/mkdocs/mkdocs.yml | 2 +-
src/games/stendhal/client/update/HttpClient.java | 2 +-
src/games/stendhal/common/Debug.java | 2 +-
9 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/app/android/client/build.gradle b/app/android/client/build.gradle
index dcff5114b2..d5f56cb075 100644
--- a/app/android/client/build.gradle
+++ b/app/android/client/build.gradle
@@ -10,8 +10,8 @@ android {
applicationId namespace
minSdk 21
targetSdk 32
- versionCode 1048005
- versionName "1.48.5"
+ versionCode 1049005
+ versionName "1.49.5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
targetSdkVersion 32
minSdkVersion 21
diff --git a/app/android/client/src/main/res/values/strings.xml b/app/android/client/src/main/res/values/strings.xml
index fe759d152f..f60bf90f42 100644
--- a/app/android/client/src/main/res/values/strings.xml
+++ b/app/android/client/src/main/res/values/strings.xml
@@ -5,6 +5,6 @@
Stendhal
stendhalprod8gps5y99pu
prod
- 1.48.5
+ 1.49.5
diff --git a/app/neutralinojs/neutralino.config.json b/app/neutralinojs/neutralino.config.json
index 5113267710..7d4ac4d423 100644
--- a/app/neutralinojs/neutralino.config.json
+++ b/app/neutralinojs/neutralino.config.json
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/neutralinojs/neutralinojs/main/schemas/neutralino.config.schema.json",
"applicationId": "org.stendhalgame.client",
- "version": "1.48.5",
+ "version": "1.49.5",
"defaultMode": "window",
"port": 0,
"documentRoot": "/resources/",
diff --git a/bin/runserver.bat b/bin/runserver.bat
index a6d9623195..91d06c7e74 100644
--- a/bin/runserver.bat
+++ b/bin/runserver.bat
@@ -1,6 +1,6 @@
@echo off
-set STENDHAL_VERSION=1.48.5
+set STENDHAL_VERSION=1.49.5
set SERVER_JAR=stendhal-server-%STENDHAL_VERSION%.jar
:: change to server directory
diff --git a/bin/runserver.sh b/bin/runserver.sh
index 1a2e475093..6dbe08452d 100755
--- a/bin/runserver.sh
+++ b/bin/runserver.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
-STENDHAL_VERSION="1.48.5"
+STENDHAL_VERSION="1.49.5"
SERVER_JAR="stendhal-server-${STENDHAL_VERSION}.jar"
# change to server directory
diff --git a/build.ant.properties b/build.ant.properties
index 427f93c1f9..fb5e66018b 100644
--- a/build.ant.properties
+++ b/build.ant.properties
@@ -91,10 +91,10 @@ updates_server_fallback = http://arianne.sourceforge.net/stendhal/updates
version_server = http://arianne.sourceforge.net/stendhal.version
# current version of stendhal
-version.old = 1.48
-version = 1.48.5
+version.old = 1.49
+version = 1.49.5
# FIXME: dynamic method to set this property?
-version.android = 1048005
+version.android = 1049005
# javac options
javac.deprecation = true
diff --git a/buildtools/mkdocs/mkdocs.yml b/buildtools/mkdocs/mkdocs.yml
index 205e9d758c..b73584a76f 100644
--- a/buildtools/mkdocs/mkdocs.yml
+++ b/buildtools/mkdocs/mkdocs.yml
@@ -1,4 +1,4 @@
-site_name: Stendhal 1.48.5 Reference Documentation
+site_name: Stendhal 1.49.5 Reference Documentation
site_description: A fun, friendly, & free multiplayer online adventure game.
copyright: Copyright © 2003-2026 Stendhal
site_url: https://stendhalgame.org/
diff --git a/src/games/stendhal/client/update/HttpClient.java b/src/games/stendhal/client/update/HttpClient.java
index f4143f3956..5cb0b66690 100644
--- a/src/games/stendhal/client/update/HttpClient.java
+++ b/src/games/stendhal/client/update/HttpClient.java
@@ -118,7 +118,7 @@ public class HttpClient {
try {
connection = (HttpURLConnection) url.openConnection();
// Wikipedia API now requires User-Agent string
- connection.setRequestProperty("User-Agent", "StendhalClient/1.48.5 (https://arianne-project.org/contact.html)");
+ connection.setRequestProperty("User-Agent", "StendhalClient/1.49.5 (https://arianne-project.org/contact.html)");
connection.setConnectTimeout(myTimeout);
connection.setInstanceFollowRedirects(true);
connection.setUseCaches(false);
diff --git a/src/games/stendhal/common/Debug.java b/src/games/stendhal/common/Debug.java
index 9ff2b7ed8f..02e0f91f7b 100644
--- a/src/games/stendhal/common/Debug.java
+++ b/src/games/stendhal/common/Debug.java
@@ -26,7 +26,7 @@ public class Debug {
/** version. */
// Note: This line is updated by build.xml using a regexp so be sure to adjust it in case you modify this line.
- public static final String VERSION = "1.48.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.
From f1d98fff5615d7a0618e4c2946a10c393a57e322 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Fri, 2 Jan 2026 04:10:44 +0100
Subject: [PATCH 041/162] access SpriteStore via singletons instead of
stendhal-variable
---
src/js/stendhal/action/DebugAction.ts | 2 +-
src/js/stendhal/data/EmojiStore.ts | 5 +-
src/js/stendhal/data/Outfit.ts | 15 ++--
src/js/stendhal/data/SpriteStore.ts | 7 +-
src/js/stendhal/entity/Corpse.ts | 3 +-
src/js/stendhal/entity/DomesticAnimal.ts | 4 +-
src/js/stendhal/entity/Door.ts | 5 +-
src/js/stendhal/entity/Entity.ts | 3 +-
src/js/stendhal/entity/EntityRegistry.ts | 84 ++++++++++---------
src/js/stendhal/entity/Food.ts | 4 +-
src/js/stendhal/entity/Gate.ts | 4 +-
.../stendhal/entity/GrowingEntitySpawner.ts | 4 +-
src/js/stendhal/entity/Item.ts | 11 ++-
src/js/stendhal/entity/RPEntity.ts | 36 ++++----
src/js/stendhal/entity/Sign.ts | 3 +-
src/js/stendhal/event/ShowItemListEvent.ts | 3 +-
src/js/stendhal/sprite/AchievementBanner.ts | 5 +-
.../sprite/ActivityIndicatorSprite.ts | 3 +-
src/js/stendhal/sprite/NotificationBubble.ts | 5 +-
.../sprite/action/MeleeAttackSprite.ts | 9 +-
.../sprite/action/RangedAttackSprite.ts | 3 +-
src/js/stendhal/ui/ViewPort.ts | 4 +-
.../ui/component/ChatInputComponent.ts | 2 +-
.../component/ItemContainerImplementation.ts | 4 +-
.../ui/component/StatusesListComponent.ts | 5 +-
src/js/stendhal/ui/dialog/EmojiMapDialog.ts | 2 +-
.../ui/dialog/outfit/OutfitColorSelector.ts | 17 ++--
.../outfit/OutfitPaletteColorSelector.ts | 16 ++--
.../ui/dialog/outfit/OutfitPartSelector.ts | 5 +-
src/js/stendhal/util/WeatherRenderer.ts | 3 +-
30 files changed, 144 insertions(+), 132 deletions(-)
diff --git a/src/js/stendhal/action/DebugAction.ts b/src/js/stendhal/action/DebugAction.ts
index 648664b972..81cfcda0a7 100644
--- a/src/js/stendhal/action/DebugAction.ts
+++ b/src/js/stendhal/action/DebugAction.ts
@@ -140,7 +140,7 @@ export class DebugAction extends SlashAction {
if (weather) {
weather = weather.replace(/ /g, "_");
const wfilename = Paths.weather + "/" + weather + ".png";
- if (!stendhal.data.sprites.getCached(wfilename)) {
+ if (!singletons.getSpriteStore().getCached(wfilename)) {
Chat.logH("error", "unknown weather: " + wfilename);
return;
}
diff --git a/src/js/stendhal/data/EmojiStore.ts b/src/js/stendhal/data/EmojiStore.ts
index 78a72e5e85..f526c78584 100644
--- a/src/js/stendhal/data/EmojiStore.ts
+++ b/src/js/stendhal/data/EmojiStore.ts
@@ -11,8 +11,7 @@
import { Paths } from "./Paths";
import { JSONLoader } from "../util/JSONLoader";
-
-declare var stendhal: any;
+import { singletons } from "../SingletonRepo";
export class EmojiStore {
@@ -71,7 +70,7 @@ export class EmojiStore {
if (!filename) {
return;
}
- return stendhal.data.sprites.get(filename);
+ return singletons.getSpriteStore().get(filename);
}
/**
diff --git a/src/js/stendhal/data/Outfit.ts b/src/js/stendhal/data/Outfit.ts
index 15010fd80a..680f6dbc11 100644
--- a/src/js/stendhal/data/Outfit.ts
+++ b/src/js/stendhal/data/Outfit.ts
@@ -10,10 +10,12 @@
* *
***************************************************************************/
+import { singletons } from "../SingletonRepo";
import { DrawingStage } from "../util/DrawingStage";
import { Pair } from "../util/Pair";
import { StringUtil } from "../util/StringUtil";
import { Paths } from "./Paths";
+import { SpriteImage } from "../data/SpriteStore";
declare var stendhal: any;
@@ -201,7 +203,7 @@ export class Outfit {
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);
+ let image: SpriteImage = singletons.getSpriteStore().getCached(sig);
const onReady = function(e?: Event) {
image.removeEventListener("load", onReady);
@@ -245,14 +247,14 @@ export class Outfit {
layerPath += ".png";
const coloring = this.getLayerColor(layerName);
if (typeof(coloring) === "undefined") {
- layers.push(stendhal.data.sprites.get(layerPath));
+ layers.push(singletons.getSpriteStore().get(layerPath));
} else {
- layers.push(stendhal.data.sprites.getFiltered(layerPath, "trueColor", coloring));
+ layers.push(singletons.getSpriteStore().getFiltered(layerPath, "trueColor", coloring));
}
}
if (layers.length == 0) {
- image = stendhal.data.sprites.getFailsafe();
+ image = singletons.getSpriteStore().getFailsafe() as SpriteImage;
callback(image);
return;
}
@@ -272,9 +274,10 @@ export class Outfit {
layer.removeEventListener("load", onLayerReady);
stage.drawImage(layer);
}
- image = stage.toImage();
+ image = stage.toImage() as SpriteImage;
stage.reset();
- stendhal.data.sprites.cache(sig, image);
+ image.counter = 1;
+ singletons.getSpriteStore().cache(sig, image);
if (image.height > 0) {
onReady();
} else {
diff --git a/src/js/stendhal/data/SpriteStore.ts b/src/js/stendhal/data/SpriteStore.ts
index 2f260c89b4..8260e44f0a 100644
--- a/src/js/stendhal/data/SpriteStore.ts
+++ b/src/js/stendhal/data/SpriteStore.ts
@@ -1,5 +1,5 @@
/***************************************************************************
- * (C) Copyright 2003-2024 - Stendhal *
+ * (C) Copyright 2003-2026 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
@@ -11,11 +11,10 @@
declare var stendhal: any;
-import { Canvas } from "util/Types";
import { Paths } from "./Paths";
-class SpriteImage extends Image {
+export class SpriteImage extends Image {
// number of times the image has been accessed after initial creation
counter = 0;
}
@@ -60,7 +59,7 @@ export class SpriteStore {
};
// TODO: move to animation.json
- private animations: {[key: string]: any} = {
+ animations: {[key: string]: any} = {
idea: {
"love": {delay: 100, offsetX: 24, offsetY: -8}
}
diff --git a/src/js/stendhal/entity/Corpse.ts b/src/js/stendhal/entity/Corpse.ts
index 18b16292aa..3c20855e20 100644
--- a/src/js/stendhal/entity/Corpse.ts
+++ b/src/js/stendhal/entity/Corpse.ts
@@ -19,6 +19,7 @@ import { Chat } from "../util/Chat";
import { PopupInventory } from "./PopupInventory";
import { Paths } from "../data/Paths";
+import { singletons } from "../SingletonRepo";
declare var marauroa: any;
declare var stendhal: any;
@@ -62,7 +63,7 @@ export class Corpse extends PopupInventory {
const tileW = stendhal.ui.gamewindow.targetTileWidth;
const tileH = stendhal.ui.gamewindow.targetTileHeight;
if (this.sprite.width == undefined || this.sprite.height == undefined) {
- const image = stendhal.data.sprites.get(this.sprite.filename);
+ const image = singletons.getSpriteStore().get(this.sprite.filename);
if (image.complete) {
this.sprite.width = image.width < tileW ? tileW : image.width;
this.sprite.height = image.height < tileH ? tileH : image.height;
diff --git a/src/js/stendhal/entity/DomesticAnimal.ts b/src/js/stendhal/entity/DomesticAnimal.ts
index 9de58f8730..2dcd199cbb 100644
--- a/src/js/stendhal/entity/DomesticAnimal.ts
+++ b/src/js/stendhal/entity/DomesticAnimal.ts
@@ -15,9 +15,9 @@ import { MenuItem} from "../action/MenuItem";
import { Color } from "../data/color/Color";
import { RenderingContext2D } from "util/Types";
import { Paths } from "../data/Paths";
+import { singletons } from "../SingletonRepo";
declare var marauroa: any;
-declare var stendhal: any;
export class DomesticAnimal extends RPEntity {
@@ -34,7 +34,7 @@ export class DomesticAnimal extends RPEntity {
var localX = this["_x"] * 32;
var localY = this["_y"] * 32;
- var image = stendhal.data.sprites.get(this.imagePath);
+ var image = singletons.getSpriteStore().get(this.imagePath);
if (image.height) {
var nFrames = 3;
var nDirections = 4;
diff --git a/src/js/stendhal/entity/Door.ts b/src/js/stendhal/entity/Door.ts
index b7577b5b8f..c677e4dbe4 100644
--- a/src/js/stendhal/entity/Door.ts
+++ b/src/js/stendhal/entity/Door.ts
@@ -13,8 +13,7 @@ import { RenderingContext2D } from "util/Types";
import { MenuItem } from "../action/MenuItem";
import { Portal } from "./Portal";
import { Paths } from "../data/Paths";
-
-declare var stendhal: any;
+import { singletons } from "../SingletonRepo";
export class Door extends Portal {
@@ -22,7 +21,7 @@ export class Door extends Portal {
override draw(ctx: RenderingContext2D) {
let imagePath = Paths.sprites + "/doors/" + this["class"] + ".png";
- let image = stendhal.data.sprites.get(imagePath);
+ let image = singletons.getSpriteStore().get(imagePath);
if (image.height) {
let height = image.height / 2;
let x = (this["x"] * 32) - ((image.width - 32) / 2);
diff --git a/src/js/stendhal/entity/Entity.ts b/src/js/stendhal/entity/Entity.ts
index 9f02254fd4..c61ffc3703 100644
--- a/src/js/stendhal/entity/Entity.ts
+++ b/src/js/stendhal/entity/Entity.ts
@@ -14,6 +14,7 @@ import { MenuItem } from "../action/MenuItem";
import { Chat } from "../util/Chat";
import { RPObject } from "./RPObject";
import { Paths } from "../data/Paths";
+import { singletons } from "../SingletonRepo";
declare var marauroa: any;
declare var stendhal: any;
@@ -220,7 +221,7 @@ export class Entity extends RPObject {
}
drawSpriteAt(ctx: RenderingContext2D, x: number, y: number) {
- var image = stendhal.data.sprites.get(this.sprite.filename);
+ var image = singletons.getSpriteStore().get(this.sprite.filename);
if (image.height) {
var offsetX = this.sprite.offsetX || 0;
var offsetY = this.sprite.offsetY || 0;
diff --git a/src/js/stendhal/entity/EntityRegistry.ts b/src/js/stendhal/entity/EntityRegistry.ts
index 535792b188..8b6ff19327 100644
--- a/src/js/stendhal/entity/EntityRegistry.ts
+++ b/src/js/stendhal/entity/EntityRegistry.ts
@@ -1,5 +1,5 @@
/***************************************************************************
- * (C) Copyright 2003-2023 - Stendhal *
+ * (C) Copyright 2003-2026 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
@@ -35,41 +35,47 @@ import { User } from "./User";
import { VisibleEntity } from "./VisibleEntity";
import { WalkBlocker } from "./WalkBlocker";
-marauroa.rpobjectFactory["area"] = InvisibleEntity;
-marauroa.rpobjectFactory["baby_dragon"] = DomesticAnimal;
-marauroa.rpobjectFactory["blackboard"] = Sign;
-marauroa.rpobjectFactory["blocktarget"] = InvisibleEntity;
-marauroa.rpobjectFactory["block"] = VisibleEntity;
-marauroa.rpobjectFactory["blood"] = Blood;
-marauroa.rpobjectFactory["cat"] = DomesticAnimal;
-marauroa.rpobjectFactory["chest"] = Chest;
-marauroa.rpobjectFactory["corpse"] = Corpse;
-marauroa.rpobjectFactory["creature"] = Creature;
-marauroa.rpobjectFactory["_default"] = UnknownEntity;
-marauroa.rpobjectFactory["domesticanimal"] = DomesticAnimal;
-marauroa.rpobjectFactory["door"] = Door;
-marauroa.rpobjectFactory["flyover"] = InvisibleEntity;
-marauroa.rpobjectFactory["food"] = Food;
-marauroa.rpobjectFactory["game_board"] = GameBoard;
-marauroa.rpobjectFactory["gate"] = Gate
-marauroa.rpobjectFactory["growing_entity_spawner"] = GrowingEntitySpawner;
-marauroa.rpobjectFactory["house_portal"] = Portal;
-marauroa.rpobjectFactory["invisible_entity"] = InvisibleEntity;
-marauroa.rpobjectFactory["item"] = Item;
-marauroa.rpobjectFactory["looped_sound_source"] = LoopedSoundSource;
-marauroa.rpobjectFactory["npc"] = NPC;
-marauroa.rpobjectFactory["plant_grower"] = VisibleEntity;
-marauroa.rpobjectFactory["player"] = Player;
-marauroa.rpobjectFactory["portal"] = Portal;
-marauroa.rpobjectFactory["rented_sign"] = Sign;
-marauroa.rpobjectFactory["sheep"] = DomesticAnimal;
-marauroa.rpobjectFactory["sign"] = Sign;
-marauroa.rpobjectFactory["tiled_entity"] = InvisibleEntity;
-marauroa.rpobjectFactory["training_dummy"] = TrainingDummy;
-marauroa.rpobjectFactory["unknown"] = UnknownEntity;
-marauroa.rpobjectFactory["useable_entity"] = UseableEntity;
-marauroa.rpobjectFactory["user"] = User;
-marauroa.rpobjectFactory["visible_entity"] = VisibleEntity;
-marauroa.rpobjectFactory["walkblocker"] = WalkBlocker;
-marauroa.rpobjectFactory["wall"] = InvisibleEntity;
-marauroa.rpobjectFactory["weather_entity"] = InvisibleEntity;
+export class EntityRegistry {
+
+ public init() {
+ marauroa.rpobjectFactory["area"] = InvisibleEntity;
+ marauroa.rpobjectFactory["baby_dragon"] = DomesticAnimal;
+ marauroa.rpobjectFactory["blackboard"] = Sign;
+ marauroa.rpobjectFactory["blocktarget"] = InvisibleEntity;
+ marauroa.rpobjectFactory["block"] = VisibleEntity;
+ marauroa.rpobjectFactory["blood"] = Blood;
+ marauroa.rpobjectFactory["cat"] = DomesticAnimal;
+ marauroa.rpobjectFactory["chest"] = Chest;
+ marauroa.rpobjectFactory["corpse"] = Corpse;
+ marauroa.rpobjectFactory["creature"] = Creature;
+ marauroa.rpobjectFactory["_default"] = UnknownEntity;
+ marauroa.rpobjectFactory["domesticanimal"] = DomesticAnimal;
+ marauroa.rpobjectFactory["door"] = Door;
+ marauroa.rpobjectFactory["flyover"] = InvisibleEntity;
+ marauroa.rpobjectFactory["food"] = Food;
+ marauroa.rpobjectFactory["game_board"] = GameBoard;
+ marauroa.rpobjectFactory["gate"] = Gate
+ marauroa.rpobjectFactory["growing_entity_spawner"] = GrowingEntitySpawner;
+ marauroa.rpobjectFactory["house_portal"] = Portal;
+ marauroa.rpobjectFactory["invisible_entity"] = InvisibleEntity;
+ marauroa.rpobjectFactory["item"] = Item;
+ marauroa.rpobjectFactory["looped_sound_source"] = LoopedSoundSource;
+ marauroa.rpobjectFactory["npc"] = NPC;
+ marauroa.rpobjectFactory["plant_grower"] = VisibleEntity;
+ marauroa.rpobjectFactory["player"] = Player;
+ marauroa.rpobjectFactory["portal"] = Portal;
+ marauroa.rpobjectFactory["rented_sign"] = Sign;
+ marauroa.rpobjectFactory["sheep"] = DomesticAnimal;
+ marauroa.rpobjectFactory["sign"] = Sign;
+ marauroa.rpobjectFactory["tiled_entity"] = InvisibleEntity;
+ marauroa.rpobjectFactory["training_dummy"] = TrainingDummy;
+ marauroa.rpobjectFactory["unknown"] = UnknownEntity;
+ marauroa.rpobjectFactory["useable_entity"] = UseableEntity;
+ marauroa.rpobjectFactory["user"] = User;
+ marauroa.rpobjectFactory["visible_entity"] = VisibleEntity;
+ marauroa.rpobjectFactory["walkblocker"] = WalkBlocker;
+ marauroa.rpobjectFactory["wall"] = InvisibleEntity;
+ marauroa.rpobjectFactory["weather_entity"] = InvisibleEntity;
+ }
+}
+
diff --git a/src/js/stendhal/entity/Food.ts b/src/js/stendhal/entity/Food.ts
index 5e862a5acd..f37ada3b26 100644
--- a/src/js/stendhal/entity/Food.ts
+++ b/src/js/stendhal/entity/Food.ts
@@ -12,9 +12,9 @@
import { RenderingContext2D } from "util/Types";
import { Entity } from "./Entity";
import { Paths } from "../data/Paths";
+import { singletons } from "../SingletonRepo";
declare var marauroa: any;
-declare var stendhal: any;
export class Food extends Entity {
@@ -30,7 +30,7 @@ export class Food extends Entity {
}
override draw(ctx: RenderingContext2D) {
- var image = stendhal.data.sprites.get(Paths.sprites + "/food.png");
+ var image = singletons.getSpriteStore().get(Paths.sprites + "/food.png");
if (image.height) {
var localX = this["x"] * 32;
var localY = this["y"] * 32;
diff --git a/src/js/stendhal/entity/Gate.ts b/src/js/stendhal/entity/Gate.ts
index c01efc68f0..7da2051a50 100644
--- a/src/js/stendhal/entity/Gate.ts
+++ b/src/js/stendhal/entity/Gate.ts
@@ -13,9 +13,9 @@ import { RenderingContext2D } from "util/Types";
import { MenuItem } from "../action/MenuItem";
import { Entity } from "./Entity";
import { Paths } from "../data/Paths";
+import { singletons } from "../SingletonRepo";
declare var marauroa: any;
-declare var stendhal: any;
export class Gate extends Entity {
@@ -49,7 +49,7 @@ export class Gate extends Entity {
override draw(ctx: RenderingContext2D) {
if (this._image == undefined) {
var filename = Paths.sprites + "/doors/" + this["image"] + "_" + this["orientation"] + ".png";
- this._image = stendhal.data.sprites.get(filename);
+ this._image = singletons.getSpriteStore().get(filename);
}
if (this._image.height) {
var xOffset = -32 * Math.floor(this._image.width / 32 / 2);
diff --git a/src/js/stendhal/entity/GrowingEntitySpawner.ts b/src/js/stendhal/entity/GrowingEntitySpawner.ts
index e05edc09c4..d625826582 100644
--- a/src/js/stendhal/entity/GrowingEntitySpawner.ts
+++ b/src/js/stendhal/entity/GrowingEntitySpawner.ts
@@ -13,9 +13,9 @@ import { RenderingContext2D } from "util/Types";
import { MenuItem } from "../action/MenuItem";
import { Entity } from "./Entity";
import { Paths } from "../data/Paths";
+import { singletons } from "../SingletonRepo";
declare var marauroa: any;
-declare var stendhal: any;
export class GrowingEntitySpawner extends Entity {
@@ -66,7 +66,7 @@ export class GrowingEntitySpawner extends Entity {
class_name = class_name.replace(" ", "_");
}
- var image = stendhal.data.sprites.get(Paths.sprites + "/" + class_name + ".png");
+ var image = singletons.getSpriteStore().get(Paths.sprites + "/" + class_name + ".png");
if (image.height) { // image.complete is true on missing image files
var count = parseInt(this["max_ripeness"], 10) + 1;
var drawHeight = image.height / count;
diff --git a/src/js/stendhal/entity/Item.ts b/src/js/stendhal/entity/Item.ts
index 0dd58ac5f6..27a338be8c 100644
--- a/src/js/stendhal/entity/Item.ts
+++ b/src/js/stendhal/entity/Item.ts
@@ -15,10 +15,9 @@ import { Entity } from "./Entity";
import { TextSprite } from "../sprite/TextSprite";
import { RenderingContext2D } from "util/Types";
import { Paths } from "../data/Paths";
+import { singletons } from "../SingletonRepo";
declare var marauroa: any;
-declare var stendhal: any;
-
export class Item extends Entity {
@@ -147,12 +146,12 @@ export class Item extends Entity {
}
public isAnimated(): boolean {
- if (!stendhal.data.sprites.get(this.sprite.filename).height) {
+ if (!singletons.getSpriteStore().get(this.sprite.filename).height) {
return false;
}
if (this.animated == null) {
// store animation state
- this.animated = (stendhal.data.sprites.get(this.sprite.filename).width / 32) > 1;
+ this.animated = (singletons.getSpriteStore().get(this.sprite.filename).width / 32) > 1;
}
return this.animated;
@@ -160,7 +159,7 @@ export class Item extends Entity {
private setXFrameIndex(idx: number) {
if (this.xFrames == null) {
- const img = stendhal.data.sprites.get(this.sprite.filename);
+ const img = singletons.getSpriteStore().get(this.sprite.filename);
this.xFrames = img.width / 32;
}
@@ -174,7 +173,7 @@ export class Item extends Entity {
private setYFrameIndex(idx: number) {
if (this.yFrames == null) {
- const img = stendhal.data.sprites.get(this.sprite.filename);
+ const img = singletons.getSpriteStore().get(this.sprite.filename);
this.yFrames = img.height / 32;
}
diff --git a/src/js/stendhal/entity/RPEntity.ts b/src/js/stendhal/entity/RPEntity.ts
index 9c9b7f5f40..a75cc876f0 100644
--- a/src/js/stendhal/entity/RPEntity.ts
+++ b/src/js/stendhal/entity/RPEntity.ts
@@ -246,7 +246,7 @@ export class RPEntity extends ActiveEntity {
if (stendhal.config.getBoolean("effect.shadows") && this.castsShadow()) {
// dressed entities should use 48x64 sprites
// FIXME: this will not display correctly for horse outfit
- const shadow = stendhal.data.sprites.getShadow("48x64");
+ const shadow = singletons.getSpriteStore().getShadow("48x64");
if (shadow && shadow.complete && shadow.height) {
// draw shadow below other layers
@@ -322,9 +322,9 @@ export class RPEntity extends ActiveEntity {
colorname = part;
}
if (typeof(colors) !== "undefined" && (typeof(colors[colorname]) !== "undefined")) {
- return stendhal.data.sprites.getFiltered(filename, "trueColor", colors[colorname]);
+ return singletons.getSpriteStore().getFiltered(filename, "trueColor", colors[colorname]);
} else {
- return stendhal.data.sprites.get(filename);
+ return singletons.getSpriteStore().get(filename);
}
}
@@ -369,13 +369,13 @@ export class RPEntity extends ActiveEntity {
}
// check for safe image
- if (!stendhal.config.getBoolean("effect.blood") && stendhal.data.sprites.hasSafeImage(filename)) {
+ if (!stendhal.config.getBoolean("effect.blood") && singletons.getSpriteStore().hasSafeImage(filename)) {
filename = filename + "-safe.png";
} else {
filename = filename + ".png";
}
- let image = stendhal.data.sprites.get(filename);
+ let image = singletons.getSpriteStore().get(filename);
if (stendhal.config.getBoolean("effect.shadows") && this.castsShadow()) {
// check for configured shadow style
@@ -385,7 +385,7 @@ export class RPEntity extends ActiveEntity {
shadow_style = (image.width / 3) + "x" + (image.height / 4);
}
- const shadow = stendhal.data.sprites.getShadow(shadow_style);
+ const shadow = singletons.getSpriteStore().getShadow(shadow_style);
// draw shadow first
if (typeof(shadow) !== "undefined") {
@@ -405,14 +405,14 @@ export class RPEntity extends ActiveEntity {
ctx.drawImage(icon, frame * xdim, 0, xdim, ydim, x, y, xdim, ydim);
}
function drawAnimatedIcon(iconPath: string, delay: number, x: number, y: number, fWidth?: number) {
- var icon = stendhal.data.sprites.get(iconPath);
+ var icon = singletons.getSpriteStore().get(iconPath);
var dimH = icon.height;
var dimW = typeof(fWidth) !== "undefined" ? fWidth : dimH;
var nFrames = icon.width / dimW;
_drawAnimatedIcon(icon, delay, nFrames, dimW, dimH, x, y);
}
function drawAnimatedIconWithFrames(iconPath: string, nFrames: number, delay: number, x: number, y: number) {
- var icon = stendhal.data.sprites.get(iconPath);
+ var icon = singletons.getSpriteStore().get(iconPath);
var ydim = icon.height;
var xdim = icon.width / nFrames;
_drawAnimatedIcon(icon, delay, nFrames, xdim, ydim, x, y);
@@ -421,19 +421,19 @@ export class RPEntity extends ActiveEntity {
var x = this["_x"] * 32 - 10;
var y = (this["_y"] + 1) * 32;
if (this.hasOwnProperty("choking")) {
- ctx.drawImage(stendhal.data.sprites.get(Paths.sprites + "/ideas/choking.png"), x, y - 10);
+ ctx.drawImage(singletons.getSpriteStore().get(Paths.sprites + "/ideas/choking.png"), x, y - 10);
} else if (this.hasOwnProperty("eating")) {
- ctx.drawImage(stendhal.data.sprites.get(Paths.sprites + "/ideas/eat.png"), x, y - 10);
+ ctx.drawImage(singletons.getSpriteStore().get(Paths.sprites + "/ideas/eat.png"), x, y - 10);
}
// NPC and pet idea icons
if (this.hasOwnProperty("idea")) {
const idea = Paths.sprites + "/ideas/" + this["idea"] + ".png";
- const ani = stendhal.data.sprites.animations.idea[this["idea"]];
+ const ani = singletons.getSpriteStore().animations.idea[this["idea"]];
if (ani) {
drawAnimatedIcon(idea, ani.delay, x + ani.offsetX * this["width"],
y - this["drawHeight"] + ani.offsetY);
} else {
- ctx.drawImage(stendhal.data.sprites.get(idea), x + 32 * this["width"],
+ ctx.drawImage(singletons.getSpriteStore().get(idea), x + 32 * this["width"],
y - this["drawHeight"]);
}
}
@@ -459,15 +459,15 @@ export class RPEntity extends ActiveEntity {
// NPC job icons
let nextX = x;
if (this.hasOwnProperty("job_healer")) {
- ctx.drawImage(stendhal.data.sprites.get(Paths.sprites + "/status/healer.png"), nextX, y - 10);
+ ctx.drawImage(singletons.getSpriteStore().get(Paths.sprites + "/status/healer.png"), nextX, y - 10);
nextX += 12;
}
if (this.hasOwnProperty("job_merchant")) {
- ctx.drawImage(stendhal.data.sprites.get(Paths.sprites + "/status/merchant.png"), nextX, y - 10);
+ ctx.drawImage(singletons.getSpriteStore().get(Paths.sprites + "/status/merchant.png"), nextX, y - 10);
nextX += 12;
}
if (this.hasOwnProperty("job_producer")) {
- ctx.drawImage(stendhal.data.sprites.get(Paths.sprites + "/status/producer.png"), nextX, y - 16);
+ ctx.drawImage(singletons.getSpriteStore().get(Paths.sprites + "/status/producer.png"), nextX, y - 16);
nextX += 16;
}
}
@@ -784,7 +784,7 @@ export class RPEntity extends ActiveEntity {
createResultIcon(imagePath: string) {
return {
initTime: Date.now(),
- image: stendhal.data.sprites.get(imagePath),
+ image: singletons.getSpriteStore().get(imagePath),
draw: function(ctx: RenderingContext2D, x: number, y: number) {
ctx.drawImage(this.image, x, y);
return (Date.now() - this.initTime > 1200);
@@ -815,12 +815,12 @@ export class RPEntity extends ActiveEntity {
const imagePath = Nature.VALUES[nature].getWeaponPath(weapon);
/*
if (weapon.startsWith("blade_strike")) {
- this.attackSprite = new BarehandAttackSprite(this, stendhal.data.sprites.get(imagePath));
+ this.attackSprite = new BarehandAttackSprite(this, singletons.getSpriteStore().get(imagePath));
} else {
this.attackSprite = new MeleeAttackSprite(this, imagePath);
}
*/
- this.attackSprite = new BarehandAttackSprite(this, stendhal.data.sprites.get(imagePath));
+ this.attackSprite = new BarehandAttackSprite(this, singletons.getSpriteStore().get(imagePath));
}
}
diff --git a/src/js/stendhal/entity/Sign.ts b/src/js/stendhal/entity/Sign.ts
index cde383aee3..a7640cb340 100644
--- a/src/js/stendhal/entity/Sign.ts
+++ b/src/js/stendhal/entity/Sign.ts
@@ -13,6 +13,7 @@ import { Entity } from "./Entity";
import { ActivityIndicatorSprite } from "../sprite/ActivityIndicatorSprite";
import { RenderingContext2D } from "util/Types";
import { Paths } from "../data/Paths";
+import { singletons } from "../SingletonRepo";
declare var stendhal: any;
@@ -38,7 +39,7 @@ export class Sign extends Entity {
if (!this.imagePath) {
this.imagePath = Paths.sprites + "/signs/" + this["class"] + ".png";
}
- var image = stendhal.data.sprites.get(this.imagePath);
+ var image = singletons.getSpriteStore().get(this.imagePath);
if (image.height) {
var localX = this["x"] * 32;
var localY = this["y"] * 32;
diff --git a/src/js/stendhal/event/ShowItemListEvent.ts b/src/js/stendhal/event/ShowItemListEvent.ts
index 9bd77e7089..9744738e6d 100644
--- a/src/js/stendhal/event/ShowItemListEvent.ts
+++ b/src/js/stendhal/event/ShowItemListEvent.ts
@@ -16,6 +16,7 @@ import { ui } from "../ui/UI";
import { DialogContentComponent } from "../ui/toolkit/DialogContentComponent";
import { Chat } from "../util/Chat";
import { Paths } from "../data/Paths";
+import { singletons } from "../SingletonRepo";
declare var stendhal: any;
@@ -94,7 +95,7 @@ export class ShowItemListEvent extends RPEvent {
// Item (image)
const itemCell = document.createElement("td");
itemCell.appendChild(
- stendhal.data.sprites.get(
+ singletons.getSpriteStore().get(
Paths.sprites + "/items/" + i.img
)
);
diff --git a/src/js/stendhal/sprite/AchievementBanner.ts b/src/js/stendhal/sprite/AchievementBanner.ts
index 12d2427b8f..5123635aeb 100644
--- a/src/js/stendhal/sprite/AchievementBanner.ts
+++ b/src/js/stendhal/sprite/AchievementBanner.ts
@@ -13,6 +13,7 @@ import { TextBubble } from "./TextBubble";
import { BackgroundPainter } from "../util/BackgroundPainter";
import { RenderingContext2D } from "util/Types";
import { Paths } from "../data/Paths";
+import { singletons } from "../SingletonRepo";
declare const stendhal: any;
@@ -34,9 +35,9 @@ export class AchievementBanner extends TextBubble {
constructor(cat: string, title: string, desc: string) {
super(desc);
this.title = title;
- const bg = stendhal.data.sprites.get(Paths.gui + "/banner_background.png");
+ const bg = singletons.getSpriteStore().get(Paths.gui + "/banner_background.png");
this.banner = new BackgroundPainter(bg);
- this.icon = stendhal.data.sprites.get(Paths.achievements
+ this.icon = singletons.getSpriteStore().get(Paths.achievements
+ "/" + cat.toLowerCase() + ".png");
/* keep achievements on the screen a bit longer since they
diff --git a/src/js/stendhal/sprite/ActivityIndicatorSprite.ts b/src/js/stendhal/sprite/ActivityIndicatorSprite.ts
index 14972fb703..a81a3358bb 100644
--- a/src/js/stendhal/sprite/ActivityIndicatorSprite.ts
+++ b/src/js/stendhal/sprite/ActivityIndicatorSprite.ts
@@ -11,6 +11,7 @@
import { RenderingContext2D } from "util/Types";
import { Paths } from "../data/Paths";
+import { singletons } from "../SingletonRepo";
declare var stendhal: any;
@@ -20,7 +21,7 @@ declare var stendhal: any;
*/
export class ActivityIndicatorSprite {
- private static readonly img = stendhal.data.sprites.get(Paths.sprites + "/ideas/activity.png");
+ private static readonly img = singletons.getSpriteStore().get(Paths.sprites + "/ideas/activity.png");
private frameIdx = 0;
private lastFrameUpdate = 0;
diff --git a/src/js/stendhal/sprite/NotificationBubble.ts b/src/js/stendhal/sprite/NotificationBubble.ts
index 7c4e6d7d83..aa81d4b9d7 100644
--- a/src/js/stendhal/sprite/NotificationBubble.ts
+++ b/src/js/stendhal/sprite/NotificationBubble.ts
@@ -20,6 +20,7 @@ import { NotificationType } from "../util/NotificationType";
import { Speech } from "../util/Speech";
import { RenderingContext2D } from "util/Types";
import { Paths } from "../data/Paths";
+import { singletons } from "../SingletonRepo";
export class NotificationBubble extends TextBubble {
@@ -134,10 +135,10 @@ export class NotificationBubble extends TextBubble {
* Loads a profile image to be drawn with text.
*/
private loadProfileSprite() {
- const img = stendhal.data.sprites.get(Paths.sprites
+ const img = singletons.getSpriteStore().get(Paths.sprites
+ "/npc/" + this.profileName + ".png");
if (img.complete && img.height) {
- this.profile = stendhal.data.sprites.getAreaOf(img, 48, 48, 48, 128);
+ this.profile = singletons.getSpriteStore().getAreaOf(img, 48, 48, 48, 128);
}
}
}
diff --git a/src/js/stendhal/sprite/action/MeleeAttackSprite.ts b/src/js/stendhal/sprite/action/MeleeAttackSprite.ts
index 8cdff51b67..a3a586325e 100644
--- a/src/js/stendhal/sprite/action/MeleeAttackSprite.ts
+++ b/src/js/stendhal/sprite/action/MeleeAttackSprite.ts
@@ -10,12 +10,11 @@
* *
***************************************************************************/
-declare var stendhal: any;
-
import { AttackSprite } from "./AttackSprite";
import { RPEntity } from "../../entity/RPEntity";
import { Direction } from "../../util/Direction";
import { RenderingContext2D } from "util/Types";
+import { singletons } from "../../SingletonRepo";
export class MeleeAttackSprite extends AttackSprite {
@@ -46,10 +45,10 @@ export class MeleeAttackSprite extends AttackSprite {
const rot = 90 * (this.dir.val - 1);
imagePath = imagePath.replace(/\.png$/, "");
this.frames = [
- //~ stendhal.data.sprites.getRotated(imagePath + "_rot45.png", rot-90),
+ //~ singletons.getSpriteStore().getRotated(imagePath + "_rot45.png", rot-90),
undefined,
- stendhal.data.sprites.getRotated(imagePath + ".png", rot),
- //~ stendhal.data.sprites.getRotated(imagePath + "_rot45.png", rot)
+ singletons.getSpriteStore().getRotated(imagePath + ".png", rot),
+ //~ singletons.getSpriteStore().getRotated(imagePath + "_rot45.png", rot)
undefined
];
if (this.frames[1].complete) {
diff --git a/src/js/stendhal/sprite/action/RangedAttackSprite.ts b/src/js/stendhal/sprite/action/RangedAttackSprite.ts
index 4323a90384..119dd59cf6 100644
--- a/src/js/stendhal/sprite/action/RangedAttackSprite.ts
+++ b/src/js/stendhal/sprite/action/RangedAttackSprite.ts
@@ -14,6 +14,7 @@ import { AttackSprite } from "./AttackSprite";
import { RPEntity } from "../../entity/RPEntity";
import { RenderingContext2D } from "util/Types";
import { Paths } from "../../data/Paths";
+import { singletons } from "../../SingletonRepo";
declare var stendhal: any;
@@ -24,7 +25,7 @@ export class RangedAttackSprite extends AttackSprite {
private readonly targetX: number;
private readonly targetY: number;
private readonly color: string;
- private readonly image = stendhal.data.sprites.get(Paths.sprites + "/combat/ranged.png");
+ private readonly image = singletons.getSpriteStore().get(Paths.sprites + "/combat/ranged.png");
private readonly weapon?: string;
diff --git a/src/js/stendhal/ui/ViewPort.ts b/src/js/stendhal/ui/ViewPort.ts
index 3ac22fa449..758db5ddf5 100644
--- a/src/js/stendhal/ui/ViewPort.ts
+++ b/src/js/stendhal/ui/ViewPort.ts
@@ -654,14 +654,14 @@ export class ViewPort {
var img = undefined;
let heldObject: HeldObject;
if (draggedEntity && draggedEntity.type === "item") {
- img = stendhal.data.sprites.getAreaOf(stendhal.data.sprites.get(draggedEntity.sprite.filename), 32, 32);
+ img = singletons.getSpriteStore().getAreaOf(singletons.getSpriteStore().get(draggedEntity.sprite.filename), 32, 32);
heldObject = {
path: draggedEntity.getIdPath(),
zone: marauroa.currentZoneName,
quantity: draggedEntity.hasOwnProperty("quantity") ? draggedEntity["quantity"] : 1
}
} else if (draggedEntity && draggedEntity.type === "corpse") {
- img = stendhal.data.sprites.get(draggedEntity.sprite.filename);
+ img = singletons.getSpriteStore().get(draggedEntity.sprite.filename);
heldObject = {
path: draggedEntity.getIdPath(),
zone: marauroa.currentZoneName,
diff --git a/src/js/stendhal/ui/component/ChatInputComponent.ts b/src/js/stendhal/ui/component/ChatInputComponent.ts
index df8c7254ae..8229ca0720 100644
--- a/src/js/stendhal/ui/component/ChatInputComponent.ts
+++ b/src/js/stendhal/ui/component/ChatInputComponent.ts
@@ -87,7 +87,7 @@ export class ChatInputComponent extends Component {
btn_emoji.innerText = "☺";
} else {
// set image for emoji button
- btn_emoji.appendChild(stendhal.data.sprites.get(Paths.sprites + "/emoji/smile.png").cloneNode());
+ btn_emoji.appendChild(singletons.getSpriteStore().get(Paths.sprites + "/emoji/smile.png").cloneNode());
}
}
diff --git a/src/js/stendhal/ui/component/ItemContainerImplementation.ts b/src/js/stendhal/ui/component/ItemContainerImplementation.ts
index 5939106f3b..d22035e5d1 100644
--- a/src/js/stendhal/ui/component/ItemContainerImplementation.ts
+++ b/src/js/stendhal/ui/component/ItemContainerImplementation.ts
@@ -132,7 +132,7 @@ export class ItemContainerImplementation {
}
e.style.backgroundImage = "url("
- + stendhal.data.sprites.checkPath(Paths.sprites
+ + singletons.getSpriteStore().checkPath(Paths.sprites
+ "/items/" + o["class"] + "/" + o["subclass"] + ".png")
+ ")";
e.style.backgroundPosition = (xOffset+1) + "px " + (yOffset+1) + "px";
@@ -191,7 +191,7 @@ export class ItemContainerImplementation {
quantity: item.hasOwnProperty("quantity") ? item["quantity"] : 1
};
- const img = stendhal.data.sprites.getAreaOf(stendhal.data.sprites.get(item.sprite.filename), 32, 32);
+ const img = singletons.getSpriteStore().getAreaOf(singletons.getSpriteStore().get(item.sprite.filename), 32, 32);
if (event instanceof DragEvent && event.dataTransfer) {
stendhal.ui.heldObject = heldObject;
event.dataTransfer.setDragImage(img, 0, 0);
diff --git a/src/js/stendhal/ui/component/StatusesListComponent.ts b/src/js/stendhal/ui/component/StatusesListComponent.ts
index 0f97733fbe..f2676e29de 100644
--- a/src/js/stendhal/ui/component/StatusesListComponent.ts
+++ b/src/js/stendhal/ui/component/StatusesListComponent.ts
@@ -13,8 +13,7 @@ import { Component } from "../toolkit/Component";
import { User } from "../../entity/User";
import { Paths } from "../../data/Paths";
-
-declare var stendhal: any;
+import { singletons } from "../../SingletonRepo";
export class StatusesListComponent extends Component {
@@ -70,7 +69,7 @@ export class StatusesListComponent extends Component {
} else {
iconPath = Paths.sprites + "/status/panel/" + id + ".png";
}
- const icon = stendhal.data.sprites.get(iconPath);
+ const icon = singletons.getSpriteStore().get(iconPath);
icon.className = "status-icon";
this.componentElement.appendChild(icon);
}
diff --git a/src/js/stendhal/ui/dialog/EmojiMapDialog.ts b/src/js/stendhal/ui/dialog/EmojiMapDialog.ts
index 37044a9c77..046cef8264 100644
--- a/src/js/stendhal/ui/dialog/EmojiMapDialog.ts
+++ b/src/js/stendhal/ui/dialog/EmojiMapDialog.ts
@@ -84,7 +84,7 @@ export class EmojiMapDialog extends DialogContentComponent {
const button = document.createElement("button");
button.classList.add("shortcut-button", "emoji-text");
if (!this.sysEmojis) {
- button.appendChild(stendhal.data.sprites.get(Paths.sprites + "/emoji/" + emoji + ".png").cloneNode());
+ button.appendChild(singletons.getSpriteStore().get(Paths.sprites + "/emoji/" + emoji + ".png").cloneNode());
} else {
button.innerText = emoji;
}
diff --git a/src/js/stendhal/ui/dialog/outfit/OutfitColorSelector.ts b/src/js/stendhal/ui/dialog/outfit/OutfitColorSelector.ts
index 200d38d204..71dd029e7e 100644
--- a/src/js/stendhal/ui/dialog/outfit/OutfitColorSelector.ts
+++ b/src/js/stendhal/ui/dialog/outfit/OutfitColorSelector.ts
@@ -9,10 +9,9 @@
* *
***************************************************************************/
+import { singletons } from "../../../SingletonRepo";
import { Canvas, RenderingContext2D } from "util/Types";
-declare var stendhal: any;
-
export class OutfitColorSelector {
protected ctx: RenderingContext2D;
private gradCtx: RenderingContext2D;
@@ -54,15 +53,15 @@ export class OutfitColorSelector {
if (this.enabled) {
const hsl = [this._x / this.baseImage.width, 1 - this._y / this.baseImage.height,
this.hX / this.baseImage.width];
- const rgbArray = stendhal.data.sprites.filter.hsl2rgb(hsl);
- return stendhal.data.sprites.filter.mergergb(rgbArray);
+ const rgbArray = singletons.getSpriteStore().filter.hsl2rgb(hsl);
+ return singletons.getSpriteStore().filter.mergergb(rgbArray);
}
return null;
}
set color(rgb) {
if (rgb != null) {
- const hsl = stendhal.data.sprites.filter.rgb2hsl(stendhal.data.sprites.filter.splitrgb(rgb));
+ const hsl = singletons.getSpriteStore().filter.rgb2hsl(singletons.getSpriteStore().filter.splitrgb(rgb));
this._x = hsl[0] * this.baseImage.width;
this._y = (1 - hsl[1]) * this.baseImage.height;
this.hX = hsl[2] * this.baseImage.width;
@@ -80,7 +79,7 @@ export class OutfitColorSelector {
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
const hsl = [x / width, 1 - y / height, 0.5];
- const rgb = stendhal.data.sprites.filter.hsl2rgb(hsl);
+ const rgb = singletons.getSpriteStore().filter.hsl2rgb(hsl);
ctx.fillStyle = this._rgbToCssString(rgb);
ctx.fillRect(x, y, 1, 1);
}
@@ -142,9 +141,9 @@ export class OutfitColorSelector {
const hslLeft = [this._x / width, 1 - this._y / height, 0.08];
const hslMiddle = [this._x / width, 1 - this._y / height, 0.5];
const hslRight = [this._x / width, 1 - this._y / height, 0.92];
- const rgbLeft = stendhal.data.sprites.filter.hsl2rgb(hslLeft);
- const rgbMiddle = stendhal.data.sprites.filter.hsl2rgb(hslMiddle);
- const rgbRight = stendhal.data.sprites.filter.hsl2rgb(hslRight);
+ const rgbLeft = singletons.getSpriteStore().filter.hsl2rgb(hslLeft);
+ const rgbMiddle = singletons.getSpriteStore().filter.hsl2rgb(hslMiddle);
+ const rgbRight = singletons.getSpriteStore().filter.hsl2rgb(hslRight);
return [rgbLeft, rgbMiddle, rgbRight];
}
diff --git a/src/js/stendhal/ui/dialog/outfit/OutfitPaletteColorSelector.ts b/src/js/stendhal/ui/dialog/outfit/OutfitPaletteColorSelector.ts
index fc21a76076..e8cdd76b70 100644
--- a/src/js/stendhal/ui/dialog/outfit/OutfitPaletteColorSelector.ts
+++ b/src/js/stendhal/ui/dialog/outfit/OutfitPaletteColorSelector.ts
@@ -9,9 +9,9 @@
* *
***************************************************************************/
+import { singletons } from "../../../SingletonRepo";
import { OutfitColorSelector } from "./OutfitColorSelector";
-declare var stendhal: any;
export class OutfitPaletteColorSelector extends OutfitColorSelector {
private _blockWidth: number;
@@ -32,8 +32,8 @@ export class OutfitPaletteColorSelector extends OutfitColorSelector {
if (this.enabled) {
const hs = this._hsMap[Math.floor(this._x / this._blockWidth)][Math.floor(this._y / this._blockHeight)];
const hsl = [hs[0], hs[1], this.hX / this.baseImage.width];
- const rgbArray = stendhal.data.sprites.filter.hsl2rgb(hsl);
- return stendhal.data.sprites.filter.mergergb(rgbArray);
+ const rgbArray = singletons.getSpriteStore().filter.hsl2rgb(hsl);
+ return singletons.getSpriteStore().filter.mergergb(rgbArray);
}
return null;
}
@@ -41,7 +41,7 @@ export class OutfitPaletteColorSelector extends OutfitColorSelector {
override set color(rgb) {
if (rgb != null) {
this.enabled = true;
- const hsl = stendhal.data.sprites.filter.rgb2hsl(stendhal.data.sprites.filter.splitrgb(rgb));
+ const hsl = singletons.getSpriteStore().filter.rgb2hsl(singletons.getSpriteStore().filter.splitrgb(rgb));
this.hX = hsl[2] * this.baseImage.width;
let bestDelta = Number.MAX_VALUE;
for (let i = 0; i < 4; i++) {
@@ -72,7 +72,7 @@ export class OutfitPaletteColorSelector extends OutfitColorSelector {
const hue = hues[j];
const sat = saturations[i];
hsMap[i].push([hue, sat]);
- const color = stendhal.data.sprites.filter.hsl2rgb([hue, sat, 0.5]);
+ const color = singletons.getSpriteStore().filter.hsl2rgb([hue, sat, 0.5]);
colors[i].push(color);
}
}
@@ -105,9 +105,9 @@ export class OutfitPaletteColorSelector extends OutfitColorSelector {
const hslLeft = [hs[0], hs[1], 0.08];
const hslMiddle = [hs[0], hs[1], 0.5];
const hslRight = [hs[0], hs[1], 0.92];
- const rgbLeft = stendhal.data.sprites.filter.hsl2rgb(hslLeft);
- const rgbMiddle = stendhal.data.sprites.filter.hsl2rgb(hslMiddle);
- const rgbRight = stendhal.data.sprites.filter.hsl2rgb(hslRight);
+ const rgbLeft = singletons.getSpriteStore().filter.hsl2rgb(hslLeft);
+ const rgbMiddle = singletons.getSpriteStore().filter.hsl2rgb(hslMiddle);
+ const rgbRight = singletons.getSpriteStore().filter.hsl2rgb(hslRight);
return [rgbLeft, rgbMiddle, rgbRight];
}
diff --git a/src/js/stendhal/ui/dialog/outfit/OutfitPartSelector.ts b/src/js/stendhal/ui/dialog/outfit/OutfitPartSelector.ts
index 5c403739e2..c1095a3503 100644
--- a/src/js/stendhal/ui/dialog/outfit/OutfitPartSelector.ts
+++ b/src/js/stendhal/ui/dialog/outfit/OutfitPartSelector.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { singletons } from "../../../SingletonRepo";
import { Paths } from "../../../data/Paths";
import { RenderingContext2D } from "util/Types";
@@ -109,8 +110,8 @@ export class OutfitPartSelector {
const fname = Paths.sprites + "/outfit/" + part + "/" + this.indexString(index) + suffix;
if (color != null) {
- return stendhal.data.sprites.getFilteredWithPromise(fname, "trueColor", color);
+ return singletons.getSpriteStore().getFilteredWithPromise(fname, "trueColor", color);
}
- return stendhal.data.sprites.getWithPromise(fname);
+ return singletons.getSpriteStore().getWithPromise(fname);
}
}
diff --git a/src/js/stendhal/util/WeatherRenderer.ts b/src/js/stendhal/util/WeatherRenderer.ts
index a35a3dd137..abf1ff5225 100644
--- a/src/js/stendhal/util/WeatherRenderer.ts
+++ b/src/js/stendhal/util/WeatherRenderer.ts
@@ -9,6 +9,7 @@
* *
***************************************************************************/
+import { singletons } from "../SingletonRepo";
import { Paths } from "../data/Paths";
import { TileStore } from "../data/TileStore";
@@ -110,7 +111,7 @@ export class WeatherRenderer {
this.sprite = undefined;
} else {
const img = Paths.weather + "/" + weather + ".png";
- this.sprite = stendhal.data.sprites.get(img);
+ this.sprite = singletons.getSpriteStore().get(img);
/* FIXME:
* "TypeError: $stendhal$$.data.$tileset$.$weatherAnimationMap$
* is undefined". TileStore.weatherMap is not always loaded
From 37d3e1c84dfc219fd151beb18cf09bdc454755f0 Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Fri, 2 Jan 2026 04:11:11 +0100
Subject: [PATCH 042/162] fixed init order and wrong "this".
---
src/js/stendhal/Client.ts | 21 +++++++++++----------
src/js/stendhal/main.ts | 22 ++++++++++++++++++----
2 files changed, 29 insertions(+), 14 deletions(-)
diff --git a/src/js/stendhal/Client.ts b/src/js/stendhal/Client.ts
index f4ccdae9cd..a466f40428 100644
--- a/src/js/stendhal/Client.ts
+++ b/src/js/stendhal/Client.ts
@@ -19,6 +19,7 @@ import { Paths } from "./data/Paths";
import { Color } from "./data/color/Color";
+import { EntityRegistry } from "./entity/EntityRegistry";
import { Ground } from "./entity/Ground";
import { RPObject } from "./entity/RPObject";
@@ -95,10 +96,10 @@ export class Client {
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();
@@ -263,7 +264,7 @@ export class Client {
if (!Client.instance.unloading) {
Chat.logH("error", "Disconnected from server.");
}
- };
+ }.bind(this);
marauroa.clientFramework.onLoginRequired = function(config: Record) {
if (config["client_login_url"]) {
@@ -281,25 +282,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,7 +332,7 @@ export class Client {
}
items[i]["ack"] = true;
}
- };
+ }.bind(this);
marauroa.clientFramework.onTransfer = function(items: any) {
var data = {} as any;
@@ -459,7 +460,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";
}
/**
@@ -518,7 +519,7 @@ export class Client {
*/
onMouseEnter(e: MouseEvent) {
// use Stendhal's built-in cursor for entire page
- (e.target as HTMLElement).style.cursor = "url(" + stendhal.paths.sprites + "/cursor/normal.png) 1 3, auto";
+ (e.target as HTMLElement).style.cursor = "url(" + Paths.sprites + "/cursor/normal.png) 1 3, auto";
}
/**
diff --git a/src/js/stendhal/main.ts b/src/js/stendhal/main.ts
index ac2046f89e..27b51971ac 100644
--- a/src/js/stendhal/main.ts
+++ b/src/js/stendhal/main.ts
@@ -1,5 +1,5 @@
/***************************************************************************
- * (C) Copyright 2003-2024 - Stendhal *
+ * (C) Copyright 2003-2026 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
@@ -9,8 +9,22 @@
* *
***************************************************************************/
+
import { Client } from "./Client";
+/*
+declare var require: any;
+require("marauroa/marauroa");
+require("marauroa/client-framework");
+require("marauroa/message-factory");
+require("marauroa/perception");
+require("marauroa/rpfactory");
+require("marauroa/inflate");
+require("marauroa/deserializer");
+require("marauroa/build"); // TODO
+require("./data/sha3");
+*/
+
declare var stendhal: any;
@@ -30,6 +44,6 @@ function initGlobals() {
stendhal.main.init();
- document.addEventListener('DOMContentLoaded', stendhal.main.startup);
- window.addEventListener('error', stendhal.main.onerror);
-})();
+ document.addEventListener('DOMContentLoaded', () => stendhal.main.startup());
+ window.addEventListener('error', (error: ErrorEvent) => stendhal.main.onError(error));
+})();
\ No newline at end of file
From da4378d30c979d9d08d6aba183958f9ac47d6e5d Mon Sep 17 00:00:00 2001
From: Hendrik Brummermann
Date: Tue, 6 Jan 2026 21:43:35 +0100
Subject: [PATCH 043/162] updated marauroa
---
libs/marauroa.jar | Bin 477179 -> 477245 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
diff --git a/libs/marauroa.jar b/libs/marauroa.jar
index 2ab331f0b554132f6b480961fdba94da58f8dc9e..85450462881a7bac13b753b0bcdf37eac5a9225f 100644
GIT binary patch
delta 23443
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
InRd7T18Wi|A^-pY
delta 23341
zcmZr&1z1$w)}9`^ySpR>=@bD4Bt$7uKon3~I){=_K|(pCgdj+V(g;Y2w16~7cZW3p
z!0>(ddY_-id02bxcjaDtoqf)5tcjpMdP5&MLK8_ti`DeiR=?vYct28x8+G#gR8d*!
zxrj4SRevr8vgyQ96Yuk_iW~JPJBs>E?>B!C;gif2gH@IU+%C=90o^Tfi+T*nkFxuJ
zUYjw56?{|T@I35m^5a=Evg#_!8Ds32OOx(=*`l*T823u8h|rUs
zGOm-YK;I3xmKqd>0|H@zKt;1`2v{@PpAvjzqbi2#*Y?83Q1|ZhDPyRh)NO2na~vIb
zgrk@r1lmZ74$fgjxw|IZ+*>$1B-5V{qfLuq`K(5D^w^4b|^Ulk!ab~fg%}a_>9ga3u=@U@8r@X9r@+S3A+PHC;17+C7Vv$ilE0T8i)j;n|
zb7)q^Sbkaje4yct6Q#r(xULCWHj$PZ3vv%shM%npd@O^Ili4XID;r+TG`#-dUT$9I
z%G@7U)YIDD2xW~F@4?FYZ@uHZ(lpI?9aEfnL+ASjj6!j))Ld7U#a8{SXH~b#_R87v
zsS~)1b@fWvqw)ciVKKE|w57bkkCZjG?@H-;V)`SXpLSpBZe4|iwftDp3q2X2Zs`4?
z$NBTL3mCj#Ai76?Y`w5xt70y4AfsQUAsBC~NO!vCR7N~nGf1>Bo`@^hQEwBlk@79s?tmD%=)|`G9
z-6zQr0~_tPerZ#eP`|a+&qx%dNIPWKP6xRxn$_?m>xEqvdZ%5cv#&3qR!KPhwuq_T
z_g&Ln*XEYiBr23q^>BbM-9+e`9GgEykCjSC`~WJo^!pj<=bS1fIJwrQ$Vg$Y$+byb9qn(|9~SUjxzWkMgx|6Em$MZ{eb7iwpvgd&%
zoVy$UgI_scgOzpZrjaXcF1BLCC-;|~TQ(p)cHO*HHPYh~rgt83G9b|pfh86_)>Uby
zBtQw`HQ4obn^ZL9bG2cIy@YY+U_!cjg+#EwZ|!$xcK#G{*ZANd+;5@3T4`+RT}rIl
zw14yW7xW^t=V_Bp?UD=3CxG&(9=MK^J@;D6nW$Wjf%+?%^7(|7igsy%|rY{+L
zchl0Ep_MjxZGC;+C8ipDyyjv)k!MLKB~0@H->%_FIqEC6Ky4a-Pc@$(c=ihc*Nt>9
zKkqW(T-C3+#JLupzWTAa;^`Y+#2N!CU?yg)HxR9zjYO~{_$C2=MDyEKn_{e$DkZOq
zVYadlcRZDZ?Yr?3n@2;**_ZN}9}R|b^HK!ffEBmdF^sVloFF4~DF?p{uPrXJ_GnUS
zCmxGrn4oyqU=h9vKJrJ5vo+W~ZlYz%IXuYkqV4hzs;tse*OJ*pFJ7<@-P9Rxj%e2
zS1GT||LSQTmcAvrxv07KeF{f6^t+5%YBf?To@UYVAV!Cxv_J%
zwzNiB+E`s^&FuR;;o5qIxjg;a3%jG~f}`Iow`G89%J?`LncJHIRH3Y!uC2!RC0qHHM9i15R8K
zWtd3zgVh3lPY5&Ocs(?EFfun3XTcXYzPmA1pcMQ0e(oFUqS_!I8-1YkX$M$fD$}K{
zQo2Ou)v}>Ycm_!i%pOjKL~`K5OI6+Q*{4?h*@
z3>ZapKlM!@lS-{$meQ59IwsqOF^x=-!_WH~v(puMG3J7?pj)vo@?u!jZ~AEpX!8J(K;0V8BaiC9
z+|8Btvg3G}K?R*sS0A6X>zz|WKJ+MW&o~aBOi8x{tGj~NMy
zbqTwA4==lnjL|?cayh+M3bsrjEmQ6rfjorLU9ILH!9%GtfziP!q+HeVVLF|%~1
z%#{NX1;^h^E6FR3UTV;N=H~Y)o8L<1y>cU;BfB#vZhp_XV$WCVraCiSqqsI{ZcT=0
zX1L~u$i2BOdMbg~KC0&jtukj4QnbhTZ`Z{&3|~58pVXga$i0rn1zC7g^p0fLe?r}V
zuT~`UQ{~ERWW=ZAD=E?bh#H-vKZkl&24&o(h)5zo4=k+v)}ZrrfD22*g!2L
z{zfnv=ynzMc#K`0n=Ys;CkkMHG^3}sko;{!->{CK^0wN-t>w`?mAs@)Qf&v<*szSp
zW}()+!Z~RK5!~$2tk>>w?|)5hLRsn%3Vj9`^=(88Qe@BgYbcvrXlj
z^-2EbMtZ)z84tp?){6JWKZ|i$Qoir5UD5h7{@0#0L1^QzeaZ0Vk=7dy_K8JU_-xOu
zy8K=j+(9<++}@zkJ9}x+t1y*F^SJANaiHydYQpc}4x=!?EYtX6)iZnYr>1=rgs2Gx7q`i!fjI1I
zp=MO9x@7Vso$J~>?uF7s{#c|2QOh3?d_g6{+g)edlOu$XtVTFB(mHrvSp#PcovO
zjVq*%^6`)557V!*X?3$bcod(m*p8xjN?LN;urZMKkqal=ou5)YT?>6w+99RWqIVY|
z{<6PmSZL%SUpn}$%x)U`e$pJ~cA;-inyEQUdoR`HA4_?Mf9=V
z_4kZVD{pO`sc#mrpMGho4c{LiKcm_StRB=fWH*oQZ>I5noE=vExiTI9iM$tp!t?t=
zAx#Bw{0*#vQ{Y)XgzO++^D=z$c+jZrUg{lEo}G3L0I%@n&@=p??^o|yIai*X23C8*
z5~8lP-T_+a+20haEIJ-IlE-G^$@4%4#Msn>
z-9#Kb^fY;-zR9F-$z}7A__N($lv@uYer!*TpCCx4KC*N;f>XLr9hmfJv!5-F7H}ET
zGEOH-GbRd%)QW>QL~_|>hVPXI`k7jOsg(I1nnFt9fUF@xKGX8~Dl*qS(l*mvG=J61
zFPz7wg=yR^+(CYkJtg{v6!z-;^lYF=*TXu(6l|-j*^m58BR=1we6br2Mb*yLiNJk2
z{1_Eqkw5B#_;ZMU$kyO|?QLamuIKjhG4*koEg{x=Mas(?m2IsR;tB^hC3^36GEEZ&
zjS>EcEoOH6p%L^u#6G~^KEUi7KJGXCKY{mgI|+5_rJzc=rX0T*V|ea4T8*zVGSW#p
zi~b5E&dVDor5enP6V_~{i)ll(moNlDb$_OJe{cNGsafo9MfIUS#m=1cj^j;h{dY*O+<0afgoLhHXiD54&
zt0U7SUg}?q(lpqM_I%9O+lmqi?K6C@T^P^arpP>`>>5M>drmI5EY0%Ey}gAX2#@iQ
zNWSs*OIA{1wKNi6{@=yL*hPurLm{6Q>U-WJGxMKfe-xvv)VWL7$h?-KROBm**PTH{
zOUdw>O?-Z9^qsdaZxL}p*0Z5}Qz%Sd^a@M}_5hOp8GfPlm&sGVqx1iKtC-rP3%IA*&
zoq=`s_O$XF^R@VgHYMSxDE$z3qYOdBAIizx&NDBus)>gL8%hPiHv(wP;Dd9I7m7lrAY&Tf^Rv
z8R7lBU@i?9L!bsjxYq9aF^DP(&L6;^KUyI^iJH1gJ4idOTap1Bc_wOES?FKaP)?Q1
z4=Svv?4U6sZc+WhLv~Z4{H|GLnh$Hs?v>I`^X+S{<+gQjylmf4v1MAC8v6+7tyhBU
z?Mho#x<+`6DU720vZ&Wdtz4rBg0`cHmzpLJxNHr-K;viZOTZ`}T4XQrwME{c7z9CF>Da?qh^WgBNcX64xp
zF~4c;o!snu#J6(`>VD^c1gGE8f5m4%Vrjt}>K`NTnR9ex^n~|WV5e#)p^wwIxVO#5
zk0|8tT;YId#ZNR>|3a}97%r+>?o$n>zDU-xQ&7ILeBeQ^#o0I3+;=OnE<)z62HOg8
zU#G7QwK+Li(Kk?$Q*GUeOz`cIuIPrrPEYPG9{r;zy!HhgvgcW^eIW`J@2{2orVQpQ
z-F6-^Hw>DeyMxa36cR4}KY0j+-@V^y6eKOZ#G*ddN
z#NS{%$?{Hy@j$86s}66Xm6`ZHqhd!SIon;@eY@sncXKQG)29iFsk;@Vhv0i+?;^NM
z9(~MST;z@)RW~O=g*?jlRJWz8i=Yp0Ke5Oy&Czt2%I?>Uj`+m>vcoj^v!P8x><~w{
zXkJ1CMHdtQ-N(MwyJ`3RZ2CQ|1Vhr)%@^jiw@f*fSEvI;D9Im>b
zcuJXJT*GuqnXTI2g*b~VB&A9`HO=qz<&r8#*Nb{>b-k@Xy^|>sd+BzC$=JWDXdx)*
ztL(M%zCp8BYABtO+gA4!Pwxkml#G*l>rU8j-(&Brj^P}-q9(%Hn)>6>+Oo(tPfr%`
zw6Bf3y}!mkUi#NyP&=oX_KaW5^x6Dk7XkC&(%ek8c(nUdn+%}V{A
z8xMIv77}*|j&^C~C9UJ-1Q{o1n~{{
zo?2^NAf1O(!nCfqlhyQgHZC4COGA5U2KZce41FB?Iv7#Q!&FHJT|sAD-##Un1oa*r
zkuYKDS_WSs#%pAXfP>5{UEjKjext6}sx&dNSFtHMfH~nUHN7GaakuP~?@#gr*9yDX
z^?kJ09Bv4gXq@$M6s)#R?mhA(9jzT%f3L0h8(^4NcM{?^ad#cGbsd6oB(XV(Q~AXrLJWMoo5nKVKk}XSFX~0ieQh&@rK)`ZLp*`1%JsFRUUq+!rJ=^h$v?
zraDdgjeG^CTJ=9Qa~hj>u0xh{8!keT;K
zSdiPR0P1jsJESj*Sy9+?kFcDWg<7*U_v4@8dEtC&Gk+YHS2qM-S&+3`I|a7yXNsA7?*LGxqx#FMU{NKG#*{vTm)?={yNLj#f=wfj)a^6is8X_h;0ex|qz4t`Qdo)^l=
zt>vE)5+=sqUc__d;BO;OjcdDGUf69H>k;2pOn
z{o%RIh;^CDb-FzBS35^}q3p>{j+%&$9qiA1$)BYvBx@e7VfTUWWjy4NewX&5@|A&}
zLoatFYLR#TV@XbcC&@N)N!K8p8V_G>2ID$}%o6;_s!oG}sq8!I
zB5E>I?$6a>A$jDIXHBY6DO(vCT_G)r!_($wsI0w{iuSLX>MQ*yCWDE`n_pvdU)f4?
z>X!6QJ&?l9u`G;DxWeTz=2A=OeN}pTV--^U^FFvDHZa78V|mmKQdJZ|x+aNN2Zz4p
z+QMRfYBW5tYZ~kk%ldPY#^!+gJ`DEH;;>FG_qk{Quz
zUX;r5$^%CoiK9K4qJYM}B
zaZ8_CP@!6KfKE%c2ysGD+~g|$r!vnp!NNvo==PK6qmIriPnD;E!=1%KGbYKaCoP2y
z8$6?DNBYVh&z(MVvoHr5RyQA>+RPQsCuCTa7TWF-j)Tm@tw)A*8`H}c6;bQUlpZx
z2{VHJ>={{YDeLpOt2W3w7k{16a43mv%^}SB2W_;FhRurYld?F!BbJxezI*{fu6@Ja
zCBGr)?cGlo!{MvBqQkK$)C6hs;PNIhqqI#zvf86)ne4ocACIbbRO_9{RUj-=6lpE*=cHfA^B`5rD*==f
z3uUqIxYA8jQq5^nu{k!qN1Od1T}5KLBPuIqDK!c5jD&Wb0G}9TS>K{{j1^g$TJku
z@x*NC%;%Al_)&p67yGs
z1Ys%Uapu@ucxf;0^9z|7M%9DnhvUb7FRI=Cj-Bxyu_$U#2(Zgeqv9^w6UHsh-?e~VWvAtSAo42RgVW>Op3TrSr!?K86TL7M8#;lx*zdiI4}OObp`b}X
z$Dy0}yw0-oy?du|p3K|FUcR(D8u!O+_bL~uCgx}r?3TIP7VfV}N^fok65HP*b%xu&
z(R{-zEDMwi+}SztIszvYU;UXs+Qny*32MjSS>358BEtiic(TWik9lj
z>`mih+-FaOzaLPSbiX$ce&Qm*G=_Ru)z3z?Rcw0YzHe4s2L+ptX$@}v2($rr3GY`S
z&%k+fg!Rcc(S268KQkH0md@io~;m1QY+kFZQ!+m&I{y^H=6DA)KoQM2sI
z3&YO_c1~t@#awR;YOnP>zs4_r3M(iInT>XDqC*88&suSMVtW=8ks)R}R#u~BzDB?5
z-n8_h6z9K76P7b#@7Ff%OnSO`8!J8gm>`?f-|81@(XZMBBCiMdl;cCPycH(KW0B8~
z10E6zy3=mb3XGE1l2MYJEYI6*Qgnx8W6>5sISm5&@bj5)mk5@RpA+|Yd<+E)m>(9s
z%E?8k8(j~UTCh@3n;y*GYI4ha7V*;ZwAZsoUIKgdb)bv#H$nDPzJg(cswMU)!ES$1
z&eSByKnkGxs>HbZf}SoW$WtW@Ju3xHoJ+DEV5LWl(Y}jlE6N
zP(=zf=GZC+d9-p#Dy!&aG=$Po#yd^*SB~4E2t<@A{dcpT*K}X?P{Lozqlph`O&}=Mcjj@n%%PCHFjZ{ab8!z9MBDpgb`c%U7vxNsVYOq5z9m0pw`hcbQ|S&SzkV}A9!TsBz*rn9YMZ-
z^z{}2RKTRrN$eQ(?Vc*u68YN-BLM`lAh`B;UH-UFY4JCYYR~b(>2AuYP_SQs?UM`}
zhP{I8R##f?2ewMShe{i5*FJ46lN{~;u=@jBM`XxRDdJQzpYTxCOQME$N}GuhVmH<8|YvW7ZfKnTC7Zx@VxLe*|WL@?ddU*_^b
zM=ZDo3^TI>6H_ji{!DeAzU%sf3neHMfg9(l-5$%a%GQnZt^pNfmje
zLsQK^={KqrbV!?o|EQkim2j#(RU89bGOo3u?w%cO%Q+_g;A4qmb~pak`izzhxAQx$
zLMDTNZg-=Tg7>t-TC-Qh`@-ZyFKSPrxqZ_IWS&Lk`?_b%ZEV_In{$z5?8J421N#0g
z5i?Cr%Qi8mPVc-NLB8`@Rnz6kR!H_rXjPG4v
z(sB^WTP~?tgjmdO|9-=V>+e2lyD6!m+`W~)ibsAe+qE%D+Yj|=A-g7-Ca~$Eb?7k=
zYVhm*mXCV7InC^q!kdo%!qc)}C3!XFV+~>)#7dq*Icc1q)TY)GI!6k4U@gt2f6{0w
zEukw+#A$gqH`+MSUcNDO!?a<@+W-Do@G$E`|D%ziE4W9wRIaBqmN|53@+2p?*r)IkZiB>w{Jlay!{tp+CZoj{N7pLsCr&6U{5(dpn;8Y<3-h6ggWB{vCgA
zk!yX%|KKSUiL)yW&C?_rrk$ThAG7L3j1L5f+kziA
zOr@M@(UH;I{t#hIf&BA`;GP*lP$%&_p?Q-Pvn%YcvlRJ`kkb={#1UfmC`M+o?Kky5
zCOKbW9Zz3zbIkP>@L0{YBm7o_GP7TDsC}~TrI!K~&^!nyyl*4T{g4fag-Ay*O!=+<
zj+&x6#ESC$R`q(Kxl=&{GR962-C-I_OVALlURYp=!pk2X7Do!*LuX{
z1tZzrB#N~rRVfTM8OOcbVsiT1t@-(}yA})TmXtfzhA=c2r0VSCL3EshYCPgvK;f45
zMlLYdW&A!-sI$5U5~PtH+*!{Cg@YfezBYK|fVE?Mcq^Lg)RoTTN~+GNUHG)E|%lfT(sn^(+nhb3cPbYEvN|8GnB`t`9>yp|=T>)Uz{oxB`Eo$!7F%hrTFfHH&Tu
zD92cBbIyR%mR|B6xpU5lcL{;To2RBJq9(GX;Fvl(n{Tc0@}F8o6XQRnxRd85u|>b^
z)IzaOjTj$vRj%ACq#Oix2PpV=Bv2dX5?;HmTikKmFJm+|d^wTI@+H};-v2FMgs@><4LzceX#C<%|*
zL(vQ9?|*nUmP@kHKvd2AhcCvu&3cz3-|*h)Q*yfLkdOXfT4Z}#G6k!h>pDrnzsuv-
zbeeV8l4@|aof8X(xUho?Ld%cb>d%AwA=mWW*N(`%a
z4!`5I!<=S}e)8SEA4(|9GATmvU4rD1+bpktBGFwxoF+ken;%TflAMnC9_4QI{_$#qvzB_h^~Z1kHcQp!woaIBr%9+fps}vu{(9eOsmCoyT=Vord<4akE(%eeTlV
zP5w}f9nSYLHFgBv>!!wMvw_u)lor{YWm*X!Txk);m+cTRRA~;kYlnwT(~q-Olw7B`J?JDiPnC?X`m_(b+=C
z6d4ZC)I8rOquTnZ;H9Ql=vDw&m%%>wH8ZZ6z+5fK@;KF1ImdVcj@7FrvfYbeIoNnc
zw5>B!%)UKfUr34E1g#3~SBFh~S1)#rgl}P46_7%#J@1VuxJ(8(hElE4eLO8d6@RK|
zuwHg~MMN-Rtj%H*wL(r`yKCK`cq}UIKi&+`tYNb&NPNs_G5d^psU%lU4aj*xyCi(4?Vh{`?eCS?NBfq^mu(unSl!a}8me2lu4s-BfwG^hPB6o`PDeS@ezOWB3PS1y2SgY$F}1$USVqna}I)qPocg
zm1e1K2_5x~b*6V+1wu77akWF&Gqy>Md7r>cU`nVe|D3HMF7#5Ql4ZbQdwcL?)s!+n>%
z8rqKAwa&$iRQ|zi1_}}s4%oSzbDRtJ}6ib^Z|N*3)p@V!Z0~N8iZz
zp;G%y6*u>f;Ji`BRbw@dsd3&%H?Kc^JW)^8`v(`7d};wdx}&ndd(TIom`Can8{wa*
zrZcmMBO2&4byp6Y7qWFSxuvl-TTO
zO$WAX1>G9S+fk6v#aOc5fMQ8y15+VCdnUgu)!e18xlxs)YXs7fG7gK50U_bX^0U$t
zp2F38^a<$Wg{*$OshOrlc@&D)W552|3fJc0&d!WDjyY+igYR@u3uAO~@Oo_RwXY4c
z-^6}MbBH-75x0$AAHFgYwz*&V^NCM!<;I6(8|mF*=q^gt89L5#O-G@B<-4{!8R1vI
z>VuldWYK1l&2;P2eek=IIsGRKPk$Q3D^7>VTO))p
z&*8@!uZL!2<#^Zcd-`v_FgYfvf6Uo@@U(%DOFJUv70>>YWC9f8t&eRLfa@+ZTlzog@Zbro{nRi_$xGu}oaosiZRV7nDD@7ALlZL$D^E5%7
z64TfeHG#sJ=(fyma1w)Ezs5$;TpXy$o
zJxQ4tt+0PlBAx)%JaSKpl?S}Q2zTla&%6txewRFeI(y-`QIN@))})7bi(hq?&-OKM
ze?(3e+=Hh~{no7R+;aoEoD?pVh8BbAP;Wm5#?9p7
zIH6!yIg-}FWUMz*>L;ohK24vDPpEdUSobR&R(y5~E&jn5LTSI1_SWxshDu5}3jNf3
zmRSQ;Hw8)&y1>nkXyjYDdMjv-B$L+6YlEo_#-z8YcT9wj%!qzKS3&Q__38GN
zwe_mkBJYt0&);%u=H3j7W_Hok|LWf`Fr^;{T-FeGoQlOu}G
z2>t^0yPtYK-{^R7nP(Fz)6J#M}>NRDjV9rpb()>cKBJV_52l|XEBl$h`AKS(#C
zA;3F{Zp?K1GEi6H7_xv9>wYP*-_h-)x
z+nNbs@yGzy1E5
zU54}R?z%h!i9ii?qc}Nuc~lFYjCwP!?WbsHgCl6Zoc_bLQ4M)>IRZdH
zgyx8^!&u?}$Q4<>8V*aFtgBVzkjki5I4T&3KFgkns
zI0*l%G5&h5Ma0∾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