diff --git a/docs/config.rst b/docs/config.rst
index 9909f13..200c5b2 100644
--- a/docs/config.rst
+++ b/docs/config.rst
@@ -1248,6 +1248,29 @@ BiomeOverlay
BiomeOverlay(biomes=[("Forest", (0, 255, 0)), ("Desert", (255, 0, 0))])
+HeatmapOverlay
+ Color the map according to when a chunk was last visited. The color for Timestamps
+ between t_invisible and t_full will be interpolated between 0 and 255.
+ This RenderPrimitive might require use of the forcerender option.
+ Otherwise the Overlay might not get updated for not visited chunks (resulting in them
+ always being the brightest color, as if recently visited).
+
+ **Options**
+
+ t_invisible
+ The timestamp when the overlay will get invisible. The default is 30 days ago.
+
+ t_now
+ The timestamp when the overlay will be fully visible. The default is today.
+
+ Example::
+
+ HeatmapOverlay(
+ t_invisible=int((t_now - timedelta(days=2)).timestamp()),
+ t_full=int(t_now.timestamp()),
+ )
+
+
Defining Custom Rendermodes
---------------------------
diff --git a/overviewer_core/rendermodes.py b/overviewer_core/rendermodes.py
index 7ae67fe..75e7ce0 100644
--- a/overviewer_core/rendermodes.py
+++ b/overviewer_core/rendermodes.py
@@ -12,6 +12,7 @@
#
# You should have received a copy of the GNU General Public License along
# with the Overviewer. If not, see .
+from datetime import datetime, timedelta
from PIL import Image
from . import textures
@@ -231,6 +232,17 @@ class BiomeOverlay(Overlay):
'alpha' : ('an integer value between 0 (transparent) and 255 (opaque)', None),
}
+class HeatmapOverlay(Overlay):
+ t_now = datetime.now()
+ name = "overlay-heatmap"
+ options = {
+ 't_invisible': (
+ 'the timestamp when the overlay will get invisible (e.g. 1 month go)',
+ int((t_now - timedelta(days=30)).timestamp())
+ ),
+ 't_full': ('the timestamp when the overlay will be fully visible (e.g. now)', int(t_now.timestamp())),
+ }
+
class Hide(RenderPrimitive):
name = "hide"
options = {
diff --git a/overviewer_core/src/primitives/overlay-heatmap.c b/overviewer_core/src/primitives/overlay-heatmap.c
new file mode 100644
index 0000000..cd7666b
--- /dev/null
+++ b/overviewer_core/src/primitives/overlay-heatmap.c
@@ -0,0 +1,93 @@
+/*
+ * This file is part of the Minecraft Overviewer.
+ *
+ * Minecraft Overviewer is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Minecraft Overviewer 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 General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with the Overviewer. If not, see .
+ */
+
+#include
+#include "overlay.h"
+
+typedef struct {
+ /* inherits from overlay */
+ RenderPrimitiveOverlay parent;
+ int t_invisible;
+ int delta_t;
+} RenderPrimitiveHeatmap;
+
+static void get_color(void* data, RenderState* state,
+ uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) {
+ RenderPrimitiveHeatmap* self = (RenderPrimitiveHeatmap*)data;
+ long mtime;
+ float _value_f;
+ char value;
+ PyObject *mtime_pyobj;
+
+ // Set default values (will get overridden based on self->mode)
+ *r = 255;
+ *g = 0;
+ *b = 0;
+
+ // Get the chunk modified time
+ mtime_pyobj = PyObject_CallMethod(state->regionset, "get_chunk_mtime", "ii", state->chunkx, state->chunkz);
+ if (mtime_pyobj == NULL) {
+ *a = 0;
+ return;
+ }
+ mtime = PyLong_AsLong(mtime_pyobj);
+
+ // Convert the time to a value in the range [0,255] based on t_invisible and delta_t
+ _value_f = (mtime - self->t_invisible) / (float)self->delta_t;
+ value = _value_f <= 0 ? 0 : (_value_f >= 1 ? 255 : 255*_value_f);
+ *a = value;
+}
+
+static bool
+overlay_heatmap_start(void* data, RenderState* state, PyObject* support) {
+ RenderPrimitiveHeatmap* self;
+ int t_full;
+
+ /* first, chain up */
+ bool ret = primitive_overlay.start(data, state, support);
+ if (ret != false)
+ return ret;
+
+ /* now do custom initializations */
+ self = (RenderPrimitiveHeatmap*)data;
+ self->parent.get_color = get_color;
+
+ if (!render_mode_parse_option(support, "t_invisible", "I", &(self->t_invisible)))
+ return true;
+
+ if (!render_mode_parse_option(support, "t_full", "I", &t_full))
+ return true;
+
+ self->delta_t = t_full - self->t_invisible;
+ return false;
+}
+
+static void
+overlay_heatmap_finish(void* data, RenderState* state) {
+ /* chain up */
+ primitive_overlay.finish(data, state);
+}
+
+RenderPrimitiveInterface primitive_overlay_heatmap = {
+ "overlay-heatmap",
+ sizeof(RenderPrimitiveHeatmap),
+ overlay_heatmap_start,
+ overlay_heatmap_finish,
+ NULL,
+ NULL,
+ overlay_draw,
+};