Let the template define the actor used for the projectile. Also let projectiles have an impact animation (such as an explosion).

This will enable us in the future to have technologies that change
projectiles.
This is also somewhat of a refactoring.

Patch By: Mate-86
Reviewed By: wraitii
Trac Tickets: #1909

Differential Revision: https://code.wildfiregames.com/D945
This was SVN commit r20676.
This commit is contained in:
wraitii 2017-12-23 09:27:19 +00:00
parent 7d7a4a6b14
commit 95c03dcc64
10 changed files with 201 additions and 65 deletions

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<actor version="1">
<castshadow/>
<group>
<variant frequency="1" name="rock">
<mesh>props/onager_projectile.dae</mesh>
<props>
<prop actor="particle/flame_large.xml" attachpoint="root"/>
</props>
<textures>
<texture file="gaia/stone_temperate_granite.dds" name="baseTex"/>
</textures>
</variant>
</group>
</actor>

View File

@ -8,7 +8,9 @@
<prop actor="particle/smoke_catapult.xml" attachpoint="root"/>
<prop actor="particle/flame_catapult.xml" attachpoint="root"/>
</props>
<textures><texture file="gaia/stone_temperate_granite.dds" name="baseTex"/></textures>
<textures>
<texture file="gaia/stone_temperate_granite.dds" name="baseTex"/>
</textures>
</variant>
</group>
</actor>

View File

@ -81,6 +81,11 @@ Attack.prototype.Schema =
"<Multiplier>2</Multiplier>" +
"</Bonus1>" +
"</Bonuses>" +
"<Projectile>" +
"<ActorName>props/units/weapons/rock_flaming.xml</ActorName>" +
"<ImpactActorName>props/units/weapons/rock_explosion.xml</ImpactActorName>" +
"<ImpactAnimationLifetime>0.1</ImpactAnimationLifetime>" +
"</Projectile>" +
"<RestrictedClasses datatype=\"tokens\">Champion</RestrictedClasses>" +
"<Splash>" +
"<Shape>Circular</Shape>" +
@ -150,6 +155,27 @@ Attack.prototype.Schema =
Attack.prototype.bonusesSchema +
Attack.prototype.preferredClassesSchema +
Attack.prototype.restrictedClassesSchema +
"<optional>" +
"<element name='Projectile'>" +
"<interleave>" +
"<oneOrMore>" +
"<choice>" +
"<element name='ActorName' a:help='actor of the projectile animation'>" +
"<text/>" +
"</element>" +
"<interleave>" +
"<element name='ImpactActorName' a:help='actor of the projectile impact animation'>" +
"<text/>" +
"</element>" +
"<element name='ImpactAnimationLifetime' a:help='length of the projectile impact animation'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"</interleave>" +
"</choice>" +
"</oneOrMore>" +
"</interleave>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='Splash'>" +
"<interleave>" +
@ -503,7 +529,35 @@ Attack.prototype.PerformAttack = function(type, target)
// Launch the graphical projectile.
let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
let id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity);
let actorName = "";
let impactActorName = "";
let impactAnimationLifetime = 0;
if (this.template.Ranged.Projectile)
{
actorName = this.template.Ranged.Projectile.ActorName || "";
impactActorName = this.template.Ranged.Projectile.ImpactActorName || "";
impactAnimationLifetime = this.template.Ranged.Projectile.ImpactAnimationLifetime || 0;
}
let launchPoint = selfPosition.clone();
// TODO: remove this when all the ranged unit templates are updated with Projectile/Launchpoint
launchPoint.y += 3;
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (cmpVisual)
{
// if the projectile definition is missing from the template
// then fallback to the projectile name and launchpoint in the visual actor
if (!actorName)
actorName = cmpVisual.GetProjectileActor();
let visualActorLaunchPoint = cmpVisual.GetProjectileLaunchPoint();
if (visualActorLaunchPoint.length() > 0)
launchPoint = visualActorLaunchPoint;
}
let id = cmpProjectileManager.LaunchProjectileAtPoint(launchPoint, realTargetPosition, horizSpeed, gravity, actorName, impactActorName, impactAnimationLifetime);
let attackImpactSound = "";
let cmpSound = Engine.QueryInterface(this.entity, IID_Sound);

