1
1
forked from 0ad/0ad

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:
Nicolas Auvray 2014-08-04 22:49:19 +00:00
parent e41f010f91
commit f7e591c9f2
22 changed files with 705 additions and 25 deletions

View File

@ -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();
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View 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);

View File

@ -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)

View 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);

View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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,

View File

@ -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;
}

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_entity_quasi">
<Fogging/>
<Identity>
<Civ>gaia</Civ>
<GenericName>Gaia</GenericName>

View File

@ -37,6 +37,7 @@
<SinkRate>3.0</SinkRate>
<SinkAccel>9.8</SinkAccel>
</Decay>
<Fogging/>
<Health>
<DeathType>corpse</DeathType>
<RegenRate>0</RegenRate>

View File

@ -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");

View File

@ -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

View File

@ -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)

View File

@ -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()))
return VIS_FOGGED;
}
CmpPtr<ICmpVision> cmpVision(ent);
if (!forceRetainInFog && !(cmpVision && cmpVision->GetRetainInFog()))
return VIS_HIDDEN;
// Otherwise not visible
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;
}
// 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;
}
@ -1487,18 +1518,29 @@ public:
EntityMap<EntityData>::iterator itEnts = m_EntityData.find(ent);
if (itEnts == m_EntityData.end())
return;
std::vector<u8> oldVisibilities;
std::vector<u8> 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);
}
}

View 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)

View 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

View 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)

View 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