// ----------------------------------------------------------------------- // // // MODULE : Gideon.cpp // // PURPOSE : Gideon AI - Implementation // // CREATED : 10/1/98 (version 3!) // // ----------------------------------------------------------------------- // #include #include "Gideon.h" #include "cpp_server_de.h" #include "SFXMsgIds.h" BEGIN_CLASS(Gideon) ADD_REALPROP(RandomHitPoints, 0.0f) \ ADD_STRINGPROP(AIState, "IDLE") \ ADD_BOOLPROP(Enhanced,DFALSE) \ END_CLASS_DEFAULT(Gideon, AI_Mgr, NULL, NULL) //static data member initialization DBOOL Gideon::m_bLoadAnims = DTRUE; CAnim_Sound Gideon::m_Anim_Sound; // ----------------------------------------------------------------------- // // // ROUTINE: // // PURPOSE: Constructor // // ----------------------------------------------------------------------- // Gideon::Gideon() : AI_Mgr() { m_fHearingDist = 0.0f; m_fSensingDist = 10000.0f; m_fSmellingDist = 0.0f; m_fSeeingDist = 10000.0f; m_fWalkSpeed = 75.0f; m_fRunSpeed = 180.0f; m_fRollSpeed = 3.5f; m_fAIMass = AI_DEFAULT_MASS; m_nAIStrength = 10; strcpy(m_szAIWeapon[0], "GIDEON_SHIELD" ); strcpy(m_szAIWeapon[1], "ENERGY_BLAST" ); strcpy(m_szAIWeapon[2], "NAGA_BLAST"); strcpy(m_szAIWeapon[3], "GIDEON_WIND"); m_nState = STATE_Idle; m_nLastState = STATE_Idle; m_dwFlags = FLAG_ALWAYSRECOIL; m_bCabal = DFALSE; // Should this be true? m_bMoveToGround = DFALSE; // We can fly, don't move to floor on creation. sprintf(m_szFireNode,"rr_gun"); // [blg] m_fAIHitPoints = 4000; m_fAIRandomHP = 0; m_fAIArmorPoints = 200; m_fFullHealth = 0.0f; m_bCreateHealth = DTRUE; m_fShieldDuration = 60.0f; // We can only shield ourselves once every 60 seonds m_fLastShield = 0.0f; m_fLastPanic = 0.0f; m_damage.SetApplyDamagePhysics(DFALSE); strcpy(m_szAIState, "IDLE"); } // ----------------------------------------------------------------------- // // // ROUTINE: EngineMessageFn // // PURPOSE: Handle engine messages // // ----------------------------------------------------------------------- // DDWORD Gideon::EngineMessageFn(DDWORD messageID, void *pData, DFLOAT fData) { switch(messageID) { case MID_PRECREATE: { // Need to call base class to have the object name read in before // we call PostPropRead() DDWORD dwRet = AI_Mgr::EngineMessageFn(messageID, pData, fData); if(fData != PRECREATE_SAVEGAME) PostPropRead((ObjectCreateStruct*)pData); return dwRet; } break; case MID_INITIALUPDATE: { InitialUpdate((DVector *)pData); CacheFiles(); break; } case MID_UPDATE: { if (m_bCreateHealth) { if (!m_fFullHealth) { HMESSAGEWRITE hWrite = m_pServerDE->StartMessage(NULL, SMSG_BOSSHEALTH); m_pServerDE->WriteToMessageFloat(hWrite,1.0f); m_pServerDE->EndMessage(hWrite); m_bCreateHealth = DFALSE; m_fFullHealth = m_damage.GetHitPoints(); } else { DFLOAT fTemp = m_damage.GetHitPoints() / m_fFullHealth; HMESSAGEWRITE hWrite = m_pServerDE->StartMessage(NULL, SMSG_BOSSHEALTH); m_pServerDE->WriteToMessageFloat(hWrite,fTemp); m_pServerDE->EndMessage(hWrite); } } } break; case MID_SAVEOBJECT: Save((HMESSAGEWRITE)pData, (DDWORD)fData); break; case MID_LOADOBJECT: Load((HMESSAGEREAD)pData, (DDWORD)fData); break; default : break; } // Store result of parent class function DDWORD dwResult = AI_Mgr::EngineMessageFn(messageID, pData, fData); // If this is initial update, we need to turn off gravity AFTER the // MID_INITIALUPDATE call in AI_Mgr... if (messageID == MID_INITIALUPDATE) { DDWORD dwFlags = m_pServerDE->GetObjectFlags(m_hObject); dwFlags &= ~FLAG_GRAVITY; m_pServerDE->SetObjectFlags(m_hObject, dwFlags); } // ...and return the stored result. return dwResult; } DDWORD Gideon::ObjectMessageFn(HOBJECT hSender, DDWORD messageID, HMESSAGEREAD hRead) { switch (messageID) { case MID_DAMAGE: { if (!m_pServerDE) break; CBaseCharacter::ObjectMessageFn(hSender, messageID, hRead); DFLOAT fTemp = m_damage.GetHitPoints() / m_fFullHealth; HMESSAGEWRITE hWrite = m_pServerDE->StartMessage(NULL, SMSG_BOSSHEALTH); m_pServerDE->WriteToMessageFloat(hWrite,fTemp); m_pServerDE->EndMessage(hWrite); if (m_damage.GetLastDamageAmount() > 20.0f) { SetNewState(STATE_Special1); } } default: break; } return AI_Mgr::ObjectMessageFn(hSender, messageID, hRead); } // ----------------------------------------------------------------------- // // // ROUTINE: PostPropRead() // // PURPOSE: Update properties // // ----------------------------------------------------------------------- // void Gideon::PostPropRead(ObjectCreateStruct *pStruct) { if (!pStruct) return; char* pFilename = "Models\\Enemies\\gideon.abc"; char *pSkin = "Skins\\Enemies\\gideon.dtx"; strcpy(pStruct->m_Filename, pFilename); strcpy(pStruct->m_SkinName, pSkin); } // ----------------------------------------------------------------------- // // // ROUTINE: InitialUpdate() // // PURPOSE: Handle initial update // // ----------------------------------------------------------------------- // DBOOL Gideon::InitialUpdate(DVector *pMovement) { m_pServerDE = BaseClass::GetServerDE(); if (!m_pServerDE) return DFALSE; if(m_bLoadAnims) { m_Anim_Sound.SetAnimationIndexes(m_hObject); m_Anim_Sound.GenerateHitSpheres(m_hObject); m_Anim_Sound.SetSoundRoot("sounds\\enemies\\Gideon"); m_bLoadAnims = DFALSE; } AI_Mgr::InitStatics(&m_Anim_Sound); m_fFullHealth = m_damage.GetHitPoints(); m_bCreateHealth = DTRUE; return DTRUE; } // ----------------------------------------------------------------------- // // ROUTINE : Gideon::MC_Dodge_Left // DESCRIPTION : run the dodge left animation // RETURN TYPE : void // ----------------------------------------------------------------------- // void Gideon::MC_Dodge_Left() { if (m_bAnimating == DFALSE || m_nCurMetacmd != MC_DODGE_LEFT) { m_fTimeStart = m_pServerDE->GetTime(); SetAnimation( m_pAnim_Sound->m_nAnim_DODGE_LEFT); m_pServerDE->SetModelLooping(m_hObject, DFALSE); DVector vLeft; VEC_MULSCALAR(vLeft, m_MoveObj.GetRightVector(), -1.0f); Move(vLeft, m_fRollSpeed); m_bAnimating = DTRUE; m_nCurMetacmd = MC_DODGE_LEFT; } else { DVector vLeft; VEC_MULSCALAR(vLeft, m_MoveObj.GetRightVector(), -1.0f); Move(vLeft, m_fRollSpeed); //Are we done? if(m_pServerDE->GetTime() - m_fTimeStart >= 1.0f) { m_bAnimating = DFALSE; Metacmd++; } } return; } // ----------------------------------------------------------------------- // // ROUTINE : Gideon::MC_Dodge_Right // DESCRIPTION : run the dodge right animation // RETURN TYPE : void // ----------------------------------------------------------------------- // void Gideon::MC_Dodge_Right() { if (m_bAnimating == DFALSE || m_nCurMetacmd != MC_DODGE_RIGHT) { m_fTimeStart = m_pServerDE->GetTime(); SetAnimation( m_pAnim_Sound->m_nAnim_DODGE_RIGHT); m_pServerDE->SetModelLooping(m_hObject, DFALSE); Move(m_MoveObj.GetRightVector(),m_fRollSpeed); m_bAnimating = DTRUE; m_nCurMetacmd = MC_DODGE_RIGHT; } else { Move(m_MoveObj.GetRightVector(),m_fRollSpeed); //Are we done? if(m_pServerDE->GetTime() - m_fTimeStart >= 1.0f) { m_bAnimating = DFALSE; Metacmd++; } } return; } // ----------------------------------------------------------------------- // // ROUTINE : Gideon::ComputeState // DESCRIPTION : Compute actual substate // RETURN TYPE : void // ----------------------------------------------------------------------- // void Gideon::ComputeState(int nStimType) { int nStim = nStimType; if(nStimType == -1) nStim = ComputeStimuli(); if(!nStim) { switch(m_nState) { case STATE_Idle: SetNewState(STATE_Special1); break; case STATE_Teleport: SetNewState(STATE_SearchVisualTarget); break; case STATE_SearchVisualTarget: SetNewState(STATE_Idle); break; default: SetNewState(STATE_Teleport); break; } } else { //if health is low or threat is high... if(m_fStimuli[HEALTH] < 0.25f || m_fStimuli[THREAT] > 0.75f) { // ...and we can shield... if (m_pServerDE->GetTime() - m_fLastShield > m_fShieldDuration) { // ...do so. SetNewState(STATE_AttackClose); } else { // ...or run like hell. if (m_fStimuli[HEALTH] < 0.05f) { if ((m_fLastPanic + 15.0f) >= m_pServerDE->GetTime()) { m_fLastPanic = m_pServerDE->GetTime(); SetNewState(STATE_Teleport); } else { SetNewState(STATE_AttackClose); } } else { // ...or use the wind attack. SetNewState(STATE_AttackClose); } } return; } switch(m_nState) { case STATE_SearchVisualTarget: { SetNewState(STATE_Special1); break; } default: { if(m_fStimuli[SIGHT] > 0.94f) { SetNewState(STATE_AttackClose); } else { if (m_fStimuli[SIGHT] > 0.75f) { SetNewState(STATE_AttackFar); } else { SetNewState(STATE_Special1); } } break; } } } return; } // ----------------------------------------------------------------------- // // ROUTINE : Gideon::MC_Fire_Stand // DESCRIPTION : Run the fire_stand animation // RETURN TYPE : void // ----------------------------------------------------------------------- // void Gideon::MC_Fire_Stand() { if (m_bAnimating == DFALSE || m_nCurMetacmd != MC_FIRE_STAND) { DBOOL bRet = DFALSE; DFLOAT fArmor = m_damage.GetArmorPoints(); if ((fArmor <= 25) && ((m_pServerDE->GetTime() - m_fLastShield) > m_fShieldDuration)) { m_InventoryMgr.ChangeWeapon(WEAP_GIDEON_SHIELD); m_damage.SetArmorPoints(fArmor + 100); m_fLastShield = m_pServerDE->GetTime(); bRet = SetAnimation(m_pAnim_Sound->m_nAnim_FIRE_STAND[TYPE_RIFLE]); } else if(m_nState == STATE_AttackFar) { if (IsRandomChance(70)) { m_InventoryMgr.ChangeWeapon(WEAP_ZEALOT_ENERGYBLAST); bRet = SetAnimation(m_pAnim_Sound->m_nAnim_FIRE_STAND[TYPE_MAGIC]); } else { m_InventoryMgr.ChangeWeapon(WEAP_NAGA_EYEBEAM); bRet = SetAnimation(m_pAnim_Sound->m_nAnim_FIRE_STAND[TYPE_RIFLE]); } } else { if (IsRandomChance(50)) { m_InventoryMgr.ChangeWeapon(WEAP_GIDEON_WIND); bRet = SetAnimation(m_pAnim_Sound->m_nAnim_FIRE_STAND[TYPE_RIFLE]); } else { m_InventoryMgr.ChangeWeapon(WEAP_NAGA_EYEBEAM); bRet = SetAnimation(m_pAnim_Sound->m_nAnim_FIRE_STAND[TYPE_RIFLE]); } } m_pServerDE->SetModelLooping(m_hObject, DFALSE); m_bAnimating = DTRUE; m_nCurMetacmd = MC_FIRE_STAND; } else { if(m_pServerDE->GetModelPlaybackState(m_hObject) & MS_PLAYDONE) { m_InventoryMgr.ChangeWeapon(WEAP_SOUL_HOOK); m_bAnimating = DFALSE; Metacmd++; return; } } return; } // ----------------------------------------------------------------------- // // ROUTINE : Gideon::AI_STATE_AttackClose // DESCRIPTION : // RETURN TYPE : void // ----------------------------------------------------------------------- // void Gideon::AI_STATE_AttackClose() { //SCHLEGZ 4/22/98 4:51:21 PM: sanity check if(m_hTarget == DNULL) { SetNewState(STATE_SearchVisualTarget); return; } switch(Metacmd) { case 1: MC_FaceTarget(); break; case 2: m_fStimuli[SIGHT] = VEC_DIST(m_MoveObj.GetPos(),m_vTargetPos); if((m_fStimuli[SIGHT] <= (m_fSeeingDist * 0.90f)) || m_nCurMetacmd == MC_FIRE_STAND) MC_Fire_Stand(); else { MC_FaceTarget(); Metacmd--; if(m_fStimuli[SIGHT] > (m_fSeeingDist * 0.40)) { SetNewState(STATE_Special1); } else { MC_Walk(); } } break; case 3: if(m_nCurMetacmd == MC_DODGE_RIGHT) MC_Dodge_Right(); else if(m_nCurMetacmd == MC_DODGE_LEFT) MC_Dodge_Left(); else if(IsRandomChance(50)) MC_Dodge_Right(); else MC_Dodge_Left(); break; case 4: ComputeState(); break; } return; } // ----------------------------------------------------------------------- // // ROUTINE : Gideon::AI_STATE_AttackFar // DESCRIPTION : // RETURN TYPE : void // ----------------------------------------------------------------------- // void Gideon::AI_STATE_AttackFar() { //SCHLEGZ 4/22/98 4:51:21 PM: sanity check if(m_hTarget == DNULL) { SetNewState(STATE_SearchVisualTarget); return; } m_fStimuli[SIGHT] = VEC_DIST(m_MoveObj.GetPos(),m_vTargetPos); switch(Metacmd) { case 1: MC_FaceTarget(); Metacmd--; if((m_fStimuli[SIGHT] <= (m_fSeeingDist * 0.30f)) || m_nCurMetacmd == MC_FIRE_STAND) { MC_Fire_Stand(); } else { SetNewState(STATE_Special1); } break; case 2: MC_Taunt_Bold(); break; case 3: MC_FaceTarget(); Metacmd--; if(m_fStimuli[SIGHT] <= (m_fSeeingDist * 0.40f)) { SetNewState(STATE_Special1); } else { MC_Walk(); } break; case 4: ComputeState(); break; } return; } void Gideon::MagicPowerup() { DVector offset; VEC_SET(offset, 0.0f, 0.0f, 0.0f); // Display the 'powerup' effect around Gideon HMESSAGEWRITE hMessage = m_pServerDE->StartInstantSpecialEffectMessage(&offset); m_pServerDE->WriteToMessageByte(hMessage, SFX_OBJECTFX_ID); m_pServerDE->WriteToMessageObject(hMessage, m_hObject); m_pServerDE->WriteToMessageVector(hMessage, &offset); m_pServerDE->WriteToMessageFloat(hMessage, 0.0f); m_pServerDE->WriteToMessageDWord(hMessage, 0); m_pServerDE->WriteToMessageDWord(hMessage, OBJFX_POWERUP_1); m_pServerDE->WriteToMessageDWord(hMessage, 0); m_pServerDE->EndMessage(hMessage); } void Gideon::MC_FadeOut() { if (m_bAnimating == DFALSE || m_nCurMetacmd != MC_FADEOUT) { m_bAnimating = DTRUE; m_nCurMetacmd = MC_FADEOUT; DDWORD dwFlags = m_pServerDE->GetObjectFlags(m_hObject); dwFlags &= ~FLAG_SOLID; m_pServerDE->SetObjectFlags(m_hObject,dwFlags); } else { DVector vColor; DFLOAT fAlpha = 0.0f; m_pServerDE->GetObjectColor(m_hObject,&vColor.x, &vColor.y, &vColor.z, &fAlpha); fAlpha -= 1.0/16; if(fAlpha <= 0.0f) fAlpha = 0.0f; m_pServerDE->SetObjectColor(m_hObject,vColor.x,vColor.y,vColor.z, fAlpha); if(fAlpha <= 0.0f) { m_bAnimating = DFALSE; Metacmd++; return; } } return; } void Gideon::AI_STATE_Teleport() { switch(Metacmd) { case 1: MC_FadeOut(); break; case 2: { m_pAnim_Sound->PlaySound(m_hObject, "teleport.wav", 1000.0f, 100); Metacmd++; break; } case 3: { IntersectQuery IQuery; IntersectInfo IInfo; DDWORD dwFlags; IQuery.m_Flags = INTERSECT_OBJECTS; IQuery.m_FilterFn = DNULL; DVector vDir; VEC_SET(vDir, m_pServerDE->Random(-1.0f,1.0f), 0.0f, m_pServerDE->Random(-1.0f,1.0f)); VEC_COPY(IQuery.m_From, m_MoveObj.GetPos()); VEC_ADDSCALED(IQuery.m_To, IQuery.m_From, vDir, 1500.0f); dwFlags = m_pServerDE->GetObjectFlags(m_hObject); dwFlags = dwFlags | FLAG_SOLID; m_pServerDE->SetObjectFlags(m_hObject,dwFlags); if(m_pServerDE->IntersectSegment(&IQuery, &IInfo)) { VEC_ADDSCALED(IInfo.m_Point, IInfo.m_Point, vDir, -100.0f); m_pServerDE->MoveObject(m_hObject, &IInfo.m_Point); } else { VEC_ADDSCALED(IInfo.m_Point, IQuery.m_To, vDir, -100.0f); m_pServerDE->MoveObject(m_hObject, &IQuery.m_To); } Metacmd++; break; } case 4: { DVector vColor; DFLOAT fAlpha = 1.0f; m_pServerDE->GetObjectColor(m_hObject,&vColor.x, &vColor.y, &vColor.z, &fAlpha); m_pServerDE->SetObjectColor(m_hObject,vColor.x,vColor.y,vColor.z, 1.0f); Metacmd++; break; } case 5: MC_FaceTarget(); break; case 6: ComputeState(); break; } return; } void Gideon::AI_STATE_Special1() { if (!m_hTarget) { SetNewState(STATE_Teleport); } switch(Metacmd) { case 1: MC_FadeOut(); break; case 2: { // char szSound[256]; // strcpy(szSound, SOUND_LAUGH); // m_pAnim_Sound->GetSoundPath(szSound,m_pServerDE->IntRandom(1,3)); // PlayAISound(szSound, 1000.0f, PLAY_WAIT); Metacmd++; break; } case 3: { IntersectQuery IQuery; IntersectInfo IInfo; DDWORD dwFlags; IQuery.m_Flags = INTERSECT_OBJECTS; IQuery.m_FilterFn = DNULL; DVector vDir, vTarget; VEC_SET(vDir, m_pServerDE->Random(-1.0f,1.0f), 0.2f, m_pServerDE->Random(-1.0f,1.0f)); m_pServerDE->GetObjectPos(m_hTarget,&vTarget); dwFlags = m_pServerDE->GetObjectFlags(m_hObject); dwFlags = dwFlags | FLAG_SOLID; m_pServerDE->SetObjectFlags(m_hObject,dwFlags); VEC_COPY(IQuery.m_From, vTarget); VEC_ADDSCALED(IQuery.m_To, IQuery.m_From, vDir, m_pServerDE->Random(400.0f,600.0f)); if(m_pServerDE->IntersectSegment(&IQuery, &IInfo)) { VEC_ADDSCALED(IInfo.m_Point, IInfo.m_Point, vDir, -300.0f); m_pServerDE->MoveObject(m_hObject, &IInfo.m_Point); } else { VEC_ADDSCALED(IInfo.m_Point, IQuery.m_To, vDir, -300.0f); m_pServerDE->MoveObject(m_hObject, &IQuery.m_To); } Metacmd++; break; } case 4: { DVector vColor; DFLOAT fAlpha = 1.0f; m_pServerDE->GetObjectColor(m_hObject,&vColor.x, &vColor.y, &vColor.z, &fAlpha); m_pServerDE->SetObjectColor(m_hObject,vColor.x,vColor.y,vColor.z, 1.0f); Metacmd++; break; } case 5: MC_FaceTarget(); break; case 6: ComputeState(); break; } return; } void Gideon::MC_Extra(const char *lpszText) { if(strncmp(lpszText,"powerup",7) == 0) { MagicPowerup(); } else { sprintf(m_szFireNode,"%s",lpszText); Fire(); } } DBOOL Gideon::Fire(DBOOL bAltFire) { DVector vPos, vDir; DRotation rRot; // Sanity check (GK 9/18/98) if (!m_InventoryMgr.GetCurrentWeapon()) return DFALSE; if(!m_pServerDE->GetModelNodeTransform(m_hObject,m_szFireNode,&vPos,&rRot)) { m_pServerDE->GetObjectPos(m_hObject,&vPos); VEC_COPY(vDir, m_MoveObj.GetForwardVector()); } else { VEC_SUB(vDir, m_vTargetPos, vPos); } VEC_NORM(vDir); m_pServerDE->AlignRotation(&rRot, &vDir, DNULL); DDWORD m_nFiredWeapon = m_InventoryMgr.FireCurrentWeapon(&vPos, &rRot, bAltFire); return DTRUE; } void Gideon::Save(HMESSAGEWRITE hWrite, DDWORD dwSaveFlags) { CServerDE* pServerDE = BaseClass::GetServerDE(); if (!pServerDE) return; pServerDE->WriteToMessageFloat(hWrite, m_fShieldDuration); pServerDE->WriteToMessageFloat(hWrite, m_fLastShield); pServerDE->WriteToMessageFloat(hWrite, m_fFullHealth); pServerDE->WriteToMessageFloat(hWrite, m_fLastPanic); } void Gideon::Load(HMESSAGEREAD hRead, DDWORD dwLoadFlags) { CServerDE* pServerDE = BaseClass::GetServerDE(); if (!pServerDE) return; m_fShieldDuration = pServerDE->ReadFromMessageFloat(hRead); m_fLastShield = pServerDE->ReadFromMessageFloat(hRead); m_fFullHealth = m_pServerDE->ReadFromMessageFloat(hRead); m_fLastPanic = m_pServerDE->ReadFromMessageFloat(hRead); } // ----------------------------------------------------------------------- // // // ROUTINE: CacheFiles // // PURPOSE: Cache resources used by the object // // ----------------------------------------------------------------------- // void Gideon::CacheFiles() { // Sanity checks... CServerDE* pServerDE = GetServerDE(); if (!pServerDE) return; if(!(pServerDE->GetServerFlags() & SS_CACHING)) { return; } if (!m_hObject) return; // Get the model filenames... char sModel[256] = { "" }; char sSkin[256] = { "" }; pServerDE->GetModelFilenames(m_hObject, sModel, 255, sSkin, 255); // Cache models... pServerDE->CacheFile(FT_MODEL, sModel); // Cache textures... pServerDE->CacheFile(FT_TEXTURE, sSkin); }