diff --git a/binaries/data/mods/public/gui/session/selection.js b/binaries/data/mods/public/gui/session/selection.js
index c678873709..fd19fd6c9e 100644
--- a/binaries/data/mods/public/gui/session/selection.js
+++ b/binaries/data/mods/public/gui/session/selection.js
@@ -235,8 +235,10 @@ EntitySelection.prototype.getTemplateNames = function()
*/
EntitySelection.prototype.update = function()
{
- var changed = false;
this.checkRenamedEntities();
+
+ var miraged = {};
+ var changed = false;
for each (var ent in this.selected)
{
var entState = GetEntityState(ent);
@@ -250,6 +252,13 @@ EntitySelection.prototype.update = function()
continue;
}
+ // Manually replace newly miraged entities by their mirages
+ if (entState.fogging && entState.fogging.mirage)
+ {
+ miraged[ent] = entState.fogging.mirage;
+ continue;
+ }
+
// Remove non-visible units (e.g. moved back into fog-of-war)
if (entState.visibility == "hidden")
{
@@ -264,6 +273,9 @@ EntitySelection.prototype.update = function()
continue;
}
}
+
+ this.rebuildSelection(miraged);
+
if (changed)
this.onChange();
};
diff --git a/binaries/data/mods/public/gui/session/selection_details.js b/binaries/data/mods/public/gui/session/selection_details.js
index 4d8a347783..baa4d90909 100644
--- a/binaries/data/mods/public/gui/session/selection_details.js
+++ b/binaries/data/mods/public/gui/session/selection_details.js
@@ -164,7 +164,7 @@ function displaySingle(entState, template)
Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = sprintf(translate("Gain: %(amount)s"), { amount: getTradingTooltip(entState.trader.goods.amount) });
}
// And for number of workers
- else if (entState.foundation)
+ else if (entState.foundation && !entState.mirage)
{
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
@@ -172,7 +172,7 @@ function displaySingle(entState, template)
Engine.GetGUIObjectByName("resourceCarryingText").caption = entState.foundation.numBuilders + " ";
Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = sprintf(translate("Number of builders.\nTasking another to this foundation would speed construction up by %(numb)s%%"), { numb : Math.round((Math.pow((entState.foundation.numBuilders+1)/entState.foundation.numBuilders, 0.7) - 1.0)*100) });
}
- else if (entState.resourceSupply && (!entState.resourceSupply.killBeforeGather || !entState.hitpoints))
+ else if (entState.resourceSupply && (!entState.resourceSupply.killBeforeGather || !entState.hitpoints) && !entState.mirage)
{
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
diff --git a/binaries/data/mods/public/gui/session/unit_actions.js b/binaries/data/mods/public/gui/session/unit_actions.js
index 4c140558b4..1dab22c78e 100644
--- a/binaries/data/mods/public/gui/session/unit_actions.js
+++ b/binaries/data/mods/public/gui/session/unit_actions.js
@@ -656,6 +656,12 @@ var g_EntityCommands =
"delete": {
"getInfo": function(entState)
{
+ if (entState.mirage)
+ return {
+ "tooltip": translate("You cannot destroy this entity because it is in the fog-of-war"),
+ "icon": "kill_small.png"
+ };
+
return {
"tooltip": translate("Delete"),
"icon": "kill_small.png"
@@ -663,6 +669,9 @@ var g_EntityCommands =
},
"execute": function(entState)
{
+ if (entState.mirage)
+ return;
+
var selection = g_Selection.toList();
if (selection.length < 1)
return;
diff --git a/binaries/data/mods/public/simulation/components/BuildingAI.js b/binaries/data/mods/public/simulation/components/BuildingAI.js
index 4eef15b28f..ef124d9270 100644
--- a/binaries/data/mods/public/simulation/components/BuildingAI.js
+++ b/binaries/data/mods/public/simulation/components/BuildingAI.js
@@ -335,6 +335,11 @@ BuildingAI.prototype.CheckTargetVisible = function(target)
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ // Entities that are hidden and miraged are considered visible
+ var cmpFogging = Engine.QueryInterface(target, IID_Fogging);
+ if (cmpFogging && cmpFogging.IsMiraged(cmpOwnership.GetOwner()))
+ return true;
+
if (cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner(), false) == "hidden")
return false;
diff --git a/binaries/data/mods/public/simulation/components/Fogging.js b/binaries/data/mods/public/simulation/components/Fogging.js
new file mode 100644
index 0000000000..87e6907f03
--- /dev/null
+++ b/binaries/data/mods/public/simulation/components/Fogging.js
@@ -0,0 +1,165 @@
+const VIS_HIDDEN = 0;
+const VIS_FOGGED = 1;
+const VIS_VISIBLE = 2;
+
+function Fogging() {}
+
+Fogging.prototype.Schema =
+ "Allows this entity to be replaced by mirages entities in the fog-of-war." +
+ "";
+
+Fogging.prototype.Init = function()
+{
+ this.mirages = [];
+ this.seen = [];
+
+ var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
+ for (var player = 0; player < cmpPlayerManager.GetNumPlayers(); ++player)
+ {
+ this.mirages.push(INVALID_ENTITY);
+ this.seen.push(false);
+ }
+};
+
+Fogging.prototype.LoadMirage = function(player)
+{
+ var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+ var templateName = "mirage|" + cmpTemplateManager.GetCurrentTemplateName(this.entity);
+
+ // If this is an entity without visibility (e.g. a foundation), it should be
+ // marked as seen for its owner
+ var cmpParentOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+ if (cmpParentOwnership && cmpParentOwnership.GetOwner() == player)
+ this.seen[player] = true;
+
+ if (!this.seen[player] || this.mirages[player] != INVALID_ENTITY)
+ return;
+
+ this.mirages[player] = Engine.AddEntity(templateName);
+ var cmpMirage = Engine.QueryInterface(this.mirages[player], IID_Mirage);
+ if (!cmpMirage)
+ {
+ error("Failed to load mirage entity for template " + templateName);
+ this.mirages[player] = INVALID_ENTITY;
+ return;
+ }
+
+ // Setup basic mirage properties
+ cmpMirage.SetPlayer(player);
+ cmpMirage.SetParent(this.entity);
+
+ // Copy cmpOwnership data
+ var cmpMirageOwnership = Engine.QueryInterface(this.mirages[player], IID_Ownership);
+ if (!cmpParentOwnership || !cmpMirageOwnership)
+ {
+ error("Failed to setup the ownership data of the fogged entity " + templateName);
+ return;
+ }
+ cmpMirageOwnership.SetOwner(cmpParentOwnership.GetOwner());
+
+ // Copy cmpPosition data
+ var cmpParentPosition = Engine.QueryInterface(this.entity, IID_Position);
+ var cmpMiragePosition = Engine.QueryInterface(this.mirages[player], IID_Position);
+ if (!cmpParentPosition || !cmpMiragePosition)
+ {
+ error("Failed to setup the position data of the fogged entity " + templateName);
+ return;
+ }
+ if (!cmpParentPosition.IsInWorld())
+ return;
+ var pos = cmpParentPosition.GetPosition();
+ cmpMiragePosition.JumpTo(pos.x, pos.z);
+ var rot = cmpParentPosition.GetRotation();
+ cmpMiragePosition.SetYRotation(rot.y);
+ cmpMiragePosition.SetXZRotation(rot.x, rot.z);
+
+ // Copy cmpVisualActor data
+ var cmpParentVisualActor = Engine.QueryInterface(this.entity, IID_Visual);
+ var cmpMirageVisualActor = Engine.QueryInterface(this.mirages[player], IID_Visual);
+ if (!cmpParentVisualActor || !cmpMirageVisualActor)
+ {
+ error("Failed to setup the visual data of the fogged entity " + templateName);
+ return;
+ }
+ cmpMirageVisualActor.SetActorSeed(cmpParentVisualActor.GetActorSeed());
+
+ // Store valuable information into the mirage component (especially for the GUI)
+ var cmpFoundation = Engine.QueryInterface(this.entity, IID_Foundation);
+ if (cmpFoundation)
+ cmpMirage.AddFoundation(cmpFoundation.GetBuildPercentage());
+
+ var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
+ if (cmpHealth)
+ cmpMirage.AddHealth(
+ cmpHealth.GetMaxHitpoints(),
+ cmpHealth.GetHitpoints(),
+ cmpHealth.IsRepairable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints())
+ );
+
+ var cmpResourceSupply = Engine.QueryInterface(this.entity, IID_ResourceSupply);
+ if (cmpResourceSupply)
+ cmpMirage.AddResourceSupply(
+ cmpResourceSupply.GetMaxAmount(),
+ cmpResourceSupply.GetCurrentAmount(),
+ cmpResourceSupply.GetType(),
+ cmpResourceSupply.IsInfinite()
+ );
+};
+
+Fogging.prototype.IsMiraged = function(player)
+{
+ if (player >= this.mirages.length)
+ return false;
+
+ return this.mirages[player] != INVALID_ENTITY;
+};
+
+Fogging.prototype.GetMirage = function(player)
+{
+ if (player >= this.mirages.length)
+ return INVALID_ENTITY;
+
+ return this.mirages[player];
+};
+
+Fogging.prototype.WasSeen = function(player)
+{
+ if (player >= this.seen.length)
+ return false;
+
+ return this.seen[player];
+};
+
+Fogging.prototype.OnVisibilityChanged = function(msg)
+{
+ if (msg.player >= this.mirages.length)
+ return;
+
+ if (msg.newVisibility == VIS_VISIBLE)
+ {
+ this.seen[msg.player] = true;
+
+ // Destroy mirages when we get back into LoS
+ if (this.mirages[msg.player] != INVALID_ENTITY)
+ {
+ Engine.DestroyEntity(this.mirages[msg.player]);
+ this.mirages[msg.player] = INVALID_ENTITY;
+ }
+ }
+
+ // Intermediate LoS state, meaning we must create a mirage
+ if (msg.newVisibility == VIS_FOGGED)
+ this.LoadMirage(msg.player);
+};
+
+Fogging.prototype.OnDestroy = function(msg)
+{
+ for (var player = 0; player < this.mirages.length; ++player)
+ {
+ var cmpMirage = Engine.QueryInterface(this.mirages[player], IID_Mirage);
+ if (cmpMirage)
+ cmpMirage.SetParent(INVALID_ENTITY);
+ }
+};
+
+Engine.RegisterComponentType(IID_Fogging, "Fogging", Fogging);
diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js
index cfca3af02a..06196ab4cb 100644
--- a/binaries/data/mods/public/simulation/components/GuiInterface.js
+++ b/binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -175,6 +175,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
"alertRaiser": null,
"buildEntities": null,
"identity": null,
+ "fogging": null,
"foundation": null,
"garrisonHolder": null,
"gate": null,
@@ -190,6 +191,9 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
"visibility": null,
};
+ // Used for several components
+ var cmpMirage = Engine.QueryInterface(ent, IID_Mirage);
+
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
if (cmpIdentity)
{
@@ -216,6 +220,12 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
ret.needsRepair = cmpHealth.IsRepairable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints());
ret.needsHeal = !cmpHealth.IsUnhealable();
}
+ if (cmpMirage && cmpMirage.Health())
+ {
+ ret.hitpoints = cmpMirage.GetHitpoints();
+ ret.maxHitpoints = cmpMirage.GetMaxHitpoints();
+ ret.needsRepair = cmpMirage.NeedsRepair();
+ }
var cmpBuilder = Engine.QueryInterface(ent, IID_Builder);
if (cmpBuilder)
@@ -251,6 +261,15 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
};
}
+ var cmpFogging = Engine.QueryInterface(ent, IID_Fogging);
+ if (cmpFogging)
+ {
+ if (cmpFogging.IsMiraged(player))
+ ret.fogging = {"mirage": cmpFogging.GetMirage(player)};
+ else
+ ret.fogging = {"mirage": null};
+ }
+
var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
if (cmpFoundation)
{
@@ -259,6 +278,12 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
"numBuilders": cmpFoundation.GetNumBuilders()
};
}
+ if (cmpMirage && cmpMirage.Foundation())
+ {
+ ret.foundation = {
+ "progress": cmpMirage.GetBuildPercentage()
+ };
+ }
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (cmpOwnership)
@@ -342,6 +367,7 @@ GuiInterface.prototype.GetExtendedEntityState = function(player, ent)
"barterMarket": null,
"buildingAI": null,
"healer": null,
+ "mirage": null,
"obstruction": null,
"turretParent":null,
"promotion": null,
@@ -351,6 +377,10 @@ GuiInterface.prototype.GetExtendedEntityState = function(player, ent)
"resourceSupply": null,
};
+ var cmpMirage = Engine.QueryInterface(ent, IID_Mirage);
+ if (cmpMirage)
+ ret.mirage = true;
+
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
var cmpAttack = Engine.QueryInterface(ent, IID_Attack);
@@ -445,6 +475,15 @@ GuiInterface.prototype.GetExtendedEntityState = function(player, ent)
"gatherers": cmpResourceSupply.GetGatherers()
};
}
+ if (cmpMirage && cmpMirage.ResourceSupply())
+ {
+ ret.resourceSupply = {
+ "max": cmpMirage.GetMaxAmount(),
+ "amount": cmpMirage.GetAmount(),
+ "type": cmpMirage.GetType(),
+ "isInfinite": cmpMirage.IsInfinite()
+ };
+ }
var cmpResourceGatherer = Engine.QueryInterface(ent, IID_ResourceGatherer);
if (cmpResourceGatherer)
diff --git a/binaries/data/mods/public/simulation/components/Mirage.js b/binaries/data/mods/public/simulation/components/Mirage.js
new file mode 100644
index 0000000000..9306149e7c
--- /dev/null
+++ b/binaries/data/mods/public/simulation/components/Mirage.js
@@ -0,0 +1,149 @@
+const VIS_HIDDEN = 0;
+const VIS_FOGGED = 1;
+const VIS_VISIBLE = 2;
+
+function Mirage() {}
+
+Mirage.prototype.Schema =
+ "Mirage entities replace real entities in the fog-of-war." +
+ "";
+
+Mirage.prototype.Init = function()
+{
+ this.player = null;
+ this.parent = INVALID_ENTITY;
+
+ this.foundation = false;
+ this.buildPercentage = null;
+
+ this.health = false;
+ this.maxHitpoints = null;
+ this.hitpoints = null;
+ this.needsRepair = null;
+
+ this.resourceSupply = false;
+ this.maxAmount = null;
+ this.amount = null;
+ this.type = null;
+ this.isInfinite = null;
+};
+
+Mirage.prototype.SetParent = function(ent)
+{
+ this.parent = ent;
+};
+
+Mirage.prototype.GetPlayer = function()
+{
+ return this.player;
+};
+
+Mirage.prototype.SetPlayer = function(player)
+{
+ this.player = player;
+};
+
+// ============================
+// Parent entity data
+
+// Foundation data
+
+Mirage.prototype.AddFoundation = function(buildPercentage)
+{
+ this.foundation = true;
+ this.buildPercentage = buildPercentage;
+};
+
+Mirage.prototype.Foundation = function()
+{
+ return this.foundation;
+};
+
+Mirage.prototype.GetBuildPercentage = function()
+{
+ return this.buildPercentage;
+};
+
+// Health data
+
+Mirage.prototype.AddHealth = function(maxHitpoints, hitpoints, needsRepair)
+{
+ this.health = true;
+ this.maxHitpoints = maxHitpoints;
+ this.hitpoints = Math.ceil(hitpoints);
+ this.needsRepair = needsRepair;
+};
+
+Mirage.prototype.Health = function()
+{
+ return this.health;
+};
+
+Mirage.prototype.GetMaxHitpoints = function()
+{
+ return this.maxHitpoints;
+};
+
+Mirage.prototype.GetHitpoints = function()
+{
+ return this.hitpoints;
+};
+
+Mirage.prototype.NeedsRepair = function()
+{
+ return this.needsRepair;
+};
+
+// ResourceSupply data
+
+Mirage.prototype.AddResourceSupply = function(maxAmount, amount, type, isInfinite)
+{
+ this.resourceSupply = true;
+ this.maxAmount = maxAmount;
+ this.amount = amount;
+ this.type = type;
+ this.isInfinite = isInfinite;
+};
+
+Mirage.prototype.ResourceSupply = function()
+{
+ return this.resourceSupply;
+};
+
+Mirage.prototype.GetMaxAmount = function()
+{
+ return this.maxAmount;
+};
+
+Mirage.prototype.GetAmount = function()
+{
+ return this.amount;
+};
+
+Mirage.prototype.GetType = function()
+{
+ return this.type;
+};
+
+Mirage.prototype.IsInfinite = function()
+{
+ return this.isInfinite;
+};
+
+// ============================
+
+Mirage.prototype.OnVisibilityChanged = function(msg)
+{
+ if (msg.player == this.player && msg.newVisibility == VIS_VISIBLE && this.parent == INVALID_ENTITY)
+ Engine.DestroyEntity(this.entity);
+};
+
+Mirage.prototype.OnDestroy = function(msg)
+{
+ if (this.parent == INVALID_ENTITY)
+ return;
+
+ Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: this.parent });
+};
+
+Engine.RegisterComponentType(IID_Mirage, "Mirage", Mirage);
diff --git a/binaries/data/mods/public/simulation/components/ResourceGatherer.js b/binaries/data/mods/public/simulation/components/ResourceGatherer.js
index 201eb679cc..2d5fc4cd0b 100644
--- a/binaries/data/mods/public/simulation/components/ResourceGatherer.js
+++ b/binaries/data/mods/public/simulation/components/ResourceGatherer.js
@@ -243,12 +243,16 @@ ResourceGatherer.prototype.GetTargetGatherRate = function(target)
{
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
+ var type;
var cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
- if (!cmpResourceSupply)
+ var cmpMirage = Engine.QueryInterface(target, IID_Mirage);
+ if (cmpResourceSupply)
+ type = cmpResourceSupply.GetType();
+ else if (cmpMirage && cmpMirage.ResourceSupply())
+ type = cmpMirage.GetType();
+ else
return 0;
- var type = cmpResourceSupply.GetType();
-
var rates = this.GetGatherRates();
var rate;
@@ -261,7 +265,12 @@ ResourceGatherer.prototype.GetTargetGatherRate = function(target)
rate = rates[type.generic] / cmpPlayer.GetCheatTimeMultiplier();
}
+ if (cmpMirage)
+ return rate || 0;
+
// Apply diminishing returns with more gatherers, for e.g. infinite farms. For most resources this has no effect. (GetDiminishingReturns will return null.)
+ // We can assume that for resources that are miraged this is the case. (else just add the diminishing returns data to the mirage data and remove the
+ // early return above)
// Note to people looking to change in a template: This is a bit complicated. Basically, the lower that number is
// the steeper diminishing returns will be. I suggest playing around with Wolfram Alpha or a graphing calculator a bit.
// In each of the following links, replace 0.65 with the gather rate of your worker for the resource with diminishing returns and
diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js
index 8aa0c0aaa8..22e1190467 100644
--- a/binaries/data/mods/public/simulation/components/UnitAI.js
+++ b/binaries/data/mods/public/simulation/components/UnitAI.js
@@ -1987,7 +1987,9 @@ UnitAI.prototype.UnitFsmSpec = {
// check that we can gather from the resource we're supposed to gather from.
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
- if (!cmpSupply || !cmpSupply.AddGatherer(cmpOwnership.GetOwner(), this.entity))
+ var cmpMirage = Engine.QueryInterface(this.gatheringTarget, IID_Mirage);
+ if ((!cmpMirage || !cmpMirage.ResourceSupply()) &&
+ (!cmpSupply || !cmpSupply.AddGatherer(cmpOwnership.GetOwner(), this.entity)))
{
// Save the current order's data in case we need it later
var oldType = this.order.data.type;
@@ -3896,6 +3898,10 @@ UnitAI.prototype.TargetIsAlive = function(ent)
if (cmpFormation)
return true;
+ var cmpMirage = Engine.QueryInterface(ent, IID_Mirage);
+ if (cmpMirage)
+ return true;
+
var cmpHealth = Engine.QueryInterface(ent, IID_Health);
if (!cmpHealth)
return false;
@@ -4386,6 +4392,11 @@ UnitAI.prototype.CheckTargetVisible = function(target)
if (!cmpRangeManager)
return false;
+ // Entities that are hidden and miraged are considered visible
+ var cmpFogging = Engine.QueryInterface(target, IID_Fogging);
+ if (cmpFogging && cmpFogging.IsMiraged(cmpOwnership.GetOwner()))
+ return true;
+
if (cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner(), false) == "hidden")
return false;
@@ -5007,8 +5018,15 @@ UnitAI.prototype.PerformGather = function(target, queued, force)
// Save the resource type now, so if the resource gets destroyed
// before we process the order then we still know what resource
// type to look for more of
+ var type;
var cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
- var type = cmpResourceSupply.GetType();
+ var cmpMirage = Engine.QueryInterface(target, IID_Mirage);
+ if (cmpResourceSupply)
+ type = cmpResourceSupply.GetType();
+ else if (cmpMirage && cmpMirage.ResourceSupply())
+ type = cmpMirage.GetType();
+ else
+ error("CanGather allowed gathering from invalid entity");
// Also save the target entity's template, so that if it's an animal,
// we won't go from hunting slow safe animals to dangerous fast ones
@@ -5552,9 +5570,10 @@ UnitAI.prototype.CanGather = function(target)
{
if (this.IsTurret())
return false;
- // The target must be a valid resource supply.
+ // The target must be a valid resource supply, or the mirage of one.
var cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
- if (!cmpResourceSupply)
+ var cmpMirage = Engine.QueryInterface(target, IID_Mirage);
+ if (!cmpResourceSupply && !(cmpMirage && cmpMirage.ResourceSupply()))
return false;
// Formation controllers should always respond to commands
diff --git a/binaries/data/mods/public/simulation/components/interfaces/Messages.js b/binaries/data/mods/public/simulation/components/interfaces/Messages.js
index c4f6c6f43b..ae3d8fc701 100644
--- a/binaries/data/mods/public/simulation/components/interfaces/Messages.js
+++ b/binaries/data/mods/public/simulation/components/interfaces/Messages.js
@@ -1,8 +1,9 @@
/**
* Broadcast message
* sent when one entity is changed to other:
- * from Foundation component when building constuction is done
- * and from Promotion component when unit is promoted
+ * - from Foundation component when building construction is done
+ * - from Promotion component when unit is promoted
+ * - from Mirage component when a fogged entity is re-discovered
* Data: { entity: , newentity: }
*/
Engine.RegisterMessageType("EntityRenamed");
diff --git a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
index 0ae49bcb0c..197785d13f 100644
--- a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
+++ b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
@@ -398,6 +398,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetEntityState(-1, 10), {
visibleClasses: ["class3", "class4"],
selectionGroupName: "Selection Group Name",
},
+ fogging: null,
foundation: null,
garrisonHolder: null,
gate: null,
@@ -425,6 +426,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedEntityState(-1, 10), {
},
buildingAI: null,
healer: null,
+ mirage: null,
obstruction: null,
turretParent: null,
promotion: null,
diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js
index 6cc67a3565..90e28c5d59 100644
--- a/binaries/data/mods/public/simulation/helpers/Commands.js
+++ b/binaries/data/mods/public/simulation/helpers/Commands.js
@@ -974,6 +974,11 @@ function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd)
"queued": cmd.queued
});
}
+
+ // Load a mirage for the owner of this new entity
+ var cmpFogging = Engine.QueryInterface(ent, IID_Fogging)
+ if (cmpFogging)
+ cmpFogging.LoadMirage(player);
return ent;
}
diff --git a/binaries/data/mods/public/simulation/templates/template_gaia.xml b/binaries/data/mods/public/simulation/templates/template_gaia.xml
index 27744daf94..7a5f54b1c0 100644
--- a/binaries/data/mods/public/simulation/templates/template_gaia.xml
+++ b/binaries/data/mods/public/simulation/templates/template_gaia.xml
@@ -1,5 +1,6 @@
+
gaia
Gaia
diff --git a/binaries/data/mods/public/simulation/templates/template_structure.xml b/binaries/data/mods/public/simulation/templates/template_structure.xml
index 6272e33fef..47cc0efea7 100644
--- a/binaries/data/mods/public/simulation/templates/template_structure.xml
+++ b/binaries/data/mods/public/simulation/templates/template_structure.xml
@@ -37,6 +37,7 @@
3.0
9.8
+
corpse
0
diff --git a/source/ps/TemplateLoader.cpp b/source/ps/TemplateLoader.cpp
index 4ef7374912..3c985fc829 100644
--- a/source/ps/TemplateLoader.cpp
+++ b/source/ps/TemplateLoader.cpp
@@ -80,6 +80,21 @@ bool CTemplateLoader::LoadTemplateFile(const std::string& templateName, int dept
return true;
}
+ // Handle special case "mirage|foo"
+ if (templateName.find("mirage|") == 0)
+ {
+ // Load the base entity template, if it wasn't already loaded
+ std::string baseName = templateName.substr(7);
+ if (!LoadTemplateFile(baseName, depth+1))
+ {
+ LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
+ return false;
+ }
+ // Copy a subset to the requested template
+ CopyMirageSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]);
+ return true;
+ }
+
// Handle special case "foundation|foo"
if (templateName.find("foundation|") == 0)
{
@@ -383,6 +398,40 @@ void CTemplateLoader::CopyPreviewSubset(CParamNode& out, const CParamNode& in, b
}
}
+void CTemplateLoader::CopyMirageSubset(CParamNode& out, const CParamNode& in)
+{
+ // Currently used for mirage entities replacing real ones in fog-of-war
+
+ std::set permittedComponentTypes;
+ permittedComponentTypes.insert("Footprint");
+ permittedComponentTypes.insert("Minimap");
+ permittedComponentTypes.insert("Ownership");
+ permittedComponentTypes.insert("Position");
+ permittedComponentTypes.insert("Selectable");
+ permittedComponentTypes.insert("VisualActor");
+
+ CParamNode::LoadXMLString(out, "");
+ out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
+
+ // Select a subset of identity data. We don't want to have, for example, a CC mirage
+ // that has also the CC class and then prevents construction of other CCs
+ std::set identitySubset;
+ identitySubset.insert("Civ");
+ identitySubset.insert("GenericName");
+ identitySubset.insert("SpecificName");
+ identitySubset.insert("Tooltip");
+ identitySubset.insert("History");
+ identitySubset.insert("Icon");
+ CParamNode identity;
+ CParamNode::LoadXMLString(identity, "");
+ identity.CopyFilteredChildrenOfChild(in.GetChild("Entity"), "Identity", identitySubset);
+ CParamNode::LoadXMLString(out, (""+utf8_from_wstring(identity.ToXML())+"").c_str());
+
+ // Set the entity as mirage entity
+ CParamNode::LoadXMLString(out, "");
+ CParamNode::LoadXMLString(out, "0truefalse");
+}
+
void CTemplateLoader::CopyFoundationSubset(CParamNode& out, const CParamNode& in)
{
// TODO: this is all kind of yucky and hard-coded; it'd be nice to have a more generic
@@ -397,6 +446,7 @@ void CTemplateLoader::CopyFoundationSubset(CParamNode& out, const CParamNode& in
permittedComponentTypes.insert("Obstruction");
permittedComponentTypes.insert("Selectable");
permittedComponentTypes.insert("Footprint");
+ permittedComponentTypes.insert("Fogging");
permittedComponentTypes.insert("Armour");
permittedComponentTypes.insert("Health");
permittedComponentTypes.insert("StatusBars");
diff --git a/source/ps/TemplateLoader.h b/source/ps/TemplateLoader.h
index 7217b69261..30888b60ba 100644
--- a/source/ps/TemplateLoader.h
+++ b/source/ps/TemplateLoader.h
@@ -78,6 +78,12 @@ private:
*/
void CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse);
+ /**
+ * Copy the components of an entity template necessary for a fogged "mirage"
+ * entity (position, actor) into a new entity template
+ */
+ void CopyMirageSubset(CParamNode& out, const CParamNode& in);
+
/**
* Copy the components of an entity template necessary for a construction foundation
* (position, actor, armour, health, etc) into a new entity template
diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h
index 8525a1ca62..eb5ac6a0ad 100644
--- a/source/simulation2/TypeList.h
+++ b/source/simulation2/TypeList.h
@@ -81,6 +81,9 @@ COMPONENT(CommandQueue)
INTERFACE(Decay)
COMPONENT(Decay)
+INTERFACE(Fogging)
+COMPONENT(FoggingScripted)
+
// Note: The VisualActor component relies on this component being initialized before itself, in order to support using
// an entity's footprint shape for the selection boxes. This dependency is not strictly necessary, but it does avoid
// some extra plumbing code to set up on-demand initialization. If you find yourself forced to break this dependency,
@@ -97,6 +100,9 @@ COMPONENT(IdentityScripted)
INTERFACE(Minimap)
COMPONENT(Minimap)
+INTERFACE(Mirage)
+COMPONENT(MirageScripted)
+
INTERFACE(Motion)
COMPONENT(MotionBall)
COMPONENT(MotionScripted)
diff --git a/source/simulation2/components/CCmpRangeManager.cpp b/source/simulation2/components/CCmpRangeManager.cpp
index 4fca628d99..f61deab5f3 100644
--- a/source/simulation2/components/CCmpRangeManager.cpp
+++ b/source/simulation2/components/CCmpRangeManager.cpp
@@ -23,6 +23,9 @@
#include "ICmpTerrain.h"
#include "simulation2/system/EntityMap.h"
#include "simulation2/MessageTypes.h"
+#include "simulation2/components/ICmpFogging.h"
+#include "simulation2/components/ICmpMirage.h"
+#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpTerritoryManager.h"
#include "simulation2/components/ICmpVision.h"
@@ -564,7 +567,6 @@ public:
case MT_Update:
{
m_DebugOverlayDirty = true;
- UpdateTerritoriesLos();
ExecuteActiveQueries();
UpdateVisibilityData();
break;
@@ -1401,21 +1403,50 @@ public:
return VIS_VISIBLE;
}
+ // Mirage entities, whatever their position, are visible for one specific player
+ CmpPtr cmpMirage(ent);
+ if (cmpMirage && cmpMirage->GetPlayer() != player)
+ return VIS_HIDDEN;
+
// Visible if within a visible region
CLosQuerier los(GetSharedLosMask(player), m_LosState, m_TerrainVerticesPerSide);
if (los.IsVisible(i, j))
return VIS_VISIBLE;
+ if (!los.IsExplored(i, j))
+ return VIS_HIDDEN;
+
// Fogged if the 'retain in fog' flag is set, and in a non-visible explored region
- if (los.IsExplored(i, j))
- {
- CmpPtr cmpVision(ent);
- if (forceRetainInFog || (cmpVision && cmpVision->GetRetainInFog()))
- return VIS_FOGGED;
- }
+ CmpPtr cmpVision(ent);
+ if (!forceRetainInFog && !(cmpVision && cmpVision->GetRetainInFog()))
+ return VIS_HIDDEN;
- // Otherwise not visible
+ if (cmpMirage && cmpMirage->GetPlayer() == player)
+ return VIS_FOGGED;
+
+ CmpPtr cmpOwnership(ent);
+ if (!cmpOwnership)
+ return VIS_VISIBLE;
+
+ if (cmpOwnership->GetOwner() == player)
+ {
+ CmpPtr cmpFogging(ent);
+ if (!cmpFogging)
+ return VIS_VISIBLE;
+
+ // Fogged entities must not disappear while the mirage is not ready
+ if (!cmpFogging->IsMiraged(player))
+ return VIS_FOGGED;
+
+ return VIS_HIDDEN;
+ }
+
+ // Fogged entities must not disappear while the mirage is not ready
+ CmpPtr cmpFogging(ent);
+ if (cmpFogging && cmpFogging->WasSeen(player) && !cmpFogging->IsMiraged(player))
+ return VIS_FOGGED;
+
return VIS_HIDDEN;
}
@@ -1487,18 +1518,29 @@ public:
EntityMap::iterator itEnts = m_EntityData.find(ent);
if (itEnts == m_EntityData.end())
return;
+
+ std::vector oldVisibilities;
+ std::vector newVisibilities;
- for (player_id_t player = 1; player <= MAX_LOS_PLAYER_ID; ++player)
+ for (player_id_t player = 1; player < MAX_LOS_PLAYER_ID+1; ++player)
{
u8 oldVis = (itEnts->second.visibilities >> (2*(player-1))) & 0x3;
u8 newVis = GetLosVisibility(itEnts->first, player, false);
+
+ oldVisibilities.push_back(oldVis);
+ newVisibilities.push_back(newVis);
if (oldVis != newVis)
- {
- CMessageVisibilityChanged msg(player, ent, oldVis, newVis);
- GetSimContext().GetComponentManager().PostMessage(ent, msg);
itEnts->second.visibilities = (itEnts->second.visibilities & ~(0x3 << 2*(player-1))) | (newVis << 2*(player-1));
- }
+ }
+
+ for (player_id_t player = 1; player < MAX_LOS_PLAYER_ID+1; ++player)
+ {
+ if (oldVisibilities[player-1] == newVisibilities[player-1])
+ continue;
+
+ CMessageVisibilityChanged msg(player, ent, oldVisibilities[player-1], newVisibilities[player-1]);
+ GetSimContext().GetComponentManager().PostMessage(ent, msg);
}
}
diff --git a/source/simulation2/components/ICmpFogging.cpp b/source/simulation2/components/ICmpFogging.cpp
new file mode 100644
index 0000000000..a981adb187
--- /dev/null
+++ b/source/simulation2/components/ICmpFogging.cpp
@@ -0,0 +1,44 @@
+/* Copyright (C) 2014 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. 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 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "ICmpFogging.h"
+
+#include "simulation2/scripting/ScriptComponent.h"
+#include "simulation2/system/InterfaceScripted.h"
+
+BEGIN_INTERFACE_WRAPPER(Fogging)
+END_INTERFACE_WRAPPER(Fogging)
+
+class CCmpFoggingScripted : public ICmpFogging
+{
+public:
+ DEFAULT_SCRIPT_WRAPPER(FoggingScripted)
+
+ virtual bool WasSeen(player_id_t player)
+ {
+ return m_Script.Call("WasSeen", player);
+ }
+
+ virtual bool IsMiraged(player_id_t player)
+ {
+ return m_Script.Call("IsMiraged", player);
+ }
+};
+
+REGISTER_COMPONENT_SCRIPT_WRAPPER(FoggingScripted)
diff --git a/source/simulation2/components/ICmpFogging.h b/source/simulation2/components/ICmpFogging.h
new file mode 100644
index 0000000000..cd759f601d
--- /dev/null
+++ b/source/simulation2/components/ICmpFogging.h
@@ -0,0 +1,39 @@
+/* Copyright (C) 2014 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. 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 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_ICMPFOGGING
+#define INCLUDED_ICMPFOGGING
+
+#include "simulation2/system/Interface.h"
+
+#include "simulation2/helpers/Player.h"
+
+/**
+ * Handles the fogging of out-of-sight enemy entities, by creating mirage
+ * entities.
+ * This allows hiding changes, especially destruction status or health.
+ */
+class ICmpFogging : public IComponent
+{
+public:
+ virtual bool WasSeen(player_id_t player) = 0;
+ virtual bool IsMiraged(player_id_t player) = 0;
+
+ DECLARE_INTERFACE_TYPE(Fogging)
+};
+
+#endif // INCLUDED_ICMPFOGGING
diff --git a/source/simulation2/components/ICmpMirage.cpp b/source/simulation2/components/ICmpMirage.cpp
new file mode 100644
index 0000000000..80ceaca2c6
--- /dev/null
+++ b/source/simulation2/components/ICmpMirage.cpp
@@ -0,0 +1,39 @@
+/* Copyright (C) 2014 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. 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 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "ICmpMirage.h"
+
+#include "simulation2/scripting/ScriptComponent.h"
+#include "simulation2/system/InterfaceScripted.h"
+
+BEGIN_INTERFACE_WRAPPER(Mirage)
+END_INTERFACE_WRAPPER(Mirage)
+
+class CCmpMirageScripted : public ICmpMirage
+{
+public:
+ DEFAULT_SCRIPT_WRAPPER(MirageScripted)
+
+ virtual player_id_t GetPlayer()
+ {
+ return m_Script.Call("GetPlayer");
+ }
+};
+
+REGISTER_COMPONENT_SCRIPT_WRAPPER(MirageScripted)
diff --git a/source/simulation2/components/ICmpMirage.h b/source/simulation2/components/ICmpMirage.h
new file mode 100644
index 0000000000..5edb22734f
--- /dev/null
+++ b/source/simulation2/components/ICmpMirage.h
@@ -0,0 +1,37 @@
+/* Copyright (C) 2014 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. 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 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_ICMPMIRAGE
+#define INCLUDED_ICMPMIRAGE
+
+#include "simulation2/system/Interface.h"
+
+#include "simulation2/helpers/Player.h"
+
+/**
+ * Component allowing mirage entities to communicate with their parent entity.
+ * See ICmpFogging.
+ */
+class ICmpMirage : public IComponent
+{
+public:
+ virtual player_id_t GetPlayer() = 0;
+
+ DECLARE_INTERFACE_TYPE(Mirage)
+};
+
+#endif // INCLUDED_ICMPMIRAGE