From e00e6b9b4c0f0b0a45e5ad3ba9591a84b401669a Mon Sep 17 00:00:00 2001 From: Hendrik Brummermann Date: Sun, 21 Dec 2025 22:19:28 +0100 Subject: [PATCH 1/7] 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 59f2ba9fbdc515c29bb813145a7b6a82dbd2174f Mon Sep 17 00:00:00 2001 From: Hendrik Brummermann Date: Mon, 22 Dec 2025 17:02:31 +0100 Subject: [PATCH 2/7] 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 3/7] 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 4/7] 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 5/7] 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 05d1fb34afbebfd7c5ab8e725c5bd19f375dc6b6 Mon Sep 17 00:00:00 2001 From: Hendrik Brummermann Date: Mon, 22 Dec 2025 19:21:51 +0100 Subject: [PATCH 6/7] 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 7/7] 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