commit 2c8283a0c38acf3315bf9620d1ae66aa5509356f
Author: Josh Matthews <josh@joshmatthews.net>
Date:   Mon Aug 13 02:12:01 2012 -0400

    Woo proxy

diff --git a/binaries/data/mods/public/simulation/components/BuildRestrictions.js b/binaries/data/mods/public/simulation/components/BuildRestrictions.js
index dcaab94..6bf2b2a 100644
--- a/binaries/data/mods/public/simulation/components/BuildRestrictions.js
+++ b/binaries/data/mods/public/simulation/components/BuildRestrictions.js
@@ -90,9 +90,16 @@ BuildRestrictions.prototype.CheckPlacement = function(player)
 	default:
 		passClassName = "building-land";
 	}
+  
+	// Proxies for other players should never be obstructions
+	var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
+	var isIgnoreable = cmpVisual &&
+				cmpVisual.GetProxiedEntity() != INVALID_ENTITY &&
+				cmpVisual.GetProxyPlayerTarget() != player;
 
 	var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
-	if (!cmpObstruction || !cmpObstruction.CheckFoundation(passClassName))
+	if (!isIgnoreable &&
+            (!cmpObstruction || !cmpObstruction.CheckFoundation(passClassName)))
 	{
 		return false;	// Fail
 	}
