Skip to content

Commit 7c50bd8

Browse files
cryptobenchclaude
andcommitted
Add server banner to README
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent f6a6795 commit 7c50bd8

9 files changed

Lines changed: 180 additions & 11 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# EasyWebMap
22

3+
> **Built for the European Hytale survival server at `play.hyfyve.net`**
4+
35
A live web map for your Hytale server. View your world in a browser, track players in real-time, and easily integrate with your community website.
46

57
---

target/EasyWebMap-1.0.0.jar

6.84 KB
Binary file not shown.
992 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
Binary file not shown.
Binary file not shown.

target/classes/web/js/map.js

Lines changed: 175 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,169 @@
11
(function() {
22
'use strict';
33

4+
// ============================================
5+
// BatchTileLayer - Custom LeafletJS tile layer
6+
// Batches multiple tile requests into single HTTP requests
7+
// ============================================
8+
L.TileLayer.Batch = L.TileLayer.extend({
9+
options: {
10+
batchDelay: 300,
11+
maxBatchSize: 2000,
12+
batchEndpoint: '/api/tiles/batch'
13+
},
14+
15+
initialize: function(urlTemplate, options) {
16+
L.TileLayer.prototype.initialize.call(this, urlTemplate, options);
17+
this._pendingTiles = new Map();
18+
this._batchTimer = null;
19+
this._emptyTileUrl = null;
20+
this._worldName = 'world';
21+
this._isSending = false;
22+
this._queuedWhileSending = new Map();
23+
},
24+
25+
setWorld: function(worldName) {
26+
this._worldName = worldName;
27+
},
28+
29+
createTile: function(coords, done) {
30+
const tile = document.createElement('img');
31+
tile.alt = '';
32+
tile.setAttribute('role', 'presentation');
33+
34+
const key = `0/${coords.x}/${coords.y}`;
35+
this._queueTileRequest(key, coords, tile, done);
36+
37+
return tile;
38+
},
39+
40+
_queueTileRequest: function(key, coords, tile, done) {
41+
// If we're currently sending, queue for next batch
42+
const targetMap = this._isSending ? this._queuedWhileSending : this._pendingTiles;
43+
44+
targetMap.set(key, {
45+
tile: tile,
46+
done: done,
47+
coords: coords
48+
});
49+
50+
if (this._batchTimer) {
51+
clearTimeout(this._batchTimer);
52+
}
53+
54+
// Only auto-send if not currently sending and we hit a huge limit
55+
if (!this._isSending && this._pendingTiles.size >= this.options.maxBatchSize) {
56+
this._sendBatch();
57+
} else if (!this._isSending) {
58+
this._batchTimer = setTimeout(() => this._sendBatch(), this.options.batchDelay);
59+
}
60+
},
61+
62+
_sendBatch: function() {
63+
if (this._pendingTiles.size === 0) return;
64+
65+
this._isSending = true;
66+
const allTiles = new Map(this._pendingTiles);
67+
this._pendingTiles.clear();
68+
this._batchTimer = null;
69+
70+
// Split into chunks of 200 tiles max
71+
const CHUNK_SIZE = 200;
72+
const chunks = [];
73+
let currentChunk = new Map();
74+
75+
for (const [key, value] of allTiles) {
76+
currentChunk.set(key, value);
77+
if (currentChunk.size >= CHUNK_SIZE) {
78+
chunks.push(currentChunk);
79+
currentChunk = new Map();
80+
}
81+
}
82+
if (currentChunk.size > 0) {
83+
chunks.push(currentChunk);
84+
}
85+
86+
console.log(`Sending ${allTiles.size} tiles in ${chunks.length} batch(es)`);
87+
88+
// Send all chunks in parallel
89+
const chunkPromises = chunks.map(chunk => this._sendChunk(chunk));
90+
91+
Promise.all(chunkPromises).finally(() => {
92+
this._isSending = false;
93+
// Process any tiles that were queued while we were sending
94+
if (this._queuedWhileSending.size > 0) {
95+
for (const [key, value] of this._queuedWhileSending) {
96+
this._pendingTiles.set(key, value);
97+
}
98+
this._queuedWhileSending.clear();
99+
// Schedule next batch
100+
this._batchTimer = setTimeout(() => this._sendBatch(), this.options.batchDelay);
101+
}
102+
});
103+
},
104+
105+
_sendChunk: function(batch) {
106+
const tiles = [];
107+
for (const [key, request] of batch) {
108+
const [z, x, y] = key.split('/').map(Number);
109+
tiles.push({ z, x, y });
110+
}
111+
112+
const requestBody = {
113+
world: this._worldName,
114+
tiles: tiles
115+
};
116+
117+
return fetch(this.options.batchEndpoint, {
118+
method: 'POST',
119+
headers: { 'Content-Type': 'application/json' },
120+
body: JSON.stringify(requestBody)
121+
})
122+
.then(response => {
123+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
124+
return response.json();
125+
})
126+
.then(data => {
127+
for (const [key, tileData] of Object.entries(data.tiles)) {
128+
const request = batch.get(key);
129+
if (!request) continue;
130+
131+
if (tileData.empty) {
132+
this._setEmptyTile(request.tile, request.done);
133+
} else if (tileData.data) {
134+
request.tile.src = 'data:image/png;base64,' + tileData.data;
135+
request.tile.onload = () => request.done(null, request.tile);
136+
request.tile.onerror = () => request.done(new Error('Image load failed'), request.tile);
137+
} else if (tileData.error) {
138+
request.done(new Error(tileData.error), request.tile);
139+
}
140+
}
141+
})
142+
.catch(error => {
143+
console.error('Batch chunk failed:', error);
144+
for (const [key, request] of batch) {
145+
request.done(error, request.tile);
146+
}
147+
});
148+
},
149+
150+
_setEmptyTile: function(tile, done) {
151+
if (!this._emptyTileUrl) {
152+
this._emptyTileUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
153+
}
154+
tile.src = this._emptyTileUrl;
155+
done(null, tile);
156+
}
157+
});
158+
159+
L.tileLayer.batch = function(urlTemplate, options) {
160+
return new L.TileLayer.Batch(urlTemplate, options);
161+
};
162+
4163
// Config - 1 tile = 1 chunk = 32 blocks
5164
const CHUNK_SIZE = 32;
6165
const TILE_SIZE = 256;
166+
const SCALE = TILE_SIZE / CHUNK_SIZE; // 8 - Leaflet units per block
7167

8168
// State
9169
let map = null;
@@ -26,23 +186,23 @@
26186

27187
map = L.map('map', {
28188
crs: L.CRS.Simple,
29-
minZoom: -6,
189+
minZoom: -4,
30190
maxZoom: 4,
31191
zoomSnap: 0.5,
32192
zoomDelta: 0.5,
33193
maxBounds: worldBounds,
34194
maxBoundsViscosity: 1.0
35195
});
36196

37-
// Start at origin (zoomed out)
38-
map.setView([0, 0], -3);
197+
// Start at origin
198+
map.setView([0, 0], 0);
39199

40200
updateTileLayer();
41201

42202
map.on('mousemove', function(e) {
43-
// In CRS.Simple with our setup: lat = -Z, lng = X
44-
const x = Math.round(e.latlng.lng);
45-
const z = Math.round(-e.latlng.lat);
203+
// Convert Leaflet coords to world coords (divide by scale factor)
204+
const x = Math.round(e.latlng.lng / SCALE);
205+
const z = Math.round(-e.latlng.lat / SCALE);
46206
document.getElementById('coords-display').textContent = `X: ${x}, Z: ${z}`;
47207
});
48208

@@ -54,25 +214,29 @@
54214
map.removeLayer(tileLayer);
55215
}
56216

57-
// Custom tile layer - use zoomOffset so tiles are always fetched at native zoom
58-
tileLayer = L.tileLayer('/api/tiles/' + currentWorld + '/0/{x}/{y}.png', {
217+
// Batch tile layer - reduces HTTP requests by batching multiple tiles per request
218+
tileLayer = L.tileLayer.batch('/api/tiles/' + currentWorld + '/0/{x}/{y}.png', {
59219
tileSize: TILE_SIZE,
60220
minNativeZoom: 0,
61221
maxNativeZoom: 0,
62-
minZoom: -6,
222+
minZoom: -4,
63223
maxZoom: 4,
64224
noWrap: true,
65225
bounds: [[-100000, -100000], [100000, 100000]],
226+
batchDelay: 300,
227+
maxBatchSize: 2000,
228+
batchEndpoint: '/api/tiles/batch'
66229
});
67230

231+
tileLayer.setWorld(currentWorld);
68232
tileLayer.addTo(map);
69233
}
70234

71235
// Convert world coords to LatLng
72236
function worldToLatLng(x, z) {
73237
// X -> lng, Z -> -lat (north is up, Z increases south)
74-
// Scale by chunk size since tiles represent chunks
75-
return L.latLng(-z, x);
238+
// Multiply by SCALE since Leaflet uses tile-pixel coords (256px per 32-block chunk)
239+
return L.latLng(-z * SCALE, x * SCALE);
76240
}
77241

78242
async function loadWorlds() {

target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
com/easywebmap/web/WebServer.class
2+
com/easywebmap/web/handlers/BatchTileHandler.class
23
com/easywebmap/tracker/PlayerTracker.class
34
com/easywebmap/web/handlers/StaticHandler.class
45
com/easywebmap/web/WebSocketHandler.class
6+
com/easywebmap/web/handlers/BatchTileHandler$TileCoord.class
57
com/easywebmap/commands/EasyWebMapCommand.class
68
com/easywebmap/map/TileCache$1.class
79
com/easywebmap/web/handlers/PlayerHandler.class

target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
/Users/golemgrid/Documents/GitHub.nosync/EasyMap/src/main/java/com/easywebmap/web/handlers/StaticHandler.java
66
/Users/golemgrid/Documents/GitHub.nosync/EasyMap/src/main/java/com/easywebmap/tracker/PlayerTracker.java
77
/Users/golemgrid/Documents/GitHub.nosync/EasyMap/src/main/java/com/easywebmap/web/WebServer.java
8+
/Users/golemgrid/Documents/GitHub.nosync/EasyMap/src/main/java/com/easywebmap/web/handlers/BatchTileHandler.java
89
/Users/golemgrid/Documents/GitHub.nosync/EasyMap/src/main/java/com/easywebmap/map/PngEncoder.java
910
/Users/golemgrid/Documents/GitHub.nosync/EasyMap/src/main/java/com/easywebmap/web/WebSocketHandler.java
1011
/Users/golemgrid/Documents/GitHub.nosync/EasyMap/src/main/java/com/easywebmap/web/handlers/PlayerHandler.java

0 commit comments

Comments
 (0)