Index: ps/trunk/binaries/data/mods/public/simulation/components/Foundation.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Foundation.js (revision 25831)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Foundation.js (revision 25832)
@@ -1,447 +1,460 @@
function Foundation() {}
Foundation.prototype.Schema =
"" +
"" +
"";
Foundation.prototype.Init = function()
{
// Foundations are initially 'uncommitted' and do not block unit movement at all
// (to prevent players exploiting free foundations to confuse enemy units).
// The first builder to reach the uncommitted foundation will tell friendly units
// and animals to move out of the way, then will commit the foundation and enable
// its obstruction once there's nothing in the way.
this.committed = false;
this.builders = new Map(); // Map of builder entities to their work per second
this.totalBuilderRate = 0; // Total amount of work the builders do each second
this.buildMultiplier = 1; // Multiplier for the amount of work builders do
this.buildTimeModifier = +this.template.BuildTimeModifier;
this.previewEntity = INVALID_ENTITY;
};
Foundation.prototype.Serialize = function()
{
let ret = Object.assign({}, this);
ret.previewEntity = INVALID_ENTITY;
return ret;
};
Foundation.prototype.Deserialize = function(data)
{
this.Init();
Object.assign(this, data);
};
Foundation.prototype.OnDeserialized = function()
{
this.CreateConstructionPreview();
};
Foundation.prototype.InitialiseConstruction = function(template)
{
this.finalTemplateName = template;
// Remember the cost here, so if it changes after construction begins (from auras or technologies)
// we will use the correct values to refund partial construction costs.
let cmpCost = Engine.QueryInterface(this.entity, IID_Cost);
if (!cmpCost)
error("A foundation, from " + template + ", must have a cost component to know the build time");
this.costs = cmpCost.GetResourceCosts();
this.maxProgress = 0;
this.initialised = true;
};
/**
* Moving the revelation logic from Build to here makes the building sink if
* it is attacked.
*/
Foundation.prototype.OnHealthChanged = function(msg)
{
let cmpPosition = Engine.QueryInterface(this.previewEntity, IID_Position);
if (cmpPosition)
cmpPosition.SetConstructionProgress(this.GetBuildProgress());
Engine.PostMessage(this.entity, MT_FoundationProgressChanged, { "to": this.GetBuildPercentage() });
};
/**
* Returns the current build progress in a [0,1] range.
*/
Foundation.prototype.GetBuildProgress = function()
{
let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
if (!cmpHealth)
return 0;
return cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints();
};
Foundation.prototype.GetBuildPercentage = function()
{
return Math.floor(this.GetBuildProgress() * 100);
};
/**
* @return {number[]} - An array containing the entity IDs of assigned builders.
*/
Foundation.prototype.GetBuilders = function()
{
return Array.from(this.builders.keys());
};
Foundation.prototype.GetNumBuilders = function()
{
return this.builders.size;
};
Foundation.prototype.IsFinished = function()
{
return (this.GetBuildProgress() == 1.0);
};
Foundation.prototype.OnOwnershipChanged = function(msg)
{
if (msg.to != INVALID_PLAYER && this.previewEntity != INVALID_ENTITY)
{
let cmpPreviewOwnership = Engine.QueryInterface(this.previewEntity, IID_Ownership);
if (cmpPreviewOwnership)
cmpPreviewOwnership.SetOwner(msg.to);
return;
}
if (msg.to != INVALID_PLAYER || !this.initialised)
return;
if (this.previewEntity != INVALID_ENTITY)
{
Engine.DestroyEntity(this.previewEntity);
this.previewEntity = INVALID_ENTITY;
}
if (this.IsFinished())
return;
let cmpPlayer = QueryPlayerIDInterface(msg.from);
let cmpStatisticsTracker = QueryPlayerIDInterface(msg.from, IID_StatisticsTracker);
// Refund a portion of the construction cost, proportional
// to the amount of build progress remaining.
for (let r in this.costs)
{
let scaled = Math.ceil(this.costs[r] * (1.0 - this.maxProgress));
if (scaled)
{
if (cmpPlayer)
cmpPlayer.AddResource(r, scaled);
if (cmpStatisticsTracker)
cmpStatisticsTracker.IncreaseResourceUsedCounter(r, -scaled);
}
}
};
/**
* @param {number[]} builders - An array containing the entity IDs of builders to assign.
*/
Foundation.prototype.AddBuilders = function(builders)
{
let changed = false;
for (let builder of builders)
changed = this.AddBuilderHelper(builder) || changed;
if (changed)
this.HandleBuildersChanged();
};
/**
* @param {number} builderEnt - The entity to add.
* @return {boolean} - Whether the addition was successful.
*/
Foundation.prototype.AddBuilderHelper = function(builderEnt)
{
if (this.builders.has(builderEnt))
return false;
let cmpBuilder = Engine.QueryInterface(builderEnt, IID_Builder) ||
Engine.QueryInterface(this.entity, IID_AutoBuildable);
if (!cmpBuilder)
return false;
let buildRate = cmpBuilder.GetRate();
this.builders.set(builderEnt, buildRate);
this.totalBuilderRate += buildRate;
return true;
};
/**
* @param {number} builderEnt - The entity to add.
*/
Foundation.prototype.AddBuilder = function(builderEnt)
{
if (this.AddBuilderHelper(builderEnt))
this.HandleBuildersChanged();
};
/**
* @param {number} builderEnt - The entity to remove.
*/
Foundation.prototype.RemoveBuilder = function(builderEnt)
{
if (!this.builders.has(builderEnt))
return;
this.totalBuilderRate -= this.builders.get(builderEnt);
this.builders.delete(builderEnt);
this.HandleBuildersChanged();
};
/**
* This has to be called whenever the number of builders change.
*/
Foundation.prototype.HandleBuildersChanged = function()
{
this.SetBuildMultiplier();
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (cmpVisual)
cmpVisual.SetVariable("numbuilders", this.GetNumBuilders());
Engine.PostMessage(this.entity, MT_FoundationBuildersChanged, { "to": this.GetBuilders() });
};
/**
* The build multiplier is a penalty that is applied to each builder.
* For example, ten women build at a combined rate of 10^0.7 = 5.01 instead of 10.
*/
Foundation.prototype.CalculateBuildMultiplier = function(num)
{
// Avoid division by zero, in particular 0/0 = NaN which isn't reliably serialized
return num < 2 ? 1 : Math.pow(num, this.buildTimeModifier) / num;
};
Foundation.prototype.SetBuildMultiplier = function()
{
this.buildMultiplier = this.CalculateBuildMultiplier(this.GetNumBuilders());
};
Foundation.prototype.GetBuildTime = function()
{
let timeLeft = (1 - this.GetBuildProgress()) * Engine.QueryInterface(this.entity, IID_Cost).GetBuildTime();
let rate = this.totalBuilderRate * this.buildMultiplier;
let rateNew = (this.totalBuilderRate + 1) * this.CalculateBuildMultiplier(this.GetNumBuilders() + 1);
return {
// Avoid division by zero, in particular 0/0 = NaN which isn't reliably serialized
"timeRemaining": rate ? timeLeft / rate : 0,
"timeRemainingNew": timeLeft / rateNew
};
};
/**
* @return {boolean} - Whether the foundation has been committed sucessfully.
*/
Foundation.prototype.Commit = function()
{
if (this.committed)
return false;
let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
if (cmpObstruction && cmpObstruction.GetBlockMovementFlag(true))
{
for (let ent of cmpObstruction.GetEntitiesDeletedUponConstruction())
Engine.DestroyEntity(ent);
let collisions = cmpObstruction.GetEntitiesBlockingConstruction();
if (collisions.length)
{
for (let ent of collisions)
{
let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
cmpUnitAI.LeaveFoundation(this.entity);
// TODO: What if an obstruction has no UnitAI?
}
// TODO: maybe we should tell the builder to use a special
// animation to indicate they're waiting for people to get
// out the way
return false;
}
}
// The obstruction always blocks new foundations/construction,
// but we've temporarily allowed units to walk all over it
// (via CCmpTemplateManager). Now we need to remove that temporary
// blocker-disabling, so that we'll perform standard unit blocking instead.
if (cmpObstruction)
cmpObstruction.SetDisableBlockMovementPathfinding(false, false, -1);
let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
cmpTrigger.CallEvent("OnConstructionStarted", {
"foundation": this.entity,
"template": this.finalTemplateName
});
let cmpFoundationVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (cmpFoundationVisual)
cmpFoundationVisual.SelectAnimation("scaffold", false, 1.0);
this.committed = true;
this.CreateConstructionPreview();
return true;
};
/**
* Perform some number of seconds of construction work.
* Returns true if the construction is completed.
*/
Foundation.prototype.Build = function(builderEnt, work)
{
// Do nothing if we've already finished building
// (The entity will be destroyed soon after completion so
// this won't happen much.)
if (this.IsFinished())
return;
if (!this.committed && !this.Commit())
return;
let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
if (!cmpHealth)
{
error("Foundation " + this.entity + " does not have a health component.");
return;
}
let deltaHP = work * this.GetBuildRate() * this.buildMultiplier;
if (deltaHP > 0)
cmpHealth.Increase(deltaHP);
// Update the total builder rate.
this.totalBuilderRate += work - this.builders.get(builderEnt);
this.builders.set(builderEnt, work);
// Remember our max progress for partial refund in case of destruction.
this.maxProgress = Math.max(this.maxProgress, this.GetBuildProgress());
if (this.maxProgress >= 1.0)
{
let cmpPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
let building = ChangeEntityTemplate(this.entity, this.finalTemplateName);
// Make sure the foundation object is the same as the final object.
const cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
const cmpBuildingVisual = Engine.QueryInterface(building, IID_Visual);
if (cmpVisual && cmpBuildingVisual)
cmpBuildingVisual.SetActorSeed(cmpVisual.GetActorSeed());
+ 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);
+ if (cmpVisualCorpse)
+ cmpVisualCorpse.RecomputeActorName();
+ }
+ }
+
if (cmpPlayerStatisticsTracker)
cmpPlayerStatisticsTracker.IncreaseConstructedBuildingsCounter(building);
PlaySound("constructed", building);
Engine.PostMessage(this.entity, MT_ConstructionFinished,
{ "entity": this.entity, "newentity": building });
for (let builder of this.GetBuilders())
{
let cmpUnitAIBuilder = Engine.QueryInterface(builder, IID_UnitAI);
if (cmpUnitAIBuilder)
cmpUnitAIBuilder.ConstructionFinished({ "entity": this.entity, "newentity": building });
}
}
};
Foundation.prototype.GetBuildRate = function()
{
let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
let cmpCost = Engine.QueryInterface(this.entity, IID_Cost);
// Return infinity for instant structure conversion
return cmpHealth.GetMaxHitpoints() / cmpCost.GetBuildTime();
};
/**
* Create preview entity and copy various parameters from the foundation.
*/
Foundation.prototype.CreateConstructionPreview = function()
{
if (this.previewEntity)
{
Engine.DestroyEntity(this.previewEntity);
this.previewEntity = INVALID_ENTITY;
}
if (!this.committed)
return;
let cmpFoundationVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (!cmpFoundationVisual || !cmpFoundationVisual.HasConstructionPreview())
return;
this.previewEntity = Engine.AddLocalEntity("construction|"+this.finalTemplateName);
let cmpFoundationOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
let cmpPreviewOwnership = Engine.QueryInterface(this.previewEntity, IID_Ownership);
if (cmpFoundationOwnership && cmpPreviewOwnership)
cmpPreviewOwnership.SetOwner(cmpFoundationOwnership.GetOwner());
// TODO: the 'preview' would be invisible if it doesn't have the below component,
// Maybe it makes more sense to simply delete it then?
// Initially hide the preview underground
let cmpPreviewPosition = Engine.QueryInterface(this.previewEntity, IID_Position);
let cmpFoundationPosition = Engine.QueryInterface(this.entity, IID_Position);
if (cmpPreviewPosition && cmpFoundationPosition)
{
let rot = cmpFoundationPosition.GetRotation();
cmpPreviewPosition.SetYRotation(rot.y);
cmpPreviewPosition.SetXZRotation(rot.x, rot.z);
let pos = cmpFoundationPosition.GetPosition2D();
cmpPreviewPosition.JumpTo(pos.x, pos.y);
cmpPreviewPosition.SetConstructionProgress(this.GetBuildProgress());
}
let cmpPreviewVisual = Engine.QueryInterface(this.previewEntity, IID_Visual);
if (cmpPreviewVisual && cmpFoundationVisual)
{
cmpPreviewVisual.SetActorSeed(cmpFoundationVisual.GetActorSeed());
cmpPreviewVisual.SelectAnimation("scaffold", false, 1.0);
}
};
Foundation.prototype.OnEntityRenamed = function(msg)
{
let cmpFoundationNew = Engine.QueryInterface(msg.newentity, IID_Foundation);
if (cmpFoundationNew)
cmpFoundationNew.AddBuilders(this.GetBuilders());
};
function FoundationMirage() {}
FoundationMirage.prototype.Init = function(cmpFoundation)
{
this.numBuilders = cmpFoundation.GetNumBuilders();
this.buildTime = cmpFoundation.GetBuildTime();
};
FoundationMirage.prototype.GetNumBuilders = function() { return this.numBuilders; };
FoundationMirage.prototype.GetBuildTime = function() { return this.buildTime; };
Engine.RegisterGlobal("FoundationMirage", FoundationMirage);
Foundation.prototype.Mirage = function()
{
let mirage = new FoundationMirage();
mirage.Init(this);
return mirage;
};
Engine.RegisterComponentType(IID_Foundation, "Foundation", Foundation);
Index: ps/trunk/binaries/data/mods/public/simulation/components/Health.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Health.js (revision 25831)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Health.js (revision 25832)
@@ -1,510 +1,526 @@
function Health() {}
Health.prototype.Schema =
"Deals with hitpoints and death." +
"" +
"100" +
"1.0" +
"0" +
"corpse" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"0" +
"1" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"vanish" +
"corpse" +
"remain" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
Health.prototype.Init = function()
{
// Cache this value so it allows techs to maintain previous health level
this.maxHitpoints = +this.template.Max;
// Default to , but use if it's undefined or zero
// (Allowing 0 initial HP would break our death detection code)
this.hitpoints = +(this.template.Initial || this.GetMaxHitpoints());
this.regenRate = ApplyValueModificationsToEntity("Health/RegenRate", +this.template.RegenRate, this.entity);
this.idleRegenRate = ApplyValueModificationsToEntity("Health/IdleRegenRate", +this.template.IdleRegenRate, this.entity);
this.CheckRegenTimer();
this.UpdateActor();
};
/**
* Returns the current hitpoint value.
* This is 0 if (and only if) the unit is dead.
*/
Health.prototype.GetHitpoints = function()
{
return this.hitpoints;
};
Health.prototype.GetMaxHitpoints = function()
{
return this.maxHitpoints;
};
/**
* @return {boolean} Whether the units are injured. Dead units are not considered injured.
*/
Health.prototype.IsInjured = function()
{
return this.hitpoints > 0 && this.hitpoints < this.GetMaxHitpoints();
};
Health.prototype.SetHitpoints = function(value)
{
// If we're already dead, don't allow resurrection
if (this.hitpoints == 0)
return;
// Before changing the value, activate Fogging if necessary to hide changes
let cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging);
if (cmpFogging)
cmpFogging.Activate();
let old = this.hitpoints;
this.hitpoints = Math.max(1, Math.min(this.GetMaxHitpoints(), value));
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpRangeManager)
cmpRangeManager.SetEntityFlag(this.entity, "injured", this.IsInjured());
this.RegisterHealthChanged(old);
};
Health.prototype.IsRepairable = function()
{
let cmpRepairable = Engine.QueryInterface(this.entity, IID_Repairable);
return cmpRepairable && cmpRepairable.IsRepairable();
};
Health.prototype.IsUnhealable = function()
{
return this.template.Unhealable == "true" ||
this.hitpoints <= 0 || !this.IsInjured();
};
Health.prototype.GetIdleRegenRate = function()
{
return this.idleRegenRate;
};
Health.prototype.GetRegenRate = function()
{
return this.regenRate;
};
Health.prototype.ExecuteRegeneration = function()
{
let regen = this.GetRegenRate();
if (this.GetIdleRegenRate() != 0)
{
let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
if (cmpUnitAI && cmpUnitAI.IsIdle())
regen += this.GetIdleRegenRate();
}
if (regen > 0)
this.Increase(regen);
else
this.Reduce(-regen);
};
/*
* Check if the regeneration timer needs to be started or stopped
*/
Health.prototype.CheckRegenTimer = function()
{
// check if we need a timer
if (this.GetRegenRate() == 0 && this.GetIdleRegenRate() == 0 ||
!this.IsInjured() && this.GetRegenRate() >= 0 && this.GetIdleRegenRate() >= 0 ||
this.hitpoints == 0)
{
// we don't need a timer, disable if one exists
if (this.regenTimer)
{
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.regenTimer);
this.regenTimer = undefined;
}
return;
}
// we need a timer, enable if one doesn't exist
if (this.regenTimer)
return;
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.regenTimer = cmpTimer.SetInterval(this.entity, IID_Health, "ExecuteRegeneration", 1000, 1000, null);
};
Health.prototype.Kill = function()
{
this.Reduce(this.hitpoints);
};
/**
* @param {number} amount - The amount of damage to be taken.
* @param {number} attacker - The entityID of the attacker.
* @param {number} attackerOwner - The playerID of the owner of the attacker.
*
* @eturn {Object} - Object of the form { "healthChange": number }.
*/
Health.prototype.TakeDamage = function(amount, attacker, attackerOwner)
{
if (!amount || !this.hitpoints)
return { "healthChange": 0 };
let change = this.Reduce(amount);
let cmpLoot = Engine.QueryInterface(this.entity, IID_Loot);
if (cmpLoot && cmpLoot.GetXp() > 0 && change.healthChange < 0)
change.xp = cmpLoot.GetXp() * -change.healthChange / this.GetMaxHitpoints();
if (!this.hitpoints)
this.KilledBy(attacker, attackerOwner);
return change;
};
/**
* Called when an entity kills us.
* @param {number} attacker - The entityID of the killer.
* @param {number} attackerOwner - The playerID of the attacker.
*/
Health.prototype.KilledBy = function(attacker, attackerOwner)
{
let cmpAttackerOwnership = Engine.QueryInterface(attacker, IID_Ownership);
if (cmpAttackerOwnership)
{
let currentAttackerOwner = cmpAttackerOwnership.GetOwner();
if (currentAttackerOwner != INVALID_PLAYER)
attackerOwner = currentAttackerOwner;
}
// Add to killer statistics.
let cmpKillerPlayerStatisticsTracker = QueryPlayerIDInterface(attackerOwner, IID_StatisticsTracker);
if (cmpKillerPlayerStatisticsTracker)
cmpKillerPlayerStatisticsTracker.KilledEntity(this.entity);
// Add to loser statistics.
let cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
if (cmpTargetPlayerStatisticsTracker)
cmpTargetPlayerStatisticsTracker.LostEntity(this.entity);
let cmpLooter = Engine.QueryInterface(attacker, IID_Looter);
if (cmpLooter)
cmpLooter.Collect(this.entity);
};
/**
* @param {number} amount - The amount of hitpoints to substract. Kills the entity if required.
* @return {{ healthChange:number }} - Number of health points lost.
*/
Health.prototype.Reduce = function(amount)
{
// If we are dead, do not do anything
// (The entity will exist a little while after calling DestroyEntity so this
// might get called multiple times)
// Likewise if the amount is 0.
if (!amount || !this.hitpoints)
return { "healthChange": 0 };
// Before changing the value, activate Fogging if necessary to hide changes
let cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging);
if (cmpFogging)
cmpFogging.Activate();
let oldHitpoints = this.hitpoints;
// If we reached 0, then die.
if (amount >= this.hitpoints)
{
this.hitpoints = 0;
this.RegisterHealthChanged(oldHitpoints);
this.HandleDeath();
return { "healthChange": -oldHitpoints };
}
// If we are not marked as injured, do it now
if (!this.IsInjured())
{
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpRangeManager)
cmpRangeManager.SetEntityFlag(this.entity, "injured", true);
}
this.hitpoints -= amount;
this.RegisterHealthChanged(oldHitpoints);
return { "healthChange": this.hitpoints - oldHitpoints };
};
/**
* Handle what happens when the entity dies.
*/
Health.prototype.HandleDeath = function()
{
let cmpDeathDamage = Engine.QueryInterface(this.entity, IID_DeathDamage);
if (cmpDeathDamage)
cmpDeathDamage.CauseDeathDamage();
PlaySound("death", this.entity);
if (this.template.SpawnEntityOnDeath)
this.CreateDeathSpawnedEntity();
switch (this.template.DeathType)
{
case "corpse":
this.CreateCorpse();
break;
case "remain":
return;
case "vanish":
break;
default:
error("Invalid template.DeathType: " + this.template.DeathType);
break;
}
Engine.DestroyEntity(this.entity);
};
Health.prototype.Increase = function(amount)
{
// Before changing the value, activate Fogging if necessary to hide changes
let cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging);
if (cmpFogging)
cmpFogging.Activate();
if (!this.IsInjured())
return { "old": this.hitpoints, "new": this.hitpoints };
// If we're already dead, don't allow resurrection
if (this.hitpoints == 0)
return undefined;
let old = this.hitpoints;
this.hitpoints = Math.min(this.hitpoints + amount, this.GetMaxHitpoints());
if (!this.IsInjured())
{
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpRangeManager)
cmpRangeManager.SetEntityFlag(this.entity, "injured", false);
}
this.RegisterHealthChanged(old);
return { "old": old, "new": this.hitpoints };
};
Health.prototype.CreateCorpse = function()
{
// If the unit died while not in the world, don't create any corpse for it
// since there's nowhere for the corpse to be placed.
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
if (!cmpPosition || !cmpPosition.IsInWorld())
return;
// Either creates a static local version of the current entity, or a
// persistent corpse retaining the ResourceSupply element of the parent.
let templateName = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetCurrentTemplateName(this.entity);
let entCorpse;
let cmpResourceSupply = Engine.QueryInterface(this.entity, IID_ResourceSupply);
let resource = cmpResourceSupply && cmpResourceSupply.GetKillBeforeGather();
if (resource)
entCorpse = Engine.AddEntity("resource|" + templateName);
else
entCorpse = Engine.AddLocalEntity("corpse|" + templateName);
// Copy various parameters so it looks just like us.
let cmpPositionCorpse = Engine.QueryInterface(entCorpse, IID_Position);
let pos = cmpPosition.GetPosition();
cmpPositionCorpse.JumpTo(pos.x, pos.z);
let rot = cmpPosition.GetRotation();
cmpPositionCorpse.SetYRotation(rot.y);
cmpPositionCorpse.SetXZRotation(rot.x, rot.z);
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
let cmpOwnershipCorpse = Engine.QueryInterface(entCorpse, IID_Ownership);
if (cmpOwnership && cmpOwnershipCorpse)
cmpOwnershipCorpse.SetOwner(cmpOwnership.GetOwner());
let cmpVisualCorpse = Engine.QueryInterface(entCorpse, IID_Visual);
if (cmpVisualCorpse)
{
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (cmpVisual)
cmpVisualCorpse.SetActorSeed(cmpVisual.GetActorSeed());
cmpVisualCorpse.SelectAnimation("death", true, 1);
}
+ const cmpIdentityCorpse = Engine.QueryInterface(entCorpse, IID_Identity);
+ if (cmpIdentityCorpse)
+ {
+ const cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
+ if (cmpIdentity)
+ {
+ const oldPhenotype = cmpIdentity.GetPhenotype();
+ if (cmpIdentityCorpse.GetPhenotype() !== oldPhenotype)
+ {
+ cmpIdentityCorpse.SetPhenotype(oldPhenotype);
+ if (cmpVisualCorpse)
+ cmpVisualCorpse.RecomputeActorName();
+ }
+ }
+ }
+
if (resource)
Engine.PostMessage(this.entity, MT_EntityRenamed, {
"entity": this.entity,
"newentity": entCorpse
});
};
Health.prototype.CreateDeathSpawnedEntity = function()
{
// If the unit died while not in the world, don't spawn a death entity for it
// since there's nowhere for it to be placed
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
if (!cmpPosition.IsInWorld())
return INVALID_ENTITY;
// Create SpawnEntityOnDeath entity
let spawnedEntity = Engine.AddLocalEntity(this.template.SpawnEntityOnDeath);
// Move to same position
let cmpSpawnedPosition = Engine.QueryInterface(spawnedEntity, IID_Position);
let pos = cmpPosition.GetPosition();
cmpSpawnedPosition.JumpTo(pos.x, pos.z);
let rot = cmpPosition.GetRotation();
cmpSpawnedPosition.SetYRotation(rot.y);
cmpSpawnedPosition.SetXZRotation(rot.x, rot.z);
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
let cmpSpawnedOwnership = Engine.QueryInterface(spawnedEntity, IID_Ownership);
if (cmpOwnership && cmpSpawnedOwnership)
cmpSpawnedOwnership.SetOwner(cmpOwnership.GetOwner());
return spawnedEntity;
};
Health.prototype.UpdateActor = function()
{
if (!this.template.DamageVariants)
return;
let ratio = this.hitpoints / this.GetMaxHitpoints();
let newDamageVariant = "alive";
if (ratio > 0)
{
let minTreshold = 1;
for (let key in this.template.DamageVariants)
{
let treshold = +this.template.DamageVariants[key];
if (treshold < ratio || treshold > minTreshold)
continue;
newDamageVariant = key;
minTreshold = treshold;
}
}
else
newDamageVariant = "death";
if (this.damageVariant && this.damageVariant == newDamageVariant)
return;
this.damageVariant = newDamageVariant;
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (cmpVisual)
cmpVisual.SetVariant("health", newDamageVariant);
};
Health.prototype.RecalculateValues = function()
{
let oldMaxHitpoints = this.GetMaxHitpoints();
let newMaxHitpoints = ApplyValueModificationsToEntity("Health/Max", +this.template.Max, this.entity);
if (oldMaxHitpoints != newMaxHitpoints)
{
let newHitpoints = this.hitpoints * newMaxHitpoints/oldMaxHitpoints;
this.maxHitpoints = newMaxHitpoints;
this.SetHitpoints(newHitpoints);
}
let oldRegenRate = this.regenRate;
this.regenRate = ApplyValueModificationsToEntity("Health/RegenRate", +this.template.RegenRate, this.entity);
let oldIdleRegenRate = this.idleRegenRate;
this.idleRegenRate = ApplyValueModificationsToEntity("Health/IdleRegenRate", +this.template.IdleRegenRate, this.entity);
if (this.regenRate != oldRegenRate || this.idleRegenRate != oldIdleRegenRate)
this.CheckRegenTimer();
};
Health.prototype.OnValueModification = function(msg)
{
if (msg.component == "Health")
this.RecalculateValues();
};
Health.prototype.OnOwnershipChanged = function(msg)
{
if (msg.to != INVALID_PLAYER)
this.RecalculateValues();
};
Health.prototype.RegisterHealthChanged = function(from)
{
this.CheckRegenTimer();
this.UpdateActor();
Engine.PostMessage(this.entity, MT_HealthChanged, { "from": from, "to": this.hitpoints });
};
function HealthMirage() {}
HealthMirage.prototype.Init = function(cmpHealth)
{
this.maxHitpoints = cmpHealth.GetMaxHitpoints();
this.hitpoints = cmpHealth.GetHitpoints();
this.repairable = cmpHealth.IsRepairable();
this.injured = cmpHealth.IsInjured();
this.unhealable = cmpHealth.IsUnhealable();
};
HealthMirage.prototype.GetMaxHitpoints = function() { return this.maxHitpoints; };
HealthMirage.prototype.GetHitpoints = function() { return this.hitpoints; };
HealthMirage.prototype.IsRepairable = function() { return this.repairable; };
HealthMirage.prototype.IsInjured = function() { return this.injured; };
HealthMirage.prototype.IsUnhealable = function() { return this.unhealable; };
Engine.RegisterGlobal("HealthMirage", HealthMirage);
Health.prototype.Mirage = function()
{
let mirage = new HealthMirage();
mirage.Init(this);
return mirage;
};
Engine.RegisterComponentType(IID_Health, "Health", Health);
Index: ps/trunk/binaries/data/mods/public/simulation/components/Identity.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Identity.js (revision 25831)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Identity.js (revision 25832)
@@ -1,224 +1,229 @@
function Identity() {}
Identity.prototype.Schema =
"Specifies various names and values associated with the entity, typically for GUI display to users." +
"" +
"athen" +
"Athenian Hoplite" +
"Hoplī́tēs Athēnaïkós" +
"units/athen_infantry_spearman.png" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"tokens" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"Basic" +
"Advanced" +
"Elite" +
"" +
"" +
"" +
"" +
"" +
"" +
"tokens" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"tokens" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"tokens" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
Identity.prototype.Init = function()
{
this.classesList = GetIdentityClasses(this.template);
this.visibleClassesList = GetVisibleIdentityClasses(this.template);
if (this.template.Phenotype)
this.phenotype = pickRandom(this.GetPossiblePhenotypes());
else
this.phenotype = "default";
this.controllable = this.template.Controllable ? this.template.Controllable == "true" : true;
};
Identity.prototype.HasSomeFormation = function()
{
return this.GetFormationsList().length > 0;
};
Identity.prototype.GetCiv = function()
{
return this.template.Civ;
};
Identity.prototype.GetLang = function()
{
return this.template.Lang || "greek"; // ugly default
};
/**
* Get a list of possible Phenotypes.
* @return {string[]} A list of possible phenotypes.
*/
Identity.prototype.GetPossiblePhenotypes = function()
{
return this.template.Phenotype._string.split(/\s+/);
};
/**
* Get the current Phenotype.
* @return {string} The current phenotype.
*/
Identity.prototype.GetPhenotype = function()
{
return this.phenotype;
};
Identity.prototype.GetRank = function()
{
return this.template.Rank || "";
};
Identity.prototype.GetClassesList = function()
{
return this.classesList;
};
Identity.prototype.GetVisibleClassesList = function()
{
return this.visibleClassesList;
};
Identity.prototype.HasClass = function(name)
{
return this.GetClassesList().indexOf(name) != -1;
};
Identity.prototype.GetFormationsList = function()
{
if (this.template.Formations && this.template.Formations._string)
return this.template.Formations._string.split(/\s+/);
return [];
};
Identity.prototype.CanUseFormation = function(template)
{
return this.GetFormationsList().indexOf(template) != -1;
};
Identity.prototype.GetSelectionGroupName = function()
{
return this.template.SelectionGroupName || "";
};
Identity.prototype.GetGenericName = function()
{
return this.template.GenericName;
};
Identity.prototype.IsUndeletable = function()
{
return this.template.Undeletable == "true";
};
Identity.prototype.IsControllable = function()
{
return this.controllable;
};
Identity.prototype.SetControllable = function(controllability)
{
this.controllable = controllability;
};
+Identity.prototype.SetPhenotype = function(phenotype)
+{
+ this.phenotype = phenotype;
+};
+
function IdentityMirage() {}
IdentityMirage.prototype.Init = function(cmpIdentity)
{
// Mirages don't get identity classes via the template-filter, so that code can query
// identity components via Engine.QueryInterface without having to explicitly check for mirages.
// This is cloned as otherwise we get a reference to Identity's property,
// and that array is deleted when serializing (as it's not seralized), which ends in OOS.
this.classes = clone(cmpIdentity.GetClassesList());
};
IdentityMirage.prototype.GetClassesList = function() { return this.classes; };
Engine.RegisterGlobal("IdentityMirage", IdentityMirage);
Identity.prototype.Mirage = function()
{
let mirage = new IdentityMirage();
mirage.Init(this);
return mirage;
};
Engine.RegisterComponentType(IID_Identity, "Identity", Identity);