diff --git a/binaries/data/mods/public/simulation/components/BuildingAI.js b/binaries/data/mods/public/simulation/components/BuildingAI.js
index a2c5985..8139cbb 100644
--- a/binaries/data/mods/public/simulation/components/BuildingAI.js
+++ b/binaries/data/mods/public/simulation/components/BuildingAI.js
@@ -17,6 +17,9 @@ BuildingAI.prototype.Schema =
  */
 BuildingAI.prototype.Init = function()
 {
+	if (IsProxyEntity(this.entity))
+		return;
+
 	if (this.GetDefaultArrowCount() > 0 || this.GetGarrisonArrowMultiplier() > 0)
 	{
 		var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
@@ -38,6 +41,15 @@ BuildingAI.prototype.OnOwnershipChanged = function(msg)
 		this.SetupGaiaRangeQuery(msg.to);
 };
 
+BuildingAI.prototype.OnVisibilityChanged = function(msg)
+{
+	if (msg.source != this.entity)
+		return;
+	var cmpOwnHealth = Engine.QueryInterface(this.entity, IID_Health);
+	var cmpProxyHealth = Engine.QueryInterface(msg.proxy, IID_Health);
+	cmpProxyHealth.SetHitpoints(cmpOwnHealth.GetHitpoints());
+}
+
 /**
  * Cleanup on destroy
  */
@@ -63,6 +75,9 @@ BuildingAI.prototype.OnDestroy = function()
  */
 BuildingAI.prototype.SetupRangeQuery = function(owner)
 {
+	if (IsProxyEntity(this.entity))
+          return;
+
 	var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
 	var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
 	if (this.enemyUnitsQuery)
diff --git a/binaries/data/mods/public/simulation/components/Foundation.js b/binaries/data/mods/public/simulation/components/Foundation.js
index e7fc336..3060656 100644
--- a/binaries/data/mods/public/simulation/components/Foundation.js
+++ b/binaries/data/mods/public/simulation/components/Foundation.js
@@ -63,6 +63,15 @@ Foundation.prototype.IsFinished = function()
 	return (this.buildProgress >= 1.0);
 };
 
+Foundation.prototype.OnVisibilityChanged = function(msg)
+{
+	if (msg.source != this.entity)
+		return;
+	var cmpOwnHealth = Engine.QueryInterface(this.entity, IID_Health);
+	var cmpProxyHealth = Engine.QueryInterface(msg.proxy, IID_Health);
+	cmpProxyHealth.SetHitpoints(cmpOwnHealth.GetHitpoints());
+}
+
 Foundation.prototype.OnDestroy = function()
 {
 	// Refund a portion of the construction cost, proportional to the amount of build progress remaining
diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js
index 9be9f46..bdd50e5 100644
--- a/binaries/data/mods/public/simulation/components/GuiInterface.js
+++ b/binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -23,6 +23,7 @@ GuiInterface.prototype.Init = function()
 	this.rallyPoints = undefined;
 	this.notifications = [];
 	this.renamedEntities = [];
+	this.proxiedEntities = {};
 };
 
 /*
@@ -124,12 +125,16 @@ GuiInterface.prototype.GetExtendedSimulationState = function(player)
 
 GuiInterface.prototype.GetRenamedEntities = function(player)
 {
-	return this.renamedEntities;
+	var proxied = [];
+	if (player in this.proxiedEntities)
+		proxied = this.proxiedEntities[player];
+	return this.renamedEntities.concat(proxied);
 };
 
 GuiInterface.prototype.ClearRenamedEntities = function(player)
 {
 	this.renamedEntities = [];
+	this.proxiedEntities[player] = [];
 };
 
 GuiInterface.prototype.GetEntityState = function(player, ent)
@@ -1614,6 +1619,26 @@ GuiInterface.prototype.OnGlobalEntityRenamed = function(msg)
 	this.renamedEntities.push(msg);
 }
 
+// Ensure that any GUI code interacting with proxies can continue
+// to interact with the original entity if the proxy is destroyed
+// (ie. the original entity becomes visible).
+GuiInterface.prototype.OnGlobalDestroy = function(msg)
+{
+	var proxy = GetProxiedEntity(msg.entity);
+	if (proxy == INVALID_ENTITY)
+		return;
+	this.renamedEntities.push({"type": "EntityRenamed", "entity": msg.entity, "newentity": proxy});
+}
+
+GuiInterface.prototype.OnGlobalVisibilityChanged = function(msg)
+{
+	if (msg.source == INVALID_ENTITY)
+		return;
+	if (!(msg.player in this.proxiedEntities))
+		this.proxiedEntities[msg.player] = [];
+	this.proxiedEntities[msg.player].push({"type": "EntityRenamed", "entity": msg.source, "newentity": msg.proxy});
+}
+
 // List the GuiInterface functions that can be safely called by GUI scripts.
 // (GUI scripts are non-deterministic and untrusted, so these functions must be
 // appropriately careful. They are called with a first argument "player", which is
diff --git a/binaries/data/mods/public/simulation/components/ResourceSupply.js b/binaries/data/mods/public/simulation/components/ResourceSupply.js
index 2dbf4e4..4f15946 100644
--- a/binaries/data/mods/public/simulation/components/ResourceSupply.js
+++ b/binaries/data/mods/public/simulation/components/ResourceSupply.js
@@ -77,4 +77,13 @@ ResourceSupply.prototype.GetType = function()
 	return { "generic": type, "specific": subtype };
 };
 
+ResourceSupply.prototype.OnVisibilityChanged = function(msg)
+{
+	if (msg.source != this.entity)
+		return;
+	var entity = msg.proxy;
+	var supply = Engine.QueryInterface(entity, IID_ResourceSupply);
+	supply.amount = this.amount;
+}
+
 Engine.RegisterComponentType(IID_ResourceSupply, "ResourceSupply", ResourceSupply);
diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js
index 455a688..820a8e5 100644
--- a/binaries/data/mods/public/simulation/components/UnitAI.js
+++ b/binaries/data/mods/public/simulation/components/UnitAI.js
@@ -997,6 +997,11 @@ var UnitFsmSpec = {
 		},
 
 		"GATHER": {
+			"EntityRenamed": function(msg) {
+				if (this.order.data.target == msg.entity)
+					this.order.data.target = msg.newentity;
+			},
+
 			"APPROACHING": {
 				"enter": function() {
 					this.SelectAnimation("move");
@@ -1925,6 +1930,9 @@ UnitAI.prototype.OnDestroy = function()
 // Wrapper function that sets up the normal, healer, and Gaia range queries.
 UnitAI.prototype.SetupRangeQueries = function()
 {
+	if (IsProxyEntity(this.entity))
+		return;
+
 	this.SetupRangeQuery();
 
 	if (this.IsHealer())
@@ -1934,6 +1942,17 @@ UnitAI.prototype.SetupRangeQueries = function()
 		this.SetupGaiaRangeQuery();
 }
 
+// Ensure that any orders focused on a proxy are correctly
+// transferred to the original entity when the proxy is destroyed
+// (ie. the original entity becomes visible).
+UnitAI.prototype.OnGlobalDestroy = function(msg)
+{
+	var orig = GetProxiedEntity(msg.entity);
+	if (orig == INVALID_ENTITY)
+          return;
+	this.OnGlobalEntityRenamed({entity: msg.entity, newentity: orig});
+}
+
 // Set up a range query for all enemy units within LOS range
 // which can be attacked.
 // This should be called whenever our ownership changes.
@@ -2280,6 +2299,18 @@ UnitAI.prototype.OnGlobalEntityRenamed = function(msg)
 	UnitFsm.ProcessMessage(this, {"type": "EntityRenamed", "entity": msg.entity, "newentity": msg.newentity});
 };
 
+// If a proxy appears and it's targeted at this unit's owner,
+// pretend the original actor has been renamed to the proxy.
+UnitAI.prototype.OnGlobalVisibilityChanged = function(msg)
+{
+	if (msg.source == INVALID_ENTITY)
+		return;
+	var cmpVisual = Engine.QueryInterface(msg.proxy, IID_Visual);
+	var cmpOwner = Engine.QueryInterface(this.entity, IID_Ownership);
+	if (cmpVisual.GetProxyPlayerTarget() == cmpOwner.GetOwner())
+		this.OnGlobalEntityRenamed({entity: msg.source, newentity: msg.proxy});
+}
+
 UnitAI.prototype.OnAttacked = function(msg)
 {
 	UnitFsm.ProcessMessage(this, {"type": "Attacked", "data": msg});
@@ -2430,6 +2461,8 @@ UnitAI.prototype.FindNearbyFoundation = function()
 		if (cmpFoundation.IsFinished())
 			continue;
 
+		if (IsProxyEntity(ent))
+			continue;
 		return ent;
 	}
 
diff --git a/binaries/data/mods/public/simulation/helpers/Proxy.js b/binaries/data/mods/public/simulation/helpers/Proxy.js
new file mode 100644
index 0000000..be12585
--- /dev/null
+++ b/binaries/data/mods/public/simulation/helpers/Proxy.js
@@ -0,0 +1,15 @@
+function IsProxyEntity(entity)
+{
+	return GetProxiedEntity(entity) != INVALID_ENTITY;
+}
+
+function GetProxiedEntity(entity)
+{
+	var cmpVisual = Engine.QueryInterface(entity, IID_Visual);
+	if (!cmpVisual)
+		return INVALID_ENTITY;
+	return cmpVisual.GetProxiedEntity();
+}
+
+Engine.RegisterGlobal("IsProxyEntity", IsProxyEntity);
+Engine.RegisterGlobal("GetProxiedEntity", GetProxiedEntity);
diff --git a/source/network/NetTurnManager.cpp b/source/network/NetTurnManager.cpp
index 0eb332d..1eec386 100644
--- a/source/network/NetTurnManager.cpp
+++ b/source/network/NetTurnManager.cpp
@@ -32,6 +32,7 @@
 #include "ps/SavedGame.h"
 #include "scriptinterface/ScriptInterface.h"
 #include "simulation2/Simulation2.h"
+#include "simulation2/system/SimContext.h"
 
 #include <sstream>
 #include <fstream>
@@ -232,7 +233,9 @@ void CNetTurnManager::OnSyncError(u32 turn, const std::string& expectedHash)
 	bool ok = m_Simulation2.ComputeStateHash(hash, quick);
 	ENSURE(ok);
 
-	OsPath path = psLogDir()/"oos_dump.txt";
+	std::stringstream fname; 
+	fname << "oos_dump_" << m_Simulation2.GetSimContext().GetCurrentDisplayedPlayer() << ".txt";
+	OsPath path = psLogDir()/fname.str();
 	std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
 	m_Simulation2.DumpDebugState(file);
 	file.close();
diff --git a/source/simulation2/MessageTypes.h b/source/simulation2/MessageTypes.h
index f149f50..10ba220 100644
--- a/source/simulation2/MessageTypes.h
+++ b/source/simulation2/MessageTypes.h
@@ -24,6 +24,7 @@
 
 #include "simulation2/helpers/Player.h"
 #include "simulation2/helpers/Position.h"
+#include "simulation2/helpers/Player.h"
 
 #include "simulation2/components/ICmpPathfinder.h"
 
@@ -100,6 +101,16 @@ public:
 	fixed turnLength;
 };
 
+class CMessageUpdate_Visibility : public CMessage
+{
+public:
+	DEFAULT_MESSAGE_IMPL(Update_Visibility)
+
+	CMessageUpdate_Visibility()
+	{
+	}
+};
+
 /**
  * Final update phase, after all other updates.
  */
@@ -380,4 +391,30 @@ public:
 	player_id_t player;
 };
 
