Webclient: Convert stendhal.data.map to TypeScript

This commit is contained in:
Jordan Irwin 2023-02-07 05:22:00 -08:00
parent 3043c286f7
commit c23ba7bbbc
6 changed files with 320 additions and 273 deletions

View file

@ -1108,7 +1108,7 @@
<property name="js-files-config" value="build/js/build.js"/>
<property name="js-files-lib1" value="srcjs/jsxgraph-util.js"/>
<property name="js-files-lib2" value=""/>
<property name="js-files-data1" value="srcjs/stendhal/data/map.js srcjs/stendhal/data/sha3.js"/>
<property name="js-files-data1" value="srcjs/stendhal/data/imagepreloader.js srcjs/stendhal/data/sha3.js"/>
<property name="js-files-data2" value=""/>
<property name="js-files-entity5" value=""/>
<property name="js-files-entity6" value=""/>

View file

@ -19,6 +19,7 @@ import { WeatherRenderer } from "./util/WeatherRenderer";
import { Animation } from "./data/Animation";
import { EmojiStore } from "./data/EmojiStore";
import { GroupManager } from "./data/GroupManager";
import { Map } from "./data/Map";
import { Paths } from "./data/Paths";
import { SpriteStore, store } from "./data/SpriteStore";
@ -54,6 +55,10 @@ export class SingletonRepo {
return LoopedSoundSourceManager.get();
}
static getMap(): Map {
return Map.get();
}
static getPaths(): typeof Paths {
return Paths;
}

227
srcjs/stendhal/data/Map.ts Normal file
View file

