From 07fc781c3accf18d2f9e7f6926b4ec3d14649893 Mon Sep 17 00:00:00 2001
From: dg <dg@51575b47-30f0-44d4-a5cc-537603b46e54>
Date: Sun, 13 Feb 2011 12:55:23 +0000
Subject: [PATCH] New smooth FOV shading (aka fog of war). Instead of being
 squares it will smooth transitions between seens and unseen tiles (only works
 with opengl framebuffers enabled)

git-svn-id: http://svn.net-core.org/repos/t-engine4@2715 51575b47-30f0-44d4-a5cc-537603b46e54
---
 game/engines/default/engine/Map.lua           |  11 +-
 game/modules/tome/class/Game.lua              |  26 +--
 game/modules/tome/class/Player.lua            |   1 +
 .../tome/data/gfx/shaders/main_fbo.frag       |  36 +++-
 .../tome/data/gfx/shaders/main_fbo.lua        |   1 +
 .../gfx/shockbolt/trap/poison_vines01.png     | Bin 7969 -> 10320 bytes
 game/modules/tome/data/talents/misc/npcs.lua  |   3 +-
 src/core_lua.c                                |   2 +-
 src/map.c                                     | 166 +++++++++++++++---
 src/map.h                                     |   8 +-
 10 files changed, 202 insertions(+), 52 deletions(-)

diff --git a/game/engines/default/engine/Map.lua b/game/engines/default/engine/Map.lua
index 8ddb87af03..3dc3841d93 100644
--- a/game/engines/default/engine/Map.lua
+++ b/game/engines/default/engine/Map.lua
@@ -444,14 +444,17 @@ function _M:minimapDisplay(dx, dy, x, y, w, h, transp)
 	self._map:toScreenMiniMap(dx, dy, x, y, w, h, transp or 0.6)
 end
 
---- Displays the map on a surface
--- @return a surface containing the drawn map
-function _M:display(x, y, nb_keyframe)
+--- Displays the map on screen
+-- @param x the coord where to start drawing, if null it uses self.display_x
+-- @param y the coord where to start drawing, if null it uses self.display_y
+-- @param nb_keyframes the number of keyframes elapsed since last draw
+-- @param always_show tell the map code to force display unseed entities as remembered (used for smooth FOV shading)
+function _M:display(x, y, nb_keyframe, always_show)
 	nb_keyframes = nb_keyframes or 1
 	local ox, oy = self.display_x, self.display_y
 	self.display_x, self.display_y = x or self.display_x, y or self.display_y
 
-	self._map:toScreen(self.display_x, self.display_y, nb_keyframe)
+	self._map:toScreen(self.display_x, self.display_y, nb_keyframe, always_show)
 
 	-- Tactical display
 	if self.view_faction then
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index df762e958b..2625f73bfa 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -338,6 +338,14 @@ function _M:setupDisplayMode(reboot, mode)
 		if gfx.tiles == "ascii" then Map.tiles.use_images = false Map.tiles.force_back_color = {r=0, g=0, b=0, a=255} end
 		if gfx.tiles == "ascii_full" then Map.tiles.use_images = false end
 		if gfx.tiles == "shockbolt" then Map.tiles.nicer_tiles = true end
+
+		-- Create the framebuffer
+		self.fbo = core.display.newFBO(Map.viewport.width, Map.viewport.height)
+		if self.fbo then
+			self.fbo_shader = Shader.new("main_fbo")
+			if not self.fbo_shader.shad then self.fbo = nil self.fbo_shader = nil end
+		end
+		if self.player then self.player:updateMainShader() end
 	end
 
 	if not mode or mode == "postinit" then
@@ -347,14 +355,6 @@ function _M:setupDisplayMode(reboot, mode)
 			self.level.map:moveViewSurround(self.player.x, self.player.y, 8, 8)
 		end
 		self:setupMiniMap()
