#include "extdll.h" #include "util.h" #include "cbase.h" #include "trains.h" #include "saverestore.h" // --------------------------------------------------------------------- // // Sprite Train // // --------------------------------------------------------------------- class CFuncSpriteTrain : public CBaseEntity { public: void Spawn( void ); void Precache( void ); void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); void KeyValue( KeyValueData* pkvd ); void EXPORT Next( void ); void EXPORT Find( void ); void EXPORT NearestPath( void ); void EXPORT DeadEnd( void ); void NextThink( float thinkTime, BOOL alwaysThink ); void SetTrack( CPathTrack *track ) { m_ppath = track->Nearest(pev->origin); } void SetControls( entvars_t *pevControls ); BOOL OnControls( entvars_t *pev ); void Animate( float frames ); virtual int Save( CSave &save ); virtual int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DIRECTIONAL_USE; } virtual void OverrideReset( void ); CPathTrack *m_ppath; float m_length; float m_height; float m_speed; float m_dir; float m_startSpeed; Vector m_controlMins; Vector m_controlMaxs; float m_flBank; float m_oldSpeed; int m_scale; float m_lastTime; float m_maxFrame; }; TYPEDESCRIPTION CFuncSpriteTrain::m_SaveData[] = { DEFINE_FIELD( CFuncSpriteTrain, m_ppath, FIELD_CLASSPTR ), DEFINE_FIELD( CFuncSpriteTrain, m_length, FIELD_FLOAT ), DEFINE_FIELD( CFuncSpriteTrain, m_height, FIELD_FLOAT ), DEFINE_FIELD( CFuncSpriteTrain, m_speed, FIELD_FLOAT ), DEFINE_FIELD( CFuncSpriteTrain, m_dir, FIELD_FLOAT ), DEFINE_FIELD( CFuncSpriteTrain, m_startSpeed, FIELD_FLOAT ), DEFINE_FIELD( CFuncSpriteTrain, m_controlMins, FIELD_VECTOR ), DEFINE_FIELD( CFuncSpriteTrain, m_controlMaxs, FIELD_VECTOR ), DEFINE_FIELD( CFuncSpriteTrain, m_flBank, FIELD_FLOAT ), DEFINE_FIELD( CFuncSpriteTrain, m_oldSpeed, FIELD_FLOAT ), DEFINE_FIELD( CFuncSpriteTrain, m_maxFrame, FIELD_FLOAT ), DEFINE_FIELD( CFuncSpriteTrain, m_lastTime, FIELD_FLOAT ), }; IMPLEMENT_SAVERESTORE( CFuncSpriteTrain, CBaseEntity ); LINK_ENTITY_TO_CLASS( env_spritetrain , CFuncSpriteTrain); void CFuncSpriteTrain :: KeyValue( KeyValueData *pkvd ) { if (FStrEq(pkvd->szKeyName, "wheels"))//! { m_length = atof(pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "height"))//! { m_height = atof(pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "speed"))//op4 { m_startSpeed = atof(pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "bank"))//! { m_flBank = atof(pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "scale"))//op4 { m_scale = atof(pkvd->szValue); pkvd->fHandled = TRUE; } else CBaseEntity::KeyValue( pkvd ); } void CFuncSpriteTrain:: NextThink( float thinkTime, BOOL alwaysThink ) { if ( alwaysThink ) pev->flags |= FL_ALWAYSTHINK; else pev->flags &= ~FL_ALWAYSTHINK; pev->nextthink = thinkTime; } void CFuncSpriteTrain:: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { if ( useType != USE_SET ) { if ( !ShouldToggle( useType, (pev->speed != 0) ) ) return; if ( pev->speed == 0 ) { pev->speed = m_speed * m_dir; Next(); } else { pev->speed = 0; pev->velocity = g_vecZero; pev->avelocity = g_vecZero; SetThink( NULL ); } } else { float delta = value; delta = ((int)(pev->speed * 4) / (int)m_speed)*0.25 + 0.25 * delta; if ( delta > 1 ) delta = 1; else if ( delta < -1 ) delta = -1; if ( pev->spawnflags & SF_TRACKTRAIN_FORWARDONLY ) { if ( delta < 0 ) delta = 0; } pev->speed = m_speed * delta; Next(); ALERT( at_aiconsole, "TRAIN(%s), speed to %.2f\n", STRING(pev->targetname), pev->speed ); } } static float Fix( float angle ) { while ( angle < 0 ) angle += 360; while ( angle > 360 ) angle -= 360; return angle; } static void FixupAngles( Vector &v ) { v.x = Fix( v.x ); v.y = Fix( v.y ); v.z = Fix( v.z ); } #define TRAIN_STARTPITCH 60 #define TRAIN_MAXPITCH 200 #define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation void CFuncSpriteTrain:: Next( void ) { float time = 0.5; if ( !pev->speed ) { ALERT( at_aiconsole, "TRAIN(%s): Speed is 0\n", STRING(pev->targetname) ); return; } if ( !m_ppath ) { ALERT( at_aiconsole, "TRAIN(%s): Lost path\n", STRING(pev->targetname) ); return; } Vector nextPos = pev->origin; nextPos.z -= m_height; CPathTrack *pnext = m_ppath->LookAhead( &nextPos, pev->speed * 0.1, 1 ); nextPos.z += m_height; pev->velocity = (nextPos - pev->origin) * 10; Vector nextFront = pev->origin; nextFront.z -= m_height; if ( m_length > 0 ) m_ppath->LookAhead( &nextFront, m_length, 0 ); else m_ppath->LookAhead( &nextFront, 100, 0 ); nextFront.z += m_height; Vector delta = nextFront - pev->origin; Vector angles = UTIL_VecToAngles( delta ); // The train actually points west angles.y += 180; // !!! All of this crap has to be done to make the angles not wrap around, revisit this. FixupAngles( angles ); FixupAngles( pev->angles ); if ( !pnext || (delta.x == 0 && delta.y == 0) ) angles = pev->angles; float vy, vx; if ( !(pev->spawnflags & SF_TRACKTRAIN_NOPITCH) ) vx = UTIL_AngleDistance( angles.x, pev->angles.x ); else vx = 0; vy = UTIL_AngleDistance( angles.y, pev->angles.y ); pev->avelocity.y = vy * 10; pev->avelocity.x = vx * 10; if ( m_flBank != 0 ) { if ( pev->avelocity.y < -5 ) pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( -m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); else if ( pev->avelocity.y > 5 ) pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); else pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( 0, pev->angles.z, m_flBank*4 ), pev->angles.z) * 4; } if ( pnext ) { if ( pnext != m_ppath ) { CPathTrack *pFire; if ( pev->speed >= 0 ) pFire = pnext; else pFire = m_ppath; m_ppath = pnext; // Fire the pass target if there is one if ( pFire->pev->message ) { FireTargets( STRING(pFire->pev->message), this, this, USE_TOGGLE, 0 ); if ( FBitSet( pFire->pev->spawnflags, SF_PATH_FIREONCE ) ) pFire->pev->message = 0; } if ( pFire->pev->spawnflags & SF_PATH_DISABLE_TRAIN ) pev->spawnflags |= SF_TRACKTRAIN_NOCONTROL; // Don't override speed if under user control if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) { if ( pFire->pev->speed != 0 ) {// don't copy speed from target if it is 0 (uninitialized) pev->speed = pFire->pev->speed; ALERT( at_aiconsole, "TrackTrain %s speed to %4.2f\n", STRING(pev->targetname), pev->speed ); } } } SetThink( &CFuncSpriteTrain::Next ); NextThink( pev->ltime + time, TRUE ); } else // end of path, stop { pev->velocity = (nextPos - pev->origin); pev->avelocity = g_vecZero; float distance = pev->velocity.Length(); m_oldSpeed = pev->speed; pev->speed = 0; // Move to the dead end // Are we there yet? if ( distance > 0 ) { // no, how long to get there? time = distance / m_oldSpeed; pev->velocity = pev->velocity * (m_oldSpeed / distance); SetThink( &CFuncSpriteTrain::DeadEnd ); NextThink( pev->ltime + time, FALSE ); } else { DeadEnd(); } } //Animate( 1 );//was 1 - too fast Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); m_lastTime = gpGlobals->time; } void CFuncSpriteTrain::DeadEnd( void ) { // Fire the dead-end target if there is one CPathTrack *pTrack, *pNext; pTrack = m_ppath; ALERT( at_aiconsole, "TRAIN(%s): Dead end ", STRING(pev->targetname) ); // Find the dead end path node // HACKHACK -- This is bugly, but the train can actually stop moving at a different node depending on it's speed // so we have to traverse the list to it's end. if ( pTrack ) { if ( m_oldSpeed < 0 ) { do { pNext = pTrack->ValidPath( pTrack->GetPrevious(), TRUE ); if ( pNext ) pTrack = pNext; } while ( pNext ); } else { do { pNext = pTrack->ValidPath( pTrack->GetNext(), TRUE ); if ( pNext ) pTrack = pNext; } while ( pNext ); } } pev->velocity = g_vecZero; pev->avelocity = g_vecZero; if ( pTrack ) { ALERT( at_aiconsole, "at %s\n", STRING(pTrack->pev->targetname) ); if ( pTrack->pev->netname ) FireTargets( STRING(pTrack->pev->netname), this, this, USE_TOGGLE, 0 ); } else ALERT( at_aiconsole, "\n" ); } void CFuncSpriteTrain :: SetControls( entvars_t *pevControls ) { Vector offset = pevControls->origin - pev->oldorigin; m_controlMins = pevControls->mins + offset; m_controlMaxs = pevControls->maxs + offset; } BOOL CFuncSpriteTrain :: OnControls( entvars_t *pevTest ) { Vector offset = pevTest->origin - pev->origin; if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) return FALSE; // Transform offset into local coordinates UTIL_MakeVectors( pev->angles ); Vector local; local.x = DotProduct( offset, gpGlobals->v_forward ); local.y = -DotProduct( offset, gpGlobals->v_right ); local.z = DotProduct( offset, gpGlobals->v_up ); if ( local.x >= m_controlMins.x && local.y >= m_controlMins.y && local.z >= m_controlMins.z && local.x <= m_controlMaxs.x && local.y <= m_controlMaxs.y && local.z <= m_controlMaxs.z ) return TRUE; return FALSE; } void CFuncSpriteTrain :: Find( void ) { m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) )); if ( !m_ppath ) return; entvars_t *pevTarget = m_ppath->pev; if ( !FClassnameIs( pevTarget, "path_track" ) ) { ALERT( at_error, "func_track_train must be on a path of path_track\n" ); m_ppath = NULL; return; } Vector nextPos = pevTarget->origin; nextPos.z += m_height; Vector look = nextPos; look.z -= m_height; m_ppath->LookAhead( &look, m_length, 0 ); look.z += m_height; pev->angles = UTIL_VecToAngles( look - nextPos ); // The train actually points west pev->angles.y += 180; if ( pev->spawnflags & SF_TRACKTRAIN_NOPITCH ) pev->angles.x = 0; UTIL_SetOrigin( pev, nextPos ); NextThink( pev->ltime + 0.1, FALSE ); SetThink( &CFuncSpriteTrain::Next ); pev->speed = m_startSpeed; } void CFuncSpriteTrain :: NearestPath( void ) { CBaseEntity *pTrack = NULL; CBaseEntity *pNearest = NULL; float dist, closest; closest = 1024; while ((pTrack = UTIL_FindEntityInSphere( pTrack, pev->origin, 1024 )) != NULL) { // filter out non-tracks if ( !(pTrack->pev->flags & (FL_CLIENT|FL_MONSTER)) && FClassnameIs( pTrack->pev, "path_track" ) ) { dist = (pev->origin - pTrack->pev->origin).Length(); if ( dist < closest ) { closest = dist; pNearest = pTrack; } } } if ( !pNearest ) { ALERT( at_console, "Can't find a nearby track !!!\n" ); SetThink(NULL); return; } ALERT( at_aiconsole, "TRAIN: %s, Nearest track is %s\n", STRING(pev->targetname), STRING(pNearest->pev->targetname) ); // If I'm closer to the next path_track on this path, then it's my real path pTrack = ((CPathTrack *)pNearest)->GetNext(); if ( pTrack ) { if ( (pev->origin - pTrack->pev->origin).Length() < (pev->origin - pNearest->pev->origin).Length() ) pNearest = pTrack; } m_ppath = (CPathTrack *)pNearest; if ( pev->speed != 0 ) { NextThink( pev->ltime + 0.1, FALSE ); SetThink( &CFuncSpriteTrain::Next ); } } void CFuncSpriteTrain ::OverrideReset( void ) { NextThink( pev->ltime + 0.1, FALSE ); SetThink( &CFuncSpriteTrain::NearestPath ); } void CFuncSpriteTrain ::Animate( float frames ) { if ( m_maxFrame > 0 ) pev->frame = fmod( pev->frame + frames, m_maxFrame ); } void CFuncSpriteTrain :: Spawn( void ) { Precache(); if ( pev->speed == 0 ) m_speed = 100; else m_speed = pev->speed; pev->speed = 0; pev->velocity = g_vecZero; pev->avelocity = g_vecZero; pev->impulse = m_speed; m_dir = 1; pev->solid = SOLID_NOT; pev->movetype = MOVETYPE_PUSH; pev->rendercolor.x = 255; pev->rendercolor.y = 255; pev->rendercolor.z = 255; pev->scale = m_scale; pev->rendermode = kRenderTransAdd; pev->renderamt = 255; SET_MODEL( ENT(pev), STRING(pev->model) ); UTIL_SetOrigin( pev, pev->origin ); pev->oldorigin = pev->origin; m_controlMins = pev->mins; m_controlMaxs = pev->maxs; m_controlMaxs.z += 72; m_lastTime = gpGlobals->time; m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; NextThink( pev->ltime + 0.1, FALSE ); SetThink( &CFuncSpriteTrain::Find ); } void CFuncSpriteTrain :: Precache( void ) { PRECACHE_MODEL( (char *)STRING(pev->model) ); }