Index: ps/trunk/binaries/data/mods/public/simulation/components/Mirage.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Mirage.js (revision 25078) +++ ps/trunk/binaries/data/mods/public/simulation/components/Mirage.js (revision 25079) @@ -1,233 +1,277 @@ 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.player = null; - this.miragedIids = new Set(); - - this.classesList = []; - - this.numBuilders = 0; - this.buildTime = {}; - - this.maxHitpoints = null; - this.hitpoints = null; - this.repairable = null; - this.unhealable = null; - this.injured = 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; + this.miragedIids = new Map(); }; Mirage.prototype.SetParent = function(ent) { this.parent = ent; }; Mirage.prototype.GetParent = function() { return this.parent; }; Mirage.prototype.SetPlayer = function(player) { this.player = player; }; Mirage.prototype.GetPlayer = function() { return this.player; }; Mirage.prototype.Mirages = function(iid) { return this.miragedIids.has(iid); }; +Mirage.prototype.Get = function(iid) +{ + return this.miragedIids.get(iid); +}; + // ============================ // Parent entity data -Mirage.prototype.CopyIdentity = function(cmpIdentity) +function MiragedIdentity() {} +MiragedIdentity.prototype.Init = function(cmpIdentity) { - this.miragedIids.add(IID_Identity); // Mirages don't get identity classes via the template-filter, so that code can query // identity components via Engine.QueryInterface without having to explicitly check for mirages. // This is cloned as otherwise we get a reference to Identity's property, // and that array is deleted when serializing (as it's not seralized), which ends in OOS. - this.classesList = clone(cmpIdentity.GetClassesList()); + this.classes = clone(cmpIdentity.GetClassesList()); }; -Mirage.prototype.GetClassesList = function() { return this.classesList; }; +MiragedIdentity.prototype.GetClassesList = function() { return this.classes; }; +Engine.RegisterGlobal("MiragedIdentity", MiragedIdentity); + +Mirage.prototype.CopyIdentity = function(cmpIdentity) +{ + let mirage = new MiragedIdentity(); + mirage.Init(cmpIdentity); + this.miragedIids.set(IID_Identity, mirage); +}; // Foundation data -Mirage.prototype.CopyFoundation = function(cmpFoundation) +function MiragedFoundation() {} +MiragedFoundation.prototype.Init = 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; }; +MiragedFoundation.prototype.GetNumBuilders = function() { return this.numBuilders; }; +MiragedFoundation.prototype.GetBuildTime = function() { return this.buildTime; }; +Engine.RegisterGlobal("MiragedFoundation", MiragedFoundation); -// Repairable data (numBuilders and buildTime shared with foundation as entities can't have both) +Mirage.prototype.CopyFoundation = function(cmpFoundation) +{ + let mirage = new MiragedFoundation(); + mirage.Init(cmpFoundation); + this.miragedIids.set(IID_Foundation, mirage); +}; -Mirage.prototype.CopyRepairable = function(cmpRepairable) +// Repairable data + +function MiragedRepairable() {} +MiragedRepairable.prototype.Init = function(cmpRepairable) { - this.miragedIids.add(IID_Repairable); this.numBuilders = cmpRepairable.GetNumBuilders(); this.buildTime = cmpRepairable.GetBuildTime(); }; +MiragedRepairable.prototype.GetNumBuilders = function() { return this.numBuilders; }; +MiragedRepairable.prototype.GetBuildTime = function() { return this.buildTime; }; +Engine.RegisterGlobal("MiragedRepairable", MiragedRepairable); + +Mirage.prototype.CopyRepairable = function(cmpRepairable) +{ + let mirage = new MiragedRepairable(); + mirage.Init(cmpRepairable); + this.miragedIids.set(IID_Repairable, mirage); +}; + // Health data -Mirage.prototype.CopyHealth = function(cmpHealth) +function MiragedHealth() {} +MiragedHealth.prototype.Init = function(cmpHealth) { - this.miragedIids.add(IID_Health); this.maxHitpoints = cmpHealth.GetMaxHitpoints(); this.hitpoints = cmpHealth.GetHitpoints(); this.repairable = cmpHealth.IsRepairable(); this.injured = cmpHealth.IsInjured(); 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.IsInjured = function() { return this.injured; }; -Mirage.prototype.IsUnhealable = function() { return this.unhealable; }; +MiragedHealth.prototype.GetMaxHitpoints = function() { return this.maxHitpoints; }; +MiragedHealth.prototype.GetHitpoints = function() { return this.hitpoints; }; +MiragedHealth.prototype.IsRepairable = function() { return this.repairable; }; +MiragedHealth.prototype.IsInjured = function() { return this.injured; }; +MiragedHealth.prototype.IsUnhealable = function() { return this.unhealable; }; +Engine.RegisterGlobal("MiragedHealth", MiragedHealth); + +Mirage.prototype.CopyHealth = function(cmpHealth) +{ + let mirage = new MiragedHealth(); + mirage.Init(cmpHealth); + this.miragedIids.set(IID_Health, mirage); +}; // Capture data -Mirage.prototype.CopyCapturable = function(cmpCapturable) +function MiragedCapturable() {} +MiragedCapturable.prototype.Init = 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; }; +MiragedCapturable.prototype.GetCapturePoints = function() { return this.capturePoints; }; +MiragedCapturable.prototype.GetMaxCapturePoints = function() { return this.maxCapturePoints; }; +MiragedCapturable.prototype.CanCapture = Capturable.prototype.CanCapture; +Engine.RegisterGlobal("MiragedCapturable", MiragedCapturable); -Mirage.prototype.CanCapture = Capturable.prototype.CanCapture; +Mirage.prototype.CopyCapturable = function(cmpCapturable) +{ + let mirage = new MiragedCapturable(); + mirage.Init(cmpCapturable); + this.miragedIids.set(IID_Capturable, mirage); +}; // ResourceSupply data - -Mirage.prototype.CopyResourceSupply = function(cmpResourceSupply) +function MiragedResourceSupply() {} +MiragedResourceSupply.prototype.Init = 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; }; +MiragedResourceSupply.prototype.GetMaxAmount = function() { return this.maxAmount; }; +MiragedResourceSupply.prototype.GetCurrentAmount = function() { return this.amount; }; +MiragedResourceSupply.prototype.GetType = function() { return this.type; }; +MiragedResourceSupply.prototype.IsInfinite = function() { return this.isInfinite; }; +MiragedResourceSupply.prototype.GetKillBeforeGather = function() { return this.killBeforeGather; }; +MiragedResourceSupply.prototype.GetMaxGatherers = function() { return this.maxGatherers; }; +MiragedResourceSupply.prototype.GetNumGatherers = function() { return this.numGatherers; }; + +// 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. +MiragedResourceSupply.prototype.GetDiminishingReturns = function() { return null; }; -// Market data +Engine.RegisterGlobal("MiragedResourceSupply", MiragedResourceSupply); -Mirage.prototype.CopyMarket = function(cmpMarket) +Mirage.prototype.CopyResourceSupply = function(cmpResourceSupply) +{ + let mirage = new MiragedResourceSupply(); + mirage.Init(cmpResourceSupply); + this.miragedIids.set(IID_ResourceSupply, mirage); +}; + +// Market data +function MiragedMarket() {} +MiragedMarket.prototype.Init = function(cmpMarket, entity, parent, player) { - this.miragedIids.add(IID_Market); + this.entity = entity; + this.parent = parent; + this.player = player; + 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); }; +MiragedMarket.prototype.HasType = function(type) { return this.marketType.has(type); }; +MiragedMarket.prototype.GetInternationalBonus = function() { return this.internationalBonus; }; +MiragedMarket.prototype.AddTrader = function(trader) { this.traders.add(trader); }; +MiragedMarket.prototype.RemoveTrader = function(trader) { this.traders.delete(trader); }; -Mirage.prototype.UpdateTraders = function(msg) +MiragedMarket.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); } }; +Engine.RegisterGlobal("MiragedMarket", MiragedMarket); + +Mirage.prototype.CopyMarket = function(cmpMarket) +{ + let mirage = new MiragedMarket(); + mirage.Init(cmpMarket, this.entity, this.parent, this.player); + this.miragedIids.set(IID_Market, mirage); +}; // ============================ 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); + this.miragedIids.get(IID_Market).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); Index: ps/trunk/binaries/data/mods/public/simulation/components/ResourceGatherer.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/ResourceGatherer.js (revision 25078) +++ ps/trunk/binaries/data/mods/public/simulation/components/ResourceGatherer.js (revision 25079) @@ -1,397 +1,391 @@ function ResourceGatherer() {} ResourceGatherer.prototype.Schema = "Lets the unit gather resources from entities that have the ResourceSupply component." + "" + "2.0" + "1.0" + "" + "1" + "3" + "3" + "2" + "" + "" + "10" + "10" + "10" + "10" + "" + "" + "" + "" + "" + "" + "" + "" + "" + Resources.BuildSchema("positiveDecimal", [], true) + "" + "" + Resources.BuildSchema("positiveDecimal") + ""; ResourceGatherer.prototype.Init = function() { this.capacities = {}; this.carrying = {}; // { generic type: integer amount currently carried } // (Note that this component supports carrying multiple types of resources, // each with an independent capacity, but the rest of the game currently // ensures and assumes we'll only be carrying one type at once) // The last exact type gathered, so we can render appropriate props this.lastCarriedType = undefined; // { generic, specific } }; /** * Returns data about what resources the unit is currently carrying, * in the form [ {"type":"wood", "amount":7, "max":10} ] */ ResourceGatherer.prototype.GetCarryingStatus = function() { let ret = []; for (let type in this.carrying) { ret.push({ "type": type, "amount": this.carrying[type], "max": +this.GetCapacity(type) }); } return ret; }; /** * Used to instantly give resources to unit * @param resources The same structure as returned form GetCarryingStatus */ ResourceGatherer.prototype.GiveResources = function(resources) { for (let resource of resources) this.carrying[resource.type] = +resource.amount; Engine.PostMessage(this.entity, MT_ResourceCarryingChanged, { "to": this.GetCarryingStatus() }); }; /** * Returns the generic type of one particular resource this unit is * currently carrying, or undefined if none. */ ResourceGatherer.prototype.GetMainCarryingType = function() { // Return the first key, if any for (let type in this.carrying) return type; return undefined; }; /** * Returns the exact resource type we last picked up, as long as * we're still carrying something similar enough, in the form * { generic, specific } */ ResourceGatherer.prototype.GetLastCarriedType = function() { if (this.lastCarriedType && this.lastCarriedType.generic in this.carrying) return this.lastCarriedType; return undefined; }; ResourceGatherer.prototype.SetLastCarriedType = function(lastCarriedType) { this.lastCarriedType = lastCarriedType; }; // Since this code is very performancecritical and applying technologies quite slow, cache it. ResourceGatherer.prototype.RecalculateGatherRates = function() { this.baseSpeed = ApplyValueModificationsToEntity("ResourceGatherer/BaseSpeed", +this.template.BaseSpeed, this.entity); this.rates = {}; for (let r in this.template.Rates) { let type = r.split("."); if (!Resources.GetResource(type[0]).subtypes[type[1]]) { error("Resource subtype not found: " + type[0] + "." + type[1]); continue; } let rate = ApplyValueModificationsToEntity("ResourceGatherer/Rates/" + r, +this.template.Rates[r], this.entity); this.rates[r] = rate * this.baseSpeed; } }; ResourceGatherer.prototype.RecalculateCapacities = function() { this.capacities = {}; for (let r in this.template.Capacities) this.capacities[r] = ApplyValueModificationsToEntity("ResourceGatherer/Capacities/" + r, +this.template.Capacities[r], this.entity); }; ResourceGatherer.prototype.RecalculateCapacity = function(type) { if (type in this.capacities) this.capacities[type] = ApplyValueModificationsToEntity("ResourceGatherer/Capacities/" + type, +this.template.Capacities[type], this.entity); }; ResourceGatherer.prototype.GetGatherRates = function() { return this.rates; }; ResourceGatherer.prototype.GetGatherRate = function(resourceType) { if (!this.template.Rates[resourceType]) return 0; return this.rates[resourceType]; }; ResourceGatherer.prototype.GetCapacity = function(resourceType) { if (!this.template.Capacities[resourceType]) return 0; return this.capacities[resourceType]; }; ResourceGatherer.prototype.GetRange = function() { return { "max": +this.template.MaxDistance, "min": 0 }; // maybe this should depend on the unit or target or something? }; /** * Gather from the target entity. This should only be called after a successful range check, * and if the target has a compatible ResourceSupply. * Call interval will be determined by gather rate, so always gather 1 amount when called. */ ResourceGatherer.prototype.PerformGather = function(target) { if (!this.GetTargetGatherRate(target)) return { "exhausted": true }; let gatherAmount = 1; let cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply); let type = cmpResourceSupply.GetType(); // Initialise the carried count if necessary if (!this.carrying[type.generic]) this.carrying[type.generic] = 0; // Find the maximum so we won't exceed our capacity let maxGathered = this.GetCapacity(type.generic) - this.carrying[type.generic]; let status = cmpResourceSupply.TakeResources(Math.min(gatherAmount, maxGathered)); this.carrying[type.generic] += status.amount; this.lastCarriedType = type; // Update stats of how much the player collected. // (We have to do it here rather than at the dropsite, because we // need to know what subtype it was) let cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker); if (cmpStatisticsTracker) cmpStatisticsTracker.IncreaseResourceGatheredCounter(type.generic, status.amount, type.specific); Engine.PostMessage(this.entity, MT_ResourceCarryingChanged, { "to": this.GetCarryingStatus() }); return { "amount": status.amount, "exhausted": status.exhausted, "filled": this.carrying[type.generic] >= this.GetCapacity(type.generic) }; }; /** * Compute the amount of resources collected per second from the target. * Returns 0 if resources cannot be collected (e.g. the target doesn't * exist, or is the wrong type). */ ResourceGatherer.prototype.GetTargetGatherRate = function(target) { let cmpResourceSupply = QueryMiragedInterface(target, IID_ResourceSupply); if (!cmpResourceSupply) return 0; let type = cmpResourceSupply.GetType(); let rate = 0; if (type.specific) rate = this.GetGatherRate(type.generic+"."+type.specific); if (rate == 0 && type.generic) rate = this.GetGatherRate(type.generic); - if ("Mirages" in cmpResourceSupply) - return rate; - - // 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) let diminishingReturns = cmpResourceSupply.GetDiminishingReturns(); if (diminishingReturns) rate *= diminishingReturns; return rate; }; /** * Returns whether this unit can carry more of the given type of resource. * (This ignores whether the unit is actually able to gather that * resource type or not.) */ ResourceGatherer.prototype.CanCarryMore = function(type) { let amount = this.carrying[type] || 0; return amount < this.GetCapacity(type); }; ResourceGatherer.prototype.IsCarrying = function(type) { let amount = this.carrying[type] || 0; return amount > 0; }; /** * Returns whether this unit is carrying any resources of a type that is * not the requested type. (This is to support cases where the unit is * only meant to be able to carry one type at once.) */ ResourceGatherer.prototype.IsCarryingAnythingExcept = function(exceptedType) { for (let type in this.carrying) if (type != exceptedType) return true; return false; }; /** * Transfer our carried resources to our owner immediately. * Only resources of the appropriate types will be transferred. * (This should typically be called after reaching a dropsite.) * * @param {number} target - The target entity ID to drop resources at. */ ResourceGatherer.prototype.CommitResources = function(target) { let cmpResourceDropsite = Engine.QueryInterface(target, IID_ResourceDropsite); if (!cmpResourceDropsite) return; let change = cmpResourceDropsite.ReceiveResources(this.carrying, this.entity); let changed = false; for (let type in change) { this.carrying[type] -= change[type]; if (this.carrying[type] == 0) delete this.carrying[type]; changed = true; } if (changed) Engine.PostMessage(this.entity, MT_ResourceCarryingChanged, { "to": this.GetCarryingStatus() }); }; /** * Drop all currently-carried resources. * (Currently they just vanish after being dropped - we don't bother depositing * them onto the ground.) */ ResourceGatherer.prototype.DropResources = function() { this.carrying = {}; Engine.PostMessage(this.entity, MT_ResourceCarryingChanged, { "to": this.GetCarryingStatus() }); }; /** * @param {string} type - A generic resource type. */ ResourceGatherer.prototype.AddToPlayerCounter = function(type) { // We need to be removed from the player counter first. if (this.lastGathered) return; let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player); if (cmpPlayer) cmpPlayer.AddResourceGatherer(type); this.lastGathered = type; }; /** * @param {number} playerid - Optionally a player ID. */ ResourceGatherer.prototype.RemoveFromPlayerCounter = function(playerid) { if (!this.lastGathered) return; let cmpPlayer = playerid != undefined ? QueryPlayerIDInterface(playerid) : QueryOwnerInterface(this.entity, IID_Player); if (cmpPlayer) cmpPlayer.RemoveResourceGatherer(this.lastGathered); delete this.lastGathered; }; // Since we cache gather rates, we need to make sure we update them when tech changes. // and when our owner change because owners can had different techs. ResourceGatherer.prototype.OnValueModification = function(msg) { if (msg.component != "ResourceGatherer") return; // NB: at the moment, 0 A.D. always uses the fast path, the other is mod support. if (msg.valueNames.length === 1) { if (msg.valueNames[0].indexOf("Capacities") !== -1) this.RecalculateCapacity(msg.valueNames[0].substr(28)); else this.RecalculateGatherRates(); } else { this.RecalculateGatherRates(); this.RecalculateCapacities(); } }; ResourceGatherer.prototype.OnOwnershipChanged = function(msg) { if (msg.to == INVALID_PLAYER) { this.RemoveFromPlayerCounter(msg.from); return; } this.RecalculateGatherRates(); this.RecalculateCapacities(); }; ResourceGatherer.prototype.OnGlobalInitGame = function(msg) { this.RecalculateGatherRates(); this.RecalculateCapacities(); }; ResourceGatherer.prototype.OnMultiplierChanged = function(msg) { let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player); if (cmpPlayer && msg.player == cmpPlayer.GetPlayerID()) this.RecalculateGatherRates(); }; Engine.RegisterComponentType(IID_ResourceGatherer, "ResourceGatherer", ResourceGatherer); Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Player.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/helpers/Player.js (revision 25078) +++ ps/trunk/binaries/data/mods/public/simulation/helpers/Player.js (revision 25079) @@ -1,407 +1,407 @@ /** * Used to create player entities prior to reading the rest of a map, * all other initialization must be done after loading map (terrain/entities). * DO NOT use other components here, as they may fail unpredictably. * settings is the object containing settings for this map. * newPlayers if true will remove old player entities or add new ones until * the new number of player entities is obtained * (used when loading a map or when Atlas changes the number of players). */ function LoadPlayerSettings(settings, newPlayers) { var playerDefaults = Engine.ReadJSONFile("simulation/data/settings/player_defaults.json").PlayerData; // Default settings if (!settings) settings = {}; // Add gaia to simplify iteration // (if gaia is not already the first civ such as when called from Atlas' ActorViewer) if (settings.PlayerData && settings.PlayerData[0] && (!settings.PlayerData[0].Civ || settings.PlayerData[0].Civ != "gaia")) settings.PlayerData.unshift(null); var playerData = settings.PlayerData; // Disable the AIIinterface when no AI players are present if (playerData && !playerData.some(v => v && !!v.AI)) Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface).Disable(); var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); var numPlayers = cmpPlayerManager.GetNumPlayers(); var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); // Remove existing players or add new ones if (newPlayers) { var settingsNumPlayers = 9; // default 8 players + gaia if (playerData) settingsNumPlayers = playerData.length; // includes gaia (see above) else warn("Player.js: Setup has no player data - using defaults"); while (settingsNumPlayers > numPlayers) { // Add player entity to engine var entID = Engine.AddEntity(GetPlayerTemplateName(getSetting(playerData, playerDefaults, numPlayers, "Civ"))); var cmpPlayer = Engine.QueryInterface(entID, IID_Player); if (!cmpPlayer) throw new Error("Player.js: Error creating player entity " + numPlayers); cmpPlayerManager.AddPlayer(entID); ++numPlayers; } while (settingsNumPlayers < numPlayers) { cmpPlayerManager.RemoveLastPlayer(); --numPlayers; } } // Even when no new player, we must check the template compatibility as player template may be civ dependent for (var i = 0; i < numPlayers; ++i) { var template = GetPlayerTemplateName(getSetting(playerData, playerDefaults, i, "Civ")); var entID = cmpPlayerManager.GetPlayerByID(i); if (cmpTemplateManager.GetCurrentTemplateName(entID) === template) continue; // We need to recreate this player to have the right template entID = Engine.AddEntity(template); cmpPlayerManager.ReplacePlayer(i, entID); } // Initialize the player data for (var i = 0; i < numPlayers; ++i) { let cmpPlayer = QueryPlayerIDInterface(i); cmpPlayer.SetName(getSetting(playerData, playerDefaults, i, "Name")); cmpPlayer.SetCiv(getSetting(playerData, playerDefaults, i, "Civ")); var color = getSetting(playerData, playerDefaults, i, "Color"); cmpPlayer.SetColor(color.r, color.g, color.b); // Special case for gaia if (i == 0) { // Gaia should be its own ally. cmpPlayer.SetAlly(0); // Gaia is everyone's enemy for (var j = 1; j < numPlayers; ++j) cmpPlayer.SetEnemy(j); continue; } // PopulationLimit { let maxPopulation = settings.PlayerData[i].PopulationLimit !== undefined ? settings.PlayerData[i].PopulationLimit : settings.PopulationCap !== undefined ? settings.PopulationCap : playerDefaults[i].PopulationLimit !== undefined ? playerDefaults[i].PopulationLimit : undefined; if (maxPopulation !== undefined) cmpPlayer.SetMaxPopulation(maxPopulation); } // StartingResources if (settings.PlayerData[i].Resources !== undefined) cmpPlayer.SetResourceCounts(settings.PlayerData[i].Resources); else if (settings.StartingResources) { let resourceCounts = cmpPlayer.GetResourceCounts(); let newResourceCounts = {}; for (let resouces in resourceCounts) newResourceCounts[resouces] = settings.StartingResources; cmpPlayer.SetResourceCounts(newResourceCounts); } else if (playerDefaults[i].Resources !== undefined) cmpPlayer.SetResourceCounts(playerDefaults[i].Resources); // StartingTechnologies { let startingTechnologies = settings.PlayerData[i].StartingTechnologies || settings.StartingTechnologies || playerDefaults[i].StartingTechnologies || []; if (startingTechnologies.length) cmpPlayer.SetStartingTechnologies(startingTechnologies); } // DisabledTechnologies { let disabledTechnologies = settings.PlayerData[i].DisabledTechnologies || settings.DisabledTechnologies || playerDefaults[i].DisabledTechnologies || []; if (disabledTechnologies.length) cmpPlayer.SetDisabledTechnologies(disabledTechnologies); } // DisabledTemplates { let disabledTemplates = settings.PlayerData[i].DisabledTemplates || settings.DisabledTemplates || playerDefaults[i].DisabledTemplates || []; if (disabledTemplates.length) cmpPlayer.SetDisabledTemplates(disabledTemplates); } // DisableSpies if (settings.DisableSpies) { cmpPlayer.AddDisabledTechnology("unlock_spies"); cmpPlayer.AddDisabledTemplate("special/spy"); } // If diplomacy explicitly defined, use that; otherwise use teams if (getSetting(playerData, playerDefaults, i, "Diplomacy") !== undefined) cmpPlayer.SetDiplomacy(getSetting(playerData, playerDefaults, i, "Diplomacy")); else { // Init diplomacy var myTeam = getSetting(playerData, playerDefaults, i, "Team"); // Set all but self as enemies as SetTeam takes care of allies for (var j = 0; j < numPlayers; ++j) { if (i == j) cmpPlayer.SetAlly(j); else cmpPlayer.SetEnemy(j); } cmpPlayer.SetTeam(myTeam === undefined ? -1 : myTeam); } cmpPlayer.SetFormations( getSetting(playerData, playerDefaults, i, "Formations") || Engine.ReadJSONFile("simulation/data/civs/" + cmpPlayer.GetCiv() + ".json").Formations); var startCam = getSetting(playerData, playerDefaults, i, "StartingCamera"); if (startCam !== undefined) cmpPlayer.SetStartingCamera(startCam.Position, startCam.Rotation); } // NOTE: We need to do the team locking here, as otherwise // SetTeam can't ally the players. if (settings.LockTeams) for (let i = 0; i < numPlayers; ++i) QueryPlayerIDInterface(i).SetLockTeams(true); } // Get a setting if it exists or return default function getSetting(settings, defaults, idx, property) { if (settings && settings[idx] && (property in settings[idx])) return settings[idx][property]; // Use defaults if (defaults && defaults[idx] && (property in defaults[idx])) return defaults[idx][property]; return undefined; } function GetPlayerTemplateName(civ) { let path = "special/player/player"; if (Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).TemplateExists(path + "_" + civ)) return path + "_" + civ; return path; } /** * @param id An entity's ID * @returns The entity ID of the owner player (not his player ID) or ent if ent is a player entity. */ function QueryOwnerEntityID(ent) { let cmpPlayer = Engine.QueryInterface(ent, IID_Player); if (cmpPlayer) return ent; let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); if (!cmpOwnership) return null; let owner = cmpOwnership.GetOwner(); if (owner == INVALID_PLAYER) return null; let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); if (!cmpPlayerManager) return null; return cmpPlayerManager.GetPlayerByID(owner); } /** * Similar to Engine.QueryInterface but applies to the player entity * that owns the given entity. * iid is typically IID_Player. */ function QueryOwnerInterface(ent, iid = IID_Player) { var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); if (!cmpOwnership) return null; var owner = cmpOwnership.GetOwner(); if (owner == INVALID_PLAYER) return null; return QueryPlayerIDInterface(owner, iid); } /** * Similar to Engine.QueryInterface but applies to the player entity * with the given ID number. * iid is typically IID_Player. */ function QueryPlayerIDInterface(id, iid = IID_Player) { var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); var playerEnt = cmpPlayerManager.GetPlayerByID(id); if (!playerEnt) return null; return Engine.QueryInterface(playerEnt, iid); } /** * Similar to Engine.QueryInterface but first checks if the entity * mirages the interface. */ function QueryMiragedInterface(ent, iid) { - var cmp = Engine.QueryInterface(ent, IID_Mirage); - if (cmp && !cmp.Mirages(iid)) + let cmpMirage = Engine.QueryInterface(ent, IID_Mirage); + if (cmpMirage && !cmpMirage.Mirages(iid)) return null; - else if (!cmp) - cmp = Engine.QueryInterface(ent, iid); + else if (!cmpMirage) + return Engine.QueryInterface(ent, iid); - return cmp; + return cmpMirage.Get(iid); } /** * Similar to Engine.QueryInterface, but checks for all interfaces * implementing a builder list (currently Foundation and Repairable) * TODO Foundation and Repairable could both implement a BuilderList component */ function QueryBuilderListInterface(ent) { return Engine.QueryInterface(ent, IID_Foundation) || Engine.QueryInterface(ent, IID_Repairable); } /** * Returns true if the entity 'target' is owned by an ally of * the owner of 'entity'. */ function IsOwnedByAllyOfEntity(entity, target) { return IsOwnedByEntityHelper(entity, target, "IsAlly"); } function IsOwnedByMutualAllyOfEntity(entity, target) { return IsOwnedByEntityHelper(entity, target, "IsMutualAlly"); } function IsOwnedByEntityHelper(entity, target, check) { // Figure out which player controls us let owner = 0; let cmpOwnership = Engine.QueryInterface(entity, IID_Ownership); if (cmpOwnership) owner = cmpOwnership.GetOwner(); // Figure out which player controls the target entity let targetOwner = 0; let cmpOwnershipTarget = Engine.QueryInterface(target, IID_Ownership); if (cmpOwnershipTarget) targetOwner = cmpOwnershipTarget.GetOwner(); let cmpPlayer = QueryPlayerIDInterface(owner); return cmpPlayer && cmpPlayer[check](targetOwner); } /** * Returns true if the entity 'target' is owned by player */ function IsOwnedByPlayer(player, target) { var cmpOwnershipTarget = Engine.QueryInterface(target, IID_Ownership); return cmpOwnershipTarget && player == cmpOwnershipTarget.GetOwner(); } function IsOwnedByGaia(target) { return IsOwnedByPlayer(0, target); } /** * Returns true if the entity 'target' is owned by an ally of player */ function IsOwnedByAllyOfPlayer(player, target) { return IsOwnedByHelper(player, target, "IsAlly"); } function IsOwnedByMutualAllyOfPlayer(player, target) { return IsOwnedByHelper(player, target, "IsMutualAlly"); } function IsOwnedByNeutralOfPlayer(player, target) { return IsOwnedByHelper(player, target, "IsNeutral"); } function IsOwnedByEnemyOfPlayer(player, target) { return IsOwnedByHelper(player, target, "IsEnemy"); } function IsOwnedByHelper(player, target, check) { let targetOwner = 0; let cmpOwnershipTarget = Engine.QueryInterface(target, IID_Ownership); if (cmpOwnershipTarget) targetOwner = cmpOwnershipTarget.GetOwner(); let cmpPlayer = QueryPlayerIDInterface(player); return cmpPlayer && cmpPlayer[check](targetOwner); } Engine.RegisterGlobal("LoadPlayerSettings", LoadPlayerSettings); Engine.RegisterGlobal("QueryOwnerEntityID", QueryOwnerEntityID); Engine.RegisterGlobal("QueryOwnerInterface", QueryOwnerInterface); Engine.RegisterGlobal("QueryPlayerIDInterface", QueryPlayerIDInterface); Engine.RegisterGlobal("QueryMiragedInterface", QueryMiragedInterface); Engine.RegisterGlobal("QueryBuilderListInterface", QueryBuilderListInterface); Engine.RegisterGlobal("IsOwnedByAllyOfEntity", IsOwnedByAllyOfEntity); Engine.RegisterGlobal("IsOwnedByMutualAllyOfEntity", IsOwnedByMutualAllyOfEntity); Engine.RegisterGlobal("IsOwnedByPlayer", IsOwnedByPlayer); Engine.RegisterGlobal("IsOwnedByGaia", IsOwnedByGaia); Engine.RegisterGlobal("IsOwnedByAllyOfPlayer", IsOwnedByAllyOfPlayer); Engine.RegisterGlobal("IsOwnedByMutualAllyOfPlayer", IsOwnedByMutualAllyOfPlayer); Engine.RegisterGlobal("IsOwnedByNeutralOfPlayer", IsOwnedByNeutralOfPlayer); Engine.RegisterGlobal("IsOwnedByEnemyOfPlayer", IsOwnedByEnemyOfPlayer);