-
-		-- Create the framebuffer
-		self.fbo = core.display.newFBO(Map.viewport.width, Map.viewport.height)
-		if self.fbo then
-			self.fbo_shader = Shader.new("main_fbo")
-			if not self.fbo_shader.shad then self.fbo = nil self.fbo_shader = nil end
-		end
-		if self.player then self.player:updateMainShader() end
 	end
 end
 
@@ -729,7 +729,8 @@ function _M:display(nb_keyframes)
 	-- Now the map, if any
 	if self.level and self.level.map and self.level.map.finished then
 		-- Display the map and compute FOV for the player if needed
-		if self.level.map.changed then
+		local changed = self.level.map.changed
+		if changed then
 			self.player:playerFOV()
 		end
 
@@ -738,18 +739,21 @@ function _M:display(nb_keyframes)
 			self.fbo:use(true)
 
 			if self.level.data.background then self.level.data.background(self.level, 0, 0, nb_keyframes) end
-			self.level.map:display(0, 0, nb_keyframes)
-			self.target:display(0, 0)
+			self.level.map:display(0, 0, nb_keyframes, true) -- Display at the base of the FBO and make sure to display everything as the shader will do smooth FOV over it
 			if self.level.data.foreground then self.level.data.foreground(self.level, 0, 0, nb_keyframes) end
 			if self.level.data.weather_particle then self.state:displayWeather(self.level, self.level.data.weather_particle, nb_keyframes) end
 
 			self.fbo:use(false)
 			_2DNoise:bind(1, false)
+			if changed then self.level.map._map:updateSeensTexture() end
+			self.level.map._map:bindSeensTexture(2)
+			self.fbo_shader:setUniform("seensinfo", {self.level.map._map:getSeensInfo()})
 			self.fbo:toScreen(
 				self.level.map.display_x, self.level.map.display_y,
 				self.level.map.viewport.width, self.level.map.viewport.height,
 				self.fbo_shader.shad
 			)
+			self.target:display()
 
 		-- Basic display
 		else
diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua
index a84ff27ac5..6d141ac749 100644
--- a/game/modules/tome/class/Player.lua
+++ b/game/modules/tome/class/Player.lua
@@ -731,6 +731,7 @@ function _M:playerUseItem(object, item, inven)
 	if game.zone.wilderness then game.logPlayer(self, "You cannot use items on the world map.") return end
 
 	local use_fct = function(o, inven, item)
+		if not o then return end
 		local co = coroutine.create(function()
 			self.changed = true
 
diff --git a/game/modules/tome/data/gfx/shaders/main_fbo.frag b/game/modules/tome/data/gfx/shaders/main_fbo.frag
index 013d4d9cba..5e68631fc9 100644
--- a/game/modules/tome/data/gfx/shaders/main_fbo.frag
+++ b/game/modules/tome/data/gfx/shaders/main_fbo.frag
@@ -5,11 +5,35 @@ uniform float tick;
 uniform sampler2D noisevol;
 uniform vec2 texSize;
 uniform sampler2D tex;
+uniform sampler2D seens;
+uniform vec4 seensinfo;
 uniform vec3 colorize;
 
+// Return the given pixel, this allows things like blur to work even with smooth fov shading
+vec4 get_pixel(vec2 coord)
+{
+	/*
+	 * Few lines to do some tricky things
+	 * The game provides use with a "seens" texture that is the computed FOV
+	 * We use this to lookup the current tile and shadow it as needed
+	 * seenscoords is arranged as this: (tile_w, tile_h, view_scene_w, view_scene_h)
+	 * We offset by 1x1.25 tiles .. dont ask why, it just works :/
+	 */
+	vec2 seenscoord = vec2((((coord.x + 64 / texSize.x) / seensinfo.r)) * texSize.x / seensinfo.b, (((coord.y + 80 / texSize.y) / seensinfo.g)) * texSize.y / seensinfo.a);
+	vec3 seen = texture2D(seens, seenscoord);
+	vec4 res = texture2D(tex, coord.xy);
+	res.r *= seen.r;
+	res.g *= seen.g;
+	res.b *= seen.b;
+
+	// Now we got our shaded pixel, we can do otehr stuff with it
+//	res = texture2D(tex, coord.xy); // this is a non shading version
+	return res;
+}
+
 void main(void)
 {
-	gl_FragColor = texture2D(tex, gl_TexCoord[0].xy);
+	gl_FragColor = get_pixel(gl_TexCoord[0]);
 
 	if (motionblur > 0.0)
 	{
@@ -34,7 +58,7 @@ void main(void)
 			{
 				for(int j = -blursize; j <= 0; j++)
 				{
-					sample += texture2D(tex, vec2(gl_TexCoord[0].xy+vec2(float(i)*offset.x, float(j)*offset.y)));
+					sample += get_pixel(vec2(gl_TexCoord[0].xy+vec2(float(i)*offset.x, float(j)*offset.y)));
 				}
 			}
 		}
@@ -44,7 +68,7 @@ void main(void)
 			{
 				for(int j = 0; j <= blursize; j++)
 				{
-					sample += texture2D(tex, vec2(gl_TexCoord[0].xy+vec2(float(i)*offset.x, float(j)*offset.y)));
+					sample += get_pixel(vec2(gl_TexCoord[0].xy+vec2(float(i)*offset.x, float(j)*offset.y)));
 				}
 			}
 		}
@@ -54,7 +78,7 @@ void main(void)
 			{
 				for(int j = -blursize; j <= 0; j++)
 				{
-					sample += texture2D(tex, vec2(gl_TexCoord[0].xy+vec2(float(i)*offset.x, float(j)*offset.y)));
+					sample += get_pixel(vec2(gl_TexCoord[0].xy+vec2(float(i)*offset.x, float(j)*offset.y)));
 				}
 			}
 		}
