soldierobserver.cpp 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456
  1. /*
  2. ** Command & Conquer Renegade(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /***********************************************************************************************
  19. *** Confidential - Westwood Studios ***
  20. ***********************************************************************************************
  21. * *
  22. * Project Name : Commando *
  23. * *
  24. * $Archive:: /Commando/Code/Combat/soldierobserver.cpp $*
  25. * *
  26. * $Author:: Byon_g $*
  27. * *
  28. * $Modtime:: 12/17/01 3:06p $*
  29. * *
  30. * $Revision:: 112 $*
  31. * *
  32. *---------------------------------------------------------------------------------------------*
  33. * Functions: *
  34. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  35. #include "soldierobserver.h"
  36. #include "soldier.h"
  37. #include "action.h"
  38. #include "debug.h"
  39. #include "crandom.h"
  40. #include "cover.h"
  41. #include "wwaudio.h"
  42. #include "persistfactory.h"
  43. #include "combatchunkid.h"
  44. #include "combat.h"
  45. #include "gameobjobserver.h"
  46. #include "gameobjmanager.h"
  47. #include "weapons.h"
  48. #include "conversationmgr.h"
  49. #include "pathfind.h"
  50. #include "timemgr.h"
  51. #include "weaponbag.h"
  52. #include "wwprofile.h"
  53. #include "playertype.h"
  54. #define THINK_RATE 1
  55. #define THINK_ID 100123
  56. #define INNATE_ACTION_ID 9000000
  57. /*
  58. **
  59. */
  60. const char * SoldierAIStateNames[SoldierObserverClass::NUM_SOLDIER_AI_STATES] =
  61. {
  62. "Relaxed Idle", //SOLDIER_AI_RELAXED_IDLE = 0,
  63. "Alert Idle", //SOLDIER_AI_ALERT_IDLE,
  64. "Footsteps Heard", //SOLDIER_AI_FOOTSTEPS_HEARD,
  65. "Bullet Heard", //SOLDIER_AI_BULLET_HEARD,
  66. "Gunshot Heard", //SOLDIER_AI_GUNSHOT_HEARD,
  67. "Enemy Seen" //SOLDIER_AI_ENEMY_SEEN,
  68. };
  69. int StatePriorities[ SoldierObserverClass::NUM_SOLDIER_AI_STATES ] =
  70. {
  71. 10, // SOLDIER_AI_RELAXED_IDLE = 0,
  72. 20, // SOLDIER_AI_ALERTED_IDLE,
  73. 50, // SOLDIER_AI_FOOTSTEPS_HEARD,
  74. 50, // SOLDIER_AI_BULLET_HEARD,
  75. 70, // SOLDIER_AI_GUNSHOT_HEARD,
  76. 90, // SOLDIER_AI_ENEMY_SEEN,
  77. };
  78. typedef struct {
  79. float StateDuration; // How long we should stay in this state
  80. int NextState; // State to enter when we complete this state
  81. } SoldierAIStateData;
  82. SoldierAIStateData StateData[SoldierObserverClass::NUM_SOLDIER_AI_STATES] = {
  83. {9999, SoldierObserverClass::SOLDIER_AI_RELAXED_IDLE},
  84. {180, SoldierObserverClass::SOLDIER_AI_RELAXED_IDLE},
  85. {6, SoldierObserverClass::SOLDIER_AI_CONDITIONAL_IDLE},
  86. {20, SoldierObserverClass::SOLDIER_AI_ALERT_IDLE},
  87. {60, SoldierObserverClass::SOLDIER_AI_ALERT_IDLE}, // {60, SoldierObserverClass::SOLDIER_AI_BULLET_HEARD},
  88. {60, SoldierObserverClass::SOLDIER_AI_BULLET_HEARD},
  89. };
  90. /*
  91. ** Speech
  92. */
  93. const char* _SpeakEnemySeen[] =
  94. {
  95. "ThereHeIs02",
  96. "KillHim02",
  97. "GetHim02",
  98. "ForKane02",
  99. };
  100. const int _SpeakEnemySeenCount = (sizeof(_SpeakEnemySeen) / sizeof(_SpeakEnemySeen[0])) ;
  101. const char* Pick_Speak_EnemySeen(void)
  102. {
  103. int index = FreeRandom.Get_Int(_SpeakEnemySeenCount);
  104. return _SpeakEnemySeen[index];
  105. }
  106. Vector3 Random_Vector(float size)
  107. {
  108. Vector3 vect( FreeRandom.Get_Float( -size, size ),
  109. FreeRandom.Get_Float( -size, size ), 0 );
  110. return vect;
  111. }
  112. /*
  113. **
  114. */
  115. SoldierObserverClass::SoldierObserverClass( void ) :
  116. State( SOLDIER_AI_RELAXED_IDLE ),
  117. StateTimer( 0 ),
  118. HomeRadius( 9999999 ),
  119. ActionTimer( 0 ),
  120. CoverPosition( NULL ),
  121. CoveredAttack( false ),
  122. LastEvent( SOLDIER_AI_RELAXED_IDLE ),
  123. ConversationTimer( WWMath::Random_Float( 1.0F, 10.0F ) ),
  124. IsAlerted( false ),
  125. Aggressiveness( 0.5f ),
  126. TakeCoverProbability( 0.5f ),
  127. IsStationary( false ),
  128. LastWeaponIndex( 0 )
  129. {
  130. }
  131. SoldierObserverClass::~SoldierObserverClass( void )
  132. {
  133. Release_Cover_Position();
  134. }
  135. /*
  136. **
  137. */
  138. void SoldierObserverClass::Release_Cover_Position( void )
  139. {
  140. if ( CoverPosition != NULL ) {
  141. CoverManager::Release_Cover( CoverPosition );
  142. CoverPosition = NULL;
  143. }
  144. }
  145. /*
  146. **
  147. */
  148. SimplePersistFactoryClass<SoldierObserverClass, CHUNKID_SOLDIER_OBSERVER> _SoldierObserverPersistFactory;
  149. const PersistFactoryClass & SoldierObserverClass::Get_Factory (void) const
  150. {
  151. return _SoldierObserverPersistFactory;
  152. }
  153. enum {
  154. CHUNKID_PARENT = 410001836,
  155. CHUNKID_VARIABLES,
  156. CHUNKID_ENEMY_OBJ_REF,
  157. MICROCHUNKID_STATE = 1,
  158. MICROCHUNKID_STATE_TIMER,
  159. MICROCHUNKID_HOME_LOCATION,
  160. MICROCHUNKID_ALERT_POSITION,
  161. MICROCHUNKID_ACTION_TIMER,
  162. XXXMICROCHUNKID_ANIMATING,
  163. XXXMICROCHUNKID_MOVING,
  164. MICROCHUNKID_COVER_POSITION,
  165. MICROCHUNKID_COVERED_ATTACK,
  166. MICROCHUNKID_HOME_RADIUS,
  167. XXXMICROCHUNKID_ESCORT_OFFSET,
  168. MICROCHUNKID_CONVERSATION_TIMER,
  169. MICROCHUNKID_IS_ALERTED,
  170. MICROCHUNKID_AGGRESSIVENESS,
  171. MICROCHUNKID_TAKE_COVER_PROBABILITY,
  172. MICROCHUNKID_IS_STATIONARY,
  173. MICROCHUNKID_LAST_WEAPON_INDEX,
  174. };
  175. bool SoldierObserverClass::Save (ChunkSaveClass &csave)
  176. {
  177. csave.Begin_Chunk( CHUNKID_PARENT );
  178. PersistentGameObjObserverClass::Save( csave );
  179. csave.End_Chunk();
  180. if ( CombatManager::Are_Observers_Active() ) {
  181. csave.Begin_Chunk( CHUNKID_VARIABLES );
  182. WRITE_MICRO_CHUNK( csave, MICROCHUNKID_STATE, State );
  183. WRITE_MICRO_CHUNK( csave, MICROCHUNKID_STATE_TIMER, StateTimer );
  184. WRITE_MICRO_CHUNK( csave, MICROCHUNKID_HOME_LOCATION, HomeLocation );
  185. WRITE_MICRO_CHUNK( csave, MICROCHUNKID_HOME_RADIUS, HomeRadius );
  186. WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ALERT_POSITION, AlertPosition );
  187. WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ACTION_TIMER, ActionTimer );
  188. WRITE_MICRO_CHUNK( csave, MICROCHUNKID_COVER_POSITION, CoverPosition );
  189. WRITE_MICRO_CHUNK( csave, MICROCHUNKID_COVERED_ATTACK, CoveredAttack );
  190. WRITE_MICRO_CHUNK( csave, MICROCHUNKID_CONVERSATION_TIMER, ConversationTimer );
  191. WRITE_MICRO_CHUNK( csave, MICROCHUNKID_IS_ALERTED, IsAlerted );
  192. WRITE_MICRO_CHUNK( csave, MICROCHUNKID_AGGRESSIVENESS, Aggressiveness );
  193. WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TAKE_COVER_PROBABILITY, TakeCoverProbability );
  194. WRITE_MICRO_CHUNK( csave, MICROCHUNKID_IS_STATIONARY, IsStationary );
  195. WRITE_MICRO_CHUNK( csave, MICROCHUNKID_LAST_WEAPON_INDEX, LastWeaponIndex );
  196. csave.End_Chunk();
  197. if ( EnemyObject != NULL ) {
  198. csave.Begin_Chunk( CHUNKID_ENEMY_OBJ_REF );
  199. EnemyObject.Save( csave );
  200. csave.End_Chunk();
  201. }
  202. }
  203. // Don't need to save SubStateString, or LastEvent
  204. return true;
  205. }
  206. bool SoldierObserverClass::Load (ChunkLoadClass &cload)
  207. {
  208. while (cload.Open_Chunk()) {
  209. switch(cload.Cur_Chunk_ID()) {
  210. case CHUNKID_PARENT:
  211. PersistentGameObjObserverClass::Load( cload );
  212. break;
  213. case CHUNKID_VARIABLES:
  214. while (cload.Open_Micro_Chunk()) {
  215. switch(cload.Cur_Micro_Chunk_ID()) {
  216. READ_MICRO_CHUNK( cload, MICROCHUNKID_STATE, State );
  217. READ_MICRO_CHUNK( cload, MICROCHUNKID_STATE_TIMER, StateTimer );
  218. READ_MICRO_CHUNK( cload, MICROCHUNKID_HOME_LOCATION, HomeLocation );
  219. READ_MICRO_CHUNK( cload, MICROCHUNKID_HOME_RADIUS, HomeRadius );
  220. READ_MICRO_CHUNK( cload, MICROCHUNKID_ALERT_POSITION, AlertPosition );
  221. READ_MICRO_CHUNK( cload, MICROCHUNKID_ACTION_TIMER, ActionTimer );
  222. READ_MICRO_CHUNK( cload, MICROCHUNKID_COVER_POSITION, CoverPosition );
  223. READ_MICRO_CHUNK( cload, MICROCHUNKID_COVERED_ATTACK, CoveredAttack );
  224. READ_MICRO_CHUNK( cload, MICROCHUNKID_CONVERSATION_TIMER, ConversationTimer );
  225. READ_MICRO_CHUNK( cload, MICROCHUNKID_IS_ALERTED, IsAlerted );
  226. READ_MICRO_CHUNK( cload, MICROCHUNKID_AGGRESSIVENESS, Aggressiveness );
  227. READ_MICRO_CHUNK( cload, MICROCHUNKID_TAKE_COVER_PROBABILITY, TakeCoverProbability );
  228. READ_MICRO_CHUNK( cload, MICROCHUNKID_IS_STATIONARY, IsStationary );
  229. READ_MICRO_CHUNK( cload, MICROCHUNKID_LAST_WEAPON_INDEX, LastWeaponIndex );
  230. default:
  231. Debug_Say(("Unhandled Variable Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__));
  232. break;
  233. }
  234. cload.Close_Micro_Chunk();
  235. }
  236. break;
  237. case CHUNKID_ENEMY_OBJ_REF:
  238. EnemyObject.Load( cload );
  239. break;
  240. default:
  241. Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
  242. break;
  243. }
  244. cload.Close_Chunk();
  245. }
  246. if ( CoverPosition ) {
  247. REQUEST_POINTER_REMAP( (void **)&CoverPosition );
  248. }
  249. return true;
  250. }
  251. /*
  252. **
  253. */
  254. void SoldierObserverClass::Attach( GameObject * obj )
  255. {
  256. // Warning, Attach may not be called on loaded scripts
  257. }
  258. void SoldierObserverClass::Detach( GameObject * obj )
  259. {
  260. //
  261. // Clear the soldier's internal innate observer pointer
  262. //
  263. SmartGameObj *smart_game_obj = obj->As_SmartGameObj();
  264. if ( smart_game_obj != NULL ) {
  265. SoldierGameObj *soldier = smart_game_obj->As_SoldierGameObj();
  266. if ( soldier != NULL ) {
  267. if ( soldier->Get_Innate_Observer () == this ) {
  268. soldier->Clear_Innate_Observer();
  269. }
  270. }
  271. }
  272. GameObjObserverManager::Delete_Register( this );
  273. }
  274. void SoldierObserverClass::Created( GameObject* obj )
  275. {
  276. SmartGameObj* smart = obj->As_SmartGameObj();
  277. WWASSERT( smart != NULL );
  278. // Debug_Say(("Innate soldier [%d] created\n", obj->Get_ID()));
  279. smart->Get_Position( &HomeLocation );
  280. // Enable enemy sensory
  281. smart->Set_Enemy_Seen_Enabled(true);
  282. obj->Start_Observer_Timer( Get_ID(), THINK_RATE, THINK_ID );
  283. Aggressiveness = smart->As_SoldierGameObj()->Get_Definition().InnateAggressiveness;
  284. // Modify aggressiveness based on difficulty
  285. switch ( CombatManager::Get_Difficulty_Level() ) {
  286. case 0:
  287. if ( Aggressiveness > 0.25f ) {
  288. Aggressiveness -= 0.25f;
  289. }
  290. break;
  291. case 1:
  292. // no change
  293. break;
  294. case 2:
  295. Aggressiveness += 0.25f;
  296. if ( Aggressiveness > 1 ) {
  297. Aggressiveness = 1;
  298. }
  299. break;
  300. };
  301. TakeCoverProbability = smart->As_SoldierGameObj()->Get_Definition().InnateTakeCoverProbability;
  302. IsStationary = smart->As_SoldierGameObj()->Get_Definition().InnateIsStationary;
  303. }
  304. void SoldierObserverClass::Destroyed(GameObject* obj)
  305. {
  306. // Debug_Say(("Innate soldier [%d] destroyed\n", obj->Get_ID()));
  307. Release_Cover_Position();
  308. }
  309. void SoldierObserverClass::Timer_Expired(GameObject* obj, int timer_id)
  310. {
  311. SmartGameObj* smart = obj->As_SmartGameObj();
  312. WWASSERT( smart != NULL );
  313. SoldierGameObj* soldier = smart->As_SoldierGameObj();
  314. WWASSERT(soldier != NULL);
  315. if (timer_id == THINK_ID) {
  316. obj->Start_Observer_Timer( Get_ID(), THINK_RATE, THINK_ID );
  317. Think(soldier);
  318. }
  319. }
  320. void SoldierObserverClass::Killed(GameObject* obj, GameObject* killer)
  321. {
  322. // Debug_Say(("Innate soldier [%d] killed by [%d]\n", obj->Get_ID(), killer ? killer->Get_ID() : -1));
  323. }
  324. void SoldierObserverClass::Damaged(GameObject* obj, GameObject* damager, float amount)
  325. {
  326. // Debug_Say(("Innate soldier [%d] damaged by [%d]\n", obj->Get_ID(), damager ? damager->Get_ID() : -1));
  327. if ( obj->Is_Hibernating() ) {
  328. return;
  329. }
  330. SmartGameObj* smart = obj->As_SmartGameObj();
  331. WWASSERT( smart != NULL );
  332. SoldierGameObj* soldier = smart->As_SoldierGameObj();
  333. WWASSERT(soldier != NULL);
  334. if (soldier->Is_Innate_Enabled(SOLDIER_INNATE_EVENT_BULLET_HEARD)) {
  335. //Debug_Say(( "Damaged\n" ));
  336. Vector3 pos;
  337. soldier->Get_Position( &pos );
  338. if ( damager != NULL ) {
  339. if ( FreeRandom.Get_Float() < 0.8 ) { // 80% chance of knowing the source
  340. damager->Get_Position( &pos );
  341. }
  342. }
  343. Set_State(soldier, SOLDIER_AI_BULLET_HEARD, pos);
  344. }
  345. // If I am attacking an object, and I am damaged by a closer object, switch targets
  346. if ( State == SOLDIER_AI_ENEMY_SEEN ) {
  347. GameObject * enemy = EnemyObject;
  348. if ( damager != NULL && enemy != damager ) {
  349. Vector3 my_pos;
  350. soldier->Get_Position( &my_pos );
  351. Vector3 pos;
  352. float target_range = 1000000;
  353. if ( enemy ) {
  354. enemy->Get_Position( &pos );
  355. pos -= my_pos;
  356. target_range = pos.Length();
  357. }
  358. damager->Get_Position( &pos );
  359. pos -= my_pos;
  360. if ( pos.Length() < target_range ) {
  361. PhysicalGameObj * pdamager = damager->As_PhysicalGameObj();
  362. if ( pdamager && pdamager->Is_Enemy( soldier ) ) {
  363. Debug_Say(( "Switching to nearer target\n" ));
  364. EnemyObject = damager;
  365. ActionTimer = 0; // Act on it now
  366. }
  367. }
  368. }
  369. }
  370. }
  371. void SoldierObserverClass::Sound_Heard(GameObject* obj, const CombatSound& sound)
  372. {
  373. if ( obj->Is_Hibernating() ) {
  374. return;
  375. }
  376. SmartGameObj* smart = obj->As_SmartGameObj();
  377. WWASSERT( smart != NULL );
  378. SoldierGameObj* soldier = smart->As_SoldierGameObj();
  379. WWASSERT(soldier != NULL);
  380. PhysicalGameObj * creator = NULL;
  381. if ( sound.Creator != NULL ) {
  382. creator = sound.Creator->As_PhysicalGameObj();
  383. }
  384. bool state_changed = false;
  385. switch (sound.Type) {
  386. case SOUND_TYPE_BULLET_HIT:
  387. {
  388. if (soldier->Is_Innate_Enabled(SOLDIER_INNATE_EVENT_BULLET_HEARD)) {
  389. Vector3 pos;
  390. pos = sound.Position;
  391. if ( creator && FreeRandom.Get_Float() < 0.7 ) { // 70% chance of knowing the source
  392. creator->Get_Position( &pos );
  393. }
  394. state_changed = Set_State(soldier, SOLDIER_AI_BULLET_HEARD, pos);
  395. }
  396. }
  397. break;
  398. case SOUND_TYPE_GUNSHOT:
  399. // Don't hear friendly
  400. if ( (creator == NULL) || !soldier->Is_Teammate( creator ) ) {
  401. if (soldier->Is_Innate_Enabled(SOLDIER_INNATE_EVENT_GUNSHOT_HEARD)) {
  402. state_changed = Set_State(soldier, SOLDIER_AI_GUNSHOT_HEARD, sound.Position);
  403. }
  404. }
  405. break;
  406. case SOUND_TYPE_FOOTSTEPS:
  407. if ( (creator == NULL) || !soldier->Is_Teammate( creator ) ) {
  408. if (soldier->Is_Innate_Enabled(SOLDIER_INNATE_EVENT_FOOTSTEP_HEARD)) {
  409. //Debug_Say(( "Heard Footsteps\n" ));
  410. state_changed = Set_State(soldier, SOLDIER_AI_FOOTSTEPS_HEARD, sound.Position);
  411. }
  412. }
  413. break;
  414. default:
  415. // Debug_Say(("Innate soldier [%d] unrecognized sound!\n", obj->Get_ID()));
  416. break;
  417. }
  418. if ( state_changed ) {
  419. Notify_Neighbors_Sound( soldier, sound );
  420. }
  421. }
  422. void SoldierObserverClass::Enemy_Seen(GameObject* obj, GameObject* enemy)
  423. {
  424. if ( obj->Is_Hibernating() ) {
  425. return;
  426. }
  427. // WWASSERT( obj != enemy );
  428. // only see the enemy if it has health
  429. WWASSERT( enemy );
  430. PhysicalGameObj * p_enemy = enemy->As_PhysicalGameObj();
  431. if ( p_enemy == NULL || p_enemy->Get_Defense_Object()->Get_Health() <= 0 ) {
  432. // Debug_Say(( "I see dead people\n" ));
  433. return;
  434. }
  435. GameObject * curr_enemy = EnemyObject;
  436. if ( curr_enemy != NULL && curr_enemy != enemy ) {
  437. // take the closer enemy
  438. Vector3 old_pos;
  439. curr_enemy->Get_Position( &old_pos );
  440. Vector3 new_pos;
  441. enemy->Get_Position( &new_pos );
  442. Vector3 my_pos;
  443. obj->Get_Position( &my_pos );
  444. old_pos -= my_pos;
  445. new_pos -= my_pos;
  446. if ( new_pos.Length2() > old_pos.Length2() ) {
  447. // Debug_Say(( "New guy is too far away!\n" ));
  448. return;
  449. }
  450. // Debug_Say(( "Found New guy!!\n" ));
  451. }
  452. SmartGameObj* smart = obj->As_SmartGameObj();
  453. WWASSERT( smart != NULL );
  454. SoldierGameObj* soldier = smart->As_SoldierGameObj();
  455. WWASSERT(soldier != NULL);
  456. // Debug_Say(("Innate soldier [%d] seen enemy [%d]\n", obj->Get_ID(), enemy->Get_ID()));
  457. bool state_changed = false;
  458. if (soldier->Is_Innate_Enabled(SOLDIER_INNATE_EVENT_ENEMY_SEEN)) {
  459. //Debug_Say(( "Seen Enemy\n" ));
  460. state_changed = Set_State(soldier, SOLDIER_AI_ENEMY_SEEN, Vector3(0,0,0), enemy);
  461. }
  462. if ( state_changed ) {
  463. Notify_Neighbors_Enemy( soldier, enemy );
  464. }
  465. }
  466. void SoldierObserverClass::Custom( GameObject * obj, int type, int param, GameObject * sender )
  467. {
  468. if ( type == CUSTOM_EVENT_ATTACK_ARRIVED ) {
  469. // Debug_Say(( "Attack Arrvied event %d\n", param ));
  470. }
  471. }
  472. void SoldierObserverClass::Action_Complete( GameObject * obj, int action_id, ActionCompleteReason complete_reason )
  473. {
  474. if ( action_id == INNATE_ACTION_ID ) {
  475. SubStateString += "\nComplete";
  476. // ActionTimer = MIN( ActionTimer, 10 ); // When he completes, only stay in the action state for 5 more seconds
  477. // if I had an enemy target, who was just killed...
  478. PhysicalGameObj* enemy = (PhysicalGameObj*)EnemyObject.Get_Ptr();
  479. if ( enemy && State == SOLDIER_AI_ENEMY_SEEN ) {
  480. if ( enemy->Get_Defense_Object()->Get_Health() <= 0 ) {
  481. // Debug_Say(( "Target dead, moving on\n" ));
  482. StateTimer = 100000; // Leave current state
  483. EnemyObject = NULL;
  484. }
  485. }
  486. if ( complete_reason == ACTION_COMPLETE_NORMAL ||
  487. complete_reason == ACTION_COMPLETE_PATH_BAD_START ||
  488. complete_reason == ACTION_COMPLETE_PATH_BAD_DEST )
  489. {
  490. if ( complete_reason == ACTION_COMPLETE_PATH_BAD_DEST ) {
  491. // Pick a new action within 1 to 2 seconds
  492. ActionTimer = MIN( ActionTimer, WWMath::Random_Float( 1.0F, 2.0F ) );
  493. } else {
  494. // Pick a new action within 2 to 3 seconds
  495. ActionTimer = MIN( ActionTimer, WWMath::Random_Float( 2.0F, 3.0F ) );
  496. }
  497. if ( State > SOLDIER_AI_ALERT_IDLE ) {
  498. StateTimer = 100000; // Leave current state
  499. }
  500. }
  501. }
  502. }
  503. /*
  504. **
  505. */
  506. void SoldierObserverClass::State_Changed( SoldierGameObj * soldier )
  507. {
  508. switch ( State ) {
  509. case SOLDIER_AI_RELAXED_IDLE:
  510. IsAlerted = false;
  511. break;
  512. case SOLDIER_AI_FOOTSTEPS_HEARD:
  513. // Leave it was it was
  514. break;
  515. case SOLDIER_AI_ALERT_IDLE:
  516. case SOLDIER_AI_BULLET_HEARD:
  517. case SOLDIER_AI_GUNSHOT_HEARD:
  518. case SOLDIER_AI_ENEMY_SEEN:
  519. IsAlerted = true;
  520. break;
  521. }
  522. // Remeber the last selected weapon
  523. if ( soldier->Get_Weapon_Bag()->Get_Index() != 0 ) {
  524. LastWeaponIndex = soldier->Get_Weapon_Bag()->Get_Index();
  525. }
  526. if ( IsAlerted ) {
  527. soldier->Get_Weapon_Bag()->Select_Index( LastWeaponIndex );
  528. } else {
  529. soldier->Get_Weapon_Bag()->Select_Index( 0 );
  530. }
  531. if ( State == SOLDIER_AI_ALERT_IDLE ) {
  532. soldier->Get_Human_State()->Drop_Weapon();
  533. }
  534. ActionTimer = 0;
  535. }
  536. /*
  537. **
  538. */
  539. bool SoldierObserverClass::Set_State( SoldierGameObj * soldier, int state, const Vector3& location, GameObject* enemy)
  540. {
  541. // WWASSERT( soldier != enemy );
  542. LastEvent = state;
  543. int old_state = State;
  544. // If this is a more extreme state, act surprised!
  545. if (StatePriorities[State] < StatePriorities[state]) {
  546. // If we have seen the enemy then make a comment.
  547. if (state == SOLDIER_AI_ENEMY_SEEN) {
  548. // Speak(obj, Pick_Speak_EnemySeen());
  549. } else if (state != SOLDIER_AI_FOOTSTEPS_HEARD) {
  550. // Duck
  551. // Action_Play_Animation(obj, "s_a_human.h_a_j21c");
  552. // Speak(obj, "Q_Huh02");
  553. }
  554. // Debug_Say(("Switching to State %d from %d\n", state, State));
  555. AlertPosition = location;
  556. if ( State != state ) {
  557. State = state;
  558. State_Changed( soldier );
  559. }
  560. // Debug_Say(( "New Innate State!\n" ));
  561. }
  562. // If this is not a less extreme state, reset state timer
  563. if (StatePriorities[State] <= StatePriorities[state]) {
  564. if ( State != state ) {
  565. State = state;
  566. State_Changed( soldier );
  567. }
  568. StateTimer = 0;
  569. if ( ( state == SOLDIER_AI_BULLET_HEARD ) ||
  570. ( state == SOLDIER_AI_GUNSHOT_HEARD ) ) {
  571. if ( ActionTimer > 1 ) {
  572. ActionTimer = 1;
  573. }
  574. }
  575. // ActionTimer = 0; no
  576. #if 01
  577. // Only take the new pos if closer
  578. Vector3 my_pos;
  579. soldier->Get_Position(&my_pos);
  580. Vector3 old_pos = AlertPosition - my_pos;
  581. Vector3 new_pos = location - my_pos;
  582. if ( old_pos.Length2() > new_pos.Length2() )
  583. #endif
  584. {
  585. AlertPosition = location;
  586. }
  587. if ( enemy != NULL ) {
  588. EnemyObject = enemy->As_PhysicalGameObj();
  589. } else {
  590. EnemyObject = NULL;
  591. }
  592. Think(soldier, (State != old_state) );
  593. }
  594. //
  595. // Reset the conversation timer if the state has changed
  596. //
  597. if ( old_state != State ) {
  598. if ( State == SOLDIER_AI_ENEMY_SEEN ) {
  599. ConversationTimer = WWMath::Random_Float( 5.0F, 30.0F );
  600. } else {
  601. ConversationTimer = WWMath::Random_Float( 20.0F, 60.0F );
  602. }
  603. }
  604. //
  605. // Decide which (if any) dialogue the soldier should say
  606. //
  607. if ( ( old_state == SOLDIER_AI_RELAXED_IDLE ) || ( old_state == SOLDIER_AI_ALERT_IDLE ) ) {
  608. if ( State == SOLDIER_AI_FOOTSTEPS_HEARD ||
  609. State == SOLDIER_AI_BULLET_HEARD ||
  610. State == SOLDIER_AI_GUNSHOT_HEARD)
  611. {
  612. soldier->Say_Dialogue( DIALOG_STATE_FROM_IDLE_TO_SEARCH );
  613. } else if ( State == SOLDIER_AI_ENEMY_SEEN ) {
  614. soldier->Say_Dialogue( DIALOG_STATE_FROM_IDLE_TO_COMBAT );
  615. }
  616. } else if ( old_state == SOLDIER_AI_ENEMY_SEEN ) {
  617. if ( State == SOLDIER_AI_FOOTSTEPS_HEARD ||
  618. State == SOLDIER_AI_BULLET_HEARD ||
  619. State == SOLDIER_AI_GUNSHOT_HEARD)
  620. {
  621. soldier->Say_Dialogue( DIALOG_STATE_FROM_COMBAT_TO_SEARCH );
  622. } else if ( ( old_state == SOLDIER_AI_RELAXED_IDLE ) || ( old_state == SOLDIER_AI_ALERT_IDLE ) ) {
  623. soldier->Say_Dialogue( DIALOG_STATE_FROM_COMBAT_TO_IDLE );
  624. }
  625. } else {
  626. if ( State == SOLDIER_AI_ENEMY_SEEN ) {
  627. soldier->Say_Dialogue( DIALOG_STATE_FROM_SEARCH_TO_COMBAT );
  628. } else if ( ( old_state == SOLDIER_AI_RELAXED_IDLE ) || ( old_state == SOLDIER_AI_ALERT_IDLE ) ) {
  629. soldier->Say_Dialogue( DIALOG_STATE_FROM_SEARCH_TO_IDLE );
  630. }
  631. }
  632. return (old_state != State);
  633. }
  634. void SoldierObserverClass::Notify_Neighbors_Sound( SoldierGameObj * soldier, const CombatSound & sound )
  635. {
  636. // Notify nearby neighbors of my knowledge
  637. // for all physicalgameobjs
  638. Vector3 my_pos;
  639. soldier->Get_Position( &my_pos );
  640. SLNode<SmartGameObj> *objnode;
  641. for ( objnode = GameObjManager::Get_Smart_Game_Obj_List()->Head(); objnode; objnode = objnode->Next()) {
  642. SmartGameObj *obj = objnode->Data()->As_SmartGameObj();
  643. if ( obj ) {
  644. if ( obj == soldier ) continue;
  645. if ( obj->Get_Player_Type() != soldier->Get_Player_Type() ) continue;
  646. Vector3 obj_pos;
  647. obj->Get_Position( &obj_pos );
  648. obj_pos -= my_pos;
  649. if ( obj_pos.Length() <= 5 ) {
  650. // Notify him of my info
  651. const GameObjObserverList & observer_list = obj->Get_Observers();
  652. for( int index = 0; index < observer_list.Count(); index++ ) {
  653. observer_list[ index ]->Sound_Heard( obj, sound );
  654. }
  655. }
  656. }
  657. }
  658. }
  659. void SoldierObserverClass::Notify_Neighbors_Enemy( SoldierGameObj * soldier, GameObject * enemy )
  660. {
  661. // Notify nearby neighbors of my knowledge
  662. // for all physicalgameobjs
  663. Vector3 my_pos;
  664. soldier->Get_Position( &my_pos );
  665. SLNode<SmartGameObj> *objnode;
  666. for ( objnode = GameObjManager::Get_Smart_Game_Obj_List()->Head(); objnode; objnode = objnode->Next()) {
  667. SmartGameObj *obj = objnode->Data()->As_SmartGameObj();
  668. if ( obj ) {
  669. if ( obj == soldier ) continue;
  670. if ( obj->Get_Player_Type() != soldier->Get_Player_Type() ) continue;
  671. Vector3 obj_pos;
  672. obj->Get_Position( &obj_pos );
  673. obj_pos -= my_pos;
  674. if ( obj_pos.Length() <= 5 ) {
  675. // Notify him of my info
  676. const GameObjObserverList & observer_list = obj->Get_Observers();
  677. for( int index = 0; index < observer_list.Count(); index++ ) {
  678. observer_list[ index ]->Enemy_Seen( obj, enemy );
  679. }
  680. }
  681. }
  682. }
  683. }
  684. void SoldierObserverClass::Poked( GameObject * obj, GameObject * poker )
  685. {
  686. if ( obj != NULL && obj->As_PhysicalGameObj() != NULL ) {
  687. PhysicalGameObj *physical_obj = obj->As_PhysicalGameObj();
  688. if ( physical_obj->As_SoldierGameObj() != NULL ) {
  689. bool face_poker = false;
  690. //
  691. // Get the soldier to say one of its pre-canned remarks
  692. //
  693. SoldierGameObj *soldier = physical_obj->As_SoldierGameObj();
  694. float duration = 2.0F;
  695. switch ( State )
  696. {
  697. case SOLDIER_AI_RELAXED_IDLE:
  698. case SOLDIER_AI_ALERT_IDLE:
  699. soldier->Say_Dialogue( DIALOG_ON_POKE_IDLE );
  700. face_poker = true;
  701. break;
  702. case SOLDIER_AI_FOOTSTEPS_HEARD:
  703. case SOLDIER_AI_BULLET_HEARD:
  704. case SOLDIER_AI_GUNSHOT_HEARD:
  705. soldier->Say_Dialogue( DIALOG_ON_POKE_SEARCH );
  706. face_poker = true;
  707. break;
  708. case SOLDIER_AI_ENEMY_SEEN:
  709. soldier->Say_Dialogue( DIALOG_ON_POKE_COMBAT );
  710. break;
  711. }
  712. //
  713. // Increment the duration by a small random amount
  714. //
  715. duration += WWMath::Random_Float( 0.5F, 2.0F );
  716. PhysicalGameObj *physical_poker_obj = poker->As_PhysicalGameObj();
  717. if ( physical_poker_obj != NULL ) {
  718. //
  719. // Get the position of the player who poked us
  720. //
  721. Vector3 poker_pos = physical_poker_obj->Get_Bullseye_Position();
  722. //
  723. // Make the soldier turn his body to face the speaker
  724. //
  725. if ( face_poker ) {
  726. ActionParamsStruct parameters;
  727. parameters.Priority = 20;
  728. parameters.ObserverID = Get_ID();
  729. parameters.Set_Face_Location( poker_pos, duration );
  730. soldier->Get_Action()->Face_Location( parameters );
  731. }
  732. //
  733. // Turn the soldier's head to look at the speaker
  734. //
  735. soldier->Look_At( poker_pos, duration );
  736. }
  737. }
  738. }
  739. return ;
  740. }
  741. void SoldierObserverClass::Think( SoldierGameObj * soldier, bool is_new_state )
  742. {
  743. WWPROFILE( "SoldierObserver Think" );
  744. WWASSERT( soldier != NULL );
  745. //
  746. // We can't switch states if the soldier is "busy". Busy usually
  747. // means he's going up an elevator, climbing a ladder, or opening a door.
  748. //
  749. if ( soldier->Get_Action()->Is_Busy() ) {
  750. return ;
  751. }
  752. // Check how long we have been in this state, and if we should switch
  753. StateTimer += 1;
  754. if (StateTimer >= StateData[State].StateDuration) {
  755. // Debug_Say(("Soldier relaxing from state %d to %d\n", State, StateData[State].NextState));
  756. Action_Reset( soldier ); // reset the old state
  757. State = StateData[State].NextState;
  758. if ( State == SOLDIER_AI_CONDITIONAL_IDLE ) {
  759. State = ( IsAlerted ) ? SOLDIER_AI_ALERT_IDLE : SOLDIER_AI_RELAXED_IDLE;
  760. }
  761. State_Changed( soldier );
  762. StateTimer = 0;
  763. }
  764. if ( ( State != SOLDIER_AI_RELAXED_IDLE ) && ( State != SOLDIER_AI_ALERT_IDLE ) ) {
  765. soldier->Reset_Loiter_Delay();
  766. }
  767. // Check if time for another state action
  768. if (soldier->Is_Innate_Enabled(SOLDIER_INNATE_ACTIONS)) {
  769. ActionTimer -= 1;
  770. if (ActionTimer <= 0) {
  771. State_Act(soldier, is_new_state);
  772. }
  773. }
  774. //
  775. // Allow this unit to start a conversation regardless of the state he's in
  776. //
  777. if ( soldier->Is_Human_Controlled () == false && soldier->Is_In_Conversation () == false ) {
  778. //
  779. // Is it time to start a new conversation?
  780. //
  781. ConversationTimer -= 1.0F;
  782. if (ConversationTimer <= 0) {
  783. //
  784. // Don't start an innate conversation if there are already
  785. // conversations taking place
  786. //
  787. #ifdef ENABLE_INNATE_CONVERSATIONS
  788. if ( ConversationMgrClass::Get_Active_Conversation_Count () == 0 ) {
  789. ConversationMgrClass::Start_Conversation( soldier );
  790. }
  791. #endif //ENABLE_INNATE_CONVERSATIONS
  792. Reset_Conversation_Timer();
  793. }
  794. }
  795. }
  796. void SoldierObserverClass::Get_Information( StringClass & string )
  797. {
  798. StringClass temp;
  799. string += "-----------\n";
  800. temp.Format( "State: %s\n", SoldierAIStateNames[ State ] );
  801. string += temp;
  802. temp.Format( "Sub: %s\n", SubStateString );
  803. string += temp;
  804. if ( IsStationary ) {
  805. string += "Stationary\n";
  806. }
  807. if ( State == SOLDIER_AI_ENEMY_SEEN ) {
  808. temp.Format( "Attacking: %d\n", EnemyObject.Get_Ptr() ? EnemyObject.Get_Ptr()->Get_ID() : 0 );
  809. string += temp;
  810. }
  811. temp.Format( "Last: %s\n", SoldierAIStateNames[ LastEvent ] );
  812. string += temp;
  813. temp.Format( "Act Time: %f\n", ActionTimer );
  814. string += temp;
  815. if ( CoverPosition != NULL ) {
  816. temp.Format( "Has Cover Pos\n" );
  817. string += temp;
  818. }
  819. }
  820. void SoldierObserverClass::Reset_Conversation_Timer( void )
  821. {
  822. ConversationTimer = WWMath::Random_Float( 20.0F, 60.0F );
  823. }
  824. /***********************************************************************************************
  825. ** Innate AI State Actions
  826. ***********************************************************************************************/
  827. void SoldierObserverClass::State_Act( SoldierGameObj * soldier, bool is_new_state )
  828. {
  829. WWASSERT( soldier != NULL );
  830. switch (State) {
  831. case SOLDIER_AI_RELAXED_IDLE:
  832. State_Act_Idle( soldier );
  833. break;
  834. case SOLDIER_AI_FOOTSTEPS_HEARD:
  835. State_Act_Footsteps_Heard( soldier );
  836. break;
  837. case SOLDIER_AI_ALERT_IDLE:
  838. case SOLDIER_AI_BULLET_HEARD:
  839. State_Act_Bullet_Heard( soldier, is_new_state );
  840. break;
  841. case SOLDIER_AI_GUNSHOT_HEARD:
  842. State_Act_Gunshot_Heard( soldier );
  843. break;
  844. case SOLDIER_AI_ENEMY_SEEN:
  845. State_Act_Attack( soldier );
  846. break;
  847. }
  848. }
  849. #define IDLE_WALK_DISTANCE 6
  850. #define IDLE_WALK_SPEED 0.3f // was 0.2f
  851. #define ALERTED_RUN_DISTANCE 6
  852. #define ALERTED_RUN_SPEED 0.9f
  853. #define ALERTED_RUN_RANGE 2
  854. /*
  855. **
  856. */
  857. void SoldierObserverClass::State_Act_Idle( SoldierGameObj * soldier )
  858. {
  859. ActionTimer = 5; // Will probably be overridden
  860. Vector3 position;
  861. soldier->Get_Position(&position);
  862. Release_Cover_Position();
  863. if ( FreeRandom.Get_Float() < 0.5f ) { // 50% chance of facing a random direction
  864. SubStateString = "Facing Random";
  865. position += Random_Vector(1);
  866. if ( COMBAT_STAR ) {
  867. SubStateString = "Facing Star";
  868. COMBAT_STAR->Get_Position( &position );
  869. }
  870. Action_Face_Location( soldier, position, AI_STATE_IDLE );
  871. ActionTimer = FreeRandom.Get_Float(4, 6); //Delay 4 to 6 seconds
  872. } else if ( !IsStationary ) { // 50% chance of walking to a random point
  873. SubStateString = "Random Walk";
  874. PathfindClass *pathfind = PathfindClass::Get_Instance(); // Lookup a safe random position to walk to
  875. if ( pathfind->Find_Random_Spot( position, IDLE_WALK_DISTANCE, &position ) ) {
  876. Action_Goto_Location( soldier, position, AI_STATE_IDLE, IDLE_WALK_SPEED, 0.3f );
  877. }
  878. ActionTimer = FreeRandom.Get_Float(7, 10); //Delay 7 to 10 seconds
  879. }
  880. Look_Random( soldier, ActionTimer );
  881. }
  882. /*
  883. **
  884. */
  885. void SoldierObserverClass::State_Act_Footsteps_Heard( SoldierGameObj * soldier )
  886. {
  887. ActionTimer = FreeRandom.Get_Float(3, 4); //Delay 7 to 10 seconds
  888. Release_Cover_Position();
  889. bool walking = false;
  890. if ( FreeRandom.Get_Float() < Aggressiveness && !IsStationary ) {
  891. SubStateString = "Walk To Footsteps";
  892. Vector3 position;
  893. PathfindClass *pathfind = PathfindClass::Get_Instance();
  894. if ( pathfind->Find_Random_Spot( AlertPosition, 2, &position ) ) {
  895. Action_Goto_Location( soldier, position, AI_STATE_IDLE, IDLE_WALK_SPEED, 0.5f );
  896. walking = true;
  897. }
  898. }
  899. if ( !walking ) {
  900. SubStateString = "Face Footsteps";
  901. Action_Face_Location( soldier, AlertPosition, AI_STATE_SEARCH );
  902. }
  903. Look_Random( soldier, 0 ); // No random looking!
  904. }
  905. /*
  906. **
  907. */
  908. void SoldierObserverClass::State_Act_Bullet_Heard( SoldierGameObj * soldier, bool is_new_state )
  909. {
  910. ActionTimer = FreeRandom.Get_Float(5,10);
  911. ActionTimer = FreeRandom.Get_Float(2,4);
  912. soldier->Get_Human_State()->Raise_Weapon(); // Keep the gun up
  913. Vector3 current_position;
  914. soldier->Get_Position(&current_position);
  915. Vector3 walk_position;
  916. #define RANGE 10 // Dive with probability tied to bullet distance; 100% at 10m, 0 percent at 20m
  917. float range = (current_position - AlertPosition).Length();
  918. float percent = WWMath::Clamp( (1 - ((range-RANGE)/RANGE)), 0, 1 );
  919. bool dive = is_new_state && (FreeRandom.Get_Float() < percent) && !IsStationary;
  920. if ( dive ) {
  921. SubStateString = "Dive Away"; // Dive away
  922. ActionTimer = FreeRandom.Get_Float(2,4);
  923. Vector3 other_dir = 2*current_position - AlertPosition;
  924. Action_Dive( soldier, other_dir );
  925. } else {
  926. bool done = false;
  927. if ( !IsStationary ) {
  928. done = Take_Cover( soldier );
  929. }
  930. if ( !done ) {
  931. if ( FreeRandom.Get_Float() < TakeCoverProbability || IsStationary ) {
  932. SubStateString = "Face Random Crouched"; // Face and crouch
  933. walk_position = current_position + Random_Vector(1);
  934. if ( COMBAT_STAR ) {
  935. SubStateString = "Face Star Crouched";
  936. COMBAT_STAR->Get_Position( &walk_position );
  937. }
  938. Action_Face_Location( soldier, walk_position, AI_STATE_IDLE, true );
  939. } else {
  940. SubStateString = "Run To";
  941. // Lookup a safe random position to run to
  942. PathfindClass *pathfind = PathfindClass::Get_Instance();
  943. if ( pathfind->Find_Random_Spot( AlertPosition, ALERTED_RUN_DISTANCE, &walk_position ) ) {
  944. Action_Goto_Location( soldier, walk_position, AI_STATE_SEARCH, ALERTED_RUN_SPEED, ALERTED_RUN_RANGE, true );
  945. }
  946. }
  947. }
  948. }
  949. Look_Random( soldier, ActionTimer );
  950. }
  951. /*
  952. **
  953. */
  954. void SoldierObserverClass::State_Act_Gunshot_Heard( SoldierGameObj * soldier )
  955. {
  956. soldier->Get_Human_State()->Raise_Weapon(); // Keep the gun up
  957. Look_Random( soldier, 0 );
  958. ActionTimer = FreeRandom.Get_Float( 5, 10 );
  959. if ( !IsStationary ) {
  960. bool done = Take_Cover( soldier, true, AlertPosition );
  961. if ( !done ) {
  962. SubStateString = "Run To Random";
  963. PathfindClass *pathfind = PathfindClass::Get_Instance();
  964. //
  965. // Lookup a safe random position to run to
  966. //
  967. Vector3 position = AlertPosition;
  968. if (pathfind->Find_Random_Spot( AlertPosition, ALERTED_RUN_DISTANCE, &position )) {
  969. Action_Goto_Location_Facing(soldier, position, AI_STATE_SEARCH, AlertPosition, ALERTED_RUN_SPEED, ALERTED_RUN_RANGE);
  970. } else {
  971. //
  972. // Wasn't possible to run to a random location near the alert position,
  973. // so simply move around your local area a little bit and look at the alert position
  974. //
  975. Vector3 curr_pos;
  976. soldier->Get_Position (&curr_pos);
  977. if (pathfind->Find_Random_Spot( curr_pos, IDLE_WALK_DISTANCE, &position )) {
  978. Action_Goto_Location_Facing( soldier, position, AI_STATE_SEARCH, AlertPosition, IDLE_WALK_SPEED, 0.5F );
  979. }
  980. //Release_Cover_Position();
  981. //SubStateString = "Face gunshot";
  982. //Action_Face_Location( soldier, AlertPosition, AI_STATE_SEARCH );
  983. }
  984. }
  985. } else {
  986. Release_Cover_Position();
  987. SubStateString = "Face gunshot";
  988. Action_Face_Location( soldier, AlertPosition, AI_STATE_SEARCH );
  989. }
  990. }
  991. /*
  992. **
  993. */
  994. void SoldierObserverClass::State_Act_Attack( SoldierGameObj * soldier )
  995. {
  996. WWASSERT( soldier != NULL );
  997. soldier->Get_Human_State()->Raise_Weapon(); // Keep the gun up
  998. ActionTimer = FreeRandom.Get_Float( 5, 8 );
  999. Look_Random( soldier, 0 );
  1000. #define RUN_SPEED 0.9f
  1001. #define WALK_SPEED 0.2f
  1002. bool done = false;
  1003. // OK, here's the attack plan....
  1004. // Periodically (every 5 seconds?), pick a new attack action. 4 Attack options are:
  1005. // If in a attack point, jump back to the cover
  1006. // If in a cover spot, possibily jump out and attack (goto attack point)
  1007. // If there is a new cover spot available, goto it
  1008. // Pick a random point and goto it
  1009. PhysicalGameObj* enemy = (PhysicalGameObj*)EnemyObject.Get_Ptr();
  1010. // WWASSERT( soldier != enemy );
  1011. if ( enemy == NULL ) {
  1012. StateTimer = 100000; // Leave this state
  1013. return;
  1014. }
  1015. Vector3 enemy_pos;
  1016. enemy->Get_Position(&enemy_pos);
  1017. float weapon_range = 60.0f;
  1018. if ( soldier->Get_Weapon() ) {
  1019. weapon_range = soldier->Get_Weapon()->Get_Range();
  1020. }
  1021. Vector3 current_position;
  1022. soldier->Get_Position(&current_position);
  1023. if ( CoverPosition != NULL ) {
  1024. if ( CoveredAttack == true ) {
  1025. SubStateString = "Return to Cover";
  1026. Vector3 cover_position = CoverPosition->Get_Transform().Get_Translation();
  1027. Action_Attack_Object( soldier, enemy, weapon_range, CoverPosition->Get_Crouch(), cover_position, 0.5f );
  1028. CoveredAttack = false;
  1029. ActionTimer = 5; // for 3 seconds
  1030. done = true;
  1031. } else if ( FreeRandom.Get_Float() < Aggressiveness ) {
  1032. if ( soldier->Get_Weapon() != NULL ) {
  1033. /*
  1034. ** Start a covered attack.
  1035. ** Pick an attack point, and go there, attacking at the target
  1036. */
  1037. SubStateString = "Covered Attack";
  1038. Vector3 attack_point = CoverPosition->Get_Attack_Position( enemy_pos );
  1039. Action_Attack_Object( soldier, enemy, weapon_range, false, attack_point, 0.5f );
  1040. CoveredAttack = true;
  1041. ActionTimer = 5; // for 3 seconds
  1042. done = true;
  1043. }
  1044. }
  1045. }
  1046. if ( CoverPosition != NULL ) {
  1047. if ( !CoverManager::Is_Cover_Safe( CoverPosition, enemy_pos ) ) {
  1048. Release_Cover_Position();
  1049. CoveredAttack = false;
  1050. done = false;
  1051. }
  1052. }
  1053. float effective_range = 20;
  1054. if ( soldier->Get_Weapon() ) {
  1055. effective_range = soldier->Get_Weapon()->Get_Effective_Range();
  1056. }
  1057. if ( !done && ( FreeRandom.Get_Float() < TakeCoverProbability ) && !IsStationary ) { // 25% chance of not finding cover
  1058. Release_Cover_Position(); // Give us the option of selecting our current spot
  1059. CoverEntryClass * cover = CoverManager::Request_Cover(current_position, enemy_pos, effective_range);
  1060. if (cover != NULL) { // Yes, take it
  1061. SubStateString = "Take Cover";
  1062. CoverPosition = cover;
  1063. Vector3 cover_position = cover->Get_Transform().Get_Translation();
  1064. Action_Attack_Object( soldier, enemy, weapon_range, CoverPosition->Get_Crouch(), cover_position, 0.5f );
  1065. ActionTimer = 15;
  1066. done = true;
  1067. }
  1068. }
  1069. if ( FreeRandom.Get_Float() < Aggressiveness && !IsStationary ) { // Charge Attack
  1070. Vector3 pos;
  1071. enemy->Get_Position( &pos );
  1072. SubStateString = "Charge Attack";
  1073. ActionTimer = 10;
  1074. bool kneel = ( FreeRandom.Get_Float() < (1-Aggressiveness) );
  1075. Action_Attack_Object( soldier, enemy, weapon_range, kneel, pos, 5 );
  1076. done = true;
  1077. }
  1078. if ( !done ) {
  1079. Vector3 best_pos = current_position;
  1080. float best_distance = 100000;
  1081. for (int i = 0; i < 3; i++) {
  1082. // Lookup a safe random position to run to
  1083. Vector3 pos = current_position;
  1084. PathfindClass *pathfind = PathfindClass::Get_Instance();
  1085. if ( pathfind->Find_Random_Spot( current_position, effective_range*2, &pos ) ) {
  1086. float distance = (pos - enemy_pos).Length();
  1087. if (WWMath::Fabs(distance - effective_range) < best_distance) {
  1088. best_distance = WWMath::Fabs(distance - effective_range);
  1089. best_pos = pos;
  1090. }
  1091. }
  1092. }
  1093. if ( IsStationary ) {
  1094. best_pos = current_position;
  1095. }
  1096. SubStateString = "Random Run Attack";
  1097. ActionTimer = 10;
  1098. bool kneel = ( FreeRandom.Get_Float() < (1-Aggressiveness) );
  1099. Action_Attack_Object( soldier, enemy, weapon_range, kneel, best_pos, 0.5f );
  1100. }
  1101. }
  1102. /***********************************************************************************************
  1103. ** Innate AI Actions
  1104. ***********************************************************************************************/
  1105. void SoldierObserverClass::Action_Reset( SoldierGameObj * soldier )
  1106. {
  1107. WWASSERT( soldier != NULL );
  1108. soldier->Get_Action()->Reset( StatePriorities[ State ] );
  1109. }
  1110. void SoldierObserverClass::Action_Face_Location( SoldierGameObj * soldier, const Vector3 & location, SoldierAIState ai_state, bool crouched )
  1111. {
  1112. WWASSERT( soldier != NULL );
  1113. ActionParamsStruct parameters;
  1114. parameters.Set_Basic( Get_ID(), StatePriorities[ State ], INNATE_ACTION_ID, ai_state );
  1115. parameters.Set_Face_Location( location, 2 );
  1116. parameters.MoveCrouched = crouched;
  1117. soldier->Get_Action()->Face_Location( parameters );
  1118. }
  1119. void SoldierObserverClass::Action_Goto_Location( SoldierGameObj * soldier, const Vector3 & location, SoldierAIState ai_state, float speed, float distance, bool crouched )
  1120. {
  1121. WWASSERT( soldier != NULL );
  1122. Vector3 move_position = location;
  1123. Stay_Within_Home( soldier, &move_position, &distance );
  1124. ActionParamsStruct parameters;
  1125. parameters.Set_Basic( Get_ID(), StatePriorities[ State ], INNATE_ACTION_ID, ai_state );
  1126. parameters.Set_Movement( move_position, speed, distance, crouched );
  1127. soldier->Get_Action()->Goto( parameters );
  1128. }
  1129. void SoldierObserverClass::Action_Goto_Location_Facing( SoldierGameObj * soldier, const Vector3 & location, SoldierAIState ai_state, const Vector3 & facing_pos, float speed, float distance, bool crouched )
  1130. {
  1131. WWASSERT( soldier != NULL );
  1132. Vector3 move_position = location;
  1133. Stay_Within_Home( soldier, &move_position, &distance );
  1134. ActionParamsStruct parameters;
  1135. parameters.Set_Basic( Get_ID(), StatePriorities[ State ], INNATE_ACTION_ID, ai_state );
  1136. parameters.Set_Movement( move_position, speed, distance, crouched );
  1137. parameters.ForceFacing = true;
  1138. parameters.FaceLocation = facing_pos;
  1139. soldier->Get_Action()->Goto( parameters );
  1140. }
  1141. void SoldierObserverClass::Action_Attack_Object( SoldierGameObj * soldier, PhysicalGameObj * enemy, float range,
  1142. bool kneel, const Vector3 & move_location, float arrived_distance )
  1143. {
  1144. WWASSERT( soldier != NULL );
  1145. Vector3 move_position = move_location;
  1146. Stay_Within_Home( soldier, &move_position, &arrived_distance );
  1147. if ( IsStationary ) { // Don't move if stationary
  1148. arrived_distance = 10000;
  1149. }
  1150. if ( enemy != NULL && soldier->Get_Weapon() != NULL ) {
  1151. ActionParamsStruct parameters;
  1152. parameters.Set_Basic( Get_ID(), StatePriorities[ State ], INNATE_ACTION_ID, AI_STATE_COMBAT );
  1153. parameters.Set_Movement( move_position, RUN_SPEED, arrived_distance, kneel );
  1154. parameters.Set_Attack( enemy, range, FreeRandom.Get_Float(2), true );
  1155. parameters.AttackCrouched = kneel;
  1156. parameters.AttackWanderAllowed = !IsStationary;
  1157. // 50% chance of looking where you are running, for a bit
  1158. if ( FreeRandom.Get_Float() < 0.5 ) {
  1159. parameters.LookLocation = move_position + Vector3( 0,0,1 );
  1160. parameters.LookDuration = FreeRandom.Get_Float( 0.3f, 1 );
  1161. }
  1162. soldier->Get_Action()->Attack( parameters );
  1163. }
  1164. }
  1165. void SoldierObserverClass::Action_Dive( SoldierGameObj * soldier, const Vector3 & location )
  1166. {
  1167. WWASSERT( soldier != NULL );
  1168. ActionParamsStruct parameters;
  1169. parameters.Set_Basic( Get_ID(), StatePriorities[ State ], INNATE_ACTION_ID, AI_STATE_SEARCH );
  1170. parameters.MoveLocation = location;
  1171. soldier->Get_Action()->Dive( parameters );
  1172. }
  1173. void SoldierObserverClass::Stay_Within_Home( SoldierGameObj * soldier, Vector3 * location, float * distance )
  1174. {
  1175. // Whenever we move, if we are outside our home radius, override the move to move back inside
  1176. Vector3 home_diff;
  1177. soldier->Get_Position( &home_diff );
  1178. home_diff -= HomeLocation;
  1179. home_diff.Z = 0; // don't consider Z
  1180. if ( home_diff.Length() > HomeRadius ) {
  1181. *location = HomeLocation;
  1182. *distance = HomeRadius;
  1183. }
  1184. }
  1185. bool SoldierObserverClass::Take_Cover( SoldierGameObj * soldier, bool force_face, const Vector3 & face_pos )
  1186. {
  1187. if ( CoverPosition != NULL ) {
  1188. if ( !CoverManager::Is_Cover_Safe( CoverPosition, AlertPosition ) ) {
  1189. Release_Cover_Position();
  1190. }
  1191. }
  1192. // Possibily take a cover spot
  1193. if ( FreeRandom.Get_Float() < TakeCoverProbability ) {
  1194. Release_Cover_Position(); // Give us the option of selecting our current spot
  1195. float cover_range = 20;
  1196. Vector3 current_position;
  1197. soldier->Get_Position(&current_position);
  1198. CoverEntryClass * cover = CoverManager::Request_Cover(current_position, AlertPosition, cover_range); // Can we find a cover spot?
  1199. if (cover != NULL) { // Yes, take it
  1200. SubStateString = "Take Cover";
  1201. CoverPosition = cover;
  1202. Vector3 cover_position = cover->Get_Transform().Get_Translation();
  1203. if ( force_face ) {
  1204. Action_Goto_Location_Facing(soldier, cover_position, AI_STATE_SEARCH, face_pos, ALERTED_RUN_SPEED, 0.5, cover->Get_Crouch() );
  1205. } else {
  1206. Action_Goto_Location(soldier, cover_position, AI_STATE_SEARCH, ALERTED_RUN_SPEED, 0.5, cover->Get_Crouch() );
  1207. }
  1208. return true;
  1209. }
  1210. }
  1211. return false;
  1212. }
  1213. void SoldierObserverClass::Look_Random( SoldierGameObj * soldier, float duration )
  1214. {
  1215. soldier->Look_Random( duration );
  1216. }