+/**
+ * Sent by range manager every simulation update to every entity that might have
+ * changed visibility states in the current frame.
+ *
+ * Also broadcast by visual actors for every new proxy created.
+ */
+class CMessageVisibilityChanged : public CMessage
+{
+public:
+	DEFAULT_MESSAGE_IMPL(VisibilityChanged)
+
+	CMessageVisibilityChanged(entity_id_t source, entity_id_t proxy) :
+		source(source), proxy(proxy)
+	{
+	}
+
+	CMessageVisibilityChanged() :
+		source(INVALID_ENTITY), proxy(INVALID_ENTITY)
+	{
+	}
+
+	// source: entity being proxied, or INVALID_ENTITY when indicating a visibility change
+	// proxy: new proxy entity, or INVALID_ENTITY when indicating a visibility change
+	entity_id_t source, proxy;
+};
+
 #endif // INCLUDED_MESSAGETYPES
diff --git a/source/simulation2/Simulation2.cpp b/source/simulation2/Simulation2.cpp
index 0021e59..bc6c5c9 100644
--- a/source/simulation2/Simulation2.cpp
+++ b/source/simulation2/Simulation2.cpp
@@ -501,6 +501,10 @@ void CSimulation2Impl::UpdateComponents(CSimContext& simContext, fixed turnLengt
 		componentManager.BroadcastMessage(msgUpdate);
 	}
 	{
+		CMessageUpdate_Visibility msgUpdate;
+		componentManager.BroadcastMessage(msgUpdate);
+	}
+	{
 		CMessageUpdate_Final msgUpdate(turnLengthFixed);
 		componentManager.BroadcastMessage(msgUpdate);
 	}
diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h
index 758b67b..a6ba356 100644
--- a/source/simulation2/TypeList.h
+++ b/source/simulation2/TypeList.h
@@ -34,6 +34,7 @@ MESSAGE(TurnStart)
 MESSAGE(Update)
 MESSAGE(Update_MotionFormation)
 MESSAGE(Update_MotionUnit)
+MESSAGE(Update_Visibility)
 MESSAGE(Update_Final)
 MESSAGE(Interpolate) // non-deterministic (use with caution)
 MESSAGE(RenderSubmit) // non-deterministic (use with caution)
@@ -48,6 +49,7 @@ MESSAGE(TerrainChanged)
 MESSAGE(TerritoriesChanged)
 MESSAGE(PathResult)
 MESSAGE(TechnologyModification)
+MESSAGE(VisibilityChanged)
 
 // TemplateManager must come before all other (non-test) components,
 // so that it is the first to be (de)serialized
diff --git a/source/simulation2/components/CCmpRangeManager.cpp b/source/simulation2/components/CCmpRangeManager.cpp
index 1363b1f..4a5a949 100644
--- a/source/simulation2/components/CCmpRangeManager.cpp
+++ b/source/simulation2/components/CCmpRangeManager.cpp
@@ -18,12 +18,14 @@
 #include "precompiled.h"
 
 #include "simulation2/system/Component.h"
+#include "simulation2/Simulation2.h"
 #include "ICmpRangeManager.h"
 
 #include "simulation2/MessageTypes.h"
 #include "simulation2/components/ICmpPosition.h"
 #include "simulation2/components/ICmpTerritoryManager.h"
 #include "simulation2/components/ICmpVision.h"
+#include "simulation2/components/ICmpVisual.h"
 #include "simulation2/helpers/Render.h"
 #include "simulation2/helpers/Spatial.h"
 
@@ -34,6 +36,7 @@
 #include "ps/CLogger.h"
 #include "ps/Overlay.h"
 #include "ps/Profile.h"
+#include "ps/Game.h"
 #include "renderer/Scene.h"
 
 #define DEBUG_RANGE_MANAGER_BOUNDS 0
@@ -190,6 +193,7 @@ public:
 		componentManager.SubscribeGloballyToMessageType(MT_Destroy);
 
 		componentManager.SubscribeToMessageType(MT_Update);
+		componentManager.SubscribeToMessageType(MT_Update_Visibility);
 
 		componentManager.SubscribeToMessageType(MT_RenderSubmit); // for debug overlays
 	}
@@ -212,8 +216,11 @@ public:
 	std::map<entity_id_t, EntityData> m_EntityData;
 	SpatialSubdivision<entity_id_t> m_Subdivision; // spatial index of m_EntityData
 
-	// LOS state:
+	// Set of entities that could have changed visibility status in the
+	// current simulation frame
+	std::set<entity_id_t> m_DirtyVisibility;
 
+	// LOS state:
 	std::map<player_id_t, bool> m_LosRevealAll;
 	bool m_LosCircular;
 	i32 m_TerrainVerticesPerSide;
@@ -331,6 +338,13 @@ public:
 			if (!cmpPosition)
 				break;
 
+			// XXXjdm On the surface this seems sensible, since proxy entities
+			//        should only exist in fogged areas. Are there any uses of
+			//        the range manager where we would care about fogged proxies?
+			CmpPtr<ICmpVisual> cmpVisual(GetSimContext(), ent);
+			if (cmpVisual && cmpVisual->GetProxiedEntity() != INVALID_ENTITY)
+				break;
+
 			// The newly-created entity will have owner -1 and position out-of-world
 			// (any initialisation of those values will happen later), so we can just
 			// use the default-constructed EntityData here
@@ -448,6 +462,16 @@ public:
 			ExecuteActiveQueries();
 			break;
 		}
