2015-02-21 02:41:09 +01:00
/* Copyright (C) 2015 Wildfire Games.
2010-03-07 22:38:39 +01:00
* 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 "ICmpProjectileManager.h"
2012-05-20 01:07:41 +02:00
# include "ICmpObstruction.h"
# include "ICmpObstructionManager.h"
2010-03-07 22:38:39 +01:00
# include "ICmpPosition.h"
2010-09-23 15:09:35 +02:00
# include "ICmpRangeManager.h"
2011-03-04 01:43:48 +01:00
# include "ICmpTerrain.h"
2010-04-17 13:34:40 +02:00
# include "ICmpVisual.h"
2010-03-07 22:38:39 +01:00
# include "simulation2/MessageTypes.h"
# include "graphics/Frustum.h"
# include "graphics/Model.h"
# include "graphics/Unit.h"
# include "graphics/UnitManager.h"
# include "maths/Matrix3D.h"
# include "maths/Quaternion.h"
# include "maths/Vector3D.h"
2010-04-17 13:34:40 +02:00
# include "ps/CLogger.h"
2010-03-07 22:38:39 +01:00
# include "renderer/Scene.h"
2010-08-01 19:38:01 +02:00
// Time (in seconds) before projectiles that stuck in the ground are destroyed
2011-05-06 23:53:33 +02:00
const static float PROJECTILE_DECAY_TIME = 30.f ;
2010-08-01 19:38:01 +02:00
2010-03-07 22:38:39 +01:00
class CCmpProjectileManager : public ICmpProjectileManager
{
public :
static void ClassInit ( CComponentManager & componentManager )
{
componentManager . SubscribeToMessageType ( MT_Interpolate ) ;
componentManager . SubscribeToMessageType ( MT_RenderSubmit ) ;
}
DEFAULT_COMPONENT_ALLOCATOR ( ProjectileManager )
2010-04-23 18:09:03 +02:00
static std : : string GetSchema ( )
{
return " <a:component type='system'/><empty/> " ;
}
2011-01-16 15:08:38 +01:00
virtual void Init ( const CParamNode & UNUSED ( paramNode ) )
2010-03-07 22:38:39 +01:00
{
2011-04-02 14:51:42 +02:00
m_ActorSeed = 0 ;
2012-11-21 00:46:23 +01:00
m_NextId = 1 ;
2010-03-07 22:38:39 +01:00
}
2011-01-16 15:08:38 +01:00
virtual void Deinit ( )
2010-03-07 22:38:39 +01:00
{
2011-04-08 02:11:27 +02:00
for ( size_t i = 0 ; i < m_Projectiles . size ( ) ; + + i )
GetSimContext ( ) . GetUnitManager ( ) . DeleteUnit ( m_Projectiles [ i ] . unit ) ;
m_Projectiles . clear ( ) ;
2010-03-07 22:38:39 +01:00
}
2012-11-21 00:46:23 +01:00
virtual void Serialize ( ISerializer & serialize )
2010-03-07 22:38:39 +01:00
{
// Because this is just graphical effects, and because it's all non-deterministic floating point,
2012-11-21 00:46:23 +01:00
// we don't do much serialization here.
2010-03-07 22:38:39 +01:00
// (That means projectiles will vanish if you save/load - is that okay?)
2012-11-21 00:46:23 +01:00
// The attack code stores the id so that the projectile gets deleted when it hits the target
serialize . NumberU32_Unbounded ( " next id " , m_NextId ) ;
2010-03-07 22:38:39 +01:00
}
2012-11-21 00:46:23 +01:00
virtual void Deserialize ( const CParamNode & paramNode , IDeserializer & deserialize )
2010-03-07 22:38:39 +01:00
{
2011-01-16 15:08:38 +01:00
Init ( paramNode ) ;
2012-11-21 00:46:23 +01:00
// The attack code stores the id so that the projectile gets deleted when it hits the target
deserialize . NumberU32_Unbounded ( " next id " , m_NextId ) ;
2010-03-07 22:38:39 +01:00
}
2011-01-16 15:08:38 +01:00
virtual void HandleMessage ( const CMessage & msg , bool UNUSED ( global ) )
2010-03-07 22:38:39 +01:00
{
switch ( msg . GetType ( ) )
{
case MT_Interpolate :
{
const CMessageInterpolate & msgData = static_cast < const CMessageInterpolate & > ( msg ) ;
2012-06-06 21:37:03 +02:00
Interpolate ( msgData . deltaSimTime ) ;
2010-03-07 22:38:39 +01:00
break ;
}
case MT_RenderSubmit :
{
const CMessageRenderSubmit & msgData = static_cast < const CMessageRenderSubmit & > ( msg ) ;
2011-01-16 15:08:38 +01:00
RenderSubmit ( msgData . collector , msgData . frustum , msgData . culling ) ;
2010-03-07 22:38:39 +01:00
break ;
}
}
}
2016-01-23 16:17:56 +01:00
virtual uint32_t LaunchProjectileAtPoint ( entity_id_t source , const CFixedVector3D & target , fixed speed , fixed gravity )
2010-03-07 22:38:39 +01:00
{
2012-05-20 01:07:41 +02:00
return LaunchProjectile ( source , target , speed , gravity ) ;
2010-03-07 22:38:39 +01:00
}
2012-05-20 01:07:41 +02:00
virtual void RemoveProjectile ( uint32_t ) ;
2010-03-07 22:38:39 +01:00
private :
struct Projectile
{
CUnit * unit ;
2013-08-15 21:01:10 +02:00
CVector3D origin ;
2010-03-07 22:38:39 +01:00
CVector3D pos ;
2013-08-15 21:01:10 +02:00
CVector3D v ;
float time ;
float timeHit ;
2010-03-07 22:38:39 +01:00
float gravity ;
2011-03-04 01:43:48 +01:00
bool stopped ;
2012-05-20 01:07:41 +02:00
uint32_t id ;
2013-08-15 21:01:10 +02:00
CVector3D position ( float t )
{
float t2 = t ;
if ( t2 > timeHit )
t2 = timeHit + logf ( 1.f + t2 - timeHit ) ;
CVector3D ret ( origin ) ;
ret . X + = v . X * t2 ;
ret . Z + = v . Z * t2 ;
ret . Y + = v . Y * t2 - 0.5f * gravity * t * t ;
return ret ;
}
2010-03-07 22:38:39 +01:00
} ;
std : : vector < Projectile > m_Projectiles ;
2011-04-02 14:51:42 +02:00
uint32_t m_ActorSeed ;
2012-05-20 01:07:41 +02:00
2012-11-21 00:46:23 +01:00
uint32_t m_NextId ;
2011-04-02 14:51:42 +02:00
2012-05-20 01:07:41 +02:00
uint32_t LaunchProjectile ( entity_id_t source , CFixedVector3D targetPoint , fixed speed , fixed gravity ) ;
2010-03-07 22:38:39 +01:00
2012-05-20 01:07:41 +02:00
void AdvanceProjectile ( Projectile & projectile , float dt ) ;
2010-03-07 22:38:39 +01:00
2012-05-20 01:07:41 +02:00
void Interpolate ( float frameTime ) ;
2010-03-07 22:38:39 +01:00
2011-01-16 15:08:38 +01:00
void RenderSubmit ( SceneCollector & collector , const CFrustum & frustum , bool culling ) ;
2010-03-07 22:38:39 +01:00
} ;
REGISTER_COMPONENT_TYPE ( ProjectileManager )
2012-05-20 01:07:41 +02:00
uint32_t CCmpProjectileManager : : LaunchProjectile ( entity_id_t source , CFixedVector3D targetPoint , fixed speed , fixed gravity )
2010-03-07 22:38:39 +01:00
{
2012-11-21 00:46:23 +01:00
// This is network synced so don't use GUI checks before incrementing or it breaks any non GUI simulations
uint32_t currentId = m_NextId + + ;
2010-05-01 11:48:39 +02:00
if ( ! GetSimContext ( ) . HasUnitManager ( ) )
2012-11-21 00:46:23 +01:00
return currentId ; // do nothing if graphics are disabled
2010-03-07 22:38:39 +01:00
2012-02-08 03:46:15 +01:00
CmpPtr < ICmpVisual > cmpSourceVisual ( GetSimContext ( ) , source ) ;
if ( ! cmpSourceVisual )
2012-11-21 00:46:23 +01:00
return currentId ;
2010-04-17 13:34:40 +02:00
2012-02-08 03:46:15 +01:00
std : : wstring name = cmpSourceVisual - > GetProjectileActor ( ) ;
2010-04-17 13:34:40 +02:00
if ( name . empty ( ) )
{
2010-09-17 19:50:43 +02:00
// If the actor was actually loaded, complain that it doesn't have a projectile
2012-02-08 03:46:15 +01:00
if ( ! cmpSourceVisual - > GetActorShortName ( ) . empty ( ) )
2015-01-22 21:37:38 +01:00
LOGERROR ( " Unit with actor '%s' launched a projectile but has no actor on 'projectile' attachpoint " , utf8_from_wstring ( cmpSourceVisual - > GetActorShortName ( ) ) ) ;
2015-02-21 02:41:09 +01:00
return currentId ;
2010-04-17 13:34:40 +02:00
}
2010-03-07 22:38:39 +01:00
2013-08-15 21:01:10 +02:00
Projectile projectile ;
projectile . id = currentId ;
projectile . time = 0.f ;
projectile . stopped = false ;
projectile . gravity = gravity . ToFloat ( ) ;
projectile . origin = cmpSourceVisual - > GetProjectileLaunchPoint ( ) ;
if ( ! projectile . origin )
2010-06-05 02:49:14 +02:00
{
// If there's no explicit launch point, take a guess based on the entity position
CmpPtr < ICmpPosition > sourcePos ( GetSimContext ( ) , source ) ;
2012-02-08 03:46:15 +01:00
if ( ! sourcePos )
2015-02-21 02:41:09 +01:00
return currentId ;
2013-08-15 21:01:10 +02:00
projectile . origin = sourcePos - > GetPosition ( ) ;
projectile . origin . Y + = 3.f ;
2010-06-05 02:49:14 +02:00
}
2010-03-07 22:38:39 +01:00
2011-04-08 02:11:27 +02:00
std : : set < CStr > selections ;
projectile . unit = GetSimContext ( ) . GetUnitManager ( ) . CreateUnit ( name , m_ActorSeed + + , selections ) ;
2013-08-15 21:01:10 +02:00
if ( ! projectile . unit ) // The error will have already been logged
2015-02-21 02:41:09 +01:00
return currentId ;
2011-04-08 02:11:27 +02:00
2013-08-15 21:01:10 +02:00
projectile . pos = projectile . origin ;
CVector3D offset ( targetPoint ) ;
offset - = projectile . pos ;
2011-10-23 14:27:34 +02:00
float horizDistance = sqrtf ( offset . X * offset . X + offset . Z * offset . Z ) ;
2013-08-15 21:01:10 +02:00
projectile . timeHit = horizDistance / speed . ToFloat ( ) ;
projectile . v = offset * ( 1.f / projectile . timeHit ) ;
2010-03-07 22:38:39 +01:00
2013-08-15 21:01:10 +02:00
projectile . v . Y = offset . Y / projectile . timeHit + 0.5f * projectile . gravity * projectile . timeHit ;
2010-03-07 22:38:39 +01:00
m_Projectiles . push_back ( projectile ) ;
2012-05-20 01:07:41 +02:00
return projectile . id ;
2010-03-07 22:38:39 +01:00
}
2012-05-20 01:07:41 +02:00
void CCmpProjectileManager : : AdvanceProjectile ( Projectile & projectile , float dt )
2010-03-07 22:38:39 +01:00
{
2013-08-15 21:01:10 +02:00
projectile . time + = dt ;
if ( projectile . stopped )
return ;
CVector3D delta ;
if ( dt < 0.1f )
delta = projectile . pos ;
else // For big dt delta is unprecise
delta = projectile . position ( projectile . time - 0.1f ) ;
projectile . pos = projectile . position ( projectile . time ) ;
2010-03-07 22:38:39 +01:00
2013-08-15 21:01:10 +02:00
delta = projectile . pos - delta ;
2010-03-07 22:38:39 +01:00
2011-03-04 01:43:48 +01:00
// If we've passed the target position and haven't stopped yet,
// carry on until we reach solid land
2013-08-15 21:01:10 +02:00
if ( projectile . time > = projectile . timeHit )
2010-08-01 19:38:01 +02:00
{
2013-09-11 22:41:53 +02:00
CmpPtr < ICmpTerrain > cmpTerrain ( GetSystemEntity ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( cmpTerrain )
2011-03-04 01:43:48 +01:00
{
float h = cmpTerrain - > GetExactGroundLevel ( projectile . pos . X , projectile . pos . Z ) ;
if ( projectile . pos . Y < h )
{
projectile . pos . Y = h ; // stick precisely to the terrain
projectile . stopped = true ;
}
}
2010-08-01 19:38:01 +02:00
}
2010-03-07 22:38:39 +01:00
// Construct a rotation matrix so that (0,1,0) is in the direction of 'delta'
CVector3D up ( 0 , 1 , 0 ) ;
delta . Normalize ( ) ;
CVector3D axis = up . Cross ( delta ) ;
if ( axis . LengthSquared ( ) < 0.0001f )
axis = CVector3D ( 1 , 0 , 0 ) ; // if up & delta are almost collinear, rotate around some other arbitrary axis
else
axis . Normalize ( ) ;
2011-08-16 13:18:32 +02:00
float angle = acosf ( up . Dot ( delta ) ) ;
2011-03-04 01:43:48 +01:00
CMatrix3D transform ;
2010-03-07 22:38:39 +01:00
CQuaternion quat ;
quat . FromAxisAngle ( axis , angle ) ;
quat . ToMatrix ( transform ) ;
// Then apply the translation
transform . Translate ( projectile . pos ) ;
// Move the model
2010-04-17 13:44:08 +02:00
projectile . unit - > GetModel ( ) . SetTransform ( transform ) ;
2010-03-07 22:38:39 +01:00
}
2012-05-20 01:07:41 +02:00
void CCmpProjectileManager : : Interpolate ( float frameTime )
2010-03-07 22:38:39 +01:00
{
for ( size_t i = 0 ; i < m_Projectiles . size ( ) ; + + i )
{
2012-05-20 01:07:41 +02:00
AdvanceProjectile ( m_Projectiles [ i ] , frameTime ) ;
2010-03-07 22:38:39 +01:00
}
// Remove the ones that have reached their target
for ( size_t i = 0 ; i < m_Projectiles . size ( ) ; )
{
2010-08-01 19:38:01 +02:00
// Projectiles hitting targets get removed immediately.
// Those hitting the ground stay for a while, because it looks pretty.
2013-08-15 21:01:10 +02:00
if ( m_Projectiles [ i ] . stopped )
2010-03-07 22:38:39 +01:00
{
2013-08-15 21:01:10 +02:00
if ( m_Projectiles [ i ] . time - m_Projectiles [ i ] . timeHit > PROJECTILE_DECAY_TIME )
2010-08-01 19:38:01 +02:00
{
// Delete in-place by swapping with the last in the list
std : : swap ( m_Projectiles [ i ] , m_Projectiles . back ( ) ) ;
2011-04-08 02:11:27 +02:00
GetSimContext ( ) . GetUnitManager ( ) . DeleteUnit ( m_Projectiles . back ( ) . unit ) ;
2010-08-01 19:38:01 +02:00
m_Projectiles . pop_back ( ) ;
continue ; // don't increment i
}
2010-03-07 22:38:39 +01:00
}
2010-08-01 19:38:01 +02:00
+ + i ;
2010-03-07 22:38:39 +01:00
}
}
2012-05-20 01:07:41 +02:00
void CCmpProjectileManager : : RemoveProjectile ( uint32_t id )
{
2012-11-21 00:46:23 +01:00
// Scan through the projectile list looking for one with the correct id to remove
for ( size_t i = 0 ; i < m_Projectiles . size ( ) ; i + + )
{
if ( m_Projectiles [ i ] . id = = id )
{
// 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 ( ) ;
return ;
}
}
2012-05-20 01:07:41 +02:00
}
2011-01-16 15:08:38 +01:00
void CCmpProjectileManager : : RenderSubmit ( SceneCollector & collector , const CFrustum & frustum , bool culling )
2010-03-07 22:38:39 +01:00
{
2013-09-11 22:41:53 +02:00
CmpPtr < ICmpRangeManager > cmpRangeManager ( GetSystemEntity ( ) ) ;
2010-09-23 15:09:35 +02:00
int player = GetSimContext ( ) . GetCurrentDisplayedPlayer ( ) ;
2015-12-28 17:27:31 +01:00
ICmpRangeManager : : CLosQuerier los ( cmpRangeManager - > GetLosQuerier ( player ) ) ;
2010-09-23 15:09:35 +02:00
bool losRevealAll = cmpRangeManager - > GetLosRevealAll ( player ) ;
2010-03-07 22:38:39 +01:00
for ( size_t i = 0 ; i < m_Projectiles . size ( ) ; + + i )
{
2010-09-23 15:09:35 +02:00
// Don't display projectiles outside the visible area
2012-01-12 13:51:10 +01:00
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 ) ;
2010-09-23 15:09:35 +02:00
if ( ! losRevealAll & & ! los . IsVisible ( posi , posj ) )
continue ;
2011-03-13 20:22:05 +01:00
CModelAbstract & model = m_Projectiles [ i ] . unit - > GetModel ( ) ;
2010-03-07 22:38:39 +01:00
2010-04-17 13:44:08 +02:00
model . ValidatePosition ( ) ;
2010-03-07 22:38:39 +01:00
2015-12-28 17:27:31 +01:00
if ( culling & & ! frustum . IsBoxVisible ( model . GetWorldBoundsRec ( ) ) )
2010-03-07 22:38:39 +01:00
continue ;
// TODO: do something about LOS (copy from CCmpVisualActor)
2010-04-17 13:44:08 +02:00
collector . SubmitRecursive ( & model ) ;
2010-03-07 22:38:39 +01:00
}
}