Index: ps/trunk/binaries/data/mods/public/simulation/components/Fogging.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Fogging.js (revision 22246) +++ ps/trunk/binaries/data/mods/public/simulation/components/Fogging.js (revision 22247) @@ -1,224 +1,229 @@ const VIS_HIDDEN = 0; const VIS_FOGGED = 1; const VIS_VISIBLE = 2; function Fogging() {} Fogging.prototype.Schema = "Allows this entity to be replaced by mirage entities in the fog-of-war." + ""; Fogging.prototype.Init = function() { this.activated = false; this.mirages = []; this.miraged = []; this.seen = []; let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers(); for (let player = 0; player < numPlayers; ++player) { this.mirages.push(INVALID_ENTITY); this.miraged.push(false); this.seen.push(false); } }; Fogging.prototype.Activate = function() { let mustUpdate = !this.activated; this.activated = true; if (mustUpdate) { // Load a mirage for each player who has already seen the entity let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers(); let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); for (let player = 0; player < numPlayers; ++player) if (this.seen[player] && cmpRangeManager.GetLosVisibility(this.entity, player) != "visible") this.LoadMirage(player); } }; Fogging.prototype.IsActivated = function() { return this.activated; }; Fogging.prototype.LoadMirage = function(player) { if (!this.activated) { error("LoadMirage called for an entity with fogging deactivated"); return; } this.miraged[player] = true; if (this.mirages[player] == INVALID_ENTITY) { var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); var templateName = "mirage|" + cmpTemplateManager.GetCurrentTemplateName(this.entity); this.mirages[player] = Engine.AddEntity(templateName); } var cmpMirage = Engine.QueryInterface(this.mirages[player], IID_Mirage); if (!cmpMirage) { error("Failed to load a mirage for entity " + this.entity); this.mirages[player] = INVALID_ENTITY; return; } // Copy basic mirage properties cmpMirage.SetPlayer(player); cmpMirage.SetParent(this.entity); // Copy cmpOwnership data var cmpParentOwnership = Engine.QueryInterface(this.entity, IID_Ownership); var cmpMirageOwnership = Engine.QueryInterface(this.mirages[player], IID_Ownership); if (!cmpParentOwnership || !cmpMirageOwnership) { error("Failed to copy the ownership data of the fogged entity " + this.entity); 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 copy the position data of the fogged entity " + this.entity); 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 copy the visual data of the fogged entity " + this.entity); return; } cmpMirageVisualActor.SetActorSeed(cmpParentVisualActor.GetActorSeed()); // Store valuable information into the mirage component (especially for the GUI) var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); if (cmpIdentity) cmpMirage.CopyIdentity(cmpIdentity); var cmpFoundation = Engine.QueryInterface(this.entity, IID_Foundation); if (cmpFoundation) cmpMirage.CopyFoundation(cmpFoundation); var cmpRepairable = Engine.QueryInterface(this.entity, IID_Repairable); if (cmpRepairable && !cmpFoundation) cmpMirage.CopyRepairable(cmpRepairable); var cmpHealth = Engine.QueryInterface(this.entity, IID_Health); if (cmpHealth) cmpMirage.CopyHealth(cmpHealth); var cmpCapturable = Engine.QueryInterface(this.entity, IID_Capturable); if (cmpCapturable) cmpMirage.CopyCapturable(cmpCapturable); var cmpResourceSupply = Engine.QueryInterface(this.entity, IID_ResourceSupply); if (cmpResourceSupply) cmpMirage.CopyResourceSupply(cmpResourceSupply); var cmpMarket = Engine.QueryInterface(this.entity, IID_Market); if (cmpMarket) cmpMirage.CopyMarket(cmpMarket); // Notify the GUI the entity has been replaced by a mirage, in case it is selected at this moment var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGuiInterface.AddMiragedEntity(player, this.entity, this.mirages[player]); // Notify the range manager the visibility of this entity must be updated let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); cmpRangeManager.RequestVisibilityUpdate(this.entity); }; Fogging.prototype.ForceMiraging = function(player) { if (!this.activated) return; this.seen[player] = true; this.LoadMirage(player); }; Fogging.prototype.IsMiraged = function(player) { if (player < 0 || player >= this.mirages.length) return false; return this.miraged[player]; }; Fogging.prototype.GetMirage = function(player) { if (player < 0 || player >= this.mirages.length) return INVALID_ENTITY; return this.mirages[player]; }; Fogging.prototype.WasSeen = function(player) { if (player < 0 || player >= this.seen.length) return false; return this.seen[player]; }; -Fogging.prototype.OnDestroy = function(msg) +Fogging.prototype.OnOwnershipChanged = function(msg) { - var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); - for (var player = 0; player < this.mirages.length; ++player) + // Always activate fogging for non-Gaia entities + if (msg.to > 0) + this.Activate(); + + if (msg.to != -1) + return; + + let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + for (let player in this.mirages) { + if (this.mirages[player] == INVALID_ENTITY) + continue; + + // When this.entity is in the line of sight of the player, its mirage is hidden, rather than destroyed, to save on performance. + // All hidden mirages can be destroyed now (they won't be needed again), and other mirages will destroy themselves when they get out of the fog. if (cmpRangeManager.GetLosVisibility(this.mirages[player], player) == "hidden") { Engine.DestroyEntity(this.mirages[player]); continue; } - var cmpMirage = Engine.QueryInterface(this.mirages[player], IID_Mirage); + let cmpMirage = Engine.QueryInterface(this.mirages[player], IID_Mirage); if (cmpMirage) cmpMirage.SetParent(INVALID_ENTITY); } }; -Fogging.prototype.OnOwnershipChanged = function(msg) -{ - // Always activate fogging for non-Gaia entities - if (msg.to > 0) - this.Activate(); -}; - Fogging.prototype.OnVisibilityChanged = function(msg) { if (msg.player < 0 || msg.player >= this.mirages.length) return; if (msg.newVisibility == VIS_VISIBLE) { this.miraged[msg.player] = false; this.seen[msg.player] = true; } if (msg.newVisibility == VIS_FOGGED && this.activated) this.LoadMirage(msg.player); }; Engine.RegisterComponentType(IID_Fogging, "Fogging", Fogging); Index: ps/trunk/binaries/data/mods/public/simulation/components/Mirage.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Mirage.js (revision 22246) +++ ps/trunk/binaries/data/mods/public/simulation/components/Mirage.js (revision 22247) @@ -1,223 +1,224 @@ 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.miragedIids = new Set(); this.classesList = []; this.numBuilders = 0; this.buildTime = {}; this.maxHitpoints = null; this.hitpoints = null; this.repairable = null; this.unhealable = null; this.capturePoints = []; this.maxCapturePoints = 0; this.maxAmount = null; this.amount = null; this.type = null; this.isInfinite = null; this.killBeforeGather = null; this.maxGatherers = null; this.numGatherers = null; this.traders = null; this.marketType = null; this.internationalBonus = null; }; Mirage.prototype.SetParent = function(ent) { this.parent = ent; }; Mirage.prototype.GetPlayer = function() { return this.player; }; Mirage.prototype.SetPlayer = function(player) { this.player = player; }; Mirage.prototype.Mirages = function(iid) { return this.miragedIids.has(iid); }; // ============================ // Parent entity data Mirage.prototype.CopyIdentity = function(cmpIdentity) { this.miragedIids.add(IID_Identity); // In almost all cases we want to ignore mirage entities when querying Identity components of owned entities. // To avoid adding a test everywhere, we don't transfer the classeslist in the template but here. // We clone this since the classes list is not synchronized and since the mirage should be a snapshot of the entity at the given time. this.classesList = clone(cmpIdentity.GetClassesList()); }; Mirage.prototype.GetClassesList = function() { return this.classesList }; // Foundation data Mirage.prototype.CopyFoundation = function(cmpFoundation) { this.miragedIids.add(IID_Foundation); this.numBuilders = cmpFoundation.GetNumBuilders(); this.buildTime = cmpFoundation.GetBuildTime(); }; Mirage.prototype.GetNumBuilders = function() { return this.numBuilders; }; Mirage.prototype.GetBuildTime = function() { return this.buildTime; }; // Repairable data (numBuilders and buildTime shared with foundation as entities can't have both) Mirage.prototype.CopyRepairable = function(cmpRepairable) { this.miragedIids.add(IID_Repairable); this.numBuilders = cmpRepairable.GetNumBuilders(); this.buildTime = cmpRepairable.GetBuildTime(); }; // Health data Mirage.prototype.CopyHealth = function(cmpHealth) { this.miragedIids.add(IID_Health); this.maxHitpoints = cmpHealth.GetMaxHitpoints(); this.hitpoints = cmpHealth.GetHitpoints(); this.repairable = cmpHealth.IsRepairable(); this.unhealable = cmpHealth.IsUnhealable(); }; Mirage.prototype.GetMaxHitpoints = function() { return this.maxHitpoints; }; Mirage.prototype.GetHitpoints = function() { return this.hitpoints; }; Mirage.prototype.IsRepairable = function() { return this.repairable; }; Mirage.prototype.IsUnhealable = function() { return this.unhealable; }; // Capture data Mirage.prototype.CopyCapturable = function(cmpCapturable) { this.miragedIids.add(IID_Capturable); this.capturePoints = clone(cmpCapturable.GetCapturePoints()); this.maxCapturePoints = cmpCapturable.GetMaxCapturePoints(); }; Mirage.prototype.GetMaxCapturePoints = function() { return this.maxCapturePoints; }; Mirage.prototype.GetCapturePoints = function() { return this.capturePoints; }; Mirage.prototype.CanCapture = Capturable.prototype.CanCapture; // ResourceSupply data Mirage.prototype.CopyResourceSupply = function(cmpResourceSupply) { this.miragedIids.add(IID_ResourceSupply); this.maxAmount = cmpResourceSupply.GetMaxAmount(); this.amount = cmpResourceSupply.GetCurrentAmount(); this.type = cmpResourceSupply.GetType(); this.isInfinite = cmpResourceSupply.IsInfinite(); this.killBeforeGather = cmpResourceSupply.GetKillBeforeGather(); this.maxGatherers = cmpResourceSupply.GetMaxGatherers(); this.numGatherers = cmpResourceSupply.GetNumGatherers(); }; Mirage.prototype.GetMaxAmount = function() { return this.maxAmount; }; Mirage.prototype.GetCurrentAmount = function() { return this.amount; }; Mirage.prototype.GetType = function() { return this.type; }; Mirage.prototype.IsInfinite = function() { return this.isInfinite; }; Mirage.prototype.GetKillBeforeGather = function() { return this.killBeforeGather; }; Mirage.prototype.GetMaxGatherers = function() { return this.maxGatherers; }; Mirage.prototype.GetNumGatherers = function() { return this.numGatherers; }; // Market data Mirage.prototype.CopyMarket = function(cmpMarket) { this.miragedIids.add(IID_Market); this.traders = new Set(); for (let trader of cmpMarket.GetTraders()) { let cmpTrader = Engine.QueryInterface(trader, IID_Trader); let cmpOwnership = Engine.QueryInterface(trader, IID_Ownership); if (!cmpTrader || !cmpOwnership) { cmpMarket.RemoveTrader(trader); continue; } if (this.player != cmpOwnership.GetOwner()) continue; cmpTrader.SwitchMarket(cmpMarket.entity, this.entity); cmpMarket.RemoveTrader(trader); this.AddTrader(trader); } this.marketType = cmpMarket.GetType(); this.internationalBonus = cmpMarket.GetInternationalBonus(); }; Mirage.prototype.HasType = function(type) { return this.marketType.has(type); }; Mirage.prototype.GetInternationalBonus = function() { return this.internationalBonus; }; Mirage.prototype.AddTrader = function(trader) { this.traders.add(trader); }; Mirage.prototype.RemoveTrader = function(trader) { this.traders.delete(trader); }; Mirage.prototype.UpdateTraders = function(msg) { let cmpMarket = Engine.QueryInterface(this.parent, IID_Market); if (!cmpMarket) // The parent market does not exist anymore { for (let trader of this.traders) { let cmpTrader = Engine.QueryInterface(trader, IID_Trader); if (cmpTrader) cmpTrader.RemoveMarket(this.entity); } return; } // The market becomes visible, switch all traders from the mirage to the market for (let trader of this.traders) { let cmpTrader = Engine.QueryInterface(trader, IID_Trader); if (!cmpTrader) continue; cmpTrader.SwitchMarket(this.entity, cmpMarket.entity); this.RemoveTrader(trader); cmpMarket.AddTrader(trader); } }; // ============================ Mirage.prototype.OnVisibilityChanged = function(msg) { + // Mirages get VIS_HIDDEN when the original entity becomes VIS_VISIBLE. if (msg.player != this.player || msg.newVisibility != VIS_HIDDEN) return; if (this.miragedIids.has(IID_Market)) this.UpdateTraders(msg); if (this.parent == INVALID_ENTITY) Engine.DestroyEntity(this.entity); else Engine.PostMessage(this.entity, MT_EntityRenamed, { "entity": this.entity, "newentity": this.parent }); }; Engine.RegisterComponentType(IID_Mirage, "Mirage", Mirage);