# Add corpse decay and building collapse.
Make 'delete' command kill the unit instead of destroying it. Make projectiles vanish after a timeout. Fix projectile landing positions with low framerates. This was SVN commit r7837.
This commit is contained in:
parent
fdb8be2fd5
commit
41ad5bd965
@ -63,7 +63,12 @@ Health.prototype.SetHitpoints = function(value)
|
||||
return;
|
||||
|
||||
this.hitpoints = Math.max(1, Math.min(this.GetMaxHitpoints(), value));
|
||||
}
|
||||
};
|
||||
|
||||
Health.prototype.Kill = function()
|
||||
{
|
||||
this.Reduce(this.hitpoints);
|
||||
};
|
||||
|
||||
Health.prototype.Reduce = function(amount)
|
||||
{
|
||||
@ -88,7 +93,7 @@ Health.prototype.Reduce = function(amount)
|
||||
{
|
||||
this.hitpoints -= amount;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Health.prototype.Increase = function(amount)
|
||||
{
|
||||
@ -97,7 +102,7 @@ Health.prototype.Increase = function(amount)
|
||||
return;
|
||||
|
||||
this.hitpoints = Math.min(this.hitpoints + amount, this.GetMaxHitpoints());
|
||||
}
|
||||
};
|
||||
|
||||
//// Private functions ////
|
||||
|
||||
@ -106,9 +111,7 @@ Health.prototype.CreateCorpse = function()
|
||||
// Create a static local version of the current entity
|
||||
var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
|
||||
var templateName = cmpTempMan.GetCurrentTemplateName(this.entity);
|
||||
var corpse = Engine.AddLocalEntity("preview|" + templateName);
|
||||
// (Maybe this should be some kind of "corpse|" instead of "preview|", if we want
|
||||
// to add things like corpse-removal timers and change the terrain conformance mode)
|
||||
var corpse = Engine.AddLocalEntity("corpse|" + templateName);
|
||||
|
||||
// Copy various parameters so it looks just like us
|
||||
|
||||
|
@ -125,11 +125,17 @@ function ProcessCommand(player, cmd)
|
||||
break;
|
||||
|
||||
case "delete-entity":
|
||||
// Verify the player owns the unit
|
||||
var cmpOwnership = Engine.QueryInterface(cmd.entity, IID_Ownership);
|
||||
var entityOwner = cmpOwnership.GetOwner()
|
||||
if (!cmpOwnership || cmpOwnership.GetOwner() != player)
|
||||
break;
|
||||
|
||||
if (player == entityOwner)
|
||||
var cmpHealth = Engine.QueryInterface(cmd.entity, IID_Health);
|
||||
if (cmpHealth)
|
||||
cmpHealth.Kill();
|
||||
else
|
||||
Engine.DestroyEntity(cmd.entity);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -21,9 +21,15 @@
|
||||
</Resources>
|
||||
</Cost>
|
||||
<Health>
|
||||
<DeathType>vanish</DeathType>
|
||||
<DeathType>corpse</DeathType>
|
||||
<Repairable>true</Repairable>
|
||||
</Health>
|
||||
<Decay>
|
||||
<Inactive/>
|
||||
<DelayTime>0.0</DelayTime>
|
||||
<SinkRate>4.0</SinkRate>
|
||||
<SinkAccel>9.8</SinkAccel>
|
||||
</Decay>
|
||||
<Armour>
|
||||
<Hack>10.0</Hack>
|
||||
<Pierce>42.0</Pierce>
|
||||
|
@ -22,6 +22,12 @@
|
||||
<Max>100</Max>
|
||||
<Healable>true</Healable>
|
||||
</Health>
|
||||
<Decay>
|
||||
<Inactive/>
|
||||
<DelayTime>15.0</DelayTime>
|
||||
<SinkRate>0.05</SinkRate>
|
||||
<SinkAccel>0.0</SinkAccel>
|
||||
</Decay>
|
||||
<Armour>
|
||||
<Hack>0.0</Hack>
|
||||
<Pierce>0.0</Pierce>
|
||||
|
@ -11,4 +11,10 @@
|
||||
<Healable>false</Healable>
|
||||
<Repairable>true</Repairable>
|
||||
</Health>
|
||||
<Decay>
|
||||
<Inactive/>
|
||||
<DelayTime>0.0</DelayTime>
|
||||
<SinkRate>0.0</SinkRate>
|
||||
<SinkAccel>2.0</SinkAccel>
|
||||
</Decay>
|
||||
</Entity>
|
||||
|
@ -6,8 +6,6 @@
|
||||
<Position>
|
||||
<Floating>true</Floating>
|
||||
</Position>
|
||||
<Health>
|
||||
</Health>
|
||||
<Attack>
|
||||
<Charge>
|
||||
<Hack>0.0</Hack>
|
||||
|
@ -56,6 +56,9 @@ COMPONENT(UnknownScript)
|
||||
INTERFACE(CommandQueue)
|
||||
COMPONENT(CommandQueue)
|
||||
|
||||
INTERFACE(Decay)
|
||||
COMPONENT(Decay)
|
||||
|
||||
INTERFACE(Footprint)
|
||||
COMPONENT(Footprint)
|
||||
|
||||
|
179
source/simulation2/components/CCmpDecay.cpp
Normal file
179
source/simulation2/components/CCmpDecay.cpp
Normal file
@ -0,0 +1,179 @@
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "simulation2/system/Component.h"
|
||||
#include "ICmpDecay.h"
|
||||
|
||||
#include "simulation2/MessageTypes.h"
|
||||
|
||||
#include "ICmpPosition.h"
|
||||
#include "ICmpTerrain.h"
|
||||
#include "ICmpVisual.h"
|
||||
|
||||
/**
|
||||
* Fairly basic decay implementation, for units and buildings etc.
|
||||
* The decaying entity remains stationary for some time period, then falls downwards
|
||||
* with some initial speed and some acceleration, until it has fully sunk.
|
||||
* The sinking depth is determined from the actor's bounding box and the terrain.
|
||||
*
|
||||
* This currently doesn't work with entities whose ICmpPosition has an initial Y offset.
|
||||
*
|
||||
* This isn't very efficient (we'll store data and iterate every frame for every entity,
|
||||
* not just for corpse entities) - it could be designed more optimally if that's a real problem.
|
||||
*
|
||||
* Eventually we might want to adjust the decay rate based on user configuration (low-spec
|
||||
* machines could have fewer corpses), number of corpses, etc.
|
||||
*
|
||||
* Must not be used on network-synchronised entities, unless \<Inactive\> is present.
|
||||
*/
|
||||
class CCmpDecay : public ICmpDecay
|
||||
{
|
||||
public:
|
||||
static void ClassInit(CComponentManager& componentManager)
|
||||
{
|
||||
componentManager.SubscribeToMessageType(MT_Interpolate);
|
||||
}
|
||||
|
||||
DEFAULT_COMPONENT_ALLOCATOR(Decay)
|
||||
|
||||
bool m_Active;
|
||||
float m_DelayTime;
|
||||
float m_SinkRate;
|
||||
float m_SinkAccel;
|
||||
|
||||
float m_CurrentTime;
|
||||
float m_TotalSinkDepth; // distance we need to sink (derived from bounding box), or -1 if undetermined
|
||||
|
||||
static std::string GetSchema()
|
||||
{
|
||||
return
|
||||
"<element name='DelayTime' a:help='Time to wait before starting to sink, in seconds'>"
|
||||
"<ref name='nonNegativeDecimal'/>"
|
||||
"</element>"
|
||||
"<element name='SinkRate' a:help='Initial rate of sinking, in metres per second'>"
|
||||
"<ref name='nonNegativeDecimal'/>"
|
||||
"</element>"
|
||||
"<element name='SinkAccel' a:help='Acceleration rate of sinking, in metres per second per second'>"
|
||||
"<ref name='nonNegativeDecimal'/>"
|
||||
"</element>"
|
||||
"<optional>"
|
||||
"<element name='Inactive' a:help='If this element is present, the entity will not do any decaying'>"
|
||||
"<empty/>"
|
||||
"</element>"
|
||||
"</optional>";
|
||||
}
|
||||
|
||||
virtual void Init(const CSimContext& UNUSED(context), const CParamNode& paramNode)
|
||||
{
|
||||
m_Active = !paramNode.GetChild("Inactive").IsOk();
|
||||
m_DelayTime = paramNode.GetChild("DelayTime").ToFixed().ToFloat();
|
||||
m_SinkRate = paramNode.GetChild("SinkRate").ToFixed().ToFloat();
|
||||
m_SinkAccel = paramNode.GetChild("SinkAccel").ToFixed().ToFloat();
|
||||
|
||||
m_CurrentTime = 0.f;
|
||||
m_TotalSinkDepth = -1.f;
|
||||
|
||||
// Detect unsafe misconfiguration
|
||||
if (m_Active && !ENTITY_IS_LOCAL(GetEntityId()))
|
||||
{
|
||||
debug_warn(L"CCmpDecay must not be used on non-local (network-synchronised) entities");
|
||||
m_Active = false;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Deinit(const CSimContext& UNUSED(context))
|
||||
{
|
||||
}
|
||||
|
||||
virtual void Serialize(ISerializer& UNUSED(serialize))
|
||||
{
|
||||
// This component isn't network-synchronised, so don't serialize anything
|
||||
}
|
||||
|
||||
virtual void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
|
||||
{
|
||||
Init(context, paramNode);
|
||||
}
|
||||
|
||||
virtual void HandleMessage(const CSimContext& UNUSED(context), const CMessage& msg, bool UNUSED(global))
|
||||
{
|
||||
switch (msg.GetType())
|
||||
{
|
||||
case MT_Interpolate:
|
||||
{
|
||||
if (!m_Active)
|
||||
break;
|
||||
|
||||
const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
|
||||
|
||||
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId());
|
||||
if (cmpPosition.null() || !cmpPosition->IsInWorld())
|
||||
{
|
||||
// If there's no position (this usually shouldn't happen), destroy the unit immediately
|
||||
GetSimContext().GetComponentManager().DestroyComponentsSoon(GetEntityId());
|
||||
break;
|
||||
}
|
||||
|
||||
// Compute the depth the first time this is called
|
||||
// (This is a bit of an ugly place to do it but at least we'll be sure
|
||||
// the actor component was loaded already)
|
||||
if (m_TotalSinkDepth < 0.f)
|
||||
{
|
||||
m_TotalSinkDepth = 1.f; // minimum so we always sink at least a little
|
||||
|
||||
CmpPtr<ICmpVisual> cmpVisual(GetSimContext(), GetEntityId());
|
||||
if (!cmpVisual.null())
|
||||
{
|
||||
CBound bound = cmpVisual->GetBounds();
|
||||
m_TotalSinkDepth = std::max(m_TotalSinkDepth, bound[1].Y - bound[0].Y);
|
||||
}
|
||||
|
||||
// If this is a floating unit, we want it to sink all the way under the terrain,
|
||||
// so find the difference between its current position and the terrain
|
||||
|
||||
CFixedVector3D pos = cmpPosition->GetPosition();
|
||||
|
||||
CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
|
||||
if (!cmpTerrain.null())
|
||||
{
|
||||
fixed ground = cmpTerrain->GetGroundLevel(pos.X, pos.Z);
|
||||
m_TotalSinkDepth += std::max(0.f, (pos.Y - ground).ToFloat());
|
||||
}
|
||||
}
|
||||
|
||||
m_CurrentTime += msgData.frameTime;
|
||||
|
||||
if (m_CurrentTime > m_DelayTime)
|
||||
{
|
||||
float t = m_CurrentTime - m_DelayTime;
|
||||
float depth = (m_SinkRate * t) + (m_SinkAccel * t * t);
|
||||
|
||||
cmpPosition->SetHeightOffset(entity_pos_t::FromFloat(-depth));
|
||||
|
||||
if (depth > m_TotalSinkDepth)
|
||||
GetSimContext().GetComponentManager().DestroyComponentsSoon(GetEntityId());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
REGISTER_COMPONENT_TYPE(Decay)
|
@ -34,6 +34,9 @@
|
||||
#include "ps/CLogger.h"
|
||||
#include "renderer/Scene.h"
|
||||
|
||||
// Time (in seconds) before projectiles that stuck in the ground are destroyed
|
||||
const static float PROJECTILE_DECAY_TIME = 60.f;
|
||||
|
||||
class CCmpProjectileManager : public ICmpProjectileManager
|
||||
{
|
||||
public:
|
||||
@ -234,7 +237,12 @@ void CCmpProjectileManager::AdvanceProjectile(const CSimContext& context, Projec
|
||||
// If we're really close to the target now, delete the remaining time so that we don't do a little
|
||||
// tiny numerically-imprecise movement next frame
|
||||
if (projectile.timeLeft < 0.01f)
|
||||
{
|
||||
projectile.timeLeft = 0;
|
||||
// Move exactly to the target, so we hit it precisely even if
|
||||
// the framerate is very low
|
||||
projectile.pos = projectile.target;
|
||||
}
|
||||
|
||||
// Construct a rotation matrix so that (0,1,0) is in the direction of 'delta'
|
||||
|
||||
@ -270,16 +278,26 @@ void CCmpProjectileManager::Interpolate(const CSimContext& context, float frameT
|
||||
// Remove the ones that have reached their target
|
||||
for (size_t i = 0; i < m_Projectiles.size(); )
|
||||
{
|
||||
// Only remove ones that were hitting entities - leave the ones that hit the ground, because
|
||||
// it looks pretty. (TODO: they should expire after some limit, not stay forever)
|
||||
if (m_Projectiles[i].timeLeft <= 0 && m_Projectiles[i].targetEnt != INVALID_ENTITY)
|
||||
// Projectiles hitting targets get removed immediately.
|
||||
// Those hitting the ground stay for a while, because it looks pretty.
|
||||
if (m_Projectiles[i].timeLeft <= 0.f)
|
||||
{
|
||||
// Delete in-place by swapping with the last in the list
|
||||
std::swap(m_Projectiles[i], m_Projectiles.back());
|
||||
m_Projectiles.pop_back();
|
||||
if (m_Projectiles[i].targetEnt == INVALID_ENTITY && m_Projectiles[i].timeLeft > -PROJECTILE_DECAY_TIME)
|
||||
{
|
||||
// AdvanceProjectile doesn't update timeLeft if the projectile's
|
||||
// already finished, so just update its timer here
|
||||
m_Projectiles[i].timeLeft -= frameTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Delete in-place by swapping with the last in the list
|
||||
std::swap(m_Projectiles[i], m_Projectiles.back());
|
||||
m_Projectiles.pop_back();
|
||||
continue; // don't increment i
|
||||
}
|
||||
}
|
||||
else
|
||||
++i;
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,7 +165,7 @@ private:
|
||||
|
||||
// Copy the non-interactive components of an entity template (position, actor, etc) into
|
||||
// a new entity template
|
||||
void CopyPreviewSubset(CParamNode& out, const CParamNode& in);
|
||||
void CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse);
|
||||
|
||||
// Copy the components of an entity necessary for a construction foundation
|
||||
// (position, actor, armour, health, etc) into a new entity template
|
||||
@ -264,7 +264,22 @@ bool CCmpTemplateManager::LoadTemplateFile(const std::string& templateName, int
|
||||
return false;
|
||||
}
|
||||
// Copy a subset to the requested template
|
||||
CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]);
|
||||
CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], false);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle special case "corpse|foo"
|
||||
if (templateName.find("corpse|") == 0)
|
||||
{
|
||||
// Load the base entity template, if it wasn't already loaded
|
||||
std::string baseName = templateName.substr(7);
|
||||
if (!LoadTemplateFile(baseName, depth+1))
|
||||
{
|
||||
LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
|
||||
return false;
|
||||
}
|
||||
// Copy a subset to the requested template
|
||||
CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -390,7 +405,7 @@ std::vector<std::wstring> CCmpTemplateManager::FindAllTemplates()
|
||||
return templates;
|
||||
}
|
||||
|
||||
void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& in)
|
||||
void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse)
|
||||
{
|
||||
// We only want to include components which are necessary (for the visual previewing of an entity)
|
||||
// and safe (i.e. won't do anything that affects the synchronised simulation state), so additions
|
||||
@ -401,6 +416,7 @@ void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& i
|
||||
permittedComponentTypes.insert("VisualActor");
|
||||
permittedComponentTypes.insert("Footprint");
|
||||
permittedComponentTypes.insert("Obstruction");
|
||||
permittedComponentTypes.insert("Decay");
|
||||
|
||||
// Need these for the Actor Viewer:
|
||||
permittedComponentTypes.insert("Attack");
|
||||
@ -416,6 +432,13 @@ void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& i
|
||||
// (but can still be used for testing this entity for collisions against others)
|
||||
if (out.GetChild("Entity").GetChild("Obstruction").IsOk())
|
||||
CParamNode::LoadXMLString(out, "<Entity><Obstruction><Inactive/></Obstruction></Entity>");
|
||||
|
||||
// Corpses should include decay components and un-inactivate them
|
||||
if (corpse)
|
||||
{
|
||||
if (out.GetChild("Entity").GetChild("Decay").IsOk())
|
||||
CParamNode::LoadXMLString(out, "<Entity><Decay><Inactive disable=''/></Decay></Entity>");
|
||||
}
|
||||
}
|
||||
|
||||
void CCmpTemplateManager::CopyFoundationSubset(CParamNode& out, const CParamNode& in)
|
||||
@ -433,6 +456,7 @@ void CCmpTemplateManager::CopyFoundationSubset(CParamNode& out, const CParamNode
|
||||
permittedComponentTypes.insert("Footprint");
|
||||
permittedComponentTypes.insert("Armour");
|
||||
permittedComponentTypes.insert("Health");
|
||||
permittedComponentTypes.insert("Decay");
|
||||
permittedComponentTypes.insert("Cost");
|
||||
|
||||
CParamNode::LoadXMLString(out, "<Entity/>");
|
||||
@ -454,4 +478,3 @@ void CCmpTemplateManager::CopyFoundationSubset(CParamNode& out, const CParamNode
|
||||
if (out.GetChild("Entity").GetChild("Cost").IsOk())
|
||||
CParamNode::LoadXMLString(out, "<Entity><Cost><PopulationBonus disable=''/></Cost></Entity>");
|
||||
}
|
||||
|
||||
|
25
source/simulation2/components/ICmpDecay.cpp
Normal file
25
source/simulation2/components/ICmpDecay.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "ICmpDecay.h"
|
||||
|
||||
#include "simulation2/system/InterfaceScripted.h"
|
||||
|
||||
BEGIN_INTERFACE_WRAPPER(Decay)
|
||||
END_INTERFACE_WRAPPER(Decay)
|
32
source/simulation2/components/ICmpDecay.h
Normal file
32
source/simulation2/components/ICmpDecay.h
Normal file
@ -0,0 +1,32 @@
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_ICMPDECAY
|
||||
#define INCLUDED_ICMPDECAY
|
||||
|
||||
#include "simulation2/system/Interface.h"
|
||||
|
||||
/**
|
||||
* Animated corpse-decay.
|
||||
*/
|
||||
class ICmpDecay : public IComponent
|
||||
{
|
||||
public:
|
||||
DECLARE_INTERFACE_TYPE(Decay)
|
||||
};
|
||||
|
||||
#endif // INCLUDED_ICMPDECAY
|
@ -47,6 +47,9 @@ public:
|
||||
* based on entity template "foo" with the non-graphical components removed.
|
||||
* (This is for previewing construction/placement of units.)
|
||||
*
|
||||
* If templateName is of the form "corpse|foo" then it will load a template
|
||||
* like "preview|foo" but with corpse-related components included.
|
||||
*
|
||||
* If templateName is of the form "foundation|foo" then it will load a template
|
||||
* based on entity template "foo" with various components removed and a few changed
|
||||
* and added. (This is for constructing foundations of buildings.)
|
||||
|
Loading…
Reference in New Issue
Block a user