@ -0,0 +1,227 @@
/***************************************************************************
* (C) Copyright 2003-2023 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation; either version 3 of the *
* License, or (at your option) any later version. *
* *
***************************************************************************/
declare var marauroa: any;
declare var stendhal: any;
import { Paths } from "./Paths";
import { LandscapeRenderingStrategy, CombinedTilesetRenderingStrategy } from "../landscape/LandscapeRenderingStrategy";
import { IndividualTilesetRenderingStrategy } from "../landscape/IndividualTilesetRenderingStrategy";
export class Map {
private currentZoneName = "";
private offsetX = 0;
private offsetY = 0;
private zoneSizeX = -1;
private zoneSizeY = -1;
private sizeX = 20;
private sizeY = 15;
private tileWidth = 32;
private tileHeight = 32;
private zoom = 100;
private tilesetFilenames: string[] = [];
private aImages = -1;
private layerNames: any = -1;
private layers: any = -1;
private firstgids: any = -1;
private gidsindex: any = [];
private drawingError = false;
private targetTileWidth = 0;
private targetTileHeight = 0;
private readonly layerGroups: any = [
["0_floor", "1_terrain", "2_object"],
["3_roof", "4_roof_add"]
];
private layerGroupIndexes: any;
private strategy: LandscapeRenderingStrategy;
private protection: any;
private collisionData: any;
// alternatives for known images that may be considered violent or mature
private knownSafeTilesets: string[] = [
Paths.tileset + "/item/armor/bloodied_small_axe",
Paths.tileset + "/item/blood/floor_stain",
Paths.tileset + "/item/blood/floor_stains_2",
Paths.tileset + "/item/blood/nsew_stains",
Paths.tileset + "/item/blood/small_stains"
];
/** Singleton instance. */
private static instance: Map;
/**
* Retrieves singleton instance.
*/
static get(): Map {
if (!Map.instance) {
Map.instance = new Map();
}
return Map.instance;
}
/**
* Hidden singleton constructor.
*/
private constructor() {
if (window.location.search.indexOf("old") > -1) {
this.strategy = new IndividualTilesetRenderingStrategy();
} else {
this.strategy = new CombinedTilesetRenderingStrategy();
}
}
/**
* Returns the index of the tileset a tile belongs to.
*/
getTilesetForGid(value: number): any {
if (value < this.gidsindex.length) {
return this.gidsindex[value];
} else {
return this.gidsindex[this.gidsindex.length - 1] + 1;
}
}
onTransfer(zoneName: string, content: any) {
this.currentZoneName = zoneName;
this.firstgids = [];
this.layers = [];
this.layerNames = [];
var body = document.getElementById("body")!;
body.style.cursor = "wait";
console.log("load map");
// tilesets, 0_floor, 1_terrain, 2_object, 3_roof, 4_roof_add, blend_ground, blend_roof, protection, collision, data_map
this.decodeTileset(content, "tilesets");
this.decodeMapLayer(content, "0_floor");
this.decodeMapLayer(content, "1_terrain");
this.decodeMapLayer(content, "2_object");
this.decodeMapLayer(content, "3_roof");
this.decodeMapLayer(content, "4_roof_add");
this.protection = this.decodeMapLayer(content, "protection");
this.collisionData = this.decodeMapLayer(content, "collision");
this.layerGroupIndexes = this.mapLayerGroup();
this.strategy.onMapLoaded(this);
marauroa.me.onEnterZone();
}
decodeTileset(content: any, name: string) {
var layerData = content[name];
var deserializer = marauroa.Deserializer.fromBase64(layerData);
var amount = deserializer.readInt() as number;
this.tilesetFilenames = [];
for (var i = 0; i < amount; i++) {
var name = deserializer.readString() as string;
var source = deserializer.readString() as string;
var firstgid = deserializer.readInt() as number;
// Security Note: The following line triggers a false positive.
// This is not input validation. It just rewrites a path used by the
// Java client to a path matching the webserver directory layout.
var filename = "/" + source.replace(/\.\.\/\.\.\//g, "");
let baseFilename = filename.replace(/\.png$/, "").replace("/tileset", Paths.tileset);
if (!stendhal.config.getBoolean("gamescreen.blood") && this.hasSafeTileset(baseFilename)) {
this.tilesetFilenames.push(baseFilename + "-safe.png");
} else {
this.tilesetFilenames.push(filename);
}
this.firstgids.push(firstgid);
}
// create a lookup table from gid to tileset index for significant performance reasons
this.gidsindex = [];
var pos: number, lastStart = 0, i: number;
for (pos = 0; pos < parseInt(this.firstgids.length, 10); pos++) {
for (i = lastStart; i < this.firstgids[pos]; i++) {
this.gidsindex.push(pos - 1);
}
lastStart = this.firstgids[pos];
}
this.strategy.onTilesetLoaded();
}
decodeMapLayer(content: any, name: string): number[]|undefined {
var layerData = content[name];
if (!layerData) {
return;
}
var deserializer = marauroa.Deserializer.fromDeflatedBase64(layerData);
deserializer.readString(); // zone name
this.zoneSizeX = deserializer.readInt();
this.zoneSizeY = deserializer.readInt();
var layerRaw = deserializer.readByteArray();
var layer: number[] = [];
for (var i = 0; i < this.zoneSizeX * this.zoneSizeY * 4 - 3; i=i+4) {
var tileId = layerRaw.getUint32(i, true);
layer.push(tileId);
}
this.layerNames.push(name);
this.layers.push(layer);
return layer;
}
collision(x: number, y: number): boolean {
return this.collisionData[y * this.zoneSizeX + x] != 0;
}
isProtected(x: number, y: number): boolean {
return this.protection[y * this.zoneSizeX + x] != 0;
}
mapLayerGroup(): any {
let res = [];
for (let layers of this.layerGroups) {
let group = [];
for (let layer of layers) {
let index = this.layerNames.indexOf(layer);
if (index > - 1) {
group.push(index);
}
}
if (group) {
res.push(group);
}
}
return res;
}
/**
* Checks if there is an alternative "safe" tileset image available.
*
* @param filename
* The tileset image base file path.
* @return
* <code>true</code> if a known safe image is available.
*/
hasSafeTileset(filename: string) {
return this.knownSafeTilesets.indexOf(filename) > -1;
}
}

View file

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

View file

@ -1,272 +0,0 @@
/***************************************************************************
* (C) Copyright 2003-2017 - Stendhal *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation; either version 3 of the *
* License, or (at your option) any later version. *
* *
***************************************************************************/
"use strict";
var CombinedTilesetRenderingStrategy = require("../../../build/ts/landscape/LandscapeRenderingStrategy").CombinedTilesetRenderingStrategy;
var IndividualTilesetRenderingStrategy = require("../../../build/ts/landscape/IndividualTilesetRenderingStrategy").IndividualTilesetRenderingStrategy;
var Paths = require("../../../build/ts/data/Paths").Paths;
var stendhal = window.stendhal = window.stendhal || {};
stendhal.data = stendhal.data || {};
/**
* preloads images
*
* @param images image url to load
* @param callback callback to invoke
* @constructor
*/
// Start http://www.webreference.com/programming/javascript/gr/column3/
function ImagePreloader(images, callback) {
// store the call-back
this.callback = callback;
// initialize internal state.
this.nLoaded = 0;
this.nProcessed = 0;
stendhal.data.map.aImages = new Array;
// record the number of images.
this.nImages = images.length;
// for each image, call preload()
for ( var i = 0; i < images.length; i++) {
this.preload(images[i]);
}
}
ImagePreloader.prototype.preload = function(image) {
// create new Image object and add to array
var oImage = new Image;
stendhal.data.map.aImages.push(oImage);
// set up event handlers for the Image object
oImage.onload = ImagePreloader.prototype.onload;
oImage.onerror = ImagePreloader.prototype.onerror;
oImage.onabort = ImagePreloader.prototype.onabort;
// assign pointer back to this.
oImage.oImagePreloader = this;
oImage.bLoaded = false;
// assign the .src property of the Image object
oImage.src = image;
};
ImagePreloader.prototype.onComplete = function() {
this.nProcessed++;
if (this.nProcessed == this.nImages) {
this.callback();
}
};
ImagePreloader.prototype.onload = function() {
this.bLoaded = true;
this.oImagePreloader.nLoaded++;
this.oImagePreloader.onComplete();
};
ImagePreloader.prototype.onerror = function() {
this.bError = true;
this.oImagePreloader.onComplete();
console.error("Error loading " + this.src);
};
ImagePreloader.prototype.onabort = function() {
this.bAbort = true;
this.oImagePreloader.onComplete();
console.error("Loading " + this.src + " was aborted");
};
// End http://www.webreference.com/programming/javascript/gr/column3/
stendhal.data.map = {
currentZoneName: "",
offsetX : 0,
offsetY : 0,
zoneSizeX : -1,
zoneSizeY : -1,
sizeX : 20,
sizeY : 15,
tileWidth : 32,
tileHeight : 32,
zoom : 100,
tilesetFilenames: [],
aImages : -1,
layerNames : -1,
layers : -1,
firstgids : -1,
drawingError : false,
targetTileWidth : 0,
targetTileHeight : 0,
layerGroups: [
["0_floor", "1_terrain", "2_object"],
["3_roof", "4_roof_add"]
],
/**
* Returns the index of the tileset a tile belongs to.
*/
getTilesetForGid: function(value) {
if (value < this.gidsindex.length) {
return this.gidsindex[value];
} else {
return this.gidsindex[this.gidsindex.length - 1] + 1;
}
},
onTransfer: function(zoneName, content) {
stendhal.data.map.currentZoneName = zoneName;
stendhal.data.map.firstgids = [];
stendhal.data.map.layers = [];
stendhal.data.map.layerNames = [];
var body = document.getElementById("body");
body.style.cursor = "wait";
console.log("load map");
// tilesets, 0_floor, 1_terrain, 2_object, 3_roof, 4_roof_add, blend_ground, blend_roof, protection, collision, data_map
stendhal.data.map.decodeTileset(content, "tilesets");
stendhal.data.map.decodeMapLayer(content, "0_floor");
stendhal.data.map.decodeMapLayer(content, "1_terrain");
stendhal.data.map.decodeMapLayer(content, "2_object");
stendhal.data.map.decodeMapLayer(content, "3_roof");
stendhal.data.map.decodeMapLayer(content, "4_roof_add");
stendhal.data.map.protection = stendhal.data.map.decodeMapLayer(content, "protection");
stendhal.data.map.collisionData = stendhal.data.map.decodeMapLayer(content, "collision");
stendhal.data.map.layerGroupIndexes = stendhal.data.map.mapLayerGroup();
stendhal.data.map.strategy.onMapLoaded(stendhal.data.map);
marauroa.me.onEnterZone();
},
decodeTileset: function(content, name) {
var layerData = content[name];
var deserializer = marauroa.Deserializer.fromBase64(layerData);
var amount = deserializer.readInt();
stendhal.data.map.tilesetFilenames = [];
for (var i = 0; i < amount; i++) {
var name = deserializer.readString();
var source = deserializer.readString();
var firstgid = deserializer.readInt()
// Security Note: The following line triggers a false positive.
// This is not input validation. It just rewrites a path used by the
// Java client to a path matching the webserver directory layout.
var filename = "/" + source.replace(/\.\.\/\.\.\//g, "");
const baseFilename = filename.replace(/\.png$/, "").replace("/tileset", Paths.tileset);
if (!stendhal.config.getBoolean("gamescreen.blood") && this.hasSafeTileset(baseFilename)) {
stendhal.data.map.tilesetFilenames.push(baseFilename + "-safe.png");
} else {
stendhal.data.map.tilesetFilenames.push(filename);
}
stendhal.data.map.firstgids.push(firstgid);
}
// create a lookup table from gid to tileset index for a significant performance reasons
stendhal.data.map.gidsindex = [];
var pos, lastStart = 0, i;
for (pos = 0; pos < parseInt(stendhal.data.map.firstgids.length, 10); pos++) {
for (i=lastStart; i<stendhal.data.map.firstgids[pos]; i++) {
stendhal.data.map.gidsindex.push(pos - 1);
}
lastStart = stendhal.data.map.firstgids[pos];
}
stendhal.data.map.strategy.onTilesetLoaded();
},
decodeMapLayer: function(content, name) {
var layerData = content[name];
if (!layerData) {
return;
}
var deserializer = marauroa.Deserializer.fromDeflatedBase64(layerData);
deserializer.readString(); // zone name
stendhal.data.map.zoneSizeX = deserializer.readInt();
stendhal.data.map.zoneSizeY = deserializer.readInt();
var layerRaw = deserializer.readByteArray();
var layer = [];
for (var i = 0; i < stendhal.data.map.zoneSizeX * stendhal.data.map.zoneSizeY * 4 - 3; i=i+4) {
var tileId = layerRaw.getUint32(i, true);
layer.push(tileId);
}
stendhal.data.map.layerNames.push(name);
stendhal.data.map.layers.push(layer);
return layer;
},
collision: function(x, y) {
return this.collisionData[y * stendhal.data.map.zoneSizeX + x] != 0;
},
isProtected: function(x, y) {
return this.protection[y * stendhal.data.map.zoneSizeX + x] != 0;
},
mapLayerGroup() {
let res = [];
for (let layers of stendhal.data.map.layerGroups) {
let group = [];
for (let layer of layers) {
let index = stendhal.data.map.layerNames.indexOf(layer);
if (index > - 1) {
group.push(index);
}
}
if (group) {
res.push(group);
}
}
return res;
},
/**
* Checks if there is an alternative "safe" tileset image available.
*
* @param filename
* The tileset image base file path.
* @return
* <code>true</code> if a known safe image is available.
*/
hasSafeTileset: function(filename) {
return this.knownSafeTilesets[filename] == true;
}
};
// alternatives for known images that may be considered violent or mature
stendhal.data.map.knownSafeTilesets = {};
stendhal.data.map.knownSafeTilesets[Paths.tileset + "/item/armor/bloodied_small_axe"] = true;
stendhal.data.map.knownSafeTilesets[Paths.tileset + "/item/blood/floor_stain"] = true;
stendhal.data.map.knownSafeTilesets[Paths.tileset + "/item/blood/floor_stains_2"] = true;
stendhal.data.map.knownSafeTilesets[Paths.tileset + "/item/blood/nsew_stains"] = true;
stendhal.data.map.knownSafeTilesets[Paths.tileset + "/item/blood/small_stains"] = true;
if (window.location.search.indexOf("old") > -1) {
stendhal.data.map.strategy = new IndividualTilesetRenderingStrategy();
} else {
stendhal.data.map.strategy = new CombinedTilesetRenderingStrategy();
};

View file

@ -52,6 +52,7 @@ stendhal.data.cstatus.init();
stendhal.data.group = singletons.getGroupManager();
stendhal.data.outfit = new Outfit();
stendhal.data.sprites = singletons.getSpriteStore();
stendhal.data.map = singletons.getMap();
stendhal.config = stendhal.config || singletons.getConfigManager();
stendhal.paths = stendhal.paths || singletons.getPaths();