View File

@ -13,6 +13,10 @@
<RepeatTime>5000</RepeatTime>
<Spread>4.0</Spread>
<Delay>0</Delay>
<Projectile>
<ImpactActorName>props/units/weapons/rock_explosion.xml</ImpactActorName>
<ImpactAnimationLifetime>0.1</ImpactAnimationLifetime>
</Projectile>
<Splash>
<Shape>Circular</Shape>
<Range>10</Range>

View File

@ -25,7 +25,6 @@
#include "ICmpPosition.h"
#include "ICmpRangeManager.h"
#include "ICmpTerrain.h"
#include "ICmpVisual.h"
#include "simulation2/MessageTypes.h"
#include "graphics/Frustum.h"
@ -107,13 +106,16 @@ public:
}
}
virtual uint32_t LaunchProjectileAtPoint(entity_id_t source, const CFixedVector3D& target, fixed speed, fixed gravity)
virtual uint32_t LaunchProjectileAtPoint(const CFixedVector3D& launchPoint, const CFixedVector3D& target, fixed speed, fixed gravity, const std::wstring& actorName, const std::wstring& impactActorName, fixed impactAnimationLifetime)
{
return LaunchProjectile(source, target, speed, gravity);
return LaunchProjectile(launchPoint, target, speed, gravity, actorName, impactActorName, impactAnimationLifetime);
}
virtual void RemoveProjectile(uint32_t);
void RenderModel(CModelAbstract& model, const CVector3D& position, SceneCollector& collector, const CFrustum& frustum, bool culling,
ICmpRangeManager::CLosQuerier los, bool losRevealAll) const;
private:
struct Projectile
{
@ -124,8 +126,11 @@ private:
float time;
float timeHit;
float gravity;
bool stopped;
float impactAnimationLifetime;
uint32_t id;
std::wstring impactActorName;
bool isImpactAnimationCreated;
bool stopped;
CVector3D position(float t)
{
@ -141,13 +146,23 @@ private:
}
};
struct ProjectileImpactAnimation
{
CUnit* unit;
CVector3D pos;
float time;
};
std::vector<Projectile> m_Projectiles;
std::vector<ProjectileImpactAnimation> m_ProjectileImpactAnimations;
uint32_t m_ActorSeed;
uint32_t m_NextId;
uint32_t LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, fixed speed, fixed gravity);
uint32_t LaunchProjectile(CFixedVector3D launchPoint, CFixedVector3D targetPoint, fixed speed, fixed gravity,
const std::wstring& actorName, const std::wstring& impactActorName, fixed impactAnimationLifetime);
void AdvanceProjectile(Projectile& projectile, float dt) const;
@ -158,46 +173,36 @@ private:
REGISTER_COMPONENT_TYPE(ProjectileManager)
uint32_t CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, fixed speed, fixed gravity)
uint32_t CCmpProjectileManager::LaunchProjectile(CFixedVector3D launchPoint, CFixedVector3D targetPoint, fixed speed, fixed gravity, const std::wstring& actorName, const std::wstring& impactActorName, fixed impactAnimationLifetime)
{
// This is network synced so don't use GUI checks before incrementing or it breaks any non GUI simulations
uint32_t currentId = m_NextId++;
if (!GetSimContext().HasUnitManager())
if (!GetSimContext().HasUnitManager() || actorName.empty())
return currentId; // do nothing if graphics are disabled
CmpPtr<ICmpVisual> cmpSourceVisual(GetSimContext(), source);
if (!cmpSourceVisual)
return currentId;
std::wstring name = cmpSourceVisual->GetProjectileActor();
if (name.empty())
{
// If the actor was actually loaded, complain that it doesn't have a projectile
if (!cmpSourceVisual->GetActorShortName().empty())
LOGERROR("Unit with actor '%s' launched a projectile but has no actor on 'projectile' attachpoint", utf8_from_wstring(cmpSourceVisual->GetActorShortName()));
return currentId;
}
Projectile projectile;
projectile.id = currentId;
projectile.time = 0.f;
projectile.stopped = false;
projectile.gravity = gravity.ToFloat();
projectile.isImpactAnimationCreated = false;
projectile.origin = cmpSourceVisual->GetProjectileLaunchPoint();
if (!projectile.origin)
if (!impactActorName.empty())
{
// If there's no explicit launch point, take a guess based on the entity position
CmpPtr<ICmpPosition> sourcePos(GetSimContext(), source);
if (!sourcePos)
return currentId;
projectile.origin = sourcePos->GetPosition();
projectile.origin.Y += 3.f;
projectile.impactActorName = impactActorName;
projectile.impactAnimationLifetime = impactAnimationLifetime.ToFloat();
}
else
{
projectile.impactActorName = L"";
projectile.impactAnimationLifetime = 0.0f;
}
projectile.origin = launchPoint;
std::set<CStr> selections;
projectile.unit = GetSimContext().GetUnitManager().CreateUnit(name, m_ActorSeed++, selections);
projectile.unit = GetSimContext().GetUnitManager().CreateUnit(actorName, m_ActorSeed++, selections);
if (!projectile.unit) // The error will have already been logged
return currentId;
@ -282,22 +287,58 @@ void CCmpProjectileManager::Interpolate(float frameTime)
// Remove the ones that have reached their target
for (size_t i = 0; i < m_Projectiles.size(); )
{
// Projectiles hitting targets get removed immediately.
// Those hitting the ground stay for a while, because it looks pretty.
if (m_Projectiles[i].stopped)
if (!m_Projectiles[i].stopped)
{
if (m_Projectiles[i].time - m_Projectiles[i].timeHit > PROJECTILE_DECAY_TIME)
{
// Delete in-place by swapping with the last in the list
std::swap(m_Projectiles[i], m_Projectiles.back());
GetSimContext().GetUnitManager().DeleteUnit(m_Projectiles.back().unit);
m_Projectiles.pop_back();
continue; // don't increment i
}
++i;
continue;
}
if (!m_Projectiles[i].impactActorName.empty() && !m_Projectiles[i].isImpactAnimationCreated)
{
m_Projectiles[i].isImpactAnimationCreated = true;
CMatrix3D transform;
CQuaternion quat;
quat.ToMatrix(transform);
transform.Translate(m_Projectiles[i].pos);
std::set<CStr> selections;
CUnit* unit = GetSimContext().GetUnitManager().CreateUnit(m_Projectiles[i].impactActorName, m_ActorSeed++, selections);
unit->GetModel().SetTransform(transform);
ProjectileImpactAnimation projectileImpactAnimation;
projectileImpactAnimation.unit = unit;
projectileImpactAnimation.time = m_Projectiles[i].impactAnimationLifetime;
projectileImpactAnimation.pos = m_Projectiles[i].pos;
m_ProjectileImpactAnimations.push_back(projectileImpactAnimation);
}
// Projectiles hitting targets get removed immediately.
// Those hitting the ground stay for a while, because it looks pretty.
if (m_Projectiles[i].time - m_Projectiles[i].timeHit > PROJECTILE_DECAY_TIME)
{
// Delete in-place by swapping with the last in the list
std::swap(m_Projectiles[i], m_Projectiles.back());
GetSimContext().GetUnitManager().DeleteUnit(m_Projectiles.back().unit);
m_Projectiles.pop_back();
continue;
}
++i;
}
for (size_t i = 0; i < m_ProjectileImpactAnimations.size();)
{
if (m_ProjectileImpactAnimations[i].time > 0)
{
m_ProjectileImpactAnimations[i].time -= frameTime;
++i;
}
else
{
std::swap(m_ProjectileImpactAnimations[i], m_ProjectileImpactAnimations.back());
GetSimContext().GetUnitManager().DeleteUnit(m_ProjectileImpactAnimations.back().unit);
m_ProjectileImpactAnimations.pop_back();
}
}
}
void CCmpProjectileManager::RemoveProjectile(uint32_t id)
@ -316,6 +357,25 @@ void CCmpProjectileManager::RemoveProjectile(uint32_t id)
}
}
void CCmpProjectileManager::RenderModel(CModelAbstract& model, const CVector3D& position, SceneCollector& collector,
const CFrustum& frustum, bool culling, ICmpRangeManager::CLosQuerier los, bool losRevealAll) const
{
// Don't display objects outside the visible area
ssize_t posi = (ssize_t)(0.5f + position.X / TERRAIN_TILE_SIZE);
ssize_t posj = (ssize_t)(0.5f + position.Z / TERRAIN_TILE_SIZE);
if (!losRevealAll && !los.IsVisible(posi, posj))
return;
model.ValidatePosition();
if (culling && !frustum.IsBoxVisible(model.GetWorldBoundsRec()))
return;
// TODO: do something about LOS (copy from CCmpVisualActor)
collector.SubmitRecursive(&model);
}
void CCmpProjectileManager::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling) const
{
CmpPtr<ICmpRangeManager> cmpRangeManager(GetSystemEntity());
@ -323,23 +383,14 @@ void CCmpProjectileManager::RenderSubmit(SceneCollector& collector, const CFrust
ICmpRangeManager::CLosQuerier los(cmpRangeManager->GetLosQuerier(player));
bool losRevealAll = cmpRangeManager->GetLosRevealAll(player);
for (size_t i = 0; i < m_Projectiles.size(); ++i)
for (const Projectile& projectile : m_Projectiles)
{
// Don't display projectiles outside the visible area
ssize_t posi = (ssize_t)(0.5f + m_Projectiles[i].pos.X / TERRAIN_TILE_SIZE);
ssize_t posj = (ssize_t)(0.5f + m_Projectiles[i].pos.Z / TERRAIN_TILE_SIZE);
if (!losRevealAll && !los.IsVisible(posi, posj))
continue;
RenderModel(projectile.unit->GetModel(), projectile.pos, collector, frustum, culling, los, losRevealAll);
}
CModelAbstract& model = m_Projectiles[i].unit->GetModel();
model.ValidatePosition();
if (culling && !frustum.IsBoxVisible(model.GetWorldBoundsRec()))
continue;
// TODO: do something about LOS (copy from CCmpVisualActor)
collector.SubmitRecursive(&model);
for (const ProjectileImpactAnimation& projectileImpactAnimation : m_ProjectileImpactAnimations)
{
RenderModel(projectileImpactAnimation.unit->GetModel(), projectileImpactAnimation.pos,
collector, frustum, culling, los, losRevealAll);
}
}

View File

@ -387,10 +387,10 @@ public:
return m_Unit->GetObject().m_ProjectileModelName;
}
virtual CVector3D GetProjectileLaunchPoint() const
virtual CFixedVector3D GetProjectileLaunchPoint() const
{
if (!m_Unit)
return CVector3D();
return CFixedVector3D();
if (m_Unit->GetModel().ToCModel())
{
@ -407,10 +407,13 @@ public:
CModelAbstract* ammo = m_Unit->GetModel().ToCModel()->FindFirstAmmoProp();
if (ammo)
return ammo->GetTransform().GetTranslation();
{
CVector3D vector = ammo->GetTransform().GetTranslation();
return CFixedVector3D(fixed::FromFloat(vector.X), fixed::FromFloat(vector.Y), fixed::FromFloat(vector.Z));
}
}
return CVector3D();
return CFixedVector3D();
}
virtual void SetVariant(const CStr& key, const CStr& selection)

