#include "precompiled.h" #pragma hdrstop #include "prey_local.h" //========================================================================== // // hhVehicleThruster // //========================================================================== CLASS_DECLARATION( idEntity, hhVehicleThruster ) EVENT( EV_Broadcast_AssignFx, hhVehicleThruster::Event_AssignFxSmoke ) END_CLASS hhVehicleThruster::hhVehicleThruster() { fxSmoke = NULL; } void hhVehicleThruster::Spawn() { owner = NULL; GetPhysics()->SetContents(0); soundDistance = spawnArgs.GetFloat("soundDistance"); bSomeThrusterActive = false; bSoundMaster = spawnArgs.GetBool("soundmaster"); localVelocity = localOffset = localDirection = vec3_origin; fl.networkSync = true; } void hhVehicleThruster::Save(idSaveGame *savefile) const { owner.Save(savefile); fxSmoke.Save(savefile); savefile->WriteVec3(localOffset); savefile->WriteVec3(localDirection); savefile->WriteFloat(soundDistance); savefile->WriteBool(bSomeThrusterActive); savefile->WriteBool(bSoundMaster); savefile->WriteVec3(localVelocity); } void hhVehicleThruster::Restore( idRestoreGame *savefile ) { owner.Restore(savefile); fxSmoke.Restore(savefile); savefile->ReadVec3(localOffset); savefile->ReadVec3(localDirection); savefile->ReadFloat(soundDistance); savefile->ReadBool(bSomeThrusterActive); savefile->ReadBool(bSoundMaster); savefile->ReadVec3(localVelocity); } void hhVehicleThruster::WriteToSnapshot( idBitMsgDelta &msg ) const { WriteBindToSnapshot(msg); GetPhysics()->WriteToSnapshot(msg); msg.WriteBits(owner.GetSpawnId(), 32); msg.WriteBits(fxSmoke.GetSpawnId(), 32); msg.WriteFloat(localOffset.x); msg.WriteFloat(localOffset.y); msg.WriteFloat(localOffset.z); msg.WriteFloat(localDirection.x); msg.WriteFloat(localDirection.y); msg.WriteFloat(localDirection.z); msg.WriteFloat(soundDistance); msg.WriteBits(bSomeThrusterActive, 1); msg.WriteBits(bSoundMaster, 1); msg.WriteFloat(localVelocity.x); msg.WriteFloat(localVelocity.y); msg.WriteFloat(localVelocity.z); msg.WriteBits(IsHidden(), 1); } void hhVehicleThruster::ReadFromSnapshot( const idBitMsgDelta &msg ) { ReadBindFromSnapshot(msg); GetPhysics()->ReadFromSnapshot(msg); owner.SetSpawnId(msg.ReadBits(32)); fxSmoke.SetSpawnId(msg.ReadBits(32)); localOffset.x = msg.ReadFloat(); localOffset.y = msg.ReadFloat(); localOffset.z = msg.ReadFloat(); localDirection.x = msg.ReadFloat(); localDirection.y = msg.ReadFloat(); localDirection.z = msg.ReadFloat(); soundDistance = msg.ReadFloat(); bSomeThrusterActive = !!msg.ReadBits(1); bSoundMaster = !!msg.ReadBits(1); localVelocity.x = msg.ReadFloat(); localVelocity.y = msg.ReadFloat(); localVelocity.z = msg.ReadFloat(); bool hidden = !!msg.ReadBits(1); if (hidden != IsHidden()) { if (hidden) { Hide(); } else { Show(); } } if (fxSmoke.IsValid() && fxSmoke.GetEntity() && fxSmoke->IsType(idEntityFx::Type)) { if (fxSmoke->IsHidden() != IsHidden()) { fxSmoke->BecomeActive( TH_THINK ); if (IsHidden()) { fxSmoke->Nozzle(false); fxSmoke->fl.hidden = true; } else { fxSmoke->Nozzle(true); fxSmoke->fl.hidden = false; } } } } void hhVehicleThruster::ClientPredictionThink( void ) { Think(); } void hhVehicleThruster::SetSmoker(bool bSmoker, idVec3 &offset, idVec3 &dir) { localOffset = offset; localDirection = dir; if ( bSmoker && (!fxSmoke.IsValid() || !fxSmoke.GetEntity()) ) { hhFxInfo fxInfo; const char *smokeName; smokeName = spawnArgs.GetString( "fx_smoke" ); if (smokeName && *smokeName) { fxInfo.SetStart(false); fxInfo.RemoveWhenDone(false); fxInfo.SetEntity( this ); idVec3 loc = owner->GetOrigin() + localOffset * owner->GetAxis(); idMat3 axis = (localDirection * owner->GetAxis()).ToMat3(); BroadcastFxInfo( smokeName, loc, axis, &fxInfo, &EV_Broadcast_AssignFx, false ); } } else if (!bSmoker && fxSmoke.IsValid() && fxSmoke.GetEntity()) { fxSmoke->PostEventMS(&EV_Remove, 0); fxSmoke = NULL; } } void hhVehicleThruster::SetThruster(bool on) { if (fxSmoke.IsValid() && fxSmoke.GetEntity()) { fxSmoke->Nozzle( on ); } on ? Show() : Hide(); } void hhVehicleThruster::SetDying(bool bDying) { if (bSoundMaster) { SetSmoker(true, localOffset, localDirection); } else { SetSmoker(bDying, localOffset, localDirection); } } void hhVehicleThruster::Update( const idVec3 &vel ) { if (bSoundMaster) { localVelocity = vel; bool on = localVelocity != vec3_origin; if (on && !bSomeThrusterActive) { // Just became active StartSound("snd_thrust", SND_CHANNEL_THRUSTERS); bSomeThrusterActive = true; } else if (!on) { // Just became inactive StopSound(SND_CHANNEL_THRUSTERS); bSomeThrusterActive = false; } } } bool hhVehicleThruster::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { idVec3 toSound; idVec3 toPilot; if (owner.IsValid()) { axis.Identity(); toSound = -localVelocity; // Position thrust sounds in the opposite direction as thrust velocity toSound.Normalize(); toPilot = (owner->GetOrigin() - GetOrigin()) + (owner->spawnArgs.GetVector("offset_pilot") + idVec3(0,0,1)*owner->spawnArgs.GetFloat("pilot_eyeHeight"))*owner->GetAxis(); origin = toPilot + (toSound * owner->GetAxis() * soundDistance); return true; } return false; } void hhVehicleThruster::Event_AssignFxSmoke( hhEntityFx* fx ) { fxSmoke = fx; } //========================================================================== // // hhPilotVehicleInterface // //========================================================================== CLASS_DECLARATION( idClass, hhPilotVehicleInterface ) END_CLASS hhPilotVehicleInterface::hhPilotVehicleInterface() { UnderScriptControl( false ); } //rww - added to handle when a player is destroyed before a vehicle (only in mp i guess) hhPilotVehicleInterface::~hhPilotVehicleInterface() { if (pilot.IsValid() && pilot.GetEntity()) { pilot->SetVehicleInterface(NULL); } if (vehicle.IsValid() && vehicle.GetEntity()) { vehicle->SetPilotInterface(NULL); } } void hhPilotVehicleInterface::Save(idSaveGame *savefile) const { pilot.Save(savefile); vehicle.Save(savefile); savefile->WriteBool(underScriptControl); } void hhPilotVehicleInterface::Restore( idRestoreGame *savefile ) { pilot.Restore(savefile); vehicle.Restore(savefile); savefile->ReadBool(underScriptControl); } void hhPilotVehicleInterface::RetrievePilotInput( usercmd_t& cmds, idAngles& viewAngles ) { if( pilot.IsValid() ) { pilot->GetPilotInput( cmds, viewAngles ); } } void hhPilotVehicleInterface::TakeControl( hhVehicle* theVehicle, idActor* thePilot ) { vehicle = theVehicle; pilot = thePilot; if( theVehicle ) { theVehicle->AcceptPilot( this ); } } void hhPilotVehicleInterface::ReleaseControl() { if (vehicle.IsValid()) { vehicle->ReleaseControl(); } vehicle = NULL; pilot = NULL; } idVec3 hhPilotVehicleInterface::DeterminePilotOrigin() const { assert( vehicle.IsValid() ); return vehicle->DeterminePilotOrigin(); } idMat3 hhPilotVehicleInterface::DeterminePilotAxis() const { assert( vehicle.IsValid() ); return vehicle->DeterminePilotAxis(); } bool hhPilotVehicleInterface::ControllingVehicle() const { return vehicle.IsValid() && vehicle->IsVehicle(); } hhVehicle* hhPilotVehicleInterface::GetVehicle() const { return vehicle.GetEntity(); } idActor* hhPilotVehicleInterface::GetPilot() const { return pilot.GetEntity(); } bool hhPilotVehicleInterface::InvalidVehicleImpulse( int impulse ) { switch( impulse ) { case IMPULSE_0: case IMPULSE_1: case IMPULSE_2: case IMPULSE_3: case IMPULSE_4: case IMPULSE_5: case IMPULSE_6: case IMPULSE_7: case IMPULSE_8: case IMPULSE_9: case IMPULSE_10: case IMPULSE_11: case IMPULSE_12: case IMPULSE_13: case IMPULSE_14: case IMPULSE_15: case IMPULSE_16: case IMPULSE_19: case IMPULSE_25: case IMPULSE_40: case IMPULSE_54: return true; } return false; } //========================================================================== // // hhAIVehicleInterface // //========================================================================== CLASS_DECLARATION( hhPilotVehicleInterface, hhAIVehicleInterface ) END_CLASS hhAIVehicleInterface::hhAIVehicleInterface(void) { stateFiring = false; stateAltFiring = false; } void hhAIVehicleInterface::Save(idSaveGame *savefile) const { savefile->WriteUsercmd(bufferedCmds); savefile->WriteAngles(bufferedViewAngles); savefile->WriteBool(stateFiring); savefile->WriteBool(stateAltFiring); savefile->WriteFloat(stateOrientSpeed); savefile->WriteFloat(stateThrustSpeed); savefile->WriteVec3(stateOrientDestination); savefile->WriteVec3(stateThrustDestination); } void hhAIVehicleInterface::Restore( idRestoreGame *savefile ) { savefile->ReadUsercmd(bufferedCmds); savefile->ReadAngles(bufferedViewAngles); savefile->ReadBool(stateFiring); savefile->ReadBool(stateAltFiring); savefile->ReadFloat(stateOrientSpeed); savefile->ReadFloat(stateThrustSpeed); savefile->ReadVec3(stateOrientDestination); savefile->ReadVec3(stateThrustDestination); } void hhAIVehicleInterface::TakeControl( hhVehicle* vehicle, idActor* pilot ) { hhPilotVehicleInterface::TakeControl( vehicle, pilot ); ClearBufferedCmds(); bufferedViewAngles = (pilot) ? pilot->GetAxis().ToAngles() : ang_zero; } void hhAIVehicleInterface::Fire(bool on) { stateFiring = on; } void hhAIVehicleInterface::AltFire(bool on) { stateAltFiring = on; } void hhAIVehicleInterface::OrientTowards( const idVec3 &loc, float speed ) { stateOrientDestination = loc; stateOrientSpeed = idMath::ClampFloat( 0.0f, 1.0f, speed ); } void hhAIVehicleInterface::ThrustTowards( const idVec3 &loc, float speed ) { stateThrustDestination = loc; stateThrustSpeed = idMath::ClampFloat( 0.0f, 1.0f, speed ); } void hhAIVehicleInterface::RetrievePilotInput( usercmd_t& cmds, idAngles& viewAngles ) { if( !ControllingVehicle() ) { return; } // First grab any buffered commands cmds = bufferedCmds; viewAngles = bufferedViewAngles; idVec3 eyePos = GetPilot()->GetEyePosition(); // Now, Override the buffered input with any script/ai requests // Based on current script requests, construct a command packet cmds.buttons |= (stateFiring ? BUTTON_ATTACK : 0); cmds.buttons |= (stateAltFiring ? BUTTON_ATTACK_ALT : 0); // Handle thrusting if ( stateThrustSpeed > 0.0f ) { idVec3 targetDir = stateThrustDestination - eyePos; // Use proportional control system to ease into destination const float proportionalGain = 0.7f; float error = idMath::ClampFloat(0.0f, 1.0f, targetDir.Normalize() / 512.0f); // error only taken into consideration when within 512 units of destination if (error < 1.0f) { error *= proportionalGain; } targetDir *= vehicle->GetAxis().Inverse(); cmds.forwardmove = targetDir.x * 127.0f * error * stateThrustSpeed; cmds.rightmove = -targetDir.y * 127.0f * error * stateThrustSpeed; cmds.upmove = targetDir.z * 127.0f * error * stateThrustSpeed; } // Handle orienting: Using direction vectors to interpolate our orientation to our moving target orientation viewAngles.pitch = -idMath::AngleNormalize180( vehicle->GetAxis()[0].ToPitch() ); viewAngles.yaw = vehicle->GetAxis()[0].ToYaw(); viewAngles.roll = 0.0f; if ( stateOrientSpeed > 0.0f ) { idVec3 localDir; idAngles idealViewAngles( ang_zero ); idVec3 targetDir = stateOrientDestination - eyePos; targetDir.Normalize(); float degrees = stateOrientSpeed * 360.0f * MS2SEC(gameLocal.msec); vehicle->GetPhysics()->GetAxis().ProjectVector( targetDir, localDir ); idealViewAngles.yaw = localDir.ToYaw(); idealViewAngles.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ); idAngles deltaViewAngles = (idealViewAngles - viewAngles).Normalize180(); deltaViewAngles.Clamp( idAngles(-degrees, -degrees, 0.0f), idAngles(degrees, degrees, 0.0f) ); if( !deltaViewAngles.Compare(ang_zero, VECTOR_EPSILON) ) { viewAngles += deltaViewAngles; bufferedViewAngles = viewAngles; // Save for next time } } } void hhAIVehicleInterface::BufferPilotCmds( const usercmd_t* cmds, const idAngles* viewAngles ) { if( cmds ) { bufferedCmds = *cmds; } if( viewAngles ) { bufferedViewAngles = *viewAngles; } } void hhAIVehicleInterface::ClearBufferedCmds() { memset( &bufferedCmds, 0, sizeof(usercmd_t) ); stateFiring = false; stateAltFiring = false; stateOrientSpeed = 0.0f; stateThrustSpeed = 0.0f; } bool hhAIVehicleInterface::IsVehicleDocked() const { return vehicle.IsValid() && vehicle->IsDocked(); } //========================================================================== // // hhPlayerVehicleInterface // //========================================================================== CLASS_DECLARATION( hhPilotVehicleInterface, hhPlayerVehicleInterface ) END_CLASS hhPlayerVehicleInterface::hhPlayerVehicleInterface() { hud = NULL; uniqueHud = true; translationAlpha.Init(gameLocal.time, 0, 0.0f, 0.0f); } hhPlayerVehicleInterface::~hhPlayerVehicleInterface() { } void hhPlayerVehicleInterface::Save(idSaveGame *savefile) const { savefile->WriteStaticObject(weaponHandState); controlHand.Save(savefile); savefile->WriteBool(uniqueHud); savefile->WriteUserInterface(hud, uniqueHud); savefile->WriteFloat( translationAlpha.GetStartTime() ); // idInterpolate savefile->WriteFloat( translationAlpha.GetDuration() ); savefile->WriteFloat( translationAlpha.GetStartValue() ); savefile->WriteFloat( translationAlpha.GetEndValue() ); } void hhPlayerVehicleInterface::Restore( idRestoreGame *savefile ) { float set; savefile->ReadStaticObject(weaponHandState); controlHand.Restore(savefile); savefile->ReadBool(uniqueHud); savefile->ReadUserInterface(hud); savefile->ReadFloat( set ); // idInterpolate translationAlpha.SetStartTime( set ); savefile->ReadFloat( set ); translationAlpha.SetDuration( set ); savefile->ReadFloat( set ); translationAlpha.SetStartValue(set); savefile->ReadFloat( set ); translationAlpha.SetEndValue( set ); } void hhPlayerVehicleInterface::UpdateControlHand( const usercmd_t& cmds ) { if( controlHand.IsValid() ) { controlHand->UpdateControlDirection( idVec3(Sign(cmds.forwardmove), Sign(cmds.rightmove), Sign(cmds.upmove)) ); } } void hhPlayerVehicleInterface::CreateControlHand( hhPlayer* pilot, const char* handName ) { //RemoveHand(); if( !handName || !handName[0] ) { return; } weaponHandState.SetPlayer( pilot ); weaponHandState.SetWeaponTransition( 1 ); weaponHandState.Archive( NULL, 0, handName, 1 ); if ( pilot->hand.IsValid() && pilot->hand->IsType( hhControlHand::Type ) ) { controlHand = static_cast( pilot->hand.GetEntity() ); } else { controlHand = NULL; } //controlHand = static_cast( hhControlHand::AddHand(pilot, handName) ); } void hhPlayerVehicleInterface::RemoveHand() { if( controlHand.IsValid() ) { //controlHand->RemoveHand(); controlHand = NULL; } weaponHandState.RestoreFromArchive(); } void hhPlayerVehicleInterface::RetrievePilotInput( usercmd_t& cmds, idAngles& viewAngles ) { if( pilot.IsValid() ) { hhPilotVehicleInterface::RetrievePilotInput( cmds, viewAngles ); UpdateControlHand( cmds ); } } void hhPlayerVehicleInterface::TakeControl( hhVehicle* vehicle, idActor* pilot ) { hhPilotVehicleInterface::TakeControl( vehicle, pilot ); if( vehicle ) { idStr hudName = vehicle->spawnArgs.GetString( "gui_hud" ); if( hudName.Length() ) { uniqueHud = vehicle->spawnArgs.GetBool("uniqueguihud", "1"); hud = uiManager->FindGui( hudName.c_str(), true, uniqueHud, !uniqueHud ); bool isPilotLocal = false; idPlayer *localPl = gameLocal.GetLocalPlayer(); if (localPl) { if (localPl->entityNumber == pilot->entityNumber || (localPl->spectating && localPl->spectator == pilot->entityNumber)) { isPilotLocal = true; } } if (isPilotLocal) { translationAlpha.Init( gameLocal.time, 0, 0.0f, 0.0f ); hud->Activate( true, gameLocal.GetTime() ); pilot->fl.clientEvents = true; //rww - hack pilot->PostEventMS( &EV_StartHudTranslation, 1000 ); pilot->fl.clientEvents = false; //rww - hack } } if (!gameLocal.isClient) { CreateControlHand( static_cast(pilot), vehicle->spawnArgs.GetString("def_hand") ); } } } void hhPlayerVehicleInterface::ReleaseControl() { if( vehicle.IsValid() ) { hud = NULL; uniqueHud = true; if (!gameLocal.isClient) { RemoveHand(); } else { if (controlHand.IsValid() && controlHand.GetEntity()) { controlHand->Hide(); } } } hhPilotVehicleInterface::ReleaseControl(); } void hhPlayerVehicleInterface::DrawHUD( idUserInterface* _hud ) { if( vehicle.IsValid() && vehicle->IsVehicle() && _hud ) { float alpha = translationAlpha.GetCurrentValue(gameLocal.GetTime()); _hud->SetStateFloat( "translationAlpha", alpha ); _hud->SetStateBool( "translationProject", false ); vehicle->DrawHUD( _hud ); } } void hhPlayerVehicleInterface::StartHUDTranslation() { translationAlpha.Init( gameLocal.time, 1000, 0.0f, 1.0f ); } //========================================================================== // // hhVehicle // //========================================================================== const idEventDef EV_VehicleExplode("", "efff"); const idEventDef EV_Vehicle_FireCannon( "fireCannon" ); const idEventDef EV_VehicleGetIn("getIn", "e"); // Script Commands const idEventDef EV_VehicleGetOut("getOut"); const idEventDef EV_VehicleFire("fire", "d"); const idEventDef EV_VehicleAltFire("altFire", "d"); const idEventDef EV_VehicleOrientTowards("orientTowards", "vf"); const idEventDef EV_VehicleStopOrientingTOwards("stopOrientingTowards"); const idEventDef EV_VehicleThrustTowards("thrustTowards", "vf"); const idEventDef EV_VehicleStopThrustingTOwards("stopThrustingTowards"); const idEventDef EV_VehicleReleaseControl("releaseControl"); const idEventDef EV_Vehicle_EjectPilot("ejectPilot"); ABSTRACT_DECLARATION( hhRenderEntity, hhVehicle ) EVENT( EV_Vehicle_FireCannon, hhVehicle::Event_FireCannon ) EVENT( EV_VehicleExplode, hhVehicle::Event_Explode ) EVENT( EV_VehicleGetIn, hhVehicle::Event_GetIn ) EVENT( EV_VehicleGetOut, hhVehicle::Event_GetOut ) EVENT( EV_VehicleFire, hhVehicle::Event_Fire ) EVENT( EV_VehicleAltFire, hhVehicle::Event_AltFire ) EVENT( EV_VehicleOrientTowards, hhVehicle::Event_OrientTowards ) EVENT( EV_VehicleThrustTowards, hhVehicle::Event_ThrustTowards ) EVENT( EV_VehicleReleaseControl, hhVehicle::Event_ReleaseScriptControl ) EVENT( EV_Vehicle_EjectPilot, hhVehicle::Event_EjectPilot ) EVENT( EV_ResetGravity, hhVehicle::Event_ResetGravity ) END_CLASS hhVehicle::~hhVehicle() { if (GetPilotInterface()) { idActor *actor = GetPilotInterface()->GetPilot(); if (actor && actor->IsType(idActor::Type)) { //if it still has a valid pilot, eject them. EjectPilot(); } } } void hhVehicle::Spawn() { memset( &oldCmds, 0, sizeof(usercmd_t) ); thrusterCost = spawnArgs.GetInt( "thrusterCost" ); currentPower = spawnArgs.GetInt( "maxPower" ); thrustFactor = spawnArgs.GetFloat( "thrustFactor" ); dockBoostFactor = spawnArgs.GetFloat( "dockBoostFactor" ); if ( gameLocal.isMultiplayer ) { noDamage = false; } else { noDamage = spawnArgs.GetBool( "noDamage", "0" ); } bDamageSelfOnCollision = spawnArgs.GetBool( "damageSelfOnCollision" ); bDamageOtherOnCollision = spawnArgs.GetBool( "damageOtherOnCollision" ); lastAttackTime = 0; fl.neverDormant = false; fl.refreshReactions = true; fireController = NULL; pilotInterface = NULL; bHeadlightOn = false; validThrustTime = gameLocal.time; bDisallowAttackUntilRelease = false; bDisallowAltAttackUntilRelease = false; InitializeAttackFuncs(); //rww - i see no reason why the dock should be set upon spawning the vehicle. //also would cause issues in mp where only one shuttle should be docked at a time. //startDock is set by the dock who created us /* hhDock *dock = (hhDock *)gameLocal.FindEntity(spawnArgs.GetString("startDock")); SetDock( dock ); if (dock && dock->IsType(hhShuttleDock::Type)) { hhShuttleDock *shDock = static_cast(dock); if (shDock->GetDockedShuttle()) { //if the dock already has a shuttle, do not dock this ship there. SetDock(NULL); } } */ InitPhysics(); BecomeConsole(); physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); SetAxis( GetPhysics()->GetAxis() ); physicsObj.SetAxis( GetPhysics()->GetAxis() ); SetPhysics( &physicsObj ); if ( spawnArgs.GetInt( "nodrop" ) ) { physicsObj.PutToRest(); } else { physicsObj.DropToFloor(); } } void hhVehicle::Save(idSaveGame *savefile) const { savefile->WriteStaticObject(physicsObj); savefile->WriteMat3(modelAxis); if( fireController ) { savefile->WriteBool(true); savefile->WriteStaticObject(*fireController); } else { savefile->WriteBool(false); } savefile->WriteUsercmd(oldCmds); headlight.Save(savefile); domelight.Save(savefile); savefile->WriteBool(bHeadlightOn); savefile->WriteInt(currentPower); savefile->WriteFloat(thrustFactor); savefile->WriteFloat(thrustMin); savefile->WriteFloat(thrustMax); savefile->WriteFloat(thrustAccel); savefile->WriteFloat(thrustScale); savefile->WriteFloat(dockBoostFactor); dock.Save(savefile); lastAttacker.Save(savefile); savefile->WriteInt(lastAttackTime); savefile->WriteInt(vehicleClipMask); savefile->WriteInt(vehicleContents); savefile->WriteBool(bDamageSelfOnCollision); savefile->WriteBool(bDamageOtherOnCollision); savefile->WriteBool(bDisallowAttackUntilRelease); savefile->WriteBool(bDisallowAltAttackUntilRelease); savefile->WriteInt(thrusterCost); savefile->WriteInt(validThrustTime); savefile->WriteEventDef(attackFunc); savefile->WriteEventDef(finishedAttackingFunc); savefile->WriteEventDef(altAttackFunc); savefile->WriteEventDef(finishedAltAttackingFunc); savefile->WriteBool(noDamage); } void hhVehicle::Restore( idRestoreGame *savefile ) { savefile->ReadStaticObject(physicsObj); RestorePhysics(&physicsObj); savefile->ReadMat3(modelAxis); bool test; savefile->ReadBool( test ); if (test) { fireController = CreateFireController(); savefile->ReadStaticObject(*fireController); } else { SAFE_DELETE_PTR(fireController); } savefile->ReadUsercmd(oldCmds); headlight.Restore(savefile); domelight.Restore(savefile); savefile->ReadBool(bHeadlightOn); savefile->ReadInt(currentPower); savefile->ReadFloat(thrustFactor); savefile->ReadFloat(thrustMin); savefile->ReadFloat(thrustMax); savefile->ReadFloat(thrustAccel); savefile->ReadFloat(thrustScale); savefile->ReadFloat(dockBoostFactor); dock.Restore(savefile); lastAttacker.Restore(savefile); savefile->ReadInt(lastAttackTime); savefile->ReadInt(vehicleClipMask); savefile->ReadInt(vehicleContents); savefile->ReadBool(bDamageSelfOnCollision); savefile->ReadBool(bDamageOtherOnCollision); savefile->ReadBool(bDisallowAttackUntilRelease); savefile->ReadBool(bDisallowAltAttackUntilRelease); savefile->ReadInt(thrusterCost); savefile->ReadInt(validThrustTime); savefile->ReadEventDef(attackFunc); savefile->ReadEventDef(finishedAttackingFunc); savefile->ReadEventDef(altAttackFunc); savefile->ReadEventDef(finishedAltAttackingFunc); savefile->ReadBool(noDamage); } const idEventDef* hhVehicle::GetAttackFunc( const char* funcName ) { function_t* function = NULL; if( !funcName || !funcName[0] ) { return NULL; } idTypeDef *funcType = gameLocal.program.FindType(funcName); if (!funcType) { return NULL; } function = gameLocal.program.FindFunction( funcName, funcType ); if( !function || !function->eventdef ) { return NULL; } HH_ASSERT( RespondsTo(*function->eventdef) ); return function->eventdef; } void hhVehicle::InitializeAttackFuncs() { idStr funcName; attackFunc = finishedAttackingFunc = altAttackFunc = finishedAltAttackingFunc = NULL; funcName = spawnArgs.GetString("attackFunc"); if (funcName.Length()) { attackFunc = GetAttackFunc( funcName.c_str() ); finishedAttackingFunc = GetAttackFunc( (funcName + "Done").c_str() ); } funcName = spawnArgs.GetString("altAttackFunc"); if (funcName.Length()) { altAttackFunc = GetAttackFunc( funcName.c_str() ); finishedAltAttackingFunc = GetAttackFunc( (funcName + "Done").c_str() ); } } void hhVehicle::Think() { usercmd_t pilotCmds; idAngles pilotViewAngles; if( IsVehicle() && GetPilotInterface() ) { GetPilotInterface()->RetrievePilotInput( pilotCmds, pilotViewAngles ); ProcessPilotInput( &pilotCmds, &pilotViewAngles ); GetPhysics()->SetAxis( mat3_identity ); } hhRenderEntity::Think(); } void hhVehicle::Present() { if (fireController) { fireController->UpdateMuzzleFlash(); } hhRenderEntity::Present(); } void hhVehicle::AcceptPilot( hhPilotVehicleInterface* pilotInterface ) { //assure that the vehicle is showing upon accepting a new pilot Show(); this->pilotInterface = pilotInterface; fl.neverDormant = true; fl.takedamage = true; StartSound( "snd_activation", SND_CHANNEL_ANY ); StartSound( "snd_inuse", SND_CHANNEL_MISC1 ); CreateDomeLight(); CreateHeadLight(); BecomeActive( TH_TICKER ); const idDict* infoDict = NULL; if ( GetPilot() && GetPilot()->IsType(idAI::Type) ) { infoDict = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_fireInfoAI"), false ); } else { infoDict = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_fireInfo"), false ); } fireController = CreateFireController(); HH_ASSERT( fireController ); fireController->Init( infoDict, this, GetPilot() ); // supress model in player views, but allow it in mirrors and remote views if (GetPilot()->IsType(hhPlayer::Type)) { GetRenderEntity()->suppressSurfaceInViewID = GetPilot()->entityNumber + 1; } BecomeVehicle(); } void hhVehicle::RestorePilot( hhPilotVehicleInterface* pilotInterface) { const idDict *infoDict = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_fireInfo"), false ); assert( infoDict ); fireController->SetWeaponDict( infoDict ); this->pilotInterface = pilotInterface; } void hhVehicle::EjectPilot() { assert( IsVehicle() ); SAFE_DELETE_PTR( fireController ); fl.takedamage = false; fl.neverDormant = false; StopSound( SND_CHANNEL_MISC1 ); StartSound( "snd_deactivation", SND_CHANNEL_ANY ); FreeDomeLight(); FreeHeadLight(); BecomeInactive( TH_TICKER ); if (GetPilotInterface()->GetPilot()) { //rww - make sure pilot is valid GetPilotInterface()->GetPilot()->ExitVehicle( this ); pilotInterface = NULL; } RemoveVehicle(); } idVec3 hhVehicle::DeterminePilotOrigin() const { return GetOrigin() + spawnArgs.GetVector("offset_pilot") * GetAxis(); } idMat3 hhVehicle::DeterminePilotAxis() const { return GetAxis(); } void hhVehicle::BecomeVehicle() { SetVehiclePhysics(); SetVehicleModel(); } bool hhVehicle::CanBecomeVehicle(idActor *pilot) { // Check to see if shuttle will fit idEntity *touch[ MAX_GENTITIES ]; idBounds bounds, localBounds; idVec3 location = GetOrigin(); localBounds[0] = spawnArgs.GetVector("mins"); localBounds[1] = spawnArgs.GetVector("maxs"); // idBox box(localBounds, location, GetAxis()); // gameRenderWorld->DebugBox(colorRed, box, 8000); idTraceModel trm; trm.SetupBox( localBounds ); idClipModel *clipModel = new idClipModel( trm ); clipModel->Link(gameLocal.clip, this, 254, location, GetAxis()); int pilotClipMask = pilot ? pilot->GetPhysics()->GetClipMask() : 0; pilotClipMask &= (~CONTENTS_HUNTERCLIP); // Vehicles don't collide with hunterclip so they don't get hung up on dock borders int num = hhUtils::EntitiesTouchingClipmodel( clipModel, touch, MAX_GENTITIES, CLIPMASK_VEHICLE | pilotClipMask ); bool blocked = false; for (int i=0; iGetBindMaster() != pilot && (touch[i]->GetPhysics()->GetContents() & CLIPMASK_VEHICLE)) { blocked = true; //gameLocal.Printf("Blocked by %s\n", touch[i]->GetName()); break; } } clipModel->Unlink(); delete clipModel; return !blocked; } bool hhVehicle::IsVehicle() const { return fireController != NULL && GetPilotInterface() && GetPilotInterface()->GetPilot(); } void hhVehicle::BecomeConsole() { SetConsolePhysics(); if( IsDocked() ) { //Feels like a hack. Shouldn't the dock do this or at least be in SetConsolePhysics SetAxis( dock->GetAxis() ); GetPhysics()->SetAxis( dock->GetAxis() ); SetOrigin( dock->GetOrigin() + dock->spawnArgs.GetVector("offset_console") * dock->GetAxis() ); physicsObj.PutToRest(); } SetConsoleModel(); } bool hhVehicle::IsConsole() const { return !IsVehicle(); } idVec3 hhVehicle::GetPortalPoint() { idVec3 offset = spawnArgs.GetVector("offset_pilot") + idVec3(0,0,1)*spawnArgs.GetFloat("pilot_eyeHeight"); return GetOrigin() + offset * GetAxis(); } void hhVehicle::Portalled(idEntity *portal) { idActor *pilot = GetPilot(); if (pilot) { // Update the view angles of the player so next time we get input, it won't reset our angles if (pilot->IsType(hhPlayer::Type)) { hhPlayer* player = static_cast( pilot ); idVec3 origin = DeterminePilotOrigin(); idVec3 viewDir = GetAxis()[0]; // Don't know if all this is necessary, might be overkill, definitely need some of it though for 'shuttle through portal' player->Unbind(); player->SetOrientation( origin, mat3_identity, viewDir, viewDir.ToAngles() ); player->SetUntransformedViewAxis( mat3_identity ); player->Bind(this, true); } } } void hhVehicle::ResetGravity() { if( IsVehicle() ) { physicsObj.SetGravity( spawnArgs.GetVector("activeGravity") ); } else { physicsObj.SetGravity( spawnArgs.GetVector("inactiveGravity") ); } } void hhVehicle::SetAxis( const idMat3& axis ) { modelAxis = axis; UpdateVisuals(); } void hhVehicle::InitPhysics() { physicsObj.SetSelf( this ); physicsObj.SetFriction( spawnArgs.GetFloat("friction_linear"), spawnArgs.GetFloat("friction_angular"), spawnArgs.GetFloat("friction_contact") ); } void hhVehicle::SetConsolePhysics() { idTraceModel trm; const char *clipModelName = spawnArgs.GetString( "clipmodel" ); assert( clipModelName[0] ); if ( !collisionModelManager->TrmFromModel( clipModelName, trm ) ) { gameLocal.Error( "hhVehicle '%s' at (%s): cannot load collision model %s\n", name.c_str(), GetPhysics()->GetOrigin().ToString(0), clipModelName ); return; } physicsObj.SetClipModel( new idClipModel(trm), spawnArgs.GetFloat("density") ); physicsObj.DisableImpact(); physicsObj.SetBouncyness( 0.0f ); physicsObj.SetContents( CONTENTS_VEHICLE ); physicsObj.SetClipMask( CLIPMASK_VEHICLE | CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP ); physicsObj.SetLinearVelocity( vec3_zero ); physicsObj.SetAngularVelocity( vec3_zero ); //Used as a cache for when noclipping vehicleClipMask = physicsObj.GetClipMask(); vehicleContents = physicsObj.GetContents(); ResetGravity(); } void hhVehicle::SetVehiclePhysics() { idBounds bounds( spawnArgs.GetVector("mins"), spawnArgs.GetVector("maxs") ); physicsObj.SetClipModel( new idClipModel(idTraceModel(bounds)), spawnArgs.GetFloat("density") ); physicsObj.SetAxis( mat3_identity ); physicsObj.EnableImpact(); physicsObj.SetBouncyness( spawnArgs.GetFloat("bouncyness") ); physicsObj.SetContents( CONTENTS_VEHICLE ); SetThrustBooster( spawnArgs.GetFloat("thrustMin"), spawnArgs.GetFloat("thrustMax"), spawnArgs.GetFloat("thrustAccel") ); int pilotClipMask = (GetPilotInterface() && GetPilotInterface()->GetPilot()) ? GetPilotInterface()->GetPilot()->GetPhysics()->GetClipMask() : 0; pilotClipMask &= (~CONTENTS_HUNTERCLIP); // Vehicles don't collide with hunterclip so they don't get hung up on dock borders physicsObj.SetClipMask( CLIPMASK_VEHICLE | pilotClipMask ); //Used as a cache for when noclipping vehicleClipMask = physicsObj.GetClipMask(); vehicleContents = physicsObj.GetContents(); ResetGravity(); } void hhVehicle::SetConsoleModel() { SetModel( spawnArgs.GetString("model") ); } void hhVehicle::SetVehicleModel() { SetModel( spawnArgs.GetString("model_active") ); } float hhVehicle::CmdScale( const usercmd_t* cmd ) const { float scale = 0.0f; if( !cmd ) { return scale; } idVec3 abscmd(abs(cmd->forwardmove), abs(cmd->rightmove), abs(cmd->upmove)); // Bound the cmd vector to a sphere whose radius is equal to the longest cmd axis int desiredLength = max(max(abscmd[0], abscmd[1]), abscmd[2]); if ( desiredLength != 0.0f ) { float currentLength = abscmd.Length(); scale = desiredLength / currentLength; } return scale; } void hhVehicle::SetThrustBooster(float minBooster, float maxBooster, float accelBooster) { thrustMin = minBooster; thrustMax = maxBooster; thrustAccel = accelBooster; thrustScale = 0.1f; } void hhVehicle::ProcessPilotInput( const usercmd_t* cmds, const idAngles* viewAngles ) { idVec3 impulse; if( viewAngles ) { SetAxis( viewAngles->ToMat3() ); } if( !cmds ) { return; } ProcessButtons( *cmds ); //Check vehicle here because we could have exited the vehicle in ProcessButtons if( !IsVehicle() ) { return; } ProcessImpulses( *cmds ); if ( gameLocal.time >= validThrustTime ) { impulse = GetAxis()[0] * cmds->forwardmove * thrustFactor; impulse -= GetAxis()[1] * cmds->rightmove * thrustFactor; impulse += GetAxis()[2] * cmds->upmove * thrustFactor; } else { impulse = vec3_origin; } // Apply booster to allow key taps to be very low thrust thrustScale = idMath::ClampFloat( thrustMin, thrustMax, thrustScale * thrustAccel ); if( impulse.LengthSqr() >= VECTOR_EPSILON ) { FireThrusters( impulse * CmdScale(cmds) * thrustScale * (60.0f * USERCMD_ONE_OVER_HZ) ); } else { thrustScale = thrustMin; } memcpy( &oldCmds, cmds, sizeof(usercmd_t) ); } void hhVehicle::ProcessButtons( const usercmd_t& cmds ) { //This is needed in case we enter a dock while firing or using the tractor beam if( (cmds.buttons & BUTTON_ATTACK) && !(oldCmds.buttons & BUTTON_ATTACK) ) { if (IsDocked() && dock->AllowsExit() && spawnArgs.GetBool("fireToExit")) { if (!gameLocal.isClient) { EjectPilot(); } return; } } else if( (cmds.buttons & BUTTON_ATTACK_ALT) && !(oldCmds.buttons & BUTTON_ATTACK_ALT) ) { if (IsDocked() && dock->AllowsExit() && spawnArgs.GetBool("altFireToExit")) { if (!gameLocal.isClient) { EjectPilot(); } return; } } if (bDisallowAttackUntilRelease) { if (!(cmds.buttons & BUTTON_ATTACK)) { bDisallowAttackUntilRelease = false; } if( oldCmds.buttons & BUTTON_ATTACK ) { oldCmds.buttons &= ~BUTTON_ATTACK; if( finishedAttackingFunc ) { ProcessEvent( finishedAttackingFunc ); } } } else { if( (cmds.buttons & BUTTON_ATTACK) ) { if( attackFunc ) { ProcessEvent( attackFunc ); } } else if( oldCmds.buttons & BUTTON_ATTACK ) { if( finishedAttackingFunc ) { ProcessEvent( finishedAttackingFunc ); } } } if (bDisallowAltAttackUntilRelease) { if (!(cmds.buttons & BUTTON_ATTACK_ALT)) { bDisallowAltAttackUntilRelease = false; } if ( oldCmds.buttons & BUTTON_ATTACK_ALT ) { oldCmds.buttons &= ~BUTTON_ATTACK_ALT; if( finishedAltAttackingFunc ) { ProcessEvent( finishedAltAttackingFunc ); } } } else { if( cmds.buttons & BUTTON_ATTACK_ALT ) { if( altAttackFunc ) { ProcessEvent( altAttackFunc ); } } else if( oldCmds.buttons & BUTTON_ATTACK_ALT ) { if( finishedAltAttackingFunc ) { ProcessEvent( finishedAltAttackingFunc ); } } } } void hhVehicle::DoPlayerImpulse(int impulse) { if ( gameLocal.isClient && GetPilot() && GetPilot()->IsType(hhPlayer::Type) ) { idBitMsg msg; byte msgBuf[MAX_EVENT_PARAM_SIZE]; hhPlayer *pl = static_cast(GetPilot()); assert( pl->entityNumber == gameLocal.localClientNum ); msg.Init( msgBuf, sizeof( msgBuf ) ); msg.BeginWriting(); msg.WriteBits( impulse, 6 ); pl->ClientSendEvent( hhPlayer::EVENT_IMPULSE, &msg ); } switch (impulse) { case IMPULSE_16: if (!gameLocal.isClient) { Headlight( !bHeadlightOn ); } break; } } void hhVehicle::ProcessImpulses( const usercmd_t& cmds ) { if( (cmds.flags & UCF_IMPULSE_SEQUENCE) == (oldCmds.flags & UCF_IMPULSE_SEQUENCE) ) { return; } DoPlayerImpulse(cmds.impulse); //rww - seperated into its own function because of networked impulse events } void hhVehicle::ApplyImpulse( idEntity* ent, int id, const idVec3& point, const idVec3& impulse ) { hhRenderEntity::ApplyImpulse( ent, id, point, impulse ); } void hhVehicle::ApplyImpulse( const idVec3& impulse ) { ApplyImpulse( gameLocal.world, 0, GetOrigin() + physicsObj.GetCenterOfMass(), impulse * physicsObj.GetMass() ); } void hhVehicle::UpdateModel( void ) { idVec3 origin; idMat3 axis; if ( GetPhysicsToVisualTransform(origin, axis) ) { //HUMANHEAD: aob GetRenderEntity()->axis = axis * GetAxis(); GetRenderEntity()->origin = GetOrigin() + origin * GetRenderEntity()->axis; //HUMANHEAD END } else { //HUMANHEAD: aob GetRenderEntity()->axis = GetAxis(); GetRenderEntity()->origin = GetOrigin(); //HUMANHEAD END } // set to invalid number to force an update the next time the PVS areas are retrieved ClearPVSAreas(); // ensure that we call Present this frame BecomeActive( TH_UPDATEVISUALS ); } void hhVehicle::CreateDomeLight() { if (spawnArgs.GetBool("domelight")) { // This method is more oriented for flexibility idVec3 light_offset = spawnArgs.GetVector("offset_domelight"); const char *objName = spawnArgs.GetString("def_domelight"); idDict args; idVec3 lightOrigin = GetPhysics()->GetOrigin() + light_offset * GetPhysics()->GetAxis(); args.SetVector( "origin", lightOrigin ); domelight = (idLight *)gameLocal.SpawnObject(objName, &args); domelight->Bind( this, true ); domelight->SetLightParm(SHADERPARM_TIMEOFFSET, -MS2SEC(gameLocal.time)); } } void hhVehicle::FreeDomeLight() { SAFE_REMOVE( domelight ); } void hhVehicle::CreateHeadLight() { if ( spawnArgs.GetBool("headlight") ) { // This method is more oriented for presenting our own lights // Depending on the speed cost of having seperate entities for lights, we may // want to present our own, like the weapons idStr light_shader = spawnArgs.GetString("mtr_headlight"); idVec3 light_color = spawnArgs.GetVector("headlight_color"); idVec3 light_offset = spawnArgs.GetVector("offset_headlight"); idVec3 light_target = spawnArgs.GetVector("offset_headlighttarget"); idVec3 light_frustum = spawnArgs.GetVector("headlight_frustum"); idDict args; idVec3 lightOrigin = GetPhysics()->GetOrigin() + light_offset * GetPhysics()->GetAxis(); light_target.Normalize(); idMat3 lightAxis = (light_target * GetPhysics()->GetAxis()).hhToMat3(); if ( light_shader.Length() ) { args.Set( "texture", light_shader ); } args.SetVector( "origin", lightOrigin ); args.Set ("angles", lightAxis.ToAngles().ToString()); args.SetVector( "_color", light_color ); args.SetVector( "light_target", lightAxis[0] * light_frustum.x ); args.SetVector( "light_right", lightAxis[1] * light_frustum.y ); args.SetVector( "light_up", lightAxis[2] * light_frustum.z ); headlight = ( idLight * )gameLocal.SpawnEntityTypeClient( idLight::Type, &args ); //rww - headlight is a pure local entity assert(headlight.IsValid() && headlight.GetEntity()); headlight->fl.networkSync = false; headlight->fl.clientEvents = true; headlight->Bind(this, true); headlight->SetLightParm( 6, 0.0f ); // fade out headlight->SetLightParm( SHADERPARM_TIMEOFFSET, 0 ); // Initially faded out already } } void hhVehicle::FreeHeadLight() { SAFE_REMOVE( headlight ); } void hhVehicle::Headlight( bool on ) { bHeadlightOn = on; float timeOffset = -MS2SEC(gameLocal.time); if( headlight.IsValid() ) { headlight->SetLightParm(SHADERPARM_TIMEOFFSET, timeOffset); SetShaderParm(7, timeOffset); if (bHeadlightOn) { headlight->SetLightParm(6, 1.0f); // Fade light in SetShaderParm(6, 1.0f); // Fade light cone in } else { headlight->SetLightParm(6, 0.0f); // Fade out SetShaderParm(6, 0.0f); // Fade out } } } void hhVehicle::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { if (InGodMode() || InDialogMode()) { return; } if (gameLocal.isMultiplayer) { //rww - don't let shuttles damage themselves or their teammates if (attacker && attacker->IsType(hhPlayer::Type) && GetPilot() == attacker) { return; } hhPlayer *playerPilot = (GetPilot() && GetPilot()->IsType( hhPlayer::Type )) ? static_cast(GetPilot()) : NULL; hhPlayer *player = (attacker && attacker->IsType( hhPlayer::Type )) ? static_cast(attacker) : NULL; const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); if ( (gameLocal.gameType == GAME_TDM && playerPilot && !gameLocal.serverInfo.GetBool( "si_teamDamage" ) && damageDef && !damageDef->GetBool( "noTeam" ) && player && player->team == playerPilot->team) ) { return; } } else { if ( noDamage ) { return; } if (attacker && attacker->IsType(idAI::Type) && GetPilot() == attacker) { return; } } if (GetPilotInterface() && GetPilotInterface()->GetPilot()) { const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); if (gameLocal.isMultiplayer && IsType(hhShuttle::Type)) { //rww - in mp, let's try flickering the tractor beam on and off when hit hhShuttle *shtlSelf = static_cast(this); if (shtlSelf->noTractorTime <= gameLocal.time && shtlSelf->TractorIsActive()) { //don't let it stay off for a long time from constant attack float dmg = ((float)damageDef->GetInt("damage", "100"))*damageScale; int dmgTime = (int)(dmg*12); //cap it to something reasonable on both ends if (dmgTime < 100) { dmgTime = 100; } else if (dmgTime > 1200) { dmgTime = 1200; } shtlSelf->noTractorTime = gameLocal.time + dmgTime; } } // Let playerview know about the impact direction if ( !damageDef ) { gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); return; } idVec3 damage_from, localDamageVector; damage_from = dir; damage_from.Normalize(); if ( GetPilotInterface()->GetPilot()->IsType(hhPlayer::Type)) { // Pass this on so we can get directional damage hhPlayer *playerPilot = static_cast(GetPilotInterface()->GetPilot()); playerPilot->viewAxis.ProjectVector( damage_from, localDamageVector ); playerPilot->playerView.DamageImpulse( localDamageVector, damageDef ); // Track last attacker for use in displaying HUD hit indicator if (!gameLocal.isClient) { playerPilot->ReportAttack(attacker); } if (gameLocal.isMultiplayer) { //rww - in MP, we want to damage the player a little from shuttle damage float plDmgScale = damageScale*0.2f; bool pilotWasTakingDamage = playerPilot->fl.takedamage; playerPilot->fl.takedamage = true; playerPilot->Damage(inflictor, attacker, dir, damageDefName, plDmgScale, INVALID_JOINT); playerPilot->fl.takedamage = pilotWasTakingDamage; if (playerPilot->health == 1) { //if the player died (or almost, kind of a hack) as a result, blow me up too damageDefName = "damage_suicide"; } } } } // Tell HUD to show impact direction if( IsVehicle() ) {//AI may want to know who hit them lastAttacker = attacker; lastAttackTime = gameLocal.time; } idEntity::Damage( inflictor, attacker, dir, damageDefName, 1.0f, location ); } void hhVehicle::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { if (gameLocal.isClient) { return; } if( gameLocal.isMultiplayer || health < spawnArgs.GetInt("gibhealth") ) { //rww - always explode in mp CancelEvents(&EV_VehicleExplode); PostEventMS(&EV_VehicleExplode, 0, attacker, dir.x, dir.y, dir.z); } else if( health + damage > 0 ) { // Only do this the first death ConsumePower(currentPower); if( domelight.IsValid() ) { domelight->SetLightParm(7, 1.0f); } StopSound( SND_CHANNEL_DYING, true ); StartSound( "snd_dying", SND_CHANNEL_DYING, 0, true ); // control release during the damage pipe was causing a crash when damaged by splash damage // the clip model is invalidated, then radius damage tries to apply to it. // Should be okay to switch back if "killed" is made into an event CancelEvents( &EV_VehicleExplode ); PostEventSec( &EV_VehicleExplode, spawnArgs.GetFloat("explodedelay"), attacker, dir.x, dir.y, dir.z ); } } bool hhVehicle::Collide( const trace_t &collision, const idVec3 &velocity ) { static const float minCollisionVelocity = 40.0f; static const float maxCollisionVelocity = 650.0f; // Velocity in normal direction float len = velocity * -collision.c.normal; if ( len > minCollisionVelocity && collision.c.material && !(collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT) ) { if (StartSound( "snd_bounce", SND_CHANNEL_BODY3 )) { // Change volume only after we know the sound played float volume = hhUtils::CalculateSoundVolume( len, minCollisionVelocity, maxCollisionVelocity ); HH_SetSoundVolume(volume, SND_CHANNEL_BODY3); } if (len > 200.0f) { // Damage other idEntity *other = gameLocal.GetTraceEntity(collision); if (other && bDamageOtherOnCollision) { idEntity *collideAttacker = this; if (GetPilotInterface() && GetPilotInterface()->GetPilot()) { //use pilot as attacker if we have one collideAttacker = GetPilotInterface()->GetPilot(); } float damageScale = hhUtils::CalculateScale(len, 200, 600); damageScale = (damageScale * 4.0f) + 1.0f; // damageScale in range [1..5] damageScale *= other->spawnArgs.GetFloat("vehicledamagescale"); // for fine tuning // NOTE: Assuming damage_vehicleCollision is set to 20! if (20 * damageScale >= other->health) { other->Damage(this, collideAttacker, vec3_origin, "damage_vehicleCollision_gib", damageScale, 0); } else { other->Damage(this, collideAttacker, vec3_origin, "damage_vehicleCollision", damageScale, 0); } } // Damage self if (bDamageSelfOnCollision) { Damage(other, other, collision.c.normal, "damage_vehicleCollision", 1.0f, 0); } } } return false; } bool hhVehicle::IsNoClipping() const { return GetPilotInterface() && GetPilotInterface()->GetPilot() && GetPilotInterface()->GetPilot()->IsType(hhPlayer::Type) && static_cast(GetPilotInterface()->GetPilot())->noclip; } bool hhVehicle::InGodMode() const { return GetPilotInterface() && GetPilotInterface()->GetPilot() && GetPilotInterface()->GetPilot()->IsType(hhPlayer::Type) && static_cast(GetPilotInterface()->GetPilot())->godmode; } bool hhVehicle::InDialogMode() const { return GetPilotInterface() && GetPilotInterface()->GetPilot() && GetPilotInterface()->GetPilot()->IsType(hhPlayer::Type) && static_cast(GetPilotInterface()->GetPilot())->InDialogDamageMode(); } void hhVehicle::GiveHealth( int amount ) { health = idMath::ClampInt( 0, spawnHealth, health + amount ); } void hhVehicle::GivePower( int amount ) { currentPower = idMath::ClampInt( 0, spawnArgs.GetInt("maxPower"), currentPower + amount ); } bool hhVehicle::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { idToken token; if( !src->ReadToken(&token) || token == ";" ) { return false; } if( !token.Icmp("controlvehicle") ) { if (IsHidden()) { //rww - was possible to get back in a vehicle once it had been hidden for removal return true; } if( entityGui->IsType(idActor::Type) ) { static_cast( entityGui )->EnterVehicle( this ); } return true; } return false; } void hhVehicle::DrawHUD( idUserInterface* _hud ) { if( _hud ) { //rww - transfer necessary hud variables from the player hud to the vehicle hud if (GetPilotInterface() && GetPilotInterface()->GetPilot() && GetPilotInterface()->GetPilot()->IsType(hhPlayer::Type)) { hhPlayer *plPilot = static_cast(GetPilotInterface()->GetPilot()); if (plPilot->hud) { //aiming _hud->SetStateFloat("aim_R", plPilot->hud->GetStateFloat("aim_R", "")); _hud->SetStateFloat("aim_G", plPilot->hud->GetStateFloat("aim_G", "")); _hud->SetStateFloat("aim_B", plPilot->hud->GetStateFloat("aim_B", "")); _hud->SetStateString("aim_text", plPilot->hud->GetStateString("aim_text", "")); //set in UpdateHud: /* _hud->SetStateString("playername", plPilot->hud->GetStateString("playername", "")); _hud->SetStateFloat("team_R", plPilot->hud->GetStateFloat("team_R", "")); _hud->SetStateFloat("team_G", plPilot->hud->GetStateFloat("team_G", "")); _hud->SetStateFloat("team_B", plPilot->hud->GetStateFloat("team_B", "")); _hud->SetStateBool("ismultiplayer", plPilot->hud->GetStateBool("ismultiplayer", "")); */ //set in UpdateHud: /* //top 4 for (int i = 0; i < 4; i++) { _hud->SetStateString( va( "player%i", i+1 ), plPilot->hud->GetStateString( va( "player%i", i+1 ), "")); _hud->SetStateString( va( "player%i_portrait", i+1 ), plPilot->hud->GetStateString(va( "player%i_portrait", i+1 ), "")); _hud->SetStateString( va( "player%i_score", i+1 ), plPilot->hud->GetStateString( va( "player%i_score", i+1 ), "")); _hud->SetStateString( va( "rank%i", i+1 ), plPilot->hud->GetStateString( va( "rank%i", i+1 ), "")); } _hud->SetStateString("rank_self", plPilot->hud->GetStateString("rank_self", "")); */ } } _hud->SetStateBool( "dying", health <= 0 ); _hud->SetStateFloat( "healthfraction", ((float)health)/(float)spawnHealth ); _hud->SetStateFloat( "powerfraction", (currentPower)/spawnArgs.GetFloat("maxPower") ); idAngles angles = GetAxis().ToAngles(); _hud->SetStateFloat( "pitch", angles.pitch ); _hud->SetStateFloat( "yaw", angles.yaw ); _hud->SetStateFloat( "roll", angles.roll ); _hud->Redraw( gameLocal.realClientTime ); } } void hhVehicle::PerformDeathAction(int deathAction, idActor *savedPilot, idEntity *attacker, idVec3 &dir) { switch(deathAction) { case 0: // Drop pilot break; case 1: // Kill pilot savedPilot->Damage(this, attacker, dir, spawnArgs.GetString("def_killpilotdamage"), 1.0f, 0); break; } } void hhVehicle::Explode( idEntity *attacker, idVec3 dir ) { if (gameLocal.isClient) { return; } if (!GetPilotInterface()) { return; } idEntityPtr savedPilot = GetPilotInterface()->GetPilot(); EjectPilot(); if (savedPilot.IsValid()) { int deathAction = savedPilot->IsType(hhPlayer::Type) ? spawnArgs.GetInt("DeathActionPlayer") : spawnArgs.GetInt("DeathActionAI"); PerformDeathAction(deathAction, savedPilot.GetEntity(), attacker, dir); } const char *deathExplosionDef = spawnArgs.GetString("def_deathexplosion", ""); if (deathExplosionDef && deathExplosionDef[0]) { gameLocal.RadiusDamage(GetOrigin(), this, this, this, this, deathExplosionDef); } // Explode into gibs idVec3 vel = dir * 300; hhUtils::SpawnDebrisMass(spawnArgs.GetString("def_debrisspawner"), GetPhysics()->GetOrigin(), NULL, &vel, 1); StopSound(SND_CHANNEL_DYING, true); StartSound("snd_death", SND_CHANNEL_ANY); } bool hhVehicle::HasPower( int amount ) const { return currentPower >= amount; } bool hhVehicle::ConsumePower( int amount ) { if( InGodMode() ) { return true; } if (currentPower >= amount) { currentPower -= amount; return true; } currentPower = 0; return false; } void hhVehicle::RemoveVehicle() { //rww - stop sounds too StopSound(SND_CHANNEL_THRUSTERS, true); StopSound(SND_CHANNEL_MISC1, true); StopSound(SND_CHANNEL_DYING, true); Hide(); GetPhysics()->SetContents( 0 ); GetPhysics()->SetClipMask( 0 ); PostEventMS( &EV_Remove, 3000 ); // Give anything targetting it a chance to retarget } void hhVehicle::WriteToSnapshot( idBitMsgDelta &msg ) const { physicsObj.WriteToSnapshot( msg ); assert(currentPower < (1<<20)); msg.WriteBits(currentPower, 20); assert(health < (1<<12)); msg.WriteBits(health, 12); msg.WriteBits(bHeadlightOn, 1); idCQuat modelQuat = modelAxis.ToCQuat(); msg.WriteFloat(modelQuat.x); msg.WriteFloat(modelQuat.y); msg.WriteFloat(modelQuat.z); msg.WriteBits(renderEntity.suppressSurfaceInViewID, GENTITYNUM_BITS); /* msg.WriteBits(currentPower, 32); msg.WriteFloat(thrustFactor); msg.WriteFloat(thrustMin); msg.WriteFloat(thrustMax); msg.WriteFloat(thrustAccel); msg.WriteFloat(thrustScale); msg.WriteFloat(dockBoostFactor); */ msg.WriteBits(IsNoClipping() ? 0 : vehicleClipMask, 32); msg.WriteBits(IsNoClipping() ? 0 : vehicleContents, 32); msg.WriteBits(IsHidden(), 1); msg.WriteBits(dock.GetSpawnId(), 32); msg.WriteBits(domelight.GetSpawnId(), 32); WriteBindToSnapshot( msg ); if (fireController) { //fire controller can be null at this point. msg.WriteBits(fireController->barrelOffsets.GetCurrentIndex(), 8); } else { msg.WriteBits(0, 8); } } void hhVehicle::ReadFromSnapshot( const idBitMsgDelta &msg ) { physicsObj.ReadFromSnapshot( msg ); currentPower = msg.ReadBits(20); health = msg.ReadBits(12); bool headlightOn = !!msg.ReadBits(1); if (headlightOn != bHeadlightOn) { Headlight(headlightOn); } idCQuat modelQuat; modelQuat.x = msg.ReadFloat(); modelQuat.y = msg.ReadFloat(); modelQuat.z = msg.ReadFloat(); modelAxis = modelQuat.ToMat3(); renderEntity.suppressSurfaceInViewID = msg.ReadBits(GENTITYNUM_BITS); /* currentPower = msg.ReadBits(32); thrustFactor = msg.ReadFloat(); thrustMin = msg.ReadFloat(); thrustMax = msg.ReadFloat(); thrustAccel = msg.ReadFloat(); thrustScale = msg.ReadFloat(); dockBoostFactor = msg.ReadFloat(); */ int newVehicleClipMask = msg.ReadBits(32); if (newVehicleClipMask != vehicleClipMask) { physicsObj.SetClipMask(newVehicleClipMask); vehicleClipMask = newVehicleClipMask; } int newVehicleContents = msg.ReadBits(32); if (newVehicleContents != vehicleContents) { physicsObj.SetContents(newVehicleContents); vehicleContents = newVehicleContents; } bool hidden = !!msg.ReadBits(1); if (hidden != IsHidden()) { if (hidden) { Hide(); } else { Show(); } } int spawnId; spawnId = msg.ReadBits(32); //rwwFIXME why is 0 check needed? something checking the container strangely? if (!spawnId) { dock = NULL; } else { dock.SetSpawnId(spawnId); } spawnId = msg.ReadBits(32); if (!spawnId) { domelight = NULL; } else { if (domelight.SetSpawnId(spawnId)) { domelight->Bind( this, true ); domelight->SetLightParm(SHADERPARM_TIMEOFFSET, -MS2SEC(gameLocal.time)); } } ReadBindFromSnapshot( msg ); int barrelIndex = msg.ReadBits(8); if (fireController) { fireController->barrelOffsets.SetCurrentIndex(barrelIndex); } } void hhVehicle::ClientPredictionThink( void ) { Think(); } bool hhVehicle::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { switch ( event ) { case EVENT_EJECT_PILOT: EjectPilot(); return true; default: return hhRenderEntity::ClientReceiveEvent( event, time, msg ); } } void hhVehicle::Event_Explode( idEntity *attacker, float dx, float dy, float dz ) { if (InDialogMode()) { // Death not allowed, repost CancelEvents(&EV_VehicleExplode); PostEventMS(&EV_VehicleExplode, 1000, attacker, dx, dy, dz); return; } idVec3 dir(dx, dy, dz); Explode(attacker, dir); } void hhVehicle::Event_ResetGravity() { ResetGravity(); } void hhVehicle::Event_FireCannon() { if( fireController && fireController->LaunchProjectiles(vec3_zero) ) { StartSound( "snd_cannon", SND_CHANNEL_ANY ); fireController->MuzzleFlash(); if (GetPilot() && GetPilot()->IsType(hhPlayer::Type)) { // Only players get weapon fire feedback, so monster shuttles aren't pushed back into docks fireController->WeaponFeedback(); } } } // Script control interfaces void hhVehicle::Event_GetIn( idEntity *ent ) { if (ent->IsType(idActor::Type)) { static_cast( ent )->EnterVehicle( this ); GetPilotInterface()->UnderScriptControl( true ); } } void hhVehicle::Event_GetOut() { if (GetPilotInterface()) { GetPilotInterface()->UnderScriptControl( true ); EjectPilot(); } } void hhVehicle::Event_Fire( bool start ) { if( GetPilotInterface() ) { GetPilotInterface()->UnderScriptControl( true ); GetPilotInterface()->Fire( start ); } } void hhVehicle::Event_AltFire( bool start ) { if( GetPilotInterface() ) { GetPilotInterface()->UnderScriptControl( true ); GetPilotInterface()->AltFire( start ); } } void hhVehicle::Event_OrientTowards( idVec3 &point, float speed ) { if( GetPilotInterface() ) { GetPilotInterface()->UnderScriptControl( true ); GetPilotInterface()->OrientTowards( point, speed ); // passed as percentage of max } } void hhVehicle::Event_StopOrientingTowards() { if( GetPilotInterface() ) { GetPilotInterface()->UnderScriptControl( true ); GetPilotInterface()->OrientTowards( vec3_origin, 0.0f ); } } void hhVehicle::Event_ThrustTowards( idVec3 &point, float speed ) { idVec3 pt = point; if( GetPilotInterface() ) { GetPilotInterface()->UnderScriptControl( true ); GetPilotInterface()->ThrustTowards( pt, speed ); // passed as percentage of max } } void hhVehicle::Event_StopThrustingTowards() { if( GetPilotInterface() ) { GetPilotInterface()->UnderScriptControl( true ); GetPilotInterface()->ThrustTowards( vec3_origin, 0.0f ); } } void hhVehicle::Event_ReleaseScriptControl() { if( GetPilotInterface() ) { GetPilotInterface()->UnderScriptControl( false ); GetPilotInterface()->ClearBufferedCmds(); } } void hhVehicle::Event_EjectPilot() { EjectPilot(); }