+		case MT_Update_Visibility:
+		{
+			CMessageVisibilityChanged msg;
+			for (std::set<entity_id_t>::iterator it = m_DirtyVisibility.begin(); it != m_DirtyVisibility.end(); ++it)
+			{
+				g_Game->GetSimulation2()->PostMessage(*it, msg);
+			}
+			m_DirtyVisibility.clear();
+			break;
+		}
 		case MT_RenderSubmit:
 		{
 			const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
@@ -949,6 +973,26 @@ public:
 	virtual ELosVisibility GetLosVisibility(entity_id_t ent, player_id_t player, bool forceRetainInFog)
 	{
 		// (We can't use m_EntityData since this needs to handle LOCAL entities too)
+		bool blankProxyExists = false;
+
+		CmpPtr<ICmpVisual> cmpVisual(GetSimContext(), ent);
+		if (cmpVisual)
+		{
+			// Proxy entities for other players are never visible
+			player_id_t target = cmpVisual->GetProxyPlayerTarget();
+			if (target != INVALID_PLAYER && target != player)
+				return VIS_HIDDEN;
+
+			// If a regular proxy exists, we should never show this actor.
+			// If an empty proxy exists (ie. an invisible proxy for something
+			// created inside FoW), we want to show this actor if it would be
+			// visible, and show nothing if it's fogged.
+			entity_id_t proxy = cmpVisual->GetProxyForPlayer(player);
+			if (proxy == SYSTEM_ENTITY)
+				blankProxyExists = true;
+			else if (proxy != INVALID_ENTITY)
+				return VIS_HIDDEN;
+		}
 
 		// Entities not with positions in the world are never visible
 		CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), ent);
@@ -976,7 +1020,7 @@ public:
 			return VIS_VISIBLE;
 
 		// Fogged if the 'retain in fog' flag is set, and in a non-visible explored region
-		if (los.IsExplored(i, j))
+		if (!blankProxyExists && los.IsExplored(i, j))
 		{
 			CmpPtr<ICmpVision> cmpVision(GetSimContext(), ent);
 			if (forceRetainInFog || (cmpVision && cmpVision->GetRetainInFog()))
@@ -1057,6 +1101,12 @@ public:
 					m_LosState[i+1 + j*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
 					m_LosState[i + (j+1)*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
 					m_LosState[i+1 + (j+1)*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
+
+					// Add any entities in this tile to the dirty visibility map
+					CFixedVector2D start(fixed::FromInt(i*TERRAIN_TILE_SIZE), fixed::FromInt(j*TERRAIN_TILE_SIZE));
+					CFixedVector2D end(fixed::FromInt((i+1)*TERRAIN_TILE_SIZE), fixed::FromInt((j+1)*TERRAIN_TILE_SIZE));
+					std::vector<entity_id_t> ents = m_Subdivision.GetInRange(start, end);
+					m_DirtyVisibility.insert(ents.begin(), ents.end());
 				}
 			}
 		}
@@ -1117,6 +1167,12 @@ public:
 			ASSERT(counts[idx] < 65535);
 			counts[idx] = (u16)(counts[idx] + 1); // ignore overflow; the player should never have 64K units
 		}
