#include "precompiled.h" #pragma hdrstop #include "prey_local.h" #define DAMAGE_INDICATOR_TIME 1100 // Update this in hud_damageindicator.guifragment too const idEventDef EV_PlayWeaponAnim( "playWeaponAnim", "sd" ); const idEventDef EV_RechargeHealth( "", NULL ); const idEventDef EV_RechargeRifleAmmo( "", NULL ); const idEventDef EV_Cinematic( "cinematic", "dd" ); const idEventDef EV_DialogStart( "dialogStart", "ddd" ); const idEventDef EV_DialogStop( "dialogStop", NULL ); const idEventDef EV_LotaTunnelMode( "lotaTunnelMode", "d" ); const idEventDef EV_DrainSpiritPower( "", NULL ); const idEventDef EV_SpawnDeathWraith( "", NULL ); const idEventDef EV_PrepareToResurrect( "prepareToResurrect" ); const idEventDef EV_ResurrectScreenFade( "" ); const idEventDef EV_Resurrect( "resurrect" ); const idEventDef EV_PrepareForDeathWorld( "", NULL ); const idEventDef EV_EnterDeathWorld( "", NULL ); const idEventDef EV_AdjustSpiritPowerDeathWalk( "" ); const idEventDef EV_SetOverlayMaterial( "setOverlayMaterial", "sd" ); const idEventDef EV_SetOverlayTime( "setOverlayTime", "fd" ); const idEventDef EV_SetOverlayColor( "setOverlayColor", "ffff" ); const idEventDef EV_DDAHeartbeat( "", NULL ); const idEventDef EV_ShouldRemainAlignedToAxial( "shouldRemainAlignedToAxial", "d" ); const idEventDef EV_StartHudTranslation( "" ); const idEventDef EV_Unfreeze( "" ); const idEventDef EV_GetSpiritPower( "getSpiritPower", "", 'f' ); //rww const idEventDef EV_SetSpiritPower( "setSpiritPower", "f" ); //rww const idEventDef EV_OnGround( "onGround", NULL, 'f' ); // bg const idEventDef EV_BindUnfroze( "", "e" ); // mdl const idEventDef EV_LockWeapon( "lockWeapon", "d" ); // mdl const idEventDef EV_UnlockWeapon( "unlockWeapon", "d" ); // mdl const idEventDef EV_SetPrivateCameraView( "setPrivateCameraView", "Ed" ); //rdr const idEventDef EV_SetCinematicFOV( "setCinematicFOV", "ffff" ); //rdr const idEventDef EV_StopSpiritWalk( "stopSpiritWalk" ); //rww const idEventDef EV_DamagePlayer( "damagePlayer", "eevsfd" ); //rww const idEventDef EV_GetSpiritProxy( "getSpiritProxy", "", 'e' ); //jsh const idEventDef EV_IsSpiritWalking( "isSpiritWalking", "", 'd' ); // pdm const idEventDef EV_IsDeathWalking( "isDeathWalking", "", 'd' ); // bjk const idEventDef EV_GetDDAValue( "getDDAValue", "", 'f' ); // cjr const idEventDef EV_AllowLighter( "allowLighter", "d" ); // jsh const idEventDef EV_DisableSpirit( "disableSpirit" ); // mdl const idEventDef EV_EnableSpirit( "enableSpirit" ); // mdl const idEventDef EV_UpdateDDA( "" ); // cjr const idEventDef EV_AllowDamage( "" ); // mdl const idEventDef EV_IgnoreDamage( "" ); // mdl const idEventDef EV_RespawnCleanup( "" ); //rww const idEventDef EV_ReturnToWeapon( "returnToWeapon", "", 'd' ); //bjk const idEventDef EV_CanAnimateTorso( "canAnimateTorso", "", 'd' ); //rww CLASS_DECLARATION( idPlayer, hhPlayer ) EVENT( EV_PlayWeaponAnim, hhPlayer::Event_PlayWeaponAnim ) EVENT( EV_RechargeHealth, hhPlayer::Event_RechargeHealth ) EVENT( EV_RechargeRifleAmmo, hhPlayer::Event_RechargeRifleAmmo ) EVENT( EV_Cinematic, hhPlayer::Event_Cinematic ) EVENT( EV_DialogStart, hhPlayer::Event_DialogStart ) EVENT( EV_DialogStop, hhPlayer::Event_DialogStop ) EVENT( EV_LotaTunnelMode, hhPlayer::Event_LotaTunnelMode ) EVENT( EV_DrainSpiritPower, hhPlayer::Event_DrainSpiritPower ) EVENT( EV_SpawnDeathWraith, hhPlayer::Event_SpawnDeathWraith ) EVENT( EV_PrepareToResurrect, hhPlayer::Event_PrepareToResurrect ) EVENT( EV_ResurrectScreenFade, hhPlayer::Event_ResurrectScreenFade ) EVENT( EV_Resurrect, hhPlayer::Event_Resurrect ) EVENT( EV_EnterDeathWorld, hhPlayer::Event_EnterDeathWorld ) EVENT( EV_PrepareForDeathWorld, hhPlayer::Event_PrepareForDeathWorld ) EVENT( EV_AdjustSpiritPowerDeathWalk, hhPlayer::Event_AdjustSpiritPowerDeathWalk ) EVENT( EV_ShouldRemainAlignedToAxial, hhPlayer::Event_ShouldRemainAlignedToAxial ) EVENT( EV_OrientToGravity, hhPlayer::Event_OrientToGravity ) EVENT( EV_ResetGravity, hhPlayer::Event_ResetGravity ) EVENT( EV_SetOverlayMaterial, hhPlayer::Event_SetOverlayMaterial ) EVENT( EV_SetOverlayTime, hhPlayer::Event_SetOverlayTime ) EVENT( EV_SetOverlayColor, hhPlayer::Event_SetOverlayColor ) EVENT( EV_DDAHeartbeat, hhPlayer::Event_DDAHeartBeat ) EVENT( EV_StartHudTranslation, hhPlayer::Event_StartHUDTranslation) EVENT( EV_Unfreeze, hhPlayer::Event_Unfreeze) EVENT( EV_GetSpiritPower, hhPlayer::Event_GetSpiritPower) //rww EVENT( EV_SetSpiritPower, hhPlayer::Event_SetSpiritPower) //rww EVENT( EV_OnGround, hhPlayer::Event_OnGround ) // bg EVENT( EV_LockWeapon, hhPlayer::Event_LockWeapon ) // mdl EVENT( EV_UnlockWeapon, hhPlayer::Event_UnlockWeapon ) // mdl EVENT( EV_SetPrivateCameraView, hhPlayer::Event_SetPrivateCameraView ) //rdr EVENT( EV_SetCinematicFOV, hhPlayer::Event_SetCinematicFOV ) //rdr EVENT( EV_StopSpiritWalk, hhPlayer::Event_StopSpiritWalk ) //rww EVENT( EV_DamagePlayer, hhPlayer::Event_DamagePlayer ) //rww EVENT( EV_GetSpiritProxy, hhPlayer::Event_GetSpiritProxy ) //rww EVENT( EV_IsSpiritWalking, hhPlayer::Event_IsSpiritWalking ) //pdm EVENT( EV_IsDeathWalking, hhPlayer::Event_IsDeathWalking ) //bjk EVENT( EV_GetDDAValue, hhPlayer::Event_GetDDAValue ) // cjr EVENT( EV_AllowLighter, hhPlayer::Event_AllowLighter ) // jsh EVENT( EV_DisableSpirit, hhPlayer::Event_DisableSpirit ) // mdl EVENT( EV_EnableSpirit, hhPlayer::Event_EnableSpirit ) // mdl EVENT( EV_UpdateDDA, hhPlayer::Event_UpdateDDA ) // cjr EVENT( EV_AllowDamage, hhPlayer::Event_AllowDamage ) // mdl EVENT( EV_IgnoreDamage, hhPlayer::Event_IgnoreDamage ) // mdl EVENT( EV_RespawnCleanup, hhPlayer::Event_RespawnCleanup ) //rww EVENT( EV_ReturnToWeapon, hhPlayer::Event_ReturnToWeapon ) //bjk EVENT( EV_CanAnimateTorso, hhPlayer::Event_CanAnimateTorso ) //rww END_CLASS //rww CLASS_DECLARATION( hhPlayer, hhArtificialPlayer ) END_CLASS /* ================= hhPlayer::hhPlayer ================= */ hhPlayer::hhPlayer( void ) : thirdPersonCameraClipBounds( idTraceModel(idBounds(idVec3(-4, -8, -8), idVec3(8, 8, 8))) ) { hand = NULL; // nla spiritProxy = NULL; possessedTommy = NULL; talon = NULL; handNext = NULL; deathLookAtEntity = NULL; guiWantsControls = NULL; lighterHandle = -1; nextTalonAttackCommentTime = 0; bTalonAttackComment = false; bSpiritWalk = false; bDeathWalk = false; bReallyDead = false; bPossessed = false; bInCinematic = false; bFrozen = false; bScopeView = false; //rww bInDeathwalkTransition = false; lastAppliedBobCycle = 0; #if GAMEPAD_SUPPORT // VENOM BEGIN lastAutoLevelTime = 0; lastAccelTime = 0; lastAccelFactor = 1.0f; #endif // VENOM END cinematicFOV.Init( gameLocal.time, 0.f, 0.f, 0.f, 90.f, 90.f ); //rdr mpHitFeedbackTime = 0; //rww bAllowSpirit = true; //mdl bCollidingWithPortal = false; bPlayingLowHealthSound = false; bLotaTunnelMode = false; for (int ix=0; ix 8 ) { // Go to the highest weapon available if no weapon is current selected (NOTE: This will be ignored if all weapons are locked) NextBestWeapon(); //HUMANHEAD bjk } //HUMANHEAD PCF mdl 04/26/06 - Made this a separate if block from above so we can disallow locked weapons if ( ! ( weaponFlags & ( 1 << ( idealWeapon - 1 ) ) ) ) { // If the current weapon is invalid now, try the next weapon NextWeapon(); if ( ! ( weaponFlags & ( 1 << ( idealWeapon - 1 ) ) ) ) { // No weapons available if ( weapon.GetEntity() ) { weapon.GetEntity()->PutAway(); weapon.GetEntity()->HideWeapon(); } idealWeapon = 0; currentWeapon = -1; } } nextSpiritTime = 0; mpHitFeedbackTime = 0; //rww airAttackerTime = 0; bDeathWalkStage2 = false; physicsObj.SetInwardGravity(-1); //rww } void hhPlayer::RestorePersistantInfo( void ) { int num; idPlayer::RestorePersistantInfo(); // Update persistent amoo maximums num = GetWeaponNum("weaponobj_soulstripper"); assert(num); weaponInfo[ num ].ammoMax = spawnArgs.GetInt( "max_ammo_energy" ); num = GetWeaponNum("weaponobj_bow"); assert(num); weaponInfo[ num ].ammoMax = spawnArgs.GetInt( "max_ammo_spiritpower" ); } /* ============== hhPlayer::SpawnTalon() ============== */ void hhPlayer::TrySpawnTalon() { // TODO: Deal with co-op issues. Have multiple Talons in co-op, or just one? if (inventory.requirements.bCanSummonTalon && !talon.IsValid()) { talon = (hhTalon *)gameLocal.SpawnObject( spawnArgs.GetString( "def_hawkpower" ), NULL ); if( talon.IsValid() ) { talon->SetOwner( this ); talon->SummonTalon(); bTalonAttackComment = true; nextTalonAttackCommentTime = 0; } } } /* ============== hhPlayer::TalonAttackComment Talon is attacking an enemy, so have Tommy make a comment ============== */ void hhPlayer::TalonAttackComment() { if ( IsDeathWalking() ) { return; } // Play a sound on the player to encourage Talon to attack if ( bTalonAttackComment && gameLocal.GetTime() > nextTalonAttackCommentTime ) { // Player can make an attack comment int num = spawnArgs.GetInt( "numAttackComments", "6" ); StartSound( va( "snd_talonattack%d", gameLocal.random.RandomInt( num ) ), SND_CHANNEL_VOICE, 0, false, NULL ); nextTalonAttackCommentTime = gameLocal.GetTime() + spawnArgs.GetInt( "talonAttackCommentTime", "30000" ); } } /* ============== hhPlayer::~hhPlayer() Release any resources used by the player. ============== */ hhPlayer::~hhPlayer() { //rww - remove me if i'm a pilot if (InVehicle() && GetVehicleInterface() && GetVehicleInterface()->GetVehicle()) { GetVehicleInterface()->GetVehicle()->EjectPilot(); } StopSpiritWalk(); RemoveResources(); } void hhPlayer::RemoveResources() { LighterOff(); SAFE_REMOVE( hand ); //FIXME: Should this be something other than a safe remove? (ie, remove hand?) SAFE_REMOVE( talon ); SAFE_REMOVE( spiritProxy ); SAFE_REMOVE( possessedTommy ); } /* ============== hhPlayer::Init() Called every time a client is placed fresh in the world: after the first ClientBegin, and after each respawn Initializes all non-persistant parts of playerState Called during SpawnToPoint ============== */ void hhPlayer::Init() { oldCmdAngles.Zero(); idPlayer::Init(); // initialize the script variables AI_ASIDE = false; bInDeathwalkTransition = false; bShowProgressBar = false; progressBarValue = 0.0f; progressBarState = 0; progressBarGuiValue.Init(gameLocal.time, 0, 0.0f, 0.0f); LighterOff(); //rww - make sure lighter is removed first (primarily for mp) bobFrac = 0.0f; lighterTemperature = 0; lighterHandle = -1; bPossessed = false; // cjr - player is unpossessed at the start possessionTimer = 0; // cjr - player is unpossessed at the start possessionFOV = 0; // cjr - no FOV change for possession at start nextTalonAttackCommentTime = 0; bTalonAttackComment = false; // cjr - If Tommy can make comments about talon attacking enemies bSpiritWalk = false; // cjr - initialize spiritwalk variables spiritProxy = NULL; // cjr - initialize spiritwalk variables possessedTommy = NULL; // cjr - initialize possessed tommy lastWeaponSpirit = 0; // cjr - initialize spiritwalk variables guiWantsControls = NULL; // pdm - gui that controls should be routed to bClampYaw = false; // pdm - whether do clamp yaw to master's axis (for rail rides) maxRelativeYaw = 180.0f; // pdm - max deviation from master axis allowed when clamping yaw maxRelativePitch = 0.0f; // rww - max deviation from master axis allowed when clamping pitch bInCinematic = false; // pdm - our simple cinematic system (position locking) lockView = false; preCinematicWeapon = 0; preCinematicWeaponFlags = 0; guiOverlay = NULL; // pdm - current overlay gui spiritWalkToggleTime = 0; bLotaTunnelMode = false; StopSound(SND_CHANNEL_HEART, false); //rww - make sure this is stopped here, could conceivably still be playing on respawn bPlayingLowHealthSound = false; // Set shuttle view off if (entityNumber == gameLocal.localClientNum) { //rww renderSystem->SetShuttleView( false ); } if (gameLocal.isMultiplayer) { //rww - reset my really-deadness when i respawn in MP bReallyDead = false; SetSkinByName(NULL); //make sure we are not using spiritwalk skin. } SetViewAnglesSensitivity(1.0f); cameraInterpolator.SetSelf( this ); for (int ix=0; ixIsType(hhForceField::Type) || ent->IsType(hhDeathWraith::Type)) ) { idPlayer::ApplyImpulse( ent, id, point, impulse ); } } /* ================ hhPlayer::CL_UpdateProgress Client function to update progress related variables called after getting a snapshot, or directly if a local client ================ */ void hhPlayer::CL_UpdateProgress(bool bBar, float value, int state) { idUserInterface *_hud = NULL; if (InVehicle() && GetVehicleInterfaceLocal() && GetVehicleInterfaceLocal()->GetHUD()) { _hud = GetVehicleInterfaceLocal()->GetHUD(); } else { _hud = hud; } // Update progress bar if (bBar && !bShowProgressBar) { _hud->HandleNamedEvent("ShowProgressBar"); } else if (!bBar && bShowProgressBar) { _hud->HandleNamedEvent("HideProgressBar"); } // See if we just entered a victory state if (state != progressBarState) { switch (state) { case 0: _hud->HandleNamedEvent("ProgressNone"); break; case 1: _hud->HandleNamedEvent("ProgressVictory"); break; case 2: _hud->HandleNamedEvent("ProgressFailure"); break; } } // Interpolate gui value to target value float curValue = progressBarGuiValue.GetCurrentValue(gameLocal.time); progressBarGuiValue.Init(gameLocal.time, 500, curValue, value); // Update variables bShowProgressBar = bBar; progressBarValue = value; progressBarState = state; } // This called once per tick, put things in here that should be updated regardless of frame rate // PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge void hhPlayer::UpdateHud( idUserInterface *_hud ) { idPlayer *aimed; if ( !_hud ) { return; } if ( entityNumber != gameLocal.localClientNum ) { return; } #define PICKUPTIME_FADEIN_START 0 #define PICKUPTIME_FADEIN_END 500 #define PICKUPTIME_FADEOUT_START 1750 #define PICKUPTIME_FADEOUT_END 2000 #define MULTIPLEITEM_SPEEDUP 7 int c = inventory.pickupItemNames.Num(); int time; int i; int pickupTimeFadeInStart, pickupTimeFadeInStop, pickupTimeFadeOutStart, pickupTimeFadeOutStop; float scale = 1.0f / (((c-1.0f)/9.0f) * (MULTIPLEITEM_SPEEDUP-1.0f) + 1.0f); pickupTimeFadeInStart = PICKUPTIME_FADEIN_START; pickupTimeFadeInStop = PICKUPTIME_FADEIN_END; pickupTimeFadeOutStart = PICKUPTIME_FADEOUT_START * scale; pickupTimeFadeOutStop = PICKUPTIME_FADEOUT_END * scale; // Determine fade in color for icons if (c > 0) { for ( i = 0; i < c; i++ ) { inventory.pickupItemNames[i].time += gameLocal.msec; time = inventory.pickupItemNames[i].time; if (time < pickupTimeFadeInStart) { inventory.pickupItemNames[i].matcolorAlpha = 0.0f; } else if (time < pickupTimeFadeInStop) { inventory.pickupItemNames[i].matcolorAlpha = ((float)(time-pickupTimeFadeInStart))/(pickupTimeFadeInStop-pickupTimeFadeInStart); } else { inventory.pickupItemNames[i].matcolorAlpha = 1.0f; } } // Determine fadeout of slot zero icon inventory.pickupItemNames[0].slotZeroTime += gameLocal.msec; time = inventory.pickupItemNames[0].slotZeroTime; if (time < pickupTimeFadeOutStart) { inventory.pickupItemNames[0].matcolorAlpha = 1.0f; } else if (time < pickupTimeFadeOutStop) { inventory.pickupItemNames[0].matcolorAlpha = 1.0f - ((float)(time-pickupTimeFadeOutStart))/(pickupTimeFadeOutStop-pickupTimeFadeOutStart); } else { inventory.pickupItemNames[0].matcolorAlpha = 0.0f; } // Remove any expired icons time = inventory.pickupItemNames[0].slotZeroTime; if (time > pickupTimeFadeOutStop) { // icon has faded out, remove from list and slide them down inventory.pickupItemNames.RemoveIndex(0); } } // HUMANHEAD pdm: Changed aim logic to always show color-coded aimee if ( MPAim != -1 && gameLocal.entities[ MPAim ] && gameLocal.entities[ MPAim ]->IsType( idPlayer::Type ) ) { aimed = static_cast< idPlayer * >( gameLocal.entities[ MPAim ] ); _hud->SetStateString( "aim_text", gameLocal.userInfo[ MPAim ].GetString( "ui_name" ) ); idVec4 teamColor = aimed->GetTeamColor(); _hud->SetStateFloat( "aim_R", teamColor.x ); _hud->SetStateFloat( "aim_G", teamColor.y ); _hud->SetStateFloat( "aim_B", teamColor.z ); //HUMANHEAD rww - health display on hud for gameplay testing if (g_showAimHealth.GetBool()) { _hud->SetStateInt("aim_health", gameLocal.entities[MPAim]->health); } //HUMANHEAD END } else { _hud->SetStateString( "aim_text", "" ); //HUMANHEAD rww _hud->SetStateInt("aim_health", 0); //HUMANHEAD END } #if !GOLD _hud->SetStateInt( "g_showProjectilePct", g_showProjectilePct.GetInteger() ); if ( numProjectilesFired ) { _hud->SetStateString( "projectilepct", va( "Hit %% %.1f", ( (float) numProjectileHits / numProjectilesFired ) * 100 ) ); } else { _hud->SetStateString( "projectilepct", "Hit % 0.0" ); } #endif if (gameLocal.isNewFrame) { //HUMANHEAD rww - only on newframe // Update low health sound if ( health > 0 && health < 25 && !IsSpiritOrDeathwalking() ) { if (!bPlayingLowHealthSound) { StartSound("snd_lowhealth", SND_CHANNEL_HEART, 0, false, NULL); bPlayingLowHealthSound = true; } } else { if (bPlayingLowHealthSound) { StopSound(SND_CHANNEL_HEART, false); bPlayingLowHealthSound = false; } } } // Handle damage indictors idVec3 localDamageVector; idVec3 toEnemy; for (int ix=0; ixHandleNamedEvent( va("Attacked%i", ix) );//Notify hud we were attacked _hud->SetStateBool("displayDamageIndicators", true); lastAttackers[ix].displayed = true; } toEnemy = lastAttackers[ix].attacker->GetOrigin() - GetOrigin(); toEnemy.z = 0.0f; GetAxis().ProjectVector( toEnemy, localDamageVector ); float yawToEnemy = idMath::AngleNormalize180(localDamageVector.ToYaw()); _hud->SetStateFloat( va("yawToEnemy%i", ix), yawToEnemy); if (gameLocal.time > lastAttackers[ix].time + DAMAGE_INDICATOR_TIME) { lastAttackers[ix].attacker = NULL; bool allExpired = true; for (int jx=0; jxSetStateBool("displayDamageIndicators", false); } } } } if (gameLocal.isMultiplayer) { bool bLagged = isLagged && gameLocal.isMultiplayer && (gameLocal.localClientNum == entityNumber); _hud->SetStateBool("hudLag", bLagged); } // HUMANHEAD PCF pdm 06-26-06: Fix for multiplayer shuttle hud elements appearing in SP after a MP session else { _hud->SetStateBool( "ismultiplayer", false ); } // HUMANHEAD END } // PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge void hhPlayer::UpdateHudWeapon(bool flashWeapon) { idUserInterface *_hud = hhPlayer::hud; // if updating the hud of a followed client if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) { idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] ); if ( p->spectating && p->spectator == entityNumber ) { assert( p->hud ); _hud = p->hud; } } if ( _hud ) { // HUMANHEAD pdm: changed to suit our needs _hud->SetStateInt( "currentweapon", GetCurrentWeapon() ); _hud->SetStateInt( "idealweapon", GetIdealWeapon() ); //HUMANHEAD PCF mdl 05/05/06 - Added !IsLocked( GetIdealWeapon() ) if ( flashWeapon && !IsLocked( GetIdealWeapon() ) ) { _hud->HandleNamedEvent( "weaponChange" ); } } } // PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge void hhPlayer::UpdateHudAmmo(idUserInterface *_hud) { assert( _hud ); float ammoPct, altPct; int ammoType, altAmmoType; float ammo, altAmmo; bool ammoLow, altAmmoLow; // HUMANHEAD pdm: Weapon switch overlay, using rover to spread the expense over frames static int rover = 0; if (++rover > 9) { rover = 0; } if (rover) { bool bHeld = false; ammoPct = 0.0f; altPct = 0.0f; ammoType = weaponInfo[rover].ammoType; altAmmoType = altWeaponInfo[rover].ammoType; ammo = inventory.ammo[ammoType]; altAmmo = inventory.ammo[altAmmoType]; ammoLow = false; altAmmoLow = false; if ( inventory.weapons & ( 1 << rover ) ) { // have this weapon bHeld = true; ammoPct = ammo / weaponInfo[rover].ammoMax; altPct = altAmmo / altWeaponInfo[rover].ammoMax; ammoLow = ammo > 0 && ammo <= weaponInfo[rover].ammoLow; altAmmoLow = altAmmo > 0 && altAmmo <= altWeaponInfo[rover].ammoLow; } // Spirit ammo doesn't display in a bar if ( ammoType == 1 ) { ammoType = 0; altAmmoType = 0; } // Zero out alt-ammo bar if appropriate if (altAmmoType == 0 || altAmmoType == ammoType) { altPct = 0.0f; } _hud->SetStateBool (va("weapon%d_held", rover), bHeld); _hud->SetStateBool (va("weapon%d_ammolow", rover), ammoLow); _hud->SetStateBool (va("weapon%d_altammolow", rover), altAmmoLow); _hud->SetStateFloat(va("weapon%d_ammo", rover), ammoPct); _hud->SetStateFloat(va("weapon%d_altammo", rover), altPct); _hud->SetStateBool (va("weapon%d_ammoempty", rover), ammoType != 0 && ammo == 0 && altAmmo == 0); } // Update the current weapon's ammo status bool bDisallowAmmoBars = false; ammoPct = 0.0f; altPct = 0.0f; ammoLow = false; altAmmoLow = false; ammoType = 0; altAmmoType = 0; ammo = 0; altAmmo = 0; if( bLotaTunnelMode || privateCameraView || IsLocked(idealWeapon) || !weapon.IsValid() || !weapon->IsLinked() || currentWeapon == -1) { // Don't display ammo bar for invalid weapons, or when weapons are locked bDisallowAmmoBars = true; } else { if (currentWeapon >= 0 && currentWeapon < 15) { ammoType = weaponInfo[currentWeapon].ammoType; altAmmoType = altWeaponInfo[currentWeapon].ammoType; ammo = inventory.ammo[ammoType]; altAmmo = inventory.ammo[altAmmoType]; ammoPct = ammo / weaponInfo[currentWeapon].ammoMax; altPct = altAmmo / altWeaponInfo[currentWeapon].ammoMax; ammoLow = ammo > 0 && ammo <= weaponInfo[currentWeapon].ammoLow; altAmmoLow = altAmmo > 0 && altAmmo <= altWeaponInfo[currentWeapon].ammoLow; } if (ammoType == 1) { bDisallowAmmoBars = true; } } if ( bDisallowAmmoBars ) { _hud->SetStateBool( "player_ammobar", false); _hud->SetStateBool( "player_altammobar", false ); } else { _hud->SetStateBool( "player_ammobar", ammoType != 0); _hud->SetStateBool( "player_altammobar", altAmmoType != 0 && altAmmoType != ammoType ); _hud->SetStateFloat( "player_ammopercent", ammoPct ); _hud->SetStateFloat( "player_altammopercent", altPct ); _hud->SetStateString( "player_ammoamounttext", ammo<0 ? "" : va("%d", ammo) ); _hud->SetStateString( "player_altammoamounttext", altAmmo<0 ? "" : va("%d", altAmmo) ); _hud->SetStateBool( "player_ammolow", ammoLow ); _hud->SetStateBool( "player_altammolow", altAmmoLow ); } } /* ================ hhPlayer::UpdateHudStats PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge ================ */ void hhPlayer::UpdateHudStats( idUserInterface *_hud ) { assert( _hud ); int spiritPower = GetSpiritPower(); // Disallow health if wrench is locked on a non-lota map --or-- if in a private camera bool disallowHealth = bLotaTunnelMode || (IsLocked(1) && !gameLocal.IsLOTA()) || privateCameraView != NULL; bool disallowProgress = bLotaTunnelMode || privateCameraView != NULL || IsDeathWalking(); if (disallowHealth) { int blah = 0; } else { int blah2 = 0; } _hud->SetStateBool("invehicle", InVehicle()); _hud->SetStateBool("deathwalking", IsDeathWalking()); _hud->SetStateBool("spiritwalking", IsSpiritWalking()); _hud->SetStateBool("lighter", IsLighterOn() ); _hud->SetStateBool("showhealth", !disallowHealth); _hud->SetStateBool("allowprogress", !disallowProgress); _hud->SetStateBool("showspiritpower", (!disallowHealth && !InVehicle() && (inventory.requirements.bCanSpiritWalk || IsDeathWalking()))); _hud->SetStateFloat("lightertemp", lighterTemperature); _hud->SetStateFloat( "player_spiritpercent", ((float)spiritPower)/inventory.maxSpirit ); _hud->SetStateFloat( "player_healthpercent", ((float)health)/inventory.maxHealth ); _hud->SetStateFloat( "player_healthR", inventory.maxHealth > 100 ? 0.75f : 0.6f ); _hud->SetStateFloat( "player_healthG", inventory.maxHealth > 100 ? 0.85f : 0.0f ); _hud->SetStateFloat( "player_healthB", inventory.maxHealth > 100 ? 1.0f : 0.0f ); _hud->SetStateFloat( "player_healthPulseR", inventory.maxHealth > 100 ? 1.0f : 1.0f ); _hud->SetStateFloat( "player_healthPulseG", inventory.maxHealth > 100 ? 1.0f : 0.0f ); _hud->SetStateFloat( "player_healthPulseB", inventory.maxHealth > 100 ? 1.0f : 0.0f ); _hud->SetStateInt( "player_health", health ); _hud->SetStateInt( "player_maxhealth", inventory.maxHealth ); //rww _hud->SetStateString( "player_spirit", spiritPower<0 ? "0" : va("%d", spiritPower) ); _hud->SetStateFloat( "progress", progressBarGuiValue.GetCurrentValue(gameLocal.time) ); if ( healthPulse ) { _hud->HandleNamedEvent( "healthPulse" ); StartSound( "snd_healthpulse", SND_CHANNEL_ITEM, 0, false, NULL ); healthPulse = false; } if ( spiritPulse ) { _hud->HandleNamedEvent( "spiritPulse" ); StartSound( "snd_spiritpulse", SND_CHANNEL_ITEM, 0, false, NULL ); spiritPulse = false; } if ( inventory.ammoPulse ) { _hud->HandleNamedEvent( "ammoPulse" ); inventory.ammoPulse = false; } if ( inventory.weaponPulse ) { // We need to update the weapon hud manually, but not // the armor/ammo/health because they are updated every // frame no matter what UpdateHudWeapon(); _hud->HandleNamedEvent( "weaponPulse" ); inventory.weaponPulse = false; } UpdateHudAmmo( _hud ); // Determine slots for our pickup icons int c = inventory.pickupItemNames.Num(); for ( int i=0; i<10; i++ ) { if (iSetStateString( va( "itemicon%i", i ), inventory.pickupItemNames[i].icon ); _hud->SetStateFloat( va( "itemalpha%i", i ), inventory.pickupItemNames[i].matcolorAlpha ); _hud->SetStateBool( va( "itemwide%i", i), inventory.pickupItemNames[i].bDoubleWide ); } else { _hud->SetStateString( va( "itemicon%i", i ), "" ); _hud->SetStateFloat( va( "itemalpha%i", i ), 0.0f ); _hud->SetStateBool( va( "itemwide%i", i), false ); } } } /* =============== hhPlayer::DrawHUD HUMANHEAD: This run only for local players PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge =============== */ void hhPlayer::DrawHUD( idUserInterface *_hud ) { if (guiOverlay) { guiOverlay->Redraw(gameLocal.realClientTime); sysEvent_t ev; const char *command; ev = sys->GenerateMouseMoveEvent( -2000, -2000 ); command = guiOverlay->HandleEvent( &ev, gameLocal.time ); // HandleGuiCommands( this, command ); return; } // HUMANHEAD pdm: removed weapon ptr check here since ours is invalid when in a vehicle // Also removed privateCameraView, since we want subtitles when they are on if ( gameLocal.GetCamera() || !_hud || !g_showHud.GetBool() || pm_thirdPerson.GetBool() ) { return; } UpdateHudStats( _hud ); if(weapon.IsValid()) { // HUMANHEAD bool allowGuiUpdate = true; //rww - update the weapon gui only if the owner is being spectated by this client, or is this client if ( gameLocal.localClientNum != entityNumber ) { // if updating the hud for a followed client if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) { idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] ); if ( !p->spectating || p->spectator != entityNumber ) { allowGuiUpdate = false; } } else { allowGuiUpdate = false; } } if (allowGuiUpdate) { weapon->UpdateGUI(); } } _hud->SetStateInt( "s_debug", cvarSystem->GetCVarInteger( "s_showLevelMeter" ) ); //HUMANHEAD aob: vehicle logic if ( InVehicle() ) { DrawHUDVehicle( _hud ); } else { _hud->Redraw( gameLocal.realClientTime ); } //HUMANHEAD END // weapon targeting crosshair if ( !GuiActive() ) { if ( cursor ) { UpdateCrosshairs(); cursor->Redraw( gameLocal.realClientTime ); } } } /* =============== hhPlayer::DrawHUDVehicle HUMANHEAD: This run only for local players =============== */ void hhPlayer::DrawHUDVehicle( idUserInterface* _hud ) { if (GetVehicleInterfaceLocal()) { GetVehicleInterfaceLocal()->DrawHUD( _hud ); } } /* =============== hhPlayer::FireWeapon =============== */ void hhPlayer::FireWeapon( void ) { idMat3 axis; idVec3 muzzle; if ( privateCameraView ) { return; } if ( g_editEntityMode.GetInteger() ) { GetViewPos( muzzle, axis ); if ( gameLocal.editEntities->SelectEntity( muzzle, axis[0], this ) ) { return; } } //HUMANHEAD: aob - removed ammo check because we allow weapons to change modes if( weapon.IsValid() ) { AI_ATTACK_HELD = true; weapon->BeginAttack(); } //HUMANEHAD END } //========================================================================= // // hhPlayer::FireWeaponAlt // //========================================================================= void hhPlayer::FireWeaponAlt( void ) { idMat3 axis; idVec3 muzzle; if ( privateCameraView ) { return; } if ( g_editEntityMode.GetBool() ) { GetViewPos( muzzle, axis ); if ( gameLocal.editEntities->SelectEntity( muzzle, axis[0], NULL ) ) { return; } } if ( gameLocal.isMultiplayer && spectating ) { static int lastSpectatorSwitch = 0; if ( gameLocal.time > lastSpectatorSwitch + 500 ) { spectator = gameLocal.GetNextClientNum( spectator ); idPlayer *player = gameLocal.GetClientByNum( spectator ); while ( player->spectating && player != this ) { player = gameLocal.GetClientByNum(spectator); } lastSpectatorSwitch = gameLocal.time; } return; } //HUMANHEAD: aob - removed ammo check because we allow weapons to change modes if( weapon.IsValid() ) { AI_ATTACK_HELD = true; weapon->BeginAltAttack(); } //HUMANEHAD END if ( hud ) { hud->HandleNamedEvent( "closeObjective" ); } } void hhPlayer::StopFiring( void ) { idPlayer::StopFiring(); if ( weapon.GetEntity() && weapon->IsType( hhWeaponRifle::Type ) ) { static_cast( weapon.GetEntity() )->ZoomOut(); } } /* =============== hhPlayer::HasAmmo =============== */ int hhPlayer::HasAmmo( ammo_t type, int amount ) { return inventory.HasAmmo( type, amount ); } /* =============== hhPlayer::UseAmmo =============== */ bool hhPlayer::UseAmmo( ammo_t type, int amount ) { return inventory.UseAmmo( type, amount ); } /* =============== hhPlayer::NextBestWeapon =============== */ void hhPlayer::NextBestWeapon( void ) { if ( ActiveGui() || IsSpiritOrDeathwalking() ) { return; } idPlayer::NextBestWeapon(); } /* =============== hhPlayer::SelectWeapon =============== */ void hhPlayer::SelectWeapon( int num, bool force ) { if ( ! ( weaponFlags & ( 1 << ( num - 1 ) ) ) ) { return; } if ( bFrozen || ActiveGui() || ( SkipWeapon(num) || IsSpiritOrDeathwalking() ) ) { return; } idPlayer::SelectWeapon( num, force ); } /* =============== hhPlayer::NextWeapon =============== */ void hhPlayer::NextWeapon( void ) { if ( ActiveGui() || IsSpiritOrDeathwalking() || bFrozen ) { // NOTE: Spirit weapon is disallowed from being cycled to using the weapon*_cycle key return; } //idPlayer::NextWeapon(); if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || privateCameraView || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { return; } if ( gameLocal.isClient ) { return; } // check if we have any weapons if ( !inventory.weapons ) { return; } const char *weap; int w, start; w = idealWeapon; start = w; while( 1 ) { w++; if ( w >= MAX_WEAPONS ) { // No weapon selected and nothing to select if ( start == -1 ) { idealWeapon = 0; currentWeapon = 0; return; } w = 0; } // Keep us from an infinite loop if no weapons are valid if ( w == start ) { if ( ! ( weaponFlags & ( 1 << ( w - 1 ) ) ) ) { idealWeapon = 0; currentWeapon = 0; } return; } weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) { continue; } if ( !weap[ 0 ] ) { continue; } if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) { continue; } // Make sure the weapon is valid for this level if ( ! ( weaponFlags & ( 1 << ( w - 1 ) ) ) ) { continue; } if ( inventory.HasAmmo( weap ) || inventory.HasAltAmmo( weap ) || spawnArgs.GetBool( va( "weapon%d_allowempty", w ) ) ) { break; } } if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) { idealWeapon = w; weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; UpdateHudWeapon(); } } /* =============== hhPlayer::PrevWeapon =============== */ void hhPlayer::PrevWeapon( void ) { if ( ActiveGui() || IsSpiritOrDeathwalking() || bFrozen ) { // NOTE: Spirit weapon is disallowed from being cycled to using the weapon*_cycle key return; } //idPlayer::PrevWeapon(); if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || privateCameraView || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { return; } if ( gameLocal.isClient ) { return; } // check if we have any weapons if ( !inventory.weapons ) { return; } const char *weap; int w = idealWeapon, start = w; if (w == -1) { w = MAX_WEAPONS - 1; } while( 1 ) { w--; if ( w < 0 ) { // No weapon selected and nothing to select if ( start == -1 ) { idealWeapon = 0; currentWeapon = 0; return; } w = MAX_WEAPONS - 1; } // Keep us from an infinite loop if no weapons are valid if ( w == start ) { if ( ! ( weaponFlags & ( 1 << ( w - 1 ) ) ) ) { idealWeapon = 0; currentWeapon = 0; } return; } weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) { continue; } if ( !weap[ 0 ] ) { continue; } if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) { continue; } // Make sure the weapon is valid for this level if ( ! ( weaponFlags & ( 1 << ( w - 1 ) ) ) ) { continue; } if ( inventory.HasAmmo( weap ) || inventory.HasAltAmmo( weap ) || spawnArgs.GetBool( va( "weapon%d_allowempty", w ) ) ) { break; } } if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) { idealWeapon = w; weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; UpdateHudWeapon(); } } /* =============== hhPlayer::SkipWeapon =============== */ bool hhPlayer::SkipWeapon( int weaponNum ) const { //No bow if not in spirit mode return !IsSpiritOrDeathwalking() && weaponNum == spawnArgs.GetInt("spirit_weapon"); } /* =============== hhPlayer::SnapDownCurrentWeapon HUMANHEAD: aob =============== */ void hhPlayer::SnapDownCurrentWeapon() { if( weapon.IsValid() ) { weapon->SnapDown(); } } /* =============== hhPlayer::SnapUpCurrentWeapon HUMANHEAD: aob =============== */ void hhPlayer::SnapUpCurrentWeapon() { if( weapon.IsValid() ) { weapon->SnapUp(); } } /* =============== hhPlayer::SelectEtherealWeapon HUMANHEAD: aob =============== */ void hhPlayer::SelectEtherealWeapon() { if ( GetCurrentWeapon() != 0 ) { lastWeaponSpirit = GetCurrentWeapon(); } weaponHandState.SetPlayer( this ); if (!gameLocal.isClient) { int spiritWeaponIndex = spawnArgs.GetInt("spirit_weapon"); const char *spiritWeaponName = NULL; if (bDeathWalk || (inventory.weapons & (1 << spiritWeaponIndex)) ) { spiritWeaponName = GetWeaponName(spiritWeaponIndex); } weaponHandState.Archive( spiritWeaponName, 0, (InGUIMode()) ? "guihand_normal" : NULL ); } if ( weapon.IsValid() ) { weapon->SetShaderParm( SHADERPARM_MODE, MS2SEC( gameLocal.time ) ); // Glow in the bow weapon->SetShaderParm( SHADERPARM_MISC, 1.0f ); // Turn on the arrow weapon->SetShaderParm( SHADERPARM_DIVERSITY, MS2SEC( gameLocal.time ) ); // Glow in the arrow } } /* =============== hhPlayer::PutawayEtherealWeapon HUMANHEAD: aob =============== */ void hhPlayer::PutawayEtherealWeapon() { //If we haven't actually changed weapons yet, put current weapon up if( GetCurrentWeapon() != lastWeaponSpirit ) { SnapDownCurrentWeapon(); } else { SnapUpCurrentWeapon(); } weaponHandState.RestoreFromArchive(); } /* =============== hhPlayer::UpdateWeapon PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge =============== */ void hhPlayer::UpdateWeapon( void ) { if ( IsDead() ) { // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override return; } assert( !spectating ); if ( gameLocal.isClient ) { // clients need to wait till the weapon and it's world model entity // are present and synchronized ( weapon.worldModel idEntityPtr to idAnimatedEntity ) if ( !weapon.GetEntity()->IsWorldModelReady() ) { return; } } else if (gameLocal.isMultiplayer) { //rww - projectile deferring if (weapon.IsValid()) { weapon->CheckDeferredProjectiles(); } } // always make sure the weapon is correctly setup before accessing it if ( weapon.GetEntity() && !weapon.GetEntity()->IsLinked() ) { if ( idealWeapon != -1 ) { animPrefix = spawnArgs.GetString( va( "def_weapon%d", idealWeapon ) ); weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ idealWeapon ] ); animPrefix.Strip( "weaponobj_" ); //HUMANHEAD rww assert( weapon.GetEntity()->IsLinked() ); } else { return; } } //HUMANEHAD rww if (weapon.IsValid() && weapon->IsType(hhWeaponSoulStripper::Type)) { hhWeaponSoulStripper *leechGun = static_cast(weapon.GetEntity()); leechGun->CheckCans(); } //HUMANHEAD END if (!InVehicle()) { if ( g_dragEntity.GetBool() ) { StopFiring(); dragEntity.Update( this ); if ( weapon.IsValid() ) { weapon.GetEntity()->FreeModelDef(); } return; } else if (ActiveGui()) { // gui handling overrides weapon use Weapon_GUI(); } else { Weapon_Combat(); } // Determine whether we are in aside state if ( weapon.IsValid() && weapon->IsAside() ) { if ( !AI_ASIDE ) { AI_ASIDE = true; SetState( "AsideWeapon" ); UpdateScript(); } } else { AI_ASIDE = false; } } //HUMANHEAD: aob - added weapon validity check if( weapon.IsValid() ) { // update weapon state, particles, dlights, etc weapon->PresentWeapon( showWeaponViewModel ); } // nla if ( hand.IsValid() ) { hand->Present(); } } /* =============== hhPlayer::InGUIMode nla - Used for the GUI hand to check if it should be up. (Coming back from spiritwalk) =============== */ bool hhPlayer::InGUIMode() { // Logic adapted from UpdateWeapon if ( ActiveGui() && !InVehicle() ) { return( true ); } return( false ); } /* =============== hhPlayer::Weapon_Combat PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge =============== */ void hhPlayer::Weapon_Combat( void ) { idMat3 axis; idVec3 muzzle; idDict args; if ( !weaponEnabled || gameLocal.inCinematic || privateCameraView ) { return; } // HUMANHEAD nla if ( hand.IsValid() && hand->IsType( hhGuiHand::Type ) && hand->IsAttached() && !hand->IsLowering() ) { hand->RemoveHand(); } // HUMANHEAD END if( idealWeapon != 0 && IsLocked(idealWeapon) ) { //HUMANHEAD bjk PCF (4-30-06) - fix wrench up in roadhouse NextWeapon(); } if ( idealWeapon == 0 ) { return; } RaiseWeapon(); if ( weapon.IsValid() ) { weapon.GetEntity()->PutUpright(); } if ( weapon.IsValid() && weapon->IsReloading() ) { if ( !AI_RELOAD ) { AI_RELOAD = true; SetState( "ReloadWeapon" ); UpdateScript(); } } else { AI_RELOAD = false; } if ( idealWeapon != currentWeapon && (!gameLocal.isMultiplayer || inventory.lastShot[idealWeapon] < gameLocal.time) ) { //HUMANHEAD bjk PATCH 7-27-06 if ( weaponCatchup ) { assert( gameLocal.isClient ); #ifndef HUMANHEAD //HUMANHEAD rww FIXME!!!! this also produces horrible memory leaks because of the dirty fire controller stuff //HUMANHEAD rww - our crazy weapon system does not work ok with this. did we change the weapon dictionary parsing? //it seems that it is very much dependant on the base class of weapon at the moment and that of course is going to be //that of currentWeapon and not idealWeapon. //currentWeapon = idealWeapon; //HUMANHEAD END weaponGone = false; animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ); weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ currentWeapon ] ); animPrefix.Strip( "weaponobj_" ); //HUMANHEAD pdm: changed from weapon_ to weaponobj_ per our naming convention weapon.GetEntity()->NetCatchup(); const function_t *newstate = GetScriptFunction( "NetCatchup" ); if ( newstate ) { SetState( newstate ); UpdateScript(); } #else assert( idealWeapon >= 0 ); assert( idealWeapon < MAX_WEAPONS ); animPrefix = spawnArgs.GetString( va( "def_weapon%d", idealWeapon ) ); const char *currentWeaponName = weapon->spawnArgs.GetString("classname", ""); //make sure the weapon i have from my snapshot, and the weapon i have selected are the same. if (currentWeaponName && !idStr::Cmp(animPrefix, currentWeaponName)) { weaponGone = false; currentWeapon = idealWeapon; weapon->GetWeaponDef( animPrefix, inventory.clip[ currentWeapon ] ); animPrefix.Strip( "weaponobj_" ); weapon->Raise(); #endif //HUMANHEAD END weaponCatchup = false; } } else { if ( weapon.IsValid() && (weapon->IsReady() || weapon->IsRising()) ) { InvalidateCurrentWeapon();//Needed incase we change weapons quickly, we can go back to old weapon weapon->PutAway(); } if ( ( !weapon.IsValid() || weapon->IsHolstered() ) && !bDeathWalk && ! bReallyDead ) { assert( idealWeapon >= 0 ); assert( idealWeapon < MAX_WEAPONS ); if ( currentWeapon > 0 && !spawnArgs.GetBool( va( "weapon%d_toggle", currentWeapon ) ) ) { //HUMANHEAD bjk previousWeapon = currentWeapon; } //HUMANHEAD PCF rww 05/03/06 - the local client might get skippiness between switching weapons if we //attempt to raise the weapon again before validating that the weapon ent is of the type that we //already desire to switch to. this bug is introduced by the concept of switching out entities when //changing weapons (not client-friendly). if (gameLocal.isClient && entityNumber == gameLocal.localClientNum) { if (weapon.IsValid() && weapon->GetDict() && idStr::Icmp(weapon->GetDict()->GetString("classname"), GetWeaponName( idealWeapon )) == 0) { currentWeapon = idealWeapon; weaponGone = false; animPrefix = GetWeaponName( currentWeapon ); animPrefix.Strip( "weaponobj_" ); //HUMANHEAD pdm: changed from weapon_ to weaponobj_ per our naming convention weapon.GetEntity()->Raise(); } } //HUMANHEAD END else { currentWeapon = idealWeapon; weaponGone = false; //HUMANHEAD: aob animPrefix = GetWeaponName( currentWeapon ); if (!gameLocal.isClient) { SAFE_REMOVE( weapon ); weapon = SpawnWeapon( animPrefix.c_str() ); } //HUMANHEAD END animPrefix.Strip( "weaponobj_" ); //HUMANHEAD pdm: changed from weapon_ to weaponobj_ per our naming convention //HUMANHEAD PCF rww 05/03/06 - safety check, make sure weapon is valid particularly for the client if (weapon.IsValid()) { weapon.GetEntity()->Raise(); } } } } } else if (!bDeathWalk && !bReallyDead) { weaponGone = false; // if you drop and re-get weap, you may miss the = false above if ( weapon.IsValid() && weapon.GetEntity()->IsHolstered() ) { if ( !weapon.GetEntity()->AmmoAvailable() ) { // weapons can switch automatically if they have no more ammo NextBestWeapon(); } else if( !gameLocal.isMultiplayer || inventory.lastShot[idealWeapon] < gameLocal.time ) { //HUMANHEAD bjk PATCH 9-11-06 weapon.GetEntity()->Raise(); state = GetScriptFunction( "RaiseWeapon" ); if ( state ) { SetState( state ); } } } } if ( weapon.IsValid() ) { weapon->PrecomputeTraceInfo(); } // check for attack AI_WEAPON_FIRED = false; if ( ( usercmd.buttons & BUTTON_ATTACK ) && !weaponGone ) { FireWeapon(); } else if ( oldButtons & BUTTON_ATTACK ) { AI_ATTACK_HELD = false; if( weapon.IsValid() ) { weapon.GetEntity()->EndAttack(); } } // HUMANHEAD if ( ( usercmd.buttons & BUTTON_ATTACK_ALT ) && !weaponGone ) { FireWeaponAlt(); } else if ( oldButtons & BUTTON_ATTACK_ALT ) { AI_ATTACK_HELD = false; if( weapon.IsValid() ) { weapon.GetEntity()->EndAltAttack(); } } // HUMANHEAD END // update our ammo clip in our inventory if ( weapon.IsValid() && ( currentWeapon >= 0 ) && ( currentWeapon < MAX_WEAPONS ) ) { inventory.clip[ currentWeapon ] = weapon->AmmoInClip(); inventory.altMode[ currentWeapon ] = weapon->GetAltMode(); } } /* =============== hhPlayer::SpawnWeapon =============== */ hhWeapon* hhPlayer::SpawnWeapon( const char* name ) { hhWeapon* weaponPtr = NULL; idDict args; args.SetVector( "origin", GetEyePosition() ); args.SetMatrix( "rotation", viewAngles.ToMat3() ); weaponPtr = static_cast( gameLocal.SpawnObject(name, &args) ); weaponPtr->SetOwner( this ); weaponPtr->Bind( this, true ); //HUMANHEAD: aob - removed last two params weaponPtr->GetWeaponDef( name ); return weaponPtr; } /* ===================== hhPlayer::GetWeaponNum HUMANHEAD ===================== */ int hhPlayer::GetWeaponNum( const char* weaponName ) const { for( int i = 1; i < MAX_WEAPONS; ++i ) { if( !idStr::Icmp(GetWeaponName(i), weaponName) ) { return i; } } return 0; } /* ===================== hhPlayer::GetWeaponName HUMANHEAD ===================== */ const char* hhPlayer::GetWeaponName( int num ) const { if( num < 1 || num >= MAX_WEAPONS ) { return NULL; } return spawnArgs.GetString( va("def_weapon%d", num) ); } /* =============== hhPlayer::Weapon_GUI PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge =============== */ void hhPlayer::Weapon_GUI( void ) { StopFiring(); weapon.GetEntity()->LowerWeapon(); // NLANOTE - Same here // disable click prediction for the GUIs. handy to check the state sync does the right thing if ( gameLocal.isClient && !net_clientPredictGUI.GetBool() ) { return; } if ( health <= 0 || bDeathWalk ) { //HUMANHEAD bjk PCF (5-5-06) - no hand stuff when dead return; } // HUMANHEAD nla if ( !bDeathWalk ) { // mdl - Don't do hand stuff while deathwalking if ( hand.IsValid() && hand->IsType( hhGuiHand::Type ) ) { //if ( !hand->IsReady() && !hand->IsRaising() ) { if ( !hand->IsReady() && !hand->IsRaising() && !hand->IsLowering() ) { hand->Reraise(); } } else if ( (!hand.IsValid() && weapon.IsValid() && !weapon->IsAside() ) || (hand.IsValid() && !hand->IsType( hhGuiHand::Type ) && !hand->IsLowering() ) || (!hand.IsValid() && idealWeapon == 0 ) ) { if (!gameLocal.isClient) { //rww hhHand::AddHand( this, GetGuiHandInfo() ); } } } // HUMANHEAD END if ( hand.IsValid() && ( oldButtons ^ usercmd.buttons ) & BUTTON_ATTACK ) { //HUMANHEAD bjk: no waiting for ready sysEvent_t ev; const char *command = NULL; bool updateVisuals = false; idUserInterface *ui = ActiveGui(); if ( ui ) { ev = sys->GenerateMouseButtonEvent( 1, ( usercmd.buttons & BUTTON_ATTACK ) != 0 ); command = ui->HandleEvent( &ev, gameLocal.time, &updateVisuals ); if ( updateVisuals && focusGUIent && ui == focusUI ) { focusGUIent->UpdateVisuals(); } } if ( gameLocal.isClient ) { // we predict enough, but don't want to execute commands if (focusGUIent) { if ( hand.IsValid() && hand->IsType( hhGuiHand::Type ) && ev.evValue2) { //rww - still predict hand hand->SetAction( focusGUIent->spawnArgs.GetString( "pressAnim", "press" ) ); //HUMANHEAD bjk hand->Action(); } } return; } if ( focusGUIent ) { HandleGuiCommands( focusGUIent, command ); // HUMANHEAD nla - Added to handle the hand stuff. = ) if ( hand.IsValid() && hand->IsType( hhGuiHand::Type ) && ev.evValue2) { hand->SetAction( focusGUIent->spawnArgs.GetString( "pressAnim", "press" ) ); //HUMANHEAD bjk hand->Action(); } // HUMANHEAD END } else { HandleGuiCommands( this, command ); } } } /* ================ hhPlayer::UpdateCrosshairs HUMANHEAD PDM ================ */ void hhPlayer::UpdateCrosshairs() { bool combatCrosshair = false; bool targeting = false; int crosshair = 0; if ( !privateCameraView && !IsLocked(idealWeapon) && (!hand.IsValid() || hand->IsLowered()) && !InCinematic() && weapon.IsValid() && g_crosshair.GetInteger() ) { weapon->UpdateCrosshairs(combatCrosshair, targeting); crosshair = g_crosshair.GetInteger(); } cursor->SetStateBool( "combatcursor", targeting ? false : combatCrosshair ); cursor->SetStateBool( "activecombatcursor", targeting ); cursor->SetStateInt( "crosshair", crosshair ); } /* ================ hhPlayer::UpdateFocus Searches nearby entities for interactive guis, possibly making one of them the focus and sending it a mouse move event PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge ================ */ void hhPlayer::UpdateFocus( void ) { idClipModel *clipModelList[ MAX_GENTITIES ]; idClipModel *clip; int listedClipModels; idEntity *oldFocus; idEntity *ent; idUserInterface *oldUI; int i, j; idVec3 start, end; const char *command; trace_t trace; guiPoint_t pt; const idKeyValue *kv; sysEvent_t ev; idUserInterface *ui; if ( gameLocal.inCinematic ) { return; } oldFocus = focusGUIent; oldUI = focusUI; if ( focusTime <= gameLocal.time ) { ClearFocus(); } // don't let spectators interact with GUIs if ( spectating ) { return; } start = GetEyePosition(); //HUMANHEAD rww - the actual viewAngles do not seem to be properly transformed to at this point, //so just get a transformed direction now. idVec3 viewDir = (untransformedViewAngles.ToMat3()*GetEyeAxis())[0]; end = start + viewDir * 70.0f; // was 50, doom=80 // HUMANHEAD pdm: Changed aim logic a bit // player identification -> names to the hud if ( gameLocal.isMultiplayer && entityNumber == gameLocal.localClientNum ) { idVec3 end = start + viewDir * 768.0f; gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum < MAX_CLIENTS ) ) { MPAim = trace.c.entityNum; } else { MPAim = -1; //HUMANHEAD rww - if we're hitting a shuttle with a pilot, set the pilot to the MPAim ent if (trace.c.entityNum >= MAX_CLIENTS && trace.c.entityNum < MAX_GENTITIES) { idEntity *traceEnt = gameLocal.entities[trace.c.entityNum]; if (traceEnt && traceEnt->IsType(hhVehicle::Type)) { //is it a vehicle? hhVehicle *traceVeh = static_cast(traceEnt); if (traceVeh->GetPilot() && traceVeh->GetPilot()->IsType(hhPlayer::Type)) { //if it's a vehicle, does it have a player pilot? MPAim = traceVeh->GetPilot()->entityNumber; } } } //HUMANHEAD END } } idBounds bounds( start ); bounds.AddPoint( end ); listedClipModels = gameLocal.clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); // no pretense at sorting here, just assume that there will only be one active // gui within range along the trace for ( i = 0; i < listedClipModels; i++ ) { clip = clipModelList[ i ]; ent = clip->GetEntity(); if ( ent->IsHidden() ) { continue; } // HUMANHEAD pdm: added support here for all guis, not just gui1 int interactiveMask = 0; renderEntity_t *renderEnt = ent->GetRenderEntity(); if ( renderEnt ) { for (int ix=0; ixgui[ix] && renderEnt->gui[ix]->IsInteractive()) { interactiveMask |= (1<GuiTrace( ent->GetModelDefHandle(), start, end/*, interactiveMask*/); // jmarshall implement interactiveMask if ( ent->fl.accurateGuiTrace ) { trace_t tr; gameLocal.clip.TracePoint(tr, start, end, CONTENTS_SOLID, this); if (tr.fraction < pt.frac) { continue; } } if ( pt.x != -1 ) { // we have a hit renderEntity_t *focusGUIrenderEntity = ent->GetRenderEntity(); if ( !focusGUIrenderEntity ) { continue; } if ( pt.guiId == 1 ) { ui = focusGUIrenderEntity->gui[ 0 ]; } else if ( pt.guiId == 2 ) { ui = focusGUIrenderEntity->gui[ 1 ]; } else { ui = focusGUIrenderEntity->gui[ 2 ]; } if ( ui == NULL ) { continue; } ClearFocus(); focusGUIent = ent; focusUI = ui; if ( oldFocus != ent ) { // new activation // going to see if we have anything in inventory a gui might be interested in // need to enumerate inventory items focusUI->SetStateInt( "inv_count", inventory.items.Num() ); for ( j = 0; j < inventory.items.Num(); j++ ) { idDict *item = inventory.items[ j ]; const char *iname = item->GetString( "inv_name" ); const char *iicon = item->GetString( "inv_icon" ); const char *itext = item->GetString( "inv_text" ); focusUI->SetStateString( va( "inv_name_%i", j), iname ); focusUI->SetStateString( va( "inv_icon_%i", j), iicon ); focusUI->SetStateString( va( "inv_text_%i", j), itext ); kv = item->MatchPrefix("inv_id", NULL); if ( kv ) { focusUI->SetStateString( va( "inv_id_%i", j ), kv->GetValue() ); } // HUMANHEAD nla - Changed to pass all "passtogui_" keys to the gui kv = item->MatchPrefix("passtogui_", NULL); if ( kv ) { focusUI->SetStateString( kv->GetKey(), kv->GetValue() ); kv = item->MatchPrefix( "passtogui_", kv ); } // HUMANHEAD END focusUI->SetStateInt( iname, 1 ); } focusUI->SetStateString( "player_health", va("%i", health ) ); focusUI->SetStateBool( "player_spiritwalking", IsSpiritWalking() ); // for hunterhand gui kv = focusGUIent->spawnArgs.MatchPrefix( "gui_parm", NULL ); while ( kv ) { focusUI->SetStateString( kv->GetKey(), kv->GetValue() ); kv = focusGUIent->spawnArgs.MatchPrefix( "gui_parm", kv ); } } // clamp the mouse to the corner ev = sys->GenerateMouseMoveEvent( -2000, -2000 ); command = focusUI->HandleEvent( &ev, gameLocal.time ); HandleGuiCommands( focusGUIent, command ); // move to an absolute position ev = sys->GenerateMouseMoveEvent( pt.x * SCREEN_WIDTH, pt.y * SCREEN_HEIGHT ); command = focusUI->HandleEvent( &ev, gameLocal.time ); HandleGuiCommands( focusGUIent, command ); focusTime = gameLocal.time + FOCUS_GUI_TIME; break; } } if ( focusGUIent && focusUI ) { if ( !oldFocus || oldFocus != focusGUIent ) { command = focusUI->Activate( true, gameLocal.time ); HandleGuiCommands( focusGUIent, command ); //StartSound( "snd_guienter", SND_CHANNEL_ANY, 0, false, NULL ); } } else if ( oldFocus && oldUI ) { command = oldUI->Activate( false, gameLocal.time ); HandleGuiCommands( oldFocus, command ); //StartSound( "snd_guiexit", SND_CHANNEL_ANY, 0, false, NULL ); } } /* =============== hhPlayer::ApplyLandDeflect HUMANHEAD: aob =============== */ idVec3 hhPlayer::ApplyLandDeflect( const idVec3& pos, float scale ) { idVec3 localPos( pos ); int delta = 0; float fraction = 0.0f; // add fall height delta = gameLocal.time - landTime; if ( delta < LAND_DEFLECT_TIME ) { fraction = (float)delta / LAND_DEFLECT_TIME; //HUMANHEAD: aob fraction *= scale; //HUMANHEAD END localPos += cameraInterpolator.GetCurrentUpVector() * landChange * fraction; } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { fraction = (float)(LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME; //HUMANHEAD: aob fraction *= scale; //HUMANHEAD END localPos += cameraInterpolator.GetCurrentUpVector() * landChange * fraction; } return localPos; } /* ================= hhPlayer::CrashLand Check for hard landings that generate sound events PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge ================= */ void hhPlayer::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { //HUMANHEAD: aob - removed unused vars idVec3 origin; idVec3 gravityNormal; float delta; waterLevel_t waterLevel; bool noDamage; //HUMANHEAD END //HUMANHEAD: aob const trace_t& trace = physicsObj.GetGroundTrace(); //HUMANHEAD END AI_SOFTLANDING = false; AI_HARDLANDING = false; //HUMANHEAD: aob - added IsBound and trace check // if the player is not on the ground if ( (!physicsObj.HasGroundContacts() || trace.fraction == 1.0f) && !IsBound() ) { return; } //HUMANHEAD: aob - only check when we land on the ground //If we get here we can assume we currently have ground contacts if( physicsObj.HadGroundContacts() ) { return; } //HUMANHEAD END gravityNormal = physicsObj.GetGravityNormal(); // if the player wasn't going down if ( ( oldVelocity * -gravityNormal ) >= 0.0f ) { return; } waterLevel = physicsObj.GetWaterLevel(); // never take falling damage if completely underwater if ( waterLevel == WATERLEVEL_HEAD ) { return; } // no falling damage if touching a nodamage surface noDamage = false; for ( int i = 0; i < physicsObj.GetNumContacts(); i++ ) { const contactInfo_t &contact = physicsObj.GetContact( i ); if ( contact.material->GetSurfaceFlags() & SURF_NODAMAGE ) { noDamage = true; break; } } //HUMANHEAD: aob - removed velocity calculations //HUMANHEAD: aob idVec3 deltaVelocity = DetermineDeltaCollisionVelocity( oldVelocity, trace ); delta = (IsBound()) ? deltaVelocity.Length() : deltaVelocity * gravityNormal; //HUMANHEAD END // reduce falling damage if there is standing water if ( waterLevel == WATERLEVEL_WAIST ) { delta *= 0.25f; } if ( waterLevel == WATERLEVEL_FEET ) { delta *= 0.5f; } if ( delta < crashlandSpeed_jump || IsSpiritOrDeathwalking() ) { return; // Early out } // Calculate landing sound volume float soundScale = hhUtils::CalculateScale( delta, crashlandSpeed_jump, crashlandSpeed_fatal ); landChange = -32 * soundScale; landTime = gameLocal.time; float min = hhMath::dB2Scale( spawnArgs.GetInt("minCrashLandVolumedB", "-20") ); float max = hhMath::dB2Scale( spawnArgs.GetInt("maxCrashLandVolumedB", "-4") ); soundScale = min + (max - min) * soundScale; idVec3 fallDir = oldVelocity; idVec3 reverseContactNormal = -physicsObj.GetGroundContactNormal(); idEntity* entity = NULL; float damageScale = hhUtils::CalculateScale( delta, crashlandSpeed_soft, crashlandSpeed_fatal ); fallDir.Normalize(); if( trace.fraction == 1.0f ) { return; } if( !IsBound() ) { PlayCrashLandSound( trace, soundScale ); gameLocal.AlertAI( this, spawnArgs.GetFloat( "land_alert_radius", "400" ) ); } // Determine damage to what you're landing on entity = gameLocal.GetTraceEntity( trace ); if( entity && trace.c.entityNum != ENTITYNUM_WORLD ) { entity->ApplyImpulse( this, 0, trace.c.point, (oldVelocity * reverseContactNormal) * reverseContactNormal );//Not sure if this impulse is large enough const char* entityDamageName = spawnArgs.GetString( "def_damageFellOnto" ); if( *entityDamageName && damageScale > 0.0f) { entity->Damage( this, this, fallDir, entityDamageName, damageScale, INVALID_JOINT ); } } // Calculate damage to self const char* selfDamageName = NULL; if ( delta < crashlandSpeed_soft ) { // Soft Fall AI_SOFTLANDING = true; selfDamageName = spawnArgs.GetString( "def_damageSoftFall" ); } else if ( delta < crashlandSpeed_fatal ) { // Hard Fall AI_HARDLANDING = true; selfDamageName = spawnArgs.GetString( "def_damageHardFall" ); } else { // Fatal Fall AI_HARDLANDING = true; selfDamageName = spawnArgs.GetString( "def_damageFatalFall" ); } if( *selfDamageName && damageScale > 0.0f && !noDamage ) { pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim Damage( NULL, NULL, fallDir, selfDamageName, damageScale, INVALID_JOINT ); } } /* =============== hhPlayer::ApplyBobCycle HUMANHEAD: aob =============== */ #define BOB_TO_NOBOB_RETURN_TIME 3000.0f idVec3 hhPlayer::ApplyBobCycle( const idVec3& pos, const idVec3& velocity ) { float delta = 0.0f; idVec3 localViewBob( pos ); const float maxBob = 6.0f; if( bob > 0.0f && !usercmd.forwardmove && !usercmd.rightmove ) {//smoothly goto bob of zero if not already there delta = gameLocal.time - lastAppliedBobCycle; bob *= ( 1.0f - (delta / BOB_TO_NOBOB_RETURN_TIME) ); if( bob < 0.0f ) { bob = 0.0f; } } else { // add bob height after any movement smoothing lastAppliedBobCycle = gameLocal.time; bob = bobfracsin * xyspeed * pm_bobup.GetFloat(); if( bob > maxBob ) { bob = maxBob; } } localViewBob -= cameraInterpolator.GetCurrentUpVector() * bob; return localViewBob; } /* =============== hhPlayer::BobCycle PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge =============== */ void hhPlayer::BobCycle( const idVec3 &pushVelocity ) { float bobmove; int old; //, deltaTime; idVec3 vel, gravityDir, velocity; idMat3 viewaxis; // float bob; float delta; float speed; // float f; // // calculate speed and cycle to be used for // all cyclic walking effects // velocity = physicsObj.GetLinearVelocity() - pushVelocity; //HUMANHEAD: aob - changed GetGravityNormal to GetCurrentUpVector to smooth out wallwalk gravityDir = -cameraInterpolator.GetCurrentUpVector(); //HUMANHEAD END vel = velocity - ( velocity * gravityDir ) * gravityDir; xyspeed = vel.LengthFast(); // do not evaluate the bob for other clients // when doing a spectate follow, don't do any weapon bobbing if ( gameLocal.isClient && entityNumber != gameLocal.localClientNum ) { //HUMANHEAD rww - allow bob when following players in spectator mode bool canBob = false; if (gameLocal.localClientNum != -1 && gameLocal.entities[gameLocal.localClientNum] && gameLocal.entities[gameLocal.localClientNum]->IsType(hhPlayer::Type)) { hhPlayer *pl = static_cast(gameLocal.entities[gameLocal.localClientNum]); if (pl->spectating && pl->spectator == entityNumber) { canBob = true; } } if (!canBob) { //HUMANHEAD END viewBobAngles.Zero(); viewBob.Zero(); return; } } if ( !physicsObj.HasGroundContacts() || influenceActive == INFLUENCE_LEVEL2 || ( gameLocal.isMultiplayer && spectating ) ) { // airborne bobCycle = 0; bobFoot = 0; bobfracsin = 0; } else if ( ( !usercmd.forwardmove && !usercmd.rightmove ) || ( xyspeed <= MIN_BOB_SPEED ) ) { // start at beginning of cycle again bobCycle = 0; bobFoot = 0; bobfracsin = 0; } else { if ( physicsObj.IsCrouching() ) { bobmove = pm_crouchbob.GetFloat(); // ducked characters never play footsteps } else { // vary the bobbing based on the speed of the player bobmove = pm_walkbob.GetFloat() * ( 1.0f - bobFrac ) + pm_runbob.GetFloat() * bobFrac; } // check for footstep / splash sounds old = bobCycle; bobCycle = (int)( old + bobmove * gameLocal.msec ) & 255; bobFoot = ( bobCycle & 128 ) >> 7; bobfracsin = idMath::Fabs( sin( ( bobCycle & 127 ) / 127.0 * idMath::PI ) ); } // calculate angles for view bobbing viewBobAngles.Zero(); viewaxis = viewAngles.ToMat3(); // add angles based on velocity delta = velocity * viewaxis[0]; viewBobAngles.pitch += delta * pm_runpitch.GetFloat(); delta = velocity * viewaxis[1]; viewBobAngles.roll -= delta * pm_runroll.GetFloat(); // add angles based on bob // make sure the bob is visible even at low speeds speed = xyspeed > 200 ? xyspeed : 200; delta = bobfracsin * pm_bobpitch.GetFloat() * speed; if ( physicsObj.IsCrouching() ) { delta *= 3; // crouching } viewBobAngles.pitch += delta; delta = bobfracsin * pm_bobroll.GetFloat() * speed; if ( physicsObj.IsCrouching() ) { delta *= 3; // crouching accentuates roll } if ( bobFoot & 1 ) { delta = -delta; } viewBobAngles.roll += delta; //HUMANHEAD: aob #if HUMANHEAD viewBob = ApplyBobCycle( ApplyLandDeflect(vec3_zero, 1.0f), velocity ); #else // calculate position for view bobbing viewBob.Zero(); if ( physicsObj.HasSteppedUp() ) { // check for stepping up before a previous step is completed deltaTime = gameLocal.time - stepUpTime; if ( deltaTime < STEPUP_TIME ) { stepUpDelta = stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME + physicsObj.GetStepUp(); } else { stepUpDelta = physicsObj.GetStepUp(); } if ( stepUpDelta > 2.0f * pm_stepsize.GetFloat() ) { stepUpDelta = 2.0f * pm_stepsize.GetFloat(); } stepUpTime = gameLocal.time; } idVec3 gravity = physicsObj.GetGravityNormal(); // if the player stepped up recently deltaTime = gameLocal.time - stepUpTime; if ( deltaTime < STEPUP_TIME ) { viewBob += gravity * ( stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME ); } // add bob height after any movement smoothing bob = bobfracsin * xyspeed * pm_bobup.GetFloat(); if ( bob > 6 ) { bob = 6; } viewBob[2] += bob; // add fall height delta = gameLocal.time - landTime; if ( delta < LAND_DEFLECT_TIME ) { f = delta / LAND_DEFLECT_TIME; viewBob -= gravity * ( landChange * f ); } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { delta -= LAND_DEFLECT_TIME; f = 1.0 - ( delta / LAND_RETURN_TIME ); viewBob -= gravity * ( landChange * f ); } #endif //HUMANHEAD END } /* ================ hhPlayer::UpdateDeltaViewAngles ================ */ void hhPlayer::UpdateDeltaViewAngles( const idAngles &angles ) { // set the delta angle idAngles delta; for( int i = 0; i < 3; i++ ) { #if 1 //rww - revert to id's method //delta[i] = ((angles[i] - GetUntransformedViewAngles()[i]) / GetViewAnglesSensitivity()) + GetUntransformedViewAngles()[i] - SHORT2ANGLE(usercmd.angles[i]); delta[ i ] = angles[ i ] - SHORT2ANGLE( usercmd.angles[ i ] ); #else // HUMANHEAD mdl: If you enable this, comment out the SetOrientation() call for vehicles in hhPlayer::Restore() delta[i] = (angles[i] - idMath::AngleNormalize180( SHORT2ANGLE(usercmd.angles[ i ]) - oldCmdAngles[i]) * GetViewAnglesSensitivity()); #endif } SetDeltaViewAngles( delta ); } /* ================ hhPlayer::DetermineViewAngles ================ */ idAngles hhPlayer::DetermineViewAngles( const usercmd_t& cmd, idAngles& cmdAngles ) { int i; idAngles localViewAngles( GetUntransformedViewAngles() ); idAngles deltaCmdAngles; if ( !noclip && ( gameLocal.inCinematic || privateCameraView || gameLocal.GetCamera() || influenceActive == INFLUENCE_LEVEL2 ) ) { // no view changes at all, but we still want to update the deltas or else when // we get out of this mode, our view will snap to a kind of random angle return GetUntransformedViewAngles(); } //HUMANHEAD rww - testing /* if (gameLoacl.isClient && gameLocal.localClientNum != entityNumber) { idQuat a = viewAngles.ToQuat(); idQuat b = GetUntransformedViewAngles().ToQuat(); idQuat c; c.Slerp(a, b, 0.3f); return c.ToAngles(); } */ //HUMANHEAD END // if dead if ( IsDead() ) { #if 0 if ( DoThirdPersonDeath() ) { localViewAngles.roll = 0.0f; localViewAngles.pitch = 30.0f; } else { localViewAngles.roll = 40.0f; localViewAngles.pitch = -15.0f; } return localViewAngles; #else //HUMANHEAD PCF rww 04/26/06 - look freely while dead in MP if ( !DoThirdPersonDeath() ) { localViewAngles.roll = 40.0f; localViewAngles.pitch = -15.0f; return localViewAngles; } #endif //HUMANHEAD END } //JSHTODO this messes up multiplayer input. remerge sensitivity code // circularly clamp the angles with deltas if ( usercmd.buttons & BUTTON_MLOOK ) { for ( i = 0; i < 3; i++ ) { cmdAngles[i] = SHORT2ANGLE( usercmd.angles[i] ); #if 1 //rww - revert to id's method //localViewAngles[i] = idMath::AngleNormalize180( cmdAngles[i] + deltaViewAngles[i] - GetUntransformedViewAngles()[i] ) * GetViewAnglesSensitivity() + GetUntransformedViewAngles()[i]; localViewAngles[i] = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i]) + deltaViewAngles[i] ); #else deltaCmdAngles[i] = idMath::AngleNormalize180( cmdAngles[i] - oldCmdAngles[i] ) * GetViewAnglesSensitivity(); oldCmdAngles[i] = cmdAngles[i]; localViewAngles[i] = deltaCmdAngles[i] + deltaViewAngles[i]; #endif } } else { #if 1 //rww - revert to id's method //localViewAngles.yaw = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[YAW] ) + deltaViewAngles[YAW] - GetUntransformedViewAngles()[YAW] ) * GetViewAnglesSensitivity() + GetUntransformedViewAngles()[YAW]; localViewAngles.yaw = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[YAW]) + deltaViewAngles[YAW] ); #if GAMEPAD_SUPPORT // VENOM BEGIN oldCmdAngles[YAW] = SHORT2ANGLE( usercmd.angles[YAW] ); #endif // VENOM END #else deltaCmdAngles[YAW] = idMath::AngleNormalize180( SHORT2ANGLE(usercmd.angles[YAW]) - oldCmdAngles[YAW] ) * GetViewAnglesSensitivity(); oldCmdAngles[YAW] = SHORT2ANGLE( usercmd.angles[YAW] ); localViewAngles.yaw = deltaCmdAngles[YAW] + deltaViewAngles[YAW]; #endif if (!centerView.IsDone(gameLocal.time)) { localViewAngles.pitch = centerView.GetCurrentValue(gameLocal.time); } } #if GAMEPAD_SUPPORT // VENOM BEGIN float fpitch = idMath::Fabs(localViewAngles.pitch); if( idMath::Abs(usercmd.rightmove) < 10 && idMath::Abs(usercmd.forwardmove) > 80 && fpitch > 1.f && idMath::Fabs(oldCmdAngles[YAW] - cmdAngles[YAW]) < 1.0f && idMath::Fabs(oldCmdAngles[PITCH] - cmdAngles[PITCH]) < 1.0f ) { // dont start the leveling until we have .5 sec of valid movement if((gameLocal.time - lastAutoLevelTime) > 500 ) { float fadd = 0.6f; if(fpitch < 10.f) { fadd *= (fpitch/10.f ); fadd+= 0.05f; } if(localViewAngles.pitch < 0 ) { localViewAngles.pitch +=fadd; } else { localViewAngles.pitch -=fadd; } } } else { lastAutoLevelTime = gameLocal.time; } oldCmdAngles[YAW] = cmdAngles[YAW]; oldCmdAngles[PITCH] = cmdAngles[PITCH]; #endif // VENOM END // clamp the pitch if ( noclip ) { localViewAngles.pitch = hhMath::ClampFloat( -89.0f, 89.0f, localViewAngles.pitch ); } else { //HUMANHEAD PCF rww 04/26/06 - look freely while dead in MP if (IsDead() && DoThirdPersonDeath()) { localViewAngles.pitch = hhMath::ClampFloat( -45.0f, 45.0f, localViewAngles.pitch ); } else { //HUMANHEAD END localViewAngles.pitch = hhMath::ClampFloat( pm_minviewpitch.GetFloat(), pm_maxviewpitch.GetFloat(), localViewAngles.pitch ); } } // HUMANHEAD pdm: clamp the yaw (only used for bindControllers) if ( !noclip && bClampYaw ) { float idealYaw=0.0f; idVec3 masterOrigin; idMat3 masterAxis; if (GetMasterPosition(masterOrigin, masterAxis)) { idealYaw = masterAxis[0].ToYaw(); idealYaw = idMath::AngleNormalize180( idealYaw ); } // Transpose the everything to "zero" space before clamping, so idealYaw is at zero in a (-180..180) normalized space float zeroSpaceYaw = idMath::AngleNormalize180( localViewAngles.yaw - idealYaw ); zeroSpaceYaw = idMath::ClampFloat(-maxRelativeYaw, maxRelativeYaw, zeroSpaceYaw); localViewAngles.yaw = idMath::AngleNormalize180( zeroSpaceYaw + idealYaw ); } // HUMANHEAD END UpdateDeltaViewAngles( localViewAngles ); // save in the log for analyzing weapon angle offsets loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ] = localViewAngles; return localViewAngles; } /* ================ hhPlayer::SetViewAnglesSensitivity ================ */ void hhPlayer::SetViewAnglesSensitivity( float factor ) { viewAnglesSensitivity = factor; if (gameLocal.localClientNum == entityNumber) { //common->SetGameSensitivityFactor(factor); // jmarshall } } /* ================ hhPlayer::GetViewAnglesSensitivity ================ */ float hhPlayer::GetViewAnglesSensitivity() const { return viewAnglesSensitivity; } /* ================ hhPlayer::UpdateViewAngles ================ */ void hhPlayer::UpdateViewAngles( void ) { if( InVehicle() ) { return; } if (!noclip && (InCinematic() && lockView)) { // no view changes at all, but we still want to update the deltas or else when // we get out of this mode, our view will snap to a kind of random angle UpdateDeltaViewAngles( viewAngles ); return; } //HUMANHEAD: aob - moved logic into helper function so we can call this somewhere else while in a vehicle viewAngles = DetermineViewAngles( usercmd, cmdAngles ); //HUMANHEAD rww - moved angle smoothing code here // update the smoothed view angles if (gameLocal.isClient) { if ( gameLocal.framenum >= smoothedFrame && entityNumber != gameLocal.localClientNum ) { idAngles anglesDiff = viewAngles - smoothedAngles; anglesDiff.Normalize180(); if ( idMath::Fabs( anglesDiff.yaw ) < 90.0f && idMath::Fabs( anglesDiff.pitch ) < 90.0f ) { // smoothen by pushing back to the previous angles viewAngles -= gameLocal.clientSmoothing * anglesDiff; viewAngles.Normalize180(); } smoothedAngles = viewAngles; } smoothedOriginUpdated = false; } // orient the model towards the direction we're looking UpdateOrientation( viewAngles ); //HUMANHEAD END } /* ===================== hhPlayer::UpdateOrientation HUMANHEAD: aob ===================== */ void hhPlayer::UpdateOrientation( const idAngles& newUntransformedViewAngles ) { idAngles angles; //This is where the camera interpolator transforms the viewAngles to work with wallwalk. //We also store the original untransformed view angles for use in some other code. //idPlayer called SetAxis, but only used the yaw but because we are transforming our view angles //we do something similier by taking the untransformed yaw and rotating by the camera interpolator's axis. //This becomes our new axis, the camera interpolator axis plus the untransformed view angles yaw SetUntransformedViewAngles( newUntransformedViewAngles ); angles = GetUntransformedViewAngles(); if( IsWallWalking() ) { angles.pitch = 0.0f; } SetUntransformedViewAxis( idAngles( 0.0f, angles.yaw, 0.0f ).ToMat3() ); viewAngles = cameraInterpolator.UpdateViewAngles( angles ); SetAxis( TransformToPlayerSpace(GetUntransformedViewAxis()) ); } //============================================================================= // // hhPlayer::CheckFOV // // Similar to idActor::CheckFOV, but isn't infinite along the vertical plane // // CJR //============================================================================= bool hhPlayer::CheckFOV( const idVec3 &pos ) { if ( fovDot == 1.0f ) { return true; } float dot; idVec3 delta; delta = pos - GetEyePosition(); delta.Normalize(); dot = delta * viewAngles.ToForward(); return ( dot >= fovDot ); } //============================================================================= // hhPlayer::CheckFOV // jsh - Similar to CheckFOV, but only checks yaw //============================================================================= bool hhPlayer::CheckYawFOV( const idVec3 &pos ) { if ( fovDot == 1.0f ) { return true; } float dot; idVec3 delta; idVec3 viewAng; delta = pos - GetEyePosition(); delta.z = 0; delta.Normalize(); viewAng = viewAngles.ToForward(); viewAng.z = 0; dot = delta * viewAng; return ( dot >= fovDot ); } /* ===================== hhPlayer::SetOrientation HUMANHEAD: aob ===================== */ void hhPlayer::SetOrientation( const idVec3& origin, const idMat3& bboxAxis, const idVec3& lookDir, const idAngles& newUntransformedViewAngles ) { //never let the untransformed view angles have a roll value idAngles modifiedUntransViewAngles = newUntransformedViewAngles; modifiedUntransViewAngles.roll = 0.0f; physicsObj.SetOrigin( GetLocalCoordinates(origin) ); physicsObj.SetAxis( bboxAxis ); physicsObj.CheckWallWalk( true ); SetViewAngles( modifiedUntransViewAngles ); BufferLoggedViewAngles( modifiedUntransViewAngles ); SetUntransformedViewAngles( modifiedUntransViewAngles ); SetAxis( lookDir.ToMat3() ); SetUntransformedViewAxis( idAngles(0.0f, modifiedUntransViewAngles.yaw, 0.0f).ToMat3() ); cameraInterpolator.Reset( origin, bboxAxis[2], EyeHeightIdeal() ); UpdateViewAngles(); UpdateVisuals(); } /* ===================== hhPlayer::RestoreOrientation HUMANHEAD: mdl Same as SetOrientation, except doesn't check wallwalk, since the materials may not be set before the first frame. ===================== */ void hhPlayer::RestoreOrientation( const idVec3& origin, const idMat3& bboxAxis, const idVec3& lookDir, const idAngles& newUntransformedViewAngles ) { physicsObj.SetOrigin( GetLocalCoordinates(origin) ); physicsObj.SetAxis( bboxAxis ); SetViewAngles( newUntransformedViewAngles ); BufferLoggedViewAngles( newUntransformedViewAngles ); SetUntransformedViewAngles( newUntransformedViewAngles ); SetAxis( lookDir.ToMat3() ); SetUntransformedViewAxis( idAngles(0.0f, newUntransformedViewAngles.yaw, 0.0f).ToMat3() ); cameraInterpolator.Reset( origin, bboxAxis[2], EyeHeightIdeal() ); UpdateViewAngles(); UpdateVisuals(); } /* ===================== hhPlayer::BufferLoggedViewAngles HUMANHEAD: aob - used when teleporting and using portals ===================== */ void hhPlayer::BufferLoggedViewAngles( const idAngles& newUntransformedViewAngles ) { idAngles deltaAngles = newUntransformedViewAngles - GetUntransformedViewAngles(); for( int ix = 0; ix < NUM_LOGGED_VIEW_ANGLES; ++ix ) { loggedViewAngles[ix] += deltaAngles; } } /* ================ hhPlayer::UpdateFromPhysics AOBMERGE - PERSISTANT MERGE ================ */ void hhPlayer::UpdateFromPhysics( bool moveBack ) { // set master delta angles for actors if ( GetBindMaster() ) { if( !InVehicle() ) { idAngles delta = GetDeltaViewAngles(); if ( moveBack ) { delta.yaw -= physicsObj.GetMasterDeltaYaw(); } else { delta.yaw += physicsObj.GetMasterDeltaYaw(); } SetDeltaViewAngles( delta ); } else { SetUntransformedViewAxis( mat3_identity ); SetUntransformedViewAngles( GetUntransformedViewAxis().ToAngles() ); viewAngles = GetUntransformedViewAngles(); SetAxis( GetBindMaster()->GetAxis() ); //AOB - now that we have an updated viewAxis we need to update the //camera. Feels like a hack! cameraInterpolator.SetTargetAxis( GetAxis(), INTERPOLATE_NONE ); } } UpdateVisuals(); } /* ============== hhPlayer::PerformImpulse ============== */ void hhPlayer::PerformImpulse( int impulse ) { if( InVehicle() && GetVehicleInterface()->InvalidVehicleImpulse(impulse) ) { return; } idPlayer::PerformImpulse( impulse ); switch( impulse ) { case IMPULSE_14: { if ( weapon.IsValid() && weapon->IsType( hhWeaponZoomable::Type ) ) { hhWeaponZoomable *weap = static_cast(weapon.GetEntity()); if ( weap && weap->GetAltMode() ) { weap->ZoomInStep(); } else { NextWeapon(); } } else { NextWeapon(); } break; } case IMPULSE_15: { if ( weapon.IsValid() && weapon->IsType( hhWeaponZoomable::Type ) ) { hhWeaponZoomable *weap = static_cast(weapon.GetEntity()); if ( weap && weap->GetAltMode() ) { weap->ZoomOutStep(); } else { PrevWeapon(); } } else { PrevWeapon(); } break; } case IMPULSE_16: // Toggle Lighter if (inventory.requirements.bCanUseLighter && weaponFlags != 0) { // mdl: Disable lighter if all weapons are disabled if (!gameLocal.isClient) { ToggleLighter(); } } break; case IMPULSE_25: // Throw grenade if ( weaponFlags != 0 && !ActiveGui()) { // mdl: Disable if all weapons are disabled if (!gameLocal.isClient) { ThrowGrenade(); } } break; case IMPULSE_54: // Spirit Power key if ( IsDeathWalking() ) { // CJR: Developer-only ability to quickly return from DeathWalk by hitting the spirit key if ( developer.GetBool() && ( gameLocal.time - deathWalkTime > spawnArgs.GetInt( "deathWalkMinTime", "4000" ) ) ) { // Force the player to stay in deathwalk for a short period of time deathWalkFlash = 0.0f; PostEventMS( &EV_PrepareToResurrect, 0 ); } } else if (inventory.requirements.bCanSpiritWalk) { ToggleSpiritWalk(); } break; case IMPULSE_18: { #if GAMEPAD_SUPPORT // VENOM BEGIN idAngles localViewAngles( GetUntransformedViewAngles() ); centerView.Init(gameLocal.time, 360, localViewAngles.pitch, 0); #else centerView.Init(gameLocal.time, 200, viewAngles.pitch, 0); #endif // VENOM END break; } } } void hhPlayer::Present() { idPlayer::Present(); if ( lighterHandle != -1 ) { // Update oscillation position idVec3 oscillation; oscillation.x = 0.0f; oscillation.y = idMath::Cos( MS2SEC(gameLocal.time) * spawnArgs.GetFloat("lighter_yspeed") ) * spawnArgs.GetFloat("lighter_ysize"); oscillation.z = idMath::Sin( MS2SEC(gameLocal.time) * spawnArgs.GetFloat("lighter_zspeed") ) * spawnArgs.GetFloat("lighter_zsize"); idVec3 offset; offset = spawnArgs.GetVector("offset_lighter"); offset.z = EyeHeight(); lighter.origin = GetOrigin() + GetAxis() * (offset + oscillation); lighter.axis = mat3_identity; lighter.shaderParms[ SHADERPARM_DIVERSITY ] = renderEntity.shaderParms[ SHADERPARM_DIVERSITY ]; gameRenderWorld->UpdateLightDef( lighterHandle, &lighter ); } } void hhPlayer::LighterOn() { if (IsSpiritOrDeathwalking() || InVehicle() || spectating || bReallyDead) { // No lighter in spirit mode, deathwalk, or when in vehicles (or when spectating or really dead -rww) return; } if (lighterHandle == -1) { // Add the dynamic light memset( &lighter, 0, sizeof( lighter ) ); lighter.lightId = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber; lighter.pointLight = true; // false; ? lighter.shader = declManager->FindMaterial( spawnArgs.GetString( "mtr_lighter" ) ); lighter.shaderParms[ SHADERPARM_RED ] = spawnArgs.GetFloat( "lighterColorR" ); lighter.shaderParms[ SHADERPARM_GREEN ] = spawnArgs.GetFloat( "lighterColorG" ); lighter.shaderParms[ SHADERPARM_BLUE ] = spawnArgs.GetFloat( "lighterColorB" ); lighter.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; lighter.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); lighter.lightRadius = spawnArgs.GetVector( "lighter_radius" ); lighter.origin = GetOrigin() + GetAxis() * spawnArgs.GetVector("offset_lighter"); lighter.axis = mat3_identity; lighterHandle = gameRenderWorld->AddLightDef( &lighter ); StartSound("snd_lighter_on", SND_CHANNEL_ITEM); } } void hhPlayer::LighterOff() { if (lighterHandle != -1) { gameRenderWorld->FreeLightDef( lighterHandle ); lighterHandle = -1; StartSound("snd_lighter_off", SND_CHANNEL_ITEM); } } bool hhPlayer::IsLighterOn() const { //HUMANHEAD PCF mdl 05/04/06 - Made const return (lighterHandle != -1); } void hhPlayer::ToggleLighter() { if (IsLighterOn()) { LighterOff(); } else { LighterOn(); } } void hhPlayer::UpdateLighter() { if (IsLighterOn()) { // Increase the lighter's temperature until it overheats if (!gameLocal.isMultiplayer) { //rww - don't bother overheating the lighter in mp lighterTemperature += spawnArgs.GetFloat( "lighterHeatRate", "0.025" ) * MS2SEC( USERCMD_MSEC ); if (lighterTemperature >= 1.0f) { // Too hot, turn the lighter off StartSound("snd_lighter_toohot", SND_CHANNEL_ANY); lighterTemperature = 1.0f; if (!(godmode || noclip)) { LighterOff(); } } } } else { // Lighter is off, so decrease the lighter's temperature if (lighterTemperature > 0) { lighterTemperature -= spawnArgs.GetFloat( "lighterCoolRate", "0.05") * MS2SEC( USERCMD_MSEC ); } } } /* ================== hhPlayer::PlayFootstepSound ================== */ void hhPlayer::PlayFootstepSound() { if ( IsSpiritOrDeathwalking() ) { return; // No footstep sounds in spiritwalk mode } if ( IsWallWalking() ) { // Special case wallwalk since contacts includes some non-wallwalk types const char* soundKey = gameLocal.MatterTypeToMatterKey( "snd_footstep", SURFTYPE_WALLWALK ); StartSound( soundKey, SND_CHANNEL_BODY3, 0, false, NULL ); return; } idPlayer::PlayFootstepSound(); } /* ===================== hhPlayer::PlayPainSound HUMANHEAD: aob ===================== */ void hhPlayer::PlayPainSound() { if( IsSpiritOrDeathwalking() ) { return; } //HUMANHEAD PCF rww 09/15/06 - female mp sounds if (IsFemale()) { StartSound( "snd_pain_small_female", SND_CHANNEL_VOICE ); return; } //HUMANHEAD END idPlayer::PlayPainSound(); } /* =============== hhPlayer::Give =============== */ bool hhPlayer::Give( const char *statname, const char *value ) { if( IsDead() ) { return false; } return inventory.Give( this, spawnArgs, statname, value, &idealWeapon, true ); } /* =============== hhPlayer::ReportAttack =============== */ void hhPlayer::ReportAttack(idEntity *attacker) { if (gameLocal.isServer && attacker) { //rww - broadcast event for this on the server idBitMsg msg; byte msgBuf[MAX_EVENT_PARAM_SIZE]; msg.Init(msgBuf, sizeof(msgBuf)); msg.WriteBits(gameLocal.GetSpawnId(attacker), 32); //unreliable since it's not super-important, and only send to this player. ServerSendEvent(EVENT_REPORTATTACK, &msg, false, -1, entityNumber, true); } int ix; for (ix=0; ix lastAttackers[ix].time + DAMAGE_INDICATOR_TIME) { lastAttackers[ix].attacker = NULL; } } // Add this attacker to first free slot, if any for (ix=0; ix 0) { lastAttackers[oldSlot].attacker = attacker; lastAttackers[oldSlot].time = gameLocal.time; lastAttackers[oldSlot].displayed = false; } } /* ================== hhPlayer::Damage ================== */ void hhPlayer::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { if( IsSpiritOrDeathwalking() ) { //Player is spirit-walking, so check for special immunities const idKeyValue *kv = spawnArgs.MatchPrefix("immunityspirit"); while( kv && kv->GetValue().Length() ) { if ( !kv->GetValue().Icmp(damageDefName) ) { return; } kv = spawnArgs.MatchPrefix("immunityspirit", kv); } // If the player is spiritwalking, then any damage takes away spirit power instead of health ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName( "ammo_spiritpower" ); if ( IsSpiritWalking() ) { const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); if ( damageDef ) { idPlayer *player = (attacker && attacker->IsType( idPlayer::Type )) ? static_cast(attacker) : NULL; if ( !(gameLocal.gameType == GAME_TDM && !gameLocal.serverInfo.GetBool( "si_teamDamage" ) && !damageDef->GetBool( "noTeam" ) && player && player != this // you get self damage no matter what && player->team == team) ) { //rww - don't damage teammates' spirit when ff off int damageWhenSpirit = damageDef->GetInt("damageWhenSpirit", "0"); //rww - special damage for knocking players back to body in mp when shot by other things (namely spirit arrows) int oldSpirit = inventory.ammo[ ammo_spiritpower ]; int spiritDamage = (damageDef->GetInt( "damage", "1" ) * spawnArgs.GetFloat( "damageScaleInSpirit" ) * damageScale )+damageWhenSpirit; if ( spiritDamage <= 0 && damageScale > 0 ) { spiritDamage = 1; } if ( !UseAmmo( ammo_spiritpower, spiritDamage ) ) { inventory.ammo[ ammo_spiritpower ] = 0; // Clear spiritpower amount when returning from excessive damage } } } lastDamagedTime = gameLocal.time; // Save the damage time for the health recharge code // Track last attacker for use in displaying HUD hit indicator if (!gameLocal.isClient) { ReportAttack(attacker); } //HUMANHEAD rww - damage feedback for hitting spirit players in mp if (gameLocal.isMultiplayer && attacker && !gameLocal.isClient) { hhPlayer *killer = NULL; if (attacker->IsType(idPlayer::Type)) { killer = static_cast(attacker); } else if (attacker->IsType(hhVehicle::Type)) { hhVehicle *veh = static_cast(attacker); if (veh->GetPilot() && veh->GetPilot()->IsType(idPlayer::Type)) { killer = static_cast(veh->GetPilot()); } } if (killer && killer->entityNumber != entityNumber && killer->mpHitFeedbackTime <= gameLocal.time) { if (killer == gameLocal.GetLocalPlayer()) { assert(IsSpiritOrDeathwalking()); if (gameLocal.gameType == GAME_TDM && team == killer->team) { killer->StartSound( "snd_hitTeamFeedback", SND_CHANNEL_ITEM, 0, false, NULL ); } else { killer->StartSound( "snd_hitSpiritFeedback", SND_CHANNEL_ITEM, 0, false, NULL ); //hardcoded health ranges for the various flash colors float h; if (health > 100) { h = 0.0f; } else if (health > 75) { h = 0.25f; } else if (health > 25) { h = 0.50f; } else { h = 0.75f; } SetShaderParm(3, h); SetShaderParm(5, -MS2SEC(gameLocal.time)*2); } } else { idBitMsg msg; byte msgBuf[MAX_EVENT_PARAM_SIZE]; msg.Init(msgBuf, sizeof(msgBuf)); msg.WriteBits(gameLocal.GetSpawnId(this), 32); killer->ServerSendEvent(EVENT_HITNOTIFICATION, &msg, false, -1, killer->entityNumber); } } } //HUMANHEAD END return; } } lastDamagedTime = gameLocal.time; // Save the damage time for the health recharge code // Track last attacker for use in displaying HUD hit indicator if (!gameLocal.isClient) { ReportAttack(attacker); } // Check if the damage should "really kill" the player (when in deathwalk mode) if ( IsDeathWalking() ) { const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); if ( damageDef ) { if ( damageDef->GetBool( "reallyKill", "0" ) ) { // Truly kill the player ReallyKilled( inflictor, attacker, damageDef->GetInt( "damage", "9999" ), dir, location ); return; } else if ( damageDef->GetBool( "spiritDamage", "0" ) ) { // Drain spirit power ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName( "ammo_spiritpower" ); UseAmmo( ammo_spiritpower, damageDef->GetInt( "damage", "1" ) * damageScale ); return; } } } //HUMANHEAD rww - keep track of last person to attack me, debounce time will vary based on ground contact if (attacker && attacker->IsType(idPlayer::Type)) { airAttacker = attacker; if (!AI_ONGROUND) { airAttackerTime = gameLocal.time + 300; } else { //if they are on the ground do a 100ms debounce in case they fall off a ledge or something as a result of leftover attack velocity airAttackerTime = gameLocal.time + 100; } } //HUMANHEAD END // CJR: DDA: Only in single player if ( !gameLocal.isMultiplayer ) { float newDamageScale = gameLocal.GetDDAValue() * 2.0f; // Scale damage from easy to hard newDamageScale *= damageScale; const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); if ( damageDef ) { bool noGod = damageDef->GetBool("noGod", "0"); if ( noGod ) { // Make sure fatal damage bypasses post-deathwalk invulnerability fl.takedamage = true; } } int oldHealth = health; idPlayer::Damage( inflictor, attacker, dir, damageDefName, (const float)newDamageScale, location ); // CJR DDA TEST if ( attacker && attacker->IsType( hhMonsterAI::Type ) ) { // CJR DDA: Damaged by a monster, add the damage to the monster count float delta = oldHealth - health; hhMonsterAI *monster = static_cast( attacker ); if ( monster ) { monster->DamagedPlayer( delta ); } } } else { idPlayer::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); } //HUMANHEAD rww - hit feedback if (gameLocal.isMultiplayer && attacker && !gameLocal.isClient) { //let's broadcast from the server only, so hit feedback is always reliable hhPlayer *killer = NULL; if (attacker->IsType(idPlayer::Type)) { killer = static_cast(attacker); } else if (attacker->IsType(hhVehicle::Type)) { hhVehicle *veh = static_cast(attacker); if (veh->GetPilot() && veh->GetPilot()->IsType(idPlayer::Type)) { killer = static_cast(veh->GetPilot()); } } if (killer && killer->entityNumber != entityNumber && killer->mpHitFeedbackTime <= gameLocal.time) { if (killer == gameLocal.GetLocalPlayer()) { //don't provide visual indicator when shooting a teammate if (gameLocal.gameType == GAME_TDM && team == killer->team) { killer->StartSound( "snd_hitTeamFeedback", SND_CHANNEL_ITEM, 0, false, NULL ); } else { if (IsSpiritOrDeathwalking()) { killer->StartSound( "snd_hitSpiritFeedback", SND_CHANNEL_ITEM, 0, false, NULL ); } else { killer->StartSound( "snd_hitFeedback", SND_CHANNEL_ITEM, 0, false, NULL ); } //hardcoded health ranges for the various flash colors float h; if (health > 100) { h = 0.0f; } else if (health > 75) { h = 0.25f; } else if (health > 25) { h = 0.50f; } else { h = 0.75f; } SetShaderParm(3, h); SetShaderParm(5, -MS2SEC(gameLocal.time)*2); } } else { idBitMsg msg; byte msgBuf[MAX_EVENT_PARAM_SIZE]; msg.Init(msgBuf, sizeof(msgBuf)); msg.WriteBits(gameLocal.GetSpawnId(this), 32); killer->ServerSendEvent(EVENT_HITNOTIFICATION, &msg, false, -1, killer->entityNumber); } //see how this feels (and more importantly how destructive it is toward bandwidth) //killer->mpHitFeedbackTime = gameLocal.time + USERCMD_MSEC; } } //HUMANHEAD END if ( bFrozen ) { const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); if ( damageDef && damageDef->GetInt( "free_cocoon", "0" ) ) { Event_Unfreeze(); } } } void hhPlayer::DoDeathDrop() { // General dropping (for monsters, souls, etc.) const idKeyValue *kv = NULL; kv = spawnArgs.MatchPrefix( "def_drops", NULL ); while ( kv ) { idStr drops = kv->GetValue(); idDict args; idStr last5 = kv->GetKey().Right(5); if ( drops.Length() && idStr::Icmp( last5, "Joint" ) != 0) { args.Set( "classname", drops ); // HUMANHEAD pdm: specify monster so souls can call back to remove body when picked up args.Set("monsterSpawnedBy", name.c_str()); idVec3 origin; idMat3 axis; idStr jointKey = kv->GetKey() + idStr("Joint"); idStr jointName = spawnArgs.GetString( jointKey ); idStr joint2JointKey = kv->GetKey() + idStr("Joint2Joint"); idStr j2jName = spawnArgs.GetString( joint2JointKey ); idEntity *newEnt = NULL; gameLocal.SpawnEntityDef( args, &newEnt ); HH_ASSERT(newEnt != NULL); if(jointName.Length()) { jointHandle_t joint = GetAnimator()->GetJointHandle( jointName ); if (!GetAnimator()->GetJointTransform( joint, gameLocal.time, origin, axis ) ) { gameLocal.Printf( "%s refers to invalid joint '%s' on entity '%s'\n", (const char*)jointKey.c_str(), (const char*)jointName, (const char*)name ); origin = renderEntity.origin; axis = renderEntity.axis; } axis *= renderEntity.axis; origin = renderEntity.origin + origin * renderEntity.axis; newEnt->SetAxis(axis); newEnt->SetOrigin(origin); } else { newEnt->SetAxis(viewAxis); newEnt->SetOrigin(GetOrigin()); } } kv = spawnArgs.MatchPrefix( "def_drops", kv ); } } //HUMANHEAD rww /* ================== hhPlayer::Event_RespawnCleanup performs any necessary operations on death to prepare for a clean spawn. only cosmetic things should be placed here, this function will not be called for sure on death, at least for the client. ================== */ void hhPlayer::Event_RespawnCleanup(void) { //remove any fx entities which are bound to the player idEntity *ent; idEntity *next; for( ent = teamChain; ent != NULL; ent = next ) { next = ent->GetTeamChain(); if ( ent->GetBindMaster() == this && ent->IsType(hhEntityFx::Type) ) { ent->Unbind(); if( !ent->fl.noRemoveWhenUnbound ) { ent->PostEventMS( &EV_Remove, 0 ); if (gameLocal.isClient) { ent->Hide(); } } next = teamChain; } } } //HUMANHEAD END /* ================== hhPlayer::Killed ================== */ void hhPlayer::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { //you only die once if( AI_DEAD ) { return; } CancelEvents(&EV_DamagePlayer); //rww - don't do any more posted damage events once dead if (gameLocal.isMultiplayer) { //rww - this was not handled at all, i guess because there are different circumstances for "death" in sp wallwalkSoundController.StopSound( SND_CHANNEL_WALLWALK, SND_CHANNEL_WALLWALK2, false ); spiritwalkSoundController.StopSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 ); LighterOff(); if (!gameLocal.isClient && weapon.IsValid() && weapon->IsType(hhWeapon::Type) && weapon->CanDrop()) { //when dying in mp, toss my weapon out. idVec3 forward, up; viewAngles.ToVectors( &forward, NULL, &up ); //rww - hackishness to keep the type of ammo in the leechgun that it was dropped with idEntity *dropped = weapon->DropItem( 50.0f * forward + 50.0f * up, 0, 60000, true ); if (dropped && weapon->IsType(hhWeaponSoulStripper::Type)) { dropped->spawnArgs.Set("def_droppedEnergyType", inventory.energyType); } } } if (!gameLocal.isClient) { DoDeathDrop(); } if ( gameLocal.isMultiplayer && !gameLocal.IsCooperative() ) { bDeathWalk = false; bSpiritWalk = false; bReallyDead = true; } else if ( !inventory.requirements.bCanDeathWalk || !gameLocal.DeathwalkMapLoaded() ) { bDeathWalk = false; bSpiritWalk = false; bReallyDead = true; } //First thing we do is get out of vehicle if (InVehicle()) { GetVehicleInterface()->GetVehicle()->EjectPilot(); } // HUMANHEAD cjr: Update DDA if ( !AI_DEAD && !gameLocal.isMultiplayer ) { gameLocal.GetDDA()->DDA_AddDeath( this, attacker ); } // HUMANHEAD END if ( attacker && attacker->spawnArgs.GetBool("death_look_at", "0")) { attacker->spawnArgs.GetString("death_look_at_bone", "", deathLookAtBone); attacker->spawnArgs.GetString("death_camera_bone", "", deathCameraBone ); deathLookAtEntity = attacker; } if ( weapon.IsValid() ) { //HUMANHEAD bjk 04/26/06 - no sniper scope when dead if ( weapon->IsType( hhWeaponRifle::Type ) ) { static_cast( weapon.GetEntity() )->ZoomOut(); } weapon->PutAway(); } // HUMANHEAD nla if ( hand.IsValid() ) { hand->PutAway(); } // HUMANHEAD END if ( !bReallyDead ) { // Only go into deathwalk mode if the player is in the transitional death state idVec3 origin; idMat3 axis; idVec3 viewDir; idAngles angles; idMat3 eyeAxis; GetResurrectionPoint( origin, axis, viewDir, angles, eyeAxis, GetPhysics()->GetAbsBounds(), GetOrigin(), GetPhysics()->GetAxis(), GetAxis()[0], viewAngles ); DeathWalk( origin, axis, viewDir.ToMat3(), angles, eyeAxis ); } else { if (gameLocal.isMultiplayer) { //HUMANHEAD rww PostEventMS(&EV_RespawnCleanup, 32); if (!gameLocal.isClient) { StartRagdoll(); //start to rag on the player so that the proxy copies off proper af status GetPhysics()->SetContents( 0 ); //make non-solid hhMPDeathProxy *prox = (hhMPDeathProxy *)hhSpiritProxy::CreateProxy( spawnArgs.GetString("def_deathProxy_mp"), this, GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles, GetEyeAxis() ); assert(((hhSpiritProxy *)prox)->IsType(hhMPDeathProxy::Type)); if (prox) { idVec3 flingForce; float capDmg = (float)damage; if (capDmg > 200.0f) { capDmg = 200.0f; } flingForce = dir; flingForce.Normalize(); flingForce *= capDmg; prox->GetPhysics()->AddForce(0, prox->GetPhysics()->GetOrigin(0), flingForce*256.0f*256.0f); prox->SetFling(prox->GetPhysics()->GetOrigin(0), flingForce); } StopRagdoll(); //stop again, as we don't need to be ragging on the actual player } Hide(); //hide player minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME; maxRespawnTime = minRespawnTime + 10000; } else { GetPhysics()->SetContents(0); Hide(); minRespawnTime = gameLocal.time + 2000; maxRespawnTime = minRespawnTime + 5000; } //HUMANHEAD END physicsObj.SetMovementType( PM_DEAD ); SAFE_REMOVE( weapon ); } AI_DEAD = true; //HUMANHEAD rww if (gameLocal.isMultiplayer) { SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 4 ); SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 4 ); SetWaitState( "" ); } //HUMANHEAD END //HUMANHEAD PCF rww 09/15/06 - female mp sounds if (IsFemale()) { StartSound( "snd_death_female", SND_CHANNEL_VOICE ); } else { //HUMANHEAD END StartSound( "snd_death", SND_CHANNEL_VOICE ); } if ( gameLocal.isMultiplayer && !gameLocal.isCoop ) { idPlayer *killer = NULL; if ( attacker->IsType( idPlayer::Type ) ) { killer = static_cast(attacker); } else { //rww - otherwise try to credit airAttacker if (airAttacker.IsValid() && airAttacker.GetEntity() && airAttackerTime > gameLocal.time) { if (airAttacker->IsType(idPlayer::Type)) { killer = static_cast(airAttacker.GetEntity()); } else if (airAttacker->IsType(hhVehicle::Type)) { hhVehicle *veh = static_cast(airAttacker.GetEntity()); if (veh->GetPilot() && veh->GetPilot()->IsType(idPlayer::Type)) { killer = static_cast(veh->GetPilot()); } } } } gameLocal.mpGame.PlayerDeath( this, killer, inflictor, false ); //HUMANHEAD rww - pass inflictor } UpdateVisuals(); airAttackerTime = 0; //HUMANHEAD rww - reset air attacker time once dead } /* ============== hhPlayer::Kill ============== */ void hhPlayer::Kill( bool delayRespawn, bool nodamage ) { if (noclip) { // HUMANHEAD pdm: Because of deathwalk, this shouldn't be allowed return; } if ( health > 0 ) { //HUMANHEAD rww if (IsSpiritOrDeathwalking()) { if (!IsSpiritWalking()) { //don't "kill" when dead. return; } StopSpiritWalk(true); } //HUMANHEAD END godmode = false; health = 0; //HUMANHEAD rww - if in a vehicle, eject now if (InVehicle()) { if (vehicleInterfaceLocal.GetVehicle()) { vehicleInterfaceLocal.GetVehicle()->EjectPilot(); } } //HUMANHEAD END Damage( NULL, NULL, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT ); } } /* =============== hhPlayer::DetermineOwnerPosition HUMANHEAD: aob =============== */ void hhPlayer::DetermineOwnerPosition( idVec3 &ownerOrigin, idMat3 &ownerAxis ) { ownerAxis = TransformToPlayerSpace( GetUntransformedViewAxis() ); ownerOrigin = cameraInterpolator.GetCurrentPosition() + idVec3(g_gun_x.GetFloat(), g_gun_y.GetFloat(), g_gun_z.GetFloat()) * GetAxis(); } /* =============== hhPlayer::GetViewPos =============== */ //HUMANHEAD bjk void hhPlayer::GetViewPos( idVec3 &origin, idMat3 &axis ) { idAngles angles; // if dead, fix the angle and don't add any kick // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override if ( IsDead() && !gameLocal.isMultiplayer ) { //rww - don't want this in mp. // HUMANHEAD END angles.yaw = viewAngles.yaw; angles.roll = 40; angles.pitch = -15; axis = angles.ToMat3(); origin = GetEyePosition(); } else { assert(kickSpring < 500.0f && kickSpring > 0.0f); assert(kickDamping < 500.0f && kickDamping > 0.0f); origin = viewBob + TransformToPlayerSpace( idVec3(g_viewNodalX.GetFloat(), g_viewNodalZ.GetFloat(), g_viewNodalZ.GetFloat() + EyeHeight()) ); axis = TransformToPlayerSpace( (GetUntransformedViewAngles() + viewBobAngles + playerView.AngleOffset(kickSpring, kickDamping)).ToMat3() ); } } /* =============== hhPlayer::CalculateRenderView =============== */ void hhPlayer::CalculateRenderView( void ) { idPlayer::CalculateRenderView(); // HUMANHEAD cjr if ( IsSpiritOrDeathwalking() ) { // If spiritwalking, then allow the player to see special objects renderView->viewSpiritEntities = true; } } /* =============== hhPlayer::OffsetThirdPersonView PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge =============== */ void hhPlayer::OffsetThirdPersonView( float angle, float range, float height, bool clip ) { idVec3 view; trace_t trace; idMat3 lookAxis; idVec3 origin; idAngles angles( GetUntransformedViewAngles() ); if ( angle ) { angles.pitch = 0.0f; } if ( angles.pitch > 45.0f && !InVehicle() ) { angles.pitch = 45.0f; // don't go too far overhead } //HUMANHEAD: aob lookAxis = TransformToPlayerSpace( angles.ToMat3() * idAngles(0.0f, angle, 0.0f).ToMat3() ); if( InVehicle() ) { origin = GetOrigin() + lookAxis[2] * (height + EyeHeight()); } else { origin = GetEyePosition() + GetEyeAxis()[2] * height; } view = origin + lookAxis[0] * -range; // trace a ray from the origin to the viewpoint to make sure the view isn't // in a solid block. Use an 16 by 16 block to prevent the view from near clipping anything if( !noclip ) { int mask = MASK_SHOT_BOUNDINGBOX; if (gameLocal.isMultiplayer) { //rww - don't want this hitting corpses. also, shouldn't we pay attention to "clip"? mask = MASK_PLAYERSOLID; } thirdPersonCameraClipBounds.SetOwner( this ); idEntity* ignore = (InVehicle()) ? GetVehicleInterface()->GetVehicle() : (idEntity*)this; gameLocal.clip.Translation( trace, origin, view, &thirdPersonCameraClipBounds, lookAxis, mask, ignore ); range *= trace.fraction; } renderView->vieworg = origin + lookAxis[0] * -range; renderView->viewaxis = lookAxis; //HUMANHEAD END renderView->viewID = 0; } /* ============================== hhPlayer::GetGuiHandInfo Retrns the proper gui hand info for a given gui ============================== */ const char *hhPlayer::GetGuiHandInfo() { const char *attrib; idDict *item = NULL; const char *handString; // Set the gui to: // "required_attribute" "Hunter Hand" // If item exists in player inventory, player uses guihand specified by item if (!IsSpiritWalking() && focusGUIent && focusGUIent->spawnArgs.GetString("required_attribute", NULL, &attrib)) { item = FindInventoryItem( attrib ); if ( item ) { if (item->GetString("def_guihand", NULL, &handString)) { return handString; } } } // If no required attribute/player doesn't have the item, check for a def_guihand string if ( focusGUIent && focusGUIent->spawnArgs.GetString( "def_guihand", NULL, &handString ) ) { return( handString ); } // Otherwise, use the default player hand return spawnArgs.GetString("def_guihand"); } //============================================================================= // // Spirit walk functions // //============================================================================= void hhPlayer::StartSpiritWalk( const bool bThrust, bool force ) { hhFxInfo fxInfo; idVec3 origin; idMat3 axis; //rww - don't do anything on the client if (gameLocal.isClient) { return; } if ( bReallyDead ) { // Don't allow spiritwalking if truly dead if (force) { gameLocal.Error("Attempted to force spirit walk when dead.\n"); } return; } // Make sure spirit walk isn't disabled if ( !bAllowSpirit ) { StartSound("snd_spiritWalkDenied", SND_CHANNEL_ANY); return; } spiritWalkToggleTime = gameLocal.time; if ( gameLocal.isMultiplayer ) { // CJR: Don't allow spiritwalking in MP unless the player has spirit power if ( GetSpiritPower() <= 0 ) { StartSound("snd_spiritWalkDenied", SND_CHANNEL_ANY, 0, true); return; } } if ( !force && nextSpiritTime > gameLocal.time ) { // mdl: Make sure they didn't just get knocked back into their body by a wraith StartSound("snd_spiritWalkDenied", SND_CHANNEL_ANY); return; } if (!IsSpiritOrDeathwalking()) { if ( !gameLocal.IsLOTA() ) { // In LOTA, spirit power never drains PostEventMS( &EV_DrainSpiritPower, spiritDrainHeartbeatMS ); } if ( !bThrust ) { // Normal spiritwalking EnableEthereal( spawnArgs.GetString( "def_spiritProxy" ), GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles, GetEyeAxis() ); } else { // Knocked out by a wraith EnableEthereal( spawnArgs.GetString( "def_possessedProxy" ), GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles, GetEyeAxis() ); } SelectEtherealWeapon(); spiritwalkSoundController.StartSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 ); // Update HUD if (hud) { hud->HandleNamedEvent("SwitchToEthereal"); } // Set the player's skin to a glowy effect SetSkinByName( spawnArgs.GetString("skin_Spiritwalk") ); SetShaderParm( SHADERPARM_TIMEOFFSET, 1.0f ); // TEMP: cjr - Required by the forcefield material. Can remove when a proper spiritwalk texture is made // Spawn in a flash GetViewPos( origin, axis ); fxInfo.SetEntity( this ); fxInfo.RemoveWhenDone( true ); fxInfo.SetBindBone( "origin" ); BroadcastFxInfoPrefixed( "fx_spiritWalkFlash", origin, axis, &fxInfo ); // Thrust the player backwards out of the body if ( bThrust ) { idVec3 vec = GetPhysics()->GetAxis()[0] * -200.0f + GetPhysics()->GetAxis()[2] * 50.0f; physicsObj.SetLinearVelocity( physicsObj.GetLinearVelocity() + vec ); // set the timer so that the player can't cancel out the movement immediately physicsObj.SetKnockBack( 100 ); } // bg - trigger map entity, provides a simple hook for map/scripts idEntity *swTrig = gameLocal.FindEntity( "sw_spiritWalkEntered" ); if( swTrig ) { swTrig->PostEventMS( &EV_Activate, 0, this ); } } } void hhPlayer::StopSpiritWalk(bool forceAllowance) { hhFxInfo fxInfo; idVec3 origin; idMat3 axis; //rww - don't do anything on the client if (gameLocal.isClient) { return; } if ( IsPossessed() ) { // If possessed and the player stops spiritwalking, they die PossessKilled(); return; } spiritWalkToggleTime = gameLocal.time; if ( bReallyDead ) { // Don't allow spiritwalking if truly dead return; } CancelEvents(&EV_SetOverlayMaterial); //HUMANHEAD rww - if being forced back out of spirit mode quickly enough, don't set overlay afterward if (IsSpiritOrDeathwalking()) { CancelEvents(&EV_DrainSpiritPower); if( weapon.IsValid() && weapon->IsType(hhWeaponSpiritBow::Type) ) { hhWeaponSpiritBow *bow = static_cast( weapon.GetEntity() ); if( bow->BowVisionIsEnabled() ) { bow->StopBowVision(); } } DisableEthereal(); spiritwalkSoundController.StopSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 ); SetSkin( NULL ); // bg - trigger map entity, provides a simple hook for map/scripts idEntity *swTrig = gameLocal.FindEntity( "sw_spiritWalkExited" ); if( swTrig ) { swTrig->PostEventMS( &EV_Activate, 0, this ); } // Update HUD if (hud) { hud->HandleNamedEvent("SwitchFromEthereal"); } buttonMask |= BUTTON_ATTACK; //HUMANHEAD bjk // Spawn in a flash GetViewPos( origin, axis ); fxInfo.SetEntity( this ); fxInfo.RemoveWhenDone( true ); fxInfo.SetBindBone( "origin" ); BroadcastFxInfoPrefixed( "fx_spiritWalkFlash", origin, axis, &fxInfo ); } } void hhPlayer::ToggleSpiritWalk( void ) { if (spectating) { //rww - do not allow spectators to spirit walk. return; } if ( bPossessed ) { // Cannot toggle away from spiritwalk when possessed StartSound("snd_spiritWalkDenied", SND_CHANNEL_ANY); return; } // spiritwalk time check -- force a delay between spiritwalking and not if (gameLocal.time < spiritWalkToggleTime + 250) { return; } if( IsSpiritOrDeathwalking() ) { StopSpiritWalk(); } else { StartSpiritWalk( false ); } } //HUMANHEAD rww void hhPlayer::RestorePlayerLocationFromDeathwalk( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& angles ) { idVec3 newOrigin; idMat3 newAxis; idVec3 newViewDir; idAngles newAngles; if( deathwalkLastCrouching ) { ForceCrouching(); } SetEyeAxis(deathwalkLastEyeAxis); GetResurrectionPoint( newOrigin, newAxis, newViewDir, newAngles, deathwalkLastEyeAxis, GetPhysics()->GetAbsBounds(), deathwalkLastOrigin, deathwalkLastBBoxAxis, viewDir, angles ); Teleport( newOrigin, newAxis, newViewDir, (newAngles.ToMat3() * deathwalkLastEyeAxis.Transpose()).ToAngles(), NULL ); } //HUMANHEAD END //============================================================================= // // hhPlayer::EnableEthereal // // Sets the player into an ethereal state (used for both spiritwalk and deathwalk) // This function does the following: // - Fade volume down on all class 0 sounds // - Sets bSpiritWalk (since this is true for both spirit and death walks) // - Spawns a proxy // - Sets the new collision // - Disables the weapons (the bow is enabled seperately for spiritwalk) // - Disables the lighter // - Unpossesses the player (if possessed) // - Updates bindings //============================================================================= void hhPlayer::EnableEthereal( const char *proxyName, const idVec3& origin, const idMat3& bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ) { //rww - don't do anything on the client - DO NOT PUT ANYTHING ABOVE HERE if (gameLocal.isClient) { return; } // Turn on low-pass effect if (gameLocal.localClientNum == entityNumber) { //rww - don't do this for anyone but the local client on listen servers gameLocal.SpiritWalkSoundMode( true ); } // Spawn proxy Tommy actor spiritProxy = hhSpiritProxy::CreateProxy( proxyName, this, origin, bboxAxis, newViewAxis, newViewAngles, newEyeAxis ); // Change player's collision type (to allow walking through forcefields, etc) GetPhysics()->SetClipMask( MASK_SPIRITPLAYER ); fl.acceptsWounds = false; spawnArgs.Set("produces_splats", "0"); // HUMANHEAD mdl: Let our enemies know about the spirit proxy for ( int i = 0; i < hhMonsterAI::allSimpleMonsters.Num(); i++ ) { if ( hhMonsterAI::allSimpleMonsters[i]->GetEnemy() == this ) { hhMonsterAI::allSimpleMonsters[i]->ProcessEvent( &MA_EnemyIsSpirit, this, spiritProxy.GetEntity() ); } } // If bound to another object (like on a rail ride), swap bindings with the proxy if ( GetBindMaster() != NULL ) { if (GetBindMaster()->IsType(hhBindController::Type)) { hhBindController *binder = static_cast(GetBindMaster()); bool loose = binder->IsLoose(); binder->Detach(); binder->Attach( spiritProxy.GetEntity(), loose ); } else { gameLocal.Warning("Handle this: spiritwalking while bound to non-rail"); SwapBindInfo( spiritProxy.GetEntity() ); // untested } } SnapDownCurrentWeapon(); LighterOff(); // Set the player into spiritwalk mode bSpiritWalk = true; } //============================================================================= // // hhPlayer::DisableEthereal // // Returns the player from an ethereal state (used for both spiritwalk and deathwalk) // This function does the following: // - Clears bSpiritWalk (since this is true for both spirit and death walks) // - Removes the proxy // - Sets the new collision // - Re-enables the weapons // - Updates bindings // - Fade volume back up on all class 0 sounds //============================================================================= void hhPlayer::DisableEthereal( void ) { hhFxInfo fxInfo; idVec3 boneOffset; idMat3 boneAxis; //rww - don't do anything on the client - DO NOT PUT ANYTHING ABOVE HERE if (gameLocal.isClient) { return; } bSpiritWalk = false; // Spawn in an effect when the spirit is snapped back GetJointWorldTransform( "waist", boneOffset, boneAxis ); fxInfo.RemoveWhenDone( true ); fxInfo.SetNormal( boneAxis[1] ); BroadcastFxInfo( spawnArgs.GetString( "fx_spiritReturn" ), boneOffset, GetAxis(), &fxInfo ); if( spiritProxy.IsValid() ) { // Handle case of returning to a body on a rail ride idEntity *spiritMaster = spiritProxy->GetBindMaster(); if (spiritMaster != NULL) { if (spiritMaster->IsType(hhBindController::Type)) { hhBindController *binder = static_cast(spiritMaster); bool loose = binder->IsLoose(); binder->Detach(); spiritProxy->DeactivateProxy(); binder->Attach(this, loose); } else if (spiritMaster->IsType(hhMonsterAI::Type)) { if (reinterpret_cast (spiritMaster)->GetEnemy() != static_cast (spiritProxy.GetEntity())) { gameLocal.Warning("Monster has spirit proxy bound to it but spirit proxy is not it's enemy!\n"); } spiritProxy->DeactivateProxy(); } else { gameLocal.Warning("Handle this: spiritwalking while bound to non-rail"); idEntity *temp = spiritProxy.GetEntity(); // this removed by DeactivateProxy spiritProxy->DeactivateProxy(); SwapBindInfo(temp); // untested } } else { // Normal case spiritProxy->DeactivateProxy(); } } PutawayEtherealWeapon(); GetPhysics()->SetClipMask( MASK_PLAYERSOLID ); fl.acceptsWounds = true; spawnArgs.Set("produces_splats", "1"); // Turn off low-pass effect if (gameLocal.localClientNum == entityNumber) { //rww - don't do this for anyone but the local client on listen servers gameLocal.SpiritWalkSoundMode( false ); } // HUMANHEAD mdl: Let our enemies know the spirit proxy is going away for ( int i = 0; i < hhMonsterAI::allSimpleMonsters.Num(); i++ ) { if ( hhMonsterAI::allSimpleMonsters[i]->GetEnemy() == spiritProxy.GetEntity() ) { hhMonsterAI::allSimpleMonsters[i]->ProcessEvent( &MA_EnemyIsPhysical, this, spiritProxy.GetEntity() ); } else if ( hhMonsterAI::allSimpleMonsters[i]->GetEnemy() == this ) { // Targetting spirit that is going away hhMonsterAI::allSimpleMonsters[i]->ProcessEvent( &MA_EnemyIsPhysical, this, NULL ); } } //HUMANHEAD rww - clear the focus now that we've changed positions by switching out of spirit mode focusTime = 0; UpdateFocus(); //HUMANHEAD END spiritProxy = NULL; } //============================================================================= // // hhPlayer::GetResurrectionPoint // // Get the position/orientation for resurrection after deathwalk //============================================================================= void hhPlayer::GetResurrectionPoint( idVec3& origin, idMat3& axis, idVec3& viewDir, idAngles& angles, idMat3& eyeAxis, const idBounds& absBounds, const idVec3& defaultOrigin, const idMat3& defaultAxis, const idVec3& defaultViewDir, const idAngles& defaultAngles ) { idClipModel *clipModelList[ MAX_GENTITIES ]; idClipModel* clipModel = NULL; idEntity* entity = NULL; hhSafeResurrectionVolume* volume = NULL; int num = gameLocal.clip.ClipModelsTouchingBounds( absBounds, CONTENTS_DEATHVOLUME, clipModelList, MAX_GENTITIES ); for( int ix = 0; ix < num; ++ix ) { clipModel = clipModelList[ix]; if( !clipModel ) { continue; } entity = clipModel->GetEntity(); if( !entity || !entity->IsType(hhSafeResurrectionVolume::Type) ) { continue; } volume = static_cast( entity ); volume->PickRandomPoint( origin, axis ); viewDir = axis[0]; angles = axis.ToAngles(); eyeAxis = mat3_identity; return; } eyeAxis = GetEyeAxis(); origin = defaultOrigin; axis = defaultAxis; viewDir = defaultViewDir; angles = defaultAngles; // gameLocal.Printf("Deathwalk @:\n"); // gameLocal.Printf(" origin: %s\n", origin.ToString(0)); // gameLocal.Printf(" axis: %s\n", axis.ToString(2)); // gameLocal.Printf(" viewDir: %s\n", viewDir.ToString(2)); // gameLocal.Printf(" angles: %s\n", angles.ToString(0)); } //============================================================================= // // hhPlayer::SquishedByDoor // //============================================================================= void hhPlayer::SquishedByDoor(idEntity *door) { if ( door == this ) { // Don't allow the squished code to send a player back it the squisher is the player itself return; } // If there is a spirit player in the door, make him go back to physical so he doesn't become stuck if (IsSpiritWalking()) { StopSpiritWalk(); } } //============================================================================= // // hhPlayer::UpdatePossession // // Updates the possession effects (if the player is possessed) // // HUMANHEAD cjr //============================================================================= void hhPlayer::UpdatePossession( void ) { if ( !IsPossessed() ) { // Player is not possessed return; } /* todo: possessionTimer -= MS2SEC( USERCMD_MSEC ); // Update a shaderparm for the view screen to update based upon possessionTimer possessionMax = spawnArgs.GetFloat( "possessionTime", "8" ); possessionScale = possessionTimer / possessionMax; playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, possessionScale ) ); // Mess with the player's FOV while they are being possessed possessionFOV = g_fov.GetFloat() + (1.0f - possessionScale) * 60 + 1.5f * sin( MS2SEC(gameLocal.time) * 3 ); if ( possessionTimer <= 0 ) { // The player is now fully possessed, kill them // TODO: Extra code to show the possessed player running around and such? Unpossess(); Damage( NULL, NULL, vec3_origin, "damage_crush", 1.0f, INVALID_JOINT ); } */ } //============================================================================= // // hhPlayer::RechargeHealth // // Recharges the player's health to 25% // // This is done to avoid gameplay issues where the player has only a few points // of health. The game will still be tense if the player is at 25%, but // will play more fairly. // // HUMANHEAD cjr //============================================================================= void hhPlayer::Event_RechargeHealth( void ) { if ( health > 0 && health < spawnArgs.GetInt( "healthRecharge", "25" ) && !IsSpiritOrDeathwalking() ) { // Only recharge if the player is alive and in the critical zone if ( gameLocal.time - lastDamagedTime >= spawnArgs.GetInt( "healthRechargeDelay", "1500" ) ) { // Delay before recharging health++; } } PostEventSec( &EV_RechargeHealth, spawnArgs.GetFloat( "healthRechargeRate", "0.5" ) ); } //============================================================================= // // hhPlayer::RechargeRifleAmmo // // Recharges rifle ammo if the player is very low. This is done to guarantee // that the player has at least some ammo to solve puzzles that require shooting // something such as a gravity switch. // // HUMANHEAD cjr //============================================================================= void hhPlayer::Event_RechargeRifleAmmo( void ) { int ammoIndex = inventory.AmmoIndexForAmmoClass( "ammo_rifle" ); int ammoCount = inventory.ammo[ ammoIndex ]; int maxAmmo = spawnArgs.GetInt( "rifleAmmoRechargeMax", "20" ); if ( ammoCount < maxAmmo && !AI_ATTACK_HELD ) { // CJR PCF 04/26/06 inventory.ammo[ ammoIndex ] += 2; if ( inventory.ammo[ ammoIndex ] > maxAmmo ) { inventory.ammo[ ammoIndex ] = maxAmmo; } } PostEventSec( &EV_RechargeRifleAmmo, spawnArgs.GetFloat( "rifleAmmoRechargeRate", "2" ) ); } //============================================================================= // // hhPlayer::Event_DrainSpiritPower // // HUMANHEAD pdm: 10 Hz timer used to drain spirit power at differing rates //============================================================================= void hhPlayer::Event_DrainSpiritPower() { // JRM: God mode, don't drain! if(godmode) { return; } ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName( "ammo_spiritpower" ); if ( gameLocal.isMultiplayer ) { // CJR: Drain spirit power in MP if ( !UseAmmo( ammo_spiritpower, 1 ) ) { StopSpiritWalk(); return; } } // spirit bow alt mode drain if( weapon.IsValid() && weapon->IsType(hhWeaponSpiritBow::Type) ) { hhWeaponSpiritBow *bow = static_cast( weapon.GetEntity() ); if( bow->BowVisionIsEnabled() ) { if( !UseAmmo(ammo_spiritpower, 1) ) { // Bow vision drains spirit power return; } } } PostEventMS( &EV_DrainSpiritPower, spiritDrainHeartbeatMS ); } //============================================================================= // // hhPlayer::DeathWalk // //============================================================================= void hhPlayer::DeathWalk( const idVec3& resurrectOrigin, const idMat3& resurrectBBoxAxis, const idMat3& resurrectViewAxis, const idAngles& resurrectViewAngles, const idMat3& resurrectEyeAxis ) { if ( IsSpiritWalking() ) { // Ensure that two proxies cannot be active at the same time StopSpiritWalk(); } bDeathWalk = true; deathWalkTime = gameLocal.time; // Get the time of death health = 0; // Force health to zero deathWalkPower = spawnArgs.GetInt( "deathWalkPowerStart" ); // Power in deathwalk. When full, the player will return fl.takedamage = false; // can no longer be damaged in deathwalk mode if (hud) { hud->HandleNamedEvent("SwitchToEthereal"); } // Disable clip until we get to deathwalk bInDeathwalkTransition = true; //rww - for deathwalk, let's store these values on the player instead of relying on the death proxy deathwalkLastOrigin = resurrectOrigin; deathwalkLastBBoxAxis = resurrectBBoxAxis; deathwalkLastViewAxis = resurrectViewAxis; deathwalkLastViewAngles = resurrectViewAngles; deathwalkLastEyeAxis = resurrectEyeAxis; deathwalkLastCrouching = IsCrouching(); EnableEthereal( spawnArgs.GetString("def_deathProxy"), resurrectOrigin, resurrectBBoxAxis, resurrectViewAxis, resurrectViewAngles, resurrectEyeAxis ); // Set the overlay material for DeathWalk playerView.SetViewOverlayColor( colorWhite ); // Guarantee that the color is reset PostEventMS( &EV_SetOverlayMaterial, 1, spawnArgs.GetString("mtr_deathWalk"), false ); //Don't allow weapon use for a bit if ( weapon.GetEntity() ) { weapon.GetEntity()->PutAway(); } // Alert the scripts that death has occured so they can manage music idEntity *dwJustDied = gameLocal.FindEntity("dw_justdied"); if (dwJustDied) { dwJustDied->PostEventMS(&EV_Activate, 0, this); } deathwalkSoundController.StartSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 ); // Flash the screen before entering death world playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, 0.0f ) ); deathWalkFlash = 0.0f; possessionFOV = 90.0f; CancelEvents( &EV_RechargeHealth ); CancelEvents( &EV_RechargeRifleAmmo ); // If this is our first time, tell the script if (!gameLocal.IsCompetitive() && !inventory.bHasDeathwalked) { const function_t *firstFunc = gameLocal.program.FindFunction( "map_deathwalk::FirstDeathwalk" ); if ( firstFunc ) { idThread *thread = new idThread(); thread->CallFunction( firstFunc, false ); thread->DelayedStart( 0 ); } } //rww - if there is a deathwalk portal, let's create a target for it and activate it if (!gameLocal.FindEntity("dw_deathLocation")) { //check to see if we've made ourselves a target yet idEntity *dwPortal = gameLocal.FindEntity( "dw_deathPortal" ); if (dwPortal) { trace_t tr; idVec3 camPos, camDir; idAngles camAngles; idDict args; //FIXME more complex logic that allows angles to change based on collision circumstances //do wider trace that takes the actual view frustum of camera into account //check for collisions with the actual view "plane" camPos = resurrectOrigin; camPos += resurrectBBoxAxis[2] * 64.0f; gameLocal.clip.TracePoint(tr, resurrectOrigin, camPos, CONTENTS_SOLID, this); camDir = resurrectOrigin-camPos; camAngles = camDir.ToAngles(); args.Clear(); args.SetVector("origin", tr.endpos); args.SetAngles("angles", camAngles); args.SetMatrix("rotation", camAngles.ToMat3()); args.Set("name", "dw_deathLocation"); gameLocal.SpawnObject("info_null", &args); dwPortal->ProcessEvent(&EV_UpdateCameraTarget); //update for the newly placed cameraTarget dwPortal->PostEventMS(&EV_Activate, 0, this); if (dwPortal->cameraTarget) { //if camera target was legitimate, force an update to the remoteRenderView now dwPortal->GetRenderEntity()->remoteRenderView = dwPortal->cameraTarget->GetRenderView(); } } } Hide(); // Don't show the player when they are in the limbo state between alive and dead PostEventSec( &EV_PrepareForDeathWorld, spawnArgs.GetFloat( "prepareForDeathWorldDelay", "1.5" ) ); } //============================================================================= // // hhPlayer::Event_PrepareForDeathWorld // // Flashes the screen / fades the FOV //============================================================================= void hhPlayer::Event_PrepareForDeathWorld() { deathWalkFlash += spawnArgs.GetFloat( "deathWalkFlashChange", "0.05" ); if ( deathWalkFlash >= 1.0f ) { deathWalkFlash = 1.0f; PostEventSec( &EV_EnterDeathWorld, 0.0f ); // Actually send player into the deathworld } else { PostEventSec( &EV_PrepareForDeathWorld, 0.02f ); } possessionFOV += spawnArgs.GetFloat( "deathWalkFOVChange", "3.0" ); playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, deathWalkFlash ) ); } //============================================================================= // // hhPlayer::Event_EnterDeathWorld // // - Teleports the player to the death world // - Spawns the death wraiths // - Gives the player a weapon //============================================================================= void hhPlayer::Event_EnterDeathWorld() { // Reset player view information playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, 0.0f ) ); // Turn off the flash possessionFOV = 0.0f; bDeathWalkStage2 = false; // Spawn in DeathWraiths after a short period of time if ( !inventory.bHasDeathwalked ) { // Delay the wraiths the first time the player deathwalks int firstDelay = spawnArgs.GetInt( "deathWalk_firstTimeDelay", "15000" ); PostEventMS( &EV_SpawnDeathWraith, firstDelay ); // CJR: delay the deathwraiths for several seconds while the Grandfather DW speech is going on PostEventMS( &EV_AdjustSpiritPowerDeathWalk, firstDelay + 2000 ); inventory.bHasDeathwalked = true; } else { PostEventMS( &EV_SpawnDeathWraith, 0 ); // CJR: Non-first time, spawn the wraiths instantly PostEventMS( &EV_AdjustSpiritPowerDeathWalk, 2000 ); } // Give the player the spirit bow SelectEtherealWeapon(); // get the entity which tells us where we want to place our dw proxy idEntity *dwProxyPosEnt = gameLocal.FindEntity( "dw_floatingBodyMarker" ); if (dwProxyPosEnt) { //if we found that, then this is the new deathwalk, so create the deathwalk proxy. hhDeathWalkProxy *dwProxy = (hhDeathWalkProxy *)gameLocal.SpawnObject( spawnArgs.GetString("def_deathWalkProxy"), NULL ); if (!dwProxy) { gameLocal.Error("hhPlayer::Event_EnterDeathWorld: Could not create hhDeathWalkProxy"); return; } idAngles dwProxyAngles; dwProxyPosEnt->spawnArgs.GetAngles("angles", "0 0 0", dwProxyAngles); dwProxy->ActivateProxy(this, dwProxyPosEnt->GetOrigin(), dwProxyAngles.ToMat3(), dwProxyAngles.ToMat3(), dwProxyAngles, GetEyeAxis()); } idEntity* deathWalkPlayerStartManager = gameLocal.FindEntity( "dw_deathWalkPlayerStartManager" ); if( !deathWalkPlayerStartManager ) { return; } idEntity* deathWalkStart = deathWalkPlayerStartManager->PickRandomTarget(); if( deathWalkStart ) { deathWalkStart->ProcessEvent( &EV_Activate, this ); } // Trigger the 'DeathWalkEntered' entity // This allows the scripters to do something upon entering deathwalk idEntity *dwe = gameLocal.FindEntity("dw_DeathWalkEntered"); if (dwe) { dwe->PostEventMS(&EV_Activate, 0, this); } // Allow collisions again now that we're in the death world bInDeathwalkTransition = false; Show(); // The player is visible in deathmode // Reset the player's health and spirit power ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName( "ammo_spiritpower" ); inventory.ammo[ ammo_spiritpower ] = 0; SetHealth( hhMath::hhMax( health, spawnArgs.GetInt("minResurrectHealth", "50") ) ); } //============================================================================= // // hhPlayer::Event_AdjustSpiritPowerDeathWalk // //============================================================================= void hhPlayer::Event_AdjustSpiritPowerDeathWalk() { if( !IsDeathWalking() ) { return; } // Increase deathwalk power int power = GetDeathWalkPower(); power += spawnArgs.GetInt( "deathWraithPowerAmount" ); SetDeathWalkPower( power ); // Check if deathWalkPower has exceeded the maximum if ( deathWalkPower >= spawnArgs.GetInt( "deathWalkPowerMax", "1000") ) { DeathWalkSuccess(); return; } CancelEvents( &EV_AdjustSpiritPowerDeathWalk ); PostEventSec( &EV_AdjustSpiritPowerDeathWalk, spawnArgs.GetFloat("deathWalkDeathPowerIncreaseRate") ); } //============================================================================= // // hhPlayer::DeathWalkSuccess // // Called when deathwalk timer runs out or we run out of spirit power //============================================================================= void hhPlayer::DeathWalkSuccess() { CancelEvents( &EV_AdjustSpiritPowerDeathWalk ); bDeathWalkStage2 = true; idEntity *dw_deathWalkResult = gameLocal.FindEntity("dw_deathWalkSuccess"); if (dw_deathWalkResult) { //if we have a result, activate it. dw_deathWalkResult->ProcessEvent(&EV_Activate, this); } } //============================================================================= // // hhPlayer::ReallyKilled // // Called when the player should be truly killed // Funnels through Killed(), but disabled the deathwalk functionality //============================================================================= void hhPlayer::ReallyKilled( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { if ( !IsSpiritOrDeathwalking() ) { return; } bDeathWalk = false; bSpiritWalk = false; bReallyDead = true; // Turn off low-pass effect if (!gameLocal.isClient) { //rww if (gameLocal.localClientNum == entityNumber) { //rww - don't do this for anyone but the local client on listen servers gameLocal.SpiritWalkSoundMode( false ); gameLocal.DialogSoundMode( false ); } } if (hud) { hud->HandleNamedEvent("SwitchFromEthereal"); } Killed( inflictor, attacker, damage, dir, location ); } //============================================================================= // // hhPlayer::Event_SpawnDeathWraith // // Spawns in a special type of wraith that is only visible in deathwalk //============================================================================= void hhPlayer::Event_SpawnDeathWraith() { idDict args; idEntity *ent; int maxDeathWalkWraiths = spawnArgs.GetInt( "deathWalkMaxWraiths" ); if ( !IsDeathWalking() ) { // Don't spawn in a wraith if the player has resurrected return; } float angle = hhMath::PI * gameLocal.random.RandomFloat(); idVec3 startPoint = idVec3( hhMath::Sin(angle) * 600.0f, hhMath::Cos(angle) * 600.0f, 0.0f ) - GetAxis()[2] * 100.0f; args.Clear(); args.SetVector( "origin", GetEyePosition() + startPoint ); args.SetMatrix( "rotation", idAngles( 0.0f, (-viewAxis[0]).ToYaw(), 0.0f ).ToMat3() ); ent = gameLocal.SpawnObject( spawnArgs.GetString("def_deathWraith"), &args ); if ( ent ) { hhDeathWraith *wraith = static_cast< hhDeathWraith * > ( ent ); wraith->SetEnemy( this ); } // Check the number of deathwraiths in the world int count = 0; for( ent = gameLocal.activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { if ( ent && ent->IsType( hhDeathWraith::Type ) ) { count++; } } if ( count < maxDeathWalkWraiths ) { PostEventMS( &EV_SpawnDeathWraith, 0 ); } } //============================================================================= // // hhPlayer::GetDeathwalkEnergyDestination // //============================================================================= idEntity *hhPlayer::GetDeathwalkEnergyDestination() { return gameLocal.FindEntityOfType(hhDeathWalkProxy::Type, NULL); } //============================================================================= // // hhPlayer::DeathWalkDamagedByWraith // //============================================================================= void hhPlayer::DeathWalkDamagedByWraith(idEntity *attacker, const char *damageType) { Damage( attacker, attacker, vec3_origin, damageType, spawnArgs.GetFloat( "deathWalkWraithDamage", "10" ), INVALID_JOINT ); CancelEvents( &EV_SpawnDeathWraith ); PostEventMS( &EV_SpawnDeathWraith, 0 ); } //============================================================================= // // hhPlayer::KilledDeathWraith // //============================================================================= void hhPlayer::KilledDeathWraith( void ) { CancelEvents( &EV_SpawnDeathWraith ); PostEventMS( &EV_SpawnDeathWraith, 0 ); } //============================================================================= // // hhPlayer::DeathWraithEnergyArived // //============================================================================= void hhPlayer::DeathWraithEnergyArived(bool energyHealth) { if (energyHealth) { const char *healthAmount = spawnArgs.GetString("deathWraithHealthAmount"); Give( "health", healthAmount ); } else { Give( "ammo_spiritpower", spawnArgs.GetString( "deathWraithSpiritAmount" ) ); } // Bump the deathwalk power up a bit so the player lowers faster int power = GetDeathWalkPower(); power += spawnArgs.GetInt( "deathWalkPowerEnergyBoost", "100" ); SetDeathWalkPower( power ); } //============================================================================= // // hhPlayer::Resurrect // //============================================================================= void hhPlayer::Resurrect( void ) { bDeathWalk = false; possessionFOV = 0.0f; PostEventSec( &EV_AllowDamage, 3 ); // Re-enable damage in 3 seconds fl.noknockback = false; // Restore knockback ability // Cancel the impending translation to deathworld (only happens if the player resurrect cheats early) CancelEvents( &EV_PrepareForDeathWorld ); CancelEvents( &EV_EnterDeathWorld ); CancelEvents( &EV_SpawnDeathWraith ); DisableEthereal(); // Reset the view overlay playerView.SetViewOverlayTime( 0, true ); playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, 0.0f ) ); //rww - deathwalk position is restored from the player now. RestorePlayerLocationFromDeathwalk(deathwalkLastOrigin, deathwalkLastBBoxAxis, deathwalkLastViewAxis[0], deathwalkLastViewAngles); deathwalkSoundController.StopSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 ); ProcessEvent( &EV_RechargeHealth ); ProcessEvent( &EV_RechargeRifleAmmo ); if (hud) { hud->HandleNamedEvent("SwitchFromEthereal"); } // TODO: Build in an autoimmune time? lastResurrectTime = gameLocal.GetTime(); // Radius blast (doesn't damage enemies / objects, only shoves them back) gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, this, this, this, spawnArgs.GetString("def_resurrect_damage") ); Show(); // Ensure that the player is visible when they resurrect // trigger the 'DeathWalkExited' entity idEntity *dwe = gameLocal.FindEntity("dw_DeathWalkExited"); if (dwe) { dwe->PostEventMS(&EV_Activate, 0, this); } // bg: Visual effect for returning from DeathWalk uses landing camera movement. landChange = -10; landTime = gameLocal.time; //rww - deactivate dw portal and remove cam target idEntity *dwPortal = gameLocal.FindEntity( "dw_deathPortal" ); if (dwPortal && dwPortal->IsActive()) { dwPortal->cameraTarget = NULL; //reset the camera target dwPortal->PostEventMS(&EV_Activate, 0, dwPortal); } idEntity *dwCamTarget = gameLocal.FindEntity( "dw_deathLocation" ); if (dwCamTarget) { dwCamTarget->PostEventMS(&EV_Remove, 50); //make sure this is removed after the portal has stopped. } } /* ============ hhPlayer::IsWallWalking ============ */ bool hhPlayer::IsWallWalking( void ) const { return( physicsObj.IsWallWalking() ); } /* ============ hhPlayer::WasWallWalking ============ */ bool hhPlayer::WasWallWalking( void ) const { return( physicsObj.WasWallWalking() ); } /* ============ hhPlayer::MangleControls ============ */ void hhPlayer::MangleControls( usercmd_t *cmd ) { // When frozen in a cinematic, we want to restrict any movement and disallow many features if (guiWantsControls.IsValid()) { if (cmd->buttons & BUTTON_ATTACK_ALT) { guiWantsControls->LockedGuiReleased(this); guiWantsControls = NULL; // release } else { guiWantsControls->PlayerControls(cmd); } cmd->forwardmove = 0; cmd->rightmove = 0; cmd->upmove = 0; cmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ZOOM|BUTTON_ATTACK_ALT); cmd->impulse = 0; } else if (InCinematic()) { cmd->forwardmove = 0; cmd->rightmove = 0; cmd->upmove = 0; cmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ZOOM|BUTTON_ATTACK_ALT); cmd->impulse = 0; } } /* ============ hhPlayer::GetPilotInput Called from PilotVehicleInterface ============ */ void hhPlayer::GetPilotInput( usercmd_t& pilotCmds, idAngles& pilotViewAngles ) { pilotCmds = gameLocal.usercmds[ entityNumber ]; pilotViewAngles = DetermineViewAngles( pilotCmds, cmdAngles ); } /* ============== hhPlayer::Think PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge ============== */ void hhPlayer::Think( void ) { renderEntity_t *headRenderEnt; UpdatePossession(); UpdatePlayerIcons(); // latch button actions oldButtons = usercmd.buttons; // grab out usercmd usercmd_t oldCmd = usercmd; usercmd = gameLocal.usercmds[ entityNumber ]; buttonMask &= usercmd.buttons; usercmd.buttons &= ~buttonMask; if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { return; } //HUMANHEAD rww - keep air attacker timer refreshed if (!AI_ONGROUND && airAttackerTime > gameLocal.time) { airAttackerTime = gameLocal.time + 300; } // HUMANHEAD pdm: Depending on our mode, might want to modify the input MangleControls( &usercmd ); // HUMANHEAD END // clear the ik before we do anything else so the skeleton doesn't get updated twice walkIK.ClearJointMods(); // if this is the very first frame of the map, set the delta view angles // based on the usercmd angles if ( !spawnAnglesSet && ( gameLocal.GameState() != GAMESTATE_STARTUP ) ) { spawnAnglesSet = true; SetViewAngles( spawnAngles ); oldFlags = usercmd.flags; } if ( gameLocal.inCinematic || influenceActive ) { usercmd.forwardmove = 0; usercmd.rightmove = 0; usercmd.upmove = 0; } // log movement changes for weapon bobbing effects if ( usercmd.forwardmove != oldCmd.forwardmove ) { loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; currentLoggedAccel++; acc->time = gameLocal.time; acc->dir[0] = usercmd.forwardmove - oldCmd.forwardmove; acc->dir[1] = acc->dir[2] = 0; } if ( usercmd.rightmove != oldCmd.rightmove ) { loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; currentLoggedAccel++; acc->time = gameLocal.time; acc->dir[1] = usercmd.rightmove - oldCmd.rightmove; acc->dir[0] = acc->dir[2] = 0; } // freelook centering if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_MLOOK ) { centerView.Init( gameLocal.time, 200, viewAngles.pitch, 0 ); } // zooming if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_ZOOM ) { if ( ( usercmd.buttons & BUTTON_ZOOM ) && weapon.GetEntity() ) { zoomFov.Init( gameLocal.time, 200.0f, CalcFov( false ), weapon.GetEntity()->GetZoomFov() ); } else { zoomFov.Init( gameLocal.time, 200.0f, zoomFov.GetCurrentValue( gameLocal.time ), DefaultFov() ); } } if ( g_fov.IsModified() ) { idEntity *weaponEnt = weapon.GetEntity(); if ( ! ( weaponEnt && weaponEnt->IsType( hhWeaponZoomable::Type ) && reinterpret_cast (weaponEnt)->IsZoomed() ) ) { GetZoomFov().Init( gameLocal.GetTime(), 0.0f, CalcFov(true), g_fov.GetInteger() ); g_fov.ClearModified(); } } // if we have an active gui, we will unrotate the view angles as // we turn the mouse movements into gui events idUserInterface *gui = ActiveGui(); if ( gui && gui != focusUI ) { RouteGuiMouse( gui ); } // set the push velocity on the weapon before running the physics if ( weapon.GetEntity() ) { weapon.GetEntity()->SetPushVelocity( physicsObj.GetPushedLinearVelocity() ); } EvaluateControls(); if ( !af.IsActive() ) { AdjustBodyAngles(); CopyJointsFromBodyToHead(); } //HUMANHEAD: aob - added vehicle check. Vehicle moves us if( !InVehicle() ) { Move(); } //HUMANHEAD END if ( !g_stopTime.GetBool() ) { //HUMANHEAD: aob - changed heath check to IsDead check. Player needs to touch triggers when deathwalking. if ( !noclip && !spectating && !IsDead() && !IsHidden() ) { TouchTriggers(); } UpdateLighter(); // update GUIs, Items, and character interactions UpdateFocus(); UpdateLocation(); // update player script UpdateScript(); // service animations if ( !spectating && !af.IsActive() && !gameLocal.inCinematic ) { UpdateConditions(); UpdateAnimState(); CheckBlink(); } // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think AI_PAIN = false; } // calculate the exact bobbed view position, which is used to // position the view weapon, among other things CalculateFirstPersonView(); // this may use firstPersonView, or a thirdPeroson / camera view CalculateRenderView(); if ( spectating ) { if (!gameLocal.isClient) { UpdateSpectating(); } //HUMANHEAD: aob - changed heath check to IsDead check. Player needs to update weapon when deathwalking } else if ( !IsDead() && !bFrozen ) { //HUMANHEAD bjk PCF (4-27-06) - no setting unnecessary weapon state if ( !gameLocal.isClient || weapon.GetEntity()) { UpdateWeapon(); } } if (InVehicle()) { UpdateHud( GetVehicleInterfaceLocal()->GetHUD() ); } else { UpdateHud( hud ); } UpdateDeathSkin( false ); if ( gameLocal.isMultiplayer ) { DrawPlayerIcons(); } if ( head.GetEntity() ) { headRenderEnt = head.GetEntity()->GetRenderEntity(); } else { headRenderEnt = NULL; } if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() ) { renderEntity.suppressShadowInViewID = 0; if ( headRenderEnt ) { headRenderEnt->suppressShadowInViewID = 0; } } else { renderEntity.suppressShadowInViewID = entityNumber+1; if ( headRenderEnt ) { headRenderEnt->suppressShadowInViewID = entityNumber+1; } } // never cast shadows from our first-person muzzle flashes renderEntity.suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber; if ( headRenderEnt ) { headRenderEnt->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber; } if ( !g_stopTime.GetBool() ) { UpdateAnimation(); Present(); UpdateDamageEffects(); if (gameLocal.isMultiplayer) { //rww UpdateWounds(); } LinkCombat(); playerView.CalculateShake(); } if ( g_showEnemies.GetBool() ) { idActor *ent; int num = 0; for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { gameLocal.Printf( "enemy (%d)'%s'\n", ent->entityNumber, ent->name.c_str() ); gameRenderWorld->DebugBounds( colorRed, ent->GetPhysics()->GetBounds().Expand( 2 ), ent->GetPhysics()->GetOrigin() ); num++; } gameLocal.Printf( "%d: enemies\n", num ); } } /* ============== hhPlayer::AdjustBodyAngles ============== */ void hhPlayer::AdjustBodyAngles( void ) { if (InVehicle()) { return; } if (bClampYaw) { //rww - for slabs //first, clamp the viewangles while bound to a slab if (maxRelativePitch >= 0.0f) { if (untransformedViewAngles.pitch > maxRelativePitch) { untransformedViewAngles.pitch = maxRelativePitch; SetViewAngles(untransformedViewAngles); } else if (untransformedViewAngles.pitch < -maxRelativePitch) { untransformedViewAngles.pitch = -maxRelativePitch; SetViewAngles(untransformedViewAngles); } } animator.SetJointAxis( hipJoint, JOINTMOD_WORLD, mat3_identity ); //no leg offset while slabbed #if 0 //old headlook code if (head.IsValid() && head.GetEntity()) { //has a head idVec3 origin; idMat3 axis; head->GetPhysics()->Evaluate(gameLocal.time-gameLocal.previousTime, gameLocal.time); //make sure it is bolted up to date if (GetMasterPosition(origin, axis)) { idAngles masterAngles = axis.ToAngles(); //bound angles idAngles ang; idMat3 rot; //determine our "look" angles ang.yaw = viewAngles.yaw-masterAngles.yaw; ang.pitch = viewAngles.pitch; ang.roll = 0.0f; ang = ang.Normalize180(); ang *= 0.5f; //scale the angles down so the head doesn't go completely sideways or anything //set the yaw only animator.SetJointAxis(headJoint, JOINTMOD_WORLD, idAngles(0.0f, ang.yaw, 0.0f).ToMat3()); ang.yaw = 0.0f; hhMath::BuildRotationMatrix(DEG2RAD(-ang.pitch), 0, rot); //create a rotation matrix for the pitch //get the local axis to multiply it by the pitch rotation matrix animator.GetJointLocalTransform(headJoint, gameLocal.time, origin, axis); //use the multiplied axis animator.SetJointAxis(headJoint, JOINTMOD_LOCAL, rot*axis); //point head in proper direction } } #endif } else { #if 0 //old headlook code //rww - reset head joint override when not on a slab animator.SetJointAxis( headJoint, JOINTMOD_LOCAL, idAngles( 0.0f, 0.0f, 0.0f ).ToMat3() ); #endif idPlayer::AdjustBodyAngles(); } } /* ============== hhPlayer::Move PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge ============== */ void hhPlayer::Move( void ) { float newEyeOffset; idVec3 oldOrigin; idVec3 oldVelocity; idVec3 pushVelocity; // save old origin and velocity for crashlanding oldOrigin = physicsObj.GetOrigin(); oldVelocity = physicsObj.GetLinearVelocity(); pushVelocity = physicsObj.GetPushedLinearVelocity(); // set physics variables physicsObj.SetMaxStepHeight( GetStepHeight() );//HUMANHEAD: aob physicsObj.SetMaxJumpHeight( pm_jumpheight.GetFloat() ); if ( noclip ) { physicsObj.SetContents( 0 ); physicsObj.SetMovementType( PM_NOCLIP ); } else if ( spectating ) { physicsObj.SetContents( 0 ); physicsObj.SetMovementType( PM_SPECTATOR ); } else if ( bInDeathwalkTransition ) { // In between life and death, don't allow ragdoll to collide with player physicsObj.SetContents( 0 ); physicsObj.SetMovementType( PM_NORMAL ); } else if ( IsDead() ) { // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override if (gameLocal.isMultiplayer) { // HUMANHEAD rww - contents 0 because we use a dead body proxy in mp physicsObj.SetContents(0); } else { physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP ); } //HUMANHEAD END physicsObj.SetMovementType( PM_DEAD ); } else if ( gameLocal.inCinematic || gameLocal.GetCamera() || privateCameraView ) { physicsObj.SetContents( CONTENTS_BODY ); physicsObj.SetMovementType( PM_FREEZE ); } else if ( bFrozen && !IsSpiritOrDeathwalking() ) { // HUMANHEAD: freeze support physicsObj.SetContents( CONTENTS_BODY ); physicsObj.SetMovementType( PM_FREEZE ); } else { physicsObj.SetContents( CONTENTS_BODY ); physicsObj.SetMovementType( PM_NORMAL ); } if ( spectating ) { physicsObj.SetClipMask( MASK_SPECTATOR ); } else if ( IsDead() ) { // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override physicsObj.SetClipMask( MASK_DEADSOLID ); // HUMANHEAD cjr: allow spirit walk } else if ( IsSpiritOrDeathwalking() ) { physicsObj.SetClipMask( MASK_SPIRITPLAYER ); // HUMANHEAD END } else { physicsObj.SetClipMask( MASK_PLAYERSOLID ); } physicsObj.SetDebugLevel( g_debugMove.GetBool() ); physicsObj.SetPlayerInput( usercmd, viewAngles ); //HUMANHEAD: aob - moved down a few lines // FIXME: physics gets disabled somehow //BecomeActive( TH_PHYSICS ); //RunPhysics(); //HUMANHEAD END // update our last valid AAS location for the AI SetAASLocation(); if ( spectating ) { newEyeOffset = 0.0f; // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override } else if ( IsDead() ) { newEyeOffset = pm_deadviewheight.GetFloat(); } else if ( physicsObj.IsCrouching() ) { newEyeOffset = pm_crouchviewheight.GetFloat(); } else if ( GetBindMaster() && GetBindMaster()->IsType( idAFEntity_Vehicle::Type ) ) { newEyeOffset = 0.0f; } else { newEyeOffset = pm_normalviewheight.GetFloat(); } if ( EyeHeight() != newEyeOffset ) { //AOB: camera does smoothing SetEyeHeight( newEyeOffset ); } // HUMANHEAD aob float camRotScale = p_camRotRateScale.GetFloat(); //rww - if on a wallwalk mover, increase the scale if (GetPhysics() == &physicsObj && physicsObj.GetGroundTrace().fraction < 1.0f) { const trace_t &grTr = physicsObj.GetGroundTrace(); if (grTr.c.entityNum >= 0 && grTr.c.entityNum < MAX_GENTITIES && gameLocal.entities[grTr.c.entityNum] && gameLocal.entities[grTr.c.entityNum]->IsType(hhMover::Type) && grTr.c.material && grTr.c.material->GetSurfaceType() == SURFTYPE_WALLWALK) { camRotScale *= 5.0f; } } cameraInterpolator.Setup( camRotScale, IT_VariableMidPointSinusoidal );//Also set in constructor // moved here to allow weapons physics get correct eyeOffset BecomeActive( TH_PHYSICS ); //rww - prevent the bug that id so lovingly left for us RunPhysics(); // HUMANHEAD END // HUMANHEAD pdm: Experimental: allow player to jump up onto wallwalk #define WALLWALK_EXPERIMENT 0 #if WALLWALK_EXPERIMENT if (!physicsObj.IsWallWalking() && physicsObj.HasJumped()) { trace_t trace; memset(&trace, 0, sizeof(trace)); idVec3 start, end; start = physicsObj.GetOrigin(); end = start + idVec3(0,0,100); //fixme: jumpheight if (gameLocal.clip.TracePoint( trace, start, end, MASK_SOLID, this )) { if (gameLocal.GetMatterType(trace, NULL) == SURFTYPE_WALLWALK) { gameLocal.Printf("Trying to flip over for wallwalk\n"); // Clear velocity, so won't miss wallwalk above physicsObj.SetLinearVelocity(vec3_origin); SetGravity(-trace.c.normal * DEFAULT_GRAVITY); ProcessEvent( &EV_ShouldRemainAlignedToAxial, (int)false ); ProcessEvent( &EV_OrientToGravity, (int)true ); physicsObj.IsWallWalking( true ); /* idVec3 flippedOrigin = trace.endpos; idMat3 flippedAxis; flippedAxis[0] = GetAxis()[0]; flippedAxis[2] = trace.c.normal; flippedAxis[1] = flippedAxis[0].Cross(flippedAxis[2]); // cameraInterpolator.SetInterpolationType(flags); // cameraInterpolator.SetTargetEyeOffset(idealEyeOffset, flags); // cameraInterpolator.SetTargetPosition(idealPosition, flags); // Flip the physics object physicsObj.SetOrigin(flippedOrigin); physicsObj.SetAxis(flippedAxis); physicsObj.CheckWallWalk(true);*/ } } } #endif // HUMANHEAD END if ( noclip || gameLocal.inCinematic ) { AI_CROUCH = false; AI_ONGROUND = false; AI_ONLADDER = false; AI_JUMP = false; AI_REALLYFALL = true; //HUMANHEAD rww - well, it fits with ONGROUND false } else { AI_CROUCH = physicsObj.IsCrouching(); AI_ONGROUND = physicsObj.HasGroundContacts(); AI_ONLADDER = physicsObj.OnLadder(); AI_JUMP = physicsObj.HasJumped(); //HUMANHEAD rww if (!physicsObj.IsInwardGravity()) { AI_REALLYFALL = !AI_ONGROUND; } else if (!AI_ONGROUND) { //for inward gravity zones, do an extra ground check when in-air AI_REALLYFALL = !physicsObj.ExtraGroundCheck(); } //HUMANHEAD END // check if we're standing on top of a monster and give a push if we are idEntity *groundEnt = physicsObj.GetGroundEntity(); if ( groundEnt && groundEnt->IsType( idAI::Type ) ) { idVec3 vel = physicsObj.GetLinearVelocity(); if ( vel.ToVec2().LengthSqr() < 0.1f ) { vel.ToVec2() = physicsObj.GetOrigin().ToVec2() - groundEnt->GetPhysics()->GetAbsBounds().GetCenter().ToVec2(); vel.ToVec2().NormalizeFast(); vel.ToVec2() *= pm_walkspeed.GetFloat(); } else { // give em a push in the direction they're going vel *= 1.1f; } physicsObj.SetLinearVelocity( vel ); } } if ( AI_JUMP ) { // bounce the view weapon loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; currentLoggedAccel++; acc->time = gameLocal.time; acc->dir[2] = 200; acc->dir[0] = acc->dir[1] = 0; } BobCycle( pushVelocity ); if ( !noclip ) { // HUMANHEAD: Only crashland if not spiritwalking or not deathwalking CrashLand( oldOrigin, oldVelocity ); } //HUMANHEAD: aob - put this into helper func if (!gameLocal.isClient) { if( IsWallWalking() && !WasWallWalking() ) { wallwalkSoundController.StartSound( SND_CHANNEL_WALLWALK, SND_CHANNEL_WALLWALK2, SSF_LOOPING, false ); } else if( !IsWallWalking() && WasWallWalking() ) { wallwalkSoundController.StopSound( SND_CHANNEL_WALLWALK, SND_CHANNEL_WALLWALK2, false ); } } physicsObj.WasWallWalking( IsWallWalking() ); //rww - moved here //HUMANHEAD END } /* =============== hhPlayer::ShouldTouchTrigger =============== */ bool hhPlayer::ShouldTouchTrigger( idEntity* entity ) const { if( !entity ) { return false; } if ( entity->fl.onlySpiritWalkTouch ) { // cjr - Trigger can only be touched by spirit return IsSpiritWalking(); } if( IsSpiritOrDeathwalking() && !entity->fl.allowSpiritWalkTouch ) { // Trigger can be touched by either physical or spirit return false; // jrm - reversed logic } return true; } /* =============== hhPlayer::HandleSingleGuiCommand =============== */ bool hhPlayer::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { idToken token; if (!src->ReadToken(&token)) { return false; } if (token == ";") { return false; } if (token.Icmp("guilockplayer") == 0) { //Now locked in place: // manglecontrols() will route controls to current gui focus if (entityGui->IsType(hhConsole::Type)) { guiWantsControls = static_cast(entityGui); } return true; } src->UnreadToken(&token); return idPlayer::HandleSingleGuiCommand(entityGui, src); } void hhPlayer::SetOverlayGui(const char *guiName) { if (guiName && *guiName && guiOverlay == NULL) { guiOverlay = uiManager->FindGui(guiName, true); if (guiOverlay) { guiOverlay->Activate(true, gameLocal.time); } } else { guiOverlay = NULL; } } /* ============ hhPlayer::ForceWeapon nla: used to instantly force the spirit weapon, without lowering and raising ============ */ void hhPlayer::ForceWeapon( int weaponNum ) { const char * weaponDef; hhWeapon * newWeapon; if (spectating) { //rww - if spectating this is bad. gameLocal.Error("hhPlayer::ForceWeapon called on spectator. (client %i)", entityNumber); } // HUMANHEAD mdl: Special case - coming out of spirit mode when no physical weapon is available if ( weaponNum == -1 ) { idealWeapon = 0; currentWeapon = -1; return; } if ( weaponNum < 1 ) { gameLocal.Warning( "Error: Illegal weapon num passed: %d\n", weaponNum ); } weaponDef = GetWeaponName( weaponNum ); newWeapon = SpawnWeapon( weaponDef ); weapon = newWeapon; idealWeapon = currentWeapon = weaponNum; animPrefix = weaponDef; animPrefix.Strip( "weaponobj_" ); } /* ============ hhPlayer::ForceWeapon ============ */ void hhPlayer::ForceWeapon( hhWeapon *newWeapon ) { int weaponNum; weapon = newWeapon; weaponNum = GetWeaponNum( weapon->GetDict()->GetString( "classname" ) ); idealWeapon = currentWeapon = weaponNum; } /* =========== hhPlayer::Teleport HUMANHEAD cjr: Now calls TeleportNoKillBox, then applies a kill box ============ */ void hhPlayer::Teleport( const idVec3& origin, const idAngles& angles, idEntity *destination ) { //pdm - converted this to call our other version so it doesn't unalign the clip model TeleportNoKillBox( origin, mat3_identity, angles.ToForward(), angles ); // mdl: Moved here from TeleportNoBox, so player can't avoid falling damage by toggling spirit mode quickly physicsObj.SetLinearVelocity( vec3_origin ); physicsObj.SetKnockBack( 250 ); // Slow the player down after a teleport for a moment // kill anything at the new position gameLocal.KillBox( this, destination != NULL ); teleportEntity = destination; } /* =========== hhPlayer::Teleport HUMANHEAD aob: Needed for coming back from deathwalk ============ */ void hhPlayer::Teleport( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& newUntransformedViewAngles, idEntity *destination ) { TeleportNoKillBox( origin, bboxAxis, viewDir, newUntransformedViewAngles ); // kill anything at the new position gameLocal.KillBox( this, destination != NULL ); teleportEntity = destination; } /* =========== hhPlayer::TeleportNoKillBox HUMANHEAD cjr: Called from SpiritProxy ============ */ void hhPlayer::TeleportNoKillBox( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& newUntransformedViewAngles ) { DisableIK(); SetOrientation( origin, bboxAxis, viewDir, newUntransformedViewAngles ); CancelEvents( &EV_ResetGravity ); ProcessEvent( &EV_ResetGravity ); // Guarantee that gravity is instantly reset after this teleport EnableIK(); } /* =========== hhPlayer::TeleportNoKillBox HUMANHEAD cjr: Teleport, but don't telefrag anything ============ */ void hhPlayer::TeleportNoKillBox( const idVec3& origin, const idMat3& bboxAxis ) { TeleportNoKillBox( origin, bboxAxis, GetAxis()[0], GetUntransformedViewAngles() ); } /* ===================== hhPlayer::Possess //HUMANHEAD: cjr - this is the entry point for possession. ===================== */ void hhPlayer::Possess( idEntity* possessor ) { bPossessed = true; /* TODO: - needs a new spirit proxy (a little hidden object just as storage that is linked to the possessed human) - disallow the player from returning until the possessed tommy has been "killed" possessed tommy: - attacks whatever - health ticking down -- when it reaches zero, ragdoll and instantly kill the spirit player - if killed by the spirit bow, then the wraith is removed (seperate health?) */ // If we're spirit walking when we're possessed, snap back to our body for a moment, then get thrown back out. //TODO this isn't ideal if ( IsSpiritOrDeathwalking() ) { DisableEthereal(); } // Must allow spirit walking at this point HH_ASSERT( bAllowSpirit ); // Force the player into spirit and thrust them backwards StartSpiritWalk( true, true ); // test: spawn in a possessed version // need: effects here...flash or whatever possessedTommy = (hhPossessedTommy *)gameLocal.SpawnObject( "monster_possessed_tommy", NULL ); if ( possessedTommy.IsValid() ) { // Copy player stats to the possessed Tommy /*if ( IsSpiritOrDeathwalking() ) { //TODO mdl: Remove this if we go with wraiths knocking players back to their body // This should means we were called by possessedTommy->SetOrigin( spiritProxy->GetOrigin() ); possessedTommy->SetAxis( spiritProxy->GetAxis() ); } else*/ { possessedTommy->SetOrigin( GetOrigin() ); possessedTommy->SetAxis( GetAxis() ); } possessedTommy->SetPossessedProxy( spiritProxy.GetEntity() ); } } /* ===================== hhPlayer::Unpossess //HUMANHEAD: cjr =====================*/ void hhPlayer::Unpossess() { bPossessed = false; possessedTommy = NULL; /* idDict args; idEntity *ent; playerView.SetViewOverlayMaterial( NULL ); possessionFOV = 0; // Restore the original FOV // Spawn a wraith to fly out of the player's origin args.Clear(); args.SetVector( "origin", GetOrigin() + GetEyePosition() + GetAxis()[0] * 50 ); args.SetMatrix( "rotation", GetAxis() ); //TODO: Externalize this monster_wraith args.Set( "classname", "monster_wraith" ); gameLocal.SpawnEntityDef( args, &ent ); ((hhWraith *)ent)->SetEnemy( this ); // TODO: Unpossessed by portalling, so kill the wraith */ } //============================================================================= // // hhPlayer::CanBePossessed // // Players can only be possessed if they are are not currently possessed //============================================================================= bool hhPlayer::CanBePossessed( void ) { if ( godmode || noclip || !bAllowSpirit ) { // Don't possess if in god mode or noclipping return false; } return !(IsPossessed() || InVehicle() || IsSpiritOrDeathwalking() ); } //============================================================================= // // hhPlayer::PossessKilled // // Killed when possessed (spirit power ran out) // // - Ragdoll the possessed body // - Return to the location of the possessed tommy and die //============================================================================= void hhPlayer::PossessKilled( void ) { if ( possessedTommy.IsValid() ) { possessedTommy->PostEventMS( &EV_Remove, 0 ); } Unpossess(); StopSpiritWalk(); Kill( 0, 0 ); // FIXME: Some other way to kill self (instead of suicide?) } //============================================================================= // // hhPlayer::Portalled // // Player just portalled //============================================================================= void hhPlayer::Portalled( idEntity *portal ) { if ( talon.IsValid() && talon->GetBindMaster() != this ) { // If Talon isn't bound to Tommy, portal him with the player talon->Portalled( portal ); } if (gameLocal.isClient) { //HUMANHEAD rww - compensate for angle jump bBufferNextSnapAngles = true; smoothedAngles = viewAngles; } smoothedFrame = 0; //HUMANHEAD rww - skip smoothing on the portalled frame walkIK.InvalidateHeights(); //HUMANHEAD rww - don't try to interpolate ik positions between two equal planes on either side of a portal if (gameLocal.isServer) { //HUMANHEAD rww - send an unreliable event in the coming snapshot idBitMsg msg; byte msgBuf[MAX_EVENT_PARAM_SIZE]; msg.Init(msgBuf, sizeof(msgBuf)); msg.WriteBits(gameLocal.GetSpawnId(portal), 32); ServerSendEvent(EVENT_PORTALLED, &msg, false, -1, -1, true); //only send it to the client who portalled } } /* ================ hhPlayer::UpdateModelTransform ================ */ void hhPlayer::UpdateModelTransform( void ) { idVec3 origin; idMat3 axis; if( GetPhysicsToVisualTransform(origin, axis) ) { //HUMANHEAD: aob GetRenderEntity()->axis = axis; idVec3 absOrigin = TransformToPlayerSpaceNotInterpolated( origin ); //rww - don't use interpolated origin GetRenderEntity()->origin = absOrigin; //HUMANHEAD END } else { //HUMANHEAD: aob GetRenderEntity()->axis = GetAxis(); GetRenderEntity()->origin = GetOrigin(); //HUMANHEAD END } } /* ==================== hhPlayer::CalcFov Fixed fov at intermissions, otherwise account for fov variable and zooms. Takes possession FOV into account HUMANHEAD cjr ==================== */ float hhPlayer::CalcFov( bool honorZoom ) { float fov; // HUMANHEAD mdl: Refactored this to work properly with possessionFOV. Being zoomed in will ignore possessionFOV, however. idEntity *weaponEnt = weapon.GetEntity(); if ( possessionFOV > 0.0f && ! ( weaponEnt && weaponEnt->IsType( hhWeaponZoomable::Type ) && reinterpret_cast (weaponEnt)->IsZoomed() ) ) { fov = possessionFOV; } else if ( IsDeathWalking() ) { fov = spawnArgs.GetFloat("deathwalkFOV", "90"); } else if ( pm_thirdPerson.GetBool() ) { fov = g_fov.GetFloat(); } else if ( InCinematic() ) { fov = cinematicFOV.GetCurrentValue(gameLocal.time); } else { fov = zoomFov.GetCurrentValue(gameLocal.time); } //HUMANHEAD: aob fov = hhMath::ClampFloat( 1.0f, 179.0f, fov ); //HUMANHEAD END return fov; } /* =============== hhPlayer::EnterVehicle =============== */ void hhPlayer::EnterVehicle( hhVehicle* vehicle ) { if (!gameLocal.isClient) { //HUMANHEAD PCF rww 05/04/06 - do not do the check on the client, just listen to what the snapshot says. if (!vehicle->WillAcceptPilot(this)) { return; } } // Cancel any pending damage allowing events, since we'll be turning damage off CancelEvents(&EV_AllowDamage); // Set shuttle view if (entityNumber == gameLocal.localClientNum) { //rww renderSystem->SetShuttleView( true ); } // Move eye to proper height SetEyeHeight( vehicle->spawnArgs.GetFloat("pilot_eyeHeight") ); // Turn off any illegal behaviors if( IsSpiritWalking() ) { StopSpiritWalk(); } LighterOff(); // nla - Remove any offset cause by jumping. (Fixes the jumping and getting in the shuttle bug) SetViewBob( vec3_origin ); // CJR: Inform Talon that the player has entered a vehicle if( talon.IsValid() ) { talon->OwnerEnteredVehicle(); } ShouldRemainAlignedToAxial( false ); cameraInterpolator.SetInterpolationType( IT_None ); SetOrientation( GetOrigin(), mat3_identity, vehicle->GetAxis()[0], vehicle->GetAxis()[0].ToAngles() ); //Need to clear untransformedViewAxis so cameraInterpolator doesn't do any unnessacary transforms SetUntransformedViewAxis( mat3_identity ); //Hack cameraInterpolator.Reset( GetOrigin(), mat3_identity[2], EyeHeightIdeal() ); idPlayer::EnterVehicle( vehicle ); } /* =============== hhPlayer::ExitVehicle This should only be called from vehicle =============== */ void hhPlayer::ExitVehicle( hhVehicle* vehicle ) { // Allow model in player's view again if (vehicle) { vehicle->GetRenderEntity()->suppressSurfaceInViewID = 0; } // Set shuttle view off if (entityNumber == gameLocal.localClientNum) { //rww renderSystem->SetShuttleView( false ); } ShouldRemainAlignedToAxial( true ); // CJR: Inform Talon that the player has left a vehicle if( talon.IsValid() ) { talon->OwnerExitedVehicle(); } idPlayer::ExitVehicle( vehicle ); buttonMask |= BUTTON_ATTACK_ALT; //HUMANHEAD bjk // Reset the animPrefix animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ); animPrefix.Strip( "weaponobj_" ); if (vehicle) { SetOrientation( GetOrigin(), mat3_identity, vehicle->GetAxis()[0], vehicle->GetAxis()[0].ToAngles().Normalize180() ); } cameraInterpolator.SetInterpolationType( IT_VariableMidPointSinusoidal ); } // // ResetClipModel() // // HUMANHEAD: aob // void hhPlayer::ResetClipModel() { //Needed for touching triggers. Our bbox is its original size. SetClipModel(); } /* =============== hhPlayer::BecameBound =============== */ void hhPlayer::BecameBound(hhBindController *b) { if( !gameLocal.isMultiplayer && weapon.IsValid() ) { //rww - can use weapon while bound in mp weapon->Hide(); } } /* =============== hhPlayer::BecameUnbound =============== */ void hhPlayer::BecameUnbound(hhBindController *b) { if( !gameLocal.isMultiplayer && weapon.IsValid() ) { //rww - can use weapon while bound in mp weapon->Show(); } } void hhPlayer::GetLocationText( idStr &locationString ) { idLocationEntity* locationEntity = gameLocal.LocationForPoint( GetEyePosition() ); if( locationEntity ) { locationString = locationEntity->GetLocation(); } else { locationString = common->GetLanguageDict()->GetString( "#str_02911" ); } } void hhPlayer::UpdateLocation( void ) { if( hud ) { hud->SetStateBool("showlocations", developer.GetBool()); if (developer.GetBool()) { idStr locationString; GetLocationText(locationString); hud->SetStateString( "location", locationString.c_str() ); hud->SetStateInt( "areanum", gameRenderWorld->PointInArea( GetEyePosition() )); } } } /* =============== hhPlayer::FillDebugVars =============== */ void hhPlayer::FillDebugVars(idDict *args, int page) { idStr text; switch(page) { case 1: args->SetInt("deathpower", GetDeathWalkPower()); args->SetInt("spiritpower", GetSpiritPower()); args->SetBool("AI_CROUCH", AI_CROUCH != 0); args->SetBool("AI_ONGROUND", AI_ONGROUND != 0); args->SetBool("AI_JUMP", AI_JUMP != 0); args->SetBool("AI_SOFTLANDING", AI_SOFTLANDING != 0); args->SetBool("AI_HARDLANDING", AI_HARDLANDING != 0); args->SetBool("AI_FORWARD", AI_FORWARD != 0); args->SetBool("AI_BACKWARD", AI_BACKWARD != 0); args->SetBool("AI_STRAFE_LEFT", AI_STRAFE_LEFT != 0); args->SetBool("AI_STRAFE_RIGHT", AI_STRAFE_RIGHT != 0); args->SetBool("AI_ATTACK_HELD", AI_ATTACK_HELD != 0); args->SetBool("AI_WEAPON_FIRED", AI_WEAPON_FIRED != 0); args->SetBool("AI_JUMP", AI_JUMP != 0); args->SetBool("AI_DEAD", AI_DEAD != 0); args->SetBool("AI_PAIN", AI_PAIN != 0); args->SetBool("AI_RELOAD", AI_RELOAD != 0); args->SetBool("AI_TELEPORT", AI_TELEPORT != 0); args->SetBool("AI_TURN_LEFT", AI_TURN_LEFT != 0); args->SetBool("AI_TURN_RIGHT", AI_TURN_RIGHT != 0); args->SetBool("AI_ASIDE", AI_ASIDE != 0); args->SetBool("AI_REALLYFALL", AI_REALLYFALL != 0); //HUMANHEAD rww args->SetBool("HasGroundContacts", physicsObj.HasGroundContacts()); args->Set("animPrefix", animPrefix.c_str()); break; case 2: args->Set("Physics Axis", GetPhysics()->GetAxis().ToAngles().ToString()); args->Set("viewAxis", viewAxis.ToAngles().ToString()); args->Set("cmdAngles", cmdAngles.ToString()); args->Set("deltaViewAngles", GetDeltaViewAngles().ToString() ); args->Set("viewAngles", viewAngles.ToString()); args->Set("untransAngles", GetUntransformedViewAngles().ToString()); args->Set("xPhysics Axis", GetPhysics()->GetAxis().ToString()); args->Set("xviewAxis", viewAxis.ToString()); args->Set("idealLegsYaw", va("%.1f", idealLegsYaw)); args->SetInt("usercmd.forwardmove", usercmd.forwardmove); args->SetInt("usercmd.rightmove", usercmd.rightmove); args->SetInt("legsForward", legsForward); switch(physicsObj.GetWaterLevel()) { case WATERLEVEL_NONE: text = "none"; break; case WATERLEVEL_FEET: text = "feet"; break; case WATERLEVEL_WAIST: text = "waist"; break; case WATERLEVEL_HEAD: text = "head"; break; } args->Set("waterlevel", text); text = collisionModelManager->ContentsName(physicsObj.GetWaterType()); args->Set("watertype", text); break; case 3: break; } idPlayer::FillDebugVars(args, page); } // // GetAimPosition() // idVec3 hhPlayer::GetAimPosition() const { return GetEyePosition(); } /* =============== hhPlayer::WriteToSnapshot =============== */ void hhPlayer::WriteToSnapshot( idBitMsgDelta &msg ) const { bool vehControlling = vehicleInterfaceLocal.ControllingVehicle(); msg.WriteBits(vehControlling, 1); //rww - our stuff is now intermingled with id's for more ideal send ordering physicsObj.WriteToSnapshot( msg, vehControlling ); if (!vehControlling) { //not syncing these causes denormalization/nan issues, FIXME msg.WriteFloat(untransformedViewAngles[0]); msg.WriteFloat(untransformedViewAngles[1]); msg.WriteFloat(untransformedViewAngles[2]); idCQuat q = untransformedViewAxis.ToCQuat(); msg.WriteFloat(q.x); msg.WriteFloat(q.y); msg.WriteFloat(q.z); //rww - write cameraInterpolator cameraInterpolator.WriteToSnapshot(msg, this); //still need delta angles //msg.WriteDeltaFloat( 0.0f, deltaViewAngles[0] ); //msg.WriteDeltaFloat( 0.0f, deltaViewAngles[1] ); //msg.WriteDeltaFloat( 0.0f, deltaViewAngles[2] ); } #if 0 //for debugging differences in snapshot else { msg.WriteFloat(0.0f); msg.WriteFloat(0.0f); msg.WriteFloat(0.0f); msg.WriteFloat(0.0f); msg.WriteFloat(0.0f); msg.WriteFloat(0.0f); msg.WriteFloat(0.0f); msg.WriteFloat(0.0f); msg.WriteFloat(0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteFloat(0.0f, 4, 4); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteFloat(0.0f, 4, 4); msg.WriteFloat(0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteFloat(0.0f, 4, 4); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); msg.WriteDeltaFloat(0.0f, 0.0f); } #endif msg.WriteDeltaFloat( 0.0f, deltaViewAngles[0] ); msg.WriteDeltaFloat( 0.0f, deltaViewAngles[1] ); msg.WriteDeltaFloat( 0.0f, deltaViewAngles[2] ); msg.WriteShort( health ); msg.WriteBits( gameLocal.ServerRemapDecl( -1, DECL_ENTITYDEF, lastDamageDef ), gameLocal.entityDefBits ); msg.WriteDir( lastDamageDir, 9 ); msg.WriteShort( lastDamageLocation ); msg.WriteBits( idealWeapon, idMath::BitsForInteger( MAX_WEAPONS ) ); msg.WriteBits( inventory.weapons, MAX_WEAPONS ); msg.WriteBits( weapon.GetSpawnId(), 32 ); msg.WriteBits( spectator, idMath::BitsForInteger( MAX_CLIENTS ) ); msg.WriteBits( lastHitToggle, 1 ); msg.WriteBits( weaponGone, 1 ); WriteBindToSnapshot( msg ); //===================END OF ID DATA msg.WriteShort(inventory.maxHealth); //rww - since maxhealth can go above 100 in mp now msg.WriteBits( spiritProxy.GetSpawnId(), 32 ); //rww - more spiritwalk stuff msg.WriteBits(lastWeaponSpirit, 32); msg.WriteBits(bSpiritWalk, 1); //not needed anymore /* msg.WriteBits( bShowProgressBar, 1 ); msg.WriteFloat( progressBarValue ); msg.WriteBits( progressBarState, 2 ); // possible values [0..2] */ //rww - send ammo for current weapon - this is because our weapon routines rely on if we have ammo //in scripts and so on, and will be predicted wrong for other players (we get our own ammo in the playerstate) //rwwFIXME: if we have proper weapon switching when you run out of ammo will this actually be necessary? //i don't think it's all that costly, but still. if (weapon.IsValid()) { ammo_t ammoType = weapon->GetAmmoType(); if (ammoType > 0) { msg.WriteBits(inventory.ammo[ammoType], ASYNC_PLAYER_INV_AMMO_BITS); } else { msg.WriteBits(0, ASYNC_PLAYER_INV_AMMO_BITS); } ammoType = weapon->GetAltAmmoType(); if (ammoType > 0) { msg.WriteBits(inventory.ammo[ammoType], ASYNC_PLAYER_INV_AMMO_BITS); } else { msg.WriteBits(0, ASYNC_PLAYER_INV_AMMO_BITS); } } else { msg.WriteBits(0, ASYNC_PLAYER_INV_AMMO_BITS); msg.WriteBits(0, ASYNC_PLAYER_INV_AMMO_BITS); } //need to sync buttonMask since we're using it for some state-based things msg.WriteBits(buttonMask, 8); msg.WriteFloat(EyeHeight()); //HUMANHEAD PCF rww 05/04/06 - do not sync AI_VEHICLE, it is now based purely on the clientside //enter/exit of vehicles, with the vehControlling stack bool determining if we should be in a //vehicle on the client or not. //msg.WriteBits(AI_VEHICLE, 1); //rww - hand stuff //weaponHandState.WriteToSnapshot(msg); msg.WriteBits(hand.GetSpawnId(), 32); msg.WriteBits(handNext.GetSpawnId(), 32); //jsh - vehicle stuff msg.WriteBits( vehicleInterfaceLocal.GetVehicleSpawnId(), 32 ); msg.WriteBits( vehicleInterfaceLocal.GetHandSpawnId(), 32 ); //vehicleInterfaceLocal.GetWeaponHandState()->WriteToSnapshot(msg); //rww - lighter sync validation msg.WriteBits((lighterHandle != -1), 1); //rww - tractor msg.WriteBits(fl.isTractored, 1); spiritwalkSoundController.WriteToSnapshot(msg); //deathwalkSoundController.WriteToSnapshot(msg); wallwalkSoundController.WriteToSnapshot(msg); //HUMANHEAD rww - leechgun energy ammo //note - current weapon's ammo always sent now //ammo_t energyammo = hhWeaponFireController::GetAmmoType("ammo_energy"); //msg.WriteBits(inventory.ammo[energyammo], ASYNC_PLAYER_INV_AMMO_BITS); } /* =============== hhPlayer::ReadFromSnapshot =============== */ void hhPlayer::ReadFromSnapshot( const idBitMsgDelta &msg ) { int i, oldHealth, newIdealWeapon, weaponSpawnId; bool newHitToggle, stateHitch; bool vehControlling = !!msg.ReadBits(1); if ( snapshotSequence - lastSnapshotSequence > 1 ) { stateHitch = true; } else { stateHitch = false; } lastSnapshotSequence = snapshotSequence; oldHealth = health; physicsObj.ReadFromSnapshot( msg, vehControlling ); if (!vehControlling) { //not syncing these causes denormalization/nan issues, FIXME if (bBufferNextSnapAngles) { idAngles n; n[0] = msg.ReadFloat(); n[1] = msg.ReadFloat(); n[2] = msg.ReadFloat(); BufferLoggedViewAngles(n); untransformedViewAngles = n; bBufferNextSnapAngles = false; } else { untransformedViewAngles[0] = msg.ReadFloat(); untransformedViewAngles[1] = msg.ReadFloat(); untransformedViewAngles[2] = msg.ReadFloat(); } idCQuat q; q.x = msg.ReadFloat(); q.y = msg.ReadFloat(); q.z = msg.ReadFloat(); untransformedViewAxis = q.ToMat3(); //rww - read cameraInterpolator cameraInterpolator.ReadFromSnapshot(msg, this); //deltaViewAngles[0] = msg.ReadDeltaFloat( 0.0f ); //deltaViewAngles[1] = msg.ReadDeltaFloat( 0.0f ); //deltaViewAngles[2] = msg.ReadDeltaFloat( 0.0f ); } #if 0 //for debugging differences in snapshot else { msg.ReadFloat(); msg.ReadFloat(); msg.ReadFloat(); msg.ReadFloat(); msg.ReadFloat(); msg.ReadFloat(); msg.ReadFloat(); msg.ReadFloat(); msg.ReadFloat(); msg.ReadDeltaFloat(0.0f); msg.ReadDeltaFloat(0.0f); msg.ReadDeltaFloat(0.0f); msg.ReadDeltaFloat(0.0f); msg.ReadDeltaFloat(0.0f); msg.ReadDeltaFloat(0.0f); msg.ReadFloat(4, 4); msg.ReadDeltaFloat(0.0f); msg.ReadDeltaFloat(0.0f); msg.ReadDeltaFloat(0.0f); msg.ReadDeltaFloat(0.0f); msg.ReadDeltaFloat(0.0f); msg.ReadDeltaFloat(0.0f); msg.ReadDeltaFloat(0.0f); msg.ReadDeltaFloat(0.0f); msg.ReadDeltaFloat(0.0f); msg.ReadFloat(4, 4); msg.ReadFloat(); msg.ReadDeltaFloat(0.0f); msg.ReadDeltaFloat(0.0f); msg.ReadFloat(4, 4); msg.ReadDeltaFloat(0.0f); msg.ReadDeltaFloat(0.0f); msg.ReadDeltaFloat(0.0f); } #endif deltaViewAngles[0] = msg.ReadDeltaFloat( 0.0f ); deltaViewAngles[1] = msg.ReadDeltaFloat( 0.0f ); deltaViewAngles[2] = msg.ReadDeltaFloat( 0.0f ); health = msg.ReadShort(); lastDamageDef = gameLocal.ClientRemapDecl( DECL_ENTITYDEF, msg.ReadBits( gameLocal.entityDefBits ) ); lastDamageDir = msg.ReadDir( 9 ); lastDamageLocation = msg.ReadShort(); newIdealWeapon = msg.ReadBits( idMath::BitsForInteger( MAX_WEAPONS ) ); inventory.weapons = msg.ReadBits( MAX_WEAPONS ); weaponSpawnId = msg.ReadBits( 32 ); spectator = msg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ); newHitToggle = msg.ReadBits( 1 ) != 0; weaponGone = msg.ReadBits( 1 ) != 0; ReadBindFromSnapshot( msg ); //===================END OF ID DATA inventory.maxHealth = msg.ReadShort(); //rww - since maxhealth can go above 100 in mp now idQuat quat; /* if( spiritProxy.SetSpawnId( msg.ReadBits( 32 ) ) ) { if (spiritProxy.GetEntity() && spiritProxy.GetEntity()->IsType(hhSpiritProxy::Type)) { spiritProxy.GetEntity()->ActivateProxy( this, GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles ); } }*/ spiritProxy.SetSpawnId( msg.ReadBits( 32 ) ); //rww - more spiritwalk stuff lastWeaponSpirit = msg.ReadBits(32); bool spiritWalking = !!msg.ReadBits(1); if (spiritWalking != bSpiritWalk && (weapon.IsValid() || !spiritWalking)) { bSpiritWalk = spiritWalking; LighterOff(); //make sure lighter is off when switching spiritwalk if (bSpiritWalk) { if (gameLocal.localClientNum == entityNumber) { if (hud) { hud->HandleNamedEvent("SwitchToEthereal"); } gameLocal.SpiritWalkSoundMode( true ); } // Set the player's skin to a glowy effect SetSkinByName( spawnArgs.GetString("skin_Spiritwalk") ); SetShaderParm( SHADERPARM_TIMEOFFSET, 1.0f ); // TEMP: cjr - Required by the forcefield material. Can remove when a proper spiritwalk texture is made //put bow in proper state SelectEtherealWeapon(); } else { if (gameLocal.localClientNum == entityNumber) { if (hud) { hud->HandleNamedEvent("SwitchFromEthereal"); } gameLocal.SpiritWalkSoundMode( false ); } SetSkinByName( NULL ); } } //not needed anymore /* // Handle progress bar bool bBar = msg.ReadBits(1) != 0; float value = msg.ReadFloat(); int state = msg.ReadBits(2); CL_UpdateProgress(bBar, value, state); */ // if not a local client assume the client has all ammo types if ( entityNumber != gameLocal.localClientNum ) { for( i = 0; i < AMMO_NUMTYPES; i++ ) { inventory.ammo[ i ] = 999; } } //rww - send ammo for current weapon - this is because our weapon routines rely on if we have ammo //in scripts and so on, and will be predicted wrong for other players (we get our own ammo in the playerstate) //rwwFIXME: if we have proper weapon switching when you run out of ammo will this actually be necessary? //i don't think it's all that costly, but still. int primAmmo = msg.ReadBits(ASYNC_PLAYER_INV_AMMO_BITS); int altAmmo = msg.ReadBits(ASYNC_PLAYER_INV_AMMO_BITS); if (entityNumber != gameLocal.localClientNum) { //since we have our own ammo in the playerstate, don't want to stomp it if (weapon.IsValid()) { ammo_t ammoType = weapon->GetAmmoType(); if (ammoType > 0) { inventory.ammo[ammoType] = primAmmo; } ammoType = weapon->GetAltAmmoType(); if (ammoType > 0) { inventory.ammo[ammoType] = altAmmo; } } } //need to sync buttonMask since we're using it for some state-based things buttonMask = msg.ReadBits(8); SetEyeHeight(msg.ReadFloat()); //HUMANHEAD PCF rww 05/04/06 - do not sync AI_VEHICLE, it is now based purely on the clientside //enter/exit of vehicles, with the vehControlling stack bool determining if we should be in a //vehicle on the client or not. //AI_VEHICLE = !!msg.ReadBits(1); //rww - hand stuff //weaponHandState.ReadFromSnapshot(msg); hand.SetSpawnId(msg.ReadBits(32)); handNext.SetSpawnId(msg.ReadBits(32)); //HUMANHEAD PCF rww 05/04/06 - base this check on AI_VEHICLE and make sure the player is exited, //even if the vehicle is not in the snapshot at this point. if (!vehControlling && AI_VEHICLE) { //then exit on the client if (vehicleInterfaceLocal.ControllingVehicle()) { hhVehicle *veh = GetVehicleInterface()->GetVehicle(); if (veh && veh->GetPilot() == this) { veh->EjectPilot(); } } ExitVehicle(NULL); //be extra safe in case vehicle is no longer around } //jsh - vehicle stuff int vehSpawnId = msg.ReadBits( 32 ); //HUMANHEAD PCF rww 05/04/06 - base this check ON AI_VEHICLE, in case snapshot where controlling and //where the vehicle is set do not exactly coincide. vehicleInterfaceLocal.SetVehicleSpawnId( vehSpawnId ); if( !AI_VEHICLE ) { if (vehControlling) { hhVehicle *veh = vehicleInterfaceLocal.GetVehicle(); if (veh && veh->IsType(hhVehicle::Type)) { //GetVehicleInterface()->TakeControl( vehicleInterfaceLocal.GetVehicle(), this ); EnterVehicle( vehicleInterfaceLocal.GetVehicle() ); } } } /* if( vehicleInterfaceLocal.SetHandSpawnId( msg.ReadBits( 32 ) ) ) { vehicleInterfaceLocal.GetHandEntity()->AttachHand( this, true ); } */ vehicleInterfaceLocal.SetHandSpawnId( msg.ReadBits( 32 ) ); //vehicleInterfaceLocal.GetWeaponHandState()->ReadFromSnapshot(msg); //rww - lighter sync validation bool newLighterOn = !!msg.ReadBits(1); if (newLighterOn != IsLighterOn()) { ToggleLighter(); } //rww - tractor fl.isTractored = !!msg.ReadBits(1); spiritwalkSoundController.ReadFromSnapshot(msg); //deathwalkSoundController.ReadFromSnapshot(msg); wallwalkSoundController.ReadFromSnapshot(msg); //=========================================== start id code if ( weapon.SetSpawnId( weaponSpawnId ) ) { //HUMANHEAD rww - i am getting crashes here and the stack is all messed up, //claiming that SetOwner is off into null, when supposedly the entity is not. //rearranging this code so it's easier to see what's going on in a crash. hhWeapon *weapEnt = weapon.GetEntity(); if ( weapEnt ) { // maintain ownership locally weapEnt->SetOwner( this ); } currentWeapon = -1; } //HUMANHEAD rww - but we do want ammo count for the leechgun on other players for prediction //note - current weapon's ammo always sent now //ammo_t energyammo = hhWeaponFireController::GetAmmoType("ammo_energy"); //inventory.ammo[energyammo] = msg.ReadBits(ASYNC_PLAYER_INV_AMMO_BITS); if ( oldHealth > 0 && health <= 0 ) { if ( stateHitch ) { // so we just hide and don't show a death skin UpdateDeathSkin( true ); } // die AI_DEAD = true; SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 4 ); SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 4 ); SetWaitState( "" ); animator.ClearAllJoints(); //HUMANHEAD rww - don't want this /* if ( entityNumber == gameLocal.localClientNum ) { playerView.Fade( colorBlack, 12000 ); } */ //HUMANHEAD END fl.clientEvents = true; //client event hackery for non-critical cosmetic cleanups PostEventMS(&EV_RespawnCleanup, 32); fl.clientEvents = false; StartRagdoll(); physicsObj.SetMovementType( PM_DEAD ); if ( !stateHitch ) { //HUMANHEAD PCF rww 09/15/06 - female mp sounds if (IsFemale()) { StartSound( "snd_death_female", SND_CHANNEL_VOICE, 0, false, NULL ); } else { //HUMANHEAD END StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); } } if ( weapon.GetEntity() ) { weapon.GetEntity()->OwnerDied(); } //HUMANHEAD rww GetPhysics()->SetContents(0); Hide(); //so camera can operate on client. minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME; maxRespawnTime = minRespawnTime + 10000; //HUMANHEAD END } else if ( oldHealth <= 0 && health > 0 ) { // respawn Init(); StopRagdoll(); SetPhysics( &physicsObj ); physicsObj.EnableClip(); SetCombatContents( true ); //HUMANHEAD rww if (!spectating) { Show(); } //HUMANHEAD END } else if ( health < oldHealth && health > 0 ) { if ( stateHitch ) { lastDmgTime = gameLocal.time; } else { // damage feedback const idDeclEntityDef *def = static_cast( declManager->DeclByIndex( DECL_ENTITYDEF, lastDamageDef, false ) ); if ( def ) { playerView.DamageImpulse( lastDamageDir * viewAxis.Transpose(), &def->dict ); AI_PAIN = Pain( NULL, NULL, oldHealth - health, lastDamageDir, lastDamageLocation ); lastDmgTime = gameLocal.time; } else { common->Warning( "NET: no damage def for damage feedback '%s'\n", lastDamageDef ); } } } // If the player is alive, restore proper physics object if ( health > 0 && IsActiveAF() ) { StopRagdoll(); SetPhysics( &physicsObj ); physicsObj.EnableClip(); SetCombatContents( true ); } if ( idealWeapon != newIdealWeapon ) { if ( stateHitch ) { weaponCatchup = true; } idealWeapon = newIdealWeapon; UpdateHudWeapon(); } if ( lastHitToggle != newHitToggle ) { SetLastHitTime( gameLocal.realClientTime ); } //=========================================== end id code if ( msg.HasChanged() ) { UpdateVisuals(); } } /* ================ hhPlayer::ServerReceiveEvent ================ */ bool hhPlayer::ServerReceiveEvent( int event, int time, const idBitMsg &msg ) { if ( idEntity::ServerReceiveEvent( event, time, msg ) ) { return true; } // client->server events switch( event ) { case EVENT_IMPULSE: { int impulse = msg.ReadBits( 6 ); PerformImpulse(impulse); if (vehicleInterfaceLocal.ControllingVehicle()) { hhVehicle *veh = vehicleInterfaceLocal.GetVehicle(); if (veh) { veh->DoPlayerImpulse(impulse); } } return true; } default: { return false; } } } /* ================ hhPlayer::WritePlayerStateToSnapshot ================ */ void hhPlayer::WritePlayerStateToSnapshot( idBitMsgDelta &msg ) const { idPlayer::WritePlayerStateToSnapshot(msg); //rww - extra playerView stuff playerView.WriteToSnapshot(msg); //rww - scope view msg.WriteBits(bScopeView, 1); //rww - zoom fov msg.WriteFloat(zoomFov.GetDuration()); msg.WriteFloat(zoomFov.GetEndValue()); msg.WriteFloat(zoomFov.GetStartTime()); msg.WriteFloat(zoomFov.GetStartValue()); //rww - view angle sensitivity msg.WriteFloat(GetViewAnglesSensitivity()); } /* ================ hhPlayer::ReadPlayerStateFromSnapshot ================ */ void hhPlayer::ReadPlayerStateFromSnapshot( const idBitMsgDelta &msg ) { idPlayer::ReadPlayerStateFromSnapshot(msg); //rww - extra playerView stuff playerView.ReadFromSnapshot(msg); //rww - scope and fov handling bool canOverrideView = true; if (weapon.IsValid() && weapon->IsType(hhWeaponZoomable::Type)) { //if we have a zoomed weapon, don't override with snapshot while the prediction fudge timer is on hhWeaponZoomable *weap = static_cast(weapon.GetEntity()); if (weap->clientZoomTime >= gameLocal.time) { canOverrideView = false; } } bool scopeView = !!msg.ReadBits(1); float zfDur = msg.ReadFloat(); float zfEnd = msg.ReadFloat(); float zfStT = msg.ReadFloat(); float zfStV = msg.ReadFloat(); renderSystem->SetShuttleView( InVehicle() ); if (canOverrideView) { //if we are currently allowed to override with snapshot values, do so. bScopeView = scopeView; if (bScopeView != renderSystem->IsScopeView()) { renderSystem->SetScopeView(bScopeView); } zoomFov.SetDuration(zfDur); zoomFov.SetEndValue(zfEnd); zoomFov.SetStartTime(zfStT); zoomFov.SetStartValue(zfStV); } //rww - view angle sensitivity SetViewAnglesSensitivity(msg.ReadFloat()); } /* ================ hhPlayer::ClientPredictionThink ================ */ void hhPlayer::ClientPredictionThink( void ) { if (gameLocal.localClientNum != entityNumber && forcePredictionButtons) { //used by some weapons which rely on prediction-based projectiles, to ensure we don't miss a launch gameLocal.usercmds[ entityNumber ].buttons |= forcePredictionButtons; if (gameLocal.isNewFrame) { forcePredictionButtons = 0; } } idPlayer::ClientPredictionThink(); if (InVehicle()) { UpdateHud( GetVehicleInterfaceLocal()->GetHUD() ); } else { UpdateHud( hud ); } } /* ================ hhPlayer::GetPhysicsToVisualTransform ================ */ bool hhPlayer::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { if ( af.IsActive() ) { af.GetPhysicsToVisualTransform( origin, axis ); return true; } //rww - adopted id's smoothing code if ( gameLocal.isClient && !bindMaster && gameLocal.framenum >= smoothedFrame && ( entityNumber != gameLocal.localClientNum || selfSmooth ) ) { if (!smoothedOriginUpdated) { idVec3 renderOrigin = TransformToPlayerSpace(modelOffset); idVec3 originalOrigin = renderOrigin; //id's cheesy smooth code (changed to vec3 because there is no "down" in prey) idVec3 originDiff = renderOrigin - smoothedOrigin; if (smoothedFrame == 0) { //for teleporting originDiff = vec3_origin; } smoothedOrigin = renderOrigin; if ( originDiff.LengthSqr() < Square( 100.0f ) ) { // smoothen by pushing back to the previous position if ( selfSmooth ) { assert( entityNumber == gameLocal.localClientNum ); renderOrigin -= net_clientSelfSmoothing.GetFloat() * originDiff; } else { renderOrigin -= gameLocal.clientSmoothing * originDiff; } //get rid of the smoothing on the "vertical" axis idVec3 a(renderOrigin.x*viewAxis[2].x, renderOrigin.y*viewAxis[2].y, renderOrigin.z*viewAxis[2].z); idVec3 b(originalOrigin.x*viewAxis[2].x, originalOrigin.y*viewAxis[2].y, originalOrigin.z*viewAxis[2].z); renderOrigin -= (a-b); } smoothedFrame = gameLocal.framenum; smoothedOriginUpdated = true; } axis = viewAxis; origin = ( smoothedOrigin - GetPhysics()->GetOrigin() ) * GetEyeAxis().Transpose(); return true; } if (bClampYaw) { //rww - lock model angles idVec3 masterOrigin; idMat3 masterAxis; if (GetMasterPosition(masterOrigin, masterAxis)) { axis = masterAxis; origin = modelOffset; return true; } } axis = viewAxis; origin = modelOffset; return true; } bool hhPlayer::UpdateAnimationControllers( void ) { bool retValue = idPlayer::UpdateAnimationControllers(); hhAnimator *theAnimator; if (head.IsValid()) { theAnimator = head->GetAnimator(); } else { theAnimator = GetAnimator(); } JawFlap(theAnimator); return retValue; } /* =============== hhPlayer::Event_PlayWeaponAnim =============== */ void hhPlayer::Event_PlayWeaponAnim( const char* animName, int numTries ) { //AOB: I would like a better solution then constantly banging until weapon is valid. if( (!weapon.IsValid() || GetCurrentWeapon() != idealWeapon) && numTries > 0 ) { CancelEvents( &EV_PlayWeaponAnim ); PostEventMS( &EV_PlayWeaponAnim, 50, animName, numTries - 1 ); return; } CancelEvents( &EV_PlayWeaponAnim ); if( weapon.IsValid() ) { weapon->ProcessEvent( &EV_PlayAnimWhenReady, animName ); } } void hhPlayer::DialogStart(bool bDisallowPlayerDeath, bool bVoiceDucking, bool bLowerWeapon) { bDialogDamageMode = bDisallowPlayerDeath; bDialogWeaponMode = bLowerWeapon; gameLocal.DialogSoundMode(bVoiceDucking); if (bDialogWeaponMode) { // Lock weapon preCinematicWeaponFlags = weaponFlags; preCinematicWeapon = GetIdealWeapon(); LockWeapon(-1); } } void hhPlayer::DialogStop() { bDialogDamageMode = false; gameLocal.DialogSoundMode(false); if (bDialogWeaponMode) { weaponFlags = preCinematicWeaponFlags; SelectWeapon(preCinematicWeapon, true); bDialogWeaponMode = false; } } /* =============== hhPlayer::Event_DialogStart HUMANHEAD pdm =============== */ void hhPlayer::Event_DialogStart( int bDisallowPlayerDeath, int bVoiceDucking, int bLowerWeapon ) { DialogStart(bDisallowPlayerDeath != 0, bVoiceDucking != 0, bLowerWeapon != 0); } /* =============== hhPlayer::Event_DialogStop HUMANHEAD pdm =============== */ void hhPlayer::Event_DialogStop() { DialogStop(); } /* =============== hhPlayer::Event_LotaTunnelMode HUMANHEAD pdm =============== */ void hhPlayer::Event_LotaTunnelMode(bool on) { bLotaTunnelMode = on; } /* =============== hhPlayer::Event_Cinematic HUMANHEAD pdm =============== */ void hhPlayer::Event_Cinematic( int on, int lockView ) { bool cinematic = (on != 0); InCinematic( cinematic ); playerView.SetLetterBox(cinematic); if (cinematic) { if ( IsSpiritOrDeathwalking() ) { StopSpiritWalk(); } if ( IsPossessed() ) { Unpossess(); } // Lock weapon preCinematicWeaponFlags = weaponFlags; preCinematicWeapon = GetCurrentWeapon(); LockWeapon(-1); // disable damage fl.takedamage = false; this->lockView = (lockView != 0); } else { fl.takedamage = true; this->lockView = false; //UnlockWeapon(preCinematicWeapon); weaponFlags = preCinematicWeaponFlags; //SetCurrentWeapon(preCinematicWeapon); SelectWeapon(preCinematicWeapon, true); } } //============================================================================= // // hhPlayer::Event_PrepareToResurrect // // Flashes the screen / fades the FOV //============================================================================= void hhPlayer::Event_PrepareToResurrect() { deathWalkFlash = 0; possessionFOV = 90.0f; PostEventSec( &EV_ResurrectScreenFade, 0 ); } //============================================================================= // // hhPlayer::Event_ResurrectScreenFade // // The actual code to incrementally flash the screen //============================================================================= void hhPlayer::Event_ResurrectScreenFade() { deathWalkFlash += spawnArgs.GetFloat( "resurrectFlashChange", "0.04" ); if ( deathWalkFlash >= 1.0f ) { deathWalkFlash = 1.0f; PostEventSec( &EV_Resurrect, 0.0f ); // Actually send player back to the physical realm } else { PostEventSec( &EV_ResurrectScreenFade, 0.02f ); } possessionFOV += spawnArgs.GetFloat( "resurrectFOVChange", "1.0" ); playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, deathWalkFlash ) ); } //============================================================================= // // hhPlayer::Event_Resurrect // //============================================================================= void hhPlayer::Event_Resurrect() { Resurrect(); } //============================================================================= // // hhPlayer::Event_Event_ShouldRemainAlignedToAxial // //============================================================================= void hhPlayer::Event_ShouldRemainAlignedToAxial( bool remainAligned ) { ShouldRemainAlignedToAxial( remainAligned ); } //============================================================================= // // hhPlayer::Event_OrientToGravity // //============================================================================= void hhPlayer::Event_OrientToGravity( bool orient ) { OrientToGravity( orient ); } //============================================================================= // // hhPlayer::Event_ResetGravity // HUMANHEAD: pdm: Posted when entity is leaving a gravity zone //============================================================================= void hhPlayer::Event_ResetGravity() { if( IsWallWalking() ) { return; // Don't reset if wallwalking } if (spectating) { //HUMANHEAD rww - don't reset if spectating return; } idPlayer::Event_ResetGravity(); OrientToGravity( true ); // let it reset orientation } //============================================================================= // // hhPlayer::Event_SetOverlayMaterial // //============================================================================= void hhPlayer::Event_SetOverlayMaterial( const char *mtrName, const int requiresScratch ) { if ( mtrName && mtrName[0] ) { playerView.SetViewOverlayMaterial( declManager->FindMaterial(mtrName), requiresScratch ); } else { playerView.SetViewOverlayMaterial( NULL ); } } //============================================================================= // // hhPlayer::Event_SetOverlayTime // //============================================================================= void hhPlayer::Event_SetOverlayTime( const float newTime, const int requiresScratch ) { playerView.SetViewOverlayTime( newTime, requiresScratch ); } //============================================================================= // // hhPlayer::Event_SetOverlayColor // //============================================================================= void hhPlayer::Event_SetOverlayColor( const float r, const float g, const float b, const float a ) { idVec4 color; color.x = r; color.y = g; color.z = b; color.w = a; playerView.SetViewOverlayColor( color ); } //============================================================================= // // hhPlayer::Event_DDAHeartBeat // //============================================================================= void hhPlayer::Event_DDAHeartBeat() { gameLocal.GetDDA()->DDA_Heartbeat( this ); PostEventMS( &EV_DDAHeartbeat, ddaHeartbeatMS ); } //================ //hhPlayer::Save //================ void hhPlayer::Save( idSaveGame *savefile ) const { // Public vars savefile->Write(weaponInfo, sizeof(weaponInfo_t)*15); savefile->Write(altWeaponInfo, sizeof(weaponInfo_t)*15); savefile->WriteFloat( lighterTemperature ); savefile->WriteRenderLight( lighter ); //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles //savefile->WriteInt( lighterHandle ); spiritProxy.Save( savefile ); savefile->WriteInt( lastWeaponSpirit ); talon.Save( savefile ); savefile->WriteInt( nextTalonAttackCommentTime ); savefile->WriteBool( bTalonAttackComment ); savefile->WriteBool( bSpiritWalk ); savefile->WriteBool( bDeathWalk ); savefile->WriteBool( bReallyDead ); guiWantsControls.Save( savefile ); savefile->WriteFloat( deathWalkFlash ); savefile->WriteInt( deathWalkTime ); savefile->WriteInt( deathWalkPower ); savefile->WriteBool( bInDeathwalkTransition ); savefile->WriteBool( bInCinematic ); savefile->WriteBool( bPlayingLowHealthSound ); savefile->WriteBool( bPossessed ); savefile->WriteFloat( possessionTimer ); savefile->WriteFloat( possessionFOV ); savefile->WriteInt( preCinematicWeapon ); savefile->WriteInt( preCinematicWeaponFlags ); savefile->WriteInt( lastDamagedTime ); savefile->WriteStaticObject( weaponHandState ); hand.Save( savefile ); handNext.Save( savefile ); possessedTommy.Save( savefile ); savefile->WriteStaticObject( vehicleInterfaceLocal ); savefile->WriteObject( vehicleInterfaceLocal.GetVehicle() ); deathLookAtEntity.Save( savefile ); savefile->WriteString( deathLookAtBone ); savefile->WriteString( deathCameraBone ); savefile->WriteStaticObject( spiritwalkSoundController ); savefile->WriteStaticObject( deathwalkSoundController ); savefile->WriteStaticObject( wallwalkSoundController ); savefile->WriteBool( bShowProgressBar ); savefile->WriteFloat( progressBarValue ); savefile->WriteFloat( progressBarGuiValue.GetStartTime() ); // idInterpolate savefile->WriteFloat( progressBarGuiValue.GetDuration() ); savefile->WriteFloat( progressBarGuiValue.GetStartValue() ); savefile->WriteFloat( progressBarGuiValue.GetEndValue() ); savefile->WriteInt( progressBarState ); savefile->WriteBool( bClampYaw ); savefile->WriteFloat( maxRelativeYaw ); savefile->WriteFloat( maxRelativePitch ); savefile->WriteFloat( bob ); savefile->WriteInt( lastAppliedBobCycle ); savefile->WriteInt( prevStepUpTime ); savefile->WriteVec3( prevStepUpOrigin ); savefile->WriteFloat( crashlandSpeed_fatal ); savefile->WriteFloat( crashlandSpeed_soft ); savefile->WriteFloat( crashlandSpeed_jump ); // Protected vars savefile->WriteUserInterface( guiOverlay, false ); thirdPersonCameraClipBounds.Save( savefile ); savefile->WriteFloat( viewAnglesSensitivity ); savefile->WriteInt( lastResurrectTime ); savefile->WriteInt( spiritDrainHeartbeatMS ); savefile->WriteInt( ddaHeartbeatMS ); savefile->WriteInt( spiritWalkToggleTime ); savefile->WriteBool( bDeathWalkStage2 ); savefile->WriteBool( bFrozen ); savefile->WriteAngles( untransformedViewAngles ); savefile->WriteMat3( untransformedViewAxis ); savefile->WriteInt( nextSpiritTime ); savefile->WriteFloat( cinematicFOV.GetStartTime() ); savefile->WriteFloat( cinematicFOV.GetAcceleration() ); savefile->WriteFloat( cinematicFOV.GetDeceleration() ); savefile->WriteFloat( cinematicFOV.GetDuration() ); savefile->WriteFloat( cinematicFOV.GetStartValue() ); savefile->WriteFloat( cinematicFOV.GetEndValue() ); savefile->WriteBool( bAllowSpirit ); savefile->WriteInt( airAttackerTime ); savefile->WriteBool( bScopeView ); savefile->WriteInt( ddaNumEnemies ); savefile->WriteFloat( ddaProbabilityAccum ); savefile->WriteInt( weaponFlags ); savefile->WriteBool( lockView ); savefile->WriteBool( bCollidingWithPortal ); savefile->WriteBool( bLotaTunnelMode ); savefile->WriteInt( forcePredictionButtons ); for (int ix=0; ixWriteInt( lastAttackers[ix].time ); savefile->WriteBool( lastAttackers[ix].displayed ); } if ( InVehicle() ) { savefile->WriteMat3( vehicleInterfaceLocal.GetVehicle()->GetPhysics()->GetAxis() ); savefile->WriteVec3( vehicleInterfaceLocal.GetVehicle()->GetAxis()[0] ); } //HUMANHEAD PCF mdl 04/28/06 - Moved camera interpolater down here to fix jump off wallwalk view angle problem cameraInterpolator.Save( savefile ); //HUMANHEAD PCF mdl 05/04/06 - Save whether the light handle is active savefile->WriteBool( IsLighterOn() ); } //================ //hhPlayer::Restore //================ void hhPlayer::Restore( idRestoreGame *savefile ) { // Public vars savefile->Read(weaponInfo, sizeof(weaponInfo_t)*15); savefile->Read(altWeaponInfo, sizeof(weaponInfo_t)*15); savefile->ReadFloat( lighterTemperature ); savefile->ReadRenderLight( lighter ); //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles //savefile->ReadInt( lighterHandle ); spiritProxy.Restore( savefile ); savefile->ReadInt( lastWeaponSpirit ); talon.Restore( savefile ); savefile->ReadInt( nextTalonAttackCommentTime ); savefile->ReadBool( bTalonAttackComment ); savefile->ReadBool( bSpiritWalk ); savefile->ReadBool( bDeathWalk ); savefile->ReadBool( bReallyDead ); guiWantsControls.Restore( savefile ); savefile->ReadFloat( deathWalkFlash ); savefile->ReadInt( deathWalkTime ); savefile->ReadInt( deathWalkPower ); savefile->ReadBool( bInDeathwalkTransition ); savefile->ReadBool( bInCinematic ); savefile->ReadBool( bPlayingLowHealthSound ); savefile->ReadBool( bPossessed ); savefile->ReadFloat( possessionTimer ); savefile->ReadFloat( possessionFOV ); savefile->ReadInt( preCinematicWeapon ); savefile->ReadInt( preCinematicWeaponFlags ); savefile->ReadInt( lastDamagedTime ); savefile->ReadStaticObject( weaponHandState ); hand.Restore( savefile ); handNext.Restore( savefile ); possessedTommy.Restore( savefile ); savefile->ReadStaticObject( vehicleInterfaceLocal ); SetVehicleInterface( &vehicleInterfaceLocal ); hhVehicle *vehicle; savefile->ReadObject( reinterpret_cast ( vehicle ) ); if( vehicle ) { vehicle->RestorePilot( &vehicleInterfaceLocal ); } deathLookAtEntity.Restore( savefile ); savefile->ReadString( deathLookAtBone ); savefile->ReadString( deathCameraBone ); savefile->ReadStaticObject( spiritwalkSoundController ); savefile->ReadStaticObject( deathwalkSoundController ); savefile->ReadStaticObject( wallwalkSoundController ); savefile->ReadBool( bShowProgressBar ); savefile->ReadFloat( progressBarValue ); float set; savefile->ReadFloat( set ); // idInterpolate progressBarGuiValue.SetStartTime( set ); savefile->ReadFloat( set ); progressBarGuiValue.SetDuration( set ); savefile->ReadFloat( set ); progressBarGuiValue.SetStartValue(set); savefile->ReadFloat( set ); progressBarGuiValue.SetEndValue( set ); savefile->ReadInt( progressBarState ); savefile->ReadBool( bClampYaw ); savefile->ReadFloat( maxRelativeYaw ); savefile->ReadFloat( maxRelativePitch ); savefile->ReadFloat( bob ); savefile->ReadInt( lastAppliedBobCycle ); savefile->ReadInt( prevStepUpTime ); savefile->ReadVec3( prevStepUpOrigin ); savefile->ReadFloat( crashlandSpeed_fatal ); savefile->ReadFloat( crashlandSpeed_soft ); savefile->ReadFloat( crashlandSpeed_jump ); // Protected vars savefile->ReadUserInterface( guiOverlay ); thirdPersonCameraClipBounds.Restore( savefile ); savefile->ReadFloat( viewAnglesSensitivity ); savefile->ReadInt( lastResurrectTime ); savefile->ReadInt( spiritDrainHeartbeatMS ); savefile->ReadInt( ddaHeartbeatMS ); savefile->ReadInt( spiritWalkToggleTime ); savefile->ReadBool( bDeathWalkStage2 ); savefile->ReadBool( bFrozen ); savefile->ReadAngles( untransformedViewAngles ); savefile->ReadMat3( untransformedViewAxis ); savefile->ReadInt( nextSpiritTime ); float startTime, accelTime, decelTime, duration, startPos, endPos; savefile->ReadFloat( startTime ); savefile->ReadFloat( accelTime ); savefile->ReadFloat( decelTime ); savefile->ReadFloat( duration ); savefile->ReadFloat( startPos ); savefile->ReadFloat( endPos ); cinematicFOV.Init( startTime, accelTime, decelTime, duration, startPos, endPos ); savefile->ReadBool( bAllowSpirit ); savefile->ReadInt( airAttackerTime ); savefile->ReadBool( bScopeView ); savefile->ReadInt( ddaNumEnemies ); savefile->ReadFloat( ddaProbabilityAccum ); savefile->ReadInt( weaponFlags ); savefile->ReadBool( lockView ); savefile->ReadBool( bCollidingWithPortal ); savefile->ReadBool( bLotaTunnelMode ); savefile->ReadInt( forcePredictionButtons ); for (int ix=0; ixReadInt( lastAttackers[ix].time ); savefile->ReadBool( lastAttackers[ix].displayed ); } // We don't want to preserve these memset( &oldCmdAngles, 0, sizeof( oldCmdAngles ) ); kickSpring = spawnArgs.GetFloat( "kickSpring" ); //HUMANHEAD bjk kickDamping = spawnArgs.GetFloat( "kickDamping" ); //HUMANHEAD bjk if( InVehicle() ) { hhVehicle *vehicle = vehicleInterfaceLocal.GetVehicle(); HH_ASSERT( vehicle ); idMat3 axis; idVec3 axis0; // This prevents a crashbug if the vehicle's physics doesn't exist yet. savefile->ReadMat3( axis ); // vehicle->GetPhysics()->GetAxis() savefile->ReadVec3( axis0 ); // vehicle->GetAxis()[0] // Keep orientation correct in vehicles RestoreOrientation( GetOrigin(), axis, axis0, axis0.ToAngles() ); //HUMANHEAD PCF mdl 05/02/06 - Added this to re-enable shuttle view if (renderSystem) { // Set shuttle view renderSystem->SetShuttleView( true ); } } else { // Keep orientation correct for walkwalk and gravity rooms RestoreOrientation( GetOrigin(), physicsObj.GetAxis(), viewAngles.ToMat3()[0], untransformedViewAngles ); } //HUMANHEAD PCF mdl 04/28/06 - Moved camera interpolater down here to fix jump off wallwalk view angle problem cameraInterpolator.Restore( savefile ); //HUMANHEAD PCF mdl 05/04/06 - Restore lighter bool bLighter; savefile->ReadBool( bLighter ); if ( bLighter ) { lighterHandle = gameRenderWorld->AddLightDef( &lighter ); } } int hhPlayer::GetSpiritPower() { ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName("ammo_spiritpower"); return inventory.ammo[ammo_spiritpower]; } void hhPlayer::SetSpiritPower(int amount) { ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName("ammo_spiritpower"); inventory.ammo[ammo_spiritpower] = amount; if (inventory.ammo[ammo_spiritpower] > inventory.maxSpirit) { inventory.ammo[ammo_spiritpower] = inventory.maxSpirit; } } void hhPlayer::Event_StartHUDTranslation() { vehicleInterfaceLocal.StartHUDTranslation(); } void hhPlayer::Freeze(float unfreezeDelay) { bFrozen = true; StopFiring(); if ( weapon.IsValid() ) { weapon->PutAway(); } if ( unfreezeDelay > 0.0f ) { PostEventSec( &EV_Unfreeze, unfreezeDelay ); } } void hhPlayer::Unfreeze(void) { bFrozen = false; if ( weapon.IsValid() ) { weapon->PostEventMS( &EV_Show, 1000 ); weapon->PostEventMS( &EV_Weapon_WeaponRising, 1000 ); } } void hhPlayer::Event_Unfreeze() { Unfreeze(); if ( bindMaster && bindMaster->RespondsTo( EV_BindUnfroze ) ) { GetBindMaster()->PostEventMS( &EV_BindUnfroze, 0, this ); } } void hhPlayer::Event_GetSpiritPower() { //rww idThread::ReturnFloat((float)GetSpiritPower()); } void hhPlayer::Event_SetSpiritPower(const float s) { //rww SetSpiritPower((int)s); } void hhPlayer::Event_OnGround() { // bg idThread::ReturnFloat( physicsObj.HasGroundContacts() ); } void hhPlayer::SetupWeaponFlags( void ) { const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); const idDecl *mapDecl = declManager->FindType(DECL_MAPDEF, mapName, false ); if ( !mapDecl ) { weaponFlags = -1; // No map decls? Allow all weapons return; } const idDeclEntityDef *mapInfo = static_cast(mapDecl); const char *weaponList = mapInfo->dict.GetString( "disableWeapons" ); weaponFlags = 0; if ( weaponList && weaponList[0] ) { if ( !idStr::Icmp( weaponList, "all" ) ) { // No weapons are enabled, so just return return; } if ( idStr::FindText( weaponList, "wrench", false ) == -1 ) { weaponFlags |= HH_WEAPON_WRENCH; } if ( idStr::FindText( weaponList, "rifle", false ) == -1 ) { weaponFlags |= HH_WEAPON_RIFLE; } if ( idStr::FindText( weaponList, "crawler", false ) == -1 ) { weaponFlags |= HH_WEAPON_CRAWLER; } if ( idStr::FindText( weaponList, "autocannon", false ) == -1 ) { weaponFlags |= HH_WEAPON_AUTOCANNON; } if ( idStr::FindText( weaponList, "hiderweapon", false ) == -1 ) { weaponFlags |= HH_WEAPON_HIDERWEAPON; } if ( idStr::FindText( weaponList, "rocketlauncher", false ) == -1 ) { weaponFlags |= HH_WEAPON_ROCKETLAUNCHER; } if ( idStr::FindText( weaponList, "soulstripper", false ) == -1 ){ weaponFlags |= HH_WEAPON_SOULSTRIPPER; } } if ( weaponFlags == 0 ) { // Allow all weapons by default weaponFlags = -1; } } void hhPlayer::LockWeapon( int weaponNum ) { // Special case, -1 locks all weapons AND the lighter if ( weaponNum == -1 ) { if (IsLighterOn()) { LighterOff(); } if (weapon.IsValid()) { weapon->PutAway(); } weaponFlags = 0; idealWeapon = 0; currentWeapon = 0; return; } if ( weaponNum <= 0 || weaponNum >= MAX_WEAPONS ) { gameLocal.Error( "Attempted to unlock unknown weapon '%d'", weaponNum ); } int flag = ( 1 << ( weaponNum - 1 ) ); if ( weaponFlags & flag ) { // Only lock if not already locked weaponFlags &= ~flag; if ( idealWeapon == weaponNum ) { if (weapon.IsValid()) { weapon->PutAway(); } NextWeapon(); } } } void hhPlayer::UnlockWeapon( int weaponNum ) { // Special case, -1 unlocks all weapons if ( weaponNum == -1 ) { weaponFlags = -1; if ( idealWeapon == 0 ) { idealWeapon = 1; // Default to the wrench } return; } if ( weaponNum <= 0 || weaponNum >= MAX_WEAPONS ) { gameLocal.Error( "Attempted to unlock unknown weapon '%d'", weaponNum ); } int flag = ( 1 << ( weaponNum - 1 ) ); if ( weaponFlags & flag ) { // Already unlocked return; } weaponFlags |= flag; idealWeapon = weaponNum; } bool hhPlayer::IsLocked(int weaponNum) { //HUMANHEAD PCF mdl 05/05/06 - Changed to < 1 to catch weaponNum = -1 if (weaponNum < 1) { return true; // CJR: if the player has no weapons, then consider all weapons locked } return !(weaponFlags & (1 << (weaponNum-1))); } void hhPlayer::Event_LockWeapon( int weaponNum ) { LockWeapon( weaponNum ); } void hhPlayer::Event_UnlockWeapon( int weaponNum ) { UnlockWeapon( weaponNum ); } //HUMANHEAD rdr void hhPlayer::Event_SetPrivateCameraView( idEntity *camView, int noHide ) { if ( camView && camView->IsType( idCamera::Type ) ) { idPlayer::SetPrivateCameraView( static_cast( camView ), noHide != 0 ); } else { idPlayer::SetPrivateCameraView( NULL ); } } //HUMANHEAD rdr void hhPlayer::Event_SetCinematicFOV( float fieldOfView, float accelTime, float decelTime, float duration ) { if ( duration > 0.f ) { cinematicFOV.Init( gameLocal.time, SEC2MS( accelTime ), SEC2MS( decelTime ), SEC2MS( duration ), cinematicFOV.GetCurrentValue( gameLocal.GetTime() ), fieldOfView ); } else { cinematicFOV.Init( gameLocal.time, 0.f, 0.f, 0.f, cinematicFOV.GetEndValue(), fieldOfView ); } } //HUMANHEAD rww void hhPlayer::Event_StopSpiritWalk() { StopSpiritWalk(); } void hhPlayer::Event_DamagePlayer(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, char *damageDefName, float damageScale, int location) { Damage(inflictor, attacker, dir, damageDefName, damageScale, location); } //HUMANHEAD END void hhPlayer::Event_GetSpiritProxy() { if ( spiritProxy.IsValid() ) { idThread::ReturnEntity( spiritProxy.GetEntity() ); } else { idThread::ReturnEntity( NULL ); } } void hhPlayer::Event_IsSpiritWalking() { idThread::ReturnInt( IsSpiritWalking() ? 1 : 0 ); } void hhPlayer::Event_IsDeathWalking() { idThread::ReturnInt( IsDeathWalking() ? 1 : 0 ); } void hhPlayer::Event_AllowLighter( bool allow ) { inventory.requirements.bCanUseLighter = allow; if ( !allow && IsLighterOn() ) { LighterOff(); } } //============================================================================= // // hhPlayer::Event_GetDDAValue // // Returns the current DDA value from zero to one //============================================================================= void hhPlayer::Event_GetDDAValue() { idThread::ReturnFloat( gameLocal.GetDDAValue() ); } //HUMANHEAD rww hhArtificialPlayer::hhArtificialPlayer(void) { memset(&lastAICmd, 0, sizeof(lastAICmd)); testCrouchActive = false; testCrouchTime = 0; } void hhArtificialPlayer::Spawn( void ) { idDict apUI; gameLocal.GetAPUserInfo(apUI, entityNumber); gameLocal.SetUserInfo(entityNumber, apUI, gameLocal.isClient, false); SetPlayerModel(false); //force an update on the player model } void hhArtificialPlayer::Think( void ) { PROFILE_START("hhArtificialPlayer::Think", PROFMASK_NORMAL); bool iFeelHoppy = false; usercmd_t *nextCmd = &gameLocal.usercmds[ entityNumber ]; idAngles idealAngles = GetViewAngles(); nextCmd->gameFrame = gameLocal.framenum; nextCmd->gameTime = gameLocal.time; nextCmd->forwardmove = idMath::ClampChar(127); //this is not meant to resemble proper ai logic, it's just for testing. idEntity *closestEnt = NULL; float closestDist = idMath::INFINITY; int numSourceAreas, sourceAreas[ idEntity::MAX_PVS_AREAS ]; numSourceAreas = gameRenderWorld->BoundsInAreas( GetPlayerPhysics()->GetAbsBounds(), sourceAreas, idEntity::MAX_PVS_AREAS ); pvsHandle_t pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); for(int i=0;iIsType(hhPlayer::Type)); hhPlayer *plEnt = static_cast(ent); float l = (ent->GetOrigin()-GetOrigin()).Length(); if(l < 4096.0f && (l < closestDist || !closestEnt) && ent->PhysicsTeamInPVS(pvsHandle)) { trace_t tr; gameLocal.clip.TracePoint(tr, GetOrigin(), ent->GetOrigin(), GetPhysics()->GetClipMask(), this); if (tr.c.entityNum == ent->entityNumber) { //if we hit the thing then it's visible. if (ent->health > 0) { if (gameLocal.gameType != GAME_TDM || plEnt->team != team) { closestDist = l; closestEnt = ent; } } } } } } gameLocal.pvs.FreeCurrentPVS( pvsHandle ); if (closestEnt) { idealAngles = (closestEnt->GetOrigin()-GetOrigin()).ToAngles(); } else { //if no one to run mindlessly at, do some stuff. const float testDist = 64.0f; idVec3 fwd = GetOrigin()+(idealAngles.ToForward()*testDist); trace_t tr; gameLocal.clip.TraceBounds(tr, GetOrigin(), fwd, GetPhysics()->GetBounds(), GetPhysics()->GetClipMask(), this); if (tr.fraction != 1.0f) { //if we hit something set the yaw to a new random angle. iFeelHoppy = true; idealAngles.yaw = rand()%360; } } if ((closestEnt || health <= 0) && rand()%2 == 1) { nextCmd->buttons |= BUTTON_ATTACK; } else { nextCmd->buttons &= ~BUTTON_ATTACK; } if (testCrouchTime < gameLocal.time) { testCrouchTime = gameLocal.time + rand()%4000; testCrouchActive = !testCrouchActive; } if (iFeelHoppy) { nextCmd->upmove = idMath::ClampChar(127); } else { if (testCrouchActive) { nextCmd->upmove = idMath::ClampChar(-127); } else { nextCmd->upmove = 0; } } idealAngles.pitch = idMath::AngleNormalize180(idealAngles.pitch); idealAngles.yaw = idMath::AngleNormalize180(idealAngles.yaw); idealAngles.roll = idMath::AngleNormalize180(idealAngles.roll); SetUntransformedViewAngles(idAngles(0.0f, 0.0f, 0.0f)); UpdateDeltaViewAngles(idealAngles); UpdateOrientation(idealAngles); PROFILE_STOP("hhArtificialPlayer::Think", PROFMASK_NORMAL); hhPlayer::Think(); } void hhArtificialPlayer::ClientPredictionThink( void ) { gameLocal.usercmds[entityNumber] = lastAICmd; //copy over the last manually snapshotted usercmd first hhPlayer::ClientPredictionThink(); } void hhArtificialPlayer::WriteToSnapshot( idBitMsgDelta &msg ) const { //instead of sync'ing, just mirror what the server has in ::Spawn /* msg.WriteDict(*gameLocal.GetUserInfo(entityNumber)); //ap's don't broadcast their info to the server, so the server otherwise //will never broadcast it out to other clients */ hhPlayer::WriteToSnapshot(msg); usercmd_t &cmd = gameLocal.usercmds[entityNumber]; msg.WriteLong( cmd.gameTime ); msg.WriteByte( cmd.buttons ); msg.WriteShort( cmd.mx ); msg.WriteShort( cmd.my ); msg.WriteChar( cmd.forwardmove ); msg.WriteChar( cmd.rightmove ); msg.WriteChar( cmd.upmove ); msg.WriteShort( cmd.angles[0] ); msg.WriteShort( cmd.angles[1] ); msg.WriteShort( cmd.angles[2] ); msg.WriteBits(spectating, 1); } void hhArtificialPlayer::ReadFromSnapshot( const idBitMsgDelta &msg ) { /* ((idBitMsgDelta)msg).ReadDict(*((idDict *)gameLocal.GetUserInfo(entityNumber))); if (!clientReceivedUI) { UserInfoChanged( false ); clientReceivedUI = true; } */ hhPlayer::ReadFromSnapshot(msg); usercmd_t &cmd = lastAICmd; cmd.gameTime = msg.ReadLong(); cmd.buttons = msg.ReadByte(); cmd.mx = msg.ReadShort(); cmd.my = msg.ReadShort(); cmd.forwardmove = msg.ReadChar(); cmd.rightmove = msg.ReadChar(); cmd.upmove = msg.ReadChar(); cmd.angles[0] = msg.ReadShort(); cmd.angles[1] = msg.ReadShort(); cmd.angles[2] = msg.ReadShort(); bool spec = !!msg.ReadBits(1); //doesn't go through right for ap's or something based on events sometimes, hack fix if (spec != spectating) { Spectate(spec); } } //HUMANHEAD END void hhPlayer::DisableSpiritWalk(int timeout) { nextSpiritTime = gameLocal.time + SEC2MS(timeout); StopSpiritWalk(); } //============================================================================= // // hhPlayer::Event_UpdateDDA // // CJR: Update the probability the player will die this tick, and // adjust the difficulty accordingly //============================================================================= void hhPlayer::Event_UpdateDDA() { idEntity *ent; int updateFlags; // Find the number of alive, non-dormant creatures attacking the player ddaNumEnemies = 0; ddaProbabilityAccum = 1.0f; updateFlags = 0; for( ent = gameLocal.activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { if ( ent->fl.isDormant || !ent->IsType( hhMonsterAI::Type ) || ent->fl.hidden || ent->health <= 0 ) { continue; } hhMonsterAI *monster = static_cast(ent); if ( monster->GetEnemy() != this ) { continue; } // PVS Check: only include creatures that the player can attack or be attacked by if ( !gameLocal.InPlayerPVS( ent ) ) { continue; } // Accumulate the survival rate against each enemy int index = monster->spawnArgs.GetInt( "ddaIndex", "0" ); if ( index == -1 ) { // Skip certain monsters, such as the vacuum or the crawlers continue; } float prob = gameLocal.GetDDA()->DDA_GetProbability( index, GetHealth() ); ddaProbabilityAccum *= prob; // Recompute the actual difficulty based upon the creatures in view updateFlags |= ( 1 << index ); ddaNumEnemies++; } gameLocal.GetDDA()->RecalculateDifficulty( updateFlags ); PostEventSec( &EV_UpdateDDA, spawnArgs.GetFloat( "updateDDARate", "0.25" ) ); if ( g_printDDA.GetBool() ) { if ( ddaNumEnemies > 0 ){ common->Printf("Probability [%.2f]\n", ddaProbabilityAccum ); } } } void hhPlayer::Event_DisableSpirit(void) { // HUMANHEAD PCF pdm 05-17-06: Removed assert, as scripters were potentially calling during deathwalk // HUMANHEAD PCF pdm 05-17-06: Only exit spirit realm if spiritwalking, not deathwalking if ( IsSpiritWalking() ) { StopSpiritWalk(); } bAllowSpirit = false; } void hhPlayer::Event_EnableSpirit(void) { // HUMANHEAD PCF pdm 05-17-06: Removed assert bAllowSpirit = true; } //HUMANHEAD bjk void hhPlayer::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { // no need for these in sp if( gameLocal.isMultiplayer ) { idActor::ProjectOverlay( origin, dir, size, material ); } } // HUMANHEAD mdl: Ripped from idAI for short invulnerability after returning from deathwalk void hhPlayer::Event_AllowDamage( void ) { fl.takedamage = true; } void hhPlayer::Event_IgnoreDamage( void ) { fl.takedamage = false; } // Precompute all the weapons' info for faster HUD updates void hhPlayer::SetupWeaponInfo() { const char *fireInfoName = NULL; const idDeclEntityDef *decl = NULL; const char *ammoName = NULL; float maxAmmo; memset(weaponInfo, 0, sizeof(weaponInfo_t)*15); memset(altWeaponInfo, 0, sizeof(weaponInfo_t)*15); for (int ix=0; ix<15; ix++) { const char *weaponClassName = spawnArgs.GetString( va( "def_weapon%d", ix ), NULL ); if ( weaponClassName && *weaponClassName ) { const idDeclEntityDef *weaponObjDecl = gameLocal.FindEntityDef( weaponClassName, false ); if ( weaponObjDecl ) { fireInfoName = weaponObjDecl->dict.GetString("def_fireInfo"); decl = gameLocal.FindEntityDef( fireInfoName, false ); if ( decl ) { weaponInfo[ix].ammoType = inventory.AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) ); weaponInfo[ix].ammoRequired = decl->dict.GetInt("ammoRequired"); weaponInfo[ix].ammoLow = decl->dict.GetInt("lowAmmo"); ammoName = idWeapon::GetAmmoNameForNum( weaponInfo[ix].ammoType ); maxAmmo = inventory.MaxAmmoForAmmoClass( this, ammoName ); weaponInfo[ix].ammoMax = max(1, maxAmmo); } fireInfoName = weaponObjDecl->dict.GetString("def_altFireInfo"); decl = gameLocal.FindEntityDef( fireInfoName, false ); if ( decl ) { altWeaponInfo[ix].ammoType = inventory.AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) ); altWeaponInfo[ix].ammoRequired = decl->dict.GetInt("ammoRequired"); altWeaponInfo[ix].ammoLow = decl->dict.GetInt("lowAmmo"); ammoName = idWeapon::GetAmmoNameForNum( altWeaponInfo[ix].ammoType ); maxAmmo = inventory.MaxAmmoForAmmoClass( this, ammoName ); altWeaponInfo[ix].ammoMax = max(1, maxAmmo); } } } } } /* =============== hhPlayer::ThrowGrenade HUMANHEAD bjk =============== */ void hhPlayer::ThrowGrenade( void ) { idMat3 axis; idVec3 muzzle; if( inventory.HasAmmo(inventory.AmmoIndexForAmmoClass( "ammo_crawler" ), 1) == false ) return; if (IsSpiritOrDeathwalking() || InVehicle() || bReallyDead) { return; } if ( privateCameraView || !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 || gameLocal.isClient ) return; if( weapon.IsValid() ) { if ( inventory.weapons & ( 1 << 12 ) && currentWeapon != 12 && currentWeapon != 3 && idealWeapon == currentWeapon ) { idealWeapon = 12; previousWeapon = currentWeapon; } else if( currentWeapon == 3 ) { FireWeapon(); usercmd.buttons = usercmd.buttons | BUTTON_ATTACK; } } } //HUMANHEAD bjk void hhPlayer::Event_ReturnToWeapon() { const char *weap; if ( !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 ) { idThread::ReturnInt(0); return; } if ( ( previousWeapon < 0 ) || ( previousWeapon >= MAX_WEAPONS ) ) { idThread::ReturnInt(0); return; } if ( gameLocal.isClient ) { idThread::ReturnInt(0); return; } weap = spawnArgs.GetString( va( "def_weapon%d", previousWeapon ) ); if ( !weap[ 0 ] ) { gameLocal.Printf( "Invalid weapon\n" ); idThread::ReturnInt(0); return; } if ( inventory.weapons & ( 1 << previousWeapon ) ) { idealWeapon = previousWeapon; } idThread::ReturnInt(1); } //HUMANHEAD rww void hhPlayer::Event_CanAnimateTorso(void) { if (!gameLocal.isMultiplayer) { //don't worry about it idThread::ReturnInt(1); return; } if (!weapon.IsValid() || !weapon->IsType(hhWeapon::Type)) { //bad idThread::ReturnInt(0); return; } idThread::ReturnInt(1); } void hhPlayer::Show(void) { idActor::Show(); hhWeapon *weap; weap = weapon.GetEntity(); if ( weap ) { if (!IsLocked(currentWeapon)) { weap->ShowWorldModel(); } else { weap->HideWorldModel(); } } }