@@ -64,7 +88,7 @@ void main(void)
 			{
 				for(int j = 0; j <= blursize; j++)
 				{
-					sample += texture2D(tex, vec2(gl_TexCoord[0].xy+vec2(float(i)*offset.x, float(j)*offset.y)));
+					sample += get_pixel(vec2(gl_TexCoord[0].xy+vec2(float(i)*offset.x, float(j)*offset.y)));
 				}
 			}
 		}
@@ -86,7 +110,7 @@ void main(void)
 		{
 			for(int j = -blursize; j <= blursize; j++)
 			{
-				sample += texture2D(tex, vec2(gl_TexCoord[0].xy+vec2(float(i)*offset.x, float(j)*offset.y)));
+				sample += get_pixel(vec2(gl_TexCoord[0].xy+vec2(float(i)*offset.x, float(j)*offset.y)));
 			}
 		}
 		sample /= (blur*2.0) * (blur*2.0);
diff --git a/game/modules/tome/data/gfx/shaders/main_fbo.lua b/game/modules/tome/data/gfx/shaders/main_fbo.lua
index 9eadd16867..423e85468f 100644
--- a/game/modules/tome/data/gfx/shaders/main_fbo.lua
+++ b/game/modules/tome/data/gfx/shaders/main_fbo.lua
@@ -23,6 +23,7 @@ return {
 	args = {
 		tex = { texture = 0 },
 		noisevol = { texture = 1 },
+		seens = { texture = 2 },
 	},
 	clone = false,
 }
diff --git a/game/modules/tome/data/gfx/shockbolt/trap/poison_vines01.png b/game/modules/tome/data/gfx/shockbolt/trap/poison_vines01.png
index e84fe111458c95d0156c93396b062a4ec06cc997..d073c63f575fba21148045b53381f90981bfd1df 100644
GIT binary patch
delta 10291
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
FV1oG*C@=s3

delta 7921
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
bvj05*C97VGR&u}?00000NkvXXu0mjf1+KaA

