//************************************************************************** //** //** PREY_ITEMS.CPP //** //** Game code for Prey-specific items //** //************************************************************************** #include "precompiled.h" #pragma hdrstop #include "prey_local.h" //========================================================================== // hhItem //========================================================================== idEventDef EV_SetPickupState( "", "dd" ); CLASS_DECLARATION( idItem, hhItem ) EVENT( EV_SetPickupState, hhItem::Event_SetPickupState ) EVENT( EV_RespawnItem, hhItem::Event_Respawn ) END_CLASS /* ================ hhItem::Spawn ================ */ void hhItem::Spawn() { // Logic to allow item cabinets to deny pickups until desired if( spawnArgs.GetBool("enablePickup", "1") ) { EnablePickup(); } else { DisablePickup(); } // Diversity for the blinking highlight shells SetShaderParm(SHADERPARM_DIVERSITY, gameLocal.random.RandomFloat()); } /* ================ hhItem::EnablePickup ================ */ void hhItem::EnablePickup() { GetPhysics()->EnableClip(); GetPhysics()->SetContents( CONTENTS_TRIGGER ); canPickUp = !spawnArgs.GetBool("triggerFirst"); } /* ================ hhItem::DisablePickup ================ */ void hhItem::DisablePickup() { GetPhysics()->DisableClip(); GetPhysics()->SetContents( 0 ); canPickUp = false; } /* ================ hhItem::Pickup ================ */ bool hhItem::Pickup( idPlayer *player ) { if( gameLocal.isMultiplayer ) { if( gameLocal.IsCooperative() ) { CoopPickup( player ); } else { if (MultiplayerPickup( player )) { //HUMANHEAD rww - see if the weapon has a dropped energy type on it const char *droppedEnergy = spawnArgs.GetString("def_droppedEnergyType"); if (droppedEnergy && droppedEnergy[0]) { //if it does, copy it to the player's inventory const idDeclEntityDef *energyDecl = gameLocal.FindEntityDef(droppedEnergy, false); if (energyDecl) { const idDeclEntityDef *fireDecl = gameLocal.FindEntityDef(energyDecl->dict.GetString("def_fireInfo"), false); if (fireDecl) { hhPlayer *hhPl = static_cast(player); int num = hhPl->GetWeaponNum("weaponobj_soulstripper"); assert(num); hhPl->inventory.energyType = droppedEnergy; hhPl->weaponInfo[ num ].ammoMax = fireDecl->dict.GetInt("ammoAmount"); hhPl->spawnArgs.SetInt( "max_ammo_energy", fireDecl->dict.GetInt("ammoAmount") ); hhPl->inventory.ammo[hhPl->inventory.AmmoIndexForAmmoClass("ammo_energy")]=0; hhPl->Give( "ammo_energy", fireDecl->dict.GetString("ammoAmount") ); } } } //HUMANHEAD END } } } else { SinglePlayerPickup( player ); } return true; } /* ================ hhItem::ShouldRespawn ================ */ bool hhItem::ShouldRespawn( float* respawnDelay ) const { float respawn = spawnArgs.GetFloat( "respawn", "5.0" ); if (gameLocal.isMultiplayer) { //rww - override default in mp respawn *= 2.0f; } if( respawnDelay && respawn > 0.0f ) { *respawnDelay = respawn; } return (respawn > 0.0f) && !spawnArgs.GetBool( "dropped" ) && gameLocal.isMultiplayer; } /* ================ hhItem::PostRespawn ================ */ void hhItem::PostRespawn( float delay ) { const char *sfx = spawnArgs.GetString( "fxRespawn" ); if ( sfx && *sfx ) { PostEventSec( &EV_RespawnFx, delay - 0.5f ); } PostEventSec( &EV_RespawnItem, delay ); } /* ================ hhItem::DetermineRemoveOrRespawn ================ */ void hhItem::DetermineRemoveOrRespawn( int removeDelay ) { bool keepThinking = false; // clear our contents so the object isn't picked up twice SetPickupState( 0, false ); if (gameLocal.isMultiplayer && !IsType(idMoveableItem::Type)) { //check for a respawning skin in mp idStr respawningSkin; if (spawnArgs.GetString("skin_itemRespawning", "", respawningSkin)) { SetSkin(declManager->FindSkin(respawningSkin.c_str())); keepThinking = true; } } if (!keepThinking) { // hide the model Hide(); } // add the highlight shell if ( itemShellHandle != -1 ) { gameRenderWorld->FreeEntityDef( itemShellHandle ); itemShellHandle = -1; } float respawnDelay = 0.0f; if ( ShouldRespawn(&respawnDelay) ) { PostRespawn( respawnDelay ); } else { // give some time for the pickup sound to play // FIXME: Play on the owner PostEventMS( &EV_Remove, removeDelay ); } if (!keepThinking) { BecomeInactive( TH_THINK ); } } /* ================ hhItem::AnnouncePickup ================ */ int hhItem::AnnouncePickup( idPlayer* player ) { ServerSendEvent( EVENT_PICKUP, NULL, false, -1 ); // play pickup sound int soundLength = 0; StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, &soundLength ); // trigger our targets ActivateTargets( player ); return soundLength; } /* ================ hhItem::SinglePlayerPickup ================ */ void hhItem::SinglePlayerPickup( idPlayer *player ) { if ( !GiveToPlayer( player ) ) { return; } DetermineRemoveOrRespawn( AnnouncePickup(player) ); } /* ================ hhItem::CoopPickup ================ */ void hhItem::CoopPickup( idPlayer* player ) { const char* weaponDef = spawnArgs.GetString( "def_weapon" ); if( weaponDef && weaponDef[0] ) { CoopWeaponPickup( player ); } else { CoopItemPickup( player ); } } /* ================ hhItem::CoopWeaponPickup ================ */ void hhItem::CoopWeaponPickup( idPlayer *player ) { float delay = 0.0f; if( !GiveToPlayer(player) ) { return; } AnnouncePickup( player ); ShouldRespawn( &delay ); SetPickupState( 0, false ); PostEventSec( &EV_SetPickupState, delay, CONTENTS_TRIGGER, true ); } /* ================ hhItem::CoopItemPickup ================ */ void hhItem::CoopItemPickup( idPlayer *player ) { idEntity* entity = NULL; hhPlayer* castPlayer = NULL; for( int ix = 0; ix < gameLocal.numClients; ++ix ) { entity = gameLocal.entities[ix]; if( !entity || !entity->IsType(hhPlayer::Type) ) { continue; } castPlayer = static_cast( entity ); castPlayer->GiveItem( this ); } DetermineRemoveOrRespawn( AnnouncePickup(player) ); } /* ================ hhItem::MultiplayerPickup ================ */ bool hhItem::MultiplayerPickup( idPlayer *player ) { if( !GiveToPlayer(player) ) { return false; } DetermineRemoveOrRespawn( AnnouncePickup(player) ); return true; } /* ================ hhItem::SetPickupState ================ */ void hhItem::SetPickupState( int contents, bool allowPickup ) { GetPhysics()->SetContents( contents ); canPickUp = allowPickup; } /* ================ hhItem::Event_SetPickupState ================ */ void hhItem::Event_SetPickupState( int contents, bool allowPickup ) { SetPickupState( contents, allowPickup ); } /* ================ hhItem::Event_Respawn ================ */ void hhItem::Event_Respawn( void ) { if ( !gameLocal.isClient ) { ServerSendEvent( EVENT_RESPAWN, NULL, false, -1 ); } BecomeActive( TH_THINK ); if (gameLocal.isMultiplayer && renderEntity.customSkin != GetNonRespawnSkin()) { SetSkin(GetNonRespawnSkin()); //restore skin } Show(); inViewTime = -1000; lastCycle = -1; //HUMANHEAD: aob SetPickupState( CONTENTS_TRIGGER, true ); //HUMANHEAD END SetOrigin( orgOrigin ); StartSound( "snd_respawn", SND_CHANNEL_ITEM ); } //============================================================================= // hhItemSoul //============================================================================= idEventDef EV_PlaySpiritSound( "playSpiritSound", NULL ); idEventDef EV_Broadcast_AssignFx_Spirit( "", "e" ); idEventDef EV_Broadcast_AssignFx_Physical( "", "e" ); CLASS_DECLARATION( hhItem, hhItemSoul ) EVENT( EV_TalonAction, hhItemSoul::Event_TalonAction ) EVENT( EV_PostSpawn, hhItemSoul::Event_PostSpawn ) EVENT( EV_PlaySpiritSound, hhItemSoul::Event_PlaySpiritSound ) EVENT( EV_Broadcast_AssignFx_Spirit, hhItemSoul::Event_AssignFxSoul_Spirit ) EVENT( EV_Broadcast_AssignFx_Physical, hhItemSoul::Event_AssignFxSoul_Physical ) END_CLASS //-------------------------------------------------------------------------- // // hhItemSoul::Spawn // //-------------------------------------------------------------------------- void hhItemSoul::Spawn() { idDict args; idVec3 offset; fl.networkSync = true; //rww fl.clientEvents = true; //rww spawnArgs.SetBool("dropped", true); //rww - don't respawn bFollowTriggered = false; // Only allow this item to be spawned if we can spirit walk if (!gameLocal.isMultiplayer) { hhPlayer *player = static_cast( gameLocal.GetLocalPlayer() ); if ( player ) { if ( !player->inventory.requirements.bCanSpiritWalk ) { Hide(); PostEventMS( &EV_Remove, 0 ); // Remove this soul before it can be spawned return; } } } BecomeActive( TH_THINK | TH_PHYSICS ); // Remove the lifeforce after 60 seconds if it hasn't been picked up if (gameLocal.isMultiplayer) { //rww PostEventSec( &EV_Remove, spawnArgs.GetFloat( "lifeTime_mp", "30" ) ); } else { PostEventSec( &EV_Remove, spawnArgs.GetFloat( "lifeTime", "60" ) ); } // Play the spirit sound, after a 10 second delay PostEventSec( &EV_PlaySpiritSound, spawnArgs.GetFloat( "spiritSoundDelay", "10" ) ); renderEntity.onlyVisibleInSpirit = true; velocity = vec3_origin; acceleration = vec3_origin; surge = 0.0f; parentMonster = NULL; PostEventMS( &EV_PostSpawn, 0 ); } //-------------------------------------------------------------------------- // // hhItemSoul::Event_PostSpawn // //-------------------------------------------------------------------------- void hhItemSoul::Event_PostSpawn() { hhFxInfo fxInfo; if (!gameLocal.isMultiplayer) { const char *monsterName = spawnArgs.GetString("monsterSpawnedBy", NULL); if (monsterName) { idEntity *ent = gameLocal.FindEntity(monsterName); if (ent && ent->IsType(idAI::Type)) { parentMonster = ent; } } } // Spawn the spirit-only fx (this fx is only seen when spiritwalking) fxInfo.SetNormal( GetAxis()[2] ); fxInfo.RemoveWhenDone( false ); fxInfo.OnlyVisibleInSpirit( true ); BroadcastFxInfo( spawnArgs.GetString("fx_lifeforce"), GetOrigin(), GetAxis(), &fxInfo, &EV_Broadcast_AssignFx_Spirit, false ); //rww - local // Spawn the physical realm fx fxInfo.SetNormal( GetAxis()[2] ); fxInfo.RemoveWhenDone( false ); fxInfo.OnlyVisibleInSpirit( false ); BroadcastFxInfo( spawnArgs.GetString("fx_lifeforcePhysical"), GetOrigin(), GetAxis(), &fxInfo, &EV_Broadcast_AssignFx_Physical, false ); //rww - local } //-------------------------------------------------------------------------- // // hhItemSoul::Event_AssignFxSoul // //-------------------------------------------------------------------------- void hhItemSoul::Event_AssignFxSoul_Spirit( hhEntityFx* fx ) { soulFx = fx; } void hhItemSoul::Event_AssignFxSoul_Physical( hhEntityFx* fx ) { physicalSoulFx = fx; } //-------------------------------------------------------------------------- // // hhItemSoul::Event_PlaySpiritSound // //-------------------------------------------------------------------------- void hhItemSoul::Event_PlaySpiritSound() { StartSound( "snd_spiritItemNear", SND_CHANNEL_SPIRITWALK, 0, true ); // Randomly start the sound over again (random delay between 12 and 22 seconds) PostEventSec( &EV_PlaySpiritSound, 12.0f + spawnArgs.GetFloat( "spiritSoundDelay", "10.0" ) * gameLocal.random.RandomFloat() ); } //-------------------------------------------------------------------------- // // hhItemSoul::~hhItemSoul // //-------------------------------------------------------------------------- hhItemSoul::~hhItemSoul() { StopSound( SND_CHANNEL_SPIRITWALK, false ); //stop now, do not broadcast on purpose SAFE_REMOVE( soulFx ); SAFE_REMOVE( physicalSoulFx ); } //-------------------------------------------------------------------------- // // hhItemSoul::Ticker // //-------------------------------------------------------------------------- void hhItemSoul::Think() { idVec3 playerOrigin; idMat3 playerAxis; // Move the soul towards the nearest spirit player //COOP FIXME: Get the NEAREST spirit player for this to support multiplayer and coop. hhPlayer *player = NULL; if (!gameLocal.isMultiplayer) { player = static_cast( gameLocal.GetLocalPlayer() ); } else { orgOrigin = GetOrigin(); spin = false; } float followSpeed = spawnArgs.GetFloat( "followSpeed", "20" ); if ( !player || player->noclip ) { velocity = vec3_origin; // mdl: If player is noclip, don't move an inch } else { player->GetViewPos( playerOrigin, playerAxis ); if ( !bFollowTriggered && player && player->IsSpiritWalking() ) { acceleration = ( playerOrigin - playerAxis[2] * 20 ) - GetOrigin(); float dot = playerAxis[0] * -acceleration; if ( dot > 0.95f ) { // Accelerate only if the player is looking at the soul bFollowTriggered = true; lastPlayerOrigin = playerOrigin; } } if ( bFollowTriggered ) { //HUMANHEAD PCF mdl 04/28/06 - Changed factor from 0.01 to 0.05 for stationary players float factor = 0.05f; // Keep acceleration slow enough for the player to see if ( ( lastPlayerOrigin - playerOrigin ).LengthSqr() > 75.0f ) { velocity = vec3_origin; // Player has moved, so clear the old velocity lastPlayerOrigin = playerOrigin; //HUMANHEAD PCF mdl 04/28/06 - Changed factor from 5 to 3.5 for moving players factor = 3.5f; // Give acceleration a little boost so the soul doesn't seem slow when the player is moving around. } acceleration = ( playerOrigin - playerAxis[2] * 20 ) - GetOrigin(); velocity += (factor * acceleration * ( spawnArgs.GetFloat( "acceleration", "0.5" ) * spawnArgs.GetFloat( "surge", "0.01" ) ) * (60.0f * USERCMD_ONE_OVER_HZ)); SetOrigin( GetOrigin() + velocity ); UpdateVisuals(); if ( soulFx.IsValid() ) { soulFx->SetOrigin( GetOrigin() ); soulFx->UpdateVisuals(); } if ( physicalSoulFx.IsValid() ) { physicalSoulFx->SetOrigin( GetOrigin() ); physicalSoulFx->UpdateVisuals(); } } } // Must be done after this movement for proper relinking hhItem::Think(); } //rww - netcode void hhItemSoul::WriteToSnapshot( idBitMsgDelta &msg ) const { GetPhysics()->WriteToSnapshot(msg); msg.WriteBits(IsHidden() || !soulFx.IsValid() || !soulFx->IsActive(TH_THINK), 1); } void hhItemSoul::ReadFromSnapshot( const idBitMsgDelta &msg ) { GetPhysics()->ReadFromSnapshot(msg); bool hidden = !!msg.ReadBits(1); if (hidden != IsHidden()) { if (hidden) { if( soulFx.IsValid() ) { soulFx->Nozzle( false ); } if( physicalSoulFx.IsValid() ) { physicalSoulFx->Nozzle( false ); } Hide(); } else { Show(); } } } void hhItemSoul::ClientPredictionThink( void ) { Think(); } //-------------------------------------------------------------------------- // // hhItemSoul::Pickup // //-------------------------------------------------------------------------- bool hhItemSoul::Pickup( idPlayer *player ) { CancelEvents( &EV_Remove ); // Remove the 60-second cancel event from when this item was spawned CancelEvents(&EV_PlaySpiritSound); //rww - also cancel pending spirit sound events StopSound( SND_CHANNEL_SPIRITWALK, true ); bool result = hhItem::Pickup( player ); if( soulFx.IsValid() ) { soulFx->Nozzle( false ); } if( physicalSoulFx.IsValid() ) { physicalSoulFx->Nozzle( false ); } return result; } //-------------------------------------------------------------------------- // // hhItemSoul::Event_TalonAction // //-------------------------------------------------------------------------- void hhItemSoul::Event_TalonAction( idEntity *talon, bool landed ) { idPlayer *player; if( !talon ) { return; } player = (idPlayer *)(((hhTalon *)talon)->GetOwner()); Pickup( player ); } //-------------------------------------------------------------------------- // // hhItemSoul::Event_Remove // //-------------------------------------------------------------------------- void hhItemSoul::Event_Remove() { // Now that spirit is gone, start the disposal countdown if (parentMonster.IsValid()) { parentMonster->StartDisposeCountdown(); } hhItem::Event_Remove(); } //================ //hhItemSoul::Save //================ void hhItemSoul::Save( idSaveGame *savefile ) const { soulFx.Save( savefile ); physicalSoulFx.Save( savefile ); parentMonster.Save( savefile ); savefile->WriteVec3( velocity ); savefile->WriteVec3( acceleration ); savefile->WriteFloat( surge ); savefile->WriteBool( bFollowTriggered ); savefile->WriteVec3( lastPlayerOrigin ); } //================ //hhItemSoul::Restore //================ void hhItemSoul::Restore( idRestoreGame *savefile ) { soulFx.Restore( savefile ); physicalSoulFx.Restore( savefile ); parentMonster.Restore( savefile ); savefile->ReadVec3( velocity ); savefile->ReadVec3( acceleration ); savefile->ReadFloat( surge ); savefile->ReadBool( bFollowTriggered ); savefile->ReadVec3( lastPlayerOrigin ); }