mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-07-13 09:15:57 +00:00
1882f28504
Instead of each CCmpVisualActor rendering itself individually, collect all the units in a single CCmpUnitRenderer. This avoids the overhead of doing Interpolate/RenderSubmit calls every frame for every object in the world. It also allows more efficient culling. CCmpUnitRenderer knows the positions of each object at the start and end of each turn, and computes the bounding sphere of the object along that path. That allows quick culling without recomputing the precise interpolated transform every frame. (In the future it could be improved much more.) Clarify and clean up the sending of PositionChanged messages, and add new InterpolatedPositionChanged. Remove the forceFloating parameter from GetInterpolatedTransform, since it doesn't fit the new design. Replace it with a (non-synchronised) flag in CCmpPosition. Move construction progress from CCmpVisualActor to CCmpPosition, so that it consistently affects all position/transform computation. Refs #2337. This was SVN commit r15265.
336 lines
12 KiB
JavaScript
336 lines
12 KiB
JavaScript
function Foundation() {}
|
|
|
|
Foundation.prototype.Schema =
|
|
"<a:component type='internal'/><empty/>";
|
|
|
|
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 = []; // builder entities
|
|
this.buildMultiplier = 1; // Multiplier for the amount of work builders do.
|
|
|
|
this.previewEntity = INVALID_ENTITY;
|
|
};
|
|
|
|
Foundation.prototype.InitialiseConstruction = function(owner, template)
|
|
{
|
|
this.finalTemplateName = template;
|
|
|
|
// We need to know the owner in OnDestroy, but at that point the entity has already been
|
|
// decoupled from its owner, so we need to remember it in here (and assume it won't change)
|
|
this.owner = owner;
|
|
|
|
// Remember our cost here, so if it changes after construction begins (from technologies)
|
|
// we will use the correct values to refund partial construction costs
|
|
var cmpCost = Engine.QueryInterface(this.entity, IID_Cost);
|
|
this.costs = cmpCost.GetResourceCosts();
|
|
|
|
this.initialised = true;
|
|
};
|
|
|
|
/**
|
|
* Moving the revelation logic from Build to here makes the building sink if
|
|
* it is attacked.
|
|
*/
|
|
Foundation.prototype.OnHealthChanged = function(msg)
|
|
{
|
|
// Gradually reveal the final building preview
|
|
var 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()
|
|
{
|
|
var cmpHealth = Engine.QueryInterface(this.entity, IID_Health)
|
|
var hitpoints = cmpHealth.GetHitpoints();
|
|
var maxHitpoints = cmpHealth.GetMaxHitpoints();
|
|
|
|
return (hitpoints / maxHitpoints);
|
|
};
|
|
|
|
Foundation.prototype.GetBuildPercentage = function()
|
|
{
|
|
return Math.floor(this.GetBuildProgress() * 100);
|
|
};
|
|
|
|
Foundation.prototype.GetNumBuilders = function()
|
|
{
|
|
return this.builders.length;
|
|
};
|
|
|
|
Foundation.prototype.IsFinished = function()
|
|
{
|
|
return (this.GetBuildProgress() == 1.0);
|
|
};
|
|
|
|
Foundation.prototype.OnDestroy = function()
|
|
{
|
|
// Refund a portion of the construction cost, proportional to the amount of build progress remaining
|
|
|
|
if (!this.initialised) // this happens if the foundation was destroyed because the player had insufficient resources
|
|
return;
|
|
|
|
if (this.previewEntity != INVALID_ENTITY)
|
|
{
|
|
Engine.DestroyEntity(this.previewEntity);
|
|
this.previewEntity = INVALID_ENTITY;
|
|
}
|
|
|
|
if (this.IsFinished())
|
|
return;
|
|
|
|
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
|
|
var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(this.owner), IID_Player);
|
|
|
|
for (var r in this.costs)
|
|
{
|
|
var scaled = Math.floor(this.costs[r] * (1.0 - this.GetBuildProgress()));
|
|
if (scaled)
|
|
{
|
|
cmpPlayer.AddResource(r, scaled);
|
|
var cmpStatisticsTracker = QueryPlayerIDInterface(this.owner, IID_StatisticsTracker);
|
|
if (cmpStatisticsTracker)
|
|
cmpStatisticsTracker.IncreaseResourceUsedCounter(r, -scaled);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Adds a builder to the counter.
|
|
*/
|
|
Foundation.prototype.AddBuilder = function(builderEnt)
|
|
{
|
|
if (this.builders.indexOf(builderEnt) === -1)
|
|
{
|
|
this.builders.push(builderEnt);
|
|
Engine.QueryInterface(this.entity, IID_Visual).SetVariable("numbuilders", this.builders.length);
|
|
this.SetBuildMultiplier();
|
|
Engine.PostMessage(this.entity, MT_FoundationBuildersChanged, { "to": this.builders });
|
|
}
|
|
};
|
|
|
|
Foundation.prototype.RemoveBuilder = function(builderEnt)
|
|
{
|
|
if (this.builders.indexOf(builderEnt) !== -1)
|
|
{
|
|
this.builders.splice(this.builders.indexOf(builderEnt),1);
|
|
Engine.QueryInterface(this.entity, IID_Visual).SetVariable("numbuilders", this.builders.length);
|
|
this.SetBuildMultiplier();
|
|
Engine.PostMessage(this.entity, MT_FoundationBuildersChanged, { "to": this.builders });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Sets the build rate multiplier, which is applied to all builders.
|
|
* Yields a total rate of construction equal to numBuilders^0.7
|
|
*/
|
|
Foundation.prototype.SetBuildMultiplier = function()
|
|
{
|
|
var numBuilders = this.builders.length;
|
|
if (numBuilders == 0)
|
|
this.buildMultiplier = 1;
|
|
else
|
|
this.buildMultiplier = Math.pow(numBuilders, 0.7) / numBuilders;
|
|
};
|
|
|
|
/**
|
|
* 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.GetBuildProgress() == 1.0)
|
|
return;
|
|
|
|
// Handle the initial 'committing' of the foundation
|
|
if (!this.committed)
|
|
{
|
|
var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
|
|
if (cmpObstruction && cmpObstruction.GetBlockMovementFlag())
|
|
{
|
|
// If there's any units in the way, ask them to move away
|
|
// and return early from this method.
|
|
// Otherwise enable this obstruction so it blocks any further
|
|
// units, and continue building.
|
|
|
|
var collisions = cmpObstruction.GetEntityCollisions(true, true);
|
|
if (collisions.length)
|
|
{
|
|
var cmpFoundationOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
|
|
for each (var ent in collisions)
|
|
{
|
|
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
|
|
if (cmpUnitAI)
|
|
cmpUnitAI.LeaveFoundation(this.entity);
|
|
else
|
|
{
|
|
// If obstructing fauna is gaia or our own but doesn't have UnitAI, just destroy it
|
|
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
|
|
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
|
|
if (cmpOwnership && cmpIdentity && cmpIdentity.HasClass("Animal")
|
|
&& (cmpOwnership.GetOwner() == 0 || cmpFoundationOwnership && cmpOwnership.GetOwner() == cmpFoundationOwnership.GetOwner()))
|
|
Engine.DestroyEntity(ent);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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.
|
|
cmpObstruction.SetDisableBlockMovementPathfinding(false, false, -1);
|
|
}
|
|
|
|
// Switch foundation to scaffold variant
|
|
var cmpFoundationVisual = Engine.QueryInterface(this.entity, IID_Visual);
|
|
if (cmpFoundationVisual)
|
|
cmpFoundationVisual.SelectAnimation("scaffold", false, 1.0, "");
|
|
|
|
// Create preview entity and copy various parameters from the foundation
|
|
if (cmpFoundationVisual && cmpFoundationVisual.HasConstructionPreview())
|
|
{
|
|
this.previewEntity = Engine.AddEntity("construction|"+this.finalTemplateName);
|
|
var cmpFoundationOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
|
|
var cmpPreviewOwnership = Engine.QueryInterface(this.previewEntity, IID_Ownership);
|
|
cmpPreviewOwnership.SetOwner(cmpFoundationOwnership.GetOwner());
|
|
|
|
// Initially hide the preview underground
|
|
var cmpPreviewPosition = Engine.QueryInterface(this.previewEntity, IID_Position);
|
|
cmpPreviewPosition.SetConstructionProgress(0.0);
|
|
|
|
var cmpPreviewVisual = Engine.QueryInterface(this.previewEntity, IID_Visual);
|
|
if (cmpPreviewVisual)
|
|
{
|
|
cmpPreviewVisual.SetActorSeed(cmpFoundationVisual.GetActorSeed());
|
|
cmpPreviewVisual.SelectAnimation("scaffold", false, 1.0, "");
|
|
}
|
|
|
|
var cmpFoundationPosition = Engine.QueryInterface(this.entity, IID_Position);
|
|
var pos = cmpFoundationPosition.GetPosition();
|
|
var rot = cmpFoundationPosition.GetRotation();
|
|
cmpPreviewPosition.SetYRotation(rot.y);
|
|
cmpPreviewPosition.SetXZRotation(rot.x, rot.z);
|
|
cmpPreviewPosition.JumpTo(pos.x, pos.z);
|
|
}
|
|
|
|
this.committed = true;
|
|
}
|
|
|
|
// Calculate the amount of progress that will be added (where 1.0 = completion)
|
|
var cmpCost = Engine.QueryInterface(this.entity, IID_Cost);
|
|
var amount = work / cmpCost.GetBuildTime();
|
|
|
|
// Add an appropriate proportion of hitpoints
|
|
var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
|
|
var maxHealth = cmpHealth.GetMaxHitpoints();
|
|
var deltaHP = Math.max(work, Math.min(maxHealth, Math.floor(maxHealth * (amount * this.buildMultiplier))));
|
|
if (deltaHP > 0)
|
|
{
|
|
cmpHealth.Increase(deltaHP);
|
|
}
|
|
|
|
if (this.GetBuildProgress() >= 1.0)
|
|
{
|
|
// Finished construction
|
|
|
|
// Create the real entity
|
|
var building = Engine.AddEntity(this.finalTemplateName);
|
|
|
|
// Copy various parameters from the foundation
|
|
|
|
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
|
|
var cmpBuildingVisual = Engine.QueryInterface(building, IID_Visual)
|
|
if (cmpVisual && cmpBuildingVisual)
|
|
cmpBuildingVisual.SetActorSeed(cmpVisual.GetActorSeed());
|
|
|
|
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
|
var cmpBuildingPosition = Engine.QueryInterface(building, IID_Position);
|
|
var pos = cmpPosition.GetPosition();
|
|
cmpBuildingPosition.JumpTo(pos.x, pos.z);
|
|
var rot = cmpPosition.GetRotation();
|
|
cmpBuildingPosition.SetYRotation(rot.y);
|
|
cmpBuildingPosition.SetXZRotation(rot.x, rot.z);
|
|
// TODO: should add a ICmpPosition::CopyFrom() instead of all this
|
|
|
|
var cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint);
|
|
var cmpBuildingRallyPoint = Engine.QueryInterface(building, IID_RallyPoint);
|
|
if(cmpRallyPoint && cmpBuildingRallyPoint)
|
|
{
|
|
var rallyCoords = cmpRallyPoint.GetPositions();
|
|
var rallyData = cmpRallyPoint.GetData();
|
|
for (var i = 0; i < rallyCoords.length; ++i)
|
|
{
|
|
cmpBuildingRallyPoint.AddPosition(rallyCoords[i].x, rallyCoords[i].z);
|
|
cmpBuildingRallyPoint.AddData(rallyData[i]);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
|
|
var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership);
|
|
cmpBuildingOwnership.SetOwner(cmpOwnership.GetOwner());
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
// Copy over the obstruction control group IDs from the foundation entities. This is needed to ensure that when a foundation
|
|
// is completed and replaced by a new entity, it remains in the same control group(s) as any other foundation entities that
|
|
// may surround it. This is the mechanism that is used to e.g. enable wall pieces to be built closely together, ignoring their
|
|
// mutual obstruction shapes (since they would otherwise be prevented from being built so closely together). If the control
|
|
// groups are not copied over, the new entity will default to a new control group containing only itself, and will hence block
|
|
// construction of any surrounding foundations that it was previously in the same control group with.
|
|
|
|
// Note that this will result in the completed building entities having control group IDs that equal entity IDs of old (and soon
|
|
// to be deleted) foundation entities. This should not have any consequences, however, since the control group IDs are only meant
|
|
// to be unique identifiers, which is still true when reusing the old ones.
|
|
|
|
var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
|
|
var cmpBuildingObstruction = Engine.QueryInterface(building, IID_Obstruction);
|
|
cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
|
|
cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
var cmpPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
|
|
cmpPlayerStatisticsTracker.IncreaseConstructedBuildingsCounter(building);
|
|
|
|
var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
|
|
var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health);
|
|
cmpBuildingHealth.SetHitpoints(cmpHealth.GetHitpoints());
|
|
|
|
PlaySound("constructed", building);
|
|
|
|
Engine.PostMessage(this.entity, MT_ConstructionFinished,
|
|
{ "entity": this.entity, "newentity": building });
|
|
Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: building });
|
|
|
|
Engine.DestroyEntity(this.entity);
|
|
}
|
|
};
|
|
|
|
Engine.RegisterComponentType(IID_Foundation, "Foundation", Foundation);
|
|
|