#include "precompiled.h" #pragma hdrstop #include "prey_local.h" const idEventDef EV_UpdateTarget( "" ); const idEventDef EV_DirectMoveToPosition("directMoveToPosition", "v" ); const idEventDef MA_GetCircleNode("getCircleNode", NULL, 'e' ); const idEventDef MA_SpinClouds("spinClouds", "f" ); const idEventDef MA_SetSeekScale("setSeekScale", "f" ); CLASS_DECLARATION(hhMonsterAI, hhSphereBoss) EVENT( EV_UpdateTarget, hhSphereBoss::Event_UpdateTarget ) EVENT( EV_DirectMoveToPosition, hhSphereBoss::Event_DirectMoveToPosition ) EVENT( MA_GetCircleNode, hhSphereBoss::Event_GetCircleNode ) EVENT( MA_SpinClouds, hhSphereBoss::Event_SpinClouds ) EVENT( MA_SetSeekScale, hhSphereBoss::Event_SetSeekScale ) END_CLASS #ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build void hhSphereBoss::Spawn() { for ( int i=0;i<10;i++ ) { lastTargetPos.Append( vec3_zero ); } nextShieldImpact = 0; lastNodeIndex = -1; PostEventSec( &EV_UpdateTarget, spawnArgs.GetFloat( "target_period", "0.1" ) ); } void hhSphereBoss::Event_UpdateTarget() { for ( int i=0;i<10;i++ ) { if ( i == 9 ) { if ( enemy.IsValid() ) { lastTargetPos[i] = enemy->GetOrigin(); } else { lastTargetPos[i] = lastTargetPos[i - 1]; } } else { lastTargetPos[i] = lastTargetPos[i + 1]; } } PostEventSec( &EV_UpdateTarget, spawnArgs.GetFloat( "target_period", "0.1" ) ); } //overridden to allow custom accuracy/numbers per projectile def idProjectile *hhSphereBoss::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { idVec3 muzzle; idVec3 dir; idVec3 start; trace_t tr; idBounds projBounds; float distance; const idClipModel *projClip; float attack_accuracy; float attack_cone; float projectile_spread; float diff; float angle; float spin; idAngles ang; int num_projectiles; int i; idMat3 axis; idVec3 tmp; idProjectile *lastProjectile; //HUMANHEAD mdc - added to support multiple projectiles if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile int projIndex = FindProjectileInfo( desiredProjectileDef ); if( projIndex >= 0 ) { SetCurrentProjectile( projIndex ); } } //HUMANHEAD END if ( !projectileDef ) { gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); return NULL; } if ( projectileDef->GetFloat( "attack_accuracy" ) ) { attack_accuracy = projectileDef->GetFloat( "attack_accuracy", "7" ); } else { attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" ); } attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); if ( projectileDef->GetFloat( "projectile_spread" ) ) { projectile_spread = projectileDef->GetFloat( "projectile_spread", "0" ); } else { projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" ); } if ( projectileDef->GetFloat( "num_projectiles" ) ) { num_projectiles = projectileDef->GetFloat( "num_projectiles", "1" ); } else { num_projectiles = spawnArgs.GetFloat( "num_projectiles", "1" ); } GetMuzzle( jointname, muzzle, axis ); if ( !projectile.GetEntity() ) { CreateProjectile( muzzle, axis[ 0 ] ); } lastProjectile = projectile.GetEntity(); if ( target != NULL ) { tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle; tmp.Normalize(); axis = tmp.ToMat3(); } else { axis = viewAxis; } // rotate it because the cone points up by default tmp = axis[2]; axis[2] = axis[0]; axis[0] = -tmp; // make sure the projectile starts inside the monster bounding box const idBounds &ownerBounds = physicsObj.GetAbsBounds(); projClip = lastProjectile->GetPhysics()->GetClipModel(); projBounds = projClip->GetBounds().Rotate( axis ); // check if the owner bounds is bigger than the projectile bounds if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) { start = muzzle + distance * viewAxis[ 0 ]; } else { start = ownerBounds.GetCenter(); } } else { // projectile bounds bigger than the owner bounds, so just start it from the center start = ownerBounds.GetCenter(); } gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); muzzle = tr.endpos; // set aiming direction if ( spawnArgs.GetBool( "lag_target", "0" ) ) { dir = (lastTargetPos[0] - muzzle).ToNormal(); } else { GetAimDir( muzzle, target, this, dir ); } ang = dir.ToAngles(); // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread. float t = MS2SEC( gameLocal.time + entityNumber * 497 ); ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy; ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy; if ( clampToAttackCone ) { // clamp the attack direction to be within monster's attack cone so he doesn't do // things like throw the missile backwards if you're behind him diff = idMath::AngleDelta( ang.yaw, current_yaw ); if ( diff > attack_cone ) { ang.yaw = current_yaw + attack_cone; } else if ( diff < -attack_cone ) { ang.yaw = current_yaw - attack_cone; } } axis = ang.ToMat3(); float spreadRad = DEG2RAD( projectile_spread ); for( i = 0; i < num_projectiles; i++ ) { // spread the projectiles out angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) ); dir.Normalize(); // launch the projectile if ( !projectile.GetEntity() ) { CreateProjectile( muzzle, dir ); } lastProjectile = projectile.GetEntity(); lastProjectile->Launch( muzzle, dir, vec3_origin ); projectile = NULL; } TriggerWeaponEffects( muzzle,axis ); lastAttackTime = gameLocal.time; //HUMANHEAD mdc - added to support multiple projectiles projectile = NULL; SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side //HUMANHEAD END return lastProjectile; } void hhSphereBoss::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { HandleNoGore(); if ( !AI_DEAD ) { AI_DEAD = true; state = GetScriptFunction( "state_SphereDeath" ); SetState( state ); SetWaitState( "" ); } } void hhSphereBoss::FlyTurn( void ) { if ( AI_FACE_ENEMY ) { TurnToward( enemy->GetOrigin() ); } else { if ( move.moveCommand == MOVE_FACE_ENEMY ) { TurnToward( lastVisibleEnemyPos ); } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); } else if ( move.speed > 0.0f ) { const idVec3 &vel = physicsObj.GetLinearVelocity(); if ( vel.ToVec2().LengthSqr() > 0.1f ) { TurnToward( vel.ToYaw() ); } } } Turn(); } void hhSphereBoss::Event_SetSeekScale( float new_scale ) { fly_seek_scale = new_scale; } void hhSphereBoss::Event_GetCircleNode() { if ( !enemy.IsValid() ) { idThread::ReturnEntity( NULL ); return; } idEntity *ent = NULL; idEntity *bestEnt = NULL; lastNodeIndex++; for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { if ( !ent || !ent->IsType( hhAINode::Type ) ) { continue; } if ( !ent->spawnArgs.GetBool( "circle_node" ) ) { continue; } if ( lastNodeIndex == ent->spawnArgs.GetInt( "circle_node_index" ) ) { bestEnt = ent; break; } } if ( !bestEnt ) { lastNodeIndex = 0; for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { if ( !ent || !ent->IsType( hhAINode::Type ) ) { continue; } if ( !ent->spawnArgs.GetBool( "circle_node" ) ) { continue; } if ( lastNodeIndex == ent->spawnArgs.GetInt( "circle_node_index" ) ) { bestEnt = ent; break; } } } if ( bestEnt ) { idThread::ReturnEntity( bestEnt ); } else { idThread::ReturnEntity( NULL ); } } void hhSphereBoss::Event_GetCombatNode() { idEntity *ent = NULL; float bestDist = 0.0f; float dist; idEntity *bestEnt = NULL; idList list; for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { if ( !ent || !ent->spawnArgs.GetBool( "ainode" ) ) { continue; } dist = (ent->GetOrigin() - GetOrigin()).Length(); if ( dist < spawnArgs.GetFloat( "node_dist", "400" ) ) { continue; } list.Append( ent ); } if ( list.Num() > 0 ) { idThread::ReturnEntity( list[gameLocal.random.RandomInt(list.Num())] ); } else { idThread::ReturnEntity( NULL ); } } void hhSphereBoss::Event_DirectMoveToPosition(const idVec3 &pos) { StopMove(MOVE_STATUS_DONE); DirectMoveToPosition(pos); } void hhSphereBoss::FlyMove( void ) { idVec3 goalPos; idVec3 oldorigin; idVec3 newDest; AI_BLOCKED = false; if ( ( move.moveCommand != MOVE_NONE ) && ReachedPos( move.moveDest, move.moveCommand ) ) { if ( AI_FACE_ENEMY ) { StopMove( MOVE_STATUS_DONE ); } else { AI_MOVE_DONE = true; } } idVec3 vel = physicsObj.GetLinearVelocity(); goalPos = move.moveDest; if ( ReachedPos( move.moveDest, move.moveCommand ) ) { StopMove( MOVE_STATUS_DONE ); } if ( move.speed ) { FlySeekGoal( vel, goalPos ); } AddFlyBob( vel ); AdjustFlySpeed( vel ); physicsObj.SetLinearVelocity( vel ); // turn FlyTurn(); // run the physics for this frame oldorigin = physicsObj.GetOrigin(); physicsObj.UseFlyMove( true ); physicsObj.UseVelocityMove( false ); physicsObj.SetDelta( vec3_zero ); physicsObj.ForceDeltaMove( disableGravity ); RunPhysics(); monsterMoveResult_t moveResult = physicsObj.GetMoveResult(); if ( !af_push_moveables && attack.Length() && TestMelee() ) { DirectDamage( attack, enemy.GetEntity() ); } else { idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); } else if ( moveResult == MM_BLOCKED ) { move.blockTime = gameLocal.time + 500; AI_BLOCKED = true; } } idVec3 org = physicsObj.GetOrigin(); if ( oldorigin != org ) { TouchTriggers(); } if ( ai_debugMove.GetBool() ) { gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 4000 ); gameRenderWorld->DebugBounds( colorOrange, physicsObj.GetBounds(), org, gameLocal.msec ); gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); gameRenderWorld->DebugLine( colorRed, org, org + physicsObj.GetLinearVelocity(), gameLocal.msec, true ); gameRenderWorld->DebugLine( colorBlue, org, goalPos, gameLocal.msec, true ); gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); DrawRoute(); } } void hhSphereBoss::AdjustFlySpeed( idVec3 &vel ) { float speed; // apply dampening float damp = spawnArgs.GetFloat( "fly_dampening", "0.01" ); vel -= vel * damp * MS2SEC( gameLocal.msec ); // gradually speed up/slow down to desired speed speed = vel.Normalize(); speed += ( move.speed - speed ) * MS2SEC( gameLocal.msec ); if ( speed < 0.0f ) { speed = 0.0f; } else if ( move.speed && ( speed > move.speed ) ) { speed = move.speed; } vel *= speed; } bool hhSphereBoss::ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const { if ( move.moveType == MOVETYPE_SLIDE ) { idBounds bnds( idVec3( -4, -4.0f, -8.0f ), idVec3( 4.0f, 4.0f, 64.0f ) ); bnds.TranslateSelf( physicsObj.GetOrigin() ); if ( bnds.ContainsPoint( pos ) ) { return true; } } else { if ( ( moveCommand == MOVE_TO_ENEMY ) || ( moveCommand == MOVE_TO_ENTITY ) ) { if ( physicsObj.GetAbsBounds().IntersectsBounds( idBounds( pos ).Expand( 8.0f ) ) ) { return true; } } else { idBounds bnds( idVec3( -64.0, -64.0f, -64.0f ), idVec3( 64.0, 64.0f, 64.0f ) ); bnds.TranslateSelf( physicsObj.GetOrigin() ); if ( bnds.ContainsPoint( pos ) ) { return true; } } } return false; } #define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) void hhSphereBoss::LinkScriptVariables() { hhMonsterAI::LinkScriptVariables(); LinkScriptVariable( AI_FACE_ENEMY ); LinkScriptVariable( AI_CAN_DAMAGE ); } void hhSphereBoss::Event_SpinClouds( float shouldSpin ) { if ( shouldSpin == 0.0f ) { const idKeyValue *kv = spawnArgs.MatchPrefix( "cloud" ); while( kv ) { idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); if ( ent ) { ent->SetShaderParm( spawnArgs.GetInt( "spin_parm", "4" ), ent->spawnArgs.GetFloat( "shaderparm4", "0" ) ); } kv = spawnArgs.MatchPrefix( "cloud", kv ); } } else { const idKeyValue *kv = spawnArgs.MatchPrefix( "cloud" ); while( kv ) { idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); if ( ent ) { ent->SetShaderParm( spawnArgs.GetInt( "spin_parm", "4" ), spawnArgs.GetFloat( "spin_value", "0.5" ) ); } kv = spawnArgs.MatchPrefix( "cloud", kv ); } } } void hhSphereBoss::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { //overridden to allow custom wound code if ( !AI_CAN_DAMAGE && inflictor && spawnArgs.MatchPrefix("mineDamage") ) { hhFxInfo fxInfo; fxInfo.RemoveWhenDone( true ); if ( gameLocal.time >= nextShieldImpact ) { nextShieldImpact = gameLocal.time + int(spawnArgs.GetFloat( "shield_impact_freq", "0.4" ) * 1000); idVec3 offset; if ( attacker && inflictor ) { offset = (attacker->GetOrigin() - inflictor->GetOrigin()).ToNormal() * spawnArgs.GetFloat( "shield_impact_offset", "150" ); const char *defName = spawnArgs.GetString( "fx_shield_impact" ); idEntityFx *impactFx = SpawnFxLocal( defName, inflictor->GetOrigin() + offset, mat3_identity, &fxInfo, gameLocal.isClient ); if ( impactFx ) { impactFx->Bind( this, true ); } } } StartSound( "snd_shield_impact", SND_CHANNEL_BODY ); if ( inflictor && inflictor->IsType( idProjectile::Type ) ) { inflictor->PostEventMS( &EV_Remove, 0 ); } } else { hhFxInfo fxInfo; fxInfo.RemoveWhenDone( true ); if ( gameLocal.time >= nextShieldImpact ) { nextShieldImpact = gameLocal.time + int(spawnArgs.GetFloat( "shield_impact_freq", "0.4" ) * 1000); idVec3 offset; if ( attacker && inflictor ) { offset = (attacker->GetOrigin() - inflictor->GetOrigin()).ToNormal() * spawnArgs.GetFloat( "shield_impact_offset", "150" ); BroadcastFxInfoPrefixed( "fx_pain_impact", inflictor->GetOrigin() + offset, mat3_identity, &fxInfo ); } } } bool mine_damage = false; if ( !AI_CAN_DAMAGE && spawnArgs.MatchPrefix("mineDamage") != NULL ) { const idKeyValue *kv = spawnArgs.MatchPrefix("mineDamage"); while( kv && kv->GetValue().Length() ) { if ( !kv->GetValue().Icmp(damageDefName) ) { mine_damage = true; break; } kv = spawnArgs.MatchPrefix("mineDamage", kv); } if (!mine_damage) { return; } } if ( !mine_damage ) { hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); } if ( !AI_CAN_DAMAGE && health > 0 ) { SetState( GetScriptFunction( "state_Pain" ) ); SetWaitState( "" ); } } /* ===================== hhSphereBoss::Save ===================== */ void hhSphereBoss::Save( idSaveGame *savefile ) const { savefile->WriteInt( lastTargetPos.Num() ); savefile->WriteInt( nextShieldImpact ); for ( int i=0;iWriteVec3( lastTargetPos[i] ); } } /* ===================== hhSphereBoss::Restore ===================== */ void hhSphereBoss::Restore( idRestoreGame *savefile ) { int num = 0; savefile->ReadInt( num ); savefile->ReadInt( nextShieldImpact ); lastTargetPos.SetNum( num ); for ( int i=0;iReadVec3( lastTargetPos[i] ); } } void hhSphereBoss::AddLocalMatterWound( jointHandle_t jointNum, const idVec3 &localOrigin, const idVec3 &localNormal, const idVec3 &localDir, int damageDefIndex, const idMaterial *collisionMaterial ) { //overridden to remove woundmanager hook and use custom wound code is Damage() if ( AI_CAN_DAMAGE ) { return hhMonsterAI::AddLocalMatterWound( jointNum, localOrigin, localNormal, localDir, damageDefIndex, collisionMaterial ); } } #endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build