Hide changes to buildings in the fog-of-war.
To achieve this, mirage entities are created per player, to replace the
real entities when these ones fall into the fog-of-war. These mirage
entities are created on-the-fly, and destroyed when they get back in
sight.
This depends heavily on the VisibilityChanged message added in
2174eaaeee
.
As a temporary adjustment, territories do not explore the map anymore
when their borders change. See #2709.
Fixes #599
This was SVN commit r15612.
This commit is contained in:
parent
e41f010f91
commit
f7e591c9f2
@ -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();
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
165
binaries/data/mods/public/simulation/components/Fogging.js
Normal file
165
binaries/data/mods/public/simulation/components/Fogging.js
Normal file
@ -0,0 +1,165 @@
|
||||
const VIS_HIDDEN = 0;
|
||||
const VIS_FOGGED = 1;
|
||||
const VIS_VISIBLE = 2;
|
||||
|
||||
function Fogging() {}
|
||||
|
||||
Fogging.prototype.Schema =
|
||||
"<a:help>Allows this entity to be replaced by mirages entities in the fog-of-war.</a:help>" +
|
||||
"<empty/>";
|
||||
|
||||
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);
|
@ -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)
|
||||
|
149
binaries/data/mods/public/simulation/components/Mirage.js
Normal file
149
binaries/data/mods/public/simulation/components/Mirage.js
Normal file
@ -0,0 +1,149 @@
|
||||
const VIS_HIDDEN = 0;
|
||||
const VIS_FOGGED = 1;
|
||||
const VIS_VISIBLE = 2;
|
||||
|
||||
function Mirage() {}
|
||||
|
||||
Mirage.prototype.Schema =
|
||||
"<a:help>Mirage entities replace real entities in the fog-of-war.</a:help>" +
|
||||
"<empty/>";
|
||||
|
||||
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);
|
@ -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 <DiminishingReturns> 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
|
||||
|
@ -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
|
||||
|
@ -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: <integer>, newentity: <integer> }
|
||||
*/
|
||||
Engine.RegisterMessageType("EntityRenamed");
|
||||
|
@ -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,
|
||||
|
@ -975,6 +975,11 @@ function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd)
|
||||
});
|
||||
}
|
||||
|
||||
// Load a mirage for the owner of this new entity
|
||||
var cmpFogging = Engine.QueryInterface(ent, IID_Fogging)
|
||||
if (cmpFogging)
|
||||
cmpFogging.LoadMirage(player);
|
||||
|
||||
return ent;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Entity parent="template_entity_quasi">
|
||||
<Fogging/>
|
||||
<Identity>
|
||||
<Civ>gaia</Civ>
|
||||
<GenericName>Gaia</GenericName>
|
||||
|
@ -37,6 +37,7 @@
|
||||
<SinkRate>3.0</SinkRate>
|
||||
<SinkAccel>9.8</SinkAccel>
|
||||
</Decay>
|
||||
<Fogging/>
|
||||
<Health>
|
||||
<DeathType>corpse</DeathType>
|
||||
<RegenRate>0</RegenRate>
|
||||
|
@ -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<std::string> permittedComponentTypes;
|
||||
permittedComponentTypes.insert("Footprint");
|
||||
permittedComponentTypes.insert("Minimap");
|
||||
permittedComponentTypes.insert("Ownership");
|
||||
permittedComponentTypes.insert("Position");
|
||||
permittedComponentTypes.insert("Selectable");
|
||||
permittedComponentTypes.insert("VisualActor");
|
||||
|
||||
CParamNode::LoadXMLString(out, "<Entity/>");
|
||||
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<std::string> 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/>");
|
||||
identity.CopyFilteredChildrenOfChild(in.GetChild("Entity"), "Identity", identitySubset);
|
||||
CParamNode::LoadXMLString(out, ("<Entity>"+utf8_from_wstring(identity.ToXML())+"</Entity>").c_str());
|
||||
|
||||
// Set the entity as mirage entity
|
||||
CParamNode::LoadXMLString(out, "<Entity><Mirage/></Entity>");
|
||||
CParamNode::LoadXMLString(out, "<Entity><Vision><Range>0</Range><RetainInFog>true</RetainInFog><AlwaysVisible>false</AlwaysVisible></Vision></Entity>");
|
||||
}
|
||||
|
||||
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");
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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<ICmpMirage> 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<ICmpVision> cmpVision(ent);
|
||||
if (forceRetainInFog || (cmpVision && cmpVision->GetRetainInFog()))
|
||||
if (!forceRetainInFog && !(cmpVision && cmpVision->GetRetainInFog()))
|
||||
return VIS_HIDDEN;
|
||||
|
||||
if (cmpMirage && cmpMirage->GetPlayer() == player)
|
||||
return VIS_FOGGED;
|
||||
|
||||
CmpPtr<ICmpOwnership> cmpOwnership(ent);
|
||||
if (!cmpOwnership)
|
||||
return VIS_VISIBLE;
|
||||
|
||||
if (cmpOwnership->GetOwner() == player)
|
||||
{
|
||||
CmpPtr<ICmpFogging> 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;
|
||||
}
|
||||
|
||||
// Otherwise not visible
|
||||
// Fogged entities must not disappear while the mirage is not ready
|
||||
CmpPtr<ICmpFogging> cmpFogging(ent);
|
||||
if (cmpFogging && cmpFogging->WasSeen(player) && !cmpFogging->IsMiraged(player))
|
||||
return VIS_FOGGED;
|
||||
|
||||
return VIS_HIDDEN;
|
||||
}
|
||||
|
||||
@ -1488,17 +1519,28 @@ public:
|
||||
if (itEnts == m_EntityData.end())
|
||||
return;
|
||||
|
||||
for (player_id_t player = 1; player <= MAX_LOS_PLAYER_ID; ++player)
|
||||
std::vector<u8> oldVisibilities;
|
||||
std::vector<u8> newVisibilities;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
44
source/simulation2/components/ICmpFogging.cpp
Normal file
44
source/simulation2/components/ICmpFogging.cpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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<bool>("WasSeen", player);
|
||||
}
|
||||
|
||||
virtual bool IsMiraged(player_id_t player)
|
||||
{
|
||||
return m_Script.Call<bool>("IsMiraged", player);
|
||||
}
|
||||
};
|
||||
|
||||
REGISTER_COMPONENT_SCRIPT_WRAPPER(FoggingScripted)
|
39
source/simulation2/components/ICmpFogging.h
Normal file
39
source/simulation2/components/ICmpFogging.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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
|
39
source/simulation2/components/ICmpMirage.cpp
Normal file
39
source/simulation2/components/ICmpMirage.cpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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<player_id_t>("GetPlayer");
|
||||
}
|
||||
};
|
||||
|
||||
REGISTER_COMPONENT_SCRIPT_WRAPPER(MirageScripted)
|
37
source/simulation2/components/ICmpMirage.h
Normal file
37
source/simulation2/components/ICmpMirage.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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
|
Loading…
Reference in New Issue
Block a user