diff --git a/game/modules/tome/data/talents/misc/npcs.lua b/game/modules/tome/data/talents/misc/npcs.lua
index 58d104ba4a..a54e967427 100644
--- a/game/modules/tome/data/talents/misc/npcs.lua
+++ b/game/modules/tome/data/talents/misc/npcs.lua
@@ -744,13 +744,14 @@ newTalent{
 			detect_power = 6 * self:getTalentLevel(t), disarm_power = 10 * self:getTalentLevel(t),
 			level_range = {self.level, self.level},
 			message = "@Target@ is caught in a web!",
+			pin_dur = dur,
 			canTrigger = function(self, x, y, who)
 				if who.type == "spiderkin" then return false end
 				return mod.class.Trap.canTrigger(self, x, y, who)
 			end,
 			triggered = function(self, x, y, who)
 				if who:checkHit(self.disarm_power + 5, who:combatPhysicalResist(), 0, 95, 15) and who:canBe("stun") and who:canBe("pin") then
-					who:setEffect(who.EFF_PINNED, dur, {})
+					who:setEffect(who.EFF_PINNED, self.pin_dur, {})
 				else
 					game.logSeen(who, "%s resists!", who.name:capitalize())
 				end
diff --git a/src/core_lua.c b/src/core_lua.c
index a6e2cbc9dd..56ab81fcde 100644
--- a/src/core_lua.c
+++ b/src/core_lua.c
@@ -1373,7 +1373,7 @@ static int gl_fbo_toscreen(lua_State *L)
 	if (lua_isuserdata(L, 6))
 	{
 		shader_type *s = (shader_type*)auxiliar_checkclass(L, "gl{program}", 6);
-		useShader(s, 0, 0, w, h, r, g, b, a);
+		useShader(s, fbo->w, fbo->h, w, h, r, g, b, a);
 	}
 
 	glDisable(GL_BLEND);
diff --git a/src/map.c b/src/map.c
index 8771dd872b..b9e7fae71d 100644
--- a/src/map.c
+++ b/src/map.c
@@ -497,17 +497,25 @@ static int map_new(lua_State *L)
 	map->mwidth = mwidth;
 	map->mheight = mheight;
 	map->grids = calloc(w, sizeof(map_object***));
-	map->grids_seens = calloc(w, sizeof(float*));
+	map->grids_seens = calloc(w * h, sizeof(float));
 	map->grids_remembers = calloc(w, sizeof(bool*));
 	map->grids_lites = calloc(w, sizeof(bool*));
 	map->minimap = calloc(w, sizeof(unsigned char*));
 	printf("C Map size %d:%d :: %d\n", mwidth, mheight,mwidth * mheight);
 
+	glGenTextures(1, &(map->seens_texture));
+	tglBindTexture(GL_TEXTURE_2D, map->seens_texture);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	glTexImage2D(GL_TEXTURE_2D, 0, 4, map->mwidth + 3, map->mheight + 3, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
+	map->seens_map = calloc((map->mwidth + 3)*(map->mheight + 3)*4, sizeof(GLubyte));
+
 	for (i = 0; i < w; i++)
 	{
 		map->grids[i] = calloc(h, sizeof(map_object**));
 		for (j = 0; j < h; j++) map->grids[i][j] = calloc(zdepth, sizeof(map_object*));
-		map->grids_seens[i] = calloc(h, sizeof(float));
+//		map->grids_seens[i] = calloc(h, sizeof(float));
 		map->grids_remembers[i] = calloc(h, sizeof(bool));
 		map->grids_lites[i] = calloc(h, sizeof(bool));
 		map->minimap[i] = calloc(h, sizeof(unsigned char));
@@ -525,7 +533,7 @@ static int map_free(lua_State *L)
 	{
 		for (j = 0; j < map->h; j++) free(map->grids[i][j]);
 		free(map->grids[i]);
-		free(map->grids_seens[i]);
+//		free(map->grids_seens[i]);
 		free(map->grids_remembers[i]);
 		free(map->grids_lites[i]);
 		free(map->minimap[i]);
@@ -542,6 +550,9 @@ static int map_free(lua_State *L)
 
 	luaL_unref(L, LUA_REGISTRYINDEX, map->mo_list_ref);
 
+	glDeleteTextures(1, &map->seens_texture);
+	free(map->seens_map);
+
 	lua_pushnumber(L, 1);
 	return 1;
 }
@@ -645,7 +656,7 @@ static int map_set_seen(lua_State *L)
 	float v = lua_tonumber(L, 4);
 
 	if (x < 0 || y < 0 || x >= map->w || y >= map->h) return 0;
-	map->grids_seens[x][y] = v;
+	map->grids_seens[y*map->w+x] = v;
 	return 0;
 }
 
@@ -680,7 +691,7 @@ static int map_clean_seen(lua_State *L)
 
 	for (i = 0; i < map->w; i++)
 		for (j = 0; j < map->h; j++)
-			map->grids_seens[i][j] = 0;
+			map->grids_seens[j*map->w+i] = 0;
 	return 0;
 }
 
@@ -706,6 +717,87 @@ static int map_clean_lite(lua_State *L)
 	return 0;
 }
 
