#include "precompiled.h" #pragma hdrstop #include "prey_local.h" //----------------------------------------------------- // hhNoClipEnt //----------------------------------------------------- CLASS_DECLARATION(idEntity, hhNoClipEnt) END_CLASS void hhNoClipEnt::Spawn( void ) { if(GetPhysics()) GetPhysics()->SetContents(0); } //----------------------------------------------------- // hhAINode //----------------------------------------------------- CLASS_DECLARATION( idEntity, hhAINode ) END_CLASS hhAINode::hhAINode( void ) { user.Clear(); } void hhAINode::Save( idSaveGame *savefile ) const { user.Save( savefile ); } void hhAINode::Restore( idRestoreGame *savefile ) { user.Restore( savefile ); } //----------------------------------------------------- // hhMonsterAI //----------------------------------------------------- idList hhMonsterAI::allSimpleMonsters; hhMonsterAI::hhMonsterAI() { lookOffset = ang_zero; shootTarget = NULL; bBossBar = false; spawnThinkFlags = 0; lastContactTime = gameLocal.time; nextSpeechTime = gameLocal.time; frozen = false; soundOnModel = false; } hhMonsterAI::~hhMonsterAI() { allSimpleMonsters.Remove(this); if ( bBossBar && spawnArgs.GetBool( "remove_bar_on_dissolve" ) ) { idPlayer *player = gameLocal.GetLocalPlayer(); if ( player && player->hud ) { bBossBar = false; player->hud->HandleNamedEvent("HideProgressBar"); player->hud->StateChanged(gameLocal.time); } } } void hhMonsterAI::Spawn() { AI_HAS_RANGE_ATTACK = spawnArgs.GetBool("has_range_attack", "0"); AI_HAS_MELEE_ATTACK = spawnArgs.GetBool("has_melee_attack", "0"); targetReaction.reactionIndex = -1; targetReaction.entity = NULL; allSimpleMonsters.AddUnique(this); bBindAxis = spawnArgs.GetBool( "bind_axis" ); bCanFall = spawnArgs.GetBool( "can_fall", "0" ); bSeeThroughPortals = spawnArgs.GetBool( "can_see_portals", "0" ); bBindOrient = spawnArgs.GetBool( "bind_orient", "0" ); hearingRange = spawnArgs.GetInt( "hearing_range", "1024" ); bCanWallwalk = spawnArgs.GetBool( "can_wallwalk", "0" ); fallDelay = spawnArgs.GetInt( "fall_delay" ); bOverrideKilledByGravityZones = spawnArgs.GetBool( "overrideKilledByGravityZones" ); bNeverTarget = spawnArgs.GetBool( "never_target", "0" ); bNoCombat = spawnArgs.GetBool( "no_combat", "0" ); const char *temp; if ( spawnArgs.GetString( "fx_custom_blood", "", &temp ) ) { bCustomBlood = true; } else { bCustomBlood = false; } nextSpiritProxyCheck = 0; nextTurnUpdate = 0; frozen = false; //handle initial rotation and sticking on wallwalk if ( !IsHidden() ) { PostEventSec( &MA_InitialWallwalk, 0.1f ); PostEventMS(&MA_EnemyOnSpawn, 10); } PostEventMS(&EV_PostSpawn, 0); if ( spawnArgs.GetInt( "wander_radius" ) ) { spawnOrigin = GetOrigin(); } // CJR: Clear the DDA values used for tracking damage inflicted on the player totalDDADamage = 0; } void hhMonsterAI::Event_PostSpawn() { CreateHealthTriggers(); //TODO: Move to character/girlfriend code when it exists // Spawn earrings for girlfriend idEntity *ent; const char *defName = spawnArgs.GetString("def_earring", NULL); if (defName && defName[0] && head.IsValid()) { const char *boneNameL = spawnArgs.GetString("earringboneL"); const char *boneNameR = spawnArgs.GetString("earringboneR"); idDict args; args.Clear(); args.Set( "origin", GetPhysics()->GetOrigin().ToString() ); ent = gameLocal.SpawnObject(defName, &args); if (ent) { ent->MoveToJoint(head.GetEntity(), boneNameL); ent->BindToJoint(head.GetEntity(), boneNameL, false); } ent = gameLocal.SpawnObject(defName, &args); if (ent) { ent->MoveToJoint(head.GetEntity(), boneNameR); ent->BindToJoint(head.GetEntity(), boneNameR, false); } } } bool hhMonsterAI::HasPathTo(const idVec3 &destPt) { if(!GetAAS()) // || !allowAAS) //TODO: support the allowAAS functionality return FALSE; idVec3 fromPos = GetPhysics()->GetOrigin(); int currAreaNum = PointReachableAreaNum(fromPos); int toAreaNum = PointReachableAreaNum(destPt); // Invalid #'s ? if(toAreaNum == 0 || currAreaNum == 0) return FALSE; aasPath_t path; return PathToGoal(path, currAreaNum, fromPos, toAreaNum, destPt); } /* ============================== hhMonsterAI::LinkScriptVariables(void) ============================== */ #define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) void hhMonsterAI::LinkScriptVariables(void) { idAI::LinkScriptVariables(); LinkScriptVariable( AI_HAS_RANGE_ATTACK ); // required for reaction system LinkScriptVariable( AI_HAS_MELEE_ATTACK ); // required for reaction system LinkScriptVariable( AI_USING_REACTION ); // TRUE if monster is currently using reaction LinkScriptVariable( AI_REACTION_FAILED ); // TRUE if the last reaction attempt failed (path blocked, exclusive problem, etc) LinkScriptVariable( AI_REACTION_ANIM ); LinkScriptVariable( AI_BACKWARD ); LinkScriptVariable( AI_STRAFE_LEFT ); LinkScriptVariable( AI_STRAFE_RIGHT ); LinkScriptVariable( AI_UPWARD ); LinkScriptVariable( AI_DOWNWARD ); LinkScriptVariable( AI_SHUTTLE_DOCKED ); LinkScriptVariable( AI_VEHICLE_ATTACK ); LinkScriptVariable( AI_VEHICLE_ALT_ATTACK ); LinkScriptVariable( AI_WALLWALK ); LinkScriptVariable( AI_FALLING ); LinkScriptVariable( AI_PATHING ); LinkScriptVariable( AI_TURN_DIR ); LinkScriptVariable( AI_FLY_NO_SEEK ); LinkScriptVariable( AI_FOLLOWING_PATH ); } void hhMonsterAI::Think( void ) { PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI); if (ai_skipThink.GetBool()) { //HUMANHEAD rww return; } idVec3 oldOrigin = physicsObj.GetOrigin(); idVec3 oldVelocity = physicsObj.GetLinearVelocity(); if ( thinkFlags & TH_THINK ) { // Update vehicle guns if(AI_VEHICLE && InVehicle()) { usercmd_t cmds; const signed char speed = 64; // In range [0..127] memset( &cmds, 0, sizeof(usercmd_t) ); cmds.buttons |= (AI_VEHICLE_ATTACK) ? BUTTON_ATTACK : 0; cmds.buttons |= (AI_VEHICLE_ALT_ATTACK) ? BUTTON_ATTACK_ALT : 0; if(AI_FORWARD) { cmds.forwardmove = speed; } else if(AI_BACKWARD) { cmds.forwardmove = -speed; } if(AI_STRAFE_RIGHT) { cmds.rightmove = speed; } else if(AI_STRAFE_LEFT) { cmds.rightmove = -speed; } if(AI_UPWARD) { cmds.upmove = speed; } else if(AI_DOWNWARD) { cmds.upmove = -speed; } GetVehicleInterfaceLocal()->BufferPilotCmds( &cmds, NULL ); AI_SHUTTLE_DOCKED = GetVehicleInterfaceLocal()->IsVehicleDocked(); } // clear out the enemy when he dies or is hidden idActor *enemyEnt = enemy.GetEntity(); if ( enemyEnt ) { if ( enemyEnt->IsType( hhSpiritProxy::Type ) ) { idPlayer *player = gameLocal.GetLocalPlayer(); if ( player && player->AI_DEAD ) { EnemyDead(); } } else if ( enemyEnt->health <= 0 ) { EnemyDead(); } } //HUMANHEAD: aob - vehicle updates our viewAxis if( !InVehicle() ) { current_yaw += deltaViewAngles.yaw; ideal_yaw = idMath::AngleNormalize180( ideal_yaw + deltaViewAngles.yaw ); deltaViewAngles.Zero(); viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3(); // Determine turn dir if (gameLocal.time > nextTurnUpdate) { AI_TURN_DIR = GetTurnDir(); nextTurnUpdate = gameLocal.time + 250; } } //HUMANHEAD END if ( num_cinematics ) { if ( !IsHidden() && torsoAnim.AnimDone( 0 ) ) { PlayCinematic(); } RunPhysics(); } else if ( !allowHiddenMovement && IsHidden() ) { // hidden monsters UpdateAIScript(); // HUMANHEAD pdm: Vehicle support } else if ( InVehicle() ) { UpdateEnemyPosition(); UpdateAIScript(); FlyMove(); // HUMANHEAD END } else { // clear the ik before we do anything else so the skeleton doesn't get updated twice walkIK.ClearJointMods(); // HUMANHEAD NLA physicsObj.ResetNumTouchEnt(0); // HUMANHEAD END switch( move.moveType ) { case MOVETYPE_DEAD : // dead monsters UpdateAIScript(); DeadMove(); break; case MOVETYPE_FLY : // flying monsters UpdateEnemyPosition(); UpdateAIScript(); FlyMove(); PlayChatter(); CheckBlink(); break; case MOVETYPE_STATIC : // static monsters UpdateEnemyPosition(); UpdateAIScript(); StaticMove(); PlayChatter(); CheckBlink(); break; case MOVETYPE_ANIM : // animation based movement UpdateEnemyPosition(); UpdateAIScript(); AnimMove(); PlayChatter(); CheckBlink(); break; case MOVETYPE_SLIDE : // velocity based movement UpdateEnemyPosition(); UpdateAIScript(); SlideMove(); PlayChatter(); CheckBlink(); break; } // HUMANHEAD NLA ClientImpacts(); // HUMANHEAD END } // clear pain flag so that we recieve any damage between now and the next time we run the script AI_PAIN = false; AI_SPECIAL_DAMAGE = 0; AI_PUSHED = false; } else if ( thinkFlags & TH_PHYSICS ) { RunPhysics(); } // HUMANHEAD jrm - need to call ticker function per aaron if (thinkFlags & TH_TICKER) { Ticker(); } if ( af_push_moveables ) { PushWithAF(); } if ( fl.hidden && allowHiddenMovement ) { // UpdateAnimation won't call frame commands when hidden, so call them here when we allow hidden movement animator.ServiceAnims( gameLocal.previousTime, gameLocal.time ); } UpdateMuzzleFlash(); UpdateAnimation(); UpdateParticles(); UpdateWounds(); Present(); UpdateDamageEffects(); LinkCombat(); CrashLand( oldOrigin, oldVelocity ); if(health > 0) { idStr tmp; if(ai_showNoAAS.GetBool() && spawnArgs.GetString("use_aas", "", tmp) && spawnArgs.GetBool("noaas_warning","1")) { if(!aas) { gameRenderWorld->DrawText("?", this->GetEyePosition() + idVec3(0.0f, 0.0f, 12.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); } } } if ( !AI_DEAD && bCanFall && !IsHidden() && !fl.isTractored ) { if ( physicsObj.HasContacts() || physicsObj.GetLinearVelocity().LengthSqr() < 4 ) { lastContactTime = gameLocal.time; if ( AI_FALLING ) { AI_FALLING = false; bCanFall = false; SetState( GetScriptFunction( "state_Idle" ) ); SetWaitState( "" ); } } else if ( !AI_FALLING && !af.IsActive() && physicsObj.GetLinearVelocity().LengthSqr() > 0 ) { if ( gameLocal.time - lastContactTime > fallDelay ) { AI_FALLING = true; SetState( GetScriptFunction( "state_Nothing" ) ); Event_AnimState(ANIMCHANNEL_TORSO, "Torso_Fall", 4); Event_AnimState(ANIMCHANNEL_LEGS, "Legs_Fall", 4); } } } //update wallwalk if ( bCanWallwalk && !fl.isTractored ) { trace_t TraceInfo; gameLocal.clip.TracePoint(TraceInfo, GetOrigin(), GetOrigin() + this->GetPhysics()->GetGravityNormal() * 50, GetPhysics()->GetClipMask(), this); if( TraceInfo.fraction < 1.0f && gameLocal.GetMatterType(TraceInfo, NULL) == SURFTYPE_WALLWALK ) { SetGravity( -TraceInfo.c.normal * DEFAULT_GRAVITY ); AI_WALLWALK = true; } else if ( AI_WALLWALK ) { SetGravity( idVec3(0,0,-DEFAULT_GRAVITY) ); AI_WALLWALK = false; } } else if ( AI_WALLWALK ) { SetGravity( idVec3(0,0,-DEFAULT_GRAVITY) ); AI_WALLWALK = false; } if ( bBossBar ) { idPlayer *player = gameLocal.GetLocalPlayer(); if ( player && player->hud ) { player->hud->SetStateFloat( "progress", idMath::ClampFloat( 0.0f, 1.0f, float(health) / (float)spawnHealth ) ); player->hud->StateChanged(gameLocal.time); player->hud->Redraw( gameLocal.realClientTime ); } } if( ai_debugBrain.GetInteger() > 0 && !IsHidden() ) { if ( enemy.IsValid() && enemy->GetHealth() > 0 ) { gameRenderWorld->DebugArrow( colorWhite, GetOrigin(), enemy->GetOrigin(), 10 ); float dist = ( GetPhysics()->GetOrigin() - enemy->GetPhysics()->GetOrigin() ).LengthFast(); gameRenderWorld->DrawText( va("%i", int(dist)), this->GetEyePosition() + idVec3(0.0f, 0.0f, 60.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); } if ( state ) { if ( physicsObj.GetClipMask() == 0 ) { gameRenderWorld->DrawText(state->Name(), GetEyePosition() + idVec3(0.0f, 0.0f, 40.0f), 0.75f, colorRed, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); } else { gameRenderWorld->DrawText(state->Name(), GetEyePosition() + idVec3(0.0f, 0.0f, 40.0f), 0.75f, colorGreen, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); } } gameRenderWorld->DrawText(torsoAnim.state, this->GetEyePosition() + idVec3(0.0f, 0.0f, 20.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); gameRenderWorld->DrawText(legsAnim.state, this->GetEyePosition() + idVec3(0.0f, 0.0f, 0.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); if ( !walkIK.IsActivated() ) { gameRenderWorld->DrawText("ik disabled", GetEyePosition() + idVec3(0.0f, 0.0f, -20.0f), 0.75f, colorRed, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); } } } void hhMonsterAI::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { //we don't want monsters pushed around by the player, projectiles, or splashDamange, so early out if they are. if( !af.IsActive() ) { if ( ent && (ent->IsType( hhProjectile::Type ) || ent->IsType( idWorldspawn::Type ) || ent->IsType( idPlayer::Type )) ) { return; } } af.GetPhysics()->UpdateTime( gameLocal.GetTime() ); BecomeActive(TH_THINK); idAI::ApplyImpulse( ent, id, point, force ); } // // hhMonsterAI::CheckValidReaction // Helper function for using reactions. // bool hhMonsterAI::CheckValidReaction() { hhReaction *reaction = targetReaction.GetReaction(); if( !reaction || !reaction->causeEntity.IsValid() ) { return false; } return true; } // // hhMonsterAI::FinishReaction // Called once a reaction has been used/failed. If it failed, set the failed flag and exit. // Otherwise, set any 'finish_key' we have. // void hhMonsterAI::FinishReaction( bool bFailed ) { AI_USING_REACTION = false; if( bFailed ) { AI_REACTION_FAILED = true; } else { hhReaction *reaction = targetReaction.GetReaction(); if( reaction && reaction->desc->finish_key.Length() ) { spawnArgs.Set( reaction->desc->finish_key.c_str(), reaction->desc->finish_val.c_str() ); } } //monster is finished with this reaction. let others use it if ( targetReaction.entity.IsValid() ) { targetReaction.entity->spawnArgs.Set( "react_inuse", "0" ); } } void hhMonsterAI::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { if ( AI_DEAD ) { AI_DAMAGE = true; return; } if ( bBossBar && spawnArgs.GetBool( "remove_bar_on_death" ) ) { idPlayer *player = gameLocal.GetLocalPlayer(); if ( player && player->hud ) { bBossBar = false; player->hud->HandleNamedEvent("HideProgressBar"); player->hud->StateChanged(gameLocal.time); } } HandleNoGore(); idAI::Killed(inflictor, attacker, damage, dir, location); fl.noPortal = 0; // CJR: Set so that killed monsters never collide with portals SendDamageToDDA(); // CJR DDA: Send any accumulated damage to the dda system // General non-item 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); // Spin to correct heading if(newEnt->IsType(hhMonsterAI::Type)) { hhMonsterAI *newAI = static_cast(newEnt); newAI->current_yaw = current_yaw; newAI->ideal_yaw = ideal_yaw; } 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 ); } } void hhMonsterAI::Event_Remove( void ) { if( InVehicle() ) { GetVehicleInterface()->GetVehicle()->EjectPilot(); } idAI::Event_Remove(); } void hhMonsterAI::EnterVehicle( hhVehicle* vehicle ) { if (!vehicle->WillAcceptPilot(this)) { return; } spawnArgs.Set( "use_aas", spawnArgs.GetString( "aas_shuttle", "aasDroid" ) ); SetAAS(); idAI::EnterVehicle( vehicle ); PostEventSec( &MA_SetVehicleState, 0.1f ); } bool hhMonsterAI::TestMelee( void ) const { trace_t trace; idActor *enemyEnt = enemy.GetEntity(); if ( !enemyEnt || !melee_range ) { return false; } //FIXME: make work with gravity vector idVec3 org = physicsObj.GetOrigin(); const idBounds &myBounds = physicsObj.GetBounds(); idBounds bounds; idBounds meleeBounds( spawnArgs.GetVector( "melee_boundmin", "0 0 0" ), spawnArgs.GetVector( "melee_boundmax", "0 0 0" ) ); if ( meleeBounds != bounds_zero ) { //check custom rotated meleebound idBox meleeBox( meleeBounds, org, renderEntity.axis ); idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds(); enemyBounds.TranslateSelf( enemyEnt->GetPhysics()->GetOrigin() ); idBox enemyBox( enemyBounds ); if ( ai_debugMove.GetBool() ) { gameRenderWorld->DebugBox( colorYellow, meleeBox ); } if ( !enemyBox.IntersectsBox( meleeBox ) ) { return false; } } else { // expand the bounds out by our melee range bounds[0][0] = -melee_range; bounds[0][1] = -melee_range; bounds[0][2] = myBounds[0][2] - 4.0f; bounds[1][0] = melee_range; bounds[1][1] = melee_range; bounds[1][2] = myBounds[1][2] + 4.0f; bounds.TranslateSelf( org ); idVec3 enemyOrg = enemyEnt->GetPhysics()->GetOrigin(); idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds(); enemyBounds.TranslateSelf( enemyOrg ); if ( ai_debugMove.GetBool() ) { gameRenderWorld->DebugBounds( colorYellow, bounds, vec3_zero, gameLocal.msec ); } if ( !bounds.IntersectsBounds( enemyBounds ) ) { return false; } } idVec3 start = GetEyePosition(); idVec3 end = enemyEnt->GetEyePosition(); gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); if ( ( trace.fraction == 1.0f ) || ( gameLocal.GetTraceEntity( trace ) == enemyEnt ) ) { return true; } return false; } bool hhMonsterAI::MoveToPosition( const idVec3 &pos, bool enemyBlocks ) { if(AI_VEHICLE && InVehicle()) { GetVehicleInterfaceLocal()->ThrustTowards( pos, 1.0f ); return true; } else { return idAI::MoveToPosition( pos, enemyBlocks ); } } bool hhMonsterAI::TurnToward( const idVec3 &pos ) { if (AI_VEHICLE && InVehicle()) { GetVehicleInterfaceLocal()->OrientTowards( pos, 0.5 ); return true; } else { return idAI::TurnToward( pos ); } } bool hhMonsterAI::CanSee( idEntity *ent, bool useFov ) { trace_t tr; idVec3 eye; idVec3 toPos; if ( ent->IsHidden() ) { return false; } if ( ent->IsType( idActor::Type ) ) { idActor *act = static_cast(ent); // If this actor is in a vehicle, look at the vehicle, not the actor if(act->InVehicle()) { ent = act->GetVehicleInterface()->GetVehicle(); } } if ( ent->IsType( idActor::Type ) ) { toPos = ( ( idActor * )ent )->GetEyePosition(); } else { toPos = ent->GetPhysics()->GetOrigin(); } if ( useFov && !CheckFOV( toPos ) ) { return false; } eye = GetEyePosition(); if ( InVehicle() ) { gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, GetVehicleInterface()->GetVehicle() ); // HUMANHEAD JRM if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { return true; } } else { gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, this ); // HUMANHEAD JRM if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { return true; } else if ( bSeeThroughPortals && aas ) { #if 0 // jmarshall - AI portals shootTarget = NULL; int myArea = gameRenderWorld->PointInArea( GetOrigin() ); int numPortals = gameRenderWorld->NumGamePortalsInArea( myArea ); if ( numPortals > 0 ) { int enemyArea = gameRenderWorld->PointInArea( ent->GetOrigin() ); for ( int i=0;iGetSoundPortal( myArea, i ).areas[0] == enemyArea ) { //find the portal and set it as this monster's shoottarget idEntity *spawnedEnt = NULL; for( spawnedEnt = gameLocal.spawnedEntities.Next(); spawnedEnt != NULL; spawnedEnt = spawnedEnt->spawnNode.Next() ) { if ( !spawnedEnt->IsType( hhPortal::Type ) ) { continue; } if ( gameRenderWorld->PointInArea( spawnedEnt->GetOrigin() ) == myArea) { shootTarget = spawnedEnt; return true; } } } } } #endif } } return false; } idPlayer* hhMonsterAI::GetClosestPlayer(void) { idEntity *closestEnt = NULL; float closestDist = idMath::INFINITY; for(int i=0;iIsType(idPlayer::Type)); float l = (ent->GetOrigin()-GetOrigin()).Length(); if(l < closestDist || !closestEnt) { closestDist = l; closestEnt = ent; } } } return static_cast(closestEnt); } void hhMonsterAI::Show() { if ( spawnThinkFlags != 0 ) { int temp = spawnThinkFlags; spawnThinkFlags = 0; BecomeActive( temp ); } idAI::Show(); PostEventMS(&MA_EnemyOnSpawn, 10); Event_InitialWallwalk(); } bool hhMonsterAI::GetFacePosAngle( const idVec3 &pos, float &delta ) { float diff; float angle1; float angle2; idVec3 sourceOrigin; idVec3 targetOrigin; sourceOrigin = GetPhysics()->GetOrigin(); targetOrigin = pos;//target->GetPhysics()->GetOrigin(); angle1 = DEG2RAD( GetGravViewAxis()[0].ToYaw() ); // VIEWAXIS_TO_GETGRAVVIEWAXIS angle2 = hhUtils::PointToAngle( targetOrigin.x - sourceOrigin.x, targetOrigin.y - sourceOrigin.y ); if(angle2 > angle1) { diff = angle2 - angle1; if( diff > DEG2RAD(180.0f) ) { delta = DEG2RAD(359.9f) - diff; return false; } else { delta = diff; return true; } } else { diff = angle1 - angle2; if( diff > DEG2RAD(180.0f) ) { delta = DEG2RAD(359.9f) - diff; return true; } else { delta = diff; return false; } } } void hhMonsterAI::Distracted( idActor *newEnemy ) { SetEnemy( newEnemy ); } void hhMonsterAI::SetEnemy( idActor *newEnemy ) { idAI::SetEnemy( newEnemy ); } idVec3 hhMonsterAI::GetTouchPos(idEntity *ent, const hhReactionDesc *desc ) { assert(ent != NULL); assert(desc != NULL); idVec3 pos = ent->GetOrigin(); idVec3 offset = desc->touchOffsets.GetVector("all", "0 0 0"); if ( offset == vec3_zero ) { // Each monster def type has its own offset etc. // touchoffset_monster_hunter offset = desc->touchOffsets.GetVector(GetEntityDefName(), "0 0 0"); } idStr touchDirType = desc->touchDir; // When touching this ent, move to it as if it were 'cover' if( touchDirType == idStr("cover") && enemy.IsValid() ) { idVec3 goalPos = ent->GetOrigin() + offset.x * (ent->GetOrigin() - GetEnemy()->GetOrigin()).ToNormal(); goalPos.z += offset.z; return goalPos; } // Move directly toward this ent from our pos else if(touchDirType == idStr("direct")) { idVec3 dir = GetOrigin() - pos; dir.Normalize(); offset *= dir.ToMat3(); return pos + offset; } else if(touchDirType == idStr("object")) { ent->GetFloorPos( 64.f, pos ); idVec3 dir = GetOrigin() - pos; dir.Normalize(); return pos + (dir * offset.x); } else { offset *= ent->GetAxis(); return pos + offset; } } bool hhMonsterAI::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { if ( af.IsActive() ) { af.GetPhysicsToVisualTransform( origin, axis ); return true; } origin = modelOffset; if ( GetBindMaster() && bindJoint != INVALID_JOINT ) { idMat3 masterAxis; idVec3 masterOrigin; GetMasterPosition( masterOrigin, masterAxis ); axis = physicsObj.localAxis * masterAxis; origin = masterOrigin + physicsObj.GetLocalOrigin() * masterAxis; } else if ( ( InVehicle() || bBindOrient ) && GetBindMaster() ) { axis = GetBindMaster()->GetAxis(); } else { axis = viewAxis; } return true; } void hhMonsterAI::UpdateModelTransform( void ) { idVec3 origin; idMat3 axis; if ( GetPhysicsToVisualTransform( origin, axis ) ) { if ( bBindAxis && GetBindMaster() ) { renderEntity.axis = GetBindMaster() ->GetAxis(); } else { renderEntity.axis = axis * GetPhysics()->GetAxis(); } if ( GetBindMaster() && bindJoint != INVALID_JOINT ) { if ( head.IsValid() && head->GetPhysics() ) { head->GetPhysics()->Evaluate(gameLocal.time-gameLocal.previousTime, gameLocal.time); } renderEntity.origin = origin; } else { if ( GetBindMaster() && head.IsValid() && head->GetPhysics() ) { head->GetPhysics()->Evaluate(gameLocal.time-gameLocal.previousTime, gameLocal.time); } renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis; } } else { renderEntity.axis = GetPhysics()->GetAxis(); renderEntity.origin = GetPhysics()->GetOrigin(); } } void hhMonsterAI::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 { SetAxis( GetBindMaster()->GetAxis() ); } } if ( UpdateAnimationControllers() ) { BecomeActive( TH_ANIMATE ); } UpdateVisuals(); } void hhMonsterAI::CreateHealthTriggers() { const char keyPrefix[] = "health_percent_trigger_"; int keyPrefixLen = strlen(keyPrefix); const idKeyValue *kv = spawnArgs.MatchPrefix(keyPrefix, NULL); healthTriggers.Clear(); while(kv) { hhMonsterHealthTrigger t; idStr k = kv->GetKey(); idStr perct = k.Right(k.Length() - strlen(keyPrefix)); float p = float(atoi(perct.c_str())) * 0.01f; t.healthThresh = int(float(health) * p); t.triggerEnt = gameLocal.FindEntity(kv->GetValue().c_str()); if(!t.triggerEnt.GetEntity()) { gameLocal.Warning("%s specified %s key with entity \"%s\", but that entity does not exist!", name.c_str(), kv->GetKey().c_str(), kv->GetValue().c_str()); } healthTriggers.Append(t); kv = spawnArgs.MatchPrefix(keyPrefix, kv); } } void hhMonsterAI::UpdateHealthTriggers(int oldHealth, int currHealth) { // Trigger them for(int i=0;iProcessEvent(&EV_Activate, this); healthTriggers[i].triggerCount++; } } } } // // Damage() // void hhMonsterAI::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { // Return if we don't take damage if ( !fl.takedamage ) { return; } if ( spawnArgs.GetBool( "noPlayerDamage", "0" ) ) { if ( attacker && attacker->IsType( idPlayer::Type ) ) { return; } } int oldHealth = health; idAI::Damage(inflictor, attacker, dir, damageDefName, damageScale, location); UpdateHealthTriggers(oldHealth, health); if ( bCustomBlood && inflictor ) { hhFxInfo fxInfo; fxInfo.RemoveWhenDone( true ); BroadcastFxInfoPrefixed( "fx_custom_blood", inflictor->GetOrigin(), mat3_identity, &fxInfo ); } const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); if ( AI_DEAD && damageDef ) { if ( damageDef->GetBool( "ice" ) && spawnArgs.GetBool( "can_freeze", "0" ) ) { SetSkinByName( spawnArgs.GetString( "skin_freeze" ) ); if( af.IsLoaded() ) { af.GetPhysics()->SetSelfCollision(false); af.GetPhysics()->SetContactFrictionScale(0); af.GetPhysics()->SetTimeScaleRamp(0,2); for(int i=0; i < af.GetPhysics()->GetNumConstraints(); i++) { idAFConstraint* constraint = af.GetPhysics()->GetConstraint(i); switch( constraint->GetType() ) { case CONSTRAINT_BALLANDSOCKETJOINT: static_cast(constraint)->SetFriction(200.0f); static_cast(constraint)->SetNoLimit(); break; case CONSTRAINT_UNIVERSALJOINT: static_cast(constraint)->SetFriction(200.0f); static_cast(constraint)->SetNoLimit(); break; case CONSTRAINT_HINGE: static_cast(constraint)->SetFriction(200.0f); static_cast(constraint)->SetNoLimit(); break; } } } spawnArgs.Set("fx_deatheffect", spawnArgs.GetString( "fx_ice" )); spawnArgs.Set("produces_splats", "0"); if( modelDefHandle > 0 ) gameRenderWorld->RemoveDecals( modelDefHandle ); //SetDeformation(DEFORMTYPE_DEATHEFFECT, gameLocal.time + 5000, 12000); SetShaderParm(SHADERPARM_TIME_OF_DEATH, MS2SEC(gameLocal.time+100000)); CancelEvents( &EV_Dispose ); PostEventSec( &EV_Dispose, 5 ); } if ( damageDef->GetBool( "burn" ) && !spawnArgs.GetBool( "no_burn" ) ) { SetSkinByName( spawnArgs.GetString( "skin_burn" ) ); spawnArgs.Set("fx_deatheffect", spawnArgs.GetString( "fx_burn" )); if( modelDefHandle > 0 ) gameRenderWorld->RemoveDecals( modelDefHandle ); CancelEvents( &EV_Dispose ); PostEventSec( &EV_Dispose, 0 ); } if ( damageDef->GetBool( "acid" ) && spawnArgs.GetBool( "acidburn" ) ) { SetSkinByName( spawnArgs.GetString( "skin_acidburn" ) ); SetDeformation(DEFORMTYPE_DEATHEFFECT, gameLocal.time + 2500, 8000); // starttime, duration PostEventSec( &EV_StartSound, 1.5f, "snd_acid", SND_CHANNEL_ANY, 1 ); spawnArgs.Set("fx_deatheffect", spawnArgs.GetString( "fx_acid" )); spawnArgs.Set("mtr_splat1", spawnArgs.GetString( "mtr_acidsplat" )); spawnArgs.Delete("mtr_splat2"); spawnArgs.Delete("mtr_splat3"); spawnArgs.Delete("mtr_splat4"); spawnArgs.Set("keepDecals", "1"); CancelEvents( &EV_Dispose ); PostEventSec( &EV_Dispose, 1.5 ); } } } bool hhMonsterAI::NearEnoughTouchPos( idEntity* ent, const idVec3& targetPos, idBounds& bounds ) { bounds.TranslateSelf( targetPos ); idBounds bnds( idVec3(-16.f, -16.f, -8.f), idVec3(16.f, 16.f, 64.f) ); bnds.TranslateSelf( physicsObj.GetOrigin() ); //uncomment these to show debug lines // gameRenderWorld->DebugBounds( colorOrange, bounds ); // gameRenderWorld->DebugArrow( colorRed, targetPos, targetPos + idVec3(0, 0, 10), 5 ); // gameRenderWorld->DebugBounds( colorRed, bnds ); if( bnds.IntersectsBounds( bounds ) ) { return true; } return false; } bool hhMonsterAI::GetTouchPosBound( const hhReactionDesc *desc, idBounds & bnds ) { idStr minName, maxName; idVec3 min, max; minName = va("min_%s", GetEntityDefName()); maxName = va("max_%s", GetEntityDefName()); if( !desc->touchOffsets.GetVector(minName.c_str(), "0 0 0", min) || !desc->touchOffsets.GetVector(maxName.c_str(), "0 0 0", max) ) { return false; } min -= idVec3( 16.f, 16.f, 8.f ); max += idVec3( 16.f, 16.f, 64.f ); bnds.Clear(); bnds[ 0 ] = min; bnds[ 1 ] = max; return true; } int hhMonsterAI::EvaluateReaction( const hhReaction *react ) { // Volume/Distance // If a volume in specified, then use that as our spatial check float distSq = ( react->causeEntity->GetOrigin() - GetOrigin() ).LengthSqr(); if( react->desc->listenerVolumes.Num() > 0 ) { int count = 0; int i; for( i = 0; i < react->desc->listenerVolumes.Num(); i++ ) { assert( react->desc->listenerVolumes[ i ]->GetPhysics() != NULL ); assert( GetPhysics() != NULL ); if( react->desc->listenerVolumes[ i ]->GetPhysics()->GetAbsBounds().IntersectsBounds( GetPhysics()->GetAbsBounds() ) ) { //MDC-TODO: Draw bounds debugging info... count++; //one is good enough to continue - no need to check the rest break; } } // If we are in ZERO of the listener volumes, then we can bail because this reaction doesn't apply to us if( count == 0 ) { return 0; } } if ( react->desc->effectVolumes.Num() > 0 && GetEnemy() ) { int count = 0; for( int i = 0; i < react->desc->effectVolumes.Num(); i++ ) { assert( react->desc->effectVolumes[ i ]->GetPhysics() != NULL ); assert( GetPhysics() != NULL ); if( react->desc->effectVolumes[ i ]->GetPhysics()->GetAbsBounds().IntersectsBounds( GetEnemy()->GetPhysics()->GetAbsBounds() ) ) { count++; //one is good enough to return false - no need to check the rest break; } } // If enemy is in ZERO of the effect volumes, then we can bail because this reaction doesn't apply to us if( count == 0 ) { return 0; } } if( react->desc->listenerRadius > 0.f || react->desc->listenerMinRadius > 0.f ) { // TOO FAR if( react->desc->listenerRadius > 0.f && distSq > react->desc->listenerRadius * react->desc->listenerRadius ) { return 0; } // TOO CLOSE if( react->desc->listenerMinRadius > 0.f && distSq < react->desc->listenerMinRadius * react->desc->listenerMinRadius ) { return 0; } } // Path-finding if( react->desc->CauseRequiresPathfinding() ) { if( !HasPathTo( GetTouchPos(react->causeEntity.GetEntity(), react->desc ) ) ) { //MDC-TODO: draw path debugging return 0; } else { //MDC-TODO: draw path debugging } } // Can-See if( react->desc->flags & hhReactionDesc::flagReq_CanSee ) { if( react->causeEntity.IsValid() ) { if( !CanSee(react->causeEntity.GetEntity(), TRUE) ) { return 0; } } } return 100; } int hhMonsterAI::ReactionTo( const idEntity *ent ) { if ( bNoCombat ) { return ATTACK_IGNORE; } const idActor *actor = static_cast( ent ); if( actor && actor->IsType(hhDeathProxy::Type) ) { return ATTACK_IGNORE; } //only attack spiritwalking players if they hurt me if ( ent->IsType( hhPlayer::Type ) ) { const hhPlayer *player = static_cast( ent ); if ( nextSpiritProxyCheck == 0 && player && player->IsSpiritWalking() ) { nextSpiritProxyCheck = gameLocal.time + SEC2MS(2); return ATTACK_ON_DAMAGE; } } if ( ent->IsType( hhSpiritProxy::Type ) ) { if ( gameLocal.time > nextSpiritProxyCheck ) { nextSpiritProxyCheck = 0; //attack spiritproxy on sight if we have no enemy or if its closer than our current enemy if ( enemy.IsValid() && enemy->IsType( hhPlayer::Type ) ) { float distToEnemy = (enemy->GetOrigin() - GetOrigin()).LengthSqr(); float distToProxy = (ent->GetOrigin() - GetOrigin()).LengthSqr(); if ( distToProxy < distToEnemy ) { return ATTACK_ON_SIGHT; } else { //HUMANHEAD jsh PCF 4/29/06 fixed logic error with spiritproxy checking return ATTACK_IGNORE; } } else { return ATTACK_ON_SIGHT; } } else { return ATTACK_IGNORE; } } if ( ent->IsType( hhMonsterAI::Type ) ) { const hhMonsterAI *entAI = static_cast( ent ); if ( entAI && entAI->bNeverTarget ) { return ATTACK_IGNORE; } } return idAI::ReactionTo( ent ); } bool hhMonsterAI::UpdateAnimationControllers( void ) { idVec3 local; idVec3 focusPos; idVec3 left; idVec3 dir; idVec3 orientationJointPos; idVec3 localDir; idAngles newLookAng; idAngles diff; idMat3 mat; idMat3 axis; idMat3 orientationJointAxis; idAFAttachment *headEnt = head.GetEntity(); idVec3 eyepos; idVec3 pos; int i; idAngles jointAng; float orientationJointYaw; if ( AI_DEAD ) { return idActor::UpdateAnimationControllers(); } if ( orientationJoint == INVALID_JOINT ) { orientationJointAxis = viewAxis; orientationJointPos = physicsObj.GetOrigin(); orientationJointYaw = current_yaw; } else { GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis ); orientationJointYaw = orientationJointAxis[ 2 ].ToYaw(); orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3(); } if ( ai_debugBrain.GetBool() ) { gameRenderWorld->DebugArrow( colorCyan, orientationJointPos, orientationJointPos + orientationJointAxis[0] * 64.0, 10, 1 ); } if ( focusJoint != INVALID_JOINT ) { if ( headEnt ) { headEnt->GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis ); } else { // JRMMERGE_GRAVAXIS - What about GetGravAxis() are we still using/needing that? GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis ); } eyeOffset.z = eyepos.z - physicsObj.GetOrigin().z; } else { eyepos = GetEyePosition(); } if ( headEnt ) { CopyJointsFromBodyToHead(); } // Update the IK after we've gotten all the joint positions we need, but before we set any joint positions. // Getting the joint positions causes the joints to be updated. The IK gets joint positions itself (which // are already up to date because of getting the joints in this function) and then sets their positions, which // forces the heirarchy to be updated again next time we get a joint or present the model. If IK is enabled, // or if we have a seperate head, we end up transforming the joints twice per frame. Characters with no // head entity and no ik will only transform their joints once. Set g_debuganim to the current entity number // in order to see how many times an entity transforms the joints per frame. idActor::UpdateAnimationControllers(); idEntity *focusEnt = focusEntity.GetEntity(); //HUMANHEAD jsh allow eyefocus independent from allowJointMod if ( ( !allowJointMod && !allowEyeFocus ) || ( gameLocal.time >= focusTime && focusTime != -1 ) || GetPhysics()->GetGravityNormal() != idVec3( 0,0,-1) ) { focusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 512.0f; } else if ( focusEnt == NULL ) { // keep looking at last position until focusTime is up focusPos = currentFocusPos; } else if ( focusEnt == enemy.GetEntity() ) { focusPos = lastVisibleEnemyPos + lastVisibleEnemyEyeOffset - eyeVerticalOffset * enemy.GetEntity()->GetPhysics()->GetGravityNormal(); } else if ( focusEnt->IsType( idActor::Type ) ) { focusPos = static_cast( focusEnt )->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal(); } else { focusPos = focusEnt->GetPhysics()->GetOrigin(); } currentFocusPos = currentFocusPos + ( focusPos - currentFocusPos ) * eyeFocusRate; // determine yaw from origin instead of from focus joint since joint may be offset, which can cause us to bounce between two angles dir = focusPos - orientationJointPos; newLookAng.yaw = idMath::AngleNormalize180( dir.ToYaw() - orientationJointYaw ); newLookAng.roll = 0.0f; newLookAng.pitch = 0.0f; newLookAng += lookOffset; #if 0 gameRenderWorld->DebugLine( colorRed, orientationJointPos, focusPos, gameLocal.msec ); gameRenderWorld->DebugLine( colorYellow, orientationJointPos, orientationJointPos + orientationJointAxis[ 0 ] * 32.0f, gameLocal.msec ); gameRenderWorld->DebugLine( colorGreen, orientationJointPos, orientationJointPos + newLookAng.ToForward() * 48.0f, gameLocal.msec ); #endif //JRMMERGE_GRAVAXIS: This changed to much to merge, see if you can get your monsters on planets changes back in here. I'll leave both versions #if OLD_CODE GetGravViewAxis().ProjectVector( dir, localDir ); // HUMANHEAD JRM: VIEWAXIS_TO_GETGRAVVIEWAXIS lookAng.yaw = idMath::AngleNormalize180( localDir.ToYaw() ); lookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ); lookAng.roll = 0.0f; #else // determine pitch from joint position dir = focusPos - eyepos; if ( ai_debugBrain.GetBool() ) { gameRenderWorld->DebugArrow( colorYellow, eyepos, eyepos + dir, 10, 1 ); } dir.NormalizeFast(); orientationJointAxis.ProjectVector( dir, localDir ); newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ) + lookOffset.pitch; newLookAng.roll = 0.0f; #endif diff = newLookAng - lookAng; if ( eyeAng != diff ) { eyeAng = diff; eyeAng.Clamp( eyeMin, eyeMax ); idAngles angDelta = diff - eyeAng; if ( !angDelta.Compare( ang_zero, 0.1f ) ) { alignHeadTime = gameLocal.time; } else { alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime; } } if ( idMath::Fabs( newLookAng.yaw ) < 0.1f ) { alignHeadTime = gameLocal.time; } if ( ( gameLocal.time >= alignHeadTime ) || ( gameLocal.time < forceAlignHeadTime ) ) { alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime; destLookAng = newLookAng; destLookAng.Clamp( lookMin, lookMax ); } diff = destLookAng - lookAng; if ( ( lookMin.pitch == -180.0f ) && ( lookMax.pitch == 180.0f ) ) { if ( ( diff.pitch > 180.0f ) || ( diff.pitch <= -180.0f ) ) { diff.pitch = 360.0f - diff.pitch; } } if ( ( lookMin.yaw == -180.0f ) && ( lookMax.yaw == 180.0f ) ) { if ( diff.yaw > 180.0f ) { diff.yaw -= 360.0f; } else if ( diff.yaw <= -180.0f ) { diff.yaw += 360.0f; } } lookAng = lookAng + diff * headFocusRate; lookAng.Normalize180(); jointAng.roll = 0.0f; if ( allowJointMod ) { for( i = 0; i < lookJoints.Num(); i++ ) { jointAng.pitch = 0; jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw; animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); idMat3 pitchRot; hhMath::BuildRotationMatrix( DEG2RAD(-lookAng.pitch * lookJointAngles[ i ].pitch), 0, pitchRot ); animator.GetJointLocalTransform( lookJoints[ i ], gameLocal.time, pos, mat ); animator.SetJointAxis( lookJoints[ i ], JOINTMOD_LOCAL_OVERRIDE, pitchRot * mat ); } } if ( move.moveType == MOVETYPE_FLY ) { // lean into turns AdjustFlyingAngles(); } if ( headEnt ) { idAnimator *headAnimator = headEnt->GetAnimator(); // HUMANHEAD pdm: Added support for look joints in head entities if ( allowJointMod ) { for( i = 0; i < headLookJoints.Num(); i++ ) { jointAng.pitch = lookAng.pitch * headLookJointAngles[ i ].pitch; jointAng.yaw = lookAng.yaw * headLookJointAngles[ i ].yaw; headAnimator->SetJointAxis( headLookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); } } // HUMANHEAD END if ( allowEyeFocus ) { idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); idMat3 headTranspose = headEnt->GetPhysics()->GetAxis().Transpose(); axis = eyeAxis * orientationJointAxis; left = axis[ 1 ] * eyeHorizontalOffset; eyepos -= headEnt->GetPhysics()->GetOrigin(); headAnimator->SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose ); headAnimator->SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f - left ) * headTranspose ); //if ( ai_debugMove.GetBool() ) { // gameRenderWorld->DebugLine( colorRed, orientationJointPos, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose, gameLocal.msec ); //} } else { headEnt->BecomeActive( TH_ANIMATE ); headAnimator->ClearJoint( leftEyeJoint ); headAnimator->ClearJoint( rightEyeJoint ); } } else { if ( allowEyeFocus ) { idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); axis = eyeAxis * orientationJointAxis; left = axis[ 1 ] * eyeHorizontalOffset; eyepos += axis[ 0 ] * 64.0f - physicsObj.GetOrigin(); animator.SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + left ); animator.SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos - left ); } else { animator.ClearJoint( leftEyeJoint ); animator.ClearJoint( rightEyeJoint ); } } //HUMANHEAD pdm jawflap hhAnimator *theAnimator; if (head.IsValid()) { theAnimator = head->GetAnimator(); } else { theAnimator = GetAnimator(); } JawFlap(theAnimator); //END HUMANHEAD return true; } //overridden to allow attack nodes farther away than our current enemydistance void hhMonsterAI::Event_GetCombatNode( void ) { int i; float dist; idEntity *targetEnt; idCombatNode *node; float bestDist; idCombatNode *bestNode; idActor *enemyEnt = enemy.GetEntity(); if ( !targets.Num() ) { // no combat nodes idThread::ReturnEntity( NULL ); return; } if ( !enemyEnt || !EnemyPositionValid() ) { // don't return a combat node if we don't have an enemy or // if we can see he's not in the last place we saw him idThread::ReturnEntity( NULL ); return; } // find the closest attack node that can see our enemy and is closer than our enemy bestNode = NULL; const idVec3 &myPos = physicsObj.GetOrigin(); bestDist = 9999999.0f ; for( i = 0; i < targets.Num(); i++ ) { targetEnt = targets[ i ].GetEntity(); if ( !targetEnt || !targetEnt->IsType( idCombatNode::Type ) ) { continue; } node = static_cast( targetEnt ); if ( !node->IsDisabled() && node->EntityInView( enemyEnt, lastVisibleEnemyPos ) ) { idVec3 org = node->GetPhysics()->GetOrigin(); dist = ( myPos - org ).LengthSqr(); if ( dist < bestDist ) { bestNode = node; bestDist = dist; } } } idThread::ReturnEntity( bestNode ); } void hhMonsterAI::FlyTurn( void ) { //overridden for vehicle code changes 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 ) { if ( InVehicle() ) { if ( move.goalEntity.IsValid() ) { TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); } else { TurnToward( GetVehicleInterface()->GetVehicle()->GetPhysics()->GetLinearVelocity().ToYaw() ); } } else { if ( vel.ToVec2().LengthSqr() > 0.1f ) { TurnToward( vel.ToYaw() ); } } } } Turn(); } //overridden to allow custom hearingRange void hhMonsterAI::UpdateEnemyPosition( void ) { idActor *enemyEnt = enemy.GetEntity(); int enemyAreaNum; int areaNum; aasPath_t path; predictedPath_t predictedPath; idVec3 enemyPos; bool onGround; if ( !enemyEnt ) { return; } const idVec3 &org = physicsObj.GetOrigin(); if ( move.moveType == MOVETYPE_FLY ) { enemyPos = enemyEnt->GetPhysics()->GetOrigin(); onGround = true; } else { onGround = enemyEnt->GetFloorPos( 64.0f, enemyPos ); if ( enemyEnt->OnLadder() ) { onGround = false; } } if ( onGround ) { // when we don't have an AAS, we can't tell if an enemy is reachable or not, // so just assume that he is. if ( !aas ) { enemyAreaNum = 0; lastReachableEnemyPos = enemyPos; } else { enemyAreaNum = PointReachableAreaNum( enemyPos, 1.0f ); if ( enemyAreaNum ) { areaNum = PointReachableAreaNum( org ); if ( PathToGoal( path, areaNum, org, enemyAreaNum, enemyPos ) ) { lastReachableEnemyPos = enemyPos; } } } } AI_ENEMY_IN_FOV = false; AI_ENEMY_VISIBLE = false; if ( CanSee( enemyEnt, false ) ) { AI_ENEMY_VISIBLE = true; if ( CheckFOV( enemyEnt->GetPhysics()->GetOrigin() ) ) { AI_ENEMY_IN_FOV = true; } SetEnemyPosition(); } else { // check if we heard any sounds in the last frame if ( enemyEnt == gameLocal.GetAlertEntity() ) { float dist = ( enemyEnt->GetPhysics()->GetOrigin() - org ).LengthSqr(); //allow the sound's own radius to override hearingRange, if it is set if ( gameLocal.lastAIAlertRadius ) { if ( dist < Square( gameLocal.lastAIAlertRadius ) ) { SetEnemyPosition(); } } else if ( dist < Square( hearingRange ) ) { SetEnemyPosition(); } } } if ( ai_debugMove.GetBool() ) { gameRenderWorld->DebugBounds( colorLtGrey, enemyEnt->GetPhysics()->GetBounds(), lastReachableEnemyPos, gameLocal.msec ); gameRenderWorld->DebugBounds( colorWhite, enemyEnt->GetPhysics()->GetBounds(), lastVisibleReachableEnemyPos, gameLocal.msec ); } } void hhMonsterAI::BecameBound(hhBindController *b) { SetState( GetScriptFunction( "state_Nothing" ) ); } void hhMonsterAI::BecameUnbound(hhBindController *b) { if ( health > 0 ) { SetState( GetScriptFunction( "state_Idle" ) ); } } void hhMonsterAI::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { const trace_t& trace = physicsObj.GetGroundTrace(); if ( af.IsActive() || (!physicsObj.HasGroundContacts() || trace.fraction == 1.0f) && !IsBound() ) { return; } //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; } // if the monster wasn't going down if ( ( oldVelocity * -physicsObj.GetGravityNormal() ) >= 0.0f ) { return; } idVec3 deltaVelocity = DetermineDeltaCollisionVelocity( oldVelocity, trace ); float delta = (IsBound()) ? deltaVelocity.Length() : deltaVelocity * physicsObj.GetGravityNormal(); if ( delta < spawnArgs.GetFloat( "fatal_fall_velocity", "900" ) ) { return; // Early out } if( trace.fraction == 1.0f ) { return; } Damage( NULL, NULL, oldVelocity.ToNormal(), "damage_monsterfall", 1, INVALID_JOINT ); } hhEntityFx* hhMonsterAI::SpawnFxLocal( const char *fxName, const idVec3 &origin, const idMat3& axis, const hhFxInfo* const fxInfo, bool forceClient ) { //overridden to use GetJointWorldTransform to set location if binding to a bone idDict fxArgs; hhEntityFx * fx = NULL; if ( g_skipFX.GetBool() ) { return NULL; } if( !fxName || !fxName[0] ) { return NULL; } // Spawn an fx fxArgs.Set( "fx", fxName ); fxArgs.SetBool( "start", fxInfo ? fxInfo->StartIsSet() : true ); fxArgs.SetVector( "origin", origin ); fxArgs.SetMatrix( "rotation", axis ); //HUMANHEAD: aob if( fxInfo ) { fxArgs.SetBool( "removeWhenDone", fxInfo->RemoveWhenDone() ); fxArgs.SetBool( "onlyVisibleInSpirit", fxInfo->OnlyVisibleInSpirit() ); // CJR fxArgs.SetBool( "onlyInvisibleInSpirit", fxInfo->OnlyInvisibleInSpirit() ); // tmj fxArgs.SetBool( "toggle", fxInfo->Toggle() ); } //HUMANHEAD END //HUMANHEAD rww - use forceClient if (forceClient) { //this can happen on the "server" in the case of listen servers as well fx = (hhEntityFx *)gameLocal.SpawnClientObject( "func_fx", &fxArgs ); } else { assert(!gameLocal.isClient); fx = (hhEntityFx *)gameLocal.SpawnObject( "func_fx", &fxArgs ); } if( fxInfo ) { fx->SetFxInfo( *fxInfo ); } if( fxInfo && fxInfo->EntityIsSet() ) { fx->fl.noRemoveWhenUnbound = fxInfo->NoRemoveWhenUnbound(); if( fxInfo->BindBoneIsSet() ) { idVec3 bonePos; idMat3 boneAxis; GetJointWorldTransform( fxInfo->GetBindBone(), bonePos, boneAxis ); fx->SetOrigin( bonePos ); fx->BindToJoint( fxInfo->GetEntity(), fxInfo->GetBindBone(), true ); } else if( fx && fx->Joint() && *fx->Joint() ) { fx->MoveToJoint( fxInfo->GetEntity(), fx->Joint() ); fx->BindToJoint( fxInfo->GetEntity(), fx->Joint(), true ); } else { fx->Bind( fxInfo->GetEntity(), true ); } } fx->Show(); return fx; } bool hhMonsterAI::AttackMelee( const char *meleeDefName ) { const idDict *meleeDef; idActor *enemyEnt = enemy.GetEntity(); const char *p; const idSoundShader *shader; meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false ); if ( !meleeDef ) { gameLocal.Error( "Unknown melee '%s'", meleeDefName ); } if ( !enemyEnt ) { p = meleeDef->GetString( "snd_miss" ); if ( p && *p ) { shader = declManager->FindSound( p ); StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); } return false; } // make sure the trace can actually hit the enemy if ( !TestMeleeDef(meleeDefName) ) { // missed p = meleeDef->GetString( "snd_miss" ); if ( p && *p ) { shader = declManager->FindSound( p ); StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); } return false; } // // do the damage // p = meleeDef->GetString( "snd_hit" ); if ( p && *p ) { shader = declManager->FindSound( p ); StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); } idVec3 kickDir; meleeDef->GetVector( "kickDir", "0 0 0", kickDir ); idVec3 globalKickDir; globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir; if ( spawnArgs.GetBool( "smart_knockback", "0" ) ) { //do some traces to determine which dir to kick enemy idAngles ang = viewAxis.ToAngles(); idVec3 forward, right, up; ang.ToVectors( &forward, &right, &up ); trace_t trace; gameLocal.clip.TraceBounds( trace, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + -forward * 400, enemyEnt->GetPhysics()->GetBounds(), MASK_SOLID, this ); if ( trace.fraction != 1.0f ) { //there's room to kick the player back globalKickDir = -forward; if ( ai_debugMove.GetBool() ) { gameRenderWorld->DebugArrow( colorRed, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + globalKickDir * 500, 10, 10000 ); } } else { gameLocal.clip.TraceBounds( trace, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + right * 400, enemyEnt->GetPhysics()->GetBounds(), MASK_SOLID, this ); if ( trace.fraction != 1.0f ) { globalKickDir = right; if ( ai_debugMove.GetBool() ) { gameRenderWorld->DebugArrow( colorRed, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + globalKickDir * 500, 10, 10000 ); } } else { gameLocal.clip.TraceBounds( trace, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + right * 400, enemyEnt->GetPhysics()->GetBounds(), MASK_SOLID, this ); if ( trace.fraction != 1.0f ) { globalKickDir = -right; if ( ai_debugMove.GetBool() ) { gameRenderWorld->DebugArrow( colorRed, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + globalKickDir * 500, 10, 10000 ); } } } } } enemyEnt->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT ); //swap skin if we have successfully hit idStr hitSkin = spawnArgs.GetString( "skin_melee_hit", "" ); if ( hitSkin.Length() ) { SetSkinByName( hitSkin.c_str() ); } lastAttackTime = gameLocal.time; return true; } bool hhMonsterAI::TestMeleeDef( const char *meleeDefName ) const { //tests using "melee_boundmin" and "melee_boundmax" in the damage def trace_t trace; idActor *enemyEnt = enemy.GetEntity(); const idDict *meleeDef; meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false ); if ( !meleeDef ) { gameLocal.Error( "Unknown melee '%s'", meleeDefName ); } if ( !enemyEnt || !melee_range ) { return false; } //FIXME: make work with gravity vector idVec3 org = physicsObj.GetOrigin(); const idBounds &myBounds = physicsObj.GetBounds(); idBounds bounds; idBounds meleeBounds( meleeDef->GetVector( "melee_boundmin" ), meleeDef->GetVector( "melee_boundmax" ) ); if ( meleeBounds != bounds_zero ) { //check custom rotated meleebound idBox meleeBox( meleeBounds, org, renderEntity.axis ); idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds(); enemyBounds.TranslateSelf( enemyEnt->GetPhysics()->GetOrigin() ); idBox enemyBox( enemyBounds ); if ( ai_debugMove.GetBool() ) { gameRenderWorld->DebugBox( colorYellow, meleeBox, 5000 ); } if ( !enemyBox.IntersectsBox( meleeBox ) ) { return false; } } else { // expand the bounds out by our melee range bounds[0][0] = -melee_range; bounds[0][1] = -melee_range; bounds[0][2] = myBounds[0][2] - 4.0f; bounds[1][0] = melee_range; bounds[1][1] = melee_range; bounds[1][2] = myBounds[1][2] + 4.0f; bounds.TranslateSelf( org ); idVec3 enemyOrg = enemyEnt->GetPhysics()->GetOrigin(); idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds(); enemyBounds.TranslateSelf( enemyOrg ); if ( ai_debugMove.GetBool() ) { gameRenderWorld->DebugBounds( colorYellow, bounds, vec3_zero, gameLocal.msec ); } if ( !bounds.IntersectsBounds( enemyBounds ) ) { return false; } } idVec3 start = GetEyePosition(); idVec3 end = enemyEnt->GetEyePosition(); gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); if ( ( trace.fraction == 1.0f ) || ( gameLocal.GetTraceEntity( trace ) == enemyEnt ) ) { return true; } return false; } void hhMonsterAI::PrintDebug() { if ( enemy.IsValid() ) { int dist = int(( GetPhysics()->GetOrigin() - enemy->GetPhysics()->GetOrigin() ).LengthFast()); gameLocal.Printf( " Enemy: %s\n", enemy->GetName() ); gameLocal.Printf( " Distance to Enemy: %d\n", dist ); gameLocal.Printf( " Enemy Area: %i\n", PointReachableAreaNum(enemy->GetOrigin()) ); gameLocal.Printf( " Enemy Reachable: %s\n", AI_ENEMY_REACHABLE ? "yes" : "no" ); gameLocal.Printf( " Enemy Visible: %s\n", AI_ENEMY_VISIBLE ? "yes" : "no" ); } else { gameLocal.Printf( " Enemy: None\n" ); } gameLocal.Printf( " Current Area: %i\n", PointReachableAreaNum(GetOrigin()) ); if ( state ) { gameLocal.Printf( " State: %s\n", state->Name() ); } gameLocal.Printf( " Health: %i/%i\n", GetHealth(), spawnArgs.GetInt( "health" ) ); gameLocal.Printf( " Torso State: %s\n", torsoAnim.state.c_str() ); gameLocal.Printf( " Legs State: %s\n", legsAnim.state.c_str() ); gameLocal.Printf( " IK: %s\n", walkIK.IsActivated() ? "enable" : "disabled" ); gameLocal.Printf( " Turnrate: %f\n", turnRate ); } bool hhMonsterAI::NewWanderDir( const idVec3 &dest ) { if ( spawnArgs.GetInt( "wander_radius" ) ) { //pick a new dest based on radius from starting origin idVec3 offset = hhUtils::RandomVector() * float(spawnArgs.GetInt( "wander_radius" )); offset.z = 0.0f; const idVec3 newDest = spawnOrigin + offset; return idAI::NewWanderDir( newDest ); } else { return idAI::NewWanderDir( dest ); } } /* ===================== hhMonsterAI::Save ===================== */ void hhMonsterAI::Save( idSaveGame *savefile ) const { savefile->WriteInt( targetReaction.reactionIndex ); targetReaction.entity.Save( savefile); shootTarget.Save( savefile ); currPassageway.Save( savefile ); savefile->WriteBool( bCanFall ); int i, num = healthTriggers.Num(); savefile->WriteInt( num ); for (i = 0; i < num; i++) { savefile->WriteInt( healthTriggers[i].healthThresh ); healthTriggers[i].triggerEnt.Save( savefile ); savefile->WriteInt( healthTriggers[i].triggerCount ); } savefile->WriteInt( hearingRange ); savefile->WriteInt( lastContactTime ); savefile->WriteBool( bSeeThroughPortals ); savefile->WriteBool( bBossBar ); savefile->WriteInt( nextSpeechTime ); savefile->WriteAngles( lookOffset ); savefile->WriteVec3( spawnOrigin ); savefile->WriteBool( bBindOrient ); savefile->WriteInt( numDDADamageSamples ); savefile->WriteFloat( totalDDADamage ); savefile->WriteInt( spawnThinkFlags ); savefile->WriteBool( bCanWallwalk ); savefile->WriteBool( bOverrideKilledByGravityZones ); savefile->WriteInt( fallDelay ); savefile->WriteBool( soundOnModel ); savefile->WriteBool( bBindAxis ); savefile->WriteBool( bCustomBlood ); savefile->WriteBool( bNoCombat ); savefile->WriteBool( bNeverTarget ); savefile->WriteInt( nextSpiritProxyCheck ); }; /* ===================== hhMonsterAI::Restore ===================== */ void hhMonsterAI::Restore( idRestoreGame *savefile ) { savefile->ReadInt( targetReaction.reactionIndex ); targetReaction.entity.Restore( savefile); shootTarget.Restore( savefile ); currPassageway.Restore( savefile ); savefile->ReadBool( bCanFall ); int i, num; savefile->ReadInt( num ); healthTriggers.SetNum( num ); for (i = 0; i < num; i++) { savefile->ReadInt( healthTriggers[i].healthThresh ); healthTriggers[i].triggerEnt.Restore( savefile ); savefile->ReadInt( healthTriggers[i].triggerCount ); } savefile->ReadInt( hearingRange ); savefile->ReadInt( lastContactTime ); savefile->ReadBool( bSeeThroughPortals ); savefile->ReadBool( bBossBar ); savefile->ReadInt( nextSpeechTime ); savefile->ReadAngles( lookOffset ); savefile->ReadVec3( spawnOrigin ); savefile->ReadBool( bBindOrient ); savefile->ReadInt( numDDADamageSamples ); savefile->ReadFloat( totalDDADamage ); savefile->ReadInt( spawnThinkFlags ); savefile->ReadBool( bCanWallwalk ); savefile->ReadBool( bOverrideKilledByGravityZones ); savefile->ReadInt( fallDelay ); savefile->ReadBool( soundOnModel ); savefile->ReadBool( bBindAxis ); savefile->ReadBool( bCustomBlood ); savefile->ReadBool( bNoCombat ); savefile->ReadBool( bNeverTarget ); savefile->ReadInt( nextSpiritProxyCheck ); allSimpleMonsters.AddUnique(this); nextTurnUpdate = 0; if ( AI_VEHICLE ) { if ( GetVehicleInterface() ) { ResetClipModel(); hhVehicle *vehicle = GetVehicleInterface()->GetVehicle(); spawnArgs.Set( "use_aas", spawnArgs.GetString( "aas_shuttle", "aasDroid" ) ); SetAAS(); SetState( GetScriptFunction( "state_VehicleCombat" ) ); } } }; /* ===================== hhMonsterAI::GetTurnDir Returns -1 if left, 0 if facing ideal, and 1 if turning right ===================== */ int hhMonsterAI::GetTurnDir( void ) { float diff; if ( !turnRate ) { return 0; } diff = idMath::AngleNormalize180( current_yaw - ideal_yaw ); if ( idMath::Fabs( diff ) < 0.01f ) { // force it to be exact current_yaw = ideal_yaw; return 0; } return (diff < 0 ? -1 : 1); } //============================================================================= // // hhMonsterAI::SendDamageToDDA() // // Informs the player of the amount of damage inflicted upon it until this // creature was killed. //============================================================================= void hhMonsterAI::SendDamageToDDA() { if ( gameLocal.isMultiplayer ) { return; } int ddaIndex = spawnArgs.GetInt( "ddaIndex", "0" ); if ( gameLocal.GetDDA() && ddaIndex >= 0 ) { // Only include certain creatures in the DDA calculation (exclude such things as crawlers and NPCs) gameLocal.GetDDA()->DDA_AddDamage( ddaIndex, totalDDADamage ); gameLocal.GetDDA()->DDA_AddSurvivalHealth( ddaIndex, gameLocal.GetLocalPlayer()->GetHealth() ); } totalDDADamage = 0; } void hhMonsterAI::FlyMove( void ) { idVec3 goalPos; idVec3 oldorigin; idVec3 newDest; AI_BLOCKED = false; if ( ( move.moveCommand != MOVE_NONE ) && ReachedPos( move.moveDest, move.moveCommand ) ) { StopMove( MOVE_STATUS_DONE ); } if ( move.moveCommand != MOVE_TO_POSITION_DIRECT ) { idVec3 vel = physicsObj.GetLinearVelocity(); if ( GetMovePos( goalPos ) ) { CheckObstacleAvoidance( goalPos, newDest ); goalPos = newDest; } if ( !AI_FLY_NO_SEEK && move.speed ) { FlySeekGoal( vel, goalPos ); } // add in bobbing AddFlyBob( vel ); if ( enemy.GetEntity() && ( move.moveCommand != MOVE_TO_POSITION ) ) { AdjustFlyHeight( vel, goalPos ); } 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 hhMonsterAI::Activate( idEntity *activator ) { if ( spawnThinkFlags != 0 ) { int temp = spawnThinkFlags; spawnThinkFlags = 0; BecomeActive( temp ); } idAI::Activate( activator ); } void hhMonsterAI::Hide() { if ( spawnThinkFlags == 0 && ai_hideSkipThink.GetBool() ) { if ( ( spawnArgs.GetBool( "hide" ) || spawnArgs.GetBool( "portal" ) ) ) { spawnThinkFlags = thinkFlags; thinkFlags = 0; } } idAI::Hide(); } void hhMonsterAI::HideNoDormant() { idAI::Hide(); } void hhMonsterAI::BecomeActive( int flags ) { if ( spawnThinkFlags != 0 ) { spawnThinkFlags |= flags; return; } idAI::BecomeActive( flags ); } void hhMonsterAI::HandleNoGore(void) { if (GERMAN_VERSION || g_nogore.GetBool()) { fl.takedamage = false; fl.canBeTractored = false; GetPhysics()->SetContents(0); float time = spawnArgs.GetFloat("nogore_dispose_time", "1"); if (time > 0.0f) { //PostEventSec(&EV_Dispose, time); } } } bool hhMonsterAI::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { if ( soundOnModel ) { idVec3 boneOrigin; idMat3 boneAxis; jointHandle_t bone = animator.GetJointHandle( spawnArgs.GetString( "sound_joint", "b1" ) ); if ( bone != INVALID_JOINT && animator.GetJointTransform( bone, gameLocal.time, boneOrigin, boneAxis ) ) { origin = boneOrigin; axis = boneAxis * viewAxis; return true; } } return idAI::GetPhysicsToSoundTransform( origin, axis ); } void hhMonsterAI::AddLocalMatterWound( jointHandle_t jointNum, const idVec3 &localOrigin, const idVec3 &localNormal, const idVec3 &localDir, int damageDefIndex, const idMaterial *collisionMaterial ) { if ( bCustomBlood ) { return; } const idDeclEntityDef *def = static_cast( declManager->DeclByIndex( DECL_ENTITYDEF, damageDefIndex ) ); if ( def == NULL ) { return; } surfTypes_t matterType = gameLocal.GetMatterType( this, collisionMaterial, "idEntity::AddLocalMatterWound" ); GetWoundManager()->AddWounds( def, matterType, jointNum, localOrigin, localNormal, localDir ); if ( head.IsValid() ) { head->AddLocalMatterWound( jointNum, localOrigin, localNormal, localDir, damageDefIndex, collisionMaterial ); } //TODO: Grab head projection code from below and put into ApplyImpactMark() }