+
+		// Add any entities in this strip to the dirty visibility map
+		CFixedVector2D start(fixed::FromInt(i0*TERRAIN_TILE_SIZE), fixed::FromInt(j*TERRAIN_TILE_SIZE));
+		CFixedVector2D end(fixed::FromInt(i1*TERRAIN_TILE_SIZE), fixed::FromInt(j*TERRAIN_TILE_SIZE));
+		std::vector<entity_id_t> ents = m_Subdivision.GetInRange(start, end);
+		m_DirtyVisibility.insert(ents.begin(), ents.end());
 	}
 
 	/**
@@ -1142,6 +1198,12 @@ public:
 				m_LosState[idx] &= ~(LOS_VISIBLE << (2*(owner-1)));
 			}
 		}
+
+		// Add any entities in this strip to the dirty visibility map
+		CFixedVector2D start(fixed::FromInt(i0*TERRAIN_TILE_SIZE), fixed::FromInt(j*TERRAIN_TILE_SIZE));
+		CFixedVector2D end(fixed::FromInt(i1*TERRAIN_TILE_SIZE), fixed::FromInt(j*TERRAIN_TILE_SIZE));
+		std::vector<entity_id_t> ents = m_Subdivision.GetInRange(start, end);
+		m_DirtyVisibility.insert(ents.begin(), ents.end());
 	}
 
 	/**
diff --git a/source/simulation2/components/CCmpTerritoryInfluence.cpp b/source/simulation2/components/CCmpTerritoryInfluence.cpp
index dcf5057..562114b 100644
--- a/source/simulation2/components/CCmpTerritoryInfluence.cpp
+++ b/source/simulation2/components/CCmpTerritoryInfluence.cpp
@@ -23,6 +23,7 @@
 #include "simulation2/components/ICmpOwnership.h"
 #include "simulation2/components/ICmpPlayerManager.h"
 #include "simulation2/components/ICmpTechnologyManager.h"
+#include "simulation2/components/ICmpVisual.h"
 
 class CCmpTerritoryInfluence : public ICmpTerritoryInfluence
 {
@@ -109,7 +110,14 @@ public:
 			CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY);
 			entity_id_t playerEnt = cmpPlayerManager->GetPlayerByID(cmpOwnership->GetOwner());
 
-			if (playerEnt != INVALID_ENTITY)
+			//XXXjdm This might be the same as just checking for being a proxy;
+			//       it's hard to envision a proxy targetted at its owner.
+			CmpPtr<ICmpVisual> cmpVisual(GetSimContext(), GetEntityId());
+			bool isIgnorable = cmpVisual &&
+					cmpVisual->GetProxiedEntity() != INVALID_ENTITY &&
+					cmpVisual->GetProxyPlayerTarget() != cmpOwnership->GetOwner();
+
+			if (playerEnt != INVALID_ENTITY && !isIgnorable)
 			{
 				CmpPtr<ICmpTechnologyManager> cmpTechnologyManager(GetSimContext(), playerEnt);
 				if (cmpTechnologyManager)
diff --git a/source/simulation2/components/CCmpTerritoryManager.cpp b/source/simulation2/components/CCmpTerritoryManager.cpp
index c8b1ec7..d0d1838 100644
--- a/source/simulation2/components/CCmpTerritoryManager.cpp
+++ b/source/simulation2/components/CCmpTerritoryManager.cpp
@@ -41,6 +41,7 @@
 #include "simulation2/components/ICmpSettlement.h"
 #include "simulation2/components/ICmpTerrain.h"
 #include "simulation2/components/ICmpTerritoryInfluence.h"
+#include "simulation2/components/ICmpVisual.h"
 #include "simulation2/helpers/Geometry.h"
 #include "simulation2/helpers/Grid.h"
 #include "simulation2/helpers/PriorityQueue.h"
@@ -378,6 +379,8 @@ void CCmpTerritoryManager::CalculateTerritories()
 	// Allow influence entities to override the terrain costs
 	RasteriseInfluences(influences, influenceGrid);
 
+	player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer();
+
 	// Split influence entities into per-player lists, ignoring any with invalid properties
 	std::map<player_id_t, std::vector<entity_id_t> > influenceEntities;
 	std::vector<entity_id_t> rootInfluenceEntities;
@@ -406,6 +409,15 @@ void CCmpTerritoryManager::CalculateTerritories()
 		if (!cmpPosition || !cmpPosition->IsInWorld())
 			continue;
 
+		// Ignore if a proxy for any other player but the current one, or
+		// if a proxy for this entity exists for the current entity.
+		CmpPtr<ICmpVisual> cmpVisual(GetSimContext(), it->first);
+		if (cmpVisual &&
+			(cmpVisual->GetProxiedEntity() != INVALID_ENTITY &&
+			 cmpVisual->GetProxyPlayerTarget() != currentPlayer) ||
+			(cmpVisual->GetProxyForPlayer(currentPlayer) != INVALID_ENTITY))
+			continue;
+
 		influenceEntities[owner].push_back(it->first);
 
 		if (cmpTerritoryInfluence->IsRoot())
diff --git a/source/simulation2/components/CCmpVisualActor.cpp b/source/simulation2/components/CCmpVisualActor.cpp
index 643c200..911152c 100644
--- a/source/simulation2/components/CCmpVisualActor.cpp
+++ b/source/simulation2/components/CCmpVisualActor.cpp
@@ -17,16 +17,22 @@
 
 #include "precompiled.h"
 
+#include <sstream>
+
+#include "simulation2/Simulation2.h"
 #include "simulation2/system/Component.h"
 #include "ICmpVisual.h"
 
 #include "ICmpOwnership.h"
+#include "ICmpPlayerManager.h"
 #include "ICmpPosition.h"
 #include "ICmpRangeManager.h"
 #include "ICmpVision.h"
+#include "ICmpTemplateManager.h"
 #include "simulation2/MessageTypes.h"
 #include "simulation2/components/ICmpFootprint.h"
 #include "simulation2/components/ICmpSelectable.h"
+#include "simulation2/serialization/SerializeTemplates.h"
 
 #include "graphics/Frustum.h"
 #include "graphics/Model.h"
@@ -37,8 +43,11 @@
 #include "graphics/UnitManager.h"
 #include "maths/Matrix3D.h"
 #include "maths/Vector3D.h"
+#include "network/NetTurnManager.h"
 #include "ps/CLogger.h"
+#include "ps/Game.h"
 #include "renderer/Scene.h"
+#include "lib/utf8.h"
 
 class CCmpVisualActor : public ICmpVisual
 {
@@ -49,6 +58,7 @@ public:
 		componentManager.SubscribeToMessageType(MT_Interpolate);
 		componentManager.SubscribeToMessageType(MT_RenderSubmit);
 		componentManager.SubscribeToMessageType(MT_OwnershipChanged);
+		componentManager.SubscribeToMessageType(MT_VisibilityChanged);
 		componentManager.SubscribeGloballyToMessageType(MT_TerrainChanged);
 	}
 
@@ -60,6 +70,15 @@ public:
 	fixed m_R, m_G, m_B; // shading colour
 
 	ICmpRangeManager::ELosVisibility m_Visibility; // only valid between Interpolate and RenderSubmit
+	std::vector<ICmpRangeManager::ELosVisibility> m_LastVisibility;
+	bool m_FirstVisUpdate;
+
+	typedef std::map<player_id_t, entity_id_t> ProxyEntityMap;
+	ProxyEntityMap m_ProxyEntities;
+	entity_id_t m_ProxiedEntity;
+	player_id_t m_ProxyPlayerTarget;
+
+	int32_t m_ActorSeed;
 
 	// Current animation state
 	fixed m_AnimRunThreshold; // if non-zero this is the special walk/run mode
@@ -141,6 +160,16 @@ public:
 
 		m_R = m_G = m_B = fixed::FromInt(1);
 
+		m_FirstVisUpdate = true;
+		CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY);
+		for (int32_t i = 0; i < cmpPlayerManager->GetNumPlayers(); i++)
+			m_LastVisibility.push_back(ICmpRangeManager::VIS_FOGGED);
+
+		m_ProxyPlayerTarget = INVALID_PLAYER;
+		m_ProxiedEntity = INVALID_ENTITY;
+
+		m_ActorSeed = GetEntityId();
+
 		if (!GetSimContext().HasUnitManager())
 			return; // do nothing further if graphics are disabled
 
@@ -151,37 +180,7 @@ public:
 		else
 			m_ActorName = paramNode.GetChild("Actor").ToString();
 
-		std::set<CStr> selections;
-		m_Unit = GetSimContext().GetUnitManager().CreateUnit(m_ActorName, GetActorSeed(), selections);
-		if (m_Unit)
-		{
-			CModelAbstract& model = m_Unit->GetModel();
-			if (model.ToCModel())
-			{
-				u32 modelFlags = 0;
-
-				if (paramNode.GetChild("SilhouetteDisplay").ToBool())
-					modelFlags |= MODELFLAG_SILHOUETTE_DISPLAY;
-
-				if (paramNode.GetChild("SilhouetteOccluder").ToBool())
-					modelFlags |= MODELFLAG_SILHOUETTE_OCCLUDER;
-
-				CmpPtr<ICmpVision> cmpVision(GetSimContext(), GetEntityId());
-				if (cmpVision && cmpVision->GetAlwaysVisible())
-					modelFlags |= MODELFLAG_IGNORE_LOS;
-
-				model.ToCModel()->AddFlagsRec(modelFlags);
-			}
-
-			// Initialize the model's selection shape descriptor. This currently relies on the component initialization order; the 
-			// Footprint component must be initialized before this component (VisualActor) to support the ability to use the footprint
-			// shape for the selection box (instead of the default recursive bounding box). See TypeList.h for the order in
-			// which components are initialized; if for whatever reason you need to get rid of this dependency, you can always just
-			// initialize the selection shape descriptor on-demand.
-			InitSelectionShapeDescriptor(model, paramNode);
-
-			m_Unit->SetID(GetEntityId());
-		}
+		ResetUnitSeed(m_ActorSeed, paramNode);
 
 		SelectAnimation("idle", false, fixed::FromInt(1), L"");
 	}
@@ -217,6 +216,12 @@ public:
 		serialize.NumberFixed_Unbounded("anim desync", m_AnimDesync);
 		serialize.NumberFixed_Unbounded("anim sync repeat time", m_AnimSyncRepeatTime);
 
+		serialize.NumberI32_Unbounded("seed", m_ActorSeed);
+		serialize.Bool("first vis update", m_FirstVisUpdate);
+		SerializeVector<SerializeU8_Enum<ICmpRangeManager::ELosVisibility, ICmpRangeManager::VIS_VISIBLE> >()(serialize, "last visibility state", m_LastVisibility);
+		serialize.NumberU32_Unbounded("proxied entity", m_ProxiedEntity);
+		serialize.NumberI32_Unbounded("proxy player target", m_ProxyPlayerTarget);
+		SerializeMap<SerializeI32_Unbounded, SerializeU32_Unbounded>()(serialize, "per-player proxy entities", m_ProxyEntities);
 		// TODO: store actor variables?
 	}
 
@@ -257,18 +262,26 @@ public:
 
 	virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
 	{
+		if (msg.GetType() == MT_VisibilityChanged)
+		{
+			const CMessageVisibilityChanged& msgData = static_cast<const CMessageVisibilityChanged&> (msg);
+			if (msgData.source == INVALID_ENTITY)
+				VisibilityChanged();
+			return;
+		}
+		else if (msg.GetType() == MT_Update_Final)
+		{
+			const CMessageUpdate_Final& msgData = static_cast<const CMessageUpdate_Final&> (msg);
+			Update(msgData.turnLength);
+			return;
+		}
+
 		// Quick exit for running in non-graphical mode
 		if (m_Unit == NULL)
 			return;
 
 		switch (msg.GetType())
 		{
-		case MT_Update_Final:
-		{
-			const CMessageUpdate_Final& msgData = static_cast<const CMessageUpdate_Final&> (msg);
-			Update(msgData.turnLength);
-			break;
-		}
 		case MT_Interpolate:
 		{
 			const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
@@ -285,6 +298,7 @@ public:
 		{
 			const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg);
 			m_Unit->GetModel().SetPlayerID(msgData.to);
+                        //XXXjdm do we need to update the ownership of proxies too?
 			break;
 		}
 		case MT_TerrainChanged:
@@ -468,6 +482,86 @@ public:
 		// TODO: should copy/reset silhouette flags
 	}
 
+	virtual entity_id_t GetProxyForPlayer(player_id_t player)
+	{
+		ProxyEntityMap::iterator result = m_ProxyEntities.find(player);
+		if (result != m_ProxyEntities.end()) {
+			return result->second;
+		}
+		return INVALID_ENTITY;
+	}
+
+	virtual void UnlinkProxyForPlayer(player_id_t player)
+	{
+		m_ProxyEntities.erase(player);
+	}
+
+	virtual entity_id_t GetProxiedEntity()
+	{
+		return m_ProxiedEntity;
+	}
+
+	virtual player_id_t GetProxyPlayerTarget()
+	{
+		return m_ProxyPlayerTarget;
+	}
+
+	virtual void SetProxyData(entity_id_t entity, player_id_t player)
+	{
+		m_ProxiedEntity = entity;
+		m_ProxyPlayerTarget = player;
+	}
+
+	void ResetUnitSeed(int32_t seed, const CParamNode& paramNode)
+	{
+		m_ActorSeed = seed;
+		if (!GetSimContext().HasUnitManager())
+			return;
+		if (m_Unit)
+			GetSimContext().GetUnitManager().DeleteUnit(m_Unit);
+
+		std::set<CStr> selections;
+		m_Unit = GetSimContext().GetUnitManager().CreateUnit(m_ActorName, GetActorSeed(), selections);
+		if (!m_Unit)
+			return;
+
+		CModelAbstract& model = m_Unit->GetModel();
+		if (model.ToCModel())
+		{
+			u32 modelFlags = 0;
+
+			if (paramNode.GetChild("SilhouetteDisplay").ToBool())
+				modelFlags |= MODELFLAG_SILHOUETTE_DISPLAY;
+
+			if (paramNode.GetChild("SilhouetteOccluder").ToBool())
+				modelFlags |= MODELFLAG_SILHOUETTE_OCCLUDER;
+
+			CmpPtr<ICmpVision> cmpVision(GetSimContext(), GetEntityId());
+			if (cmpVision && cmpVision->GetAlwaysVisible())
+				modelFlags |= MODELFLAG_IGNORE_LOS;
+
+			model.ToCModel()->AddFlagsRec(modelFlags);
+		}
+
+		// Initialize the model's selection shape descriptor. This currently relies on the component initialization order; the 
+		// Footprint component must be initialized before this component (VisualActor) to support the ability to use the footprint
+		// shape for the selection box (instead of the default recursive bounding box). See TypeList.h for the order in
+		// which components are initialized; if for whatever reason you need to get rid of this dependency, you can always just
+		// initialize the selection shape descriptor on-demand.
+		InitSelectionShapeDescriptor(model, paramNode);
+
+		m_Unit->SetID(GetEntityId());
+	}
+
+	virtual void ResetUnitSeed(int32_t seed, const std::string& templateName)
+	{
+			CSimulation2& simulation = *g_Game->GetSimulation2();
+			CmpPtr<ICmpTemplateManager> cmpTemplateManager(simulation, SYSTEM_ENTITY);
+			const CParamNode* rootNode = cmpTemplateManager->GetTemplate(templateName);
+			const CParamNode& paramNode = rootNode->GetChild("VisualActor");
+			ResetUnitSeed(seed, paramNode);
+	}
+
 private:
 	/// Whether the visual actor has been rendered at least once.
 	/// Necessary because the visibility update runs on simulation update,
@@ -476,7 +570,7 @@ private:
 
 	int32_t GetActorSeed()
 	{
-		return GetEntityId();
+		return m_ActorSeed;
 	}
 
 	/// Helper method; initializes the model selection shape descriptor from XML. Factored out for readability of @ref Init.
@@ -488,6 +582,7 @@ private:
 
 	void Update(fixed turnLength);
 	void UpdateVisibility();
+	void VisibilityChanged();
 	void Interpolate(float frameTime, float frameOffset);
 	void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
 };
@@ -572,6 +667,8 @@ void CCmpVisualActor::Update(fixed turnLength)
 		return;
 
 	UpdateVisibility();
+	if (m_FirstVisUpdate)
+		VisibilityChanged();
 
 	// If we're in the special movement mode, select an appropriate animation
 	if (!m_AnimRunThreshold.IsZero())
@@ -638,6 +735,104 @@ void CCmpVisualActor::UpdateVisibility()
 	}
 }
 
+void CCmpVisualActor::VisibilityChanged()
+{
+	CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY);
+	for (int32_t i = 0; i < cmpPlayerManager->GetNumPlayers() - 1; i++)
+	{
+		player_id_t player = i + 1;
+		CmpPtr<ICmpRangeManager> cmpRangeManager(GetSimContext(), SYSTEM_ENTITY);
+		ICmpRangeManager::ELosVisibility vis = cmpRangeManager->GetLosVisibility(GetEntityId(), player);
+
+		bool forceProxy = false;
+		if (m_FirstVisUpdate)
+		{
+			m_LastVisibility[i] = vis;
+			if (vis == ICmpRangeManager::VIS_FOGGED)
+			{
+				forceProxy = true;
+			}
+		}
+
+		// We need to create proxies for existing entities that are sitting just outside the starting
+		// visible territory at the beginning of the game. However, any entities created after game start
+		// inside fogged territory should not be visible.
+		if (vis != m_LastVisibility[i] || (forceProxy && g_Game->GetTurnManager()->GetCurrentTurn() == 1))
+		{
+			// We only care about entities that become fogged or visible.
+			// In the first case, we want to create a proxy entity for a particular player that retains
+			// a snapshot of this entity at this current moment.
+			// In the second case, we only care if a proxy entity becomes visible, at which point it
+			// should be destroyed (proxies can outlive their proxied entities).
+			
+			CSimulation2& simulation = *g_Game->GetSimulation2();
+			// If this actor is fogged, and is not a proxy, then create a proxy
+			if (vis == ICmpRangeManager::VIS_FOGGED && m_ProxiedEntity == INVALID_ENTITY)
+			{
+				// Spawn a new entity that is identical to the one being proxied
+				CmpPtr<ICmpTemplateManager> cmpTemplateManager(simulation, SYSTEM_ENTITY);
+				std::string templ = cmpTemplateManager->GetCurrentTemplateName(GetEntityId());
+				std::wstring wtempl = wstring_from_utf8(templ);
+				entity_id_t proxy = simulation.AddEntity(wtempl);
+				CmpPtr<ICmpVisual> proxyVis(simulation, proxy);
+				proxyVis->SetProxyData(GetEntityId(), player);
+				// Ensure any random seed is overwritten with this entity's seed
+				proxyVis->ResetUnitSeed(GetActorSeed(), templ);
+				m_ProxyEntities[player] = proxy;
+
+				// Ownership and position information are common among all relevant actors
+				CmpPtr<ICmpPosition> ownPos(GetSimContext(), GetEntityId());
+				if (ownPos)
+				{
+					CmpPtr<ICmpPosition> proxyPos(GetSimContext(), proxy);
+					CFixedVector3D pos = ownPos->GetPosition();
+					proxyPos->JumpTo(pos.X, pos.Z);
+					CFixedVector3D rot = ownPos->GetRotation();
+					proxyPos->SetYRotation(rot.Y);
+					proxyPos->SetXZRotation(rot.X, rot.Z);
+				}
+				CmpPtr<ICmpOwnership> ownOwnership(GetSimContext(), GetEntityId());
+				if (ownOwnership)
+				{
+					CmpPtr<ICmpOwnership> proxyOwnership(GetSimContext(), proxy);
+					proxyOwnership->SetOwner(ownOwnership->GetOwner());
+				}
+
+				// Broadcast this proxy creation so the relevant components can duplicate all
+				// component-specific properties
+				CMessageVisibilityChanged msg(GetEntityId(), proxy);
+				simulation.BroadcastMessage(msg);
+			}
+			else if (vis == ICmpRangeManager::VIS_VISIBLE)
+			{
+				if (m_ProxiedEntity != INVALID_ENTITY)
+				{
+					// Does the proxied actor still exist?
+					CmpPtr<ICmpVisual> proxyVis(simulation, m_ProxiedEntity);
+					if (proxyVis)
+					{
+						// Notify the original that this proxy no longer exists
+						proxyVis->UnlinkProxyForPlayer(player);
+					}
+					// The proxied actor should now be visible, so this proxy should die
+					simulation.DestroyEntity(GetEntityId());
+					break;
+				}
+				m_ProxyEntities.erase(player);
+ 			}
+		}
+		else if (forceProxy && m_ProxiedEntity == INVALID_ENTITY)
+		{
+			// This feels like a hack, but it's the easiest way to avoid rendering a physical proxy
+			// for a newly-created fogged entitiy.
+			m_ProxyEntities[player] = SYSTEM_ENTITY;
+		}
+		m_LastVisibility[i] = vis;
+	}
+
+	m_FirstVisUpdate = false;
+}
+
 void CCmpVisualActor::Interpolate(float frameTime, float frameOffset)
 {
 	if (m_Unit == NULL)
@@ -650,6 +845,7 @@ void CCmpVisualActor::Interpolate(float frameTime, float frameOffset)
 	else if (!m_PreviouslyRendered)
 	{
 		UpdateVisibility();
+		VisibilityChanged();
 		m_PreviouslyRendered = true;
 	}
 
diff --git a/source/simulation2/components/ICmpVisual.cpp b/source/simulation2/components/ICmpVisual.cpp
index 2a6e580..aa986c3 100644
--- a/source/simulation2/components/ICmpVisual.cpp
+++ b/source/simulation2/components/ICmpVisual.cpp
@@ -28,4 +28,6 @@ DEFINE_INTERFACE_METHOD_1("SetAnimationSyncRepeat", void, ICmpVisual, SetAnimati
 DEFINE_INTERFACE_METHOD_1("SetAnimationSyncOffset", void, ICmpVisual, SetAnimationSyncOffset, fixed)
 DEFINE_INTERFACE_METHOD_4("SetShadingColour", void, ICmpVisual, SetShadingColour, fixed, fixed, fixed, fixed)
 DEFINE_INTERFACE_METHOD_2("SetVariable", void, ICmpVisual, SetVariable, std::string, float)
+DEFINE_INTERFACE_METHOD_0("GetProxyPlayerTarget", player_id_t, ICmpVisual, GetProxyPlayerTarget)
+DEFINE_INTERFACE_METHOD_0("GetProxiedEntity", entity_id_t, ICmpVisual, GetProxiedEntity)
 END_INTERFACE_WRAPPER(Visual)
diff --git a/source/simulation2/components/ICmpVisual.h b/source/simulation2/components/ICmpVisual.h
index ade286e..e19923c 100644
--- a/source/simulation2/components/ICmpVisual.h
+++ b/source/simulation2/components/ICmpVisual.h
@@ -19,6 +19,7 @@
 #define INCLUDED_ICMPVISUAL
 
 #include "simulation2/system/Interface.h"
+#include "simulation2/helpers/Player.h"
 
 #include "ps/CStr.h"
 #include "maths/BoundingBoxOriented.h"
@@ -138,7 +139,42 @@ public:
 	 */
 	virtual void Hotload(const VfsPath& name) = 0;
 
