diff --git a/Dockerfile b/Dockerfile
index 2833be55..328abd62 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,7 +7,11 @@ FROM golang:1.24.3 as go-builder
COPY . /data
COPY --from=bundle-builder /public/js/bundle* /data/public/js/
WORKDIR /data
-RUN CGO_ENABLED=0 go build .
+RUN --mount=type=cache,target=/go/pkg/mod \
+ go mod download
+RUN --mount=type=cache,target=/go/pkg/mod \
+ --mount=type=cache,target=/root/.cache/go-build \
+ CGO_ENABLED=0 go build .
FROM alpine:3.21.3
COPY --from=go-builder /data/mapserver /bin/mapserver
@@ -15,4 +19,4 @@ ENV MT_CONFIG_PATH "mapserver.json"
ENV MT_LOGLEVEL "INFO"
ENV MT_READONLY "false"
EXPOSE 8080
-ENTRYPOINT ["/bin/mapserver"]
\ No newline at end of file
+ENTRYPOINT ["/bin/mapserver"]
diff --git a/app/config.go b/app/config.go
index 036f1b89..b56bcecd 100644
--- a/app/config.go
+++ b/app/config.go
@@ -71,6 +71,7 @@ func ParseConfig(filename string) (*Config, error) {
Digiterms: true,
Digilines: true,
Travelnet: true,
+ RespawnPlaces: true,
MapserverPlayer: true,
MapserverPOI: true,
MapserverLabel: true,
@@ -109,6 +110,10 @@ func ParseConfig(filename string) (*Config, error) {
SkinsPath: "",
}
+ respawn := RespawnConfig{
+ PlacesColor: "green",
+ }
+
cfg := Config{
ConfigVersion: 1,
Port: 8080,
@@ -134,6 +139,7 @@ func ParseConfig(filename string) (*Config, error) {
WorldPath: "./",
DataPath: "./",
ColorsTxtPath: "./",
+ Respawn: &respawn,
}
info, err := os.Stat(filename)
diff --git a/app/types.go b/app/types.go
index b4340539..7d037dfe 100644
--- a/app/types.go
+++ b/app/types.go
@@ -27,6 +27,7 @@ type Config struct {
WorldPath string `json:"worldpath"`
DataPath string `json:"datapath"`
ColorsTxtPath string `json:"colorstxtpath"`
+ Respawn *RespawnConfig `json:"respawn"`
}
type MapBlockAccessorConfig struct {
@@ -49,6 +50,7 @@ type MapObjectConfig struct {
Digiterms bool `json:"digiterms"`
Digilines bool `json:"digilines"`
Travelnet bool `json:"travelnet"`
+ RespawnPlaces bool `json:"respawn_places"`
MapserverPlayer bool `json:"mapserver_player"`
MapserverPOI bool `json:"mapserver_poi"`
MapserverLabel bool `json:"mapserver_label"`
@@ -82,3 +84,7 @@ type SkinsConfig struct {
EnableSkinsDB bool `json:"enableskinsdb"`
SkinsPath string `json:"skinspath"`
}
+
+type RespawnConfig struct {
+ PlacesColor string `json:"placescolor"`
+}
diff --git a/dev/mapserver.json b/dev/mapserver.json
index d1588d90..3d87a452 100644
--- a/dev/mapserver.json
+++ b/dev/mapserver.json
@@ -45,6 +45,7 @@
"digiterms": true,
"digilines": true,
"travelnet": true,
+ "respawn_places": true,
"mapserver_player": true,
"mapserver_poi": true,
"mapserver_label": true,
@@ -82,5 +83,8 @@
},
"worldpath": "./",
"datapath": "./",
- "colorstxtpath": "./"
-}
\ No newline at end of file
+ "colorstxtpath": "./",
+ "respawn": {
+ "placescolor": "green"
+ }
+}
diff --git a/doc/config.md b/doc/config.md
index 97884420..fcd28c1d 100644
--- a/doc/config.md
+++ b/doc/config.md
@@ -50,6 +50,7 @@ The mapserver will generate a fresh `mapserver.json` if there is none at startup
"digiterms": true,
"digilines": true,
"travelnet": true,
+ "respawn_places": true,
"mapserver": true,
"mission": true,
"jumpdrive": true,
@@ -62,10 +63,13 @@ The mapserver will generate a fresh `mapserver.json` if there is none at startup
"purgetime": "15s",
"maxitems": 5000
},
- "skins": {
- "enableskinsdb": true,
- "skinspath": "/path/to/minetest/mods/skinsdb/textures"
- }
+ "skins": {
+ "enableskinsdb": true,
+ "skinspath": "/path/to/minetest/mods/skinsdb/textures"
+ },
+ "respawn": {
+ "placescolor": "green"
+ },
}
```
@@ -129,3 +133,6 @@ Enables support for serving/displaying custom player skins provided by the Skins
The path to where SkinsDB textures are stored. This should be the SkinsDB textures directory.
Example: `/path/to/minetest/mods/skinsdb/textures`
+
+#### respawn.placescolor
+The [marker color](https://github.com/lennardv2/Leaflet.awesome-markers) of the map pin for each respawn point.
diff --git a/mapobject/mcl_sign.go b/mapobject/mcl_sign.go
new file mode 100644
index 00000000..7a80b23c
--- /dev/null
+++ b/mapobject/mcl_sign.go
@@ -0,0 +1,49 @@
+package mapobject
+
+import (
+ "regexp"
+ "strconv"
+ "strings"
+ "mapserver/mapobjectdb"
+ "mapserver/types"
+
+ "github.com/minetest-go/mapparser"
+)
+
+var utextNumberRE = regexp.MustCompile(`\d+`)
+func decodeUText(value string) string {
+ // Extract all decimal numbers from:
+ // return {115,105,103,110,49,10,65,10,97}
+ matches := utextNumberRE.FindAllString(value, -1)
+ if len(matches) == 0 {
+ return ""
+ }
+
+ var b strings.Builder
+
+ for _, match := range matches {
+ n, err := strconv.Atoi(match)
+ if err != nil || n < 0 || n > 255 {
+ continue
+ }
+
+ b.WriteByte(byte(n))
+ }
+
+ return b.String()
+}
+
+type MclSignBlock struct {
+ Material string
+}
+
+func (this *MclSignBlock) onMapObject(mbpos *types.MapBlockCoords, x, y, z int, block *mapparser.MapBlock) *mapobjectdb.MapObject {
+ md := block.Metadata.GetMetadata(x, y, z)
+ displayText := decodeUText(md["utext"])
+
+ o := mapobjectdb.NewMapObject(mbpos, x, y, z, "sign")
+ o.Attributes["display_text"] = displayText
+ o.Attributes["material"] = this.Material
+
+ return o
+}
diff --git a/mapobject/setup.go b/mapobject/setup.go
index 831e7f70..6e504f59 100644
--- a/mapobject/setup.go
+++ b/mapobject/setup.go
@@ -190,61 +190,82 @@ func Setup(ctx *app.App) {
if ctx.Config.MapObjects.Signs {
l.AddMapObject("default:sign_wall_wood", &SignBlock{Material: "wood"})
l.AddMapObject("default:sign_wall_steel", &SignBlock{Material: "steel"})
- l.AddMapObject("mcl_signs:wall_sign", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign22_5", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign45", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign67_5", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:wall_sign_sprucewood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign_sprucewood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign22_5_sprucewood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign45_sprucewood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign67_5_sprucewood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:wall_sign_darkwood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign_darkwood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign22_5_darkwood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign45_darkwood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign67_5_darkwood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:wall_sign_junglewood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign_junglewood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign22_5_junglewood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign45_junglewood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign67_5_junglewood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:wall_sign_acaciawood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign_acaciawood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign22_5_acaciawood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign45_acaciawood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign67_5_acaciawood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:wall_sign_mangrove_wood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign_mangrove_wood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign22_5_mangrove_wood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign45_mangrove_wood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign67_5_mangrove_wood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:wall_sign_warped_hyphae_wood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign_warped_hyphae_wood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign22_5_warped_hyphae_wood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign45_warped_hyphae_wood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign67_5_warped_hyphae_wood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:wall_sign_crimson_hyphae_wood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign_crimson_hyphae_wood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign22_5_crimson_hyphae_wood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign45_crimson_hyphae_wood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign67_5_crimson_hyphae_wood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:wall_sign_bamboo", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign_bamboo", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign22_5_bamboo", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign45_bamboo", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign67_5_bamboo", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:wall_sign_birchwood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign_birchwood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign22_5_birchwood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign45_birchwood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign67_5_birchwood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:wall_sign_cherrywood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign_cherrywood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign22_5_cherrywood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign45_cherrywood", &SignBlock{Material: "wood"})
- l.AddMapObject("mcl_signs:standing_sign67_5_cherrywood", &SignBlock{Material: "wood"})
+ // Mineclonia legacy
+ l.AddMapObject("mcl_signs:wall_sign", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign22_5", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign45", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign67_5", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:wall_sign_sprucewood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign_sprucewood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign22_5_sprucewood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign45_sprucewood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign67_5_sprucewood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:wall_sign_darkwood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign_darkwood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign22_5_darkwood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign45_darkwood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign67_5_darkwood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:wall_sign_junglewood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign_junglewood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign22_5_junglewood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign45_junglewood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign67_5_junglewood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:wall_sign_acaciawood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign_acaciawood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign22_5_acaciawood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign45_acaciawood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign67_5_acaciawood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:wall_sign_mangrove_wood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign_mangrove_wood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign22_5_mangrove_wood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign45_mangrove_wood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign67_5_mangrove_wood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:wall_sign_warped_hyphae_wood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign_warped_hyphae_wood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign22_5_warped_hyphae_wood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign45_warped_hyphae_wood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign67_5_warped_hyphae_wood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:wall_sign_crimson_hyphae_wood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign_crimson_hyphae_wood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign22_5_crimson_hyphae_wood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign45_crimson_hyphae_wood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign67_5_crimson_hyphae_wood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:wall_sign_bamboo", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign_bamboo", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign22_5_bamboo", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign45_bamboo", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign67_5_bamboo", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:wall_sign_birchwood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign_birchwood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign22_5_birchwood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign45_birchwood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign67_5_birchwood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:wall_sign_cherrywood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign_cherrywood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign22_5_cherrywood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign45_cherrywood", &MclSignBlock{Material: "wood"})
+ l.AddMapObject("mcl_signs:standing_sign67_5_cherrywood", &MclSignBlock{Material: "wood"})
+ // Mineclonia current
+ prefix := "mcl_signs:"
+ materials := []string{
+ "acacia", "bamboo", "birch", "cherry_blossom", "crimson",
+ "dark_oak", "jungle", "mangrove", "oak", "pale_oak",
+ "spruce", "warped",
+ }
+ types := []string{
+ "hanging_sign",
+ "hanging_sign_attached",
+ "hanging_sign_wall",
+ "standing_sign",
+ "wall_sign",
+ }
+ for _, t := range types {
+ for _, m := range materials {
+ blockName := prefix + t + "_" + m
+ l.AddMapObject(blockName, &MclSignBlock{Material: "wood"})
+ }
+ }
}
//Phonograph
diff --git a/public/js/map/Overlaysetup.js b/public/js/map/Overlaysetup.js
index 7fad4155..8a851a4b 100644
--- a/public/js/map/Overlaysetup.js
+++ b/public/js/map/Overlaysetup.js
@@ -4,6 +4,7 @@ import ShopOverlay from './overlays/ShopOverlay.js';
import LabelOverlay from './overlays/LabelOverlay.js';
import TrainlineOverlay from './overlays/TrainlineOverlay.js';
import TravelnetOverlay from './overlays/TravelnetOverlay.js';
+import RespawnOverlay from './overlays/RespawnOverlay.js';
import BonesOverlay from './overlays/BonesOverlay.js';
import LcdOverlay from './overlays/LcdOverlay.js';
import DigitermOverlay from './overlays/DigitermOverlay.js';
@@ -81,6 +82,13 @@ export default function(cfg, map, overlays, wsChannel){
}
}
+ if (cfg.mapobjects.respawn_places) {
+ overlays["Respawn places"] = new RespawnOverlay(cfg);
+ if (isDefault("respawn_places")) {
+ map.addLayer(overlays.RespawnPlaces);
+ }
+ }
+
if (cfg.mapobjects.bones) {
overlays.Bones = new BonesOverlay();
if (isDefault("bones")) {
diff --git a/public/js/map/overlays/RespawnOverlay.js b/public/js/map/overlays/RespawnOverlay.js
new file mode 100644
index 00000000..dfc9093b
--- /dev/null
+++ b/public/js/map/overlays/RespawnOverlay.js
@@ -0,0 +1,119 @@
+import debounce from '../../util/debounce.js';
+import layerMgr from '../../LayerManager.js';
+
+function escapeHtml(value) {
+ return String(value)
+ .replaceAll("&", "&")
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll('"', """)
+ .replaceAll("'", "'");
+}
+
+const get_places = async () => {
+ const response = await fetch("api/respawnplaces", {
+ headers: {
+ "Accept": "application/json"
+ }
+ });
+
+ if (!response.ok) {
+ throw new Error(
+ `Unable to load respawn places: ${response.status} ${response.statusText}`
+ );
+ }
+ const places = await response.json();
+ return places || {};
+};
+
+export default L.LayerGroup.extend({
+ initialize: function(cfg) {
+ L.LayerGroup.prototype.initialize.call(this);
+ this.placescolor = "green";
+ if (cfg.respawn.placescolor)
+ this.placescolor = cfg.respawn.placescolor;
+ this.currentObjects = {};
+ this.reDraw = this.reDraw.bind(this);
+ this.onMapMove = debounce(this.onMapMove.bind(this), 50);
+ // no onMinetestUpdate needed, like AirUtilsPlanesOverlay.js. Places do not change very often.
+ },
+
+ createPopup: function(place, name) {
+ if (!name) name = "unnamed";
+ let html = "" + escapeHtml(name) + "";
+ if (place.full_name)
+ html += "
" + escapeHtml(place.full_name);
+ return html;
+ },
+
+ getIcon: function(color){
+ return L.AwesomeMarkers.icon({
+ // person-arrow-up-from-line is my first choice, but not present
+ // in the bundled fontawesome file.
+ icon: "user-plus",
+ prefix: "fa",
+ markerColor: color || this.placescolor || "green"
+ })
+ },
+
+ createMarker: function(place, name) {
+ let marker = L.marker([place.pos.z + 0.5, place.pos.x + 0.5]);
+ marker.bindPopup(this.createPopup(place, name));
+ marker.setIcon(this.getIcon());
+ return marker;
+ },
+
+ isInCurrentLayer: function(plane) {
+ let mapLayer = layerMgr.getCurrentLayer();
+
+ return (
+ plane.pos.y >= (mapLayer.from*16) &&
+ plane.pos.y <= ((mapLayer.to*16) + 15)
+ );
+ },
+
+ getMaxDisplayedZoom: function(){
+ return 5;
+ },
+
+ reDraw: async function() {
+ if (this.map.getZoom() < this.getMaxDisplayedZoom()) {
+ this.currentObjects = {};
+ this.clearLayers();
+ return;
+ }
+
+ const places = await get_places();
+ this.currentObjects = {};
+ this.clearLayers();
+
+ Object.entries(places).forEach(([name,place]) => {
+ if (!this.isInCurrentLayer(place)) {
+ // not in current layer
+ return;
+ }
+ let marker = this.createMarker(place, name);
+ marker.addTo(this);
+ this.currentObjects[name] = marker;
+ });
+ },
+
+ onMapMove: function(){
+ this.reDraw(false);
+ },
+
+ onLayerChange: function(/*layer*/){
+ this.reDraw(true);
+ },
+
+ onAdd: function(map) {
+ this.map = map;
+ map.on("zoomend", this.onMapMove);
+ this.reDraw();
+ },
+
+ onRemove: function(map) {
+ map.off("zoomend", this.onMapMove);
+ this.clearLayers();
+ }
+});
diff --git a/readme.md b/readme.md
index 26a5ff14..b475c04a 100644
--- a/readme.md
+++ b/readme.md
@@ -65,6 +65,7 @@ See: [Incremental rendering](doc/incrementalrendering.md)
* Protector display
* LCD Displays as markers
* Monitoring with [Prometheus](doc/prometheus.md)
+* Mod [respawn](https://gitlab.com/cronvel/mt-respawn) places
## Planned Features
@@ -121,4 +122,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
-This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
\ No newline at end of file
+This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
diff --git a/respawnparser/respawnparser.go b/respawnparser/respawnparser.go
new file mode 100644
index 00000000..d99024d6
--- /dev/null
+++ b/respawnparser/respawnparser.go
@@ -0,0 +1,61 @@
+package respawnparser
+// ref https://github.com/minetest-go/areasparser/blob/master/parser.go
+// duck.ai
+// docker go cache help: https://oneuptime.com/blog/post/2026-02-08-how-to-speed-up-docker-build-for-go-projects/view
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+)
+
+type File map[string]RespawnPlace
+
+type RespawnPlace struct {
+ FullName string `json:"full_name,omitempty"`
+ Pos GenericPos `json:"pos"`
+ Look *Direction `json:"look,omitempty"`
+}
+
+type GenericPos struct {
+ X int `json:"x"`
+ Y int `json:"y"`
+ Z int `json:"z"`
+}
+
+type Direction struct {
+ H float64 `json:"h"`
+ V float64 `json:"v"`
+}
+
+func getInt(o interface{}) int {
+ v, _ := o.(float64)
+ return int(v)
+}
+
+func (pos *GenericPos) UnmarshalJSON(data []byte) error {
+ m := make(map[string]interface{})
+ err := json.Unmarshal(data, &m)
+ if err != nil {
+ return err
+ }
+ pos.X = getInt(m["x"])
+ pos.Y = getInt(m["y"])
+ pos.Z = getInt(m["z"])
+ // do not cast to int like https://github.com/minetest-go/areasparser/blob/master/parser.go
+ return nil
+}
+
+func ParseFile(filename string) (File, error) {
+ data, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return nil, err
+ }
+
+ var respawnplaces File
+ if err := json.Unmarshal(data, &respawnplaces); err != nil {
+ return nil, fmt.Errorf("parse %s: %w", filename, err)
+ }
+
+ return respawnplaces, nil
+}
diff --git a/web/config.go b/web/config.go
index 966fadb9..9c384a19 100644
--- a/web/config.go
+++ b/web/config.go
@@ -15,6 +15,7 @@ type PublicConfig struct {
DefaultOverlays []string `json:"defaultoverlays"`
PageName string `json:"pagename"`
EnableSearch bool `json:"enablesearch"`
+ Respawn *app.RespawnConfig `json:"respawn"`
}
func (api *Api) GetConfig(resp http.ResponseWriter, req *http.Request) {
@@ -27,6 +28,7 @@ func (api *Api) GetConfig(resp http.ResponseWriter, req *http.Request) {
webcfg.DefaultOverlays = api.Context.Config.DefaultOverlays
webcfg.PageName = api.Context.Config.PageName
webcfg.EnableSearch = api.Context.Config.EnableSearch
+ webcfg.Respawn = api.Context.Config.Respawn
json.NewEncoder(resp).Encode(webcfg)
}
diff --git a/web/respawn.go b/web/respawn.go
new file mode 100644
index 00000000..483abeae
--- /dev/null
+++ b/web/respawn.go
@@ -0,0 +1,57 @@
+package web
+
+import (
+ "encoding/json"
+ "net/http"
+
+ "os"
+ "sync"
+ "time"
+
+ "mapserver/app"
+ "mapserver/respawnparser"
+)
+
+type RespawnPlacesHandler struct {
+ ctx *app.App
+ cache map[string]respawnparser.RespawnPlace
+ lasttime int64
+}
+
+var mutex_respawn = &sync.Mutex{}
+
+const RESPAWN_PLACES_FILENAME = "places.respawn.db"
+
+func (h *RespawnPlacesHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+ info, err := os.Stat(RESPAWN_PLACES_FILENAME)
+ if info == nil || err != nil {
+ // no places file
+ resp.Header().Add("content-type", "application/json")
+ resp.Write([]byte("[]"))
+ return
+ }
+
+ now := time.Now().Unix()
+ diff := now - h.lasttime
+
+ if diff > 5 {
+ mutex_respawn.Lock()
+
+ h.lasttime = now
+ places, err := respawnparser.ParseFile(RESPAWN_PLACES_FILENAME)
+
+ if err != nil {
+ resp.WriteHeader(500)
+ resp.Write([]byte(err.Error()))
+ return
+ }
+
+ h.cache = places
+
+ mutex_respawn.Unlock()
+ }
+
+ resp.Header().Add("content-type", "application/json")
+ json.NewEncoder(resp).Encode(h.cache)
+
+}
diff --git a/web/serve.go b/web/serve.go
index 90f3ac54..39da95c1 100644
--- a/web/serve.go
+++ b/web/serve.go
@@ -48,6 +48,10 @@ func Serve(ctx *app.App) {
mux.Handle("/api/areas", &AreasHandler{ctx: ctx})
}
+ if ctx.Config.MapObjects.RespawnPlaces {
+ mux.Handle("/api/respawnplaces", &RespawnPlacesHandler{ctx: ctx})
+ }
+
if ctx.Config.EnablePrometheus {
mux.Handle("/metrics", promhttp.Handler())
}