// Game_Vomiter.cpp // // spews vomit chunks periodically // triggering will cause an eruption #include "precompiled.h" #pragma hdrstop #include "prey_local.h" const idEventDef EV_Erupt("erupt", "d"); const idEventDef EV_UnErupt("unerupt", NULL); const idEventDef EV_DamagePulse("damagepulse", NULL); const idEventDef EV_MovablePulse(""); const idEventDef EV_Broadcast_AssignFxErupt( "", "e" ); CLASS_DECLARATION(hhAnimatedEntity, hhVomiter) EVENT( EV_Activate, hhVomiter::Event_Trigger ) EVENT( EV_Erupt, hhVomiter::Event_Erupt ) EVENT( EV_UnErupt, hhVomiter::Event_UnErupt ) EVENT( EV_PlayIdle, hhVomiter::Event_PlayIdle ) EVENT( EV_DamagePulse, hhVomiter::Event_DamagePulse ) EVENT( EV_MovablePulse, hhVomiter::Event_MovablePulse ) EVENT( EV_Broadcast_AssignFxErupt, hhVomiter::Event_AssignFxErupt ) END_CLASS hhVomiter::hhVomiter(void) { // mdl: Moved this here to prevent uninitialized fxErupt from crashing the destructor fxErupt = NULL; } void hhVomiter::Spawn(void) { detectionTrigger = NULL; spawnArgs.GetFloat("minDelay", "3", mindelay); spawnArgs.GetFloat("maxDelay", "7", maxdelay); secBetweenDamage = spawnArgs.GetFloat("secBetweenDamage"); secBetweenMovables = spawnArgs.GetFloat("secBetweenMovables"); minMovableVel = spawnArgs.GetFloat("minMovableVel"); maxMovableVel = spawnArgs.GetFloat("maxMovableVel"); removeTime = spawnArgs.GetFloat("movableRemoveTime"); numMovables = spawnArgs.GetInt("numMovables"); goAwayChance = spawnArgs.GetFloat("GoAwayChance"); idleAnim = GetAnimator()->GetAnim("idle"); painAnim = GetAnimator()->GetAnim("pain"); spewAnim = GetAnimator()->GetAnim("spew"); deathAnim = GetAnimator()->GetAnim("death"); bErupting = false; bIdling = false; // setup the clipModel GetPhysics()->SetContents( CONTENTS_SOLID ); // Spawn trigger used for waking it up SpawnTrigger(); //TODO: Spawn this the first time it erupts instead ? // Spawn FX system hhFxInfo fxInfo; fxInfo.SetStart( false ); fxInfo.SetNormal( GetAxis()[2] ); fxInfo.RemoveWhenDone( false ); fxInfo.Triggered( true ); BroadcastFxInfo(spawnArgs.GetString("fx_vomit"), GetOrigin(), GetAxis(), &fxInfo, &EV_Broadcast_AssignFxErupt ); StartIdle(); GoToSleep(); fl.takedamage = true; } hhVomiter::~hhVomiter() { SAFE_REMOVE(fxErupt); } void hhVomiter::Save(idSaveGame *savefile) const { savefile->WriteBool(bErupting); savefile->WriteBool(bIdling); savefile->WriteBool(bAwake); savefile->WriteFloat(mindelay); savefile->WriteFloat(maxdelay); savefile->WriteFloat(secBetweenDamage); savefile->WriteFloat(secBetweenMovables); savefile->WriteInt(idleAnim); savefile->WriteInt(painAnim); savefile->WriteInt(spewAnim); savefile->WriteInt(deathAnim); savefile->WriteFloat(minMovableVel); savefile->WriteFloat(maxMovableVel); savefile->WriteFloat(removeTime); savefile->WriteInt(numMovables); savefile->WriteFloat(goAwayChance); savefile->WriteObject(fxErupt); detectionTrigger.Save(savefile); } void hhVomiter::Restore( idRestoreGame *savefile ) { savefile->ReadBool(bErupting); savefile->ReadBool(bIdling); savefile->ReadBool(bAwake); savefile->ReadFloat(mindelay); savefile->ReadFloat(maxdelay); savefile->ReadFloat(secBetweenDamage); savefile->ReadFloat(secBetweenMovables); savefile->ReadInt(idleAnim); savefile->ReadInt(painAnim); savefile->ReadInt(spewAnim); savefile->ReadInt(deathAnim); savefile->ReadFloat(minMovableVel); savefile->ReadFloat(maxMovableVel); savefile->ReadFloat(removeTime); savefile->ReadInt(numMovables); savefile->ReadFloat(goAwayChance); savefile->ReadObject( reinterpret_cast(fxErupt) ); detectionTrigger.Restore(savefile); } void hhVomiter::SpawnTrigger() { idDict Args; hhUtils::PassArgs( spawnArgs, Args, "triggerpass_" ); Args.Set( "target", name.c_str() ); Args.Set( "bind", name.c_str() ); Args.SetVector( "origin", GetOrigin() + spawnArgs.GetVector("offset_trigger") ); Args.SetMatrix( "rotation", GetAxis() ); detectionTrigger = gameLocal.SpawnObject( spawnArgs.GetString("def_trigger"), &Args ); } void hhVomiter::DormantBegin( void ) { } void hhVomiter::DormantEnd( void ) { } void hhVomiter::Erupt(int repeat) { if (bErupting || health <= 0 || fl.isDormant) { if (fl.isDormant) { UnErupt(); // go to sleep, etc., so we can wake up again later } return; } bErupting = true; StartSound( "snd_erupt", SND_CHANNEL_ANY ); GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, spewAnim, gameLocal.time, 0); int startTime = GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->Length(); PostEventMS( &EV_PlayIdle, startTime ); PostEventMS( &EV_MovablePulse, 0 ); // Self repeating moveable spawn PostEventMS( &EV_DamagePulse, 0 ); // Self repeating damage pulse // Activate FX if (fxErupt) { fxErupt->PostEventMS( &EV_Activate, 0, this ); } // Stop erupting in a while PostEventSec( &EV_UnErupt, spawnArgs.GetFloat("vomitTime") ); } void hhVomiter::UnErupt() { CancelEvents(&EV_Erupt); // Stop any pending events CancelEvents(&EV_DamagePulse); CancelEvents(&EV_MovablePulse); if (bErupting) { bErupting = false; // Deactivate FX if (fxErupt) { fxErupt->PostEventMS( &EV_Fx_KillFx, 0 ); } } GoToSleep(); } void hhVomiter::GoToSleep() { bAwake = false; } void hhVomiter::WakeUp() { if (!bAwake && !bErupting && health > 0) { bAwake = true; StartSound( "snd_wakeup", SND_CHANNEL_ANY ); PostEventSec( &EV_Erupt, 0.1+mindelay + gameLocal.random.RandomFloat()*(maxdelay-mindelay), true); } } bool hhVomiter::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { if (painAnim) { StartSound( "snd_pain", SND_CHANNEL_ANY ); GetAnimator()->ClearAllAnims( gameLocal.time, 200 ); GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, painAnim, gameLocal.time, 200); int startTime = GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->Length(); PostEventMS( &EV_PlayIdle, startTime ); } return( idEntity::Pain(inflictor, attacker, damage, dir, location) ); } void hhVomiter::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { if (health + damage > 0) { // Only do this the first death // Clear Event Queue of all eruptions CancelEvents(&EV_Erupt); CancelEvents(&EV_DamagePulse); if (bErupting) { UnErupt(); } StartSound( "snd_die", SND_CHANNEL_ANY ); // Spawn dead effect -- currently an explosion hhFxInfo fxInfo; fxInfo.SetNormal( GetAxis()[2] ); fxInfo.RemoveWhenDone( true ); BroadcastFxInfoPrefixed( "fx_death", GetOrigin(), -GetAxis(), &fxInfo ); ActivateTargets( attacker ); StopIdle(); GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, deathAnim, gameLocal.time, 0); } else { // Post-death pain // GetAnimator()->ClearAllAnims( gameLocal.time, 200 ); // GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, painAnim, gameLocal.time, 200); } } void hhVomiter::StartIdle() { hhFxInfo fxInfo; if (idleAnim && !bIdling && health > 0) { PostEventMS(&EV_PlayIdle, 0); StartSound( "snd_idle", SND_CHANNEL_IDLE ); bIdling = true; } } void hhVomiter::StopIdle() { CancelEvents(&EV_PlayIdle); StopSound( SND_CHANNEL_IDLE ); bIdling = false; } // // Events // void hhVomiter::Event_AssignFxErupt( hhEntityFx* fx ) { fxErupt = fx; } void hhVomiter::Event_PlayIdle() { GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 100); } void hhVomiter::Event_Trigger( idEntity *activator ) { WakeUp(); } void hhVomiter::Event_Erupt(int repeat) { Erupt(repeat); } void hhVomiter::Event_UnErupt() { UnErupt(); } void hhVomiter::Event_DamagePulse() { idEntity *touch[ MAX_GENTITIES ]; int num; // Damage all entities touching trigger's clipmodel num = hhUtils::EntitiesTouchingClipmodel( detectionTrigger->GetPhysics()->GetClipModel(), touch, MAX_GENTITIES, MASK_SHOT_BOUNDINGBOX ); for (int i = 0; i < num; i++ ) { if( !touch[i] || touch[i] == this ) { continue; } touch[i]->Damage(this, this, GetAxis()[2], spawnArgs.GetString("def_damage"), 1.0f, 0); } PostEventSec( &EV_DamagePulse, secBetweenDamage ); } void hhVomiter::Event_MovablePulse() { // Spawn movables idDict args; idEntity *ent; idVec3 vel, dir; const char *movableName = spawnArgs.RandomPrefix("def_movable", gameLocal.random); if (movableName && *movableName) { args.Clear(); args.SetVector("origin", GetOrigin()); if (gameLocal.random.RandomFloat() < goAwayChance) { args.SetBool("removeOnCollision", true); } else { args.SetFloat("removeTime", removeTime); } for (int ix=0; ixGetPhysics()->SetLinearVelocity( vel ); ent->fl.takedamage = false; // don't let the radius damage affect them } } PostEventSec( &EV_MovablePulse, secBetweenMovables ); // Self repeating event }