-	DECLARE_INTERFACE_TYPE(Visual)
+    /**
+     * Return the ID of the corresponding proxy entity for the given player.
+	 * Returns INVALID_ENTITY if no such proxy exists.
+     */
+	virtual entity_id_t GetProxyForPlayer(player_id_t player) = 0;
+
+	/**
+	 * Remove any knowledge of a proxy entity for the given player. Called by proxy
+	 * entities on the source entity when the proxy is destroying itself.
+	 */
+	virtual void UnlinkProxyForPlayer(player_id_t player) = 0;
+
+	/**
+	 * Get the ID of the source entity for this entity. Returns INVALID_ENTITY if this
+	 * entity is not a proxy.
+	 */
+	virtual entity_id_t GetProxiedEntity() = 0;
+
+	/**
+	 * Get the ID of the player for whom this proxy entity is visible. Returns INVALID_PLAYER
+	 * if this entity is not a proxy.
+	 */
+	virtual player_id_t GetProxyPlayerTarget() = 0;
+
+	/**
+	 * Initialize this entity as a proxy for the given source entity, visible only to the
+	 * given player.
+	 */
+	virtual void SetProxyData(entity_id_t entity, player_id_t player) = 0;
+
+	/**
+	 * Set the actor seed of this entity and recreate the actor's unit appropriately.
+	 */
+	virtual void ResetUnitSeed(int32_t seed, const std::string& templateName) = 0;
+  
+    DECLARE_INTERFACE_TYPE(Visual)
 };
 
 // TODO: rename this to VisualActor, because the interface is actor-specific, maybe?
