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)
@@ -298,4 +302,55 @@
this.UpdateLimitRemoval();
};
+EntityLimits.prototype.OnGlobalIdentityClassesChanged = function(msg)
+{
+ const playerID = (Engine.QueryInterface(this.entity, IID_Player)).GetPlayerID();
+ const cmpOwnership = Engine.QueryInterface(msg.entity, IID_Ownership);
+ if (!cmpOwnership || cmpOwnership.GetOwner() != playerID)
+ return;
+
+ // foundations shouldn't change the entity limits until they're completed
+ if (Engine.QueryInterface(msg.entity, IID_Foundation))
+ return;
+
+ const oldClasses = msg.from;
+ const newClasses = msg.to;
+
+ for (const category in this.changers)
+ for (const cls in this.changers[category])
+ {
+ let inOld = oldClasses.indexOf(cls) !== -1;
+ let inNew = newClasses.indexOf(cls) !== -1;
+ // Change limit counts if there is a difference between the old and new classes
+ if (!inOld && inNew)
+ {
+ if (this.limit[category] !== undefined)
+ this.limit[category] += this.changers[category][cls];
+ if (this.removedLimit[category] !== undefined)
+ this.removedLimit[category] += this.changers[category][cls];
+ }
+ else if (inOld && !inNew)
+ {
+ if (this.limit[category] != undefined)
+ this.limit[category] -= this.changers[category][cls];
+ if (this.removedLimit[category] != undefined)
+ this.removedLimit[category] -= this.changers[category][cls];
+ }
+ }
+
+ for (const category in this.removers)
+ if ("RequiredClasses" in this.removers[category])
+ for (const cls of this.removers[category].RequiredClasses)
+ {
+ let inOld = oldClasses.indexOf(cls) >= 0;
+ let inNew = newClasses.indexOf(cls) >= 0;
+ if (!inOld && inNew)
+ this.classCount[cls]++;
+ else if (inOld && !inNew)
+ this.classCount[cls]--;
+ }
+
+ this.UpdateLimitRemoval();
+};
+
Engine.RegisterComponentType(IID_EntityLimits, "EntityLimits", EntityLimits);
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);
@@ -337,18 +343,6 @@
let building = ChangeEntityTemplate(this.entity, this.finalTemplateName);
- const cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
- const cmpBuildingIdentity = Engine.QueryInterface(building, IID_Identity);
- if (cmpIdentity && cmpBuildingIdentity)
- {
- const oldPhenotype = cmpIdentity.GetPhenotype();
- if (cmpBuildingIdentity.GetPhenotype() !== oldPhenotype)
- {
- cmpBuildingIdentity.SetPhenotype(oldPhenotype);
- Engine.QueryInterface(building, IID_Visual)?.RecomputeActorName();
- }
- }
-
if (cmpPlayerStatisticsTracker)
cmpPlayerStatisticsTracker.IncreaseConstructedBuildingsCounter(building);
@@ -357,6 +351,12 @@
Engine.PostMessage(this.entity, MT_ConstructionFinished,
{ "entity": this.entity, "newentity": building });
+ if (this.previewEntity)
+ {
+ Engine.DestroyEntity(this.previewEntity);
+ this.previewEntity = INVALID_ENTITY;
+ }
+
for (let builder of this.GetBuilders())
{
let cmpUnitAIBuilder = Engine.QueryInterface(builder, IID_UnitAI);
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,19 @@
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());
+
+ if (!this.locked) {
+ // TODO: ordering dependency on cmpObstruction here...
+ let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
+ cmpObstruction.SetDisableBlockMovementPathfinding(this.opened, true, 0);
+ }
+};
+
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,29 @@
this.renamedEntities.push(msg);
};
+GuiInterface.prototype.OnGlobalTemplateChanged = function(msg)
+{
+ this.renamedEntities.push({ entity: msg.entity, newentity: msg.entity });
+
+ const cmpOwnership = Engine.QueryInterface(msg.entity, IID_Ownership);
+ if (cmpOwnership && cmpOwnership.GetOwner()) {
+ this.templateModified[cmpOwnership.GetOwner()] = true;
+ this.selectionDirty[cmpOwnership.GetOwner()] = true;
+ }
+};
+
+GuiInterface.prototype.OnGlobalConstructionFinished = function(msg)
+{
+ this.renamedEntities.push({ entity: msg.entity, newentity: msg.entity });
+
+ const cmpOwnership = Engine.QueryInterface(msg.entity, IID_Ownership);
+ if (cmpOwnership && cmpOwnership.GetOwner()) {
+ this.templateModified[cmpOwnership.GetOwner()] = true;
+ this.selectionDirty[cmpOwnership.GetOwner()] = true;
+ }
+};
+
+
/**
* 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,79 @@
}
};
+TechnologyManager.prototype.OnGlobalIdentityClassesChanged = function(msg)
+{
+ const playerID = (Engine.QueryInterface(this.entity, IID_Player)).GetPlayerID();
+ const cmpOwnership = Engine.QueryInterface(msg.entity, IID_Ownership);
+ if (!cmpOwnership || cmpOwnership.GetOwner() != playerID)
+ return;
+
+ var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+ var template = cmpTemplateManager.GetCurrentTemplateName(msg.entity);
+
+ // don't use foundations for the class counts but check if techs apply (e.g. health increase)
+ if (Engine.QueryInterface(msg.entity, IID_Foundation))
+ return;
+
+ let oldClasses = msg.from;
+ let newClasses = msg.to;
+
+ // Add first so we don't delete the old class if it's the same as the new one
+ for (let cls of newClasses)
+ {
+ 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;
+ }
+
+ for (let cls of oldClasses)
+ {
+ this.classCounts[cls] -= 1;
+ if (this.classCounts[cls] <= 0)
+ delete this.classCounts[cls];
+
+ this.typeCountsByClass[cls][template] -= 1;
+ if (this.typeCountsByClass[cls][template] <= 0)
+ delete this.typeCountsByClass[cls][template];
+ }
+};
+
+TechnologyManager.prototype.OnGlobalTemplateChanged = function(msg)
+{
+ const playerID = (Engine.QueryInterface(this.entity, IID_Player)).GetPlayerID();
+ const cmpOwnership = Engine.QueryInterface(msg.entity, IID_Ownership);
+ if (!cmpOwnership || cmpOwnership.GetOwner() != playerID)
+ return;
+
+ // don't use foundations for the class counts but check if techs apply (e.g. health increase)
+ if (Engine.QueryInterface(msg.entity, IID_Foundation))
+ return;
+
+ const cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
+ if (!cmpIdentity)
+ return;
+
+ // We assume that the Identity changes are handled above.
+ const classes = cmpIdentity.GetClassesList();
+
+ // Add first so we don't delete the old class if it's the same as the new one
+ for (let cls of classes)
+ {
+ this.typeCountsByClass[cls] = this.typeCountsByClass[cls] || {};
+ this.typeCountsByClass[cls][msg.to] = this.typeCountsByClass[cls][msg.to] || 0;
+ this.typeCountsByClass[cls][msg.to] += 1;
+ }
+ for (let cls of classes)
+ {
+ this.typeCountsByClass[cls][msg.from] -= 1;
+ if (this.typeCountsByClass[cls][msg.from] <= 0)
+ delete this.typeCountsByClass[cls][msg.from];
+ }
+};
+
/**
* 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,12 @@
this.territoryOwnership = !isFinite(+this.template.DecayRate);
};
+TerritoryDecay.prototype.Update = function(newTemplate) {
+ this.template = newTemplate;
+ this.territoryOwnership = !isFinite(+this.template.DecayRate);
+ this.UpdateDecayState();
+}
+
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,21 @@
this.expendedResources = {};
};
+Upgrade.prototype.Update = function(newTemplate) {
+ this.template = newTemplate;
+
+ const cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+ if (!cmpOwnership) {
+ warn("Upgrade needs an Ownership component to work - updating template to for " + this.entity);
+ return;
+ }
+ this.owner = cmpOwnership.GetOwner();
+
+ if (!this.completed)
+ this.CancelUpgrade(this.owner);
+ this.DetermineUpgrades();
+};
+
// 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/components/interfaces/Messages.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/Messages.js
+++ binaries/data/mods/public/simulation/components/interfaces/Messages.js
@@ -11,6 +11,12 @@
*/
Engine.RegisterMessageType("EntityRenamed");
+/**
+ * Message of the form { "entity": number, "from": old, "to": new }
+ * sent when an entity changes template.
+ */
+Engine.RegisterMessageType("TemplateChanged");
+
/**
* Message of the form {}
* sent from InitGame for component map-dependent initialization.
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
@@ -3,7 +3,14 @@
// returns the ID of the new entity or INVALID_ENTITY.
function ChangeEntityTemplate(oldEnt, newTemplate)
{
- // Done un/packing, copy our parameters to the final entity
+ const cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+ const oldTemplate = cmpTemplateManager.GetCurrentTemplateName(oldEnt)
+
+ Engine.UpdateEntityTemplate(newTemplate, oldEnt);
+ Engine.PostMessage(oldEnt, MT_TemplateChanged, { "entity": oldEnt, "from": oldTemplate, "to": newTemplate });
+
+ return oldEnt;
+
var newEnt = Engine.AddEntity(newTemplate);
if (newEnt == INVALID_ENTITY)
{
Index: binaries/data/mods/public/simulation/templates/special/players/maur.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/special/players/maur.xml
+++ binaries/data/mods/public/simulation/templates/special/players/maur.xml
@@ -3,6 +3,14 @@
teambonuses/maur_player_teambonus
+
+
+
+ Cavalry
+
+
+
+
maur
Mauryas
Index: binaries/data/mods/public/simulation/templates/structures/maur/house.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/structures/maur/house.xml
+++ binaries/data/mods/public/simulation/templates/structures/maur/house.xml
@@ -10,6 +10,10 @@
+
+ structures/maur/outpost
+ 1
+
structures/mauryas/house.xml
Index: binaries/data/mods/public/simulation/templates/structures/maur/outpost.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/structures/maur/outpost.xml
+++ binaries/data/mods/public/simulation/templates/structures/maur/outpost.xml
@@ -4,6 +4,10 @@
maur
Uparaksana
+
+ structures/maur/house
+ 1
+
structures/mauryas/outpost.xml
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/MessageTypes.h
===================================================================
--- source/simulation2/MessageTypes.h
+++ source/simulation2/MessageTypes.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
@@ -301,7 +301,9 @@
CVector3D pos1;
};
-/*Sent whenever the territory type (neutral,own,enemy) differs from the former type*/
+/**
+ * Sent whenever the territory type (neutral, own, enemy) under an entity differs from the former type
+ */
class CMessageTerritoryPositionChanged final : public CMessage
{
public:
@@ -316,6 +318,25 @@
player_id_t newTerritory;
};
+
+/**
+ * Sent whenever the territory influence changes.
+ */
+class CMessageTerritoryInfluenceChanged final : public CMessage
+{
+public:
+ DEFAULT_MESSAGE_IMPL(TerritoryInfluenceChanged)
+
+ CMessageTerritoryInfluenceChanged(entity_id_t entity) :
+ entity(entity)
+ {
+ }
+
+ entity_id_t entity;
+};
+
+
+
/**
* Sent by CCmpUnitMotion during Update if an event happened that might interest other components.
*/
@@ -491,7 +512,7 @@
};
/**
- * Sent by aura manager when a value of a certain entity's component is changed
+ * Sent by techs / aura / ... when the value of a certain modified has changed.
*/
class CMessageValueModification final : public CMessage
{
Index: source/simulation2/TypeList.h
===================================================================
--- source/simulation2/TypeList.h
+++ source/simulation2/TypeList.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
@@ -45,6 +45,7 @@
MESSAGE(PositionChanged)
MESSAGE(InterpolatedPositionChanged)
MESSAGE(TerritoryPositionChanged)
+MESSAGE(TerritoryInfluenceChanged)
MESSAGE(MotionUpdate)
MESSAGE(RangeUpdate)
MESSAGE(TerrainChanged)
@@ -184,12 +185,13 @@
INTERFACE(TerritoryDecayManager)
COMPONENT(TerritoryDecayManagerScripted)
-INTERFACE(TerritoryInfluence)
-COMPONENT(TerritoryInfluence)
-
INTERFACE(TerritoryManager)
COMPONENT(TerritoryManager)
+// Must come after the manager
+INTERFACE(TerritoryInfluence)
+COMPONENT(TerritoryInfluence)
+
INTERFACE(TurretHolder)
COMPONENT(TurretHolderScripted)
Index: source/simulation2/components/CCmpDecay.cpp
===================================================================
--- source/simulation2/components/CCmpDecay.cpp
+++ source/simulation2/components/CCmpDecay.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
@@ -115,6 +115,15 @@
{
}
+ void Update(const CParamNode& paramNode) override
+ {
+ m_Active = paramNode.GetChild("Active").ToBool();
+ m_ShipSink = paramNode.GetChild("SinkingAnim").ToBool();
+ m_DelayTime = paramNode.GetChild("DelayTime").ToFixed().ToFloat();
+ m_SinkRate = paramNode.GetChild("SinkRate").ToFixed().ToFloat();
+ m_SinkAccel = paramNode.GetChild("SinkAccel").ToFixed().ToFloat();
+ }
+
void Serialize(ISerializer& UNUSED(serialize)) override
{
// This component isn't network-synchronised, so don't serialize anything
Index: source/simulation2/components/CCmpFootprint.cpp
===================================================================
--- source/simulation2/components/CCmpFootprint.cpp
+++ source/simulation2/components/CCmpFootprint.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
@@ -125,6 +125,11 @@
{
}
+ void Update(const CParamNode& paramNode) override
+ {
+ Init(paramNode);
+ }
+
void Serialize(ISerializer& UNUSED(serialize)) override
{
// No dynamic state to serialize
Index: source/simulation2/components/CCmpMinimap.cpp
===================================================================
--- source/simulation2/components/CCmpMinimap.cpp
+++ source/simulation2/components/CCmpMinimap.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
@@ -140,6 +140,36 @@
{
}
+ void Update(const CParamNode& paramNode) override
+ {
+ const CParamNode& color = paramNode.GetChild("Color");
+ if (color.IsOk())
+ {
+ m_UsePlayerColor = false;
+ m_R = (u8)color.GetChild("@r").ToInt();
+ m_G = (u8)color.GetChild("@g").ToInt();
+ m_B = (u8)color.GetChild("@b").ToInt();
+ }
+ else
+ {
+ m_UsePlayerColor = true;
+ UpdateColor();
+ }
+
+ const CParamNode& iconNode = paramNode.GetChild("Icon");
+ if (iconNode.IsOk())
+ {
+ const CParamNode& iconSizeNode = iconNode.GetChild("@size");
+ if (iconSizeNode.IsOk())
+ {
+ m_HasIcon = true;
+ m_IconPath = "art/textures/ui/session/icons/minimap/" + iconNode.ToString();
+ m_IconSize = iconSizeNode.ToFloat();
+ }
+ } else
+ m_HasIcon = false;
+ }
+
template
void SerializeCommon(S& serialize)
{
Index: source/simulation2/components/CCmpObstruction.cpp
===================================================================
--- source/simulation2/components/CCmpObstruction.cpp
+++ source/simulation2/components/CCmpObstruction.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
@@ -275,6 +275,29 @@
void Deinit() override
{
+ m_IsDestroyed = true;
+ }
+
+ void Update(const CParamNode& paramNode) override
+ {
+ bool active = m_Active;
+ bool moving = m_Moving;
+ entity_id_t cg1 = m_ControlGroup;
+ entity_id_t cg2 = m_ControlGroup2;
+
+ bool block_movement = m_Flags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT;
+ bool block_pathfinding = m_Flags & ICmpObstructionManager::FLAG_BLOCK_PATHFINDING;
+
+ SetActive(false);
+
+ Init(paramNode);
+ m_Active = false;
+
+ m_Moving = moving;
+ m_ControlGroup = cg1;
+ m_ControlGroup2 = cg2;
+
+ SetActive(active);
}
template
Index: source/simulation2/components/CCmpOwnership.cpp
===================================================================
--- source/simulation2/components/CCmpOwnership.cpp
+++ source/simulation2/components/CCmpOwnership.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
@@ -54,6 +54,10 @@
{
}
+ void Update(const CParamNode& UNUSED(paramNode)) override
+ {
+ }
+
void Serialize(ISerializer& serialize) override
{
serialize.NumberI32_Unbounded("owner", m_Owner);
Index: source/simulation2/components/CCmpPosition.cpp
===================================================================
--- source/simulation2/components/CCmpPosition.cpp
+++ source/simulation2/components/CCmpPosition.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
@@ -175,6 +175,29 @@
{
}
+ void Update(const CParamNode& paramNode) override
+ {
+ const std::string& anchor = paramNode.GetChild("Anchor").ToString();
+ if (anchor == "pitch")
+ m_AnchorType = PITCH;
+ else if (anchor == "pitch-roll")
+ m_AnchorType = PITCH_ROLL;
+ else if (anchor == "roll")
+ m_AnchorType = ROLL;
+ else
+ m_AnchorType = UPRIGHT;
+
+ if (m_RelativeToGround)
+ m_Y = paramNode.GetChild("Altitude").ToFixed();
+
+ m_Floating = paramNode.GetChild("Floating").ToBool();
+ m_FloatDepth = paramNode.GetChild("FloatDepth").ToFixed();
+
+ m_RotYSpeed = paramNode.GetChild("TurnRate").ToFixed();
+
+ AdvertiseInterpolatedPositionChanges();
+ }
+
void Serialize(ISerializer& serialize) override
{
serialize.Bool("in world", m_InWorld);
@@ -191,7 +214,6 @@
serialize.NumberFixed_Unbounded("rot x", m_RotX);
serialize.NumberFixed_Unbounded("rot y", m_RotY);
serialize.NumberFixed_Unbounded("rot z", m_RotZ);
- serialize.NumberFixed_Unbounded("rot y speed", m_RotYSpeed);
serialize.NumberFixed_Unbounded("altitude", m_Y);
serialize.Bool("relative", m_RelativeToGround);
serialize.Bool("floating", m_Floating);
@@ -250,7 +272,6 @@
deserialize.NumberFixed_Unbounded("rot x", m_RotX);
deserialize.NumberFixed_Unbounded("rot y", m_RotY);
deserialize.NumberFixed_Unbounded("rot z", m_RotZ);
- deserialize.NumberFixed_Unbounded("rot y speed", m_RotYSpeed);
deserialize.NumberFixed_Unbounded("altitude", m_Y);
deserialize.Bool("relative", m_RelativeToGround);
deserialize.Bool("floating", m_Floating);
Index: source/simulation2/components/CCmpTerritoryInfluence.cpp
===================================================================
--- source/simulation2/components/CCmpTerritoryInfluence.cpp
+++ source/simulation2/components/CCmpTerritoryInfluence.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
@@ -20,6 +20,7 @@
#include "simulation2/system/Component.h"
#include "ICmpTerritoryInfluence.h"
+#include "simulation2/MessageTypes.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpValueModificationManager.h"
@@ -27,8 +28,10 @@
class CCmpTerritoryInfluence final : public ICmpTerritoryInfluence
{
public:
- static void ClassInit(CComponentManager& UNUSED(componentManager))
+ static void ClassInit(CComponentManager& componentManager)
{
+ componentManager.SubscribeToMessageType(MT_OwnershipChanged);
+ componentManager.SubscribeToMessageType(MT_ValueModification);
}
DEFAULT_COMPONENT_ALLOCATOR(TerritoryInfluence)
@@ -62,6 +65,14 @@
void Deinit() override
{
+ OnChanged();
+ }
+
+ void Update(const CParamNode& paramNode) override
+ {
+ // TODO: only update on change
+ Init(paramNode);
+ OnChanged();
}
void Serialize(ISerializer& UNUSED(serialize)) override
@@ -73,6 +84,34 @@
Init(paramNode);
}
+ void HandleMessage(const CMessage& msg, bool UNUSED(global)) override
+ {
+ switch (msg.GetType())
+ {
+ case MT_OwnershipChanged:
+ {
+ const CMessageOwnershipChanged& msgData = static_cast (msg);
+ // handled in DeInit();
+ if (msgData.to != INVALID_PLAYER)
+ OnChanged();
+ break;
+ }
+ case MT_ValueModification:
+ {
+ const CMessageValueModification& msgData = static_cast (msg);
+ if (msgData.component == L"TerritoryInfluence")
+ OnChanged();
+ break;
+ }
+ }
+ }
+
+ void OnChanged() const
+ {
+ CMessageTerritoryInfluenceChanged msg(GetEntityId());
+ GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
+ }
+
bool IsRoot() const override
{
CmpPtr cmpValueModificationManager(GetSystemEntity());
Index: source/simulation2/components/CCmpTerritoryManager.cpp
===================================================================
--- source/simulation2/components/CCmpTerritoryManager.cpp
+++ source/simulation2/components/CCmpTerritoryManager.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
@@ -60,10 +60,10 @@
public:
static void ClassInit(CComponentManager& componentManager)
{
- componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged);
+
componentManager.SubscribeGloballyToMessageType(MT_PlayerColorChanged);
componentManager.SubscribeGloballyToMessageType(MT_PositionChanged);
- componentManager.SubscribeGloballyToMessageType(MT_ValueModification);
+ componentManager.SubscribeGloballyToMessageType(MT_TerritoryInfluenceChanged);
componentManager.SubscribeToMessageType(MT_ObstructionMapShapeChanged);
componentManager.SubscribeToMessageType(MT_TerrainChanged);
componentManager.SubscribeToMessageType(MT_WaterChanged);
@@ -171,12 +171,6 @@
{
switch (msg.GetType())
{
- case MT_OwnershipChanged:
- {
- const CMessageOwnershipChanged& msgData = static_cast (msg);
- MakeDirtyIfRelevantEntity(msgData.entity);
- break;
- }
case MT_PlayerColorChanged:
{
MakeDirty();
@@ -188,11 +182,9 @@
MakeDirtyIfRelevantEntity(msgData.entity);
break;
}
- case MT_ValueModification:
+ case MT_TerritoryInfluenceChanged:
{
- const CMessageValueModification& msgData = static_cast (msg);
- if (msgData.component == L"TerritoryInfluence")
- MakeDirty();
+ MakeDirty();
break;
}
case MT_ObstructionMapShapeChanged:
Index: source/simulation2/components/CCmpUnitMotion.h
===================================================================
--- source/simulation2/components/CCmpUnitMotion.h
+++ source/simulation2/components/CCmpUnitMotion.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
@@ -316,6 +316,32 @@
{
}
+ void Update(const CParamNode& paramNode) override
+ {
+ m_IsFormationController = paramNode.GetChild("FormationController").ToBool();
+ m_WalkSpeed = m_TemplateWalkSpeed = m_Speed = paramNode.GetChild("WalkSpeed").ToFixed();
+
+ if (paramNode.GetChild("RunMultiplier").IsOk())
+ m_TemplateRunMultiplier = paramNode.GetChild("RunMultiplier").ToFixed();
+ else
+ m_TemplateRunMultiplier = fixed::FromInt(1);
+
+ m_InstantTurnAngle = paramNode.GetChild("InstantTurnAngle").ToFixed();
+ m_TemplateAcceleration = paramNode.GetChild("Acceleration").ToFixed();
+ m_TemplateWeight = paramNode.GetChild("Weight").ToFixed();
+
+ m_PassClassName = paramNode.GetChild("PassabilityClass").ToString();
+ SetPassabilityData(m_PassClassName);
+
+ CmpPtr cmpObstruction(GetEntityHandle());
+ if (cmpObstruction)
+ m_BlockMovement = cmpObstruction->GetBlockMovementFlag(false);
+
+ SetParticipateInPushing(!paramNode.GetChild("DisablePushing").IsOk() || !paramNode.GetChild("DisablePushing").ToBool());
+
+ OnValueModification();
+ }
+
template
void SerializeCommon(S& serialize)
{
Index: source/simulation2/components/CCmpVision.cpp
===================================================================
--- source/simulation2/components/CCmpVision.cpp
+++ source/simulation2/components/CCmpVision.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
@@ -72,6 +72,17 @@
{
}
+ void Update(const CParamNode& paramNode) override
+ {
+ m_BaseRange = paramNode.GetChild("Range").ToFixed();
+
+ if (paramNode.GetChild("RevealShore").IsOk() && !m_RevealShore)
+ // Need to not be lazy and send a message
+ LOGWARNING("Updating to an entity that reveals the shore is unsupported");
+
+ ReloadRange();
+ }
+
void Serialize(ISerializer& UNUSED(serialize)) override
{
// No dynamic state to serialize
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/MessageTypeConversions.cpp
===================================================================
--- source/simulation2/scripting/MessageTypeConversions.cpp
+++ source/simulation2/scripting/MessageTypeConversions.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
@@ -265,6 +265,22 @@
////////////////////////////////
+JS::Value CMessageTerritoryInfluenceChanged::ToJSVal(const ScriptInterface& scriptInterface) const
+{
+ TOJSVAL_SETUP();
+ SET_MSG_PROPERTY(entity);
+ return JS::ObjectValue(*obj);
+}
+
+CMessage* CMessageTerritoryInfluenceChanged::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val)
+{
+ FROMJSVAL_SETUP();
+ GET_MSG_PROPERTY(entity_id_t, entity);
+ return new CMessageTerritoryInfluenceChanged(entity);
+}
+
+////////////////////////////////
+
const std::array CMessageMotionUpdate::UpdateTypeStr = { {
"likelySuccess", "likelyFailure", "obstructed", "veryObstructed"
} };
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,103 @@
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;
+ }
+
+ // Make it inaccessible from outside
+ handle.GetComponentCache()->interfaces[m_ComponentTypesById[iit->first].iid] = nullptr;
+ m_ComponentsByInterface[m_ComponentTypesById[iit->first].iid][ent] = nullptr;
+
+ // TODO -> should we de-init sooner / later ?
+ eit->second->Deinit();
+ RemoveComponentDynamicSubscriptions(eit->second);
+
+ m_ComponentTypesById[iit->first].dealloc(eit->second);
+
+ // TODO: it would be nice to clean up the null pointers before the entity gets destroyed.
+ eit->second = 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,9 +1021,11 @@
std::map::iterator eit = iit->second.find(ent);
if (eit != iit->second.end())
{
- eit->second->Deinit();
- RemoveComponentDynamicSubscriptions(eit->second);
- m_ComponentTypesById[iit->first].dealloc(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;
}
@@ -970,7 +1076,8 @@
std::unordered_map::const_iterator it = m_ComponentsByInterface[iid].begin();
for (; it != m_ComponentsByInterface[iid].end(); ++it)
- ret.push_back(*it);
+ if (it->second)
+ ret.push_back(*it);
std::sort(ret.begin(), ret.end()); // lexicographic pair comparison means this'll sort by entity ID
@@ -1008,7 +1115,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 +1141,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 +1181,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,8 @@
{
return JS::NullValue();
}
+
+void IComponent::Update(const CParamNode& paramNode) {
+ LOGWARNING("Warn - Update for %s not implemented yet", GetSimContext().GetComponentManager().LookupComponentTypeName(GetComponentTypeId()));
+ // TODO: remove this, this is just a placeholder for compilation.
+}