#include "precompiled.h" #include "Projectile.h" #include "ScriptObject.h" #include "graphics/GameView.h" #include "graphics/Model.h" #include "graphics/ObjectEntry.h" #include "graphics/ObjectManager.h" #include "graphics/Terrain.h" #include "graphics/Unit.h" #include "maths/Matrix3D.h" #include "ps/CLogger.h" #include "ps/Game.h" #include "simulation/Collision.h" #include "simulation/Entity.h" const double GRAVITY = 0.00005; const double GRAVITY_2 = GRAVITY * 0.5; CProjectile::CProjectile( const CModel* Actor, const CVector3D& Position, const CVector3D& Target, float Speed, CEntity* Originator, const CScriptObject& ImpactScript, const CScriptObject& MissScript ) { m_Actor = Actor->Clone(); m_Position = m_Position_Previous = m_Position_Graphics = Position; m_Speed_H = Speed; m_Originator = Originator; m_ImpactEventHandler = ImpactScript; m_MissEventHandler = MissScript; AddHandler( EVENT_IMPACT, &m_ImpactEventHandler ); AddHandler( EVENT_MISS, &m_MissEventHandler ); // That was the easy stuff. // We want horizontal distance only: m_Axis = Target - Position; double s = m_Axis.Length(); m_Axis /= (float)s; // Now vertical distance: double d_h = Target.Y - Position.Y; // Time of impact: double t = s / m_Speed_H; // Required vertical velocity at launch: m_Speed_V = (float)( d_h / t + GRAVITY_2 * t ); } CProjectile::~CProjectile() { CProjectileManager& projectileManager = g_Game->GetWorld()->GetProjectileManager(); std::vector::iterator it; for( it = projectileManager.m_Projectiles.begin(); it != projectileManager.m_Projectiles.end(); ++it ) if( *it == this ) { projectileManager.m_Projectiles.erase( it ); break; } delete( m_Actor ); } bool CProjectile::Update( size_t timestep_millis ) { m_Position_Previous = m_Position; m_Position.X += timestep_millis * m_Axis.x * m_Speed_H; m_Position.Z += timestep_millis * m_Axis.y * m_Speed_H; m_Position.Y += (float)( timestep_millis * ( m_Speed_V - timestep_millis * GRAVITY_2 ) ); m_Speed_V -= (float)( timestep_millis * GRAVITY ); float height = m_Position.Y - g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel( m_Position.X, m_Position.Z ); if( height < 0.0f ) { // We appear to have missed. CEventProjectileMiss evt( m_Originator, m_Position ); DispatchEvent( &evt ); // Not going to let this be cancelled. return( false ); } RayIntersects& r = GetProjectileIntersection( m_Position_Previous, m_Axis, timestep_millis * m_Speed_H ); RayIntersects::iterator it; for( it = r.begin(); it != r.end(); it++ ) { // Hit something? if( *it != m_Originator ) /* That wouldn't be fair at all... */ { // Low enough to hit it? if( height < (*it)->m_bounds->m_height ) { CEventProjectileImpact evt( m_Originator, *it, m_Position ); if( DispatchEvent( &evt ) ) return( false ); } } } return( true ); } void CProjectile::Interpolate( size_t timestep_millis ) { m_Position_Graphics.X = m_Position_Previous.X + timestep_millis * m_Speed_H * m_Axis.x; m_Position_Graphics.Z = m_Position_Previous.Z + timestep_millis * m_Speed_H * m_Axis.y; m_Position_Graphics.Y = (float)( m_Position_Previous.Y + timestep_millis * ( m_Speed_V - timestep_millis * GRAVITY_2 ) ); float dh_dt = (float)( m_Speed_V - timestep_millis * GRAVITY ); float scale = 1 / sqrt( m_Speed_H * m_Speed_H + dh_dt * dh_dt ); float scale2 = m_Speed_H * scale; float y = dh_dt * scale; CMatrix3D rotateInc; rotateInc.SetIdentity(); rotateInc._22 = rotateInc._33 = y; rotateInc._23 = -( rotateInc._32 = scale2 ); CMatrix3D rotateDir; rotateDir.SetIdentity(); rotateDir._11 = rotateDir._33 = m_Axis.y; rotateDir._31 = -( rotateDir._13 = m_Axis.x ); rotateInc.Concatenate( rotateDir ); rotateInc._14 = m_Position_Graphics.X; rotateInc._24 = m_Position_Graphics.Y; rotateInc._34 = m_Position_Graphics.Z; m_Actor->SetTransform( rotateInc ); } void CProjectile::ScriptingInit() { CJSObject::ScriptingInit( "Projectile", Construct, 4 ); } JSBool CProjectile::Construct( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval ) { debug_assert( argc >= 4 ); CStr ModelString; CVector3D Here, There; float Speed; CEntity* Temp, *Originator = NULL; CObjectEntry* oe = NULL; CModel* Model = NULL; CScriptObject Impact, Miss; const char* err = NULL; Temp = ToNative( argv[0] ); if(Temp) { Model = Temp->m_actor->GetObject()->m_ProjectileModel; if( !Model ) { err = "No projectile model is defined for that entity's actor."; goto fail; } } else if( !ToPrimitive( cx, argv[0], ModelString ) || NULL == ( oe = g_Game->GetView()->GetObjectManager().FindObject( ModelString ) ) || NULL == ( Model = oe->m_Model ) ) { err = "Invalid actor"; goto fail; } Temp = ToNative( argv[1] ); if(Temp) { // Use the position vector of this entity, add a bit (so the arrow doesn't appear out of the ground) // In future, find the appropriate position from the entity (location of a specific prop point?) Here = Temp->m_position; Here.Y = g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel( Here.X, Here.Z ) + 2.5f; } else if( !( ToPrimitive( cx, argv[1], Here ) ) ) { err = "Invalid vector"; goto fail; } Temp = ToNative( argv[2] ); if(Temp) { // Use the position vector of this entity. // TODO: Maybe: Correct for the movement of this entity. // Then again, that doesn't belong here. There = Temp->m_position; There.Y = g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel( There.X, There.Z ) + 2.5f; } else if( !( ToPrimitive( cx, argv[2], There ) ) ) { err = "Invalid vector"; goto fail; } Speed = ToPrimitive( cx, argv[3] ); if( Speed == 0.0f ) { // Either wasn't specified, or was zero. In either case, // can't allow it: div/0 errors in the physics err = "Invalid speed"; goto fail; } // Ignore errors in these last few and use the defaults if there's a problem. if( argc >= 5 ) Originator = ToNative( argv[4] ); if( argc >= 6 ) Impact = argv[5]; // Script to run on impact with an entity. if( argc >= 7 ) Miss = argv[6]; // Script to run on impact with the floor. { CProjectile* p = g_Game->GetWorld()->GetProjectileManager() .AddProjectile( Model, Here, There, Speed / 1000.0f, Originator, Impact, Miss ); *rval = ToJSVal( *p ); return( JS_TRUE ); } fail: *rval = JSVAL_NULL; JS_ReportError( cx, err ); return( JS_TRUE ); } CEventProjectileImpact::CEventProjectileImpact( CEntity* Originator, CEntity* Impact, const CVector3D& Position ) : CScriptEvent( L"ProjectileImpact", EVENT_IMPACT ) { m_Originator = Originator; m_Impact = Impact; m_Position = Position; AddLocalProperty( L"originator", &m_Originator, true ); AddLocalProperty( L"impacted", &m_Impact ); AddLocalProperty( L"position", &m_Position ); } CEventProjectileMiss::CEventProjectileMiss( CEntity* Originator, const CVector3D& Position ) : CScriptEvent( L"ProjectileMiss", EVENT_MISS ) { m_Originator = Originator; m_Position = Position; AddLocalProperty( L"originator", &m_Originator, true ); AddLocalProperty( L"position", &m_Position ); } CProjectileManager::CProjectileManager() { m_LastTurnLength = 0; } CProjectileManager::~CProjectileManager() { DeleteAll(); } void CProjectileManager::DeleteAll() { while( !( m_Projectiles.empty() ) ) delete( m_Projectiles[0] ); // removes itself from m_Projectiles } CProjectile* CProjectileManager::AddProjectile( const CModel* Actor, const CVector3D& Position, const CVector3D& Target, float Speed, CEntity* Originator, const CScriptObject& ImpactScript, const CScriptObject& MissScript ) { CProjectile* p = new CProjectile( Actor, Position, Target, Speed, Originator, ImpactScript, MissScript ); m_Projectiles.push_back( p ); return( p ); } void CProjectileManager::DeleteProjectile( CProjectile* p ) { delete( p ); } void CProjectileManager::UpdateAll( size_t timestep ) { m_LastTurnLength = timestep; for( size_t i = 0; i < m_Projectiles.size(); ) if( !( m_Projectiles[i]->Update( timestep ) ) ) delete( m_Projectiles[i] ); // removes itself from m_Projectiles else i++; } void CProjectileManager::InterpolateAll( double relativeOffset ) { size_t absoluteOffset = (size_t)( (double)m_LastTurnLength * relativeOffset ); std::vector::iterator it; for( it = m_Projectiles.begin(); it != m_Projectiles.end(); ++it ) (*it)->Interpolate( absoluteOffset ); }