diff --git a/source/simulation2/scripting/MessageTypeConversions.cpp b/source/simulation2/scripting/MessageTypeConversions.cpp
index 21ed454..d5400e2 100644
--- a/source/simulation2/scripting/MessageTypeConversions.cpp
+++ b/source/simulation2/scripting/MessageTypeConversions.cpp
@@ -90,6 +90,17 @@ MESSAGE_1(Update_MotionFormation, fixed, turnLength)
 MESSAGE_1(Update_MotionUnit, fixed, turnLength)
 MESSAGE_1(Update_Final, fixed, turnLength)
 
+jsval CMessageUpdate_Visibility::ToJSVal(ScriptInterface& scriptInterface) const
+{
+	TOJSVAL_SETUP();
+	return OBJECT_TO_JSVAL(obj);
+}
+
+CMessage* CMessageUpdate_Visibility::FromJSVal(ScriptInterface& UNUSED(scriptInterface), jsval UNUSED(val))
+{
+	return NULL;
+}
+
 ////////////////////////////////
 
 jsval CMessageInterpolate::ToJSVal(ScriptInterface& scriptInterface) const
@@ -316,6 +327,22 @@ CMessage* CMessageTechnologyModification::FromJSVal(ScriptInterface& scriptInter
 	return new CMessageTechnologyModification(component, player);
 }
 
+////////////////////////////////
+
+jsval CMessageVisibilityChanged::ToJSVal(ScriptInterface& scriptInterface) const
+{
+	TOJSVAL_SETUP();
+	SET_MSG_PROPERTY(source);
+	SET_MSG_PROPERTY(proxy);
+	return OBJECT_TO_JSVAL(obj);
+}
+
+CMessage* CMessageVisibilityChanged::FromJSVal(ScriptInterface& UNUSED(scriptInterface), jsval UNUSED(val))
+{
+	LOGWARNING(L"CMessageVisibilityChanged::FromJSVal not implemented");
+	return NULL;
+}
+
 ////////////////////////////////////////////////////////////////
 
 CMessage* CMessageFromJSVal(int mtid, ScriptInterface& scriptingInterface, jsval val)