+static int map_get_seensinfo(lua_State *L)
+{
+	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
+	lua_pushnumber(L, map->tile_w);
+	lua_pushnumber(L, map->tile_h);
+	lua_pushnumber(L, map->seensinfo_w);
+	lua_pushnumber(L, map->seensinfo_h);
+	return 4;
+}
+
+static int map_update_seen_texture(lua_State *L)
+{
+	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
+
+	tglBindTexture(GL_TEXTURE_2D, map->seens_texture);
+
+	int mx = map->used_mx;
+	int my = map->used_my;
+	GLubyte *seens = map->seens_map;
+	int ptr = 0;
+	int ii, jj;
+	map->seensinfo_w = map->mwidth + 3;
+	map->seensinfo_h = map->mheight + 3;
+
+	for (jj = map->mheight + 3 - 1; jj >= 0; jj--)
+	{
+		for (ii = 0; ii < map->mwidth + 3; ii++)
+		{
+			int i = mx - 1 + ii, j = my - 1 + jj;
+			if ((i < 0) || (j < 0) || (i >= map->w) || (j >= map->h))
+			{
+				seens[ptr] = 0;
+				seens[ptr+1] = 0;
+				seens[ptr+2] = 0;
+				seens[ptr+3] = 255;
+				ptr += 4;
+				continue;
+			}
+			float v = map->grids_seens[j*map->w+i] * 255;
+			if (v)
+			{
+				if (v > 255) v = 255;
+				if (v < 0) v = 0;
+				seens[ptr] = (GLubyte)(map->shown_b * v);
+				seens[ptr+1] = (GLubyte)(map->shown_g * v);
+				seens[ptr+2] = (GLubyte)(map->shown_r * v);
+			}
+			else if (map->grids_remembers[i][j])
+			{
+				seens[ptr] = (GLubyte)(map->obscure_b * 255);
+				seens[ptr+1] = (GLubyte)(map->obscure_g * 255);
+				seens[ptr+2] = (GLubyte)(map->obscure_r * 255);
+			}
+			else
+			{
+				seens[ptr] = 0;
+				seens[ptr+1] = 0;
+				seens[ptr+2] = 0;
+			}
+			seens[ptr+3] = 255;
+			ptr += 4;
+		}
+	}
+	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, map->mwidth + 3, map->mheight + 3, GL_BGRA, GL_UNSIGNED_BYTE, seens);
+
+	return 0;
+}
+
+static int map_bind_seen_texture(lua_State *L)
+{
+	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
+	int unit = luaL_checknumber(L, 2);
+	if (unit > 0 && !multitexture_active) return 0;
+
+	if (unit > 0) tglActiveTexture(GL_TEXTURE0+unit);
+	tglBindTexture(GL_TEXTURE_2D, map->seens_texture);
+	if (unit > 0) tglActiveTexture(GL_TEXTURE0);
+
+	return 0;
+}
+
 static int map_set_scroll(lua_State *L)
 {
 	map_type *map = (map_type*)auxiliar_checkclass(L, "core{map}", 1);
@@ -800,8 +892,8 @@ static int map_get_scroll(lua_State *L)
 	} \
 }
 