View File

@ -22,6 +22,6 @@
#include "simulation2/system/InterfaceScripted.h"
BEGIN_INTERFACE_WRAPPER(ProjectileManager)
DEFINE_INTERFACE_METHOD_4("LaunchProjectileAtPoint", uint32_t, ICmpProjectileManager, LaunchProjectileAtPoint, entity_id_t, CFixedVector3D, fixed, fixed)
DEFINE_INTERFACE_METHOD_7("LaunchProjectileAtPoint", uint32_t, ICmpProjectileManager, LaunchProjectileAtPoint, CFixedVector3D, CFixedVector3D, fixed, fixed, std::wstring, std::wstring, fixed)
DEFINE_INTERFACE_METHOD_1("RemoveProjectile", void, ICmpProjectileManager, RemoveProjectile, uint32_t)
END_INTERFACE_WRAPPER(ProjectileManager)

View File

@ -38,9 +38,12 @@ public:
* @param target target point
* @param speed horizontal speed in m/s
* @param gravity gravitational acceleration in m/s^2 (determines the height of the ballistic curve)
* @param actorName name of the flying projectile actor
* @param impactActorName name of the animation actor played when the projectile hits the target or the ground
* @param impactAnimationLifetime animation lenth
* @return id of the created projectile
*/
virtual uint32_t LaunchProjectileAtPoint(entity_id_t source, const CFixedVector3D& target, fixed speed, fixed gravity) = 0;
virtual uint32_t LaunchProjectileAtPoint(const CFixedVector3D& launchPoint, const CFixedVector3D& target, fixed speed, fixed gravity, const std::wstring& actorName, const std::wstring& impactActorName, fixed impactAnimationLifetime) = 0;
/**
* Removes a projectile, used when the projectile has hit a target

View File

@ -24,6 +24,8 @@
BEGIN_INTERFACE_WRAPPER(Visual)
DEFINE_INTERFACE_METHOD_2("SetVariant", void, ICmpVisual, SetVariant, CStr, CStr)
DEFINE_INTERFACE_METHOD_CONST_0("GetAnimationName", std::string, ICmpVisual, GetAnimationName)
DEFINE_INTERFACE_METHOD_CONST_0("GetProjectileActor", std::wstring, ICmpVisual, GetProjectileActor)
DEFINE_INTERFACE_METHOD_CONST_0("GetProjectileLaunchPoint", CFixedVector3D, ICmpVisual, GetProjectileLaunchPoint)
DEFINE_INTERFACE_METHOD_4("SelectAnimation", void, ICmpVisual, SelectAnimation, std::string, bool, fixed, std::wstring)
DEFINE_INTERFACE_METHOD_1("SelectMovementAnimation", void, ICmpVisual, SelectMovementAnimation, fixed)
DEFINE_INTERFACE_METHOD_1("ResetMoveAnimation", void, ICmpVisual, ResetMoveAnimation, std::string)

View File

@ -24,6 +24,7 @@
#include "maths/BoundingBoxOriented.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/Fixed.h"
#include "maths/FixedVector3D.h"
#include "lib/file/vfs/vfs_path.h"
class CUnit;
@ -68,9 +69,10 @@ public:
/**
* Return the exact position where a projectile should be launched from (based on the actor's
* ammo prop points).
* Return type is CFixedVector3D because it is exposed to the JS interface.
* Returns (0,0,0) if no point can be found.
*/
virtual CVector3D GetProjectileLaunchPoint() const = 0;
virtual CFixedVector3D GetProjectileLaunchPoint() const = 0;
/**
* Returns the underlying unit of this visual actor. May return NULL to indicate that no unit exists (e.g. may happen if the