Index: binaries/data/mods/public/simulation/ai/common-api/shared.js
===================================================================
--- binaries/data/mods/public/simulation/ai/common-api/shared.js
+++ binaries/data/mods/public/simulation/ai/common-api/shared.js
@@ -218,9 +218,13 @@
for (let evt of state.events.ConstructionFinished)
{
- // metada are already moved by EntityRenamed when needed (i.e. construction, not repair)
- if (evt.entity != evt.newentity)
- foundationFinished[evt.entity] = true;
+ foundationFinished[evt.entity] = true;
+ for (let player of this._players)
+ {
+ this._entityMetadata[player][evt.newentity] = this._entityMetadata[player][evt.entity];
+ this._entityMetadata[player][evt.entity] = {};
+ }
+ state.entities[evt.newentity].foundationProgress = undefined;
}
for (let evt of state.events.AIMetadata)
Index: binaries/data/mods/public/simulation/ai/petra/basesManager.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/basesManager.js
+++ binaries/data/mods/public/simulation/ai/petra/basesManager.js
@@ -200,13 +200,39 @@
for (const evt of events.ConstructionFinished)
{
- if (evt.newentity == evt.entity) // repaired building
- continue;
+ //if (evt.newentity == evt.entity) // repaired building
+ //continue;
const ent = gameState.getEntityById(evt.newentity);
if (!ent || ent.owner() != PlayerID)
continue;
if (ent.getMetadata(PlayerID, "base") === undefined)
- continue;
+ {
+ if (ent.hasClass("Unit"))
+ PETRA.getBestBase(gameState, ent).assignEntity(gameState, ent);
+ else if (ent.hasClass("CivCentre")) // build a new base around it
+ {
+ let newbase;
+ if (ent.foundationProgress() !== undefined)
+ newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_UNCONSTRUCTED);
+ else
+ {
+ newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_CAPTURED);
+ addBase = true;
+ }
+ newbase.assignEntity(gameState, ent);
+ }
+ else
+ {
+ let base;
+ // If dropsite on new island, create a base around it
+ if (!ent.decaying() && ent.resourceDropsiteTypes())
+ base = this.createBase(gameState, ent, PETRA.BaseManager.STATE_ANCHORLESS);
+ else
+ base = PETRA.getBestBase(gameState, ent) || this.noBase;
+ base.assignEntity(gameState, ent);
+ }
+ }
+
const base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
base.buildings.updateEnt(ent);
if (ent.resourceDropsiteTypes())
Index: binaries/data/mods/public/simulation/components/AIProxy.js
===================================================================
--- binaries/data/mods/public/simulation/components/AIProxy.js
+++ binaries/data/mods/public/simulation/components/AIProxy.js
@@ -37,6 +37,12 @@
this.cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface);
};
+AIProxy.prototype.Update = function()
+{
+ this.needsFullGet = true;
+ this.NotifyChange();
+};
+
AIProxy.prototype.Serialize = null; // we have no dynamic state to save
AIProxy.prototype.Deserialize = function()
Index: binaries/data/mods/public/simulation/components/Attack.js
===================================================================
--- binaries/data/mods/public/simulation/components/Attack.js
+++ binaries/data/mods/public/simulation/components/Attack.js
@@ -207,6 +207,15 @@
{
};
+Attack.prototype.Update = function(newTemplate) {
+ this.template = newTemplate;
+
+ // TODO: this should probably be done in UnitAI directly instead.
+ let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
+ if (cmpUnitAI)
+ cmpUnitAI.UpdateRangeQueries();
+};
+
Attack.prototype.GetAttackTypes = function(wantedTypes)
{
let types = g_AttackTypes.filter(type => !!this.template[type]);
Index: binaries/data/mods/public/simulation/components/AttackDetection.js
===================================================================
--- binaries/data/mods/public/simulation/components/AttackDetection.js
+++ binaries/data/mods/public/simulation/components/AttackDetection.js
@@ -22,6 +22,13 @@
this.suppressedList = [];
};
+AttackDetection.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+ this.Init();
+};
+
+
AttackDetection.prototype.ActivateTimer = function()
{
Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).SetTimeout(this.entity, IID_AttackDetection, "HandleTimeout", this.suppressionTime);
Index: binaries/data/mods/public/simulation/components/Auras.js
===================================================================
--- binaries/data/mods/public/simulation/components/Auras.js
+++ binaries/data/mods/public/simulation/components/Auras.js
@@ -18,6 +18,12 @@
this.Clean();
};
+Auras.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+ this.Init();
+};
+
// We can modify identifier if we want stackable auras in some case.
Auras.prototype.GetModifierIdentifier = function(name)
{
Index: binaries/data/mods/public/simulation/components/AutoBuildable.js
===================================================================
--- binaries/data/mods/public/simulation/components/AutoBuildable.js
+++ binaries/data/mods/public/simulation/components/AutoBuildable.js
@@ -5,6 +5,12 @@
this.UpdateRate();
}
+ Update(newTemplate)
+ {
+ this.template = newTemplate;
+ this.UpdateRate();
+ }
+
/**
* @return {number} - The rate with technologies and aura modification applied.
*/
Index: binaries/data/mods/public/simulation/components/BattleDetection.js
===================================================================
--- binaries/data/mods/public/simulation/components/BattleDetection.js
+++ binaries/data/mods/public/simulation/components/BattleDetection.js
@@ -39,6 +39,11 @@
this.state = "PEACE";
};
+BattleDetection.prototype.Update = function(newTemplate)
+{
+ warn("BattleDetection.Update() is not implemented");
+};
+
BattleDetection.prototype.SetState = function(state)
{
if (state == this.state)
Index: binaries/data/mods/public/simulation/components/Builder.js
===================================================================
--- binaries/data/mods/public/simulation/components/Builder.js
+++ binaries/data/mods/public/simulation/components/Builder.js
@@ -27,6 +27,16 @@
{
};
+Builder.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+
+ // Token changes may require selection updates.
+ let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
+ if (cmpPlayer)
+ Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).SetSelectionDirty(cmpPlayer.GetPlayerID());
+};
+
Builder.prototype.GetEntitiesList = function()
{
let string = this.template.Entities._string;
Index: binaries/data/mods/public/simulation/components/BuildingAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/BuildingAI.js
+++ binaries/data/mods/public/simulation/components/BuildingAI.js
@@ -30,6 +30,14 @@
this.targetUnits = [];
};
+BuildingAI.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+ this.targetUnits = [];
+ this.SetupRangeQuery();
+ this.SetupGaiaRangeQuery();
+};
+
BuildingAI.prototype.OnGarrisonedUnitsChanged = function(msg)
{
let classes = this.template.GarrisonArrowClasses;
Index: binaries/data/mods/public/simulation/components/Capturable.js
===================================================================
--- binaries/data/mods/public/simulation/components/Capturable.js
+++ binaries/data/mods/public/simulation/components/Capturable.js
@@ -19,6 +19,31 @@
this.capturePoints = [];
};
+Capturable.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+
+ if (!this.capturePoints.length)
+ {
+ this.UpdateCachedValues();
+ let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
+
+ const cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+ // As a sanity fallback, assign the points to gaia if we don't have an owner.
+ const to = cmpOwnership ? cmpOwnership.GetOwner() : 0;
+ for (let i = 0; i < numPlayers; ++i)
+ {
+ if (i == to)
+ this.capturePoints[i] = this.maxCapturePoints;
+ else
+ this.capturePoints[i] = 0;
+ }
+ Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.capturePoints });
+ this.CheckTimer();
+ } else
+ this.UpdateCachedValuesAndNotify();
+}
+
// Interface functions
/**
Index: binaries/data/mods/public/simulation/components/Cost.js
===================================================================
--- binaries/data/mods/public/simulation/components/Cost.js
+++ binaries/data/mods/public/simulation/components/Cost.js
@@ -27,6 +27,12 @@
this.populationCost = +this.template.Population;
};
+Cost.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+ this.populationCost = +this.template.Population;
+};
+
Cost.prototype.GetPopCost = function()
{
return this.populationCost;
Index: binaries/data/mods/public/simulation/components/EntityLimits.js
===================================================================
--- binaries/data/mods/public/simulation/components/EntityLimits.js
+++ binaries/data/mods/public/simulation/components/EntityLimits.js
@@ -101,6 +101,10 @@
}
};
+EntityLimits.prototype.Update = function(newTemplate)
+{
+}
+
EntityLimits.prototype.ChangeCount = function(category, value)
{
if (this.count[category] !== undefined)
Index: binaries/data/mods/public/simulation/components/Fogging.js
===================================================================
--- binaries/data/mods/public/simulation/components/Fogging.js
+++ binaries/data/mods/public/simulation/components/Fogging.js
@@ -39,6 +39,12 @@
}
};
+Fogging.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+ // Do we need to do anything ?
+};
+
Fogging.prototype.Activate = function()
{
let mustUpdate = !this.activated;
Index: binaries/data/mods/public/simulation/components/Formation.js
===================================================================
--- binaries/data/mods/public/simulation/components/Formation.js
+++ binaries/data/mods/public/simulation/components/Formation.js
@@ -159,6 +159,11 @@
.SetInterval(this.entity, IID_Formation, "ShapeUpdate", 1000, 1000, null);
};
+Formation.prototype.Update = function(newTemplate)
+{
+ warn("Updating Formation is not supported");
+};
+
Formation.prototype.Serialize = function()
{
let result = {};
Index: binaries/data/mods/public/simulation/components/FormationAttack.js
===================================================================
--- binaries/data/mods/public/simulation/components/FormationAttack.js
+++ binaries/data/mods/public/simulation/components/FormationAttack.js
@@ -10,6 +10,12 @@
this.canAttackAsFormation = this.template.CanAttackAsFormation == "true";
};
+FormationAttack.prototype.Update = function(newTemplate)
+{
+ // TODO
+ warn("FormationAttack component doesn't support template updates yet");
+};
+
FormationAttack.prototype.CanAttackAsFormation = function()
{
return this.canAttackAsFormation;
Index: binaries/data/mods/public/simulation/components/Foundation.js
===================================================================
--- binaries/data/mods/public/simulation/components/Foundation.js
+++ binaries/data/mods/public/simulation/components/Foundation.js
@@ -23,6 +23,12 @@
this.previewEntity = INVALID_ENTITY;
};
+Foundation.prototype.Update = function(newTemplate)
+{
+ // TODO
+ warn("Foundation component doesn't support template updates yet");
+}
+
Foundation.prototype.Serialize = function()
{
let ret = Object.assign({}, this);
Index: binaries/data/mods/public/simulation/components/GarrisonHolder.js
===================================================================
--- binaries/data/mods/public/simulation/components/GarrisonHolder.js
+++ binaries/data/mods/public/simulation/components/GarrisonHolder.js
@@ -49,6 +49,14 @@
this.allowedClasses = ApplyValueModificationsToEntity("GarrisonHolder/List/_string", this.template.List._string, this.entity);
};
+
+GarrisonHolder.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+ this.allowedClasses = ApplyValueModificationsToEntity("GarrisonHolder/List/_string", this.template.List._string, this.entity);
+ this.EjectOrKill(this.entities.filter(entity => !this.IsAllowedToBeGarrisoned(entity)));
+};
+
/**
* @param {number} entity - The entity to verify.
* @return {boolean} - Whether the given entity is garrisoned in this GarrisonHolder.
Index: binaries/data/mods/public/simulation/components/Garrisonable.js
===================================================================
--- binaries/data/mods/public/simulation/components/Garrisonable.js
+++ binaries/data/mods/public/simulation/components/Garrisonable.js
@@ -13,6 +13,22 @@
{
};
+Garrisonable.prototype.Update = function(newTemplate)
+{
+ if (!this.holder)
+ return;
+
+ let holder = this.holder;
+ this.UnGarrison(true, true);
+
+ // Now that we're ungarrisoned, we can update our size.
+ this.template = newTemplate;
+
+ let cmpGarrisonable = Engine.QueryInterface(msg.newentity, IID_Garrisonable);
+ if (cmpGarrisonable)
+ cmpGarrisonable.Garrison(holder, true);
+};
+
/**
* @param {string} type - Unused.
* @param {number} target - The entity ID of the target to check.
Index: binaries/data/mods/public/simulation/components/Gate.js
===================================================================
--- binaries/data/mods/public/simulation/components/Gate.js
+++ binaries/data/mods/public/simulation/components/Gate.js
@@ -20,6 +20,13 @@
this.locked = false;
};
+Gate.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+ // We should have an owner, as we had one before the template change.
+ this.SetupRangeQuery(Engine.QueryInterface(this.entity, IID_Ownership).GetOwner());
+};
+
Gate.prototype.OnOwnershipChanged = function(msg)
{
if (msg.to != INVALID_PLAYER)
Index: binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/GuiInterface.js
+++ binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -2051,6 +2051,12 @@
this.renamedEntities.push(msg);
};
+GuiInterface.prototype.RefreshEntity = function(entity)
+{
+ this.renamedEntities.push({ entity: entity, newentity: entity });
+};
+
+
/**
* List the GuiInterface functions that can be safely called by GUI scripts.
* (GUI scripts are non-deterministic and untrusted, so these functions must be
Index: binaries/data/mods/public/simulation/components/Heal.js
===================================================================
--- binaries/data/mods/public/simulation/components/Heal.js
+++ binaries/data/mods/public/simulation/components/Heal.js
@@ -49,6 +49,17 @@
{
};
+Heal.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+ // TODO: only do this if something changed
+ let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
+ if (!cmpUnitAI)
+ return;
+
+ cmpUnitAI.UpdateRangeQueries();
+};
+
Heal.prototype.GetTimers = function()
{
return {
Index: binaries/data/mods/public/simulation/components/Health.js
===================================================================
--- binaries/data/mods/public/simulation/components/Health.js
+++ binaries/data/mods/public/simulation/components/Health.js
@@ -64,6 +64,12 @@
this.UpdateActor();
};
+Health.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+ this.RecalculateValues();
+};
+
/**
* Returns the current hitpoint value.
* This is 0 if (and only if) the unit is dead.
Index: binaries/data/mods/public/simulation/components/Identity.js
===================================================================
--- binaries/data/mods/public/simulation/components/Identity.js
+++ binaries/data/mods/public/simulation/components/Identity.js
@@ -99,6 +99,25 @@
this.controllable = this.template.Controllable ? this.template.Controllable == "true" : true;
};
+Identity.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+
+ const oldClasses = this.classesList;
+ this.classesList = GetIdentityClasses(this.template);
+ this.visibleClassesList = GetVisibleIdentityClasses(this.template);
+
+ if (!this.template.Phenotype)
+ delete this.phenotype;
+ else if (this.GetPossiblePhenotypes().indexOf(this.phenotype) === -1)
+ this.phenotype = pickRandom(this.GetPossiblePhenotypes());
+
+ this.controllable = this.template.Controllable ? this.template.Controllable == "true" : true;
+
+ Engine.PostMessage(this.entity, MT_IdentityClassesChanged, { "entity": this.entity, "from": oldClasses, "to": this.classesList });
+};
+
+
Identity.prototype.GetCiv = function()
{
return this.template.Civ;
Index: binaries/data/mods/public/simulation/components/Market.js
===================================================================
--- binaries/data/mods/public/simulation/components/Market.js
+++ binaries/data/mods/public/simulation/components/Market.js
@@ -21,6 +21,12 @@
this.tradeType = new Set(this.template.TradeType.split(/\s+/));
};
+Market.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+ warn("Market.Update: not implemented");
+};
+
Market.prototype.AddTrader = function(ent)
{
this.traders.add(ent);
Index: binaries/data/mods/public/simulation/components/Mirage.js
===================================================================
--- binaries/data/mods/public/simulation/components/Mirage.js
+++ binaries/data/mods/public/simulation/components/Mirage.js
@@ -16,6 +16,12 @@
this.miragedIids = new Map();
};
+Mirage.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+ warn("Mirage.Update: not implemented");
+};
+
Mirage.prototype.SetParent = function(ent)
{
this.parent = ent;
Index: binaries/data/mods/public/simulation/components/ModifiersManager.js
===================================================================
--- binaries/data/mods/public/simulation/components/ModifiersManager.js
+++ binaries/data/mods/public/simulation/components/ModifiersManager.js
@@ -254,6 +254,49 @@
Engine.PostMessage(msg.entity, MT_ValueModification, { "entities": [msg.entity], "component": component, "valueNames": modifiedComponents[component] });
};
+ModifiersManager.prototype.OnGlobalIdentityClassesChanged = function(msg)
+{
+ const entity = msg.entity;
+ const newclasses = msg.to;
+ const oldclasses = msg.from;
+
+ // Invalidate all caches.
+ for (let propName of this.cachedValues.keys())
+ this.InvalidateCache(propName, entity);
+
+ // Warn entities that our values have changed.
+ // Local modifiers will be added by the relevant components, so no need to check for them here.
+ let modifiedComponents = {};
+ let modifs = this.modifiersStorage.GetAllItems(entity);
+
+ let owner = QueryOwnerEntityID(entity);
+ if (owner)
+ {
+ let playerModifs = this.modifiersStorage.GetAllItems(QueryOwnerEntityID(entity));
+ for (let propertyName in playerModifs)
+ modifs[propertyName] = playerModifs[propertyName];
+ }
+
+ for (let propertyName in modifs)
+ {
+ // We only need to find one one tech per component for a match.
+ let component = propertyName.split("/")[0];
+ // Only inform if the modifier actually applies to the entity as an optimisation.
+ // TODO: would it be better to call FetchModifiedProperty here and compare values?
+ modifs[propertyName].forEach(item => item.value.forEach(modif => {
+ if (!DoesModificationApply(modif, newclasses) && !DoesModificationApply(modif, oldclasses))
+ return;
+ if (!modifiedComponents[component])
+ modifiedComponents[component] = [];
+ modifiedComponents[component].push(propertyName);
+ }));
+ }
+
+ for (let component in modifiedComponents)
+ Engine.PostMessage(entity, MT_ValueModification, { "entities": [entity], "component": component, "valueNames": modifiedComponents[component] });
+};
+
+
/**
* The following functions simply proxy MultiKeyMap's interface.
*/
Index: binaries/data/mods/public/simulation/components/Pack.js
===================================================================
--- binaries/data/mods/public/simulation/components/Pack.js
+++ binaries/data/mods/public/simulation/components/Pack.js
@@ -24,6 +24,12 @@
this.timer = undefined;
};
+Pack.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+ warn("Pack.Update: not implemented");
+};
+
Pack.prototype.OnDestroy = function()
{
this.CancelTimer();
Index: binaries/data/mods/public/simulation/components/Player.js
===================================================================
--- binaries/data/mods/public/simulation/components/Player.js
+++ binaries/data/mods/public/simulation/components/Player.js
@@ -111,6 +111,11 @@
});
};
+Player.prototype.Update = function(newTemplate)
+{
+ warn("Player.Update: not implemented");
+};
+
Player.prototype.SetPlayerID = function(id)
{
this.playerID = id;
Index: binaries/data/mods/public/simulation/components/Population.js
===================================================================
--- binaries/data/mods/public/simulation/components/Population.js
+++ binaries/data/mods/public/simulation/components/Population.js
@@ -14,6 +14,12 @@
this.bonus = +this.template.Bonus;
};
+Population.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+ this.RecalculateValues();
+};
+
/**
* @return {number} - The population space provided by this entity.
*/
Index: binaries/data/mods/public/simulation/components/Promotion.js
===================================================================
--- binaries/data/mods/public/simulation/components/Promotion.js
+++ binaries/data/mods/public/simulation/components/Promotion.js
@@ -19,6 +19,13 @@
this.ComputeTrickleRate();
};
+Promotion.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+ this.ComputeTrickleRate();
+ this.IncreaseXp(0);
+};
+
Promotion.prototype.GetRequiredXp = function()
{
return ApplyValueModificationsToEntity("Promotion/RequiredXp", +this.template.RequiredXp, this.entity);
Index: binaries/data/mods/public/simulation/components/RangeOverlayManager.js
===================================================================
--- binaries/data/mods/public/simulation/components/RangeOverlayManager.js
+++ binaries/data/mods/public/simulation/components/RangeOverlayManager.js
@@ -14,6 +14,13 @@
this.rangeVisualizations = new Map();
};
+RangeOverlayManager.prototype.Update = function()
+{
+ this.UpdateRangeOverlays("Heal")
+ this.UpdateRangeOverlays("Attack");
+ this.RegenerateRangeOverlays(false);
+};
+
// The GUI enables visualizations
RangeOverlayManager.prototype.Serialize = null;
Index: binaries/data/mods/public/simulation/components/Repairable.js
===================================================================
--- binaries/data/mods/public/simulation/components/Repairable.js
+++ binaries/data/mods/public/simulation/components/Repairable.js
@@ -18,6 +18,12 @@
this.repairTimeRatio = +this.template.RepairTimeRatio;
};
+Repairable.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+ this.repairTimeRatio = +this.template.RepairTimeRatio;
+};
+
/**
* Returns the current build progress in a [0,1] range.
*/
Index: binaries/data/mods/public/simulation/components/Researcher.js
===================================================================
--- binaries/data/mods/public/simulation/components/Researcher.js
+++ binaries/data/mods/public/simulation/components/Researcher.js
@@ -147,6 +147,12 @@
this.queue = new Map();
};
+Researcher.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+ // TODO: what to do if the queue now contains invalid items ?
+};
+
Researcher.prototype.Serialize = function()
{
const queue = [];
Index: binaries/data/mods/public/simulation/components/Resistance.js
===================================================================
--- binaries/data/mods/public/simulation/components/Resistance.js
+++ binaries/data/mods/public/simulation/components/Resistance.js
@@ -76,6 +76,11 @@
this.attackers = new Set();
};
+Resistance.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+};
+
Resistance.prototype.IsInvulnerable = function()
{
return this.invulnerable;
Index: binaries/data/mods/public/simulation/components/ResourceDropsite.js
===================================================================
--- binaries/data/mods/public/simulation/components/ResourceDropsite.js
+++ binaries/data/mods/public/simulation/components/ResourceDropsite.js
@@ -18,6 +18,16 @@
this.shared = this.sharable;
};
+ResourceDropsite.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+
+ let wasSharable = this.sharable;
+ this.sharable = this.template.Sharable == "true";
+ if (this.sharable && !wasSharable)
+ this.shared = this.sharable;
+};
+
/**
* Returns the list of resource types accepted by this dropsite,
* as defined by it being referred to in the template and the resource being enabled.
Index: binaries/data/mods/public/simulation/components/ResourceGatherer.js
===================================================================
--- binaries/data/mods/public/simulation/components/ResourceGatherer.js
+++ binaries/data/mods/public/simulation/components/ResourceGatherer.js
@@ -49,6 +49,20 @@
this.lastCarriedType = undefined; // { generic, specific }
};
+ResourceGatherer.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+ this.RecalculateGatherRates();
+ this.RecalculateCapacities();
+
+ for (let type in this.carrying) {
+ if (!(type in this.capacities))
+ delete this.carrying[type];
+ if (this.carrying[type] > this.capacities[type])
+ this.carrying[type] = this.capacities[type];
+ }
+};
+
/**
* Returns data about what resources the unit is currently carrying,
* in the form [ {"type":"wood", "amount":7, "max":10} ]
Index: binaries/data/mods/public/simulation/components/ResourceSupply.js
===================================================================
--- binaries/data/mods/public/simulation/components/ResourceSupply.js
+++ binaries/data/mods/public/simulation/components/ResourceSupply.js
@@ -115,6 +115,31 @@
}
};
+ResourceSupply.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+
+ // TODO: figure out what we want to do with the amount exactly.
+ let oldAmount = this.amount;
+ let oldType = this.cachedType;
+
+ this.amount = +(this.template.Initial || this.template.Max);
+
+ let [type, subtype] = this.template.Type.split('.');
+ this.cachedType = { "generic": type, "specific": subtype };
+
+ if (oldType.generic != type || oldType.specific != subtype) {
+ // TODO: send message.
+ } else if (oldAmount != this.amount) {
+ Engine.PostMessage(this.entity, MT_ResourceSupplyChanged, {
+ "from": oldAmount,
+ "to": this.amount
+ });
+ }
+
+ this.RecalculateValues();
+};
+
ResourceSupply.prototype.IsInfinite = function()
{
return !isFinite(+this.template.Max);
Index: binaries/data/mods/public/simulation/components/ResourceTrickle.js
===================================================================
--- binaries/data/mods/public/simulation/components/ResourceTrickle.js
+++ binaries/data/mods/public/simulation/components/ResourceTrickle.js
@@ -15,6 +15,11 @@
this.CheckTimer();
};
+ResourceTrickle.prototype.Update = function()
+{
+ warn("ResourceTrickle.Update not implemented");
+};
+
ResourceTrickle.prototype.GetInterval = function()
{
return this.trickleInterval;
Index: binaries/data/mods/public/simulation/components/SkirmishReplacer.js
===================================================================
--- binaries/data/mods/public/simulation/components/SkirmishReplacer.js
+++ binaries/data/mods/public/simulation/components/SkirmishReplacer.js
@@ -13,6 +13,11 @@
{
};
+SkirmishReplacer.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+};
+
SkirmishReplacer.prototype.Serialize = null; // We have no dynamic state to save
function getReplacementEntities(civ)
Index: binaries/data/mods/public/simulation/components/StatisticsTracker.js
===================================================================
--- binaries/data/mods/public/simulation/components/StatisticsTracker.js
+++ binaries/data/mods/public/simulation/components/StatisticsTracker.js
@@ -92,6 +92,10 @@
this.entity, IID_StatisticsTracker, "UpdateSequences", 0, this.UpdateSequenceInterval);
};
+StatisticsTracker.prototype.Update = function() {
+ warn("StatisticsTracker.Update is not implemented")
+}
+
StatisticsTracker.prototype.OnGlobalInitGame = function()
{
this.sequences = clone(this.GetStatistics());
Index: binaries/data/mods/public/simulation/components/StatusBars.js
===================================================================
--- binaries/data/mods/public/simulation/components/StatusBars.js
+++ binaries/data/mods/public/simulation/components/StatusBars.js
@@ -43,6 +43,13 @@
this.auraSources = new Map();
};
+StatusBars.prototype.Update = function(newTemplate) {
+ this.template = newTemplate;
+
+ if (this.enabled)
+ this.RegenerateSprites();
+};
+
/**
* Don't serialise this.enabled since it's modified by the GUI.
*/
Index: binaries/data/mods/public/simulation/components/TechnologyManager.js
===================================================================
--- binaries/data/mods/public/simulation/components/TechnologyManager.js
+++ binaries/data/mods/public/simulation/components/TechnologyManager.js
@@ -3,6 +3,10 @@
TechnologyManager.prototype.Schema =
"";
+TechnologyManager.prototype.Update = function() {
+ warn("TechnologyManager.Update: not implemented");
+}
+
/**
* This object represents a technology under research.
* @param {string} templateName - The name of the template to research.
@@ -423,6 +427,32 @@
}
};
+TechnologyManager.prototype.OnGlobalIdentityClassesChanged = function(msg)
+{
+ warn("TODO: fix this");
+ var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+ var template = cmpTemplateManager.GetCurrentTemplateName(msg.entity);
+
+ var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
+ if (!cmpIdentity)
+ return;
+
+ var classes = cmpIdentity.GetClassesList();
+ // don't use foundations for the class counts but check if techs apply (e.g. health increase)
+ if (!Engine.QueryInterface(msg.entity, IID_Foundation))
+ {
+ for (let cls of classes)
+ {
+ this.classCounts[cls] = this.classCounts[cls] || 0;
+ this.classCounts[cls] += 1;
+
+ this.typeCountsByClass[cls] = this.typeCountsByClass[cls] || {};
+ this.typeCountsByClass[cls][template] = this.typeCountsByClass[cls][template] || 0;
+ this.typeCountsByClass[cls][template] += 1;
+ }
+ }
+};
+
/**
* This does neither apply effects nor verify requirements.
* @param {string} tech - The name of the technology to mark as researched.
Index: binaries/data/mods/public/simulation/components/TerritoryDecay.js
===================================================================
--- binaries/data/mods/public/simulation/components/TerritoryDecay.js
+++ binaries/data/mods/public/simulation/components/TerritoryDecay.js
@@ -23,6 +23,10 @@
this.territoryOwnership = !isFinite(+this.template.DecayRate);
};
+TerritoryDecay.prototype.Update = function() {
+ warn("TerritoryDecay.Update is not implemented")
+}
+
TerritoryDecay.prototype.IsConnected = function()
{
this.connectedNeighbours.fill(0);
Index: binaries/data/mods/public/simulation/components/Trader.js
===================================================================
--- binaries/data/mods/public/simulation/components/Trader.js
+++ binaries/data/mods/public/simulation/components/Trader.js
@@ -28,6 +28,10 @@
};
};
+Trader.prototype.Update = function() {
+ warn("Trader.Update is not implemented")
+}
+
Trader.prototype.CalculateGain = function(currentMarket, nextMarket)
{
let cmpMarket = QueryMiragedInterface(currentMarket, IID_Market);
Index: binaries/data/mods/public/simulation/components/Trainer.js
===================================================================
--- binaries/data/mods/public/simulation/components/Trainer.js
+++ binaries/data/mods/public/simulation/components/Trainer.js
@@ -22,6 +22,20 @@
"" +
"";
+Trainer.prototype.Update = function(newTemplate) {
+ this.template = newTemplate;
+
+ // This also updates the queued production if necessary.
+ this.CalculateEntitiesMap();
+
+ // Inform the GUI that it'll need to recompute the selection panel.
+ // TODO: it would be better to only send the message if something actually changing
+ // for the current training queue.
+ const cmpPlayer = QueryOwnerInterface(this.entity);
+ if (cmpPlayer)
+ Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).SetSelectionDirty(cmpPlayer.GetPlayerID());
+}
+
/**
* This object represents a batch of entities being trained.
* @param {string} templateName - The name of the template we ought to train.
Index: binaries/data/mods/public/simulation/components/Treasure.js
===================================================================
--- binaries/data/mods/public/simulation/components/Treasure.js
+++ binaries/data/mods/public/simulation/components/Treasure.js
@@ -21,6 +21,10 @@
{
};
+Treasure.prototype.Update = function() {
+ warn("Treasure.Update is not implemented")
+};
+
Treasure.prototype.ComputeReward = function()
{
for (let resource in this.template.Resources)
Index: binaries/data/mods/public/simulation/components/TreasureCollector.js
===================================================================
--- binaries/data/mods/public/simulation/components/TreasureCollector.js
+++ binaries/data/mods/public/simulation/components/TreasureCollector.js
@@ -13,6 +13,11 @@
{
};
+TreasureCollector.prototype.Update = function(newTemplate)
+{
+ this.template = newTemplate;
+};
+
/**
* @return {Object} - Min/Max range at which this entity can claim a treasure.
*/
Index: binaries/data/mods/public/simulation/components/TriggerPoint.js
===================================================================
--- binaries/data/mods/public/simulation/components/TriggerPoint.js
+++ binaries/data/mods/public/simulation/components/TriggerPoint.js
@@ -18,6 +18,10 @@
this.triggers = {};
};
+TriggerPoint.prototype.Update = function() {
+ error("TriggerPoint cannot be upgraded");
+};
+
TriggerPoint.prototype.OnDestroy = function()
{
if (this.template && this.template.EntityReference)
Index: binaries/data/mods/public/simulation/components/TurretHolder.js
===================================================================
--- binaries/data/mods/public/simulation/components/TurretHolder.js
+++ binaries/data/mods/public/simulation/components/TurretHolder.js
@@ -25,6 +25,10 @@
});
}
+ Update() {
+ warn("TurretHolder.js: Update() is not implemented");
+ }
+
/**
* Add a subunit as specified in the template.
* This function creates an entity and places it on the turret point.
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js
+++ binaries/data/mods/public/simulation/components/UnitAI.js
@@ -3469,6 +3469,19 @@
this.SetStance(this.template.DefaultStance);
};
+UnitAI.prototype.Update = function(newTemplate) {
+ this.template = newTemplate;
+ this.cheeringTime = +(this.template.CheeringTime || 0);
+
+ // The current order might not be so achievable anymore, so re-run it.
+ // TODO: would it be better to send an event to the FSM? Probably
+ // TODO: this should be done after all components are updated.
+ if (!this.orderQueue.length)
+ return;
+ let order = this.orderQueue.splice(0, 1)[0];
+ this.PushOrderFront(order.type, order.data);
+};
+
/**
* @param {cmpTurretable} cmpTurretable - Optionally the component to save a query here.
* @return {boolean} - Whether we are occupying a turret point.
Index: binaries/data/mods/public/simulation/components/UnitMotionFlying.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitMotionFlying.js
+++ binaries/data/mods/public/simulation/components/UnitMotionFlying.js
@@ -63,6 +63,10 @@
this.passabilityClass = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder).GetPassabilityClass(this.template.PassabilityClass);
};
+UnitMotionFlying.prototype.Update = function(newTemplate) {
+ warn("UnitMotionFlying.Update() is not implemented");
+};
+
UnitMotionFlying.prototype.OnUpdate = function(msg)
{
let turnLength = msg.turnLength;
Index: binaries/data/mods/public/simulation/components/Upgrade.js
===================================================================
--- binaries/data/mods/public/simulation/components/Upgrade.js
+++ binaries/data/mods/public/simulation/components/Upgrade.js
@@ -55,6 +55,10 @@
this.expendedResources = {};
};
+Upgrade.prototype.Update = function(newTemplate) {
+ warn("Upgrade.Update() is not implemented");
+};
+
// This will also deal with the "OnDestroy" case.
Upgrade.prototype.OnOwnershipChanged = function(msg)
{
Index: binaries/data/mods/public/simulation/components/Upkeep.js
===================================================================
--- binaries/data/mods/public/simulation/components/Upkeep.js
+++ binaries/data/mods/public/simulation/components/Upkeep.js
@@ -15,6 +15,11 @@
this.CheckTimer();
};
+Upkeep.prototype.Update = function(newTemplate) {
+ this.template = newTemplate;
+ this.CheckTimer();
+};
+
/**
* @return {number} - The interval between resource subtractions, in ms.
*/
Index: binaries/data/mods/public/simulation/components/Visibility.js
===================================================================
--- binaries/data/mods/public/simulation/components/Visibility.js
+++ binaries/data/mods/public/simulation/components/Visibility.js
@@ -18,6 +18,7 @@
"" +
"";
+
Visibility.prototype.Init = function()
{
this.retainInFog = this.template.RetainInFog == "true";
@@ -31,6 +32,18 @@
this.SetActivated(true);
};
+Visibility.prototype.Update = function(newTemplate) {
+ this.template = newTemplate;
+
+ this.retainInFog = this.template.RetainInFog == "true";
+ this.alwaysVisible = this.template.AlwaysVisible == "true";
+ this.corpse = this.template.Corpse == "true";
+ this.preview = this.template.Preview == "true";
+
+ // Notify the range manager the visibility of this entity must be updated.
+ Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).RequestVisibilityUpdate(this.entity);
+};
+
/**
* Sets the range manager scriptedVisibility flag for this entity.
*/
Index: binaries/data/mods/public/simulation/components/VisionSharing.js
===================================================================
--- binaries/data/mods/public/simulation/components/VisionSharing.js
+++ binaries/data/mods/public/simulation/components/VisionSharing.js
@@ -23,6 +23,12 @@
this.spies = undefined;
};
+VisionSharing.prototype.Update = function(newTemplate) {
+ this.template = newTemplate;
+ if (this.activated)
+ this.CheckVisionSharings();
+};
+
/**
* As entities have not necessarily the VisionSharing component, it has to be activated
* before use so that the rangeManager can register it
Index: binaries/data/mods/public/simulation/components/interfaces/Identity.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/interfaces/Identity.js
@@ -0,0 +1,5 @@
+/**
+ * Message of the form { "entity": entity, "from": oldClasses, "to": newClasses }
+ * sent from Identity component whenever identity classes changes.
+ */
+Engine.RegisterMessageType("IdentityClassesChanged");
Index: binaries/data/mods/public/simulation/helpers/Transform.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Transform.js
+++ binaries/data/mods/public/simulation/helpers/Transform.js
@@ -4,6 +4,13 @@
function ChangeEntityTemplate(oldEnt, newTemplate)
{
// Done un/packing, copy our parameters to the final entity
+ Engine.UpdateEntityTemplate(newTemplate, oldEnt);
+ const cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+ if (cmpGuiInterface)
+ cmpGuiInterface.RefreshEntity(oldEnt);
+
+ return oldEnt;
+
var newEnt = Engine.AddEntity(newTemplate);
if (newEnt == INVALID_ENTITY)
{
Index: binaries/data/mods/public/simulation/templates/units/maur/cavalry_javelineer_a.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/units/maur/cavalry_javelineer_a.xml
+++ binaries/data/mods/public/simulation/templates/units/maur/cavalry_javelineer_a.xml
@@ -4,7 +4,7 @@
Advanced
- units/maur/cavalry_javelineer_e
+ units/maur/infantry_archer_e
units/mauryas/cavalry_javelinist_a_m.xml
Index: binaries/data/mods/public/simulation/templates/units/maur/infantry_archer_b.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/units/maur/infantry_archer_b.xml
+++ binaries/data/mods/public/simulation/templates/units/maur/infantry_archer_b.xml
@@ -14,7 +14,7 @@
units/maur/infantry_archer.png
- units/maur/infantry_archer_a
+ units/maur/cavalry_javelineer_a
units/mauryas/infantry_archer_b.xml
Index: source/simulation2/components/CCmpVisualActor.cpp
===================================================================
--- source/simulation2/components/CCmpVisualActor.cpp
+++ source/simulation2/components/CCmpVisualActor.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2022 Wildfire Games.
+/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -229,6 +229,26 @@
}
}
+ void Update(const CParamNode& paramNode) override
+ {
+ m_ConstructionPreview = paramNode.GetChild("ConstructionPreview").IsOk();
+ m_IsFoundationActor = paramNode.GetChild("Foundation").IsOk() && paramNode.GetChild("FoundationActor").IsOk();
+
+ m_BaseActorName = paramNode.GetChild(m_IsFoundationActor ? "FoundationActor" : "Actor").ToWString();
+ // Don't parse, we recompute down below
+
+ m_VisibleInAtlasOnly = paramNode.GetChild("VisibleInAtlasOnly").ToBool();
+ m_IsActorOnly = paramNode.GetChild("ActorOnly").IsOk();
+
+ m_SilhouetteDisplay = paramNode.GetChild("SilhouetteDisplay").ToBool();
+ m_SilhouetteOccluder = paramNode.GetChild("SilhouetteOccluder").ToBool();
+ m_DisableShadows = paramNode.GetChild("DisableShadows").ToBool();
+
+ InitSelectionShapeDescriptor(paramNode);
+
+ RecomputeActorName();
+ }
+
template
void SerializeCommon(S& serialize)
{
@@ -656,6 +676,7 @@
void CCmpVisualActor::InitSelectionShapeDescriptor(const CParamNode& paramNode)
{
// by default, we don't need a custom selection shape and we can just keep the default behaviour
+ // We don't need to delete the descriptor here, because the CModelAbstract holds it.
m_ShapeDescriptor = nullptr;
const CParamNode& shapeNode = paramNode.GetChild("SelectionShape");
Index: source/simulation2/scripting/ScriptComponent.h
===================================================================
--- source/simulation2/scripting/ScriptComponent.h
+++ source/simulation2/scripting/ScriptComponent.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2022 Wildfire Games.
+/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -41,6 +41,7 @@
void Init(const CParamNode& paramNode, entity_id_t ent);
void Deinit();
+ void Update(const CParamNode& paramNode);
void HandleMessage(const CMessage& msg, bool global);
void Serialize(ISerializer& serialize);
@@ -110,6 +111,10 @@
{ \
m_Script.Deinit(); \
} \
+ void Update(const CParamNode& paramNode) override \
+ { \
+ m_Script.Update(paramNode); \
+ } \
void HandleMessage(const CMessage& msg, bool global) override \
{ \
m_Script.HandleMessage(msg, global); \
Index: source/simulation2/scripting/ScriptComponent.cpp
===================================================================
--- source/simulation2/scripting/ScriptComponent.cpp
+++ source/simulation2/scripting/ScriptComponent.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -36,7 +36,7 @@
{
ScriptRequest rq(m_ScriptInterface);
Script::SetProperty(rq, m_Instance, "entity", (int)ent, true, false);
- Script::SetProperty(rq, m_Instance, "template", paramNode, true, false);
+ Script::SetProperty(rq, m_Instance, "template", paramNode, false, false);
ScriptFunction::CallVoid(rq, m_Instance, "Init");
}
@@ -46,6 +46,12 @@
ScriptFunction::CallVoid(rq, m_Instance, "Deinit");
}
+void CComponentTypeScript::Update(const CParamNode& paramNode) {
+ ScriptRequest rq(m_ScriptInterface);
+ if (!ScriptFunction::CallVoid(rq, m_Instance, "Update", paramNode))
+ Script::SetProperty(rq, m_Instance, "template", paramNode, false, false);
+}
+
void CComponentTypeScript::HandleMessage(const CMessage& msg, bool global)
{
ScriptRequest rq(m_ScriptInterface);
Index: source/simulation2/system/Component.h
===================================================================
--- source/simulation2/system/Component.h
+++ source/simulation2/system/Component.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2022 Wildfire Games.
+/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -55,6 +55,9 @@
void Deinit() override \
{ \
} \
+ void Update(const CParamNode& paramNode) override \
+ { \
+ } \
void Serialize(ISerializer& UNUSED(serialize)) override \
{ \
} \
Index: source/simulation2/system/ComponentManager.h
===================================================================
--- source/simulation2/system/ComponentManager.h
+++ source/simulation2/system/ComponentManager.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2022 Wildfire Games.
+/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -213,6 +213,8 @@
*/
entity_id_t AddEntity(const std::wstring& templateName, entity_id_t ent);
+ bool UpdateEntityTemplate(const std::wstring& templateName, entity_id_t ent);
+
/**
* Destroys all the components belonging to the specified entity when FlushDestroyedComponents is called.
* Has no effect if the entity does not exist, or has already been added to the destruction queue.
@@ -286,6 +288,7 @@
void Script_BroadcastMessage(int mtid, JS::HandleValue data);
int Script_AddEntity(const std::wstring& templateName);
int Script_AddLocalEntity(const std::wstring& templateName);
+ bool Script_UpdateEntityTemplate(const std::wstring& templateName, entity_id_t ent);
const CParamNode& Script_GetTemplate(const std::string& templateName);
CMessage* ConstructMessage(int mtid, JS::HandleValue data);
@@ -309,6 +312,7 @@
// TODO: some of these should be vectors
std::map m_ComponentTypesById;
std::vector m_ScriptedSystemComponents;
+ // TODO: the two containers below must guarantee stability when inserting for now, @see ConstructComponent
std::vector > m_ComponentsByInterface; // indexed by InterfaceId
std::map > m_ComponentsByTypeId;
std::map > m_LocalMessageSubscriptions;
Index: source/simulation2/system/ComponentManager.cpp
===================================================================
--- source/simulation2/system/ComponentManager.cpp
+++ source/simulation2/system/ComponentManager.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2022 Wildfire Games.
+/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -85,6 +85,7 @@
ScriptFunction::Register<&CComponentManager::Script_BroadcastMessage, Getter>(rq, "BroadcastMessage");
ScriptFunction::Register<&CComponentManager::Script_AddEntity, Getter>(rq, "AddEntity");
ScriptFunction::Register<&CComponentManager::Script_AddLocalEntity, Getter>(rq, "AddLocalEntity");
+ ScriptFunction::Register<&CComponentManager::Script_UpdateEntityTemplate, Getter>(rq, "UpdateEntityTemplate");
ScriptFunction::Register<&CComponentManager::QueryInterface, Getter>(rq, "QueryInterface");
ScriptFunction::Register<&CComponentManager::DestroyComponentsSoon, Getter>(rq, "DestroyEntity");
ScriptFunction::Register<&CComponentManager::FlushDestroyedComponents, Getter>(rq, "FlushDestroyedEntities");
@@ -475,6 +476,13 @@
return AddEntity(templateName, AllocateNewLocalEntity());
}
+bool CComponentManager::Script_UpdateEntityTemplate(const std::wstring& templateName, entity_id_t ent)
+{
+ // TODO: should validate the string to make sure it doesn't contain scary characters
+ // that will let it access non-component-template files
+ return UpdateEntityTemplate(templateName, ent);
+}
+
void CComponentManager::ResetState()
{
// Delete all dynamic message subscriptions
@@ -488,7 +496,8 @@
std::map::iterator eit = iit->second.begin();
for (; eit != iit->second.end(); ++eit)
{
- eit->second->Deinit();
+ if (eit->second)
+ eit->second->Deinit();
m_ComponentTypesById[iit->first].dealloc(eit->second);
}
}
@@ -729,7 +738,7 @@
ENSURE((size_t)ct.iid < m_ComponentsByInterface.size());
std::unordered_map& emap1 = m_ComponentsByInterface[ct.iid];
- if (emap1.find(ent.GetId()) != emap1.end())
+ if (emap1.find(ent.GetId()) != emap1.end() && emap1.find(ent.GetId())->second)
{
LOGERROR("Multiple components for interface %d", ct.iid);
return NULL;
@@ -758,13 +767,11 @@
component->SetSimContext(m_SimContext);
// Store a reference to the new component
- emap1.insert(std::make_pair(ent.GetId(), component));
- emap2.insert(std::make_pair(ent.GetId(), component));
- // TODO: We need to more careful about this - if an entity is constructed by a component
- // while we're iterating over all components, this will invalidate the iterators and everything
- // will break.
- // We probably need some kind of delayed addition, so they get pushed onto a queue and then
- // inserted into the world later on. (Be careful about immediation deletion in that case, too.)
+ // NB: this requires that insertion does not invalidate iterators, as we might create components
+ // while iterating over all components.
+ // TODO: figure out if we want some kind of delayed addition like we have for destruction.
+ emap1.insert_or_assign(ent.GetId(), component);
+ emap2.insert_or_assign(ent.GetId(), component);
SEntityComponentCache* cache = ent.GetComponentCache();
ENSURE(cache != NULL && ct.iid < (int)cache->numInterfaces && cache->interfaces[ct.iid] == NULL);
@@ -871,6 +878,98 @@
return ent;
}
+bool CComponentManager::UpdateEntityTemplate(const std::wstring& templateName, entity_id_t ent)
+{
+ LOGWARNING("Update template : %s, %i", utf8_from_wstring(templateName), ent);
+ ICmpTemplateManager *cmpTemplateManager = static_cast (QueryInterface(SYSTEM_ENTITY, IID_TemplateManager));
+ if (!cmpTemplateManager)
+ {
+ debug_warn(L"No ICmpTemplateManager loaded");
+ return false;
+ }
+
+ // Don't use loadTemplate, that updates the cache
+ const CParamNode* newTemplate = cmpTemplateManager->GetTemplate(utf8_from_wstring(templateName));
+ if (!newTemplate)
+ return false; // LoadTemplate will have reported the error
+
+ CEntityHandle handle = LookupEntityHandle(ent, false);
+ if (handle.GetComponentCache() == nullptr)
+ {
+ LOGERROR("Entity %i does not exist", ent);
+ return false;
+ }
+
+ const CParamNode* oldTemplate = cmpTemplateManager->GetTemplate(cmpTemplateManager->GetCurrentTemplateName(ent));
+
+ const CParamNode::ChildrenMap& tmplChilds = newTemplate->GetChildren();
+
+ // Now pass through the new template and update / create new components
+ for (CParamNode::ChildrenMap::const_iterator it = tmplChilds.begin(); it != tmplChilds.end(); ++it)
+ {
+ // Ignore attributes on the root element
+ if (it->first.length() && it->first[0] == '@')
+ continue;
+
+ CComponentManager::ComponentTypeId cid = LookupCID(it->first);
+ if (cid == CID__Invalid)
+ {
+ LOGERROR("Unrecognized component type name '%s' in entity template '%s'", it->first, utf8_from_wstring(templateName));
+ return false;
+ }
+ IComponent* component = handle.GetComponentCache()->interfaces[m_ComponentTypesById[cid].iid];
+ if (!component)
+ {
+ if (!AddComponent(handle, cid, it->second))
+ {
+ LOGERROR("Failed to construct component type name '%s' in entity template '%s'", it->first, utf8_from_wstring(templateName));
+ return false;
+ }
+ // TODO: make AddComponent return the component bruh.
+ component = handle.GetComponentCache()->interfaces[m_ComponentTypesById[cid].iid];
+ }
+ component->Update(it->second);
+ }
+
+ // Finally pass through existing templates and delete all outstanding components.
+ // This is the really tricky/hacky bit.
+ for (CParamNode::ChildrenMap::const_iterator it = oldTemplate->GetChildren().begin(); it != oldTemplate->GetChildren().end(); ++it) {
+ // Ignore attributes on the root element
+ if (it->first.length() && it->first[0] == '@')
+ continue;
+ CParamNode::ChildrenMap::const_iterator itOld = tmplChilds.find(it->first);
+ if (itOld != tmplChilds.end())
+ continue;
+
+ CComponentManager::ComponentTypeId cid = LookupCID(it->first);
+ std::map >::iterator iit = m_ComponentsByTypeId.find(cid);
+ if (iit == m_ComponentsByTypeId.end())
+ {
+ LOGWARNING("Did not find expected component when updating template - %s", it->first);
+ continue;
+ }
+ std::map::iterator eit = iit->second.find(ent);
+ if (eit == iit->second.end())
+ {
+ LOGWARNING("Did not find expected component when updating template - %s", it->first);
+ continue;
+ }
+ // TODO -> should we de-init sooner / later ?
+ eit->second->Deinit();
+ RemoveComponentDynamicSubscriptions(eit->second);
+
+ // TODO: fix memory leak, and fix container leaks. Otherwise things crash down the line.
+ eit->second = nullptr;
+ handle.GetComponentCache()->interfaces[m_ComponentTypesById[iit->first].iid] = nullptr;
+ m_ComponentsByInterface[m_ComponentTypesById[iit->first].iid][ent] = nullptr;
+ }
+
+ // Update the cache
+ cmpTemplateManager->LoadTemplate(ent, utf8_from_wstring(templateName));
+
+ return true;
+}
+
bool CComponentManager::EntityExists(entity_id_t ent) const
{
return m_ComponentCaches.find(ent) != m_ComponentCaches.end();
@@ -917,8 +1016,10 @@
std::map::iterator eit = iit->second.find(ent);
if (eit != iit->second.end())
{
- eit->second->Deinit();
- RemoveComponentDynamicSubscriptions(eit->second);
+ if (eit->second) {
+ eit->second->Deinit();
+ RemoveComponentDynamicSubscriptions(eit->second);
+ }
m_ComponentTypesById[iit->first].dealloc(eit->second);
iit->second.erase(ent);
handle.GetComponentCache()->interfaces[m_ComponentTypesById[iit->first].iid] = NULL;
@@ -1008,7 +1109,7 @@
// Send the message to all of them
std::map::const_iterator eit = emap->second.find(ent);
- if (eit != emap->second.end())
+ if (eit != emap->second.end() && eit->second)
eit->second->HandleMessage(msg, false);
}
}
@@ -1034,7 +1135,8 @@
// Send the message to all of them
std::map::const_iterator eit = emap->second.begin();
for (; eit != emap->second.end(); ++eit)
- eit->second->HandleMessage(msg, false);
+ if (eit->second)
+ eit->second->HandleMessage(msg, false);
}
}
@@ -1073,7 +1175,8 @@
// Send the message to all of them
std::map::const_iterator eit = emap->second.begin();
for (; eit != emap->second.end(); ++eit)
- eit->second->HandleMessage(msg, true);
+ if (eit->second)
+ eit->second->HandleMessage(msg, true);
}
}
Index: source/simulation2/system/IComponent.h
===================================================================
--- source/simulation2/system/IComponent.h
+++ source/simulation2/system/IComponent.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -46,6 +46,8 @@
virtual void Init(const CParamNode& paramNode) = 0;
virtual void Deinit() = 0;
+ virtual void Update(const CParamNode& paramNode);
+
virtual void HandleMessage(const CMessage& msg, bool global);
CEntityHandle GetEntityHandle() const { return m_EntityHandle; }
Index: source/simulation2/system/IComponent.cpp
===================================================================
--- source/simulation2/system/IComponent.cpp
+++ source/simulation2/system/IComponent.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -56,3 +56,7 @@
{
return JS::NullValue();
}
+
+void IComponent::Update(const CParamNode& paramNode) {
+ // TODO: remove this, this is just a placeholder for compilation.
+}