-inline void display_map_quad(GLuint *cur_tex, int *vert_idx, int *col_idx, map_type *map, int dx, int dy, float dz, map_object *m, int i, int j, float a, float seen, int nb_keyframes) ALWAYS_INLINE;
-void display_map_quad(GLuint *cur_tex, int *vert_idx, int *col_idx, map_type *map, int dx, int dy, float dz, map_object *m, int i, int j, float a, float seen, int nb_keyframes)
+inline void display_map_quad(GLuint *cur_tex, int *vert_idx, int *col_idx, map_type *map, int dx, int dy, float dz, map_object *m, int i, int j, float a, float seen, int nb_keyframes, bool always_show) ALWAYS_INLINE;
+void display_map_quad(GLuint *cur_tex, int *vert_idx, int *col_idx, map_type *map, int dx, int dy, float dz, map_object *m, int i, int j, float a, float seen, int nb_keyframes, bool always_show)
 {
 	float r, g, b;
 	GLfloat *vertices = map->vertices;
@@ -811,29 +903,40 @@ void display_map_quad(GLuint *cur_tex, int *vert_idx, int *col_idx, map_type *ma
 	/********************************************************
 	 ** Select the color to use
 	 ********************************************************/
-	if (seen)
+	if (always_show)
 	{
-		if (m->tint_r < 1 || m->tint_g < 1 || m->tint_b < 1)
-		{
-			r = (map->shown_r + m->tint_r)/2; g = (map->shown_g + m->tint_g)/2; b = (map->shown_b + m->tint_b)/2;
-		}
-		else
-		{
-			r = map->shown_r; g = map->shown_g; b = map->shown_b;
-		}
-		r *= seen;
-		g *= seen;
-		b *= seen;
+		// In smooth fov mode it's the shader that does FOV display
+		r = m->tint_r; g = m->tint_g; b = m->tint_b;
+		a = 1;
 	}
 	else
 	{
-		if (m->tint_r < 1 || m->tint_g < 1 || m->tint_b < 1)
+		if (seen)
 		{
-			r = (map->obscure_r + m->tint_r)/2; g = (map->obscure_g + m->tint_g)/2; b = (map->obscure_b + m->tint_b)/2;
+			if (m->tint_r < 1 || m->tint_g < 1 || m->tint_b < 1)
+			{
+				r = (map->shown_r + m->tint_r)/2; g = (map->shown_g + m->tint_g)/2; b = (map->shown_b + m->tint_b)/2;
+			}
+			else
+			{
+				r = map->shown_r; g = map->shown_g; b = map->shown_b;
+			}
+			r *= seen;
+			g *= seen;
+			b *= seen;
+			a = seen;
 		}
 		else
 		{
-			r = map->obscure_r; g = map->obscure_g; b = map->obscure_b;
+			if (m->tint_r < 1 || m->tint_g < 1 || m->tint_b < 1)
+			{
+				r = (map->obscure_r + m->tint_r)/2; g = (map->obscure_g + m->tint_g)/2; b = (map->obscure_b + m->tint_b)/2;
+			}
+			else
+			{
+				r = map->obscure_r; g = map->obscure_g; b = map->obscure_b;
+			}
+			a = map->obscure_r;
 		}
 	}
 
@@ -932,6 +1035,7 @@ static int map_to_screen(lua_State *L)
 	int x = luaL_checknumber(L, 2);
 	int y = luaL_checknumber(L, 3);
 	int nb_keyframes = luaL_checknumber(L, 4);
+	bool always_show = lua_toboolean(L, 5);
 	int i = 0, j = 0, z = 0;
 	int vert_idx = 0;
 	int col_idx = 0;
@@ -952,6 +1056,9 @@ static int map_to_screen(lua_State *L)
 	x -= animdx;
 	y -= animdy;
 
+	map->used_mx = mx;
+	map->used_my = my;
+
 	// Always display some more of the map to make sure we always see it all
 	for (z = 0; z < map->zdepth; z++)
 	{
@@ -966,15 +1073,15 @@ static int map_to_screen(lua_State *L)
 				map_object *mo = map->grids[i][j][z];
 				if (!mo) continue;
 
-				if ((mo->on_seen && map->grids_seens[i][j]) || (mo->on_remember && map->grids_remembers[i][j]) || mo->on_unknown)
+				if ((mo->on_seen && map->grids_seens[j*map->w+i]) || (mo->on_remember && (always_show || map->grids_remembers[i][j])) || mo->on_unknown)
 				{
-					if (map->grids_seens[i][j])
+					if (map->grids_seens[j*map->w+i])
 					{
-						display_map_quad(&cur_tex, &vert_idx, &col_idx, map, dx, dy, z, mo, i, j, map->shown_a, map->grids_seens[i][j], nb_keyframes);
+						display_map_quad(&cur_tex, &vert_idx, &col_idx, map, dx, dy, z, mo, i, j, map->shown_a, map->grids_seens[j*map->w+i], nb_keyframes, always_show);
 					}
 					else
 					{
-						display_map_quad(&cur_tex, &vert_idx, &col_idx, map, dx, dy, z, mo, i, j, map->obscure_a, 0, nb_keyframes);
+						display_map_quad(&cur_tex, &vert_idx, &col_idx, map, dx, dy, z, mo, i, j, map->obscure_a, 0, nb_keyframes, always_show);
 					}
 				}
 			}
@@ -1039,9 +1146,9 @@ static int minimap_to_screen(lua_State *L)
 				map_object *mo = map->grids[i][j][z];
 				if (!mo || mo->mm_r < 0) continue;
 
-				if ((mo->on_seen && map->grids_seens[i][j]) || (mo->on_remember && map->grids_remembers[i][j]) || mo->on_unknown)
+				if ((mo->on_seen && map->grids_seens[j*map->w+i]) || (mo->on_remember && map->grids_remembers[i][j]) || mo->on_unknown)
 				{
-					if (map->grids_seens[i][j])
+					if (map->grids_seens[j*map->w+i])
 					{
 						r = mo->mm_r; g = mo->mm_g; b = mo->mm_b; a = transp;
 						colors[col_idx] = r; colors[col_idx+1] = g; colors[col_idx+2] = b; colors[col_idx+3] = (a);
@@ -1097,6 +1204,8 @@ static const struct luaL_reg map_reg[] =
 {
 	{"__gc", map_free},
 	{"close", map_free},
+	{"updateSeensTexture", map_update_seen_texture},
+	{"bindSeensTexture", map_bind_seen_texture},
 	{"setZoom", map_set_zoom},
 	{"setShown", map_set_shown},
 	{"setObscure", map_set_obscure},
@@ -1107,6 +1216,7 @@ static const struct luaL_reg map_reg[] =
 	{"setSeen", map_set_seen},
 	{"setRemember", map_set_remember},
 	{"setLite", map_set_lite},
+	{"getSeensInfo", map_get_seensinfo},
 	{"setScroll", map_set_scroll},
 	{"getScroll", map_get_scroll},
 	{"toScreen", map_to_screen},
diff --git a/src/map.h b/src/map.h
index 08125680af..a885e8fb12 100644
--- a/src/map.h
+++ b/src/map.h
@@ -50,7 +50,7 @@ typedef struct {
 
 typedef struct {
 	map_object* ***grids;
-	float **grids_seens;
+	float *grids_seens;
 	bool **grids_remembers;
 	bool **grids_lites;
 	unsigned char **minimap;
@@ -58,6 +58,9 @@ typedef struct {
 	GLfloat *vertices;
 	GLfloat *colors;
 	GLfloat *texcoords;
+	GLubyte *seens_map;
+
+	GLuint seens_texture;
 
 	int mo_list_ref;
 
@@ -78,6 +81,9 @@ typedef struct {
 	int mx, my, mwidth, mheight;
 	float oldmx, oldmy;
 	int move_step, move_max;
+	float used_mx, used_my;
+	int seensinfo_w;
+	int seensinfo_h;
 } map_type;
 
 #endif
-- 
GitLab